@@ -8,7 +8,7 @@ import { stopKeyboardPropagation } from "@/browser/utils/events";
88import { cn } from "@/common/lib/utils" ;
99import { usePersistedState } from "@/browser/hooks/usePersistedState" ;
1010import { VIM_ENABLED_KEY } from "@/common/constants/storage" ;
11- import { CommandHighlightOverlay } from "./CommandHighlightOverlay" ;
11+ import { CommandHighlightOverlay , extractCommandPrefix } from "./CommandHighlightOverlay" ;
1212
1313/**
1414 * VimTextArea – minimal Vim-like editing for a textarea.
@@ -183,6 +183,9 @@ export const VimTextArea = React.forwardRef<HTMLTextAreaElement, VimTextAreaProp
183183 const pendingCommand = showVimMode ? vim . formatPendingCommand ( pendingOp ) : "" ;
184184 const showFocusHint = ! isFocused && ! isVscodeWebview ( ) ;
185185
186+ // Check if there's a command prefix to highlight
187+ const hasCommandPrefix = extractCommandPrefix ( value ) !== null ;
188+
186189 return (
187190 < div style = { { width : "100%" } } data-component = "VimTextAreaContainer" >
188191 < div
@@ -228,7 +231,30 @@ export const VimTextArea = React.forwardRef<HTMLTextAreaElement, VimTextAreaProp
228231 </ div >
229232 ) }
230233 </ div >
231- < div style = { { position : "relative" } } data-component = "VimTextAreaWrapper" >
234+ { /*
235+ Wrapper owns ALL shared typography (font, size, line-height).
236+ Both textarea and highlight overlay inherit from here.
237+ This ensures pixel-perfect alignment without duplicating styles.
238+ Background is on wrapper so overlay (behind textarea) shows correctly.
239+ */ }
240+ < div
241+ className = { cn (
242+ "relative rounded text-[13px]" ,
243+ vimEnabled ? "font-monospace" : "font-sans" ,
244+ isEditing ? "bg-editing-mode-alpha" : "bg-dark"
245+ ) }
246+ data-component = "VimTextAreaWrapper"
247+ >
248+ { /*
249+ Command highlight overlay - positioned BEHIND textarea.
250+ Uses transparent textarea text pattern: overlay shows through
251+ the transparent textarea, caret remains visible via caret-color.
252+ */ }
253+ < CommandHighlightOverlay
254+ value = { value }
255+ hasCommand = { hasCommandPrefix }
256+ className = "absolute inset-0 overflow-hidden rounded border border-transparent px-2 py-1.5"
257+ />
232258 < textarea
233259 ref = { textareaRef }
234260 value = { value }
@@ -249,32 +275,42 @@ export const VimTextArea = React.forwardRef<HTMLTextAreaElement, VimTextAreaProp
249275 ...( trailingAction ? { scrollbarGutter : "stable both-edges" } : { } ) ,
250276 // Focus border color from agent definition
251277 "--focus-border-color" : ! isEditing ? focusBorderColor : undefined ,
278+ // Transparent text pattern: when command is present, make textarea
279+ // text invisible so the highlight overlay shows through.
280+ // The caret remains visible via caret-color below.
281+ WebkitTextFillColor : hasCommandPrefix ? "transparent" : undefined ,
252282 } as React . CSSProperties
253283 }
254284 className = { cn (
255- "w-full border text-light py-1.5 px-2 rounded text-[13px] resize-none min-h-8 max-h-[50vh] overflow-y-auto" ,
256- vimEnabled ? "font-monospace" : "font-sans" ,
285+ // Layout & sizing
286+ "relative w-full py-1.5 px-2 rounded resize-none min-h-8 max-h-[50vh] overflow-y-auto" ,
287+ // Typography inherited from wrapper, but explicitly set color for non-command state
288+ "text-light" ,
289+ // Font inherited from wrapper via `font: inherit` - but we need to repeat for
290+ // the CSS cascade since className comes after style inheritance
291+ "font-[inherit]" ,
292+ // Border
293+ "border" ,
294+ // Background - always transparent so overlay can show through
295+ "bg-transparent" ,
296+ // Placeholder
257297 "placeholder:text-placeholder" ,
298+ // Focus
258299 "focus:outline-none" ,
300+ // Trailing action padding
259301 trailingAction && "pr-10" ,
302+ // Border colors based on state
260303 isEditing
261- ? "bg-editing-mode-alpha border-editing-mode focus:border-editing-mode"
262- : "bg-dark border-border-light focus:border-[var(--focus-border-color)]" ,
304+ ? "border-editing-mode focus:border-editing-mode"
305+ : "border-border-light focus:border-[var(--focus-border-color)]" ,
306+ // Caret: always visible (the highlight overlay handles text color)
307+ // In vim normal mode, hide caret and show block selection
263308 vimMode === "normal"
264309 ? "caret-transparent selection:bg-white/50"
265- : "caret-current selection:bg-selection" ,
310+ : "caret-light selection:bg-selection" ,
266311 rest . className
267312 ) }
268313 />
269- { /* Command highlight overlay - positioned on top of textarea, pointer-events: none allows clicks through */ }
270- < CommandHighlightOverlay
271- value = { value }
272- vimEnabled = { vimEnabled }
273- className = { cn (
274- "absolute inset-0 overflow-hidden rounded border border-transparent" ,
275- vimEnabled ? "font-monospace" : "font-sans"
276- ) }
277- />
278314 { trailingAction && (
279315 < div className = "pointer-events-none absolute right-3.5 bottom-2.5 flex items-center" >
280316 < div className = "pointer-events-auto" > { trailingAction } </ div >
0 commit comments