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
//! ZLE movement operations
//!
//! Direct port from zsh/Src/Zle/zle_move.c
use super::main::Zle;
impl Zle {
/// Move cursor to the start of the current logical line.
/// Port of `findbol()` from Src/Zle/zle_utils.c:1158 — same scan,
/// just mutates zlecs in-place instead of returning the index.
/// `find_bol` (in utils.rs) is the side-effect-free equivalent.
pub fn move_to_bol(&mut self) {
while self.zlecs > 0 && self.zleline[self.zlecs - 1] != '\n' {
self.zlecs -= 1;
}
}
/// Move cursor to the end of the current logical line.
/// Port of `findeol()` from Src/Zle/zle_utils.c:1169 — mutating
/// counterpart to `find_eol`.
pub fn move_to_eol(&mut self) {
while self.zlecs < self.zlell && self.zleline[self.zlecs] != '\n' {
self.zlecs += 1;
}
}
/// Move cursor up one logical line, preserving the column.
/// Simplified port of `upline()` from Src/Zle/zle_hist.c:243 with
/// fixed n=1 — captures the column-preserve behaviour without the
/// lastcol sticky-column tracking the C source uses for repeated
/// up/down chains. Returns false at top-of-buffer.
pub fn move_up(&mut self) -> bool {
let col = self.current_column();
// Find start of current line
let mut line_start = self.zlecs;
while line_start > 0 && self.zleline[line_start - 1] != '\n' {
line_start -= 1;
}
if line_start == 0 {
return false; // Already on first line
}
// Move to end of previous line
self.zlecs = line_start - 1;
// Find start of previous line
let mut prev_start = self.zlecs;
while prev_start > 0 && self.zleline[prev_start - 1] != '\n' {
prev_start -= 1;
}
// Move to same column or end of line
self.zlecs = prev_start + col.min(self.zlecs - prev_start);
true
}
/// Move cursor down one logical line, preserving the column.
/// Simplified port of `downline()` from Src/Zle/zle_hist.c:332
/// with fixed n=1. Returns false at end-of-buffer.
pub fn move_down(&mut self) -> bool {
let col = self.current_column();
// Find end of current line
let mut line_end = self.zlecs;
while line_end < self.zlell && self.zleline[line_end] != '\n' {
line_end += 1;
}
if line_end >= self.zlell {
return false; // Already on last line
}
// Move to start of next line
self.zlecs = line_end + 1;
// Find end of next line
let mut next_end = self.zlecs;
while next_end < self.zlell && self.zleline[next_end] != '\n' {
next_end += 1;
}
// Move to same column or end of line
self.zlecs = (self.zlecs + col).min(next_end);
true
}
/// Compute the cursor's 0-indexed column on its current logical line.
/// Equivalent to `zlecs - findbol()` — the offset zsh's vertical-
/// motion code at Src/Zle/zle_hist.c:253 caches in `lastcol` for
/// sticky-column behaviour across up/down chains.
pub fn current_column(&self) -> usize {
let mut col = 0;
let mut i = self.zlecs;
while i > 0 && self.zleline[i - 1] != '\n' {
i -= 1;
col += 1;
}
col
}
/// Compute the 0-indexed logical-line number containing the cursor.
/// Port of `findline()` from Src/Zle/zle_utils.c:1180 (which fills
/// in start/end of the cursor's line) but returning just the line
/// number — counts newlines before the cursor.
pub fn current_line(&self) -> usize {
self.zleline[..self.zlecs]
.iter()
.filter(|&&c| c == '\n')
.count()
}
/// Count the total number of logical lines in the buffer.
/// Used by display code to size the multi-line refresh region —
/// mirrors `nlnct` (number of lines counted) tracked by zsh's
/// `zrefresh()` in Src/Zle/zle_refresh.c.
pub fn count_lines(&self) -> usize {
self.zleline.iter().filter(|&&c| c == '\n').count() + 1
}
}