diff --git a/crates/assistant/src/slash_command.rs b/crates/assistant/src/slash_command.rs index 73f5cfc2e6..f9fb4fa803 100644 --- a/crates/assistant/src/slash_command.rs +++ b/crates/assistant/src/slash_command.rs @@ -149,6 +149,7 @@ impl SlashCommandCompletionProvider { server_id: LanguageServerId(0), lsp_completion: Default::default(), confirm, + resolved: true, }) }) .collect() @@ -242,6 +243,7 @@ impl SlashCommandCompletionProvider { server_id: LanguageServerId(0), lsp_completion: Default::default(), confirm, + resolved: true, } }) .collect()) @@ -330,16 +332,6 @@ impl CompletionProvider for SlashCommandCompletionProvider { Task::ready(Ok(true)) } - fn apply_additional_edits_for_completion( - &self, - _: Model, - _: project::Completion, - _: bool, - _: &mut ViewContext, - ) -> Task>> { - Task::ready(Ok(None)) - } - fn is_completion_trigger( &self, buffer: &Model, diff --git a/crates/collab_ui/src/chat_panel/message_editor.rs b/crates/collab_ui/src/chat_panel/message_editor.rs index 1dd1b4e0bc..7a05e6bce4 100644 --- a/crates/collab_ui/src/chat_panel/message_editor.rs +++ b/crates/collab_ui/src/chat_panel/message_editor.rs @@ -79,16 +79,6 @@ impl CompletionProvider for MessageEditorCompletionProvider { Task::ready(Ok(false)) } - fn apply_additional_edits_for_completion( - &self, - _buffer: Model, - _completion: Completion, - _push_to_history: bool, - _cx: &mut ViewContext, - ) -> Task>> { - Task::ready(Ok(None)) - } - fn is_completion_trigger( &self, _buffer: &Model, @@ -319,6 +309,7 @@ impl MessageEditor { server_id: LanguageServerId(0), // TODO: Make this optional or something? lsp_completion: Default::default(), // TODO: Make this optional or something? confirm: None, + resolved: true, } }) .collect() diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index fbd9a4d94f..9f591368f0 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -224,6 +224,7 @@ impl CompletionsMenu { documentation: None, lsp_completion: Default::default(), confirm: None, + resolved: true, }) .collect(); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 02f01dbaec..bddcc631f1 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3830,8 +3830,11 @@ impl Editor { }; let buffer_handle = completions_menu.buffer; - let completions = completions_menu.completions.borrow_mut(); - let completion = completions.get(mat.candidate_id)?; + let completion = completions_menu + .completions + .borrow() + .get(mat.candidate_id)? + .clone(); cx.stop_propagation(); let snippet; @@ -3975,9 +3978,11 @@ impl Editor { } let provider = self.completion_provider.as_ref()?; + drop(completion); let apply_edits = provider.apply_additional_edits_for_completion( buffer_handle, - completion.clone(), + completions_menu.completions.clone(), + mat.candidate_id, true, cx, ); @@ -5087,7 +5092,7 @@ impl Editor { })) } - #[cfg(feature = "test-support")] + #[cfg(any(feature = "test-support", test))] pub fn context_menu_visible(&self) -> bool { self.context_menu .borrow() @@ -13447,11 +13452,14 @@ pub trait CompletionProvider { fn apply_additional_edits_for_completion( &self, - buffer: Model, - completion: Completion, - push_to_history: bool, - cx: &mut ViewContext, - ) -> Task>>; + _buffer: Model, + _completions: Rc>>, + _completion_index: usize, + _push_to_history: bool, + _cx: &mut ViewContext, + ) -> Task>> { + Task::ready(Ok(None)) + } fn is_completion_trigger( &self, @@ -13610,6 +13618,7 @@ fn snippet_completions( Some(Completion { old_range: range, new_text: snippet.body.clone(), + resolved: false, label: CodeLabel { text: matching_prefix.clone(), runs: vec![], @@ -13675,19 +13684,30 @@ impl CompletionProvider for Model { cx: &mut ViewContext, ) -> Task> { self.update(cx, |project, cx| { - project.resolve_completions(buffer, completion_indices, completions, cx) + project.lsp_store().update(cx, |lsp_store, cx| { + lsp_store.resolve_completions(buffer, completion_indices, completions, cx) + }) }) } fn apply_additional_edits_for_completion( &self, buffer: Model, - completion: Completion, + completions: Rc>>, + completion_index: usize, push_to_history: bool, cx: &mut ViewContext, ) -> Task>> { self.update(cx, |project, cx| { - project.apply_additional_edits_for_completion(buffer, completion, push_to_history, cx) + project.lsp_store().update(cx, |lsp_store, cx| { + lsp_store.apply_additional_edits_for_completion( + buffer, + completions, + completion_index, + push_to_history, + cx, + ) + }) }) } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index cc7d612bbf..7377b6809b 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -8402,7 +8402,6 @@ async fn test_completion(cx: &mut gpui::TestAppContext) { additional edit "}); - handle_resolve_completion_request(&mut cx, None).await; apply_additional_edits.await.unwrap(); update_test_language_settings(&mut cx, |settings| { @@ -10698,10 +10697,14 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches( ..lsp::CompletionItem::default() }; - cx.handle_request::(move |_, _, _| { + let item1 = item1.clone(); + cx.handle_request::({ let item1 = item1.clone(); - let item2 = item2.clone(); - async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) } + move |_, _, _| { + let item1 = item1.clone(); + let item2 = item2.clone(); + async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) } + } }) .next() .await; @@ -10728,43 +10731,41 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches( } }); - cx.handle_request::(move |_, _, _| async move { - Ok(lsp::CompletionItem { - label: "method id()".to_string(), - filter_text: Some("id".to_string()), - detail: Some("Now resolved!".to_string()), - documentation: Some(lsp::Documentation::String("Docs".to_string())), - text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { - range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)), - new_text: ".id".to_string(), - })), - ..lsp::CompletionItem::default() - }) + cx.handle_request::({ + let item1 = item1.clone(); + move |_, item_to_resolve, _| { + let item1 = item1.clone(); + async move { + if item1 == item_to_resolve { + Ok(lsp::CompletionItem { + label: "method id()".to_string(), + filter_text: Some("id".to_string()), + detail: Some("Now resolved!".to_string()), + documentation: Some(lsp::Documentation::String("Docs".to_string())), + text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { + range: lsp::Range::new( + lsp::Position::new(0, 22), + lsp::Position::new(0, 22), + ), + new_text: ".id".to_string(), + })), + ..lsp::CompletionItem::default() + }) + } else { + Ok(item_to_resolve) + } + } + } }) .next() - .await; + .await + .unwrap(); cx.run_until_parked(); cx.update_editor(|editor, cx| { editor.context_menu_next(&Default::default(), cx); }); - cx.handle_request::(move |_, _, _| async move { - Ok(lsp::CompletionItem { - label: "invalid changed label".to_string(), - detail: Some("Now resolved!".to_string()), - documentation: Some(lsp::Documentation::String("Docs".to_string())), - text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { - range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)), - new_text: ".id".to_string(), - })), - ..lsp::CompletionItem::default() - }) - }) - .next() - .await; - cx.run_until_parked(); - cx.update_editor(|editor, _| { let context_menu = editor.context_menu.borrow_mut(); let context_menu = context_menu @@ -10787,6 +10788,172 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches( }); } +#[gpui::test] +async fn test_completions_resolve_happens_once(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 unresolved_item_1 = lsp::CompletionItem { + label: "id".to_string(), + filter_text: Some("id".to_string()), + detail: None, + documentation: None, + text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)), + new_text: ".id".to_string(), + })), + ..lsp::CompletionItem::default() + }; + let resolved_item_1 = lsp::CompletionItem { + additional_text_edits: Some(vec![lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)), + new_text: "!!".to_string(), + }]), + ..unresolved_item_1.clone() + }; + let unresolved_item_2 = lsp::CompletionItem { + label: "other".to_string(), + filter_text: Some("other".to_string()), + detail: None, + documentation: None, + text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)), + new_text: ".other".to_string(), + })), + ..lsp::CompletionItem::default() + }; + let resolved_item_2 = lsp::CompletionItem { + additional_text_edits: Some(vec![lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)), + new_text: "??".to_string(), + }]), + ..unresolved_item_2.clone() + }; + + let resolve_requests_1 = Arc::new(AtomicUsize::new(0)); + let resolve_requests_2 = Arc::new(AtomicUsize::new(0)); + cx.lsp + .server + .on_request::({ + let unresolved_item_1 = unresolved_item_1.clone(); + let resolved_item_1 = resolved_item_1.clone(); + let unresolved_item_2 = unresolved_item_2.clone(); + let resolved_item_2 = resolved_item_2.clone(); + let resolve_requests_1 = resolve_requests_1.clone(); + let resolve_requests_2 = resolve_requests_2.clone(); + move |unresolved_request, _| { + let unresolved_item_1 = unresolved_item_1.clone(); + let resolved_item_1 = resolved_item_1.clone(); + let unresolved_item_2 = unresolved_item_2.clone(); + let resolved_item_2 = resolved_item_2.clone(); + let resolve_requests_1 = resolve_requests_1.clone(); + let resolve_requests_2 = resolve_requests_2.clone(); + async move { + if unresolved_request == unresolved_item_1 { + resolve_requests_1.fetch_add(1, atomic::Ordering::Release); + Ok(resolved_item_1.clone()) + } else if unresolved_request == unresolved_item_2 { + resolve_requests_2.fetch_add(1, atomic::Ordering::Release); + Ok(resolved_item_2.clone()) + } else { + panic!("Unexpected completion item {unresolved_request:?}") + } + } + } + }) + .detach(); + + cx.handle_request::(move |_, _, _| { + let unresolved_item_1 = unresolved_item_1.clone(); + let unresolved_item_2 = unresolved_item_2.clone(); + async move { + Ok(Some(lsp::CompletionResponse::Array(vec![ + unresolved_item_1, + unresolved_item_2, + ]))) + } + }) + .next() + .await; + + cx.condition(|editor, _| editor.context_menu_visible()) + .await; + cx.update_editor(|editor, _| { + let context_menu = editor.context_menu.borrow_mut(); + let context_menu = context_menu + .as_ref() + .expect("Should have the context menu deployed"); + match context_menu { + CodeContextMenu::Completions(completions_menu) => { + let completions = completions_menu.completions.borrow_mut(); + assert_eq!( + completions + .iter() + .map(|completion| &completion.label.text) + .collect::>(), + vec!["id", "other"] + ) + } + CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"), + } + }); + cx.run_until_parked(); + + cx.update_editor(|editor, cx| { + editor.context_menu_next(&ContextMenuNext, cx); + }); + cx.run_until_parked(); + cx.update_editor(|editor, cx| { + editor.context_menu_prev(&ContextMenuPrev, cx); + }); + cx.run_until_parked(); + cx.update_editor(|editor, cx| { + editor.context_menu_next(&ContextMenuNext, cx); + }); + cx.run_until_parked(); + cx.update_editor(|editor, cx| { + editor + .compose_completion(&ComposeCompletion::default(), cx) + .expect("No task returned") + }) + .await + .expect("Completion failed"); + cx.run_until_parked(); + + cx.update_editor(|editor, cx| { + assert_eq!( + resolve_requests_1.load(atomic::Ordering::Acquire), + 1, + "Should always resolve once despite multiple selections" + ); + assert_eq!( + resolve_requests_2.load(atomic::Ordering::Acquire), + 1, + "Should always resolve once after multiple selections and applying the completion" + ); + assert_eq!( + editor.text(cx), + "fn main() { let a = ??.other; }", + "Should use resolved data when applying the completion" + ); + }); +} + #[gpui::test] async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); @@ -10950,15 +11117,10 @@ async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppCo // Completions that have already been resolved are skipped. assert_eq!( *resolved_items.lock(), - [ - // Selected item is always resolved even if it was resolved before. - &items_out[items_out.len() - 1..items_out.len()], - &items_out[items_out.len() - 16..items_out.len() - 4] - ] - .concat() - .iter() - .cloned() - .collect::>() + items_out[items_out.len() - 16..items_out.len() - 4] + .iter() + .cloned() + .collect::>() ); resolved_items.lock().clear(); } diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index b68e22051f..5b8f7759c0 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1918,6 +1918,7 @@ impl LspCommand for GetCompletions { new_text, server_id, lsp_completion, + resolved: false, } }) .collect()) diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 4ec6f905b7..940ddce2d0 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -4152,38 +4152,27 @@ impl LspStore { let mut did_resolve = false; if let Some((client, project_id)) = client { for completion_index in completion_indices { - let (server_id, completion) = { - let completions = completions.borrow_mut(); - let completion = &completions[completion_index]; - did_resolve = true; - let server_id = completion.server_id; - let completion = completion.lsp_completion.clone(); + let server_id = completions.borrow()[completion_index].server_id; - (server_id, completion) - }; - - Self::resolve_completion_remote( + if Self::resolve_completion_remote( project_id, server_id, buffer_id, completions.clone(), completion_index, - completion, client.clone(), language_registry.clone(), ) - .await; + .await + .log_err() + .is_some() + { + did_resolve = true; + } } } else { for completion_index in completion_indices { - let (server_id, completion) = { - let completions = completions.borrow_mut(); - let completion = &completions[completion_index]; - let server_id = completion.server_id; - let completion = completion.lsp_completion.clone(); - - (server_id, completion) - }; + let server_id = completions.borrow()[completion_index].server_id; let server_and_adapter = this .read_with(&cx, |lsp_store, _| { @@ -4198,17 +4187,27 @@ impl LspStore { continue; }; - did_resolve = true; - Self::resolve_completion_local( + let resolved = Self::resolve_completion_local( server, - adapter, &buffer_snapshot, completions.clone(), completion_index, - completion, - language_registry.clone(), ) - .await; + .await + .log_err() + .is_some(); + if resolved { + Self::regenerate_completion_labels( + adapter, + &buffer_snapshot, + completions.clone(), + completion_index, + language_registry.clone(), + ) + .await + .log_err(); + did_resolve = true; + } } } @@ -4218,13 +4217,10 @@ impl LspStore { async fn resolve_completion_local( server: Arc, - adapter: Arc, snapshot: &BufferSnapshot, completions: Rc>>, completion_index: usize, - completion: lsp::CompletionItem, - language_registry: Arc, - ) { + ) -> Result<()> { let can_resolve = server .capabilities() .completion_provider @@ -4232,30 +4228,17 @@ impl LspStore { .and_then(|options| options.resolve_provider) .unwrap_or(false); if !can_resolve { - return; + return Ok(()); } - let request = server.request::(completion); - let Some(completion_item) = request.await.log_err() else { - return; + let request = { + let completion = &completions.borrow()[completion_index]; + if completion.resolved { + return Ok(()); + } + server.request::(completion.lsp_completion.clone()) }; - - if let Some(lsp_documentation) = completion_item.documentation.as_ref() { - let documentation = language::prepare_completion_documentation( - lsp_documentation, - &language_registry, - snapshot.language().cloned(), - ) - .await; - - let mut completions = completions.borrow_mut(); - let completion = &mut completions[completion_index]; - completion.documentation = Some(documentation); - } else { - let mut completions = completions.borrow_mut(); - let completion = &mut completions[completion_index]; - completion.documentation = Some(Documentation::Undocumented); - } + let completion_item = request.await?; if let Some(text_edit) = completion_item.text_edit.as_ref() { // Technically we don't have to parse the whole `text_edit`, since the only @@ -4283,28 +4266,61 @@ impl LspStore { } } + let mut completions = completions.borrow_mut(); + let completion = &mut completions[completion_index]; + completion.lsp_completion = completion_item; + completion.resolved = true; + Ok(()) + } + + async fn regenerate_completion_labels( + adapter: Arc, + snapshot: &BufferSnapshot, + completions: Rc>>, + completion_index: usize, + language_registry: Arc, + ) -> Result<()> { + let completion_item = completions.borrow()[completion_index] + .lsp_completion + .clone(); + if let Some(lsp_documentation) = completion_item.documentation.as_ref() { + let documentation = language::prepare_completion_documentation( + lsp_documentation, + &language_registry, + snapshot.language().cloned(), + ) + .await; + + let mut completions = completions.borrow_mut(); + let completion = &mut completions[completion_index]; + completion.documentation = Some(documentation); + } else { + let mut completions = completions.borrow_mut(); + let completion = &mut completions[completion_index]; + completion.documentation = Some(Documentation::Undocumented); + } + // NB: Zed does not have `details` inside the completion resolve capabilities, but certain language servers violate the spec and do not return `details` immediately, e.g. https://github.com/yioneko/vtsls/issues/213 // So we have to update the label here anyway... let new_label = match snapshot.language() { - Some(language) => adapter - .labels_for_completions(&[completion_item.clone()], language) - .await - .log_err() - .unwrap_or_default(), + Some(language) => { + adapter + .labels_for_completions(&[completion_item.clone()], language) + .await? + } None => Vec::new(), } .pop() .flatten() .unwrap_or_else(|| { CodeLabel::plain( - completion_item.label.clone(), + completion_item.label, completion_item.filter_text.as_deref(), ) }); let mut completions = completions.borrow_mut(); let completion = &mut completions[completion_index]; - completion.lsp_completion = completion_item; if completion.label.filter_text() == new_label.filter_text() { completion.label = new_label; } else { @@ -4317,6 +4333,8 @@ impl LspStore { new_label.filter_text() ); } + + Ok(()) } #[allow(clippy::too_many_arguments)] @@ -4326,29 +4344,30 @@ impl LspStore { buffer_id: BufferId, completions: Rc>>, completion_index: usize, - completion: lsp::CompletionItem, client: AnyProtoClient, language_registry: Arc, - ) { + ) -> Result<()> { + let lsp_completion = { + let completion = &completions.borrow()[completion_index]; + if completion.resolved { + return Ok(()); + } + serde_json::to_string(&completion.lsp_completion) + .unwrap() + .into_bytes() + }; let request = proto::ResolveCompletionDocumentation { project_id, language_server_id: server_id.0 as u64, - lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(), + lsp_completion, buffer_id: buffer_id.into(), }; - let Some(response) = client + let response = client .request(request) .await - .context("completion documentation resolve proto request") - .log_err() - else { - return; - }; - let Some(lsp_completion) = serde_json::from_slice(&response.lsp_completion).log_err() - else { - return; - }; + .context("completion documentation resolve proto request")?; + let lsp_completion = serde_json::from_slice(&response.lsp_completion)?; let documentation = if response.documentation.is_empty() { Documentation::Undocumented @@ -4366,6 +4385,7 @@ impl LspStore { let completion = &mut completions[completion_index]; completion.documentation = Some(documentation); completion.lsp_completion = lsp_completion; + completion.resolved = true; let old_range = response .old_start @@ -4377,12 +4397,15 @@ impl LspStore { completion.old_range = old_start..old_end; } } + + Ok(()) } pub fn apply_additional_edits_for_completion( &self, buffer_handle: Model, - completion: Completion, + completions: Rc>>, + completion_index: usize, push_to_history: bool, cx: &mut ModelContext, ) -> Task>> { @@ -4391,8 +4414,9 @@ impl LspStore { if let Some((client, project_id)) = self.upstream_client() { cx.spawn(move |_, mut cx| async move { - let response = client - .request(proto::ApplyCompletionAdditionalEdits { + let request = { + let completion = completions.borrow()[completion_index].clone(); + proto::ApplyCompletionAdditionalEdits { project_id, buffer_id: buffer_id.into(), completion: Some(Self::serialize_completion(&CoreCompletion { @@ -4400,9 +4424,13 @@ impl LspStore { new_text: completion.new_text, server_id: completion.server_id, lsp_completion: completion.lsp_completion, + resolved: completion.resolved, })), - }) - .await?; + } + }; + + let response = client.request(request).await?; + completions.borrow_mut()[completion_index].resolved = true; if let Some(transaction) = response.transaction { let transaction = language::proto::deserialize_transaction(transaction)?; @@ -4422,34 +4450,31 @@ impl LspStore { } }) } else { - let server_id = completion.server_id; - let lang_server = match self.language_server_for_local_buffer(buffer, server_id, cx) { + let server_id = completions.borrow()[completion_index].server_id; + let server = match self.language_server_for_local_buffer(buffer, server_id, cx) { Some((_, server)) => server.clone(), - _ => return Task::ready(Ok(Default::default())), + _ => return Task::ready(Ok(None)), }; + let snapshot = buffer_handle.read(&cx).snapshot(); cx.spawn(move |this, mut cx| async move { - let can_resolve = lang_server - .capabilities() - .completion_provider - .as_ref() - .and_then(|options| options.resolve_provider) - .unwrap_or(false); - let additional_text_edits = if can_resolve { - lang_server - .request::(completion.lsp_completion) - .await? - .additional_text_edits - } else { - completion.lsp_completion.additional_text_edits - }; + Self::resolve_completion_local( + server.clone(), + &snapshot, + completions.clone(), + completion_index, + ) + .await + .context("resolving completion")?; + let completion = completions.borrow()[completion_index].clone(); + let additional_text_edits = completion.lsp_completion.additional_text_edits; if let Some(edits) = additional_text_edits { let edits = this .update(&mut cx, |this, cx| { this.as_local_mut().unwrap().edits_from_lsp( &buffer_handle, edits, - lang_server.server_id(), + server.server_id(), None, cx, ) @@ -6803,7 +6828,7 @@ impl LspStore { let apply_additional_edits = this.update(&mut cx, |this, cx| { this.apply_additional_edits_for_completion( buffer, - Completion { + Rc::new(RefCell::new(Box::new([Completion { old_range: completion.old_range, new_text: completion.new_text, lsp_completion: completion.lsp_completion, @@ -6815,7 +6840,9 @@ impl LspStore { filter_range: Default::default(), }, confirm: None, - }, + resolved: completion.resolved, + }]))), + 0, false, cx, ) @@ -7780,6 +7807,7 @@ impl LspStore { new_text: completion.new_text.clone(), server_id: completion.server_id.0 as u64, lsp_completion: serde_json::to_vec(&completion.lsp_completion).unwrap(), + resolved: completion.resolved, } } @@ -7799,6 +7827,7 @@ impl LspStore { new_text: completion.new_text, server_id: LanguageServerId(completion.server_id as usize), lsp_completion, + resolved: completion.resolved, }) } @@ -7900,6 +7929,7 @@ async fn populate_labels_for_completions( documentation, lsp_completion, confirm: None, + resolved: false, }) } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 855be5cec2..177e05bd62 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -73,10 +73,8 @@ use snippet::Snippet; use snippet_provider::SnippetProvider; use std::{ borrow::Cow, - cell::RefCell, ops::Range, path::{Component, Path, PathBuf}, - rc::Rc, str, sync::Arc, time::Duration, @@ -353,6 +351,8 @@ pub struct Completion { pub documentation: Option, /// The raw completion provided by the language server. pub lsp_completion: lsp::CompletionItem, + /// Whether this completion has been resolved, to ensure it happens once per completion. + pub resolved: bool, /// An optional callback to invoke when this completion is confirmed. /// Returns, whether new completions should be retriggered after the current one. /// If `true` is returned, the editor will show a new completion menu after this completion is confirmed. @@ -380,6 +380,7 @@ pub(crate) struct CoreCompletion { new_text: String, server_id: LanguageServerId, lsp_completion: lsp::CompletionItem, + resolved: bool, } /// A code action provided by a language server. @@ -2863,35 +2864,6 @@ impl Project { }) } - pub fn resolve_completions( - &self, - buffer: Model, - completion_indices: Vec, - completions: Rc>>, - cx: &mut ModelContext, - ) -> Task> { - self.lsp_store.update(cx, |lsp_store, cx| { - lsp_store.resolve_completions(buffer, completion_indices, completions, cx) - }) - } - - pub fn apply_additional_edits_for_completion( - &self, - buffer_handle: Model, - completion: Completion, - push_to_history: bool, - cx: &mut ModelContext, - ) -> Task>> { - self.lsp_store.update(cx, |lsp_store, cx| { - lsp_store.apply_additional_edits_for_completion( - buffer_handle, - completion, - push_to_history, - cx, - ) - }) - } - pub fn code_actions( &mut self, buffer_handle: &Model, diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index 14dc999031..29e90cc71e 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -927,6 +927,7 @@ message Completion { string new_text = 3; uint64 server_id = 4; bytes lsp_completion = 5; + bool resolved = 6; } message GetCodeActions {