diff --git a/Cargo.lock b/Cargo.lock index be2dac24..de814c7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4250,7 +4250,8 @@ dependencies = [ "console_error_panic_hook", "js-sys", "reflexo-typst", - "tinymist-world", + "tinymist-project", + "tinymist-query", "vergen", "wasm-bindgen", ] diff --git a/crates/tinymist-core/Cargo.toml b/crates/tinymist-core/Cargo.toml index 31081c6e..6d3f230f 100644 --- a/crates/tinymist-core/Cargo.toml +++ b/crates/tinymist-core/Cargo.toml @@ -22,7 +22,7 @@ web = [ "js-sys", "console_error_panic_hook", "no-content-hint", - "tinymist-world/web", + "tinymist-project/web", "reflexo-typst/web", ] @@ -31,7 +31,8 @@ no-content-hint = ["reflexo-typst/no-content-hint"] [dependencies] wasm-bindgen = { version = "0.2.100", optional = true } js-sys = { version = "0.3.77", optional = true } -tinymist-world.workspace = true +tinymist-project.workspace = true +tinymist-query.workspace = true reflexo-typst.workspace = true console_error_panic_hook = { version = "0.1.2", optional = true } diff --git a/crates/tinymist-project/Cargo.toml b/crates/tinymist-project/Cargo.toml index 319a242a..95578f57 100644 --- a/crates/tinymist-project/Cargo.toml +++ b/crates/tinymist-project/Cargo.toml @@ -40,9 +40,11 @@ notify.workspace = true fonts = ["typst-assets/fonts"] no-content-hint = ["tinymist-task/no-content-hint"] -lsp = ["system", "toml"] +lsp = ["toml"] +# "system", system = ["tinymist-std/system", "tinymist-world/system"] +web = ["tinymist-std/web", "tinymist-world/web"] [lints] workspace = true diff --git a/crates/tinymist-project/src/lib.rs b/crates/tinymist-project/src/lib.rs index d91dd421..fc54d1cd 100644 --- a/crates/tinymist-project/src/lib.rs +++ b/crates/tinymist-project/src/lib.rs @@ -11,22 +11,20 @@ mod lock; mod lsp; #[cfg(feature = "system")] mod watch; -#[cfg(feature = "system")] + pub mod world; pub use args::*; pub use compiler::*; pub use entry::*; -pub use model::*; - -#[cfg(feature = "lsp")] pub use lock::*; +pub use model::*; +pub use world::*; + #[cfg(feature = "lsp")] pub use lsp::*; #[cfg(feature = "system")] pub use watch::*; -#[cfg(feature = "system")] -pub use world::*; pub use tinymist_world::{CompileSignal, CompileSnapshot, ProjectInsId}; diff --git a/crates/tinymist-project/src/lsp.rs b/crates/tinymist-project/src/lsp.rs index d5c90757..065c62c3 100644 --- a/crates/tinymist-project/src/lsp.rs +++ b/crates/tinymist-project/src/lsp.rs @@ -1,24 +1,20 @@ use std::path::Path; -use std::{borrow::Cow, sync::Arc}; +use std::sync::Arc; use tinymist_std::error::prelude::*; -use tinymist_std::{bail, ImmutPath}; +use tinymist_std::ImmutPath; use tinymist_task::ExportTarget; -use tinymist_world::config::CompileFontOpts; -use tinymist_world::font::system::SystemFontSearcher; -use tinymist_world::package::{registry::HttpRegistry, RegistryPathMapper}; -use tinymist_world::vfs::{system::SystemAccessModel, Vfs}; +use tinymist_world::package::RegistryPathMapper; +use tinymist_world::vfs::Vfs; use tinymist_world::{args::*, WorldComputeGraph}; use tinymist_world::{ CompileSnapshot, CompilerFeat, CompilerUniverse, CompilerWorld, EntryOpts, EntryState, }; use typst::diag::FileResult; -use typst::foundations::{Bytes, Dict, Str, Value}; +use typst::foundations::{Bytes, Dict}; use typst::utils::LazyHash; use typst::Features; -use crate::ProjectInput; - use crate::world::font::FontResolverImpl; use crate::{CompiledArtifact, Interrupt}; @@ -33,7 +29,11 @@ impl CompilerFeat for LspCompilerFeat { /// It accesses a physical file system. type AccessModel = DynAccessModel; /// It performs native HTTP requests for fetching package data. - type Registry = HttpRegistry; + #[cfg(feature = "system")] + type Registry = tinymist_world::package::registry::HttpRegistry; + // todo: registry in browser + #[cfg(not(feature = "system"))] + type Registry = tinymist_world::package::registry::DummyRegistry; } /// LSP universe that spawns LSP worlds. @@ -59,6 +59,7 @@ pub trait WorldProvider { fn resolve(&self) -> Result; } +#[cfg(feature = "system")] impl WorldProvider for CompileOnceArgs { fn resolve(&self) -> Result { let entry = self.entry()?.try_into()?; @@ -78,7 +79,7 @@ impl WorldProvider for CompileOnceArgs { packages, fonts, self.creation_timestamp, - DynAccessModel(Arc::new(SystemAccessModel {})), + DynAccessModel(Arc::new(tinymist_world::vfs::system::SystemAccessModel {})), )) } @@ -129,8 +130,11 @@ impl WorldProvider for CompileOnceArgs { } // todo: merge me with the above impl -impl WorldProvider for (ProjectInput, ImmutPath) { +#[cfg(feature = "system")] +impl WorldProvider for (crate::ProjectInput, ImmutPath) { fn resolve(&self) -> Result { + use typst::foundations::{Str, Value}; + let (proj, lock_dir) = self; let entry = self.entry()?.try_into()?; let inputs = proj @@ -172,7 +176,7 @@ impl WorldProvider for (ProjectInput, ImmutPath) { packages, Arc::new(fonts), None, // creation_timestamp - not available in project file context - DynAccessModel(Arc::new(SystemAccessModel {})), + DynAccessModel(Arc::new(tinymist_world::vfs::system::SystemAccessModel {})), )) } @@ -207,6 +211,11 @@ impl WorldProvider for (ProjectInput, ImmutPath) { } } +#[cfg(not(feature = "system"))] +type LspRegistry = tinymist_world::package::registry::DummyRegistry; +#[cfg(feature = "system")] +type LspRegistry = tinymist_world::package::registry::HttpRegistry; + /// Builder for LSP universe. pub struct LspUniverseBuilder; @@ -219,7 +228,7 @@ impl LspUniverseBuilder { export_target: ExportTarget, features: Features, inputs: ImmutDict, - package_registry: HttpRegistry, + package_registry: LspRegistry, font_resolver: Arc, creation_timestamp: Option, access_model: DynAccessModel, @@ -246,38 +255,54 @@ impl LspUniverseBuilder { } /// Resolve fonts from given options. + #[cfg(feature = "system")] pub fn only_embedded_fonts() -> Result { - let mut searcher = SystemFontSearcher::new(); - searcher.resolve_opts(CompileFontOpts { + 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(Cow::Borrowed).collect(), + with_embedded_fonts: typst_assets::fonts() + .map(std::borrow::Cow::Borrowed) + .collect(), })?; Ok(searcher.build()) } /// Resolve fonts from given options. + #[cfg(feature = "system")] pub fn resolve_fonts(args: CompileFontArgs) -> Result { - let mut searcher = SystemFontSearcher::new(); - searcher.resolve_opts(CompileFontOpts { + 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(Cow::Borrowed).collect(), + with_embedded_fonts: typst_assets::fonts() + .map(std::borrow::Cow::Borrowed) + .collect(), })?; Ok(searcher.build()) } - /// Resolve package registry from given options. + /// Resolves package registry from given options. + #[cfg(feature = "system")] pub fn resolve_package( cert_path: Option, args: Option<&CompilePackageArgs>, - ) -> HttpRegistry { - HttpRegistry::new( + ) -> 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())), ) } + + /// Resolves package registry from given options. + #[cfg(not(feature = "system"))] + pub fn resolve_package( + _cert_path: Option, + _args: Option<&CompilePackageArgs>, + ) -> tinymist_world::package::registry::DummyRegistry { + tinymist_world::package::registry::DummyRegistry + } } /// Access model for LSP universe and worlds. diff --git a/crates/tinymist-project/src/world.rs b/crates/tinymist-project/src/world.rs index a5e67856..a4703254 100644 --- a/crates/tinymist-project/src/world.rs +++ b/crates/tinymist-project/src/world.rs @@ -4,8 +4,11 @@ pub use tinymist_world as base; pub use tinymist_world::args::*; pub use tinymist_world::config::CompileFontOpts; pub use tinymist_world::entry::*; -pub use tinymist_world::{diag, font, package, system, vfs}; +pub use tinymist_world::{diag, font, package, vfs}; pub use tinymist_world::{ with_main, CompilerUniverse, CompilerWorld, DiagnosticFormat, EntryOpts, EntryState, RevisingUniverse, SourceWorld, TaskInputs, }; + +#[cfg(feature = "system")] +pub use tinymist_world::system; diff --git a/crates/tinymist-query/src/analysis/completion/type.rs b/crates/tinymist-query/src/analysis/completion/type.rs index d840691e..3f9d76ba 100644 --- a/crates/tinymist-query/src/analysis/completion/type.rs +++ b/crates/tinymist-query/src/analysis/completion/type.rs @@ -209,26 +209,28 @@ impl TypeCompletionWorker<'_, '_, '_, '_> { BuiltinTy::TextSize => return None, BuiltinTy::TextLang => { for (&key, desc) in rust_iso639::ALL_MAP.entries() { - let detail = eco_format!("An ISO 639-1/2/3 language code, {}.", desc.name); + let detail = + eco_format!("An ISO 639-1/2/3 language code, {}.", desc.get_name()); self.base.push_completion(Completion { kind: CompletionKind::Syntax, label: key.to_lowercase().into(), apply: Some(eco_format!("\"{}\"", key.to_lowercase())), detail: Some(detail), - label_details: Some(desc.name.into()), + label_details: Some(desc.get_name()), ..Completion::default() }); } } BuiltinTy::TextRegion => { for (&key, desc) in rust_iso3166::ALPHA2_MAP.entries() { - let detail = eco_format!("An ISO 3166-1 alpha-2 region code, {}.", desc.name); + let detail = + eco_format!("An ISO 3166-1 alpha-2 region code, {}.", desc.get_name()); self.base.push_completion(Completion { kind: CompletionKind::Syntax, label: key.to_lowercase().into(), apply: Some(eco_format!("\"{}\"", key.to_lowercase())), detail: Some(detail), - label_details: Some(desc.name.into()), + label_details: Some(desc.get_name()), ..Completion::default() }); } @@ -346,3 +348,37 @@ impl TypeCompletionWorker<'_, '_, '_, '_> { Some(()) } } + +// desc.name() + +trait GetName { + fn get_name(&self) -> EcoString; +} + +#[cfg(not(target_arch = "wasm32"))] +impl GetName for rust_iso639::LanguageCode<'_> { + fn get_name(&self) -> EcoString { + self.name.into() + } +} + +#[cfg(not(all(direct_wasm, target_arch = "wasm32")))] +impl GetName for rust_iso3166::CountryCode { + fn get_name(&self) -> EcoString { + self.name.into() + } +} + +#[cfg(target_arch = "wasm32")] +impl GetName for rust_iso639::LanguageCode { + fn get_name(&self) -> EcoString { + self.name().into() + } +} + +#[cfg(all(direct_wasm, target_arch = "wasm32"))] +impl GetName for rust_iso3166::CountryCode { + fn get_name(&self) -> EcoString { + self.name().into() + } +} diff --git a/crates/tinymist-query/src/analysis/global.rs b/crates/tinymist-query/src/analysis/global.rs index ee4fa5a4..c5272e19 100644 --- a/crates/tinymist-query/src/analysis/global.rs +++ b/crates/tinymist-query/src/analysis/global.rs @@ -20,9 +20,7 @@ use tinymist_std::typst::TypstDocument; use tinymist_world::debug_loc::DataSource; use tinymist_world::vfs::{PathResolution, WorkspaceResolver}; use tinymist_world::{EntryReader, DETACHED_ENTRY}; -use typst::diag::{ - eco_format, At, FileError, FileResult, SourceDiagnostic, SourceResult, StrResult, -}; +use typst::diag::{At, FileError, FileResult, SourceDiagnostic, SourceResult, StrResult}; use typst::foundations::{Bytes, IntoValue, Module, StyleChain, Styles}; use typst::introspection::Introspector; use typst::layout::Position; @@ -674,6 +672,7 @@ impl SharedContext { } /// Get the local packages and their descriptions. + #[cfg(not(target_arch = "wasm32"))] pub fn local_packages(&self) -> EcoVec { crate::package::list_package_by_namespace(&self.world.registry, eco_format!("local")) .into_iter() @@ -681,6 +680,12 @@ impl SharedContext { .collect() } + /// Get the local packages and their descriptions. + #[cfg(target_arch = "wasm32")] + pub fn local_packages(&self) -> EcoVec { + eco_vec![] + } + pub(crate) fn const_eval(rr: ast::Expr<'_>) -> Option { Some(match rr { ast::Expr::None(_) => Value::None, diff --git a/crates/tinymist-query/src/lsp_typst_boundary.rs b/crates/tinymist-query/src/lsp_typst_boundary.rs index 78afc051..96a72f35 100644 --- a/crates/tinymist-query/src/lsp_typst_boundary.rs +++ b/crates/tinymist-query/src/lsp_typst_boundary.rs @@ -31,11 +31,7 @@ pub fn path_to_url(path: &Path) -> anyhow::Result { return untitled_url(untitled); } - Url::from_file_path(path).or_else(|never| { - let _: () = never; - - anyhow::bail!("could not convert path to URI: path: {path:?}",) - }) + url_from_file_path(path) } /// Convert a path resolution to a URL. @@ -54,9 +50,7 @@ pub fn url_to_path(uri: Url) -> PathBuf { return PathBuf::from("/untitled/nEoViM-BuG"); } - return uri - .to_file_path() - .unwrap_or_else(|_| panic!("could not convert URI to path: URI: {uri:?}",)); + return url_to_file_path(&uri); } if uri.scheme() == "untitled" { @@ -73,7 +67,40 @@ pub fn url_to_path(uri: Url) -> PathBuf { return Path::new(String::from_utf8_lossy(&bytes).as_ref()).clean(); } - uri.to_file_path().unwrap() + url_to_file_path(&uri) +} + +#[cfg(not(target_arch = "wasm32"))] +fn url_from_file_path(path: &Path) -> anyhow::Result { + Url::from_file_path(path).or_else(|never| { + let _: () = never; + + anyhow::bail!("could not convert path to URI: path: {path:?}",) + }) +} + +#[cfg(target_arch = "wasm32")] +fn url_from_file_path(path: &Path) -> anyhow::Result { + // In WASM, create a simple file:// URL + let path_str = path.to_string_lossy(); + let url_str = if path_str.starts_with('/') { + format!("file://{}", path_str) + } else { + format!("file:///{}", path_str) + }; + Url::parse(&url_str).map_err(|e| anyhow::anyhow!("could not convert path to URI: {}", e)) +} + +#[cfg(not(target_arch = "wasm32"))] +fn url_to_file_path(uri: &Url) -> PathBuf { + uri.to_file_path() + .unwrap_or_else(|_| panic!("could not convert URI to path: URI: {uri:?}",)) +} + +#[cfg(target_arch = "wasm32")] +fn url_to_file_path(uri: &Url) -> PathBuf { + // In WASM, manually parse the URL path + PathBuf::from(uri.path()) } #[cfg(test)] diff --git a/crates/tinymist-query/src/package.rs b/crates/tinymist-query/src/package.rs index 39cdfc17..38d64ad1 100644 --- a/crates/tinymist-query/src/package.rs +++ b/crates/tinymist-query/src/package.rs @@ -1,14 +1,10 @@ //! Package management tools. -use std::collections::HashSet; use std::path::PathBuf; -use std::sync::OnceLock; -use ecow::{eco_format, eco_vec, EcoVec}; -use parking_lot::Mutex; +use ecow::eco_format; // use reflexo_typst::typst::prelude::*; use serde::{Deserialize, Serialize}; -use tinymist_world::package::registry::HttpRegistry; use tinymist_world::package::PackageSpec; use typst::diag::{EcoString, StrResult}; use typst::syntax::package::PackageManifest; @@ -77,11 +73,47 @@ pub fn check_package(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<() Ok(()) } +#[cfg(not(target_arch = "wasm32"))] /// Get the packages in namespaces and their descriptions. pub fn list_package_by_namespace( - registry: &HttpRegistry, + registry: &tinymist_world::package::registry::HttpRegistry, ns: EcoString, -) -> EcoVec<(PathBuf, PackageSpec)> { +) -> ecow::EcoVec<(PathBuf, PackageSpec)> { + use std::collections::HashSet; + use std::sync::OnceLock; + + use ecow::eco_vec; + use parking_lot::Mutex; + + trait IsDirFollowLinks { + fn is_dir_follow_links(&self) -> bool; + } + + impl IsDirFollowLinks for PathBuf { + fn is_dir_follow_links(&self) -> bool { + // Although `canonicalize` is heavy, we must use it because `symlink_metadata` + // is not reliable. + self.canonicalize() + .map(|meta| meta.is_dir()) + .unwrap_or(false) + } + } + + fn once_log(result: Result, site: &'static str) -> Option { + let err = match result { + Ok(value) => return Some(value), + Err(err) => err, + }; + + static ONCE: OnceLock>> = OnceLock::new(); + let mut once = ONCE.get_or_init(Default::default).lock(); + if once.insert(site) { + log::error!("failed to perform {site}: {err}"); + } + + None + } + // search packages locally. We only search in the data // directory and not the cache directory, because the latter is not // intended for storage of local packages. @@ -148,32 +180,3 @@ pub fn list_package_by_namespace( packages } - -trait IsDirFollowLinks { - fn is_dir_follow_links(&self) -> bool; -} - -impl IsDirFollowLinks for PathBuf { - fn is_dir_follow_links(&self) -> bool { - // Although `canonicalize` is heavy, we must use it because `symlink_metadata` - // is not reliable. - self.canonicalize() - .map(|meta| meta.is_dir()) - .unwrap_or(false) - } -} - -fn once_log(result: Result, site: &'static str) -> Option { - let err = match result { - Ok(value) => return Some(value), - Err(err) => err, - }; - - static ONCE: OnceLock>> = OnceLock::new(); - let mut once = ONCE.get_or_init(Default::default).lock(); - if once.insert(site) { - log::error!("failed to perform {site}: {err}"); - } - - None -} diff --git a/crates/tinymist-world/src/world.rs b/crates/tinymist-world/src/world.rs index e39978d7..f1ea5725 100644 --- a/crates/tinymist-world/src/world.rs +++ b/crates/tinymist-world/src/world.rs @@ -814,7 +814,6 @@ impl World for CompilerWorld { if let Some(timestamp) = self.creation_timestamp { tinymist_std::time::UtcDateTime::from_unix_timestamp(timestamp) .unwrap_or_else(|_| now().into()) - .into() } else { now().into() } diff --git a/crates/typlite/Cargo.toml b/crates/typlite/Cargo.toml index b2b2d839..1a04bc75 100644 --- a/crates/typlite/Cargo.toml +++ b/crates/typlite/Cargo.toml @@ -51,10 +51,13 @@ clap = ["dep:clap"] # Note: this is the feature for typlite as a CLI, not for others. # `docx` is enabled in CLI mode, but not in library mode. # `fonts` is enabled in CLI mode. -cli = ["clap", "clap/wrap_help", "docx", "fonts"] +cli = ["clap", "clap/wrap_help", "docx", "fonts", "system"] no-content-hint = ["tinymist-project/no-content-hint"] docx = ["docx-rs", "image", "resvg"] +system = ["tinymist-project/system"] +web = ["tinymist-project/web"] + # Embeds Typst's default fonts for # - text (Linux Libertine), # - math (New Computer Modern Math), and