feat: prepublish tinymist-world (#1248)

This commit is contained in:
Myriad-Dreamin 2025-02-02 14:51:49 +08:00 committed by GitHub
parent d534325c83
commit 88912bc12e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 220 additions and 78 deletions

View file

@ -17,13 +17,19 @@ crate-type = ["cdylib", "rlib"]
[features]
default = ["web", "no-content-hint"]
web = ["wasm-bindgen", "no-content-hint", "tinymist-world/web"]
web = [
"wasm-bindgen",
"no-content-hint",
"tinymist-world/web",
"reflexo-typst/web",
]
no-content-hint = ["tinymist-world/no-content-hint"]
no-content-hint = ["reflexo-typst/no-content-hint"]
[dependencies]
wasm-bindgen = { version = "0.2.92", optional = true }
tinymist-world.workspace = true
reflexo-typst.workspace = true
[build-dependencies]
anyhow.workspace = true

View file

@ -40,8 +40,7 @@ notify.workspace = true
[features]
fonts = ["typst-assets/fonts"]
# "reflexo-typst/no-content-hint"
no-content-hint = []
no-content-hint = ["reflexo-typst/no-content-hint"]
[lints]
workspace = true

View file

@ -56,23 +56,14 @@ pub trait WorldProvider {
impl WorldProvider for CompileOnceArgs {
fn resolve(&self) -> Result<LspUniverse> {
let entry = self.entry()?.try_into()?;
let inputs = self
.inputs
.iter()
.map(|(k, v)| (Str::from(k.as_str()), Value::Str(Str::from(v.as_str()))))
.collect();
let fonts = LspUniverseBuilder::resolve_fonts(self.font.clone())?;
let inputs = self.resolve_inputs().unwrap_or_default();
let fonts = Arc::new(LspUniverseBuilder::resolve_fonts(self.font.clone())?);
let package = LspUniverseBuilder::resolve_package(
self.cert.as_deref().map(From::from),
Some(&self.package),
);
Ok(LspUniverseBuilder::build(
entry,
Arc::new(LazyHash::new(inputs)),
Arc::new(fonts),
package,
))
Ok(LspUniverseBuilder::build(entry, inputs, fonts, package))
}
fn entry(&self) -> Result<EntryOpts> {

View file

@ -11,10 +11,6 @@ homepage.workspace = true
repository.workspace = true
rust-version.workspace = true
[features]
default = ["no-content-hint"]
no-content-hint = []
[dependencies]
anyhow.workspace = true
@ -59,7 +55,7 @@ hashbrown.workspace = true
triomphe.workspace = true
base64.workspace = true
typlite.workspace = true
tinymist-world = { workspace = true, features = ["no-content-hint"] }
tinymist-world = { workspace = true }
tinymist-project.workspace = true
tinymist-analysis.workspace = true
tinymist-derive.workspace = true
@ -74,5 +70,8 @@ typst-assets = { workspace = true, features = ["fonts"] }
sha2 = { version = "0.10" }
hex = { version = "0.4" }
[features]
no-content-hint = ["tinymist-project/no-content-hint"]
[lints]
workspace = true

View file

@ -1,7 +1,6 @@
//! Additional functions wrapping Rust's standard library.
pub mod adt;
pub use tinymist_analysis::debug_loc;
pub mod error;
pub mod fs;
pub mod hash;
@ -14,6 +13,8 @@ pub use concepts::*;
pub use error::{ErrKind, Error, Result};
#[cfg(feature = "typst")]
pub use tinymist_analysis::debug_loc;
#[cfg(feature = "typst")]
pub use typst_shim;

View file

@ -1,3 +1,3 @@
# tinymist-vfs
Vfs for [tinymist.](https://github.com/Myriad-Dreamin/tinymist)
Virtual file system for [tinymist.](https://github.com/Myriad-Dreamin/tinymist)

View file

@ -41,28 +41,31 @@ mod utils;
mod path_mapper;
pub use path_mapper::{PathResolution, RootResolver, WorkspaceResolution, WorkspaceResolver};
use rpds::RedBlackTreeMapSync;
pub use typst::foundations::Bytes;
pub use typst::syntax::FileId as TypstFileId;
pub use tinymist_std::time::Time;
pub use tinymist_std::ImmutPath;
use typst::syntax::Source;
use core::fmt;
use std::num::NonZeroUsize;
use std::sync::OnceLock;
use std::{path::Path, sync::Arc};
use parking_lot::{Mutex, RwLock};
use rpds::RedBlackTreeMapSync;
use typst::diag::{FileError, FileResult};
use typst::foundations::Dict;
use typst::syntax::Source;
use typst::utils::LazyHash;
use crate::notify::NotifyAccessModel;
use crate::overlay::OverlayAccessModel;
use crate::resolve::ResolveAccessModel;
pub use tinymist_std::time::Time;
pub use tinymist_std::ImmutPath;
pub use typst::foundations::Bytes;
pub use typst::syntax::FileId as TypstFileId;
/// Handle to a file in [`Vfs`]
pub type FileId = TypstFileId;
/// Immutable prehashed reference to dictionary.
pub type ImmutDict = Arc<LazyHash<Dict>>;
/// A trait for accessing underlying file system.
///

View file

@ -26,7 +26,6 @@ hex.workspace = true
js-sys = { workspace = true, optional = true }
log.workspace = true
parking_lot.workspace = true
reflexo-typst = { workspace = true, default-features = false }
reqwest = { workspace = true, optional = true }
serde.workspace = true
serde_json.workspace = true
@ -54,7 +53,6 @@ web = [
"serde-wasm-bindgen",
"tinymist-std/web",
"tinymist-vfs/web",
"reflexo-typst/web",
]
browser = ["tinymist-vfs/browser", "web"]
system = [
@ -63,23 +61,10 @@ system = [
"http-registry",
"tinymist-std/system",
"tinymist-vfs/system",
"reflexo-typst/system",
]
# todo: remove me
no-content-hint = ["reflexo-typst/no-content-hint"]
[dev-dependencies]
tinymist-world = { path = ".", features = ["system"] }
[lints]
workspace = true
# ====
# [dependencies]
# [features]
# fonts = ["typst-assets/fonts"]
# no-content-hint = ["reflexo-typst/no-content-hint"]
# [lints]
# workspace = true

View file

@ -1,3 +1,24 @@
# tinymist-world
Typst's World implementation for [tinymist.](https://github.com/Myriad-Dreamin/tinymist)
### Example: Resolves a system universe from system arguments
```rust
let args = CompileOnceArgs::parse();
let universe = args
.resolve_system()
.expect("failed to resolve system universe");
```
### Example: Runs a typst compilation
```rust
let world = verse.snapshot();
// in current thread
let doc = typst::compile(&world)?;
// the snapshot is Send + Sync
std::thread::spawn(move || {
let doc = typst::compile(&world)?;
});
```

View file

@ -1,8 +1,16 @@
use std::path::PathBuf;
use std::{
path::{Path, PathBuf},
sync::Arc,
};
use chrono::{DateTime, Utc};
use clap::{builder::ValueParser, ArgAction, Parser};
use serde::{Deserialize, Serialize};
use tinymist_std::{bail, error::prelude::*};
use tinymist_vfs::ImmutDict;
use typst::{foundations::IntoValue, utils::LazyHash};
use crate::EntryOpts;
const ENV_PATH_SEP: char = if cfg!(windows) { ';' } else { ':' };
@ -88,6 +96,84 @@ pub struct CompileOnceArgs {
pub cert: Option<PathBuf>,
}
impl CompileOnceArgs {
pub fn resolve_inputs(&self) -> Option<ImmutDict> {
if self.inputs.is_empty() {
return None;
}
let pairs = self.inputs.iter();
let pairs = pairs.map(|(k, v)| (k.as_str().into(), v.as_str().into_value()));
Some(Arc::new(LazyHash::new(pairs.collect())))
}
/// Resolves the entry options.
pub fn resolve_sys_entry_opts(&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 CompileOnceArgs {
/// Resolves the arguments into a system universe. This is also a sample
/// implementation of how to resolve the arguments (user inputs) into a
/// universe.
pub fn resolve_system(&self) -> Result<crate::TypstSystemUniverse> {
use crate::system::SystemUniverseBuilder;
let entry = self.resolve_sys_entry_opts()?.try_into()?;
let inputs = self.resolve_inputs().unwrap_or_default();
let fonts = Arc::new(SystemUniverseBuilder::resolve_fonts(self.font.clone())?);
let package = SystemUniverseBuilder::resolve_package(
self.cert.as_deref().map(From::from),
Some(&self.package),
);
Ok(SystemUniverseBuilder::build(entry, inputs, fonts, package))
}
}
/// Parses key/value pairs split by the first equal sign.
///
/// This function will return an error if the argument contains no equals sign

View file

@ -146,3 +146,6 @@ pub mod build_info {
/// The version of the reflexo-world crate.
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
}
#[cfg(test)]
mod tests;

View file

@ -1,13 +1,15 @@
use std::sync::Arc;
use std::{borrow::Cow, sync::Arc};
use tinymist_std::error::prelude::*;
use tinymist_vfs::{system::SystemAccessModel, Vfs};
use tinymist_std::{error::prelude::*, ImmutPath};
use tinymist_vfs::{system::SystemAccessModel, ImmutDict, Vfs};
use typst::utils::LazyHash;
use crate::{
config::CompileOpts,
args::{CompileFontArgs, CompilePackageArgs},
config::{CompileFontOpts, CompileOpts},
font::{system::SystemFontSearcher, FontResolverImpl},
package::{http::HttpRegistry, RegistryPathMapper},
EntryState,
};
/// type trait of [`TypstSystemWorld`].
@ -52,3 +54,52 @@ impl TypstSystemUniverse {
Ok(searcher.into())
}
}
/// Builders for Typst universe.
pub struct SystemUniverseBuilder;
impl SystemUniverseBuilder {
/// Create [`TypstSystemUniverse`] with the given options.
/// See [`LspCompilerFeat`] for instantiation details.
pub fn build(
entry: EntryState,
inputs: ImmutDict,
font_resolver: Arc<FontResolverImpl>,
package_registry: HttpRegistry,
) -> TypstSystemUniverse {
let registry = Arc::new(package_registry);
let resolver = Arc::new(RegistryPathMapper::new(registry.clone()));
TypstSystemUniverse::new_raw(
entry,
Some(inputs),
Vfs::new(resolver, SystemAccessModel {}),
registry,
font_resolver,
)
}
/// Resolve fonts from given options.
pub fn resolve_fonts(args: CompileFontArgs) -> Result<FontResolverImpl> {
let mut searcher = SystemFontSearcher::new();
searcher.resolve_opts(CompileFontOpts {
font_profile_cache_path: Default::default(),
font_paths: args.font_paths,
no_system_fonts: args.ignore_system_fonts,
with_embedded_fonts: typst_assets::fonts().map(Cow::Borrowed).collect(),
})?;
Ok(searcher.into())
}
/// Resolve package registry from given options.
pub fn resolve_package(
cert_path: Option<ImmutPath>,
args: Option<&CompilePackageArgs>,
) -> HttpRegistry {
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())),
)
}
}

View file

@ -0,0 +1,15 @@
use clap::Parser;
use crate::args::CompileOnceArgs;
#[test]
#[cfg(feature = "system")]
fn test_args() {
let args = CompileOnceArgs::parse_from(["tinymist", "main.typ"]);
let verse = args
.resolve_system()
.expect("failed to resolve system universe");
let world = verse.snapshot();
let _res = typst::compile(&world);
}

View file

@ -100,6 +100,7 @@ embed-fonts = ["tinymist-project/fonts"]
# Disable the default content hint.
# This requires modifying typst.
no-content-hint = [
"tinymist-query/no-content-hint",
"tinymist-project/no-content-hint",
"reflexo-typst/no-content-hint",
"reflexo-vec2svg/no-content-hint",

View file

@ -596,20 +596,11 @@ impl CompileConfig {
Err(e) => bail!("failed to parse typstExtraArgs: {e}"),
};
// Convert the input pairs to a dictionary.
let inputs: TypstDict = if command.inputs.is_empty() {
TypstDict::default()
} else {
let pairs = command.inputs.iter();
let pairs = pairs.map(|(k, v)| (k.as_str().into(), v.as_str().into_value()));
pairs.collect()
};
// todo: the command.root may be not absolute
self.typst_extra_args = Some(CompileExtraOpts {
inputs: command.resolve_inputs().unwrap_or_default(),
entry: command.input.map(|e| Path::new(&e).into()),
root_dir: command.root.as_ref().map(|r| r.as_path().into()),
inputs: Arc::new(LazyHash::new(inputs)),
font: command.font,
package: command.package,
creation_timestamp: command.creation_timestamp,

View file

@ -6,7 +6,6 @@ use std::{
io,
path::{Path, PathBuf},
str::FromStr,
sync::Arc,
};
use clap::Parser;
@ -16,7 +15,7 @@ use futures::future::MaybeDone;
use lsp_server::RequestId;
use once_cell::sync::Lazy;
use reflexo::ImmutPath;
use reflexo_typst::{package::PackageSpec, TypstDict};
use reflexo_typst::package::PackageSpec;
use serde_json::Value as JsonValue;
use sync_lsp::{
internal_error,
@ -32,8 +31,6 @@ use tinymist_core::LONG_VERSION;
use tinymist_project::EntryResolver;
use tinymist_query::package::PackageInfo;
use tinymist_std::{bail, error::prelude::*};
use typst::foundations::IntoValue;
use typst_shim::utils::LazyHash;
use crate::args::*;
@ -147,11 +144,11 @@ pub fn lsp_main(args: LspArgs) -> Result<()> {
/// The main entry point for the compiler.
pub fn trace_lsp_main(args: TraceLspArgs) -> Result<()> {
let inputs = args.compile.resolve_inputs();
let mut input = PathBuf::from(match args.compile.input {
Some(value) => value,
None => Err(anyhow::anyhow!("provide a valid path"))?,
});
let mut root_path = args.compile.root.unwrap_or(PathBuf::from("."));
if root_path.is_relative() {
@ -164,14 +161,6 @@ pub fn trace_lsp_main(args: TraceLspArgs) -> Result<()> {
bail!("input file is not within the root path: {input:?} not in {root_path:?}");
}
let inputs = Arc::new(LazyHash::new(if args.compile.inputs.is_empty() {
TypstDict::default()
} else {
let pairs = args.compile.inputs.iter();
let pairs = pairs.map(|(k, v)| (k.as_str().into(), v.as_str().into_value()));
pairs.collect()
}));
with_stdio_transport(args.mirror.clone(), |conn| {
let client_root = LspClientRoot::new(RUNTIMES.tokio_runtime.handle().clone(), conn.sender);
let client = client_root.weak();
@ -227,7 +216,7 @@ pub fn trace_lsp_main(args: TraceLspArgs) -> Result<()> {
RUNTIMES.tokio_runtime.block_on(async {
let w = snap.world.task(TaskInputs {
entry: Some(entry),
inputs: Some(inputs),
inputs,
});
UserActionTask::trace_main(client, state, &w, args.rpc_kind, req_id).await