source dump of claude code
at main 199 lines 6.3 kB view raw
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}