Early work on tab completion
This commit is contained in:
@@ -1,6 +1,4 @@
|
|||||||
use std::os::fd::AsRawFd;
|
use crate::{expand::{arithmetic::expand_arith_string, tilde::expand_tilde_string, vars::expand_string}, prelude::*};
|
||||||
|
|
||||||
use crate::{expand::{arithmetic::expand_arith_string, tilde::expand_tilde_string, vars::{expand_string, expand_var}}, prelude::*};
|
|
||||||
use shellenv::jobs::{ChildProc, JobBldr};
|
use shellenv::jobs::{ChildProc, JobBldr};
|
||||||
|
|
||||||
pub mod shellcmd;
|
pub mod shellcmd;
|
||||||
@@ -23,6 +21,7 @@ pub fn exec_input<S: Into<String>>(input: S, shenv: &mut ShEnv) -> ShResult<()>
|
|||||||
|
|
||||||
let parse_time = std::time::Instant::now();
|
let parse_time = std::time::Instant::now();
|
||||||
let syn_tree = Parser::new(token_stream,shenv).parse()?;
|
let syn_tree = Parser::new(token_stream,shenv).parse()?;
|
||||||
|
log!(TRACE,syn_tree);
|
||||||
log!(INFO, "Parsing done in {:?}", parse_time.elapsed());
|
log!(INFO, "Parsing done in {:?}", parse_time.elapsed());
|
||||||
if !shenv.ctx().flags().contains(ExecFlags::IN_FUNC) {
|
if !shenv.ctx().flags().contains(ExecFlags::IN_FUNC) {
|
||||||
shenv.save_io()?;
|
shenv.save_io()?;
|
||||||
@@ -79,8 +78,6 @@ fn exec_list(list: Vec<(Option<CmdGuard>, Node)>, shenv: &mut ShEnv) -> ShResult
|
|||||||
while let Some(cmd_info) = list.fpop() {
|
while let Some(cmd_info) = list.fpop() {
|
||||||
let guard = cmd_info.0;
|
let guard = cmd_info.0;
|
||||||
let cmd = cmd_info.1;
|
let cmd = cmd_info.1;
|
||||||
let span = cmd.span();
|
|
||||||
let cmd_raw = cmd.as_raw(shenv);
|
|
||||||
|
|
||||||
if let Some(guard) = guard {
|
if let Some(guard) = guard {
|
||||||
let code = shenv.get_code();
|
let code = shenv.get_code();
|
||||||
@@ -122,7 +119,9 @@ fn dispatch_command(mut node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
let mut is_subsh = false;
|
let mut is_subsh = false;
|
||||||
let mut is_assign = false;
|
let mut is_assign = false;
|
||||||
if let NdRule::Command { ref mut argv, redirs: _ } = node.rule_mut() {
|
if let NdRule::Command { ref mut argv, redirs: _ } = node.rule_mut() {
|
||||||
|
if !shenv.ctx().flags().contains(ExecFlags::NO_EXPAND) {
|
||||||
*argv = expand_argv(argv.to_vec(), shenv)?;
|
*argv = expand_argv(argv.to_vec(), shenv)?;
|
||||||
|
}
|
||||||
let cmd = argv.first().unwrap().as_raw(shenv);
|
let cmd = argv.first().unwrap().as_raw(shenv);
|
||||||
if shenv.logic().get_function(&cmd).is_some() {
|
if shenv.logic().get_function(&cmd).is_some() {
|
||||||
is_func = true;
|
is_func = true;
|
||||||
@@ -130,7 +129,9 @@ fn dispatch_command(mut node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
is_builtin = true;
|
is_builtin = true;
|
||||||
}
|
}
|
||||||
} else if let NdRule::Subshell { body: _, ref mut argv, redirs: _ } = node.rule_mut() {
|
} else if let NdRule::Subshell { body: _, ref mut argv, redirs: _ } = node.rule_mut() {
|
||||||
|
if !shenv.ctx().flags().contains(ExecFlags::NO_EXPAND) {
|
||||||
*argv = expand_argv(argv.to_vec(), shenv)?;
|
*argv = expand_argv(argv.to_vec(), shenv)?;
|
||||||
|
}
|
||||||
is_subsh = true;
|
is_subsh = true;
|
||||||
} else if let NdRule::Assignment { assignments: _, cmd: _ } = node.rule() {
|
} else if let NdRule::Assignment { assignments: _, cmd: _ } = node.rule() {
|
||||||
is_assign = true;
|
is_assign = true;
|
||||||
@@ -508,5 +509,6 @@ fn prep_execve(argv: Vec<Token>, shenv: &mut ShEnv) -> (Vec<String>, Vec<String>
|
|||||||
envp.push(formatted);
|
envp.push(formatted);
|
||||||
}
|
}
|
||||||
log!(TRACE, argv_s);
|
log!(TRACE, argv_s);
|
||||||
|
log!(DEBUG, argv_s);
|
||||||
(argv_s, envp)
|
(argv_s, envp)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,15 @@ pub fn expand_aliases(tokens: Vec<Token>, shenv: &mut ShEnv) -> Vec<Token> {
|
|||||||
is_command = true;
|
is_command = true;
|
||||||
processed.push(token.clone());
|
processed.push(token.clone());
|
||||||
}
|
}
|
||||||
|
TkRule::Case | TkRule::For => {
|
||||||
|
processed.push(token.clone());
|
||||||
|
while let Some(token) = stream.next() {
|
||||||
|
processed.push(token.clone());
|
||||||
|
if token.rule() == TkRule::Sep {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
TkRule::Ident if is_command => {
|
TkRule::Ident if is_command => {
|
||||||
is_command = false;
|
is_command = false;
|
||||||
let mut alias_tokens = expand_alias(token.clone(), shenv);
|
let mut alias_tokens = expand_alias(token.clone(), shenv);
|
||||||
|
|||||||
1
src/filefilefile.txt
Normal file
1
src/filefilefile.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
foobar
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::{fmt::Display, os::fd::AsRawFd};
|
use std::{fmt::Display, os::{fd::AsRawFd, unix::fs::PermissionsExt}};
|
||||||
|
|
||||||
use nix::sys::termios;
|
use nix::sys::termios;
|
||||||
|
|
||||||
@@ -6,10 +6,42 @@ use crate::prelude::*;
|
|||||||
|
|
||||||
pub const SIG_EXIT_OFFSET: i32 = 128;
|
pub const SIG_EXIT_OFFSET: i32 = 128;
|
||||||
|
|
||||||
|
pub fn get_path_cmds() -> ShResult<Vec<String>> {
|
||||||
|
let mut cmds = vec![];
|
||||||
|
let path_var = std::env::var("PATH")?;
|
||||||
|
let paths = path_var.split(':');
|
||||||
|
|
||||||
|
for path in paths {
|
||||||
|
let path = PathBuf::from(&path);
|
||||||
|
if path.is_dir() {
|
||||||
|
let path_files = std::fs::read_dir(&path)?;
|
||||||
|
for file in path_files {
|
||||||
|
let file_path = file?.path();
|
||||||
|
if file_path.is_file() {
|
||||||
|
if let Ok(meta) = std::fs::metadata(&file_path) {
|
||||||
|
let perms = meta.permissions();
|
||||||
|
if perms.mode() & 0o111 != 0 {
|
||||||
|
let file_name = file_path.file_name().unwrap();
|
||||||
|
cmds.push(file_name.to_str().unwrap().to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(cmds)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_bin_path(command: &str, shenv: &ShEnv) -> Option<PathBuf> {
|
pub fn get_bin_path(command: &str, shenv: &ShEnv) -> Option<PathBuf> {
|
||||||
let env = shenv.vars().env();
|
let env = shenv.vars().env();
|
||||||
let path_var = env.get("PATH")?;
|
let path_var = env.get("PATH")?;
|
||||||
let mut paths = path_var.split(':');
|
let mut paths = path_var.split(':');
|
||||||
|
|
||||||
|
let script_check = PathBuf::from(command);
|
||||||
|
if script_check.is_file() {
|
||||||
|
return Some(script_check)
|
||||||
|
}
|
||||||
while let Some(raw_path) = paths.next() {
|
while let Some(raw_path) = paths.next() {
|
||||||
let mut path = PathBuf::from(raw_path);
|
let mut path = PathBuf::from(raw_path);
|
||||||
path.push(command);
|
path.push(command);
|
||||||
|
|||||||
66
src/main.rs
66
src/main.rs
@@ -1,4 +1,4 @@
|
|||||||
#![allow(unused_unsafe)]
|
#![allow(static_mut_refs,unused_unsafe)]
|
||||||
|
|
||||||
pub mod libsh;
|
pub mod libsh;
|
||||||
pub mod shellenv;
|
pub mod shellenv;
|
||||||
@@ -20,6 +20,14 @@ use crate::prelude::*;
|
|||||||
|
|
||||||
pub static mut SAVED_TERMIOS: Option<Option<Termios>> = None;
|
pub static mut SAVED_TERMIOS: Option<Option<Termios>> = None;
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
pub struct FernFlags: u32 {
|
||||||
|
const NO_RC = 0b000001;
|
||||||
|
const NO_HIST = 0b000010;
|
||||||
|
const INTERACTIVE = 0b000100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn save_termios() {
|
pub fn save_termios() {
|
||||||
unsafe {
|
unsafe {
|
||||||
SAVED_TERMIOS = Some(if isatty(std::io::stdin().as_raw_fd()).unwrap() {
|
SAVED_TERMIOS = Some(if isatty(std::io::stdin().as_raw_fd()).unwrap() {
|
||||||
@@ -34,6 +42,8 @@ pub fn save_termios() {
|
|||||||
}
|
}
|
||||||
pub fn get_saved_termios() -> Option<Termios> {
|
pub fn get_saved_termios() -> Option<Termios> {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
// This is only used when the shell exits so it's fine
|
||||||
|
// SAVED_TERMIOS is only mutated once at the start as well
|
||||||
SAVED_TERMIOS.clone().flatten()
|
SAVED_TERMIOS.clone().flatten()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,22 +54,68 @@ fn set_termios() {
|
|||||||
termios::tcsetattr(std::io::stdin(), nix::sys::termios::SetArg::TCSANOW, &termios).unwrap();
|
termios::tcsetattr(std::io::stdin(), nix::sys::termios::SetArg::TCSANOW, &termios).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn parse_args(shenv: &mut ShEnv) {
|
||||||
|
let mut args = std::env::args().skip(1);
|
||||||
|
let mut script_path: Option<PathBuf> = None;
|
||||||
|
let mut command: Option<String> = None;
|
||||||
|
let mut flags = FernFlags::empty();
|
||||||
|
|
||||||
|
log!(DEBUG, args);
|
||||||
|
while let Some(mut arg) = args.next() {
|
||||||
|
log!(DEBUG, arg);
|
||||||
|
if arg.starts_with("--") {
|
||||||
|
arg = arg.strip_prefix("--").unwrap().to_string();
|
||||||
|
match arg.as_str() {
|
||||||
|
"no-rc" => flags |= FernFlags::NO_RC,
|
||||||
|
"no-hist" => flags |= FernFlags::NO_HIST,
|
||||||
|
_ => eprintln!("Warning - Unrecognized option: {arg}")
|
||||||
|
}
|
||||||
|
} else if arg.starts_with('-') {
|
||||||
|
arg = arg.strip_prefix('-').unwrap().to_string();
|
||||||
|
match arg.as_str() {
|
||||||
|
"c" => command = args.next(),
|
||||||
|
_ => eprintln!("Warning - Unrecognized option: {arg}")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let path_check = PathBuf::from(&arg);
|
||||||
|
if path_check.is_file() {
|
||||||
|
script_path = Some(path_check);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !flags.contains(FernFlags::NO_RC) {
|
||||||
|
let _ = shenv.source_rc().eprint();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(cmd) = command {
|
||||||
|
let input = clean_string(cmd);
|
||||||
|
let _ = exec_input(input, shenv).eprint();
|
||||||
|
|
||||||
|
} else if let Some(script) = script_path {
|
||||||
|
let _ = shenv.source_file(script).eprint();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
interactive(shenv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
sig_setup();
|
sig_setup();
|
||||||
save_termios();
|
save_termios();
|
||||||
set_termios();
|
set_termios();
|
||||||
let mut shenv = ShEnv::new();
|
let mut shenv = ShEnv::new();
|
||||||
if let Err(e) = shenv.source_rc() {
|
|
||||||
eprintln!("Error sourcing rc file: {}", e.to_string());
|
parse_args(&mut shenv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn interactive(shenv: &mut ShEnv) {
|
||||||
loop {
|
loop {
|
||||||
log!(TRACE, "Entered loop");
|
log!(TRACE, "Entered loop");
|
||||||
match prompt::read_line(&mut shenv) {
|
match prompt::read_line(shenv) {
|
||||||
Ok(line) => {
|
Ok(line) => {
|
||||||
shenv.meta_mut().start_timer();
|
shenv.meta_mut().start_timer();
|
||||||
let _ = exec_input(line, &mut shenv).eprint();
|
let _ = exec_input(line, shenv).eprint();
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("{}",e);
|
eprintln!("{}",e);
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ impl<'a> Lexer<'a> {
|
|||||||
rule = TkRule::Ident
|
rule = TkRule::Ident
|
||||||
|
|
||||||
// If we are in a command right now, after this we are in arguments
|
// If we are in a command right now, after this we are in arguments
|
||||||
} else if self.is_command && rule != TkRule::Whitespace && !KEYWORDS.contains(&rule) {
|
} else if self.is_command && !matches!(rule, TkRule::Comment | TkRule::Whitespace) && !KEYWORDS.contains(&rule) {
|
||||||
self.is_command = false;
|
self.is_command = false;
|
||||||
}
|
}
|
||||||
// If we see a separator like && or ;, we are now in a command again
|
// If we see a separator like && or ;, we are now in a command again
|
||||||
@@ -295,7 +295,7 @@ impl TkRule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tkrule_def!(Comment, |input: &str| {
|
tkrule_def!(Comment, |input: &str| {
|
||||||
let mut chars = input.chars();
|
let mut chars = input.chars().peekable();
|
||||||
let mut len = 0;
|
let mut len = 0;
|
||||||
|
|
||||||
if let Some('#') = chars.next() {
|
if let Some('#') = chars.next() {
|
||||||
@@ -304,6 +304,14 @@ tkrule_def!(Comment, |input: &str| {
|
|||||||
let chlen = ch.len_utf8();
|
let chlen = ch.len_utf8();
|
||||||
len += chlen;
|
len += chlen;
|
||||||
if ch == '\n' {
|
if ch == '\n' {
|
||||||
|
while let Some(ch) = chars.peek() {
|
||||||
|
if *ch == '\n' {
|
||||||
|
len += 1;
|
||||||
|
chars.next();
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -743,31 +751,53 @@ tkrule_def!(SQuote, |input: &str| {
|
|||||||
// Double quoted strings
|
// Double quoted strings
|
||||||
let mut chars = input.chars();
|
let mut chars = input.chars();
|
||||||
let mut len = 0;
|
let mut len = 0;
|
||||||
let mut quoted = false;
|
let mut quote_count = 0;
|
||||||
|
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
match ch {
|
match ch {
|
||||||
'\\' => {
|
'\\' => {
|
||||||
chars.next();
|
len += 1;
|
||||||
len += 2;
|
if let Some(ch) = chars.next() {
|
||||||
}
|
|
||||||
'\'' if !quoted => {
|
|
||||||
let chlen = ch.len_utf8();
|
let chlen = ch.len_utf8();
|
||||||
len += chlen;
|
len += chlen;
|
||||||
quoted = true;
|
|
||||||
}
|
}
|
||||||
'\'' if quoted => {
|
}
|
||||||
|
'\'' => {
|
||||||
let chlen = ch.len_utf8();
|
let chlen = ch.len_utf8();
|
||||||
len += chlen;
|
len += chlen;
|
||||||
|
quote_count += 1;
|
||||||
|
}
|
||||||
|
' ' | '\t' | ';' | '\n' if quote_count % 2 == 0 => {
|
||||||
|
if quote_count > 0 {
|
||||||
|
if quote_count % 2 == 0 {
|
||||||
return Some(len)
|
return Some(len)
|
||||||
}
|
} else {
|
||||||
_ if !quoted => {
|
return None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let chlen = ch.len_utf8();
|
||||||
|
len += chlen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match len {
|
||||||
|
0 => None,
|
||||||
|
_ => {
|
||||||
|
if quote_count > 0 {
|
||||||
|
if quote_count % 2 == 0 {
|
||||||
|
return Some(len)
|
||||||
|
} else {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
return None
|
return None
|
||||||
}
|
}
|
||||||
_ => len += 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
|
||||||
});
|
});
|
||||||
|
|
||||||
tkrule_def!(DQuote, |input: &str| {
|
tkrule_def!(DQuote, |input: &str| {
|
||||||
|
|||||||
@@ -158,18 +158,18 @@ impl<'a> Parser<'a> {
|
|||||||
|
|
||||||
pub fn parse(mut self) -> ShResult<SynTree> {
|
pub fn parse(mut self) -> ShResult<SynTree> {
|
||||||
log!(TRACE, "Starting parse");
|
log!(TRACE, "Starting parse");
|
||||||
let mut lists = VecDeque::new();
|
let mut lists = vec![];
|
||||||
let token_slice = &*self.token_stream;
|
let token_slice = &*self.token_stream;
|
||||||
// Get the Main rule
|
// Get the Main rule
|
||||||
if let Some(mut node) = Main::try_match(token_slice,self.shenv)? {
|
if let Some(mut node) = Main::try_match(token_slice,self.shenv)? {
|
||||||
// Extract the inner lists
|
// Extract the inner lists
|
||||||
if let NdRule::Main { ref mut cmd_lists } = node.rule_mut() {
|
if let NdRule::Main { ref mut cmd_lists } = node.rule_mut() {
|
||||||
while let Some(node) = cmd_lists.pop() {
|
while let Some(node) = cmd_lists.pop() {
|
||||||
lists.bpush(node)
|
lists.push(node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while let Some(node) = lists.bpop() {
|
while let Some(node) = lists.pop() {
|
||||||
// Push inner command lists to self.ast
|
// Push inner command lists to self.ast
|
||||||
self.ast.push_node(node);
|
self.ast.push_node(node);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ pub use crate::{
|
|||||||
},
|
},
|
||||||
sys::{
|
sys::{
|
||||||
self,
|
self,
|
||||||
|
get_path_cmds,
|
||||||
get_bin_path,
|
get_bin_path,
|
||||||
sh_quit,
|
sh_quit,
|
||||||
read_to_string,
|
read_to_string,
|
||||||
|
|||||||
86
src/prompt/comp.rs
Normal file
86
src/prompt/comp.rs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
use rustyline::completion::{Candidate, Completer};
|
||||||
|
|
||||||
|
use crate::{expand::cmdsub::expand_cmdsub_string, parse::lex::KEYWORDS, prelude::*};
|
||||||
|
|
||||||
|
use super::readline::SynHelper;
|
||||||
|
|
||||||
|
impl<'a> Completer for SynHelper<'a> {
|
||||||
|
type Candidate = String;
|
||||||
|
fn complete( &self, line: &str, pos: usize, ctx: &rustyline::Context<'_>,) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
|
||||||
|
let mut shenv = self.shenv.clone();
|
||||||
|
let mut comps = vec![];
|
||||||
|
shenv.new_input(line);
|
||||||
|
let mut token_stream = Lexer::new(line.to_string(), &mut shenv).lex();
|
||||||
|
if let Some(comp_token) = token_stream.pop() {
|
||||||
|
let raw = comp_token.as_raw(&mut shenv);
|
||||||
|
let is_cmd = if let Some(token) = token_stream.pop() {
|
||||||
|
match token.rule() {
|
||||||
|
TkRule::Sep => true,
|
||||||
|
_ if KEYWORDS.contains(&token.rule()) => true,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
};
|
||||||
|
if let TkRule::Ident | TkRule::Whitespace = comp_token.rule() {
|
||||||
|
if is_cmd {
|
||||||
|
let cmds = shenv.meta().path_cmds();
|
||||||
|
comps.extend(cmds.iter().map(|cmd| cmd.to_string()));
|
||||||
|
comps.retain(|cmd| cmd.starts_with(&raw));
|
||||||
|
if !comps.is_empty() && comps.len() > 1 {
|
||||||
|
if get_bin_path("fzf", &self.shenv).is_some() {
|
||||||
|
if let Some(mut selection) = fzf_comp(&comps, &mut shenv) {
|
||||||
|
while selection.starts_with(&raw) {
|
||||||
|
selection = selection.strip_prefix(&raw).unwrap().to_string();
|
||||||
|
}
|
||||||
|
comps = vec![selection];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(mut comp) = comps.pop() {
|
||||||
|
while comp.starts_with(&raw) {
|
||||||
|
comp = comp.strip_prefix(&raw).unwrap().to_string();
|
||||||
|
}
|
||||||
|
comps = vec![comp];
|
||||||
|
}
|
||||||
|
return Ok((pos,comps))
|
||||||
|
} else {
|
||||||
|
let (start, matches) = self.file_comp.complete(line, pos, ctx)?;
|
||||||
|
comps.extend(matches.iter().map(|c| c.display().to_string()));
|
||||||
|
|
||||||
|
if !comps.is_empty() && comps.len() > 1 {
|
||||||
|
if get_bin_path("fzf", &self.shenv).is_some() {
|
||||||
|
if let Some(selection) = fzf_comp(&comps, &mut shenv) {
|
||||||
|
return Ok((start, vec![selection]))
|
||||||
|
} else {
|
||||||
|
return Ok((start, comps))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Ok((start, comps))
|
||||||
|
}
|
||||||
|
} else if let Some(comp) = comps.pop() {
|
||||||
|
// Slice off the already typed bit
|
||||||
|
return Ok((start, vec![comp]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((pos,comps))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fzf_comp(comps: &[String], shenv: &mut ShEnv) -> Option<String> {
|
||||||
|
// All of the fzf wrapper libraries suck
|
||||||
|
// So we gotta do this now
|
||||||
|
let echo_args = comps.join("\n");
|
||||||
|
let echo = format!("echo \"{echo_args}\"");
|
||||||
|
let fzf = "fzf --height=~30% --layout=reverse --border --border-label=completion";
|
||||||
|
let command = format!("{echo} | {fzf}");
|
||||||
|
|
||||||
|
shenv.ctx_mut().set_flag(ExecFlags::NO_EXPAND); // Prevent any pesky shell injections with filenames like '$(rm -rf /)'
|
||||||
|
let selection = expand_cmdsub_string(&command, shenv).ok()?;
|
||||||
|
if selection.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(selection.trim().to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ use rustyline::{config::Configurer, history::{DefaultHistory, History}, ColorMod
|
|||||||
pub mod readline;
|
pub mod readline;
|
||||||
pub mod highlight;
|
pub mod highlight;
|
||||||
pub mod validate;
|
pub mod validate;
|
||||||
|
pub mod comp;
|
||||||
|
|
||||||
fn init_rl<'a>(shenv: &'a mut ShEnv) -> Editor<SynHelper<'a>, DefaultHistory> {
|
fn init_rl<'a>(shenv: &'a mut ShEnv) -> Editor<SynHelper<'a>, DefaultHistory> {
|
||||||
let hist_path = std::env::var("FERN_HIST").unwrap_or_default();
|
let hist_path = std::env::var("FERN_HIST").unwrap_or_default();
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
use rustyline::{completion::{Completer, FilenameCompleter}, hint::{Hint, Hinter}, history::{History, SearchDirection}, Helper};
|
use rustyline::{completion::{Candidate, Completer, FilenameCompleter}, hint::{Hint, Hinter}, history::{History, SearchDirection}, Helper};
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub struct SynHelper<'a> {
|
pub struct SynHelper<'a> {
|
||||||
file_comp: FilenameCompleter,
|
pub file_comp: FilenameCompleter,
|
||||||
pub shenv: &'a mut ShEnv,
|
pub shenv: &'a mut ShEnv,
|
||||||
pub commands: Vec<String>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Helper for SynHelper<'a> {}
|
impl<'a> Helper for SynHelper<'a> {}
|
||||||
@@ -15,7 +14,6 @@ impl<'a> SynHelper<'a> {
|
|||||||
Self {
|
Self {
|
||||||
file_comp: FilenameCompleter::new(),
|
file_comp: FilenameCompleter::new(),
|
||||||
shenv,
|
shenv,
|
||||||
commands: vec![]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,12 +33,6 @@ impl<'a> SynHelper<'a> {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
impl<'a> Completer for SynHelper<'a> {
|
|
||||||
type Candidate = String;
|
|
||||||
fn complete( &self, line: &str, pos: usize, ctx: &rustyline::Context<'_>,) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
|
|
||||||
Ok((0,vec![]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SynHint {
|
pub struct SynHint {
|
||||||
text: String,
|
text: String,
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ use crate::prelude::*;
|
|||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Copy,Clone,Debug,PartialEq,PartialOrd)]
|
#[derive(Copy,Clone,Debug,PartialEq,PartialOrd)]
|
||||||
pub struct ExecFlags: u32 {
|
pub struct ExecFlags: u32 {
|
||||||
const NO_FORK = 0x00000001;
|
const NO_FORK = 0b00000001;
|
||||||
const IN_FUNC = 0x00000010;
|
const IN_FUNC = 0b00000010;
|
||||||
|
const NO_EXPAND = 0b00000100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,16 @@ use crate::prelude::*;
|
|||||||
pub struct MetaTab {
|
pub struct MetaTab {
|
||||||
timer_start: Instant,
|
timer_start: Instant,
|
||||||
last_runtime: Option<Duration>,
|
last_runtime: Option<Duration>,
|
||||||
|
path_cmds: Vec<String> // Used for command completion
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetaTab {
|
impl MetaTab {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
let path_cmds = get_path_cmds().unwrap_or_default();
|
||||||
Self {
|
Self {
|
||||||
timer_start: Instant::now(),
|
timer_start: Instant::now(),
|
||||||
last_runtime: None,
|
last_runtime: None,
|
||||||
|
path_cmds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn start_timer(&mut self) {
|
pub fn start_timer(&mut self) {
|
||||||
@@ -23,4 +26,7 @@ impl MetaTab {
|
|||||||
pub fn get_runtime(&self) -> Option<Duration> {
|
pub fn get_runtime(&self) -> Option<Duration> {
|
||||||
self.last_runtime
|
self.last_runtime
|
||||||
}
|
}
|
||||||
|
pub fn path_cmds(&self) -> &[String] {
|
||||||
|
&self.path_cmds
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user