feat: add js transport to sync-ls (#2029)
Some checks are pending
tinymist::auto_tag / auto-tag (push) Waiting to run
tinymist::ci / Duplicate Actions Detection (push) Waiting to run
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Waiting to run
tinymist::ci / Check Minimum Rust version and Tests (Windows) (push) Waiting to run
tinymist::ci / prepare-build (push) Waiting to run
tinymist::ci / announce (push) Blocked by required conditions
tinymist::ci / build (push) Blocked by required conditions
tinymist::gh_pages / build-gh-pages (push) Waiting to run

This commit is contained in:
Myriad-Dreamin 2025-08-12 06:07:23 +08:00 committed by GitHub
parent 5613bd29be
commit a561059a4e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 471 additions and 476 deletions

View file

@ -32,11 +32,11 @@ jobs:
sudo apt-get install llvm sudo apt-get install llvm
- name: Publish crates - name: Publish crates
run: | run: |
cargo publish --no-verify -p sync-ls || true
cargo publish --no-verify -p typst-shim || true cargo publish --no-verify -p typst-shim || true
cargo publish --no-verify -p tinymist-derive || true cargo publish --no-verify -p tinymist-derive || true
cargo publish --no-verify -p tinymist-l10n || true cargo publish --no-verify -p tinymist-l10n || true
cargo publish --no-verify -p tinymist-std || true cargo publish --no-verify -p tinymist-std || true
cargo publish --no-verify -p sync-ls || true
cargo publish --no-verify -p tinymist-package || true cargo publish --no-verify -p tinymist-package || true
cargo publish --no-verify -p tinymist-vfs || true cargo publish --no-verify -p tinymist-vfs || true
cargo publish --no-verify -p tinymist-world || true cargo publish --no-verify -p tinymist-world || true
@ -56,11 +56,11 @@ jobs:
cargo publish --no-verify -p tinymist-cli || true cargo publish --no-verify -p tinymist-cli || true
- name: Verifies crate health (Optional) - name: Verifies crate health (Optional)
run: | run: |
cargo publish --dry-run -p sync-ls
cargo publish --dry-run -p typst-shim cargo publish --dry-run -p typst-shim
cargo publish --dry-run -p tinymist-derive cargo publish --dry-run -p tinymist-derive
cargo publish --dry-run -p tinymist-l10n cargo publish --dry-run -p tinymist-l10n
cargo publish --dry-run -p tinymist-std cargo publish --dry-run -p tinymist-std
cargo publish --dry-run -p sync-ls
cargo publish --dry-run -p tinymist-vfs cargo publish --dry-run -p tinymist-vfs
cargo publish --dry-run -p tinymist-package cargo publish --dry-run -p tinymist-package
cargo publish --dry-run -p tinymist-world cargo publish --dry-run -p tinymist-world

3
Cargo.lock generated
View file

@ -3872,14 +3872,17 @@ dependencies = [
"crossbeam-channel", "crossbeam-channel",
"dapts", "dapts",
"futures", "futures",
"js-sys",
"log", "log",
"lsp-types", "lsp-types",
"parking_lot", "parking_lot",
"serde", "serde",
"serde-wasm-bindgen",
"serde_json", "serde_json",
"tinymist-std", "tinymist-std",
"tokio", "tokio",
"tokio-util", "tokio-util",
"wasm-bindgen",
] ]
[[package]] [[package]]

View file

@ -27,12 +27,16 @@ tinymist-std.workspace = true
clap = { workspace = true, optional = true } clap = { workspace = true, optional = true }
tokio = { workspace = true, features = ["rt"], optional = true } tokio = { workspace = true, features = ["rt"], optional = true }
tokio-util = { workspace = true, optional = true } tokio-util = { workspace = true, optional = true }
js-sys = { workspace = true, optional = true }
wasm-bindgen = { workspace = true, optional = true }
serde-wasm-bindgen = { workspace = true, optional = true }
[features] [features]
dap = ["dapts"] dap = ["dapts"]
lsp = ["lsp-types"] lsp = ["lsp-types"]
server = ["tokio"] server = ["tokio"]
system = ["tokio", "tokio/time", "tokio-util", "clap"] system = ["tokio", "tokio/time", "tokio-util", "clap", "tinymist-std/system"]
web = ["js-sys", "wasm-bindgen", "serde-wasm-bindgen", "tinymist-std/web"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["dap", "lsp", "system", "server"] features = ["dap", "lsp", "system", "server"]

View file

@ -28,3 +28,24 @@ pub use crate::msg::ResponseError;
pub type LspResult<T> = Result<T, ResponseError>; pub type LspResult<T> = Result<T, ResponseError>;
/// The common event type for language servers. /// The common event type for language servers.
pub type Event = Box<dyn Any + Send>; pub type Event = Box<dyn Any + Send>;
/// Note that we must have our logging only write out to stderr.
#[cfg(feature = "web")]
fn dummy_transport<M: TryFrom<Message, Error = anyhow::Error> + GetMessageKind>() -> Connection<M> {
let (event_sender, event_receiver) = crossbeam_channel::bounded::<crate::Event>(0);
let (writer_sender, writer_receiver) = crossbeam_channel::bounded::<Message>(0);
Connection {
// lsp_sender,
// lsp_receiver,
sender: TConnectionTx {
event: event_sender,
lsp: writer_sender,
marker: std::marker::PhantomData,
},
receiver: TConnectionRx {
event: event_receiver,
lsp: writer_receiver,
marker: std::marker::PhantomData,
},
}
}

View file

@ -6,8 +6,8 @@ use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
invalid_data_fmt, read_msg_text, write_msg_text, ExtractError, LspOrDapResponse, RequestId, invalid_data_fmt, read_msg_text, write_msg_text, ExtractError, LspOrDapResponse, LspResult,
ResponseError, RequestId, ResponseError,
}; };
/// A message in the Language Server Protocol. /// A message in the Language Server Protocol.
@ -134,26 +134,19 @@ pub struct Response {
} }
impl Response { impl Response {
/// Creates a response with the success payload. /// Creates a response
pub fn new_ok<R: serde::Serialize>(id: RequestId, result: R) -> Response { pub fn new(id: RequestId, result: LspResult<serde_json::Value>) -> Response {
Response { match result {
Ok(result) => Response {
id, id,
result: Some(serde_json::to_value(result).unwrap()), result: Some(result),
error: None, error: None,
} },
} Err(err) => Response {
/// Creates a response with the failure reason.
pub fn new_err(id: RequestId, code: i32, message: String) -> Response {
let error = ResponseError {
code,
message,
data: None,
};
Response {
id, id,
result: None, result: None,
error: Some(error), error: Some(err),
},
} }
} }
} }

View file

@ -185,21 +185,17 @@ pub enum MessageKind {
/// Gets the kind of the message. /// Gets the kind of the message.
pub trait GetMessageKind { pub trait GetMessageKind {
/// Returns the kind of the message. /// Returns the kind of the message.
fn get_message_kind() -> MessageKind; const MESSAGE_KIND: MessageKind;
} }
#[cfg(feature = "lsp")] #[cfg(feature = "lsp")]
impl GetMessageKind for LspMessage { impl GetMessageKind for LspMessage {
fn get_message_kind() -> MessageKind { const MESSAGE_KIND: MessageKind = MessageKind::Lsp;
MessageKind::Lsp
}
} }
#[cfg(feature = "dap")] #[cfg(feature = "dap")]
impl GetMessageKind for DapMessage { impl GetMessageKind for DapMessage {
fn get_message_kind() -> MessageKind { const MESSAGE_KIND: MessageKind = MessageKind::Dap;
MessageKind::Dap
}
} }
#[allow(unused)] #[allow(unused)]

View file

@ -11,6 +11,8 @@ use std::any::Any;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::pin::Pin; use std::pin::Pin;
#[cfg(feature = "web")]
use std::sync::atomic::AtomicU32;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use futures::future::MaybeDone; use futures::future::MaybeDone;
@ -19,8 +21,6 @@ use serde::Serialize;
use serde_json::{from_value, Value as JsonValue}; use serde_json::{from_value, Value as JsonValue};
use tinymist_std::time::Instant; use tinymist_std::time::Instant;
#[cfg(feature = "lsp")]
use crate::lsp::{Notification, Request};
use crate::msg::*; use crate::msg::*;
use crate::req_queue; use crate::req_queue;
use crate::*; use crate::*;
@ -35,6 +35,8 @@ pub type LspResponseFuture<T> = LspResult<ResponseFuture<T>>;
pub type SchedulableResponse<T> = LspResponseFuture<LspResult<T>>; pub type SchedulableResponse<T> = LspResponseFuture<LspResult<T>>;
/// The common response future type for language servers. /// The common response future type for language servers.
pub type AnySchedulableResponse = SchedulableResponse<JsonValue>; pub type AnySchedulableResponse = SchedulableResponse<JsonValue>;
/// The result of a scheduling response
pub type ScheduleResult = AnySchedulableResponse;
/// The result of a scheduled response which could be finally caught by /// The result of a scheduled response which could be finally caught by
/// `schedule_tail`. /// `schedule_tail`.
/// - Returns Ok(Some()) -> Already responded /// - Returns Ok(Some()) -> Already responded
@ -152,15 +154,7 @@ impl<S: 'static> TypedLspClient<S> {
/// Sends a event to the client itself. /// Sends a event to the client itself.
pub fn send_event<T: std::any::Any + Send + 'static>(&self, event: T) { pub fn send_event<T: std::any::Any + Send + 'static>(&self, event: T) {
let Some(sender) = self.sender.upgrade() else { self.sender.send_event(event);
log::warn!("failed to send request: connection closed");
return;
};
let Err(res) = sender.event.send(Box::new(event)) else {
return;
};
log::warn!("failed to send event: {res:?}");
} }
} }
@ -181,6 +175,8 @@ impl<S> std::ops::Deref for TypedLspClient<S> {
} }
} }
// send_request: Function,
// send_notification: Function,
/// The root of the language server host. /// The root of the language server host.
/// Will close connection when dropped. /// Will close connection when dropped.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -198,8 +194,27 @@ impl LspClientRoot {
let _strong = Arc::new(sender.into()); let _strong = Arc::new(sender.into());
let weak = LspClient { let weak = LspClient {
handle, handle,
msg_kind: M::get_message_kind(), msg_kind: M::MESSAGE_KIND,
sender: TransportHost::System(SystemTransportSender {
sender: Arc::downgrade(&_strong), sender: Arc::downgrade(&_strong),
}),
req_queue: Arc::new(Mutex::new(ReqQueue::default())),
hook: Arc::new(()),
};
Self { weak, _strong }
}
/// Creates a new language server host from js.
#[cfg(feature = "web")]
pub fn new_js(handle: tokio::runtime::Handle, transport: JsTransportSender) -> Self {
let dummy = dummy_transport::<LspMessage>();
let _strong = Arc::new(dummy.sender.into());
let weak = LspClient {
handle,
msg_kind: LspMessage::MESSAGE_KIND,
sender: TransportHost::Js(transport),
req_queue: Arc::new(Mutex::new(ReqQueue::default())), req_queue: Arc::new(Mutex::new(ReqQueue::default())),
hook: Arc::new(()), hook: Arc::new(()),
@ -222,6 +237,166 @@ impl LspClientRoot {
type ReqHandler = Box<dyn for<'a> FnOnce(&'a mut dyn Any, LspOrDapResponse) + Send + Sync>; type ReqHandler = Box<dyn for<'a> FnOnce(&'a mut dyn Any, LspOrDapResponse) + Send + Sync>;
type ReqQueue = req_queue::ReqQueue<(String, Instant), ReqHandler>; type ReqQueue = req_queue::ReqQueue<(String, Instant), ReqHandler>;
#[derive(Debug, Clone)]
enum TransportHost {
System(SystemTransportSender),
#[cfg(feature = "web")]
Js(JsTransportSender),
}
#[derive(Debug, Clone)]
struct SystemTransportSender {
pub(crate) sender: Weak<ConnectionTx>,
}
/// Creates a new js transport host.
#[cfg(feature = "web")]
#[derive(Debug, Clone)]
pub struct JsTransportSender {
event_id: Arc<AtomicU32>,
events: Arc<Mutex<HashMap<u32, Event>>>,
pub(crate) sender_event: js_sys::Function,
pub(crate) sender_request: js_sys::Function,
pub(crate) sender_notification: js_sys::Function,
}
#[cfg(feature = "web")]
impl JsTransportSender {
/// Creates a new JS transport host.
pub fn new(
sender_event: js_sys::Function,
sender_request: js_sys::Function,
sender_notification: js_sys::Function,
) -> Self {
Self {
event_id: Arc::new(AtomicU32::new(0)),
events: Arc::new(Mutex::new(HashMap::new())),
sender_event,
sender_request,
sender_notification,
}
}
}
#[cfg(feature = "web")]
/// SAFETY:
/// This is only safe if the `JsTransportHost` is used in a single thread.
unsafe impl Send for TransportHost {}
#[cfg(feature = "web")]
/// SAFETY:
/// This is only safe if the `JsTransportHost` is used in a single thread.
unsafe impl Sync for TransportHost {}
impl TransportHost {
/// Sends a event to the server itself.
pub fn send_event<T: std::any::Any + Send + 'static>(&self, event: T) {
match self {
TransportHost::System(host) => {
let Some(sender) = host.sender.upgrade() else {
log::warn!("failed to send request: connection closed");
return;
};
if let Err(res) = sender.event.send(Box::new(event)) {
log::warn!("failed to send event: {res:?}");
}
}
#[cfg(feature = "web")]
TransportHost::Js(host) => {
let event_id = {
let event_id = host
.event_id
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
let mut lg = host.events.lock();
lg.insert(event_id, Box::new(event));
js_sys::Number::from(event_id)
};
if let Err(err) = host
.sender_event
.call1(&wasm_bindgen::JsValue::UNDEFINED, &event_id.into())
{
log::error!("failed to send event: {err:?}");
}
}
}
}
pub fn send_message(&self, response: Message) {
match self {
TransportHost::System(host) => {
let Some(sender) = host.sender.upgrade() else {
log::warn!("failed to send response: connection closed");
return;
};
if let Err(res) = sender.lsp.send(response) {
log::warn!("failed to send response: {res:?}");
}
}
#[cfg(feature = "web")]
TransportHost::Js(host) => match response {
#[cfg(feature = "lsp")]
Message::Lsp(lsp::Message::Request(req)) => {
let msg = to_js_value(&req).expect("failed to serialize request to js value");
if let Err(err) = host
.sender_request
.call1(&wasm_bindgen::JsValue::UNDEFINED, &msg)
{
log::error!("failed to send request: {err:?}");
}
}
#[cfg(feature = "lsp")]
Message::Lsp(lsp::Message::Notification(req)) => {
let msg = to_js_value(&req).expect("failed to serialize request to js value");
if let Err(err) = host
.sender_notification
.call1(&wasm_bindgen::JsValue::UNDEFINED, &msg)
{
log::error!("failed to send request: {err:?}");
}
}
#[cfg(feature = "lsp")]
Message::Lsp(lsp::Message::Response(req)) => {
panic!("unexpected response to js world: {req:?}");
}
#[cfg(feature = "dap")]
Message::Dap(dap::Message::Request(req)) => {
let msg = to_js_value(&req).expect("failed to serialize request to js value");
if let Err(err) = host
.sender_request
.call1(&wasm_bindgen::JsValue::UNDEFINED, &msg)
{
log::error!("failed to send request: {err:?}");
}
}
#[cfg(feature = "dap")]
Message::Dap(dap::Message::Event(req)) => {
let msg = to_js_value(&req).expect("failed to serialize request to js value");
if let Err(err) = host
.sender_notification
.call1(&wasm_bindgen::JsValue::UNDEFINED, &msg)
{
log::error!("failed to send request: {err:?}");
}
}
#[cfg(feature = "dap")]
Message::Dap(dap::Message::Response(req)) => {
panic!("unexpected response to js world: {req:?}");
}
},
}
}
}
// todo: poor performance, struct -> serde_json -> serde_wasm_bindgen ->
// serialize -> deserialize??
#[cfg(feature = "web")]
fn to_js_value<T: serde::Serialize>(
value: &T,
) -> Result<wasm_bindgen::JsValue, serde_wasm_bindgen::Error> {
value.serialize(&serde_wasm_bindgen::Serializer::new().serialize_maps_as_objects(true))
}
/// The host for the language server, or known as the LSP client. /// The host for the language server, or known as the LSP client.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct LspClient { pub struct LspClient {
@ -229,7 +404,7 @@ pub struct LspClient {
pub handle: tokio::runtime::Handle, pub handle: tokio::runtime::Handle,
pub(crate) msg_kind: MessageKind, pub(crate) msg_kind: MessageKind,
pub(crate) sender: Weak<ConnectionTx>, sender: TransportHost,
pub(crate) req_queue: Arc<Mutex<ReqQueue>>, pub(crate) req_queue: Arc<Mutex<ReqQueue>>,
pub(crate) hook: Arc<dyn LsHook>, pub(crate) hook: Arc<dyn LsHook>,
@ -261,14 +436,7 @@ impl LspClient {
/// Sends a event to the server itself. /// Sends a event to the server itself.
pub fn send_event<T: std::any::Any + Send + 'static>(&self, event: T) { pub fn send_event<T: std::any::Any + Send + 'static>(&self, event: T) {
let Some(sender) = self.sender.upgrade() else { self.sender.send_event(event);
log::warn!("failed to send request: connection closed");
return;
};
if let Err(res) = sender.event.send(Box::new(event)) {
log::warn!("failed to send event: {res:?}");
}
} }
/// Completes an server2client request in the request queue. /// Completes an server2client request in the request queue.
@ -308,19 +476,11 @@ impl LspClient {
.register(id.clone(), (method.to_owned(), received_at)); .register(id.clone(), (method.to_owned(), received_at));
} }
/// Responds a typed result to the client. fn respond_result(&self, id: RequestId, result: LspResult<JsonValue>) {
pub fn respond_result<T: Serialize>(&self, id: RequestId, result: LspResult<T>) {
let result = result.and_then(|t| serde_json::to_value(t).map_err(internal_error));
self.respond_any_result(id, result);
}
fn respond_any_result(&self, id: RequestId, result: LspResult<JsonValue>) {
let req_id = id.clone(); let req_id = id.clone();
let msg: Message = match (self.msg_kind, result) { let msg: Message = match (self.msg_kind, result) {
#[cfg(feature = "lsp")] #[cfg(feature = "lsp")]
(MessageKind::Lsp, Ok(resp)) => lsp::Response::new_ok(id, resp).into(), (MessageKind::Lsp, res) => lsp::Response::new(id, res).into(),
#[cfg(feature = "lsp")]
(MessageKind::Lsp, Err(e)) => lsp::Response::new_err(id, e.code, e.message).into(),
#[cfg(feature = "dap")] #[cfg(feature = "dap")]
(MessageKind::Dap, Ok(resp)) => dap::Response::success(RequestId::dap(id), resp).into(), (MessageKind::Dap, Ok(resp)) => dap::Response::success(RequestId::dap(id), resp).into(),
#[cfg(feature = "dap")] #[cfg(feature = "dap")]
@ -341,53 +501,28 @@ impl LspClient {
self.hook.stop_request(&id, &method, received_at); self.hook.stop_request(&id, &method, received_at);
let Some(sender) = self.sender.upgrade() else { self.sender.send_message(response);
log::warn!("failed to send response ({method}, {id}): connection closed");
return;
};
if let Err(res) = sender.lsp.send(response) {
log::warn!("failed to send response ({method}, {id}): {res:?}");
}
} }
} }
impl LspClient { impl LspClient {
/// Schedules a request from the client.
pub fn schedule<T: Serialize + 'static>(
&self,
req_id: RequestId,
resp: SchedulableResponse<T>,
) -> ScheduledResult {
let resp = resp?;
use futures::future::MaybeDone::*;
match resp {
Done(output) => {
self.respond_result(req_id, output);
}
Future(fut) => {
let client = self.clone();
let req_id = req_id.clone();
self.handle.spawn(async move {
client.respond_result(req_id, fut.await);
});
}
Gone => {
log::warn!("response for request({req_id:?}) already taken");
}
};
Ok(Some(()))
}
/// Finally sends the response if it is not sent before. /// Finally sends the response if it is not sent before.
/// From the definition, the response is already sent if it is `Some(())`. /// From the definition, the response is already sent if it is `Some(())`.
pub fn schedule_tail(&self, req_id: RequestId, resp: ScheduledResult) { pub async fn schedule_tail(self, req_id: RequestId, resp: ScheduleResult) {
match resp { match resp {
// Already responded Ok(MaybeDone::Done(result)) => {
Ok(Some(())) => {} self.respond_result(req_id, result);
// The requests that doesn't start. }
_ => self.respond_result(req_id, resp), Ok(MaybeDone::Future(result)) => {
self.respond_result(req_id, result.await);
}
Ok(MaybeDone::Gone) => {
log::warn!("response for request({req_id:?}) already taken");
self.respond_result(req_id, Err(internal_error("response already taken")));
}
Err(err) => {
self.respond_result(req_id, Err(err));
}
} }
} }
} }
@ -429,9 +564,9 @@ impl LsHook for () {
} }
type AsyncHandler<S, T, R> = fn(srv: &mut S, args: T) -> SchedulableResponse<R>; type AsyncHandler<S, T, R> = fn(srv: &mut S, args: T) -> SchedulableResponse<R>;
type RawHandler<S, T> = fn(srv: &mut S, req_id: RequestId, args: T) -> ScheduledResult; type RawHandler<S, T> = fn(srv: &mut S, args: T) -> ScheduleResult;
type BoxPureHandler<S, T> = Box<dyn Fn(&mut S, T) -> LspResult<()>>; type BoxPureHandler<S, T> = Box<dyn Fn(&mut S, T) -> LspResult<()>>;
type BoxHandler<S, T> = Box<dyn Fn(&mut S, &LspClient, RequestId, T) -> ScheduledResult>; type BoxHandler<S, T> = Box<dyn Fn(&mut S, T) -> SchedulableResponse<JsonValue>>;
type ExecuteCmdMap<S> = HashMap<&'static str, BoxHandler<S, Vec<JsonValue>>>; type ExecuteCmdMap<S> = HashMap<&'static str, BoxHandler<S, Vec<JsonValue>>>;
type RegularCmdMap<S> = HashMap<&'static str, BoxHandler<S, JsonValue>>; type RegularCmdMap<S> = HashMap<&'static str, BoxHandler<S, JsonValue>>;
type NotifyCmdMap<S> = HashMap<&'static str, BoxPureHandler<S, JsonValue>>; type NotifyCmdMap<S> = HashMap<&'static str, BoxPureHandler<S, JsonValue>>;
@ -510,26 +645,14 @@ where
self self
} }
/// Registers a raw resource handler.
pub fn with_resource_(
mut self,
path: ImmutPath,
handler: RawHandler<Args::S, Vec<JsonValue>>,
) -> Self {
self.resource_handlers.insert(path, raw_to_boxed(handler));
self
}
/// Registers an async resource handler. /// Registers an async resource handler.
pub fn with_resource( pub fn with_resource(
mut self, mut self,
path: &'static str, path: &'static str,
handler: fn(&mut Args::S, Vec<JsonValue>) -> AnySchedulableResponse, handler: fn(&mut Args::S, Vec<JsonValue>) -> AnySchedulableResponse,
) -> Self { ) -> Self {
self.resource_handlers.insert( self.resource_handlers
Path::new(path).into(), .insert(Path::new(path).into(), Box::new(handler));
Box::new(move |s, client, req_id, req| client.schedule(req_id, handler(s, req))),
);
self self
} }
@ -639,7 +762,7 @@ impl<M, Args: Initializer> LsDriver<M, Args> {
/// Get static resources with help of tinymist service, for example, a /// Get static resources with help of tinymist service, for example, a
/// static help pages for some typst function. /// static help pages for some typst function.
pub fn get_resources(&mut self, req_id: RequestId, args: Vec<JsonValue>) -> ScheduledResult { pub fn get_resources(&mut self, args: Vec<JsonValue>) -> ScheduleResult {
let s = self.state.opt_mut().ok_or_else(not_initialized)?; let s = self.state.opt_mut().ok_or_else(not_initialized)?;
let path = let path =
@ -651,7 +774,7 @@ impl<M, Args: Initializer> LsDriver<M, Args> {
}; };
// Note our redirection will keep the first path argument in the args vec. // Note our redirection will keep the first path argument in the args vec.
handler(s, &self.client, req_id, args) handler(s, args)
} }
} }
@ -701,8 +824,24 @@ fn from_json<T: serde::de::DeserializeOwned>(json: JsonValue) -> LspResult<T> {
serde_json::from_value(json).map_err(invalid_request) serde_json::from_value(json).map_err(invalid_request)
} }
fn raw_to_boxed<S: 'static, T: 'static>(handler: RawHandler<S, T>) -> BoxHandler<S, T> { /// Erases the response type to a generic `JsonValue`.
Box::new(move |s, _client, req_id, req| handler(s, req_id, req)) pub fn erased_response<T: Serialize + 'static>(resp: SchedulableResponse<T>) -> ScheduleResult {
/// Responds a typed result to the client.
fn map_respond_result<T: Serialize>(result: LspResult<T>) -> LspResult<JsonValue> {
result.and_then(|t| serde_json::to_value(t).map_err(internal_error))
}
let resp = resp?;
use futures::future::MaybeDone::*;
Ok(match resp {
Done(result) => MaybeDone::Done(map_respond_result(result)),
Future(fut) => MaybeDone::Future(Box::pin(async move { map_respond_result(fut.await) })),
Gone => {
log::warn!("response already taken");
MaybeDone::Done(Err(internal_error("response already taken")))
}
})
} }
fn resp_err(code: ErrorCode, msg: impl fmt::Display) -> ResponseError { fn resp_err(code: ErrorCode, msg: impl fmt::Display) -> ResponseError {

View file

@ -12,14 +12,7 @@ impl<S: 'static> TypedLspClient<S> {
/// Sends an untyped dap_event to the client. /// Sends an untyped dap_event to the client.
pub fn send_dap_event_(&self, evt: dap::Event) { pub fn send_dap_event_(&self, evt: dap::Event) {
let method = &evt.event; self.sender.send_message(evt.into());
let Some(sender) = self.sender.upgrade() else {
log::warn!("failed to send dap event ({method}): connection closed");
return;
};
if let Err(res) = sender.lsp.send(evt.into()) {
log::warn!("failed to send dap event: {res:?}");
}
} }
} }
@ -27,16 +20,6 @@ impl<Args: Initializer> LsBuilder<DapMessage, Args>
where where
Args::S: 'static, Args::S: 'static,
{ {
/// Registers an raw event handler.
pub fn with_command_(
mut self,
cmd: &'static str,
handler: RawHandler<Args::S, Vec<JsonValue>>,
) -> Self {
self.command_handlers.insert(cmd, raw_to_boxed(handler));
self
}
/// Registers an async command handler. /// Registers an async command handler.
pub fn with_command<R: Serialize + 'static>( pub fn with_command<R: Serialize + 'static>(
mut self, mut self,
@ -45,7 +28,7 @@ where
) -> Self { ) -> Self {
self.command_handlers.insert( self.command_handlers.insert(
cmd, cmd,
Box::new(move |s, client, req_id, req| client.schedule(req_id, handler(s, req))), Box::new(move |s, req| erased_response(handler(s, req))),
); );
self self
} }
@ -56,7 +39,7 @@ where
mut self, mut self,
handler: RawHandler<Args::S, JsonValue>, handler: RawHandler<Args::S, JsonValue>,
) -> Self { ) -> Self {
self.req_handlers.insert(R::COMMAND, raw_to_boxed(handler)); self.req_handlers.insert(R::COMMAND, Box::new(handler));
self self
} }
@ -65,11 +48,11 @@ where
/// request. /// request.
pub fn with_request_<R: dapts::IRequest>( pub fn with_request_<R: dapts::IRequest>(
mut self, mut self,
handler: fn(&mut Args::S, RequestId, R::Arguments) -> ScheduledResult, handler: fn(&mut Args::S, R::Arguments) -> ScheduleResult,
) -> Self { ) -> Self {
self.req_handlers.insert( self.req_handlers.insert(
R::COMMAND, R::COMMAND,
Box::new(move |s, _client, req_id, req| handler(s, req_id, from_json(req)?)), Box::new(move |s, req| handler(s, from_json(req)?)),
); );
self self
} }
@ -81,9 +64,7 @@ where
) -> Self { ) -> Self {
self.req_handlers.insert( self.req_handlers.insert(
R::COMMAND, R::COMMAND,
Box::new(move |s, client, req_id, req| { Box::new(move |s, req| erased_response(handler(s, from_json(req)?))),
client.schedule(req_id, handler(s, from_json(req)?))
}),
); );
self self
} }
@ -100,6 +81,7 @@ where
/// ///
/// See [`transport::MirrorArgs`] for information about the record-replay /// See [`transport::MirrorArgs`] for information about the record-replay
/// feature. /// feature.
#[cfg(feature = "system")]
pub fn start( pub fn start(
&mut self, &mut self,
inbox: TConnectionRx<DapMessage>, inbox: TConnectionRx<DapMessage>,
@ -133,6 +115,7 @@ where
} }
/// Starts the debug adaptor on the given connection. /// Starts the debug adaptor on the given connection.
#[cfg(feature = "system")]
pub fn start_(&mut self, inbox: TConnectionRx<DapMessage>) -> anyhow::Result<()> { pub fn start_(&mut self, inbox: TConnectionRx<DapMessage>) -> anyhow::Result<()> {
use EventOrMessage::*; use EventOrMessage::*;
@ -156,7 +139,13 @@ where
event_handler(s, &self.client, event)?; event_handler(s, &self.client, event)?;
} }
Msg(DapMessage::Request(req)) => self.on_request(loop_start, req), Msg(DapMessage::Request(req)) => {
let client = self.client.clone();
let req_id = (req.seq as i32).into();
client.register_request(&req.command, &req_id, loop_start);
let fut = client.schedule_tail(req_id, self.on_request(req));
self.client.handle.spawn(fut);
}
Msg(DapMessage::Event(not)) => { Msg(DapMessage::Event(not)) => {
self.on_event(loop_start, not)?; self.on_event(loop_start, not)?;
} }
@ -180,12 +169,8 @@ where
/// Registers and handles a request. This should only be called once per /// Registers and handles a request. This should only be called once per
/// incoming request. /// incoming request.
fn on_request(&mut self, request_received: Instant, req: dap::Request) { fn on_request(&mut self, req: dap::Request) -> ScheduleResult {
let req_id = (req.seq as i32).into(); match (&mut self.state, &*req.command) {
self.client
.register_request(&req.command, &req_id, request_received);
let resp = match (&mut self.state, &*req.command) {
(State::Uninitialized(args), dapts::request::Initialize::COMMAND) => { (State::Uninitialized(args), dapts::request::Initialize::COMMAND) => {
// todo: what will happen if the request cannot be deserialized? // todo: what will happen if the request cannot be deserialized?
let params = serde_json::from_value::<Args::I>(req.arguments); let params = serde_json::from_value::<Args::I>(req.arguments);
@ -240,22 +225,18 @@ where
break 'serve_req just_result(Err(method_not_found())); break 'serve_req just_result(Err(method_not_found()));
}; };
let result = handler(s, &self.client, req_id.clone(), req.arguments); let resp = handler(s, req.arguments);
self.client.schedule_tail(req_id, result);
if is_disconnect { if is_disconnect {
self.state = State::ShuttingDown; self.state = State::ShuttingDown;
} }
return; resp
} }
(State::ShuttingDown, _) => { (State::ShuttingDown, _) => {
just_result(Err(invalid_request("server is shutting down"))) just_result(Err(invalid_request("server is shutting down")))
} }
}; }
let result = self.client.schedule(req_id.clone(), resp);
self.client.schedule_tail(req_id, result);
} }
/// Handles an incoming event. /// Handles an incoming event.

View file

@ -4,19 +4,6 @@ use lsp_types::{notification::Notification as Notif, request::Request as Req, *}
type PureHandler<S, T> = fn(srv: &mut S, args: T) -> LspResult<()>; type PureHandler<S, T> = fn(srv: &mut S, args: T) -> LspResult<()>;
/// Converts a `ScheduledResult` to a `SchedulableResponse`.
macro_rules! reschedule {
($expr:expr) => {
match $expr {
Ok(Some(())) => return,
Ok(None) => Ok(futures::future::MaybeDone::Done(Ok(
serde_json::Value::Null,
))),
Err(e) => Err(e),
}
};
}
impl<S: 'static> TypedLspClient<S> { impl<S: 'static> TypedLspClient<S> {
/// Sends a request to the client and registers a handler handled by the /// Sends a request to the client and registers a handler handled by the
/// service `S`. /// service `S`.
@ -45,13 +32,7 @@ impl LspClient {
Box::new(|s, resp| handler(s, resp.try_into().unwrap())), Box::new(|s, resp| handler(s, resp.try_into().unwrap())),
); );
let Some(sender) = self.sender.upgrade() else { self.sender.send_message(request.into());
log::warn!("failed to send request: connection closed");
return;
};
if let Err(res) = sender.lsp.send(request.into()) {
log::warn!("failed to send request: {res:?}");
}
} }
/// Completes an client2server request in the request queue. /// Completes an client2server request in the request queue.
@ -66,14 +47,7 @@ impl LspClient {
/// Sends an untyped notification to the client. /// Sends an untyped notification to the client.
pub fn send_notification_(&self, notif: lsp::Notification) { pub fn send_notification_(&self, notif: lsp::Notification) {
let method = &notif.method; self.sender.send_message(notif.into());
let Some(sender) = self.sender.upgrade() else {
log::warn!("failed to send notification ({method}): connection closed");
return;
};
if let Err(res) = sender.lsp.send(notif.into()) {
log::warn!("failed to send notification: {res:?}");
}
} }
} }
@ -87,7 +61,7 @@ where
cmd: &'static str, cmd: &'static str,
handler: RawHandler<Args::S, Vec<JsonValue>>, handler: RawHandler<Args::S, Vec<JsonValue>>,
) -> Self { ) -> Self {
self.command_handlers.insert(cmd, raw_to_boxed(handler)); self.command_handlers.insert(cmd, Box::new(handler));
self self
} }
@ -99,7 +73,7 @@ where
) -> Self { ) -> Self {
self.command_handlers.insert( self.command_handlers.insert(
cmd, cmd,
Box::new(move |s, client, req_id, req| client.schedule(req_id, handler(s, req))), Box::new(move |s, req| erased_response(handler(s, req))),
); );
self self
} }
@ -125,7 +99,7 @@ where
/// Registers a raw request handler that handlers a kind of untyped lsp /// Registers a raw request handler that handlers a kind of untyped lsp
/// request. /// request.
pub fn with_raw_request<R: Req>(mut self, handler: RawHandler<Args::S, JsonValue>) -> Self { pub fn with_raw_request<R: Req>(mut self, handler: RawHandler<Args::S, JsonValue>) -> Self {
self.req_handlers.insert(R::METHOD, raw_to_boxed(handler)); self.req_handlers.insert(R::METHOD, Box::new(handler));
self self
} }
@ -134,11 +108,11 @@ where
/// request. /// request.
pub fn with_request_<R: Req>( pub fn with_request_<R: Req>(
mut self, mut self,
handler: fn(&mut Args::S, RequestId, R::Params) -> ScheduledResult, handler: fn(&mut Args::S, R::Params) -> ScheduleResult,
) -> Self { ) -> Self {
self.req_handlers.insert( self.req_handlers.insert(
R::METHOD, R::METHOD,
Box::new(move |s, _client, req_id, req| handler(s, req_id, from_json(req)?)), Box::new(move |s, req| handler(s, from_json(req)?)),
); );
self self
} }
@ -150,9 +124,7 @@ where
) -> Self { ) -> Self {
self.req_handlers.insert( self.req_handlers.insert(
R::METHOD, R::METHOD,
Box::new(move |s, client, req_id, req| { Box::new(move |s, req| erased_response(handler(s, from_json(req)?))),
client.schedule(req_id, handler(s, from_json(req)?))
}),
); );
self self
} }
@ -247,10 +219,21 @@ where
event_handler(s, &self.client, event)?; event_handler(s, &self.client, event)?;
} }
Msg(LspMessage::Request(req)) => self.on_lsp_request(loop_start, req), Msg(LspMessage::Request(req)) => {
let client = self.client.clone();
let req_id = req.id.clone();
client.register_request(&req.method, &req_id, loop_start);
let fut =
client.schedule_tail(req_id, self.on_lsp_request(&req.method, req.params));
self.client.handle.spawn(fut);
}
Msg(LspMessage::Notification(not)) => { Msg(LspMessage::Notification(not)) => {
let is_exit = not.method == EXIT_METHOD; let is_exit = not.method == EXIT_METHOD;
self.on_notification(loop_start, not)?; self.client.hook.start_notification(&not.method);
let result = self.on_notification(&not.method, not.params);
self.client
.hook
.stop_notification(&not.method, loop_start, result);
if is_exit { if is_exit {
return Ok(()); return Ok(());
} }
@ -273,17 +256,45 @@ where
Ok(()) Ok(())
} }
/// Handles an incoming server event.
#[cfg(feature = "web")]
pub fn on_server_event(&mut self, event_id: u32) {
let evt = match &self.client.sender {
TransportHost::Js(sender) => sender.events.lock().remove(&event_id),
TransportHost::System(_) => {
panic!("cannot send server event in system transport");
}
};
if let Some(event) = evt {
let Some(event_handler) = self.events.get(&event.as_ref().type_id()) else {
log::warn!("unhandled event: {:?}", event.as_ref().type_id());
return;
};
let s = match &mut self.state {
State::Uninitialized(u) => ServiceState::Uninitialized(u.as_deref_mut()),
State::Initializing(s) | State::Ready(s) => ServiceState::Ready(s),
State::ShuttingDown => {
log::warn!("server is shutting down");
return;
}
};
let res = event_handler(s, &self.client, event);
if let Err(err) = res {
log::error!("failed to handle server event {event_id}: {err}");
}
}
}
/// 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.
pub fn on_lsp_request(&mut self, request_received: Instant, req: Request) { pub fn on_lsp_request(&mut self, method: &str, params: JsonValue) -> ScheduleResult {
self.client match (&mut self.state, method) {
.register_request(&req.method, &req.id, request_received);
let req_id = req.id.clone();
let resp = match (&mut self.state, &*req.method) {
(State::Uninitialized(args), request::Initialize::METHOD) => { (State::Uninitialized(args), request::Initialize::METHOD) => {
// todo: what will happen if the request cannot be deserialized? // todo: what will happen if the request cannot be deserialized?
let params = serde_json::from_value::<Args::I>(req.params); let params = serde_json::from_value::<Args::I>(params);
match params { match params {
Ok(params) => { Ok(params) => {
let args = args.take().expect("already initialized"); let args = args.take().expect("already initialized");
@ -301,11 +312,8 @@ where
just_result(Err(invalid_request("server is already initialized"))) just_result(Err(invalid_request("server is already initialized")))
} }
// todo: generalize this // todo: generalize this
(State::Ready(..), request::ExecuteCommand::METHOD) => { (State::Ready(..), request::ExecuteCommand::METHOD) => self.on_execute_command(params),
reschedule!(self.on_execute_command(req)) (State::Ready(s), method) => 'serve_req: {
}
(State::Ready(s), _) => 'serve_req: {
let method = req.method.as_str();
let is_shutdown = method == request::Shutdown::METHOD; let is_shutdown = method == request::Shutdown::METHOD;
let Some(handler) = self.requests.get(method) else { let Some(handler) = self.requests.get(method) else {
@ -313,29 +321,25 @@ where
break 'serve_req just_result(Err(method_not_found())); break 'serve_req just_result(Err(method_not_found()));
}; };
let result = handler(s, &self.client, req_id.clone(), req.params); let resp = handler(s, params);
self.client.schedule_tail(req_id, result);
if is_shutdown { if is_shutdown {
self.state = State::ShuttingDown; self.state = State::ShuttingDown;
} }
return; resp
} }
(State::ShuttingDown, _) => { (State::ShuttingDown, _) => {
just_result(Err(invalid_request("server is shutting down"))) just_result(Err(invalid_request("server is shutting down")))
} }
}; }
let result = self.client.schedule(req_id.clone(), resp);
self.client.schedule_tail(req_id, result);
} }
/// The entry point for the `workspace/executeCommand` request. /// The entry point for the `workspace/executeCommand` request.
fn on_execute_command(&mut self, req: Request) -> ScheduledResult { fn on_execute_command(&mut self, params: JsonValue) -> ScheduleResult {
let s = self.state.opt_mut().ok_or_else(not_initialized)?; let s = self.state.opt_mut().ok_or_else(not_initialized)?;
let params = from_value::<ExecuteCommandParams>(req.params) let params = from_value::<ExecuteCommandParams>(params)
.map_err(|e| invalid_params(e.to_string()))?; .map_err(|e| invalid_params(e.to_string()))?;
let ExecuteCommandParams { let ExecuteCommandParams {
@ -344,38 +348,28 @@ where
// todo: generalize this // todo: generalize this
if command == "tinymist.getResources" { if command == "tinymist.getResources" {
self.get_resources(req.id, arguments) self.get_resources(arguments)
} else { } else {
let Some(handler) = self.commands.get(command.as_str()) else { let Some(handler) = self.commands.get(command.as_str()) else {
log::error!("asked to execute unknown command: {command}"); log::error!("asked to execute unknown command: {command}");
return Err(method_not_found()); return Err(method_not_found());
}; };
handler(s, &self.client, req.id, arguments) handler(s, arguments)
} }
} }
/// Handles an incoming notification. /// Handles an incoming notification.
pub fn on_notification( pub fn on_notification(&mut self, method: &str, params: JsonValue) -> LspResult<()> {
&mut self, let handle = |s, method: &str, params: JsonValue| {
received_at: Instant, let Some(handler) = self.notifications.get(method) else {
not: Notification,
) -> anyhow::Result<()> {
self.client.hook.start_notification(&not.method);
let handle = |s, Notification { method, params }: Notification| {
let Some(handler) = self.notifications.get(method.as_str()) else {
log::warn!("unhandled notification: {method}"); log::warn!("unhandled notification: {method}");
return Ok(()); return Ok(());
}; };
let result = handler(s, params); handler(s, params)
self.client
.hook
.stop_notification(&method, received_at, result);
Ok(())
}; };
match (&mut self.state, &*not.method) { match (&mut self.state, method) {
(state, notification::Initialized::METHOD) => { (state, notification::Initialized::METHOD) => {
let mut s = State::ShuttingDown; let mut s = State::ShuttingDown;
std::mem::swap(state, &mut s); std::mem::swap(state, &mut s);
@ -395,9 +389,9 @@ where
return Ok(()); return Ok(());
} }
}; };
handle(s, not) handle(s, method, params)
} }
(State::Ready(state), _) => handle(state, not), (State::Ready(state), method) => handle(state, method, params),
// todo: whether it is safe to ignore notifications // todo: whether it is safe to ignore notifications
(State::Uninitialized(..) | State::Initializing(..), method) => { (State::Uninitialized(..) | State::Initializing(..), method) => {
log::warn!("server is not ready yet, while received notification {method}"); log::warn!("server is not ready yet, while received notification {method}");

View file

@ -46,7 +46,7 @@ pub fn with_stdio_transport<M: TryFrom<Message, Error = anyhow::Error> + GetMess
args: MirrorArgs, args: MirrorArgs,
f: impl FnOnce(Connection<M>) -> anyhow::Result<()>, f: impl FnOnce(Connection<M>) -> anyhow::Result<()>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
with_stdio_transport_impl(args, M::get_message_kind(), |conn| f(conn.into())) with_stdio_transport_impl(args, M::MESSAGE_KIND, |conn| f(conn.into()))
} }
/// Note that we must have our logging only write out to stderr. /// Note that we must have our logging only write out to stderr.

View file

@ -62,7 +62,7 @@ strum.workspace = true
sync-ls = { workspace = true, features = ["lsp", "server", "system"] } sync-ls = { workspace = true, features = ["lsp", "server", "system"] }
tinymist-assets = { workspace = true } tinymist-assets = { workspace = true }
tinymist-query.workspace = true tinymist-query.workspace = true
tinymist-std.workspace = true tinymist-std = { workspace = true, default-features = false }
tinymist = { workspace = true, default-features = false, features = ["system"] } tinymist = { workspace = true, default-features = false, features = ["system"] }
tinymist-project = { workspace = true, features = ["lsp"] } tinymist-project = { workspace = true, features = ["lsp"] }
tinymist-render.workspace = true tinymist-render.workspace = true

View file

@ -21,7 +21,7 @@
"scripts": { "scripts": {
"build:dev": "wasm-pack build --target web --dev -- --no-default-features --features web,no-content-hint", "build:dev": "wasm-pack build --target web --dev -- --no-default-features --features web,no-content-hint",
"build:node": "wasm-pack build --target nodejs -- --no-default-features --features web,no-content-hint", "build:node": "wasm-pack build --target nodejs -- --no-default-features --features web,no-content-hint",
"build": "wasm-pack build --target web -- --no-default-features --features web,no-content-hint", "build": "wasm-pack build --target web --dev -- --no-default-features --features web,no-content-hint",
"publish:dry": "npm publish --dry-run", "publish:dry": "npm publish --dry-run",
"publish:lib": "npm publish || exit 0", "publish:lib": "npm publish || exit 0",
"test:chrome": "wasm-pack test --chrome --headless --release", "test:chrome": "wasm-pack test --chrome --headless --release",

View file

@ -6,7 +6,6 @@ use std::path::PathBuf;
use lsp_types::TextDocumentIdentifier; use lsp_types::TextDocumentIdentifier;
use serde::Deserialize; use serde::Deserialize;
use serde_json::Value as JsonValue; use serde_json::Value as JsonValue;
use sync_ls::RequestId;
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
use task::TraceParams; use task::TraceParams;
use tinymist_assets::TYPST_PREVIEW_HTML; use tinymist_assets::TYPST_PREVIEW_HTML;
@ -23,7 +22,7 @@ use typst::syntax::{LinkedNode, Source};
use world::TaskInputs; use world::TaskInputs;
use super::*; use super::*;
use crate::lsp::query::{run_query, LspClientExt}; use crate::lsp::query::run_query;
use crate::tool::ast::AstRepr; use crate::tool::ast::AstRepr;
#[cfg(feature = "system")] #[cfg(feature = "system")]
@ -86,7 +85,7 @@ struct ExportSyntaxRangeOpts {
/// Here are implemented the handlers for each command. /// Here are implemented the handlers for each command.
impl ServerState { impl ServerState {
/// Export the current document as PDF file(s). /// Export the current document as PDF file(s).
pub fn export_pdf(&mut self, req_id: RequestId, mut args: Vec<JsonValue>) -> ScheduledResult { pub fn export_pdf(&mut self, mut args: Vec<JsonValue>) -> ScheduleResult {
let opts = get_arg_or_default!(args[1] as ExportOpts); let opts = get_arg_or_default!(args[1] as ExportOpts);
let creation_timestamp = if let Some(value) = opts.creation_timestamp { let creation_timestamp = if let Some(value) = opts.creation_timestamp {
@ -101,7 +100,6 @@ impl ServerState {
let export = self.config.export_task(); let export = self.config.export_task();
self.export( self.export(
req_id,
ProjectTask::ExportPdf(ExportPdfTask { ProjectTask::ExportPdf(ExportPdfTask {
export, export,
pdf_standards: pdf_standards.unwrap_or_default(), pdf_standards: pdf_standards.unwrap_or_default(),
@ -113,11 +111,10 @@ impl ServerState {
} }
/// Export the current document as HTML file(s). /// Export the current document as HTML file(s).
pub fn export_html(&mut self, req_id: RequestId, mut args: Vec<JsonValue>) -> ScheduledResult { pub fn export_html(&mut self, mut args: Vec<JsonValue>) -> ScheduleResult {
let opts = get_arg_or_default!(args[1] as ExportOpts); let opts = get_arg_or_default!(args[1] as ExportOpts);
let export = self.config.export_task(); let export = self.config.export_task();
self.export( self.export(
req_id,
ProjectTask::ExportHtml(ExportHtmlTask { export }), ProjectTask::ExportHtml(ExportHtmlTask { export }),
opts.open.unwrap_or_default(), opts.open.unwrap_or_default(),
args, args,
@ -125,15 +122,10 @@ impl ServerState {
} }
/// Export the current document as Markdown file(s). /// Export the current document as Markdown file(s).
pub fn export_markdown( pub fn export_markdown(&mut self, mut args: Vec<JsonValue>) -> ScheduleResult {
&mut self,
req_id: RequestId,
mut args: Vec<JsonValue>,
) -> ScheduledResult {
let opts = get_arg_or_default!(args[1] as ExportTypliteOpts); let opts = get_arg_or_default!(args[1] as ExportTypliteOpts);
let export = self.config.export_task(); let export = self.config.export_task();
self.export( self.export(
req_id,
ProjectTask::ExportMd(ExportMarkdownTask { ProjectTask::ExportMd(ExportMarkdownTask {
processor: opts.processor, processor: opts.processor,
assets_path: opts.assets_path, assets_path: opts.assets_path,
@ -145,11 +137,10 @@ impl ServerState {
} }
/// Export the current document as Tex file(s). /// Export the current document as Tex file(s).
pub fn export_tex(&mut self, req_id: RequestId, mut args: Vec<JsonValue>) -> ScheduledResult { pub fn export_tex(&mut self, mut args: Vec<JsonValue>) -> ScheduleResult {
let opts = get_arg_or_default!(args[1] as ExportTypliteOpts); let opts = get_arg_or_default!(args[1] as ExportTypliteOpts);
let export = self.config.export_task(); let export = self.config.export_task();
self.export( self.export(
req_id,
ProjectTask::ExportTeX(ExportTeXTask { ProjectTask::ExportTeX(ExportTeXTask {
processor: opts.processor, processor: opts.processor,
assets_path: opts.assets_path, assets_path: opts.assets_path,
@ -161,11 +152,10 @@ impl ServerState {
} }
/// Export the current document as Text file(s). /// Export the current document as Text file(s).
pub fn export_text(&mut self, req_id: RequestId, mut args: Vec<JsonValue>) -> ScheduledResult { pub fn export_text(&mut self, mut args: Vec<JsonValue>) -> ScheduleResult {
let opts = get_arg_or_default!(args[1] as ExportOpts); let opts = get_arg_or_default!(args[1] as ExportOpts);
let export = self.config.export_task(); let export = self.config.export_task();
self.export( self.export(
req_id,
ProjectTask::ExportText(ExportTextTask { export }), ProjectTask::ExportText(ExportTextTask { export }),
opts.open.unwrap_or_default(), opts.open.unwrap_or_default(),
args, args,
@ -173,7 +163,7 @@ impl ServerState {
} }
/// Query the current document and export the result as JSON file(s). /// Query the current document and export the result as JSON file(s).
pub fn export_query(&mut self, req_id: RequestId, mut args: Vec<JsonValue>) -> ScheduledResult { pub fn export_query(&mut self, mut args: Vec<JsonValue>) -> ScheduleResult {
let opts = get_arg_or_default!(args[1] as QueryOpts); let opts = get_arg_or_default!(args[1] as QueryOpts);
// todo: deprecate it // todo: deprecate it
let _ = opts.strict; let _ = opts.strict;
@ -184,7 +174,6 @@ impl ServerState {
} }
self.export( self.export(
req_id,
ProjectTask::Query(QueryTask { ProjectTask::Query(QueryTask {
format: opts.format, format: opts.format,
output_extension: opts.output_extension, output_extension: opts.output_extension,
@ -199,14 +188,13 @@ impl ServerState {
} }
/// Export the current document as Svg file(s). /// Export the current document as Svg file(s).
pub fn export_svg(&mut self, req_id: RequestId, mut args: Vec<JsonValue>) -> ScheduledResult { pub fn export_svg(&mut self, mut args: Vec<JsonValue>) -> ScheduleResult {
let opts = get_arg_or_default!(args[1] as ExportOpts); let opts = get_arg_or_default!(args[1] as ExportOpts);
let mut export = self.config.export_task(); let mut export = self.config.export_task();
select_page(&mut export, opts.page).map_err(invalid_params)?; select_page(&mut export, opts.page).map_err(invalid_params)?;
self.export( self.export(
req_id,
ProjectTask::ExportSvg(ExportSvgTask { export }), ProjectTask::ExportSvg(ExportSvgTask { export }),
opts.open.unwrap_or_default(), opts.open.unwrap_or_default(),
args, args,
@ -214,7 +202,7 @@ impl ServerState {
} }
/// Export the current document as Png file(s). /// Export the current document as Png file(s).
pub fn export_png(&mut self, req_id: RequestId, mut args: Vec<JsonValue>) -> ScheduledResult { pub fn export_png(&mut self, mut args: Vec<JsonValue>) -> ScheduleResult {
let opts = get_arg_or_default!(args[1] as ExportOpts); let opts = get_arg_or_default!(args[1] as ExportOpts);
let ppi = opts.ppi.unwrap_or(144.); let ppi = opts.ppi.unwrap_or(144.);
@ -227,7 +215,6 @@ impl ServerState {
select_page(&mut export, opts.page).map_err(invalid_params)?; select_page(&mut export, opts.page).map_err(invalid_params)?;
self.export( self.export(
req_id,
ProjectTask::ExportPng(ExportPngTask { ProjectTask::ExportPng(ExportPngTask {
fill: opts.fill, fill: opts.fill,
ppi, ppi,
@ -242,14 +229,14 @@ impl ServerState {
/// for passing the correct absolute path of typst document. /// for passing the correct absolute path of typst document.
pub fn export( pub fn export(
&mut self, &mut self,
req_id: RequestId,
task: ProjectTask, task: ProjectTask,
open: bool, open: bool,
mut args: Vec<JsonValue>, mut args: Vec<JsonValue>,
) -> ScheduledResult { ) -> ScheduleResult {
let path = get_arg!(args[0] as PathBuf); let path = get_arg!(args[0] as PathBuf);
run_query!(req_id, self.OnExport(path, open, task)) run_query!(self.OnExport(path, open, task))
} }
/// Export a range of the current document as Ansi highlighted text. /// Export a range of the current document as Ansi highlighted text.
@ -294,9 +281,7 @@ impl ServerState {
range: Option<LspRange>, range: Option<LspRange>,
f: impl Fn(Source, Option<Range<usize>>) -> LspResult<T>, f: impl Fn(Source, Option<Range<usize>>) -> LspResult<T>,
) -> LspResult<T> { ) -> LspResult<T> {
let s = self let s = self.query_source(path.into(), Ok)?;
.query_source(path.into(), Ok)
.map_err(|e| internal_error(format!("cannot find source: {e}")))?;
// todo: cannot select syntax-sensitive data well // todo: cannot select syntax-sensitive data well
// let node = LinkedNode::new(s.root()); // let node = LinkedNode::new(s.root());
@ -512,11 +497,7 @@ impl ServerState {
} }
/// Interact with the code context at the source file. /// Interact with the code context at the source file.
pub fn interact_code_context( pub fn interact_code_context(&mut self, _arguments: Vec<JsonValue>) -> ScheduleResult {
&mut self,
req_id: RequestId,
_arguments: Vec<JsonValue>,
) -> ScheduledResult {
let queries = _arguments.into_iter().next().ok_or_else(|| { let queries = _arguments.into_iter().next().ok_or_else(|| {
invalid_params("The first parameter is not a valid code context query array") invalid_params("The first parameter is not a valid code context query array")
})?; })?;
@ -533,7 +514,7 @@ impl ServerState {
let path = as_path(params.text_document); let path = as_path(params.text_document);
let query = params.query; let query = params.query;
run_query!(req_id, self.InteractCodeContext(path, query)) run_query!(self.InteractCodeContext(path, query))
} }
/// Get the trace data of the document. /// Get the trace data of the document.
@ -631,31 +612,19 @@ impl ServerState {
} }
/// Get the metrics of the document. /// Get the metrics of the document.
pub fn get_document_metrics( pub fn get_document_metrics(&mut self, mut args: Vec<JsonValue>) -> ScheduleResult {
&mut self,
req_id: RequestId,
mut args: Vec<JsonValue>,
) -> ScheduledResult {
let path = get_arg!(args[0] as PathBuf); let path = get_arg!(args[0] as PathBuf);
run_query!(req_id, self.DocumentMetrics(path)) run_query!(self.DocumentMetrics(path))
} }
/// Get all syntactic labels in workspace. /// Get all syntactic labels in workspace.
pub fn get_workspace_labels( pub fn get_workspace_labels(&mut self, _arguments: Vec<JsonValue>) -> ScheduleResult {
&mut self, run_query!(self.WorkspaceLabel())
req_id: RequestId,
_arguments: Vec<JsonValue>,
) -> ScheduledResult {
run_query!(req_id, self.WorkspaceLabel())
} }
/// Get the server info. /// Get the server info.
pub fn get_server_info( pub fn get_server_info(&mut self, _arguments: Vec<JsonValue>) -> ScheduleResult {
&mut self, run_query!(self.ServerInfo())
req_id: RequestId,
_arguments: Vec<JsonValue>,
) -> ScheduledResult {
run_query!(req_id, self.ServerInfo())
} }
} }

View file

@ -111,10 +111,10 @@ impl ServerState {
pub fn query_source<T>( pub fn query_source<T>(
&self, &self,
path: ImmutPath, path: ImmutPath,
f: impl FnOnce(Source) -> Result<T>, f: impl FnOnce(Source) -> LspResult<T>,
) -> Result<T> { ) -> LspResult<T> {
let snapshot = self.memory_changes.get(&path); let snapshot = self.memory_changes.get(&path);
let snapshot = snapshot.ok_or_else(|| anyhow::anyhow!("file missing {path:?}"))?; let snapshot = snapshot.ok_or_else(|| internal_error(format!("file missing {path:?}")))?;
let source = snapshot.clone(); let source = snapshot.clone();
f(source) f(source)
} }

View file

@ -91,10 +91,10 @@ impl Initializer for SuperInit {
} = self; } = self;
let const_config = config.const_config.clone(); let const_config = config.const_config.clone();
// Bootstrap server // Bootstrap server
let service = ServerState::main(client, config, err.is_none()); let state = ServerState::main(client, config, err.is_none());
if let Some(err) = err { if let Some(err) = err {
return (service, Err(err)); return (state, Err(err));
} }
let semantic_tokens_provider = (!const_config.tokens_dynamic_registration).then(|| { let semantic_tokens_provider = (!const_config.tokens_dynamic_registration).then(|| {
@ -209,6 +209,6 @@ impl Initializer for SuperInit {
}; };
let res = serde_json::to_value(res).map_err(|e| invalid_params(e.to_string())); let res = serde_json::to_value(res).map_err(|e| invalid_params(e.to_string()));
(service, just_result(res)) (state, just_result(res))
} }
} }

View file

@ -1,6 +1,5 @@
//! tinymist's language server //! tinymist's language server
use futures::future::MaybeDone;
use lsp_types::request::GotoDeclarationParams; use lsp_types::request::GotoDeclarationParams;
use lsp_types::*; use lsp_types::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -8,223 +7,134 @@ use sync_ls::*;
use tinymist_query::{ use tinymist_query::{
CompilerQueryRequest, CompilerQueryResponse, FoldRequestFeature, SyntaxRequest, CompilerQueryRequest, CompilerQueryResponse, FoldRequestFeature, SyntaxRequest,
}; };
use tinymist_std::{ImmutPath, Result}; use tinymist_std::ImmutPath;
use crate::project::{EntryState, TaskInputs, DETACHED_ENTRY}; use crate::project::{EntryState, TaskInputs, DETACHED_ENTRY};
use crate::{as_path, as_path_, as_path_pos, FormatterMode, ServerState}; use crate::{as_path, as_path_, as_path_pos, FormatterMode, ServerState};
/// The future type for a lsp query. /// The future type for a lsp query.
pub type QueryFuture = Result<ResponseFuture<Result<CompilerQueryResponse>>>; pub type QueryFuture = SchedulableResponse<CompilerQueryResponse>;
pub trait LspClientExt {
fn schedule_query(&self, req_id: RequestId, query_fut: QueryFuture) -> ScheduledResult;
}
impl LspClientExt for LspClient {
/// Schedules a query from the client.
fn schedule_query(&self, req_id: RequestId, query_fut: QueryFuture) -> ScheduledResult {
let fut = query_fut.map_err(|e| internal_error(e.to_string()))?;
let fut: SchedulableResponse<CompilerQueryResponse> = Ok(match fut {
MaybeDone::Done(res) => {
MaybeDone::Done(res.map_err(|err| internal_error(err.to_string())))
}
MaybeDone::Future(fut) => MaybeDone::Future(Box::pin(async move {
let res = fut.await;
res.map_err(|err| internal_error(err.to_string()))
})),
MaybeDone::Gone => MaybeDone::Gone,
});
self.schedule(req_id, fut)
}
}
macro_rules! run_query { macro_rules! run_query {
($req_id: ident, $self: ident.$query: ident ($($arg_key:ident),* $(,)?)) => {{ ($self: ident.$query: ident ($($arg_key:ident),* $(,)?)) => {{
use tinymist_query::*; use tinymist_query::*;
let req = paste::paste! { [<$query Request>] { $($arg_key),* } }; let req = paste::paste! { [<$query Request>] { $($arg_key),* } };
let query_fut = $self.query(CompilerQueryRequest::$query(req.clone())); erased_response($self.query(CompilerQueryRequest::$query(req.clone())))
$self.client.untyped().schedule_query($req_id, query_fut)
}}; }};
} }
pub(crate) use run_query; pub(crate) use run_query;
/// LSP Standard Language Features /// LSP Standard Language Features
impl ServerState { impl ServerState {
pub(crate) fn goto_definition( pub(crate) fn goto_definition(&mut self, params: GotoDefinitionParams) -> ScheduleResult {
&mut self,
req_id: RequestId,
params: GotoDefinitionParams,
) -> ScheduledResult {
let (path, position) = as_path_pos(params.text_document_position_params); let (path, position) = as_path_pos(params.text_document_position_params);
run_query!(req_id, self.GotoDefinition(path, position)) run_query!(self.GotoDefinition(path, position))
} }
pub(crate) fn goto_declaration( pub(crate) fn goto_declaration(&mut self, params: GotoDeclarationParams) -> ScheduleResult {
&mut self,
req_id: RequestId,
params: GotoDeclarationParams,
) -> ScheduledResult {
let (path, position) = as_path_pos(params.text_document_position_params); let (path, position) = as_path_pos(params.text_document_position_params);
run_query!(req_id, self.GotoDeclaration(path, position)) run_query!(self.GotoDeclaration(path, position))
} }
pub(crate) fn references( pub(crate) fn references(&mut self, params: ReferenceParams) -> ScheduleResult {
&mut self,
req_id: RequestId,
params: ReferenceParams,
) -> ScheduledResult {
let (path, position) = as_path_pos(params.text_document_position); let (path, position) = as_path_pos(params.text_document_position);
run_query!(req_id, self.References(path, position)) run_query!(self.References(path, position))
} }
pub(crate) fn hover(&mut self, req_id: RequestId, params: HoverParams) -> ScheduledResult { pub(crate) fn hover(&mut self, params: HoverParams) -> ScheduleResult {
let (path, position) = as_path_pos(params.text_document_position_params); let (path, position) = as_path_pos(params.text_document_position_params);
self.implicit_focus_entry(|| Some(path.as_path().into()), 'h'); self.implicit_focus_entry(|| Some(path.as_path().into()), 'h');
self.implicit_position = Some(position); self.implicit_position = Some(position);
run_query!(req_id, self.Hover(path, position)) run_query!(self.Hover(path, position))
} }
pub(crate) fn folding_range( pub(crate) fn folding_range(&mut self, params: FoldingRangeParams) -> ScheduleResult {
&mut self,
req_id: RequestId,
params: FoldingRangeParams,
) -> ScheduledResult {
let path = as_path(params.text_document); let path = as_path(params.text_document);
let line_folding_only = self.const_config().doc_line_folding_only; let line_folding_only = self.const_config().doc_line_folding_only;
self.implicit_focus_entry(|| Some(path.as_path().into()), 'f'); self.implicit_focus_entry(|| Some(path.as_path().into()), 'f');
run_query!(req_id, self.FoldingRange(path, line_folding_only)) run_query!(self.FoldingRange(path, line_folding_only))
} }
pub(crate) fn selection_range( pub(crate) fn selection_range(&mut self, params: SelectionRangeParams) -> ScheduleResult {
&mut self,
req_id: RequestId,
params: SelectionRangeParams,
) -> ScheduledResult {
let path = as_path(params.text_document); let path = as_path(params.text_document);
let positions = params.positions; let positions = params.positions;
run_query!(req_id, self.SelectionRange(path, positions)) run_query!(self.SelectionRange(path, positions))
} }
pub(crate) fn document_highlight( pub(crate) fn document_highlight(&mut self, params: DocumentHighlightParams) -> ScheduleResult {
&mut self,
req_id: RequestId,
params: DocumentHighlightParams,
) -> ScheduledResult {
let (path, position) = as_path_pos(params.text_document_position_params); let (path, position) = as_path_pos(params.text_document_position_params);
run_query!(req_id, self.DocumentHighlight(path, position)) run_query!(self.DocumentHighlight(path, position))
} }
pub(crate) fn document_symbol( pub(crate) fn document_symbol(&mut self, params: DocumentSymbolParams) -> ScheduleResult {
&mut self,
req_id: RequestId,
params: DocumentSymbolParams,
) -> ScheduledResult {
let path = as_path(params.text_document); let path = as_path(params.text_document);
run_query!(req_id, self.DocumentSymbol(path)) run_query!(self.DocumentSymbol(path))
} }
pub(crate) fn semantic_tokens_full( pub(crate) fn semantic_tokens_full(&mut self, params: SemanticTokensParams) -> ScheduleResult {
&mut self,
req_id: RequestId,
params: SemanticTokensParams,
) -> ScheduledResult {
let path = as_path(params.text_document); let path = as_path(params.text_document);
self.implicit_focus_entry(|| Some(path.as_path().into()), 't'); self.implicit_focus_entry(|| Some(path.as_path().into()), 't');
run_query!(req_id, self.SemanticTokensFull(path)) run_query!(self.SemanticTokensFull(path))
} }
pub(crate) fn semantic_tokens_full_delta( pub(crate) fn semantic_tokens_full_delta(
&mut self, &mut self,
req_id: RequestId,
params: SemanticTokensDeltaParams, params: SemanticTokensDeltaParams,
) -> ScheduledResult { ) -> ScheduleResult {
let path = as_path(params.text_document); let path = as_path(params.text_document);
let previous_result_id = params.previous_result_id; let previous_result_id = params.previous_result_id;
self.implicit_focus_entry(|| Some(path.as_path().into()), 't'); self.implicit_focus_entry(|| Some(path.as_path().into()), 't');
run_query!(req_id, self.SemanticTokensDelta(path, previous_result_id)) run_query!(self.SemanticTokensDelta(path, previous_result_id))
} }
pub(crate) fn formatting( pub(crate) fn formatting(&mut self, params: DocumentFormattingParams) -> ScheduleResult {
&mut self,
req_id: RequestId,
params: DocumentFormattingParams,
) -> ScheduledResult {
if matches!(self.config.formatter_mode, FormatterMode::Disable) { if matches!(self.config.formatter_mode, FormatterMode::Disable) {
return Ok(None); return just_ok(serde_json::Value::Null);
} }
let path: ImmutPath = as_path(params.text_document).as_path().into(); let path: ImmutPath = as_path(params.text_document).as_path().into();
let source = self let source = self.query_source(path, |source: typst::syntax::Source| Ok(source))?;
.query_source(path, |source: typst::syntax::Source| Ok(source)) erased_response(self.formatter.run(source))
.map_err(|e| internal_error(format!("could not format document: {e}")))?;
self.client.schedule(req_id, self.formatter.run(source))
} }
pub(crate) fn inlay_hint( pub(crate) fn inlay_hint(&mut self, params: InlayHintParams) -> ScheduleResult {
&mut self,
req_id: RequestId,
params: InlayHintParams,
) -> ScheduledResult {
let path = as_path(params.text_document); let path = as_path(params.text_document);
let range = params.range; let range = params.range;
run_query!(req_id, self.InlayHint(path, range)) run_query!(self.InlayHint(path, range))
} }
pub(crate) fn document_color( pub(crate) fn document_color(&mut self, params: DocumentColorParams) -> ScheduleResult {
&mut self,
req_id: RequestId,
params: DocumentColorParams,
) -> ScheduledResult {
let path = as_path(params.text_document); let path = as_path(params.text_document);
run_query!(req_id, self.DocumentColor(path)) run_query!(self.DocumentColor(path))
} }
pub(crate) fn document_link( pub(crate) fn document_link(&mut self, params: DocumentLinkParams) -> ScheduleResult {
&mut self,
req_id: RequestId,
params: DocumentLinkParams,
) -> ScheduledResult {
let path = as_path(params.text_document); let path = as_path(params.text_document);
run_query!(req_id, self.DocumentLink(path)) run_query!(self.DocumentLink(path))
} }
pub(crate) fn color_presentation( pub(crate) fn color_presentation(&mut self, params: ColorPresentationParams) -> ScheduleResult {
&mut self,
req_id: RequestId,
params: ColorPresentationParams,
) -> ScheduledResult {
let path = as_path(params.text_document); let path = as_path(params.text_document);
let color = params.color; let color = params.color;
let range = params.range; let range = params.range;
run_query!(req_id, self.ColorPresentation(path, color, range)) run_query!(self.ColorPresentation(path, color, range))
} }
pub(crate) fn code_action( pub(crate) fn code_action(&mut self, params: CodeActionParams) -> ScheduleResult {
&mut self,
req_id: RequestId,
params: CodeActionParams,
) -> ScheduledResult {
let path = as_path(params.text_document); let path = as_path(params.text_document);
let range = params.range; let range = params.range;
let context = params.context; let context = params.context;
run_query!(req_id, self.CodeAction(path, range, context)) run_query!(self.CodeAction(path, range, context))
} }
pub(crate) fn code_lens( pub(crate) fn code_lens(&mut self, params: CodeLensParams) -> ScheduleResult {
&mut self,
req_id: RequestId,
params: CodeLensParams,
) -> ScheduledResult {
let path = as_path(params.text_document); let path = as_path(params.text_document);
run_query!(req_id, self.CodeLens(path)) run_query!(self.CodeLens(path))
} }
pub(crate) fn completion( pub(crate) fn completion(&mut self, params: CompletionParams) -> ScheduleResult {
&mut self,
req_id: RequestId,
params: CompletionParams,
) -> ScheduledResult {
let (path, position) = as_path_pos(params.text_document_position); let (path, position) = as_path_pos(params.text_document_position);
let context = params.context.as_ref(); let context = params.context.as_ref();
let explicit = let explicit =
@ -234,58 +144,39 @@ impl ServerState {
.and_then(|c| c.chars().next()); .and_then(|c| c.chars().next());
self.implicit_position = Some(position); self.implicit_position = Some(position);
run_query!( run_query!(self.Completion(path, position, explicit, trigger_character))
req_id,
self.Completion(path, position, explicit, trigger_character)
)
} }
pub(crate) fn signature_help( pub(crate) fn signature_help(&mut self, params: SignatureHelpParams) -> ScheduleResult {
&mut self,
req_id: RequestId,
params: SignatureHelpParams,
) -> ScheduledResult {
let (path, position) = as_path_pos(params.text_document_position_params); let (path, position) = as_path_pos(params.text_document_position_params);
self.implicit_position = Some(position); self.implicit_position = Some(position);
run_query!(req_id, self.SignatureHelp(path, position)) run_query!(self.SignatureHelp(path, position))
} }
pub(crate) fn rename(&mut self, req_id: RequestId, params: RenameParams) -> ScheduledResult { pub(crate) fn rename(&mut self, params: RenameParams) -> ScheduleResult {
let (path, position) = as_path_pos(params.text_document_position); let (path, position) = as_path_pos(params.text_document_position);
let new_name = params.new_name; let new_name = params.new_name;
run_query!(req_id, self.Rename(path, position, new_name)) run_query!(self.Rename(path, position, new_name))
} }
pub(crate) fn prepare_rename( pub(crate) fn prepare_rename(&mut self, params: TextDocumentPositionParams) -> ScheduleResult {
&mut self,
req_id: RequestId,
params: TextDocumentPositionParams,
) -> ScheduledResult {
let (path, position) = as_path_pos(params); let (path, position) = as_path_pos(params);
run_query!(req_id, self.PrepareRename(path, position)) run_query!(self.PrepareRename(path, position))
} }
pub(crate) fn symbol( pub(crate) fn symbol(&mut self, params: WorkspaceSymbolParams) -> ScheduleResult {
&mut self,
req_id: RequestId,
params: WorkspaceSymbolParams,
) -> ScheduledResult {
let pattern = (!params.query.is_empty()).then_some(params.query); let pattern = (!params.query.is_empty()).then_some(params.query);
run_query!(req_id, self.Symbol(pattern)) run_query!(self.Symbol(pattern))
} }
pub(crate) fn on_enter(&mut self, req_id: RequestId, params: OnEnterParams) -> ScheduledResult { pub(crate) fn on_enter(&mut self, params: OnEnterParams) -> ScheduleResult {
let path = as_path(params.text_document); let path = as_path(params.text_document);
let range = params.range; let range = params.range;
run_query!(req_id, self.OnEnter(path, range)) run_query!(self.OnEnter(path, range))
} }
pub(crate) fn will_rename_files( pub(crate) fn will_rename_files(&mut self, params: RenameFilesParams) -> ScheduleResult {
&mut self,
req_id: RequestId,
params: RenameFilesParams,
) -> ScheduledResult {
log::info!("will rename files {params:?}"); log::info!("will rename files {params:?}");
let paths = params let paths = params
.files .files
@ -299,7 +190,7 @@ impl ServerState {
.collect::<Option<Vec<_>>>() .collect::<Option<Vec<_>>>()
.ok_or_else(|| invalid_params("invalid urls"))?; .ok_or_else(|| invalid_params("invalid urls"))?;
run_query!(req_id, self.WillRenameFiles(paths)) run_query!(self.WillRenameFiles(paths))
} }
} }
@ -329,9 +220,7 @@ impl ServerState {
#[cfg(feature = "export")] #[cfg(feature = "export")]
OnExport(req) => return self.on_export(req), OnExport(req) => return self.on_export(req),
#[cfg(not(feature = "export"))] #[cfg(not(feature = "export"))]
OnExport(_req) => { OnExport(_req) => return Err(internal_error("export feature is not enabled")),
return Err(tinymist_std::error_once!("export feature is not enabled"))
}
ServerInfo(_) => return self.collect_server_info(), ServerInfo(_) => return self.collect_server_info(),
// todo: query on dedicate projects // todo: query on dedicate projects
_ => return self.query_on(query), _ => return self.query_on(query),
@ -343,7 +232,9 @@ impl ServerState {
type R = CompilerQueryResponse; type R = CompilerQueryResponse;
assert!(query.fold_feature() != FoldRequestFeature::ContextFreeUnique); assert!(query.fold_feature() != FoldRequestFeature::ContextFreeUnique);
let (mut snap, stat) = self.query_snapshot_with_stat(&query)?; let (mut snap, stat) = self
.query_snapshot_with_stat(&query)
.map_err(internal_error)?;
// todo: whether it is safe to inherit success_doc with changed entry // todo: whether it is safe to inherit success_doc with changed entry
if !self.is_pinning() { if !self.is_pinning() {
let input = query let input = query
@ -377,7 +268,7 @@ impl ServerState {
} }
} }
match query { let res = match query {
SemanticTokensFull(req) => snap.run_semantic(req, R::SemanticTokensFull), SemanticTokensFull(req) => snap.run_semantic(req, R::SemanticTokensFull),
SemanticTokensDelta(req) => snap.run_semantic(req, R::SemanticTokensDelta), SemanticTokensDelta(req) => snap.run_semantic(req, R::SemanticTokensDelta),
InteractCodeContext(req) => snap.run_semantic(req, R::InteractCodeContext), InteractCodeContext(req) => snap.run_semantic(req, R::InteractCodeContext),
@ -400,7 +291,9 @@ impl ServerState {
WorkspaceLabel(req) => snap.run_semantic(req, R::WorkspaceLabel), WorkspaceLabel(req) => snap.run_semantic(req, R::WorkspaceLabel),
DocumentMetrics(req) => snap.run_stateful(req, R::DocumentMetrics), DocumentMetrics(req) => snap.run_stateful(req, R::DocumentMetrics),
_ => unreachable!(), _ => unreachable!(),
} };
res.map_err(internal_error)
}) })
} }
} }

View file

@ -438,7 +438,7 @@ impl ServerState {
let query_stats = self.project.analysis.report_query_stats(); let query_stats = self.project.analysis.report_query_stats();
let alloc_stats = self.project.analysis.report_alloc_stats(); let alloc_stats = self.project.analysis.report_alloc_stats();
let snap = self.snapshot()?; let snap = self.snapshot().map_err(internal_error)?;
just_future(async move { just_future(async move {
let w = snap.world(); let w = snap.world();

View file

@ -8,7 +8,7 @@ use std::{ops::DerefMut, pin::Pin};
use reflexo::ImmutPath; use reflexo::ImmutPath;
use reflexo_typst::{Bytes, CompilationTask, ExportComputation}; use reflexo_typst::{Bytes, CompilationTask, ExportComputation};
use sync_ls::just_future; use sync_ls::{internal_error, just_future};
use tinymist_project::LspWorld; use tinymist_project::LspWorld;
use tinymist_query::OnExportRequest; use tinymist_query::OnExportRequest;
use tinymist_std::error::prelude::*; use tinymist_std::error::prelude::*;
@ -61,7 +61,7 @@ impl ServerState {
} }
}); });
let snap = self.snapshot()?; let snap = self.snapshot().map_err(internal_error)?;
just_future(async move { just_future(async move {
let snap = snap.task(TaskInputs { let snap = snap.task(TaskInputs {
entry: Some(entry), entry: Some(entry),
@ -70,7 +70,9 @@ impl ServerState {
let is_html = matches!(task, ProjectTask::ExportHtml { .. }); let is_html = matches!(task, ProjectTask::ExportHtml { .. });
let artifact = CompiledArtifact::from_graph(snap.clone(), is_html); let artifact = CompiledArtifact::from_graph(snap.clone(), is_html);
let res = ExportTask::do_export(task, artifact, lock_dir).await?; let res = ExportTask::do_export(task, artifact, lock_dir)
.await
.map_err(internal_error)?;
if let Some(update_dep) = update_dep { if let Some(update_dep) = update_dep {
tokio::spawn(update_dep(snap)); tokio::spawn(update_dep(snap));
} }