Detailed changes
@@ -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<Buffer>,
- _: project::Completion,
- _: bool,
- _: &mut ViewContext<Editor>,
- ) -> Task<Result<Option<language::Transaction>>> {
- Task::ready(Ok(None))
- }
-
fn is_completion_trigger(
&self,
buffer: &Model<Buffer>,
@@ -79,16 +79,6 @@ impl CompletionProvider for MessageEditorCompletionProvider {
Task::ready(Ok(false))
}
- fn apply_additional_edits_for_completion(
- &self,
- _buffer: Model<Buffer>,
- _completion: Completion,
- _push_to_history: bool,
- _cx: &mut ViewContext<Editor>,
- ) -> Task<Result<Option<language::Transaction>>> {
- Task::ready(Ok(None))
- }
-
fn is_completion_trigger(
&self,
_buffer: &Model<Buffer>,
@@ -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()
@@ -224,6 +224,7 @@ impl CompletionsMenu {
documentation: None,
lsp_completion: Default::default(),
confirm: None,
+ resolved: true,
})
.collect();
@@ -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<Buffer>,
- completion: Completion,
- push_to_history: bool,
- cx: &mut ViewContext<Editor>,
- ) -> Task<Result<Option<language::Transaction>>>;
+ _buffer: Model<Buffer>,
+ _completions: Rc<RefCell<Box<[Completion]>>>,
+ _completion_index: usize,
+ _push_to_history: bool,
+ _cx: &mut ViewContext<Editor>,
+ ) -> Task<Result<Option<language::Transaction>>> {
+ 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<Project> {
cx: &mut ViewContext<Editor>,
) -> Task<Result<bool>> {
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<Buffer>,
- completion: Completion,
+ completions: Rc<RefCell<Box<[Completion]>>>,
+ completion_index: usize,
push_to_history: bool,
cx: &mut ViewContext<Editor>,
) -> Task<Result<Option<language::Transaction>>> {
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,
+ )
+ })
})
}
@@ -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::<lsp::request::Completion, _, _>(move |_, _, _| {
+ let item1 = item1.clone();
+ cx.handle_request::<lsp::request::Completion, _, _>({
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,168 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches(
}
});
- cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(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::<lsp::request::ResolveCompletionItem, _, _>({
+ 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::<lsp::request::ResolveCompletionItem, _, _>(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()
+ 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<_>>(),
+ vec!["method id()", "other"],
+ "Should update first completion label, but not second as the filter text did not match."
+ );
+ }
+ CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
+ }
+ });
+}
+
+#[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::<lsp::request::ResolveCompletionItem, _, _>({
+ 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::<lsp::request::Completion, _, _>(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.run_until_parked();
+ 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
@@ -10778,13 +10906,52 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches(
.iter()
.map(|completion| &completion.label.text)
.collect::<Vec<_>>(),
- vec!["method id()", "other"],
- "Should update first completion label, but not second as the filter text did not match."
- );
+ 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]
@@ -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::<Vec<lsp::CompletionItem>>()
+ items_out[items_out.len() - 16..items_out.len() - 4]
+ .iter()
+ .cloned()
+ .collect::<Vec<lsp::CompletionItem>>()
);
resolved_items.lock().clear();
}
@@ -1918,6 +1918,7 @@ impl LspCommand for GetCompletions {
new_text,
server_id,
lsp_completion,
+ resolved: false,
}
})
.collect())
@@ -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();
-
- (server_id, completion)
- };
+ let server_id = completions.borrow()[completion_index].server_id;
- 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<lsp::LanguageServer>,
- adapter: Arc<CachedLspAdapter>,
snapshot: &BufferSnapshot,
completions: Rc<RefCell<Box<[Completion]>>>,
completion_index: usize,
- completion: lsp::CompletionItem,
- language_registry: Arc<LanguageRegistry>,
- ) {
+ ) -> 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::<lsp::request::ResolveCompletionItem>(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::<lsp::request::ResolveCompletionItem>(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<CachedLspAdapter>,
+ snapshot: &BufferSnapshot,
+ completions: Rc<RefCell<Box<[Completion]>>>,
+ completion_index: usize,
+ language_registry: Arc<LanguageRegistry>,
+ ) -> 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<RefCell<Box<[Completion]>>>,
completion_index: usize,
- completion: lsp::CompletionItem,
client: AnyProtoClient,
language_registry: Arc<LanguageRegistry>,
- ) {
+ ) -> 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<Buffer>,
- completion: Completion,
+ completions: Rc<RefCell<Box<[Completion]>>>,
+ completion_index: usize,
push_to_history: bool,
cx: &mut ModelContext<Self>,
) -> Task<Result<Option<Transaction>>> {
@@ -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::<lsp::request::ResolveCompletionItem>(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,
})
}
}
@@ -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<Documentation>,
/// 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<Buffer>,
- completion_indices: Vec<usize>,
- completions: Rc<RefCell<Box<[Completion]>>>,
- cx: &mut ModelContext<Self>,
- ) -> Task<Result<bool>> {
- 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<Buffer>,
- completion: Completion,
- push_to_history: bool,
- cx: &mut ModelContext<Self>,
- ) -> Task<Result<Option<Transaction>>> {
- 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<T: Clone + ToOffset>(
&mut self,
buffer_handle: &Model<Buffer>,
@@ -927,6 +927,7 @@ message Completion {
string new_text = 3;
uint64 server_id = 4;
bytes lsp_completion = 5;
+ bool resolved = 6;
}
message GetCodeActions {