mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +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,
|
Scope, StyleChain, Styles, Type, Value,
|
||||||
};
|
};
|
||||||
use typst::model::Document;
|
use typst::model::Document;
|
||||||
|
use typst::syntax::ast::AstNode;
|
||||||
use typst::syntax::{
|
use typst::syntax::{
|
||||||
ast, is_id_continue, is_id_start, is_ident, LinkedNode, Side, Source, SyntaxKind,
|
ast, is_id_continue, is_id_start, is_ident, LinkedNode, Side, Source, SyntaxKind,
|
||||||
};
|
};
|
||||||
@ -40,7 +41,9 @@ pub fn autocomplete(
|
|||||||
cursor: usize,
|
cursor: usize,
|
||||||
explicit: bool,
|
explicit: bool,
|
||||||
) -> Option<(usize, Vec<Completion>)> {
|
) -> 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)
|
let _ = complete_comments(&mut ctx)
|
||||||
|| complete_field_accesses(&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.
|
/// Complete call and set rule parameters.
|
||||||
fn complete_params(ctx: &mut CompletionContext) -> bool {
|
fn complete_params(ctx: &mut CompletionContext) -> bool {
|
||||||
// Ensure that we are in a function call or set rule's argument list.
|
// 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) = ctx.leaf.parent();
|
||||||
if let Some(parent) = match parent.kind() {
|
if let Some(parent) = match parent.kind() {
|
||||||
SyntaxKind::Named => parent.parent(),
|
SyntaxKind::Named => parent.parent(),
|
||||||
@ -666,7 +669,7 @@ fn complete_params(ctx: &mut CompletionContext) -> bool {
|
|||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
then {
|
then {
|
||||||
(callee, set, args)
|
(callee, set, args, parent)
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -706,7 +709,7 @@ fn complete_params(ctx: &mut CompletionContext) -> bool {
|
|||||||
ctx.from = ctx.cursor.min(next.offset());
|
ctx.from = ctx.cursor.min(next.offset());
|
||||||
}
|
}
|
||||||
|
|
||||||
param_completions(ctx, callee, set, args);
|
param_completions(ctx, callee, set, args, args_linked);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -720,29 +723,49 @@ fn param_completions<'a>(
|
|||||||
callee: ast::Expr<'a>,
|
callee: ast::Expr<'a>,
|
||||||
set: bool,
|
set: bool,
|
||||||
args: ast::Args<'a>,
|
args: ast::Args<'a>,
|
||||||
|
args_linked: &'a LinkedNode<'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.
|
// Determine which arguments are already present.
|
||||||
let exclude: Vec<_> = args
|
let mut existing_positional = 0;
|
||||||
.items()
|
let mut existing_named = HashSet::new();
|
||||||
.filter_map(|arg| match arg {
|
for arg in args.items() {
|
||||||
ast::Arg::Named(named) => Some(named.name()),
|
match arg {
|
||||||
_ => None,
|
ast::Arg::Pos(_) => {
|
||||||
})
|
let Some(node) = args_linked.find(arg.span()) else { continue };
|
||||||
.collect();
|
if node.range().end < ctx.cursor {
|
||||||
|
existing_positional += 1;
|
||||||
for param in params {
|
}
|
||||||
if exclude.iter().any(|ident| ident.as_str() == param.name) {
|
}
|
||||||
continue;
|
ast::Arg::Named(named) => {
|
||||||
|
existing_named.insert(named.name().as_str());
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut skipped_positional = 0;
|
||||||
|
for param in params {
|
||||||
if set && !param.settable {
|
if set && !param.settable {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if param.positional {
|
||||||
|
if skipped_positional < existing_positional && !param.variadic {
|
||||||
|
skipped_positional += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.cast_completions(¶m.input);
|
||||||
|
}
|
||||||
|
|
||||||
if param.named {
|
if param.named {
|
||||||
|
if existing_named.contains(¶m.name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
ctx.completions.push(Completion {
|
ctx.completions.push(Completion {
|
||||||
kind: CompletionKind::Param,
|
kind: CompletionKind::Param,
|
||||||
label: param.name.into(),
|
label: param.name.into(),
|
||||||
@ -750,10 +773,6 @@ fn param_completions<'a>(
|
|||||||
detail: Some(plain_docs_sentence(param.docs)),
|
detail: Some(plain_docs_sentence(param.docs)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if param.positional {
|
|
||||||
ctx.cast_completions(¶m.input);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.before.ends_with(',') {
|
if ctx.before.ends_with(',') {
|
||||||
@ -1011,7 +1030,7 @@ struct CompletionContext<'a> {
|
|||||||
text: &'a str,
|
text: &'a str,
|
||||||
before: &'a str,
|
before: &'a str,
|
||||||
after: &'a str,
|
after: &'a str,
|
||||||
leaf: LinkedNode<'a>,
|
leaf: &'a LinkedNode<'a>,
|
||||||
cursor: usize,
|
cursor: usize,
|
||||||
explicit: bool,
|
explicit: bool,
|
||||||
from: usize,
|
from: usize,
|
||||||
@ -1025,12 +1044,12 @@ impl<'a> CompletionContext<'a> {
|
|||||||
world: &'a (dyn World + 'a),
|
world: &'a (dyn World + 'a),
|
||||||
document: Option<&'a Document>,
|
document: Option<&'a Document>,
|
||||||
source: &'a Source,
|
source: &'a Source,
|
||||||
|
leaf: &'a LinkedNode<'a>,
|
||||||
cursor: usize,
|
cursor: usize,
|
||||||
explicit: bool,
|
explicit: bool,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
let text = source.text();
|
let text = source.text();
|
||||||
let library = world.library();
|
let library = world.library();
|
||||||
let leaf = LinkedNode::new(source.root()).leaf_at(cursor, Side::Before)?;
|
|
||||||
Some(Self {
|
Some(Self {
|
||||||
world,
|
world,
|
||||||
document,
|
document,
|
||||||
@ -1538,4 +1557,20 @@ mod tests {
|
|||||||
test("#()", 1).must_apply("list", None);
|
test("#()", 1).must_apply("list", None);
|
||||||
test("#[]", 1).must_apply("strong", 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