@@ -31506,6 +31506,96 @@ async fn test_inlay_hints_request_timeout(cx: &mut TestAppContext) {
.unwrap();
}
+#[gpui::test]
+async fn test_click_on_parameter_inlay_hint_places_cursor_correctly(cx: &mut TestAppContext) {
+ use crate::inlays::inlay_hints::tests::{cached_hint_labels, visible_hint_labels};
+
+ let mut cx = EditorLspTestContext::new_rust(
+ lsp::ServerCapabilities {
+ inlay_hint_provider: Some(lsp::OneOf::Left(true)),
+ ..Default::default()
+ },
+ cx,
+ )
+ .await;
+
+ cx.update(|_, cx| {
+ SettingsStore::update_global(cx, |store, cx| {
+ store.update_user_settings(cx, &|settings: &mut SettingsContent| {
+ settings.project.all_languages.defaults.inlay_hints =
+ Some(InlayHintSettingsContent {
+ enabled: Some(true),
+ show_parameter_hints: Some(true),
+ show_type_hints: Some(true),
+ edit_debounce_ms: Some(0),
+ scroll_debounce_ms: Some(0),
+ ..Default::default()
+ })
+ });
+ });
+ });
+
+ cx.set_state("fn foo(value: i32) {} fn main() { foo(ˇ42); }");
+
+ // Buffer: `fn foo(value: i32) {} fn main() { foo(42); }`
+ // The parameter hint "value:" appears before "42"
+ let hint_start_offset = cx.ranges("fn foo(value: i32) {} fn main() { foo(ˇ42); }")[0].start;
+ let hint_position = cx.to_lsp(MultiBufferOffset(hint_start_offset));
+ let hint_label = "value:";
+ let expected_uri = cx.buffer_lsp_url.clone();
+ cx.lsp
+ .set_request_handler::<lsp::request::InlayHintRequest, _, _>(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::String(hint_label.to_string()),
+ kind: Some(lsp::InlayHintKind::PARAMETER),
+ text_edits: None,
+ tooltip: None,
+ padding_left: None,
+ padding_right: Some(true),
+ data: None,
+ }]))
+ }
+ })
+ .next()
+ .await;
+ cx.background_executor.run_until_parked();
+
+ cx.update_editor(|editor, _window, cx| {
+ let expected_labels = vec!["value: ".to_string()];
+ assert_eq!(expected_labels, cached_hint_labels(editor, cx));
+ assert_eq!(expected_labels, visible_hint_labels(editor, cx));
+ });
+
+ // The cursor is at `4` in `42`. The parameter hint "value: " appears just
+ // before it in display space. We'll click a few characters to the left of
+ // the cursor position to land inside the inlay hint text.
+ let cursor_display_point = cx.update_editor(|editor, _window, cx| {
+ editor
+ .selections
+ .newest_display(&editor.display_snapshot(cx))
+ .head()
+ });
+ let cursor_pixel = cx.pixel_position_for(cursor_display_point);
+ let em_width =
+ cx.update_editor(|editor, _, _| editor.last_position_map.as_ref().unwrap().em_layout_width);
+ // Click 3 characters to the left of the cursor, which lands inside the
+ // "value: " inlay hint text.
+ let click_position = gpui::Point {
+ x: cursor_pixel.x - em_width * 3.0,
+ y: cursor_pixel.y,
+ };
+ cx.simulate_click(click_position, Modifiers::none());
+ cx.background_executor.run_until_parked();
+
+ // The cursor should be placed after the `(`, at the `4` in `42`,
+ // NOT before the `(`.
+ cx.assert_editor_state("fn foo(value: i32) {} fn main() { foo(ˇ42); }");
+}
+
#[gpui::test]
async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
init_test(cx, |_| {});
@@ -843,7 +843,7 @@ impl EditorElement {
}
}
- let position = point_for_position.previous_valid;
+ let position = point_for_position.nearest_valid;
if let Some(mode) = Editor::columnar_selection_mode(&modifiers, cx) {
editor.select(
SelectPhase::BeginColumnar {
@@ -898,7 +898,7 @@ impl EditorElement {
{
let point_for_position = position_map.point_for_position(event.position);
editor.set_gutter_context_menu(
- point_for_position.previous_valid.row(),
+ point_for_position.nearest_valid.row(),
None,
event.position,
window,
@@ -916,7 +916,7 @@ impl EditorElement {
mouse_context_menu::deploy_context_menu(
editor,
Some(event.position),
- point_for_position.previous_valid,
+ point_for_position.nearest_valid,
window,
cx,
);
@@ -935,7 +935,7 @@ impl EditorElement {
}
let point_for_position = position_map.point_for_position(event.position);
- let position = point_for_position.previous_valid;
+ let position = point_for_position.nearest_valid;
editor.select(
SelectPhase::BeginColumnar {
@@ -977,7 +977,7 @@ impl EditorElement {
if event.position == *click_position {
editor.select(
SelectPhase::Begin {
- position: point_for_position.previous_valid,
+ position: point_for_position.nearest_valid,
add: false,
click_count: 1, // ready to drag state only occurs on click count 1
},
@@ -1001,7 +1001,7 @@ impl EditorElement {
|| cfg!(not(target_os = "macos")) && event.modifiers.control);
editor.move_selection_on_drop(
&selection.clone(),
- point_for_position.previous_valid,
+ point_for_position.nearest_valid,
is_cut,
window,
cx,
@@ -1037,7 +1037,7 @@ impl EditorElement {
if EditorSettings::get_global(cx).middle_click_paste {
if let Some(text) = cx.read_from_primary().and_then(|item| item.text()) {
let point_for_position = position_map.point_for_position(event.position);
- let position = point_for_position.previous_valid;
+ let position = point_for_position.nearest_valid;
editor.select(
SelectPhase::Begin {
@@ -1166,7 +1166,7 @@ impl EditorElement {
if !editor.has_pending_selection() {
let drop_anchor = position_map
.snapshot
- .display_point_to_anchor(point_for_position.previous_valid, Bias::Left);
+ .display_point_to_anchor(point_for_position.nearest_valid, Bias::Left);
match editor.selection_drag_state {
SelectionDragState::Dragging {
ref mut drop_cursor,
@@ -1210,7 +1210,7 @@ impl EditorElement {
editor.selection_drag_state = SelectionDragState::None;
editor.select(
SelectPhase::Begin {
- position: click_point.previous_valid,
+ position: click_point.nearest_valid,
add: false,
click_count: 1,
},
@@ -1219,7 +1219,7 @@ impl EditorElement {
);
editor.select(
SelectPhase::Update {
- position: point_for_position.previous_valid,
+ position: point_for_position.nearest_valid,
goal_column: point_for_position.exact_unclipped.column(),
scroll_delta,
},
@@ -1233,7 +1233,7 @@ impl EditorElement {
} else {
editor.select(
SelectPhase::Update {
- position: point_for_position.previous_valid,
+ position: point_for_position.nearest_valid,
goal_column: point_for_position.exact_unclipped.column(),
scroll_delta,
},
@@ -1260,7 +1260,7 @@ impl EditorElement {
editor.show_mouse_cursor(cx);
let point_for_position = position_map.point_for_position(event.position);
- let valid_point = point_for_position.previous_valid;
+ let valid_point = point_for_position.nearest_valid;
// Update diff review drag state if we're dragging
if editor.diff_review_drag_state.is_some() {
@@ -6688,7 +6688,7 @@ impl EditorElement {
let snapshot = editor.snapshot(window, cx);
let anchor = snapshot
.display_snapshot
- .display_point_to_anchor(point_for_position.previous_valid, Bias::Left);
+ .display_point_to_anchor(point_for_position.nearest_valid, Bias::Left);
editor.change_selections(
SelectionEffects::scroll(Autoscroll::top_relative(line_index)),
window,
@@ -11902,6 +11902,7 @@ pub(crate) struct PositionMap {
pub struct PointForPosition {
pub previous_valid: DisplayPoint,
pub next_valid: DisplayPoint,
+ pub nearest_valid: DisplayPoint,
pub exact_unclipped: DisplayPoint,
pub column_overshoot_after_line_end: u32,
}
@@ -11971,12 +11972,23 @@ impl PositionMap {
let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left);
let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right);
+ let nearest_valid = if previous_valid == next_valid {
+ previous_valid
+ } else {
+ match self.snapshot.inlay_bias_at(exact_unclipped) {
+ Some(Bias::Left) => next_valid,
+ Some(Bias::Right) => previous_valid,
+ None => previous_valid,
+ }
+ };
+
let column_overshoot_after_line_end =
(x_overshoot_after_line_end / self.em_layout_width) as u32;
*exact_unclipped.column_mut() += column_overshoot_after_line_end;
PointForPosition {
previous_valid,
next_valid,
+ nearest_valid,
exact_unclipped,
column_overshoot_after_line_end,
}
@@ -12006,12 +12018,23 @@ impl PositionMap {
let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left);
let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right);
+ let nearest_valid = if previous_valid == next_valid {
+ previous_valid
+ } else {
+ match self.snapshot.inlay_bias_at(exact_unclipped) {
+ Some(Bias::Left) => next_valid,
+ Some(Bias::Right) => previous_valid,
+ None => previous_valid,
+ }
+ };
+
let column_overshoot_after_line_end =
(x_overshoot_after_line_end / self.em_layout_width) as u32;
*exact_unclipped.column_mut() += column_overshoot_after_line_end;
PointForPosition {
previous_valid,
next_valid,
+ nearest_valid,
exact_unclipped,
column_overshoot_after_line_end,
}