From 5d003fb1b2114a11fbdcc5390d03222bd159092c Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sat, 9 Nov 2024 11:48:59 +0100 Subject: [PATCH] Autocomplete different functions with different brackets --- crates/typst-ide/src/complete.rs | 95 +++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 14 deletions(-) diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs index f7a404d7d..a1b5f1d2e 100644 --- a/crates/typst-ide/src/complete.rs +++ b/crates/typst-ide/src/complete.rs @@ -1191,16 +1191,17 @@ impl<'a> CompletionContext<'a> { }); 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 func - .params() - .is_some_and(|params| params.iter().all(|param| param.name == "self")) - { - apply = Some(eco_format!("{label}()${{}}")); - } else { - apply = Some(eco_format!("{label}(${{}})")); - } + apply = Some(match BracketMode::of(func) { + BracketMode::RoundAfter => eco_format!("{label}()${{}}"), + BracketMode::RoundWithin => eco_format!("{label}(${{}})"), + BracketMode::RoundNewline => eco_format!("{label}(\n ${{}}\n)"), + BracketMode::SquareWithin => eco_format!("{label}[${{}}]"), + }); } } else if at { 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)] mod tests { use std::collections::BTreeSet; @@ -1363,21 +1397,26 @@ mod tests { type Response = Option<(usize, Vec)>; trait ResponseExt { + fn completions(&self) -> &[Completion]; fn labels(&self) -> BTreeSet<&str>; fn must_include<'a>(&self, includes: impl IntoIterator) -> &Self; fn must_exclude<'a>(&self, excludes: impl IntoIterator) -> &Self; + fn must_apply<'a>(&self, label: &str, apply: impl Into>) + -> &Self; } impl ResponseExt for Response { - fn labels(&self) -> BTreeSet<&str> { + fn completions(&self) -> &[Completion] { match self { - None => BTreeSet::new(), - Some((_, completions)) => { - completions.iter().map(|c| c.label.as_str()).collect() - } + Some((_, completions)) => completions.as_slice(), + None => &[], } } + fn labels(&self) -> BTreeSet<&str> { + self.completions().iter().map(|c| c.label.as_str()).collect() + } + #[track_caller] fn must_include<'a>(&self, includes: impl IntoIterator) -> &Self { let labels = self.labels(); @@ -1401,6 +1440,20 @@ mod tests { } self } + + #[track_caller] + fn must_apply<'a>( + &self, + label: &str, + apply: impl Into>, + ) -> &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] @@ -1474,4 +1527,18 @@ mod tests { .must_include(["netwok", "glacier-melt", "supplement"]) .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); + } }