mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Add tooltips to a closure (#2164)
This commit is contained in:
parent
b10f9ae7b7
commit
3955b25a10
@ -597,15 +597,15 @@ cast! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A visitor that determines which variables to capture for a closure.
|
/// A visitor that determines which variables to capture for a closure.
|
||||||
pub(super) struct CapturesVisitor<'a> {
|
pub struct CapturesVisitor<'a> {
|
||||||
external: &'a Scopes<'a>,
|
external: Option<&'a Scopes<'a>>,
|
||||||
internal: Scopes<'a>,
|
internal: Scopes<'a>,
|
||||||
captures: Scope,
|
captures: Scope,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CapturesVisitor<'a> {
|
impl<'a> CapturesVisitor<'a> {
|
||||||
/// Create a new visitor for the given external scopes.
|
/// 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 {
|
Self {
|
||||||
external,
|
external,
|
||||||
internal: Scopes::new(None),
|
internal: Scopes::new(None),
|
||||||
@ -626,8 +626,10 @@ impl<'a> CapturesVisitor<'a> {
|
|||||||
// Identifiers that shouldn't count as captures because they
|
// Identifiers that shouldn't count as captures because they
|
||||||
// actually bind a new name are handled below (individually through
|
// actually bind a new name are handled below (individually through
|
||||||
// the expressions that contain them).
|
// the expressions that contain them).
|
||||||
Some(ast::Expr::Ident(ident)) => self.capture(ident),
|
Some(ast::Expr::Ident(ident)) => self.capture(&ident, Scopes::get),
|
||||||
Some(ast::Expr::MathIdent(ident)) => self.capture_in_math(ident),
|
Some(ast::Expr::MathIdent(ident)) => {
|
||||||
|
self.capture(&ident, Scopes::get_in_math)
|
||||||
|
}
|
||||||
|
|
||||||
// Code and content blocks create a scope.
|
// Code and content blocks create a scope.
|
||||||
Some(ast::Expr::Code(_) | ast::Expr::Content(_)) => {
|
Some(ast::Expr::Code(_) | ast::Expr::Content(_)) => {
|
||||||
@ -736,20 +738,22 @@ impl<'a> CapturesVisitor<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Capture a variable if it isn't internal.
|
/// Capture a variable if it isn't internal.
|
||||||
fn capture(&mut self, ident: ast::Ident) {
|
#[inline]
|
||||||
if self.internal.get(&ident).is_err() {
|
fn capture(
|
||||||
if let Ok(value) = self.external.get(&ident) {
|
&mut self,
|
||||||
self.captures.define_captured(ident.get().clone(), value.clone());
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
/// Capture a variable in math mode if it isn't internal.
|
self.captures.define_captured(ident, value.clone());
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -767,7 +771,7 @@ mod tests {
|
|||||||
scopes.top.define("y", 0);
|
scopes.top.define("y", 0);
|
||||||
scopes.top.define("z", 0);
|
scopes.top.define("z", 0);
|
||||||
|
|
||||||
let mut visitor = CapturesVisitor::new(&scopes);
|
let mut visitor = CapturesVisitor::new(Some(&scopes));
|
||||||
let root = parse(text);
|
let root = parse(text);
|
||||||
visitor.visit(&root);
|
visitor.visit(&root);
|
||||||
|
|
||||||
|
@ -50,7 +50,9 @@ pub use self::cast::{
|
|||||||
pub use self::datetime::Datetime;
|
pub use self::datetime::Datetime;
|
||||||
pub use self::dict::{dict, Dict};
|
pub use self::dict::{dict, Dict};
|
||||||
pub use self::duration::Duration;
|
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::library::{set_lang_items, LangItems, Library};
|
||||||
pub use self::module::Module;
|
pub use self::module::Module;
|
||||||
pub use self::none::NoneValue;
|
pub use self::none::NoneValue;
|
||||||
@ -74,7 +76,7 @@ use if_chain::if_chain;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use self::func::{CapturesVisitor, Closure};
|
use self::func::Closure;
|
||||||
use crate::diag::{
|
use crate::diag::{
|
||||||
bail, error, warning, At, FileError, Hint, SourceDiagnostic, SourceResult, StrResult,
|
bail, error, warning, At, FileError, Hint, SourceDiagnostic, SourceResult, StrResult,
|
||||||
Trace, Tracepoint,
|
Trace, Tracepoint,
|
||||||
@ -1340,7 +1342,7 @@ impl Eval for ast::Closure<'_> {
|
|||||||
|
|
||||||
// Collect captured variables.
|
// Collect captured variables.
|
||||||
let captured = {
|
let captured = {
|
||||||
let mut visitor = CapturesVisitor::new(&vm.scopes);
|
let mut visitor = CapturesVisitor::new(Some(&vm.scopes));
|
||||||
visitor.visit(self.to_untyped());
|
visitor.visit(self.to_untyped());
|
||||||
visitor.finish()
|
visitor.finish()
|
||||||
};
|
};
|
||||||
|
@ -7,10 +7,11 @@ use if_chain::if_chain;
|
|||||||
use super::analyze::analyze_labels;
|
use super::analyze::analyze_labels;
|
||||||
use super::{analyze_expr, plain_docs_sentence, summarize_font_family};
|
use super::{analyze_expr, plain_docs_sentence, summarize_font_family};
|
||||||
use crate::doc::Frame;
|
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::geom::{round_2, Length, Numeric};
|
||||||
use crate::syntax::{ast, LinkedNode, Source, SyntaxKind};
|
use crate::syntax::ast::{self, AstNode};
|
||||||
use crate::util::pretty_comma_list;
|
use crate::syntax::{LinkedNode, Source, SyntaxKind};
|
||||||
|
use crate::util::{pretty_comma_list, separated_list};
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
/// Describe the item under the cursor.
|
/// Describe the item under the cursor.
|
||||||
@ -29,6 +30,7 @@ pub fn tooltip(
|
|||||||
.or_else(|| font_tooltip(world, &leaf))
|
.or_else(|| font_tooltip(world, &leaf))
|
||||||
.or_else(|| ref_tooltip(world, frames, &leaf))
|
.or_else(|| ref_tooltip(world, frames, &leaf))
|
||||||
.or_else(|| expr_tooltip(world, &leaf))
|
.or_else(|| expr_tooltip(world, &leaf))
|
||||||
|
.or_else(|| closure_tooltip(&leaf))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A hover tooltip.
|
/// 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.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.
|
/// Tooltip text for a hovered length.
|
||||||
fn length_tooltip(length: Length) -> Option<Tooltip> {
|
fn length_tooltip(length: Length) -> Option<Tooltip> {
|
||||||
length.em.is_zero().then(|| {
|
length.em.is_zero().then(|| {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user