Let fake and real LanguageServer access AsyncAppContext in handler callbacks
Also, reimplement FakeLanguageServer by wrapping LanguageServer, instead of duplicating its functionality differently.
This commit is contained in:
parent
afbddc1bcd
commit
e987a8ba63
5 changed files with 317 additions and 419 deletions
|
@ -8928,7 +8928,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
cx.foreground().start_waiting();
|
cx.foreground().start_waiting();
|
||||||
let mut fake_server = fake_servers.next().await.unwrap();
|
let fake_server = fake_servers.next().await.unwrap();
|
||||||
|
|
||||||
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
|
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||||
let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
|
let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
|
||||||
|
@ -8942,10 +8942,10 @@ mod tests {
|
||||||
params.text_document.uri,
|
params.text_document.uri,
|
||||||
lsp::Url::from_file_path("/file.rs").unwrap()
|
lsp::Url::from_file_path("/file.rs").unwrap()
|
||||||
);
|
);
|
||||||
Some(vec![lsp::TextEdit::new(
|
Ok(Some(vec![lsp::TextEdit::new(
|
||||||
lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
|
lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
|
||||||
", ".to_string(),
|
", ".to_string(),
|
||||||
)])
|
)]))
|
||||||
})
|
})
|
||||||
.next()
|
.next()
|
||||||
.await;
|
.await;
|
||||||
|
@ -9173,7 +9173,7 @@ mod tests {
|
||||||
params.text_document_position.position,
|
params.text_document_position.position,
|
||||||
lsp::Position::new(position.row, position.column)
|
lsp::Position::new(position.row, position.column)
|
||||||
);
|
);
|
||||||
Some(lsp::CompletionResponse::Array(
|
Ok(Some(lsp::CompletionResponse::Array(
|
||||||
completions
|
completions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(range, new_text)| lsp::CompletionItem {
|
.map(|(range, new_text)| lsp::CompletionItem {
|
||||||
|
@ -9188,7 +9188,7 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
))
|
)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.next()
|
.next()
|
||||||
|
@ -9202,7 +9202,7 @@ mod tests {
|
||||||
fake.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _| {
|
fake.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _| {
|
||||||
let edit = edit.clone();
|
let edit = edit.clone();
|
||||||
async move {
|
async move {
|
||||||
lsp::CompletionItem {
|
Ok(lsp::CompletionItem {
|
||||||
additional_text_edits: edit.map(|(range, new_text)| {
|
additional_text_edits: edit.map(|(range, new_text)| {
|
||||||
vec![lsp::TextEdit::new(
|
vec![lsp::TextEdit::new(
|
||||||
lsp::Range::new(
|
lsp::Range::new(
|
||||||
|
@ -9213,7 +9213,7 @@ mod tests {
|
||||||
)]
|
)]
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.next()
|
.next()
|
||||||
|
|
|
@ -263,14 +263,12 @@ impl LanguageRegistry {
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
if language.fake_adapter.is_some() {
|
if language.fake_adapter.is_some() {
|
||||||
let language = language.clone();
|
let language = language.clone();
|
||||||
return Some(cx.spawn(|mut cx| async move {
|
return Some(cx.spawn(|cx| async move {
|
||||||
let (servers_tx, fake_adapter) = language.fake_adapter.as_ref().unwrap();
|
let (servers_tx, fake_adapter) = language.fake_adapter.as_ref().unwrap();
|
||||||
let (server, mut fake_server) = cx.update(|cx| {
|
let (server, mut fake_server) = lsp::LanguageServer::fake_with_capabilities(
|
||||||
lsp::LanguageServer::fake_with_capabilities(
|
fake_adapter.capabilities.clone(),
|
||||||
fake_adapter.capabilities.clone(),
|
cx.clone(),
|
||||||
cx,
|
);
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(initializer) = &fake_adapter.initializer {
|
if let Some(initializer) = &fake_adapter.initializer {
|
||||||
initializer(&mut fake_server);
|
initializer(&mut fake_server);
|
||||||
|
@ -297,10 +295,9 @@ impl LanguageRegistry {
|
||||||
|
|
||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
let adapter = language.adapter.clone()?;
|
let adapter = language.adapter.clone()?;
|
||||||
let background = cx.background().clone();
|
|
||||||
let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone();
|
let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone();
|
||||||
let login_shell_env_loaded = self.login_shell_env_loaded.clone();
|
let login_shell_env_loaded = self.login_shell_env_loaded.clone();
|
||||||
Some(cx.background().spawn(async move {
|
Some(cx.spawn(|cx| async move {
|
||||||
login_shell_env_loaded.await;
|
login_shell_env_loaded.await;
|
||||||
let server_binary_path = this
|
let server_binary_path = this
|
||||||
.lsp_binary_paths
|
.lsp_binary_paths
|
||||||
|
@ -328,8 +325,7 @@ impl LanguageRegistry {
|
||||||
&server_binary_path,
|
&server_binary_path,
|
||||||
server_args,
|
server_args,
|
||||||
&root_path,
|
&root_path,
|
||||||
adapter.initialization_options(),
|
cx,
|
||||||
background,
|
|
||||||
)?;
|
)?;
|
||||||
Ok(server)
|
Ok(server)
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
|
pub use lsp_types::*;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use futures::{channel::oneshot, io::BufWriter, AsyncRead, AsyncWrite};
|
use futures::{channel::oneshot, io::BufWriter, AsyncRead, AsyncWrite};
|
||||||
use gpui::{executor, Task};
|
use gpui::{executor, AsyncAppContext, Task};
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::Mutex;
|
||||||
use postage::{barrier, prelude::Stream};
|
use postage::{barrier, prelude::Stream};
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
use serde_json::{json, value::RawValue, Value};
|
use serde_json::{json, value::RawValue, Value};
|
||||||
use smol::{
|
use smol::{
|
||||||
channel,
|
channel,
|
||||||
io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader},
|
io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader},
|
||||||
process::Command,
|
process,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
future::Future,
|
future::Future,
|
||||||
|
@ -22,15 +24,12 @@ use std::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use std::{path::Path, process::Stdio};
|
use std::{path::Path, process::Stdio};
|
||||||
use util::TryFutureExt;
|
use util::{ResultExt, TryFutureExt};
|
||||||
|
|
||||||
pub use lsp_types::*;
|
|
||||||
|
|
||||||
const JSON_RPC_VERSION: &'static str = "2.0";
|
const JSON_RPC_VERSION: &'static str = "2.0";
|
||||||
const CONTENT_LEN_HEADER: &'static str = "Content-Length: ";
|
const CONTENT_LEN_HEADER: &'static str = "Content-Length: ";
|
||||||
|
|
||||||
type NotificationHandler =
|
type NotificationHandler = Box<dyn Send + FnMut(Option<usize>, &str, AsyncAppContext)>;
|
||||||
Box<dyn Send + Sync + FnMut(Option<usize>, &str, &mut channel::Sender<Vec<u8>>) -> Result<()>>;
|
|
||||||
type ResponseHandler = Box<dyn Send + FnOnce(Result<&str, Error>)>;
|
type ResponseHandler = Box<dyn Send + FnOnce(Result<&str, Error>)>;
|
||||||
|
|
||||||
pub struct LanguageServer {
|
pub struct LanguageServer {
|
||||||
|
@ -39,18 +38,17 @@ pub struct LanguageServer {
|
||||||
outbound_tx: channel::Sender<Vec<u8>>,
|
outbound_tx: channel::Sender<Vec<u8>>,
|
||||||
name: String,
|
name: String,
|
||||||
capabilities: ServerCapabilities,
|
capabilities: ServerCapabilities,
|
||||||
notification_handlers: Arc<RwLock<HashMap<&'static str, NotificationHandler>>>,
|
notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
|
||||||
response_handlers: Arc<Mutex<HashMap<usize, ResponseHandler>>>,
|
response_handlers: Arc<Mutex<HashMap<usize, ResponseHandler>>>,
|
||||||
executor: Arc<executor::Background>,
|
executor: Arc<executor::Background>,
|
||||||
io_tasks: Mutex<Option<(Task<Option<()>>, Task<Option<()>>)>>,
|
io_tasks: Mutex<Option<(Task<Option<()>>, Task<Option<()>>)>>,
|
||||||
output_done_rx: Mutex<Option<barrier::Receiver>>,
|
output_done_rx: Mutex<Option<barrier::Receiver>>,
|
||||||
root_path: PathBuf,
|
root_path: PathBuf,
|
||||||
options: Option<Value>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Subscription {
|
pub struct Subscription {
|
||||||
method: &'static str,
|
method: &'static str,
|
||||||
notification_handlers: Arc<RwLock<HashMap<&'static str, NotificationHandler>>>,
|
notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -61,18 +59,6 @@ struct Request<'a, T> {
|
||||||
params: T,
|
params: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct AnyRequest<'a> {
|
|
||||||
id: usize,
|
|
||||||
#[serde(borrow)]
|
|
||||||
jsonrpc: &'a str,
|
|
||||||
#[serde(borrow)]
|
|
||||||
method: &'a str,
|
|
||||||
#[serde(borrow)]
|
|
||||||
params: &'a RawValue,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct AnyResponse<'a> {
|
struct AnyResponse<'a> {
|
||||||
id: usize,
|
id: usize,
|
||||||
|
@ -85,7 +71,8 @@ struct AnyResponse<'a> {
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct Response<T> {
|
struct Response<T> {
|
||||||
id: usize,
|
id: usize,
|
||||||
result: T,
|
result: Option<T>,
|
||||||
|
error: Option<Error>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -118,15 +105,14 @@ impl LanguageServer {
|
||||||
binary_path: &Path,
|
binary_path: &Path,
|
||||||
args: &[&str],
|
args: &[&str],
|
||||||
root_path: &Path,
|
root_path: &Path,
|
||||||
options: Option<Value>,
|
cx: AsyncAppContext,
|
||||||
background: Arc<executor::Background>,
|
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let working_dir = if root_path.is_dir() {
|
let working_dir = if root_path.is_dir() {
|
||||||
root_path
|
root_path
|
||||||
} else {
|
} else {
|
||||||
root_path.parent().unwrap_or(Path::new("/"))
|
root_path.parent().unwrap_or(Path::new("/"))
|
||||||
};
|
};
|
||||||
let mut server = Command::new(binary_path)
|
let mut server = process::Command::new(binary_path)
|
||||||
.current_dir(working_dir)
|
.current_dir(working_dir)
|
||||||
.args(args)
|
.args(args)
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
|
@ -136,95 +122,91 @@ impl LanguageServer {
|
||||||
let stdin = server.stdin.take().unwrap();
|
let stdin = server.stdin.take().unwrap();
|
||||||
let stdout = server.stdout.take().unwrap();
|
let stdout = server.stdout.take().unwrap();
|
||||||
let mut server =
|
let mut server =
|
||||||
Self::new_internal(server_id, stdin, stdout, root_path, options, background);
|
Self::new_internal(server_id, stdin, stdout, root_path, cx, |notification| {
|
||||||
|
log::info!(
|
||||||
|
"unhandled notification {}:\n{}",
|
||||||
|
notification.method,
|
||||||
|
serde_json::to_string_pretty(
|
||||||
|
&Value::from_str(notification.params.get()).unwrap()
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
});
|
||||||
if let Some(name) = binary_path.file_name() {
|
if let Some(name) = binary_path.file_name() {
|
||||||
server.name = name.to_string_lossy().to_string();
|
server.name = name.to_string_lossy().to_string();
|
||||||
}
|
}
|
||||||
Ok(server)
|
Ok(server)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_internal<Stdin, Stdout>(
|
fn new_internal<Stdin, Stdout, F>(
|
||||||
server_id: usize,
|
server_id: usize,
|
||||||
stdin: Stdin,
|
stdin: Stdin,
|
||||||
stdout: Stdout,
|
stdout: Stdout,
|
||||||
root_path: &Path,
|
root_path: &Path,
|
||||||
options: Option<Value>,
|
cx: AsyncAppContext,
|
||||||
executor: Arc<executor::Background>,
|
mut on_unhandled_notification: F,
|
||||||
) -> Self
|
) -> Self
|
||||||
where
|
where
|
||||||
Stdin: AsyncWrite + Unpin + Send + 'static,
|
Stdin: AsyncWrite + Unpin + Send + 'static,
|
||||||
Stdout: AsyncRead + Unpin + Send + 'static,
|
Stdout: AsyncRead + Unpin + Send + 'static,
|
||||||
|
F: FnMut(AnyNotification) + 'static + Send,
|
||||||
{
|
{
|
||||||
let mut stdin = BufWriter::new(stdin);
|
let mut stdin = BufWriter::new(stdin);
|
||||||
let mut stdout = BufReader::new(stdout);
|
let mut stdout = BufReader::new(stdout);
|
||||||
let (outbound_tx, outbound_rx) = channel::unbounded::<Vec<u8>>();
|
let (outbound_tx, outbound_rx) = channel::unbounded::<Vec<u8>>();
|
||||||
let notification_handlers =
|
let notification_handlers =
|
||||||
Arc::new(RwLock::new(HashMap::<_, NotificationHandler>::default()));
|
Arc::new(Mutex::new(HashMap::<_, NotificationHandler>::default()));
|
||||||
let response_handlers = Arc::new(Mutex::new(HashMap::<_, ResponseHandler>::default()));
|
let response_handlers = Arc::new(Mutex::new(HashMap::<_, ResponseHandler>::default()));
|
||||||
let input_task = executor.spawn(
|
let input_task = cx.spawn(|cx| {
|
||||||
{
|
let notification_handlers = notification_handlers.clone();
|
||||||
let notification_handlers = notification_handlers.clone();
|
let response_handlers = response_handlers.clone();
|
||||||
let response_handlers = response_handlers.clone();
|
async move {
|
||||||
let mut outbound_tx = outbound_tx.clone();
|
let _clear_response_handlers = ClearResponseHandlers(response_handlers.clone());
|
||||||
async move {
|
let mut buffer = Vec::new();
|
||||||
let _clear_response_handlers = ClearResponseHandlers(response_handlers.clone());
|
loop {
|
||||||
let mut buffer = Vec::new();
|
buffer.clear();
|
||||||
loop {
|
stdout.read_until(b'\n', &mut buffer).await?;
|
||||||
buffer.clear();
|
stdout.read_until(b'\n', &mut buffer).await?;
|
||||||
stdout.read_until(b'\n', &mut buffer).await?;
|
let message_len: usize = std::str::from_utf8(&buffer)?
|
||||||
stdout.read_until(b'\n', &mut buffer).await?;
|
.strip_prefix(CONTENT_LEN_HEADER)
|
||||||
let message_len: usize = std::str::from_utf8(&buffer)?
|
.ok_or_else(|| anyhow!("invalid header"))?
|
||||||
.strip_prefix(CONTENT_LEN_HEADER)
|
.trim_end()
|
||||||
.ok_or_else(|| anyhow!("invalid header"))?
|
.parse()?;
|
||||||
.trim_end()
|
|
||||||
.parse()?;
|
|
||||||
|
|
||||||
buffer.resize(message_len, 0);
|
buffer.resize(message_len, 0);
|
||||||
stdout.read_exact(&mut buffer).await?;
|
stdout.read_exact(&mut buffer).await?;
|
||||||
log::trace!("incoming message:{}", String::from_utf8_lossy(&buffer));
|
log::trace!("incoming message:{}", String::from_utf8_lossy(&buffer));
|
||||||
|
|
||||||
if let Ok(AnyNotification { id, method, params }) =
|
if let Ok(msg) = serde_json::from_slice::<AnyNotification>(&buffer) {
|
||||||
serde_json::from_slice(&buffer)
|
if let Some(handler) = notification_handlers.lock().get_mut(msg.method) {
|
||||||
{
|
handler(msg.id, msg.params.get(), cx.clone());
|
||||||
if let Some(handler) = notification_handlers.write().get_mut(method) {
|
|
||||||
if let Err(e) = handler(id, params.get(), &mut outbound_tx) {
|
|
||||||
log::error!("error handling {} message: {:?}", method, e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log::info!(
|
|
||||||
"unhandled notification {}:\n{}",
|
|
||||||
method,
|
|
||||||
serde_json::to_string_pretty(
|
|
||||||
&Value::from_str(params.get()).unwrap()
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if let Ok(AnyResponse { id, error, result }) =
|
|
||||||
serde_json::from_slice(&buffer)
|
|
||||||
{
|
|
||||||
if let Some(handler) = response_handlers.lock().remove(&id) {
|
|
||||||
if let Some(error) = error {
|
|
||||||
handler(Err(error));
|
|
||||||
} else if let Some(result) = result {
|
|
||||||
handler(Ok(result.get()));
|
|
||||||
} else {
|
|
||||||
handler(Ok("null"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return Err(anyhow!(
|
on_unhandled_notification(msg);
|
||||||
"failed to deserialize message:\n{}",
|
|
||||||
std::str::from_utf8(&buffer)?
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
} else if let Ok(AnyResponse { id, error, result }) =
|
||||||
|
serde_json::from_slice(&buffer)
|
||||||
|
{
|
||||||
|
if let Some(handler) = response_handlers.lock().remove(&id) {
|
||||||
|
if let Some(error) = error {
|
||||||
|
handler(Err(error));
|
||||||
|
} else if let Some(result) = result {
|
||||||
|
handler(Ok(result.get()));
|
||||||
|
} else {
|
||||||
|
handler(Ok("null"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"failed to deserialize message:\n{}",
|
||||||
|
std::str::from_utf8(&buffer)?
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.log_err(),
|
.log_err()
|
||||||
);
|
});
|
||||||
let (output_done_tx, output_done_rx) = barrier::channel();
|
let (output_done_tx, output_done_rx) = barrier::channel();
|
||||||
let output_task = executor.spawn({
|
let output_task = cx.background().spawn({
|
||||||
let response_handlers = response_handlers.clone();
|
let response_handlers = response_handlers.clone();
|
||||||
async move {
|
async move {
|
||||||
let _clear_response_handlers = ClearResponseHandlers(response_handlers);
|
let _clear_response_handlers = ClearResponseHandlers(response_handlers);
|
||||||
|
@ -253,18 +235,15 @@ impl LanguageServer {
|
||||||
capabilities: Default::default(),
|
capabilities: Default::default(),
|
||||||
next_id: Default::default(),
|
next_id: Default::default(),
|
||||||
outbound_tx,
|
outbound_tx,
|
||||||
executor: executor.clone(),
|
executor: cx.background().clone(),
|
||||||
io_tasks: Mutex::new(Some((input_task, output_task))),
|
io_tasks: Mutex::new(Some((input_task, output_task))),
|
||||||
output_done_rx: Mutex::new(Some(output_done_rx)),
|
output_done_rx: Mutex::new(Some(output_done_rx)),
|
||||||
root_path: root_path.to_path_buf(),
|
root_path: root_path.to_path_buf(),
|
||||||
options,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn initialize(mut self) -> Result<Arc<Self>> {
|
pub async fn initialize(mut self, options: Option<Value>) -> Result<Arc<Self>> {
|
||||||
let options = self.options.take();
|
let root_uri = Url::from_file_path(&self.root_path).unwrap();
|
||||||
let mut this = Arc::new(self);
|
|
||||||
let root_uri = Url::from_file_path(&this.root_path).unwrap();
|
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
let params = InitializeParams {
|
let params = InitializeParams {
|
||||||
process_id: Default::default(),
|
process_id: Default::default(),
|
||||||
|
@ -290,12 +269,13 @@ impl LanguageServer {
|
||||||
value_set: vec![
|
value_set: vec![
|
||||||
CodeActionKind::REFACTOR.as_str().into(),
|
CodeActionKind::REFACTOR.as_str().into(),
|
||||||
CodeActionKind::QUICKFIX.as_str().into(),
|
CodeActionKind::QUICKFIX.as_str().into(),
|
||||||
|
CodeActionKind::SOURCE.as_str().into(),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
data_support: Some(true),
|
data_support: Some(true),
|
||||||
resolve_support: Some(CodeActionCapabilityResolveSupport {
|
resolve_support: Some(CodeActionCapabilityResolveSupport {
|
||||||
properties: vec!["edit".to_string()],
|
properties: vec!["edit".to_string(), "command".to_string()],
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
|
@ -326,16 +306,14 @@ impl LanguageServer {
|
||||||
locale: Default::default(),
|
locale: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = this.request::<request::Initialize>(params).await?;
|
let response = self.request::<request::Initialize>(params).await?;
|
||||||
{
|
if let Some(info) = response.server_info {
|
||||||
let this = Arc::get_mut(&mut this).unwrap();
|
self.name = info.name;
|
||||||
if let Some(info) = response.server_info {
|
|
||||||
this.name = info.name;
|
|
||||||
}
|
|
||||||
this.capabilities = response.capabilities;
|
|
||||||
}
|
}
|
||||||
this.notify::<notification::Initialized>(InitializedParams {})?;
|
self.capabilities = response.capabilities;
|
||||||
Ok(this)
|
|
||||||
|
self.notify::<notification::Initialized>(InitializedParams {})?;
|
||||||
|
Ok(Arc::new(self))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shutdown(&self) -> Option<impl 'static + Send + Future<Output = Option<()>>> {
|
pub fn shutdown(&self) -> Option<impl 'static + Send + Future<Output = Option<()>>> {
|
||||||
|
@ -370,37 +348,42 @@ impl LanguageServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_notification<T, F>(&mut self, f: F) -> Subscription
|
#[must_use]
|
||||||
|
pub fn on_notification<T, F>(&self, f: F) -> Subscription
|
||||||
where
|
where
|
||||||
T: notification::Notification,
|
T: notification::Notification,
|
||||||
F: 'static + Send + Sync + FnMut(T::Params),
|
F: 'static + Send + FnMut(T::Params, AsyncAppContext),
|
||||||
{
|
{
|
||||||
self.on_custom_notification(T::METHOD, f)
|
self.on_custom_notification(T::METHOD, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_request<T, F>(&mut self, f: F) -> Subscription
|
#[must_use]
|
||||||
|
pub fn on_request<T, F, Fut>(&self, f: F) -> Subscription
|
||||||
where
|
where
|
||||||
T: request::Request,
|
T: request::Request,
|
||||||
F: 'static + Send + Sync + FnMut(T::Params) -> Result<T::Result>,
|
T::Params: 'static + Send,
|
||||||
|
F: 'static + Send + FnMut(T::Params, AsyncAppContext) -> Fut,
|
||||||
|
Fut: 'static + Future<Output = Result<T::Result>>,
|
||||||
{
|
{
|
||||||
self.on_custom_request(T::METHOD, f)
|
self.on_custom_request(T::METHOD, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_custom_notification<Params, F>(
|
pub fn remove_request_handler<T: request::Request>(&self) {
|
||||||
&mut self,
|
self.notification_handlers.lock().remove(T::METHOD);
|
||||||
method: &'static str,
|
}
|
||||||
mut f: F,
|
|
||||||
) -> Subscription
|
#[must_use]
|
||||||
|
pub fn on_custom_notification<Params, F>(&self, method: &'static str, mut f: F) -> Subscription
|
||||||
where
|
where
|
||||||
F: 'static + Send + Sync + FnMut(Params),
|
F: 'static + Send + FnMut(Params, AsyncAppContext),
|
||||||
Params: DeserializeOwned,
|
Params: DeserializeOwned,
|
||||||
{
|
{
|
||||||
let prev_handler = self.notification_handlers.write().insert(
|
let prev_handler = self.notification_handlers.lock().insert(
|
||||||
method,
|
method,
|
||||||
Box::new(move |_, params, _| {
|
Box::new(move |_, params, cx| {
|
||||||
let params = serde_json::from_str(params)?;
|
if let Some(params) = serde_json::from_str(params).log_err() {
|
||||||
f(params);
|
f(params, cx);
|
||||||
Ok(())
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
|
@ -413,26 +396,52 @@ impl LanguageServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_custom_request<Params, Res, F>(
|
#[must_use]
|
||||||
&mut self,
|
pub fn on_custom_request<Params, Res, Fut, F>(
|
||||||
|
&self,
|
||||||
method: &'static str,
|
method: &'static str,
|
||||||
mut f: F,
|
mut f: F,
|
||||||
) -> Subscription
|
) -> Subscription
|
||||||
where
|
where
|
||||||
F: 'static + Send + Sync + FnMut(Params) -> Result<Res>,
|
F: 'static + Send + FnMut(Params, AsyncAppContext) -> Fut,
|
||||||
Params: DeserializeOwned,
|
Fut: 'static + Future<Output = Result<Res>>,
|
||||||
|
Params: DeserializeOwned + Send + 'static,
|
||||||
Res: Serialize,
|
Res: Serialize,
|
||||||
{
|
{
|
||||||
let prev_handler = self.notification_handlers.write().insert(
|
let outbound_tx = self.outbound_tx.clone();
|
||||||
|
let prev_handler = self.notification_handlers.lock().insert(
|
||||||
method,
|
method,
|
||||||
Box::new(move |id, params, tx| {
|
Box::new(move |id, params, cx| {
|
||||||
if let Some(id) = id {
|
if let Some(id) = id {
|
||||||
let params = serde_json::from_str(params)?;
|
if let Some(params) = serde_json::from_str(params).log_err() {
|
||||||
let result = f(params)?;
|
let response = f(params, cx.clone());
|
||||||
let response = serde_json::to_vec(&Response { id, result })?;
|
cx.foreground()
|
||||||
tx.try_send(response)?;
|
.spawn({
|
||||||
|
let outbound_tx = outbound_tx.clone();
|
||||||
|
async move {
|
||||||
|
let response = match response.await {
|
||||||
|
Ok(result) => Response {
|
||||||
|
id,
|
||||||
|
result: Some(result),
|
||||||
|
error: None,
|
||||||
|
},
|
||||||
|
Err(error) => Response {
|
||||||
|
id,
|
||||||
|
result: None,
|
||||||
|
error: Some(Error {
|
||||||
|
message: error.to_string(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if let Some(response) = serde_json::to_vec(&response).log_err()
|
||||||
|
{
|
||||||
|
outbound_tx.try_send(response).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
|
@ -458,7 +467,7 @@ impl LanguageServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request<T: request::Request>(
|
pub fn request<T: request::Request>(
|
||||||
self: &Arc<Self>,
|
&self,
|
||||||
params: T::Params,
|
params: T::Params,
|
||||||
) -> impl Future<Output = Result<T::Result>>
|
) -> impl Future<Output = Result<T::Result>>
|
||||||
where
|
where
|
||||||
|
@ -549,36 +558,16 @@ impl Subscription {
|
||||||
|
|
||||||
impl Drop for Subscription {
|
impl Drop for Subscription {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.notification_handlers.write().remove(self.method);
|
self.notification_handlers.lock().remove(self.method);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub struct FakeLanguageServer {
|
pub struct FakeLanguageServer {
|
||||||
handlers: FakeLanguageServerHandlers,
|
server: Arc<LanguageServer>,
|
||||||
outgoing_tx: futures::channel::mpsc::UnboundedSender<Vec<u8>>,
|
notifications_rx: channel::Receiver<(String, String)>,
|
||||||
incoming_rx: futures::channel::mpsc::UnboundedReceiver<Vec<u8>>,
|
|
||||||
_input_task: Task<Result<()>>,
|
|
||||||
_output_task: Task<Result<()>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
type FakeLanguageServerHandlers = Arc<
|
|
||||||
Mutex<
|
|
||||||
HashMap<
|
|
||||||
&'static str,
|
|
||||||
Box<
|
|
||||||
dyn Send
|
|
||||||
+ FnMut(
|
|
||||||
usize,
|
|
||||||
&[u8],
|
|
||||||
gpui::AsyncAppContext,
|
|
||||||
) -> futures::future::BoxFuture<'static, Vec<u8>>,
|
|
||||||
>,
|
|
||||||
>,
|
|
||||||
>,
|
|
||||||
>;
|
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
impl LanguageServer {
|
impl LanguageServer {
|
||||||
pub fn full_capabilities() -> ServerCapabilities {
|
pub fn full_capabilities() -> ServerCapabilities {
|
||||||
|
@ -591,177 +580,101 @@ impl LanguageServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fake(cx: &mut gpui::MutableAppContext) -> (Self, FakeLanguageServer) {
|
pub fn fake(cx: AsyncAppContext) -> (Self, FakeLanguageServer) {
|
||||||
Self::fake_with_capabilities(Self::full_capabilities(), cx)
|
Self::fake_with_capabilities(Self::full_capabilities(), cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fake_with_capabilities(
|
pub fn fake_with_capabilities(
|
||||||
capabilities: ServerCapabilities,
|
capabilities: ServerCapabilities,
|
||||||
cx: &mut gpui::MutableAppContext,
|
cx: AsyncAppContext,
|
||||||
) -> (Self, FakeLanguageServer) {
|
) -> (Self, FakeLanguageServer) {
|
||||||
let (stdin_writer, stdin_reader) = async_pipe::pipe();
|
let (stdin_writer, stdin_reader) = async_pipe::pipe();
|
||||||
let (stdout_writer, stdout_reader) = async_pipe::pipe();
|
let (stdout_writer, stdout_reader) = async_pipe::pipe();
|
||||||
|
let (notifications_tx, notifications_rx) = channel::unbounded();
|
||||||
|
|
||||||
let mut fake = FakeLanguageServer::new(stdin_reader, stdout_writer, cx);
|
|
||||||
fake.handle_request::<request::Initialize, _, _>({
|
|
||||||
let capabilities = capabilities.clone();
|
|
||||||
move |_, _| {
|
|
||||||
let capabilities = capabilities.clone();
|
|
||||||
async move {
|
|
||||||
InitializeResult {
|
|
||||||
capabilities,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let executor = cx.background().clone();
|
|
||||||
let server = Self::new_internal(
|
let server = Self::new_internal(
|
||||||
0,
|
0,
|
||||||
stdin_writer,
|
stdin_writer,
|
||||||
stdout_reader,
|
stdout_reader,
|
||||||
Path::new("/"),
|
Path::new("/"),
|
||||||
None,
|
cx.clone(),
|
||||||
executor,
|
|_| {},
|
||||||
);
|
);
|
||||||
|
let fake = FakeLanguageServer {
|
||||||
|
server: Arc::new(Self::new_internal(
|
||||||
|
0,
|
||||||
|
stdout_writer,
|
||||||
|
stdin_reader,
|
||||||
|
Path::new("/"),
|
||||||
|
cx.clone(),
|
||||||
|
move |msg| {
|
||||||
|
notifications_tx
|
||||||
|
.try_send((msg.method.to_string(), msg.params.get().to_string()))
|
||||||
|
.ok();
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
notifications_rx,
|
||||||
|
};
|
||||||
|
fake.handle_request::<request::Initialize, _, _>({
|
||||||
|
let capabilities = capabilities.clone();
|
||||||
|
move |_, _| {
|
||||||
|
let capabilities = capabilities.clone();
|
||||||
|
async move {
|
||||||
|
Ok(InitializeResult {
|
||||||
|
capabilities,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
(server, fake)
|
(server, fake)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
impl FakeLanguageServer {
|
impl FakeLanguageServer {
|
||||||
fn new(
|
pub fn notify<T: notification::Notification>(&self, params: T::Params) {
|
||||||
stdin: async_pipe::PipeReader,
|
self.server.notify::<T>(params).ok();
|
||||||
stdout: async_pipe::PipeWriter,
|
|
||||||
cx: &mut gpui::MutableAppContext,
|
|
||||||
) -> Self {
|
|
||||||
use futures::StreamExt as _;
|
|
||||||
|
|
||||||
let (incoming_tx, incoming_rx) = futures::channel::mpsc::unbounded();
|
|
||||||
let (outgoing_tx, mut outgoing_rx) = futures::channel::mpsc::unbounded();
|
|
||||||
let handlers = FakeLanguageServerHandlers::default();
|
|
||||||
|
|
||||||
let input_task = cx.spawn(|cx| {
|
|
||||||
let handlers = handlers.clone();
|
|
||||||
let outgoing_tx = outgoing_tx.clone();
|
|
||||||
async move {
|
|
||||||
let mut buffer = Vec::new();
|
|
||||||
let mut stdin = smol::io::BufReader::new(stdin);
|
|
||||||
while Self::receive(&mut stdin, &mut buffer).await.is_ok() {
|
|
||||||
cx.background().simulate_random_delay().await;
|
|
||||||
|
|
||||||
if let Ok(request) = serde_json::from_slice::<AnyRequest>(&buffer) {
|
|
||||||
assert_eq!(request.jsonrpc, JSON_RPC_VERSION);
|
|
||||||
|
|
||||||
let response;
|
|
||||||
if let Some(handler) = handlers.lock().get_mut(request.method) {
|
|
||||||
response =
|
|
||||||
handler(request.id, request.params.get().as_bytes(), cx.clone())
|
|
||||||
.await;
|
|
||||||
log::debug!("handled lsp request. method:{}", request.method);
|
|
||||||
} else {
|
|
||||||
response = serde_json::to_vec(&AnyResponse {
|
|
||||||
id: request.id,
|
|
||||||
error: Some(Error {
|
|
||||||
message: "no handler".to_string(),
|
|
||||||
}),
|
|
||||||
result: None,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
log::debug!("unhandled lsp request. method:{}", request.method);
|
|
||||||
}
|
|
||||||
outgoing_tx.unbounded_send(response)?;
|
|
||||||
} else {
|
|
||||||
incoming_tx.unbounded_send(buffer.clone())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok::<_, anyhow::Error>(())
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let output_task = cx.background().spawn(async move {
|
|
||||||
let mut stdout = smol::io::BufWriter::new(stdout);
|
|
||||||
while let Some(message) = outgoing_rx.next().await {
|
|
||||||
stdout.write_all(CONTENT_LEN_HEADER.as_bytes()).await?;
|
|
||||||
stdout
|
|
||||||
.write_all((format!("{}", message.len())).as_bytes())
|
|
||||||
.await?;
|
|
||||||
stdout.write_all("\r\n\r\n".as_bytes()).await?;
|
|
||||||
stdout.write_all(&message).await?;
|
|
||||||
stdout.flush().await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
|
||||||
outgoing_tx,
|
|
||||||
incoming_rx,
|
|
||||||
handlers,
|
|
||||||
_input_task: input_task,
|
|
||||||
_output_task: output_task,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn notify<T: notification::Notification>(&mut self, params: T::Params) {
|
|
||||||
let message = serde_json::to_vec(&Notification {
|
|
||||||
jsonrpc: JSON_RPC_VERSION,
|
|
||||||
method: T::METHOD,
|
|
||||||
params,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
self.outgoing_tx.unbounded_send(message).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn receive_notification<T: notification::Notification>(&mut self) -> T::Params {
|
pub async fn receive_notification<T: notification::Notification>(&mut self) -> T::Params {
|
||||||
use futures::StreamExt as _;
|
use futures::StreamExt as _;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let bytes = self.incoming_rx.next().await.unwrap();
|
let (method, params) = self.notifications_rx.next().await.unwrap();
|
||||||
if let Ok(notification) = serde_json::from_slice::<Notification<T::Params>>(&bytes) {
|
if &method == T::METHOD {
|
||||||
assert_eq!(notification.method, T::METHOD);
|
return serde_json::from_str::<T::Params>(¶ms).unwrap();
|
||||||
return notification.params;
|
|
||||||
} else {
|
} else {
|
||||||
log::info!(
|
log::info!("skipping message in fake language server {:?}", params);
|
||||||
"skipping message in fake language server {:?}",
|
|
||||||
std::str::from_utf8(&bytes)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_request<T, F, Fut>(
|
pub fn handle_request<T, F, Fut>(
|
||||||
&mut self,
|
&self,
|
||||||
mut handler: F,
|
mut handler: F,
|
||||||
) -> futures::channel::mpsc::UnboundedReceiver<()>
|
) -> futures::channel::mpsc::UnboundedReceiver<()>
|
||||||
where
|
where
|
||||||
T: 'static + request::Request,
|
T: 'static + request::Request,
|
||||||
|
T::Params: 'static + Send,
|
||||||
F: 'static + Send + FnMut(T::Params, gpui::AsyncAppContext) -> Fut,
|
F: 'static + Send + FnMut(T::Params, gpui::AsyncAppContext) -> Fut,
|
||||||
Fut: 'static + Send + Future<Output = T::Result>,
|
Fut: 'static + Send + Future<Output = Result<T::Result>>,
|
||||||
{
|
{
|
||||||
use futures::FutureExt as _;
|
|
||||||
|
|
||||||
let (responded_tx, responded_rx) = futures::channel::mpsc::unbounded();
|
let (responded_tx, responded_rx) = futures::channel::mpsc::unbounded();
|
||||||
self.handlers.lock().insert(
|
self.server.remove_request_handler::<T>();
|
||||||
T::METHOD,
|
self.server
|
||||||
Box::new(move |id, params, cx| {
|
.on_request::<T, _, _>(move |params, cx| {
|
||||||
let result = handler(serde_json::from_slice::<T::Params>(params).unwrap(), cx);
|
let result = handler(params, cx.clone());
|
||||||
let responded_tx = responded_tx.clone();
|
let responded_tx = responded_tx.clone();
|
||||||
async move {
|
async move {
|
||||||
|
cx.background().simulate_random_delay().await;
|
||||||
let result = result.await;
|
let result = result.await;
|
||||||
let result = serde_json::to_string(&result).unwrap();
|
|
||||||
let result = serde_json::from_str::<&RawValue>(&result).unwrap();
|
|
||||||
let response = AnyResponse {
|
|
||||||
id,
|
|
||||||
error: None,
|
|
||||||
result: Some(result),
|
|
||||||
};
|
|
||||||
responded_tx.unbounded_send(()).ok();
|
responded_tx.unbounded_send(()).ok();
|
||||||
serde_json::to_vec(&response).unwrap()
|
result
|
||||||
}
|
}
|
||||||
.boxed()
|
})
|
||||||
}),
|
.detach();
|
||||||
);
|
|
||||||
responded_rx
|
responded_rx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -769,7 +682,7 @@ impl FakeLanguageServer {
|
||||||
where
|
where
|
||||||
T: 'static + request::Request,
|
T: 'static + request::Request,
|
||||||
{
|
{
|
||||||
self.handlers.lock().remove(T::METHOD);
|
self.server.remove_request_handler::<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start_progress(&mut self, token: impl Into<String>) {
|
pub async fn start_progress(&mut self, token: impl Into<String>) {
|
||||||
|
@ -785,25 +698,6 @@ impl FakeLanguageServer {
|
||||||
value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(Default::default())),
|
value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(Default::default())),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(
|
|
||||||
stdin: &mut smol::io::BufReader<async_pipe::PipeReader>,
|
|
||||||
buffer: &mut Vec<u8>,
|
|
||||||
) -> Result<()> {
|
|
||||||
buffer.clear();
|
|
||||||
stdin.read_until(b'\n', buffer).await?;
|
|
||||||
stdin.read_until(b'\n', buffer).await?;
|
|
||||||
let message_len: usize = std::str::from_utf8(buffer)
|
|
||||||
.unwrap()
|
|
||||||
.strip_prefix(CONTENT_LEN_HEADER)
|
|
||||||
.ok_or_else(|| anyhow!("invalid content length header"))?
|
|
||||||
.trim_end()
|
|
||||||
.parse()
|
|
||||||
.unwrap();
|
|
||||||
buffer.resize(message_len, 0);
|
|
||||||
stdin.read_exact(buffer).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ClearResponseHandlers(Arc<Mutex<HashMap<usize, ResponseHandler>>>);
|
struct ClearResponseHandlers(Arc<Mutex<HashMap<usize, ResponseHandler>>>);
|
||||||
|
@ -828,22 +722,22 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_fake(cx: &mut TestAppContext) {
|
async fn test_fake(cx: &mut TestAppContext) {
|
||||||
let (mut server, mut fake) = cx.update(LanguageServer::fake);
|
let (server, mut fake) = LanguageServer::fake(cx.to_async());
|
||||||
|
|
||||||
let (message_tx, message_rx) = channel::unbounded();
|
let (message_tx, message_rx) = channel::unbounded();
|
||||||
let (diagnostics_tx, diagnostics_rx) = channel::unbounded();
|
let (diagnostics_tx, diagnostics_rx) = channel::unbounded();
|
||||||
server
|
server
|
||||||
.on_notification::<notification::ShowMessage, _>(move |params| {
|
.on_notification::<notification::ShowMessage, _>(move |params, _| {
|
||||||
message_tx.try_send(params).unwrap()
|
message_tx.try_send(params).unwrap()
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
server
|
server
|
||||||
.on_notification::<notification::PublishDiagnostics, _>(move |params| {
|
.on_notification::<notification::PublishDiagnostics, _>(move |params, _| {
|
||||||
diagnostics_tx.try_send(params).unwrap()
|
diagnostics_tx.try_send(params).unwrap()
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
let server = server.initialize().await.unwrap();
|
let server = server.initialize(None).await.unwrap();
|
||||||
server
|
server
|
||||||
.notify::<notification::DidOpenTextDocument>(DidOpenTextDocumentParams {
|
.notify::<notification::DidOpenTextDocument>(DidOpenTextDocumentParams {
|
||||||
text_document: TextDocumentItem::new(
|
text_document: TextDocumentItem::new(
|
||||||
|
@ -878,7 +772,7 @@ mod tests {
|
||||||
"file://b/c"
|
"file://b/c"
|
||||||
);
|
);
|
||||||
|
|
||||||
fake.handle_request::<request::Shutdown, _, _>(|_, _| async move {});
|
fake.handle_request::<request::Shutdown, _, _>(|_, _| async move { Ok(()) });
|
||||||
|
|
||||||
drop(server);
|
drop(server);
|
||||||
fake.receive_notification::<notification::Exit>().await;
|
fake.receive_notification::<notification::Exit>().await;
|
||||||
|
|
|
@ -1325,7 +1325,7 @@ impl Project {
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
cx.spawn_weak(|this, mut cx| async move {
|
cx.spawn_weak(|this, mut cx| async move {
|
||||||
let mut language_server = language_server?.await.log_err()?;
|
let language_server = language_server?.await.log_err()?;
|
||||||
let this = this.upgrade(&cx)?;
|
let this = this.upgrade(&cx)?;
|
||||||
let (language_server_events_tx, language_server_events_rx) =
|
let (language_server_events_tx, language_server_events_rx) =
|
||||||
smol::channel::unbounded();
|
smol::channel::unbounded();
|
||||||
|
@ -1333,7 +1333,7 @@ impl Project {
|
||||||
language_server
|
language_server
|
||||||
.on_notification::<lsp::notification::PublishDiagnostics, _>({
|
.on_notification::<lsp::notification::PublishDiagnostics, _>({
|
||||||
let language_server_events_tx = language_server_events_tx.clone();
|
let language_server_events_tx = language_server_events_tx.clone();
|
||||||
move |params| {
|
move |params, _| {
|
||||||
language_server_events_tx
|
language_server_events_tx
|
||||||
.try_send(LanguageServerEvent::DiagnosticsUpdate(params))
|
.try_send(LanguageServerEvent::DiagnosticsUpdate(params))
|
||||||
.ok();
|
.ok();
|
||||||
|
@ -1342,31 +1342,33 @@ impl Project {
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
language_server
|
language_server
|
||||||
.on_request::<lsp::request::WorkspaceConfiguration, _>({
|
.on_request::<lsp::request::WorkspaceConfiguration, _, _>({
|
||||||
let settings = this
|
let settings = this
|
||||||
.read_with(&cx, |this, _| this.language_server_settings.clone());
|
.read_with(&cx, |this, _| this.language_server_settings.clone());
|
||||||
move |params| {
|
move |params, _| {
|
||||||
let settings = settings.lock();
|
let settings = settings.lock().clone();
|
||||||
Ok(params
|
async move {
|
||||||
.items
|
Ok(params
|
||||||
.into_iter()
|
.items
|
||||||
.map(|item| {
|
.into_iter()
|
||||||
if let Some(section) = &item.section {
|
.map(|item| {
|
||||||
settings
|
if let Some(section) = &item.section {
|
||||||
.get(section)
|
settings
|
||||||
.cloned()
|
.get(section)
|
||||||
.unwrap_or(serde_json::Value::Null)
|
.cloned()
|
||||||
} else {
|
.unwrap_or(serde_json::Value::Null)
|
||||||
settings.clone()
|
} else {
|
||||||
}
|
settings.clone()
|
||||||
})
|
}
|
||||||
.collect())
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
language_server
|
language_server
|
||||||
.on_notification::<lsp::notification::Progress, _>(move |params| {
|
.on_notification::<lsp::notification::Progress, _>(move |params, _| {
|
||||||
let token = match params.token {
|
let token = match params.token {
|
||||||
lsp::NumberOrString::String(token) => token,
|
lsp::NumberOrString::String(token) => token,
|
||||||
lsp::NumberOrString::Number(token) => {
|
lsp::NumberOrString::Number(token) => {
|
||||||
|
@ -1406,6 +1408,11 @@ impl Project {
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
let language_server = language_server
|
||||||
|
.initialize(adapter.initialization_options())
|
||||||
|
.await
|
||||||
|
.log_err()?;
|
||||||
|
|
||||||
// Process all the LSP events.
|
// Process all the LSP events.
|
||||||
cx.spawn(|mut cx| {
|
cx.spawn(|mut cx| {
|
||||||
let this = this.downgrade();
|
let this = this.downgrade();
|
||||||
|
@ -1424,7 +1431,6 @@ impl Project {
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
let language_server = language_server.initialize().await.log_err()?;
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.language_servers
|
this.language_servers
|
||||||
.insert(key.clone(), (adapter, language_server.clone()));
|
.insert(key.clone(), (adapter, language_server.clone()));
|
||||||
|
@ -4974,9 +4980,9 @@ mod tests {
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut rust_shutdown_requests = fake_rust_server
|
let mut rust_shutdown_requests = fake_rust_server
|
||||||
.handle_request::<lsp::request::Shutdown, _, _>(|_, _| future::ready(()));
|
.handle_request::<lsp::request::Shutdown, _, _>(|_, _| future::ready(Ok(())));
|
||||||
let mut json_shutdown_requests = fake_json_server
|
let mut json_shutdown_requests = fake_json_server
|
||||||
.handle_request::<lsp::request::Shutdown, _, _>(|_, _| future::ready(()));
|
.handle_request::<lsp::request::Shutdown, _, _>(|_, _| future::ready(Ok(())));
|
||||||
futures::join!(rust_shutdown_requests.next(), json_shutdown_requests.next());
|
futures::join!(rust_shutdown_requests.next(), json_shutdown_requests.next());
|
||||||
|
|
||||||
let mut fake_rust_server = fake_rust_servers.next().await.unwrap();
|
let mut fake_rust_server = fake_rust_servers.next().await.unwrap();
|
||||||
|
@ -5917,19 +5923,11 @@ mod tests {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let buffer = project
|
let buffer = project
|
||||||
.update(cx, |project, cx| {
|
.update(cx, |project, cx| project.open_buffer((worktree_id, ""), cx))
|
||||||
project.open_buffer(
|
|
||||||
ProjectPath {
|
|
||||||
worktree_id,
|
|
||||||
path: Path::new("").into(),
|
|
||||||
},
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut fake_server = fake_servers.next().await.unwrap();
|
let fake_server = fake_servers.next().await.unwrap();
|
||||||
fake_server.handle_request::<lsp::request::GotoDefinition, _, _>(|params, _| async move {
|
fake_server.handle_request::<lsp::request::GotoDefinition, _, _>(|params, _| async move {
|
||||||
let params = params.text_document_position_params;
|
let params = params.text_document_position_params;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -5938,9 +5936,11 @@ mod tests {
|
||||||
);
|
);
|
||||||
assert_eq!(params.position, lsp::Position::new(0, 22));
|
assert_eq!(params.position, lsp::Position::new(0, 22));
|
||||||
|
|
||||||
Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
|
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
|
||||||
lsp::Url::from_file_path("/dir/a.rs").unwrap(),
|
lsp::Location::new(
|
||||||
lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
|
lsp::Url::from_file_path("/dir/a.rs").unwrap(),
|
||||||
|
lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
|
||||||
|
),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -6854,7 +6854,7 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut fake_server = fake_servers.next().await.unwrap();
|
let fake_server = fake_servers.next().await.unwrap();
|
||||||
|
|
||||||
let response = project.update(cx, |project, cx| {
|
let response = project.update(cx, |project, cx| {
|
||||||
project.prepare_rename(buffer.clone(), 7, cx)
|
project.prepare_rename(buffer.clone(), 7, cx)
|
||||||
|
@ -6863,10 +6863,10 @@ mod tests {
|
||||||
.handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
|
.handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
|
||||||
assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
|
assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
|
||||||
assert_eq!(params.position, lsp::Position::new(0, 7));
|
assert_eq!(params.position, lsp::Position::new(0, 7));
|
||||||
Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
|
Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
|
||||||
lsp::Position::new(0, 6),
|
lsp::Position::new(0, 6),
|
||||||
lsp::Position::new(0, 9),
|
lsp::Position::new(0, 9),
|
||||||
)))
|
))))
|
||||||
})
|
})
|
||||||
.next()
|
.next()
|
||||||
.await
|
.await
|
||||||
|
@ -6889,7 +6889,7 @@ mod tests {
|
||||||
lsp::Position::new(0, 7)
|
lsp::Position::new(0, 7)
|
||||||
);
|
);
|
||||||
assert_eq!(params.new_name, "THREE");
|
assert_eq!(params.new_name, "THREE");
|
||||||
Some(lsp::WorkspaceEdit {
|
Ok(Some(lsp::WorkspaceEdit {
|
||||||
changes: Some(
|
changes: Some(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
|
@ -6926,7 +6926,7 @@ mod tests {
|
||||||
.collect(),
|
.collect(),
|
||||||
),
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
}))
|
||||||
})
|
})
|
||||||
.next()
|
.next()
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -2342,7 +2342,7 @@ mod tests {
|
||||||
Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
|
Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
buffer_b
|
buffer_b
|
||||||
.condition(&cx_b, |buffer, _| !buffer.completion_triggers().is_empty())
|
.condition(&cx_b, |buffer, _| !buffer.completion_triggers().is_empty())
|
||||||
.await;
|
.await;
|
||||||
|
@ -2368,7 +2368,7 @@ mod tests {
|
||||||
lsp::Position::new(0, 14),
|
lsp::Position::new(0, 14),
|
||||||
);
|
);
|
||||||
|
|
||||||
Some(lsp::CompletionResponse::Array(vec![
|
Ok(Some(lsp::CompletionResponse::Array(vec![
|
||||||
lsp::CompletionItem {
|
lsp::CompletionItem {
|
||||||
label: "first_method(…)".into(),
|
label: "first_method(…)".into(),
|
||||||
detail: Some("fn(&mut self, B) -> C".into()),
|
detail: Some("fn(&mut self, B) -> C".into()),
|
||||||
|
@ -2395,7 +2395,7 @@ mod tests {
|
||||||
insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
|
insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
]))
|
])))
|
||||||
})
|
})
|
||||||
.next()
|
.next()
|
||||||
.await
|
.await
|
||||||
|
@ -2425,7 +2425,7 @@ mod tests {
|
||||||
fake_language_server.handle_request::<lsp::request::ResolveCompletionItem, _, _>(
|
fake_language_server.handle_request::<lsp::request::ResolveCompletionItem, _, _>(
|
||||||
|params, _| async move {
|
|params, _| async move {
|
||||||
assert_eq!(params.label, "first_method(…)");
|
assert_eq!(params.label, "first_method(…)");
|
||||||
lsp::CompletionItem {
|
Ok(lsp::CompletionItem {
|
||||||
label: "first_method(…)".into(),
|
label: "first_method(…)".into(),
|
||||||
detail: Some("fn(&mut self, B) -> C".into()),
|
detail: Some("fn(&mut self, B) -> C".into()),
|
||||||
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
|
@ -2441,7 +2441,7 @@ mod tests {
|
||||||
}]),
|
}]),
|
||||||
insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
|
insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
})
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -2530,9 +2530,9 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
fake_language_server.handle_request::<lsp::request::Formatting, _, _>(|_, _| async move {
|
fake_language_server.handle_request::<lsp::request::Formatting, _, _>(|_, _| async move {
|
||||||
Some(vec![
|
Ok(Some(vec![
|
||||||
lsp::TextEdit {
|
lsp::TextEdit {
|
||||||
range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
|
range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
|
||||||
new_text: "h".to_string(),
|
new_text: "h".to_string(),
|
||||||
|
@ -2541,7 +2541,7 @@ mod tests {
|
||||||
range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 7)),
|
range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 7)),
|
||||||
new_text: "y".to_string(),
|
new_text: "y".to_string(),
|
||||||
},
|
},
|
||||||
])
|
]))
|
||||||
});
|
});
|
||||||
|
|
||||||
project_b
|
project_b
|
||||||
|
@ -2637,12 +2637,14 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Request the definition of a symbol as the guest.
|
// Request the definition of a symbol as the guest.
|
||||||
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(
|
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(
|
||||||
|_, _| async move {
|
|_, _| async move {
|
||||||
Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
|
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
|
||||||
lsp::Url::from_file_path("/root-2/b.rs").unwrap(),
|
lsp::Location::new(
|
||||||
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
lsp::Url::from_file_path("/root-2/b.rs").unwrap(),
|
||||||
|
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
||||||
|
),
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -2669,9 +2671,11 @@ mod tests {
|
||||||
// the previous call to `definition`.
|
// the previous call to `definition`.
|
||||||
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(
|
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(
|
||||||
|_, _| async move {
|
|_, _| async move {
|
||||||
Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
|
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
|
||||||
lsp::Url::from_file_path("/root-2/b.rs").unwrap(),
|
lsp::Location::new(
|
||||||
lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
|
lsp::Url::from_file_path("/root-2/b.rs").unwrap(),
|
||||||
|
lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
|
||||||
|
),
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -2778,14 +2782,14 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Request references to a symbol as the guest.
|
// Request references to a symbol as the guest.
|
||||||
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
fake_language_server.handle_request::<lsp::request::References, _, _>(
|
fake_language_server.handle_request::<lsp::request::References, _, _>(
|
||||||
|params, _| async move {
|
|params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document_position.text_document.uri.as_str(),
|
params.text_document_position.text_document.uri.as_str(),
|
||||||
"file:///root-1/one.rs"
|
"file:///root-1/one.rs"
|
||||||
);
|
);
|
||||||
Some(vec![
|
Ok(Some(vec![
|
||||||
lsp::Location {
|
lsp::Location {
|
||||||
uri: lsp::Url::from_file_path("/root-1/two.rs").unwrap(),
|
uri: lsp::Url::from_file_path("/root-1/two.rs").unwrap(),
|
||||||
range: lsp::Range::new(
|
range: lsp::Range::new(
|
||||||
|
@ -2807,7 +2811,7 @@ mod tests {
|
||||||
lsp::Position::new(0, 40),
|
lsp::Position::new(0, 40),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
])
|
]))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3018,7 +3022,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Request document highlights as the guest.
|
// Request document highlights as the guest.
|
||||||
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
fake_language_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>(
|
fake_language_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>(
|
||||||
|params, _| async move {
|
|params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -3033,7 +3037,7 @@ mod tests {
|
||||||
params.text_document_position_params.position,
|
params.text_document_position_params.position,
|
||||||
lsp::Position::new(0, 34)
|
lsp::Position::new(0, 34)
|
||||||
);
|
);
|
||||||
Some(vec![
|
Ok(Some(vec![
|
||||||
lsp::DocumentHighlight {
|
lsp::DocumentHighlight {
|
||||||
kind: Some(lsp::DocumentHighlightKind::WRITE),
|
kind: Some(lsp::DocumentHighlightKind::WRITE),
|
||||||
range: lsp::Range::new(
|
range: lsp::Range::new(
|
||||||
|
@ -3055,7 +3059,7 @@ mod tests {
|
||||||
lsp::Position::new(0, 47),
|
lsp::Position::new(0, 47),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
])
|
]))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3162,11 +3166,11 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
fake_language_server.handle_request::<lsp::request::WorkspaceSymbol, _, _>(
|
fake_language_server.handle_request::<lsp::request::WorkspaceSymbol, _, _>(
|
||||||
|_, _| async move {
|
|_, _| async move {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
Some(vec![lsp::SymbolInformation {
|
Ok(Some(vec![lsp::SymbolInformation {
|
||||||
name: "TWO".into(),
|
name: "TWO".into(),
|
||||||
location: lsp::Location {
|
location: lsp::Location {
|
||||||
uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
|
uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
|
||||||
|
@ -3176,7 +3180,7 @@ mod tests {
|
||||||
tags: None,
|
tags: None,
|
||||||
container_name: None,
|
container_name: None,
|
||||||
deprecated: None,
|
deprecated: None,
|
||||||
}])
|
}]))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3292,12 +3296,14 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(
|
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(
|
||||||
|_, _| async move {
|
|_, _| async move {
|
||||||
Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
|
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
|
||||||
lsp::Url::from_file_path("/root/b.rs").unwrap(),
|
lsp::Location::new(
|
||||||
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
lsp::Url::from_file_path("/root/b.rs").unwrap(),
|
||||||
|
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
||||||
|
),
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -3413,7 +3419,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
assert_eq!(params.range.start, lsp::Position::new(0, 0));
|
assert_eq!(params.range.start, lsp::Position::new(0, 0));
|
||||||
assert_eq!(params.range.end, lsp::Position::new(0, 0));
|
assert_eq!(params.range.end, lsp::Position::new(0, 0));
|
||||||
None
|
Ok(None)
|
||||||
})
|
})
|
||||||
.next()
|
.next()
|
||||||
.await;
|
.await;
|
||||||
|
@ -3433,7 +3439,7 @@ mod tests {
|
||||||
assert_eq!(params.range.start, lsp::Position::new(1, 31));
|
assert_eq!(params.range.start, lsp::Position::new(1, 31));
|
||||||
assert_eq!(params.range.end, lsp::Position::new(1, 31));
|
assert_eq!(params.range.end, lsp::Position::new(1, 31));
|
||||||
|
|
||||||
Some(vec![lsp::CodeActionOrCommand::CodeAction(
|
Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
|
||||||
lsp::CodeAction {
|
lsp::CodeAction {
|
||||||
title: "Inline into all callers".to_string(),
|
title: "Inline into all callers".to_string(),
|
||||||
edit: Some(lsp::WorkspaceEdit {
|
edit: Some(lsp::WorkspaceEdit {
|
||||||
|
@ -3475,7 +3481,7 @@ mod tests {
|
||||||
})),
|
})),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)])
|
)]))
|
||||||
})
|
})
|
||||||
.next()
|
.next()
|
||||||
.await;
|
.await;
|
||||||
|
@ -3498,7 +3504,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
|
fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
|
||||||
|_, _| async move {
|
|_, _| async move {
|
||||||
lsp::CodeAction {
|
Ok(lsp::CodeAction {
|
||||||
title: "Inline into all callers".to_string(),
|
title: "Inline into all callers".to_string(),
|
||||||
edit: Some(lsp::WorkspaceEdit {
|
edit: Some(lsp::WorkspaceEdit {
|
||||||
changes: Some(
|
changes: Some(
|
||||||
|
@ -3530,7 +3536,7 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
})
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3637,7 +3643,7 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.downcast::<Editor>()
|
.downcast::<Editor>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut fake_language_server = fake_language_servers.next().await.unwrap();
|
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
|
|
||||||
// Move cursor to a location that can be renamed.
|
// Move cursor to a location that can be renamed.
|
||||||
let prepare_rename = editor_b.update(cx_b, |editor, cx| {
|
let prepare_rename = editor_b.update(cx_b, |editor, cx| {
|
||||||
|
@ -3649,10 +3655,10 @@ mod tests {
|
||||||
.handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
|
.handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
|
||||||
assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
|
assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
|
||||||
assert_eq!(params.position, lsp::Position::new(0, 7));
|
assert_eq!(params.position, lsp::Position::new(0, 7));
|
||||||
Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
|
Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
|
||||||
lsp::Position::new(0, 6),
|
lsp::Position::new(0, 6),
|
||||||
lsp::Position::new(0, 9),
|
lsp::Position::new(0, 9),
|
||||||
)))
|
))))
|
||||||
})
|
})
|
||||||
.next()
|
.next()
|
||||||
.await
|
.await
|
||||||
|
@ -3686,7 +3692,7 @@ mod tests {
|
||||||
lsp::Position::new(0, 6)
|
lsp::Position::new(0, 6)
|
||||||
);
|
);
|
||||||
assert_eq!(params.new_name, "THREE");
|
assert_eq!(params.new_name, "THREE");
|
||||||
Some(lsp::WorkspaceEdit {
|
Ok(Some(lsp::WorkspaceEdit {
|
||||||
changes: Some(
|
changes: Some(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
|
@ -3723,7 +3729,7 @@ mod tests {
|
||||||
.collect(),
|
.collect(),
|
||||||
),
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
}))
|
||||||
})
|
})
|
||||||
.next()
|
.next()
|
||||||
.await
|
.await
|
||||||
|
@ -4894,36 +4900,38 @@ mod tests {
|
||||||
move |fake_server: &mut FakeLanguageServer| {
|
move |fake_server: &mut FakeLanguageServer| {
|
||||||
fake_server.handle_request::<lsp::request::Completion, _, _>(
|
fake_server.handle_request::<lsp::request::Completion, _, _>(
|
||||||
|_, _| async move {
|
|_, _| async move {
|
||||||
Some(lsp::CompletionResponse::Array(vec![lsp::CompletionItem {
|
Ok(Some(lsp::CompletionResponse::Array(vec![
|
||||||
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
lsp::CompletionItem {
|
||||||
range: lsp::Range::new(
|
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
lsp::Position::new(0, 0),
|
range: lsp::Range::new(
|
||||||
lsp::Position::new(0, 0),
|
lsp::Position::new(0, 0),
|
||||||
),
|
lsp::Position::new(0, 0),
|
||||||
new_text: "the-new-text".to_string(),
|
),
|
||||||
})),
|
new_text: "the-new-text".to_string(),
|
||||||
..Default::default()
|
})),
|
||||||
}]))
|
..Default::default()
|
||||||
|
},
|
||||||
|
])))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
|
fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
|
||||||
|_, _| async move {
|
|_, _| async move {
|
||||||
Some(vec![lsp::CodeActionOrCommand::CodeAction(
|
Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
|
||||||
lsp::CodeAction {
|
lsp::CodeAction {
|
||||||
title: "the-code-action".to_string(),
|
title: "the-code-action".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)])
|
)]))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
fake_server.handle_request::<lsp::request::PrepareRenameRequest, _, _>(
|
fake_server.handle_request::<lsp::request::PrepareRenameRequest, _, _>(
|
||||||
|params, _| async move {
|
|params, _| async move {
|
||||||
Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
|
Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
|
||||||
params.position,
|
params.position,
|
||||||
params.position,
|
params.position,
|
||||||
)))
|
))))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -4941,7 +4949,7 @@ mod tests {
|
||||||
.map(|_| files.choose(&mut *rng).unwrap())
|
.map(|_| files.choose(&mut *rng).unwrap())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
log::info!("LSP: Returning definitions in files {:?}", &files);
|
log::info!("LSP: Returning definitions in files {:?}", &files);
|
||||||
Some(lsp::GotoDefinitionResponse::Array(
|
Ok(Some(lsp::GotoDefinitionResponse::Array(
|
||||||
files
|
files
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|file| lsp::Location {
|
.map(|file| lsp::Location {
|
||||||
|
@ -4949,7 +4957,7 @@ mod tests {
|
||||||
range: Default::default(),
|
range: Default::default(),
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -4991,7 +4999,7 @@ mod tests {
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
async move { highlights }
|
async move { Ok(highlights) }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue