From 3c7a510c2158272ae83f680206604957a246a722 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Thu, 13 Nov 2025 03:02:37 +0000 Subject: [PATCH] refactor: move selection capture from webview to extension - Remove selection context state management from ChatView - Remove requestSelectionContext message type and handler - Extension now captures selection directly in newTask and askResponse handlers - Eliminates race condition between selection request and message send - Simplifies architecture by removing unnecessary webview complexity --- src/core/webview/webviewMessageHandler.ts | 103 ++++++++------------ src/shared/ExtensionMessage.ts | 5 - src/shared/WebviewMessage.ts | 5 - webview-ui/src/components/chat/ChatView.tsx | 37 +------ 4 files changed, 46 insertions(+), 104 deletions(-) diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 3a840d8836..bc93fbca42 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -427,45 +427,46 @@ export const webviewMessageHandler = async ( } } - switch (message.type) { - case "requestSelectionContext": { - // Get the active editor and its selection - const editor = vscode.window.activeTextEditor - if (editor && !editor.selection.isEmpty) { - const selection = editor.selection - const selectedText = editor.document.getText(selection) - const filePath = editor.document.uri.fsPath + // Helper function to capture current editor selection + const captureSelectionContext = (): + | { + selectedText: string + selectionFilePath: string + selectionStartLine: number + selectionEndLine: number + } + | undefined => { + const editor = vscode.window.activeTextEditor + if (editor && !editor.selection.isEmpty) { + const selection = editor.selection + const selectedText = editor.document.getText(selection) + const filePath = editor.document.uri.fsPath - // Convert to workspace-relative path if possible - const workspacePath = provider.cwd - let relativeFilePath: string - if (filePath.startsWith(workspacePath)) { - relativeFilePath = path.relative(workspacePath, filePath) - } else { - // File is outside workspace, use absolute path - relativeFilePath = filePath - } - - // VSCode uses 0-based line numbers, convert to 1-based for user-friendly display - const startLine = selection.start.line + 1 - const endLine = selection.end.line + 1 - - // Send selection context to webview only - don't store in task - await provider.postMessageToWebview({ - type: "selectionContext", - selectedText, - selectionFilePath: relativeFilePath, - selectionStartLine: startLine, - selectionEndLine: endLine, - }) + // Convert to workspace-relative path if possible + const workspacePath = provider.cwd + let relativeFilePath: string + if (filePath.startsWith(workspacePath)) { + relativeFilePath = path.relative(workspacePath, filePath) } else { - // No selection, send empty context - await provider.postMessageToWebview({ - type: "selectionContext", - }) + // File is outside workspace, use absolute path + relativeFilePath = filePath + } + + // VSCode uses 0-based line numbers, convert to 1-based for user-friendly display + const startLine = selection.start.line + 1 + const endLine = selection.end.line + 1 + + return { + selectedText, + selectionFilePath: relativeFilePath, + selectionStartLine: startLine, + selectionEndLine: endLine, } - break } + return undefined + } + + switch (message.type) { case "webviewDidLaunch": // Load custom modes first const customModes = await provider.customModesManager.getCustomModes() @@ -545,19 +546,11 @@ export const webviewMessageHandler = async ( // agentically running promises in old instance don't affect our new // task. This essentially creates a fresh slate for the new task. try { + // Capture selection context directly when creating task + const selectionContext = captureSelectionContext() + await provider.createTask(message.text, message.images, undefined, { - selectionContext: - message.selectedText && - message.selectionFilePath && - typeof message.selectionStartLine === "number" && - typeof message.selectionEndLine === "number" - ? { - selectedText: message.selectedText, - selectionFilePath: message.selectionFilePath, - selectionStartLine: message.selectionStartLine, - selectionEndLine: message.selectionEndLine, - } - : undefined, + selectionContext, }) // Task created successfully - notify the UI to reset @@ -577,19 +570,9 @@ export const webviewMessageHandler = async ( case "askResponse": { const task = provider.getCurrentTask() - // Pass selection context through to handleWebviewAskResponse - const selectionContext = - message.selectedText && - message.selectionFilePath && - typeof message.selectionStartLine === "number" && - typeof message.selectionEndLine === "number" - ? { - selectedText: message.selectedText, - selectionFilePath: message.selectionFilePath, - selectionStartLine: message.selectionStartLine, - selectionEndLine: message.selectionEndLine, - } - : undefined + // Capture selection context directly when handling response + const selectionContext = captureSelectionContext() + task?.handleWebviewAskResponse(message.askResponse!, message.text, message.images, selectionContext) break } diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index c8969842c0..80c5532930 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -128,12 +128,7 @@ export interface ExtensionMessage { | "dismissedUpsells" | "organizationSwitchResult" | "interactionRequired" - | "selectionContext" text?: string - selectedText?: string - selectionFilePath?: string - selectionStartLine?: number - selectionEndLine?: number payload?: any // Add a generic payload for now, can refine later // Checkpoint warning message checkpointWarning?: { diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 64eeeb37a8..02f0876ad3 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -165,12 +165,7 @@ export interface WebviewMessage { | "dismissUpsell" | "getDismissedUpsells" | "updateSettings" - | "requestSelectionContext" text?: string - selectedText?: string - selectionFilePath?: string - selectionStartLine?: number - selectionEndLine?: number editedMessageContent?: string tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "cloud" disabled?: boolean diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index d728b85104..9402746ee8 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -134,13 +134,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction(null) const [sendingDisabled, setSendingDisabled] = useState(false) const [selectedImages, setSelectedImages] = useState([]) - const [selectionContext, setSelectionContext] = useState<{ - selectedText?: string - selectionFilePath?: string - selectionStartLine?: number - selectionEndLine?: number - } | null>(null) - // We need to hold on to the ask because useEffect > lastMessage will always // let us know when an ask comes in and handle it, but by the time // handleMessage is called, the last message might not be the ask anymore @@ -568,16 +561,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction