diff --git a/crates/typst-ide/src/tooltip.rs b/crates/typst-ide/src/tooltip.rs index 3ecc4d8fe..4864a2660 100644 --- a/crates/typst-ide/src/tooltip.rs +++ b/crates/typst-ide/src/tooltip.rs @@ -38,7 +38,7 @@ pub fn tooltip( } /// A hover tooltip. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum Tooltip { /// A string of text. Text(EcoString), @@ -250,3 +250,39 @@ fn font_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option { None } + +#[cfg(test)] +mod tests { + use typst::eval::Tracer; + use typst::syntax::Side; + + use super::{tooltip, Tooltip}; + use crate::tests::TestWorld; + + fn text(text: &str) -> Option { + Some(Tooltip::Text(text.into())) + } + + fn code(code: &str) -> Option { + Some(Tooltip::Code(code.into())) + } + + #[track_caller] + fn test(text: &str, cursor: usize, side: Side, expected: Option) { + let world = TestWorld::new(text); + let doc = typst::compile(&world, &mut Tracer::new()).ok(); + assert_eq!(tooltip(&world, doc.as_ref(), &world.main, cursor, side), expected); + } + + #[test] + fn test_tooltip() { + test("#let x = 1 + 2", 5, Side::After, code("3")); + test("#let x = 1 + 2", 6, Side::Before, code("3")); + test("#let f(x) = x + y", 11, Side::Before, text("This closure captures `y`.")); + } + + #[test] + fn test_empty_contextual() { + test("#{context}", 10, Side::Before, code("context()")); + } +} diff --git a/crates/typst-syntax/src/ast.rs b/crates/typst-syntax/src/ast.rs index a434e39af..01e0944dd 100644 --- a/crates/typst-syntax/src/ast.rs +++ b/crates/typst-syntax/src/ast.rs @@ -24,12 +24,6 @@ pub trait AstNode<'a>: Sized { } } -/// A static syntax node used as a fallback value. This is returned instead of -/// panicking when the syntactical structure isn't valid. In a normal -/// compilation, evaluation isn't attempted on a broken file, but for IDE -/// functionality, it is. -static ARBITRARY: SyntaxNode = SyntaxNode::arbitrary(); - macro_rules! node { ($(#[$attr:meta])* $name:ident) => { #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] @@ -56,7 +50,9 @@ macro_rules! node { impl Default for $name<'_> { #[inline] fn default() -> Self { - Self(&ARBITRARY) + static PLACEHOLDER: SyntaxNode + = SyntaxNode::placeholder(SyntaxKind::$name); + Self(&PLACEHOLDER) } } }; @@ -392,7 +388,7 @@ impl Expr<'_> { impl Default for Expr<'_> { fn default() -> Self { - Expr::Space(Space::default()) + Expr::None(None::default()) } } @@ -2135,3 +2131,13 @@ impl<'a> FuncReturn<'a> { self.0.cast_last_match() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_expr_default() { + assert!(Expr::default().to_untyped().cast::().is_some()); + } +} diff --git a/crates/typst-syntax/src/node.rs b/crates/typst-syntax/src/node.rs index b9efde6e6..8e623d51e 100644 --- a/crates/typst-syntax/src/node.rs +++ b/crates/typst-syntax/src/node.rs @@ -39,6 +39,21 @@ impl SyntaxNode { Self(Repr::Error(Arc::new(ErrorNode::new(message, text)))) } + /// Create a dummy node of the given kind. + /// + /// Panics if `kind` is `SyntaxKind::Error`. + #[track_caller] + pub const fn placeholder(kind: SyntaxKind) -> Self { + if matches!(kind, SyntaxKind::Error) { + panic!("cannot create error placeholder"); + } + Self(Repr::Leaf(LeafNode { + kind, + text: EcoString::new(), + span: Span::detached(), + })) + } + /// The type of the node. pub fn kind(&self) -> SyntaxKind { match &self.0 { @@ -297,17 +312,6 @@ impl SyntaxNode { Repr::Error(node) => node.error.span.number() + 1, } } - - /// An arbitrary node just for filling a slot in memory. - /// - /// In contrast to `default()`, this is a const fn. - pub(super) const fn arbitrary() -> Self { - Self(Repr::Leaf(LeafNode { - kind: SyntaxKind::End, - text: EcoString::new(), - span: Span::detached(), - })) - } } impl Debug for SyntaxNode { @@ -322,7 +326,7 @@ impl Debug for SyntaxNode { impl Default for SyntaxNode { fn default() -> Self { - Self::arbitrary() + Self::leaf(SyntaxKind::End, EcoString::new()) } }