Skip to main content

zsh/zle/
vi.rs

1//! ZLE vi mode operations
2//!
3//! Direct port from zsh/Src/Zle/zle_vi.c
4
5use super::main::{ModifierFlags, Zle};
6
7/// Vi operation pending
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ViPendingOp {
10    None,
11    Delete,
12    Change,
13    Yank,
14    ShiftLeft,
15    ShiftRight,
16    Filter,
17    Case,
18}
19
20/// Vi state
21#[derive(Debug, Default)]
22pub struct ViState {
23    /// Pending operator
24    pub pending_op: Option<ViPendingOp>,
25    /// Character to find
26    pub find_char: Option<char>,
27    /// Find direction (true = forward)
28    pub find_forward: bool,
29    /// Find skip (t/T vs f/F)
30    pub find_skip: bool,
31    /// Last change for dot repeat
32    pub last_change: Option<ViChange>,
33    /// Numeric argument being built
34    pub arg: Option<i32>,
35}
36
37/// A recorded vi change for repeat
38#[derive(Debug, Clone)]
39pub struct ViChange {
40    /// Keys that made up the change
41    pub keys: Vec<u8>,
42    /// Starting cursor position
43    pub start_cs: usize,
44}
45
46impl Zle {
47    /// Get numeric argument (mult)
48    pub fn vi_get_arg(&self) -> i32 {
49        if self.zmod.flags.contains(ModifierFlags::MULT) {
50            self.zmod.mult
51        } else {
52            1
53        }
54    }
55
56    /// Handle vi find character (f/F/t/T)
57    pub fn vi_find_char(&mut self, forward: bool, skip: bool) {
58        // Read the character to find
59        let c = match self.getfullchar(true) {
60            Some(c) => c,
61            None => return,
62        };
63
64        let count = self.vi_get_arg();
65
66        for _ in 0..count {
67            if forward {
68                // Search forward
69                let mut pos = self.zlecs + 1;
70                while pos < self.zlell {
71                    if self.zleline[pos] == c {
72                        self.zlecs = if skip { pos - 1 } else { pos };
73                        break;
74                    }
75                    pos += 1;
76                }
77            } else {
78                // Search backward
79                if self.zlecs > 0 {
80                    let mut pos = self.zlecs - 1;
81                    loop {
82                        if self.zleline[pos] == c {
83                            self.zlecs = if skip { pos + 1 } else { pos };
84                            break;
85                        }
86                        if pos == 0 {
87                            break;
88                        }
89                        pos -= 1;
90                    }
91                }
92            }
93        }
94
95        self.resetneeded = true;
96    }
97
98    /// Vi percent match (find matching bracket)
99    pub fn vi_match_bracket(&mut self) {
100        let c = if self.zlecs < self.zlell {
101            self.zleline[self.zlecs]
102        } else {
103            return;
104        };
105
106        let (target, forward) = match c {
107            '(' => (')', true),
108            ')' => ('(', false),
109            '[' => (']', true),
110            ']' => ('[', false),
111            '{' => ('}', true),
112            '}' => ('{', false),
113            '<' => ('>', true),
114            '>' => ('<', false),
115            _ => return,
116        };
117
118        let mut depth = 1;
119        let mut pos = self.zlecs;
120
121        if forward {
122            pos += 1;
123            while pos < self.zlell && depth > 0 {
124                if self.zleline[pos] == c {
125                    depth += 1;
126                } else if self.zleline[pos] == target {
127                    depth -= 1;
128                }
129                if depth > 0 {
130                    pos += 1;
131                }
132            }
133        } else {
134            if pos > 0 {
135                pos -= 1;
136                loop {
137                    if self.zleline[pos] == c {
138                        depth += 1;
139                    } else if self.zleline[pos] == target {
140                        depth -= 1;
141                    }
142                    if depth == 0 || pos == 0 {
143                        break;
144                    }
145                    pos -= 1;
146                }
147            }
148        }
149
150        if depth == 0 {
151            self.zlecs = pos;
152            self.resetneeded = true;
153        }
154    }
155
156    /// Vi replace mode (R command)
157    pub fn vi_replace_mode(&mut self) {
158        self.keymaps.select("viins");
159        self.insmode = false; // Overwrite mode
160    }
161
162    /// Vi swap case
163    pub fn vi_swap_case(&mut self) {
164        let count = self.vi_get_arg() as usize;
165
166        for _ in 0..count {
167            if self.zlecs < self.zlell {
168                let c = self.zleline[self.zlecs];
169                self.zleline[self.zlecs] = if c.is_uppercase() {
170                    c.to_lowercase().next().unwrap_or(c)
171                } else if c.is_lowercase() {
172                    c.to_uppercase().next().unwrap_or(c)
173                } else {
174                    c
175                };
176                self.zlecs += 1;
177            }
178        }
179
180        // Move back one if we went past end
181        if self.zlecs > 0 && self.zlecs == self.zlell {
182            self.zlecs -= 1;
183        }
184
185        self.resetneeded = true;
186    }
187
188    /// Vi undo (u command)
189    pub fn vi_undo(&mut self) {
190        // TODO: implement full undo
191    }
192
193    /// Vi visual mode
194    pub fn vi_visual_mode(&mut self) {
195        self.mark = self.zlecs;
196        // TODO: implement visual mode state
197    }
198
199    /// Vi visual line mode
200    pub fn vi_visual_line_mode(&mut self) {
201        self.mark = self.zlecs;
202        // TODO: implement visual line mode
203    }
204
205    /// Vi visual block mode
206    pub fn vi_visual_block_mode(&mut self) {
207        self.mark = self.zlecs;
208        // TODO: implement visual block mode
209    }
210
211    /// Vi set mark
212    pub fn vi_set_mark(&mut self, name: char) {
213        // TODO: implement named marks
214        let _ = name;
215        self.mark = self.zlecs;
216    }
217
218    /// Vi goto mark
219    pub fn vi_goto_mark(&mut self, name: char) {
220        // TODO: implement named marks
221        let _ = name;
222    }
223
224    /// Record keys for vi repeat
225    pub fn vi_record_change(&mut self, key: u8) {
226        // TODO: implement change recording
227        let _ = key;
228    }
229
230    /// Replay last change (dot command)
231    pub fn vi_repeat_change(&mut self) {
232        // TODO: implement change replay
233    }
234}