From 61be2f78e40b0a33d9fe36911f448da5468e5905 Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin <35292584+Myriad-Dreamin@users.noreply.github.com> Date: Fri, 15 Mar 2024 00:37:57 +0800 Subject: [PATCH] feat: implement inlay hint configuration (#37) * fix: unstable order of reference results * feat: inlay hint configuration * dev: test inlay hint on `lr` --- .../src/fixtures/inlay_hints/base.typ | 3 +- .../fixtures/inlay_hints/math_markup_mod.typ | 2 + .../src/fixtures/inlay_hints/math_mod.typ | 1 + .../src/fixtures/inlay_hints/named.typ | 1 + .../src/fixtures/inlay_hints/named_or_pos.typ | 3 + ...test@base.typ.snap => smart@base.typ.snap} | 14 +- ...p => smart@imcomplete-expression.typ.snap} | 0 .../snaps/smart@math_markup_mod.typ.snap | 23 +++ .../inlay_hints/snaps/smart@math_mod.typ.snap | 6 + .../inlay_hints/snaps/smart@named.typ.snap | 6 + .../snaps/smart@named_or_pos.typ.snap | 6 + .../snaps/smart@unique_positional.typ.snap | 6 + .../inlay_hints/unique_positional.typ | 2 + .../references/snaps/test@at_def.typ.snap | 4 +- .../references/snaps/test@redefine.typ.snap | 6 +- crates/tinymist-query/src/inlay_hint.rs | 143 ++++++++++++++++-- crates/tinymist-query/src/references.rs | 8 + 17 files changed, 216 insertions(+), 18 deletions(-) create mode 100644 crates/tinymist-query/src/fixtures/inlay_hints/math_markup_mod.typ create mode 100644 crates/tinymist-query/src/fixtures/inlay_hints/math_mod.typ create mode 100644 crates/tinymist-query/src/fixtures/inlay_hints/named.typ create mode 100644 crates/tinymist-query/src/fixtures/inlay_hints/named_or_pos.typ rename crates/tinymist-query/src/fixtures/inlay_hints/snaps/{test@base.typ.snap => smart@base.typ.snap} (61%) rename crates/tinymist-query/src/fixtures/inlay_hints/snaps/{test@imcomplete-expression.typ.snap => smart@imcomplete-expression.typ.snap} (100%) create mode 100644 crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@math_markup_mod.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@math_mod.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@named.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@named_or_pos.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@unique_positional.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/inlay_hints/unique_positional.typ diff --git a/crates/tinymist-query/src/fixtures/inlay_hints/base.typ b/crates/tinymist-query/src/fixtures/inlay_hints/base.typ index 516b0bff8..58cf0dd33 100644 --- a/crates/tinymist-query/src/fixtures/inlay_hints/base.typ +++ b/crates/tinymist-query/src/fixtures/inlay_hints/base.typ @@ -1 +1,2 @@ -#text("") +#let f(x, y) = x + y +#f(1, 2) diff --git a/crates/tinymist-query/src/fixtures/inlay_hints/math_markup_mod.typ b/crates/tinymist-query/src/fixtures/inlay_hints/math_markup_mod.typ new file mode 100644 index 000000000..6207a0448 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/inlay_hints/math_markup_mod.typ @@ -0,0 +1,2 @@ +#let f(x, y) = [#(x + y)]; +$lr(#f(1, 2))$ \ No newline at end of file diff --git a/crates/tinymist-query/src/fixtures/inlay_hints/math_mod.typ b/crates/tinymist-query/src/fixtures/inlay_hints/math_mod.typ new file mode 100644 index 000000000..dc8a46ae4 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/inlay_hints/math_mod.typ @@ -0,0 +1 @@ +$lr(1, 2, 3)$ \ No newline at end of file diff --git a/crates/tinymist-query/src/fixtures/inlay_hints/named.typ b/crates/tinymist-query/src/fixtures/inlay_hints/named.typ new file mode 100644 index 000000000..ea04f7f20 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/inlay_hints/named.typ @@ -0,0 +1 @@ +#table(align: left)[] diff --git a/crates/tinymist-query/src/fixtures/inlay_hints/named_or_pos.typ b/crates/tinymist-query/src/fixtures/inlay_hints/named_or_pos.typ new file mode 100644 index 000000000..d77f54d9e --- /dev/null +++ b/crates/tinymist-query/src/fixtures/inlay_hints/named_or_pos.typ @@ -0,0 +1,3 @@ +#text("") +#text(red, "") +#text(18pt, red, "") diff --git a/crates/tinymist-query/src/fixtures/inlay_hints/snaps/test@base.typ.snap b/crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@base.typ.snap similarity index 61% rename from crates/tinymist-query/src/fixtures/inlay_hints/snaps/test@base.typ.snap rename to crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@base.typ.snap index dcdde7866..80d5a2388 100644 --- a/crates/tinymist-query/src/fixtures/inlay_hints/snaps/test@base.typ.snap +++ b/crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@base.typ.snap @@ -6,10 +6,18 @@ input_file: crates/tinymist-query/src/fixtures/inlay_hints/base.typ [ { "kind": 2, - "label": ": body", + "label": ": x", "position": { - "character": 8, - "line": 0 + "character": 4, + "line": 1 + } + }, + { + "kind": 2, + "label": ": y", + "position": { + "character": 7, + "line": 1 } } ] diff --git a/crates/tinymist-query/src/fixtures/inlay_hints/snaps/test@imcomplete-expression.typ.snap b/crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@imcomplete-expression.typ.snap similarity index 100% rename from crates/tinymist-query/src/fixtures/inlay_hints/snaps/test@imcomplete-expression.typ.snap rename to crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@imcomplete-expression.typ.snap diff --git a/crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@math_markup_mod.typ.snap b/crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@math_markup_mod.typ.snap new file mode 100644 index 000000000..11e18ea2d --- /dev/null +++ b/crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@math_markup_mod.typ.snap @@ -0,0 +1,23 @@ +--- +source: crates/tinymist-query/src/inlay_hint.rs +expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" +input_file: crates/tinymist-query/src/fixtures/inlay_hints/math_markup_mod.typ +--- +[ + { + "kind": 2, + "label": ": x", + "position": { + "character": 8, + "line": 1 + } + }, + { + "kind": 2, + "label": ": y", + "position": { + "character": 11, + "line": 1 + } + } +] diff --git a/crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@math_mod.typ.snap b/crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@math_mod.typ.snap new file mode 100644 index 000000000..c31096cc0 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@math_mod.typ.snap @@ -0,0 +1,6 @@ +--- +source: crates/tinymist-query/src/inlay_hint.rs +expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" +input_file: crates/tinymist-query/src/fixtures/inlay_hints/math_mod.typ +--- +[] diff --git a/crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@named.typ.snap b/crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@named.typ.snap new file mode 100644 index 000000000..ad7148624 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@named.typ.snap @@ -0,0 +1,6 @@ +--- +source: crates/tinymist-query/src/inlay_hint.rs +expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" +input_file: crates/tinymist-query/src/fixtures/inlay_hints/named.typ +--- +[] diff --git a/crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@named_or_pos.typ.snap b/crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@named_or_pos.typ.snap new file mode 100644 index 000000000..08868b6db --- /dev/null +++ b/crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@named_or_pos.typ.snap @@ -0,0 +1,6 @@ +--- +source: crates/tinymist-query/src/inlay_hint.rs +expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" +input_file: crates/tinymist-query/src/fixtures/inlay_hints/named_or_pos.typ +--- +[] diff --git a/crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@unique_positional.typ.snap b/crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@unique_positional.typ.snap new file mode 100644 index 000000000..6fb56aad0 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/inlay_hints/snaps/smart@unique_positional.typ.snap @@ -0,0 +1,6 @@ +--- +source: crates/tinymist-query/src/inlay_hint.rs +expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" +input_file: crates/tinymist-query/src/fixtures/inlay_hints/unique_positional.typ +--- +[] diff --git a/crates/tinymist-query/src/fixtures/inlay_hints/unique_positional.typ b/crates/tinymist-query/src/fixtures/inlay_hints/unique_positional.typ new file mode 100644 index 000000000..3ea967eb7 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/inlay_hints/unique_positional.typ @@ -0,0 +1,2 @@ +#text("") +#align(left)[] \ No newline at end of file diff --git a/crates/tinymist-query/src/fixtures/references/snaps/test@at_def.typ.snap b/crates/tinymist-query/src/fixtures/references/snaps/test@at_def.typ.snap index ce10091e8..eb1b497a7 100644 --- a/crates/tinymist-query/src/fixtures/references/snaps/test@at_def.typ.snap +++ b/crates/tinymist-query/src/fixtures/references/snaps/test@at_def.typ.snap @@ -5,9 +5,9 @@ input_file: crates/tinymist-query/src/fixtures/references/at_def.typ --- [ { - "range": "2:2:2:3" + "range": "1:2:1:3" }, { - "range": "1:2:1:3" + "range": "2:2:2:3" } ] diff --git a/crates/tinymist-query/src/fixtures/references/snaps/test@redefine.typ.snap b/crates/tinymist-query/src/fixtures/references/snaps/test@redefine.typ.snap index cb8ef2440..f98da45dd 100644 --- a/crates/tinymist-query/src/fixtures/references/snaps/test@redefine.typ.snap +++ b/crates/tinymist-query/src/fixtures/references/snaps/test@redefine.typ.snap @@ -4,9 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" input_file: crates/tinymist-query/src/fixtures/references/redefine.typ --- [ - { - "range": "8:9:8:10" - }, { "range": "3:2:3:3" }, @@ -15,5 +12,8 @@ input_file: crates/tinymist-query/src/fixtures/references/redefine.typ }, { "range": "6:12:6:13" + }, + { + "range": "8:9:8:10" } ] diff --git a/crates/tinymist-query/src/inlay_hint.rs b/crates/tinymist-query/src/inlay_hint.rs index 79d511663..dbc1cd341 100644 --- a/crates/tinymist-query/src/inlay_hint.rs +++ b/crates/tinymist-query/src/inlay_hint.rs @@ -11,6 +11,34 @@ use typst_ts_core::typst::prelude::eco_vec; use crate::prelude::*; +pub struct InlayHintConfig { + // positional arguments group + pub on_pos_args: bool, + pub off_single_pos_arg: bool, + + // variadic arguments group + pub on_variadic_args: bool, + pub only_first_variadic_args: bool, + + // todo + // The typst sugar grammar + pub on_content_block_args: bool, +} + +impl InlayHintConfig { + pub const fn smart() -> Self { + Self { + on_pos_args: true, + off_single_pos_arg: true, + + on_variadic_args: true, + only_first_variadic_args: true, + + on_content_block_args: true, + } + } +} + #[derive(Debug, Clone)] pub struct InlayHintRequest { pub path: PathBuf, @@ -26,7 +54,7 @@ impl InlayHintRequest { let source = get_suitable_source_in_workspace(world, &self.path).ok()?; let range = lsp_to_typst::range(self.range, position_encoding, &source)?; - let hints = inlay_hints(world, &source, range, position_encoding).ok()?; + let hints = inlay_hint(world, &source, range, position_encoding).ok()?; debug!( "got inlay hints on {source:?} => {hints:?}", source = source.id(), @@ -41,12 +69,14 @@ impl InlayHintRequest { } } -fn inlay_hints( +fn inlay_hint( world: &TypstSystemWorld, source: &Source, range: Range, encoding: PositionEncoding, ) -> FileResult> { + const SMART: InlayHintConfig = InlayHintConfig::smart(); + struct InlayHintWorker<'a> { world: &'a TypstSystemWorld, source: &'a Source, @@ -110,10 +140,48 @@ fn inlay_hints( Value::Func(f) => Some(f), _ => None, })?; - trace!("got function {func:?}"); + log::info!("got function {func:?}"); let call_info = analyze_call(func, args)?; - trace!("got call_info {call_info:?}"); + log::info!("got call_info {call_info:?}"); + + let check_single_pos_arg = || { + let mut pos = 0; + let mut content_pos = 0; + + for arg in args.items() { + let Some(arg_node) = args_node.find(arg.span()) else { + continue; + }; + + let Some(info) = call_info.arg_mapping.get(&arg_node) else { + continue; + }; + + if info.kind != ParamKind::Named { + if info.is_content_block { + content_pos += 1; + } else { + pos += 1; + }; + + if pos > 1 && content_pos > 1 { + break; + } + } + } + + (pos <= 1, content_pos <= 1) + }; + + let (disable_by_single_pos_arg, disable_by_single_content_pos_arg) = + if SMART.on_pos_args && SMART.off_single_pos_arg { + check_single_pos_arg() + } else { + (false, false) + }; + + let mut is_first_variadic_arg = true; for arg in args.items() { let Some(arg_node) = args_node.find(arg.span()) else { @@ -128,6 +196,36 @@ fn inlay_hints( continue; } + match info.kind { + ParamKind::Named => { + continue; + } + ParamKind::Positional + if call_info.signature.has_fill_or_size_or_stroke => + { + continue + } + ParamKind::Positional + if !SMART.on_pos_args + || (info.is_content_block + && disable_by_single_content_pos_arg) + || (!info.is_content_block && disable_by_single_pos_arg) => + { + continue + } + ParamKind::Rest + if (!SMART.on_variadic_args + || (!is_first_variadic_arg + && SMART.only_first_variadic_args)) => + { + continue; + } + ParamKind::Rest => { + is_first_variadic_arg = false; + } + ParamKind::Positional => {} + } + let pos = arg_node.range().end; let lsp_pos = typst_to_lsp::offset_to_position(pos, self.encoding, self.source); @@ -186,12 +284,14 @@ enum ParamKind { #[derive(Debug, Clone)] struct CallParamInfo { kind: ParamKind, + is_content_block: bool, param: Arc, // types: EcoVec<()>, } #[derive(Debug, Clone)] struct CallInfo { + signature: Arc, arg_mapping: HashMap, } @@ -201,10 +301,6 @@ fn analyze_call(func: Func, args: ast::Args<'_>) -> Option> { } fn analyze_call_no_cache(func: Func, args: ast::Args<'_>) -> Option { - let mut info = CallInfo { - arg_mapping: HashMap::new(), - }; - #[derive(Debug, Clone)] enum ArgValue<'a> { Instance(Args), @@ -223,6 +319,11 @@ fn analyze_call_no_cache(func: Func, args: ast::Args<'_>) -> Option { let signature = analyze_signature(func); trace!("got signature {signature:?}"); + let mut info = CallInfo { + arg_mapping: HashMap::new(), + signature: signature.clone(), + }; + enum PosState { Init, Pos(usize), @@ -265,10 +366,13 @@ fn analyze_call_no_cache(func: Func, args: ast::Args<'_>) -> Option { }; if let Some(arg) = arg { + // todo: process desugar + let is_content_block = arg.kind() == SyntaxKind::ContentBlock; info.arg_mapping.insert( arg, CallParamInfo { kind, + is_content_block, param: param.clone(), // types: eco_vec![], }, @@ -296,10 +400,13 @@ fn analyze_call_no_cache(func: Func, args: ast::Args<'_>) -> Option { }; if let Some(arg) = arg { + // todo: process desugar + let is_content_block = arg.kind() == SyntaxKind::ContentBlock; info.arg_mapping.insert( arg, CallParamInfo { kind: ParamKind::Rest, + is_content_block, param: rest.clone(), // types: eco_vec![], }, @@ -333,6 +440,7 @@ fn analyze_call_no_cache(func: Func, args: ast::Args<'_>) -> Option { arg_tag, CallParamInfo { kind: ParamKind::Named, + is_content_block: false, param: param.clone(), // types: eco_vec![], }, @@ -386,6 +494,7 @@ impl ParamSpec { struct Signature { pos: Vec>, named: HashMap, Arc>, + has_fill_or_size_or_stroke: bool, rest: Option>, _broken: bool, } @@ -406,9 +515,24 @@ fn analyze_signature(func: Func) -> Arc { let mut named = HashMap::new(); let mut rest = None; let mut broken = false; + let mut has_fill = false; + let mut has_stroke = false; + let mut has_size = false; for param in params.into_iter() { if param.named { + match param.name.as_ref() { + "fill" => { + has_fill = true; + } + "stroke" => { + has_stroke = true; + } + "size" => { + has_size = true; + } + _ => {} + } named.insert(param.name.clone(), param.clone()); } @@ -429,6 +553,7 @@ fn analyze_signature(func: Func) -> Arc { pos, named, rest, + has_fill_or_size_or_stroke: has_fill || has_stroke || has_size, _broken: broken, }) } @@ -502,7 +627,7 @@ mod tests { use crate::tests::*; #[test] - fn test() { + fn smart() { snapshot_testing("inlay_hints", &|world, path| { let source = get_suitable_source_in_workspace(world, &path).unwrap(); diff --git a/crates/tinymist-query/src/references.rs b/crates/tinymist-query/src/references.rs index 50c869df6..c4c143aee 100644 --- a/crates/tinymist-query/src/references.rs +++ b/crates/tinymist-query/src/references.rs @@ -119,6 +119,14 @@ mod tests { }; let result = request.request(world, PositionEncoding::Utf16); + // sort + let result = result.map(|mut e| { + e.sort_by(|a, b| match a.range.start.cmp(&b.range.start) { + std::cmp::Ordering::Equal => a.range.end.cmp(&b.range.end), + e => e, + }); + e + }); assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC)); }); }