feat: check surrounding syntax for elements/selectors (#236)

* feat: check surrounding syntax for elements/selectors

* dev: update snapshot

* dev: update snapshot
This commit is contained in:
Myriad-Dreamin 2024-05-05 19:36:30 +08:00 committed by GitHub
parent d774304574
commit bf8a505135
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 186 additions and 83 deletions

View file

@ -236,6 +236,7 @@ mod tests {
.into_iter()
.map(|item| CompletionItem {
label: item.label,
label_details: item.label_details,
sort_text: item.sort_text,
kind: item.kind,
text_edit: item.text_edit,

View file

@ -65,7 +65,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_args.typ
{
"kind": 7,
"label": "content",
"sortText": "031",
"sortText": "050",
"textEdit": {
"newText": "content",
"range": {

View file

@ -65,7 +65,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_with_args.typ
{
"kind": 7,
"label": "content",
"sortText": "031",
"sortText": "050",
"textEdit": {
"newText": "content",
"range": {

View file

@ -25,23 +25,6 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_star.typ
}
}
},
{
"kind": 3,
"label": "aa.where",
"textEdit": {
"newText": "aa.where(${1:})",
"range": {
"end": {
"character": 4,
"line": 2
},
"start": {
"character": 1,
"line": 2
}
}
}
},
{
"kind": 3,
"label": "aa.with",

View file

@ -1212,12 +1212,7 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
}
self.completions.push(Completion {
kind: match value {
Value::Func(_) => CompletionKind::Func,
Value::Type(_) => CompletionKind::Type,
Value::Symbol(s) => CompletionKind::Symbol(s.get()),
_ => CompletionKind::Constant,
},
kind: value_to_completion_kind(value),
label,
apply,
detail,

View file

@ -48,9 +48,10 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
pub fn scope_completions_(&mut self, parens: bool, filter: impl Fn(Option<&Value>) -> bool) {
let mut defined = BTreeMap::new();
#[derive(Debug, Clone)]
enum DefKind {
Syntax(Span),
Instance(Spanned<Value>),
Instance(Span, Value),
}
let mut try_insert = |name: EcoString, kind: (CompletionKind, DefKind)| {
@ -114,12 +115,12 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
})();
let def_kind = analyzed.clone().map(|module_ins| {
DefKind::Instance(Spanned::new(
module_ins,
DefKind::Instance(
v.new_name()
.map(|n| n.span())
.unwrap_or_else(Span::detached),
))
module_ins,
)
});
if let Some((name, def_kind)) = name.zip(def_kind) {
@ -159,32 +160,23 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
if let Some(scope) = module_ins.scope() {
for (name, v) in scope.iter() {
let kind = match v {
Value::Func(..) => CompletionKind::Func,
Value::Module(..) => CompletionKind::Module,
Value::Type(..) => CompletionKind::Type,
_ => CompletionKind::Constant,
};
let kind = value_to_completion_kind(v);
let def_kind = match &import_filter {
Some(import_filter) => {
let w = import_filter.get(name);
match w {
Some(DefKind::Syntax(span)) => {
Some(DefKind::Instance(Spanned::new(
v.clone(),
*span,
)))
Some(DefKind::Instance(*span, v.clone()))
}
Some(DefKind::Instance(v)) => {
Some(DefKind::Instance(v.clone()))
Some(DefKind::Instance(span, v)) => {
Some(DefKind::Instance(*span, v.clone()))
}
None => None,
}
}
None => Some(DefKind::Instance(Spanned::new(
v.clone(),
Span::detached(),
))),
None => {
Some(DefKind::Instance(Span::detached(), v.clone()))
}
};
if let Some(def_kind) = def_kind {
try_insert(name.clone(), (kind, def_kind));
@ -263,43 +255,98 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
.clone();
for (name, value) in scope.iter() {
if filter(Some(value)) && !defined.contains_key(name) {
self.value_completion(Some(name.clone()), value, parens, None);
defined.insert(
name.clone(),
(
value_to_completion_kind(value),
DefKind::Instance(Span::detached(), value.clone()),
),
);
}
}
enum SurroundingSyntax {
Regular,
Selector,
SetRule,
}
let surrounding_syntax = check_surrounding_syntax(&self.leaf)
.or_else(|| check_previous_syntax(&self.leaf))
.unwrap_or(SurroundingSyntax::Regular);
for (name, (kind, def_kind)) in defined {
if filter(None) && !name.is_empty() {
if !filter(None) || name.is_empty() {
continue;
}
let _ = types;
let _ = def_kind;
if kind == CompletionKind::Func {
let apply = eco_format!("{}.with(${{}})", name);
self.completions.push(Completion {
let base = Completion {
kind: kind.clone(),
// todo: only vscode and neovim (0.9.1) support this
command: Some("editor.action.triggerSuggest"),
..Default::default()
};
let zero_args = match &def_kind {
DefKind::Instance(_, Value::Func(func)) => func
.params()
.is_some_and(|params| params.iter().all(|param| param.name == "self")),
_ => false,
};
let is_element = match &def_kind {
DefKind::Instance(_, Value::Func(func)) => func.element().is_some(),
_ => false,
};
log::debug!("is_element: {} {:?} -> {:?}", name, def_kind, is_element);
if !zero_args && matches!(surrounding_syntax, SurroundingSyntax::Regular) {
self.completions.push(Completion {
label: eco_format!("{}.with", name),
apply: Some(apply),
// todo: only vscode and neovim (0.9.1) support this
command: Some("editor.action.triggerSuggest"),
..Default::default()
apply: Some(eco_format!("{}.with(${{}})", name)),
..base.clone()
});
let apply = eco_format!("{}.where(${{}})", name);
}
if is_element && !matches!(surrounding_syntax, SurroundingSyntax::SetRule) {
self.completions.push(Completion {
kind: kind.clone(),
label: eco_format!("{}.where", name),
apply: Some(apply),
// todo: only vscode and neovim (0.9.1) support this
command: Some("editor.action.triggerSuggest"),
..Default::default()
apply: Some(eco_format!("{}.where(${{}})", name)),
..base.clone()
});
// todo: check arguments, if empty, jump to after the parens
let apply = eco_format!("{}(${{}})", name);
}
let bad_instantiate = matches!(
surrounding_syntax,
SurroundingSyntax::Selector | SurroundingSyntax::SetRule
) && !is_element;
if !bad_instantiate {
if !parens {
self.completions.push(Completion {
kind: kind.clone(),
label: name,
apply: Some(apply),
// todo: only vscode and neovim (0.9.1) support this
command: Some("editor.action.triggerSuggest"),
..Completion::default()
..base
});
} else if zero_args {
self.completions.push(Completion {
apply: Some(eco_format!("{}()${{}}", name)),
label: name,
..base
});
} else {
self.completions.push(Completion {
apply: Some(eco_format!("{}(${{}})", name)),
label: name,
..base
});
}
}
} else if let DefKind::Instance(_, v) = def_kind {
let bad_instantiate = matches!(
surrounding_syntax,
SurroundingSyntax::Selector | SurroundingSyntax::SetRule
) && !matches!(&v, Value::Func(func) if func.element().is_some());
if !bad_instantiate {
self.value_completion(Some(name), &v, parens, None);
}
} else {
self.completions.push(Completion {
kind,
@ -308,8 +355,75 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
});
}
}
fn check_surrounding_syntax(mut leaf: &LinkedNode) -> Option<SurroundingSyntax> {
use SurroundingSyntax::*;
let mut met_args = false;
while let Some(parent) = leaf.parent() {
log::debug!(
"check_surrounding_syntax: {:?}::{:?}",
parent.kind(),
leaf.kind()
);
match parent.kind() {
SyntaxKind::CodeBlock | SyntaxKind::ContentBlock | SyntaxKind::Equation => {
return Some(Regular);
}
SyntaxKind::Named => {
return Some(Regular);
}
SyntaxKind::Args => {
met_args = true;
}
SyntaxKind::SetRule => {
let rule = parent.get().cast::<ast::SetRule>()?;
if met_args || encolsed_by(parent, rule.condition().map(|s| s.span()), leaf)
{
return Some(Regular);
} else {
return Some(SetRule);
}
}
SyntaxKind::ShowRule => {
let rule = parent.get().cast::<ast::ShowRule>()?;
if encolsed_by(parent, Some(rule.transform().span()), leaf) {
return Some(Regular);
} else {
return Some(Selector); // query's first argument
}
}
_ => {}
}
leaf = parent;
}
None
}
fn check_previous_syntax(leaf: &LinkedNode) -> Option<SurroundingSyntax> {
let mut leaf = leaf.clone();
if leaf.kind().is_trivia() {
leaf = leaf.prev_sibling()?;
}
if matches!(leaf.kind(), SyntaxKind::ShowRule | SyntaxKind::SetRule) {
return check_surrounding_syntax(&leaf.rightmost_leaf()?);
}
if matches!(leaf.kind(), SyntaxKind::Show) {
return Some(SurroundingSyntax::Selector);
}
if matches!(leaf.kind(), SyntaxKind::Set) {
return Some(SurroundingSyntax::SetRule);
}
None
}
}
}
fn encolsed_by(parent: &LinkedNode, s: Option<Span>, leaf: &LinkedNode) -> bool {
s.and_then(|s| parent.find(s)?.find(leaf.span())).is_some()
}
fn sort_and_explicit_code_completion(ctx: &mut CompletionContext) {
@ -364,6 +478,16 @@ fn sort_and_explicit_code_completion(ctx: &mut CompletionContext) {
log::debug!("sort_and_explicit_code_completion: {:?}", ctx.completions);
}
pub fn value_to_completion_kind(value: &Value) -> CompletionKind {
match value {
Value::Func(..) => CompletionKind::Func,
Value::Module(..) => CompletionKind::Module,
Value::Type(..) => CompletionKind::Type,
Value::Symbol(s) => CompletionKind::Symbol(s.get()),
_ => CompletionKind::Constant,
}
}
/// Add completions for the parameters of a function.
pub fn param_completions<'a>(
ctx: &mut CompletionContext<'a, '_>,

View file

@ -374,7 +374,7 @@ fn e2e() {
});
let hash = replay_log(&tinymist_binary, &root.join("neovim"));
insta::assert_snapshot!(hash, @"siphash128_13:35e217e61e97a024b17ba8020374a65f");
insta::assert_snapshot!(hash, @"siphash128_13:10b6c633caf6174b1b1728fe79be4c7a");
}
{
@ -385,7 +385,7 @@ fn e2e() {
});
let hash = replay_log(&tinymist_binary, &root.join("vscode"));
insta::assert_snapshot!(hash, @"siphash128_13:e40d55d49e7012058d1623c8ea1a1573");
insta::assert_snapshot!(hash, @"siphash128_13:fffd39b26d0e52d1b274290e378df265");
}
}