/ vim / types.ts
types.ts
  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  
 33  export type Operator = 'delete' | 'change' | 'yank'
 34  
 35  export type FindType = 'f' | 'F' | 't' | 'T'
 36  
 37  export 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   */
 49  export 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   */
 59  export 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   */
 81  export 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   */
 92  export 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  
125  export const OPERATORS = {
126    d: 'delete',
127    c: 'change',
128    y: 'yank',
129  } as const satisfies Record<string, Operator>
130  
131  export function isOperatorKey(key: string): key is keyof typeof OPERATORS {
132    return key in OPERATORS
133  }
134  
135  export 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  
151  export const FIND_KEYS = new Set(['f', 'F', 't', 'T'])
152  
153  export const TEXT_OBJ_SCOPES = {
154    i: 'inner',
155    a: 'around',
156  } as const satisfies Record<string, TextObjScope>
157  
158  export function isTextObjScopeKey(
159    key: string,
160  ): key is keyof typeof TEXT_OBJ_SCOPES {
161    return key in TEXT_OBJ_SCOPES
162  }
163  
164  export 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  
182  export const MAX_VIM_COUNT = 10000
183  
184  // ============================================================================
185  // State Factories
186  // ============================================================================
187  
188  export function createInitialVimState(): VimState {
189    return { mode: 'INSERT', insertedText: '' }
190  }
191  
192  export function createInitialPersistentState(): PersistentState {
193    return {
194      lastChange: null,
195      lastFind: null,
196      register: '',
197      registerIsLinewise: false,
198    }
199  }