Skip to main content

zsh/zle/
widget.rs

1//! ZLE widgets - line editor commands
2//!
3//! Direct port from zsh/Src/Zle/zle.h widget structures
4//!
5//! A widget is a ZLE command that can be bound to keys or executed by name.
6//! Widgets can be internal (implemented in Rust) or user-defined (shell functions).
7
8use super::main::Zle;
9
10/// Widget function type
11pub type ZleIntFunc = fn(&mut Zle) -> i32;
12
13/// Widget function variants
14#[derive(Clone)]
15pub enum WidgetFunc {
16    /// Internally implemented widget
17    Internal(fn(&mut Zle)),
18    /// User-defined widget (name of shell function)
19    User(String),
20}
21
22impl std::fmt::Debug for WidgetFunc {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        match self {
25            WidgetFunc::Internal(_) => write!(f, "Internal(<fn>)"),
26            WidgetFunc::User(name) => write!(f, "User({})", name),
27        }
28    }
29}
30
31bitflags::bitflags! {
32    /// Widget flags
33    #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
34    pub struct WidgetFlags: u32 {
35        /// Widget is internally implemented
36        const INT = 1 << 0;
37        /// New style completion widget
38        const NCOMP = 1 << 1;
39        /// DON'T invalidate completion list
40        const MENUCMP = 1 << 2;
41        /// Yank after cursor
42        const YANKAFTER = 1 << 3;
43        /// Yank before cursor
44        const YANKBEFORE = 1 << 4;
45        /// Yank (either direction)
46        const YANK = Self::YANKAFTER.bits() | Self::YANKBEFORE.bits();
47        /// Command is a line-oriented movement
48        const LINEMOVE = 1 << 5;
49        /// Widget reads further keys so wait if prefix
50        const VIOPER = 1 << 6;
51        /// Command maintains lastcol correctly
52        const LASTCOL = 1 << 7;
53        /// Kill command
54        const KILL = 1 << 8;
55        /// DON'T remove added suffix
56        const KEEPSUFFIX = 1 << 9;
57        /// Widget should not alter lastcmd
58        const NOTCOMMAND = 1 << 10;
59        /// Usable for new style completion
60        const ISCOMP = 1 << 11;
61        /// Widget is in use
62        const INUSE = 1 << 12;
63        /// Request to free when no longer in use
64        const FREE = 1 << 13;
65        /// Widget should not alter lbindk
66        const NOLAST = 1 << 14;
67    }
68}
69
70/// A widget (ZLE command)
71#[derive(Debug, Clone)]
72pub struct Widget {
73    /// Flags
74    pub flags: WidgetFlags,
75    /// Widget function
76    pub func: WidgetFunc,
77}
78
79impl Widget {
80    /// Create a new internal widget
81    pub fn internal(name: &str, func: fn(&mut Zle), flags: WidgetFlags) -> Self {
82        let _ = name; // Would be used for registration
83        Widget {
84            flags: flags | WidgetFlags::INT,
85            func: WidgetFunc::Internal(func),
86        }
87    }
88
89    /// Create a builtin widget by name
90    pub fn builtin(name: &str) -> Self {
91        let (func, flags) = get_builtin_widget(name);
92        Widget {
93            flags: flags | WidgetFlags::INT,
94            func: WidgetFunc::Internal(func),
95        }
96    }
97
98    /// Create a user-defined widget
99    pub fn user_defined(name: &str, func_name: &str) -> Self {
100        let _ = name;
101        Widget {
102            flags: WidgetFlags::empty(),
103            func: WidgetFunc::User(func_name.to_string()),
104        }
105    }
106}
107
108/// Get the builtin widget function for a name
109fn get_builtin_widget(name: &str) -> (fn(&mut Zle), WidgetFlags) {
110    match name {
111        // Accept/execute
112        "accept-line" => (widget_accept_line, WidgetFlags::empty()),
113        "accept-and-hold" => (widget_accept_and_hold, WidgetFlags::empty()),
114        "accept-line-and-down-history" => {
115            (widget_accept_line_and_down_history, WidgetFlags::empty())
116        }
117
118        // Self-insert
119        "self-insert" => (widget_self_insert, WidgetFlags::empty()),
120        "self-insert-unmeta" => (widget_self_insert_unmeta, WidgetFlags::empty()),
121
122        // Movement - character
123        "forward-char" => (widget_forward_char, WidgetFlags::empty()),
124        "backward-char" => (widget_backward_char, WidgetFlags::empty()),
125
126        // Movement - word
127        "forward-word" => (widget_forward_word, WidgetFlags::empty()),
128        "backward-word" => (widget_backward_word, WidgetFlags::empty()),
129
130        // Movement - line
131        "beginning-of-line" => (widget_beginning_of_line, WidgetFlags::empty()),
132        "end-of-line" => (widget_end_of_line, WidgetFlags::empty()),
133
134        // Delete
135        "delete-char" => (widget_delete_char, WidgetFlags::empty()),
136        "backward-delete-char" => (widget_backward_delete_char, WidgetFlags::empty()),
137        "delete-char-or-list" => (widget_delete_char_or_list, WidgetFlags::empty()),
138
139        // Kill
140        "kill-line" => (widget_kill_line, WidgetFlags::KILL),
141        "backward-kill-line" => (widget_backward_kill_line, WidgetFlags::KILL),
142        "kill-whole-line" => (widget_kill_whole_line, WidgetFlags::KILL),
143        "kill-word" => (widget_kill_word, WidgetFlags::KILL),
144        "backward-kill-word" => (widget_backward_kill_word, WidgetFlags::KILL),
145
146        // Yank
147        "yank" => (widget_yank, WidgetFlags::YANK),
148        "yank-pop" => (widget_yank_pop, WidgetFlags::YANK),
149
150        // Undo
151        "undo" => (widget_undo, WidgetFlags::empty()),
152        "redo" => (widget_redo, WidgetFlags::empty()),
153
154        // History
155        "up-line-or-history" => (widget_up_line_or_history, WidgetFlags::LINEMOVE),
156        "down-line-or-history" => (widget_down_line_or_history, WidgetFlags::LINEMOVE),
157        "up-history" => (widget_up_history, WidgetFlags::LINEMOVE),
158        "down-history" => (widget_down_history, WidgetFlags::LINEMOVE),
159        "history-incremental-search-backward" => {
160            (widget_history_isearch_backward, WidgetFlags::empty())
161        }
162        "history-incremental-search-forward" => {
163            (widget_history_isearch_forward, WidgetFlags::empty())
164        }
165        "beginning-of-buffer-or-history" => {
166            (widget_beginning_of_buffer_or_history, WidgetFlags::LINEMOVE)
167        }
168        "end-of-buffer-or-history" => (widget_end_of_buffer_or_history, WidgetFlags::LINEMOVE),
169
170        // Misc
171        "transpose-chars" => (widget_transpose_chars, WidgetFlags::empty()),
172        "clear-screen" => (widget_clear_screen, WidgetFlags::empty()),
173        "redisplay" => (widget_redisplay, WidgetFlags::empty()),
174        "send-break" => (widget_send_break, WidgetFlags::empty()),
175        "overwrite-mode" => (widget_overwrite_mode, WidgetFlags::empty()),
176        "quoted-insert" => (widget_quoted_insert, WidgetFlags::empty()),
177
178        // Completion
179        "expand-or-complete" => (widget_expand_or_complete, WidgetFlags::MENUCMP),
180        "complete-word" => (widget_complete_word, WidgetFlags::MENUCMP),
181        "expand-word" => (widget_expand_word, WidgetFlags::empty()),
182        "list-choices" => (widget_list_choices, WidgetFlags::MENUCMP),
183        "menu-complete" => (widget_menu_complete, WidgetFlags::MENUCMP),
184
185        // Vi mode
186        "vi-cmd-mode" => (widget_vi_cmd_mode, WidgetFlags::empty()),
187        "vi-insert" => (widget_vi_insert, WidgetFlags::empty()),
188        "vi-insert-bol" => (widget_vi_insert_bol, WidgetFlags::empty()),
189        "vi-add-next" => (widget_vi_add_next, WidgetFlags::empty()),
190        "vi-add-eol" => (widget_vi_add_eol, WidgetFlags::empty()),
191        "vi-forward-char" => (widget_vi_forward_char, WidgetFlags::empty()),
192        "vi-backward-char" => (widget_vi_backward_char, WidgetFlags::empty()),
193        "vi-forward-word" => (widget_vi_forward_word, WidgetFlags::empty()),
194        "vi-forward-word-end" => (widget_vi_forward_word_end, WidgetFlags::empty()),
195        "vi-forward-blank-word" => (widget_vi_forward_blank_word, WidgetFlags::empty()),
196        "vi-forward-blank-word-end" => (widget_vi_forward_blank_word_end, WidgetFlags::empty()),
197        "vi-backward-word" => (widget_vi_backward_word, WidgetFlags::empty()),
198        "vi-backward-blank-word" => (widget_vi_backward_blank_word, WidgetFlags::empty()),
199        "vi-delete" => (widget_vi_delete, WidgetFlags::VIOPER | WidgetFlags::KILL),
200        "vi-delete-char" => (widget_vi_delete_char, WidgetFlags::empty()),
201        "vi-backward-delete-char" => (widget_vi_backward_delete_char, WidgetFlags::empty()),
202        "vi-change" => (widget_vi_change, WidgetFlags::VIOPER | WidgetFlags::KILL),
203        "vi-change-eol" => (widget_vi_change_eol, WidgetFlags::KILL),
204        "vi-kill-eol" => (widget_vi_kill_eol, WidgetFlags::KILL),
205        "vi-yank" => (widget_vi_yank, WidgetFlags::VIOPER),
206        "vi-yank-whole-line" => (widget_vi_yank_whole_line, WidgetFlags::empty()),
207        "vi-put-after" => (widget_vi_put_after, WidgetFlags::YANK),
208        "vi-put-before" => (widget_vi_put_before, WidgetFlags::YANK),
209        "vi-replace" => (widget_vi_replace, WidgetFlags::empty()),
210        "vi-replace-chars" => (widget_vi_replace_chars, WidgetFlags::empty()),
211        "vi-substitute" => (widget_vi_substitute, WidgetFlags::empty()),
212        "vi-change-whole-line" => (widget_vi_change_whole_line, WidgetFlags::KILL),
213        "vi-first-non-blank" => (widget_vi_first_non_blank, WidgetFlags::empty()),
214        "vi-end-of-line" => (widget_vi_end_of_line, WidgetFlags::empty()),
215        "vi-digit-or-beginning-of-line" => {
216            (widget_vi_digit_or_beginning_of_line, WidgetFlags::empty())
217        }
218        "vi-open-line-below" => (widget_vi_open_line_below, WidgetFlags::empty()),
219        "vi-open-line-above" => (widget_vi_open_line_above, WidgetFlags::empty()),
220        "vi-join" => (widget_vi_join, WidgetFlags::empty()),
221        "vi-repeat-change" => (widget_vi_repeat_change, WidgetFlags::empty()),
222        "vi-find-next-char" => (widget_vi_find_next_char, WidgetFlags::empty()),
223        "vi-find-prev-char" => (widget_vi_find_prev_char, WidgetFlags::empty()),
224        "vi-find-next-char-skip" => (widget_vi_find_next_char_skip, WidgetFlags::empty()),
225        "vi-find-prev-char-skip" => (widget_vi_find_prev_char_skip, WidgetFlags::empty()),
226        "vi-repeat-find" => (widget_vi_repeat_find, WidgetFlags::empty()),
227        "vi-rev-repeat-find" => (widget_vi_rev_repeat_find, WidgetFlags::empty()),
228        "vi-history-search-forward" => (widget_vi_history_search_forward, WidgetFlags::empty()),
229        "vi-history-search-backward" => (widget_vi_history_search_backward, WidgetFlags::empty()),
230        "vi-repeat-search" => (widget_vi_repeat_search, WidgetFlags::empty()),
231        "vi-rev-repeat-search" => (widget_vi_rev_repeat_search, WidgetFlags::empty()),
232        "vi-fetch-history" => (widget_vi_fetch_history, WidgetFlags::LINEMOVE),
233        "vi-goto-column" => (widget_vi_goto_column, WidgetFlags::empty()),
234        "vi-backward-kill-word" => (widget_vi_backward_kill_word, WidgetFlags::KILL),
235
236        // Digit argument
237        "digit-argument" => (widget_digit_argument, WidgetFlags::NOTCOMMAND),
238
239        // Default: undefined widget
240        _ => (widget_undefined, WidgetFlags::empty()),
241    }
242}
243
244// Widget implementations
245
246fn widget_accept_line(zle: &mut Zle) {
247    zle.accept_line();
248}
249
250fn widget_accept_and_hold(zle: &mut Zle) {
251    // TODO: implement accept-and-hold
252    zle.accept_line();
253}
254
255fn widget_accept_line_and_down_history(zle: &mut Zle) {
256    // TODO: implement accept-line-and-down-history
257    zle.accept_line();
258}
259
260fn widget_self_insert(zle: &mut Zle) {
261    #[cfg(feature = "multibyte")]
262    if let Some(c) = char::from_u32(zle.lastchar as u32) {
263        zle.self_insert(c);
264    }
265    #[cfg(not(feature = "multibyte"))]
266    if zle.lastchar >= 0 && zle.lastchar <= 127 {
267        zle.self_insert(zle.lastchar as u8 as char);
268    }
269}
270
271fn widget_self_insert_unmeta(zle: &mut Zle) {
272    let c = (zle.lastchar & 0x7f) as u8 as char;
273    zle.self_insert(c);
274}
275
276fn widget_forward_char(zle: &mut Zle) {
277    if zle.zlecs < zle.zlell {
278        zle.zlecs += 1;
279        zle.resetneeded = true;
280    }
281}
282
283fn widget_backward_char(zle: &mut Zle) {
284    if zle.zlecs > 0 {
285        zle.zlecs -= 1;
286        zle.resetneeded = true;
287    }
288}
289
290fn widget_forward_word(zle: &mut Zle) {
291    // Skip current word
292    while zle.zlecs < zle.zlell && is_word_char(zle.zleline[zle.zlecs]) {
293        zle.zlecs += 1;
294    }
295    // Skip non-word characters
296    while zle.zlecs < zle.zlell && !is_word_char(zle.zleline[zle.zlecs]) {
297        zle.zlecs += 1;
298    }
299    zle.resetneeded = true;
300}
301
302fn widget_backward_word(zle: &mut Zle) {
303    // Skip non-word characters
304    while zle.zlecs > 0 && !is_word_char(zle.zleline[zle.zlecs - 1]) {
305        zle.zlecs -= 1;
306    }
307    // Skip word
308    while zle.zlecs > 0 && is_word_char(zle.zleline[zle.zlecs - 1]) {
309        zle.zlecs -= 1;
310    }
311    zle.resetneeded = true;
312}
313
314fn widget_beginning_of_line(zle: &mut Zle) {
315    zle.zlecs = 0;
316    zle.resetneeded = true;
317}
318
319fn widget_end_of_line(zle: &mut Zle) {
320    zle.zlecs = zle.zlell;
321    zle.resetneeded = true;
322}
323
324fn widget_delete_char(zle: &mut Zle) {
325    if zle.zlecs < zle.zlell {
326        zle.zleline.remove(zle.zlecs);
327        zle.zlell -= 1;
328        zle.resetneeded = true;
329    }
330}
331
332fn widget_backward_delete_char(zle: &mut Zle) {
333    if zle.zlecs > 0 {
334        zle.zlecs -= 1;
335        zle.zleline.remove(zle.zlecs);
336        zle.zlell -= 1;
337        zle.resetneeded = true;
338    }
339}
340
341fn widget_delete_char_or_list(zle: &mut Zle) {
342    if zle.zlell == 0 {
343        // On empty line, send EOF
344        zle.done = true;
345    } else if zle.zlecs < zle.zlell {
346        widget_delete_char(zle);
347    } else {
348        // At end of line, list completions
349        // TODO: implement completion listing
350    }
351}
352
353fn widget_kill_line(zle: &mut Zle) {
354    if zle.zlecs < zle.zlell {
355        let killed: Vec<char> = zle.zleline.drain(zle.zlecs..).collect();
356        zle.zlell = zle.zlecs;
357        // Push to kill ring
358        zle.killring.push_front(killed);
359        if zle.killring.len() > zle.killringmax {
360            zle.killring.pop_back();
361        }
362        zle.resetneeded = true;
363    }
364}
365
366fn widget_backward_kill_line(zle: &mut Zle) {
367    if zle.zlecs > 0 {
368        let killed: Vec<char> = zle.zleline.drain(..zle.zlecs).collect();
369        zle.zlell -= zle.zlecs;
370        zle.zlecs = 0;
371        zle.killring.push_front(killed);
372        if zle.killring.len() > zle.killringmax {
373            zle.killring.pop_back();
374        }
375        zle.resetneeded = true;
376    }
377}
378
379fn widget_kill_whole_line(zle: &mut Zle) {
380    if zle.zlell > 0 {
381        let killed = std::mem::take(&mut zle.zleline);
382        zle.killring.push_front(killed);
383        if zle.killring.len() > zle.killringmax {
384            zle.killring.pop_back();
385        }
386        zle.zlecs = 0;
387        zle.zlell = 0;
388        zle.resetneeded = true;
389    }
390}
391
392fn widget_kill_word(zle: &mut Zle) {
393    let start = zle.zlecs;
394    // Skip non-word characters
395    while zle.zlecs < zle.zlell && !is_word_char(zle.zleline[zle.zlecs]) {
396        zle.zlecs += 1;
397    }
398    // Skip word
399    while zle.zlecs < zle.zlell && is_word_char(zle.zleline[zle.zlecs]) {
400        zle.zlecs += 1;
401    }
402    let end = zle.zlecs;
403    zle.zlecs = start;
404
405    if end > start {
406        let killed: Vec<char> = zle.zleline.drain(start..end).collect();
407        zle.zlell -= end - start;
408        zle.killring.push_front(killed);
409        if zle.killring.len() > zle.killringmax {
410            zle.killring.pop_back();
411        }
412        zle.resetneeded = true;
413    }
414}
415
416fn widget_backward_kill_word(zle: &mut Zle) {
417    let end = zle.zlecs;
418    // Skip non-word characters
419    while zle.zlecs > 0 && !is_word_char(zle.zleline[zle.zlecs - 1]) {
420        zle.zlecs -= 1;
421    }
422    // Skip word
423    while zle.zlecs > 0 && is_word_char(zle.zleline[zle.zlecs - 1]) {
424        zle.zlecs -= 1;
425    }
426    let start = zle.zlecs;
427
428    if end > start {
429        let killed: Vec<char> = zle.zleline.drain(start..end).collect();
430        zle.zlell -= end - start;
431        zle.killring.push_front(killed);
432        if zle.killring.len() > zle.killringmax {
433            zle.killring.pop_back();
434        }
435        zle.resetneeded = true;
436    }
437}
438
439fn widget_yank(zle: &mut Zle) {
440    if let Some(text) = zle.killring.front().cloned() {
441        for c in text {
442            zle.zleline.insert(zle.zlecs, c);
443            zle.zlecs += 1;
444            zle.zlell += 1;
445        }
446        zle.resetneeded = true;
447    }
448}
449
450fn widget_yank_pop(zle: &mut Zle) {
451    // Rotate kill ring and yank
452    if let Some(text) = zle.killring.pop_front() {
453        zle.killring.push_back(text);
454    }
455    // TODO: implement proper yank-pop (replace previous yank)
456}
457
458fn widget_undo(zle: &mut Zle) {
459    // TODO: implement undo
460    let _ = zle;
461}
462
463fn widget_redo(zle: &mut Zle) {
464    // TODO: implement redo
465    let _ = zle;
466}
467
468fn widget_up_line_or_history(zle: &mut Zle) {
469    // TODO: implement history navigation
470    let _ = zle;
471}
472
473fn widget_down_line_or_history(zle: &mut Zle) {
474    // TODO: implement history navigation
475    let _ = zle;
476}
477
478fn widget_up_history(zle: &mut Zle) {
479    // TODO: implement history navigation
480    let _ = zle;
481}
482
483fn widget_down_history(zle: &mut Zle) {
484    // TODO: implement history navigation
485    let _ = zle;
486}
487
488fn widget_history_isearch_backward(zle: &mut Zle) {
489    // TODO: implement incremental search
490    let _ = zle;
491}
492
493fn widget_history_isearch_forward(zle: &mut Zle) {
494    // TODO: implement incremental search
495    let _ = zle;
496}
497
498fn widget_beginning_of_buffer_or_history(zle: &mut Zle) {
499    zle.zlecs = 0;
500    zle.resetneeded = true;
501}
502
503fn widget_end_of_buffer_or_history(zle: &mut Zle) {
504    zle.zlecs = zle.zlell;
505    zle.resetneeded = true;
506}
507
508fn widget_transpose_chars(zle: &mut Zle) {
509    if zle.zlecs > 0 && zle.zlell >= 2 {
510        let pos = if zle.zlecs == zle.zlell {
511            zle.zlecs - 1
512        } else {
513            zle.zlecs
514        };
515        if pos > 0 {
516            zle.zleline.swap(pos - 1, pos);
517            zle.zlecs = pos + 1;
518            zle.resetneeded = true;
519        }
520    }
521}
522
523fn widget_clear_screen(zle: &mut Zle) {
524    print!("\x1b[2J\x1b[H");
525    zle.resetneeded = true;
526}
527
528fn widget_redisplay(zle: &mut Zle) {
529    zle.resetneeded = true;
530}
531
532fn widget_send_break(zle: &mut Zle) {
533    zle.send_break();
534}
535
536fn widget_overwrite_mode(zle: &mut Zle) {
537    zle.insmode = !zle.insmode;
538}
539
540fn widget_quoted_insert(zle: &mut Zle) {
541    // Read next char literally
542    if let Some(c) = zle.getfullchar(true) {
543        zle.self_insert(c);
544    }
545}
546
547fn widget_expand_or_complete(zle: &mut Zle) {
548    // TODO: implement completion
549    let _ = zle;
550}
551
552fn widget_complete_word(zle: &mut Zle) {
553    // TODO: implement completion
554    let _ = zle;
555}
556
557fn widget_expand_word(zle: &mut Zle) {
558    // TODO: implement expansion
559    let _ = zle;
560}
561
562fn widget_list_choices(zle: &mut Zle) {
563    // TODO: implement completion listing
564    let _ = zle;
565}
566
567fn widget_menu_complete(zle: &mut Zle) {
568    // TODO: implement menu completion
569    let _ = zle;
570}
571
572// Vi mode widgets
573
574fn widget_vi_cmd_mode(zle: &mut Zle) {
575    zle.keymaps.select("vicmd");
576    if zle.zlecs > 0 {
577        zle.zlecs -= 1;
578    }
579    zle.resetneeded = true;
580}
581
582fn widget_vi_insert(zle: &mut Zle) {
583    zle.keymaps.select("viins");
584    zle.insmode = true;
585}
586
587fn widget_vi_insert_bol(zle: &mut Zle) {
588    zle.keymaps.select("viins");
589    zle.insmode = true;
590    // Move to first non-blank
591    zle.zlecs = 0;
592    while zle.zlecs < zle.zlell && zle.zleline[zle.zlecs].is_whitespace() {
593        zle.zlecs += 1;
594    }
595    zle.resetneeded = true;
596}
597
598fn widget_vi_add_next(zle: &mut Zle) {
599    zle.keymaps.select("viins");
600    zle.insmode = true;
601    if zle.zlecs < zle.zlell {
602        zle.zlecs += 1;
603    }
604    zle.resetneeded = true;
605}
606
607fn widget_vi_add_eol(zle: &mut Zle) {
608    zle.keymaps.select("viins");
609    zle.insmode = true;
610    zle.zlecs = zle.zlell;
611    zle.resetneeded = true;
612}
613
614fn widget_vi_forward_char(zle: &mut Zle) {
615    if zle.zlecs < zle.zlell.saturating_sub(1) {
616        zle.zlecs += 1;
617        zle.resetneeded = true;
618    }
619}
620
621fn widget_vi_backward_char(zle: &mut Zle) {
622    if zle.zlecs > 0 {
623        zle.zlecs -= 1;
624        zle.resetneeded = true;
625    }
626}
627
628fn widget_vi_forward_word(zle: &mut Zle) {
629    widget_forward_word(zle);
630}
631
632fn widget_vi_forward_word_end(zle: &mut Zle) {
633    if zle.zlecs < zle.zlell {
634        zle.zlecs += 1;
635    }
636    // Skip non-word
637    while zle.zlecs < zle.zlell && !is_word_char(zle.zleline[zle.zlecs]) {
638        zle.zlecs += 1;
639    }
640    // Skip word
641    while zle.zlecs < zle.zlell.saturating_sub(1) && is_word_char(zle.zleline[zle.zlecs + 1]) {
642        zle.zlecs += 1;
643    }
644    zle.resetneeded = true;
645}
646
647fn widget_vi_forward_blank_word(zle: &mut Zle) {
648    // Skip non-blank
649    while zle.zlecs < zle.zlell && !zle.zleline[zle.zlecs].is_whitespace() {
650        zle.zlecs += 1;
651    }
652    // Skip blank
653    while zle.zlecs < zle.zlell && zle.zleline[zle.zlecs].is_whitespace() {
654        zle.zlecs += 1;
655    }
656    zle.resetneeded = true;
657}
658
659fn widget_vi_forward_blank_word_end(zle: &mut Zle) {
660    if zle.zlecs < zle.zlell {
661        zle.zlecs += 1;
662    }
663    // Skip whitespace
664    while zle.zlecs < zle.zlell && zle.zleline[zle.zlecs].is_whitespace() {
665        zle.zlecs += 1;
666    }
667    // Skip non-whitespace
668    while zle.zlecs < zle.zlell.saturating_sub(1) && !zle.zleline[zle.zlecs + 1].is_whitespace() {
669        zle.zlecs += 1;
670    }
671    zle.resetneeded = true;
672}
673
674fn widget_vi_backward_word(zle: &mut Zle) {
675    widget_backward_word(zle);
676}
677
678fn widget_vi_backward_blank_word(zle: &mut Zle) {
679    // Skip blanks
680    while zle.zlecs > 0 && zle.zleline[zle.zlecs - 1].is_whitespace() {
681        zle.zlecs -= 1;
682    }
683    // Skip non-blanks
684    while zle.zlecs > 0 && !zle.zleline[zle.zlecs - 1].is_whitespace() {
685        zle.zlecs -= 1;
686    }
687    zle.resetneeded = true;
688}
689
690fn widget_vi_delete(zle: &mut Zle) {
691    // TODO: implement vi delete operator
692    let _ = zle;
693}
694
695fn widget_vi_delete_char(zle: &mut Zle) {
696    widget_delete_char(zle);
697}
698
699fn widget_vi_backward_delete_char(zle: &mut Zle) {
700    widget_backward_delete_char(zle);
701}
702
703fn widget_vi_change(zle: &mut Zle) {
704    // TODO: implement vi change operator
705    let _ = zle;
706}
707
708fn widget_vi_change_eol(zle: &mut Zle) {
709    widget_kill_line(zle);
710    widget_vi_insert(zle);
711}
712
713fn widget_vi_kill_eol(zle: &mut Zle) {
714    widget_kill_line(zle);
715}
716
717fn widget_vi_yank(zle: &mut Zle) {
718    // TODO: implement vi yank operator
719    let _ = zle;
720}
721
722fn widget_vi_yank_whole_line(zle: &mut Zle) {
723    zle.killring.push_front(zle.zleline.clone());
724    if zle.killring.len() > zle.killringmax {
725        zle.killring.pop_back();
726    }
727}
728
729fn widget_vi_put_after(zle: &mut Zle) {
730    if zle.zlecs < zle.zlell {
731        zle.zlecs += 1;
732    }
733    widget_yank(zle);
734}
735
736fn widget_vi_put_before(zle: &mut Zle) {
737    widget_yank(zle);
738}
739
740fn widget_vi_replace(zle: &mut Zle) {
741    zle.keymaps.select("viins");
742    zle.insmode = false;
743}
744
745fn widget_vi_replace_chars(zle: &mut Zle) {
746    // Read replacement char
747    if let Some(c) = zle.getfullchar(true) {
748        if zle.zlecs < zle.zlell {
749            zle.zleline[zle.zlecs] = c;
750            zle.resetneeded = true;
751        }
752    }
753}
754
755fn widget_vi_substitute(zle: &mut Zle) {
756    widget_delete_char(zle);
757    widget_vi_insert(zle);
758}
759
760fn widget_vi_change_whole_line(zle: &mut Zle) {
761    widget_kill_whole_line(zle);
762    widget_vi_insert(zle);
763}
764
765fn widget_vi_first_non_blank(zle: &mut Zle) {
766    zle.zlecs = 0;
767    while zle.zlecs < zle.zlell && zle.zleline[zle.zlecs].is_whitespace() {
768        zle.zlecs += 1;
769    }
770    zle.resetneeded = true;
771}
772
773fn widget_vi_end_of_line(zle: &mut Zle) {
774    if zle.zlell > 0 {
775        zle.zlecs = zle.zlell - 1;
776    }
777    zle.resetneeded = true;
778}
779
780fn widget_vi_digit_or_beginning_of_line(zle: &mut Zle) {
781    if zle.zmod.flags.contains(super::main::ModifierFlags::MULT) {
782        widget_digit_argument(zle);
783    } else {
784        widget_beginning_of_line(zle);
785    }
786}
787
788fn widget_vi_open_line_below(zle: &mut Zle) {
789    zle.zlecs = zle.zlell;
790    zle.self_insert('\n');
791    widget_vi_insert(zle);
792}
793
794fn widget_vi_open_line_above(zle: &mut Zle) {
795    zle.zlecs = 0;
796    zle.self_insert('\n');
797    zle.zlecs = 0;
798    widget_vi_insert(zle);
799}
800
801fn widget_vi_join(zle: &mut Zle) {
802    // Find newline and remove it
803    while zle.zlecs < zle.zlell {
804        if zle.zleline[zle.zlecs] == '\n' {
805            zle.zleline.remove(zle.zlecs);
806            zle.zlell -= 1;
807            // Insert space if needed
808            if zle.zlecs > 0 && zle.zlecs < zle.zlell {
809                zle.zleline.insert(zle.zlecs, ' ');
810                zle.zlell += 1;
811            }
812            break;
813        }
814        zle.zlecs += 1;
815    }
816    zle.resetneeded = true;
817}
818
819fn widget_vi_repeat_change(zle: &mut Zle) {
820    // TODO: implement vi repeat change
821    let _ = zle;
822}
823
824fn widget_vi_find_next_char(zle: &mut Zle) {
825    // TODO: implement vi find next char
826    let _ = zle;
827}
828
829fn widget_vi_find_prev_char(zle: &mut Zle) {
830    // TODO: implement vi find prev char
831    let _ = zle;
832}
833
834fn widget_vi_find_next_char_skip(zle: &mut Zle) {
835    // TODO: implement vi find next char skip
836    let _ = zle;
837}
838
839fn widget_vi_find_prev_char_skip(zle: &mut Zle) {
840    // TODO: implement vi find prev char skip
841    let _ = zle;
842}
843
844fn widget_vi_repeat_find(zle: &mut Zle) {
845    // TODO: implement vi repeat find
846    let _ = zle;
847}
848
849fn widget_vi_rev_repeat_find(zle: &mut Zle) {
850    // TODO: implement vi reverse repeat find
851    let _ = zle;
852}
853
854fn widget_vi_history_search_forward(zle: &mut Zle) {
855    // TODO: implement vi history search
856    let _ = zle;
857}
858
859fn widget_vi_history_search_backward(zle: &mut Zle) {
860    // TODO: implement vi history search
861    let _ = zle;
862}
863
864fn widget_vi_repeat_search(zle: &mut Zle) {
865    // TODO: implement vi repeat search
866    let _ = zle;
867}
868
869fn widget_vi_rev_repeat_search(zle: &mut Zle) {
870    // TODO: implement vi reverse repeat search
871    let _ = zle;
872}
873
874fn widget_vi_fetch_history(zle: &mut Zle) {
875    // TODO: implement vi fetch history
876    let _ = zle;
877}
878
879fn widget_vi_goto_column(zle: &mut Zle) {
880    let col = zle.zmod.mult.saturating_sub(1) as usize;
881    zle.zlecs = col.min(zle.zlell);
882    zle.resetneeded = true;
883}
884
885fn widget_vi_backward_kill_word(zle: &mut Zle) {
886    widget_backward_kill_word(zle);
887}
888
889fn widget_digit_argument(zle: &mut Zle) {
890    let digit = (zle.lastchar as u8).saturating_sub(b'0') as i32;
891
892    if zle.zmod.flags.contains(super::main::ModifierFlags::TMULT) {
893        zle.zmod.tmult = zle.zmod.tmult * zle.zmod.base + digit;
894    } else {
895        zle.zmod.flags.insert(super::main::ModifierFlags::TMULT);
896        zle.zmod.tmult = digit;
897    }
898
899    zle.prefixflag = true;
900}
901
902fn widget_undefined(zle: &mut Zle) {
903    // Beep or do nothing
904    let _ = zle;
905}
906
907/// Check if a character is a word character
908fn is_word_char(c: char) -> bool {
909    c.is_alphanumeric() || c == '_'
910}