@@ -1,6 +1,7 @@
use super::*;
use crate::{
JoinLines,
+ linked_editing_ranges::LinkedEditingRanges,
scroll::scroll_amount::ScrollAmount,
test::{
assert_text_with_selections, build_editor,
@@ -19559,6 +19560,146 @@ async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
});
}
+#[gpui::test]
+async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_file(path!("/file.html"), Default::default())
+ .await;
+
+ let project = Project::test(fs, [path!("/").as_ref()], cx).await;
+
+ let language_registry = project.read_with(cx, |project, _| project.languages().clone());
+ let html_language = Arc::new(Language::new(
+ LanguageConfig {
+ name: "HTML".into(),
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["html".to_string()],
+ ..LanguageMatcher::default()
+ },
+ brackets: BracketPairConfig {
+ pairs: vec![BracketPair {
+ start: "<".into(),
+ end: ">".into(),
+ close: true,
+ ..Default::default()
+ }],
+ ..Default::default()
+ },
+ ..Default::default()
+ },
+ Some(tree_sitter_html::LANGUAGE.into()),
+ ));
+ language_registry.add(html_language);
+ let mut fake_servers = language_registry.register_fake_lsp(
+ "HTML",
+ FakeLspAdapter {
+ capabilities: lsp::ServerCapabilities {
+ completion_provider: Some(lsp::CompletionOptions {
+ resolve_provider: Some(true),
+ ..Default::default()
+ }),
+ ..Default::default()
+ },
+ ..Default::default()
+ },
+ );
+
+ let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
+ let cx = &mut VisualTestContext::from_window(*workspace, cx);
+
+ let worktree_id = workspace
+ .update(cx, |workspace, _window, cx| {
+ workspace.project().update(cx, |project, cx| {
+ project.worktrees(cx).next().unwrap().read(cx).id()
+ })
+ })
+ .unwrap();
+ project
+ .update(cx, |project, cx| {
+ project.open_local_buffer_with_lsp(path!("/file.html"), cx)
+ })
+ .await
+ .unwrap();
+ let editor = workspace
+ .update(cx, |workspace, window, cx| {
+ workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
+ })
+ .unwrap()
+ .await
+ .unwrap()
+ .downcast::<Editor>()
+ .unwrap();
+
+ let fake_server = fake_servers.next().await.unwrap();
+ editor.update_in(cx, |editor, window, cx| {
+ editor.set_text("<ad></ad>", window, cx);
+ editor.change_selections(None, window, cx, |selections| {
+ selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
+ });
+ let Some((buffer, _)) = editor
+ .buffer
+ .read(cx)
+ .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
+ else {
+ panic!("Failed to get buffer for selection position");
+ };
+ let buffer = buffer.read(cx);
+ let buffer_id = buffer.remote_id();
+ let opening_range =
+ buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
+ let closing_range =
+ buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
+ let mut linked_ranges = HashMap::default();
+ linked_ranges.insert(
+ buffer_id,
+ vec![(opening_range.clone(), vec![closing_range.clone()])],
+ );
+ editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
+ });
+ let mut completion_handle =
+ fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
+ Ok(Some(lsp::CompletionResponse::Array(vec![
+ lsp::CompletionItem {
+ label: "head".to_string(),
+ text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
+ lsp::InsertReplaceEdit {
+ new_text: "head".to_string(),
+ insert: lsp::Range::new(
+ lsp::Position::new(0, 1),
+ lsp::Position::new(0, 3),
+ ),
+ replace: lsp::Range::new(
+ lsp::Position::new(0, 1),
+ lsp::Position::new(0, 3),
+ ),
+ },
+ )),
+ ..Default::default()
+ },
+ ])))
+ });
+ editor.update_in(cx, |editor, window, cx| {
+ editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
+ });
+ cx.run_until_parked();
+ completion_handle.next().await.unwrap();
+ editor.update(cx, |editor, _| {
+ assert!(
+ editor.context_menu_visible(),
+ "Completion menu should be visible"
+ );
+ });
+ editor.update_in(cx, |editor, window, cx| {
+ editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
+ });
+ cx.executor().run_until_parked();
+ editor.update(cx, |editor, cx| {
+ assert_eq!(editor.text(cx), "<head></head>");
+ });
+}
+
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
point..point