Allow returning futures in fake language server request handlers

This commit is contained in:
Antonio Scandurra 2022-03-28 10:44:32 +02:00
parent 3ae5fc74c9
commit cbd266052d
4 changed files with 250 additions and 197 deletions

View File

@ -8850,31 +8850,34 @@ mod tests {
position: Point,
completions: Vec<(Range<Point>, &'static str)>,
) {
fake.handle_request::<lsp::request::Completion, _>(move |params, _| {
assert_eq!(
params.text_document_position.text_document.uri,
lsp::Url::from_file_path(path).unwrap()
);
assert_eq!(
params.text_document_position.position,
lsp::Position::new(position.row, position.column)
);
Some(lsp::CompletionResponse::Array(
completions
.iter()
.map(|(range, new_text)| lsp::CompletionItem {
label: new_text.to_string(),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(
lsp::Position::new(range.start.row, range.start.column),
lsp::Position::new(range.start.row, range.start.column),
),
new_text: new_text.to_string(),
})),
..Default::default()
})
.collect(),
))
fake.handle_request::<lsp::request::Completion, _, _>(move |params, _| {
let completions = completions.clone();
async move {
assert_eq!(
params.text_document_position.text_document.uri,
lsp::Url::from_file_path(path).unwrap()
);
assert_eq!(
params.text_document_position.position,
lsp::Position::new(position.row, position.column)
);
Some(lsp::CompletionResponse::Array(
completions
.iter()
.map(|(range, new_text)| lsp::CompletionItem {
label: new_text.to_string(),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(
lsp::Position::new(range.start.row, range.start.column),
lsp::Position::new(range.start.row, range.start.column),
),
new_text: new_text.to_string(),
})),
..Default::default()
})
.collect(),
))
}
})
.next()
.await;
@ -8884,18 +8887,21 @@ mod tests {
fake: &mut FakeLanguageServer,
edit: Option<(Range<Point>, &'static str)>,
) {
fake.handle_request::<lsp::request::ResolveCompletionItem, _>(move |_, _| {
lsp::CompletionItem {
additional_text_edits: edit.clone().map(|(range, new_text)| {
vec![lsp::TextEdit::new(
lsp::Range::new(
lsp::Position::new(range.start.row, range.start.column),
lsp::Position::new(range.end.row, range.end.column),
),
new_text.to_string(),
)]
}),
..Default::default()
fake.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _| {
let edit = edit.clone();
async move {
lsp::CompletionItem {
additional_text_edits: edit.map(|(range, new_text)| {
vec![lsp::TextEdit::new(
lsp::Range::new(
lsp::Position::new(range.start.row, range.start.column),
lsp::Position::new(range.end.row, range.end.column),
),
new_text.to_string(),
)]
}),
..Default::default()
}
}
})
.next()

View File

@ -1,6 +1,8 @@
use anyhow::{anyhow, Context, Result};
use collections::HashMap;
use futures::{channel::oneshot, io::BufWriter, AsyncRead, AsyncWrite};
use futures::{
channel::oneshot, future::BoxFuture, io::BufWriter, AsyncRead, AsyncWrite, FutureExt,
};
use gpui::{executor, Task};
use parking_lot::{Mutex, RwLock};
use postage::{barrier, prelude::Stream};
@ -556,7 +558,10 @@ type FakeLanguageServerHandlers = Arc<
Mutex<
HashMap<
&'static str,
Box<dyn Send + FnMut(usize, &[u8], gpui::AsyncAppContext) -> Vec<u8>>,
Box<
dyn Send
+ FnMut(usize, &[u8], gpui::AsyncAppContext) -> BoxFuture<'static, Vec<u8>>,
>,
>,
>,
>;
@ -585,11 +590,16 @@ impl LanguageServer {
let (stdout_writer, stdout_reader) = async_pipe::pipe();
let mut fake = FakeLanguageServer::new(stdin_reader, stdout_writer, cx);
fake.handle_request::<request::Initialize, _>({
fake.handle_request::<request::Initialize, _, _>({
let capabilities = capabilities.clone();
move |_, _| InitializeResult {
capabilities: capabilities.clone(),
..Default::default()
move |_, _| {
let capabilities = capabilities.clone();
async move {
InitializeResult {
capabilities,
..Default::default()
}
}
}
});
@ -628,7 +638,8 @@ impl FakeLanguageServer {
let response;
if let Some(handler) = handlers.lock().get_mut(request.method) {
response =
handler(request.id, request.params.get().as_bytes(), cx.clone());
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 {
@ -704,28 +715,34 @@ impl FakeLanguageServer {
}
}
pub fn handle_request<T, F>(
pub fn handle_request<T, F, Fut>(
&mut self,
mut handler: F,
) -> futures::channel::mpsc::UnboundedReceiver<()>
where
T: 'static + request::Request,
F: 'static + Send + FnMut(T::Params, gpui::AsyncAppContext) -> T::Result,
F: 'static + Send + FnMut(T::Params, gpui::AsyncAppContext) -> Fut,
Fut: 'static + Send + Future<Output = T::Result>,
{
let (responded_tx, responded_rx) = futures::channel::mpsc::unbounded();
self.handlers.lock().insert(
T::METHOD,
Box::new(move |id, params, cx| {
let result = handler(serde_json::from_slice::<T::Params>(params).unwrap(), cx);
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();
serde_json::to_vec(&response).unwrap()
let responded_tx = responded_tx.clone();
async move {
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();
serde_json::to_vec(&response).unwrap()
}
.boxed()
}),
);
responded_rx
@ -844,7 +861,7 @@ mod tests {
"file://b/c"
);
fake.handle_request::<request::Shutdown, _>(|_, _| ());
fake.handle_request::<request::Shutdown, _, _>(|_, _| async move {});
drop(server);
fake.receive_notification::<notification::Exit>().await;

View File

@ -5789,7 +5789,7 @@ mod tests {
.unwrap();
let mut fake_server = fake_servers.next().await.unwrap();
fake_server.handle_request::<lsp::request::GotoDefinition, _>(move |params, _| {
fake_server.handle_request::<lsp::request::GotoDefinition, _, _>(|params, _| async move {
let params = params.text_document_position_params;
assert_eq!(
params.text_document.uri.to_file_path().unwrap(),
@ -6724,7 +6724,7 @@ mod tests {
project.prepare_rename(buffer.clone(), 7, cx)
});
fake_server
.handle_request::<lsp::request::PrepareRenameRequest, _>(|params, _| {
.handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
assert_eq!(params.position, lsp::Position::new(0, 7));
Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
@ -6743,7 +6743,7 @@ mod tests {
project.perform_rename(buffer.clone(), 7, "THREE".to_string(), true, cx)
});
fake_server
.handle_request::<lsp::request::Rename, _>(|params, _| {
.handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
assert_eq!(
params.text_document_position.text_document.uri.as_str(),
"file:///dir/one.rs"

View File

@ -2360,7 +2360,7 @@ mod tests {
// Return some completions from the host's language server.
cx_a.foreground().start_waiting();
fake_language_server
.handle_request::<lsp::request::Completion, _>(|params, _| {
.handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
assert_eq!(
params.text_document_position.text_document.uri,
lsp::Url::from_file_path("/a/main.rs").unwrap(),
@ -2424,8 +2424,8 @@ mod tests {
// Return a resolved completion from the host's language server.
// The resolved completion has an additional text edit.
fake_language_server.handle_request::<lsp::request::ResolveCompletionItem, _>(
|params, _| {
fake_language_server.handle_request::<lsp::request::ResolveCompletionItem, _, _>(
|params, _| async move {
assert_eq!(params.label, "first_method(…)");
lsp::CompletionItem {
label: "first_method(…)".into(),
@ -2535,7 +2535,7 @@ mod tests {
.unwrap();
let mut fake_language_server = fake_language_servers.next().await.unwrap();
fake_language_server.handle_request::<lsp::request::Formatting, _>(|_, _| {
fake_language_server.handle_request::<lsp::request::Formatting, _, _>(|_, _| async move {
Some(vec![
lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
@ -2644,12 +2644,14 @@ mod tests {
// Request the definition of a symbol as the guest.
let mut fake_language_server = fake_language_servers.next().await.unwrap();
fake_language_server.handle_request::<lsp::request::GotoDefinition, _>(|_, _| {
Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
lsp::Url::from_file_path("/root-2/b.rs").unwrap(),
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
)))
});
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(
|_, _| async move {
Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
lsp::Url::from_file_path("/root-2/b.rs").unwrap(),
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
)))
},
);
let definitions_1 = project_b
.update(cx_b, |p, cx| p.definition(&buffer_b, 23, cx))
@ -2671,12 +2673,14 @@ mod tests {
// Try getting more definitions for the same buffer, ensuring the buffer gets reused from
// the previous call to `definition`.
fake_language_server.handle_request::<lsp::request::GotoDefinition, _>(|_, _| {
Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
lsp::Url::from_file_path("/root-2/b.rs").unwrap(),
lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
)))
});
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(
|_, _| async move {
Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
lsp::Url::from_file_path("/root-2/b.rs").unwrap(),
lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
)))
},
);
let definitions_2 = project_b
.update(cx_b, |p, cx| p.definition(&buffer_b, 33, cx))
@ -2783,26 +2787,37 @@ mod tests {
// Request references to a symbol as the guest.
let mut fake_language_server = fake_language_servers.next().await.unwrap();
fake_language_server.handle_request::<lsp::request::References, _>(|params, _| {
assert_eq!(
params.text_document_position.text_document.uri.as_str(),
"file:///root-1/one.rs"
);
Some(vec![
lsp::Location {
uri: lsp::Url::from_file_path("/root-1/two.rs").unwrap(),
range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
},
lsp::Location {
uri: lsp::Url::from_file_path("/root-1/two.rs").unwrap(),
range: lsp::Range::new(lsp::Position::new(0, 35), lsp::Position::new(0, 38)),
},
lsp::Location {
uri: lsp::Url::from_file_path("/root-2/three.rs").unwrap(),
range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
},
])
});
fake_language_server.handle_request::<lsp::request::References, _, _>(
|params, _| async move {
assert_eq!(
params.text_document_position.text_document.uri.as_str(),
"file:///root-1/one.rs"
);
Some(vec![
lsp::Location {
uri: lsp::Url::from_file_path("/root-1/two.rs").unwrap(),
range: lsp::Range::new(
lsp::Position::new(0, 24),
lsp::Position::new(0, 27),
),
},
lsp::Location {
uri: lsp::Url::from_file_path("/root-1/two.rs").unwrap(),
range: lsp::Range::new(
lsp::Position::new(0, 35),
lsp::Position::new(0, 38),
),
},
lsp::Location {
uri: lsp::Url::from_file_path("/root-2/three.rs").unwrap(),
range: lsp::Range::new(
lsp::Position::new(0, 37),
lsp::Position::new(0, 40),
),
},
])
},
);
let references = project_b
.update(cx_b, |p, cx| p.references(&buffer_b, 7, cx))
@ -3012,8 +3027,8 @@ mod tests {
// Request document highlights as the guest.
let mut fake_language_server = fake_language_servers.next().await.unwrap();
fake_language_server.handle_request::<lsp::request::DocumentHighlightRequest, _>(
|params, _| {
fake_language_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>(
|params, _| async move {
assert_eq!(
params
.text_document_position_params
@ -3158,20 +3173,22 @@ mod tests {
.unwrap();
let mut fake_language_server = fake_language_servers.next().await.unwrap();
fake_language_server.handle_request::<lsp::request::WorkspaceSymbol, _>(|_, _| {
#[allow(deprecated)]
Some(vec![lsp::SymbolInformation {
name: "TWO".into(),
location: lsp::Location {
uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
},
kind: lsp::SymbolKind::CONSTANT,
tags: None,
container_name: None,
deprecated: None,
}])
});
fake_language_server.handle_request::<lsp::request::WorkspaceSymbol, _, _>(
|_, _| async move {
#[allow(deprecated)]
Some(vec![lsp::SymbolInformation {
name: "TWO".into(),
location: lsp::Location {
uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
},
kind: lsp::SymbolKind::CONSTANT,
tags: None,
container_name: None,
deprecated: None,
}])
},
);
// Request the definition of a symbol as the guest.
let symbols = project_b
@ -3289,12 +3306,14 @@ mod tests {
.unwrap();
let mut fake_language_server = fake_language_servers.next().await.unwrap();
fake_language_server.handle_request::<lsp::request::GotoDefinition, _>(|_, _| {
Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
lsp::Url::from_file_path("/root/b.rs").unwrap(),
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
)))
});
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(
|_, _| async move {
Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
lsp::Url::from_file_path("/root/b.rs").unwrap(),
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
)))
},
);
let definitions;
let buffer_b2;
@ -3402,7 +3421,7 @@ mod tests {
let mut fake_language_server = fake_language_servers.next().await.unwrap();
fake_language_server
.handle_request::<lsp::request::CodeActionRequest, _>(|params, _| {
.handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
assert_eq!(
params.text_document.uri,
lsp::Url::from_file_path("/a/main.rs").unwrap(),
@ -3421,7 +3440,7 @@ mod tests {
});
fake_language_server
.handle_request::<lsp::request::CodeActionRequest, _>(|params, _| {
.handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
assert_eq!(
params.text_document.uri,
lsp::Url::from_file_path("/a/main.rs").unwrap(),
@ -3492,41 +3511,43 @@ mod tests {
Editor::confirm_code_action(workspace, &ConfirmCodeAction(Some(0)), cx)
})
.unwrap();
fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _>(|_, _| {
lsp::CodeAction {
title: "Inline into all callers".to_string(),
edit: Some(lsp::WorkspaceEdit {
changes: Some(
[
(
lsp::Url::from_file_path("/a/main.rs").unwrap(),
vec![lsp::TextEdit::new(
lsp::Range::new(
lsp::Position::new(1, 22),
lsp::Position::new(1, 34),
),
"4".to_string(),
)],
),
(
lsp::Url::from_file_path("/a/other.rs").unwrap(),
vec![lsp::TextEdit::new(
lsp::Range::new(
lsp::Position::new(0, 0),
lsp::Position::new(0, 27),
),
"".to_string(),
)],
),
]
.into_iter()
.collect(),
),
fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
|_, _| async move {
lsp::CodeAction {
title: "Inline into all callers".to_string(),
edit: Some(lsp::WorkspaceEdit {
changes: Some(
[
(
lsp::Url::from_file_path("/a/main.rs").unwrap(),
vec![lsp::TextEdit::new(
lsp::Range::new(
lsp::Position::new(1, 22),
lsp::Position::new(1, 34),
),
"4".to_string(),
)],
),
(
lsp::Url::from_file_path("/a/other.rs").unwrap(),
vec![lsp::TextEdit::new(
lsp::Range::new(
lsp::Position::new(0, 0),
lsp::Position::new(0, 27),
),
"".to_string(),
)],
),
]
.into_iter()
.collect(),
),
..Default::default()
}),
..Default::default()
}),
..Default::default()
}
});
}
},
);
// After the action is confirmed, an editor containing both modified files is opened.
confirm_action.await.unwrap();
@ -3642,7 +3663,7 @@ mod tests {
});
fake_language_server
.handle_request::<lsp::request::PrepareRenameRequest, _>(|params, _| {
.handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
assert_eq!(params.position, lsp::Position::new(0, 7));
Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
@ -3672,7 +3693,7 @@ mod tests {
Editor::confirm_rename(workspace, &ConfirmRename, cx).unwrap()
});
fake_language_server
.handle_request::<lsp::request::Rename, _>(|params, _| {
.handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
assert_eq!(
params.text_document_position.text_document.uri.as_str(),
"file:///dir/one.rs"
@ -5320,30 +5341,34 @@ mod tests {
let files = files.clone();
let project = project.downgrade();
move |fake_server| {
fake_server.handle_request::<lsp::request::Completion, _>(|_, _| {
Some(lsp::CompletionResponse::Array(vec![lsp::CompletionItem {
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(
lsp::Position::new(0, 0),
lsp::Position::new(0, 0),
),
new_text: "the-new-text".to_string(),
})),
..Default::default()
}]))
});
fake_server.handle_request::<lsp::request::CodeActionRequest, _>(|_, _| {
Some(vec![lsp::CodeActionOrCommand::CodeAction(
lsp::CodeAction {
title: "the-code-action".to_string(),
fake_server.handle_request::<lsp::request::Completion, _, _>(
|_, _| async move {
Some(lsp::CompletionResponse::Array(vec![lsp::CompletionItem {
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(
lsp::Position::new(0, 0),
lsp::Position::new(0, 0),
),
new_text: "the-new-text".to_string(),
})),
..Default::default()
},
)])
});
}]))
},
);
fake_server.handle_request::<lsp::request::PrepareRenameRequest, _>(
|params, _| {
fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
|_, _| async move {
Some(vec![lsp::CodeActionOrCommand::CodeAction(
lsp::CodeAction {
title: "the-code-action".to_string(),
..Default::default()
},
)])
},
);
fake_server.handle_request::<lsp::request::PrepareRenameRequest, _, _>(
|params, _| async move {
Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
params.position,
params.position,
@ -5351,34 +5376,38 @@ mod tests {
},
);
fake_server.handle_request::<lsp::request::GotoDefinition, _>({
fake_server.handle_request::<lsp::request::GotoDefinition, _, _>({
let files = files.clone();
let rng = rng.clone();
move |_, _| {
let files = files.lock();
let mut rng = rng.lock();
let count = rng.gen_range::<usize, _>(1..3);
let files = (0..count)
.map(|_| files.choose(&mut *rng).unwrap())
.collect::<Vec<_>>();
log::info!("LSP: Returning definitions in files {:?}", &files);
Some(lsp::GotoDefinitionResponse::Array(
files
.into_iter()
.map(|file| lsp::Location {
uri: lsp::Url::from_file_path(file).unwrap(),
range: Default::default(),
})
.collect(),
))
let files = files.clone();
let rng = rng.clone();
async move {
let files = files.lock();
let mut rng = rng.lock();
let count = rng.gen_range::<usize, _>(1..3);
let files = (0..count)
.map(|_| files.choose(&mut *rng).unwrap())
.collect::<Vec<_>>();
log::info!("LSP: Returning definitions in files {:?}", &files);
Some(lsp::GotoDefinitionResponse::Array(
files
.into_iter()
.map(|file| lsp::Location {
uri: lsp::Url::from_file_path(file).unwrap(),
range: Default::default(),
})
.collect(),
))
}
}
});
fake_server.handle_request::<lsp::request::DocumentHighlightRequest, _>({
fake_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>({
let rng = rng.clone();
let project = project.clone();
move |params, mut cx| {
if let Some(project) = project.upgrade(&cx) {
let highlights = if let Some(project) = project.upgrade(&cx) {
project.update(&mut cx, |project, cx| {
let path = params
.text_document_position_params
@ -5415,7 +5444,8 @@ mod tests {
})
} else {
None
}
};
async move { highlights }
}
});
}