Fix crash when LSP returns multiple nested definition ranges (#52701)

This fixes a crash when navigating to multiple definitions in a
singleton buffer (https://github.com/zed-industries/zed/pull/51461).

Release Notes:

- N/A
This commit is contained in:
Max Brunsfeld authored and GitHub committed 2026-03-30 09:34:29 +02:00
1 parent 326752f12a
commit 577c9909fa
2 files changed
+63

No files matched your search

+14
View File
@@ -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()
+49
View File
@@ -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, |_| {});