aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml6
-rw-r--r--man/rs.1.scd3
-rw-r--r--src/commandline.rs6
-rw-r--r--src/commandline/builtins.rs7
-rw-r--r--src/commandline/command.rs14
-rw-r--r--src/completer.rs160
-rw-r--r--src/main.rs21
7 files changed, 196 insertions, 21 deletions
diff --git a/Cargo.toml b/Cargo.toml
index e455a3c..f894eda 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,6 +9,8 @@ edition = "2018"
# https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+nix = "0.19.1"
libc = "0.2"
-rustyline = "6.3.0"
-
+rustyline = "7.0.0"
+rustyline-derive = "0.4.0"
+radix_trie = "0.2"
diff --git a/man/rs.1.scd b/man/rs.1.scd
index c9c423e..e5e24f7 100644
--- a/man/rs.1.scd
+++ b/man/rs.1.scd
@@ -51,6 +51,9 @@ is used by default.
- {$} is replaced by '#' for root and '$' for other users
- {PWD} is replaced by current directory path
+## Autocompletion
+Executables in $PATH are added to autocompletion.
+
# AUTHORS
Maintained by Aqua <aqua@iserlohn-fortress.net>. The source code can be found
at https://neueland.iserlohn-fortress.net/cgit/rshell/.
diff --git a/src/commandline.rs b/src/commandline.rs
index 489eaa8..0737b75 100644
--- a/src/commandline.rs
+++ b/src/commandline.rs
@@ -17,7 +17,11 @@ impl CommandLine {
CommandLine(split(line))
}
- pub fn run(&self,home: &PathBuf, mut status: Option<ExitStatus>) -> Result<Option<ExitStatus>, Error> {
+ pub fn run(
+ &self,
+ home: &PathBuf,
+ mut status: Option<ExitStatus>,
+ ) -> Result<Option<ExitStatus>, Error> {
for cmd in &self.0 {
if cmd.when.can_run(&status) {
match cmd.run(&home, &status)? {
diff --git a/src/commandline/builtins.rs b/src/commandline/builtins.rs
index c5999f2..882a690 100644
--- a/src/commandline/builtins.rs
+++ b/src/commandline/builtins.rs
@@ -1,13 +1,10 @@
+use super::command::RunResult;
use std::io::{Error, ErrorKind::InvalidInput};
use std::path::{Path, PathBuf};
-use super::command::RunResult;
pub(in crate::commandline) fn cd(args: &[String], home: &PathBuf) -> Result<RunResult, Error> {
if args.len() > 1 {
- return Err(Error::new(
- InvalidInput,
- "Too many arguments passed to cd",
- ));
+ return Err(Error::new(InvalidInput, "Too many arguments passed to cd"));
}
let root = if args.len() == 0 {
diff --git a/src/commandline/command.rs b/src/commandline/command.rs
index d00f420..c8b326d 100644
--- a/src/commandline/command.rs
+++ b/src/commandline/command.rs
@@ -1,8 +1,8 @@
-use std::io::{Error};
-use std::path::{PathBuf};
-use std::process::ExitStatus;
-use std::process::Command as Process;
use super::builtins::{cd, set, unset};
+use std::io::Error;
+use std::path::PathBuf;
+use std::process::Command as Process;
+use std::process::ExitStatus;
// > overwrite
// >> append
@@ -16,7 +16,11 @@ enum Redirect {
}
#[derive(Debug, PartialEq, Copy, Clone)]
-pub(in crate::commandline) enum RunIf { Always, ExitSuccess, ExitFailure }
+pub(in crate::commandline) enum RunIf {
+ Always,
+ ExitSuccess,
+ ExitFailure,
+}
impl RunIf {
pub(in crate::commandline) fn can_run(&self, status: &Option<ExitStatus>) -> bool {
diff --git a/src/completer.rs b/src/completer.rs
new file mode 100644
index 0000000..6bf717b
--- /dev/null
+++ b/src/completer.rs
@@ -0,0 +1,160 @@
+use nix::sys::stat::Mode;
+use radix_trie::{Trie, TrieCommon};
+use rustyline::hint::{Hint, Hinter};
+use rustyline::Context;
+use rustyline_derive::{Completer, Helper, Highlighter, Validator};
+use std::ffi::OsString;
+use std::os::unix::fs::PermissionsExt;
+
+static EXE_MASK: Mode = Mode::from_bits_truncate(0o111);
+#[test]
+fn test_exe_mask() {
+ let a = Mode::from_bits_truncate(0o100755);
+ assert!(a.contains(EXE_MASK));
+}
+
+#[derive(Completer, Helper, Validator, Highlighter)]
+pub struct CommandHinter {
+ cmd_hints: Trie<String, CompleterHint>,
+ path_hints: Trie<String, CompleterHint>,
+}
+
+#[derive(Hash, Debug, PartialEq, Eq)]
+pub struct CompleterHint {
+ display: String,
+ complete_up_to: usize,
+}
+
+impl Hint for CompleterHint {
+ fn display(&self) -> &str {
+ &self.display
+ }
+
+ fn completion(&self) -> Option<&str> {
+ if self.complete_up_to > 0 {
+ Some(&self.display[..self.complete_up_to])
+ } else {
+ None
+ }
+ }
+}
+
+impl CompleterHint {
+ fn new(text: &str, complete_up_to: &str) -> CompleterHint {
+ assert!(text.starts_with(complete_up_to));
+ CompleterHint {
+ display: text.into(),
+ complete_up_to: complete_up_to.len(),
+ }
+ }
+
+ fn from_path(text: OsString) -> CompleterHint {
+ CompleterHint {
+ complete_up_to: text.len(),
+ display: text.into_string().unwrap(),
+ }
+ }
+
+ fn suffix(&self, strip_chars: usize) -> CompleterHint {
+ CompleterHint {
+ display: self.display[strip_chars..].to_owned(),
+ complete_up_to: self.complete_up_to.saturating_sub(strip_chars),
+ }
+ }
+}
+
+impl CommandHinter {
+ pub fn new() -> Result<CommandHinter, std::io::Error> {
+ let mut cmd_hints = Trie::new();
+ let path_hints = Trie::new();
+
+ for path in std::env::var("PATH").unwrap_or_default().split(":") {
+ #[cfg(debug_assertions)]
+ println!("+index {}", path);
+
+ if let Ok(entries) = std::fs::read_dir(path) {
+ for x in entries {
+ if let Ok(entry) = x {
+ let mode = Mode::from_bits_truncate(entry.metadata()?.permissions().mode());
+ if mode.contains(EXE_MASK) {
+ cmd_hints.insert(
+ entry.file_name().into_string().unwrap(),
+ CompleterHint::from_path(entry.file_name()),
+ );
+ }
+ }
+ }
+ }
+ }
+
+ Ok(CommandHinter {
+ cmd_hints,
+ path_hints,
+ })
+ }
+
+ pub fn set_cwd(&mut self, path: &std::path::PathBuf) {
+ self.path_hints = Trie::new();
+ if let Ok(entries) = std::fs::read_dir(path) {
+ for x in entries {
+ if let Ok(entry) = x {
+ self.path_hints.insert(
+ entry.file_name().into_string().unwrap(),
+ CompleterHint::from_path(entry.file_name()),
+ );
+ }
+ }
+ }
+ }
+}
+
+fn hints(
+ trie: &Trie<String, CompleterHint>,
+ line: &str,
+ pos: usize,
+) -> Option<std::collections::VecDeque<CompleterHint>> {
+ match trie.get_raw_descendant(line) {
+ Some(subtrie) => Some(
+ subtrie
+ .values()
+ .filter_map(|hint| {
+ if pos > 0 && hint.display.starts_with(&line[..pos]) {
+ Some(hint.suffix(pos))
+ } else {
+ None
+ }
+ })
+ .collect(),
+ ),
+ None => None,
+ }
+}
+
+impl Hinter for CommandHinter {
+ type Hint = CompleterHint;
+
+ /// Takes the currently edited `line` with the cursor `pos`ition and
+ /// returns the string that should be displayed or `None`
+ /// if no hint is available for the text the user currently typed.
+ fn hint(&self, line: &str, pos: usize, _ctx: &Context<'_>) -> Option<CompleterHint> {
+ if line.is_empty() {
+ return None;
+ }
+
+ if pos < line.len() {
+ return None;
+ }
+
+ if let Some(mut idx) = line.rfind(' ') {
+ idx += 1;
+ if pos <= idx {
+ return None;
+ }
+
+ return hints(&self.path_hints, &line[idx..], pos - idx)
+ .and_then(|mut l| l.pop_front());
+ }
+
+ hints(&self.cmd_hints, line, pos).and_then(|mut l| l.pop_front())
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index ca5dc00..118d2e2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,31 +2,36 @@ use rustyline::error::ReadlineError;
use rustyline::Editor;
mod commandline;
-use commandline::CommandLine;
+mod completer;
mod prompt;
-fn main() -> Result<(), std::io::Error>{
+fn main() -> Result<(), std::io::Error> {
if let Ok(motd) = std::fs::read_to_string("/etc/motd") {
print!("{}", motd)
}
- // TODO: [completer] `()` can be used when no completer is required
- let mut rl = Editor::<()>::new();
- let prompt = prompt::Prompt::new()?;
+ let mut status = None; // exit status of last command
+ // map of variables
+
+ let hinter = completer::CommandHinter::new()?;
+ let mut rl = Editor::<completer::CommandHinter>::new();
+ rl.set_helper(Some(hinter));
- let mut status = None; // exit status of last command
- // map of variables
+ let prompt = prompt::Prompt::new()?;
/*if rl.load_history("history.txt").is_err() {
println!("No previous history.");
}*/
'repl: loop {
+ let cwd = std::env::current_dir()?;
+ rl.helper_mut().unwrap().set_cwd(&cwd);
+
match rl.readline(&prompt.print()) {
Ok(line) => {
rl.add_history_entry(line.as_str());
- let cmd = CommandLine::new(&line);
+ let cmd = commandline::CommandLine::new(&line);
match cmd.run(&prompt.home, status) {
Ok(s) => status = s,
Err(e) => eprintln!("{}", e),