mirror of
https://github.com/typst/typst
synced 2025-05-18 11:05:28 +08:00
Label autocompletion
This commit is contained in:
parent
7ac9b1a365
commit
5a6cadefda
@ -21,5 +21,6 @@ typst = { path = "../typst" }
|
|||||||
comemo = "0.3"
|
comemo = "0.3"
|
||||||
ecow = { version = "0.2", features = ["serde"] }
|
ecow = { version = "0.2", features = ["serde"] }
|
||||||
if_chain = "1"
|
if_chain = "1"
|
||||||
|
log = "0.4"
|
||||||
serde = { version = "1.0.184", features = ["derive"] }
|
serde = { version = "1.0.184", features = ["derive"] }
|
||||||
unscanny = "0.1"
|
unscanny = "0.1"
|
||||||
|
@ -9,6 +9,7 @@ use typst::eval::{
|
|||||||
format_str, AutoValue, CastInfo, Func, Library, NoneValue, Repr, Scope, Type, Value,
|
format_str, AutoValue, CastInfo, Func, Library, NoneValue, Repr, Scope, Type, Value,
|
||||||
};
|
};
|
||||||
use typst::geom::Color;
|
use typst::geom::Color;
|
||||||
|
use typst::model::Label;
|
||||||
use typst::syntax::{
|
use typst::syntax::{
|
||||||
ast, is_id_continue, is_id_start, is_ident, LinkedNode, Source, SyntaxKind,
|
ast, is_id_continue, is_id_start, is_ident, LinkedNode, Source, SyntaxKind,
|
||||||
};
|
};
|
||||||
@ -37,6 +38,7 @@ pub fn autocomplete(
|
|||||||
|
|
||||||
let _ = complete_comments(&mut ctx)
|
let _ = complete_comments(&mut ctx)
|
||||||
|| complete_field_accesses(&mut ctx)
|
|| complete_field_accesses(&mut ctx)
|
||||||
|
|| complete_open_labels(&mut ctx)
|
||||||
|| complete_imports(&mut ctx)
|
|| complete_imports(&mut ctx)
|
||||||
|| complete_rules(&mut ctx)
|
|| complete_rules(&mut ctx)
|
||||||
|| complete_params(&mut ctx)
|
|| complete_params(&mut ctx)
|
||||||
@ -110,7 +112,7 @@ fn complete_markup(ctx: &mut CompletionContext) -> bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start of an reference: "@|" or "@he|".
|
// Start of a reference: "@|" or "@he|".
|
||||||
if ctx.leaf.kind() == SyntaxKind::RefMarker {
|
if ctx.leaf.kind() == SyntaxKind::RefMarker {
|
||||||
ctx.from = ctx.leaf.offset() + 1;
|
ctx.from = ctx.leaf.offset() + 1;
|
||||||
ctx.label_completions();
|
ctx.label_completions();
|
||||||
@ -429,6 +431,18 @@ fn field_access_completions(ctx: &mut CompletionContext, value: &Value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Complete half-finished labels.
|
||||||
|
fn complete_open_labels(ctx: &mut CompletionContext) -> bool {
|
||||||
|
// A label anywhere in code: "(<la|".
|
||||||
|
if ctx.leaf.kind().is_error() && ctx.leaf.text().starts_with('<') {
|
||||||
|
ctx.from = ctx.leaf.offset() + 1;
|
||||||
|
ctx.label_completions();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// Complete imports.
|
/// Complete imports.
|
||||||
fn complete_imports(ctx: &mut CompletionContext) -> bool {
|
fn complete_imports(ctx: &mut CompletionContext) -> bool {
|
||||||
// In an import path for a package:
|
// In an import path for a package:
|
||||||
@ -659,16 +673,7 @@ fn complete_params(ctx: &mut CompletionContext) -> bool {
|
|||||||
ctx.from = ctx.cursor.min(next.offset());
|
ctx.from = ctx.cursor.min(next.offset());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exclude arguments which are already present.
|
param_completions(ctx, callee, set, args);
|
||||||
let exclude: Vec<_> = args
|
|
||||||
.items()
|
|
||||||
.filter_map(|arg| match arg {
|
|
||||||
ast::Arg::Named(named) => Some(named.name()),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
param_completions(ctx, callee, set, &exclude);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -681,11 +686,20 @@ fn param_completions<'a>(
|
|||||||
ctx: &mut CompletionContext<'a>,
|
ctx: &mut CompletionContext<'a>,
|
||||||
callee: ast::Expr<'a>,
|
callee: ast::Expr<'a>,
|
||||||
set: bool,
|
set: bool,
|
||||||
exclude: &[ast::Ident<'a>],
|
args: ast::Args<'a>,
|
||||||
) {
|
) {
|
||||||
let Some(func) = resolve_global_callee(ctx, callee) else { return };
|
let Some(func) = resolve_global_callee(ctx, callee) else { return };
|
||||||
let Some(params) = func.params() else { return };
|
let Some(params) = func.params() else { return };
|
||||||
|
|
||||||
|
// Exclude named arguments which are already present.
|
||||||
|
let exclude: Vec<_> = args
|
||||||
|
.items()
|
||||||
|
.filter_map(|arg| match arg {
|
||||||
|
ast::Arg::Named(named) => Some(named.name()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
for param in params {
|
for param in params {
|
||||||
if exclude.iter().any(|ident| ident.as_str() == param.name) {
|
if exclude.iter().any(|ident| ident.as_str() == param.name) {
|
||||||
continue;
|
continue;
|
||||||
@ -780,6 +794,13 @@ fn complete_code(ctx: &mut CompletionContext) -> bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A potential label (only at the start of an argument list): "(<|".
|
||||||
|
if ctx.before.ends_with("(<") {
|
||||||
|
ctx.from = ctx.cursor;
|
||||||
|
ctx.label_completions();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Anywhere: "{ | }".
|
// Anywhere: "{ | }".
|
||||||
// But not within or after an expression.
|
// But not within or after an expression.
|
||||||
if ctx.explicit
|
if ctx.explicit
|
||||||
@ -990,6 +1011,11 @@ impl<'a> CompletionContext<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A small window of context before the cursor.
|
||||||
|
fn before_window(&self, size: usize) -> &str {
|
||||||
|
&self.before[self.cursor.saturating_sub(size)..]
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a prefix and suffix to all applications.
|
/// Add a prefix and suffix to all applications.
|
||||||
fn enrich(&mut self, prefix: &str, suffix: &str) {
|
fn enrich(&mut self, prefix: &str, suffix: &str) {
|
||||||
for Completion { label, apply, .. } in &mut self.completions {
|
for Completion { label, apply, .. } in &mut self.completions {
|
||||||
@ -1015,7 +1041,7 @@ impl<'a> CompletionContext<'a> {
|
|||||||
|
|
||||||
/// Add completions for all font families.
|
/// Add completions for all font families.
|
||||||
fn font_completions(&mut self) {
|
fn font_completions(&mut self) {
|
||||||
let equation = self.before[self.cursor.saturating_sub(25)..].contains("equation");
|
let equation = self.before_window(25).contains("equation");
|
||||||
for (family, iter) in self.world.book().families() {
|
for (family, iter) in self.world.book().families() {
|
||||||
let detail = summarize_font_family(iter);
|
let detail = summarize_font_family(iter);
|
||||||
if !equation || family.contains("Math") {
|
if !equation || family.contains("Math") {
|
||||||
@ -1068,13 +1094,36 @@ impl<'a> CompletionContext<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add completions for all labels.
|
/// Add completions for labels and references.
|
||||||
fn label_completions(&mut self) {
|
fn label_completions(&mut self) {
|
||||||
for (label, detail) in analyze_labels(self.world, self.frames).0 {
|
let (labels, split) = analyze_labels(self.world, self.frames);
|
||||||
|
|
||||||
|
let head = &self.text[..self.from];
|
||||||
|
let at = head.ends_with('@');
|
||||||
|
let open = !at && !head.ends_with('<');
|
||||||
|
let close = !at && !self.after.starts_with('>');
|
||||||
|
let citation = !at && self.before_window(15).contains("cite");
|
||||||
|
|
||||||
|
let (skip, take) = if at {
|
||||||
|
(0, usize::MAX)
|
||||||
|
} else if citation {
|
||||||
|
(split, usize::MAX)
|
||||||
|
} else {
|
||||||
|
(0, split)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (label, detail) in labels.into_iter().skip(skip).take(take) {
|
||||||
self.completions.push(Completion {
|
self.completions.push(Completion {
|
||||||
kind: CompletionKind::Constant,
|
kind: CompletionKind::Constant,
|
||||||
|
apply: (open || close).then(|| {
|
||||||
|
eco_format!(
|
||||||
|
"{}{}{}",
|
||||||
|
if open { "<" } else { "" },
|
||||||
|
label.0,
|
||||||
|
if close { ">" } else { "" }
|
||||||
|
)
|
||||||
|
}),
|
||||||
label: label.0,
|
label: label.0,
|
||||||
apply: None,
|
|
||||||
detail,
|
detail,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1190,6 +1239,8 @@ impl<'a> CompletionContext<'a> {
|
|||||||
"A custom HSLA color.",
|
"A custom HSLA color.",
|
||||||
);
|
);
|
||||||
self.scope_completions(false, |value| value.ty() == *ty);
|
self.scope_completions(false, |value| value.ty() == *ty);
|
||||||
|
} else if *ty == Type::of::<Label>() {
|
||||||
|
self.label_completions()
|
||||||
} else if *ty == Type::of::<Func>() {
|
} else if *ty == Type::of::<Func>() {
|
||||||
self.snippet_completion(
|
self.snippet_completion(
|
||||||
"function",
|
"function",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user