use std::path::Path;
use std::sync::Arc;
use tinymist_std::ImmutPath;
use tinymist_std::error::prelude::*;
use tinymist_task::ExportTarget;
use tinymist_world::package::RegistryPathMapper;
#[cfg(all(not(feature = "system"), feature = "web"))]
use tinymist_world::package::registry::ProxyContext;
use tinymist_world::vfs::Vfs;
use tinymist_world::{
CompileSnapshot, CompilerFeat, CompilerUniverse, CompilerWorld, EntryOpts, EntryState,
};
use tinymist_world::{WorldComputeGraph, args::*};
use typst::Features;
use typst::diag::FileResult;
use typst::foundations::{Bytes, Dict};
use typst::utils::LazyHash;
use crate::world::font::FontResolverImpl;
use crate::{CompiledArtifact, Interrupt};
#[derive(Debug, Clone, Copy)]
pub struct LspCompilerFeat;
impl CompilerFeat for LspCompilerFeat {
type FontResolver = FontResolverImpl;
type AccessModel = DynAccessModel;
type Registry = LspRegistry;
}
pub type LspUniverse = CompilerUniverse<LspCompilerFeat>;
pub type LspWorld = CompilerWorld<LspCompilerFeat>;
pub type LspCompileSnapshot = CompileSnapshot<LspCompilerFeat>;
pub type LspCompiledArtifact = CompiledArtifact<LspCompilerFeat>;
pub type LspComputeGraph = Arc<WorldComputeGraph<LspCompilerFeat>>;
pub type LspInterrupt = Interrupt<LspCompilerFeat>;
pub type ImmutDict = Arc<LazyHash<Dict>>;
pub trait WorldProvider {
fn entry(&self) -> Result<EntryOpts>;
fn resolve(&self) -> Result<LspUniverse>;
}
#[cfg(feature = "system")]
impl WorldProvider for CompileOnceArgs {
fn resolve(&self) -> Result<LspUniverse> {
let entry = self.entry()?.try_into()?;
let inputs = self.resolve_inputs().unwrap_or_default();
let fonts = Arc::new(LspUniverseBuilder::resolve_fonts(self.font.clone())?);
let packages = LspUniverseBuilder::resolve_package(
self.cert.as_deref().map(From::from),
Some(&self.package),
);
Ok(LspUniverseBuilder::build(
entry,
ExportTarget::Paged,
self.resolve_features(),
inputs,
packages,
fonts,
self.creation_timestamp,
DynAccessModel(Arc::new(tinymist_world::vfs::system::SystemAccessModel {})),
))
}
fn entry(&self) -> Result<EntryOpts> {
let mut cwd = None;
let mut cwd = move || {
cwd.get_or_insert_with(|| {
std::env::current_dir().context("failed to get current directory")
})
.clone()
};
let main = {
let input = self.input.as_ref().context("entry file must be provided")?;
let input = Path::new(&input);
if input.is_absolute() {
input.to_owned()
} else {
cwd()?.join(input)
}
};
let root = if let Some(root) = &self.root {
if root.is_absolute() {
root.clone()
} else {
cwd()?.join(root)
}
} else {
main.parent()
.context("entry file don't have a valid parent as root")?
.to_owned()
};
let relative_main = match main.strip_prefix(&root) {
Ok(relative_main) => relative_main,
Err(_) => {
log::error!("entry file must be inside the root, file: {main:?}, root: {root:?}");
bail!("entry file must be inside the root, file: {main:?}, root: {root:?}");
}
};
Ok(EntryOpts::new_rooted(
root.clone(),
Some(relative_main.to_owned()),
))
}
}
#[cfg(feature = "system")]
impl WorldProvider for (crate::ProjectInput, ImmutPath) {
fn resolve(&self) -> Result<LspUniverse> {
use typst::foundations::{Str, Value};
let (proj, lock_dir) = self;
let entry = self.entry()?.try_into()?;
let inputs = proj
.inputs
.iter()
.map(|(k, v)| (Str::from(k.as_str()), Value::Str(Str::from(v.as_str()))))
.collect();
let fonts = LspUniverseBuilder::resolve_fonts(CompileFontArgs {
font_paths: {
proj.font_paths
.iter()
.flat_map(|p| p.to_abs_path(lock_dir))
.collect::<Vec<_>>()
},
ignore_system_fonts: !proj.system_fonts,
})?;
let packages = LspUniverseBuilder::resolve_package(
None,
Some(&CompilePackageArgs {
package_path: proj
.package_path
.as_ref()
.and_then(|p| p.to_abs_path(lock_dir)),
package_cache_path: proj
.package_cache_path
.as_ref()
.and_then(|p| p.to_abs_path(lock_dir)),
}),
);
Ok(LspUniverseBuilder::build(
entry,
ExportTarget::Paged,
Features::default(),
Arc::new(LazyHash::new(inputs)),
packages,
Arc::new(fonts),
None, DynAccessModel(Arc::new(tinymist_world::vfs::system::SystemAccessModel {})),
))
}
fn entry(&self) -> Result<EntryOpts> {
let (proj, lock_dir) = self;
let entry = proj
.main
.to_abs_path(lock_dir)
.context("failed to resolve entry file")?;
let root = if let Some(root) = &proj.root {
root.to_abs_path(lock_dir)
.context("failed to resolve root")?
} else {
lock_dir.as_ref().to_owned()
};
if !entry.starts_with(&root) {
bail!("entry file must be in the root directory, {entry:?}, {root:?}");
}
let relative_entry = match entry.strip_prefix(&root) {
Ok(relative_entry) => relative_entry,
Err(_) => bail!("entry path must be inside the root: {}", entry.display()),
};
Ok(EntryOpts::new_rooted(
root.clone(),
Some(relative_entry.to_owned()),
))
}
}
#[cfg(all(not(feature = "system"), feature = "web"))]
type LspRegistry = tinymist_world::package::registry::JsRegistry;
#[cfg(feature = "system")]
type LspRegistry = tinymist_world::package::registry::HttpRegistry;
#[cfg(not(any(feature = "system", feature = "web")))]
type LspRegistry = tinymist_world::package::registry::DummyRegistry;
pub struct LspUniverseBuilder;
impl LspUniverseBuilder {
#[allow(clippy::too_many_arguments)]
pub fn build(
entry: EntryState,
export_target: ExportTarget,
features: Features,
inputs: ImmutDict,
package_registry: LspRegistry,
font_resolver: Arc<FontResolverImpl>,
creation_timestamp: Option<i64>,
access_model: DynAccessModel,
) -> LspUniverse {
let package_registry = Arc::new(package_registry);
let resolver = Arc::new(RegistryPathMapper::new(package_registry.clone()));
let target_feature = match export_target {
ExportTarget::Html => Some(typst::Feature::Html),
ExportTarget::Bundle => Some(typst::Feature::Bundle),
ExportTarget::Paged => None,
};
let features = [
typst::Feature::Html,
typst::Feature::A11yExtras,
typst::Feature::Bundle,
]
.into_iter()
.filter(|feature| features.is_enabled(*feature) || Some(*feature) == target_feature)
.collect();
LspUniverse::new_raw(
entry,
features,
Some(inputs),
Vfs::new(resolver, access_model),
package_registry,
font_resolver,
creation_timestamp,
)
}
#[cfg(feature = "system")]
pub fn only_embedded_fonts() -> Result<FontResolverImpl> {
let mut searcher = tinymist_world::font::system::SystemFontSearcher::new();
searcher.resolve_opts(tinymist_world::config::CompileFontOpts {
font_paths: vec![],
no_system_fonts: true,
with_embedded_fonts: typst_assets::fonts()
.map(std::borrow::Cow::Borrowed)
.collect(),
})?;
Ok(searcher.build())
}
#[cfg(feature = "system")]
pub fn resolve_fonts(args: CompileFontArgs) -> Result<FontResolverImpl> {
let mut searcher = tinymist_world::font::system::SystemFontSearcher::new();
searcher.resolve_opts(tinymist_world::config::CompileFontOpts {
font_paths: args.font_paths,
no_system_fonts: args.ignore_system_fonts,
with_embedded_fonts: typst_assets::fonts()
.map(std::borrow::Cow::Borrowed)
.collect(),
})?;
Ok(searcher.build())
}
#[cfg(all(not(feature = "system"), feature = "web"))]
pub fn resolve_fonts(args: CompileFontArgs) -> Result<FontResolverImpl> {
let mut searcher = tinymist_world::font::web::BrowserFontSearcher::new();
searcher.resolve_opts(tinymist_world::config::CompileFontOpts {
font_paths: args.font_paths,
no_system_fonts: args.ignore_system_fonts,
with_embedded_fonts: typst_assets::fonts()
.map(std::borrow::Cow::Borrowed)
.collect(),
})?;
Ok(searcher.build())
}
#[cfg(not(any(feature = "system", feature = "web")))]
pub fn resolve_fonts(_args: CompileFontArgs) -> Result<FontResolverImpl> {
let mut searcher = tinymist_world::font::memory::MemoryFontSearcher::default();
searcher.add_memory_fonts(typst_assets::fonts().map(Bytes::new).collect::<Vec<_>>());
Ok(searcher.build())
}
#[cfg(feature = "system")]
pub fn resolve_package(
cert_path: Option<ImmutPath>,
args: Option<&CompilePackageArgs>,
) -> tinymist_world::package::registry::HttpRegistry {
tinymist_world::package::registry::HttpRegistry::new(
cert_path,
args.and_then(|args| Some(args.package_path.clone()?.into())),
args.and_then(|args| Some(args.package_cache_path.clone()?.into())),
)
}
#[cfg(all(not(feature = "system"), feature = "web"))]
pub fn resolve_package(
_cert_path: Option<ImmutPath>,
_args: Option<&CompilePackageArgs>,
resolve_fn: js_sys::Function,
) -> tinymist_world::package::registry::JsRegistry {
tinymist_world::package::registry::JsRegistry {
context: ProxyContext::new(wasm_bindgen::JsValue::NULL),
real_resolve_fn: resolve_fn,
}
}
#[cfg(not(any(feature = "system", feature = "web")))]
pub fn resolve_package(
_cert_path: Option<ImmutPath>,
_args: Option<&CompilePackageArgs>,
) -> tinymist_world::package::registry::DummyRegistry {
tinymist_world::package::registry::DummyRegistry
}
}
pub trait LspAccessModel: Send + Sync {
fn content(&self, src: &Path) -> FileResult<Bytes>;
}
impl<T> LspAccessModel for T
where
T: tinymist_world::vfs::PathAccessModel + Send + Sync + 'static,
{
fn content(&self, src: &Path) -> FileResult<Bytes> {
self.content(src)
}
}
#[derive(Clone)]
pub struct DynAccessModel(pub Arc<dyn LspAccessModel>);
impl DynAccessModel {
pub fn new(access_model: Arc<dyn LspAccessModel>) -> Self {
Self(access_model)
}
}
impl tinymist_world::vfs::PathAccessModel for DynAccessModel {
fn content(&self, src: &Path) -> FileResult<Bytes> {
self.0.content(src)
}
fn reset(&mut self) {}
}