lsp: Refactor capabilities as an async OnceCell
First step in making LSP init asynchronous
This commit is contained in:
parent
41f1e8e4fb
commit
c3a58cdadd
2 changed files with 40 additions and 24 deletions
|
@ -13,7 +13,10 @@ use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{BufReader, BufWriter},
|
io::{BufReader, BufWriter},
|
||||||
process::{Child, Command},
|
process::{Child, Command},
|
||||||
sync::mpsc::{channel, UnboundedReceiver, UnboundedSender},
|
sync::{
|
||||||
|
mpsc::{channel, UnboundedReceiver, UnboundedSender},
|
||||||
|
OnceCell,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -22,7 +25,7 @@ pub struct Client {
|
||||||
_process: Child,
|
_process: Child,
|
||||||
server_tx: UnboundedSender<Payload>,
|
server_tx: UnboundedSender<Payload>,
|
||||||
request_counter: AtomicU64,
|
request_counter: AtomicU64,
|
||||||
capabilities: Option<lsp::ServerCapabilities>,
|
pub(crate) capabilities: OnceCell<lsp::ServerCapabilities>,
|
||||||
offset_encoding: OffsetEncoding,
|
offset_encoding: OffsetEncoding,
|
||||||
config: Option<Value>,
|
config: Option<Value>,
|
||||||
}
|
}
|
||||||
|
@ -57,14 +60,11 @@ impl Client {
|
||||||
_process: process,
|
_process: process,
|
||||||
server_tx,
|
server_tx,
|
||||||
request_counter: AtomicU64::new(0),
|
request_counter: AtomicU64::new(0),
|
||||||
capabilities: None,
|
capabilities: OnceCell::new(),
|
||||||
offset_encoding: OffsetEncoding::Utf8,
|
offset_encoding: OffsetEncoding::Utf8,
|
||||||
config,
|
config,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: async client.initialize()
|
|
||||||
// maybe use an arc<atomic> flag
|
|
||||||
|
|
||||||
Ok((client, server_rx))
|
Ok((client, server_rx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ impl Client {
|
||||||
|
|
||||||
pub fn capabilities(&self) -> &lsp::ServerCapabilities {
|
pub fn capabilities(&self) -> &lsp::ServerCapabilities {
|
||||||
self.capabilities
|
self.capabilities
|
||||||
.as_ref()
|
.get()
|
||||||
.expect("language server not yet initialized!")
|
.expect("language server not yet initialized!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ impl Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a RPC notification to the language server.
|
/// Send a RPC notification to the language server.
|
||||||
fn notify<R: lsp::notification::Notification>(
|
pub fn notify<R: lsp::notification::Notification>(
|
||||||
&self,
|
&self,
|
||||||
params: R::Params,
|
params: R::Params,
|
||||||
) -> impl Future<Output = Result<()>>
|
) -> impl Future<Output = Result<()>>
|
||||||
|
@ -213,7 +213,7 @@ impl Client {
|
||||||
// General messages
|
// General messages
|
||||||
// -------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
pub(crate) async fn initialize(&mut self) -> Result<()> {
|
pub(crate) async fn initialize(&self) -> Result<lsp::InitializeResult> {
|
||||||
// TODO: delay any requests that are triggered prior to initialize
|
// TODO: delay any requests that are triggered prior to initialize
|
||||||
let root = find_root(None).and_then(|root| lsp::Url::from_file_path(root).ok());
|
let root = find_root(None).and_then(|root| lsp::Url::from_file_path(root).ok());
|
||||||
|
|
||||||
|
@ -281,14 +281,7 @@ impl Client {
|
||||||
locale: None, // TODO
|
locale: None, // TODO
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = self.request::<lsp::request::Initialize>(params).await?;
|
self.request::<lsp::request::Initialize>(params).await
|
||||||
self.capabilities = Some(response.capabilities);
|
|
||||||
|
|
||||||
// next up, notify<initialized>
|
|
||||||
self.notify::<lsp::notification::Initialized>(lsp::InitializedParams {})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn shutdown(&self) -> Result<()> {
|
pub async fn shutdown(&self) -> Result<()> {
|
||||||
|
@ -445,7 +438,7 @@ impl Client {
|
||||||
) -> Option<impl Future<Output = Result<()>>> {
|
) -> Option<impl Future<Output = Result<()>>> {
|
||||||
// figure out what kind of sync the server supports
|
// figure out what kind of sync the server supports
|
||||||
|
|
||||||
let capabilities = self.capabilities.as_ref().unwrap();
|
let capabilities = self.capabilities.get().unwrap();
|
||||||
|
|
||||||
let sync_capabilities = match capabilities.text_document_sync {
|
let sync_capabilities = match capabilities.text_document_sync {
|
||||||
Some(lsp::TextDocumentSyncCapability::Kind(kind))
|
Some(lsp::TextDocumentSyncCapability::Kind(kind))
|
||||||
|
@ -496,7 +489,7 @@ impl Client {
|
||||||
text_document: lsp::TextDocumentIdentifier,
|
text_document: lsp::TextDocumentIdentifier,
|
||||||
text: &Rope,
|
text: &Rope,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let capabilities = self.capabilities.as_ref().unwrap();
|
let capabilities = self.capabilities.get().unwrap();
|
||||||
|
|
||||||
let include_text = match &capabilities.text_document_sync {
|
let include_text = match &capabilities.text_document_sync {
|
||||||
Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
|
Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
|
||||||
|
@ -590,7 +583,7 @@ impl Client {
|
||||||
options: lsp::FormattingOptions,
|
options: lsp::FormattingOptions,
|
||||||
work_done_token: Option<lsp::ProgressToken>,
|
work_done_token: Option<lsp::ProgressToken>,
|
||||||
) -> anyhow::Result<Vec<lsp::TextEdit>> {
|
) -> anyhow::Result<Vec<lsp::TextEdit>> {
|
||||||
let capabilities = self.capabilities.as_ref().unwrap();
|
let capabilities = self.capabilities.get().unwrap();
|
||||||
|
|
||||||
// check if we're able to format
|
// check if we're able to format
|
||||||
match capabilities.document_formatting_provider {
|
match capabilities.document_formatting_provider {
|
||||||
|
@ -618,7 +611,7 @@ impl Client {
|
||||||
options: lsp::FormattingOptions,
|
options: lsp::FormattingOptions,
|
||||||
work_done_token: Option<lsp::ProgressToken>,
|
work_done_token: Option<lsp::ProgressToken>,
|
||||||
) -> anyhow::Result<Vec<lsp::TextEdit>> {
|
) -> anyhow::Result<Vec<lsp::TextEdit>> {
|
||||||
let capabilities = self.capabilities.as_ref().unwrap();
|
let capabilities = self.capabilities.get().unwrap();
|
||||||
|
|
||||||
// check if we're able to format
|
// check if we're able to format
|
||||||
match capabilities.document_range_formatting_provider {
|
match capabilities.document_range_formatting_provider {
|
||||||
|
|
|
@ -312,17 +312,40 @@ impl Registry {
|
||||||
Entry::Vacant(entry) => {
|
Entry::Vacant(entry) => {
|
||||||
// initialize a new client
|
// initialize a new client
|
||||||
let id = self.counter.fetch_add(1, Ordering::Relaxed);
|
let id = self.counter.fetch_add(1, Ordering::Relaxed);
|
||||||
let (mut client, incoming) = Client::start(
|
let (client, incoming) = Client::start(
|
||||||
&config.command,
|
&config.command,
|
||||||
&config.args,
|
&config.args,
|
||||||
serde_json::from_str(language_config.config.as_deref().unwrap_or("")).ok(),
|
serde_json::from_str(language_config.config.as_deref().unwrap_or("")).ok(),
|
||||||
id,
|
id,
|
||||||
)?;
|
)?;
|
||||||
// TODO: run this async without blocking
|
|
||||||
futures_executor::block_on(client.initialize())?;
|
|
||||||
s_incoming.push(UnboundedReceiverStream::new(incoming));
|
s_incoming.push(UnboundedReceiverStream::new(incoming));
|
||||||
let client = Arc::new(client);
|
let client = Arc::new(client);
|
||||||
|
|
||||||
|
let _client = client.clone();
|
||||||
|
let initialize = tokio::spawn(async move {
|
||||||
|
use futures_util::TryFutureExt;
|
||||||
|
|
||||||
|
let value = _client
|
||||||
|
.capabilities
|
||||||
|
.get_or_try_init(|| {
|
||||||
|
_client
|
||||||
|
.initialize()
|
||||||
|
.map_ok(|response| response.capabilities)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
value.expect("failed to initialize capabilities");
|
||||||
|
|
||||||
|
// next up, notify<initialized>
|
||||||
|
_client
|
||||||
|
.notify::<lsp::notification::Initialized>(lsp::InitializedParams {})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: remove this block
|
||||||
|
futures_executor::block_on(initialize).map_err(|_| anyhow::anyhow!("bail"))?;
|
||||||
|
|
||||||
entry.insert((id, client.clone()));
|
entry.insert((id, client.clone()));
|
||||||
Ok(client)
|
Ok(client)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue