From 0559e1f348507caac8d9b5d2a6484ce398e163a4 Mon Sep 17 00:00:00 2001 From: smit <0xtimsb@gmail.com> Date: Wed, 26 Feb 2025 01:05:00 +0530 Subject: [PATCH] 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 --- crates/editor/src/editor.rs | 7 +++++-- crates/editor/src/hover_popover.rs | 14 ++++++++------ crates/multi_buffer/src/multi_buffer.rs | 24 ++++++++++++++++++++++-- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 801b500e8cbdcf0351d13a34a91656f7482672a5..8b12c2be50d33fcd40f5b403f1e7b538c0f4c141 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -132,7 +132,7 @@ pub use multi_buffer::{ }; use multi_buffer::{ ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow, - ToOffsetUtf16, + MultiOrSingleBufferOffsetRange, ToOffsetUtf16, }; use project::{ lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle}, @@ -10738,7 +10738,10 @@ impl Editor { while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone()) { 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) && !display_map.intersects_fold(new_range.end) { diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index a343dbf3234047d0089509876aa415a14788bbb8..9df88849659a6c173a6b248a3f59463486158d56 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -15,7 +15,7 @@ use itertools::Itertools; use language::{DiagnosticEntry, Language, LanguageRegistry}; use lsp::DiagnosticSeverity; use markdown::{Markdown, MarkdownStyle}; -use multi_buffer::ToOffset; +use multi_buffer::{MultiOrSingleBufferOffsetRange, ToOffset}; use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart}; use settings::Settings; use std::{borrow::Cow, cell::RefCell}; @@ -447,11 +447,13 @@ fn show_hover( }) .or_else(|| { let snapshot = &snapshot.buffer_snapshot; - let offset_range = snapshot.syntax_ancestor(anchor..anchor)?.1; - Some( - snapshot.anchor_before(offset_range.start) - ..snapshot.anchor_after(offset_range.end), - ) + match snapshot.syntax_ancestor(anchor..anchor)?.1 { + MultiOrSingleBufferOffsetRange::Multi(range) => Some( + snapshot.anchor_before(range.start) + ..snapshot.anchor_after(range.end), + ), + MultiOrSingleBufferOffsetRange::Single(_) => None, + } }) .unwrap_or_else(|| anchor..anchor); diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 9ff81458e1610f4b2c553eccb5b0b81b6f552594..34988a6b2f245d7e99d5718edcc41a80332ad765 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -78,6 +78,12 @@ pub struct MultiBuffer { capability: Capability, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum MultiOrSingleBufferOffsetRange { + Single(Range), + Multi(Range), +} + #[derive(Clone, Debug, PartialEq, Eq)] pub enum Event { ExcerptsAdded { @@ -5682,13 +5688,19 @@ impl MultiBufferSnapshot { pub fn syntax_ancestor( &self, range: Range, - ) -> Option<(tree_sitter::Node, Range)> { + ) -> Option<(tree_sitter::Node, MultiOrSingleBufferOffsetRange)> { let range = range.start.to_offset(self)..range.end.to_offset(self); let mut excerpt = self.excerpt_containing(range.clone())?; let node = excerpt .buffer() .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> { @@ -6660,9 +6672,17 @@ impl<'a> MultiBufferExcerpt<'a> { /// Map a range within the [`Buffer`] to a range within the [`MultiBuffer`] pub fn map_range_from_buffer(&mut self, buffer_range: Range) -> Range { + 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 excerpt_offset = ExcerptDimension(self.excerpt_offset.0 + overshoot); 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 start = self.diff_transforms.start().0 .0 + overshoot;