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 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)]
|
||||
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 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 {
|
||||
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 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,
|
||||
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")]
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue