mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Introduce IdeWorld
trait
This commit is contained in:
parent
03ba5a0cb1
commit
a5a4b0b72f
@ -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<Styles>)> {
|
||||
let Some(expr) = node.cast::<ast::Expr>() 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<Value> {
|
||||
pub fn analyze_import(world: &dyn IdeWorld, source: &LinkedNode) -> Option<Value> {
|
||||
// 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<Value> {
|
||||
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(),
|
||||
|
@ -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>,
|
||||
|
@ -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,
|
||||
|
@ -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<Self> {
|
||||
fn from_span(world: &dyn IdeWorld, span: Span) -> Option<Self> {
|
||||
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,
|
||||
|
@ -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<EcoString>)] {
|
||||
&[]
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
@ -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<T>(
|
||||
world: &dyn World,
|
||||
world: &dyn IdeWorld,
|
||||
position: LinkedNode,
|
||||
mut recv: impl FnMut(NamedItem) -> Option<T>,
|
||||
) -> Option<T> {
|
||||
|
@ -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<Tooltip> {
|
||||
fn expr_tooltip(world: &dyn IdeWorld, leaf: &LinkedNode) -> Option<Tooltip> {
|
||||
let mut ancestor = leaf;
|
||||
while !ancestor.is::<ast::Expr>() {
|
||||
ancestor = ancestor.parent()?;
|
||||
@ -112,8 +111,9 @@ fn expr_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<Tooltip> {
|
||||
}
|
||||
|
||||
/// Tooltips for imports.
|
||||
fn import_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<Tooltip> {
|
||||
fn import_tooltip(world: &dyn IdeWorld, leaf: &LinkedNode) -> Option<Tooltip> {
|
||||
if_chain! {
|
||||
if leaf.kind() == SyntaxKind::Star;
|
||||
if let Some(parent) = leaf.parent();
|
||||
if let Some(import) = parent.cast::<ast::ModuleImport>();
|
||||
if let Some(node) = parent.find(import.source().span());
|
||||
@ -192,7 +192,7 @@ fn label_tooltip(document: &Document, leaf: &LinkedNode) -> Option<Tooltip> {
|
||||
}
|
||||
|
||||
/// Tooltips for components of a named parameter.
|
||||
fn named_param_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<Tooltip> {
|
||||
fn named_param_tooltip(world: &dyn IdeWorld, leaf: &LinkedNode) -> Option<Tooltip> {
|
||||
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<Tooltip> {
|
||||
fn font_tooltip(world: &dyn IdeWorld, leaf: &LinkedNode) -> Option<Tooltip> {
|
||||
if_chain! {
|
||||
// Ensure that we are on top of a string.
|
||||
if let Some(string) = leaf.cast::<ast::Str>();
|
||||
@ -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]
|
||||
|
@ -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<i64>) -> Option<Datetime>;
|
||||
|
||||
/// 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<EcoString>)] {
|
||||
&[]
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! world_impl {
|
||||
@ -125,10 +113,6 @@ macro_rules! world_impl {
|
||||
fn today(&self, offset: Option<i64>) -> Option<Datetime> {
|
||||
self.deref().today(offset)
|
||||
}
|
||||
|
||||
fn packages(&self) -> &[(PackageSpec, Option<EcoString>)] {
|
||||
self.deref().packages()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user