Skip to content

Commit 201eecf

Browse files
committed
🤖 Add VimTextArea and integrate Vim keybindings into ChatInput (MVP)
- New VimTextArea component with basic Vim modes (insert/normal) - Supports h/j/k/l, 0, $, w, b; i/a/I/A/o/O; x, dd, yy, p/P; u, Ctrl-r - Integrated with ChatInput, preserves existing send/cancel keybinds - ESC now only intercepted for edit/interrupt; otherwise passes to Vim or suggestions - Avoids interfering with CommandSuggestions via suppressKeys _Generated with `cmux`_
1 parent 4ee0b12 commit 201eecf

File tree

2 files changed

+424
-47
lines changed

2 files changed

+424
-47
lines changed

src/components/ChatInput.tsx

Lines changed: 8 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import { TooltipWrapper, Tooltip, HelpIndicator } from "./Tooltip";
2121
import { matchesKeybind, formatKeybind, KEYBINDS } from "@/utils/ui/keybinds";
2222
import { defaultModel } from "@/utils/ai/models";
23+
import { VimTextArea } from "./VimTextArea";
2324

2425
const InputSection = styled.div`
2526
position: relative;
@@ -37,39 +38,7 @@ const InputControls = styled.div`
3738
align-items: flex-end;
3839
`;
3940

40-
const InputField = styled.textarea<{
41-
isEditing?: boolean;
42-
canInterrupt?: boolean;
43-
mode: UIMode;
44-
}>`
45-
flex: 1;
46-
background: ${(props) => (props.isEditing ? "var(--color-editing-mode-alpha)" : "#1e1e1e")};
47-
border: 1px solid ${(props) => (props.isEditing ? "var(--color-editing-mode)" : "#3e3e42")};
48-
color: #d4d4d4;
49-
padding: 8px 12px;
50-
border-radius: 4px;
51-
font-family: inherit;
52-
font-size: 13px;
53-
resize: none;
54-
min-height: 36px;
55-
max-height: 200px;
56-
overflow-y: auto;
57-
max-height: 120px;
58-
59-
&:focus {
60-
outline: none;
61-
border-color: ${(props) =>
62-
props.isEditing
63-
? "var(--color-editing-mode)"
64-
: props.mode === "plan"
65-
? "var(--color-plan-mode)"
66-
: "var(--color-exec-mode)"};
67-
}
68-
69-
&::placeholder {
70-
color: #6b6b6b;
71-
}
72-
`;
41+
// Input now rendered by VimTextArea; styles moved there
7342

7443
const ModeToggles = styled.div`
7544
display: flex;
@@ -590,22 +559,22 @@ for a new Assistant to continue helping the user. Prioritize specific, actionabl
590559
const handleKeyDown = (e: React.KeyboardEvent) => {
591560
// Handle cancel/escape
592561
if (matchesKeybind(e, KEYBINDS.CANCEL)) {
593-
e.preventDefault();
594-
595562
// Priority 1: Cancel editing if in edit mode
596563
if (editingMessage && onCancelEdit) {
564+
e.preventDefault();
597565
onCancelEdit();
598566
return;
599567
}
600568

601569
// Priority 2: Interrupt streaming if active
602570
if (canInterrupt) {
571+
e.preventDefault();
603572
// Send empty message to trigger interrupt
604573
void window.api.workspace.sendMessage(workspaceId, "");
605574
return;
606575
}
607576

608-
return;
577+
// Otherwise, do not preventDefault here: allow VimTextArea or other handlers (like suggestions) to process ESC
609578
}
610579

611580
// Don't handle keys if command suggestions are visible
@@ -640,21 +609,14 @@ for a new Assistant to continue helping the user. Prioritize specific, actionabl
640609
isVisible={showCommandSuggestions}
641610
/>
642611
<InputControls>
643-
<InputField
612+
<VimTextArea
644613
ref={inputRef}
645614
value={input}
646615
isEditing={!!editingMessage}
647616
mode={mode}
648-
onChange={(e) => {
649-
const newValue = e.target.value;
650-
setInput(newValue);
651-
// Auto-resize textarea
652-
e.target.style.height = "auto";
653-
e.target.style.height = Math.min(e.target.scrollHeight, 200) + "px";
654-
655-
// Don't clear toast when typing - let user dismiss it manually or it auto-dismisses
656-
}}
617+
onChange={setInput}
657618
onKeyDown={handleKeyDown}
619+
suppressKeys={showCommandSuggestions ? COMMAND_SUGGESTION_KEYS : undefined}
658620
placeholder={
659621
editingMessage
660622
? `Edit your message... (${formatKeybind(KEYBINDS.CANCEL)} to cancel, ${formatKeybind(KEYBINDS.SEND_MESSAGE)} to send)`
@@ -665,7 +627,6 @@ for a new Assistant to continue helping the user. Prioritize specific, actionabl
665627
: `Type a message... (${formatKeybind(KEYBINDS.SEND_MESSAGE)} to send, ${formatKeybind(KEYBINDS.NEW_LINE)} for newline)`
666628
}
667629
disabled={disabled || isSending || isCompacting}
668-
canInterrupt={canInterrupt}
669630
/>
670631
</InputControls>
671632
<ModeToggles>

0 commit comments

Comments
 (0)