Cargo.lock 🔗
@@ -1617,6 +1617,7 @@ dependencies = [
"collections",
"ctor",
"env_logger",
+ "futures",
"fuzzy",
"gpui",
"itertools",
Antonio Scandurra created
Skip formatting during save if it takes too long
Cargo.lock | 1
crates/editor/Cargo.toml | 1
crates/editor/src/editor.rs | 170 +++++++++++++++----
crates/editor/src/items.rs | 19 +
crates/gpui/src/executor.rs | 2
crates/lsp/src/lsp.rs | 57 ++++--
crates/project/src/project.rs | 6
crates/server/src/rpc.rs | 306 ++++++++++++++++++++----------------
8 files changed, 360 insertions(+), 202 deletions(-)
@@ -1617,6 +1617,7 @@ dependencies = [
"collections",
"ctor",
"env_logger",
+ "futures",
"fuzzy",
"gpui",
"itertools",
@@ -35,6 +35,7 @@ util = { path = "../util" }
workspace = { path = "../workspace" }
aho-corasick = "0.7"
anyhow = "1.0"
+futures = "0.3"
itertools = "0.10"
lazy_static = "1.4"
log = "0.4"
@@ -6291,7 +6291,7 @@ mod tests {
use text::Point;
use unindent::Unindent;
use util::test::{marked_text_by, sample_text};
- use workspace::FollowableItem;
+ use workspace::{FollowableItem, ItemHandle};
#[gpui::test]
fn test_edit_events(cx: &mut MutableAppContext) {
@@ -8669,6 +8669,94 @@ mod tests {
});
}
+ #[gpui::test]
+ async fn test_format_during_save(cx: &mut gpui::TestAppContext) {
+ cx.foreground().forbid_parking();
+ cx.update(populate_settings);
+
+ let (mut language_server_config, mut fake_servers) = LanguageServerConfig::fake();
+ language_server_config.set_fake_capabilities(lsp::ServerCapabilities {
+ document_formatting_provider: Some(lsp::OneOf::Left(true)),
+ ..Default::default()
+ });
+ let language = Arc::new(Language::new(
+ LanguageConfig {
+ name: "Rust".into(),
+ path_suffixes: vec!["rs".to_string()],
+ language_server: Some(language_server_config),
+ ..Default::default()
+ },
+ Some(tree_sitter_rust::language()),
+ ));
+
+ let fs = FakeFs::new(cx.background().clone());
+ fs.insert_file("/file.rs", Default::default()).await;
+
+ let project = Project::test(fs, cx);
+ project.update(cx, |project, _| project.languages().add(language));
+
+ let worktree_id = project
+ .update(cx, |project, cx| {
+ project.find_or_create_local_worktree("/file.rs", true, cx)
+ })
+ .await
+ .unwrap()
+ .0
+ .read_with(cx, |tree, _| tree.id());
+ let buffer = project
+ .update(cx, |project, cx| project.open_buffer((worktree_id, ""), cx))
+ .await
+ .unwrap();
+ let mut fake_server = fake_servers.next().await.unwrap();
+
+ let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+ let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+ editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
+ assert!(cx.read(|cx| editor.is_dirty(cx)));
+
+ let save = cx.update(|cx| editor.save(project.clone(), cx));
+ fake_server
+ .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
+ assert_eq!(
+ params.text_document.uri,
+ lsp::Url::from_file_path("/file.rs").unwrap()
+ );
+ Some(vec![lsp::TextEdit::new(
+ lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
+ ", ".to_string(),
+ )])
+ })
+ .next()
+ .await;
+ save.await.unwrap();
+ assert_eq!(
+ editor.read_with(cx, |editor, cx| editor.text(cx)),
+ "one, two\nthree\n"
+ );
+ assert!(!cx.read(|cx| editor.is_dirty(cx)));
+
+ editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
+ assert!(cx.read(|cx| editor.is_dirty(cx)));
+
+ // Ensure we can still save even if formatting hangs.
+ fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
+ assert_eq!(
+ params.text_document.uri,
+ lsp::Url::from_file_path("/file.rs").unwrap()
+ );
+ futures::future::pending::<()>().await;
+ unreachable!()
+ });
+ let save = cx.update(|cx| editor.save(project.clone(), cx));
+ cx.foreground().advance_clock(items::FORMAT_TIMEOUT);
+ save.await.unwrap();
+ assert_eq!(
+ editor.read_with(cx, |editor, cx| editor.text(cx)),
+ "one\ntwo\nthree\n"
+ );
+ assert!(!cx.read(|cx| editor.is_dirty(cx)));
+ }
+
#[gpui::test]
async fn test_completion(cx: &mut gpui::TestAppContext) {
cx.update(populate_settings);
@@ -8850,31 +8938,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 +8975,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()
@@ -1,5 +1,6 @@
use crate::{Anchor, Autoscroll, Editor, Event, ExcerptId, NavigationData, ToOffset, ToPoint as _};
use anyhow::{anyhow, Result};
+use futures::FutureExt;
use gpui::{
elements::*, geometry::vector::vec2f, AppContext, Entity, ModelHandle, MutableAppContext,
RenderContext, Subscription, Task, View, ViewContext, ViewHandle,
@@ -7,13 +8,15 @@ use gpui::{
use language::{Bias, Buffer, Diagnostic, File as _, SelectionGoal};
use project::{File, Project, ProjectEntryId, ProjectPath};
use rpc::proto::{self, update_view};
-use std::{fmt::Write, path::PathBuf};
+use std::{fmt::Write, path::PathBuf, time::Duration};
use text::{Point, Selection};
-use util::ResultExt;
+use util::TryFutureExt;
use workspace::{
FollowableItem, Item, ItemHandle, ItemNavHistory, ProjectItem, Settings, StatusItemView,
};
+pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
+
impl FollowableItem for Editor {
fn from_state_proto(
pane: ViewHandle<workspace::Pane>,
@@ -317,9 +320,17 @@ impl Item for Editor {
) -> Task<Result<()>> {
let buffer = self.buffer().clone();
let buffers = buffer.read(cx).all_buffers();
- let transaction = project.update(cx, |project, cx| project.format(buffers, true, cx));
+ let mut timeout = cx.background().timer(FORMAT_TIMEOUT).fuse();
+ let format = project.update(cx, |project, cx| project.format(buffers, true, cx));
cx.spawn(|this, mut cx| async move {
- let transaction = transaction.await.log_err();
+ let transaction = futures::select_biased! {
+ _ = timeout => {
+ log::warn!("timed out waiting for formatting");
+ None
+ }
+ transaction = format.log_err().fuse() => transaction,
+ };
+
this.update(&mut cx, |editor, cx| {
editor.request_autoscroll(Autoscroll::Fit, cx)
});
@@ -337,7 +337,7 @@ impl Deterministic {
if let Some((_, wakeup_time, _)) = state.pending_timers.first() {
let wakeup_time = *wakeup_time;
- if wakeup_time < new_now {
+ if wakeup_time <= new_now {
let timer_count = state
.pending_timers
.iter()
@@ -556,7 +556,14 @@ 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,
+ ) -> futures::future::BoxFuture<'static, Vec<u8>>,
+ >,
>,
>,
>;
@@ -585,11 +592,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 +640,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 +717,36 @@ 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>,
{
+ use futures::FutureExt as _;
+
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 +865,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;
@@ -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"
@@ -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::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, _| {
+ 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 }
}
});
}