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
//! Debug-only `sticky_col` (vim's `curswant`) invariant — phase 0 of the
//! cursor-move refactor described in `docs/backlog.md`.
//!
//! Moving the cursor and maintaining `sticky_col` are two separate actions
//! today and nothing forces the second, which is how `c022a3a4` happened:
//! `/pattern<CR>` moved the cursor without resetting `curswant`, so the next
//! `j` snapped back to the pre-search column. Phases 2–4 will migrate ~186
//! call sites onto `Editor::move_cursor`; this check is the net that makes a
//! misclassification during that migration fail loudly inside the test suite
//! instead of shipping as a silent behaviour change.
//!
//! Compiled out entirely in release: the whole module is
//! `#[cfg(debug_assertions)]` and both of its hooks in [`crate::dispatch_input`]
//! are gated the same way.
use crate;
use cratevim;
use char_col_to_visual_col;
use Editor;
use ;
/// Pre-keystroke state, captured before the FSM runs.
/// The invariant itself: is the editor's `(cursor, sticky_col)` pair one the
/// two curswant rules can produce? See [`assert_invariant`] for the three
/// legal shapes.
/// Check the `curswant` invariant after one keystroke has been dispatched.
///
/// # The rule
///
/// Already documented on `Editor::jump_cursor` and
/// `crate::vim::motion::apply_sticky_col`:
///
/// - **vertical** motions (`j`, `k`, and the `<C-e>`/`<C-y>` screen
/// equivalents) READ `sticky_col`, clamp the landing column to the row's
/// length, and leave `sticky_col` alone;
/// - **everything else** that moves the cursor — search hits, `gg`/`G`,
/// marks, clicks, word motions, `$`/`^`, `]d` — SETS `sticky_col` to the
/// column it landed on.
///
/// # What is checked
///
/// Rather than classify the keystroke as vertical or not (which is ambiguous:
/// `j` is vertical bare, but a target in `dj` / `fj` / `rj`, and a literal
/// char in Insert mode), the check tests the *state* the two rules can
/// produce. After a keystroke that moved the cursor to `(row, col)`,
/// `sticky_col` may only be:
///
/// 1. `None` — no motion has ever run on this editor; or
/// 2. `Some(col)` — the "everything else" rule; or
/// 3. `Some(want)` with `want > col` **and** `col` at the end of `row` —
/// the vertical rule parked on a row shorter than `want`.
///
/// Those are exactly the states phase 1's `Move` variants can reach (`Raw`
/// excepted, which is why `Raw` must stay justified per site). A stale
/// `curswant` left behind by a cursor move that forgot to sync — the
/// `c022a3a4` class — falls outside all three and trips the assert. Being
/// state-based rather than key-based, the check also does not need teaching
/// about new motions: one added later is covered the day it is written.
///
/// The check is on the *transition*, not on the state alone: it fires only
/// when the keystroke moved from a legal `(cursor, sticky_col)` pair to an
/// illegal one. A motion that inherits an already-stale `curswant` is not the
/// site that has to change, and reporting it would point the next phase at
/// the wrong file.
///
/// # What is deliberately *not* checked
///
/// Only keystrokes that were plain motions are checked — see [`Pre`] and the
/// guards below. Operators, edits, undo/redo, Insert mode and the `:` /
/// search command lines all move the cursor without being motions, and in
/// hjkl none of them maintain `curswant` today. Those are real deviations
/// from vim (vim resets `curswant` on every one of them) but they are a
/// different bug class from the one this net exists for, and they are
/// enumerated in the phase-0 report rather than silenced one by one. Widening
/// the check to cover them is a later phase's job — do not narrow it further.