mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: add Current Editor Context option to @ context menu
- Add EditorContext to ContextMenuOptionType enum - Update WebviewMessage types to support requestEditorContext and editorContext - Implement backend handler in webviewMessageHandler.ts using EditorUtils.getEditorContext() - Update ExtensionMessage interface to include editorContext field - Add Current Editor Context option to context menu with edit icon - Implement ChatTextArea handling for editor context selection and formatting - Update tests to reflect new context menu option count - Fix React Hook dependencies for proper linting The feature allows users to type @ in chat, select 'Current Editor Context' to insert a formatted mention containing current file context, line numbers, and selected text from the active editor.
This commit is contained in:
parent
b03d03d860
commit
c813feac55
7 changed files with 118 additions and 18 deletions
|
|
@ -2195,6 +2195,26 @@ export const webviewMessageHandler = async (
|
|||
break
|
||||
}
|
||||
|
||||
case "requestEditorContext": {
|
||||
try {
|
||||
const { EditorUtils } = await import("../../integrations/editor/EditorUtils")
|
||||
const editorContext = await EditorUtils.getEditorContext()
|
||||
|
||||
await provider.postMessageToWebview({
|
||||
type: "editorContext",
|
||||
editorContext: editorContext || undefined,
|
||||
})
|
||||
} catch (error) {
|
||||
provider.log(
|
||||
`Error getting editor context: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
|
||||
)
|
||||
await provider.postMessageToWebview({
|
||||
type: "editorContext",
|
||||
editorContext: undefined,
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
case "switchTab": {
|
||||
if (message.tab) {
|
||||
// Capture tab shown event for all switchTab messages (which are user-initiated)
|
||||
|
|
|
|||
|
|
@ -105,8 +105,16 @@ export interface ExtensionMessage {
|
|||
| "shareTaskSuccess"
|
||||
| "codeIndexSettingsSaved"
|
||||
| "codeIndexSecretStatus"
|
||||
| "editorContext"
|
||||
text?: string
|
||||
payload?: any // Add a generic payload for now, can refine later
|
||||
editorContext?: {
|
||||
filePath?: string
|
||||
selectedText?: string
|
||||
startLine?: number
|
||||
endLine?: number
|
||||
diagnostics?: any[]
|
||||
}
|
||||
action?:
|
||||
| "chatButtonClicked"
|
||||
| "mcpButtonClicked"
|
||||
|
|
|
|||
|
|
@ -193,6 +193,8 @@ export interface WebviewMessage {
|
|||
| "checkRulesDirectoryResult"
|
||||
| "saveCodeIndexSettingsAtomic"
|
||||
| "requestCodeIndexSecretStatus"
|
||||
| "requestEditorContext"
|
||||
| "editorContext"
|
||||
text?: string
|
||||
editedMessageContent?: string
|
||||
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account"
|
||||
|
|
@ -252,6 +254,13 @@ export interface WebviewMessage {
|
|||
codebaseIndexOpenAiCompatibleApiKey?: string
|
||||
codebaseIndexGeminiApiKey?: string
|
||||
}
|
||||
editorContext?: {
|
||||
filePath?: string
|
||||
selectedText?: string
|
||||
startLine?: number
|
||||
endLine?: number
|
||||
diagnostics?: any[]
|
||||
}
|
||||
}
|
||||
|
||||
export const checkoutDiffPayloadSchema = z.object({
|
||||
|
|
|
|||
|
|
@ -96,6 +96,21 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
const [fileSearchResults, setFileSearchResults] = useState<SearchResult[]>([])
|
||||
const [searchLoading, setSearchLoading] = useState(false)
|
||||
const [searchRequestId, setSearchRequestId] = useState<string>("")
|
||||
const [isDraggingOver, setIsDraggingOver] = useState(false)
|
||||
const [textAreaBaseHeight, setTextAreaBaseHeight] = useState<number | undefined>(undefined)
|
||||
const [showContextMenu, setShowContextMenu] = useState(false)
|
||||
const [cursorPosition, setCursorPosition] = useState(0)
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const textAreaRef = useRef<HTMLTextAreaElement | null>(null)
|
||||
const [isMouseDownOnMenu, setIsMouseDownOnMenu] = useState(false)
|
||||
const highlightLayerRef = useRef<HTMLDivElement>(null)
|
||||
const [selectedMenuIndex, setSelectedMenuIndex] = useState(-1)
|
||||
const [selectedType, setSelectedType] = useState<ContextMenuOptionType | null>(null)
|
||||
const [justDeletedSpaceAfterMention, setJustDeletedSpaceAfterMention] = useState(false)
|
||||
const [intendedCursorPosition, setIntendedCursorPosition] = useState<number | null>(null)
|
||||
const contextMenuContainerRef = useRef<HTMLDivElement>(null)
|
||||
const [isEnhancingPrompt, setIsEnhancingPrompt] = useState(false)
|
||||
const [isFocused, setIsFocused] = useState(false)
|
||||
|
||||
// Close dropdown when clicking outside.
|
||||
useEffect(() => {
|
||||
|
|
@ -135,28 +150,63 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
if (message.requestId === searchRequestId) {
|
||||
setFileSearchResults(message.results || [])
|
||||
}
|
||||
} else if (message.type === "editorContext") {
|
||||
// Handle editor context response
|
||||
if (message.editorContext && textAreaRef.current) {
|
||||
const editorContext = message.editorContext
|
||||
let insertValue = ""
|
||||
|
||||
if (editorContext.filePath) {
|
||||
// Format: filename:startLine-endLine (selected text preview)
|
||||
const fileName = editorContext.filePath.split("/").pop() || editorContext.filePath
|
||||
let contextText = fileName
|
||||
|
||||
if (editorContext.startLine !== undefined) {
|
||||
if (
|
||||
editorContext.endLine !== undefined &&
|
||||
editorContext.endLine !== editorContext.startLine
|
||||
) {
|
||||
contextText += `:${editorContext.startLine}-${editorContext.endLine}`
|
||||
} else {
|
||||
contextText += `:${editorContext.startLine}`
|
||||
}
|
||||
}
|
||||
|
||||
if (editorContext.selectedText && editorContext.selectedText.trim()) {
|
||||
const preview = editorContext.selectedText.trim().substring(0, 50)
|
||||
contextText += ` (${preview}${editorContext.selectedText.length > 50 ? "..." : ""})`
|
||||
}
|
||||
|
||||
insertValue = contextText
|
||||
} else {
|
||||
insertValue = "current-editor"
|
||||
}
|
||||
|
||||
const { newValue, mentionIndex } = insertMention(
|
||||
textAreaRef.current.value,
|
||||
cursorPosition,
|
||||
insertValue,
|
||||
)
|
||||
|
||||
setInputValue(newValue)
|
||||
const newCursorPosition = newValue.indexOf(" ", mentionIndex + insertValue.length) + 1
|
||||
setCursorPosition(newCursorPosition)
|
||||
setIntendedCursorPosition(newCursorPosition)
|
||||
|
||||
// Scroll to cursor
|
||||
setTimeout(() => {
|
||||
if (textAreaRef.current) {
|
||||
textAreaRef.current.blur()
|
||||
textAreaRef.current.focus()
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("message", messageHandler)
|
||||
return () => window.removeEventListener("message", messageHandler)
|
||||
}, [setInputValue, searchRequestId])
|
||||
|
||||
const [isDraggingOver, setIsDraggingOver] = useState(false)
|
||||
const [textAreaBaseHeight, setTextAreaBaseHeight] = useState<number | undefined>(undefined)
|
||||
const [showContextMenu, setShowContextMenu] = useState(false)
|
||||
const [cursorPosition, setCursorPosition] = useState(0)
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const textAreaRef = useRef<HTMLTextAreaElement | null>(null)
|
||||
const [isMouseDownOnMenu, setIsMouseDownOnMenu] = useState(false)
|
||||
const highlightLayerRef = useRef<HTMLDivElement>(null)
|
||||
const [selectedMenuIndex, setSelectedMenuIndex] = useState(-1)
|
||||
const [selectedType, setSelectedType] = useState<ContextMenuOptionType | null>(null)
|
||||
const [justDeletedSpaceAfterMention, setJustDeletedSpaceAfterMention] = useState(false)
|
||||
const [intendedCursorPosition, setIntendedCursorPosition] = useState<number | null>(null)
|
||||
const contextMenuContainerRef = useRef<HTMLDivElement>(null)
|
||||
const [isEnhancingPrompt, setIsEnhancingPrompt] = useState(false)
|
||||
const [isFocused, setIsFocused] = useState(false)
|
||||
}, [setInputValue, searchRequestId, cursorPosition])
|
||||
|
||||
// Use custom hook for prompt history navigation
|
||||
const { handleHistoryNavigation, resetHistoryNavigation, resetOnInputChange } = usePromptHistory({
|
||||
|
|
@ -277,6 +327,12 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
insertValue = "problems"
|
||||
} else if (type === ContextMenuOptionType.Terminal) {
|
||||
insertValue = "terminal"
|
||||
} else if (type === ContextMenuOptionType.EditorContext) {
|
||||
// Request editor context from backend
|
||||
vscode.postMessage({ type: "requestEditorContext" })
|
||||
setShowContextMenu(false)
|
||||
setSelectedType(null)
|
||||
return
|
||||
} else if (type === ContextMenuOptionType.Git) {
|
||||
insertValue = value || ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,6 +91,8 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
|
|||
return <span>Problems</span>
|
||||
case ContextMenuOptionType.Terminal:
|
||||
return <span>Terminal</span>
|
||||
case ContextMenuOptionType.EditorContext:
|
||||
return <span>Current Editor Context</span>
|
||||
case ContextMenuOptionType.URL:
|
||||
return <span>Paste URL to fetch contents</span>
|
||||
case ContextMenuOptionType.NoResults:
|
||||
|
|
@ -173,6 +175,8 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
|
|||
return "warning"
|
||||
case ContextMenuOptionType.Terminal:
|
||||
return "terminal"
|
||||
case ContextMenuOptionType.EditorContext:
|
||||
return "edit"
|
||||
case ContextMenuOptionType.URL:
|
||||
return "link"
|
||||
case ContextMenuOptionType.Git:
|
||||
|
|
|
|||
|
|
@ -196,8 +196,9 @@ describe("getContextMenuOptions", () => {
|
|||
|
||||
it("should return all option types for empty query", () => {
|
||||
const result = getContextMenuOptions("", "", null, [])
|
||||
expect(result).toHaveLength(6)
|
||||
expect(result).toHaveLength(7)
|
||||
expect(result.map((item) => item.type)).toEqual([
|
||||
ContextMenuOptionType.EditorContext,
|
||||
ContextMenuOptionType.Problems,
|
||||
ContextMenuOptionType.Terminal,
|
||||
ContextMenuOptionType.URL,
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ export enum ContextMenuOptionType {
|
|||
Git = "git",
|
||||
NoResults = "noResults",
|
||||
Mode = "mode", // Add mode type
|
||||
EditorContext = "editorContext", // Add editor context type
|
||||
}
|
||||
|
||||
export interface ContextMenuQueryItem {
|
||||
|
|
@ -192,6 +193,7 @@ export function getContextMenuOptions(
|
|||
}
|
||||
|
||||
return [
|
||||
{ type: ContextMenuOptionType.EditorContext },
|
||||
{ type: ContextMenuOptionType.Problems },
|
||||
{ type: ContextMenuOptionType.Terminal },
|
||||
{ type: ContextMenuOptionType.URL },
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue