zed/src/editor/display_map.rs 🔗
@@ -1,6 +1,6 @@
mod fold_map;
mod tab_map;
-// mod wrap_map;
+mod wrap_map;
use super::{buffer, Anchor, Bias, Buffer, Point, ToOffset, ToPoint};
use fold_map::FoldMap;
Antonio Scandurra and Nathan Sobo created
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
zed/src/editor/display_map.rs | 2
zed/src/editor/display_map/fold_map.rs | 12
zed/src/editor/display_map/tab_map.rs | 115 +++++++++--
zed/src/editor/display_map/wrap_map.rs | 284 +++++++++++++++------------
4 files changed, 256 insertions(+), 157 deletions(-)
@@ -1,6 +1,6 @@
mod fold_map;
mod tab_map;
-// mod wrap_map;
+mod wrap_map;
use super::{buffer, Anchor, Bias, Buffer, Point, ToOffset, ToPoint};
use fold_map::FoldMap;
@@ -483,14 +483,14 @@ impl Snapshot {
summary
}
- pub fn len(&self) -> usize {
- self.transforms.summary().output.bytes
+ pub fn len(&self) -> OutputOffset {
+ OutputOffset(self.transforms.summary().output.bytes)
}
pub fn line_len(&self, row: u32) -> u32 {
let line_start = self.to_output_offset(OutputPoint::new(row, 0)).0;
let line_end = if row >= self.max_point().row() {
- self.len()
+ self.len().0
} else {
self.to_output_offset(OutputPoint::new(row + 1, 0)).0 - 1
};
@@ -1442,8 +1442,10 @@ mod tests {
}
for _ in 0..5 {
- let offset = snapshot
- .clip_offset(OutputOffset(rng.gen_range(0..=snapshot.len())), Bias::Right);
+ let offset = snapshot.clip_offset(
+ OutputOffset(rng.gen_range(0..=snapshot.len().0)),
+ Bias::Right,
+ );
assert_eq!(
snapshot.chunks_at(offset).collect::<String>(),
&expected_text[offset.0..],
@@ -47,6 +47,35 @@ pub struct Snapshot {
}
impl Snapshot {
+ pub fn text_summary(&self) -> TextSummary {
+ // TODO: expand tabs on first and last line, ignoring the longest row.
+ let summary = self.input.text_summary();
+ TextSummary {
+ lines: summary.lines,
+ first_line_chars: summary.first_line_chars,
+ last_line_chars: summary.last_line_chars,
+ longest_row: summary.longest_row,
+ longest_row_chars: summary.longest_row_chars,
+ }
+ }
+
+ pub fn text_summary_for_rows(&self, rows: Range<u32>) -> TextSummary {
+ // TODO: expand tabs on first and last line, ignoring the longest row.
+ let range = InputPoint::new(rows.start, 0)..InputPoint::new(rows.end, 0);
+ let summary = self.input.text_summary_for_range(range);
+ TextSummary {
+ lines: summary.lines,
+ first_line_chars: summary.first_line_chars,
+ last_line_chars: summary.last_line_chars,
+ longest_row: summary.longest_row,
+ longest_row_chars: summary.longest_row_chars,
+ }
+ }
+
+ pub fn version(&self) -> usize {
+ self.input.version
+ }
+
pub fn chunks_at(&self, point: OutputPoint) -> Chunks {
let (point, expanded_char_column, to_next_stop) = self.to_input_point(point, Bias::Left);
let fold_chunks = self.input.chunks_at(self.input.to_output_offset(point));
@@ -73,6 +102,15 @@ impl Snapshot {
}
}
+ #[cfg(test)]
+ pub fn text(&self) -> String {
+ self.chunks_at(Default::default()).collect()
+ }
+
+ pub fn len(&self) -> OutputOffset {
+ self.to_output_offset(self.input.len())
+ }
+
pub fn line_len(&self, row: u32) -> u32 {
self.to_output_point(InputPoint::new(row, self.input.line_len(row)))
.column()
@@ -184,27 +222,7 @@ impl Snapshot {
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub struct OutputOffset(usize);
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct Edit {
- pub old_bytes: Range<OutputOffset>,
- pub new_bytes: Range<OutputOffset>,
-}
-
-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.0 - self.old_bytes.start.0
- }
-
- pub fn inserted_bytes(&self) -> usize {
- self.new_bytes.end.0 - self.new_bytes.start.0
- }
-}
+pub struct OutputOffset(pub usize);
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
pub struct OutputPoint(super::Point);
@@ -235,6 +253,61 @@ impl OutputPoint {
}
}
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Edit {
+ pub old_bytes: Range<OutputOffset>,
+ pub new_bytes: Range<OutputOffset>,
+}
+
+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.0 - self.old_bytes.start.0
+ }
+
+ pub fn inserted_bytes(&self) -> usize {
+ self.new_bytes.end.0 - self.new_bytes.start.0
+ }
+}
+
+#[derive(Clone, Debug, Default, Eq, PartialEq)]
+pub struct TextSummary {
+ pub lines: super::Point,
+ pub first_line_chars: u32,
+ pub last_line_chars: u32,
+ pub longest_row: u32,
+ pub longest_row_chars: u32,
+}
+
+impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
+ fn add_assign(&mut self, other: &'a Self) {
+ let joined_chars = self.last_line_chars + other.first_line_chars;
+ if joined_chars > self.longest_row_chars {
+ self.longest_row = self.lines.row;
+ self.longest_row_chars = joined_chars;
+ }
+ if other.longest_row_chars > self.longest_row_chars {
+ self.longest_row = self.lines.row + other.longest_row;
+ self.longest_row_chars = other.longest_row_chars;
+ }
+
+ if self.lines.row == 0 {
+ self.first_line_chars += other.first_line_chars;
+ }
+
+ if other.lines.row == 0 {
+ self.last_line_chars += other.first_line_chars;
+ } else {
+ self.last_line_chars = other.last_line_chars;
+ }
+
+ self.lines += &other.lines;
+ }
+}
+
// Handles a tab width <= 16
const SPACES: &'static str = " ";
@@ -1,24 +1,22 @@
-use std::sync::Arc;
-
+use super::tab_map::{
+ Edit as InputEdit, OutputOffset as InputOffset, Snapshot as InputSnapshot, TextSummary,
+};
use crate::{
- editor::{
- display_map::fold_map::{self, DisplayOffset, FoldedPoint},
- Point, TextSummary,
- },
+ editor::Point,
sum_tree::{self, SumTree},
- util::Bias,
};
use gpui::{font_cache::FamilyId, AppContext, FontCache, FontSystem, Task};
use parking_lot::Mutex;
use postage::{prelude::Sink, watch};
use smol::channel;
+use std::sync::Arc;
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
-pub struct WrappedPoint(Point);
+pub struct OutputPoint(super::Point);
-impl WrappedPoint {
+impl OutputPoint {
pub fn new(row: u32, column: u32) -> Self {
- Self(Point::new(row, column))
+ Self(super::Point::new(row, column))
}
pub fn zero() -> Self {
@@ -42,13 +40,32 @@ impl WrappedPoint {
}
}
-#[derive(Clone, Default)]
+#[derive(Clone)]
pub struct Snapshot {
transforms: SumTree<Transform>,
- folds_snapshot: fold_map::Snapshot,
+ input: InputSnapshot,
version: usize,
}
+impl Snapshot {
+ fn new(input: InputSnapshot) -> Self {
+ Self {
+ transforms: SumTree::from_item(
+ Transform {
+ summary: TransformSummary {
+ input: input.text_summary(),
+ output: input.text_summary(),
+ },
+ display_text: None,
+ },
+ &(),
+ ),
+ version: input.version(),
+ input,
+ }
+ }
+}
+
struct State {
snapshot: Snapshot,
interpolated_version: usize,
@@ -63,38 +80,26 @@ pub struct Config {
pub struct WrapMap {
state: Mutex<State>,
- edits_tx: channel::Sender<(fold_map::Snapshot, Vec<fold_map::Edit>)>,
+ edits_tx: channel::Sender<(InputSnapshot, Vec<InputEdit>)>,
background_snapshots: watch::Receiver<Snapshot>,
_background_task: Task<()>,
}
impl WrapMap {
- pub fn new(folds_snapshot: fold_map::Snapshot, config: Config, cx: &AppContext) -> Self {
+ pub fn new(input: InputSnapshot, config: Config, cx: &AppContext) -> Self {
let font_cache = cx.font_cache().clone();
let font_system = cx.platform().fonts();
- let snapshot = Snapshot {
- transforms: SumTree::from_item(
- Transform {
- summary: TransformSummary {
- folded: folds_snapshot.text_summary(),
- wrapped: folds_snapshot.text_summary(),
- },
- display_text: None,
- },
- &(),
- ),
- folds_snapshot,
- version: folds_snapshot.version,
- };
+ let snapshot = Snapshot::new(input.clone());
let (background_snapshots_tx, background_snapshots_rx) =
watch::channel_with(snapshot.clone());
let (edits_tx, edits_rx) = channel::unbounded();
- let background_task = cx.background().spawn(async move {
- let mut wrapper = BackgroundWrapper::new(config, font_cache, font_system);
- wrapper
- .run(folds_snapshot, edits_rx, background_snapshots_tx)
- .await;
- });
+ let background_task = {
+ let snapshot = snapshot.clone();
+ cx.background().spawn(async move {
+ let mut wrapper = BackgroundWrapper::new(snapshot, config, font_cache, font_system);
+ wrapper.run(input, edits_rx, background_snapshots_tx).await;
+ })
+ };
Self {
state: Mutex::new(State {
@@ -107,9 +112,9 @@ impl WrapMap {
}
}
- pub fn sync(&self, folds_snapshot: fold_map::Snapshot, edits: Vec<fold_map::Edit>) -> Snapshot {
+ pub fn sync(&self, input: InputSnapshot, edits: Vec<InputEdit>) -> Snapshot {
// TODO: interpolate
- self.edits_tx.try_send((folds_snapshot, edits)).unwrap();
+ self.edits_tx.try_send((input, edits)).unwrap();
self.state.lock().snapshot.clone()
}
}
@@ -122,24 +127,29 @@ struct BackgroundWrapper {
}
impl BackgroundWrapper {
- fn new(config: Config, font_cache: Arc<FontCache>, font_system: Arc<dyn FontSystem>) -> Self {
+ fn new(
+ snapshot: Snapshot,
+ config: Config,
+ font_cache: Arc<FontCache>,
+ font_system: Arc<dyn FontSystem>,
+ ) -> Self {
Self {
config,
font_cache,
font_system,
- snapshot: Snapshot::default(),
+ snapshot,
}
}
async fn run(
&mut self,
- snapshot: fold_map::Snapshot,
- edits_rx: channel::Receiver<(fold_map::Snapshot, Vec<fold_map::Edit>)>,
+ snapshot: InputSnapshot,
+ edits_rx: channel::Receiver<(InputSnapshot, Vec<InputEdit>)>,
mut snapshots_tx: watch::Sender<Snapshot>,
) {
- let edit = fold_map::Edit {
- old_bytes: DisplayOffset(0)..DisplayOffset(0),
- new_bytes: DisplayOffset(0)..DisplayOffset(snapshot.len()),
+ let edit = InputEdit {
+ old_bytes: InputOffset(0)..InputOffset(0),
+ new_bytes: InputOffset(0)..snapshot.len(),
};
self.sync(snapshot, vec![edit]);
if snapshots_tx.send(self.snapshot.clone()).await.is_err() {
@@ -154,7 +164,7 @@ impl BackgroundWrapper {
}
}
- fn sync(&mut self, snapshot: fold_map::Snapshot, edits: Vec<fold_map::Edit>) {
+ fn sync(&mut self, snapshot: InputSnapshot, edits: Vec<InputEdit>) {
let font_id = self
.font_cache
.select_font(self.config.font_family, &Default::default());
@@ -163,76 +173,76 @@ impl BackgroundWrapper {
let mut new_transforms = SumTree::new();
{
- let mut old_cursor = self.snapshot.transforms.cursor::<DisplayPoint, ()>();
- let mut position = DisplayPoint::zero();
- for edit in edits {
- let old_start = DisplayPoint::new(
- edit.old_bytes.start.to_display_point(&self.snapshot).row(),
- 0,
- );
- let old_end = DisplayPoint::new(
- edit.old_bytes.end.to_display_point(&self.snapshot).row() + 1,
- 0,
- );
- let new_start =
- DisplayPoint::new(edit.new_bytes.start.to_display_point(&snapshot).row(), 0);
- let new_end =
- DisplayPoint::new(edit.new_bytes.end.to_display_point(&snapshot).row() + 1, 0);
-
- if position > old_cursor.seek_start() && old_start >= old_cursor.seek_end(&()) {
- old_cursor.next(&());
- }
-
- let prefix = old_cursor.slice(&old_start, Bias::Right, &());
- new_transforms.push_tree(prefix, &());
- new_transforms.push(
- Transform::isomorphic(
- self.snapshot
- .folds_snapshot
- .text_summary_for_range(position..old_start),
- ),
- &(),
- );
-
- let mut row = new_start.row();
- let mut line = String::new();
- 'outer: for chunk in snapshot.chunks_at(snapshot.to_display_offset(new_start)) {
- for (ix, line_chunk) in chunk.split('\n').enumerate() {
- if ix > 0 {
- let mut prev_boundary_ix = 0;
- for boundary_ix in self
- .font_system
- .wrap_line(&line, font_id, font_size, wrap_width)
- {
- let wrapped = &line[prev_boundary_ix..boundary_ix];
- new_transforms
- .push(Transform::isomorphic(TextSummary::from(wrapped)));
- new_transforms.push(Transform::newline());
- prev_boundary_ix = boundary_ix;
- }
-
- line.clear();
- row += 1;
- if row == new_end.row() {
- break 'outer;
- }
- }
-
- line.push_str(line_chunk);
- }
- }
-
- old_cursor.seek_forward(&old_end, Bias::Right, &());
- position = old_end;
- }
-
- if position > old_cursor.seek_start() && old_start >= old_cursor.seek_end(&()) {
- old_cursor.next(&());
- }
+ // let mut old_cursor = self.snapshot.transforms.cursor::<DisplayPoint, ()>();
+ // let mut position = DisplayPoint::zero();
+ // for edit in edits {
+ // let old_start = DisplayPoint::new(
+ // edit.old_bytes.start.to_display_point(&self.snapshot).row(),
+ // 0,
+ // );
+ // let old_end = DisplayPoint::new(
+ // edit.old_bytes.end.to_display_point(&self.snapshot).row() + 1,
+ // 0,
+ // );
+ // let new_start =
+ // DisplayPoint::new(edit.new_bytes.start.to_display_point(&snapshot).row(), 0);
+ // let new_end =
+ // DisplayPoint::new(edit.new_bytes.end.to_display_point(&snapshot).row() + 1, 0);
+
+ // if position > old_cursor.seek_start() && old_start >= old_cursor.seek_end(&()) {
+ // old_cursor.next(&());
+ // }
+
+ // let prefix = old_cursor.slice(&old_start, Bias::Right, &());
+ // new_transforms.push_tree(prefix, &());
+ // new_transforms.push(
+ // Transform::isomorphic(
+ // self.snapshot
+ // .folds_snapshot
+ // .text_summary_for_range(position..old_start),
+ // ),
+ // &(),
+ // );
+
+ // let mut row = new_start.row();
+ // let mut line = String::new();
+ // 'outer: for chunk in snapshot.chunks_at(snapshot.to_display_offset(new_start)) {
+ // for (ix, line_chunk) in chunk.split('\n').enumerate() {
+ // if ix > 0 {
+ // let mut prev_boundary_ix = 0;
+ // for boundary_ix in self
+ // .font_system
+ // .wrap_line(&line, font_id, font_size, wrap_width)
+ // {
+ // let wrapped = &line[prev_boundary_ix..boundary_ix];
+ // new_transforms
+ // .push(Transform::isomorphic(TextSummary::from(wrapped)));
+ // new_transforms.push(Transform::newline());
+ // prev_boundary_ix = boundary_ix;
+ // }
+
+ // line.clear();
+ // row += 1;
+ // if row == new_end.row() {
+ // break 'outer;
+ // }
+ // }
+
+ // line.push_str(line_chunk);
+ // }
+ // }
+
+ // old_cursor.seek_forward(&old_end, Bias::Right, &());
+ // position = old_end;
+ // }
+
+ // if position > old_cursor.seek_start() && old_start >= old_cursor.seek_end(&()) {
+ // old_cursor.next(&());
+ // }
}
self.snapshot.transforms = new_transforms;
- self.snapshot.version = snapshot.version;
+ self.snapshot.version = snapshot.version();
}
}
@@ -246,8 +256,8 @@ impl Transform {
fn isomorphic(summary: TextSummary) -> Self {
Self {
summary: TransformSummary {
- folded: summary.clone(),
- wrapped: summary,
+ input: summary.clone(),
+ output: summary,
},
display_text: None,
}
@@ -256,8 +266,14 @@ impl Transform {
fn newline() -> Self {
Self {
summary: TransformSummary {
- folded: TextSummary::default(),
- wrapped: TextSummary::from("\n"),
+ input: TextSummary::default(),
+ output: TextSummary {
+ lines: Point::new(1, 0),
+ first_line_chars: 0,
+ last_line_chars: 0,
+ longest_row: 0,
+ longest_row_chars: 0,
+ },
},
display_text: Some("\n"),
}
@@ -274,22 +290,22 @@ impl sum_tree::Item for Transform {
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct TransformSummary {
- folded: TextSummary,
- wrapped: TextSummary,
+ input: TextSummary,
+ output: TextSummary,
}
impl sum_tree::Summary for TransformSummary {
type Context = ();
fn add_summary(&mut self, other: &Self, _: &()) {
- self.folded += &other.folded;
- self.wrapped += &other.wrapped;
+ self.input += &other.input;
+ self.output += &other.output;
}
}
impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
- *self += &summary.folded.lines;
+ *self += &summary.input.lines;
}
}
@@ -297,12 +313,14 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
mod tests {
use super::*;
use crate::{
- editor::{display_map::fold_map::FoldMap, Buffer},
+ editor::{
+ display_map::{fold_map::FoldMap, tab_map::TabMap},
+ Buffer,
+ },
util::RandomCharIter,
};
use rand::prelude::*;
use std::env;
- use Bias::{Left, Right};
#[gpui::test]
fn test_random_wraps(cx: &mut gpui::MutableAppContext) {
@@ -329,7 +347,9 @@ mod tests {
Buffer::new(0, text, cx)
});
let fold_map = FoldMap::new(buffer.clone(), cx.as_ref());
- let (snapshot, _) = fold_map.read(cx.as_ref());
+ let (folds_snapshot, edits) = fold_map.read(cx.as_ref());
+ let tab_map = TabMap::new(folds_snapshot.clone(), rng.gen_range(1..=4));
+ let (tabs_snapshot, _) = tab_map.sync(folds_snapshot, edits);
let font_cache = cx.font_cache().clone();
let font_system = cx.platform().fonts();
let config = Config {
@@ -340,16 +360,20 @@ mod tests {
let font_id = font_cache
.select_font(config.font_family, &Default::default())
.unwrap();
- let mut wrapper =
- BackgroundWrapper::new(config.clone(), font_cache.clone(), font_system.clone());
- let edit = fold_map::Edit {
- old_bytes: DisplayOffset(0)..DisplayOffset(0),
- new_bytes: DisplayOffset(0)..DisplayOffset(snapshot.len()),
+ let mut wrapper = BackgroundWrapper::new(
+ Snapshot::new(tabs_snapshot.clone()),
+ config.clone(),
+ font_cache.clone(),
+ font_system.clone(),
+ );
+ let edit = InputEdit {
+ old_bytes: InputOffset(0)..InputOffset(0),
+ new_bytes: InputOffset(0)..tabs_snapshot.len(),
};
- wrapper.sync(snapshot.clone(), vec![edit]);
+ wrapper.sync(tabs_snapshot.clone(), vec![edit]);
let mut expected_text = String::new();
- for line in snapshot.text().lines() {
+ for line in tabs_snapshot.text().lines() {
let mut prev_ix = 0;
for ix in font_system.wrap_line(line, font_id, 14.0, config.wrap_width) {
expected_text.push_str(&line[prev_ix..ix]);