feat: adjust label and ref completion range (#1444)

* feat: adjust label and ref completion range

* docs: comment
This commit is contained in:
Myriad-Dreamin 2025-03-05 14:45:53 +08:00 committed by GitHub
parent 3ef71747c9
commit 4e9460758f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 86 additions and 41 deletions

View file

@ -157,7 +157,7 @@ pub struct CompletionCursor<'a> {
/// Cache for the last lsp range conversion. /// Cache for the last lsp range conversion.
last_lsp_range_pair: Option<(Range<usize>, LspRange)>, last_lsp_range_pair: Option<(Range<usize>, LspRange)>,
/// Cache for the ident cursor. /// Cache for the ident cursor.
ident_cursor: OnceLock<Option<LinkedNode<'a>>>, ident_cursor: OnceLock<Option<SelectedNode<'a>>>,
/// Cache for the arg cursor. /// Cache for the arg cursor.
arg_cursor: OnceLock<Option<SyntaxNode>>, arg_cursor: OnceLock<Option<SyntaxNode>>,
} }
@ -207,16 +207,37 @@ impl<'a> CompletionCursor<'a> {
matches!(self.syntax, Some(SyntaxClass::Callee(..))) matches!(self.syntax, Some(SyntaxClass::Callee(..)))
} }
/// Gets Identifier under cursor. /// Gets selected node under cursor.
fn ident_cursor(&self) -> &Option<LinkedNode> { fn selected_node(&self) -> &Option<SelectedNode<'a>> {
self.ident_cursor.get_or_init(|| { self.ident_cursor.get_or_init(|| {
// identifier
// ^ from
let is_from_ident = matches!( let is_from_ident = matches!(
self.syntax, self.syntax,
Some(SyntaxClass::Callee(..) | SyntaxClass::VarAccess(..)) Some(SyntaxClass::Callee(..) | SyntaxClass::VarAccess(..))
) && is_ident_like(&self.leaf) ) && is_ident_like(&self.leaf)
&& self.leaf.offset() == self.from; && self.leaf.offset() == self.from;
if is_from_ident {
return Some(SelectedNode::Ident(self.leaf.clone()));
}
is_from_ident.then(|| self.leaf.clone()) // <identifier
// ^ from
let is_from_label = matches!(self.syntax, Some(SyntaxClass::Label { .. }))
&& self.leaf.offset() + 1 == self.from;
if is_from_label {
return Some(SelectedNode::Label(self.leaf.clone()));
}
// @identifier
// ^ from
let is_from_ref = matches!(self.syntax, Some(SyntaxClass::Ref(..)))
&& self.leaf.offset() + 1 == self.from;
if is_from_ref {
return Some(SelectedNode::Ref(self.leaf.clone()));
}
None
}) })
} }
@ -269,23 +290,43 @@ impl<'a> CompletionCursor<'a> {
fn lsp_item_of(&mut self, item: &Completion) -> LspCompletion { fn lsp_item_of(&mut self, item: &Completion) -> LspCompletion {
// Determine range to replace // Determine range to replace
let mut snippet = item.apply.as_ref().unwrap_or(&item.label).clone(); let mut snippet = item.apply.as_ref().unwrap_or(&item.label).clone();
let replace_range = if let Some(from_ident) = self.ident_cursor() { let replace_range = match self.selected_node() {
let mut rng = from_ident.range(); Some(SelectedNode::Ident(from_ident)) => {
let mut rng = from_ident.range();
// if modifying some arguments, we need to truncate and add a comma // if modifying some arguments, we need to truncate and add a comma
if !self.is_callee() && self.cursor != rng.end && is_arg_like_context(from_ident) { if !self.is_callee() && self.cursor != rng.end && is_arg_like_context(from_ident) {
// extend comma // extend comma
if !snippet.trim_end().ends_with(',') { if !snippet.trim_end().ends_with(',') {
snippet.push_str(", "); snippet.push_str(", ");
}
// Truncate
rng.end = self.cursor;
} }
// Truncate self.lsp_range_of(rng)
rng.end = self.cursor;
} }
Some(SelectedNode::Label(from_label)) => {
let mut rng = from_label.range();
if from_label.text().starts_with('<') && !snippet.starts_with('<') {
rng.start += 1;
}
if from_label.text().ends_with('>') && !snippet.ends_with('>') {
rng.end -= 1;
}
self.lsp_range_of(rng) self.lsp_range_of(rng)
} else { }
self.lsp_range_of(self.from..self.cursor) Some(SelectedNode::Ref(from_ref)) => {
let mut rng = from_ref.range();
if from_ref.text().starts_with('@') && !snippet.starts_with('@') {
rng.start += 1;
}
self.lsp_range_of(rng)
}
None => self.lsp_range_of(self.from..self.cursor),
}; };
let text_edit = EcoTextEdit::new(replace_range, snippet); let text_edit = EcoTextEdit::new(replace_range, snippet);
@ -309,6 +350,16 @@ impl<'a> CompletionCursor<'a> {
/// Alias for a completion cursor, [`CompletionCursor`]. /// Alias for a completion cursor, [`CompletionCursor`].
type Cursor<'a> = CompletionCursor<'a>; type Cursor<'a> = CompletionCursor<'a>;
/// A node selected by [`CompletionCursor`].
enum SelectedNode<'a> {
/// Selects an identifier, e.g. `foo|` or `fo|o`.
Ident(LinkedNode<'a>),
/// Selects a label, e.g. `<foo|>` or `<fo|o>`.
Label(LinkedNode<'a>),
/// Selects a reference, e.g. `@foo|` or `@fo|o`.
Ref(LinkedNode<'a>),
}
/// Autocomplete a cursor position in a source file. /// Autocomplete a cursor position in a source file.
/// ///
/// Returns the position from which the completions apply and a list of /// Returns the position from which the completions apply and a list of
@ -390,7 +441,7 @@ impl<'a> CompletionWorker<'a> {
/// Starts the completion process. /// Starts the completion process.
pub(crate) fn work(&mut self, cursor: &mut Cursor) -> Option<()> { pub(crate) fn work(&mut self, cursor: &mut Cursor) -> Option<()> {
// Skip if is the let binding item *directly* // Skips if is the let binding item *directly*
if let Some(SyntaxClass::VarAccess(var)) = &cursor.syntax { if let Some(SyntaxClass::VarAccess(var)) = &cursor.syntax {
let node = var.node(); let node = var.node();
match node.parent_kind() { match node.parent_kind() {
@ -411,7 +462,7 @@ impl<'a> CompletionWorker<'a> {
} }
} }
// Skip if an error node starts with number (e.g. `1pt`) // Skips if an error node starts with number (e.g. `1pt`)
if matches!( if matches!(
cursor.syntax, cursor.syntax,
Some(SyntaxClass::Callee(..) | SyntaxClass::VarAccess(..) | SyntaxClass::Normal(..)) Some(SyntaxClass::Callee(..) | SyntaxClass::VarAccess(..) | SyntaxClass::Normal(..))
@ -429,7 +480,7 @@ impl<'a> CompletionWorker<'a> {
} }
} }
// Exclude it self from auto completion // Excludes it self from auto completion
// e.g. `#let x = (1.);` // e.g. `#let x = (1.);`
let self_ty = cursor.leaf.cast::<ast::Expr>().and_then(|leaf| { let self_ty = cursor.leaf.cast::<ast::Expr>().and_then(|leaf| {
let v = self.ctx.mini_eval(leaf)?; let v = self.ctx.mini_eval(leaf)?;
@ -446,8 +497,8 @@ impl<'a> CompletionWorker<'a> {
}; };
let _ = pair.complete_cursor(); let _ = pair.complete_cursor();
// Filter // Filters
if let Some(from_ident) = cursor.ident_cursor() { if let Some(SelectedNode::Ident(from_ident)) = cursor.selected_node() {
let ident_prefix = cursor.text[from_ident.offset()..cursor.cursor].to_string(); let ident_prefix = cursor.text[from_ident.offset()..cursor.cursor].to_string();
self.completions.retain(|item| { self.completions.retain(|item| {

View file

@ -3,7 +3,6 @@ source: crates/tinymist-query/src/completion.rs
description: Completion on t (44..45) description: Completion on t (44..45)
expression: "JsonRepr::new_pure(results)" expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/complete_half_label.typ input_file: crates/tinymist-query/src/fixtures/completion/complete_half_label.typ
snapshot_kind: text
--- ---
[ [
{ {
@ -20,7 +19,7 @@ snapshot_kind: text
"newText": "test>", "newText": "test>",
"range": { "range": {
"end": { "end": {
"character": 2, "character": 3,
"line": 3 "line": 3
}, },
"start": { "start": {

View file

@ -3,7 +3,6 @@ source: crates/tinymist-query/src/completion.rs
description: Completion on t (56..57) description: Completion on t (56..57)
expression: "JsonRepr::new_pure(results)" expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/complete_half_label_cite.typ input_file: crates/tinymist-query/src/fixtures/completion/complete_half_label_cite.typ
snapshot_kind: text
--- ---
[ [
{ {
@ -20,7 +19,7 @@ snapshot_kind: text
"newText": "tarry>", "newText": "tarry>",
"range": { "range": {
"end": { "end": {
"character": 7, "character": 8,
"line": 3 "line": 3
}, },
"start": { "start": {

View file

@ -3,7 +3,6 @@ source: crates/tinymist-query/src/completion.rs
description: Completion on t (103..104) description: Completion on t (103..104)
expression: "JsonRepr::new_pure(results)" expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/complete_purely_label.typ input_file: crates/tinymist-query/src/fixtures/completion/complete_purely_label.typ
snapshot_kind: text
--- ---
[ [
{ {
@ -20,7 +19,7 @@ snapshot_kind: text
"newText": "test>", "newText": "test>",
"range": { "range": {
"end": { "end": {
"character": 2, "character": 4,
"line": 11 "line": 11
}, },
"start": { "start": {

View file

@ -3,7 +3,6 @@ source: crates/tinymist-query/src/completion.rs
description: Completion on @R (135..137) description: Completion on @R (135..137)
expression: "JsonRepr::new_pure(results)" expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/completion_title.typ input_file: crates/tinymist-query/src/fixtures/completion/completion_title.typ
snapshot_kind: text
--- ---
[ [
{ {
@ -23,7 +22,7 @@ snapshot_kind: text
"newText": "Russell:1908", "newText": "Russell:1908",
"range": { "range": {
"end": { "end": {
"character": 1, "character": 2,
"line": 5 "line": 5
}, },
"start": { "start": {
@ -43,7 +42,7 @@ snapshot_kind: text
"newText": "Russell:1908", "newText": "Russell:1908",
"range": { "range": {
"end": { "end": {
"character": 1, "character": 2,
"line": 5 "line": 5
}, },
"start": { "start": {

View file

@ -3,7 +3,6 @@ source: crates/tinymist-query/src/completion.rs
description: Completion on 9 (109..110) description: Completion on 9 (109..110)
expression: "JsonRepr::new_pure(results)" expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/completion_title2.typ input_file: crates/tinymist-query/src/fixtures/completion/completion_title2.typ
snapshot_kind: text
--- ---
[ [
{ {
@ -20,7 +19,7 @@ snapshot_kind: text
"newText": "Russell:1908>", "newText": "Russell:1908>",
"range": { "range": {
"end": { "end": {
"character": 16, "character": 20,
"line": 3 "line": 3
}, },
"start": { "start": {
@ -41,7 +40,7 @@ snapshot_kind: text
"newText": "Russell:1908>", "newText": "Russell:1908>",
"range": { "range": {
"end": { "end": {
"character": 16, "character": 20,
"line": 3 "line": 3
}, },
"start": { "start": {

View file

@ -3,7 +3,6 @@ source: crates/tinymist-query/src/completion.rs
description: Completion on e (57..58) description: Completion on e (57..58)
expression: "JsonRepr::new_pure(results)" expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/label_middle.typ input_file: crates/tinymist-query/src/fixtures/completion/label_middle.typ
snapshot_kind: text
--- ---
[ [
{ {
@ -20,7 +19,7 @@ snapshot_kind: text
"newText": "abcd>", "newText": "abcd>",
"range": { "range": {
"end": { "end": {
"character": 4, "character": 5,
"line": 3 "line": 3
}, },
"start": { "start": {
@ -41,7 +40,7 @@ snapshot_kind: text
"newText": "abef>", "newText": "abef>",
"range": { "range": {
"end": { "end": {
"character": 4, "character": 5,
"line": 3 "line": 3
}, },
"start": { "start": {
@ -62,7 +61,7 @@ snapshot_kind: text
"newText": "efg>", "newText": "efg>",
"range": { "range": {
"end": { "end": {
"character": 4, "character": 5,
"line": 3 "line": 3
}, },
"start": { "start": {

View file

@ -18,7 +18,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/ref_half.typ
"newText": "test", "newText": "test",
"range": { "range": {
"end": { "end": {
"character": 1, "character": 2,
"line": 11 "line": 11
}, },
"start": { "start": {

View file

@ -18,7 +18,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/ref_half2.typ
"newText": "test", "newText": "test",
"range": { "range": {
"end": { "end": {
"character": 1, "character": 3,
"line": 11 "line": 11
}, },
"start": { "start": {
@ -43,7 +43,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/ref_half2.typ
"newText": "test", "newText": "test",
"range": { "range": {
"end": { "end": {
"character": 2, "character": 3,
"line": 11 "line": 11
}, },
"start": { "start": {

View file

@ -18,7 +18,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/ref_half_colon.typ
"newText": "sec:it", "newText": "sec:it",
"range": { "range": {
"end": { "end": {
"character": 5, "character": 6,
"line": 13 "line": 13
}, },
"start": { "start": {