/ vim / motions.ts
motions.ts
 1  /**
 2   * Vim Motion Functions
 3   *
 4   * Pure functions for resolving vim motions to cursor positions.
 5   */
 6  
 7  import type { Cursor } from '../utils/Cursor.js'
 8  
 9  /**
10   * Resolve a motion to a target cursor position.
11   * Does not modify anything - pure calculation.
12   */
13  export function resolveMotion(
14    key: string,
15    cursor: Cursor,
16    count: number,
17  ): Cursor {
18    let result = cursor
19    for (let i = 0; i < count; i++) {
20      const next = applySingleMotion(key, result)
21      if (next.equals(result)) break
22      result = next
23    }
24    return result
25  }
26  
27  /**
28   * Apply a single motion step.
29   */
30  function applySingleMotion(key: string, cursor: Cursor): Cursor {
31    switch (key) {
32      case 'h':
33        return cursor.left()
34      case 'l':
35        return cursor.right()
36      case 'j':
37        return cursor.downLogicalLine()
38      case 'k':
39        return cursor.upLogicalLine()
40      case 'gj':
41        return cursor.down()
42      case 'gk':
43        return cursor.up()
44      case 'w':
45        return cursor.nextVimWord()
46      case 'b':
47        return cursor.prevVimWord()
48      case 'e':
49        return cursor.endOfVimWord()
50      case 'W':
51        return cursor.nextWORD()
52      case 'B':
53        return cursor.prevWORD()
54      case 'E':
55        return cursor.endOfWORD()
56      case '0':
57        return cursor.startOfLogicalLine()
58      case '^':
59        return cursor.firstNonBlankInLogicalLine()
60      case '$':
61        return cursor.endOfLogicalLine()
62      case 'G':
63        return cursor.startOfLastLine()
64      default:
65        return cursor
66    }
67  }
68  
69  /**
70   * Check if a motion is inclusive (includes character at destination).
71   */
72  export function isInclusiveMotion(key: string): boolean {
73    return 'eE$'.includes(key)
74  }
75  
76  /**
77   * Check if a motion is linewise (operates on full lines when used with operators).
78   * Note: gj/gk are characterwise exclusive per `:help gj`, not linewise.
79   */
80  export function isLinewiseMotion(key: string): boolean {
81    return 'jkG'.includes(key) || key === 'gg'
82  }