mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 13:13:43 +00:00
feat: improve and filter completion in show/set syntax (#858)
This commit is contained in:
parent
37ea3b8d3d
commit
6f8d9750d7
10 changed files with 165 additions and 104 deletions
|
@ -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 */
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
---
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
---
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue