@@ -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, |_| {});
@@ -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()
}
}
}