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)ˇ; }"});
|
||||
}
|
||||
|
||||
#[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]
|
||||
async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
|
|
@ -697,6 +697,7 @@ impl LanguageServer {
|
|||
"commitCharacters".to_owned(),
|
||||
"editRange".to_owned(),
|
||||
"insertTextMode".to_owned(),
|
||||
"insertTextFormat".to_owned(),
|
||||
"data".to_owned(),
|
||||
]),
|
||||
}),
|
||||
|
|
|
@ -1775,21 +1775,54 @@ impl LspCommand for GetCompletions {
|
|||
if let Some(item_defaults) = item_defaults {
|
||||
let default_data = item_defaults.data.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();
|
||||
|
||||
if default_data.is_some()
|
||||
|| default_commit_characters.is_some()
|
||||
|| default_edit_range.is_some()
|
||||
|| default_insert_text_format.is_some()
|
||||
|| default_insert_text_mode.is_some()
|
||||
{
|
||||
for item in completions.iter_mut() {
|
||||
if let Some(data) = default_data {
|
||||
item.data = Some(data.clone())
|
||||
if item.data.is_none() && default_data.is_some() {
|
||||
item.data = default_data.cloned()
|
||||
}
|
||||
if let Some(characters) = default_commit_characters {
|
||||
item.commit_characters = Some(characters.clone())
|
||||
if item.commit_characters.is_none() && default_commit_characters.is_some() {
|
||||
item.commit_characters = default_commit_characters.cloned()
|
||||
}
|
||||
if let Some(text_mode) = default_insert_text_mode {
|
||||
item.insert_text_mode = Some(*text_mode)
|
||||
if item.text_edit.is_none() {
|
||||
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