feat: replace rule completion with scoped type completion (#861)

* feat: replace rule completion with scoped type completion

* test: update snapshot
This commit is contained in:
Myriad-Dreamin 2024-11-20 15:49:58 +08:00 committed by GitHub
parent e2c8418c35
commit d8ee287b77
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 205 additions and 243 deletions

View file

@ -77,7 +77,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_args.typ
"labelDetails": {
"description": "type"
},
"sortText": "052",
"sortText": "031",
"textEdit": {
"newText": "content",
"range": {

View file

@ -35,7 +35,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_builtin_args.typ
"labelDetails": {
"description": "(int, content, gutter: relative) => columns"
},
"sortText": "056",
"sortText": "037",
"textEdit": {
"newText": "columns(${1:})",
"range": {

View file

@ -77,7 +77,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_with_args.typ
"labelDetails": {
"description": "type"
},
"sortText": "052",
"sortText": "031",
"textEdit": {
"newText": "content",
"range": {

View file

@ -14,6 +14,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/set.typ
"labelDetails": {
"description": "(str, align: alignment, block: bool, lang: none | str, syntaxes: [syntax], tab-size: int, theme: [theme]) => raw"
},
"sortText": "052",
"textEdit": {
"newText": "raw(${1:})",
"range": {

View file

@ -14,6 +14,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/set2.typ
"labelDetails": {
"description": "(str, align: alignment, block: bool, lang: none | str, syntaxes: [syntax], tab-size: int, theme: [theme]) => raw"
},
"sortText": "052",
"textEdit": {
"newText": "raw(${1:})",
"range": {

View file

@ -14,6 +14,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/set_in_show.typ
"labelDetails": {
"description": "(str, align: alignment, block: bool, lang: none | str, syntaxes: [syntax], tab-size: int, theme: [theme]) => raw"
},
"sortText": "052",
"textEdit": {
"newText": "raw(${1:})",
"range": {

View file

@ -8,12 +8,31 @@ input_file: crates/tinymist-query/src/fixtures/completion/show.typ
{
"isIncomplete": false,
"items": [
{
"kind": 15,
"label": "regex selector",
"sortText": "000",
"textEdit": {
"newText": "regex(\"${1:regex}\"): ${2:}",
"range": {
"end": {
"character": 7,
"line": 1
},
"start": {
"character": 6,
"line": 1
}
}
}
},
{
"kind": 3,
"label": "raw",
"labelDetails": {
"description": "(str, align: alignment, block: bool, lang: none | str, syntaxes: [syntax], tab-size: int, theme: [theme]) => raw"
},
"sortText": "096",
"textEdit": {
"newText": "raw: ${1:}",
"range": {
@ -34,6 +53,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/show.typ
"labelDetails": {
"description": "(str, align: alignment, block: bool, lang: none | str, syntaxes: [syntax], tab-size: int, theme: [theme]) => raw"
},
"sortText": "097",
"textEdit": {
"newText": "raw.where(${1:}): ${2:}",
"range": {
@ -47,23 +67,6 @@ input_file: crates/tinymist-query/src/fixtures/completion/show.typ
}
}
}
},
{
"kind": 15,
"label": "regex selector",
"textEdit": {
"newText": "regex(\"${1:regex}\"): ${2:}",
"range": {
"end": {
"character": 7,
"line": 1
},
"start": {
"character": 6,
"line": 1
}
}
}
}
]
}

View file

@ -8,12 +8,31 @@ input_file: crates/tinymist-query/src/fixtures/completion/show2.typ
{
"isIncomplete": false,
"items": [
{
"kind": 15,
"label": "regex selector",
"sortText": "000",
"textEdit": {
"newText": "regex(\"${1:regex}\"): ${2:}",
"range": {
"end": {
"character": 6,
"line": 1
},
"start": {
"character": 6,
"line": 1
}
}
}
},
{
"kind": 3,
"label": "raw",
"labelDetails": {
"description": "(str, align: alignment, block: bool, lang: none | str, syntaxes: [syntax], tab-size: int, theme: [theme]) => raw"
},
"sortText": "096",
"textEdit": {
"newText": "raw: ${1:}",
"range": {
@ -34,6 +53,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/show2.typ
"labelDetails": {
"description": "(str, align: alignment, block: bool, lang: none | str, syntaxes: [syntax], tab-size: int, theme: [theme]) => raw"
},
"sortText": "097",
"textEdit": {
"newText": "raw.where(${1:}): ${2:}",
"range": {
@ -47,23 +67,6 @@ input_file: crates/tinymist-query/src/fixtures/completion/show2.typ
}
}
}
},
{
"kind": 15,
"label": "regex selector",
"textEdit": {
"newText": "regex(\"${1:regex}\"): ${2:}",
"range": {
"end": {
"character": 6,
"line": 1
},
"start": {
"character": 6,
"line": 1
}
}
}
}
]
}

View file

@ -8,12 +8,31 @@ input_file: crates/tinymist-query/src/fixtures/completion/show3.typ
{
"isIncomplete": false,
"items": [
{
"kind": 15,
"label": "regex selector",
"sortText": "000",
"textEdit": {
"newText": "regex(\"${1:regex}\"): ${2:}",
"range": {
"end": {
"character": 6,
"line": 1
},
"start": {
"character": 6,
"line": 1
}
}
}
},
{
"kind": 3,
"label": "raw",
"labelDetails": {
"description": "(str, align: alignment, block: bool, lang: none | str, syntaxes: [syntax], tab-size: int, theme: [theme]) => raw"
},
"sortText": "096",
"textEdit": {
"newText": "raw: ${1:}",
"range": {
@ -34,6 +53,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/show3.typ
"labelDetails": {
"description": "(str, align: alignment, block: bool, lang: none | str, syntaxes: [syntax], tab-size: int, theme: [theme]) => raw"
},
"sortText": "097",
"textEdit": {
"newText": "raw.where(${1:}): ${2:}",
"range": {
@ -47,23 +67,6 @@ input_file: crates/tinymist-query/src/fixtures/completion/show3.typ
}
}
}
},
{
"kind": 15,
"label": "regex selector",
"textEdit": {
"newText": "regex(\"${1:regex}\"): ${2:}",
"range": {
"end": {
"character": 6,
"line": 1
},
"start": {
"character": 6,
"line": 1
}
}
}
}
]
}

View file

@ -9,13 +9,11 @@ input_file: crates/tinymist-query/src/fixtures/completion/show_transform.typ
"isIncomplete": false,
"items": [
{
"kind": 3,
"label": "raw",
"labelDetails": {
"description": "(str, align: alignment, block: bool, lang: none | str, syntaxes: [syntax], tab-size: int, theme: [theme]) => raw"
},
"kind": 15,
"label": "replacement",
"sortText": "000",
"textEdit": {
"newText": "raw",
"newText": "[${1:content}]",
"range": {
"end": {
"character": 12,
@ -30,12 +28,13 @@ input_file: crates/tinymist-query/src/fixtures/completion/show_transform.typ
},
{
"kind": 3,
"label": "raw.where",
"label": "raw",
"labelDetails": {
"description": "(str, align: alignment, block: bool, lang: none | str, syntaxes: [syntax], tab-size: int, theme: [theme]) => raw"
},
"sortText": "157",
"textEdit": {
"newText": "raw.where(${1:})",
"newText": "raw(${1:})",
"range": {
"end": {
"character": 12,
@ -54,6 +53,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/show_transform.typ
"labelDetails": {
"description": "(str, align: alignment, block: bool, lang: none | str, syntaxes: [syntax], tab-size: int, theme: [theme]) => raw"
},
"sortText": "158",
"textEdit": {
"newText": "raw.with(${1:})",
"range": {
@ -74,8 +74,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/show_transform.typ
"labelDetails": {
"description": "([any], encoding: \"utf8\" | none) => bytes | str"
},
"sortText": "159",
"textEdit": {
"newText": "read",
"newText": "read(${1:})",
"range": {
"end": {
"character": 12,
@ -94,6 +95,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/show_transform.typ
"labelDetails": {
"description": "([any], encoding: \"utf8\" | none) => bytes | str"
},
"sortText": "160",
"textEdit": {
"newText": "read.with(${1:})",
"range": {
@ -107,23 +109,6 @@ input_file: crates/tinymist-query/src/fixtures/completion/show_transform.typ
}
}
}
},
{
"kind": 15,
"label": "replacement",
"textEdit": {
"newText": "[${1:content}]",
"range": {
"end": {
"character": 12,
"line": 1
},
"start": {
"character": 11,
"line": 1
}
}
}
}
]
}

View file

@ -8,14 +8,33 @@ input_file: crates/tinymist-query/src/fixtures/completion/show_transform2.typ
{
"isIncomplete": false,
"items": [
{
"kind": 15,
"label": "replacement",
"sortText": "000",
"textEdit": {
"newText": "[${1:content}]",
"range": {
"end": {
"character": 11,
"line": 1
},
"start": {
"character": 11,
"line": 1
}
}
}
},
{
"kind": 3,
"label": "raw",
"labelDetails": {
"description": "(str, align: alignment, block: bool, lang: none | str, syntaxes: [syntax], tab-size: int, theme: [theme]) => raw"
},
"sortText": "157",
"textEdit": {
"newText": "raw",
"newText": "raw(${1:})",
"range": {
"end": {
"character": 11,
@ -30,12 +49,13 @@ input_file: crates/tinymist-query/src/fixtures/completion/show_transform2.typ
},
{
"kind": 3,
"label": "raw.where",
"label": "raw.with",
"labelDetails": {
"description": "(str, align: alignment, block: bool, lang: none | str, syntaxes: [syntax], tab-size: int, theme: [theme]) => raw"
},
"sortText": "158",
"textEdit": {
"newText": "raw.where(${1:})",
"newText": "raw.with(${1:})",
"range": {
"end": {
"character": 11,
@ -49,10 +69,35 @@ input_file: crates/tinymist-query/src/fixtures/completion/show_transform2.typ
}
},
{
"kind": 15,
"label": "replacement",
"kind": 3,
"label": "read",
"labelDetails": {
"description": "([any], encoding: \"utf8\" | none) => bytes | str"
},
"sortText": "159",
"textEdit": {
"newText": "[${1:content}]",
"newText": "read(${1:})",
"range": {
"end": {
"character": 11,
"line": 1
},
"start": {
"character": 11,
"line": 1
}
}
}
},
{
"kind": 3,
"label": "read.with",
"labelDetails": {
"description": "([any], encoding: \"utf8\" | none) => bytes | str"
},
"sortText": "160",
"textEdit": {
"newText": "read.with(${1:})",
"range": {
"end": {
"character": 11,

View file

@ -14,7 +14,7 @@ input_file: crates/tinymist-query/src/fixtures/pkgs/touying-core-slides.typ
"labelDetails": {
"description": "() => any"
},
"sortText": "050",
"sortText": "029",
"textEdit": {
"newText": "config-xxx()${1:}",
"range": {

View file

@ -56,7 +56,7 @@ input_file: crates/tinymist-query/src/fixtures/pkgs/touying-core-slides.typ
"labelDetails": {
"description": "(content, gap: length, justify: bool) => repeat"
},
"sortText": "249",
"sortText": "139",
"textEdit": {
"newText": "repeat[${1:}]",
"range": {

View file

@ -35,7 +35,7 @@ input_file: crates/tinymist-query/src/fixtures/pkgs/touying-utils-cover-with-rec
"labelDetails": {
"description": "type"
},
"sortText": "295",
"sortText": "161",
"textEdit": {
"newText": "stroke(${1:})",
"range": {

View file

@ -32,7 +32,7 @@ input_file: crates/tinymist-query/src/fixtures/pkgs/touying-utils-current-headin
"labelDetails": {
"description": "type"
},
"sortText": "133",
"sortText": "079",
"textEdit": {
"newText": "int(${1:})",
"range": {

View file

@ -68,7 +68,7 @@ input_file: crates/tinymist-query/src/fixtures/pkgs/touying-utils-markup-text.ty
"labelDetails": {
"description": "type"
},
"sortText": "287",
"sortText": "156",
"textEdit": {
"newText": "str(${1:})",
"range": {

View file

@ -42,7 +42,6 @@ pub fn autocomplete(
complete_labels(&mut ctx)
|| complete_field_accesses(&mut ctx)
|| complete_imports(&mut ctx)
|| complete_rules(&mut ctx)
|| complete_markup(&mut ctx)
|| complete_math(&mut ctx)
|| complete_code(&mut ctx, false)
@ -341,7 +340,7 @@ fn complete_math(ctx: &mut CompletionContext) -> bool {
/// Add completions for math snippets.
#[rustfmt::skip]
fn math_completions(ctx: &mut CompletionContext) {
ctx.scope_completions(true, |_, _| true);
ctx.scope_completions(true);
ctx.snippet_completion(
"subscript",
@ -589,109 +588,20 @@ fn import_item_completions<'a>(
}
}
/// Complete set and show rules.
fn complete_rules(ctx: &mut CompletionContext) -> bool {
let Some(prev) = ctx.leaf.prev_leaf() else {
return false;
};
// Behind the set keyword: "set |".
if matches!(prev.kind(), SyntaxKind::Set) {
ctx.from = if ctx.leaf.kind().is_trivia() {
ctx.cursor
} else {
ctx.leaf.offset()
};
set_rule_completions(ctx);
return true;
}
// Behind the show keyword: "show |".
if matches!(prev.kind(), SyntaxKind::Show) {
ctx.from = if ctx.leaf.kind().is_trivia() {
ctx.cursor
} else {
ctx.leaf.offset()
};
show_rule_selector_completions(ctx);
return true;
}
// Behind a half-completed show rule: "show strong: |".
if_chain! {
if let Some(prev) = ctx.leaf.prev_leaf();
if matches!(prev.kind(), SyntaxKind::Colon);
if matches!(prev.parent_kind(), Some(SyntaxKind::ShowRule));
then {
ctx.from = if ctx.leaf.kind().is_trivia() {
ctx.cursor
} else {
ctx.leaf.offset()
};
show_rule_recipe_completions(ctx);
return true;
}
}
false
}
/// Add completions for all functions from the global scope.
fn set_rule_completions(ctx: &mut CompletionContext) {
ctx.scope_completions(true, |_, _| true);
}
/// Add completions for selectors.
fn show_rule_selector_completions(ctx: &mut CompletionContext) {
ctx.scope_completions(false, |_, _| true);
ctx.snippet_completion(
"text selector",
"\"${text}\"",
"Replace occurrences of specific text.",
);
ctx.snippet_completion(
"regex selector",
"regex(\"${regex}\")",
"Replace matches of a regular expression.",
);
ctx.enrich("", ": ${}");
}
/// Add completions for recipes.
fn show_rule_recipe_completions(ctx: &mut CompletionContext) {
ctx.snippet_completion(
"replacement",
"[${content}]",
"Replace the selected element with content.",
);
ctx.snippet_completion(
"replacement (string)",
"\"${text}\"",
"Replace the selected element with a string of text.",
);
ctx.snippet_completion(
"transformation",
"element => [${content}]",
"Transform the element with a function.",
);
ctx.scope_completions(false, |_, c| !c.functions.is_empty());
}
/// Complete in code mode.
fn complete_code(ctx: &mut CompletionContext, from_type: bool) -> bool {
let surrounding_syntax = ctx.surrounding_syntax();
if matches!(
ctx.leaf.parent_kind(),
None | Some(SyntaxKind::Markup)
| Some(SyntaxKind::Math)
| Some(SyntaxKind::MathFrac)
| Some(SyntaxKind::MathAttach)
| Some(SyntaxKind::MathRoot)
(ctx.leaf.parent_kind(), surrounding_syntax),
(
None | Some(SyntaxKind::Markup)
| Some(SyntaxKind::Math)
| Some(SyntaxKind::MathFrac)
| Some(SyntaxKind::MathAttach)
| Some(SyntaxKind::MathRoot),
SurroundingSyntax::Regular
)
) {
return false;
}
@ -730,11 +640,9 @@ fn complete_code(ctx: &mut CompletionContext, from_type: bool) -> bool {
/// Add completions for expression snippets.
#[rustfmt::skip]
fn code_completions(ctx: &mut CompletionContext, hash: bool) {
ctx.scope_completions(true, |_value, _| !hash || {
// todo: filter code completions
// matches!(value, Value::Symbol(_) | Value::Func(_) | Value::Type(_) | Value::Module(_))
true
});
// todo: filter code completions
// matches!(value, Value::Symbol(_) | Value::Func(_) | Value::Type(_) | Value::Module(_))
ctx.scope_completions(true);
ctx.snippet_completion(
"function call",

View file

@ -107,19 +107,11 @@ impl<'a> CompletionContext<'a> {
self.ctx.world()
}
pub fn scope_completions(
&mut self,
parens: bool,
filter: impl Fn(&Ty, &CompletionKindChecker) -> bool,
) {
self.scope_completions_(parens, filter);
}
fn seen_field(&mut self, field: Interned<str>) -> bool {
!self.seen_fields.insert(field)
}
fn surrounding_syntax(&mut self) -> SurroundingSyntax {
pub(crate) fn surrounding_syntax(&mut self) -> SurroundingSyntax {
check_previous_syntax(&self.leaf)
.or_else(|| check_surrounding_syntax(&self.leaf))
.unwrap_or(SurroundingSyntax::Regular)
@ -402,11 +394,7 @@ impl<'a> CompletionContext<'a> {
/// Add completions for definitions that are available at the cursor.
///
/// Filters the global/math scope with the given filter.
pub fn scope_completions_(
&mut self,
parens: bool,
filter: impl Fn(&Ty, &CompletionKindChecker) -> bool,
) {
pub fn scope_completions(&mut self, parens: bool) {
let Some((_, defines)) = self.defines() else {
return;
};
@ -421,8 +409,8 @@ impl<'a> CompletionContext<'a> {
functions: HashSet::default(),
};
let filter = |ty: &Ty, c: &CompletionKindChecker| {
let s = match surrounding_syntax {
let filter = |c: &CompletionKindChecker| {
match surrounding_syntax {
SurroundingSyntax::Regular => true,
SurroundingSyntax::Selector => 'selector: {
for func in &c.functions {
@ -433,6 +421,7 @@ impl<'a> CompletionContext<'a> {
false
}
SurroundingSyntax::ShowTransform => !c.functions.is_empty(),
SurroundingSyntax::SetRule => 'set_rule: {
// todo: user defined elements
for func in &c.functions {
@ -445,8 +434,7 @@ impl<'a> CompletionContext<'a> {
false
}
};
s && filter(ty, c)
}
};
// we don't check literal type here for faster completion
@ -456,7 +444,7 @@ impl<'a> CompletionContext<'a> {
}
kind_checker.check(&ty);
if !filter(&ty, &kind_checker) {
if !filter(&kind_checker) {
continue;
}
@ -492,7 +480,7 @@ impl<'a> CompletionContext<'a> {
log::debug!("fn_feat: {name} {ty:?} -> {fn_feat:?}");
if matches!(surrounding_syntax, SurroundingSyntax::Regular)
if matches!(surrounding_syntax, SurroundingSyntax::ShowTransform)
&& (fn_feat.min_pos() > 0 || fn_feat.min_named() > 0)
{
self.completions.push(Completion {
@ -501,7 +489,7 @@ impl<'a> CompletionContext<'a> {
..base.clone()
});
}
if fn_feat.is_element && !matches!(surrounding_syntax, SurroundingSyntax::SetRule) {
if fn_feat.is_element && matches!(surrounding_syntax, SurroundingSyntax::Selector) {
self.completions.push(Completion {
label: eco_format!("{}.where", name),
apply: Some(eco_format!("{}.where(${{}})", name)),
@ -514,7 +502,7 @@ impl<'a> CompletionContext<'a> {
SurroundingSyntax::Selector | SurroundingSyntax::SetRule
) && !fn_feat.is_element;
if !bad_instantiate {
if !parens {
if !parens || matches!(surrounding_syntax, SurroundingSyntax::Selector) {
self.completions.push(Completion {
label: name,
..base
@ -554,10 +542,11 @@ impl<'a> CompletionContext<'a> {
}
}
#[derive(Debug)]
enum SurroundingSyntax {
#[derive(Debug, Clone, Copy)]
pub(crate) enum SurroundingSyntax {
Regular,
Selector,
ShowTransform,
SetRule,
}
@ -594,13 +583,13 @@ fn check_surrounding_syntax(mut leaf: &LinkedNode) -> Option<SurroundingSyntax>
.to_untyped()
.children()
.find(|s| s.kind() == SyntaxKind::Colon);
if colon.is_none() {
let Some(colon) = colon.and_then(|colon| parent.find(colon.span())) else {
// incomplete show rule
return Some(Selector);
}
};
if encolsed_by(parent, Some(rule.transform().span()), leaf) {
return Some(Regular);
if leaf.offset() >= colon.offset() {
return Some(ShowTransform);
} else {
return Some(Selector); // query's first argument
}
@ -1410,14 +1399,11 @@ pub(crate) fn complete_type(ctx: &mut CompletionContext) -> Option<()> {
{
args_node = e.parent().map(|s| s.get().clone());
}
Some(CheckTarget::Normal(e))
if matches!(
e.kind(),
SyntaxKind::Ident | SyntaxKind::Label | SyntaxKind::Ref | SyntaxKind::Str
) => {}
Some(CheckTarget::Paren { .. }) => {}
Some(CheckTarget::Normal(..)) => return None,
None => return None,
// todo: complete type field
Some(CheckTarget::Normal(e)) if matches!(e.kind(), SyntaxKind::FieldAccess) => {
return None;
}
Some(CheckTarget::Paren { .. } | CheckTarget::Normal(..)) | None => {}
}
log::debug!("ctx.leaf {:?}", ctx.leaf.clone());
@ -1425,16 +1411,24 @@ pub(crate) fn complete_type(ctx: &mut CompletionContext) -> Option<()> {
let ty = ctx
.ctx
.literal_type_of_node(ctx.leaf.clone())
.filter(|ty| !matches!(ty, Ty::Any))?;
.filter(|ty| !matches!(ty, Ty::Any));
let scope = ctx.surrounding_syntax();
if matches!((scope, &ty), (SurroundingSyntax::Regular, None)) {
return None;
}
log::debug!("complete_type: {:?} -> ({scope:?}, {ty:#?})", ctx.leaf);
// adjust the completion position
if is_ident_like(&ctx.leaf) {
ctx.from = ctx.leaf.offset();
}
log::debug!("complete_type: ty {:?} -> {ty:#?}", ctx.leaf);
if let Some(ty) = ty {
type_completion(ctx, &ty, None);
}
type_completion(ctx, &ty, None);
if ctx.before.ends_with(',') || ctx.before.ends_with(':') {
ctx.enrich(" ", "");
}
@ -1454,9 +1448,7 @@ pub(crate) fn complete_type(ctx: &mut CompletionContext) -> Option<()> {
}
}
let surrounding_syntax = ctx.surrounding_syntax();
match surrounding_syntax {
match scope {
SurroundingSyntax::Regular => {}
SurroundingSyntax::Selector => {
ctx.snippet_completion(
@ -1471,16 +1463,36 @@ pub(crate) fn complete_type(ctx: &mut CompletionContext) -> Option<()> {
"Replace matches of a regular expression.",
);
}
SurroundingSyntax::ShowTransform => {
ctx.snippet_completion(
"replacement",
"[${content}]",
"Replace the selected element with content.",
);
ctx.snippet_completion(
"replacement (string)",
"\"${text}\"",
"Replace the selected element with a string of text.",
);
ctx.snippet_completion(
"transformation",
"element => [${content}]",
"Transform the element with a function.",
);
}
SurroundingSyntax::SetRule => {}
}
sort_and_explicit_code_completion(ctx);
match surrounding_syntax {
match scope {
SurroundingSyntax::Regular => {}
SurroundingSyntax::Selector => {
ctx.enrich("", ": ${}");
}
SurroundingSyntax::ShowTransform => {}
SurroundingSyntax::SetRule => {}
}