aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/ansi.rs137
-rw-r--r--src/main.rs213
-rw-r--r--src/run/builtin.rs27
-rw-r--r--src/run/mod.rs2
4 files changed, 214 insertions, 165 deletions
diff --git a/src/ansi.rs b/src/ansi.rs
new file mode 100644
index 0000000..e04f6d9
--- /dev/null
+++ b/src/ansi.rs
@@ -0,0 +1,137 @@
+use std::io::Read;
+
+use crate::cursor::Direction;
+
+pub enum KeyboardInput {
+ Eof,
+ Key(u8),
+ CtrlA,
+ CtrlB,
+ CtrlC,
+ CtrlE,
+ CtrlD,
+ CtrlL,
+ CtrlR,
+ Arrow(Direction),
+ CtrlArrow(Direction),
+ DeleteLeft,
+ DeleteRight,
+ CtrlDeleteRight,
+ Home,
+ End,
+}
+
+fn read1() -> Option<u8> {
+ let mut buf = [0];
+ match std::io::stdin().lock().read_exact(&mut buf) {
+ Ok(_) => Some(buf[0]),
+ Err(_) => None,
+ }
+}
+
+fn byte_to_dir(b: u8) -> Option<Direction> {
+ use Direction::*;
+ match b {
+ b'A' => Some(Up),
+ b'B' => Some(Down),
+ b'C' => Some(Right),
+ b'D' => Some(Left),
+ _ => None,
+ }
+}
+
+fn read_escape(debug: bool) -> KeyboardInput {
+ use Direction::*;
+ use KeyboardInput::*;
+
+ let mut seq = vec![match read1() {
+ Some(x) => x,
+ None => return Eof,
+ }];
+
+ if seq[0] == b'[' {
+ // still more
+ while {
+ let last = seq[seq.len() - 1];
+ !(0x40..=0x7E).contains(&last) || seq.len() == 1
+ } {
+ seq.push(match read1() {
+ Some(x) => x,
+ None => return Eof,
+ });
+ }
+
+ if debug {
+ println!("escape: {}", seq.escape_ascii());
+ }
+
+ match seq[1] {
+ b'3' => {
+ if seq.len() > 2 && seq[2] == b'~' {
+ DeleteRight
+ } else {
+ todo!("unhandled: {}", seq.escape_ascii());
+ }
+ }
+ b'H' => Home,
+ b'F' => End,
+ b'd' => CtrlDeleteRight,
+
+ // Ctrl Arrow
+ b'1' => {
+ if seq[1..].starts_with(b"1;5") {
+ if seq.len() == 4 {
+ todo!("idk what this is.");
+ }
+ match seq[4] {
+ b'A' => CtrlArrow(Up),
+ b'B' => CtrlArrow(Down),
+ b'C' => CtrlArrow(Right),
+ b'D' => CtrlArrow(Left),
+ _ => todo!("unhandled {}", seq.escape_ascii()),
+ }
+ } else {
+ todo!("unhandled {}", seq[1..].escape_ascii())
+ }
+ }
+
+ x => {
+ if let Some(dir) = byte_to_dir(x) {
+ Arrow(dir)
+ } else {
+ todo!("escape characters {}", seq[1..].escape_ascii())
+ }
+ }
+ }
+ } else {
+ if debug {
+ println!("escape: {}", seq.escape_ascii());
+ }
+ match seq[0] {
+ b'd' => CtrlDeleteRight,
+ x => todo!("unhandled escape code: ESC {x}"),
+ }
+ }
+}
+
+pub fn read(debug: bool) -> KeyboardInput {
+ use KeyboardInput::*;
+
+ let Some(x) = read1() else {
+ return KeyboardInput::Eof;
+ };
+
+ match x {
+ 1 => CtrlA,
+ 2 => CtrlB,
+ 3 => CtrlC,
+ 4 => CtrlD,
+ 8 | 127 => DeleteLeft,
+ 12 => CtrlL,
+ 18 => CtrlR,
+ 27 => read_escape(debug),
+ b'\r' => Key(x),
+ x if !x.is_ascii_control() => Key(x),
+ x => todo!("unimplemented control code: {x}"),
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 9286653..e0d9e84 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -13,6 +13,7 @@ use std::sync::{Arc, Mutex};
use std::thread::sleep;
use std::time::Duration;
+pub mod ansi;
pub mod basedir;
pub mod completion;
pub mod ctrlc;
@@ -273,12 +274,6 @@ impl Session {
}
}
-fn read1() -> u8 {
- let mut buf = [0];
- io::stdin().lock().read_exact(&mut buf).unwrap();
- buf[0]
-}
-
fn event_loop() {
history::setup();
@@ -316,56 +311,31 @@ fn event_loop() {
let _ctrlc = ctrlc::setup(session.clone());
loop {
- let mut buf = [0u8; 1];
-
- let Ok(_) = stdin.lock().read_exact(&mut buf) else {
- break;
- };
-
let mut se = session.lock().unwrap();
- if se.debug_keystrokes {
- println!("{}", buf.escape_ascii());
- }
-
- match buf[0] {
- // Ctrl+A
- 1 => {
- se.move_to_begin();
- }
+ use ansi::KeyboardInput as Kb;
- // Ctrl+E
- 5 => {
- se.move_to_end();
- }
-
- // Ctrl+C
- 3 => {
+ match ansi::read(se.debug_keystrokes) {
+ Kb::Eof => todo!(),
+ Kb::CtrlA => se.move_to_begin(),
+ Kb::CtrlB => todo!(),
+ Kb::CtrlC => {
se.clear_prompt();
se.history_visit = 0;
}
-
- // EOF
- 4 => {
- break;
- }
-
- // apparently another backspace, don't do anything.
- 8 => {}
-
- // Ctrl+L
- 12 => {
+ Kb::CtrlE => se.move_to_end(),
+ 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();
}
-
- // Ctrl+R
- 18 => {}
-
- // Enter
- b'\r' => {
+ 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() {
@@ -391,19 +361,7 @@ fn event_loop() {
run::run(session.clone(), parsed);
}
}
-
- // Backspace (127 on most systems)
- 127 => {
- 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();
- }
- }
-
- b'\t' => {
+ Kb::Key(b'\t') => {
let cmd = se.line.pre().to_vec();
drop(se);
@@ -422,117 +380,44 @@ fn event_loop() {
se.reprint_prompt();
}
}
-
- // Escape sequence
- 27 => {
- let mut seq = vec![read1()];
-
- if seq[0] == b'[' {
- // still more
- while {
- let last = seq[seq.len() - 1];
- !(0x40..=0x7E).contains(&last) || seq.len() == 1
- } {
- seq.push(read1());
- }
-
- if se.debug_keystrokes {
- println!("escape: {}", seq.escape_ascii());
+ Kb::Arrow(dir) => match dir {
+ Direction::Up => se.history_up(),
+ Direction::Down => se.history_down(),
+ Direction::Left => {
+ if se.line.left() {
+ move_cursor(Direction::Left, 1);
+ io::stdout().lock().flush().unwrap();
}
-
- match seq[1] {
- b'A' => {
- // up
- se.history_up();
- }
- b'B' => {
- // down
- se.history_down();
- }
- b'C' => {
- if se.line.right() {
- move_cursor(Direction::Right, 1);
- io::stdout().lock().flush().unwrap();
- }
- }
- b'D' => {
- if se.line.left() {
- move_cursor(Direction::Left, 1);
- io::stdout().lock().flush().unwrap();
- }
- }
- b'3' => {
- if seq.len() > 2 && seq[2] == b'~' {
- se.del_right();
- } else {
- todo!("unhandled: {}", seq.escape_ascii());
- }
- }
-
- // HOME button
- b'H' => {
- se.move_to_begin();
- }
-
- // END button
- b'F' => {
- se.move_to_end();
- }
-
- // Ctrl Arrow
- b'1' => {
- if seq[1..].starts_with(b"1;5") {
- if seq.len() == 4 {
- todo!("idk what this is.");
- }
- match seq[4] {
- b'A' => {
- println!("Ctrl+Up");
- se.reprint_prompt();
- continue;
- }
- b'B' => {
- println!("Ctrl+Down");
- se.reprint_prompt();
- continue;
- }
- b'C' => {
- se.move_right_word();
- }
- b'D' => {
- se.move_left_word();
- }
- _ => todo!("unhandled {}", seq.escape_ascii()),
- }
- continue;
- }
- todo!("unhandled {}", seq[1..].escape_ascii())
- }
-
- _ => todo!("escape characters {}", seq[1..].escape_ascii()),
+ }
+ Direction::Right => {
+ if se.line.right() {
+ move_cursor(Direction::Right, 1);
+ io::stdout().lock().flush().unwrap();
}
+ }
+ },
+ Kb::CtrlArrow(dir) => match dir {
+ Direction::Left => se.move_left_word(),
+ Direction::Right => se.move_right_word(),
+ _ => {
+ println!("Ctrl+{dir:?} not implemented");
+ se.reprint_prompt();
+ }
+ },
+ 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 {
- if se.debug_keystrokes {
- println!("escape: {}", seq.escape_ascii());
- }
- if seq[0] == b'd' {
- se.del_right_word();
- }
+ se.del_left();
}
}
-
- b'|' if se.line.is_empty() && !se.history.is_empty() => {
- let mut cmd = se.history[se.history.len() - 1].cmd.clone();
- cmd.extend_from_slice(b" | ");
- io::stdout().write_all(&cmd).unwrap();
- io::stdout().flush().unwrap();
- se.line.set_content(cmd);
- }
-
- // Normal character
- x => {
- se.type_byte(x);
- }
+ Kb::DeleteRight => se.del_right(),
+ Kb::CtrlDeleteRight => se.del_right_word(),
+ Kb::Home => se.move_to_begin(),
+ Kb::End => se.move_to_end(),
+ Kb::Key(x) => se.type_byte(x),
}
}
diff --git a/src/run/builtin.rs b/src/run/builtin.rs
index 7ca1e5e..c080c93 100644
--- a/src/run/builtin.rs
+++ b/src/run/builtin.rs
@@ -487,7 +487,8 @@ impl Builtin for alias {
let mut parse_fail = false;
let mut alias_args = Vec::new();
for arg in args {
- match <Vec<crate::parse::ExpString> as crate::parse::Parse>::parse_from_bytes(&arg[..]) {
+ match <Vec<crate::parse::ExpString> as crate::parse::Parse>::parse_from_bytes(&arg[..])
+ {
Ok(mut parsed) => {
alias_args.append(&mut parsed);
}
@@ -539,3 +540,27 @@ impl Builtin for unalias {
Ok(())
}
}
+
+pub struct debug;
+impl Builtin for debug {
+ fn name(&self) -> &str {
+ "debug"
+ }
+
+ fn io(
+ &self,
+ session: Arc<Mutex<Session>>,
+ args: &[BString],
+ _stdin: &mut dyn Read,
+ stdout: &mut dyn Write,
+ ) -> Result {
+ let mut se = session.lock().unwrap();
+ for arg in args {
+ match &arg[..] {
+ b"keys" | b"keystrokes" => se.debug_keystrokes = !se.debug_keystrokes,
+ _ => writeln!(stdout, "debug: unknown option {}", arg.escape_ascii())?,
+ }
+ }
+ Ok(())
+ }
+}
diff --git a/src/run/mod.rs b/src/run/mod.rs
index 36cd454..6fe0d45 100644
--- a/src/run/mod.rs
+++ b/src/run/mod.rs
@@ -539,6 +539,8 @@ const BUILTINS: &[&'static dyn Builtin] = &[
&builtin::completion,
&builtin::alias,
&builtin::unalias,
+ #[cfg(debug_assertions)]
+ &builtin::debug,
];
pub fn builtin_map() -> HashMap<BString, &'static dyn Builtin> {