diff --git a/src/bin/relish.rs b/src/bin/relish.rs
index 9f1ea40..e5b7acd 100644
--- a/src/bin/relish.rs
+++ b/src/bin/relish.rs
@@ -15,25 +15,43 @@
* along with this program. If not, see .
*/
-use relish::ast::{eval, lex, Ctr, Seg, SymTable, run, load_defaults, load_environment};
-use relish::stdlib::{dynamic_stdlib, static_stdlib};
-use relish::aux::ShellState;
-
-use std::cell::RefCell;
-use std::rc::Rc;
-use std::borrow::Cow;
-use std::env;
-
-use nix::unistd;
-use nu_ansi_term::{Color, Style};
-use dirs::home_dir;
-use reedline::{
- FileBackedHistory, DefaultHinter, DefaultValidator, Reedline, Signal,
- Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus,
+use {
+ relish::{
+ ast::{
+ eval, lex, run,
+ Ctr, Seg, SymTable,
+ load_defaults, load_environment
+ },
+ stdlib::{
+ static_stdlib, dynamic_stdlib
+ },
+ aux::ShellState,
+ },
+ std::{
+ cell::RefCell,
+ rc::Rc,
+ borrow::Cow,
+ env,
+ env::current_dir,
+ path::{PathBuf, Path},
+ },
+ reedline::{
+ FileBackedHistory, DefaultHinter, DefaultValidator,
+ Reedline, Signal, Prompt, PromptEditMode, PromptHistorySearch,
+ PromptHistorySearchStatus, Completer, Suggestion, Span,
+ KeyModifiers, KeyCode, ReedlineEvent, Keybindings,
+ ColumnarMenu, Emacs, ReedlineMenu,
+ default_emacs_keybindings,
+ },
+ nix::unistd,
+ nu_ansi_term::{Color, Style},
+ dirs::home_dir,
};
#[derive(Clone)]
pub struct CustomPrompt(String, String, String);
+#[derive(Clone)]
+pub struct CustomCompleter(Vec);
impl Prompt for CustomPrompt {
fn render_prompt_left(&self) -> Cow {
@@ -72,6 +90,127 @@ impl Prompt for CustomPrompt {
}
}
+impl Completer for CustomCompleter {
+ fn complete(&mut self, line: &str, pos: usize) -> Vec {
+ let current_dir_path = current_dir()
+ .expect("current dir bad?");
+ let (tok, is_str, start) = get_token_to_complete(line, pos);
+ let mut sugg = vec![];
+ if !is_str {
+ let mut offcenter_match = vec![];
+ for sym in &self.0 {
+ if sym.starts_with(tok.as_str()) {
+ sugg.push(Suggestion {
+ value: sym.clone(),
+ description: None,
+ extra: None,
+ append_whitespace: false,
+ span: Span {
+ start,
+ end: pos
+ }
+ });
+ }
+
+ if sym.contains(tok.as_str()) {
+ offcenter_match.push(sym.clone());
+ }
+ }
+
+ for i in offcenter_match {
+ sugg.push(Suggestion {
+ value: i.clone(),
+ description: None,
+ extra: None,
+ append_whitespace: false,
+ span: Span {
+ start,
+ end: pos
+ }
+ });
+ }
+ } else {
+ let mut path: PathBuf = Path::new(&tok).to_path_buf();
+ if path.is_relative() {
+ path = current_dir_path.join(path);
+ }
+
+ if let Ok(npath) = path.canonicalize() {
+ path = npath;
+ }
+
+ if path.exists() && path.is_dir() {
+ if let Ok(entries) = path.read_dir() {
+ for entry in entries {
+ if let Ok(e) = entry {
+ sugg.push(Suggestion {
+ value: e.path()
+ .as_os_str()
+ .to_str()
+ .expect("bad dir somehow?")
+ .to_owned(),
+ description: None,
+ extra: None,
+ append_whitespace: false,
+ span: Span {
+ start,
+ end: pos
+ }
+ });
+ }
+ }
+ }
+ } else {
+ // check parent to autocomplete path
+ if let Some(parent) = path.parent() {
+ if let Ok(entries) = parent.read_dir() {
+ for entry in entries {
+ if let Ok(e) = entry {
+ if e.file_name()
+ .to_string_lossy()
+ .starts_with(path.file_name()
+ // TODO: dont
+ .expect("bad file somehow?")
+ .to_string_lossy()
+ .to_mut()
+ .as_str()
+ ) {
+ sugg.push(Suggestion {
+ value: e.path()
+ .as_os_str()
+ .to_str()
+ .expect("bad dir somehow?")
+ .to_owned(),
+ description: None,
+ extra: None,
+ append_whitespace: false,
+ span: Span {
+ start,
+ end: pos
+ }
+ });
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ sugg
+ }
+}
+
+fn add_menu_keybindings(keybindings: &mut Keybindings) {
+ keybindings.add_binding(
+ KeyModifiers::NONE,
+ KeyCode::Tab,
+ ReedlineEvent::UntilFound(vec![
+ ReedlineEvent::Menu("completion_menu".to_string()),
+ ReedlineEvent::MenuNext,
+ ]),
+ );
+}
+
fn main() {
const HIST_FILE: &str = "/.relish_hist";
const CONFIG_FILE_DEFAULT: &str = "/.relishrc";
@@ -117,12 +256,25 @@ fn main() {
dynamic_stdlib(&mut syms, Some(shell_state_bindings)).unwrap_or_else(|err: String| eprintln!("{}", err));
// setup readline
- let mut rl = Reedline::create();
+
+ let completion_menu = Box::new(ColumnarMenu::default().with_name("completion_menu"));
+ let mut keybindings = default_emacs_keybindings();
+ add_menu_keybindings(&mut keybindings);
+ let edit_mode = Box::new(Emacs::new(keybindings));
+ let mut rl = Reedline::create()
+ .with_menu(ReedlineMenu::EngineCompleter(completion_menu))
+ .with_edit_mode(edit_mode)
+ .with_quick_completions(true)
+ .with_partial_completions(true)
+ .with_ansi_colors(true);
+
let maybe_hist: Box;
if !hist_file_name.is_empty() {
- maybe_hist = Box::new(FileBackedHistory::with_file(5000, hist_file_name.into())
- .expect("error reading history!"));
- rl = rl.with_history(maybe_hist);
+ maybe_hist = Box::new(
+ FileBackedHistory::with_file(5000, hist_file_name.into())
+ .expect("error reading history!")
+ );
+ rl = rl.with_history(maybe_hist);
}
rl = rl.with_hinter(Box::new(
DefaultHinter::default()
@@ -132,6 +284,9 @@ fn main() {
// repl :)
loop {
let readline_prompt = make_prompt(&mut syms);
+ let completer = make_completer(&mut syms);
+ // realloc with each loop because syms can change
+ rl = rl.with_completer(Box::new(completer));
let user_doc = rl.read_line(&readline_prompt).unwrap();
match user_doc {
Signal::Success(line) => {
@@ -199,3 +354,41 @@ fn make_prompt(syms: &mut SymTable) -> CustomPrompt {
CustomPrompt(l_str, r_str, d_str)
}
+
+fn make_completer(syms: &mut SymTable) -> CustomCompleter {
+ let mut toks = vec![];
+ for i in syms.keys(){
+ toks.push(i.clone());
+ }
+ CustomCompleter(toks)
+}
+
+fn get_token_to_complete(line: &str, pos: usize) -> (String, bool, usize) {
+ let mut res = String::with_capacity(1);
+ let mut doc = line.chars().rev();
+ for _ in pos..line.len() {
+ doc.next();
+ }
+ let mut is_str = false;
+ let mut start_idx = pos;
+ for idx in (0..pos).rev() {
+ let iter = doc.next()
+ .expect(format!("AC: no idx {}", idx)
+ .as_str());
+ match iter {
+ ' ' | '(' | '\n' => {
+ break
+ },
+ '\'' | '"' => {
+ is_str = true;
+ break
+ },
+ _ => {
+ start_idx = idx;
+ res.insert(0, iter);
+ }
+ }
+ }
+
+ return (res, is_str, start_idx)
+}
diff --git a/src/sym.rs b/src/sym.rs
index 114b18b..9104d20 100644
--- a/src/sym.rs
+++ b/src/sym.rs
@@ -96,6 +96,10 @@ impl SymTable {
self.0.iter()
}
+ pub fn keys(&self) -> std::collections::hash_map::Keys {
+ self.0.keys()
+ }
+
pub fn update(&mut self, other: &mut SymTable) {
/* updates self with all syms in other that match the following cases:
* * sym is not in self