Skip to content

Commit 7a05a23

Browse files
committed
fix Memory leak risk in ChatTextArea.tsx
1 parent d1fca3b commit 7a05a23

File tree

2 files changed

+39
-26
lines changed

2 files changed

+39
-26
lines changed

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

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -225,35 +225,28 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
225225
setInputValue,
226226
})
227227

228-
// Handle modifier key detection for mention clicks
229-
const handleGlobalKeyDown = useCallback(
230-
(e: KeyboardEvent) => {
228+
// Add global event listeners for modifier key detection
229+
useEffect(() => {
230+
const handleGlobalKeyDown = (e: KeyboardEvent) => {
231231
if ((isMac && e.metaKey) || (!isMac && e.ctrlKey)) {
232232
setIsModifierPressed(true)
233233
}
234-
},
235-
[isMac],
236-
)
234+
}
237235

238-
const handleGlobalKeyUp = useCallback(
239-
(e: KeyboardEvent) => {
236+
const handleGlobalKeyUp = (e: KeyboardEvent) => {
240237
if ((isMac && !e.metaKey) || (!isMac && !e.ctrlKey)) {
241238
setIsModifierPressed(false)
242239
}
243-
},
244-
[isMac],
245-
)
240+
}
246241

247-
// Add global event listeners for modifier key detection
248-
useEffect(() => {
249242
document.addEventListener("keydown", handleGlobalKeyDown)
250243
document.addEventListener("keyup", handleGlobalKeyUp)
251244

252245
return () => {
253246
document.removeEventListener("keydown", handleGlobalKeyDown)
254247
document.removeEventListener("keyup", handleGlobalKeyUp)
255248
}
256-
}, [handleGlobalKeyDown, handleGlobalKeyUp])
249+
}, [isMac])
257250

258251
// Handle clicks on mentions in the highlight layer
259252
const handleMentionClick = useCallback(
@@ -273,6 +266,27 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
273266
[isModifierPressed],
274267
)
275268

269+
// Handle keyboard events on mentions for accessibility
270+
const handleMentionKeyDown = useCallback(
271+
(e: KeyboardEvent) => {
272+
if (!isModifierPressed) return
273+
274+
const target = e.target as HTMLElement
275+
if (target.tagName === "MARK" && target.classList.contains("mention-context-textarea-highlight")) {
276+
if (e.key === "Enter" || e.key === " ") {
277+
e.preventDefault()
278+
const mentionText = target.textContent
279+
if (mentionText) {
280+
// Remove @ symbol if present and send to VSCode
281+
const cleanText = mentionText.startsWith("@") ? mentionText.slice(1) : mentionText
282+
vscode.postMessage({ type: "openMention", text: cleanText })
283+
}
284+
}
285+
}
286+
},
287+
[isModifierPressed],
288+
)
289+
276290
// Fetch git commits when Git is selected or when typing a hash.
277291
useEffect(() => {
278292
if (selectedType === ContextMenuOptionType.Git || /^[a-f0-9]+$/i.test(searchQuery)) {
@@ -771,14 +785,18 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
771785

772786
// Determine the class to use based on modifier key state
773787
const mentionClass = `mention-context-textarea-highlight${isModifierPressed ? " clickable" : ""}`
788+
const tabIndex = isModifierPressed ? 0 : -1
789+
const ariaLabel = isModifierPressed
790+
? `${isMac ? "Cmd" : "Ctrl"} + Click or Enter to open`
791+
: `Hold ${isMac ? "Cmd" : "Ctrl"} + Click to open`
774792

775793
// Process the text to highlight mentions and valid commands
776794
let processedText = text
777795
.replace(/\n$/, "\n\n")
778796
.replace(/[<>&]/g, (c) => ({ "<": "&lt;", ">": "&gt;", "&": "&amp;" })[c] || c)
779797
.replace(
780798
mentionRegexGlobal,
781-
`<mark class="${mentionClass}" title="${isModifierPressed ? `${isMac ? "Cmd" : "Ctrl"} + Click to open` : `Hold ${isMac ? "Cmd" : "Ctrl"} + Click to open`}">$&</mark>`,
799+
`<mark class="${mentionClass}" tabindex="${tabIndex}" role="button" aria-label="${ariaLabel}" title="${ariaLabel}">$&</mark>`,
782800
)
783801

784802
// Custom replacement for commands - only highlight valid ones
@@ -791,10 +809,10 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
791809

792810
if (startsWithSpace) {
793811
// Keep the space but only highlight the command part
794-
return ` <mark class="${mentionClass}" title="${isModifierPressed ? `${isMac ? "Cmd" : "Ctrl"} + Click to open` : `Hold ${isMac ? "Cmd" : "Ctrl"} + Click to open`}">${commandPart}</mark>`
812+
return ` <mark class="${mentionClass}" tabindex="${tabIndex}" role="button" aria-label="${ariaLabel}" title="${ariaLabel}">${commandPart}</mark>`
795813
} else {
796814
// Highlight the entire command (starts at beginning of line)
797-
return `<mark class="${mentionClass}" title="${isModifierPressed ? `${isMac ? "Cmd" : "Ctrl"} + Click to open` : `Hold ${isMac ? "Cmd" : "Ctrl"} + Click to open`}">${commandPart}</mark>`
815+
return `<mark class="${mentionClass}" tabindex="${tabIndex}" role="button" aria-label="${ariaLabel}" title="${ariaLabel}">${commandPart}</mark>`
798816
}
799817
}
800818
return match // Return unhighlighted if command is not valid
@@ -810,16 +828,18 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
810828
updateHighlights()
811829
}, [inputValue, updateHighlights])
812830

813-
// Add click event listener to highlight layer for mention clicks
831+
// Add click and keyboard event listeners to highlight layer for mention interactions
814832
useEffect(() => {
815833
const highlightLayer = highlightLayerRef.current
816834
if (highlightLayer) {
817835
highlightLayer.addEventListener("click", handleMentionClick)
836+
highlightLayer.addEventListener("keydown", handleMentionKeyDown)
818837
return () => {
819838
highlightLayer.removeEventListener("click", handleMentionClick)
839+
highlightLayer.removeEventListener("keydown", handleMentionKeyDown)
820840
}
821841
}
822-
}, [handleMentionClick])
842+
}, [handleMentionClick, handleMentionKeyDown])
823843

824844
const updateCursorPosition = useCallback(() => {
825845
if (textAreaRef.current) {

webview-ui/src/index.css

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -379,13 +379,6 @@ vscode-dropdown::part(listbox) {
379379

380380
/* Context mentions */
381381

382-
.mention-context-textarea-highlight {
383-
background-color: color-mix(in srgb, var(--vscode-badge-foreground) 30%, transparent);
384-
border-radius: 3px;
385-
box-shadow: 0 0 0 0.5px color-mix(in srgb, var(--vscode-badge-foreground) 30%, transparent);
386-
color: transparent;
387-
}
388-
389382
.mention-context-highlight {
390383
background-color: color-mix(in srgb, var(--vscode-badge-foreground) 30%, transparent);
391384
border-radius: 3px;

0 commit comments

Comments
 (0)