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