diff options
Diffstat (limited to 'src/parse/mod.rs')
| -rw-r--r-- | src/parse/mod.rs | 124 |
1 files changed, 91 insertions, 33 deletions
diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 47e2d87..4f7dac5 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -61,12 +61,14 @@ pub fn pipes<const N: usize>(cmds: [Command<PreExpansion>; N]) -> Ast<PreExpansi pub fn estr(x: &[u8]) -> ExpString { ExpString { parts: vec![StringPart::Boring(x.to_vec())], + delim: b' ', } } pub fn str<const N: usize>(parts: [StringPart; N]) -> ExpString { ExpString { parts: parts.to_vec(), + delim: b' ', } } @@ -78,6 +80,7 @@ pub fn var(x: &[u8]) -> StringPart { StringPart::Var(Var { name: VarName { name: x.to_vec() }, default: None, + already_complete: true, }) } @@ -85,6 +88,7 @@ pub fn var_default(x: &[u8], default: ExpString) -> StringPart { StringPart::Var(Var { name: VarName { name: x.to_vec() }, default: Some(default), + already_complete: true, }) } @@ -338,6 +342,20 @@ pub enum StringPart { pub struct Var { name: VarName, default: Option<ExpString>, + /// if pressing tab right after the parsed variable should not try to complete the variable + /// + /// i.e. `${HOM}` -> true, `$HOM` -> false, `${HOM` -> false + already_complete: bool, +} + +impl Var { + pub fn new(name: VarName) -> Self { + Self { + name, + default: None, + already_complete: false, + } + } } impl StringPart { @@ -356,6 +374,7 @@ impl StringPart { /// `"hi ${var} $(cmd) "` gets mapped to `[Boring("hi "), Var("var"), String(" "), Cmd(...), Boring(" ")]` pub struct ExpString { parts: Vec<StringPart>, + delim: u8, } impl ExpString { @@ -456,7 +475,7 @@ impl Parse for ExpString { b.adv(); } else if is_symbol(delim) && delim != b'$' && delim != b'\\' { return if already_parsed { - Ok(Self { parts }) + Ok(Self { parts, delim }) } else { Err(ParseError::NotAString) }; @@ -507,7 +526,7 @@ impl Parse for ExpString { if x == b'\'' || x == b'"' { break; } else { - return Ok(Self { parts }); + return Ok(Self { parts, delim }); } } @@ -540,23 +559,19 @@ impl Parse for ExpString { if x == b'?' || x == b'!' { b.adv(); - p.push(StringPart::Var(Var { - name: VarName { name: vec![x] }, - default: None, - })) + p.push(StringPart::Var(Var::new(VarName { name: vec![x] }))) } else if is_var_begin(x) { let v = VarName::parse(b)?; - p.push(StringPart::Var(Var { - name: v, - default: None, - })); + p.push(StringPart::Var(Var::new(v))); } else if x == b'{' { b.adv(); let v = VarName::parse(b)?; let mut default = None; if !b.has() { - return Err(ParseError::Eof); + if !b.is_completion() { + return Err(ParseError::Eof); + } } else if b.peek() == b':' { b.adv(); if !b.has() { @@ -571,13 +586,24 @@ impl Parse for ExpString { } if !b.has() { - return Err(ParseError::Eof); + if !b.is_completion() { + return Err(ParseError::Eof); + } } else if b.peek() != b'}' { - return Err(ParseError::Incomplete); + return Err(ParseError::Expected('}')); } - b.adv(); - p.push(StringPart::Var(Var { name: v, default })); + let already_complete = b.has(); + + if already_complete { + b.adv(); + } + + p.push(StringPart::Var(Var { + name: v, + default, + already_complete, + })); } else if x == b'(' { b.adv(); let cmd = Ast::parse(b)?; @@ -608,6 +634,7 @@ impl Parse for ExpString { name: b"HOME".to_vec(), }, default: None, + already_complete: true, })); } else { add_char(p, x); @@ -626,7 +653,7 @@ impl Parse for ExpString { } if b.is_completion() || already_parsed { - Ok(Self { parts }) + Ok(Self { parts, delim: b' ' }) } else { Err(ParseError::Eof) } @@ -687,6 +714,7 @@ pub fn do_parse(x: &[u8]) -> Res<Ast<PreExpansion>, (ParseError, &[u8])> { pub enum CompletionKind { Command, Argument, + Variable, None, } @@ -704,30 +732,60 @@ impl CompletionContext { } } -fn expstr_cc<E: Expander>(s: &ExpString, kind: CompletionKind, e: &mut E) -> CompletionContext { - match s.clone().expand(e) { - Ok(buf) => CompletionContext { kind, partial: buf }, - Err(_) => CompletionContext::none(), +impl Ast<PreExpansion> { + fn completion<E: Expander>(&self, e: &mut E) -> CompletionContext { + match self { + Ast::FunDecl(fd) => fd.body.body.completion(e), + Ast::VarAssign(va) => va.val.completion(e, CompletionKind::Argument), + Ast::Pipes(p) => p.completion(e), + } + } +} + +impl ExpString { + fn completion<E: Expander>(&self, e: &mut E, kind: CompletionKind) -> CompletionContext { + if let Some(StringPart::Var(var)) = self.parts.last() + && !var.already_complete + { + CompletionContext { + kind: CompletionKind::Variable, + partial: var.name.name.clone(), + } + } else if let Ok(s) = self.clone().expand(e) { + CompletionContext { kind, partial: s } + } else { + CompletionContext::none() + } + } +} + +impl Pipes<PreExpansion> { + fn completion<E: Expander>(&self, e: &mut E) -> CompletionContext { + let Some(cmd) = self.cmds.last() else { + return CompletionContext::none(); + }; + + if let Some(arg) = cmd.args.last() { + arg.completion(e, CompletionKind::Argument) + } else { + cmd.cmd.completion(e, CompletionKind::Command) + } } } pub fn completion_context<'a, E: Expander>(x: &'a [u8], e: &mut E) -> 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() { - expstr_cc(&cmd.cmd, CompletionKind::Command, e) - } else { - expstr_cc(&cmd.args[cmd.args.len() - 1], CompletionKind::Argument, e) - } - } else { - CompletionContext::none() - } - } - _ => CompletionContext::none(), + + let Ok(ast) = ast else { + return CompletionContext::none(); + }; + + if cursor.spaced { + return CompletionContext::none(); } + + ast.completion(e) } trait Parse: Sized { |
