mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 13:13:43 +00:00
feat: implement inlay hint configuration (#37)
* fix: unstable order of reference results * feat: inlay hint configuration * dev: test inlay hint on `lr`
This commit is contained in:
parent
dbd1726d08
commit
61be2f78e4
17 changed files with 216 additions and 18 deletions
|
@ -1 +1,2 @@
|
|||
#text("")
|
||||
#let f(x, y) = x + y
|
||||
#f(1, 2)
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
#let f(x, y) = [#(x + y)];
|
||||
$lr(#f(1, 2))$
|
|
@ -0,0 +1 @@
|
|||
$lr(1, 2, 3)$
|
1
crates/tinymist-query/src/fixtures/inlay_hints/named.typ
Normal file
1
crates/tinymist-query/src/fixtures/inlay_hints/named.typ
Normal file
|
@ -0,0 +1 @@
|
|||
#table(align: left)[]
|
|
@ -0,0 +1,3 @@
|
|||
#text("")
|
||||
#text(red, "")
|
||||
#text(18pt, red, "")
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
|
@ -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
|
||||
---
|
||||
[]
|
|
@ -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
|
||||
---
|
||||
[]
|
|
@ -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
|
||||
---
|
||||
[]
|
|
@ -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
|
||||
---
|
||||
[]
|
|
@ -0,0 +1,2 @@
|
|||
#text("")
|
||||
#align(left)[]
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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<usize>,
|
||||
encoding: PositionEncoding,
|
||||
) -> FileResult<Vec<InlayHint>> {
|
||||
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<ParamSpec>,
|
||||
// types: EcoVec<()>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct CallInfo {
|
||||
signature: Arc<Signature>,
|
||||
arg_mapping: HashMap<SyntaxNode, CallParamInfo>,
|
||||
}
|
||||
|
||||
|
@ -201,10 +301,6 @@ fn analyze_call(func: Func, args: ast::Args<'_>) -> Option<Arc<CallInfo>> {
|
|||
}
|
||||
|
||||
fn analyze_call_no_cache(func: Func, args: ast::Args<'_>) -> Option<CallInfo> {
|
||||
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<CallInfo> {
|
|||
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<CallInfo> {
|
|||
};
|
||||
|
||||
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<CallInfo> {
|
|||
};
|
||||
|
||||
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<CallInfo> {
|
|||
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<Arc<ParamSpec>>,
|
||||
named: HashMap<Cow<'static, str>, Arc<ParamSpec>>,
|
||||
has_fill_or_size_or_stroke: bool,
|
||||
rest: Option<Arc<ParamSpec>>,
|
||||
_broken: bool,
|
||||
}
|
||||
|
@ -406,9 +515,24 @@ fn analyze_signature(func: Func) -> Arc<Signature> {
|
|||
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<Signature> {
|
|||
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();
|
||||
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue