Detailed changes
@@ -6481,6 +6481,7 @@ impl Editor {
.selections
.all::<MultiBufferOffset>(&self.display_snapshot(cx));
let mut ranges = Vec::new();
+ let mut all_commit_ranges = Vec::new();
let mut linked_edits = LinkedEdits::new();
let text: Arc<str> = new_text.clone().into();
@@ -6506,10 +6507,12 @@ impl Editor {
ranges.push(range.clone());
+ let start_anchor = snapshot.anchor_before(range.start);
+ let end_anchor = snapshot.anchor_after(range.end);
+ let anchor_range = start_anchor.text_anchor..end_anchor.text_anchor;
+ all_commit_ranges.push(anchor_range.clone());
+
if !self.linked_edit_ranges.is_empty() {
- let start_anchor = snapshot.anchor_before(range.start);
- let end_anchor = snapshot.anchor_after(range.end);
- let anchor_range = start_anchor.text_anchor..end_anchor.text_anchor;
linked_edits.push(&self, anchor_range, text.clone(), cx);
}
}
@@ -6596,6 +6599,7 @@ impl Editor {
completions_menu.completions.clone(),
candidate_id,
true,
+ all_commit_ranges,
cx,
);
@@ -26575,6 +26579,7 @@ pub trait CompletionProvider {
_completions: Rc<RefCell<Box<[Completion]>>>,
_completion_index: usize,
_push_to_history: bool,
+ _all_commit_ranges: Vec<Range<language::Anchor>>,
_cx: &mut Context<Editor>,
) -> Task<Result<Option<language::Transaction>>> {
Task::ready(Ok(None))
@@ -26943,6 +26948,7 @@ impl CompletionProvider for Entity<Project> {
completions: Rc<RefCell<Box<[Completion]>>>,
completion_index: usize,
push_to_history: bool,
+ all_commit_ranges: Vec<Range<language::Anchor>>,
cx: &mut Context<Editor>,
) -> Task<Result<Option<language::Transaction>>> {
self.update(cx, |project, cx| {
@@ -26952,6 +26958,7 @@ impl CompletionProvider for Entity<Project> {
completions,
completion_index,
push_to_history,
+ all_commit_ranges,
cx,
)
})
@@ -19888,6 +19888,100 @@ async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
}
+#[gpui::test]
+async fn test_completions_with_additional_edits_and_multiple_cursors(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorLspTestContext::new_typescript(
+ lsp::ServerCapabilities {
+ completion_provider: Some(lsp::CompletionOptions {
+ resolve_provider: Some(true),
+ ..Default::default()
+ }),
+ ..Default::default()
+ },
+ cx,
+ )
+ .await;
+
+ cx.set_state(
+ "import { «Fooˇ» } from './types';\n\nclass Bar {\n method(): «Fooˇ» { return new Foo(); }\n}",
+ );
+
+ cx.simulate_keystroke("F");
+ cx.simulate_keystroke("o");
+
+ let completion_item = lsp::CompletionItem {
+ label: "FooBar".into(),
+ kind: Some(lsp::CompletionItemKind::CLASS),
+ text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+ range: lsp::Range {
+ start: lsp::Position {
+ line: 3,
+ character: 14,
+ },
+ end: lsp::Position {
+ line: 3,
+ character: 16,
+ },
+ },
+ new_text: "FooBar".to_string(),
+ })),
+ additional_text_edits: Some(vec![lsp::TextEdit {
+ range: lsp::Range {
+ start: lsp::Position {
+ line: 0,
+ character: 9,
+ },
+ end: lsp::Position {
+ line: 0,
+ character: 11,
+ },
+ },
+ new_text: "FooBar".to_string(),
+ }]),
+ ..Default::default()
+ };
+
+ let closure_completion_item = completion_item.clone();
+ let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
+ let task_completion_item = closure_completion_item.clone();
+ async move {
+ Ok(Some(lsp::CompletionResponse::Array(vec![
+ task_completion_item,
+ ])))
+ }
+ });
+
+ request.next().await;
+
+ cx.condition(|editor, _| editor.context_menu_visible())
+ .await;
+ let apply_additional_edits = cx.update_editor(|editor, window, cx| {
+ editor
+ .confirm_completion(&ConfirmCompletion::default(), window, cx)
+ .unwrap()
+ });
+
+ cx.assert_editor_state(
+ "import { FooBarˇ } from './types';\n\nclass Bar {\n method(): FooBarˇ { return new Foo(); }\n}",
+ );
+
+ cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
+ let task_completion_item = completion_item.clone();
+ async move { Ok(task_completion_item) }
+ })
+ .next()
+ .await
+ .unwrap();
+
+ apply_additional_edits.await.unwrap();
+
+ cx.assert_editor_state(
+ "import { FooBarˇ } from './types';\n\nclass Bar {\n method(): FooBarˇ { return new Foo(); }\n}",
+ );
+}
+
#[gpui::test]
async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
init_test(cx, |_| {});
@@ -6643,6 +6643,7 @@ impl LspStore {
completions: Rc<RefCell<Box<[Completion]>>>,
completion_index: usize,
push_to_history: bool,
+ all_commit_ranges: Vec<Range<language::Anchor>>,
cx: &mut Context<Self>,
) -> Task<Result<Option<Transaction>>> {
if let Some((client, project_id)) = self.upstream_client() {
@@ -6659,6 +6660,11 @@ impl LspStore {
new_text: completion.new_text,
source: completion.source,
})),
+ all_commit_ranges: all_commit_ranges
+ .iter()
+ .cloned()
+ .map(language::proto::serialize_anchor_range)
+ .collect(),
}
};
@@ -6752,12 +6758,15 @@ impl LspStore {
let has_overlap = if is_file_start_auto_import {
false
} else {
- let start_within = primary.start.cmp(&range.start, buffer).is_le()
- && primary.end.cmp(&range.start, buffer).is_ge();
- let end_within = range.start.cmp(&primary.end, buffer).is_le()
- && range.end.cmp(&primary.end, buffer).is_ge();
- let result = start_within || end_within;
- result
+ all_commit_ranges.iter().any(|commit_range| {
+ let start_within =
+ commit_range.start.cmp(&range.start, buffer).is_le()
+ && commit_range.end.cmp(&range.start, buffer).is_ge();
+ let end_within =
+ range.start.cmp(&commit_range.end, buffer).is_le()
+ && range.end.cmp(&commit_range.end, buffer).is_ge();
+ start_within || end_within
+ })
};
//Skip additional edits which overlap with the primary completion edit
@@ -10418,13 +10427,19 @@ impl LspStore {
envelope: TypedEnvelope<proto::ApplyCompletionAdditionalEdits>,
mut cx: AsyncApp,
) -> Result<proto::ApplyCompletionAdditionalEditsResponse> {
- let (buffer, completion) = this.update(&mut cx, |this, cx| {
+ let (buffer, completion, all_commit_ranges) = this.update(&mut cx, |this, cx| {
let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
let buffer = this.buffer_store.read(cx).get_existing(buffer_id)?;
let completion = Self::deserialize_completion(
envelope.payload.completion.context("invalid completion")?,
)?;
- anyhow::Ok((buffer, completion))
+ let all_commit_ranges = envelope
+ .payload
+ .all_commit_ranges
+ .into_iter()
+ .map(language::proto::deserialize_anchor_range)
+ .collect::<Result<Vec<_>, _>>()?;
+ anyhow::Ok((buffer, completion, all_commit_ranges))
})?;
let apply_additional_edits = this.update(&mut cx, |this, cx| {
@@ -10444,6 +10459,7 @@ impl LspStore {
}]))),
0,
false,
+ all_commit_ranges,
cx,
)
});
@@ -230,6 +230,7 @@ message ApplyCompletionAdditionalEdits {
uint64 project_id = 1;
uint64 buffer_id = 2;
Completion completion = 3;
+ repeated AnchorRange all_commit_ranges = 4;
}
message ApplyCompletionAdditionalEditsResponse {