From a5a4b0b72fcf948bc58dd5800eefbabb1d67a43d Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sun, 10 Nov 2024 12:15:28 +0100 Subject: [PATCH] Introduce `IdeWorld` trait --- crates/typst-ide/src/analyze.rs | 11 ++++++----- crates/typst-ide/src/complete.rs | 8 ++++---- crates/typst-ide/src/definition.rs | 7 ++++--- crates/typst-ide/src/jump.rs | 7 ++++--- crates/typst-ide/src/lib.rs | 31 ++++++++++++++++++++++++++++++ crates/typst-ide/src/matchers.rs | 5 ++--- crates/typst-ide/src/tooltip.rs | 16 +++++++-------- crates/typst-library/src/lib.rs | 16 --------------- 8 files changed, 59 insertions(+), 42 deletions(-) diff --git a/crates/typst-ide/src/analyze.rs b/crates/typst-ide/src/analyze.rs index 75ffaede5..ce7fe478b 100644 --- a/crates/typst-ide/src/analyze.rs +++ b/crates/typst-ide/src/analyze.rs @@ -5,12 +5,13 @@ use typst::foundations::{Context, Label, Scopes, Styles, Value}; use typst::introspection::Introspector; use typst::model::{BibliographyElem, Document}; use typst::syntax::{ast, LinkedNode, Span, SyntaxKind}; -use typst::World; use typst_eval::Vm; +use crate::IdeWorld; + /// Try to determine a set of possible values for an expression. pub fn analyze_expr( - world: &dyn World, + world: &dyn IdeWorld, node: &LinkedNode, ) -> EcoVec<(Value, Option)> { let Some(expr) = node.cast::() else { @@ -38,7 +39,7 @@ pub fn analyze_expr( } } - return typst::trace(world, node.span()); + return typst::trace(world.upcast(), node.span()); } }; @@ -46,7 +47,7 @@ pub fn analyze_expr( } /// Try to load a module from the current source file. -pub fn analyze_import(world: &dyn World, source: &LinkedNode) -> Option { +pub fn analyze_import(world: &dyn IdeWorld, source: &LinkedNode) -> Option { // Use span in the node for resolving imports with relative paths. let source_span = source.span(); let (source, _) = analyze_expr(world, source).into_iter().next()?; @@ -59,7 +60,7 @@ pub fn analyze_import(world: &dyn World, source: &LinkedNode) -> Option { let mut sink = Sink::new(); let engine = Engine { routines: &typst::ROUTINES, - world: world.track(), + world: world.upcast().track(), introspector: introspector.track(), traced: traced.track(), sink: sink.track_mut(), diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs index 2a606e4a7..6ea0a212c 100644 --- a/crates/typst-ide/src/complete.rs +++ b/crates/typst-ide/src/complete.rs @@ -15,12 +15,11 @@ use typst::syntax::{ }; use typst::text::RawElem; use typst::visualize::Color; -use typst::World; use unscanny::Scanner; use crate::{ analyze_expr, analyze_import, analyze_labels, named_items, plain_docs_sentence, - summarize_font_family, + summarize_font_family, IdeWorld, }; /// Autocomplete a cursor position in a source file. @@ -35,7 +34,7 @@ use crate::{ /// the autocompletions. Label completions, for instance, are only generated /// when the document is available. pub fn autocomplete( - world: &dyn World, + world: &dyn IdeWorld, document: Option<&Document>, source: &Source, cursor: usize, @@ -1023,7 +1022,7 @@ fn code_completions(ctx: &mut CompletionContext, hash: bool) { /// Context for autocompletion. struct CompletionContext<'a> { - world: &'a (dyn World + 'a), + world: &'a (dyn IdeWorld + 'a), document: Option<&'a Document>, global: &'a Scope, math: &'a Scope, @@ -1042,6 +1041,7 @@ impl<'a> CompletionContext<'a> { /// Create a new autocompletion context. fn new( world: &'a (dyn World + 'a), + world: &'a (dyn IdeWorld + 'a), document: Option<&'a Document>, source: &'a Source, leaf: &'a LinkedNode<'a>, diff --git a/crates/typst-ide/src/definition.rs b/crates/typst-ide/src/definition.rs index 4323226d3..a8286554b 100644 --- a/crates/typst-ide/src/definition.rs +++ b/crates/typst-ide/src/definition.rs @@ -3,9 +3,10 @@ use typst::foundations::{Label, Module, Selector, Value}; use typst::model::Document; use typst::syntax::ast::AstNode; use typst::syntax::{ast, LinkedNode, Side, Source, Span, SyntaxKind}; -use typst::World; -use crate::{analyze_import, deref_target, named_items, DerefTarget, NamedItem}; +use crate::{ + analyze_import, deref_target, named_items, DerefTarget, IdeWorld, NamedItem, +}; /// Find the definition of the item under the cursor. /// @@ -13,7 +14,7 @@ use crate::{analyze_import, deref_target, named_items, DerefTarget, NamedItem}; /// the definition search. Label definitions, for instance, are only generated /// when the document is available. pub fn definition( - world: &dyn World, + world: &dyn IdeWorld, document: Option<&Document>, source: &Source, cursor: usize, diff --git a/crates/typst-ide/src/jump.rs b/crates/typst-ide/src/jump.rs index e48db9865..5d270f04f 100644 --- a/crates/typst-ide/src/jump.rs +++ b/crates/typst-ide/src/jump.rs @@ -4,7 +4,8 @@ use typst::layout::{Frame, FrameItem, Point, Position, Size}; use typst::model::{Destination, Document, Url}; use typst::syntax::{FileId, LinkedNode, Side, Source, Span, SyntaxKind}; use typst::visualize::Geometry; -use typst::World; + +use crate::IdeWorld; /// Where to [jump](jump_from_click) to. #[derive(Debug, Clone, Eq, PartialEq)] @@ -18,7 +19,7 @@ pub enum Jump { } impl Jump { - fn from_span(world: &dyn World, span: Span) -> Option { + fn from_span(world: &dyn IdeWorld, span: Span) -> Option { let id = span.id()?; let source = world.source(id).ok()?; let node = source.find(span)?; @@ -28,7 +29,7 @@ impl Jump { /// Determine where to jump to based on a click in a frame. pub fn jump_from_click( - world: &dyn World, + world: &dyn IdeWorld, document: &Document, frame: &Frame, click: Point, diff --git a/crates/typst-ide/src/lib.rs b/crates/typst-ide/src/lib.rs index 4c1542fd5..f66999b43 100644 --- a/crates/typst-ide/src/lib.rs +++ b/crates/typst-ide/src/lib.rs @@ -17,7 +17,30 @@ pub use self::tooltip::{tooltip, Tooltip}; use std::fmt::Write; use ecow::{eco_format, EcoString}; +use typst::syntax::package::PackageSpec; use typst::text::{FontInfo, FontStyle}; +use typst::World; + +/// Extends the `World` for IDE functionality. +pub trait IdeWorld: World { + /// Turn into a normal [`World`]. + /// + /// This is necessary because trait upcasting is experimental in Rust. + /// See: https://github.com/rust-lang/rust/issues/65991 + /// + /// Implementors can simply return `self`. + fn upcast(&self) -> &dyn World; + + /// A list of all available packages and optionally descriptions for them. + /// + /// This function is **optional** to implement. It enhances the user + /// experience by enabling autocompletion for packages. Details about + /// packages from the `@preview` namespace are available from + /// `https://packages.typst.org/preview/index.json`. + fn packages(&self) -> &[(PackageSpec, Option)] { + &[] + } +} /// Extract the first sentence of plain text of a piece of documentation. /// @@ -107,6 +130,8 @@ mod tests { use typst::utils::{singleton, LazyHash}; use typst::{Library, World}; + use crate::IdeWorld; + /// A world for IDE testing. pub struct TestWorld { pub main: Source, @@ -193,6 +218,12 @@ mod tests { } } + impl IdeWorld for TestWorld { + fn upcast(&self) -> &dyn World { + self + } + } + /// Extra methods for [`Source`]. pub trait SourceExt { /// Negative cursors index from the back. diff --git a/crates/typst-ide/src/matchers.rs b/crates/typst-ide/src/matchers.rs index 1daec8193..dd7dfd1ff 100644 --- a/crates/typst-ide/src/matchers.rs +++ b/crates/typst-ide/src/matchers.rs @@ -2,13 +2,12 @@ use ecow::EcoString; use typst::foundations::{Module, Value}; use typst::syntax::ast::AstNode; use typst::syntax::{ast, LinkedNode, Span, SyntaxKind, SyntaxNode}; -use typst::World; -use crate::analyze_import; +use crate::{analyze_import, IdeWorld}; /// Find the named items starting from the given position. pub fn named_items( - world: &dyn World, + world: &dyn IdeWorld, position: LinkedNode, mut recv: impl FnMut(NamedItem) -> Option, ) -> Option { diff --git a/crates/typst-ide/src/tooltip.rs b/crates/typst-ide/src/tooltip.rs index 0936f2783..ade453e74 100644 --- a/crates/typst-ide/src/tooltip.rs +++ b/crates/typst-ide/src/tooltip.rs @@ -9,12 +9,11 @@ use typst::model::Document; use typst::syntax::ast::AstNode; use typst::syntax::{ast, LinkedNode, Side, Source, SyntaxKind}; use typst::utils::{round_with_precision, Numeric}; -use typst::World; use typst_eval::CapturesVisitor; use crate::{ analyze_expr, analyze_import, analyze_labels, plain_docs_sentence, - summarize_font_family, + summarize_font_family, IdeWorld, }; /// Describe the item under the cursor. @@ -23,7 +22,7 @@ use crate::{ /// the tooltips. Label tooltips, for instance, are only generated when the /// document is available. pub fn tooltip( - world: &dyn World, + world: &dyn IdeWorld, document: Option<&Document>, source: &Source, cursor: usize, @@ -52,7 +51,7 @@ pub enum Tooltip { } /// Tooltip for a hovered expression. -fn expr_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option { +fn expr_tooltip(world: &dyn IdeWorld, leaf: &LinkedNode) -> Option { let mut ancestor = leaf; while !ancestor.is::() { ancestor = ancestor.parent()?; @@ -112,8 +111,9 @@ fn expr_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option { } /// Tooltips for imports. -fn import_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option { +fn import_tooltip(world: &dyn IdeWorld, leaf: &LinkedNode) -> Option { if_chain! { + if leaf.kind() == SyntaxKind::Star; if let Some(parent) = leaf.parent(); if let Some(import) = parent.cast::(); if let Some(node) = parent.find(import.source().span()); @@ -192,7 +192,7 @@ fn label_tooltip(document: &Document, leaf: &LinkedNode) -> Option { } /// Tooltips for components of a named parameter. -fn named_param_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option { +fn named_param_tooltip(world: &dyn IdeWorld, leaf: &LinkedNode) -> Option { let (func, named) = if_chain! { // Ensure that we are in a named pair in the arguments to a function // call or set rule. @@ -249,7 +249,7 @@ fn find_string_doc(info: &CastInfo, string: &str) -> Option<&'static str> { } /// Tooltip for font. -fn font_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option { +fn font_tooltip(world: &dyn IdeWorld, leaf: &LinkedNode) -> Option { if_chain! { // Ensure that we are on top of a string. if let Some(string) = leaf.cast::(); @@ -320,7 +320,7 @@ mod tests { fn test_with_world(world: &TestWorld, cursor: isize, side: Side) -> Response { let source = &world.main; let doc = typst::compile(&world).output.ok(); - tooltip(&world, doc.as_ref(), source, source.cursor(cursor), side) + tooltip(world, doc.as_ref(), source, source.cursor(cursor), side) } #[test] diff --git a/crates/typst-library/src/lib.rs b/crates/typst-library/src/lib.rs index 55cd11328..4db377e94 100644 --- a/crates/typst-library/src/lib.rs +++ b/crates/typst-library/src/lib.rs @@ -27,8 +27,6 @@ pub mod visualize; use std::ops::{Deref, Range}; -use ecow::EcoString; -use typst_syntax::package::PackageSpec; use typst_syntax::{FileId, Source, Span}; use typst_utils::{LazyHash, SmallBitSet}; @@ -83,16 +81,6 @@ pub trait World: Send + Sync { /// If this function returns `None`, Typst's `datetime` function will /// return an error. fn today(&self, offset: Option) -> Option; - - /// A list of all available packages and optionally descriptions for them. - /// - /// This function is optional to implement. It enhances the user experience - /// by enabling autocompletion for packages. Details about packages from the - /// `@preview` namespace are available from - /// `https://packages.typst.org/preview/index.json`. - fn packages(&self) -> &[(PackageSpec, Option)] { - &[] - } } macro_rules! world_impl { @@ -125,10 +113,6 @@ macro_rules! world_impl { fn today(&self, offset: Option) -> Option { self.deref().today(offset) } - - fn packages(&self) -> &[(PackageSpec, Option)] { - self.deref().packages() - } } }; }