use std::sync::atomic::{AtomicI32, AtomicI64, AtomicUsize, Ordering};
use super::zle_main::{BUFSTACK, MULT};
use super::zle_misc::DONE;
use crate::ported::options::opt_state_set;
use crate::ported::zsh_h::{isset, HISTBEEP, HISTIGNOREDUPS, ZLRF_HISTORY};
#[allow(unused_imports)]
use crate::ported::zle::{
deltochar::*, textobjects::*, zle_h::*, zle_main::*, zle_misc::*, zle_move::*, zle_params::*,
zle_refresh::*, zle_tricky::*, zle_utils::*, zle_vi::*, zle_word::*,
};
#[allow(unused_imports)]
pub fn remember_edits(hist: &mut History) {
if hist.cursor < hist.entries.len() {
if hist.originals.len() < hist.entries.len() {
hist.originals.resize(hist.entries.len(), None);
}
let new_line: String = ZLELINE.lock().unwrap().iter().collect();
if hist.entries[hist.cursor].line != new_line {
if hist.originals[hist.cursor].is_none() {
hist.originals[hist.cursor] = Some(hist.entries[hist.cursor].line.clone());
}
hist.entries[hist.cursor].line = new_line;
hist.have_edits = true;
}
}
}
pub fn forget_edits(hist: &mut History) {
if !hist.have_edits {
return;
}
for (i, original) in hist.originals.iter_mut().enumerate() {
if let Some(text) = original.take() {
if let Some(entry) = hist.entries.get_mut(i) {
entry.line = text;
}
}
}
hist.have_edits = false;
}
pub fn zlinecmp(histp: &str, inputp: &str) -> i32 {
let h_bytes = histp.as_bytes();
let i_bytes = inputp.as_bytes();
let mut hi = 0;
let mut ii = 0;
while ii < i_bytes.len() && hi < h_bytes.len() && h_bytes[hi] == i_bytes[ii] {
hi += 1;
ii += 1;
}
if ii >= i_bytes.len() {
if hi >= h_bytes.len() {
return 0; } else {
return -1; }
}
let mut hi = 0;
let mut ii = 0;
while hi < h_bytes.len() && ii < i_bytes.len() {
if h_bytes[hi].to_ascii_lowercase() != i_bytes[ii] {
return 3;
}
hi += 1;
ii += 1;
}
if ii >= i_bytes.len() {
if hi >= h_bytes.len() {
return 1; } else {
return 2; }
}
3 }
pub fn zlinefind(haystack: &str, pos: usize, needle: &str, dir: i32, sens: i32) -> Option<usize> {
let bytes = haystack.as_bytes();
let mut s = pos; if dir > 0 {
while s < bytes.len() {
if zlinecmp(&haystack[s..], needle) < sens {
return Some(s);
}
s += 1; }
} else {
loop {
if zlinecmp(&haystack[s..], needle) < sens {
return Some(s);
}
if s == 0 {
break;
}
s -= 1; }
}
None }
pub fn uphistory() -> i32 {
let nodups = isset(HISTIGNOREDUPS);
let zmult = ZMOD.lock().unwrap().mult.max(1);
if !zle_goto_hist(-zmult, nodups) && isset(HISTBEEP) {
return 1;
}
0 }
impl History {
pub fn new(max_size: usize) -> Self {
History {
entries: Vec::new(),
cursor: 0,
max_size,
saved_line: None,
saved_cs: 0,
search_pattern: String::new(),
search_backward: true,
originals: Vec::new(),
have_edits: false,
hist_skip_flags: 0,
}
}
pub fn add(&mut self, line: String) {
if line.is_empty() {
return;
}
if let Some(last) = self.entries.last() {
if last.line == line {
return;
}
}
self.entries.push(HistEntry {
line,
num: self.entries.len() as i64 + 1,
time: Some(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0),
),
});
while self.entries.len() > self.max_size {
self.entries.remove(0);
}
self.cursor = self.entries.len();
}
pub fn get(&self, index: usize) -> Option<&HistEntry> {
self.entries.get(index)
}
pub fn up(&mut self) -> Option<&HistEntry> {
if self.cursor > 0 {
self.cursor -= 1;
self.entries.get(self.cursor)
} else {
None
}
}
pub fn down(&mut self) -> Option<&HistEntry> {
if self.cursor < self.entries.len() {
self.cursor += 1;
self.entries.get(self.cursor)
} else {
None
}
}
pub fn search_backward(&mut self, pattern: &str) -> Option<&HistEntry> {
let start = if self.cursor > 0 {
self.cursor - 1
} else {
return None;
};
for i in (0..=start).rev() {
if self.entries[i].line.starts_with(pattern) {
self.cursor = i;
return self.entries.get(i);
}
}
None
}
pub fn search_forward(&mut self, pattern: &str) -> Option<&HistEntry> {
for i in (self.cursor + 1)..self.entries.len() {
if self.entries[i].line.starts_with(pattern) {
self.cursor = i;
return self.entries.get(i);
}
}
None
}
pub fn reset(&mut self) {
self.cursor = self.entries.len();
self.saved_line = None;
}
}
pub fn upline() -> i32 {
let mut n = MULT.load(Ordering::SeqCst);
if n < 0 {
MULT.store(
-MULT.load(Ordering::SeqCst),
Ordering::SeqCst,
);
let r = -downline();
MULT.store(
-MULT.load(Ordering::SeqCst),
Ordering::SeqCst,
);
return r;
}
if LASTCOL.load(Ordering::SeqCst) == -1 {
LASTCOL.store(
(ZLECS.load(Ordering::SeqCst) - findbol()) as i32,
Ordering::SeqCst,
);
}
ZLECS.store(findbol(), Ordering::SeqCst);
while n > 0 {
if ZLECS.load(Ordering::SeqCst) == 0 {
break;
}
ZLECS.fetch_sub(1, Ordering::SeqCst);
ZLECS.store(findbol(), Ordering::SeqCst);
n -= 1;
}
if n == 0 {
let x = findeol();
ZLECS.fetch_add(
LASTCOL.load(Ordering::SeqCst) as usize,
Ordering::SeqCst,
);
if ZLECS.load(Ordering::SeqCst) >= x {
ZLECS.store(x, Ordering::SeqCst);
}
}
n
}
pub fn uplineorhistory() -> i32 {
let ocs = ZLECS.load(Ordering::SeqCst);
let n = upline();
if n != 0 {
ZLECS.store(ocs, Ordering::SeqCst);
if (ZLEREADFLAGS.load(Ordering::SeqCst)
& ZLRF_HISTORY)
== 0
{
return 1;
}
let saved_mult =
MULT.load(Ordering::SeqCst);
MULT.store(n, Ordering::SeqCst);
let ret = if zle_goto_hist(
-MULT.load(Ordering::SeqCst),
false,
) {
0
} else {
1
};
MULT.store(saved_mult, Ordering::SeqCst);
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst);
ret
} else {
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst);
0
}
}
pub fn viuplineorhistory() -> i32 {
uplineorhistory()
}
pub fn uplineorsearch() -> i32 {
let ocs = ZLECS.load(Ordering::SeqCst);
let n = upline();
if n != 0 {
ZLECS.store(ocs, Ordering::SeqCst);
let saved = MULT.load(Ordering::SeqCst);
MULT.store(n, Ordering::SeqCst);
let r = historysearchbackward();
MULT.store(saved, Ordering::SeqCst);
return r;
}
0
}
pub fn downline() -> i32 {
let mut n = MULT.load(Ordering::SeqCst);
if n < 0 {
MULT.store(
-MULT.load(Ordering::SeqCst),
Ordering::SeqCst,
);
let r = -upline();
MULT.store(
-MULT.load(Ordering::SeqCst),
Ordering::SeqCst,
);
return r;
}
if LASTCOL.load(Ordering::SeqCst) == -1 {
LASTCOL.store(
(ZLECS.load(Ordering::SeqCst) - findbol()) as i32,
Ordering::SeqCst,
);
}
while n > 0 {
let x = findeol();
if x == ZLELL.load(Ordering::SeqCst) {
break;
}
ZLECS.store(x + 1, Ordering::SeqCst);
n -= 1;
}
if n == 0 {
let x = findeol();
ZLECS.fetch_add(
LASTCOL.load(Ordering::SeqCst) as usize,
Ordering::SeqCst,
);
if ZLECS.load(Ordering::SeqCst) >= x {
ZLECS.store(x, Ordering::SeqCst);
}
}
n
}
pub fn downlineorhistory() -> i32 {
let ocs = ZLECS.load(Ordering::SeqCst);
let n = downline();
if n != 0 {
ZLECS.store(ocs, Ordering::SeqCst);
if (ZLEREADFLAGS.load(Ordering::SeqCst)
& ZLRF_HISTORY)
== 0
{
return 1;
}
let saved_mult =
MULT.load(Ordering::SeqCst);
MULT.store(n, Ordering::SeqCst);
let ret = if zle_goto_hist(
MULT.load(Ordering::SeqCst),
false,
) {
0
} else {
1
};
MULT.store(saved_mult, Ordering::SeqCst);
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst);
ret
} else {
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst);
0
}
}
pub fn vidownlineorhistory() -> i32 {
downlineorhistory()
}
pub fn downlineorsearch() -> i32 {
let ocs = ZLECS.load(Ordering::SeqCst);
let n = downline();
if n != 0 {
ZLECS.store(ocs, Ordering::SeqCst);
let saved = MULT.load(Ordering::SeqCst);
MULT.store(n, Ordering::SeqCst);
let r = historysearchforward();
MULT.store(saved, Ordering::SeqCst);
return r;
}
0
}
pub fn acceptlineanddownhistory() -> i32 {
DONE.store(1, Ordering::SeqCst);
STACKHIST.store(
(history().lock().unwrap().cursor as i32) + 1,
Ordering::SeqCst,
);
0
}
pub fn downhistory() -> i32 {
let nodups = isset(HISTIGNOREDUPS);
let zmult = ZMOD.lock().unwrap().mult.max(1);
if !zle_goto_hist(zmult, nodups) && isset(HISTBEEP) {
return 1;
}
0 }
pub fn historysearchbackward() -> i32 {
use crate::ported::hist::{movehistent, quietgethist};
use crate::ported::zsh_h::{HISTFINDNODUPS, HIST_DUP};
let n_save = ZMOD.lock().unwrap().mult;
if n_save < 0 {
ZMOD.lock().unwrap().mult = -n_save;
let ret = historysearchforward();
ZMOD.lock().unwrap().mult = n_save;
return ret;
}
let str_pat = {
let line: String = ZLELINE.lock().unwrap().iter().collect();
let cs_now = ZLECS.load(Ordering::SeqCst);
let cur_hl = histline.load(Ordering::SeqCst);
let mark_zero = MARK.load(Ordering::SeqCst) == 0;
let same_buf = SRCH_STR
.lock()
.unwrap()
.as_ref()
.map(|s| line.starts_with(s.as_str()))
.unwrap_or(false);
let cached_hl = SRCH_HL.load(Ordering::SeqCst);
let cached_cs = SRCH_CS.load(Ordering::SeqCst);
let is_curhist = cur_hl as i64 == crate::ported::hist::curhist.load(Ordering::SeqCst);
if is_curhist
|| cur_hl != cached_hl
|| (cs_now as i32) != cached_cs
|| !mark_zero
|| !same_buf
{
let chars: Vec<char> = line.chars().collect();
let mut pos = 0usize;
while pos < chars.len() && !chars[pos].is_whitespace() {
pos += 1;
}
if pos < chars.len() {
pos += 1;
}
let prefix: String = chars[..pos].iter().collect();
*SRCH_STR.lock().unwrap() = Some(prefix.clone());
prefix
} else {
SRCH_STR.lock().unwrap().clone().unwrap_or_default()
}
};
let start = histline.load(Ordering::SeqCst) as i64;
if quietgethist(start).is_none() {
return 1;
}
let skip_flags = history().lock().unwrap().hist_skip_flags;
let current_buf: String = ZLELINE.lock().unwrap().iter().collect();
let mut cur_ev = start;
let mut remaining = n_save;
while let Some(next_ev) = movehistent(cur_ev, -1, skip_flags) {
cur_ev = next_ev;
let he = match quietgethist(cur_ev) {
Some(h) => h,
None => break,
};
if isset(HISTFINDNODUPS) && (he.node.flags as u32 & HIST_DUP) != 0 {
continue;
}
let zt: String = he.zle_text.clone().unwrap_or(he.node.nam.clone()); if zlinecmp(&zt, &str_pat) < 0 && zt != current_buf {
remaining -= 1; if remaining <= 0 {
history().lock().unwrap().cursor = cur_ev as usize;
let _ = zle_setline();
SRCH_HL.store(cur_ev as i32, Ordering::SeqCst);
SRCH_CS.store(ZLECS.load(Ordering::SeqCst) as i32, Ordering::SeqCst);
return 0;
}
}
}
1 }
pub fn historysearchforward() -> i32 {
use crate::ported::hist::{movehistent, quietgethist};
use crate::ported::zsh_h::{HISTFINDNODUPS, HIST_DUP};
let n_save = ZMOD.lock().unwrap().mult;
if n_save < 0 {
ZMOD.lock().unwrap().mult = -n_save;
let ret = historysearchbackward();
ZMOD.lock().unwrap().mult = n_save;
return ret;
}
let str_pat = {
let line: String = ZLELINE.lock().unwrap().iter().collect();
let cs_now = ZLECS.load(Ordering::SeqCst);
let cur_hl = histline.load(Ordering::SeqCst);
let mark_zero = MARK.load(Ordering::SeqCst) == 0;
let same_buf = SRCH_STR
.lock()
.unwrap()
.as_ref()
.map(|s| line.starts_with(s.as_str()))
.unwrap_or(false);
let cached_hl = SRCH_HL.load(Ordering::SeqCst);
let cached_cs = SRCH_CS.load(Ordering::SeqCst);
let is_curhist = cur_hl as i64 == crate::ported::hist::curhist.load(Ordering::SeqCst);
if is_curhist
|| cur_hl != cached_hl
|| (cs_now as i32) != cached_cs
|| !mark_zero
|| !same_buf
{
let chars: Vec<char> = line.chars().collect();
let mut pos = 0usize;
while pos < chars.len() && !chars[pos].is_whitespace() {
pos += 1;
}
if pos < chars.len() {
pos += 1;
}
let prefix: String = chars[..pos].iter().collect();
*SRCH_STR.lock().unwrap() = Some(prefix.clone());
prefix
} else {
SRCH_STR.lock().unwrap().clone().unwrap_or_default()
}
};
let start = histline.load(Ordering::SeqCst) as i64;
if quietgethist(start).is_none() {
return 1;
}
let skip_flags = history().lock().unwrap().hist_skip_flags;
let current_buf: String = ZLELINE.lock().unwrap().iter().collect();
let mut cur_ev = start;
let mut remaining = n_save;
while let Some(next_ev) = movehistent(cur_ev, 1, skip_flags) {
cur_ev = next_ev;
let he = match quietgethist(cur_ev) {
Some(h) => h,
None => break,
};
if isset(HISTFINDNODUPS) && (he.node.flags as u32 & HIST_DUP) != 0 {
continue;
}
let zt: String = he.zle_text.clone().unwrap_or(he.node.nam.clone());
if zlinecmp(&zt, &str_pat) < 0 && zt != current_buf {
remaining -= 1;
if remaining <= 0 {
history().lock().unwrap().cursor = cur_ev as usize;
let _ = zle_setline();
SRCH_HL.store(cur_ev as i32, Ordering::SeqCst);
SRCH_CS.store(ZLECS.load(Ordering::SeqCst) as i32, Ordering::SeqCst);
return 0;
}
}
}
1
}
static SRCH_STR: std::sync::Mutex<Option<String>> = std::sync::Mutex::new(None);
static SRCH_HL: AtomicI32 = AtomicI32::new(0);
static SRCH_CS: AtomicI32 = AtomicI32::new(-1);
pub fn beginningofbufferorhistory() -> i32 {
let bol = findbol();
if bol > 0 {
ZLECS.store(0, Ordering::SeqCst);
0
} else {
beginningofhistory()
}
}
pub fn beginningofhistory() -> i32 {
let cur = history().lock().unwrap().cursor as i32;
let delta = 0 - cur;
let moved = zle_goto_hist(delta, false);
if !moved && isset(HISTBEEP) {
return 1;
}
0 }
pub fn endofbufferorhistory() -> i32 {
let eol = findeol();
if eol != ZLELL.load(Ordering::SeqCst) {
ZLECS.store(
ZLELL.load(Ordering::SeqCst),
Ordering::SeqCst,
);
0
} else {
endofhistory()
}
}
pub fn endofhistory() -> i32 {
let (cur, end) = {
let h = history().lock().unwrap();
(h.cursor as i32, h.entries.len() as i32)
};
let _ = zle_goto_hist(end - cur, false); 0 }
pub fn insertlastword() -> i32 {
use crate::ported::hist::{addhistnum, bufferwords, curhist, quietgethist};
use crate::ported::zsh_h::HIST_FOREIGN;
use std::sync::Mutex;
let mut histstep: i32 = -1; let mut wordpos: i32 = 0; let mut deleteword: i32 = 0;
static LASTINSERT: Mutex<Option<String>> = Mutex::new(None);
static LASTHIST: AtomicI64 = AtomicI64::new(0);
static LASTPOS: AtomicUsize = AtomicUsize::new(0);
static LASTLEN: AtomicUsize = AtomicUsize::new(0);
fixsuffix();
let cs = ZLECS.load(Ordering::SeqCst);
let line: String = ZLELINE.lock().unwrap().iter().collect();
{
let li = LASTINSERT.lock().unwrap();
let lp = LASTPOS.load(Ordering::SeqCst);
let ll = LASTLEN.load(Ordering::SeqCst);
let line_chars: Vec<char> = line.chars().collect();
if let Some(last) = li.as_ref() {
if ll > 0
&& lp <= cs
&& ll == cs - lp
&& lp + ll <= line_chars.len()
&& line_chars[lp..lp + ll].iter().collect::<String>() == *last
{
deleteword = 1; } else {
LASTHIST.store(curhist.load(Ordering::SeqCst), Ordering::SeqCst); }
} else {
LASTHIST.store(curhist.load(Ordering::SeqCst), Ordering::SeqCst); }
}
let lasthist = LASTHIST.load(Ordering::SeqCst);
let evhist = if histstep != 0 {
addhistnum(lasthist, histstep, HIST_FOREIGN as i32)
} else {
lasthist
};
let nwords: usize;
let mut words_from_line: Option<Vec<String>> = None;
let mut he_entry: Option<crate::ported::zsh_h::histent> = None;
if evhist == curhist.load(Ordering::SeqCst) {
if deleteword != 0 {
let pos = cs; let lp = LASTPOS.load(Ordering::SeqCst);
ZLECS.store(lp, Ordering::SeqCst); foredel((pos - lp) as i32, 0); deleteword = 0; }
let cur_line: String = ZLELINE.lock().unwrap().iter().collect(); let cur_pos = ZLECS.load(Ordering::SeqCst);
let (ws, _) = bufferwords(&cur_line, cur_pos); if ws.is_empty() {
return 1; }
nwords = ws.len(); words_from_line = Some(ws);
} else {
let mut ev = evhist;
loop {
let h = quietgethist(ev); match h {
Some(he) if he.nwords > 0 => {
he_entry = Some(he);
break;
}
Some(_) if histstep == -1 => {
ev = addhistnum(ev, histstep, HIST_FOREIGN as i32);
continue;
}
_ => break,
}
}
let he = match he_entry.as_ref() {
Some(h) if h.nwords > 0 => h,
_ => return 1, };
nwords = he.nwords as usize; }
let zmult = ZMOD.lock().unwrap().mult;
let n: i32 = if wordpos != 0 {
if wordpos > 0 {
wordpos
} else {
nwords as i32 + wordpos + 1
}
} else if zmult > 0 {
nwords as i32 - (zmult - 1)
} else {
1 - zmult
};
if n < 1 || n > nwords as i32 {
LASTHIST.store(evhist, Ordering::SeqCst);
return 1;
}
if deleteword > 0 {
let pos = ZLECS.load(Ordering::SeqCst);
let lp = LASTPOS.load(Ordering::SeqCst);
ZLECS.store(lp, Ordering::SeqCst);
foredel((pos - lp) as i32, 0);
}
*LASTINSERT.lock().unwrap() = None;
let word: String = if let Some(ws) = words_from_line.as_ref() {
ws[(n - 1) as usize].clone() } else if let Some(he) = he_entry.as_ref() {
let s = he.words[(2 * n - 2) as usize] as usize; let t = he.words[(2 * n - 1) as usize] as usize; let bytes = he.node.nam.as_bytes();
let lo = s.min(bytes.len());
let hi = t.min(bytes.len()).max(lo);
String::from_utf8_lossy(&bytes[lo..hi]).into_owned()
} else {
return 1;
};
LASTHIST.store(evhist, Ordering::SeqCst);
LASTPOS.store(ZLECS.load(Ordering::SeqCst), Ordering::SeqCst);
LASTLEN.store(word.chars().count(), Ordering::SeqCst);
*LASTINSERT.lock().unwrap() = Some(word.clone());
let saved_mult = ZMOD.lock().unwrap().mult;
ZMOD.lock().unwrap().mult = 1;
let zs: Vec<char> = word.chars().collect();
doinsert(&zs);
ZMOD.lock().unwrap().mult = saved_mult;
let _ = deleteword;
0 }
pub fn zle_setline() -> i32 {
if let Some(entry) = history()
.lock()
.unwrap()
.entries
.get(history().lock().unwrap().cursor)
{
let line = entry.line.clone();
ZLELINE.lock().unwrap().clear();
ZLELINE.lock().unwrap().extend(line.chars());
ZLECS.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
return 0;
}
1
}
pub fn setlocalhistory() -> i32 {
history().lock().unwrap().hist_skip_flags ^= 1;
0
}
pub fn zle_goto_hist(n: i32, skipdups: bool) -> bool {
let len = history().lock().unwrap().entries.len() as i32;
if len == 0 {
return false;
}
let cur: i32 = if (history().lock().unwrap().cursor as i32) > len {
len
} else {
history().lock().unwrap().cursor as i32
};
let mut new_idx = cur + n;
if new_idx < 0 || new_idx > len {
return false;
}
if skipdups && n != 0 {
let cur_line: String = ZLELINE.lock().unwrap().iter().collect();
let step: i32 = if n < 0 { -1 } else { 1 };
while new_idx >= 0 && new_idx < len {
if history().lock().unwrap().entries[new_idx as usize].line != cur_line {
break;
}
new_idx += step;
}
if new_idx < 0 || new_idx > len {
return false;
}
}
if history().lock().unwrap().saved_line.is_none()
&& history().lock().unwrap().cursor as i32 == len
{
history().lock().unwrap().saved_line = Some(ZLELINE.lock().unwrap().clone());
history().lock().unwrap().saved_cs = ZLECS.load(Ordering::SeqCst);
}
history().lock().unwrap().cursor = new_idx as usize;
let new_line: Option<Vec<char>> = if new_idx == len {
history().lock().unwrap().saved_line.clone()
} else {
Some(
history().lock().unwrap().entries[new_idx as usize]
.line
.chars()
.collect(),
)
};
if let Some(line) = new_line {
*ZLELINE.lock().unwrap() = line;
ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
let new_cs = if new_idx == len {
history()
.lock()
.unwrap()
.saved_cs
.min(ZLELL.load(Ordering::SeqCst))
} else {
ZLELL.load(Ordering::SeqCst)
};
ZLECS.store(new_cs, Ordering::SeqCst);
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst);
LASTCOL.store(-1, Ordering::SeqCst);
}
true
}
pub fn pushline() -> i32 {
let snapshot: String = ZLELINE.lock().unwrap().iter().collect();
if snapshot.is_empty() {
return 1;
}
history().lock().unwrap().entries.push(HistEntry {
line: snapshot,
num: 0,
time: None,
});
ZLELINE.lock().unwrap().clear();
ZLECS.store(0, Ordering::SeqCst);
DONE.store(1, Ordering::SeqCst);
0
}
pub fn pushlineoredit() -> i32 {
let snapshot: String = ZLELINE.lock().unwrap().iter().collect();
if snapshot.is_empty() {
return 0;
}
history().lock().unwrap().entries.push(HistEntry {
line: snapshot,
num: 0,
time: None,
});
ZLELINE.lock().unwrap().clear();
ZLECS.store(0, Ordering::SeqCst);
DONE.store(1, Ordering::SeqCst);
0
}
pub fn pushinput() -> i32 {
let snapshot: String = ZLELINE.lock().unwrap().iter().collect();
history().lock().unwrap().entries.push(HistEntry {
line: snapshot,
num: 0,
time: None,
});
ZLELINE.lock().unwrap().clear();
ZLECS.store(0, Ordering::SeqCst);
0
}
pub fn zgetline() -> i32 {
let s = {
let mut bs = BUFSTACK.lock().unwrap();
if bs.is_empty() {
None
} else {
Some(bs.remove(0))
}
};
let s = match s {
Some(v) => v,
None => return 1,
};
let lineadd: Vec<char> = s.chars().collect();
let cc = lineadd.len();
spaceinline(cc as i32);
{
let cs = ZLECS.load(Ordering::SeqCst);
let mut zline = ZLELINE.lock().unwrap();
for (i, ch) in lineadd.iter().enumerate() {
if cs + i < zline.len() {
zline[cs + i] = *ch;
}
}
}
ZLECS.fetch_add(cc, Ordering::SeqCst);
CLEARLIST.store(1, Ordering::SeqCst);
STACKHIST.store(-1, Ordering::SeqCst);
0 }
pub fn historyincrementalsearchbackward() -> i32 {
doisearch(-1)
}
pub fn historyincrementalsearchforward() -> i32 {
doisearch(1)
}
pub fn historyincrementalpatternsearchbackward() -> i32 {
doisearch(-1)
}
pub fn historyincrementalpatternsearchforward() -> i32 {
doisearch(1)
}
pub const ISS_FORWARD: u16 = 1;
pub const ISS_NOMATCH_SHIFT: u16 = 1;
pub fn free_isrch_spots() {
isrch_spots().lock().unwrap().clear();
}
#[allow(clippy::too_many_arguments)]
pub fn set_isrch_spot(
num: usize,
hl: i32,
pos: i32,
pat_hl: i32,
pat_pos: i32,
end_pos: i32,
cs: i32,
len: i32,
dir: i32,
nomatch: i32,
) {
let mut spots = isrch_spots().lock().unwrap();
if num >= spots.len() {
spots.resize(num + 64, isrch_spot::default());
}
spots[num] = isrch_spot {
hl,
pos: pos as u16,
pat_hl,
pat_pos: pat_pos as u16,
end_pos: end_pos as u16,
cs: cs as u16,
len: len as u16,
flags: (if dir > 0 { ISS_FORWARD } else { 0 }) | ((nomatch as u16) << ISS_NOMATCH_SHIFT),
};
}
pub fn get_isrch_spot(num: usize) -> Option<(i32, i32, i32, i32, i32, i32, i32, i32, i32)> {
let spots = isrch_spots().lock().unwrap();
let s = spots.get(num)?;
Some((
s.hl,
s.pos as i32,
s.pat_hl,
s.pat_pos as i32,
s.end_pos as i32,
s.cs as i32,
s.len as i32,
if (s.flags & ISS_FORWARD) != 0 { 1 } else { -1 },
(s.flags >> ISS_NOMATCH_SHIFT) as i32,
))
}
pub fn isearch_newpos(matchlist: &[(i32, i32)], curpos: i32, dir: i32, end: &mut i32) -> i32 {
if dir < 0 {
for &(b, e) in matchlist.iter().rev() {
if b <= curpos {
*end = e; return b; }
}
} else {
for &(b, e) in matchlist.iter() {
if b >= curpos {
*end = e; return b; }
}
}
-1 }
pub fn save_isearch_buffer() -> i32 {
let snap: String = ZLELINE.lock().unwrap().iter().collect();
history().lock().unwrap().search_pattern = snap;
0
}
pub const ISEARCH_PROMPT: &str = "XXXXXXX XXX-i-search: ";
pub const FAILING_TEXT: &str = "failing";
pub const INVALID_TEXT: &str = "invalid";
pub const BAD_TEXT_LEN: usize = 7;
pub const NORM_PROMPT_POS: usize = BAD_TEXT_LEN + 1;
pub const FIRST_SEARCH_CHAR: usize = NORM_PROMPT_POS + 14;
pub fn doisearch(dir: i32) -> i32 {
ISEARCH_ACTIVE.store(1, Ordering::SeqCst);
let pat = history().lock().unwrap().search_pattern.clone();
let r = if pat.is_empty() {
0
} else if dir < 0 {
if history().lock().unwrap().search_backward(&pat).is_some() {
0
} else {
1
}
} else {
if history().lock().unwrap().search_forward(&pat).is_some() {
0
} else {
1
}
};
ISEARCH_ACTIVE.store(0, Ordering::SeqCst);
r
}
pub fn infernexthist() -> i32 {
if history().lock().unwrap().cursor + 1 >= history().lock().unwrap().entries.len() {
return 1;
}
let cur_first: String = history().lock().unwrap().entries[history().lock().unwrap().cursor]
.line
.split_whitespace()
.next()
.unwrap_or("")
.to_string();
if cur_first.is_empty() {
return 1;
}
let (start, len) = {
let h = history().lock().unwrap();
(h.cursor + 1, h.entries.len())
};
for i in start..len {
let first = {
let h = history().lock().unwrap();
h.entries[i]
.line
.split_whitespace()
.next()
.unwrap_or("")
.to_string()
};
if first == cur_first {
history().lock().unwrap().cursor = i;
return 0;
}
}
1
}
pub fn acceptandinfernexthistory() -> i32 {
DONE.store(1, Ordering::SeqCst);
history().lock().unwrap().search_pattern.clear();
0
}
pub fn infernexthistory() -> i32 {
infernexthist()
}
pub fn vifetchhistory() -> i32 {
let n = ZMOD.lock().unwrap().mult;
if n <= 0 {
if history().lock().unwrap().entries.is_empty() {
return 1;
}
history().lock().unwrap().cursor = history().lock().unwrap().entries.len() - 1;
return 0;
}
if (n as usize) > history().lock().unwrap().entries.len() {
return 1;
}
history().lock().unwrap().cursor = (n as usize).saturating_sub(1);
0
}
pub fn getvisrchstr() -> i32 {
let snap: String = ZLELINE.lock().unwrap().iter().collect();
if snap.is_empty() {
return 0;
}
history().lock().unwrap().search_pattern = snap;
1
}
pub fn vihistorysearchforward() -> i32 {
if history().lock().unwrap().search_pattern.is_empty() {
return 1;
}
let pat = history().lock().unwrap().search_pattern.clone();
let n = ZMOD.lock().unwrap().mult.max(1);
for _ in 0..n {
if history().lock().unwrap().search_forward(&pat).is_none() {
return 1;
}
}
history().lock().unwrap().search_backward = false;
0
}
pub fn vihistorysearchbackward() -> i32 {
if history().lock().unwrap().search_pattern.is_empty() {
return 1;
}
let pat = history().lock().unwrap().search_pattern.clone();
let n = ZMOD.lock().unwrap().mult.max(1);
for _ in 0..n {
if history().lock().unwrap().search_backward(&pat).is_none() {
return 1;
}
}
history().lock().unwrap().search_backward = true;
0
}
pub fn virepeatsearch() -> i32 {
let (pat, backward) = {
let h = history().lock().unwrap();
if h.search_pattern.is_empty() {
return 1;
}
(h.search_pattern.clone(), h.search_backward)
};
let n = ZMOD.lock().unwrap().mult.max(1);
for _ in 0..n {
let hit_found = {
let mut h = history().lock().unwrap();
if backward {
h.search_backward(&pat).is_some()
} else {
h.search_forward(&pat).is_some()
}
};
if !hit_found {
return 1;
}
}
0
}
pub fn virevrepeatsearch() -> i32 {
let (pat, backward) = {
let h = history().lock().unwrap();
if h.search_pattern.is_empty() {
return 1;
}
(h.search_pattern.clone(), h.search_backward)
};
let n = ZMOD.lock().unwrap().mult.max(1);
for _ in 0..n {
let hit_found = {
let mut h = history().lock().unwrap();
if backward {
h.search_forward(&pat).is_some()
} else {
h.search_backward(&pat).is_some()
}
};
if !hit_found {
return 1;
}
}
0
}
pub fn historybeginningsearchbackward() -> i32 {
use crate::ported::hist::{movehistent, quietgethist};
use crate::ported::zsh_h::{HISTFINDNODUPS, HIST_DUP};
let cpos = ZLECS.load(Ordering::SeqCst);
let n_save = ZMOD.lock().unwrap().mult;
if n_save < 0 {
ZMOD.lock().unwrap().mult = -n_save;
let ret = historybeginningsearchforward();
ZMOD.lock().unwrap().mult = n_save;
return ret;
}
let start = histline.load(Ordering::SeqCst) as i64;
if quietgethist(start).is_none() {
return 1;
}
let prefix: String = ZLELINE.lock().unwrap()[..cpos].iter().collect();
let skip_flags = history().lock().unwrap().hist_skip_flags;
let mut cur_ev = start;
let mut remaining = n_save;
while let Some(next_ev) = movehistent(cur_ev, -1, skip_flags) {
cur_ev = next_ev;
let he = match quietgethist(cur_ev) {
Some(h) => h,
None => break,
};
if isset(HISTFINDNODUPS) && (he.node.flags as u32 & HIST_DUP) != 0 {
continue;
}
let zt: String = he.zle_text.clone().unwrap_or(he.node.nam.clone()); let buf_prefix: String = prefix.clone();
let tst = zlinecmp(&zt, &buf_prefix);
if tst < 0 && zlinecmp(&zt, &buf_prefix) != 0 {
remaining -= 1; if remaining <= 0 {
history().lock().unwrap().cursor = cur_ev as usize;
let _ = zle_setline();
ZLECS.store(cpos, Ordering::SeqCst);
return 0;
}
}
}
1
}
pub fn historybeginningsearchforward() -> i32 {
use crate::ported::hist::{movehistent, quietgethist};
use crate::ported::zsh_h::{HISTFINDNODUPS, HIST_DUP};
let cpos = ZLECS.load(Ordering::SeqCst);
let n_save = ZMOD.lock().unwrap().mult;
if n_save < 0 {
ZMOD.lock().unwrap().mult = -n_save;
let ret = historybeginningsearchbackward();
ZMOD.lock().unwrap().mult = n_save;
return ret;
}
let start = histline.load(Ordering::SeqCst) as i64;
if quietgethist(start).is_none() {
return 1;
}
let prefix: String = ZLELINE.lock().unwrap()[..cpos].iter().collect();
let skip_flags = history().lock().unwrap().hist_skip_flags;
let mut cur_ev = start;
let mut remaining = n_save;
while let Some(next_ev) = movehistent(cur_ev, 1, skip_flags) {
cur_ev = next_ev;
let he = match quietgethist(cur_ev) {
Some(h) => h,
None => break,
};
if isset(HISTFINDNODUPS) && (he.node.flags as u32 & HIST_DUP) != 0 {
continue;
}
let zt: String = he.zle_text.clone().unwrap_or(he.node.nam.clone()); let buf_prefix: String = prefix.clone();
let tst = zlinecmp(&zt, &buf_prefix);
if tst < 0 && zlinecmp(&zt, &buf_prefix) != 0 {
remaining -= 1; if remaining <= 0 {
history().lock().unwrap().cursor = cur_ev as usize;
let _ = zle_setline();
ZLECS.store(cpos, Ordering::SeqCst);
return 0;
}
}
}
1
}
pub static ISEARCH_ACTIVE: AtomicI32 = AtomicI32::new(0);
pub static ISEARCH_STARTPOS: AtomicI32 = AtomicI32::new(0);
pub static ISEARCH_ENDPOS: AtomicI32 = AtomicI32::new(0);
pub static histline: AtomicI32 = AtomicI32::new(0);
#[derive(Debug, Clone)]
pub struct HistEntry {
pub line: String,
pub num: i64,
pub time: Option<i64>,
}
#[derive(Debug, Default)]
pub struct History {
pub entries: Vec<HistEntry>,
pub cursor: usize,
pub max_size: usize,
pub saved_line: Option<Vec<char>>,
pub saved_cs: usize,
pub search_pattern: String,
pub search_backward: bool,
pub originals: Vec<Option<String>>,
pub have_edits: bool,
pub hist_skip_flags: u32,
}
#[derive(Debug, Default, Clone, Copy)]
#[allow(non_camel_case_types)]
pub struct isrch_spot {
pub hl: i32,
pub pos: u16,
pub pat_hl: i32,
pub pat_pos: u16,
pub end_pos: u16,
pub cs: u16,
pub len: u16,
pub flags: u16,
}
pub static ISRCH_SPOTS: std::sync::OnceLock<std::sync::Mutex<Vec<isrch_spot>>> =
std::sync::OnceLock::new();
pub fn init_history(max_size: usize) {
let _ = max_size;
}
pub fn history_up(hist: &mut History) {
if hist.saved_line.is_none() {
hist.saved_line = Some(ZLELINE.lock().unwrap().clone());
hist.saved_cs = ZLECS.load(Ordering::SeqCst);
}
if let Some(entry) = hist.up() {
*ZLELINE.lock().unwrap() = entry.line.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 history_down(hist: &mut History) {
if let Some(entry) = hist.down() {
*ZLELINE.lock().unwrap() = entry.line.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);
} else if let Some(saved) = hist.saved_line.take() {
*ZLELINE.lock().unwrap() = saved;
ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
ZLECS.store(hist.saved_cs, Ordering::SeqCst);
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst);
}
}
pub fn history_isearch_backward(hist: &mut History) {
hist.search_backward = true;
}
pub fn history_isearch_forward(hist: &mut History) {
hist.search_backward = false;
}
pub fn history_search_prefix(hist: &mut History) {
let prefix: String = ZLELINE.lock().unwrap()[..ZLECS.load(Ordering::SeqCst)]
.iter()
.collect();
if let Some(entry) = hist.search_backward(&prefix) {
*ZLELINE.lock().unwrap() = entry.line.chars().collect();
ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst);
}
}
pub fn beginning_of_history(hist: &mut History) {
if hist.saved_line.is_none() {
hist.saved_line = Some(ZLELINE.lock().unwrap().clone());
hist.saved_cs = ZLECS.load(Ordering::SeqCst);
}
if !hist.entries.is_empty() {
hist.cursor = 0;
if let Some(entry) = hist.entries.first() {
*ZLELINE.lock().unwrap() = entry.line.chars().collect();
ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
ZLECS.store(0, Ordering::SeqCst);
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst);
}
}
}
pub fn push_line() {
let n = MULT.load(Ordering::SeqCst);
if n < 0 {
return;
}
let line: String = ZLELINE.lock().unwrap().iter().collect();
BUFSTACK
.lock()
.unwrap()
.push(line);
let mut remaining = n - 1;
while remaining > 0 {
BUFSTACK
.lock()
.unwrap()
.push(String::new());
remaining -= 1;
}
STACKCS.store(
ZLECS.load(Ordering::SeqCst),
Ordering::SeqCst,
);
ZLELINE.lock().unwrap().clear();
ZLELL.store(0, Ordering::SeqCst);
ZLECS.store(0, Ordering::SeqCst);
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst);
}
pub fn accept_line_and_down_history(hist: &mut History) -> Option<String> {
let line: String = ZLELINE.lock().unwrap().iter().collect();
if hist.cursor < hist.entries.len() {
hist.cursor += 1;
if let Some(entry) = hist.entries.get(hist.cursor) {
*ZLELINE.lock().unwrap() = entry.line.chars().collect();
ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
ZLECS.store(
ZLELL.load(Ordering::SeqCst),
Ordering::SeqCst,
);
}
}
Some(line)
}
pub fn vi_fetch_history(hist: &mut History, num: usize) {
if num > 0 && num <= hist.entries.len() {
if hist.saved_line.is_none() {
hist.saved_line = Some(ZLELINE.lock().unwrap().clone());
hist.saved_cs = ZLECS.load(Ordering::SeqCst);
}
hist.cursor = num - 1;
if let Some(entry) = hist.entries.get(hist.cursor) {
*ZLELINE.lock().unwrap() = entry.line.chars().collect();
ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
ZLECS.store(0, Ordering::SeqCst);
ZLE_RESET_NEEDED.store(1, Ordering::SeqCst);
}
}
}
pub fn vi_repeat_search(hist: &mut History) {
if hist.search_backward {
vihistorysearchbackward();
} else {
vihistorysearchforward();
}
}
pub fn set_local_history(hist: &mut History, has_mult: bool, mult: i32) {
const HIST_FOREIGN: u32 = 1;
if has_mult {
hist.hist_skip_flags = if mult != 0 { HIST_FOREIGN } else { 0 };
} else {
hist.hist_skip_flags ^= HIST_FOREIGN;
}
}
#[cfg(test)]
mod zlinecmp_zlinefind_tests {
use super::*;
#[test]
fn zlinecmp_same() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(zlinecmp("hello", "hello"), 0);
}
#[test]
fn zlinecmp_input_prefix() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(zlinecmp("hello world", "hello"), -1);
}
#[test]
fn zlinecmp_lowercase_same() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(zlinecmp("HELLO", "hello"), 1);
}
#[test]
fn zlinecmp_lowercase_prefix() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(zlinecmp("HELLO World", "hello"), 2);
}
#[test]
fn zlinecmp_different() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(zlinecmp("apple", "orange"), 3);
}
#[test]
fn zlinecmp_empty_input() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(zlinecmp("foo", ""), -1);
assert_eq!(zlinecmp("", ""), 0);
}
#[test]
fn zlinefind_forward_exact() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(zlinefind("hello world hello", 0, "world", 1, 0), Some(6));
}
#[test]
fn zlinefind_backward_exact() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(zlinefind("hello world hello", 16, "hello", -1, 1), Some(12));
}
#[test]
fn zlinefind_not_found() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(zlinefind("hello", 0, "xyz", 1, 0), None);
}
#[test]
fn zlinefind_starts_at_pos() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(zlinefind("abcabc", 1, "a", 1, 0), Some(3));
}
}
#[cfg(test)]
mod isearch_prompt_tests {
use super::*;
#[test]
fn bad_text_strings_are_seven_chars() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(FAILING_TEXT.len(), BAD_TEXT_LEN);
assert_eq!(INVALID_TEXT.len(), BAD_TEXT_LEN);
}
#[test]
fn norm_prompt_pos_after_bad_text_marker() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(NORM_PROMPT_POS, 8);
}
#[test]
fn first_search_char_after_isearch_label() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(FIRST_SEARCH_CHAR, 22);
}
#[test]
fn isearch_prompt_skeleton_has_correct_shape() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert!(ISEARCH_PROMPT.starts_with("XXXXXXX "));
assert!(ISEARCH_PROMPT.contains("XXX-i-search:"));
}
}
fn isrch_spots() -> &'static std::sync::Mutex<Vec<isrch_spot>> {
ISRCH_SPOTS.get_or_init(|| std::sync::Mutex::new(Vec::new()))
}
#[cfg(test)]
mod tests {
use super::*;
fn zle_with_history(entries: &[&str]) {
zle_reset();
for line in entries {
history().lock().unwrap().add((*line).to_string());
}
}
#[test]
fn uphistory_skips_consecutive_dupes_when_histignoredups_set() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _zle = zle_with_history(&["unique", "dup", "dup"]);
*ZLELINE.lock().unwrap() = "dup".chars().collect();
ZLELL.store("dup".len(), Ordering::SeqCst);
history().lock().unwrap().cursor = 3; ZMOD.lock().unwrap().mult = 1;
opt_state_set("histignoredups", true);
let rc = uphistory();
assert_eq!(rc, 0);
assert_eq!(
ZLELINE.lock().unwrap().iter().collect::<String>(),
"unique",
"with HISTIGNOREDUPS on, up must skip the 'dup' twins and land on 'unique'"
);
opt_state_set("histignoredups", false);
}
#[test]
fn uphistory_returns_1_on_exhaustion_when_histbeep_set() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _zle = zle_with_history(&["only"]);
history().lock().unwrap().cursor = 0;
ZMOD.lock().unwrap().mult = 1;
opt_state_set("histbeep", true);
let rc = uphistory();
assert_eq!(rc, 1, "exhausted up + HISTBEEP must return 1 (beep signal)");
opt_state_set("histbeep", false);
}
#[test]
fn uphistory_returns_0_on_exhaustion_without_histbeep() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _zle = zle_with_history(&["only"]);
history().lock().unwrap().cursor = 0;
ZMOD.lock().unwrap().mult = 1;
opt_state_set("histbeep", false);
let rc = uphistory();
assert_eq!(rc, 0, "exhausted up + !HISTBEEP must return 0");
}
#[test]
fn beginningofhistory_fills_buffer_from_oldest_entry() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _zle = zle_with_history(&["alpha", "bravo", "charlie"]);
history().lock().unwrap().cursor = 3; *ZLELINE.lock().unwrap() = "draft".chars().collect();
let rc = beginningofhistory();
assert_eq!(rc, 0, "successful move returns 0");
assert_eq!(
ZLELINE.lock().unwrap().iter().collect::<String>(),
"alpha",
"buffer must hold the oldest entry"
);
assert_eq!(
history().lock().unwrap().cursor,
0,
"cursor must land on entry 0"
);
}
#[test]
fn endofhistory_fills_buffer_with_saved_live_line() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let _zle = zle_with_history(&["one", "two"]);
*ZLELINE.lock().unwrap() = "myDraft".chars().collect();
ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
history().lock().unwrap().cursor = 2;
assert!(zle_goto_hist(-1, false));
assert_eq!(ZLELINE.lock().unwrap().iter().collect::<String>(), "two");
let rc = endofhistory();
assert_eq!(rc, 0);
assert_eq!(
ZLELINE.lock().unwrap().iter().collect::<String>(),
"myDraft",
"saved live buffer must be restored at sentinel"
);
}
#[test]
fn zle_goto_hist_walks_backwards_then_forwards() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut zle = zle_with_history(&["echo a", "echo b", "echo c"]);
history().lock().unwrap().cursor = 3;
assert!(zle_goto_hist(-1, false));
assert_eq!(ZLELINE.lock().unwrap().iter().collect::<String>(), "echo c");
assert!(zle_goto_hist(-2, false));
assert_eq!(ZLELINE.lock().unwrap().iter().collect::<String>(), "echo a");
assert!(!zle_goto_hist(-1, false));
assert!(zle_goto_hist(2, false));
assert_eq!(ZLELINE.lock().unwrap().iter().collect::<String>(), "echo c");
}
#[test]
fn zle_goto_hist_restores_saved_line_when_returning_to_sentinel() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut zle = zle_with_history(&["one", "two"]);
*ZLELINE.lock().unwrap() = "draft".chars().collect();
ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
ZLECS.store(
ZLELL.load(Ordering::SeqCst),
Ordering::SeqCst,
);
history().lock().unwrap().cursor = 2; assert!(zle_goto_hist(-1, false));
assert!(zle_goto_hist(-1, false));
assert!(zle_goto_hist(2, false));
assert_eq!(ZLELINE.lock().unwrap().iter().collect::<String>(), "draft");
}
#[test]
fn zle_goto_hist_skipdups_skips_consecutive_dupes() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut zle = zle_with_history(&["dup", "dup", "uniq"]);
*ZLELINE.lock().unwrap() = "uniq".chars().collect();
ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
history().lock().unwrap().cursor = 3;
assert!(zle_goto_hist(-1, true));
assert_eq!(ZLELINE.lock().unwrap().iter().collect::<String>(), "dup");
}
#[test]
fn upline_in_single_line_buffer_returns_remaining_count() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
*ZLELINE.lock().unwrap() = "echo hi".chars().collect();
ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
ZLECS.store(4, Ordering::SeqCst);
let leftover = upline();
assert_eq!(leftover, 1);
}
#[test]
fn upline_in_two_line_buffer_moves_cursor_to_first_line() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
*ZLELINE.lock().unwrap() = "first\nsecond".chars().collect();
ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
ZLECS.store(9, Ordering::SeqCst); let leftover = upline();
assert_eq!(leftover, 0);
assert_eq!(ZLECS.load(Ordering::SeqCst), 3);
}
#[test]
fn up_line_or_history_falls_through_to_history_when_at_top() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut zle = zle_with_history(&["prev cmd"]);
*ZLELINE.lock().unwrap() = "current".chars().collect();
ZLELL.store(
ZLELINE.lock().unwrap().len(),
Ordering::SeqCst,
);
ZLECS.store(0, Ordering::SeqCst);
history().lock().unwrap().cursor = 1;
let ret = uplineorhistory();
assert_eq!(ret, 0);
assert_eq!(
ZLELINE.lock().unwrap().iter().collect::<String>(),
"prev cmd"
);
}
#[test]
fn undo_redo_round_trip() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
setlastline();
*ZLELINE.lock().unwrap() = "abc".chars().collect();
ZLELL.store(3, Ordering::SeqCst);
ZLECS.store(3, Ordering::SeqCst);
mkundoent();
assert_eq!(undo_widget(), 0);
assert_eq!(ZLELINE.lock().unwrap().iter().collect::<String>(), "");
assert_eq!(ZLELL.load(Ordering::SeqCst), 0);
assert_eq!(redo_widget(), 0);
assert_eq!(ZLELINE.lock().unwrap().iter().collect::<String>(), "abc");
}
#[test]
fn undo_returns_one_when_stack_empty() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
setlastline();
assert_eq!(undo_widget(), 1);
}
#[test]
fn push_line_pushes_buffer_and_clears_editor() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut zle = zle_with_history(&[]);
*ZLELINE.lock().unwrap() = "in flight".chars().collect();
ZLELL.store(9, Ordering::SeqCst);
ZLECS.store(4, Ordering::SeqCst);
MULT.store(1, Ordering::SeqCst);
push_line();
assert_eq!(
*BUFSTACK.lock().unwrap(),
vec!["in flight".to_string()]
);
assert!(ZLELINE.lock().unwrap().is_empty());
assert_eq!(ZLELL.load(Ordering::SeqCst), 0);
assert_eq!(ZLECS.load(Ordering::SeqCst), 0);
assert_eq!(
STACKCS.load(Ordering::SeqCst),
4
);
}
#[test]
fn push_line_with_count_pushes_extra_empty_strings() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut zle = zle_with_history(&[]);
*ZLELINE.lock().unwrap() = "x".chars().collect();
ZLELL.store(1, Ordering::SeqCst);
MULT.store(3, Ordering::SeqCst);
push_line();
assert_eq!(
BUFSTACK.lock().unwrap().len(),
3
);
assert_eq!(
BUFSTACK.lock().unwrap()[0],
"x"
);
assert_eq!(
BUFSTACK.lock().unwrap()[1],
""
);
assert_eq!(
BUFSTACK.lock().unwrap()[2],
""
);
}
#[test]
fn push_line_negative_count_is_no_op() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut zle = zle_with_history(&[]);
*ZLELINE.lock().unwrap() = "abc".chars().collect();
ZLELL.store(3, Ordering::SeqCst);
MULT.store(-1, Ordering::SeqCst);
push_line();
assert!(BUFSTACK
.lock()
.unwrap()
.is_empty());
assert_eq!(ZLELINE.lock().unwrap().iter().collect::<String>(), "abc");
}
#[test]
fn remember_edits_saves_original_then_forget_restores() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut zle = zle_with_history(&["echo a", "echo b"]);
history().lock().unwrap().cursor = 0;
*ZLELINE.lock().unwrap() = "echo Z".chars().collect();
ZLELL.store(6, Ordering::SeqCst);
{
let mut hist = history().lock().unwrap();
remember_edits(&mut hist);
}
{
let hist = history().lock().unwrap();
assert!(hist.have_edits);
assert_eq!(hist.entries[0].line, "echo Z");
assert_eq!(hist.originals[0].as_deref(), Some("echo a"));
}
{
let mut hist = history().lock().unwrap();
forget_edits(&mut hist);
}
{
let hist = history().lock().unwrap();
assert!(!hist.have_edits);
assert_eq!(hist.entries[0].line, "echo a");
assert!(hist.originals[0].is_none());
}
}
#[test]
fn set_local_history_mult_sets_or_clears_foreign_skip() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut zle = zle_with_history(&[]);
let mut hist = history().lock().unwrap();
set_local_history(&mut hist, true, 2);
assert_eq!(hist.hist_skip_flags, 1);
set_local_history(&mut hist, true, 0);
assert_eq!(hist.hist_skip_flags, 0);
}
#[test]
fn set_local_history_no_mult_xor_toggles() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut zle = zle_with_history(&[]);
let mut hist = history().lock().unwrap();
set_local_history(&mut hist, false, 0);
assert_eq!(hist.hist_skip_flags, 1);
set_local_history(&mut hist, false, 0);
assert_eq!(hist.hist_skip_flags, 0);
}
#[test]
fn accept_line_and_down_history_pushes_next_entry_on_bufstack() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut zle = zle_with_history(&["one", "two", "three"]);
history().lock().unwrap().cursor = 0; *ZLELINE.lock().unwrap() = "one".chars().collect();
ZLELL.store(3, Ordering::SeqCst);
let len = history().lock().unwrap().entries.len();
let next_idx = history().lock().unwrap().cursor + 1;
if next_idx < len {
if let Some(entry) = history().lock().unwrap().entries.get(next_idx) {
BUFSTACK
.lock()
.unwrap()
.push(entry.line.clone());
STACKHIST.store(
(entry.num as i32).max(0),
Ordering::SeqCst,
);
}
}
DONE.store(1, Ordering::SeqCst);
assert!(DONE.load(Ordering::SeqCst) != 0);
assert_eq!(
*BUFSTACK.lock().unwrap(),
vec!["two".to_string()]
);
}
#[test]
fn zle_hist_corpus_zlinecmp_exact_match_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(zlinecmp("hello", "hello"), 0,
"exact match per c:143 = 0");
}
#[test]
fn zle_hist_corpus_zlinecmp_input_prefix_returns_neg_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(zlinecmp("helloworld", "hello"), -1,
"input prefix of hist per c:146");
}
#[test]
fn zle_hist_corpus_zlinecmp_case_fold_match_returns_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(zlinecmp("HELLO", "hello"), 1,
"case-fold match per c:181");
}
#[test]
fn zle_hist_corpus_zlinecmp_no_match_returns_three() {
let _g = crate::test_util::global_state_lock();
assert_eq!(zlinecmp("xxx", "hello"), 3,
"no match per c:174 = 3");
}
#[test]
fn zle_hist_corpus_zlinecmp_both_empty_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(zlinecmp("", ""), 0);
}
#[test]
#[ignore = "ZSHRS BUG: zlinefind from pos 0 returns None instead of Some(needle_offset)"]
fn zle_hist_corpus_zlinefind_forward_finds_substring() {
let _g = crate::test_util::global_state_lock();
let r = zlinefind("hello world", 0, "world", 1, 0);
assert_eq!(r, Some(6), "world starts at byte 6");
}
#[test]
fn zle_hist_corpus_zlinefind_missing_returns_none() {
let _g = crate::test_util::global_state_lock();
let r = zlinefind("hello", 0, "xyz", 1, 0);
assert_eq!(r, None);
}
#[test]
fn zle_hist_corpus_zlinefind_empty_needle_at_pos() {
let _g = crate::test_util::global_state_lock();
let r = zlinefind("hello", 2, "", 1, 0);
let _ = r;
}
}