dev: insert commas in arg context for completing before identifiers (#251)

This commit is contained in:
Myriad-Dreamin 2024-05-07 19:03:25 +08:00 committed by GitHub
parent c640d53396
commit bbded48a1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 116 additions and 13 deletions

View file

@ -99,6 +99,7 @@ impl StatefulRequest for CompletionRequest {
let mut match_ident = None;
let mut completion_result = None;
let is_callee = matches!(deref_target, Some(DerefTarget::Callee(..)));
match deref_target {
Some(DerefTarget::Callee(v) | DerefTarget::VarAccess(v)) => {
if v.is::<ast::Ident>() {
@ -152,10 +153,9 @@ impl StatefulRequest for CompletionRequest {
let replace_range;
if match_ident.as_ref().is_some_and(|i| i.offset() == offset) {
let match_ident = match_ident.unwrap();
let rng = match_ident.range();
replace_range = ctx.to_lsp_range(match_ident.range(), &source);
let mut rng = match_ident.range();
let ident_prefix = source.text()[rng.start..cursor].to_string();
completions.retain(|c| {
// c.label
let mut prefix_matcher = c.label.chars();
@ -171,6 +171,29 @@ impl StatefulRequest for CompletionRequest {
true
});
// if modifying some arguments, we need to truncate and add a comma
if !is_callee && cursor != rng.end && is_arg_like_context(&match_ident) {
// extend comma
for c in completions.iter_mut() {
let apply = match &mut c.apply {
Some(w) => w,
None => {
c.apply = Some(c.label.clone());
c.apply.as_mut().unwrap()
}
};
if apply.trim_end().ends_with(',') {
continue;
}
apply.push_str(", ");
}
// Truncate
rng.end = cursor;
}
replace_range = ctx.to_lsp_range(rng, &source);
} else {
let lsp_start_position = ctx.to_lsp_pos(offset, &source);
replace_range = LspRange::new(lsp_start_position, self.position);
@ -198,6 +221,28 @@ impl StatefulRequest for CompletionRequest {
}
}
fn is_arg_like_context(mut matching: &LinkedNode) -> bool {
while let Some(parent) = matching.parent() {
use SyntaxKind::*;
// if parent.kind() == SyntaxKind::Markup | SyntaxKind::Markup |
// SyntaxKind::Markup { return true;
// }
// if parent.kind() == SyntaxKind::Args {
// return true;
// }
// todo: contextual
match parent.kind() {
ContentBlock | Equation | CodeBlock | Markup | Math | Code => return false,
Args | Params | Destructuring | Array | Dict => return true,
_ => {}
}
matching = parent;
}
false
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;

View file

@ -0,0 +1,17 @@
// contains: content,authors,font,class
#let tmpl(content, authors: (), font: none, class: "article") = {
if class != "article" and class != "letter" {
panic("")
}
set document(author: authors)
set text(font: font)
set page(paper: "a4") if class == "article"
set page(paper: "us-letter") if class == "letter"
content
}
#tmpl(/* range after 1..2 */authors: (),)

View file

@ -0,0 +1,31 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on u (371..372)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/func_args_after.typ
---
[
{
"isIncomplete": false,
"items": [
{
"kind": 6,
"label": "class",
"sortText": "000",
"textEdit": {
"newText": "class: ${1:}, ",
"range": {
"end": {
"character": 29,
"line": 16
},
"start": {
"character": 28,
"line": 16
}
}
}
}
]
}
]

View file

@ -158,18 +158,28 @@ pub fn run_with_sources<T>(source: &str, f: impl FnOnce(&mut TypstSystemWorld, P
pub fn find_test_range(s: &Source) -> Range<usize> {
// /* range -3..-1 */
let re = s.text().find("/* range ").unwrap();
let re_base = re;
let re = re + "/* range ".len();
let re = re..s.text().find(" */").unwrap();
let re = &s.text()[re];
fn find_prefix(s: &str, sub: &str, left: bool) -> Option<(usize, usize, bool)> {
Some((s.find(sub)?, sub.len(), left))
}
let (re_base, re_len, is_after) = find_prefix(s.text(), "/* range after ", true)
.or_else(|| find_prefix(s.text(), "/* range ", false))
.unwrap();
let re_end = re_base + re_len;
let range_rng = re_end..(s.text()[re_end..].find(" */").unwrap() + re_end);
let range_base = if is_after {
range_rng.end + " */".len()
} else {
re_base
};
let range = &s.text()[range_rng];
// split by ".."
let mut re = re.split("..");
let mut bounds = range.split("..");
// parse the range
let start: isize = re.next().unwrap().parse().unwrap();
let end: isize = re.next().unwrap().parse().unwrap();
let start = start + re_base as isize;
let end = end + re_base as isize;
let start: isize = bounds.next().unwrap().parse().unwrap();
let end: isize = bounds.next().unwrap().parse().unwrap();
let start = start + range_base as isize;
let end = end + range_base as isize;
start as usize..end as usize
}