mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-11-24 21:19:37 +00:00
refactor: create main files (#2054)
This commit is contained in:
parent
846e6ffbbf
commit
5ee01b59a9
19 changed files with 798 additions and 742 deletions
|
|
@ -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,
|
|
||||||
}
|
|
||||||
|
|
@ -2,13 +2,11 @@
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use reflexo::ImmutPath;
|
|
||||||
use reflexo_typst::WorldComputeGraph;
|
|
||||||
use tinymist_std::error::prelude::*;
|
|
||||||
|
|
||||||
use tinymist::ExportTask;
|
use tinymist::ExportTask;
|
||||||
use tinymist::project::*;
|
use tinymist::project::*;
|
||||||
|
use tinymist::world::WorldComputeGraph;
|
||||||
use tinymist::world::system::print_diagnostics;
|
use tinymist::world::system::print_diagnostics;
|
||||||
|
use tinymist_std::{ImmutPath, error::prelude::*};
|
||||||
|
|
||||||
/// Arguments for project compilation.
|
/// Arguments for project compilation.
|
||||||
#[derive(Debug, Clone, clap::Parser)]
|
#[derive(Debug, Clone, clap::Parser)]
|
||||||
97
crates/tinymist-cli/src/cmd/completion.rs
Normal file
97
crates/tinymist-cli/src/cmd/completion.rs
Normal 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
crates/tinymist-cli/src/cmd/cov.rs
Normal file
36
crates/tinymist-cli/src/cmd/cov.rs
Normal 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)
|
||||||
|
}
|
||||||
37
crates/tinymist-cli/src/cmd/dap.rs
Normal file
37
crates/tinymist-cli/src/cmd/dap.rs
Normal 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(())
|
||||||
|
}
|
||||||
35
crates/tinymist-cli/src/cmd/doc.rs
Normal file
35
crates/tinymist-cli/src/cmd/doc.rs
Normal 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(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -2,11 +2,12 @@
|
||||||
|
|
||||||
use std::{borrow::Cow, path::Path};
|
use std::{borrow::Cow, path::Path};
|
||||||
|
|
||||||
use clap_complete::Shell;
|
|
||||||
use reflexo::path::unix_slash;
|
|
||||||
use tinymist::project::*;
|
use tinymist::project::*;
|
||||||
|
use tinymist_std::path::unix_slash;
|
||||||
use tinymist_std::{bail, error::prelude::*};
|
use tinymist_std::{bail, error::prelude::*};
|
||||||
|
|
||||||
|
use crate::completion::Shell;
|
||||||
|
|
||||||
/// Arguments for generating a build script.
|
/// Arguments for generating a build script.
|
||||||
#[derive(Debug, Clone, clap::Parser)]
|
#[derive(Debug, Clone, clap::Parser)]
|
||||||
pub struct GenerateScriptArgs {
|
pub struct GenerateScriptArgs {
|
||||||
46
crates/tinymist-cli/src/cmd/lsp.rs
Normal file
46
crates/tinymist-cli/src/cmd/lsp.rs
Normal 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(())
|
||||||
|
}
|
||||||
122
crates/tinymist-cli/src/cmd/query.rs
Normal file
122
crates/tinymist-cli/src/cmd/query.rs
Normal 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,
|
||||||
|
}
|
||||||
95
crates/tinymist-cli/src/cmd/task.rs
Normal file
95
crates/tinymist-cli/src/cmd/task.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,50 +9,30 @@ use std::sync::{Arc, atomic::AtomicBool};
|
||||||
use itertools::Either;
|
use itertools::Either;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||||
use reflexo::ImmutPath;
|
use tinymist::project::*;
|
||||||
use reflexo_typst::{TypstDocument, TypstHtmlDocument, vfs::FileId};
|
use tinymist::tool::project::{StartProjectResult, start_project};
|
||||||
|
use tinymist::world::{SourceWorld, with_main};
|
||||||
use tinymist_debug::CoverageResult;
|
use tinymist_debug::CoverageResult;
|
||||||
use tinymist_project::world::{DiagnosticFormat, system::print_diagnostics};
|
use tinymist_project::world::{DiagnosticFormat, system::print_diagnostics};
|
||||||
use tinymist_query::analysis::Analysis;
|
use tinymist_query::analysis::Analysis;
|
||||||
use tinymist_query::syntax::{cast_include_expr, find_source_by_expr, node_ancestors};
|
use tinymist_query::syntax::{cast_include_expr, find_source_by_expr, node_ancestors};
|
||||||
use tinymist_query::testing::{TestCaseKind, TestSuites};
|
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 tinymist_std::{bail, error::prelude::*, fs::paths::write_atomic, typst::TypstPagedDocument};
|
||||||
use typst::diag::{Severity, SourceDiagnostic};
|
use typst::diag::{Severity, SourceDiagnostic};
|
||||||
use typst::ecow::EcoVec;
|
use typst::ecow::EcoVec;
|
||||||
use typst::foundations::{Context, Label};
|
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::{World, utils::PicoStr};
|
||||||
use typst_shim::eval::TypstEngine;
|
use typst_shim::eval::TypstEngine;
|
||||||
|
|
||||||
use tinymist::project::*;
|
use crate::print_diag_or_error;
|
||||||
use tinymist::tool::project::{StartProjectResult, start_project};
|
|
||||||
use tinymist::world::{SourceWorld, with_main};
|
|
||||||
|
|
||||||
use crate::utils::exit_on_ctrl_c;
|
use crate::utils::exit_on_ctrl_c;
|
||||||
|
|
||||||
const TEST_EVICT_MAX_AGE: usize = 30;
|
const TEST_EVICT_MAX_AGE: usize = 30;
|
||||||
const PREFIX_LEN: usize = 7;
|
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
|
/// Testing arguments
|
||||||
#[derive(Debug, Clone, clap::Parser)]
|
#[derive(Debug, Clone, clap::Parser)]
|
||||||
pub struct TestArgs {
|
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")
|
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 {
|
enum Level {
|
||||||
Error,
|
Error,
|
||||||
Info,
|
Info,
|
||||||
101
crates/tinymist-cli/src/cmd/trace_lsp.rs
Normal file
101
crates/tinymist-cli/src/cmd/trace_lsp.rs
Normal 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(())
|
||||||
|
}
|
||||||
80
crates/tinymist-cli/src/conn.rs
Normal file
80
crates/tinymist-cli/src/conn.rs
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -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::*;
|
|
||||||
|
|
@ -1,57 +1,39 @@
|
||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
mod args;
|
mod conn;
|
||||||
#[cfg(feature = "export")]
|
|
||||||
mod compile;
|
|
||||||
mod generate_script;
|
|
||||||
#[cfg(feature = "preview")]
|
|
||||||
mod preview;
|
|
||||||
mod testing;
|
|
||||||
mod utils;
|
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;
|
#[cfg(feature = "lock")]
|
||||||
use std::collections::HashMap;
|
pub mod doc;
|
||||||
use std::io;
|
#[cfg(feature = "lock")]
|
||||||
use std::path::{Path, PathBuf};
|
pub mod task;
|
||||||
use std::str::FromStr;
|
}
|
||||||
use std::sync::{Arc, LazyLock};
|
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use clap::Parser;
|
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")]
|
#[cfg(feature = "l10n")]
|
||||||
use tinymist_l10n::{load_translations, set_translations};
|
use tinymist_l10n::{load_translations, set_translations};
|
||||||
#[cfg(feature = "preview")]
|
use tinymist_std::error::prelude::*;
|
||||||
use tinymist_project::LockFile;
|
|
||||||
#[cfg(feature = "preview")]
|
|
||||||
use tinymist_task::Id;
|
|
||||||
|
|
||||||
use crate::args::*;
|
use crate::cmd::*;
|
||||||
|
use crate::compile::CompileArgs;
|
||||||
#[cfg(feature = "export")]
|
use crate::conn::client_root;
|
||||||
use crate::compile::compile_main;
|
use crate::utils::*;
|
||||||
use crate::generate_script::generate_script_main;
|
|
||||||
#[cfg(feature = "preview")]
|
|
||||||
use crate::preview::preview_main;
|
|
||||||
use crate::testing::{coverage_main, test_main};
|
|
||||||
|
|
||||||
#[cfg(feature = "dhat-heap")]
|
#[cfg(feature = "dhat-heap")]
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
|
|
@ -76,16 +58,74 @@ impl Default for Runtimes {
|
||||||
|
|
||||||
static RUNTIMES: LazyLock<Runtimes> = LazyLock::new(Runtimes::default);
|
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.
|
/// The main entry point.
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
|
// The root allocator for heap memory profiling.
|
||||||
#[cfg(feature = "dhat-heap")]
|
#[cfg(feature = "dhat-heap")]
|
||||||
let _profiler = dhat::Profiler::new_heap();
|
let _profiler = dhat::Profiler::new_heap();
|
||||||
|
|
||||||
// Parses command line arguments
|
// 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
|
// Probes soon to avoid other initializations causing errors
|
||||||
if matches!(args.command, Some(Commands::Probe)) {
|
if matches!(cmd, Commands::Probe) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,10 +135,10 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
// Starts logging
|
// Starts logging
|
||||||
let _ = {
|
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::*;
|
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_no_info = is_transient_cmd || is_test_no_verbose;
|
||||||
let base_level = if base_no_info { Warn } else { Info };
|
let base_level = if base_no_info { Warn } else { Info };
|
||||||
let preview_level = if is_test_no_verbose { Warn } else { Debug };
|
let preview_level = if is_test_no_verbose { Warn } else { Debug };
|
||||||
|
|
@ -115,390 +155,29 @@ fn main() -> Result<()> {
|
||||||
.try_init()
|
.try_init()
|
||||||
};
|
};
|
||||||
|
|
||||||
match args.command.unwrap_or_default() {
|
match cmd {
|
||||||
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),
|
|
||||||
Commands::Probe => Ok(()),
|
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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
pub fn exit_on_ctrl_c() {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let _ = tokio::signal::ctrl_c().await;
|
let _ = tokio::signal::ctrl_c().await;
|
||||||
|
|
@ -5,3 +9,22 @@ pub fn exit_on_ctrl_c() {
|
||||||
std::process::exit(0);
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ pub use tinymist_world::config::CompileFontOpts;
|
||||||
pub use tinymist_world::entry::*;
|
pub use tinymist_world::entry::*;
|
||||||
pub use tinymist_world::{
|
pub use tinymist_world::{
|
||||||
CompilerUniverse, CompilerWorld, DiagnosticFormat, EntryOpts, EntryState, RevisingUniverse,
|
CompilerUniverse, CompilerWorld, DiagnosticFormat, EntryOpts, EntryState, RevisingUniverse,
|
||||||
SourceWorld, TaskInputs, with_main,
|
SourceWorld, TaskInputs, WorldComputeGraph, with_main,
|
||||||
};
|
};
|
||||||
pub use tinymist_world::{diag, font, package, vfs};
|
pub use tinymist_world::{diag, font, package, vfs};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue