Properly use lsp::CompletionList defaults (#21202)
- Closes https://github.com/zed-industries/zed/issues/21185 Release Notes: - Fixed incorrect handling of the completion list defaults
This commit is contained in:
parent
968ffaa3fd
commit
7d67bb4cf6
3 changed files with 234 additions and 6 deletions
|
@ -10541,6 +10541,200 @@ async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
|
||||||
cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
|
cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
|
||||||
|
init_test(cx, |_| {});
|
||||||
|
|
||||||
|
let mut cx = EditorLspTestContext::new_rust(
|
||||||
|
lsp::ServerCapabilities {
|
||||||
|
completion_provider: Some(lsp::CompletionOptions {
|
||||||
|
trigger_characters: Some(vec![".".to_string()]),
|
||||||
|
resolve_provider: Some(true),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
|
||||||
|
cx.simulate_keystroke(".");
|
||||||
|
|
||||||
|
let default_commit_characters = vec!["?".to_string()];
|
||||||
|
let default_data = json!({ "very": "special"});
|
||||||
|
let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
|
||||||
|
let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
|
||||||
|
let default_edit_range = lsp::Range {
|
||||||
|
start: lsp::Position {
|
||||||
|
line: 0,
|
||||||
|
character: 5,
|
||||||
|
},
|
||||||
|
end: lsp::Position {
|
||||||
|
line: 0,
|
||||||
|
character: 5,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let completion_data = default_data.clone();
|
||||||
|
let completion_characters = default_commit_characters.clone();
|
||||||
|
cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
|
||||||
|
let default_data = completion_data.clone();
|
||||||
|
let default_commit_characters = completion_characters.clone();
|
||||||
|
async move {
|
||||||
|
Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
|
||||||
|
items: vec![
|
||||||
|
lsp::CompletionItem {
|
||||||
|
label: "Some(2)".into(),
|
||||||
|
insert_text: Some("Some(2)".into()),
|
||||||
|
data: Some(json!({ "very": "special"})),
|
||||||
|
insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
|
||||||
|
text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
|
||||||
|
lsp::InsertReplaceEdit {
|
||||||
|
new_text: "Some(2)".to_string(),
|
||||||
|
insert: lsp::Range::default(),
|
||||||
|
replace: lsp::Range::default(),
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
..lsp::CompletionItem::default()
|
||||||
|
},
|
||||||
|
lsp::CompletionItem {
|
||||||
|
label: "vec![2]".into(),
|
||||||
|
insert_text: Some("vec![2]".into()),
|
||||||
|
insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
|
||||||
|
..lsp::CompletionItem::default()
|
||||||
|
},
|
||||||
|
],
|
||||||
|
item_defaults: Some(lsp::CompletionListItemDefaults {
|
||||||
|
data: Some(default_data.clone()),
|
||||||
|
commit_characters: Some(default_commit_characters.clone()),
|
||||||
|
edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
|
||||||
|
default_edit_range,
|
||||||
|
)),
|
||||||
|
insert_text_format: Some(default_insert_text_format),
|
||||||
|
insert_text_mode: Some(default_insert_text_mode),
|
||||||
|
}),
|
||||||
|
..lsp::CompletionList::default()
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
cx.condition(|editor, _| editor.context_menu_visible())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
cx.update_editor(|editor, _| {
|
||||||
|
let menu = editor.context_menu.read();
|
||||||
|
match menu.as_ref().expect("should have the completions menu") {
|
||||||
|
ContextMenu::Completions(completions_menu) => {
|
||||||
|
assert_eq!(
|
||||||
|
completions_menu
|
||||||
|
.matches
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.string.as_str())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
vec!["Some(2)", "vec![2]"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.update_editor(|editor, cx| {
|
||||||
|
editor.context_menu_first(&ContextMenuFirst, cx);
|
||||||
|
});
|
||||||
|
let first_item_resolve_characters = default_commit_characters.clone();
|
||||||
|
cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, item_to_resolve, _| {
|
||||||
|
let default_commit_characters = first_item_resolve_characters.clone();
|
||||||
|
|
||||||
|
async move {
|
||||||
|
assert_eq!(
|
||||||
|
item_to_resolve.label, "Some(2)",
|
||||||
|
"Should have selected the first item"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
item_to_resolve.data,
|
||||||
|
Some(json!({ "very": "special"})),
|
||||||
|
"First item should bring its own data for resolving"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
item_to_resolve.commit_characters,
|
||||||
|
Some(default_commit_characters),
|
||||||
|
"First item had no own commit characters and should inherit the default ones"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
item_to_resolve.text_edit,
|
||||||
|
Some(lsp::CompletionTextEdit::InsertAndReplace { .. })
|
||||||
|
),
|
||||||
|
"First item should bring its own edit range for resolving"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
item_to_resolve.insert_text_format,
|
||||||
|
Some(default_insert_text_format),
|
||||||
|
"First item had no own insert text format and should inherit the default one"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
item_to_resolve.insert_text_mode,
|
||||||
|
Some(lsp::InsertTextMode::ADJUST_INDENTATION),
|
||||||
|
"First item should bring its own insert text mode for resolving"
|
||||||
|
);
|
||||||
|
Ok(item_to_resolve)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
cx.update_editor(|editor, cx| {
|
||||||
|
editor.context_menu_last(&ContextMenuLast, cx);
|
||||||
|
});
|
||||||
|
cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, item_to_resolve, _| {
|
||||||
|
let default_data = default_data.clone();
|
||||||
|
let default_commit_characters = default_commit_characters.clone();
|
||||||
|
async move {
|
||||||
|
assert_eq!(
|
||||||
|
item_to_resolve.label, "vec![2]",
|
||||||
|
"Should have selected the last item"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
item_to_resolve.data,
|
||||||
|
Some(default_data),
|
||||||
|
"Last item has no own resolve data and should inherit the default one"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
item_to_resolve.commit_characters,
|
||||||
|
Some(default_commit_characters),
|
||||||
|
"Last item had no own commit characters and should inherit the default ones"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
item_to_resolve.text_edit,
|
||||||
|
Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
|
range: default_edit_range,
|
||||||
|
new_text: "vec![2]".to_string()
|
||||||
|
})),
|
||||||
|
"Last item had no own edit range and should inherit the default one"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
item_to_resolve.insert_text_format,
|
||||||
|
Some(lsp::InsertTextFormat::PLAIN_TEXT),
|
||||||
|
"Last item should bring its own insert text format for resolving"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
item_to_resolve.insert_text_mode,
|
||||||
|
Some(default_insert_text_mode),
|
||||||
|
"Last item had no own insert text mode and should inherit the default one"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(item_to_resolve)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
|
async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
|
||||||
init_test(cx, |_| {});
|
init_test(cx, |_| {});
|
||||||
|
|
|
@ -697,6 +697,7 @@ impl LanguageServer {
|
||||||
"commitCharacters".to_owned(),
|
"commitCharacters".to_owned(),
|
||||||
"editRange".to_owned(),
|
"editRange".to_owned(),
|
||||||
"insertTextMode".to_owned(),
|
"insertTextMode".to_owned(),
|
||||||
|
"insertTextFormat".to_owned(),
|
||||||
"data".to_owned(),
|
"data".to_owned(),
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -1775,21 +1775,54 @@ impl LspCommand for GetCompletions {
|
||||||
if let Some(item_defaults) = item_defaults {
|
if let Some(item_defaults) = item_defaults {
|
||||||
let default_data = item_defaults.data.as_ref();
|
let default_data = item_defaults.data.as_ref();
|
||||||
let default_commit_characters = item_defaults.commit_characters.as_ref();
|
let default_commit_characters = item_defaults.commit_characters.as_ref();
|
||||||
|
let default_edit_range = item_defaults.edit_range.as_ref();
|
||||||
|
let default_insert_text_format = item_defaults.insert_text_format.as_ref();
|
||||||
let default_insert_text_mode = item_defaults.insert_text_mode.as_ref();
|
let default_insert_text_mode = item_defaults.insert_text_mode.as_ref();
|
||||||
|
|
||||||
if default_data.is_some()
|
if default_data.is_some()
|
||||||
|| default_commit_characters.is_some()
|
|| default_commit_characters.is_some()
|
||||||
|
|| default_edit_range.is_some()
|
||||||
|
|| default_insert_text_format.is_some()
|
||||||
|| default_insert_text_mode.is_some()
|
|| default_insert_text_mode.is_some()
|
||||||
{
|
{
|
||||||
for item in completions.iter_mut() {
|
for item in completions.iter_mut() {
|
||||||
if let Some(data) = default_data {
|
if item.data.is_none() && default_data.is_some() {
|
||||||
item.data = Some(data.clone())
|
item.data = default_data.cloned()
|
||||||
}
|
}
|
||||||
if let Some(characters) = default_commit_characters {
|
if item.commit_characters.is_none() && default_commit_characters.is_some() {
|
||||||
item.commit_characters = Some(characters.clone())
|
item.commit_characters = default_commit_characters.cloned()
|
||||||
}
|
}
|
||||||
if let Some(text_mode) = default_insert_text_mode {
|
if item.text_edit.is_none() {
|
||||||
item.insert_text_mode = Some(*text_mode)
|
if let Some(default_edit_range) = default_edit_range {
|
||||||
|
match default_edit_range {
|
||||||
|
CompletionListItemDefaultsEditRange::Range(range) => {
|
||||||
|
item.text_edit =
|
||||||
|
Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
|
range: *range,
|
||||||
|
new_text: item.label.clone(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
CompletionListItemDefaultsEditRange::InsertAndReplace {
|
||||||
|
insert,
|
||||||
|
replace,
|
||||||
|
} => {
|
||||||
|
item.text_edit =
|
||||||
|
Some(lsp::CompletionTextEdit::InsertAndReplace(
|
||||||
|
lsp::InsertReplaceEdit {
|
||||||
|
new_text: item.label.clone(),
|
||||||
|
insert: *insert,
|
||||||
|
replace: *replace,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if item.insert_text_format.is_none() && default_insert_text_format.is_some() {
|
||||||
|
item.insert_text_format = default_insert_text_format.cloned()
|
||||||
|
}
|
||||||
|
if item.insert_text_mode.is_none() && default_insert_text_mode.is_some() {
|
||||||
|
item.insert_text_mode = default_insert_text_mode.cloned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue