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.
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
#[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;
|
||||
};
|
||||
|
||||
/// 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());
|
||||
}
|
||||
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);
|
||||
|
||||
|
@ -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()
|
||||
};
|
||||
|
@ -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(|| {
|
||||
|
Loading…
x
Reference in New Issue
Block a user