mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Use autocomplete instead for switching modes
This commit is contained in:
parent
5297cad5e2
commit
ebd9084e56
4 changed files with 127 additions and 21 deletions
|
|
@ -179,6 +179,18 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
return
|
||||
}
|
||||
|
||||
if (type === ContextMenuOptionType.Mode && value) {
|
||||
// Handle mode selection
|
||||
setMode(value)
|
||||
setInputValue("")
|
||||
setShowContextMenu(false)
|
||||
vscode.postMessage({
|
||||
type: "mode",
|
||||
text: value,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
type === ContextMenuOptionType.File ||
|
||||
type === ContextMenuOptionType.Folder ||
|
||||
|
|
@ -242,7 +254,12 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
event.preventDefault()
|
||||
setSelectedMenuIndex((prevIndex) => {
|
||||
const direction = event.key === "ArrowUp" ? -1 : 1
|
||||
const options = getContextMenuOptions(searchQuery, selectedType, queryItems)
|
||||
const options = getContextMenuOptions(
|
||||
searchQuery,
|
||||
selectedType,
|
||||
queryItems,
|
||||
getAllModes(customModes),
|
||||
)
|
||||
const optionsLength = options.length
|
||||
|
||||
if (optionsLength === 0) return prevIndex
|
||||
|
|
@ -272,9 +289,12 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
}
|
||||
if ((event.key === "Enter" || event.key === "Tab") && selectedMenuIndex !== -1) {
|
||||
event.preventDefault()
|
||||
const selectedOption = getContextMenuOptions(searchQuery, selectedType, queryItems)[
|
||||
selectedMenuIndex
|
||||
]
|
||||
const selectedOption = getContextMenuOptions(
|
||||
searchQuery,
|
||||
selectedType,
|
||||
queryItems,
|
||||
getAllModes(customModes),
|
||||
)[selectedMenuIndex]
|
||||
if (
|
||||
selectedOption &&
|
||||
selectedOption.type !== ContextMenuOptionType.URL &&
|
||||
|
|
@ -340,6 +360,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
setInputValue,
|
||||
justDeletedSpaceAfterMention,
|
||||
queryItems,
|
||||
customModes,
|
||||
],
|
||||
)
|
||||
|
||||
|
|
@ -360,13 +381,21 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
|
||||
setShowContextMenu(showMenu)
|
||||
if (showMenu) {
|
||||
const lastAtIndex = newValue.lastIndexOf("@", newCursorPosition - 1)
|
||||
const query = newValue.slice(lastAtIndex + 1, newCursorPosition)
|
||||
setSearchQuery(query)
|
||||
if (query.length > 0) {
|
||||
if (newValue.startsWith("/")) {
|
||||
// Handle slash command
|
||||
const query = newValue
|
||||
setSearchQuery(query)
|
||||
setSelectedMenuIndex(0)
|
||||
} else {
|
||||
setSelectedMenuIndex(3) // Set to "File" option by default
|
||||
// Existing @ mention handling
|
||||
const lastAtIndex = newValue.lastIndexOf("@", newCursorPosition - 1)
|
||||
const query = newValue.slice(lastAtIndex + 1, newCursorPosition)
|
||||
setSearchQuery(query)
|
||||
if (query.length > 0) {
|
||||
setSelectedMenuIndex(0)
|
||||
} else {
|
||||
setSelectedMenuIndex(3) // Set to "File" option by default
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setSearchQuery("")
|
||||
|
|
@ -614,6 +643,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
setSelectedIndex={setSelectedMenuIndex}
|
||||
selectedType={selectedType}
|
||||
queryItems={queryItems}
|
||||
modes={getAllModes(customModes)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -878,7 +878,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||
|
||||
const placeholderText = useMemo(() => {
|
||||
const baseText = task ? "Type a message..." : "Type your task here..."
|
||||
const contextText = "(@ to add context"
|
||||
const contextText = "(@ to add context, / to switch modes"
|
||||
const imageText = shouldDisableImages ? "" : ", hold shift to drag in images"
|
||||
const helpText = imageText ? `\n${contextText}${imageText})` : `\n${contextText})`
|
||||
return baseText + helpText
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useEffect, useMemo, useRef } from "react"
|
||||
import { ContextMenuOptionType, ContextMenuQueryItem, getContextMenuOptions } from "../../utils/context-mentions"
|
||||
import { removeLeadingNonAlphanumeric } from "../common/CodeAccordian"
|
||||
import { ModeConfig } from "../../../../src/shared/modes"
|
||||
|
||||
interface ContextMenuProps {
|
||||
onSelect: (type: ContextMenuOptionType, value?: string) => void
|
||||
|
|
@ -10,6 +11,7 @@ interface ContextMenuProps {
|
|||
setSelectedIndex: (index: number) => void
|
||||
selectedType: ContextMenuOptionType | null
|
||||
queryItems: ContextMenuQueryItem[]
|
||||
modes?: ModeConfig[]
|
||||
}
|
||||
|
||||
const ContextMenu: React.FC<ContextMenuProps> = ({
|
||||
|
|
@ -20,12 +22,13 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
|
|||
setSelectedIndex,
|
||||
selectedType,
|
||||
queryItems,
|
||||
modes,
|
||||
}) => {
|
||||
const menuRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const filteredOptions = useMemo(
|
||||
() => getContextMenuOptions(searchQuery, selectedType, queryItems),
|
||||
[searchQuery, selectedType, queryItems],
|
||||
() => getContextMenuOptions(searchQuery, selectedType, queryItems, modes),
|
||||
[searchQuery, selectedType, queryItems, modes],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -46,6 +49,25 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
|
|||
|
||||
const renderOptionContent = (option: ContextMenuQueryItem) => {
|
||||
switch (option.type) {
|
||||
case ContextMenuOptionType.Mode:
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "2px" }}>
|
||||
<span style={{ lineHeight: "1.2" }}>{option.label}</span>
|
||||
{option.description && (
|
||||
<span
|
||||
style={{
|
||||
opacity: 0.5,
|
||||
fontSize: "0.9em",
|
||||
lineHeight: "1.2",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
}}>
|
||||
{option.description}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
case ContextMenuOptionType.Problems:
|
||||
return <span>Problems</span>
|
||||
case ContextMenuOptionType.URL:
|
||||
|
|
@ -101,6 +123,8 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
|
|||
|
||||
const getIconForOption = (option: ContextMenuQueryItem): string => {
|
||||
switch (option.type) {
|
||||
case ContextMenuOptionType.Mode:
|
||||
return "symbol-misc"
|
||||
case ContextMenuOptionType.OpenedFile:
|
||||
return "window"
|
||||
case ContextMenuOptionType.File:
|
||||
|
|
@ -174,15 +198,17 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
|
|||
overflow: "hidden",
|
||||
paddingTop: 0,
|
||||
}}>
|
||||
<i
|
||||
className={`codicon codicon-${getIconForOption(option)}`}
|
||||
style={{
|
||||
marginRight: "6px",
|
||||
flexShrink: 0,
|
||||
fontSize: "14px",
|
||||
marginTop: 0,
|
||||
}}
|
||||
/>
|
||||
{option.type !== ContextMenuOptionType.Mode && getIconForOption(option) && (
|
||||
<i
|
||||
className={`codicon codicon-${getIconForOption(option)}`}
|
||||
style={{
|
||||
marginRight: "6px",
|
||||
flexShrink: 0,
|
||||
fontSize: "14px",
|
||||
marginTop: 0,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{renderOptionContent(option)}
|
||||
</div>
|
||||
{(option.type === ContextMenuOptionType.File ||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,20 @@
|
|||
import { mentionRegex } from "../../../src/shared/context-mentions"
|
||||
import { Fzf } from "fzf"
|
||||
import { ModeConfig } from "../../../src/shared/modes"
|
||||
|
||||
export function insertMention(
|
||||
text: string,
|
||||
position: number,
|
||||
value: string,
|
||||
): { newValue: string; mentionIndex: number } {
|
||||
// Handle slash command
|
||||
if (text.startsWith("/")) {
|
||||
return {
|
||||
newValue: value,
|
||||
mentionIndex: 0,
|
||||
}
|
||||
}
|
||||
|
||||
const beforeCursor = text.slice(0, position)
|
||||
const afterCursor = text.slice(position)
|
||||
|
||||
|
|
@ -55,6 +64,7 @@ export enum ContextMenuOptionType {
|
|||
URL = "url",
|
||||
Git = "git",
|
||||
NoResults = "noResults",
|
||||
Mode = "mode", // Add mode type
|
||||
}
|
||||
|
||||
export interface ContextMenuQueryItem {
|
||||
|
|
@ -69,7 +79,42 @@ export function getContextMenuOptions(
|
|||
query: string,
|
||||
selectedType: ContextMenuOptionType | null = null,
|
||||
queryItems: ContextMenuQueryItem[],
|
||||
modes?: ModeConfig[],
|
||||
): ContextMenuQueryItem[] {
|
||||
// Handle slash commands for modes
|
||||
if (query.startsWith("/")) {
|
||||
const modeQuery = query.slice(1)
|
||||
if (!modes?.length) return [{ type: ContextMenuOptionType.NoResults }]
|
||||
|
||||
// Create searchable strings array for fzf
|
||||
const searchableItems = modes.map((mode) => ({
|
||||
original: mode,
|
||||
searchStr: mode.name,
|
||||
}))
|
||||
|
||||
// Initialize fzf instance for fuzzy search
|
||||
const fzf = new Fzf(searchableItems, {
|
||||
selector: (item) => item.searchStr,
|
||||
})
|
||||
|
||||
// Get fuzzy matching items
|
||||
const matchingModes = modeQuery
|
||||
? fzf.find(modeQuery).map((result) => ({
|
||||
type: ContextMenuOptionType.Mode,
|
||||
value: result.item.original.slug,
|
||||
label: result.item.original.name,
|
||||
description: result.item.original.roleDefinition.split("\n")[0],
|
||||
}))
|
||||
: modes.map((mode) => ({
|
||||
type: ContextMenuOptionType.Mode,
|
||||
value: mode.slug,
|
||||
label: mode.name,
|
||||
description: mode.roleDefinition.split("\n")[0],
|
||||
}))
|
||||
|
||||
return matchingModes.length > 0 ? matchingModes : [{ type: ContextMenuOptionType.NoResults }]
|
||||
}
|
||||
|
||||
const workingChanges: ContextMenuQueryItem = {
|
||||
type: ContextMenuOptionType.Git,
|
||||
value: "git-changes",
|
||||
|
|
@ -203,6 +248,11 @@ export function getContextMenuOptions(
|
|||
}
|
||||
|
||||
export function shouldShowContextMenu(text: string, position: number): boolean {
|
||||
// Handle slash command
|
||||
if (text.startsWith("/")) {
|
||||
return position <= text.length && !text.includes(" ")
|
||||
}
|
||||
|
||||
const beforeCursor = text.slice(0, position)
|
||||
const atIndex = beforeCursor.lastIndexOf("@")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue