editor: Newtype WrapRow (#41843)

Makes a better distinction between `WrapRow` and `BlockRow`

Release Notes:

- N/A *or* Added/Fixed/Improved ...
This commit is contained in:
Lukas Wirth
2025-11-04 08:48:38 +00:00
committed by GitHub
parent 4002b32ad4
commit 3e7b8efb98
6 changed files with 484 additions and 332 deletions
+17 -8
View File
@@ -17,6 +17,9 @@
//! [Editor]: crate::Editor
//! [EditorElement]: crate::element::EditorElement
#[macro_use]
mod dimensions;
mod block_map;
mod crease_map;
mod custom_highlights;
@@ -167,11 +170,12 @@ impl DisplayMap {
}
pub fn snapshot(&mut self, cx: &mut Context<Self>) -> DisplaySnapshot {
let tab_size = Self::tab_size(&self.buffer, cx);
let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner();
let (inlay_snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot, edits);
let tab_size = Self::tab_size(&self.buffer, cx);
let (tab_snapshot, edits) = self.tab_map.sync(fold_snapshot, edits, tab_size);
let (wrap_snapshot, edits) = self
.wrap_map
@@ -915,7 +919,7 @@ impl DisplaySnapshot {
pub fn text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
self.block_snapshot
.chunks(
display_row.0..self.max_point().row().next_row().0,
BlockRow(display_row.0)..BlockRow(self.max_point().row().next_row().0),
false,
self.masked,
Highlights::default(),
@@ -927,7 +931,12 @@ impl DisplaySnapshot {
pub fn reverse_text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
(0..=display_row.0).rev().flat_map(move |row| {
self.block_snapshot
.chunks(row..row + 1, false, self.masked, Highlights::default())
.chunks(
BlockRow(row)..BlockRow(row + 1),
false,
self.masked,
Highlights::default(),
)
.map(|h| h.text)
.collect::<Vec<_>>()
.into_iter()
@@ -942,7 +951,7 @@ impl DisplaySnapshot {
highlight_styles: HighlightStyles,
) -> DisplayChunks<'_> {
self.block_snapshot.chunks(
display_rows.start.0..display_rows.end.0,
BlockRow(display_rows.start.0)..BlockRow(display_rows.end.0),
language_aware,
self.masked,
Highlights {
@@ -1178,8 +1187,8 @@ impl DisplaySnapshot {
rows: Range<DisplayRow>,
) -> impl Iterator<Item = (DisplayRow, &Block)> {
self.block_snapshot
.blocks_in_range(rows.start.0..rows.end.0)
.map(|(row, block)| (DisplayRow(row), block))
.blocks_in_range(BlockRow(rows.start.0)..BlockRow(rows.end.0))
.map(|(row, block)| (DisplayRow(row.0), block))
}
pub fn sticky_header_excerpt(&self, row: f64) -> Option<StickyHeaderExcerpt<'_>> {
@@ -1211,7 +1220,7 @@ impl DisplaySnapshot {
pub fn soft_wrap_indent(&self, display_row: DisplayRow) -> Option<u32> {
let wrap_row = self
.block_snapshot
.to_wrap_point(BlockPoint::new(display_row.0, 0), Bias::Left)
.to_wrap_point(BlockPoint::new(BlockRow(display_row.0), 0), Bias::Left)
.row();
self.wrap_snapshot().soft_wrap_indent(wrap_row)
}
@@ -1242,7 +1251,7 @@ impl DisplaySnapshot {
}
pub fn longest_row(&self) -> DisplayRow {
DisplayRow(self.block_snapshot.longest_row())
DisplayRow(self.block_snapshot.longest_row().0)
}
pub fn longest_row_in_range(&self, range: Range<DisplayRow>) -> DisplayRow {
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,96 @@
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
pub struct RowDelta(pub u32);
impl RowDelta {
pub fn saturating_sub(self, other: RowDelta) -> RowDelta {
RowDelta(self.0.saturating_sub(other.0))
}
}
impl ::std::ops::Add for RowDelta {
type Output = RowDelta;
fn add(self, rhs: RowDelta) -> Self::Output {
RowDelta(self.0 + rhs.0)
}
}
impl ::std::ops::Sub for RowDelta {
type Output = RowDelta;
fn sub(self, rhs: RowDelta) -> Self::Output {
RowDelta(self.0 - rhs.0)
}
}
impl ::std::ops::AddAssign for RowDelta {
fn add_assign(&mut self, rhs: RowDelta) {
self.0 += rhs.0;
}
}
impl ::std::ops::SubAssign for RowDelta {
fn sub_assign(&mut self, rhs: RowDelta) {
self.0 -= rhs.0;
}
}
macro_rules! impl_for_row_types {
($row:ident => $row_delta:ident) => {
impl $row {
pub fn saturating_sub(self, other: $row_delta) -> $row {
$row(self.0.saturating_sub(other.0))
}
}
impl ::std::ops::Add for $row {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self(self.0 + rhs.0)
}
}
impl ::std::ops::Add<$row_delta> for $row {
type Output = Self;
fn add(self, rhs: $row_delta) -> Self::Output {
Self(self.0 + rhs.0)
}
}
impl ::std::ops::Sub for $row {
type Output = $row_delta;
fn sub(self, rhs: Self) -> Self::Output {
$row_delta(self.0 - rhs.0)
}
}
impl ::std::ops::Sub<$row_delta> for $row {
type Output = $row;
fn sub(self, rhs: $row_delta) -> Self::Output {
$row(self.0 - rhs.0)
}
}
impl ::std::ops::AddAssign for $row {
fn add_assign(&mut self, rhs: Self) {
self.0 += rhs.0;
}
}
impl ::std::ops::AddAssign<$row_delta> for $row {
fn add_assign(&mut self, rhs: $row_delta) {
self.0 += rhs.0;
}
}
impl ::std::ops::SubAssign<$row_delta> for $row {
fn sub_assign(&mut self, rhs: $row_delta) {
self.0 -= rhs.0;
}
}
};
}
+70 -55
View File
@@ -1,5 +1,6 @@
use super::{
Highlights,
dimensions::RowDelta,
fold_map::{Chunk, FoldRows},
tab_map::{self, TabEdit, TabPoint, TabSnapshot},
};
@@ -7,13 +8,20 @@ use gpui::{App, AppContext as _, Context, Entity, Font, LineWrapper, Pixels, Tas
use language::Point;
use multi_buffer::{MultiBufferSnapshot, RowInfo};
use smol::future::yield_now;
use std::sync::LazyLock;
use std::{cmp, collections::VecDeque, mem, ops::Range, time::Duration};
use std::{cmp, collections::VecDeque, mem, ops::Range, sync::LazyLock, time::Duration};
use sum_tree::{Bias, Cursor, Dimensions, SumTree};
use text::Patch;
pub use super::tab_map::TextSummary;
pub type WrapEdit = text::Edit<u32>;
pub type WrapEdit = text::Edit<WrapRow>;
pub type WrapPatch = text::Patch<WrapRow>;
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
pub struct WrapRow(pub u32);
impl_for_row_types! {
WrapRow => RowDelta
}
/// Handles soft wrapping of text.
///
@@ -21,8 +29,8 @@ pub type WrapEdit = text::Edit<u32>;
pub struct WrapMap {
snapshot: WrapSnapshot,
pending_edits: VecDeque<(TabSnapshot, Vec<TabEdit>)>,
interpolated_edits: Patch<u32>,
edits_since_sync: Patch<u32>,
interpolated_edits: WrapPatch,
edits_since_sync: WrapPatch,
wrap_width: Option<Pixels>,
background_task: Option<Task<()>>,
font_with_size: (Font, Pixels),
@@ -54,7 +62,7 @@ pub struct WrapChunks<'a> {
input_chunks: tab_map::TabChunks<'a>,
input_chunk: Chunk<'a>,
output_position: WrapPoint,
max_output_row: u32,
max_output_row: WrapRow,
transforms: Cursor<'a, 'static, Transform, Dimensions<WrapPoint, TabPoint>>,
snapshot: &'a WrapSnapshot,
}
@@ -63,19 +71,19 @@ pub struct WrapChunks<'a> {
pub struct WrapRows<'a> {
input_buffer_rows: FoldRows<'a>,
input_buffer_row: RowInfo,
output_row: u32,
output_row: WrapRow,
soft_wrapped: bool,
max_output_row: u32,
max_output_row: WrapRow,
transforms: Cursor<'a, 'static, Transform, Dimensions<WrapPoint, TabPoint>>,
}
impl WrapRows<'_> {
pub(crate) fn seek(&mut self, start_row: u32) {
pub(crate) fn seek(&mut self, start_row: WrapRow) {
self.transforms
.seek(&WrapPoint::new(start_row, 0), Bias::Left);
let mut input_row = self.transforms.start().1.row();
if self.transforms.item().is_some_and(|t| t.is_isomorphic()) {
input_row += start_row - self.transforms.start().0.row();
input_row += (start_row - self.transforms.start().0.row()).0;
}
self.soft_wrapped = self.transforms.item().is_some_and(|t| !t.is_isomorphic());
self.input_buffer_rows.seek(input_row);
@@ -120,7 +128,7 @@ impl WrapMap {
tab_snapshot: TabSnapshot,
edits: Vec<TabEdit>,
cx: &mut Context<Self>,
) -> (WrapSnapshot, Patch<u32>) {
) -> (WrapSnapshot, WrapPatch) {
if self.wrap_width.is_some() {
self.pending_edits.push_back((tab_snapshot, edits));
self.flush_edits(cx);
@@ -226,8 +234,8 @@ impl WrapMap {
let new_rows = self.snapshot.transforms.summary().output.lines.row + 1;
self.snapshot.interpolated = false;
self.edits_since_sync = self.edits_since_sync.compose(Patch::new(vec![WrapEdit {
old: 0..old_rows,
new: 0..new_rows,
old: WrapRow(0)..WrapRow(old_rows),
new: WrapRow(0)..WrapRow(new_rows),
}]));
}
}
@@ -331,7 +339,7 @@ impl WrapSnapshot {
self.tab_snapshot.buffer_snapshot()
}
fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, tab_edits: &[TabEdit]) -> Patch<u32> {
fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, tab_edits: &[TabEdit]) -> WrapPatch {
let mut new_transforms;
if tab_edits.is_empty() {
new_transforms = self.transforms.clone();
@@ -401,7 +409,7 @@ impl WrapSnapshot {
tab_edits: &[TabEdit],
wrap_width: Pixels,
line_wrapper: &mut LineWrapper,
) -> Patch<u32> {
) -> WrapPatch {
#[derive(Debug)]
struct RowEdit {
old_rows: Range<u32>,
@@ -554,7 +562,7 @@ impl WrapSnapshot {
old_snapshot.compute_edits(tab_edits, self)
}
fn compute_edits(&self, tab_edits: &[TabEdit], new_snapshot: &WrapSnapshot) -> Patch<u32> {
fn compute_edits(&self, tab_edits: &[TabEdit], new_snapshot: &WrapSnapshot) -> WrapPatch {
let mut wrap_edits = Vec::with_capacity(tab_edits.len());
let mut old_cursor = self.transforms.cursor::<TransformSummary>(());
let mut new_cursor = new_snapshot.transforms.cursor::<TransformSummary>(());
@@ -581,8 +589,8 @@ impl WrapSnapshot {
new_end += tab_edit.new.end.0 - new_cursor.start().input.lines;
wrap_edits.push(WrapEdit {
old: old_start.row..old_end.row,
new: new_start.row..new_end.row,
old: WrapRow(old_start.row)..WrapRow(old_end.row),
new: WrapRow(new_start.row)..WrapRow(new_end.row),
});
}
@@ -592,7 +600,7 @@ impl WrapSnapshot {
pub(crate) fn chunks<'a>(
&'a self,
rows: Range<u32>,
rows: Range<WrapRow>,
language_aware: bool,
highlights: Highlights<'a>,
) -> WrapChunks<'a> {
@@ -627,17 +635,17 @@ impl WrapSnapshot {
WrapPoint(self.transforms.summary().output.lines)
}
pub fn line_len(&self, row: u32) -> u32 {
pub fn line_len(&self, row: WrapRow) -> u32 {
let (start, _, item) = self.transforms.find::<Dimensions<WrapPoint, TabPoint>, _>(
(),
&WrapPoint::new(row + 1, 0),
&WrapPoint::new(row + WrapRow(1), 0),
Bias::Left,
);
if item.is_some_and(|transform| transform.is_isomorphic()) {
let overshoot = row - start.0.row();
let tab_row = start.1.row() + overshoot;
let tab_row = start.1.row() + overshoot.0;
let tab_line_len = self.tab_snapshot.line_len(tab_row);
if overshoot == 0 {
if overshoot.0 == 0 {
start.0.column() + (tab_line_len - start.1.column())
} else {
tab_line_len
@@ -647,7 +655,7 @@ impl WrapSnapshot {
}
}
pub fn text_summary_for_range(&self, rows: Range<u32>) -> TextSummary {
pub fn text_summary_for_range(&self, rows: Range<WrapRow>) -> TextSummary {
let mut summary = TextSummary::default();
let start = WrapPoint::new(rows.start, 0);
@@ -708,10 +716,12 @@ impl WrapSnapshot {
summary
}
pub fn soft_wrap_indent(&self, row: u32) -> Option<u32> {
let (.., item) =
self.transforms
.find::<WrapPoint, _>((), &WrapPoint::new(row + 1, 0), Bias::Right);
pub fn soft_wrap_indent(&self, row: WrapRow) -> Option<u32> {
let (.., item) = self.transforms.find::<WrapPoint, _>(
(),
&WrapPoint::new(row + WrapRow(1), 0),
Bias::Right,
);
item.and_then(|transform| {
if transform.is_isomorphic() {
None
@@ -725,14 +735,14 @@ impl WrapSnapshot {
self.transforms.summary().output.longest_row
}
pub fn row_infos(&self, start_row: u32) -> WrapRows<'_> {
pub fn row_infos(&self, start_row: WrapRow) -> WrapRows<'_> {
let mut transforms = self
.transforms
.cursor::<Dimensions<WrapPoint, TabPoint>>(());
transforms.seek(&WrapPoint::new(start_row, 0), Bias::Left);
let mut input_row = transforms.start().1.row();
if transforms.item().is_some_and(|t| t.is_isomorphic()) {
input_row += start_row - transforms.start().0.row();
input_row += (start_row - transforms.start().0.row()).0;
}
let soft_wrapped = transforms.item().is_some_and(|t| !t.is_isomorphic());
let mut input_buffer_rows = self.tab_snapshot.rows(input_row);
@@ -787,9 +797,9 @@ impl WrapSnapshot {
self.tab_point_to_wrap_point(self.tab_snapshot.clip_point(self.to_tab_point(point), bias))
}
pub fn prev_row_boundary(&self, mut point: WrapPoint) -> u32 {
pub fn prev_row_boundary(&self, mut point: WrapPoint) -> WrapRow {
if self.transforms.is_empty() {
return 0;
return WrapRow(0);
}
*point.column_mut() = 0;
@@ -813,7 +823,7 @@ impl WrapSnapshot {
unreachable!()
}
pub fn next_row_boundary(&self, mut point: WrapPoint) -> Option<u32> {
pub fn next_row_boundary(&self, mut point: WrapPoint) -> Option<WrapRow> {
point.0 += Point::new(1, 0);
let mut cursor = self
@@ -833,13 +843,13 @@ impl WrapSnapshot {
#[cfg(test)]
pub fn text(&self) -> String {
self.text_chunks(0).collect()
self.text_chunks(WrapRow(0)).collect()
}
#[cfg(test)]
pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator<Item = &str> {
pub fn text_chunks(&self, wrap_row: WrapRow) -> impl Iterator<Item = &str> {
self.chunks(
wrap_row..self.max_point().row() + 1,
wrap_row..self.max_point().row() + WrapRow(1),
false,
Highlights::default(),
)
@@ -867,21 +877,22 @@ impl WrapSnapshot {
let mut input_buffer_rows = self.tab_snapshot.rows(0);
let mut expected_buffer_rows = Vec::new();
let mut prev_tab_row = 0;
for display_row in 0..=self.max_point().row() {
for display_row in 0..=self.max_point().row().0 {
let display_row = WrapRow(display_row);
let tab_point = self.to_tab_point(WrapPoint::new(display_row, 0));
if tab_point.row() == prev_tab_row && display_row != 0 {
if tab_point.row() == prev_tab_row && display_row != WrapRow(0) {
expected_buffer_rows.push(None);
} else {
expected_buffer_rows.push(input_buffer_rows.next().unwrap().buffer_row);
}
prev_tab_row = tab_point.row();
assert_eq!(self.line_len(display_row), text.line_len(display_row));
assert_eq!(self.line_len(display_row), text.line_len(display_row.0));
}
for start_display_row in 0..expected_buffer_rows.len() {
assert_eq!(
self.row_infos(start_display_row as u32)
self.row_infos(WrapRow(start_display_row as u32))
.map(|row_info| row_info.buffer_row)
.collect::<Vec<_>>(),
&expected_buffer_rows[start_display_row..],
@@ -894,7 +905,7 @@ impl WrapSnapshot {
}
impl WrapChunks<'_> {
pub(crate) fn seek(&mut self, rows: Range<u32>) {
pub(crate) fn seek(&mut self, rows: Range<WrapRow>) {
let output_start = WrapPoint::new(rows.start, 0);
let output_end = WrapPoint::new(rows.end, 0);
self.transforms.seek(&output_start, Bias::Right);
@@ -931,7 +942,7 @@ impl<'a> Iterator for WrapChunks<'a> {
// Exclude newline starting prior to the desired row.
start_ix = 1;
summary.row = 0;
} else if self.output_position.row() + 1 >= self.max_output_row {
} else if self.output_position.row() + WrapRow(1) >= self.max_output_row {
// Exclude soft indentation ending after the desired row.
end_ix = 1;
summary.column = 0;
@@ -997,7 +1008,7 @@ impl Iterator for WrapRows<'_> {
let soft_wrapped = self.soft_wrapped;
let diff_status = self.input_buffer_row.diff_status;
self.output_row += 1;
self.output_row += WrapRow(1);
self.transforms
.seek_forward(&WrapPoint::new(self.output_row, 0), Bias::Left);
if self.transforms.item().is_some_and(|t| t.is_isomorphic()) {
@@ -1107,12 +1118,12 @@ impl SumTreeExt for SumTree<Transform> {
}
impl WrapPoint {
pub fn new(row: u32, column: u32) -> Self {
Self(Point::new(row, column))
pub fn new(row: WrapRow, column: u32) -> Self {
Self(Point::new(row.0, column))
}
pub fn row(self) -> u32 {
self.0.row
pub fn row(self) -> WrapRow {
WrapRow(self.0.row)
}
pub fn row_mut(&mut self) -> &mut u32 {
@@ -1417,14 +1428,14 @@ mod tests {
for (snapshot, patch) in edits {
let snapshot_text = Rope::from(snapshot.text().as_str());
for edit in &patch {
let old_start = initial_text.point_to_offset(Point::new(edit.new.start, 0));
let old_start = initial_text.point_to_offset(Point::new(edit.new.start.0, 0));
let old_end = initial_text.point_to_offset(cmp::min(
Point::new(edit.new.start + edit.old.len() as u32, 0),
Point::new(edit.new.start.0 + (edit.old.end - edit.old.start).0, 0),
initial_text.max_point(),
));
let new_start = snapshot_text.point_to_offset(Point::new(edit.new.start, 0));
let new_start = snapshot_text.point_to_offset(Point::new(edit.new.start.0, 0));
let new_end = snapshot_text.point_to_offset(cmp::min(
Point::new(edit.new.end, 0),
Point::new(edit.new.end.0, 0),
snapshot_text.max_point(),
));
let new_text = snapshot_text
@@ -1484,11 +1495,11 @@ mod tests {
impl WrapSnapshot {
fn verify_chunks(&mut self, rng: &mut impl Rng) {
for _ in 0..5 {
let mut end_row = rng.random_range(0..=self.max_point().row());
let mut end_row = rng.random_range(0..=self.max_point().row().0);
let start_row = rng.random_range(0..=end_row);
end_row += 1;
let mut expected_text = self.text_chunks(start_row).collect::<String>();
let mut expected_text = self.text_chunks(WrapRow(start_row)).collect::<String>();
if expected_text.ends_with('\n') {
expected_text.push('\n');
}
@@ -1497,12 +1508,16 @@ mod tests {
.take((end_row - start_row) as usize)
.collect::<Vec<_>>()
.join("\n");
if end_row <= self.max_point().row() {
if end_row <= self.max_point().row().0 {
expected_text.push('\n');
}
let actual_text = self
.chunks(start_row..end_row, true, Highlights::default())
.chunks(
WrapRow(start_row)..WrapRow(end_row),
true,
Highlights::default(),
)
.map(|c| c.text)
.collect::<String>();
assert_eq!(
+44 -41
View File
@@ -9,15 +9,7 @@ pub struct Patch<T>(Vec<Edit<T>>);
impl<T> Patch<T>
where
T: 'static
+ Clone
+ Copy
+ Ord
+ Sub<T, Output = T>
+ Add<T, Output = T>
+ AddAssign
+ Default
+ PartialEq,
T: 'static + Clone + Copy + Ord + Default,
{
pub fn new(edits: Vec<Edit<T>>) -> Self {
#[cfg(debug_assertions)]
@@ -41,7 +33,50 @@ where
pub fn into_inner(self) -> Vec<Edit<T>> {
self.0
}
pub fn invert(&mut self) -> &mut Self {
for edit in &mut self.0 {
mem::swap(&mut edit.old, &mut edit.new);
}
self
}
pub fn clear(&mut self) {
self.0.clear();
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn push(&mut self, edit: Edit<T>) {
if edit.is_empty() {
return;
}
if let Some(last) = self.0.last_mut() {
if last.old.end >= edit.old.start {
last.old.end = edit.old.end;
last.new.end = edit.new.end;
} else {
self.0.push(edit);
}
} else {
self.0.push(edit);
}
}
}
impl<T, TDelta> Patch<T>
where
T: 'static
+ Copy
+ Ord
+ Sub<T, Output = TDelta>
+ Add<TDelta, Output = T>
+ AddAssign<TDelta>
+ Default,
TDelta: Ord + Copy,
{
#[must_use]
pub fn compose(&self, new_edits_iter: impl IntoIterator<Item = Edit<T>>) -> Self {
let mut old_edits_iter = self.0.iter().cloned().peekable();
@@ -169,38 +204,6 @@ where
composed
}
pub fn invert(&mut self) -> &mut Self {
for edit in &mut self.0 {
mem::swap(&mut edit.old, &mut edit.new);
}
self
}
pub fn clear(&mut self) {
self.0.clear();
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn push(&mut self, edit: Edit<T>) {
if edit.is_empty() {
return;
}
if let Some(last) = self.0.last_mut() {
if last.old.end >= edit.old.start {
last.old.end = edit.old.end;
last.new.end = edit.new.end;
} else {
self.0.push(edit);
}
} else {
self.0.push(edit);
}
}
pub fn old_to_new(&self, old: T) -> T {
let ix = match self.0.binary_search_by(|probe| probe.old.start.cmp(&old)) {
Ok(ix) => ix,
+12 -8
View File
@@ -496,22 +496,26 @@ pub struct Edit<D> {
pub old: Range<D>,
pub new: Range<D>,
}
impl<D> Edit<D>
where
D: Sub<D, Output = D> + PartialEq + Copy,
D: PartialEq,
{
pub fn old_len(&self) -> D {
pub fn is_empty(&self) -> bool {
self.old.start == self.old.end && self.new.start == self.new.end
}
}
impl<D, DDelta> Edit<D>
where
D: Sub<D, Output = DDelta> + Copy,
{
pub fn old_len(&self) -> DDelta {
self.old.end - self.old.start
}
pub fn new_len(&self) -> D {
pub fn new_len(&self) -> DDelta {
self.new.end - self.new.start
}
pub fn is_empty(&self) -> bool {
self.old.start == self.old.end && self.new.start == self.new.end
}
}
impl<D1, D2> Edit<(D1, D2)> {