mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Do not complete existing positional parameters
This commit is contained in:
parent
0b143c7df9
commit
03ba5a0cb1
@ -9,6 +9,7 @@ use typst::foundations::{
|
||||
Scope, StyleChain, Styles, Type, Value,
|
||||
};
|
||||
use typst::model::Document;
|
||||
use typst::syntax::ast::AstNode;
|
||||
use typst::syntax::{
|
||||
ast, is_id_continue, is_id_start, is_ident, LinkedNode, Side, Source, SyntaxKind,
|
||||
};
|
||||
@ -40,7 +41,9 @@ pub fn autocomplete(
|
||||
cursor: usize,
|
||||
explicit: bool,
|
||||
) -> Option<(usize, Vec<Completion>)> {
|
||||
let mut ctx = CompletionContext::new(world, document, source, cursor, explicit)?;
|
||||
let leaf = LinkedNode::new(source.root()).leaf_at(cursor, Side::Before)?;
|
||||
let mut ctx =
|
||||
CompletionContext::new(world, document, source, &leaf, cursor, explicit)?;
|
||||
|
||||
let _ = complete_comments(&mut ctx)
|
||||
|| complete_field_accesses(&mut ctx)
|
||||
@ -650,7 +653,7 @@ fn show_rule_recipe_completions(ctx: &mut CompletionContext) {
|
||||
/// Complete call and set rule parameters.
|
||||
fn complete_params(ctx: &mut CompletionContext) -> bool {
|
||||
// Ensure that we are in a function call or set rule's argument list.
|
||||
let (callee, set, args) = if_chain! {
|
||||
let (callee, set, args, args_linked) = if_chain! {
|
||||
if let Some(parent) = ctx.leaf.parent();
|
||||
if let Some(parent) = match parent.kind() {
|
||||
SyntaxKind::Named => parent.parent(),
|
||||
@ -666,7 +669,7 @@ fn complete_params(ctx: &mut CompletionContext) -> bool {
|
||||
_ => None,
|
||||
};
|
||||
then {
|
||||
(callee, set, args)
|
||||
(callee, set, args, parent)
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@ -706,7 +709,7 @@ fn complete_params(ctx: &mut CompletionContext) -> bool {
|
||||
ctx.from = ctx.cursor.min(next.offset());
|
||||
}
|
||||
|
||||
param_completions(ctx, callee, set, args);
|
||||
param_completions(ctx, callee, set, args, args_linked);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -720,29 +723,49 @@ fn param_completions<'a>(
|
||||
callee: ast::Expr<'a>,
|
||||
set: bool,
|
||||
args: ast::Args<'a>,
|
||||
args_linked: &'a LinkedNode<'a>,
|
||||
) {
|
||||
let Some(func) = resolve_global_callee(ctx, callee) 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 {
|
||||
if exclude.iter().any(|ident| ident.as_str() == param.name) {
|
||||
continue;
|
||||
// Determine which arguments are already present.
|
||||
let mut existing_positional = 0;
|
||||
let mut existing_named = HashSet::new();
|
||||
for arg in args.items() {
|
||||
match arg {
|
||||
ast::Arg::Pos(_) => {
|
||||
let Some(node) = args_linked.find(arg.span()) else { continue };
|
||||
if node.range().end < ctx.cursor {
|
||||
existing_positional += 1;
|
||||
}
|
||||
}
|
||||
ast::Arg::Named(named) => {
|
||||
existing_named.insert(named.name().as_str());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let mut skipped_positional = 0;
|
||||
for param in params {
|
||||
if set && !param.settable {
|
||||
continue;
|
||||
}
|
||||
|
||||
if param.positional {
|
||||
if skipped_positional < existing_positional && !param.variadic {
|
||||
skipped_positional += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
ctx.cast_completions(¶m.input);
|
||||
}
|
||||
|
||||
if param.named {
|
||||
if existing_named.contains(¶m.name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ctx.completions.push(Completion {
|
||||
kind: CompletionKind::Param,
|
||||
label: param.name.into(),
|
||||
@ -750,10 +773,6 @@ fn param_completions<'a>(
|
||||
detail: Some(plain_docs_sentence(param.docs)),
|
||||
});
|
||||
}
|
||||
|
||||
if param.positional {
|
||||
ctx.cast_completions(¶m.input);
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.before.ends_with(',') {
|
||||
@ -1011,7 +1030,7 @@ struct CompletionContext<'a> {
|
||||
text: &'a str,
|
||||
before: &'a str,
|
||||
after: &'a str,
|
||||
leaf: LinkedNode<'a>,
|
||||
leaf: &'a LinkedNode<'a>,
|
||||
cursor: usize,
|
||||
explicit: bool,
|
||||
from: usize,
|
||||
@ -1025,12 +1044,12 @@ impl<'a> CompletionContext<'a> {
|
||||
world: &'a (dyn World + 'a),
|
||||
document: Option<&'a Document>,
|
||||
source: &'a Source,
|
||||
leaf: &'a LinkedNode<'a>,
|
||||
cursor: usize,
|
||||
explicit: bool,
|
||||
) -> Option<Self> {
|
||||
let text = source.text();
|
||||
let library = world.library();
|
||||
let leaf = LinkedNode::new(source.root()).leaf_at(cursor, Side::Before)?;
|
||||
Some(Self {
|
||||
world,
|
||||
document,
|
||||
@ -1538,4 +1557,20 @@ mod tests {
|
||||
test("#()", 1).must_apply("list", None);
|
||||
test("#[]", 1).must_apply("strong", None);
|
||||
}
|
||||
|
||||
/// Test that we only complete positional parameters if they aren't
|
||||
/// already present.
|
||||
#[test]
|
||||
fn test_autocomplete_positional_param() {
|
||||
// No string given yet.
|
||||
test("#numbering()", -1).must_include(["string", "integer"]);
|
||||
// String is already given.
|
||||
test("#numbering(\"foo\", )", -1)
|
||||
.must_include(["integer"])
|
||||
.must_exclude(["string"]);
|
||||
// Integer is already given, but numbering is variadic.
|
||||
test("#numbering(\"foo\", 1, )", -1)
|
||||
.must_include(["integer"])
|
||||
.must_exclude(["string"]);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user