Add tooltips to a closure (#2164)

This commit is contained in:
Myriad-Dreamin 2023-09-18 20:51:55 +08:00 committed by GitHub
parent b10f9ae7b7
commit 3955b25a10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 59 additions and 25 deletions

View File

@ -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);

View File

@ -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()
};

View File

@ -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(|| {