Add integration test for code actions

This commit is contained in:
Antonio Scandurra 2022-02-14 11:42:56 +01:00
parent 68917c78be
commit 1eea2f3653
5 changed files with 259 additions and 9 deletions

View file

@ -533,14 +533,14 @@ impl Client {
match future.await { match future.await {
Ok(()) => { Ok(()) => {
log::debug!( log::debug!(
"{}: rpc message '{}' handled", "rpc message handled. client_id:{}, name:{}",
client_id, client_id,
type_name type_name
); );
} }
Err(error) => { Err(error) => {
log::error!( log::error!(
"{}: error handling rpc message '{}', {}", "error handling rpc message. client_id:{}, name:{}, error:{}",
client_id, client_id,
type_name, type_name,
error error

View file

@ -2090,7 +2090,7 @@ impl Editor {
})) }))
} }
fn toggle_code_actions( pub fn toggle_code_actions(
&mut self, &mut self,
&ToggleCodeActions(deployed_from_indicator): &ToggleCodeActions, &ToggleCodeActions(deployed_from_indicator): &ToggleCodeActions,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
@ -2136,7 +2136,7 @@ impl Editor {
.detach_and_log_err(cx); .detach_and_log_err(cx);
} }
fn confirm_code_action( pub fn confirm_code_action(
workspace: &mut Workspace, workspace: &mut Workspace,
ConfirmCodeAction(action_ix): &ConfirmCodeAction, ConfirmCodeAction(action_ix): &ConfirmCodeAction,
cx: &mut ViewContext<Workspace>, cx: &mut ViewContext<Workspace>,
@ -2297,7 +2297,7 @@ impl Editor {
self.completion_tasks.clear(); self.completion_tasks.clear();
} }
self.context_menu = Some(menu); self.context_menu = Some(menu);
cx.notify() cx.notify();
} }
fn hide_context_menu(&mut self, cx: &mut ViewContext<Self>) -> Option<ContextMenu> { fn hide_context_menu(&mut self, cx: &mut ViewContext<Self>) -> Option<ContextMenu> {

View file

@ -2703,7 +2703,7 @@ impl<T: Entity> ModelHandle<T> {
let (mut tx, mut rx) = mpsc::channel(1); let (mut tx, mut rx) = mpsc::channel(1);
let mut cx = cx.cx.borrow_mut(); let mut cx = cx.cx.borrow_mut();
let subscription = cx.observe(self, move |_, _| { let subscription = cx.observe(self, move |_, _| {
tx.blocking_send(()).ok(); tx.try_send(()).ok();
}); });
let duration = if std::env::var("CI").is_ok() { let duration = if std::env::var("CI").is_ok() {
@ -3007,7 +3007,7 @@ impl<T: View> ViewHandle<T> {
let (mut tx, mut rx) = mpsc::channel(1); let (mut tx, mut rx) = mpsc::channel(1);
let mut cx = cx.cx.borrow_mut(); let mut cx = cx.cx.borrow_mut();
let subscription = cx.observe(self, move |_, _| { let subscription = cx.observe(self, move |_, _| {
tx.blocking_send(()).ok(); tx.try_send(()).ok();
}); });
let duration = if std::env::var("CI").is_ok() { let duration = if std::env::var("CI").is_ok() {

View file

@ -179,7 +179,12 @@ impl Peer {
let channel = response_channels.lock().as_mut()?.remove(&responding_to); let channel = response_channels.lock().as_mut()?.remove(&responding_to);
if let Some(mut tx) = channel { if let Some(mut tx) = channel {
let mut requester_resumed = barrier::channel(); let mut requester_resumed = barrier::channel();
tx.send((incoming, requester_resumed.0)).await.ok(); if let Err(error) = tx.send((incoming, requester_resumed.0)).await {
log::debug!(
"received RPC but request future was dropped {:?}",
error.0 .0
);
}
// Drop response channel before awaiting on the barrier. This allows the // Drop response channel before awaiting on the barrier. This allows the
// barrier to get dropped even if the request's future is dropped before it // barrier to get dropped even if the request's future is dropped before it
// has a chance to observe the response. // has a chance to observe the response.

View file

@ -1089,7 +1089,10 @@ mod tests {
self, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Credentials, self, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Credentials,
EstablishConnectionError, UserStore, EstablishConnectionError, UserStore,
}, },
editor::{ConfirmCompletion, Editor, EditorSettings, Input, MultiBuffer}, editor::{
self, ConfirmCodeAction, ConfirmCompletion, Editor, EditorSettings, Input, MultiBuffer,
Redo, ToggleCodeActions, Undo,
},
fs::{FakeFs, Fs as _}, fs::{FakeFs, Fs as _},
language::{ language::{
tree_sitter_rust, AnchorRangeExt, Diagnostic, DiagnosticEntry, Language, tree_sitter_rust, AnchorRangeExt, Diagnostic, DiagnosticEntry, Language,
@ -1097,6 +1100,7 @@ mod tests {
}, },
lsp, lsp,
project::{worktree::WorktreeHandle, DiagnosticSummary, Project, ProjectPath}, project::{worktree::WorktreeHandle, DiagnosticSummary, Project, ProjectPath},
workspace::{Workspace, WorkspaceParams},
}; };
#[cfg(test)] #[cfg(test)]
@ -2724,6 +2728,247 @@ mod tests {
assert_eq!(definitions[0].target_buffer, buffer_b2); assert_eq!(definitions[0].target_buffer, buffer_b2);
} }
#[gpui::test(iterations = 10)]
async fn test_collaborating_with_code_actions(
mut cx_a: TestAppContext,
mut cx_b: TestAppContext,
) {
cx_a.foreground().forbid_parking();
let mut lang_registry = Arc::new(LanguageRegistry::new());
let fs = Arc::new(FakeFs::new(cx_a.background()));
let mut path_openers_b = Vec::new();
cx_b.update(|cx| editor::init(cx, &mut path_openers_b));
// Set up a fake language server.
let (language_server_config, mut fake_language_server) =
LanguageServerConfig::fake_with_capabilities(
lsp::ServerCapabilities {
..Default::default()
},
&cx_a,
)
.await;
Arc::get_mut(&mut lang_registry)
.unwrap()
.add(Arc::new(Language::new(
LanguageConfig {
name: "Rust".to_string(),
path_suffixes: vec!["rs".to_string()],
language_server: Some(language_server_config),
..Default::default()
},
Some(tree_sitter_rust::language()),
)));
// Connect to a server as 2 clients.
let mut server = TestServer::start(cx_a.foreground()).await;
let client_a = server.create_client(&mut cx_a, "user_a").await;
let client_b = server.create_client(&mut cx_b, "user_b").await;
// Share a project as client A
fs.insert_tree(
"/a",
json!({
".zed.toml": r#"collaborators = ["user_b"]"#,
"main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
"other.rs": "pub fn foo() -> usize { 4 }",
}),
)
.await;
let project_a = cx_a.update(|cx| {
Project::local(
client_a.clone(),
client_a.user_store.clone(),
lang_registry.clone(),
fs.clone(),
cx,
)
});
let (worktree_a, _) = project_a
.update(&mut cx_a, |p, cx| {
p.find_or_create_local_worktree("/a", false, cx)
})
.await
.unwrap();
worktree_a
.read_with(&cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
.await;
let project_id = project_a.update(&mut cx_a, |p, _| p.next_remote_id()).await;
let worktree_id = worktree_a.read_with(&cx_a, |tree, _| tree.id());
project_a
.update(&mut cx_a, |p, cx| p.share(cx))
.await
.unwrap();
// Join the worktree as client B.
let project_b = Project::remote(
project_id,
client_b.clone(),
client_b.user_store.clone(),
lang_registry.clone(),
fs.clone(),
&mut cx_b.to_async(),
)
.await
.unwrap();
let mut params = cx_b.update(WorkspaceParams::test);
params.languages = lang_registry.clone();
params.client = client_b.client.clone();
params.user_store = client_b.user_store.clone();
params.project = project_b;
params.path_openers = path_openers_b.into();
let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::new(&params, cx));
let editor_b = workspace_b
.update(&mut cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs").into(), cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
fake_language_server
.handle_request::<lsp::request::CodeActionRequest, _>(|params| {
assert_eq!(
params.text_document.uri,
lsp::Url::from_file_path("/a/main.rs").unwrap(),
);
assert_eq!(params.range.start, lsp::Position::new(0, 0));
assert_eq!(params.range.end, lsp::Position::new(0, 0));
None
})
.next()
.await;
// Move cursor to a location that contains code actions.
editor_b.update(&mut cx_b, |editor, cx| {
editor.select_ranges([Point::new(1, 31)..Point::new(1, 31)], None, cx);
cx.focus(&editor_b);
});
fake_language_server.handle_request::<lsp::request::CodeActionRequest, _>(|params| {
assert_eq!(
params.text_document.uri,
lsp::Url::from_file_path("/a/main.rs").unwrap(),
);
assert_eq!(params.range.start, lsp::Position::new(1, 31));
assert_eq!(params.range.end, lsp::Position::new(1, 31));
Some(vec![lsp::CodeActionOrCommand::CodeAction(
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()
}),
data: Some(json!({
"codeActionParams": {
"range": {
"start": {"line": 1, "column": 31},
"end": {"line": 1, "column": 31},
}
}
})),
..Default::default()
},
)])
});
// Toggle code actions and wait for them to display.
editor_b.update(&mut cx_b, |editor, cx| {
editor.toggle_code_actions(&ToggleCodeActions(false), cx);
});
editor_b
.condition(&cx_b, |editor, _| editor.context_menu_visible())
.await;
// Confirming the code action will trigger a resolve request.
let confirm_action = workspace_b
.update(&mut cx_b, |workspace, cx| {
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(),
),
..Default::default()
}),
..Default::default()
}
});
// After the action is confirmed, an editor containing both modified files is opened.
confirm_action.await.unwrap();
let code_action_editor = workspace_b.read_with(&cx_b, |workspace, cx| {
workspace
.active_item(cx)
.unwrap()
.downcast::<Editor>()
.unwrap()
});
code_action_editor.update(&mut cx_b, |editor, cx| {
assert_eq!(editor.text(cx), "\nmod other;\nfn main() { let foo = 4; }");
editor.undo(&Undo, cx);
assert_eq!(
editor.text(cx),
"pub fn foo() -> usize { 4 }\nmod other;\nfn main() { let foo = other::foo(); }"
);
editor.redo(&Redo, cx);
assert_eq!(editor.text(cx), "\nmod other;\nfn main() { let foo = 4; }");
});
}
#[gpui::test(iterations = 10)] #[gpui::test(iterations = 10)]
async fn test_basic_chat(mut cx_a: TestAppContext, mut cx_b: TestAppContext) { async fn test_basic_chat(mut cx_a: TestAppContext, mut cx_b: TestAppContext) {
cx_a.foreground().forbid_parking(); cx_a.foreground().forbid_parking();