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:
Myriad-Dreamin 2024-06-23 12:31:03 +08:00 committed by GitHub
parent 7d65829ed7
commit 5e4e1e9877
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 754 additions and 707 deletions

41
Cargo.lock generated
View file

@ -174,6 +174,26 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" 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]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.80" version = "0.1.80"
@ -3912,6 +3932,7 @@ name = "tinymist"
version = "0.11.11" version = "0.11.11"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-lsp",
"async-trait", "async-trait",
"await-tree", "await-tree",
"base64 0.22.1", "base64 0.22.1",
@ -3937,6 +3958,7 @@ dependencies = [
"open", "open",
"parking_lot", "parking_lot",
"paste", "paste",
"pin-project-lite",
"serde", "serde",
"serde_json", "serde_json",
"tinymist-assets 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", "tinymist-assets 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
@ -3945,6 +3967,8 @@ dependencies = [
"tokio", "tokio",
"tokio-util", "tokio-util",
"toml 0.8.13", "toml 0.8.13",
"tower-layer",
"tower-service",
"typst", "typst",
"typst-assets", "typst-assets",
"typst-pdf", "typst-pdf",
@ -4123,6 +4147,7 @@ checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
"futures-io",
"futures-sink", "futures-sink",
"pin-project-lite", "pin-project-lite",
"tokio", "tokio",
@ -4187,6 +4212,12 @@ dependencies = [
"winnow 0.6.8", "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]] [[package]]
name = "tower-service" name = "tower-service"
version = "0.3.2" version = "0.3.2"
@ -4854,6 +4885,16 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 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]] [[package]]
name = "walkdir" name = "walkdir"
version = "2.5.0" version = "2.5.0"

View file

@ -58,6 +58,7 @@ typst-ts-svg-exporter = { version = "0.5.0-rc4" }
typstyle = "0.11.26" typstyle = "0.11.26"
typstfmt_lib = { git = "https://github.com/astrale-sharp/typstfmt", tag = "0.2.7" } 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-server = "0.7.6"
lsp-types = { version = "=0.95.0", features = ["proposed"] } lsp-types = { version = "=0.95.0", features = ["proposed"] }
crossbeam-channel = "0.5.12" crossbeam-channel = "0.5.12"
@ -79,7 +80,7 @@ tokio = { version = "1.36.0", features = [
"rt-multi-thread", "rt-multi-thread",
"io-std", "io-std",
] } ] }
tokio-util = "0.7.10" tokio-util = { version = "0.7.10", features = ["compat"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
serde_yaml = "0.9" serde_yaml = "0.9"

View file

@ -13,68 +13,67 @@ pub mod syntax;
pub mod ty; pub mod ty;
mod upstream; mod upstream;
pub(crate) mod diagnostics;
use std::sync::Arc; use std::sync::Arc;
pub use analysis::AnalysisContext; pub use analysis::AnalysisContext;
use typst::{model::Document as TypstDocument, syntax::Source}; use typst::{model::Document as TypstDocument, syntax::Source};
mod diagnostics;
pub use diagnostics::*; pub use diagnostics::*;
pub(crate) mod code_action; mod code_action;
pub use code_action::*; pub use code_action::*;
pub(crate) mod code_context; mod code_context;
pub use code_context::*; pub use code_context::*;
pub(crate) mod code_lens; mod code_lens;
pub use code_lens::*; pub use code_lens::*;
pub(crate) mod completion; mod completion;
pub use completion::*; pub use completion::*;
pub(crate) mod color_presentation; mod color_presentation;
pub use color_presentation::*; pub use color_presentation::*;
pub(crate) mod document_color; mod document_color;
pub use document_color::*; pub use document_color::*;
pub(crate) mod document_highlight; mod document_highlight;
pub use document_highlight::*; pub use document_highlight::*;
pub(crate) mod document_symbol; mod document_symbol;
pub use document_symbol::*; pub use document_symbol::*;
pub(crate) mod document_metrics; mod document_metrics;
pub use document_metrics::*; pub use document_metrics::*;
pub(crate) mod folding_range; mod folding_range;
pub use folding_range::*; pub use folding_range::*;
pub(crate) mod goto_declaration; mod goto_declaration;
pub use goto_declaration::*; pub use goto_declaration::*;
pub(crate) mod goto_definition; mod goto_definition;
pub use goto_definition::*; pub use goto_definition::*;
pub(crate) mod hover; mod hover;
pub use hover::*; pub use hover::*;
pub(crate) mod inlay_hint; mod inlay_hint;
pub use inlay_hint::*; pub use inlay_hint::*;
pub(crate) mod jump; mod jump;
pub use jump::*; pub use jump::*;
pub(crate) mod rename; mod rename;
pub use rename::*; pub use rename::*;
pub(crate) mod selection_range; mod selection_range;
pub use selection_range::*; pub use selection_range::*;
pub(crate) mod semantic_tokens; mod semantic_tokens;
pub use semantic_tokens::*; pub use semantic_tokens::*;
pub(crate) mod semantic_tokens_full; mod semantic_tokens_full;
pub use semantic_tokens_full::*; pub use semantic_tokens_full::*;
pub(crate) mod semantic_tokens_delta; mod semantic_tokens_delta;
pub use semantic_tokens_delta::*; pub use semantic_tokens_delta::*;
pub(crate) mod signature_help; mod signature_help;
pub use signature_help::*; pub use signature_help::*;
pub(crate) mod symbol; mod symbol;
pub use symbol::*; pub use symbol::*;
pub(crate) mod on_enter; mod on_enter;
pub use on_enter::*; pub use on_enter::*;
pub(crate) mod prepare_rename; mod prepare_rename;
pub use prepare_rename::*; pub use prepare_rename::*;
pub(crate) mod references; mod references;
pub use references::*; pub use references::*;
pub mod lsp_typst_boundary; mod lsp_typst_boundary;
pub use lsp_typst_boundary::*; pub use lsp_typst_boundary::*;
pub(crate) mod lsp_features; mod lsp_features;
pub use lsp_features::*; pub use lsp_features::*;
mod prelude; mod prelude;
@ -136,9 +135,10 @@ mod polymorphic {
use super::prelude::*; use super::prelude::*;
use super::*; use super::*;
#[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub enum PageSelection { pub enum PageSelection {
#[default]
First, First,
Merged, Merged,
} }
@ -234,68 +234,69 @@ mod polymorphic {
pub fn fold_feature(&self) -> FoldRequestFeature { pub fn fold_feature(&self) -> FoldRequestFeature {
use FoldRequestFeature::*; use FoldRequestFeature::*;
match self { match self {
CompilerQueryRequest::OnExport(..) => Mergeable, Self::OnExport(..) => Mergeable,
CompilerQueryRequest::OnSaveExport(..) => Mergeable, Self::OnSaveExport(..) => Mergeable,
CompilerQueryRequest::Hover(..) => PinnedFirst, Self::Hover(..) => PinnedFirst,
CompilerQueryRequest::GotoDefinition(..) => PinnedFirst, Self::GotoDefinition(..) => PinnedFirst,
CompilerQueryRequest::GotoDeclaration(..) => PinnedFirst, Self::GotoDeclaration(..) => PinnedFirst,
CompilerQueryRequest::References(..) => PinnedFirst, Self::References(..) => PinnedFirst,
CompilerQueryRequest::InlayHint(..) => Unique, Self::InlayHint(..) => Unique,
CompilerQueryRequest::DocumentColor(..) => PinnedFirst, Self::DocumentColor(..) => PinnedFirst,
CompilerQueryRequest::DocumentHighlight(..) => PinnedFirst, Self::DocumentHighlight(..) => PinnedFirst,
CompilerQueryRequest::ColorPresentation(..) => ContextFreeUnique, Self::ColorPresentation(..) => ContextFreeUnique,
CompilerQueryRequest::CodeAction(..) => Unique, Self::CodeAction(..) => Unique,
CompilerQueryRequest::CodeLens(..) => Unique, Self::CodeLens(..) => Unique,
CompilerQueryRequest::Completion(..) => Mergeable, Self::Completion(..) => Mergeable,
CompilerQueryRequest::SignatureHelp(..) => PinnedFirst, Self::SignatureHelp(..) => PinnedFirst,
CompilerQueryRequest::Rename(..) => Mergeable, Self::Rename(..) => Mergeable,
CompilerQueryRequest::PrepareRename(..) => Mergeable, Self::PrepareRename(..) => Mergeable,
CompilerQueryRequest::DocumentSymbol(..) => ContextFreeUnique, Self::DocumentSymbol(..) => ContextFreeUnique,
CompilerQueryRequest::Symbol(..) => Mergeable, Self::Symbol(..) => Mergeable,
CompilerQueryRequest::SemanticTokensFull(..) => ContextFreeUnique, Self::SemanticTokensFull(..) => ContextFreeUnique,
CompilerQueryRequest::SemanticTokensDelta(..) => ContextFreeUnique, Self::SemanticTokensDelta(..) => ContextFreeUnique,
CompilerQueryRequest::Formatting(..) => ContextFreeUnique, Self::Formatting(..) => ContextFreeUnique,
CompilerQueryRequest::FoldingRange(..) => ContextFreeUnique, Self::FoldingRange(..) => ContextFreeUnique,
CompilerQueryRequest::SelectionRange(..) => ContextFreeUnique, Self::SelectionRange(..) => ContextFreeUnique,
CompilerQueryRequest::InteractCodeContext(..) => PinnedFirst, Self::InteractCodeContext(..) => PinnedFirst,
CompilerQueryRequest::OnEnter(..) => ContextFreeUnique, Self::OnEnter(..) => ContextFreeUnique,
CompilerQueryRequest::DocumentMetrics(..) => PinnedFirst, Self::DocumentMetrics(..) => PinnedFirst,
CompilerQueryRequest::ServerInfo(..) => Mergeable, Self::ServerInfo(..) => Mergeable,
} }
} }
pub fn associated_path(&self) -> Option<&Path> { pub fn associated_path(&self) -> Option<&Path> {
Some(match self { Some(match self {
CompilerQueryRequest::OnExport(..) => return None, Self::OnExport(..) => return None,
CompilerQueryRequest::OnSaveExport(req) => &req.path, Self::OnSaveExport(req) => &req.path,
CompilerQueryRequest::Hover(req) => &req.path, Self::Hover(req) => &req.path,
CompilerQueryRequest::GotoDefinition(req) => &req.path, Self::GotoDefinition(req) => &req.path,
CompilerQueryRequest::GotoDeclaration(req) => &req.path, Self::GotoDeclaration(req) => &req.path,
CompilerQueryRequest::References(req) => &req.path, Self::References(req) => &req.path,
CompilerQueryRequest::InlayHint(req) => &req.path, Self::InlayHint(req) => &req.path,
CompilerQueryRequest::DocumentColor(req) => &req.path, Self::DocumentColor(req) => &req.path,
CompilerQueryRequest::DocumentHighlight(req) => &req.path, Self::DocumentHighlight(req) => &req.path,
CompilerQueryRequest::ColorPresentation(req) => &req.path, Self::ColorPresentation(req) => &req.path,
CompilerQueryRequest::CodeAction(req) => &req.path, Self::CodeAction(req) => &req.path,
CompilerQueryRequest::CodeLens(req) => &req.path, Self::CodeLens(req) => &req.path,
CompilerQueryRequest::Completion(req) => &req.path, Self::Completion(req) => &req.path,
CompilerQueryRequest::SignatureHelp(req) => &req.path, Self::SignatureHelp(req) => &req.path,
CompilerQueryRequest::Rename(req) => &req.path, Self::Rename(req) => &req.path,
CompilerQueryRequest::PrepareRename(req) => &req.path, Self::PrepareRename(req) => &req.path,
CompilerQueryRequest::DocumentSymbol(req) => &req.path, Self::DocumentSymbol(req) => &req.path,
CompilerQueryRequest::Symbol(..) => return None, Self::Symbol(..) => return None,
CompilerQueryRequest::SemanticTokensFull(req) => &req.path, Self::SemanticTokensFull(req) => &req.path,
CompilerQueryRequest::SemanticTokensDelta(req) => &req.path, Self::SemanticTokensDelta(req) => &req.path,
CompilerQueryRequest::Formatting(req) => &req.path, Self::Formatting(req) => &req.path,
CompilerQueryRequest::FoldingRange(req) => &req.path, Self::FoldingRange(req) => &req.path,
CompilerQueryRequest::SelectionRange(req) => &req.path, Self::SelectionRange(req) => &req.path,
CompilerQueryRequest::InteractCodeContext(req) => &req.path, Self::InteractCodeContext(req) => &req.path,
CompilerQueryRequest::OnEnter(req) => &req.path,
CompilerQueryRequest::DocumentMetrics(req) => &req.path, Self::OnEnter(req) => &req.path,
CompilerQueryRequest::ServerInfo(..) => return None,
Self::DocumentMetrics(req) => &req.path,
Self::ServerInfo(..) => return None,
}) })
} }
} }

View file

@ -58,6 +58,7 @@ toml.workspace = true
walkdir.workspace = true walkdir.workspace = true
typst-preview = { workspace = true, optional = true } typst-preview = { workspace = true, optional = true }
lsp-server.workspace = true lsp-server.workspace = true
async-lsp.workspace = true
crossbeam-channel.workspace = true crossbeam-channel.workspace = true
lsp-types.workspace = true lsp-types.workspace = true
dhat = { version = "0.3.3", optional = true } dhat = { version = "0.3.3", optional = true }
@ -66,6 +67,9 @@ unicode-script = "0.5"
await-tree = "0.1.2" await-tree = "0.1.2"
hyper = { version = "0.14", features = ["full"], optional = true } hyper = { version = "0.14", features = ["full"], optional = true }
open = { version = "5.1.3", 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 base64.workspace = true
[features] [features]

View file

@ -7,7 +7,7 @@ use lsp_types::{Diagnostic, Url};
use tinymist_query::{DiagnosticsMap, LspDiagnostic}; use tinymist_query::{DiagnosticsMap, LspDiagnostic};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use crate::{tools::word_count::WordsCount, LspHost, TypstLanguageServer}; use crate::{tools::word_count::WordsCount, LspHost, LanguageState};
pub enum EditorRequest { pub enum EditorRequest {
Diag(String, Option<DiagnosticsMap>), Diag(String, Option<DiagnosticsMap>),
@ -16,7 +16,7 @@ pub enum EditorRequest {
} }
pub struct EditorActor { pub struct EditorActor {
host: LspHost<TypstLanguageServer>, host: LspHost<LanguageState>,
editor_rx: mpsc::UnboundedReceiver<EditorRequest>, editor_rx: mpsc::UnboundedReceiver<EditorRequest>,
diagnostics: HashMap<Url, HashMap<String, Vec<LspDiagnostic>>>, diagnostics: HashMap<Url, HashMap<String, Vec<LspDiagnostic>>>,
@ -27,7 +27,7 @@ pub struct EditorActor {
impl EditorActor { impl EditorActor {
pub fn new( pub fn new(
host: LspHost<TypstLanguageServer>, host: LspHost<LanguageState>,
editor_rx: mpsc::UnboundedReceiver<EditorRequest>, editor_rx: mpsc::UnboundedReceiver<EditorRequest>,
notify_compile_status: bool, notify_compile_status: bool,
) -> Self { ) -> Self {

View file

@ -7,7 +7,7 @@ use lsp_types::TextEdit;
use tinymist_query::{typst_to_lsp, PositionEncoding}; use tinymist_query::{typst_to_lsp, PositionEncoding};
use typst::syntax::Source; 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)] #[derive(Debug, Clone)]
pub struct FormatConfig { pub struct FormatConfig {
@ -23,7 +23,7 @@ pub enum FormatRequest {
pub fn run_format_thread( pub fn run_format_thread(
config: FormatConfig, config: FormatConfig,
format_rx: crossbeam_channel::Receiver<FormatRequest>, format_rx: crossbeam_channel::Receiver<FormatRequest>,
client: LspHost<TypstLanguageServer>, client: LspHost<LanguageState>,
position_encoding: PositionEncoding, position_encoding: PositionEncoding,
) { ) {
type FmtFn = Box<dyn Fn(Source) -> LspResult<Option<Vec<TextEdit>>>>; type FmtFn = Box<dyn Fn(Source) -> LspResult<Option<Vec<TextEdit>>>>;

View file

@ -24,12 +24,12 @@ use self::{
user_action::run_user_action_thread, user_action::run_user_action_thread,
}; };
use crate::{ use crate::{
compiler::CompileServer, compile::CompileState,
world::{ImmutDict, LspWorldBuilder}, world::{ImmutDict, LspWorldBuilder},
TypstLanguageServer, LanguageState,
}; };
impl CompileServer { impl CompileState {
pub fn restart_server(&mut self, group: &str) { pub fn restart_server(&mut self, group: &str) {
let server = self.server( let server = self.server(
group.to_owned(), group.to_owned(),
@ -132,7 +132,7 @@ impl CompileServer {
} }
} }
impl TypstLanguageServer { impl LanguageState {
pub fn server( pub fn server(
&self, &self,
diag_group: String, diag_group: String,

View file

@ -65,7 +65,7 @@ use super::{
}; };
use crate::{ use crate::{
actor::export::ExportRequest, actor::export::ExportRequest,
compiler_init::CompileConfig, compile_init::CompileConfig,
tools::preview::{CompilationHandle, CompileStatus}, tools::preview::{CompilationHandle, CompileStatus},
utils, utils,
world::{LspCompilerFeat, LspWorld}, world::{LspCompilerFeat, LspWorld},

View file

@ -75,7 +75,7 @@ pub struct CompileServerActor<C: Compiler, F: CompilerFeat> {
estimated_shadow_files: HashSet<Arc<Path>>, estimated_shadow_files: HashSet<Arc<Path>>,
/// The latest compiled document. /// The latest compiled document.
pub(crate) latest_doc: Option<Arc<TypstDocument>>, pub(crate) latest_doc: Option<Arc<TypstDocument>>,
/// The latest successly compiled document. /// The latest successfully compiled document.
latest_success_doc: Option<Arc<TypstDocument>>, latest_success_doc: Option<Arc<TypstDocument>>,
/// feature set for compile_once mode. /// feature set for compile_once mode.
once_feature_set: Arc<FeatureSet>, once_feature_set: Arc<FeatureSet>,

View file

@ -8,7 +8,7 @@ use lsp_server::RequestId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use typst_ts_core::TypstDict; 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)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -26,7 +26,7 @@ pub enum UserActionRequest {
pub fn run_user_action_thread( pub fn run_user_action_thread(
user_action_rx: crossbeam_channel::Receiver<UserActionRequest>, user_action_rx: crossbeam_channel::Receiver<UserActionRequest>,
client: LspHost<TypstLanguageServer>, client: LspHost<LanguageState>,
) { ) {
while let Ok(req) = user_action_rx.recv() { while let Ok(req) = user_action_rx.recv() {
match req { match req {

View file

@ -1,8 +1,8 @@
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use tinymist::preview::PreviewCliArgs;
use tinymist::transport::MirrorArgs; use tinymist::transport::MirrorArgs;
use tinymist::compiler_init::{CompileOnceArgs, FontArgs}; use tinymist::compile_init::{CompileOnceArgs, FontArgs};
use tinymist::preview::PreviewCliArgs;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[cfg_attr(feature = "clap", derive(clap::Parser))] #[cfg_attr(feature = "clap", derive(clap::Parser))]

View file

@ -37,8 +37,8 @@ pub mod transport;
mod utils; mod utils;
mod world; mod world;
pub use crate::harness::LspHost; pub use crate::harness::LspHost;
pub use server::compiler; pub use server::compile;
pub use server::compiler_init; pub use server::compile_init;
pub use server::lsp::*; pub use server::lsp::*;
pub use server::lsp_init::*; pub use server::lsp_init::*;
pub use server::preview; pub use server::preview;
@ -46,6 +46,7 @@ pub use world::{
CompileFontOpts, CompileOnceOpts, CompileOpts, LspUniverse, LspWorld, LspWorldBuilder, CompileFontOpts, CompileOnceOpts, CompileOpts, LspUniverse, LspWorld, LspWorldBuilder,
}; };
// use async_lsp::ClientSocket;
use lsp_server::ResponseError; use lsp_server::ResponseError;
type LspResult<Res> = Result<Res, ResponseError>; type LspResult<Res> = Result<Res, ResponseError>;

View file

@ -11,11 +11,11 @@ use lsp_types::{InitializeParams, InitializedParams};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use parking_lot::RwLock; use parking_lot::RwLock;
use tinymist::{ use tinymist::{
compiler_init::{CompileInit, CompileInitializeParams}, compile_init::{CompileInit, CompileInitializeParams},
harness::{lsp_harness, InitializedLspDriver, LspDriver, LspHost}, harness::{lsp_harness, InitializedLspDriver, LspDriver, LspHost},
preview::preview_main, preview::preview_main,
transport::with_stdio_transport, transport::with_stdio_transport,
CompileFontOpts, Init, LspWorld, TypstLanguageServer, CompileFontOpts, Init, LspWorld, LanguageState,
}; };
use tokio::sync::mpsc; use tokio::sync::mpsc;
use typst::World; use typst::World;
@ -90,7 +90,7 @@ pub fn lsp_main(args: LspArgs) -> anyhow::Result<()> {
impl LspDriver for Lsp { impl LspDriver for Lsp {
type InitParams = InitializeParams; type InitParams = InitializeParams;
type InitResult = lsp_types::InitializeResult; type InitResult = lsp_types::InitializeResult;
type InitializedSelf = TypstLanguageServer; type InitializedSelf = LanguageState;
fn initialize( fn initialize(
self, self,

View file

@ -14,7 +14,7 @@ mod prelude {
pub use typst_ts_svg_exporter::ir::{GlyphItem, GlyphRef}; pub use typst_ts_svg_exporter::ir::{GlyphItem, GlyphRef};
pub use typst_ts_svg_exporter::{DefaultExportFeature, SvgTask, SvgText}; pub use typst_ts_svg_exporter::{DefaultExportFeature, SvgTask, SvgText};
pub use crate::TypstLanguageServer; pub use crate::LanguageState;
pub type Svg<'a> = SvgTask<'a, DefaultExportFeature>; pub type Svg<'a> = SvgTask<'a, DefaultExportFeature>;
} }

View file

@ -156,7 +156,7 @@ static CAT_MAP: Lazy<HashMap<&str, SymCategory>> = Lazy::new(|| {
]) ])
}); });
impl TypstLanguageServer { impl LanguageState {
/// Get the all valid symbols /// Get the all valid symbols
pub fn get_symbol_resources(&self) -> ZResult<JsonValue> { pub fn get_symbol_resources(&self) -> ZResult<JsonValue> {
let mut symbols = ResourceSymbolMap::new(); let mut symbols = ResourceSymbolMap::new();

View file

@ -4,40 +4,30 @@ use std::{collections::HashMap, path::Path, sync::Arc, time::Instant};
use crossbeam_channel::{select, Receiver}; use crossbeam_channel::{select, Receiver};
use log::{error, info, warn}; use log::{error, info, warn};
use lsp_server::{ErrorCode, Message, Notification, Request, RequestId, Response, ResponseError}; 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 once_cell::sync::OnceCell;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value as JsonValue}; use serde_json::{Map, Value as JsonValue};
use tinymist_query::{ExportKind, PageSelection};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use typst::{diag::FileResult, syntax::Source}; use typst::{diag::FileResult, syntax::Source};
use typst_ts_compiler::vfs::notify::FileChangeSet; 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::{ use crate::{
actor::{editor::EditorRequest, export::ExportConfig, typ_client::CompileClientActor}, actor::{editor::EditorRequest, export::ExportConfig, typ_client::CompileClientActor},
compiler_init::{CompileConfig, CompilerConstConfig}, compile_init::{CompileConfig, ConstCompileConfig},
harness::InitializedLspDriver, harness::InitializedLspDriver,
internal_error, invalid_params, method_not_found, run_query, invalid_params,
state::MemoryFileMeta, state::MemoryFileMeta,
LspHost, LspResult, LspHost, LspResult,
}; };
type LspMethod<Res> = fn(srv: &mut CompileServer, args: JsonValue) -> LspResult<Res>; type LspMethod<Res> = fn(srv: &mut CompileState, args: JsonValue) -> LspResult<Res>;
type LspHandler<Req, Res> = fn(srv: &mut CompileServer, args: Req) -> 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 ExecuteCmdMap = HashMap<&'static str, LspHandler<Vec<JsonValue>, JsonValue>>;
type NotifyCmdMap = HashMap<&'static str, LspMethod<()>>; type NotifyCmdMap = HashMap<&'static str, LspMethod<()>>;
type RegularCmdMap = HashMap<&'static str, LspMethod<JsonValue>>; 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_export]
macro_rules! request_fn { macro_rules! request_fn {
($desc: ty, Self::$method: ident) => { ($desc: ty, Self::$method: ident) => {
@ -67,9 +57,9 @@ macro_rules! notify_fn {
} }
/// The object providing the language server functionality. /// The object providing the language server functionality.
pub struct CompileServer { pub struct CompileState {
/// The language server client. /// The language server client.
pub client: LspHost<CompileServer>, pub client: LspHost<CompileState>,
// State to synchronize with the client. // State to synchronize with the client.
/// Whether the server is shutting down. /// Whether the server is shutting down.
@ -80,7 +70,7 @@ pub struct CompileServer {
pub config: CompileConfig, pub config: CompileConfig,
/// Const configuration initialized at the start of the session. /// Const configuration initialized at the start of the session.
/// For example, the position encoding. /// For example, the position encoding.
pub const_config: CompilerConstConfig, pub const_config: ConstCompileConfig,
// Command maps // Command maps
/// Extra commands provided with `textDocument/executeCommand`. /// Extra commands provided with `textDocument/executeCommand`.
@ -101,15 +91,15 @@ pub struct CompileServer {
pub compiler: Option<CompileClientActor>, pub compiler: Option<CompileClientActor>,
} }
impl CompileServer { impl CompileState {
pub fn new( pub fn new(
client: LspHost<CompileServer>, client: LspHost<CompileState>,
compile_config: CompileConfig, compile_config: CompileConfig,
const_config: CompilerConstConfig, const_config: ConstCompileConfig,
editor_tx: mpsc::UnboundedSender<EditorRequest>, editor_tx: mpsc::UnboundedSender<EditorRequest>,
handle: tokio::runtime::Handle, handle: tokio::runtime::Handle,
) -> Self { ) -> Self {
CompileServer { CompileState {
client, client,
editor_tx, editor_tx,
shutdown_requested: false, shutdown_requested: false,
@ -125,7 +115,7 @@ impl CompileServer {
} }
} }
pub fn const_config(&self) -> &CompilerConstConfig { pub fn const_config(&self) -> &ConstCompileConfig {
&self.const_config &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 initialized(&mut self, _params: lsp_types::InitializedParams) {}
fn main_loop(&mut self, inbox: crossbeam_channel::Receiver<Message>) -> anyhow::Result<()> { 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> { fn next_event(&self, inbox: &Receiver<Message>) -> Option<Event> {
select! { select! {
recv(inbox) -> msg => 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<()> { pub fn on_changed_configuration(&mut self, values: Map<String, JsonValue>) -> LspResult<()> {
let config = self.config.clone(); let config = self.config.clone();
match self.config.update_by_map(&values) { match self.config.update_by_map(&values) {
@ -357,121 +347,3 @@ impl CompileServer {
} }
struct Cancelled; 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)?)),
}
}

View 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)
}
}

View file

@ -19,7 +19,7 @@ use typst_ts_core::config::compiler::EntryState;
use typst_ts_core::{ImmutPath, TypstDict}; use typst_ts_core::{ImmutPath, TypstDict};
use crate::actor::editor::EditorRequest; use crate::actor::editor::EditorRequest;
use crate::compiler::CompileServer; use crate::compile::CompileState;
use crate::harness::LspDriver; use crate::harness::LspDriver;
use crate::utils::{try_, try_or_default}; use crate::utils::{try_, try_or_default};
use crate::world::{ImmutDict, SharedFontResolver}; use crate::world::{ImmutDict, SharedFontResolver};
@ -372,21 +372,13 @@ impl CompileConfig {
/// Configuration set at initialization that won't change within a single /// Configuration set at initialization that won't change within a single
/// session. /// session.
#[derive(Debug, Clone)] #[derive(Default, Debug, Clone)]
pub struct CompilerConstConfig { pub struct ConstCompileConfig {
/// Determined position encoding, either UTF-8 or UTF-16. /// Determined position encoding, either UTF-8 or UTF-16.
/// Defaults to UTF-16 if not specified. /// Defaults to UTF-16 if not specified.
pub position_encoding: PositionEncoding, pub position_encoding: PositionEncoding,
} }
impl Default for CompilerConstConfig {
fn default() -> Self {
Self {
position_encoding: PositionEncoding::Utf16,
}
}
}
pub struct CompileInit { pub struct CompileInit {
pub handle: tokio::runtime::Handle, pub handle: tokio::runtime::Handle,
pub font: CompileFontOpts, pub font: CompileFontOpts,
@ -402,7 +394,7 @@ pub struct CompileInitializeParams {
impl LspDriver for CompileInit { impl LspDriver for CompileInit {
type InitParams = CompileInitializeParams; type InitParams = CompileInitializeParams;
type InitResult = (); type InitResult = ();
type InitializedSelf = CompileServer; type InitializedSelf = CompileState;
fn initialize( fn initialize(
self, self,
@ -418,10 +410,10 @@ impl LspDriver for CompileInit {
}; };
compile_config.update(&params.config).unwrap(); compile_config.update(&params.config).unwrap();
let mut service = CompileServer::new( let mut service = CompileState::new(
client, client,
compile_config, compile_config,
CompilerConstConfig { ConstCompileConfig {
position_encoding: params position_encoding: params
.position_encoding .position_encoding
.map(|x| match x.as_str() { .map(|x| match x.as_str() {

View file

@ -1,7 +1,5 @@
//! tinymist LSP mode //! tinymist LSP mode
use std::ops::Deref;
use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use std::time::Instant; use std::time::Instant;
use std::{collections::HashMap, path::PathBuf}; use std::{collections::HashMap, path::PathBuf};
@ -18,48 +16,37 @@ use serde::{Deserialize, Serialize};
use serde_json::{Map, Value as JsonValue}; use serde_json::{Map, Value as JsonValue};
use tinymist_query::{ use tinymist_query::{
get_semantic_tokens_options, get_semantic_tokens_registration, 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 tokio::sync::mpsc;
use typst::diag::StrResult; use typst_ts_core::ImmutPath;
use typst::syntax::package::{PackageSpec, VersionlessPackageSpec};
use typst_ts_core::path::PathClean;
use typst_ts_core::{error::prelude::*, ImmutPath};
use super::lsp_init::*; use super::lsp_init::*;
use crate::actor::editor::EditorRequest; use crate::actor::editor::EditorRequest;
use crate::actor::format::{FormatConfig, FormatRequest}; use crate::actor::format::{FormatConfig, FormatRequest};
use crate::actor::typ_client::CompileClientActor; use crate::actor::typ_client::CompileClientActor;
use crate::actor::user_action::{TraceParams, UserActionRequest}; use crate::actor::user_action::UserActionRequest;
use crate::compiler::CompileServer; use crate::compile::CompileState;
use crate::compiler_init::CompilerConstConfig; use crate::compile_init::ConstCompileConfig;
use crate::harness::{InitializedLspDriver, LspHost}; use crate::harness::{InitializedLspDriver, LspHost};
use crate::tools::package::InitTask;
use crate::{run_query, LspResult}; use crate::{run_query, LspResult};
pub type MaySyncResult<'a> = Result<JsonValue, BoxFuture<'a, JsonValue>>; pub type MaySyncResult<'a> = Result<JsonValue, BoxFuture<'a, JsonValue>>;
type LspMethod<Res> = fn(srv: &mut TypstLanguageServer, args: JsonValue) -> LspResult<Res>; type LspMethod<Res> = fn(srv: &mut LanguageState, args: JsonValue) -> LspResult<Res>;
type LspHandler<Req, Res> = fn(srv: &mut TypstLanguageServer, args: Req) -> LspResult<Res>; type LspHandler<Req, Res> = fn(srv: &mut LanguageState, args: Req) -> LspResult<Res>;
/// Returns Ok(Some()) -> Already responded /// Returns Ok(Some()) -> Already responded
/// Returns Ok(None) -> Need to respond none /// Returns Ok(None) -> Need to respond none
/// Returns Err(..) -> Need to respond error /// Returns Err(..) -> Need to respond error
type LspRawHandler<T> = 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 ExecuteCmdMap = HashMap<&'static str, LspRawHandler<Vec<JsonValue>>>;
type NotifyCmdMap = HashMap<&'static str, LspMethod<()>>; type NotifyCmdMap = HashMap<&'static str, LspMethod<()>>;
type RegularCmdMap = HashMap<&'static str, LspRawHandler<JsonValue>>; type RegularCmdMap = HashMap<&'static str, LspRawHandler<JsonValue>>;
type ResourceMap = HashMap<ImmutPath, LspHandler<Vec<JsonValue>, 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_ { macro_rules! request_fn_ {
($desc: ty, Self::$method: ident) => { ($desc: ty, Self::$method: ident) => {
(<$desc>::METHOD, { (<$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 { macro_rules! notify_fn {
($desc: ty, Self::$method: ident) => { ($desc: ty, Self::$method: ident) => {
(<$desc>::METHOD, { (<$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) as_path_(inp.uri)
} }
fn as_path_(uri: Url) -> PathBuf { pub(super) fn as_path_(uri: Url) -> PathBuf {
tinymist_query::url_to_path(uri) 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. /// The object providing the language server functionality.
pub struct TypstLanguageServer { pub struct LanguageState {
/// The language server client. /// The language server client.
pub client: LspHost<TypstLanguageServer>, pub client: LspHost<LanguageState>,
// State to synchronize with the client. // State to synchronize with the client.
/// Whether the server is shutting down. /// Whether the server is shutting down.
@ -173,15 +138,15 @@ pub struct TypstLanguageServer {
/// Regular commands for dispatching. /// Regular commands for dispatching.
pub regular_cmds: RegularCmdMap, pub regular_cmds: RegularCmdMap,
/// Regular commands for dispatching. /// Regular commands for dispatching.
pub resources_routes: ResourceMap, pub resource_routes: ResourceMap,
// Resources // Resources
/// The semantic token context. /// The semantic token context.
pub tokens_ctx: SemanticTokenContext, pub tokens_ctx: SemanticTokenContext,
/// The compiler for general purpose. /// The compiler for general purpose.
pub primary: CompileServer, pub primary: CompileState,
/// The compilers for tasks /// The compilers for tasks
pub dedicates: Vec<CompileServer>, pub dedicates: Vec<CompileState>,
/// The formatter thread running in backend. /// The formatter thread running in backend.
/// Note: The thread will exit if you drop the sender. /// Note: The thread will exit if you drop the sender.
pub format_thread: Option<crossbeam_channel::Sender<FormatRequest>>, pub format_thread: Option<crossbeam_channel::Sender<FormatRequest>>,
@ -191,10 +156,10 @@ pub struct TypstLanguageServer {
} }
/// Getters and the main loop. /// Getters and the main loop.
impl TypstLanguageServer { impl LanguageState {
/// Create a new language server. /// Create a new language server.
pub fn new( pub fn new(
client: LspHost<TypstLanguageServer>, client: LspHost<LanguageState>,
const_config: ConstConfig, const_config: ConstConfig,
editor_tx: mpsc::UnboundedSender<EditorRequest>, editor_tx: mpsc::UnboundedSender<EditorRequest>,
handle: tokio::runtime::Handle, handle: tokio::runtime::Handle,
@ -206,10 +171,10 @@ impl TypstLanguageServer {
); );
Self { Self {
client, client,
primary: CompileServer::new( primary: CompileState::new(
LspHost::new(Arc::new(RwLock::new(None))), LspHost::new(Arc::new(RwLock::new(None))),
Default::default(), Default::default(),
CompilerConstConfig { ConstCompileConfig {
position_encoding: const_config.position_encoding, position_encoding: const_config.position_encoding,
}, },
editor_tx, editor_tx,
@ -227,7 +192,7 @@ impl TypstLanguageServer {
exec_cmds: Self::get_exec_commands(), exec_cmds: Self::get_exec_commands(),
regular_cmds: Self::get_regular_cmds(), regular_cmds: Self::get_regular_cmds(),
notify_cmds: Self::get_notify_cmds(), notify_cmds: Self::get_notify_cmds(),
resources_routes: Self::get_resources_routes(), resource_routes: Self::get_resource_routes(),
pinning: false, pinning: false,
focusing: None, 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 /// The [`initialized`] notification is sent from the client to the server
/// after the client received the result of the initialize request but /// after the client received the result of the initialize request but
/// before the client sends anything else. /// 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 /// Registers and handles a request. This should only be called once per
/// incoming request. /// incoming request.
fn on_request(&mut self, request_received: Instant, req: Request) { fn on_request(&mut self, request_received: Instant, req: Request) {
@ -533,7 +498,7 @@ impl TypstLanguageServer {
/// low-level implementation details. /// low-level implementation details.
/// ///
/// [Language Server Protocol]: https://microsoft.github.io/language-server-protocol/ /// [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 /// The [`shutdown`] request asks the server to gracefully shut down, but to
/// not exit. /// 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 /// Document Synchronization
impl TypstLanguageServer { impl LanguageState {
fn did_open(&mut self, params: DidOpenTextDocumentParams) -> LspResult<()> { 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 = as_path_(params.text_document.uri); let path = as_path_(params.text_document.uri);
@ -1005,7 +632,7 @@ impl TypstLanguageServer {
} }
/// Standard Language Features /// Standard Language Features
impl TypstLanguageServer { impl LanguageState {
fn goto_definition( fn goto_definition(
&mut self, &mut self,
params: GotoDefinitionParams, params: GotoDefinitionParams,
@ -1195,32 +822,6 @@ struct ExportOpts {
page: PageSelection, 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 { pub fn invalid_params(msg: impl Into<String>) -> ResponseError {
ResponseError { ResponseError {
code: ErrorCode::InvalidParams as i32, code: ErrorCode::InvalidParams as i32,
@ -1270,6 +871,9 @@ impl lsp_types::request::Request for OnEnter {
#[test] #[test]
fn test_as_path() { fn test_as_path() {
use std::path::Path;
use typst_ts_core::path::PathClean;
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());

View 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())
}
}

View file

@ -11,11 +11,11 @@ use tokio::sync::mpsc;
use typst_ts_core::ImmutPath; use typst_ts_core::ImmutPath;
use crate::actor::editor::EditorActor; use crate::actor::editor::EditorActor;
use crate::compiler_init::CompileConfig; use crate::compile_init::CompileConfig;
use crate::harness::LspHost; use crate::harness::LspHost;
use crate::utils::{try_, try_or}; use crate::utils::{try_, try_or};
use crate::world::ImmutDict; 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 // todo: svelte-language-server responds to a Goto Definition request with
// LocationLink[] even if the client does not report the // LocationLink[] even if the client does not report the
@ -217,7 +217,7 @@ impl From<&InitializeParams> for ConstConfig {
} }
pub struct Init { pub struct Init {
pub host: LspHost<TypstLanguageServer>, pub host: LspHost<LanguageState>,
pub handle: tokio::runtime::Handle, pub handle: tokio::runtime::Handle,
pub compile_opts: CompileFontOpts, pub compile_opts: CompileFontOpts,
} }
@ -241,7 +241,7 @@ impl Init {
pub fn initialize( pub fn initialize(
mut self, mut self,
params: InitializeParams, params: InitializeParams,
) -> (TypstLanguageServer, LspResult<InitializeResult>) { ) -> (LanguageState, LspResult<InitializeResult>) {
// self.tracing_init(); // self.tracing_init();
// Initialize configurations // Initialize configurations
@ -279,7 +279,7 @@ impl Init {
// Bootstrap server // Bootstrap server
let (editor_tx, editor_rx) = mpsc::unbounded_channel(); let (editor_tx, editor_rx) = mpsc::unbounded_channel();
let mut service = TypstLanguageServer::new( let mut service = LanguageState::new(
self.host.clone(), self.host.clone(),
cc.clone(), cc.clone(),
editor_tx, editor_tx,

View file

@ -1,7 +1,41 @@
pub mod lsp; pub mod lsp;
pub mod lsp_cmd;
pub mod lsp_init; pub mod lsp_init;
pub mod compiler; pub mod compile;
pub mod compiler_init; pub mod compile_cmd;
pub mod compile_init;
pub mod preview; 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;

View file

@ -56,7 +56,7 @@ pub struct PreviewCliArgs {
mod compiler; mod compiler;
use compiler::CompileServer; use compiler::CompileServer;
use crate::compiler_init::CompileOnceArgs; use crate::compile_init::CompileOnceArgs;
pub fn make_static_host( pub fn make_static_host(
previewer: &Previewer, previewer: &Previewer,

View file

@ -16,7 +16,7 @@ use typst_preview::{CompilationHandle, CompileStatus};
use crate::actor::typ_client::CompileClientActorImpl; use crate::actor::typ_client::CompileClientActorImpl;
use crate::actor::typ_server::CompileServerActor; use crate::actor::typ_server::CompileServerActor;
use crate::compiler_init::CompileConfig; use crate::compile_init::CompileConfig;
use crate::world::{LspCompilerFeat, LspWorld}; use crate::world::{LspCompilerFeat, LspWorld};
pub type CompileService<H> = pub type CompileService<H> =

View file

@ -15,9 +15,9 @@ use typst_ts_compiler::{
}; };
use typst_ts_core::{error::prelude::*, Bytes, Error, ImmutPath}; 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. /// Focus main file to some path.
pub fn do_change_entry(&mut self, new_entry: Option<ImmutPath>) -> Result<bool, Error> { pub fn do_change_entry(&mut self, new_entry: Option<ImmutPath>) -> Result<bool, Error> {
self.compiler self.compiler
@ -27,7 +27,7 @@ impl CompileServer {
} }
} }
impl TypstLanguageServer { impl LanguageState {
/// Pin the entry to the given path /// Pin the entry to the given path
pub fn pin_entry(&mut self, new_entry: Option<ImmutPath>) -> Result<(), Error> { pub fn pin_entry(&mut self, new_entry: Option<ImmutPath>) -> Result<(), Error> {
self.pinning = new_entry.is_some(); self.pinning = new_entry.is_some();
@ -98,11 +98,11 @@ pub struct MemoryFileMeta {
pub content: Source, pub content: Source,
} }
impl TypstLanguageServer { impl LanguageState {
fn update_source(&self, files: FileChangeSet) -> Result<(), Error> { fn update_source(&self, files: FileChangeSet) -> Result<(), Error> {
let primary = Some(self.primary()); let primary = Some(self.primary());
let clients_to_notify = 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 { for client in clients_to_notify {
client.add_memory_changes(MemoryEvent::Update(files.clone())); 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>( pub fn query_source<T>(
&self, &self,
path: ImmutPath, path: ImmutPath,
@ -316,9 +316,9 @@ impl TypstLanguageServer {
} }
} }
impl CompileServer { impl CompileState {
pub fn query(&self, query: CompilerQueryRequest) -> anyhow::Result<CompilerQueryResponse> { pub fn query(&self, query: CompilerQueryRequest) -> anyhow::Result<CompilerQueryResponse> {
let client = self.compiler.as_ref().unwrap(); let client = self.compiler.as_ref().unwrap();
TypstLanguageServer::query_on(client, query) LanguageState::query_on(client, query)
} }
} }