refactor: create main files (#2054)

This commit is contained in:
Myriad-Dreamin 2025-08-24 06:12:11 +08:00 committed by GitHub
parent 846e6ffbbf
commit 5ee01b59a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 798 additions and 742 deletions

View file

@ -1,238 +0,0 @@
use std::path::Path;
use sync_ls::transport::MirrorArgs;
use tinymist::LONG_VERSION;
use tinymist::project::DocCommands;
use tinymist::{CompileFontArgs, CompileOnceArgs};
#[cfg(feature = "preview")]
use tinymist::tool::preview::PreviewArgs;
#[cfg(feature = "preview")]
use tinymist_project::DocNewArgs;
#[cfg(feature = "preview")]
use tinymist_task::TaskWhen;
use crate::compile::CompileArgs;
use crate::generate_script::GenerateScriptArgs;
use crate::testing::TestArgs;
#[derive(Debug, Clone, clap::Parser)]
#[clap(name = "tinymist", author, version, about, long_version(LONG_VERSION.as_str()))]
pub struct CliArguments {
/// Mode of the binary
#[clap(subcommand)]
pub command: Option<Commands>,
}
#[derive(Debug, Clone, clap::Subcommand)]
#[clap(rename_all = "kebab-case")]
pub enum Commands {
/// Probes existence (Nop run)
Probe,
/// Generates completion script to stdout
Completion(ShellCompletionArgs),
/// Runs language server
Lsp(LspArgs),
/// Runs debug adapter
Dap(DapArgs),
/// Runs language server for tracing some typst program.
#[clap(hide(true))]
TraceLsp(TraceLspArgs),
/// Runs preview server
#[cfg(feature = "preview")]
Preview(tinymist::tool::preview::PreviewCliArgs),
/// Execute a document and collect coverage
#[clap(hide(true))] // still in development
Cov(CompileOnceArgs),
/// Test a document and gives summary
Test(TestArgs),
/// Runs compile command like `typst-cli compile`
Compile(CompileArgs),
/// Generates build script for compilation
#[clap(hide(true))] // still in development
GenerateScript(GenerateScriptArgs),
/// Runs language query
#[clap(hide(true))] // still in development
#[clap(subcommand)]
Query(QueryCommands),
/// Runs documents
#[clap(hide(true))] // still in development
#[clap(subcommand)]
Doc(DocCommands),
/// Runs tasks
#[clap(hide(true))] // still in development
#[clap(subcommand)]
Task(TaskCommands),
}
impl Default for Commands {
fn default() -> Self {
Self::Lsp(LspArgs::default())
}
}
#[derive(Debug, Clone, clap::Parser)]
pub struct ShellCompletionArgs {
/// The shell to generate the completion script for. If not provided, it
/// will be inferred from the environment.
#[clap(value_enum)]
pub shell: Option<Shell>,
}
#[allow(clippy::enum_variant_names)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)]
#[clap(rename_all = "lowercase")]
pub enum Shell {
Bash,
Elvish,
Fig,
Fish,
PowerShell,
Zsh,
Nushell,
}
impl Shell {
pub fn from_env() -> Option<Self> {
if let Some(env_shell) = std::env::var_os("SHELL") {
let name = Path::new(&env_shell).file_stem()?.to_str()?;
match name {
"bash" => Some(Shell::Bash),
"zsh" => Some(Shell::Zsh),
"fig" => Some(Shell::Fig),
"fish" => Some(Shell::Fish),
"elvish" => Some(Shell::Elvish),
"powershell" | "powershell_ise" => Some(Shell::PowerShell),
"nushell" => Some(Shell::Nushell),
_ => None,
}
} else if cfg!(windows) {
Some(Shell::PowerShell)
} else {
None
}
}
}
impl clap_complete::Generator for Shell {
fn file_name(&self, name: &str) -> String {
use clap_complete::shells::{Bash, Elvish, Fish, PowerShell, Zsh};
use clap_complete_fig::Fig;
use clap_complete_nushell::Nushell;
match self {
Shell::Bash => Bash.file_name(name),
Shell::Elvish => Elvish.file_name(name),
Shell::Fig => Fig.file_name(name),
Shell::Fish => Fish.file_name(name),
Shell::PowerShell => PowerShell.file_name(name),
Shell::Zsh => Zsh.file_name(name),
Shell::Nushell => Nushell.file_name(name),
}
}
fn generate(&self, cmd: &clap::Command, buf: &mut dyn std::io::Write) {
use clap_complete::shells::{Bash, Elvish, Fish, PowerShell, Zsh};
use clap_complete_fig::Fig;
use clap_complete_nushell::Nushell;
match self {
Shell::Bash => Bash.generate(cmd, buf),
Shell::Elvish => Elvish.generate(cmd, buf),
Shell::Fig => Fig.generate(cmd, buf),
Shell::Fish => Fish.generate(cmd, buf),
Shell::PowerShell => PowerShell.generate(cmd, buf),
Shell::Zsh => Zsh.generate(cmd, buf),
Shell::Nushell => Nushell.generate(cmd, buf),
}
}
}
#[derive(Debug, Clone, Default, clap::Parser)]
pub struct TraceLspArgs {
#[clap(long, default_value = "false")]
pub persist: bool,
// lsp or http
#[clap(long, default_value = "lsp")]
pub rpc_kind: String,
#[clap(flatten)]
pub mirror: MirrorArgs,
#[clap(flatten)]
pub compile: CompileOnceArgs,
}
#[derive(Debug, Clone, Default, clap::Parser)]
pub struct LspArgs {
#[clap(flatten)]
pub mirror: MirrorArgs,
#[clap(flatten)]
pub font: CompileFontArgs,
}
pub type DapArgs = LspArgs;
#[derive(Debug, Clone, clap::Subcommand)]
#[clap(rename_all = "camelCase")]
pub enum QueryCommands {
/// Get the documentation for a specific package.
PackageDocs(PackageDocsArgs),
/// Check a specific package.
CheckPackage(PackageDocsArgs),
}
#[derive(Debug, Clone, clap::Parser)]
pub struct PackageDocsArgs {
/// The path of the package to request docs for.
#[clap(long)]
pub path: Option<String>,
/// The package of the package to request docs for.
#[clap(long)]
pub id: String,
/// The output path for the requested docs.
#[clap(short, long)]
pub output: String,
// /// The format of requested docs.
// #[clap(long)]
// pub format: Option<QueryDocsFormat>,
}
#[derive(Debug, Clone, Default, clap::ValueEnum)]
#[clap(rename_all = "camelCase")]
pub enum QueryDocsFormat {
#[default]
Json,
Markdown,
}
/// Project task commands.
#[derive(Debug, Clone, clap::Subcommand)]
#[clap(rename_all = "kebab-case")]
pub enum TaskCommands {
/// Declare a preview task.
#[cfg(feature = "preview")]
Preview(TaskPreviewArgs),
}
/// Declare an lsp task.
#[derive(Debug, Clone, clap::Parser)]
#[cfg(feature = "preview")]
pub struct TaskPreviewArgs {
/// Argument to identify a project.
#[clap(flatten)]
pub declare: DocNewArgs,
/// Name a task.
#[clap(long = "task")]
pub task_name: Option<String>,
/// When to run the task
#[arg(long = "when")]
pub when: Option<TaskWhen>,
/// Preview arguments
#[clap(flatten)]
pub preview: PreviewArgs,
}

View file

@ -2,13 +2,11 @@
use std::path::PathBuf;
use reflexo::ImmutPath;
use reflexo_typst::WorldComputeGraph;
use tinymist_std::error::prelude::*;
use tinymist::ExportTask;
use tinymist::project::*;
use tinymist::world::WorldComputeGraph;
use tinymist::world::system::print_diagnostics;
use tinymist_std::{ImmutPath, error::prelude::*};
/// Arguments for project compilation.
#[derive(Debug, Clone, clap::Parser)]

View file

@ -0,0 +1,97 @@
use std::io;
use std::path::Path;
use clap::CommandFactory;
use clap_complete::generate;
use tinymist_std::error::prelude::*;
/// Arguments for shell completion.
#[derive(Debug, Clone, clap::Parser)]
pub struct ShellCompletionArgs {
/// The shell to generate the completion script for. If not provided, it
/// will be inferred from the environment.
#[clap(value_enum)]
pub shell: Option<Shell>,
}
/// Generates completion script to stdout.
pub fn completion_main(args: ShellCompletionArgs) -> Result<()> {
let Some(shell) = args.shell.or_else(Shell::from_env) else {
tinymist_std::bail!("could not infer shell");
};
let mut cmd = crate::Args::command();
generate(shell, &mut cmd, "tinymist", &mut io::stdout());
Ok(())
}
#[allow(clippy::enum_variant_names)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)]
#[clap(rename_all = "lowercase")]
pub enum Shell {
Bash,
Elvish,
Fig,
Fish,
PowerShell,
Zsh,
Nushell,
}
impl Shell {
pub fn from_env() -> Option<Self> {
if let Some(env_shell) = std::env::var_os("SHELL") {
let name = Path::new(&env_shell).file_stem()?.to_str()?;
match name {
"bash" => Some(Shell::Bash),
"zsh" => Some(Shell::Zsh),
"fig" => Some(Shell::Fig),
"fish" => Some(Shell::Fish),
"elvish" => Some(Shell::Elvish),
"powershell" | "powershell_ise" => Some(Shell::PowerShell),
"nushell" => Some(Shell::Nushell),
_ => None,
}
} else if cfg!(windows) {
Some(Shell::PowerShell)
} else {
None
}
}
}
impl clap_complete::Generator for Shell {
fn file_name(&self, name: &str) -> String {
use clap_complete::shells::{Bash, Elvish, Fish, PowerShell, Zsh};
use clap_complete_fig::Fig;
use clap_complete_nushell::Nushell;
match self {
Shell::Bash => Bash.file_name(name),
Shell::Elvish => Elvish.file_name(name),
Shell::Fig => Fig.file_name(name),
Shell::Fish => Fish.file_name(name),
Shell::PowerShell => PowerShell.file_name(name),
Shell::Zsh => Zsh.file_name(name),
Shell::Nushell => Nushell.file_name(name),
}
}
fn generate(&self, cmd: &clap::Command, buf: &mut dyn std::io::Write) {
use clap_complete::shells::{Bash, Elvish, Fish, PowerShell, Zsh};
use clap_complete_fig::Fig;
use clap_complete_nushell::Nushell;
match self {
Shell::Bash => Bash.generate(cmd, buf),
Shell::Elvish => Elvish.generate(cmd, buf),
Shell::Fig => Fig.generate(cmd, buf),
Shell::Fish => Fish.generate(cmd, buf),
Shell::PowerShell => PowerShell.generate(cmd, buf),
Shell::Zsh => Zsh.generate(cmd, buf),
Shell::Nushell => Nushell.generate(cmd, buf),
}
}
}

View file

@ -0,0 +1,36 @@
use std::path::Path;
use reflexo_typst::TypstPagedDocument;
use tinymist::CompileOnceArgs;
use tinymist_project::WorldProvider;
use tinymist_std::error::prelude::*;
use crate::print_diag_or_error;
/// Coverage Testing arguments
#[derive(Debug, Clone, clap::Parser)]
pub struct CovArgs {
/// The argument to compile once.
#[clap(flatten)]
pub compile: CompileOnceArgs,
}
/// Runs coverage test on a document
pub fn cov_main(args: CovArgs) -> Result<()> {
// Prepares for the compilation
let universe = args.compile.resolve()?;
let world = universe.snapshot();
let result = Ok(()).and_then(|_| -> Result<()> {
let res = tinymist_debug::collect_coverage::<TypstPagedDocument, _>(&world)?;
let cov_path = Path::new("target/coverage.json");
let res = serde_json::to_string(&res.to_json(&world)).context("coverage")?;
std::fs::create_dir_all(cov_path.parent().context("parent")?).context("create coverage")?;
std::fs::write(cov_path, res).context("write coverage")?;
Ok(())
});
print_diag_or_error(&world, result)
}

View file

@ -0,0 +1,37 @@
use sync_ls::transport::with_stdio_transport;
use sync_ls::{DapBuilder, DapMessage};
use tinymist::LONG_VERSION;
use tinymist::ServerState;
use tinymist_std::error::prelude::*;
use crate::*;
/// Arguments for dap.
pub type DapArgs = crate::lsp::LspArgs;
/// The main entry point for the language server.
pub fn dap_main(args: DapArgs) -> Result<()> {
let pairs = LONG_VERSION.trim().split('\n');
let pairs = pairs
.map(|e| e.splitn(2, ":").map(|e| e.trim()).collect::<Vec<_>>())
.collect::<Vec<_>>();
log::info!("tinymist version information: {pairs:?}");
log::info!("starting debug adaptor: {args:?}");
let is_replay = !args.mirror.replay.is_empty();
with_stdio_transport::<DapMessage>(args.mirror.clone(), |conn| {
let client = client_root(conn.sender);
ServerState::install_dap(DapBuilder::new(
tinymist::DapRegularInit {
client: client.weak().to_typed(),
font_opts: args.font,
},
client.weak(),
))
.build()
.start(conn.receiver, is_replay)
})?;
log::info!("language server did shut down");
Ok(())
}

View file

@ -0,0 +1,35 @@
use std::path::Path;
#[cfg(feature = "preview")]
use tinymist_project::LockFile;
use tinymist_std::error::prelude::*;
#[cfg(feature = "preview")]
use tinymist_task::Id;
/// Project document commands' main
#[cfg(feature = "lock")]
pub fn doc_main(args: tinymist_project::DocCommands) -> Result<()> {
use tinymist_project::DocCommands;
let cwd = std::env::current_dir().context("cannot get cwd")?;
LockFile::update(&cwd, |state| {
let ctx: (&Path, &Path) = (&cwd, &cwd);
match args {
DocCommands::New(args) => {
state.replace_document(args.to_input(ctx));
}
DocCommands::Configure(args) => {
use tinymist_project::ProjectRoute;
let id: Id = args.id.id(ctx);
state.route.push(ProjectRoute {
id: id.clone(),
priority: args.priority,
});
}
}
Ok(())
})
}

View file

@ -2,11 +2,12 @@
use std::{borrow::Cow, path::Path};
use clap_complete::Shell;
use reflexo::path::unix_slash;
use tinymist::project::*;
use tinymist_std::path::unix_slash;
use tinymist_std::{bail, error::prelude::*};
use crate::completion::Shell;
/// Arguments for generating a build script.
#[derive(Debug, Clone, clap::Parser)]
pub struct GenerateScriptArgs {

View file

@ -0,0 +1,46 @@
use sync_ls::transport::{MirrorArgs, with_stdio_transport};
use sync_ls::{LspBuilder, LspMessage};
use tinymist::LONG_VERSION;
use tinymist::world::CompileFontArgs;
use tinymist::{RegularInit, ServerState};
use crate::*;
/// Arguments for starting LSP server.
#[derive(Debug, Clone, Default, clap::Parser)]
pub struct LspArgs {
/// Arguments for mirroring the transport.
#[clap(flatten)]
pub mirror: MirrorArgs,
/// Arguments for font.
#[clap(flatten)]
pub font: CompileFontArgs,
}
/// The main entry point for the language server.
pub fn lsp_main(args: LspArgs) -> Result<()> {
let pairs = LONG_VERSION.trim().split('\n');
let pairs = pairs
.map(|e| e.splitn(2, ":").map(|e| e.trim()).collect::<Vec<_>>())
.collect::<Vec<_>>();
log::info!("tinymist version information: {pairs:?}");
log::info!("starting language server: {args:?}");
let is_replay = !args.mirror.replay.is_empty();
with_stdio_transport::<LspMessage>(args.mirror.clone(), |conn| {
let client = client_root(conn.sender);
ServerState::install_lsp(LspBuilder::new(
RegularInit {
client: client.weak().to_typed(),
font_opts: args.font,
exec_cmds: Vec::new(),
},
client.weak(),
))
.build()
.start(conn.receiver, is_replay)
})?;
log::info!("language server did shut down");
Ok(())
}

View file

@ -0,0 +1,122 @@
use std::path::{Path, PathBuf};
use std::str::FromStr;
use futures::future::MaybeDone;
use reflexo_typst::package::PackageSpec;
use sync_ls::transport::{MirrorArgs, with_stdio_transport};
use sync_ls::{LspBuilder, LspMessage, LspResult, internal_error};
use tinymist::{Config, ServerState, SuperInit};
use tinymist_query::package::PackageInfo;
use tinymist_std::error::prelude::*;
use crate::*;
#[derive(Debug, Clone, clap::Subcommand)]
#[clap(rename_all = "camelCase")]
pub enum QueryCommands {
/// Get the documentation for a specific package.
PackageDocs(PackageDocsArgs),
/// Check a specific package.
CheckPackage(PackageDocsArgs),
}
#[derive(Debug, Clone, clap::Parser)]
pub struct PackageDocsArgs {
/// The path of the package to request docs for.
#[clap(long)]
pub path: Option<String>,
/// The package of the package to request docs for.
#[clap(long)]
pub id: String,
/// The output path for the requested docs.
#[clap(short, long)]
pub output: String,
// /// The format of requested docs.
// #[clap(long)]
// pub format: Option<QueryDocsFormat>,
}
/// The main entry point for language server queries.
pub fn query_main(cmds: QueryCommands) -> Result<()> {
use tinymist_project::package::PackageRegistry;
with_stdio_transport::<LspMessage>(MirrorArgs::default(), |conn| {
let client_root = client_root(conn.sender);
let client = client_root.weak();
// todo: roots, inputs, font_opts
let config = Config::default();
let mut service = ServerState::install_lsp(LspBuilder::new(
SuperInit {
client: client.to_typed(),
exec_cmds: Vec::new(),
config,
err: None,
},
client.clone(),
))
.build();
let resp = service.ready(()).unwrap();
let MaybeDone::Done(resp) = resp else {
anyhow::bail!("internal error: not sync init")
};
resp.unwrap();
let state = service.state_mut().unwrap();
let snap = state.snapshot().unwrap();
let res = RUNTIMES.tokio_runtime.block_on(async move {
match cmds {
QueryCommands::PackageDocs(args) => {
let pkg = PackageSpec::from_str(&args.id).unwrap();
let path = args.path.map(PathBuf::from);
let path = path
.unwrap_or_else(|| snap.registry().resolve(&pkg).unwrap().as_ref().into());
let res = state
.resource_package_docs_(PackageInfo {
path,
namespace: pkg.namespace,
name: pkg.name,
version: pkg.version.to_string(),
})?
.await?;
let output_path = Path::new(&args.output);
std::fs::write(output_path, res).map_err(internal_error)?;
}
QueryCommands::CheckPackage(args) => {
let pkg = PackageSpec::from_str(&args.id).unwrap();
let path = args.path.map(PathBuf::from);
let path = path
.unwrap_or_else(|| snap.registry().resolve(&pkg).unwrap().as_ref().into());
state
.check_package(PackageInfo {
path,
namespace: pkg.namespace,
name: pkg.name,
version: pkg.version.to_string(),
})?
.await?;
}
};
LspResult::Ok(())
});
res.map_err(|e| anyhow::anyhow!("{e:?}"))
})?;
Ok(())
}
#[derive(Debug, Clone, Default, clap::ValueEnum)]
#[clap(rename_all = "camelCase")]
enum QueryDocsFormat {
#[default]
Json,
Markdown,
}

View file

@ -0,0 +1,95 @@
use std::path::Path;
#[cfg(feature = "l10n")]
use tinymist_std::error::prelude::*;
#[cfg(feature = "preview")]
use tinymist::tool::preview::PreviewArgs;
#[cfg(feature = "preview")]
use tinymist_project::DocNewArgs;
#[cfg(feature = "preview")]
use tinymist_project::LockFile;
#[cfg(feature = "preview")]
use tinymist_task::Id;
#[cfg(feature = "preview")]
use tinymist_task::TaskWhen;
/// Project task commands.
#[derive(Debug, Clone, clap::Subcommand)]
#[clap(rename_all = "kebab-case")]
pub enum TaskCommands {
/// Declare a preview task.
#[cfg(feature = "preview")]
Preview(TaskPreviewArgs),
}
/// Declare an lsp task.
#[derive(Debug, Clone, clap::Parser)]
#[cfg(feature = "preview")]
pub struct TaskPreviewArgs {
/// Argument to identify a project.
#[clap(flatten)]
pub declare: DocNewArgs,
/// Name a task.
#[clap(long = "task")]
pub task_name: Option<String>,
/// When to run the task
#[arg(long = "when")]
pub when: Option<TaskWhen>,
/// Preview arguments
#[clap(flatten)]
pub preview: PreviewArgs,
}
/// Project task commands' main
pub fn task_main(args: TaskCommands) -> Result<()> {
let cwd = std::env::current_dir().context("cannot get cwd")?;
LockFile::update(&cwd, |state| {
let _ = state;
match args {
#[cfg(feature = "preview")]
TaskCommands::Preview(args) => {
let ctx: (&Path, &Path) = (&cwd, &cwd);
let input = args.declare.to_input(ctx);
let id = input.id.clone();
state.replace_document(input);
let _ = state.preview(id, &args);
Ok(())
}
}
})
}
#[cfg(feature = "preview")]
trait LockFileExt {
fn preview(&mut self, doc_id: Id, args: &TaskPreviewArgs) -> Result<Id>;
}
#[cfg(feature = "preview")]
impl LockFileExt for LockFile {
fn preview(&mut self, doc_id: Id, args: &TaskPreviewArgs) -> Result<Id> {
use tinymist_task::{ApplyProjectTask, PreviewTask, ProjectTask, TaskWhen};
let task_id = args
.task_name
.as_ref()
.map(|t| Id::new(t.clone()))
.unwrap_or(doc_id.clone());
let when = args.when.clone().unwrap_or(TaskWhen::OnType);
let task = ProjectTask::Preview(PreviewTask { when });
let task = ApplyProjectTask {
id: task_id.clone(),
document: doc_id,
task,
};
self.replace_task(task);
Ok(task_id)
}
}

View file

@ -9,50 +9,30 @@ use std::sync::{Arc, atomic::AtomicBool};
use itertools::Either;
use parking_lot::Mutex;
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use reflexo::ImmutPath;
use reflexo_typst::{TypstDocument, TypstHtmlDocument, vfs::FileId};
use tinymist::project::*;
use tinymist::tool::project::{StartProjectResult, start_project};
use tinymist::world::{SourceWorld, with_main};
use tinymist_debug::CoverageResult;
use tinymist_project::world::{DiagnosticFormat, system::print_diagnostics};
use tinymist_query::analysis::Analysis;
use tinymist_query::syntax::{cast_include_expr, find_source_by_expr, node_ancestors};
use tinymist_query::testing::{TestCaseKind, TestSuites};
use tinymist_std::ImmutPath;
use tinymist_std::typst::{TypstDocument, TypstHtmlDocument};
use tinymist_std::{bail, error::prelude::*, fs::paths::write_atomic, typst::TypstPagedDocument};
use typst::diag::{Severity, SourceDiagnostic};
use typst::ecow::EcoVec;
use typst::foundations::{Context, Label};
use typst::syntax::{LinkedNode, Source, Span, ast};
use typst::syntax::{FileId, LinkedNode, Source, Span, ast};
use typst::{World, utils::PicoStr};
use typst_shim::eval::TypstEngine;
use tinymist::project::*;
use tinymist::tool::project::{StartProjectResult, start_project};
use tinymist::world::{SourceWorld, with_main};
use crate::print_diag_or_error;
use crate::utils::exit_on_ctrl_c;
const TEST_EVICT_MAX_AGE: usize = 30;
const PREFIX_LEN: usize = 7;
/// Runs coverage test on a document
pub fn coverage_main(args: CompileOnceArgs) -> Result<()> {
// Prepares for the compilation
let universe = args.resolve()?;
let world = universe.snapshot();
let result = Ok(()).and_then(|_| -> Result<()> {
let res = tinymist_debug::collect_coverage::<TypstPagedDocument, _>(&world)?;
let cov_path = Path::new("target/coverage.json");
let res = serde_json::to_string(&res.to_json(&world)).context("coverage")?;
std::fs::create_dir_all(cov_path.parent().context("parent")?).context("create coverage")?;
std::fs::write(cov_path, res).context("write coverage")?;
Ok(())
});
print_diag_or_error(&world, result)
}
/// Testing arguments
#[derive(Debug, Clone, clap::Parser)]
pub struct TestArgs {
@ -595,21 +575,6 @@ fn get_example_file(world: &dyn World, name: &str, id: FileId, span: Span) -> Re
find_source_by_expr(world, id, included).context("cannot find example file")
}
fn print_diag_or_error<T>(world: &impl SourceWorld, result: Result<T>) -> Result<T> {
match result {
Ok(v) => Ok(v),
Err(err) => {
if let Some(diagnostics) = err.diagnostics() {
print_diagnostics(world, diagnostics.iter(), DiagnosticFormat::Human)
.context_ut("print diagnostics")?;
bail!("");
}
Err(err)
}
}
}
enum Level {
Error,
Info,

View file

@ -0,0 +1,101 @@
use std::path::PathBuf;
use futures::future::MaybeDone;
use reflexo::ImmutPath;
use sync_ls::transport::{MirrorArgs, with_stdio_transport};
use sync_ls::{LspBuilder, LspMessage, RequestId};
use tinymist::world::TaskInputs;
use tinymist::{CompileOnceArgs, Config, ServerState, SuperInit, UserActionTask};
use tinymist_project::EntryResolver;
use tinymist_std::{bail, error::prelude::*};
use crate::*;
#[derive(Debug, Clone, Default, clap::Parser)]
pub struct TraceLspArgs {
#[clap(long, default_value = "false")]
pub persist: bool,
// lsp or http
#[clap(long, default_value = "lsp")]
pub rpc_kind: String,
#[clap(flatten)]
pub mirror: MirrorArgs,
#[clap(flatten)]
pub compile: CompileOnceArgs,
}
/// 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() {
root_path = std::env::current_dir().context("cwd")?.join(root_path);
}
if input.is_relative() {
input = std::env::current_dir().context("cwd")?.join(input);
}
if !input.starts_with(&root_path) {
bail!("input file is not within the root path: {input:?} not in {root_path:?}");
}
with_stdio_transport::<LspMessage>(args.mirror.clone(), |conn| {
let client_root = client_root(conn.sender);
let client = client_root.weak();
let roots = vec![ImmutPath::from(root_path)];
let config = Config {
entry_resolver: EntryResolver {
roots,
..EntryResolver::default()
},
font_opts: args.compile.font,
..Config::default()
};
let mut service = ServerState::install_lsp(LspBuilder::new(
SuperInit {
client: client.to_typed(),
exec_cmds: Vec::new(),
config,
err: None,
},
client.clone(),
))
.build();
let resp = service.ready(()).unwrap();
let MaybeDone::Done(resp) = resp else {
anyhow::bail!("internal error: not sync init")
};
resp.unwrap();
// todo: persist
let request_received = reflexo::time::Instant::now();
let req_id: RequestId = 0.into();
client.register_request("tinymistExt/documentProfiling", &req_id, request_received);
let state = service.state_mut().unwrap();
let entry = state.entry_resolver().resolve(Some(input.as_path().into()));
let snap = state.snapshot().unwrap();
RUNTIMES.tokio_runtime.block_on(async {
let w = snap.world().clone().task(TaskInputs {
entry: Some(entry),
inputs,
});
UserActionTask::trace_main(client, state, &w, args.rpc_kind, req_id).await
});
Ok(())
})?;
Ok(())
}

View file

@ -0,0 +1,80 @@
use core::fmt;
use std::collections::HashMap;
use std::sync::Arc;
use parking_lot::Mutex;
use sync_ls::{
GetMessageKind, LsHook, LspClientRoot, LspResult, Message, RequestId, TConnectionTx,
};
use tinymist_std::hash::{FxBuildHasher, FxHashMap};
use typst::ecow::EcoString;
use crate::*;
/// Creates a new client root (connection).
pub fn client_root<M: TryFrom<Message, Error = anyhow::Error> + GetMessageKind>(
sender: TConnectionTx<M>,
) -> LspClientRoot {
LspClientRoot::new(RUNTIMES.tokio_runtime.handle().clone(), sender)
.with_hook(Arc::new(TinymistHook::default()))
}
#[derive(Default)]
struct TinymistHook(Mutex<FxHashMap<RequestId, typst_timing::TimingScope>>);
impl fmt::Debug for TinymistHook {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TinymistHook").finish()
}
}
impl LsHook for TinymistHook {
fn start_request(&self, req_id: &RequestId, method: &str) {
().start_request(req_id, method);
if let Some(scope) = typst_timing::TimingScope::new(static_str(method)) {
let mut map = self.0.lock();
map.insert(req_id.clone(), scope);
}
}
fn stop_request(
&self,
req_id: &RequestId,
method: &str,
received_at: tinymist_std::time::Instant,
) {
().stop_request(req_id, method, received_at);
if let Some(scope) = self.0.lock().remove(req_id) {
let _ = scope;
}
}
fn start_notification(&self, method: &str) {
().start_notification(method);
}
fn stop_notification(
&self,
method: &str,
received_at: tinymist_std::time::Instant,
result: LspResult<()>,
) {
().stop_notification(method, received_at, result);
}
}
fn static_str(s: &str) -> &'static str {
static STRS: Mutex<FxHashMap<EcoString, &'static str>> =
Mutex::new(HashMap::with_hasher(FxBuildHasher));
let mut strs = STRS.lock();
if let Some(&s) = strs.get(s) {
return s;
}
let static_ref: &'static str = String::from(s).leak();
strs.insert(static_ref.into(), static_ref);
static_ref
}

View file

@ -1,21 +0,0 @@
//! # tinymist
//!
//! This crate provides a CLI that starts services for [Typst](https://typst.app/). It provides:
//! + `tinymist lsp`: A language server following the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/).
//! + `tinymist preview`: A preview server for Typst.
//!
//! ## Usage
//!
//! See [Features: Command Line Interface](https://myriad-dreamin.github.io/tinymist/feature/cli.html).
//!
//! ## Documentation
//!
//! See [Crate Docs](https://myriad-dreamin.github.io/tinymist/rs/tinymist/index.html).
//!
//! Also see [Developer Guide: Tinymist LSP](https://myriad-dreamin.github.io/tinymist/module/lsp.html).
//!
//! ## Contributing
//!
//! See [CONTRIBUTING.md](https://github.com/Myriad-Dreamin/tinymist/blob/main/CONTRIBUTING.md).
pub use tinymist::*;

View file

@ -1,57 +1,39 @@
#![doc = include_str!("../README.md")]
mod args;
#[cfg(feature = "export")]
mod compile;
mod generate_script;
#[cfg(feature = "preview")]
mod preview;
mod testing;
mod conn;
mod utils;
mod cmd {
#[cfg(feature = "export")]
pub mod compile;
pub mod completion;
pub mod cov;
#[cfg(feature = "dap")]
pub mod dap;
pub mod generate_script;
pub mod lsp;
#[cfg(feature = "preview")]
pub mod preview;
pub mod query;
pub mod test;
pub mod trace_lsp;
use core::fmt;
use std::collections::HashMap;
use std::io;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::{Arc, LazyLock};
#[cfg(feature = "lock")]
pub mod doc;
#[cfg(feature = "lock")]
pub mod task;
}
use std::sync::LazyLock;
use clap::Parser;
use clap_builder::CommandFactory;
use clap_complete::generate;
use futures::future::MaybeDone;
use parking_lot::Mutex;
use reflexo::ImmutPath;
use reflexo_typst::package::PackageSpec;
use sync_ls::transport::{MirrorArgs, with_stdio_transport};
use sync_ls::{
DapBuilder, DapMessage, GetMessageKind, LsHook, LspBuilder, LspClientRoot, LspMessage,
LspResult, Message, RequestId, TConnectionTx, internal_error,
};
use tinymist::LONG_VERSION;
use tinymist::world::TaskInputs;
use tinymist::{Config, RegularInit, ServerState, SuperInit, UserActionTask};
use tinymist_project::EntryResolver;
use tinymist_query::package::PackageInfo;
use tinymist_std::hash::{FxBuildHasher, FxHashMap};
use tinymist_std::{bail, error::prelude::*};
use typst::ecow::EcoString;
#[cfg(feature = "l10n")]
use tinymist_l10n::{load_translations, set_translations};
#[cfg(feature = "preview")]
use tinymist_project::LockFile;
#[cfg(feature = "preview")]
use tinymist_task::Id;
use tinymist_std::error::prelude::*;
use crate::args::*;
#[cfg(feature = "export")]
use crate::compile::compile_main;
use crate::generate_script::generate_script_main;
#[cfg(feature = "preview")]
use crate::preview::preview_main;
use crate::testing::{coverage_main, test_main};
use crate::cmd::*;
use crate::compile::CompileArgs;
use crate::conn::client_root;
use crate::utils::*;
#[cfg(feature = "dhat-heap")]
#[global_allocator]
@ -76,16 +58,74 @@ impl Default for Runtimes {
static RUNTIMES: LazyLock<Runtimes> = LazyLock::new(Runtimes::default);
#[derive(Debug, Clone, clap::Parser)]
#[clap(name = "tinymist", author, version, about, long_version(tinymist::LONG_VERSION.as_str()))]
struct Args {
/// Mode of the binary
#[clap(subcommand)]
pub cmd: Option<Commands>,
}
#[derive(Debug, Clone, clap::Subcommand)]
#[clap(rename_all = "kebab-case")]
enum Commands {
/// Probes existence (Nop run)
Probe,
/// Runs language server
Lsp(crate::lsp::LspArgs),
/// Runs debug adapter
#[cfg(feature = "dap")]
Dap(crate::dap::DapArgs),
/// Runs language server for tracing some typst program.
#[clap(hide(true))]
TraceLsp(crate::trace_lsp::TraceLspArgs),
/// Runs language query
#[clap(hide(true))] // still in development
#[clap(subcommand)]
Query(crate::query::QueryCommands),
/// Runs preview server
#[cfg(feature = "preview")]
Preview(tinymist::tool::preview::PreviewCliArgs),
/// Runs compile command like `typst-cli compile`
Compile(CompileArgs),
/// Generates completion script to stdout
Completion(crate::completion::ShellCompletionArgs),
/// Generates build script for compilation
#[clap(hide(true))] // still in development
GenerateScript(crate::generate_script::GenerateScriptArgs),
/// Runs documents
#[clap(hide(true))] // still in development
#[clap(subcommand)]
Doc(tinymist::project::DocCommands),
/// Runs tasks
#[cfg(feature = "lock")]
#[clap(hide(true))] // still in development
#[clap(subcommand)]
Task(crate::task::TaskCommands),
/// Execute a document and collect coverage
#[clap(hide(true))] // still in development
Cov(crate::cov::CovArgs),
/// Test a document and gives summary
Test(crate::test::TestArgs),
}
/// The main entry point.
fn main() -> Result<()> {
// The root allocator for heap memory profiling.
#[cfg(feature = "dhat-heap")]
let _profiler = dhat::Profiler::new_heap();
// Parses command line arguments
let args = CliArguments::parse();
let cmd = Args::parse().cmd;
let cmd = cmd.unwrap_or_else(|| Commands::Lsp(Default::default()));
// Probes soon to avoid other initializations causing errors
if matches!(args.command, Some(Commands::Probe)) {
if matches!(cmd, Commands::Probe) {
return Ok(());
}
@ -95,10 +135,10 @@ fn main() -> Result<()> {
// Starts logging
let _ = {
let is_transient_cmd = matches!(args.command, Some(Commands::Compile(..)));
let is_test_no_verbose =
matches!(&args.command, Some(Commands::Test(test)) if !test.verbose);
use log::LevelFilter::*;
let is_transient_cmd = matches!(cmd, Commands::Compile(..));
let is_test_no_verbose = matches!(&cmd, Commands::Test(test) if !test.verbose);
let base_no_info = is_transient_cmd || is_test_no_verbose;
let base_level = if base_no_info { Warn } else { Info };
let preview_level = if is_test_no_verbose { Warn } else { Debug };
@ -115,390 +155,29 @@ fn main() -> Result<()> {
.try_init()
};
match args.command.unwrap_or_default() {
Commands::Completion(args) => completion(args),
Commands::Cov(args) => coverage_main(args),
Commands::Test(args) => RUNTIMES.tokio_runtime.block_on(test_main(args)),
#[cfg(feature = "export")]
Commands::Compile(args) => RUNTIMES.tokio_runtime.block_on(compile_main(args)),
Commands::GenerateScript(args) => generate_script_main(args),
Commands::Query(query_cmds) => query_main(query_cmds),
Commands::Lsp(args) => lsp_main(args),
#[cfg(feature = "dap")]
Commands::Dap(args) => dap_main(args),
Commands::TraceLsp(args) => trace_lsp_main(args),
#[cfg(feature = "preview")]
Commands::Preview(args) => RUNTIMES.tokio_runtime.block_on(preview_main(args)),
Commands::Doc(args) => project_main(args),
Commands::Task(args) => task_main(args),
match cmd {
Commands::Probe => Ok(()),
Commands::Lsp(args) => crate::lsp::lsp_main(args),
#[cfg(feature = "dap")]
Commands::Dap(args) => crate::dap::dap_main(args),
Commands::TraceLsp(args) => crate::trace_lsp::trace_lsp_main(args),
Commands::Query(cmds) => crate::query::query_main(cmds),
#[cfg(feature = "preview")]
Commands::Preview(args) => block_on(crate::preview::preview_main(args)),
#[cfg(feature = "export")]
Commands::Compile(args) => block_on(crate::compile::compile_main(args)),
Commands::Completion(args) => crate::completion::completion_main(args),
Commands::GenerateScript(args) => crate::generate_script::generate_script_main(args),
#[cfg(feature = "lock")]
Commands::Doc(cmds) => crate::doc::doc_main(cmds),
#[cfg(feature = "lock")]
Commands::Task(cmds) => crate::task::task_main(cmds),
Commands::Cov(args) => crate::cov::cov_main(args),
Commands::Test(args) => block_on(crate::test::test_main(args)),
}
}
/// Generates completion script to stdout.
pub fn completion(args: ShellCompletionArgs) -> Result<()> {
let Some(shell) = args.shell.or_else(Shell::from_env) else {
tinymist_std::bail!("could not infer shell");
};
let mut cmd = CliArguments::command();
generate(shell, &mut cmd, "tinymist", &mut io::stdout());
Ok(())
}
/// The main entry point for the language server.
pub fn lsp_main(args: LspArgs) -> Result<()> {
let pairs = LONG_VERSION.trim().split('\n');
let pairs = pairs
.map(|e| e.splitn(2, ":").map(|e| e.trim()).collect::<Vec<_>>())
.collect::<Vec<_>>();
log::info!("tinymist version information: {pairs:?}");
log::info!("starting language server: {args:?}");
let is_replay = !args.mirror.replay.is_empty();
with_stdio_transport::<LspMessage>(args.mirror.clone(), |conn| {
let client = client_root(conn.sender);
ServerState::install_lsp(LspBuilder::new(
RegularInit {
client: client.weak().to_typed(),
font_opts: args.font,
exec_cmds: Vec::new(),
},
client.weak(),
))
.build()
.start(conn.receiver, is_replay)
})?;
log::info!("language server did shut down");
Ok(())
}
/// The main entry point for the language server.
#[cfg(feature = "dap")]
pub fn dap_main(args: DapArgs) -> Result<()> {
let pairs = LONG_VERSION.trim().split('\n');
let pairs = pairs
.map(|e| e.splitn(2, ":").map(|e| e.trim()).collect::<Vec<_>>())
.collect::<Vec<_>>();
log::info!("tinymist version information: {pairs:?}");
log::info!("starting debug adaptor: {args:?}");
let is_replay = !args.mirror.replay.is_empty();
with_stdio_transport::<DapMessage>(args.mirror.clone(), |conn| {
let client = client_root(conn.sender);
ServerState::install_dap(DapBuilder::new(
tinymist::DapRegularInit {
client: client.weak().to_typed(),
font_opts: args.font,
},
client.weak(),
))
.build()
.start(conn.receiver, is_replay)
})?;
log::info!("language server did shut down");
Ok(())
}
/// 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() {
root_path = std::env::current_dir().context("cwd")?.join(root_path);
}
if input.is_relative() {
input = std::env::current_dir().context("cwd")?.join(input);
}
if !input.starts_with(&root_path) {
bail!("input file is not within the root path: {input:?} not in {root_path:?}");
}
with_stdio_transport::<LspMessage>(args.mirror.clone(), |conn| {
let client_root = client_root(conn.sender);
let client = client_root.weak();
let roots = vec![ImmutPath::from(root_path)];
let config = Config {
entry_resolver: EntryResolver {
roots,
..EntryResolver::default()
},
font_opts: args.compile.font,
..Config::default()
};
let mut service = ServerState::install_lsp(LspBuilder::new(
SuperInit {
client: client.to_typed(),
exec_cmds: Vec::new(),
config,
err: None,
},
client.clone(),
))
.build();
let resp = service.ready(()).unwrap();
let MaybeDone::Done(resp) = resp else {
anyhow::bail!("internal error: not sync init")
};
resp.unwrap();
// todo: persist
let request_received = reflexo::time::Instant::now();
let req_id: RequestId = 0.into();
client.register_request("tinymistExt/documentProfiling", &req_id, request_received);
let state = service.state_mut().unwrap();
let entry = state.entry_resolver().resolve(Some(input.as_path().into()));
let snap = state.snapshot().unwrap();
RUNTIMES.tokio_runtime.block_on(async {
let w = snap.world().clone().task(TaskInputs {
entry: Some(entry),
inputs,
});
UserActionTask::trace_main(client, state, &w, args.rpc_kind, req_id).await
});
Ok(())
})?;
Ok(())
}
/// The main entry point for language server queries.
pub fn query_main(cmds: QueryCommands) -> Result<()> {
use tinymist_project::package::PackageRegistry;
with_stdio_transport::<LspMessage>(MirrorArgs::default(), |conn| {
let client_root = client_root(conn.sender);
let client = client_root.weak();
// todo: roots, inputs, font_opts
let config = Config::default();
let mut service = ServerState::install_lsp(LspBuilder::new(
SuperInit {
client: client.to_typed(),
exec_cmds: Vec::new(),
config,
err: None,
},
client.clone(),
))
.build();
let resp = service.ready(()).unwrap();
let MaybeDone::Done(resp) = resp else {
anyhow::bail!("internal error: not sync init")
};
resp.unwrap();
let state = service.state_mut().unwrap();
let snap = state.snapshot().unwrap();
let res = RUNTIMES.tokio_runtime.block_on(async move {
match cmds {
QueryCommands::PackageDocs(args) => {
let pkg = PackageSpec::from_str(&args.id).unwrap();
let path = args.path.map(PathBuf::from);
let path = path
.unwrap_or_else(|| snap.registry().resolve(&pkg).unwrap().as_ref().into());
let res = state
.resource_package_docs_(PackageInfo {
path,
namespace: pkg.namespace,
name: pkg.name,
version: pkg.version.to_string(),
})?
.await?;
let output_path = Path::new(&args.output);
std::fs::write(output_path, res).map_err(internal_error)?;
}
QueryCommands::CheckPackage(args) => {
let pkg = PackageSpec::from_str(&args.id).unwrap();
let path = args.path.map(PathBuf::from);
let path = path
.unwrap_or_else(|| snap.registry().resolve(&pkg).unwrap().as_ref().into());
state
.check_package(PackageInfo {
path,
namespace: pkg.namespace,
name: pkg.name,
version: pkg.version.to_string(),
})?
.await?;
}
};
LspResult::Ok(())
});
res.map_err(|e| anyhow::anyhow!("{e:?}"))
})?;
Ok(())
}
#[cfg(feature = "preview")]
trait LockFileExt {
fn preview(&mut self, doc_id: Id, args: &TaskPreviewArgs) -> Result<Id>;
}
#[cfg(feature = "preview")]
impl LockFileExt for LockFile {
fn preview(&mut self, doc_id: Id, args: &TaskPreviewArgs) -> Result<Id> {
use tinymist_task::{ApplyProjectTask, PreviewTask, ProjectTask, TaskWhen};
let task_id = args
.task_name
.as_ref()
.map(|t| Id::new(t.clone()))
.unwrap_or(doc_id.clone());
let when = args.when.clone().unwrap_or(TaskWhen::OnType);
let task = ProjectTask::Preview(PreviewTask { when });
let task = ApplyProjectTask {
id: task_id.clone(),
document: doc_id,
task,
};
self.replace_task(task);
Ok(task_id)
}
}
/// Project document commands' main
#[cfg(feature = "lock")]
pub fn project_main(args: tinymist_project::DocCommands) -> Result<()> {
use tinymist_project::DocCommands;
let cwd = std::env::current_dir().context("cannot get cwd")?;
LockFile::update(&cwd, |state| {
let ctx: (&Path, &Path) = (&cwd, &cwd);
match args {
DocCommands::New(args) => {
state.replace_document(args.to_input(ctx));
}
DocCommands::Configure(args) => {
use tinymist_project::ProjectRoute;
let id: Id = args.id.id(ctx);
state.route.push(ProjectRoute {
id: id.clone(),
priority: args.priority,
});
}
}
Ok(())
})
}
/// Project task commands' main
#[cfg(feature = "lock")]
pub fn task_main(args: TaskCommands) -> Result<()> {
let cwd = std::env::current_dir().context("cannot get cwd")?;
LockFile::update(&cwd, |state| {
let _ = state;
match args {
#[cfg(feature = "preview")]
TaskCommands::Preview(args) => {
let ctx: (&Path, &Path) = (&cwd, &cwd);
let input = args.declare.to_input(ctx);
let id = input.id.clone();
state.replace_document(input);
let _ = state.preview(id, &args);
Ok(())
}
}
})
}
/// Creates a new language server host.
fn client_root<M: TryFrom<Message, Error = anyhow::Error> + GetMessageKind>(
sender: TConnectionTx<M>,
) -> LspClientRoot {
LspClientRoot::new(RUNTIMES.tokio_runtime.handle().clone(), sender)
.with_hook(Arc::new(TypstLsHook::default()))
}
#[derive(Default)]
struct TypstLsHook(Mutex<FxHashMap<RequestId, typst_timing::TimingScope>>);
impl fmt::Debug for TypstLsHook {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TypstLsHook").finish()
}
}
impl LsHook for TypstLsHook {
fn start_request(&self, req_id: &RequestId, method: &str) {
().start_request(req_id, method);
if let Some(scope) = typst_timing::TimingScope::new(static_str(method)) {
let mut map = self.0.lock();
map.insert(req_id.clone(), scope);
}
}
fn stop_request(
&self,
req_id: &RequestId,
method: &str,
received_at: tinymist_std::time::Instant,
) {
().stop_request(req_id, method, received_at);
if let Some(scope) = self.0.lock().remove(req_id) {
let _ = scope;
}
}
fn start_notification(&self, method: &str) {
().start_notification(method);
}
fn stop_notification(
&self,
method: &str,
received_at: tinymist_std::time::Instant,
result: LspResult<()>,
) {
().stop_notification(method, received_at, result);
}
}
fn static_str(s: &str) -> &'static str {
static STRS: Mutex<FxHashMap<EcoString, &'static str>> =
Mutex::new(HashMap::with_hasher(FxBuildHasher));
let mut strs = STRS.lock();
if let Some(&s) = strs.get(s) {
return s;
}
let static_ref: &'static str = String::from(s).leak();
strs.insert(static_ref.into(), static_ref);
static_ref
}

View file

@ -1,3 +1,7 @@
use tinymist::world::system::print_diagnostics;
use tinymist::world::{DiagnosticFormat, SourceWorld};
use tinymist_std::{bail, error::prelude::*};
pub fn exit_on_ctrl_c() {
tokio::spawn(async move {
let _ = tokio::signal::ctrl_c().await;
@ -5,3 +9,22 @@ pub fn exit_on_ctrl_c() {
std::process::exit(0);
});
}
pub fn block_on<F: Future>(future: F) -> F::Output {
crate::RUNTIMES.tokio_runtime.block_on(future)
}
pub fn print_diag_or_error<T>(world: &impl SourceWorld, result: Result<T>) -> Result<T> {
match result {
Ok(v) => Ok(v),
Err(err) => {
if let Some(diagnostics) = err.diagnostics() {
print_diagnostics(world, diagnostics.iter(), DiagnosticFormat::Human)
.context_ut("print diagnostics")?;
bail!("");
}
Err(err)
}
}
}

View file

@ -6,7 +6,7 @@ pub use tinymist_world::config::CompileFontOpts;
pub use tinymist_world::entry::*;
pub use tinymist_world::{
CompilerUniverse, CompilerWorld, DiagnosticFormat, EntryOpts, EntryState, RevisingUniverse,
SourceWorld, TaskInputs, with_main,
SourceWorld, TaskInputs, WorldComputeGraph, with_main,
};
pub use tinymist_world::{diag, font, package, vfs};