Skip to main content

zsh/zle/
widgets.rs

1//! ZLE Widget implementations
2
3use super::ZleState;
4
5/// Result of executing a widget
6#[derive(Debug, Clone)]
7pub enum WidgetResult {
8    /// Widget executed successfully
9    Ok,
10    /// Widget needs to call a shell function
11    CallFunction(String),
12    /// Accept the line (execute command)
13    Accept,
14    /// Abort/break
15    Abort,
16    /// Key sequence is incomplete, wait for more input
17    Pending,
18    /// Key was not handled
19    Ignored,
20    /// Error occurred
21    Error(String),
22    /// Refresh display
23    Refresh,
24    /// Clear screen
25    Clear,
26    /// Trigger completion (expand-or-complete, complete-word)
27    TriggerCompletion,
28    /// Menu complete (cycle forward)
29    MenuComplete,
30    /// Reverse menu complete (cycle backward)
31    ReverseMenuComplete,
32}
33
34/// A ZLE widget
35#[derive(Debug, Clone)]
36pub enum Widget {
37    /// Built-in widget
38    Builtin(BuiltinWidget),
39    /// User-defined widget (shell function name)
40    User(String),
41}
42
43/// Built-in widget types - all ZLE widgets from `man zshzle`
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
45pub enum BuiltinWidget {
46    // =========================================================================
47    // Movement
48    // =========================================================================
49    ForwardChar,
50    BackwardChar,
51    ForwardWord,
52    BackwardWord,
53    EmacsForwardWord,
54    EmacsBackwardWord,
55    BeginningOfLine,
56    EndOfLine,
57    ViBeginningOfLine,
58    ViEndOfLine,
59    ViFirstNonBlank,
60    ViForwardChar,
61    ViBackwardChar,
62    ViForwardWord,
63    ViBackwardWord,
64    ViForwardWordEnd,
65    ViBackwardWordEnd,
66    ViForwardBlankWord,
67    ViBackwardBlankWord,
68    ViForwardBlankWordEnd,
69    ViBackwardBlankWordEnd,
70    ViFindNextChar,
71    ViFindNextCharSkip,
72    ViFindPrevChar,
73    ViFindPrevCharSkip,
74    ViRepeatFind,
75    ViRevRepeatFind,
76    ViGotoColumn,
77    ViGotoMark,
78    ViGotoMarkLine,
79    UpLine,
80    DownLine,
81
82    // =========================================================================
83    // History Navigation
84    // =========================================================================
85    UpLineOrHistory,
86    DownLineOrHistory,
87    ViUpLineOrHistory,
88    ViDownLineOrHistory,
89    UpLineOrSearch,
90    DownLineOrSearch,
91    UpHistory,
92    DownHistory,
93    BeginningOfHistory,
94    EndOfHistory,
95    BeginningOfBufferOrHistory,
96    EndOfBufferOrHistory,
97    BeginningOfLineHist,
98    EndOfLineHist,
99    ViFetchHistory,
100    HistoryIncrementalSearchBackward,
101    HistoryIncrementalSearchForward,
102    HistoryIncrementalPatternSearchBackward,
103    HistoryIncrementalPatternSearchForward,
104    HistorySearchBackward,
105    HistorySearchForward,
106    ViHistorySearchBackward,
107    ViHistorySearchForward,
108    HistoryBeginningSearchBackward,
109    HistoryBeginningSearchForward,
110    InferNextHistory,
111    InsertLastWord,
112    ViRepeatSearch,
113    ViRevRepeatSearch,
114    SetLocalHistory,
115
116    // =========================================================================
117    // Editing - Insert/Delete
118    // =========================================================================
119    SelfInsert,
120    SelfInsertUnmeta,
121    QuotedInsert,
122    ViQuotedInsert,
123    DeleteChar,
124    BackwardDeleteChar,
125    ViDeleteChar,
126    ViBackwardDeleteChar,
127    DeleteWord,
128    BackwardDeleteWord,
129
130    // =========================================================================
131    // Editing - Kill/Yank
132    // =========================================================================
133    KillLine,
134    BackwardKillLine,
135    ViKillLine,
136    ViKillEol,
137    KillWord,
138    BackwardKillWord,
139    ViBackwardKillWord,
140    KillWholeLine,
141    KillBuffer,
142    KillRegion,
143    CopyRegionAsKill,
144    CopyPrevWord,
145    CopyPrevShellWord,
146    Yank,
147    YankPop,
148    ViYank,
149    ViYankWholeLine,
150    ViYankEol,
151    ViPutBefore,
152    ViPutAfter,
153    PutReplaceSelection,
154
155    // =========================================================================
156    // Editing - Case change
157    // =========================================================================
158    CapitalizeWord,
159    DownCaseWord,
160    UpCaseWord,
161    ViDownCase,
162    ViUpCase,
163    ViSwapCase,
164    ViOperSwapCase,
165
166    // =========================================================================
167    // Editing - Transpose
168    // =========================================================================
169    TransposeChars,
170    TransposeWords,
171    GosmacsTransposeChars,
172
173    // =========================================================================
174    // Editing - Vi operators/changes
175    // =========================================================================
176    ViChange,
177    ViChangeEol,
178    ViChangeWholeLine,
179    ViDelete,
180    ViIndent,
181    ViUnindent,
182    ViSubstitute,
183    ViAddNext,
184    ViAddEol,
185    ViInsert,
186    ViInsertBol,
187    ViOpenLineAbove,
188    ViOpenLineBelow,
189    ViReplace,
190    ViReplaceChars,
191    ViRepeatChange,
192    ViJoin,
193    ViMatchBracket,
194
195    // =========================================================================
196    // Undo/Redo
197    // =========================================================================
198    Undo,
199    Redo,
200    ViUndoChange,
201    SplitUndo,
202
203    // =========================================================================
204    // Completion
205    // =========================================================================
206    ExpandOrComplete,
207    ExpandOrCompletePrefix,
208    CompleteWord,
209    MenuComplete,
210    MenuExpandOrComplete,
211    ReverseMenuComplete,
212    AcceptAndMenuComplete,
213    DeleteCharOrList,
214    ExpandCmdPath,
215    ExpandHistory,
216    ExpandWord,
217    ListChoices,
218    ListExpand,
219    MagicSpace,
220    EndOfList,
221
222    // =========================================================================
223    // Accept/Execute
224    // =========================================================================
225    AcceptLine,
226    AcceptAndHold,
227    AcceptAndInferNextHistory,
228    AcceptLineAndDownHistory,
229    SendBreak,
230
231    // =========================================================================
232    // Mode switching
233    // =========================================================================
234    ViCmdMode,
235    ViCapsLockPanic,
236
237    // =========================================================================
238    // Numeric argument
239    // =========================================================================
240    DigitArgument,
241    NegArgument,
242    UniversalArgument,
243    ArgumentBase,
244    ViDigitOrBeginningOfLine,
245
246    // =========================================================================
247    // Marks and Region
248    // =========================================================================
249    SetMarkCommand,
250    ExchangePointAndMark,
251    ViSetMark,
252    ViSetBuffer,
253    DeactivateRegion,
254    VisualMode,
255    VisualLineMode,
256    SelectAWord,
257    SelectABlankWord,
258    SelectAShellWord,
259    SelectInWord,
260    SelectInBlankWord,
261    SelectInShellWord,
262
263    // =========================================================================
264    // Miscellaneous
265    // =========================================================================
266    ClearScreen,
267    Redisplay,
268    ResetPrompt,
269    OverwriteMode,
270    UndefinedKey,
271    BracketedPaste,
272    PushLine,
273    PushLineOrEdit,
274    PushInput,
275    GetLine,
276    PoundInsert,
277    ViPoundInsert,
278    QuoteLine,
279    QuoteRegion,
280    ReadCommand,
281    RecursiveEdit,
282    RunHelp,
283    SpellWord,
284    WhatCursorPosition,
285    WhereIs,
286    WhichCommand,
287    ExecuteNamedCmd,
288    ExecuteLastNamedCmd,
289    DescribeKeyBriefly,
290    AutoSuffixRemove,
291    AutoSuffixRetain,
292
293    // =========================================================================
294    // Delete-to-char (Emacs zap-to-char)
295    // =========================================================================
296    DeleteToChar,
297    ZapToChar,
298
299    // =========================================================================
300    // Special hooks (user-defined but special names)
301    // =========================================================================
302    ZleLineInit,
303    ZleLineFinish,
304    ZleLinePreRedraw,
305    ZleKeymapSelect,
306    ZleHistoryLineSet,
307    ZleIsearchUpdate,
308    ZleIsearchExit,
309}
310
311/// Execute a builtin widget
312pub fn execute_builtin(
313    state: &mut ZleState,
314    widget: BuiltinWidget,
315    key: Option<char>,
316) -> WidgetResult {
317    state.save_undo();
318
319    match widget {
320        // Movement
321        BuiltinWidget::ForwardChar => {
322            let n = state.numeric_arg.unwrap_or(1).unsigned_abs() as usize;
323            let chars: Vec<char> = state.buffer.chars().collect();
324            state.cursor = (state.cursor + n).min(chars.len());
325            state.numeric_arg = None;
326            WidgetResult::Ok
327        }
328        BuiltinWidget::BackwardChar => {
329            let n = state.numeric_arg.unwrap_or(1).unsigned_abs() as usize;
330            state.cursor = state.cursor.saturating_sub(n);
331            state.numeric_arg = None;
332            WidgetResult::Ok
333        }
334        BuiltinWidget::ForwardWord => {
335            let chars: Vec<char> = state.buffer.chars().collect();
336            let mut pos = state.cursor;
337            // Skip non-word chars
338            while pos < chars.len() && !chars[pos].is_alphanumeric() {
339                pos += 1;
340            }
341            // Skip word chars
342            while pos < chars.len() && chars[pos].is_alphanumeric() {
343                pos += 1;
344            }
345            state.cursor = pos;
346            WidgetResult::Ok
347        }
348        BuiltinWidget::BackwardWord => {
349            let chars: Vec<char> = state.buffer.chars().collect();
350            let mut pos = state.cursor;
351            // Skip non-word chars
352            while pos > 0 && !chars[pos.saturating_sub(1)].is_alphanumeric() {
353                pos -= 1;
354            }
355            // Skip word chars
356            while pos > 0 && chars[pos.saturating_sub(1)].is_alphanumeric() {
357                pos -= 1;
358            }
359            state.cursor = pos;
360            WidgetResult::Ok
361        }
362        BuiltinWidget::BeginningOfLine => {
363            state.cursor = 0;
364            WidgetResult::Ok
365        }
366        BuiltinWidget::EndOfLine => {
367            state.cursor = state.buffer.chars().count();
368            WidgetResult::Ok
369        }
370
371        // Editing
372        BuiltinWidget::SelfInsert => {
373            if let Some(c) = key {
374                let chars: Vec<char> = state.buffer.chars().collect();
375                let mut new_buffer = String::new();
376                for (i, ch) in chars.iter().enumerate() {
377                    if i == state.cursor {
378                        new_buffer.push(c);
379                    }
380                    new_buffer.push(*ch);
381                }
382                if state.cursor >= chars.len() {
383                    new_buffer.push(c);
384                }
385                state.buffer = new_buffer;
386                state.cursor += 1;
387                WidgetResult::Ok
388            } else {
389                WidgetResult::Ignored
390            }
391        }
392        BuiltinWidget::DeleteChar => {
393            let chars: Vec<char> = state.buffer.chars().collect();
394            if state.cursor < chars.len() {
395                let mut new_buffer = String::new();
396                for (i, ch) in chars.iter().enumerate() {
397                    if i != state.cursor {
398                        new_buffer.push(*ch);
399                    }
400                }
401                state.buffer = new_buffer;
402            }
403            WidgetResult::Ok
404        }
405        BuiltinWidget::BackwardDeleteChar => {
406            if state.cursor > 0 {
407                let chars: Vec<char> = state.buffer.chars().collect();
408                let mut new_buffer = String::new();
409                for (i, ch) in chars.iter().enumerate() {
410                    if i != state.cursor - 1 {
411                        new_buffer.push(*ch);
412                    }
413                }
414                state.buffer = new_buffer;
415                state.cursor -= 1;
416            }
417            WidgetResult::Ok
418        }
419        BuiltinWidget::KillLine => {
420            let chars: Vec<char> = state.buffer.chars().collect();
421            let killed: String = chars[state.cursor..].iter().collect();
422            state.kill_add(&killed);
423            state.buffer = chars[..state.cursor].iter().collect();
424            WidgetResult::Ok
425        }
426        BuiltinWidget::BackwardKillLine => {
427            let chars: Vec<char> = state.buffer.chars().collect();
428            let killed: String = chars[..state.cursor].iter().collect();
429            state.kill_add(&killed);
430            state.buffer = chars[state.cursor..].iter().collect();
431            state.cursor = 0;
432            WidgetResult::Ok
433        }
434        BuiltinWidget::KillWord => {
435            let chars: Vec<char> = state.buffer.chars().collect();
436            let mut end = state.cursor;
437            // Skip non-word chars
438            while end < chars.len() && !chars[end].is_alphanumeric() {
439                end += 1;
440            }
441            // Skip word chars
442            while end < chars.len() && chars[end].is_alphanumeric() {
443                end += 1;
444            }
445            let killed: String = chars[state.cursor..end].iter().collect();
446            state.kill_add(&killed);
447            let mut new_buffer: String = chars[..state.cursor].iter().collect();
448            new_buffer.push_str(&chars[end..].iter().collect::<String>());
449            state.buffer = new_buffer;
450            WidgetResult::Ok
451        }
452        BuiltinWidget::BackwardKillWord => {
453            let chars: Vec<char> = state.buffer.chars().collect();
454            let mut start = state.cursor;
455            // Skip non-word chars
456            while start > 0 && !chars[start.saturating_sub(1)].is_alphanumeric() {
457                start -= 1;
458            }
459            // Skip word chars
460            while start > 0 && chars[start.saturating_sub(1)].is_alphanumeric() {
461                start -= 1;
462            }
463            let killed: String = chars[start..state.cursor].iter().collect();
464            state.kill_add(&killed);
465            let mut new_buffer: String = chars[..start].iter().collect();
466            new_buffer.push_str(&chars[state.cursor..].iter().collect::<String>());
467            state.buffer = new_buffer;
468            state.cursor = start;
469            WidgetResult::Ok
470        }
471        BuiltinWidget::KillWholeLine => {
472            let buffer = state.buffer.clone();
473            state.kill_add(&buffer);
474            state.buffer.clear();
475            state.cursor = 0;
476            WidgetResult::Ok
477        }
478        BuiltinWidget::Yank => {
479            if let Some(text) = state.yank() {
480                let text = text.to_string();
481                let chars: Vec<char> = state.buffer.chars().collect();
482                let mut new_buffer: String = chars[..state.cursor].iter().collect();
483                new_buffer.push_str(&text);
484                new_buffer.push_str(&chars[state.cursor..].iter().collect::<String>());
485                state.cursor += text.chars().count();
486                state.buffer = new_buffer;
487            }
488            WidgetResult::Ok
489        }
490        BuiltinWidget::YankPop => {
491            if let Some(text) = state.yank_pop() {
492                let text = text.to_string();
493                // Would need to track last yank position
494                let chars: Vec<char> = state.buffer.chars().collect();
495                let mut new_buffer: String = chars[..state.cursor].iter().collect();
496                new_buffer.push_str(&text);
497                new_buffer.push_str(&chars[state.cursor..].iter().collect::<String>());
498                state.cursor += text.chars().count();
499                state.buffer = new_buffer;
500            }
501            WidgetResult::Ok
502        }
503
504        // Undo
505        BuiltinWidget::Undo => {
506            // Pop the undo we just saved at the start
507            state.undo_stack.pop();
508            state.undo();
509            WidgetResult::Ok
510        }
511        BuiltinWidget::Redo => {
512            state.undo_stack.pop();
513            state.redo();
514            WidgetResult::Ok
515        }
516
517        // History (would need history integration)
518        BuiltinWidget::UpLineOrHistory => WidgetResult::Ok,
519        BuiltinWidget::DownLineOrHistory => WidgetResult::Ok,
520        BuiltinWidget::BeginningOfHistory => WidgetResult::Ok,
521        BuiltinWidget::EndOfHistory => WidgetResult::Ok,
522        BuiltinWidget::HistoryIncrementalSearchBackward => WidgetResult::Ok,
523        BuiltinWidget::HistoryIncrementalSearchForward => WidgetResult::Ok,
524
525        // Completion - trigger compsys
526        BuiltinWidget::ExpandOrComplete
527        | BuiltinWidget::ExpandOrCompletePrefix
528        | BuiltinWidget::CompleteWord
529        | BuiltinWidget::ExpandWord
530        | BuiltinWidget::ExpandCmdPath
531        | BuiltinWidget::ListChoices
532        | BuiltinWidget::ListExpand => WidgetResult::TriggerCompletion,
533        BuiltinWidget::MenuComplete
534        | BuiltinWidget::MenuExpandOrComplete
535        | BuiltinWidget::AcceptAndMenuComplete => WidgetResult::MenuComplete,
536        BuiltinWidget::ReverseMenuComplete => WidgetResult::ReverseMenuComplete,
537        BuiltinWidget::DeleteCharOrList => {
538            let chars: Vec<char> = state.buffer.chars().collect();
539            if state.cursor >= chars.len() {
540                WidgetResult::TriggerCompletion
541            } else {
542                let mut new_buffer = String::new();
543                for (i, ch) in chars.iter().enumerate() {
544                    if i != state.cursor {
545                        new_buffer.push(*ch);
546                    }
547                }
548                state.buffer = new_buffer;
549                WidgetResult::Ok
550            }
551        }
552        BuiltinWidget::ExpandHistory | BuiltinWidget::MagicSpace | BuiltinWidget::EndOfList => {
553            WidgetResult::Ok
554        }
555
556        // Accept/Execute
557        BuiltinWidget::AcceptLine => WidgetResult::Accept,
558        BuiltinWidget::AcceptAndHold
559        | BuiltinWidget::AcceptAndInferNextHistory
560        | BuiltinWidget::AcceptLineAndDownHistory => WidgetResult::Accept,
561        BuiltinWidget::SendBreak => WidgetResult::Abort,
562
563        // Misc
564        BuiltinWidget::ClearScreen => WidgetResult::Clear,
565        BuiltinWidget::Redisplay => WidgetResult::Refresh,
566        BuiltinWidget::TransposeChars => {
567            let chars: Vec<char> = state.buffer.chars().collect();
568            if state.cursor > 0 && state.cursor < chars.len() {
569                let mut new_chars = chars.clone();
570                new_chars.swap(state.cursor - 1, state.cursor);
571                state.buffer = new_chars.iter().collect();
572                state.cursor += 1;
573            } else if state.cursor >= 2 && state.cursor == chars.len() {
574                let mut new_chars = chars.clone();
575                new_chars.swap(state.cursor - 2, state.cursor - 1);
576                state.buffer = new_chars.iter().collect();
577            }
578            WidgetResult::Ok
579        }
580        BuiltinWidget::TransposeWords => {
581            // Complex - would need word boundary detection
582            WidgetResult::Ok
583        }
584        BuiltinWidget::CapitalizeWord => {
585            let chars: Vec<char> = state.buffer.chars().collect();
586            let mut new_buffer = String::new();
587            let mut pos = state.cursor;
588            let mut first = true;
589
590            // Copy before cursor
591            for ch in &chars[..pos] {
592                new_buffer.push(*ch);
593            }
594
595            // Skip non-word
596            while pos < chars.len() && !chars[pos].is_alphanumeric() {
597                new_buffer.push(chars[pos]);
598                pos += 1;
599            }
600
601            // Capitalize first, lowercase rest
602            while pos < chars.len() && chars[pos].is_alphanumeric() {
603                if first {
604                    new_buffer.extend(chars[pos].to_uppercase());
605                    first = false;
606                } else {
607                    new_buffer.extend(chars[pos].to_lowercase());
608                }
609                pos += 1;
610            }
611
612            // Copy rest
613            for ch in &chars[pos..] {
614                new_buffer.push(*ch);
615            }
616
617            state.buffer = new_buffer;
618            state.cursor = pos;
619            WidgetResult::Ok
620        }
621        BuiltinWidget::DownCaseWord => {
622            let chars: Vec<char> = state.buffer.chars().collect();
623            let mut new_buffer = String::new();
624            let mut pos = state.cursor;
625
626            for ch in &chars[..pos] {
627                new_buffer.push(*ch);
628            }
629
630            while pos < chars.len() && !chars[pos].is_alphanumeric() {
631                new_buffer.push(chars[pos]);
632                pos += 1;
633            }
634
635            while pos < chars.len() && chars[pos].is_alphanumeric() {
636                new_buffer.extend(chars[pos].to_lowercase());
637                pos += 1;
638            }
639
640            for ch in &chars[pos..] {
641                new_buffer.push(*ch);
642            }
643
644            state.buffer = new_buffer;
645            state.cursor = pos;
646            WidgetResult::Ok
647        }
648        BuiltinWidget::UpCaseWord => {
649            let chars: Vec<char> = state.buffer.chars().collect();
650            let mut new_buffer = String::new();
651            let mut pos = state.cursor;
652
653            for ch in &chars[..pos] {
654                new_buffer.push(*ch);
655            }
656
657            while pos < chars.len() && !chars[pos].is_alphanumeric() {
658                new_buffer.push(chars[pos]);
659                pos += 1;
660            }
661
662            while pos < chars.len() && chars[pos].is_alphanumeric() {
663                new_buffer.extend(chars[pos].to_uppercase());
664                pos += 1;
665            }
666
667            for ch in &chars[pos..] {
668                new_buffer.push(*ch);
669            }
670
671            state.buffer = new_buffer;
672            state.cursor = pos;
673            WidgetResult::Ok
674        }
675        BuiltinWidget::QuotedInsert => {
676            // Next character should be inserted literally
677            WidgetResult::Pending
678        }
679        BuiltinWidget::ViCmdMode => {
680            state.vi_cmd_mode = true;
681            state.keymap = super::KeymapName::ViCommand;
682            WidgetResult::Ok
683        }
684        BuiltinWidget::ViInsert => {
685            state.vi_cmd_mode = false;
686            state.keymap = super::KeymapName::ViInsert;
687            WidgetResult::Ok
688        }
689        BuiltinWidget::SetMarkCommand => {
690            state.mark = state.cursor;
691            state.region_active = true;
692            WidgetResult::Ok
693        }
694        BuiltinWidget::ExchangePointAndMark => {
695            std::mem::swap(&mut state.cursor, &mut state.mark);
696            WidgetResult::Ok
697        }
698        BuiltinWidget::KillRegion => {
699            if state.region_active {
700                let (start, end) = if state.cursor < state.mark {
701                    (state.cursor, state.mark)
702                } else {
703                    (state.mark, state.cursor)
704                };
705                let chars: Vec<char> = state.buffer.chars().collect();
706                let killed: String = chars[start..end].iter().collect();
707                state.kill_add(&killed);
708                let mut new_buffer: String = chars[..start].iter().collect();
709                new_buffer.push_str(&chars[end..].iter().collect::<String>());
710                state.buffer = new_buffer;
711                state.cursor = start;
712                state.region_active = false;
713            }
714            WidgetResult::Ok
715        }
716        BuiltinWidget::CopyRegionAsKill => {
717            if state.region_active {
718                let (start, end) = if state.cursor < state.mark {
719                    (state.cursor, state.mark)
720                } else {
721                    (state.mark, state.cursor)
722                };
723                let chars: Vec<char> = state.buffer.chars().collect();
724                let copied: String = chars[start..end].iter().collect();
725                state.kill_add(&copied);
726                state.region_active = false;
727            }
728            WidgetResult::Ok
729        }
730
731        // Delete-to-char / Zap-to-char (Emacs style)
732        BuiltinWidget::DeleteToChar | BuiltinWidget::ZapToChar => {
733            // This widget needs a character argument - would be read from next key press
734            // For now, return Pending to indicate we need more input
735            WidgetResult::Pending
736        }
737
738        // Unimplemented widgets return Ok to avoid breaking the editor
739        _ => WidgetResult::Ok,
740    }
741}
742
743/// Delete to a specified character (implementation for delete-to-char/zap-to-char)
744/// `zap` parameter: if true, don't include the target character in the deletion
745pub fn delete_to_char(state: &mut ZleState, target: char, count: i32, zap: bool) -> WidgetResult {
746    state.save_undo();
747    let chars: Vec<char> = state.buffer.chars().collect();
748    let mut dest = state.cursor;
749
750    if count > 0 {
751        let mut remaining = count;
752        while remaining > 0 && dest < chars.len() {
753            while dest < chars.len() && chars[dest] != target {
754                dest += 1;
755            }
756            if dest < chars.len() {
757                if !zap || remaining > 1 {
758                    dest += 1;
759                }
760                remaining -= 1;
761                if remaining == 0 {
762                    let killed: String = chars[state.cursor..dest].iter().collect();
763                    state.kill_add(&killed);
764                    let mut new_buffer: String = chars[..state.cursor].iter().collect();
765                    new_buffer.push_str(&chars[dest..].iter().collect::<String>());
766                    state.buffer = new_buffer;
767                    return WidgetResult::Ok;
768                }
769            }
770        }
771    } else {
772        if dest > 0 {
773            dest -= 1;
774        }
775        let mut remaining = -count;
776        while remaining > 0 && dest > 0 {
777            while dest > 0 && chars[dest] != target {
778                dest -= 1;
779            }
780            if chars[dest] == target {
781                remaining -= 1;
782                if remaining == 0 {
783                    let adjust = if zap { 1 } else { 0 };
784                    let killed: String = chars[dest + adjust..state.cursor].iter().collect();
785                    state.kill_add(&killed);
786                    let mut new_buffer: String = chars[..dest + adjust].iter().collect();
787                    new_buffer.push_str(&chars[state.cursor..].iter().collect::<String>());
788                    state.buffer = new_buffer;
789                    state.cursor = dest + adjust;
790                    return WidgetResult::Ok;
791                }
792                if dest > 0 {
793                    dest -= 1;
794                }
795            }
796        }
797    }
798
799    WidgetResult::Ok
800}