on master help sign in
lsp: Refactor capabilities as an async OnceCell
c3a58cdBlaž Hrastnik
helix-lsp/src/client.rs+14−21
@@ -13,7 +13,10 @@ use std::sync::atomic::{AtomicU64, Ordering};
13use tokio::{
13use tokio::{
14 io::{BufReader, BufWriter},
14 io::{BufReader, BufWriter},
15 process::{Child, Command},
15 process::{Child, Command},
16+ sync::{
16− sync::mpsc::{channel, UnboundedReceiver, UnboundedSender},
17+ mpsc::{channel, UnboundedReceiver, UnboundedSender},
18+ OnceCell,
19+ },
17};
20};
18
21
19#[derive(Debug)]
22#[derive(Debug)]
@@ -22,7 +25,7 @@ pub struct Client {
22 _process: Child,
25 _process: Child,
23 server_tx: UnboundedSender<Payload>,
26 server_tx: UnboundedSender<Payload>,
24 request_counter: AtomicU64,
27 request_counter: AtomicU64,
25− capabilities: Option<lsp::ServerCapabilities>,
28+ pub(crate) capabilities: OnceCell<lsp::ServerCapabilities>,
26 offset_encoding: OffsetEncoding,
29 offset_encoding: OffsetEncoding,
27 config: Option<Value>,
30 config: Option<Value>,
28}
31}
@@ -57,14 +60,11 @@ impl Client {
57 _process: process,
60 _process: process,
58 server_tx,
61 server_tx,
59 request_counter: AtomicU64::new(0),
62 request_counter: AtomicU64::new(0),
60− capabilities: None,
63+ capabilities: OnceCell::new(),
61 offset_encoding: OffsetEncoding::Utf8,
64 offset_encoding: OffsetEncoding::Utf8,
62 config,
65 config,
63 };
66 };
64
67
65− // TODO: async client.initialize()
66− // maybe use an arc<atomic> flag
67−
68 Ok((client, server_rx))
68 Ok((client, server_rx))
69 }
69 }
70
70
@@ -90,7 +90,7 @@ impl Client {
90
90
91 pub fn capabilities(&self) -> &lsp::ServerCapabilities {
91 pub fn capabilities(&self) -> &lsp::ServerCapabilities {
92 self.capabilities
92 self.capabilities
93− .as_ref()
93+ .get()
94 .expect("language server not yet initialized!")
94 .expect("language server not yet initialized!")
95 }
95 }
96
96
@@ -151,7 +151,7 @@ impl Client {
151 }
151 }
152
152
153 /// Send a RPC notification to the language server.
153 /// Send a RPC notification to the language server.
154− fn notify<R: lsp::notification::Notification>(
154+ pub fn notify<R: lsp::notification::Notification>(
155 &self,
155 &self,
156 params: R::Params,
156 params: R::Params,
157 ) -> impl Future<Output = Result<()>>
157 ) -> impl Future<Output = Result<()>>
@@ -213,7 +213,7 @@ impl Client {
213 // General messages
213 // General messages
214 // -------------------------------------------------------------------------------------------
214 // -------------------------------------------------------------------------------------------
215
215
216− pub(crate) async fn initialize(&mut self) -> Result<()> {
216+ pub(crate) async fn initialize(&self) -> Result<lsp::InitializeResult> {
217 // TODO: delay any requests that are triggered prior to initialize
217 // TODO: delay any requests that are triggered prior to initialize
218 let root = find_root(None).and_then(|root| lsp::Url::from_file_path(root).ok());
218 let root = find_root(None).and_then(|root| lsp::Url::from_file_path(root).ok());
219
219
@@ -281,14 +281,7 @@ impl Client {
281 locale: None, // TODO
281 locale: None, // TODO
282 };
282 };
283
283
284− let response = self.request::<lsp::request::Initialize>(params).await?;
284+ self.request::<lsp::request::Initialize>(params).await
285− self.capabilities = Some(response.capabilities);
286−
287− // next up, notify<initialized>
288− self.notify::<lsp::notification::Initialized>(lsp::InitializedParams {})
289− .await?;
290−
291− Ok(())
292 }
285 }
293
286
294 pub async fn shutdown(&self) -> Result<()> {
287 pub async fn shutdown(&self) -> Result<()> {
@@ -445,7 +438,7 @@ impl Client {
445 ) -> Option<impl Future<Output = Result<()>>> {
438 ) -> Option<impl Future<Output = Result<()>>> {
446 // figure out what kind of sync the server supports
439 // figure out what kind of sync the server supports
447
440
448− let capabilities = self.capabilities.as_ref().unwrap();
441+ let capabilities = self.capabilities.get().unwrap();
449
442
450 let sync_capabilities = match capabilities.text_document_sync {
443 let sync_capabilities = match capabilities.text_document_sync {
451 Some(lsp::TextDocumentSyncCapability::Kind(kind))
444 Some(lsp::TextDocumentSyncCapability::Kind(kind))
@@ -496,7 +489,7 @@ impl Client {
496 text_document: lsp::TextDocumentIdentifier,
489 text_document: lsp::TextDocumentIdentifier,
497 text: &Rope,
490 text: &Rope,
498 ) -> Result<()> {
491 ) -> Result<()> {
499− let capabilities = self.capabilities.as_ref().unwrap();
492+ let capabilities = self.capabilities.get().unwrap();
500
493
501 let include_text = match &capabilities.text_document_sync {
494 let include_text = match &capabilities.text_document_sync {
502 Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
495 Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
@@ -590,7 +583,7 @@ impl Client {
590 options: lsp::FormattingOptions,
583 options: lsp::FormattingOptions,
591 work_done_token: Option<lsp::ProgressToken>,
584 work_done_token: Option<lsp::ProgressToken>,
592 ) -> anyhow::Result<Vec<lsp::TextEdit>> {
585 ) -> anyhow::Result<Vec<lsp::TextEdit>> {
593− let capabilities = self.capabilities.as_ref().unwrap();
586+ let capabilities = self.capabilities.get().unwrap();
594
587
595 // check if we're able to format
588 // check if we're able to format
596 match capabilities.document_formatting_provider {
589 match capabilities.document_formatting_provider {
@@ -618,7 +611,7 @@ impl Client {
618 options: lsp::FormattingOptions,
611 options: lsp::FormattingOptions,
619 work_done_token: Option<lsp::ProgressToken>,
612 work_done_token: Option<lsp::ProgressToken>,
620 ) -> anyhow::Result<Vec<lsp::TextEdit>> {
613 ) -> anyhow::Result<Vec<lsp::TextEdit>> {
621− let capabilities = self.capabilities.as_ref().unwrap();
614+ let capabilities = self.capabilities.get().unwrap();
622
615
623 // check if we're able to format
616 // check if we're able to format
624 match capabilities.document_range_formatting_provider {
617 match capabilities.document_range_formatting_provider {
helix-lsp/src/lib.rs+26−3
@@ -312,17 +312,40 @@ impl Registry {
312 Entry::Vacant(entry) => {
312 Entry::Vacant(entry) => {
313 // initialize a new client
313 // initialize a new client
314 let id = self.counter.fetch_add(1, Ordering::Relaxed);
314 let id = self.counter.fetch_add(1, Ordering::Relaxed);
315− let (mut client, incoming) = Client::start(
315+ let (client, incoming) = Client::start(
316 &config.command,
316 &config.command,
317 &config.args,
317 &config.args,
318 serde_json::from_str(language_config.config.as_deref().unwrap_or("")).ok(),
318 serde_json::from_str(language_config.config.as_deref().unwrap_or("")).ok(),
319 id,
319 id,
320 )?;
320 )?;
321− // TODO: run this async without blocking
322− futures_executor::block_on(client.initialize())?;
323 s_incoming.push(UnboundedReceiverStream::new(incoming));
321 s_incoming.push(UnboundedReceiverStream::new(incoming));
324 let client = Arc::new(client);
322 let client = Arc::new(client);
325
323
324+ let _client = client.clone();
325+ let initialize = tokio::spawn(async move {
326+ use futures_util::TryFutureExt;
327+
328+ let value = _client
329+ .capabilities
330+ .get_or_try_init(|| {
331+ _client
332+ .initialize()
333+ .map_ok(|response| response.capabilities)
334+ })
335+ .await;
336+
337+ value.expect("failed to initialize capabilities");
338+
339+ // next up, notify<initialized>
340+ _client
341+ .notify::<lsp::notification::Initialized>(lsp::InitializedParams {})
342+ .await
343+ .unwrap();
344+ });
345+
346+ // TODO: remove this block
347+ futures_executor::block_on(initialize).map_err(|_| anyhow::anyhow!("bail"))?;
348+
326 entry.insert((id, client.clone()));
349 entry.insert((id, client.clone()));
327 Ok(client)
350 Ok(client)
328 }
351 }