use std::sync::atomic::Ordering;
use super::zle_h::{CH_NEXT, CH_PREV, CUT_RAW, MOD_VIBUF, ZSL_COPY, ZSL_TOEND};
use crate::ported::builtin::RETFLAG;
use crate::ported::utils::errflag;
use crate::ported::zle::compcore::{ZLEMETACS, ZLEMETALINE, ZLEMETALL};
use crate::ported::zsh_h::ERRFLAG_INT;
#[allow(unused_imports)]
use crate::ported::zle::{
deltochar::*, textobjects::*, zle_hist::*, zle_main::*, zle_misc::*, zle_move::*,
zle_params::*, zle_refresh::*, zle_tricky::*, zle_vi::*, zle_word::*,
};
#[allow(unused_imports)]
#[allow(unused_imports)]
use crate::zle::zle_main::{
history, vibuf, CURCHANGE, KILLRING, KILLRINGMAX, LASTCS, LASTLINE, LASTLL, MARK,
UNDO_CHANGENO, UNDO_LIMITNO, UNDO_STACK, VISTARTCHANGE, ZLECS, ZLELINE, ZLELL,
ZLE_RESET_NEEDED, ZMOD,
};
pub fn sizeline(sz: usize) {
let mut __g_zleline = ZLELINE.lock().unwrap();
let cur_len = __g_zleline.len();
if sz > cur_len {
__g_zleline.reserve(sz - cur_len + 256);
}
}
pub fn zleaddtoline(ch: i32) {
ZLELINE.lock().unwrap().push(ch as u8 as char);
ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
}
pub fn zlecharasstring(inchar: char, buf: &mut String) -> i32 {
let start = buf.len();
buf.push(inchar);
(buf.len() - start) as i32
}
pub fn zlelineasstring(line: &[char], ll: usize, _flags: i32) -> String {
line.iter().take(ll).collect()
}
pub fn stringaszleline(s: &str) -> Vec<char> {
let mut out = Vec::new();
let bytes = s.as_bytes();
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if b == 0x83 && i + 1 < bytes.len() {
i += 1;
out.push((bytes[i] ^ 32) as char);
} else {
out.push(b as char);
}
i += 1;
}
out
}
pub fn zlegetline(
ll: &mut usize,
cs: &mut usize,
) -> Vec<char> {
*ll = ZLELL.load(Ordering::SeqCst);
*cs = ZLECS.load(Ordering::SeqCst);
ZLELINE.lock().unwrap().clone()
}
pub fn free_region_highlights_memos() { }
#[derive(Debug, Clone)]
#[allow(non_camel_case_types)]
pub struct zle_position {
pub cs: usize,
pub mk: usize,
pub ll: usize,
}
pub fn zle_save_positions() {
use crate::ported::zle::zle_refresh::REGION_HIGHLIGHTS;
let mk = MARK.load(Ordering::SeqCst); let cs = ZLECS.load(Ordering::SeqCst); let ll = ZLELL.load(Ordering::SeqCst);
const N_SPECIAL_HIGHLIGHTS: usize = 4;
let regions: Vec<crate::ported::zle::zle_refresh::RegionHighlight> = REGION_HIGHLIGHTS
.lock()
.unwrap()
.iter()
.skip(N_SPECIAL_HIGHLIGHTS)
.cloned()
.collect();
let pos = ZlePosition {
mk,
cs,
ll,
regions,
};
if let Ok(mut s) = ZLE_POSITIONS.lock() {
s.push(pos);
}
}
pub fn zle_restore_positions() {
use crate::ported::zle::zle_refresh::REGION_HIGHLIGHTS;
let oldpos = match ZLE_POSITIONS.lock().ok().and_then(|mut s| s.pop()) {
Some(p) => p,
None => return,
};
MARK.store(oldpos.mk, Ordering::SeqCst); ZLECS.store(
oldpos.cs.min(oldpos.ll), Ordering::SeqCst,
);
ZLELL.store(oldpos.ll, Ordering::SeqCst);
const N_SPECIAL_HIGHLIGHTS: usize = 4;
if let Ok(mut rh) = REGION_HIGHLIGHTS.lock() {
rh.truncate(N_SPECIAL_HIGHLIGHTS); for r in &oldpos.regions {
rh.push(r.clone()); }
}
}
pub fn zle_free_positions() {
if let Ok(mut s) = ZLE_POSITIONS.lock() {
s.pop(); }
}
pub fn spaceinline(ct: i32) {
if ct <= 0 {
return;
}
let ct = ct as usize;
for _ in 0..ct {
ZLELINE
.lock()
.unwrap()
.insert(ZLECS.load(Ordering::SeqCst), '\0');
}
ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
}
pub fn shiftchars(to: i32, cnt: i32) {
let mark_cur = MARK.load(Ordering::SeqCst) as i32;
if mark_cur >= to + cnt {
MARK
.store((mark_cur - cnt).max(0) as usize, Ordering::SeqCst); } else if mark_cur > to {
MARK.store(to.max(0) as usize, Ordering::SeqCst); }
use crate::ported::zle::zle_h::N_SPECIAL_HIGHLIGHTS;
use crate::ported::zle::zle_refresh::REGION_HIGHLIGHTS;
let n_special = N_SPECIAL_HIGHLIGHTS as usize;
if let Ok(mut rh) = REGION_HIGHLIGHTS.lock() {
let total = rh.len();
let upper = total;
for idx in n_special..upper {
let entry = &mut rh[idx];
if (entry.start as i32) > to {
if (entry.start as i32) > to + cnt {
entry.start = entry.start.saturating_sub(cnt as usize); } else {
entry.start = to.max(0) as usize; }
}
if (entry.end as i32) > to {
if (entry.end as i32) > to + cnt {
entry.end = entry.end.saturating_sub(cnt as usize); } else {
entry.end = to.max(0) as usize; }
}
}
}
let to = to as usize;
let cnt = cnt as usize;
let mut line = ZLELINE.lock().unwrap();
let len = line.len();
if to >= len {
ZLELL.store(len, Ordering::SeqCst);
return;
}
if to + cnt >= len {
line.truncate(to); } else {
line.drain(to..to + cnt); }
ZLELL.store(line.len(), Ordering::SeqCst); }
pub fn cut(i: i32, ct: i32, dir: i32) -> i32 {
let line = ZLELINE.lock().unwrap(); let start = (i.max(0) as usize).min(line.len());
let end = (start + ct.max(0) as usize).min(line.len());
cuttext(&line[start..end].to_vec(), dir); 0
}
pub fn cuttext(
txt: &[char], flags: i32,
) {
use crate::ported::zle::zle_h::{
CUTBUFFER_LINE, CUT_FRONT, CUT_REPLACE, CUT_YANK, MOD_NULL, MOD_VIAPP, ZLE_KILL,
};
use crate::ported::zle::zle_main::{CUTBUF, KILLRING, KILLRINGMAX, KRINGNUM, LASTCMD};
use crate::ported::zle::zle_vi::VILINERANGE;
let ct = txt.len();
let vilinerange = VILINERANGE.load(Ordering::Relaxed) != 0;
if (ct == 0 && !vilinerange) || ZMOD.lock().unwrap().flags & MOD_NULL != 0 {
return;
}
let mod_flags = ZMOD.lock().unwrap().flags;
let mod_vibuf = ZMOD.lock().unwrap().vibuf as usize;
let chars: Vec<char> = txt.to_vec();
if mod_flags & MOD_VIBUF != 0 {
let idx = mod_vibuf.min(vibuf().lock().unwrap().len().saturating_sub(1));
let viapp = mod_flags & MOD_VIAPP != 0;
let mut vibuf_guard = vibuf().lock().unwrap();
if !viapp || vibuf_guard[idx].is_empty() {
vibuf_guard[idx] = chars;
} else {
if vilinerange {
vibuf_guard[idx].push('\n');
}
vibuf_guard[idx].extend(chars);
}
return;
} else if flags & CUT_YANK != 0 {
if let Some(slot) = vibuf().lock().unwrap().get_mut(26) {
*slot = chars;
}
} else {
let mut v = vibuf().lock().unwrap();
for n in (28..36).rev() {
v[n] = v[n - 1].clone();
}
v[27] = chars.clone();
}
let lastcmd_v = LASTCMD.load(Ordering::Relaxed) as i32;
let cutbuf_empty = CUTBUF.lock().unwrap().buf.is_empty();
let should_rotate = !cutbuf_empty
&& ((lastcmd_v & ZLE_KILL) == 0 || (flags & CUT_REPLACE) != 0);
if should_rotate {
let old: Vec<char> = CUTBUF.lock().unwrap().buf.chars().collect();
KILLRING.lock().unwrap().push_front(old);
let max = KILLRINGMAX.load(Ordering::SeqCst);
while KILLRING.lock().unwrap().len() > max {
KILLRING.lock().unwrap().pop_back();
}
KRINGNUM.store(0, Ordering::Relaxed);
let mut cb = CUTBUF.lock().unwrap();
cb.buf.clear();
cb.len = 0;
cb.flags = 0;
}
let mut cb = CUTBUF.lock().unwrap();
let cell: String = txt.iter().collect();
if flags & (CUT_FRONT | CUT_REPLACE) != 0 {
if flags & CUT_REPLACE != 0 {
cb.buf = cell;
} else {
cb.buf = format!("{}{}", cell, cb.buf);
}
} else {
cb.buf.push_str(&cell);
}
cb.len = cb.buf.chars().count();
if vilinerange {
cb.flags |= CUTBUFFER_LINE;
}
}
pub fn backkill(ct: i32, flags: i32) {
let ct = ct as usize;
if ct == 0 || ZLECS.load(Ordering::SeqCst) == 0 {
return;
}
let _ = flags; let take_n = ct.min(ZLECS.load(Ordering::SeqCst));
let start = ZLECS.load(Ordering::SeqCst) - take_n;
let cut_chars: Vec<char> = ZLELINE
.lock()
.unwrap()
.drain(start..ZLECS.load(Ordering::SeqCst))
.collect(); ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
ZLECS.store(start, Ordering::SeqCst);
KILLRING.lock().unwrap().push_front(cut_chars);
if KILLRING.lock().unwrap().len() > KILLRINGMAX.load(Ordering::SeqCst) {
KILLRING.lock().unwrap().pop_back();
}
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst); }
pub fn forekill(ct: i32, flags: i32) {
let ct = ct as usize;
if ct == 0
|| ZLECS.load(Ordering::SeqCst)
>= ZLELL.load(Ordering::SeqCst)
{
return;
}
let _ = flags; let take_n = ct.min(
ZLELL.load(Ordering::SeqCst)
- ZLECS.load(Ordering::SeqCst),
);
let i = ZLECS.load(Ordering::SeqCst);
let cut_chars: Vec<char> = ZLELINE.lock().unwrap().drain(i..i + take_n).collect(); ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
KILLRING.lock().unwrap().push_front(cut_chars);
if KILLRING.lock().unwrap().len() > KILLRINGMAX.load(Ordering::SeqCst) {
KILLRING.lock().unwrap().pop_back();
}
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst); }
pub fn backdel(ct: i32, _flags: i32) {
let ct = ct as usize;
if ct == 0 || ZLECS.load(Ordering::SeqCst) == 0 {
return;
}
let take_n = ct.min(ZLECS.load(Ordering::SeqCst));
let start = ZLECS.load(Ordering::SeqCst) - take_n;
ZLELINE
.lock()
.unwrap()
.drain(start..ZLECS.load(Ordering::SeqCst)); ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
ZLECS.store(start, Ordering::SeqCst);
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst); }
pub fn foredel(ct: i32, flags: i32) {
if ct <= 0 {
return;
}
if (flags & CUT_RAW) != 0 { let zml_active = ZLEMETALINE.get().is_some();
if zml_active {
if let Some(m) = ZLEMETALINE.get() {
if let Ok(mut g) = m.lock() {
let cs = ZLEMETACS.load(Ordering::Relaxed) as usize;
if cs < g.len() {
let end = (cs + ct as usize).min(g.len());
let bytes = g.as_bytes();
let new_line: String =
String::from_utf8_lossy(&bytes[..cs]).into_owned()
+ &String::from_utf8_lossy(&bytes[end..]);
*g = new_line;
ZLEMETALL.store(g.len() as i32, Ordering::Relaxed);
}
}
}
return;
}
let ct = ct as usize;
if ZLECS.load(Ordering::SeqCst) >= ZLELL.load(Ordering::SeqCst) {
return;
}
let take_n =
ct.min(ZLELL.load(Ordering::SeqCst) - ZLECS.load(Ordering::SeqCst));
let i = ZLECS.load(Ordering::SeqCst);
ZLELINE.lock().unwrap().drain(i..i + take_n); ZLELL.store(ZLELINE.lock().unwrap().len(), Ordering::SeqCst);
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst); return;
}
let ct = ct as usize;
if ZLECS.load(Ordering::SeqCst) >= ZLELL.load(Ordering::SeqCst) {
return;
}
let take_n =
ct.min(ZLELL.load(Ordering::SeqCst) - ZLECS.load(Ordering::SeqCst));
let i = ZLECS.load(Ordering::SeqCst);
ZLELINE.lock().unwrap().drain(i..i + take_n); ZLELL.store(ZLELINE.lock().unwrap().len(), Ordering::SeqCst);
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst); }
pub fn setline(
s: &str, flags: i32,
) {
let _ = ZSL_COPY; let mut line = ZLELINE.lock().unwrap();
line.clear();
line.extend(s.chars());
let new_len = line.len();
drop(line);
ZLELL.store(new_len, Ordering::SeqCst);
if (flags & ZSL_TOEND) != 0 {
ZLECS.store(new_len, Ordering::SeqCst);
} else if (ZLECS.load(Ordering::SeqCst)) > new_len {
ZLECS.store(new_len, Ordering::SeqCst);
}
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst); }
pub fn findbol() -> usize {
let mut x = ZLECS.load(Ordering::SeqCst); while x > 0 && ZLELINE.lock().unwrap().get(x - 1) != Some(&'\n') {
x -= 1; }
x }
pub fn findeol() -> usize {
let mut x = ZLECS.load(Ordering::SeqCst); while x != ZLELL.load(Ordering::SeqCst)
&& ZLELINE.lock().unwrap().get(x) != Some(&'\n')
{
x += 1; }
x }
#[cfg(test)]
mod tests_hooks {
use super::*;
#[test]
fn call_hook_queues_for_host_dispatch() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
call_hook("zle-line-init", None);
call_hook("zle-keymap-select", Some("vicmd"));
let drained = drain_hooks();
assert_eq!(drained.len(), 2);
assert_eq!(drained[0], ("zle-line-init".to_string(), None));
assert_eq!(
drained[1],
("zle-keymap-select".to_string(), Some("vicmd".to_string()))
);
assert!(drain_hooks().is_empty());
}
#[test]
fn redrawhook_queues_pre_redraw_hook() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
redrawhook();
let drained = drain_hooks();
assert_eq!(drained, vec![("zle-line-pre-redraw".to_string(), None)]);
}
#[test]
fn reexpandprompt_re_runs_expansion_against_raw_templates() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
*RAW_LP.lock().unwrap() = "%% > ".to_string();
*RAW_RP.lock().unwrap() = "[%%]".to_string();
reexpandprompt();
assert_eq!(prompt(), "% > ");
assert_eq!(rprompt(), "[%]");
}
}
#[cfg(test)]
mod tests_bindkey_format {
use super::bindztrdup;
use super::printbind;
use crate::ported::zle::zle_main::zle_test_setup;
#[test]
fn bind_ztrdup_emits_caret_form_for_control_chars() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(bindztrdup(b"\x01"), "^A");
assert_eq!(bindztrdup(b"\x1f"), "^_");
assert_eq!(bindztrdup(b"\x7f"), "^?");
}
#[test]
fn bind_ztrdup_escapes_backslash_and_caret() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(bindztrdup(b"\\"), "\\\\");
assert_eq!(bindztrdup(b"^"), "\\^");
}
#[test]
fn bind_ztrdup_handles_high_bit_as_meta() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(bindztrdup(b"\xC1"), "\\M-A");
}
#[test]
fn printbind_caret_form_matches_describe_key_output() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(printbind(b"\x01"), "^A");
assert_eq!(printbind(b"\x1b"), "^[");
}
}
pub fn findline() -> (usize, usize) {
(findbol(), findeol()) }
pub fn getzlequery() -> i32 {
let c = getfullchar(false);
errflag.fetch_and(!ERRFLAG_INT, Ordering::Relaxed);
let c = match c {
Some('\t') => 'y', Some(ch) if ch.is_control() => 'n', None => 'n', Some(ch) => ch.to_ascii_lowercase(), };
if c == 'y' {
1
} else {
0
}
}
pub fn bindztrdup(str: &[u8]) -> String {
let mut buf = String::new();
for &b in str {
let mut c = b;
if c & 0x80 != 0 {
buf.push('\\');
buf.push('M');
buf.push('-');
c &= 0x7f;
}
if c < 32 || c == 0x7f {
buf.push('^');
c ^= 64;
}
if c == b'\\' || c == b'^' {
buf.push('\\');
}
buf.push(c as char);
}
buf
}
pub fn printbind(seq: &[u8]) -> String {
bindztrdup(seq) }
pub fn showmsg(msg: &str) {
let fd = crate::ported::init::SHTTY.load(Ordering::Relaxed);
let out = if fd >= 0 { fd } else { 2 };
let _ = crate::ported::utils::write_loop(out, msg.as_bytes());
let _ = crate::ported::utils::write_loop(out, b"\n");
}
pub fn handlefeep() -> i32 {
crate::ported::utils::zbeep(); 0 }
pub fn handlesuffix(c: i32) -> i32 {
let _ = c;
0 }
pub fn initundo() {
freeundo();
}
pub fn freeundo() { }
pub fn freechanges() { }
pub fn handleundo() {
setlastline();
}
pub fn mkundoent() {
if LASTLL.load(Ordering::SeqCst)
== ZLELL.load(Ordering::SeqCst)
&& LASTLINE.lock().unwrap()[..LASTLL.load(Ordering::SeqCst)]
== ZLELINE.lock().unwrap()[..ZLELL.load(Ordering::SeqCst)]
{
LASTCS.store(
ZLECS.load(Ordering::SeqCst),
Ordering::SeqCst,
);
return;
}
let sh = LASTLL
.load(Ordering::SeqCst)
.min(ZLELL.load(Ordering::SeqCst));
let mut pre = 0usize;
while pre < sh && ZLELINE.lock().unwrap()[pre] == LASTLINE.lock().unwrap()[pre] {
pre += 1;
}
let mut suf = 0usize;
while suf < sh - pre
&& ZLELINE.lock().unwrap()[ZLELL.load(Ordering::SeqCst) - 1 - suf]
== LASTLINE.lock().unwrap()[LASTLL.load(Ordering::SeqCst) - 1 - suf]
{
suf += 1;
}
let del: Vec<char> = if suf + pre == LASTLL.load(Ordering::SeqCst) {
Vec::new()
} else {
LASTLINE.lock().unwrap()[pre..LASTLL.load(Ordering::SeqCst) - suf]
.to_vec()
};
let ins: Vec<char> = if suf + pre == ZLELL.load(Ordering::SeqCst) {
Vec::new()
} else {
ZLELINE.lock().unwrap()[pre..ZLELL.load(Ordering::SeqCst) - suf].to_vec()
};
UNDO_CHANGENO.fetch_add(1, Ordering::SeqCst);
let del_str: String = del.iter().collect();
let ins_str: String = ins.iter().collect();
let dell = del_str.chars().count() as i32;
let insl = ins_str.chars().count() as i32;
let ch = crate::ported::zle::zle_h::change {
prev: None,
next: None,
flags: 0,
hist: history().lock().unwrap().cursor as i32,
off: pre as i32,
del: del_str,
dell,
ins: ins_str,
insl,
old_cs: LASTCS.load(Ordering::SeqCst) as i32,
new_cs: ZLECS.load(Ordering::SeqCst) as i32,
changeno: UNDO_CHANGENO.load(Ordering::SeqCst) as i64,
};
UNDO_STACK
.lock()
.unwrap()
.truncate(CURCHANGE.load(Ordering::SeqCst));
UNDO_STACK.lock().unwrap().push(ch);
CURCHANGE.store(
UNDO_STACK.lock().unwrap().len(),
Ordering::SeqCst,
);
}
pub fn setlastline() {
let snapshot = ZLELINE.lock().unwrap().clone();
let mut ll = LASTLINE.lock().unwrap();
ll.clear();
ll.extend_from_slice(&snapshot);
drop(ll);
LASTLL.store(
ZLELL.load(Ordering::SeqCst),
Ordering::SeqCst,
);
LASTCS.store(
ZLECS.load(Ordering::SeqCst),
Ordering::SeqCst,
);
}
pub fn undo(args: &[String]) -> i32 {
let last_change: i64 = if !args.is_empty() {
args[0].parse().unwrap_or(-1)
} else {
-1
};
loop {
if CURCHANGE.load(Ordering::SeqCst) == 0 {
return 1;
} let prev_idx = CURCHANGE.load(Ordering::SeqCst) - 1;
let prev_chno = UNDO_STACK.lock().unwrap()[prev_idx].changeno as i64;
if prev_chno <= last_change {
break;
} if (prev_chno as u64) <= UNDO_LIMITNO.load(Ordering::SeqCst)
&& args.is_empty()
{
return 1;
}
if unapplychange(prev_idx as i32) == 0 {
if last_change >= 0 {
unapplychange(prev_idx as i32); CURCHANGE.store(prev_idx, Ordering::SeqCst); }
} else {
CURCHANGE.store(prev_idx, Ordering::SeqCst); }
let has_prev = UNDO_STACK
.lock()
.unwrap()
.get(prev_idx)
.map(|c| c.flags & CH_PREV != 0)
.unwrap_or(false);
if !(last_change >= 0 || has_prev) {
break;
} }
0 }
pub fn unapplychange(ch: i32) -> i32 {
let idx = ch as usize;
if idx >= UNDO_STACK.lock().unwrap().len() {
return 0;
}
let change = UNDO_STACK.lock().unwrap()[idx].clone();
let off = change.off as usize;
let ins_n = change.insl as usize;
if off + ins_n <= ZLELINE.lock().unwrap().len() {
ZLELINE.lock().unwrap().drain(off..off + ins_n); }
for (i, c) in change.del.chars().enumerate() {
if off + i <= ZLELINE.lock().unwrap().len() {
ZLELINE.lock().unwrap().insert(off + i, c);
} else {
ZLELINE.lock().unwrap().push(c);
}
}
ZLECS.store(change.old_cs as usize, Ordering::SeqCst); ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
if change.flags & CH_PREV != 0 {
1
} else {
0
}
}
pub fn redo() -> i32 {
loop {
if CURCHANGE.load(Ordering::SeqCst) >= UNDO_STACK.lock().unwrap().len() {
return 1;
} let cur_idx = CURCHANGE.load(Ordering::SeqCst);
if applychange(cur_idx as i32) == 0 {
break;
} CURCHANGE.store(cur_idx + 1, Ordering::SeqCst);
let has_next = UNDO_STACK
.lock()
.unwrap()
.get(cur_idx)
.map(|c| c.flags & CH_NEXT != 0)
.unwrap_or(false);
if !has_next {
break;
} }
CURCHANGE.fetch_add(1, Ordering::SeqCst); 0 }
pub fn applychange(ch: i32) -> i32 {
let idx = ch as usize;
if idx >= UNDO_STACK.lock().unwrap().len() {
return 0;
}
let change = UNDO_STACK.lock().unwrap()[idx].clone();
let off = change.off as usize;
let del_n = change.dell as usize;
if off + del_n <= ZLELINE.lock().unwrap().len() {
ZLELINE.lock().unwrap().drain(off..off + del_n); }
for (i, c) in change.ins.chars().enumerate() {
if off + i <= ZLELINE.lock().unwrap().len() {
ZLELINE.lock().unwrap().insert(off + i, c);
} else {
ZLELINE.lock().unwrap().push(c);
}
}
ZLECS.store(change.new_cs as usize, Ordering::SeqCst); ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
if change.flags & CH_NEXT != 0 {
1
} else {
0
}
}
pub fn viundochange(
args: &[String],
) -> i32 {
handleundo(); if CURCHANGE.load(Ordering::SeqCst) < UNDO_STACK.lock().unwrap().len() {
while CURCHANGE.load(Ordering::SeqCst) < UNDO_STACK.lock().unwrap().len()
{
let idx = CURCHANGE.load(Ordering::SeqCst);
applychange(idx as i32); CURCHANGE.store(idx + 1, Ordering::SeqCst); }
0 } else {
undo(args) }
}
pub fn splitundo() -> i32 {
if VISTARTCHANGE.load(Ordering::SeqCst) != u64::MAX {
mergeundo(); VISTARTCHANGE.store(
UNDO_CHANGENO.load(Ordering::SeqCst),
Ordering::SeqCst,
); }
handleundo(); 0 }
pub fn mergeundo() {
if CURCHANGE.load(Ordering::SeqCst) == 0 {
return;
}
let mut current = CURCHANGE.load(Ordering::SeqCst) - 1; while current > 0
&& UNDO_STACK.lock().unwrap()[current].changeno
> VISTARTCHANGE.load(Ordering::SeqCst) as i64 + 1
{
UNDO_STACK.lock().unwrap()[current].flags |= CH_PREV; UNDO_STACK.lock().unwrap()[current - 1].flags |= CH_NEXT; current -= 1;
}
VISTARTCHANGE.store(u64::MAX, Ordering::SeqCst); }
pub fn zlecallhook(name: &str, arg: Option<&str>) {
if !crate::ported::zle::zle_thingy::rthingy_nocreate(name) {
return;
}
let saverrflag = errflag.load(Ordering::Relaxed);
let savretflag = RETFLAG.load(Ordering::Relaxed);
let args: Vec<String> = match arg {
Some(a) => vec![a.to_string()],
None => Vec::new(),
};
let _ = execzlefunc(name, &args, 1, 0);
crate::ported::zle::zle_thingy::unrefthingy(name);
let cur_errflag = errflag.load(Ordering::Relaxed);
errflag.store(saverrflag | (cur_errflag & ERRFLAG_INT), Ordering::Relaxed);
RETFLAG.store(savretflag, Ordering::Relaxed); }
pub fn get_undo_current_change() -> i64 {
let remetafy: i32; let zml_active = crate::ported::zle::compcore::ZLEMETALINE
.get()
.and_then(|m| m.lock().ok().map(|s| !s.is_empty()))
.unwrap_or(false);
if zml_active {
remetafy = 1; } else {
remetafy = 0; }
mkundoent(); setlastline();
if remetafy != 0 { }
UNDO_CHANGENO.load(Ordering::SeqCst) as i64 }
pub fn get_undo_limit_change() -> i64 {
UNDO_LIMITNO.load(Ordering::SeqCst) as i64
}
pub fn set_undo_limit_change(value: i64) -> i32 {
let cap = UNDO_CHANGENO.load(Ordering::SeqCst) as i64;
let clamped = value.max(0).min(cap);
UNDO_LIMITNO.store(clamped as u64, Ordering::SeqCst);
0
}
pub fn insert_str(s: &str) {
for c in s.chars() {
ZLELINE
.lock()
.unwrap()
.insert(ZLECS.load(Ordering::SeqCst), c);
ZLECS.fetch_add(1, Ordering::SeqCst);
ZLELL.fetch_add(1, Ordering::SeqCst);
}
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst);
}
pub fn insert_chars(chars: &[char]) {
for &c in chars {
ZLELINE
.lock()
.unwrap()
.insert(ZLECS.load(Ordering::SeqCst), c);
ZLECS.fetch_add(1, Ordering::SeqCst);
ZLELL.fetch_add(1, Ordering::SeqCst);
}
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst);
}
pub fn delete_chars(n: usize) {
let n = n.min(
ZLELL.load(Ordering::SeqCst)
- ZLECS.load(Ordering::SeqCst),
);
for _ in 0..n {
if ZLECS.load(Ordering::SeqCst)
< ZLELL.load(Ordering::SeqCst)
{
ZLELINE
.lock()
.unwrap()
.remove(ZLECS.load(Ordering::SeqCst));
ZLELL.fetch_sub(1, Ordering::SeqCst);
}
}
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst);
}
pub fn backspace_chars(n: usize) {
let n = n.min(ZLECS.load(Ordering::SeqCst));
for _ in 0..n {
if ZLECS.load(Ordering::SeqCst) > 0 {
ZLECS.fetch_sub(1, Ordering::SeqCst);
ZLELINE
.lock()
.unwrap()
.remove(ZLECS.load(Ordering::SeqCst));
ZLELL.fetch_sub(1, Ordering::SeqCst);
}
}
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst);
}
pub fn get_line() -> String {
ZLELINE.lock().unwrap().iter().collect()
}
pub fn set_line_keep_cursor(s: &str) {
*ZLELINE.lock().unwrap() = s.chars().collect();
ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
ZLECS.store(
ZLECS
.load(Ordering::SeqCst)
.min(ZLELL.load(Ordering::SeqCst)),
Ordering::SeqCst,
);
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst);
}
pub fn clear_line() {
ZLELINE.lock().unwrap().clear();
ZLELL.store(0, Ordering::SeqCst);
ZLECS.store(0, Ordering::SeqCst);
MARK.store(0, Ordering::SeqCst);
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst);
}
pub fn get_region() -> Vec<char> {
let (start, end) = if ZLECS.load(Ordering::SeqCst)
< MARK.load(Ordering::SeqCst)
{
(
ZLECS.load(Ordering::SeqCst),
MARK.load(Ordering::SeqCst),
)
} else {
(
MARK.load(Ordering::SeqCst),
ZLECS.load(Ordering::SeqCst),
)
};
ZLELINE.lock().unwrap()[start..end].to_vec()
}
pub fn cut_to_buffer(buf: usize, append: bool) {
if buf < vibuf().lock().unwrap().len() {
let (start, end) = if ZLECS.load(Ordering::SeqCst)
< MARK.load(Ordering::SeqCst)
{
(
ZLECS.load(Ordering::SeqCst),
MARK.load(Ordering::SeqCst),
)
} else {
(
MARK.load(Ordering::SeqCst),
ZLECS.load(Ordering::SeqCst),
)
};
let text: Vec<char> = ZLELINE.lock().unwrap()[start..end].to_vec();
if append {
vibuf().lock().unwrap()[buf].extend(text);
} else {
vibuf().lock().unwrap()[buf] = text;
}
}
}
pub fn paste_from_buffer(buf: usize, after: bool) {
if buf < vibuf().lock().unwrap().len() {
let text = vibuf().lock().unwrap()[buf].clone();
if !text.is_empty() {
if after
&& ZLECS.load(Ordering::SeqCst)
< ZLELL.load(Ordering::SeqCst)
{
ZLECS.fetch_add(1, Ordering::SeqCst);
}
insert_chars(&text);
}
}
}
pub fn set_last_line() {
setlastline();
}
pub fn handle_feep() {
print!("\x07"); }
pub fn get_zle_query() -> bool {
let c = match getfullchar(false) {
Some(c) => c,
None => return false, };
let resolved = if c == '\t' {
'y'
} else if c.is_control() {
'n'
} else {
c.to_ascii_lowercase()
};
if resolved != '\n' {
use std::sync::atomic::Ordering;
let fd = crate::ported::init::SHTTY.load(Ordering::Relaxed);
let out = if fd >= 0 { fd } else { 1 };
let mut buf = [0u8; 4];
let s = resolved.encode_utf8(&mut buf);
let _ = crate::ported::utils::write_loop(out, s.as_bytes());
}
resolved == 'y'
}
#[derive(Debug, Clone, Default)]
pub struct ZlePosition {
pub cs: usize, pub mk: usize, pub ll: usize, pub regions: Vec<crate::ported::zle::zle_refresh::RegionHighlight>,
}
pub static ZLE_POSITIONS: std::sync::Mutex<Vec<ZlePosition>> = std::sync::Mutex::new(Vec::new());
pub fn handle_suffix() {
call_hook("handle-suffix", None);
}
pub fn set_line(s: &str) {
*ZLELINE.lock().unwrap() = s.chars().collect();
ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
ZLECS.store(
ZLELL.load(Ordering::SeqCst),
Ordering::SeqCst,
);
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst);
}
pub fn call_hook(name: &str, arg: Option<&str>) {
PENDING_HOOKS
.lock()
.unwrap()
.push((name.to_string(), arg.map(|s| s.to_string())));
}
pub fn drain_hooks() -> Vec<(String, Option<String>)> {
std::mem::take(&mut *PENDING_HOOKS.lock().unwrap())
}
pub fn unapply_change(idx: usize) -> bool {
if idx >= UNDO_STACK.lock().unwrap().len() {
return false;
}
let (off, dell, insl, old_cs);
let del_vec;
let ins_len;
{
let ch = &UNDO_STACK.lock().unwrap()[idx];
off = ch.off as usize;
dell = ch.dell as usize;
insl = ch.insl as usize;
ins_len = insl;
old_cs = ch.old_cs as usize;
del_vec = ch.del.chars().collect::<Vec<char>>();
}
let _ = ins_len;
ZLECS.store(off, Ordering::SeqCst);
if insl > 0 {
ZLELINE.lock().unwrap().drain(off..off + insl);
}
if dell > 0 {
for (i, c) in del_vec.into_iter().enumerate() {
ZLELINE.lock().unwrap().insert(off + i, c);
}
}
ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
ZLECS.store(
old_cs.min(ZLELL.load(Ordering::SeqCst)),
Ordering::SeqCst,
);
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst);
true
}
pub fn apply_change(idx: usize) -> bool {
if idx >= UNDO_STACK.lock().unwrap().len() {
return false;
}
let (off, dell, insl, new_cs);
let ins_vec;
{
let ch = &UNDO_STACK.lock().unwrap()[idx];
off = ch.off as usize;
dell = ch.dell as usize;
insl = ch.insl as usize;
new_cs = ch.new_cs as usize;
ins_vec = ch.ins.chars().collect::<Vec<char>>();
}
ZLECS.store(off, Ordering::SeqCst);
if dell > 0 {
ZLELINE.lock().unwrap().drain(off..off + dell);
}
if insl > 0 {
for (i, c) in ins_vec.into_iter().enumerate() {
ZLELINE.lock().unwrap().insert(off + i, c);
}
}
ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
ZLECS.store(
new_cs.min(ZLELL.load(Ordering::SeqCst)),
Ordering::SeqCst,
);
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst);
true
}
pub fn undo_widget() -> i32 {
mkundoent();
if CURCHANGE.load(Ordering::SeqCst) == 0 {
return 1;
}
let prev_idx = CURCHANGE.load(Ordering::SeqCst) - 1;
if UNDO_STACK.lock().unwrap()[prev_idx].changeno
<= UNDO_LIMITNO.load(Ordering::SeqCst) as i64
{
return 1;
}
if unapply_change(prev_idx) {
CURCHANGE.store(prev_idx, Ordering::SeqCst);
}
setlastline();
0
}
pub fn redo_widget() -> i32 {
mkundoent();
if CURCHANGE.load(Ordering::SeqCst) >= UNDO_STACK.lock().unwrap().len() {
return 1;
}
if apply_change(CURCHANGE.load(Ordering::SeqCst)) {
CURCHANGE.fetch_add(1, Ordering::SeqCst);
}
setlastline();
0
}
#[cfg(test)]
mod findbol_findeol_tests {
use super::*;
fn zle_with(line: &str, cs: usize) {
zle_reset();
*ZLELINE.lock().unwrap() = line.chars().collect();
ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
ZLECS.store(cs, Ordering::SeqCst);
}
#[test]
fn findbol_no_newline_returns_zero() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let z = zle_with("hello world", 7);
assert_eq!(findbol(), 0);
}
#[test]
fn findbol_finds_preceding_newline() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let z = zle_with("abc\ndef\nghi", 9);
assert_eq!(findbol(), 8);
}
#[test]
fn findbol_at_start_returns_zero() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let z = zle_with("anything", 0);
assert_eq!(findbol(), 0);
}
#[test]
fn findeol_no_newline_returns_end() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let z = zle_with("hello world", 0);
assert_eq!(findeol(), 11);
}
#[test]
fn findeol_finds_next_newline() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let z = zle_with("abc\ndef", 0);
assert_eq!(findeol(), 3);
}
#[test]
fn findeol_at_end_returns_zlell() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let z = zle_with("hello", 5);
assert_eq!(findeol(), 5);
}
#[test]
fn findline_returns_bol_eol_pair() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let z = zle_with("abc\ndef\nghi", 5);
let (bol, eol) = findline();
assert_eq!(bol, 4);
assert_eq!(eol, 7);
}
#[test]
fn shiftchars_common_case_removes_cnt_chars_at_to() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
zle_with("abcdef", 0);
shiftchars(2, 2);
let line: String = ZLELINE.lock().unwrap().iter().collect();
assert_eq!(line, "abef");
assert_eq!(ZLELL.load(Ordering::SeqCst), 4);
}
#[test]
fn shiftchars_to_plus_cnt_equals_zlell_truncates_to_offset() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
zle_with("abcdef", 0);
shiftchars(3, 3);
let line: String = ZLELINE.lock().unwrap().iter().collect();
assert_eq!(
line, "abc",
"c:915 — to+cnt==zlell → zlell=to (truncate to offset 3)"
);
assert_eq!(ZLELL.load(Ordering::SeqCst), 3);
}
#[test]
fn shiftchars_to_plus_cnt_past_zlell_truncates_to_to() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
zle_with("abcdef", 0);
shiftchars(3, 100);
let line: String = ZLELINE.lock().unwrap().iter().collect();
assert_eq!(
line, "abc",
"c:915 — to+cnt>zlell → zlell=to (the previous port silently no-op'd here)"
);
assert_eq!(ZLELL.load(Ordering::SeqCst), 3);
}
#[test]
fn shiftchars_to_past_zlell_clamps_to_len() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
zle_with("abc", 0);
shiftchars(100, 5);
let line: String = ZLELINE.lock().unwrap().iter().collect();
assert_eq!(line, "abc");
let zlell = ZLELL.load(Ordering::SeqCst);
assert!(
zlell <= 3,
"c:915 — Rust clamps zlell to storage len ({zlell})"
);
}
#[test]
fn shiftchars_zero_count_is_noop() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
zle_with("abcdef", 0);
shiftchars(3, 0);
let line: String = ZLELINE.lock().unwrap().iter().collect();
assert_eq!(line, "abcdef", "cnt=0 → no chars removed");
assert_eq!(ZLELL.load(Ordering::SeqCst), 6);
}
#[test]
fn shiftchars_adjusts_mark_per_c851_c854() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
zle_with("0123456789", 0);
MARK.store(8, Ordering::SeqCst);
shiftchars(2, 3);
assert_eq!(
MARK.load(Ordering::SeqCst),
5,
"c:851-852 — mark(8) >= to(2)+cnt(3) → mark -= cnt → 5"
);
zle_with("0123456789", 0);
MARK.store(4, Ordering::SeqCst);
shiftchars(2, 3);
assert_eq!(
MARK.load(Ordering::SeqCst),
2,
"c:853-854 — mark(4) inside (to=2, cnt=3] range → clamp to `to`(2)"
);
zle_with("0123456789", 0);
MARK.store(1, Ordering::SeqCst);
shiftchars(2, 3);
assert_eq!(
MARK.load(Ordering::SeqCst),
1,
"c:851/853 — mark(1) < to(2) → unchanged"
);
}
#[test]
fn spaceinline_inserts_at_cursor_and_grows_zlell() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
zle_with("abc", 1);
spaceinline(2);
let zlell = ZLELL.load(Ordering::SeqCst);
assert_eq!(zlell, 5, "c:842 — zlell += ct (was 3, ct=2 → 5)");
assert_eq!(ZLELINE.lock().unwrap().len(), 5);
assert_eq!(ZLELINE.lock().unwrap()[0], 'a');
assert_eq!(ZLELINE.lock().unwrap()[3], 'b');
assert_eq!(ZLELINE.lock().unwrap()[4], 'c');
}
#[test]
fn spaceinline_zero_or_negative_is_noop() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
zle_with("xyz", 1);
spaceinline(0);
assert_eq!(
ZLELL.load(Ordering::SeqCst),
3,
"ct=0 → zlell unchanged"
);
spaceinline(-5);
assert_eq!(
ZLELL.load(Ordering::SeqCst),
3,
"ct<0 → zlell unchanged"
);
}
#[test]
fn findbol_lands_after_previous_newline_mid_buffer() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
zle_with("abc\ndef", 6);
assert_eq!(
findbol(),
4,
"c:1162 — bol is at offset AFTER the previous newline"
);
}
#[test]
fn setline_with_zsl_toend_moves_cursor_to_end() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
zle_with("xxxxxxxxxx", 5); setline("hi", ZSL_TOEND); let line: String = ZLELINE.lock().unwrap().iter().collect();
assert_eq!(line, "hi");
assert_eq!(ZLELL.load(Ordering::SeqCst), 2);
assert_eq!(
ZLECS.load(Ordering::SeqCst),
2,
"c:1146 — ZSL_TOEND → cursor at zlell"
);
}
#[test]
fn setline_without_zsl_toend_clamps_overshoot_cursor() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
zle_with("xxxxxxxxxxxx", 10);
setline("abc", 0); let line: String = ZLELINE.lock().unwrap().iter().collect();
assert_eq!(line, "abc");
assert_eq!(ZLELL.load(Ordering::SeqCst), 3);
assert_eq!(
ZLECS.load(Ordering::SeqCst),
3,
"c:1148-1149 — cursor past new zlell must clamp to zlell"
);
}
#[test]
fn setline_without_zsl_toend_preserves_in_range_cursor() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
zle_with("abcdef", 2); setline("ABCDEFGH", 0); let line: String = ZLELINE.lock().unwrap().iter().collect();
assert_eq!(line, "ABCDEFGH");
assert_eq!(
ZLECS.load(Ordering::SeqCst),
2,
"c:1148-1149 — in-range cursor stays put when ZSL_TOEND unset"
);
}
#[test]
fn setline_with_zsl_copy_alone_does_not_change_cursor_logic() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
zle_with("abcdefghij", 5);
setline("xyz", ZSL_COPY); assert_eq!(
ZLECS.load(Ordering::SeqCst),
3,
"c:1148-1149 — ZSL_COPY alone doesn't trigger TOEND; clamp still applies"
);
}
#[test]
fn zsl_constants_match_c_source_values() {
let _g = crate::test_util::global_state_lock();
assert_eq!(ZSL_COPY, 1, "Src/Zle/zle.h:406 — ZSL_COPY = 1");
assert_eq!(ZSL_TOEND, 2, "Src/Zle/zle.h:407 — ZSL_TOEND = 2");
assert_eq!(
ZSL_COPY & ZSL_TOEND,
0,
"flag bits must be disjoint for OR-composition"
);
}
#[test]
fn zle_utils_corpus_zlelineasstring_basic() {
let _g = crate::test_util::global_state_lock();
let line: Vec<char> = vec!['a', 'b', 'c'];
assert_eq!(zlelineasstring(&line, 3, 0), "abc");
}
#[test]
fn zle_utils_corpus_zlelineasstring_ll_truncates() {
let _g = crate::test_util::global_state_lock();
let line: Vec<char> = vec!['a', 'b', 'c', 'd', 'e'];
assert_eq!(zlelineasstring(&line, 3, 0), "abc",
"ll=3 takes first 3 chars only");
}
#[test]
fn zle_utils_corpus_zlelineasstring_zero_len_empty() {
let _g = crate::test_util::global_state_lock();
let line: Vec<char> = vec!['a', 'b'];
assert_eq!(zlelineasstring(&line, 0, 0), "");
}
#[test]
fn zle_utils_corpus_zlelineasstring_empty_slice() {
let _g = crate::test_util::global_state_lock();
let line: Vec<char> = vec![];
assert_eq!(zlelineasstring(&line, 0, 0), "");
}
#[test]
fn zle_utils_corpus_stringaszleline_ascii_passthrough() {
let _g = crate::test_util::global_state_lock();
let v = stringaszleline("hello");
let s: String = v.iter().collect();
assert_eq!(s, "hello");
}
#[test]
fn zle_utils_corpus_stringaszleline_empty_returns_empty() {
let _g = crate::test_util::global_state_lock();
assert!(stringaszleline("").is_empty());
}
#[test]
fn zle_utils_corpus_string_round_trip_ascii() {
let _g = crate::test_util::global_state_lock();
let input = "abc123";
let v = stringaszleline(input);
let s = zlelineasstring(&v, v.len(), 0);
assert_eq!(s, input);
}
}