tinymist_world/
snapshot.rs

1//! Project compiler for tinymist.
2
3use core::fmt;
4
5use crate::{CompilerFeat, CompilerWorld, EntryReader, TaskInputs, args::TaskWhen};
6use ecow::EcoString;
7use serde::{Deserialize, Serialize};
8use tinymist_std::typst::TypstDocument;
9
10/// Project instance id. This is slightly different from the project ids that
11/// persist in disk.
12#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct ProjectInsId(pub EcoString);
14
15impl fmt::Display for ProjectInsId {
16    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17        self.0.fmt(f)
18    }
19}
20
21impl ProjectInsId {
22    /// The primary project id.
23    pub const PRIMARY: ProjectInsId = ProjectInsId(EcoString::inline("primary"));
24}
25
26/// The export signal for the document.
27#[deprecated(note = "Use `CompileSignal` directly.")]
28pub type ExportSignal = CompileSignal;
29
30/// A signal that possibly triggers a compile (export).
31///
32/// Whether to compile (export) depends on the current state of the document and
33/// the user settings.
34#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
35#[serde(rename_all = "camelCase")]
36pub struct CompileSignal {
37    /// Whether the revision is annotated by memory events.
38    pub by_mem_events: bool,
39    /// Whether the revision is annotated by file system events.
40    pub by_fs_events: bool,
41    /// Whether the revision is annotated by entry update.
42    pub by_entry_update: bool,
43}
44
45impl CompileSignal {
46    /// Merges two signals.
47    pub fn merge(&mut self, other: CompileSignal) {
48        self.by_mem_events |= other.by_mem_events;
49        self.by_fs_events |= other.by_fs_events;
50        self.by_entry_update |= other.by_entry_update;
51    }
52
53    /// Whether there is any reason to compile (export).
54    ///
55    /// This is used to determine if the document should be compiled.
56    pub fn any(&self) -> bool {
57        self.by_mem_events || self.by_fs_events || self.by_entry_update
58    }
59
60    /// Excludes some signals.
61    pub fn exclude(&self, excluded: Self) -> Self {
62        Self {
63            by_mem_events: self.by_mem_events && !excluded.by_mem_events,
64            by_fs_events: self.by_fs_events && !excluded.by_fs_events,
65            by_entry_update: self.by_entry_update && !excluded.by_entry_update,
66        }
67    }
68
69    /// Whether the task should run.
70    pub fn should_run_task_dyn(
71        &self,
72        when: &TaskWhen,
73        docs: Option<&TypstDocument>,
74    ) -> Option<bool> {
75        match docs {
76            Some(TypstDocument::Paged(doc)) => self.should_run_task(when, Some(doc.as_ref())),
77            Some(TypstDocument::Html(doc)) => self.should_run_task(when, Some(doc.as_ref())),
78            None => self.should_run_task::<typst::layout::PagedDocument>(when, None),
79        }
80    }
81
82    /// Whether the task should run.
83    pub fn should_run_task<D: typst::Document>(
84        &self,
85        when: &TaskWhen,
86        docs: Option<&D>,
87    ) -> Option<bool> {
88        match when {
89            TaskWhen::Never => Some(false),
90            // todo: by script
91            TaskWhen::Script => Some(self.by_entry_update),
92            TaskWhen::OnType => Some(self.by_mem_events),
93            TaskWhen::OnSave => Some(self.by_fs_events),
94            TaskWhen::OnDocumentHasTitle if self.by_fs_events => {
95                docs.map(|doc| doc.info().title.is_some())
96            }
97            TaskWhen::OnDocumentHasTitle => Some(false),
98        }
99    }
100}
101
102/// A snapshot of the project and compilation state.
103///
104/// This is used to store the state of the project and compilation.
105pub struct CompileSnapshot<F: CompilerFeat> {
106    /// The project id.
107    pub id: ProjectInsId,
108    /// The export signal for the document.
109    pub signal: CompileSignal,
110    /// The world.
111    pub world: CompilerWorld<F>,
112    /// The last successfully compiled document.
113    pub success_doc: Option<TypstDocument>,
114}
115
116impl<F: CompilerFeat + 'static> CompileSnapshot<F> {
117    /// Creates a snapshot from the world.
118    pub fn from_world(world: CompilerWorld<F>) -> Self {
119        Self {
120            id: ProjectInsId("primary".into()),
121            signal: CompileSignal::default(),
122            world,
123            success_doc: None,
124        }
125    }
126
127    /// Forks a new snapshot that compiles a different document.
128    ///
129    /// Note: the resulting document should not be shared in system, because we
130    /// generally believe that the document is revisioned, but temporary
131    /// tasks break this assumption.
132    pub fn task(mut self, inputs: TaskInputs) -> Self {
133        'check_changed: {
134            if let Some(entry) = &inputs.entry
135                && *entry != self.world.entry_state()
136            {
137                break 'check_changed;
138            }
139            if let Some(inputs) = &inputs.inputs
140                && inputs.clone() != self.world.inputs()
141            {
142                break 'check_changed;
143            }
144
145            return self;
146        };
147
148        self.world = self.world.task(inputs);
149
150        self
151    }
152}
153
154impl<F: CompilerFeat> Clone for CompileSnapshot<F> {
155    fn clone(&self) -> Self {
156        Self {
157            id: self.id.clone(),
158            signal: self.signal,
159            world: self.world.clone(),
160            success_doc: self.success_doc.clone(),
161        }
162    }
163}