diff --git a/crates/djls-server/src/client.rs b/crates/djls-server/src/client.rs deleted file mode 100644 index 35e616fb..00000000 --- a/crates/djls-server/src/client.rs +++ /dev/null @@ -1,235 +0,0 @@ -use std::fmt::Display; -use std::sync::Arc; -use std::sync::OnceLock; - -pub use messages::*; -use tower_lsp_server::jsonrpc::Error; -use tower_lsp_server::Client; - -static CLIENT: OnceLock> = OnceLock::new(); - -pub fn init_client(client: Client) { - let client_arc = Arc::new(client); - CLIENT - .set(client_arc) - .expect("client should only be initialized once"); -} - -fn get_client() -> Option> { - CLIENT.get().cloned() -} - -/// Generates a fire-and-forget notification function that spawns an async task. -/// -/// This macro creates a wrapper function that: -/// 1. Gets the global client instance -/// 2. Spawns a new Tokio task that calls the client method asynchronously -/// 3. Does not wait for completion or handle errors -/// -/// This... -/// ```rust,ignore -/// notify!(log_message, message_type: MessageType, message: impl Display + Send + 'static); -/// ``` -/// -/// ...expands to: -/// ```rust,ignore -/// pub fn log_message(message_type: MessageType, message: impl Display + Send + 'static) { -/// if let Some(client) = get_client() { -/// tokio::spawn(async move { -/// client.log_message(message_type, message).await; -/// }); -/// } -/// } -/// ``` -macro_rules! notify { - ($name:ident, $($param:ident: $type:ty),*) => { - pub fn $name($($param: $type),*) { - if let Some(client) = get_client() { - tokio::spawn(async move { - client.$name($($param),*).await; - }); - } - } - }; -} - -/// Generates a fire-and-forget notification function that spawns an async task and discards any errors. -/// -/// Similar to `notify!`, but explicitly discards any errors returned by the client method. -/// This is useful for methods that might return a Result but where you don't care about the outcome. -/// -/// This... -/// ```rust,ignore -/// notify_discard!(code_lens_refresh,); -/// ``` -/// -/// ...expands to: -/// ```rust,ignore -/// pub fn code_lens_refresh() { -/// if let Some(client) = get_client() { -/// tokio::spawn(async move { -/// let _ = client.code_lens_refresh().await; -/// }); -/// } -/// } -/// ``` -macro_rules! notify_discard { - ($name:ident, $($param:ident: $type:ty),*) => { - pub fn $name($($param: $type),*) { - if let Some(client) = get_client() { - tokio::spawn(async move { - let _ = client.$name($($param),*).await; - }); - } - } - }; -} - -/// Generates an async request function that awaits a response from the client. -/// -/// Unlike the notification macros, this creates a function that: -/// 1. Is marked as `async` and must be awaited -/// 2. Returns a `Result` with the response type -/// 3. Fails with an internal error if the client is not available -/// -/// The semi-colon (`;`) separates the parameters from the return type. -/// -/// This... -/// ```rust,ignore -/// request!(show_document, params: ShowDocumentParams ; bool); -/// ``` -/// -/// ...expands to: -/// ```rust,ignore -/// pub async fn show_document(params: ShowDocumentParams) -> Result { -/// if let Some(client) = get_client() { -/// client.show_document(params).await -/// } else { -/// Err(Error::internal_error()) -/// } -/// } -/// ``` -macro_rules! request { - ($name:ident, $($param:ident: $type:ty),* ; $result:ty) => { - pub async fn $name($($param: $type),*) -> Result<$result, Error> { - if let Some(client) = get_client() { - client.$name($($param),*).await - } else { - Err(Error::internal_error()) - } - } - }; -} - -#[allow(dead_code)] -pub mod messages { - use tower_lsp_server::lsp_types; - - use super::get_client; - use super::Display; - use super::Error; - - notify!(log_message, message_type: lsp_types::MessageType, message: impl Display + Send + 'static); - notify!(show_message, message_type: lsp_types::MessageType, message: impl Display + Send + 'static); - request!(show_message_request, message_type: lsp_types::MessageType, message: impl Display + Send + 'static, actions: Option> ; Option); - request!(show_document, params: lsp_types::ShowDocumentParams ; bool); -} - -#[allow(dead_code)] -pub mod diagnostics { - use tower_lsp_server::lsp_types; - - use super::get_client; - - notify!(publish_diagnostics, uri: lsp_types::Uri, diagnostics: Vec, version: Option); - notify_discard!(workspace_diagnostic_refresh,); -} - -#[allow(dead_code)] -pub mod workspace { - use tower_lsp_server::lsp_types; - - use super::get_client; - use super::Error; - - request!(apply_edit, edit: lsp_types::WorkspaceEdit ; lsp_types::ApplyWorkspaceEditResponse); - request!(configuration, items: Vec ; Vec); - request!(workspace_folders, ; Option>); -} - -#[allow(dead_code)] -pub mod editor { - use super::get_client; - - notify_discard!(code_lens_refresh,); - notify_discard!(semantic_tokens_refresh,); - notify_discard!(inline_value_refresh,); - notify_discard!(inlay_hint_refresh,); -} - -#[allow(dead_code)] -pub mod capabilities { - use tower_lsp_server::lsp_types; - - use super::get_client; - - notify_discard!(register_capability, registrations: Vec); - notify_discard!(unregister_capability, unregisterations: Vec); -} - -#[allow(dead_code)] -pub mod monitoring { - use serde::Serialize; - use tower_lsp_server::lsp_types; - use tower_lsp_server::Progress; - - use super::get_client; - - pub fn telemetry_event(data: S) { - if let Some(client) = get_client() { - tokio::spawn(async move { - client.telemetry_event(data).await; - }); - } - } - - pub fn progress + Send>( - token: lsp_types::ProgressToken, - title: T, - ) -> Option { - get_client().map(|client| client.progress(token, title)) - } -} - -#[allow(dead_code)] -pub mod protocol { - use tower_lsp_server::lsp_types; - - use super::get_client; - use super::Error; - - pub fn send_notification(params: N::Params) - where - N: lsp_types::notification::Notification, - N::Params: Send + 'static, - { - if let Some(client) = get_client() { - tokio::spawn(async move { - client.send_notification::(params).await; - }); - } - } - - pub async fn send_request(params: R::Params) -> Result - where - R: lsp_types::request::Request, - R::Params: Send + 'static, - R::Result: Send + 'static, - { - if let Some(client) = get_client() { - client.send_request::(params).await - } else { - Err(Error::internal_error()) - } - } -} diff --git a/crates/djls-server/src/lib.rs b/crates/djls-server/src/lib.rs index 055f9d03..453988e1 100644 --- a/crates/djls-server/src/lib.rs +++ b/crates/djls-server/src/lib.rs @@ -1,4 +1,3 @@ -mod client; mod completions; mod logging; mod queue; @@ -46,13 +45,17 @@ pub fn run() -> Result<()> { let stdout = tokio::io::stdout(); let (service, socket) = LspService::build(|client| { - client::init_client(client); - - let log_guard = logging::init_tracing(|message_type, message| { - client::log_message(message_type, message); + let log_guard = logging::init_tracing({ + let client = client.clone(); + move |message_type, message| { + let client = client.clone(); + tokio::spawn(async move { + client.log_message(message_type, message).await; + }); + } }); - DjangoLanguageServer::new(log_guard) + DjangoLanguageServer::new(client, log_guard) }) .finish(); diff --git a/crates/djls-server/src/server.rs b/crates/djls-server/src/server.rs index f0ef4c1c..e5cc7804 100644 --- a/crates/djls-server/src/server.rs +++ b/crates/djls-server/src/server.rs @@ -6,6 +6,7 @@ use djls_workspace::FileKind; use tokio::sync::RwLock; use tower_lsp_server::jsonrpc::Result as LspResult; use tower_lsp_server::lsp_types; +use tower_lsp_server::Client; use tower_lsp_server::LanguageServer; use tracing_appender::non_blocking::WorkerGuard; @@ -16,6 +17,8 @@ const SERVER_NAME: &str = "Django Language Server"; const SERVER_VERSION: &str = "0.1.0"; pub struct DjangoLanguageServer { + #[allow(dead_code)] // will be needed when diagnostics and other features are added + client: Client, session: Arc>>, queue: Queue, _log_guard: WorkerGuard, @@ -23,8 +26,9 @@ pub struct DjangoLanguageServer { impl DjangoLanguageServer { #[must_use] - pub fn new(log_guard: WorkerGuard) -> Self { + pub fn new(client: Client, log_guard: WorkerGuard) -> Self { Self { + client, session: Arc::new(RwLock::new(None)), queue: Queue::new(), _log_guard: log_guard, @@ -131,7 +135,7 @@ impl LanguageServer for DjangoLanguageServer { async fn initialized(&self, _params: lsp_types::InitializedParams) { tracing::info!("Server received initialized notification."); - self.with_session_task(|session_arc| async move { + self.with_session_task(move |session_arc| async move { let project_path_and_venv = { let session_lock = session_arc.read().await; match &*session_lock {