use std::io::{self, Write};
#[allow(non_snake_case)]
pub fn ZR_memset( dst: &mut [crate::ported::zle::zle_h::REFRESH_ELEMENT],
rc: crate::ported::zle::zle_h::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(";"))
}
}
}
#[allow(non_snake_case)]
pub fn ZR_strcpy( dst: &mut [crate::ported::zle::zle_h::REFRESH_ELEMENT],
src: &[crate::ported::zle::zle_h::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: &[crate::ported::zle::zle_h::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: &[crate::ported::zle::zle_h::REFRESH_ELEMENT],
newwstr: &[crate::ported::zle::zle_h::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) = (
crate::ported::utils::adjustcolumns(),
crate::ported::utils::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) = (
crate::ported::utils::adjustcolumns(),
crate::ported::utils::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();
}
}
}
use HighlightCategory as HC;
use crate::ported::zsh_h::TXT_MULTIWORD_MASK;
#[allow(unused_imports)]
#[allow(unused_imports)]
use crate::ported::zle::zle_main::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_misc::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_hist::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_move::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_word::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_params::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_vi::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_utils::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_tricky::*;
#[allow(unused_imports)]
use crate::ported::zle::textobjects::*;
#[allow(unused_imports)]
use crate::ported::zle::deltochar::*;
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 [
HC::Region,
HC::Isearch,
HC::Suffix,
HC::Paste,
HC::Default,
HC::Special,
HC::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" => HC::Region,
"isearch" => HC::Isearch,
"suffix" => HC::Suffix,
"paste" => HC::Paste,
"default" => HC::Default,
"special" => HC::Special,
"ellipsis" => HC::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(&HC::Region) {
manager.category_attrs.insert(HC::Region, default_standout);
}
if !seen.contains(&HC::Isearch) {
manager.category_attrs.insert(HC::Isearch, default_underline);
}
if !seen.contains(&HC::Suffix) {
manager.category_attrs.insert(HC::Suffix, default_bold);
}
if !seen.contains(&HC::Special) {
manager.category_attrs.insert(HC::Special, default_standout);
}
}
pub fn zle_free_highlight() { }
pub fn tcoutclear(to_end: bool) { use std::sync::atomic::Ordering;
let bytes: &[u8] = if to_end { b"\x1b[J" } else { b"\x1b[2J" }; let fd = crate::ported::init::SHTTY.load(Ordering::Relaxed); let _ = crate::ported::utils::write_loop(if fd >= 0 { fd } else { 1 }, bytes);
}
pub fn zwcputc(c: char) {
let mut buf = [0u8; 4];
let s = c.encode_utf8(&mut buf);
let _ = crate::ported::utils::write_loop({ use std::sync::atomic::Ordering; let f = crate::ported::init::SHTTY.load(Ordering::Relaxed); if f >= 0 { f } else { 1 } }, s.as_bytes());
}
pub fn zwcwrite(s: &str) {
let _ = crate::ported::utils::write_loop({ use std::sync::atomic::Ordering; let f = crate::ported::init::SHTTY.load(Ordering::Relaxed); if f >= 0 { f } else { 1 } }, s.as_bytes());
}
pub const DEF_MWBUF_ALLOC: usize = 32;
pub fn freevideo(state: &mut RefreshState) { state.old_video = None;
state.new_video = None;
}
pub fn resetvideo(state: &mut RefreshState) { let cols = crate::ported::utils::adjustcolumns();
let rows = crate::ported::utils::adjustlines();
state.columns = cols;
state.lines = rows;
state.old_video = Some(VideoBuffer::new(cols, rows));
state.new_video = Some(VideoBuffer::new(cols, rows));
state.need_full_redraw = true;
}
pub fn scrollwindow(lines: i32) {
let s = if lines > 0 {
format!("\x1b[{}S", lines)
} else if lines < 0 {
format!("\x1b[{}T", -lines)
} else {
return;
};
let _ = crate::ported::utils::write_loop({ use std::sync::atomic::Ordering; let f = crate::ported::init::SHTTY.load(Ordering::Relaxed); if f >= 0 { f } else { 1 } }, s.as_bytes());
}
#[allow(unused_variables)]
pub fn nextline(rpms: &mut RefreshState, wrapped: i32) -> i32 { rpms.vln += 1;
if rpms.vln >= rpms.lines {
return 1; }
rpms.vcs = 0;
0
}
pub fn snextline(rpms: &mut RefreshState) -> i32 { if rpms.vln > 0 {
rpms.vln -= 1;
}
rpms.vcs = 0;
0
}
pub fn addmultiword(base: &mut crate::ported::zle::zle_h::REFRESH_ELEMENT, _tptr: &[char], _ichars: usize) {
base.atr |= TXT_MULTIWORD_MASK;
}
pub fn bufswap(state: &mut RefreshState) { std::mem::swap(&mut state.old_video, &mut state.new_video);
}
pub fn zrefresh() { use std::fmt::Write as FmtWrite;
let mut handle = String::new();
let (cols, _rows) = (crate::ported::utils::adjustcolumns(), crate::ported::utils::adjustlines());
let prompt = prompt().to_string();
let rprompt = rprompt().to_string();
let cursor = crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst);
let prompt_width = countprompt(&prompt);
let rprompt_width = countprompt(&rprompt);
let buffer_before_cursor: String = crate::ported::zle::zle_main::ZLELINE.lock().unwrap()[..cursor.min(crate::ported::zle::zle_main::ZLELINE.lock().unwrap().len())]
.iter()
.collect();
let cursor_col = prompt_width + countprompt(&buffer_before_cursor);
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 = crate::ported::zle::zle_main::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);
use std::sync::atomic::Ordering;
let fd = crate::ported::init::SHTTY.load(Ordering::Relaxed);
let out_fd = if fd >= 0 { fd } else { 1 };
let _ = crate::ported::utils::write_loop(out_fd, handle.as_bytes());
}
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,
});
}
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: &[crate::ported::zle::zle_h::REFRESH_ELEMENT],
news: &[crate::ported::zle::zle_h::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) { use std::sync::atomic::Ordering;
let mut nl: crate::ported::zle::zle_h::REFRESH_STRING = Vec::new(); let mut ol: crate::ported::zle::zle_h::REFRESH_STRING = Vec::new(); let _p1: crate::ported::zle::zle_h::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 = crate::ported::zle::zle_h::REFRESH_ELEMENT { chr: ' ',
atr: 0,
};
rnllen = nl.len() as i32;
nllen = rnllen;
let _olnct: i32 = 0; ollen = ol.len() 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)
&& (crate::ported::init::tclen.lock().unwrap()[crate::ported::zsh_h::TCCLEAREOL as usize] != 0) {
moveto(ln as usize, 0); tcoutclear(true); return; }
let put_rpmpt = PUT_RPMPT.load(Ordering::SeqCst);
let oput_rpmpt = OPUT_RPMPT.load(Ordering::SeqCst);
let winw = crate::ported::zle::zle_refresh::WINW.load(Ordering::SeqCst);
if cleareol || (nllen == 0 && (ln != 0 || put_rpmpt == 0)) || (ln == 0 && put_rpmpt != oput_rpmpt) {
let mut padded: crate::ported::zle::zle_h::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(crate::ported::zle::zle_h::REFRESH_ELEMENT { chr: ' ', atr: 0 });
}
padded.push(crate::ported::zle::zle_h::REFRESH_ELEMENT { chr: '\0', atr: 0 }); if nllen < winw { padded.push(crate::ported::zle::zle_h::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: crate::ported::zle::zle_h::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(crate::ported::zle::zle_h::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 (crate::ported::init::tclen.lock().unwrap()[crate::ported::zsh_h::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 = 1;
if j_loc > i_loc + tclen_clear { col_cleareol = i_loc; }
}
}
}
let vcs = crate::ported::zle::zle_refresh::VCS.load(Ordering::SeqCst);
let mut vln = crate::ported::zle::zle_refresh::VLN.load(Ordering::SeqCst);
if hasam_v && vcs == winw { let next_is_nl = false; if next_is_nl { vln += 1; let new_vcs = 1; crate::ported::zle::zle_refresh::VLN.store(vln, Ordering::SeqCst);
crate::ported::zle::zle_refresh::VCS.store(new_vcs, Ordering::SeqCst);
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; crate::ported::zle::zle_refresh::VLN.store(vln, Ordering::SeqCst);
crate::ported::zle::zle_refresh::VCS.store(0, Ordering::SeqCst);
}
}
ins_last = 0;
let lpromptw = crate::ported::zle::zle_refresh::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; }
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
{
crate::ported::zle::zle_refresh::VCS.store(vcs + 1, Ordering::SeqCst);
return; }
if char_ins <= 0 || ccs >= winw { return; }
if (crate::ported::init::tclen.lock().unwrap()[crate::ported::zsh_h::TCCLEAREOL as usize] != 0) && (char_ins >= 1)
&& col_cleareol != -2
{
col_cleareol = 0; }
}
moveto(ln as usize, ccs as usize);
if col_cleareol >= 0 && ccs >= col_cleareol { tcoutclear(true); return; }
if nl.is_empty() || nl[0].chr == '\0' { let i_pad = if winw - ccs < char_ins { winw - ccs
} else {
char_ins
};
let can_del = (crate::ported::init::tclen.lock().unwrap()[crate::ported::zsh_h::TCDEL as usize] != 0) && i_pad <= i_pad + 1;
if can_del {
} else { crate::ported::zle::zle_refresh::VCS.store(vcs + i_pad, Ordering::SeqCst); }
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 { crate::ported::zle::zle_refresh::VCS.store(vcs + i_write, Ordering::SeqCst); }
if col_cleareol >= 0 { tcoutclear(true); }
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 (crate::ported::init::tclen.lock().unwrap()[crate::ported::zsh_h::TCDEL as usize] != 0) { let mut i_try = 1i32; while (i_try as usize) < ol.len() && ol[i_try as usize].chr != '\0' {
let cheap_delete = false; if cheap_delete {
for _ in 0..i_try {
if !ol.is_empty() { ol.remove(0); } }
char_ins -= i_try; i_try = 0; break;
}
i_try += 1;
}
if i_try != 0 { continue; } }
let zterm_lines = crate::ported::zle::zle_refresh::WINH.load(Ordering::SeqCst);
if (crate::ported::init::tclen.lock().unwrap()[crate::ported::zsh_h::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 cheap_insert = false; if cheap_insert {
for _ in 0..i_try {
if !nl.is_empty() { nl.remove(0); } }
char_ins += i_try; crate::ported::zle::zle_refresh::VCS.store(vcs + i_try, Ordering::SeqCst);
ccs += i_try; 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] = crate::ported::zle::zle_h::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 !nl.is_empty() { nl.remove(0); } if !ol.is_empty() && ol[0].chr != '\0' { ol.remove(0); }
ccs += 1; crate::ported::zle::zle_refresh::VCS.store(vcs + 1, Ordering::SeqCst);
break;
}
}
let _ = (rnllen, ollen, ins_last, _olnct, _p1, _j); }
pub fn moveto(row: usize, col: usize) { let s = format!("\x1b[{};{}H", row + 1, col + 1);
let _ = crate::ported::utils::write_loop({ use std::sync::atomic::Ordering; let f = crate::ported::init::SHTTY.load(Ordering::Relaxed); if f >= 0 { f } else { 1 } }, s.as_bytes());
}
pub fn tcmultout(cap: &str, count: i32) { use std::sync::atomic::Ordering;
let fd = crate::ported::init::SHTTY.load(Ordering::Relaxed);
let out_fd = if fd >= 0 { fd } else { 1 };
for _ in 0..count { let _ = crate::ported::utils::write_loop(out_fd, cap.as_bytes());
}
}
pub fn tc_rightcurs(count: usize) {
if count > 0 {
let s = format!("\x1b[{}C", count);
let _ = crate::ported::utils::write_loop({ use std::sync::atomic::Ordering; let f = crate::ported::init::SHTTY.load(Ordering::Relaxed); if f >= 0 { f } else { 1 } }, s.as_bytes());
}
}
pub fn tc_downcurs(count: usize) {
if count > 0 {
let s = format!("\x1b[{}B", count);
let _ = crate::ported::utils::write_loop({ use std::sync::atomic::Ordering; let f = crate::ported::init::SHTTY.load(Ordering::Relaxed); if f >= 0 { f } else { 1 } }, s.as_bytes());
}
}
pub fn tcout_via_func(_cap: i32, _arg: i32) -> i32 { if crate::ported::utils::getshfunc("tcout").is_some() {
let _ = crate::ported::zle::compcore::shfunc_call("tcout");
return 0;
}
1
}
pub fn tcout(cap: &str) { use std::sync::atomic::Ordering;
let fd = crate::ported::init::SHTTY.load(Ordering::Relaxed);
if fd >= 0 {
let _ = crate::ported::utils::write_loop(fd, cap.as_bytes());
} else {
let _ = crate::ported::utils::write_loop(1, cap.as_bytes());
}
}
pub fn tcoutarg(cap: &str, arg: i32) { use std::sync::atomic::Ordering;
let s = cap.replace("%d", &arg.to_string());
let fd = crate::ported::init::SHTTY.load(Ordering::Relaxed);
let out_fd = if fd >= 0 { fd } else { 1 };
let _ = crate::ported::utils::write_loop(out_fd, s.as_bytes()); }
pub fn clearscreen() { let _ = crate::ported::utils::write_loop({ use std::sync::atomic::Ordering; let f = crate::ported::init::SHTTY.load(Ordering::Relaxed); if f >= 0 { f } else { 1 } }, b"\x1b[2J\x1b[H");
zrefresh();
}
pub fn redisplay() { zrefresh();
}
pub fn singlerefresh(tmpline: &[char], tmpll: i32, mut tmpcs: i32) { use std::sync::atomic::Ordering;
use crate::ported::zle::zle_h::REFRESH_ELEMENT;
let mut vbuf: crate::ported::zle::zle_h::REFRESH_STRING; let mut vp: usize; let _refreshop: crate::ported::zle::zle_h::REFRESH_STRING = Vec::new(); let mut t0: i32; let mut vsiz: i32; let mut nvcs: i32 = 0; let owinpos: i32 = -1; let _owinprompt: i32 = 0; 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 crate::ported::zsh_h::isset(
crate::ported::zsh_h::COMBININGCHARS,
) {
while t0 < tmpll - 1 { let next = *tmpline.get((t0 + 1) as usize).unwrap_or(&'\0');
let is_combining = unicode_width::UnicodeWidthChar::width(next)
== Some(0);
if !is_combining { 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 ichars: i32 = 1; if vp < vbuf.len() {
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: '\0', 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 = -1;
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 }; }
}
drop(vbuf); nvcs -= winpos;
let _winprompt: i32 = if winpos < lpromptw { lpromptw - winpos } else { 0 };
if winpos != owinpos { singmoveto(&mut crate::ported::zle::zle_refresh::RefreshState::new(), 0); }
singmoveto(&mut crate::ported::zle::zle_refresh::RefreshState::new(), nvcs as usize);
let _ = (lpromptw, width); }
pub fn singmoveto(state: &mut RefreshState, pos: usize) { state.vcs = pos;
}
pub fn zle_refresh_boot() -> RefreshState {
RefreshState::new()
}
pub fn zle_refresh_finish() { let mut state = RefreshState::new(); freevideo(&mut state); let mut rh = REGION_HIGHLIGHTS.lock().unwrap(); if !rh.is_empty() {
crate::ported::zle::zle_utils::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>,
}
#[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 = crate::ported::zle::zle_main::ZLELINE.lock().unwrap().len();
let mut attrs: Vec<Option<TextAttr>> = vec![None; buf_len];
let visual_attr = crate::ported::zle::zle_main::highlight().lock().unwrap()
.category_attrs
.get(&HighlightCategory::Region)
.copied()
.unwrap_or(TextAttr {
standout: true,
..TextAttr::default()
});
if crate::ported::zle::zle_main::REGION_ACTIVE.load(std::sync::atomic::Ordering::SeqCst) != 0 {
let (lo, hi) = if crate::ported::zle::zle_main::MARK.load(std::sync::atomic::Ordering::SeqCst) <= crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst) {
(crate::ported::zle::zle_main::MARK.load(std::sync::atomic::Ordering::SeqCst), crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst))
} else {
(crate::ported::zle::zle_main::ZLECS.load(std::sync::atomic::Ordering::SeqCst), crate::ported::zle::zle_main::MARK.load(std::sync::atomic::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 &crate::ported::zle::zle_main::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<()> {
use std::sync::atomic::Ordering;
let fd = crate::ported::init::SHTTY.load(Ordering::Relaxed);
let out = if fd >= 0 { fd } else { 1 };
let _ = crate::ported::utils::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: crate::ported::zle::zle_h::REFRESH_ELEMENT,
b: crate::ported::zle::zle_h::REFRESH_ELEMENT,
) -> bool {
a == b
}
#[inline]
#[allow(non_snake_case)]
pub fn ZR_memcpy( dst: &mut [crate::ported::zle::zle_h::REFRESH_ELEMENT],
src: &[crate::ported::zle::zle_h::REFRESH_ELEMENT],
l: usize,
) {
dst[..l].copy_from_slice(&src[..l]);
}
pub static ZR_END_ELLIPSIS: &[crate::ported::zle::zle_h::REFRESH_ELEMENT] = &[ crate::ported::zle::zle_h::REFRESH_ELEMENT { chr: ' ', atr: 0 },
crate::ported::zle::zle_h::REFRESH_ELEMENT { chr: '.', atr: crate::ported::zsh_h::TXT_ERROR },
crate::ported::zle::zle_h::REFRESH_ELEMENT { chr: '.', atr: crate::ported::zsh_h::TXT_ERROR },
crate::ported::zle::zle_h::REFRESH_ELEMENT { chr: '.', atr: crate::ported::zsh_h::TXT_ERROR },
crate::ported::zle::zle_h::REFRESH_ELEMENT { chr: '.', atr: crate::ported::zsh_h::TXT_ERROR },
crate::ported::zle::zle_h::REFRESH_ELEMENT { chr: '>', atr: 0 },
];
pub static ZR_MID_ELLIPSIS1: &[crate::ported::zle::zle_h::REFRESH_ELEMENT] = &[ crate::ported::zle::zle_h::REFRESH_ELEMENT { chr: ' ', atr: 0 },
crate::ported::zle::zle_h::REFRESH_ELEMENT { chr: '<', atr: 0 },
crate::ported::zle::zle_h::REFRESH_ELEMENT { chr: '.', atr: crate::ported::zsh_h::TXT_ERROR },
crate::ported::zle::zle_h::REFRESH_ELEMENT { chr: '.', atr: crate::ported::zsh_h::TXT_ERROR },
crate::ported::zle::zle_h::REFRESH_ELEMENT { chr: '.', atr: crate::ported::zsh_h::TXT_ERROR },
crate::ported::zle::zle_h::REFRESH_ELEMENT { chr: '.', atr: crate::ported::zsh_h::TXT_ERROR },
];
pub static ZR_MID_ELLIPSIS2: &[crate::ported::zle::zle_h::REFRESH_ELEMENT] = &[ crate::ported::zle::zle_h::REFRESH_ELEMENT { chr: '>', atr: crate::ported::zsh_h::TXT_ERROR },
crate::ported::zle::zle_h::REFRESH_ELEMENT { chr: ' ', atr: 0 },
];
pub static ZR_START_ELLIPSIS: &[crate::ported::zle::zle_h::REFRESH_ELEMENT] = &[ crate::ported::zle::zle_h::REFRESH_ELEMENT { chr: '>', atr: 0 },
crate::ported::zle::zle_h::REFRESH_ELEMENT { chr: '.', atr: crate::ported::zsh_h::TXT_ERROR },
crate::ported::zle::zle_h::REFRESH_ELEMENT { chr: '.', atr: crate::ported::zsh_h::TXT_ERROR },
crate::ported::zle::zle_h::REFRESH_ELEMENT { chr: '.', atr: crate::ported::zsh_h::TXT_ERROR },
crate::ported::zle::zle_h::REFRESH_ELEMENT { chr: '.', atr: crate::ported::zsh_h::TXT_ERROR },
];
#[inline] pub fn tcinscost(x: i32) -> i32 { x.max(0)
}
#[inline] pub fn tcdelcost(x: i32) -> i32 { x.max(0)
}
#[inline] pub fn tc_delchars(_x: i32) { }
#[inline] pub fn tc_inschars(_x: i32) { }
#[inline] pub fn tc_upcurs(_x: i32) { }
#[inline] pub fn tc_leftcurs(_x: i32) { }
pub static TCOUT_FUNC_NAME: std::sync::Mutex<Option<String>> = std::sync::Mutex::new(None);
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 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 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 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..]; }
let _ = flags;
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 = crate::ported::zle::zle_refresh::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,
});
}
}
}
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::{TXT_MULTIWORD_MASK, TXTBOLDFACE};
fn re(c: char, a: u64) -> REFRESH_ELEMENT {
REFRESH_ELEMENT { chr: c, atr: a }
}
#[test]
fn zr_memset_fills_slice() {
let _g = crate::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::zle_test_setup();
let s = [re('\0', 0)];
assert_eq!(ZR_strlen(&s), 0);
}
#[test]
fn zr_strcpy_copies_through_nul() {
let _g = crate::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::zle_test_setup();
assert_eq!(DEF_MWBUF_ALLOC, 32);
}
#[test]
fn tc_costs_handle_negative() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert_eq!(tcinscost(-1), 0);
assert_eq!(tcdelcost(-1), 0);
assert_eq!(tcinscost(5), 5);
assert_eq!(tcdelcost(5), 5);
}
#[test]
fn rparams_default_zeros_all_fields() {
let _g = crate::ported::zle::zle_main::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 test_countprompt() {
let _g = crate::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::zle_test_setup();
*crate::ported::zle::zle_main::ZLELINE.lock().unwrap() = "hello world".chars().collect();
crate::ported::zle::zle_main::ZLELL.store(crate::ported::zle::zle_main::ZLELINE.lock().unwrap().len(), std::sync::atomic::Ordering::SeqCst);
crate::ported::zle::zle_main::MARK.store(2, std::sync::atomic::Ordering::SeqCst);
crate::ported::zle::zle_main::ZLECS.store(7, std::sync::atomic::Ordering::SeqCst);
crate::ported::zle::zle_main::REGION_ACTIVE.store(1, std::sync::atomic::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::ported::zle::zle_main::zle_test_setup();
*crate::ported::zle::zle_main::ZLELINE.lock().unwrap() = "abcdef".chars().collect();
crate::ported::zle::zle_main::ZLELL.store(6, std::sync::atomic::Ordering::SeqCst);
crate::ported::zle::zle_main::MARK.store(5, std::sync::atomic::Ordering::SeqCst);
crate::ported::zle::zle_main::ZLECS.store(1, std::sync::atomic::Ordering::SeqCst);
crate::ported::zle::zle_main::REGION_ACTIVE.store(2, std::sync::atomic::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::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::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::ported::zle::zle_main::zle_test_setup();
crate::ported::zle::zle_main::zle_reset();
*crate::ported::zle::zle_main::ZLELINE.lock().unwrap() = "abcde".chars().collect();
crate::ported::zle::zle_main::ZLELL.store(5, std::sync::atomic::Ordering::SeqCst);
crate::ported::zle::zle_main::MARK.store(1, std::sync::atomic::Ordering::SeqCst);
crate::ported::zle::zle_main::ZLECS.store(4, std::sync::atomic::Ordering::SeqCst);
crate::ported::zle::zle_main::REGION_ACTIVE.store(1, std::sync::atomic::Ordering::SeqCst);
zle_set_highlight(&mut crate::ported::zle::zle_main::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::ported::zle::zle_main::zle_test_setup();
*crate::ported::zle::zle_main::ZLELINE.lock().unwrap() = "abcde".chars().collect();
crate::ported::zle::zle_main::ZLELL.store(5, std::sync::atomic::Ordering::SeqCst);
let custom = TextAttr {
bold: true,
fg_color: Some(1),
..TextAttr::default()
};
crate::ported::zle::zle_main::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());
}
}