Autocomplete different functions with different brackets

This commit is contained in:
Laurenz 2024-11-09 11:48:59 +01:00
parent c526f031cc
commit 5d003fb1b2

View File

@ -1191,16 +1191,17 @@ impl<'a> CompletionContext<'a> {
}); });
let mut apply = None; let mut apply = None;
if parens && matches!(value, Value::Func(_)) { if parens
&& matches!(value, Value::Func(_))
&& !self.after.starts_with(['(', '['])
{
if let Value::Func(func) = value { if let Value::Func(func) = value {
if func apply = Some(match BracketMode::of(func) {
.params() BracketMode::RoundAfter => eco_format!("{label}()${{}}"),
.is_some_and(|params| params.iter().all(|param| param.name == "self")) BracketMode::RoundWithin => eco_format!("{label}(${{}})"),
{ BracketMode::RoundNewline => eco_format!("{label}(\n ${{}}\n)"),
apply = Some(eco_format!("{label}()${{}}")); BracketMode::SquareWithin => eco_format!("{label}[${{}}]"),
} else { });
apply = Some(eco_format!("{label}(${{}})"));
}
} }
} else if at { } else if at {
apply = Some(eco_format!("at(\"{label}\")")); apply = Some(eco_format!("at(\"{label}\")"));
@ -1351,6 +1352,39 @@ impl<'a> CompletionContext<'a> {
} }
} }
/// What kind of parentheses to autocomplete for a function.
enum BracketMode {
/// Round parenthesis, with the cursor within: `(|)`.
RoundWithin,
/// Round parenthesis, with the cursor after them: `()|`.
RoundAfter,
/// Round parenthesis, with newlines and indent.
RoundNewline,
/// Square brackets, with the cursor within: `[|]`.
SquareWithin,
}
impl BracketMode {
fn of(func: &Func) -> Self {
if func
.params()
.is_some_and(|params| params.iter().all(|param| param.name == "self"))
{
return Self::RoundAfter;
}
match func.name() {
Some(
"emph" | "footnote" | "quote" | "strong" | "highlight" | "overline"
| "underline" | "smallcaps" | "strike" | "sub" | "super",
) => Self::SquareWithin,
Some("colbreak" | "parbreak" | "linebreak" | "pagebreak") => Self::RoundAfter,
Some("figure" | "table" | "grid" | "stack") => Self::RoundNewline,
_ => Self::RoundWithin,
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::collections::BTreeSet; use std::collections::BTreeSet;
@ -1363,21 +1397,26 @@ mod tests {
type Response = Option<(usize, Vec<Completion>)>; type Response = Option<(usize, Vec<Completion>)>;
trait ResponseExt { trait ResponseExt {
fn completions(&self) -> &[Completion];
fn labels(&self) -> BTreeSet<&str>; fn labels(&self) -> BTreeSet<&str>;
fn must_include<'a>(&self, includes: impl IntoIterator<Item = &'a str>) -> &Self; fn must_include<'a>(&self, includes: impl IntoIterator<Item = &'a str>) -> &Self;
fn must_exclude<'a>(&self, excludes: impl IntoIterator<Item = &'a str>) -> &Self; fn must_exclude<'a>(&self, excludes: impl IntoIterator<Item = &'a str>) -> &Self;
fn must_apply<'a>(&self, label: &str, apply: impl Into<Option<&'a str>>)
-> &Self;
} }
impl ResponseExt for Response { impl ResponseExt for Response {
fn labels(&self) -> BTreeSet<&str> { fn completions(&self) -> &[Completion] {
match self { match self {
None => BTreeSet::new(), Some((_, completions)) => completions.as_slice(),
Some((_, completions)) => { None => &[],
completions.iter().map(|c| c.label.as_str()).collect()
}
} }
} }
fn labels(&self) -> BTreeSet<&str> {
self.completions().iter().map(|c| c.label.as_str()).collect()
}
#[track_caller] #[track_caller]
fn must_include<'a>(&self, includes: impl IntoIterator<Item = &'a str>) -> &Self { fn must_include<'a>(&self, includes: impl IntoIterator<Item = &'a str>) -> &Self {
let labels = self.labels(); let labels = self.labels();
@ -1401,6 +1440,20 @@ mod tests {
} }
self self
} }
#[track_caller]
fn must_apply<'a>(
&self,
label: &str,
apply: impl Into<Option<&'a str>>,
) -> &Self {
let Some(completion) = self.completions().iter().find(|c| c.label == label)
else {
panic!("found no completion for {label:?}");
};
assert_eq!(completion.apply.as_deref(), apply.into());
self
}
} }
#[track_caller] #[track_caller]
@ -1474,4 +1527,18 @@ mod tests {
.must_include(["netwok", "glacier-melt", "supplement"]) .must_include(["netwok", "glacier-melt", "supplement"])
.must_exclude(["bib"]); .must_exclude(["bib"]);
} }
/// Test what kind of brackets we autocomplete for function calls depending
/// on the function and existing parens.
#[test]
fn test_autocomplete_bracket_mode() {
test("#", 1).must_apply("list", "list(${})");
test("#", 1).must_apply("linebreak", "linebreak()${}");
test("#", 1).must_apply("strong", "strong[${}]");
test("#", 1).must_apply("footnote", "footnote[${}]");
test("#", 1).must_apply("figure", "figure(\n ${}\n)");
test("#", 1).must_apply("table", "table(\n ${}\n)");
test("#()", 1).must_apply("list", None);
test("#[]", 1).must_apply("strong", None);
}
} }