editor: Fix panic when editor::SelectLargerSyntaxNode overflows excerpt in multi buffer (#25585)

Closes #25513

This PR handles case when `editor::SelectLargerSyntaxNode` expands
across excerpt boundaries and eventually crashes in multi buffer.

Release Notes:

- Fixed panic caused when `editor::SelectLargerSyntaxNode` is called
repetedly in multi buffer.

Co-authored-by: Ben Kunkle <ben.kunkle@gmail.com>
This commit is contained in:
smit 2025-02-26 01:05:00 +05:30 committed by GitHub
parent 014d9dfce1
commit 0559e1f348
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 35 additions and 10 deletions

View File

@ -132,7 +132,7 @@ pub use multi_buffer::{
}; };
use multi_buffer::{ use multi_buffer::{
ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow, ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
ToOffsetUtf16, MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
}; };
use project::{ use project::{
lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle}, lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
@ -10738,7 +10738,10 @@ impl Editor {
while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone()) while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
{ {
new_node = Some(node); new_node = Some(node);
new_range = containing_range; new_range = match containing_range {
MultiOrSingleBufferOffsetRange::Single(_) => break,
MultiOrSingleBufferOffsetRange::Multi(range) => range,
};
if !display_map.intersects_fold(new_range.start) if !display_map.intersects_fold(new_range.start)
&& !display_map.intersects_fold(new_range.end) && !display_map.intersects_fold(new_range.end)
{ {

View File

@ -15,7 +15,7 @@ use itertools::Itertools;
use language::{DiagnosticEntry, Language, LanguageRegistry}; use language::{DiagnosticEntry, Language, LanguageRegistry};
use lsp::DiagnosticSeverity; use lsp::DiagnosticSeverity;
use markdown::{Markdown, MarkdownStyle}; use markdown::{Markdown, MarkdownStyle};
use multi_buffer::ToOffset; use multi_buffer::{MultiOrSingleBufferOffsetRange, ToOffset};
use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart}; use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart};
use settings::Settings; use settings::Settings;
use std::{borrow::Cow, cell::RefCell}; use std::{borrow::Cow, cell::RefCell};
@ -447,11 +447,13 @@ fn show_hover(
}) })
.or_else(|| { .or_else(|| {
let snapshot = &snapshot.buffer_snapshot; let snapshot = &snapshot.buffer_snapshot;
let offset_range = snapshot.syntax_ancestor(anchor..anchor)?.1; match snapshot.syntax_ancestor(anchor..anchor)?.1 {
Some( MultiOrSingleBufferOffsetRange::Multi(range) => Some(
snapshot.anchor_before(offset_range.start) snapshot.anchor_before(range.start)
..snapshot.anchor_after(offset_range.end), ..snapshot.anchor_after(range.end),
) ),
MultiOrSingleBufferOffsetRange::Single(_) => None,
}
}) })
.unwrap_or_else(|| anchor..anchor); .unwrap_or_else(|| anchor..anchor);

View File

@ -78,6 +78,12 @@ pub struct MultiBuffer {
capability: Capability, capability: Capability,
} }
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MultiOrSingleBufferOffsetRange {
Single(Range<usize>),
Multi(Range<usize>),
}
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum Event { pub enum Event {
ExcerptsAdded { ExcerptsAdded {
@ -5682,13 +5688,19 @@ impl MultiBufferSnapshot {
pub fn syntax_ancestor<T: ToOffset>( pub fn syntax_ancestor<T: ToOffset>(
&self, &self,
range: Range<T>, range: Range<T>,
) -> Option<(tree_sitter::Node, Range<usize>)> { ) -> Option<(tree_sitter::Node, MultiOrSingleBufferOffsetRange)> {
let range = range.start.to_offset(self)..range.end.to_offset(self); let range = range.start.to_offset(self)..range.end.to_offset(self);
let mut excerpt = self.excerpt_containing(range.clone())?; let mut excerpt = self.excerpt_containing(range.clone())?;
let node = excerpt let node = excerpt
.buffer() .buffer()
.syntax_ancestor(excerpt.map_range_to_buffer(range))?; .syntax_ancestor(excerpt.map_range_to_buffer(range))?;
Some((node, excerpt.map_range_from_buffer(node.byte_range()))) let node_range = node.byte_range();
let range = if excerpt.contains_buffer_range(node_range.clone()) {
MultiOrSingleBufferOffsetRange::Multi(excerpt.map_range_from_buffer(node_range))
} else {
MultiOrSingleBufferOffsetRange::Single(node_range)
};
Some((node, range))
} }
pub fn outline(&self, theme: Option<&SyntaxTheme>) -> Option<Outline<Anchor>> { pub fn outline(&self, theme: Option<&SyntaxTheme>) -> Option<Outline<Anchor>> {
@ -6660,9 +6672,17 @@ impl<'a> MultiBufferExcerpt<'a> {
/// Map a range within the [`Buffer`] to a range within the [`MultiBuffer`] /// Map a range within the [`Buffer`] to a range within the [`MultiBuffer`]
pub fn map_range_from_buffer(&mut self, buffer_range: Range<usize>) -> Range<usize> { pub fn map_range_from_buffer(&mut self, buffer_range: Range<usize>) -> Range<usize> {
if buffer_range.start < self.buffer_offset {
log::warn!("Attempting to map a range from a buffer offset that starts before the current buffer offset");
return buffer_range;
}
let overshoot = buffer_range.start - self.buffer_offset; let overshoot = buffer_range.start - self.buffer_offset;
let excerpt_offset = ExcerptDimension(self.excerpt_offset.0 + overshoot); let excerpt_offset = ExcerptDimension(self.excerpt_offset.0 + overshoot);
self.diff_transforms.seek(&excerpt_offset, Bias::Right, &()); self.diff_transforms.seek(&excerpt_offset, Bias::Right, &());
if excerpt_offset.0 < self.diff_transforms.start().1 .0 {
log::warn!("Attempting to map a range from a buffer offset that starts before the current buffer offset");
return buffer_range;
}
let overshoot = excerpt_offset.0 - self.diff_transforms.start().1 .0; let overshoot = excerpt_offset.0 - self.diff_transforms.start().1 .0;
let start = self.diff_transforms.start().0 .0 + overshoot; let start = self.diff_transforms.start().0 .0 + overshoot;