mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-08-03 09:52:27 +00:00
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:
parent
d774304574
commit
bf8a505135
7 changed files with 186 additions and 83 deletions
|
@ -236,6 +236,7 @@ mod tests {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|item| CompletionItem {
|
.map(|item| CompletionItem {
|
||||||
label: item.label,
|
label: item.label,
|
||||||
|
label_details: item.label_details,
|
||||||
sort_text: item.sort_text,
|
sort_text: item.sort_text,
|
||||||
kind: item.kind,
|
kind: item.kind,
|
||||||
text_edit: item.text_edit,
|
text_edit: item.text_edit,
|
||||||
|
|
|
@ -65,7 +65,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_args.typ
|
||||||
{
|
{
|
||||||
"kind": 7,
|
"kind": 7,
|
||||||
"label": "content",
|
"label": "content",
|
||||||
"sortText": "031",
|
"sortText": "050",
|
||||||
"textEdit": {
|
"textEdit": {
|
||||||
"newText": "content",
|
"newText": "content",
|
||||||
"range": {
|
"range": {
|
||||||
|
|
|
@ -65,7 +65,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_with_args.typ
|
||||||
{
|
{
|
||||||
"kind": 7,
|
"kind": 7,
|
||||||
"label": "content",
|
"label": "content",
|
||||||
"sortText": "031",
|
"sortText": "050",
|
||||||
"textEdit": {
|
"textEdit": {
|
||||||
"newText": "content",
|
"newText": "content",
|
||||||
"range": {
|
"range": {
|
||||||
|
|
|
@ -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,
|
"kind": 3,
|
||||||
"label": "aa.with",
|
"label": "aa.with",
|
||||||
|
|
|
@ -1212,12 +1212,7 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.completions.push(Completion {
|
self.completions.push(Completion {
|
||||||
kind: match value {
|
kind: value_to_completion_kind(value),
|
||||||
Value::Func(_) => CompletionKind::Func,
|
|
||||||
Value::Type(_) => CompletionKind::Type,
|
|
||||||
Value::Symbol(s) => CompletionKind::Symbol(s.get()),
|
|
||||||
_ => CompletionKind::Constant,
|
|
||||||
},
|
|
||||||
label,
|
label,
|
||||||
apply,
|
apply,
|
||||||
detail,
|
detail,
|
||||||
|
|
|
@ -48,9 +48,10 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
|
||||||
pub fn scope_completions_(&mut self, parens: bool, filter: impl Fn(Option<&Value>) -> bool) {
|
pub fn scope_completions_(&mut self, parens: bool, filter: impl Fn(Option<&Value>) -> bool) {
|
||||||
let mut defined = BTreeMap::new();
|
let mut defined = BTreeMap::new();
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
enum DefKind {
|
enum DefKind {
|
||||||
Syntax(Span),
|
Syntax(Span),
|
||||||
Instance(Spanned<Value>),
|
Instance(Span, Value),
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut try_insert = |name: EcoString, kind: (CompletionKind, DefKind)| {
|
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| {
|
let def_kind = analyzed.clone().map(|module_ins| {
|
||||||
DefKind::Instance(Spanned::new(
|
DefKind::Instance(
|
||||||
module_ins,
|
|
||||||
v.new_name()
|
v.new_name()
|
||||||
.map(|n| n.span())
|
.map(|n| n.span())
|
||||||
.unwrap_or_else(Span::detached),
|
.unwrap_or_else(Span::detached),
|
||||||
))
|
module_ins,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some((name, def_kind)) = name.zip(def_kind) {
|
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() {
|
if let Some(scope) = module_ins.scope() {
|
||||||
for (name, v) in scope.iter() {
|
for (name, v) in scope.iter() {
|
||||||
let kind = match v {
|
let kind = value_to_completion_kind(v);
|
||||||
Value::Func(..) => CompletionKind::Func,
|
|
||||||
Value::Module(..) => CompletionKind::Module,
|
|
||||||
Value::Type(..) => CompletionKind::Type,
|
|
||||||
_ => CompletionKind::Constant,
|
|
||||||
};
|
|
||||||
let def_kind = match &import_filter {
|
let def_kind = match &import_filter {
|
||||||
Some(import_filter) => {
|
Some(import_filter) => {
|
||||||
let w = import_filter.get(name);
|
let w = import_filter.get(name);
|
||||||
match w {
|
match w {
|
||||||
Some(DefKind::Syntax(span)) => {
|
Some(DefKind::Syntax(span)) => {
|
||||||
Some(DefKind::Instance(Spanned::new(
|
Some(DefKind::Instance(*span, v.clone()))
|
||||||
v.clone(),
|
|
||||||
*span,
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
Some(DefKind::Instance(v)) => {
|
Some(DefKind::Instance(span, v)) => {
|
||||||
Some(DefKind::Instance(v.clone()))
|
Some(DefKind::Instance(*span, v.clone()))
|
||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => Some(DefKind::Instance(Spanned::new(
|
None => {
|
||||||
v.clone(),
|
Some(DefKind::Instance(Span::detached(), v.clone()))
|
||||||
Span::detached(),
|
}
|
||||||
))),
|
|
||||||
};
|
};
|
||||||
if let Some(def_kind) = def_kind {
|
if let Some(def_kind) = def_kind {
|
||||||
try_insert(name.clone(), (kind, def_kind));
|
try_insert(name.clone(), (kind, def_kind));
|
||||||
|
@ -263,55 +255,177 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
|
||||||
.clone();
|
.clone();
|
||||||
for (name, value) in scope.iter() {
|
for (name, value) in scope.iter() {
|
||||||
if filter(Some(value)) && !defined.contains_key(name) {
|
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 {
|
for (name, (kind, def_kind)) in defined {
|
||||||
if filter(None) && !name.is_empty() {
|
if !filter(None) || name.is_empty() {
|
||||||
let _ = types;
|
continue;
|
||||||
let _ = def_kind;
|
}
|
||||||
if kind == CompletionKind::Func {
|
let _ = types;
|
||||||
let apply = eco_format!("{}.with(${{}})", name);
|
if kind == CompletionKind::Func {
|
||||||
|
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 {
|
self.completions.push(Completion {
|
||||||
kind: kind.clone(),
|
|
||||||
label: eco_format!("{}.with", name),
|
label: eco_format!("{}.with", name),
|
||||||
apply: Some(apply),
|
apply: Some(eco_format!("{}.with(${{}})", name)),
|
||||||
// todo: only vscode and neovim (0.9.1) support this
|
..base.clone()
|
||||||
command: Some("editor.action.triggerSuggest"),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
let apply = eco_format!("{}.where(${{}})", name);
|
|
||||||
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()
|
|
||||||
});
|
|
||||||
// todo: check arguments, if empty, jump to after the parens
|
|
||||||
let apply = eco_format!("{}(${{}})", name);
|
|
||||||
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()
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
self.completions.push(Completion {
|
|
||||||
kind,
|
|
||||||
label: name,
|
|
||||||
..Completion::default()
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if is_element && !matches!(surrounding_syntax, SurroundingSyntax::SetRule) {
|
||||||
|
self.completions.push(Completion {
|
||||||
|
label: eco_format!("{}.where", name),
|
||||||
|
apply: Some(eco_format!("{}.where(${{}})", name)),
|
||||||
|
..base.clone()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let bad_instantiate = matches!(
|
||||||
|
surrounding_syntax,
|
||||||
|
SurroundingSyntax::Selector | SurroundingSyntax::SetRule
|
||||||
|
) && !is_element;
|
||||||
|
if !bad_instantiate {
|
||||||
|
if !parens {
|
||||||
|
self.completions.push(Completion {
|
||||||
|
label: name,
|
||||||
|
..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,
|
||||||
|
label: name,
|
||||||
|
..Completion::default()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
fn sort_and_explicit_code_completion(ctx: &mut CompletionContext) {
|
||||||
let mut completions = std::mem::take(&mut ctx.completions);
|
let mut completions = std::mem::take(&mut ctx.completions);
|
||||||
let explict = ctx.explicit;
|
let explict = ctx.explicit;
|
||||||
|
@ -364,6 +478,16 @@ fn sort_and_explicit_code_completion(ctx: &mut CompletionContext) {
|
||||||
log::debug!("sort_and_explicit_code_completion: {:?}", ctx.completions);
|
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.
|
/// Add completions for the parameters of a function.
|
||||||
pub fn param_completions<'a>(
|
pub fn param_completions<'a>(
|
||||||
ctx: &mut CompletionContext<'a, '_>,
|
ctx: &mut CompletionContext<'a, '_>,
|
||||||
|
|
|
@ -374,7 +374,7 @@ fn e2e() {
|
||||||
});
|
});
|
||||||
|
|
||||||
let hash = replay_log(&tinymist_binary, &root.join("neovim"));
|
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"));
|
let hash = replay_log(&tinymist_binary, &root.join("vscode"));
|
||||||
insta::assert_snapshot!(hash, @"siphash128_13:e40d55d49e7012058d1623c8ea1a1573");
|
insta::assert_snapshot!(hash, @"siphash128_13:fffd39b26d0e52d1b274290e378df265");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue