aboutsummaryrefslogtreecommitdiffstats
path: root/src/parse/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/parse/mod.rs')
-rw-r--r--src/parse/mod.rs124
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 {