feat: forbid signature help in some cases (#1742)

* feat: forbid signature help in some cases

* test: update snapshot
This commit is contained in:
Myriad-Dreamin 2025-05-06 14:55:24 +08:00 committed by GitHub
parent 29893aa15f
commit 4ded5a624a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 173 additions and 56 deletions

View file

@ -1334,6 +1334,22 @@ fn callee_context<'a>(callee: LinkedNode<'a>, node: LinkedNode<'a>) -> Option<Sy
}; };
let args = parent.find(args.span())?; let args = parent.find(args.span())?;
let mut parent = &node;
loop {
use SyntaxKind::*;
match parent.kind() {
ContentBlock | CodeBlock | Str | Raw | LineComment | BlockComment => {
return Option::None
}
Args if parent.range() == args.range() => {
break;
}
_ => {}
}
parent = parent.parent()?;
}
let is_set = parent.kind() == SyntaxKind::SetRule; let is_set = parent.kind() == SyntaxKind::SetRule;
let target = arg_context(args.clone(), node, ArgSourceKind::Call)?; let target = arg_context(args.clone(), node, ArgSourceKind::Call)?;
Some(SyntaxContext::Arg { Some(SyntaxContext::Arg {

View file

@ -99,7 +99,7 @@ mod tests {
let result = request.request(ctx); let result = request.request(ctx);
with_settings!({ with_settings!({
description => format!("Code Action on {})", make_range_annoation(&source)), description => format!("Code Action on {}", make_range_annoation(&source)),
}, { }, {
assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC)); assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC));
}) })

View file

@ -1,3 +1,3 @@
#let f(x) = x; #let f(x) = x;
#(f(/* position */)); #(f(/* loc 0, 0 */));

View file

@ -1 +1 @@
#(math.underline(/* position */)); #(math.underline(/* loc 0, 0 */));

View file

@ -1 +1 @@
$underline(/* position */)$ $underline(/* loc 0, 0 */)$

View file

@ -1 +1 @@
#show list.item.where(/* position */): it => it #show list.item.where(/* loc 0, 0 */): it => it

View file

@ -0,0 +1,2 @@
/* loc 1,6 */
#strong("")

View file

@ -0,0 +1,2 @@
/* loc 1,7 */
#strong("")

View file

@ -0,0 +1,4 @@
#let cmp = l => r => l == r
/* loc 1, 5 */
#cmp(1)(1)

View file

@ -0,0 +1,4 @@
#let cmp = l => r => l == r
/* loc 1, 8 */
#cmp(1)(1)

View file

@ -0,0 +1,2 @@
/* loc 1,9 */
#strong("")

View file

@ -1,8 +1,8 @@
--- ---
source: crates/tinymist-query/src/signature_help.rs source: crates/tinymist-query/src/signature_help.rs
description: "signature help on = x;\n\n#(f(|/* loc 0, "
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/signature_help/base.typ input_file: crates/tinymist-query/src/fixtures/signature_help/base.typ
snapshot_kind: text
--- ---
{ {
"activeSignature": 0, "activeSignature": 0,

View file

@ -1,8 +1,8 @@
--- ---
source: crates/tinymist-query/src/signature_help.rs source: crates/tinymist-query/src/signature_help.rs
description: "signature help on underline(|/* loc 0, "
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/signature_help/builtin.typ input_file: crates/tinymist-query/src/fixtures/signature_help/builtin.typ
snapshot_kind: text
--- ---
{ {
"activeSignature": 0, "activeSignature": 0,

View file

@ -1,8 +1,8 @@
--- ---
source: crates/tinymist-query/src/signature_help.rs source: crates/tinymist-query/src/signature_help.rs
description: "signature help on underline(|/* loc 0, "
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/signature_help/builtin2.typ input_file: crates/tinymist-query/src/fixtures/signature_help/builtin2.typ
snapshot_kind: text
--- ---
{ {
"activeSignature": 0, "activeSignature": 0,

View file

@ -1,8 +1,8 @@
--- ---
source: crates/tinymist-query/src/signature_help.rs source: crates/tinymist-query/src/signature_help.rs
description: "signature help on tem.where(|/* loc 0, "
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/signature_help/list_item.typ input_file: crates/tinymist-query/src/fixtures/signature_help/list_item.typ
snapshot_kind: text
--- ---
{ {
"activeSignature": 0, "activeSignature": 0,

View file

@ -0,0 +1,7 @@
---
source: crates/tinymist-query/src/signature_help.rs
description: "signature help on */\n#stron|g(\"\")\n"
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/signature_help/pos_callee.typ
---
null

View file

@ -0,0 +1,35 @@
---
source: crates/tinymist-query/src/signature_help.rs
description: "signature help on */\n#strong|(\"\")\n"
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/signature_help/pos_callee2.typ
---
{
"activeSignature": 0,
"signatures": [
{
"activeParameter": 0,
"documentation": {
"kind": "markdown",
"value": "Strongly emphasizes content by increasing the font weight.\n\nIncreases the current font weight by a given `delta`.\n\n# Example\n```example\nThis is *strong.* \\\nThis is #strong[too.] \\\n\n#show strong: set text(red)\nAnd this is *evermore.*\n```\n\n# Syntax\nThis function also has dedicated syntax: To strongly emphasize content,\nsimply enclose it in stars/asterisks (`*`). Note that this only works at\nword boundaries. To strongly emphasize part of a word, you have to use the\nfunction."
},
"label": "strong(body: content, delta: int) -> strong",
"parameters": [
{
"documentation": {
"kind": "markdown",
"value": "The content to strongly emphasize."
},
"label": "body:"
},
{
"documentation": {
"kind": "markdown",
"value": "The delta to apply on the font weight.\n\n```example\n#set strong(delta: 0)\nNo *effect!*\n```"
},
"label": "delta:"
}
]
}
]
}

View file

@ -0,0 +1,7 @@
---
source: crates/tinymist-query/src/signature_help.rs
description: "signature help on 5 */\n#cmp(|1)(1)\n"
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/signature_help/pos_chain_call.typ
---
null

View file

@ -0,0 +1,7 @@
---
source: crates/tinymist-query/src/signature_help.rs
description: "signature help on /\n#cmp(1)(|1)\n"
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/signature_help/pos_chain_call2.typ
---
null

View file

@ -0,0 +1,7 @@
---
source: crates/tinymist-query/src/signature_help.rs
description: "signature help on \n#strong(\"|\")\n"
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/signature_help/pos_string.typ
---
null

View file

@ -1,8 +1,8 @@
--- ---
source: crates/tinymist-query/src/signature_help.rs source: crates/tinymist-query/src/signature_help.rs
description: "signature help on ine.where(|/* loc 0, "
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/signature_help/where.typ input_file: crates/tinymist-query/src/fixtures/signature_help/where.typ
snapshot_kind: text
--- ---
{ {
"activeSignature": 0, "activeSignature": 0,

View file

@ -1 +1 @@
#show math.underline.where(/* position */): it => it #show math.underline.where(/* loc 0, 0 */): it => it

View file

@ -141,14 +141,16 @@ mod tests {
fn test() { fn test() {
snapshot_testing("signature_help", &|ctx, path| { snapshot_testing("signature_help", &|ctx, path| {
let source = ctx.source_by_path(&path).unwrap(); let source = ctx.source_by_path(&path).unwrap();
let (position, anno) = make_pos_annoation(&source);
let request = SignatureHelpRequest { let request = SignatureHelpRequest { path, position };
path: path.clone(),
position: find_test_position(&source),
};
let result = request.request(ctx); let result = request.request(ctx);
assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC)); with_settings!({
description => format!("signature help on {anno}"),
}, {
assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC));
})
}); });
} }
} }

View file

@ -28,7 +28,7 @@ pub use tinymist_world::WorldComputeGraph;
pub use crate::syntax::find_module_level_docs; pub use crate::syntax::find_module_level_docs;
use crate::{analysis::Analysis, prelude::LocalContext, LspPosition, PositionEncoding}; use crate::{analysis::Analysis, prelude::LocalContext, LspPosition, PositionEncoding};
use crate::{to_lsp_position, CompletionFeat}; use crate::{to_lsp_position, to_typst_position, CompletionFeat};
pub fn snapshot_testing(name: &str, f: &impl Fn(&mut LocalContext, PathBuf)) { pub fn snapshot_testing(name: &str, f: &impl Fn(&mut LocalContext, PathBuf)) {
tinymist_tests::snapshot_testing!(name, |verse, path| { tinymist_tests::snapshot_testing!(name, |verse, path| {
@ -174,50 +174,60 @@ pub fn find_test_lsp_pos(s: &Source, offset: usize) -> LspPosition {
} }
pub fn find_test_typst_pos(s: &Source) -> usize { pub fn find_test_typst_pos(s: &Source) -> usize {
enum AstMatcher { enum PosMatcher {
MatchAny { prev: bool }, Pos { prev: bool, ident: bool },
MatchIdent { prev: bool }, LoC { line: i32, column: i32 },
} }
use AstMatcher::*; use PosMatcher::*;
let re = s fn pos(prev: bool, ident: bool) -> Option<PosMatcher> {
.text() Some(Pos { prev, ident })
.find("/* position */") }
.zip(Some(MatchAny { prev: true }));
let re = re.or_else(|| {
s.text()
.find("/* position after */")
.zip(Some(MatchAny { prev: false }))
});
let re = re.or_else(|| {
s.text()
.find("/* ident */")
.zip(Some(MatchIdent { prev: true }))
});
let re = re.or_else(|| {
s.text()
.find("/* ident after */")
.zip(Some(MatchIdent { prev: false }))
});
let (re, m) = re
.ok_or_else(|| panic!("No position marker found in source:\n{}", s.text()))
.unwrap();
let n = LinkedNode::new(s.root()); let re = s.text().find("/* position */").zip(pos(true, false));
let mut n = n.leaf_at_compat(re + 1).unwrap(); let re = re.or_else(|| s.text().find("/* position after */").zip(pos(false, false)));
let re = re.or_else(|| s.text().find("/* ident */").zip(pos(true, true)));
let re = re.or_else(|| s.text().find("/* ident after */").zip(pos(false, true)));
let re = re.or_else(|| {
let re = s.text().find("/* loc ")?;
let (parts, _) = s.text()[re + "/* loc ".len()..]
.trim()
.split_once("*/")
.expect("bad loc marker");
let (line, column) = parts.split_once(',').expect("bad loc marker");
let line = line.trim().parse::<i32>().expect("bad loc marker");
let column = column.trim().parse::<i32>().expect("bad loc marker");
Some((re, LoC { line, column }))
});
let match_prev = match &m { let Some((rel_offset, matcher)) = re else {
MatchAny { prev } => *prev, panic!("No (or bad) position marker found in source:\n{}", s.text())
MatchIdent { prev } => *prev,
};
let match_ident = match m {
MatchAny { .. } => false,
MatchIdent { .. } => true,
}; };
match matcher {
Pos { prev, ident } => {
let node = LinkedNode::new(s.root());
let node = node.leaf_at_compat(rel_offset + 1).unwrap();
match_by_pos(node, prev, ident)
}
LoC { line, column } => {
let column = if line != 0 { column } else { 0 };
let rel_pos = to_lsp_position(rel_offset, PositionEncoding::Utf16, s);
let pos = LspPosition {
line: (rel_pos.line as i32 + line) as u32,
character: (rel_pos.character as i32 + column) as u32,
};
to_typst_position(pos, PositionEncoding::Utf16, s).expect("invalid loc")
}
}
}
fn match_by_pos(mut n: LinkedNode, prev: bool, ident: bool) -> usize {
'match_loop: loop { 'match_loop: loop {
if n.kind().is_trivia() || n.kind().is_error() { if n.kind().is_trivia() || n.kind().is_error() {
let m = if match_prev { let m = if prev {
n.prev_sibling() n.prev_sibling()
} else { } else {
n.next_sibling() n.next_sibling()
@ -226,7 +236,7 @@ pub fn find_test_typst_pos(s: &Source) -> usize {
continue; continue;
} }
if matches!(n.kind(), SyntaxKind::Named) { if matches!(n.kind(), SyntaxKind::Named) {
if match_ident { if ident {
n = n n = n
.children() .children()
.find(|n| matches!(n.kind(), SyntaxKind::Ident)) .find(|n| matches!(n.kind(), SyntaxKind::Ident))
@ -236,7 +246,7 @@ pub fn find_test_typst_pos(s: &Source) -> usize {
} }
continue; continue;
} }
if match_ident { if ident {
match n.kind() { match n.kind() {
SyntaxKind::Closure => { SyntaxKind::Closure => {
let closure = n.cast::<ast::Closure>().unwrap(); let closure = n.cast::<ast::Closure>().unwrap();
@ -265,6 +275,18 @@ pub fn find_test_typst_pos(s: &Source) -> usize {
n.offset() n.offset()
} }
pub fn make_pos_annoation(source: &Source) -> (LspPosition, String) {
let pos = find_test_typst_pos(source);
let range_before = pos.saturating_sub(10)..pos;
let range_after = pos..pos.saturating_add(10).min(source.text().len());
let window_before = &source.text()[range_before];
let window_after = &source.text()[range_after];
let pos = to_lsp_position(pos, PositionEncoding::Utf16, source);
(pos, format!("{window_before}|{window_after}"))
}
pub fn make_range_annoation(source: &Source) -> String { pub fn make_range_annoation(source: &Source) -> String {
let range = find_test_range_(source); let range = find_test_range_(source);
let range_before = range.start.saturating_sub(10)..range.start; let range_before = range.start.saturating_sub(10)..range.start;

View file

@ -388,7 +388,7 @@ fn e2e() {
}); });
let hash = replay_log(&tinymist_binary, &root.join("vscode")); let hash = replay_log(&tinymist_binary, &root.join("vscode"));
insta::assert_snapshot!(hash, @"siphash128_13:3cce756a8b70867a8041cfab6f4337ef"); insta::assert_snapshot!(hash, @"siphash128_13:bc403d246654d90466ba47cf9057fee2");
} }
} }