source dump of claude code
1/**
2 * Vim Mode State Machine Types
3 *
4 * This file defines the complete state machine for vim input handling.
5 * The types ARE the documentation - reading them tells you how the system works.
6 *
7 * State Diagram:
8 * ```
9 * VimState
10 * ┌──────────────────────────────┬──────────────────────────────────────┐
11 * │ INSERT │ NORMAL │
12 * │ (tracks insertedText) │ (CommandState machine) │
13 * │ │ │
14 * │ │ idle ──┬─[d/c/y]──► operator │
15 * │ │ ├─[1-9]────► count │
16 * │ │ ├─[fFtT]───► find │
17 * │ │ ├─[g]──────► g │
18 * │ │ ├─[r]──────► replace │
19 * │ │ └─[><]─────► indent │
20 * │ │ │
21 * │ │ operator ─┬─[motion]──► execute │
22 * │ │ ├─[0-9]────► operatorCount│
23 * │ │ ├─[ia]─────► operatorTextObj
24 * │ │ └─[fFtT]───► operatorFind │
25 * └──────────────────────────────┴──────────────────────────────────────┘
26 * ```
27 */
28
29// ============================================================================
30// Core Types
31// ============================================================================
32
33export type Operator = 'delete' | 'change' | 'yank'
34
35export type FindType = 'f' | 'F' | 't' | 'T'
36
37export type TextObjScope = 'inner' | 'around'
38
39// ============================================================================
40// State Machine Types
41// ============================================================================
42
43/**
44 * Complete vim state. Mode determines what data is tracked.
45 *
46 * INSERT mode: Track text being typed (for dot-repeat)
47 * NORMAL mode: Track command being parsed (state machine)
48 */
49export type VimState =
50 | { mode: 'INSERT'; insertedText: string }
51 | { mode: 'NORMAL'; command: CommandState }
52
53/**
54 * Command state machine for NORMAL mode.
55 *
56 * Each state knows exactly what input it's waiting for.
57 * TypeScript ensures exhaustive handling in switches.
58 */
59export type CommandState =
60 | { type: 'idle' }
61 | { type: 'count'; digits: string }
62 | { type: 'operator'; op: Operator; count: number }
63 | { type: 'operatorCount'; op: Operator; count: number; digits: string }
64 | { type: 'operatorFind'; op: Operator; count: number; find: FindType }
65 | {
66 type: 'operatorTextObj'
67 op: Operator
68 count: number
69 scope: TextObjScope
70 }
71 | { type: 'find'; find: FindType; count: number }
72 | { type: 'g'; count: number }
73 | { type: 'operatorG'; op: Operator; count: number }
74 | { type: 'replace'; count: number }
75 | { type: 'indent'; dir: '>' | '<'; count: number }
76
77/**
78 * Persistent state that survives across commands.
79 * This is the "memory" of vim - what gets recalled for repeats and pastes.
80 */
81export type PersistentState = {
82 lastChange: RecordedChange | null
83 lastFind: { type: FindType; char: string } | null
84 register: string
85 registerIsLinewise: boolean
86}
87
88/**
89 * Recorded change for dot-repeat.
90 * Captures everything needed to replay a command.
91 */
92export type RecordedChange =
93 | { type: 'insert'; text: string }
94 | {
95 type: 'operator'
96 op: Operator
97 motion: string
98 count: number
99 }
100 | {
101 type: 'operatorTextObj'
102 op: Operator
103 objType: string
104 scope: TextObjScope
105 count: number
106 }
107 | {
108 type: 'operatorFind'
109 op: Operator
110 find: FindType
111 char: string
112 count: number
113 }
114 | { type: 'replace'; char: string; count: number }
115 | { type: 'x'; count: number }
116 | { type: 'toggleCase'; count: number }
117 | { type: 'indent'; dir: '>' | '<'; count: number }
118 | { type: 'openLine'; direction: 'above' | 'below' }
119 | { type: 'join'; count: number }
120
121// ============================================================================
122// Key Groups - Named constants, no magic strings
123// ============================================================================
124
125export const OPERATORS = {
126 d: 'delete',
127 c: 'change',
128 y: 'yank',
129} as const satisfies Record<string, Operator>
130
131export function isOperatorKey(key: string): key is keyof typeof OPERATORS {
132 return key in OPERATORS
133}
134
135export const SIMPLE_MOTIONS = new Set([
136 'h',
137 'l',
138 'j',
139 'k', // Basic movement
140 'w',
141 'b',
142 'e',
143 'W',
144 'B',
145 'E', // Word motions
146 '0',
147 '^',
148 '$', // Line positions
149])
150
151export const FIND_KEYS = new Set(['f', 'F', 't', 'T'])
152
153export const TEXT_OBJ_SCOPES = {
154 i: 'inner',
155 a: 'around',
156} as const satisfies Record<string, TextObjScope>
157
158export function isTextObjScopeKey(
159 key: string,
160): key is keyof typeof TEXT_OBJ_SCOPES {
161 return key in TEXT_OBJ_SCOPES
162}
163
164export const TEXT_OBJ_TYPES = new Set([
165 'w',
166 'W', // Word/WORD
167 '"',
168 "'",
169 '`', // Quotes
170 '(',
171 ')',
172 'b', // Parens
173 '[',
174 ']', // Brackets
175 '{',
176 '}',
177 'B', // Braces
178 '<',
179 '>', // Angle brackets
180])
181
182export const MAX_VIM_COUNT = 10000
183
184// ============================================================================
185// State Factories
186// ============================================================================
187
188export function createInitialVimState(): VimState {
189 return { mode: 'INSERT', insertedText: '' }
190}
191
192export function createInitialPersistentState(): PersistentState {
193 return {
194 lastChange: null,
195 lastFind: null,
196 register: '',
197 registerIsLinewise: false,
198 }
199}