aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJonas Maier <>2026-05-03 11:29:42 +0200
committerJonas Maier <>2026-05-03 11:29:42 +0200
commit110b179e3651570f6ede3090e2e8f304dac7024d (patch)
treef8af54954d0845132d5ee3affed167b06fbc853e
parentd5953e52f0df8ca6727e71fa07f147467a7369c1 (diff)
downloadpish-110b179e3651570f6ede3090e2e8f304dac7024d.tar.gz
keybinds seem to work somewhat
-rw-r--r--src/ansi/mod.rs4
-rw-r--r--src/main.rs73
-rw-r--r--src/run/builtin.rs114
-rw-r--r--src/run/mod.rs21
4 files changed, 135 insertions, 77 deletions
diff --git a/src/ansi/mod.rs b/src/ansi/mod.rs
index 0b43ede..cbdcbe6 100644
--- a/src/ansi/mod.rs
+++ b/src/ansi/mod.rs
@@ -190,7 +190,7 @@ enum EscapeTrie {
More(BTreeMap<u8, EscapeTrie>),
}
-#[derive(Debug)]
+#[derive(Clone, Debug)]
pub enum KbInput<'a> {
Key([u8; 1]),
Escape(Escape<'a>),
@@ -207,7 +207,7 @@ impl<'a> KbInput<'a> {
}
}
-#[derive(Debug)]
+#[derive(Clone, Debug)]
pub struct Escape<'a> {
pub keys: &'a [&'a str],
pub value: Vec<u8>,
diff --git a/src/main.rs b/src/main.rs
index f2b7544..a21b313 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,8 @@
-#![feature(unix_socket_ancillary_data, peer_credentials_unix_socket)]
+#![feature(
+ unix_socket_ancillary_data,
+ peer_credentials_unix_socket,
+ associated_type_defaults
+)]
#![allow(clippy::needless_range_loop)]
use std::collections::HashMap;
@@ -81,7 +85,7 @@ pub struct Session {
line: LineBuf,
history: Vec<HistoryEntry>,
prev_path: BString,
- builtins: HashMap<BString, &'static dyn run::Builtin>,
+ builtins: HashMap<BString, &'static dyn run::BuiltinClone>,
vars: HashMap<BString, BString>,
funs: HashMap<BString, Block>,
aliases: run::Aliases,
@@ -214,6 +218,16 @@ impl Session {
self.line.display_post(b" ");
}
+ fn del_left_or_previous(&mut self) {
+ if self.line.is_empty() && !self.line.is_dirty() && !self.history.is_empty() {
+ // take previous command for editing
+ let cmd = self.history[self.history.len() - 1].cmd.clone();
+ self.type_bytes(&cmd);
+ } else {
+ self.del_left();
+ }
+ }
+
fn move_to_begin(&mut self) {
cursor::move_cursor(Direction::Left, self.line.all_left());
io::stdout().flush().unwrap();
@@ -316,6 +330,11 @@ impl Session {
se.reprint_prompt();
}
}
+
+ fn screen_clear(&mut self) {
+ clear_screen();
+ self.reprint_prompt();
+ }
}
fn exec_rc_file(se: Arc<Mutex<Session>>) {
@@ -375,7 +394,7 @@ fn event_loop() {
aliases: run::Aliases::new(),
path_cache: Default::default(),
ctrlc: Default::default(),
- debug_keystrokes: true,
+ debug_keystrokes: false,
loud: false,
ti_keybinds: HashMap::new(),
ascii_keybinds: HashMap::new(),
@@ -488,47 +507,6 @@ fn event_loop() {
/*
match key {
- Kb::CtrlA => se.move_to_begin(),
- Kb::CtrlE => se.move_to_end(),
- Kb::Eof | Kb::CtrlD => break,
- Kb::CtrlL => {
- clear_screen();
- print!("{}", se.prompt());
- io::stdout().write_all(&se.line.into_bytes()).unwrap();
- cursor::move_cursor(Direction::Left, se.line.distance_from_right_end());
- io::stdout().lock().flush().unwrap();
- }
- Kb::CtrlR => {
- println!(" search is not yet implemented");
- se.reprint_prompt();
- }
- Kb::Key(b'\r' | b'\n') => {
- let line = se.line.into_bytes();
-
- if !line.is_empty() {
- let parsed = match parse::do_parse(&line) {
- Ok(p) => p,
- Err((crate::parse::ParseError::Eof, _)) => {
- se.line.add(b'\n');
- print!("\r\n> ");
- continue;
- }
- Err(e) => {
- println!("{e:?}\n{}", se.prompt());
- continue;
- }
- };
- print!("\r\n");
- let entry = HistoryEntry::new(line.clone());
- history::persist(&entry);
- se.history.push(entry);
- se.history_visit = 0;
- se.line.dump();
- drop(se);
- run::run(session.clone(), parsed);
- }
- }
- Kb::Key(b'\t') => {}
Kb::Arrow(dir) => match dir {
Direction::Up => se.history_up(),
Direction::Down => se.history_down(),
@@ -554,13 +532,6 @@ fn event_loop() {
}
},
Kb::DeleteLeft => {
- if se.line.is_empty() && !se.line.is_dirty() && !se.history.is_empty() {
- // take previous command for editing
- let cmd = se.history[se.history.len() - 1].cmd.clone();
- se.type_bytes(&cmd);
- } else {
- se.del_left();
- }
}
Kb::DeleteRight => se.del_right(),
Kb::CtrlDeleteRight => se.del_right_word(),
diff --git a/src/run/builtin.rs b/src/run/builtin.rs
index 507759f..2886d71 100644
--- a/src/run/builtin.rs
+++ b/src/run/builtin.rs
@@ -48,6 +48,7 @@ fn read_args<T: ArgParse>(args: &[BString], w: &mut dyn Write) -> std::result::R
Err(Error::Exit(-2))
}
+#[derive(Copy, Clone)]
pub struct cd;
impl Builtin for cd {
fn name(&self) -> &str {
@@ -91,6 +92,7 @@ impl Builtin for cd {
}
}
+#[derive(Copy, Clone)]
pub struct clear;
impl Builtin for clear {
fn name(&self) -> &str {
@@ -110,13 +112,14 @@ impl Builtin for clear {
}
/// restart shell
+#[derive(Copy, Clone)]
pub struct re;
impl Builtin for re {
fn name(&self) -> &str {
"re"
}
- fn special(&self, session: Arc<Mutex<Session>>, _args: &[BString]) {
+ fn special(&mut self, session: Arc<Mutex<Session>>, _args: &[BString]) {
session.lock().unwrap().raw.disable();
crate::reload::begin_reload();
session.lock().unwrap().raw.enable(); // something went wrong, let's restore raw mode
@@ -133,6 +136,7 @@ impl Builtin for re {
}
}
+#[derive(Copy, Clone)]
pub struct Sink {
name: &'static str,
append: bool,
@@ -168,6 +172,7 @@ pub const fn sink(name: &'static str, append: bool) -> Sink {
Sink { name, append }
}
+#[derive(Copy, Clone)]
pub struct from;
impl Builtin for from {
fn name(&self) -> &str {
@@ -191,6 +196,7 @@ impl Builtin for from {
}
}
+#[derive(Copy, Clone)]
pub struct _type;
impl Builtin for _type {
fn name(&self) -> &str {
@@ -227,6 +233,7 @@ impl Builtin for _type {
}
}
+#[derive(Copy, Clone)]
pub struct builtins;
impl Builtin for builtins {
fn name(&self) -> &str {
@@ -280,6 +287,7 @@ fn display_history<T: Iterator<Item = HistoryEntry>>(it: T, stdout: &mut dyn Wri
Ok(())
}
+#[derive(Copy, Clone)]
pub struct history;
impl Builtin for history {
fn name(&self) -> &str {
@@ -332,6 +340,7 @@ impl Builtin for history {
}
}
+#[derive(Copy, Clone)]
pub struct escape;
impl Builtin for escape {
fn name(&self) -> &str {
@@ -354,6 +363,7 @@ impl Builtin for escape {
}
}
+#[derive(Copy, Clone)]
pub struct parse;
impl Builtin for parse {
fn name(&self) -> &str {
@@ -383,6 +393,7 @@ impl Builtin for parse {
}
}
+#[derive(Copy, Clone)]
pub struct completion;
impl Builtin for completion {
fn name(&self) -> &str {
@@ -410,6 +421,7 @@ impl Builtin for completion {
}
}
+#[derive(Copy, Clone)]
pub struct null;
impl Builtin for null {
fn name(&self) -> &str {
@@ -428,6 +440,7 @@ impl Builtin for null {
}
}
+#[derive(Copy, Clone)]
pub struct var;
impl Builtin for var {
fn name(&self) -> &str {
@@ -456,6 +469,7 @@ impl Builtin for var {
}
}
+#[derive(Copy, Clone)]
pub struct alias;
impl Builtin for alias {
fn name(&self) -> &str {
@@ -517,6 +531,7 @@ impl Builtin for alias {
}
}
+#[derive(Copy, Clone)]
pub struct unalias;
impl Builtin for unalias {
fn name(&self) -> &str {
@@ -541,6 +556,7 @@ impl Builtin for unalias {
}
}
+#[derive(Copy, Clone)]
pub struct debug;
impl Builtin for debug {
fn name(&self) -> &str {
@@ -565,6 +581,7 @@ impl Builtin for debug {
}
}
+#[derive(Copy, Clone)]
pub struct terminfo;
impl Builtin for terminfo {
fn name(&self) -> &str {
@@ -602,12 +619,35 @@ impl Builtin for terminfo {
}
}
-pub struct bind;
+#[derive(Clone)]
+pub struct bind {
+ key: Option<crate::ansi::KbInput<'static>>,
+}
+
+impl bind {
+ pub const fn new() -> Self {
+ Self { key: None }
+ }
+
+ fn is_interactive(args: &[BString]) -> bool {
+ matches!(args.get(0).map(|x| &x[..]), Some(b"i" | b"interactive"))
+ }
+}
+
impl Builtin for bind {
fn name(&self) -> &str {
"bind"
}
+ fn special(&mut self, _session: Arc<Mutex<Session>>, args: &[BString]) {
+ if Self::is_interactive(args) {
+ let raw = crate::raw::ScopedRawMode::on_fd(0);
+ raw.enable();
+ self.key = crate::ansi::read(false);
+ raw.disable();
+ }
+ }
+
fn io(
&self,
session: Arc<Mutex<Session>>,
@@ -618,47 +658,78 @@ impl Builtin for bind {
let mut usage = || {
writeln!(
stdout,
- "usage: bind ti NAMED_KEYBIND COMMAND | bind key KEY COMMAND"
+ "usage: bind ti NAMED_KEYBIND COMMAND | bind key KEY COMMAND | bind [i|interactive]"
)?;
Err(Error::Exit(1))
};
- if args.len() < 3 {
+ if args.len() < 2 {
return usage();
}
let kind = &args[0];
- let key = &args[1];
- let cmd = &args[2];
- let args = &args[3..];
let mut se = session.lock().unwrap();
- let map = match &kind[..] {
- b"ti" => &mut se.ti_keybinds,
- b"key" => &mut se.ascii_keybinds,
- _ => return usage(),
+ let bind0 = |map: &mut HashMap<BString, _>, key: &[u8], cmd: &[BString]| {
+ map.insert(
+ key.to_vec(),
+ crate::parse::Command {
+ cmd: cmd[0].clone(),
+ args: cmd[1..].to_vec(),
+ },
+ );
+ };
+
+ let bind1 = |map: &mut HashMap<_, _>| {
+ bind0(map, &args[1], &args[2..]);
};
- map.insert(
- key.clone(),
- crate::parse::Command {
- cmd: cmd.clone(),
- args: args.to_vec(),
- },
- );
+ if Self::is_interactive(args) {
+ let Some(key) = self.key.as_ref() else {
+ writeln!(stdout, "no input available.")?;
+ return Err(Error::Exit(-1));
+ };
+
+ let x = match key {
+ ansi::KbInput::Escape(e) => {
+ bind0(&mut se.ascii_keybinds, e.keys[0].as_bytes(), &args[1..]);
+ format!("ti {}", e.keys[0])
+ }
+ _ => {
+ bind0(&mut se.ti_keybinds, key.as_bytes(), &args[1..]);
+ format!("key {}", key.as_bytes().escape_ascii())
+ }
+ };
+
+ // print what gets bound
+ write!(stdout, "bind {x} ")?;
+ for arg in args.iter().skip(1) {
+ stdout.write(&arg[..])?;
+ write!(stdout, " ")?;
+ }
+ writeln!(stdout)?;
+ return Ok(());
+ }
+
+ match &kind[..] {
+ b"ti" => bind1(&mut se.ti_keybinds),
+ b"key" => bind1(&mut se.ascii_keybinds),
+ _ => return usage(),
+ }
Ok(())
}
}
+#[derive(Clone)]
pub struct exit;
impl Builtin for exit {
fn name(&self) -> &str {
"exit"
}
- fn special(&self, _session: Arc<Mutex<Session>>, args: &[BString]) {
+ fn special(&mut self, _session: Arc<Mutex<Session>>, args: &[BString]) {
let exit_code: i32 = loop {
let Some(arg) = args.get(0) else {
break 0;
@@ -687,6 +758,7 @@ impl Builtin for exit {
}
/// control terminal
+#[derive(Copy, Clone)]
pub struct ct;
impl Builtin for ct {
fn name(&self) -> &str {
@@ -714,12 +786,16 @@ impl Builtin for ct {
b"cursor_right_word" => se.cursor_right_word(),
b"cursor_left_word" => se.cursor_left_word(),
b"prompt_clear" => se.prompt_clear(),
+ b"screen_clear" => se.screen_clear(),
b"complete" => {
drop(se);
Session::complete(session)
}
b"history_previous" => se.history_up(),
b"history_next" => se.history_down(),
+ b"prompt_del_left" => se.del_left(),
+ b"prompt_del_right" => se.del_right(),
+ b"prompt_del_left_or_previous" => se.del_left_or_previous(),
_ => return Err(Error::Exit(-2)),
}
diff --git a/src/run/mod.rs b/src/run/mod.rs
index a31ff1a..f727b01 100644
--- a/src/run/mod.rs
+++ b/src/run/mod.rs
@@ -202,6 +202,7 @@ impl Executor {
) -> SpawnedCmd {
match cmd {
CommandKind::Builtin(builtin) => {
+ let mut builtin = builtin.clone_box();
builtin.special(self.se.clone(), &args[1..]);
let cloned_session = self.se.clone();
let handle = wait::spawn(move || {
@@ -532,7 +533,7 @@ type BuiltinResult = Result<(), BuiltinError>;
pub trait Builtin: Send + Sync {
fn name(&self) -> &str;
- fn special(&self, session: Arc<Mutex<Session>>, args: &[BString]) {}
+ fn special(&mut self, session: Arc<Mutex<Session>>, args: &[BString]) {}
fn io(
&self,
@@ -543,7 +544,17 @@ pub trait Builtin: Send + Sync {
) -> BuiltinResult;
}
-const BUILTINS: &[&'static dyn Builtin] = &[
+pub trait BuiltinClone : Builtin {
+ fn clone_box(&self) -> Box<dyn Builtin>;
+}
+
+impl<T: 'static + Builtin + Clone> BuiltinClone for T {
+ fn clone_box(&self) -> Box<dyn Builtin> {
+ Box::new(self.clone())
+ }
+}
+
+const BUILTINS: &[&'static dyn BuiltinClone] = &[
&builtin::cd,
&builtin::clear,
#[cfg(debug_assertions)]
@@ -565,12 +576,12 @@ const BUILTINS: &[&'static dyn Builtin] = &[
#[cfg(debug_assertions)]
&builtin::debug,
&builtin::terminfo,
- &builtin::bind,
+ &builtin::bind::new(),
&builtin::exit,
&builtin::ct,
];
-pub fn builtin_map() -> HashMap<BString, &'static dyn Builtin> {
+pub fn builtin_map() -> HashMap<BString, &'static dyn BuiltinClone> {
let mut map = HashMap::new();
for &b in BUILTINS {
map.insert(b.name().as_bytes().to_vec(), b);
@@ -580,7 +591,7 @@ pub fn builtin_map() -> HashMap<BString, &'static dyn Builtin> {
#[derive(Clone)]
pub enum CommandKind {
- Builtin(&'static dyn Builtin),
+ Builtin(&'static dyn BuiltinClone),
Fun(Block),
Path(PathBuf),
}