mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-11-22 12:34:39 +00:00
feat: move and compile tinymist crate for wasm32 target (#2027)
Some checks are pending
tinymist::auto_tag / auto-tag (push) Waiting to run
tinymist::ci / Duplicate Actions Detection (push) Waiting to run
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Waiting to run
tinymist::ci / Check Minimum Rust version and Tests (Windows) (push) Waiting to run
tinymist::ci / prepare-build (push) Waiting to run
tinymist::ci / announce (push) Blocked by required conditions
tinymist::ci / build (push) Blocked by required conditions
tinymist::gh_pages / build-gh-pages (push) Waiting to run
Some checks are pending
tinymist::auto_tag / auto-tag (push) Waiting to run
tinymist::ci / Duplicate Actions Detection (push) Waiting to run
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Waiting to run
tinymist::ci / Check Minimum Rust version and Tests (Windows) (push) Waiting to run
tinymist::ci / prepare-build (push) Waiting to run
tinymist::ci / announce (push) Blocked by required conditions
tinymist::ci / build (push) Blocked by required conditions
tinymist::gh_pages / build-gh-pages (push) Waiting to run
This commit is contained in:
parent
79f68dc94d
commit
ce5ab81760
56 changed files with 1558 additions and 1122 deletions
4
.github/workflows/build-vscode-others.yml
vendored
4
.github/workflows/build-vscode-others.yml
vendored
|
|
@ -162,12 +162,12 @@ jobs:
|
|||
run: |
|
||||
npm pack > package-name
|
||||
mv $(cat package-name) tinymist-${{ env.target }}.tar.gz
|
||||
working-directory: ./crates/tinymist-core
|
||||
working-directory: ./crates/tinymist
|
||||
- name: Upload tinymist npm library
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tinymist-${{ env.target }}-npm
|
||||
path: crates/tinymist-core/tinymist-${{ env.target }}.tar.gz
|
||||
path: crates/tinymist/tinymist-${{ env.target }}.tar.gz
|
||||
|
||||
- name: Download PDF Documentation
|
||||
uses: actions/download-artifact@v4
|
||||
|
|
|
|||
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
|
|
@ -62,12 +62,12 @@ jobs:
|
|||
- name: Generate completions
|
||||
run: |
|
||||
mkdir -p completions/{zsh,bash,fish/vendor_completions.d,elvish/lib,nushell/vendor/autoload,powershell}/
|
||||
cargo run -p tinymist -- completion zsh > completions/zsh/_tinymist
|
||||
cargo run -p tinymist -- completion bash > completions/bash/tinymist
|
||||
cargo run -p tinymist -- completion fish > completions/fish/vendor_completions.d/tinymist.fish
|
||||
cargo run -p tinymist -- completion elvish > completions/elvish/lib/tinymist.elv
|
||||
cargo run -p tinymist -- completion nushell > completions/nushell/vendor/autoload/tinymist.nu
|
||||
cargo run -p tinymist -- completion powershell > completions/powershell/tinymist.ps1
|
||||
cargo run --bin tinymist -- completion zsh > completions/zsh/_tinymist
|
||||
cargo run --bin tinymist -- completion bash > completions/bash/tinymist
|
||||
cargo run --bin tinymist -- completion fish > completions/fish/vendor_completions.d/tinymist.fish
|
||||
cargo run --bin tinymist -- completion elvish > completions/elvish/lib/tinymist.elv
|
||||
cargo run --bin tinymist -- completion nushell > completions/nushell/vendor/autoload/tinymist.nu
|
||||
cargo run --bin tinymist -- completion powershell > completions/powershell/tinymist.ps1
|
||||
tar -czvf tinymist-completions.tar.gz completions
|
||||
- name: upload completions
|
||||
uses: actions/upload-artifact@v4
|
||||
|
|
|
|||
2
.github/workflows/release-crates.yml
vendored
2
.github/workflows/release-crates.yml
vendored
|
|
@ -51,9 +51,9 @@ jobs:
|
|||
cargo publish --no-verify -p tinymist-lint || true
|
||||
cargo publish --no-verify -p tinymist-query || true
|
||||
cargo publish --no-verify -p tinymist-render || true
|
||||
cargo publish --no-verify -p tinymist-core || true
|
||||
cargo publish --no-verify -p tinymist-preview || true
|
||||
cargo publish --no-verify -p tinymist || true
|
||||
cargo publish --no-verify -p tinymist-cli || true
|
||||
- name: Verifies crate health (Optional)
|
||||
run: |
|
||||
cargo publish --dry-run -p sync-ls
|
||||
|
|
|
|||
71
Cargo.lock
generated
71
Cargo.lock
generated
|
|
@ -4133,6 +4133,7 @@ dependencies = [
|
|||
"clap_mangen",
|
||||
"codespan-reporting",
|
||||
"comemo",
|
||||
"console_error_panic_hook",
|
||||
"crossbeam-channel",
|
||||
"dapts",
|
||||
"dhat",
|
||||
|
|
@ -4144,6 +4145,7 @@ dependencies = [
|
|||
"hyper-tungstenite",
|
||||
"hyper-util",
|
||||
"itertools 0.13.0",
|
||||
"js-sys",
|
||||
"log",
|
||||
"lsp-types",
|
||||
"open",
|
||||
|
|
@ -4161,7 +4163,6 @@ dependencies = [
|
|||
"sync-ls",
|
||||
"temp-env",
|
||||
"tinymist-assets 0.13.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tinymist-core",
|
||||
"tinymist-debug",
|
||||
"tinymist-l10n",
|
||||
"tinymist-preview",
|
||||
|
|
@ -4188,6 +4189,7 @@ dependencies = [
|
|||
"unicode-script",
|
||||
"vergen",
|
||||
"walkdir",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4242,18 +4244,77 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinymist-core"
|
||||
name = "tinymist-cli"
|
||||
version = "0.13.22"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"base64",
|
||||
"cargo_metadata",
|
||||
"console_error_panic_hook",
|
||||
"js-sys",
|
||||
"chrono",
|
||||
"clap",
|
||||
"clap_builder",
|
||||
"clap_complete",
|
||||
"clap_complete_fig",
|
||||
"clap_complete_nushell",
|
||||
"clap_mangen",
|
||||
"codespan-reporting",
|
||||
"comemo",
|
||||
"crossbeam-channel",
|
||||
"dapts",
|
||||
"dhat",
|
||||
"dirs",
|
||||
"env_logger",
|
||||
"futures",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-tungstenite",
|
||||
"hyper-util",
|
||||
"itertools 0.13.0",
|
||||
"log",
|
||||
"lsp-types",
|
||||
"open",
|
||||
"parking_lot",
|
||||
"paste",
|
||||
"rayon",
|
||||
"reflexo",
|
||||
"reflexo-typst",
|
||||
"reflexo-vec2svg",
|
||||
"rpds",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"strum",
|
||||
"sync-ls",
|
||||
"temp-env",
|
||||
"tinymist",
|
||||
"tinymist-assets 0.13.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tinymist-debug",
|
||||
"tinymist-l10n",
|
||||
"tinymist-preview",
|
||||
"tinymist-project",
|
||||
"tinymist-query",
|
||||
"tinymist-render",
|
||||
"tinymist-std",
|
||||
"tinymist-task",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"toml",
|
||||
"ttf-parser",
|
||||
"typlite",
|
||||
"typst",
|
||||
"typst-ansi-hl",
|
||||
"typst-html",
|
||||
"typst-pdf",
|
||||
"typst-render",
|
||||
"typst-shim",
|
||||
"typst-svg",
|
||||
"typst-timing",
|
||||
"typstfmt",
|
||||
"typstyle-core",
|
||||
"unicode-script",
|
||||
"vergen",
|
||||
"wasm-bindgen",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ reqwest = { version = "^0.12", default-features = false, features = [
|
|||
"blocking",
|
||||
"multipart",
|
||||
] }
|
||||
http-body-util = "0.1.2"
|
||||
|
||||
# Algorithms
|
||||
base64 = "0.22"
|
||||
|
|
@ -201,9 +202,9 @@ typst-shim = { path = "./crates/typst-shim", version = "0.13.16" }
|
|||
tinymist-tests = { path = "./crates/tinymist-tests/" }
|
||||
|
||||
sync-ls = { path = "./crates/sync-lsp", version = "0.13.22" }
|
||||
tinymist = { path = "./crates/tinymist/", version = "0.13.22" }
|
||||
tinymist = { path = "./crates/tinymist/", version = "0.13.22", default-features = false }
|
||||
tinymist-analysis = { path = "./crates/tinymist-analysis/", version = "0.13.22" }
|
||||
tinymist-core = { path = "./crates/tinymist-core/", version = "0.13.22", default-features = false }
|
||||
tinymist-cli = { path = "./crates/tinymist-cli/", version = "0.13.22" }
|
||||
tinymist-debug = { path = "./crates/tinymist-debug/", version = "0.13.22" }
|
||||
tinymist-lint = { path = "./crates/tinymist-lint/", version = "0.13.22" }
|
||||
tinymist-query = { path = "./crates/tinymist-query/", version = "0.13.22" }
|
||||
|
|
|
|||
|
|
@ -14,33 +14,27 @@ rust-version.workspace = true
|
|||
[dependencies]
|
||||
|
||||
anyhow.workspace = true
|
||||
crossbeam-channel.workspace = true
|
||||
dapts = { workspace = true, optional = true }
|
||||
futures.workspace = true
|
||||
log.workspace = true
|
||||
lsp-types = { workspace = true, optional = true }
|
||||
parking_lot.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
clap = { workspace = true, optional = true }
|
||||
crossbeam-channel = { workspace = true, optional = true }
|
||||
futures = { workspace = true, optional = true }
|
||||
parking_lot = { workspace = true, optional = true }
|
||||
tokio = { workspace = true, features = ["rt", "time"], optional = true }
|
||||
tokio = { workspace = true, features = ["rt"], optional = true }
|
||||
tokio-util = { workspace = true, optional = true }
|
||||
|
||||
[features]
|
||||
dap = ["dapts"]
|
||||
lsp = ["lsp-types"]
|
||||
server = [
|
||||
"crossbeam-channel",
|
||||
"futures",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"clap",
|
||||
"parking_lot",
|
||||
]
|
||||
server = ["tokio"]
|
||||
system = ["tokio", "tokio/time", "tokio-util", "clap"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["dap", "lsp", "server"]
|
||||
features = ["dap", "lsp", "system", "server"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ pub use server::*;
|
|||
pub mod req_queue;
|
||||
#[cfg(feature = "server")]
|
||||
mod server;
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg(all(feature = "server", feature = "system"))]
|
||||
pub mod transport;
|
||||
|
||||
use std::any::Any;
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ impl Notification {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg(all(feature = "server", feature = "system"))]
|
||||
pub(crate) fn is_exit(&self) -> bool {
|
||||
self.method == "exit"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ pub struct TConnectionRx<M> {
|
|||
|
||||
impl<M: TryFrom<Message, Error = anyhow::Error>> TConnectionRx<M> {
|
||||
/// Receives a message or an event.
|
||||
pub(crate) fn recv(&self) -> anyhow::Result<EventOrMessage<M>> {
|
||||
pub fn recv(&self) -> anyhow::Result<EventOrMessage<M>> {
|
||||
crossbeam_channel::select_biased! {
|
||||
recv(self.lsp) -> msg => Ok(EventOrMessage::Msg(msg?.try_into()?)),
|
||||
recv(self.event) -> event => Ok(event.map(EventOrMessage::Evt)?),
|
||||
|
|
@ -78,8 +78,10 @@ impl<M: TryFrom<Message, Error = anyhow::Error>> TConnectionRx<M> {
|
|||
}
|
||||
|
||||
/// This is a helper enum to handle both events and messages.
|
||||
pub(crate) enum EventOrMessage<M> {
|
||||
pub enum EventOrMessage<M> {
|
||||
/// An event received.
|
||||
Evt(Event),
|
||||
/// A message received.
|
||||
Msg(M),
|
||||
}
|
||||
|
||||
|
|
@ -380,7 +382,7 @@ impl LspClient {
|
|||
|
||||
/// Finally sends the response if it is not sent before.
|
||||
/// From the definition, the response is already sent if it is `Some(())`.
|
||||
pub(crate) fn schedule_tail(&self, req_id: RequestId, resp: ScheduledResult) {
|
||||
pub fn schedule_tail(&self, req_id: RequestId, resp: ScheduledResult) {
|
||||
match resp {
|
||||
// Already responded
|
||||
Ok(Some(())) => {}
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ where
|
|||
///
|
||||
/// See [`transport::MirrorArgs`] for information about the record-replay
|
||||
/// feature.
|
||||
#[cfg(feature = "system")]
|
||||
pub fn start(
|
||||
&mut self,
|
||||
inbox: TConnectionRx<LspMessage>,
|
||||
|
|
@ -202,6 +203,7 @@ where
|
|||
}
|
||||
|
||||
/// Starts the language server on the given connection.
|
||||
#[cfg(feature = "system")]
|
||||
pub fn start_(&mut self, inbox: TConnectionRx<LspMessage>) -> anyhow::Result<()> {
|
||||
use EventOrMessage::*;
|
||||
// todo: follow what rust analyzer does
|
||||
|
|
@ -273,7 +275,7 @@ where
|
|||
|
||||
/// Registers and handles a request. This should only be called once per
|
||||
/// incoming request.
|
||||
fn on_lsp_request(&mut self, request_received: Instant, req: Request) {
|
||||
pub fn on_lsp_request(&mut self, request_received: Instant, req: Request) {
|
||||
self.client
|
||||
.register_request(&req.method, &req.id, request_received);
|
||||
|
||||
|
|
@ -353,7 +355,11 @@ where
|
|||
}
|
||||
|
||||
/// Handles an incoming notification.
|
||||
fn on_notification(&mut self, received_at: Instant, not: Notification) -> anyhow::Result<()> {
|
||||
pub fn on_notification(
|
||||
&mut self,
|
||||
received_at: Instant,
|
||||
not: Notification,
|
||||
) -> anyhow::Result<()> {
|
||||
self.client.hook.start_notification(¬.method);
|
||||
let handle = |s, Notification { method, params }: Notification| {
|
||||
let Some(handler) = self.notifications.get(method.as_str()) else {
|
||||
|
|
|
|||
150
crates/tinymist-cli/Cargo.toml
Normal file
150
crates/tinymist-cli/Cargo.toml
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
[package]
|
||||
name = "tinymist-cli"
|
||||
description = "An integrated language service for Typst."
|
||||
categories = ["compilers", "command-line-utilities"]
|
||||
keywords = ["cli", "lsp", "language", "typst"]
|
||||
authors.workspace = true
|
||||
version.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "tinymist"
|
||||
path = "src/main.rs"
|
||||
doc = false
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
base64.workspace = true
|
||||
chrono.workspace = true
|
||||
clap.workspace = true
|
||||
clap_builder.workspace = true
|
||||
clap_complete.workspace = true
|
||||
clap_complete_fig.workspace = true
|
||||
clap_complete_nushell.workspace = true
|
||||
clap_mangen.workspace = true
|
||||
crossbeam-channel.workspace = true
|
||||
codespan-reporting.workspace = true
|
||||
comemo.workspace = true
|
||||
dhat = { workspace = true, optional = true }
|
||||
dirs.workspace = true
|
||||
env_logger.workspace = true
|
||||
futures.workspace = true
|
||||
hyper.workspace = true
|
||||
hyper-util = { workspace = true, features = [
|
||||
"server",
|
||||
"http1",
|
||||
"http2",
|
||||
"server-graceful",
|
||||
"server-auto",
|
||||
] }
|
||||
http-body-util = "0.1.2"
|
||||
hyper-tungstenite = { workspace = true, optional = true }
|
||||
itertools.workspace = true
|
||||
lsp-types.workspace = true
|
||||
log.workspace = true
|
||||
open.workspace = true
|
||||
parking_lot.workspace = true
|
||||
paste.workspace = true
|
||||
rayon.workspace = true
|
||||
reflexo.workspace = true
|
||||
reflexo-typst = { workspace = true, features = ["system", "svg"] }
|
||||
reflexo-vec2svg.workspace = true
|
||||
rpds.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
strum.workspace = true
|
||||
sync-ls = { workspace = true, features = ["lsp", "server", "system"] }
|
||||
tinymist-assets = { workspace = true }
|
||||
tinymist-query.workspace = true
|
||||
tinymist-std.workspace = true
|
||||
tinymist = { workspace = true, default-features = false, features = ["system"] }
|
||||
tinymist-project = { workspace = true, features = ["lsp"] }
|
||||
tinymist-render.workspace = true
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "io-std"] }
|
||||
tokio-util.workspace = true
|
||||
toml.workspace = true
|
||||
ttf-parser.workspace = true
|
||||
typlite = { workspace = true, default-features = false }
|
||||
typst.workspace = true
|
||||
typst-svg.workspace = true
|
||||
typst-pdf.workspace = true
|
||||
typst-render.workspace = true
|
||||
typst-timing.workspace = true
|
||||
typst-html.workspace = true
|
||||
typst-shim.workspace = true
|
||||
tinymist-preview = { workspace = true, optional = true }
|
||||
typst-ansi-hl.workspace = true
|
||||
tinymist-task.workspace = true
|
||||
tinymist-debug.workspace = true
|
||||
typstfmt.workspace = true
|
||||
typstyle-core.workspace = true
|
||||
unicode-script.workspace = true
|
||||
walkdir.workspace = true
|
||||
tinymist-l10n.workspace = true
|
||||
|
||||
dapts.workspace = true
|
||||
|
||||
[features]
|
||||
default = [
|
||||
"cli",
|
||||
"pdf",
|
||||
"l10n",
|
||||
"lock",
|
||||
"export",
|
||||
"preview",
|
||||
"trace",
|
||||
"embed-fonts",
|
||||
"no-content-hint",
|
||||
"dap",
|
||||
]
|
||||
|
||||
cli = ["sync-ls/clap", "clap/wrap_help"]
|
||||
|
||||
dhat-heap = ["dhat"]
|
||||
|
||||
# Embeds Typst's default fonts for
|
||||
# - text (Linux Libertine),
|
||||
# - math (New Computer Modern Math), and
|
||||
# - code (Deja Vu Sans Mono)
|
||||
# and additionally New Computer Modern for text
|
||||
# into the binary.
|
||||
embed-fonts = ["tinymist-project/fonts"]
|
||||
|
||||
pdf = ["tinymist-task/pdf"]
|
||||
|
||||
# Disable the default content hint.
|
||||
# This requires modifying typst.
|
||||
no-content-hint = [
|
||||
"tinymist-task/no-content-hint",
|
||||
"tinymist-project/no-content-hint",
|
||||
"tinymist-preview/no-content-hint",
|
||||
"typlite/no-content-hint",
|
||||
"reflexo-typst/no-content-hint",
|
||||
"reflexo-vec2svg/no-content-hint",
|
||||
]
|
||||
|
||||
export = ["tinymist/export"]
|
||||
lock = ["tinymist/lock"]
|
||||
preview = ["tinymist/preview", "hyper-tungstenite"]
|
||||
trace = ["tinymist/trace"]
|
||||
|
||||
dap = ["tinymist/dap"]
|
||||
|
||||
l10n = ["tinymist-assets/l10n"]
|
||||
|
||||
[dev-dependencies]
|
||||
temp-env.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
anyhow.workspace = true
|
||||
cargo_metadata = "0.18.0"
|
||||
vergen.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
@ -2,10 +2,19 @@ use std::path::Path;
|
|||
|
||||
use sync_ls::transport::MirrorArgs;
|
||||
use tinymist::project::DocCommands;
|
||||
use tinymist::tool::project::{CompileArgs, GenerateScriptArgs, TaskCommands};
|
||||
use tinymist::tool::testing::TestArgs;
|
||||
use tinymist::LONG_VERSION;
|
||||
use tinymist::{CompileFontArgs, CompileOnceArgs};
|
||||
use tinymist_core::LONG_VERSION;
|
||||
|
||||
#[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()))]
|
||||
|
|
@ -197,3 +206,33 @@ pub enum QueryDocsFormat {
|
|||
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,
|
||||
}
|
||||
88
crates/tinymist-cli/src/compile.rs
Normal file
88
crates/tinymist-cli/src/compile.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
//! Project management tools.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use reflexo::ImmutPath;
|
||||
use reflexo_typst::WorldComputeGraph;
|
||||
use tinymist_std::error::prelude::*;
|
||||
|
||||
use tinymist::project::*;
|
||||
use tinymist::world::system::print_diagnostics;
|
||||
use tinymist::ExportTask;
|
||||
|
||||
/// Arguments for project compilation.
|
||||
#[derive(Debug, Clone, clap::Parser)]
|
||||
pub struct CompileArgs {
|
||||
/// Inherits the compile task arguments.
|
||||
#[clap(flatten)]
|
||||
pub compile: TaskCompileArgs,
|
||||
|
||||
/// Saves the compilation arguments to the lock file.
|
||||
#[clap(long)]
|
||||
pub save_lock: bool,
|
||||
|
||||
/// Specifies the path to the lock file. If the path is
|
||||
/// set, the lock file will be saved.
|
||||
#[clap(long)]
|
||||
pub lockfile: Option<PathBuf>,
|
||||
}
|
||||
|
||||
/// Runs project compilation(s)
|
||||
pub async fn compile_main(args: CompileArgs) -> Result<()> {
|
||||
let cwd = std::env::current_dir().context("cannot get cwd")?;
|
||||
// todo: respect the name of the lock file
|
||||
|
||||
// Saves the lock file if the flags are set
|
||||
let save_lock = args.save_lock || args.lockfile.is_some();
|
||||
|
||||
let lock_dir: ImmutPath = if let Some(lockfile) = args.lockfile {
|
||||
let lockfile = if lockfile.is_absolute() {
|
||||
lockfile
|
||||
} else {
|
||||
cwd.join(lockfile)
|
||||
};
|
||||
lockfile
|
||||
.parent()
|
||||
.context("lock file must have a parent directory")?
|
||||
.into()
|
||||
} else {
|
||||
cwd.as_path().into()
|
||||
};
|
||||
|
||||
// Identifies the input and output
|
||||
let input = args.compile.declare.to_input((&cwd, &lock_dir));
|
||||
let output = args.compile.to_task(input.id.clone(), &cwd)?;
|
||||
|
||||
if save_lock {
|
||||
LockFile::update(&lock_dir, |state| {
|
||||
state.replace_document(input.relative_to(&lock_dir));
|
||||
state.replace_task(output.clone());
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
|
||||
// Prepares for the compilation
|
||||
let universe = (input, lock_dir.clone()).resolve()?;
|
||||
let world = universe.snapshot();
|
||||
let graph = WorldComputeGraph::from_world(world);
|
||||
|
||||
// Compiles the project
|
||||
let is_html = matches!(output.task, ProjectTask::ExportHtml(..));
|
||||
let compiled = CompiledArtifact::from_graph(graph, is_html);
|
||||
|
||||
let diag = compiled.diagnostics();
|
||||
print_diagnostics(compiled.world(), diag, DiagnosticFormat::Human)
|
||||
.context_ut("print diagnostics")?;
|
||||
|
||||
if compiled.has_errors() {
|
||||
// todo: we should process case of compile error in fn main function
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Exports the compiled project
|
||||
let lock_dir = save_lock.then_some(lock_dir);
|
||||
ExportTask::do_export(output.task, compiled, lock_dir).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
225
crates/tinymist-cli/src/generate_script.rs
Normal file
225
crates/tinymist-cli/src/generate_script.rs
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
//! Project management tools.
|
||||
|
||||
use std::{borrow::Cow, path::Path};
|
||||
|
||||
use clap_complete::Shell;
|
||||
use reflexo::path::unix_slash;
|
||||
use tinymist::project::*;
|
||||
use tinymist_std::{bail, error::prelude::*};
|
||||
|
||||
/// Arguments for generating a build script.
|
||||
#[derive(Debug, Clone, clap::Parser)]
|
||||
pub struct GenerateScriptArgs {
|
||||
/// 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>,
|
||||
/// The path to the output script.
|
||||
#[clap(short, long)]
|
||||
pub output: Option<String>,
|
||||
}
|
||||
|
||||
/// Generates a build script for compilation
|
||||
pub fn generate_script_main(args: GenerateScriptArgs) -> Result<()> {
|
||||
let Some(shell) = args.shell.or_else(Shell::from_env) else {
|
||||
bail!("could not infer shell");
|
||||
};
|
||||
let output = Path::new(args.output.as_deref().unwrap_or("build"));
|
||||
|
||||
let output = match shell {
|
||||
Shell::Bash | Shell::Zsh | Shell::Elvish | Shell::Fish => output.with_extension("sh"),
|
||||
Shell::PowerShell => output.with_extension("ps1"),
|
||||
_ => bail!("unsupported shell: {shell:?}"),
|
||||
};
|
||||
|
||||
let script = match shell {
|
||||
Shell::Bash | Shell::Zsh | Shell::PowerShell => shell_build_script(shell)?,
|
||||
_ => bail!("unsupported shell: {shell:?}"),
|
||||
};
|
||||
|
||||
std::fs::write(output, script).context("write script")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generates a build script for shell-like shells
|
||||
fn shell_build_script(shell: Shell) -> Result<String> {
|
||||
let mut output = String::new();
|
||||
|
||||
match shell {
|
||||
Shell::Bash => {
|
||||
output.push_str("#!/usr/bin/env bash\n\n");
|
||||
}
|
||||
Shell::Zsh => {
|
||||
output.push_str("#!/usr/bin/env zsh\n\n");
|
||||
}
|
||||
Shell::PowerShell => {}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let lock_dir = std::env::current_dir().context("current directory")?;
|
||||
|
||||
let lock = LockFile::read(&lock_dir)?;
|
||||
|
||||
struct CmdBuilder(Vec<Cow<'static, str>>);
|
||||
|
||||
impl CmdBuilder {
|
||||
fn new() -> Self {
|
||||
Self(vec![])
|
||||
}
|
||||
|
||||
fn extend(&mut self, args: impl IntoIterator<Item = impl Into<Cow<'static, str>>>) {
|
||||
for arg in args {
|
||||
self.0.push(arg.into());
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, arg: impl Into<Cow<'static, str>>) {
|
||||
self.0.push(arg.into());
|
||||
}
|
||||
|
||||
fn build(self) -> String {
|
||||
self.0.join(" ")
|
||||
}
|
||||
}
|
||||
|
||||
let quote_escape = |s: &str| s.replace("'", r#"'"'"'"#);
|
||||
let quote = |s: &str| format!("'{}'", s.replace("'", r#"'"'"'"#));
|
||||
|
||||
let path_of = |p: &ResourcePath, loc: &str| {
|
||||
let Some(path) = p.to_rel_path(&lock_dir) else {
|
||||
log::error!("could not resolve path for {loc}, path: {p:?}");
|
||||
return String::default();
|
||||
};
|
||||
|
||||
quote(&unix_slash(&path))
|
||||
};
|
||||
|
||||
let base_cmd: Vec<&str> = vec!["tinymist", "compile", "--save-lock"];
|
||||
|
||||
for task in lock.task.iter() {
|
||||
let Some(input) = lock.get_document(&task.document) else {
|
||||
log::warn!(
|
||||
"could not find document for task {:?}, whose document is {:?}",
|
||||
task.id,
|
||||
task.doc_id()
|
||||
);
|
||||
continue;
|
||||
};
|
||||
// todo: preview/query commands
|
||||
let Some(export) = task.task.as_export() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut cmd = CmdBuilder::new();
|
||||
cmd.extend(base_cmd.iter().copied());
|
||||
cmd.push("--task");
|
||||
cmd.push(quote(&task.id.to_string()));
|
||||
|
||||
cmd.push(path_of(&input.main, "main"));
|
||||
|
||||
if let Some(root) = &input.root {
|
||||
cmd.push("--root");
|
||||
cmd.push(path_of(root, "root"));
|
||||
}
|
||||
|
||||
for (k, v) in &input.inputs {
|
||||
cmd.push(format!(
|
||||
r#"--input='{}={}'"#,
|
||||
quote_escape(k),
|
||||
quote_escape(v)
|
||||
));
|
||||
}
|
||||
|
||||
for p in &input.font_paths {
|
||||
cmd.push("--font-path");
|
||||
cmd.push(path_of(p, "font-path"));
|
||||
}
|
||||
|
||||
if !input.system_fonts {
|
||||
cmd.push("--ignore-system-fonts");
|
||||
}
|
||||
|
||||
if let Some(p) = &input.package_path {
|
||||
cmd.push("--package-path");
|
||||
cmd.push(path_of(p, "package-path"));
|
||||
}
|
||||
|
||||
if let Some(p) = &input.package_cache_path {
|
||||
cmd.push("--package-cache-path");
|
||||
cmd.push(path_of(p, "package-cache-path"));
|
||||
}
|
||||
|
||||
if let Some(p) = &export.output {
|
||||
cmd.push("--output");
|
||||
cmd.push(quote(&p.to_string()));
|
||||
}
|
||||
|
||||
for t in &export.transform {
|
||||
match t {
|
||||
ExportTransform::Pretty { .. } => {
|
||||
cmd.push("--pretty");
|
||||
}
|
||||
ExportTransform::Pages { ranges } => {
|
||||
for r in ranges {
|
||||
cmd.push("--pages");
|
||||
cmd.push(r.to_string());
|
||||
}
|
||||
}
|
||||
// todo: export me
|
||||
ExportTransform::Merge { .. } | ExportTransform::Script { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
match &task.task {
|
||||
ProjectTask::Preview(..) | ProjectTask::Query(..) => {}
|
||||
ProjectTask::ExportPdf(task) => {
|
||||
cmd.push("--format=pdf");
|
||||
|
||||
for s in &task.pdf_standards {
|
||||
cmd.push("--pdf-standard");
|
||||
let s = serde_json::to_string(s).context("pdf standard")?;
|
||||
cmd.push(s);
|
||||
}
|
||||
|
||||
if let Some(output) = &task.creation_timestamp {
|
||||
cmd.push("--creation-timestamp");
|
||||
cmd.push(output.to_string());
|
||||
}
|
||||
}
|
||||
ProjectTask::ExportSvg(..) => {
|
||||
cmd.push("--format=svg");
|
||||
}
|
||||
ProjectTask::ExportSvgHtml(..) => {
|
||||
cmd.push("--format=svg_html");
|
||||
}
|
||||
ProjectTask::ExportMd(..) => {
|
||||
cmd.push("--format=md");
|
||||
}
|
||||
ProjectTask::ExportTeX(..) => {
|
||||
cmd.push("--format=tex");
|
||||
}
|
||||
ProjectTask::ExportPng(..) => {
|
||||
cmd.push("--format=png");
|
||||
}
|
||||
ProjectTask::ExportText(..) => {
|
||||
cmd.push("--format=txt");
|
||||
}
|
||||
ProjectTask::ExportHtml(..) => {
|
||||
cmd.push("--format=html");
|
||||
}
|
||||
}
|
||||
|
||||
let ext = task.task.extension();
|
||||
|
||||
output.push_str(&format!(
|
||||
"# From {} to {} ({ext})\n",
|
||||
task.doc_id(),
|
||||
task.id
|
||||
));
|
||||
output.push_str(&cmd.build());
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
21
crates/tinymist-cli/src/lib.rs
Normal file
21
crates/tinymist-cli/src/lib.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
//! # 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,6 +1,13 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
|
||||
mod args;
|
||||
#[cfg(feature = "export")]
|
||||
mod compile;
|
||||
mod generate_script;
|
||||
#[cfg(feature = "preview")]
|
||||
mod preview;
|
||||
mod testing;
|
||||
mod utils;
|
||||
|
||||
use core::fmt;
|
||||
use std::collections::HashMap;
|
||||
|
|
@ -21,22 +28,31 @@ use sync_ls::{
|
|||
internal_error, DapBuilder, DapMessage, GetMessageKind, LsHook, LspBuilder, LspClientRoot,
|
||||
LspMessage, LspResult, Message, RequestId, TConnectionTx,
|
||||
};
|
||||
use tinymist::tool::project::{compile_main, generate_script_main, project_main, task_main};
|
||||
use tinymist::tool::testing::{coverage_main, test_main};
|
||||
use tinymist::world::TaskInputs;
|
||||
use tinymist::{Config, DapRegularInit, RegularInit, ServerState, SuperInit, UserActionTask};
|
||||
use tinymist_core::LONG_VERSION;
|
||||
use tinymist::LONG_VERSION;
|
||||
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};
|
||||
use typst::ecow::EcoString;
|
||||
#[cfg(feature = "preview")]
|
||||
use tinymist_project::LockFile;
|
||||
#[cfg(feature = "preview")]
|
||||
use tinymist_task::Id;
|
||||
|
||||
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};
|
||||
|
||||
#[cfg(feature = "dhat-heap")]
|
||||
#[global_allocator]
|
||||
static ALLOC: dhat::Alloc = dhat::Alloc;
|
||||
|
|
@ -103,19 +119,16 @@ fn main() -> Result<()> {
|
|||
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) => {
|
||||
#[cfg(feature = "preview")]
|
||||
use tinymist::tool::preview::preview_main;
|
||||
|
||||
RUNTIMES.tokio_runtime.block_on(preview_main(args))
|
||||
}
|
||||
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(()),
|
||||
|
|
@ -163,6 +176,7 @@ pub fn lsp_main(args: LspArgs) -> Result<()> {
|
|||
}
|
||||
|
||||
/// 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
|
||||
|
|
@ -175,7 +189,7 @@ pub fn dap_main(args: DapArgs) -> Result<()> {
|
|||
with_stdio_transport::<DapMessage>(args.mirror.clone(), |conn| {
|
||||
let client = client_root(conn.sender);
|
||||
ServerState::install_dap(DapBuilder::new(
|
||||
DapRegularInit {
|
||||
tinymist::DapRegularInit {
|
||||
client: client.weak().to_typed(),
|
||||
font_opts: args.font,
|
||||
},
|
||||
|
|
@ -342,6 +356,85 @@ pub fn query_main(cmds: QueryCommands) -> Result<()> {
|
|||
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>,
|
||||
170
crates/tinymist-cli/src/preview.rs
Normal file
170
crates/tinymist-cli/src/preview.rs
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use hyper_tungstenite::tungstenite::Message;
|
||||
use tinymist::{
|
||||
project::ProjectPreviewState,
|
||||
tool::{
|
||||
preview::{bind_streams, make_http_server, PreviewCliArgs, ProjectPreviewHandler},
|
||||
project::{start_project, ProjectOpts, StartProjectResult},
|
||||
},
|
||||
};
|
||||
use tinymist_assets::TYPST_PREVIEW_HTML;
|
||||
use tinymist_preview::{
|
||||
frontend_html, ControlPlaneMessage, ControlPlaneTx, PreviewBuilder, PreviewConfig,
|
||||
};
|
||||
use tinymist_project::WorldProvider;
|
||||
use tinymist_std::error::prelude::*;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::utils::exit_on_ctrl_c;
|
||||
|
||||
/// Entry point of the preview tool.
|
||||
pub async fn preview_main(args: PreviewCliArgs) -> Result<()> {
|
||||
log::info!("Arguments: {args:#?}");
|
||||
let handle = tokio::runtime::Handle::current();
|
||||
|
||||
let config = args.preview.config(&PreviewConfig::default());
|
||||
#[cfg(feature = "open")]
|
||||
let open_in_browser = args.open_in_browser(true);
|
||||
let static_file_host =
|
||||
if args.static_file_host == args.data_plane_host || !args.static_file_host.is_empty() {
|
||||
Some(args.static_file_host)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
exit_on_ctrl_c();
|
||||
|
||||
let verse = args.compile.resolve()?;
|
||||
let previewer = PreviewBuilder::new(config);
|
||||
|
||||
let (service, handle) = {
|
||||
let preview_state = ProjectPreviewState::default();
|
||||
let opts = ProjectOpts {
|
||||
handle: Some(handle),
|
||||
preview: preview_state.clone(),
|
||||
..ProjectOpts::default()
|
||||
};
|
||||
|
||||
let StartProjectResult {
|
||||
service,
|
||||
intr_tx,
|
||||
mut editor_rx,
|
||||
} = start_project(verse, Some(opts), |compiler, intr, next| {
|
||||
next(compiler, intr)
|
||||
});
|
||||
|
||||
// Consume editor_rx
|
||||
tokio::spawn(async move { while editor_rx.recv().await.is_some() {} });
|
||||
|
||||
let id = service.compiler.primary.id.clone();
|
||||
let registered = preview_state.register(&id, previewer.compile_watcher(args.task_id));
|
||||
if !registered {
|
||||
tinymist_std::bail!("failed to register preview");
|
||||
}
|
||||
|
||||
let handle: Arc<ProjectPreviewHandler> = Arc::new(ProjectPreviewHandler {
|
||||
project_id: id,
|
||||
client: Box::new(intr_tx),
|
||||
});
|
||||
|
||||
(service, handle)
|
||||
};
|
||||
|
||||
let (lsp_tx, mut lsp_rx) = ControlPlaneTx::new(true);
|
||||
|
||||
let control_plane_server_handle = tokio::spawn(async move {
|
||||
let (control_sock_tx, mut control_sock_rx) = mpsc::unbounded_channel();
|
||||
|
||||
let srv =
|
||||
make_http_server(String::default(), args.control_plane_host, control_sock_tx).await;
|
||||
log::info!("Control panel server listening on: {}", srv.addr);
|
||||
|
||||
let control_websocket = control_sock_rx.recv().await.unwrap();
|
||||
let ws = control_websocket.await.unwrap();
|
||||
|
||||
tokio::pin!(ws);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some(resp) = lsp_rx.resp_rx.recv() => {
|
||||
let r = ws
|
||||
.send(Message::Text(serde_json::to_string(&resp).unwrap()))
|
||||
.await;
|
||||
let Err(err) = r else {
|
||||
continue;
|
||||
};
|
||||
|
||||
log::warn!("failed to send response to editor {err:?}");
|
||||
break;
|
||||
|
||||
}
|
||||
msg = ws.next() => {
|
||||
let msg = match msg {
|
||||
Some(Ok(Message::Text(msg))) => Some(msg),
|
||||
Some(Ok(msg)) => {
|
||||
log::error!("unsupported message: {msg:?}");
|
||||
break;
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
log::error!("failed to receive message: {e}");
|
||||
break;
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(msg) = msg {
|
||||
let Ok(msg) = serde_json::from_str::<ControlPlaneMessage>(&msg) else {
|
||||
log::warn!("failed to parse control plane request: {msg:?}");
|
||||
break;
|
||||
};
|
||||
|
||||
lsp_rx.ctl_tx.send(msg).unwrap();
|
||||
} else {
|
||||
// todo: inform the editor that the connection is closed.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
let _ = srv.shutdown_tx.send(());
|
||||
let _ = srv.join.await;
|
||||
});
|
||||
|
||||
let (websocket_tx, websocket_rx) = mpsc::unbounded_channel();
|
||||
let mut previewer = previewer.build(lsp_tx, handle.clone()).await;
|
||||
tokio::spawn(service.run());
|
||||
|
||||
bind_streams(&mut previewer, websocket_rx);
|
||||
|
||||
let frontend_html = frontend_html(TYPST_PREVIEW_HTML, args.preview.preview_mode, "/");
|
||||
|
||||
let static_server = if let Some(static_file_host) = static_file_host {
|
||||
log::warn!("--static-file-host is deprecated, which will be removed in the future. Use --data-plane-host instead.");
|
||||
let html = frontend_html.clone();
|
||||
Some(make_http_server(html, static_file_host, websocket_tx.clone()).await)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let srv = make_http_server(frontend_html, args.data_plane_host, websocket_tx).await;
|
||||
log::info!("Data plane server listening on: {}", srv.addr);
|
||||
|
||||
let static_server_addr = static_server.as_ref().map(|s| s.addr).unwrap_or(srv.addr);
|
||||
log::info!("Static file server listening on: {static_server_addr}");
|
||||
|
||||
#[cfg(feature = "open")]
|
||||
if open_in_browser {
|
||||
open::that_detached(format!("http://{static_server_addr}"))
|
||||
.log_error("failed to open browser for preview");
|
||||
}
|
||||
|
||||
let _ = tokio::join!(previewer.join(), srv.join, control_plane_server_handle);
|
||||
// Assert that the static server's lifetime is longer than the previewer.
|
||||
let _s = static_server;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -24,9 +24,11 @@ use typst::syntax::{ast, LinkedNode, Source, Span};
|
|||
use typst::{utils::PicoStr, World};
|
||||
use typst_shim::eval::TypstEngine;
|
||||
|
||||
use super::project::{start_project, StartProjectResult};
|
||||
use crate::world::{with_main, SourceWorld};
|
||||
use crate::{project::*, utils::exit_on_ctrl_c};
|
||||
use tinymist::project::*;
|
||||
use tinymist::tool::project::{start_project, StartProjectResult};
|
||||
use tinymist::world::{with_main, SourceWorld};
|
||||
|
||||
use crate::utils::exit_on_ctrl_c;
|
||||
|
||||
const TEST_EVICT_MAX_AGE: usize = 30;
|
||||
const PREFIX_LEN: usize = 7;
|
||||
7
crates/tinymist-cli/src/utils.rs
Normal file
7
crates/tinymist-cli/src/utils.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
pub fn exit_on_ctrl_c() {
|
||||
tokio::spawn(async move {
|
||||
let _ = tokio::signal::ctrl_c().await;
|
||||
log::info!("Ctrl-C received, exiting");
|
||||
std::process::exit(0);
|
||||
});
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
[package]
|
||||
name = "tinymist-core"
|
||||
description = "Tinymist core library."
|
||||
categories = ["compilers", "command-line-utilities"]
|
||||
keywords = ["api", "language", "typst"]
|
||||
authors.workspace = true
|
||||
version.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
default = ["web", "no-content-hint"]
|
||||
|
||||
web = [
|
||||
"wasm-bindgen",
|
||||
"js-sys",
|
||||
"console_error_panic_hook",
|
||||
"no-content-hint",
|
||||
"tinymist-project/web",
|
||||
"reflexo-typst/web",
|
||||
]
|
||||
|
||||
no-content-hint = ["reflexo-typst/no-content-hint"]
|
||||
|
||||
[dependencies]
|
||||
wasm-bindgen = { version = "0.2.100", optional = true }
|
||||
js-sys = { version = "0.3.77", optional = true }
|
||||
tinymist-project.workspace = true
|
||||
tinymist-query.workspace = true
|
||||
reflexo-typst.workspace = true
|
||||
|
||||
console_error_panic_hook = { version = "0.1.2", optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
anyhow.workspace = true
|
||||
vergen.workspace = true
|
||||
cargo_metadata = "0.18.0"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
//! Tinymist Core Library
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
/// The long version description of the library
|
||||
pub static LONG_VERSION: LazyLock<String> = LazyLock::new(|| {
|
||||
format!(
|
||||
"
|
||||
Build Timestamp: {}
|
||||
Build Git Describe: {}
|
||||
Commit SHA: {}
|
||||
Commit Date: {}
|
||||
Commit Branch: {}
|
||||
Cargo Target Triple: {}
|
||||
Typst Version: {}
|
||||
Typst Source: {}
|
||||
",
|
||||
env!("VERGEN_BUILD_TIMESTAMP"),
|
||||
env!("VERGEN_GIT_DESCRIBE"),
|
||||
option_env!("VERGEN_GIT_SHA").unwrap_or("None"),
|
||||
option_env!("VERGEN_GIT_COMMIT_TIMESTAMP").unwrap_or("None"),
|
||||
option_env!("VERGEN_GIT_BRANCH").unwrap_or("None"),
|
||||
env!("VERGEN_CARGO_TARGET_TRIPLE"),
|
||||
env!("TYPST_VERSION"),
|
||||
env!("TYPST_SOURCE"),
|
||||
)
|
||||
});
|
||||
|
||||
#[cfg(feature = "web")]
|
||||
pub mod web;
|
||||
|
|
@ -282,6 +282,28 @@ impl LspUniverseBuilder {
|
|||
Ok(searcher.build())
|
||||
}
|
||||
|
||||
/// Resolve fonts from given options.
|
||||
#[cfg(all(not(feature = "system"), feature = "web"))]
|
||||
pub fn resolve_fonts(args: CompileFontArgs) -> Result<FontResolverImpl> {
|
||||
let mut searcher = tinymist_world::font::web::BrowserFontSearcher::new();
|
||||
searcher.resolve_opts(tinymist_world::config::CompileFontOpts {
|
||||
font_paths: args.font_paths,
|
||||
no_system_fonts: args.ignore_system_fonts,
|
||||
with_embedded_fonts: typst_assets::fonts()
|
||||
.map(std::borrow::Cow::Borrowed)
|
||||
.collect(),
|
||||
})?;
|
||||
Ok(searcher.build())
|
||||
}
|
||||
|
||||
/// Resolve fonts from given options.
|
||||
#[cfg(not(any(feature = "system", feature = "web")))]
|
||||
pub fn resolve_fonts(_args: CompileFontArgs) -> Result<FontResolverImpl> {
|
||||
let mut searcher = tinymist_world::font::memory::MemoryFontSearcher::default();
|
||||
searcher.add_memory_fonts(typst_assets::fonts().map(Bytes::new).collect::<Vec<_>>());
|
||||
Ok(searcher.build())
|
||||
}
|
||||
|
||||
/// Resolves package registry from given options.
|
||||
#[cfg(feature = "system")]
|
||||
pub fn resolve_package(
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ sha2 = { version = "0.10" }
|
|||
hex = { version = "0.4" }
|
||||
|
||||
[features]
|
||||
local-registry = ["tinymist-world/system"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ impl CompletionPair<'_, '_, '_> {
|
|||
.iter()
|
||||
.map(|(spec, desc)| (spec, desc.clone()))
|
||||
.collect();
|
||||
#[cfg(feature = "http-registry")]
|
||||
{
|
||||
// local_packages to references and add them to the packages
|
||||
let local_packages_refs = self.worker.ctx.local_packages();
|
||||
packages.extend(
|
||||
|
|
@ -41,6 +43,7 @@ impl CompletionPair<'_, '_, '_> {
|
|||
.iter()
|
||||
.map(|spec| (spec, Some(eco_format!("{} v{}", spec.name, spec.version)))),
|
||||
);
|
||||
}
|
||||
|
||||
packages.sort_by_key(|(spec, _)| (&spec.namespace, &spec.name, Reverse(spec.version)));
|
||||
if !all_versions {
|
||||
|
|
|
|||
|
|
@ -672,7 +672,7 @@ impl SharedContext {
|
|||
}
|
||||
|
||||
/// Get the local packages and their descriptions.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(feature = "local-registry")]
|
||||
pub fn local_packages(&self) -> EcoVec<PackageSpec> {
|
||||
crate::package::list_package_by_namespace(&self.world.registry, eco_format!("local"))
|
||||
.into_iter()
|
||||
|
|
@ -681,7 +681,7 @@ impl SharedContext {
|
|||
}
|
||||
|
||||
/// Get the local packages and their descriptions.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(not(feature = "local-registry"))]
|
||||
pub fn local_packages(&self) -> EcoVec<PackageSpec> {
|
||||
eco_vec![]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,14 +43,14 @@ pub fn path_res_to_url(path: PathResolution) -> anyhow::Result<Url> {
|
|||
}
|
||||
|
||||
/// Convert a URL to a path.
|
||||
pub fn url_to_path(uri: Url) -> PathBuf {
|
||||
pub fn url_to_path(uri: &Url) -> PathBuf {
|
||||
if uri.scheme() == "file" {
|
||||
// typst converts an empty path to `Path::new("/")`, which is undesirable.
|
||||
if !uri.has_host() && uri.path() == "/" {
|
||||
return PathBuf::from("/untitled/nEoViM-BuG");
|
||||
}
|
||||
|
||||
return url_to_file_path(&uri);
|
||||
return url_to_file_path(uri);
|
||||
}
|
||||
|
||||
if uri.scheme() == "untitled" {
|
||||
|
|
@ -67,7 +67,7 @@ pub fn url_to_path(uri: Url) -> PathBuf {
|
|||
return Path::new(String::from_utf8_lossy(&bytes).as_ref()).clean();
|
||||
}
|
||||
|
||||
url_to_file_path(&uri)
|
||||
url_to_file_path(uri)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
|
@ -114,7 +114,7 @@ mod test {
|
|||
assert_eq!(uri.scheme(), "untitled");
|
||||
assert_eq!(uri.path(), "test");
|
||||
|
||||
let path = url_to_path(uri);
|
||||
let path = url_to_path(&uri);
|
||||
assert_eq!(path, Path::new("/untitled/test").clean());
|
||||
}
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ mod test {
|
|||
fn unnamed_buffer() {
|
||||
// https://github.com/neovim/nvim-lspconfig/pull/2226
|
||||
let uri = EMPTY_URL.clone();
|
||||
let path = url_to_path(uri);
|
||||
let path = url_to_path(&uri);
|
||||
assert_eq!(path, Path::new("/untitled/nEoViM-BuG"));
|
||||
|
||||
let uri2 = path_to_url(&path).unwrap();
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ pub fn check_package(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<()
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(feature = "local-registry")]
|
||||
/// Get the packages in namespaces and their descriptions.
|
||||
pub fn list_package_by_namespace(
|
||||
registry: &tinymist_world::package::registry::HttpRegistry,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use rayon::iter::ParallelIterator;
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
use typst::foundations::Bytes;
|
||||
use typst::text::{FontBook, FontInfo};
|
||||
|
||||
|
|
@ -25,17 +25,41 @@ impl MemoryFontSearcher {
|
|||
Self { fonts: vec![] }
|
||||
}
|
||||
|
||||
/// Create a new browser searcher with fonts in a FontResolverImpl.
|
||||
pub fn from_resolver(resolver: FontResolverImpl) -> Self {
|
||||
let fonts = resolver
|
||||
.slots
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(idx, slot)| {
|
||||
(
|
||||
resolver
|
||||
.book
|
||||
.info(idx)
|
||||
.expect("font should be in font book")
|
||||
.clone(),
|
||||
slot,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Self { fonts }
|
||||
}
|
||||
|
||||
/// Adds an in-memory font.
|
||||
pub fn add_memory_font(&mut self, data: Bytes) {
|
||||
self.add_memory_fonts(rayon::iter::once(data));
|
||||
}
|
||||
|
||||
/// Adds in-memory fonts.
|
||||
pub fn add_memory_fonts(&mut self, data: impl ParallelIterator<Item = Bytes>) {
|
||||
pub fn add_memory_fonts(&mut self, data: impl IntoParallelIterator<Item = Bytes>) {
|
||||
let source = DataSource::Memory(MemoryDataSource {
|
||||
name: "<memory>".to_owned(),
|
||||
});
|
||||
self.extend_bytes(data.map(|data| (data, Some(source.clone()))));
|
||||
self.extend_bytes(
|
||||
data.into_par_iter()
|
||||
.map(|data| (data, Some(source.clone()))),
|
||||
);
|
||||
}
|
||||
|
||||
/// Adds a number of raw font resources.
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use js_sys::ArrayBuffer;
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
use tinymist_std::error::prelude::*;
|
||||
use typst::foundations::Bytes;
|
||||
use typst::text::{
|
||||
Coverage, Font, FontBook, FontFlags, FontInfo, FontStretch, FontStyle, FontVariant, FontWeight,
|
||||
Coverage, Font, FontFlags, FontInfo, FontStretch, FontStyle, FontVariant, FontWeight,
|
||||
};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use super::{BufferFontLoader, FontLoader, FontResolverImpl, FontSlot};
|
||||
use crate::config::CompileFontOpts;
|
||||
use crate::font::cache::FontInfoCache;
|
||||
use crate::font::info::typst_typographic_family;
|
||||
use crate::font::memory::MemoryFontSearcher;
|
||||
|
||||
/// Destructures a JS `[key, value]` pair into a tuple of [`Deserializer`]s.
|
||||
pub(crate) fn convert_pair(pair: JsValue) -> (JsValue, JsValue) {
|
||||
|
|
@ -365,71 +370,47 @@ impl FontLoader for WebFontLoader {
|
|||
|
||||
/// Searches for fonts.
|
||||
pub struct BrowserFontSearcher {
|
||||
pub fonts: Vec<(FontInfo, FontSlot)>,
|
||||
/// The base font searcher.
|
||||
base: MemoryFontSearcher,
|
||||
}
|
||||
|
||||
impl BrowserFontSearcher {
|
||||
/// Create a new, empty browser searcher.
|
||||
/// Creates a new, empty browser searcher.
|
||||
pub fn new() -> Self {
|
||||
Self { fonts: vec![] }
|
||||
Self {
|
||||
base: MemoryFontSearcher::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new browser searcher with fonts in a FontResolverImpl.
|
||||
/// Creates a new browser searcher with fonts in a FontResolverImpl.
|
||||
pub fn from_resolver(resolver: FontResolverImpl) -> Self {
|
||||
let fonts = resolver
|
||||
.slots
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(idx, slot)| {
|
||||
(
|
||||
resolver
|
||||
.book
|
||||
.info(idx)
|
||||
.expect("font should be in font book")
|
||||
.clone(),
|
||||
slot,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Self { fonts }
|
||||
let base = MemoryFontSearcher::from_resolver(resolver);
|
||||
Self { base }
|
||||
}
|
||||
|
||||
/// Create a new browser searcher with fonts cloned from a FontResolverImpl.
|
||||
/// Since FontSlot only holds QueryRef to font data, cloning is cheap.
|
||||
pub fn new_with_resolver(resolver: &FontResolverImpl) -> Self {
|
||||
let fonts = resolver
|
||||
.slots
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, slot)| {
|
||||
(
|
||||
resolver
|
||||
.book
|
||||
.info(idx)
|
||||
.expect("font should be in font book")
|
||||
.clone(),
|
||||
slot.clone(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Self { fonts }
|
||||
}
|
||||
|
||||
/// Build a FontResolverImpl.
|
||||
/// Builds a FontResolverImpl.
|
||||
pub fn build(self) -> FontResolverImpl {
|
||||
let (info, slots): (Vec<FontInfo>, Vec<FontSlot>) = self.fonts.into_iter().unzip();
|
||||
|
||||
let book = FontBook::from_infos(info);
|
||||
|
||||
FontResolverImpl::new(vec![], book, slots)
|
||||
self.base.build()
|
||||
}
|
||||
}
|
||||
|
||||
impl BrowserFontSearcher {
|
||||
/// Add fonts that are embedded in the binary.
|
||||
/// Resolves fonts from given options.
|
||||
pub fn resolve_opts(&mut self, opts: CompileFontOpts) -> Result<()> {
|
||||
// Source3: add the fonts in memory.
|
||||
self.add_memory_fonts(opts.with_embedded_fonts.into_par_iter().map(|font_data| {
|
||||
match font_data {
|
||||
Cow::Borrowed(data) => Bytes::new(data),
|
||||
Cow::Owned(data) => Bytes::new(data),
|
||||
}
|
||||
}));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds fonts that are embedded in the binary.
|
||||
#[cfg(feature = "fonts")]
|
||||
#[deprecated(note = "use `typst_assets::fonts` directly")]
|
||||
pub fn add_embedded(&mut self) {
|
||||
for font_data in typst_assets::fonts() {
|
||||
let buffer = Bytes::new(font_data);
|
||||
|
|
@ -441,6 +422,11 @@ impl BrowserFontSearcher {
|
|||
}
|
||||
}
|
||||
|
||||
/// Adds in-memory fonts.
|
||||
pub fn add_memory_fonts(&mut self, data: impl ParallelIterator<Item = Bytes>) {
|
||||
self.base.add_memory_fonts(data);
|
||||
}
|
||||
|
||||
pub async fn add_web_fonts(&mut self, fonts: js_sys::Array) -> Result<()> {
|
||||
let font_builder = FontBuilder {};
|
||||
|
||||
|
|
@ -448,8 +434,8 @@ impl BrowserFontSearcher {
|
|||
let (font_ref, font_blob_loader, font_info) = font_builder.font_web_to_typst(&v)?;
|
||||
|
||||
for (i, info) in font_info.into_iter().enumerate() {
|
||||
let index = self.fonts.len();
|
||||
self.fonts.push((
|
||||
let index = self.base.fonts.len();
|
||||
self.base.fonts.push((
|
||||
info.clone(),
|
||||
FontSlot::new(WebFontLoader {
|
||||
font: WebFont {
|
||||
|
|
@ -470,7 +456,7 @@ impl BrowserFontSearcher {
|
|||
pub fn add_font_data(&mut self, buffer: Bytes) {
|
||||
for (i, info) in FontInfo::iter(buffer.as_slice()).enumerate() {
|
||||
let buffer = buffer.clone();
|
||||
self.fonts.push((
|
||||
self.base.fonts.push((
|
||||
info,
|
||||
FontSlot::new(BufferFontLoader {
|
||||
buffer: Some(buffer),
|
||||
|
|
@ -481,7 +467,7 @@ impl BrowserFontSearcher {
|
|||
}
|
||||
|
||||
pub fn with_fonts_mut(&mut self, func: impl FnOnce(&mut Vec<(FontInfo, FontSlot)>)) {
|
||||
func(&mut self.fonts);
|
||||
func(&mut self.base.fonts);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
name = "tinymist"
|
||||
description = "An integrated language service for Typst."
|
||||
categories = ["compilers", "command-line-utilities"]
|
||||
keywords = ["cli", "lsp", "language", "typst"]
|
||||
keywords = ["api", "language", "typst"]
|
||||
authors.workspace = true
|
||||
version.workspace = true
|
||||
license.workspace = true
|
||||
|
|
@ -11,6 +11,9 @@ homepage.workspace = true
|
|||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
|
|
@ -25,29 +28,20 @@ clap_mangen.workspace = true
|
|||
crossbeam-channel.workspace = true
|
||||
codespan-reporting.workspace = true
|
||||
comemo.workspace = true
|
||||
dapts.workspace = true
|
||||
dhat = { workspace = true, optional = true }
|
||||
dirs.workspace = true
|
||||
env_logger.workspace = true
|
||||
futures.workspace = true
|
||||
hyper.workspace = true
|
||||
hyper-util = { workspace = true, features = [
|
||||
"server",
|
||||
"http1",
|
||||
"http2",
|
||||
"server-graceful",
|
||||
"server-auto",
|
||||
] }
|
||||
http-body-util = "0.1.2"
|
||||
hyper-tungstenite = { workspace = true, optional = true }
|
||||
itertools.workspace = true
|
||||
lsp-types.workspace = true
|
||||
log.workspace = true
|
||||
open.workspace = true
|
||||
open = { workspace = true, optional = true }
|
||||
parking_lot.workspace = true
|
||||
paste.workspace = true
|
||||
rayon.workspace = true
|
||||
reflexo.workspace = true
|
||||
reflexo-typst = { workspace = true, features = ["system", "svg"] }
|
||||
reflexo-typst = { workspace = true, features = ["svg"] }
|
||||
reflexo-vec2svg.workspace = true
|
||||
rpds.workspace = true
|
||||
serde.workspace = true
|
||||
|
|
@ -56,12 +50,15 @@ serde_yaml.workspace = true
|
|||
strum.workspace = true
|
||||
sync-ls = { workspace = true, features = ["lsp", "server"] }
|
||||
tinymist-assets = { workspace = true }
|
||||
tinymist-debug = { workspace = true, optional = true }
|
||||
tinymist-l10n.workspace = true
|
||||
tinymist-query.workspace = true
|
||||
tinymist-std.workspace = true
|
||||
tinymist-core = { workspace = true, default-features = false, features = [] }
|
||||
tinymist-preview = { workspace = true, optional = true }
|
||||
tinymist-project = { workspace = true, features = ["lsp"] }
|
||||
tinymist-render.workspace = true
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "io-std"] }
|
||||
tinymist-task.workspace = true
|
||||
tokio = { workspace = true }
|
||||
tokio-util.workspace = true
|
||||
toml.workspace = true
|
||||
ttf-parser.workspace = true
|
||||
|
|
@ -71,78 +68,73 @@ typst-svg.workspace = true
|
|||
typst-pdf.workspace = true
|
||||
typst-render.workspace = true
|
||||
typst-timing.workspace = true
|
||||
typst-html = { workspace = true, optional = true }
|
||||
typst-html.workspace = true
|
||||
typst-shim.workspace = true
|
||||
tinymist-preview = { workspace = true, optional = true }
|
||||
typst-ansi-hl.workspace = true
|
||||
tinymist-task.workspace = true
|
||||
tinymist-debug.workspace = true
|
||||
typstfmt.workspace = true
|
||||
typstyle-core.workspace = true
|
||||
unicode-script.workspace = true
|
||||
walkdir.workspace = true
|
||||
tinymist-l10n.workspace = true
|
||||
|
||||
dapts.workspace = true
|
||||
http-body-util = { workspace = true, optional = true }
|
||||
hyper = { workspace = true, optional = true }
|
||||
hyper-util = { workspace = true, optional = true, features = [
|
||||
"server",
|
||||
"http1",
|
||||
"http2",
|
||||
"server-graceful",
|
||||
"server-auto",
|
||||
] }
|
||||
hyper-tungstenite = { workspace = true, optional = true }
|
||||
|
||||
[features]
|
||||
default = [
|
||||
"cli",
|
||||
"html",
|
||||
"pdf",
|
||||
"l10n",
|
||||
"preview",
|
||||
"embed-fonts",
|
||||
"no-content-hint",
|
||||
"dap",
|
||||
]
|
||||
|
||||
cli = ["sync-ls/clap", "clap/wrap_help"]
|
||||
|
||||
dhat-heap = ["dhat"]
|
||||
|
||||
# Embeds Typst's default fonts for
|
||||
# - text (Linux Libertine),
|
||||
# - math (New Computer Modern Math), and
|
||||
# - code (Deja Vu Sans Mono)
|
||||
# and additionally New Computer Modern for text
|
||||
# into the binary.
|
||||
embed-fonts = ["tinymist-project/fonts"]
|
||||
|
||||
# Enable the experimental HTML backend.
|
||||
html = ["dep:typst-html"]
|
||||
|
||||
pdf = ["tinymist-task/pdf"]
|
||||
|
||||
# Disable the default content hint.
|
||||
# This requires modifying typst.
|
||||
no-content-hint = [
|
||||
"tinymist-task/no-content-hint",
|
||||
"tinymist-project/no-content-hint",
|
||||
"tinymist-preview/no-content-hint",
|
||||
"typlite/no-content-hint",
|
||||
"reflexo-typst/no-content-hint",
|
||||
"reflexo-vec2svg/no-content-hint",
|
||||
]
|
||||
|
||||
preview = [
|
||||
"tinymist-preview",
|
||||
"tinymist-preview/clap",
|
||||
"tinymist-assets/typst-preview",
|
||||
"hyper-tungstenite",
|
||||
]
|
||||
|
||||
dap = ["sync-ls/dap"]
|
||||
|
||||
l10n = ["tinymist-assets/l10n"]
|
||||
console_error_panic_hook = { version = "0.1.2", optional = true }
|
||||
js-sys = { version = "0.3.77", optional = true }
|
||||
wasm-bindgen = { version = "0.2.100", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
temp-env.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
anyhow.workspace = true
|
||||
cargo_metadata = "0.18.0"
|
||||
vergen.workspace = true
|
||||
cargo_metadata = "0.18.0"
|
||||
|
||||
[features]
|
||||
dap = ["sync-ls/dap", "tinymist-debug"]
|
||||
default = ["web", "no-content-hint"]
|
||||
preview = [
|
||||
"open",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"hyper-tungstenite",
|
||||
"tinymist-preview",
|
||||
"tinymist-preview/clap",
|
||||
"tinymist-assets/typst-preview",
|
||||
]
|
||||
lock = []
|
||||
export = []
|
||||
trace = []
|
||||
web = [
|
||||
"console_error_panic_hook",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"reflexo-typst/web",
|
||||
"tinymist-project/web",
|
||||
]
|
||||
open = ["dep:open"]
|
||||
system = [
|
||||
"lock",
|
||||
"open",
|
||||
"reflexo-typst/system",
|
||||
"tinymist-project/system",
|
||||
"tinymist-query/local-registry",
|
||||
"sync-ls/system",
|
||||
"tokio/rt-multi-thread",
|
||||
"tokio/io-std",
|
||||
]
|
||||
|
||||
no-content-hint = ["reflexo-typst/no-content-hint"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
|||
|
|
@ -9,19 +9,19 @@
|
|||
"Typst"
|
||||
],
|
||||
"type": "module",
|
||||
"module": "pkg/tinymist_core.js",
|
||||
"types": "pkg/tinymist_core.d.ts",
|
||||
"module": "pkg/tinymist.js",
|
||||
"types": "pkg/tinymist.d.ts",
|
||||
"files": [
|
||||
"pkg/tinymist_core_bg.wasm",
|
||||
"pkg/tinymist_core_bg.wasm.d.ts",
|
||||
"pkg/tinymist_core_bg.js",
|
||||
"pkg/tinymist_core.js",
|
||||
"pkg/tinymist_core.d.ts"
|
||||
"pkg/tinymist_bg.wasm",
|
||||
"pkg/tinymist_bg.wasm.d.ts",
|
||||
"pkg/tinymist_bg.js",
|
||||
"pkg/tinymist.js",
|
||||
"pkg/tinymist.d.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"build:dev": "wasm-pack build --target web --dev -- --no-default-features --features web",
|
||||
"build:node": "wasm-pack build --target nodejs -- --no-default-features --features web",
|
||||
"build": "wasm-pack build --target web -- --no-default-features --features web",
|
||||
"build:dev": "wasm-pack build --target web --dev -- --no-default-features --features web,no-content-hint",
|
||||
"build:node": "wasm-pack build --target nodejs -- --no-default-features --features web,no-content-hint",
|
||||
"build": "wasm-pack build --target web -- --no-default-features --features web,no-content-hint",
|
||||
"publish:dry": "npm publish --dry-run",
|
||||
"publish:lib": "npm publish || exit 0",
|
||||
"test:chrome": "wasm-pack test --chrome --headless --release",
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
//! Tinymist LSP commands
|
||||
|
||||
use std::ops::{Deref, Range};
|
||||
use std::ops::Range;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use lsp_types::TextDocumentIdentifier;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
use sync_ls::RequestId;
|
||||
#[cfg(feature = "trace")]
|
||||
use task::TraceParams;
|
||||
use tinymist_assets::TYPST_PREVIEW_HTML;
|
||||
use tinymist_project::{
|
||||
|
|
@ -17,14 +18,20 @@ use tinymist_query::package::PackageInfo;
|
|||
use tinymist_query::{LocalContextGuard, LspRange};
|
||||
use tinymist_std::error::prelude::*;
|
||||
use tinymist_task::ExportMarkdownTask;
|
||||
use typst::diag::{eco_format, EcoString, StrResult};
|
||||
use typst::syntax::package::{PackageSpec, VersionlessPackageSpec};
|
||||
use typst::diag::{eco_format, StrResult};
|
||||
use typst::syntax::{LinkedNode, Source};
|
||||
use world::TaskInputs;
|
||||
|
||||
use super::*;
|
||||
use crate::lsp::query::{run_query, LspClientExt};
|
||||
use crate::tool::ast::AstRepr;
|
||||
|
||||
#[cfg(feature = "system")]
|
||||
use typst::diag::EcoString;
|
||||
#[cfg(feature = "system")]
|
||||
use typst::syntax::package::{PackageSpec, VersionlessPackageSpec};
|
||||
|
||||
#[cfg(feature = "system")]
|
||||
use crate::tool::package::InitTask;
|
||||
|
||||
/// See [`ProjectTask`].
|
||||
|
|
@ -416,10 +423,11 @@ impl ServerState {
|
|||
}
|
||||
|
||||
/// Initialize a new template.
|
||||
#[cfg(feature = "system")]
|
||||
pub fn init_template(&mut self, mut args: Vec<JsonValue>) -> AnySchedulableResponse {
|
||||
use crate::tool::package::{self, TemplateSource};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct InitResult {
|
||||
entry_path: PathBuf,
|
||||
|
|
@ -466,6 +474,7 @@ impl ServerState {
|
|||
}
|
||||
|
||||
/// Get the entry of a template.
|
||||
#[cfg(feature = "system")]
|
||||
pub fn get_template_entry(&mut self, mut args: Vec<JsonValue>) -> AnySchedulableResponse {
|
||||
use crate::tool::package::{self, TemplateSource};
|
||||
|
||||
|
|
@ -528,7 +537,9 @@ impl ServerState {
|
|||
}
|
||||
|
||||
/// Get the trace data of the document.
|
||||
#[cfg(feature = "trace")]
|
||||
pub fn get_document_trace(&mut self, mut args: Vec<JsonValue>) -> AnySchedulableResponse {
|
||||
use std::ops::Deref;
|
||||
let path = get_arg!(args[0] as PathBuf).into();
|
||||
|
||||
// get path to self program
|
||||
|
|
@ -573,6 +584,7 @@ impl ServerState {
|
|||
}
|
||||
|
||||
/// Start to get the trace data of the server.
|
||||
#[cfg(feature = "trace")]
|
||||
pub fn start_server_trace(&mut self, _args: Vec<JsonValue>) -> AnySchedulableResponse {
|
||||
let task_cell = &mut self.server_trace;
|
||||
if task_cell
|
||||
|
|
@ -595,6 +607,7 @@ impl ServerState {
|
|||
}
|
||||
|
||||
/// Stop getting the trace data of the server.
|
||||
#[cfg(feature = "trace")]
|
||||
pub fn stop_server_trace(&mut self, _args: Vec<JsonValue>) -> AnySchedulableResponse {
|
||||
let task_cell = &mut self.server_trace;
|
||||
if task_cell
|
||||
|
|
@ -671,6 +684,7 @@ impl ServerState {
|
|||
}
|
||||
|
||||
/// Get directory of packages
|
||||
#[cfg(feature = "system")]
|
||||
pub fn resource_package_dirs(&mut self, _arguments: Vec<JsonValue>) -> AnySchedulableResponse {
|
||||
let snap = self.snapshot().map_err(internal_error)?;
|
||||
just_future(async move {
|
||||
|
|
@ -681,6 +695,7 @@ impl ServerState {
|
|||
}
|
||||
|
||||
/// Get writable directory of packages
|
||||
#[cfg(feature = "system")]
|
||||
pub fn resource_local_package_dir(
|
||||
&mut self,
|
||||
_arguments: Vec<JsonValue>,
|
||||
|
|
@ -694,6 +709,7 @@ impl ServerState {
|
|||
}
|
||||
|
||||
/// Get writable directory of packages
|
||||
#[cfg(feature = "system")]
|
||||
pub fn resource_package_by_ns(
|
||||
&mut self,
|
||||
mut arguments: Vec<JsonValue>,
|
||||
|
|
|
|||
|
|
@ -11,12 +11,11 @@ use reflexo_typst::{ImmutPath, TypstDict};
|
|||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value as JsonValue};
|
||||
use strum::IntoEnumIterator;
|
||||
use task::{ExportUserConfig, FormatUserConfig, FormatterConfig};
|
||||
use task::{FormatUserConfig, FormatterConfig};
|
||||
use tinymist_l10n::DebugL10n;
|
||||
use tinymist_preview::{PreviewConfig, PreviewInvertColors};
|
||||
use tinymist_project::{DynAccessModel, LspAccessModel};
|
||||
use tinymist_query::analysis::{Modifier, TokenType};
|
||||
use tinymist_query::{CompletionFeat, PositionEncoding};
|
||||
use tinymist_query::{url_to_path, CompletionFeat, PositionEncoding};
|
||||
use tinymist_render::PeriscopeArgs;
|
||||
use tinymist_std::error::prelude::*;
|
||||
use tinymist_task::ExportTarget;
|
||||
|
|
@ -26,11 +25,18 @@ use typst_shim::utils::LazyHash;
|
|||
|
||||
use super::*;
|
||||
use crate::project::{
|
||||
EntryResolver, ExportPdfTask, ExportTask, ImmutDict, PathPattern, ProjectResolutionKind,
|
||||
ProjectTask, TaskWhen,
|
||||
EntryResolver, ExportTask, ImmutDict, PathPattern, ProjectResolutionKind, TaskWhen,
|
||||
};
|
||||
use crate::world::font::FontResolverImpl;
|
||||
|
||||
#[cfg(feature = "export")]
|
||||
use task::ExportUserConfig;
|
||||
#[cfg(feature = "preview")]
|
||||
use tinymist_preview::{PreviewConfig, PreviewInvertColors};
|
||||
|
||||
#[cfg(feature = "export")]
|
||||
use crate::project::{ExportPdfTask, ProjectTask};
|
||||
|
||||
// region Configuration Items
|
||||
const CONFIG_ITEMS: &[&str] = &[
|
||||
"tinymist",
|
||||
|
|
@ -172,13 +178,13 @@ impl Config {
|
|||
let roots = match params.workspace_folders.as_ref() {
|
||||
Some(roots) => roots
|
||||
.iter()
|
||||
.filter_map(|root| root.uri.to_file_path().ok().map(ImmutPath::from))
|
||||
.map(|root| ImmutPath::from(url_to_path(&root.uri)))
|
||||
.collect(),
|
||||
#[allow(deprecated)] // `params.root_path` is marked as deprecated
|
||||
None => params
|
||||
.root_uri
|
||||
.as_ref()
|
||||
.and_then(|uri| uri.to_file_path().ok().map(ImmutPath::from))
|
||||
.map(|uri| ImmutPath::from(url_to_path(uri)))
|
||||
.or_else(|| Some(Path::new(¶ms.root_path.as_ref()?).into()))
|
||||
.into_iter()
|
||||
.collect(),
|
||||
|
|
@ -510,6 +516,7 @@ impl Config {
|
|||
}
|
||||
|
||||
/// Gets the preview configuration.
|
||||
#[cfg(feature = "preview")]
|
||||
pub fn preview(&self) -> PreviewConfig {
|
||||
PreviewConfig {
|
||||
enable_partial_rendering: self.preview.partial_rendering,
|
||||
|
|
@ -529,6 +536,7 @@ impl Config {
|
|||
}
|
||||
|
||||
/// Gets the export configuration.
|
||||
#[cfg(feature = "export")]
|
||||
pub(crate) fn export(&self) -> ExportUserConfig {
|
||||
let export = self.export_task();
|
||||
ExportUserConfig {
|
||||
|
|
@ -675,7 +683,7 @@ impl Config {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(not(feature = "system"))]
|
||||
fn create_physical_access_model(
|
||||
&self,
|
||||
client: &TypedLspClient<ServerState>,
|
||||
|
|
@ -683,7 +691,7 @@ impl Config {
|
|||
self.create_delegate_access_model(client)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(feature = "system")]
|
||||
fn create_physical_access_model(
|
||||
&self,
|
||||
_client: &TypedLspClient<ServerState>,
|
||||
|
|
@ -874,6 +882,7 @@ pub struct PreviewFeat {
|
|||
#[serde(default, deserialize_with = "deserialize_null_default")]
|
||||
pub partial_rendering: bool,
|
||||
/// Invert colors for the preview.
|
||||
#[cfg(feature = "preview")]
|
||||
#[serde(default, deserialize_with = "deserialize_null_default")]
|
||||
pub invert_colors: PreviewInvertColors,
|
||||
}
|
||||
|
|
@ -971,6 +980,7 @@ where
|
|||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
#[cfg(feature = "preview")]
|
||||
use tinymist_preview::{PreviewInvertColor, PreviewInvertColorObject};
|
||||
|
||||
fn update_config(config: &mut Config, update: &JsonValue) -> Result<()> {
|
||||
|
|
@ -1174,7 +1184,9 @@ mod tests {
|
|||
test_good_config("preview.background.args");
|
||||
test_good_config("preview.refresh");
|
||||
test_good_config("preview.partialRendering");
|
||||
#[cfg(feature = "preview")]
|
||||
let c = test_good_config("preview.invertColors");
|
||||
#[cfg(feature = "preview")]
|
||||
assert_eq!(
|
||||
c.preview.invert_colors,
|
||||
PreviewInvertColors::Enum(PreviewInvertColor::Never)
|
||||
|
|
@ -1377,6 +1389,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "preview")]
|
||||
fn test_default_preview_config() {
|
||||
let config = Config::default().preview();
|
||||
assert!(!config.enable_partial_rendering);
|
||||
|
|
@ -1385,6 +1398,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "preview")]
|
||||
fn test_preview_config() {
|
||||
let config = Config {
|
||||
preview: PreviewFeat {
|
||||
|
|
@ -1434,6 +1448,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "preview")]
|
||||
fn test_invert_colors_validation() {
|
||||
fn test(s: &str) -> anyhow::Result<PreviewInvertColors> {
|
||||
Ok(serde_json::from_str(s)?)
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ use tinymist_std::error::prelude::*;
|
|||
use tinymist_std::ImmutPath;
|
||||
use typst::{diag::FileResult, syntax::Source};
|
||||
|
||||
use crate::project::{Interrupt, ProjectResolutionKind};
|
||||
use crate::route::ProjectResolution;
|
||||
use crate::project::Interrupt;
|
||||
use crate::world::vfs::{notify::MemoryEvent, FileChangeSet};
|
||||
use crate::world::TaskInputs;
|
||||
use crate::*;
|
||||
|
|
@ -14,6 +13,11 @@ use crate::*;
|
|||
mod client;
|
||||
pub use client::ClientAccessModel;
|
||||
|
||||
#[cfg(feature = "lock")]
|
||||
use crate::project::ProjectResolutionKind;
|
||||
#[cfg(feature = "lock")]
|
||||
use crate::route::ProjectResolution;
|
||||
|
||||
/// In memory source file management.
|
||||
impl ServerState {
|
||||
/// Updates a set of source files.
|
||||
|
|
@ -232,6 +236,12 @@ impl ServerState {
|
|||
.unwrap_or_else(|| self.resolve_task_without_lock(path))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "lock"))]
|
||||
pub(crate) fn resolve_task(&mut self, path: ImmutPath) -> TaskInputs {
|
||||
self.resolve_task_without_lock(Some(path))
|
||||
}
|
||||
|
||||
#[cfg(feature = "lock")]
|
||||
pub(crate) fn resolve_task(&mut self, path: ImmutPath) -> TaskInputs {
|
||||
let proj_input = matches!(
|
||||
self.entry_resolver().project_resolution,
|
||||
|
|
|
|||
|
|
@ -1,52 +1,75 @@
|
|||
//! # 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).
|
||||
|
||||
mod actor;
|
||||
mod cmd;
|
||||
pub(crate) mod config;
|
||||
pub(crate) mod dap;
|
||||
pub(crate) mod input;
|
||||
pub(crate) mod lsp;
|
||||
pub mod project;
|
||||
mod resource;
|
||||
pub(crate) mod route;
|
||||
mod server;
|
||||
mod stats;
|
||||
mod task;
|
||||
pub mod tool;
|
||||
mod utils;
|
||||
//! Tinymist Core Library
|
||||
|
||||
pub use config::*;
|
||||
pub use dap::RegularInit as DapRegularInit;
|
||||
pub use dap::SuperInit as DapSuperInit;
|
||||
pub use lsp::init::*;
|
||||
pub use server::*;
|
||||
pub use sync_ls::LspClient;
|
||||
pub use task::export2 as export;
|
||||
pub use task::UserActionTask;
|
||||
pub use tinymist_project::world;
|
||||
pub use tinymist_query as query;
|
||||
pub use world::{CompileFontArgs, CompileOnceArgs, CompilePackageArgs};
|
||||
|
||||
#[cfg(feature = "export")]
|
||||
pub use task::export2 as export;
|
||||
#[cfg(feature = "export")]
|
||||
pub use task::ExportTask;
|
||||
#[cfg(feature = "trace")]
|
||||
pub use task::UserActionTask;
|
||||
|
||||
#[cfg(feature = "dap")]
|
||||
pub use dap::RegularInit as DapRegularInit;
|
||||
#[cfg(feature = "dap")]
|
||||
pub use dap::SuperInit as DapSuperInit;
|
||||
|
||||
pub mod project;
|
||||
pub mod tool;
|
||||
|
||||
pub(crate) mod config;
|
||||
#[cfg(feature = "dap")]
|
||||
pub(crate) mod dap;
|
||||
pub(crate) mod input;
|
||||
pub(crate) mod lsp;
|
||||
#[cfg(feature = "lock")]
|
||||
pub(crate) mod route;
|
||||
|
||||
mod actor;
|
||||
mod cmd;
|
||||
mod resource;
|
||||
mod server;
|
||||
mod stats;
|
||||
mod task;
|
||||
mod utils;
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use lsp::query::QueryFuture;
|
||||
use serde_json::from_value;
|
||||
use sync_ls::*;
|
||||
use utils::*;
|
||||
use world::*;
|
||||
|
||||
/// The long version description of the library
|
||||
pub static LONG_VERSION: LazyLock<String> = LazyLock::new(|| {
|
||||
format!(
|
||||
"
|
||||
Build Timestamp: {}
|
||||
Build Git Describe: {}
|
||||
Commit SHA: {}
|
||||
Commit Date: {}
|
||||
Commit Branch: {}
|
||||
Cargo Target Triple: {}
|
||||
Typst Version: {}
|
||||
Typst Source: {}
|
||||
",
|
||||
env!("VERGEN_BUILD_TIMESTAMP"),
|
||||
env!("VERGEN_GIT_DESCRIBE"),
|
||||
option_env!("VERGEN_GIT_SHA").unwrap_or("None"),
|
||||
option_env!("VERGEN_GIT_COMMIT_TIMESTAMP").unwrap_or("None"),
|
||||
option_env!("VERGEN_GIT_BRANCH").unwrap_or("None"),
|
||||
env!("VERGEN_CARGO_TARGET_TRIPLE"),
|
||||
env!("TYPST_VERSION"),
|
||||
env!("TYPST_SOURCE"),
|
||||
)
|
||||
});
|
||||
|
||||
#[cfg(feature = "web")]
|
||||
pub mod web;
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ impl ServerState {
|
|||
impl ServerState {
|
||||
pub(crate) fn did_open(&mut self, params: DidOpenTextDocumentParams) -> LspResult<()> {
|
||||
log::info!("did open {}", params.text_document.uri);
|
||||
let path: ImmutPath = as_path_(params.text_document.uri).as_path().into();
|
||||
let path: ImmutPath = as_path_(¶ms.text_document.uri).as_path().into();
|
||||
let text = params.text_document.text;
|
||||
|
||||
self.create_source(path.clone(), text)
|
||||
|
|
@ -98,14 +98,14 @@ impl ServerState {
|
|||
}
|
||||
|
||||
pub(crate) fn did_close(&mut self, params: DidCloseTextDocumentParams) -> LspResult<()> {
|
||||
let path = as_path_(params.text_document.uri).as_path().into();
|
||||
let path = as_path(params.text_document).as_path().into();
|
||||
|
||||
self.remove_source(path).map_err(invalid_params)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn did_change(&mut self, params: DidChangeTextDocumentParams) -> LspResult<()> {
|
||||
let path = as_path_(params.text_document.uri).as_path().into();
|
||||
let path = as_path_(¶ms.text_document.uri).as_path().into();
|
||||
let changes = params.content_changes;
|
||||
|
||||
self.edit_source(path, changes, self.const_config().position_encoding)
|
||||
|
|
@ -114,7 +114,7 @@ impl ServerState {
|
|||
}
|
||||
|
||||
pub(crate) fn did_save(&mut self, params: DidSaveTextDocumentParams) -> LspResult<()> {
|
||||
let path = as_path_(params.text_document.uri).as_path().into();
|
||||
let path = as_path(params.text_document).as_path().into();
|
||||
self.save_source(path).map_err(invalid_params)?;
|
||||
|
||||
Ok(())
|
||||
|
|
@ -139,10 +139,13 @@ impl ServerState {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "export")]
|
||||
{
|
||||
let new_export_config = self.config.export();
|
||||
if old_config.export() != new_export_config {
|
||||
self.change_export_config(new_export_config);
|
||||
}
|
||||
}
|
||||
|
||||
if old_config.notify_status != self.config.notify_status {
|
||||
self.editor_tx
|
||||
|
|
|
|||
|
|
@ -292,8 +292,8 @@ impl ServerState {
|
|||
.iter()
|
||||
.map(|f| {
|
||||
Some((
|
||||
as_path_(Url::parse(&f.old_uri).ok()?),
|
||||
as_path_(Url::parse(&f.new_uri).ok()?),
|
||||
as_path_(&Url::parse(&f.old_uri).ok()?),
|
||||
as_path_(&Url::parse(&f.new_uri).ok()?),
|
||||
))
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()
|
||||
|
|
@ -326,7 +326,12 @@ impl ServerState {
|
|||
DocumentSymbol(req) => query_source!(self, DocumentSymbol, req)?,
|
||||
OnEnter(req) => query_source!(self, OnEnter, req)?,
|
||||
ColorPresentation(req) => CompilerQueryResponse::ColorPresentation(req.request()),
|
||||
#[cfg(feature = "export")]
|
||||
OnExport(req) => return self.on_export(req),
|
||||
#[cfg(not(feature = "export"))]
|
||||
OnExport(_req) => {
|
||||
return Err(tinymist_std::error_once!("export feature is not enabled"))
|
||||
}
|
||||
ServerInfo(_) => return self.collect_server_info(),
|
||||
// todo: query on dedicate projects
|
||||
_ => return self.query_on(query),
|
||||
|
|
@ -360,6 +365,8 @@ impl ServerState {
|
|||
just_future(async move {
|
||||
stat.snap();
|
||||
|
||||
// todo: preload in web
|
||||
#[cfg(feature = "system")]
|
||||
if matches!(query, Completion(..)) {
|
||||
// Prefetch the package index for completion.
|
||||
if snap.registry().cached_index().is_none() {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use reflexo_typst::{diag::print_diagnostics, TypstDocument};
|
||||
use reflexo_typst::TypstDocument;
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub use tinymist_project::*;
|
||||
|
||||
|
|
@ -42,8 +42,11 @@ use typst::{diag::FileResult, foundations::Bytes, layout::Position as TypstPosit
|
|||
use super::ServerState;
|
||||
use crate::actor::editor::{EditorRequest, ProjVersion};
|
||||
use crate::stats::{CompilerQueryStats, QueryStatGuard};
|
||||
#[cfg(feature = "export")]
|
||||
use crate::task::ExportUserConfig;
|
||||
use crate::{Config, ServerEvent};
|
||||
use crate::Config;
|
||||
#[cfg(feature = "preview")]
|
||||
use crate::ServerEvent;
|
||||
|
||||
type EditorSender = mpsc::UnboundedSender<EditorRequest>;
|
||||
|
||||
|
|
@ -53,6 +56,7 @@ pub type LspProjectCompiler = ProjectCompiler<LspCompilerFeat, ProjectInsStateEx
|
|||
/// Project access and mutations.
|
||||
impl ServerState {
|
||||
/// Changes the export configuration.
|
||||
#[cfg(feature = "export")]
|
||||
pub fn change_export_config(&mut self, config: ExportUserConfig) {
|
||||
self.project.export.change_config(config);
|
||||
}
|
||||
|
|
@ -138,6 +142,7 @@ impl ServerState {
|
|||
let const_config = &config.const_config;
|
||||
|
||||
// Run Export actors before preparing cluster to avoid loss of events
|
||||
#[cfg(feature = "export")]
|
||||
let export = crate::task::ExportTask::new(
|
||||
client.handle.clone(),
|
||||
Some(editor_tx.clone()),
|
||||
|
|
@ -150,6 +155,7 @@ impl ServerState {
|
|||
#[cfg(feature = "preview")]
|
||||
preview,
|
||||
is_standalone: false,
|
||||
#[cfg(feature = "export")]
|
||||
export: export.clone(),
|
||||
editor_tx: editor_tx.clone(),
|
||||
client: Arc::new(client.clone().to_untyped()),
|
||||
|
|
@ -207,11 +213,20 @@ impl ServerState {
|
|||
|
||||
// todo: unify filesystem watcher
|
||||
let (dep_tx, dep_rx) = mpsc::unbounded_channel();
|
||||
// todo: notify feature?
|
||||
#[cfg(feature = "system")]
|
||||
{
|
||||
let fs_client = client.clone().to_untyped();
|
||||
let async_handle = client.handle.clone();
|
||||
async_handle.spawn(watch_deps(dep_rx, move |event| {
|
||||
fs_client.send_event(LspInterrupt::Fs(event));
|
||||
}));
|
||||
}
|
||||
#[cfg(not(feature = "system"))]
|
||||
{
|
||||
let _ = dep_rx;
|
||||
log::warn!("Project: system watcher is not enabled, file changes will not be watched");
|
||||
}
|
||||
|
||||
// Create the actor
|
||||
let compile_handle = handle.clone();
|
||||
|
|
@ -227,9 +242,11 @@ impl ServerState {
|
|||
|
||||
ProjectState {
|
||||
compiler,
|
||||
#[cfg(feature = "preview")]
|
||||
preview: handle.preview.clone(),
|
||||
analysis: handle.analysis.clone(),
|
||||
stats: CompilerQueryStats::default(),
|
||||
#[cfg(feature = "export")]
|
||||
export: handle.export.clone(),
|
||||
}
|
||||
}
|
||||
|
|
@ -298,9 +315,11 @@ impl ProjectInsStateExt {
|
|||
|
||||
pub struct ProjectState {
|
||||
pub compiler: LspProjectCompiler,
|
||||
pub preview: ProjectPreviewState,
|
||||
pub analysis: Arc<Analysis>,
|
||||
pub stats: CompilerQueryStats,
|
||||
#[cfg(feature = "preview")]
|
||||
pub preview: ProjectPreviewState,
|
||||
#[cfg(feature = "export")]
|
||||
pub export: crate::task::ExportTask,
|
||||
}
|
||||
|
||||
|
|
@ -414,6 +433,7 @@ pub struct CompileHandlerImpl {
|
|||
/// language server).
|
||||
pub is_standalone: bool,
|
||||
|
||||
#[cfg(feature = "export")]
|
||||
pub(crate) export: crate::task::ExportTask,
|
||||
pub(crate) editor_tx: EditorSender,
|
||||
pub(crate) client: Arc<dyn ProjectClient>,
|
||||
|
|
@ -422,9 +442,11 @@ pub struct CompileHandlerImpl {
|
|||
pub(crate) notified_revision: Mutex<FxHashMap<ProjectInsId, (usize, CompileSignal)>>,
|
||||
}
|
||||
|
||||
pub(crate) trait ProjectClient: Send + Sync + 'static {
|
||||
pub trait ProjectClient: Send + Sync + 'static {
|
||||
fn interrupt(&self, event: LspInterrupt);
|
||||
#[cfg(feature = "preview")]
|
||||
fn server_event(&self, event: ServerEvent);
|
||||
#[cfg(feature = "export")]
|
||||
fn dev_event(&self, event: DevEvent);
|
||||
}
|
||||
|
||||
|
|
@ -433,10 +455,12 @@ impl ProjectClient for LspClient {
|
|||
self.send_event(event);
|
||||
}
|
||||
|
||||
#[cfg(feature = "preview")]
|
||||
fn server_event(&self, event: ServerEvent) {
|
||||
self.send_event(event);
|
||||
}
|
||||
|
||||
#[cfg(feature = "export")]
|
||||
fn dev_event(&self, event: DevEvent) {
|
||||
self.send_notification::<DevEvent>(&event);
|
||||
}
|
||||
|
|
@ -447,10 +471,12 @@ impl ProjectClient for mpsc::UnboundedSender<LspInterrupt> {
|
|||
self.send(event).log_error("failed to send interrupt");
|
||||
}
|
||||
|
||||
#[cfg(feature = "preview")]
|
||||
fn server_event(&self, _event: ServerEvent) {
|
||||
log::warn!("ProjectClient: server_event is not implemented for mpsc::UnboundedSender<LspInterrupt>");
|
||||
}
|
||||
|
||||
#[cfg(feature = "export")]
|
||||
fn dev_event(&self, _event: DevEvent) {
|
||||
log::warn!(
|
||||
"ProjectClient: dev_event is not implemented for mpsc::UnboundedSender<LspInterrupt>"
|
||||
|
|
@ -696,8 +722,9 @@ impl CompileHandler<LspCompilerFeat, ProjectInsStateExt> for CompileHandlerImpl
|
|||
|
||||
// Prints the diagnostics when we are running the compilation in standalone
|
||||
// CLI.
|
||||
#[cfg(feature = "system")]
|
||||
if self.is_standalone {
|
||||
print_diagnostics(
|
||||
crate::project::system::print_diagnostics(
|
||||
art.world(),
|
||||
art.diagnostics(),
|
||||
reflexo_typst::DiagnosticFormat::Human,
|
||||
|
|
@ -705,6 +732,7 @@ impl CompileHandler<LspCompilerFeat, ProjectInsStateExt> for CompileHandlerImpl
|
|||
.log_error("failed to print diagnostics");
|
||||
}
|
||||
|
||||
#[cfg(feature = "export")]
|
||||
self.export.signal(art, &self.client);
|
||||
|
||||
#[cfg(feature = "preview")]
|
||||
|
|
@ -723,7 +751,7 @@ pub type QuerySnapWithStat = (LspQuerySnapshot, QueryStatGuard);
|
|||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct DevExportEvent {
|
||||
pub struct DevExportEvent {
|
||||
pub id: String,
|
||||
pub when: TaskWhen,
|
||||
pub need_export: bool,
|
||||
|
|
@ -733,7 +761,7 @@ pub(crate) struct DevExportEvent {
|
|||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", tag = "type")]
|
||||
pub(crate) enum DevEvent {
|
||||
pub enum DevEvent {
|
||||
Export(DevExportEvent),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,31 +7,30 @@ use lsp_types::request::ShowMessageRequest;
|
|||
use lsp_types::*;
|
||||
use reflexo::debug_loc::LspPosition;
|
||||
use sync_ls::*;
|
||||
use tinymist_query::{OnExportRequest, ServerInfoResponse};
|
||||
use tinymist_query::ServerInfoResponse;
|
||||
use tinymist_std::error::prelude::*;
|
||||
use tinymist_std::ImmutPath;
|
||||
use tinymist_task::ProjectTask;
|
||||
use tokio::sync::mpsc;
|
||||
use typst::syntax::Source;
|
||||
|
||||
use crate::actor::editor::{EditorActor, EditorRequest};
|
||||
use crate::lsp::query::OnEnter;
|
||||
use crate::project::{
|
||||
update_lock, CompiledArtifact, EntryResolver, LspComputeGraph, LspInterrupt, ProjectInsId,
|
||||
ProjectState, PROJECT_ROUTE_USER_ACTION_PRIORITY,
|
||||
};
|
||||
use crate::route::ProjectRouteState;
|
||||
use crate::task::{ExportTask, FormatTask, ServerTraceTask, UserActionTask};
|
||||
use crate::world::TaskInputs;
|
||||
use crate::project::{EntryResolver, LspInterrupt, ProjectInsId, ProjectState};
|
||||
use crate::task::FormatTask;
|
||||
use crate::{lsp::init::*, *};
|
||||
|
||||
#[cfg(feature = "lock")]
|
||||
use crate::route::ProjectRouteState;
|
||||
#[cfg(feature = "trace")]
|
||||
use crate::task::{ServerTraceTask, UserActionTask};
|
||||
|
||||
pub(crate) use futures::Future;
|
||||
|
||||
pub(crate) fn as_path(inp: TextDocumentIdentifier) -> PathBuf {
|
||||
as_path_(inp.uri)
|
||||
as_path_(&inp.uri)
|
||||
}
|
||||
|
||||
pub(crate) fn as_path_(uri: Url) -> PathBuf {
|
||||
pub(crate) fn as_path_(uri: &Url) -> PathBuf {
|
||||
tinymist_query::url_to_path(uri)
|
||||
}
|
||||
|
||||
|
|
@ -45,10 +44,11 @@ pub struct ServerState {
|
|||
pub client: TypedLspClient<Self>,
|
||||
|
||||
// State
|
||||
/// The project route state.
|
||||
pub route: ProjectRouteState,
|
||||
/// The project state.
|
||||
pub project: ProjectState,
|
||||
/// The project route state.
|
||||
#[cfg(feature = "lock")]
|
||||
pub route: ProjectRouteState,
|
||||
/// The preview state.
|
||||
#[cfg(feature = "preview")]
|
||||
pub preview: tool::preview::PreviewState,
|
||||
|
|
@ -59,6 +59,7 @@ pub struct ServerState {
|
|||
pub formatter: FormatTask,
|
||||
/// The user action tasks running in backend, which will be scheduled by
|
||||
/// async runtime.
|
||||
#[cfg(feature = "trace")]
|
||||
pub user_action: UserActionTask,
|
||||
|
||||
// State to synchronize with the client.
|
||||
|
|
@ -83,6 +84,7 @@ pub struct ServerState {
|
|||
/// The client ever sent manual focusing request.
|
||||
pub ever_manual_focusing: bool,
|
||||
/// The running server trace.
|
||||
#[cfg(feature = "trace")]
|
||||
pub server_trace: Option<ServerTraceTask>,
|
||||
|
||||
// Configurations
|
||||
|
|
@ -115,33 +117,36 @@ impl ServerState {
|
|||
);
|
||||
|
||||
Self {
|
||||
client: client.clone(),
|
||||
#[cfg(feature = "dap")]
|
||||
debug: crate::dap::DebugState::default(),
|
||||
#[cfg(feature = "lock")]
|
||||
route: ProjectRouteState::default(),
|
||||
project: handle,
|
||||
editor_tx,
|
||||
memory_changes: HashMap::new(),
|
||||
#[cfg(feature = "preview")]
|
||||
preview: tool::preview::PreviewState::new(
|
||||
&config,
|
||||
watchers,
|
||||
client.cast(|s| &mut s.preview),
|
||||
),
|
||||
#[cfg(feature = "dap")]
|
||||
debug: crate::dap::DebugState::default(),
|
||||
#[cfg(feature = "trace")]
|
||||
server_trace: None,
|
||||
#[cfg(feature = "trace")]
|
||||
user_action: UserActionTask,
|
||||
|
||||
client: client.clone(),
|
||||
project: handle,
|
||||
editor_tx,
|
||||
memory_changes: HashMap::new(),
|
||||
ever_focusing_by_activities: false,
|
||||
ever_manual_focusing: false,
|
||||
sema_tokens_registered: false,
|
||||
formatter_registered: false,
|
||||
server_trace: None,
|
||||
config,
|
||||
|
||||
pinning_by_user: false,
|
||||
pinning_by_preview: false,
|
||||
pinning_by_browsing_preview: false,
|
||||
focusing: None,
|
||||
implicit_position: None,
|
||||
formatter,
|
||||
user_action: UserActionTask,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -216,6 +221,20 @@ impl ServerState {
|
|||
.with_command("tinymist.doStartBrowsingPreview", State::browse_preview)
|
||||
.with_command("tinymist.doKillPreview", State::kill_preview);
|
||||
|
||||
#[cfg(feature = "trace")]
|
||||
let provider = provider
|
||||
.with_command("tinymist.getDocumentTrace", State::get_document_trace)
|
||||
.with_command("tinymist.startServerProfiling", State::start_server_trace)
|
||||
.with_command("tinymist.stopServerProfiling", State::stop_server_trace);
|
||||
|
||||
#[cfg(feature = "system")]
|
||||
let provider = provider
|
||||
.with_command("tinymist.doInitTemplate", State::init_template)
|
||||
.with_command("tinymist.doGetTemplateEntry", State::get_template_entry)
|
||||
.with_resource("/package/by-namespace", State::resource_package_by_ns)
|
||||
.with_resource("/dir/package", State::resource_package_dirs)
|
||||
.with_resource("/dir/package/local", State::resource_local_package_dir);
|
||||
|
||||
// todo: .on_sync_mut::<notifs::Cancel>(handlers::handle_cancel)?
|
||||
let mut provider = provider
|
||||
.with_request::<Shutdown>(State::shutdown)
|
||||
|
|
@ -277,12 +296,7 @@ impl ServerState {
|
|||
.with_command("tinymist.doClearCache", State::clear_cache)
|
||||
.with_command("tinymist.pinMain", State::pin_document)
|
||||
.with_command("tinymist.focusMain", State::focus_document)
|
||||
.with_command("tinymist.doInitTemplate", State::init_template)
|
||||
.with_command("tinymist.doGetTemplateEntry", State::get_template_entry)
|
||||
.with_command_("tinymist.interactCodeContext", State::interact_code_context)
|
||||
.with_command("tinymist.getDocumentTrace", State::get_document_trace)
|
||||
.with_command("tinymist.startServerProfiling", State::start_server_trace)
|
||||
.with_command("tinymist.stopServerProfiling", State::stop_server_trace)
|
||||
.with_command_("tinymist.getDocumentMetrics", State::get_document_metrics)
|
||||
.with_command_("tinymist.getWorkspaceLabels", State::get_workspace_labels)
|
||||
.with_command_("tinymist.getServerInfo", State::get_server_info)
|
||||
|
|
@ -291,11 +305,8 @@ impl ServerState {
|
|||
.with_resource("/symbols", State::resource_symbols)
|
||||
.with_resource("/preview/index.html", State::resource_preview_html)
|
||||
.with_resource("/tutorial", State::resource_tutoral)
|
||||
.with_resource("/package/by-namespace", State::resource_package_by_ns)
|
||||
.with_resource("/package/symbol", State::resource_package_symbols)
|
||||
.with_resource("/package/docs", State::resource_package_docs)
|
||||
.with_resource("/dir/package", State::resource_package_dirs)
|
||||
.with_resource("/dir/package/local", State::resource_local_package_dir);
|
||||
.with_resource("/package/docs", State::resource_package_docs);
|
||||
|
||||
// todo: generalize me
|
||||
provider.args.add_commands(
|
||||
|
|
@ -310,6 +321,7 @@ impl ServerState {
|
|||
}
|
||||
|
||||
/// Installs DAP handlers to the language server.
|
||||
#[cfg(feature = "dap")]
|
||||
pub fn install_dap<T: Initializer<S = Self> + 'static>(
|
||||
provider: DapBuilder<T>,
|
||||
) -> DapBuilder<T> {
|
||||
|
|
@ -445,62 +457,6 @@ impl ServerState {
|
|||
Ok(tinymist_query::CompilerQueryResponse::ServerInfo(info))
|
||||
})
|
||||
}
|
||||
|
||||
/// Exports the current document.
|
||||
pub fn on_export(&mut self, req: OnExportRequest) -> QueryFuture {
|
||||
let OnExportRequest { path, task, open } = req;
|
||||
let entry = self.entry_resolver().resolve(Some(path.as_path().into()));
|
||||
let lock_dir = self.entry_resolver().resolve_lock(&entry);
|
||||
|
||||
let update_dep = lock_dir.clone().map(|lock_dir| {
|
||||
|snap: LspComputeGraph| async move {
|
||||
let mut updater = update_lock(lock_dir.clone());
|
||||
let world = snap.world();
|
||||
// todo: rootless.
|
||||
let root_dir = world.entry_state().root()?;
|
||||
let doc_id = updater.compiled(world, (&root_dir, &lock_dir))?;
|
||||
|
||||
updater.update_materials(doc_id.clone(), world.depended_fs_paths());
|
||||
updater.route(doc_id, PROJECT_ROUTE_USER_ACTION_PRIORITY);
|
||||
|
||||
updater.commit();
|
||||
|
||||
Some(())
|
||||
}
|
||||
});
|
||||
|
||||
let snap = self.snapshot()?;
|
||||
just_future(async move {
|
||||
let snap = snap.task(TaskInputs {
|
||||
entry: Some(entry),
|
||||
..TaskInputs::default()
|
||||
});
|
||||
|
||||
let is_html = matches!(task, ProjectTask::ExportHtml { .. });
|
||||
let artifact = CompiledArtifact::from_graph(snap.clone(), is_html);
|
||||
let res = ExportTask::do_export(task, artifact, lock_dir).await?;
|
||||
if let Some(update_dep) = update_dep {
|
||||
tokio::spawn(update_dep(snap));
|
||||
}
|
||||
|
||||
// See https://github.com/Myriad-Dreamin/tinymist/issues/837
|
||||
// Also see https://github.com/Byron/open-rs/issues/105
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let do_open = ::open::that_detached;
|
||||
#[cfg(target_os = "windows")]
|
||||
fn do_open(path: impl AsRef<std::ffi::OsStr>) -> std::io::Result<()> {
|
||||
::open::with_detached(path, "explorer")
|
||||
}
|
||||
|
||||
if let Some(Some(path)) = open.then_some(res.as_ref()) {
|
||||
log::trace!("open with system default apps: {path:?}");
|
||||
do_open(path).log_error("failed to open with system default apps");
|
||||
}
|
||||
|
||||
log::trace!("CompileActor: on export end: {path:?} as {res:?}");
|
||||
Ok(tinymist_query::CompilerQueryResponse::OnExport(res))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -509,11 +465,11 @@ fn test_as_path() {
|
|||
use std::path::Path;
|
||||
|
||||
let uri = Url::parse("untitled:/path/to/file").unwrap();
|
||||
assert_eq!(as_path_(uri), Path::new("/untitled/path/to/file").clean());
|
||||
assert_eq!(as_path_(&uri), Path::new("/untitled/path/to/file").clean());
|
||||
|
||||
let uri = Url::parse("untitled:/path/to/file%20with%20space").unwrap();
|
||||
assert_eq!(
|
||||
as_path_(uri),
|
||||
as_path_(&uri),
|
||||
Path::new("/untitled/path/to/file with space").clean()
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,13 @@ use std::path::PathBuf;
|
|||
use std::str::FromStr;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use std::{ops::DerefMut, pin::Pin};
|
||||
|
||||
use reflexo::ImmutPath;
|
||||
use reflexo_typst::{Bytes, CompilationTask, ExportComputation};
|
||||
use tinymist_project::{LspWorld, PROJECT_ROUTE_USER_ACTION_PRIORITY};
|
||||
use sync_ls::just_future;
|
||||
use tinymist_project::LspWorld;
|
||||
use tinymist_query::OnExportRequest;
|
||||
use tinymist_std::error::prelude::*;
|
||||
use tinymist_std::fs::paths::write_atomic;
|
||||
use tinymist_std::path::PathClean;
|
||||
|
|
@ -18,24 +21,102 @@ use typlite::{Format, Typlite};
|
|||
use typst::foundations::IntoValue;
|
||||
use typst::visualize::Color;
|
||||
|
||||
use super::{FutureFolder, SyncTaskFactory};
|
||||
use futures::Future;
|
||||
use parking_lot::Mutex;
|
||||
use rayon::Scope;
|
||||
|
||||
use super::SyncTaskFactory;
|
||||
use crate::lsp::query::QueryFuture;
|
||||
use crate::project::{
|
||||
ApplyProjectTask, CompiledArtifact, DevEvent, DevExportEvent, EntryReader, ExportHtmlTask,
|
||||
ExportPdfTask, ExportPngTask, ExportSvgTask, ExportTask as ProjectExportTask, ExportTeXTask,
|
||||
ExportTextTask, LspCompiledArtifact, ProjectClient, ProjectTask, QueryTask, TaskWhen,
|
||||
update_lock, ApplyProjectTask, CompiledArtifact, DevEvent, DevExportEvent, EntryReader,
|
||||
ExportHtmlTask, ExportPdfTask, ExportPngTask, ExportSvgTask, ExportTask as ProjectExportTask,
|
||||
ExportTeXTask, ExportTextTask, LspCompiledArtifact, LspComputeGraph, ProjectClient,
|
||||
ProjectTask, QueryTask, TaskWhen, PROJECT_ROUTE_USER_ACTION_PRIORITY,
|
||||
};
|
||||
use crate::world::TaskInputs;
|
||||
use crate::ServerState;
|
||||
use crate::{actor::editor::EditorRequest, tool::word_count};
|
||||
|
||||
impl ServerState {
|
||||
/// Exports the current document.
|
||||
pub fn on_export(&mut self, req: OnExportRequest) -> QueryFuture {
|
||||
let OnExportRequest { path, task, open } = req;
|
||||
let entry = self.entry_resolver().resolve(Some(path.as_path().into()));
|
||||
let lock_dir = self.entry_resolver().resolve_lock(&entry);
|
||||
|
||||
let update_dep = lock_dir.clone().map(|lock_dir| {
|
||||
|snap: LspComputeGraph| async move {
|
||||
let mut updater = update_lock(lock_dir.clone());
|
||||
let world = snap.world();
|
||||
// todo: rootless.
|
||||
let root_dir = world.entry_state().root()?;
|
||||
let doc_id = updater.compiled(world, (&root_dir, &lock_dir))?;
|
||||
|
||||
updater.update_materials(doc_id.clone(), world.depended_fs_paths());
|
||||
updater.route(doc_id, PROJECT_ROUTE_USER_ACTION_PRIORITY);
|
||||
|
||||
updater.commit();
|
||||
|
||||
Some(())
|
||||
}
|
||||
});
|
||||
|
||||
let snap = self.snapshot()?;
|
||||
just_future(async move {
|
||||
let snap = snap.task(TaskInputs {
|
||||
entry: Some(entry),
|
||||
..TaskInputs::default()
|
||||
});
|
||||
|
||||
let is_html = matches!(task, ProjectTask::ExportHtml { .. });
|
||||
let artifact = CompiledArtifact::from_graph(snap.clone(), is_html);
|
||||
let res = ExportTask::do_export(task, artifact, lock_dir).await?;
|
||||
if let Some(update_dep) = update_dep {
|
||||
tokio::spawn(update_dep(snap));
|
||||
}
|
||||
#[cfg(not(feature = "open"))]
|
||||
if open {
|
||||
log::warn!("open is not supported in this build, ignoring");
|
||||
}
|
||||
|
||||
#[cfg(feature = "open")]
|
||||
{
|
||||
// See https://github.com/Myriad-Dreamin/tinymist/issues/837
|
||||
// Also see https://github.com/Byron/open-rs/issues/105
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let do_open = ::open::that_detached;
|
||||
#[cfg(target_os = "windows")]
|
||||
fn do_open(path: impl AsRef<std::ffi::OsStr>) -> std::io::Result<()> {
|
||||
::open::with_detached(path, "explorer")
|
||||
}
|
||||
|
||||
if let Some(Some(path)) = open.then_some(res.as_ref()) {
|
||||
log::trace!("open with system default apps: {path:?}");
|
||||
do_open(path).log_error("failed to open with system default apps");
|
||||
}
|
||||
}
|
||||
|
||||
log::trace!("CompileActor: on export end: {path:?} as {res:?}");
|
||||
Ok(tinymist_query::CompilerQueryResponse::OnExport(res))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs a export document task.
|
||||
#[derive(Clone)]
|
||||
pub struct ExportTask {
|
||||
/// The handle running the task.
|
||||
pub handle: tokio::runtime::Handle,
|
||||
/// The editor request sender.
|
||||
pub editor_tx: Option<mpsc::UnboundedSender<EditorRequest>>,
|
||||
/// The task factory for export.
|
||||
pub factory: SyncTaskFactory<ExportUserConfig>,
|
||||
export_folder: FutureFolder,
|
||||
count_word_folder: FutureFolder,
|
||||
}
|
||||
|
||||
impl ExportTask {
|
||||
/// Creates a new export task.
|
||||
pub fn new(
|
||||
handle: tokio::runtime::Handle,
|
||||
editor_tx: Option<mpsc::UnboundedSender<EditorRequest>>,
|
||||
|
|
@ -50,6 +131,7 @@ impl ExportTask {
|
|||
}
|
||||
}
|
||||
|
||||
/// Changes the export configuration.
|
||||
pub fn change_config(&self, config: ExportUserConfig) {
|
||||
self.factory.mutate(|data| *data = config);
|
||||
}
|
||||
|
|
@ -162,6 +244,7 @@ impl ExportTask {
|
|||
Some(())
|
||||
}
|
||||
|
||||
/// Exports a document.
|
||||
pub async fn do_export(
|
||||
task: ProjectTask,
|
||||
artifact: LspCompiledArtifact,
|
||||
|
|
@ -489,6 +572,75 @@ fn serialize(data: &impl serde::Serialize, format: &str, pretty: bool) -> Result
|
|||
})
|
||||
}
|
||||
|
||||
type FoldFuture = Pin<Box<dyn Future<Output = Option<()>> + Send>>;
|
||||
|
||||
#[derive(Default)]
|
||||
struct FoldingState {
|
||||
running: bool,
|
||||
task: Option<(usize, FoldFuture)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct FutureFolder {
|
||||
state: Arc<Mutex<FoldingState>>,
|
||||
}
|
||||
|
||||
impl FutureFolder {
|
||||
async fn compute<'scope, OP, R: Send + 'static>(op: OP) -> Result<R>
|
||||
where
|
||||
OP: FnOnce(&Scope<'scope>) -> R + Send + 'static,
|
||||
{
|
||||
tokio::task::spawn_blocking(move || -> R { rayon::in_place_scope(op) })
|
||||
.await
|
||||
.context_ut("compute error")
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn spawn(
|
||||
&self,
|
||||
revision: usize,
|
||||
fut: impl FnOnce() -> FoldFuture,
|
||||
) -> Option<impl Future<Output = ()> + Send + 'static> {
|
||||
let mut state = self.state.lock();
|
||||
let state = state.deref_mut();
|
||||
|
||||
match &mut state.task {
|
||||
Some((prev_revision, prev)) => {
|
||||
if *prev_revision < revision {
|
||||
*prev = fut();
|
||||
*prev_revision = revision;
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
next_update => {
|
||||
*next_update = Some((revision, fut()));
|
||||
}
|
||||
}
|
||||
|
||||
if state.running {
|
||||
return None;
|
||||
}
|
||||
|
||||
state.running = true;
|
||||
|
||||
let state = self.state.clone();
|
||||
Some(async move {
|
||||
loop {
|
||||
let fut = {
|
||||
let mut state = state.lock();
|
||||
let Some((_, fut)) = state.task.take() else {
|
||||
state.running = false;
|
||||
return;
|
||||
};
|
||||
fut
|
||||
};
|
||||
fut.await;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use clap::Parser;
|
||||
|
|
|
|||
|
|
@ -2,21 +2,22 @@
|
|||
//! [`SyncTaskFactory`] can hold *mutable* configuration but the mutations don't
|
||||
//! blocking the computation, i.e. the mutations are non-blocking.
|
||||
|
||||
#[cfg(feature = "export")]
|
||||
mod export;
|
||||
#[cfg(feature = "export")]
|
||||
pub use export::*;
|
||||
#[cfg(feature = "export")]
|
||||
pub mod export2;
|
||||
mod format;
|
||||
pub use format::*;
|
||||
#[cfg(feature = "trace")]
|
||||
mod user_action;
|
||||
#[cfg(feature = "trace")]
|
||||
pub use user_action::*;
|
||||
|
||||
use std::{ops::DerefMut, pin::Pin, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
|
||||
use futures::Future;
|
||||
use parking_lot::Mutex;
|
||||
use rayon::Scope;
|
||||
use reflexo::TakeAs;
|
||||
use tinymist_std::error::prelude::*;
|
||||
|
||||
/// Please uses this if you believe all mutations are fast
|
||||
#[derive(Clone, Default)]
|
||||
|
|
@ -40,72 +41,3 @@ impl<T: Clone> SyncTaskFactory<T> {
|
|||
self.0.read().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
type FoldFuture = Pin<Box<dyn Future<Output = Option<()>> + Send>>;
|
||||
|
||||
#[derive(Default)]
|
||||
struct FoldingState {
|
||||
running: bool,
|
||||
task: Option<(usize, FoldFuture)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct FutureFolder {
|
||||
state: Arc<Mutex<FoldingState>>,
|
||||
}
|
||||
|
||||
impl FutureFolder {
|
||||
async fn compute<'scope, OP, R: Send + 'static>(op: OP) -> Result<R>
|
||||
where
|
||||
OP: FnOnce(&Scope<'scope>) -> R + Send + 'static,
|
||||
{
|
||||
tokio::task::spawn_blocking(move || -> R { rayon::in_place_scope(op) })
|
||||
.await
|
||||
.context_ut("compute error")
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn spawn(
|
||||
&self,
|
||||
revision: usize,
|
||||
fut: impl FnOnce() -> FoldFuture,
|
||||
) -> Option<impl Future<Output = ()> + Send + 'static> {
|
||||
let mut state = self.state.lock();
|
||||
let state = state.deref_mut();
|
||||
|
||||
match &mut state.task {
|
||||
Some((prev_revision, prev)) => {
|
||||
if *prev_revision < revision {
|
||||
*prev = fut();
|
||||
*prev_revision = revision;
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
next_update => {
|
||||
*next_update = Some((revision, fut()));
|
||||
}
|
||||
}
|
||||
|
||||
if state.running {
|
||||
return None;
|
||||
}
|
||||
|
||||
state.running = true;
|
||||
|
||||
let state = self.state.clone();
|
||||
Some(async move {
|
||||
loop {
|
||||
let fut = {
|
||||
let mut state = state.lock();
|
||||
let Some((_, fut)) = state.task.take() else {
|
||||
state.running = false;
|
||||
return;
|
||||
};
|
||||
fut
|
||||
};
|
||||
fut.await;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
pub mod ast;
|
||||
pub mod package;
|
||||
pub mod project;
|
||||
pub mod testing;
|
||||
pub mod word_count;
|
||||
|
||||
#[cfg(feature = "preview")]
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ mod http;
|
|||
use std::{collections::HashMap, path::Path, sync::Arc};
|
||||
|
||||
use clap::{Parser, ValueEnum};
|
||||
use futures::{SinkExt, StreamExt, TryStreamExt};
|
||||
use futures::{SinkExt, TryStreamExt};
|
||||
use hyper_tungstenite::{tungstenite::Message, HyperWebsocket, HyperWebsocketStream};
|
||||
use lsp_types::notification::Notification;
|
||||
use lsp_types::Url;
|
||||
|
|
@ -27,8 +27,7 @@ use tinymist_std::error::IgnoreLogging;
|
|||
use tokio::sync::{mpsc, oneshot};
|
||||
|
||||
use crate::actor::preview::{PreviewActor, PreviewRequest, PreviewTab};
|
||||
use crate::project::{ProjectInsId, ProjectPreviewState, WorldProvider};
|
||||
use crate::tool::project::{start_project, ProjectOpts, StartProjectResult};
|
||||
use crate::project::{ProjectInsId, ProjectPreviewState};
|
||||
use crate::*;
|
||||
|
||||
/// The kind of the preview.
|
||||
|
|
@ -489,6 +488,7 @@ impl PreviewState {
|
|||
is_primary,
|
||||
};
|
||||
|
||||
#[cfg(feature = "open")]
|
||||
if open_in_browser {
|
||||
open::that_detached(format!("http://127.0.0.1:{}", addr.port()))
|
||||
.log_error("failed to open browser for preview");
|
||||
|
|
@ -546,154 +546,6 @@ impl PreviewState {
|
|||
}
|
||||
}
|
||||
|
||||
/// Entry point of the preview tool.
|
||||
pub async fn preview_main(args: PreviewCliArgs) -> Result<()> {
|
||||
log::info!("Arguments: {args:#?}");
|
||||
let handle = tokio::runtime::Handle::current();
|
||||
|
||||
let config = args.preview.config(&PreviewConfig::default());
|
||||
let open_in_browser = args.open_in_browser(true);
|
||||
let static_file_host =
|
||||
if args.static_file_host == args.data_plane_host || !args.static_file_host.is_empty() {
|
||||
Some(args.static_file_host)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
exit_on_ctrl_c();
|
||||
|
||||
let verse = args.compile.resolve()?;
|
||||
let previewer = PreviewBuilder::new(config);
|
||||
|
||||
let (service, handle) = {
|
||||
let preview_state = ProjectPreviewState::default();
|
||||
let opts = ProjectOpts {
|
||||
handle: Some(handle),
|
||||
preview: preview_state.clone(),
|
||||
..ProjectOpts::default()
|
||||
};
|
||||
|
||||
let StartProjectResult {
|
||||
service,
|
||||
intr_tx,
|
||||
mut editor_rx,
|
||||
} = start_project(verse, Some(opts), |compiler, intr, next| {
|
||||
next(compiler, intr)
|
||||
});
|
||||
|
||||
// Consume editor_rx
|
||||
tokio::spawn(async move { while editor_rx.recv().await.is_some() {} });
|
||||
|
||||
let id = service.compiler.primary.id.clone();
|
||||
let registered = preview_state.register(&id, previewer.compile_watcher(args.task_id));
|
||||
if !registered {
|
||||
tinymist_std::bail!("failed to register preview");
|
||||
}
|
||||
|
||||
let handle: Arc<ProjectPreviewHandler> = Arc::new(ProjectPreviewHandler {
|
||||
project_id: id,
|
||||
client: Box::new(intr_tx),
|
||||
});
|
||||
|
||||
(service, handle)
|
||||
};
|
||||
|
||||
let (lsp_tx, mut lsp_rx) = ControlPlaneTx::new(true);
|
||||
|
||||
let control_plane_server_handle = tokio::spawn(async move {
|
||||
let (control_sock_tx, mut control_sock_rx) = mpsc::unbounded_channel();
|
||||
|
||||
let srv =
|
||||
make_http_server(String::default(), args.control_plane_host, control_sock_tx).await;
|
||||
log::info!("Control panel server listening on: {}", srv.addr);
|
||||
|
||||
let control_websocket = control_sock_rx.recv().await.unwrap();
|
||||
let ws = control_websocket.await.unwrap();
|
||||
|
||||
tokio::pin!(ws);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some(resp) = lsp_rx.resp_rx.recv() => {
|
||||
let r = ws
|
||||
.send(Message::Text(serde_json::to_string(&resp).unwrap()))
|
||||
.await;
|
||||
let Err(err) = r else {
|
||||
continue;
|
||||
};
|
||||
|
||||
log::warn!("failed to send response to editor {err:?}");
|
||||
break;
|
||||
|
||||
}
|
||||
msg = ws.next() => {
|
||||
let msg = match msg {
|
||||
Some(Ok(Message::Text(msg))) => Some(msg),
|
||||
Some(Ok(msg)) => {
|
||||
log::error!("unsupported message: {msg:?}");
|
||||
break;
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
log::error!("failed to receive message: {e}");
|
||||
break;
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(msg) = msg {
|
||||
let Ok(msg) = serde_json::from_str::<ControlPlaneMessage>(&msg) else {
|
||||
log::warn!("failed to parse control plane request: {msg:?}");
|
||||
break;
|
||||
};
|
||||
|
||||
lsp_rx.ctl_tx.send(msg).unwrap();
|
||||
} else {
|
||||
// todo: inform the editor that the connection is closed.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
let _ = srv.shutdown_tx.send(());
|
||||
let _ = srv.join.await;
|
||||
});
|
||||
|
||||
let (websocket_tx, websocket_rx) = mpsc::unbounded_channel();
|
||||
let mut previewer = previewer.build(lsp_tx, handle.clone()).await;
|
||||
tokio::spawn(service.run());
|
||||
|
||||
bind_streams(&mut previewer, websocket_rx);
|
||||
|
||||
let frontend_html = frontend_html(TYPST_PREVIEW_HTML, args.preview.preview_mode, "/");
|
||||
|
||||
let static_server = if let Some(static_file_host) = static_file_host {
|
||||
log::warn!("--static-file-host is deprecated, which will be removed in the future. Use --data-plane-host instead.");
|
||||
let html = frontend_html.clone();
|
||||
Some(make_http_server(html, static_file_host, websocket_tx.clone()).await)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let srv = make_http_server(frontend_html, args.data_plane_host, websocket_tx).await;
|
||||
log::info!("Data plane server listening on: {}", srv.addr);
|
||||
|
||||
let static_server_addr = static_server.as_ref().map(|s| s.addr).unwrap_or(srv.addr);
|
||||
log::info!("Static file server listening on: {static_server_addr}");
|
||||
|
||||
if open_in_browser {
|
||||
open::that_detached(format!("http://{static_server_addr}"))
|
||||
.log_error("failed to open browser for preview");
|
||||
}
|
||||
|
||||
let _ = tokio::join!(previewer.join(), srv.join, control_plane_server_handle);
|
||||
// Assert that the static server's lifetime is longer than the previewer.
|
||||
let _s = static_server;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct ScrollSource;
|
||||
|
||||
impl Notification for ScrollSource {
|
||||
|
|
@ -750,7 +602,11 @@ fn send_show_document(client: &TypedLspClient<PreviewState>, s: &DocToSrcJumpInf
|
|||
);
|
||||
}
|
||||
|
||||
fn bind_streams(previewer: &mut Previewer, websocket_rx: mpsc::UnboundedReceiver<HyperWebsocket>) {
|
||||
/// Bind the hyper websocket streams to the previewer.
|
||||
pub fn bind_streams(
|
||||
previewer: &mut Previewer,
|
||||
websocket_rx: mpsc::UnboundedReceiver<HyperWebsocket>,
|
||||
) {
|
||||
previewer.start_data_plane(
|
||||
websocket_rx,
|
||||
|conn: Result<HyperWebsocketStream, hyper_tungstenite::tungstenite::Error>| {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ pub struct ProjectPreviewHandler {
|
|||
/// The project id.
|
||||
pub project_id: ProjectInsId,
|
||||
/// The connection to the compiler compiling projects (language server).
|
||||
pub(crate) client: Box<dyn ProjectClient>,
|
||||
pub client: Box<dyn ProjectClient>,
|
||||
}
|
||||
|
||||
impl ProjectPreviewHandler {
|
||||
|
|
|
|||
|
|
@ -1,424 +1,17 @@
|
|||
//! Project management tools.
|
||||
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap_complete::Shell;
|
||||
use parking_lot::Mutex;
|
||||
use reflexo::{path::unix_slash, ImmutPath};
|
||||
use reflexo_typst::WorldComputeGraph;
|
||||
use tinymist_query::analysis::Analysis;
|
||||
use tinymist_std::{bail, error::prelude::*};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::{actor::editor::EditorRequest, world::system::print_diagnostics, Config};
|
||||
use crate::{project::*, task::ExportTask};
|
||||
|
||||
/// Arguments for project compilation.
|
||||
#[derive(Debug, Clone, clap::Parser)]
|
||||
pub struct CompileArgs {
|
||||
/// Inherits the compile task arguments.
|
||||
#[clap(flatten)]
|
||||
pub compile: TaskCompileArgs,
|
||||
|
||||
/// Saves the compilation arguments to the lock file.
|
||||
#[clap(long)]
|
||||
pub save_lock: bool,
|
||||
|
||||
/// Specifies the path to the lock file. If the path is
|
||||
/// set, the lock file will be saved.
|
||||
#[clap(long)]
|
||||
pub lockfile: Option<PathBuf>,
|
||||
}
|
||||
|
||||
/// Arguments for generating a build script.
|
||||
#[derive(Debug, Clone, clap::Parser)]
|
||||
pub struct GenerateScriptArgs {
|
||||
/// 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>,
|
||||
/// The path to the output script.
|
||||
#[clap(short, long)]
|
||||
pub output: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "preview")]
|
||||
pub use super::preview::PreviewArgs;
|
||||
#[cfg(feature = "preview")]
|
||||
pub use tinymist_preview::PreviewMode;
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
#[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> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs project compilation(s)
|
||||
pub async fn compile_main(args: CompileArgs) -> Result<()> {
|
||||
let cwd = std::env::current_dir().context("cannot get cwd")?;
|
||||
// todo: respect the name of the lock file
|
||||
|
||||
// Saves the lock file if the flags are set
|
||||
let save_lock = args.save_lock || args.lockfile.is_some();
|
||||
|
||||
let lock_dir: ImmutPath = if let Some(lockfile) = args.lockfile {
|
||||
let lockfile = if lockfile.is_absolute() {
|
||||
lockfile
|
||||
} else {
|
||||
cwd.join(lockfile)
|
||||
};
|
||||
lockfile
|
||||
.parent()
|
||||
.context("lock file must have a parent directory")?
|
||||
.into()
|
||||
} else {
|
||||
cwd.as_path().into()
|
||||
};
|
||||
|
||||
// Identifies the input and output
|
||||
let input = args.compile.declare.to_input((&cwd, &lock_dir));
|
||||
let output = args.compile.to_task(input.id.clone(), &cwd)?;
|
||||
|
||||
if save_lock {
|
||||
LockFile::update(&lock_dir, |state| {
|
||||
state.replace_document(input.relative_to(&lock_dir));
|
||||
state.replace_task(output.clone());
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
|
||||
// Prepares for the compilation
|
||||
let universe = (input, lock_dir.clone()).resolve()?;
|
||||
let world = universe.snapshot();
|
||||
let graph = WorldComputeGraph::from_world(world);
|
||||
|
||||
// Compiles the project
|
||||
let is_html = matches!(output.task, ProjectTask::ExportHtml(..));
|
||||
let compiled = CompiledArtifact::from_graph(graph, is_html);
|
||||
|
||||
let diag = compiled.diagnostics();
|
||||
print_diagnostics(compiled.world(), diag, DiagnosticFormat::Human)
|
||||
.context_ut("print diagnostics")?;
|
||||
|
||||
if compiled.has_errors() {
|
||||
// todo: we should process case of compile error in fn main function
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Exports the compiled project
|
||||
let lock_dir = save_lock.then_some(lock_dir);
|
||||
ExportTask::do_export(output.task, compiled, lock_dir).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generates a build script for compilation
|
||||
pub fn generate_script_main(args: GenerateScriptArgs) -> Result<()> {
|
||||
let Some(shell) = args.shell.or_else(Shell::from_env) else {
|
||||
bail!("could not infer shell");
|
||||
};
|
||||
let output = Path::new(args.output.as_deref().unwrap_or("build"));
|
||||
|
||||
let output = match shell {
|
||||
Shell::Bash | Shell::Zsh | Shell::Elvish | Shell::Fish => output.with_extension("sh"),
|
||||
Shell::PowerShell => output.with_extension("ps1"),
|
||||
_ => bail!("unsupported shell: {shell:?}"),
|
||||
};
|
||||
|
||||
let script = match shell {
|
||||
Shell::Bash | Shell::Zsh | Shell::PowerShell => shell_build_script(shell)?,
|
||||
_ => bail!("unsupported shell: {shell:?}"),
|
||||
};
|
||||
|
||||
std::fs::write(output, script).context("write script")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generates a build script for shell-like shells
|
||||
fn shell_build_script(shell: Shell) -> Result<String> {
|
||||
let mut output = String::new();
|
||||
|
||||
match shell {
|
||||
Shell::Bash => {
|
||||
output.push_str("#!/usr/bin/env bash\n\n");
|
||||
}
|
||||
Shell::Zsh => {
|
||||
output.push_str("#!/usr/bin/env zsh\n\n");
|
||||
}
|
||||
Shell::PowerShell => {}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let lock_dir = std::env::current_dir().context("current directory")?;
|
||||
|
||||
let lock = LockFile::read(&lock_dir)?;
|
||||
|
||||
struct CmdBuilder(Vec<Cow<'static, str>>);
|
||||
|
||||
impl CmdBuilder {
|
||||
fn new() -> Self {
|
||||
Self(vec![])
|
||||
}
|
||||
|
||||
fn extend(&mut self, args: impl IntoIterator<Item = impl Into<Cow<'static, str>>>) {
|
||||
for arg in args {
|
||||
self.0.push(arg.into());
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, arg: impl Into<Cow<'static, str>>) {
|
||||
self.0.push(arg.into());
|
||||
}
|
||||
|
||||
fn build(self) -> String {
|
||||
self.0.join(" ")
|
||||
}
|
||||
}
|
||||
|
||||
let quote_escape = |s: &str| s.replace("'", r#"'"'"'"#);
|
||||
let quote = |s: &str| format!("'{}'", s.replace("'", r#"'"'"'"#));
|
||||
|
||||
let path_of = |p: &ResourcePath, loc: &str| {
|
||||
let Some(path) = p.to_rel_path(&lock_dir) else {
|
||||
log::error!("could not resolve path for {loc}, path: {p:?}");
|
||||
return String::default();
|
||||
};
|
||||
|
||||
quote(&unix_slash(&path))
|
||||
};
|
||||
|
||||
let base_cmd: Vec<&str> = vec!["tinymist", "compile", "--save-lock"];
|
||||
|
||||
for task in lock.task.iter() {
|
||||
let Some(input) = lock.get_document(&task.document) else {
|
||||
log::warn!(
|
||||
"could not find document for task {:?}, whose document is {:?}",
|
||||
task.id,
|
||||
task.doc_id()
|
||||
);
|
||||
continue;
|
||||
};
|
||||
// todo: preview/query commands
|
||||
let Some(export) = task.task.as_export() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut cmd = CmdBuilder::new();
|
||||
cmd.extend(base_cmd.iter().copied());
|
||||
cmd.push("--task");
|
||||
cmd.push(quote(&task.id.to_string()));
|
||||
|
||||
cmd.push(path_of(&input.main, "main"));
|
||||
|
||||
if let Some(root) = &input.root {
|
||||
cmd.push("--root");
|
||||
cmd.push(path_of(root, "root"));
|
||||
}
|
||||
|
||||
for (k, v) in &input.inputs {
|
||||
cmd.push(format!(
|
||||
r#"--input='{}={}'"#,
|
||||
quote_escape(k),
|
||||
quote_escape(v)
|
||||
));
|
||||
}
|
||||
|
||||
for p in &input.font_paths {
|
||||
cmd.push("--font-path");
|
||||
cmd.push(path_of(p, "font-path"));
|
||||
}
|
||||
|
||||
if !input.system_fonts {
|
||||
cmd.push("--ignore-system-fonts");
|
||||
}
|
||||
|
||||
if let Some(p) = &input.package_path {
|
||||
cmd.push("--package-path");
|
||||
cmd.push(path_of(p, "package-path"));
|
||||
}
|
||||
|
||||
if let Some(p) = &input.package_cache_path {
|
||||
cmd.push("--package-cache-path");
|
||||
cmd.push(path_of(p, "package-cache-path"));
|
||||
}
|
||||
|
||||
if let Some(p) = &export.output {
|
||||
cmd.push("--output");
|
||||
cmd.push(quote(&p.to_string()));
|
||||
}
|
||||
|
||||
for t in &export.transform {
|
||||
match t {
|
||||
ExportTransform::Pretty { .. } => {
|
||||
cmd.push("--pretty");
|
||||
}
|
||||
ExportTransform::Pages { ranges } => {
|
||||
for r in ranges {
|
||||
cmd.push("--pages");
|
||||
cmd.push(r.to_string());
|
||||
}
|
||||
}
|
||||
// todo: export me
|
||||
ExportTransform::Merge { .. } | ExportTransform::Script { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
match &task.task {
|
||||
ProjectTask::Preview(..) | ProjectTask::Query(..) => {}
|
||||
ProjectTask::ExportPdf(task) => {
|
||||
cmd.push("--format=pdf");
|
||||
|
||||
for s in &task.pdf_standards {
|
||||
cmd.push("--pdf-standard");
|
||||
let s = serde_json::to_string(s).context("pdf standard")?;
|
||||
cmd.push(s);
|
||||
}
|
||||
|
||||
if let Some(output) = &task.creation_timestamp {
|
||||
cmd.push("--creation-timestamp");
|
||||
cmd.push(output.to_string());
|
||||
}
|
||||
}
|
||||
ProjectTask::ExportSvg(..) => {
|
||||
cmd.push("--format=svg");
|
||||
}
|
||||
ProjectTask::ExportSvgHtml(..) => {
|
||||
cmd.push("--format=svg_html");
|
||||
}
|
||||
ProjectTask::ExportMd(..) => {
|
||||
cmd.push("--format=md");
|
||||
}
|
||||
ProjectTask::ExportTeX(..) => {
|
||||
cmd.push("--format=tex");
|
||||
}
|
||||
ProjectTask::ExportPng(..) => {
|
||||
cmd.push("--format=png");
|
||||
}
|
||||
ProjectTask::ExportText(..) => {
|
||||
cmd.push("--format=txt");
|
||||
}
|
||||
ProjectTask::ExportHtml(..) => {
|
||||
cmd.push("--format=html");
|
||||
}
|
||||
}
|
||||
|
||||
let ext = task.task.extension();
|
||||
|
||||
output.push_str(&format!(
|
||||
"# From {} to {} ({ext})\n",
|
||||
task.doc_id(),
|
||||
task.id
|
||||
));
|
||||
output.push_str(&cmd.build());
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Project document commands' main
|
||||
pub fn project_main(args: DocCommands) -> Result<()> {
|
||||
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) => {
|
||||
let id: Id = args.id.id(ctx);
|
||||
|
||||
state.route.push(ProjectRoute {
|
||||
id: id.clone(),
|
||||
priority: args.priority,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// 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 ctx: (&Path, &Path) = (&cwd, &cwd);
|
||||
let _ = state;
|
||||
match args {
|
||||
#[cfg(feature = "preview")]
|
||||
TaskCommands::Preview(args) => {
|
||||
let input = args.declare.to_input(ctx);
|
||||
let id = input.id.clone();
|
||||
state.replace_document(input);
|
||||
let _ = state.preview(id, &args);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
use crate::project::*;
|
||||
use crate::{actor::editor::EditorRequest, Config};
|
||||
|
||||
/// Options for starting a project.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct ProjectOpts {
|
||||
pub struct ProjectOpts {
|
||||
/// The tokio runtime handle.
|
||||
pub handle: Option<tokio::runtime::Handle>,
|
||||
/// The shared preview state.
|
||||
|
|
@ -426,21 +19,26 @@ pub(crate) struct ProjectOpts {
|
|||
/// The shared config.
|
||||
pub config: Config,
|
||||
/// The shared preview state.
|
||||
#[cfg(feature = "preview")]
|
||||
pub preview: ProjectPreviewState,
|
||||
/// The export target.
|
||||
pub export_target: ExportTarget,
|
||||
}
|
||||
|
||||
pub(crate) struct StartProjectResult<F> {
|
||||
/// Result of starting a project.
|
||||
pub struct StartProjectResult<F> {
|
||||
/// A future service that runs the project.
|
||||
pub service: WatchService<F>,
|
||||
/// The interrupt sender.
|
||||
pub intr_tx: mpsc::UnboundedSender<LspInterrupt>,
|
||||
/// The editor request receiver.
|
||||
pub editor_rx: mpsc::UnboundedReceiver<EditorRequest>,
|
||||
}
|
||||
|
||||
// todo: This is only extracted from the `tinymist preview` command, and we need
|
||||
// to abstract it in future.
|
||||
/// Start a project with the given universe.
|
||||
pub(crate) fn start_project<F>(
|
||||
/// Starts a project with the given universe.
|
||||
pub fn start_project<F>(
|
||||
verse: LspUniverse,
|
||||
opts: Option<ProjectOpts>,
|
||||
intr_handler: F,
|
||||
|
|
@ -453,23 +51,37 @@ where
|
|||
),
|
||||
{
|
||||
let opts = opts.unwrap_or_default();
|
||||
#[cfg(any(feature = "export", feature = "system"))]
|
||||
let handle = opts.handle.unwrap_or_else(tokio::runtime::Handle::current);
|
||||
|
||||
let _ = opts.config;
|
||||
|
||||
// type EditorSender = mpsc::UnboundedSender<EditorRequest>;
|
||||
let (editor_tx, editor_rx) = mpsc::unbounded_channel();
|
||||
let (intr_tx, intr_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
// todo: unify filesystem watcher
|
||||
let (dep_tx, dep_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let (dep_tx, dep_rx) = mpsc::unbounded_channel();
|
||||
// todo: notify feature?
|
||||
#[cfg(feature = "system")]
|
||||
{
|
||||
let fs_intr_tx = intr_tx.clone();
|
||||
handle.spawn(watch_deps(dep_rx, move |event| {
|
||||
fs_intr_tx.interrupt(LspInterrupt::Fs(event));
|
||||
}));
|
||||
}
|
||||
#[cfg(not(feature = "system"))]
|
||||
{
|
||||
let _ = dep_rx;
|
||||
log::warn!("Project: system watcher is not enabled, file changes will not be watched");
|
||||
}
|
||||
|
||||
// Create the actor
|
||||
let compile_handle = Arc::new(CompileHandlerImpl {
|
||||
#[cfg(feature = "preview")]
|
||||
preview: opts.preview,
|
||||
is_standalone: true,
|
||||
#[cfg(feature = "export")]
|
||||
export: crate::task::ExportTask::new(handle, Some(editor_tx.clone()), opts.config.export()),
|
||||
editor_tx,
|
||||
client: Arc::new(intr_tx.clone()),
|
||||
|
|
@ -502,7 +114,9 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WatchService<F> {
|
||||
/// A service that watches for project changes and compiles them.
|
||||
pub struct WatchService<F> {
|
||||
/// The project compiler.
|
||||
pub compiler: LspProjectCompiler,
|
||||
intr_rx: tokio::sync::mpsc::UnboundedReceiver<LspInterrupt>,
|
||||
intr_handler: F,
|
||||
|
|
@ -517,6 +131,7 @@ where
|
|||
) + Send
|
||||
+ 'static,
|
||||
{
|
||||
/// Runs the project service.
|
||||
pub async fn run(self) {
|
||||
let Self {
|
||||
mut compiler,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,19 @@
|
|||
use core::fmt;
|
||||
|
||||
#[cfg(feature = "system")]
|
||||
use std::pin::Pin;
|
||||
#[cfg(feature = "system")]
|
||||
use std::sync::atomic::AtomicU64;
|
||||
#[cfg(feature = "system")]
|
||||
use std::sync::Arc;
|
||||
#[cfg(feature = "system")]
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
#[cfg(feature = "system")]
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
#[cfg(feature = "system")]
|
||||
use tokio::net::TcpStream;
|
||||
#[cfg(feature = "system")]
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Derived<T>(pub T);
|
||||
|
|
@ -52,7 +59,6 @@ macro_rules! get_arg_or_default {
|
|||
}};
|
||||
}
|
||||
pub(crate) use get_arg_or_default;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
pub fn try_<T>(f: impl FnOnce() -> Option<T>) -> Option<T> {
|
||||
f()
|
||||
|
|
@ -62,17 +68,11 @@ pub fn try_or<T>(f: impl FnOnce() -> Option<T>, default: T) -> T {
|
|||
f().unwrap_or(default)
|
||||
}
|
||||
|
||||
pub fn exit_on_ctrl_c() {
|
||||
tokio::spawn(async move {
|
||||
let _ = tokio::signal::ctrl_c().await;
|
||||
log::info!("Ctrl-C received, exiting");
|
||||
std::process::exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "system")]
|
||||
#[derive(Default)]
|
||||
pub(crate) struct AliveLock(Arc<AtomicU64>);
|
||||
|
||||
#[cfg(feature = "system")]
|
||||
impl AliveLock {
|
||||
pub fn hold(cnt: Arc<AtomicU64>) -> Self {
|
||||
let held = cnt.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||
|
|
@ -81,6 +81,7 @@ impl AliveLock {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "system")]
|
||||
impl Drop for AliveLock {
|
||||
fn drop(&mut self) {
|
||||
let cnt = self.0.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
|
||||
|
|
@ -88,11 +89,13 @@ impl Drop for AliveLock {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "system")]
|
||||
pub(crate) struct ConnWithCancel {
|
||||
stream: TcpStream,
|
||||
pub cancel: CancellationToken,
|
||||
}
|
||||
|
||||
#[cfg(feature = "system")]
|
||||
impl ConnWithCancel {
|
||||
pub fn new(stream: TcpStream) -> Self {
|
||||
Self {
|
||||
|
|
@ -102,12 +105,14 @@ impl ConnWithCancel {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "system")]
|
||||
impl Drop for ConnWithCancel {
|
||||
fn drop(&mut self) {
|
||||
self.cancel.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "system")]
|
||||
impl AsyncRead for ConnWithCancel {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
|
|
@ -118,6 +123,7 @@ impl AsyncRead for ConnWithCancel {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "system")]
|
||||
impl AsyncWrite for ConnWithCancel {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import tinymist_init from "../pkg/tinymist_core.js";
|
||||
import * as tinymist from "../pkg/tinymist_core.js";
|
||||
import tinymist_init from "../pkg/tinymist.js";
|
||||
import * as tinymist from "../pkg/tinymist.js";
|
||||
import fs from "fs";
|
||||
|
||||
const wasmData = fs.readFileSync("pkg/tinymist_core_bg.wasm");
|
||||
const wasmData = fs.readFileSync("pkg/tinymist_bg.wasm");
|
||||
|
||||
async function main() {
|
||||
await tinymist_init({
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
!out/tinymist-docs.pdf
|
||||
!out/extension.js
|
||||
!out/extension.web.js
|
||||
!out/tinymist_core_bg.wasm
|
||||
!out/tinymist_bg.wasm
|
||||
!out/tinymist
|
||||
!out/tinymist.exe
|
||||
!out/typst.tmLanguage.json
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
"type": "module",
|
||||
"license": "Apache-2.0",
|
||||
"workspaces": [
|
||||
"crates/tinymist-core",
|
||||
"crates/tinymist",
|
||||
"editors/vscode",
|
||||
"contrib/typst-preview/editors/vscode",
|
||||
"contrib/html/editors/vscode",
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
"build:preview": "cd tools/typst-preview-frontend && yarn run build && rimraf ../../crates/tinymist-assets/src/typst-preview.html && cpr ./dist/index.html ../../crates/tinymist-assets/src/typst-preview.html",
|
||||
"build:l10n": "yarn extract:l10n && node scripts/build-l10n.mjs",
|
||||
"build:docker": "docker build -t myriaddreamin/tinymist:0.13.22 .",
|
||||
"build:web": "cd crates/tinymist-core && yarn build && cp pkg/tinymist_core_bg.wasm ../../editors/vscode/out/tinymist_core_bg.wasm",
|
||||
"build:web": "cd crates/tinymist && yarn build && cp pkg/tinymist_bg.wasm ../../editors/vscode/out/tinymist_bg.wasm",
|
||||
"extract:l10n": "yarn extract:l10n:ts && yarn extract:l10n:rs",
|
||||
"extract:l10n:ts": "cargo run --release --bin tinymist-l10n -- --kind ts --dir ./editors/vscode --output ./locales/tinymist-vscode-rt.toml",
|
||||
"extract:l10n:rs": "cargo run --release --bin tinymist-l10n -- --kind rs --dir ./crates --output ./locales/tinymist-rt.toml && rimraf ./crates/tinymist-assets/src/tinymist-rt.toml && cpr ./locales/tinymist-rt.toml ./crates/tinymist-assets/src/tinymist-rt.toml",
|
||||
|
|
|
|||
|
|
@ -5,3 +5,10 @@ cargo clippy -p sync-ls --no-default-features --features=lsp,dap
|
|||
|
||||
cargo clippy -p typlite --no-default-features --features=cli,no-content-hint
|
||||
cargo clippy -p typlite --no-default-features --features=cli,docx,no-content-hint
|
||||
|
||||
cargo clippy -p tinymist --no-default-features --features=no-content-hint
|
||||
cargo clippy -p tinymist --no-default-features --features=no-content-hint,preview
|
||||
# cargo clippy -p tinymist --no-default-features --features=no-content-hint,export
|
||||
# cargo clippy -p tinymist --no-default-features --features=no-content-hint,trace
|
||||
cargo clippy -p tinymist --no-default-features --features=no-content-hint,dap
|
||||
cargo clippy -p tinymist --no-default-features --features=no-content-hint,web
|
||||
|
|
|
|||
|
|
@ -207,8 +207,8 @@ class NightlyUtils {
|
|||
'typlite',
|
||||
'typst-shim',
|
||||
'sync-lsp',
|
||||
'tinymist-cli',
|
||||
'tinymist-analysis',
|
||||
'tinymist-core',
|
||||
'tinymist-debug',
|
||||
'tinymist-l10n',
|
||||
'tinymist-package',
|
||||
|
|
@ -249,7 +249,7 @@ class NightlyUtils {
|
|||
await this.ensureInit();
|
||||
|
||||
const nonWorldCrates = [
|
||||
'sync-ls', 'tinymist', 'tinymist-analysis', 'tinymist-core', 'tinymist-debug',
|
||||
'sync-ls', 'tinymist-cli', 'tinymist-analysis', 'tinymist', 'tinymist-debug',
|
||||
'tinymist-lint', 'tinymist-query', 'tinymist-render', 'tinymist-preview', 'typlite'
|
||||
];
|
||||
await this.updateDependencies(nonWorldCrates, newVersion);
|
||||
|
|
@ -265,7 +265,7 @@ class NightlyUtils {
|
|||
async updateVersionFiles(newVersion) {
|
||||
const jsonFiles = [
|
||||
'contrib/html/editors/vscode/package.json',
|
||||
'crates/tinymist-core/package.json',
|
||||
'crates/tinymist/package.json',
|
||||
'editors/vscode/package.json',
|
||||
'syntaxes/textmate/package.json'
|
||||
];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue