1use std::path::Path;
2use std::sync::Arc;
3
4use tinymist_std::ImmutPath;
5use tinymist_std::error::prelude::*;
6use tinymist_task::ExportTarget;
7use tinymist_world::package::RegistryPathMapper;
8use tinymist_world::vfs::Vfs;
9use tinymist_world::{
10 CompileSnapshot, CompilerFeat, CompilerUniverse, CompilerWorld, EntryOpts, EntryState,
11};
12use tinymist_world::{WorldComputeGraph, args::*};
13use typst::Features;
14use typst::diag::FileResult;
15use typst::foundations::{Bytes, Dict};
16use typst::utils::LazyHash;
17
18use crate::world::font::FontResolverImpl;
19use crate::{CompiledArtifact, Interrupt};
20
21#[derive(Debug, Clone, Copy)]
24pub struct LspCompilerFeat;
25
26impl CompilerFeat for LspCompilerFeat {
27 type FontResolver = FontResolverImpl;
29 type AccessModel = DynAccessModel;
31 #[cfg(feature = "system")]
33 type Registry = tinymist_world::package::registry::HttpRegistry;
34 #[cfg(not(feature = "system"))]
36 type Registry = tinymist_world::package::registry::DummyRegistry;
37}
38
39pub type LspUniverse = CompilerUniverse<LspCompilerFeat>;
41pub type LspWorld = CompilerWorld<LspCompilerFeat>;
43pub type LspCompileSnapshot = CompileSnapshot<LspCompilerFeat>;
45pub type LspCompiledArtifact = CompiledArtifact<LspCompilerFeat>;
47pub type LspComputeGraph = Arc<WorldComputeGraph<LspCompilerFeat>>;
49pub type LspInterrupt = Interrupt<LspCompilerFeat>;
51pub type ImmutDict = Arc<LazyHash<Dict>>;
53
54pub trait WorldProvider {
56 fn entry(&self) -> Result<EntryOpts>;
58 fn resolve(&self) -> Result<LspUniverse>;
60}
61
62#[cfg(feature = "system")]
63impl WorldProvider for CompileOnceArgs {
64 fn resolve(&self) -> Result<LspUniverse> {
65 let entry = self.entry()?.try_into()?;
66 let inputs = self.resolve_inputs().unwrap_or_default();
67 let fonts = Arc::new(LspUniverseBuilder::resolve_fonts(self.font.clone())?);
68 let packages = LspUniverseBuilder::resolve_package(
69 self.cert.as_deref().map(From::from),
70 Some(&self.package),
71 );
72
73 Ok(LspUniverseBuilder::build(
75 entry,
76 ExportTarget::Paged,
77 self.resolve_features(),
78 inputs,
79 packages,
80 fonts,
81 self.creation_timestamp,
82 DynAccessModel(Arc::new(tinymist_world::vfs::system::SystemAccessModel {})),
83 ))
84 }
85
86 fn entry(&self) -> Result<EntryOpts> {
87 let mut cwd = None;
88 let mut cwd = move || {
89 cwd.get_or_insert_with(|| {
90 std::env::current_dir().context("failed to get current directory")
91 })
92 .clone()
93 };
94
95 let main = {
96 let input = self.input.as_ref().context("entry file must be provided")?;
97 let input = Path::new(&input);
98 if input.is_absolute() {
99 input.to_owned()
100 } else {
101 cwd()?.join(input)
102 }
103 };
104
105 let root = if let Some(root) = &self.root {
106 if root.is_absolute() {
107 root.clone()
108 } else {
109 cwd()?.join(root)
110 }
111 } else {
112 main.parent()
113 .context("entry file don't have a valid parent as root")?
114 .to_owned()
115 };
116
117 let relative_main = match main.strip_prefix(&root) {
118 Ok(relative_main) => relative_main,
119 Err(_) => {
120 log::error!("entry file must be inside the root, file: {main:?}, root: {root:?}");
121 bail!("entry file must be inside the root, file: {main:?}, root: {root:?}");
122 }
123 };
124
125 Ok(EntryOpts::new_rooted(
126 root.clone(),
127 Some(relative_main.to_owned()),
128 ))
129 }
130}
131
132#[cfg(feature = "system")]
134impl WorldProvider for (crate::ProjectInput, ImmutPath) {
135 fn resolve(&self) -> Result<LspUniverse> {
136 use typst::foundations::{Str, Value};
137
138 let (proj, lock_dir) = self;
139 let entry = self.entry()?.try_into()?;
140 let inputs = proj
141 .inputs
142 .iter()
143 .map(|(k, v)| (Str::from(k.as_str()), Value::Str(Str::from(v.as_str()))))
144 .collect();
145 let fonts = LspUniverseBuilder::resolve_fonts(CompileFontArgs {
146 font_paths: {
147 proj.font_paths
148 .iter()
149 .flat_map(|p| p.to_abs_path(lock_dir))
150 .collect::<Vec<_>>()
151 },
152 ignore_system_fonts: !proj.system_fonts,
153 })?;
154 let packages = LspUniverseBuilder::resolve_package(
155 None,
157 Some(&CompilePackageArgs {
158 package_path: proj
159 .package_path
160 .as_ref()
161 .and_then(|p| p.to_abs_path(lock_dir)),
162 package_cache_path: proj
163 .package_cache_path
164 .as_ref()
165 .and_then(|p| p.to_abs_path(lock_dir)),
166 }),
167 );
168
169 Ok(LspUniverseBuilder::build(
171 entry,
172 ExportTarget::Paged,
173 Features::default(),
175 Arc::new(LazyHash::new(inputs)),
176 packages,
177 Arc::new(fonts),
178 None, DynAccessModel(Arc::new(tinymist_world::vfs::system::SystemAccessModel {})),
180 ))
181 }
182
183 fn entry(&self) -> Result<EntryOpts> {
184 let (proj, lock_dir) = self;
185
186 let entry = proj
187 .main
188 .to_abs_path(lock_dir)
189 .context("failed to resolve entry file")?;
190
191 let root = if let Some(root) = &proj.root {
192 root.to_abs_path(lock_dir)
193 .context("failed to resolve root")?
194 } else {
195 lock_dir.as_ref().to_owned()
196 };
197
198 if !entry.starts_with(&root) {
199 bail!("entry file must be in the root directory, {entry:?}, {root:?}");
200 }
201
202 let relative_entry = match entry.strip_prefix(&root) {
203 Ok(relative_entry) => relative_entry,
204 Err(_) => bail!("entry path must be inside the root: {}", entry.display()),
205 };
206
207 Ok(EntryOpts::new_rooted(
208 root.clone(),
209 Some(relative_entry.to_owned()),
210 ))
211 }
212}
213
214#[cfg(not(feature = "system"))]
215type LspRegistry = tinymist_world::package::registry::DummyRegistry;
216#[cfg(feature = "system")]
217type LspRegistry = tinymist_world::package::registry::HttpRegistry;
218
219pub struct LspUniverseBuilder;
221
222impl LspUniverseBuilder {
223 #[allow(clippy::too_many_arguments)]
226 pub fn build(
227 entry: EntryState,
228 export_target: ExportTarget,
229 features: Features,
230 inputs: ImmutDict,
231 package_registry: LspRegistry,
232 font_resolver: Arc<FontResolverImpl>,
233 creation_timestamp: Option<i64>,
234 access_model: DynAccessModel,
235 ) -> LspUniverse {
236 let package_registry = Arc::new(package_registry);
237 let resolver = Arc::new(RegistryPathMapper::new(package_registry.clone()));
238
239 let features = if matches!(export_target, ExportTarget::Html) {
241 Features::from_iter([typst::Feature::Html])
242 } else {
243 features
244 };
245
246 LspUniverse::new_raw(
247 entry,
248 features,
249 Some(inputs),
250 Vfs::new(resolver, access_model),
251 package_registry,
252 font_resolver,
253 creation_timestamp,
254 )
255 }
256
257 #[cfg(feature = "system")]
259 pub fn only_embedded_fonts() -> Result<FontResolverImpl> {
260 let mut searcher = tinymist_world::font::system::SystemFontSearcher::new();
261 searcher.resolve_opts(tinymist_world::config::CompileFontOpts {
262 font_paths: vec![],
263 no_system_fonts: true,
264 with_embedded_fonts: typst_assets::fonts()
265 .map(std::borrow::Cow::Borrowed)
266 .collect(),
267 })?;
268 Ok(searcher.build())
269 }
270
271 #[cfg(feature = "system")]
273 pub fn resolve_fonts(args: CompileFontArgs) -> Result<FontResolverImpl> {
274 let mut searcher = tinymist_world::font::system::SystemFontSearcher::new();
275 searcher.resolve_opts(tinymist_world::config::CompileFontOpts {
276 font_paths: args.font_paths,
277 no_system_fonts: args.ignore_system_fonts,
278 with_embedded_fonts: typst_assets::fonts()
279 .map(std::borrow::Cow::Borrowed)
280 .collect(),
281 })?;
282 Ok(searcher.build())
283 }
284
285 #[cfg(all(not(feature = "system"), feature = "web"))]
287 pub fn resolve_fonts(args: CompileFontArgs) -> Result<FontResolverImpl> {
288 let mut searcher = tinymist_world::font::web::BrowserFontSearcher::new();
289 searcher.resolve_opts(tinymist_world::config::CompileFontOpts {
290 font_paths: args.font_paths,
291 no_system_fonts: args.ignore_system_fonts,
292 with_embedded_fonts: typst_assets::fonts()
293 .map(std::borrow::Cow::Borrowed)
294 .collect(),
295 })?;
296 Ok(searcher.build())
297 }
298
299 #[cfg(not(any(feature = "system", feature = "web")))]
301 pub fn resolve_fonts(_args: CompileFontArgs) -> Result<FontResolverImpl> {
302 let mut searcher = tinymist_world::font::memory::MemoryFontSearcher::default();
303 searcher.add_memory_fonts(typst_assets::fonts().map(Bytes::new).collect::<Vec<_>>());
304 Ok(searcher.build())
305 }
306
307 #[cfg(feature = "system")]
309 pub fn resolve_package(
310 cert_path: Option<ImmutPath>,
311 args: Option<&CompilePackageArgs>,
312 ) -> tinymist_world::package::registry::HttpRegistry {
313 tinymist_world::package::registry::HttpRegistry::new(
314 cert_path,
315 args.and_then(|args| Some(args.package_path.clone()?.into())),
316 args.and_then(|args| Some(args.package_cache_path.clone()?.into())),
317 )
318 }
319
320 #[cfg(not(feature = "system"))]
322 pub fn resolve_package(
323 _cert_path: Option<ImmutPath>,
324 _args: Option<&CompilePackageArgs>,
325 ) -> tinymist_world::package::registry::DummyRegistry {
326 tinymist_world::package::registry::DummyRegistry
327 }
328}
329
330pub trait LspAccessModel: Send + Sync {
332 fn content(&self, src: &Path) -> FileResult<Bytes>;
334}
335
336impl<T> LspAccessModel for T
337where
338 T: tinymist_world::vfs::PathAccessModel + Send + Sync + 'static,
339{
340 fn content(&self, src: &Path) -> FileResult<Bytes> {
341 self.content(src)
342 }
343}
344
345#[derive(Clone)]
347pub struct DynAccessModel(pub Arc<dyn LspAccessModel>);
348
349impl DynAccessModel {
350 pub fn new(access_model: Arc<dyn LspAccessModel>) -> Self {
352 Self(access_model)
353 }
354}
355
356impl tinymist_world::vfs::PathAccessModel for DynAccessModel {
357 fn content(&self, src: &Path) -> FileResult<Bytes> {
358 self.0.content(src)
359 }
360
361 fn reset(&mut self) {}
362}