Add integration test for getting and resolving completions
This commit is contained in:
parent
6e33f14218
commit
3dfff3866a
4 changed files with 237 additions and 4 deletions
|
@ -1677,7 +1677,7 @@ impl Editor {
|
||||||
self.completion_state.take()
|
self.completion_state.take()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm_completion(
|
pub fn confirm_completion(
|
||||||
&mut self,
|
&mut self,
|
||||||
completion_ix: Option<usize>,
|
completion_ix: Option<usize>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
|
|
|
@ -1339,7 +1339,7 @@ impl MultiBufferSnapshot {
|
||||||
range: range.clone(),
|
range: range.clone(),
|
||||||
excerpts: self.excerpts.cursor(),
|
excerpts: self.excerpts.cursor(),
|
||||||
excerpt_chunks: None,
|
excerpt_chunks: None,
|
||||||
language_aware: language_aware,
|
language_aware,
|
||||||
};
|
};
|
||||||
chunks.seek(range.start);
|
chunks.seek(range.start);
|
||||||
chunks
|
chunks
|
||||||
|
|
|
@ -363,7 +363,15 @@ impl LanguageServerConfig {
|
||||||
pub async fn fake(
|
pub async fn fake(
|
||||||
executor: Arc<gpui::executor::Background>,
|
executor: Arc<gpui::executor::Background>,
|
||||||
) -> (Self, lsp::FakeLanguageServer) {
|
) -> (Self, lsp::FakeLanguageServer) {
|
||||||
let (server, fake) = lsp::LanguageServer::fake(executor).await;
|
Self::fake_with_capabilities(Default::default(), executor).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn fake_with_capabilities(
|
||||||
|
capabilites: lsp::ServerCapabilities,
|
||||||
|
executor: Arc<gpui::executor::Background>,
|
||||||
|
) -> (Self, lsp::FakeLanguageServer) {
|
||||||
|
let (server, fake) =
|
||||||
|
lsp::LanguageServer::fake_with_capabilities(capabilites, executor).await;
|
||||||
fake.started
|
fake.started
|
||||||
.store(false, std::sync::atomic::Ordering::SeqCst);
|
.store(false, std::sync::atomic::Ordering::SeqCst);
|
||||||
let started = fake.started.clone();
|
let started = fake.started.clone();
|
||||||
|
|
|
@ -343,7 +343,7 @@ impl Server {
|
||||||
self.peer.send(
|
self.peer.send(
|
||||||
conn_id,
|
conn_id,
|
||||||
proto::AddProjectCollaborator {
|
proto::AddProjectCollaborator {
|
||||||
project_id: project_id,
|
project_id,
|
||||||
collaborator: Some(proto::Collaborator {
|
collaborator: Some(proto::Collaborator {
|
||||||
peer_id: request.sender_id.0,
|
peer_id: request.sender_id.0,
|
||||||
replica_id: response.replica_id,
|
replica_id: response.replica_id,
|
||||||
|
@ -2297,6 +2297,231 @@ mod tests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_collaborating_with_completion(
|
||||||
|
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()));
|
||||||
|
|
||||||
|
// Set up a fake language server.
|
||||||
|
let (language_server_config, mut fake_language_server) =
|
||||||
|
LanguageServerConfig::fake_with_capabilities(
|
||||||
|
lsp::ServerCapabilities {
|
||||||
|
completion_provider: Some(lsp::CompletionOptions {
|
||||||
|
trigger_characters: Some(vec![".".to_string()]),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
cx_a.background(),
|
||||||
|
)
|
||||||
|
.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": "fn main() { a }",
|
||||||
|
"other.rs": "",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.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();
|
||||||
|
|
||||||
|
// Open a file in an editor as the guest.
|
||||||
|
let buffer_b = project_b
|
||||||
|
.update(&mut cx_b, |p, cx| {
|
||||||
|
p.open_buffer((worktree_id, "main.rs"), cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let (window_b, _) = cx_b.add_window(|_| EmptyView);
|
||||||
|
let editor_b = cx_b.add_view(window_b, |cx| {
|
||||||
|
Editor::for_buffer(
|
||||||
|
cx.add_model(|cx| MultiBuffer::singleton(buffer_b.clone(), cx)),
|
||||||
|
Arc::new(|cx| EditorSettings::test(cx)),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Type a completion trigger character as the guest.
|
||||||
|
editor_b.update(&mut cx_b, |editor, cx| {
|
||||||
|
editor.select_ranges([13..13], None, cx);
|
||||||
|
editor.handle_input(&Input(".".into()), cx);
|
||||||
|
cx.focus(&editor_b);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Receive a completion request as the host's language server.
|
||||||
|
let (request_id, params) = fake_language_server
|
||||||
|
.receive_request::<lsp::request::Completion>()
|
||||||
|
.await;
|
||||||
|
assert_eq!(
|
||||||
|
params.text_document_position.text_document.uri,
|
||||||
|
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
params.text_document_position.position,
|
||||||
|
lsp::Position::new(0, 14),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Return some completions from the host's language server.
|
||||||
|
fake_language_server
|
||||||
|
.respond(
|
||||||
|
request_id,
|
||||||
|
Some(lsp::CompletionResponse::Array(vec![
|
||||||
|
lsp::CompletionItem {
|
||||||
|
label: "first_method(…)".into(),
|
||||||
|
detail: Some("fn(&mut self, B) -> C".into()),
|
||||||
|
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
|
new_text: "first_method($1)".to_string(),
|
||||||
|
range: lsp::Range::new(
|
||||||
|
lsp::Position::new(0, 14),
|
||||||
|
lsp::Position::new(0, 14),
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
lsp::CompletionItem {
|
||||||
|
label: "second_method(…)".into(),
|
||||||
|
detail: Some("fn(&mut self, C) -> D<E>".into()),
|
||||||
|
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
|
new_text: "second_method()".to_string(),
|
||||||
|
range: lsp::Range::new(
|
||||||
|
lsp::Position::new(0, 14),
|
||||||
|
lsp::Position::new(0, 14),
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
])),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Open the buffer on the host.
|
||||||
|
let buffer_a = project_a
|
||||||
|
.update(&mut cx_a, |p, cx| {
|
||||||
|
p.open_buffer((worktree_id, "main.rs"), cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
buffer_a
|
||||||
|
.condition(&cx_a, |buffer, _| buffer.text() == "fn main() { a. }")
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Confirm a completion on the guest.
|
||||||
|
editor_b.next_notification(&cx_b).await;
|
||||||
|
editor_b.update(&mut cx_b, |editor, cx| {
|
||||||
|
assert!(editor.has_completions());
|
||||||
|
editor.confirm_completion(Some(0), cx);
|
||||||
|
assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
|
||||||
|
});
|
||||||
|
|
||||||
|
buffer_a
|
||||||
|
.condition(&cx_a, |buffer, _| {
|
||||||
|
buffer.text() == "fn main() { a.first_method() }"
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Receive a request resolve the selected completion on the host's language server.
|
||||||
|
let (request_id, params) = fake_language_server
|
||||||
|
.receive_request::<lsp::request::ResolveCompletionItem>()
|
||||||
|
.await;
|
||||||
|
assert_eq!(params.label, "first_method(…)");
|
||||||
|
|
||||||
|
// Return a resolved completion from the host's language server.
|
||||||
|
// The resolved completion has an additional text edit.
|
||||||
|
fake_language_server
|
||||||
|
.respond(
|
||||||
|
request_id,
|
||||||
|
lsp::CompletionItem {
|
||||||
|
label: "first_method(…)".into(),
|
||||||
|
detail: Some("fn(&mut self, B) -> C".into()),
|
||||||
|
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
|
new_text: "first_method($1)".to_string(),
|
||||||
|
range: lsp::Range::new(
|
||||||
|
lsp::Position::new(0, 14),
|
||||||
|
lsp::Position::new(0, 14),
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
additional_text_edits: Some(vec![lsp::TextEdit {
|
||||||
|
new_text: "use d::SomeTrait;\n".to_string(),
|
||||||
|
range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
|
||||||
|
}]),
|
||||||
|
insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// The additional edit is applied.
|
||||||
|
buffer_b
|
||||||
|
.condition(&cx_b, |buffer, _| {
|
||||||
|
buffer.text() == "use d::SomeTrait;\nfn main() { a.first_method() }"
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
assert_eq!(
|
||||||
|
buffer_a.read_with(&cx_a, |buffer, _| buffer.text()),
|
||||||
|
buffer_b.read_with(&cx_b, |buffer, _| buffer.text()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_formatting_buffer(mut cx_a: TestAppContext, mut cx_b: TestAppContext) {
|
async fn test_formatting_buffer(mut cx_a: TestAppContext, mut cx_b: TestAppContext) {
|
||||||
cx_a.foreground().forbid_parking();
|
cx_a.foreground().forbid_parking();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue