Skip to content

Commit 9e13422

Browse files
Move client handling from server to global static (#153)
1 parent 460c1ce commit 9e13422

File tree

3 files changed

+310
-97
lines changed

3 files changed

+310
-97
lines changed

crates/djls-server/src/client.rs

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
use std::fmt::Display;
2+
use std::sync::Arc;
3+
use std::sync::OnceLock;
4+
5+
pub use messages::*;
6+
use tower_lsp_server::jsonrpc::Error;
7+
use tower_lsp_server::Client;
8+
9+
static CLIENT: OnceLock<Arc<Client>> = OnceLock::new();
10+
11+
pub fn init_client(client: Client) {
12+
let client_arc = Arc::new(client);
13+
CLIENT
14+
.set(client_arc)
15+
.expect("client should only be initialized once");
16+
}
17+
18+
fn get_client() -> Option<Arc<Client>> {
19+
CLIENT.get().cloned()
20+
}
21+
22+
/// Generates a fire-and-forget notification function that spawns an async task.
23+
///
24+
/// This macro creates a wrapper function that:
25+
/// 1. Gets the global client instance
26+
/// 2. Spawns a new Tokio task that calls the client method asynchronously
27+
/// 3. Does not wait for completion or handle errors
28+
///
29+
/// This...
30+
/// ```rust,ignore
31+
/// notify!(log_message, message_type: MessageType, message: impl Display + Send + 'static);
32+
/// ```
33+
///
34+
/// ...expands to:
35+
/// ```rust,ignore
36+
/// pub fn log_message(message_type: MessageType, message: impl Display + Send + 'static) {
37+
/// if let Some(client) = get_client() {
38+
/// tokio::spawn(async move {
39+
/// client.log_message(message_type, message).await;
40+
/// });
41+
/// }
42+
/// }
43+
/// ```
44+
macro_rules! notify {
45+
($name:ident, $($param:ident: $type:ty),*) => {
46+
pub fn $name($($param: $type),*) {
47+
if let Some(client) = get_client() {
48+
tokio::spawn(async move {
49+
client.$name($($param),*).await;
50+
});
51+
}
52+
}
53+
};
54+
}
55+
56+
/// Generates a fire-and-forget notification function that spawns an async task and discards any errors.
57+
///
58+
/// Similar to `notify!`, but explicitly discards any errors returned by the client method.
59+
/// This is useful for methods that might return a Result but where you don't care about the outcome.
60+
///
61+
/// This...
62+
/// ```rust,ignore
63+
/// notify_discard!(code_lens_refresh,);
64+
/// ```
65+
///
66+
/// ...expands to:
67+
/// ```rust,ignore
68+
/// pub fn code_lens_refresh() {
69+
/// if let Some(client) = get_client() {
70+
/// tokio::spawn(async move {
71+
/// let _ = client.code_lens_refresh().await;
72+
/// });
73+
/// }
74+
/// }
75+
/// ```
76+
macro_rules! notify_discard {
77+
($name:ident, $($param:ident: $type:ty),*) => {
78+
pub fn $name($($param: $type),*) {
79+
if let Some(client) = get_client() {
80+
tokio::spawn(async move {
81+
let _ = client.$name($($param),*).await;
82+
});
83+
}
84+
}
85+
};
86+
}
87+
88+
/// Generates an async request function that awaits a response from the client.
89+
///
90+
/// Unlike the notification macros, this creates a function that:
91+
/// 1. Is marked as `async` and must be awaited
92+
/// 2. Returns a `Result<T, Error>` with the response type
93+
/// 3. Fails with an internal error if the client is not available
94+
///
95+
/// The semi-colon (`;`) separates the parameters from the return type.
96+
///
97+
/// This...
98+
/// ```rust,ignore
99+
/// request!(show_document, params: ShowDocumentParams ; bool);
100+
/// ```
101+
///
102+
/// ...expands to:
103+
/// ```rust,ignore
104+
/// pub async fn show_document(params: ShowDocumentParams) -> Result<bool, Error> {
105+
/// if let Some(client) = get_client() {
106+
/// client.show_document(params).await
107+
/// } else {
108+
/// Err(Error::internal_error())
109+
/// }
110+
/// }
111+
/// ```
112+
macro_rules! request {
113+
($name:ident, $($param:ident: $type:ty),* ; $result:ty) => {
114+
pub async fn $name($($param: $type),*) -> Result<$result, Error> {
115+
if let Some(client) = get_client() {
116+
client.$name($($param),*).await
117+
} else {
118+
Err(Error::internal_error())
119+
}
120+
}
121+
};
122+
}
123+
124+
#[allow(dead_code)]
125+
pub mod messages {
126+
use tower_lsp_server::lsp_types::MessageActionItem;
127+
use tower_lsp_server::lsp_types::MessageType;
128+
use tower_lsp_server::lsp_types::ShowDocumentParams;
129+
130+
use super::get_client;
131+
use super::Display;
132+
use super::Error;
133+
134+
notify!(log_message, message_type: MessageType, message: impl Display + Send + 'static);
135+
notify!(show_message, message_type: MessageType, message: impl Display + Send + 'static);
136+
request!(show_message_request, message_type: MessageType, message: impl Display + Send + 'static, actions: Option<Vec<MessageActionItem>> ; Option<MessageActionItem>);
137+
request!(show_document, params: ShowDocumentParams ; bool);
138+
}
139+
140+
#[allow(dead_code)]
141+
pub mod diagnostics {
142+
use tower_lsp_server::lsp_types::Diagnostic;
143+
use tower_lsp_server::lsp_types::Uri;
144+
145+
use super::get_client;
146+
147+
notify!(publish_diagnostics, uri: Uri, diagnostics: Vec<Diagnostic>, version: Option<i32>);
148+
notify_discard!(workspace_diagnostic_refresh,);
149+
}
150+
151+
#[allow(dead_code)]
152+
pub mod workspace {
153+
use tower_lsp_server::lsp_types::ApplyWorkspaceEditResponse;
154+
use tower_lsp_server::lsp_types::ConfigurationItem;
155+
use tower_lsp_server::lsp_types::LSPAny;
156+
use tower_lsp_server::lsp_types::WorkspaceEdit;
157+
use tower_lsp_server::lsp_types::WorkspaceFolder;
158+
159+
use super::get_client;
160+
use super::Error;
161+
162+
request!(apply_edit, edit: WorkspaceEdit ; ApplyWorkspaceEditResponse);
163+
request!(configuration, items: Vec<ConfigurationItem> ; Vec<LSPAny>);
164+
request!(workspace_folders, ; Option<Vec<WorkspaceFolder>>);
165+
}
166+
167+
#[allow(dead_code)]
168+
pub mod editor {
169+
use super::get_client;
170+
171+
notify_discard!(code_lens_refresh,);
172+
notify_discard!(semantic_tokens_refresh,);
173+
notify_discard!(inline_value_refresh,);
174+
notify_discard!(inlay_hint_refresh,);
175+
}
176+
177+
#[allow(dead_code)]
178+
pub mod capabilities {
179+
use tower_lsp_server::lsp_types::Registration;
180+
use tower_lsp_server::lsp_types::Unregistration;
181+
182+
use super::get_client;
183+
184+
notify_discard!(register_capability, registrations: Vec<Registration>);
185+
notify_discard!(unregister_capability, unregisterations: Vec<Unregistration>);
186+
}
187+
188+
#[allow(dead_code)]
189+
pub mod monitoring {
190+
use serde::Serialize;
191+
use tower_lsp_server::lsp_types::ProgressToken;
192+
use tower_lsp_server::Progress;
193+
194+
use super::get_client;
195+
196+
pub fn telemetry_event<S: Serialize + Send + 'static>(data: S) {
197+
if let Some(client) = get_client() {
198+
tokio::spawn(async move {
199+
client.telemetry_event(data).await;
200+
});
201+
}
202+
}
203+
204+
pub fn progress<T: Into<String> + Send>(token: ProgressToken, title: T) -> Option<Progress> {
205+
get_client().map(|client| client.progress(token, title))
206+
}
207+
}
208+
209+
#[allow(dead_code)]
210+
pub mod protocol {
211+
use tower_lsp_server::lsp_types::notification::Notification;
212+
use tower_lsp_server::lsp_types::request::Request;
213+
214+
use super::get_client;
215+
use super::Error;
216+
217+
pub fn send_notification<N>(params: N::Params)
218+
where
219+
N: Notification,
220+
N::Params: Send + 'static,
221+
{
222+
if let Some(client) = get_client() {
223+
tokio::spawn(async move {
224+
client.send_notification::<N>(params).await;
225+
});
226+
}
227+
}
228+
229+
pub async fn send_request<R>(params: R::Params) -> Result<R::Result, Error>
230+
where
231+
R: Request,
232+
R::Params: Send + 'static,
233+
R::Result: Send + 'static,
234+
{
235+
if let Some(client) = get_client() {
236+
client.send_request::<R>(params).await
237+
} else {
238+
Err(Error::internal_error())
239+
}
240+
}
241+
}

crates/djls-server/src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod client;
12
mod db;
23
mod documents;
34
mod queue;
@@ -20,7 +21,11 @@ pub fn run() -> Result<()> {
2021
let stdin = tokio::io::stdin();
2122
let stdout = tokio::io::stdout();
2223

23-
let (service, socket) = LspService::build(DjangoLanguageServer::new).finish();
24+
let (service, socket) = LspService::build(|client| {
25+
client::init_client(client);
26+
DjangoLanguageServer::new()
27+
})
28+
.finish();
2429

2530
Server::new(stdin, stdout, socket).serve(service).await;
2631

0 commit comments

Comments
 (0)