Fix panic in UnwrapSyntaxNode (#39139)
Closes #39139 Fixes ZED-1HY Release Notes: - Fixed a panic in UnwrapSyntaxNode in multi-buffers
This commit is contained in:
@@ -776,6 +776,8 @@ actions!(
|
||||
UniqueLinesCaseInsensitive,
|
||||
/// Removes duplicate lines (case-sensitive).
|
||||
UniqueLinesCaseSensitive,
|
||||
/// Removes the surrounding syntax node (for example brackets, or closures)
|
||||
/// from the current selections.
|
||||
UnwrapSyntaxNode,
|
||||
/// Wraps selections in tag specified by language.
|
||||
WrapSelectionsInTag
|
||||
|
||||
@@ -142,7 +142,7 @@ use mouse_context_menu::MouseContextMenu;
|
||||
use movement::TextLayoutDetails;
|
||||
use multi_buffer::{
|
||||
ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
|
||||
MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
|
||||
ToOffsetUtf16,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use persistence::DB;
|
||||
@@ -15175,12 +15175,8 @@ impl Editor {
|
||||
}
|
||||
|
||||
let mut new_range = old_range.clone();
|
||||
while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
|
||||
{
|
||||
new_range = match containing_range {
|
||||
MultiOrSingleBufferOffsetRange::Single(_) => break,
|
||||
MultiOrSingleBufferOffsetRange::Multi(range) => range,
|
||||
};
|
||||
while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
|
||||
new_range = range;
|
||||
if !node.is_named() {
|
||||
continue;
|
||||
}
|
||||
@@ -15310,20 +15306,14 @@ impl Editor {
|
||||
&& let Some((_, ancestor_range)) =
|
||||
buffer.syntax_ancestor(selection.start..selection.end)
|
||||
{
|
||||
match ancestor_range {
|
||||
MultiOrSingleBufferOffsetRange::Single(range) => range,
|
||||
MultiOrSingleBufferOffsetRange::Multi(range) => range,
|
||||
}
|
||||
ancestor_range
|
||||
} else {
|
||||
selection.range()
|
||||
};
|
||||
|
||||
let mut parent = child.clone();
|
||||
while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
|
||||
parent = match ancestor_range {
|
||||
MultiOrSingleBufferOffsetRange::Single(range) => range,
|
||||
MultiOrSingleBufferOffsetRange::Multi(range) => range,
|
||||
};
|
||||
parent = ancestor_range;
|
||||
if parent.start < child.start || parent.end > child.end {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -9161,6 +9161,64 @@ async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
|
||||
});
|
||||
|
||||
cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
|
||||
|
||||
cx.set_state(indoc! { r#"fn a() {
|
||||
// what
|
||||
// a
|
||||
// ˇlong
|
||||
// method
|
||||
// I
|
||||
// sure
|
||||
// hope
|
||||
// it
|
||||
// works
|
||||
}"# });
|
||||
|
||||
let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
|
||||
let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
|
||||
cx.update(|_, cx| {
|
||||
multi_buffer.update(cx, |multi_buffer, cx| {
|
||||
multi_buffer.set_excerpts_for_path(
|
||||
PathKey::for_buffer(&buffer, cx),
|
||||
buffer,
|
||||
[Point::new(1, 0)..Point::new(1, 0)],
|
||||
3,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
let editor2 = cx.new_window_entity(|window, cx| {
|
||||
Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
|
||||
});
|
||||
|
||||
let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::default(), window, cx, |s| {
|
||||
s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
|
||||
})
|
||||
});
|
||||
|
||||
cx.assert_editor_state(indoc! { "
|
||||
fn a() {
|
||||
// what
|
||||
// a
|
||||
ˇ // long
|
||||
// method"});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
|
||||
});
|
||||
|
||||
// Although we could potentially make the action work when the syntax node
|
||||
// is half-hidden, it seems a bit dangerous as you can't easily tell what it
|
||||
// did. Maybe we could also expand the excerpt to contain the range?
|
||||
cx.assert_editor_state(indoc! { "
|
||||
fn a() {
|
||||
// what
|
||||
// a
|
||||
ˇ // long
|
||||
// method"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
||||
@@ -16,7 +16,7 @@ use itertools::Itertools;
|
||||
use language::{DiagnosticEntry, Language, LanguageRegistry};
|
||||
use lsp::DiagnosticSeverity;
|
||||
use markdown::{Markdown, MarkdownElement, MarkdownStyle};
|
||||
use multi_buffer::{MultiOrSingleBufferOffsetRange, ToOffset, ToPoint};
|
||||
use multi_buffer::{ToOffset, ToPoint};
|
||||
use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart};
|
||||
use settings::Settings;
|
||||
use std::{borrow::Cow, cell::RefCell};
|
||||
@@ -477,13 +477,8 @@ fn show_hover(
|
||||
})
|
||||
.or_else(|| {
|
||||
let snapshot = &snapshot.buffer_snapshot;
|
||||
match snapshot.syntax_ancestor(anchor..anchor)?.1 {
|
||||
MultiOrSingleBufferOffsetRange::Multi(range) => Some(
|
||||
snapshot.anchor_before(range.start)
|
||||
..snapshot.anchor_after(range.end),
|
||||
),
|
||||
MultiOrSingleBufferOffsetRange::Single(_) => None,
|
||||
}
|
||||
let range = snapshot.syntax_ancestor(anchor..anchor)?.1;
|
||||
Some(snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end))
|
||||
})
|
||||
.unwrap_or_else(|| anchor..anchor);
|
||||
|
||||
|
||||
@@ -79,12 +79,6 @@ pub struct MultiBuffer {
|
||||
buffer_changed_since_sync: Rc<Cell<bool>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum MultiOrSingleBufferOffsetRange {
|
||||
Single(Range<usize>),
|
||||
Multi(Range<usize>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Event {
|
||||
ExcerptsAdded {
|
||||
@@ -6077,19 +6071,17 @@ impl MultiBufferSnapshot {
|
||||
pub fn syntax_ancestor<T: ToOffset>(
|
||||
&self,
|
||||
range: Range<T>,
|
||||
) -> Option<(tree_sitter::Node<'_>, MultiOrSingleBufferOffsetRange)> {
|
||||
) -> Option<(tree_sitter::Node<'_>, Range<usize>)> {
|
||||
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))?;
|
||||
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)
|
||||
if !excerpt.contains_buffer_range(node_range.clone()) {
|
||||
return None;
|
||||
};
|
||||
Some((node, range))
|
||||
Some((node, excerpt.map_range_from_buffer(node_range)))
|
||||
}
|
||||
|
||||
pub fn syntax_next_sibling<T: ToOffset>(
|
||||
|
||||
Reference in New Issue
Block a user