use crate::compsys::ported::_message::_message;
use crate::ported::builtin::bin_print;
use crate::ported::exec::dispatch_function_call;
use crate::ported::hashtable_h::BIN_PRINT;
use crate::ported::modules::zutil::lookupstyle;
use crate::ported::params::{getaparam, getiparam, getsparam, setiparam};
use crate::ported::zle::compcore::{get_compstate_str, set_compstate_str};
use crate::ported::zsh_h::{options, MAX_OPS};
use std::os::unix::io::AsRawFd;
fn make_ops() -> options {
options {
ind: [0u8; MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
}
}
pub fn _complete_debug(args: &[String]) -> i32 {
let n = getiparam("_debug_count") + 1;
setiparam("_debug_count", n);
let tmpprefix = getsparam("TMPPREFIX").unwrap_or_else(|| "/tmp/zsh".to_string());
let pid = std::process::id();
let words = getaparam("words").unwrap_or_default();
let cmd_name = words.first().cloned().unwrap_or_default();
let tmp = format!("{}{}{}{}", tmpprefix, pid, basename(&cmd_name), n);
let w = words
.iter()
.map(|s| single_quote(s))
.collect::<Vec<_>>()
.join(" ");
let mut debug_fd: i32 = -1;
let mut _capture: Option<std::fs::File> = None;
if unsafe { libc::isatty(2) } == 1 {
if let Ok(f) = std::fs::File::create(&tmp) {
let saved = unsafe { libc::dup(2) };
if saved >= 0 {
if unsafe { libc::dup2(f.as_raw_fd(), 2) } >= 0 {
debug_fd = saved;
_capture = Some(f);
} else {
unsafe {
libc::close(saved);
}
}
}
}
}
let target = args
.first()
.cloned()
.filter(|s| !s.is_empty())
.unwrap_or_else(|| "_main_complete".to_string());
let prev_xtrace = crate::ported::zsh_h::isset(crate::ported::zsh_h::XTRACE);
if debug_fd != -1 {
crate::ported::options::setoption("xtrace", 1);
}
let ret = dispatch_function_call(&target, &[]).unwrap_or(1);
if debug_fd != -1 {
crate::ported::options::setoption("xtrace", prev_xtrace as i32);
}
if debug_fd != -1 {
let style_pager = lookupstyle(":completion:complete-debug::::", "pager")
.into_iter()
.next()
.unwrap_or_default();
let pager = first_nonempty(&[
style_pager,
getsparam("PAGER").unwrap_or_default(),
getsparam("VISUAL").unwrap_or_default(),
getsparam("EDITOR").unwrap_or_default(),
"more".to_string(),
]);
let cmd = format!("{} {} ;: {}", pager, single_quote(&tmp), w);
let mut ops = make_ops();
ops.ind[b's' as usize] = 1;
ops.ind[b'R' as usize] = 1;
let _ = bin_print("print", &[cmd], &ops, BIN_PRINT);
let _ = _message(&[
"-r".to_string(),
format!("Trace output left in {} (up-history to view)", tmp),
]);
let nmatches: i64 = get_compstate_str("nmatches")
.and_then(|s| s.parse().ok())
.unwrap_or(0);
let list = get_compstate_str("list").unwrap_or_default();
if nmatches <= 1 && !list.contains("force") {
set_compstate_str("list", "list force messages");
}
}
if debug_fd != -1 {
unsafe {
libc::dup2(debug_fd, 2);
libc::close(debug_fd);
}
}
drop(_capture);
ret
}
fn basename(s: &str) -> String {
s.rsplit('/').next().unwrap_or("").to_string()
}
fn first_nonempty(xs: &[String]) -> String {
xs.iter().find(|s| !s.is_empty()).cloned().unwrap_or_default()
}
fn single_quote(s: &str) -> String {
format!("'{}'", s.replace('\'', "'\\''"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn returns_one_without_executor() {
let _g = crate::test_util::global_state_lock();
assert_eq!(_complete_debug(&[]), 1);
}
#[test]
fn increments_debug_count() {
let _g = crate::test_util::global_state_lock();
setiparam("_debug_count", 5);
let _ = _complete_debug(&[]);
assert_eq!(getiparam("_debug_count"), 6);
}
#[test]
fn no_trace_file_when_stderr_not_tty() {
let _g = crate::test_util::global_state_lock();
setiparam("_debug_count", 0);
let tmpprefix = getsparam("TMPPREFIX").unwrap_or_else(|| "/tmp/zsh".to_string());
let pid = std::process::id();
let tmp = format!("{}{}{}{}", tmpprefix, pid, "", 1);
let _ = std::fs::remove_file(&tmp);
let _ = _complete_debug(&[]);
if unsafe { libc::isatty(2) } == 0 {
assert!(
!std::path::Path::new(&tmp).exists(),
"no trace file must be created when stderr is not a tty"
);
}
}
}