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 features = if matches!(export_target, ExportTarget::Html) {
244 Features::from_iter([typst::Feature::Html])
245 } else {
246 features
247 };
248
249 LspUniverse::new_raw(
250 entry,
251 features,
252 Some(inputs),
253 Vfs::new(resolver, access_model),
254 package_registry,
255 font_resolver,
256 creation_timestamp,
257 )
258 }
259
260 #[cfg(feature = "system")]
262 pub fn only_embedded_fonts() -> Result<FontResolverImpl> {
263 let mut searcher = tinymist_world::font::system::SystemFontSearcher::new();
264 searcher.resolve_opts(tinymist_world::config::CompileFontOpts {
265 font_paths: vec![],
266 no_system_fonts: true,
267 with_embedded_fonts: typst_assets::fonts()
268 .map(std::borrow::Cow::Borrowed)
269 .collect(),
270 })?;
271 Ok(searcher.build())
272 }
273
274 #[cfg(feature = "system")]
276 pub fn resolve_fonts(args: CompileFontArgs) -> Result<FontResolverImpl> {
277 let mut searcher = tinymist_world::font::system::SystemFontSearcher::new();
278 searcher.resolve_opts(tinymist_world::config::CompileFontOpts {
279 font_paths: args.font_paths,
280 no_system_fonts: args.ignore_system_fonts,
281 with_embedded_fonts: typst_assets::fonts()
282 .map(std::borrow::Cow::Borrowed)
283 .collect(),
284 })?;
285 Ok(searcher.build())
286 }
287
288 #[cfg(all(not(feature = "system"), feature = "web"))]
290 pub fn resolve_fonts(args: CompileFontArgs) -> Result<FontResolverImpl> {
291 let mut searcher = tinymist_world::font::web::BrowserFontSearcher::new();
292 searcher.resolve_opts(tinymist_world::config::CompileFontOpts {
293 font_paths: args.font_paths,
294 no_system_fonts: args.ignore_system_fonts,
295 with_embedded_fonts: typst_assets::fonts()
296 .map(std::borrow::Cow::Borrowed)
297 .collect(),
298 })?;
299 Ok(searcher.build())
300 }
301
302 #[cfg(not(any(feature = "system", feature = "web")))]
304 pub fn resolve_fonts(_args: CompileFontArgs) -> Result<FontResolverImpl> {
305 let mut searcher = tinymist_world::font::memory::MemoryFontSearcher::default();
306 searcher.add_memory_fonts(typst_assets::fonts().map(Bytes::new).collect::<Vec<_>>());
307 Ok(searcher.build())
308 }
309
310 #[cfg(feature = "system")]
312 pub fn resolve_package(
313 cert_path: Option<ImmutPath>,
314 args: Option<&CompilePackageArgs>,
315 ) -> tinymist_world::package::registry::HttpRegistry {
316 tinymist_world::package::registry::HttpRegistry::new(
317 cert_path,
318 args.and_then(|args| Some(args.package_path.clone()?.into())),
319 args.and_then(|args| Some(args.package_cache_path.clone()?.into())),
320 )
321 }
322
323 #[cfg(all(not(feature = "system"), feature = "web"))]
325 pub fn resolve_package(
326 _cert_path: Option<ImmutPath>,
327 _args: Option<&CompilePackageArgs>,
328 resolve_fn: js_sys::Function,
329 ) -> tinymist_world::package::registry::JsRegistry {
330 tinymist_world::package::registry::JsRegistry {
331 context: ProxyContext::new(wasm_bindgen::JsValue::NULL),
332 real_resolve_fn: resolve_fn,
333 }
334 }
335
336 #[cfg(not(any(feature = "system", feature = "web")))]
338 pub fn resolve_package(
339 _cert_path: Option<ImmutPath>,
340 _args: Option<&CompilePackageArgs>,
341 ) -> tinymist_world::package::registry::DummyRegistry {
342 tinymist_world::package::registry::DummyRegistry
343 }
344}
345
346pub trait LspAccessModel: Send + Sync {
348 fn content(&self, src: &Path) -> FileResult<Bytes>;
350}
351
352impl<T> LspAccessModel for T
353where
354 T: tinymist_world::vfs::PathAccessModel + Send + Sync + 'static,
355{
356 fn content(&self, src: &Path) -> FileResult<Bytes> {
357 self.content(src)
358 }
359}
360
361#[derive(Clone)]
363pub struct DynAccessModel(pub Arc<dyn LspAccessModel>);
364
365impl DynAccessModel {
366 pub fn new(access_model: Arc<dyn LspAccessModel>) -> Self {
368 Self(access_model)
369 }
370}
371
372impl tinymist_world::vfs::PathAccessModel for DynAccessModel {
373 fn content(&self, src: &Path) -> FileResult<Bytes> {
374 self.0.content(src)
375 }
376
377 fn reset(&mut self) {}
378}