typescript: Complete function calls with snippets (#11157)
This allows function call (i.e. snippet) completion with `typescript-language-server`. So far that didn't work, because `typescript-language-server` doesn't respond with `insertText` when getting the completions, but only when then sending `completionItem/resolve` requests. See: https://github.com/hrsh7th/nvim-cmp/issues/646#issuecomment-992765479 What this PR does is to support text edits in the response to `completionItem/resolve`, which means updating the completion item. It then enables this feature by default for `typescript-language-server`. TODOs: - [x] Make this work over collab - [x] Test that this doesn't break existing language server support - [x] Refactor duplicated code Release Notes: - Added support for function call completion when using `typescript-language-server`. This will result in parameters being added, which can then be changed and navigated with `<tab>`. For this to work with `typescript-language-server`, the documentation for a given completion item needs to be resolved, meaning that if one types very quickly and accepts completion before `typescript-language-server` could respond with the documentation, no full function completion is used. Demo: https://github.com/zed-industries/zed/assets/1185253/c23ebe12-5902-4b50-888c-d9b8cd32965d
This commit is contained in:
parent
d8ca15372c
commit
c81230405f
7 changed files with 256 additions and 50 deletions
|
@ -6,8 +6,8 @@ use call::ActiveCall;
|
|||
use collections::HashMap;
|
||||
use editor::{
|
||||
actions::{
|
||||
ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Redo, Rename, RevertSelectedHunks,
|
||||
ToggleCodeActions, Undo,
|
||||
ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst, Redo, Rename,
|
||||
RevertSelectedHunks, ToggleCodeActions, Undo,
|
||||
},
|
||||
test::{
|
||||
editor_hunks,
|
||||
|
@ -444,6 +444,93 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
|||
"use d::SomeTrait;\nfn main() { a.first_method() }"
|
||||
);
|
||||
});
|
||||
|
||||
// Now we do a second completion, this time to ensure that documentation/snippets are
|
||||
// resolved
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([46..46]));
|
||||
editor.handle_input("; a", cx);
|
||||
editor.handle_input(".", cx);
|
||||
});
|
||||
|
||||
buffer_b.read_with(cx_b, |buffer, _| {
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
"use d::SomeTrait;\nfn main() { a.first_method(); a. }"
|
||||
);
|
||||
});
|
||||
|
||||
let mut completion_response = fake_language_server
|
||||
.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(),
|
||||
);
|
||||
assert_eq!(
|
||||
params.text_document_position.position,
|
||||
lsp::Position::new(1, 32),
|
||||
);
|
||||
|
||||
Ok(Some(lsp::CompletionResponse::Array(vec![
|
||||
lsp::CompletionItem {
|
||||
label: "third_method(…)".into(),
|
||||
detail: Some("fn(&mut self, B, C, D) -> E".into()),
|
||||
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||
// no snippet placehodlers
|
||||
new_text: "third_method".to_string(),
|
||||
range: lsp::Range::new(
|
||||
lsp::Position::new(1, 32),
|
||||
lsp::Position::new(1, 32),
|
||||
),
|
||||
})),
|
||||
insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
|
||||
documentation: None,
|
||||
..Default::default()
|
||||
},
|
||||
])))
|
||||
});
|
||||
|
||||
// The completion now gets a new `text_edit.new_text` when resolving the completion item
|
||||
let mut resolve_completion_response = fake_language_server
|
||||
.handle_request::<lsp::request::ResolveCompletionItem, _, _>(|params, _| async move {
|
||||
assert_eq!(params.label, "third_method(…)");
|
||||
Ok(lsp::CompletionItem {
|
||||
label: "third_method(…)".into(),
|
||||
detail: Some("fn(&mut self, B, C, D) -> E".into()),
|
||||
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||
// Now it's a snippet
|
||||
new_text: "third_method($1, $2, $3)".to_string(),
|
||||
range: lsp::Range::new(lsp::Position::new(1, 32), lsp::Position::new(1, 32)),
|
||||
})),
|
||||
insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
|
||||
documentation: Some(lsp::Documentation::String(
|
||||
"this is the documentation".into(),
|
||||
)),
|
||||
..Default::default()
|
||||
})
|
||||
});
|
||||
|
||||
cx_b.executor().run_until_parked();
|
||||
|
||||
completion_response.next().await.unwrap();
|
||||
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert!(editor.context_menu_visible());
|
||||
editor.context_menu_first(&ContextMenuFirst {}, cx);
|
||||
});
|
||||
|
||||
resolve_completion_response.next().await.unwrap();
|
||||
cx_b.executor().run_until_parked();
|
||||
|
||||
// When accepting the completion, the snippet is insert.
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert!(editor.context_menu_visible());
|
||||
editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
"use d::SomeTrait;\nfn main() { a.first_method(); a.third_method(, , ) }"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue