1#![allow(non_snake_case, non_upper_case_globals, dead_code)]
23
24use crate::ported::context::zcontext_restore_partial;
25use crate::ported::module::{gethookdef, runhookdef};
26use crate::ported::params::{getsparam, paramtab, paramtab_hashed_storage, setaparam, setsparam};
27use crate::ported::signals::{queue_signals, unqueue_signals};
28use crate::ported::zle::comp_h::{
29 Aminfo, Brinfo, Cadata, Ccmakedat, Cexpl, Cline, Cmatch, Cmgroup, Cmlist, Menuinfo, CAF_ALL,
30 CAF_MATCH, CAF_MATSORT, CAF_NOSORT, CAF_NUMSORT, CAF_QUOTE, CAF_REVSORT, CAF_UNIQALL,
31 CAF_UNIQCON, CGF_MATSORT, CGF_NOSORT, CGF_NUMSORT, CGF_REVSORT, CGF_UNIQALL, CGF_UNIQCON,
32 CMF_DELETE, CMF_DISPLINE, CMF_FMULT, CMF_MULT, CMF_NOLIST, CMF_PACKED, CMF_PARBR, CMF_PARNEST,
33 CMF_ROWS,
34};
35use crate::ported::zle::complete::{
36 COMPIPREFIX, COMPLIST, COMPPREFIX, COMPQSTACK, COMPSUFFIX, INCOMPFUNC,
37};
38use crate::ported::zle::compmatch::{bld_parts, cline_matched};
39use crate::ported::zle::compresult::{do_ambig_menu, ztat};
40use crate::ported::zle::zle_h::{invalidatelist, COMP_LIST_COMPLETE, COMP_LIST_EXPAND, CUT_RAW};
41use crate::ported::zle::zle_refresh::{CLEARLIST, SHOWINGLIST};
42use crate::ported::zle::zle_tricky::{
43 inststr, MENUCMP, ORIGCS, ORIGLINE, USEGLOB, USEMENU, VALIDLIST, WOULDINSTAB,
44};
45use crate::ported::zle::zle_utils::foredel;
46#[allow(unused_imports)]
47use crate::ported::zle::{
48 deltochar::*, textobjects::*, zle_h::*, zle_hist::*, zle_main::*, zle_misc::*, zle_move::*,
49 zle_params::*, zle_refresh::*, zle_tricky::*, zle_utils::*, zle_vi::*, zle_word::*,
50};
51use crate::ported::zsh_h::{
52 isset, Bnull, Dnull, Equals, Hat, Inbrace, Inbrack, Inpar, Outbrace, Outpar, Pound, Qstring,
53 Quest, Snull, Star, Stringg, Tilde, BASHAUTOLIST, NUMERICGLOBSORT, PM_HASHED, PM_TYPE,
54 QT_BACKSLASH, QT_DOLLARS, QT_DOUBLE, QT_NONE, QT_SINGLE, RCQUOTES, SORTIT_IGNORING_BACKSLASHES,
55 SORTIT_NUMERICALLY, ZCONTEXT_HIST, ZCONTEXT_LEX, ZCONTEXT_PARSE,
56};
57use crate::DPUTS;
58use std::sync::atomic::{AtomicI32, Ordering};
59use std::sync::{Mutex, OnceLock};
60
61pub fn do_completion(s: &str, incmd: i32, lst: i32) -> i32 {
75 let osl = SHOWINGLIST.load(Ordering::Relaxed); let mut ret: i32 = 0; if let Ok(mut g) = ainfo.get_or_init(|| Mutex::new(None)).lock() {
82 *g = None;
83 }
84 if let Ok(mut g) = fainfo.get_or_init(|| Mutex::new(None)).lock() {
85 *g = None;
86 }
87 if let Ok(mut g) = matchers.get_or_init(|| Mutex::new(Vec::new())).lock() {
88 g.clear(); }
90
91 let instring = INSTRING.load(Ordering::Relaxed); let head_q: char = if instring == QT_NONE {
96 QT_BACKSLASH as u8 as char
98 } else {
99 instring as u8 as char
100 };
101 if let Ok(mut g) = compqstack.get_or_init(|| Mutex::new(String::new())).lock() {
102 *g = head_q.to_string(); }
104
105 hasunqu.store(0, Ordering::Relaxed); let wouldinstab_v = WOULDINSTAB.load(Ordering::Relaxed); useline.store(
108 if wouldinstab_v != 0 {
110 -1
111 } else if lst != COMP_LIST_COMPLETE {
112 1
113 } else {
114 0
115 },
116 Ordering::Relaxed,
117 );
118 useexact.store(opt_isset("RECEXACT"), Ordering::Relaxed); set_compstate_str("exact_string", ""); let useline_v = useline.load(Ordering::Relaxed);
121 uselist.store(
122 if useline_v != 0 {
124 if opt_isset("AUTOLIST") != 0 && opt_isset("BASHAUTOLIST") == 0 {
125 if opt_isset("LISTAMBIGUOUS") != 0 {
126 3
127 } else {
128 2
129 }
130 } else {
131 0
132 }
133 } else {
134 1
135 },
136 Ordering::Relaxed,
137 );
138
139 let useglob_v = USEGLOB.load(Ordering::Relaxed); let opm: String = if useglob_v != 0 {
141 "*".into()
142 } else {
143 "".into()
144 };
145 if let Ok(mut g) = comppatmatch.get_or_init(|| Mutex::new(None)).lock() {
146 *g = Some(opm.clone()); }
148 set_compstate_str("pattern_insert", "menu"); forcelist.store(0, Ordering::Relaxed); haspattern.store(0, Ordering::Relaxed); let _complistmax = env_iparam("LISTMAX"); set_compstate_str(
154 "last_prompt",
156 if opt_isset("ALWAYSLASTPROMPT") != 0 {
157 "yes"
158 } else {
159 ""
160 },
161 );
162 dolastprompt.store(1, Ordering::Relaxed); let cl_str = if opt_isset("LISTROWSFIRST") != 0 {
166 if opt_isset("LISTPACKED") != 0 {
167 "packed rows"
168 } else {
169 "rows"
170 }
171 } else if opt_isset("LISTPACKED") != 0 {
172 "packed"
173 } else {
174 ""
175 };
176 if let Ok(mut g) = COMPLIST.get_or_init(|| Mutex::new(String::new())).lock() {
177 *g = cl_str.into(); }
179 startauto.store(opt_isset("AUTOMENU"), Ordering::Relaxed); let zlc = ZLEMETACS.load(Ordering::Relaxed);
182 let we_v = WE.load(Ordering::Relaxed);
183 movetoend.store(
184 if zlc == we_v || opt_isset("ALWAYSTOEND") != 0 {
186 2
187 } else {
188 1
189 },
190 Ordering::Relaxed,
191 );
192 SHOWINGLIST.store(0, Ordering::Relaxed); hasmatched.store(0, Ordering::Relaxed); hasunmatched.store(0, Ordering::Relaxed); minmlen.store(1_000_000, Ordering::Relaxed); maxmlen.store(-1, Ordering::Relaxed); nmessages.store(0, Ordering::Relaxed); hasallmatch.store(0, Ordering::Relaxed); if makecomplist(s, incmd, lst) != 0 {
202 ZLEMETACS.store(0, Ordering::Relaxed); foredel(ZLEMETALL.load(Ordering::Relaxed), CUT_RAW); let _ = inststr(
207 &ORIGLINE
208 .get_or_init(|| Mutex::new(String::new()))
209 .lock()
210 .map(|g| g.clone())
211 .unwrap_or_default(),
212 ); ZLEMETACS.store(ORIGCS.load(Ordering::Relaxed), Ordering::Relaxed); CLEARLIST.store(1, Ordering::Relaxed); ret = 1;
216 if let Ok(mut g) = MINFO.get_or_init(|| Mutex::new(Menuinfo::default())).lock() {
217 g.cur = None;
218 } if useline.load(Ordering::Relaxed) < 0 {
220 unmetafy_line();
222 ret = selfinsert(&[]); metafy_line();
224 }
225 return goto_compend(ret); }
227
228 lastprebr_set(""); lastpostbr_set(""); let curpm = comppatmatch
233 .get_or_init(|| Mutex::new(None))
234 .lock()
235 .ok()
236 .and_then(|g| g.clone())
237 .unwrap_or_default();
238 if !curpm.is_empty() && curpm != opm {
239 haspattern.store(1, Ordering::Relaxed); }
242 let nm = nmatches.load(Ordering::Relaxed); let dm = diffmatches.load(Ordering::Relaxed);
244 if iforcemenu.load(Ordering::Relaxed) != 0 {
245 if nm != 0 {
247 {
248 let _ = do_ambig_menu();
249 };
250 } ret = if nm == 0 { 1 } else { 0 }; } else if useline.load(Ordering::Relaxed) < 0 {
253 unmetafy_line();
255 ret = selfinsert(&[]); metafy_line();
257 } else if useline.load(Ordering::Relaxed) == 0 && uselist.load(Ordering::Relaxed) != 0 {
258 ZLEMETACS.store(0, Ordering::Relaxed); foredel(ZLEMETALL.load(Ordering::Relaxed), CUT_RAW); let _ = inststr(
262 &ORIGLINE
263 .get_or_init(|| Mutex::new(String::new()))
264 .lock()
265 .map(|g| g.clone())
266 .unwrap_or_default(),
267 ); ZLEMETACS.store(ORIGCS.load(Ordering::Relaxed), Ordering::Relaxed); SHOWINGLIST.store(-2, Ordering::Relaxed);
270 } else if useline.load(Ordering::Relaxed) == 2 && nm > 1 {
272 {
276 let groups = amatches
277 .get_or_init(|| Mutex::new(Vec::new()))
278 .lock()
279 .map(|g| g.clone())
280 .unwrap_or_default();
281 let mut all: Vec<String> = Vec::new();
282 for g in groups {
283 for m in g.matches {
284 if let Some(s) = m.str {
285 all.push(s);
286 }
287 }
288 }
289 let buf = ZLEMETALINE
290 .get_or_init(|| Mutex::new(String::new()))
291 .lock()
292 .map(|g| g.clone())
293 .unwrap_or_default();
294 let cs = ZLEMETACS.load(Ordering::Relaxed) as usize;
295 let wb = WB.load(Ordering::Relaxed) as usize;
296 let we = WE.load(Ordering::Relaxed) as usize;
297 let (new_buf, new_cs) =
298 crate::ported::zle::compresult::do_allmatches(&buf, cs, wb, we, &all, " ");
299 if let Ok(mut g) = ZLEMETALINE.get_or_init(|| Mutex::new(String::new())).lock() {
300 *g = new_buf;
301 ZLEMETALL.store(g.len() as i32, Ordering::Relaxed);
302 }
303 ZLEMETACS.store(new_cs as i32, Ordering::Relaxed);
304 }
305 if let Ok(mut g) = MINFO.get_or_init(|| Mutex::new(Menuinfo::default())).lock() {
306 g.cur = None;
307 } if forcelist.load(Ordering::Relaxed) != 0 {
309 SHOWINGLIST.store(-2, Ordering::Relaxed);
311 } else {
312 invalidatelist(); }
314 } else if useline.load(Ordering::Relaxed) != 0 {
315 if nm > 1 && dm != 0 {
317 ret = {
321 let groups = amatches
322 .get_or_init(|| Mutex::new(Vec::new()))
323 .lock()
324 .map(|g| g.clone())
325 .unwrap_or_default();
326 let all: Vec<String> = groups
327 .into_iter()
328 .flat_map(|g| g.matches.into_iter().filter_map(|m| m.str))
329 .collect();
330 crate::ported::zle::compresult::do_ambiguous(&all)
331 };
332 if SHOWINGLIST.load(Ordering::Relaxed) == 0
333 && uselist.load(Ordering::Relaxed) != 0
334 && LISTSHOWN.load(Ordering::Relaxed) != 0
335 && (USEMENU.load(Ordering::Relaxed) == 2 || oldlist.load(Ordering::Relaxed) != 0)
336 {
337 SHOWINGLIST.store(osl, Ordering::Relaxed);
338 }
340 } else if nm == 1 || (nm > 1 && dm == 0) {
341 do_single_first_match(); if forcelist.load(Ordering::Relaxed) != 0 {
344 if uselist.load(Ordering::Relaxed) != 0 {
346 SHOWINGLIST.store(-2, Ordering::Relaxed);
347 } else {
348 CLEARLIST.store(1, Ordering::Relaxed);
349 }
350 } else {
351 invalidatelist(); }
353 } else if nmessages.load(Ordering::Relaxed) != 0 && forcelist.load(Ordering::Relaxed) != 0 {
354 if uselist.load(Ordering::Relaxed) != 0 {
356 SHOWINGLIST.store(-2, Ordering::Relaxed);
357 } else {
358 CLEARLIST.store(1, Ordering::Relaxed);
359 }
360 }
361 } else {
362 invalidatelist(); LASTAMBIG.store(
365 opt_isset("BASHAUTOLIST"),
367 Ordering::Relaxed,
368 );
369 if forcelist.load(Ordering::Relaxed) != 0 {
370 CLEARLIST.store(1, Ordering::Relaxed);
371 } ZLEMETACS.store(0, Ordering::Relaxed); foredel(ZLEMETALL.load(Ordering::Relaxed), CUT_RAW); let _ = inststr(
375 &ORIGLINE
376 .get_or_init(|| Mutex::new(String::new()))
377 .lock()
378 .map(|g| g.clone())
379 .unwrap_or_default(),
380 ); ZLEMETACS.store(ORIGCS.load(Ordering::Relaxed), Ordering::Relaxed); }
383
384 if SHOWINGLIST.load(Ordering::Relaxed) == 0
386 && VALIDLIST.load(Ordering::Relaxed) != 0
387 && USEMENU.load(Ordering::Relaxed) != 2
388 && uselist.load(Ordering::Relaxed) != 0
389 && (nm != 1 || dm != 0)
390 && useline.load(Ordering::Relaxed) >= 0
391 && useline.load(Ordering::Relaxed) != 2
392 && (oldlist.load(Ordering::Relaxed) == 0 || LISTSHOWN.load(Ordering::Relaxed) == 0)
393 {
394 onlyexpl.store(3, Ordering::Relaxed); SHOWINGLIST.store(-2, Ordering::Relaxed);
396 }
398
399 goto_compend(ret)
400}
401
402pub fn before_complete(lst: &mut i32) -> i32 {
413 OLDMENUCMP.store(MENUCMP.load(Ordering::Relaxed), Ordering::Relaxed);
417
418 if SHOWAGAIN.load(Ordering::Relaxed) != 0 && VALIDLIST.load(Ordering::Relaxed) != 0 {
420 SHOWINGLIST.store(-2, Ordering::Relaxed);
421 }
422 SHOWAGAIN.store(0, Ordering::Relaxed);
424
425 let has_cur = MINFO
426 .get()
427 .and_then(|m| m.lock().ok())
428 .map(|m| m.cur.is_some())
429 .unwrap_or(false);
430 let menucmp_v = MENUCMP.load(Ordering::Relaxed);
431
432 if has_cur && menucmp_v != 0 && *lst != COMP_LIST_EXPAND {
434 return 1; }
442 if has_cur
444 && menucmp_v != 0
445 && VALIDLIST.load(Ordering::Relaxed) != 0
446 && *lst == COMP_LIST_COMPLETE
447 {
448 SHOWINGLIST.store(-2, Ordering::Relaxed);
449 onlyexpl.store(0, Ordering::Relaxed); if let Some(ld) = listdat.get() {
452 if let Ok(mut g) = ld.lock() {
453 g.valid = 0;
454 }
455 }
456 return 1; }
458
459 if (fromcomp.load(Ordering::Relaxed) & crate::ported::zle::comp_h::FC_INWORD) != 0 {
463 let le = lastend.load(Ordering::Relaxed);
464 let ll = ZLEMETALL.load(Ordering::Relaxed);
465 let new_cs = if le > ll { ll } else { le };
466 ZLEMETACS.store(new_cs, Ordering::Relaxed);
467 }
468
469 if startauto.load(Ordering::Relaxed) != 0 && LASTAMBIG.load(Ordering::Relaxed) != 0 {
471 let bashauto = isset(BASHAUTOLIST);
472 let last = LASTAMBIG.load(Ordering::Relaxed);
473 if !bashauto || last == 2 {
474 USEMENU.store(2, Ordering::Relaxed);
475 }
476 }
477
478 0 }
480
481pub fn after_complete(dat: &mut [i32]) -> i32 {
499 let menucmp_v = MENUCMP.load(Ordering::Relaxed);
501 let oldmenucmp_v = OLDMENUCMP.load(Ordering::Relaxed);
502
503 if menucmp_v == 0 || oldmenucmp_v != 0 {
505 return 0; }
507
508 let mut ret: i32 = 0;
523 let h_menu_start = gethookdef("menu_start");
524 if !h_menu_start.is_null() {
525 ret = runhookdef(h_menu_start, std::ptr::null_mut());
526 }
527
528 if ret == 0 {
529 return 0; }
531
532 if dat.len() > 1 {
535 dat[1] = 0;
536 }
537 MENUCMP.store(0, Ordering::Relaxed);
539 menuacc.store(0, Ordering::Relaxed);
540 if let Some(m) = MINFO.get() {
542 if let Ok(mut mi) = m.lock() {
543 mi.cur = None;
544 }
545 }
546
547 if ret >= 2 {
548 fixsuffix();
551 ZLEMETACS.store(0, Ordering::Relaxed);
553 let metall = ZLEMETALL.load(Ordering::Relaxed);
555 foredel(metall, CUT_RAW);
556 let origline_v: String = ORIGLINE
558 .get()
559 .and_then(|m| m.lock().ok().map(|g| g.clone()))
560 .unwrap_or_default();
561 let _ = inststr(&origline_v);
562 let origcs_v = ORIGCS.load(Ordering::Relaxed);
564 ZLEMETACS.store(origcs_v, Ordering::Relaxed);
565
566 if ret == 2 {
567 CLEARLIST.store(1, Ordering::Relaxed);
570 invalidatelist();
572 }
573 }
574
575 0 }
577
578pub fn callcompfunc(s: &str, fn_name: &str) {
588 if fn_name.is_empty() {
591 return;
592 } let _lv = crate::ported::builtin::LASTVAL.load(Ordering::Relaxed); let _icf = INCOMPFUNC.load(Ordering::Relaxed); let _osc = crate::ported::builtin::SFCONTEXT.load(Ordering::Relaxed); let _useglob = USEGLOB.load(Ordering::Relaxed); let context = compcontext_for(s); set_compstate_str("context", &context); set_compstate_str(
606 "last_prompt",
607 if dolastprompt.load(Ordering::Relaxed) != 0 {
608 "yes"
609 } else {
610 ""
611 },
612 );
613
614 let cl_value = COMPLIST
616 .get_or_init(|| Mutex::new(String::new()))
617 .lock()
618 .map(|g| g.clone())
619 .unwrap_or_default();
620 set_compstate_str("list", &cl_value); let ul = useline.load(Ordering::Relaxed);
624 let um = USEMENU.load(Ordering::Relaxed);
625 let ins = if ul != 0 {
626 match um {
627 0 => "unambiguous",
628 1 => "menu",
629 2 => "automenu",
630 _ => "",
631 }
632 } else {
633 ""
634 };
635 set_compstate_str("insert", ins); set_compstate_str(
639 "exact",
640 if useexact.load(Ordering::Relaxed) != 0 {
641 "accept"
642 } else {
643 ""
644 },
645 );
646
647 set_compstate_str(
649 "to_end",
650 if movetoend.load(Ordering::Relaxed) == 1 {
651 "single"
652 } else {
653 "match"
654 },
655 );
656
657 INCOMPFUNC.store(1, Ordering::Relaxed); let largs: Vec<String> = {
665 let mut v = vec![fn_name.to_string()];
666 if let Ok(cf) = crate::ported::zle::zle_tricky::cfargs.lock() {
667 v.extend(cf.iter().cloned());
668 }
669 v
670 };
671
672 let oxt = crate::ported::zsh_h::isset(crate::ported::zsh_h::XTRACE) as i32;
676 crate::ported::options::opt_state_set(
677 &crate::ported::zsh_h::opt_name(crate::ported::zsh_h::XTRACE),
678 false,
679 );
680 let _ = oxt; let largs_for_body = largs.clone();
692 let fn_name_owned = fn_name.to_string();
693 let body_runner = move || -> i32 {
694 if let Some(rust_fn) = crate::compsys::router::try_rust_dispatch(&fn_name_owned) {
698 return rust_fn(&largs_for_body[1..]);
700 }
701 crate::ported::exec::dispatch_function_call(&fn_name_owned, &largs_for_body[1..])
702 .unwrap_or_else(|| crate::ported::builtin::LASTVAL.load(Ordering::Relaxed))
703 };
704
705 let mut synth_shf = crate::ported::zsh_h::shfunc {
708 node: crate::ported::zsh_h::hashnode {
709 next: None,
710 nam: fn_name.to_string(),
711 flags: 0,
712 },
713 filename: None,
714 lineno: 0,
715 funcdef: None,
716 redir: None,
717 sticky: None,
718 body: None,
719 };
720 let cfret_val = crate::ported::exec::doshfunc(&mut synth_shf, largs, true, body_runner);
721 crate::ported::zle::zle_tricky::cfret.store(cfret_val, Ordering::Relaxed);
722
723 crate::ported::options::opt_state_set(
725 &crate::ported::zsh_h::opt_name(crate::ported::zsh_h::XTRACE),
726 oxt != 0,
727 );
728
729 let post_insert = getsparam("compstate[insert]").unwrap_or_default();
733 if !post_insert.is_empty() {
734 if post_insert.contains("automenu") {
735 USEMENU.store(2, Ordering::Relaxed);
736 } else if post_insert.contains("menu") {
737 USEMENU.store(1, Ordering::Relaxed);
738 }
739 }
740
741 INCOMPFUNC.store(_icf, Ordering::Relaxed);
743}
744
745pub fn makecomplist(s: &str, incmd: i32, lst: i32) -> i32 {
754 let owb = WB.load(Ordering::Relaxed); let owe = WE.load(Ordering::Relaxed);
757 let ooffs = OFFS.load(Ordering::Relaxed);
758
759 let mut s_owned = s.to_string();
761 if compfunc_active() {
762 if let Some(p) = check_param(&s_owned, false, false) {
763 s_owned = s_owned[p..].to_string(); PARWB.store(owb, Ordering::Relaxed); PARWE.store(owe, Ordering::Relaxed); PAROFFS.store(ooffs, Ordering::Relaxed); } else {
769 PARWB.store(-1, Ordering::Relaxed); }
771 } else {
772 PARWB.store(-1, Ordering::Relaxed); }
774
775 linwhat.store(INWHAT.load(Ordering::Relaxed), Ordering::Relaxed); if compfunc_active() {
778 let os = s_owned.clone(); let onm = nmatches.load(Ordering::Relaxed); let odm = diffmatches.load(Ordering::Relaxed); let osi = movefd(0); if let Ok(mut g) = bmatchers.get_or_init(|| Mutex::new(None)).lock() {
786 *g = None;
787 }
788 if let Ok(mut g) = mstack.get_or_init(|| Mutex::new(None)).lock() {
789 *g = None;
790 }
791 if let Ok(mut g) = ainfo.get_or_init(|| Mutex::new(None)).lock() {
793 *g = Some(Aminfo::default());
794 }
795 if let Ok(mut g) = fainfo.get_or_init(|| Mutex::new(None)).lock() {
796 *g = Some(Aminfo::default());
797 }
798 if let Ok(mut g) = freecl.get_or_init(|| Mutex::new(None)).lock() {
799 *g = None; }
801 if VALIDLIST.load(Ordering::Relaxed) == 0 {
802 LASTAMBIG.store(0, Ordering::Relaxed);
803 }
805 if let Ok(mut g) = amatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
806 g.clear(); }
808 mnum.store(0, Ordering::Relaxed); unambig_mnum.store(-1, Ordering::Relaxed); if let Ok(mut g) = isuf.get_or_init(|| Mutex::new(String::new())).lock() {
811 g.clear(); }
813 insmnum.store(ZMULT.load(Ordering::Relaxed), Ordering::Relaxed); oldlist.store(0, Ordering::Relaxed); oldins.store(0, Ordering::Relaxed); begcmgroup(Some("default"), 0); MENUCMP.store(0, Ordering::Relaxed); menuacc.store(0, Ordering::Relaxed); newmatches.store(0, Ordering::Relaxed); onlyexpl.store(0, Ordering::Relaxed); let dup_s = crate::ported::mem::dupstring(&os); let cf_name = compfunc
824 .get_or_init(|| Mutex::new(None))
825 .lock()
826 .ok()
827 .and_then(|g| g.clone())
828 .unwrap_or_default();
829 callcompfunc(&dup_s, &cf_name); endcmgroup(None); runhookdef_compcore("COMPCTLCLEANUPHOOK"); if oldlist.load(Ordering::Relaxed) != 0 {
836 nmatches.store(onm, Ordering::Relaxed); diffmatches.store(odm, Ordering::Relaxed); VALIDLIST.store(1, Ordering::Relaxed); if let Ok(mut g) = amatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
841 if let Ok(last) = lastmatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
842 *g = last.clone(); }
844 }
845 if let Ok(mut g) = lmatches.get_or_init(|| Mutex::new(None)).lock() {
846 let last_l = lastlmatches
847 .get_or_init(|| Mutex::new(None))
848 .lock()
849 .ok()
850 .and_then(|g| g.clone());
851 *g = last_l; }
853 let drained = pmatches
855 .get_or_init(|| Mutex::new(Vec::new()))
856 .lock()
857 .map(|mut g| std::mem::take(&mut *g))
858 .unwrap_or_default();
859 freematches(drained, 1); hasperm.store(0, Ordering::Relaxed); redup(osi); return 0; }
864 if !lastmatches
865 .get_or_init(|| Mutex::new(Vec::new()))
866 .lock()
867 .map(|g| g.is_empty())
868 .unwrap_or(true)
869 {
870 if let Ok(mut g) = lastmatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
872 g.clear(); }
874 }
875 permmatches(1); let p_snap = pmatches
878 .get_or_init(|| Mutex::new(Vec::new()))
879 .lock()
880 .ok()
881 .map(|g| g.clone())
882 .unwrap_or_default();
883 if let Ok(mut g) = amatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
884 *g = p_snap.clone(); }
886 lastpermmnum.store(permmnum.load(Ordering::Relaxed), Ordering::Relaxed); lastpermgnum.store(permgnum.load(Ordering::Relaxed), Ordering::Relaxed); if let Ok(mut g) = lastmatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
889 *g = p_snap; }
891 let lm_snap = lmatches
892 .get_or_init(|| Mutex::new(None))
893 .lock()
894 .ok()
895 .and_then(|g| g.clone());
896 if let Ok(mut g) = lastlmatches.get_or_init(|| Mutex::new(None)).lock() {
897 *g = lm_snap; }
899 if let Ok(mut g) = pmatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
900 g.clear(); }
902 hasperm.store(0, Ordering::Relaxed); hasoldlist.store(1, Ordering::Relaxed); let any_nm =
906 nmatches.load(Ordering::Relaxed) != 0 || nmessages.load(Ordering::Relaxed) != 0;
907 let errset = errflag_get();
908 if any_nm && !errset {
909 VALIDLIST.store(1, Ordering::Relaxed); redup(osi); return 0; }
914 redup(osi); return 1; } else {
917 let mut dat = Ccmakedat {
920 str: Some(s_owned.clone()), incmd, lst, };
924 runhookdef_compctlmake(&mut dat); runhookdef_compcore("COMPCTLCLEANUPHOOK"); return dat.lst; }
928}
929
930pub fn multiquote(s: &str, ign: i32) -> String {
937 let stack = COMPQSTACK .get_or_init(|| Mutex::new(String::new()))
940 .lock()
941 .map(|g| g.clone())
942 .unwrap_or_default();
943 let p_bytes = stack.as_bytes();
944 if !p_bytes.is_empty() && (ign == 0 || p_bytes.len() > 1) {
945 let start = if ign != 0 { 1 } else { 0 }; let mut cur = s.to_string();
948 for &q in &p_bytes[start..] {
949 let qt = match q as i32 {
951 x if x == QT_BACKSLASH => QT_BACKSLASH,
953 x if x == QT_SINGLE => QT_SINGLE,
954 x if x == QT_DOUBLE => QT_DOUBLE,
955 x if x == QT_DOLLARS => QT_DOLLARS,
956 _ => QT_BACKSLASH,
957 };
958 cur = crate::ported::utils::quotestring(&cur, qt);
959 }
960 cur } else {
962 s.to_string() }
964}
965
966pub fn tildequote(s: &str, ign: i32) -> String {
973 let bytes = s.as_bytes(); let tilde = !bytes.is_empty() && bytes[0] == b'~'; let staged = if tilde {
977 let mut tmp = String::with_capacity(s.len());
979 tmp.push('x');
980 tmp.push_str(&s[1..]);
981 tmp
982 } else {
983 s.to_string()
984 };
985 let mut quoted = multiquote(&staged, ign); if tilde && !quoted.is_empty() {
987 let mut new_q = String::with_capacity(quoted.len());
989 let mut swapped = false;
990 for c in quoted.chars() {
991 if !swapped && c == 'x' {
992 new_q.push('~');
993 swapped = true;
994 } else {
995 new_q.push(c);
996 }
997 }
998 quoted = new_q;
999 }
1000 quoted }
1002
1003pub fn check_param(s: &str, set: bool, test: bool) -> Option<usize> {
1015 if let Ok(mut g) = parpre.get_or_init(|| Mutex::new(String::new())).lock() {
1019 g.clear();
1020 }
1021
1022 if !test {
1023 ispar.store(0, Ordering::Relaxed); parq.store(0, Ordering::Relaxed); eparq.store(0, Ordering::Relaxed); }
1028
1029 let bytes = s.as_bytes(); let offs_v = OFFS.load(Ordering::Relaxed) as usize; let mut found = false; let mut qstring = false; let mut p: usize = offs_v.min(bytes.len().saturating_sub(1)); loop {
1038 if p < bytes.len() {
1039 let ch = char_at(bytes, p);
1040 if ch == Stringg || ch == Qstring {
1041 let next = char_at(bytes, p + ch.len_utf8());
1043 let snull_next = ch == Stringg && next == Snull; let qstr_quot = ch == Qstring && next == '\''; if p < offs_v && !snull_next && !qstr_quot {
1046 found = true; qstring = ch == Qstring; break;
1049 }
1050 }
1051 }
1052 if p == 0 {
1053 break;
1054 } p = prev_char_index(bytes, p);
1056 }
1057
1058 if found {
1059 while p > 0 {
1062 let prev = prev_char_index(bytes, p);
1063 let pc = char_at(bytes, prev);
1064 if pc == Stringg || pc == Qstring {
1065 p = prev;
1066 } else {
1067 break;
1068 }
1069 }
1070 loop {
1071 let n1 = p + char_at(bytes, p).len_utf8();
1073 if n1 >= bytes.len() {
1074 break;
1075 }
1076 let c1 = char_at(bytes, n1);
1077 let n2 = n1 + c1.len_utf8();
1078 if n2 >= bytes.len() {
1079 break;
1080 }
1081 let c2 = char_at(bytes, n2);
1082 if (c1 == Stringg || c1 == Qstring) && (c2 == Stringg || c2 == Qstring) {
1083 p = n2;
1084 } else {
1085 break;
1086 }
1087 }
1088 }
1089
1090 let next_char = if p + 1 <= bytes.len() {
1092 let dollar_len = char_at(bytes, p).len_utf8();
1093 char_at(bytes, p + dollar_len)
1094 } else {
1095 '\0'
1096 };
1097 if !(found && next_char != Inpar && next_char != Inbrack && next_char != Snull) {
1098 return None; }
1100
1101 let dollar_len = char_at(bytes, p).len_utf8();
1103 let mut b: usize = p + dollar_len; let mut br: i32 = 1; let mut nest: i32 = 0; if char_at(bytes, b) == Inbrace {
1108 let mut tb: &str = &s[b..];
1111 let bal = crate::ported::utils::skipparens(Inbrace, Outbrace, &mut tb);
1112 let tb_after = s.len() - tb.len();
1113 if bal == 0 && tb_after <= offs_v {
1114 return None; }
1116
1117 b += Inbrace.len_utf8(); br += 1;
1119 let mut b_str: &str = &s[b..];
1124 let flag_ret: i32 = if qstring {
1125 crate::ported::utils::skipparens('(', ')', &mut b_str)
1126 } else {
1127 crate::ported::utils::skipparens(Inpar, Outpar, &mut b_str)
1128 };
1129 let after_flags_pos = s.len() - b_str.len();
1130 if flag_ret > 0 || after_flags_pos > offs_v {
1131 ispar.store(2, Ordering::Relaxed); return None; }
1134 b = after_flags_pos;
1135
1136 let mut tb = p;
1138 while tb > 0 {
1139 let prev = prev_char_index(bytes, tb);
1140 let pc = char_at(bytes, prev);
1141 if pc == Outbrace || pc == Inbrace {
1142 tb = prev;
1143 break;
1144 }
1145 tb = prev;
1146 }
1147 if tb > 0 {
1148 let cc = char_at(bytes, tb);
1149 let prev = prev_char_index(bytes, tb);
1150 let pp = char_at(bytes, prev);
1151 if cc == Inbrace && (pp == Stringg || cc == Qstring) {
1152 nest = 1; }
1154 }
1155 }
1156
1157 while b < bytes.len() {
1159 let c = char_at(bytes, b);
1160 if c == '^' || c == Hat || c == '=' || c == Equals || c == '~' || c == Tilde {
1161 b += c.len_utf8();
1162 } else {
1163 break;
1164 }
1165 }
1166 if b < bytes.len() {
1168 let c = char_at(bytes, b);
1169 if c == '#' || c == Pound || c == '+' {
1170 b += c.len_utf8();
1171 }
1172 }
1173
1174 let mut e: usize = b; if br != 0 {
1176 let qopen = if test { Dnull } else { '"' };
1178 while e < bytes.len() && char_at(bytes, e) == qopen {
1179 e += qopen.len_utf8();
1181 parq.fetch_add(1, Ordering::Relaxed); }
1183 if !test {
1184 b = e;
1185 } }
1187
1188 if e < bytes.len() {
1190 let c = char_at(bytes, e);
1191 let one_char_name = matches!(c,
1192 ch if ch == Quest || ch == Star || ch == Stringg || ch == Qstring
1193 || ch == '?' || ch == '*' || ch == '$' || ch == '-' || ch == '!' || ch == '@');
1194 if one_char_name {
1195 e += c.len_utf8();
1197 } else if c.is_ascii_digit() {
1198 while e < bytes.len() && char_at(bytes, e).is_ascii_digit() {
1200 e += 1;
1202 }
1203 } else {
1204 let walked = walk_namespace(&bytes[e..]);
1206 if walked > 0 {
1207 e += walked;
1208 } else if c == '.' {
1209 e += 1;
1211 }
1212 }
1213 }
1214
1215 if offs_v <= e && offs_v >= b {
1217 if br != 0 {
1219 let qopen = if test { Dnull } else { '"' };
1220 let mut pq = e;
1221 while pq < bytes.len() && char_at(bytes, pq) == qopen {
1222 pq += qopen.len_utf8();
1223 parq.fetch_sub(1, Ordering::Relaxed);
1224 eparq.fetch_add(1, Ordering::Relaxed);
1225 }
1226 }
1227 if test {
1228 return Some(b); }
1231 if set {
1232 if br >= 2 {
1234 mflags.fetch_or(CMF_PARBR, Ordering::Relaxed); if nest != 0 {
1237 mflags.fetch_or(CMF_PARNEST, Ordering::Relaxed); }
1240 }
1241 let mut tail = String::from_utf8_lossy(&bytes[e..]).into_owned();
1243 tail = strip_tokens(&tail); if let Ok(mut g) = isuf.get_or_init(|| Mutex::new(String::new())).lock() {
1245 *g = tail;
1246 }
1247 let head = String::from_utf8_lossy(&bytes[..b]).into_owned();
1249 if let Ok(mut g) = ripre.get_or_init(|| Mutex::new(String::new())).lock() {
1250 *g = format!("{}{}", *g, head);
1251 }
1252 if let Ok(mut g) = ipre.get_or_init(|| Mutex::new(String::new())).lock() {
1253 *g = strip_tokens(&format!("{}{}", *g, head));
1254 }
1255 }
1256 let cf_active = compfunc
1258 .get_or_init(|| Mutex::new(None))
1259 .lock()
1260 .ok()
1261 .and_then(|g| g.clone())
1262 .map(|s| !s.is_empty())
1263 .unwrap_or(false);
1264 if cf_active {
1265 let pf = if br >= 2 {
1266 CMF_PARBR | (if nest != 0 { CMF_PARNEST } else { 0 })
1267 } else {
1268 0
1269 };
1270 parflags.store(pf, Ordering::Relaxed); let head = String::from_utf8_lossy(&bytes[..b]).into_owned();
1272 if let Ok(mut g) = parpre.get_or_init(|| Mutex::new(String::new())).lock() {
1273 *g = strip_tokens(&head); }
1275 }
1276 let off_delta = b as i32;
1278 OFFS.fetch_sub(off_delta, Ordering::Relaxed); let new_offs = OFFS.load(Ordering::Relaxed);
1280 let zlc = ZLEMETACS.load(Ordering::Relaxed);
1281 WB.store(zlc - new_offs, Ordering::Relaxed); WE.store(
1283 WB.load(Ordering::Relaxed) + (e - b) as i32,
1284 Ordering::Relaxed,
1285 ); ispar.store(if br >= 2 { 2 } else { 1 }, Ordering::Relaxed); return Some(b); } else if offs_v > e && e < bytes.len() && char_at(bytes, e) == ':' {
1289 let offsptr = offs_v;
1292 let mut e2 = e;
1293 while e2 < offsptr && e2 < bytes.len() {
1294 let c = char_at(bytes, e2);
1295 if c != ':' && !c.is_alphanumeric() {
1296 break;
1297 }
1298 e2 += c.len_utf8();
1299 }
1300 ispar.store(if br >= 2 { 2 } else { 1 }, Ordering::Relaxed); return None; }
1303
1304 let _ = (Bnull,); None }
1307
1308pub fn rembslash(s: &str) -> String {
1316 let mut result = String::with_capacity(s.len()); let mut chars = s.chars().peekable(); while let Some(c) = chars.next() {
1320 if c == '\\' {
1321 if let Some(nxt) = chars.next() {
1323 result.push(nxt);
1325 }
1326 } else {
1327 result.push(c); }
1329 }
1330 result }
1332
1333pub fn remsquote(s: &mut String) -> i32 {
1339 let rcquotes = isset(RCQUOTES); let qa: usize = if rcquotes { 1 } else { 3 };
1342
1343 let bytes = s.as_bytes(); let mut t = Vec::<u8>::with_capacity(bytes.len());
1345 let mut ret: i32 = 0;
1346 let mut i = 0;
1347 while i < bytes.len() {
1348 let matched = if qa == 1 {
1350 i + 1 < bytes.len() && bytes[i] == b'\'' && bytes[i + 1] == b'\''
1352 } else {
1353 i + 3 < bytes.len() && bytes[i] == b'\''
1355 && bytes[i + 1] == b'\\'
1356 && bytes[i + 2] == b'\''
1357 && bytes[i + 3] == b'\''
1358 };
1359 if matched {
1360 ret += qa as i32; t.push(b'\''); i += qa + 1; } else {
1364 t.push(bytes[i]); i += 1;
1366 }
1367 }
1368 *s = String::from_utf8(t).unwrap_or_default(); ret }
1371
1372pub fn ctokenize(p: &str) -> String {
1382 let bytes = p.as_bytes(); let mut out = Vec::<u8>::with_capacity(bytes.len());
1385 let mut bslash = false; let mut prev_idx: Option<usize> = None;
1387 let mut i = 0;
1388 while i < bytes.len() {
1389 let b = bytes[i]; if b == b'\\' {
1391 bslash = true;
1393 out.push(b);
1394 prev_idx = Some(out.len() - 1);
1395 } else {
1396 if b == b'$' || b == b'{' || b == b'}' {
1397 if bslash {
1399 if let Some(pi) = prev_idx {
1401 out.truncate(pi);
1403 let mut buf = [0u8; 4];
1404 out.extend_from_slice(Bnull.encode_utf8(&mut buf).as_bytes());
1405 }
1406 out.push(b);
1407 } else {
1408 let tok = if b == b'$' {
1409 Stringg
1410 }
1411 else if b == b'{' {
1413 Inbrace
1414 }
1415 else {
1417 Outbrace
1418 }; let mut buf = [0u8; 4];
1420 out.extend_from_slice(tok.encode_utf8(&mut buf).as_bytes());
1421 }
1422 } else {
1423 out.push(b);
1424 }
1425 bslash = false; prev_idx = Some(out.len().saturating_sub(1));
1427 }
1428 i += 1;
1429 }
1430 String::from_utf8(out).unwrap_or_default() }
1432
1433pub fn comp_str(untok: bool) -> (String, i32, i32) {
1440 let mut p = COMPPREFIX
1442 .get_or_init(|| Mutex::new(String::new())) .lock()
1444 .unwrap()
1445 .clone();
1446 let mut s = COMPSUFFIX
1447 .get_or_init(|| Mutex::new(String::new())) .lock()
1449 .unwrap()
1450 .clone();
1451 let ip = COMPIPREFIX
1452 .get_or_init(|| Mutex::new(String::new())) .lock()
1454 .unwrap()
1455 .clone();
1456 if !untok {
1457 p = ctokenize(&p); p = p.chars().filter(|&c| c != Bnull).collect(); s = ctokenize(&s); s = s.chars().filter(|&c| c != Bnull).collect(); }
1463 let lp = p.len() as i32; let lip = ip.len() as i32; let mut str = String::with_capacity(ip.len() + p.len() + s.len() + 1); str.push_str(&ip); str.push_str(&p); str.push_str(&s); (str, lip, lp) }
1471
1472pub fn comp_quoting_string(stype: i32) -> &'static str {
1479 match stype {
1481 x if x == QT_SINGLE => "'", x if x == QT_DOUBLE => "\"", x if x == QT_DOLLARS => "$'", _ => {
1486 let _ = QT_BACKSLASH;
1488 "\\" }
1490 }
1491}
1492
1493pub fn set_comp_sep() -> i32 {
1506 let (_s, _lip, _lp) = comp_str(false); let owe = WE.load(Ordering::Relaxed); let owb = WB.load(Ordering::Relaxed);
1510 let _ooffs = OFFS.load(Ordering::Relaxed);
1511 let lex_saved = lexsave(); lexrestore(lex_saved); WB.store(owb, Ordering::Relaxed);
1529 WE.store(owe, Ordering::Relaxed);
1530
1531 1 }
1533
1534#[doc(hidden)]
1537pub fn set_list_array(name: &str, l: &[String]) {
1545 let _ = setaparam(name, l.to_vec()); }
1548
1549pub fn get_user_var(nam: Option<&str>) -> Option<Vec<String>> {
1556 let nam = nam?; if nam.starts_with('(') {
1559 let mut arrlist: Vec<String> = Vec::new();
1561 let bytes = nam.as_bytes();
1562 let mut buf = Vec::<u8>::new();
1563 let mut notempty = false; let mut brk = false;
1565 let mut i = 1; while i < bytes.len() {
1567 let b = bytes[i];
1568 if b == b'\\' && i + 1 < bytes.len() {
1569 buf.push(bytes[i + 1]); notempty = true;
1572 i += 2;
1573 continue;
1574 }
1575 if b == b',' || b == b' ' || b == b'\t' || b == b'\n' || b == b')' {
1576 if b == b')' {
1577 brk = true;
1578 } if notempty {
1580 let mut start = 0;
1582 if !buf.is_empty() && buf[0] == b'\n' {
1583 start = 1;
1584 } let s = String::from_utf8_lossy(&buf[start..]).into_owned();
1586 arrlist.push(s); }
1588 buf.clear(); notempty = false;
1590 } else {
1591 notempty = true; buf.push(b);
1593 }
1594 i += 1;
1595 if brk {
1596 break;
1597 } }
1599 if !brk || arrlist.is_empty() {
1600 return None;
1601 } Some(arrlist) } else {
1604 queue_signals();
1611 let result = {
1612 let tab = match paramtab().read() {
1613 Ok(t) => t,
1614 Err(_) => {
1615 unqueue_signals();
1616 return None;
1617 }
1618 };
1619 tab.get(nam).and_then(|pm| {
1620 if let Some(arr) = pm.u_arr.as_ref() {
1621 Some(arr.clone()) } else if let Some(s) = pm.u_str.as_ref() {
1623 Some(vec![s.clone()]) } else {
1625 None
1626 }
1627 })
1628 };
1629 unqueue_signals(); result
1631 }
1632}
1633
1634pub fn get_data_arr(name: &str, keys: bool) -> Option<Vec<String>> {
1646 queue_signals(); let is_hashed = match paramtab().read() {
1655 Ok(t) => t
1656 .get(name)
1657 .map(|pm| PM_TYPE(pm.node.flags as u32) == PM_HASHED)
1658 .unwrap_or(false),
1659 Err(_) => false,
1660 };
1661
1662 let result = if is_hashed {
1663 paramtab_hashed_storage().lock().ok().and_then(|m| {
1664 m.get(name).map(|map| {
1665 if keys {
1666 map.keys().cloned().collect::<Vec<_>>()
1667 } else {
1668 map.values().cloned().collect::<Vec<_>>()
1669 }
1670 })
1671 })
1672 } else {
1673 None
1675 };
1676
1677 unqueue_signals(); result
1679}
1680
1681pub fn addmatch(str: &str, flags: i32, disp: Option<&str>, line: bool) {
1688 let mut cm = Cmatch::default(); cm.str = Some(str.to_string()); let complist_extra = {
1694 let s = COMPLIST
1695 .get_or_init(|| Mutex::new(String::new()))
1696 .lock()
1697 .map(|g| g.clone())
1698 .unwrap_or_default();
1699 let packed = if s.contains("packed") { CMF_PACKED } else { 0 }; let rows = if s.contains("rows") { CMF_ROWS } else { 0 }; if s.is_empty() {
1702 0
1703 } else {
1704 packed | rows
1705 }
1706 };
1707 cm.flags = flags | complist_extra; if let Some(d) = disp {
1709 cm.disp = Some(d.to_string()); } else if line {
1712 cm.disp = Some(String::new()); cm.flags |= CMF_DISPLINE; }
1716 mnum.fetch_add(1, Ordering::Relaxed); {
1718 let cell = curexpl.get_or_init(|| Mutex::new(None)); if let Ok(mut g) = cell.lock() {
1720 if let Some(e) = g.as_mut() {
1721 e.count += 1;
1722 }
1723 }
1724 }
1725 let mcell = matches.get_or_init(|| Mutex::new(Vec::new())); if let Ok(mut g) = mcell.lock() {
1727 g.push(cm);
1728 }
1729 newmatches.store(1, Ordering::Relaxed); {
1731 let cell = mgroup.get_or_init(|| Mutex::new(None)); if let Ok(mut g) = cell.lock() {
1733 if let Some(grp) = g.as_mut() {
1734 grp.new_ = 1;
1735 }
1736 }
1737 }
1738}
1739
1740pub fn addmatches(
1761 dat: &mut Cadata, argv: &[String],
1763) -> i32 {
1764 let _nm = mnum.load(Ordering::Relaxed); if dat.dummies >= 0 {
1767 dat.aflags = (dat.aflags | CAF_NOSORT | CAF_UNIQCON) & !CAF_UNIQALL; }
1770
1771 let gflags = (if (dat.aflags & CAF_NOSORT) != 0 {
1772 CGF_NOSORT
1773 } else {
1774 0
1775 }) | (if (dat.aflags & CAF_MATSORT) != 0 {
1776 CGF_MATSORT
1777 } else {
1778 0
1779 }) | (if (dat.aflags & CAF_NUMSORT) != 0 {
1780 CGF_NUMSORT
1781 } else {
1782 0
1783 }) | (if (dat.aflags & CAF_REVSORT) != 0 {
1784 CGF_REVSORT
1785 } else {
1786 0
1787 }) | (if (dat.aflags & CAF_UNIQALL) != 0 {
1788 CGF_UNIQALL
1789 } else {
1790 0
1791 }) | (if (dat.aflags & CAF_UNIQCON) != 0 {
1792 CGF_UNIQCON
1793 } else {
1794 0
1795 });
1796
1797 if let Some(g) = dat.group.as_deref() {
1798 endcmgroup(None); begcmgroup(Some(g), gflags); } else {
1802 endcmgroup(None); begcmgroup(Some("default"), 0); }
1805
1806 if dat.mesg.is_some() || dat.exp.is_some() {
1807 let mut e = Cexpl::default(); e.always = if dat.mesg.is_some() { 1 } else { 0 }; e.count = 0;
1811 e.fcount = 0; e.str = Some(
1813 dat.mesg
1814 .clone() .or_else(|| dat.exp.clone())
1816 .unwrap_or_default(),
1817 );
1818 if let Ok(mut g) = curexpl.get_or_init(|| Mutex::new(None)).lock() {
1819 *g = Some(e);
1820 }
1821 if dat.mesg.is_some() && dat.dpar.is_empty() && dat.opar.is_none() && dat.apar.is_none() {
1822 addexpl(true); }
1825 } else if let Ok(mut g) = curexpl.get_or_init(|| Mutex::new(None)).lock() {
1826 *g = None; }
1828
1829 if argv.is_empty() && dat.dummies == 0 && (dat.aflags & CAF_ALL) == 0 {
1831 return 1; }
1833
1834 let _quote_mode = (dat.aflags & CAF_QUOTE) != 0; if (dat.flags & 0x0008) != 0 {
1838 dat.flags |= parflags.load(Ordering::Relaxed); }
1841
1842 let qc = compquote_first(); if let Some(q) = qc {
1844 match q {
1846 '`' => {
1847 instring_set(0);
1848 inbackt_set(0);
1849 autoq_set("");
1850 } '\'' => instring_set(QT_SINGLE), '"' => instring_set(QT_DOUBLE), '$' => instring_set(QT_DOLLARS), _ => {}
1855 }
1856 } else {
1857 instring_set(0);
1858 inbackt_set(0);
1859 autoq_set(""); }
1861
1862 let exact_str = getsparam("compexact").unwrap_or_default();
1868 useexact.store(if exact_str == "accept" { 1 } else { 0 }, Ordering::Relaxed);
1869
1870 if let Some(ref m) = dat.match_ {
1873 if let Ok(mut mst) = mstack.get_or_init(|| Mutex::new(None)).lock() {
1875 let new_link = Box::new(Cmlist {
1877 next: mst.take(),
1878 matcher: m.clone(),
1879 str: String::new(),
1880 });
1881 *mst = Some(new_link);
1882 }
1883 crate::ported::zle::compmatch::add_bmatchers(Some(m));
1885 if let Ok(mut g) = matchers.get_or_init(|| Mutex::new(Vec::new())).lock() {
1887 g.push(m.clone());
1888 }
1889 }
1890
1891 let (aign, pign) = if let Some(ign_name) = dat.ign.as_deref() {
1893 let aign_raw = get_user_var(Some(ign_name)).unwrap_or_default();
1895 let mut literal_suffixes: Vec<String> = Vec::new();
1896 let mut pat_progs: Vec<crate::ported::pattern::Patprog> = Vec::new();
1897 for entry in aign_raw {
1898 let mut tmp = entry.clone();
1902 crate::ported::glob::tokenize(&mut tmp); crate::ported::glob::remnulargs(&mut tmp); let tch: Vec<char> = tmp.chars().collect();
1908 let suffix: String = tch.iter().skip(2).collect();
1909 let star_prefix = tch.len() >= 3
1910 && ((tch[0] == crate::ported::zsh_h::Quest && tch[1] == crate::ported::zsh_h::Star)
1911 || (tch[1] == crate::ported::zsh_h::Quest
1912 && tch[0] == crate::ported::zsh_h::Star))
1913 && !crate::ported::pattern::haswilds(&suffix);
1914 if star_prefix {
1915 literal_suffixes.push(crate::ported::lex::untokenize(&suffix));
1917 } else if let Some(prog) =
1918 crate::ported::pattern::patcompile(&tmp, 0, None::<&mut String>)
1919 {
1920 pat_progs.push(prog);
1921 }
1922 }
1923 (literal_suffixes, pat_progs)
1924 } else {
1925 (Vec::new(), Vec::new())
1926 };
1927
1928 let disp_arr: Vec<String> = if let Some(ref d) = dat.disp {
1930 get_user_var(Some(d.as_str())).unwrap_or_default()
1931 } else {
1932 Vec::new()
1933 };
1934
1935 let compiprefix_s = COMPIPREFIX
1937 .get_or_init(|| Mutex::new(String::new()))
1938 .lock()
1939 .map(|g| g.clone())
1940 .unwrap_or_default();
1941 let compisuffix_s = crate::ported::zle::complete::COMPISUFFIX
1942 .get_or_init(|| Mutex::new(String::new()))
1943 .lock()
1944 .map(|g| g.clone())
1945 .unwrap_or_default();
1946 let compprefix_s = COMPPREFIX
1947 .get_or_init(|| Mutex::new(String::new()))
1948 .lock()
1949 .map(|g| g.clone())
1950 .unwrap_or_default();
1951 let compsuffix_s = COMPSUFFIX
1952 .get_or_init(|| Mutex::new(String::new()))
1953 .lock()
1954 .map(|g| g.clone())
1955 .unwrap_or_default();
1956 let lipre = compiprefix_s.clone();
1957 let lisuf = compisuffix_s.clone();
1958 let lpre = compprefix_s.clone();
1959 let lsuf = compsuffix_s.clone();
1960
1961 if let Some(ref existing) = dat.ipre.clone() {
1963 dat.ipre = Some(if !lipre.is_empty() {
1964 format!("{}{}", lipre, existing)
1965 } else {
1966 existing.clone()
1967 });
1968 } else if !lipre.is_empty() {
1969 dat.ipre = Some(lipre.clone());
1970 }
1971 if let Some(ref existing) = dat.isuf.clone() {
1972 dat.isuf = Some(if !lisuf.is_empty() {
1973 format!("{}{}", lisuf, existing)
1974 } else {
1975 existing.clone()
1976 });
1977 } else if !lisuf.is_empty() {
1978 dat.isuf = Some(lisuf.clone());
1979 }
1980 let quote_flag = if (dat.aflags & CAF_QUOTE) != 0 { 1 } else { 0 };
1981 if let Some(ref existing) = dat.ppre.clone() {
1982 let quoted = if (dat.flags & 0x0001) != 0 {
1983 tildequote(existing, quote_flag)
1984 } else {
1985 multiquote(existing, quote_flag)
1986 };
1987 dat.ppre = Some(quoted);
1988 }
1989 if let Some(ref existing) = dat.psuf.clone() {
1990 dat.psuf = Some(multiquote(existing, quote_flag));
1991 }
1992 let ppl = dat.ppre.as_deref().map(|s| s.len()).unwrap_or(0);
1993 let psl = dat.psuf.as_deref().map(|s| s.len()).unwrap_or(0);
1994
1995 let doadd = dat.apar.is_none() && dat.opar.is_none() && dat.dpar.is_empty();
1997 let mut apar_list: Vec<String> = Vec::new();
1998 let mut opar_list: Vec<String> = Vec::new();
1999
2000 let mut added = 0i32;
2002 let mut disp_idx = 0usize;
2003 let mut compignored_local = 0i32;
2004 'cand: for word in argv {
2005 let cur_disp = if !disp_arr.is_empty() && disp_idx < disp_arr.len() {
2008 let d = disp_arr[disp_idx].clone();
2009 disp_idx += 1;
2010 Some(d)
2011 } else {
2012 None
2013 };
2014
2015 if !aign.is_empty() || !pign.is_empty() {
2017 let full = format!(
2018 "{}{}{}",
2019 dat.ppre.as_deref().unwrap_or(""),
2020 word,
2021 dat.psuf.as_deref().unwrap_or("")
2022 );
2023 for suf in &aign {
2025 if full.len() >= suf.len() && full.ends_with(suf.as_str()) {
2026 compignored_local += 1;
2027 continue 'cand;
2028 }
2029 }
2030 for prog in &pign {
2032 if crate::ported::pattern::pattry(prog, &full) {
2033 compignored_local += 1;
2034 continue 'cand;
2035 }
2036 }
2037 }
2038
2039 let ms: String;
2044 let _lc;
2045 let isexact;
2046 if (dat.aflags & CAF_MATCH) == 0 {
2047 ms = if (dat.aflags & CAF_QUOTE) != 0 {
2049 word.clone()
2050 } else {
2051 multiquote(word, 0)
2052 };
2053 let sl = ms.len() as i32;
2054 _lc = bld_parts(&ms, sl, -1, None, None);
2055 isexact = 0;
2056 } else {
2057 let qu = if (dat.aflags & CAF_QUOTE) != 0 {
2060 0
2061 } else if dat.ppre.is_some() || (dat.flags & 0x0001) == 0 {
2062 1
2063 } else {
2064 2
2065 };
2066 let mut lc_out: Option<Box<Cline>> = None;
2067 let mut isexact_out = 0i32;
2068 match crate::ported::zle::compmatch::comp_match(
2071 &lpre,
2072 &lsuf,
2073 word,
2074 None,
2075 Some(&mut lc_out),
2076 qu,
2077 None,
2078 0,
2079 None,
2080 0,
2081 &mut isexact_out,
2082 ) {
2083 Some(matched) => {
2084 ms = matched;
2085 _lc = lc_out;
2086 isexact = isexact_out;
2087 }
2088 None => {
2089 continue 'cand; }
2091 }
2092 }
2093
2094 if doadd {
2095 let cm = add_match_data(
2098 0,
2099 &ms,
2100 word,
2101 _lc.clone(), dat.ipre.as_deref().unwrap_or(""),
2103 "", dat.isuf.as_deref().unwrap_or(""),
2105 dat.pre.as_deref().unwrap_or(""),
2106 dat.prpre.as_deref().unwrap_or(""),
2107 dat.ppre.as_deref().unwrap_or(""),
2108 None, dat.psuf.as_deref().unwrap_or(""),
2110 None, dat.suf.as_deref().unwrap_or(""),
2112 dat.flags,
2113 isexact,
2114 );
2115 let _ = cur_disp;
2116 let _ = cm;
2117 added += 1;
2118 } else {
2119 if dat.apar.is_some() {
2121 apar_list.push(ms.clone());
2123 }
2124 if dat.opar.is_some() {
2125 opar_list.push(word.clone());
2127 }
2128 }
2129 }
2130
2131 if let Some(ref name) = dat.apar {
2133 setaparam(name, apar_list);
2134 }
2135 if let Some(ref name) = dat.opar {
2136 setaparam(name, opar_list);
2137 }
2138
2139 if dat.exp.is_some() {
2141 addexpl(false);
2143 }
2144
2145 let hasall = hasallmatch.load(Ordering::Relaxed);
2147 if hasall == 0 && (dat.aflags & CAF_ALL) != 0 {
2148 addmatch(
2149 "<all>",
2150 dat.flags | crate::ported::zle::comp_h::CMF_ALL,
2151 None,
2152 true,
2153 );
2154 hasallmatch.store(1, Ordering::Relaxed);
2155 }
2156
2157 while dat.dummies > 0 {
2159 addmatch(
2160 "",
2161 dat.flags | crate::ported::zle::comp_h::CMF_DUMMY,
2162 None,
2163 false,
2164 );
2165 dat.dummies -= 1;
2166 }
2167
2168 let _ = (ppl, psl, compignored_local, added);
2169 0 }
2171
2172#[allow(clippy::too_many_arguments)]
2187pub fn add_match_data(
2188 alt: i32,
2190 str: &str,
2191 orig: &str,
2192 mut line: Option<Box<Cline>>,
2193 ipre_: &str,
2194 ripre_: &str,
2195 isuf_: &str,
2196 pre: &str,
2197 prpre: &str,
2198 ppre: &str,
2199 mut pline: Option<Box<Cline>>,
2200 psuf: &str,
2201 mut sline: Option<Box<Cline>>,
2202 suf: &str,
2203 flags: i32,
2204 exact: i32,
2205) -> Cmatch {
2206 DPUTS!(line.is_none(), "BUG: add_match_data() without cline"); let _ai_ref = if alt != 0 { &fainfo } else { &ainfo }; cline_matched(&mut line);
2212 if pline.is_some() {
2213 cline_matched(&mut pline);
2214 }
2215 if sline.is_some() {
2216 cline_matched(&mut sline);
2217 }
2218
2219 let psl = psuf.len();
2221 let isl = isuf_.len();
2222 let qisuf_v = qisuf_get(); let qisl = qisuf_v.len();
2224 let _salen = (if sline.is_none() { psl } else { 0 }) + isl + qisl; let ipl = ipre_.len();
2227 let _ppl = ppre.len();
2228 let _pl = pre.len();
2229 let qipre_v = qipre_get(); let qipl_v = qipre_v.clone();
2231 let _qipl = qipl_v.len();
2232
2233 let _stl = str.len();
2234 let _lpl = ripre_.len();
2235 let _lsl = suf.len();
2236 let _ml = ipl;
2237
2238 let psl_local = if sline.is_none() && !psuf.is_empty() {
2243 psuf.len() as i32
2244 } else {
2245 0
2246 };
2247 let isl_local = isuf_.len() as i32;
2248 let qisl_local = qisuf_v.len() as i32;
2249 let salen = psl_local + isl_local + qisl_local;
2250 if salen > 0 && line.is_some() {
2251 unsafe {
2253 let mut tail: *mut Option<Box<Cline>> = &mut line;
2254 while let Some(ref n) = *tail {
2255 if n.next.is_none() {
2256 break;
2257 }
2258 tail = &mut (*tail).as_mut().unwrap().next;
2259 }
2260 if psl_local > 0 {
2263 let s = bld_parts(psuf, psl_local, psl_local, None, None);
2264 if let Some(node) = (*tail).as_mut() {
2265 node.next = s;
2266 while let Some(ref nn) = node.next {
2267 if nn.next.is_none() {
2268 break;
2269 }
2270 break;
2272 }
2273 }
2274 while let Some(ref n) = *tail {
2276 if n.next.is_none() {
2277 break;
2278 }
2279 tail = &mut (*tail).as_mut().unwrap().next;
2280 }
2281 }
2282 if isl_local > 0 {
2283 let s = bld_parts(isuf_, isl_local, isl_local, None, None);
2284 if let Some(node) = (*tail).as_mut() {
2285 node.next = s;
2286 }
2287 while let Some(ref n) = *tail {
2288 if n.next.is_none() {
2289 break;
2290 }
2291 tail = &mut (*tail).as_mut().unwrap().next;
2292 }
2293 }
2294 if qisl_local > 0 {
2295 let mut s = bld_parts(&qisuf_v, qisl_local, qisl_local, None, None);
2296 if let Some(qsl) = s.as_mut() {
2298 qsl.flags |= crate::ported::zle::comp_h::CLF_SUF;
2299 qsl.suffix = qsl.prefix.take();
2300 }
2301 if let Some(node) = (*tail).as_mut() {
2302 node.next = s;
2303 }
2304 }
2305 }
2306 }
2307
2308 let qipl_local = qipre_v.len() as i32;
2312 let ipl_local = ipre_.len() as i32;
2313 let pl_local = pre.len() as i32;
2314 let ppl_local = if pline.is_none() && !ppre.is_empty() {
2315 ppre.len() as i32
2316 } else {
2317 0
2318 };
2319 if pl_local > 0 {
2320 if ppl_local > 0 {
2321 let p = bld_parts(ppre, ppl_local, ppl_local, None, None);
2322 if p.is_some() {
2324 let mut p_chain = p;
2325 let mut tail: *mut Option<Box<Cline>> = &mut p_chain;
2326 unsafe {
2327 while let Some(ref n) = *tail {
2328 if n.next.is_none() {
2329 break;
2330 }
2331 tail = &mut (*tail).as_mut().unwrap().next;
2332 }
2333 if let Some(t) = (*tail).as_mut() {
2334 t.next = line.take();
2335 }
2336 }
2337 line = p_chain;
2338 }
2339 }
2340 let p = bld_parts(pre, pl_local, pl_local, None, None);
2341 if let Some(mut head) = p {
2342 let mut t: *mut Option<Box<Cline>> = &mut head.next;
2343 unsafe {
2344 while (*t).is_some() {
2345 if (*t).as_deref().unwrap().next.is_none() {
2346 break;
2347 }
2348 t = &mut (*t).as_mut().unwrap().next;
2349 }
2350 *t = line.take();
2351 }
2352 line = Some(head);
2353 }
2354 if ipl_local > 0 {
2355 let p = bld_parts(ipre_, ipl_local, ipl_local, None, None);
2356 if let Some(mut head) = p {
2357 let mut t: *mut Option<Box<Cline>> = &mut head.next;
2358 unsafe {
2359 while (*t).is_some() {
2360 if (*t).as_deref().unwrap().next.is_none() {
2361 break;
2362 }
2363 t = &mut (*t).as_mut().unwrap().next;
2364 }
2365 *t = line.take();
2366 }
2367 line = Some(head);
2368 }
2369 }
2370 if qipl_local > 0 {
2371 let p = bld_parts(&qipre_v, qipl_local, qipl_local, None, None);
2372 if let Some(mut head) = p {
2373 let mut t: *mut Option<Box<Cline>> = &mut head.next;
2374 unsafe {
2375 while (*t).is_some() {
2376 if (*t).as_deref().unwrap().next.is_none() {
2377 break;
2378 }
2379 t = &mut (*t).as_mut().unwrap().next;
2380 }
2381 *t = line.take();
2382 }
2383 line = Some(head);
2384 }
2385 }
2386 } else if qipl_local + ipl_local + pl_local + ppl_local > 0 || pline.is_some() {
2387 let apre = format!(
2389 "{}{}{}{}",
2390 qipre_v.as_str(),
2391 ipre_,
2392 pre,
2393 if pline.is_none() { ppre } else { "" }
2394 );
2395 let apre_len = apre.len() as i32;
2396 if apre_len > 0 {
2397 let p = bld_parts(&apre, apre_len, apre_len, None, None);
2398 if let Some(mut head) = p {
2399 let mut t: *mut Option<Box<Cline>> = &mut head.next;
2400 unsafe {
2401 while (*t).is_some() {
2402 if (*t).as_deref().unwrap().next.is_none() {
2403 break;
2404 }
2405 t = &mut (*t).as_mut().unwrap().next;
2406 }
2407 *t = line.take();
2408 }
2409 line = Some(head);
2410 }
2411 }
2412 }
2413
2414 let stl = str.len();
2415
2416 let mut cm = Cmatch::default(); cm.str = Some(str.to_string()); cm.orig = Some(orig.to_string()); cm.ppre = if ppre.is_empty() {
2421 None
2422 } else {
2423 Some(ppre.into())
2424 }; cm.psuf = if psuf.is_empty() {
2426 None
2427 } else {
2428 Some(psuf.into())
2429 }; cm.prpre = if (flags & CMF_FILE) != 0 && !prpre.is_empty() {
2433 Some(prpre.into())
2434 } else {
2435 None
2436 };
2437
2438 cm.ipre = if !qipre_v.is_empty() {
2441 if !ipre_.is_empty() {
2442 Some(format!("{}{}", qipre_v, ipre_))
2443 } else {
2444 Some(qipre_v.clone())
2445 }
2446 } else if !ipre_.is_empty() {
2447 Some(ipre_.into())
2448 } else {
2449 None
2450 };
2451
2452 cm.ripre = if ripre_.is_empty() {
2453 None
2454 } else {
2455 Some(ripre_.into())
2456 }; cm.isuf = if !qisuf_v.is_empty() {
2460 if !isuf_.is_empty() {
2461 Some(format!("{}{}", isuf_, qisuf_v))
2462 } else {
2463 Some(qisuf_v.clone())
2464 }
2465 } else if !isuf_.is_empty() {
2466 Some(isuf_.into())
2467 } else {
2468 None
2469 };
2470
2471 cm.pre = if pre.is_empty() {
2472 None
2473 } else {
2474 Some(pre.into())
2475 }; cm.suf = if suf.is_empty() {
2477 None
2478 } else {
2479 Some(suf.into())
2480 }; let complist_s = COMPLIST
2484 .get()
2485 .and_then(|m| m.lock().ok().map(|g| g.clone()))
2486 .unwrap_or_default();
2487 let extra_flags = (if complist_s.contains("packed") {
2488 CMF_PACKED
2489 } else {
2490 0
2491 }) | (if complist_s.contains("rows") {
2492 CMF_ROWS
2493 } else {
2494 0
2495 });
2496 cm.flags = flags | extra_flags;
2497
2498 cm.mode = 0;
2500 cm.fmode = 0;
2501 cm.modec = '\0';
2502 cm.fmodec = '\0';
2503
2504 use crate::ported::zle::comp_h::CMF_FILE;
2506 if (flags & CMF_FILE) != 0 && !orig.is_empty() && !orig.ends_with('/') {
2507 let pb = format!("{}{}", cm.prpre.as_deref().unwrap_or("./"), orig);
2508 if let Some(meta) = ztat(&pb, false) {
2510 use std::os::unix::fs::MetadataExt;
2511 cm.mode = meta.mode();
2512 }
2513 if let Some(meta) = ztat(&pb, true) {
2515 use std::os::unix::fs::MetadataExt;
2516 cm.fmode = meta.mode();
2517 }
2518 }
2519
2520 cm.brpl = BRBEG
2526 .get_or_init(|| Mutex::new(None))
2527 .lock()
2528 .ok()
2529 .and_then(|g| {
2530 g.as_ref().map(|head| {
2531 let mut out: Vec<i32> = Vec::new();
2532 let mut cur = Some(head.as_ref());
2533 while let Some(n) = cur {
2534 out.push(n.qpos);
2535 cur = n.next.as_deref();
2536 }
2537 out
2538 })
2539 })
2540 .unwrap_or_default();
2541 cm.brsl = BREND
2542 .get_or_init(|| Mutex::new(None))
2543 .lock()
2544 .ok()
2545 .and_then(|g| {
2546 g.as_ref().map(|head| {
2547 let mut out: Vec<i32> = Vec::new();
2548 let mut cur = Some(head.as_ref());
2549 while let Some(n) = cur {
2550 out.push(n.qpos);
2551 cur = n.next.as_deref();
2552 }
2553 out
2554 })
2555 })
2556 .unwrap_or_default();
2557
2558 cm.qipl = qipre_v.len() as i32; cm.qisl = qisuf_v.len() as i32; let autoq_v = AUTOQ
2562 .get()
2563 .and_then(|m| m.lock().ok().map(|g| g.clone()))
2564 .unwrap_or_default();
2565 cm.autoq = if !autoq_v.is_empty() {
2566 Some(autoq_v)
2567 } else if INBACKT.load(Ordering::Relaxed) != 0 {
2568 Some("`".into())
2569 } else {
2570 None
2571 };
2572
2573 cm.rems = None;
2574 cm.remf = None;
2575 cm.disp = None; if let Ok(mut g) = ainfo.get_or_init(|| Mutex::new(None)).lock() {
2579 if let Some(a) = g.as_mut() {
2580 let old_line = a.line.take();
2581 a.line = crate::ported::zle::compmatch::join_clines(old_line, line);
2582 }
2583 }
2584
2585 mnum.fetch_add(1, Ordering::Relaxed);
2587
2588 if let Ok(mut g) = ainfo.get_or_init(|| Mutex::new(None)).lock() {
2590 if let Some(a) = g.as_mut() {
2591 a.count += 1;
2592 }
2593 }
2594
2595 newmatches.store(1, Ordering::Relaxed);
2600
2601 if alt != 0 {
2603 crate::ported::zle::complete::COMPIGNORED.fetch_add(1, Ordering::Relaxed);
2604 }
2605
2606 let complastprompt_v = crate::ported::zle::complete::COMPLASTPREFIX
2608 .get()
2609 .and_then(|m| m.lock().ok().map(|g| g.clone()))
2610 .unwrap_or_default();
2611 if complastprompt_v.is_empty() {
2612 dolastprompt.store(0, Ordering::Relaxed);
2613 }
2614
2615 if let Ok(mut g) = curexpl.get_or_init(|| Mutex::new(None)).lock() {
2617 if let Some(e) = g.as_mut() {
2618 if alt != 0 {
2619 e.fcount += 1;
2620 } else {
2621 e.count += 1;
2622 }
2623 }
2624 }
2625
2626 if let Ok(mut g) = ainfo.get_or_init(|| Mutex::new(None)).lock() {
2628 if let Some(a) = g.as_mut() {
2629 if a.firstm.is_none() {
2630 a.firstm = Some(Box::new(cm.clone()));
2631 }
2632 }
2633 }
2634
2635 let lpl = cm.ppre.as_deref().map(|s| s.len()).unwrap_or(0);
2637 let lsl = cm.psuf.as_deref().map(|s| s.len()).unwrap_or(0);
2638 let ml = (stl + lpl + lsl) as i32;
2639 let cur_min = minmlen.load(Ordering::Relaxed);
2640 let cur_max = maxmlen.load(Ordering::Relaxed);
2641 if ml < cur_min {
2642 minmlen.store(ml, Ordering::Relaxed);
2643 }
2644 if ml > cur_max {
2645 maxmlen.store(ml, Ordering::Relaxed);
2646 }
2647
2648 if exact != 0 {
2650 if let Ok(mut g) = ainfo.get_or_init(|| Mutex::new(None)).lock() {
2652 if let Some(a) = g.as_mut() {
2653 if a.exact == 0 {
2654 a.exact = useexact.load(Ordering::Relaxed);
2656 a.exactm = Some(Box::new(cm.clone())); } else if useexact.load(Ordering::Relaxed) != 0 {
2658 a.exact = 2;
2661 a.exactm = None;
2662 }
2663 }
2664 }
2665 }
2666
2667 let cell = if alt != 0 {
2669 fmatches.get_or_init(|| Mutex::new(Vec::new()))
2670 } else {
2671 matches.get_or_init(|| Mutex::new(Vec::new()))
2672 };
2673 if let Ok(mut g) = cell.lock() {
2674 g.push(cm.clone());
2675 }
2676
2677 cm }
2679
2680pub fn begcmgroup(n: Option<&str>, flags: i32) {
2690 if let Some(name) = n {
2692 let mask = CGF_NOSORT | CGF_UNIQALL | CGF_UNIQCON | CGF_MATSORT | CGF_NUMSORT | CGF_REVSORT;
2695 let cell = amatches.get_or_init(|| Mutex::new(Vec::new()));
2696 if let Ok(g) = cell.lock() {
2697 for grp in g.iter() {
2698 if grp.name.as_deref() == Some(name) && (grp.flags & mask) == flags
2701 {
2702 let active = grp.clone(); let mc = mgroup.get_or_init(|| Mutex::new(None));
2704 if let Ok(mut s) = mc.lock() {
2705 *s = Some(active);
2706 }
2707 return; }
2709 }
2710 }
2711 }
2712 let mut grp = Cmgroup::default(); grp.name = n.map(String::from); grp.flags = flags; let cell = amatches.get_or_init(|| Mutex::new(Vec::new()));
2716 if let Ok(mut g) = cell.lock() {
2717 g.insert(0, grp.clone()); }
2719 let mc = mgroup.get_or_init(|| Mutex::new(None));
2720 if let Ok(mut s) = mc.lock() {
2721 *s = Some(grp);
2722 }
2723 if let Ok(mut g) = expls.get_or_init(|| Mutex::new(Vec::new())).lock() {
2724 g.clear();
2725 }
2726 if let Ok(mut g) = matches.get_or_init(|| Mutex::new(Vec::new())).lock() {
2727 g.clear();
2728 }
2729 if let Ok(mut g) = fmatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
2730 g.clear();
2731 }
2732 if let Ok(mut g) = allccs.get_or_init(|| Mutex::new(Vec::new())).lock() {
2733 g.clear();
2734 }
2735}
2736
2737pub fn endcmgroup(ylist: Option<Vec<String>>) {
2744 if let Ok(mut g) = mgroup.get_or_init(|| Mutex::new(None)).lock() {
2746 if let Some(grp) = g.as_mut() {
2747 grp.ylist = ylist.unwrap_or_default();
2748 } }
2750}
2751
2752pub fn addexpl(always: bool) {
2758 let curexpl_snap = {
2760 let cell = curexpl.get_or_init(|| Mutex::new(None));
2761 cell.lock().ok().and_then(|g| g.clone())
2762 };
2763 let curexpl_str = match curexpl_snap.as_ref().and_then(|e| e.str.clone()) {
2764 Some(s) => s,
2765 None => return,
2766 };
2767 let curexpl_count = curexpl_snap.as_ref().map(|e| e.count).unwrap_or(0);
2768 let curexpl_fcount = curexpl_snap.as_ref().map(|e| e.fcount).unwrap_or(0);
2769
2770 let elist = expls.get_or_init(|| Mutex::new(Vec::new()));
2771 if let Ok(mut g) = elist.lock() {
2772 for e in g.iter_mut() {
2773 if e.str.as_deref() == Some(curexpl_str.as_str()) {
2775 e.count += curexpl_count; e.fcount += curexpl_fcount; if always {
2779 e.always = 1;
2781 nmessages.fetch_add(1, Ordering::Relaxed); newmatches.store(1, Ordering::Relaxed); let mc = mgroup.get_or_init(|| Mutex::new(None));
2784 if let Ok(mut mg) = mc.lock() {
2785 if let Some(grp) = mg.as_mut() {
2786 grp.new_ = 1;
2787 }
2788 }
2789 }
2790 return; }
2792 }
2793 if let Some(e) = curexpl_snap {
2794 g.push(e);
2796 }
2797 }
2798 newmatches.store(1, Ordering::Relaxed); if always {
2800 let mc = mgroup.get_or_init(|| Mutex::new(None));
2802 if let Ok(mut mg) = mc.lock() {
2803 if let Some(grp) = mg.as_mut() {
2804 grp.new_ = 1;
2805 }
2806 }
2807 nmessages.fetch_add(1, Ordering::Relaxed); }
2809}
2810
2811pub fn matchcmp(a: &Cmatch, b: &Cmatch) -> std::cmp::Ordering {
2818 let order = MATCHORDER.load(Ordering::Relaxed);
2820 let sortdir = if (order & CGF_REVSORT) != 0 { -1 } else { 1 }; let cmp = (b.disp.is_some() as i32) - (a.disp.is_some() as i32); let (as_, bs) = if (order & CGF_MATSORT) != 0 || (cmp == 0 && a.disp.is_none()) {
2824 (
2825 a.str.clone().unwrap_or_default(), b.str.clone().unwrap_or_default(),
2827 ) } else {
2829 if cmp != 0 {
2830 let raw = (cmp as i32) * sortdir;
2832 return if raw < 0 {
2833 std::cmp::Ordering::Less
2834 }
2835 else if raw > 0 {
2837 std::cmp::Ordering::Greater
2838 } else {
2839 std::cmp::Ordering::Equal
2840 };
2841 }
2842 let displine_cmp = (b.flags & CMF_DISPLINE) - (a.flags & CMF_DISPLINE); if displine_cmp != 0 {
2844 let raw = displine_cmp * sortdir;
2846 return if raw < 0 {
2847 std::cmp::Ordering::Less
2848 } else if raw > 0 {
2849 std::cmp::Ordering::Greater
2850 } else {
2851 std::cmp::Ordering::Equal
2852 };
2853 }
2854 (
2855 a.disp.clone().unwrap_or_default(), b.disp.clone().unwrap_or_default(),
2857 ) };
2859 let raw = sortdir
2860 * if as_ == bs {
2861 0
2862 } else if as_ < bs {
2863 -1
2864 } else {
2865 1
2866 };
2867 if raw < 0 {
2868 std::cmp::Ordering::Less
2869 }
2870 else if raw > 0 {
2872 std::cmp::Ordering::Greater
2873 } else {
2874 std::cmp::Ordering::Equal
2875 }
2876}
2877
2878pub fn matcheq(a: &Cmatch, b: &Cmatch) -> bool {
2881 matchstreq(a.ipre.as_ref(), b.ipre.as_ref()) && matchstreq(a.pre.as_ref(), b.pre.as_ref()) && matchstreq(a.ppre.as_ref(), b.ppre.as_ref()) && matchstreq(a.psuf.as_ref(), b.psuf.as_ref()) && matchstreq(a.suf.as_ref(), b.suf.as_ref()) && matchstreq(a.str.as_ref(), b.str.as_ref()) }
2889
2890pub fn makearray(mut rp: Vec<Cmatch>, flags: i32) -> (Vec<Cmatch>, i32, i32, i32) {
2903 let mut n: i32 = rp.len() as i32; let mut nl: i32 = 0; let mut ll: i32 = 0; if n > 0 {
2909 if (flags & CGF_NOSORT) == 0 {
2911 MATCHORDER.store(flags, Ordering::Relaxed); rp.sort_by(matchcmp); if (flags & CGF_UNIQCON) == 0 {
2917 let mut cp = 0usize; let mut ap = 0usize;
2921 while ap < rp.len() {
2922 if ap != cp {
2924 rp.swap(ap, cp);
2925 } cp += 1;
2927 let mut bp = ap;
2928 while bp + 1 < rp.len() && matcheq(&rp[ap], &rp[bp + 1]) {
2929 bp += 1;
2930 n -= 1; }
2932 let mut dup = 0i32; while bp + 1 < rp.len()
2934 && rp[ap].disp.is_none()
2935 && rp[bp + 1].disp.is_none() && rp[ap].str == rp[bp + 1].str
2937 {
2938 rp[bp + 1].flags |= CMF_MULT; dup = 1; bp += 1;
2941 }
2942 if dup != 0 {
2943 rp[ap].flags |= CMF_FMULT; }
2946 ap = bp + 1; }
2948 rp.truncate(cp); }
2950 for m in rp.iter() {
2951 if m.disp.is_some() && (m.flags & CMF_DISPLINE) != 0 {
2953 ll += 1;
2955 }
2956 if (m.flags & (CMF_NOLIST | CMF_MULT)) != 0 {
2957 nl += 1;
2959 }
2960 }
2961 } else {
2962 if (flags & CGF_UNIQALL) == 0 && (flags & CGF_UNIQCON) == 0 {
2964 MATCHORDER.store(flags, Ordering::Relaxed); let mut sp: Vec<Cmatch> = rp.clone(); sp.sort_by(matchcmp); let mut del = false; for w in sp.windows(2) {
2972 if matcheq(&w[0], &w[1]) {
2974 for m in rp.iter_mut() {
2976 if matcheq(m, &w[1]) {
2977 m.flags = CMF_DELETE; del = true; break;
2980 }
2981 }
2982 } else if w[0].disp.is_none() {
2983 if w[1].disp.is_none() && w[0].str == w[1].str {
2984 for m in rp.iter_mut() {
2986 if matcheq(m, &w[1]) {
2987 m.flags |= CMF_MULT; break;
2989 }
2990 }
2991 for m in rp.iter_mut() {
2992 if matcheq(m, &w[0]) {
2993 m.flags |= CMF_FMULT; break;
2995 }
2996 }
2997 }
2998 }
2999 }
3000 if del {
3001 rp.retain(|m| (m.flags & CMF_DELETE) == 0); n = rp.len() as i32;
3004 }
3005 } else if (flags & CGF_UNIQCON) == 0 {
3006 let mut cp = 0usize;
3008 let mut ap = 0usize;
3009 while ap < rp.len() {
3010 if ap != cp {
3012 rp.swap(ap, cp);
3013 }
3014 cp += 1;
3015 let mut bp = ap;
3016 while bp + 1 < rp.len() && matcheq(&rp[ap], &rp[bp + 1]) {
3017 bp += 1;
3018 n -= 1; }
3020 let mut dup = 0i32;
3021 while bp + 1 < rp.len()
3022 && rp[ap].disp.is_none()
3023 && rp[bp + 1].disp.is_none()
3024 && rp[ap].str == rp[bp + 1].str
3025 {
3026 rp[bp + 1].flags |= CMF_MULT; dup = 1; bp += 1;
3029 }
3030 if dup != 0 {
3031 rp[ap].flags |= CMF_FMULT; }
3033 ap = bp + 1;
3034 }
3035 rp.truncate(cp); }
3037 for m in rp.iter() {
3038 if m.disp.is_some() && (m.flags & CMF_DISPLINE) != 0 {
3040 ll += 1;
3042 }
3043 if (m.flags & (CMF_NOLIST | CMF_MULT)) != 0 {
3044 nl += 1;
3046 }
3047 }
3048 }
3049 }
3050 (rp, n, nl, ll) }
3052
3053pub fn dupmatch(m: &Cmatch, nbeg: i32, nend: i32) -> Cmatch {
3061 let mut r = Cmatch::default(); r.str = m.str.clone(); r.orig = m.orig.clone(); r.ipre = m.ipre.clone(); r.ripre = m.ripre.clone(); r.isuf = m.isuf.clone(); r.ppre = m.ppre.clone(); r.psuf = m.psuf.clone(); r.prpre = m.prpre.clone(); r.pre = m.pre.clone(); r.suf = m.suf.clone(); r.flags = m.flags; if !m.brpl.is_empty() {
3075 let take = (nbeg as usize).min(m.brpl.len()); r.brpl = m.brpl[..take].to_vec(); } else {
3079 r.brpl = Vec::new(); }
3081 if !m.brsl.is_empty() {
3082 let take = (nend as usize).min(m.brsl.len()); r.brsl = m.brsl[..take].to_vec(); } else {
3086 r.brsl = Vec::new(); }
3088 r.rems = m.rems.clone(); r.remf = m.remf.clone(); r.autoq = m.autoq.clone(); r.qipl = m.qipl; r.qisl = m.qisl; r.disp = m.disp.clone(); r.mode = m.mode; r.modec = m.modec; r.fmode = m.fmode; r.fmodec = m.fmodec; r }
3100
3101pub fn permmatches(last: i32) -> i32 {
3105 let ofi = PERMMATCHES_FI.load(Ordering::Relaxed); let pmatches_set = pmatches
3110 .get_or_init(|| Mutex::new(Vec::new()))
3111 .lock()
3112 .map(|g| !g.is_empty())
3113 .unwrap_or(false);
3114 if pmatches_set && newmatches.load(Ordering::Relaxed) == 0 {
3115 if last != 0 && PERMMATCHES_FI.load(Ordering::Relaxed) != 0 {
3117 let famref = fainfo
3120 .get_or_init(|| Mutex::new(None))
3121 .lock()
3122 .ok()
3123 .and_then(|g| g.clone());
3124 if let Ok(mut a) = ainfo.get_or_init(|| Mutex::new(None)).lock() {
3125 *a = famref;
3126 }
3127 }
3128 return PERMMATCHES_FI.load(Ordering::Relaxed); }
3130 newmatches.store(0, Ordering::Relaxed); PERMMATCHES_FI.store(0, Ordering::Relaxed); {
3134 if let Ok(mut g) = pmatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
3136 g.clear();
3137 }
3138 if let Ok(mut g) = lmatches.get_or_init(|| Mutex::new(None)).lock() {
3139 *g = None;
3140 }
3141 }
3142 nmatches.store(0, Ordering::Relaxed); smatches.store(0, Ordering::Relaxed); diffmatches.store(0, Ordering::Relaxed); let ainfo_count = ainfo
3148 .get_or_init(|| Mutex::new(None))
3149 .lock()
3150 .ok()
3151 .and_then(|g| g.as_ref().map(|a| a.count))
3152 .unwrap_or(0);
3153 if ainfo_count == 0 {
3154 if last != 0 {
3156 let famref = fainfo
3158 .get_or_init(|| Mutex::new(None))
3159 .lock()
3160 .ok()
3161 .and_then(|g| g.clone());
3162 if let Ok(mut a) = ainfo.get_or_init(|| Mutex::new(None)).lock() {
3163 *a = famref;
3164 }
3165 }
3166 PERMMATCHES_FI.store(1, Ordering::Relaxed); }
3168
3169 let nbeg = NBRBEG.load(Ordering::Relaxed);
3170 let nend = NBREND.load(Ordering::Relaxed);
3171
3172 let mut gn: i32 = 1; let mut mn: i32 = 1; let fi = PERMMATCHES_FI.load(Ordering::Relaxed);
3175
3176 let groups_snapshot: Vec<Cmgroup> = {
3177 amatches
3178 .get_or_init(|| Mutex::new(Vec::new()))
3179 .lock()
3180 .ok()
3181 .map(|g| g.clone())
3182 .unwrap_or_default()
3183 };
3184 let mut new_pmatches: Vec<Cmgroup> = Vec::with_capacity(groups_snapshot.len());
3185
3186 for g_orig in groups_snapshot.into_iter() {
3187 let mut g = g_orig; let must_rebuild = fi != ofi || g.perm.is_none() || g.new_ != 0; if must_rebuild {
3191 let src_list = if fi != 0 {
3193 g.lfmatches.clone()
3194 }
3195 else {
3197 g.lmatches.clone()
3198 }; let (arr, nn, nl, ll) = makearray(src_list, g.flags); g.mcount = nn; g.lcount = nn - nl; if g.lcount < 0 {
3204 g.lcount = 0;
3205 } g.llcount = ll; if !g.ylist.is_empty() {
3208 g.lcount = g.ylist.len() as i32; smatches.store(2, Ordering::Relaxed); }
3212 let mut exps = g.lexpls.clone(); g.ecount = exps.len() as i32;
3215 g.ccount = 0; nmatches.fetch_add(g.mcount, Ordering::Relaxed); smatches.fetch_add(g.lcount, Ordering::Relaxed); if g.mcount > 1 {
3220 diffmatches.store(1, Ordering::Relaxed); }
3223
3224 let mut n_grp = Cmgroup::default();
3226 g.perm = None; n_grp.num = gn;
3232 gn += 1; n_grp.flags = g.flags; n_grp.mcount = g.mcount; n_grp.matches = arr
3236 .iter() .map(|m| dupmatch(m, nbeg, nend))
3238 .collect();
3239 n_grp.name = g.name.clone(); n_grp.lcount = g.lcount; n_grp.llcount = g.llcount; if !g.ylist.is_empty() {
3243 n_grp.ylist = g.ylist.clone(); } else {
3246 n_grp.ylist = Vec::new(); }
3248 if g.ecount != 0 {
3249 n_grp.expls = exps
3253 .drain(..)
3254 .map(|o| Cexpl {
3255 count: if fi != 0 { o.fcount } else { o.count }, always: o.always, fcount: 0, str: o.str.clone(), })
3261 .collect();
3262 n_grp.ecount = g.ecount;
3263 } else {
3264 n_grp.expls = Vec::new(); }
3266 n_grp.widths = Vec::new(); g.matches = arr; g.perm = Some(Box::new(n_grp.clone())); new_pmatches.push(n_grp); } else {
3272 nmatches.fetch_add(g.mcount, Ordering::Relaxed); smatches.fetch_add(g.lcount, Ordering::Relaxed); if g.mcount > 1 {
3276 diffmatches.store(1, Ordering::Relaxed); }
3278 g.num = gn;
3279 gn += 1; if let Some(p) = g.perm.as_deref() {
3281 new_pmatches.push(p.clone()); }
3283 }
3284 g.new_ = 0; }
3286
3287 let mut first_first: Option<Cmatch> = None;
3289 for g_pm in new_pmatches.iter_mut() {
3290 g_pm.nbrbeg = nbeg; g_pm.nbrend = nend; let mut rn = 1i32; for m in g_pm.matches.iter_mut() {
3294 m.rnum = rn;
3295 rn += 1; m.gnum = mn;
3297 mn += 1; }
3299 if diffmatches.load(Ordering::Relaxed) == 0 && !g_pm.matches.is_empty() {
3300 match first_first.as_ref() {
3301 Some(p0) => {
3303 if !matcheq(&g_pm.matches[0], p0) {
3304 diffmatches.store(1, Ordering::Relaxed); }
3306 }
3307 None => first_first = Some(g_pm.matches[0].clone()), }
3309 }
3310 }
3311
3312 if let Ok(mut g) = pmatches.get_or_init(|| Mutex::new(Vec::new())).lock() {
3313 *g = new_pmatches;
3314 }
3315
3316 hasperm.store(1, Ordering::Relaxed); permmnum.store(mn - 1, Ordering::Relaxed); permgnum.store(gn - 1, Ordering::Relaxed); if let Ok(mut ld) = listdat
3320 .get_or_init(|| Mutex::new(Default::default()))
3321 .lock()
3322 {
3323 ld.valid = 0; }
3325
3326 fi }
3328
3329pub fn freematch(m: Cmatch, _nbeg: i32, _nend: i32) {
3342 drop(m); }
3347
3348pub fn freematches(g: Vec<Cmgroup>, cm: i32) {
3356 drop(g);
3358 if cm != 0 {
3359 if let Ok(mut g) = MINFO.get_or_init(|| Mutex::new(Menuinfo::default())).lock() {
3361 g.cur = None; }
3363 }
3364}
3365
3366pub static LASTEND: AtomicI32 = AtomicI32::new(0); pub static WB: AtomicI32 = AtomicI32::new(0); pub static WE: AtomicI32 = AtomicI32::new(0); pub static ZLEMETACS: AtomicI32 = AtomicI32::new(0); pub static ZLEMETALL: AtomicI32 = AtomicI32::new(0); pub static ADDEDX: AtomicI32 = AtomicI32::new(0); pub static ZLEMETALINE: OnceLock<Mutex<String>> = OnceLock::new(); pub static ZLELINE: OnceLock<Mutex<String>> = OnceLock::new(); pub static ZLECS: AtomicI32 = AtomicI32::new(0); pub static ZLELL: AtomicI32 = AtomicI32::new(0); pub static INWHAT: AtomicI32 = AtomicI32::new(0); pub static ZMULT: AtomicI32 = AtomicI32::new(1); pub static compfunc: OnceLock<Mutex<Option<String>>> = OnceLock::new(); pub static comppatmatch: OnceLock<Mutex<Option<String>>> = OnceLock::new();
3421pub static compqstack: OnceLock<Mutex<String>> = OnceLock::new();
3424pub static useexact: AtomicI32 = AtomicI32::new(0); pub static useline: AtomicI32 = AtomicI32::new(0); pub static uselist: AtomicI32 = AtomicI32::new(0); pub static forcelist: AtomicI32 = AtomicI32::new(0); pub static startauto: AtomicI32 = AtomicI32::new(0); pub static iforcemenu: AtomicI32 = AtomicI32::new(0); pub static dolastprompt: AtomicI32 = AtomicI32::new(0); pub static oldlist: AtomicI32 = AtomicI32::new(0); pub static oldins: AtomicI32 = AtomicI32::new(0); pub static origlpre: AtomicI32 = AtomicI32::new(0); pub static origlsuf: AtomicI32 = AtomicI32::new(0); pub static lenchanged: AtomicI32 = AtomicI32::new(0); pub static movetoend: AtomicI32 = AtomicI32::new(0); pub static insmnum: AtomicI32 = AtomicI32::new(0); pub static insspace: AtomicI32 = AtomicI32::new(0); pub static menuacc: AtomicI32 = AtomicI32::new(0); pub static hasunqu: AtomicI32 = AtomicI32::new(0); pub static useqbr: AtomicI32 = AtomicI32::new(0); pub static brpcs: AtomicI32 = AtomicI32::new(0); pub static brscs: AtomicI32 = AtomicI32::new(0); pub static ispar: AtomicI32 = AtomicI32::new(0); pub static linwhat: AtomicI32 = AtomicI32::new(0); pub static parpre: OnceLock<Mutex<String>> = OnceLock::new(); pub static parflags: AtomicI32 = AtomicI32::new(0); pub static mflags: AtomicI32 = AtomicI32::new(0); pub static parq: AtomicI32 = AtomicI32::new(0); pub static eparq: AtomicI32 = AtomicI32::new(0); pub static ipre: OnceLock<Mutex<String>> = OnceLock::new(); pub static ripre: OnceLock<Mutex<String>> = OnceLock::new(); pub static isuf: OnceLock<Mutex<String>> = OnceLock::new(); pub static matches: OnceLock<Mutex<Vec<Cmatch>>> = OnceLock::new(); pub static fmatches: OnceLock<Mutex<Vec<Cmatch>>> = OnceLock::new(); pub static amatches: OnceLock<Mutex<Vec<Cmgroup>>> = OnceLock::new(); pub static pmatches: OnceLock<Mutex<Vec<Cmgroup>>> = OnceLock::new(); pub static lastmatches: OnceLock<Mutex<Vec<Cmgroup>>> = OnceLock::new(); pub static lmatches: OnceLock<Mutex<Option<Cmgroup>>> = OnceLock::new(); pub static lastlmatches: OnceLock<Mutex<Option<Cmgroup>>> = OnceLock::new(); pub static hasoldlist: AtomicI32 = AtomicI32::new(0); pub static hasperm: AtomicI32 = AtomicI32::new(0); pub static hasallmatch: AtomicI32 = AtomicI32::new(0); pub static newmatches: AtomicI32 = AtomicI32::new(0); pub static permmnum: AtomicI32 = AtomicI32::new(0); pub static permgnum: AtomicI32 = AtomicI32::new(0); pub static lastpermmnum: AtomicI32 = AtomicI32::new(0); pub static lastpermgnum: AtomicI32 = AtomicI32::new(0); pub static nmatches: AtomicI32 = AtomicI32::new(0); pub static smatches: AtomicI32 = AtomicI32::new(0); pub static diffmatches: AtomicI32 = AtomicI32::new(0); pub static nmessages: AtomicI32 = AtomicI32::new(0); pub static onlyexpl: AtomicI32 = AtomicI32::new(0); pub static listdat: OnceLock<Mutex<crate::ported::zle::comp_h::Cldata>> = OnceLock::new(); pub static ispattern: AtomicI32 = AtomicI32::new(0); pub static haspattern: AtomicI32 = AtomicI32::new(0); pub static hasmatched: AtomicI32 = AtomicI32::new(0); pub static hasunmatched: AtomicI32 = AtomicI32::new(0); pub static mgroup: OnceLock<Mutex<Option<Cmgroup>>> = OnceLock::new(); pub static mnum: AtomicI32 = AtomicI32::new(0); pub static unambig_mnum: AtomicI32 = AtomicI32::new(0); pub static maxmlen: AtomicI32 = AtomicI32::new(0); pub static minmlen: AtomicI32 = AtomicI32::new(0); pub static expls: OnceLock<Mutex<Vec<Cexpl>>> = OnceLock::new(); pub static curexpl: OnceLock<Mutex<Option<Cexpl>>> = OnceLock::new(); pub static matchers: OnceLock<Mutex<Vec<Box<crate::ported::zle::comp_h::Cmatcher>>>> =
3591 OnceLock::new(); pub static ainfo: OnceLock<Mutex<Option<Aminfo>>> = OnceLock::new(); pub static fainfo: OnceLock<Mutex<Option<Aminfo>>> = OnceLock::new(); pub static allccs: OnceLock<Mutex<Vec<String>>> = OnceLock::new(); pub static fromcomp: AtomicI32 = AtomicI32::new(0); pub static lastend: AtomicI32 = AtomicI32::new(0); pub static BRBEG: OnceLock<Mutex<Option<Box<Brinfo>>>> = OnceLock::new(); pub static BREND: OnceLock<Mutex<Option<Box<Brinfo>>>> = OnceLock::new(); pub static OLDMENUCMP: AtomicI32 = AtomicI32::new(0); pub static PARWB: AtomicI32 = AtomicI32::new(0); pub static PARWE: AtomicI32 = AtomicI32::new(0); pub static PAROFFS: AtomicI32 = AtomicI32::new(0); pub static MATCHORDER: AtomicI32 = AtomicI32::new(0); pub static LASTCHAR: AtomicI32 = AtomicI32::new(0); pub static MINFO: OnceLock<Mutex<Menuinfo>> = OnceLock::new(); #[inline]
3645fn matchstreq(a: Option<&String>, b: Option<&String>) -> bool {
3646 match (a, b) {
3648 (None, None) => true,
3649 (Some(x), Some(y)) => x == y,
3650 _ => false,
3651 }
3652}
3653
3654fn do_single_first_match() {
3657 let groups = amatches
3659 .get_or_init(|| Mutex::new(Vec::new()))
3660 .lock()
3661 .ok()
3662 .map(|g| g.clone())
3663 .unwrap_or_default();
3664 let first = groups
3665 .into_iter()
3666 .find(|g| g.mcount > 0)
3667 .and_then(|g| g.matches.first().cloned());
3668 if let Some(m) = first {
3669 if let Ok(mut g) = MINFO.get_or_init(|| Mutex::new(Menuinfo::default())).lock() {
3670 g.cur = None;
3671 } if let Ok(mut g) = MINFO.get_or_init(|| Mutex::new(Menuinfo::default())).lock() {
3673 g.asked = 0;
3674 } if let Ok(mut g) = MINFO.get_or_init(|| Mutex::new(Menuinfo::default())).lock() {
3679 g.cur = Some(Box::new(m));
3680 }
3681 }
3682}
3683
3684fn goto_compend(ret: i32) -> i32 {
3686 if let Ok(mut g) = matchers.get_or_init(|| Mutex::new(Vec::new())).lock() {
3688 g.clear(); }
3690 let line_len = ZLEMETALL.load(Ordering::Relaxed); if ZLEMETACS.load(Ordering::Relaxed) > line_len {
3692 ZLEMETACS.store(line_len, Ordering::Relaxed); }
3695 ret }
3697
3698pub const IN_NOTHING_LW: i32 = 0; pub const IN_CMD_LW: i32 = 1; pub const IN_COND_LW: i32 = 2; pub const IN_MATH_LW: i32 = 3; pub const IN_PAR_LW: i32 = 4; pub const IN_ENV_LW: i32 = 5; fn unmetafy_line() {
3754 let meta = ZLEMETALINE
3756 .get_or_init(|| Mutex::new(String::new()))
3757 .lock()
3758 .map(|g| g.clone())
3759 .unwrap_or_default();
3760 let zlemetacs = ZLEMETACS.load(Ordering::Relaxed) as i32;
3761 let mut out_ll: i32 = 0;
3762 let mut out_cs: i32 = 0;
3763 let line = crate::ported::zle::zle_utils::stringaszleline(
3765 &meta,
3766 zlemetacs,
3767 Some(&mut out_ll),
3768 None,
3769 Some(&mut out_cs),
3770 );
3771 if let Ok(mut g) = ZLELINE.get_or_init(|| Mutex::new(String::new())).lock() {
3772 *g = line.iter().collect();
3773 }
3774 ZLELL.store(out_ll, Ordering::Relaxed);
3775 ZLECS.store(out_cs, Ordering::Relaxed);
3776 if let Some(m) = ZLEMETALINE.get() {
3779 if let Ok(mut g) = m.lock() {
3780 g.clear();
3781 }
3782 }
3783 ZLEMETALL.store(0, Ordering::Relaxed);
3784 }
3787
3788fn metafy_line() {
3800 let raw_vec: Vec<char> = ZLELINE
3802 .get_or_init(|| Mutex::new(String::new()))
3803 .lock()
3804 .map(|g| g.chars().collect())
3805 .unwrap_or_default();
3806 let zlell = raw_vec.len();
3807 let zlecs = ZLECS.load(Ordering::Relaxed);
3808 let mut out_ll: i32 = 0;
3809 let mut out_cs: i32 = 0;
3810 let meta = crate::ported::zle::zle_utils::zlelineasstring(
3812 &raw_vec,
3813 zlell,
3814 zlecs,
3815 Some(&mut out_ll),
3816 Some(&mut out_cs),
3817 0,
3818 );
3819 if let Ok(mut g) = ZLEMETALINE.get_or_init(|| Mutex::new(String::new())).lock() {
3820 *g = meta;
3821 }
3822 ZLEMETALL.store(out_ll, Ordering::Relaxed);
3823 ZLEMETACS.store(out_cs, Ordering::Relaxed);
3824 if let Ok(mut g) = ZLELINE.get().unwrap().lock() {
3829 g.clear();
3830 }
3831 ZLELL.store(0, Ordering::Relaxed);
3832}
3833
3834fn opt_isset(name: &str) -> i32 {
3835 if crate::ported::options::opt_state_get(name).unwrap_or(false) {
3837 1
3838 } else {
3839 0
3840 }
3841}
3842fn env_iparam(name: &str) -> i32 {
3846 crate::ported::params::getiparam(name) as i32
3848}
3849fn lastprebr_set(s: &str) {
3850 if let Ok(mut g) = LASTPREBR.get_or_init(|| Mutex::new(String::new())).lock() {
3852 *g = s.to_string();
3853 }
3854}
3855fn lastpostbr_set(s: &str) {
3856 if let Ok(mut g) = LASTPOSTBR.get_or_init(|| Mutex::new(String::new())).lock() {
3858 *g = s.to_string();
3859 }
3860}
3861
3862fn compcontext_for(_s: &str) -> String {
3865 let ip = ispar.load(Ordering::Relaxed); if ip == 2 {
3868 return "brace_parameter".into();
3869 } if ip == 1 {
3871 return "parameter".into();
3872 } let lw = linwhat.load(Ordering::Relaxed); match lw {
3875 x if x == IN_PAR_LW => "assign_parameter".into(), x if x == IN_MATH_LW => "math".into(), x if x == IN_COND_LW => "condition".into(), x if x == IN_ENV_LW => "value".into(), _ => "command".into(), }
3882}
3883
3884pub static OFFS: AtomicI32 = AtomicI32::new(0); pub static freecl: OnceLock<Mutex<Option<i32>>> = OnceLock::new(); pub fn shfunc_call(name: &str) -> i32 {
3898 if crate::ported::utils::getshfunc(name).is_none() {
3900 return 1; }
3903 crate::ported::exec::dispatch_function_call(name, &[])
3908 .unwrap_or_else(|| crate::ported::builtin::LASTVAL.load(Ordering::Relaxed))
3909}
3910pub fn set_compstate_str(key: &str, val: &str) {
3920 let pname = format!("compstate[{}]", key);
3923 let _ = setsparam(&pname, val);
3924
3925 if let Ok(mut tab) = paramtab_hashed_storage().lock() {
3930 tab.entry("compstate".to_string())
3931 .or_default()
3932 .insert(key.to_string(), val.to_string());
3933 }
3934}
3935
3936pub fn get_compstate_str(key: &str) -> Option<String> {
3943 if let Ok(tab) = paramtab_hashed_storage().lock() {
3944 if let Some(hash) = tab.get("compstate") {
3945 if let Some(v) = hash.get(key) {
3946 return Some(v.clone());
3947 }
3948 }
3949 }
3950 let pname = format!("compstate[{}]", key);
3952 getsparam(&pname)
3953}
3954
3955#[inline]
3957fn prev_char_index(bytes: &[u8], pos: usize) -> usize {
3958 if pos == 0 {
3960 return 0;
3961 }
3962 let mut i = pos - 1;
3963 while i > 0 && (bytes[i] & 0xC0) == 0x80 {
3964 i -= 1;
3965 }
3966 i
3967}
3968
3969#[inline]
3970fn char_at(bytes: &[u8], pos: usize) -> char {
3971 if pos >= bytes.len() {
3973 return '\0';
3974 }
3975 let s = match std::str::from_utf8(&bytes[pos..]) {
3976 Ok(s) => s,
3977 Err(_) => return '\0',
3978 };
3979 s.chars().next().unwrap_or('\0')
3980}
3981
3982static LEXSAVE_DEPTH: AtomicI32 = AtomicI32::new(0); fn skip_token_parens(bytes: &[u8], start: usize, open: char, close: char) -> Option<usize> {
3991 let mut depth: i32 = 0;
3992 let mut i = start;
3993 while i < bytes.len() {
3994 let c = char_at(bytes, i);
3995 if c == open {
3996 depth += 1;
3997 } else if c == close {
3998 depth -= 1;
3999 if depth == 0 {
4000 return Some(i + c.len_utf8());
4001 }
4002 }
4003 i += c.len_utf8();
4004 }
4005 if depth == 0 {
4006 Some(i)
4007 } else {
4008 None
4009 }
4010}
4011
4012fn walk_namespace(bytes: &[u8]) -> usize {
4015 let s = match std::str::from_utf8(bytes) {
4017 Ok(s) => s,
4018 Err(_) => return 0,
4019 };
4020 let mut len = 0usize;
4021 for c in s.chars() {
4022 if c.is_alphanumeric() || c == '_' {
4023 len += c.len_utf8();
4024 } else {
4025 break;
4026 }
4027 }
4028 len
4029}
4030
4031fn strip_tokens(s: &str) -> String {
4035 crate::lex::untokenize(s).to_string()
4037}
4038
4039fn compfunc_active() -> bool {
4041 compfunc
4042 .get_or_init(|| Mutex::new(None))
4043 .lock()
4044 .ok()
4045 .and_then(|g| g.clone())
4046 .map(|s| !s.is_empty())
4047 .unwrap_or(false)
4048}
4049
4050fn lexsave() -> usize {
4055 crate::ported::context::zcontext_save();
4057 (LEXSAVE_DEPTH.fetch_add(1, Ordering::SeqCst) + 1) as usize
4058}
4059
4060fn lexrestore(_token: usize) {
4064 let parts = ZCONTEXT_HIST | ZCONTEXT_LEX | ZCONTEXT_PARSE;
4066 zcontext_restore_partial(parts);
4067 LEXSAVE_DEPTH.fetch_sub(1, Ordering::SeqCst);
4068}
4069
4070fn compquote_first() -> Option<char> {
4073 COMPQUOTE
4075 .get_or_init(|| Mutex::new(String::new()))
4076 .lock()
4077 .ok()
4078 .and_then(|g| g.chars().next())
4079}
4080fn instring_set(v: i32) {
4081 INSTRING.store(v, Ordering::Relaxed);
4083}
4084fn inbackt_set(v: i32) {
4085 INBACKT.store(v, Ordering::Relaxed);
4087}
4088fn autoq_set(s: &str) {
4089 if let Ok(mut g) = AUTOQ.get_or_init(|| Mutex::new(String::new())).lock() {
4091 *g = s.to_string();
4092 }
4093}
4094
4095pub static bmatchers: OnceLock<Mutex<Option<Box<Cmlist>>>> = OnceLock::new(); pub static mstack: OnceLock<Mutex<Option<Box<Cmlist>>>> = OnceLock::new(); fn cline_matched_compcore(line: Option<&str>) {
4114 let Some(s) = line else {
4116 return;
4117 };
4118 if s.is_empty() {
4119 return;
4120 }
4121 let mut head = Some(Box::new(Cline {
4122 line: Some(s.to_string()),
4123 llen: s.len() as i32,
4124 ..Default::default()
4125 }));
4126 cline_matched(&mut head);
4127}
4128fn qisuf_get() -> String {
4131 getsparam("qisuf").unwrap_or_default()
4133}
4134fn qipre_get() -> String {
4135 getsparam("qipre").unwrap_or_default()
4137}
4138
4139fn movefd(fd: i32) -> i32 {
4142 crate::ported::utils::movefd(fd)
4144}
4145
4146fn redup(new: i32) {
4151 crate::ported::utils::redup(new, -1);
4153}
4154
4155pub static HOOK_FNS: OnceLock<Mutex<std::collections::HashMap<String, Vec<String>>>> =
4158 OnceLock::new(); fn errflag_get() -> bool {
4163 crate::ported::utils::errflag.load(Ordering::Relaxed) != 0 }
4165
4166fn runhookdef_compcore(hook: &str) -> i32 {
4172 let h = gethookdef(hook);
4174 if h.is_null() {
4175 return 0;
4176 }
4177 runhookdef(h, std::ptr::null_mut())
4178}
4179
4180fn runhookdef_compctlmake(
4185 dat: &mut Ccmakedat,
4187) {
4188 let s = dat.str.clone().unwrap_or_default();
4190 let _ = crate::ported::zle::compctl::makecomplistctl(dat.lst);
4191 let _ = s;
4192}
4193
4194static PERMMATCHES_FI: AtomicI32 = AtomicI32::new(0); pub fn makearray_strings(mut rp: Vec<String>, flags: i32) -> (Vec<String>, i32) {
4205 let mut n: i32 = rp.len() as i32;
4207 if flags != 0 && n > 0 {
4208 let numeric = isset(NUMERICGLOBSORT); let mut sf = SORTIT_IGNORING_BACKSLASHES as u32;
4211 if numeric {
4212 sf |= SORTIT_NUMERICALLY as u32;
4213 }
4214 crate::ported::sort::strmetasort(&mut rp, sf, None); let mut cp = 0usize;
4218 let mut ap = 0usize;
4219 while ap < rp.len() {
4220 if ap != cp {
4221 rp.swap(ap, cp);
4222 }
4223 cp += 1;
4224 let mut bp = ap;
4225 while bp + 1 < rp.len() && rp[ap] == rp[bp + 1] {
4226 bp += 1;
4228 n -= 1;
4229 }
4230 ap = bp + 1; }
4232 rp.truncate(cp); }
4234 (rp, n)
4235}
4236
4237#[cfg(test)]
4238mod tests {
4239 use super::*;
4240
4241 #[test]
4242 fn rembslash_basic() {
4243 let _g = crate::test_util::global_state_lock();
4244 let _g = zle_test_setup();
4245 assert_eq!(rembslash("hello\\ world"), "hello world");
4246 assert_eq!(rembslash("no\\\\slash"), "no\\slash");
4247 assert_eq!(rembslash("plain"), "plain");
4248 }
4249
4250 #[test]
4251 fn comp_quoting_string_table() {
4252 let _g = crate::test_util::global_state_lock();
4253 let _g = zle_test_setup();
4254 assert_eq!(comp_quoting_string(QT_SINGLE), "'");
4255 assert_eq!(comp_quoting_string(QT_DOUBLE), "\"");
4256 assert_eq!(comp_quoting_string(QT_DOLLARS), "$'");
4257 assert_eq!(comp_quoting_string(0), "\\");
4258 assert_eq!(comp_quoting_string(QT_BACKSLASH), "\\");
4259 }
4260
4261 #[test]
4262 fn matcheq_equal_strings() {
4263 let _g = crate::test_util::global_state_lock();
4264 let _g = zle_test_setup();
4265 let mut a = Cmatch::default();
4266 a.str = Some("foo".into());
4267 let mut b = Cmatch::default();
4268 b.str = Some("foo".into());
4269 assert!(matcheq(&a, &b));
4270 }
4271
4272 #[test]
4273 fn matcheq_different_strings() {
4274 let _g = crate::test_util::global_state_lock();
4275 let _g = zle_test_setup();
4276 let mut a = Cmatch::default();
4277 a.str = Some("foo".into());
4278 let mut b = Cmatch::default();
4279 b.str = Some("bar".into());
4280 assert!(!matcheq(&a, &b));
4281 }
4282
4283 #[test]
4284 fn matcheq_one_side_none() {
4285 let _g = crate::test_util::global_state_lock();
4286 let _g = zle_test_setup();
4287 let mut a = Cmatch::default();
4288 a.pre = Some("p".into());
4289 let b = Cmatch::default();
4290 assert!(!matcheq(&a, &b));
4291 }
4292
4293 #[test]
4294 fn get_user_var_reads_array_from_paramtab() {
4295 let _g = crate::test_util::global_state_lock();
4296 let _g = zle_test_setup();
4299 setaparam("__test_arr", vec!["a".into(), "bb".into(), "ccc".into()]);
4300 let got = get_user_var(Some("__test_arr"));
4301 assert_eq!(got, Some(vec!["a".into(), "bb".into(), "ccc".into()]));
4302 setaparam("__test_arr", vec![]);
4304 }
4305
4306 #[test]
4307 fn get_user_var_reads_scalar_as_single_element_array() {
4308 let _g = crate::test_util::global_state_lock();
4309 let _g = zle_test_setup();
4311 setsparam("__test_scalar", "hello");
4312 let got = get_user_var(Some("__test_scalar"));
4313 assert_eq!(got, Some(vec!["hello".to_string()]));
4314 setsparam("__test_scalar", "");
4315 }
4316
4317 #[test]
4318 fn get_user_var_paren_list_splits_on_separators() {
4319 let _g = crate::test_util::global_state_lock();
4320 let _g = zle_test_setup();
4322 let got = get_user_var(Some("(one two three)"));
4323 assert_eq!(got, Some(vec!["one".into(), "two".into(), "three".into()]));
4324 }
4325
4326 #[test]
4327 fn get_user_var_none_for_missing() {
4328 let _g = crate::test_util::global_state_lock();
4329 let _g = zle_test_setup();
4331 let got = get_user_var(Some("__definitely_not_a_param_xyz"));
4333 assert_eq!(got, None);
4334 }
4335
4336 #[test]
4337 fn get_data_arr_reads_hashed_keys_or_values() {
4338 let _g = crate::test_util::global_state_lock();
4339 let _g = zle_test_setup();
4341 crate::ported::params::sethparam(
4342 "__test_hash",
4343 vec!["k1".into(), "v1".into(), "k2".into(), "v2".into()],
4344 );
4345
4346 let keys = get_data_arr("__test_hash", true);
4347 assert!(keys.is_some(), "hashed param should have keys");
4348 let mut keys = keys.unwrap();
4349 keys.sort();
4350 assert_eq!(keys, vec!["k1".to_string(), "k2".to_string()]);
4351
4352 let vals = get_data_arr("__test_hash", false);
4353 assert!(vals.is_some(), "hashed param should have values");
4354 let mut vals = vals.unwrap();
4355 vals.sort();
4356 assert_eq!(vals, vec!["v1".to_string(), "v2".to_string()]);
4357 }
4358
4359 #[test]
4360 fn get_data_arr_none_for_non_hashed() {
4361 let _g = crate::test_util::global_state_lock();
4362 let _g = zle_test_setup();
4365 setsparam("__test_scalar2", "value");
4366 let got = get_data_arr("__test_scalar2", false);
4367 assert_eq!(got, None, "scalar params must NOT come out of get_data_arr");
4368 }
4369
4370 #[test]
4371 fn before_complete_snapshots_oldmenucmp() {
4372 let _g = crate::test_util::global_state_lock();
4373 let _g = zle_test_setup();
4375 MENUCMP.store(7, Ordering::Relaxed);
4376 OLDMENUCMP.store(0, Ordering::Relaxed);
4377 let mut lst = 0;
4378 let _ = before_complete(&mut lst);
4379 assert_eq!(OLDMENUCMP.load(Ordering::Relaxed), 7);
4380 MENUCMP.store(0, Ordering::Relaxed);
4382 OLDMENUCMP.store(0, Ordering::Relaxed);
4383 }
4384
4385 #[test]
4386 fn before_complete_clears_showagain() {
4387 let _g = crate::test_util::global_state_lock();
4388 let _g = zle_test_setup();
4390 SHOWAGAIN.store(5, Ordering::Relaxed);
4391 let mut lst = 0;
4392 let _ = before_complete(&mut lst);
4393 assert_eq!(
4394 SHOWAGAIN.load(Ordering::Relaxed),
4395 0,
4396 "SHOWAGAIN must be cleared by before_complete"
4397 );
4398 }
4399
4400 #[test]
4401 fn remsquote_default_quoting() {
4402 let _g = crate::test_util::global_state_lock();
4403 let _g = zle_test_setup();
4404 let mut s = String::from("a'\\''b");
4405 let n = remsquote(&mut s);
4406 assert_eq!(s, "a'b");
4407 assert_eq!(n, 3);
4408 }
4409
4410 #[test]
4411 fn ctokenize_dollar_substitution() {
4412 let _g = crate::test_util::global_state_lock();
4413 let _g = zle_test_setup();
4414 let out = ctokenize("$x{y}");
4415 let chars: Vec<char> = out.chars().collect();
4416 assert_eq!(chars[0], Stringg);
4417 assert_eq!(chars[1], 'x');
4418 assert_eq!(chars[2], Inbrace);
4419 assert_eq!(chars[3], 'y');
4420 assert_eq!(chars[4], Outbrace);
4421 }
4422
4423 #[test]
4424 fn get_user_var_inline_list() {
4425 let _g = crate::test_util::global_state_lock();
4426 let _g = zle_test_setup();
4427 let result = get_user_var(Some("(a b c)")).unwrap();
4428 assert_eq!(result, vec!["a", "b", "c"]);
4429 }
4430
4431 #[test]
4432 fn matchcmp_str_sort_default() {
4433 let _g = crate::test_util::global_state_lock();
4434 let _g = zle_test_setup();
4435 MATCHORDER.store(CGF_MATSORT, Ordering::Relaxed);
4436 let mut a = Cmatch::default();
4437 a.str = Some("apple".into());
4438 let mut b = Cmatch::default();
4439 b.str = Some("banana".into());
4440 assert_eq!(matchcmp(&a, &b), std::cmp::Ordering::Less);
4441 assert_eq!(matchcmp(&b, &a), std::cmp::Ordering::Greater);
4442 assert_eq!(matchcmp(&a, &a), std::cmp::Ordering::Equal);
4443 MATCHORDER.store(0, Ordering::Relaxed);
4444 }
4445
4446 #[test]
4447 fn dupmatch_clones_strings_and_truncates_braces() {
4448 let _g = crate::test_util::global_state_lock();
4449 let _g = zle_test_setup();
4450 let mut src = Cmatch::default();
4452 src.str = Some("foo".into());
4453 src.ipre = Some("ipre".into());
4454 src.flags = 7;
4455 src.brpl = vec![10, 20, 30, 40];
4456 src.brsl = vec![5, 6, 7];
4457 src.qipl = 1;
4458 src.qisl = 2;
4459 src.mode = 0o755;
4460 src.modec = 'd';
4461
4462 let r = dupmatch(&src, 2, 1);
4463 assert_eq!(r.str.as_deref(), Some("foo"));
4464 assert_eq!(r.ipre.as_deref(), Some("ipre"));
4465 assert_eq!(r.flags, 7);
4466 assert_eq!(r.brpl, vec![10, 20]); assert_eq!(r.brsl, vec![5]); assert_eq!(r.qipl, 1);
4469 assert_eq!(r.qisl, 2);
4470 assert_eq!(r.mode, 0o755);
4471 assert_eq!(r.modec, 'd');
4472 }
4473
4474 #[test]
4475 fn dupmatch_empty_braces_stay_empty() {
4476 let _g = crate::test_util::global_state_lock();
4477 let _g = zle_test_setup();
4478 let src = Cmatch::default();
4480 let r = dupmatch(&src, 5, 5);
4481 assert!(r.brpl.is_empty());
4482 assert!(r.brsl.is_empty());
4483 }
4484
4485 #[test]
4486 fn makearray_sorted_and_deduped() {
4487 let _g = crate::test_util::global_state_lock();
4488 let _g = zle_test_setup();
4489 let mut a = Cmatch::default();
4492 a.str = Some("z".into());
4493 let mut b = Cmatch::default();
4494 b.str = Some("a".into());
4495 let mut c = Cmatch::default();
4496 c.str = Some("a".into());
4497 let (arr, n, _nl, _ll) = makearray(vec![a, b, c], CGF_MATSORT);
4498 assert_eq!(arr.len(), 2);
4500 assert_eq!(n, 2);
4501 assert_eq!(arr[0].str.as_deref(), Some("a"));
4502 assert_eq!(arr[1].str.as_deref(), Some("z"));
4503 }
4504
4505 #[test]
4506 fn makearray_nosort_unchanged_order() {
4507 let _g = crate::test_util::global_state_lock();
4508 let _g = zle_test_setup();
4509 let mut a = Cmatch::default();
4511 a.str = Some("z".into());
4512 let mut b = Cmatch::default();
4513 b.str = Some("a".into());
4514 let (arr, n, _, _) = makearray(vec![a, b], CGF_NOSORT | CGF_UNIQALL);
4515 assert_eq!(n, 2);
4517 assert_eq!(arr[0].str.as_deref(), Some("z"));
4518 assert_eq!(arr[1].str.as_deref(), Some("a"));
4519 }
4520
4521 #[test]
4522 fn makearray_strings_dedup_consecutive() {
4523 let _g = crate::test_util::global_state_lock();
4524 let _g = zle_test_setup();
4525 let (arr, n) = makearray_strings(vec!["b".into(), "a".into(), "a".into(), "c".into()], 1);
4527 assert_eq!(n, 3);
4528 assert_eq!(arr, vec!["a", "b", "c"]);
4529 }
4530
4531 #[test]
4532 fn check_param_no_dollar_returns_none() {
4533 let _g = crate::test_util::global_state_lock();
4534 let _g = zle_test_setup();
4535 OFFS.store(2, Ordering::Relaxed);
4537 assert_eq!(check_param("abc", false, false), None);
4538 }
4539
4540 #[test]
4541 fn check_param_simple_dollar_var_at_cursor() {
4542 let _g = crate::test_util::global_state_lock();
4543 let _g = zle_test_setup();
4544 OFFS.store(2, Ordering::Relaxed);
4546 let s = format!("{}FOO", Stringg);
4547 let r = check_param(&s, false, true);
4548 assert!(r.is_some(), "expected Some(b) inside $FOO");
4549 }
4550
4551 #[test]
4552 fn callcompfunc_empty_fn_no_panic() {
4553 let _g = crate::test_util::global_state_lock();
4554 let _g = zle_test_setup();
4555 callcompfunc("anything", "");
4557 }
4558
4559 #[test]
4560 fn callcompfunc_sets_compstate_context() {
4561 let _g = crate::test_util::global_state_lock();
4562 let _g = zle_test_setup();
4563 let _g = GLOBAL_MUT_LOCK.lock().unwrap();
4564 ispar.store(0, Ordering::Relaxed);
4569 linwhat.store(IN_PAR_LW, Ordering::Relaxed);
4570 assert_eq!(compcontext_for("foo"), "assign_parameter");
4571 callcompfunc("foo", "_test_fn");
4573 }
4574
4575 static GLOBAL_MUT_LOCK: Mutex<()> = Mutex::new(());
4577
4578 #[test]
4579 fn compcontext_for_routes_ispar_first() {
4580 let _g = crate::test_util::global_state_lock();
4581 let _g = zle_test_setup();
4582 let _g = GLOBAL_MUT_LOCK.lock().unwrap();
4583 ispar.store(2, Ordering::Relaxed);
4584 linwhat.store(IN_NOTHING_LW, Ordering::Relaxed);
4585 assert_eq!(compcontext_for("x"), "brace_parameter");
4586 ispar.store(1, Ordering::Relaxed);
4587 assert_eq!(compcontext_for("x"), "parameter");
4588 ispar.store(0, Ordering::Relaxed);
4589 linwhat.store(IN_MATH_LW, Ordering::Relaxed);
4590 assert_eq!(compcontext_for("x"), "math");
4591 linwhat.store(IN_COND_LW, Ordering::Relaxed);
4592 assert_eq!(compcontext_for("x"), "condition");
4593 linwhat.store(IN_ENV_LW, Ordering::Relaxed);
4594 assert_eq!(compcontext_for("x"), "value");
4595 linwhat.store(IN_NOTHING_LW, Ordering::Relaxed);
4596 assert_eq!(compcontext_for("x"), "command");
4597 }
4598
4599 #[test]
4600 fn addmatches_empty_argv_early_return() {
4601 let _g = crate::test_util::global_state_lock();
4602 let _g = zle_test_setup();
4603 let mut dat = Cadata::default();
4605 dat.dummies = 0;
4606 dat.aflags = 0;
4607 assert_eq!(addmatches(&mut dat, &[]), 1);
4608 }
4609
4610 #[test]
4611 fn addmatches_appends_argv_to_default_group() {
4612 let _g = crate::test_util::global_state_lock();
4613 let _g = zle_test_setup();
4614 let _g = GLOBAL_MUT_LOCK.lock().unwrap();
4615 amatches
4617 .get_or_init(|| Mutex::new(Vec::new()))
4618 .lock()
4619 .unwrap()
4620 .clear();
4621 matches
4622 .get_or_init(|| Mutex::new(Vec::new()))
4623 .lock()
4624 .unwrap()
4625 .clear();
4626 let mut dat = Cadata::default();
4627 dat.dummies = -1;
4628 let _ = addmatches(&mut dat, &["a".into(), "b".into()]);
4629 let n = matches.get().unwrap().lock().unwrap().len();
4630 assert!(n >= 2);
4631 }
4632
4633 #[test]
4634 fn add_match_data_returns_populated_cmatch() {
4635 let _g = crate::test_util::global_state_lock();
4636 let _g = zle_test_setup();
4637 let _g = GLOBAL_MUT_LOCK.lock().unwrap();
4638 matches
4640 .get_or_init(|| Mutex::new(Vec::new()))
4641 .lock()
4642 .unwrap()
4643 .clear();
4644 let before = mnum.load(Ordering::Relaxed);
4645 let cm = add_match_data(
4646 0,
4647 "match",
4648 "match-orig",
4649 None,
4650 "ipre",
4651 "ripre",
4652 "isuf",
4653 "pre",
4654 "prpre",
4655 "ppre",
4656 None,
4657 "psuf",
4658 None,
4659 "suf",
4660 0,
4661 0,
4662 );
4663 assert_eq!(cm.str.as_deref(), Some("match"));
4664 assert_eq!(cm.orig.as_deref(), Some("match-orig"));
4665 assert_eq!(cm.pre.as_deref(), Some("pre"));
4666 assert_eq!(cm.suf.as_deref(), Some("suf"));
4667 assert_eq!(mnum.load(Ordering::Relaxed), before + 1);
4668 }
4669
4670 #[test]
4671 fn add_match_data_exact_records_into_ainfo() {
4672 let _g = crate::test_util::global_state_lock();
4673 let _g = zle_test_setup();
4674 let _g = GLOBAL_MUT_LOCK.lock().unwrap();
4675 let saved_useexact = useexact.load(Ordering::Relaxed);
4679 useexact.store(1, Ordering::Relaxed);
4680 if let Ok(mut g) = ainfo.get_or_init(|| Mutex::new(None)).lock() {
4681 *g = Some(Aminfo::default());
4682 }
4683 let _ = add_match_data(
4684 0, "x", "x", None, "", "", "", "", "", "", None, "", None, "", 0, 1,
4685 );
4686 let a = ainfo.get().unwrap().lock().unwrap().clone().unwrap();
4687 useexact.store(saved_useexact, Ordering::Relaxed);
4688 assert_eq!(a.exact, 1);
4689 assert!(a.exactm.is_some());
4690 }
4691
4692 #[test]
4693 fn set_comp_sep_returns_one() {
4694 let _g = crate::test_util::global_state_lock();
4695 let _g = zle_test_setup();
4696 assert_eq!(set_comp_sep(), 1);
4698 }
4699
4700 #[test]
4701 fn foredel_deletes_forward_from_zlemetacs() {
4702 let _g = crate::test_util::global_state_lock();
4703 let _g = zle_test_setup();
4704 let _g = GLOBAL_MUT_LOCK.lock().unwrap();
4705 if let Ok(mut g) = ZLEMETALINE.get_or_init(|| Mutex::new(String::new())).lock() {
4707 *g = "abcdef".to_string();
4708 }
4709 ZLEMETACS.store(2, Ordering::Relaxed);
4710 ZLEMETALL.store(6, Ordering::Relaxed);
4711 foredel(3, CUT_RAW);
4712 let line = ZLEMETALINE.get().unwrap().lock().unwrap().clone();
4713 assert_eq!(line, "abf");
4714 assert_eq!(ZLEMETALL.load(Ordering::Relaxed), 3);
4715 }
4716
4717 #[test]
4718 fn inststr_inserts_at_zlemetacs() {
4719 let _g = crate::test_util::global_state_lock();
4720 let _g = zle_test_setup();
4721 let _g = GLOBAL_MUT_LOCK.lock().unwrap();
4722 if let Ok(mut g) = ZLEMETALINE.get_or_init(|| Mutex::new(String::new())).lock() {
4724 *g = "hello".to_string();
4725 }
4726 ZLEMETACS.store(5, Ordering::Relaxed);
4727 ZLEMETALL.store(5, Ordering::Relaxed);
4728 let _ = inststr(" world");
4729 let line = ZLEMETALINE.get().unwrap().lock().unwrap().clone();
4730 assert_eq!(line, "hello world");
4731 assert_eq!(ZLEMETACS.load(Ordering::Relaxed), 11);
4732 }
4733
4734 #[test]
4735 fn metafy_and_unmetafy_roundtrip_globals() {
4736 let _g = crate::test_util::global_state_lock();
4737 let _g = zle_test_setup();
4738 let _g = GLOBAL_MUT_LOCK.lock().unwrap();
4739 if let Ok(mut g) = ZLELINE.get_or_init(|| Mutex::new(String::new())).lock() {
4741 *g = "plain ascii".to_string();
4742 }
4743 ZLECS.store(3, Ordering::Relaxed);
4744 ZLELL.store(11, Ordering::Relaxed);
4745 metafy_line();
4746 assert_eq!(
4748 ZLEMETALINE.get().unwrap().lock().unwrap().clone(),
4749 "plain ascii"
4750 );
4751 assert_eq!(ZLEMETACS.load(Ordering::Relaxed), 3);
4752 unmetafy_line();
4753 assert_eq!(
4754 ZLELINE.get().unwrap().lock().unwrap().clone(),
4755 "plain ascii"
4756 );
4757 }
4758
4759 #[test]
4760 fn selfinsert_appends_lastchar_at_zlecs() {
4761 let _g = crate::test_util::global_state_lock();
4762 let _g = zle_test_setup();
4763 let _g = GLOBAL_MUT_LOCK.lock().unwrap();
4764 {
4779 let mut g = crate::ported::zle::zle_main::ZLELINE.lock().unwrap();
4780 *g = "ab".chars().collect();
4781 }
4782 crate::ported::zle::zle_main::ZLECS.store(2, Ordering::Relaxed);
4783 crate::ported::zle::zle_main::ZLELL.store(2, Ordering::Relaxed);
4784 LASTCHAR.store(b'c' as i32, Ordering::Relaxed);
4785 LASTCHAR_WIDE_VALID.store(0, Ordering::Relaxed);
4788 let rv = selfinsert(&[]);
4789 assert_eq!(rv, 0);
4790 let buf: String = crate::ported::zle::zle_main::ZLELINE
4791 .lock()
4792 .unwrap()
4793 .iter()
4794 .collect();
4795 assert_eq!(buf, "abc");
4796 assert_eq!(
4797 crate::ported::zle::zle_main::ZLECS.load(Ordering::Relaxed),
4798 3,
4799 );
4800 }
4801
4802 #[test]
4803 fn minfo_clear_and_asked_zero_mutate_state() {
4804 let _g = crate::test_util::global_state_lock();
4805 let _g = zle_test_setup();
4806 let _g = GLOBAL_MUT_LOCK.lock().unwrap();
4807 if let Ok(mut g) = MINFO.get_or_init(|| Mutex::new(Menuinfo::default())).lock() {
4808 let mut cm = Cmatch::default();
4809 cm.str = Some("x".into());
4810 g.cur = Some(Box::new(cm));
4811 g.asked = 1;
4812 }
4813 if let Ok(mut g) = MINFO.get_or_init(|| Mutex::new(Menuinfo::default())).lock() {
4814 g.cur = None;
4815 }
4816 if let Ok(mut g) = MINFO.get_or_init(|| Mutex::new(Menuinfo::default())).lock() {
4817 g.asked = 0;
4818 }
4819 let m = MINFO.get().unwrap().lock().unwrap().clone();
4820 assert!(m.cur.is_none());
4821 assert_eq!(m.asked, 0);
4822 }
4823
4824 #[test]
4825 fn cline_matched_stub_marks_node() {
4826 let _g = crate::test_util::global_state_lock();
4827 let _g = zle_test_setup();
4828 cline_matched_compcore(Some("foo"));
4833 cline_matched_compcore(None);
4834 cline_matched_compcore(Some(""));
4835 }
4836
4837 #[test]
4838 fn permmatches_returns_fi_zero_when_count_present() {
4839 let _g = crate::test_util::global_state_lock();
4840 let _g = zle_test_setup();
4841 let _g = GLOBAL_MUT_LOCK.lock().unwrap();
4842 amatches
4844 .get_or_init(|| Mutex::new(Vec::new()))
4845 .lock()
4846 .unwrap()
4847 .clear();
4848 pmatches
4849 .get_or_init(|| Mutex::new(Vec::new()))
4850 .lock()
4851 .unwrap()
4852 .clear();
4853 if let Ok(mut a) = ainfo.get_or_init(|| Mutex::new(None)).lock() {
4854 *a = Some(Aminfo {
4855 count: 5,
4856 ..Default::default()
4857 });
4858 }
4859 newmatches.store(1, Ordering::Relaxed);
4860 let fi = permmatches(0);
4861 assert_eq!(fi, 0);
4862 assert_eq!(hasperm.load(Ordering::Relaxed), 1);
4863 }
4864
4865 #[test]
4871 fn rembslash_unescapes_canonical_pairs() {
4872 let _g = crate::test_util::global_state_lock();
4873 assert_eq!(rembslash(r"\a"), "a");
4874 assert_eq!(rembslash(r"\\"), r"\");
4875 assert_eq!(rembslash(r"\$foo"), "$foo");
4876 assert_eq!(rembslash("plain"), "plain");
4877 }
4878
4879 #[test]
4882 fn rembslash_empty_input_returns_empty() {
4883 let _g = crate::test_util::global_state_lock();
4884 assert_eq!(rembslash(""), "");
4885 }
4886
4887 #[test]
4892 fn rembslash_trailing_lone_backslash_drops_silently() {
4893 let _g = crate::test_util::global_state_lock();
4894 assert_eq!(rembslash(r"foo\"), "foo");
4895 }
4896
4897 #[test]
4901 fn ctokenize_passes_alphanumerics_through() {
4902 let _g = crate::test_util::global_state_lock();
4903 assert_eq!(ctokenize("foo123"), "foo123");
4904 assert_eq!(ctokenize(""), "");
4905 assert_eq!(ctokenize("path/to"), "path/to");
4906 }
4907
4908 #[test]
4913 fn comp_quoting_string_dispatches_known_styles() {
4914 let _g = crate::test_util::global_state_lock();
4915 for stype in 0..=8 {
4919 let _ = comp_quoting_string(stype);
4920 }
4921 }
4922
4923 #[test]
4928 fn multiquote_empty_stack_returns_input_unchanged() {
4929 let _g = crate::test_util::global_state_lock();
4930 if let Some(c) = COMPQSTACK.get() {
4932 if let Ok(mut g) = c.lock() {
4933 g.clear();
4934 }
4935 }
4936 assert_eq!(multiquote("hello", 0), "hello");
4937 assert_eq!(multiquote("", 0), "");
4938 }
4939
4940 #[test]
4945 fn tildequote_non_tilde_input_unchanged() {
4946 let _g = crate::test_util::global_state_lock();
4947 if let Some(c) = COMPQSTACK.get() {
4949 if let Ok(mut g) = c.lock() {
4950 g.clear();
4951 }
4952 }
4953 assert_eq!(tildequote("foo/bar", 0), "foo/bar");
4954 }
4955
4956 #[test]
4958 fn tildequote_empty_input_empty_output() {
4959 let _g = crate::test_util::global_state_lock();
4960 if let Some(c) = COMPQSTACK.get() {
4961 if let Ok(mut g) = c.lock() {
4962 g.clear();
4963 }
4964 }
4965 assert_eq!(tildequote("", 0), "");
4966 }
4967
4968 #[test]
4972 fn compcore_corpus_rembslash_strips_escapes() {
4973 let _g = crate::test_util::global_state_lock();
4974 assert_eq!(rembslash(r"\a\b\c"), "abc");
4975 }
4976
4977 #[test]
4979 fn compcore_corpus_rembslash_no_escapes_identity() {
4980 let _g = crate::test_util::global_state_lock();
4981 assert_eq!(rembslash("hello"), "hello");
4982 }
4983
4984 #[test]
4986 fn compcore_corpus_rembslash_empty_is_empty() {
4987 let _g = crate::test_util::global_state_lock();
4988 assert_eq!(rembslash(""), "");
4989 }
4990
4991 #[test]
4993 fn compcore_corpus_rembslash_escaped_backslash() {
4994 let _g = crate::test_util::global_state_lock();
4995 assert_eq!(rembslash(r"\\"), r"\");
4997 }
4998
4999 #[test]
5001 fn compcore_corpus_rembslash_preserves_context() {
5002 let _g = crate::test_util::global_state_lock();
5003 assert_eq!(rembslash(r"hello\ world"), "hello world");
5004 }
5005
5006 #[test]
5008 fn compcore_corpus_rembslash_trailing_backslash_consumed() {
5009 let _g = crate::test_util::global_state_lock();
5010 assert_eq!(rembslash(r"abc\"), "abc");
5011 }
5012
5013 #[test]
5020 fn rembslash_no_backslash_is_identity() {
5021 let _g = crate::test_util::global_state_lock();
5022 assert_eq!(rembslash("abc"), "abc");
5023 assert_eq!(rembslash("hello world"), "hello world");
5024 }
5025
5026 #[test]
5028 fn rembslash_drops_backslash_keeps_next() {
5029 let _g = crate::test_util::global_state_lock();
5030 assert_eq!(rembslash(r"\a"), "a");
5031 assert_eq!(rembslash(r"\."), ".");
5032 assert_eq!(rembslash(r"\$"), "$");
5033 }
5034
5035 #[test]
5037 fn rembslash_multiple_escapes_chain() {
5038 let _g = crate::test_util::global_state_lock();
5039 assert_eq!(rembslash(r"\a\b\c"), "abc");
5040 }
5041
5042 #[test]
5044 fn remsquote_empty_returns_zero() {
5045 let _g = crate::test_util::global_state_lock();
5046 let mut s = String::new();
5047 let r = remsquote(&mut s);
5048 assert_eq!(r, 0);
5049 assert_eq!(s, "");
5050 }
5051
5052 #[test]
5055 fn remsquote_no_quotes_is_identity() {
5056 let _g = crate::test_util::global_state_lock();
5057 let mut s = String::from("hello world");
5058 let r = remsquote(&mut s);
5059 assert_eq!(r, 0, "no quote sequences → 0");
5060 assert_eq!(s, "hello world", "string unchanged");
5061 }
5062
5063 #[test]
5065 fn ctokenize_empty_returns_empty() {
5066 let _g = crate::test_util::global_state_lock();
5067 assert_eq!(ctokenize(""), "");
5068 }
5069
5070 #[test]
5072 fn ctokenize_plain_ascii_preserved() {
5073 let _g = crate::test_util::global_state_lock();
5074 let r = ctokenize("abc");
5076 assert_eq!(r.as_bytes()[0], b'a');
5077 assert_eq!(r.as_bytes()[1], b'b');
5078 assert_eq!(r.as_bytes()[2], b'c');
5079 }
5080
5081 #[test]
5083 fn comp_quoting_string_returns_static_str_for_all_stypes() {
5084 let _g = crate::test_util::global_state_lock();
5085 for stype in 0..10 {
5086 let _s = comp_quoting_string(stype);
5087 }
5089 }
5090
5091 #[test]
5093 fn multiquote_empty_returns_empty() {
5094 let _g = crate::test_util::global_state_lock();
5095 let _g2 = zle_test_setup();
5096 let r = multiquote("", 0);
5097 assert_eq!(r, "");
5098 }
5099
5100 #[test]
5102 fn tildequote_empty_returns_empty() {
5103 let _g = crate::test_util::global_state_lock();
5104 let _g2 = zle_test_setup();
5105 let r = tildequote("", 0);
5106 assert_eq!(r, "");
5107 }
5108
5109 #[test]
5111 fn tildequote_no_tilde_is_identity() {
5112 let _g = crate::test_util::global_state_lock();
5113 let _g2 = zle_test_setup();
5114 let r = tildequote("plain", 0);
5115 assert_eq!(r, "plain", "no tilde → input unchanged");
5116 }
5117
5118 #[test]
5127 fn rembslash_empty_returns_empty() {
5128 assert_eq!(rembslash(""), "");
5129 }
5130
5131 #[test]
5133 fn rembslash_is_pure() {
5134 for s in ["", "abc", r"\a", r"\\\\"] {
5135 let first = rembslash(s);
5136 for _ in 0..3 {
5137 assert_eq!(rembslash(s), first, "rembslash({:?}) must be pure", s);
5138 }
5139 }
5140 }
5141
5142 #[test]
5144 fn ctokenize_is_deterministic() {
5145 for s in ["", "abc", "a*b", "a?b"] {
5146 let first = ctokenize(s);
5147 for _ in 0..3 {
5148 assert_eq!(
5149 ctokenize(s),
5150 first,
5151 "ctokenize({:?}) must be deterministic",
5152 s
5153 );
5154 }
5155 }
5156 }
5157
5158 #[test]
5160 fn comp_quoting_string_zero_returns_static_str() {
5161 let _: &'static str = comp_quoting_string(0);
5162 }
5163
5164 #[test]
5166 fn multiquote_is_pure() {
5167 let _g = crate::test_util::global_state_lock();
5168 let _g2 = zle_test_setup();
5169 for s in ["", "abc", "x y"] {
5170 let first = multiquote(s, 0);
5171 for _ in 0..3 {
5172 assert_eq!(
5173 multiquote(s, 0),
5174 first,
5175 "multiquote({:?}, 0) must be pure",
5176 s
5177 );
5178 }
5179 }
5180 }
5181
5182 #[test]
5184 fn tildequote_is_pure() {
5185 let _g = crate::test_util::global_state_lock();
5186 let _g2 = zle_test_setup();
5187 for s in ["", "abc", "no_tilde", "/path/to/file"] {
5188 let first = tildequote(s, 0);
5189 for _ in 0..3 {
5190 assert_eq!(
5191 tildequote(s, 0),
5192 first,
5193 "tildequote({:?}, 0) must be pure",
5194 s
5195 );
5196 }
5197 }
5198 }
5199
5200 #[test]
5202 fn remsquote_empty_returns_zero_pin() {
5203 let mut s = String::new();
5204 let r = remsquote(&mut s);
5205 assert_eq!(r, 0, "empty input → 0");
5206 }
5207
5208 #[test]
5210 fn check_param_empty_returns_option_type() {
5211 let _g = crate::test_util::global_state_lock();
5212 let _g2 = zle_test_setup();
5213 let _: Option<usize> = check_param("", false, false);
5214 }
5215
5216 #[test]
5218 fn check_param_empty_is_deterministic() {
5219 let _g = crate::test_util::global_state_lock();
5220 let _g2 = zle_test_setup();
5221 let first = check_param("", false, false);
5222 for _ in 0..3 {
5223 assert_eq!(check_param("", false, false), first);
5224 }
5225 }
5226
5227 #[test]
5229 fn comp_str_returns_string_i32_i32_tuple() {
5230 let _g = crate::test_util::global_state_lock();
5231 let _g2 = zle_test_setup();
5232 let _: (String, i32, i32) = comp_str(false);
5233 }
5234
5235 #[test]
5244 fn set_comp_sep_returns_i32_type() {
5245 let _g = crate::test_util::global_state_lock();
5246 let _g2 = zle_test_setup();
5247 let _: i32 = set_comp_sep();
5248 }
5249
5250 #[test]
5252 fn get_user_var_returns_option_vec_string_type() {
5253 let _g = crate::test_util::global_state_lock();
5254 let _g2 = zle_test_setup();
5255 let _: Option<Vec<String>> = get_user_var(None);
5256 }
5257
5258 #[test]
5260 fn get_user_var_none_is_deterministic() {
5261 let _g = crate::test_util::global_state_lock();
5262 let _g2 = zle_test_setup();
5263 let first = get_user_var(None);
5264 for _ in 0..3 {
5265 assert_eq!(
5266 get_user_var(None),
5267 first,
5268 "get_user_var(None) must be deterministic"
5269 );
5270 }
5271 }
5272
5273 #[test]
5275 fn get_data_arr_returns_option_vec_string_type() {
5276 let _g = crate::test_util::global_state_lock();
5277 let _g2 = zle_test_setup();
5278 let _: Option<Vec<String>> = get_data_arr("", false);
5279 }
5280
5281 #[test]
5283 fn get_data_arr_empty_name_returns_none() {
5284 let _g = crate::test_util::global_state_lock();
5285 let _g2 = zle_test_setup();
5286 assert!(get_data_arr("", false).is_none(), "empty name → None");
5287 assert!(
5288 get_data_arr("", true).is_none(),
5289 "empty name (keys=true) → None"
5290 );
5291 }
5292
5293 #[test]
5295 fn begcmgroup_none_no_panic() {
5296 let _g = crate::test_util::global_state_lock();
5297 let _g2 = zle_test_setup();
5298 begcmgroup(None, 0);
5299 }
5300
5301 #[test]
5303 fn endcmgroup_none_no_panic() {
5304 let _g = crate::test_util::global_state_lock();
5305 let _g2 = zle_test_setup();
5306 endcmgroup(None);
5307 }
5308
5309 #[test]
5311 fn begcmgroup_endcmgroup_round_trip_safe() {
5312 let _g = crate::test_util::global_state_lock();
5313 let _g2 = zle_test_setup();
5314 for _ in 0..3 {
5315 begcmgroup(None, 0);
5316 endcmgroup(None);
5317 }
5318 }
5319
5320 #[test]
5322 fn addexpl_false_no_panic() {
5323 let _g = crate::test_util::global_state_lock();
5324 let _g2 = zle_test_setup();
5325 addexpl(false);
5326 }
5327
5328 #[test]
5330 fn set_list_array_empty_no_panic() {
5331 let _g = crate::test_util::global_state_lock();
5332 let _g2 = zle_test_setup();
5333 set_list_array("", &[]);
5334 }
5335
5336 #[test]
5338 fn comp_quoting_string_returns_static_str_type() {
5339 let _: &'static str = comp_quoting_string(0);
5340 }
5341
5342 #[test]
5344 fn comp_quoting_string_pure_across_stypes() {
5345 for stype in 0..10 {
5346 let first = comp_quoting_string(stype);
5347 for _ in 0..3 {
5348 assert_eq!(
5349 comp_quoting_string(stype),
5350 first,
5351 "comp_quoting_string({}) must be pure",
5352 stype
5353 );
5354 }
5355 }
5356 }
5357}