Add new argument vim text object (#7791)

This PR adds a new `argument` vim text object, inspired by
[targets.vim](https://github.com/wellle/targets.vim).

As it's the first vim text object to use the syntax tree, it needed to
operate on the `Buffer` level, not the `MultiBuffer` level, then map the
buffer coordinates to `DisplayPoint` as necessary.

This required two main changes:
1. `innermost_enclosing_bracket_ranges` and `enclosing_bracket_ranges`
were moved into `Buffer`. The `MultiBuffer` implementations were updated
to map to/from these.
2. `MultiBuffer::excerpt_containing` was made public, returning a new
`MultiBufferExcerpt` type that contains a reference to the excerpt and
methods for mapping to/from `Buffer` and `MultiBuffer` offsets and
ranges.

Release Notes:
- Added new `argument` vim text object, inspired by
[targets.vim](https://github.com/wellle/targets.vim).
This commit is contained in:
vultix 2024-02-23 20:37:13 -06:00 committed by GitHub
parent dc7e14f888
commit 2e616f8388
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 392 additions and 93 deletions

View file

@ -191,6 +191,16 @@ struct Excerpt {
has_trailing_newline: bool,
}
/// A public view into an [`Excerpt`] in a [`MultiBuffer`].
///
/// Contains methods for getting the [`Buffer`] of the excerpt,
/// as well as mapping offsets to/from buffer and multibuffer coordinates.
#[derive(Copy, Clone)]
pub struct MultiBufferExcerpt<'a> {
excerpt: &'a Excerpt,
excerpt_offset: usize,
}
#[derive(Clone, Debug)]
struct ExcerptIdMapping {
id: ExcerptId,
@ -2912,33 +2922,36 @@ impl MultiBufferSnapshot {
/// Returns the smallest enclosing bracket ranges containing the given range or
/// None if no brackets contain range or the range is not contained in a single
/// excerpt
///
/// Can optionally pass a range_filter to filter the ranges of brackets to consider
pub fn innermost_enclosing_bracket_ranges<T: ToOffset>(
&self,
range: Range<T>,
range_filter: Option<&dyn Fn(Range<usize>, Range<usize>) -> bool>,
) -> Option<(Range<usize>, Range<usize>)> {
let range = range.start.to_offset(self)..range.end.to_offset(self);
let excerpt = self.excerpt_containing(range.clone())?;
// Get the ranges of the innermost pair of brackets.
let mut result: Option<(Range<usize>, Range<usize>)> = None;
let Some(enclosing_bracket_ranges) = self.enclosing_bracket_ranges(range.clone()) else {
return None;
// Filter to ranges contained in the excerpt
let range_filter = |open: Range<usize>, close: Range<usize>| -> bool {
excerpt.contains_buffer_range(open.start..close.end)
&& range_filter.map_or(true, |filter| {
filter(
excerpt.map_range_from_buffer(open),
excerpt.map_range_from_buffer(close),
)
})
};
for (open, close) in enclosing_bracket_ranges {
let len = close.end - open.start;
let (open, close) = excerpt.buffer().innermost_enclosing_bracket_ranges(
excerpt.map_range_to_buffer(range),
Some(&range_filter),
)?;
if let Some((existing_open, existing_close)) = &result {
let existing_len = existing_close.end - existing_open.start;
if len > existing_len {
continue;
}
}
result = Some((open, close));
}
result
Some((
excerpt.map_range_from_buffer(open),
excerpt.map_range_from_buffer(close),
))
}
/// Returns enclosing bracket ranges containing the given range or returns None if the range is
@ -2948,11 +2961,14 @@ impl MultiBufferSnapshot {
range: Range<T>,
) -> Option<impl Iterator<Item = (Range<usize>, Range<usize>)> + 'a> {
let range = range.start.to_offset(self)..range.end.to_offset(self);
let excerpt = self.excerpt_containing(range.clone())?;
self.bracket_ranges(range.clone()).map(|range_pairs| {
range_pairs
.filter(move |(open, close)| open.start <= range.start && close.end >= range.end)
})
Some(
excerpt
.buffer()
.enclosing_bracket_ranges(excerpt.map_range_to_buffer(range))
.filter(move |(open, close)| excerpt.contains_buffer_range(open.start..close.end)),
)
}
/// Returns bracket range pairs overlapping the given `range` or returns None if the `range` is
@ -2962,38 +2978,24 @@ impl MultiBufferSnapshot {
range: Range<T>,
) -> Option<impl Iterator<Item = (Range<usize>, Range<usize>)> + 'a> {
let range = range.start.to_offset(self)..range.end.to_offset(self);
let excerpt = self.excerpt_containing(range.clone());
excerpt.map(|(excerpt, excerpt_offset)| {
let excerpt_buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
let excerpt_buffer_end = excerpt_buffer_start + excerpt.text_summary.len;
let start_in_buffer = excerpt_buffer_start + range.start.saturating_sub(excerpt_offset);
let end_in_buffer = excerpt_buffer_start + range.end.saturating_sub(excerpt_offset);
let excerpt = self.excerpt_containing(range.clone())?;
Some(
excerpt
.buffer
.bracket_ranges(start_in_buffer..end_in_buffer)
.filter_map(move |(start_bracket_range, end_bracket_range)| {
if start_bracket_range.start < excerpt_buffer_start
|| end_bracket_range.end > excerpt_buffer_end
{
return None;
.buffer()
.bracket_ranges(excerpt.map_range_to_buffer(range))
.filter_map(move |(start_bracket_range, close_bracket_range)| {
let buffer_range = start_bracket_range.start..close_bracket_range.end;
if excerpt.contains_buffer_range(buffer_range) {
Some((
excerpt.map_range_from_buffer(start_bracket_range),
excerpt.map_range_from_buffer(close_bracket_range),
))
} else {
None
}
let mut start_bracket_range = start_bracket_range.clone();
start_bracket_range.start =
excerpt_offset + (start_bracket_range.start - excerpt_buffer_start);
start_bracket_range.end =
excerpt_offset + (start_bracket_range.end - excerpt_buffer_start);
let mut end_bracket_range = end_bracket_range.clone();
end_bracket_range.start =
excerpt_offset + (end_bracket_range.start - excerpt_buffer_start);
end_bracket_range.end =
excerpt_offset + (end_bracket_range.end - excerpt_buffer_start);
Some((start_bracket_range, end_bracket_range))
})
})
}),
)
}
pub fn redacted_ranges<'a, T: ToOffset>(
@ -3260,26 +3262,13 @@ impl MultiBufferSnapshot {
pub fn range_for_syntax_ancestor<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
let range = range.start.to_offset(self)..range.end.to_offset(self);
let excerpt = self.excerpt_containing(range.clone())?;
self.excerpt_containing(range.clone())
.and_then(|(excerpt, excerpt_offset)| {
let excerpt_buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
let excerpt_buffer_end = excerpt_buffer_start + excerpt.text_summary.len;
let ancestor_buffer_range = excerpt
.buffer()
.range_for_syntax_ancestor(excerpt.map_range_to_buffer(range))?;
let start_in_buffer =
excerpt_buffer_start + range.start.saturating_sub(excerpt_offset);
let end_in_buffer = excerpt_buffer_start + range.end.saturating_sub(excerpt_offset);
let mut ancestor_buffer_range = excerpt
.buffer
.range_for_syntax_ancestor(start_in_buffer..end_in_buffer)?;
ancestor_buffer_range.start =
cmp::max(ancestor_buffer_range.start, excerpt_buffer_start);
ancestor_buffer_range.end = cmp::min(ancestor_buffer_range.end, excerpt_buffer_end);
let start = excerpt_offset + (ancestor_buffer_range.start - excerpt_buffer_start);
let end = excerpt_offset + (ancestor_buffer_range.end - excerpt_buffer_start);
Some(start..end)
})
Some(excerpt.map_range_from_buffer(ancestor_buffer_range))
}
pub fn outline(&self, theme: Option<&SyntaxTheme>) -> Option<Outline<Anchor>> {
@ -3366,32 +3355,25 @@ impl MultiBufferSnapshot {
}
/// Returns the excerpt containing range and its offset start within the multibuffer or none if `range` spans multiple excerpts
fn excerpt_containing<'a, T: ToOffset>(
&'a self,
range: Range<T>,
) -> Option<(&'a Excerpt, usize)> {
pub fn excerpt_containing<T: ToOffset>(&self, range: Range<T>) -> Option<MultiBufferExcerpt> {
let range = range.start.to_offset(self)..range.end.to_offset(self);
let mut cursor = self.excerpts.cursor::<usize>();
cursor.seek(&range.start, Bias::Right, &());
let start_excerpt = cursor.item();
let start_excerpt = cursor.item()?;
if range.start == range.end {
return start_excerpt.map(|excerpt| (excerpt, *cursor.start()));
return Some(MultiBufferExcerpt::new(start_excerpt, *cursor.start()));
}
cursor.seek(&range.end, Bias::Right, &());
let end_excerpt = cursor.item();
let end_excerpt = cursor.item()?;
start_excerpt
.zip(end_excerpt)
.and_then(|(start_excerpt, end_excerpt)| {
if start_excerpt.id != end_excerpt.id {
return None;
}
Some((start_excerpt, *cursor.start()))
})
if start_excerpt.id != end_excerpt.id {
None
} else {
Some(MultiBufferExcerpt::new(start_excerpt, *cursor.start()))
}
}
pub fn remote_selections_in_range<'a>(
@ -3768,6 +3750,61 @@ impl Excerpt {
.cmp(&anchor.text_anchor, &self.buffer)
.is_ge()
}
/// The [`Excerpt`]'s start offset in its [`Buffer`]
fn buffer_start_offset(&self) -> usize {
self.range.context.start.to_offset(&self.buffer)
}
/// The [`Excerpt`]'s end offset in its [`Buffer`]
fn buffer_end_offset(&self) -> usize {
self.buffer_start_offset() + self.text_summary.len
}
}
impl<'a> MultiBufferExcerpt<'a> {
fn new(excerpt: &'a Excerpt, excerpt_offset: usize) -> Self {
MultiBufferExcerpt {
excerpt,
excerpt_offset,
}
}
pub fn buffer(&self) -> &'a BufferSnapshot {
&self.excerpt.buffer
}
/// Maps an offset within the [`MultiBuffer`] to an offset within the [`Buffer`]
pub fn map_offset_to_buffer(&self, offset: usize) -> usize {
self.excerpt.buffer_start_offset() + offset.saturating_sub(self.excerpt_offset)
}
/// Maps a range within the [`MultiBuffer`] to a range within the [`Buffer`]
pub fn map_range_to_buffer(&self, range: Range<usize>) -> Range<usize> {
self.map_offset_to_buffer(range.start)..self.map_offset_to_buffer(range.end)
}
/// Map an offset within the [`Buffer`] to an offset within the [`MultiBuffer`]
pub fn map_offset_from_buffer(&self, buffer_offset: usize) -> usize {
let mut buffer_offset_in_excerpt =
buffer_offset.saturating_sub(self.excerpt.buffer_start_offset());
buffer_offset_in_excerpt =
cmp::min(buffer_offset_in_excerpt, self.excerpt.text_summary.len);
self.excerpt_offset + buffer_offset_in_excerpt
}
/// Map a range within the [`Buffer`] to a range within the [`MultiBuffer`]
pub fn map_range_from_buffer(&self, buffer_range: Range<usize>) -> Range<usize> {
self.map_offset_from_buffer(buffer_range.start)
..self.map_offset_from_buffer(buffer_range.end)
}
/// Returns true if the entirety of the given range is in the buffer's excerpt
pub fn contains_buffer_range(&self, range: Range<usize>) -> bool {
range.start >= self.excerpt.buffer_start_offset()
&& range.end <= self.excerpt.buffer_end_offset()
}
}
impl ExcerptId {