diff --git a/src/builtin/alias.rs b/src/builtin/alias.rs index 149c839..412726c 100644 --- a/src/builtin/alias.rs +++ b/src/builtin/alias.rs @@ -85,7 +85,6 @@ pub fn unalias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResul write(stdout, alias_output.as_bytes())?; // Write it } else { for (arg, span) in argv { - log::debug!("{arg:?}"); if read_logic(|l| l.get_alias(&arg)).is_none() { return Err(ShErr::full( ShErrKind::SyntaxErr, diff --git a/src/builtin/flowctl.rs b/src/builtin/flowctl.rs index 87ec23a..236ff33 100644 --- a/src/builtin/flowctl.rs +++ b/src/builtin/flowctl.rs @@ -32,7 +32,6 @@ pub fn flowctl(node: Node, kind: ShErrKind) -> ShResult<()> { code = status; } - log::debug!("{code:?}"); let kind = match kind { LoopContinue(_) => LoopContinue(code), diff --git a/src/builtin/test.rs b/src/builtin/test.rs index eb5bbc9..551b4fa 100644 --- a/src/builtin/test.rs +++ b/src/builtin/test.rs @@ -247,9 +247,6 @@ pub fn double_bracket_test(node: Node) -> ShResult { let rhs = rhs.expand()?.get_words().join(" "); conjunct_op = conjunct; let test_op = operator.as_str().parse::()?; - log::debug!("{lhs:?}"); - log::debug!("{rhs:?}"); - log::debug!("{test_op:?}"); match test_op { TestOp::Unary(_) => { return Err(ShErr::Full { @@ -298,7 +295,6 @@ pub fn double_bracket_test(node: Node) -> ShResult { } } }; - log::debug!("{last_result:?}"); if let Some(op) = conjunct_op { match op { @@ -316,6 +312,5 @@ pub fn double_bracket_test(node: Node) -> ShResult { last_result = result; } } - log::debug!("{last_result:?}"); Ok(last_result) } diff --git a/src/expand.rs b/src/expand.rs index dd4c2b1..90ef098 100644 --- a/src/expand.rs +++ b/src/expand.rs @@ -59,7 +59,7 @@ pub struct Expander { impl Expander { pub fn new(raw: Tk) -> ShResult { let raw = raw.span.as_str(); - Self::from_raw(&raw) + Self::from_raw(raw) } pub fn from_raw(raw: &str) -> ShResult { let raw = expand_braces_full(raw)?.join(" "); @@ -69,10 +69,24 @@ impl Expander { pub fn expand(&mut self) -> ShResult> { let mut chars = self.raw.chars().peekable(); self.raw = expand_raw(&mut chars)?; + + let has_trailing_slash = self.raw.ends_with('/'); + let has_leading_dot_slash = self.raw.starts_with("./"); + if let Ok(glob_exp) = expand_glob(&self.raw) - && !glob_exp.is_empty() { - self.raw = glob_exp; - } + && !glob_exp.is_empty() { + self.raw = glob_exp; + } + + if has_trailing_slash && !self.raw.ends_with('/') { + // glob expansion can remove trailing slashes and leading dot-slashes, but we want to preserve them + // so that things like tab completion don't break + self.raw.push('/'); + } + if has_leading_dot_slash && !self.raw.starts_with("./") { + self.raw.insert_str(0, "./"); + } + Ok(self.split_words()) } pub fn split_words(&mut self) -> Vec { @@ -462,14 +476,12 @@ pub fn expand_raw(chars: &mut Peekable>) -> ShResult { result.push_str(&fd_path); } VAR_SUB => { - log::info!("{chars:?}"); let expanded = expand_var(chars)?; result.push_str(&expanded); } _ => result.push(ch), } } - log::debug!("expand_raw result: {result:?}"); Ok(result) } @@ -481,12 +493,19 @@ pub fn expand_var(chars: &mut Peekable>) -> ShResult { SUBSH if var_name.is_empty() => { chars.next(); // now safe to consume let mut subsh_body = String::new(); + let mut found_end = false; while let Some(c) = chars.next() { if c == SUBSH { + found_end = true; break; } subsh_body.push(c); } + if !found_end { + // if there isnt a closing SUBSH, we are probably in some tab completion context + // and we got passed some unfinished input. Just treat it as literal text + return Ok(format!("$({subsh_body}")); + } let expanded = expand_cmd_sub(&subsh_body)?; return Ok(expanded); } @@ -512,14 +531,10 @@ pub fn expand_var(chars: &mut Peekable>) -> ShResult { return Ok(NULL_EXPAND.to_string()); } - log::debug!("{val:?}"); return Ok(val); } ch if is_hard_sep(ch) || !(ch.is_alphanumeric() || ch == '_' || ch == '-') => { let val = read_vars(|v| v.get_var(&var_name)); - log::info!("{var_name:?}"); - log::info!("{val:?}"); - log::info!("{ch:?}"); return Ok(val); } _ => { @@ -530,7 +545,6 @@ pub fn expand_var(chars: &mut Peekable>) -> ShResult { } if !var_name.is_empty() { let var_val = read_vars(|v| v.get_var(&var_name)); - log::info!("{var_val:?}"); Ok(var_val) } else { Ok(String::new()) @@ -781,7 +795,6 @@ pub fn expand_proc_sub(raw: &str, is_input: bool) -> ShResult { ForkResult::Parent { child } => { write_jobs(|j| j.register_fd(child, register_fd)); let registered = read_jobs(|j| j.registered_fds().to_vec()); - log::debug!("{registered:?}"); // Do not wait; process may run in background Ok(path) } @@ -790,8 +803,6 @@ pub fn expand_proc_sub(raw: &str, is_input: bool) -> ShResult { /// Get the command output of a given command input as a String pub fn expand_cmd_sub(raw: &str) -> ShResult { - log::debug!("in expand_cmd_sub"); - log::debug!("{raw:?}"); if raw.starts_with('(') && raw.ends_with(')') && let Ok(output) = expand_arithmetic(raw) { return Ok(output); // It's actually an arithmetic sub @@ -815,7 +826,6 @@ pub fn expand_cmd_sub(raw: &str) -> ShResult { std::mem::drop(cmd_sub_io_frame); // Closes the write pipe // Read output first (before waiting) to avoid deadlock if child fills pipe buffer - log::debug!("filling buffer"); loop { match io_buf.fill_buffer() { Ok(()) => break, @@ -823,7 +833,6 @@ pub fn expand_cmd_sub(raw: &str) -> ShResult { Err(e) => return Err(e.into()), } } - log::debug!("done"); // Wait for child with EINTR retry let status = loop { @@ -1105,7 +1114,6 @@ pub fn unescape_math(raw: &str) -> String { let mut result = String::new(); while let Some(ch) = chars.next() { - log::debug!("{result:?}"); match ch { '\\' => { if let Some(next_ch) = chars.next() { @@ -1148,7 +1156,6 @@ pub fn unescape_math(raw: &str) -> String { _ => result.push(ch), } } - log::info!("{result:?}"); result } @@ -1302,9 +1309,7 @@ pub fn perform_param_expansion(raw: &str) -> ShResult { } } - log::debug!("{rest:?}"); if let Ok(expansion) = rest.parse::() { - log::debug!("{expansion:?}"); match expansion { ParamExp::Len => unreachable!(), ParamExp::DefaultUnsetOrNull(default) => { @@ -1523,7 +1528,6 @@ fn glob_to_regex(glob: &str, anchored: bool) -> Regex { if anchored { regex.push('$'); } - log::debug!("{regex:?}"); Regex::new(®ex).unwrap() } @@ -1946,7 +1950,6 @@ pub fn expand_prompt(raw: &str) -> ShResult { PromptTk::FailureSymbol => todo!(), PromptTk::JobCount => todo!(), PromptTk::Function(f) => { - log::debug!("Expanding prompt function: {}", f); let output = expand_cmd_sub(&f)?; result.push_str(&output); } diff --git a/src/getopt.rs b/src/getopt.rs index 51725ec..07d9db5 100644 --- a/src/getopt.rs +++ b/src/getopt.rs @@ -106,7 +106,6 @@ pub fn get_opts_from_tokens(tokens: Vec, opt_specs: &[OptSpec]) -> (Vec, } if !pushed { non_opts.push(token.clone()); - log::warn!("Unexpected flag '{opt}'"); } } } diff --git a/src/jobs.rs b/src/jobs.rs index 04db2ba..7db2da8 100644 --- a/src/jobs.rs +++ b/src/jobs.rs @@ -84,7 +84,6 @@ impl ChildProc { if let Some(pgid) = pgid { child.set_pgid(pgid).ok(); } - log::trace!("new child: {:?}", child); Ok(child) } pub fn pid(&self) -> Pid { @@ -520,11 +519,7 @@ impl Job { } pub fn wait_pgrp(&mut self) -> ShResult> { let mut stats = vec![]; - log::trace!("waiting on children"); - log::trace!("{:?}", self.children); for child in self.children.iter_mut() { - log::trace!("shell pid {}", Pid::this()); - log::trace!("child pid {}", child.pid); if child.pid == Pid::this() { // TODO: figure out some way to get the exit code of builtins let code = state::get_status(); @@ -667,7 +662,6 @@ pub fn wait_fg(job: Job) -> ShResult<()> { if job.children().is_empty() { return Ok(()); // Nothing to do } - log::trace!("Waiting on foreground job"); let mut code = 0; let mut was_stopped = false; attach_tty(job.pgid())?; @@ -699,7 +693,6 @@ pub fn wait_fg(job: Job) -> ShResult<()> { } take_term()?; set_status(code); - log::trace!("exit code: {}", code); enable_reaping(); Ok(()) } @@ -719,7 +712,6 @@ pub fn attach_tty(pgid: Pid) -> ShResult<()> { if !isatty(0).unwrap_or(false) || pgid == term_ctlr() || killpg(pgid, None).is_err() { return Ok(()); } - log::trace!("Attaching tty to pgid: {}", pgid); if pgid == getpgrp() && term_ctlr() != getpgrp() { kill(term_ctlr(), Signal::SIGTTOU).ok(); @@ -745,7 +737,6 @@ pub fn attach_tty(pgid: Pid) -> ShResult<()> { match result { Ok(_) => Ok(()), Err(e) => { - log::error!("error while switching term control: {}", e); tcsetpgrp(borrow_fd(0), getpgrp())?; Ok(()) } diff --git a/src/libsh/utils.rs b/src/libsh/utils.rs index 6d3441f..5e602ab 100644 --- a/src/libsh/utils.rs +++ b/src/libsh/utils.rs @@ -84,7 +84,6 @@ impl TkVecUtils for Vec { } fn debug_tokens(&self) { for token in self { - log::debug!("token: {}", token) } } } diff --git a/src/main.rs b/src/main.rs index 51f0775..bc82e3e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -208,8 +208,6 @@ fn fern_interactive() -> ShResult<()> { // Reset for next command with fresh prompt readline.reset(get_prompt().ok()); let real_end = start.elapsed(); - log::info!("Command execution time: {:.2?}", command_run_time); - log::info!("Total round trip time: {:.2?}", real_end); } Ok(ReadlineEvent::Eof) => { // Ctrl+D on empty line diff --git a/src/parse/execute.rs b/src/parse/execute.rs index cb97448..2ef0f17 100644 --- a/src/parse/execute.rs +++ b/src/parse/execute.rs @@ -138,7 +138,6 @@ impl Dispatcher { } } pub fn begin_dispatch(&mut self) -> ShResult<()> { - log::trace!("beginning dispatch"); while let Some(node) = self.nodes.pop_front() { let blame = node.get_span(); self.dispatch_node(node).try_blame(blame)?; @@ -401,10 +400,23 @@ impl Dispatcher { Ok(()) } fn exec_for(&mut self, for_stmt: Node) -> ShResult<()> { - let NdRule::ForNode { vars, arr, body } = for_stmt.class else { unreachable!(); }; + + let to_expanded_strings = |tks: Vec| -> ShResult> { + Ok(tks.into_iter() + .map(|tk| tk.expand().map(|tk| tk.get_words())) + .collect::>>>()? + .into_iter() + .flatten() + .collect::>()) + }; + + // Expand all array variables + let arr: Vec = to_expanded_strings(arr)?; + let vars: Vec = to_expanded_strings(vars)?; + let mut for_guard = VarCtxGuard::new( vars.iter().map(|v| v.to_string()).collect() ); @@ -415,7 +427,7 @@ impl Dispatcher { .redirect()?; 'outer: for chunk in arr.chunks(vars.len()) { - let empty = Tk::default(); + let empty = String::new(); let chunk_iter = vars.iter().zip( chunk.iter().chain(std::iter::repeat(&empty)), ); @@ -540,7 +552,6 @@ impl Dispatcher { return self.dispatch_cmd(cmd); } - log::trace!("doing builtin"); let result = match cmd_raw.span.as_str() { "echo" => echo(cmd, io_stack_mut, curr_job_mut), "cd" => cd(cmd, curr_job_mut), diff --git a/src/parse/lex.rs b/src/parse/lex.rs index c2db38b..f7d2492 100644 --- a/src/parse/lex.rs +++ b/src/parse/lex.rs @@ -47,6 +47,11 @@ impl Span { pub fn range(&self) -> Range { self.range.clone() } + /// With great power comes great responsibility + /// Only use this in the most dire of circumstances + pub fn set_range(&mut self, range: Range) { + self.range = range; + } } /// Allows simple access to the underlying range wrapped by the span @@ -176,7 +181,6 @@ bitflags! { impl LexStream { pub fn new(source: Arc, flags: LexFlags) -> Self { - log::trace!("new lex stream"); let flags = flags | LexFlags::FRESH | LexFlags::NEXT_IS_CMD; Self { source, @@ -260,7 +264,7 @@ impl LexStream { pos += 1; } - if !found_fd { + if !found_fd && !self.flags.contains(LexFlags::LEX_UNFINISHED) { return Some(Err(ShErr::full( ShErrKind::ParseErr, "Invalid redirection", @@ -790,7 +794,6 @@ impl Iterator for LexStream { match self.read_string() { Ok(tk) => tk, Err(e) => { - log::error!("{e:?}"); return Some(Err(e)); } } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index aed0173..4fada91 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -860,6 +860,10 @@ impl ParseStream { let redir_bldr = redir_bldr.with_io_mode(io_mode); let redir = redir_bldr.build(); redirs.push(redir); + } else { + // io_mode is already set (e.g., for fd redirections like 2>&1) + let redir = redir_bldr.build(); + redirs.push(redir); } } Ok(()) @@ -1346,6 +1350,10 @@ impl ParseStream { let redir_bldr = redir_bldr.with_io_mode(io_mode); let redir = redir_bldr.build(); redirs.push(redir); + } else { + // io_mode is already set (e.g., for fd redirections like 2>&1) + let redir = redir_bldr.build(); + redirs.push(redir); } } _ => unimplemented!("Unexpected token rule `{:?}` in parse_cmd()", tk.class), diff --git a/src/procio.rs b/src/procio.rs index 24119c6..6d2e0c7 100644 --- a/src/procio.rs +++ b/src/procio.rs @@ -137,9 +137,7 @@ impl IoBuf { pub fn fill_buffer(&mut self) -> io::Result<()> { let mut temp_buf = vec![0; 1024]; // Read in chunks loop { - log::debug!("reading bytes"); let bytes_read = self.reader.read(&mut temp_buf)?; - log::debug!("{bytes_read:?}"); if bytes_read == 0 { break; // EOF reached } @@ -220,11 +218,9 @@ impl<'e> IoFrame { self.save(); for redir in &mut self.redirs { let io_mode = &mut redir.io_mode; - log::debug!("{io_mode:?}"); if let IoMode::File { .. } = io_mode { *io_mode = io_mode.clone().open_file()?; }; - log::debug!("{io_mode:?}"); let tgt_fd = io_mode.tgt_fd(); let src_fd = io_mode.src_fd(); dup2(src_fd, tgt_fd)?; diff --git a/src/prompt/mod.rs b/src/prompt/mod.rs index cac53b7..559b0a0 100644 --- a/src/prompt/mod.rs +++ b/src/prompt/mod.rs @@ -17,7 +17,6 @@ pub fn get_prompt() -> ShResult { return expand_prompt(default); }; let sanitized = format!("\\e[0m{prompt}"); - log::debug!("Using prompt: {}", sanitized.replace("\n", "\\n")); expand_prompt(&sanitized) } diff --git a/src/prompt/readline/complete.rs b/src/prompt/readline/complete.rs new file mode 100644 index 0000000..7b9c40e --- /dev/null +++ b/src/prompt/readline/complete.rs @@ -0,0 +1,318 @@ +use std::{env, os::unix::fs::PermissionsExt, path::PathBuf, sync::Arc}; + +use crate::{builtin::BUILTINS, libsh::error::ShResult, parse::lex::{self, LexFlags, Tk, TkFlags}, prompt::readline::{annotate_input, annotate_input_recursive, get_insertions, markers::{self, is_marker}}, state::read_logic}; + +pub enum CompCtx { + CmdName, + FileName +} + +pub enum CompResult { + NoMatch, + Single { + result: String + }, + Many { + candidates: Vec + } +} + +impl CompResult { + pub fn from_candidates(candidates: Vec) -> Self { + if candidates.is_empty() { + Self::NoMatch + } else if candidates.len() == 1 { + Self::Single { result: candidates[0].clone() } + } else { + Self::Many { candidates } + } + } +} + +pub struct Completer { + pub candidates: Vec, + pub selected_idx: usize, + pub original_input: String, + pub token_span: (usize, usize), + pub active: bool, +} + +impl Completer { + pub fn new() -> Self { + Self { + candidates: vec![], + selected_idx: 0, + original_input: String::new(), + token_span: (0, 0), + active: false, + } + } + + pub fn slice_line(line: &str, cursor_pos: usize) -> (&str, &str) { + let (before_cursor, after_cursor) = line.split_at(cursor_pos); + (before_cursor, after_cursor) + } + + fn get_completion_context(&self, line: &str, cursor_pos: usize) -> (bool, usize) { + let annotated = annotate_input_recursive(line); + log::debug!("Annotated input for completion context: {:?}", annotated); + let mut in_cmd = false; + let mut same_position = false; // so that arg markers do not overwrite command markers if they are in the same spot + let mut ctx_start = 0; + let mut pos = 0; + + for ch in annotated.chars() { + match ch { + _ if is_marker(ch) => { + match ch { + markers::COMMAND | markers::BUILTIN => { + log::debug!("Found command marker at position {}", pos); + ctx_start = pos; + same_position = true; + in_cmd = true; + } + markers::ARG => { + log::debug!("Found argument marker at position {}", pos); + if !same_position { + ctx_start = pos; + in_cmd = false; + } + } + _ => {} + } + } + _ => { + same_position = false; + pos += 1; // we hit a normal character, advance our position + if pos >= cursor_pos { + log::debug!("Cursor is at position {}, current context: {}", pos, if in_cmd { "command" } else { "argument" }); + break; + } + } + } + } + + (in_cmd, ctx_start) + } + + pub fn reset(&mut self) { + self.candidates.clear(); + self.selected_idx = 0; + self.original_input.clear(); + self.token_span = (0, 0); + self.active = false; + } + + pub fn complete(&mut self, line: String, cursor_pos: usize, direction: i32) -> ShResult> { + if self.active { + Ok(Some(self.cycle_completion(direction))) + } else { + self.start_completion(line, cursor_pos) + } + } + + pub fn selected_candidate(&self) -> Option { + self.candidates.get(self.selected_idx).cloned() + } + + pub fn cycle_completion(&mut self, direction: i32) -> String { + if self.candidates.is_empty() { + return self.original_input.clone(); + } + + let len = self.candidates.len(); + self.selected_idx = (self.selected_idx as i32 + direction).rem_euclid(len as i32) as usize; + + self.get_completed_line() + } + + pub fn start_completion(&mut self, line: String, cursor_pos: usize) -> ShResult> { + let result = self.get_candidates(line.clone(), cursor_pos)?; + match result { + CompResult::Many { candidates } => { + self.candidates = candidates.clone(); + self.selected_idx = 0; + self.original_input = line; + self.active = true; + + Ok(Some(self.get_completed_line())) + } + CompResult::Single { result } => { + self.candidates = vec![result.clone()]; + self.selected_idx = 0; + self.original_input = line; + self.active = false; + + Ok(Some(self.get_completed_line())) + } + CompResult::NoMatch => Ok(None) + + } + } + + pub fn get_completed_line(&self) -> String { + if self.candidates.is_empty() { + return self.original_input.clone(); + } + + let selected = &self.candidates[self.selected_idx]; + let (start, end) = self.token_span; + format!("{}{}{}", &self.original_input[..start], selected, &self.original_input[end..]) + } + + pub fn get_candidates(&mut self, line: String, cursor_pos: usize) -> ShResult { + let source = Arc::new(line.clone()); + let tokens = lex::LexStream::new(source, LexFlags::LEX_UNFINISHED).collect::>>()?; + + let Some(mut cur_token) = tokens.into_iter().find(|tk| { + let start = tk.span.start; + let end = tk.span.end; + (start..=end).contains(&cursor_pos) + }) else { + log::debug!("No token found at cursor position"); + let candidates = Self::complete_filename("./"); // Default to filename completion if no token is found + let end_pos = line.len(); + self.token_span = (end_pos, end_pos); + return Ok(CompResult::from_candidates(candidates)); + }; + + self.token_span = (cur_token.span.start, cur_token.span.end); + + + // Look for marker at the START of what we're completing, not at cursor + let (is_cmd, token_start) = self.get_completion_context(&line, cursor_pos); + self.token_span.0 = token_start; // Update start of token span based on context + log::debug!("Completion context: {}, token span: {:?}, token_start: {}", if is_cmd { "command" } else { "argument" }, self.token_span, token_start); + cur_token.span.set_range(self.token_span.0..self.token_span.1); // Update token span to reflect context + + // If token contains '=', only complete after the '=' + let token_str = cur_token.span.as_str(); + if let Some(eq_pos) = token_str.rfind('=') { + // Adjust span to only replace the part after '=' + self.token_span.0 = cur_token.span.start + eq_pos + 1; + } + + let expanded_tk = cur_token.expand()?; + let expanded_words = expanded_tk.get_words().into_iter().collect::>(); + let expanded = expanded_words.join("\\ "); + + let candidates = if is_cmd { + log::debug!("Completing command: {}", &expanded); + Self::complete_command(&expanded)? + } else { + log::debug!("Completing filename: {}", &expanded); + Self::complete_filename(&expanded) + }; + + Ok(CompResult::from_candidates(candidates)) + } + + fn complete_command(start: &str) -> ShResult> { + let mut candidates = vec![]; + + let path = env::var("PATH").unwrap_or_default(); + let paths = path.split(':').map(PathBuf::from).collect::>(); + for path in paths { + // Skip directories that don't exist (common in PATH) + let Ok(entries) = std::fs::read_dir(path) else { continue; }; + for entry in entries { + let Ok(entry) = entry else { continue; }; + let Ok(meta) = entry.metadata() else { continue; }; + + let file_name = entry.file_name().to_string_lossy().to_string(); + + if meta.is_file() + && (meta.permissions().mode() & 0o111) != 0 + && file_name.starts_with(start) { + candidates.push(file_name); + } + } + } + + let builtin_candidates = BUILTINS + .iter() + .filter(|b| b.starts_with(start)) + .map(|s| s.to_string()); + + candidates.extend(builtin_candidates); + + read_logic(|l| { + let func_table = l.funcs(); + let matches = func_table + .keys() + .filter(|k| k.starts_with(start)) + .map(|k| k.to_string()); + + candidates.extend(matches); + + let aliases = l.aliases(); + let matches = aliases + .keys() + .filter(|k| k.starts_with(start)) + .map(|k| k.to_string()); + + candidates.extend(matches); + }); + + // Deduplicate (same command may appear in multiple PATH dirs) + candidates.sort(); + candidates.dedup(); + + Ok(candidates) + } + + fn complete_filename(start: &str) -> Vec { + let mut candidates = vec![]; + + // If completing after '=', only use the part after it + let start = if let Some(eq_pos) = start.rfind('=') { + &start[eq_pos + 1..] + } else { + start + }; + + // Split path into directory and filename parts + // Use "." if start is empty (e.g., after "foo=") + let path = PathBuf::from(if start.is_empty() { "." } else { start }); + let (dir, prefix) = if start.ends_with('/') || start.is_empty() { + // Completing inside a directory: "src/" → dir="src/", prefix="" + (path, "") + } else if let Some(parent) = path.parent() + && !parent.as_os_str().is_empty() { + // Has directory component: "src/ma" → dir="src", prefix="ma" + (parent.to_path_buf(), path.file_name().unwrap().to_str().unwrap_or("")) + } else { + // No directory: "fil" → dir=".", prefix="fil" + (PathBuf::from("."), start) + }; + + let Ok(entries) = std::fs::read_dir(&dir) else { + return candidates; + }; + + for entry in entries.flatten() { + let file_name = entry.file_name(); + let file_str = file_name.to_string_lossy(); + + // Skip hidden files unless explicitly requested + if !prefix.starts_with('.') && file_str.starts_with('.') { + continue; + } + + if file_str.starts_with(prefix) { + // Reconstruct full path + let mut full_path = dir.join(&file_name); + + // Add trailing slash for directories + if entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false) { + full_path.push(""); // adds trailing / + } + + candidates.push(full_path.to_string_lossy().to_string()); + } + } + + candidates.sort(); + candidates + } +} diff --git a/src/prompt/readline/highlight.rs b/src/prompt/readline/highlight.rs index 7d57835..2d2c32a 100644 --- a/src/prompt/readline/highlight.rs +++ b/src/prompt/readline/highlight.rs @@ -173,7 +173,6 @@ impl Highlighter { input_chars.next(); // consume the end marker break; } else if markers::is_marker(*ch) { - log::warn!("Unhandled marker character in variable substitution: U+{:04X}", *ch as u32); input_chars.next(); // skip the marker continue; } @@ -187,7 +186,6 @@ impl Highlighter { } _ => { if markers::is_marker(ch) { - log::warn!("Unhandled marker character in highlighter: U+{:04X}", ch as u32); } else { self.output.push(ch); self.last_was_reset = false; @@ -202,7 +200,6 @@ impl Highlighter { /// Clears the input buffer, style stack, and returns the generated output /// containing ANSI escape codes. The highlighter is ready for reuse after this. pub fn take(&mut self) -> String { - log::info!("Highlighting result: {:?}", self.output); self.input.clear(); self.clear_styles(); std::mem::take(&mut self.output) diff --git a/src/prompt/readline/history.rs b/src/prompt/readline/history.rs index 36cfe9f..853aefa 100644 --- a/src/prompt/readline/history.rs +++ b/src/prompt/readline/history.rs @@ -303,7 +303,6 @@ impl History { } pub fn constrain_entries(&mut self, constraint: SearchConstraint) { - log::debug!("{constraint:?}"); let SearchConstraint { kind, term } = constraint; match kind { SearchKind::Prefix => { @@ -318,7 +317,6 @@ impl History { .collect(); self.search_mask = dedupe_entries(&filtered); - log::debug!("search mask len: {}", self.search_mask.len()); } self.cursor = self.search_mask.len().saturating_sub(1); } @@ -328,12 +326,10 @@ impl History { pub fn hint_entry(&self) -> Option<&HistEntry> { let second_to_last = self.search_mask.len().checked_sub(2)?; - log::info!("search mask: {:?}", self.search_mask.iter().map(|e| e.command()).collect::>()); self.search_mask.get(second_to_last) } pub fn get_hint(&self) -> Option { - log::info!("checking cursor entry: {:?}", self.cursor_entry()); if self .cursor_entry() .is_some_and(|ent| ent.is_new() && !ent.command().is_empty()) diff --git a/src/prompt/readline/linebuf.rs b/src/prompt/readline/linebuf.rs index 8a22693..8a73df9 100644 --- a/src/prompt/readline/linebuf.rs +++ b/src/prompt/readline/linebuf.rs @@ -368,7 +368,6 @@ impl LineBuf { } else { self.hint = None } - log::debug!("{:?}", self.hint) } pub fn accept_hint(&mut self) { let Some(hint) = self.hint.take() else { return }; @@ -406,7 +405,6 @@ impl LineBuf { #[track_caller] pub fn update_graphemes(&mut self) { let indices: Vec<_> = self.buffer.grapheme_indices(true).map(|(i, _)| i).collect(); - log::debug!("{:?}", std::panic::Location::caller()); self.cursor.set_max(indices.len()); self.grapheme_indices = Some(indices) } @@ -577,7 +575,6 @@ impl LineBuf { let end = self.grapheme_indices()[end]; self.buffer.drain(start..end).collect() }; - log::debug!("{drained:?}"); self.update_graphemes(); drained } @@ -1073,7 +1070,6 @@ impl LineBuf { let Some(gr) = self.grapheme_at(idx) else { break; }; - log::debug!("{gr:?}"); if is_whitespace(gr) { end += 1; } else { @@ -1203,7 +1199,6 @@ impl LineBuf { let Some(gr) = self.grapheme_at(idx) else { break; }; - log::debug!("{gr:?}"); if is_whitespace(gr) { end += 1; } else { @@ -1901,10 +1896,7 @@ impl LineBuf { let Some(line) = self.slice(start..end).map(|s| s.to_string()) else { return MotionKind::Null; }; - log::debug!("{target_col:?}"); - log::debug!("{target_col:?}"); let mut target_pos = self.grapheme_index_for_display_col(&line, target_col); - log::debug!("{target_pos:?}"); if self.cursor.exclusive && line.ends_with("\n") && self.grapheme_at(target_pos) == Some("\n") @@ -2107,7 +2099,6 @@ impl LineBuf { Motion::BackwardChar => target.sub(1), Motion::ForwardChar => { if self.cursor.exclusive && self.grapheme_at(target.ret_add(1)) == Some("\n") { - log::debug!("returning null"); return MotionKind::Null; } target.add(1); @@ -2116,7 +2107,6 @@ impl LineBuf { _ => unreachable!(), } if self.grapheme_at(target.get()) == Some("\n") { - log::debug!("returning null outside of match"); return MotionKind::Null; } } @@ -2132,7 +2122,6 @@ impl LineBuf { }) else { return MotionKind::Null; }; - log::debug!("{:?}", self.slice(start..end)); let target_col = if let Some(col) = self.saved_col { col @@ -2145,10 +2134,7 @@ impl LineBuf { let Some(line) = self.slice(start..end).map(|s| s.to_string()) else { return MotionKind::Null; }; - log::debug!("{target_col:?}"); - log::debug!("{target_col:?}"); let mut target_pos = self.grapheme_index_for_display_col(&line, target_col); - log::debug!("{target_pos:?}"); if self.cursor.exclusive && line.ends_with("\n") && self.grapheme_at(target_pos) == Some("\n") @@ -2173,8 +2159,6 @@ impl LineBuf { }) else { return MotionKind::Null; }; - log::debug!("{start:?}, {end:?}"); - log::debug!("{:?}", self.slice(start..end)); let target_col = if let Some(col) = self.saved_col { col @@ -2239,9 +2223,6 @@ impl LineBuf { let has_consumed_hint = (self.cursor.exclusive && self.cursor.get() >= last_grapheme_pos) || (!self.cursor.exclusive && self.cursor.get() > last_grapheme_pos); - log::debug!("{has_consumed_hint:?}"); - log::debug!("{:?}", self.cursor.get()); - log::debug!("{last_grapheme_pos:?}"); if has_consumed_hint { let buf_end = if self.cursor.exclusive { @@ -2403,7 +2384,6 @@ impl LineBuf { } else { let drained = self.drain(start, end); self.update_graphemes(); - log::debug!("{:?}", self.cursor); drained }; register.write_to_register(register_text); diff --git a/src/prompt/readline/mod.rs b/src/prompt/readline/mod.rs index adb550c..8892556 100644 --- a/src/prompt/readline/mod.rs +++ b/src/prompt/readline/mod.rs @@ -9,7 +9,7 @@ use vimode::{CmdReplay, ModeReport, ViInsert, ViMode, ViNormal, ViReplace, ViVis use crate::{libsh::{ error::{ShErrKind, ShResult}, term::{Style, Styled}, -}, parse::lex::{self, LexFlags, Tk, TkFlags, TkRule}, prompt::readline::highlight::Highlighter}; +}, parse::lex::{self, LexFlags, Tk, TkFlags, TkRule}, prompt::readline::{complete::{CompResult, Completer}, highlight::Highlighter}}; use crate::prelude::*; pub mod history; @@ -21,6 +21,7 @@ pub mod term; pub mod vicmd; pub mod vimode; pub mod highlight; +pub mod complete; pub mod markers { // token-level (derived from token class) @@ -101,15 +102,20 @@ pub enum ReadlineEvent { pub struct FernVi { pub reader: PollReader, pub writer: Box, + pub prompt: String, pub highlighter: Highlighter, + pub completer: Completer, + pub mode: Box, - pub old_layout: Option, pub repeat_action: Option, pub repeat_motion: Option, pub editor: LineBuf, + + pub old_layout: Option, pub history: History, - needs_redraw: bool, + + pub needs_redraw: bool, } impl FernVi { @@ -118,6 +124,7 @@ impl FernVi { reader: PollReader::new(), writer: Box::new(TermWriter::new(STDOUT_FILENO)), prompt: prompt.unwrap_or("$ ".styled(Style::Green)), + completer: Completer::new(), highlighter: Highlighter::new(), mode: Box::new(ViInsert::new()), old_layout: None, @@ -139,10 +146,8 @@ impl FernVi { /// Feed raw bytes from stdin into the reader's buffer pub fn feed_bytes(&mut self, bytes: &[u8]) { - log::info!("Feeding bytes: {:?}", bytes.iter().map(|b| *b as char).collect::()); let test_input = "echo \"hello $USER\" | grep $(whoami)"; let annotated = annotate_input(test_input); - log::info!("Annotated test input: {:?}", annotated); self.reader.feed_bytes(bytes); } @@ -173,7 +178,6 @@ impl FernVi { // Process all available keys while let Some(key) = self.reader.read_key()? { - log::debug!("{key:?}"); if self.should_accept_hint(&key) { self.editor.accept_hint(); @@ -182,10 +186,42 @@ impl FernVi { continue; } + if let KeyEvent(KeyCode::Tab, mod_keys) = key { + let direction = match mod_keys { + ModKeys::SHIFT => -1, + _ => 1, + }; + let line = self.editor.as_str().to_string(); + let cursor_pos = self.editor.cursor_byte_pos(); + + match self.completer.complete(line, cursor_pos, direction)? { + Some(mut line) => { + let span_start = self.completer.token_span.0; + let new_cursor = span_start + self.completer.selected_candidate().map(|c| c.len()).unwrap_or_default(); + + self.editor.set_buffer(line); + self.editor.cursor.set(new_cursor); + + self.history.update_pending_cmd(self.editor.as_str()); + let hint = self.history.get_hint(); + self.editor.set_hint(hint); + } + None => { + self.writer.flush_write("\x07")?; // Bell character + } + } + + self.needs_redraw = true; + continue; + } + + // if we are here, we didnt press tab + // so we should reset the completer state + self.completer.reset(); + let Some(mut cmd) = self.mode.handle_key(key) else { continue; }; - log::debug!("{cmd:?}"); cmd.alter_line_motion_if_no_verb(); if self.should_grab_history(&cmd) { @@ -240,13 +276,11 @@ impl FernVi { } pub fn get_layout(&mut self, line: &str) -> Layout { - log::debug!("{line:?}"); let to_cursor = self.editor.slice_to_cursor().unwrap_or_default(); let (cols, _) = get_win_size(STDIN_FILENO); Layout::from_parts(/* tab_stop: */ 8, cols, &self.prompt, to_cursor, line) } pub fn scroll_history(&mut self, cmd: ViCmd) { - log::debug!("scrolling"); /* if self.history.cursor_entry().is_some_and(|ent| ent.is_new()) { let constraint = SearchConstraint::new(SearchKind::Prefix, self.editor.to_string()); @@ -255,23 +289,17 @@ impl FernVi { */ let count = &cmd.motion().unwrap().0; let motion = &cmd.motion().unwrap().1; - log::debug!("{count:?}, {motion:?}"); - log::debug!("{:?}", self.history.masked_entries()); let entry = match motion { Motion::LineUpCharwise => { let Some(hist_entry) = self.history.scroll(-(*count as isize)) else { return; }; - log::debug!("found entry"); - log::debug!("{:?}", hist_entry.command()); hist_entry } Motion::LineDownCharwise => { let Some(hist_entry) = self.history.scroll(*count as isize) else { return; }; - log::debug!("found entry"); - log::debug!("{:?}", hist_entry.command()); hist_entry } _ => unreachable!(), @@ -296,8 +324,6 @@ impl FernVi { self.editor = buf } pub fn should_accept_hint(&self, event: &KeyEvent) -> bool { - log::debug!("{:?}", self.editor.cursor_at_max()); - log::debug!("{:?}", self.editor.cursor); if self.editor.cursor_at_max() && self.editor.has_hint() { match self.mode.report_mode() { ModeReport::Replace | ModeReport::Insert => { @@ -337,7 +363,6 @@ impl FernVi { let hint = self.editor.get_hint_text(); let complete = format!("{highlighted}{hint}"); let end = start.elapsed(); - log::info!("Line styling done in: {:.2?}", end); complete } @@ -538,15 +563,95 @@ pub fn annotate_input(input: &str) -> String { let input = Arc::new(input.to_string()); let tokens: Vec = lex::LexStream::new(input, LexFlags::LEX_UNFINISHED) .flatten() + .filter(|tk| !matches!(tk.class, TkRule::SOI | TkRule::EOI | TkRule::Null)) .collect(); for tk in tokens.into_iter().rev() { - annotate_token(&mut annotated, tk); + let insertions = annotate_token(tk); + for (pos, marker) in insertions { + let pos = pos.max(0).min(annotated.len()); + annotated.insert(pos, marker); + } } annotated } +/// Recursively annotates nested constructs in the input string +pub fn annotate_input_recursive(input: &str) -> String { + let mut annotated = annotate_input(input); + let mut chars = annotated.char_indices().peekable(); + let mut changes = vec![]; + + while let Some((pos,ch)) = chars.next() { + match ch { + markers::CMD_SUB | + markers::SUBSH | + markers::PROC_SUB => { + let mut body = String::new(); + let span_start = pos + ch.len_utf8(); + let mut span_end = span_start; + let closing_marker = match ch { + markers::CMD_SUB => markers::CMD_SUB_END, + markers::SUBSH => markers::SUBSH_END, + markers::PROC_SUB => markers::PROC_SUB_END, + _ => unreachable!() + }; + while let Some((sub_pos,sub_ch)) = chars.next() { + match sub_ch { + _ if sub_ch == closing_marker => { + span_end = sub_pos; + break; + } + _ => body.push(sub_ch), + } + } + let prefix = match ch { + markers::PROC_SUB => { + match chars.peek().map(|(_, c)| *c) { + Some('>') => ">(", + Some('<') => "<(", + _ => { + log::error!("Unexpected character after PROC_SUB marker: expected '>' or '<'"); + "<(" + } + } + } + markers::CMD_SUB => "$(", + markers::SUBSH => "(", + _ => unreachable!() + }; + + body = body.trim_start_matches(prefix).to_string(); + let annotated_body = annotate_input_recursive(&body); + let final_str = format!("{prefix}{annotated_body})"); + changes.push((span_start, span_end, final_str)); + } + _ => {} + } + } + + for change in changes.into_iter().rev() { + let (start, end, replacement) = change; + annotated.replace_range(start..end, &replacement); + } + + annotated +} + +pub fn get_insertions(input: &str) -> Vec<(usize, char)> { + let input = Arc::new(input.to_string()); + let tokens: Vec = lex::LexStream::new(input, LexFlags::LEX_UNFINISHED) + .flatten() + .collect(); + + let mut insertions = vec![]; + for tk in tokens.into_iter().rev() { + insertions.extend(annotate_token(tk)); + } + insertions +} + /// Maps token class to its corresponding marker character /// /// Returns the appropriate Unicode marker for token-level syntax elements. @@ -578,43 +683,7 @@ pub fn marker_for(class: &TkRule) -> Option { } } -/// Annotates a single token with markers for both token-level and sub-token constructs -/// -/// This is the core annotation function that handles the complexity of shell syntax. -/// It uses a two-phase approach: -/// -/// # Phase 1: Analysis (Delayed Insertion) -/// Scans through the token character by character, recording marker insertions -/// as `(position, marker)` pairs in a list. This avoids borrowing issues and -/// allows context queries during the scan. -/// -/// The analysis phase handles: -/// - **Strings**: Single/double quoted regions (with escaping rules) -/// - **Variables**: `$VAR` and `${VAR}` expansions -/// - **Command substitutions**: `$(...)` with depth tracking -/// - **Process substitutions**: `<(...)` and `>(...)` -/// - **Globs**: `*`, `?`, `[...]` patterns (context-aware) -/// - **Escapes**: Backslash escaping -/// -/// # Phase 2: Application (Sorted Insertion) -/// Markers are sorted by position (descending) to avoid index invalidation when -/// inserting into the string. At the same position, markers are ordered: -/// 1. RESET (rightmost) -/// 2. Regular markers (middle) -/// 3. END markers (leftmost) -/// -/// This produces the pattern: `[END][TOGGLE][RESET]` at boundaries. -/// -/// # Context Tracking -/// The `in_context` closure queries the insertion list to determine the active -/// syntax context at the current position. This enables context-aware decisions -/// like "only highlight globs in arguments, not in command names". -/// -/// # Depth Tracking -/// Nested constructs like `$(echo $(date))` are tracked with depth counters. -/// Only the outermost construct is marked; inner content is handled recursively -/// by the highlighter. -pub fn annotate_token(input: &mut String, token: Tk) { +pub fn annotate_token(token: Tk) -> Vec<(usize, char)> { // Sort by position descending, with priority ordering at same position: // - RESET first (inserted first, ends up rightmost) // - Regular markers middle @@ -686,18 +755,21 @@ pub fn annotate_token(input: &mut String, token: Tk) { ctx.1 == c }; + let mut insertions: Vec<(usize, char)> = vec![]; + + if token.class != TkRule::Str && let Some(marker) = marker_for(&token.class) { - input.insert(token.span.end, markers::RESET); - input.insert(token.span.start, marker); - return; + insertions.push((token.span.end, markers::RESET)); + insertions.push((token.span.start, marker)); + return insertions; } else if token.flags.contains(TkFlags::IS_SUBSH) { let token_raw = token.span.as_str(); if token_raw.ends_with(')') { - input.insert(token.span.end, markers::SUBSH_END); + insertions.push((token.span.end, markers::SUBSH_END)); } - input.insert(token.span.start, markers::SUBSH); - return; + insertions.push((token.span.start, markers::SUBSH)); + return insertions; } @@ -713,8 +785,6 @@ pub fn annotate_token(input: &mut String, token: Tk) { let mut cmd_sub_depth = 0; let mut proc_sub_depth = 0; - let mut insertions: Vec<(usize, char)> = vec![]; - if token.flags.contains(TkFlags::BUILTIN) { insertions.insert(0, (span_start, markers::BUILTIN)); } else if token.flags.contains(TkFlags::IS_CMD) { @@ -895,9 +965,5 @@ pub fn annotate_token(input: &mut String, token: Tk) { sort_insertions(&mut insertions); - for (pos, marker) in insertions { - log::info!("Inserting marker {marker:?} at position {pos}"); - let pos = pos.max(0).min(input.len()); - input.insert(pos, marker); - } + insertions } diff --git a/src/prompt/readline/term.rs b/src/prompt/readline/term.rs index 3e706d4..116eeb6 100644 --- a/src/prompt/readline/term.rs +++ b/src/prompt/readline/term.rs @@ -239,13 +239,10 @@ impl TermBuffer { impl Read for TermBuffer { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { assert!(isatty(self.tty).is_ok_and(|r| r)); - log::debug!("TermBuffer::read() ENTERING read syscall"); let result = nix::unistd::read(self.tty, buf); - log::debug!("TermBuffer::read() EXITED read syscall: {:?}", result); match result { Ok(n) => Ok(n), Err(Errno::EINTR) => { - log::debug!("TermBuffer::read() returning EINTR"); Err(Errno::EINTR.into()) } Err(e) => Err(std::io::Error::from_raw_os_error(e as i32)), @@ -409,6 +406,10 @@ impl Perform for KeyCollector { let mods = params.get(1).map(|&m| Self::parse_modifiers(m)).unwrap_or(ModKeys::empty()); KeyEvent(KeyCode::End, mods) } + // Shift+Tab: CSI Z + ([], 'Z') => { + KeyEvent(KeyCode::Tab, ModKeys::SHIFT) + } // Special keys with tilde: CSI num ~ or CSI num;mod ~ ([], '~') => { let key_num = params.first().copied().unwrap_or(0); @@ -643,7 +644,6 @@ impl KeyReader for TermReader { loop { let byte = self.next_byte()?; - log::debug!("read byte: {:?}", byte as char); collected.push(byte); // If it's an escape seq, delegate to ESC sequence handler @@ -706,7 +706,6 @@ impl Layout { to_cursor: &str, to_end: &str, ) -> Self { - log::debug!("{to_cursor:?}"); let prompt_end = Self::calc_pos(tab_stop, term_width, prompt, Pos { col: 0, row: 0 }); let cursor = Self::calc_pos(tab_stop, term_width, to_cursor, prompt_end); let end = Self::calc_pos(tab_stop, term_width, to_end, prompt_end); diff --git a/src/prompt/readline/vimode.rs b/src/prompt/readline/vimode.rs index 5c76507..032341d 100644 --- a/src/prompt/readline/vimode.rs +++ b/src/prompt/readline/vimode.rs @@ -348,14 +348,11 @@ impl ViNormal { /// End the parse and clear the pending sequence #[track_caller] pub fn quit_parse(&mut self) -> Option { - log::debug!("{:?}", std::panic::Location::caller()); - log::warn!("exiting parse early with sequence: {}", self.pending_seq); self.clear_cmd(); None } pub fn try_parse(&mut self, ch: char) -> Option { self.pending_seq.push(ch); - log::debug!("parsing {}", ch); let mut chars = self.pending_seq.chars().peekable(); /* @@ -998,8 +995,6 @@ impl ViNormal { }; if chars.peek().is_some() { - log::warn!("Unused characters in Vi command parse!"); - log::warn!("{:?}", chars) } let verb_ref = verb.as_ref().map(|v| &v.1); @@ -1145,8 +1140,6 @@ impl ViVisual { /// End the parse and clear the pending sequence #[track_caller] pub fn quit_parse(&mut self) -> Option { - log::debug!("{:?}", std::panic::Location::caller()); - log::warn!("exiting parse early with sequence: {}", self.pending_seq); self.clear_cmd(); None } @@ -1630,7 +1623,6 @@ impl ViVisual { )); } ch if ch == 'i' || ch == 'a' => { - log::debug!("in text_obj parse"); let bound = match ch { 'i' => Bound::Inside, 'a' => Bound::Around, @@ -1654,7 +1646,6 @@ impl ViVisual { _ => return self.quit_parse(), }; chars = chars_clone; - log::debug!("{obj:?}, {bound:?}"); break 'motion_parse Some(MotionCmd(count, Motion::TextObj(obj))); } _ => return self.quit_parse(), @@ -1662,13 +1653,10 @@ impl ViVisual { }; if chars.peek().is_some() { - log::warn!("Unused characters in Vi command parse!"); - log::warn!("{:?}", chars) } let verb_ref = verb.as_ref().map(|v| &v.1); let motion_ref = motion.as_ref().map(|m| &m.1); - log::debug!("{verb_ref:?}, {motion_ref:?}"); match self.validate_combination(verb_ref, motion_ref) { CmdState::Complete => Some(ViCmd { diff --git a/src/signal.rs b/src/signal.rs index 7084e86..018bd80 100644 --- a/src/signal.rs +++ b/src/signal.rs @@ -29,38 +29,30 @@ pub fn signals_pending() -> bool { pub fn check_signals() -> ShResult<()> { if GOT_SIGINT.swap(false, Ordering::SeqCst) { - log::debug!("check_signals: processing SIGINT"); interrupt()?; return Err(ShErr::simple(ShErrKind::ClearReadline, "")); } if GOT_SIGHUP.swap(false, Ordering::SeqCst) { - log::debug!("check_signals: processing SIGHUP"); hang_up(0); } if GOT_SIGTSTP.swap(false, Ordering::SeqCst) { - log::debug!("check_signals: processing SIGTSTP"); terminal_stop()?; } if REAPING_ENABLED.load(Ordering::SeqCst) && GOT_SIGCHLD.swap(false, Ordering::SeqCst) { - log::debug!("check_signals: processing SIGCHLD (reaping enabled)"); wait_child()?; } else if GOT_SIGCHLD.load(Ordering::SeqCst) { - log::debug!("check_signals: SIGCHLD pending but reaping disabled"); } if SHOULD_QUIT.load(Ordering::SeqCst) { let code = QUIT_CODE.load(Ordering::SeqCst); - log::debug!("check_signals: SHOULD_QUIT set, exiting with code {}", code); return Err(ShErr::simple(ShErrKind::CleanExit(code), "exit")); } Ok(()) } pub fn disable_reaping() { - log::debug!("disable_reaping: turning off SIGCHLD processing"); REAPING_ENABLED.store(false, Ordering::SeqCst); } pub fn enable_reaping() { - log::debug!("enable_reaping: turning on SIGCHLD processing"); REAPING_ENABLED.store(true, Ordering::SeqCst); } @@ -166,13 +158,10 @@ extern "C" fn handle_sigint(_: libc::c_int) { } pub fn interrupt() -> ShResult<()> { - log::debug!("interrupt: checking for fg job to send SIGINT"); write_jobs(|j| { if let Some(job) = j.get_fg_mut() { - log::debug!("interrupt: sending SIGINT to fg job pgid {}", job.pgid()); job.killpg(Signal::SIGINT) } else { - log::debug!("interrupt: no fg job, clearing readline"); Ok(()) } }) @@ -188,28 +177,22 @@ extern "C" fn handle_sigchld(_: libc::c_int) { } pub fn wait_child() -> ShResult<()> { - log::debug!("wait_child: starting reap loop"); let flags = WtFlag::WNOHANG | WtFlag::WSTOPPED; while let Ok(status) = waitpid(None, Some(flags)) { match status { WtStat::Exited(pid, code) => { - log::debug!("wait_child: pid {} exited with code {}", pid, code); child_exited(pid, status)?; } WtStat::Signaled(pid, signal, _) => { - log::debug!("wait_child: pid {} signaled with {:?}", pid, signal); child_signaled(pid, signal)?; } WtStat::Stopped(pid, signal) => { - log::debug!("wait_child: pid {} stopped with {:?}", pid, signal); child_stopped(pid, signal)?; } WtStat::Continued(pid) => { - log::debug!("wait_child: pid {} continued", pid); child_continued(pid)?; } WtStat::StillAlive => { - log::debug!("wait_child: no more children to reap"); break; } _ => unimplemented!(), diff --git a/src/state.rs b/src/state.rs index 3a360cf..88aeef3 100644 --- a/src/state.rs +++ b/src/state.rs @@ -315,10 +315,7 @@ impl LogTab { self.aliases.get(name).cloned() } pub fn remove_alias(&mut self, name: &str) { - log::debug!("{:?}", self.aliases); - log::debug!("{name:?}"); self.aliases.remove(name); - log::debug!("{:?}", self.aliases); } pub fn clear_aliases(&mut self) { self.aliases.clear() @@ -655,7 +652,6 @@ impl VarTab { } } pub fn var_exists(&self, var_name: &str) -> bool { - log::debug!("checking existence of {}", var_name); if let Ok(param) = var_name.parse::() { return self.params.contains_key(¶m); }