mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-08-03 01:42:14 +00:00
refactor: pick state changes (#338)
* dev: make clippy happy * refactor: pick state changes --------- Co-authored-by: QuarticCat <QuarticCat@pm.me>
This commit is contained in:
parent
7d65829ed7
commit
5e4e1e9877
25 changed files with 754 additions and 707 deletions
41
Cargo.lock
generated
41
Cargo.lock
generated
|
@ -174,6 +174,26 @@ version = "0.7.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
|
||||
|
||||
[[package]]
|
||||
name = "async-lsp"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "855d6246a5d31e6e469eeef718d9a098f2d99207985a00dfdd3f4b5c5003c09a"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"lsp-types",
|
||||
"pin-project-lite",
|
||||
"rustix",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"waitpid-any",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.80"
|
||||
|
@ -3912,6 +3932,7 @@ name = "tinymist"
|
|||
version = "0.11.11"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-lsp",
|
||||
"async-trait",
|
||||
"await-tree",
|
||||
"base64 0.22.1",
|
||||
|
@ -3937,6 +3958,7 @@ dependencies = [
|
|||
"open",
|
||||
"parking_lot",
|
||||
"paste",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tinymist-assets 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -3945,6 +3967,8 @@ dependencies = [
|
|||
"tokio",
|
||||
"tokio-util",
|
||||
"toml 0.8.13",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"typst",
|
||||
"typst-assets",
|
||||
"typst-pdf",
|
||||
|
@ -4123,6 +4147,7 @@ checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
|
|||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
|
@ -4187,6 +4212,12 @@ dependencies = [
|
|||
"winnow 0.6.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.2"
|
||||
|
@ -4854,6 +4885,16 @@ version = "0.9.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "waitpid-any"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0189157c93c54d86e5c61ddf0c1223baa25e5bfb2f6f9983c678985b028d7c12"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
|
|
|
@ -58,6 +58,7 @@ typst-ts-svg-exporter = { version = "0.5.0-rc4" }
|
|||
typstyle = "0.11.26"
|
||||
typstfmt_lib = { git = "https://github.com/astrale-sharp/typstfmt", tag = "0.2.7" }
|
||||
|
||||
async-lsp = { version = "0.2.0", features = ["tokio"] }
|
||||
lsp-server = "0.7.6"
|
||||
lsp-types = { version = "=0.95.0", features = ["proposed"] }
|
||||
crossbeam-channel = "0.5.12"
|
||||
|
@ -79,7 +80,7 @@ tokio = { version = "1.36.0", features = [
|
|||
"rt-multi-thread",
|
||||
"io-std",
|
||||
] }
|
||||
tokio-util = "0.7.10"
|
||||
tokio-util = { version = "0.7.10", features = ["compat"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_yaml = "0.9"
|
||||
|
|
|
@ -13,68 +13,67 @@ pub mod syntax;
|
|||
pub mod ty;
|
||||
mod upstream;
|
||||
|
||||
pub(crate) mod diagnostics;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use analysis::AnalysisContext;
|
||||
use typst::{model::Document as TypstDocument, syntax::Source};
|
||||
|
||||
mod diagnostics;
|
||||
pub use diagnostics::*;
|
||||
pub(crate) mod code_action;
|
||||
mod code_action;
|
||||
pub use code_action::*;
|
||||
pub(crate) mod code_context;
|
||||
mod code_context;
|
||||
pub use code_context::*;
|
||||
pub(crate) mod code_lens;
|
||||
mod code_lens;
|
||||
pub use code_lens::*;
|
||||
pub(crate) mod completion;
|
||||
mod completion;
|
||||
pub use completion::*;
|
||||
pub(crate) mod color_presentation;
|
||||
mod color_presentation;
|
||||
pub use color_presentation::*;
|
||||
pub(crate) mod document_color;
|
||||
mod document_color;
|
||||
pub use document_color::*;
|
||||
pub(crate) mod document_highlight;
|
||||
mod document_highlight;
|
||||
pub use document_highlight::*;
|
||||
pub(crate) mod document_symbol;
|
||||
mod document_symbol;
|
||||
pub use document_symbol::*;
|
||||
pub(crate) mod document_metrics;
|
||||
mod document_metrics;
|
||||
pub use document_metrics::*;
|
||||
pub(crate) mod folding_range;
|
||||
mod folding_range;
|
||||
pub use folding_range::*;
|
||||
pub(crate) mod goto_declaration;
|
||||
mod goto_declaration;
|
||||
pub use goto_declaration::*;
|
||||
pub(crate) mod goto_definition;
|
||||
mod goto_definition;
|
||||
pub use goto_definition::*;
|
||||
pub(crate) mod hover;
|
||||
mod hover;
|
||||
pub use hover::*;
|
||||
pub(crate) mod inlay_hint;
|
||||
mod inlay_hint;
|
||||
pub use inlay_hint::*;
|
||||
pub(crate) mod jump;
|
||||
mod jump;
|
||||
pub use jump::*;
|
||||
pub(crate) mod rename;
|
||||
mod rename;
|
||||
pub use rename::*;
|
||||
pub(crate) mod selection_range;
|
||||
mod selection_range;
|
||||
pub use selection_range::*;
|
||||
pub(crate) mod semantic_tokens;
|
||||
mod semantic_tokens;
|
||||
pub use semantic_tokens::*;
|
||||
pub(crate) mod semantic_tokens_full;
|
||||
mod semantic_tokens_full;
|
||||
pub use semantic_tokens_full::*;
|
||||
pub(crate) mod semantic_tokens_delta;
|
||||
mod semantic_tokens_delta;
|
||||
pub use semantic_tokens_delta::*;
|
||||
pub(crate) mod signature_help;
|
||||
mod signature_help;
|
||||
pub use signature_help::*;
|
||||
pub(crate) mod symbol;
|
||||
mod symbol;
|
||||
pub use symbol::*;
|
||||
pub(crate) mod on_enter;
|
||||
mod on_enter;
|
||||
pub use on_enter::*;
|
||||
pub(crate) mod prepare_rename;
|
||||
mod prepare_rename;
|
||||
pub use prepare_rename::*;
|
||||
pub(crate) mod references;
|
||||
mod references;
|
||||
pub use references::*;
|
||||
|
||||
pub mod lsp_typst_boundary;
|
||||
mod lsp_typst_boundary;
|
||||
pub use lsp_typst_boundary::*;
|
||||
pub(crate) mod lsp_features;
|
||||
mod lsp_features;
|
||||
pub use lsp_features::*;
|
||||
|
||||
mod prelude;
|
||||
|
@ -136,9 +135,10 @@ mod polymorphic {
|
|||
use super::prelude::*;
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum PageSelection {
|
||||
#[default]
|
||||
First,
|
||||
Merged,
|
||||
}
|
||||
|
@ -234,68 +234,69 @@ mod polymorphic {
|
|||
pub fn fold_feature(&self) -> FoldRequestFeature {
|
||||
use FoldRequestFeature::*;
|
||||
match self {
|
||||
CompilerQueryRequest::OnExport(..) => Mergeable,
|
||||
CompilerQueryRequest::OnSaveExport(..) => Mergeable,
|
||||
CompilerQueryRequest::Hover(..) => PinnedFirst,
|
||||
CompilerQueryRequest::GotoDefinition(..) => PinnedFirst,
|
||||
CompilerQueryRequest::GotoDeclaration(..) => PinnedFirst,
|
||||
CompilerQueryRequest::References(..) => PinnedFirst,
|
||||
CompilerQueryRequest::InlayHint(..) => Unique,
|
||||
CompilerQueryRequest::DocumentColor(..) => PinnedFirst,
|
||||
CompilerQueryRequest::DocumentHighlight(..) => PinnedFirst,
|
||||
CompilerQueryRequest::ColorPresentation(..) => ContextFreeUnique,
|
||||
CompilerQueryRequest::CodeAction(..) => Unique,
|
||||
CompilerQueryRequest::CodeLens(..) => Unique,
|
||||
CompilerQueryRequest::Completion(..) => Mergeable,
|
||||
CompilerQueryRequest::SignatureHelp(..) => PinnedFirst,
|
||||
CompilerQueryRequest::Rename(..) => Mergeable,
|
||||
CompilerQueryRequest::PrepareRename(..) => Mergeable,
|
||||
CompilerQueryRequest::DocumentSymbol(..) => ContextFreeUnique,
|
||||
CompilerQueryRequest::Symbol(..) => Mergeable,
|
||||
CompilerQueryRequest::SemanticTokensFull(..) => ContextFreeUnique,
|
||||
CompilerQueryRequest::SemanticTokensDelta(..) => ContextFreeUnique,
|
||||
CompilerQueryRequest::Formatting(..) => ContextFreeUnique,
|
||||
CompilerQueryRequest::FoldingRange(..) => ContextFreeUnique,
|
||||
CompilerQueryRequest::SelectionRange(..) => ContextFreeUnique,
|
||||
CompilerQueryRequest::InteractCodeContext(..) => PinnedFirst,
|
||||
Self::OnExport(..) => Mergeable,
|
||||
Self::OnSaveExport(..) => Mergeable,
|
||||
Self::Hover(..) => PinnedFirst,
|
||||
Self::GotoDefinition(..) => PinnedFirst,
|
||||
Self::GotoDeclaration(..) => PinnedFirst,
|
||||
Self::References(..) => PinnedFirst,
|
||||
Self::InlayHint(..) => Unique,
|
||||
Self::DocumentColor(..) => PinnedFirst,
|
||||
Self::DocumentHighlight(..) => PinnedFirst,
|
||||
Self::ColorPresentation(..) => ContextFreeUnique,
|
||||
Self::CodeAction(..) => Unique,
|
||||
Self::CodeLens(..) => Unique,
|
||||
Self::Completion(..) => Mergeable,
|
||||
Self::SignatureHelp(..) => PinnedFirst,
|
||||
Self::Rename(..) => Mergeable,
|
||||
Self::PrepareRename(..) => Mergeable,
|
||||
Self::DocumentSymbol(..) => ContextFreeUnique,
|
||||
Self::Symbol(..) => Mergeable,
|
||||
Self::SemanticTokensFull(..) => ContextFreeUnique,
|
||||
Self::SemanticTokensDelta(..) => ContextFreeUnique,
|
||||
Self::Formatting(..) => ContextFreeUnique,
|
||||
Self::FoldingRange(..) => ContextFreeUnique,
|
||||
Self::SelectionRange(..) => ContextFreeUnique,
|
||||
Self::InteractCodeContext(..) => PinnedFirst,
|
||||
|
||||
CompilerQueryRequest::OnEnter(..) => ContextFreeUnique,
|
||||
Self::OnEnter(..) => ContextFreeUnique,
|
||||
|
||||
CompilerQueryRequest::DocumentMetrics(..) => PinnedFirst,
|
||||
CompilerQueryRequest::ServerInfo(..) => Mergeable,
|
||||
Self::DocumentMetrics(..) => PinnedFirst,
|
||||
Self::ServerInfo(..) => Mergeable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn associated_path(&self) -> Option<&Path> {
|
||||
Some(match self {
|
||||
CompilerQueryRequest::OnExport(..) => return None,
|
||||
CompilerQueryRequest::OnSaveExport(req) => &req.path,
|
||||
CompilerQueryRequest::Hover(req) => &req.path,
|
||||
CompilerQueryRequest::GotoDefinition(req) => &req.path,
|
||||
CompilerQueryRequest::GotoDeclaration(req) => &req.path,
|
||||
CompilerQueryRequest::References(req) => &req.path,
|
||||
CompilerQueryRequest::InlayHint(req) => &req.path,
|
||||
CompilerQueryRequest::DocumentColor(req) => &req.path,
|
||||
CompilerQueryRequest::DocumentHighlight(req) => &req.path,
|
||||
CompilerQueryRequest::ColorPresentation(req) => &req.path,
|
||||
CompilerQueryRequest::CodeAction(req) => &req.path,
|
||||
CompilerQueryRequest::CodeLens(req) => &req.path,
|
||||
CompilerQueryRequest::Completion(req) => &req.path,
|
||||
CompilerQueryRequest::SignatureHelp(req) => &req.path,
|
||||
CompilerQueryRequest::Rename(req) => &req.path,
|
||||
CompilerQueryRequest::PrepareRename(req) => &req.path,
|
||||
CompilerQueryRequest::DocumentSymbol(req) => &req.path,
|
||||
CompilerQueryRequest::Symbol(..) => return None,
|
||||
CompilerQueryRequest::SemanticTokensFull(req) => &req.path,
|
||||
CompilerQueryRequest::SemanticTokensDelta(req) => &req.path,
|
||||
CompilerQueryRequest::Formatting(req) => &req.path,
|
||||
CompilerQueryRequest::FoldingRange(req) => &req.path,
|
||||
CompilerQueryRequest::SelectionRange(req) => &req.path,
|
||||
CompilerQueryRequest::InteractCodeContext(req) => &req.path,
|
||||
CompilerQueryRequest::OnEnter(req) => &req.path,
|
||||
Self::OnExport(..) => return None,
|
||||
Self::OnSaveExport(req) => &req.path,
|
||||
Self::Hover(req) => &req.path,
|
||||
Self::GotoDefinition(req) => &req.path,
|
||||
Self::GotoDeclaration(req) => &req.path,
|
||||
Self::References(req) => &req.path,
|
||||
Self::InlayHint(req) => &req.path,
|
||||
Self::DocumentColor(req) => &req.path,
|
||||
Self::DocumentHighlight(req) => &req.path,
|
||||
Self::ColorPresentation(req) => &req.path,
|
||||
Self::CodeAction(req) => &req.path,
|
||||
Self::CodeLens(req) => &req.path,
|
||||
Self::Completion(req) => &req.path,
|
||||
Self::SignatureHelp(req) => &req.path,
|
||||
Self::Rename(req) => &req.path,
|
||||
Self::PrepareRename(req) => &req.path,
|
||||
Self::DocumentSymbol(req) => &req.path,
|
||||
Self::Symbol(..) => return None,
|
||||
Self::SemanticTokensFull(req) => &req.path,
|
||||
Self::SemanticTokensDelta(req) => &req.path,
|
||||
Self::Formatting(req) => &req.path,
|
||||
Self::FoldingRange(req) => &req.path,
|
||||
Self::SelectionRange(req) => &req.path,
|
||||
Self::InteractCodeContext(req) => &req.path,
|
||||
|
||||
CompilerQueryRequest::DocumentMetrics(req) => &req.path,
|
||||
CompilerQueryRequest::ServerInfo(..) => return None,
|
||||
Self::OnEnter(req) => &req.path,
|
||||
|
||||
Self::DocumentMetrics(req) => &req.path,
|
||||
Self::ServerInfo(..) => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ toml.workspace = true
|
|||
walkdir.workspace = true
|
||||
typst-preview = { workspace = true, optional = true }
|
||||
lsp-server.workspace = true
|
||||
async-lsp.workspace = true
|
||||
crossbeam-channel.workspace = true
|
||||
lsp-types.workspace = true
|
||||
dhat = { version = "0.3.3", optional = true }
|
||||
|
@ -66,6 +67,9 @@ unicode-script = "0.5"
|
|||
await-tree = "0.1.2"
|
||||
hyper = { version = "0.14", features = ["full"], optional = true }
|
||||
open = { version = "5.1.3", optional = true }
|
||||
tower-layer = "0.3.2"
|
||||
tower-service = "0.3.2"
|
||||
pin-project-lite = "0.2.13"
|
||||
base64.workspace = true
|
||||
|
||||
[features]
|
||||
|
|
|
@ -7,7 +7,7 @@ use lsp_types::{Diagnostic, Url};
|
|||
use tinymist_query::{DiagnosticsMap, LspDiagnostic};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::{tools::word_count::WordsCount, LspHost, TypstLanguageServer};
|
||||
use crate::{tools::word_count::WordsCount, LspHost, LanguageState};
|
||||
|
||||
pub enum EditorRequest {
|
||||
Diag(String, Option<DiagnosticsMap>),
|
||||
|
@ -16,7 +16,7 @@ pub enum EditorRequest {
|
|||
}
|
||||
|
||||
pub struct EditorActor {
|
||||
host: LspHost<TypstLanguageServer>,
|
||||
host: LspHost<LanguageState>,
|
||||
editor_rx: mpsc::UnboundedReceiver<EditorRequest>,
|
||||
|
||||
diagnostics: HashMap<Url, HashMap<String, Vec<LspDiagnostic>>>,
|
||||
|
@ -27,7 +27,7 @@ pub struct EditorActor {
|
|||
|
||||
impl EditorActor {
|
||||
pub fn new(
|
||||
host: LspHost<TypstLanguageServer>,
|
||||
host: LspHost<LanguageState>,
|
||||
editor_rx: mpsc::UnboundedReceiver<EditorRequest>,
|
||||
notify_compile_status: bool,
|
||||
) -> Self {
|
||||
|
|
|
@ -7,7 +7,7 @@ use lsp_types::TextEdit;
|
|||
use tinymist_query::{typst_to_lsp, PositionEncoding};
|
||||
use typst::syntax::Source;
|
||||
|
||||
use crate::{result_to_response, FormatterMode, LspHost, LspResult, TypstLanguageServer};
|
||||
use crate::{result_to_response, FormatterMode, LspHost, LspResult, LanguageState};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FormatConfig {
|
||||
|
@ -23,7 +23,7 @@ pub enum FormatRequest {
|
|||
pub fn run_format_thread(
|
||||
config: FormatConfig,
|
||||
format_rx: crossbeam_channel::Receiver<FormatRequest>,
|
||||
client: LspHost<TypstLanguageServer>,
|
||||
client: LspHost<LanguageState>,
|
||||
position_encoding: PositionEncoding,
|
||||
) {
|
||||
type FmtFn = Box<dyn Fn(Source) -> LspResult<Option<Vec<TextEdit>>>>;
|
||||
|
|
|
@ -24,12 +24,12 @@ use self::{
|
|||
user_action::run_user_action_thread,
|
||||
};
|
||||
use crate::{
|
||||
compiler::CompileServer,
|
||||
compile::CompileState,
|
||||
world::{ImmutDict, LspWorldBuilder},
|
||||
TypstLanguageServer,
|
||||
LanguageState,
|
||||
};
|
||||
|
||||
impl CompileServer {
|
||||
impl CompileState {
|
||||
pub fn restart_server(&mut self, group: &str) {
|
||||
let server = self.server(
|
||||
group.to_owned(),
|
||||
|
@ -132,7 +132,7 @@ impl CompileServer {
|
|||
}
|
||||
}
|
||||
|
||||
impl TypstLanguageServer {
|
||||
impl LanguageState {
|
||||
pub fn server(
|
||||
&self,
|
||||
diag_group: String,
|
||||
|
|
|
@ -65,7 +65,7 @@ use super::{
|
|||
};
|
||||
use crate::{
|
||||
actor::export::ExportRequest,
|
||||
compiler_init::CompileConfig,
|
||||
compile_init::CompileConfig,
|
||||
tools::preview::{CompilationHandle, CompileStatus},
|
||||
utils,
|
||||
world::{LspCompilerFeat, LspWorld},
|
||||
|
|
|
@ -75,7 +75,7 @@ pub struct CompileServerActor<C: Compiler, F: CompilerFeat> {
|
|||
estimated_shadow_files: HashSet<Arc<Path>>,
|
||||
/// The latest compiled document.
|
||||
pub(crate) latest_doc: Option<Arc<TypstDocument>>,
|
||||
/// The latest successly compiled document.
|
||||
/// The latest successfully compiled document.
|
||||
latest_success_doc: Option<Arc<TypstDocument>>,
|
||||
/// feature set for compile_once mode.
|
||||
once_feature_set: Arc<FeatureSet>,
|
||||
|
|
|
@ -8,7 +8,7 @@ use lsp_server::RequestId;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use typst_ts_core::TypstDict;
|
||||
|
||||
use crate::{internal_error, result_to_response, LspHost, TypstLanguageServer};
|
||||
use crate::{internal_error, result_to_response, LspHost, LanguageState};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
@ -26,7 +26,7 @@ pub enum UserActionRequest {
|
|||
|
||||
pub fn run_user_action_thread(
|
||||
user_action_rx: crossbeam_channel::Receiver<UserActionRequest>,
|
||||
client: LspHost<TypstLanguageServer>,
|
||||
client: LspHost<LanguageState>,
|
||||
) {
|
||||
while let Ok(req) = user_action_rx.recv() {
|
||||
match req {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use once_cell::sync::Lazy;
|
||||
use tinymist::preview::PreviewCliArgs;
|
||||
use tinymist::transport::MirrorArgs;
|
||||
|
||||
use tinymist::compiler_init::{CompileOnceArgs, FontArgs};
|
||||
use tinymist::preview::PreviewCliArgs;
|
||||
use tinymist::compile_init::{CompileOnceArgs, FontArgs};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "clap", derive(clap::Parser))]
|
||||
|
|
|
@ -37,8 +37,8 @@ pub mod transport;
|
|||
mod utils;
|
||||
mod world;
|
||||
pub use crate::harness::LspHost;
|
||||
pub use server::compiler;
|
||||
pub use server::compiler_init;
|
||||
pub use server::compile;
|
||||
pub use server::compile_init;
|
||||
pub use server::lsp::*;
|
||||
pub use server::lsp_init::*;
|
||||
pub use server::preview;
|
||||
|
@ -46,6 +46,7 @@ pub use world::{
|
|||
CompileFontOpts, CompileOnceOpts, CompileOpts, LspUniverse, LspWorld, LspWorldBuilder,
|
||||
};
|
||||
|
||||
// use async_lsp::ClientSocket;
|
||||
use lsp_server::ResponseError;
|
||||
|
||||
type LspResult<Res> = Result<Res, ResponseError>;
|
||||
|
|
|
@ -11,11 +11,11 @@ use lsp_types::{InitializeParams, InitializedParams};
|
|||
use once_cell::sync::Lazy;
|
||||
use parking_lot::RwLock;
|
||||
use tinymist::{
|
||||
compiler_init::{CompileInit, CompileInitializeParams},
|
||||
compile_init::{CompileInit, CompileInitializeParams},
|
||||
harness::{lsp_harness, InitializedLspDriver, LspDriver, LspHost},
|
||||
preview::preview_main,
|
||||
transport::with_stdio_transport,
|
||||
CompileFontOpts, Init, LspWorld, TypstLanguageServer,
|
||||
CompileFontOpts, Init, LspWorld, LanguageState,
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
use typst::World;
|
||||
|
@ -90,7 +90,7 @@ pub fn lsp_main(args: LspArgs) -> anyhow::Result<()> {
|
|||
impl LspDriver for Lsp {
|
||||
type InitParams = InitializeParams;
|
||||
type InitResult = lsp_types::InitializeResult;
|
||||
type InitializedSelf = TypstLanguageServer;
|
||||
type InitializedSelf = LanguageState;
|
||||
|
||||
fn initialize(
|
||||
self,
|
||||
|
|
|
@ -14,7 +14,7 @@ mod prelude {
|
|||
pub use typst_ts_svg_exporter::ir::{GlyphItem, GlyphRef};
|
||||
pub use typst_ts_svg_exporter::{DefaultExportFeature, SvgTask, SvgText};
|
||||
|
||||
pub use crate::TypstLanguageServer;
|
||||
pub use crate::LanguageState;
|
||||
|
||||
pub type Svg<'a> = SvgTask<'a, DefaultExportFeature>;
|
||||
}
|
||||
|
|
|
@ -156,7 +156,7 @@ static CAT_MAP: Lazy<HashMap<&str, SymCategory>> = Lazy::new(|| {
|
|||
])
|
||||
});
|
||||
|
||||
impl TypstLanguageServer {
|
||||
impl LanguageState {
|
||||
/// Get the all valid symbols
|
||||
pub fn get_symbol_resources(&self) -> ZResult<JsonValue> {
|
||||
let mut symbols = ResourceSymbolMap::new();
|
||||
|
|
|
@ -4,40 +4,30 @@ use std::{collections::HashMap, path::Path, sync::Arc, time::Instant};
|
|||
use crossbeam_channel::{select, Receiver};
|
||||
use log::{error, info, warn};
|
||||
use lsp_server::{ErrorCode, Message, Notification, Request, RequestId, Response, ResponseError};
|
||||
use lsp_types::{notification::Notification as _, ExecuteCommandParams};
|
||||
use lsp_types::notification::Notification as _;
|
||||
use once_cell::sync::OnceCell;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value as JsonValue};
|
||||
use tinymist_query::{ExportKind, PageSelection};
|
||||
use tokio::sync::mpsc;
|
||||
use typst::{diag::FileResult, syntax::Source};
|
||||
use typst_ts_compiler::vfs::notify::FileChangeSet;
|
||||
use typst_ts_core::{config::compiler::DETACHED_ENTRY, ImmutPath};
|
||||
use typst_ts_core::config::compiler::DETACHED_ENTRY;
|
||||
|
||||
use crate::{
|
||||
actor::{editor::EditorRequest, export::ExportConfig, typ_client::CompileClientActor},
|
||||
compiler_init::{CompileConfig, CompilerConstConfig},
|
||||
compile_init::{CompileConfig, ConstCompileConfig},
|
||||
harness::InitializedLspDriver,
|
||||
internal_error, invalid_params, method_not_found, run_query,
|
||||
invalid_params,
|
||||
state::MemoryFileMeta,
|
||||
LspHost, LspResult,
|
||||
};
|
||||
|
||||
type LspMethod<Res> = fn(srv: &mut CompileServer, args: JsonValue) -> LspResult<Res>;
|
||||
type LspHandler<Req, Res> = fn(srv: &mut CompileServer, args: Req) -> LspResult<Res>;
|
||||
type LspMethod<Res> = fn(srv: &mut CompileState, args: JsonValue) -> LspResult<Res>;
|
||||
pub(crate) type LspHandler<Req, Res> = fn(srv: &mut CompileState, args: Req) -> LspResult<Res>;
|
||||
|
||||
type ExecuteCmdMap = HashMap<&'static str, LspHandler<Vec<JsonValue>, JsonValue>>;
|
||||
type NotifyCmdMap = HashMap<&'static str, LspMethod<()>>;
|
||||
type RegularCmdMap = HashMap<&'static str, LspMethod<JsonValue>>;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! exec_fn {
|
||||
($ty: ty, Self::$method: ident, $($arg_key:ident),+ $(,)?) => {{
|
||||
const E: $ty = |this, $($arg_key),+| this.$method($($arg_key),+);
|
||||
E
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! request_fn {
|
||||
($desc: ty, Self::$method: ident) => {
|
||||
|
@ -67,9 +57,9 @@ macro_rules! notify_fn {
|
|||
}
|
||||
|
||||
/// The object providing the language server functionality.
|
||||
pub struct CompileServer {
|
||||
pub struct CompileState {
|
||||
/// The language server client.
|
||||
pub client: LspHost<CompileServer>,
|
||||
pub client: LspHost<CompileState>,
|
||||
|
||||
// State to synchronize with the client.
|
||||
/// Whether the server is shutting down.
|
||||
|
@ -80,7 +70,7 @@ pub struct CompileServer {
|
|||
pub config: CompileConfig,
|
||||
/// Const configuration initialized at the start of the session.
|
||||
/// For example, the position encoding.
|
||||
pub const_config: CompilerConstConfig,
|
||||
pub const_config: ConstCompileConfig,
|
||||
|
||||
// Command maps
|
||||
/// Extra commands provided with `textDocument/executeCommand`.
|
||||
|
@ -101,15 +91,15 @@ pub struct CompileServer {
|
|||
pub compiler: Option<CompileClientActor>,
|
||||
}
|
||||
|
||||
impl CompileServer {
|
||||
impl CompileState {
|
||||
pub fn new(
|
||||
client: LspHost<CompileServer>,
|
||||
client: LspHost<CompileState>,
|
||||
compile_config: CompileConfig,
|
||||
const_config: CompilerConstConfig,
|
||||
const_config: ConstCompileConfig,
|
||||
editor_tx: mpsc::UnboundedSender<EditorRequest>,
|
||||
handle: tokio::runtime::Handle,
|
||||
) -> Self {
|
||||
CompileServer {
|
||||
CompileState {
|
||||
client,
|
||||
editor_tx,
|
||||
shutdown_requested: false,
|
||||
|
@ -125,7 +115,7 @@ impl CompileServer {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn const_config(&self) -> &CompilerConstConfig {
|
||||
pub fn const_config(&self) -> &ConstCompileConfig {
|
||||
&self.const_config
|
||||
}
|
||||
|
||||
|
@ -203,7 +193,7 @@ impl fmt::Display for Event {
|
|||
}
|
||||
}
|
||||
|
||||
impl InitializedLspDriver for CompileServer {
|
||||
impl InitializedLspDriver for CompileState {
|
||||
fn initialized(&mut self, _params: lsp_types::InitializedParams) {}
|
||||
|
||||
fn main_loop(&mut self, inbox: crossbeam_channel::Receiver<Message>) -> anyhow::Result<()> {
|
||||
|
@ -223,7 +213,7 @@ impl InitializedLspDriver for CompileServer {
|
|||
}
|
||||
}
|
||||
|
||||
impl CompileServer {
|
||||
impl CompileState {
|
||||
fn next_event(&self, inbox: &Receiver<Message>) -> Option<Event> {
|
||||
select! {
|
||||
recv(inbox) -> msg =>
|
||||
|
@ -314,7 +304,7 @@ impl CompileServer {
|
|||
}
|
||||
}
|
||||
|
||||
impl CompileServer {
|
||||
impl CompileState {
|
||||
pub fn on_changed_configuration(&mut self, values: Map<String, JsonValue>) -> LspResult<()> {
|
||||
let config = self.config.clone();
|
||||
match self.config.update_by_map(&values) {
|
||||
|
@ -357,121 +347,3 @@ impl CompileServer {
|
|||
}
|
||||
|
||||
struct Cancelled;
|
||||
|
||||
impl CompileServer {
|
||||
fn get_exec_commands() -> ExecuteCmdMap {
|
||||
macro_rules! redirected_command {
|
||||
($key: expr, Self::$method: ident) => {
|
||||
(
|
||||
$key,
|
||||
exec_fn!(LspHandler<Vec<JsonValue>, JsonValue>, Self::$method, inputs),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
ExecuteCmdMap::from_iter([
|
||||
redirected_command!("tinymist.exportPdf", Self::export_pdf),
|
||||
redirected_command!("tinymist.exportSvg", Self::export_svg),
|
||||
redirected_command!("tinymist.exportPng", Self::export_png),
|
||||
redirected_command!("tinymist.doClearCache", Self::clear_cache),
|
||||
redirected_command!("tinymist.changeEntry", Self::change_entry),
|
||||
])
|
||||
}
|
||||
|
||||
/// The entry point for the `workspace/executeCommand` request.
|
||||
fn execute_command(&mut self, params: ExecuteCommandParams) -> LspResult<JsonValue> {
|
||||
let ExecuteCommandParams {
|
||||
command,
|
||||
arguments,
|
||||
work_done_progress_params: _,
|
||||
} = params;
|
||||
let Some(handler) = self.exec_cmds.get(command.as_str()) else {
|
||||
error!("asked to execute unknown command");
|
||||
return Err(method_not_found());
|
||||
};
|
||||
handler(self, arguments)
|
||||
}
|
||||
|
||||
/// Export the current document as a PDF file.
|
||||
pub fn export_pdf(&self, arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
self.export(ExportKind::Pdf, arguments)
|
||||
}
|
||||
|
||||
/// Export the current document as a Svg file.
|
||||
pub fn export_svg(&self, arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let opts = parse_opts(arguments.get(1))?;
|
||||
self.export(ExportKind::Svg { page: opts.page }, arguments)
|
||||
}
|
||||
|
||||
/// Export the current document as a Png file.
|
||||
pub fn export_png(&self, arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let opts = parse_opts(arguments.get(1))?;
|
||||
self.export(ExportKind::Png { page: opts.page }, arguments)
|
||||
}
|
||||
|
||||
/// Export the current document as some format. The client is responsible
|
||||
/// for passing the correct absolute path of typst document.
|
||||
pub fn export(&self, kind: ExportKind, arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let path = parse_path(arguments.first())?.as_ref().to_owned();
|
||||
|
||||
let res = run_query!(self.OnExport(path, kind))?;
|
||||
let res = serde_json::to_value(res).map_err(|_| internal_error("Cannot serialize path"))?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Clear all cached resources.
|
||||
///
|
||||
/// # Errors
|
||||
/// Errors if the cache could not be cleared.
|
||||
pub fn clear_cache(&self, _arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
comemo::evict(0);
|
||||
Ok(JsonValue::Null)
|
||||
}
|
||||
|
||||
/// Focus main file to some path.
|
||||
pub fn change_entry(&mut self, arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let new_entry = parse_path_or_null(arguments.first())?;
|
||||
|
||||
let update_result = self.do_change_entry(new_entry.clone());
|
||||
update_result.map_err(|err| internal_error(format!("could not focus file: {err}")))?;
|
||||
|
||||
info!("entry changed: {entry:?}", entry = new_entry);
|
||||
Ok(JsonValue::Null)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct ExportOpts {
|
||||
page: PageSelection,
|
||||
}
|
||||
|
||||
fn parse_opts(v: Option<&JsonValue>) -> LspResult<ExportOpts> {
|
||||
Ok(match v {
|
||||
Some(opts) => serde_json::from_value::<ExportOpts>(opts.clone())
|
||||
.map_err(|_| invalid_params("The third argument is not a valid object"))?,
|
||||
_ => ExportOpts {
|
||||
page: PageSelection::First,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_path(v: Option<&JsonValue>) -> LspResult<ImmutPath> {
|
||||
let new_entry = match v {
|
||||
Some(JsonValue::String(s)) => Path::new(s).into(),
|
||||
_ => {
|
||||
return Err(invalid_params(
|
||||
"The first parameter is not a valid path or null",
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(new_entry)
|
||||
}
|
||||
|
||||
fn parse_path_or_null(v: Option<&JsonValue>) -> LspResult<Option<ImmutPath>> {
|
||||
match v {
|
||||
Some(JsonValue::Null) => Ok(None),
|
||||
v => Ok(Some(parse_path(v)?)),
|
||||
}
|
||||
}
|
110
crates/tinymist/src/server/compile_cmd.rs
Normal file
110
crates/tinymist/src/server/compile_cmd.rs
Normal file
|
@ -0,0 +1,110 @@
|
|||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use log::{error, info};
|
||||
use lsp_types::ExecuteCommandParams;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
use tinymist_query::{ExportKind, PageSelection};
|
||||
|
||||
use crate::{internal_error, invalid_params, method_not_found, run_query, LspResult};
|
||||
|
||||
use super::compile::*;
|
||||
use super::*;
|
||||
|
||||
macro_rules! exec_fn {
|
||||
($ty: ty, Self::$method: ident, $($arg_key:ident),+ $(,)?) => {{
|
||||
const E: $ty = |this, $($arg_key),+| this.$method($($arg_key),+);
|
||||
E
|
||||
}};
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
struct ExportOpts {
|
||||
page: PageSelection,
|
||||
}
|
||||
|
||||
type ExecuteCmdMap = HashMap<&'static str, LspHandler<Vec<JsonValue>, JsonValue>>;
|
||||
|
||||
impl CompileState {
|
||||
pub fn get_exec_commands() -> ExecuteCmdMap {
|
||||
macro_rules! redirected_command {
|
||||
($key: expr, Self::$method: ident) => {
|
||||
(
|
||||
$key,
|
||||
exec_fn!(LspHandler<Vec<JsonValue>, JsonValue>, Self::$method, inputs),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
ExecuteCmdMap::from_iter([
|
||||
redirected_command!("tinymist.exportPdf", Self::export_pdf),
|
||||
redirected_command!("tinymist.exportSvg", Self::export_svg),
|
||||
redirected_command!("tinymist.exportPng", Self::export_png),
|
||||
redirected_command!("tinymist.doClearCache", Self::clear_cache),
|
||||
redirected_command!("tinymist.changeEntry", Self::change_entry),
|
||||
])
|
||||
}
|
||||
|
||||
/// The entry point for the `workspace/executeCommand` request.
|
||||
pub fn execute_command(&mut self, params: ExecuteCommandParams) -> LspResult<JsonValue> {
|
||||
let ExecuteCommandParams {
|
||||
command,
|
||||
arguments: args,
|
||||
work_done_progress_params: _,
|
||||
} = params;
|
||||
let Some(handler) = self.exec_cmds.get(command.as_str()) else {
|
||||
error!("asked to execute unknown command");
|
||||
return Err(method_not_found());
|
||||
};
|
||||
handler(self, args)
|
||||
}
|
||||
|
||||
/// Export the current document as a PDF file.
|
||||
pub fn export_pdf(&self, args: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
self.export(ExportKind::Pdf, args)
|
||||
}
|
||||
|
||||
/// Export the current document as a Svg file.
|
||||
pub fn export_svg(&self, mut args: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let opts = get_arg_or_default!(args[1] as ExportOpts);
|
||||
self.export(ExportKind::Svg { page: opts.page }, args)
|
||||
}
|
||||
|
||||
/// Export the current document as a Png file.
|
||||
pub fn export_png(&self, mut args: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let opts = get_arg_or_default!(args[1] as ExportOpts);
|
||||
self.export(ExportKind::Png { page: opts.page }, args)
|
||||
}
|
||||
|
||||
/// Export the current document as some format. The client is responsible
|
||||
/// for passing the correct absolute path of typst document.
|
||||
pub fn export(&self, kind: ExportKind, mut args: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let path = get_arg!(args[0] as PathBuf);
|
||||
|
||||
let res = run_query!(self.OnExport(path, kind))?;
|
||||
let res = serde_json::to_value(res).map_err(|_| internal_error("Cannot serialize path"))?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Clear all cached resources.
|
||||
///
|
||||
/// # Errors
|
||||
/// Errors if the cache could not be cleared.
|
||||
pub fn clear_cache(&self, _arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
comemo::evict(0);
|
||||
self.compiler().clear_cache();
|
||||
Ok(JsonValue::Null)
|
||||
}
|
||||
|
||||
/// Focus main file to some path.
|
||||
pub fn change_entry(&mut self, mut args: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let entry = get_arg!(args[0] as Option<PathBuf>).map(From::from);
|
||||
|
||||
let update_result = self.do_change_entry(entry.clone());
|
||||
update_result.map_err(|err| internal_error(format!("could not focus file: {err}")))?;
|
||||
|
||||
info!("entry changed: {entry:?}");
|
||||
Ok(JsonValue::Null)
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ use typst_ts_core::config::compiler::EntryState;
|
|||
use typst_ts_core::{ImmutPath, TypstDict};
|
||||
|
||||
use crate::actor::editor::EditorRequest;
|
||||
use crate::compiler::CompileServer;
|
||||
use crate::compile::CompileState;
|
||||
use crate::harness::LspDriver;
|
||||
use crate::utils::{try_, try_or_default};
|
||||
use crate::world::{ImmutDict, SharedFontResolver};
|
||||
|
@ -372,21 +372,13 @@ impl CompileConfig {
|
|||
|
||||
/// Configuration set at initialization that won't change within a single
|
||||
/// session.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CompilerConstConfig {
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct ConstCompileConfig {
|
||||
/// Determined position encoding, either UTF-8 or UTF-16.
|
||||
/// Defaults to UTF-16 if not specified.
|
||||
pub position_encoding: PositionEncoding,
|
||||
}
|
||||
|
||||
impl Default for CompilerConstConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
position_encoding: PositionEncoding::Utf16,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CompileInit {
|
||||
pub handle: tokio::runtime::Handle,
|
||||
pub font: CompileFontOpts,
|
||||
|
@ -402,7 +394,7 @@ pub struct CompileInitializeParams {
|
|||
impl LspDriver for CompileInit {
|
||||
type InitParams = CompileInitializeParams;
|
||||
type InitResult = ();
|
||||
type InitializedSelf = CompileServer;
|
||||
type InitializedSelf = CompileState;
|
||||
|
||||
fn initialize(
|
||||
self,
|
||||
|
@ -418,10 +410,10 @@ impl LspDriver for CompileInit {
|
|||
};
|
||||
compile_config.update(¶ms.config).unwrap();
|
||||
|
||||
let mut service = CompileServer::new(
|
||||
let mut service = CompileState::new(
|
||||
client,
|
||||
compile_config,
|
||||
CompilerConstConfig {
|
||||
ConstCompileConfig {
|
||||
position_encoding: params
|
||||
.position_encoding
|
||||
.map(|x| match x.as_str() {
|
|
@ -1,7 +1,5 @@
|
|||
//! tinymist LSP mode
|
||||
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
@ -18,48 +16,37 @@ use serde::{Deserialize, Serialize};
|
|||
use serde_json::{Map, Value as JsonValue};
|
||||
use tinymist_query::{
|
||||
get_semantic_tokens_options, get_semantic_tokens_registration,
|
||||
get_semantic_tokens_unregistration, ExportKind, PageSelection, SemanticTokenContext,
|
||||
get_semantic_tokens_unregistration, PageSelection, SemanticTokenContext,
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
use typst::diag::StrResult;
|
||||
use typst::syntax::package::{PackageSpec, VersionlessPackageSpec};
|
||||
use typst_ts_core::path::PathClean;
|
||||
use typst_ts_core::{error::prelude::*, ImmutPath};
|
||||
use typst_ts_core::ImmutPath;
|
||||
|
||||
use super::lsp_init::*;
|
||||
use crate::actor::editor::EditorRequest;
|
||||
use crate::actor::format::{FormatConfig, FormatRequest};
|
||||
use crate::actor::typ_client::CompileClientActor;
|
||||
use crate::actor::user_action::{TraceParams, UserActionRequest};
|
||||
use crate::compiler::CompileServer;
|
||||
use crate::compiler_init::CompilerConstConfig;
|
||||
use crate::actor::user_action::UserActionRequest;
|
||||
use crate::compile::CompileState;
|
||||
use crate::compile_init::ConstCompileConfig;
|
||||
use crate::harness::{InitializedLspDriver, LspHost};
|
||||
use crate::tools::package::InitTask;
|
||||
use crate::{run_query, LspResult};
|
||||
|
||||
pub type MaySyncResult<'a> = Result<JsonValue, BoxFuture<'a, JsonValue>>;
|
||||
|
||||
type LspMethod<Res> = fn(srv: &mut TypstLanguageServer, args: JsonValue) -> LspResult<Res>;
|
||||
type LspHandler<Req, Res> = fn(srv: &mut TypstLanguageServer, args: Req) -> LspResult<Res>;
|
||||
type LspMethod<Res> = fn(srv: &mut LanguageState, args: JsonValue) -> LspResult<Res>;
|
||||
type LspHandler<Req, Res> = fn(srv: &mut LanguageState, args: Req) -> LspResult<Res>;
|
||||
|
||||
/// Returns Ok(Some()) -> Already responded
|
||||
/// Returns Ok(None) -> Need to respond none
|
||||
/// Returns Err(..) -> Need to respond error
|
||||
type LspRawHandler<T> =
|
||||
fn(srv: &mut TypstLanguageServer, req_id: RequestId, args: T) -> LspResult<Option<()>>;
|
||||
fn(srv: &mut LanguageState, req_id: RequestId, args: T) -> LspResult<Option<()>>;
|
||||
|
||||
type ExecuteCmdMap = HashMap<&'static str, LspRawHandler<Vec<JsonValue>>>;
|
||||
type NotifyCmdMap = HashMap<&'static str, LspMethod<()>>;
|
||||
type RegularCmdMap = HashMap<&'static str, LspRawHandler<JsonValue>>;
|
||||
type ResourceMap = HashMap<ImmutPath, LspHandler<Vec<JsonValue>, JsonValue>>;
|
||||
|
||||
macro_rules! resource_fn {
|
||||
($ty: ty, Self::$method: ident, $($arg_key:ident),+ $(,)?) => {{
|
||||
const E: $ty = |this, $($arg_key),+| this.$method($($arg_key),+);
|
||||
E
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! request_fn_ {
|
||||
($desc: ty, Self::$method: ident) => {
|
||||
(<$desc>::METHOD, {
|
||||
|
@ -90,28 +77,6 @@ macro_rules! request_fn {
|
|||
};
|
||||
}
|
||||
|
||||
macro_rules! exec_fn_ {
|
||||
($key: expr, Self::$method: ident) => {
|
||||
($key, {
|
||||
const E: LspRawHandler<Vec<JsonValue>> = |this, req_id, req| this.$method(req_id, req);
|
||||
E
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! exec_fn {
|
||||
($key: expr, Self::$method: ident) => {
|
||||
($key, {
|
||||
const E: LspRawHandler<Vec<JsonValue>> = |this, req_id, args| {
|
||||
let res = this.$method(args);
|
||||
this.client.respond(result_to_response(req_id, res));
|
||||
Ok(Some(()))
|
||||
};
|
||||
E
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! notify_fn {
|
||||
($desc: ty, Self::$method: ident) => {
|
||||
(<$desc>::METHOD, {
|
||||
|
@ -125,11 +90,11 @@ macro_rules! notify_fn {
|
|||
};
|
||||
}
|
||||
|
||||
fn as_path(inp: TextDocumentIdentifier) -> PathBuf {
|
||||
pub(super) fn as_path(inp: TextDocumentIdentifier) -> PathBuf {
|
||||
as_path_(inp.uri)
|
||||
}
|
||||
|
||||
fn as_path_(uri: Url) -> PathBuf {
|
||||
pub(super) fn as_path_(uri: Url) -> PathBuf {
|
||||
tinymist_query::url_to_path(uri)
|
||||
}
|
||||
|
||||
|
@ -138,9 +103,9 @@ fn as_path_pos(inp: TextDocumentPositionParams) -> (PathBuf, Position) {
|
|||
}
|
||||
|
||||
/// The object providing the language server functionality.
|
||||
pub struct TypstLanguageServer {
|
||||
pub struct LanguageState {
|
||||
/// The language server client.
|
||||
pub client: LspHost<TypstLanguageServer>,
|
||||
pub client: LspHost<LanguageState>,
|
||||
|
||||
// State to synchronize with the client.
|
||||
/// Whether the server is shutting down.
|
||||
|
@ -173,15 +138,15 @@ pub struct TypstLanguageServer {
|
|||
/// Regular commands for dispatching.
|
||||
pub regular_cmds: RegularCmdMap,
|
||||
/// Regular commands for dispatching.
|
||||
pub resources_routes: ResourceMap,
|
||||
pub resource_routes: ResourceMap,
|
||||
|
||||
// Resources
|
||||
/// The semantic token context.
|
||||
pub tokens_ctx: SemanticTokenContext,
|
||||
/// The compiler for general purpose.
|
||||
pub primary: CompileServer,
|
||||
pub primary: CompileState,
|
||||
/// The compilers for tasks
|
||||
pub dedicates: Vec<CompileServer>,
|
||||
pub dedicates: Vec<CompileState>,
|
||||
/// The formatter thread running in backend.
|
||||
/// Note: The thread will exit if you drop the sender.
|
||||
pub format_thread: Option<crossbeam_channel::Sender<FormatRequest>>,
|
||||
|
@ -191,10 +156,10 @@ pub struct TypstLanguageServer {
|
|||
}
|
||||
|
||||
/// Getters and the main loop.
|
||||
impl TypstLanguageServer {
|
||||
impl LanguageState {
|
||||
/// Create a new language server.
|
||||
pub fn new(
|
||||
client: LspHost<TypstLanguageServer>,
|
||||
client: LspHost<LanguageState>,
|
||||
const_config: ConstConfig,
|
||||
editor_tx: mpsc::UnboundedSender<EditorRequest>,
|
||||
handle: tokio::runtime::Handle,
|
||||
|
@ -206,10 +171,10 @@ impl TypstLanguageServer {
|
|||
);
|
||||
Self {
|
||||
client,
|
||||
primary: CompileServer::new(
|
||||
primary: CompileState::new(
|
||||
LspHost::new(Arc::new(RwLock::new(None))),
|
||||
Default::default(),
|
||||
CompilerConstConfig {
|
||||
ConstCompileConfig {
|
||||
position_encoding: const_config.position_encoding,
|
||||
},
|
||||
editor_tx,
|
||||
|
@ -227,7 +192,7 @@ impl TypstLanguageServer {
|
|||
exec_cmds: Self::get_exec_commands(),
|
||||
regular_cmds: Self::get_regular_cmds(),
|
||||
notify_cmds: Self::get_notify_cmds(),
|
||||
resources_routes: Self::get_resources_routes(),
|
||||
resource_routes: Self::get_resource_routes(),
|
||||
|
||||
pinning: false,
|
||||
focusing: None,
|
||||
|
@ -294,7 +259,7 @@ impl TypstLanguageServer {
|
|||
}
|
||||
}
|
||||
|
||||
impl InitializedLspDriver for TypstLanguageServer {
|
||||
impl InitializedLspDriver for LanguageState {
|
||||
/// The [`initialized`] notification is sent from the client to the server
|
||||
/// after the client received the result of the initialize request but
|
||||
/// before the client sends anything else.
|
||||
|
@ -383,7 +348,7 @@ impl InitializedLspDriver for TypstLanguageServer {
|
|||
}
|
||||
}
|
||||
|
||||
impl TypstLanguageServer {
|
||||
impl LanguageState {
|
||||
/// Registers and handles a request. This should only be called once per
|
||||
/// incoming request.
|
||||
fn on_request(&mut self, request_received: Instant, req: Request) {
|
||||
|
@ -533,7 +498,7 @@ impl TypstLanguageServer {
|
|||
/// low-level implementation details.
|
||||
///
|
||||
/// [Language Server Protocol]: https://microsoft.github.io/language-server-protocol/
|
||||
impl TypstLanguageServer {
|
||||
impl LanguageState {
|
||||
/// The [`shutdown`] request asks the server to gracefully shut down, but to
|
||||
/// not exit.
|
||||
///
|
||||
|
@ -553,346 +518,8 @@ impl TypstLanguageServer {
|
|||
}
|
||||
}
|
||||
|
||||
/// Here are implemented the handlers for each command.
|
||||
impl TypstLanguageServer {
|
||||
fn get_exec_commands() -> ExecuteCmdMap {
|
||||
ExecuteCmdMap::from_iter([
|
||||
exec_fn!("tinymist.exportPdf", Self::export_pdf),
|
||||
exec_fn!("tinymist.exportSvg", Self::export_svg),
|
||||
exec_fn!("tinymist.exportPng", Self::export_png),
|
||||
exec_fn!("tinymist.doClearCache", Self::clear_cache),
|
||||
exec_fn!("tinymist.pinMain", Self::pin_document),
|
||||
exec_fn!("tinymist.focusMain", Self::focus_document),
|
||||
exec_fn!("tinymist.doInitTemplate", Self::init_template),
|
||||
exec_fn!("tinymist.doGetTemplateEntry", Self::do_get_template_entry),
|
||||
exec_fn!("tinymist.interactCodeContext", Self::interact_code_context),
|
||||
exec_fn_!("tinymist.getDocumentTrace", Self::get_document_trace),
|
||||
exec_fn!("tinymist.getDocumentMetrics", Self::get_document_metrics),
|
||||
exec_fn!("tinymist.getServerInfo", Self::get_server_info),
|
||||
// For Documentations
|
||||
exec_fn!("tinymist.getResources", Self::get_resources),
|
||||
])
|
||||
}
|
||||
|
||||
/// Export the current document as a PDF file.
|
||||
pub fn export_pdf(&mut self, arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
self.export(ExportKind::Pdf, arguments)
|
||||
}
|
||||
|
||||
/// Export the current document as a Svg file.
|
||||
pub fn export_svg(&mut self, arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let opts = parse_opts(arguments.get(1))?;
|
||||
self.export(ExportKind::Svg { page: opts.page }, arguments)
|
||||
}
|
||||
|
||||
/// Export the current document as a Png file.
|
||||
pub fn export_png(&mut self, arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let opts = parse_opts(arguments.get(1))?;
|
||||
self.export(ExportKind::Png { page: opts.page }, arguments)
|
||||
}
|
||||
|
||||
/// Export the current document as some format. The client is responsible
|
||||
/// for passing the correct absolute path of typst document.
|
||||
pub fn export(&mut self, kind: ExportKind, arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let path = parse_path(arguments.first())?.as_ref().to_owned();
|
||||
|
||||
let res = run_query!(self.OnExport(path, kind))?;
|
||||
let res = serde_json::to_value(res).map_err(|_| internal_error("Cannot serialize path"))?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Interact with the code context at the source file.
|
||||
pub fn interact_code_context(&mut self, _arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let queries = _arguments.into_iter().next().ok_or_else(|| {
|
||||
invalid_params("The first parameter is not a valid code context query array")
|
||||
})?;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InteractCodeContextParams {
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
pub query: Vec<tinymist_query::InteractCodeContextQuery>,
|
||||
}
|
||||
|
||||
let params: InteractCodeContextParams = serde_json::from_value(queries)
|
||||
.map_err(|e| invalid_params(format!("Cannot parse code context queries: {e}")))?;
|
||||
let path = as_path(params.text_document);
|
||||
let query = params.query;
|
||||
|
||||
let res = run_query!(self.InteractCodeContext(path, query))?;
|
||||
let res =
|
||||
serde_json::to_value(res).map_err(|_| internal_error("Cannot serialize responses"))?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Get the trace data of the document.
|
||||
pub fn get_document_trace(
|
||||
&mut self,
|
||||
req_id: RequestId,
|
||||
arguments: Vec<JsonValue>,
|
||||
) -> LspResult<Option<()>> {
|
||||
let path = parse_path(arguments.first())?;
|
||||
|
||||
// get path to self program
|
||||
let self_path = std::env::current_exe()
|
||||
.map_err(|e| internal_error(format!("Cannot get typst compiler {e}")))?;
|
||||
|
||||
let thread = self.user_action_thread.clone();
|
||||
let entry = self.config.compile.determine_entry(Some(path));
|
||||
|
||||
let res = self
|
||||
.primary()
|
||||
.steal(move |c| {
|
||||
let verse = &c.verse;
|
||||
|
||||
// todo: rootless file
|
||||
// todo: memory dirty file
|
||||
let root = entry.root().ok_or_else(|| {
|
||||
anyhow::anyhow!("root must be determined for trace, got {entry:?}")
|
||||
})?;
|
||||
let main = entry
|
||||
.main()
|
||||
.and_then(|e| e.vpath().resolve(&root))
|
||||
.ok_or_else(|| anyhow::anyhow!("main file must be resolved, got {entry:?}"))?;
|
||||
|
||||
if let Some(f) = thread {
|
||||
f.send(UserActionRequest::Trace(
|
||||
req_id,
|
||||
TraceParams {
|
||||
compiler_program: self_path,
|
||||
root: root.as_ref().to_owned(),
|
||||
main,
|
||||
inputs: verse.inputs().as_ref().deref().clone(),
|
||||
font_paths: verse.font_resolver.font_paths().to_owned(),
|
||||
},
|
||||
))
|
||||
.context("cannot send trace request")?;
|
||||
} else {
|
||||
bail!("user action thread is not available");
|
||||
}
|
||||
|
||||
Ok(Some(()))
|
||||
})
|
||||
.context("cannot steal primary compiler");
|
||||
|
||||
let res = match res {
|
||||
Ok(res) => res,
|
||||
Err(res) => Err(res),
|
||||
};
|
||||
|
||||
res.map_err(|e| internal_error(format!("could not get document trace: {e}")))
|
||||
}
|
||||
|
||||
/// Get the metrics of the document.
|
||||
pub fn get_document_metrics(&mut self, arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let path = parse_path(arguments.first())?.as_ref().to_owned();
|
||||
|
||||
let res = run_query!(self.DocumentMetrics(path))?;
|
||||
let res = serde_json::to_value(res)
|
||||
.map_err(|e| internal_error(format!("Cannot serialize response {e}")))?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Get the server info.
|
||||
pub fn get_server_info(&mut self, _arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let res = run_query!(self.ServerInfo())?;
|
||||
|
||||
let res = serde_json::to_value(res)
|
||||
.map_err(|e| internal_error(format!("Cannot serialize response {e}")))?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Clear all cached resources.
|
||||
///
|
||||
/// # Errors
|
||||
/// Errors if the cache could not be cleared.
|
||||
pub fn clear_cache(&self, _arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
comemo::evict(0);
|
||||
for v in Some(self.primary())
|
||||
.into_iter()
|
||||
.chain(self.dedicates.iter().map(|v| v.compiler()))
|
||||
{
|
||||
v.clear_cache();
|
||||
}
|
||||
Ok(JsonValue::Null)
|
||||
}
|
||||
|
||||
/// Pin main file to some path.
|
||||
pub fn pin_document(&mut self, arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let new_entry = parse_path_or_null(arguments.first())?;
|
||||
|
||||
let update_result = self.pin_entry(new_entry.clone());
|
||||
update_result.map_err(|err| internal_error(format!("could not pin file: {err}")))?;
|
||||
|
||||
info!("file pinned: {entry:?}", entry = new_entry);
|
||||
Ok(JsonValue::Null)
|
||||
}
|
||||
|
||||
/// Focus main file to some path.
|
||||
pub fn focus_document(&mut self, arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let new_entry = parse_path_or_null(arguments.first())?;
|
||||
|
||||
if !self.ever_manual_focusing {
|
||||
self.ever_manual_focusing = true;
|
||||
log::info!("first manual focusing is coming");
|
||||
}
|
||||
|
||||
let ok = self.focus_entry(new_entry.clone());
|
||||
let ok = ok.map_err(|err| internal_error(format!("could not focus file: {err}")))?;
|
||||
|
||||
if ok {
|
||||
info!("file focused: {new_entry:?}");
|
||||
}
|
||||
Ok(JsonValue::Null)
|
||||
}
|
||||
|
||||
/// Initialize a new template.
|
||||
pub fn init_template(&self, arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
use crate::tools::package::{self, determine_latest_version, TemplateSource};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct InitResult {
|
||||
entry_path: PathBuf,
|
||||
}
|
||||
|
||||
let from_source = arguments
|
||||
.first()
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_owned())
|
||||
.ok_or_else(|| invalid_params("The first parameter is not a valid source or null"))?;
|
||||
let to_path = parse_path_or_null(arguments.get(1))?;
|
||||
let res = self
|
||||
.primary()
|
||||
.steal(move |c| {
|
||||
let world = c.verse.spawn();
|
||||
// Parse the package specification. If the user didn't specify the version,
|
||||
// we try to figure it out automatically by downloading the package index
|
||||
// or searching the disk.
|
||||
let spec: PackageSpec = from_source
|
||||
.parse()
|
||||
.or_else(|err| {
|
||||
// Try to parse without version, but prefer the error message of the
|
||||
// normal package spec parsing if it fails.
|
||||
let spec: VersionlessPackageSpec = from_source.parse().map_err(|_| err)?;
|
||||
let version = determine_latest_version(&c.verse, &spec)?;
|
||||
StrResult::Ok(spec.at(version))
|
||||
})
|
||||
.map_err(map_string_err("failed to parse package spec"))?;
|
||||
|
||||
let from_source = TemplateSource::Package(spec);
|
||||
|
||||
let entry_path = package::init(
|
||||
&world,
|
||||
InitTask {
|
||||
tmpl: from_source.clone(),
|
||||
dir: to_path.clone(),
|
||||
},
|
||||
)
|
||||
.map_err(map_string_err("failed to initialize template"))?;
|
||||
|
||||
info!("template initialized: {from_source:?} to {to_path:?}");
|
||||
|
||||
ZResult::Ok(InitResult { entry_path })
|
||||
})
|
||||
.and_then(|e| e)
|
||||
.map_err(|e| invalid_params(format!("failed to determine template source: {e}")))?;
|
||||
|
||||
serde_json::to_value(res).map_err(|_| internal_error("Cannot serialize path"))
|
||||
}
|
||||
|
||||
/// Get the entry of a template.
|
||||
pub fn do_get_template_entry(&self, arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
use crate::tools::package::{self, determine_latest_version, TemplateSource};
|
||||
|
||||
let from_source = arguments
|
||||
.first()
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_owned())
|
||||
.ok_or_else(|| invalid_params("The first parameter is not a valid source or null"))?;
|
||||
|
||||
let entry = self
|
||||
.primary()
|
||||
.steal(move |c| {
|
||||
// Parse the package specification. If the user didn't specify the version,
|
||||
// we try to figure it out automatically by downloading the package index
|
||||
// or searching the disk.
|
||||
let spec: PackageSpec = from_source
|
||||
.parse()
|
||||
.or_else(|err| {
|
||||
// Try to parse without version, but prefer the error message of the
|
||||
// normal package spec parsing if it fails.
|
||||
let spec: VersionlessPackageSpec = from_source.parse().map_err(|_| err)?;
|
||||
let version = determine_latest_version(&c.verse, &spec)?;
|
||||
StrResult::Ok(spec.at(version))
|
||||
})
|
||||
.map_err(map_string_err("failed to parse package spec"))?;
|
||||
|
||||
let from_source = TemplateSource::Package(spec);
|
||||
|
||||
let entry = package::get_entry(&c.verse, from_source)
|
||||
.map_err(map_string_err("failed to get template entry"))?;
|
||||
|
||||
ZResult::Ok(entry)
|
||||
})
|
||||
.and_then(|e| e)
|
||||
.map_err(|e| invalid_params(format!("failed to determine template entry: {e}")))?;
|
||||
|
||||
let entry = String::from_utf8(entry.to_vec())
|
||||
.map_err(|_| invalid_params("template entry is not a valid UTF-8 string"))?;
|
||||
|
||||
Ok(JsonValue::String(entry))
|
||||
}
|
||||
}
|
||||
|
||||
impl TypstLanguageServer {
|
||||
fn get_resources_routes() -> ResourceMap {
|
||||
macro_rules! resources_at {
|
||||
($key: expr, Self::$method: ident) => {
|
||||
(
|
||||
Path::new($key).clean().as_path().into(),
|
||||
resource_fn!(LspHandler<Vec<JsonValue>, JsonValue>, Self::$method, inputs),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
ResourceMap::from_iter([
|
||||
resources_at!("/symbols", Self::resources_alt_symbols),
|
||||
resources_at!("/tutorial", Self::resource_tutoral),
|
||||
])
|
||||
}
|
||||
|
||||
/// Get static resources with help of tinymist service, for example, a
|
||||
/// static help pages for some typst function.
|
||||
pub fn get_resources(&mut self, arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let u = parse_path(arguments.first())?;
|
||||
|
||||
let Some(handler) = self.resources_routes.get(u.as_ref()) else {
|
||||
error!("asked for unknown resource: {u:?}");
|
||||
return Err(method_not_found());
|
||||
};
|
||||
|
||||
// Note our redirection will keep the first path argument in the arguments vec.
|
||||
handler(self, arguments)
|
||||
}
|
||||
/// Get the all valid symbols
|
||||
pub fn resources_alt_symbols(&self, _arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let resp = self.get_symbol_resources();
|
||||
resp.map_err(|e| internal_error(e.to_string()))
|
||||
}
|
||||
|
||||
/// Get tutorial web page
|
||||
pub fn resource_tutoral(&self, _arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
Err(method_not_found())
|
||||
}
|
||||
}
|
||||
|
||||
/// Document Synchronization
|
||||
impl TypstLanguageServer {
|
||||
impl LanguageState {
|
||||
fn did_open(&mut self, params: DidOpenTextDocumentParams) -> LspResult<()> {
|
||||
log::info!("did open {:?}", params.text_document.uri);
|
||||
let path = as_path_(params.text_document.uri);
|
||||
|
@ -1005,7 +632,7 @@ impl TypstLanguageServer {
|
|||
}
|
||||
|
||||
/// Standard Language Features
|
||||
impl TypstLanguageServer {
|
||||
impl LanguageState {
|
||||
fn goto_definition(
|
||||
&mut self,
|
||||
params: GotoDefinitionParams,
|
||||
|
@ -1195,32 +822,6 @@ struct ExportOpts {
|
|||
page: PageSelection,
|
||||
}
|
||||
|
||||
fn parse_opts(v: Option<&JsonValue>) -> LspResult<ExportOpts> {
|
||||
Ok(match v {
|
||||
Some(opts) => serde_json::from_value::<ExportOpts>(opts.clone())
|
||||
.map_err(|_| invalid_params("The third argument is not a valid object"))?,
|
||||
_ => ExportOpts {
|
||||
page: PageSelection::First,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_path(v: Option<&JsonValue>) -> LspResult<ImmutPath> {
|
||||
let new_entry = match v {
|
||||
Some(JsonValue::String(s)) => Path::new(s).clean().as_path().into(),
|
||||
_ => return Err(invalid_params("The first parameter is not a valid path")),
|
||||
};
|
||||
|
||||
Ok(new_entry)
|
||||
}
|
||||
|
||||
fn parse_path_or_null(v: Option<&JsonValue>) -> LspResult<Option<ImmutPath>> {
|
||||
match v {
|
||||
Some(JsonValue::Null) => Ok(None),
|
||||
v => Ok(Some(parse_path(v)?)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invalid_params(msg: impl Into<String>) -> ResponseError {
|
||||
ResponseError {
|
||||
code: ErrorCode::InvalidParams as i32,
|
||||
|
@ -1270,6 +871,9 @@ impl lsp_types::request::Request for OnEnter {
|
|||
|
||||
#[test]
|
||||
fn test_as_path() {
|
||||
use std::path::Path;
|
||||
use typst_ts_core::path::PathClean;
|
||||
|
||||
let uri = Url::parse("untitled:/path/to/file").unwrap();
|
||||
assert_eq!(as_path_(uri), Path::new("/untitled/path/to/file").clean());
|
||||
|
||||
|
|
387
crates/tinymist/src/server/lsp_cmd.rs
Normal file
387
crates/tinymist/src/server/lsp_cmd.rs
Normal file
|
@ -0,0 +1,387 @@
|
|||
//! tinymist LSP mode
|
||||
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use anyhow::{bail, Context};
|
||||
use log::{error, info};
|
||||
use lsp_server::RequestId;
|
||||
use lsp_types::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
use tinymist_query::ExportKind;
|
||||
use typst::diag::StrResult;
|
||||
use typst::syntax::package::{PackageSpec, VersionlessPackageSpec};
|
||||
use typst_ts_core::path::PathClean;
|
||||
use typst_ts_core::{error::prelude::*, ImmutPath};
|
||||
|
||||
use crate::actor::user_action::{TraceParams, UserActionRequest};
|
||||
use crate::tools::package::InitTask;
|
||||
use crate::{run_query, LspResult};
|
||||
|
||||
use super::lsp::*;
|
||||
use super::*;
|
||||
|
||||
macro_rules! exec_fn_ {
|
||||
($key: expr, Self::$method: ident) => {
|
||||
($key, {
|
||||
const E: LspRawHandler<Vec<JsonValue>> = |this, req_id, req| this.$method(req_id, req);
|
||||
E
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! exec_fn {
|
||||
($key: expr, Self::$method: ident) => {
|
||||
($key, {
|
||||
const E: LspRawHandler<Vec<JsonValue>> = |this, req_id, args| {
|
||||
let res = this.$method(args);
|
||||
this.client.respond(result_to_response(req_id, res));
|
||||
Ok(Some(()))
|
||||
};
|
||||
E
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! resource_fn {
|
||||
($ty: ty, Self::$method: ident, $($arg_key:ident),+ $(,)?) => {{
|
||||
const E: $ty = |this, $($arg_key),+| this.$method($($arg_key),+);
|
||||
E
|
||||
}};
|
||||
}
|
||||
|
||||
type LspHandler<Req, Res> = fn(srv: &mut LanguageState, args: Req) -> LspResult<Res>;
|
||||
|
||||
/// Returns Ok(Some()) -> Already responded
|
||||
/// Returns Ok(None) -> Need to respond none
|
||||
/// Returns Err(..) -> Need to respond error
|
||||
type LspRawHandler<T> =
|
||||
fn(srv: &mut LanguageState, req_id: RequestId, args: T) -> LspResult<Option<()>>;
|
||||
|
||||
type ExecuteCmdMap = HashMap<&'static str, LspRawHandler<Vec<JsonValue>>>;
|
||||
type ResourceMap = HashMap<ImmutPath, LspHandler<Vec<JsonValue>, JsonValue>>;
|
||||
|
||||
/// Here are implemented the handlers for each command.
|
||||
impl LanguageState {
|
||||
pub fn get_exec_commands() -> ExecuteCmdMap {
|
||||
ExecuteCmdMap::from_iter([
|
||||
exec_fn!("tinymist.exportPdf", Self::export_pdf),
|
||||
exec_fn!("tinymist.exportSvg", Self::export_svg),
|
||||
exec_fn!("tinymist.exportPng", Self::export_png),
|
||||
exec_fn!("tinymist.doClearCache", Self::clear_cache),
|
||||
exec_fn!("tinymist.pinMain", Self::pin_document),
|
||||
exec_fn!("tinymist.focusMain", Self::focus_document),
|
||||
exec_fn!("tinymist.doInitTemplate", Self::init_template),
|
||||
exec_fn!("tinymist.doGetTemplateEntry", Self::do_get_template_entry),
|
||||
exec_fn!("tinymist.interactCodeContext", Self::interact_code_context),
|
||||
exec_fn_!("tinymist.getDocumentTrace", Self::get_document_trace),
|
||||
exec_fn!("tinymist.getDocumentMetrics", Self::get_document_metrics),
|
||||
exec_fn!("tinymist.getServerInfo", Self::get_server_info),
|
||||
// For Documentations
|
||||
exec_fn!("tinymist.getResources", Self::get_resources),
|
||||
])
|
||||
}
|
||||
|
||||
/// Export the current document as a PDF file.
|
||||
pub fn export_pdf(&mut self, args: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
self.primary.export_pdf(args)
|
||||
}
|
||||
|
||||
/// Export the current document as a Svg file.
|
||||
pub fn export_svg(&mut self, args: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
self.primary.export_svg(args)
|
||||
}
|
||||
|
||||
/// Export the current document as a Png file.
|
||||
pub fn export_png(&mut self, args: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
self.primary.export_png(args)
|
||||
}
|
||||
|
||||
/// Export the current document as some format. The client is responsible
|
||||
/// for passing the correct absolute path of typst document.
|
||||
pub fn export(&mut self, kind: ExportKind, args: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
self.primary.export(kind, args)
|
||||
}
|
||||
|
||||
/// Clear all cached resources.
|
||||
///
|
||||
/// # Errors
|
||||
/// Errors if the cache could not be cleared.
|
||||
pub fn clear_cache(&self, _arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
comemo::evict(0);
|
||||
for v in Some(self.primary())
|
||||
.into_iter()
|
||||
.chain(self.dedicates.iter().map(|v| v.compiler()))
|
||||
{
|
||||
v.clear_cache();
|
||||
}
|
||||
Ok(JsonValue::Null)
|
||||
}
|
||||
|
||||
/// Pin main file to some path.
|
||||
pub fn pin_document(&mut self, mut args: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let entry = get_arg!(args[0] as Option<PathBuf>).map(From::from);
|
||||
|
||||
let update_result = self.pin_entry(entry.clone());
|
||||
update_result.map_err(|err| internal_error(format!("could not pin file: {err}")))?;
|
||||
|
||||
info!("file pinned: {entry:?}");
|
||||
Ok(JsonValue::Null)
|
||||
}
|
||||
|
||||
/// Focus main file to some path.
|
||||
pub fn focus_document(&mut self, mut args: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let entry = get_arg!(args[0] as Option<PathBuf>).map(From::from);
|
||||
|
||||
if !self.ever_manual_focusing {
|
||||
self.ever_manual_focusing = true;
|
||||
log::info!("first manual focusing is coming");
|
||||
}
|
||||
|
||||
let ok = self.focus_entry(entry.clone());
|
||||
let ok = ok.map_err(|err| internal_error(format!("could not focus file: {err}")))?;
|
||||
|
||||
if ok {
|
||||
info!("file focused: {entry:?}");
|
||||
}
|
||||
Ok(JsonValue::Null)
|
||||
}
|
||||
|
||||
/// Initialize a new template.
|
||||
pub fn init_template(&self, mut args: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
use crate::tools::package::{self, determine_latest_version, TemplateSource};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct InitResult {
|
||||
entry_path: PathBuf,
|
||||
}
|
||||
|
||||
let from_source = get_arg!(args[0] as String);
|
||||
let to_path = get_arg!(args[1] as Option<PathBuf>).map(From::from);
|
||||
let res = self
|
||||
.primary()
|
||||
.steal(move |c| {
|
||||
let world = c.verse.spawn();
|
||||
// Parse the package specification. If the user didn't specify the version,
|
||||
// we try to figure it out automatically by downloading the package index
|
||||
// or searching the disk.
|
||||
let spec: PackageSpec = from_source
|
||||
.parse()
|
||||
.or_else(|err| {
|
||||
// Try to parse without version, but prefer the error message of the
|
||||
// normal package spec parsing if it fails.
|
||||
let spec: VersionlessPackageSpec = from_source.parse().map_err(|_| err)?;
|
||||
let version = determine_latest_version(&c.verse, &spec)?;
|
||||
StrResult::Ok(spec.at(version))
|
||||
})
|
||||
.map_err(map_string_err("failed to parse package spec"))?;
|
||||
|
||||
let from_source = TemplateSource::Package(spec);
|
||||
|
||||
let entry_path = package::init(
|
||||
&world,
|
||||
InitTask {
|
||||
tmpl: from_source.clone(),
|
||||
dir: to_path.clone(),
|
||||
},
|
||||
)
|
||||
.map_err(map_string_err("failed to initialize template"))?;
|
||||
|
||||
info!("template initialized: {from_source:?} to {to_path:?}");
|
||||
|
||||
ZResult::Ok(InitResult { entry_path })
|
||||
})
|
||||
.and_then(|e| e)
|
||||
.map_err(|e| invalid_params(format!("failed to determine template source: {e}")))?;
|
||||
|
||||
serde_json::to_value(res).map_err(|_| internal_error("Cannot serialize path"))
|
||||
}
|
||||
|
||||
/// Get the entry of a template.
|
||||
pub fn do_get_template_entry(&self, mut args: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
use crate::tools::package::{self, determine_latest_version, TemplateSource};
|
||||
|
||||
let from_source = get_arg!(args[0] as String);
|
||||
let entry = self
|
||||
.primary()
|
||||
.steal(move |c| {
|
||||
// Parse the package specification. If the user didn't specify the version,
|
||||
// we try to figure it out automatically by downloading the package index
|
||||
// or searching the disk.
|
||||
let spec: PackageSpec = from_source
|
||||
.parse()
|
||||
.or_else(|err| {
|
||||
// Try to parse without version, but prefer the error message of the
|
||||
// normal package spec parsing if it fails.
|
||||
let spec: VersionlessPackageSpec = from_source.parse().map_err(|_| err)?;
|
||||
let version = determine_latest_version(&c.verse, &spec)?;
|
||||
StrResult::Ok(spec.at(version))
|
||||
})
|
||||
.map_err(map_string_err("failed to parse package spec"))?;
|
||||
|
||||
let from_source = TemplateSource::Package(spec);
|
||||
|
||||
let entry = package::get_entry(&c.verse, from_source)
|
||||
.map_err(map_string_err("failed to get template entry"))?;
|
||||
|
||||
ZResult::Ok(entry)
|
||||
})
|
||||
.and_then(|e| e)
|
||||
.map_err(|e| invalid_params(format!("failed to determine template entry: {e}")))?;
|
||||
|
||||
let entry = String::from_utf8(entry.to_vec())
|
||||
.map_err(|_| invalid_params("template entry is not a valid UTF-8 string"))?;
|
||||
|
||||
Ok(JsonValue::String(entry))
|
||||
}
|
||||
|
||||
/// Interact with the code context at the source file.
|
||||
pub fn interact_code_context(&mut self, _arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let queries = _arguments.into_iter().next().ok_or_else(|| {
|
||||
invalid_params("The first parameter is not a valid code context query array")
|
||||
})?;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InteractCodeContextParams {
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
pub query: Vec<tinymist_query::InteractCodeContextQuery>,
|
||||
}
|
||||
|
||||
let params: InteractCodeContextParams = serde_json::from_value(queries)
|
||||
.map_err(|e| invalid_params(format!("Cannot parse code context queries: {e}")))?;
|
||||
let path = as_path(params.text_document);
|
||||
let query = params.query;
|
||||
|
||||
let res = run_query!(self.InteractCodeContext(path, query))?;
|
||||
let res =
|
||||
serde_json::to_value(res).map_err(|_| internal_error("Cannot serialize responses"))?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Get the trace data of the document.
|
||||
pub fn get_document_trace(
|
||||
&mut self,
|
||||
req_id: RequestId,
|
||||
mut args: Vec<JsonValue>,
|
||||
) -> LspResult<Option<()>> {
|
||||
let path = get_arg!(args[0] as PathBuf).into();
|
||||
|
||||
// get path to self program
|
||||
let self_path = std::env::current_exe()
|
||||
.map_err(|e| internal_error(format!("Cannot get typst compiler {e}")))?;
|
||||
|
||||
let thread = self.user_action_thread.clone();
|
||||
let entry = self.config.compile.determine_entry(Some(path));
|
||||
|
||||
let res = self
|
||||
.primary()
|
||||
.steal(move |c| {
|
||||
let verse = &c.verse;
|
||||
|
||||
// todo: rootless file
|
||||
// todo: memory dirty file
|
||||
let root = entry.root().ok_or_else(|| {
|
||||
anyhow::anyhow!("root must be determined for trace, got {entry:?}")
|
||||
})?;
|
||||
let main = entry
|
||||
.main()
|
||||
.and_then(|e| e.vpath().resolve(&root))
|
||||
.ok_or_else(|| anyhow::anyhow!("main file must be resolved, got {entry:?}"))?;
|
||||
|
||||
if let Some(f) = thread {
|
||||
f.send(UserActionRequest::Trace(
|
||||
req_id,
|
||||
TraceParams {
|
||||
compiler_program: self_path,
|
||||
root: root.as_ref().to_owned(),
|
||||
main,
|
||||
inputs: verse.inputs().as_ref().deref().clone(),
|
||||
font_paths: verse.font_resolver.font_paths().to_owned(),
|
||||
},
|
||||
))
|
||||
.context("cannot send trace request")?;
|
||||
} else {
|
||||
bail!("user action thread is not available");
|
||||
}
|
||||
|
||||
Ok(Some(()))
|
||||
})
|
||||
.context("cannot steal primary compiler");
|
||||
|
||||
let res = match res {
|
||||
Ok(res) => res,
|
||||
Err(res) => Err(res),
|
||||
};
|
||||
|
||||
res.map_err(|e| internal_error(format!("could not get document trace: {e}")))
|
||||
}
|
||||
|
||||
/// Get the metrics of the document.
|
||||
pub fn get_document_metrics(&mut self, mut args: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let path = get_arg!(args[0] as PathBuf);
|
||||
|
||||
let res = run_query!(self.DocumentMetrics(path))?;
|
||||
let res = serde_json::to_value(res)
|
||||
.map_err(|e| internal_error(format!("Cannot serialize response {e}")))?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Get the server info.
|
||||
pub fn get_server_info(&mut self, _arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let res = run_query!(self.ServerInfo())?;
|
||||
|
||||
let res = serde_json::to_value(res)
|
||||
.map_err(|e| internal_error(format!("Cannot serialize response {e}")))?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Get static resources with help of tinymist service, for example, a
|
||||
/// static help pages for some typst function.
|
||||
pub fn get_resources(&mut self, mut args: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let path = get_arg!(args[0] as PathBuf);
|
||||
|
||||
let Some(handler) = self.resource_routes.get(path.as_path()) else {
|
||||
error!("asked for unknown resource: {path:?}");
|
||||
return Err(method_not_found());
|
||||
};
|
||||
|
||||
// Note our redirection will keep the first path argument in the args vec.
|
||||
handler(self, args)
|
||||
}
|
||||
}
|
||||
|
||||
impl LanguageState {
|
||||
pub fn get_resource_routes() -> ResourceMap {
|
||||
macro_rules! resources_at {
|
||||
($key: expr, Self::$method: ident) => {
|
||||
(
|
||||
Path::new($key).clean().as_path().into(),
|
||||
resource_fn!(LspHandler<Vec<JsonValue>, JsonValue>, Self::$method, inputs),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
ResourceMap::from_iter([
|
||||
resources_at!("/symbols", Self::resource_symbols),
|
||||
resources_at!("/tutorial", Self::resource_tutoral),
|
||||
])
|
||||
}
|
||||
|
||||
/// Get the all valid symbols
|
||||
pub fn resource_symbols(&self, _arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
let resp = self.get_symbol_resources();
|
||||
resp.map_err(|e| internal_error(e.to_string()))
|
||||
}
|
||||
|
||||
/// Get tutorial web page
|
||||
pub fn resource_tutoral(&self, _arguments: Vec<JsonValue>) -> LspResult<JsonValue> {
|
||||
Err(method_not_found())
|
||||
}
|
||||
}
|
|
@ -11,11 +11,11 @@ use tokio::sync::mpsc;
|
|||
use typst_ts_core::ImmutPath;
|
||||
|
||||
use crate::actor::editor::EditorActor;
|
||||
use crate::compiler_init::CompileConfig;
|
||||
use crate::compile_init::CompileConfig;
|
||||
use crate::harness::LspHost;
|
||||
use crate::utils::{try_, try_or};
|
||||
use crate::world::ImmutDict;
|
||||
use crate::{invalid_params, CompileFontOpts, LspResult, TypstLanguageServer};
|
||||
use crate::{invalid_params, CompileFontOpts, LanguageState, LspResult};
|
||||
|
||||
// todo: svelte-language-server responds to a Goto Definition request with
|
||||
// LocationLink[] even if the client does not report the
|
||||
|
@ -217,7 +217,7 @@ impl From<&InitializeParams> for ConstConfig {
|
|||
}
|
||||
|
||||
pub struct Init {
|
||||
pub host: LspHost<TypstLanguageServer>,
|
||||
pub host: LspHost<LanguageState>,
|
||||
pub handle: tokio::runtime::Handle,
|
||||
pub compile_opts: CompileFontOpts,
|
||||
}
|
||||
|
@ -241,7 +241,7 @@ impl Init {
|
|||
pub fn initialize(
|
||||
mut self,
|
||||
params: InitializeParams,
|
||||
) -> (TypstLanguageServer, LspResult<InitializeResult>) {
|
||||
) -> (LanguageState, LspResult<InitializeResult>) {
|
||||
// self.tracing_init();
|
||||
|
||||
// Initialize configurations
|
||||
|
@ -279,7 +279,7 @@ impl Init {
|
|||
// Bootstrap server
|
||||
let (editor_tx, editor_rx) = mpsc::unbounded_channel();
|
||||
|
||||
let mut service = TypstLanguageServer::new(
|
||||
let mut service = LanguageState::new(
|
||||
self.host.clone(),
|
||||
cc.clone(),
|
||||
editor_tx,
|
||||
|
|
|
@ -1,7 +1,41 @@
|
|||
pub mod lsp;
|
||||
pub mod lsp_cmd;
|
||||
pub mod lsp_init;
|
||||
|
||||
pub mod compiler;
|
||||
pub mod compiler_init;
|
||||
pub mod compile;
|
||||
pub mod compile_cmd;
|
||||
pub mod compile_init;
|
||||
|
||||
pub mod preview;
|
||||
|
||||
use serde_json::from_value;
|
||||
|
||||
/// Get a parsed command argument.
|
||||
/// Return `INVALID_PARAMS` when no arg or parse failed.
|
||||
macro_rules! get_arg {
|
||||
($args:ident[$idx:expr] as $ty:ty) => {{
|
||||
let arg = $args.get_mut($idx);
|
||||
let arg = arg.and_then(|x| from_value::<$ty>(x.take()).ok());
|
||||
match arg {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
let msg = concat!("expect ", stringify!($ty), "at args[", $idx, "]");
|
||||
return Err(invalid_params(msg));
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
use get_arg;
|
||||
|
||||
/// Get a parsed command argument or default if no arg.
|
||||
/// Return `INVALID_PARAMS` when parse failed.
|
||||
macro_rules! get_arg_or_default {
|
||||
($args:ident[$idx:expr] as $ty:ty) => {{
|
||||
if $idx >= $args.len() {
|
||||
Default::default()
|
||||
} else {
|
||||
get_arg!($args[$idx] as $ty)
|
||||
}
|
||||
}};
|
||||
}
|
||||
use get_arg_or_default;
|
||||
|
|
|
@ -56,7 +56,7 @@ pub struct PreviewCliArgs {
|
|||
mod compiler;
|
||||
use compiler::CompileServer;
|
||||
|
||||
use crate::compiler_init::CompileOnceArgs;
|
||||
use crate::compile_init::CompileOnceArgs;
|
||||
|
||||
pub fn make_static_host(
|
||||
previewer: &Previewer,
|
||||
|
|
|
@ -16,7 +16,7 @@ use typst_preview::{CompilationHandle, CompileStatus};
|
|||
|
||||
use crate::actor::typ_client::CompileClientActorImpl;
|
||||
use crate::actor::typ_server::CompileServerActor;
|
||||
use crate::compiler_init::CompileConfig;
|
||||
use crate::compile_init::CompileConfig;
|
||||
use crate::world::{LspCompilerFeat, LspWorld};
|
||||
|
||||
pub type CompileService<H> =
|
||||
|
|
|
@ -15,9 +15,9 @@ use typst_ts_compiler::{
|
|||
};
|
||||
use typst_ts_core::{error::prelude::*, Bytes, Error, ImmutPath};
|
||||
|
||||
use crate::{actor::typ_client::CompileClientActor, compiler::CompileServer, TypstLanguageServer};
|
||||
use crate::{actor::typ_client::CompileClientActor, compile::CompileState, LanguageState};
|
||||
|
||||
impl CompileServer {
|
||||
impl CompileState {
|
||||
/// Focus main file to some path.
|
||||
pub fn do_change_entry(&mut self, new_entry: Option<ImmutPath>) -> Result<bool, Error> {
|
||||
self.compiler
|
||||
|
@ -27,7 +27,7 @@ impl CompileServer {
|
|||
}
|
||||
}
|
||||
|
||||
impl TypstLanguageServer {
|
||||
impl LanguageState {
|
||||
/// Pin the entry to the given path
|
||||
pub fn pin_entry(&mut self, new_entry: Option<ImmutPath>) -> Result<(), Error> {
|
||||
self.pinning = new_entry.is_some();
|
||||
|
@ -98,11 +98,11 @@ pub struct MemoryFileMeta {
|
|||
pub content: Source,
|
||||
}
|
||||
|
||||
impl TypstLanguageServer {
|
||||
impl LanguageState {
|
||||
fn update_source(&self, files: FileChangeSet) -> Result<(), Error> {
|
||||
let primary = Some(self.primary());
|
||||
let clients_to_notify =
|
||||
(primary.into_iter()).chain(self.dedicates.iter().map(CompileServer::compiler));
|
||||
(primary.into_iter()).chain(self.dedicates.iter().map(CompileState::compiler));
|
||||
|
||||
for client in clients_to_notify {
|
||||
client.add_memory_changes(MemoryEvent::Update(files.clone()));
|
||||
|
@ -240,7 +240,7 @@ macro_rules! query_world {
|
|||
}};
|
||||
}
|
||||
|
||||
impl TypstLanguageServer {
|
||||
impl LanguageState {
|
||||
pub fn query_source<T>(
|
||||
&self,
|
||||
path: ImmutPath,
|
||||
|
@ -316,9 +316,9 @@ impl TypstLanguageServer {
|
|||
}
|
||||
}
|
||||
|
||||
impl CompileServer {
|
||||
impl CompileState {
|
||||
pub fn query(&self, query: CompilerQueryRequest) -> anyhow::Result<CompilerQueryResponse> {
|
||||
let client = self.compiler.as_ref().unwrap();
|
||||
TypstLanguageServer::query_on(client, query)
|
||||
LanguageState::query_on(client, query)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue