Communicate with language servers in terms of UTF-16 coordinates
This required indexing UTF-16 positions in `Rope`. We tried opting into the UTF-8 experimental support but it didn't seem to work correctly and the standard is UTF-16 anyway. Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
9c1b01521a
commit
6212ebad9b
11 changed files with 564 additions and 221 deletions
|
@ -1,6 +1,7 @@
|
|||
mod anchor;
|
||||
mod operation_queue;
|
||||
mod point;
|
||||
mod point_utf16;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub mod random_char_iter;
|
||||
pub mod rope;
|
||||
|
@ -13,8 +14,10 @@ use anyhow::{anyhow, Result};
|
|||
use clock::ReplicaId;
|
||||
use operation_queue::OperationQueue;
|
||||
pub use point::*;
|
||||
pub use point_utf16::*;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub use random_char_iter::*;
|
||||
use rope::TextDimension;
|
||||
pub use rope::{Chunks, Rope, TextSummary};
|
||||
use rpc::proto;
|
||||
pub use selection::*;
|
||||
|
@ -309,41 +312,34 @@ impl UndoMap {
|
|||
}
|
||||
}
|
||||
|
||||
struct Edits<'a, F: FnMut(&FragmentSummary) -> bool> {
|
||||
visible_text: &'a Rope,
|
||||
deleted_text: &'a Rope,
|
||||
cursor: Option<FilterCursor<'a, F, Fragment, FragmentTextSummary>>,
|
||||
struct Edits<'a, D: TextDimension<'a>, F: FnMut(&FragmentSummary) -> bool> {
|
||||
visible_cursor: rope::Cursor<'a>,
|
||||
deleted_cursor: rope::Cursor<'a>,
|
||||
fragments_cursor: Option<FilterCursor<'a, F, Fragment, FragmentTextSummary>>,
|
||||
undos: &'a UndoMap,
|
||||
since: clock::Global,
|
||||
old_offset: usize,
|
||||
new_offset: usize,
|
||||
old_point: Point,
|
||||
new_point: Point,
|
||||
old_end: D,
|
||||
new_end: D,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct Edit {
|
||||
pub old_bytes: Range<usize>,
|
||||
pub new_bytes: Range<usize>,
|
||||
pub old_lines: Range<Point>,
|
||||
pub new_lines: Range<Point>,
|
||||
pub struct Edit<D> {
|
||||
pub old: Range<D>,
|
||||
pub new: Range<D>,
|
||||
}
|
||||
|
||||
impl Edit {
|
||||
pub fn delta(&self) -> isize {
|
||||
self.inserted_bytes() as isize - self.deleted_bytes() as isize
|
||||
}
|
||||
|
||||
pub fn deleted_bytes(&self) -> usize {
|
||||
self.old_bytes.end - self.old_bytes.start
|
||||
}
|
||||
|
||||
pub fn inserted_bytes(&self) -> usize {
|
||||
self.new_bytes.end - self.new_bytes.start
|
||||
}
|
||||
|
||||
pub fn deleted_lines(&self) -> Point {
|
||||
self.old_lines.end - self.old_lines.start
|
||||
impl<D1, D2> Edit<(D1, D2)> {
|
||||
pub fn flatten(self) -> (Edit<D1>, Edit<D2>) {
|
||||
(
|
||||
Edit {
|
||||
old: self.old.start.0..self.old.end.0,
|
||||
new: self.new.start.0..self.new.end.0,
|
||||
},
|
||||
Edit {
|
||||
old: self.old.start.1..self.old.end.1,
|
||||
new: self.new.start.1..self.new.end.1,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1369,7 +1365,10 @@ impl Buffer {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn edits_since<'a>(&'a self, since: clock::Global) -> impl 'a + Iterator<Item = Edit> {
|
||||
pub fn edits_since<'a, D>(&'a self, since: clock::Global) -> impl 'a + Iterator<Item = Edit<D>>
|
||||
where
|
||||
D: 'a + TextDimension<'a> + Ord,
|
||||
{
|
||||
self.content().edits_since(since)
|
||||
}
|
||||
}
|
||||
|
@ -1589,11 +1588,11 @@ impl Snapshot {
|
|||
}
|
||||
|
||||
pub fn to_offset(&self, point: Point) -> usize {
|
||||
self.visible_text.to_offset(point)
|
||||
self.visible_text.point_to_offset(point)
|
||||
}
|
||||
|
||||
pub fn to_point(&self, offset: usize) -> Point {
|
||||
self.visible_text.to_point(offset)
|
||||
self.visible_text.offset_to_point(offset)
|
||||
}
|
||||
|
||||
pub fn anchor_before<T: ToOffset>(&self, position: T) -> Anchor {
|
||||
|
@ -1604,7 +1603,10 @@ impl Snapshot {
|
|||
self.content().anchor_at(position, Bias::Right)
|
||||
}
|
||||
|
||||
pub fn edits_since<'a>(&'a self, since: clock::Global) -> impl 'a + Iterator<Item = Edit> {
|
||||
pub fn edits_since<'a, D>(&'a self, since: clock::Global) -> impl 'a + Iterator<Item = Edit<D>>
|
||||
where
|
||||
D: 'a + TextDimension<'a> + Ord,
|
||||
{
|
||||
self.content().edits_since(since)
|
||||
}
|
||||
|
||||
|
@ -1756,7 +1758,7 @@ impl<'a> Content<'a> {
|
|||
} else {
|
||||
0
|
||||
};
|
||||
summary += rope_cursor.summary(cursor.start().1 + overshoot);
|
||||
summary += rope_cursor.summary::<TextSummary>(cursor.start().1 + overshoot);
|
||||
(summary.clone(), value)
|
||||
})
|
||||
}
|
||||
|
@ -1785,7 +1787,7 @@ impl<'a> Content<'a> {
|
|||
} else {
|
||||
0
|
||||
};
|
||||
summary += rope_cursor.summary(cursor.start().1 + overshoot);
|
||||
summary += rope_cursor.summary::<TextSummary>(cursor.start().1 + overshoot);
|
||||
let start_summary = summary.clone();
|
||||
|
||||
cursor.seek_forward(&VersionedFullOffset::Offset(*end_offset), *end_bias, &cx);
|
||||
|
@ -1794,7 +1796,7 @@ impl<'a> Content<'a> {
|
|||
} else {
|
||||
0
|
||||
};
|
||||
summary += rope_cursor.summary(cursor.start().1 + overshoot);
|
||||
summary += rope_cursor.summary::<TextSummary>(cursor.start().1 + overshoot);
|
||||
let end_summary = summary.clone();
|
||||
|
||||
(start_summary..end_summary, value)
|
||||
|
@ -1921,6 +1923,10 @@ impl<'a> Content<'a> {
|
|||
self.visible_text.clip_point(point, bias)
|
||||
}
|
||||
|
||||
pub fn clip_point_utf16(&self, point: PointUtf16, bias: Bias) -> PointUtf16 {
|
||||
self.visible_text.clip_point_utf16(point, bias)
|
||||
}
|
||||
|
||||
fn point_for_offset(&self, offset: usize) -> Result<Point> {
|
||||
if offset <= self.len() {
|
||||
Ok(self.text_summary_for_range(0..offset).lines)
|
||||
|
@ -1930,9 +1936,12 @@ impl<'a> Content<'a> {
|
|||
}
|
||||
|
||||
// TODO: take a reference to clock::Global.
|
||||
pub fn edits_since(&self, since: clock::Global) -> impl 'a + Iterator<Item = Edit> {
|
||||
pub fn edits_since<D>(&self, since: clock::Global) -> impl 'a + Iterator<Item = Edit<D>>
|
||||
where
|
||||
D: 'a + TextDimension<'a> + Ord,
|
||||
{
|
||||
let since_2 = since.clone();
|
||||
let cursor = if since == *self.version {
|
||||
let fragments_cursor = if since == *self.version {
|
||||
None
|
||||
} else {
|
||||
Some(self.fragments.filter(
|
||||
|
@ -1942,15 +1951,13 @@ impl<'a> Content<'a> {
|
|||
};
|
||||
|
||||
Edits {
|
||||
visible_text: &self.visible_text,
|
||||
deleted_text: &self.deleted_text,
|
||||
cursor,
|
||||
visible_cursor: self.visible_text.cursor(0),
|
||||
deleted_cursor: self.deleted_text.cursor(0),
|
||||
fragments_cursor,
|
||||
undos: &self.undo_map,
|
||||
since,
|
||||
old_offset: 0,
|
||||
new_offset: 0,
|
||||
old_point: Point::zero(),
|
||||
new_point: Point::zero(),
|
||||
old_end: Default::default(),
|
||||
new_end: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2008,70 +2015,61 @@ impl<'a> RopeBuilder<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, F: FnMut(&FragmentSummary) -> bool> Iterator for Edits<'a, F> {
|
||||
type Item = Edit;
|
||||
impl<'a, D: TextDimension<'a> + Ord, F: FnMut(&FragmentSummary) -> bool> Iterator
|
||||
for Edits<'a, D, F>
|
||||
{
|
||||
type Item = Edit<D>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mut change: Option<Edit> = None;
|
||||
let cursor = self.cursor.as_mut()?;
|
||||
let mut pending_edit: Option<Edit<D>> = None;
|
||||
let cursor = self.fragments_cursor.as_mut()?;
|
||||
|
||||
while let Some(fragment) = cursor.item() {
|
||||
let bytes = cursor.start().visible - self.new_offset;
|
||||
let lines = self.visible_text.to_point(cursor.start().visible) - self.new_point;
|
||||
self.old_offset += bytes;
|
||||
self.old_point += &lines;
|
||||
self.new_offset += bytes;
|
||||
self.new_point += &lines;
|
||||
let summary = self.visible_cursor.summary(cursor.start().visible);
|
||||
self.old_end.add_assign(&summary);
|
||||
self.new_end.add_assign(&summary);
|
||||
if pending_edit
|
||||
.as_ref()
|
||||
.map_or(false, |change| change.new.end < self.new_end)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if !fragment.was_visible(&self.since, &self.undos) && fragment.visible {
|
||||
let fragment_lines =
|
||||
self.visible_text.to_point(self.new_offset + fragment.len) - self.new_point;
|
||||
if let Some(ref mut change) = change {
|
||||
if change.new_bytes.end == self.new_offset {
|
||||
change.new_bytes.end += fragment.len;
|
||||
change.new_lines.end += fragment_lines;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
let fragment_summary = self.visible_cursor.summary(cursor.end(&None).visible);
|
||||
let mut new_end = self.new_end.clone();
|
||||
new_end.add_assign(&fragment_summary);
|
||||
if let Some(pending_edit) = pending_edit.as_mut() {
|
||||
pending_edit.new.end = new_end.clone();
|
||||
} else {
|
||||
change = Some(Edit {
|
||||
old_bytes: self.old_offset..self.old_offset,
|
||||
new_bytes: self.new_offset..self.new_offset + fragment.len,
|
||||
old_lines: self.old_point..self.old_point,
|
||||
new_lines: self.new_point..self.new_point + fragment_lines,
|
||||
pending_edit = Some(Edit {
|
||||
old: self.old_end.clone()..self.old_end.clone(),
|
||||
new: self.new_end.clone()..new_end.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
self.new_offset += fragment.len;
|
||||
self.new_point += &fragment_lines;
|
||||
self.new_end = new_end;
|
||||
} else if fragment.was_visible(&self.since, &self.undos) && !fragment.visible {
|
||||
let deleted_start = cursor.start().deleted;
|
||||
let fragment_lines = self.deleted_text.to_point(deleted_start + fragment.len)
|
||||
- self.deleted_text.to_point(deleted_start);
|
||||
if let Some(ref mut change) = change {
|
||||
if change.new_bytes.end == self.new_offset {
|
||||
change.old_bytes.end += fragment.len;
|
||||
change.old_lines.end += &fragment_lines;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
self.deleted_cursor.seek_forward(cursor.start().deleted);
|
||||
let fragment_summary = self.deleted_cursor.summary(cursor.end(&None).deleted);
|
||||
let mut old_end = self.old_end.clone();
|
||||
old_end.add_assign(&fragment_summary);
|
||||
if let Some(pending_edit) = pending_edit.as_mut() {
|
||||
pending_edit.old.end = old_end.clone();
|
||||
} else {
|
||||
change = Some(Edit {
|
||||
old_bytes: self.old_offset..self.old_offset + fragment.len,
|
||||
new_bytes: self.new_offset..self.new_offset,
|
||||
old_lines: self.old_point..self.old_point + &fragment_lines,
|
||||
new_lines: self.new_point..self.new_point,
|
||||
pending_edit = Some(Edit {
|
||||
old: self.old_end.clone()..old_end.clone(),
|
||||
new: self.new_end.clone()..self.new_end.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
self.old_offset += fragment.len;
|
||||
self.old_point += &fragment_lines;
|
||||
self.old_end = old_end;
|
||||
}
|
||||
|
||||
cursor.next(&None);
|
||||
}
|
||||
|
||||
change
|
||||
pending_edit
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2531,7 +2529,13 @@ pub trait ToOffset {
|
|||
|
||||
impl ToOffset for Point {
|
||||
fn to_offset<'a>(&self, content: impl Into<Content<'a>>) -> usize {
|
||||
content.into().visible_text.to_offset(*self)
|
||||
content.into().visible_text.point_to_offset(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOffset for PointUtf16 {
|
||||
fn to_offset<'a>(&self, content: impl Into<Content<'a>>) -> usize {
|
||||
content.into().visible_text.point_utf16_to_offset(*self)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2566,7 +2570,7 @@ impl ToPoint for Anchor {
|
|||
|
||||
impl ToPoint for usize {
|
||||
fn to_point<'a>(&self, content: impl Into<Content<'a>>) -> Point {
|
||||
content.into().visible_text.to_point(*self)
|
||||
content.into().visible_text.offset_to_point(*self)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,14 @@ impl<'a> Add<&'a Self> for Point {
|
|||
type Output = Point;
|
||||
|
||||
fn add(self, other: &'a Self) -> Self::Output {
|
||||
self + *other
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Point {
|
||||
type Output = Point;
|
||||
|
||||
fn add(self, other: Self) -> Self::Output {
|
||||
if other.row == 0 {
|
||||
Point::new(self.row, self.column + other.column)
|
||||
} else {
|
||||
|
@ -40,25 +48,11 @@ impl<'a> Add<&'a Self> for Point {
|
|||
}
|
||||
}
|
||||
|
||||
impl Add for Point {
|
||||
type Output = Point;
|
||||
|
||||
fn add(self, other: Self) -> Self::Output {
|
||||
self + &other
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Sub<&'a Self> for Point {
|
||||
type Output = Point;
|
||||
|
||||
fn sub(self, other: &'a Self) -> Self::Output {
|
||||
debug_assert!(*other <= self);
|
||||
|
||||
if self.row == other.row {
|
||||
Point::new(0, self.column - other.column)
|
||||
} else {
|
||||
Point::new(self.row - other.row, self.column)
|
||||
}
|
||||
self - *other
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,7 +60,13 @@ impl Sub for Point {
|
|||
type Output = Point;
|
||||
|
||||
fn sub(self, other: Self) -> Self::Output {
|
||||
self - &other
|
||||
debug_assert!(other <= self);
|
||||
|
||||
if self.row == other.row {
|
||||
Point::new(0, self.column - other.column)
|
||||
} else {
|
||||
Point::new(self.row - other.row, self.column)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
111
crates/buffer/src/point_utf16.rs
Normal file
111
crates/buffer/src/point_utf16.rs
Normal file
|
@ -0,0 +1,111 @@
|
|||
use std::{
|
||||
cmp::Ordering,
|
||||
ops::{Add, AddAssign, Sub},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash)]
|
||||
pub struct PointUtf16 {
|
||||
pub row: u32,
|
||||
pub column: u32,
|
||||
}
|
||||
|
||||
impl PointUtf16 {
|
||||
pub const MAX: Self = Self {
|
||||
row: u32::MAX,
|
||||
column: u32::MAX,
|
||||
};
|
||||
|
||||
pub fn new(row: u32, column: u32) -> Self {
|
||||
PointUtf16 { row, column }
|
||||
}
|
||||
|
||||
pub fn zero() -> Self {
|
||||
PointUtf16::new(0, 0)
|
||||
}
|
||||
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.row == 0 && self.column == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Add<&'a Self> for PointUtf16 {
|
||||
type Output = PointUtf16;
|
||||
|
||||
fn add(self, other: &'a Self) -> Self::Output {
|
||||
self + *other
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for PointUtf16 {
|
||||
type Output = PointUtf16;
|
||||
|
||||
fn add(self, other: Self) -> Self::Output {
|
||||
if other.row == 0 {
|
||||
PointUtf16::new(self.row, self.column + other.column)
|
||||
} else {
|
||||
PointUtf16::new(self.row + other.row, other.column)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Sub<&'a Self> for PointUtf16 {
|
||||
type Output = PointUtf16;
|
||||
|
||||
fn sub(self, other: &'a Self) -> Self::Output {
|
||||
self - *other
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for PointUtf16 {
|
||||
type Output = PointUtf16;
|
||||
|
||||
fn sub(self, other: Self) -> Self::Output {
|
||||
debug_assert!(other <= self);
|
||||
|
||||
if self.row == other.row {
|
||||
PointUtf16::new(0, self.column - other.column)
|
||||
} else {
|
||||
PointUtf16::new(self.row - other.row, self.column)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AddAssign<&'a Self> for PointUtf16 {
|
||||
fn add_assign(&mut self, other: &'a Self) {
|
||||
*self += *other;
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<Self> for PointUtf16 {
|
||||
fn add_assign(&mut self, other: Self) {
|
||||
if other.row == 0 {
|
||||
self.column += other.column;
|
||||
} else {
|
||||
self.row += other.row;
|
||||
self.column = other.column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for PointUtf16 {
|
||||
fn partial_cmp(&self, other: &PointUtf16) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for PointUtf16 {
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
fn cmp(&self, other: &PointUtf16) -> Ordering {
|
||||
let a = (self.row as usize) << 32 | self.column as usize;
|
||||
let b = (other.row as usize) << 32 | other.column as usize;
|
||||
a.cmp(&b)
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
fn cmp(&self, other: &PointUtf16) -> Ordering {
|
||||
match self.row.cmp(&other.row) {
|
||||
Ordering::Equal => self.column.cmp(&other.column),
|
||||
comparison @ _ => comparison,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
use crate::PointUtf16;
|
||||
|
||||
use super::Point;
|
||||
use arrayvec::ArrayString;
|
||||
use smallvec::SmallVec;
|
||||
use std::{cmp, ops::Range, str};
|
||||
use sum_tree::{Bias, SumTree};
|
||||
use sum_tree::{Bias, Dimension, SumTree};
|
||||
|
||||
#[cfg(test)]
|
||||
const CHUNK_BASE: usize = 6;
|
||||
|
@ -136,7 +138,7 @@ impl Rope {
|
|||
Chunks::new(self, range, true)
|
||||
}
|
||||
|
||||
pub fn to_point(&self, offset: usize) -> Point {
|
||||
pub fn offset_to_point(&self, offset: usize) -> Point {
|
||||
assert!(offset <= self.summary().bytes);
|
||||
let mut cursor = self.chunks.cursor::<(usize, Point)>();
|
||||
cursor.seek(&offset, Bias::Left, &());
|
||||
|
@ -144,15 +146,40 @@ impl Rope {
|
|||
cursor.start().1
|
||||
+ cursor
|
||||
.item()
|
||||
.map_or(Point::zero(), |chunk| chunk.to_point(overshoot))
|
||||
.map_or(Point::zero(), |chunk| chunk.offset_to_point(overshoot))
|
||||
}
|
||||
|
||||
pub fn to_offset(&self, point: Point) -> usize {
|
||||
pub fn offset_to_point_utf16(&self, offset: usize) -> PointUtf16 {
|
||||
assert!(offset <= self.summary().bytes);
|
||||
let mut cursor = self.chunks.cursor::<(usize, PointUtf16)>();
|
||||
cursor.seek(&offset, Bias::Left, &());
|
||||
let overshoot = offset - cursor.start().0;
|
||||
cursor.start().1
|
||||
+ cursor.item().map_or(PointUtf16::zero(), |chunk| {
|
||||
chunk.offset_to_point_utf16(overshoot)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn point_to_offset(&self, point: Point) -> usize {
|
||||
assert!(point <= self.summary().lines);
|
||||
let mut cursor = self.chunks.cursor::<(Point, usize)>();
|
||||
cursor.seek(&point, Bias::Left, &());
|
||||
let overshoot = point - cursor.start().0;
|
||||
cursor.start().1 + cursor.item().map_or(0, |chunk| chunk.to_offset(overshoot))
|
||||
cursor.start().1
|
||||
+ cursor
|
||||
.item()
|
||||
.map_or(0, |chunk| chunk.point_to_offset(overshoot))
|
||||
}
|
||||
|
||||
pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize {
|
||||
assert!(point <= self.summary().lines_utf16);
|
||||
let mut cursor = self.chunks.cursor::<(PointUtf16, usize)>();
|
||||
cursor.seek(&point, Bias::Left, &());
|
||||
let overshoot = point - cursor.start().0;
|
||||
cursor.start().1
|
||||
+ cursor
|
||||
.item()
|
||||
.map_or(0, |chunk| chunk.point_utf16_to_offset(overshoot))
|
||||
}
|
||||
|
||||
pub fn clip_offset(&self, mut offset: usize, bias: Bias) -> usize {
|
||||
|
@ -188,6 +215,17 @@ impl Rope {
|
|||
self.summary().lines
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clip_point_utf16(&self, point: PointUtf16, bias: Bias) -> PointUtf16 {
|
||||
let mut cursor = self.chunks.cursor::<PointUtf16>();
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
if let Some(chunk) = cursor.item() {
|
||||
let overshoot = point - cursor.start();
|
||||
*cursor.start() + chunk.clip_point_utf16(overshoot, bias)
|
||||
} else {
|
||||
self.summary().lines_utf16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Rope {
|
||||
|
@ -258,22 +296,24 @@ impl<'a> Cursor<'a> {
|
|||
slice
|
||||
}
|
||||
|
||||
pub fn summary(&mut self, end_offset: usize) -> TextSummary {
|
||||
pub fn summary<D: TextDimension<'a>>(&mut self, end_offset: usize) -> D {
|
||||
debug_assert!(end_offset >= self.offset);
|
||||
|
||||
let mut summary = TextSummary::default();
|
||||
let mut summary = D::default();
|
||||
if let Some(start_chunk) = self.chunks.item() {
|
||||
let start_ix = self.offset - self.chunks.start();
|
||||
let end_ix = cmp::min(end_offset, self.chunks.end(&())) - self.chunks.start();
|
||||
summary = TextSummary::from(&start_chunk.0[start_ix..end_ix]);
|
||||
summary.add_assign(&D::from_summary(&TextSummary::from(
|
||||
&start_chunk.0[start_ix..end_ix],
|
||||
)));
|
||||
}
|
||||
|
||||
if end_offset > self.chunks.end(&()) {
|
||||
self.chunks.next(&());
|
||||
summary += &self.chunks.summary(&end_offset, Bias::Right, &());
|
||||
summary.add_assign(&self.chunks.summary(&end_offset, Bias::Right, &()));
|
||||
if let Some(end_chunk) = self.chunks.item() {
|
||||
let end_ix = end_offset - self.chunks.start();
|
||||
summary += TextSummary::from(&end_chunk.0[..end_ix]);
|
||||
summary.add_assign(&D::from_summary(&TextSummary::from(&end_chunk.0[..end_ix])));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -375,7 +415,7 @@ impl<'a> Iterator for Chunks<'a> {
|
|||
struct Chunk(ArrayString<{ 2 * CHUNK_BASE }>);
|
||||
|
||||
impl Chunk {
|
||||
fn to_point(&self, target: usize) -> Point {
|
||||
fn offset_to_point(&self, target: usize) -> Point {
|
||||
let mut offset = 0;
|
||||
let mut point = Point::new(0, 0);
|
||||
for ch in self.0.chars() {
|
||||
|
@ -394,7 +434,26 @@ impl Chunk {
|
|||
point
|
||||
}
|
||||
|
||||
fn to_offset(&self, target: Point) -> usize {
|
||||
fn offset_to_point_utf16(&self, target: usize) -> PointUtf16 {
|
||||
let mut offset = 0;
|
||||
let mut point = PointUtf16::new(0, 0);
|
||||
for ch in self.0.chars() {
|
||||
if offset >= target {
|
||||
break;
|
||||
}
|
||||
|
||||
if ch == '\n' {
|
||||
point.row += 1;
|
||||
point.column = 0;
|
||||
} else {
|
||||
point.column += ch.len_utf16() as u32;
|
||||
}
|
||||
offset += ch.len_utf8();
|
||||
}
|
||||
point
|
||||
}
|
||||
|
||||
fn point_to_offset(&self, target: Point) -> usize {
|
||||
let mut offset = 0;
|
||||
let mut point = Point::new(0, 0);
|
||||
for ch in self.0.chars() {
|
||||
|
@ -416,6 +475,28 @@ impl Chunk {
|
|||
offset
|
||||
}
|
||||
|
||||
fn point_utf16_to_offset(&self, target: PointUtf16) -> usize {
|
||||
let mut offset = 0;
|
||||
let mut point = PointUtf16::new(0, 0);
|
||||
for ch in self.0.chars() {
|
||||
if point >= target {
|
||||
if point > target {
|
||||
panic!("point {:?} is inside of character {:?}", target, ch);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if ch == '\n' {
|
||||
point.row += 1;
|
||||
point.column = 0;
|
||||
} else {
|
||||
point.column += ch.len_utf16() as u32;
|
||||
}
|
||||
offset += ch.len_utf8();
|
||||
}
|
||||
offset
|
||||
}
|
||||
|
||||
fn clip_point(&self, target: Point, bias: Bias) -> Point {
|
||||
for (row, line) in self.0.split('\n').enumerate() {
|
||||
if row == target.row as usize {
|
||||
|
@ -431,6 +512,23 @@ impl Chunk {
|
|||
}
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn clip_point_utf16(&self, target: PointUtf16, bias: Bias) -> PointUtf16 {
|
||||
for (row, line) in self.0.split('\n').enumerate() {
|
||||
if row == target.row as usize {
|
||||
let mut code_units = line.encode_utf16();
|
||||
let mut column = code_units.by_ref().take(target.column as usize).count();
|
||||
if char::decode_utf16(code_units).next().transpose().is_err() {
|
||||
match bias {
|
||||
Bias::Left => column -= 1,
|
||||
Bias::Right => column += 1,
|
||||
}
|
||||
}
|
||||
return PointUtf16::new(row as u32, column as u32);
|
||||
}
|
||||
}
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::Item for Chunk {
|
||||
|
@ -445,6 +543,7 @@ impl sum_tree::Item for Chunk {
|
|||
pub struct TextSummary {
|
||||
pub bytes: usize,
|
||||
pub lines: Point,
|
||||
pub lines_utf16: PointUtf16,
|
||||
pub first_line_chars: u32,
|
||||
pub last_line_chars: u32,
|
||||
pub longest_row: u32,
|
||||
|
@ -454,17 +553,19 @@ pub struct TextSummary {
|
|||
impl<'a> From<&'a str> for TextSummary {
|
||||
fn from(text: &'a str) -> Self {
|
||||
let mut lines = Point::new(0, 0);
|
||||
let mut lines_utf16 = PointUtf16::new(0, 0);
|
||||
let mut first_line_chars = 0;
|
||||
let mut last_line_chars = 0;
|
||||
let mut longest_row = 0;
|
||||
let mut longest_row_chars = 0;
|
||||
for c in text.chars() {
|
||||
if c == '\n' {
|
||||
lines.row += 1;
|
||||
lines.column = 0;
|
||||
lines += Point::new(1, 0);
|
||||
lines_utf16 += PointUtf16::new(1, 0);
|
||||
last_line_chars = 0;
|
||||
} else {
|
||||
lines.column += c.len_utf8() as u32;
|
||||
lines_utf16.column += c.len_utf16() as u32;
|
||||
last_line_chars += 1;
|
||||
}
|
||||
|
||||
|
@ -481,6 +582,7 @@ impl<'a> From<&'a str> for TextSummary {
|
|||
TextSummary {
|
||||
bytes: text.len(),
|
||||
lines,
|
||||
lines_utf16,
|
||||
first_line_chars,
|
||||
last_line_chars,
|
||||
longest_row,
|
||||
|
@ -520,7 +622,8 @@ impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
|
|||
}
|
||||
|
||||
self.bytes += other.bytes;
|
||||
self.lines += &other.lines;
|
||||
self.lines += other.lines;
|
||||
self.lines_utf16 += other.lines_utf16;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -530,15 +633,77 @@ impl std::ops::AddAssign<Self> for TextSummary {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait TextDimension<'a>: Dimension<'a, TextSummary> {
|
||||
fn from_summary(summary: &TextSummary) -> Self;
|
||||
fn add_assign(&mut self, other: &Self);
|
||||
}
|
||||
|
||||
impl<'a, D1: TextDimension<'a>, D2: TextDimension<'a>> TextDimension<'a> for (D1, D2) {
|
||||
fn from_summary(summary: &TextSummary) -> Self {
|
||||
(D1::from_summary(summary), D2::from_summary(summary))
|
||||
}
|
||||
|
||||
fn add_assign(&mut self, other: &Self) {
|
||||
self.0.add_assign(&other.0);
|
||||
self.1.add_assign(&other.1);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TextDimension<'a> for TextSummary {
|
||||
fn from_summary(summary: &TextSummary) -> Self {
|
||||
summary.clone()
|
||||
}
|
||||
|
||||
fn add_assign(&mut self, other: &Self) {
|
||||
*self += other;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TextSummary> for usize {
|
||||
fn add_summary(&mut self, summary: &'a TextSummary, _: &()) {
|
||||
*self += summary.bytes;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TextDimension<'a> for usize {
|
||||
fn from_summary(summary: &TextSummary) -> Self {
|
||||
summary.bytes
|
||||
}
|
||||
|
||||
fn add_assign(&mut self, other: &Self) {
|
||||
*self += other;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TextSummary> for Point {
|
||||
fn add_summary(&mut self, summary: &'a TextSummary, _: &()) {
|
||||
*self += &summary.lines;
|
||||
*self += summary.lines;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TextDimension<'a> for Point {
|
||||
fn from_summary(summary: &TextSummary) -> Self {
|
||||
summary.lines
|
||||
}
|
||||
|
||||
fn add_assign(&mut self, other: &Self) {
|
||||
*self += other;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TextSummary> for PointUtf16 {
|
||||
fn add_summary(&mut self, summary: &'a TextSummary, _: &()) {
|
||||
*self += summary.lines_utf16;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TextDimension<'a> for PointUtf16 {
|
||||
fn from_summary(summary: &TextSummary) -> Self {
|
||||
summary.lines_utf16
|
||||
}
|
||||
|
||||
fn add_assign(&mut self, other: &Self) {
|
||||
*self += other;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -577,6 +742,41 @@ mod tests {
|
|||
assert_eq!(rope.text(), text);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clip() {
|
||||
let rope = Rope::from("🧘");
|
||||
|
||||
assert_eq!(rope.clip_offset(1, Bias::Left), 0);
|
||||
assert_eq!(rope.clip_offset(1, Bias::Right), 4);
|
||||
assert_eq!(rope.clip_offset(5, Bias::Right), 4);
|
||||
|
||||
assert_eq!(
|
||||
rope.clip_point(Point::new(0, 1), Bias::Left),
|
||||
Point::new(0, 0)
|
||||
);
|
||||
assert_eq!(
|
||||
rope.clip_point(Point::new(0, 1), Bias::Right),
|
||||
Point::new(0, 4)
|
||||
);
|
||||
assert_eq!(
|
||||
rope.clip_point(Point::new(0, 5), Bias::Right),
|
||||
Point::new(0, 4)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
rope.clip_point_utf16(PointUtf16::new(0, 1), Bias::Left),
|
||||
PointUtf16::new(0, 0)
|
||||
);
|
||||
assert_eq!(
|
||||
rope.clip_point_utf16(PointUtf16::new(0, 1), Bias::Right),
|
||||
PointUtf16::new(0, 2)
|
||||
);
|
||||
assert_eq!(
|
||||
rope.clip_point_utf16(PointUtf16::new(0, 3), Bias::Right),
|
||||
PointUtf16::new(0, 2)
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
fn test_random(mut rng: StdRng) {
|
||||
let operations = env::var("OPERATIONS")
|
||||
|
@ -624,14 +824,33 @@ mod tests {
|
|||
}
|
||||
|
||||
let mut point = Point::new(0, 0);
|
||||
let mut point_utf16 = PointUtf16::new(0, 0);
|
||||
for (ix, ch) in expected.char_indices().chain(Some((expected.len(), '\0'))) {
|
||||
assert_eq!(actual.to_point(ix), point, "to_point({})", ix);
|
||||
assert_eq!(actual.to_offset(point), ix, "to_offset({:?})", point);
|
||||
assert_eq!(actual.offset_to_point(ix), point, "offset_to_point({})", ix);
|
||||
assert_eq!(
|
||||
actual.offset_to_point_utf16(ix),
|
||||
point_utf16,
|
||||
"offset_to_point_utf16({})",
|
||||
ix
|
||||
);
|
||||
assert_eq!(
|
||||
actual.point_to_offset(point),
|
||||
ix,
|
||||
"point_to_offset({:?})",
|
||||
point
|
||||
);
|
||||
assert_eq!(
|
||||
actual.point_utf16_to_offset(point_utf16),
|
||||
ix,
|
||||
"point_utf16_to_offset({:?})",
|
||||
point_utf16
|
||||
);
|
||||
if ch == '\n' {
|
||||
point.row += 1;
|
||||
point.column = 0
|
||||
point += Point::new(1, 0);
|
||||
point_utf16 += PointUtf16::new(1, 0);
|
||||
} else {
|
||||
point.column += ch.len_utf8() as u32;
|
||||
point_utf16.column += ch.len_utf16() as u32;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -639,7 +858,7 @@ mod tests {
|
|||
let end_ix = clip_offset(&expected, rng.gen_range(0..=expected.len()), Right);
|
||||
let start_ix = clip_offset(&expected, rng.gen_range(0..=end_ix), Left);
|
||||
assert_eq!(
|
||||
actual.cursor(start_ix).summary(end_ix),
|
||||
actual.cursor(start_ix).summary::<TextSummary>(end_ix),
|
||||
TextSummary::from(&expected[start_ix..end_ix])
|
||||
);
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ fn test_random_edits(mut rng: StdRng) {
|
|||
|
||||
for mut old_buffer in buffer_versions {
|
||||
let edits = buffer
|
||||
.edits_since(old_buffer.version.clone())
|
||||
.edits_since::<usize>(old_buffer.version.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
log::info!(
|
||||
|
@ -88,12 +88,12 @@ fn test_random_edits(mut rng: StdRng) {
|
|||
edits,
|
||||
);
|
||||
|
||||
let mut delta = 0_isize;
|
||||
for edit in edits {
|
||||
let old_start = (edit.old_bytes.start as isize + delta) as usize;
|
||||
let new_text: String = buffer.text_for_range(edit.new_bytes.clone()).collect();
|
||||
old_buffer.edit(Some(old_start..old_start + edit.deleted_bytes()), new_text);
|
||||
delta += edit.delta();
|
||||
let new_text: String = buffer.text_for_range(edit.new.clone()).collect();
|
||||
old_buffer.edit(
|
||||
Some(edit.new.start..edit.new.start + edit.old.len()),
|
||||
new_text,
|
||||
);
|
||||
}
|
||||
assert_eq!(old_buffer.text(), buffer.text());
|
||||
}
|
||||
|
@ -123,6 +123,7 @@ fn test_text_summary_for_range() {
|
|||
TextSummary {
|
||||
bytes: 2,
|
||||
lines: Point::new(1, 0),
|
||||
lines_utf16: PointUtf16::new(1, 0),
|
||||
first_line_chars: 1,
|
||||
last_line_chars: 0,
|
||||
longest_row: 0,
|
||||
|
@ -134,6 +135,7 @@ fn test_text_summary_for_range() {
|
|||
TextSummary {
|
||||
bytes: 11,
|
||||
lines: Point::new(3, 0),
|
||||
lines_utf16: PointUtf16::new(3, 0),
|
||||
first_line_chars: 1,
|
||||
last_line_chars: 0,
|
||||
longest_row: 2,
|
||||
|
@ -145,6 +147,7 @@ fn test_text_summary_for_range() {
|
|||
TextSummary {
|
||||
bytes: 20,
|
||||
lines: Point::new(4, 1),
|
||||
lines_utf16: PointUtf16::new(4, 1),
|
||||
first_line_chars: 2,
|
||||
last_line_chars: 1,
|
||||
longest_row: 3,
|
||||
|
@ -156,6 +159,7 @@ fn test_text_summary_for_range() {
|
|||
TextSummary {
|
||||
bytes: 22,
|
||||
lines: Point::new(4, 3),
|
||||
lines_utf16: PointUtf16::new(4, 3),
|
||||
first_line_chars: 2,
|
||||
last_line_chars: 3,
|
||||
longest_row: 3,
|
||||
|
@ -167,6 +171,7 @@ fn test_text_summary_for_range() {
|
|||
TextSummary {
|
||||
bytes: 15,
|
||||
lines: Point::new(2, 3),
|
||||
lines_utf16: PointUtf16::new(2, 3),
|
||||
first_line_chars: 4,
|
||||
last_line_chars: 3,
|
||||
longest_row: 1,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue