Skip to main content

zsh/ported/zle/
textobjects.rs

1//! ZLE text objects — port of `Src/Zle/textobjects.c`.
2//!
3//! Three C functions, zero structs/enums. The Rust port matches:
4//! three free fns over a `&mut Zle`, no Rust-only types.
5
6use crate::ported::zle::zle_h::{MOD_MULT, MOD_TMULT, MOD_VIBUF, MOD_VIAPP, MOD_NEG, MOD_NULL, MOD_CHAR, MOD_LINE, MOD_PRI, MOD_CLIP, MOD_OSSEL};
7
8/// Port of `blankwordclass(ZLE_CHAR_T x)` from `Src/Zle/textobjects.c:34`. The
9/// vi blank-word class predicate. Returns 0 for blanks, 1 otherwise.
10
11// --- AUTO: cross-zle hoisted-fn use glob ---
12#[allow(unused_imports)]
13#[allow(unused_imports)]
14use crate::ported::zle::zle_main::*;
15#[allow(unused_imports)]
16use crate::ported::zle::zle_misc::*;
17#[allow(unused_imports)]
18use crate::ported::zle::zle_hist::*;
19#[allow(unused_imports)]
20use crate::ported::zle::zle_move::*;
21#[allow(unused_imports)]
22use crate::ported::zle::zle_word::*;
23#[allow(unused_imports)]
24use crate::ported::zle::zle_params::*;
25#[allow(unused_imports)]
26use crate::ported::zle::zle_vi::*;
27#[allow(unused_imports)]
28use crate::ported::zle::zle_utils::*;
29#[allow(unused_imports)]
30use crate::ported::zle::zle_refresh::*;
31#[allow(unused_imports)]
32use crate::ported::zle::zle_tricky::*;
33#[allow(unused_imports)]
34use crate::ported::zle::deltochar::*;
35
36pub fn blankwordclass(x: char) -> i32 {                                  // c:34
37    // C: `return (ZC_iblank(x) ? 0 : 1);`
38    if x == ' ' || x == '\t' { 0 } else { 1 }                            // c:36
39}
40
41/// Port of `selectword(UNUSED(char **args))` from `Src/Zle/textobjects.c:41`.
42/// Faithful 1:1 port of the C body. Variable names track the C
43/// source where possible.
44///
45/// `INCCS()` / `DECCS()` / `INCPOS()` / `DECPOS()` collapse to
46/// `+= 1` / `-= 1` in the Rust port because zshrs's buffer is
47/// `Vec<char>` (already multibyte-aware at the storage layer; no
48/// per-char byte-walk needed).
49///
50/// `virangeflag` is a `Src/Zle/zle_vi.c:36` file-global. The
51/// cursor-adjustment arm at `c:196-203` reads it. zshrs sets the
52/// flag during the live `vi`-operator-pending key-read loop in the
53/// ZLE file-scope statics; standalone widget invocation reaches this
54/// fn with the flag clear, which is the only state the cursor-
55/// adjustment needs to handle (the `range`-set branch only fires
56/// from inside `getvirange`, which has its own copy).
57pub fn selectword() -> i32 {                                // c:41
58    let mut n: i32 = if crate::ported::zle::zle_main::ZMOD.lock().unwrap().flags & MOD_MULT != 0 {   // c:41 zmult
59        crate::ported::zle::zle_main::ZMOD.lock().unwrap().mult
60    } else {
61        1
62    };
63    let widget = crate::ported::zle::zle_main::BINDK.lock().unwrap().as_ref().map(|t| t.nam.clone()).unwrap_or_default();
64    let widget = widget.as_str();
65    let is_aword       = widget == "select-a-word";
66    let is_inword      = widget == "select-in-word";
67    let is_ablankword  = widget == "select-a-blank-word";
68    let mut all: i32 = (is_aword || is_ablankword) as i32;               // c:43-44
69    let viclass: fn(char) -> i32 = if is_aword || is_inword {
70        crate::ported::zle::zle_word::wordclass                          // c:46-47
71    } else {
72        blankwordclass
73    };
74    if crate::ported::zle::zle_main::ZLELL.load(std::sync::atomic::Ordering::SeqCst) == 0 {
75        return 1;
76    }
77    let cur = crate::ported::zle::zle_main::ZLELINE.lock().unwrap().get(crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst)).copied().unwrap_or('\n');
78    let mut sclass: i32 = viclass(cur);                                  // c:48
79    let mut doblanks: i32 = all & ((sclass != 0) as i32);                // c:49 all && sclass
80
81    let region_active = crate::ported::zle::zle_main::REGION_ACTIVE.load(std::sync::atomic::Ordering::SeqCst) != 0;                          // c:51 (read once)
82
83    // C's `mark == -1` sentinel doesn't exist in the Rust port (mark
84    // is `usize`); the equivalent "mark is unset" condition collapses
85    // into `!region_active` since mark is only meaningful when the
86    // region is active. Drop the `mark == -1` disjunct.
87    if !region_active || crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst) == crate::ported::zle::zle_main::MARK.load(std::sync::atomic::Ordering::SeqCst) {                         // c:51
88        // search back to first character of same class as the start
89        // position; also stop at the beginning of the line.
90        crate::ported::zle::zle_main::MARK.store(crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst), std::sync::atomic::Ordering::SeqCst);                                            // c:54
91        while crate::ported::zle::zle_main::MARK.load(std::sync::atomic::Ordering::SeqCst) != 0 {                                            // c:55
92            let pos = crate::ported::zle::zle_main::MARK.load(std::sync::atomic::Ordering::SeqCst) - 1;                                      // c:56-57 DECPOS
93            let cp = crate::ported::zle::zle_main::ZLELINE.lock().unwrap().get(pos).copied().unwrap_or('\n');
94            if cp == '\n' || viclass(cp) != sclass {                     // c:58
95                break;                                                   // c:59
96            }
97            crate::ported::zle::zle_main::MARK.store(pos, std::sync::atomic::Ordering::SeqCst);                                              // c:60
98        }
99        // similarly scan forward over characters of the same class.
100        while crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst) < crate::ported::zle::zle_main::ZLELL.load(std::sync::atomic::Ordering::SeqCst) {                                    // c:63
101            crate::ported::zle::zle_main::ZLECS.fetch_add(1, std::sync::atomic::Ordering::SeqCst);                                              // c:64 INCCS
102            let mut pos = crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst);                                     // c:65
103            // single newlines within blanks are included.
104            if all != 0 && sclass == 0 && pos < crate::ported::zle::zle_main::ZLELL.load(std::sync::atomic::Ordering::SeqCst)                // c:67
105                && crate::ported::zle::zle_main::ZLELINE.lock().unwrap().get(pos).copied() == Some('\n')
106            {
107                pos += 1;                                                // c:68 INCPOS(pos)
108            }
109            let pc = crate::ported::zle::zle_main::ZLELINE.lock().unwrap().get(pos).copied().unwrap_or('\n');
110            if pc == '\n' || viclass(pc) != sclass {                     // c:70
111                break;                                                   // c:71
112            }
113        }
114
115        if all != 0 {                                                    // c:74
116            let cc = crate::ported::zle::zle_main::ZLELINE.lock().unwrap().get(crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst)).copied().unwrap_or('\n');
117            let nclass = viclass(cc);                                    // c:75
118            // if either start or new position is blank advance over a
119            // new block of characters of a common type.
120            if nclass == 0 || sclass == 0 {                              // c:78
121                while crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst) < crate::ported::zle::zle_main::ZLELL.load(std::sync::atomic::Ordering::SeqCst) {                            // c:79
122                    crate::ported::zle::zle_main::ZLECS.fetch_add(1, std::sync::atomic::Ordering::SeqCst);                                      // c:80 INCCS
123                    let cc = crate::ported::zle::zle_main::ZLELINE.lock().unwrap().get(crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst)).copied().unwrap_or('\n');
124                    if cc == '\n' || viclass(cc) != nclass {             // c:81
125                        break;                                           // c:82
126                    }
127                }
128                if n < 2 {                                               // c:85
129                    doblanks = 0;                                        // c:86
130                }
131            }
132        }
133    } else {                                                             // c:89
134        // For visual mode, advance one char so repeated invocations
135        // select subsequent words.
136        if crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst) > crate::ported::zle::zle_main::MARK.load(std::sync::atomic::Ordering::SeqCst) {                                        // c:92
137            if crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst) < crate::ported::zle::zle_main::ZLELL.load(std::sync::atomic::Ordering::SeqCst) {                                   // c:93
138                crate::ported::zle::zle_main::ZLECS.fetch_add(1, std::sync::atomic::Ordering::SeqCst);                                          // c:94 INCCS
139            }
140        } else if crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst) != 0 {                                       // c:95
141            crate::ported::zle::zle_main::ZLECS.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);                                              // c:96 DECCS
142        }
143        if crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst) < crate::ported::zle::zle_main::MARK.load(std::sync::atomic::Ordering::SeqCst) {                                        // c:97
144            // visual mode with the cursor before the mark: move
145            // cursor back.
146            while {
147                let cont = n > 0;
148                n -= 1;
149                cont
150            } {                                                          // c:99 while (n-- > 0)
151                let mut pos = crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst);                                 // c:100
152                let zc_pos = crate::ported::zle::zle_main::ZLELINE.lock().unwrap().get(pos).copied().unwrap_or('\n');
153                // first over blanks
154                if all != 0 && (viclass(zc_pos) == 0 || zc_pos == '\n') {  // c:102
155                    all = 0;                                             // c:104
156                    while pos != 0 {                                     // c:105
157                        pos -= 1;                                        // c:106 DECPOS
158                        let pc = crate::ported::zle::zle_main::ZLELINE.lock().unwrap().get(pos).copied().unwrap_or('\n');
159                        if pc == '\n' {                                  // c:107
160                            break;                                       // c:108
161                        }
162                        crate::ported::zle::zle_main::ZLECS.store(pos, std::sync::atomic::Ordering::SeqCst);                                 // c:109
163                        if viclass(pc) != 0 {                            // c:110
164                            break;                                       // c:111
165                        }
166                    }
167                } else if crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst) != 0
168                    && crate::ported::zle::zle_main::ZLELINE.lock().unwrap().get(crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst)).copied() == Some('\n')
169                {                                                        // c:114
170                    // for 'in' widgets pass over one newline
171                    pos -= 1;                                            // c:116 DECPOS(pos)
172                    let pc = crate::ported::zle::zle_main::ZLELINE.lock().unwrap().get(pos).copied().unwrap_or('\n');
173                    if pc != '\n' {                                      // c:117
174                        crate::ported::zle::zle_main::ZLECS.store(pos, std::sync::atomic::Ordering::SeqCst);                                 // c:118
175                    }
176                }
177                pos = crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst);                                         // c:121
178                let cur = crate::ported::zle::zle_main::ZLELINE.lock().unwrap().get(crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst)).copied().unwrap_or('\n');
179                sclass = viclass(cur);                                   // c:122
180                // now retreat over non-blanks
181                loop {                                                   // c:124
182                    let pc = crate::ported::zle::zle_main::ZLELINE.lock().unwrap().get(pos).copied().unwrap_or('\n');
183                    if pc == '\n' || viclass(pc) != sclass {
184                        break;
185                    }
186                    crate::ported::zle::zle_main::ZLECS.store(pos, std::sync::atomic::Ordering::SeqCst);                                     // c:126
187                    if pos == 0 {                                        // c:127
188                        crate::ported::zle::zle_main::ZLECS.store(0, std::sync::atomic::Ordering::SeqCst);                                   // c:128
189                        break;                                           // c:129
190                    }
191                    pos -= 1;                                            // c:131 DECPOS
192                }
193                // blanks again but only if there were none first time
194                if all != 0 && crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst) != 0 {                          // c:134
195                    pos = crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst);
196                    pos -= 1;                                            // c:136 DECPOS
197                    let pc = crate::ported::zle::zle_main::ZLELINE.lock().unwrap().get(pos).copied().unwrap_or('\n');
198                    if viclass(pc) == 0 {                                // c:137
199                        while pos != 0 {                                 // c:138
200                            pos -= 1;                                    // c:139 DECPOS
201                            let pc = crate::ported::zle::zle_main::ZLELINE.lock().unwrap().get(pos).copied().unwrap_or('\n');
202                            if pc == '\n' || viclass(pc) != 0 {          // c:140
203                                break;                                   // c:142
204                            }
205                            crate::ported::zle::zle_main::ZLECS.store(pos, std::sync::atomic::Ordering::SeqCst);                             // c:143
206                        }
207                    }
208                }
209            }
210            return 0;                                                    // c:147
211        }
212        n += 1;                                                          // c:148
213        doblanks = 0;                                                    // c:149
214    }
215    // force to character-wise — c:152
216    crate::ported::zle::zle_main::REGION_ACTIVE.store(if region_active { 1 } else { 0 }, std::sync::atomic::Ordering::SeqCst);
217
218    // for each digit argument, advance over a further block of one class
219    while {
220        n -= 1;
221        n > 0
222    } {                                                                  // c:155
223        if crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst) < crate::ported::zle::zle_main::ZLELL.load(std::sync::atomic::Ordering::SeqCst)
224            && crate::ported::zle::zle_main::ZLELINE.lock().unwrap().get(crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst)).copied() == Some('\n')
225        {                                                                // c:156
226            crate::ported::zle::zle_main::ZLECS.fetch_add(1, std::sync::atomic::Ordering::SeqCst);                                              // c:157 INCCS
227        }
228        let cur = crate::ported::zle::zle_main::ZLELINE.lock().unwrap().get(crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst)).copied().unwrap_or('\n');
229        sclass = viclass(cur);                                           // c:158
230        while crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst) < crate::ported::zle::zle_main::ZLELL.load(std::sync::atomic::Ordering::SeqCst) {                                    // c:159
231            crate::ported::zle::zle_main::ZLECS.fetch_add(1, std::sync::atomic::Ordering::SeqCst);                                              // c:160 INCCS
232            let cc = crate::ported::zle::zle_main::ZLELINE.lock().unwrap().get(crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst)).copied().unwrap_or('\n');
233            if cc == '\n' || viclass(cc) != sclass {                     // c:161
234                break;                                                   // c:163
235            }
236        }
237        // for 'a' widgets, advance extra block if either consists of blanks
238        if all != 0 {                                                    // c:165
239            if crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst) < crate::ported::zle::zle_main::ZLELL.load(std::sync::atomic::Ordering::SeqCst)
240                && crate::ported::zle::zle_main::ZLELINE.lock().unwrap().get(crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst)).copied() == Some('\n')
241            {                                                            // c:166
242                crate::ported::zle::zle_main::ZLECS.fetch_add(1, std::sync::atomic::Ordering::SeqCst);                                          // c:167 INCCS
243            }
244            let cc = crate::ported::zle::zle_main::ZLELINE.lock().unwrap().get(crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst)).copied().unwrap_or('\n');
245            let cls_here = viclass(cc);
246            if sclass == 0 || cls_here == 0 {                            // c:168
247                sclass = cls_here;                                       // c:169
248                if n == 1 && sclass == 0 {                               // c:170
249                    doblanks = 0;                                        // c:171
250                }
251                while crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst) < crate::ported::zle::zle_main::ZLELL.load(std::sync::atomic::Ordering::SeqCst) {                            // c:172
252                    crate::ported::zle::zle_main::ZLECS.fetch_add(1, std::sync::atomic::Ordering::SeqCst);                                      // c:173 INCCS
253                    let cc = crate::ported::zle::zle_main::ZLELINE.lock().unwrap().get(crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst)).copied().unwrap_or('\n');
254                    if cc == '\n' || viclass(cc) != sclass {             // c:174
255                        break;                                           // c:176
256                    }
257                }
258            }
259        }
260    }
261
262    // if we didn't remove blanks at either end we remove some at the start
263    if doblanks != 0 {                                                   // c:181
264        let mut pos = crate::ported::zle::zle_main::MARK.load(std::sync::atomic::Ordering::SeqCst);                                          // c:182
265        while pos != 0 {                                                 // c:183
266            pos -= 1;                                                    // c:184 DECPOS
267            // don't remove blanks at the start of the line, i.e. indentation
268            let pc = crate::ported::zle::zle_main::ZLELINE.lock().unwrap().get(pos).copied().unwrap_or('\n');
269            if pc == '\n' {                                              // c:186
270                break;                                                   // c:187
271            }
272            if !(pc == ' ' || pc == '\t') {                              // c:188 !ZC_iblank
273                pos += 1;                                                // c:189 INCPOS
274                crate::ported::zle::zle_main::MARK.store(pos, std::sync::atomic::Ordering::SeqCst);                                          // c:190
275                break;                                                   // c:191
276            }
277        }
278    }
279    // Adjustment: vi operators don't include the cursor position; in
280    // insert or emacs mode the region also doesn't, but for vi visual
281    // mode it is included.
282    //
283    // c:196 — `virangeflag` file-global (zle_vi.c:36). When non-zero
284    // a vi range operation is pending, in which case the region
285    // adjustment below is suppressed because the operator already
286    // handled it.
287    let virangeflag = crate::ported::zle::zle_vi::VIRANGEFLAG
288        .load(std::sync::atomic::Ordering::Relaxed) != 0;
289    if !virangeflag {                                                    // c:196
290        if !in_vi_cmd_mode() {                                       // c:197
291            crate::ported::zle::zle_main::REGION_ACTIVE.store(1, std::sync::atomic::Ordering::SeqCst);                                       // c:198
292        } else if crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst) != 0 && crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst) > crate::ported::zle::zle_main::MARK.load(std::sync::atomic::Ordering::SeqCst) {               // c:199
293            crate::ported::zle::zle_main::ZLECS.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);                                              // c:200 DECCS
294        }
295    }
296
297    0                                                                    // c:204
298}
299
300/// Port of `selectargument(UNUSED(char **args))` from `Src/Zle/textobjects.c:212`.
301///
302/// The C body uses the shell's `ctxtlex()` lexer-walk machinery
303/// (textobjects.c:233-257) to drive real shell tokenisation over
304/// the buffer. zshrs lowers the lexer through fusevm bytecode and
305/// does not expose a free-running `ctxtlex`-style scanner; this
306/// port uses whitespace-split tokenisation against the buffer
307/// (matches C output for simple commands without quoting /
308/// expansion / heredocs). Returns 1 when `n` is out of range,
309/// matching C textobjects.c:225.
310pub fn selectargument() -> i32 {                            // c:212
311    let n: i32 = if crate::ported::zle::zle_main::ZMOD.lock().unwrap().flags & MOD_MULT != 0 {
312        crate::ported::zle::zle_main::ZMOD.lock().unwrap().mult                                                    // c:222 zmult
313    } else {
314        1
315    };
316    if n < 1 || (2 * n as usize) > crate::ported::zle::zle_main::ZLELL.load(std::sync::atomic::Ordering::SeqCst) + 1 {                       // c:225
317        return 1;
318    }
319    if !in_vi_cmd_mode() {                                           // c:228
320        crate::ported::zle::zle_main::REGION_ACTIVE.store(1, std::sync::atomic::Ordering::SeqCst);                                           // c:229
321        crate::ported::zle::zle_main::MARK.store(crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst), std::sync::atomic::Ordering::SeqCst);                                            // c:230
322    }
323    // Whitespace-split tokenisation (see fn-doc for the ctxtlex
324    // tradeoff).
325    let mut starts: Vec<usize> = Vec::with_capacity(n as usize);
326    let mut in_word = false;
327    let mut word_start = 0usize;
328    starts.push(0);
329    for (i, &c) in crate::ported::zle::zle_main::ZLELINE.lock().unwrap().iter().enumerate() {
330        if c.is_whitespace() {
331            if in_word {
332                in_word = false;
333                if starts.len() < n as usize { starts.push(i + 1); }
334            }
335        } else if !in_word {
336            in_word = true;
337            word_start = i;
338            if i >= crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst) { break; }
339        }
340    }
341    let arg_idx = (n - 1) as usize;
342    let s = starts.get(arg_idx).copied().unwrap_or(word_start);
343    let e = (s..crate::ported::zle::zle_main::ZLELL.load(std::sync::atomic::Ordering::SeqCst))
344        .find(|&i| crate::ported::zle::zle_main::ZLELINE.lock().unwrap().get(i).copied().map_or(true, |c| c.is_whitespace()))
345        .unwrap_or(crate::ported::zle::zle_main::ZLELL.load(std::sync::atomic::Ordering::SeqCst));
346    crate::ported::zle::zle_main::MARK.store(s, std::sync::atomic::Ordering::SeqCst);
347    crate::ported::zle::zle_main::ZLECS.store(e, std::sync::atomic::Ordering::SeqCst);
348    if in_vi_cmd_mode() && crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst) > 0 {
349        crate::ported::zle::zle_main::ZLECS.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
350    }
351    0
352}