diff --git a/src/main.rs b/src/main.rs index 79a9984..ffdcf03 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,6 +31,7 @@ use nix::unistd::read; use crate::builtin::keymap::KeyMapMatch; use crate::builtin::trap::TrapTarget; use crate::libsh::error::{self, ShErr, ShErrKind, ShResult}; +use crate::libsh::guards::scope_guard; use crate::libsh::sys::TTY_FILENO; use crate::libsh::utils::AutoCmdVecUtils; use crate::parse::execute::{exec_dash_c, exec_input}; @@ -39,7 +40,7 @@ use crate::procio::borrow_fd; use crate::readline::term::{LineWriter, RawModeGuard, raw_mode}; use crate::readline::{Prompt, ReadlineEvent, ShedVi}; use crate::signal::{GOT_SIGWINCH, JOB_DONE, QUIT_CODE, check_signals, sig_setup, signals_pending}; -use crate::state::{AutoCmdKind, read_logic, read_shopts, source_rc, write_jobs, write_meta}; +use crate::state::{AutoCmdKind, read_logic, read_shopts, source_rc, write_jobs, write_meta, write_shopts}; use clap::Parser; use state::{read_vars, write_vars}; @@ -264,14 +265,38 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> { PollFlags::POLLIN, )]; + let mut exec_if_timeout = None; + let timeout = if readline.pending_keymap.is_empty() { - PollTimeout::MAX + let screensaver_cmd = read_shopts(|o| o.prompt.screensaver_cmd.clone()); + let screensaver_idle_time = read_shopts(|o| o.prompt.screensaver_idle_time); + if screensaver_idle_time > 0 && !screensaver_cmd.is_empty() { + exec_if_timeout = Some(screensaver_cmd); + PollTimeout::from((screensaver_idle_time * 1000) as u16) + } else { + PollTimeout::MAX + } } else { - PollTimeout::from(1000u16) + PollTimeout::from(1000u16) }; match poll(&mut fds, timeout) { - Ok(_) => {} + Ok(0) => { + // We timed out. + if let Some(cmd) = exec_if_timeout { + let prepared = ReadlineEvent::Line(cmd); + let saved_hist_opt = read_shopts(|o| o.core.auto_hist); + let _guard = scopeguard::guard(saved_hist_opt, |opt| { + write_shopts(|o| o.core.auto_hist = opt); + }); + write_shopts(|o| o.core.auto_hist = false); // don't save screensaver command to history + + match handle_readline_event(&mut readline, Ok(prepared))? { + true => return Ok(()), + false => continue + } + } + } Err(Errno::EINTR) => { // Interrupted by signal, loop back to handle it continue; @@ -280,6 +305,7 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> { eprintln!("poll error: {e}"); break; } + Ok(_) => {} } // Timeout — resolve pending keymap ambiguity diff --git a/src/readline/complete.rs b/src/readline/complete.rs index f6475ea..c0a05e8 100644 --- a/src/readline/complete.rs +++ b/src/readline/complete.rs @@ -1,7 +1,7 @@ use std::{ collections::HashSet, fmt::{Debug, Write}, - path::PathBuf, + path::{Path, PathBuf}, sync::Arc, }; @@ -22,7 +22,7 @@ use crate::{ term::{LineWriter, TermWriter, calc_str_width, get_win_size}, vimode::{ViInsert, ViMode}, }, - state::{VarFlags, VarKind, read_jobs, read_logic, read_meta, read_vars, write_vars}, + state::{VarFlags, VarKind, read_jobs, read_logic, read_meta, read_shopts, read_vars, write_vars}, }; pub fn complete_signals(start: &str) -> Vec { @@ -173,6 +173,11 @@ fn complete_commands(start: &str) -> Vec { .collect() }); + if read_shopts(|o| o.core.autocd) { + let dirs = complete_dirs(start); + candidates.extend(dirs); + } + candidates.sort(); candidates } diff --git a/src/shopt.rs b/src/shopt.rs index 3940e3f..1bc6e9e 100644 --- a/src/shopt.rs +++ b/src/shopt.rs @@ -360,6 +360,8 @@ pub struct ShOptPrompt { pub linebreak_on_incomplete: bool, pub leader: String, pub line_numbers: bool, + pub screensaver_cmd: String, + pub screensaver_idle_time: usize, } impl ShOptPrompt { @@ -431,6 +433,18 @@ impl ShOptPrompt { }; self.line_numbers = val; } + "screensaver_cmd" => { + self.screensaver_cmd = val.to_string(); + } + "screensaver_idle_time" => { + let Ok(val) = val.parse::() else { + return Err(ShErr::simple( + ShErrKind::SyntaxErr, + "shopt: expected a positive integer for screensaver_idle_time value", + )); + }; + self.screensaver_idle_time = val; + } "custom" => { todo!() } @@ -496,6 +510,16 @@ impl ShOptPrompt { output.push_str(&format!("{}", self.line_numbers)); Ok(Some(output)) } + "screensaver_cmd" => { + let mut output = String::from("Command to execute as a screensaver after idle timeout\n"); + output.push_str(&self.screensaver_cmd); + Ok(Some(output)) + } + "screensaver_idle_time" => { + let mut output = String::from("Idle time in seconds before running screensaver_cmd (0 = disabled)\n"); + output.push_str(&format!("{}", self.screensaver_idle_time)); + Ok(Some(output)) + } _ => Err(ShErr::simple( ShErrKind::SyntaxErr, format!("shopt: Unexpected 'prompt' option '{query}'"), @@ -519,6 +543,8 @@ impl Display for ShOptPrompt { )); output.push(format!("leader = {}", self.leader)); output.push(format!("line_numbers = {}", self.line_numbers)); + output.push(format!("screensaver_cmd = {}", self.screensaver_cmd)); + output.push(format!("screensaver_idle_time = {}", self.screensaver_idle_time)); let final_output = output.join("\n"); @@ -537,6 +563,8 @@ impl Default for ShOptPrompt { linebreak_on_incomplete: true, leader: "\\".to_string(), line_numbers: true, + screensaver_cmd: String::new(), + screensaver_idle_time: 0, } } }