Skip to content

Commit 746921f

Browse files
committed
fix: Tab drills into folder without closing picker; Enter selects folder (#8076)
1 parent 8a6bdf9 commit 746921f

File tree

1 file changed

+68
-4
lines changed

1 file changed

+68
-4
lines changed

webview-ui/src/components/chat/ChatTextArea.tsx

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
SearchResult,
2121
} from "@src/utils/context-mentions"
2222
import { cn } from "@src/lib/utils"
23-
import { convertToMentionPath } from "@src/utils/path-mentions"
23+
import { convertToMentionPath, escapeSpaces } from "@src/utils/path-mentions"
2424
import { StandardTooltip } from "@src/components/ui"
2525

2626
import Thumbnails from "../common/Thumbnails"
@@ -293,7 +293,7 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
293293
}, [showContextMenu, setShowContextMenu])
294294

295295
const handleMentionSelect = useCallback(
296-
(type: ContextMenuOptionType, value?: string) => {
296+
(type: ContextMenuOptionType, value?: string, trigger?: "Enter" | "Tab" | "Click") => {
297297
if (type === ContextMenuOptionType.NoResults) {
298298
return
299299
}
@@ -341,6 +341,69 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
341341
}
342342
}
343343

344+
// Special handling for folder drill-in via Tab only: keep picker open and do not add trailing space
345+
if (type === ContextMenuOptionType.Folder && value && textAreaRef.current && trigger === "Tab") {
346+
// Ensure trailing slash and escape spaces like insertMention does
347+
let insertValue = value.endsWith("/") ? value : value + "/"
348+
if (insertValue.startsWith("/") && insertValue.includes(" ") && !insertValue.includes("\\ ")) {
349+
insertValue = escapeSpaces(insertValue)
350+
}
351+
352+
const text = textAreaRef.current.value
353+
const position = cursorPosition
354+
const beforeCursor = text.slice(0, position)
355+
const afterCursor = text.slice(position)
356+
const lastAtIndex = beforeCursor.lastIndexOf("@")
357+
358+
// Mirror insertMention behavior for replacing after '@' without adding a trailing space
359+
const afterCursorContent = /^[a-zA-Z0-9\s]*$/.test(afterCursor)
360+
? afterCursor.replace(/^[^\s]*/, "")
361+
: afterCursor
362+
363+
let newValue: string
364+
let mentionIndex: number
365+
366+
if (lastAtIndex !== -1) {
367+
const beforeMention = text.slice(0, lastAtIndex)
368+
newValue = beforeMention + "@" + insertValue + afterCursorContent
369+
mentionIndex = lastAtIndex
370+
} else {
371+
newValue = beforeCursor + "@" + insertValue + afterCursor
372+
mentionIndex = position
373+
}
374+
375+
setInputValue(newValue)
376+
377+
// Place caret right after the inserted folder path (after trailing slash)
378+
const newCursorPos = mentionIndex + 1 + insertValue.length
379+
setCursorPosition(newCursorPos)
380+
setIntendedCursorPosition(newCursorPos)
381+
382+
// Keep the context menu open and immediately search within the folder
383+
setShowContextMenu(true)
384+
setSelectedType(null)
385+
const folderQuery = insertValue.replace(/^\/+/, "")
386+
setSearchQuery(folderQuery)
387+
setSelectedMenuIndex(0)
388+
389+
// Trigger immediate search (no debounce) to repopulate with folder children
390+
const reqId = Math.random().toString(36).substring(2, 9)
391+
setSearchRequestId(reqId)
392+
setSearchLoading(true)
393+
vscode.postMessage({
394+
type: "searchFiles",
395+
query: unescapeSpaces(folderQuery),
396+
requestId: reqId,
397+
})
398+
399+
// Position caret without blurring to avoid closing the menu
400+
if (textAreaRef.current) {
401+
textAreaRef.current.setSelectionRange(newCursorPos, newCursorPos)
402+
}
403+
404+
return
405+
}
406+
344407
setShowContextMenu(false)
345408
setSelectedType(null)
346409

@@ -454,7 +517,7 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
454517
selectedOption.type !== ContextMenuOptionType.NoResults &&
455518
selectedOption.type !== ContextMenuOptionType.SectionHeader
456519
) {
457-
handleMentionSelect(selectedOption.type, selectedOption.value)
520+
handleMentionSelect(selectedOption.type, selectedOption.value, event.key as "Enter" | "Tab")
458521
}
459522
return
460523
}
@@ -575,7 +638,8 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
575638
} else {
576639
// Existing @ mention handling.
577640
const lastAtIndex = newValue.lastIndexOf("@", newCursorPosition - 1)
578-
const query = newValue.slice(lastAtIndex + 1, newCursorPosition)
641+
const rawQuery = newValue.slice(lastAtIndex + 1, newCursorPosition)
642+
const query = rawQuery.startsWith("/") ? rawQuery.slice(1) : rawQuery
579643
setSearchQuery(query)
580644

581645
// Send file search request if query is not empty.

0 commit comments

Comments
 (0)