diff options
| -rw-r--r-- | src/completion.rs | 16 | ||||
| -rw-r--r-- | src/cursor.rs | 10 | ||||
| -rw-r--r-- | src/main.rs | 45 | ||||
| -rw-r--r-- | src/parse.rs | 169 |
4 files changed, 199 insertions, 41 deletions
diff --git a/src/completion.rs b/src/completion.rs index 63ac866..cb030a1 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -6,7 +6,7 @@ pub struct Suggestion { full: BString, } -pub fn _path_completion(mut prefix: BString) -> io::Result<Vec<Suggestion>> { +fn _path_completion(mut prefix: BString) -> io::Result<Vec<Suggestion>> { let mut partial_entry = BString::new(); while let Some(c) = prefix.last().cloned() { if c == b'/' { @@ -19,6 +19,10 @@ pub fn _path_completion(mut prefix: BString) -> io::Result<Vec<Suggestion>> { let mut sugs = Vec::new(); + if prefix.is_empty() { + prefix.push(b'.'); + } + for entry in fs::read_dir(OsStr::from_bytes(&prefix))? { let entry = entry?; let name = entry.file_name().as_bytes().to_vec(); @@ -32,3 +36,13 @@ pub fn _path_completion(mut prefix: BString) -> io::Result<Vec<Suggestion>> { Ok(sugs) } + +pub fn path_completion(prefix: BString) -> Vec<Suggestion> { + match _path_completion(prefix) { + Ok(suggestions) => suggestions, + Err(err) => { + println!("path completion failed: {err:?}\r"); + Vec::new() + } + } +} diff --git a/src/cursor.rs b/src/cursor.rs index 7913d3c..fbaacbb 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -1,3 +1,5 @@ +use std::io::Write; + #[derive(Debug, Clone, Copy)] pub enum Direction { Up, @@ -21,6 +23,14 @@ pub fn move_cursor(direction: Direction, n: usize) { print!("\x1b[{n}{code}"); } +pub fn save() { + std::io::stdout().lock().write_all(b"\x1b[s").unwrap(); +} + +pub fn restore() { + std::io::stdout().lock().write_all(b"\x1b[u").unwrap(); +} + /// Represents a cursor position #[derive(Debug, Clone, Copy)] pub struct CursorPos { diff --git a/src/main.rs b/src/main.rs index 5b195e4..a5820e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,14 +5,14 @@ use std::os::unix::io::AsRawFd; use std::path::Path; use std::process::{Command, Stdio}; +pub mod completion; pub mod cursor; pub mod linebuf; pub mod panic; pub mod parse; pub mod raw; -pub mod run; pub mod reload; -pub mod completion; +pub mod run; use linebuf::LineBuf; use raw::*; @@ -181,7 +181,40 @@ fn event_loop() { } b'\t' => { - todo!() + let cmd = se.line.into_bytes(); + let comp = parse::completion_context(&cmd); + match comp.kind { + parse::CompletionKind::Command => todo!(), + parse::CompletionKind::Argument => { + let suggestions = completion::path_completion(comp.partial); + if suggestions.len() == 0 { + continue; + } + + if suggestions.len() == 1 { + // apply suggestion + todo!("apply suggestion"); + } + + cursor::save(); + + // one line below + print!("\r\n"); + for s in suggestions { + io::stdout().lock().write_all(&s.display).unwrap(); + println!(); + } + + cursor::restore(); + stdout.lock().flush().unwrap(); + } + parse::CompletionKind::None => { + for _ in 0..4 { + se.line.add(b' '); + print!(" ") + } + } + } } // Escape sequence @@ -265,8 +298,10 @@ fn main() { Ok(_) => break, Err(_) => { #[cfg(debug_assertions)] - unsafe { reload::continue_reload() } - }, + unsafe { + reload::continue_reload() + } + } } } diff --git a/src/parse.rs b/src/parse.rs index 39ede4b..2da97c7 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,3 +1,5 @@ +use crate::BString; + #[derive(Debug)] pub enum Ast { AssignVar(AssignVar), @@ -36,45 +38,142 @@ pub enum ParseError { type Result<T> = std::result::Result<T, ParseError>; -pub fn do_parse(mut x: &[u8]) -> Result<Ast> { - Ast::parse(&mut x) +pub fn do_parse(x: &[u8]) -> Result<Ast> { + Ast::parse(&mut Cursor::new(x, ParseMode::Command)) } -trait Parse: Sized { - fn parse(b: &mut &[u8]) -> Result<Self>; +pub enum CompletionKind { + Command, + Argument, + None, } -#[inline(always)] -fn parse<T: Parse>(b: &mut &[u8]) -> Result<T> { - T::parse(b) +pub struct CompletionContext { + pub kind: CompletionKind, + pub partial: BString, } -fn spaces(b: &mut &[u8]) { - while let Some(b' ' | b'\t') = b.get(0) { - *b = &b[1..]; +pub fn completion_context<'a>(x: &'a [u8]) -> CompletionContext { + let mut cursor = Cursor::new(x, ParseMode::Completion); + let ast = Ast::parse(&mut cursor); + match ast { + Ok(Ast::Pipes(pipes)) if cursor.spaced == false => { + if let Some(cmd) = pipes.cmds.last() { + if cmd.args.is_empty() { + CompletionContext { + kind: CompletionKind::Command, + partial: cmd.cmd.clone(), + } + } else { + CompletionContext { + kind: CompletionKind::Argument, + partial: cmd.args[cmd.args.len() - 1].clone(), + } + } + } else { + CompletionContext { + kind: CompletionKind::None, + partial: Vec::new(), + } + } + } + _ => CompletionContext { + kind: CompletionKind::None, + partial: Vec::new(), + }, } } -#[inline(always)] -fn adv(b: &mut &[u8]) { - *b = &b[1..] +trait Parse: Sized { + fn parse(b: &mut Cursor<'_>) -> Result<Self>; +} + +enum ParseMode { + Command, + Completion, +} + +struct Cursor<'a> { + buf: &'a [u8], + mode: ParseMode, + + /// if the last byte that was consumed was whitespace or part of a word + spaced: bool, } -fn parse_quoted_string(b: &mut &[u8], delim: u8) -> Result<Vec<u8>> { +impl<'a> Cursor<'a> { + fn new(buf: &'a [u8], mode: ParseMode) -> Self { + Self { + buf, + mode, + spaced: false, + } + } + + // non empty + fn has(&self) -> bool { + !self.buf.is_empty() + } + + fn is_empty(&self) -> bool { + self.buf.is_empty() + } + + fn peek(&self) -> u8 { + self.buf[0] + } + + fn adv(&mut self) -> u8 { + let out = self.buf[0]; + self.buf = &self.buf[1..]; + self.spaced = false; + out + } + + fn spaces(&mut self) { + while let Some(b' ' | b'\t') = self.buf.first() { + self.adv(); + self.spaced = true; + } + } + + fn is_completion(&self) -> bool { + match self.mode { + ParseMode::Completion => true, + _ => false, + } + } + + fn parse<T: Parse>(&mut self) -> Result<T> { + T::parse(self) + } +} + +fn parse_quoted_string(b: &mut Cursor<'_>, delim: u8) -> Result<Vec<u8>> { // TODO: escape sequence stuff let mut s = Vec::new(); - while b.len() > 0 { - if b[0] == delim { - adv(b); + while b.has() { + if delim == b' ' && b.peek() == b'|' { + return if s.len() == 0 { + Err(ParseError::UnexpectedPipe) + } else { + Ok(s) + }; + } + + if b.peek() == delim { + b.adv(); + if delim == b' ' { + b.spaced = true; + } return Ok(s); } - s.push(b[0]); - adv(b); + s.push(b.adv()); } - if delim == b' ' { + if delim == b' ' || b.is_completion() { Ok(s) } else { Err(ParseError::Incomplete) @@ -82,16 +181,16 @@ fn parse_quoted_string(b: &mut &[u8], delim: u8) -> Result<Vec<u8>> { } impl Parse for Vec<u8> { - fn parse(b: &mut &[u8]) -> Result<Self> { - spaces(b); + fn parse(b: &mut Cursor<'_>) -> Result<Self> { + b.spaces(); if b.is_empty() { return Err(ParseError::Eof); } - let c = b[0]; + let c = b.peek(); if c == b'|' { Err(ParseError::UnexpectedPipe) } else if c == b'\'' || c == b'"' { - adv(b); + b.adv(); parse_quoted_string(b, c) } else if c.is_ascii_graphic() { parse_quoted_string(b, b' ') @@ -102,17 +201,17 @@ impl Parse for Vec<u8> { } impl Parse for Ast { - fn parse(b: &mut &[u8]) -> Result<Self> { - Ok(Self::Pipes(parse(b)?)) + fn parse(b: &mut Cursor<'_>) -> Result<Self> { + Ok(Self::Pipes(b.parse()?)) } } impl Parse for Command { - fn parse(b: &mut &[u8]) -> Result<Self> { - let path: Vec<u8> = parse(b)?; + fn parse(b: &mut Cursor<'_>) -> Result<Self> { + let path: Vec<u8> = b.parse()?; let mut args = Vec::new(); loop { - let arg: Result<Vec<u8>> = parse(b); + let arg: Result<Vec<u8>> = b.parse(); match arg { Ok(arg) => args.push(arg), Err(ParseError::Eof | ParseError::UnexpectedPipe) => break, @@ -124,19 +223,19 @@ impl Parse for Command { } impl Parse for Pipes { - fn parse(b: &mut &[u8]) -> Result<Self> { - let mut cmds: Vec<Command> = vec![parse(b)?]; + fn parse(b: &mut Cursor<'_>) -> Result<Self> { + let mut cmds: Vec<Command> = vec![b.parse()?]; loop { - spaces(b); + b.spaces(); if b.is_empty() { return Ok(Pipes { cmds }); } - let c = b[0]; + let c = b.peek(); if c == b'|' { - adv(b); - cmds.push(parse(b)?); + b.adv(); + cmds.push(b.parse()?); } else { Err(ParseError::Unknown(c))?; } |
