diff --git a/crates/editor2/src/editor_tests.rs b/crates/editor2/src/editor_tests.rs index e640be8efe030250876138ed23a885119a5b7dbb..265bde908b564fcb34549cfea42f377e10837dbc 100644 --- a/crates/editor2/src/editor_tests.rs +++ b/crates/editor2/src/editor_tests.rs @@ -6325,7 +6325,6 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) { }); } -//todo!(finish editor tests) // #[gpui::test] // fn test_highlighted_ranges(cx: &mut TestAppContext) { // init_test(cx, |_| {}); diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 3abe5a37f97b9ce2d23f0c0c1d215e05883588b9..8f555ba9de714d9bcddedc8178ad94be88716327 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -328,7 +328,7 @@ impl EditorElement { }); } - fn modifiers_changed( + pub(crate) fn modifiers_changed( editor: &mut Editor, event: &ModifiersChangedEvent, cx: &mut ViewContext, diff --git a/crates/editor2/src/git.rs b/crates/editor2/src/git.rs index 6e408cd3a01f7603ccdf97660b8896fdee833d65..9190eed05a40a24063ccf78ca30a6b2966f43c47 100644 --- a/crates/editor2/src/git.rs +++ b/crates/editor2/src/git.rs @@ -88,195 +88,195 @@ pub fn diff_hunk_to_display(hunk: DiffHunk, snapshot: &DisplaySnapshot) -> } } -// #[cfg(any(test, feature = "test_support"))] -// mod tests { -// // use crate::editor_tests::init_test; -// use crate::Point; -// use gpui::TestAppContext; -// use multi_buffer::{ExcerptRange, MultiBuffer}; -// use project::{FakeFs, Project}; -// use unindent::Unindent; -// #[gpui::test] -// async fn test_diff_hunks_in_range(cx: &mut TestAppContext) { -// use git::diff::DiffHunkStatus; -// init_test(cx, |_| {}); +#[cfg(any(test, feature = "test_support"))] +mod tests { + use crate::editor_tests::init_test; + use crate::Point; + use gpui::{Context, TestAppContext}; + use multi_buffer::{ExcerptRange, MultiBuffer}; + use project::{FakeFs, Project}; + use unindent::Unindent; + #[gpui::test] + async fn test_diff_hunks_in_range(cx: &mut TestAppContext) { + use git::diff::DiffHunkStatus; + init_test(cx, |_| {}); -// let fs = FakeFs::new(cx.background()); -// let project = Project::test(fs, [], cx).await; + let fs = FakeFs::new(cx.background_executor.clone()); + let project = Project::test(fs, [], cx).await; -// // buffer has two modified hunks with two rows each -// let buffer_1 = project -// .update(cx, |project, cx| { -// project.create_buffer( -// " -// 1.zero -// 1.ONE -// 1.TWO -// 1.three -// 1.FOUR -// 1.FIVE -// 1.six -// " -// .unindent() -// .as_str(), -// None, -// cx, -// ) -// }) -// .unwrap(); -// buffer_1.update(cx, |buffer, cx| { -// buffer.set_diff_base( -// Some( -// " -// 1.zero -// 1.one -// 1.two -// 1.three -// 1.four -// 1.five -// 1.six -// " -// .unindent(), -// ), -// cx, -// ); -// }); + // buffer has two modified hunks with two rows each + let buffer_1 = project + .update(cx, |project, cx| { + project.create_buffer( + " + 1.zero + 1.ONE + 1.TWO + 1.three + 1.FOUR + 1.FIVE + 1.six + " + .unindent() + .as_str(), + None, + cx, + ) + }) + .unwrap(); + buffer_1.update(cx, |buffer, cx| { + buffer.set_diff_base( + Some( + " + 1.zero + 1.one + 1.two + 1.three + 1.four + 1.five + 1.six + " + .unindent(), + ), + cx, + ); + }); -// // buffer has a deletion hunk and an insertion hunk -// let buffer_2 = project -// .update(cx, |project, cx| { -// project.create_buffer( -// " -// 2.zero -// 2.one -// 2.two -// 2.three -// 2.four -// 2.five -// 2.six -// " -// .unindent() -// .as_str(), -// None, -// cx, -// ) -// }) -// .unwrap(); -// buffer_2.update(cx, |buffer, cx| { -// buffer.set_diff_base( -// Some( -// " -// 2.zero -// 2.one -// 2.one-and-a-half -// 2.two -// 2.three -// 2.four -// 2.six -// " -// .unindent(), -// ), -// cx, -// ); -// }); + // buffer has a deletion hunk and an insertion hunk + let buffer_2 = project + .update(cx, |project, cx| { + project.create_buffer( + " + 2.zero + 2.one + 2.two + 2.three + 2.four + 2.five + 2.six + " + .unindent() + .as_str(), + None, + cx, + ) + }) + .unwrap(); + buffer_2.update(cx, |buffer, cx| { + buffer.set_diff_base( + Some( + " + 2.zero + 2.one + 2.one-and-a-half + 2.two + 2.three + 2.four + 2.six + " + .unindent(), + ), + cx, + ); + }); -// cx.foreground().run_until_parked(); + cx.background_executor.run_until_parked(); -// let multibuffer = cx.add_model(|cx| { -// let mut multibuffer = MultiBuffer::new(0); -// multibuffer.push_excerpts( -// buffer_1.clone(), -// [ -// // excerpt ends in the middle of a modified hunk -// ExcerptRange { -// context: Point::new(0, 0)..Point::new(1, 5), -// primary: Default::default(), -// }, -// // excerpt begins in the middle of a modified hunk -// ExcerptRange { -// context: Point::new(5, 0)..Point::new(6, 5), -// primary: Default::default(), -// }, -// ], -// cx, -// ); -// multibuffer.push_excerpts( -// buffer_2.clone(), -// [ -// // excerpt ends at a deletion -// ExcerptRange { -// context: Point::new(0, 0)..Point::new(1, 5), -// primary: Default::default(), -// }, -// // excerpt starts at a deletion -// ExcerptRange { -// context: Point::new(2, 0)..Point::new(2, 5), -// primary: Default::default(), -// }, -// // excerpt fully contains a deletion hunk -// ExcerptRange { -// context: Point::new(1, 0)..Point::new(2, 5), -// primary: Default::default(), -// }, -// // excerpt fully contains an insertion hunk -// ExcerptRange { -// context: Point::new(4, 0)..Point::new(6, 5), -// primary: Default::default(), -// }, -// ], -// cx, -// ); -// multibuffer -// }); + let multibuffer = cx.build_model(|cx| { + let mut multibuffer = MultiBuffer::new(0); + multibuffer.push_excerpts( + buffer_1.clone(), + [ + // excerpt ends in the middle of a modified hunk + ExcerptRange { + context: Point::new(0, 0)..Point::new(1, 5), + primary: Default::default(), + }, + // excerpt begins in the middle of a modified hunk + ExcerptRange { + context: Point::new(5, 0)..Point::new(6, 5), + primary: Default::default(), + }, + ], + cx, + ); + multibuffer.push_excerpts( + buffer_2.clone(), + [ + // excerpt ends at a deletion + ExcerptRange { + context: Point::new(0, 0)..Point::new(1, 5), + primary: Default::default(), + }, + // excerpt starts at a deletion + ExcerptRange { + context: Point::new(2, 0)..Point::new(2, 5), + primary: Default::default(), + }, + // excerpt fully contains a deletion hunk + ExcerptRange { + context: Point::new(1, 0)..Point::new(2, 5), + primary: Default::default(), + }, + // excerpt fully contains an insertion hunk + ExcerptRange { + context: Point::new(4, 0)..Point::new(6, 5), + primary: Default::default(), + }, + ], + cx, + ); + multibuffer + }); -// let snapshot = multibuffer.read_with(cx, |b, cx| b.snapshot(cx)); + let snapshot = multibuffer.read_with(cx, |b, cx| b.snapshot(cx)); -// assert_eq!( -// snapshot.text(), -// " -// 1.zero -// 1.ONE -// 1.FIVE -// 1.six -// 2.zero -// 2.one -// 2.two -// 2.one -// 2.two -// 2.four -// 2.five -// 2.six" -// .unindent() -// ); + assert_eq!( + snapshot.text(), + " + 1.zero + 1.ONE + 1.FIVE + 1.six + 2.zero + 2.one + 2.two + 2.one + 2.two + 2.four + 2.five + 2.six" + .unindent() + ); -// let expected = [ -// (DiffHunkStatus::Modified, 1..2), -// (DiffHunkStatus::Modified, 2..3), -// //TODO: Define better when and where removed hunks show up at range extremities -// (DiffHunkStatus::Removed, 6..6), -// (DiffHunkStatus::Removed, 8..8), -// (DiffHunkStatus::Added, 10..11), -// ]; + let expected = [ + (DiffHunkStatus::Modified, 1..2), + (DiffHunkStatus::Modified, 2..3), + //TODO: Define better when and where removed hunks show up at range extremities + (DiffHunkStatus::Removed, 6..6), + (DiffHunkStatus::Removed, 8..8), + (DiffHunkStatus::Added, 10..11), + ]; -// assert_eq!( -// snapshot -// .git_diff_hunks_in_range(0..12) -// .map(|hunk| (hunk.status(), hunk.buffer_range)) -// .collect::>(), -// &expected, -// ); + assert_eq!( + snapshot + .git_diff_hunks_in_range(0..12) + .map(|hunk| (hunk.status(), hunk.buffer_range)) + .collect::>(), + &expected, + ); -// assert_eq!( -// snapshot -// .git_diff_hunks_in_range_rev(0..12) -// .map(|hunk| (hunk.status(), hunk.buffer_range)) -// .collect::>(), -// expected -// .iter() -// .rev() -// .cloned() -// .collect::>() -// .as_slice(), -// ); -// } -// } + assert_eq!( + snapshot + .git_diff_hunks_in_range_rev(0..12) + .map(|hunk| (hunk.status(), hunk.buffer_range)) + .collect::>(), + expected + .iter() + .rev() + .cloned() + .collect::>() + .as_slice(), + ); + } +} diff --git a/crates/editor2/src/link_go_to_definition.rs b/crates/editor2/src/link_go_to_definition.rs index 092882573c59961dc9e6cba6ee65aa022367107d..60c966d4c7cf68ea0e420ee3d7270be1d671cbff 100644 --- a/crates/editor2/src/link_go_to_definition.rs +++ b/crates/editor2/src/link_go_to_definition.rs @@ -608,671 +608,672 @@ fn go_to_fetched_definition_of_kind( } } -// #[cfg(test)] -// mod tests { -// use super::*; -// use crate::{ -// display_map::ToDisplayPoint, -// editor_tests::init_test, -// inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels}, -// test::editor_lsp_test_context::EditorLspTestContext, -// }; -// use futures::StreamExt; -// use gpui::{ -// platform::{self, Modifiers, ModifiersChangedEvent}, -// View, -// }; -// use indoc::indoc; -// use language::language_settings::InlayHintSettings; -// use lsp::request::{GotoDefinition, GotoTypeDefinition}; -// use util::assert_set_eq; - -// #[gpui::test] -// async fn test_link_go_to_type_definition(cx: &mut gpui::TestAppContext) { -// init_test(cx, |_| {}); - -// let mut cx = EditorLspTestContext::new_rust( -// lsp::ServerCapabilities { -// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), -// type_definition_provider: Some(lsp::TypeDefinitionProviderCapability::Simple(true)), -// ..Default::default() -// }, -// cx, -// ) -// .await; - -// cx.set_state(indoc! {" -// struct A; -// let vˇariable = A; -// "}); - -// // Basic hold cmd+shift, expect highlight in region if response contains type definition -// let hover_point = cx.display_point(indoc! {" -// struct A; -// let vˇariable = A; -// "}); -// let symbol_range = cx.lsp_range(indoc! {" -// struct A; -// let «variable» = A; -// "}); -// let target_range = cx.lsp_range(indoc! {" -// struct «A»; -// let variable = A; -// "}); - -// let mut requests = -// cx.handle_request::(move |url, _, _| async move { -// Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![ -// lsp::LocationLink { -// origin_selection_range: Some(symbol_range), -// target_uri: url.clone(), -// target_range, -// target_selection_range: target_range, -// }, -// ]))) -// }); - -// // Press cmd+shift to trigger highlight -// cx.update_editor(|editor, cx| { -// update_go_to_definition_link( -// editor, -// Some(GoToDefinitionTrigger::Text(hover_point)), -// true, -// true, -// cx, -// ); -// }); -// requests.next().await; -// cx.foreground().run_until_parked(); -// cx.assert_editor_text_highlights::(indoc! {" -// struct A; -// let «variable» = A; -// "}); - -// // Unpress shift causes highlight to go away (normal goto-definition is not valid here) -// cx.update_editor(|editor, cx| { -// editor.modifiers_changed( -// &platform::ModifiersChangedEvent { -// modifiers: Modifiers { -// cmd: true, -// ..Default::default() -// }, -// ..Default::default() -// }, -// cx, -// ); -// }); -// // Assert no link highlights -// cx.assert_editor_text_highlights::(indoc! {" -// struct A; -// let variable = A; -// "}); - -// // Cmd+shift click without existing definition requests and jumps -// let hover_point = cx.display_point(indoc! {" -// struct A; -// let vˇariable = A; -// "}); -// let target_range = cx.lsp_range(indoc! {" -// struct «A»; -// let variable = A; -// "}); - -// let mut requests = -// cx.handle_request::(move |url, _, _| async move { -// Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![ -// lsp::LocationLink { -// origin_selection_range: None, -// target_uri: url, -// target_range, -// target_selection_range: target_range, -// }, -// ]))) -// }); - -// cx.update_editor(|editor, cx| { -// go_to_fetched_type_definition(editor, PointForPosition::valid(hover_point), false, cx); -// }); -// requests.next().await; -// cx.foreground().run_until_parked(); - -// cx.assert_editor_state(indoc! {" -// struct «Aˇ»; -// let variable = A; -// "}); -// } - -// #[gpui::test] -// async fn test_link_go_to_definition(cx: &mut gpui::TestAppContext) { -// init_test(cx, |_| {}); - -// let mut cx = EditorLspTestContext::new_rust( -// lsp::ServerCapabilities { -// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), -// ..Default::default() -// }, -// cx, -// ) -// .await; - -// cx.set_state(indoc! {" -// fn ˇtest() { do_work(); } -// fn do_work() { test(); } -// "}); - -// // Basic hold cmd, expect highlight in region if response contains definition -// let hover_point = cx.display_point(indoc! {" -// fn test() { do_wˇork(); } -// fn do_work() { test(); } -// "}); -// let symbol_range = cx.lsp_range(indoc! {" -// fn test() { «do_work»(); } -// fn do_work() { test(); } -// "}); -// let target_range = cx.lsp_range(indoc! {" -// fn test() { do_work(); } -// fn «do_work»() { test(); } -// "}); - -// let mut requests = cx.handle_request::(move |url, _, _| async move { -// Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ -// lsp::LocationLink { -// origin_selection_range: Some(symbol_range), -// target_uri: url.clone(), -// target_range, -// target_selection_range: target_range, -// }, -// ]))) -// }); - -// cx.update_editor(|editor, cx| { -// update_go_to_definition_link( -// editor, -// Some(GoToDefinitionTrigger::Text(hover_point)), -// true, -// false, -// cx, -// ); -// }); -// requests.next().await; -// cx.foreground().run_until_parked(); -// cx.assert_editor_text_highlights::(indoc! {" -// fn test() { «do_work»(); } -// fn do_work() { test(); } -// "}); - -// // Unpress cmd causes highlight to go away -// cx.update_editor(|editor, cx| { -// editor.modifiers_changed(&Default::default(), cx); -// }); - -// // Assert no link highlights -// cx.assert_editor_text_highlights::(indoc! {" -// fn test() { do_work(); } -// fn do_work() { test(); } -// "}); - -// // Response without source range still highlights word -// cx.update_editor(|editor, _| editor.link_go_to_definition_state.last_trigger_point = None); -// let mut requests = cx.handle_request::(move |url, _, _| async move { -// Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ -// lsp::LocationLink { -// // No origin range -// origin_selection_range: None, -// target_uri: url.clone(), -// target_range, -// target_selection_range: target_range, -// }, -// ]))) -// }); -// cx.update_editor(|editor, cx| { -// update_go_to_definition_link( -// editor, -// Some(GoToDefinitionTrigger::Text(hover_point)), -// true, -// false, -// cx, -// ); -// }); -// requests.next().await; -// cx.foreground().run_until_parked(); - -// cx.assert_editor_text_highlights::(indoc! {" -// fn test() { «do_work»(); } -// fn do_work() { test(); } -// "}); - -// // Moving mouse to location with no response dismisses highlight -// let hover_point = cx.display_point(indoc! {" -// fˇn test() { do_work(); } -// fn do_work() { test(); } -// "}); -// let mut requests = cx -// .lsp -// .handle_request::(move |_, _| async move { -// // No definitions returned -// Ok(Some(lsp::GotoDefinitionResponse::Link(vec![]))) -// }); -// cx.update_editor(|editor, cx| { -// update_go_to_definition_link( -// editor, -// Some(GoToDefinitionTrigger::Text(hover_point)), -// true, -// false, -// cx, -// ); -// }); -// requests.next().await; -// cx.foreground().run_until_parked(); - -// // Assert no link highlights -// cx.assert_editor_text_highlights::(indoc! {" -// fn test() { do_work(); } -// fn do_work() { test(); } -// "}); - -// // Move mouse without cmd and then pressing cmd triggers highlight -// let hover_point = cx.display_point(indoc! {" -// fn test() { do_work(); } -// fn do_work() { teˇst(); } -// "}); -// cx.update_editor(|editor, cx| { -// update_go_to_definition_link( -// editor, -// Some(GoToDefinitionTrigger::Text(hover_point)), -// false, -// false, -// cx, -// ); -// }); -// cx.foreground().run_until_parked(); - -// // Assert no link highlights -// cx.assert_editor_text_highlights::(indoc! {" -// fn test() { do_work(); } -// fn do_work() { test(); } -// "}); - -// let symbol_range = cx.lsp_range(indoc! {" -// fn test() { do_work(); } -// fn do_work() { «test»(); } -// "}); -// let target_range = cx.lsp_range(indoc! {" -// fn «test»() { do_work(); } -// fn do_work() { test(); } -// "}); - -// let mut requests = cx.handle_request::(move |url, _, _| async move { -// Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ -// lsp::LocationLink { -// origin_selection_range: Some(symbol_range), -// target_uri: url, -// target_range, -// target_selection_range: target_range, -// }, -// ]))) -// }); -// cx.update_editor(|editor, cx| { -// editor.modifiers_changed( -// &ModifiersChangedEvent { -// modifiers: Modifiers { -// cmd: true, -// ..Default::default() -// }, -// }, -// cx, -// ); -// }); -// requests.next().await; -// cx.foreground().run_until_parked(); - -// cx.assert_editor_text_highlights::(indoc! {" -// fn test() { do_work(); } -// fn do_work() { «test»(); } -// "}); - -// // Deactivating the window dismisses the highlight -// cx.update_workspace(|workspace, cx| { -// workspace.on_window_activation_changed(false, cx); -// }); -// cx.assert_editor_text_highlights::(indoc! {" -// fn test() { do_work(); } -// fn do_work() { test(); } -// "}); - -// // Moving the mouse restores the highlights. -// cx.update_editor(|editor, cx| { -// update_go_to_definition_link( -// editor, -// Some(GoToDefinitionTrigger::Text(hover_point)), -// true, -// false, -// cx, -// ); -// }); -// cx.foreground().run_until_parked(); -// cx.assert_editor_text_highlights::(indoc! {" -// fn test() { do_work(); } -// fn do_work() { «test»(); } -// "}); - -// // Moving again within the same symbol range doesn't re-request -// let hover_point = cx.display_point(indoc! {" -// fn test() { do_work(); } -// fn do_work() { tesˇt(); } -// "}); -// cx.update_editor(|editor, cx| { -// update_go_to_definition_link( -// editor, -// Some(GoToDefinitionTrigger::Text(hover_point)), -// true, -// false, -// cx, -// ); -// }); -// cx.foreground().run_until_parked(); -// cx.assert_editor_text_highlights::(indoc! {" -// fn test() { do_work(); } -// fn do_work() { «test»(); } -// "}); - -// // Cmd click with existing definition doesn't re-request and dismisses highlight -// cx.update_editor(|editor, cx| { -// go_to_fetched_definition(editor, PointForPosition::valid(hover_point), false, cx); -// }); -// // Assert selection moved to to definition -// cx.lsp -// .handle_request::(move |_, _| async move { -// // Empty definition response to make sure we aren't hitting the lsp and using -// // the cached location instead -// Ok(Some(lsp::GotoDefinitionResponse::Link(vec![]))) -// }); -// cx.foreground().run_until_parked(); -// cx.assert_editor_state(indoc! {" -// fn «testˇ»() { do_work(); } -// fn do_work() { test(); } -// "}); - -// // Assert no link highlights after jump -// cx.assert_editor_text_highlights::(indoc! {" -// fn test() { do_work(); } -// fn do_work() { test(); } -// "}); - -// // Cmd click without existing definition requests and jumps -// let hover_point = cx.display_point(indoc! {" -// fn test() { do_wˇork(); } -// fn do_work() { test(); } -// "}); -// let target_range = cx.lsp_range(indoc! {" -// fn test() { do_work(); } -// fn «do_work»() { test(); } -// "}); - -// let mut requests = cx.handle_request::(move |url, _, _| async move { -// Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ -// lsp::LocationLink { -// origin_selection_range: None, -// target_uri: url, -// target_range, -// target_selection_range: target_range, -// }, -// ]))) -// }); -// cx.update_editor(|editor, cx| { -// go_to_fetched_definition(editor, PointForPosition::valid(hover_point), false, cx); -// }); -// requests.next().await; -// cx.foreground().run_until_parked(); -// cx.assert_editor_state(indoc! {" -// fn test() { do_work(); } -// fn «do_workˇ»() { test(); } -// "}); - -// // 1. We have a pending selection, mouse point is over a symbol that we have a response for, hitting cmd and nothing happens -// // 2. Selection is completed, hovering -// let hover_point = cx.display_point(indoc! {" -// fn test() { do_wˇork(); } -// fn do_work() { test(); } -// "}); -// let target_range = cx.lsp_range(indoc! {" -// fn test() { do_work(); } -// fn «do_work»() { test(); } -// "}); -// let mut requests = cx.handle_request::(move |url, _, _| async move { -// Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ -// lsp::LocationLink { -// origin_selection_range: None, -// target_uri: url, -// target_range, -// target_selection_range: target_range, -// }, -// ]))) -// }); - -// // create a pending selection -// let selection_range = cx.ranges(indoc! {" -// fn «test() { do_w»ork(); } -// fn do_work() { test(); } -// "})[0] -// .clone(); -// cx.update_editor(|editor, cx| { -// let snapshot = editor.buffer().read(cx).snapshot(cx); -// let anchor_range = snapshot.anchor_before(selection_range.start) -// ..snapshot.anchor_after(selection_range.end); -// editor.change_selections(Some(crate::Autoscroll::fit()), cx, |s| { -// s.set_pending_anchor_range(anchor_range, crate::SelectMode::Character) -// }); -// }); -// cx.update_editor(|editor, cx| { -// update_go_to_definition_link( -// editor, -// Some(GoToDefinitionTrigger::Text(hover_point)), -// true, -// false, -// cx, -// ); -// }); -// cx.foreground().run_until_parked(); -// assert!(requests.try_next().is_err()); -// cx.assert_editor_text_highlights::(indoc! {" -// fn test() { do_work(); } -// fn do_work() { test(); } -// "}); -// cx.foreground().run_until_parked(); -// } - -// #[gpui::test] -// async fn test_link_go_to_inlay(cx: &mut gpui::TestAppContext) { -// init_test(cx, |settings| { -// settings.defaults.inlay_hints = Some(InlayHintSettings { -// enabled: true, -// show_type_hints: true, -// show_parameter_hints: true, -// show_other_hints: true, -// }) -// }); - -// let mut cx = EditorLspTestContext::new_rust( -// lsp::ServerCapabilities { -// inlay_hint_provider: Some(lsp::OneOf::Left(true)), -// ..Default::default() -// }, -// cx, -// ) -// .await; -// cx.set_state(indoc! {" -// struct TestStruct; - -// fn main() { -// let variableˇ = TestStruct; -// } -// "}); -// let hint_start_offset = cx.ranges(indoc! {" -// struct TestStruct; - -// fn main() { -// let variableˇ = TestStruct; -// } -// "})[0] -// .start; -// let hint_position = cx.to_lsp(hint_start_offset); -// let target_range = cx.lsp_range(indoc! {" -// struct «TestStruct»; - -// fn main() { -// let variable = TestStruct; -// } -// "}); - -// let expected_uri = cx.buffer_lsp_url.clone(); -// let hint_label = ": TestStruct"; -// cx.lsp -// .handle_request::(move |params, _| { -// let expected_uri = expected_uri.clone(); -// async move { -// assert_eq!(params.text_document.uri, expected_uri); -// Ok(Some(vec![lsp::InlayHint { -// position: hint_position, -// label: lsp::InlayHintLabel::LabelParts(vec![lsp::InlayHintLabelPart { -// value: hint_label.to_string(), -// location: Some(lsp::Location { -// uri: params.text_document.uri, -// range: target_range, -// }), -// ..Default::default() -// }]), -// kind: Some(lsp::InlayHintKind::TYPE), -// text_edits: None, -// tooltip: None, -// padding_left: Some(false), -// padding_right: Some(false), -// data: None, -// }])) -// } -// }) -// .next() -// .await; -// cx.foreground().run_until_parked(); -// cx.update_editor(|editor, cx| { -// let expected_layers = vec![hint_label.to_string()]; -// assert_eq!(expected_layers, cached_hint_labels(editor)); -// assert_eq!(expected_layers, visible_hint_labels(editor, cx)); -// }); - -// let inlay_range = cx -// .ranges(indoc! {" -// struct TestStruct; - -// fn main() { -// let variable« »= TestStruct; -// } -// "}) -// .get(0) -// .cloned() -// .unwrap(); -// let hint_hover_position = cx.update_editor(|editor, cx| { -// let snapshot = editor.snapshot(cx); -// let previous_valid = inlay_range.start.to_display_point(&snapshot); -// let next_valid = inlay_range.end.to_display_point(&snapshot); -// assert_eq!(previous_valid.row(), next_valid.row()); -// assert!(previous_valid.column() < next_valid.column()); -// let exact_unclipped = DisplayPoint::new( -// previous_valid.row(), -// previous_valid.column() + (hint_label.len() / 2) as u32, -// ); -// PointForPosition { -// previous_valid, -// next_valid, -// exact_unclipped, -// column_overshoot_after_line_end: 0, -// } -// }); -// // Press cmd to trigger highlight -// cx.update_editor(|editor, cx| { -// update_inlay_link_and_hover_points( -// &editor.snapshot(cx), -// hint_hover_position, -// editor, -// true, -// false, -// cx, -// ); -// }); -// cx.foreground().run_until_parked(); -// cx.update_editor(|editor, cx| { -// let snapshot = editor.snapshot(cx); -// let actual_highlights = snapshot -// .inlay_highlights::() -// .into_iter() -// .flat_map(|highlights| highlights.values().map(|(_, highlight)| highlight)) -// .collect::>(); - -// let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); -// let expected_highlight = InlayHighlight { -// inlay: InlayId::Hint(0), -// inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), -// range: 0..hint_label.len(), -// }; -// assert_set_eq!(actual_highlights, vec![&expected_highlight]); -// }); - -// // Unpress cmd causes highlight to go away -// cx.update_editor(|editor, cx| { -// editor.modifiers_changed( -// &platform::ModifiersChangedEvent { -// modifiers: Modifiers { -// cmd: false, -// ..Default::default() -// }, -// ..Default::default() -// }, -// cx, -// ); -// }); -// // Assert no link highlights -// cx.update_editor(|editor, cx| { -// let snapshot = editor.snapshot(cx); -// let actual_ranges = snapshot -// .text_highlight_ranges::() -// .map(|ranges| ranges.as_ref().clone().1) -// .unwrap_or_default(); - -// assert!(actual_ranges.is_empty(), "When no cmd is pressed, should have no hint label selected, but got: {actual_ranges:?}"); -// }); - -// // Cmd+click without existing definition requests and jumps -// cx.update_editor(|editor, cx| { -// editor.modifiers_changed( -// &platform::ModifiersChangedEvent { -// modifiers: Modifiers { -// cmd: true, -// ..Default::default() -// }, -// ..Default::default() -// }, -// cx, -// ); -// update_inlay_link_and_hover_points( -// &editor.snapshot(cx), -// hint_hover_position, -// editor, -// true, -// false, -// cx, -// ); -// }); -// cx.foreground().run_until_parked(); -// cx.update_editor(|editor, cx| { -// go_to_fetched_type_definition(editor, hint_hover_position, false, cx); -// }); -// cx.foreground().run_until_parked(); -// cx.assert_editor_state(indoc! {" -// struct «TestStructˇ»; - -// fn main() { -// let variable = TestStruct; -// } -// "}); -// } -// } +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + display_map::ToDisplayPoint, + editor_tests::init_test, + inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels}, + test::editor_lsp_test_context::EditorLspTestContext, + }; + use futures::StreamExt; + use gpui::{Modifiers, ModifiersChangedEvent, View}; + use indoc::indoc; + use language::language_settings::InlayHintSettings; + use lsp::request::{GotoDefinition, GotoTypeDefinition}; + use util::assert_set_eq; + + #[gpui::test] + async fn test_link_go_to_type_definition(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), + type_definition_provider: Some(lsp::TypeDefinitionProviderCapability::Simple(true)), + ..Default::default() + }, + cx, + ) + .await; + + cx.set_state(indoc! {" + struct A; + let vˇariable = A; + "}); + + // Basic hold cmd+shift, expect highlight in region if response contains type definition + let hover_point = cx.display_point(indoc! {" + struct A; + let vˇariable = A; + "}); + let symbol_range = cx.lsp_range(indoc! {" + struct A; + let «variable» = A; + "}); + let target_range = cx.lsp_range(indoc! {" + struct «A»; + let variable = A; + "}); + + let mut requests = + cx.handle_request::(move |url, _, _| async move { + Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![ + lsp::LocationLink { + origin_selection_range: Some(symbol_range), + target_uri: url.clone(), + target_range, + target_selection_range: target_range, + }, + ]))) + }); + + // Press cmd+shift to trigger highlight + cx.update_editor(|editor, cx| { + update_go_to_definition_link( + editor, + Some(GoToDefinitionTrigger::Text(hover_point)), + true, + true, + cx, + ); + }); + requests.next().await; + cx.background_executor.run_until_parked(); + cx.assert_editor_text_highlights::(indoc! {" + struct A; + let «variable» = A; + "}); + + // Unpress shift causes highlight to go away (normal goto-definition is not valid here) + cx.update_editor(|editor, cx| { + crate::element::EditorElement::modifiers_changed( + editor, + &ModifiersChangedEvent { + modifiers: Modifiers { + command: true, + ..Default::default() + }, + ..Default::default() + }, + cx, + ); + }); + // Assert no link highlights + cx.assert_editor_text_highlights::(indoc! {" + struct A; + let variable = A; + "}); + + // Cmd+shift click without existing definition requests and jumps + let hover_point = cx.display_point(indoc! {" + struct A; + let vˇariable = A; + "}); + let target_range = cx.lsp_range(indoc! {" + struct «A»; + let variable = A; + "}); + + let mut requests = + cx.handle_request::(move |url, _, _| async move { + Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![ + lsp::LocationLink { + origin_selection_range: None, + target_uri: url, + target_range, + target_selection_range: target_range, + }, + ]))) + }); + + cx.update_editor(|editor, cx| { + go_to_fetched_type_definition(editor, PointForPosition::valid(hover_point), false, cx); + }); + requests.next().await; + cx.background_executor.run_until_parked(); + + cx.assert_editor_state(indoc! {" + struct «Aˇ»; + let variable = A; + "}); + } + + #[gpui::test] + async fn test_link_go_to_definition(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), + ..Default::default() + }, + cx, + ) + .await; + + cx.set_state(indoc! {" + fn ˇtest() { do_work(); } + fn do_work() { test(); } + "}); + + // Basic hold cmd, expect highlight in region if response contains definition + let hover_point = cx.display_point(indoc! {" + fn test() { do_wˇork(); } + fn do_work() { test(); } + "}); + let symbol_range = cx.lsp_range(indoc! {" + fn test() { «do_work»(); } + fn do_work() { test(); } + "}); + let target_range = cx.lsp_range(indoc! {" + fn test() { do_work(); } + fn «do_work»() { test(); } + "}); + + let mut requests = cx.handle_request::(move |url, _, _| async move { + Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ + lsp::LocationLink { + origin_selection_range: Some(symbol_range), + target_uri: url.clone(), + target_range, + target_selection_range: target_range, + }, + ]))) + }); + + cx.update_editor(|editor, cx| { + update_go_to_definition_link( + editor, + Some(GoToDefinitionTrigger::Text(hover_point)), + true, + false, + cx, + ); + }); + requests.next().await; + cx.background_executor.run_until_parked(); + cx.assert_editor_text_highlights::(indoc! {" + fn test() { «do_work»(); } + fn do_work() { test(); } + "}); + + // Unpress cmd causes highlight to go away + cx.update_editor(|editor, cx| { + crate::element::EditorElement::modifiers_changed(editor, &Default::default(), cx); + }); + + // Assert no link highlights + cx.assert_editor_text_highlights::(indoc! {" + fn test() { do_work(); } + fn do_work() { test(); } + "}); + + // Response without source range still highlights word + cx.update_editor(|editor, _| editor.link_go_to_definition_state.last_trigger_point = None); + let mut requests = cx.handle_request::(move |url, _, _| async move { + Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ + lsp::LocationLink { + // No origin range + origin_selection_range: None, + target_uri: url.clone(), + target_range, + target_selection_range: target_range, + }, + ]))) + }); + cx.update_editor(|editor, cx| { + update_go_to_definition_link( + editor, + Some(GoToDefinitionTrigger::Text(hover_point)), + true, + false, + cx, + ); + }); + requests.next().await; + cx.background_executor.run_until_parked(); + + cx.assert_editor_text_highlights::(indoc! {" + fn test() { «do_work»(); } + fn do_work() { test(); } + "}); + + // Moving mouse to location with no response dismisses highlight + let hover_point = cx.display_point(indoc! {" + fˇn test() { do_work(); } + fn do_work() { test(); } + "}); + let mut requests = cx + .lsp + .handle_request::(move |_, _| async move { + // No definitions returned + Ok(Some(lsp::GotoDefinitionResponse::Link(vec![]))) + }); + cx.update_editor(|editor, cx| { + update_go_to_definition_link( + editor, + Some(GoToDefinitionTrigger::Text(hover_point)), + true, + false, + cx, + ); + }); + requests.next().await; + cx.background_executor.run_until_parked(); + + // Assert no link highlights + cx.assert_editor_text_highlights::(indoc! {" + fn test() { do_work(); } + fn do_work() { test(); } + "}); + + // Move mouse without cmd and then pressing cmd triggers highlight + let hover_point = cx.display_point(indoc! {" + fn test() { do_work(); } + fn do_work() { teˇst(); } + "}); + cx.update_editor(|editor, cx| { + update_go_to_definition_link( + editor, + Some(GoToDefinitionTrigger::Text(hover_point)), + false, + false, + cx, + ); + }); + cx.background_executor.run_until_parked(); + + // Assert no link highlights + cx.assert_editor_text_highlights::(indoc! {" + fn test() { do_work(); } + fn do_work() { test(); } + "}); + + let symbol_range = cx.lsp_range(indoc! {" + fn test() { do_work(); } + fn do_work() { «test»(); } + "}); + let target_range = cx.lsp_range(indoc! {" + fn «test»() { do_work(); } + fn do_work() { test(); } + "}); + + let mut requests = cx.handle_request::(move |url, _, _| async move { + Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ + lsp::LocationLink { + origin_selection_range: Some(symbol_range), + target_uri: url, + target_range, + target_selection_range: target_range, + }, + ]))) + }); + cx.update_editor(|editor, cx| { + crate::element::EditorElement::modifiers_changed( + editor, + &ModifiersChangedEvent { + modifiers: Modifiers { + command: true, + ..Default::default() + }, + }, + cx, + ); + }); + requests.next().await; + cx.background_executor.run_until_parked(); + + cx.assert_editor_text_highlights::(indoc! {" + fn test() { do_work(); } + fn do_work() { «test»(); } + "}); + + // Deactivating the window dismisses the highlight + cx.update_workspace(|workspace, cx| { + workspace.on_window_activation_changed(cx); + }); + cx.assert_editor_text_highlights::(indoc! {" + fn test() { do_work(); } + fn do_work() { test(); } + "}); + + // Moving the mouse restores the highlights. + cx.update_editor(|editor, cx| { + update_go_to_definition_link( + editor, + Some(GoToDefinitionTrigger::Text(hover_point)), + true, + false, + cx, + ); + }); + cx.background_executor.run_until_parked(); + cx.assert_editor_text_highlights::(indoc! {" + fn test() { do_work(); } + fn do_work() { «test»(); } + "}); + + // Moving again within the same symbol range doesn't re-request + let hover_point = cx.display_point(indoc! {" + fn test() { do_work(); } + fn do_work() { tesˇt(); } + "}); + cx.update_editor(|editor, cx| { + update_go_to_definition_link( + editor, + Some(GoToDefinitionTrigger::Text(hover_point)), + true, + false, + cx, + ); + }); + cx.background_executor.run_until_parked(); + cx.assert_editor_text_highlights::(indoc! {" + fn test() { do_work(); } + fn do_work() { «test»(); } + "}); + + // Cmd click with existing definition doesn't re-request and dismisses highlight + cx.update_editor(|editor, cx| { + go_to_fetched_definition(editor, PointForPosition::valid(hover_point), false, cx); + }); + // Assert selection moved to to definition + cx.lsp + .handle_request::(move |_, _| async move { + // Empty definition response to make sure we aren't hitting the lsp and using + // the cached location instead + Ok(Some(lsp::GotoDefinitionResponse::Link(vec![]))) + }); + cx.background_executor.run_until_parked(); + cx.assert_editor_state(indoc! {" + fn «testˇ»() { do_work(); } + fn do_work() { test(); } + "}); + + // Assert no link highlights after jump + cx.assert_editor_text_highlights::(indoc! {" + fn test() { do_work(); } + fn do_work() { test(); } + "}); + + // Cmd click without existing definition requests and jumps + let hover_point = cx.display_point(indoc! {" + fn test() { do_wˇork(); } + fn do_work() { test(); } + "}); + let target_range = cx.lsp_range(indoc! {" + fn test() { do_work(); } + fn «do_work»() { test(); } + "}); + + let mut requests = cx.handle_request::(move |url, _, _| async move { + Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ + lsp::LocationLink { + origin_selection_range: None, + target_uri: url, + target_range, + target_selection_range: target_range, + }, + ]))) + }); + cx.update_editor(|editor, cx| { + go_to_fetched_definition(editor, PointForPosition::valid(hover_point), false, cx); + }); + requests.next().await; + cx.background_executor.run_until_parked(); + cx.assert_editor_state(indoc! {" + fn test() { do_work(); } + fn «do_workˇ»() { test(); } + "}); + + // 1. We have a pending selection, mouse point is over a symbol that we have a response for, hitting cmd and nothing happens + // 2. Selection is completed, hovering + let hover_point = cx.display_point(indoc! {" + fn test() { do_wˇork(); } + fn do_work() { test(); } + "}); + let target_range = cx.lsp_range(indoc! {" + fn test() { do_work(); } + fn «do_work»() { test(); } + "}); + let mut requests = cx.handle_request::(move |url, _, _| async move { + Ok(Some(lsp::GotoDefinitionResponse::Link(vec![ + lsp::LocationLink { + origin_selection_range: None, + target_uri: url, + target_range, + target_selection_range: target_range, + }, + ]))) + }); + + // create a pending selection + let selection_range = cx.ranges(indoc! {" + fn «test() { do_w»ork(); } + fn do_work() { test(); } + "})[0] + .clone(); + cx.update_editor(|editor, cx| { + let snapshot = editor.buffer().read(cx).snapshot(cx); + let anchor_range = snapshot.anchor_before(selection_range.start) + ..snapshot.anchor_after(selection_range.end); + editor.change_selections(Some(crate::Autoscroll::fit()), cx, |s| { + s.set_pending_anchor_range(anchor_range, crate::SelectMode::Character) + }); + }); + cx.update_editor(|editor, cx| { + update_go_to_definition_link( + editor, + Some(GoToDefinitionTrigger::Text(hover_point)), + true, + false, + cx, + ); + }); + cx.background_executor.run_until_parked(); + assert!(requests.try_next().is_err()); + cx.assert_editor_text_highlights::(indoc! {" + fn test() { do_work(); } + fn do_work() { test(); } + "}); + cx.background_executor.run_until_parked(); + } + + #[gpui::test] + async fn test_link_go_to_inlay(cx: &mut gpui::TestAppContext) { + init_test(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: true, + show_parameter_hints: true, + show_other_hints: true, + }) + }); + + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + inlay_hint_provider: Some(lsp::OneOf::Left(true)), + ..Default::default() + }, + cx, + ) + .await; + cx.set_state(indoc! {" + struct TestStruct; + + fn main() { + let variableˇ = TestStruct; + } + "}); + let hint_start_offset = cx.ranges(indoc! {" + struct TestStruct; + + fn main() { + let variableˇ = TestStruct; + } + "})[0] + .start; + let hint_position = cx.to_lsp(hint_start_offset); + let target_range = cx.lsp_range(indoc! {" + struct «TestStruct»; + + fn main() { + let variable = TestStruct; + } + "}); + + let expected_uri = cx.buffer_lsp_url.clone(); + let hint_label = ": TestStruct"; + cx.lsp + .handle_request::(move |params, _| { + let expected_uri = expected_uri.clone(); + async move { + assert_eq!(params.text_document.uri, expected_uri); + Ok(Some(vec![lsp::InlayHint { + position: hint_position, + label: lsp::InlayHintLabel::LabelParts(vec![lsp::InlayHintLabelPart { + value: hint_label.to_string(), + location: Some(lsp::Location { + uri: params.text_document.uri, + range: target_range, + }), + ..Default::default() + }]), + kind: Some(lsp::InlayHintKind::TYPE), + text_edits: None, + tooltip: None, + padding_left: Some(false), + padding_right: Some(false), + data: None, + }])) + } + }) + .next() + .await; + cx.background_executor.run_until_parked(); + cx.update_editor(|editor, cx| { + let expected_layers = vec![hint_label.to_string()]; + assert_eq!(expected_layers, cached_hint_labels(editor)); + assert_eq!(expected_layers, visible_hint_labels(editor, cx)); + }); + + let inlay_range = cx + .ranges(indoc! {" + struct TestStruct; + + fn main() { + let variable« »= TestStruct; + } + "}) + .get(0) + .cloned() + .unwrap(); + let hint_hover_position = cx.update_editor(|editor, cx| { + let snapshot = editor.snapshot(cx); + let previous_valid = inlay_range.start.to_display_point(&snapshot); + let next_valid = inlay_range.end.to_display_point(&snapshot); + assert_eq!(previous_valid.row(), next_valid.row()); + assert!(previous_valid.column() < next_valid.column()); + let exact_unclipped = DisplayPoint::new( + previous_valid.row(), + previous_valid.column() + (hint_label.len() / 2) as u32, + ); + PointForPosition { + previous_valid, + next_valid, + exact_unclipped, + column_overshoot_after_line_end: 0, + } + }); + // Press cmd to trigger highlight + cx.update_editor(|editor, cx| { + update_inlay_link_and_hover_points( + &editor.snapshot(cx), + hint_hover_position, + editor, + true, + false, + cx, + ); + }); + cx.background_executor.run_until_parked(); + cx.update_editor(|editor, cx| { + let snapshot = editor.snapshot(cx); + let actual_highlights = snapshot + .inlay_highlights::() + .into_iter() + .flat_map(|highlights| highlights.values().map(|(_, highlight)| highlight)) + .collect::>(); + + let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); + let expected_highlight = InlayHighlight { + inlay: InlayId::Hint(0), + inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), + range: 0..hint_label.len(), + }; + assert_set_eq!(actual_highlights, vec![&expected_highlight]); + }); + + // Unpress cmd causes highlight to go away + cx.update_editor(|editor, cx| { + crate::element::EditorElement::modifiers_changed( + editor, + &ModifiersChangedEvent { + modifiers: Modifiers { + command: false, + ..Default::default() + }, + ..Default::default() + }, + cx, + ); + }); + // Assert no link highlights + cx.update_editor(|editor, cx| { + let snapshot = editor.snapshot(cx); + let actual_ranges = snapshot + .text_highlight_ranges::() + .map(|ranges| ranges.as_ref().clone().1) + .unwrap_or_default(); + + assert!(actual_ranges.is_empty(), "When no cmd is pressed, should have no hint label selected, but got: {actual_ranges:?}"); + }); + + // Cmd+click without existing definition requests and jumps + cx.update_editor(|editor, cx| { + crate::element::EditorElement::modifiers_changed( + editor, + &ModifiersChangedEvent { + modifiers: Modifiers { + command: true, + ..Default::default() + }, + ..Default::default() + }, + cx, + ); + update_inlay_link_and_hover_points( + &editor.snapshot(cx), + hint_hover_position, + editor, + true, + false, + cx, + ); + }); + cx.background_executor.run_until_parked(); + cx.update_editor(|editor, cx| { + go_to_fetched_type_definition(editor, hint_hover_position, false, cx); + }); + cx.background_executor.run_until_parked(); + cx.assert_editor_state(indoc! {" + struct «TestStructˇ»; + + fn main() { + let variable = TestStruct; + } + "}); + } +}