tab complete! yay!
Signed-off-by: Ava Hahn <ava@sunnypup.io>
This commit is contained in:
parent
c2cc8571bc
commit
1ef0a534db
2 changed files with 216 additions and 19 deletions
|
|
@ -15,25 +15,43 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String>);
|
||||
|
||||
impl Prompt for CustomPrompt {
|
||||
fn render_prompt_left(&self) -> Cow<str> {
|
||||
|
|
@ -72,6 +90,127 @@ impl Prompt for CustomPrompt {
|
|||
}
|
||||
}
|
||||
|
||||
impl Completer for CustomCompleter {
|
||||
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||
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<FileBackedHistory>;
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,6 +96,10 @@ impl SymTable {
|
|||
self.0.iter()
|
||||
}
|
||||
|
||||
pub fn keys(&self) -> std::collections::hash_map::Keys<String, Symbol> {
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue