Skip to content

Commit 8ea2aa4

Browse files
committed
fix: resolve linting errors in ChatView and QueuedMessages
- Remove unused startTransition import from ChatView - Fix React hooks order violations in QueuedMessages by moving all hooks before conditional returns - Prefix unused parameter with underscore to satisfy linting rules
1 parent ded23b7 commit 8ea2aa4

File tree

4 files changed

+508
-352
lines changed

4 files changed

+508
-352
lines changed

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

Lines changed: 96 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -560,59 +560,62 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
560560
const newCursorPosition = e.target.selectionStart
561561
setCursorPosition(newCursorPosition)
562562

563-
const showMenu = shouldShowContextMenu(newValue, newCursorPosition)
564-
setShowContextMenu(showMenu)
565-
566-
if (showMenu) {
567-
if (newValue.startsWith("/") && !newValue.includes(" ")) {
568-
// Handle slash command - request fresh commands
569-
const query = newValue
570-
setSearchQuery(query)
571-
// Set to first selectable item (skip section headers)
572-
setSelectedMenuIndex(1) // Section header is at 0, first command is at 1
573-
// Request commands fresh each time slash menu is shown
574-
vscode.postMessage({ type: "requestCommands" })
575-
} else {
576-
// Existing @ mention handling.
577-
const lastAtIndex = newValue.lastIndexOf("@", newCursorPosition - 1)
578-
const query = newValue.slice(lastAtIndex + 1, newCursorPosition)
579-
setSearchQuery(query)
563+
// Defer context menu logic to avoid blocking input
564+
requestAnimationFrame(() => {
565+
const showMenu = shouldShowContextMenu(newValue, newCursorPosition)
566+
setShowContextMenu(showMenu)
567+
568+
if (showMenu) {
569+
if (newValue.startsWith("/") && !newValue.includes(" ")) {
570+
// Handle slash command - request fresh commands
571+
const query = newValue
572+
setSearchQuery(query)
573+
// Set to first selectable item (skip section headers)
574+
setSelectedMenuIndex(1) // Section header is at 0, first command is at 1
575+
// Request commands fresh each time slash menu is shown
576+
vscode.postMessage({ type: "requestCommands" })
577+
} else {
578+
// Existing @ mention handling.
579+
const lastAtIndex = newValue.lastIndexOf("@", newCursorPosition - 1)
580+
const query = newValue.slice(lastAtIndex + 1, newCursorPosition)
581+
setSearchQuery(query)
580582

581-
// Send file search request if query is not empty.
582-
if (query.length > 0) {
583-
setSelectedMenuIndex(0)
583+
// Send file search request if query is not empty.
584+
if (query.length > 0) {
585+
setSelectedMenuIndex(0)
584586

585-
// Don't clear results until we have new ones. This
586-
// prevents flickering.
587+
// Don't clear results until we have new ones. This
588+
// prevents flickering.
587589

588-
// Clear any existing timeout.
589-
if (searchTimeoutRef.current) {
590-
clearTimeout(searchTimeoutRef.current)
591-
}
590+
// Clear any existing timeout.
591+
if (searchTimeoutRef.current) {
592+
clearTimeout(searchTimeoutRef.current)
593+
}
592594

593-
// Set a timeout to debounce the search requests.
594-
searchTimeoutRef.current = setTimeout(() => {
595-
// Generate a request ID for this search.
596-
const reqId = Math.random().toString(36).substring(2, 9)
597-
setSearchRequestId(reqId)
598-
setSearchLoading(true)
599-
600-
// Send message to extension to search files.
601-
vscode.postMessage({
602-
type: "searchFiles",
603-
query: unescapeSpaces(query),
604-
requestId: reqId,
605-
})
606-
}, 200) // 200ms debounce.
607-
} else {
608-
setSelectedMenuIndex(3) // Set to "File" option by default.
595+
// Set a timeout to debounce the search requests.
596+
searchTimeoutRef.current = setTimeout(() => {
597+
// Generate a request ID for this search.
598+
const reqId = Math.random().toString(36).substring(2, 9)
599+
setSearchRequestId(reqId)
600+
setSearchLoading(true)
601+
602+
// Send message to extension to search files.
603+
vscode.postMessage({
604+
type: "searchFiles",
605+
query: unescapeSpaces(query),
606+
requestId: reqId,
607+
})
608+
}, 200) // 200ms debounce.
609+
} else {
610+
setSelectedMenuIndex(3) // Set to "File" option by default.
611+
}
609612
}
613+
} else {
614+
setSearchQuery("")
615+
setSelectedMenuIndex(-1)
616+
setFileSearchResults([]) // Clear file search results.
610617
}
611-
} else {
612-
setSearchQuery("")
613-
setSelectedMenuIndex(-1)
614-
setFileSearchResults([]) // Clear file search results.
615-
}
618+
})
616619
},
617620
[setInputValue, setSearchRequestId, setFileSearchResults, setSearchLoading, resetOnInputChange],
618621
)
@@ -714,45 +717,56 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
714717
setIsMouseDownOnMenu(true)
715718
}, [])
716719

720+
// Debounce highlight updates for better performance
721+
const updateHighlightsDebounced = useRef<NodeJS.Timeout | null>(null)
722+
717723
const updateHighlights = useCallback(() => {
718-
if (!textAreaRef.current || !highlightLayerRef.current) return
724+
// Clear existing timeout
725+
if (updateHighlightsDebounced.current) {
726+
clearTimeout(updateHighlightsDebounced.current)
727+
}
719728

720-
const text = textAreaRef.current.value
729+
// Debounce the highlight update
730+
updateHighlightsDebounced.current = setTimeout(() => {
731+
if (!textAreaRef.current || !highlightLayerRef.current) return
721732

722-
// Helper function to check if a command is valid
723-
const isValidCommand = (commandName: string): boolean => {
724-
return commands?.some((cmd) => cmd.name === commandName) || false
725-
}
733+
const text = textAreaRef.current.value
726734

727-
// Process the text to highlight mentions and valid commands
728-
let processedText = text
729-
.replace(/\n$/, "\n\n")
730-
.replace(/[<>&]/g, (c) => ({ "<": "&lt;", ">": "&gt;", "&": "&amp;" })[c] || c)
731-
.replace(mentionRegexGlobal, '<mark class="mention-context-textarea-highlight">$&</mark>')
732-
733-
// Custom replacement for commands - only highlight valid ones
734-
processedText = processedText.replace(commandRegexGlobal, (match, commandName) => {
735-
// Only highlight if the command exists in the valid commands list
736-
if (isValidCommand(commandName)) {
737-
// Check if the match starts with a space
738-
const startsWithSpace = match.startsWith(" ")
739-
const commandPart = `/${commandName}`
740-
741-
if (startsWithSpace) {
742-
// Keep the space but only highlight the command part
743-
return ` <mark class="mention-context-textarea-highlight">${commandPart}</mark>`
744-
} else {
745-
// Highlight the entire command (starts at beginning of line)
746-
return `<mark class="mention-context-textarea-highlight">${commandPart}</mark>`
747-
}
735+
// Helper function to check if a command is valid
736+
const isValidCommand = (commandName: string): boolean => {
737+
return commands?.some((cmd) => cmd.name === commandName) || false
748738
}
749-
return match // Return unhighlighted if command is not valid
750-
})
751739

752-
highlightLayerRef.current.innerHTML = processedText
740+
// Process the text to highlight mentions and valid commands
741+
let processedText = text
742+
.replace(/\n$/, "\n\n")
743+
.replace(/[<>&]/g, (c) => ({ "<": "&lt;", ">": "&gt;", "&": "&amp;" })[c] || c)
744+
.replace(mentionRegexGlobal, '<mark class="mention-context-textarea-highlight">$&</mark>')
745+
746+
// Custom replacement for commands - only highlight valid ones
747+
processedText = processedText.replace(commandRegexGlobal, (match, commandName) => {
748+
// Only highlight if the command exists in the valid commands list
749+
if (isValidCommand(commandName)) {
750+
// Check if the match starts with a space
751+
const startsWithSpace = match.startsWith(" ")
752+
const commandPart = `/${commandName}`
753+
754+
if (startsWithSpace) {
755+
// Keep the space but only highlight the command part
756+
return ` <mark class="mention-context-textarea-highlight">${commandPart}</mark>`
757+
} else {
758+
// Highlight the entire command (starts at beginning of line)
759+
return `<mark class="mention-context-textarea-highlight">${commandPart}</mark>`
760+
}
761+
}
762+
return match // Return unhighlighted if command is not valid
763+
})
764+
765+
highlightLayerRef.current.innerHTML = processedText
753766

754-
highlightLayerRef.current.scrollTop = textAreaRef.current.scrollTop
755-
highlightLayerRef.current.scrollLeft = textAreaRef.current.scrollLeft
767+
highlightLayerRef.current.scrollTop = textAreaRef.current.scrollTop
768+
highlightLayerRef.current.scrollLeft = textAreaRef.current.scrollLeft
769+
}, 50) // 50ms debounce for highlight updates
756770
}, [commands])
757771

758772
useLayoutEffect(() => {
@@ -1023,7 +1037,8 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
10231037
value={inputValue}
10241038
onChange={(e) => {
10251039
handleInputChange(e)
1026-
updateHighlights()
1040+
// Defer highlight update to not block typing
1041+
requestAnimationFrame(() => updateHighlights())
10271042
}}
10281043
onFocus={() => setIsFocused(true)}
10291044
onKeyDown={(e) => {
@@ -1081,7 +1096,7 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
10811096
"scrollbar-none",
10821097
"scrollbar-hide",
10831098
)}
1084-
onScroll={() => updateHighlights()}
1099+
onScroll={() => requestAnimationFrame(() => updateHighlights())}
10851100
/>
10861101

10871102
<div className="absolute bottom-2 right-1 z-30 flex flex-col items-center gap-0">

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,11 +1337,11 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
13371337
return result
13381338
}, [isCondensing, visibleMessages])
13391339

1340-
// scrolling
1340+
// scrolling - increase debounce delay for smoother performance
13411341

13421342
const scrollToBottomSmooth = useMemo(
13431343
() =>
1344-
debounce(() => virtuosoRef.current?.scrollTo({ top: Number.MAX_SAFE_INTEGER, behavior: "smooth" }), 10, {
1344+
debounce(() => virtuosoRef.current?.scrollTo({ top: Number.MAX_SAFE_INTEGER, behavior: "smooth" }), 50, {
13451345
immediate: true,
13461346
}),
13471347
[],
@@ -1398,7 +1398,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
13981398
useEffect(() => {
13991399
let timer: ReturnType<typeof setTimeout> | undefined
14001400
if (!disableAutoScrollRef.current) {
1401-
timer = setTimeout(() => scrollToBottomSmooth(), 50)
1401+
// Increase delay for better performance
1402+
timer = setTimeout(() => scrollToBottomSmooth(), 100)
14021403
}
14031404
return () => {
14041405
if (timer) {

0 commit comments

Comments
 (0)