feat: optionally make symbol completion stepless (#1313)

* feat: support to steplessly complete symbols

* fix: default enum

* feat: implement it

* fix: bad nest

* dev: order

* fix: modifier
This commit is contained in:
Myriad-Dreamin 2025-04-19 11:32:53 +08:00 committed by GitHub
parent 258cf85601
commit e26d817b79
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 97 additions and 44 deletions

View file

@ -83,6 +83,9 @@ pub struct CompletionFeat {
#[serde(default)]
pub trigger_suggest_and_parameter_hints: bool,
/// The Way to complete symbols.
pub symbol: Option<SymbolCompletionWay>,
/// Whether to enable postfix completion.
pub postfix: Option<bool>,
/// Whether to enable ufcs completion.
@ -127,6 +130,22 @@ impl CompletionFeat {
.as_ref()
.unwrap_or(&DEFAULT_POSTFIX_SNIPPET)
}
pub(crate) fn is_stepless(&self) -> bool {
matches!(self.symbol, Some(SymbolCompletionWay::Stepless))
}
}
/// Whether to make symbol completion stepless. For example, `$ar|$` will be
/// completed to `$arrow.r$`. Hint: Restarting the editor is required to change
/// this setting.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum SymbolCompletionWay {
/// Complete symbols step by step
Step,
/// Complete symbols steplessly
Stepless,
}
/// The struct describing how a completion worker views the editor's cursor.

View file

@ -73,16 +73,7 @@ impl CompletionPair<'_, '_, '_> {
match value {
Value::Symbol(symbol) => {
for modifier in symbol.modifiers() {
if let Ok(modified) = symbol.clone().modified(modifier) {
self.push_completion(Completion {
kind: CompletionKind::Symbol(modified.get()),
label: modifier.into(),
label_details: Some(symbol_label_detail(modified.get())),
..Completion::default()
});
}
}
self.symbol_var_completions(&symbol, None);
if valid_postfix_target {
self.ufcs_completions(target);

View file

@ -1,13 +1,15 @@
//! Completion kind analysis.
use typst::foundations::Symbol;
use super::*;
pub(crate) struct CompletionKindChecker {
pub symbols: HashSet<char>,
pub functions: HashSet<Ty>,
pub(crate) struct CompletionKindChecker<'a> {
pub symbols: HashSet<&'a Symbol>,
pub functions: HashSet<&'a Ty>,
}
impl CompletionKindChecker {
impl<'a> CompletionKindChecker<'a> {
/// Reset the checker status for a fresh checking.
fn reset(&mut self) {
self.symbols.clear();
@ -15,29 +17,29 @@ impl CompletionKindChecker {
}
/// Check the completion kind of a type.
pub fn check(&mut self, ty: &Ty) {
pub fn check(&mut self, ty: &'a Ty) {
self.reset();
match ty {
Ty::Value(val) => match &val.val {
Value::Type(t) if t.constructor().is_ok() => {
self.functions.insert(ty.clone());
self.functions.insert(ty);
}
Value::Func(..) => {
self.functions.insert(ty.clone());
self.functions.insert(ty);
}
Value::Symbol(s) => {
self.symbols.insert(s.get());
self.symbols.insert(s);
}
_ => {}
},
Ty::Func(..) | Ty::With(..) => {
self.functions.insert(ty.clone());
self.functions.insert(ty);
}
Ty::Builtin(BuiltinTy::TypeType(t)) if t.constructor().is_ok() => {
self.functions.insert(ty.clone());
self.functions.insert(ty);
}
Ty::Builtin(BuiltinTy::Element(..)) => {
self.functions.insert(ty.clone());
self.functions.insert(ty);
}
Ty::Let(bounds) => {
for bound in bounds.ubs.iter().chain(bounds.lbs.iter()) {

View file

@ -151,30 +151,23 @@ impl CompletionPair<'_, '_, '_> {
};
// we don't check literal type here for faster completion
for (name, ty) in defines {
for (name, ty) in &defines {
if name.is_empty() {
continue;
}
kind_checker.check(&ty);
kind_checker.check(ty);
if !filter(&kind_checker) {
continue;
}
if let Some(ch) = kind_checker.symbols.iter().min().copied() {
// todo: describe all chars
let kind = CompletionKind::Symbol(ch);
self.push_completion(Completion {
kind,
label: name,
label_details: Some(symbol_label_detail(ch)),
detail: Some(symbol_detail(ch)),
..Completion::default()
});
// todo: describe all chars
if let Some(sym) = kind_checker.symbols.iter().min_by_key(|s| s.get()) {
self.symbol_completions(name.clone(), sym);
continue;
}
let docs = default_docs.get(&name).cloned();
let docs = default_docs.get(name).cloned();
let label_details = ty.describe().or_else(|| Some("any".into()));
@ -182,16 +175,17 @@ impl CompletionPair<'_, '_, '_> {
let detail = docs.or_else(|| label_details.clone());
if !kind_checker.functions.is_empty() {
let fn_feat = FnCompletionFeat::default().check(kind_checker.functions.iter());
let fn_feat =
FnCompletionFeat::default().check(kind_checker.functions.iter().copied());
crate::log_debug_ct!("fn_feat: {name} {ty:?} -> {fn_feat:?}");
self.func_completion(mode, fn_feat, name, label_details, detail, parens);
self.func_completion(mode, fn_feat, name.clone(), label_details, detail, parens);
continue;
}
let kind = type_to_completion_kind(&ty);
let kind = type_to_completion_kind(ty);
self.push_completion(Completion {
kind,
label: name,
label: name.clone(),
label_details,
detail,
..Completion::default()

View file

@ -213,15 +213,15 @@ impl CompletionPair<'_, '_, '_> {
let rb = if is_content_block { "" } else { ")" };
// we don't check literal type here for faster completion
for (name, ty) in defines.defines {
for (name, ty) in &defines.defines {
// todo: filter ty
if name.is_empty() {
continue;
}
kind_checker.check(&ty);
kind_checker.check(ty);
if kind_checker.symbols.iter().min().copied().is_some() {
if !kind_checker.symbols.is_empty() {
continue;
}
if kind_checker.functions.is_empty() {
@ -242,7 +242,7 @@ impl CompletionPair<'_, '_, '_> {
.map(From::from),
..Default::default()
};
let fn_feat = FnCompletionFeat::default().check(kind_checker.functions.iter());
let fn_feat = FnCompletionFeat::default().check(kind_checker.functions.iter().copied());
crate::log_debug_ct!("fn_feat: {name} {ty:?} -> {fn_feat:?}");

View file

@ -1,6 +1,8 @@
//! Completion by typst specific semantics, like `font`, `package`, `label`, or
//! `typst::foundations::Value`.
use typst::foundations::Symbol;
use super::*;
impl CompletionPair<'_, '_, '_> {
/// Add completions for all font families.
@ -206,14 +208,15 @@ impl CompletionPair<'_, '_, '_> {
let mut apply = None;
if parens && matches!(value, Value::Func(_)) {
let mode = self.cursor.leaf_mode();
let ty = Ty::Value(InsTy::new(value.clone()));
let kind_checker = CompletionKindChecker {
symbols: HashSet::default(),
functions: HashSet::from_iter([Ty::Value(InsTy::new(value.clone()))]),
functions: HashSet::from_iter([&ty]),
};
let mut fn_feat = FnCompletionFeat::default();
// todo: unify bound self checking
fn_feat.bound_self = bound_self;
let fn_feat = fn_feat.check(kind_checker.functions.iter());
let fn_feat = fn_feat.check(kind_checker.functions.iter().copied());
self.func_completion(mode, fn_feat, label, label_details, detail, parens);
return;
} else if at {
@ -246,6 +249,36 @@ impl CompletionPair<'_, '_, '_> {
..Completion::default()
});
}
pub fn symbol_completions(&mut self, label: EcoString, symbol: &Symbol) {
let ch = symbol.get();
let kind = CompletionKind::Symbol(ch);
self.push_completion(Completion {
kind,
label: label.clone(),
label_details: Some(symbol_label_detail(ch)),
detail: Some(symbol_detail(ch)),
..Completion::default()
});
let is_stepless = self.cursor.ctx.analysis.completion_feat.is_stepless();
if is_stepless {
self.symbol_var_completions(symbol, Some(&label));
}
}
pub fn symbol_var_completions(&mut self, symbol: &Symbol, prefix: Option<&str>) {
for modifier in symbol.modifiers() {
if let Ok(modified) = symbol.clone().modified(modifier) {
let label = match &prefix {
Some(prefix) => eco_format!("{prefix}.{modifier}"),
None => modifier.into(),
};
self.symbol_completions(label, &modified);
}
}
}
}
#[derive(Debug, Clone, Default)]