tinymist_world/
snapshot.rs

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