@@ -18219,6 +18219,20 @@ impl Editor {
for ranges in locations.values_mut() {
ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
ranges.dedup();
+ // Merge overlapping or contained ranges. After sorting by
+ // (start, Reverse(end)), we can merge in a single pass:
+ // if the next range starts before the current one ends,
+ // extend the current range's end if needed.
+ let mut i = 0;
+ while i + 1 < ranges.len() {
+ if ranges[i + 1].start <= ranges[i].end {
+ let merged_end = ranges[i].end.max(ranges[i + 1].end);
+ ranges[i].end = merged_end;
+ ranges.remove(i + 1);
+ } else {
+ i += 1;
+ }
+ }
let fits_in_one_excerpt = ranges
.iter()
.tuple_windows()
@@ -25403,6 +25403,55 @@ async fn test_goto_definition_far_ranges_open_multibuffer(cx: &mut TestAppContex
});
}
+#[gpui::test]
+async fn test_goto_definition_contained_ranges(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+ let mut cx = EditorLspTestContext::new_rust(
+ lsp::ServerCapabilities {
+ definition_provider: Some(lsp::OneOf::Left(true)),
+ ..lsp::ServerCapabilities::default()
+ },
+ cx,
+ )
+ .await;
+
+ // The LSP returns two single-line definitions on the same row where one
+ // range contains the other. Both are on the same line so the
+ // `fits_in_one_excerpt` check won't underflow, and the code reaches
+ // `change_selections`.
+ cx.set_state(
+ &r#"fn caller() {
+ let _ = หtarget();
+ }
+ fn target_outer() { fn target_inner() {} }
+ "#
+ .unindent(),
+ );
+
+ // Return two definitions on the same line: an outer range covering the
+ // whole line and an inner range for just the inner function name.
+ cx.set_request_handler::<lsp::request::GotoDefinition, _, _>(move |url, _, _| async move {
+ Ok(Some(lsp::GotoDefinitionResponse::Array(vec![
+ // Inner range: just "target_inner" (cols 23..35)
+ lsp::Location {
+ uri: url.clone(),
+ range: lsp::Range::new(lsp::Position::new(3, 23), lsp::Position::new(3, 35)),
+ },
+ // Outer range: the whole line (cols 0..48)
+ lsp::Location {
+ uri: url,
+ range: lsp::Range::new(lsp::Position::new(3, 0), lsp::Position::new(3, 48)),
+ },
+ ])))
+ });
+
+ let navigated = cx
+ .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
+ .await
+ .expect("Failed to navigate to definitions");
+ assert_eq!(navigated, Navigated::Yes);
+}
+
#[gpui::test]
async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
init_test(cx, |_| {});