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: |
|
run: |
|
||||||
npm pack > package-name
|
npm pack > package-name
|
||||||
mv $(cat package-name) tinymist-${{ env.target }}.tar.gz
|
mv $(cat package-name) tinymist-${{ env.target }}.tar.gz
|
||||||
working-directory: ./crates/tinymist-core
|
working-directory: ./crates/tinymist
|
||||||
- name: Upload tinymist npm library
|
- name: Upload tinymist npm library
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: tinymist-${{ env.target }}-npm
|
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
|
- name: Download PDF Documentation
|
||||||
uses: actions/download-artifact@v4
|
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
|
- name: Generate completions
|
||||||
run: |
|
run: |
|
||||||
mkdir -p completions/{zsh,bash,fish/vendor_completions.d,elvish/lib,nushell/vendor/autoload,powershell}/
|
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 --bin tinymist -- completion zsh > completions/zsh/_tinymist
|
||||||
cargo run -p tinymist -- completion bash > completions/bash/tinymist
|
cargo run --bin tinymist -- completion bash > completions/bash/tinymist
|
||||||
cargo run -p tinymist -- completion fish > completions/fish/vendor_completions.d/tinymist.fish
|
cargo run --bin tinymist -- completion fish > completions/fish/vendor_completions.d/tinymist.fish
|
||||||
cargo run -p tinymist -- completion elvish > completions/elvish/lib/tinymist.elv
|
cargo run --bin tinymist -- completion elvish > completions/elvish/lib/tinymist.elv
|
||||||
cargo run -p tinymist -- completion nushell > completions/nushell/vendor/autoload/tinymist.nu
|
cargo run --bin tinymist -- completion nushell > completions/nushell/vendor/autoload/tinymist.nu
|
||||||
cargo run -p tinymist -- completion powershell > completions/powershell/tinymist.ps1
|
cargo run --bin tinymist -- completion powershell > completions/powershell/tinymist.ps1
|
||||||
tar -czvf tinymist-completions.tar.gz completions
|
tar -czvf tinymist-completions.tar.gz completions
|
||||||
- name: upload completions
|
- name: upload completions
|
||||||
uses: actions/upload-artifact@v4
|
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-lint || true
|
||||||
cargo publish --no-verify -p tinymist-query || true
|
cargo publish --no-verify -p tinymist-query || true
|
||||||
cargo publish --no-verify -p tinymist-render || 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-preview || true
|
||||||
cargo publish --no-verify -p tinymist || true
|
cargo publish --no-verify -p tinymist || true
|
||||||
|
cargo publish --no-verify -p tinymist-cli || true
|
||||||
- name: Verifies crate health (Optional)
|
- name: Verifies crate health (Optional)
|
||||||
run: |
|
run: |
|
||||||
cargo publish --dry-run -p sync-ls
|
cargo publish --dry-run -p sync-ls
|
||||||
|
|
|
||||||
71
Cargo.lock
generated
71
Cargo.lock
generated
|
|
@ -4133,6 +4133,7 @@ dependencies = [
|
||||||
"clap_mangen",
|
"clap_mangen",
|
||||||
"codespan-reporting",
|
"codespan-reporting",
|
||||||
"comemo",
|
"comemo",
|
||||||
|
"console_error_panic_hook",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"dapts",
|
"dapts",
|
||||||
"dhat",
|
"dhat",
|
||||||
|
|
@ -4144,6 +4145,7 @@ dependencies = [
|
||||||
"hyper-tungstenite",
|
"hyper-tungstenite",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"itertools 0.13.0",
|
"itertools 0.13.0",
|
||||||
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"lsp-types",
|
"lsp-types",
|
||||||
"open",
|
"open",
|
||||||
|
|
@ -4161,7 +4163,6 @@ dependencies = [
|
||||||
"sync-ls",
|
"sync-ls",
|
||||||
"temp-env",
|
"temp-env",
|
||||||
"tinymist-assets 0.13.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
"tinymist-assets 0.13.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"tinymist-core",
|
|
||||||
"tinymist-debug",
|
"tinymist-debug",
|
||||||
"tinymist-l10n",
|
"tinymist-l10n",
|
||||||
"tinymist-preview",
|
"tinymist-preview",
|
||||||
|
|
@ -4188,6 +4189,7 @@ dependencies = [
|
||||||
"unicode-script",
|
"unicode-script",
|
||||||
"vergen",
|
"vergen",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -4242,18 +4244,77 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinymist-core"
|
name = "tinymist-cli"
|
||||||
version = "0.13.22"
|
version = "0.13.22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"async-trait",
|
||||||
|
"base64",
|
||||||
"cargo_metadata",
|
"cargo_metadata",
|
||||||
"console_error_panic_hook",
|
"chrono",
|
||||||
"js-sys",
|
"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-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-project",
|
||||||
"tinymist-query",
|
"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",
|
"vergen",
|
||||||
"wasm-bindgen",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ reqwest = { version = "^0.12", default-features = false, features = [
|
||||||
"blocking",
|
"blocking",
|
||||||
"multipart",
|
"multipart",
|
||||||
] }
|
] }
|
||||||
|
http-body-util = "0.1.2"
|
||||||
|
|
||||||
# Algorithms
|
# Algorithms
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
|
|
@ -201,9 +202,9 @@ typst-shim = { path = "./crates/typst-shim", version = "0.13.16" }
|
||||||
tinymist-tests = { path = "./crates/tinymist-tests/" }
|
tinymist-tests = { path = "./crates/tinymist-tests/" }
|
||||||
|
|
||||||
sync-ls = { path = "./crates/sync-lsp", version = "0.13.22" }
|
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-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-debug = { path = "./crates/tinymist-debug/", version = "0.13.22" }
|
||||||
tinymist-lint = { path = "./crates/tinymist-lint/", version = "0.13.22" }
|
tinymist-lint = { path = "./crates/tinymist-lint/", version = "0.13.22" }
|
||||||
tinymist-query = { path = "./crates/tinymist-query/", version = "0.13.22" }
|
tinymist-query = { path = "./crates/tinymist-query/", version = "0.13.22" }
|
||||||
|
|
|
||||||
|
|
@ -14,33 +14,27 @@ rust-version.workspace = true
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
|
crossbeam-channel.workspace = true
|
||||||
dapts = { workspace = true, optional = true }
|
dapts = { workspace = true, optional = true }
|
||||||
|
futures.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
lsp-types = { workspace = true, optional = true }
|
lsp-types = { workspace = true, optional = true }
|
||||||
|
parking_lot.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
|
||||||
clap = { workspace = true, optional = true }
|
clap = { workspace = true, optional = true }
|
||||||
crossbeam-channel = { workspace = true, optional = true }
|
tokio = { workspace = true, features = ["rt"], optional = true }
|
||||||
futures = { workspace = true, optional = true }
|
|
||||||
parking_lot = { workspace = true, optional = true }
|
|
||||||
tokio = { workspace = true, features = ["rt", "time"], optional = true }
|
|
||||||
tokio-util = { workspace = true, optional = true }
|
tokio-util = { workspace = true, optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
dap = ["dapts"]
|
dap = ["dapts"]
|
||||||
lsp = ["lsp-types"]
|
lsp = ["lsp-types"]
|
||||||
server = [
|
server = ["tokio"]
|
||||||
"crossbeam-channel",
|
system = ["tokio", "tokio/time", "tokio-util", "clap"]
|
||||||
"futures",
|
|
||||||
"tokio",
|
|
||||||
"tokio-util",
|
|
||||||
"clap",
|
|
||||||
"parking_lot",
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["dap", "lsp", "server"]
|
features = ["dap", "lsp", "system", "server"]
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ pub use server::*;
|
||||||
pub mod req_queue;
|
pub mod req_queue;
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
mod server;
|
mod server;
|
||||||
#[cfg(feature = "server")]
|
#[cfg(all(feature = "server", feature = "system"))]
|
||||||
pub mod transport;
|
pub mod transport;
|
||||||
|
|
||||||
use std::any::Any;
|
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 {
|
pub(crate) fn is_exit(&self) -> bool {
|
||||||
self.method == "exit"
|
self.method == "exit"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ pub struct TConnectionRx<M> {
|
||||||
|
|
||||||
impl<M: TryFrom<Message, Error = anyhow::Error>> TConnectionRx<M> {
|
impl<M: TryFrom<Message, Error = anyhow::Error>> TConnectionRx<M> {
|
||||||
/// Receives a message or an event.
|
/// 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! {
|
crossbeam_channel::select_biased! {
|
||||||
recv(self.lsp) -> msg => Ok(EventOrMessage::Msg(msg?.try_into()?)),
|
recv(self.lsp) -> msg => Ok(EventOrMessage::Msg(msg?.try_into()?)),
|
||||||
recv(self.event) -> event => Ok(event.map(EventOrMessage::Evt)?),
|
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.
|
/// 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),
|
Evt(Event),
|
||||||
|
/// A message received.
|
||||||
Msg(M),
|
Msg(M),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -380,7 +382,7 @@ impl LspClient {
|
||||||
|
|
||||||
/// Finally sends the response if it is not sent before.
|
/// Finally sends the response if it is not sent before.
|
||||||
/// From the definition, the response is already sent if it is `Some(())`.
|
/// 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 {
|
match resp {
|
||||||
// Already responded
|
// Already responded
|
||||||
Ok(Some(())) => {}
|
Ok(Some(())) => {}
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,7 @@ where
|
||||||
///
|
///
|
||||||
/// See [`transport::MirrorArgs`] for information about the record-replay
|
/// See [`transport::MirrorArgs`] for information about the record-replay
|
||||||
/// feature.
|
/// feature.
|
||||||
|
#[cfg(feature = "system")]
|
||||||
pub fn start(
|
pub fn start(
|
||||||
&mut self,
|
&mut self,
|
||||||
inbox: TConnectionRx<LspMessage>,
|
inbox: TConnectionRx<LspMessage>,
|
||||||
|
|
@ -202,6 +203,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts the language server on the given connection.
|
/// Starts the language server on the given connection.
|
||||||
|
#[cfg(feature = "system")]
|
||||||
pub fn start_(&mut self, inbox: TConnectionRx<LspMessage>) -> anyhow::Result<()> {
|
pub fn start_(&mut self, inbox: TConnectionRx<LspMessage>) -> anyhow::Result<()> {
|
||||||
use EventOrMessage::*;
|
use EventOrMessage::*;
|
||||||
// todo: follow what rust analyzer does
|
// todo: follow what rust analyzer does
|
||||||
|
|
@ -273,7 +275,7 @@ where
|
||||||
|
|
||||||
/// Registers and handles a request. This should only be called once per
|
/// Registers and handles a request. This should only be called once per
|
||||||
/// incoming request.
|
/// 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
|
self.client
|
||||||
.register_request(&req.method, &req.id, request_received);
|
.register_request(&req.method, &req.id, request_received);
|
||||||
|
|
||||||
|
|
@ -353,7 +355,11 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles an incoming notification.
|
/// 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);
|
self.client.hook.start_notification(¬.method);
|
||||||
let handle = |s, Notification { method, params }: Notification| {
|
let handle = |s, Notification { method, params }: Notification| {
|
||||||
let Some(handler) = self.notifications.get(method.as_str()) else {
|
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 sync_ls::transport::MirrorArgs;
|
||||||
use tinymist::project::DocCommands;
|
use tinymist::project::DocCommands;
|
||||||
use tinymist::tool::project::{CompileArgs, GenerateScriptArgs, TaskCommands};
|
use tinymist::LONG_VERSION;
|
||||||
use tinymist::tool::testing::TestArgs;
|
|
||||||
use tinymist::{CompileFontArgs, CompileOnceArgs};
|
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)]
|
#[derive(Debug, Clone, clap::Parser)]
|
||||||
#[clap(name = "tinymist", author, version, about, long_version(LONG_VERSION.as_str()))]
|
#[clap(name = "tinymist", author, version, about, long_version(LONG_VERSION.as_str()))]
|
||||||
|
|
@ -197,3 +206,33 @@ pub enum QueryDocsFormat {
|
||||||
Json,
|
Json,
|
||||||
Markdown,
|
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")]
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
mod args;
|
mod args;
|
||||||
|
#[cfg(feature = "export")]
|
||||||
|
mod compile;
|
||||||
|
mod generate_script;
|
||||||
|
#[cfg(feature = "preview")]
|
||||||
|
mod preview;
|
||||||
|
mod testing;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
@ -21,22 +28,31 @@ use sync_ls::{
|
||||||
internal_error, DapBuilder, DapMessage, GetMessageKind, LsHook, LspBuilder, LspClientRoot,
|
internal_error, DapBuilder, DapMessage, GetMessageKind, LsHook, LspBuilder, LspClientRoot,
|
||||||
LspMessage, LspResult, Message, RequestId, TConnectionTx,
|
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::world::TaskInputs;
|
||||||
use tinymist::{Config, DapRegularInit, RegularInit, ServerState, SuperInit, UserActionTask};
|
use tinymist::LONG_VERSION;
|
||||||
use tinymist_core::LONG_VERSION;
|
use tinymist::{Config, RegularInit, ServerState, SuperInit, UserActionTask};
|
||||||
use tinymist_project::EntryResolver;
|
use tinymist_project::EntryResolver;
|
||||||
use tinymist_query::package::PackageInfo;
|
use tinymist_query::package::PackageInfo;
|
||||||
use tinymist_std::hash::{FxBuildHasher, FxHashMap};
|
use tinymist_std::hash::{FxBuildHasher, FxHashMap};
|
||||||
use tinymist_std::{bail, error::prelude::*};
|
use tinymist_std::{bail, error::prelude::*};
|
||||||
|
use typst::ecow::EcoString;
|
||||||
|
|
||||||
#[cfg(feature = "l10n")]
|
#[cfg(feature = "l10n")]
|
||||||
use tinymist_l10n::{load_translations, set_translations};
|
use tinymist_l10n::{load_translations, set_translations};
|
||||||
use typst::ecow::EcoString;
|
#[cfg(feature = "preview")]
|
||||||
|
use tinymist_project::LockFile;
|
||||||
|
#[cfg(feature = "preview")]
|
||||||
|
use tinymist_task::Id;
|
||||||
|
|
||||||
use crate::args::*;
|
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")]
|
#[cfg(feature = "dhat-heap")]
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
static ALLOC: dhat::Alloc = dhat::Alloc;
|
static ALLOC: dhat::Alloc = dhat::Alloc;
|
||||||
|
|
@ -103,19 +119,16 @@ fn main() -> Result<()> {
|
||||||
Commands::Completion(args) => completion(args),
|
Commands::Completion(args) => completion(args),
|
||||||
Commands::Cov(args) => coverage_main(args),
|
Commands::Cov(args) => coverage_main(args),
|
||||||
Commands::Test(args) => RUNTIMES.tokio_runtime.block_on(test_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::Compile(args) => RUNTIMES.tokio_runtime.block_on(compile_main(args)),
|
||||||
Commands::GenerateScript(args) => generate_script_main(args),
|
Commands::GenerateScript(args) => generate_script_main(args),
|
||||||
Commands::Query(query_cmds) => query_main(query_cmds),
|
Commands::Query(query_cmds) => query_main(query_cmds),
|
||||||
Commands::Lsp(args) => lsp_main(args),
|
Commands::Lsp(args) => lsp_main(args),
|
||||||
|
#[cfg(feature = "dap")]
|
||||||
Commands::Dap(args) => dap_main(args),
|
Commands::Dap(args) => dap_main(args),
|
||||||
Commands::TraceLsp(args) => trace_lsp_main(args),
|
Commands::TraceLsp(args) => trace_lsp_main(args),
|
||||||
#[cfg(feature = "preview")]
|
#[cfg(feature = "preview")]
|
||||||
Commands::Preview(args) => {
|
Commands::Preview(args) => RUNTIMES.tokio_runtime.block_on(preview_main(args)),
|
||||||
#[cfg(feature = "preview")]
|
|
||||||
use tinymist::tool::preview::preview_main;
|
|
||||||
|
|
||||||
RUNTIMES.tokio_runtime.block_on(preview_main(args))
|
|
||||||
}
|
|
||||||
Commands::Doc(args) => project_main(args),
|
Commands::Doc(args) => project_main(args),
|
||||||
Commands::Task(args) => task_main(args),
|
Commands::Task(args) => task_main(args),
|
||||||
Commands::Probe => Ok(()),
|
Commands::Probe => Ok(()),
|
||||||
|
|
@ -163,6 +176,7 @@ pub fn lsp_main(args: LspArgs) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The main entry point for the language server.
|
/// The main entry point for the language server.
|
||||||
|
#[cfg(feature = "dap")]
|
||||||
pub fn dap_main(args: DapArgs) -> Result<()> {
|
pub fn dap_main(args: DapArgs) -> Result<()> {
|
||||||
let pairs = LONG_VERSION.trim().split('\n');
|
let pairs = LONG_VERSION.trim().split('\n');
|
||||||
let pairs = pairs
|
let pairs = pairs
|
||||||
|
|
@ -175,7 +189,7 @@ pub fn dap_main(args: DapArgs) -> Result<()> {
|
||||||
with_stdio_transport::<DapMessage>(args.mirror.clone(), |conn| {
|
with_stdio_transport::<DapMessage>(args.mirror.clone(), |conn| {
|
||||||
let client = client_root(conn.sender);
|
let client = client_root(conn.sender);
|
||||||
ServerState::install_dap(DapBuilder::new(
|
ServerState::install_dap(DapBuilder::new(
|
||||||
DapRegularInit {
|
tinymist::DapRegularInit {
|
||||||
client: client.weak().to_typed(),
|
client: client.weak().to_typed(),
|
||||||
font_opts: args.font,
|
font_opts: args.font,
|
||||||
},
|
},
|
||||||
|
|
@ -342,6 +356,85 @@ pub fn query_main(cmds: QueryCommands) -> Result<()> {
|
||||||
Ok(())
|
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.
|
/// Creates a new language server host.
|
||||||
fn client_root<M: TryFrom<Message, Error = anyhow::Error> + GetMessageKind>(
|
fn client_root<M: TryFrom<Message, Error = anyhow::Error> + GetMessageKind>(
|
||||||
sender: TConnectionTx<M>,
|
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::{utils::PicoStr, World};
|
||||||
use typst_shim::eval::TypstEngine;
|
use typst_shim::eval::TypstEngine;
|
||||||
|
|
||||||
use super::project::{start_project, StartProjectResult};
|
use tinymist::project::*;
|
||||||
use crate::world::{with_main, SourceWorld};
|
use tinymist::tool::project::{start_project, StartProjectResult};
|
||||||
use crate::{project::*, utils::exit_on_ctrl_c};
|
use tinymist::world::{with_main, SourceWorld};
|
||||||
|
|
||||||
|
use crate::utils::exit_on_ctrl_c;
|
||||||
|
|
||||||
const TEST_EVICT_MAX_AGE: usize = 30;
|
const TEST_EVICT_MAX_AGE: usize = 30;
|
||||||
const PREFIX_LEN: usize = 7;
|
const PREFIX_LEN: usize = 7;
|
||||||
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())
|
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.
|
/// Resolves package registry from given options.
|
||||||
#[cfg(feature = "system")]
|
#[cfg(feature = "system")]
|
||||||
pub fn resolve_package(
|
pub fn resolve_package(
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ sha2 = { version = "0.10" }
|
||||||
hex = { version = "0.4" }
|
hex = { version = "0.4" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
local-registry = ["tinymist-world/system"]
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,8 @@ impl CompletionPair<'_, '_, '_> {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(spec, desc)| (spec, desc.clone()))
|
.map(|(spec, desc)| (spec, desc.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
|
#[cfg(feature = "http-registry")]
|
||||||
|
{
|
||||||
// local_packages to references and add them to the packages
|
// local_packages to references and add them to the packages
|
||||||
let local_packages_refs = self.worker.ctx.local_packages();
|
let local_packages_refs = self.worker.ctx.local_packages();
|
||||||
packages.extend(
|
packages.extend(
|
||||||
|
|
@ -41,6 +43,7 @@ impl CompletionPair<'_, '_, '_> {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|spec| (spec, Some(eco_format!("{} v{}", spec.name, spec.version)))),
|
.map(|spec| (spec, Some(eco_format!("{} v{}", spec.name, spec.version)))),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
packages.sort_by_key(|(spec, _)| (&spec.namespace, &spec.name, Reverse(spec.version)));
|
packages.sort_by_key(|(spec, _)| (&spec.namespace, &spec.name, Reverse(spec.version)));
|
||||||
if !all_versions {
|
if !all_versions {
|
||||||
|
|
|
||||||
|
|
@ -672,7 +672,7 @@ impl SharedContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the local packages and their descriptions.
|
/// Get the local packages and their descriptions.
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(feature = "local-registry")]
|
||||||
pub fn local_packages(&self) -> EcoVec<PackageSpec> {
|
pub fn local_packages(&self) -> EcoVec<PackageSpec> {
|
||||||
crate::package::list_package_by_namespace(&self.world.registry, eco_format!("local"))
|
crate::package::list_package_by_namespace(&self.world.registry, eco_format!("local"))
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
@ -681,7 +681,7 @@ impl SharedContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the local packages and their descriptions.
|
/// Get the local packages and their descriptions.
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(not(feature = "local-registry"))]
|
||||||
pub fn local_packages(&self) -> EcoVec<PackageSpec> {
|
pub fn local_packages(&self) -> EcoVec<PackageSpec> {
|
||||||
eco_vec![]
|
eco_vec![]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,14 +43,14 @@ pub fn path_res_to_url(path: PathResolution) -> anyhow::Result<Url> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a URL to a path.
|
/// 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" {
|
if uri.scheme() == "file" {
|
||||||
// typst converts an empty path to `Path::new("/")`, which is undesirable.
|
// typst converts an empty path to `Path::new("/")`, which is undesirable.
|
||||||
if !uri.has_host() && uri.path() == "/" {
|
if !uri.has_host() && uri.path() == "/" {
|
||||||
return PathBuf::from("/untitled/nEoViM-BuG");
|
return PathBuf::from("/untitled/nEoViM-BuG");
|
||||||
}
|
}
|
||||||
|
|
||||||
return url_to_file_path(&uri);
|
return url_to_file_path(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
if uri.scheme() == "untitled" {
|
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();
|
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"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
|
@ -114,7 +114,7 @@ mod test {
|
||||||
assert_eq!(uri.scheme(), "untitled");
|
assert_eq!(uri.scheme(), "untitled");
|
||||||
assert_eq!(uri.path(), "test");
|
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());
|
assert_eq!(path, Path::new("/untitled/test").clean());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,7 +122,7 @@ mod test {
|
||||||
fn unnamed_buffer() {
|
fn unnamed_buffer() {
|
||||||
// https://github.com/neovim/nvim-lspconfig/pull/2226
|
// https://github.com/neovim/nvim-lspconfig/pull/2226
|
||||||
let uri = EMPTY_URL.clone();
|
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"));
|
assert_eq!(path, Path::new("/untitled/nEoViM-BuG"));
|
||||||
|
|
||||||
let uri2 = path_to_url(&path).unwrap();
|
let uri2 = path_to_url(&path).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ pub fn check_package(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<()
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(feature = "local-registry")]
|
||||||
/// Get the packages in namespaces and their descriptions.
|
/// Get the packages in namespaces and their descriptions.
|
||||||
pub fn list_package_by_namespace(
|
pub fn list_package_by_namespace(
|
||||||
registry: &tinymist_world::package::registry::HttpRegistry,
|
registry: &tinymist_world::package::registry::HttpRegistry,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use rayon::iter::ParallelIterator;
|
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||||
use typst::foundations::Bytes;
|
use typst::foundations::Bytes;
|
||||||
use typst::text::{FontBook, FontInfo};
|
use typst::text::{FontBook, FontInfo};
|
||||||
|
|
||||||
|
|
@ -25,17 +25,41 @@ impl MemoryFontSearcher {
|
||||||
Self { fonts: vec![] }
|
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.
|
/// Adds an in-memory font.
|
||||||
pub fn add_memory_font(&mut self, data: Bytes) {
|
pub fn add_memory_font(&mut self, data: Bytes) {
|
||||||
self.add_memory_fonts(rayon::iter::once(data));
|
self.add_memory_fonts(rayon::iter::once(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds in-memory fonts.
|
/// 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 {
|
let source = DataSource::Memory(MemoryDataSource {
|
||||||
name: "<memory>".to_owned(),
|
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.
|
/// Adds a number of raw font resources.
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,19 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use js_sys::ArrayBuffer;
|
use js_sys::ArrayBuffer;
|
||||||
|
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||||
use tinymist_std::error::prelude::*;
|
use tinymist_std::error::prelude::*;
|
||||||
use typst::foundations::Bytes;
|
use typst::foundations::Bytes;
|
||||||
use typst::text::{
|
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 wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
use super::{BufferFontLoader, FontLoader, FontResolverImpl, FontSlot};
|
use super::{BufferFontLoader, FontLoader, FontResolverImpl, FontSlot};
|
||||||
|
use crate::config::CompileFontOpts;
|
||||||
use crate::font::cache::FontInfoCache;
|
use crate::font::cache::FontInfoCache;
|
||||||
use crate::font::info::typst_typographic_family;
|
use crate::font::info::typst_typographic_family;
|
||||||
|
use crate::font::memory::MemoryFontSearcher;
|
||||||
|
|
||||||
/// Destructures a JS `[key, value]` pair into a tuple of [`Deserializer`]s.
|
/// Destructures a JS `[key, value]` pair into a tuple of [`Deserializer`]s.
|
||||||
pub(crate) fn convert_pair(pair: JsValue) -> (JsValue, JsValue) {
|
pub(crate) fn convert_pair(pair: JsValue) -> (JsValue, JsValue) {
|
||||||
|
|
@ -365,71 +370,47 @@ impl FontLoader for WebFontLoader {
|
||||||
|
|
||||||
/// Searches for fonts.
|
/// Searches for fonts.
|
||||||
pub struct BrowserFontSearcher {
|
pub struct BrowserFontSearcher {
|
||||||
pub fonts: Vec<(FontInfo, FontSlot)>,
|
/// The base font searcher.
|
||||||
|
base: MemoryFontSearcher,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BrowserFontSearcher {
|
impl BrowserFontSearcher {
|
||||||
/// Create a new, empty browser searcher.
|
/// Creates a new, empty browser searcher.
|
||||||
pub fn new() -> Self {
|
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 {
|
pub fn from_resolver(resolver: FontResolverImpl) -> Self {
|
||||||
let fonts = resolver
|
let base = MemoryFontSearcher::from_resolver(resolver);
|
||||||
.slots
|
Self { base }
|
||||||
.into_iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(idx, slot)| {
|
|
||||||
(
|
|
||||||
resolver
|
|
||||||
.book
|
|
||||||
.info(idx)
|
|
||||||
.expect("font should be in font book")
|
|
||||||
.clone(),
|
|
||||||
slot,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Self { fonts }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new browser searcher with fonts cloned from a FontResolverImpl.
|
/// Builds 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.
|
|
||||||
pub fn build(self) -> FontResolverImpl {
|
pub fn build(self) -> FontResolverImpl {
|
||||||
let (info, slots): (Vec<FontInfo>, Vec<FontSlot>) = self.fonts.into_iter().unzip();
|
self.base.build()
|
||||||
|
|
||||||
let book = FontBook::from_infos(info);
|
|
||||||
|
|
||||||
FontResolverImpl::new(vec![], book, slots)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BrowserFontSearcher {
|
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")]
|
#[cfg(feature = "fonts")]
|
||||||
|
#[deprecated(note = "use `typst_assets::fonts` directly")]
|
||||||
pub fn add_embedded(&mut self) {
|
pub fn add_embedded(&mut self) {
|
||||||
for font_data in typst_assets::fonts() {
|
for font_data in typst_assets::fonts() {
|
||||||
let buffer = Bytes::new(font_data);
|
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<()> {
|
pub async fn add_web_fonts(&mut self, fonts: js_sys::Array) -> Result<()> {
|
||||||
let font_builder = FontBuilder {};
|
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)?;
|
let (font_ref, font_blob_loader, font_info) = font_builder.font_web_to_typst(&v)?;
|
||||||
|
|
||||||
for (i, info) in font_info.into_iter().enumerate() {
|
for (i, info) in font_info.into_iter().enumerate() {
|
||||||
let index = self.fonts.len();
|
let index = self.base.fonts.len();
|
||||||
self.fonts.push((
|
self.base.fonts.push((
|
||||||
info.clone(),
|
info.clone(),
|
||||||
FontSlot::new(WebFontLoader {
|
FontSlot::new(WebFontLoader {
|
||||||
font: WebFont {
|
font: WebFont {
|
||||||
|
|
@ -470,7 +456,7 @@ impl BrowserFontSearcher {
|
||||||
pub fn add_font_data(&mut self, buffer: Bytes) {
|
pub fn add_font_data(&mut self, buffer: Bytes) {
|
||||||
for (i, info) in FontInfo::iter(buffer.as_slice()).enumerate() {
|
for (i, info) in FontInfo::iter(buffer.as_slice()).enumerate() {
|
||||||
let buffer = buffer.clone();
|
let buffer = buffer.clone();
|
||||||
self.fonts.push((
|
self.base.fonts.push((
|
||||||
info,
|
info,
|
||||||
FontSlot::new(BufferFontLoader {
|
FontSlot::new(BufferFontLoader {
|
||||||
buffer: Some(buffer),
|
buffer: Some(buffer),
|
||||||
|
|
@ -481,7 +467,7 @@ impl BrowserFontSearcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_fonts_mut(&mut self, func: impl FnOnce(&mut Vec<(FontInfo, FontSlot)>)) {
|
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"
|
name = "tinymist"
|
||||||
description = "An integrated language service for Typst."
|
description = "An integrated language service for Typst."
|
||||||
categories = ["compilers", "command-line-utilities"]
|
categories = ["compilers", "command-line-utilities"]
|
||||||
keywords = ["cli", "lsp", "language", "typst"]
|
keywords = ["api", "language", "typst"]
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
@ -11,6 +11,9 @@ homepage.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
rust-version.workspace = true
|
rust-version.workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
|
|
@ -25,29 +28,20 @@ clap_mangen.workspace = true
|
||||||
crossbeam-channel.workspace = true
|
crossbeam-channel.workspace = true
|
||||||
codespan-reporting.workspace = true
|
codespan-reporting.workspace = true
|
||||||
comemo.workspace = true
|
comemo.workspace = true
|
||||||
|
dapts.workspace = true
|
||||||
dhat = { workspace = true, optional = true }
|
dhat = { workspace = true, optional = true }
|
||||||
dirs.workspace = true
|
dirs.workspace = true
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
futures.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
|
itertools.workspace = true
|
||||||
lsp-types.workspace = true
|
lsp-types.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
open.workspace = true
|
open = { workspace = true, optional = true }
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
paste.workspace = true
|
paste.workspace = true
|
||||||
rayon.workspace = true
|
rayon.workspace = true
|
||||||
reflexo.workspace = true
|
reflexo.workspace = true
|
||||||
reflexo-typst = { workspace = true, features = ["system", "svg"] }
|
reflexo-typst = { workspace = true, features = ["svg"] }
|
||||||
reflexo-vec2svg.workspace = true
|
reflexo-vec2svg.workspace = true
|
||||||
rpds.workspace = true
|
rpds.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
|
@ -56,12 +50,15 @@ serde_yaml.workspace = true
|
||||||
strum.workspace = true
|
strum.workspace = true
|
||||||
sync-ls = { workspace = true, features = ["lsp", "server"] }
|
sync-ls = { workspace = true, features = ["lsp", "server"] }
|
||||||
tinymist-assets = { workspace = true }
|
tinymist-assets = { workspace = true }
|
||||||
|
tinymist-debug = { workspace = true, optional = true }
|
||||||
|
tinymist-l10n.workspace = true
|
||||||
tinymist-query.workspace = true
|
tinymist-query.workspace = true
|
||||||
tinymist-std.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-project = { workspace = true, features = ["lsp"] }
|
||||||
tinymist-render.workspace = true
|
tinymist-render.workspace = true
|
||||||
tokio = { workspace = true, features = ["rt-multi-thread", "io-std"] }
|
tinymist-task.workspace = true
|
||||||
|
tokio = { workspace = true }
|
||||||
tokio-util.workspace = true
|
tokio-util.workspace = true
|
||||||
toml.workspace = true
|
toml.workspace = true
|
||||||
ttf-parser.workspace = true
|
ttf-parser.workspace = true
|
||||||
|
|
@ -71,78 +68,73 @@ typst-svg.workspace = true
|
||||||
typst-pdf.workspace = true
|
typst-pdf.workspace = true
|
||||||
typst-render.workspace = true
|
typst-render.workspace = true
|
||||||
typst-timing.workspace = true
|
typst-timing.workspace = true
|
||||||
typst-html = { workspace = true, optional = true }
|
typst-html.workspace = true
|
||||||
typst-shim.workspace = true
|
typst-shim.workspace = true
|
||||||
tinymist-preview = { workspace = true, optional = true }
|
|
||||||
typst-ansi-hl.workspace = true
|
typst-ansi-hl.workspace = true
|
||||||
tinymist-task.workspace = true
|
|
||||||
tinymist-debug.workspace = true
|
|
||||||
typstfmt.workspace = true
|
typstfmt.workspace = true
|
||||||
typstyle-core.workspace = true
|
typstyle-core.workspace = true
|
||||||
unicode-script.workspace = true
|
unicode-script.workspace = true
|
||||||
walkdir.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]
|
console_error_panic_hook = { version = "0.1.2", optional = true }
|
||||||
default = [
|
js-sys = { version = "0.3.77", optional = true }
|
||||||
"cli",
|
wasm-bindgen = { version = "0.2.100", optional = true }
|
||||||
"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"]
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
temp-env.workspace = true
|
temp-env.workspace = true
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
cargo_metadata = "0.18.0"
|
|
||||||
vergen.workspace = true
|
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]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
|
||||||
|
|
@ -9,19 +9,19 @@
|
||||||
"Typst"
|
"Typst"
|
||||||
],
|
],
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"module": "pkg/tinymist_core.js",
|
"module": "pkg/tinymist.js",
|
||||||
"types": "pkg/tinymist_core.d.ts",
|
"types": "pkg/tinymist.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
"pkg/tinymist_core_bg.wasm",
|
"pkg/tinymist_bg.wasm",
|
||||||
"pkg/tinymist_core_bg.wasm.d.ts",
|
"pkg/tinymist_bg.wasm.d.ts",
|
||||||
"pkg/tinymist_core_bg.js",
|
"pkg/tinymist_bg.js",
|
||||||
"pkg/tinymist_core.js",
|
"pkg/tinymist.js",
|
||||||
"pkg/tinymist_core.d.ts"
|
"pkg/tinymist.d.ts"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:dev": "wasm-pack build --target web --dev -- --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",
|
"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",
|
"build": "wasm-pack build --target web -- --no-default-features --features web,no-content-hint",
|
||||||
"publish:dry": "npm publish --dry-run",
|
"publish:dry": "npm publish --dry-run",
|
||||||
"publish:lib": "npm publish || exit 0",
|
"publish:lib": "npm publish || exit 0",
|
||||||
"test:chrome": "wasm-pack test --chrome --headless --release",
|
"test:chrome": "wasm-pack test --chrome --headless --release",
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
//! Tinymist LSP commands
|
//! Tinymist LSP commands
|
||||||
|
|
||||||
use std::ops::{Deref, Range};
|
use std::ops::Range;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use lsp_types::TextDocumentIdentifier;
|
use lsp_types::TextDocumentIdentifier;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Deserialize;
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value as JsonValue;
|
||||||
use sync_ls::RequestId;
|
use sync_ls::RequestId;
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
use task::TraceParams;
|
use task::TraceParams;
|
||||||
use tinymist_assets::TYPST_PREVIEW_HTML;
|
use tinymist_assets::TYPST_PREVIEW_HTML;
|
||||||
use tinymist_project::{
|
use tinymist_project::{
|
||||||
|
|
@ -17,14 +18,20 @@ use tinymist_query::package::PackageInfo;
|
||||||
use tinymist_query::{LocalContextGuard, LspRange};
|
use tinymist_query::{LocalContextGuard, LspRange};
|
||||||
use tinymist_std::error::prelude::*;
|
use tinymist_std::error::prelude::*;
|
||||||
use tinymist_task::ExportMarkdownTask;
|
use tinymist_task::ExportMarkdownTask;
|
||||||
use typst::diag::{eco_format, EcoString, StrResult};
|
use typst::diag::{eco_format, StrResult};
|
||||||
use typst::syntax::package::{PackageSpec, VersionlessPackageSpec};
|
|
||||||
use typst::syntax::{LinkedNode, Source};
|
use typst::syntax::{LinkedNode, Source};
|
||||||
use world::TaskInputs;
|
use world::TaskInputs;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::lsp::query::{run_query, LspClientExt};
|
use crate::lsp::query::{run_query, LspClientExt};
|
||||||
use crate::tool::ast::AstRepr;
|
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;
|
use crate::tool::package::InitTask;
|
||||||
|
|
||||||
/// See [`ProjectTask`].
|
/// See [`ProjectTask`].
|
||||||
|
|
@ -416,10 +423,11 @@ impl ServerState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize a new template.
|
/// Initialize a new template.
|
||||||
|
#[cfg(feature = "system")]
|
||||||
pub fn init_template(&mut self, mut args: Vec<JsonValue>) -> AnySchedulableResponse {
|
pub fn init_template(&mut self, mut args: Vec<JsonValue>) -> AnySchedulableResponse {
|
||||||
use crate::tool::package::{self, TemplateSource};
|
use crate::tool::package::{self, TemplateSource};
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, serde::Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct InitResult {
|
struct InitResult {
|
||||||
entry_path: PathBuf,
|
entry_path: PathBuf,
|
||||||
|
|
@ -466,6 +474,7 @@ impl ServerState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the entry of a template.
|
/// Get the entry of a template.
|
||||||
|
#[cfg(feature = "system")]
|
||||||
pub fn get_template_entry(&mut self, mut args: Vec<JsonValue>) -> AnySchedulableResponse {
|
pub fn get_template_entry(&mut self, mut args: Vec<JsonValue>) -> AnySchedulableResponse {
|
||||||
use crate::tool::package::{self, TemplateSource};
|
use crate::tool::package::{self, TemplateSource};
|
||||||
|
|
||||||
|
|
@ -528,7 +537,9 @@ impl ServerState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the trace data of the document.
|
/// Get the trace data of the document.
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
pub fn get_document_trace(&mut self, mut args: Vec<JsonValue>) -> AnySchedulableResponse {
|
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();
|
let path = get_arg!(args[0] as PathBuf).into();
|
||||||
|
|
||||||
// get path to self program
|
// get path to self program
|
||||||
|
|
@ -573,6 +584,7 @@ impl ServerState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start to get the trace data of the server.
|
/// Start to get the trace data of the server.
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
pub fn start_server_trace(&mut self, _args: Vec<JsonValue>) -> AnySchedulableResponse {
|
pub fn start_server_trace(&mut self, _args: Vec<JsonValue>) -> AnySchedulableResponse {
|
||||||
let task_cell = &mut self.server_trace;
|
let task_cell = &mut self.server_trace;
|
||||||
if task_cell
|
if task_cell
|
||||||
|
|
@ -595,6 +607,7 @@ impl ServerState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stop getting the trace data of the server.
|
/// Stop getting the trace data of the server.
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
pub fn stop_server_trace(&mut self, _args: Vec<JsonValue>) -> AnySchedulableResponse {
|
pub fn stop_server_trace(&mut self, _args: Vec<JsonValue>) -> AnySchedulableResponse {
|
||||||
let task_cell = &mut self.server_trace;
|
let task_cell = &mut self.server_trace;
|
||||||
if task_cell
|
if task_cell
|
||||||
|
|
@ -671,6 +684,7 @@ impl ServerState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get directory of packages
|
/// Get directory of packages
|
||||||
|
#[cfg(feature = "system")]
|
||||||
pub fn resource_package_dirs(&mut self, _arguments: Vec<JsonValue>) -> AnySchedulableResponse {
|
pub fn resource_package_dirs(&mut self, _arguments: Vec<JsonValue>) -> AnySchedulableResponse {
|
||||||
let snap = self.snapshot().map_err(internal_error)?;
|
let snap = self.snapshot().map_err(internal_error)?;
|
||||||
just_future(async move {
|
just_future(async move {
|
||||||
|
|
@ -681,6 +695,7 @@ impl ServerState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get writable directory of packages
|
/// Get writable directory of packages
|
||||||
|
#[cfg(feature = "system")]
|
||||||
pub fn resource_local_package_dir(
|
pub fn resource_local_package_dir(
|
||||||
&mut self,
|
&mut self,
|
||||||
_arguments: Vec<JsonValue>,
|
_arguments: Vec<JsonValue>,
|
||||||
|
|
@ -694,6 +709,7 @@ impl ServerState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get writable directory of packages
|
/// Get writable directory of packages
|
||||||
|
#[cfg(feature = "system")]
|
||||||
pub fn resource_package_by_ns(
|
pub fn resource_package_by_ns(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut arguments: Vec<JsonValue>,
|
mut arguments: Vec<JsonValue>,
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,11 @@ use reflexo_typst::{ImmutPath, TypstDict};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{Map, Value as JsonValue};
|
use serde_json::{Map, Value as JsonValue};
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
use task::{ExportUserConfig, FormatUserConfig, FormatterConfig};
|
use task::{FormatUserConfig, FormatterConfig};
|
||||||
use tinymist_l10n::DebugL10n;
|
use tinymist_l10n::DebugL10n;
|
||||||
use tinymist_preview::{PreviewConfig, PreviewInvertColors};
|
|
||||||
use tinymist_project::{DynAccessModel, LspAccessModel};
|
use tinymist_project::{DynAccessModel, LspAccessModel};
|
||||||
use tinymist_query::analysis::{Modifier, TokenType};
|
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_render::PeriscopeArgs;
|
||||||
use tinymist_std::error::prelude::*;
|
use tinymist_std::error::prelude::*;
|
||||||
use tinymist_task::ExportTarget;
|
use tinymist_task::ExportTarget;
|
||||||
|
|
@ -26,11 +25,18 @@ use typst_shim::utils::LazyHash;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::project::{
|
use crate::project::{
|
||||||
EntryResolver, ExportPdfTask, ExportTask, ImmutDict, PathPattern, ProjectResolutionKind,
|
EntryResolver, ExportTask, ImmutDict, PathPattern, ProjectResolutionKind, TaskWhen,
|
||||||
ProjectTask, TaskWhen,
|
|
||||||
};
|
};
|
||||||
use crate::world::font::FontResolverImpl;
|
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
|
// region Configuration Items
|
||||||
const CONFIG_ITEMS: &[&str] = &[
|
const CONFIG_ITEMS: &[&str] = &[
|
||||||
"tinymist",
|
"tinymist",
|
||||||
|
|
@ -172,13 +178,13 @@ impl Config {
|
||||||
let roots = match params.workspace_folders.as_ref() {
|
let roots = match params.workspace_folders.as_ref() {
|
||||||
Some(roots) => roots
|
Some(roots) => roots
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|root| root.uri.to_file_path().ok().map(ImmutPath::from))
|
.map(|root| ImmutPath::from(url_to_path(&root.uri)))
|
||||||
.collect(),
|
.collect(),
|
||||||
#[allow(deprecated)] // `params.root_path` is marked as deprecated
|
#[allow(deprecated)] // `params.root_path` is marked as deprecated
|
||||||
None => params
|
None => params
|
||||||
.root_uri
|
.root_uri
|
||||||
.as_ref()
|
.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()))
|
.or_else(|| Some(Path::new(¶ms.root_path.as_ref()?).into()))
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect(),
|
.collect(),
|
||||||
|
|
@ -510,6 +516,7 @@ impl Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the preview configuration.
|
/// Gets the preview configuration.
|
||||||
|
#[cfg(feature = "preview")]
|
||||||
pub fn preview(&self) -> PreviewConfig {
|
pub fn preview(&self) -> PreviewConfig {
|
||||||
PreviewConfig {
|
PreviewConfig {
|
||||||
enable_partial_rendering: self.preview.partial_rendering,
|
enable_partial_rendering: self.preview.partial_rendering,
|
||||||
|
|
@ -529,6 +536,7 @@ impl Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the export configuration.
|
/// Gets the export configuration.
|
||||||
|
#[cfg(feature = "export")]
|
||||||
pub(crate) fn export(&self) -> ExportUserConfig {
|
pub(crate) fn export(&self) -> ExportUserConfig {
|
||||||
let export = self.export_task();
|
let export = self.export_task();
|
||||||
ExportUserConfig {
|
ExportUserConfig {
|
||||||
|
|
@ -675,7 +683,7 @@ impl Config {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(not(feature = "system"))]
|
||||||
fn create_physical_access_model(
|
fn create_physical_access_model(
|
||||||
&self,
|
&self,
|
||||||
client: &TypedLspClient<ServerState>,
|
client: &TypedLspClient<ServerState>,
|
||||||
|
|
@ -683,7 +691,7 @@ impl Config {
|
||||||
self.create_delegate_access_model(client)
|
self.create_delegate_access_model(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(feature = "system")]
|
||||||
fn create_physical_access_model(
|
fn create_physical_access_model(
|
||||||
&self,
|
&self,
|
||||||
_client: &TypedLspClient<ServerState>,
|
_client: &TypedLspClient<ServerState>,
|
||||||
|
|
@ -874,6 +882,7 @@ pub struct PreviewFeat {
|
||||||
#[serde(default, deserialize_with = "deserialize_null_default")]
|
#[serde(default, deserialize_with = "deserialize_null_default")]
|
||||||
pub partial_rendering: bool,
|
pub partial_rendering: bool,
|
||||||
/// Invert colors for the preview.
|
/// Invert colors for the preview.
|
||||||
|
#[cfg(feature = "preview")]
|
||||||
#[serde(default, deserialize_with = "deserialize_null_default")]
|
#[serde(default, deserialize_with = "deserialize_null_default")]
|
||||||
pub invert_colors: PreviewInvertColors,
|
pub invert_colors: PreviewInvertColors,
|
||||||
}
|
}
|
||||||
|
|
@ -971,6 +980,7 @@ where
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
#[cfg(feature = "preview")]
|
||||||
use tinymist_preview::{PreviewInvertColor, PreviewInvertColorObject};
|
use tinymist_preview::{PreviewInvertColor, PreviewInvertColorObject};
|
||||||
|
|
||||||
fn update_config(config: &mut Config, update: &JsonValue) -> Result<()> {
|
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.background.args");
|
||||||
test_good_config("preview.refresh");
|
test_good_config("preview.refresh");
|
||||||
test_good_config("preview.partialRendering");
|
test_good_config("preview.partialRendering");
|
||||||
|
#[cfg(feature = "preview")]
|
||||||
let c = test_good_config("preview.invertColors");
|
let c = test_good_config("preview.invertColors");
|
||||||
|
#[cfg(feature = "preview")]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
c.preview.invert_colors,
|
c.preview.invert_colors,
|
||||||
PreviewInvertColors::Enum(PreviewInvertColor::Never)
|
PreviewInvertColors::Enum(PreviewInvertColor::Never)
|
||||||
|
|
@ -1377,6 +1389,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "preview")]
|
||||||
fn test_default_preview_config() {
|
fn test_default_preview_config() {
|
||||||
let config = Config::default().preview();
|
let config = Config::default().preview();
|
||||||
assert!(!config.enable_partial_rendering);
|
assert!(!config.enable_partial_rendering);
|
||||||
|
|
@ -1385,6 +1398,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "preview")]
|
||||||
fn test_preview_config() {
|
fn test_preview_config() {
|
||||||
let config = Config {
|
let config = Config {
|
||||||
preview: PreviewFeat {
|
preview: PreviewFeat {
|
||||||
|
|
@ -1434,6 +1448,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "preview")]
|
||||||
fn test_invert_colors_validation() {
|
fn test_invert_colors_validation() {
|
||||||
fn test(s: &str) -> anyhow::Result<PreviewInvertColors> {
|
fn test(s: &str) -> anyhow::Result<PreviewInvertColors> {
|
||||||
Ok(serde_json::from_str(s)?)
|
Ok(serde_json::from_str(s)?)
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,7 @@ use tinymist_std::error::prelude::*;
|
||||||
use tinymist_std::ImmutPath;
|
use tinymist_std::ImmutPath;
|
||||||
use typst::{diag::FileResult, syntax::Source};
|
use typst::{diag::FileResult, syntax::Source};
|
||||||
|
|
||||||
use crate::project::{Interrupt, ProjectResolutionKind};
|
use crate::project::Interrupt;
|
||||||
use crate::route::ProjectResolution;
|
|
||||||
use crate::world::vfs::{notify::MemoryEvent, FileChangeSet};
|
use crate::world::vfs::{notify::MemoryEvent, FileChangeSet};
|
||||||
use crate::world::TaskInputs;
|
use crate::world::TaskInputs;
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
@ -14,6 +13,11 @@ use crate::*;
|
||||||
mod client;
|
mod client;
|
||||||
pub use client::ClientAccessModel;
|
pub use client::ClientAccessModel;
|
||||||
|
|
||||||
|
#[cfg(feature = "lock")]
|
||||||
|
use crate::project::ProjectResolutionKind;
|
||||||
|
#[cfg(feature = "lock")]
|
||||||
|
use crate::route::ProjectResolution;
|
||||||
|
|
||||||
/// In memory source file management.
|
/// In memory source file management.
|
||||||
impl ServerState {
|
impl ServerState {
|
||||||
/// Updates a set of source files.
|
/// Updates a set of source files.
|
||||||
|
|
@ -232,6 +236,12 @@ impl ServerState {
|
||||||
.unwrap_or_else(|| self.resolve_task_without_lock(path))
|
.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 {
|
pub(crate) fn resolve_task(&mut self, path: ImmutPath) -> TaskInputs {
|
||||||
let proj_input = matches!(
|
let proj_input = matches!(
|
||||||
self.entry_resolver().project_resolution,
|
self.entry_resolver().project_resolution,
|
||||||
|
|
|
||||||
|
|
@ -1,52 +1,75 @@
|
||||||
//! # tinymist
|
//! Tinymist Core Library
|
||||||
//!
|
|
||||||
//! 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;
|
|
||||||
|
|
||||||
pub use config::*;
|
pub use config::*;
|
||||||
pub use dap::RegularInit as DapRegularInit;
|
|
||||||
pub use dap::SuperInit as DapSuperInit;
|
|
||||||
pub use lsp::init::*;
|
pub use lsp::init::*;
|
||||||
pub use server::*;
|
pub use server::*;
|
||||||
pub use sync_ls::LspClient;
|
pub use sync_ls::LspClient;
|
||||||
pub use task::export2 as export;
|
|
||||||
pub use task::UserActionTask;
|
|
||||||
pub use tinymist_project::world;
|
pub use tinymist_project::world;
|
||||||
pub use tinymist_query as query;
|
pub use tinymist_query as query;
|
||||||
pub use world::{CompileFontArgs, CompileOnceArgs, CompilePackageArgs};
|
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 lsp::query::QueryFuture;
|
||||||
use serde_json::from_value;
|
use serde_json::from_value;
|
||||||
use sync_ls::*;
|
use sync_ls::*;
|
||||||
use utils::*;
|
use utils::*;
|
||||||
use world::*;
|
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 {
|
impl ServerState {
|
||||||
pub(crate) fn did_open(&mut self, params: DidOpenTextDocumentParams) -> LspResult<()> {
|
pub(crate) fn did_open(&mut self, params: DidOpenTextDocumentParams) -> LspResult<()> {
|
||||||
log::info!("did open {}", params.text_document.uri);
|
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;
|
let text = params.text_document.text;
|
||||||
|
|
||||||
self.create_source(path.clone(), text)
|
self.create_source(path.clone(), text)
|
||||||
|
|
@ -98,14 +98,14 @@ impl ServerState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn did_close(&mut self, params: DidCloseTextDocumentParams) -> LspResult<()> {
|
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)?;
|
self.remove_source(path).map_err(invalid_params)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn did_change(&mut self, params: DidChangeTextDocumentParams) -> LspResult<()> {
|
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;
|
let changes = params.content_changes;
|
||||||
|
|
||||||
self.edit_source(path, changes, self.const_config().position_encoding)
|
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<()> {
|
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)?;
|
self.save_source(path).map_err(invalid_params)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -139,10 +139,13 @@ impl ServerState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "export")]
|
||||||
|
{
|
||||||
let new_export_config = self.config.export();
|
let new_export_config = self.config.export();
|
||||||
if old_config.export() != new_export_config {
|
if old_config.export() != new_export_config {
|
||||||
self.change_export_config(new_export_config);
|
self.change_export_config(new_export_config);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if old_config.notify_status != self.config.notify_status {
|
if old_config.notify_status != self.config.notify_status {
|
||||||
self.editor_tx
|
self.editor_tx
|
||||||
|
|
|
||||||
|
|
@ -292,8 +292,8 @@ impl ServerState {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|f| {
|
.map(|f| {
|
||||||
Some((
|
Some((
|
||||||
as_path_(Url::parse(&f.old_uri).ok()?),
|
as_path_(&Url::parse(&f.old_uri).ok()?),
|
||||||
as_path_(Url::parse(&f.new_uri).ok()?),
|
as_path_(&Url::parse(&f.new_uri).ok()?),
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.collect::<Option<Vec<_>>>()
|
.collect::<Option<Vec<_>>>()
|
||||||
|
|
@ -326,7 +326,12 @@ impl ServerState {
|
||||||
DocumentSymbol(req) => query_source!(self, DocumentSymbol, req)?,
|
DocumentSymbol(req) => query_source!(self, DocumentSymbol, req)?,
|
||||||
OnEnter(req) => query_source!(self, OnEnter, req)?,
|
OnEnter(req) => query_source!(self, OnEnter, req)?,
|
||||||
ColorPresentation(req) => CompilerQueryResponse::ColorPresentation(req.request()),
|
ColorPresentation(req) => CompilerQueryResponse::ColorPresentation(req.request()),
|
||||||
|
#[cfg(feature = "export")]
|
||||||
OnExport(req) => return self.on_export(req),
|
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(),
|
ServerInfo(_) => return self.collect_server_info(),
|
||||||
// todo: query on dedicate projects
|
// todo: query on dedicate projects
|
||||||
_ => return self.query_on(query),
|
_ => return self.query_on(query),
|
||||||
|
|
@ -360,6 +365,8 @@ impl ServerState {
|
||||||
just_future(async move {
|
just_future(async move {
|
||||||
stat.snap();
|
stat.snap();
|
||||||
|
|
||||||
|
// todo: preload in web
|
||||||
|
#[cfg(feature = "system")]
|
||||||
if matches!(query, Completion(..)) {
|
if matches!(query, Completion(..)) {
|
||||||
// Prefetch the package index for completion.
|
// Prefetch the package index for completion.
|
||||||
if snap.registry().cached_index().is_none() {
|
if snap.registry().cached_index().is_none() {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
use reflexo_typst::{diag::print_diagnostics, TypstDocument};
|
use reflexo_typst::TypstDocument;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
pub use tinymist_project::*;
|
pub use tinymist_project::*;
|
||||||
|
|
||||||
|
|
@ -42,8 +42,11 @@ use typst::{diag::FileResult, foundations::Bytes, layout::Position as TypstPosit
|
||||||
use super::ServerState;
|
use super::ServerState;
|
||||||
use crate::actor::editor::{EditorRequest, ProjVersion};
|
use crate::actor::editor::{EditorRequest, ProjVersion};
|
||||||
use crate::stats::{CompilerQueryStats, QueryStatGuard};
|
use crate::stats::{CompilerQueryStats, QueryStatGuard};
|
||||||
|
#[cfg(feature = "export")]
|
||||||
use crate::task::ExportUserConfig;
|
use crate::task::ExportUserConfig;
|
||||||
use crate::{Config, ServerEvent};
|
use crate::Config;
|
||||||
|
#[cfg(feature = "preview")]
|
||||||
|
use crate::ServerEvent;
|
||||||
|
|
||||||
type EditorSender = mpsc::UnboundedSender<EditorRequest>;
|
type EditorSender = mpsc::UnboundedSender<EditorRequest>;
|
||||||
|
|
||||||
|
|
@ -53,6 +56,7 @@ pub type LspProjectCompiler = ProjectCompiler<LspCompilerFeat, ProjectInsStateEx
|
||||||
/// Project access and mutations.
|
/// Project access and mutations.
|
||||||
impl ServerState {
|
impl ServerState {
|
||||||
/// Changes the export configuration.
|
/// Changes the export configuration.
|
||||||
|
#[cfg(feature = "export")]
|
||||||
pub fn change_export_config(&mut self, config: ExportUserConfig) {
|
pub fn change_export_config(&mut self, config: ExportUserConfig) {
|
||||||
self.project.export.change_config(config);
|
self.project.export.change_config(config);
|
||||||
}
|
}
|
||||||
|
|
@ -138,6 +142,7 @@ impl ServerState {
|
||||||
let const_config = &config.const_config;
|
let const_config = &config.const_config;
|
||||||
|
|
||||||
// Run Export actors before preparing cluster to avoid loss of events
|
// Run Export actors before preparing cluster to avoid loss of events
|
||||||
|
#[cfg(feature = "export")]
|
||||||
let export = crate::task::ExportTask::new(
|
let export = crate::task::ExportTask::new(
|
||||||
client.handle.clone(),
|
client.handle.clone(),
|
||||||
Some(editor_tx.clone()),
|
Some(editor_tx.clone()),
|
||||||
|
|
@ -150,6 +155,7 @@ impl ServerState {
|
||||||
#[cfg(feature = "preview")]
|
#[cfg(feature = "preview")]
|
||||||
preview,
|
preview,
|
||||||
is_standalone: false,
|
is_standalone: false,
|
||||||
|
#[cfg(feature = "export")]
|
||||||
export: export.clone(),
|
export: export.clone(),
|
||||||
editor_tx: editor_tx.clone(),
|
editor_tx: editor_tx.clone(),
|
||||||
client: Arc::new(client.clone().to_untyped()),
|
client: Arc::new(client.clone().to_untyped()),
|
||||||
|
|
@ -207,11 +213,20 @@ impl ServerState {
|
||||||
|
|
||||||
// todo: unify filesystem watcher
|
// todo: unify filesystem watcher
|
||||||
let (dep_tx, dep_rx) = mpsc::unbounded_channel();
|
let (dep_tx, dep_rx) = mpsc::unbounded_channel();
|
||||||
|
// todo: notify feature?
|
||||||
|
#[cfg(feature = "system")]
|
||||||
|
{
|
||||||
let fs_client = client.clone().to_untyped();
|
let fs_client = client.clone().to_untyped();
|
||||||
let async_handle = client.handle.clone();
|
let async_handle = client.handle.clone();
|
||||||
async_handle.spawn(watch_deps(dep_rx, move |event| {
|
async_handle.spawn(watch_deps(dep_rx, move |event| {
|
||||||
fs_client.send_event(LspInterrupt::Fs(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
|
// Create the actor
|
||||||
let compile_handle = handle.clone();
|
let compile_handle = handle.clone();
|
||||||
|
|
@ -227,9 +242,11 @@ impl ServerState {
|
||||||
|
|
||||||
ProjectState {
|
ProjectState {
|
||||||
compiler,
|
compiler,
|
||||||
|
#[cfg(feature = "preview")]
|
||||||
preview: handle.preview.clone(),
|
preview: handle.preview.clone(),
|
||||||
analysis: handle.analysis.clone(),
|
analysis: handle.analysis.clone(),
|
||||||
stats: CompilerQueryStats::default(),
|
stats: CompilerQueryStats::default(),
|
||||||
|
#[cfg(feature = "export")]
|
||||||
export: handle.export.clone(),
|
export: handle.export.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -298,9 +315,11 @@ impl ProjectInsStateExt {
|
||||||
|
|
||||||
pub struct ProjectState {
|
pub struct ProjectState {
|
||||||
pub compiler: LspProjectCompiler,
|
pub compiler: LspProjectCompiler,
|
||||||
pub preview: ProjectPreviewState,
|
|
||||||
pub analysis: Arc<Analysis>,
|
pub analysis: Arc<Analysis>,
|
||||||
pub stats: CompilerQueryStats,
|
pub stats: CompilerQueryStats,
|
||||||
|
#[cfg(feature = "preview")]
|
||||||
|
pub preview: ProjectPreviewState,
|
||||||
|
#[cfg(feature = "export")]
|
||||||
pub export: crate::task::ExportTask,
|
pub export: crate::task::ExportTask,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -414,6 +433,7 @@ pub struct CompileHandlerImpl {
|
||||||
/// language server).
|
/// language server).
|
||||||
pub is_standalone: bool,
|
pub is_standalone: bool,
|
||||||
|
|
||||||
|
#[cfg(feature = "export")]
|
||||||
pub(crate) export: crate::task::ExportTask,
|
pub(crate) export: crate::task::ExportTask,
|
||||||
pub(crate) editor_tx: EditorSender,
|
pub(crate) editor_tx: EditorSender,
|
||||||
pub(crate) client: Arc<dyn ProjectClient>,
|
pub(crate) client: Arc<dyn ProjectClient>,
|
||||||
|
|
@ -422,9 +442,11 @@ pub struct CompileHandlerImpl {
|
||||||
pub(crate) notified_revision: Mutex<FxHashMap<ProjectInsId, (usize, CompileSignal)>>,
|
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);
|
fn interrupt(&self, event: LspInterrupt);
|
||||||
|
#[cfg(feature = "preview")]
|
||||||
fn server_event(&self, event: ServerEvent);
|
fn server_event(&self, event: ServerEvent);
|
||||||
|
#[cfg(feature = "export")]
|
||||||
fn dev_event(&self, event: DevEvent);
|
fn dev_event(&self, event: DevEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -433,10 +455,12 @@ impl ProjectClient for LspClient {
|
||||||
self.send_event(event);
|
self.send_event(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "preview")]
|
||||||
fn server_event(&self, event: ServerEvent) {
|
fn server_event(&self, event: ServerEvent) {
|
||||||
self.send_event(event);
|
self.send_event(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "export")]
|
||||||
fn dev_event(&self, event: DevEvent) {
|
fn dev_event(&self, event: DevEvent) {
|
||||||
self.send_notification::<DevEvent>(&event);
|
self.send_notification::<DevEvent>(&event);
|
||||||
}
|
}
|
||||||
|
|
@ -447,10 +471,12 @@ impl ProjectClient for mpsc::UnboundedSender<LspInterrupt> {
|
||||||
self.send(event).log_error("failed to send interrupt");
|
self.send(event).log_error("failed to send interrupt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "preview")]
|
||||||
fn server_event(&self, _event: ServerEvent) {
|
fn server_event(&self, _event: ServerEvent) {
|
||||||
log::warn!("ProjectClient: server_event is not implemented for mpsc::UnboundedSender<LspInterrupt>");
|
log::warn!("ProjectClient: server_event is not implemented for mpsc::UnboundedSender<LspInterrupt>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "export")]
|
||||||
fn dev_event(&self, _event: DevEvent) {
|
fn dev_event(&self, _event: DevEvent) {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"ProjectClient: dev_event is not implemented for mpsc::UnboundedSender<LspInterrupt>"
|
"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
|
// Prints the diagnostics when we are running the compilation in standalone
|
||||||
// CLI.
|
// CLI.
|
||||||
|
#[cfg(feature = "system")]
|
||||||
if self.is_standalone {
|
if self.is_standalone {
|
||||||
print_diagnostics(
|
crate::project::system::print_diagnostics(
|
||||||
art.world(),
|
art.world(),
|
||||||
art.diagnostics(),
|
art.diagnostics(),
|
||||||
reflexo_typst::DiagnosticFormat::Human,
|
reflexo_typst::DiagnosticFormat::Human,
|
||||||
|
|
@ -705,6 +732,7 @@ impl CompileHandler<LspCompilerFeat, ProjectInsStateExt> for CompileHandlerImpl
|
||||||
.log_error("failed to print diagnostics");
|
.log_error("failed to print diagnostics");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "export")]
|
||||||
self.export.signal(art, &self.client);
|
self.export.signal(art, &self.client);
|
||||||
|
|
||||||
#[cfg(feature = "preview")]
|
#[cfg(feature = "preview")]
|
||||||
|
|
@ -723,7 +751,7 @@ pub type QuerySnapWithStat = (LspQuerySnapshot, QueryStatGuard);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub(crate) struct DevExportEvent {
|
pub struct DevExportEvent {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub when: TaskWhen,
|
pub when: TaskWhen,
|
||||||
pub need_export: bool,
|
pub need_export: bool,
|
||||||
|
|
@ -733,7 +761,7 @@ pub(crate) struct DevExportEvent {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase", tag = "type")]
|
#[serde(rename_all = "camelCase", tag = "type")]
|
||||||
pub(crate) enum DevEvent {
|
pub enum DevEvent {
|
||||||
Export(DevExportEvent),
|
Export(DevExportEvent),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,31 +7,30 @@ use lsp_types::request::ShowMessageRequest;
|
||||||
use lsp_types::*;
|
use lsp_types::*;
|
||||||
use reflexo::debug_loc::LspPosition;
|
use reflexo::debug_loc::LspPosition;
|
||||||
use sync_ls::*;
|
use sync_ls::*;
|
||||||
use tinymist_query::{OnExportRequest, ServerInfoResponse};
|
use tinymist_query::ServerInfoResponse;
|
||||||
use tinymist_std::error::prelude::*;
|
use tinymist_std::error::prelude::*;
|
||||||
use tinymist_std::ImmutPath;
|
use tinymist_std::ImmutPath;
|
||||||
use tinymist_task::ProjectTask;
|
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use typst::syntax::Source;
|
use typst::syntax::Source;
|
||||||
|
|
||||||
use crate::actor::editor::{EditorActor, EditorRequest};
|
use crate::actor::editor::{EditorActor, EditorRequest};
|
||||||
use crate::lsp::query::OnEnter;
|
use crate::lsp::query::OnEnter;
|
||||||
use crate::project::{
|
use crate::project::{EntryResolver, LspInterrupt, ProjectInsId, ProjectState};
|
||||||
update_lock, CompiledArtifact, EntryResolver, LspComputeGraph, LspInterrupt, ProjectInsId,
|
use crate::task::FormatTask;
|
||||||
ProjectState, PROJECT_ROUTE_USER_ACTION_PRIORITY,
|
|
||||||
};
|
|
||||||
use crate::route::ProjectRouteState;
|
|
||||||
use crate::task::{ExportTask, FormatTask, ServerTraceTask, UserActionTask};
|
|
||||||
use crate::world::TaskInputs;
|
|
||||||
use crate::{lsp::init::*, *};
|
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) use futures::Future;
|
||||||
|
|
||||||
pub(crate) fn as_path(inp: TextDocumentIdentifier) -> PathBuf {
|
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)
|
tinymist_query::url_to_path(uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,10 +44,11 @@ pub struct ServerState {
|
||||||
pub client: TypedLspClient<Self>,
|
pub client: TypedLspClient<Self>,
|
||||||
|
|
||||||
// State
|
// State
|
||||||
/// The project route state.
|
|
||||||
pub route: ProjectRouteState,
|
|
||||||
/// The project state.
|
/// The project state.
|
||||||
pub project: ProjectState,
|
pub project: ProjectState,
|
||||||
|
/// The project route state.
|
||||||
|
#[cfg(feature = "lock")]
|
||||||
|
pub route: ProjectRouteState,
|
||||||
/// The preview state.
|
/// The preview state.
|
||||||
#[cfg(feature = "preview")]
|
#[cfg(feature = "preview")]
|
||||||
pub preview: tool::preview::PreviewState,
|
pub preview: tool::preview::PreviewState,
|
||||||
|
|
@ -59,6 +59,7 @@ pub struct ServerState {
|
||||||
pub formatter: FormatTask,
|
pub formatter: FormatTask,
|
||||||
/// The user action tasks running in backend, which will be scheduled by
|
/// The user action tasks running in backend, which will be scheduled by
|
||||||
/// async runtime.
|
/// async runtime.
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
pub user_action: UserActionTask,
|
pub user_action: UserActionTask,
|
||||||
|
|
||||||
// State to synchronize with the client.
|
// State to synchronize with the client.
|
||||||
|
|
@ -83,6 +84,7 @@ pub struct ServerState {
|
||||||
/// The client ever sent manual focusing request.
|
/// The client ever sent manual focusing request.
|
||||||
pub ever_manual_focusing: bool,
|
pub ever_manual_focusing: bool,
|
||||||
/// The running server trace.
|
/// The running server trace.
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
pub server_trace: Option<ServerTraceTask>,
|
pub server_trace: Option<ServerTraceTask>,
|
||||||
|
|
||||||
// Configurations
|
// Configurations
|
||||||
|
|
@ -115,33 +117,36 @@ impl ServerState {
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
client: client.clone(),
|
#[cfg(feature = "dap")]
|
||||||
|
debug: crate::dap::DebugState::default(),
|
||||||
|
#[cfg(feature = "lock")]
|
||||||
route: ProjectRouteState::default(),
|
route: ProjectRouteState::default(),
|
||||||
project: handle,
|
|
||||||
editor_tx,
|
|
||||||
memory_changes: HashMap::new(),
|
|
||||||
#[cfg(feature = "preview")]
|
#[cfg(feature = "preview")]
|
||||||
preview: tool::preview::PreviewState::new(
|
preview: tool::preview::PreviewState::new(
|
||||||
&config,
|
&config,
|
||||||
watchers,
|
watchers,
|
||||||
client.cast(|s| &mut s.preview),
|
client.cast(|s| &mut s.preview),
|
||||||
),
|
),
|
||||||
#[cfg(feature = "dap")]
|
#[cfg(feature = "trace")]
|
||||||
debug: crate::dap::DebugState::default(),
|
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_focusing_by_activities: false,
|
||||||
ever_manual_focusing: false,
|
ever_manual_focusing: false,
|
||||||
sema_tokens_registered: false,
|
sema_tokens_registered: false,
|
||||||
formatter_registered: false,
|
formatter_registered: false,
|
||||||
server_trace: None,
|
|
||||||
config,
|
config,
|
||||||
|
|
||||||
pinning_by_user: false,
|
pinning_by_user: false,
|
||||||
pinning_by_preview: false,
|
pinning_by_preview: false,
|
||||||
pinning_by_browsing_preview: false,
|
pinning_by_browsing_preview: false,
|
||||||
focusing: None,
|
focusing: None,
|
||||||
implicit_position: None,
|
implicit_position: None,
|
||||||
formatter,
|
formatter,
|
||||||
user_action: UserActionTask,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,6 +221,20 @@ impl ServerState {
|
||||||
.with_command("tinymist.doStartBrowsingPreview", State::browse_preview)
|
.with_command("tinymist.doStartBrowsingPreview", State::browse_preview)
|
||||||
.with_command("tinymist.doKillPreview", State::kill_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)?
|
// todo: .on_sync_mut::<notifs::Cancel>(handlers::handle_cancel)?
|
||||||
let mut provider = provider
|
let mut provider = provider
|
||||||
.with_request::<Shutdown>(State::shutdown)
|
.with_request::<Shutdown>(State::shutdown)
|
||||||
|
|
@ -277,12 +296,7 @@ impl ServerState {
|
||||||
.with_command("tinymist.doClearCache", State::clear_cache)
|
.with_command("tinymist.doClearCache", State::clear_cache)
|
||||||
.with_command("tinymist.pinMain", State::pin_document)
|
.with_command("tinymist.pinMain", State::pin_document)
|
||||||
.with_command("tinymist.focusMain", State::focus_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.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.getDocumentMetrics", State::get_document_metrics)
|
||||||
.with_command_("tinymist.getWorkspaceLabels", State::get_workspace_labels)
|
.with_command_("tinymist.getWorkspaceLabels", State::get_workspace_labels)
|
||||||
.with_command_("tinymist.getServerInfo", State::get_server_info)
|
.with_command_("tinymist.getServerInfo", State::get_server_info)
|
||||||
|
|
@ -291,11 +305,8 @@ impl ServerState {
|
||||||
.with_resource("/symbols", State::resource_symbols)
|
.with_resource("/symbols", State::resource_symbols)
|
||||||
.with_resource("/preview/index.html", State::resource_preview_html)
|
.with_resource("/preview/index.html", State::resource_preview_html)
|
||||||
.with_resource("/tutorial", State::resource_tutoral)
|
.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/symbol", State::resource_package_symbols)
|
||||||
.with_resource("/package/docs", State::resource_package_docs)
|
.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);
|
|
||||||
|
|
||||||
// todo: generalize me
|
// todo: generalize me
|
||||||
provider.args.add_commands(
|
provider.args.add_commands(
|
||||||
|
|
@ -310,6 +321,7 @@ impl ServerState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Installs DAP handlers to the language server.
|
/// Installs DAP handlers to the language server.
|
||||||
|
#[cfg(feature = "dap")]
|
||||||
pub fn install_dap<T: Initializer<S = Self> + 'static>(
|
pub fn install_dap<T: Initializer<S = Self> + 'static>(
|
||||||
provider: DapBuilder<T>,
|
provider: DapBuilder<T>,
|
||||||
) -> DapBuilder<T> {
|
) -> DapBuilder<T> {
|
||||||
|
|
@ -445,62 +457,6 @@ impl ServerState {
|
||||||
Ok(tinymist_query::CompilerQueryResponse::ServerInfo(info))
|
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]
|
#[test]
|
||||||
|
|
@ -509,11 +465,11 @@ fn test_as_path() {
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
let uri = Url::parse("untitled:/path/to/file").unwrap();
|
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();
|
let uri = Url::parse("untitled:/path/to/file%20with%20space").unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
as_path_(uri),
|
as_path_(&uri),
|
||||||
Path::new("/untitled/path/to/file with space").clean()
|
Path::new("/untitled/path/to/file with space").clean()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,13 @@ use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::atomic::AtomicUsize;
|
use std::sync::atomic::AtomicUsize;
|
||||||
use std::sync::{Arc, OnceLock};
|
use std::sync::{Arc, OnceLock};
|
||||||
|
use std::{ops::DerefMut, pin::Pin};
|
||||||
|
|
||||||
use reflexo::ImmutPath;
|
use reflexo::ImmutPath;
|
||||||
use reflexo_typst::{Bytes, CompilationTask, ExportComputation};
|
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::error::prelude::*;
|
||||||
use tinymist_std::fs::paths::write_atomic;
|
use tinymist_std::fs::paths::write_atomic;
|
||||||
use tinymist_std::path::PathClean;
|
use tinymist_std::path::PathClean;
|
||||||
|
|
@ -18,24 +21,102 @@ use typlite::{Format, Typlite};
|
||||||
use typst::foundations::IntoValue;
|
use typst::foundations::IntoValue;
|
||||||
use typst::visualize::Color;
|
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::{
|
use crate::project::{
|
||||||
ApplyProjectTask, CompiledArtifact, DevEvent, DevExportEvent, EntryReader, ExportHtmlTask,
|
update_lock, ApplyProjectTask, CompiledArtifact, DevEvent, DevExportEvent, EntryReader,
|
||||||
ExportPdfTask, ExportPngTask, ExportSvgTask, ExportTask as ProjectExportTask, ExportTeXTask,
|
ExportHtmlTask, ExportPdfTask, ExportPngTask, ExportSvgTask, ExportTask as ProjectExportTask,
|
||||||
ExportTextTask, LspCompiledArtifact, ProjectClient, ProjectTask, QueryTask, TaskWhen,
|
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};
|
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)]
|
#[derive(Clone)]
|
||||||
pub struct ExportTask {
|
pub struct ExportTask {
|
||||||
|
/// The handle running the task.
|
||||||
pub handle: tokio::runtime::Handle,
|
pub handle: tokio::runtime::Handle,
|
||||||
|
/// The editor request sender.
|
||||||
pub editor_tx: Option<mpsc::UnboundedSender<EditorRequest>>,
|
pub editor_tx: Option<mpsc::UnboundedSender<EditorRequest>>,
|
||||||
|
/// The task factory for export.
|
||||||
pub factory: SyncTaskFactory<ExportUserConfig>,
|
pub factory: SyncTaskFactory<ExportUserConfig>,
|
||||||
export_folder: FutureFolder,
|
export_folder: FutureFolder,
|
||||||
count_word_folder: FutureFolder,
|
count_word_folder: FutureFolder,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExportTask {
|
impl ExportTask {
|
||||||
|
/// Creates a new export task.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
handle: tokio::runtime::Handle,
|
handle: tokio::runtime::Handle,
|
||||||
editor_tx: Option<mpsc::UnboundedSender<EditorRequest>>,
|
editor_tx: Option<mpsc::UnboundedSender<EditorRequest>>,
|
||||||
|
|
@ -50,6 +131,7 @@ impl ExportTask {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Changes the export configuration.
|
||||||
pub fn change_config(&self, config: ExportUserConfig) {
|
pub fn change_config(&self, config: ExportUserConfig) {
|
||||||
self.factory.mutate(|data| *data = config);
|
self.factory.mutate(|data| *data = config);
|
||||||
}
|
}
|
||||||
|
|
@ -162,6 +244,7 @@ impl ExportTask {
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Exports a document.
|
||||||
pub async fn do_export(
|
pub async fn do_export(
|
||||||
task: ProjectTask,
|
task: ProjectTask,
|
||||||
artifact: LspCompiledArtifact,
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,22 @@
|
||||||
//! [`SyncTaskFactory`] can hold *mutable* configuration but the mutations don't
|
//! [`SyncTaskFactory`] can hold *mutable* configuration but the mutations don't
|
||||||
//! blocking the computation, i.e. the mutations are non-blocking.
|
//! blocking the computation, i.e. the mutations are non-blocking.
|
||||||
|
|
||||||
|
#[cfg(feature = "export")]
|
||||||
mod export;
|
mod export;
|
||||||
|
#[cfg(feature = "export")]
|
||||||
pub use export::*;
|
pub use export::*;
|
||||||
|
#[cfg(feature = "export")]
|
||||||
pub mod export2;
|
pub mod export2;
|
||||||
mod format;
|
mod format;
|
||||||
pub use format::*;
|
pub use format::*;
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
mod user_action;
|
mod user_action;
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
pub use user_action::*;
|
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 reflexo::TakeAs;
|
||||||
use tinymist_std::error::prelude::*;
|
|
||||||
|
|
||||||
/// Please uses this if you believe all mutations are fast
|
/// Please uses this if you believe all mutations are fast
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
|
|
@ -40,72 +41,3 @@ impl<T: Clone> SyncTaskFactory<T> {
|
||||||
self.0.read().unwrap().clone()
|
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 ast;
|
||||||
pub mod package;
|
pub mod package;
|
||||||
pub mod project;
|
pub mod project;
|
||||||
pub mod testing;
|
|
||||||
pub mod word_count;
|
pub mod word_count;
|
||||||
|
|
||||||
#[cfg(feature = "preview")]
|
#[cfg(feature = "preview")]
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ mod http;
|
||||||
use std::{collections::HashMap, path::Path, sync::Arc};
|
use std::{collections::HashMap, path::Path, sync::Arc};
|
||||||
|
|
||||||
use clap::{Parser, ValueEnum};
|
use clap::{Parser, ValueEnum};
|
||||||
use futures::{SinkExt, StreamExt, TryStreamExt};
|
use futures::{SinkExt, TryStreamExt};
|
||||||
use hyper_tungstenite::{tungstenite::Message, HyperWebsocket, HyperWebsocketStream};
|
use hyper_tungstenite::{tungstenite::Message, HyperWebsocket, HyperWebsocketStream};
|
||||||
use lsp_types::notification::Notification;
|
use lsp_types::notification::Notification;
|
||||||
use lsp_types::Url;
|
use lsp_types::Url;
|
||||||
|
|
@ -27,8 +27,7 @@ use tinymist_std::error::IgnoreLogging;
|
||||||
use tokio::sync::{mpsc, oneshot};
|
use tokio::sync::{mpsc, oneshot};
|
||||||
|
|
||||||
use crate::actor::preview::{PreviewActor, PreviewRequest, PreviewTab};
|
use crate::actor::preview::{PreviewActor, PreviewRequest, PreviewTab};
|
||||||
use crate::project::{ProjectInsId, ProjectPreviewState, WorldProvider};
|
use crate::project::{ProjectInsId, ProjectPreviewState};
|
||||||
use crate::tool::project::{start_project, ProjectOpts, StartProjectResult};
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
/// The kind of the preview.
|
/// The kind of the preview.
|
||||||
|
|
@ -489,6 +488,7 @@ impl PreviewState {
|
||||||
is_primary,
|
is_primary,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "open")]
|
||||||
if open_in_browser {
|
if open_in_browser {
|
||||||
open::that_detached(format!("http://127.0.0.1:{}", addr.port()))
|
open::that_detached(format!("http://127.0.0.1:{}", addr.port()))
|
||||||
.log_error("failed to open browser for preview");
|
.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;
|
struct ScrollSource;
|
||||||
|
|
||||||
impl Notification for 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(
|
previewer.start_data_plane(
|
||||||
websocket_rx,
|
websocket_rx,
|
||||||
|conn: Result<HyperWebsocketStream, hyper_tungstenite::tungstenite::Error>| {
|
|conn: Result<HyperWebsocketStream, hyper_tungstenite::tungstenite::Error>| {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ pub struct ProjectPreviewHandler {
|
||||||
/// The project id.
|
/// The project id.
|
||||||
pub project_id: ProjectInsId,
|
pub project_id: ProjectInsId,
|
||||||
/// The connection to the compiler compiling projects (language server).
|
/// The connection to the compiler compiling projects (language server).
|
||||||
pub(crate) client: Box<dyn ProjectClient>,
|
pub client: Box<dyn ProjectClient>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProjectPreviewHandler {
|
impl ProjectPreviewHandler {
|
||||||
|
|
|
||||||
|
|
@ -1,424 +1,17 @@
|
||||||
//! Project management tools.
|
//! Project management tools.
|
||||||
|
|
||||||
use std::{
|
use std::sync::Arc;
|
||||||
borrow::Cow,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use clap_complete::Shell;
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use reflexo::{path::unix_slash, ImmutPath};
|
|
||||||
use reflexo_typst::WorldComputeGraph;
|
|
||||||
use tinymist_query::analysis::Analysis;
|
use tinymist_query::analysis::Analysis;
|
||||||
use tinymist_std::{bail, error::prelude::*};
|
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
use crate::{actor::editor::EditorRequest, world::system::print_diagnostics, Config};
|
use crate::project::*;
|
||||||
use crate::{project::*, task::ExportTask};
|
use crate::{actor::editor::EditorRequest, Config};
|
||||||
|
|
||||||
/// 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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// Options for starting a project.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct ProjectOpts {
|
pub struct ProjectOpts {
|
||||||
/// The tokio runtime handle.
|
/// The tokio runtime handle.
|
||||||
pub handle: Option<tokio::runtime::Handle>,
|
pub handle: Option<tokio::runtime::Handle>,
|
||||||
/// The shared preview state.
|
/// The shared preview state.
|
||||||
|
|
@ -426,21 +19,26 @@ pub(crate) struct ProjectOpts {
|
||||||
/// The shared config.
|
/// The shared config.
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
/// The shared preview state.
|
/// The shared preview state.
|
||||||
|
#[cfg(feature = "preview")]
|
||||||
pub preview: ProjectPreviewState,
|
pub preview: ProjectPreviewState,
|
||||||
/// The export target.
|
/// The export target.
|
||||||
pub export_target: ExportTarget,
|
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>,
|
pub service: WatchService<F>,
|
||||||
|
/// The interrupt sender.
|
||||||
pub intr_tx: mpsc::UnboundedSender<LspInterrupt>,
|
pub intr_tx: mpsc::UnboundedSender<LspInterrupt>,
|
||||||
|
/// The editor request receiver.
|
||||||
pub editor_rx: mpsc::UnboundedReceiver<EditorRequest>,
|
pub editor_rx: mpsc::UnboundedReceiver<EditorRequest>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: This is only extracted from the `tinymist preview` command, and we need
|
// todo: This is only extracted from the `tinymist preview` command, and we need
|
||||||
// to abstract it in future.
|
// to abstract it in future.
|
||||||
/// Start a project with the given universe.
|
/// Starts a project with the given universe.
|
||||||
pub(crate) fn start_project<F>(
|
pub fn start_project<F>(
|
||||||
verse: LspUniverse,
|
verse: LspUniverse,
|
||||||
opts: Option<ProjectOpts>,
|
opts: Option<ProjectOpts>,
|
||||||
intr_handler: F,
|
intr_handler: F,
|
||||||
|
|
@ -453,23 +51,37 @@ where
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
let opts = opts.unwrap_or_default();
|
let opts = opts.unwrap_or_default();
|
||||||
|
#[cfg(any(feature = "export", feature = "system"))]
|
||||||
let handle = opts.handle.unwrap_or_else(tokio::runtime::Handle::current);
|
let handle = opts.handle.unwrap_or_else(tokio::runtime::Handle::current);
|
||||||
|
|
||||||
|
let _ = opts.config;
|
||||||
|
|
||||||
// type EditorSender = mpsc::UnboundedSender<EditorRequest>;
|
// type EditorSender = mpsc::UnboundedSender<EditorRequest>;
|
||||||
let (editor_tx, editor_rx) = mpsc::unbounded_channel();
|
let (editor_tx, editor_rx) = mpsc::unbounded_channel();
|
||||||
let (intr_tx, intr_rx) = tokio::sync::mpsc::unbounded_channel();
|
let (intr_tx, intr_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||||
|
|
||||||
// todo: unify filesystem watcher
|
// 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();
|
let fs_intr_tx = intr_tx.clone();
|
||||||
handle.spawn(watch_deps(dep_rx, move |event| {
|
handle.spawn(watch_deps(dep_rx, move |event| {
|
||||||
fs_intr_tx.interrupt(LspInterrupt::Fs(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
|
// Create the actor
|
||||||
let compile_handle = Arc::new(CompileHandlerImpl {
|
let compile_handle = Arc::new(CompileHandlerImpl {
|
||||||
|
#[cfg(feature = "preview")]
|
||||||
preview: opts.preview,
|
preview: opts.preview,
|
||||||
is_standalone: true,
|
is_standalone: true,
|
||||||
|
#[cfg(feature = "export")]
|
||||||
export: crate::task::ExportTask::new(handle, Some(editor_tx.clone()), opts.config.export()),
|
export: crate::task::ExportTask::new(handle, Some(editor_tx.clone()), opts.config.export()),
|
||||||
editor_tx,
|
editor_tx,
|
||||||
client: Arc::new(intr_tx.clone()),
|
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,
|
pub compiler: LspProjectCompiler,
|
||||||
intr_rx: tokio::sync::mpsc::UnboundedReceiver<LspInterrupt>,
|
intr_rx: tokio::sync::mpsc::UnboundedReceiver<LspInterrupt>,
|
||||||
intr_handler: F,
|
intr_handler: F,
|
||||||
|
|
@ -517,6 +131,7 @@ where
|
||||||
) + Send
|
) + Send
|
||||||
+ 'static,
|
+ 'static,
|
||||||
{
|
{
|
||||||
|
/// Runs the project service.
|
||||||
pub async fn run(self) {
|
pub async fn run(self) {
|
||||||
let Self {
|
let Self {
|
||||||
mut compiler,
|
mut compiler,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,19 @@
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
|
||||||
|
#[cfg(feature = "system")]
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
#[cfg(feature = "system")]
|
||||||
use std::sync::atomic::AtomicU64;
|
use std::sync::atomic::AtomicU64;
|
||||||
|
#[cfg(feature = "system")]
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
#[cfg(feature = "system")]
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
#[cfg(feature = "system")]
|
||||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||||
|
#[cfg(feature = "system")]
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
|
#[cfg(feature = "system")]
|
||||||
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Derived<T>(pub T);
|
pub struct Derived<T>(pub T);
|
||||||
|
|
@ -52,7 +59,6 @@ macro_rules! get_arg_or_default {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
pub(crate) use 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> {
|
pub fn try_<T>(f: impl FnOnce() -> Option<T>) -> Option<T> {
|
||||||
f()
|
f()
|
||||||
|
|
@ -62,17 +68,11 @@ pub fn try_or<T>(f: impl FnOnce() -> Option<T>, default: T) -> T {
|
||||||
f().unwrap_or(default)
|
f().unwrap_or(default)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exit_on_ctrl_c() {
|
#[cfg(feature = "system")]
|
||||||
tokio::spawn(async move {
|
|
||||||
let _ = tokio::signal::ctrl_c().await;
|
|
||||||
log::info!("Ctrl-C received, exiting");
|
|
||||||
std::process::exit(0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct AliveLock(Arc<AtomicU64>);
|
pub(crate) struct AliveLock(Arc<AtomicU64>);
|
||||||
|
|
||||||
|
#[cfg(feature = "system")]
|
||||||
impl AliveLock {
|
impl AliveLock {
|
||||||
pub fn hold(cnt: Arc<AtomicU64>) -> Self {
|
pub fn hold(cnt: Arc<AtomicU64>) -> Self {
|
||||||
let held = cnt.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
let held = cnt.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||||
|
|
@ -81,6 +81,7 @@ impl AliveLock {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "system")]
|
||||||
impl Drop for AliveLock {
|
impl Drop for AliveLock {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let cnt = self.0.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
|
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 {
|
pub(crate) struct ConnWithCancel {
|
||||||
stream: TcpStream,
|
stream: TcpStream,
|
||||||
pub cancel: CancellationToken,
|
pub cancel: CancellationToken,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "system")]
|
||||||
impl ConnWithCancel {
|
impl ConnWithCancel {
|
||||||
pub fn new(stream: TcpStream) -> Self {
|
pub fn new(stream: TcpStream) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -102,12 +105,14 @@ impl ConnWithCancel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "system")]
|
||||||
impl Drop for ConnWithCancel {
|
impl Drop for ConnWithCancel {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.cancel.cancel()
|
self.cancel.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "system")]
|
||||||
impl AsyncRead for ConnWithCancel {
|
impl AsyncRead for ConnWithCancel {
|
||||||
fn poll_read(
|
fn poll_read(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
|
|
@ -118,6 +123,7 @@ impl AsyncRead for ConnWithCancel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "system")]
|
||||||
impl AsyncWrite for ConnWithCancel {
|
impl AsyncWrite for ConnWithCancel {
|
||||||
fn poll_write(
|
fn poll_write(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import tinymist_init from "../pkg/tinymist_core.js";
|
import tinymist_init from "../pkg/tinymist.js";
|
||||||
import * as tinymist from "../pkg/tinymist_core.js";
|
import * as tinymist from "../pkg/tinymist.js";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
|
||||||
const wasmData = fs.readFileSync("pkg/tinymist_core_bg.wasm");
|
const wasmData = fs.readFileSync("pkg/tinymist_bg.wasm");
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
await tinymist_init({
|
await tinymist_init({
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
!out/tinymist-docs.pdf
|
!out/tinymist-docs.pdf
|
||||||
!out/extension.js
|
!out/extension.js
|
||||||
!out/extension.web.js
|
!out/extension.web.js
|
||||||
!out/tinymist_core_bg.wasm
|
!out/tinymist_bg.wasm
|
||||||
!out/tinymist
|
!out/tinymist
|
||||||
!out/tinymist.exe
|
!out/tinymist.exe
|
||||||
!out/typst.tmLanguage.json
|
!out/typst.tmLanguage.json
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"crates/tinymist-core",
|
"crates/tinymist",
|
||||||
"editors/vscode",
|
"editors/vscode",
|
||||||
"contrib/typst-preview/editors/vscode",
|
"contrib/typst-preview/editors/vscode",
|
||||||
"contrib/html/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: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:l10n": "yarn extract:l10n && node scripts/build-l10n.mjs",
|
||||||
"build:docker": "docker build -t myriaddreamin/tinymist:0.13.22 .",
|
"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": "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: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",
|
"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,no-content-hint
|
||||||
cargo clippy -p typlite --no-default-features --features=cli,docx,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',
|
'typlite',
|
||||||
'typst-shim',
|
'typst-shim',
|
||||||
'sync-lsp',
|
'sync-lsp',
|
||||||
|
'tinymist-cli',
|
||||||
'tinymist-analysis',
|
'tinymist-analysis',
|
||||||
'tinymist-core',
|
|
||||||
'tinymist-debug',
|
'tinymist-debug',
|
||||||
'tinymist-l10n',
|
'tinymist-l10n',
|
||||||
'tinymist-package',
|
'tinymist-package',
|
||||||
|
|
@ -249,7 +249,7 @@ class NightlyUtils {
|
||||||
await this.ensureInit();
|
await this.ensureInit();
|
||||||
|
|
||||||
const nonWorldCrates = [
|
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'
|
'tinymist-lint', 'tinymist-query', 'tinymist-render', 'tinymist-preview', 'typlite'
|
||||||
];
|
];
|
||||||
await this.updateDependencies(nonWorldCrates, newVersion);
|
await this.updateDependencies(nonWorldCrates, newVersion);
|
||||||
|
|
@ -265,7 +265,7 @@ class NightlyUtils {
|
||||||
async updateVersionFiles(newVersion) {
|
async updateVersionFiles(newVersion) {
|
||||||
const jsonFiles = [
|
const jsonFiles = [
|
||||||
'contrib/html/editors/vscode/package.json',
|
'contrib/html/editors/vscode/package.json',
|
||||||
'crates/tinymist-core/package.json',
|
'crates/tinymist/package.json',
|
||||||
'editors/vscode/package.json',
|
'editors/vscode/package.json',
|
||||||
'syntaxes/textmate/package.json'
|
'syntaxes/textmate/package.json'
|
||||||
];
|
];
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue