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
This commit is contained in:
Roo Code 2025-11-13 03:02:37 +00:00
parent 338d8d01a3
commit 3c7a510c21
4 changed files with 46 additions and 104 deletions

View file

@ -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
}

View file

@ -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?: {

View file

@ -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

View file

@ -134,13 +134,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
const textAreaRef = useRef<HTMLTextAreaElement>(null)
const [sendingDisabled, setSendingDisabled] = useState(false)
const [selectedImages, setSelectedImages] = useState<string[]>([])
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<ChatViewRef, ChatViewPro
// Mark that user has responded - this prevents any pending auto-approvals.
userRespondedRef.current = true
// Request fresh selection context before sending
vscode.postMessage({ type: "requestSelectionContext" })
if (messagesRef.current.length === 0) {
vscode.postMessage({
type: "newTask",
text,
images,
...(selectionContext || {}),
})
vscode.postMessage({ type: "newTask", text, images })
} else if (clineAskRef.current) {
if (clineAskRef.current === "followup") {
markFollowUpAsAnswered()
@ -602,26 +587,19 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
askResponse: "messageResponse",
text,
images,
...(selectionContext || {}),
})
break
// There is no other case that a textfield should be enabled.
}
} else {
// This is a new message in an ongoing task.
vscode.postMessage({
type: "askResponse",
askResponse: "messageResponse",
text,
images,
...(selectionContext || {}),
})
vscode.postMessage({ type: "askResponse", askResponse: "messageResponse", text, images })
}
handleChatReset()
}
},
[handleChatReset, markFollowUpAsAnswered, sendingDisabled, isStreaming, messageQueue.length, selectionContext], // messagesRef and clineAskRef are stable
[handleChatReset, markFollowUpAsAnswered, sendingDisabled, isStreaming, messageQueue.length], // messagesRef and clineAskRef are stable
)
const handleSetChatBoxMessage = useCallback(
@ -764,15 +742,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
break
}
break
case "selectionContext":
// Update selection context when received from extension
setSelectionContext({
selectedText: message.selectedText,
selectionFilePath: message.selectionFilePath,
selectionStartLine: message.selectionStartLine,
selectionEndLine: message.selectionEndLine,
})
break
case "selectedImages":
// Only handle selectedImages if it's not for editing context
// When context is "edit", ChatRow will handle the images