init
This commit is contained in:
+199
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* 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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user