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