feat: improve and filter completion in show/set syntax (#858)

This commit is contained in:
Myriad-Dreamin 2024-11-20 02:15:53 +08:00 committed by GitHub
parent 37ea3b8d3d
commit 6f8d9750d7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 165 additions and 104 deletions

View file

@ -1,2 +1,2 @@
/// contains: raw, read, raw.with, raw.where, read.with, read.where, replacement
/// contains: raw, read, raw.with, raw.where, read.with, read.where, replacement, red
#set r/* range 0..1 */

View file

@ -1,2 +1,2 @@
/// contains: raw, read, raw.with, raw.where, read.with, read.where, replacement, regex selector
/// contains: raw, read, raw.with, raw.where, read.with, read.where, replacement, regex selector, red
#show r/* range 0..1 */

View file

@ -1,2 +1,2 @@
/// contains: raw, read, raw.with, raw.where, read.with, read.where, replacement, regex selector
/// contains: raw, read, raw.with, raw.where, read.with, read.where, replacement, regex selector, red
#show /* range 0..1 */

View file

@ -1,6 +1,6 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on / (87..88)
description: Completion on / (92..93)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/set.typ
---

View file

@ -1,6 +1,6 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on / (104..105)
description: Completion on / (109..110)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/show.typ
---
@ -15,7 +15,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/show.typ
"description": "(str, align: alignment, block: bool, lang: none | str, syntaxes: [syntax], tab-size: int, theme: [theme]) => raw"
},
"textEdit": {
"newText": "raw(${1:})",
"newText": "raw: ${1:}",
"range": {
"end": {
"character": 7,
@ -35,7 +35,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/show.typ
"description": "(str, align: alignment, block: bool, lang: none | str, syntaxes: [syntax], tab-size: int, theme: [theme]) => raw"
},
"textEdit": {
"newText": "raw.where(${1:})",
"newText": "raw.where(${1:}): ${2:}",
"range": {
"end": {
"character": 7,
@ -49,53 +49,10 @@ input_file: crates/tinymist-query/src/fixtures/completion/show.typ
}
},
{
"kind": 3,
"label": "raw.with",
"labelDetails": {
"description": "(str, align: alignment, block: bool, lang: none | str, syntaxes: [syntax], tab-size: int, theme: [theme]) => raw"
},
"kind": 15,
"label": "regex selector",
"textEdit": {
"newText": "raw.with(${1:})",
"range": {
"end": {
"character": 7,
"line": 1
},
"start": {
"character": 6,
"line": 1
}
}
}
},
{
"kind": 3,
"label": "read",
"labelDetails": {
"description": "([any], encoding: \"utf8\" | none) => bytes | str"
},
"textEdit": {
"newText": "read(${1:})",
"range": {
"end": {
"character": 7,
"line": 1
},
"start": {
"character": 6,
"line": 1
}
}
}
},
{
"kind": 3,
"label": "read.with",
"labelDetails": {
"description": "([any], encoding: \"utf8\" | none) => bytes | str"
},
"textEdit": {
"newText": "read.with(${1:})",
"newText": "regex(\"${1:regex}\"): ${2:}",
"range": {
"end": {
"character": 7,

View file

@ -1,6 +1,6 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on / (103..104)
description: Completion on / (108..109)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/show2.typ
---

View file

@ -15,7 +15,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/show_transform.typ
"description": "(str, align: alignment, block: bool, lang: none | str, syntaxes: [syntax], tab-size: int, theme: [theme]) => raw"
},
"textEdit": {
"newText": "raw(${1:})",
"newText": "raw",
"range": {
"end": {
"character": 12,
@ -75,7 +75,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/show_transform.typ
"description": "([any], encoding: \"utf8\" | none) => bytes | str"
},
"textEdit": {
"newText": "read(${1:})",
"newText": "read",
"range": {
"end": {
"character": 12,
@ -107,6 +107,23 @@ 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

@ -15,7 +15,7 @@ use reflexo_typst::TypstFileId;
use rustc_hash::{FxHashMap, FxHashSet};
use serde::{Deserialize, Serialize};
use typst::{
foundations::{Content, ParamInfo, Type, Value},
foundations::{Content, Element, ParamInfo, Type, Value},
syntax::{ast, Span, SyntaxKind, SyntaxNode},
};
@ -222,21 +222,34 @@ impl Ty {
}
}
/// Get the type of the type
pub fn element(&self) -> Option<Element> {
match self {
Ty::Value(v) => match &v.val {
Value::Func(f) => f.element(),
_ => None,
},
Ty::Builtin(BuiltinTy::Element(v)) => Some(*v),
_ => None,
}
}
pub(crate) fn satisfy<T: TyCtx>(&self, ctx: &T, f: impl FnMut(&Ty, bool)) {
self.bounds(true, &mut BoundPred::new(ctx, f));
}
pub(crate) fn is_content<T: TyCtx>(&self, ctx: &T) -> bool {
let mut res = false;
self.bounds(
false,
&mut BoundPred::new(ctx, |ty: &Ty, _pol| {
res = res || {
match ty {
Ty::Value(v) => matches!(v.val, Value::Content(..)),
Ty::Builtin(BuiltinTy::Content | BuiltinTy::Element(..)) => true,
Ty::Builtin(BuiltinTy::Type(v)) => *v == Type::of::<Content>(),
_ => false,
}
self.satisfy(ctx, |ty: &Ty, _pol| {
res = res || {
match ty {
Ty::Value(v) => matches!(v.val, Value::Content(..)),
Ty::Builtin(BuiltinTy::Content | BuiltinTy::Element(..)) => true,
Ty::Builtin(BuiltinTy::Type(v)) => *v == Type::of::<Content>(),
_ => false,
}
}),
);
}
});
res
}
}

View file

@ -341,7 +341,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, |_, _| true);
ctx.snippet_completion(
"subscript",
@ -591,25 +591,28 @@ fn import_item_completions<'a>(
/// Complete set and show rules.
fn complete_rules(ctx: &mut CompletionContext) -> bool {
// We don't want to complete directly behind the keyword.
if !ctx.leaf.kind().is_trivia() {
return false;
}
let Some(prev) = ctx.leaf.prev_leaf() else {
return false;
};
// Behind the set keyword: "set |".
if matches!(prev.kind(), SyntaxKind::Set) {
ctx.from = ctx.cursor;
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 = ctx.cursor;
ctx.from = if ctx.leaf.kind().is_trivia() {
ctx.cursor
} else {
ctx.leaf.offset()
};
show_rule_selector_completions(ctx);
return true;
}
@ -620,7 +623,11 @@ fn complete_rules(ctx: &mut CompletionContext) -> bool {
if matches!(prev.kind(), SyntaxKind::Colon);
if matches!(prev.parent_kind(), Some(SyntaxKind::ShowRule));
then {
ctx.from = ctx.cursor;
ctx.from = if ctx.leaf.kind().is_trivia() {
ctx.cursor
} else {
ctx.leaf.offset()
};
show_rule_recipe_completions(ctx);
return true;
}
@ -631,23 +638,12 @@ fn complete_rules(ctx: &mut CompletionContext) -> bool {
/// Add completions for all functions from the global scope.
fn set_rule_completions(ctx: &mut CompletionContext) {
ctx.scope_completions(true, |value| {
matches!(
value,
Value::Func(func) if func.params()
.unwrap_or_default()
.iter()
.any(|param| param.settable),
)
});
ctx.scope_completions(true, |_, _| true);
}
/// Add completions for selectors.
fn show_rule_selector_completions(ctx: &mut CompletionContext) {
ctx.scope_completions(
false,
|value| matches!(value, Value::Func(func) if func.element().is_some()),
);
ctx.scope_completions(false, |_, _| true);
ctx.snippet_completion(
"text selector",
@ -684,7 +680,7 @@ fn show_rule_recipe_completions(ctx: &mut CompletionContext) {
"Transform the element with a function.",
);
ctx.scope_completions(false, |value| matches!(value, Value::Func(_)));
ctx.scope_completions(false, |_, c| !c.functions.is_empty());
}
/// Complete in code mode.
@ -734,8 +730,10 @@ 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 || {
matches!(value, Value::Symbol(_) | Value::Func(_) | Value::Type(_) | Value::Module(_))
ctx.scope_completions(true, |_value, _| !hash || {
// todo: filter code completions
// matches!(value, Value::Symbol(_) | Value::Func(_) | Value::Type(_) | Value::Module(_))
true
});
ctx.snippet_completion(

View file

@ -107,8 +107,12 @@ impl<'a> CompletionContext<'a> {
self.ctx.world()
}
pub fn scope_completions(&mut self, parens: bool, filter: impl Fn(&Value) -> bool) {
self.scope_completions_(parens, |v| v.map_or(true, &filter));
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 {
@ -398,7 +402,11 @@ 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(Option<&Value>) -> bool) {
pub fn scope_completions_(
&mut self,
parens: bool,
filter: impl Fn(&Ty, &CompletionKindChecker) -> bool,
) {
let Some((_, defines)) = self.defines() else {
return;
};
@ -412,14 +420,44 @@ impl<'a> CompletionContext<'a> {
functions: HashSet::default(),
};
let filter = |ty: &Ty, c: &CompletionKindChecker| {
let s = match surrounding_syntax {
SurroundingSyntax::Regular => true,
SurroundingSyntax::Selector => 'selector: {
for func in &c.functions {
if func.element().is_some() {
break 'selector true;
}
}
false
}
SurroundingSyntax::SetRule => 'set_rule: {
// todo: user defined elements
for func in &c.functions {
if let Some(elem) = func.element() {
if elem.params().iter().any(|param| param.settable) {
break 'set_rule true;
}
}
}
false
}
};
s && filter(ty, c)
};
// we don't check literal type here for faster completion
for (name, ty) in defines {
// todo: filter ty
if !filter(None) || name.is_empty() {
if name.is_empty() {
continue;
}
kind_checker.check(&ty);
if !filter(&ty, &kind_checker) {
continue;
}
if let Some(ch) = kind_checker.symbols.iter().min().copied() {
// todo: describe all chars
@ -514,6 +552,7 @@ impl<'a> CompletionContext<'a> {
}
}
#[derive(Debug)]
enum SurroundingSyntax {
Regular,
Selector,
@ -549,6 +588,15 @@ fn check_surrounding_syntax(mut leaf: &LinkedNode) -> Option<SurroundingSyntax>
}
SyntaxKind::ShowRule => {
let rule = parent.get().cast::<ast::ShowRule>()?;
let colon = rule
.to_untyped()
.children()
.find(|s| s.kind() == SyntaxKind::Colon);
if colon.is_none() {
// incomplete show rule
return Some(Selector);
}
if encolsed_by(parent, Some(rule.transform().span()), leaf) {
return Some(Regular);
} else {
@ -667,9 +715,9 @@ impl<'a> IfaceChecker for ScopeChecker<'a> {
}
}
struct CompletionKindChecker {
symbols: HashSet<char>,
functions: HashSet<Ty>,
pub(crate) struct CompletionKindChecker {
pub(crate) symbols: HashSet<char>,
pub(crate) functions: HashSet<Ty>,
}
impl CompletionKindChecker {
fn reset(&mut self) {
@ -1389,8 +1437,6 @@ pub(crate) fn complete_type(ctx: &mut CompletionContext) -> Option<()> {
ctx.enrich(" ", "");
}
sort_and_explicit_code_completion(ctx);
if let Some(c) = args_node {
log::debug!("content block compl: args {c:?}");
let is_unclosed = matches!(c.kind(), SyntaxKind::Args)
@ -1406,6 +1452,36 @@ pub(crate) fn complete_type(ctx: &mut CompletionContext) -> Option<()> {
}
}
let surrounding_syntax = ctx.surrounding_syntax();
match surrounding_syntax {
SurroundingSyntax::Regular => {}
SurroundingSyntax::Selector => {
ctx.snippet_completion(
"text selector",
"\"${text}\"",
"Replace occurrences of specific text.",
);
ctx.snippet_completion(
"regex selector",
"regex(\"${regex}\")",
"Replace matches of a regular expression.",
);
}
SurroundingSyntax::SetRule => {}
}
sort_and_explicit_code_completion(ctx);
match surrounding_syntax {
SurroundingSyntax::Regular => {}
SurroundingSyntax::Selector => {
ctx.enrich("", ": ${}");
}
SurroundingSyntax::SetRule => {}
}
Some(())
}