Skip to content

Commit

Permalink
feat: hover tooltip on functions (#76)
Browse files Browse the repository at this point in the history
* dev: introduce upstream tooltip

* feat: basic function definition
  • Loading branch information
Myriad-Dreamin authored Mar 19, 2024
1 parent b780a2a commit 14fc481
Show file tree
Hide file tree
Showing 10 changed files with 490 additions and 18 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/tinymist-query/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ typst-ts-core = { version = "0.4.2-rc8", default-features = false, features = [
] }

lsp-types.workspace = true
if_chain = "1"

[dev-dependencies]
once_cell.workspace = true
Expand Down
15 changes: 7 additions & 8 deletions crates/tinymist-query/src/goto_definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ impl SyntaxRequest for GotoDefinitionRequest {
}

pub(crate) struct DefinitionLink {
pub kind: LexicalKind,
pub value: Option<Value>,
pub fid: TypstFileId,
pub name: String,
Expand All @@ -97,6 +98,7 @@ pub(crate) fn find_definition(
let e = parent.cast::<ast::ModuleImport>()?;
let source = find_source_by_import(ctx.world, def_fid, e)?;
return Some(DefinitionLink {
kind: LexicalKind::Mod(LexicalModKind::PathVar),
name: String::new(),
value: None,
fid: source.id(),
Expand Down Expand Up @@ -151,6 +153,7 @@ pub(crate) fn find_definition(
let source = ctx.source_by_id(fid).ok()?;

return Some(DefinitionLink {
kind: LexicalKind::Var(LexicalVarKind::Function),
name: name.to_owned(),
value: Some(Value::Func(f.clone())),
fid,
Expand Down Expand Up @@ -178,6 +181,7 @@ pub(crate) fn find_definition(
| LexicalModKind::Alias { .. }
| LexicalModKind::Ident,
) => Some(DefinitionLink {
kind: def.kind.clone(),
name: def.name.clone(),
value: None,
fid: def_fid,
Expand All @@ -190,18 +194,13 @@ pub(crate) fn find_definition(
let def_name = root.leaf_at(def.range.start + 1)?;
log::info!("def_name for function: {def_name:?}", def_name = def_name);
let values = analyze_expr(ctx.world, &def_name);
let Some(func) = values.into_iter().find_map(|v| match v.0 {
Value::Func(f) => Some(f),
_ => None,
}) else {
log::info!("no func found... {:?}", def.name);
return None;
};
let func = values.into_iter().find(|v| matches!(v.0, Value::Func(..)));
log::info!("okay for function: {func:?}");

Some(DefinitionLink {
kind: def.kind.clone(),
name: def.name.clone(),
value: Some(Value::Func(func.clone())),
value: func.map(|v| v.0),
fid: def_fid,
def_range: def.range.clone(),
name_range: Some(def.range.clone()),
Expand Down
143 changes: 141 additions & 2 deletions crates/tinymist-query/src/hover.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
use crate::{prelude::*, StatefulRequest};
use core::fmt;

use ecow::eco_format;

use crate::{
analyze_signature, find_definition,
prelude::*,
syntax::{get_deref_target, LexicalVarKind},
upstream::{expr_tooltip, tooltip, Tooltip},
StatefulRequest,
};

#[derive(Debug, Clone)]
pub struct HoverRequest {
Expand All @@ -21,7 +31,8 @@ impl StatefulRequest for HoverRequest {
// the typst's cursor is 1-based, so we need to add 1 to the offset
let cursor = offset + 1;

let typst_tooltip = typst_ide::tooltip(ctx.world, doc, &source, cursor)?;
let typst_tooltip = def_tooltip(ctx, &source, cursor)
.or_else(|| tooltip(ctx.world, doc, &source, cursor))?;

let ast_node = LinkedNode::new(source.root()).leaf_at(cursor)?;
let range = ctx.to_lsp_range(ast_node.range(), &source);
Expand All @@ -32,3 +43,131 @@ impl StatefulRequest for HoverRequest {
})
}
}

fn def_tooltip(ctx: &mut AnalysisContext, source: &Source, cursor: usize) -> Option<Tooltip> {
let leaf = LinkedNode::new(source.root()).leaf_at(cursor)?;

let deref_target = get_deref_target(leaf.clone())?;

let lnk = find_definition(ctx, source.clone(), deref_target.clone())?;

match lnk.kind {
crate::syntax::LexicalKind::Mod(_)
| crate::syntax::LexicalKind::Var(LexicalVarKind::Label)
| crate::syntax::LexicalKind::Var(LexicalVarKind::LabelRef)
| crate::syntax::LexicalKind::Var(LexicalVarKind::ValRef)
| crate::syntax::LexicalKind::Block
| crate::syntax::LexicalKind::Heading(..) => None,
crate::syntax::LexicalKind::Var(LexicalVarKind::Function) => {
let f = lnk.value.as_ref();
Some(Tooltip::Text(eco_format!(
r#"```typc
let {}({});
```{}"#,
lnk.name,
ParamTooltip(f),
DocTooltip(f),
)))
}
crate::syntax::LexicalKind::Var(LexicalVarKind::Variable) => {
let deref_node = deref_target.node();
let v = expr_tooltip(ctx.world, deref_node)
.map(|t| match t {
Tooltip::Text(s) => format!("\n\nValues: {}", s),
Tooltip::Code(s) => format!("\n\nValues: ```{}```", s),
})
.unwrap_or_default();

Some(Tooltip::Text(eco_format!(
r#"
```typc
let {};
```{v}"#,
lnk.name
)))
}
}
}

struct ParamTooltip<'a>(Option<&'a Value>);

impl<'a> fmt::Display for ParamTooltip<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Some(Value::Func(func)) = self.0 else {
return Ok(());
};

let sig = analyze_signature(func.clone());

let mut is_first = true;
let mut write_sep = |f: &mut fmt::Formatter<'_>| {
if is_first {
is_first = false;
return Ok(());
}
f.write_str(", ")
};
for p in &sig.pos {
write_sep(f)?;
write!(f, "{}", p.name)?;
}
if let Some(rest) = &sig.rest {
write_sep(f)?;
write!(f, "{}", rest.name)?;
}

if !sig.named.is_empty() {
let mut name_prints = vec![];
for v in sig.named.values() {
name_prints.push((v.name.clone(), v.expr.clone()))
}
name_prints.sort();
for (k, v) in name_prints {
write_sep(f)?;
let v = v.as_deref().unwrap_or("any");
let mut v = v.trim();
if v.starts_with('{') && v.ends_with('}') && v.len() > 30 {
v = "{ ... }"
}
if v.starts_with('`') && v.ends_with('`') && v.len() > 30 {
v = "raw"
}
if v.starts_with('[') && v.ends_with(']') && v.len() > 30 {
v = "content"
}
write!(f, "{k}: {v}")?;
}
}

Ok(())
}
}

struct DocTooltip<'a>(Option<&'a Value>);

impl<'a> fmt::Display for DocTooltip<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Some(Value::Func(func)) = self.0 else {
return Ok(());
};

f.write_str("\n\n")?;

use typst::foundations::func::Repr;
let mut func = func;
let docs = 'search: loop {
match func.inner() {
Repr::Native(n) => break 'search n.docs,
Repr::Element(e) => break 'search e.docs(),
Repr::With(w) => {
func = &w.0;
}
Repr::Closure(..) => {
return Ok(());
}
}
};

f.write_str(docs)
}
}
51 changes: 44 additions & 7 deletions crates/tinymist-query/src/inlay_hint.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use core::fmt;
use std::{borrow::Cow, ops::Range};

use ecow::eco_vec;
use ecow::{eco_format, eco_vec};
use log::debug;
use lsp_types::{InlayHintKind, InlayHintLabel};
use typst::{
foundations::{Args, Closure},
foundations::{Args, CastInfo, Closure},
syntax::SyntaxNode,
util::LazyHash,
};
Expand Down Expand Up @@ -486,6 +487,8 @@ fn analyze_call_no_cache(func: Func, args: ast::Args<'_>) -> Option<CallInfo> {
pub struct ParamSpec {
/// The parameter's name.
pub name: Cow<'static, str>,
/// The parameter's default name.
pub expr: Option<EcoString>,
/// Creates an instance of the parameter's default value.
pub default: Option<fn() -> Value>,
/// Is the parameter positional?
Expand All @@ -503,6 +506,7 @@ impl ParamSpec {
fn from_static(s: &ParamInfo) -> Arc<Self> {
Arc::new(Self {
name: Cow::Borrowed(s.name),
expr: Some(eco_format!("{}", TypeExpr(&s.input))),
default: s.default,
positional: s.positional,
named: s.named,
Expand All @@ -512,16 +516,16 @@ impl ParamSpec {
}

#[derive(Debug, Clone)]
struct Signature {
pos: Vec<Arc<ParamSpec>>,
named: HashMap<Cow<'static, str>, Arc<ParamSpec>>,
pub struct Signature {
pub pos: Vec<Arc<ParamSpec>>,
pub named: HashMap<Cow<'static, str>, Arc<ParamSpec>>,
has_fill_or_size_or_stroke: bool,
rest: Option<Arc<ParamSpec>>,
pub rest: Option<Arc<ParamSpec>>,
_broken: bool,
}

#[comemo::memoize]
fn analyze_signature(func: Func) -> Arc<Signature> {
pub(crate) fn analyze_signature(func: Func) -> Arc<Signature> {
use typst::foundations::func::Repr;
let params = match func.inner() {
Repr::With(..) => unreachable!(),
Expand Down Expand Up @@ -595,6 +599,7 @@ fn analyze_closure_signature(c: Arc<LazyHash<Closure>>) -> Vec<Arc<ParamSpec>> {
ast::Param::Pos(ast::Pattern::Placeholder(..)) => {
params.push(Arc::new(ParamSpec {
name: Cow::Borrowed("_"),
expr: None,
default: None,
positional: true,
named: false,
Expand All @@ -611,15 +616,18 @@ fn analyze_closure_signature(c: Arc<LazyHash<Closure>>) -> Vec<Arc<ParamSpec>> {

params.push(Arc::new(ParamSpec {
name: Cow::Owned(name.to_owned()),
expr: None,
default: None,
positional: true,
named: false,
variadic: false,
}));
}
// todo: pattern
ast::Param::Named(n) => {
params.push(Arc::new(ParamSpec {
name: Cow::Owned(n.name().as_str().to_owned()),
expr: Some(unwrap_expr(n.expr()).to_untyped().clone().into_text()),
default: None,
positional: false,
named: true,
Expand All @@ -630,6 +638,7 @@ fn analyze_closure_signature(c: Arc<LazyHash<Closure>>) -> Vec<Arc<ParamSpec>> {
let ident = n.sink_ident().map(|e| e.as_str());
params.push(Arc::new(ParamSpec {
name: Cow::Owned(ident.unwrap_or_default().to_owned()),
expr: None,
default: None,
positional: false,
named: true,
Expand All @@ -654,6 +663,34 @@ fn is_one_line_(src: &Source, arg_node: &LinkedNode<'_>) -> Option<bool> {
Some(ll == rl)
}

fn unwrap_expr(mut e: ast::Expr) -> ast::Expr {
while let ast::Expr::Parenthesized(p) = e {
e = p.expr();
}

e
}

struct TypeExpr<'a>(&'a CastInfo);

impl<'a> fmt::Display for TypeExpr<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self.0 {
CastInfo::Any => "any",
CastInfo::Value(.., v) => v,
CastInfo::Type(v) => {
f.write_str(v.short_name())?;
return Ok(());
}
CastInfo::Union(v) => {
let mut values = v.iter().map(|e| TypeExpr(e).to_string());
f.write_str(&values.join(" | "))?;
return Ok(());
}
})
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
1 change: 1 addition & 0 deletions crates/tinymist-query/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod adt;
pub mod analysis;
pub mod syntax;
mod upstream;

pub(crate) mod diagnostics;

Expand Down
2 changes: 1 addition & 1 deletion crates/tinymist-query/src/lsp_typst_boundary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub type TypstSpan = typst::syntax::Span;
pub type LspRange = lsp_types::Range;
pub type TypstRange = std::ops::Range<usize>;

pub type TypstTooltip = typst_ide::Tooltip;
pub type TypstTooltip = crate::upstream::Tooltip;
pub type LspHoverContents = lsp_types::HoverContents;

pub type LspDiagnostic = lsp_types::Diagnostic;
Expand Down
1 change: 1 addition & 0 deletions crates/tinymist-query/src/syntax/lexical_hierarchy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ impl LexicalHierarchyWorker {
}
}
} else {
// todo: for loop variable
match node.kind() {
SyntaxKind::LetBinding => 'let_binding: {
let name = node.children().find(|n| n.cast::<ast::Pattern>().is_some());
Expand Down
Loading

0 comments on commit 14fc481

Please sign in to comment.