project: Add tests for more cases in LSP completions (#27650)

This PR adds two more cases to existing LSP completion cases.

- When text_edit exists:  (New test)
   1. we use text_edit, over insert_text and label

- When edit range exists (and text_edit is None):  (New test)
   1. insert_text is used over label if exists
   2. label is used otherwise

- When not edit range exists (and text_edit is None):  (Existing test)
   1. insert_text is used over label if exists
   2. label is used otherwise

Release Notes:

- N/A
This commit is contained in:
Smit Barmase 2025-03-28 03:13:18 -07:00 committed by GitHub
parent c8105863c8
commit 7ac51e4c82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -2776,6 +2776,210 @@ async fn test_definition(cx: &mut gpui::TestAppContext) {
} }
} }
#[gpui::test]
async fn test_completions_with_text_edit(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
path!("/dir"),
json!({
"a.ts": "",
}),
)
.await;
let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(typescript_lang());
let mut fake_language_servers = language_registry.register_fake_lsp(
"TypeScript",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
completion_provider: Some(lsp::CompletionOptions {
trigger_characters: Some(vec![".".to_string()]),
..Default::default()
}),
..Default::default()
},
..Default::default()
},
);
let (buffer, _handle) = project
.update(cx, |p, cx| {
p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
})
.await
.unwrap();
let fake_server = fake_language_servers.next().await.unwrap();
// When text_edit exists, it takes precedence over insert_text and label
let text = "let a = obj.fqn";
buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
let completions = project.update(cx, |project, cx| {
project.completions(&buffer, text.len(), DEFAULT_COMPLETION_CONTEXT, cx)
});
fake_server
.set_request_handler::<lsp::request::Completion, _, _>(|_, _| async {
Ok(Some(lsp::CompletionResponse::Array(vec![
lsp::CompletionItem {
label: "labelText".into(),
insert_text: Some("insertText".into()),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(
lsp::Position::new(0, text.len() as u32 - 3),
lsp::Position::new(0, text.len() as u32),
),
new_text: "textEditText".into(),
})),
..Default::default()
},
])))
})
.next()
.await;
let completions = completions.await.unwrap().unwrap();
let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
assert_eq!(completions.len(), 1);
assert_eq!(completions[0].new_text, "textEditText");
assert_eq!(
completions[0].old_range.to_offset(&snapshot),
text.len() - 3..text.len()
);
}
#[gpui::test]
async fn test_completions_with_edit_ranges(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
path!("/dir"),
json!({
"a.ts": "",
}),
)
.await;
let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(typescript_lang());
let mut fake_language_servers = language_registry.register_fake_lsp(
"TypeScript",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
completion_provider: Some(lsp::CompletionOptions {
trigger_characters: Some(vec![".".to_string()]),
..Default::default()
}),
..Default::default()
},
..Default::default()
},
);
let (buffer, _handle) = project
.update(cx, |p, cx| {
p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
})
.await
.unwrap();
let fake_server = fake_language_servers.next().await.unwrap();
let text = "let a = obj.fqn";
// Test 1: When text_edit is None but insert_text exists with default edit_range
{
buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
let completions = project.update(cx, |project, cx| {
project.completions(&buffer, text.len(), DEFAULT_COMPLETION_CONTEXT, cx)
});
fake_server
.set_request_handler::<lsp::request::Completion, _, _>(|_, _| async {
Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
is_incomplete: false,
item_defaults: Some(lsp::CompletionListItemDefaults {
edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
lsp::Range::new(
lsp::Position::new(0, text.len() as u32 - 3),
lsp::Position::new(0, text.len() as u32),
),
)),
..Default::default()
}),
items: vec![lsp::CompletionItem {
label: "labelText".into(),
insert_text: Some("insertText".into()),
text_edit: None,
..Default::default()
}],
})))
})
.next()
.await;
let completions = completions.await.unwrap().unwrap();
let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
assert_eq!(completions.len(), 1);
assert_eq!(completions[0].new_text, "insertText");
assert_eq!(
completions[0].old_range.to_offset(&snapshot),
text.len() - 3..text.len()
);
}
// Test 2: When both text_edit and insert_text are None with default edit_range
{
buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
let completions = project.update(cx, |project, cx| {
project.completions(&buffer, text.len(), DEFAULT_COMPLETION_CONTEXT, cx)
});
fake_server
.set_request_handler::<lsp::request::Completion, _, _>(|_, _| async {
Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
is_incomplete: false,
item_defaults: Some(lsp::CompletionListItemDefaults {
edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
lsp::Range::new(
lsp::Position::new(0, text.len() as u32 - 3),
lsp::Position::new(0, text.len() as u32),
),
)),
..Default::default()
}),
items: vec![lsp::CompletionItem {
label: "labelText".into(),
insert_text: None,
text_edit: None,
..Default::default()
}],
})))
})
.next()
.await;
let completions = completions.await.unwrap().unwrap();
let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
assert_eq!(completions.len(), 1);
assert_eq!(completions[0].new_text, "labelText");
assert_eq!(
completions[0].old_range.to_offset(&snapshot),
text.len() - 3..text.len()
);
}
}
#[gpui::test] #[gpui::test]
async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) { async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
init_test(cx); init_test(cx);
@ -2816,6 +3020,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
let fake_server = fake_language_servers.next().await.unwrap(); let fake_server = fake_language_servers.next().await.unwrap();
// Test 1: When text_edit is None but insert_text exists (no edit_range in defaults)
let text = "let a = b.fqn"; let text = "let a = b.fqn";
buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
let completions = project.update(cx, |project, cx| { let completions = project.update(cx, |project, cx| {
@ -2843,6 +3048,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
text.len() - 3..text.len() text.len() - 3..text.len()
); );
// Test 2: When both text_edit and insert_text are None (no edit_range in defaults)
let text = "let a = \"atoms/cmp\""; let text = "let a = \"atoms/cmp\"";
buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
let completions = project.update(cx, |project, cx| { let completions = project.update(cx, |project, cx| {