use super::zle_h::{REFRESH_ELEMENT, REFRESH_STRING};
use crate::ported::init::{tclen, SHTTY};
use crate::ported::utils::{adjustcolumns, adjustlines, write_loop};
#[allow(unused_imports)]
use crate::ported::zle::{
deltochar::*, textobjects::*, zle_hist::*, zle_main::*, zle_misc::*, zle_move::*,
zle_params::*, zle_tricky::*, zle_utils::*, zle_vi::*, zle_word::*,
};
use crate::ported::zsh_h::{
isset, COMBININGCHARS, TCCLEAREOL, TCDEL, TCINS, TXT_ERROR, TXT_MULTIWORD_MASK,
};
use std::fmt::Write;
use std::io;
use std::sync::atomic::Ordering;
#[allow(non_snake_case)]
pub fn ZR_memset(
dst: &mut [REFRESH_ELEMENT],
rc: REFRESH_ELEMENT,
len: usize,
) {
let n = len.min(dst.len());
for slot in dst.iter_mut().take(n) {
*slot = rc;
}
}
impl TextAttr {
pub fn to_ansi(&self) -> String {
let mut codes = Vec::new();
if self.bold {
codes.push("1".to_string());
}
if self.underline {
codes.push("4".to_string());
}
if self.standout {
codes.push("7".to_string());
}
if self.blink {
codes.push("5".to_string());
}
if let Some(fg) = self.fg_color {
codes.push(format!("38;5;{}", fg));
}
if let Some(bg) = self.bg_color {
codes.push(format!("48;5;{}", bg));
}
if codes.is_empty() {
String::new()
} else {
format!("\x1b[{}m", codes.join(";"))
}
}
}
pub const ZWC_WEOF: char = '\u{FFFF}';
#[allow(non_snake_case)]
pub fn ZR_strcpy(
dst: &mut [REFRESH_ELEMENT],
src: &[REFRESH_ELEMENT],
) {
let n = src.iter().take_while(|e| e.chr != '\0').count() + 1; let n = n.min(src.len()).min(dst.len());
dst[..n].copy_from_slice(&src[..n]);
}
impl RefreshElement {
pub fn new(chr: char) -> Self {
let width = unicode_width::UnicodeWidthChar::width(chr).unwrap_or(1) as u8;
RefreshElement {
chr,
atr: TextAttr::default(),
width,
}
}
pub fn with_attr(chr: char, atr: TextAttr) -> Self {
let width = unicode_width::UnicodeWidthChar::width(chr).unwrap_or(1) as u8;
RefreshElement { chr, atr, width }
}
}
#[allow(non_snake_case)]
pub fn ZR_strlen(wstr: &[REFRESH_ELEMENT]) -> usize {
let mut len = 0; while len < wstr.len() && wstr[len].chr != '\0' {
len += 1; }
len }
impl VideoBuffer {
pub fn new(cols: usize, rows: usize) -> Self {
let lines = vec![vec![RefreshElement::new(' '); cols]; rows];
VideoBuffer { lines, cols, rows }
}
pub fn clear(&mut self) {
for line in &mut self.lines {
for elem in line.iter_mut() {
*elem = RefreshElement::new(' ');
}
}
}
pub fn resize(&mut self, cols: usize, rows: usize) {
self.cols = cols;
self.rows = rows;
self.lines
.resize(rows, vec![RefreshElement::new(' '); cols]);
for line in &mut self.lines {
line.resize(cols, RefreshElement::new(' '));
}
}
pub fn set(&mut self, row: usize, col: usize, elem: RefreshElement) {
if row < self.rows && col < self.cols {
self.lines[row][col] = elem;
}
}
pub fn get(&self, row: usize, col: usize) -> Option<&RefreshElement> {
self.lines.get(row).and_then(|line| line.get(col))
}
}
#[allow(non_snake_case)]
pub fn ZR_strncmp(
oldwstr: &[REFRESH_ELEMENT],
newwstr: &[REFRESH_ELEMENT],
len: usize,
) -> i32 {
let mut i = 0;
while i < len {
if i >= oldwstr.len() || i >= newwstr.len() {
return if oldwstr.get(i) == newwstr.get(i) {
0
} else {
1
};
}
let o = oldwstr[i];
let n = newwstr[i];
let old_is_nul = (o.atr & TXT_MULTIWORD_MASK) == 0 && o.chr == '\0';
let new_is_nul = (n.atr & TXT_MULTIWORD_MASK) == 0 && n.chr == '\0';
if old_is_nul || new_is_nul {
return if o == n { 0 } else { 1 }; }
if o != n {
return 1;
}
i += 1; }
0 }
impl RefreshState {
pub fn new() -> Self {
let (cols, rows) = (adjustcolumns(), adjustlines());
RefreshState {
columns: cols,
lines: rows,
old_video: Some(VideoBuffer::new(cols, rows)),
new_video: Some(VideoBuffer::new(cols, rows)),
need_full_redraw: true,
..Default::default()
}
}
pub fn reset_video(&mut self) {
let (cols, rows) = (adjustcolumns(), adjustlines());
self.columns = cols;
self.lines = rows;
self.old_video = Some(VideoBuffer::new(cols, rows));
self.new_video = Some(VideoBuffer::new(cols, rows));
self.need_full_redraw = true;
}
pub fn free_video(&mut self) {
self.old_video = None;
self.new_video = None;
}
pub fn swap_buffers(&mut self) {
std::mem::swap(&mut self.old_video, &mut self.new_video);
if let Some(ref mut new) = self.new_video {
new.clear();
}
}
}
#[allow(unused_imports)]
#[allow(unused_imports)]
pub const ZR_END_ELLIPSIS_SIZE: usize = ZR_END_ELLIPSIS.len();
pub const ZR_MID_ELLIPSIS1_SIZE: usize = ZR_MID_ELLIPSIS1.len();
pub const ZR_MID_ELLIPSIS2_SIZE: usize = ZR_MID_ELLIPSIS2.len();
pub const ZR_START_ELLIPSIS_SIZE: usize = ZR_START_ELLIPSIS.len();
pub fn zle_set_highlight(manager: &mut HighlightManager, atrs: &[&str]) {
let mut seen = std::collections::HashSet::new();
for entry in atrs {
if entry.is_empty() {
continue;
}
if *entry == "none" {
for cat in [
HighlightCategory::Region,
HighlightCategory::Isearch,
HighlightCategory::Suffix,
HighlightCategory::Paste,
HighlightCategory::Default,
HighlightCategory::Special,
HighlightCategory::Ellipsis,
] {
manager.category_attrs.insert(cat, TextAttr::default());
seen.insert(cat);
}
continue;
}
let (prefix, rest) = match entry.split_once(':') {
Some(t) => t,
None => continue,
};
let cat = match prefix {
"region" => HighlightCategory::Region,
"isearch" => HighlightCategory::Isearch,
"suffix" => HighlightCategory::Suffix,
"paste" => HighlightCategory::Paste,
"default" => HighlightCategory::Default,
"special" => HighlightCategory::Special,
"ellipsis" => HighlightCategory::Ellipsis,
_ => continue,
};
manager.category_attrs.insert(cat, match_highlight(rest));
seen.insert(cat);
}
let default_standout = TextAttr {
standout: true,
..TextAttr::default()
};
let default_underline = TextAttr {
underline: true,
..TextAttr::default()
};
let default_bold = TextAttr {
bold: true,
..TextAttr::default()
};
if !seen.contains(&HighlightCategory::Region) {
manager
.category_attrs
.insert(HighlightCategory::Region, default_standout);
}
if !seen.contains(&HighlightCategory::Isearch) {
manager
.category_attrs
.insert(HighlightCategory::Isearch, default_underline);
}
if !seen.contains(&HighlightCategory::Suffix) {
manager
.category_attrs
.insert(HighlightCategory::Suffix, default_bold);
}
if !seen.contains(&HighlightCategory::Special) {
manager
.category_attrs
.insert(HighlightCategory::Special, default_standout);
}
}
pub fn zle_free_highlight() { }
pub fn tcoutclear(cap: i32) {
use crate::ported::zsh_h::TCCLEAREOL;
let attr = if cap == TCCLEAREOL {
PROMPT_ATTR.load(Ordering::SeqCst)
} else {
0
};
crate::ported::prompt::treplaceattrs(attr);
let sgr = crate::ported::prompt::applytextattributes(0);
let fd = SHTTY.load(Ordering::Relaxed);
let out_fd = if fd >= 0 { fd } else { 1 };
if !sgr.is_empty() {
let _ = write_loop(out_fd, sgr.as_bytes());
}
tcout(cap); }
pub fn zwcputc(c: &REFRESH_ELEMENT) {
use std::sync::atomic::Ordering;
crate::ported::prompt::treplaceattrs(c.atr);
let mut out = crate::ported::prompt::applytextattributes(0);
if c.atr & TXT_MULTIWORD_MASK != 0 {
let base = c.chr as u32 as usize; NMWBUF.with(|buf| {
let b = buf.borrow();
if let Some(&nchars) = b.get(base) {
let mut p = base + 1; for _ in 0..nchars {
if let Some(ch) = b.get(p).copied().and_then(char::from_u32) {
let mut tmp = [0u8; 4];
out.push_str(ch.encode_utf8(&mut tmp));
}
p += 1;
}
}
});
} else if c.chr != '\0' && c.chr != ZWC_WEOF {
let mut buf = [0u8; 4];
out.push_str(c.chr.encode_utf8(&mut buf));
}
if !out.is_empty() {
let f = SHTTY.load(Ordering::Relaxed);
let _ = write_loop(if f >= 0 { f } else { 1 }, out.as_bytes());
}
}
pub fn zwcwrite(s: &[REFRESH_ELEMENT], i: usize) -> usize {
let n = i.min(s.len());
for cell in &s[..n] {
zwcputc(cell); }
n }
pub const DEF_MWBUF_ALLOC: usize = 32;
thread_local! {
pub static NMWBUF: std::cell::RefCell<Vec<u32>> = const {
std::cell::RefCell::new(Vec::new()) };
pub static OMWBUF: std::cell::RefCell<Vec<u32>> = const {
std::cell::RefCell::new(Vec::new()) };
pub static NMW_IND: std::cell::Cell<usize> = const {
std::cell::Cell::new(1) };
pub static NMW_SIZE: std::cell::Cell<usize> = const {
std::cell::Cell::new(0) };
pub static OMW_SIZE: std::cell::Cell<usize> = const {
std::cell::Cell::new(0) };
}
pub fn freevideo() {
*NBUF.lock().unwrap() = Vec::new();
*OBUF.lock().unwrap() = Vec::new();
NMWBUF.with(|b| b.borrow_mut().clear()); OMWBUF.with(|b| b.borrow_mut().clear()); NMW_SIZE.with(|c| c.set(0)); OMW_SIZE.with(|c| c.set(0)); NMW_IND.with(|c| c.set(1)); }
pub fn resetvideo(state: &mut RefreshState) {
use crate::ported::params::TERMFLAGS;
use crate::ported::prompt::countprompt;
use crate::ported::zsh_h::TERM_SHORT;
let cols = adjustcolumns();
state.columns = cols;
WINW.store(cols as i32, Ordering::Relaxed);
let real_lines = adjustlines();
let rows = if TERMFLAGS.load(Ordering::Relaxed) & TERM_SHORT != 0 {
1
} else if real_lines < 2 {
24
} else {
real_lines
};
state.lines = rows;
WINH.store(rows as i32, Ordering::Relaxed);
RWINH.store(real_lines as i32, Ordering::Relaxed);
VLN.store(0, Ordering::Relaxed);
VMAXLN.store(0, Ordering::Relaxed);
WINPROMPT.store(0, Ordering::Relaxed);
WINPOS.store(-1, Ordering::Relaxed);
let nrows = (rows + 1) as usize;
let rowlen = (cols + 2) as usize;
let fresh = || -> Vec<REFRESH_STRING> {
(0..nrows)
.map(|_| vec![REFRESH_ELEMENT::default(); rowlen])
.collect()
};
*NBUF.lock().unwrap() = fresh();
*OBUF.lock().unwrap() = fresh();
let mut lpromptwof_v = 0i32;
let mut lprompth_v = 0i32;
let mut rpromptw_v = 0i32;
let mut rprompth_v = 0i32;
countprompt(&state.lpromptbuf, &mut lpromptwof_v, &mut lprompth_v, 1);
countprompt(&state.rpromptbuf, &mut rpromptw_v, &mut rprompth_v, 0);
let lpromptw_v = if lpromptwof_v != cols as i32 {
lpromptwof_v
} else {
lprompth_v += 1;
0
};
state.lpromptw = lpromptw_v as usize;
state.rpromptw = rpromptw_v as usize;
LPROMPTW.store(lpromptw_v, Ordering::Relaxed);
LPROMPTH.store(lprompth_v, Ordering::Relaxed);
LPROMPTWOF.store(lpromptwof_v, Ordering::Relaxed);
RPROMPTW.store(rpromptw_v, Ordering::Relaxed);
RPROMPTH.store(rprompth_v, Ordering::Relaxed);
if lpromptw_v > 0 {
let spaces = lpromptw_v as usize;
if let Some(v) = state.new_video.as_mut() {
if let Some(row) = v.lines.get_mut(0) {
for cell in row.iter_mut().take(spaces) {
cell.chr = ' ';
}
}
}
if let Some(v) = state.old_video.as_mut() {
if let Some(row) = v.lines.get_mut(0) {
for cell in row.iter_mut().take(spaces) {
cell.chr = ' ';
}
}
}
}
state.vcs = lpromptw_v as usize;
VCS.store(lpromptw_v, Ordering::Relaxed);
OLNCT.store(0, Ordering::Relaxed);
NLNCT.store(0, Ordering::Relaxed);
if SHOWINGLIST.load(Ordering::Relaxed) > 0 {
SHOWINGLIST.store(-2, Ordering::Relaxed);
}
TRASHEDZLE.store(0, Ordering::Relaxed);
state.need_full_redraw = true;
}
pub fn scrollwindow(tline: i32) {
let winh = WINH.load(Ordering::SeqCst);
if tline >= 0 {
let t = tline as usize;
let mut nbuf = NBUF.lock().unwrap();
let end = (winh as usize).min(nbuf.len());
if t < end {
nbuf[t..end].rotate_left(1);
}
}
if tline == 0 {
MORE_START.store(1, Ordering::SeqCst);
}
}
pub fn nextline(rpms: &mut rparams, wrapped: i32) -> i32 {
let winw = WINW.load(Ordering::SeqCst);
let winh = WINH.load(Ordering::SeqCst);
{
let mut nbuf = NBUF.lock().unwrap();
if let Some(row) = nbuf.get_mut(rpms.ln as usize) {
let end_idx = (winw + 1) as usize;
if end_idx < row.len() {
row[end_idx] = if wrapped != 0 {
REFRESH_ELEMENT { chr: '\n', atr: 0 }
} else {
REFRESH_ELEMENT::default()
};
}
if rpms.pos < row.len() {
row[rpms.pos] = REFRESH_ELEMENT::default();
}
}
}
if rpms.ln != winh - 1 {
rpms.ln += 1;
} else {
if rpms.canscroll == 0 {
let onumscrolls = ONUMSCROLLS.load(Ordering::Relaxed);
let numscrolls = NUMSCROLLS.load(Ordering::Relaxed);
if rpms.nvln != -1
&& rpms.nvln != winh - 1
&& (numscrolls != onumscrolls - 1 || rpms.nvln <= winh / 2)
{
return 1;
}
NUMSCROLLS.fetch_add(1, Ordering::Relaxed); rpms.canscroll = winh / 2; }
rpms.canscroll -= 1; scrollwindow(0); if rpms.nvln != -1 {
rpms.nvln -= 1; }
}
{
let mut nbuf = NBUF.lock().unwrap();
if rpms.ln as usize >= nbuf.len() {
nbuf.resize(
rpms.ln as usize + 1,
vec![REFRESH_ELEMENT::default(); (winw + 2) as usize],
);
}
}
rpms.pos = 0;
rpms.end = winw as usize;
0 }
pub fn snextline(rpms: &mut rparams) {
let winw = WINW.load(Ordering::SeqCst);
let winh = WINH.load(Ordering::SeqCst);
{
let mut nbuf = NBUF.lock().unwrap();
if let Some(row) = nbuf.get_mut(rpms.ln as usize) {
if rpms.pos < row.len() {
row[rpms.pos] = REFRESH_ELEMENT::default();
}
}
}
if rpms.ln != winh - 1 {
rpms.ln += 1; } else if rpms.tosln > rpms.ln {
rpms.tosln -= 1;
if rpms.nvln > 1 {
scrollwindow(0); rpms.nvln -= 1; } else {
MORE_END.store(1, Ordering::SeqCst); }
} else if rpms.tosln > 2 && rpms.nvln > 1 {
rpms.tosln -= 1;
if rpms.tosln <= rpms.nvln {
scrollwindow(0); rpms.nvln -= 1; } else {
scrollwindow(rpms.tosln); MORE_END.store(1, Ordering::SeqCst); }
} else {
rpms.more_status = 1; scrollwindow(rpms.tosln + 1); }
{
let mut nbuf = NBUF.lock().unwrap();
if rpms.ln as usize >= nbuf.len() {
nbuf.resize(
rpms.ln as usize + 1,
vec![REFRESH_ELEMENT::default(); (winw + 2) as usize],
);
}
}
rpms.pos = 0; rpms.end = winw as usize; }
pub fn addmultiword(
base: &mut REFRESH_ELEMENT, tptr: &[char],
ichars: usize,
) {
let iadd = ichars + 1;
base.atr |= TXT_MULTIWORD_MASK;
NMWBUF.with(|buf| {
let ind = NMW_IND.get();
let size = NMW_SIZE.get();
if ind + iadd > size {
let mw_more = if iadd > DEF_MWBUF_ALLOC {
iadd
} else {
DEF_MWBUF_ALLOC
}; let new_size = size + mw_more;
buf.borrow_mut().resize(new_size, 0); NMW_SIZE.set(new_size); }
let mut b = buf.borrow_mut();
b[ind] = ichars as u32; for icnt in 0..ichars {
b[ind + 1 + icnt] = tptr[icnt] as u32; }
});
let ind = NMW_IND.get();
base.chr = char::from_u32(ind as u32).unwrap_or('\0');
NMW_IND.set(ind + iadd);
}
pub fn bufswap() {
let mut nbuf = NBUF.lock().unwrap();
let mut obuf = OBUF.lock().unwrap();
std::mem::swap(&mut *nbuf, &mut *obuf);
NMWBUF.with(|nmw| {
OMWBUF.with(|omw| {
std::mem::swap(&mut *nmw.borrow_mut(), &mut *omw.borrow_mut()); });
});
NMW_SIZE.with(|ns| {
OMW_SIZE.with(|os| {
let t = ns.get(); ns.set(os.get());
os.set(t);
});
});
NMW_IND.with(|ind| ind.set(1)); }
pub fn zrefresh() {
let mut handle = String::new();
let cols = adjustcolumns();
{
use crate::ported::params::TERMFLAGS;
use crate::ported::zsh_h::TERM_SHORT;
WINW.store(cols as i32, Ordering::Relaxed); let real_lines = adjustlines();
let rows = if TERMFLAGS.load(Ordering::Relaxed) & TERM_SHORT != 0 {
1 } else if real_lines < 2 {
24 } else {
real_lines
};
WINH.store(rows as i32, Ordering::Relaxed);
RWINH.store(real_lines as i32, Ordering::Relaxed); }
let prompt = prompt().to_string();
let rprompt = rprompt().to_string();
let cursor = ZLECS.load(Ordering::SeqCst);
let prompt_width = countprompt(&prompt);
LPROMPTW.store(prompt_width as i32, Ordering::Relaxed);
let rprompt_width = countprompt(&rprompt);
let buffer_before_cursor: String = {
let guard = ZLELINE.lock().unwrap();
let end = cursor.min(guard.len());
guard[..end].iter().collect()
};
let cursor_col = prompt_width + countprompt(&buffer_before_cursor);
if std::env::var_os("ZSHRS_ZLE_LOG").is_some() {
use std::io::Write;
let zl: String = ZLELINE.lock().unwrap().iter().collect();
if let Ok(mut f) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open("/tmp/zshrs_zle.log")
{
let _ = writeln!(
f,
"refresh: cols={} prompt_w={} cursor_cs={} cursor_col={} ll={} line={:?}",
cols,
prompt_width,
cursor,
cursor_col,
ZLELL.load(Ordering::SeqCst),
zl
);
}
}
let scroll_margin = 8;
let effective_cols = cols.saturating_sub(1);
let scroll_offset = if cursor_col >= effective_cols.saturating_sub(scroll_margin) {
cursor_col.saturating_sub(effective_cols / 2)
} else {
0
};
let attrs = compute_render_attrs();
let _ = write!(handle, "\r\x1b[K");
if scroll_offset < prompt_width {
let mut width = 0;
let mut byte_idx = 0;
let mut in_escape = false;
for (i, c) in prompt.char_indices() {
if width >= scroll_offset {
byte_idx = i;
break;
}
if in_escape {
if c.is_ascii_alphabetic() {
in_escape = false;
}
} else if c == '\x1b' {
in_escape = true;
} else {
width += unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
}
byte_idx = i + c.len_utf8();
}
let _ = write!(handle, "{}", &prompt[byte_idx..]);
}
let buffer_start = scroll_offset.saturating_sub(prompt_width);
let drawn_prompt_width = prompt_width.saturating_sub(scroll_offset);
let rprompt_reserve = if rprompt_width > 0 {
rprompt_width + 1
} else {
0
};
let buffer_budget = effective_cols
.saturating_sub(drawn_prompt_width)
.saturating_sub(rprompt_reserve);
let mut current_attr: Option<TextAttr> = None;
let line_snapshot = ZLELINE.lock().unwrap().clone();
for (written, (idx, ch)) in line_snapshot
.iter()
.enumerate()
.skip(buffer_start)
.enumerate()
{
if written >= buffer_budget {
break;
}
let want_attr = attrs.get(idx).and_then(|a| *a);
if want_attr != current_attr {
let _ = write!(handle, "\x1b[0m");
if let Some(a) = want_attr {
let _ = write!(handle, "{}", a.to_ansi());
}
current_attr = want_attr;
}
let _ = write!(handle, "{}", ch);
}
if current_attr.is_some() {
let _ = write!(handle, "\x1b[0m");
}
if rprompt_width > 0 && rprompt_width + 2 < effective_cols {
let rprompt_col = effective_cols.saturating_sub(rprompt_width);
let _ = write!(handle, "\r\x1b[{}C{}\x1b[0m", rprompt_col, rprompt);
}
let display_cursor_col = cursor_col.saturating_sub(scroll_offset);
let _ = write!(handle, "\r\x1b[{}C", display_cursor_col);
let fd = SHTTY.load(Ordering::Relaxed);
let out_fd = if fd >= 0 { fd } else { 1 };
let _ = write_loop(out_fd, handle.as_bytes());
return;
let mut cur_nvln: i32 = -1;
let mut cur_nvcs: i32 = 0;
{
bufswap();
OLNCT.store(NLNCT.load(Ordering::SeqCst), Ordering::SeqCst);
NUMSCROLLS.store(0, Ordering::SeqCst);
NBUF.lock().unwrap().clear();
use crate::ported::zsh_h::zattr;
let to_zattr = |ta: &TextAttr| -> zattr {
use crate::ported::zsh_h::{
TXTBGCOLOUR, TXTBOLDFACE, TXTFGCOLOUR, TXTSTANDOUT, TXTUNDERLINE,
TXT_ATTR_BG_COL_SHIFT, TXT_ATTR_FG_COL_SHIFT,
};
let mut a: zattr = 0;
if ta.bold {
a |= TXTBOLDFACE;
}
if ta.underline {
a |= TXTUNDERLINE;
}
if ta.standout {
a |= TXTSTANDOUT;
}
if let Some(fg) = ta.fg_color {
a |= TXTFGCOLOUR | ((fg as zattr) << TXT_ATTR_FG_COL_SHIFT);
}
if let Some(bg) = ta.bg_color {
a |= TXTBGCOLOUR | ((bg as zattr) << TXT_ATTR_BG_COL_SHIFT);
}
a
};
let cols_n = cols.max(1);
let winw_b = WINW.load(Ordering::SeqCst);
let winh_b = WINH.load(Ordering::SeqCst);
{
let nrows = (winh_b + 1).max(1) as usize;
let rowlen = (winw_b + 2) as usize;
*NBUF.lock().unwrap() = (0..nrows)
.map(|_| vec![REFRESH_ELEMENT::default(); rowlen])
.collect();
}
let mut rpms = rparams::default();
rpms.nvln = -1; MORE_START.store(0, Ordering::SeqCst);
MORE_END.store(0, Ordering::SeqCst);
let mut emit = |rpms: &mut rparams, chr: char, atr: zattr| -> bool {
{
let mut nbuf = NBUF.lock().unwrap();
if let Some(row) = nbuf.get_mut(rpms.ln as usize) {
if rpms.pos < row.len() {
row[rpms.pos] = REFRESH_ELEMENT { chr, atr };
}
}
}
rpms.pos += 1;
if rpms.pos >= cols_n as usize {
return nextline(rpms, 1) != 0; }
false
};
let mut emit_cell = |rpms: &mut rparams, cell: REFRESH_ELEMENT| -> bool {
{
let mut nbuf = NBUF.lock().unwrap();
if let Some(row) = nbuf.get_mut(rpms.ln as usize) {
if rpms.pos < row.len() {
row[rpms.pos] = cell;
}
}
}
rpms.pos += 1;
if rpms.pos >= cols_n as usize {
return nextline(rpms, 1) != 0;
}
false
};
let apply_sgr = |mut attr: zattr, params: &str| -> zattr {
use crate::ported::zsh_h::{
TXTBGCOLOUR, TXTBOLDFACE, TXTFGCOLOUR, TXTSTANDOUT, TXTUNDERLINE,
TXT_ATTR_BG_COL_SHIFT, TXT_ATTR_FG_COL_SHIFT,
};
let nums: Vec<i64> = if params.is_empty() {
vec![0] } else {
params.split(';').filter_map(|s| s.parse().ok()).collect()
};
let set_fg = |a: zattr, c: i64| {
(a & !(TXTFGCOLOUR | (0xff << TXT_ATTR_FG_COL_SHIFT)))
| TXTFGCOLOUR
| ((c as zattr) << TXT_ATTR_FG_COL_SHIFT)
};
let set_bg = |a: zattr, c: i64| {
(a & !(TXTBGCOLOUR | (0xff << TXT_ATTR_BG_COL_SHIFT)))
| TXTBGCOLOUR
| ((c as zattr) << TXT_ATTR_BG_COL_SHIFT)
};
let mut i = 0;
while i < nums.len() {
match nums[i] {
0 => attr = 0,
1 => attr |= TXTBOLDFACE,
4 => attr |= TXTUNDERLINE,
7 => attr |= TXTSTANDOUT,
30..=37 => attr = set_fg(attr, nums[i] - 30),
40..=47 => attr = set_bg(attr, nums[i] - 40),
90..=97 => attr = set_fg(attr, nums[i] - 90 + 8),
100..=107 => attr = set_bg(attr, nums[i] - 100 + 8),
38 if i + 2 < nums.len() && nums[i + 1] == 5 => {
attr = set_fg(attr, nums[i + 2]);
i += 2;
}
48 if i + 2 < nums.len() && nums[i + 1] == 5 => {
attr = set_bg(attr, nums[i + 2]);
i += 2;
}
_ => {}
}
i += 1;
}
attr
};
let mut prompt_attr: zattr = 0;
let mut esc_params = String::new();
let mut in_esc = false;
for c in prompt.chars() {
if in_esc {
if c == '[' {
} else if c == 'm' {
prompt_attr = apply_sgr(prompt_attr, &esc_params); in_esc = false;
esc_params.clear();
} else if c.is_ascii_alphabetic() {
in_esc = false; esc_params.clear();
} else {
esc_params.push(c);
}
} else if c == '\x1b' {
in_esc = true;
esc_params.clear();
} else if emit(&mut rpms, c, prompt_attr) {
break; }
}
PROMPT_ATTR.store(prompt_attr, Ordering::Relaxed);
let cursor_idx = ZLECS.load(Ordering::SeqCst);
let mut skip_combining = 0usize;
for (i, &ch) in line_snapshot.iter().enumerate() {
if skip_combining > 0 {
skip_combining -= 1;
continue;
}
if i == cursor_idx {
rpms.nvln = rpms.ln;
rpms.nvcs = rpms.pos as i32;
}
let atr = attrs
.get(i)
.and_then(|o| o.as_ref())
.map(&to_zattr)
.unwrap_or(0);
if ch == '\n' {
if nextline(&mut rpms, 0) != 0 {
break;
}
} else if ch == '\t' {
let mut bail = false;
loop {
if emit(&mut rpms, ' ', atr) {
bail = true;
break;
}
if rpms.pos % 8 == 0 {
break;
}
}
if bail {
break;
}
} else if (ch as u32) < 0x20 || ch as u32 == 0x7f {
if emit(&mut rpms, '^', atr) {
break;
}
let c2 = if ((ch as u32) & !0x80u32) > 31 {
'?'
} else {
char::from_u32((ch as u32) | 0x40).unwrap_or('?')
};
if emit(&mut rpms, c2, atr) {
break;
}
} else {
let cw = unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0);
if cw == 0 {
let hex = if (ch as u32) > 0xffff {
format!("<{:08x}>", ch as u32) } else {
format!("<{:04x}>", ch as u32) };
let mut bail = false;
for hc in hex.chars() {
if emit(&mut rpms, hc, atr) {
bail = true;
break;
}
}
if bail {
break;
}
continue; }
let width = cw;
let remaining = (cols_n as usize).saturating_sub(rpms.pos);
if width > remaining {
let mut bail = false;
for _ in 0..remaining {
if emit(&mut rpms, ' ', atr) {
bail = true;
break;
}
}
if bail {
break; }
if i == cursor_idx {
rpms.nvln = rpms.ln;
rpms.nvcs = rpms.pos as i32;
}
}
let mut ichars = 1usize;
if isset(COMBININGCHARS) {
while i + ichars < line_snapshot.len()
&& crate::ported::zsh_h::IS_COMBINING(line_snapshot[i + ichars])
{
ichars += 1; }
}
skip_combining = ichars - 1; let remaining2 = (cols_n as usize).saturating_sub(rpms.pos);
if width > remaining2 {
if emit(&mut rpms, '?', atr) {
break; }
} else {
let mut cell = REFRESH_ELEMENT { chr: ch, atr };
if ichars > 1 {
let cluster: Vec<char> =
line_snapshot[i..i + ichars].to_vec();
addmultiword(&mut cell, &cluster, ichars); }
if emit_cell(&mut rpms, cell) {
break; }
let mut w = width - 1;
let mut bail = false;
while w > 0 {
if emit_cell(
&mut rpms,
REFRESH_ELEMENT { chr: ZWC_WEOF, atr },
) {
bail = true;
break;
}
w -= 1;
}
if bail {
break;
}
}
}
}
if cursor_idx >= line_snapshot.len() {
rpms.nvln = rpms.ln;
rpms.nvcs = rpms.pos as i32;
if rpms.nvcs == WINW.load(Ordering::SeqCst) {
let _ = nextline(&mut rpms, 1); rpms.nvcs = 0; rpms.nvln += 1; }
}
let statusline_present = if let Some(status) =
STATUSLINE.lock().unwrap().clone()
{
let winw_s = WINW.load(Ordering::SeqCst);
let all_attr = SPECIAL_ATTR.load(Ordering::SeqCst); rpms.tosln = rpms.ln + 1;
{
let mut nbuf = NBUF.lock().unwrap();
if let Some(row) = nbuf.get_mut(rpms.ln as usize) {
let end_idx = (winw_s + 1) as usize;
if end_idx < row.len() {
row[end_idx] = REFRESH_ELEMENT::default();
}
}
}
snextline(&mut rpms); let mut semit = |rpms: &mut rparams, chr: char, atr: zattr| {
{
let mut nbuf = NBUF.lock().unwrap();
if let Some(row) = nbuf.get_mut(rpms.ln as usize) {
if rpms.pos < row.len() {
row[rpms.pos] = REFRESH_ELEMENT { chr, atr };
}
}
}
rpms.pos += 1;
if rpms.pos >= rpms.end {
{
let mut nbuf = NBUF.lock().unwrap();
if let Some(row) = nbuf.get_mut(rpms.ln as usize) {
let end_idx = (winw_s + 1) as usize;
if end_idx < row.len() {
row[end_idx] =
REFRESH_ELEMENT { chr: '\n', atr: 0 };
}
}
}
snextline(rpms);
}
};
let mut semit_cell = |rpms: &mut rparams, cell: REFRESH_ELEMENT| {
{
let mut nbuf = NBUF.lock().unwrap();
if let Some(row) = nbuf.get_mut(rpms.ln as usize) {
if rpms.pos < row.len() {
row[rpms.pos] = cell;
}
}
}
rpms.pos += 1;
if rpms.pos >= rpms.end {
{
let mut nbuf = NBUF.lock().unwrap();
if let Some(row) = nbuf.get_mut(rpms.ln as usize) {
let end_idx = (winw_s + 1) as usize;
if end_idx < row.len() {
row[end_idx] =
REFRESH_ELEMENT { chr: '\n', atr: 0 };
}
}
}
snextline(rpms);
}
};
let status_chars: Vec<char> = status.chars().collect();
let mut skip_s = 0usize;
for su in 0..status_chars.len() {
if skip_s > 0 {
skip_s -= 1;
continue;
}
let u = status_chars[su];
let width = unicode_width::UnicodeWidthChar::width(u).unwrap_or(0);
let is_ctrl = (u as u32) < 0x20 || u as u32 == 0x7f;
if width > 0 && !is_ctrl {
let mut ichars = 1usize;
if isset(COMBININGCHARS) {
while su + ichars < status_chars.len()
&& crate::ported::zsh_h::IS_COMBINING(status_chars[su + ichars])
{
ichars += 1;
}
}
skip_s = ichars - 1;
let remaining = rpms.end.saturating_sub(rpms.pos);
if width as usize > remaining {
for _ in 0..remaining {
semit(&mut rpms, ' ', 0); }
}
let remaining2 = rpms.end.saturating_sub(rpms.pos);
if width as usize > remaining2 {
semit(&mut rpms, '?', all_attr); } else {
let mut cell = REFRESH_ELEMENT { chr: u, atr: 0 }; if ichars > 1 {
let cluster: Vec<char> =
status_chars[su..su + ichars].to_vec();
addmultiword(&mut cell, &cluster, ichars); }
semit_cell(&mut rpms, cell);
let mut w = width - 1;
while w > 0 {
semit_cell(&mut rpms, REFRESH_ELEMENT {
chr: ZWC_WEOF,
atr: 0,
});
w -= 1;
}
}
} else if is_ctrl {
semit(&mut rpms, '^', all_attr); let c2 = if ((u as u32) & !0x80u32) > 31 {
'?' } else {
char::from_u32((u as u32) | 0x40).unwrap_or('?') };
semit(&mut rpms, c2, all_attr); } else {
let hex = if (u as u32) > 0xffff {
format!("<{:08x}>", u as u32) } else {
format!("<{:04x}>", u as u32) };
for hc in hex.chars() {
semit(&mut rpms, hc, all_attr); }
}
}
{
let mut nbuf = NBUF.lock().unwrap();
if let Some(row) = nbuf.get_mut(rpms.ln as usize) {
if rpms.pos < row.len() {
row[rpms.pos] = REFRESH_ELEMENT::default();
}
}
}
true
} else {
false
};
let nlnct = rpms.ln + 1;
NBUF.lock().unwrap().truncate(nlnct as usize);
NLNCT.store(nlnct, Ordering::SeqCst);
if MORE_END.load(Ordering::SeqCst) != 0 {
let winw_e = WINW.load(Ordering::SeqCst);
let winh_e = WINH.load(Ordering::SeqCst);
let prompt_attr = PROMPT_ATTR.load(Ordering::SeqCst);
let ellipsis_attr = ELLIPSIS_ATTR.load(Ordering::SeqCst);
if !statusline_present {
rpms.tosln = winh_e;
}
let target = (rpms.tosln - 1).max(0) as usize; let sen = (winw_e - 7).max(0) as usize; let mut nbuf = NBUF.lock().unwrap();
if let Some(row) = nbuf.get_mut(target) {
let lim = sen.min(row.len());
for s in 0..lim {
if row[s].chr == '\0' {
for k in s..lim {
row[k] = REFRESH_ELEMENT { chr: ' ', atr: prompt_attr };
}
if sen < row.len() {
row[sen].chr = '\0'; }
break;
}
}
for (k, cell) in ZR_END_ELLIPSIS.iter().enumerate() {
let idx = sen + k;
if idx < row.len() {
row[idx] = *cell;
}
}
if sen < row.len() {
row[sen].atr = prompt_attr; }
if sen + 1 < row.len() {
row[sen + 1].atr = ellipsis_attr; }
let wi = winw_e as usize;
if wi < row.len() {
row[wi] = REFRESH_ELEMENT::default();
}
if wi + 1 < row.len() {
row[wi + 1] = REFRESH_ELEMENT::default();
}
}
}
if rpms.more_status != 0 {
let winw_m = WINW.load(Ordering::SeqCst);
let prompt_attr = PROMPT_ATTR.load(Ordering::SeqCst);
let ellipsis_attr = ELLIPSIS_ATTR.load(Ordering::SeqCst);
let target = rpms.tosln.max(0) as usize; let sen = (winw_m - 8).max(0) as usize; let mut nbuf = NBUF.lock().unwrap();
if let Some(row) = nbuf.get_mut(target) {
let lim = sen.min(row.len());
for s in 0..lim {
if row[s].chr == '\0' {
for k in s..lim {
row[k] = REFRESH_ELEMENT { chr: ' ', atr: 0 };
}
break;
}
}
for (k, cell) in ZR_MID_ELLIPSIS1.iter().enumerate() {
let idx = sen + k;
if idx < row.len() {
row[idx] = *cell;
}
}
if sen + 1 < row.len() {
row[sen + 1].atr = ellipsis_attr; }
let sen2 = sen + ZR_MID_ELLIPSIS1_SIZE; for (k, cell) in ZR_MID_ELLIPSIS2.iter().enumerate() {
let idx = sen2 + k;
if idx < row.len() {
row[idx] = *cell;
}
}
if sen2 + 1 < row.len() {
row[sen2 + 1].atr = prompt_attr; }
let wi = winw_m as usize;
if wi < row.len() {
row[wi] = REFRESH_ELEMENT::default();
}
if wi + 1 < row.len() {
row[wi + 1] = REFRESH_ELEMENT::default();
}
}
}
cur_nvln = rpms.nvln;
cur_nvcs = rpms.nvcs;
}
let mut rprompt_off: i32 = 1;
if MORE_START.load(Ordering::SeqCst) != 0 {
let winw = WINW.load(Ordering::SeqCst).max(0) as usize;
let lpromptw = (LPROMPTW.load(Ordering::SeqCst).max(0) as usize).min(winw);
let prompt_attr = PROMPT_ATTR.load(Ordering::SeqCst);
let ellipsis_attr = ELLIPSIS_ATTR.load(Ordering::SeqCst);
let t0 = (winw - lpromptw).min(ZR_START_ELLIPSIS_SIZE); let mut row0: REFRESH_STRING = Vec::with_capacity(winw + 2);
for _ in 0..lpromptw {
row0.push(REFRESH_ELEMENT { chr: ' ', atr: 0 }); }
for k in 0..t0 {
let mut cell = ZR_START_ELLIPSIS[k]; if k == 0 {
cell.atr = ellipsis_attr; }
row0.push(cell);
}
for j in 0..(winw - t0 - lpromptw) {
let atr = if j == 0 { prompt_attr } else { 0 }; row0.push(REFRESH_ELEMENT { chr: ' ', atr });
}
row0.push(REFRESH_ELEMENT::default());
row0.push(REFRESH_ELEMENT::default());
let mut nbuf = NBUF.lock().unwrap();
if let Some(first) = nbuf.get_mut(0) {
*first = row0;
}
} else {
if TRASHEDZLE.load(Ordering::SeqCst) != 0
&& isset(crate::ported::zsh_h::TRANSIENTRPROMPT)
{
PUT_RPMPT.store(0, Ordering::SeqCst);
} else {
let rp = crate::ported::zle::zle_main::rprompt();
let winw = WINW.load(Ordering::SeqCst);
let rpromptw = RPROMPTW.load(Ordering::SeqCst);
let mut put = (RPROMPTH.load(Ordering::SeqCst) == 1
&& !rp.is_empty()
&& !rp.contains('\t')) as i32;
if put != 0 {
rprompt_off = *crate::ported::params::RPROMPT_INDENT.lock().unwrap();
if rprompt_off < 0 {
rprompt_off = 0;
}
let nlen = {
let nbuf = NBUF.lock().unwrap();
nbuf.first().map(|r| ZR_strlen(r) as i32).unwrap_or(0)
};
put = (nlen + rpromptw < winw - rprompt_off) as i32;
}
PUT_RPMPT.store(put, Ordering::SeqCst);
}
}
let nlnct = NLNCT.load(Ordering::SeqCst);
let olnct = OLNCT.load(Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst); VLN.store(0, Ordering::SeqCst);
let saved_cleareol = CLEAREOL.load(Ordering::SeqCst);
let clearf = CLEARFLAG.load(Ordering::SeqCst) != 0;
let winw = WINW.load(Ordering::SeqCst);
let hasam_v = crate::ported::init::hasam.load(Ordering::SeqCst) != 0;
enum LineOp {
None,
Del,
Ins,
}
for iln in 0..nlnct {
let olnct_now = OLNCT.load(Ordering::SeqCst);
if iln >= olnct_now {
CLEAREOL.store(1, Ordering::SeqCst);
}
if !clearf
&& iln > 0
&& iln < olnct_now - 1
&& !(hasam_v && VCS.load(Ordering::SeqCst) == winw)
{
let tcan_del =
tclen.lock().unwrap()[crate::ported::zsh_h::TCDELLINE as usize] != 0;
let tcan_ins =
tclen.lock().unwrap()[crate::ported::zsh_h::TCINSLINE as usize] != 0;
let vmaxln = VMAXLN.load(Ordering::SeqCst);
let i = iln as usize;
let op = {
let nbuf = NBUF.lock().unwrap();
let obuf = OBUF.lock().unwrap();
let nb_i = nbuf.get(i);
let ob_i = obuf.get(i);
let outer = match (nb_i, ob_i) {
(Some(nb), Some(ob)) => ZR_strncmp(ob, nb, 16) != 0,
_ => false,
};
if !outer {
LineOp::None
} else if tcan_del
&& obuf
.get(i + 1)
.and_then(|r| r.first())
.map(|c| c.chr != '\0')
.unwrap_or(false)
&& nb_i.is_some()
&& ZR_strncmp(obuf.get(i + 1).unwrap(), nb_i.unwrap(), 16) == 0
{
LineOp::Del
} else if tcan_ins
&& olnct_now < vmaxln
&& nbuf.get(i + 1).is_some()
&& ob_i.is_some()
&& ZR_strncmp(ob_i.unwrap(), nbuf.get(i + 1).unwrap(), 16) == 0
{
LineOp::Ins
} else {
LineOp::None
}
};
match op {
LineOp::Del => {
moveto(i, 0); tcout(crate::ported::zsh_h::TCDELLINE); let mut obuf = OBUF.lock().unwrap();
if i < obuf.len() {
obuf.remove(i);
}
OLNCT.store(olnct_now - 1, Ordering::SeqCst);
}
LineOp::Ins => {
moveto(i, 0); tcout(crate::ported::zsh_h::TCINSLINE); let mut obuf = OBUF.lock().unwrap();
let at = i.min(obuf.len());
obuf.insert(at, Vec::new());
OLNCT.store(olnct_now + 1, Ordering::SeqCst);
}
LineOp::None => {}
}
}
refreshline(iln);
if PUT_RPMPT.load(Ordering::SeqCst) != 0
&& iln == 0
&& OPUT_RPMPT.load(Ordering::SeqCst) == 0
{
let rpromptw = RPROMPTW.load(Ordering::SeqCst);
let col = (winw - rprompt_off - rpromptw).max(0) as usize;
moveto(0, col);
crate::ported::prompt::treplaceattrs(PMPT_ATTR.load(Ordering::SeqCst));
let mut out = crate::ported::prompt::applytextattributes(0);
out.push_str(&crate::ported::zle::zle_main::rprompt());
{
let fd = SHTTY.load(Ordering::Relaxed);
let _ = write_loop(if fd >= 0 { fd } else { 1 }, out.as_bytes());
}
if rprompt_off != 0 {
VCS.store(winw - rprompt_off, Ordering::SeqCst); } else {
zwcputc(&REFRESH_ELEMENT { chr: '\r', atr: 0 }); VCS.store(0, Ordering::SeqCst); }
crate::ported::prompt::treplaceattrs(RPMPT_ATTR.load(Ordering::SeqCst));
}
}
CLEAREOL.store(saved_cleareol, Ordering::SeqCst);
OPUT_RPMPT.store(PUT_RPMPT.load(Ordering::SeqCst), Ordering::SeqCst);
if nlnct > VMAXLN.load(Ordering::SeqCst) {
VMAXLN.store(nlnct, Ordering::SeqCst);
}
let olnct = OLNCT.load(Ordering::SeqCst);
if olnct > nlnct {
CLEAREOL.store(1, Ordering::SeqCst);
for iln in nlnct..olnct {
refreshline(iln);
}
CLEAREOL.store(0, Ordering::SeqCst);
}
let cols_c = cols.max(1);
if cur_nvln >= 0 {
moveto(cur_nvln as usize, cur_nvcs.max(0) as usize);
} else {
moveto(cursor_col / cols_c, cursor_col % cols_c);
}
crate::ported::zle::termquery::cursor_form();
ONUMSCROLLS.store(NUMSCROLLS.load(Ordering::SeqCst), Ordering::SeqCst);
}
impl HighlightManager {
pub fn new() -> Self {
HighlightManager {
regions: Vec::new(),
category_attrs: std::collections::HashMap::new(),
}
}
pub fn add_region(&mut self, start: usize, end: usize, attr: TextAttr) {
self.regions.push(RegionHighlight {
start,
end,
attr,
memo: None,
flags: 0,
});
}
pub fn get_region_highlight(&self, pos: usize) -> Option<&RegionHighlight> {
self.regions.iter().find(|r| pos >= r.start && pos < r.end)
}
pub fn unset_region_highlight(&mut self) {
self.regions.clear();
}
pub fn free(&mut self) {
self.regions.clear();
}
}
pub fn wpfxlen(olds: &[REFRESH_ELEMENT], news: &[REFRESH_ELEMENT]) -> usize {
let mut i = 0;
while i < olds.len() && i < news.len() && olds[i].chr != '\0' && olds[i] == news[i] {
i += 1;
}
i
}
pub fn refreshline(ln: i32) {
let mut nl: REFRESH_STRING = NBUF
.lock()
.unwrap()
.get(ln as usize)
.cloned()
.unwrap_or_default();
let mut ol: REFRESH_STRING = if ln < OLNCT.load(Ordering::SeqCst) {
OBUF.lock()
.unwrap()
.get(ln as usize)
.cloned()
.unwrap_or_default()
} else {
Vec::new()
};
let _p1: REFRESH_STRING = Vec::new(); let mut ccs: i32 = 0; let mut char_ins: i32 = 0; let mut col_cleareol: i32; let mut i: i32; let mut _j: i32 = 0; let mut ins_last: i32; let mut nllen: i32; let ollen: i32; let rnllen: i32; let zr_pad = REFRESH_ELEMENT {
chr: ' ',
atr: PROMPT_ATTR.load(Ordering::SeqCst),
};
rnllen = ZR_strlen(&nl) as i32;
nllen = rnllen;
ollen = ZR_strlen(&ol) as i32;
let cleareol = CLEAREOL.load(Ordering::SeqCst) != 0;
let hasam_v = crate::ported::init::hasam.load(Ordering::SeqCst) != 0; let nlnct_v = NLNCT.load(Ordering::SeqCst);
if cleareol && nllen == 0
&& !(hasam_v && ln < nlnct_v - 1)
&& (tclen.lock().unwrap()[TCCLEAREOL as usize] != 0)
{
moveto(ln as usize, 0); tcoutclear(TCCLEAREOL); return; }
let put_rpmpt = PUT_RPMPT.load(Ordering::SeqCst);
let oput_rpmpt = OPUT_RPMPT.load(Ordering::SeqCst);
let winw = WINW.load(Ordering::SeqCst);
if cleareol || (nllen == 0 && (ln != 0 || put_rpmpt == 0)) || (ln == 0 && put_rpmpt != oput_rpmpt)
{
let mut padded: REFRESH_STRING = Vec::with_capacity((winw + 2) as usize);
for el in nl.iter().take(nllen as usize) {
padded.push(*el);
}
for _ in nllen..winw {
padded.push(REFRESH_ELEMENT { chr: ' ', atr: 0 });
}
padded.push(REFRESH_ELEMENT { chr: '\0', atr: 0 }); if nllen < winw {
padded.push(REFRESH_ELEMENT { chr: '\0', atr: 0 });
} else if let Some(extra) = nl.get((winw + 1) as usize).copied() {
padded.push(extra);
}
if ln != 0 && !nl.is_empty() {
let copy_len = ((winw + 2) as usize).min(padded.len()); if nl.len() >= copy_len {
for k in 0..copy_len {
nl[k] = padded[k];
}
} else {
nl = padded.clone();
}
} else {
nl = padded; }
nllen = winw; } else if ollen > nllen {
let mut padded: REFRESH_STRING = Vec::with_capacity((ollen + 1) as usize);
for el in nl.iter().take(nllen as usize) {
padded.push(*el);
}
for _ in nllen..ollen {
padded.push(zr_pad);
}
padded.push(REFRESH_ELEMENT { chr: '\0', atr: 0 }); nl = padded; nllen = ollen; }
if hasam_v && ln < nlnct_v - 1 && rnllen == winw {
col_cleareol = -2; } else {
col_cleareol = -1; if (tclen.lock().unwrap()[TCCLEAREOL as usize] != 0) && (nllen == winw || put_rpmpt != oput_rpmpt)
{
let a = nl.get((nllen - 1) as usize).map(|e| e.atr).unwrap_or(0); let mut i_loc = nllen; while i_loc > 0
&& nl
.get((i_loc - 1) as usize)
.map(|e| e.chr == ' ' && e.atr == a)
.unwrap_or(false)
{
i_loc -= 1; }
if nllen == winw && i_loc < nllen {
col_cleareol = i_loc; } else {
let a = ol.get((ollen - 1) as usize).map(|e| e.atr).unwrap_or(0); let mut j_loc = ollen; while j_loc > 0
&& ol
.get((j_loc - 1) as usize)
.map(|e| e.chr == ' ' && e.atr == a)
.unwrap_or(false)
{
j_loc -= 1; }
let tclen_clear: i32 = tclen.lock().unwrap()[TCCLEAREOL as usize];
if j_loc > i_loc + tclen_clear {
col_cleareol = i_loc; }
}
}
}
let mut vcs = VCS.load(Ordering::SeqCst);
let mut vln = VLN.load(Ordering::SeqCst);
if hasam_v && vcs == winw {
let next_is_nl = {
let nbuf = NBUF.lock().unwrap();
nbuf.get(vln as usize)
.and_then(|row| row.get((vcs + 1) as usize))
.map(|cell| cell.chr == '\n')
.unwrap_or(false)
};
if next_is_nl {
vln += 1; vcs = 1;
VLN.store(vln, Ordering::SeqCst);
VCS.store(1, Ordering::SeqCst);
let first_cell = {
let nbuf = NBUF.lock().unwrap();
nbuf.get(vln as usize)
.and_then(|row| row.first())
.copied()
.filter(|c| c.chr != '\0')
};
match first_cell {
Some(cell) => zwcputc(&cell),
None => zwcputc(&REFRESH_ELEMENT { chr: ' ', atr: 0 }), }
if ln == vln {
if !nl.is_empty() {
nl.remove(0); }
if !ol.is_empty() && ol[0].chr != '\0' {
ol.remove(0); }
ccs = 1; }
} else {
vln += 1; vcs = 0;
VLN.store(vln, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
zwcputc(&REFRESH_ELEMENT { chr: '\n', atr: 0 }); }
}
ins_last = 0;
let lpromptw = LPROMPTW.load(Ordering::SeqCst);
if ln == 0 && lpromptw != 0 {
i = lpromptw - ccs; let j_loc = ol.len() as i32; for _ in 0..i.min(nl.len() as i32) {
nl.remove(0);
}
let ol_skip = if i > j_loc { j_loc } else { i };
for _ in 0..ol_skip.min(ol.len() as i32) {
ol.remove(0);
}
ccs = lpromptw; }
while nl.first().map(|c| c.chr == ZWC_WEOF).unwrap_or(false) {
nl.remove(0); ccs += 1; vcs += 1; if ol.first().map(|c| c.chr != '\0').unwrap_or(false) {
ol.remove(0); }
}
VCS.store(vcs, Ordering::SeqCst);
loop {
let nl_first = nl.first().copied();
let ol_first = ol.first().copied();
let nl_second = nl.get(1).copied();
let ol_second = ol.get(1).copied();
if nl_first.map(|e| e.chr != '\0').unwrap_or(false) && ol_first.map(|e| e.chr != '\0').unwrap_or(false)
&& nl_second == ol_second
{
while !nl.is_empty() && nl[0].chr != '\0'
&& !ol.is_empty()
&& nl[0] == ol[0]
{
nl.remove(0); ol.remove(0);
ccs += 1;
}
}
if nl.is_empty() || nl[0].chr == '\0' {
if ccs == winw && hasam_v && char_ins > 0 && ins_last != 0 && vcs != winw
{
let deferred = NBUF
.lock()
.unwrap()
.get(ln as usize)
.and_then(|row| row.get((winw - 1) as usize))
.copied();
moveto(ln as usize, (winw - 1) as usize); vcs = winw - 1; if let Some(cell) = deferred {
zwcputc(&cell);
}
vcs += 1; VCS.store(vcs, Ordering::SeqCst);
return; }
if char_ins <= 0 || ccs >= winw {
return; }
let tcleareol_len = tclen.lock().unwrap()[TCCLEAREOL as usize];
if tcleareol_len != 0
&& char_ins >= tcleareol_len && col_cleareol != -2
{
col_cleareol = 0; }
}
moveto(ln as usize, ccs as usize); vcs = ccs;
if col_cleareol >= 0 && ccs >= col_cleareol {
tcoutclear(TCCLEAREOL); return; }
if nl.is_empty() || nl[0].chr == '\0' {
let i_pad = if winw - ccs < char_ins {
winw - ccs
} else {
char_ins
};
let tcdel_len = tclen.lock().unwrap()[TCDEL as usize];
let can_del = tcdel_len != 0
&& tcdelcost(i_pad) <= i_pad + 1;
if can_del {
tc_delchars(i_pad);
} else {
vcs += i_pad;
VCS.store(vcs, Ordering::SeqCst);
for _ in 0..i_pad {
zwcputc(&zr_pad); }
}
return; }
if ol.is_empty() || ol[0].chr == '\0' {
let i_remain = if col_cleareol >= 0 {
col_cleareol
} else {
nllen
}; let i_write = i_remain - vcs; if i_write > 0 {
zwcwrite(&nl, i_write as usize);
vcs += i_write; VCS.store(vcs, Ordering::SeqCst);
}
if col_cleareol >= 0 {
tcoutclear(TCCLEAREOL); }
return; }
let eligible = (ln != 0 || put_rpmpt == 0 || oput_rpmpt == 0)
&& !nl.is_empty()
&& nl_second.map(|e| e.chr != '\0').unwrap_or(false)
&& !ol.is_empty()
&& ol_second.map(|e| e.chr != '\0').unwrap_or(false)
&& ol_second != nl_second;
if eligible {
if (tclen.lock().unwrap()[TCDEL as usize] != 0)
{
let mut first = true; let mut i_try = 1i32; while (i_try as usize) < ol.len() && ol[i_try as usize].chr != '\0' {
let ol_tail = &ol[i_try as usize..];
let cheap_delete = tcdelcost(i_try) < wpfxlen(ol_tail, &nl) as i32;
if cheap_delete {
if first {
crate::ported::prompt::treplaceattrs(
PROMPT_ATTR.load(Ordering::SeqCst),
); let sgr = crate::ported::prompt::applytextattributes(0); if !sgr.is_empty() {
let fd = SHTTY.load(Ordering::Relaxed);
let _ = write_loop(if fd >= 0 { fd } else { 1 }, sgr.as_bytes());
}
first = false; }
tc_delchars(i_try);
for _ in 0..i_try {
if !ol.is_empty() {
ol.remove(0);
} }
char_ins -= i_try; while ol.first().map(|c| c.chr == ZWC_WEOF).unwrap_or(false) {
ol.remove(0);
char_ins -= 1;
}
i_try = 0; break;
}
i_try += 1;
}
if i_try == 0 {
continue;
}
}
let zterm_lines = WINH.load(Ordering::SeqCst);
if (tclen.lock().unwrap()[TCINS as usize] != 0) && vln != zterm_lines - 1
{
let mut i_try = 1i32; while (i_try as usize) < nl.len() && nl[i_try as usize].chr != '\0' {
let nl_tail = &nl[i_try as usize..];
let cheap_insert = tcinscost(i_try) < wpfxlen(&ol, nl_tail) as i32;
if cheap_insert {
tc_inschars(i_try); zwcwrite(&nl, i_try as usize); for _ in 0..i_try {
if !nl.is_empty() {
nl.remove(0);
} }
let mut i_eff = i_try;
while nl.first().map(|c| c.chr == ZWC_WEOF).unwrap_or(false) {
nl.remove(0);
i_eff += 1;
}
char_ins += i_eff; vcs += i_eff;
VCS.store(vcs, Ordering::SeqCst);
ccs += i_eff; let mut k = 0i32;
while (k as usize) < ol.len()
&& ol[k as usize].chr != '\0'
&& k < winw - ccs
{
k += 1; }
if k >= winw - ccs && (k as usize) < ol.len() {
ol[k as usize] = REFRESH_ELEMENT {
chr: '\0',
atr: 0, };
ins_last = 1; }
i_try = 0; break;
}
i_try += 1;
}
if i_try == 0 {
continue;
}
}
}
if !nl.is_empty() && nl[0].chr == '\0' {
break; }
loop {
if let Some(cell) = nl.first().cloned() {
zwcputc(&cell); }
if !nl.is_empty() {
nl.remove(0);
} if !ol.is_empty() && ol[0].chr != '\0' {
ol.remove(0); }
ccs += 1; vcs += 1; VCS.store(vcs, Ordering::SeqCst);
let ol_chr = ol.first().map(|c| c.chr).unwrap_or('\0');
let nl_chr = nl.first().map(|c| c.chr).unwrap_or('\0');
if (ol_chr == ZWC_WEOF && nl_chr != '\0') || (nl_chr == ZWC_WEOF && ol_chr != '\0')
{
continue;
}
break;
}
}
let _ = (rnllen, ollen, ins_last, _p1, _j); }
pub fn moveto(row: usize, col: usize) {
let zr_cr = REFRESH_ELEMENT { chr: '\r', atr: 0 };
let zr_nl = REFRESH_ELEMENT { chr: '\n', atr: 0 };
let zr_sp = REFRESH_ELEMENT { chr: ' ', atr: 0 };
let ln = row as i32;
let cl = col as i32;
let winw = WINW.load(Ordering::SeqCst);
let hasam_v = crate::ported::init::hasam.load(Ordering::SeqCst) != 0;
let mut vcs = VCS.load(Ordering::SeqCst);
let mut vln = VLN.load(Ordering::SeqCst);
if vcs == winw {
vln += 1; vcs = 0;
if !hasam_v {
zwcputc(&zr_cr);
zwcputc(&zr_nl);
} else {
let nlnct = NLNCT.load(Ordering::SeqCst);
let rep = {
let nbuf = NBUF.lock().unwrap();
nbuf.get(vln as usize)
.filter(|_| vln < nlnct)
.and_then(|r| r.first())
.copied()
.filter(|c| c.chr != '\0')
.unwrap_or(zr_sp)
};
zwcputc(&rep); zwcputc(&zr_cr); let olnct = OLNCT.load(Ordering::SeqCst);
if vln < olnct {
let mut obuf = OBUF.lock().unwrap();
if let Some(orow) = obuf.get_mut(vln as usize) {
if let Some(first) = orow.first_mut() {
if first.chr != '\0' {
*first = rep;
}
}
}
}
}
VLN.store(vln, Ordering::SeqCst);
VCS.store(vcs, Ordering::SeqCst);
}
if ln == vln && cl == vcs {
return;
}
if ln < vln {
tc_upcurs(vln - ln); vln = ln; VLN.store(vln, Ordering::SeqCst);
}
while ln > vln {
let vmaxln = VMAXLN.load(Ordering::SeqCst);
if vln < vmaxln - 1 {
if ln > vmaxln - 1 {
if tc_downcurs(vmaxln - 1 - vln) != 0 {
vcs = 0;
VCS.store(0, Ordering::SeqCst);
}
vln = vmaxln - 1;
VLN.store(vln, Ordering::SeqCst);
} else {
if tc_downcurs(ln - vln) != 0 {
vcs = 0;
VCS.store(0, Ordering::SeqCst);
}
vln = ln;
VLN.store(vln, Ordering::SeqCst);
continue;
}
}
zwcputc(&zr_cr);
vcs = 0;
VCS.store(0, Ordering::SeqCst);
while ln > vln {
zwcputc(&zr_nl);
vln += 1;
}
VLN.store(vln, Ordering::SeqCst);
}
if cl != vcs {
singmoveto(cl);
}
}
pub fn tcoutarg(cap: i32, arg: i32) {
use crate::ported::init::tcstr;
use crate::ported::zsh_h::TC_COUNT;
use std::ffi::{CStr, CString};
extern "C" {
fn tgoto(
cap: *const libc::c_char,
col: libc::c_int,
row: libc::c_int,
) -> *mut libc::c_char;
}
let cap_idx = cap as usize;
if cap_idx >= TC_COUNT as usize {
return;
}
let cap_str = tcstr.lock().unwrap()[cap_idx].clone();
if cap_str.is_empty() {
return;
}
let c_cap = match CString::new(cap_str) {
Ok(c) => c,
Err(_) => return,
};
let result = unsafe { tgoto(c_cap.as_ptr(), arg as libc::c_int, arg as libc::c_int) };
if result.is_null() {
return;
}
let bytes = unsafe { CStr::from_ptr(result) }.to_bytes();
let fd = SHTTY.load(Ordering::Relaxed);
let out_fd = if fd >= 0 { fd } else { 1 };
let _ = write_loop(out_fd, bytes);
}
pub fn tcmultout(cap: i32, multcap: i32, ct: i32) -> i32 {
use crate::ported::init::{tclen, tcstr};
use crate::ported::zsh_h::TC_COUNT;
if ct <= 0 {
return 0;
}
let cap_idx = cap as usize;
let multcap_idx = multcap as usize;
let count = TC_COUNT as usize;
let (cap_str, cap_len) = if cap_idx < count {
let s = tcstr.lock().unwrap()[cap_idx].clone();
let l = tclen.lock().unwrap()[cap_idx];
(s, l)
} else {
(String::new(), 0)
};
let (mult_str, mult_len) = if multcap_idx < count {
let s = tcstr.lock().unwrap()[multcap_idx].clone();
let l = tclen.lock().unwrap()[multcap_idx];
(s, l)
} else {
(String::new(), 0)
};
let fd = SHTTY.load(Ordering::Relaxed);
let out_fd = if fd >= 0 { fd } else { 1 };
let mult_ok = mult_len > 0;
let cap_ok = cap_len > 0;
let _ = mult_str;
if mult_ok && (!cap_ok || mult_len <= cap_len * ct) {
tcoutarg(multcap, ct);
return 1;
} else if cap_ok {
for _ in 0..ct {
let _ = write_loop(out_fd, cap_str.as_bytes());
}
return 1;
}
0
}
pub fn tc_rightcurs(count: usize) {
if count == 0 {
return;
}
use crate::ported::init::tclen;
use crate::ported::params::TERMFLAGS;
use crate::ported::zsh_h::{TCHORIZPOS, TCMULTRIGHT, TCNEXTTAB, TCRIGHT, TERM_SHORT};
let zr_cr = REFRESH_ELEMENT { chr: '\r', atr: 0 };
let zr_sp = REFRESH_ELEMENT { chr: ' ', atr: 0 };
let mut ct = count as i32; let vcs = VCS.load(Ordering::SeqCst);
let vln = VLN.load(Ordering::SeqCst);
let winw = WINW.load(Ordering::SeqCst);
let mut i = vcs; let cl = ct + vcs; let out_fd = {
let f = SHTTY.load(Ordering::Relaxed);
if f >= 0 {
f
} else {
1
}
};
if tclen.lock().unwrap()[TCMULTRIGHT as usize] != 0 {
tcoutarg(TCMULTRIGHT, ct);
return;
}
if tclen.lock().unwrap()[TCHORIZPOS as usize] != 0 {
tcoutarg(TCHORIZPOS, cl);
return;
}
if OXTABS.load(Ordering::SeqCst) == 0
&& tclen.lock().unwrap()[TCNEXTTAB as usize] != 0
&& (vcs | 7) < cl
{
i = (vcs | 7) + 1; tcout(TCNEXTTAB); while i + 8 <= cl {
tcout(TCNEXTTAB);
i += 8;
}
ct = cl - i; if ct == 0 {
return; }
}
let lpromptw = LPROMPTW.load(Ordering::SeqCst);
if vln == 0 && i < lpromptw && (TERMFLAGS.load(Ordering::Relaxed) & TERM_SHORT) == 0 {
let lpromptbuf = crate::ported::zle::zle_main::prompt();
let tcright_len = tclen.lock().unwrap()[TCRIGHT as usize];
if tcright_len != 0
&& (tcright_len * ct) as usize <= crate::ported::utils::ztrlen(&lpromptbuf)
{
let mut k = lpromptw - i;
while k > 0 {
tcout(TCRIGHT);
k -= 1;
}
} else {
if i != 0 {
zwcputc(&zr_cr); }
tc_upcurs(LPROMPTH.load(Ordering::SeqCst) - 1); let _ = write_loop(out_fd, lpromptbuf.as_bytes()); if LPROMPTWOF.load(Ordering::SeqCst) == winw {
let _ = write_loop(out_fd, b"\n");
}
}
i = lpromptw; ct = cl - i; }
let row = NBUF.lock().unwrap().get(vln as usize).cloned(); if let Some(t) = row {
let mut idx = 0usize;
let mut j = 0i32;
while idx < t.len() && t[idx].chr != '\0' && j < i {
j += 1;
idx += 1;
}
if j == i {
while idx < t.len() && t[idx].chr != '\0' && ct > 0 {
zwcputc(&t[idx]);
ct -= 1;
idx += 1;
}
}
}
while ct > 0 {
zwcputc(&zr_sp);
ct -= 1;
}
}
pub fn tc_downcurs(ct: i32) -> i32 {
let mut ret = 0; if ct != 0 && tcmultout(crate::ported::zsh_h::TCDOWN, crate::ported::zsh_h::TCMULTDOWN, ct) == 0
{
let mut c = ct; while c > 0 {
zwcputc(&REFRESH_ELEMENT { chr: '\n', atr: 0 }); c -= 1;
}
zwcputc(&REFRESH_ELEMENT { chr: '\r', atr: 0 }); ret = -1; }
ret }
pub fn tcout_via_func(cap: i32, arg: i32) -> i32 {
use crate::ported::builtin::STOPMSG;
use crate::ported::exec::sfcontext;
use crate::ported::init::tccap_get_name;
use crate::ported::params::getsparam;
use crate::ported::utils::{callhookfunc, getshfunc, write_loop, INCOMPFUNC};
use crate::ported::zsh_h::SFC_SUBST;
let osc = sfcontext.load(Ordering::Relaxed);
let osm = STOPMSG.load(Ordering::Relaxed);
let old_incompfunc = INCOMPFUNC.load(Ordering::Relaxed);
sfcontext.store(SFC_SUBST, Ordering::Relaxed);
INCOMPFUNC.store(0, Ordering::Relaxed);
let func_name = TCOUT_FUNC_NAME
.lock()
.unwrap()
.clone()
.unwrap_or_else(|| "tcout".to_string());
let dispatched = if getshfunc(&func_name).is_some() {
let mut argv: Vec<String> = Vec::with_capacity(3);
argv.push(func_name.clone()); argv.push(tccap_get_name(cap as usize).to_string()); if arg != -1 {
argv.push(arg.to_string());
}
let shf_clone: Option<crate::ported::zsh_h::shfunc> =
crate::ported::hashtable::shfunctab_lock()
.read()
.ok()
.and_then(|t| t.get(&func_name).cloned());
if let Some(mut shf) = shf_clone {
let name_for_body = func_name.clone();
let body_args = argv.clone();
let body_runner = move || -> i32 {
crate::ported::exec::run_function_body(&name_for_body, &body_args[1..])
.unwrap_or(0)
};
let _ = crate::ported::exec::doshfunc(&mut shf, argv.clone(), true, body_runner);
} else {
let _ = callhookfunc(&func_name, Some(&argv), 0, std::ptr::null_mut());
}
if let Some(reply) = getsparam("REPLY") {
let bytes = reply.as_bytes();
let mut out: Vec<u8> = Vec::with_capacity(bytes.len());
let mut i = 0;
while i < bytes.len() {
if bytes[i] == 0x83 && i + 1 < bytes.len() {
out.push(bytes[i + 1] ^ 32); i += 2;
} else {
out.push(bytes[i]); i += 1;
}
}
let fd = SHTTY.load(Ordering::Relaxed);
let out_fd = if fd >= 0 { fd } else { 1 };
let _ = write_loop(out_fd, &out);
}
true
} else {
false
};
sfcontext.store(osc, Ordering::Relaxed);
STOPMSG.store(osm, Ordering::Relaxed);
INCOMPFUNC.store(old_incompfunc, Ordering::Relaxed);
if dispatched {
0
} else {
1
}
}
pub fn tcout(cap: i32) {
use crate::ported::init::tcstr;
use crate::ported::zsh_h::TC_COUNT;
let cap_idx = cap as usize;
if cap_idx >= TC_COUNT as usize {
return;
}
let escape = tcstr.lock().unwrap()[cap_idx].clone();
if escape.is_empty() {
return;
}
let fd = SHTTY.load(Ordering::Relaxed);
let out_fd = if fd >= 0 { fd } else { 1 };
let _ = write_loop(out_fd, escape.as_bytes());
}
pub fn clearscreen() -> i32 {
tcoutclear(crate::ported::zsh_h::TCCLEARSCREEN);
RESETNEEDED.store(1, Ordering::SeqCst); CLEARFLAG.store(0, Ordering::SeqCst); crate::ported::zle::zle_main::reexpandprompt(); zrefresh();
0 }
pub fn redisplay() -> i32 {
moveto(0, 0); zwcputc(&REFRESH_ELEMENT { chr: '\r', atr: 0 }); let lprompth = LPROMPTH.load(Ordering::SeqCst);
tc_upcurs(lprompth - 1); RESETNEEDED.store(1, Ordering::SeqCst); CLEARFLAG.store(0, Ordering::SeqCst); zrefresh();
0
}
pub fn singlerefresh(tmpline: &[char], tmpll: i32, mut tmpcs: i32) {
let mut vbuf: REFRESH_STRING; let mut vp: usize; let _refreshop: REFRESH_STRING = Vec::new(); let mut t0: i32; let mut vsiz: i32; let mut nvcs: i32 = 0; let owinpos: i32 = WINPOS.load(Ordering::SeqCst); let owinprompt: i32 = WINPROMPT.load(Ordering::SeqCst); let mut width: i32 = 0;
NLNCT.store(1, Ordering::SeqCst);
let lpromptw = LPROMPTW.load(Ordering::SeqCst);
vsiz = 1 + lpromptw; t0 = 0;
while t0 != tmpll {
let ch = *tmpline.get(t0 as usize).unwrap_or(&'\0');
if ch == '\t' {
vsiz = (vsiz | 7) + 2; } else if ch.is_alphanumeric() || ch.is_ascii_graphic() {
width = unicode_width::UnicodeWidthChar::width(ch) .unwrap_or(1) as i32;
if width > 0 {
vsiz += width; if isset(COMBININGCHARS) {
while t0 < tmpll - 1 {
let next = *tmpline.get((t0 + 1) as usize).unwrap_or(&'\0');
if !crate::ported::zsh_h::IS_COMBINING(next) {
break;
}
t0 += 1; }
}
}
} else if (ch as u32) < 0x20 || (ch as u32) == 0x7F {
if (ch as u32) <= 0xff {
vsiz += 2; }
} else {
vsiz += 10; }
t0 += 1;
}
vbuf = vec![REFRESH_ELEMENT { chr: '\0', atr: 0 }; vsiz as usize];
if tmpcs < 0 {
tmpcs = 0; }
for k in 0..(lpromptw as usize).min(vbuf.len()) {
vbuf[k] = REFRESH_ELEMENT { chr: ' ', atr: 0 };
}
vp = lpromptw as usize; if vp < vbuf.len() {
vbuf[vp] = REFRESH_ELEMENT { chr: '\0', atr: 0 }; }
t0 = 0;
while t0 < tmpll {
let base_attr: u64 = 0; let all_attr: u64 = 0;
if t0 == tmpcs {
nvcs = vp as i32; }
let ch = *tmpline.get(t0 as usize).unwrap_or(&'\0');
if ch == '\t' {
if vp < vbuf.len() {
vbuf[vp] = REFRESH_ELEMENT {
chr: ' ',
atr: base_attr,
};
vp += 1; }
while (vp & 7) != 0 && vp < vbuf.len() {
vbuf[vp] = REFRESH_ELEMENT {
chr: ' ',
atr: base_attr,
};
vp += 1; }
} else if ch == '\n' {
if vp < vbuf.len() {
vbuf[vp] = REFRESH_ELEMENT {
chr: '\\',
atr: all_attr,
}; vp += 1; }
if vp < vbuf.len() {
vbuf[vp] = REFRESH_ELEMENT {
chr: 'n',
atr: all_attr,
}; vp += 1; }
} else if ch.is_ascii_graphic() || (ch.is_alphanumeric()) {
width = unicode_width::UnicodeWidthChar::width(ch) .unwrap_or(1) as i32;
if width > 0 {
let mut ichars: i32 = 1; if isset(COMBININGCHARS) {
while (t0 + ichars) < tmpll {
let nxt = *tmpline.get((t0 + ichars) as usize).unwrap_or(&'\0');
if !crate::ported::zsh_h::IS_COMBINING(nxt) {
break;
}
ichars += 1; }
}
if vp < vbuf.len() {
if ichars > 1 {
let cluster: Vec<char> =
tmpline[t0 as usize..(t0 + ichars) as usize].to_vec();
let mut cell = REFRESH_ELEMENT { chr: ch, atr: base_attr };
addmultiword(&mut cell, &cluster, ichars as usize);
vbuf[vp] = cell;
} else {
vbuf[vp] = REFRESH_ELEMENT {
chr: ch,
atr: base_attr,
}; }
vp += 1; }
let mut w = width - 1;
while w > 0 {
if vp < vbuf.len() {
vbuf[vp] = REFRESH_ELEMENT {
chr: ZWC_WEOF,
atr: base_attr,
}; vp += 1; }
w -= 1;
}
t0 += ichars - 1; }
} else if (ch as u32) < 0x20 || (ch as u32) == 0x7F {
if (ch as u32) <= 0xff {
let t = ch as u32; if vp < vbuf.len() {
vbuf[vp] = REFRESH_ELEMENT {
chr: '^',
atr: all_attr,
}; vp += 1; }
let display: char = if (t & !0x80) > 31 {
'?'
} else {
((t | 0x40) as u8) as char };
if vp < vbuf.len() {
vbuf[vp] = REFRESH_ELEMENT {
chr: display,
atr: all_attr,
};
vp += 1; }
}
} else {
let hex = if (ch as u32) > 0xFFFF {
format!("<{:08x}>", ch as u32) } else {
format!("<{:04x}>", ch as u32) };
for c in hex.chars() {
if vp < vbuf.len() {
vbuf[vp] = REFRESH_ELEMENT {
chr: c,
atr: all_attr,
}; vp += 1; }
}
}
t0 += 1;
}
if t0 == tmpcs {
nvcs = vp as i32; }
if vp < vbuf.len() {
vbuf[vp] = REFRESH_ELEMENT { chr: '\0', atr: 0 }; }
let mut winpos: i32 = WINPOS.load(Ordering::SeqCst);
let winw = WINW.load(Ordering::SeqCst);
let hasam_v = crate::ported::init::hasam.load(Ordering::SeqCst);
if winpos == -1 {
winpos = 0; }
if (winpos != 0 && nvcs < winpos + 1) || (nvcs > winpos + winw - 2)
{
winpos = nvcs - ((winw - hasam_v) / 2); if winpos < 0 {
winpos = 0; }
}
if winpos != 0 && (winpos as usize) < vbuf.len() {
vbuf[winpos as usize] = REFRESH_ELEMENT { chr: '<', atr: 0 }; }
let suffix_start = winpos as usize;
let suffix_len = vbuf
.iter()
.skip(suffix_start)
.take_while(|e| e.chr != '\0')
.count();
let max_visible = (winw - hasam_v) as usize;
if suffix_len > max_visible {
let trunc_pos = suffix_start + max_visible - 1;
if trunc_pos < vbuf.len() {
vbuf[trunc_pos] = REFRESH_ELEMENT { chr: '>', atr: 0 }; }
if trunc_pos + 1 < vbuf.len() {
vbuf[trunc_pos + 1] = REFRESH_ELEMENT { chr: '\0', atr: 0 }; }
}
{
let start = winpos.max(0) as usize;
let src: &[REFRESH_ELEMENT] = if start < vbuf.len() {
&vbuf[start..]
} else {
&[]
};
let mut nbuf = NBUF.lock().unwrap();
if nbuf.is_empty() {
let w = (WINW.load(Ordering::SeqCst) + 2).max(1) as usize;
nbuf.push(vec![REFRESH_ELEMENT::default(); w]);
}
if let Some(row) = nbuf.get_mut(0) {
ZR_strcpy(row, src);
}
}
drop(vbuf); nvcs -= winpos;
WINPOS.store(winpos, Ordering::SeqCst);
let winprompt: i32 = if winpos < lpromptw {
lpromptw - winpos } else {
0 };
WINPROMPT.store(winprompt, Ordering::SeqCst);
if winpos != owinpos && winprompt != 0 {
singmoveto(0);
VCS.store(winprompt, Ordering::SeqCst);
}
let nl0: REFRESH_STRING = NBUF.lock().unwrap().get(0).cloned().unwrap_or_default();
let ol0: REFRESH_STRING = OBUF.lock().unwrap().get(0).cloned().unwrap_or_default();
let zr_sp = REFRESH_ELEMENT { chr: ' ', atr: 0 };
let mut t0c: i32 = winprompt; let mut vp = winprompt.max(0) as usize; let mut rp = winprompt.max(0) as usize; loop {
if (vp as i32) >= owinprompt {
while vp < nl0.len()
&& nl0[vp].chr != '\0'
&& rp < ol0.len()
&& ol0[rp] == nl0[vp]
{
t0c += 1; vp += 1;
rp += 1;
}
}
let nchr = nl0.get(vp).map(|c| c.chr).unwrap_or('\0');
let ochr = ol0.get(rp).map(|c| c.chr).unwrap_or('\0');
if nchr == '\0' && ochr == '\0' {
break;
}
singmoveto(t0c); if ochr == '\0' {
let rest = &nl0[vp..];
let len = ZR_strlen(rest); if len != 0 {
zwcwrite(rest, len); }
VCS.fetch_add(len as i32, Ordering::SeqCst); break; }
if nchr == '\0' {
let tcleareol = tclen.lock().unwrap()[TCCLEAREOL as usize];
if tcleareol != 0 {
tcoutclear(TCCLEAREOL); } else {
let mut k = rp;
while k < ol0.len() && ol0[k].chr != '\0' {
zwcputc(&zr_sp); VCS.fetch_add(1, Ordering::SeqCst);
k += 1;
}
}
break; }
zwcputc(&nl0[vp]); VCS.fetch_add(1, Ordering::SeqCst); t0c += 1; vp += 1; rp += 1; }
singmoveto(nvcs);
bufswap();
let _ = (lpromptw, width); }
pub fn singmoveto(pos: i32) {
use crate::ported::init::tclen;
use crate::ported::zsh_h::TCMULTLEFT;
let vcs = VCS.load(Ordering::SeqCst);
if pos == vcs {
return;
}
let multleft_present = tclen.lock().unwrap()[TCMULTLEFT as usize] > 0;
let mut cur = vcs;
if (!multleft_present || pos == 0) && pos <= cur / 2 {
let fd = SHTTY.load(Ordering::Relaxed);
let out_fd = if fd >= 0 { fd } else { 1 };
let _ = write_loop(out_fd, b"\r"); cur = 0;
VCS.store(0, Ordering::SeqCst); }
if pos < cur {
tc_leftcurs(cur - pos);
} else if pos > cur {
tc_rightcurs((pos - cur) as usize);
}
VCS.store(pos, Ordering::SeqCst);
}
pub fn zle_refresh_boot() -> RefreshState {
RefreshState::new()
}
pub fn zle_refresh_finish() {
freevideo(); let mut rh = REGION_HIGHLIGHTS.lock().unwrap(); if !rh.is_empty() {
free_region_highlights_memos(); rh.clear(); }
drop(rh);
crate::ported::zle::termquery::free_cursor_forms(); }
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct TextAttr {
pub bold: bool,
pub underline: bool,
pub standout: bool,
pub blink: bool,
pub fg_color: Option<u8>,
pub bg_color: Option<u8>,
}
#[derive(Debug, Clone, Default)]
pub struct RefreshElement {
pub chr: char,
pub atr: TextAttr,
pub width: u8,
}
#[derive(Debug, Clone)]
pub struct VideoBuffer {
pub lines: Vec<Vec<RefreshElement>>,
pub cols: usize,
pub rows: usize,
}
#[derive(Debug, Clone, Default)]
pub struct RefreshState {
pub columns: usize, pub lines: usize, pub vln: usize, pub vcs: usize, pub lpromptw: usize, pub rpromptw: usize, pub scrolloff: usize,
pub region_highlight_start: Option<usize>,
pub region_highlight_end: Option<usize>,
pub old_video: Option<VideoBuffer>,
pub new_video: Option<VideoBuffer>,
pub lpromptbuf: String,
pub rpromptbuf: String,
pub need_full_redraw: bool,
pub predisplay: String,
pub postdisplay: String,
}
#[derive(Debug, Clone)]
pub struct RegionHighlight {
pub start: usize,
pub end: usize,
pub attr: TextAttr,
pub memo: Option<String>,
pub flags: i32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HighlightCategory {
Region,
Isearch,
Suffix,
Paste,
Default,
Special,
Ellipsis,
}
#[derive(Debug, Default)]
pub struct HighlightManager {
pub regions: Vec<RegionHighlight>,
pub category_attrs: std::collections::HashMap<HighlightCategory, TextAttr>,
}
pub fn compute_render_attrs() -> Vec<Option<TextAttr>> {
let buf_len = ZLELINE.lock().unwrap().len();
let mut attrs: Vec<Option<TextAttr>> = vec![None; buf_len];
let visual_attr = highlight()
.lock()
.unwrap()
.category_attrs
.get(&HighlightCategory::Region)
.copied()
.unwrap_or(TextAttr {
standout: true,
..TextAttr::default()
});
if REGION_ACTIVE.load(Ordering::SeqCst) != 0 {
let (lo, hi) = if MARK.load(Ordering::SeqCst) <= ZLECS.load(Ordering::SeqCst) {
(MARK.load(Ordering::SeqCst), ZLECS.load(Ordering::SeqCst))
} else {
(ZLECS.load(Ordering::SeqCst), MARK.load(Ordering::SeqCst))
};
let lo = lo.min(buf_len);
let hi = hi.min(buf_len);
for slot in attrs.iter_mut().take(hi).skip(lo) {
*slot = Some(visual_attr);
}
}
for region in &highlight().lock().unwrap().regions {
let start = region.start.min(buf_len);
let end = region.end.min(buf_len);
for slot in attrs.iter_mut().take(end).skip(start) {
*slot = Some(region.attr);
}
}
attrs
}
pub fn full_refresh() -> io::Result<()> {
let fd = SHTTY.load(Ordering::Relaxed);
let out = if fd >= 0 { fd } else { 1 };
let _ = write_loop(out, b"\x1b[2J\x1b[H");
zrefresh();
Ok(())
}
pub fn partial_refresh() -> io::Result<()> {
zrefresh();
Ok(())
}
fn countprompt(s: &str) -> usize {
let mut chars = s.chars().peekable();
let mut width: usize = 0;
while let Some(c) = chars.next() {
if c == '\x1b' {
if chars.peek() == Some(&'[') {
chars.next();
while let Some(&nxt) = chars.peek() {
chars.next();
if nxt.is_ascii_alphabetic() {
break;
}
}
}
continue;
}
width += unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
}
width
}
pub fn match_highlight(spec: &str) -> TextAttr {
let mut attr = TextAttr::default();
for token in spec.split(',') {
let token = token.trim();
if token.is_empty() {
continue;
}
match token {
"none" => {
attr = TextAttr::default();
}
"bold" => attr.bold = true,
"nobold" => attr.bold = false,
"underline" => attr.underline = true,
"nounderline" => attr.underline = false,
"standout" => attr.standout = true,
"nostandout" => attr.standout = false,
"blink" => attr.blink = true,
"noblink" => attr.blink = false,
other => {
let parse = |name: &str| -> Option<u8> {
match name {
"black" => Some(0),
"red" => Some(1),
"green" => Some(2),
"yellow" => Some(3),
"blue" => Some(4),
"magenta" => Some(5),
"cyan" => Some(6),
"white" => Some(7),
"default" => None,
n => n.parse::<u8>().ok(),
}
};
if let Some(rest) = other.strip_prefix("fg=") {
attr.fg_color = parse(rest);
} else if let Some(rest) = other.strip_prefix("bg=") {
attr.bg_color = parse(rest);
}
}
}
}
attr
}
#[inline]
#[allow(non_snake_case)]
pub fn ZR_equal(
a: REFRESH_ELEMENT,
b: REFRESH_ELEMENT,
) -> bool {
a == b
}
#[inline]
#[allow(non_snake_case)]
pub fn ZR_memcpy(
dst: &mut [REFRESH_ELEMENT],
src: &[REFRESH_ELEMENT],
l: usize,
) {
dst[..l].copy_from_slice(&src[..l]);
}
pub static ZR_END_ELLIPSIS: &[REFRESH_ELEMENT] = &[
REFRESH_ELEMENT { chr: ' ', atr: 0 },
REFRESH_ELEMENT {
chr: '.',
atr: TXT_ERROR,
},
REFRESH_ELEMENT {
chr: '.',
atr: TXT_ERROR,
},
REFRESH_ELEMENT {
chr: '.',
atr: TXT_ERROR,
},
REFRESH_ELEMENT {
chr: '.',
atr: TXT_ERROR,
},
REFRESH_ELEMENT { chr: '>', atr: 0 },
];
pub static ZR_MID_ELLIPSIS1: &[REFRESH_ELEMENT] = &[
REFRESH_ELEMENT { chr: ' ', atr: 0 },
REFRESH_ELEMENT { chr: '<', atr: 0 },
REFRESH_ELEMENT {
chr: '.',
atr: TXT_ERROR,
},
REFRESH_ELEMENT {
chr: '.',
atr: TXT_ERROR,
},
REFRESH_ELEMENT {
chr: '.',
atr: TXT_ERROR,
},
REFRESH_ELEMENT {
chr: '.',
atr: TXT_ERROR,
},
];
pub static ZR_MID_ELLIPSIS2: &[REFRESH_ELEMENT] = &[
REFRESH_ELEMENT {
chr: '>',
atr: TXT_ERROR,
},
REFRESH_ELEMENT { chr: ' ', atr: 0 },
];
pub static ZR_START_ELLIPSIS: &[REFRESH_ELEMENT] = &[
REFRESH_ELEMENT { chr: '>', atr: 0 },
REFRESH_ELEMENT {
chr: '.',
atr: TXT_ERROR,
},
REFRESH_ELEMENT {
chr: '.',
atr: TXT_ERROR,
},
REFRESH_ELEMENT {
chr: '.',
atr: TXT_ERROR,
},
REFRESH_ELEMENT {
chr: '.',
atr: TXT_ERROR,
},
];
#[inline]
pub fn tcinscost(x: i32) -> i32 {
use crate::ported::init::tclen;
use crate::ported::zsh_h::{TCINS, TCMULTINS};
let t = tclen.lock().unwrap();
if t[TCMULTINS as usize] != 0 {
t[TCMULTINS as usize]
} else {
x * t[TCINS as usize]
}
}
#[inline]
pub fn tcdelcost(x: i32) -> i32 {
use crate::ported::init::tclen;
use crate::ported::zsh_h::{TCDEL, TCMULTDEL};
let t = tclen.lock().unwrap();
if t[TCMULTDEL as usize] != 0 {
t[TCMULTDEL as usize]
} else {
x * t[TCDEL as usize]
}
}
#[inline]
pub fn tc_delchars(x: i32) {
let _ = tcmultout(
crate::ported::zsh_h::TCDEL,
crate::ported::zsh_h::TCMULTDEL,
x,
);
}
#[inline]
pub fn tc_inschars(x: i32) {
let _ = tcmultout(
crate::ported::zsh_h::TCINS,
crate::ported::zsh_h::TCMULTINS,
x,
);
}
#[inline]
pub fn tc_upcurs(x: i32) {
let _ = tcmultout(crate::ported::zsh_h::TCUP, crate::ported::zsh_h::TCMULTUP, x);
}
#[inline]
pub fn tc_leftcurs(x: i32) {
let _ = tcmultout(
crate::ported::zsh_h::TCLEFT,
crate::ported::zsh_h::TCMULTLEFT,
x,
);
}
pub static TCOUT_FUNC_NAME: std::sync::Mutex<Option<String>> = std::sync::Mutex::new(None);
pub static PMPT_ATTR: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); pub static RPMPT_ATTR: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); pub static PROMPT_ATTR: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
pub static ELLIPSIS_ATTR: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
pub static SPECIAL_ATTR: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
pub static CLEAREOL: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static CLEARF: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static PUT_RPMPT: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static OPUT_RPMPT: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static OXTABS: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static NUMSCROLLS: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static ONUMSCROLLS: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static NLNCT: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static NBUF: std::sync::Mutex<Vec<REFRESH_STRING>> = std::sync::Mutex::new(Vec::new()); pub static OBUF: std::sync::Mutex<Vec<REFRESH_STRING>> = std::sync::Mutex::new(Vec::new());
pub static WINW: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(80);
pub static WINH: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(24);
pub static MORE_START: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
pub static MORE_END: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
pub static RESETNEEDED: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
pub static LPROMPTW: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static VCS: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static VLN: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static SHOWINGLIST: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static LISTSHOWN: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static LASTLISTLEN: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static VMAXLN: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static WINPROMPT: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static WINPOS: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(-1);
pub static RWINH: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(24);
pub static LPROMPTWOF: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static LPROMPTH: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static RPROMPTW: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static RPROMPTH: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static OLNCT: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static TRASHEDZLE: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static CLEARFLAG: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static CLEARLIST: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
#[derive(Debug, Clone, Default)]
#[allow(non_camel_case_types)]
pub struct rparams {
pub canscroll: i32, pub ln: i32, pub more_status: i32, pub nvcs: i32, pub nvln: i32, pub tosln: i32, pub pos: usize, pub end: usize, }
pub fn set_region_highlight(aval: Option<&[String]>) {
let aval = match aval {
Some(a) => a,
None => {
REGION_HIGHLIGHTS.lock().unwrap().clear();
return; }
};
let mut rh = REGION_HIGHLIGHTS.lock().unwrap();
rh.clear(); for entry in aval.iter() {
let mut oldstrp: &str = entry.as_str(); let mut flags: i32 = 0; if oldstrp.starts_with('P') {
flags = ZRH_PREDISPLAY; oldstrp = &oldstrp[1..]; }
oldstrp = oldstrp.trim_start_matches(|c: char| c == ' ' || c == '\t'); let (start_val, rest1) = crate::ported::utils::zstrtol(oldstrp, 10); let start = if oldstrp.len() == rest1.len() {
-1i32
} else {
start_val as i32
}; let strp = rest1.trim_start_matches(|c: char| c == ' ' || c == '\t'); let (end_val, rest2) = crate::ported::utils::zstrtol(strp, 10); let end = if strp.len() == rest2.len() {
-1i32
} else {
end_val as i32
}; let strp = rest2.trim_start_matches(|c: char| c == ' ' || c == '\t'); let attr = match_highlight(strp);
let memo = if let Some(rest) = strp.strip_prefix("memo=") {
let end_pos = rest
.find(|c: char| c == ',' || c == ' ' || c == '\t' || c == '\0')
.unwrap_or(rest.len());
Some(rest[..end_pos].to_string()) } else {
None
}; if start >= 0 && end >= 0 {
rh.push(RegionHighlight {
start: start as usize,
end: end as usize,
attr,
memo,
flags, });
}
}
}
pub fn unset_region_highlight(pm: &mut crate::ported::zsh_h::param, exp: i32) {
if exp != 0 {
set_region_highlight(None); crate::ported::params::stdunsetfn(pm, exp); }
}
pub fn get_region_highlight(_pm: &crate::ported::zsh_h::param) -> Vec<String> {
use crate::ported::zsh_h::{
zattr, TXTBGCOLOUR, TXTBOLDFACE, TXTFGCOLOUR, TXTSTANDOUT, TXTUNDERLINE,
TXT_ATTR_BG_COL_SHIFT, TXT_ATTR_FG_COL_SHIFT,
};
let rh = REGION_HIGHLIGHTS.lock().unwrap();
rh.iter()
.map(|rhp| {
let mut s = String::new();
if rhp.flags & ZRH_PREDISPLAY != 0 {
s.push('P'); }
s.push_str(&format!("{} {} ", rhp.start, rhp.end));
let ta = &rhp.attr;
let mut atr: zattr = 0;
let mut mask: zattr = 0;
if ta.bold {
atr |= TXTBOLDFACE;
mask |= TXTBOLDFACE;
}
if ta.underline {
atr |= TXTUNDERLINE;
mask |= TXTUNDERLINE;
}
if ta.standout {
atr |= TXTSTANDOUT;
mask |= TXTSTANDOUT;
}
if let Some(fg) = ta.fg_color {
atr |= TXTFGCOLOUR | ((fg as zattr) << TXT_ATTR_FG_COL_SHIFT);
mask |= TXTFGCOLOUR;
}
if let Some(bg) = ta.bg_color {
atr |= TXTBGCOLOUR | ((bg as zattr) << TXT_ATTR_BG_COL_SHIFT);
mask |= TXTBGCOLOUR;
}
s.push_str(&crate::ported::prompt::output_highlight(atr, mask));
if let Some(memo) = &rhp.memo {
s.push_str(" memo=");
s.push_str(memo);
}
s
})
.collect()
}
pub static REGION_HIGHLIGHTS: once_cell::sync::Lazy<std::sync::Mutex<Vec<RegionHighlight>>> =
once_cell::sync::Lazy::new(|| std::sync::Mutex::new(Vec::new()));
pub const ZRH_PREDISPLAY: i32 = 1;
#[cfg(test)]
mod zr_tests {
use super::*;
use crate::ported::zle::zle_h::REFRESH_ELEMENT;
use crate::ported::zsh_h::{TXTBOLDFACE, TXT_MULTIWORD_MASK};
fn re(c: char, a: u64) -> REFRESH_ELEMENT {
REFRESH_ELEMENT { chr: c, atr: a }
}
#[test]
fn zr_memset_fills_slice() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut buf = [REFRESH_ELEMENT::default(); 4];
let fill = re('x', 0);
ZR_memset(&mut buf, fill, 3);
assert_eq!(buf[0], fill);
assert_eq!(buf[1], fill);
assert_eq!(buf[2], fill);
assert_eq!(buf[3], REFRESH_ELEMENT::default());
}
#[test]
fn zr_memset_clamps_to_dst_len() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut buf = [REFRESH_ELEMENT::default(); 2];
let fill = re('y', 0);
ZR_memset(&mut buf, fill, 99); assert_eq!(buf[0], fill);
assert_eq!(buf[1], fill);
}
#[test]
fn zr_strlen_counts_to_nul() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let s = [re('h', 0), re('i', 0), re('\0', 0)];
assert_eq!(ZR_strlen(&s), 2);
}
#[test]
fn zr_strlen_empty_starts_with_nul() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let s = [re('\0', 0)];
assert_eq!(ZR_strlen(&s), 0);
}
#[test]
fn zr_strcpy_copies_through_nul() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let src = [re('a', 0), re('b', 0), re('\0', 0)];
let mut dst = [REFRESH_ELEMENT::default(); 5];
ZR_strcpy(&mut dst, &src);
assert_eq!(dst[0], re('a', 0));
assert_eq!(dst[1], re('b', 0));
assert_eq!(dst[2], re('\0', 0));
}
#[test]
fn zr_strncmp_equal_strings() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let a = [re('h', 0), re('i', 0)];
let b = [re('h', 0), re('i', 0)];
assert_eq!(ZR_strncmp(&a, &b, 2), 0);
}
#[test]
fn zr_strncmp_diff_chr_returns_1() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let a = [re('h', 0), re('i', 0)];
let b = [re('h', 0), re('o', 0)];
assert_eq!(ZR_strncmp(&a, &b, 2), 1);
}
#[test]
fn zr_strncmp_diff_atr_returns_1() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let a = [re('h', 0)];
let b = [re('h', TXTBOLDFACE)];
assert_eq!(ZR_strncmp(&a, &b, 1), 1);
}
#[test]
fn zr_strncmp_early_nul_old() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let a = [re('\0', 0)];
let b = [re('x', 0)];
assert_eq!(ZR_strncmp(&a, &b, 1), 1); let a = [re('\0', 0)];
let b = [re('\0', 0)];
assert_eq!(ZR_strncmp(&a, &b, 1), 0); }
#[test]
fn zr_strncmp_multiword_mask_skips_nul_check() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let a = [re('\0', TXT_MULTIWORD_MASK)];
let b = [re('\0', TXT_MULTIWORD_MASK)];
assert_eq!(ZR_strncmp(&a, &b, 1), 0);
}
#[test]
fn zr_equal_same_returns_true() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let a = re('a', 0);
assert!(ZR_equal(a, a));
let b = re('b', 0);
assert!(!ZR_equal(a, b));
}
#[test]
fn zr_memcpy_copies_n_elements() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut dst = [re('\0', 0); 5];
let src = [re('a', 0), re('b', 0), re('c', 0), re('d', 0), re('e', 0)];
ZR_memcpy(&mut dst, &src, 3);
assert_eq!(dst[0].chr, 'a');
assert_eq!(dst[1].chr, 'b');
assert_eq!(dst[2].chr, 'c');
assert_eq!(dst[3].chr, '\0');
}
#[test]
fn ellipsis_sizes_match_table_lengths() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(ZR_END_ELLIPSIS_SIZE, 6);
assert_eq!(ZR_MID_ELLIPSIS1_SIZE, 6);
assert_eq!(ZR_MID_ELLIPSIS2_SIZE, 2);
assert_eq!(ZR_START_ELLIPSIS_SIZE, 5);
}
#[test]
fn def_mwbuf_alloc_is_32() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(DEF_MWBUF_ALLOC, 32);
}
#[test]
fn tc_costs_handle_negative() {
use crate::ported::init::tclen;
use crate::ported::zsh_h::{TCDEL, TCINS, TCMULTDEL, TCMULTINS};
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let save = {
let t = tclen.lock().unwrap();
(
t[TCMULTINS as usize],
t[TCINS as usize],
t[TCMULTDEL as usize],
t[TCDEL as usize],
)
};
{
let mut t = tclen.lock().unwrap();
t[TCMULTINS as usize] = 0; t[TCINS as usize] = 1;
t[TCMULTDEL as usize] = 0;
t[TCDEL as usize] = 1;
}
assert_eq!(tcinscost(-1), -1); assert_eq!(tcdelcost(-1), -1);
assert_eq!(tcinscost(5), 5);
assert_eq!(tcdelcost(5), 5);
let mut t = tclen.lock().unwrap();
t[TCMULTINS as usize] = save.0;
t[TCINS as usize] = save.1;
t[TCMULTDEL as usize] = save.2;
t[TCDEL as usize] = save.3;
}
#[test]
fn rparams_default_zeros_all_fields() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let r = rparams::default();
assert_eq!(r.canscroll, 0);
assert_eq!(r.ln, 0);
assert_eq!(r.more_status, 0);
assert_eq!(r.nvcs, 0);
assert_eq!(r.nvln, 0);
assert_eq!(r.tosln, 0);
assert_eq!(r.pos, 0);
assert_eq!(r.end, 0);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zrefresh_builds_nbuf_cells() {
let _g = crate::test_util::global_state_lock();
*ZLELINE.lock().unwrap() = "ab\tc\u{1}d".chars().collect();
ZLECS.store(0, Ordering::SeqCst);
ZLELL.store(6, Ordering::SeqCst);
zrefresh();
let nbuf = NBUF.lock().unwrap();
let row0: String = nbuf
.first()
.map(|r| r.iter().map(|c| c.chr).take_while(|&c| c != '\0').collect())
.unwrap_or_default();
assert!(
row0.contains("ab") && row0.ends_with("c^Ad"),
"NBUF row 0 should render the line with tab + ^A expansion, got {:?}",
row0
);
}
#[test]
fn zrefresh_clusters_combining_chars_in_nbuf() {
let _g = crate::test_util::global_state_lock();
let cc = crate::ported::zsh_h::opt_name(crate::ported::zsh_h::COMBININGCHARS);
crate::ported::options::opt_state_set(cc, true);
*ZLELINE.lock().unwrap() = "e\u{0301}".chars().collect(); ZLECS.store(2, Ordering::SeqCst);
ZLELL.store(2, Ordering::SeqCst);
*NBUF.lock().unwrap() = vec![];
*OBUF.lock().unwrap() = vec![];
let devnull =
unsafe { libc::open(b"/dev/null\0".as_ptr() as *const _, libc::O_WRONLY) };
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(devnull, Ordering::SeqCst);
zrefresh();
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(devnull) };
crate::ported::options::opt_state_set(cc, false);
let has_multiword = NBUF
.lock()
.unwrap()
.iter()
.flat_map(|r| r.iter())
.any(|c| c.atr & TXT_MULTIWORD_MASK != 0);
assert!(
has_multiword,
"COMBININGCHARS build must cluster e+U+0301 into a TXT_MULTIWORD_MASK cell"
);
}
#[test]
fn zrefresh_wide_char_pads_weof_in_nbuf() {
let _g = crate::test_util::global_state_lock();
*ZLELINE.lock().unwrap() = "日a".chars().collect();
ZLECS.store(0, Ordering::SeqCst);
ZLELL.store(2, Ordering::SeqCst);
*NBUF.lock().unwrap() = vec![];
*OBUF.lock().unwrap() = vec![];
let devnull =
unsafe { libc::open(b"/dev/null\0".as_ptr() as *const _, libc::O_WRONLY) };
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(devnull, Ordering::SeqCst);
zrefresh();
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(devnull) };
let row0 = NBUF.lock().unwrap().first().cloned().unwrap_or_default();
let idx = row0
.iter()
.position(|c| c.chr == '日')
.expect("ideograph must be in row 0");
assert_eq!(
row0.get(idx + 1).map(|c| c.chr),
Some(ZWC_WEOF),
"wide glyph's second column must be a WEOF placeholder"
);
assert_eq!(
row0.get(idx + 2).map(|c| c.chr),
Some('a'),
"'a' must follow the WEOF placeholder, not sit in the wide char's column"
);
}
#[test]
fn zrefresh_renders_zero_width_orphan_as_hex() {
let _g = crate::test_util::global_state_lock();
let cc = crate::ported::zsh_h::opt_name(crate::ported::zsh_h::COMBININGCHARS);
crate::ported::options::opt_state_set(cc, false); *ZLELINE.lock().unwrap() = "a\u{0301}".chars().collect();
ZLECS.store(0, Ordering::SeqCst);
ZLELL.store(2, Ordering::SeqCst);
*NBUF.lock().unwrap() = vec![];
*OBUF.lock().unwrap() = vec![];
let devnull =
unsafe { libc::open(b"/dev/null\0".as_ptr() as *const _, libc::O_WRONLY) };
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(devnull, Ordering::SeqCst);
zrefresh();
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(devnull) };
let row0: String = NBUF
.lock()
.unwrap()
.first()
.map(|r| r.iter().map(|c| c.chr).take_while(|&c| c != '\0').collect())
.unwrap_or_default();
assert!(
row0.contains("a<0301>"),
"orphan combining mark must render as <0301> hex; got {:?}",
row0
);
}
#[test]
fn zrefresh_emits_right_prompt_when_it_fits() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
*crate::ported::zle::zle_main::RPROMPT.lock().unwrap() = "RP".to_string();
RPROMPTW.store(2, Ordering::SeqCst);
RPROMPTH.store(1, Ordering::SeqCst);
TRASHEDZLE.store(0, Ordering::SeqCst);
OPUT_RPMPT.store(0, Ordering::SeqCst); LPROMPTW.store(0, Ordering::SeqCst);
*ZLELINE.lock().unwrap() = "a".chars().collect();
ZLECS.store(1, Ordering::SeqCst);
ZLELL.store(1, Ordering::SeqCst);
*NBUF.lock().unwrap() = vec![];
*OBUF.lock().unwrap() = vec![];
NLNCT.store(0, Ordering::SeqCst);
OLNCT.store(0, Ordering::SeqCst);
let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(wr, Ordering::SeqCst);
zrefresh();
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(wr) };
*crate::ported::zle::zle_main::RPROMPT.lock().unwrap() = String::new();
let put = PUT_RPMPT.load(Ordering::SeqCst);
let oput = OPUT_RPMPT.load(Ordering::SeqCst);
PUT_RPMPT.store(0, Ordering::SeqCst);
OPUT_RPMPT.store(0, Ordering::SeqCst);
RPROMPTW.store(0, Ordering::SeqCst);
RPROMPTH.store(0, Ordering::SeqCst);
let mut out = Vec::new();
let _ = unsafe { std::fs::File::from_raw_fd(rd) }.read_to_end(&mut out);
let s = String::from_utf8_lossy(&out);
assert!(
s.contains("RP"),
"right prompt 'RP' must be emitted to the right; got {:?}",
s
);
assert_eq!(put, 1, "put_rpmpt set");
assert_eq!(oput, 1, "oput_rpmpt carries put_rpmpt for the next frame (c:1738)");
}
#[test]
fn zrefresh_cursor_lands_on_second_video_line() {
let _g = crate::test_util::global_state_lock();
*ZLELINE.lock().unwrap() = "a\nb".chars().collect();
ZLECS.store(3, Ordering::SeqCst); ZLELL.store(3, Ordering::SeqCst);
zrefresh();
let rows: Vec<String> = NBUF
.lock()
.unwrap()
.iter()
.map(|r| r.iter().map(|c| c.chr).take_while(|&c| c != '\0').collect())
.collect();
assert!(
rows.len() >= 2 && rows[1].contains('b'),
"newline must create a 2nd video row with 'b'; rows={:?}",
rows
);
assert_eq!(
VLN.load(Ordering::SeqCst),
1,
"cursor must land on video line 1 (nvln), not line 0 (recompute)"
);
}
#[test]
fn zrefresh_renders_statusline_below_editable() {
let _g = crate::test_util::global_state_lock();
*ZLELINE.lock().unwrap() = "ab".chars().collect();
ZLECS.store(0, Ordering::SeqCst);
ZLELL.store(2, Ordering::SeqCst);
*crate::ported::zle::zle_main::STATUSLINE.lock().unwrap() =
Some("x\u{1}y".to_string());
zrefresh();
let rows: Vec<String> = NBUF
.lock()
.unwrap()
.iter()
.map(|r| r.iter().map(|c| c.chr).take_while(|&c| c != '\0').collect())
.collect();
*crate::ported::zle::zle_main::STATUSLINE.lock().unwrap() = None;
let edit_row = rows.iter().position(|r| r.contains("ab"));
let stat_row = rows.iter().position(|r| r.contains("x^Ay"));
assert!(
edit_row.is_some() && stat_row.is_some() && stat_row > edit_row,
"status row (x^Ay) must render below the editable row (ab); rows={:?}",
rows
);
}
#[test]
fn zrefresh_statusline_wide_char_pads_weof() {
let _g = crate::test_util::global_state_lock();
*ZLELINE.lock().unwrap() = "a".chars().collect();
ZLECS.store(0, Ordering::SeqCst);
ZLELL.store(1, Ordering::SeqCst);
*crate::ported::zle::zle_main::STATUSLINE.lock().unwrap() =
Some("日x".to_string());
*NBUF.lock().unwrap() = vec![];
*OBUF.lock().unwrap() = vec![];
let devnull =
unsafe { libc::open(b"/dev/null\0".as_ptr() as *const _, libc::O_WRONLY) };
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(devnull, Ordering::SeqCst);
zrefresh();
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(devnull) };
let rows = NBUF.lock().unwrap().clone();
*crate::ported::zle::zle_main::STATUSLINE.lock().unwrap() = None;
let mut checked = false;
for row in &rows {
if let Some(idx) = row.iter().position(|c| c.chr == '日') {
assert_eq!(
row.get(idx + 1).map(|c| c.chr),
Some(ZWC_WEOF),
"status wide glyph's 2nd column must be a WEOF placeholder"
);
assert_eq!(
row.get(idx + 2).map(|c| c.chr),
Some('x'),
"'x' must follow the WEOF placeholder in the status row"
);
checked = true;
}
}
assert!(checked, "a status row containing 日 must exist; rows={:?}",
rows.iter().map(|r| r.iter().map(|c| c.chr)
.take_while(|&c| c != '\0').collect::<String>()).collect::<Vec<_>>());
}
#[test]
fn zrefresh_statusline_zero_width_orphan_as_hex() {
let _g = crate::test_util::global_state_lock();
let cc = crate::ported::zsh_h::opt_name(crate::ported::zsh_h::COMBININGCHARS);
crate::ported::options::opt_state_set(cc, false);
*ZLELINE.lock().unwrap() = "a".chars().collect();
ZLECS.store(0, Ordering::SeqCst);
ZLELL.store(1, Ordering::SeqCst);
*crate::ported::zle::zle_main::STATUSLINE.lock().unwrap() =
Some("x\u{0301}".to_string());
*NBUF.lock().unwrap() = vec![];
*OBUF.lock().unwrap() = vec![];
let devnull =
unsafe { libc::open(b"/dev/null\0".as_ptr() as *const _, libc::O_WRONLY) };
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(devnull, Ordering::SeqCst);
zrefresh();
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(devnull) };
let rows: Vec<String> = NBUF
.lock()
.unwrap()
.iter()
.map(|r| r.iter().map(|c| c.chr).take_while(|&c| c != '\0').collect())
.collect();
*crate::ported::zle::zle_main::STATUSLINE.lock().unwrap() = None;
assert!(
rows.iter().any(|r| r.contains("x<0301>")),
"status orphan combining mark must render as <0301>; rows={:?}",
rows
);
}
#[test]
fn refreshline_emits_new_line_cells() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
let old_shtty = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(wr, Ordering::SeqCst);
*NBUF.lock().unwrap() = vec!["abc"
.chars()
.map(|c| REFRESH_ELEMENT { chr: c, atr: 0 })
.collect()];
*OBUF.lock().unwrap() = vec![];
NLNCT.store(1, Ordering::SeqCst);
OLNCT.store(0, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
CLEAREOL.store(0, Ordering::SeqCst);
LPROMPTW.store(0, Ordering::SeqCst);
crate::ported::init::hasam.store(0, Ordering::SeqCst);
refreshline(0);
crate::ported::init::SHTTY.store(old_shtty, Ordering::SeqCst);
unsafe { libc::close(wr) };
let mut out = Vec::new();
let mut f = unsafe { std::fs::File::from_raw_fd(rd) };
let _ = f.read_to_end(&mut out);
let s = String::from_utf8_lossy(&out);
assert!(
s.contains('a') && s.contains('b') && s.contains('c'),
"refreshline should emit the new-line cells a/b/c; got {:?}",
s
);
}
#[test]
fn zwcputc_emits_multiword_cluster() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
let _g = crate::test_util::global_state_lock();
let cluster = ['e', '\u{0301}'];
let mut cell = REFRESH_ELEMENT::default();
addmultiword(&mut cell, &cluster, cluster.len());
assert!(
cell.atr & TXT_MULTIWORD_MASK != 0,
"addmultiword must set the multiword mask"
);
let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
let old = SHTTY.load(Ordering::SeqCst);
SHTTY.store(wr, Ordering::SeqCst);
zwcputc(&cell);
SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(wr) };
let mut out = Vec::new();
let mut f = unsafe { std::fs::File::from_raw_fd(rd) };
let _ = f.read_to_end(&mut out);
let s = String::from_utf8_lossy(&out);
assert!(
s.contains('e') && s.contains('\u{0301}'),
"zwcputc must emit both cluster codepoints (e + combining acute); got {:?}",
s
);
}
#[test]
fn zrefresh_renders_line_via_diff() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
*NBUF.lock().unwrap() = vec![];
*OBUF.lock().unwrap() = vec![];
NLNCT.store(0, Ordering::SeqCst);
OLNCT.store(0, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
LPROMPTW.store(0, Ordering::SeqCst);
crate::ported::init::hasam.store(0, Ordering::SeqCst);
*ZLELINE.lock().unwrap() = "hello".chars().collect();
ZLECS.store(5, Ordering::SeqCst);
ZLELL.store(5, Ordering::SeqCst);
let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
let old_shtty = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(wr, Ordering::SeqCst);
zrefresh();
crate::ported::init::SHTTY.store(old_shtty, Ordering::SeqCst);
unsafe { libc::close(wr) };
let mut out = Vec::new();
let mut f = unsafe { std::fs::File::from_raw_fd(rd) };
let _ = f.read_to_end(&mut out);
let s = String::from_utf8_lossy(&out);
assert!(
s.contains("hello"),
"live zrefresh (diff path) should render the line; got {:?}",
s
);
}
#[test]
fn zrefresh_nbuf_renders_via_refreshline() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
*ZLELINE.lock().unwrap() = "hello".chars().collect();
ZLECS.store(5, Ordering::SeqCst);
ZLELL.store(5, Ordering::SeqCst);
let old_shtty = crate::ported::init::SHTTY.load(Ordering::SeqCst);
let devnull = unsafe {
libc::open(b"/dev/null\0".as_ptr() as *const _, libc::O_WRONLY)
};
crate::ported::init::SHTTY.store(devnull, Ordering::SeqCst);
zrefresh(); if devnull >= 0 {
unsafe { libc::close(devnull) };
}
let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
crate::ported::init::SHTTY.store(wr, Ordering::SeqCst);
*OBUF.lock().unwrap() = vec![];
OLNCT.store(0, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
CLEAREOL.store(0, Ordering::SeqCst);
LPROMPTW.store(0, Ordering::SeqCst);
crate::ported::init::hasam.store(0, Ordering::SeqCst);
refreshline(0);
crate::ported::init::SHTTY.store(old_shtty, Ordering::SeqCst);
unsafe { libc::close(wr) };
let mut out = Vec::new();
let mut f = unsafe { std::fs::File::from_raw_fd(rd) };
let _ = f.read_to_end(&mut out);
let s = String::from_utf8_lossy(&out);
assert!(
s.contains("hello"),
"zrefresh-built NBUF should render 'hello' via refreshline; got {:?}",
s
);
}
#[test]
fn refreshline_erases_shortened_tail() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
let old_shtty = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(wr, Ordering::SeqCst);
let mk = |s: &str| -> REFRESH_STRING {
s.chars().map(|c| REFRESH_ELEMENT { chr: c, atr: 0 }).collect()
};
*OBUF.lock().unwrap() = vec![mk("abcd")];
*NBUF.lock().unwrap() = vec![mk("ab")];
NLNCT.store(1, Ordering::SeqCst);
OLNCT.store(1, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
CLEAREOL.store(0, Ordering::SeqCst);
LPROMPTW.store(0, Ordering::SeqCst);
crate::ported::init::hasam.store(0, Ordering::SeqCst);
tclen.lock().unwrap()[TCCLEAREOL as usize] = 0;
refreshline(0);
crate::ported::init::SHTTY.store(old_shtty, Ordering::SeqCst);
unsafe { libc::close(wr) };
let mut out = Vec::new();
let mut f = unsafe { std::fs::File::from_raw_fd(rd) };
let _ = f.read_to_end(&mut out);
let s = String::from_utf8_lossy(&out);
assert!(
s.matches(' ').count() >= 2,
"shortened line should erase the 2 old tail cells with spaces; got {:?}",
s
);
}
#[test]
fn refreshline_emits_changed_cell_on_edit() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
let old_shtty = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(wr, Ordering::SeqCst);
let mk = |s: &str| -> REFRESH_STRING {
s.chars().map(|c| REFRESH_ELEMENT { chr: c, atr: 0 }).collect()
};
*OBUF.lock().unwrap() = vec![mk("abc")];
*NBUF.lock().unwrap() = vec![mk("abd")];
NLNCT.store(1, Ordering::SeqCst);
OLNCT.store(1, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
CLEAREOL.store(0, Ordering::SeqCst);
LPROMPTW.store(0, Ordering::SeqCst);
crate::ported::init::hasam.store(0, Ordering::SeqCst);
refreshline(0);
crate::ported::init::SHTTY.store(old_shtty, Ordering::SeqCst);
unsafe { libc::close(wr) };
let mut out = Vec::new();
let mut f = unsafe { std::fs::File::from_raw_fd(rd) };
let _ = f.read_to_end(&mut out);
let s = String::from_utf8_lossy(&out);
assert!(
s.contains('d'),
"edit should emit the changed cell 'd'; got {:?}",
s
);
}
#[test]
fn refreshline_diffs_wide_char_line_correctly() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
WINW.store(8, Ordering::SeqCst);
let wide = |tail: char| -> REFRESH_STRING {
let mut r = vec![
REFRESH_ELEMENT { chr: '日', atr: 0 },
REFRESH_ELEMENT { chr: ZWC_WEOF, atr: 0 },
REFRESH_ELEMENT { chr: tail, atr: 0 },
];
r.resize(10, REFRESH_ELEMENT::default());
r
};
*OBUF.lock().unwrap() = vec![wide('Y')];
*NBUF.lock().unwrap() = vec![wide('X')];
NLNCT.store(1, Ordering::SeqCst);
OLNCT.store(1, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
CLEAREOL.store(0, Ordering::SeqCst);
LPROMPTW.store(0, Ordering::SeqCst);
crate::ported::init::hasam.store(0, Ordering::SeqCst);
let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(wr, Ordering::SeqCst);
refreshline(0);
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(wr) };
let mut out = Vec::new();
let _ = unsafe { std::fs::File::from_raw_fd(rd) }.read_to_end(&mut out);
let s = String::from_utf8_lossy(&out);
assert!(
s.contains('X')
&& !s.contains('\u{FFFF}')
&& !s.contains('日'),
"wide-char diff must emit only 'X' (skip the matched 日+WEOF) and \
never write the WEOF placeholder; got {:?}",
s
);
}
#[test]
fn refreshline_tracks_vcs_across_prefix_skip() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let devnull = unsafe { libc::open(b"/dev/null\0".as_ptr() as *const _, libc::O_WRONLY) };
let old_shtty = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(devnull, Ordering::SeqCst);
let mk = |s: &str| -> REFRESH_STRING {
s.chars().map(|c| REFRESH_ELEMENT { chr: c, atr: 0 }).collect()
};
*OBUF.lock().unwrap() = vec![mk("abc")];
*NBUF.lock().unwrap() = vec![mk("abd")];
NLNCT.store(1, Ordering::SeqCst);
OLNCT.store(1, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
CLEAREOL.store(0, Ordering::SeqCst);
LPROMPTW.store(0, Ordering::SeqCst);
crate::ported::init::hasam.store(0, Ordering::SeqCst);
refreshline(0);
crate::ported::init::SHTTY.store(old_shtty, Ordering::SeqCst);
unsafe { libc::close(devnull) };
assert_eq!(
VCS.load(Ordering::SeqCst),
3,
"VCS must track to column 3 (ccs=2 after prefix skip + 1 written cell)"
);
}
#[test]
fn zrefresh_clears_newly_grown_line() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
use crate::ported::init::{tclen, tcstr};
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let saved_tc = tclen.lock().unwrap()[TCCLEAREOL as usize];
let saved_str = tcstr.lock().unwrap()[TCCLEAREOL as usize].clone();
tclen.lock().unwrap()[TCCLEAREOL as usize] = 3; tcstr.lock().unwrap()[TCCLEAREOL as usize] = "\x1b[K".to_string();
*NBUF.lock().unwrap() = vec![];
*OBUF.lock().unwrap() = vec![];
NLNCT.store(0, Ordering::SeqCst);
OLNCT.store(0, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
LPROMPTW.store(0, Ordering::SeqCst);
CLEAREOL.store(0, Ordering::SeqCst);
crate::ported::init::hasam.store(0, Ordering::SeqCst);
let devnull = unsafe { libc::open(b"/dev/null\0".as_ptr() as *const _, libc::O_WRONLY) };
let old_shtty = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(devnull, Ordering::SeqCst);
*ZLELINE.lock().unwrap() = "a".chars().collect();
ZLECS.store(1, Ordering::SeqCst);
ZLELL.store(1, Ordering::SeqCst);
zrefresh();
unsafe { libc::close(devnull) };
let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
crate::ported::init::SHTTY.store(wr, Ordering::SeqCst);
CLEAREOL.store(0, Ordering::SeqCst); *ZLELINE.lock().unwrap() = "a\n".chars().collect();
ZLECS.store(2, Ordering::SeqCst);
ZLELL.store(2, Ordering::SeqCst);
zrefresh();
crate::ported::init::SHTTY.store(old_shtty, Ordering::SeqCst);
unsafe { libc::close(wr) };
tclen.lock().unwrap()[TCCLEAREOL as usize] = saved_tc;
tcstr.lock().unwrap()[TCCLEAREOL as usize] = saved_str;
let mut out = Vec::new();
let mut f = unsafe { std::fs::File::from_raw_fd(rd) };
let _ = f.read_to_end(&mut out);
let s = String::from_utf8_lossy(&out);
assert!(
s.contains("\x1b[K"),
"grown line 1 should be cleared to end-of-line (CSI K); got {:?}",
s
);
}
#[test]
fn zrefresh_deletes_line_via_tcdelline() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
use crate::ported::init::{tclen, tcstr};
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let di = crate::ported::zsh_h::TCDELLINE as usize;
let saved_len = tclen.lock().unwrap()[di];
let saved_str = tcstr.lock().unwrap()[di].clone();
tclen.lock().unwrap()[di] = 3; tcstr.lock().unwrap()[di] = "\x1b[M".to_string();
*NBUF.lock().unwrap() = vec![];
*OBUF.lock().unwrap() = vec![];
NLNCT.store(0, Ordering::SeqCst);
OLNCT.store(0, Ordering::SeqCst);
VMAXLN.store(0, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
LPROMPTW.store(0, Ordering::SeqCst);
CLEAREOL.store(0, Ordering::SeqCst);
CLEARFLAG.store(0, Ordering::SeqCst);
crate::ported::init::hasam.store(0, Ordering::SeqCst);
let devnull = unsafe { libc::open(b"/dev/null\0".as_ptr() as *const _, libc::O_WRONLY) };
let old_shtty = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(devnull, Ordering::SeqCst);
*ZLELINE.lock().unwrap() = "L0\nXX\nL2".chars().collect();
ZLECS.store(8, Ordering::SeqCst);
ZLELL.store(8, Ordering::SeqCst);
zrefresh();
unsafe { libc::close(devnull) };
let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
crate::ported::init::SHTTY.store(wr, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
*ZLELINE.lock().unwrap() = "L0\nL2\nYY".chars().collect();
ZLECS.store(8, Ordering::SeqCst);
ZLELL.store(8, Ordering::SeqCst);
zrefresh();
crate::ported::init::SHTTY.store(old_shtty, Ordering::SeqCst);
unsafe { libc::close(wr) };
tclen.lock().unwrap()[di] = saved_len;
tcstr.lock().unwrap()[di] = saved_str;
let mut out = Vec::new();
let mut f = unsafe { std::fs::File::from_raw_fd(rd) };
let _ = f.read_to_end(&mut out);
let s = String::from_utf8_lossy(&out);
assert!(
s.contains("\x1b[M"),
"line-delete opt should emit the TCDELLINE escape; got {:?}",
s
);
}
#[test]
fn zrefresh_nbuf_cells_carry_attr() {
use crate::ported::zsh_h::TXTBOLDFACE;
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
*ZLELINE.lock().unwrap() = "abc".chars().collect();
ZLECS.store(0, Ordering::SeqCst);
ZLELL.store(3, Ordering::SeqCst);
let custom = TextAttr {
bold: true,
..TextAttr::default()
};
highlight().lock().unwrap().add_region(0, 3, custom);
zrefresh();
let nbuf = NBUF.lock().unwrap();
let row0 = nbuf.first().expect("NBUF has a row");
let a_cell = row0
.iter()
.find(|c| c.chr == 'a')
.expect("'a' cell present");
assert!(
a_cell.atr & TXTBOLDFACE != 0,
"bold region -> cell carries TXTBOLDFACE, got atr={:#x}",
a_cell.atr
);
}
fn reset_nmw_state() {
NMW_IND.with(|c| c.set(1)); NMW_SIZE.with(|c| c.set(DEF_MWBUF_ALLOC)); NMWBUF.with(|b| {
let mut buf = b.borrow_mut();
buf.clear();
buf.resize(DEF_MWBUF_ALLOC, 0); });
}
#[test]
fn addmultiword_stores_cluster_and_sets_chr_index() {
let _g = crate::test_util::global_state_lock();
reset_nmw_state();
let mut base = REFRESH_ELEMENT { chr: '\0', atr: 0 };
let cluster = ['a', '\u{0301}'];
addmultiword(&mut base, &cluster, 2);
assert_ne!(base.atr & TXT_MULTIWORD_MASK, 0, "c:920 flag set");
assert_eq!(base.chr as u32, 1, "c:934 base.chr = old nmw_ind");
NMWBUF.with(|b| {
let buf = b.borrow();
assert_eq!(buf[1], 2, "c:930 nmwbuf[ind] = ichars");
assert_eq!(buf[2], 'a' as u32, "c:932 nmwbuf[ind+1] = tptr[0]");
assert_eq!(buf[3], '\u{0301}' as u32, "c:932 nmwbuf[ind+2] = tptr[1]");
});
assert_eq!(NMW_IND.get(), 4, "c:935 nmw_ind += iadd (1 + 2 + 1)");
reset_nmw_state();
}
#[test]
fn addmultiword_no_grow_when_capacity_fits() {
let _g = crate::test_util::global_state_lock();
reset_nmw_state();
let pre_size = NMW_SIZE.get();
let mut base = REFRESH_ELEMENT { chr: '\0', atr: 0 };
addmultiword(&mut base, &['x'], 1);
assert_eq!(
NMW_SIZE.get(),
pre_size,
"c:921 — no grow needed (1+2 ≤ 32)"
);
reset_nmw_state();
}
#[test]
fn addmultiword_grows_by_iadd_for_large_cluster() {
let _g = crate::test_util::global_state_lock();
reset_nmw_state();
let pre_size = NMW_SIZE.get();
let mut base = REFRESH_ELEMENT { chr: '\0', atr: 0 };
let cluster: Vec<char> = (0..40)
.map(|i| char::from_u32(0x300 + i as u32).unwrap())
.collect();
addmultiword(&mut base, &cluster, 40);
assert_eq!(
NMW_SIZE.get(),
pre_size + 41,
"c:922 — grow = iadd, not DEF_MWBUF_ALLOC"
);
reset_nmw_state();
}
#[test]
fn addmultiword_consecutive_pushes_land_at_correct_indices() {
let _g = crate::test_util::global_state_lock();
reset_nmw_state();
let mut a = REFRESH_ELEMENT { chr: '\0', atr: 0 };
addmultiword(&mut a, &['e', '\u{0301}'], 2);
let mut b = REFRESH_ELEMENT { chr: '\0', atr: 0 };
addmultiword(&mut b, &['n', '\u{0303}'], 2);
assert_eq!(a.chr as u32, 1, "first push index");
assert_eq!(
b.chr as u32, 4,
"second push index (c:43 invariant — never 0)"
);
assert_eq!(NMW_IND.get(), 7, "after two 2-clusters: 1 + 3 + 3 = 7");
reset_nmw_state();
}
#[test]
fn test_countprompt() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(countprompt("hello"), 5);
assert_eq!(countprompt("\x1b[31mhello\x1b[0m"), 5);
assert_eq!(countprompt("日本語"), 6); }
#[test]
fn test_video_buffer() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut buf = VideoBuffer::new(80, 24);
assert_eq!(buf.cols, 80);
assert_eq!(buf.rows, 24);
buf.set(0, 0, RefreshElement::new('A'));
assert_eq!(buf.get(0, 0).map(|e| e.chr), Some('A'));
buf.clear();
assert_eq!(buf.get(0, 0).map(|e| e.chr), Some(' '));
}
#[test]
fn test_refresh_state() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut state = RefreshState::new();
assert!(state.old_video.is_some());
assert!(state.new_video.is_some());
state.swap_buffers();
state.free_video();
assert!(state.old_video.is_none());
}
#[test]
fn compute_render_attrs_empty_buffer_yields_empty_overlay() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert!(compute_render_attrs().is_empty());
}
#[test]
fn compute_render_attrs_visual_mode_paints_mark_to_cursor_in_standout() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
*ZLELINE.lock().unwrap() = "hello world".chars().collect();
ZLELL.store(ZLELINE.lock().unwrap().len(), Ordering::SeqCst);
MARK.store(2, Ordering::SeqCst);
ZLECS.store(7, Ordering::SeqCst);
REGION_ACTIVE.store(1, Ordering::SeqCst); let attrs = compute_render_attrs();
assert_eq!(attrs.len(), 11);
for slot in attrs.iter().take(2) {
assert!(slot.is_none());
}
for slot in attrs.iter().skip(7) {
assert!(slot.is_none());
}
for slot in attrs.iter().take(7).skip(2) {
let attr = slot.expect("standout");
assert!(attr.standout);
}
}
#[test]
fn compute_render_attrs_visual_mode_handles_reverse_mark_order() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
*ZLELINE.lock().unwrap() = "abcdef".chars().collect();
ZLELL.store(6, Ordering::SeqCst);
MARK.store(5, Ordering::SeqCst);
ZLECS.store(1, Ordering::SeqCst);
REGION_ACTIVE.store(2, Ordering::SeqCst); let attrs = compute_render_attrs();
assert!(attrs[0].is_none());
for slot in attrs.iter().take(5).skip(1) {
assert!(slot.unwrap().standout);
}
assert!(attrs[5].is_none());
}
#[test]
fn match_highlight_handles_combined_attrs() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let attr = match_highlight("bold,fg=red,underline");
assert!(attr.bold);
assert!(attr.underline);
assert_eq!(attr.fg_color, Some(1));
}
#[test]
fn match_highlight_named_and_numeric_colors() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
assert_eq!(match_highlight("fg=cyan").fg_color, Some(6));
assert_eq!(match_highlight("bg=42").bg_color, Some(42));
assert_eq!(match_highlight("fg=999").fg_color, None);
}
#[test]
fn match_highlight_negation_clears_attr() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let attr = match_highlight("bold,nobold,underline");
assert!(!attr.bold);
assert!(attr.underline);
}
#[test]
fn match_highlight_none_resets_everything() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let attr = match_highlight("bold,fg=red,none,underline");
assert!(!attr.bold);
assert!(attr.underline);
assert_eq!(attr.fg_color, None);
}
#[test]
fn zle_set_highlight_populates_categories_and_defaults() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut mgr = HighlightManager::new();
let entries = ["region:fg=red,bold", "isearch:fg=blue"];
zle_set_highlight(&mut mgr, &entries);
let region = mgr.category_attrs[&HighlightCategory::Region];
assert!(region.bold);
assert_eq!(region.fg_color, Some(1));
let isearch = mgr.category_attrs[&HighlightCategory::Isearch];
assert_eq!(isearch.fg_color, Some(4));
let suffix = mgr.category_attrs[&HighlightCategory::Suffix];
assert!(suffix.bold);
let special = mgr.category_attrs[&HighlightCategory::Special];
assert!(special.standout);
}
#[test]
fn zle_set_highlight_none_clears_every_slot() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
let mut mgr = HighlightManager::new();
zle_set_highlight(&mut mgr, &["none"]);
for cat in [
HighlightCategory::Region,
HighlightCategory::Isearch,
HighlightCategory::Suffix,
HighlightCategory::Paste,
] {
let attr = mgr.category_attrs[&cat];
assert_eq!(attr, TextAttr::default());
}
}
#[test]
fn compute_render_attrs_visual_uses_zle_highlight_region_attr() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
zle_reset();
*ZLELINE.lock().unwrap() = "abcde".chars().collect();
ZLELL.store(5, Ordering::SeqCst);
MARK.store(1, Ordering::SeqCst);
ZLECS.store(4, Ordering::SeqCst);
REGION_ACTIVE.store(1, Ordering::SeqCst);
zle_set_highlight(&mut highlight().lock().unwrap(), &["region:fg=red,bold"]);
let attrs = compute_render_attrs();
for slot in attrs.iter().take(4).skip(1) {
let a = slot.expect("region painted");
assert!(a.bold);
assert_eq!(a.fg_color, Some(1));
assert!(!a.standout);
}
}
#[test]
fn compute_render_attrs_explicit_regions_override_default() {
let _g = crate::test_util::global_state_lock();
let _g = zle_test_setup();
*ZLELINE.lock().unwrap() = "abcde".chars().collect();
ZLELL.store(5, Ordering::SeqCst);
let custom = TextAttr {
bold: true,
fg_color: Some(1),
..TextAttr::default()
};
highlight().lock().unwrap().add_region(1, 4, custom);
let attrs = compute_render_attrs();
assert!(attrs[0].is_none());
for slot in attrs.iter().take(4).skip(1) {
let a = slot.expect("custom");
assert!(a.bold);
assert_eq!(a.fg_color, Some(1));
}
assert!(attrs[4].is_none());
}
#[test]
fn ZR_strlen_empty_terminated_returns_zero() {
let _g = crate::test_util::global_state_lock();
let buf = [REFRESH_ELEMENT { chr: '\0', atr: 0 }];
assert_eq!(ZR_strlen(&buf), 0);
}
#[test]
fn ZR_strlen_counts_chars_before_nul() {
let _g = crate::test_util::global_state_lock();
let buf = [
REFRESH_ELEMENT { chr: 'a', atr: 0 },
REFRESH_ELEMENT { chr: 'b', atr: 0 },
REFRESH_ELEMENT { chr: 'c', atr: 0 },
REFRESH_ELEMENT { chr: '\0', atr: 0 },
];
assert_eq!(ZR_strlen(&buf), 3);
}
#[test]
fn ZR_strlen_no_nul_returns_full_len() {
let _g = crate::test_util::global_state_lock();
let buf = [
REFRESH_ELEMENT { chr: 'x', atr: 0 },
REFRESH_ELEMENT { chr: 'y', atr: 0 },
];
assert_eq!(ZR_strlen(&buf), 2, "no NUL → bounded by slice");
}
#[test]
fn tcoutclear_runs_without_panic() {
let _g = crate::test_util::global_state_lock();
tcoutclear(crate::ported::zsh_h::TCCLEARSCREEN);
tcoutclear(crate::ported::zsh_h::TCCLEAREOL);
}
#[test]
fn zle_free_highlight_no_panic() {
let _g = crate::test_util::global_state_lock();
zle_free_highlight();
zle_free_highlight();
}
#[test]
fn zr_memset_fills_first_n_cells() {
let _g = crate::test_util::global_state_lock();
let mut buf: Vec<REFRESH_ELEMENT> = vec![REFRESH_ELEMENT { chr: 'X', atr: 0 }; 10];
let rc = REFRESH_ELEMENT { chr: 'A', atr: 0 };
ZR_memset(&mut buf, rc, 5);
for i in 0..5 {
assert_eq!(buf[i].chr, 'A', "cell {} must be filled", i);
}
for i in 5..10 {
assert_eq!(buf[i].chr, 'X', "cell {} must remain", i);
}
}
#[test]
fn zr_memset_zero_len_no_op() {
let _g = crate::test_util::global_state_lock();
let mut buf: Vec<REFRESH_ELEMENT> = vec![REFRESH_ELEMENT { chr: 'X', atr: 0 }; 3];
let rc = REFRESH_ELEMENT { chr: 'A', atr: 0 };
ZR_memset(&mut buf, rc, 0);
for elt in &buf {
assert_eq!(elt.chr, 'X', "len=0 must not modify any cell");
}
}
#[test]
fn zr_memset_clamps_to_dst_len() {
let _g = crate::test_util::global_state_lock();
let mut buf: Vec<REFRESH_ELEMENT> = vec![REFRESH_ELEMENT { chr: 'X', atr: 0 }; 3];
let rc = REFRESH_ELEMENT { chr: 'A', atr: 0 };
ZR_memset(&mut buf, rc, 100);
for elt in &buf {
assert_eq!(elt.chr, 'A');
}
}
#[test]
fn zr_strcpy_includes_nul_terminator() {
let _g = crate::test_util::global_state_lock();
let src: Vec<REFRESH_ELEMENT> = vec![
REFRESH_ELEMENT { chr: 'a', atr: 0 },
REFRESH_ELEMENT { chr: 'b', atr: 0 },
REFRESH_ELEMENT { chr: 'c', atr: 0 },
REFRESH_ELEMENT { chr: '\0', atr: 0 },
];
let mut dst: Vec<REFRESH_ELEMENT> = vec![REFRESH_ELEMENT { chr: 'X', atr: 0 }; 5];
ZR_strcpy(&mut dst, &src);
assert_eq!(dst[0].chr, 'a');
assert_eq!(dst[1].chr, 'b');
assert_eq!(dst[2].chr, 'c');
assert_eq!(dst[3].chr, '\0', "NUL terminator must be copied");
}
#[test]
fn zr_strlen_immediate_nul_returns_zero() {
let _g = crate::test_util::global_state_lock();
let buf: Vec<REFRESH_ELEMENT> = vec![REFRESH_ELEMENT { chr: '\0', atr: 0 }];
assert_eq!(ZR_strlen(&buf), 0);
}
#[test]
fn zr_strlen_counts_until_nul() {
let _g = crate::test_util::global_state_lock();
let buf: Vec<REFRESH_ELEMENT> = vec![
REFRESH_ELEMENT { chr: 'a', atr: 0 },
REFRESH_ELEMENT { chr: 'b', atr: 0 },
REFRESH_ELEMENT { chr: 'c', atr: 0 },
REFRESH_ELEMENT { chr: '\0', atr: 0 },
REFRESH_ELEMENT { chr: 'x', atr: 0 }, ];
assert_eq!(ZR_strlen(&buf), 3);
}
#[test]
fn zr_strncmp_equal_strings_return_zero() {
let _g = crate::test_util::global_state_lock();
let a: Vec<REFRESH_ELEMENT> = vec![
REFRESH_ELEMENT { chr: 'a', atr: 0 },
REFRESH_ELEMENT { chr: 'b', atr: 0 },
];
let b = a.clone();
assert_eq!(ZR_strncmp(&a, &b, 2), 0);
assert_eq!(ZR_strncmp(&a, &b, 1), 0, "prefix match also returns 0");
}
#[test]
fn zr_strncmp_diff_returns_one() {
let _g = crate::test_util::global_state_lock();
let a: Vec<REFRESH_ELEMENT> = vec![
REFRESH_ELEMENT { chr: 'a', atr: 0 },
REFRESH_ELEMENT { chr: 'b', atr: 0 },
];
let b: Vec<REFRESH_ELEMENT> = vec![
REFRESH_ELEMENT { chr: 'a', atr: 0 },
REFRESH_ELEMENT { chr: 'X', atr: 0 },
];
assert_eq!(ZR_strncmp(&a, &b, 2), 1, "differ at idx 1 → 1");
assert_eq!(ZR_strncmp(&a, &b, 1), 0, "first char matches → 0");
}
#[test]
fn zr_strncmp_zero_len_returns_zero() {
let _g = crate::test_util::global_state_lock();
let a: Vec<REFRESH_ELEMENT> = vec![REFRESH_ELEMENT { chr: 'a', atr: 0 }];
let b: Vec<REFRESH_ELEMENT> = vec![REFRESH_ELEMENT { chr: 'z', atr: 0 }];
assert_eq!(ZR_strncmp(&a, &b, 0), 0, "n=0 → no comparison");
}
#[test]
fn zr_strlen_returns_usize_type() {
let _: usize = ZR_strlen(&[]);
}
#[test]
fn zr_strlen_empty_slice_returns_zero() {
assert_eq!(ZR_strlen(&[]), 0);
}
#[test]
fn zr_strncmp_reflexive_returns_zero() {
let a: Vec<REFRESH_ELEMENT> = vec![
REFRESH_ELEMENT { chr: 'a', atr: 0 },
REFRESH_ELEMENT { chr: 'b', atr: 0 },
];
assert_eq!(ZR_strncmp(&a, &a, 2), 0, "self-compare must be 0");
}
#[test]
fn zr_strncmp_both_empty_zero_len_returns_zero() {
let empty: Vec<REFRESH_ELEMENT> = vec![];
assert_eq!(ZR_strncmp(&empty, &empty, 0), 0);
}
#[test]
fn zle_free_highlight_idempotent() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for _ in 0..5 {
zle_free_highlight();
}
}
#[test]
fn tcoutclear_both_modes_safe() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
tcoutclear(crate::ported::zsh_h::TCCLEAREOL);
tcoutclear(crate::ported::zsh_h::TCCLEARSCREEN);
}
#[test]
fn scrollwindow_rotates_buffer_and_sets_more_start() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let mk = |s: &str| -> REFRESH_STRING {
s.chars().map(|c| REFRESH_ELEMENT { chr: c, atr: 0 }).collect()
};
WINH.store(4, Ordering::SeqCst);
MORE_START.store(0, Ordering::SeqCst);
*NBUF.lock().unwrap() = vec![mk("L0"), mk("L1"), mk("L2"), mk("L3")];
scrollwindow(0);
let rows: Vec<String> = NBUF
.lock()
.unwrap()
.iter()
.map(|r| r.iter().map(|c| c.chr).collect())
.collect();
assert_eq!(rows, vec!["L1", "L2", "L3", "L0"], "rotate-left by one");
assert_eq!(
MORE_START.load(Ordering::SeqCst),
1,
"scrolling from the top sets more_start"
);
}
#[test]
fn scrollwindow_from_nonzero_line() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let mk = |s: &str| -> REFRESH_STRING {
s.chars().map(|c| REFRESH_ELEMENT { chr: c, atr: 0 }).collect()
};
WINH.store(4, Ordering::SeqCst);
MORE_START.store(0, Ordering::SeqCst);
*NBUF.lock().unwrap() = vec![mk("L0"), mk("L1"), mk("L2"), mk("L3")];
scrollwindow(1);
let rows: Vec<String> = NBUF
.lock()
.unwrap()
.iter()
.map(|r| r.iter().map(|c| c.chr).collect())
.collect();
assert_eq!(rows, vec!["L0", "L2", "L3", "L1"], "rotate from line 1");
assert_eq!(
MORE_START.load(Ordering::SeqCst),
0,
"non-top scroll must not set more_start"
);
}
#[test]
fn scrollwindow_out_of_range_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
*NBUF.lock().unwrap() = vec![];
scrollwindow(-1);
scrollwindow(0);
scrollwindow(i32::MAX);
scrollwindow(i32::MIN);
}
#[test]
fn wpfxlen_both_empty_returns_zero() {
assert_eq!(wpfxlen(&[], &[]), 0);
}
#[test]
fn wpfxlen_identical_returns_full_len() {
let s: Vec<REFRESH_ELEMENT> = vec![
REFRESH_ELEMENT { chr: 'a', atr: 0 },
REFRESH_ELEMENT { chr: 'b', atr: 0 },
REFRESH_ELEMENT { chr: 'c', atr: 0 },
];
assert_eq!(wpfxlen(&s, &s), s.len(), "identical → full prefix");
}
#[test]
fn zwcputc_any_char_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for c in ['\0', 'a', '日', '\u{1F600}', '\u{10FFFF}'] {
zwcputc(&REFRESH_ELEMENT { chr: c, atr: 0 });
}
}
#[test]
fn zr_memset_empty_slice_no_panic() {
let mut buf: Vec<REFRESH_ELEMENT> = vec![];
ZR_memset(&mut buf, REFRESH_ELEMENT { chr: ' ', atr: 0 }, 0);
}
#[test]
fn zr_memset_writes_fill_value() {
let mut buf: Vec<REFRESH_ELEMENT> = vec![
REFRESH_ELEMENT { chr: 'a', atr: 0 },
REFRESH_ELEMENT { chr: 'b', atr: 0 },
REFRESH_ELEMENT { chr: 'c', atr: 0 },
];
let fill = REFRESH_ELEMENT { chr: 'X', atr: 0 };
ZR_memset(&mut buf, fill, 3);
for e in &buf {
assert_eq!(e.chr, 'X', "ZR_memset must fill with X");
}
}
#[test]
fn zr_strcpy_empty_src_no_panic() {
let mut dst: Vec<REFRESH_ELEMENT> = vec![REFRESH_ELEMENT { chr: '\0', atr: 0 }];
ZR_strcpy(&mut dst, &[]);
}
#[test]
fn zr_strncmp_empty_inputs_returns_zero() {
let r = ZR_strncmp(&[], &[], 0);
assert_eq!(r, 0, "empty + empty + 0 → 0");
}
#[test]
fn zr_strncmp_returns_i32_type() {
let _: i32 = ZR_strncmp(&[], &[], 0);
}
#[test]
fn zr_strncmp_empty_with_nonzero_cap_returns_zero() {
assert_eq!(ZR_strncmp(&[], &[], 5), 0);
}
#[test]
fn zwcwrite_empty_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
zwcwrite(&[], 0);
}
#[test]
fn zwcwrite_returns_clamped_cell_count() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let cells = [
REFRESH_ELEMENT { chr: 'a', atr: 0 },
REFRESH_ELEMENT { chr: 'b', atr: 0 },
REFRESH_ELEMENT { chr: 'c', atr: 0 },
];
assert_eq!(zwcwrite(&cells, 2), 2, "c:660 — returns i");
assert_eq!(zwcwrite(&cells, 5), 3, "clamped to available length");
assert_eq!(zwcwrite(&cells, 0), 0);
}
#[test]
fn wpfxlen_returns_usize_type() {
let _: usize = wpfxlen(&[], &[]);
}
#[test]
fn moveto_returns_void_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: () = moveto(0, 0);
}
#[test]
fn moveto_updates_vcs_vln() {
use std::sync::atomic::Ordering;
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
moveto(3, 7);
assert_eq!(VLN.load(Ordering::SeqCst), 3, "moveto must set VLN to row");
assert_eq!(VCS.load(Ordering::SeqCst), 7, "moveto must set VCS to col");
}
#[test]
fn tcmultout_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let _: i32 = tcmultout(0, 0, 0);
}
#[test]
fn build_loop_scrolls_global_nbuf_past_winh() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let winw = 4i32;
let winh = 3i32;
WINW.store(winw, Ordering::SeqCst);
WINH.store(winh, Ordering::SeqCst);
VMAXLN.store(0, Ordering::SeqCst);
NUMSCROLLS.store(0, Ordering::SeqCst);
ONUMSCROLLS.store(0, Ordering::SeqCst);
*NBUF.lock().unwrap() =
vec![vec![REFRESH_ELEMENT::default(); (winw + 2) as usize]; (winh + 1) as usize];
let mut rpms = rparams::default();
rpms.nvln = -1; for (i, label) in ['0', '1', '2', '3', '4'].iter().enumerate() {
if i > 0 {
nextline(&mut rpms, 0);
}
{
let mut nbuf = NBUF.lock().unwrap();
let ln = rpms.ln as usize;
if ln < nbuf.len() && !nbuf[ln].is_empty() {
nbuf[ln][0] = REFRESH_ELEMENT { chr: *label, atr: 0 };
}
}
rpms.pos = 1;
}
let visible: String = {
let nbuf = NBUF.lock().unwrap();
(0..winh as usize)
.map(|i| nbuf[i][0].chr)
.collect()
};
assert_eq!(visible, "234", "oldest lines scrolled off; newest visible");
}
#[test]
fn freevideo_clears_global_nbuf_obuf() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
*NBUF.lock().unwrap() = vec![vec![REFRESH_ELEMENT::default(); 6]; 3];
*OBUF.lock().unwrap() = vec![vec![REFRESH_ELEMENT::default(); 6]; 3];
NMWBUF.with(|b| *b.borrow_mut() = vec![0, 5, 6]);
OMWBUF.with(|b| *b.borrow_mut() = vec![0, 7]);
NMW_SIZE.with(|c| c.set(3));
OMW_SIZE.with(|c| c.set(2));
NMW_IND.with(|c| c.set(9));
freevideo();
assert!(NBUF.lock().unwrap().is_empty(), "NBUF freed");
assert!(OBUF.lock().unwrap().is_empty(), "OBUF freed");
NMWBUF.with(|b| assert!(b.borrow().is_empty(), "NMWBUF freed"));
OMWBUF.with(|b| assert!(b.borrow().is_empty(), "OMWBUF freed"));
assert_eq!(NMW_SIZE.with(|c| c.get()), 0, "nmw_size reset");
assert_eq!(OMW_SIZE.with(|c| c.get()), 0, "omw_size reset");
assert_eq!(NMW_IND.with(|c| c.get()), 1, "nmw_ind reset to 1 (c:713)");
}
#[test]
fn resetvideo_allocates_global_nbuf_obuf() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
*NBUF.lock().unwrap() = vec![];
*OBUF.lock().unwrap() = vec![];
let mut state = RefreshState::new();
resetvideo(&mut state);
let winw = WINW.load(Ordering::SeqCst);
let winh = WINH.load(Ordering::SeqCst);
let nbuf = NBUF.lock().unwrap();
let obuf = OBUF.lock().unwrap();
assert_eq!(nbuf.len() as i32, winh + 1, "NBUF has winh+1 rows");
assert_eq!(obuf.len() as i32, winh + 1, "OBUF has winh+1 rows");
assert!(!nbuf.is_empty());
assert_eq!(nbuf[0].len() as i32, winw + 2, "row is winw+2 cells");
}
#[test]
fn zrefresh_more_start_indicator_on_scroll() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
*crate::ported::zle::zle_main::LPROMPT.lock().unwrap() = String::new();
let line: Vec<char> = std::iter::repeat('\n').take(300).collect();
let n = line.len();
*ZLELINE.lock().unwrap() = line;
ZLECS.store(n, Ordering::SeqCst); ZLELL.store(n, Ordering::SeqCst);
*NBUF.lock().unwrap() = vec![];
*OBUF.lock().unwrap() = vec![];
NLNCT.store(0, Ordering::SeqCst);
OLNCT.store(0, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
let devnull = unsafe { libc::open(b"/dev/null\0".as_ptr() as *const _, libc::O_WRONLY) };
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(devnull, Ordering::SeqCst);
zrefresh();
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(devnull) };
assert_eq!(
MORE_START.load(Ordering::SeqCst),
1,
"a buffer taller than winh must scroll → more_start"
);
let row0: String = NBUF.lock().unwrap()[0]
.iter()
.map(|c| c.chr)
.take_while(|&c| c != '\0')
.collect();
assert!(
row0.starts_with(">...."),
"line 0 shows the start-ellipsis indicator; got {:?}",
row0
);
}
#[test]
fn zrefresh_numscrolls_resets_and_carries_to_onumscrolls() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
*crate::ported::zle::zle_main::LPROMPT.lock().unwrap() = String::new();
NUMSCROLLS.store(1000, Ordering::SeqCst);
ONUMSCROLLS.store(999, Ordering::SeqCst);
let line: Vec<char> = std::iter::repeat('\n').take(300).collect();
let n = line.len();
*ZLELINE.lock().unwrap() = line;
ZLECS.store(n, Ordering::SeqCst); ZLELL.store(n, Ordering::SeqCst);
*NBUF.lock().unwrap() = vec![];
*OBUF.lock().unwrap() = vec![];
NLNCT.store(0, Ordering::SeqCst);
OLNCT.store(0, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
let devnull =
unsafe { libc::open(b"/dev/null\0".as_ptr() as *const _, libc::O_WRONLY) };
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(devnull, Ordering::SeqCst);
zrefresh();
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(devnull) };
let ns = NUMSCROLLS.load(Ordering::SeqCst);
let ons = ONUMSCROLLS.load(Ordering::SeqCst);
assert!(
ns > 0 && ns < 1000,
"NUMSCROLLS must reset (not accumulate past the seeded 1000) and \
count real scrolls; got {}",
ns
);
assert_eq!(ons, ns, "frame-end must store ONUMSCROLLS = NUMSCROLLS (c:1750)");
}
#[test]
fn zrefresh_resets_more_flags_each_frame() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
MORE_START.store(1, Ordering::SeqCst); MORE_END.store(1, Ordering::SeqCst);
*ZLELINE.lock().unwrap() = "x".chars().collect();
ZLECS.store(1, Ordering::SeqCst);
ZLELL.store(1, Ordering::SeqCst);
*NBUF.lock().unwrap() = vec![];
*OBUF.lock().unwrap() = vec![];
NLNCT.store(0, Ordering::SeqCst);
OLNCT.store(0, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
let devnull = unsafe { libc::open(b"/dev/null\0".as_ptr() as *const _, libc::O_WRONLY) };
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(devnull, Ordering::SeqCst);
zrefresh();
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(devnull) };
assert_eq!(MORE_START.load(Ordering::SeqCst), 0, "more_start reset");
assert_eq!(MORE_END.load(Ordering::SeqCst), 0, "more_end reset");
}
#[test]
fn nextline_bails_to_keep_cursor_visible() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
WINW.store(4, Ordering::SeqCst);
WINH.store(3, Ordering::SeqCst);
NUMSCROLLS.store(0, Ordering::SeqCst);
ONUMSCROLLS.store(0, Ordering::SeqCst);
*NBUF.lock().unwrap() = vec![vec![REFRESH_ELEMENT::default(); 6]; 4];
let mut rpms = rparams::default();
rpms.ln = 2; rpms.nvln = 1; rpms.canscroll = 0;
let ret = nextline(&mut rpms, 0);
assert_eq!(ret, 1, "must bail rather than scroll the cursor off-screen");
assert_eq!(rpms.ln, 2, "ln unchanged on bail (no advance, no scroll)");
}
#[test]
fn snextline_advances_then_scrolls_status_pane() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
WINW.store(4, Ordering::SeqCst);
WINH.store(3, Ordering::SeqCst);
let row = |c: char| -> REFRESH_STRING {
vec![REFRESH_ELEMENT { chr: c, atr: 0 }; 6]
};
*NBUF.lock().unwrap() = vec![row('A'), row('B'), row('C')];
let mut rpms = rparams::default();
rpms.ln = 0;
rpms.pos = 2;
snextline(&mut rpms);
assert_eq!(rpms.ln, 1, "advanced");
assert_eq!(rpms.pos, 0);
assert_eq!(rpms.end, 4);
assert_eq!(NBUF.lock().unwrap()[0][2].chr, '\0', "terminated at pos");
let mut rpms2 = rparams::default();
rpms2.ln = 2; rpms2.tosln = 5;
rpms2.nvln = 2;
snextline(&mut rpms2);
assert_eq!(rpms2.tosln, 4, "tosln decremented");
assert_eq!(rpms2.nvln, 1, "nvln decremented after scroll");
assert_eq!(
NBUF.lock().unwrap()[0][0].chr,
'B',
"scrollwindow(0) rotated row 1 up to row 0"
);
}
#[test]
fn snextline_sets_more_status_on_full_pane() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
WINW.store(4, Ordering::SeqCst);
WINH.store(3, Ordering::SeqCst); MORE_START.store(0, Ordering::SeqCst);
let row = |c: char| -> REFRESH_STRING {
vec![REFRESH_ELEMENT { chr: c, atr: 0 }; 6]
};
*NBUF.lock().unwrap() = vec![row('A'), row('B'), row('C')];
let mut rpms = rparams::default();
rpms.ln = 2; rpms.tosln = 2; rpms.nvln = 1; snextline(&mut rpms);
assert_eq!(rpms.more_status, 1, "more_status set on a full status pane");
assert_eq!(
MORE_START.load(Ordering::SeqCst),
0,
"scrollwindow(tosln+1) must not set MORE_START"
);
}
#[test]
fn nextline_advances_then_scrolls_on_global_nbuf() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
WINW.store(4, Ordering::SeqCst);
WINH.store(3, Ordering::SeqCst);
NUMSCROLLS.store(0, Ordering::SeqCst);
ONUMSCROLLS.store(0, Ordering::SeqCst);
let row = |c: char| -> REFRESH_STRING {
vec![REFRESH_ELEMENT { chr: c, atr: 0 }; 6] };
*NBUF.lock().unwrap() = vec![row('A'), row('B'), row('C')];
let mut rpms = rparams::default();
rpms.ln = 0;
rpms.pos = 2;
rpms.nvln = -1;
let ret = nextline(&mut rpms, 1);
assert_eq!(ret, 0);
assert_eq!(rpms.ln, 1, "advanced to next line");
assert_eq!(rpms.pos, 0); assert_eq!(rpms.end, 4); {
let nbuf = NBUF.lock().unwrap();
assert_eq!(nbuf[0][5].chr, '\n', "wrap marker at winw+1"); assert_eq!(nbuf[0][2].chr, '\0', "terminated at pos"); }
let mut rpms2 = rparams::default();
rpms2.ln = 2;
rpms2.nvln = -1;
let ret2 = nextline(&mut rpms2, 0);
assert_eq!(ret2, 0);
assert_eq!(rpms2.ln, 2, "stays at the bottom row after scroll");
assert_eq!(
NBUF.lock().unwrap()[0][0].chr,
'B',
"buffer scrolled: row 1 rose to row 0"
);
}
#[test]
fn get_region_highlight_formats_user_entries() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
set_region_highlight(Some(&["0 5 fg=red".to_string()]));
let arr = get_region_highlight(&crate::ported::zsh_h::param::default());
assert_eq!(arr.len(), 1, "one user highlight → one entry; got {:?}", arr);
assert_eq!(arr[0], "0 5 fg=red", "spec round-trips, not SGR");
let mut pm = crate::ported::zsh_h::param::default();
unset_region_highlight(&mut pm, 1);
assert!(
get_region_highlight(&pm).is_empty(),
"no highlights → empty array"
);
}
#[test]
fn unset_region_highlight_clears_only_on_exp() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
set_region_highlight(Some(&["0 5 fg=red".to_string()]));
let with_user = REGION_HIGHLIGHTS.lock().unwrap().len();
assert!(with_user > 0, "set_region_highlight should add a user entry");
let mut pm = crate::ported::zsh_h::param::default();
unset_region_highlight(&mut pm, 0);
assert_eq!(
REGION_HIGHLIGHTS.lock().unwrap().len(),
with_user,
"exp=0 must be a no-op"
);
unset_region_highlight(&mut pm, 1);
assert!(
REGION_HIGHLIGHTS.lock().unwrap().len() < with_user,
"exp!=0 must clear the user highlights"
);
}
#[test]
fn zrefresh_publishes_prompt_attr() {
use crate::ported::zsh_h::TXTBOLDFACE;
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let saved = crate::ported::zle::zle_main::LPROMPT.lock().unwrap().clone();
*crate::ported::zle::zle_main::LPROMPT.lock().unwrap() = "\x1b[1mPS>".to_string();
PROMPT_ATTR.store(0, Ordering::SeqCst);
*ZLELINE.lock().unwrap() = "x".chars().collect();
ZLECS.store(1, Ordering::SeqCst);
ZLELL.store(1, Ordering::SeqCst);
*NBUF.lock().unwrap() = vec![];
*OBUF.lock().unwrap() = vec![];
NLNCT.store(0, Ordering::SeqCst);
OLNCT.store(0, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
let devnull = unsafe { libc::open(b"/dev/null\0".as_ptr() as *const _, libc::O_WRONLY) };
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(devnull, Ordering::SeqCst);
zrefresh();
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(devnull) };
*crate::ported::zle::zle_main::LPROMPT.lock().unwrap() = saved;
assert_ne!(
PROMPT_ATTR.load(Ordering::SeqCst) & TXTBOLDFACE,
0,
"bold prompt must publish TXTBOLDFACE to PROMPT_ATTR"
);
}
#[test]
fn zrefresh_syncs_lpromptw_from_prompt() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let saved_prompt = crate::ported::zle::zle_main::LPROMPT.lock().unwrap().clone();
*crate::ported::zle::zle_main::LPROMPT.lock().unwrap() = "abc".to_string();
LPROMPTW.store(999, Ordering::SeqCst); *ZLELINE.lock().unwrap() = "x".chars().collect();
ZLECS.store(1, Ordering::SeqCst);
ZLELL.store(1, Ordering::SeqCst);
*NBUF.lock().unwrap() = vec![];
*OBUF.lock().unwrap() = vec![];
NLNCT.store(0, Ordering::SeqCst);
OLNCT.store(0, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
let devnull = unsafe { libc::open(b"/dev/null\0".as_ptr() as *const _, libc::O_WRONLY) };
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(devnull, Ordering::SeqCst);
zrefresh();
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(devnull) };
*crate::ported::zle::zle_main::LPROMPT.lock().unwrap() = saved_prompt;
assert_eq!(
LPROMPTW.load(Ordering::SeqCst),
3,
"LPROMPTW must sync to the width of prompt \"abc\""
);
}
#[test]
fn zrefresh_syncs_winw_to_terminal() {
use crate::ported::utils::adjustcolumns;
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
WINW.store(999, Ordering::SeqCst); *ZLELINE.lock().unwrap() = "x".chars().collect();
ZLECS.store(1, Ordering::SeqCst);
ZLELL.store(1, Ordering::SeqCst);
*NBUF.lock().unwrap() = vec![];
*OBUF.lock().unwrap() = vec![];
NLNCT.store(0, Ordering::SeqCst);
OLNCT.store(0, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
let devnull = unsafe { libc::open(b"/dev/null\0".as_ptr() as *const _, libc::O_WRONLY) };
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(devnull, Ordering::SeqCst);
zrefresh();
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(devnull) };
assert_eq!(
WINW.load(Ordering::SeqCst),
adjustcolumns() as i32,
"zrefresh must sync WINW to the terminal width"
);
}
#[test]
fn redisplay_homes_and_sets_flags() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
CLEARFLAG.store(1, Ordering::SeqCst);
RESETNEEDED.store(0, Ordering::SeqCst);
LPROMPTH.store(1, Ordering::SeqCst); *ZLELINE.lock().unwrap() = "x".chars().collect();
ZLECS.store(1, Ordering::SeqCst);
ZLELL.store(1, Ordering::SeqCst);
*NBUF.lock().unwrap() = vec![];
*OBUF.lock().unwrap() = vec![];
NLNCT.store(0, Ordering::SeqCst);
OLNCT.store(0, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(wr, Ordering::SeqCst);
let ret = redisplay();
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(wr) };
let mut out = Vec::new();
let _ = unsafe { std::fs::File::from_raw_fd(rd) }.read_to_end(&mut out);
let s = String::from_utf8_lossy(&out);
assert_eq!(ret, 0, "redisplay returns 0");
assert!(s.contains('\r'), "redisplay emits the safety CR; got {:?}", s);
assert_eq!(CLEARFLAG.load(Ordering::SeqCst), 0, "clearflag zeroed");
assert_eq!(RESETNEEDED.load(Ordering::SeqCst), 1, "resetneeded set");
}
#[test]
fn clearscreen_uses_clear_cap_and_sets_flags() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
use crate::ported::init::{tclen, tcstr};
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let ci = crate::ported::zsh_h::TCCLEARSCREEN as usize;
let save_len = tclen.lock().unwrap()[ci];
let save_str = tcstr.lock().unwrap()[ci].clone();
tclen.lock().unwrap()[ci] = 4;
tcstr.lock().unwrap()[ci] = "\x1b[2J".to_string();
CLEARFLAG.store(1, Ordering::SeqCst);
RESETNEEDED.store(0, Ordering::SeqCst);
*crate::ported::zle::zle_main::RAW_LP.lock().unwrap() = "%% > ".to_string();
*ZLELINE.lock().unwrap() = "x".chars().collect();
ZLECS.store(1, Ordering::SeqCst);
ZLELL.store(1, Ordering::SeqCst);
*NBUF.lock().unwrap() = vec![];
*OBUF.lock().unwrap() = vec![];
NLNCT.store(0, Ordering::SeqCst);
OLNCT.store(0, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(wr, Ordering::SeqCst);
let ret = clearscreen();
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(wr) };
tclen.lock().unwrap()[ci] = save_len;
tcstr.lock().unwrap()[ci] = save_str;
let mut out = Vec::new();
let _ = unsafe { std::fs::File::from_raw_fd(rd) }.read_to_end(&mut out);
let s = String::from_utf8_lossy(&out);
assert_eq!(ret, 0, "clearscreen returns 0");
assert!(s.contains("\x1b[2J"), "must emit the clear capability; got {:?}", s);
assert_eq!(CLEARFLAG.load(Ordering::SeqCst), 0, "clearflag zeroed");
assert_eq!(RESETNEEDED.load(Ordering::SeqCst), 1, "resetneeded set");
assert_eq!(
crate::ported::zle::zle_main::prompt(),
"% > ",
"clearscreen must reexpandprompt the raw template"
);
}
#[test]
fn bufswap_exchanges_global_nbuf_obuf() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let mk = |s: &str| -> REFRESH_STRING {
s.chars().map(|c| REFRESH_ELEMENT { chr: c, atr: 0 }).collect()
};
*NBUF.lock().unwrap() = vec![mk("new")];
*OBUF.lock().unwrap() = vec![mk("old")];
bufswap();
let nb: String = NBUF.lock().unwrap()[0].iter().map(|c| c.chr).collect();
let ob: String = OBUF.lock().unwrap()[0].iter().map(|c| c.chr).collect();
assert_eq!(nb, "old", "NBUF must hold the previously-old buffer");
assert_eq!(ob, "new", "OBUF must hold the previously-new buffer");
}
#[test]
fn bufswap_swaps_multiword_buffers_and_resets_index() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
NMWBUF.with(|b| *b.borrow_mut() = vec![0, 11, 22]);
OMWBUF.with(|b| *b.borrow_mut() = vec![0, 99]);
NMW_SIZE.with(|c| c.set(3));
OMW_SIZE.with(|c| c.set(2));
NMW_IND.with(|c| c.set(7));
*NBUF.lock().unwrap() = vec![];
*OBUF.lock().unwrap() = vec![];
bufswap();
NMWBUF.with(|b| {
assert_eq!(*b.borrow(), vec![0, 99], "NMWBUF now holds the old store")
});
OMWBUF.with(|b| {
assert_eq!(*b.borrow(), vec![0, 11, 22], "OMWBUF now holds the new store")
});
assert_eq!(NMW_SIZE.with(|c| c.get()), 2, "nmw_size swapped");
assert_eq!(OMW_SIZE.with(|c| c.get()), 3, "omw_size swapped");
assert_eq!(NMW_IND.with(|c| c.get()), 1, "nmw_ind reset to 1 (c:967)");
}
#[test]
fn refreshline_automargin_deferred_char_keeps_attr() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
use crate::ported::init::{tclen, tcstr};
use crate::ported::zsh_h::TXTBOLDFACE;
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let ins = crate::ported::zsh_h::TCINS as usize;
let mins = crate::ported::zsh_h::TCMULTINS as usize;
let del = crate::ported::zsh_h::TCDEL as usize;
let s_ins = (tclen.lock().unwrap()[ins], tcstr.lock().unwrap()[ins].clone());
let s_mins = tclen.lock().unwrap()[mins];
let s_del = tclen.lock().unwrap()[del];
tclen.lock().unwrap()[ins] = 1;
tcstr.lock().unwrap()[ins] = "\x1b[@".to_string();
tclen.lock().unwrap()[mins] = 0;
tclen.lock().unwrap()[del] = 0;
let save_tf = crate::ported::params::TERMFLAGS.load(Ordering::SeqCst);
crate::ported::params::TERMFLAGS.store(0, Ordering::SeqCst);
WINW.store(4, Ordering::SeqCst);
WINH.store(24, Ordering::SeqCst);
crate::ported::init::hasam.store(1, Ordering::SeqCst); PUT_RPMPT.store(0, Ordering::SeqCst);
OPUT_RPMPT.store(0, Ordering::SeqCst);
LPROMPTW.store(0, Ordering::SeqCst);
CLEAREOL.store(0, Ordering::SeqCst);
let cell = |c: char, a: u64| REFRESH_ELEMENT { chr: c, atr: a };
let mut nl: REFRESH_STRING =
vec![cell('W', 0), cell('X', 0), cell('Y', 0), cell('Z', TXTBOLDFACE)];
nl.resize(6, REFRESH_ELEMENT::default());
let mut ol: REFRESH_STRING = vec![cell('X', 0), cell('Y', 0), cell('Z', 0)];
ol.resize(6, REFRESH_ELEMENT::default());
*NBUF.lock().unwrap() = vec![nl];
*OBUF.lock().unwrap() = vec![ol];
NLNCT.store(1, Ordering::SeqCst);
OLNCT.store(1, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(wr, Ordering::SeqCst);
refreshline(0);
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(wr) };
tclen.lock().unwrap()[ins] = s_ins.0;
tcstr.lock().unwrap()[ins] = s_ins.1;
tclen.lock().unwrap()[mins] = s_mins;
tclen.lock().unwrap()[del] = s_del;
crate::ported::init::hasam.store(0, Ordering::SeqCst);
crate::ported::params::TERMFLAGS.store(save_tf, Ordering::SeqCst);
let mut out = Vec::new();
let _ = unsafe { std::fs::File::from_raw_fd(rd) }.read_to_end(&mut out);
let s = String::from_utf8_lossy(&out).into_owned();
assert!(
s.contains("\x1b[1m") && s.contains('Z'),
"deferred automargin char must keep its bold attr; got {:?}",
s
);
}
#[test]
fn refreshline_insert_path_emits_tcins_and_new_char() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
use crate::ported::init::{tclen, tcstr};
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let ins = crate::ported::zsh_h::TCINS as usize;
let mins = crate::ported::zsh_h::TCMULTINS as usize;
let del = crate::ported::zsh_h::TCDEL as usize;
let save_ins_len = tclen.lock().unwrap()[ins];
let save_ins_str = tcstr.lock().unwrap()[ins].clone();
let save_mins_len = tclen.lock().unwrap()[mins];
let save_del_len = tclen.lock().unwrap()[del];
tclen.lock().unwrap()[ins] = 1;
tcstr.lock().unwrap()[ins] = "\x1b[@".to_string();
tclen.lock().unwrap()[mins] = 0;
tclen.lock().unwrap()[del] = 0;
let winw = WINW.load(Ordering::SeqCst).max(8);
WINH.store(24, Ordering::SeqCst);
PUT_RPMPT.store(0, Ordering::SeqCst);
OPUT_RPMPT.store(0, Ordering::SeqCst);
let mk = |s: &str| -> REFRESH_STRING {
let mut r: REFRESH_STRING =
s.chars().map(|c| REFRESH_ELEMENT { chr: c, atr: 0 }).collect();
r.resize((winw + 2) as usize, REFRESH_ELEMENT::default());
r
};
*NBUF.lock().unwrap() = vec![mk("abc")];
*OBUF.lock().unwrap() = vec![mk("bc")];
NLNCT.store(1, Ordering::SeqCst);
OLNCT.store(1, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
CLEAREOL.store(0, Ordering::SeqCst);
LPROMPTW.store(0, Ordering::SeqCst);
crate::ported::init::hasam.store(0, Ordering::SeqCst);
let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(wr, Ordering::SeqCst);
refreshline(0);
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(wr) };
let mut out = Vec::new();
let _ = unsafe { std::fs::File::from_raw_fd(rd) }.read_to_end(&mut out);
let s = String::from_utf8_lossy(&out).into_owned();
tclen.lock().unwrap()[ins] = save_ins_len;
tcstr.lock().unwrap()[ins] = save_ins_str;
tclen.lock().unwrap()[mins] = save_mins_len;
tclen.lock().unwrap()[del] = save_del_len;
assert!(
s.contains("\x1b[@") && s.contains('a'),
"insert path must emit the TCINS sequence and the new char 'a'; got {:?}",
s
);
}
#[test]
fn refreshline_cleanup_pads_when_delete_costlier() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
use crate::ported::init::{tclen, tcstr};
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let ins = crate::ported::zsh_h::TCINS as usize;
let mins = crate::ported::zsh_h::TCMULTINS as usize;
let del = crate::ported::zsh_h::TCDEL as usize;
let mdel = crate::ported::zsh_h::TCMULTDEL as usize;
let save = |i: usize| (tclen.lock().unwrap()[i], tcstr.lock().unwrap()[i].clone());
let (s_ins_l, s_ins_s) = save(ins);
let (s_mins_l, _) = save(mins);
let (s_del_l, s_del_s) = save(del);
let (s_mdel_l, _) = save(mdel);
tclen.lock().unwrap()[ins] = 1;
tcstr.lock().unwrap()[ins] = "\x1b[@".to_string();
tclen.lock().unwrap()[mins] = 0;
tclen.lock().unwrap()[del] = 5;
tcstr.lock().unwrap()[del] = "\x1bDEL".to_string();
tclen.lock().unwrap()[mdel] = 0;
let winw = WINW.load(Ordering::SeqCst).max(8);
WINH.store(24, Ordering::SeqCst);
PUT_RPMPT.store(0, Ordering::SeqCst);
OPUT_RPMPT.store(0, Ordering::SeqCst);
let mk = |s: &str| -> REFRESH_STRING {
let mut r: REFRESH_STRING =
s.chars().map(|c| REFRESH_ELEMENT { chr: c, atr: 0 }).collect();
r.resize((winw + 2) as usize, REFRESH_ELEMENT::default());
r
};
*NBUF.lock().unwrap() = vec![mk("abc")];
*OBUF.lock().unwrap() = vec![mk("bc")];
NLNCT.store(1, Ordering::SeqCst);
OLNCT.store(1, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
CLEAREOL.store(0, Ordering::SeqCst);
LPROMPTW.store(0, Ordering::SeqCst);
crate::ported::init::hasam.store(0, Ordering::SeqCst);
let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(wr, Ordering::SeqCst);
refreshline(0);
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(wr) };
let mut out = Vec::new();
let _ = unsafe { std::fs::File::from_raw_fd(rd) }.read_to_end(&mut out);
let s = String::from_utf8_lossy(&out).into_owned();
tclen.lock().unwrap()[ins] = s_ins_l;
tcstr.lock().unwrap()[ins] = s_ins_s;
tclen.lock().unwrap()[mins] = s_mins_l;
tclen.lock().unwrap()[del] = s_del_l;
tcstr.lock().unwrap()[del] = s_del_s;
tclen.lock().unwrap()[mdel] = s_mdel_l;
assert!(
s.contains("\x1b[@"),
"insert must fire so char_ins>0 reaches the cleanup; got {:?}",
s
);
assert!(
!s.contains("\x1bDEL"),
"delete costlier than padding → must NOT emit TCDEL; got {:?}",
s
);
}
#[test]
fn refreshline_cleanup_pad_carries_prompt_attr() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
use crate::ported::init::{tclen, tcstr};
use crate::ported::zsh_h::TXTBOLDFACE;
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let ins = crate::ported::zsh_h::TCINS as usize;
let mins = crate::ported::zsh_h::TCMULTINS as usize;
let del = crate::ported::zsh_h::TCDEL as usize;
let s_ins = (tclen.lock().unwrap()[ins], tcstr.lock().unwrap()[ins].clone());
let s_mins = tclen.lock().unwrap()[mins];
let s_del = tclen.lock().unwrap()[del];
tclen.lock().unwrap()[ins] = 1;
tcstr.lock().unwrap()[ins] = "\x1b[@".to_string();
tclen.lock().unwrap()[mins] = 0;
tclen.lock().unwrap()[del] = 5;
let save_tf = crate::ported::params::TERMFLAGS.load(Ordering::SeqCst);
crate::ported::params::TERMFLAGS.store(0, Ordering::SeqCst); let save_pa = PROMPT_ATTR.load(Ordering::SeqCst);
PROMPT_ATTR.store(TXTBOLDFACE, Ordering::SeqCst);
let winw = WINW.load(Ordering::SeqCst).max(8);
WINH.store(24, Ordering::SeqCst);
PUT_RPMPT.store(0, Ordering::SeqCst);
OPUT_RPMPT.store(0, Ordering::SeqCst);
let mk = |s: &str| -> REFRESH_STRING {
let mut r: REFRESH_STRING =
s.chars().map(|c| REFRESH_ELEMENT { chr: c, atr: 0 }).collect();
r.resize((winw + 2) as usize, REFRESH_ELEMENT::default());
r
};
*NBUF.lock().unwrap() = vec![mk("abc")];
*OBUF.lock().unwrap() = vec![mk("bc")];
NLNCT.store(1, Ordering::SeqCst);
OLNCT.store(1, Ordering::SeqCst);
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
CLEAREOL.store(0, Ordering::SeqCst);
LPROMPTW.store(0, Ordering::SeqCst);
crate::ported::init::hasam.store(0, Ordering::SeqCst);
let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(wr, Ordering::SeqCst);
refreshline(0);
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(wr) };
tclen.lock().unwrap()[ins] = s_ins.0;
tcstr.lock().unwrap()[ins] = s_ins.1;
tclen.lock().unwrap()[mins] = s_mins;
tclen.lock().unwrap()[del] = s_del;
PROMPT_ATTR.store(save_pa, Ordering::SeqCst);
crate::ported::params::TERMFLAGS.store(save_tf, Ordering::SeqCst);
let mut out = Vec::new();
let _ = unsafe { std::fs::File::from_raw_fd(rd) }.read_to_end(&mut out);
let s = String::from_utf8_lossy(&out).into_owned();
assert!(
s.contains("\x1b[1m"),
"the cleanup pad (zr_pad) must carry prompt_attr (bold SGR); got {:?}",
s
);
}
#[test]
fn tc_rightcurs_prefers_loaded_capability() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
use crate::ported::init::{tclen, tcstr};
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let mr = crate::ported::zsh_h::TCMULTRIGHT as usize;
let hp = crate::ported::zsh_h::TCHORIZPOS as usize;
let save_mr_len = tclen.lock().unwrap()[mr];
let save_mr_str = tcstr.lock().unwrap()[mr].clone();
let save_hp_len = tclen.lock().unwrap()[hp];
let capture = |f: &dyn Fn()| -> String {
let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(wr, Ordering::SeqCst);
f();
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(wr) };
let mut out = Vec::new();
let _ = unsafe { std::fs::File::from_raw_fd(rd) }.read_to_end(&mut out);
String::from_utf8_lossy(&out).into_owned()
};
let nt = crate::ported::zsh_h::TCNEXTTAB as usize;
let save_nt_len = tclen.lock().unwrap()[nt];
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
tclen.lock().unwrap()[mr] = 3;
tcstr.lock().unwrap()[mr] = "\x1bX%dY".to_string();
tclen.lock().unwrap()[hp] = 0;
let with_cap = capture(&|| tc_rightcurs(5));
assert_eq!(with_cap, "\x1bX5Y", "should use loaded TCMULTRIGHT with count");
tclen.lock().unwrap()[mr] = 0;
tclen.lock().unwrap()[nt] = 0;
LPROMPTW.store(0, Ordering::SeqCst);
NBUF.lock().unwrap().clear();
let headless = capture(&|| tc_rightcurs(5));
assert_eq!(headless, " ", "no cap → space pad (c:2314-2315)");
tclen.lock().unwrap()[nt] = save_nt_len;
tclen.lock().unwrap()[mr] = save_mr_len;
tcstr.lock().unwrap()[mr] = save_mr_str;
tclen.lock().unwrap()[hp] = save_hp_len;
}
#[test]
fn moveto_down_past_vmaxln_emits_newlines() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
use crate::ported::init::tclen;
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let save_d = tclen.lock().unwrap()[crate::ported::zsh_h::TCDOWN as usize];
let save_md = tclen.lock().unwrap()[crate::ported::zsh_h::TCMULTDOWN as usize];
tclen.lock().unwrap()[crate::ported::zsh_h::TCDOWN as usize] = 0;
tclen.lock().unwrap()[crate::ported::zsh_h::TCMULTDOWN as usize] = 0;
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
VMAXLN.store(1, Ordering::SeqCst);
let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(wr, Ordering::SeqCst);
moveto(3, 0);
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(wr) };
tclen.lock().unwrap()[crate::ported::zsh_h::TCDOWN as usize] = save_d;
tclen.lock().unwrap()[crate::ported::zsh_h::TCMULTDOWN as usize] = save_md;
let mut out = Vec::new();
let _ = unsafe { std::fs::File::from_raw_fd(rd) }.read_to_end(&mut out);
let s = String::from_utf8_lossy(&out);
assert_eq!(VLN.load(Ordering::SeqCst), 3, "moveto must land VLN at 3");
assert!(
s.contains("\n\n\n"),
"down-past-vmaxln must emit newlines to create lines; got {:?}",
s
);
}
#[test]
fn singmoveto_tracks_global_vcs() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
use crate::ported::init::tclen;
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let li = crate::ported::zsh_h::TCMULTLEFT as usize;
let save_li = tclen.lock().unwrap()[li];
tclen.lock().unwrap()[li] = 0;
let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(wr, Ordering::SeqCst);
VCS.store(10, Ordering::SeqCst);
singmoveto(2);
let vcs_after = VCS.load(Ordering::SeqCst);
singmoveto(2);
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(wr) };
tclen.lock().unwrap()[li] = save_li;
let mut out = Vec::new();
let _ = unsafe { std::fs::File::from_raw_fd(rd) }.read_to_end(&mut out);
let s = String::from_utf8_lossy(&out);
assert_eq!(vcs_after, 2, "singmoveto must land global VCS at the target");
assert!(s.contains('\r'), "CR-home optimisation should emit \\r; got {:?}", s);
}
#[test]
fn singlerefresh_renders_line_against_blank_old() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
WINW.store(80, Ordering::SeqCst);
LPROMPTW.store(0, Ordering::SeqCst);
crate::ported::init::hasam.store(0, Ordering::SeqCst);
WINPOS.store(-1, Ordering::SeqCst);
WINPROMPT.store(0, Ordering::SeqCst);
*NBUF.lock().unwrap() = vec![vec![REFRESH_ELEMENT::default(); 82]];
*OBUF.lock().unwrap() = vec![vec![REFRESH_ELEMENT::default(); 82]];
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
let line: Vec<char> = "abc".chars().collect();
let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(wr, Ordering::SeqCst);
singlerefresh(&line, line.len() as i32, line.len() as i32);
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(wr) };
let mut out = Vec::new();
let _ = unsafe { std::fs::File::from_raw_fd(rd) }.read_to_end(&mut out);
let s = String::from_utf8_lossy(&out);
assert!(
s.contains("abc"),
"singlerefresh must emit the visible line 'abc'; got {:?}",
s
);
}
#[test]
fn singlerefresh_clusters_combining_chars() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let cc = crate::ported::zsh_h::opt_name(crate::ported::zsh_h::COMBININGCHARS);
crate::ported::options::opt_state_set(cc, true);
WINW.store(80, Ordering::SeqCst);
LPROMPTW.store(0, Ordering::SeqCst);
crate::ported::init::hasam.store(0, Ordering::SeqCst);
WINPOS.store(-1, Ordering::SeqCst);
WINPROMPT.store(0, Ordering::SeqCst);
*NBUF.lock().unwrap() = vec![vec![REFRESH_ELEMENT::default(); 82]];
*OBUF.lock().unwrap() = vec![vec![REFRESH_ELEMENT::default(); 82]];
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
let line: Vec<char> = "e\u{0301}".chars().collect(); let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(wr, Ordering::SeqCst);
singlerefresh(&line, line.len() as i32, line.len() as i32);
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(wr) };
crate::ported::options::opt_state_set(cc, false);
let mut out = Vec::new();
let _ = unsafe { std::fs::File::from_raw_fd(rd) }.read_to_end(&mut out);
let s = String::from_utf8_lossy(&out);
assert!(
s.contains('e') && s.contains('\u{0301}') && !s.contains("0301>"),
"combining mark must cluster onto 'e' (emit e+U+0301), not a <hex> \
escape; got {:?}",
s
);
}
#[test]
fn singlerefresh_wide_char_does_not_truncate_line() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
WINW.store(80, Ordering::SeqCst);
LPROMPTW.store(0, Ordering::SeqCst);
crate::ported::init::hasam.store(0, Ordering::SeqCst);
WINPOS.store(-1, Ordering::SeqCst);
WINPROMPT.store(0, Ordering::SeqCst);
*NBUF.lock().unwrap() = vec![vec![REFRESH_ELEMENT::default(); 82]];
*OBUF.lock().unwrap() = vec![vec![REFRESH_ELEMENT::default(); 82]];
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
let line: Vec<char> = "日a".chars().collect(); let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(wr, Ordering::SeqCst);
singlerefresh(&line, line.len() as i32, line.len() as i32);
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(wr) };
let mut out = Vec::new();
let _ = unsafe { std::fs::File::from_raw_fd(rd) }.read_to_end(&mut out);
let s = String::from_utf8_lossy(&out);
assert!(
s.contains('日') && s.contains('a') && !s.contains('\u{FFFF}'),
"wide char must not truncate the line: both '日' and 'a' render, \
WEOF placeholder not emitted; got {:?}",
s
);
}
#[test]
fn singlerefresh_persists_winpos_to_static() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
WINW.store(10, Ordering::SeqCst);
LPROMPTW.store(0, Ordering::SeqCst);
crate::ported::init::hasam.store(0, Ordering::SeqCst);
*NBUF.lock().unwrap() = vec![vec![REFRESH_ELEMENT::default(); 12]];
*OBUF.lock().unwrap() = vec![vec![REFRESH_ELEMENT::default(); 12]];
VCS.store(0, Ordering::SeqCst);
VLN.store(0, Ordering::SeqCst);
WINPOS.store(99, Ordering::SeqCst);
WINPROMPT.store(77, Ordering::SeqCst);
let line: Vec<char> = "01234567".chars().collect();
let devnull =
unsafe { libc::open(b"/dev/null\0".as_ptr() as *const _, libc::O_WRONLY) };
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(devnull, Ordering::SeqCst);
singlerefresh(&line, line.len() as i32, line.len() as i32);
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(devnull) };
assert_eq!(
WINPOS.load(Ordering::SeqCst),
3,
"winpos must load the persisted static, recompute (nvcs-winw/2=3), \
and store back — not stay at the bogus 99 (old local-only stub)"
);
assert_eq!(
WINPROMPT.load(Ordering::SeqCst),
0,
"winprompt must be recomputed+stored (winpos>=lpromptw → 0), not 77"
);
}
#[test]
fn tc_downcurs_newline_fallback_and_capability() {
use std::io::Read;
use std::os::unix::io::FromRawFd;
use crate::ported::init::{tclen, tcstr};
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let di = crate::ported::zsh_h::TCDOWN as usize;
let mi = crate::ported::zsh_h::TCMULTDOWN as usize;
let save_di_len = tclen.lock().unwrap()[di];
let save_di_str = tcstr.lock().unwrap()[di].clone();
let save_mi_len = tclen.lock().unwrap()[mi];
{
let mut t = tclen.lock().unwrap();
t[di] = 0;
t[mi] = 0;
}
let mut fds = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe()");
let (rd, wr) = (fds[0], fds[1]);
let old = crate::ported::init::SHTTY.load(Ordering::SeqCst);
crate::ported::init::SHTTY.store(wr, Ordering::SeqCst);
let ret = tc_downcurs(3);
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(wr) };
let mut out = Vec::new();
let _ = unsafe { std::fs::File::from_raw_fd(rd) }.read_to_end(&mut out);
assert_eq!(ret, -1, "newline fallback returns -1 (column reset)");
assert_eq!(
String::from_utf8_lossy(&out),
"\n\n\n\r",
"no down-cap → 3 newlines + CR"
);
{
let mut t = tclen.lock().unwrap();
t[di] = 4;
}
tcstr.lock().unwrap()[di] = "\x1b[B".to_string();
let mut fds2 = [0i32; 2];
assert_eq!(unsafe { libc::pipe(fds2.as_mut_ptr()) }, 0, "pipe()");
let (rd2, wr2) = (fds2[0], fds2[1]);
crate::ported::init::SHTTY.store(wr2, Ordering::SeqCst);
let ret2 = tc_downcurs(2);
crate::ported::init::SHTTY.store(old, Ordering::SeqCst);
unsafe { libc::close(wr2) };
let mut out2 = Vec::new();
let _ = unsafe { std::fs::File::from_raw_fd(rd2) }.read_to_end(&mut out2);
assert_eq!(ret2, 0, "capability path returns 0 (column preserved)");
assert_eq!(
String::from_utf8_lossy(&out2),
"\x1b[B\x1b[B",
"down-cap looped ct times, no newlines"
);
tclen.lock().unwrap()[di] = save_di_len;
tcstr.lock().unwrap()[di] = save_di_str;
tclen.lock().unwrap()[mi] = save_mi_len;
}
#[test]
fn tc_ins_del_cost_use_real_tclen() {
use crate::ported::init::tclen;
use crate::ported::zsh_h::{TCDEL, TCINS, TCMULTDEL, TCMULTINS};
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
let save = {
let t = tclen.lock().unwrap();
(
t[TCMULTINS as usize],
t[TCINS as usize],
t[TCMULTDEL as usize],
t[TCDEL as usize],
)
};
{
let mut t = tclen.lock().unwrap();
t[TCMULTINS as usize] = 0;
t[TCINS as usize] = 2;
t[TCMULTDEL as usize] = 0;
t[TCDEL as usize] = 3;
}
assert_eq!(tcinscost(4), 8, "insert: 4 chars * tclen[TCINS]=2");
assert_eq!(tcdelcost(4), 12, "delete: 4 chars * tclen[TCDEL]=3");
{
let mut t = tclen.lock().unwrap();
t[TCMULTINS as usize] = 5;
t[TCMULTDEL as usize] = 7;
}
assert_eq!(tcinscost(4), 5, "insert: parametrised tclen[TCMULTINS]=5");
assert_eq!(tcdelcost(4), 7, "delete: parametrised tclen[TCMULTDEL]=7");
let mut t = tclen.lock().unwrap();
t[TCMULTINS as usize] = save.0;
t[TCINS as usize] = save.1;
t[TCMULTDEL as usize] = save.2;
t[TCDEL as usize] = save.3;
}
#[test]
fn zr_strlen_returns_usize_type_pin2() {
let _: usize = ZR_strlen(&[]);
}
#[test]
fn zr_strlen_pure_full_sweep() {
let cases: Vec<Vec<REFRESH_ELEMENT>> = vec![
vec![],
vec![REFRESH_ELEMENT { chr: 'a', atr: 0 }],
vec![REFRESH_ELEMENT { chr: '\0', atr: 0 }],
vec![
REFRESH_ELEMENT { chr: 'x', atr: 0 },
REFRESH_ELEMENT { chr: 'y', atr: 0 },
],
];
for c in &cases {
let first = ZR_strlen(c);
for _ in 0..3 {
assert_eq!(ZR_strlen(c), first, "ZR_strlen must be pure");
}
}
}
#[test]
fn zr_strlen_empty_returns_zero() {
assert_eq!(ZR_strlen(&[]), 0, "empty → 0 length");
}
#[test]
fn zr_strncmp_symmetric_boolean_for_distinct() {
let a = [REFRESH_ELEMENT { chr: 'a', atr: 0 }];
let b = [REFRESH_ELEMENT { chr: 'b', atr: 0 }];
let ab = ZR_strncmp(&a, &b, 1);
let ba = ZR_strncmp(&b, &a, 1);
assert_eq!(ab, 1, "ZR_strncmp(a, b, 1) = 1 (different)");
assert_eq!(ba, 1, "ZR_strncmp(b, a, 1) = 1 (different, symmetric)");
}
#[test]
fn zr_strncmp_reflexive_returns_zero_alt() {
let buf = [
REFRESH_ELEMENT { chr: 'x', atr: 0 },
REFRESH_ELEMENT { chr: 'y', atr: 0 },
];
assert_eq!(
ZR_strncmp(&buf, &buf, 2),
0,
"ZR_strncmp(x, x, n) must be 0"
);
}
#[test]
fn zwcwrite_empty_idempotent() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for _ in 0..10 {
zwcwrite(&[], 0);
}
}
#[test]
fn zwcputc_various_chars_safe() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for c in ['a', '\n', '\t', '\0', '日'] {
zwcputc(&REFRESH_ELEMENT { chr: c, atr: 0 });
}
}
#[test]
fn zle_free_highlight_idempotent_alt() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
for _ in 0..5 {
zle_free_highlight();
}
}
#[test]
fn tcoutclear_both_arms_safe() {
let _g = crate::test_util::global_state_lock();
let _g2 = zle_test_setup();
tcoutclear(crate::ported::zsh_h::TCCLEARSCREEN);
tcoutclear(crate::ported::zsh_h::TCCLEAREOL);
}
#[test]
fn wpfxlen_both_empty_returns_zero_alt() {
assert_eq!(wpfxlen(&[], &[]), 0, "empty + empty → 0 common prefix");
}
#[test]
fn wpfxlen_symmetric() {
let a = [
REFRESH_ELEMENT { chr: 'a', atr: 0 },
REFRESH_ELEMENT { chr: 'b', atr: 0 },
];
let b = [
REFRESH_ELEMENT { chr: 'a', atr: 0 },
REFRESH_ELEMENT { chr: 'c', atr: 0 },
];
assert_eq!(
wpfxlen(&a, &b),
wpfxlen(&b, &a),
"wpfxlen must be symmetric"
);
}
#[test]
fn wpfxlen_identical_inputs_full_match() {
let buf = [
REFRESH_ELEMENT { chr: 'a', atr: 0 },
REFRESH_ELEMENT { chr: 'b', atr: 0 },
REFRESH_ELEMENT { chr: 'c', atr: 0 },
];
let n = wpfxlen(&buf, &buf);
assert_eq!(n, 3, "x vs x → full length 3");
}
#[test]
fn zr_memset_zero_n_safe() {
let mut buf: Vec<REFRESH_ELEMENT> = vec![];
ZR_memset(&mut buf, REFRESH_ELEMENT { chr: 'x', atr: 0 }, 0);
}
}