Introduce DisplayRow, MultiBufferRow newtypes and BufferRow type alias (#11656)

Part of https://github.com/zed-industries/zed/issues/8081

To avoid confusion and bugs when converting between various row `u32`'s,
use different types for each.
Further PRs should split `Point` into buffer and multi buffer variants
and make the code more readable.

Release Notes:

- N/A

---------

Co-authored-by: Piotr <piotr@zed.dev>
This commit is contained in:
Kirill Bulatov 2024-05-11 00:06:51 +03:00 committed by GitHub
parent 38f110852f
commit df41435d1a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
46 changed files with 1726 additions and 1220 deletions

View file

@ -35,6 +35,7 @@ log.workspace = true
parking_lot.workspace = true
rand.workspace = true
settings.workspace = true
serde.workspace = true
smallvec.workspace = true
sum_tree.workspace = true
text.workspace = true

View file

@ -11,9 +11,9 @@ use itertools::Itertools;
use language::{
char_kind,
language_settings::{language_settings, LanguageSettings},
AutoindentMode, Buffer, BufferChunks, BufferSnapshot, Capability, CharKind, Chunk, CursorShape,
DiagnosticEntry, File, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16,
Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _,
AutoindentMode, Buffer, BufferChunks, BufferRow, BufferSnapshot, Capability, CharKind, Chunk,
CursorShape, DiagnosticEntry, File, IndentSize, Language, LanguageScope, OffsetRangeExt,
OffsetUtf16, Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _,
ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped,
};
use smallvec::SmallVec;
@ -100,6 +100,16 @@ pub enum Event {
DiagnosticsUpdated,
}
pub type MultiBufferPoint = Point;
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq, serde::Deserialize)]
#[serde(transparent)]
pub struct MultiBufferRow(pub u32);
impl MultiBufferRow {
pub const MIN: Self = Self(0);
pub const MAX: Self = Self(u32::MAX);
}
#[derive(Clone)]
struct History {
next_transaction_id: TransactionId,
@ -165,8 +175,7 @@ pub struct MultiBufferSnapshot {
/// A boundary between [`Excerpt`]s in a [`MultiBuffer`]
pub struct ExcerptBoundary {
pub id: ExcerptId,
/// The row in the `MultiBuffer` where the boundary is located
pub row: u32,
pub row: MultiBufferRow,
pub buffer: BufferSnapshot,
pub range: ExcerptRange<text::Anchor>,
/// It's possible to have multiple excerpts in the same buffer,
@ -190,7 +199,7 @@ struct Excerpt {
/// The range of the buffer to be shown in the excerpt
range: ExcerptRange<text::Anchor>,
/// The last row in the excerpted slice of the buffer
max_buffer_row: u32,
max_buffer_row: BufferRow,
/// A summary of the text in the excerpt
text_summary: TextSummary,
has_trailing_newline: bool,
@ -229,7 +238,7 @@ struct ExcerptSummary {
/// The location of the last [`Excerpt`] being summarized
excerpt_locator: Locator,
/// The maximum row of the [`Excerpt`]s being summarized
max_buffer_row: u32,
max_buffer_row: MultiBufferRow,
text: TextSummary,
}
@ -2111,8 +2120,8 @@ impl MultiBufferSnapshot {
self.chunks(range, false).map(|chunk| chunk.text)
}
pub fn is_line_blank(&self, row: u32) -> bool {
self.text_for_range(Point::new(row, 0)..Point::new(row, self.line_len(row)))
pub fn is_line_blank(&self, row: MultiBufferRow) -> bool {
self.text_for_range(Point::new(row.0, 0)..Point::new(row.0, self.line_len(row)))
.all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none())
}
@ -2181,7 +2190,7 @@ impl MultiBufferSnapshot {
self.excerpts.summary().text.len == 0
}
pub fn max_buffer_row(&self) -> u32 {
pub fn max_buffer_row(&self) -> MultiBufferRow {
self.excerpts.summary().max_buffer_row
}
@ -2312,7 +2321,7 @@ impl MultiBufferSnapshot {
}
}
pub fn buffer_rows(&self, start_row: u32) -> MultiBufferRows {
pub fn buffer_rows(&self, start_row: MultiBufferRow) -> MultiBufferRows {
let mut result = MultiBufferRows {
buffer_row_range: 0..0,
excerpts: self.excerpts.cursor(),
@ -2509,7 +2518,7 @@ impl MultiBufferSnapshot {
&self,
rows: impl IntoIterator<Item = u32>,
cx: &AppContext,
) -> BTreeMap<u32, IndentSize> {
) -> BTreeMap<MultiBufferRow, IndentSize> {
let mut result = BTreeMap::new();
let mut rows_for_excerpt = Vec::new();
@ -2555,16 +2564,19 @@ impl MultiBufferSnapshot {
let buffer_indents = excerpt
.buffer
.suggested_indents(buffer_rows, single_indent_size);
let multibuffer_indents = buffer_indents
.into_iter()
.map(|(row, indent)| (start_multibuffer_row + row - start_buffer_row, indent));
let multibuffer_indents = buffer_indents.into_iter().map(|(row, indent)| {
(
MultiBufferRow(start_multibuffer_row + row - start_buffer_row),
indent,
)
});
result.extend(multibuffer_indents);
}
result
}
pub fn indent_size_for_line(&self, row: u32) -> IndentSize {
pub fn indent_size_for_line(&self, row: MultiBufferRow) -> IndentSize {
if let Some((buffer, range)) = self.buffer_line_for_row(row) {
let mut size = buffer.indent_size_for_line(range.start.row);
size.len = size
@ -2577,9 +2589,9 @@ impl MultiBufferSnapshot {
}
}
pub fn prev_non_blank_row(&self, mut row: u32) -> Option<u32> {
while row > 0 {
row -= 1;
pub fn prev_non_blank_row(&self, mut row: MultiBufferRow) -> Option<MultiBufferRow> {
while row.0 > 0 {
row.0 -= 1;
if !self.is_line_blank(row) {
return Some(row);
}
@ -2587,7 +2599,7 @@ impl MultiBufferSnapshot {
None
}
pub fn line_len(&self, row: u32) -> u32 {
pub fn line_len(&self, row: MultiBufferRow) -> u32 {
if let Some((_, range)) = self.buffer_line_for_row(row) {
range.end.column - range.start.column
} else {
@ -2595,15 +2607,18 @@ impl MultiBufferSnapshot {
}
}
pub fn buffer_line_for_row(&self, row: u32) -> Option<(&BufferSnapshot, Range<Point>)> {
pub fn buffer_line_for_row(
&self,
row: MultiBufferRow,
) -> Option<(&BufferSnapshot, Range<Point>)> {
let mut cursor = self.excerpts.cursor::<Point>();
let point = Point::new(row, 0);
let point = Point::new(row.0, 0);
cursor.seek(&point, Bias::Right, &());
if cursor.item().is_none() && *cursor.start() == point {
cursor.prev(&());
}
if let Some(excerpt) = cursor.item() {
let overshoot = row - cursor.start().row;
let overshoot = row.0 - cursor.start().row;
let excerpt_start = excerpt.range.context.start.to_point(&excerpt.buffer);
let excerpt_end = excerpt.range.context.end.to_point(&excerpt.buffer);
let buffer_row = excerpt_start.row + overshoot;
@ -3028,7 +3043,7 @@ impl MultiBufferSnapshot {
let starts_new_buffer = Some(excerpt.buffer_id) != prev_buffer_id;
let boundary = ExcerptBoundary {
id: excerpt.id,
row: cursor.start().1.row,
row: MultiBufferRow(cursor.start().1.row),
buffer: excerpt.buffer.clone(),
range: excerpt.range.clone(),
starts_new_buffer,
@ -3312,11 +3327,11 @@ impl MultiBufferSnapshot {
pub fn git_diff_hunks_in_range_rev(
&self,
row_range: Range<u32>,
) -> impl Iterator<Item = DiffHunk<u32>> + '_ {
row_range: Range<MultiBufferRow>,
) -> impl Iterator<Item = DiffHunk<MultiBufferRow>> + '_ {
let mut cursor = self.excerpts.cursor::<Point>();
cursor.seek(&Point::new(row_range.end, 0), Bias::Left, &());
cursor.seek(&Point::new(row_range.end.0, 0), Bias::Left, &());
if cursor.item().is_none() {
cursor.prev(&());
}
@ -3325,7 +3340,7 @@ impl MultiBufferSnapshot {
let excerpt = cursor.item()?;
let multibuffer_start = *cursor.start();
let multibuffer_end = multibuffer_start + excerpt.text_summary.lines;
if multibuffer_start.row >= row_range.end {
if multibuffer_start.row >= row_range.end.0 {
return None;
}
@ -3334,15 +3349,15 @@ impl MultiBufferSnapshot {
let excerpt_start_point = buffer_start.to_point(&excerpt.buffer);
let excerpt_end_point = excerpt_start_point + excerpt.text_summary.lines;
if row_range.start > multibuffer_start.row {
if row_range.start.0 > multibuffer_start.row {
let buffer_start_point =
excerpt_start_point + Point::new(row_range.start - multibuffer_start.row, 0);
excerpt_start_point + Point::new(row_range.start.0 - multibuffer_start.row, 0);
buffer_start = excerpt.buffer.anchor_before(buffer_start_point);
}
if row_range.end < multibuffer_end.row {
if row_range.end.0 < multibuffer_end.row {
let buffer_end_point =
excerpt_start_point + Point::new(row_range.end - multibuffer_start.row, 0);
excerpt_start_point + Point::new(row_range.end.0 - multibuffer_start.row, 0);
buffer_end = excerpt.buffer.anchor_before(buffer_end_point);
}
@ -3363,7 +3378,7 @@ impl MultiBufferSnapshot {
.saturating_sub(excerpt_start_point.row);
DiffHunk {
associated_range: start..end,
associated_range: MultiBufferRow(start)..MultiBufferRow(end),
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
buffer_range: hunk.buffer_range.clone(),
buffer_id: hunk.buffer_id,
@ -3379,11 +3394,11 @@ impl MultiBufferSnapshot {
pub fn git_diff_hunks_in_range(
&self,
row_range: Range<u32>,
) -> impl Iterator<Item = DiffHunk<u32>> + '_ {
row_range: Range<MultiBufferRow>,
) -> impl Iterator<Item = DiffHunk<MultiBufferRow>> + '_ {
let mut cursor = self.excerpts.cursor::<Point>();
cursor.seek(&Point::new(row_range.start, 0), Bias::Left, &());
cursor.seek(&Point::new(row_range.start.0, 0), Bias::Left, &());
std::iter::from_fn(move || {
let excerpt = cursor.item()?;
@ -3392,25 +3407,25 @@ impl MultiBufferSnapshot {
let mut buffer_start = excerpt.range.context.start;
let mut buffer_end = excerpt.range.context.end;
let excerpt_rows = match multibuffer_start.row.cmp(&row_range.end) {
let excerpt_rows = match multibuffer_start.row.cmp(&row_range.end.0) {
cmp::Ordering::Less => {
let excerpt_start_point = buffer_start.to_point(&excerpt.buffer);
let excerpt_end_point = excerpt_start_point + excerpt.text_summary.lines;
if row_range.start > multibuffer_start.row {
if row_range.start.0 > multibuffer_start.row {
let buffer_start_point = excerpt_start_point
+ Point::new(row_range.start - multibuffer_start.row, 0);
+ Point::new(row_range.start.0 - multibuffer_start.row, 0);
buffer_start = excerpt.buffer.anchor_before(buffer_start_point);
}
if row_range.end < multibuffer_end.row {
if row_range.end.0 < multibuffer_end.row {
let buffer_end_point = excerpt_start_point
+ Point::new(row_range.end - multibuffer_start.row, 0);
+ Point::new(row_range.end.0 - multibuffer_start.row, 0);
buffer_end = excerpt.buffer.anchor_before(buffer_end_point);
}
excerpt_start_point.row..excerpt_end_point.row
}
cmp::Ordering::Equal if row_range.end == 0 => {
cmp::Ordering::Equal if row_range.end.0 == 0 => {
buffer_end = buffer_start;
0..0
}
@ -3422,7 +3437,7 @@ impl MultiBufferSnapshot {
.git_diff_hunks_intersecting_range(buffer_start..buffer_end)
.map(move |hunk| {
let buffer_range = if excerpt_rows.start == 0 && excerpt_rows.end == 0 {
0..1
MultiBufferRow(0)..MultiBufferRow(1)
} else {
let start = multibuffer_start.row
+ hunk
@ -3435,7 +3450,7 @@ impl MultiBufferSnapshot {
.end
.min(excerpt_rows.end + 1)
.saturating_sub(excerpt_rows.start);
start..end
MultiBufferRow(start)..MultiBufferRow(end)
};
DiffHunk {
associated_range: buffer_range,
@ -4096,7 +4111,7 @@ impl sum_tree::Item for Excerpt {
ExcerptSummary {
excerpt_id: self.id,
excerpt_locator: self.locator.clone(),
max_buffer_row: self.max_buffer_row,
max_buffer_row: MultiBufferRow(self.max_buffer_row),
text,
}
}
@ -4198,22 +4213,22 @@ impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<ExcerptId> {
}
impl<'a> MultiBufferRows<'a> {
pub fn seek(&mut self, row: u32) {
pub fn seek(&mut self, row: MultiBufferRow) {
self.buffer_row_range = 0..0;
self.excerpts
.seek_forward(&Point::new(row, 0), Bias::Right, &());
.seek_forward(&Point::new(row.0, 0), Bias::Right, &());
if self.excerpts.item().is_none() {
self.excerpts.prev(&());
if self.excerpts.item().is_none() && row == 0 {
if self.excerpts.item().is_none() && row.0 == 0 {
self.buffer_row_range = 0..1;
return;
}
}
if let Some(excerpt) = self.excerpts.item() {
let overshoot = row - self.excerpts.start().row;
let overshoot = row.0 - self.excerpts.start().row;
let excerpt_start = excerpt.range.context.start.to_point(&excerpt.buffer).row;
self.buffer_row_range.start = excerpt_start + overshoot;
self.buffer_row_range.end = excerpt_start + excerpt.text_summary.lines.row + 1;
@ -4546,7 +4561,7 @@ mod tests {
assert_eq!(snapshot.text(), buffer.read(cx).text());
assert_eq!(
snapshot.buffer_rows(0).collect::<Vec<_>>(),
snapshot.buffer_rows(MultiBufferRow(0)).collect::<Vec<_>>(),
(0..buffer.read(cx).row_count())
.map(Some)
.collect::<Vec<_>>()
@ -4557,7 +4572,7 @@ mod tests {
assert_eq!(snapshot.text(), buffer.read(cx).text());
assert_eq!(
snapshot.buffer_rows(0).collect::<Vec<_>>(),
snapshot.buffer_rows(MultiBufferRow(0)).collect::<Vec<_>>(),
(0..buffer.read(cx).row_count())
.map(Some)
.collect::<Vec<_>>()
@ -4685,27 +4700,33 @@ mod tests {
)
);
assert_eq!(
snapshot.buffer_rows(0).collect::<Vec<_>>(),
snapshot.buffer_rows(MultiBufferRow(0)).collect::<Vec<_>>(),
[Some(1), Some(2), Some(3), Some(4), Some(3)]
);
assert_eq!(
snapshot.buffer_rows(2).collect::<Vec<_>>(),
snapshot.buffer_rows(MultiBufferRow(2)).collect::<Vec<_>>(),
[Some(3), Some(4), Some(3)]
);
assert_eq!(snapshot.buffer_rows(4).collect::<Vec<_>>(), [Some(3)]);
assert_eq!(snapshot.buffer_rows(5).collect::<Vec<_>>(), []);
assert_eq!(
snapshot.buffer_rows(MultiBufferRow(4)).collect::<Vec<_>>(),
[Some(3)]
);
assert_eq!(
snapshot.buffer_rows(MultiBufferRow(5)).collect::<Vec<_>>(),
[]
);
assert_eq!(
boundaries_in_range(Point::new(0, 0)..Point::new(4, 2), &snapshot),
&[
(0, "bbbb\nccccc".to_string(), true),
(2, "ddd\neeee".to_string(), false),
(4, "jj".to_string(), true),
(MultiBufferRow(0), "bbbb\nccccc".to_string(), true),
(MultiBufferRow(2), "ddd\neeee".to_string(), false),
(MultiBufferRow(4), "jj".to_string(), true),
]
);
assert_eq!(
boundaries_in_range(Point::new(0, 0)..Point::new(2, 0), &snapshot),
&[(0, "bbbb\nccccc".to_string(), true)]
&[(MultiBufferRow(0), "bbbb\nccccc".to_string(), true)]
);
assert_eq!(
boundaries_in_range(Point::new(1, 0)..Point::new(1, 5), &snapshot),
@ -4717,19 +4738,19 @@ mod tests {
);
assert_eq!(
boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot),
&[(2, "ddd\neeee".to_string(), false)]
&[(MultiBufferRow(2), "ddd\neeee".to_string(), false)]
);
assert_eq!(
boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot),
&[(2, "ddd\neeee".to_string(), false)]
&[(MultiBufferRow(2), "ddd\neeee".to_string(), false)]
);
assert_eq!(
boundaries_in_range(Point::new(2, 0)..Point::new(3, 0), &snapshot),
&[(2, "ddd\neeee".to_string(), false)]
&[(MultiBufferRow(2), "ddd\neeee".to_string(), false)]
);
assert_eq!(
boundaries_in_range(Point::new(4, 0)..Point::new(4, 2), &snapshot),
&[(4, "jj".to_string(), true)]
&[(MultiBufferRow(4), "jj".to_string(), true)]
);
assert_eq!(
boundaries_in_range(Point::new(4, 2)..Point::new(4, 2), &snapshot),
@ -4812,7 +4833,7 @@ mod tests {
fn boundaries_in_range(
range: Range<Point>,
snapshot: &MultiBufferSnapshot,
) -> Vec<(u32, String, bool)> {
) -> Vec<(MultiBufferRow, String, bool)> {
snapshot
.excerpt_boundaries_in_range(range)
.map(|boundary| {
@ -5100,8 +5121,14 @@ mod tests {
let snapshot = multibuffer.read(cx).snapshot(cx);
assert_eq!(snapshot.text(), "");
assert_eq!(snapshot.buffer_rows(0).collect::<Vec<_>>(), &[Some(0)]);
assert_eq!(snapshot.buffer_rows(1).collect::<Vec<_>>(), &[]);
assert_eq!(
snapshot.buffer_rows(MultiBufferRow(0)).collect::<Vec<_>>(),
&[Some(0)]
);
assert_eq!(
snapshot.buffer_rows(MultiBufferRow(1)).collect::<Vec<_>>(),
&[]
);
}
#[gpui::test]
@ -5518,14 +5545,16 @@ mod tests {
log::info!("MultiBuffer text: {:?}", expected_text);
assert_eq!(
snapshot.buffer_rows(0).collect::<Vec<_>>(),
snapshot.buffer_rows(MultiBufferRow(0)).collect::<Vec<_>>(),
expected_buffer_rows,
);
for _ in 0..5 {
let start_row = rng.gen_range(0..=expected_buffer_rows.len());
assert_eq!(
snapshot.buffer_rows(start_row as u32).collect::<Vec<_>>(),
snapshot
.buffer_rows(MultiBufferRow(start_row as u32))
.collect::<Vec<_>>(),
&expected_buffer_rows[start_row..],
"buffer_rows({})",
start_row
@ -5533,7 +5562,7 @@ mod tests {
}
assert_eq!(
snapshot.max_buffer_row(),
snapshot.max_buffer_row().0,
expected_buffer_rows.into_iter().flatten().max().unwrap()
);
@ -5666,7 +5695,7 @@ mod tests {
for (row, line) in expected_text.split('\n').enumerate() {
assert_eq!(
snapshot.line_len(row as u32),
snapshot.line_len(MultiBufferRow(row as u32)),
line.len() as u32,
"line_len({}).",
row