mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-08-03 17:58:17 +00:00
feat: rearrange hover providers (#1108)
* feat: rearrange hover providers * fix: warnings * test: update snapshot * test: update hover snapshot
This commit is contained in:
parent
671783a964
commit
975e4a27bf
27 changed files with 273 additions and 301 deletions
|
@ -11,16 +11,14 @@ use reflexo::debug_loc::DataSource;
|
|||
use reflexo::hash::{hash128, FxDashMap};
|
||||
use reflexo_typst::{EntryReader, WorldDeps};
|
||||
use rustc_hash::FxHashMap;
|
||||
use tinymist_world::LspWorld;
|
||||
use tinymist_world::DETACHED_ENTRY;
|
||||
use tinymist_world::{LspWorld, DETACHED_ENTRY};
|
||||
use typst::diag::{eco_format, At, FileError, FileResult, SourceResult, StrResult};
|
||||
use typst::engine::{Route, Sink, Traced};
|
||||
use typst::eval::Eval;
|
||||
use typst::foundations::{Bytes, Module, Styles};
|
||||
use typst::layout::Position;
|
||||
use typst::model::Document;
|
||||
use typst::syntax::package::PackageManifest;
|
||||
use typst::syntax::{package::PackageSpec, Span, VirtualPath};
|
||||
use typst::syntax::package::{PackageManifest, PackageSpec};
|
||||
use typst::syntax::{Span, VirtualPath};
|
||||
|
||||
use crate::adt::revision::{RevisionLock, RevisionManager, RevisionManagerLike, RevisionSlot};
|
||||
use crate::analysis::prelude::*;
|
||||
|
@ -880,14 +878,9 @@ impl SharedContext {
|
|||
/// Passing a `document` (from a previous compilation) is optional, but
|
||||
/// enhances the autocompletions. Label completions, for instance, are
|
||||
/// only generated when the document is available.
|
||||
pub fn tooltip(
|
||||
&self,
|
||||
document: Option<&Document>,
|
||||
source: &Source,
|
||||
cursor: usize,
|
||||
) -> Option<Tooltip> {
|
||||
pub fn tooltip(&self, source: &Source, cursor: usize) -> Option<Tooltip> {
|
||||
let token = &self.analysis.workers.tooltip;
|
||||
token.enter(|| tooltip_(&self.world, document, source, cursor))
|
||||
token.enter(|| tooltip_(&self.world, source, cursor))
|
||||
}
|
||||
|
||||
/// Get the manifest of a package by file id.
|
||||
|
|
|
@ -5,6 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/hover/annotate_dict_param.typ
|
|||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"contents": "```typc\nlet show-example(\n ..options: arguments,\n inherited-scope: dictionary = (:),\n) = none;\n```\n\n---\n- <!-- typlite:begin:list-item 0 -->inherited-scope (dictionary): Definitions that are made available to the entire parsed\n module. This parameter is only used internally.<!-- typlite:end:list-item 0 -->\n\n# Rest Parameters\n\n## options\n\n```typc\ntype: arguments\n```\n\n\n\n# Named Parameters\n\n## inherited-scope\n\n```typc\ntype: dictionary\n```\n\nDefinitions that are made available to the entire parsed\n module. This parameter is only used internally.",
|
||||
"contents": "```typc\nlet show-example(\n ..options: arguments,\n inherited-scope: dictionary = (:),\n) = none;\n```\n\n---\n\n- <!-- typlite:begin:list-item 0 -->inherited-scope (dictionary): Definitions that are made available to the entire parsed\n module. This parameter is only used internally.<!-- typlite:end:list-item 0 -->\n\n# Rest Parameters\n\n## options\n\n```typc\ntype: arguments\n```\n\n\n\n# Named Parameters\n\n## inherited-scope\n\n```typc\ntype: dictionary\n```\n\nDefinitions that are made available to the entire parsed\n module. This parameter is only used internally.",
|
||||
"range": "7:20:7:32"
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/hover/annotate_dict_param2.typ
|
|||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"contents": "```typc\nlet inherited-scope = dictionary;\n```\n\n---\nDefinitions that are made available to the entire parsed\n module. This parameter is only used internally.",
|
||||
"contents": "```typc\nlet inherited-scope = dictionary;\n```\n\n---\n\nDefinitions that are made available to the entire parsed\n module. This parameter is only used internally.",
|
||||
"range": "6:21:6:36"
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/hover/annotate_docs_error.typ
|
|||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"contents": "```typc\nlet speaker-note(\n note: any,\n mode: str = \"typ\",\n setting: (any) => any = Closure(..),\n) = none;\n```\n\n---\nSpeaker notes are a way to add additional information to your slides that is not visible to the audience. This can be useful for providing additional context or reminders to yourself.\n\n ## Example\n\n ```typ\n#speaker-note[This is a speaker note]\n\n```\n```\nRender Error\ncompiling node: error: unknown variable: speaker-note at \"/__render__.typ\":201..213\nHint: if you meant to use subtraction, try adding spaces around the minus sign: \\`speaker - note\\`\n\n```\n\n# Positional Parameters\n\n## note\n\n```typc\ntype: \n```\n\n\n\n# Named Parameters\n\n## mode\n\n```typc\ntype: \"typ\"\n```\n\n\n\n## setting (named)\n\n```typc\ntype: (any) => any\n```\n\n",
|
||||
"contents": "```typc\nlet speaker-note(\n note: any,\n mode: str = \"typ\",\n setting: (any) => any = Closure(..),\n) = none;\n```\n\n---\n\nSpeaker notes are a way to add additional information to your slides that is not visible to the audience. This can be useful for providing additional context or reminders to yourself.\n\n ## Example\n\n ```typ\n#speaker-note[This is a speaker note]\n\n```\n```\nRender Error\ncompiling node: error: unknown variable: speaker-note at \"/__render__.typ\":201..213\nHint: if you meant to use subtraction, try adding spaces around the minus sign: \\`speaker - note\\`\n\n```\n\n# Positional Parameters\n\n## note\n\n```typc\ntype: \n```\n\n\n\n# Named Parameters\n\n## mode\n\n```typc\ntype: \"typ\"\n```\n\n\n\n## setting (named)\n\n```typc\ntype: (any) => any\n```\n\n",
|
||||
"range": "11:20:11:32"
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/hover/annotate_fn.typ
|
|||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"contents": "```typc\nlet touying-fn-wrapper(\n fn: (..: any) => any | function,\n ..args: arguments,\n max-repetitions: int | none = none,\n repetitions: int | none = none,\n) = none;\n```\n\n---\n\n\n# Positional Parameters\n\n## fn\n\n```typc\ntype: (..: any) => any | function\n```\n\nThe `fn`.\n\n# Rest Parameters\n\n## args\n\n```typc\ntype: arguments\n```\n\nThe `args`.\n\n# Named Parameters\n\n## max-repetitions\n\n```typc\ntype: int | none\n```\n\nThe `max-repetitions`.\n\n## repetitions (named)\n\n```typc\ntype: int | none\n```\n\nThe `repetitions`.",
|
||||
"contents": "```typc\nlet touying-fn-wrapper(\n fn: (..: any) => any | function,\n ..args: arguments,\n max-repetitions: int | none = none,\n repetitions: int | none = none,\n) = none;\n```\n\n---\n\n\n\n# Positional Parameters\n\n## fn\n\n```typc\ntype: (..: any) => any | function\n```\n\nThe `fn`.\n\n# Rest Parameters\n\n## args\n\n```typc\ntype: arguments\n```\n\nThe `args`.\n\n# Named Parameters\n\n## max-repetitions\n\n```typc\ntype: int | none\n```\n\nThe `max-repetitions`.\n\n## repetitions (named)\n\n```typc\ntype: int | none\n```\n\nThe `repetitions`.",
|
||||
"range": "8:20:8:38"
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/hover/annotate_ret.typ
|
|||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"contents": "```typc\nlet _delayed-wrapper(\n body: any,\n) = content;\n```\n\n---\n\n\n# Positional Parameters\n\n## body\n\n```typc\ntype: \n```\n\n",
|
||||
"contents": "```typc\nlet _delayed-wrapper(\n body: any,\n) = content;\n```\n\n---\n\n\n\n# Positional Parameters\n\n## body\n\n```typc\ntype: \n```\n\n",
|
||||
"range": "6:20:6:36"
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -5,6 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/hover/builtin_module.typ
|
|||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"contents": "```typc\n<module sys>\n```\n\n---\n```typc\nlet sys;\n```",
|
||||
"contents": "```typc\nlet sys;\n```\n\n---\n\n### Sampled Values\n```typc\n<module sys>\n```",
|
||||
"range": "0:20:0:23"
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/hover/builtin_var.typ
|
|||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"contents": "```typc\nrgb(\"#ff4136\")\n```",
|
||||
"contents": "### Sampled Values\n```typc\nrgb(\"#ff4136\")\n```",
|
||||
"range": "0:20:0:23"
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/hover/builtin_var2.typ
|
|||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"contents": "```typc\n<module sys>\n```\n\n---\n```typc\nlet sys;\n```",
|
||||
"contents": "```typc\nlet sys;\n```\n\n---\n\n### Sampled Values\n```typc\n<module sys>\n```",
|
||||
"range": "0:20:0:23"
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/hover/builtin_var3.typ
|
|||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"contents": "```typc\n<module sys>\n```\n\n---\n```typc\nlet sys;\n```",
|
||||
"contents": "```typc\nlet sys;\n```\n\n---\n\n### Sampled Values\n```typc\n<module sys>\n```",
|
||||
"range": "0:2:0:5"
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/hover/module_alias.typ
|
|||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"contents": "# The Module (Alias)",
|
||||
"contents": "### Sampled Values\n```typc\n<module themod>\n```\n\n---\n\n# The Module (Alias)",
|
||||
"range": "2:24:2:31"
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/hover/module_star.typ
|
|||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"contents": "This star imports line",
|
||||
"contents": "This star imports line\n\n---\n\n### Sampled Values\n```typc\nnone\n```",
|
||||
"range": "0:41:0:42"
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/hover/module_star_const_eval.typ
|
|||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"contents": "```typc\nlet line() = int;\n```\n\n---\nThe draw line.",
|
||||
"contents": "```typc\nlet line() = int;\n```\n\n---\n\nThe draw line.",
|
||||
"range": "2:23:2:27"
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/hover/module_star_rename.typ
|
|||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"contents": "```typc\nlet line() = int;\n```\n\n---\nThe draw line.",
|
||||
"contents": "```typc\nlet line() = int;\n```\n\n---\n\nThe draw line.",
|
||||
"range": "3:23:3:27"
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/hover/module_star_shadow.typ
|
|||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"contents": "```typc\nlet line() = int;\n```\n\n---\nThe draw line.",
|
||||
"contents": "```typc\nlet line() = int;\n```\n\n---\n\nThe draw line.",
|
||||
"range": "2:23:2:27"
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/hover/module_var.typ
|
|||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"contents": "# The Module",
|
||||
"contents": "### Sampled Values\n```typc\n<module themod>\n```\n\n---\n\n# The Module",
|
||||
"range": "2:24:2:30"
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/hover/pagebreak.typ
|
|||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"contents": "```typc\nlet pagebreak(\n to: none | str = none,\n weak: bool = false,\n);\n```\n\n---\nA manual page break.\n\nMust not be used inside any containers.\n\n# Example\n```typ\nThe next page contains\nmore details on compound theory.\n#pagebreak()\n\n== Compound Theory\nIn 1984, the first ...\n```\n\n# Named Parameters\n\n## to\n\n```typc\ntype: \"even\" | \"odd\" | none\n```\n\nIf given, ensures that the next page will be an even/odd page, with an\nempty page in between if necessary.\n\n```typ\n#set page(height: 30pt)\n\nFirst.\n#pagebreak(to: \"odd\")\nThird.\n```\n\n## weak (named)\n\n```typc\ntype: bool\n```\n\nIf `true`, the page break is skipped if the current page is already\nempty.\n\n---\n[Open docs](https://typst.app/docs/reference/layout/pagebreak/)",
|
||||
"contents": "```typc\nlet pagebreak(\n to: none | str = none,\n weak: bool = false,\n);\n```\n\n---\n\nA manual page break.\n\nMust not be used inside any containers.\n\n# Example\n```typ\nThe next page contains\nmore details on compound theory.\n#pagebreak()\n\n== Compound Theory\nIn 1984, the first ...\n```\n\n---\n\nA manual page break.\n\nMust not be used inside any containers.\n\n# Example\n```typ\nThe next page contains\nmore details on compound theory.\n#pagebreak()\n\n== Compound Theory\nIn 1984, the first ...\n```\n\n# Named Parameters\n\n## to\n\n```typc\ntype: \"even\" | \"odd\" | none\n```\n\nIf given, ensures that the next page will be an even/odd page, with an\nempty page in between if necessary.\n\n```typ\n#set page(height: 30pt)\n\nFirst.\n#pagebreak(to: \"odd\")\nThird.\n```\n\n## weak (named)\n\n```typc\ntype: bool\n```\n\nIf `true`, the page break is skipped if the current page is already\nempty.\n\n---\n\n[Open docs](https://typst.app/docs/reference/layout/pagebreak/)",
|
||||
"range": "0:20:0:29"
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/hover/param.typ
|
|||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"contents": "```typc\nlet param = int;\n```\n\n---\nThe `parameter`.",
|
||||
"contents": "```typc\nlet param = int;\n```\n\n---\n\nThe `parameter`.",
|
||||
"range": "3:25:3:30"
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/hover/render_equation.typ
|
|||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"contents": "```typc\nlet lam(\n A: type,\n B: type,\n) = dictionary;\n```\n\n---\nLambda constructor.\n\n Typing Rule:\n\n <p align=\"center\"><img alt=\"typst-block\" src=\"data:image-hash/svg+xml;base64,redacted\" /></p>\n\n# Positional Parameters\n\n## A\n\n```typc\ntype: type\n```\n\nThe type of the argument.\n - <!-- typlite:begin:list-item 1 -->It can be also regarded as the condition of the proposition.<!-- typlite:end:list-item 1 -->\n\n## B (positional)\n\n```typc\ntype: type\n```\n\nThe type of the body.\n - <!-- typlite:begin:list-item 1 -->It can be also regarded as the conclusion of the proposition.<!-- typlite:end:list-item 1 -->",
|
||||
"contents": "```typc\nlet lam(\n A: type,\n B: type,\n) = dictionary;\n```\n\n---\n\nLambda constructor.\n\n Typing Rule:\n\n <p align=\"center\"><img alt=\"typst-block\" src=\"data:image-hash/svg+xml;base64,redacted\" /></p>\n\n# Positional Parameters\n\n## A\n\n```typc\ntype: type\n```\n\nThe type of the argument.\n - <!-- typlite:begin:list-item 1 -->It can be also regarded as the condition of the proposition.<!-- typlite:end:list-item 1 -->\n\n## B (positional)\n\n```typc\ntype: type\n```\n\nThe type of the body.\n - <!-- typlite:begin:list-item 1 -->It can be also regarded as the conclusion of the proposition.<!-- typlite:end:list-item 1 -->",
|
||||
"range": "12:20:12:23"
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/hover/render_equation_no_html.typ
|
|||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"contents": "```typc\nlet lam(\n A: type,\n B: type,\n) = dictionary;\n```\n\n---\nLambda constructor.\n\n Typing Rule:\n\n ```typc\n$ (Γ , x : A ⊢ M : B #h(2em) Γ ⊢ a:B)/(Γ ⊢ λ (x : A) → M : π (x : A) → B) $\n```\n\n# Positional Parameters\n\n## A\n\n```typc\ntype: type\n```\n\nThe type of the argument.\n - It can be also regarded as the condition of the proposition.\n\n## B (positional)\n\n```typc\ntype: type\n```\n\nThe type of the body.\n - It can be also regarded as the conclusion of the proposition.",
|
||||
"contents": "```typc\nlet lam(\n A: type,\n B: type,\n) = dictionary;\n```\n\n---\n\nLambda constructor.\n\n Typing Rule:\n\n ```typc\n$ (Γ , x : A ⊢ M : B #h(2em) Γ ⊢ a:B)/(Γ ⊢ λ (x : A) → M : π (x : A) → B) $\n```\n\n# Positional Parameters\n\n## A\n\n```typc\ntype: type\n```\n\nThe type of the argument.\n - It can be also regarded as the condition of the proposition.\n\n## B (positional)\n\n```typc\ntype: type\n```\n\nThe type of the body.\n - It can be also regarded as the conclusion of the proposition.",
|
||||
"range": "14:20:14:23"
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/hover/user.typ
|
|||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"contents": "```typc\nlet f() = int;\n```\n\n---\nTest",
|
||||
"contents": "```typc\nlet f() = int;\n```\n\n---\n\nTest",
|
||||
"range": "3:20:3:21"
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ input_file: crates/tinymist-query/src/fixtures/hover/value_repr.typ
|
|||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"contents": "```typc\nlet f(\n x: any,\n y: any,\n z: any,\n w01: int = 1,\n w02: str = \"test\",\n w03: any = 1 + 2,\n w04: any = Label(test),\n w05: (content | none, baseline: relative, clip: bool, fill: color, height: auto | relative, inset: inset, outset: outset, radius: radius, stroke: stroke, width: auto | fraction | relative) => box = (content | none, baseline: relative, clip: bool, fill: color, height: auto | relative, inset: inset, outset: outset, radius: radius, stroke: stroke, width: auto | fraction | relative) => box,\n w06: (content) => item | any = (body-indent: length, indent: length, marker: array | content | function, spacing: auto | length, tight: bool, ..: content) => list.item,\n w07: content = Expr(..),\n w08: any = Expr(..),\n w09: any = 1 + 2,\n w10: array = (\n 1,\n 2,\n ),\n w11: array = (),\n w12: dictionary = (:),\n w13: dictionary = (a: 1),\n w14: dictionary = (a: (content | none, baseline: relative, clip: bool, fill: color, height: auto | relative, inset: inset, outset: outset, radius: radius, stroke: stroke, width: auto | fraction | relative) => box),\n w15: dictionary = (a: (body-indent: length, indent: length, marker: array | content | function, spacing: auto | length, tight: bool, ..: content) => list.item),\n) = int;\n```\n\n---\n\n\n# Positional Parameters\n\n## x\n\n```typc\ntype: \n```\n\n\n\n## y (positional)\n\n```typc\ntype: \n```\n\n\n\n## z (positional)\n\n```typc\ntype: \n```\n\n\n\n# Named Parameters\n\n## w01\n\n```typc\ntype: 1\n```\n\n\n\n## w02 (named)\n\n```typc\ntype: \"test\"\n```\n\n\n\n## w03 (named)\n\n```typc\ntype: any\n```\n\n\n\n## w04 (named)\n\n```typc\ntype: \n```\n\n\n\n## w05 (named)\n\n```typc\ntype: (content | none, baseline: relative, clip: bool, fill: color, height: auto | relative, inset: inset, outset: outset, radius: radius, stroke: stroke, width: auto | fraction | relative) => box\n```\n\n\n\n## w06 (named)\n\n```typc\ntype: (content) => item | any\n```\n\n\n\n## w07 (named)\n\n```typc\ntype: content\n```\n\n\n\n## w08 (named)\n\n```typc\ntype: any\n```\n\n\n\n## w09 (named)\n\n```typc\ntype: any\n```\n\n\n\n## w10 (named)\n\n```typc\ntype: array\n```\n\n\n\n## w11 (named)\n\n```typc\ntype: array\n```\n\n\n\n## w12 (named)\n\n```typc\ntype: dictionary\n```\n\n\n\n## w13 (named)\n\n```typc\ntype: dictionary\n```\n\n\n\n## w14 (named)\n\n```typc\ntype: dictionary\n```\n\n\n\n## w15 (named)\n\n```typc\ntype: dictionary\n```\n\n",
|
||||
"contents": "```typc\nlet f(\n x: any,\n y: any,\n z: any,\n w01: int = 1,\n w02: str = \"test\",\n w03: any = 1 + 2,\n w04: any = Label(test),\n w05: (content | none, baseline: relative, clip: bool, fill: color, height: auto | relative, inset: inset, outset: outset, radius: radius, stroke: stroke, width: auto | fraction | relative) => box = (content | none, baseline: relative, clip: bool, fill: color, height: auto | relative, inset: inset, outset: outset, radius: radius, stroke: stroke, width: auto | fraction | relative) => box,\n w06: (content) => item | any = (body-indent: length, indent: length, marker: array | content | function, spacing: auto | length, tight: bool, ..: content) => list.item,\n w07: content = Expr(..),\n w08: any = Expr(..),\n w09: any = 1 + 2,\n w10: array = (\n 1,\n 2,\n ),\n w11: array = (),\n w12: dictionary = (:),\n w13: dictionary = (a: 1),\n w14: dictionary = (a: (content | none, baseline: relative, clip: bool, fill: color, height: auto | relative, inset: inset, outset: outset, radius: radius, stroke: stroke, width: auto | fraction | relative) => box),\n w15: dictionary = (a: (body-indent: length, indent: length, marker: array | content | function, spacing: auto | length, tight: bool, ..: content) => list.item),\n) = int;\n```\n\n---\n\n\n\n# Positional Parameters\n\n## x\n\n```typc\ntype: \n```\n\n\n\n## y (positional)\n\n```typc\ntype: \n```\n\n\n\n## z (positional)\n\n```typc\ntype: \n```\n\n\n\n# Named Parameters\n\n## w01\n\n```typc\ntype: 1\n```\n\n\n\n## w02 (named)\n\n```typc\ntype: \"test\"\n```\n\n\n\n## w03 (named)\n\n```typc\ntype: any\n```\n\n\n\n## w04 (named)\n\n```typc\ntype: \n```\n\n\n\n## w05 (named)\n\n```typc\ntype: (content | none, baseline: relative, clip: bool, fill: color, height: auto | relative, inset: inset, outset: outset, radius: radius, stroke: stroke, width: auto | fraction | relative) => box\n```\n\n\n\n## w06 (named)\n\n```typc\ntype: (content) => item | any\n```\n\n\n\n## w07 (named)\n\n```typc\ntype: content\n```\n\n\n\n## w08 (named)\n\n```typc\ntype: any\n```\n\n\n\n## w09 (named)\n\n```typc\ntype: any\n```\n\n\n\n## w10 (named)\n\n```typc\ntype: array\n```\n\n\n\n## w11 (named)\n\n```typc\ntype: array\n```\n\n\n\n## w12 (named)\n\n```typc\ntype: dictionary\n```\n\n\n\n## w13 (named)\n\n```typc\ntype: dictionary\n```\n\n\n\n## w14 (named)\n\n```typc\ntype: dictionary\n```\n\n\n\n## w15 (named)\n\n```typc\ntype: dictionary\n```\n\n",
|
||||
"range": "23:20:23:21"
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use typst_shim::syntax::LinkedNodeExt;
|
|||
use crate::analysis::get_link_exprs_in;
|
||||
use crate::jump_from_cursor;
|
||||
use crate::prelude::*;
|
||||
use crate::upstream::{expr_tooltip, route_of_value, truncated_repr, Tooltip};
|
||||
use crate::upstream::{route_of_value, truncated_repr, Tooltip};
|
||||
|
||||
/// The [`textDocument/hover`] request asks the server for hover information at
|
||||
/// a given text document position.
|
||||
|
@ -31,8 +31,6 @@ impl StatefulRequest for HoverRequest {
|
|||
ctx: &mut LocalContext,
|
||||
doc: Option<VersionedDocument>,
|
||||
) -> Option<Self::Response> {
|
||||
let doc_ref = doc.as_ref().map(|doc| doc.document.as_ref());
|
||||
|
||||
let source = ctx.source_by_path(&self.path).ok()?;
|
||||
let offset = ctx.to_typst_pos(self.position, &source)?;
|
||||
// the typst's cursor is 1-based, so we need to add 1 to the offset
|
||||
|
@ -41,242 +39,245 @@ impl StatefulRequest for HoverRequest {
|
|||
let node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
|
||||
let range = ctx.to_lsp_range(node.range(), &source);
|
||||
|
||||
let contents = def_tooltip(ctx, &source, doc.as_ref(), cursor)
|
||||
.or_else(|| star_tooltip(ctx, &node))
|
||||
.or_else(|| link_tooltip(ctx, &node, cursor))
|
||||
.or_else(|| Some(to_lsp_tooltip(&ctx.tooltip(doc_ref, &source, cursor)?)))?;
|
||||
|
||||
// Neovim shows ugly hover if the hover content is in array, so we join them
|
||||
// manually with divider bars.
|
||||
let mut contents = match contents {
|
||||
HoverContents::Array(contents) => contents
|
||||
.into_iter()
|
||||
.map(|content| match content {
|
||||
MarkedString::LanguageString(content) => {
|
||||
format!("```{}\n{}\n```", content.language, content.value)
|
||||
}
|
||||
MarkedString::String(content) => content,
|
||||
})
|
||||
.join("\n\n---\n"),
|
||||
HoverContents::Scalar(MarkedString::String(contents)) => contents,
|
||||
HoverContents::Scalar(MarkedString::LanguageString(contents)) => {
|
||||
format!("```{}\n{}\n```", contents.language, contents.value)
|
||||
}
|
||||
lsp_types::HoverContents::Markup(content) => {
|
||||
match content.kind {
|
||||
MarkupKind::Markdown => content.value,
|
||||
// todo: escape
|
||||
MarkupKind::PlainText => content.value,
|
||||
}
|
||||
}
|
||||
let mut worker = HoverWorker {
|
||||
ctx,
|
||||
source,
|
||||
doc,
|
||||
cursor,
|
||||
def: Default::default(),
|
||||
value: Default::default(),
|
||||
preview: Default::default(),
|
||||
docs: Default::default(),
|
||||
actions: Default::default(),
|
||||
};
|
||||
|
||||
if let Some(provider) = ctx.analysis.periscope.clone() {
|
||||
if let Some(doc) = doc.clone() {
|
||||
let position = jump_from_cursor(&doc.document, &source, cursor);
|
||||
let position = position.or_else(|| {
|
||||
for idx in 1..100 {
|
||||
let next_cursor = cursor + idx;
|
||||
if next_cursor < source.text().len() {
|
||||
let position = jump_from_cursor(&doc.document, &source, next_cursor);
|
||||
if position.is_some() {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
let prev_cursor = cursor.checked_sub(idx);
|
||||
if let Some(prev_cursor) = prev_cursor {
|
||||
let position = jump_from_cursor(&doc.document, &source, prev_cursor);
|
||||
if position.is_some() {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
}
|
||||
worker.work();
|
||||
|
||||
None
|
||||
});
|
||||
let mut contents = vec![];
|
||||
|
||||
log::info!("telescope position: {:?}", position);
|
||||
let content = position.and_then(|pos| provider.periscope_at(ctx, doc, pos));
|
||||
if let Some(preview_content) = content {
|
||||
contents = format!("{preview_content}\n---\n{contents}");
|
||||
}
|
||||
}
|
||||
contents.append(&mut worker.def);
|
||||
contents.append(&mut worker.value);
|
||||
contents.append(&mut worker.preview);
|
||||
contents.append(&mut worker.docs);
|
||||
if !worker.actions.is_empty() {
|
||||
let content = worker.actions.into_iter().join(" | ");
|
||||
contents.push(content);
|
||||
}
|
||||
|
||||
if contents.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Hover {
|
||||
contents: HoverContents::Scalar(MarkedString::String(contents)),
|
||||
// Neovim shows ugly hover if the hover content is in array, so we join them
|
||||
// manually with divider bars.
|
||||
contents: HoverContents::Scalar(MarkedString::String(contents.join("\n\n---\n\n"))),
|
||||
range: Some(range),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn def_tooltip(
|
||||
ctx: &mut LocalContext,
|
||||
source: &Source,
|
||||
document: Option<&VersionedDocument>,
|
||||
struct HoverWorker<'a> {
|
||||
ctx: &'a mut LocalContext,
|
||||
source: Source,
|
||||
doc: Option<VersionedDocument>,
|
||||
cursor: usize,
|
||||
) -> Option<HoverContents> {
|
||||
let leaf = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
|
||||
let syntax = classify_syntax(leaf.clone(), cursor)?;
|
||||
let def = ctx.def_of_syntax(source, document, syntax.clone())?;
|
||||
def: Vec<String>,
|
||||
value: Vec<String>,
|
||||
preview: Vec<String>,
|
||||
docs: Vec<String>,
|
||||
actions: Vec<CommandLink>,
|
||||
}
|
||||
|
||||
let mut results = vec![];
|
||||
let mut actions = vec![];
|
||||
impl HoverWorker<'_> {
|
||||
fn work(&mut self) {
|
||||
self.static_analysis();
|
||||
self.preview();
|
||||
self.dynamic_analysis();
|
||||
}
|
||||
|
||||
use Decl::*;
|
||||
match def.decl.as_ref() {
|
||||
Label(..) => {
|
||||
results.push(MarkedString::String(format!("Label: {}\n", def.name())));
|
||||
// todo: type repr
|
||||
if let Some(val) = def.term.as_ref().and_then(|v| v.value()) {
|
||||
let repr = truncated_repr(&val);
|
||||
results.push(MarkedString::String(format!("{repr}")));
|
||||
/// Static analysis results
|
||||
fn static_analysis(&mut self) -> Option<()> {
|
||||
let source = self.source.clone();
|
||||
let leaf = LinkedNode::new(source.root()).leaf_at_compat(self.cursor)?;
|
||||
|
||||
self.definition()
|
||||
.or_else(|| self.star(&leaf))
|
||||
.or_else(|| self.link(&leaf))
|
||||
}
|
||||
|
||||
/// Dynamic analysis results
|
||||
fn dynamic_analysis(&mut self) -> Option<()> {
|
||||
let typst_tooltip = self.ctx.tooltip(&self.source, self.cursor)?;
|
||||
self.value.push(match typst_tooltip {
|
||||
Tooltip::Text(text) => text.to_string(),
|
||||
Tooltip::Code(code) => format!("### Sampled Values\n```typc\n{code}\n```"),
|
||||
});
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Definition analysis results
|
||||
fn definition(&mut self) -> Option<()> {
|
||||
let leaf = LinkedNode::new(self.source.root()).leaf_at_compat(self.cursor)?;
|
||||
let syntax = classify_syntax(leaf.clone(), self.cursor)?;
|
||||
let def = self
|
||||
.ctx
|
||||
.def_of_syntax(&self.source, self.doc.as_ref(), syntax.clone())?;
|
||||
|
||||
use Decl::*;
|
||||
match def.decl.as_ref() {
|
||||
Label(..) => {
|
||||
self.def.push(format!("Label: {}\n", def.name()));
|
||||
// todo: type repr
|
||||
if let Some(val) = def.term.as_ref().and_then(|v| v.value()) {
|
||||
self.def.push(truncated_repr(&val).into());
|
||||
}
|
||||
}
|
||||
Some(HoverContents::Array(results))
|
||||
}
|
||||
BibEntry(..) => {
|
||||
results.push(MarkedString::String(format!(
|
||||
"Bibliography: @{}",
|
||||
def.name()
|
||||
)));
|
||||
BibEntry(..) => {
|
||||
self.def.push(format!("Bibliography: @{}", def.name()));
|
||||
}
|
||||
_ => {
|
||||
let sym_docs = self.ctx.def_docs(&def);
|
||||
|
||||
Some(HoverContents::Array(results))
|
||||
}
|
||||
_ => {
|
||||
let sym_docs = ctx.def_docs(&def);
|
||||
// todo: hover with `with_stack`
|
||||
|
||||
if matches!(def.decl.kind(), DefKind::Variable | DefKind::Constant) {
|
||||
// todo: check sensible length, value highlighting
|
||||
if let Some(values) = expr_tooltip(ctx.world(), syntax.node()) {
|
||||
match values {
|
||||
Tooltip::Text(values) => {
|
||||
results.push(MarkedString::String(values.into()));
|
||||
if matches!(
|
||||
def.decl.kind(),
|
||||
DefKind::Function | DefKind::Variable | DefKind::Constant
|
||||
) && !def.name().is_empty()
|
||||
{
|
||||
let mut type_doc = String::new();
|
||||
type_doc.push_str("let ");
|
||||
type_doc.push_str(def.name());
|
||||
|
||||
match &sym_docs {
|
||||
Some(DefDocs::Variable(docs)) => {
|
||||
push_result_ty(def.name(), docs.return_ty.as_ref(), &mut type_doc);
|
||||
}
|
||||
Tooltip::Code(values) => {
|
||||
results.push(MarkedString::LanguageString(LanguageString {
|
||||
language: "typc".to_owned(),
|
||||
value: values.into(),
|
||||
}));
|
||||
Some(DefDocs::Function(docs)) => {
|
||||
let _ = docs.print(&mut type_doc);
|
||||
push_result_ty(def.name(), docs.ret_ty.as_ref(), &mut type_doc);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.def.push(format!("```typc\n{type_doc};\n```"));
|
||||
}
|
||||
|
||||
if let Some(doc) = sym_docs {
|
||||
let hover_docs = doc.hover_docs();
|
||||
|
||||
if !hover_docs.trim().is_empty() {
|
||||
self.docs.push(hover_docs.into());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(link) = ExternalDocLink::get(&def) {
|
||||
self.actions.push(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn star(&mut self, mut node: &LinkedNode) -> Option<()> {
|
||||
if !matches!(node.kind(), SyntaxKind::Star) {
|
||||
return None;
|
||||
}
|
||||
|
||||
while !matches!(node.kind(), SyntaxKind::ModuleImport) {
|
||||
node = node.parent()?;
|
||||
}
|
||||
|
||||
let import_node = node.cast::<ast::ModuleImport>()?;
|
||||
let scope_val = self
|
||||
.ctx
|
||||
.module_by_syntax(import_node.source().to_untyped())?;
|
||||
|
||||
let scope_items = scope_val.scope()?.iter();
|
||||
let mut names = scope_items.map(|item| item.0.as_str()).collect::<Vec<_>>();
|
||||
names.sort();
|
||||
|
||||
let content = format!("This star imports {}", separated_list(&names, "and"));
|
||||
self.def.push(content);
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn link(&mut self, mut node: &LinkedNode) -> Option<()> {
|
||||
while !matches!(node.kind(), SyntaxKind::FuncCall) {
|
||||
node = node.parent()?;
|
||||
}
|
||||
|
||||
let links = get_link_exprs_in(node)?;
|
||||
let links = links
|
||||
.objects
|
||||
.iter()
|
||||
.filter(|link| link.range.contains(&self.cursor))
|
||||
.collect::<Vec<_>>();
|
||||
if links.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
for obj in links {
|
||||
let Some(target) = obj.target.resolve(self.ctx) else {
|
||||
continue;
|
||||
};
|
||||
// open file in tab or system application
|
||||
self.actions.push(CommandLink {
|
||||
title: Some("Open in Tab".to_string()),
|
||||
command_or_links: vec![CommandOrLink::Command {
|
||||
id: "tinymist.openInternal".to_string(),
|
||||
args: vec![JsonValue::String(target.to_string())],
|
||||
}],
|
||||
});
|
||||
self.actions.push(CommandLink {
|
||||
title: Some("Open Externally".to_string()),
|
||||
command_or_links: vec![CommandOrLink::Command {
|
||||
id: "tinymist.openExternal".to_string(),
|
||||
args: vec![JsonValue::String(target.to_string())],
|
||||
}],
|
||||
});
|
||||
if let Some(kind) = PathPreference::from_ext(target.path()) {
|
||||
self.def.push(format!("A `{kind:?}` file."));
|
||||
}
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn preview(&mut self) -> Option<()> {
|
||||
// Preview results
|
||||
let provider = self.ctx.analysis.periscope.clone()?;
|
||||
let doc = self.doc.as_ref()?;
|
||||
let position = jump_from_cursor(&doc.document, &self.source, self.cursor);
|
||||
let position = position.or_else(|| {
|
||||
for idx in 1..100 {
|
||||
let next_cursor = self.cursor + idx;
|
||||
if next_cursor < self.source.text().len() {
|
||||
let position = jump_from_cursor(&doc.document, &self.source, next_cursor);
|
||||
if position.is_some() {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
let prev_cursor = self.cursor.checked_sub(idx);
|
||||
if let Some(prev_cursor) = prev_cursor {
|
||||
let position = jump_from_cursor(&doc.document, &self.source, prev_cursor);
|
||||
if position.is_some() {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo: hover with `with_stack`
|
||||
|
||||
if matches!(
|
||||
def.decl.kind(),
|
||||
DefKind::Function | DefKind::Variable | DefKind::Constant
|
||||
) && !def.name().is_empty()
|
||||
{
|
||||
results.push(MarkedString::LanguageString(LanguageString {
|
||||
language: "typc".to_owned(),
|
||||
value: {
|
||||
let mut type_doc = String::new();
|
||||
type_doc.push_str("let ");
|
||||
type_doc.push_str(def.name());
|
||||
|
||||
match &sym_docs {
|
||||
Some(DefDocs::Variable(docs)) => {
|
||||
push_result_ty(def.name(), docs.return_ty.as_ref(), &mut type_doc);
|
||||
}
|
||||
Some(DefDocs::Function(docs)) => {
|
||||
let _ = docs.print(&mut type_doc);
|
||||
push_result_ty(def.name(), docs.ret_ty.as_ref(), &mut type_doc);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
type_doc.push(';');
|
||||
type_doc
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
if let Some(doc) = sym_docs {
|
||||
results.push(MarkedString::String(doc.hover_docs().into()));
|
||||
}
|
||||
|
||||
if let Some(link) = ExternalDocLink::get(&def) {
|
||||
actions.push(link);
|
||||
}
|
||||
|
||||
render_actions(&mut results, actions);
|
||||
Some(HoverContents::Array(results))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn star_tooltip(ctx: &mut LocalContext, mut node: &LinkedNode) -> Option<HoverContents> {
|
||||
if !matches!(node.kind(), SyntaxKind::Star) {
|
||||
return None;
|
||||
}
|
||||
|
||||
while !matches!(node.kind(), SyntaxKind::ModuleImport) {
|
||||
node = node.parent()?;
|
||||
}
|
||||
|
||||
let import_node = node.cast::<ast::ModuleImport>()?;
|
||||
let scope_val = ctx.module_by_syntax(import_node.source().to_untyped())?;
|
||||
|
||||
let scope_items = scope_val.scope()?.iter();
|
||||
let mut names = scope_items.map(|item| item.0.as_str()).collect::<Vec<_>>();
|
||||
names.sort();
|
||||
|
||||
let content = format!("This star imports {}", separated_list(&names, "and"));
|
||||
Some(HoverContents::Scalar(MarkedString::String(content)))
|
||||
}
|
||||
|
||||
fn link_tooltip(
|
||||
ctx: &mut LocalContext,
|
||||
mut node: &LinkedNode,
|
||||
cursor: usize,
|
||||
) -> Option<HoverContents> {
|
||||
while !matches!(node.kind(), SyntaxKind::FuncCall) {
|
||||
node = node.parent()?;
|
||||
}
|
||||
|
||||
let links = get_link_exprs_in(node)?;
|
||||
let links = links
|
||||
.objects
|
||||
.iter()
|
||||
.filter(|link| link.range.contains(&cursor))
|
||||
.collect::<Vec<_>>();
|
||||
if links.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut results = vec![];
|
||||
let mut actions = vec![];
|
||||
for obj in links {
|
||||
let Some(target) = obj.target.resolve(ctx) else {
|
||||
continue;
|
||||
};
|
||||
// open file in tab or system application
|
||||
actions.push(CommandLink {
|
||||
title: Some("Open in Tab".to_string()),
|
||||
command_or_links: vec![CommandOrLink::Command {
|
||||
id: "tinymist.openInternal".to_string(),
|
||||
args: vec![JsonValue::String(target.to_string())],
|
||||
}],
|
||||
None
|
||||
});
|
||||
actions.push(CommandLink {
|
||||
title: Some("Open Externally".to_string()),
|
||||
command_or_links: vec![CommandOrLink::Command {
|
||||
id: "tinymist.openExternal".to_string(),
|
||||
args: vec![JsonValue::String(target.to_string())],
|
||||
}],
|
||||
});
|
||||
if let Some(kind) = PathPreference::from_ext(target.path()) {
|
||||
let preview = format!("A `{kind:?}` file.");
|
||||
results.push(MarkedString::String(preview));
|
||||
}
|
||||
}
|
||||
render_actions(&mut results, actions);
|
||||
if results.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(HoverContents::Array(results))
|
||||
log::info!("telescope position: {position:?}");
|
||||
|
||||
let preview_content = provider.periscope_at(self.ctx, doc.clone(), position?)?;
|
||||
self.preview.push(preview_content);
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
fn push_result_ty(
|
||||
|
@ -294,14 +295,6 @@ fn push_result_ty(
|
|||
let _ = write!(type_doc, " = {short}");
|
||||
}
|
||||
|
||||
fn render_actions(results: &mut Vec<MarkedString>, actions: Vec<CommandLink>) {
|
||||
if actions.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
results.push(MarkedString::String(actions.into_iter().join(" | ")));
|
||||
}
|
||||
|
||||
struct ExternalDocLink;
|
||||
|
||||
impl ExternalDocLink {
|
||||
|
@ -390,17 +383,6 @@ impl fmt::Display for CommandOrLink {
|
|||
}
|
||||
}
|
||||
|
||||
fn to_lsp_tooltip(typst_tooltip: &Tooltip) -> HoverContents {
|
||||
let lsp_marked_string = match typst_tooltip {
|
||||
Tooltip::Text(text) => MarkedString::String(text.to_string()),
|
||||
Tooltip::Code(code) => MarkedString::LanguageString(LanguageString {
|
||||
language: "typc".to_owned(),
|
||||
value: code.to_string(),
|
||||
}),
|
||||
};
|
||||
HoverContents::Scalar(lsp_marked_string)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -11,7 +11,7 @@ pub use lsp_types::{
|
|||
ColorInformation, ColorPresentation, Diagnostic, DiagnosticRelatedInformation,
|
||||
DiagnosticSeverity, DocumentHighlight, DocumentLink, DocumentSymbol, DocumentSymbolResponse,
|
||||
Documentation, FoldingRange, GotoDefinitionResponse, Hover, HoverContents, InlayHint,
|
||||
LanguageString, Location as LspLocation, LocationLink, MarkedString, MarkupContent, MarkupKind,
|
||||
Location as LspLocation, LocationLink, MarkedString, MarkupContent, MarkupKind,
|
||||
ParameterInformation, Position as LspPosition, PrepareRenameResponse, SelectionRange,
|
||||
SemanticTokens, SemanticTokensDelta, SemanticTokensFullDeltaResult, SemanticTokensResult,
|
||||
SignatureHelp, SignatureInformation, SymbolInformation, TextEdit, Url, WorkspaceEdit,
|
||||
|
|
|
@ -6,26 +6,20 @@ use typst::engine::Sink;
|
|||
use typst::eval::CapturesVisitor;
|
||||
use typst::foundations::{repr, Capturer, CastInfo, Value};
|
||||
use typst::layout::Length;
|
||||
use typst::model::Document;
|
||||
use typst::syntax::{ast, LinkedNode, Source, SyntaxKind};
|
||||
use typst::World;
|
||||
use typst_shim::syntax::LinkedNodeExt;
|
||||
use typst_shim::utils::{round_2, Numeric};
|
||||
|
||||
use super::{plain_docs_sentence, summarize_font_family, truncated_repr};
|
||||
use crate::analysis::{analyze_expr, analyze_labels, DynLabel};
|
||||
use crate::analysis::analyze_expr;
|
||||
|
||||
/// Describe the item under the cursor.
|
||||
///
|
||||
/// Passing a `document` (from a previous compilation) is optional, but enhances
|
||||
/// the autocompletions. Label completions, for instance, are only generated
|
||||
/// when the document is available.
|
||||
pub fn tooltip_(
|
||||
world: &dyn World,
|
||||
document: Option<&Document>,
|
||||
source: &Source,
|
||||
cursor: usize,
|
||||
) -> Option<Tooltip> {
|
||||
pub fn tooltip_(world: &dyn World, source: &Source, cursor: usize) -> Option<Tooltip> {
|
||||
let leaf = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
|
||||
if leaf.kind().is_trivia() {
|
||||
return None;
|
||||
|
@ -33,7 +27,8 @@ pub fn tooltip_(
|
|||
|
||||
named_param_tooltip(world, &leaf)
|
||||
.or_else(|| font_tooltip(world, &leaf))
|
||||
.or_else(|| document.and_then(|doc| label_tooltip(doc, &leaf)))
|
||||
// todo: test that label_tooltip can be removed safely
|
||||
// .or_else(|| document.and_then(|doc| label_tooltip(doc, &leaf)))
|
||||
.or_else(|| expr_tooltip(world, &leaf))
|
||||
.or_else(|| closure_tooltip(&leaf))
|
||||
}
|
||||
|
@ -79,6 +74,8 @@ pub fn expr_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<Tooltip> {
|
|||
|
||||
let mut last = None;
|
||||
let mut pieces: Vec<EcoString> = vec![];
|
||||
let mut unique_func: Option<Value> = None;
|
||||
let mut unique = true;
|
||||
let mut iter = values.iter();
|
||||
for (value, _) in (&mut iter).take(Sink::MAX_VALUES - 1) {
|
||||
if let Some((prev, count)) = &mut last {
|
||||
|
@ -89,10 +86,32 @@ pub fn expr_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<Tooltip> {
|
|||
write!(pieces.last_mut().unwrap(), " (x{count})").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(value, Value::Func(..) | Value::Type(..)) {
|
||||
match &unique_func {
|
||||
Some(unique_func) if unique => {
|
||||
unique = unique_func == value;
|
||||
}
|
||||
Some(_) => {}
|
||||
None => {
|
||||
unique_func = Some(value.clone());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unique = false;
|
||||
}
|
||||
|
||||
pieces.push(truncated_repr(value));
|
||||
last = Some((value, 1));
|
||||
}
|
||||
|
||||
// Don't report the only function reference...
|
||||
// Note we usually expect the `definition` analyzer work in this case, otherwise
|
||||
// please open an issue for this.
|
||||
if unique_func.is_some() && unique {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some((_, count)) = last {
|
||||
if count > 1 {
|
||||
write!(pieces.last_mut().unwrap(), " (x{count})").unwrap();
|
||||
|
@ -104,6 +123,7 @@ pub fn expr_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<Tooltip> {
|
|||
}
|
||||
|
||||
let tooltip = repr::pretty_comma_list(&pieces, false);
|
||||
// todo: check sensible length, value highlighting
|
||||
(!tooltip.is_empty()).then(|| Tooltip::Code(tooltip.into()))
|
||||
}
|
||||
|
||||
|
@ -155,29 +175,6 @@ fn length_tooltip(length: Length) -> Option<Tooltip> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Tooltip for a hovered reference or label.
|
||||
fn label_tooltip(document: &Document, leaf: &LinkedNode) -> Option<Tooltip> {
|
||||
let target = match leaf.kind() {
|
||||
SyntaxKind::RefMarker => leaf.text().trim_start_matches('@'),
|
||||
SyntaxKind::Label => leaf.text().trim_start_matches('<').trim_end_matches('>'),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
for DynLabel {
|
||||
label,
|
||||
label_desc: _,
|
||||
detail,
|
||||
..
|
||||
} in analyze_labels(document).0
|
||||
{
|
||||
if label.as_str() == target {
|
||||
return Some(Tooltip::Text(detail?));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Tooltips for components of a named parameter.
|
||||
fn named_param_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<Tooltip> {
|
||||
let (func, named) = if_chain! {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue