Skip to main content

zsh/zle/
misc.rs

1//! ZLE miscellaneous operations
2//!
3//! Direct port from zsh/Src/Zle/zle_misc.c
4//!
5//! Implements misc editing widgets:
6//! - self-insert, self-insert-unmeta
7//! - accept-line, accept-and-hold
8//! - quoted-insert, bracketed-paste
9//! - delete-char, backward-delete-char
10//! - kill-line, backward-kill-line, kill-buffer, kill-whole-line
11//! - copy-region-as-kill, kill-region
12//! - yank, yank-pop
13//! - transpose-chars, quote-line, quote-region
14//! - what-cursor-position, universal-argument, digit-argument
15//! - undefined-key, send-break
16//! - vi-put-after, vi-put-before, overwrite-mode
17
18use super::main::Zle;
19
20/// Clipboard/paste buffer for yank operations
21#[derive(Debug, Default)]
22pub struct PasteBuffer {
23    pub content: Vec<char>,
24}
25
26impl Zle {
27    /// Self insert - insert the typed character
28    /// Port of selfinsert() from zle_misc.c
29    pub fn self_insert(&mut self, c: char) {
30        self.zleline.insert(self.zlecs, c);
31        self.zlecs += 1;
32        self.zlell += 1;
33        self.resetneeded = true;
34    }
35
36    /// Self insert unmeta - insert character with meta bit stripped
37    /// Port of selfinsertunmeta() from zle_misc.c
38    pub fn self_insert_unmeta(&mut self, c: char) {
39        let unmetaed = if (c as u32) >= 0x80 && (c as u32) < 0x100 {
40            char::from_u32((c as u32) & 0x7f).unwrap_or(c)
41        } else {
42            c
43        };
44        self.self_insert(unmetaed);
45    }
46
47    /// Accept line - return the current line for execution
48    /// Port of acceptline() from zle_misc.c
49    pub fn accept_line(&self) -> String {
50        self.zleline.iter().collect()
51    }
52
53    /// Accept and hold - accept line but keep it in the buffer
54    /// Port of acceptandhold() from zle_misc.c
55    pub fn accept_and_hold(&self) -> String {
56        self.zleline.iter().collect()
57    }
58
59    /// Quoted insert - insert next char literally
60    /// Port of quotedinsert() from zle_misc.c
61    pub fn quoted_insert(&mut self, c: char) {
62        self.zleline.insert(self.zlecs, c);
63        self.zlecs += 1;
64        self.zlell += 1;
65        self.resetneeded = true;
66    }
67
68    /// Bracketed paste - handle paste mode
69    /// Port of bracketedpaste() from zle_misc.c
70    pub fn bracketed_paste(&mut self, text: &str) {
71        for c in text.chars() {
72            if c != '\x1b' {
73                self.zleline.insert(self.zlecs, c);
74                self.zlecs += 1;
75                self.zlell += 1;
76            }
77        }
78        self.resetneeded = true;
79    }
80
81    /// Delete char under cursor
82    /// Port of deletechar() from zle_misc.c
83    pub fn delete_char(&mut self) {
84        if self.zlecs < self.zlell {
85            self.zleline.remove(self.zlecs);
86            self.zlell -= 1;
87            self.resetneeded = true;
88        }
89    }
90
91    /// Delete char before cursor
92    /// Port of backwarddeletechar() from zle_misc.c
93    pub fn backward_delete_char(&mut self) {
94        if self.zlecs > 0 {
95            self.zlecs -= 1;
96            self.zleline.remove(self.zlecs);
97            self.zlell -= 1;
98            self.resetneeded = true;
99        }
100    }
101
102    /// Kill from cursor to end of line
103    /// Port of killline() from zle_misc.c
104    pub fn kill_line(&mut self) {
105        if self.zlecs < self.zlell {
106            let text: Vec<char> = self.zleline.drain(self.zlecs..).collect();
107            self.killring.push_front(text);
108            if self.killring.len() > self.killringmax {
109                self.killring.pop_back();
110            }
111            self.zlell = self.zlecs;
112            self.resetneeded = true;
113        }
114    }
115
116    /// Kill from beginning of line to cursor
117    /// Port of backwardkillline() from zle_misc.c
118    pub fn backward_kill_line(&mut self) {
119        if self.zlecs > 0 {
120            let text: Vec<char> = self.zleline.drain(..self.zlecs).collect();
121            self.killring.push_front(text);
122            if self.killring.len() > self.killringmax {
123                self.killring.pop_back();
124            }
125            self.zlell -= self.zlecs;
126            self.zlecs = 0;
127            self.resetneeded = true;
128        }
129    }
130
131    /// Kill entire buffer
132    /// Port of killbuffer() from zle_misc.c
133    pub fn kill_buffer(&mut self) {
134        if !self.zleline.is_empty() {
135            let text: Vec<char> = self.zleline.drain(..).collect();
136            self.killring.push_front(text);
137            if self.killring.len() > self.killringmax {
138                self.killring.pop_back();
139            }
140            self.zlell = 0;
141            self.zlecs = 0;
142            self.mark = 0;
143            self.resetneeded = true;
144        }
145    }
146
147    /// Kill whole line (including newlines in multi-line mode)
148    /// Port of killwholeline() from zle_misc.c
149    pub fn kill_whole_line(&mut self) {
150        self.kill_buffer();
151    }
152
153    /// Exchange point and mark
154    pub fn exchange_point_and_mark(&mut self) {
155        std::mem::swap(&mut self.zlecs, &mut self.mark);
156        self.resetneeded = true;
157    }
158
159    /// Set mark at current position
160    pub fn set_mark_here(&mut self) {
161        self.mark = self.zlecs;
162    }
163
164    /// Copy region as kill
165    /// Port of copyregionaskill() from zle_misc.c
166    pub fn copy_region_as_kill(&mut self) {
167        let (start, end) = if self.zlecs < self.mark {
168            (self.zlecs, self.mark)
169        } else {
170            (self.mark, self.zlecs)
171        };
172
173        let text: Vec<char> = self.zleline[start..end].to_vec();
174        self.killring.push_front(text);
175        if self.killring.len() > self.killringmax {
176            self.killring.pop_back();
177        }
178    }
179
180    /// Kill region (between point and mark)
181    /// Port of killregion() from zle_misc.c
182    pub fn kill_region(&mut self) {
183        let (start, end) = if self.zlecs < self.mark {
184            (self.zlecs, self.mark)
185        } else {
186            (self.mark, self.zlecs)
187        };
188
189        let text: Vec<char> = self.zleline.drain(start..end).collect();
190        self.killring.push_front(text);
191        if self.killring.len() > self.killringmax {
192            self.killring.pop_back();
193        }
194
195        self.zlell -= end - start;
196        self.zlecs = start;
197        self.mark = start;
198        self.resetneeded = true;
199    }
200
201    /// Yank - insert from kill ring
202    /// Port of yank() from zle_misc.c
203    pub fn yank(&mut self) {
204        if let Some(text) = self.killring.front() {
205            self.mark = self.zlecs;
206            for &c in text {
207                self.zleline.insert(self.zlecs, c);
208                self.zlecs += 1;
209            }
210            self.zlell = self.zleline.len();
211            self.yanklast = true;
212            self.resetneeded = true;
213        }
214    }
215
216    /// Yank pop - cycle through kill ring
217    /// Port of yankpop() from zle_misc.c
218    pub fn yank_pop(&mut self) {
219        if !self.yanklast || self.killring.is_empty() {
220            return;
221        }
222
223        // Remove previously yanked text
224        let prev_len = self.killring.front().map(|v| v.len()).unwrap_or(0);
225        let start = self.mark;
226        for _ in 0..prev_len {
227            if start < self.zleline.len() {
228                self.zleline.remove(start);
229            }
230        }
231        self.zlecs = start;
232        self.zlell = self.zleline.len();
233
234        // Rotate kill ring
235        if let Some(front) = self.killring.pop_front() {
236            self.killring.push_back(front);
237        }
238
239        // Insert new text
240        if let Some(text) = self.killring.front() {
241            for &c in text {
242                self.zleline.insert(self.zlecs, c);
243                self.zlecs += 1;
244            }
245            self.zlell = self.zleline.len();
246        }
247
248        self.resetneeded = true;
249    }
250
251    /// Transpose chars
252    /// Port of transposechars() from zle_misc.c
253    pub fn transpose_chars(&mut self) {
254        if self.zlecs == 0 || self.zlell < 2 {
255            return;
256        }
257
258        let pos = if self.zlecs == self.zlell {
259            self.zlecs - 1
260        } else {
261            self.zlecs
262        };
263
264        if pos > 0 {
265            self.zleline.swap(pos - 1, pos);
266            self.zlecs = pos + 1;
267            self.resetneeded = true;
268        }
269    }
270
271    /// Capitalize word
272    pub fn capitalize_word(&mut self) {
273        while self.zlecs < self.zlell && !self.zleline[self.zlecs].is_alphanumeric() {
274            self.zlecs += 1;
275        }
276
277        if self.zlecs < self.zlell && self.zleline[self.zlecs].is_alphabetic() {
278            self.zleline[self.zlecs] = self.zleline[self.zlecs]
279                .to_uppercase()
280                .next()
281                .unwrap_or(self.zleline[self.zlecs]);
282            self.zlecs += 1;
283        }
284
285        while self.zlecs < self.zlell && self.zleline[self.zlecs].is_alphanumeric() {
286            self.zleline[self.zlecs] = self.zleline[self.zlecs]
287                .to_lowercase()
288                .next()
289                .unwrap_or(self.zleline[self.zlecs]);
290            self.zlecs += 1;
291        }
292
293        self.resetneeded = true;
294    }
295
296    /// Downcase word
297    pub fn downcase_word(&mut self) {
298        while self.zlecs < self.zlell && !self.zleline[self.zlecs].is_alphanumeric() {
299            self.zlecs += 1;
300        }
301
302        while self.zlecs < self.zlell && self.zleline[self.zlecs].is_alphanumeric() {
303            self.zleline[self.zlecs] = self.zleline[self.zlecs]
304                .to_lowercase()
305                .next()
306                .unwrap_or(self.zleline[self.zlecs]);
307            self.zlecs += 1;
308        }
309
310        self.resetneeded = true;
311    }
312
313    /// Upcase word
314    pub fn upcase_word(&mut self) {
315        while self.zlecs < self.zlell && !self.zleline[self.zlecs].is_alphanumeric() {
316            self.zlecs += 1;
317        }
318
319        while self.zlecs < self.zlell && self.zleline[self.zlecs].is_alphanumeric() {
320            self.zleline[self.zlecs] = self.zleline[self.zlecs]
321                .to_uppercase()
322                .next()
323                .unwrap_or(self.zleline[self.zlecs]);
324            self.zlecs += 1;
325        }
326
327        self.resetneeded = true;
328    }
329
330    /// Transpose words
331    /// Port of transpose words logic
332    pub fn transpose_words(&mut self) {
333        if self.zlell < 3 {
334            return;
335        }
336
337        // Find boundaries of two words
338        let mut end2 = self.zlecs;
339        while end2 < self.zlell && self.zleline[end2].is_alphanumeric() {
340            end2 += 1;
341        }
342        while end2 < self.zlell && !self.zleline[end2].is_alphanumeric() {
343            end2 += 1;
344        }
345        while end2 < self.zlell && self.zleline[end2].is_alphanumeric() {
346            end2 += 1;
347        }
348
349        let mut start2 = end2;
350        while start2 > 0 && self.zleline[start2 - 1].is_alphanumeric() {
351            start2 -= 1;
352        }
353
354        let mut end1 = start2;
355        while end1 > 0 && !self.zleline[end1 - 1].is_alphanumeric() {
356            end1 -= 1;
357        }
358
359        let mut start1 = end1;
360        while start1 > 0 && self.zleline[start1 - 1].is_alphanumeric() {
361            start1 -= 1;
362        }
363
364        if start1 < end1 && start2 < end2 {
365            let word1: Vec<char> = self.zleline[start1..end1].to_vec();
366            let word2: Vec<char> = self.zleline[start2..end2].to_vec();
367
368            // Replace word2 first (higher index)
369            self.zleline.drain(start2..end2);
370            for (i, c) in word1.iter().enumerate() {
371                self.zleline.insert(start2 + i, *c);
372            }
373
374            // Replace word1
375            let new_end1 = end1 - (end2 - start2) + word1.len();
376            let _new_start1 = start1;
377            self.zleline.drain(start1..end1);
378            for (i, c) in word2.iter().enumerate() {
379                self.zleline.insert(start1 + i, *c);
380            }
381
382            self.zlell = self.zleline.len();
383            self.zlecs = new_end1;
384            self.resetneeded = true;
385        }
386    }
387
388    /// Quote line
389    /// Port of quoteline() from zle_misc.c
390    pub fn quote_line(&mut self) {
391        self.zleline.insert(0, '\'');
392        self.zlell += 1;
393        self.zlecs += 1;
394        self.zleline.push('\'');
395        self.zlell += 1;
396        self.resetneeded = true;
397    }
398
399    /// Quote region
400    /// Port of quoteregion() from zle_misc.c
401    pub fn quote_region(&mut self) {
402        let (start, end) = if self.zlecs < self.mark {
403            (self.zlecs, self.mark)
404        } else {
405            (self.mark, self.zlecs)
406        };
407
408        self.zleline.insert(end, '\'');
409        self.zleline.insert(start, '\'');
410        self.zlell += 2;
411        self.zlecs = end + 2;
412        self.mark = start;
413        self.resetneeded = true;
414    }
415
416    /// What cursor position - display cursor info
417    /// Port of whatcursorposition() from zle_misc.c
418    pub fn what_cursor_position(&self) -> String {
419        if self.zlecs >= self.zlell {
420            return format!("point={} of {} (EOL)", self.zlecs, self.zlell);
421        }
422
423        let c = self.zleline[self.zlecs];
424        let code = c as u32;
425        format!(
426            "Char: {} (0{:o}, {:?}, 0x{:x})  point {} of {} ({}%)",
427            c,
428            code,
429            code,
430            code,
431            self.zlecs,
432            self.zlell,
433            if self.zlell == 0 {
434                0
435            } else {
436                self.zlecs * 100 / self.zlell
437            }
438        )
439    }
440
441    /// Universal argument - multiply next command
442    /// Port of universalargument() from zle_misc.c
443    pub fn universal_argument(&mut self) {
444        self.mult = self.mult.saturating_mul(4);
445    }
446
447    /// Digit argument - accumulate numeric argument
448    /// Port of digitargument() from zle_misc.c
449    pub fn digit_argument(&mut self, digit: u8) {
450        if self.mult == 1 && !self.neg_arg {
451            self.mult = 0;
452        }
453        self.mult = self.mult.saturating_mul(10).saturating_add(digit as i32);
454    }
455
456    /// Negative argument
457    /// Port of negargument() from zle_misc.c
458    pub fn neg_argument(&mut self) {
459        self.neg_arg = !self.neg_arg;
460    }
461
462    /// Undefined key - beep
463    /// Port of undefinedkey() from zle_misc.c
464    pub fn undefined_key(&self) {
465        print!("\x07"); // Bell
466    }
467
468    /// Send break - abort current operation
469    /// Port of sendbreak() from zle_misc.c
470    pub fn send_break(&mut self) {
471        self.zleline.clear();
472        self.zlell = 0;
473        self.zlecs = 0;
474        self.mark = 0;
475        self.resetneeded = true;
476    }
477
478    /// Vi put after cursor
479    /// Port of viputafter() from zle_misc.c
480    pub fn vi_put_after(&mut self) {
481        if self.zlecs < self.zlell {
482            self.zlecs += 1;
483        }
484        self.yank();
485        if self.zlecs > 0 {
486            self.zlecs -= 1;
487        }
488    }
489
490    /// Vi put before cursor
491    /// Port of viputbefore() from zle_misc.c
492    pub fn vi_put_before(&mut self) {
493        self.yank();
494    }
495
496    /// Overwrite mode toggle
497    /// Port of overwritemode() from zle_misc.c
498    pub fn overwrite_mode(&mut self) {
499        self.insmode = !self.insmode;
500    }
501
502    /// Copy previous word
503    /// Port of copyprevword() from zle_misc.c
504    pub fn copy_prev_word(&mut self) {
505        if self.zlecs == 0 {
506            return;
507        }
508
509        // Find start of previous word
510        let mut end = self.zlecs;
511        while end > 0 && self.zleline[end - 1].is_whitespace() {
512            end -= 1;
513        }
514        let mut start = end;
515        while start > 0 && !self.zleline[start - 1].is_whitespace() {
516            start -= 1;
517        }
518
519        if start < end {
520            let word: Vec<char> = self.zleline[start..end].to_vec();
521            for c in word {
522                self.zleline.insert(self.zlecs, c);
523                self.zlecs += 1;
524            }
525            self.zlell = self.zleline.len();
526            self.resetneeded = true;
527        }
528    }
529
530    /// Copy previous shell word (respects quoting)
531    /// Port of copyprevshellword() from zle_misc.c
532    pub fn copy_prev_shell_word(&mut self) {
533        // Simplified - doesn't handle full shell quoting
534        self.copy_prev_word();
535    }
536
537    /// Pound insert - comment toggle for vi mode
538    /// Port of poundinsert() from zle_misc.c
539    pub fn pound_insert(&mut self) {
540        if !self.zleline.is_empty() && self.zleline[0] == '#' {
541            self.zleline.remove(0);
542            self.zlell -= 1;
543            if self.zlecs > 0 {
544                self.zlecs -= 1;
545            }
546        } else {
547            self.zleline.insert(0, '#');
548            self.zlell += 1;
549            self.zlecs += 1;
550        }
551        self.resetneeded = true;
552    }
553}