summaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorMyriad-Dreamin <35292584+Myriad-Dreamin@users.noreply.github.com>2023-09-18 20:51:55 +0800
committerGitHub <noreply@github.com>2023-09-18 14:51:55 +0200
commit3955b25a10d56c83552763c0ca42fb137fcefb87 (patch)
treefc39c9e120bd7a5d4bb2aa46914f14f148b33ca8 /crates
parentb10f9ae7b7f5a15c797441d4d67ea69d2ab9b617 (diff)
Add tooltips to a closure (#2164)
Diffstat (limited to 'crates')
-rw-r--r--crates/typst/src/eval/func.rs44
-rw-r--r--crates/typst/src/eval/mod.rs8
-rw-r--r--crates/typst/src/ide/tooltip.rs34
3 files changed, 60 insertions, 26 deletions
diff --git a/crates/typst/src/eval/func.rs b/crates/typst/src/eval/func.rs
index 3e4ea370..87237352 100644
--- a/crates/typst/src/eval/func.rs
+++ b/crates/typst/src/eval/func.rs
@@ -597,15 +597,15 @@ cast! {
}
/// A visitor that determines which variables to capture for a closure.
-pub(super) struct CapturesVisitor<'a> {
- external: &'a Scopes<'a>,
+pub struct CapturesVisitor<'a> {
+ external: Option<&'a Scopes<'a>>,
internal: Scopes<'a>,
captures: Scope,
}
impl<'a> CapturesVisitor<'a> {
/// Create a new visitor for the given external scopes.
- pub fn new(external: &'a Scopes) -> Self {
+ pub fn new(external: Option<&'a Scopes<'a>>) -> Self {
Self {
external,
internal: Scopes::new(None),
@@ -626,8 +626,10 @@ impl<'a> CapturesVisitor<'a> {
// Identifiers that shouldn't count as captures because they
// actually bind a new name are handled below (individually through
// the expressions that contain them).
- Some(ast::Expr::Ident(ident)) => self.capture(ident),
- Some(ast::Expr::MathIdent(ident)) => self.capture_in_math(ident),
+ Some(ast::Expr::Ident(ident)) => self.capture(&ident, Scopes::get),
+ Some(ast::Expr::MathIdent(ident)) => {
+ self.capture(&ident, Scopes::get_in_math)
+ }
// Code and content blocks create a scope.
Some(ast::Expr::Code(_) | ast::Expr::Content(_)) => {
@@ -736,20 +738,22 @@ impl<'a> CapturesVisitor<'a> {
}
/// Capture a variable if it isn't internal.
- fn capture(&mut self, ident: ast::Ident) {
- if self.internal.get(&ident).is_err() {
- if let Ok(value) = self.external.get(&ident) {
- self.captures.define_captured(ident.get().clone(), value.clone());
- }
- }
- }
-
- /// Capture a variable in math mode if it isn't internal.
- fn capture_in_math(&mut self, ident: ast::MathIdent) {
- if self.internal.get(&ident).is_err() {
- if let Ok(value) = self.external.get_in_math(&ident) {
- self.captures.define_captured(ident.get().clone(), value.clone());
- }
+ #[inline]
+ fn capture(
+ &mut self,
+ ident: &str,
+ getter: impl FnOnce(&'a Scopes<'a>, &str) -> StrResult<&'a Value>,
+ ) {
+ if self.internal.get(ident).is_err() {
+ let Some(value) = self
+ .external
+ .map(|external| getter(external, ident).ok())
+ .unwrap_or(Some(&Value::None))
+ else {
+ return;
+ };
+
+ self.captures.define_captured(ident, value.clone());
}
}
}
@@ -767,7 +771,7 @@ mod tests {
scopes.top.define("y", 0);
scopes.top.define("z", 0);
- let mut visitor = CapturesVisitor::new(&scopes);
+ let mut visitor = CapturesVisitor::new(Some(&scopes));
let root = parse(text);
visitor.visit(&root);
diff --git a/crates/typst/src/eval/mod.rs b/crates/typst/src/eval/mod.rs
index cbd00bc1..da0aadd0 100644
--- a/crates/typst/src/eval/mod.rs
+++ b/crates/typst/src/eval/mod.rs
@@ -50,7 +50,9 @@ pub use self::cast::{
pub use self::datetime::Datetime;
pub use self::dict::{dict, Dict};
pub use self::duration::Duration;
-pub use self::func::{func, Func, NativeFunc, NativeFuncData, ParamInfo};
+pub use self::func::{
+ func, CapturesVisitor, Func, NativeFunc, NativeFuncData, ParamInfo,
+};
pub use self::library::{set_lang_items, LangItems, Library};
pub use self::module::Module;
pub use self::none::NoneValue;
@@ -74,7 +76,7 @@ use if_chain::if_chain;
use serde::{Deserialize, Serialize};
use unicode_segmentation::UnicodeSegmentation;
-use self::func::{CapturesVisitor, Closure};
+use self::func::Closure;
use crate::diag::{
bail, error, warning, At, FileError, Hint, SourceDiagnostic, SourceResult, StrResult,
Trace, Tracepoint,
@@ -1340,7 +1342,7 @@ impl Eval for ast::Closure<'_> {
// Collect captured variables.
let captured = {
- let mut visitor = CapturesVisitor::new(&vm.scopes);
+ let mut visitor = CapturesVisitor::new(Some(&vm.scopes));
visitor.visit(self.to_untyped());
visitor.finish()
};
diff --git a/crates/typst/src/ide/tooltip.rs b/crates/typst/src/ide/tooltip.rs
index f310cad0..6f8c8927 100644
--- a/crates/typst/src/ide/tooltip.rs
+++ b/crates/typst/src/ide/tooltip.rs
@@ -7,10 +7,11 @@ use if_chain::if_chain;
use super::analyze::analyze_labels;
use super::{analyze_expr, plain_docs_sentence, summarize_font_family};
use crate::doc::Frame;
-use crate::eval::{CastInfo, Tracer, Value};
+use crate::eval::{CapturesVisitor, CastInfo, Tracer, Value};
use crate::geom::{round_2, Length, Numeric};
-use crate::syntax::{ast, LinkedNode, Source, SyntaxKind};
-use crate::util::pretty_comma_list;
+use crate::syntax::ast::{self, AstNode};
+use crate::syntax::{LinkedNode, Source, SyntaxKind};
+use crate::util::{pretty_comma_list, separated_list};
use crate::World;
/// Describe the item under the cursor.
@@ -29,6 +30,7 @@ pub fn tooltip(
.or_else(|| font_tooltip(world, &leaf))
.or_else(|| ref_tooltip(world, frames, &leaf))
.or_else(|| expr_tooltip(world, &leaf))
+ .or_else(|| closure_tooltip(&leaf))
}
/// A hover tooltip.
@@ -100,6 +102,32 @@ fn expr_tooltip(world: &(dyn World + 'static), leaf: &LinkedNode) -> Option<Tool
(!tooltip.is_empty()).then(|| Tooltip::Code(tooltip.into()))
}
+/// Tooltip for a hovered closure.
+fn closure_tooltip(leaf: &LinkedNode) -> Option<Tooltip> {
+ // Find the closure to analyze.
+ let mut ancestor = leaf;
+ while !ancestor.is::<ast::Closure>() {
+ ancestor = ancestor.parent()?;
+ }
+ let closure = ancestor.cast::<ast::Closure>()?.to_untyped();
+
+ // Analyze the closure's captures.
+ let mut visitor = CapturesVisitor::new(None);
+ visitor.visit(closure);
+
+ let captures = visitor.finish();
+ let mut names: Vec<_> =
+ captures.iter().map(|(name, _)| eco_format!("`{name}`")).collect();
+ if names.is_empty() {
+ return None;
+ }
+
+ names.sort();
+
+ let tooltip = separated_list(&names, "and");
+ Some(Tooltip::Text(eco_format!("This closure captures {tooltip}.")))
+}
+
/// Tooltip text for a hovered length.
fn length_tooltip(length: Length) -> Option<Tooltip> {
length.em.is_zero().then(|| {