use std::cell::RefCell;
use std::env;
pub(crate) mod prompt_tls {
use std::cell::RefCell;
use std::env;
thread_local! {
pub(super) static PWD: RefCell<String> = const { RefCell::new(String::new()) };
pub(super) static HOME: RefCell<String> = const { RefCell::new(String::new()) };
pub(super) static USER: RefCell<String> = const { RefCell::new(String::new()) };
pub(super) static HOST: RefCell<String> = const { RefCell::new(String::new()) };
pub(super) static HOST_SHORT: RefCell<String> = const { RefCell::new(String::new()) };
pub(super) static TTY: RefCell<String> = const { RefCell::new(String::new()) };
pub(super) static LASTVAL: RefCell<i32> = const { RefCell::new(0) };
pub(super) static HISTNUM: RefCell<i64> = const { RefCell::new(1) };
pub(super) static SHLVL: RefCell<i32> = const { RefCell::new(1) };
pub(super) static NUM_JOBS: RefCell<i32> = const { RefCell::new(0) };
pub(super) static IS_ROOT: RefCell<bool> = const { RefCell::new(false) };
pub(super) static CMDSTACK: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
pub(super) static PSVAR: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
pub(super) static TERM_WIDTH: RefCell<usize> = const { RefCell::new(80) };
pub(super) static LINENO: RefCell<i64> = const { RefCell::new(1) };
pub(super) static SCRIPTNAME: RefCell<Option<String>> = const { RefCell::new(None) };
pub(super) static SCRIPTFILENAME: RefCell<Option<String>> =
const { RefCell::new(None) };
pub(super) static ARGEXTRA: RefCell<String> = const { RefCell::new(String::new()) };
pub(super) static FUNC_LINE_BASE: RefCell<Option<i64>> = const { RefCell::new(None) };
pub(super) static FUNCSTACK_FILENAME: RefCell<Option<String>> =
const { RefCell::new(None) };
}
pub(crate) fn sync_from_globals() {
use crate::ported::params::getsparam;
let pwd = getsparam("PWD")
.filter(|p| !p.is_empty())
.or_else(|| env::var("PWD").ok().filter(|p| !p.is_empty()))
.or_else(|| {
env::current_dir()
.ok()
.map(|p| p.to_string_lossy().to_string())
})
.unwrap_or_else(|| "/".to_string());
let home = getsparam("HOME").unwrap_or_default();
let user = getsparam("USER")
.or_else(|| getsparam("LOGNAME"))
.or_else(|| env::var("USER").ok())
.or_else(|| env::var("LOGNAME").ok())
.unwrap_or_else(|| "user".to_string());
let host = getsparam("HOST")
.or_else(|| {
hostname::get().ok().map(|h| h.to_string_lossy().to_string())
})
.unwrap_or_else(|| "localhost".to_string());
let host_short = host.split('.').next().unwrap_or(&host).to_string();
let shlvl = getsparam("SHLVL")
.and_then(|s| s.parse().ok())
.or_else(|| env::var("SHLVL").ok().and_then(|s| s.parse().ok()))
.unwrap_or(1);
PWD.with(|c| *c.borrow_mut() = pwd);
HOME.with(|c| *c.borrow_mut() = home);
USER.with(|c| *c.borrow_mut() = user);
HOST.with(|c| *c.borrow_mut() = host);
HOST_SHORT.with(|c| *c.borrow_mut() = host_short);
TTY.with(|c| c.borrow_mut().clear());
LASTVAL.with(|c| {
*c.borrow_mut() = crate::ported::builtin::LASTVAL
.load(std::sync::atomic::Ordering::Relaxed);
});
HISTNUM.with(|c| {
*c.borrow_mut() =
crate::ported::hist::curhist.load(std::sync::atomic::Ordering::Relaxed);
});
SHLVL.with(|c| *c.borrow_mut() = shlvl);
NUM_JOBS.with(|c| {
*c.borrow_mut() = crate::ported::jobs::JOBTAB
.get_or_init(|| std::sync::Mutex::new(Vec::new()))
.lock()
.map(|t| t.iter().filter(|j| j.is_inuse()).count() as i32)
.unwrap_or(0);
});
IS_ROOT.with(|c| *c.borrow_mut() = unsafe { libc::geteuid() } == 0);
CMDSTACK.with(|c| {
*c.borrow_mut() = super::CMDSTACK.with(|stack| stack.borrow().clone());
});
PSVAR.with(|c| {
*c.borrow_mut() = crate::ported::params::paramtab().read()
.ok()
.and_then(|t| t.get("psvar").and_then(|p| p.u_arr.clone()))
.unwrap_or_default();
});
TERM_WIDTH.with(|c| {
*c.borrow_mut() = crate::ported::utils::adjustcolumns();
});
LINENO.with(|c| {
*c.borrow_mut() = getsparam("LINENO")
.and_then(|s| s.parse::<i64>().ok())
.unwrap_or(1);
});
let scriptname = crate::ported::utils::scriptname_get();
SCRIPTNAME.with(|c| *c.borrow_mut() = scriptname.clone()
.or_else(|| getsparam("0")));
SCRIPTFILENAME.with(|c| *c.borrow_mut() = scriptname.clone()
.or_else(|| getsparam("0")));
FUNC_LINE_BASE.with(|c| *c.borrow_mut() = None);
FUNCSTACK_FILENAME.with(|c| {
*c.borrow_mut() = crate::ported::modules::parameter::FUNCSTACK
.lock().ok()
.and_then(|s| s.last().and_then(|fs| fs.filename.clone()));
});
ARGEXTRA.with(|c| {
*c.borrow_mut() = getsparam("ZSH_ARGZERO")
.or_else(|| env::args().next())
.unwrap_or_else(|| "zsh".to_string());
});
}
}
pub static CMDNAMES: [&str; crate::ported::zsh_h::CS_COUNT as usize] = [
"for", "while", "repeat", "select", "until", "if", "then", "else", "elif", "math", "cond", "cmdor", "cmdand", "pipe", "errpipe", "foreach", "case", "function", "subsh", "cursh", "array", "quote", "dquote", "bquote", "cmdsubst", "mathsubst", "elif-then", "heredoc", "heredocd", "brace", "braceparam", "always", ];
use crate::ported::zsh_h::{
TXTBOLDFACE, TXTFGCOLOUR, TXTBGCOLOUR, TXTSTANDOUT, TXTUNDERLINE, TXT_ATTR_FG_COL_MASK, TXT_ATTR_FG_COL_SHIFT,
TXT_ATTR_BG_COL_MASK, TXT_ATTR_BG_COL_SHIFT,
TXT_ATTR_FG_24BIT, TXT_ATTR_BG_24BIT,
TXT_ATTR_FG_MASK, TXT_ATTR_BG_MASK,
zattr,
};
use crate::zsh_h::{Inpar, Nularg, Outpar};
pub type Color = u32; pub const COLOR_24BIT: Color = 0x0100_0000;
pub const COLOUR_DEFAULT: u8 = 8;
pub const COLOR_BLACK: Color = 0; pub const COLOR_RED: Color = 1; pub const COLOR_GREEN: Color = 2; pub const COLOR_YELLOW: Color = 3; pub const COLOR_BLUE: Color = 4; pub const COLOR_MAGENTA: Color = 5; pub const COLOR_CYAN: Color = 6; pub const COLOR_WHITE: Color = 7; pub const COLOR_DEFAULT: Color = COLOUR_DEFAULT as Color;
pub static COLOUR_NAMES: [&str; 9] = [
"black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "default", ];
fn color_rgb(r: u8, g: u8, b: u8) -> Color {
COLOR_24BIT | ((r as Color) << 16) | ((g as Color) << 8) | (b as Color)
}
fn color_get_rgb(c: Color) -> Option<(u8, u8, u8)> {
if c & COLOR_24BIT == 0 {
None
} else {
Some((
((c >> 16) & 0xff) as u8,
((c >> 8) & 0xff) as u8,
(c & 0xff) as u8,
))
}
}
fn color_to_ansi(c: Color, is_fg: bool) -> String {
if let Some((r, g, b)) = color_get_rgb(c) {
let lead = if is_fg { 38 } else { 48 };
format!("\x1b[{};2;{};{};{}m", lead, r, g, b)
} else {
output_colour(c as u8, is_fg)
}
}
fn color_from_name(name: &str) -> Option<Color> {
if let Some(rest) = name.strip_prefix('#') {
if rest.len() == 6 {
let r = u8::from_str_radix(&rest[0..2], 16).ok();
let g = u8::from_str_radix(&rest[2..4], 16).ok();
let b = u8::from_str_radix(&rest[4..6], 16).ok();
match (r, g, b) {
(Some(r), Some(g), Some(b)) => Some(color_rgb(r, g, b) as Color),
_ => None,
}
} else {
match_named_colour(name).map(|idx| idx as Color)
}
} else {
match_named_colour(name).map(|idx| idx as Color)
}
}
fn zattr_set_fg_palette(attrs: zattr, idx: u8) -> zattr {
let cleared = attrs & !TXT_ATTR_FG_MASK;
cleared | TXTFGCOLOUR | ((idx as zattr) << TXT_ATTR_FG_COL_SHIFT)
}
fn zattr_set_fg_rgb(attrs: zattr, r: u8, g: u8, b: u8) -> zattr {
let cleared = attrs & !TXT_ATTR_FG_MASK;
let rgb = ((r as zattr) << 16) | ((g as zattr) << 8) | (b as zattr);
cleared | TXTFGCOLOUR | TXT_ATTR_FG_24BIT | (rgb << TXT_ATTR_FG_COL_SHIFT)
}
fn zattr_set_bg_palette(attrs: zattr, idx: u8) -> zattr {
let cleared = attrs & !TXT_ATTR_BG_MASK;
cleared | TXTBGCOLOUR | ((idx as zattr) << TXT_ATTR_BG_COL_SHIFT)
}
fn zattr_set_bg_rgb(attrs: zattr, r: u8, g: u8, b: u8) -> zattr {
let cleared = attrs & !TXT_ATTR_BG_MASK;
let rgb = ((r as zattr) << 16) | ((g as zattr) << 8) | (b as zattr);
cleared | TXTBGCOLOUR | TXT_ATTR_BG_24BIT | (rgb << TXT_ATTR_BG_COL_SHIFT)
}
#[allow(non_camel_case_types)]
pub struct buf_vars { pwd: String,
home: String,
user: String,
host: String,
host_short: String,
tty: String,
lastval: i32,
histnum: i64,
shlvl: i32,
num_jobs: i32,
is_root: bool,
cmd_stack: Vec<u8>,
psvar: Vec<String>,
term_width: usize,
lineno: i64,
scriptname: Option<String>,
scriptfilename: Option<String>,
argzero: String,
func_line_base: Option<i64>,
funcstack_filename: Option<String>,
pub buf: Vec<u8>,
pub bufspc: usize,
pub bp: usize,
pub bufline: usize,
pub bp1: Option<usize>,
pub fm: String,
pub fm_pos: usize,
pub truncwidth: i32,
pub dontcount: i32,
pub trunccount: i32,
pub rstring: Option<String>,
pub Rstring: Option<String>,
attrs: zattr,
in_escape: bool,
prompt_percent: bool,
prompt_bang: bool,
}
impl buf_vars {
pub fn new(input: &str) -> Self {
Self {
pwd: prompt_tls::PWD.with(|c| c.borrow().clone()),
home: prompt_tls::HOME.with(|c| c.borrow().clone()),
user: prompt_tls::USER.with(|c| c.borrow().clone()),
host: prompt_tls::HOST.with(|c| c.borrow().clone()),
host_short: prompt_tls::HOST_SHORT.with(|c| c.borrow().clone()),
tty: prompt_tls::TTY.with(|c| c.borrow().clone()),
lastval: prompt_tls::LASTVAL.with(|c| *c.borrow()),
histnum: prompt_tls::HISTNUM.with(|c| *c.borrow()),
shlvl: prompt_tls::SHLVL.with(|c| *c.borrow()),
num_jobs: prompt_tls::NUM_JOBS.with(|c| *c.borrow()),
is_root: prompt_tls::IS_ROOT.with(|c| *c.borrow()),
cmd_stack: prompt_tls::CMDSTACK.with(|c| c.borrow().clone()),
psvar: prompt_tls::PSVAR.with(|c| c.borrow().clone()),
term_width: prompt_tls::TERM_WIDTH.with(|c| *c.borrow()),
lineno: prompt_tls::LINENO.with(|c| *c.borrow()),
scriptname: prompt_tls::SCRIPTNAME.with(|c| c.borrow().clone()),
scriptfilename: prompt_tls::SCRIPTFILENAME.with(|c| c.borrow().clone()),
argzero: prompt_tls::ARGEXTRA.with(|c| c.borrow().clone()),
func_line_base: prompt_tls::FUNC_LINE_BASE.with(|c| *c.borrow()),
funcstack_filename: prompt_tls::FUNCSTACK_FILENAME.with(|c| c.borrow().clone()),
buf: vec![0u8; 256],
bufspc: 256,
bp: 0,
bufline: 0,
bp1: None,
fm: input.to_string(),
fm_pos: 0,
truncwidth: 0,
dontcount: 0,
trunccount: 0,
rstring: None,
Rstring: None,
attrs: 0 as zattr, in_escape: false,
prompt_percent: true, prompt_bang: true, }
}
pub fn with_prompt_percent(mut self, enable: bool) -> Self {
self.prompt_percent = enable;
self
}
pub fn with_prompt_bang(mut self, enable: bool) -> Self {
self.prompt_bang = enable;
self
}
fn fork_snapshot(&self, input: String) -> buf_vars {
buf_vars {
pwd: self.pwd.clone(),
home: self.home.clone(),
user: self.user.clone(),
host: self.host.clone(),
host_short: self.host_short.clone(),
tty: self.tty.clone(),
lastval: self.lastval,
histnum: self.histnum,
shlvl: self.shlvl,
num_jobs: self.num_jobs,
is_root: self.is_root,
cmd_stack: self.cmd_stack.clone(),
psvar: self.psvar.clone(),
term_width: self.term_width,
lineno: self.lineno,
scriptname: self.scriptname.clone(),
scriptfilename: self.scriptfilename.clone(),
argzero: self.argzero.clone(),
func_line_base: self.func_line_base,
funcstack_filename: self.funcstack_filename.clone(),
buf: Vec::new(),
bufspc: 0,
bp: 0,
bufline: 0,
bp1: None,
fm: input,
fm_pos: 0,
truncwidth: 0,
dontcount: 0,
trunccount: 0,
rstring: None,
Rstring: None,
attrs: self.attrs,
in_escape: false,
prompt_percent: self.prompt_percent,
prompt_bang: self.prompt_bang,
}
}
fn addbufspc(&mut self, need: usize) {
let need = need.saturating_mul(2).max(need.max(1));
self.buf.reserve(need);
self.bufspc = self.buf.capacity();
}
fn pputc(&mut self, c: u8) {
self.addbufspc(2);
let bp = self.bp;
if crate::ported::utils::imeta_byte(c) {
self.buf.resize(bp + 2, 0);
self.buf[bp] = crate::ported::utils::Meta;
self.buf[bp + 1] = c ^ 32;
self.bp = bp + 2;
} else {
self.buf.resize(bp + 1, 0);
self.buf[bp] = c;
self.bp = bp + 1;
}
if c == b'\n' && self.dontcount == 0 {
self.bufline = self.bp;
}
}
fn out_raw_byte(&mut self, b: u8) {
self.addbufspc(1);
let bp = self.bp;
self.buf.resize(bp + 1, 0);
self.buf[bp] = b;
self.bp = bp + 1;
}
fn out_char(&mut self, c: char) {
let mut tmp = [0u8; 4];
let enc = c.encode_utf8(&mut tmp);
for &b in enc.as_bytes() {
self.pputc(b);
}
}
fn out_str(&mut self, s: &str) {
for &b in s.as_bytes() {
self.pputc(b);
}
}
fn append_buf_from(&mut self, other: &buf_vars) {
let end = other.bp.min(other.buf.len());
if end == 0 {
return;
}
self.addbufspc(end);
let bp0 = self.bp;
self.buf.resize(bp0 + end, 0);
self.buf[bp0..bp0 + end].copy_from_slice(&other.buf[..end]);
self.bp = bp0 + end;
if self.dontcount == 0 {
for i in 0..end {
if other.buf[i] == b'\n' {
self.bufline = bp0 + i + 1;
}
}
}
}
pub fn expanded_utf8(&self) -> String {
let end = self.bp.min(self.buf.len());
let mut v = self.buf[..end].to_vec();
crate::ported::utils::unmetafy(&mut v);
String::from_utf8_lossy(&v).into_owned()
}
fn strip_prompt_tokens_ns0(&mut self) {
let end = self.bp.min(self.buf.len());
let mut v = Vec::with_capacity(end);
let mut i = 0usize;
while i < end {
let b = self.buf[i];
if b == crate::ported::utils::Meta {
if i + 1 < end {
v.push(b);
v.push(self.buf[i + 1]);
i += 2;
} else {
i += 1;
}
continue;
}
if b == Inpar as u8 || b == Outpar as u8 || b == Nularg as u8 {
i += 1;
continue;
}
v.push(b);
i += 1;
}
self.buf = v;
self.bp = self.buf.len();
}
pub fn finish_expanded_string(&mut self, keep_spacing_tokens: bool) -> String {
if !keep_spacing_tokens {
self.strip_prompt_tokens_ns0();
}
self.expanded_utf8()
}
pub(crate) fn run_putpromptchar(&mut self, doprint: i32, endchar: i32) -> i32 {
loop {
if self.fm_pos >= self.fm.len() {
return 0;
}
let ec = endchar as u8;
if ec != 0 {
let b = self.fm.as_bytes()[self.fm_pos];
if b == ec {
return endchar;
}
}
let c = match self.peek() {
Some(c) => c,
None => return 0,
};
if c == '%' && self.prompt_percent {
self.advance();
self.process_percent(doprint);
} else if c == '!' && self.prompt_bang {
if doprint != 0 {
self.advance();
if self.peek() == Some('!') {
self.advance();
self.out_char('!');
} else {
self.out_str(&self.histnum.to_string());
}
} else {
self.advance();
if self.peek() == Some('!') {
self.advance();
}
}
} else {
self.advance();
if doprint != 0 {
self.out_char(c);
}
}
}
}
fn peek(&self) -> Option<char> {
self.fm[self.fm_pos..].chars().next()
}
fn advance(&mut self) -> Option<char> {
let c = self.peek()?;
self.fm_pos += c.len_utf8();
Some(c)
}
fn parse_number(&mut self) -> Option<i32> {
let start = self.fm_pos;
let mut negative = false;
if self.peek() == Some('-') {
negative = true;
self.advance();
}
while let Some(c) = self.peek() {
if c.is_ascii_digit() {
self.advance();
} else {
break;
}
}
if self.fm_pos == start || (negative && self.fm_pos == start + 1) {
if negative {
self.fm_pos = start;
}
return None;
}
let num_str = &self.fm[if negative { start + 1 } else { start }..self.fm_pos];
let num: i32 = num_str.parse().ok()?;
Some(if negative { -num } else { num })
}
fn parse_braced_arg(&mut self) -> Option<String> {
if self.peek() != Some('{') {
return None;
}
self.advance();
let start = self.fm_pos;
let mut depth = 1;
while let Some(c) = self.advance() {
match c {
'{' => depth += 1,
'}' => {
depth -= 1;
if depth == 0 {
return Some(self.fm[start..self.fm_pos - 1].to_string());
}
}
'\\' => {
self.advance(); }
_ => {}
}
}
None
}
fn path_with_tilde(&self, path: &str) -> String {
if !self.home.is_empty() && path.starts_with(&self.home) {
format!("~{}", &path[self.home.len()..])
} else {
path.to_string()
}
}
fn trailing_path(&self, path: &str, n: usize, with_tilde: bool) -> String {
let path = if with_tilde {
self.path_with_tilde(path)
} else {
path.to_string()
};
if n == 0 {
return path;
}
let components: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
if components.len() <= n {
return path;
}
components[components.len() - n..].join("/")
}
fn leading_path(&self, path: &str, n: usize) -> String {
if n == 0 {
return path.to_string();
}
let components: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
if components.len() <= n {
return path.to_string();
}
let result = components[..n].join("/");
if path.starts_with('/') {
format!("/{}", result)
} else {
result
}
}
fn start_escape(&mut self) {
if !self.in_escape {
self.out_char('\x01'); self.in_escape = true;
}
}
fn end_escape(&mut self) {
if self.in_escape {
self.out_char('\x02'); self.in_escape = false;
}
}
fn apply_attrs(&mut self) {
self.start_escape();
if self.attrs & TXTBOLDFACE != 0 { self.out_str("\x1b[1m");
}
if self.attrs & TXTUNDERLINE != 0 { self.out_str("\x1b[4m");
}
if self.attrs & TXTSTANDOUT != 0 { self.out_str("\x1b[3m");
}
if self.attrs & TXTFGCOLOUR != 0 { let raw = (self.attrs & TXT_ATTR_FG_COL_MASK) >> TXT_ATTR_FG_COL_SHIFT;
let c = if self.attrs & TXT_ATTR_FG_24BIT != 0 {
COLOR_24BIT | (raw as Color & 0x00ff_ffff)
} else { raw as Color };
self.out_str(&color_to_ansi(c, true));
}
if self.attrs & TXTBGCOLOUR != 0 { let raw = (self.attrs & TXT_ATTR_BG_COL_MASK) >> TXT_ATTR_BG_COL_SHIFT;
let c = if self.attrs & TXT_ATTR_BG_24BIT != 0 {
COLOR_24BIT | (raw as Color & 0x00ff_ffff)
} else { raw as Color };
self.out_str(&color_to_ansi(c, false));
}
self.end_escape();
}
fn parse_conditional(&mut self, arg: i32, doprint: i32) -> bool {
if self.peek() != Some('(') {
return false;
}
self.advance();
let cond_char = match self.advance() {
Some(c) => c,
None => return false,
};
let test = match cond_char {
'/' | 'c' | '.' | '~' | 'C' => {
let path = self.path_with_tilde(&self.pwd);
let depth = path.matches('/').count() as i32;
if arg == 0 {
depth > 0
} else {
depth >= arg
}
}
'?' => self.lastval == arg,
'#' => {
let euid = unsafe { libc::geteuid() };
euid == arg as u32
}
'L' => self.shlvl >= arg,
'j' => self.num_jobs >= arg,
'v' => (arg as usize) <= self.psvar.len(),
'V' => {
if arg <= 0 || (arg as usize) > self.psvar.len() {
false
} else {
!self.psvar[arg as usize - 1].is_empty()
}
}
'_' => self.cmd_stack.len() >= arg as usize,
't' | 'T' | 'd' | 'D' | 'w' => {
let now = chrono::Local::now();
match cond_char {
't' => now.format("%M").to_string().parse::<i32>().unwrap_or(0) == arg,
'T' => now.format("%H").to_string().parse::<i32>().unwrap_or(0) == arg,
'd' => now.format("%d").to_string().parse::<i32>().unwrap_or(0) == arg,
'D' => now.format("%m").to_string().parse::<i32>().unwrap_or(0) == arg - 1,
'w' => now.format("%w").to_string().parse::<i32>().unwrap_or(0) == arg,
_ => false,
}
}
'!' => self.is_root,
_ => false,
};
let sep = match self.advance() {
Some(c) => c,
None => return false,
};
let true_start = self.fm_pos;
let mut depth = 1;
while let Some(c) = self.peek() {
if c == '(' {
depth += 1;
} else if c == ')' {
depth -= 1;
if depth == 0 {
break;
}
} else if c == sep && depth == 1 {
break;
}
self.advance();
}
let true_branch = self.fm[true_start..self.fm_pos].to_string();
if self.peek() != Some(sep) {
return false;
}
self.advance();
let false_start = self.fm_pos;
depth = 1;
while let Some(c) = self.peek() {
if c == '(' {
depth += 1;
} else if c == ')' {
depth -= 1;
if depth == 0 {
break;
}
}
self.advance();
}
let false_branch = self.fm[false_start..self.fm_pos].to_string();
if self.peek() != Some(')') {
return false;
}
self.advance();
let mut tsub = self.fork_snapshot(true_branch);
tsub.run_putpromptchar(if test { doprint } else { 0 }, 0);
self.append_buf_from(&tsub);
let mut fsub = self.fork_snapshot(false_branch);
fsub.run_putpromptchar(if test { 0 } else { doprint }, 0);
self.append_buf_from(&fsub);
true
}
fn process_percent(&mut self, doprint: i32) {
let arg = self.parse_number().unwrap_or(0);
if self.peek() == Some('(') {
self.parse_conditional(arg, doprint);
return;
}
if doprint == 0 {
match self.peek() {
Some('[') => {
self.advance();
let _ = self.parse_number();
while self.peek() != Some(']') {
if self.advance().is_none() {
break;
}
}
let _ = self.advance();
return;
}
Some('<') | Some('>') => {
let end = self.peek().unwrap();
self.advance();
while self.peek() != Some(end) {
if self.advance().is_none() {
break;
}
}
let _ = self.advance();
return;
}
Some('D') => {
self.advance();
if self.peek() == Some('{') {
while self.peek() != Some('}') {
if self.advance().is_none() {
break;
}
}
let _ = self.advance();
}
return;
}
_ => {
let _ = self.advance();
return;
}
}
}
let c = match self.advance() {
Some(c) => c,
None => return,
};
match c {
'~' => {
let path = if arg == 0 {
self.path_with_tilde(&self.pwd)
} else if arg > 0 {
self.trailing_path(&self.pwd, arg as usize, true)
} else {
self.leading_path(&self.path_with_tilde(&self.pwd), (-arg) as usize)
};
self.out_str(&path);
}
'd' | '/' => {
let path = if arg == 0 {
self.pwd.clone()
} else if arg > 0 {
self.trailing_path(&self.pwd, arg as usize, false)
} else {
self.leading_path(&self.pwd, (-arg) as usize)
};
self.out_str(&path);
}
'c' | '.' => {
let n = if arg == 0 {
1
} else {
arg.unsigned_abs() as usize
};
let path = self.trailing_path(&self.pwd, n, true);
self.out_str(&path);
}
'C' => {
let n = if arg == 0 {
1
} else {
arg.unsigned_abs() as usize
};
let path = self.trailing_path(&self.pwd, n, false);
self.out_str(&path);
}
'N' => {
let name = self
.scriptname
.clone()
.unwrap_or_else(|| self.argzero.clone());
let n = if arg <= 0 {
0
} else {
arg.unsigned_abs() as usize
};
if n == 0 {
self.out_str(&name);
} else {
let tail = self.trailing_path(&name, n, false);
self.out_str(&tail);
}
}
'n' => {
let u = self.user.clone();
self.out_str(&u);
}
'M' => {
let h = self.host.clone();
self.out_str(&h);
}
'm' => {
let n = if arg == 0 { 1 } else { arg };
if n > 0 {
let parts: Vec<&str> = self.host.split('.').collect();
let take = (n as usize).min(parts.len());
self.out_str(&parts[..take].join("."));
} else {
let parts: Vec<&str> = self.host.split('.').collect();
let skip = ((-n) as usize).min(parts.len());
self.out_str(&parts[skip..].join("."));
}
}
'l' => {
let tty = if self.tty.starts_with("/dev/tty") {
self.tty[8..].to_string()
} else if self.tty.starts_with("/dev/") {
self.tty[5..].to_string()
} else {
"()".to_string()
};
self.out_str(&tty);
}
'y' => {
let tty = if self.tty.is_empty() {
"()".to_string()
} else if self.tty.starts_with("/dev/") {
self.tty[5..].to_string()
} else {
self.tty.clone()
};
self.out_str(&tty);
}
'?' => self.out_str(&self.lastval.to_string()),
'#' => self.out_char(if self.is_root { '#' } else { '%' }),
'h' | '!' => self.out_str(&self.histnum.to_string()),
'j' => self.out_str(&self.num_jobs.to_string()),
'L' => self.out_str(&self.shlvl.to_string()),
'i' => self.out_str(&self.lineno.to_string()),
'I' => {
let n = if let Some(base) = self.func_line_base {
self.lineno.saturating_add(base)
} else {
self.lineno
};
self.out_str(&n.to_string());
}
'x' => {
let n = if arg <= 0 {
0
} else {
arg.unsigned_abs() as usize
};
if self.func_line_base.is_some() {
let path = self
.funcstack_filename
.clone()
.unwrap_or_default();
if n == 0 {
self.out_str(&path);
} else {
let tail = self.trailing_path(&path, n, false);
self.out_str(&tail);
}
} else {
let name = self
.scriptfilename
.clone()
.or_else(|| self.scriptname.clone())
.unwrap_or_else(|| self.argzero.clone());
if n == 0 {
self.out_str(&name);
} else {
let tail = self.trailing_path(&name, n, false);
self.out_str(&tail);
}
}
}
'D' => {
let now = chrono::Local::now();
if let Some(fmt) = self.parse_braced_arg() {
let mut chrono_fmt = String::new();
let mut chars = fmt.chars().peekable();
while let Some(c) = chars.next() {
if c == '%' {
match chars.next() {
Some('a') => chrono_fmt.push_str("%a"),
Some('A') => chrono_fmt.push_str("%A"),
Some('b') | Some('h') => chrono_fmt.push_str("%b"),
Some('B') => chrono_fmt.push_str("%B"),
Some('c') => chrono_fmt.push_str("%c"),
Some('C') => chrono_fmt.push_str("%y"),
Some('d') => chrono_fmt.push_str("%d"),
Some('D') => chrono_fmt.push_str("%m/%d/%y"),
Some('e') => chrono_fmt.push_str("%e"),
Some('f') => chrono_fmt.push_str("%e"),
Some('F') => chrono_fmt.push_str("%Y-%m-%d"),
Some('H') => chrono_fmt.push_str("%H"),
Some('I') => chrono_fmt.push_str("%I"),
Some('j') => chrono_fmt.push_str("%j"),
Some('k') => chrono_fmt.push_str("%k"),
Some('K') => chrono_fmt.push_str("%H"),
Some('l') => chrono_fmt.push_str("%l"),
Some('L') => chrono_fmt.push_str("%3f"),
Some('m') => chrono_fmt.push_str("%m"),
Some('M') => chrono_fmt.push_str("%M"),
Some('n') => chrono_fmt.push('\n'),
Some('N') => chrono_fmt.push_str("%9f"),
Some('p') => chrono_fmt.push_str("%p"),
Some('P') => chrono_fmt.push_str("%P"),
Some('r') => chrono_fmt.push_str("%r"),
Some('R') => chrono_fmt.push_str("%R"),
Some('s') => chrono_fmt.push_str("%s"),
Some('S') => chrono_fmt.push_str("%S"),
Some('t') => chrono_fmt.push('\t'),
Some('T') => chrono_fmt.push_str("%T"),
Some('u') => chrono_fmt.push_str("%u"),
Some('U') => chrono_fmt.push_str("%U"),
Some('V') => chrono_fmt.push_str("%V"),
Some('w') => chrono_fmt.push_str("%w"),
Some('W') => chrono_fmt.push_str("%W"),
Some('x') => chrono_fmt.push_str("%x"),
Some('X') => chrono_fmt.push_str("%X"),
Some('y') => chrono_fmt.push_str("%y"),
Some('Y') => chrono_fmt.push_str("%Y"),
Some('z') => chrono_fmt.push_str("%z"),
Some('Z') => chrono_fmt.push_str("%Z"),
Some('%') => chrono_fmt.push('%'),
Some(other) => {
chrono_fmt.push('%');
chrono_fmt.push(other);
}
None => chrono_fmt.push('%'),
}
} else {
chrono_fmt.push(c);
}
}
self.out_str(&now.format(&chrono_fmt).to_string());
} else {
self.out_str(&now.format("%y-%m-%d").to_string());
}
}
'T' => {
let now = chrono::Local::now();
let formatted = now.format("%k:%M").to_string();
self.out_str(formatted.trim_start());
}
'*' => {
let now = chrono::Local::now();
let formatted = now.format("%k:%M:%S").to_string();
self.out_str(formatted.trim_start());
}
't' | '@' => {
let now = chrono::Local::now();
self.out_str(&now.format("%l:%M%p").to_string());
}
'w' => {
let now = chrono::Local::now();
self.out_str(&now.format("%a %e").to_string());
}
'W' => {
let now = chrono::Local::now();
self.out_str(&now.format("%m/%d/%y").to_string());
}
'B' => {
self.attrs |= TXTBOLDFACE; self.start_escape();
self.out_str("\x1b[1m");
self.end_escape();
}
'b' => {
self.attrs &= !TXTBOLDFACE; self.start_escape();
self.out_str("\x1b[0m");
self.end_escape();
}
'U' => {
self.attrs |= TXTUNDERLINE; self.start_escape();
self.out_str("\x1b[4m");
self.end_escape();
}
'u' => {
self.attrs &= !TXTUNDERLINE; self.start_escape();
self.out_str("\x1b[24m");
self.end_escape();
}
'S' => {
self.attrs |= TXTSTANDOUT; self.start_escape();
self.out_str("\x1b[3m");
self.end_escape();
}
's' => {
self.attrs &= !TXTSTANDOUT; self.start_escape();
self.out_str("\x1b[23m");
self.end_escape();
}
'F' => {
let color: Option<Color> = if let Some(name) = self.parse_braced_arg() {
color_from_name(&name) } else if arg > 0 {
Some(arg as Color) } else {
None
};
if let Some(c) = color {
if let Some((r, g, b)) = color_get_rgb(c) {
self.attrs = zattr_set_fg_rgb(self.attrs, r, g, b); } else {
self.attrs = zattr_set_fg_palette(self.attrs, c as u8); }
self.start_escape();
self.out_str(&color_to_ansi(c, true));
self.end_escape();
}
}
'f' => {
self.attrs &= !TXT_ATTR_FG_MASK; self.start_escape();
self.out_str("\x1b[39m");
self.end_escape();
}
'K' => {
let color: Option<Color> = if let Some(name) = self.parse_braced_arg() {
color_from_name(&name) } else if arg > 0 {
Some(arg as Color) } else {
None
};
if let Some(c) = color {
if let Some((r, g, b)) = color_get_rgb(c) {
self.attrs = zattr_set_bg_rgb(self.attrs, r, g, b); } else {
self.attrs = zattr_set_bg_palette(self.attrs, c as u8); }
self.start_escape();
self.out_str(&color_to_ansi(c, false));
self.end_escape();
}
}
'k' => {
self.attrs &= !TXT_ATTR_BG_MASK; self.start_escape();
self.out_str("\x1b[49m");
self.end_escape();
}
'{' => self.start_escape(),
'}' => self.end_escape(),
'G' => {
let n = if arg > 0 { arg as usize } else { 1 };
for _ in 0..n {
self.out_char(' ');
}
}
'v' => {
let idx = if arg == 0 { 1 } else { arg };
if idx > 0 && (idx as usize) <= self.psvar.len() {
let s = self.psvar[idx as usize - 1].clone();
self.out_str(&s);
}
}
'_' => {
let cmdsp = self.cmd_stack.len();
if cmdsp > 0 {
let names: Vec<&str> = if arg >= 0 {
let mut n = if arg == 0 { cmdsp } else { arg as usize };
if n > cmdsp {
n = cmdsp;
}
self.cmd_stack
.iter()
.skip(cmdsp - n)
.filter_map(|b| CMDNAMES.get(*b as usize).copied())
.collect()
} else {
let mut n = (-arg) as usize;
if n > cmdsp {
n = cmdsp;
}
self.cmd_stack
.iter()
.take(n)
.filter_map(|b| CMDNAMES.get(*b as usize).copied())
.collect()
};
self.out_str(&names.join(" "));
}
}
'E' => {
self.start_escape();
self.out_str("\x1b[K");
self.end_escape();
}
'%' => self.out_char('%'),
')' => self.out_char(')'),
'\0' => {}
_ => {
self.out_char('%');
self.out_char(c);
}
}
}
pub fn expand(mut self) -> String {
self.run_putpromptchar(1, 0);
self.finish_expanded_string(false)
}
}
pub fn expand_prompt(s: &str) -> String {
prompt_tls::sync_from_globals();
buf_vars::new(s).expand() }
pub fn expand_prompt_default(s: &str) -> String {
expand_prompt(s)
}
pub fn prompt_width(s: &str) -> usize {
let mut width = 0;
let mut in_escape = false;
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
match c {
'\x01' => in_escape = true, '\x02' => in_escape = false, '\x1b' => {
while let Some(&next) = chars.peek() {
chars.next();
if next == 'm' {
break;
}
}
}
_ if !in_escape => {
width += unicode_width::UnicodeWidthChar::width(c).unwrap_or(1);
}
_ => {}
}
}
width
}
pub fn match_named_colour(teststrp: &str) -> Option<u8> { let lower = teststrp.to_lowercase(); for (i, &n) in COLOUR_NAMES.iter().enumerate() { if n == lower {
return Some(i as u8); }
}
teststrp.parse::<u8>().ok() }
pub fn output_colour(colour: u8, is_fg: bool) -> String { let base = if is_fg { 30 } else { 40 };
if colour < 8 {
format!("\x1b[{}m", base + colour)
} else if colour < 16 {
format!("\x1b[{};1m", base + colour - 8)
} else {
let mode = if is_fg { 38 } else { 48 };
format!("\x1b[{};5;{}m", mode, colour)
}
}
pub fn output_truecolor(r: u8, g: u8, b: u8, is_fg: bool) -> String {
let mode = if is_fg { 38 } else { 48 };
format!("\x1b[{};2;{};{};{}m", mode, r, g, b)
}
pub fn parsehighlight(spec: &str) -> zattr { let mut attrs: zattr = 0;
for part in spec.split(',') {
let part = part.trim();
match part {
"bold" => attrs |= TXTBOLDFACE, "underline" => attrs |= TXTUNDERLINE, "standout" => attrs |= TXTSTANDOUT, "none" => {
attrs = 0; }
s if s.starts_with("fg=") => {
if let Some(code) = match_named_colour(&s[3..]) { attrs = zattr_set_fg_palette(attrs, code); }
}
s if s.starts_with("bg=") => {
if let Some(code) = match_named_colour(&s[3..]) { attrs = zattr_set_bg_palette(attrs, code); }
}
_ => {}
}
}
attrs
}
pub fn apply_text_attributes(attrs: zattr) -> String { let mut codes: Vec<String> = Vec::new();
if attrs & TXTBOLDFACE != 0 { codes.push("1".to_string()); } if attrs & TXTUNDERLINE != 0 { codes.push("4".to_string()); } if attrs & TXTSTANDOUT != 0 { codes.push("7".to_string()); } if attrs & TXTFGCOLOUR != 0 { let raw = (attrs & TXT_ATTR_FG_COL_MASK) >> TXT_ATTR_FG_COL_SHIFT;
let c = if attrs & TXT_ATTR_FG_24BIT != 0 {
COLOR_24BIT | (raw as Color & 0x00ff_ffff)
} else {
raw as Color
};
codes.push(color_to_ansi(c, true).trim_start_matches("\x1b[")
.trim_end_matches('m').to_string());
}
if attrs & TXTBGCOLOUR != 0 { let raw = (attrs & TXT_ATTR_BG_COL_MASK) >> TXT_ATTR_BG_COL_SHIFT;
let c = if attrs & TXT_ATTR_BG_24BIT != 0 {
COLOR_24BIT | (raw as Color & 0x00ff_ffff)
} else {
raw as Color
};
codes.push(color_to_ansi(c, false).trim_start_matches("\x1b[")
.trim_end_matches('m').to_string());
}
if codes.is_empty() {
String::new()
} else {
format!("\x1b[{}m", codes.join(";"))
}
}
pub fn reset_text_attributes() -> &'static str {
"\x1b[0m"
}
pub fn set_default_colour_sequences() -> (String, String) {
("\x1b[0m".to_string(), "\x1b[0m".to_string())
}
pub fn right_prompt_padding(
left_width: usize,
right_prompt: &str,
term_width: usize,
indent: usize,
) -> Option<String> {
let right_width = prompt_width(right_prompt);
let total = left_width + right_width + indent;
if total >= term_width {
return None; }
let padding = term_width - total;
Some(" ".repeat(padding))
}
pub fn transient_prompt(_original: &str) -> String {
String::new()
}
pub fn promptpath(path: &str, npath: usize, tilde: bool, home: &str) -> String { let display = if tilde && !home.is_empty() && path.starts_with(home) {
let rest = &path[home.len()..];
if rest.is_empty() || rest.starts_with('/') {
format!("~{}", rest)
} else {
path.to_string()
}
} else {
path.to_string()
};
if npath == 0 {
return display;
}
let components: Vec<&str> = display.split('/').filter(|s| !s.is_empty()).collect();
if components.len() <= npath {
return display;
}
components[components.len() - npath..].join("/")
}
pub fn promptexpand( s: &str,
_ns: i32,
_marker: Option<&str>,
) -> (String, Option<usize>, Option<usize>) {
let expanded = expand_prompt(s);
let rs_offset = s.find("%E").or_else(|| s.find("%E)")); let cap_rs_offset = s.find("%>>"); (expanded, rs_offset, cap_rs_offset)
}
pub fn zattrescape(attrs: zattr) -> String { let mut result = String::new();
if attrs & TXTBOLDFACE != 0 { result.push_str("%B"); } if attrs & TXTUNDERLINE != 0 { result.push_str("%U"); } if attrs & TXTSTANDOUT != 0 { result.push_str("%S"); } if attrs & TXTFGCOLOUR != 0 { let raw = (attrs & TXT_ATTR_FG_COL_MASK) >> TXT_ATTR_FG_COL_SHIFT;
let c = if attrs & TXT_ATTR_FG_24BIT != 0 {
COLOR_24BIT | (raw as Color & 0x00ff_ffff)
} else { raw as Color };
result.push_str(&format!("%F{{{}}}", color_name(c)));
}
if attrs & TXTBGCOLOUR != 0 { let raw = (attrs & TXT_ATTR_BG_COL_MASK) >> TXT_ATTR_BG_COL_SHIFT;
let c = if attrs & TXT_ATTR_BG_24BIT != 0 {
COLOR_24BIT | (raw as Color & 0x00ff_ffff)
} else { raw as Color };
result.push_str(&format!("%K{{{}}}", color_name(c)));
}
result
}
fn color_name(c: Color) -> String {
if let Some((r, g, b)) = color_get_rgb(c) {
return format!("#{:02x}{:02x}{:02x}", r, g, b);
}
let idx = (c & 0xff) as usize;
if idx < COLOUR_NAMES.len() {
return COLOUR_NAMES[idx].to_string();
}
idx.to_string()
}
pub fn parsecolorchar(arg: &str, is_fg: bool) -> Option<(Color, String)> { let color = color_from_name(arg)?; let ansi = color_to_ansi(color, is_fg); Some((color, ansi))
}
pub fn pputc(buf: &mut String, c: char) { buf.push(c);
}
pub fn addbufspc(_buf: &mut String, _need: usize) { }
pub fn stradd(buf: &mut String, s: &str) { buf.push_str(s);
}
pub fn tsetcap(cap: &str) -> String { match cap {
"md" | "bold" => "\x1b[1m".to_string(),
"me" | "sgr0" => "\x1b[0m".to_string(),
"so" | "smso" => "\x1b[7m".to_string(),
"se" | "rmso" => "\x1b[27m".to_string(),
"us" | "smul" => "\x1b[4m".to_string(),
"ue" | "rmul" => "\x1b[24m".to_string(),
_ => String::new(),
}
}
pub fn putstr(d: &str) -> String {
tsetcap(d)
}
pub fn treplaceattrs(old: zattr, new: zattr) -> String { let mut result = String::new();
let old_b = old & TXTBOLDFACE != 0;
let new_b = new & TXTBOLDFACE != 0;
let old_u = old & TXTUNDERLINE != 0;
let new_u = new & TXTUNDERLINE != 0;
let old_s = old & TXTSTANDOUT != 0;
let new_s = new & TXTSTANDOUT != 0;
let need_reset = (old_b && !new_b) || (old_u && !new_u) || (old_s && !new_s);
if need_reset {
result.push_str("\x1b[0m");
if new_b { result.push_str("\x1b[1m"); }
if new_u { result.push_str("\x1b[4m"); }
if new_s { result.push_str("\x1b[7m"); }
} else {
if !old_b && new_b { result.push_str("\x1b[1m"); }
if !old_u && new_u { result.push_str("\x1b[4m"); }
if !old_s && new_s { result.push_str("\x1b[7m"); }
}
if (old & TXT_ATTR_FG_MASK) != (new & TXT_ATTR_FG_MASK) {
if new & TXTFGCOLOUR != 0 {
let raw = (new & TXT_ATTR_FG_COL_MASK) >> TXT_ATTR_FG_COL_SHIFT;
let c = if new & TXT_ATTR_FG_24BIT != 0 {
COLOR_24BIT | (raw as Color & 0x00ff_ffff)
} else { raw as Color };
result.push_str(&color_to_ansi(c, true));
} else {
result.push_str("\x1b[39m");
}
}
if (old & TXT_ATTR_BG_MASK) != (new & TXT_ATTR_BG_MASK) {
if new & TXTBGCOLOUR != 0 {
let raw = (new & TXT_ATTR_BG_COL_MASK) >> TXT_ATTR_BG_COL_SHIFT;
let c = if new & TXT_ATTR_BG_24BIT != 0 {
COLOR_24BIT | (raw as Color & 0x00ff_ffff)
} else { raw as Color };
result.push_str(&color_to_ansi(c, false));
} else {
result.push_str("\x1b[49m");
}
}
result
}
pub fn tsetattrs(newattrs: zattr) -> String { apply_text_attributes(newattrs)
}
pub fn tunsetattrs(newattrs: zattr) -> String { let mut result = String::new();
if newattrs & TXTBOLDFACE != 0 { result.push_str("\x1b[22m"); }
if newattrs & TXTUNDERLINE != 0 {
result.push_str("\x1b[24m");
}
if newattrs & TXTSTANDOUT != 0 { result.push_str("\x1b[27m"); }
if newattrs & TXTFGCOLOUR != 0 { result.push_str("\x1b[39m"); }
if newattrs & TXTBGCOLOUR != 0 { result.push_str("\x1b[49m"); }
result
}
pub fn match_colour(spec: &str, is_fg: bool) -> Option<String> {
if let Some(code) = match_named_colour(spec) {
return Some(output_colour(code, is_fg));
}
if spec.starts_with('#') && spec.len() == 7 {
let r = u8::from_str_radix(&spec[1..3], 16).ok()?;
let g = u8::from_str_radix(&spec[3..5], 16).ok()?;
let b = u8::from_str_radix(&spec[5..7], 16).ok()?;
return Some(output_truecolor(r, g, b, is_fg));
}
if let Ok(n) = spec.parse::<u8>() {
return Some(output_colour(n, is_fg));
}
None
}
pub fn match_highlight(spec: &str) -> (zattr, zattr) {
let attrs = parsehighlight(spec);
let mut mask: zattr = 0;
mask |= attrs & (TXTBOLDFACE | TXTUNDERLINE | TXTSTANDOUT); if attrs & TXTFGCOLOUR != 0 { mask |= TXTFGCOLOUR; } if attrs & TXTBGCOLOUR != 0 { mask |= TXTBGCOLOUR; } (attrs, mask)
}
pub fn output_highlight(attrs: zattr) -> String {
apply_text_attributes(attrs)
}
pub fn putpromptchar(bv: &mut buf_vars, doprint: i32, endchar: i32) -> i32 {
bv.run_putpromptchar(doprint, endchar)
}
pub fn mixattrs(primary: zattr, mask: zattr, secondary: zattr) -> zattr {
let mut out: zattr = 0;
for bit in [TXTBOLDFACE, TXTUNDERLINE, TXTSTANDOUT] {
if mask & bit != 0 { out |= primary & bit; } else { out |= secondary & bit; }
}
if mask & TXTFGCOLOUR != 0 { out |= primary & TXT_ATTR_FG_MASK; }
else { out |= secondary & TXT_ATTR_FG_MASK; }
if mask & TXTBGCOLOUR != 0 { out |= primary & TXT_ATTR_BG_MASK; }
else { out |= secondary & TXT_ATTR_BG_MASK; }
out
}
pub fn truecolor_terminal() -> bool {
if let Ok(ct) = std::env::var("COLORTERM") {
if ct == "truecolor" || ct == "24bit" {
return true;
}
}
if let Ok(term) = std::env::var("TERM") {
if term.contains("256color") || term.contains("direct") || term.contains("kitty") {
return true;
}
}
false
}
pub fn set_colour_code(spec: &str) -> Option<String> {
match_colour(spec, true)
}
pub fn allocate_colour_buffer() {
}
pub fn free_colour_buffer() {
}
pub fn set_colour_attribute(color: Color, is_fg: bool) -> String { color_to_ansi(color, is_fg) }
const CMDSTACKSZ: usize = 256;
thread_local! {
static CMDSTACK: std::cell::RefCell<Vec<u8>> = const { std::cell::RefCell::new(Vec::new())
};
}
pub fn cmdpush(cmdtok: u8) {
CMDSTACK.with(|s| {
let mut stack = s.borrow_mut();
if stack.len() < CMDSTACKSZ {
stack.push(cmdtok);
}
});
}
pub fn cmdpop() {
CMDSTACK.with(|s| {
s.borrow_mut().pop();
});
}
#[allow(non_snake_case)]
pub fn map256toRGB(atr: &mut u64, shift: u32, set24: u64) {
if (*atr & set24) != 0 {
return;
}
let colour: u32 = ((*atr >> shift) & 0xff) as u32;
if colour < 16 {
return;
}
let (red, green, blue) = if (16..232).contains(&colour) {
let mut c = colour - 16;
let blue = (if c != 0 { 0x37 } else { 0 }) + 40 * (c % 6);
c /= 6;
let green = (if c != 0 { 0x37 } else { 0 }) + 40 * (c % 6);
c /= 6;
let red = (if c != 0 { 0x37 } else { 0 }) + 40 * c;
(red, green, blue)
} else {
let v = 8 + 10 * (colour - 232);
(v, v, v)
};
*atr &= !((0xffffff_u64) << shift);
*atr |= set24 | ((((red as u64) << 8 | green as u64) << 8 | blue as u64) << shift);
}
fn current_attrs_lock() -> &'static std::sync::Mutex<zattr> {
static CUR: std::sync::OnceLock<std::sync::Mutex<zattr>> = std::sync::OnceLock::new();
CUR.get_or_init(|| std::sync::Mutex::new(0 as zattr))
}
fn pending_attrs_lock() -> &'static std::sync::Mutex<zattr> {
static PND: std::sync::OnceLock<std::sync::Mutex<zattr>> = std::sync::OnceLock::new();
PND.get_or_init(|| std::sync::Mutex::new(0 as zattr))
}
pub fn set_pending_text_attrs(attrs: zattr) {
*pending_attrs_lock()
.lock()
.expect("pending_attrs poisoned") = attrs;
}
#[allow(unused_variables)]
pub fn applytextattributes(flags: i32) -> String {
let mut current = current_attrs_lock().lock().expect("current_attrs poisoned");
let pending = pending_attrs_lock()
.lock()
.expect("pending_attrs poisoned")
.clone();
let diff = treplaceattrs(*current, pending);
*current = pending;
diff
}
#[allow(unused_variables)]
pub fn prompttrunc(arg: i32, truncchar: i32, doprint: i32, endchar: i32) -> i32 {
0
}