dev: stateful requests now accept snapshot (#1581)

* dev: stateful requests now accept snapshot

* dev: add some convenient methods

* dev: remove unused latest_doc state

* dev: use graph

* docs: comment

* fix: bad flag
This commit is contained in:
Myriad-Dreamin 2025-03-25 16:28:00 +08:00 committed by GitHub
parent 5b42231a77
commit 10ec787cc9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 383 additions and 504 deletions

View file

@ -1,70 +1,72 @@
//! Project compiler for tinymist.
use core::fmt;
use std::any::TypeId;
use std::collections::HashSet;
use std::path::Path;
use std::sync::{Arc, OnceLock};
use ecow::{eco_vec, EcoVec};
use tinymist_std::error::prelude::Result;
use tinymist_std::typst::{TypstHtmlDocument, TypstPagedDocument};
use tinymist_std::{typst::TypstDocument, ImmutPath};
use tinymist_world::vfs::notify::{
FilesystemEvent, MemoryEvent, NotifyDeps, NotifyMessage, UpstreamUpdateEvent,
};
use tinymist_world::vfs::{FileId, FsProvider, RevisingVfs, WorkspaceResolver};
use tinymist_world::{
CompileSnapshot, CompilerFeat, CompilerUniverse, EntryReader, EntryState, ExportSignal,
ProjectInsId, TaskInputs, WorldComputeGraph, WorldDeps,
CompileSnapshot, CompilerFeat, CompilerUniverse, DiagnosticsTask, EntryReader, EntryState,
ExportSignal, FlagTask, HtmlCompilationTask, PagedCompilationTask, ProjectInsId, TaskInputs,
WorldComputeGraph, WorldDeps,
};
use tokio::sync::mpsc;
use typst::diag::{SourceDiagnostic, SourceResult, Warned};
/// A compiled artifact.
pub struct CompiledArtifact<F: CompilerFeat> {
/// The used snapshot.
pub snap: CompileSnapshot<F>,
/// The used compute graph.
pub graph: Arc<WorldComputeGraph<F>>,
/// The diagnostics of the document.
pub warnings: EcoVec<SourceDiagnostic>,
pub diag: Arc<DiagnosticsTask>,
/// The compiled document.
pub doc: SourceResult<TypstDocument>,
pub doc: Option<TypstDocument>,
/// The depended files.
pub deps: OnceLock<EcoVec<FileId>>,
}
impl<F: CompilerFeat> fmt::Display for CompiledArtifact<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let rev = self.world.revision();
write!(f, "CompiledArtifact({:?}, rev={rev:?})", self.id)
let rev = self.graph.snap.world.revision();
write!(f, "CompiledArtifact({:?}, rev={rev:?})", self.graph.snap.id)
}
}
impl<F: CompilerFeat> std::ops::Deref for CompiledArtifact<F> {
type Target = CompileSnapshot<F>;
type Target = Arc<WorldComputeGraph<F>>;
fn deref(&self) -> &Self::Target {
&self.snap
&self.graph
}
}
impl<F: CompilerFeat> Clone for CompiledArtifact<F> {
fn clone(&self) -> Self {
Self {
snap: self.snap.clone(),
graph: self.graph.clone(),
doc: self.doc.clone(),
warnings: self.warnings.clone(),
diag: self.diag.clone(),
deps: self.deps.clone(),
}
}
}
impl<F: CompilerFeat> CompiledArtifact<F> {
/// Returns the project id.
pub fn id(&self) -> &ProjectInsId {
&self.graph.snap.id
}
/// Returns the last successfully compiled document.
pub fn success_doc(&self) -> Option<TypstDocument> {
self.doc
.as_ref()
.ok()
.cloned()
.or_else(|| self.snap.success_doc.clone())
}
@ -73,7 +75,7 @@ impl<F: CompilerFeat> CompiledArtifact<F> {
pub fn depended_files(&self) -> &EcoVec<FileId> {
self.deps.get_or_init(|| {
let mut deps = EcoVec::default();
self.world.iter_dependencies(&mut |f| {
self.graph.snap.world.iter_dependencies(&mut |f| {
deps.push(f);
});
@ -82,108 +84,52 @@ impl<F: CompilerFeat> CompiledArtifact<F> {
}
/// Runs the compiler and returns the compiled document.
pub fn from_snapshot(snap: CompileSnapshot<F>) -> CompiledArtifact<F> {
let is_html = snap.world.library.features.is_enabled(typst::Feature::Html);
pub fn from_graph(graph: Arc<WorldComputeGraph<F>>) -> CompiledArtifact<F> {
let is_html = graph.library().features.is_enabled(typst::Feature::Html);
if is_html {
Self::from_snapshot_inner::<TypstHtmlDocument>(snap)
let _ = graph.provide::<FlagTask<HtmlCompilationTask>>(Ok(FlagTask::flag(is_html)));
let _ = graph.provide::<FlagTask<PagedCompilationTask>>(Ok(FlagTask::flag(!is_html)));
let doc = if is_html {
graph.shared_compile_html().expect("html").map(From::from)
} else {
Self::from_snapshot_inner::<TypstPagedDocument>(snap)
}
}
/// Runs the compiler and returns the compiled document.
fn from_snapshot_inner<D>(mut snap: CompileSnapshot<F>) -> CompiledArtifact<F>
where
D: typst::Document + 'static,
Arc<D>: Into<TypstDocument>,
{
snap.world.set_is_compiling(true);
let res = ::typst::compile::<D>(&snap.world);
snap.world.set_is_compiling(false);
Self::from_snapshot_result(
snap,
Warned {
output: res.output.map(Arc::new),
warnings: res.warnings,
},
)
}
/// Runs the compiler and returns the compiled document.
pub fn from_snapshot_result<D>(
snap: CompileSnapshot<F>,
res: Warned<SourceResult<Arc<D>>>,
) -> CompiledArtifact<F>
where
D: typst::Document + 'static,
Arc<D>: Into<TypstDocument>,
{
let is_html_compilation = TypeId::of::<D>() == TypeId::of::<TypstHtmlDocument>();
let warned = match res.output {
Ok(doc) => Ok(Warned {
output: doc,
warnings: res.warnings,
}),
Err(diags) => match (res.warnings.is_empty(), diags.is_empty()) {
(true, true) => Err(diags),
(true, false) => Err(diags),
(false, true) => Err(res.warnings),
(false, false) => {
let mut warnings = res.warnings;
warnings.extend(diags);
Err(warnings)
}
},
};
let (doc, warnings) = match warned {
Ok(doc) => (Ok(doc.output.into()), doc.warnings),
Err(err) => (Err(err), EcoVec::default()),
};
let exclude_html_warnings = if !is_html_compilation {
warnings
} else if warnings.len() == 1
&& warnings[0]
.message
.starts_with("html export is under active development")
{
EcoVec::new()
} else {
warnings
graph.shared_compile().expect("paged").map(From::from)
};
CompiledArtifact {
snap,
diag: graph.shared_diagnostics().expect("diag"),
graph,
doc,
warnings: exclude_html_warnings,
deps: OnceLock::default(),
}
}
/// Returns error diagnostics.
pub fn errors(&self) -> Option<&EcoVec<SourceDiagnostic>> {
self.doc.as_ref().err()
/// Returns the error count.
pub fn error_cnt(&self) -> usize {
self.diag.error_cnt()
}
/// Returns warning diagnostics.
pub fn warnings(&self) -> &EcoVec<SourceDiagnostic> {
&self.warnings
/// Returns the warning count.
pub fn warning_cnt(&self) -> usize {
self.diag.warning_cnt()
}
/// Returns the diagnostics.
pub fn diagnostics(&self) -> impl Iterator<Item = &typst::diag::SourceDiagnostic> {
self.diag.diagnostics()
}
/// Returns whether there are any errors.
pub fn has_errors(&self) -> bool {
self.errors().is_some_and(|e| !e.is_empty())
self.error_cnt() > 0
}
/// Returns whether there are any warnings.
pub fn diagnostics(&self) -> impl Iterator<Item = &SourceDiagnostic> {
self.errors()
.into_iter()
.flatten()
.chain(self.warnings.iter())
/// Sets the signal.
pub fn with_signal(mut self, signal: ExportSignal) -> Self {
let mut snap = self.snap.clone();
snap.signal = signal;
self.graph = self.graph.snapshot_unsafe(snap);
self
}
}
@ -315,7 +261,7 @@ impl<F: CompilerFeat> fmt::Debug for Interrupt<F> {
match self {
Interrupt::Compile(id) => write!(f, "Compile({id:?})"),
Interrupt::Settle(id) => write!(f, "Settle({id:?})"),
Interrupt::Compiled(artifact) => write!(f, "Compiled({:?})", artifact.id),
Interrupt::Compiled(artifact) => write!(f, "Compiled({:?})", artifact.id()),
Interrupt::ChangeTask(id, change) => {
write!(f, "ChangeTask({id:?}, entry={:?})", change.entry.is_some())
}
@ -472,7 +418,7 @@ impl<F: CompilerFeat + Send + Sync + 'static, Ext: Default + 'static> ProjectCom
}
/// Creates a snapshot of the primary project.
pub fn snapshot(&mut self) -> CompileSnapshot<F> {
pub fn snapshot(&mut self) -> Arc<WorldComputeGraph<F>> {
self.primary.snapshot()
}
@ -500,7 +446,6 @@ impl<F: CompilerFeat + Send + Sync + 'static, Ext: Default + 'static> ProjectCom
snapshot: None,
handler,
compilation: OnceLock::default(),
latest_doc: None,
latest_success_doc: None,
deps: Default::default(),
committed_revision: 0,
@ -591,7 +536,8 @@ impl<F: CompilerFeat + Send + Sync + 'static, Ext: Default + 'static> ProjectCom
proj.reason.see(reason_by_entry_change());
}
Interrupt::Compiled(artifact) => {
let proj = Self::find_project(&mut self.primary, &mut self.dedicates, &artifact.id);
let proj =
Self::find_project(&mut self.primary, &mut self.dedicates, artifact.id());
let processed = proj.process_compile(artifact);
@ -636,7 +582,6 @@ impl<F: CompilerFeat + Send + Sync + 'static, Ext: Default + 'static> ProjectCom
}
// Reset the watch state and document state.
proj.latest_doc = None;
proj.latest_success_doc = None;
}
@ -796,8 +741,8 @@ pub struct ProjectInsState<F: CompilerFeat, Ext> {
pub verse: CompilerUniverse<F>,
/// The reason to compile.
pub reason: CompileReasons,
/// The latest snapshot.
snapshot: Option<CompileSnapshot<F>>,
/// The latest compute graph (snapshot).
snapshot: Option<Arc<WorldComputeGraph<F>>>,
/// The latest compilation.
pub compilation: OnceLock<CompiledArtifact<F>>,
/// The compilation handle.
@ -805,8 +750,6 @@ pub struct ProjectInsState<F: CompilerFeat, Ext> {
/// The file dependencies.
deps: EcoVec<ImmutPath>,
/// The latest compiled document.
pub(crate) latest_doc: Option<TypstDocument>,
/// The latest successly compiled document.
latest_success_doc: Option<TypstDocument>,
@ -815,9 +758,9 @@ pub struct ProjectInsState<F: CompilerFeat, Ext> {
impl<F: CompilerFeat, Ext: 'static> ProjectInsState<F, Ext> {
/// Creates a snapshot of the project.
pub fn snapshot(&mut self) -> CompileSnapshot<F> {
pub fn snapshot(&mut self) -> Arc<WorldComputeGraph<F>> {
match self.snapshot.as_ref() {
Some(snap) if snap.world.revision() == self.verse.revision => snap.clone(),
Some(snap) if snap.world().revision() == self.verse.revision => snap.clone(),
_ => {
let snap = self.make_snapshot();
self.snapshot = Some(snap.clone());
@ -826,9 +769,9 @@ impl<F: CompilerFeat, Ext: 'static> ProjectInsState<F, Ext> {
}
}
fn make_snapshot(&self) -> CompileSnapshot<F> {
fn make_snapshot(&self) -> Arc<WorldComputeGraph<F>> {
let world = self.verse.snapshot();
CompileSnapshot {
let snap = CompileSnapshot {
id: self.id.clone(),
world,
signal: ExportSignal {
@ -837,7 +780,8 @@ impl<F: CompilerFeat, Ext: 'static> ProjectInsState<F, Ext> {
by_fs_events: self.reason.by_fs_events,
},
success_doc: self.latest_success_doc.clone(),
}
};
WorldComputeGraph::new(snap)
}
/// Compile the document once if there is any reason and the entry is
@ -854,9 +798,8 @@ impl<F: CompilerFeat, Ext: 'static> ProjectInsState<F, Ext> {
let snap = self.snapshot();
self.reason = Default::default();
Some(move || {
let compiled = WorldComputeGraph::new(snap);
compute(&compiled);
compiled
compute(&snap);
snap
})
}
@ -880,33 +823,33 @@ impl<F: CompilerFeat, Ext: 'static> ProjectInsState<F, Ext> {
/// Compile the document once.
fn run_compile(
h: Arc<dyn CompileHandler<F, Ext>>,
snap: CompileSnapshot<F>,
graph: Arc<WorldComputeGraph<F>>,
) -> impl FnOnce() -> CompiledArtifact<F> {
let start = tinymist_std::time::now();
// todo unwrap main id
let id = snap.world.main_id().unwrap();
let revision = snap.world.revision().get();
let id = graph.world().main_id().unwrap();
let revision = graph.world().revision().get();
h.status(
revision,
&snap.id,
&graph.snap.id,
CompileReport::Stage(id, "compiling", start),
);
move || {
let compiled = CompiledArtifact::from_snapshot(snap);
let compiled = CompiledArtifact::from_graph(graph);
let elapsed = start.elapsed().unwrap_or_default();
let rep = match &compiled.doc {
Ok(..) => CompileReport::CompileSuccess(id, compiled.warnings.len(), elapsed),
Err(err) => CompileReport::CompileError(id, err.len(), elapsed),
Some(..) => CompileReport::CompileSuccess(id, compiled.warning_cnt(), elapsed),
None => CompileReport::CompileError(id, compiled.error_cnt(), elapsed),
};
// todo: we need to check revision for really concurrent compilation
log_compile_report(&rep);
h.status(revision, &compiled.id, rep);
h.status(revision, compiled.id(), rep);
h.notify_compile(&compiled);
compiled
@ -921,11 +864,10 @@ impl<F: CompilerFeat, Ext: 'static> ProjectInsState<F, Ext> {
}
// Update state.
let doc = artifact.doc.ok();
let doc = artifact.doc.clone();
self.committed_revision = compiled_revision;
self.latest_doc.clone_from(&doc);
if doc.is_some() {
self.latest_success_doc.clone_from(&self.latest_doc);
self.latest_success_doc = doc;
}
// Notify the new file dependencies.
@ -938,7 +880,7 @@ impl<F: CompilerFeat, Ext: 'static> ProjectInsState<F, Ext> {
self.deps = deps.clone();
let mut world = artifact.snap.world;
let mut world = world.clone();
let is_primary = self.id == ProjectInsId("primary".into());

View file

@ -4,11 +4,11 @@ use std::{borrow::Cow, sync::Arc};
use tinymist_std::error::prelude::*;
use tinymist_std::{bail, ImmutPath};
use tinymist_task::ExportTarget;
use tinymist_world::args::*;
use tinymist_world::config::CompileFontOpts;
use tinymist_world::font::system::SystemFontSearcher;
use tinymist_world::package::{http::HttpRegistry, RegistryPathMapper};
use tinymist_world::vfs::{system::SystemAccessModel, Vfs};
use tinymist_world::{args::*, WorldComputeGraph};
use tinymist_world::{
CompileSnapshot, CompilerFeat, CompilerUniverse, CompilerWorld, EntryOpts, EntryState,
};
@ -42,6 +42,8 @@ pub type LspWorld = CompilerWorld<LspCompilerFeat>;
pub type LspCompileSnapshot = CompileSnapshot<LspCompilerFeat>;
/// LSP compiled artifact.
pub type LspCompiledArtifact = CompiledArtifact<LspCompilerFeat>;
/// LSP compute graph.
pub type LspComputeGraph = Arc<WorldComputeGraph<LspCompilerFeat>>;
/// LSP interrupt.
pub type LspInterrupt = Interrupt<LspCompilerFeat>;
/// Immutable prehashed reference to dictionary.

View file

@ -1,5 +1,6 @@
//! Linked definition analysis
use tinymist_std::typst::TypstDocument;
use typst::foundations::{IntoValue, Label, Selector, Type};
use typst::introspection::Introspector;
use typst::model::BibliographyElem;
@ -7,7 +8,6 @@ use typst::model::BibliographyElem;
use super::{prelude::*, InsTy, SharedContext};
use crate::syntax::{Decl, DeclExpr, Expr, ExprInfo, SyntaxClass, VarClass};
use crate::ty::DocSource;
use crate::VersionedDocument;
/// A linked definition in the source code
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
@ -59,7 +59,7 @@ impl Definition {
pub fn definition(
ctx: &Arc<SharedContext>,
source: &Source,
document: Option<&VersionedDocument>,
document: Option<&TypstDocument>,
syntax: SyntaxClass,
) -> Option<Definition> {
match syntax {
@ -81,7 +81,7 @@ pub fn definition(
_ => return None,
};
let introspector = &document?.document.introspector();
let introspector = &document?.introspector();
bib_definition(ctx, introspector, name)
.or_else(|| ref_definition(introspector, name, ref_expr))
}

View file

@ -11,6 +11,7 @@ use rustc_hash::FxHashMap;
use tinymist_project::LspWorld;
use tinymist_std::debug_loc::DataSource;
use tinymist_std::hash::{hash128, FxDashMap};
use tinymist_std::typst::TypstDocument;
use tinymist_world::vfs::{PathResolution, WorkspaceResolver};
use tinymist_world::{EntryReader, DETACHED_ENTRY};
use typst::diag::{eco_format, At, FileError, FileResult, SourceResult, StrResult};
@ -37,7 +38,6 @@ use crate::syntax::{
use crate::upstream::{tooltip_, Tooltip};
use crate::{
ColorTheme, CompilerQueryRequest, LspPosition, LspRange, LspWorldExt, PositionEncoding,
VersionedDocument,
};
use super::TypeEnv;
@ -195,7 +195,7 @@ pub trait PeriscopeProvider {
fn periscope_at(
&self,
_ctx: &mut LocalContext,
_doc: VersionedDocument,
_doc: &TypstDocument,
_pos: Position,
) -> Option<String> {
None
@ -796,7 +796,7 @@ impl SharedContext {
pub(crate) fn def_of_span(
self: &Arc<Self>,
source: &Source,
doc: Option<&VersionedDocument>,
doc: Option<&TypstDocument>,
span: Span,
) -> Option<Definition> {
let syntax = self.classify_span(source, span)?;
@ -814,7 +814,7 @@ impl SharedContext {
pub(crate) fn def_of_syntax(
self: &Arc<Self>,
source: &Source,
doc: Option<&VersionedDocument>,
doc: Option<&TypstDocument>,
syntax: SyntaxClass,
) -> Option<Definition> {
definition(self, source, doc, syntax)

View file

@ -41,11 +41,7 @@ pub struct CompletionRequest {
impl StatefulRequest for CompletionRequest {
type Response = CompletionList;
fn request(
self,
ctx: &mut LocalContext,
doc: Option<VersionedDocument>,
) -> Option<Self::Response> {
fn request(self, ctx: &mut LocalContext, graph: LspComputeGraph) -> Option<Self::Response> {
// These trigger characters are for completion on positional arguments,
// which follows the configuration item
// `tinymist.completion.triggerOnSnippetPlaceholders`.
@ -55,7 +51,7 @@ impl StatefulRequest for CompletionRequest {
return None;
}
let document = doc.as_ref().map(|doc| &doc.document);
let document = graph.snap.success_doc.as_ref();
let source = ctx.source_by_path(&self.path).ok()?;
let cursor = ctx.to_typst_pos_offset(&source, self.position, 0)?;
@ -134,7 +130,7 @@ mod tests {
let mut includes = HashSet::new();
let mut excludes = HashSet::new();
let doc = compile_doc_for_test(ctx, &properties);
let graph = compile_doc_for_test(ctx, &properties);
for kk in properties.get("contains").iter().flat_map(|v| v.split(',')) {
// split first char
@ -188,7 +184,7 @@ mod tests {
trigger_character,
};
let result = request
.request(ctx, doc.clone())
.request(ctx, graph.clone())
.map(|list| CompletionList {
is_incomplete: list.is_incomplete,
items: get_items(list.items),

View file

@ -98,13 +98,8 @@ pub struct DocumentMetricsRequest {
impl StatefulRequest for DocumentMetricsRequest {
type Response = DocumentMetricsResponse;
fn request(
self,
ctx: &mut LocalContext,
doc: Option<VersionedDocument>,
) -> Option<Self::Response> {
let doc = doc?;
let doc = doc.document;
fn request(self, ctx: &mut LocalContext, graph: LspComputeGraph) -> Option<Self::Response> {
let doc = graph.snap.success_doc.as_ref()?;
let mut worker = DocumentMetricsWorker {
ctx,
@ -113,7 +108,7 @@ impl StatefulRequest for DocumentMetricsRequest {
font_info: Default::default(),
};
worker.work(&doc)?;
worker.work(doc)?;
let font_info = worker.compute()?;
let span_info = SpanInfo {

View file

@ -26,16 +26,13 @@ pub struct GotoDefinitionRequest {
impl StatefulRequest for GotoDefinitionRequest {
type Response = GotoDefinitionResponse;
fn request(
self,
ctx: &mut LocalContext,
doc: Option<VersionedDocument>,
) -> Option<Self::Response> {
fn request(self, ctx: &mut LocalContext, graph: LspComputeGraph) -> Option<Self::Response> {
let doc = graph.snap.success_doc.as_ref();
let source = ctx.source_by_path(&self.path).ok()?;
let syntax = ctx.classify_for_decl(&source, self.position)?;
let origin_selection_range = ctx.to_lsp_range(syntax.node().range(), &source);
let def = ctx.def_of_syntax(&source, doc.as_ref(), syntax)?;
let def = ctx.def_of_syntax(&source, doc, syntax)?;
let (fid, def_range) = def.location(ctx.shared())?;
let uri = ctx.uri_for_id(fid).ok()?;

View file

@ -1,5 +1,6 @@
use core::fmt::{self, Write};
use tinymist_std::typst::TypstDocument;
use typst::foundations::repr::separated_list;
use typst_shim::syntax::LinkedNodeExt;
@ -26,11 +27,8 @@ pub struct HoverRequest {
impl StatefulRequest for HoverRequest {
type Response = Hover;
fn request(
self,
ctx: &mut LocalContext,
doc: Option<VersionedDocument>,
) -> Option<Self::Response> {
fn request(self, ctx: &mut LocalContext, graph: LspComputeGraph) -> Option<Self::Response> {
let doc = graph.snap.success_doc.clone();
let source = ctx.source_by_path(&self.path).ok()?;
let offset = ctx.to_typst_pos(self.position, &source)?;
// the typst's cursor is 1-based, so we need to add 1 to the offset
@ -80,7 +78,7 @@ impl StatefulRequest for HoverRequest {
struct HoverWorker<'a> {
ctx: &'a mut LocalContext,
source: Source,
doc: Option<VersionedDocument>,
doc: Option<TypstDocument>,
cursor: usize,
def: Vec<String>,
value: Vec<String>,
@ -250,19 +248,19 @@ impl HoverWorker<'_> {
// Preview results
let provider = self.ctx.analysis.periscope.clone()?;
let doc = self.doc.as_ref()?;
let position = jump_from_cursor(&doc.document, &self.source, self.cursor);
let position = jump_from_cursor(doc, &self.source, self.cursor);
let position = position.or_else(|| {
for idx in 1..100 {
let next_cursor = self.cursor + idx;
if next_cursor < self.source.text().len() {
let position = jump_from_cursor(&doc.document, &self.source, next_cursor);
let position = jump_from_cursor(doc, &self.source, next_cursor);
if position.is_some() {
return position;
}
}
let prev_cursor = self.cursor.checked_sub(idx);
if let Some(prev_cursor) = prev_cursor {
let position = jump_from_cursor(&doc.document, &self.source, prev_cursor);
let position = jump_from_cursor(doc, &self.source, prev_cursor);
if position.is_some() {
return position;
}
@ -274,7 +272,7 @@ impl HoverWorker<'_> {
log::info!("telescope position: {position:?}");
let preview_content = provider.periscope_at(self.ctx, doc.clone(), position?)?;
let preview_content = provider.periscope_at(self.ctx, doc, position?)?;
self.preview.push(preview_content);
Some(())
}
@ -390,15 +388,16 @@ mod tests {
#[test]
fn test() {
snapshot_testing("hover", &|world, path| {
let source = world.source_by_path(&path).unwrap();
snapshot_testing("hover", &|ctx, path| {
let source = ctx.source_by_path(&path).unwrap();
let request = HoverRequest {
path: path.clone(),
position: find_test_position(&source),
};
let result = request.request(world, None);
let snap = WorldComputeGraph::from_world(ctx.world.clone());
let result = request.request(ctx, snap);
assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC));
});
}

View file

@ -7,97 +7,88 @@
//! code. Currently it provides:
//! + language queries defined by the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/).
mod adt;
pub use analysis::{CompletionFeat, LocalContext, LocalContextGuard, LspWorldExt};
pub use completion::{CompletionRequest, PostfixSnippet};
pub use typlite::ColorTheme;
pub use upstream::with_vm;
pub use code_action::*;
pub use code_context::*;
pub use code_lens::*;
pub use color_presentation::*;
pub use diagnostics::*;
pub use document_color::*;
pub use document_highlight::*;
pub use document_link::*;
pub use document_metrics::*;
pub use document_symbol::*;
pub use folding_range::*;
pub use goto_declaration::*;
pub use goto_definition::*;
pub use hover::*;
pub use inlay_hint::*;
pub use jump::*;
pub use lsp_typst_boundary::*;
pub use on_enter::*;
pub use prepare_rename::*;
pub use references::*;
pub use rename::*;
pub use selection_range::*;
pub use semantic_tokens_delta::*;
pub use semantic_tokens_full::*;
pub use signature_help::*;
pub use symbol::*;
pub use will_rename_files::*;
pub use workspace_label::*;
pub mod analysis;
pub mod docs;
pub mod package;
pub mod syntax;
pub mod testing;
pub mod ty;
mod upstream;
pub use analysis::{CompletionFeat, LocalContext, LocalContextGuard, LspWorldExt};
pub use completion::PostfixSnippet;
pub use upstream::with_vm;
mod diagnostics;
pub use diagnostics::*;
mod code_action;
pub use code_action::*;
mod code_context;
pub use code_context::*;
mod code_lens;
pub use code_lens::*;
mod completion;
pub use completion::CompletionRequest;
mod color_presentation;
pub use color_presentation::*;
mod document_color;
pub use document_color::*;
mod document_highlight;
pub use document_highlight::*;
mod document_symbol;
pub use document_symbol::*;
mod document_link;
pub use document_link::*;
mod workspace_label;
pub use workspace_label::*;
mod document_metrics;
pub use document_metrics::*;
mod folding_range;
pub use folding_range::*;
mod goto_declaration;
pub use goto_declaration::*;
mod goto_definition;
pub use goto_definition::*;
mod hover;
pub use hover::*;
mod inlay_hint;
pub use inlay_hint::*;
mod jump;
pub use jump::*;
mod will_rename_files;
pub use will_rename_files::*;
mod rename;
pub use rename::*;
mod selection_range;
pub use selection_range::*;
mod semantic_tokens_full;
pub use semantic_tokens_full::*;
mod semantic_tokens_delta;
pub use semantic_tokens_delta::*;
mod signature_help;
pub use signature_help::*;
mod symbol;
pub use symbol::*;
mod on_enter;
pub use on_enter::*;
mod prepare_rename;
pub use prepare_rename::*;
mod references;
pub use references::*;
mod lsp_typst_boundary;
pub use lsp_typst_boundary::*;
mod prelude;
use tinymist_std::typst::TypstDocument;
use typst::syntax::Source;
/// The physical position in a document.
pub type FramePosition = typst::layout::Position;
pub use typlite::ColorTheme;
mod adt;
mod lsp_typst_boundary;
mod prelude;
/// A compiled document with an self-incremented logical version.
#[derive(Debug, Clone)]
pub struct VersionedDocument {
/// The version of the document.
pub version: usize,
/// The compiled document.
pub document: TypstDocument,
}
mod code_action;
mod code_context;
mod code_lens;
mod color_presentation;
mod completion;
mod diagnostics;
mod document_color;
mod document_highlight;
mod document_link;
mod document_metrics;
mod document_symbol;
mod folding_range;
mod goto_declaration;
mod goto_definition;
mod hover;
mod inlay_hint;
mod jump;
mod on_enter;
mod prepare_rename;
mod references;
mod rename;
mod selection_range;
mod semantic_tokens_delta;
mod semantic_tokens_full;
mod signature_help;
mod symbol;
mod upstream;
mod will_rename_files;
mod workspace_label;
use typst::syntax::Source;
use tinymist_analysis::log_debug_ct;
use tinymist_project::LspComputeGraph;
/// A request handler with given syntax information.
pub trait SyntaxRequest {
@ -121,22 +112,16 @@ pub trait SemanticRequest {
fn request(self, ctx: &mut LocalContext) -> Option<Self::Response>;
}
/// A request handler with given (semantic) analysis context and a versioned
/// document.
/// A request handler with given (semantic) analysis context and a project
/// snapshot.
pub trait StatefulRequest {
/// The response type of the request.
type Response;
/// Request the information from the given context.
fn request(
self,
ctx: &mut LocalContext,
doc: Option<VersionedDocument>,
) -> Option<Self::Response>;
fn request(self, ctx: &mut LocalContext, graph: LspComputeGraph) -> Option<Self::Response>;
}
use tinymist_analysis::log_debug_ct;
#[allow(missing_docs)]
mod polymorphic {
use completion::CompletionList;

View file

@ -17,6 +17,7 @@ pub use lsp_types::{
SignatureHelp, SignatureInformation, SymbolInformation, TextEdit, Url, WorkspaceEdit,
};
pub use serde_json::Value as JsonValue;
pub use tinymist_project::LspComputeGraph;
pub use tinymist_std::DefId;
pub use typst::diag::{EcoString, Tracepoint};
pub use typst::foundations::Value;
@ -35,4 +36,4 @@ pub use crate::lsp_typst_boundary::{
};
pub use crate::syntax::{classify_syntax, Decl, DefKind};
pub(crate) use crate::ty::PathPreference;
pub use crate::{SemanticRequest, StatefulRequest, VersionedDocument};
pub use crate::{SemanticRequest, StatefulRequest};

View file

@ -34,11 +34,8 @@ pub struct PrepareRenameRequest {
impl StatefulRequest for PrepareRenameRequest {
type Response = PrepareRenameResponse;
fn request(
self,
ctx: &mut LocalContext,
doc: Option<VersionedDocument>,
) -> Option<Self::Response> {
fn request(self, ctx: &mut LocalContext, graph: LspComputeGraph) -> Option<Self::Response> {
let doc = graph.snap.success_doc.as_ref();
let source = ctx.source_by_path(&self.path).ok()?;
let syntax = ctx.classify_for_decl(&source, self.position)?;
if matches!(syntax.node().kind(), SyntaxKind::FieldAccess) {
@ -48,7 +45,7 @@ impl StatefulRequest for PrepareRenameRequest {
}
let origin_selection_range = ctx.to_lsp_range(syntax.node().range(), &source);
let def = ctx.def_of_syntax(&source, doc.as_ref(), syntax.clone())?;
let def = ctx.def_of_syntax(&source, doc, syntax.clone())?;
let (name, range) = prepare_renaming(ctx, &syntax, &def)?;
@ -136,15 +133,16 @@ mod tests {
#[test]
fn prepare() {
snapshot_testing("rename", &|world, path| {
let source = world.source_by_path(&path).unwrap();
snapshot_testing("rename", &|ctx, path| {
let source = ctx.source_by_path(&path).unwrap();
let request = PrepareRenameRequest {
path: path.clone(),
position: find_test_position(&source),
};
let snap = WorldComputeGraph::from_world(ctx.world.clone());
let result = request.request(world, None);
let result = request.request(ctx, snap);
assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC));
});
}

View file

@ -1,5 +1,6 @@
use std::sync::OnceLock;
use tinymist_std::typst::TypstDocument;
use typst::syntax::Span;
use crate::{
@ -25,15 +26,12 @@ pub struct ReferencesRequest {
impl StatefulRequest for ReferencesRequest {
type Response = Vec<LspLocation>;
fn request(
self,
ctx: &mut LocalContext,
doc: Option<VersionedDocument>,
) -> Option<Self::Response> {
fn request(self, ctx: &mut LocalContext, graph: LspComputeGraph) -> Option<Self::Response> {
let doc = graph.snap.success_doc.as_ref();
let source = ctx.source_by_path(&self.path).ok()?;
let syntax = ctx.classify_for_decl(&source, self.position)?;
let locations = find_references(ctx, &source, doc.as_ref(), syntax)?;
let locations = find_references(ctx, &source, doc, syntax)?;
crate::log_debug_ct!("references: {locations:?}");
Some(locations)
@ -43,7 +41,7 @@ impl StatefulRequest for ReferencesRequest {
pub(crate) fn find_references(
ctx: &mut LocalContext,
source: &Source,
doc: Option<&VersionedDocument>,
doc: Option<&TypstDocument>,
syntax: SyntaxClass<'_>,
) -> Option<Vec<LspLocation>> {
let finding_label = match syntax {

View file

@ -36,15 +36,13 @@ pub struct RenameRequest {
impl StatefulRequest for RenameRequest {
type Response = WorkspaceEdit;
fn request(
self,
ctx: &mut LocalContext,
doc: Option<VersionedDocument>,
) -> Option<Self::Response> {
fn request(self, ctx: &mut LocalContext, graph: LspComputeGraph) -> Option<Self::Response> {
let doc = graph.snap.success_doc.as_ref();
let source = ctx.source_by_path(&self.path).ok()?;
let syntax = ctx.classify_for_decl(&source, self.position)?;
let def = ctx.def_of_syntax(&source, doc.as_ref(), syntax.clone())?;
let def = ctx.def_of_syntax(&source, doc, syntax.clone())?;
prepare_renaming(ctx, &syntax, &def)?;
@ -95,7 +93,7 @@ impl StatefulRequest for RenameRequest {
})
}
_ => {
let references = find_references(ctx, &source, doc.as_ref(), syntax)?;
let references = find_references(ctx, &source, doc, syntax)?;
let mut edits = HashMap::new();
@ -321,16 +319,17 @@ mod tests {
#[test]
fn test() {
snapshot_testing("rename", &|world, path| {
let source = world.source_by_path(&path).unwrap();
snapshot_testing("rename", &|ctx, path| {
let source = ctx.source_by_path(&path).unwrap();
let request = RenameRequest {
path: path.clone(),
position: find_test_position(&source),
new_name: "new_name".to_string(),
};
let snap = WorldComputeGraph::from_world(ctx.world.clone());
let mut result = request.request(world, None);
let mut result = request.request(ctx, snap);
// sort the edits to make the snapshot stable
if let Some(r) = result.as_mut().and_then(|r| r.changes.as_mut()) {
for edits in r.values_mut() {

View file

@ -10,14 +10,12 @@ use std::{
use once_cell::sync::Lazy;
use serde_json::{ser::PrettyFormatter, Serializer, Value};
use tinymist_project::{CompileFontArgs, ExportTarget};
use tinymist_project::{CompileFontArgs, ExportTarget, LspCompileSnapshot, LspComputeGraph};
use tinymist_std::debug_loc::LspRange;
use tinymist_std::typst::TypstDocument;
use tinymist_world::package::PackageSpec;
use tinymist_world::vfs::WorkspaceResolver;
use tinymist_world::EntryState;
use tinymist_world::TaskInputs;
use tinymist_world::{EntryManager, EntryReader, ShadowApi};
use tinymist_world::{EntryManager, EntryReader, EntryState, ShadowApi, TaskInputs};
use typst::foundations::Bytes;
use typst::syntax::ast::{self, AstNode};
use typst::syntax::{LinkedNode, Source, SyntaxKind, VirtualPath};
@ -26,12 +24,11 @@ pub use insta::assert_snapshot;
pub use serde::Serialize;
pub use serde_json::json;
pub use tinymist_project::{LspUniverse, LspUniverseBuilder};
pub use tinymist_world::WorldComputeGraph;
use typst_shim::syntax::LinkedNodeExt;
use crate::syntax::find_module_level_docs;
use crate::{
analysis::Analysis, prelude::LocalContext, LspPosition, PositionEncoding, VersionedDocument,
};
use crate::{analysis::Analysis, prelude::LocalContext, LspPosition, PositionEncoding};
use crate::{to_lsp_position, CompletionFeat, LspWorldExt};
pub fn snapshot_testing(name: &str, f: &impl Fn(&mut LocalContext, PathBuf)) {
@ -122,13 +119,13 @@ pub fn get_test_properties(s: &str) -> HashMap<&'_ str, &'_ str> {
pub fn compile_doc_for_test(
ctx: &mut LocalContext,
properties: &HashMap<&str, &str>,
) -> Option<VersionedDocument> {
) -> LspComputeGraph {
let prev = ctx.world.entry_state();
let next = match properties.get("compile")?.trim() {
"true" => prev.clone(),
"false" => return None,
path if path.ends_with(".typ") => prev.select_in_workspace(Path::new(path)),
v => panic!("invalid value for 'compile' property: {v}"),
let next = match properties.get("compile").map(|s| s.trim()) {
Some("true") => prev.clone(),
None | Some("false") => return WorldComputeGraph::from_world(ctx.world.clone()),
Some(path) if path.ends_with(".typ") => prev.select_in_workspace(Path::new(path)),
v => panic!("invalid value for 'compile' property: {v:?}"),
};
let mut world = Cow::Borrowed(&ctx.world);
@ -138,14 +135,12 @@ pub fn compile_doc_for_test(
..Default::default()
}));
}
let mut world = world.into_owned();
world.set_is_compiling(true);
let mut snap = LspCompileSnapshot::from_world(world.into_owned());
snap.world.set_is_compiling(true);
let doc = typst::compile(&world).output.unwrap();
Some(VersionedDocument {
version: 0,
document: TypstDocument::Paged(Arc::new(doc)),
})
let doc = typst::compile(&snap.world).output.unwrap();
snap.success_doc = Some(TypstDocument::Paged(Arc::new(doc)));
WorldComputeGraph::new(snap)
}
pub fn run_with_sources<T>(source: &str, f: impl FnOnce(&mut LspUniverse, PathBuf) -> T) -> T {

View file

@ -15,11 +15,7 @@ pub struct WillRenameFilesRequest {
impl StatefulRequest for WillRenameFilesRequest {
type Response = WorkspaceEdit;
fn request(
self,
ctx: &mut LocalContext,
_doc: Option<VersionedDocument>,
) -> Option<Self::Response> {
fn request(self, ctx: &mut LocalContext, _graph: LspComputeGraph) -> Option<Self::Response> {
let mut edits: HashMap<Url, Vec<TextEdit>> = HashMap::new();
self.paths

View file

@ -9,7 +9,7 @@ use core::fmt;
use base64::Engine;
use reflexo_vec2svg::{ExportFeature, SvgExporter, SvgText};
use tinymist_query::{FramePosition, LocalContext, VersionedDocument};
use tinymist_query::{FramePosition, LocalContext};
use tinymist_std::typst::TypstDocument;
struct PeriscopeExportFeature {}
@ -74,7 +74,7 @@ impl PeriscopeRenderer {
pub fn render_marked(
&self,
ctx: &mut LocalContext,
doc: VersionedDocument,
doc: &TypstDocument,
pos: FramePosition,
) -> Option<String> {
let (svg_payload, w, h) = self.render(ctx, doc, pos)?;
@ -95,10 +95,10 @@ impl PeriscopeRenderer {
pub fn render(
&self,
_ctx: &mut LocalContext,
doc: VersionedDocument,
doc: &TypstDocument,
pos: FramePosition,
) -> Option<(String, f32, f32)> {
match &doc.document {
match doc {
TypstDocument::Paged(paged_doc) => {
// todo: svg viewer compatibility
type UsingExporter = SvgExporter<PeriscopeExportFeature>;

View file

@ -12,7 +12,7 @@ use typst::ecow::EcoVec;
use typst::syntax::Span;
use crate::snapshot::CompileSnapshot;
use crate::{CompilerFeat, CompilerWorld, EntryReader};
use crate::{CompilerFeat, CompilerWorld, EntryReader, TaskInputs};
type AnyArc = Arc<dyn std::any::Any + Send + Sync>;
@ -89,14 +89,33 @@ impl<F: CompilerFeat> WorldComputeGraph<F> {
})
}
/// Creates a graph from the world.
pub fn from_world(world: CompilerWorld<F>) -> Arc<Self> {
Self::new(CompileSnapshot::from_world(world))
}
/// Clones the graph with the same snapshot.
pub fn snapshot(&self) -> Arc<Self> {
self.snapshot_unsafe(self.snap.clone())
}
/// Clones the graph with the same snapshot. Take care of the consistency by
/// your self.
pub fn snapshot_unsafe(&self, snap: CompileSnapshot<F>) -> Arc<Self> {
Arc::new(Self {
snap: self.snap.clone(),
snap,
entries: Mutex::new(self.entries.lock().clone()),
})
}
/// Forks a new snapshot that compiles a different document.
// todo: share cache if task doesn't change.
pub fn task(&self, inputs: TaskInputs) -> Arc<Self> {
let mut snap = self.snap.clone();
snap = snap.task(inputs);
Self::new(snap)
}
/// Gets a world computed.
pub fn must_get<T: WorldComputable<F>>(&self) -> Result<Arc<T::Output>> {
let res = self.get::<T>().transpose()?;
@ -148,6 +167,18 @@ impl<F: CompilerFeat> WorldComputeGraph<F> {
entry
}
}
pub fn world(&self) -> &CompilerWorld<F> {
&self.snap.world
}
pub fn registry(&self) -> &Arc<F::Registry> {
&self.snap.world.registry
}
pub fn library(&self) -> &typst::Library {
&self.snap.world.library
}
}
pub trait ExportDetection<F: CompilerFeat, D> {
@ -219,7 +250,21 @@ pub type HtmlCompilationTask = CompilationTask<TypstHtmlDocument>;
pub struct CompilationTask<D>(std::marker::PhantomData<D>);
impl<D: typst::Document + Send + Sync + 'static> CompilationTask<D> {
pub fn ensure_main<F: CompilerFeat>(world: &CompilerWorld<F>) -> SourceResult<()> {
let main_id = world.main_id();
let checked = main_id.ok_or_else(|| typst::diag::eco_format!("entry file is not set"));
checked.at(Span::detached()).map(|_| ())
}
pub fn execute<F: CompilerFeat>(world: &CompilerWorld<F>) -> Warned<SourceResult<Arc<D>>> {
let res = Self::ensure_main(world);
if let Err(err) = res {
return Warned {
output: Err(err),
warnings: EcoVec::new(),
};
}
let is_paged_compilation = TypeId::of::<D>() == TypeId::of::<TypstPagedDocument>();
let is_html_compilation = TypeId::of::<D>() == TypeId::of::<TypstHtmlDocument>();
@ -233,7 +278,7 @@ impl<D: typst::Document + Send + Sync + 'static> CompilationTask<D> {
};
world.to_mut().set_is_compiling(true);
let compiled = typst::compile::<D>(world.as_ref());
let compiled = ::typst::compile::<D>(world.as_ref());
world.to_mut().set_is_compiling(false);
let exclude_html_warnings = if !is_html_compilation {
@ -346,83 +391,16 @@ impl DiagnosticsTask {
}
}
// pub type ErasedVecExportTask<E> = ErasedExportTask<SourceResult<Bytes>, E>;
// pub type ErasedStrExportTask<E> = ErasedExportTask<SourceResult<String>, E>;
// pub struct ErasedExportTask<T, E> {
// _phantom: std::marker::PhantomData<(T, E)>,
// }
// #[allow(clippy::type_complexity)]
// struct ErasedExportImpl<F: CompilerFeat, T, E> {
// f: Arc<dyn Fn(&Arc<WorldComputeGraph<F>>) -> Result<Option<T>> + Send +
// Sync>, _phantom: std::marker::PhantomData<E>,
// }
// impl<T: Send + Sync + 'static, E: Send + Sync + 'static> ErasedExportTask<T,
// E> { #[must_use = "the result must be checked"]
// pub fn provide_raw<F: CompilerFeat>(
// graph: &Arc<WorldComputeGraph<F>>,
// f: impl Fn(&Arc<WorldComputeGraph<F>>) -> Result<Option<T>> + Send +
// Sync + 'static, ) -> Result<()> {
// let provided = graph.provide::<ConfigTask<ErasedExportImpl<F, T,
// E>>>(Ok(Arc::new({ ErasedExportImpl {
// f: Arc::new(f),
// _phantom: std::marker::PhantomData,
// }
// })));
// if provided.is_err() {
// tinymist_std::bail!("already provided")
// }
// Ok(())
// }
// #[must_use = "the result must be checked"]
// pub fn provide<F: CompilerFeat, D, C>(graph: &Arc<WorldComputeGraph<F>>)
// -> Result<()> where
// D: typst::Document + Send + Sync + 'static,
// C: WorldComputable<F> + ExportComputation<F, D, Output = T>,
// {
// Self::provide_raw(graph, OptionDocumentTask::run_export::<F, C>)
// }
// }
// impl<F: CompilerFeat, T: Send + Sync + 'static, E: Send + Sync + 'static>
// WorldComputable<F> for ErasedExportTask<T, E>
// {
// type Output = Option<T>;
// fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self::Output> {
// let conf = graph.must_get::<ConfigTask<ErasedExportImpl<F, T,
// E>>>()?; (conf.f)(graph)
// }
// }
impl<F: CompilerFeat> WorldComputeGraph<F> {
pub fn ensure_main(&self) -> SourceResult<()> {
let main_id = self.snap.world.main_id();
let checked = main_id.ok_or_else(|| typst::diag::eco_format!("entry file is not set"));
checked.at(Span::detached()).map(|_| ())
CompilationTask::<TypstPagedDocument>::ensure_main(&self.snap.world)
}
/// Compile once from scratch.
pub fn pure_compile<D: ::typst::Document>(&self) -> Warned<SourceResult<Arc<D>>> {
let res = self.ensure_main();
if let Err(err) = res {
return Warned {
output: Err(err),
warnings: EcoVec::new(),
};
}
let res = ::typst::compile::<D>(&self.snap.world);
// compile document
Warned {
output: res.output.map(Arc::new),
warnings: res.warnings,
}
pub fn pure_compile<D: ::typst::Document + Send + Sync + 'static>(
&self,
) -> Warned<SourceResult<Arc<D>>> {
CompilationTask::<D>::execute(&self.snap.world)
}
/// Compile once from scratch.
@ -431,12 +409,24 @@ impl<F: CompilerFeat> WorldComputeGraph<F> {
}
/// Compile to html once from scratch.
pub fn compile_html(&self) -> Warned<SourceResult<Arc<::typst::html::HtmlDocument>>> {
pub fn compile_html(&self) -> Warned<SourceResult<Arc<TypstHtmlDocument>>> {
self.pure_compile()
}
// With **the compilation state**, query the matches for the selector.
// fn query(&mut self, selector: String, document: &TypstDocument) ->
// SourceResult<Vec<Content>> { self.pure_query(world, selector,
// document) }
/// Compile paged document with cache
pub fn shared_compile(self: &Arc<Self>) -> Result<Option<Arc<TypstPagedDocument>>> {
let doc = self.compute::<OptionDocumentTask<TypstPagedDocument>>()?;
Ok(doc.as_ref().clone())
}
/// Compile HTML document with cache
pub fn shared_compile_html(self: &Arc<Self>) -> Result<Option<Arc<TypstHtmlDocument>>> {
let doc = self.compute::<OptionDocumentTask<TypstHtmlDocument>>()?;
Ok(doc.as_ref().clone())
}
/// Gets the diagnostics from shared compilation.
pub fn shared_diagnostics(self: &Arc<Self>) -> Result<Arc<DiagnosticsTask>> {
self.compute::<DiagnosticsTask>()
}
}

View file

@ -369,7 +369,7 @@ impl ServerState {
// Try to parse without version, but prefer the error message of the
// normal package spec parsing if it fails.
let spec: VersionlessPackageSpec = from_source.parse().map_err(|_| err)?;
let version = snap.world.registry.determine_latest_version(&spec)?;
let version = snap.registry().determine_latest_version(&spec)?;
StrResult::Ok(spec.at(version))
})
.map_err(map_string_err("failed to parse package spec"))
@ -378,7 +378,7 @@ impl ServerState {
let from_source = TemplateSource::Package(spec);
let entry_path = package::init(
&snap.world,
snap.world(),
InitTask {
tmpl: from_source.clone(),
dir: to_path.clone(),
@ -412,7 +412,7 @@ impl ServerState {
// Try to parse without version, but prefer the error message of the
// normal package spec parsing if it fails.
let spec: VersionlessPackageSpec = from_source.parse().map_err(|_| err)?;
let version = snap.world.registry.determine_latest_version(&spec)?;
let version = snap.registry().determine_latest_version(&spec)?;
StrResult::Ok(spec.at(version))
})
.map_err(map_string_err("failed to parse package spec"))
@ -420,7 +420,7 @@ impl ServerState {
let from_source = TemplateSource::Package(spec);
let entry = package::get_entry(&snap.world, from_source)
let entry = package::get_entry(snap.world(), from_source)
.map_err(map_string_err("failed to get template entry"))
.map_err(internal_error)?;
@ -490,8 +490,8 @@ impl ServerState {
compiler_program: self_path,
root: root.as_ref().to_owned(),
main,
inputs: snap.world.inputs().as_ref().deref().clone(),
font_paths: snap.world.font_resolver.font_paths().to_owned(),
inputs: snap.world().inputs().as_ref().deref().clone(),
font_paths: snap.world().font_resolver.font_paths().to_owned(),
rpc_kind: "http".into(),
})?;
@ -558,7 +558,7 @@ impl ServerState {
pub fn resource_package_dirs(&mut self, _arguments: Vec<JsonValue>) -> AnySchedulableResponse {
let snap = self.snapshot().map_err(internal_error)?;
just_future(async move {
let paths = snap.world.registry.paths();
let paths = snap.registry().paths();
let paths = paths.iter().map(|p| p.as_ref()).collect::<Vec<_>>();
serde_json::to_value(paths).map_err(|e| internal_error(e.to_string()))
})
@ -571,7 +571,7 @@ impl ServerState {
) -> AnySchedulableResponse {
let snap = self.snapshot().map_err(internal_error)?;
just_future(async move {
let paths = snap.world.registry.local_path();
let paths = snap.registry().local_path();
let paths = paths.as_deref().into_iter().collect::<Vec<_>>();
serde_json::to_value(paths).map_err(|e| internal_error(e.to_string()))
})
@ -586,11 +586,10 @@ impl ServerState {
let snap = self.snapshot().map_err(internal_error)?;
just_future(async move {
let packages =
tinymist_query::package::list_package_by_namespace(&snap.world.registry, ns)
.into_iter()
.map(PackageInfo::from)
.collect::<Vec<_>>();
let packages = tinymist_query::package::list_package_by_namespace(snap.registry(), ns)
.into_iter()
.map(PackageInfo::from)
.collect::<Vec<_>>();
serde_json::to_value(packages).map_err(|e| internal_error(e.to_string()))
})
@ -662,7 +661,7 @@ impl ServerState {
let snap = self.query_snapshot().map_err(internal_error)?;
Ok(async move {
let world = &snap.world;
let world = snap.world();
let entry: StrResult<EntryState> = Ok(()).and_then(|_| {
let toml_id = tinymist_query::package::get_manifest_id(&info)?;

View file

@ -108,7 +108,7 @@ impl ServerState {
inputs: input.inputs,
};
let snapshot = self.project.snapshot().unwrap().task(input);
let snapshot = self.project.snapshot().unwrap().snap.clone().task(input);
let world = &snapshot.world;
let main = world

View file

@ -356,8 +356,8 @@ impl ServerState {
if matches!(query, Completion(..)) {
// Prefetch the package index for completion.
if snap.world.registry.cached_index().is_none() {
let registry = snap.world.registry.clone();
if snap.registry().cached_index().is_none() {
let registry = snap.registry().clone();
tokio::spawn(async move {
let _ = registry.download_index();
});

View file

@ -246,7 +246,7 @@ pub fn trace_lsp_main(args: TraceLspArgs) -> Result<()> {
let snap = state.snapshot().unwrap();
RUNTIMES.tokio_runtime.block_on(async {
let w = snap.world.task(TaskInputs {
let w = snap.world().clone().task(TaskInputs {
entry: Some(entry),
inputs,
});
@ -296,9 +296,8 @@ pub fn query_main(cmds: QueryCommands) -> Result<()> {
QueryCommands::PackageDocs(args) => {
let pkg = PackageSpec::from_str(&args.id).unwrap();
let path = args.path.map(PathBuf::from);
let path = path.unwrap_or_else(|| {
snap.world.registry.resolve(&pkg).unwrap().as_ref().into()
});
let path = path
.unwrap_or_else(|| snap.registry().resolve(&pkg).unwrap().as_ref().into());
let res = state
.resource_package_docs_(PackageInfo {
@ -315,9 +314,8 @@ pub fn query_main(cmds: QueryCommands) -> Result<()> {
QueryCommands::CheckPackage(args) => {
let pkg = PackageSpec::from_str(&args.id).unwrap();
let path = args.path.map(PathBuf::from);
let path = path.unwrap_or_else(|| {
snap.world.registry.resolve(&pkg).unwrap().as_ref().into()
});
let path = path
.unwrap_or_else(|| snap.registry().resolve(&pkg).unwrap().as_ref().into());
state
.check_package(PackageInfo {

View file

@ -19,7 +19,7 @@
#![allow(missing_docs)]
use reflexo_typst::diag::print_diagnostics;
use reflexo_typst::{diag::print_diagnostics, TypstDocument};
pub use tinymist_project::*;
use std::{num::NonZeroUsize, sync::Arc};
@ -31,7 +31,7 @@ use tinymist_project::vfs::{FileChangeSet, MemoryEvent};
use tinymist_query::{
analysis::{Analysis, AnalysisRevLock, LocalContextGuard, PeriscopeProvider},
CompilerQueryRequest, CompilerQueryResponse, DiagnosticsMap, LocalContext, SemanticRequest,
StatefulRequest, VersionedDocument,
StatefulRequest,
};
use tinymist_render::PeriscopeRenderer;
use tinymist_std::{error::prelude::*, ImmutPath};
@ -57,7 +57,7 @@ impl ServerState {
}
/// Snapshots the project for tasks
pub fn snapshot(&mut self) -> Result<LspCompileSnapshot> {
pub fn snapshot(&mut self) -> Result<LspComputeGraph> {
self.project.snapshot()
}
@ -240,7 +240,7 @@ impl ProjectInsStateExt {
handler: &dyn CompileHandler<LspCompilerFeat, ProjectInsStateExt>,
compilation: &LspCompiledArtifact,
) {
let rev = compilation.world.revision().get();
let rev = compilation.world().revision().get();
if self.notified_revision >= rev {
return;
}
@ -262,7 +262,7 @@ impl ProjectInsStateExt {
return false;
};
let last_rev = last_compilation.world.revision();
let last_rev = last_compilation.world().revision();
if last_rev != *revision {
return false;
}
@ -272,8 +272,7 @@ impl ProjectInsStateExt {
return false;
}
self.emitted_reasons.see(self.pending_reasons);
let mut last_compilation = last_compilation.clone();
last_compilation.snap.signal = pending_reasons.into();
let last_compilation = last_compilation.clone().with_signal(pending_reasons.into());
handler.notify_compile(&last_compilation);
self.pending_reasons = CompileReasons::default();
@ -297,7 +296,7 @@ impl ProjectState {
}
/// Snapshot the compiler thread for tasks
pub fn snapshot(&mut self) -> Result<LspCompileSnapshot> {
pub fn snapshot(&mut self) -> Result<LspComputeGraph> {
Ok(self.compiler.snapshot())
}
@ -316,7 +315,7 @@ impl ProjectState {
pub fn do_interrupt(compiler: &mut LspProjectCompiler, intr: Interrupt<LspCompilerFeat>) {
if let Interrupt::Compiled(compiled) = &intr {
let proj = compiler.projects().find(|p| p.id == compiled.id);
let proj = compiler.projects().find(|p| &p.id == compiled.id());
if let Some(proj) = proj {
proj.ext
.compiled(&proj.verse.revision, proj.handler.as_ref(), compiled);
@ -351,7 +350,7 @@ impl PeriscopeProvider for TypstPeriscopeProvider {
fn periscope_at(
&self,
ctx: &mut LocalContext,
doc: VersionedDocument,
doc: &TypstDocument,
pos: TypstPosition,
) -> Option<String> {
self.0.render_marked(ctx, doc, pos)
@ -440,9 +439,9 @@ impl CompileHandlerImpl {
}
fn notify_diagnostics(&self, snap: &LspCompiledArtifact) {
let world = &snap.world;
let world = snap.world();
let dv = ProjVersion {
id: snap.id.clone(),
id: snap.id().clone(),
revision: world.revision().get(),
};
@ -450,11 +449,9 @@ impl CompileHandlerImpl {
// todo: check all errors in this file
let valid = !world.entry_state().is_inactive();
let diagnostics = valid.then(|| {
let errors = snap.doc.as_ref().err().into_iter().flatten();
let warnings = snap.warnings.as_ref();
let diagnostics = tinymist_query::convert_diagnostics(
world,
errors.chain(warnings),
snap.diagnostics(),
self.analysis.position_encoding,
);
@ -491,7 +488,7 @@ impl CompileHandler<LspCompilerFeat, ProjectInsStateExt> for CompileHandlerImpl
break 'vfs_is_clean false;
};
let last_rev = compilation.world.vfs().revision();
let last_rev = compilation.world().vfs().revision();
let deps = compilation.depended_files().clone();
s.verse.vfs().is_clean_compile(last_rev.get(), &deps)
}
@ -601,29 +598,23 @@ impl CompileHandler<LspCompilerFeat, ProjectInsStateExt> for CompileHandlerImpl
fn notify_compile(&self, snap: &LspCompiledArtifact) {
{
let mut n_revs = self.notified_revision.lock();
let n_rev = n_revs.entry(snap.id.clone()).or_default();
if *n_rev >= snap.world.revision().get() {
let n_rev = n_revs.entry(snap.id().clone()).or_default();
if *n_rev >= snap.world().revision().get() {
log::info!(
"Project: already notified for revision {} <= {n_rev}",
snap.world.revision(),
snap.world().revision(),
);
return;
}
*n_rev = snap.world.revision().get();
*n_rev = snap.world().revision().get();
}
// Prints the diagnostics when we are running the compilation in standalone
// CLI.
if self.is_standalone {
print_diagnostics(
&snap.world,
snap.doc
.as_ref()
.err()
.cloned()
.iter()
.flatten()
.chain(snap.warnings.iter()),
snap.world(),
snap.diagnostics(),
reflexo_typst::DiagnosticFormat::Human,
)
.log_error("failed to print diagnostics");
@ -635,11 +626,11 @@ impl CompileHandler<LspCompilerFeat, ProjectInsStateExt> for CompileHandlerImpl
self.export.signal(snap);
#[cfg(feature = "preview")]
if let Some(inner) = self.preview.get(&snap.id) {
if let Some(inner) = self.preview.get(snap.id()) {
let snap = snap.clone();
inner.notify_compile(Arc::new(crate::tool::preview::PreviewCompileView { snap }));
} else {
log::info!("Project: no preview for {:?}", snap.id);
log::info!("Project: no preview for {:?}", snap.id());
}
}
}
@ -647,13 +638,13 @@ impl CompileHandler<LspCompilerFeat, ProjectInsStateExt> for CompileHandlerImpl
pub type QuerySnapWithStat = (LspQuerySnapshot, QueryStatGuard);
pub struct LspQuerySnapshot {
pub snap: LspCompileSnapshot,
pub snap: LspComputeGraph,
analysis: Arc<Analysis>,
rev_lock: AnalysisRevLock,
}
impl std::ops::Deref for LspQuerySnapshot {
type Target = LspCompileSnapshot;
type Target = LspComputeGraph;
fn deref(&self) -> &Self::Target {
&self.snap
@ -671,11 +662,8 @@ impl LspQuerySnapshot {
query: T,
wrapper: fn(Option<T::Response>) -> CompilerQueryResponse,
) -> Result<CompilerQueryResponse> {
let doc = self.snap.success_doc.as_ref().map(|doc| VersionedDocument {
version: self.world.revision().get(),
document: doc.clone(),
});
self.run_analysis(|ctx| query.request(ctx, doc))
let graph = self.snap.clone();
self.run_analysis(|ctx| query.request(ctx, graph))
.map(wrapper)
}
@ -688,7 +676,7 @@ impl LspQuerySnapshot {
}
pub fn run_analysis<T>(self, f: impl FnOnce(&mut LocalContextGuard) -> T) -> Result<T> {
let world = self.snap.world;
let world = self.snap.world().clone();
let Some(..) = world.main_id() else {
log::error!("Project: main file is not set");
bail!("main file is not set");

View file

@ -5,7 +5,7 @@ use tinymist_std::debug_loc::DataSource;
use typst::text::{FontStretch, FontStyle, FontWeight};
use super::prelude::*;
use crate::project::LspCompileSnapshot;
use crate::project::LspComputeGraph;
use crate::world::font::FontResolver;
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -43,9 +43,9 @@ struct FontResourceResult {
impl ServerState {
/// Get the all valid fonts
pub async fn get_font_resources(snap: LspCompileSnapshot) -> LspResult<JsonValue> {
pub async fn get_font_resources(snap: LspComputeGraph) -> LspResult<JsonValue> {
// fonts
let resolver = &snap.world.font_resolver;
let resolver = &snap.world().font_resolver;
let font_book = resolver.font_book();
let mut source_map: HashMap<Arc<DataSource>, u32> = HashMap::new();
let mut sources: Vec<DataSource> = Vec::new();

View file

@ -8,7 +8,7 @@ use typst::foundations::Bytes;
use typst::{syntax::VirtualPath, World};
use super::prelude::*;
use crate::project::LspCompileSnapshot;
use crate::project::LspComputeGraph;
use crate::world::{base::ShadowApi, EntryState, TaskInputs};
#[derive(Debug, Serialize, Deserialize)]
@ -948,12 +948,11 @@ static CAT_MAP: Lazy<HashMap<&str, SymCategory>> = Lazy::new(|| {
impl ServerState {
/// Get the all valid symbols
pub async fn get_symbol_resources(snap: LspCompileSnapshot) -> LspResult<JsonValue> {
pub async fn get_symbol_resources(snap: LspComputeGraph) -> LspResult<JsonValue> {
let mut symbols = ResourceSymbolMap::new();
let std = snap
.world
.library
.library()
.std
.read()
.scope()
@ -993,7 +992,7 @@ impl ServerState {
let new_entry = EntryState::new_rootless(VirtualPath::new(&entry_path));
let mut forked = snap.world.task(TaskInputs {
let mut forked = snap.world().task(TaskInputs {
entry: Some(new_entry),
..TaskInputs::default()
});

View file

@ -15,7 +15,7 @@ use typst::syntax::Source;
use crate::actor::editor::{EditorActor, EditorRequest};
use crate::lsp::query::OnEnter;
use crate::project::{
update_lock, CompiledArtifact, EntryResolver, LspCompileSnapshot, LspInterrupt, ProjectInsId,
update_lock, CompiledArtifact, EntryResolver, LspComputeGraph, LspInterrupt, ProjectInsId,
ProjectState, PROJECT_ROUTE_USER_ACTION_PRIORITY,
};
use crate::route::ProjectRouteState;
@ -396,7 +396,7 @@ impl ServerState {
let snap = self.snapshot()?;
just_future(async move {
let w = &snap.world;
let w = snap.world();
let info = ServerInfoResponse {
root: w.entry_state().root().map(|e| e.as_ref().to_owned()),
@ -421,12 +421,12 @@ impl ServerState {
let lock_dir = self.entry_resolver().resolve_lock(&entry);
let update_dep = lock_dir.clone().map(|lock_dir| {
|snap: LspCompileSnapshot| async move {
|snap: LspComputeGraph| async move {
let mut updater = update_lock(lock_dir);
let world = snap.world.clone();
let doc_id = updater.compiled(&world)?;
let world = snap.world();
let doc_id = updater.compiled(world)?;
updater.update_materials(doc_id.clone(), snap.world.depended_fs_paths());
updater.update_materials(doc_id.clone(), world.depended_fs_paths());
updater.route(doc_id, PROJECT_ROUTE_USER_ACTION_PRIORITY);
updater.commit();
@ -442,7 +442,7 @@ impl ServerState {
..TaskInputs::default()
});
let artifact = CompiledArtifact::from_snapshot(snap.clone());
let artifact = CompiledArtifact::from_graph(snap.clone());
let res = ExportTask::do_export(task, artifact, lock_dir).await?;
if let Some(update_dep) = update_dep {
tokio::spawn(update_dep(snap));

View file

@ -66,8 +66,8 @@ impl ExportTask {
artifact: &LspCompiledArtifact,
config: &Arc<ExportUserConfig>,
) -> Option<()> {
let doc = artifact.doc.as_ref().ok()?;
let s = artifact.signal;
let doc = artifact.doc.as_ref()?;
let s = artifact.snap.signal;
let when = config.task.when().unwrap_or_default();
let need_export = (!matches!(when, TaskWhen::Never) && s.by_entry_update)
@ -82,7 +82,7 @@ impl ExportTask {
return None;
}
let rev = artifact.world.revision().get();
let rev = artifact.world().revision().get();
let fut = self.export_folder.spawn(rev, || {
let task = config.task.clone();
let artifact = artifact.clone();
@ -107,12 +107,12 @@ impl ExportTask {
}
let editor_tx = self.editor_tx.clone()?;
let rev = artifact.world.revision().get();
let rev = artifact.world().revision().get();
let fut = self.count_word_folder.spawn(rev, || {
let artifact = artifact.clone();
Box::pin(async move {
let id = artifact.snap.id;
let doc = artifact.doc.ok()?;
let id = artifact.id().clone();
let doc = artifact.doc?;
let wc =
log_err(FutureFolder::compute(move |_| word_count::word_count(&doc)).await);
log::debug!("WordCount({id:?}:{rev}): {wc:?}");
@ -138,10 +138,10 @@ impl ExportTask {
use reflexo_vec2svg::DefaultExportFeature;
use ProjectTask::*;
let CompiledArtifact { snap, doc, .. } = artifact;
let CompiledArtifact { graph, doc, .. } = artifact;
// Prepare the output path.
let entry = snap.world.entry_state();
let entry = graph.snap.world.entry_state();
let config = task.as_export().unwrap();
let output = config.output.clone().unwrap_or_default();
let Some(to) = output.substitute(&entry) else {
@ -168,7 +168,7 @@ impl ExportTask {
let _: Option<()> = lock_dir.and_then(|lock_dir| {
let mut updater = crate::project::update_lock(lock_dir);
let doc_id = updater.compiled(&snap.world)?;
let doc_id = updater.compiled(graph.world())?;
updater.task(ApplyProjectTask {
id: doc_id.clone(),
@ -181,7 +181,7 @@ impl ExportTask {
});
// Prepare the document.
let doc = doc.ok().context("cannot export with compilation errors")?;
let doc = doc.context("cannot export with compilation errors")?;
// Prepare data.
let kind2 = task.clone();
@ -196,7 +196,7 @@ impl ExportTask {
.get_or_init(|| -> Result<_> {
Ok(match &doc {
TypstDocument::Html(html_doc) => html_doc.clone(),
TypstDocument::Paged(_) => extra_compile_for_export(&snap.world)?,
TypstDocument::Paged(_) => extra_compile_for_export(graph.world())?,
})
})
.as_ref()
@ -208,7 +208,7 @@ impl ExportTask {
.get_or_init(|| -> Result<_> {
Ok(match &doc {
TypstDocument::Paged(paged_doc) => paged_doc.clone(),
TypstDocument::Html(_) => extra_compile_for_export(&snap.world)?,
TypstDocument::Html(_) => extra_compile_for_export(graph.world())?,
})
})
.as_ref()
@ -252,7 +252,7 @@ impl ExportTask {
one,
}) => {
let pretty = false;
let elements = reflexo_typst::query::retrieve(&snap.world, &selector, doc)
let elements = reflexo_typst::query::retrieve(&graph.world(), &selector, doc)
.map_err(|e| anyhow::anyhow!("failed to retrieve: {e}"))?;
if one && elements.len() != 1 {
bail!("expected exactly one element, found {}", elements.len());
@ -287,7 +287,7 @@ impl ExportTask {
TextExport::run_on_doc(doc)?.into_bytes()
}
ExportMd(ExportMarkdownTask { export: _ }) => {
let conv = Typlite::new(Arc::new(snap.world))
let conv = Typlite::new(Arc::new(graph.world().clone()))
.convert()
.map_err(|e| anyhow::anyhow!("failed to convert to markdown: {e}"))?;

View file

@ -183,26 +183,26 @@ pub struct PreviewCompileView {
impl typst_preview::CompileView for PreviewCompileView {
fn doc(&self) -> Option<TypstDocument> {
self.snap.doc.clone().ok()
self.snap.doc.clone()
}
fn status(&self) -> CompileStatus {
match self.snap.doc {
Ok(_) => CompileStatus::CompileSuccess,
Err(_) => CompileStatus::CompileError,
Some(_) => CompileStatus::CompileSuccess,
None => CompileStatus::CompileError,
}
}
fn is_on_saved(&self) -> bool {
self.snap.signal.by_fs_events
self.snap.snap.signal.by_fs_events
}
fn is_by_entry_update(&self) -> bool {
self.snap.signal.by_entry_update
self.snap.snap.signal.by_entry_update
}
fn resolve_source_span(&self, loc: Location) -> Option<SourceSpanOffset> {
let world = &self.snap.world;
let world = self.snap.world();
let Location::Src(loc) = loc;
let source_id = world.id_for_path(Path::new(&loc.filepath))?;
@ -230,7 +230,7 @@ impl typst_preview::CompileView for PreviewCompileView {
let TypstDocument::Paged(doc) = self.doc()? else {
return None;
};
let world = &self.snap.world;
let world = self.snap.world();
let page = pos.page_no.checked_sub(1)?;
let page = doc.pages.get(page)?;
@ -240,7 +240,7 @@ impl typst_preview::CompileView for PreviewCompileView {
}
fn resolve_document_position(&self, loc: Location) -> Vec<Position> {
let world = &self.snap.world;
let world = self.snap.world();
let Location::Src(src_loc) = loc;
let line = src_loc.pos.line as usize;
@ -265,7 +265,7 @@ impl typst_preview::CompileView for PreviewCompileView {
}
fn resolve_span(&self, span: Span, offset: Option<usize>) -> Option<DocToSrcJumpInfo> {
let world = &self.snap.world;
let world = self.snap.world();
let resolve_off =
|src: &Source, off: usize| src.byte_to_line(off).zip(src.byte_to_column(off));

View file

@ -9,6 +9,7 @@ use std::{
use clap_complete::Shell;
use parking_lot::Mutex;
use reflexo::{path::unix_slash, ImmutPath};
use reflexo_typst::WorldComputeGraph;
use tinymist_query::analysis::Analysis;
use tinymist_std::{bail, error::prelude::*};
use tokio::sync::mpsc;
@ -137,13 +138,13 @@ pub async fn compile_main(args: CompileArgs) -> Result<()> {
// Prepares for the compilation
let universe = (input, lock_dir.clone()).resolve()?;
let world = universe.snapshot();
let snap = CompileSnapshot::from_world(world);
let graph = WorldComputeGraph::from_world(world);
// Compiles the project
let compiled = CompiledArtifact::from_snapshot(snap);
let compiled = CompiledArtifact::from_graph(graph);
let diag = compiled.diagnostics();
print_diagnostics(&compiled.world, diag, DiagnosticFormat::Human)
print_diagnostics(compiled.world(), diag, DiagnosticFormat::Human)
.context_ut("print diagnostics")?;
if compiled.has_errors() {

View file

@ -189,9 +189,10 @@ pub async fn test_main(args: TestArgs) -> Result<()> {
log_info!("Runs testing again...");
}
// Sets is_compiling to track dependencies
artifact.snap.world.set_is_compiling(true);
let res = test_once(&artifact.world, &config);
artifact.snap.world.set_is_compiling(false);
let mut world = artifact.snap.world.clone();
world.set_is_compiling(true);
let res = test_once(&world, &config);
world.set_is_compiling(false);
if let Err(err) = res {
test_error!("Fatal:", "{err}");