diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-01-27 12:05:00 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-01-27 12:05:12 +0100 |
| commit | 43ef60c09cc48f6b7c6dd752ab7af7c0d6071bc5 (patch) | |
| tree | 525320e583fc53e9474fd4b4d4944cdf8117d406 /src/ide | |
| parent | c56299c6bde121807c3febbef0766ff2fe2b32f2 (diff) | |
Tracing-based expression tooltips
Diffstat (limited to 'src/ide')
| -rw-r--r-- | src/ide/analyze.rs | 38 | ||||
| -rw-r--r-- | src/ide/mod.rs | 2 | ||||
| -rw-r--r-- | src/ide/tooltip.rs | 74 |
3 files changed, 94 insertions, 20 deletions
diff --git a/src/ide/analyze.rs b/src/ide/analyze.rs new file mode 100644 index 00000000..d8925cfc --- /dev/null +++ b/src/ide/analyze.rs @@ -0,0 +1,38 @@ +use comemo::Track; + +use crate::model::{eval, Route, Tracer, Value}; +use crate::syntax::{ast, LinkedNode, SyntaxKind}; +use crate::World; + +/// Try to determine a set of possible values for an expression. +pub fn analyze(world: &(dyn World + 'static), node: &LinkedNode) -> Vec<Value> { + match node.cast::<ast::Expr>() { + Some(ast::Expr::Ident(_) | ast::Expr::MathIdent(_)) => { + if let Some(parent) = node.parent() { + if parent.kind() == SyntaxKind::FieldAccess && node.index() > 0 { + return analyze(world, parent); + } + } + + let span = node.span(); + let source = world.source(span.source()); + let route = Route::default(); + let mut tracer = Tracer::new(Some(span)); + eval(world.track(), route.track(), tracer.track_mut(), source).ok(); + return tracer.finish(); + } + + Some(ast::Expr::FieldAccess(access)) => { + if let Some(child) = node.children().next() { + return analyze(world, &child) + .into_iter() + .filter_map(|target| target.field(&access.field()).ok()) + .collect(); + } + } + + _ => {} + } + + vec![] +} diff --git a/src/ide/mod.rs b/src/ide/mod.rs index f52fa076..ac69b38a 100644 --- a/src/ide/mod.rs +++ b/src/ide/mod.rs @@ -1,9 +1,11 @@ //! Capabilities for IDE support. +mod analyze; mod complete; mod highlight; mod tooltip; +pub use analyze::*; pub use complete::*; pub use highlight::*; pub use tooltip::*; diff --git a/src/ide/tooltip.rs b/src/ide/tooltip.rs index 076e2b45..202efd8e 100644 --- a/src/ide/tooltip.rs +++ b/src/ide/tooltip.rs @@ -1,40 +1,71 @@ use if_chain::if_chain; +use unicode_segmentation::UnicodeSegmentation; -use super::{plain_docs_sentence, summarize_font_family}; -use crate::model::{CastInfo, Value}; +use super::{analyze, plain_docs_sentence, summarize_font_family}; +use crate::model::{CastInfo, Tracer, Value}; use crate::syntax::ast; use crate::syntax::{LinkedNode, Source, SyntaxKind}; use crate::World; /// Describe the item under the cursor. -pub fn tooltip(world: &dyn World, source: &Source, cursor: usize) -> Option<String> { +pub fn tooltip( + world: &(dyn World + 'static), + source: &Source, + cursor: usize, +) -> Option<String> { let leaf = LinkedNode::new(source.root()).leaf_at(cursor)?; - function_tooltip(world, &leaf) - .or_else(|| named_param_tooltip(world, &leaf)) + named_param_tooltip(world, &leaf) .or_else(|| font_family_tooltip(world, &leaf)) + .or_else(|| expr_tooltip(world, &leaf)) } -/// Tooltip for a function or set rule name. -fn function_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> { - if_chain! { - if let Some(ident) = leaf.cast::<ast::Ident>(); - if matches!( - leaf.parent_kind(), - Some(SyntaxKind::FuncCall | SyntaxKind::SetRule), - ); - if let Some(Value::Func(func)) = world.library().global.scope().get(&ident); - if let Some(info) = func.info(); - then { - return Some(plain_docs_sentence(info.docs)); +/// Tooltip for a hovered expression. +fn expr_tooltip(world: &(dyn World + 'static), leaf: &LinkedNode) -> Option<String> { + if !leaf.is::<ast::Expr>() { + return None; + } + + let values = analyze(world, leaf); + if let [value] = values.as_slice() { + if let Some(docs) = value.docs() { + return Some(plain_docs_sentence(docs)); } } - None + let mut tooltip = String::new(); + let mut iter = values.into_iter().enumerate(); + for (i, value) in (&mut iter).take(Tracer::MAX - 1) { + if i > 0 && !tooltip.is_empty() { + tooltip.push_str(", "); + } + let repr = value.repr(); + let repr = repr.as_str(); + let len = repr.len(); + if len <= 40 { + tooltip.push_str(repr); + } else { + let mut graphemes = repr.graphemes(true); + let r = graphemes.next_back().map_or(0, str::len); + let l = graphemes.take(40).map(str::len).sum(); + tooltip.push_str(&repr[..l]); + tooltip.push_str("..."); + tooltip.push_str(&repr[len - r..]); + } + } + + if iter.next().is_some() { + tooltip.push_str(", ..."); + } + + (!tooltip.is_empty()).then(|| tooltip) } /// Tooltips for components of a named parameter. -fn named_param_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> { +fn named_param_tooltip( + world: &(dyn World + 'static), + leaf: &LinkedNode, +) -> Option<String> { let (info, named) = if_chain! { // Ensure that we are in a named pair in the arguments to a function // call or set rule. @@ -92,7 +123,10 @@ fn find_string_doc(info: &CastInfo, string: &str) -> Option<&'static str> { } /// Tooltip for font family. -fn font_family_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> { +fn font_family_tooltip( + world: &(dyn World + 'static), + leaf: &LinkedNode, +) -> Option<String> { if_chain! { // Ensure that we are on top of a string. if let Some(string) = leaf.cast::<ast::Str>(); |
