Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: hover tooltip on functions #76

Merged
merged 2 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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