Cargo.lock š
@@ -1622,6 +1622,8 @@ dependencies = [
"anyhow",
"buffer",
"clock",
+ "ctor",
+ "env_logger",
"gpui",
"language",
"lazy_static",
Max Brunsfeld created
Allow full diagnostic messages to be displayed in the editor
Cargo.lock | 2
crates/buffer/src/anchor.rs | 98 +
crates/buffer/src/point.rs | 9
crates/buffer/src/rope.rs | 106 +
crates/buffer/src/selection.rs | 32
crates/editor/Cargo.toml | 2
crates/editor/src/display_map.rs | 337 +++-
crates/editor/src/display_map/block_map.rs | 1600 ++++++++++++++++++++++++
crates/editor/src/display_map/fold_map.rs | 132 -
crates/editor/src/display_map/patch.rs | 511 +++++++
crates/editor/src/display_map/tab_map.rs | 239 ++-
crates/editor/src/display_map/wrap_map.rs | 476 ++++--
crates/editor/src/element.rs | 155 +
crates/editor/src/lib.rs | 511 +++++-
crates/editor/src/movement.rs | 32
crates/editor/src/test.rs | 7
crates/gpui/src/fonts.rs | 2
crates/gpui/src/platform/mac/event.rs | 46
crates/language/src/lib.rs | 278 ++-
crates/language/src/proto.rs | 4
crates/language/src/tests.rs | 316 ++++
crates/project/src/worktree.rs | 4
crates/rpc/proto/zed.proto | 2
crates/server/src/rpc.rs | 6
crates/sum_tree/src/lib.rs | 6
crates/theme/src/lib.rs | 41
crates/workspace/src/items.rs | 7
crates/zed/assets/themes/_base.toml | 15
28 files changed, 4,157 insertions(+), 819 deletions(-)
@@ -1622,6 +1622,8 @@ dependencies = [
"anyhow",
"buffer",
"clock",
+ "ctor",
+ "env_logger",
"gpui",
"language",
"lazy_static",
@@ -194,6 +194,66 @@ impl<T> AnchorRangeMap<T> {
.iter()
.map(|(range, value)| (range.start.0..range.end.0, value))
}
+
+ pub fn min_by_key<'a, C, D, F, K>(
+ &self,
+ content: C,
+ mut extract_key: F,
+ ) -> Option<(Range<D>, &T)>
+ where
+ C: Into<Content<'a>>,
+ D: 'a + TextDimension<'a>,
+ F: FnMut(&T) -> K,
+ K: Ord,
+ {
+ let content = content.into();
+ self.entries
+ .iter()
+ .min_by_key(|(_, value)| extract_key(value))
+ .map(|(range, value)| (self.resolve_range(range, &content), value))
+ }
+
+ pub fn max_by_key<'a, C, D, F, K>(
+ &self,
+ content: C,
+ mut extract_key: F,
+ ) -> Option<(Range<D>, &T)>
+ where
+ C: Into<Content<'a>>,
+ D: 'a + TextDimension<'a>,
+ F: FnMut(&T) -> K,
+ K: Ord,
+ {
+ let content = content.into();
+ self.entries
+ .iter()
+ .max_by_key(|(_, value)| extract_key(value))
+ .map(|(range, value)| (self.resolve_range(range, &content), value))
+ }
+
+ fn resolve_range<'a, D>(
+ &self,
+ range: &Range<(FullOffset, Bias)>,
+ content: &Content<'a>,
+ ) -> Range<D>
+ where
+ D: 'a + TextDimension<'a>,
+ {
+ let (start, start_bias) = range.start;
+ let mut anchor = Anchor {
+ full_offset: start,
+ bias: start_bias,
+ version: self.version.clone(),
+ };
+ let start = content.summary_for_anchor(&anchor);
+
+ let (end, end_bias) = range.end;
+ anchor.full_offset = end;
+ anchor.bias = end_bias;
+ let end = content.summary_for_anchor(&anchor);
+
+ start..end
+ }
}
impl<T: PartialEq> PartialEq for AnchorRangeMap<T> {
@@ -354,6 +414,38 @@ impl<T: Clone> AnchorRangeMultimap<T> {
.cursor::<()>()
.map(|entry| (entry.range.start..entry.range.end, &entry.value))
}
+
+ pub fn filter<'a, O, F>(
+ &'a self,
+ content: Content<'a>,
+ mut f: F,
+ ) -> impl 'a + Iterator<Item = (usize, Range<O>, &T)>
+ where
+ O: FromAnchor,
+ F: 'a + FnMut(&'a T) -> bool,
+ {
+ let mut endpoint = Anchor {
+ full_offset: FullOffset(0),
+ bias: Bias::Left,
+ version: self.version.clone(),
+ };
+ self.entries
+ .cursor::<()>()
+ .enumerate()
+ .filter_map(move |(ix, entry)| {
+ if f(&entry.value) {
+ endpoint.full_offset = entry.range.start;
+ endpoint.bias = self.start_bias;
+ let start = O::from_anchor(&endpoint, &content);
+ endpoint.full_offset = entry.range.end;
+ endpoint.bias = self.end_bias;
+ let end = O::from_anchor(&endpoint, &content);
+ Some((ix, start..end, &entry.value))
+ } else {
+ None
+ }
+ })
+ }
}
impl<T: Clone> sum_tree::Item for AnchorRangeMultimapEntry<T> {
@@ -435,6 +527,7 @@ impl<'a> sum_tree::SeekTarget<'a, AnchorRangeMultimapSummary, FullOffsetRange> f
pub trait AnchorRangeExt {
fn cmp<'a>(&self, b: &Range<Anchor>, buffer: impl Into<Content<'a>>) -> Result<Ordering>;
+ fn to_offset<'a>(&self, content: impl Into<Content<'a>>) -> Range<usize>;
}
impl AnchorRangeExt for Range<Anchor> {
@@ -445,4 +538,9 @@ impl AnchorRangeExt for Range<Anchor> {
ord @ _ => ord,
})
}
+
+ fn to_offset<'a>(&self, content: impl Into<Content<'a>>) -> Range<usize> {
+ let content = content.into();
+ self.start.to_offset(&content)..self.end.to_offset(&content)
+ }
}
@@ -23,6 +23,15 @@ impl Point {
Point::new(0, 0)
}
+ pub fn from_str(s: &str) -> Self {
+ let mut point = Self::zero();
+ for (row, line) in s.split('\n').enumerate() {
+ point.row = row as u32;
+ point.column = line.len() as u32;
+ }
+ point
+ }
+
pub fn is_zero(&self) -> bool {
self.row == 0 && self.column == 0
}
@@ -3,7 +3,7 @@ use crate::PointUtf16;
use super::Point;
use arrayvec::ArrayString;
use smallvec::SmallVec;
-use std::{cmp, ops::Range, str};
+use std::{cmp, fmt, mem, ops::Range, str};
use sum_tree::{Bias, Dimension, SumTree};
#[cfg(test)]
@@ -38,6 +38,16 @@ impl Rope {
self.check_invariants();
}
+ pub fn replace(&mut self, range: Range<usize>, text: &str) {
+ let mut new_rope = Rope::new();
+ let mut cursor = self.cursor(0);
+ new_rope.append(cursor.slice(range.start));
+ cursor.seek_forward(range.end);
+ new_rope.push(text);
+ new_rope.append(cursor.suffix());
+ *self = new_rope;
+ }
+
pub fn push(&mut self, text: &str) {
let mut new_chunks = SmallVec::<[_; 16]>::new();
let mut new_chunk = ArrayString::new();
@@ -79,6 +89,11 @@ impl Rope {
self.check_invariants();
}
+ pub fn push_front(&mut self, text: &str) {
+ let suffix = mem::replace(self, Rope::from(text));
+ self.append(suffix);
+ }
+
fn check_invariants(&self) {
#[cfg(test)]
{
@@ -139,7 +154,9 @@ impl Rope {
}
pub fn offset_to_point(&self, offset: usize) -> Point {
- assert!(offset <= self.summary().bytes);
+ if offset >= self.summary().bytes {
+ return self.summary().lines;
+ }
let mut cursor = self.chunks.cursor::<(usize, Point)>();
cursor.seek(&offset, Bias::Left, &());
let overshoot = offset - cursor.start().0;
@@ -150,7 +167,9 @@ impl Rope {
}
pub fn offset_to_point_utf16(&self, offset: usize) -> PointUtf16 {
- assert!(offset <= self.summary().bytes);
+ if offset >= self.summary().bytes {
+ return self.summary().lines_utf16;
+ }
let mut cursor = self.chunks.cursor::<(usize, PointUtf16)>();
cursor.seek(&offset, Bias::Left, &());
let overshoot = offset - cursor.start().0;
@@ -161,7 +180,9 @@ impl Rope {
}
pub fn point_to_offset(&self, point: Point) -> usize {
- assert!(point <= self.summary().lines);
+ if point >= self.summary().lines {
+ return self.summary().bytes;
+ }
let mut cursor = self.chunks.cursor::<(Point, usize)>();
cursor.seek(&point, Bias::Left, &());
let overshoot = point - cursor.start().0;
@@ -172,7 +193,9 @@ impl Rope {
}
pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize {
- assert!(point <= self.summary().lines_utf16);
+ if point >= self.summary().lines_utf16 {
+ return self.summary().bytes;
+ }
let mut cursor = self.chunks.cursor::<(PointUtf16, usize)>();
cursor.seek(&point, Bias::Left, &());
let overshoot = point - cursor.start().0;
@@ -226,6 +249,11 @@ impl Rope {
self.summary().lines_utf16
}
}
+
+ pub fn line_len(&self, row: u32) -> u32 {
+ self.clip_point(Point::new(row, u32::MAX), Bias::Left)
+ .column
+ }
}
impl<'a> From<&'a str> for Rope {
@@ -236,9 +264,12 @@ impl<'a> From<&'a str> for Rope {
}
}
-impl Into<String> for Rope {
- fn into(self) -> String {
- self.chunks().collect()
+impl fmt::Display for Rope {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ for chunk in self.chunks() {
+ write!(f, "{}", chunk)?;
+ }
+ Ok(())
}
}
@@ -303,7 +334,7 @@ impl<'a> Cursor<'a> {
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.add_assign(&D::from_summary(&TextSummary::from(
+ summary.add_assign(&D::from_text_summary(&TextSummary::from(
&start_chunk.0[start_ix..end_ix],
)));
}
@@ -313,7 +344,9 @@ impl<'a> Cursor<'a> {
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.add_assign(&D::from_summary(&TextSummary::from(&end_chunk.0[..end_ix])));
+ summary.add_assign(&D::from_text_summary(&TextSummary::from(
+ &end_chunk.0[..end_ix],
+ )));
}
}
@@ -634,13 +667,16 @@ impl std::ops::AddAssign<Self> for TextSummary {
}
pub trait TextDimension<'a>: Dimension<'a, TextSummary> {
- fn from_summary(summary: &TextSummary) -> Self;
+ fn from_text_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 from_text_summary(summary: &TextSummary) -> Self {
+ (
+ D1::from_text_summary(summary),
+ D2::from_text_summary(summary),
+ )
}
fn add_assign(&mut self, other: &Self) {
@@ -650,7 +686,7 @@ impl<'a, D1: TextDimension<'a>, D2: TextDimension<'a>> TextDimension<'a> for (D1
}
impl<'a> TextDimension<'a> for TextSummary {
- fn from_summary(summary: &TextSummary) -> Self {
+ fn from_text_summary(summary: &TextSummary) -> Self {
summary.clone()
}
@@ -666,7 +702,7 @@ impl<'a> sum_tree::Dimension<'a, TextSummary> for usize {
}
impl<'a> TextDimension<'a> for usize {
- fn from_summary(summary: &TextSummary) -> Self {
+ fn from_text_summary(summary: &TextSummary) -> Self {
summary.bytes
}
@@ -682,7 +718,7 @@ impl<'a> sum_tree::Dimension<'a, TextSummary> for Point {
}
impl<'a> TextDimension<'a> for Point {
- fn from_summary(summary: &TextSummary) -> Self {
+ fn from_text_summary(summary: &TextSummary) -> Self {
summary.lines
}
@@ -698,7 +734,7 @@ impl<'a> sum_tree::Dimension<'a, TextSummary> for PointUtf16 {
}
impl<'a> TextDimension<'a> for PointUtf16 {
- fn from_summary(summary: &TextSummary) -> Self {
+ fn from_text_summary(summary: &TextSummary) -> Self {
summary.lines_utf16
}
@@ -731,7 +767,7 @@ mod tests {
use super::*;
use crate::random_char_iter::RandomCharIter;
use rand::prelude::*;
- use std::env;
+ use std::{cmp::Ordering, env};
use Bias::{Left, Right};
#[test]
@@ -778,7 +814,7 @@ mod tests {
}
#[gpui::test(iterations = 100)]
- fn test_random(mut rng: StdRng) {
+ fn test_random_rope(mut rng: StdRng) {
let operations = env::var("OPERATIONS")
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
.unwrap_or(10);
@@ -862,6 +898,38 @@ mod tests {
TextSummary::from(&expected[start_ix..end_ix])
);
}
+
+ let mut expected_longest_rows = Vec::new();
+ let mut longest_line_len = -1_isize;
+ for (row, line) in expected.split('\n').enumerate() {
+ let row = row as u32;
+ assert_eq!(
+ actual.line_len(row),
+ line.len() as u32,
+ "invalid line len for row {}",
+ row
+ );
+
+ let line_char_count = line.chars().count() as isize;
+ match line_char_count.cmp(&longest_line_len) {
+ Ordering::Less => {}
+ Ordering::Equal => expected_longest_rows.push(row),
+ Ordering::Greater => {
+ longest_line_len = line_char_count;
+ expected_longest_rows.clear();
+ expected_longest_rows.push(row);
+ }
+ }
+ }
+
+ let longest_row = actual.summary().longest_row;
+ assert!(
+ expected_longest_rows.contains(&longest_row),
+ "incorrect longest row {}. expected {:?} with length {}",
+ longest_row,
+ expected_longest_rows,
+ longest_line_len,
+ );
}
}
@@ -116,4 +116,36 @@ impl SelectionSet {
goal: state.goal,
})
}
+
+ pub fn oldest_selection<'a, D, C>(&'a self, content: C) -> Option<Selection<D>>
+ where
+ D: 'a + TextDimension<'a>,
+ C: 'a + Into<Content<'a>>,
+ {
+ self.selections
+ .min_by_key(content, |selection| selection.id)
+ .map(|(range, state)| Selection {
+ id: state.id,
+ start: range.start,
+ end: range.end,
+ reversed: state.reversed,
+ goal: state.goal,
+ })
+ }
+
+ pub fn newest_selection<'a, D, C>(&'a self, content: C) -> Option<Selection<D>>
+ where
+ D: 'a + TextDimension<'a>,
+ C: 'a + Into<Content<'a>>,
+ {
+ self.selections
+ .max_by_key(content, |selection| selection.id)
+ .map(|(range, state)| Selection {
+ id: state.id,
+ start: range.start,
+ end: range.end,
+ reversed: state.reversed,
+ goal: state.goal,
+ })
+ }
}
@@ -31,6 +31,8 @@ smol = "1.2"
buffer = { path = "../buffer", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
+ctor = "0.1"
+env_logger = "0.8"
rand = "0.8"
unindent = "0.1.7"
tree-sitter = "0.19"
@@ -1,18 +1,29 @@
+mod block_map;
mod fold_map;
+mod patch;
mod tab_map;
mod wrap_map;
+pub use block_map::{BlockDisposition, BlockId, BlockProperties, BufferRows, Chunks};
+use block_map::{BlockMap, BlockPoint};
+use buffer::Rope;
use fold_map::{FoldMap, ToFoldPoint as _};
-use gpui::{fonts::FontId, Entity, ModelContext, ModelHandle};
+use gpui::{
+ fonts::{FontId, HighlightStyle},
+ AppContext, Entity, ModelContext, ModelHandle,
+};
use language::{Anchor, Buffer, Point, ToOffset, ToPoint};
-use std::ops::Range;
+use std::{
+ collections::{HashMap, HashSet},
+ ops::Range,
+};
use sum_tree::Bias;
use tab_map::TabMap;
+use theme::{BlockStyle, SyntaxTheme};
use wrap_map::WrapMap;
-pub use wrap_map::{BufferRows, HighlightedChunks};
pub trait ToDisplayPoint {
- fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint;
+ fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint;
}
pub struct DisplayMap {
@@ -20,6 +31,7 @@ pub struct DisplayMap {
fold_map: FoldMap,
tab_map: TabMap,
wrap_map: ModelHandle<WrapMap>,
+ block_map: BlockMap,
}
impl Entity for DisplayMap {
@@ -37,28 +49,32 @@ impl DisplayMap {
) -> Self {
let (fold_map, snapshot) = FoldMap::new(buffer.clone(), cx);
let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
- let wrap_map =
- cx.add_model(|cx| WrapMap::new(snapshot, font_id, font_size, wrap_width, cx));
+ let (wrap_map, snapshot) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx);
+ let block_map = BlockMap::new(buffer.clone(), snapshot);
cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
DisplayMap {
buffer,
fold_map,
tab_map,
wrap_map,
+ block_map,
}
}
pub fn snapshot(&self, cx: &mut ModelContext<Self>) -> DisplayMapSnapshot {
let (folds_snapshot, edits) = self.fold_map.read(cx);
let (tabs_snapshot, edits) = self.tab_map.sync(folds_snapshot.clone(), edits);
- let wraps_snapshot = self
+ let (wraps_snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(tabs_snapshot.clone(), edits, cx));
+ let blocks_snapshot = self.block_map.read(wraps_snapshot.clone(), edits, cx);
+
DisplayMapSnapshot {
buffer_snapshot: self.buffer.read(cx).snapshot(),
folds_snapshot,
tabs_snapshot,
wraps_snapshot,
+ blocks_snapshot,
}
}
@@ -69,12 +85,16 @@ impl DisplayMap {
) {
let (mut fold_map, snapshot, edits) = self.fold_map.write(cx);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
- self.wrap_map
+ let (snapshot, edits) = self
+ .wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
+ self.block_map.read(snapshot, edits, cx);
let (snapshot, edits) = fold_map.fold(ranges, cx);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
- self.wrap_map
+ let (snapshot, edits) = self
+ .wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
+ self.block_map.read(snapshot, edits, cx);
}
pub fn unfold<T: ToOffset>(
@@ -84,12 +104,52 @@ impl DisplayMap {
) {
let (mut fold_map, snapshot, edits) = self.fold_map.write(cx);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
- self.wrap_map
+ let (snapshot, edits) = self
+ .wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
+ self.block_map.read(snapshot, edits, cx);
let (snapshot, edits) = fold_map.unfold(ranges, cx);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
- self.wrap_map
+ let (snapshot, edits) = self
+ .wrap_map
+ .update(cx, |map, cx| map.sync(snapshot, edits, cx));
+ self.block_map.read(snapshot, edits, cx);
+ }
+
+ pub fn insert_blocks<P, T>(
+ &mut self,
+ blocks: impl IntoIterator<Item = BlockProperties<P, T>>,
+ cx: &mut ModelContext<Self>,
+ ) -> Vec<BlockId>
+ where
+ P: ToOffset + Clone,
+ T: Into<Rope> + Clone,
+ {
+ let (snapshot, edits) = self.fold_map.read(cx);
+ let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
+ let (snapshot, edits) = self
+ .wrap_map
+ .update(cx, |map, cx| map.sync(snapshot, edits, cx));
+ let mut block_map = self.block_map.write(snapshot, edits, cx);
+ block_map.insert(blocks, cx)
+ }
+
+ pub fn restyle_blocks<F1, F2>(&mut self, styles: HashMap<BlockId, (Option<F1>, Option<F2>)>)
+ where
+ F1: 'static + Fn(&AppContext) -> Vec<(usize, HighlightStyle)>,
+ F2: 'static + Fn(&AppContext) -> BlockStyle,
+ {
+ self.block_map.restyle(styles);
+ }
+
+ pub fn remove_blocks(&mut self, ids: HashSet<BlockId>, cx: &mut ModelContext<Self>) {
+ let (snapshot, edits) = self.fold_map.read(cx);
+ let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
+ let (snapshot, edits) = self
+ .wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
+ let mut block_map = self.block_map.write(snapshot, edits, cx);
+ block_map.remove(ids, cx);
}
pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext<Self>) {
@@ -113,6 +173,7 @@ pub struct DisplayMapSnapshot {
folds_snapshot: fold_map::Snapshot,
tabs_snapshot: tab_map::Snapshot,
wraps_snapshot: wrap_map::Snapshot,
+ blocks_snapshot: block_map::BlockSnapshot,
}
impl DisplayMapSnapshot {
@@ -125,8 +186,8 @@ impl DisplayMapSnapshot {
self.buffer_snapshot.len() == 0
}
- pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
- self.wraps_snapshot.buffer_rows(start_row)
+ pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: Option<&'a AppContext>) -> BufferRows<'a> {
+ self.blocks_snapshot.buffer_rows(start_row, cx)
}
pub fn buffer_row_count(&self) -> u32 {
@@ -136,9 +197,9 @@ impl DisplayMapSnapshot {
pub fn prev_row_boundary(&self, mut display_point: DisplayPoint) -> (DisplayPoint, Point) {
loop {
*display_point.column_mut() = 0;
- let mut point = display_point.to_buffer_point(self, Bias::Left);
+ let mut point = display_point.to_point(self);
point.column = 0;
- let next_display_point = point.to_display_point(self, Bias::Left);
+ let next_display_point = self.point_to_display_point(point, Bias::Left);
if next_display_point == display_point {
return (display_point, point);
}
@@ -149,9 +210,9 @@ impl DisplayMapSnapshot {
pub fn next_row_boundary(&self, mut display_point: DisplayPoint) -> (DisplayPoint, Point) {
loop {
*display_point.column_mut() = self.line_len(display_point.row());
- let mut point = display_point.to_buffer_point(self, Bias::Right);
+ let mut point = display_point.to_point(self);
point.column = self.buffer_snapshot.line_len(point.row);
- let next_display_point = point.to_display_point(self, Bias::Right);
+ let next_display_point = self.point_to_display_point(point, Bias::Right);
if next_display_point == display_point {
return (display_point, point);
}
@@ -159,25 +220,46 @@ impl DisplayMapSnapshot {
}
}
+ fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint {
+ DisplayPoint(
+ self.blocks_snapshot.to_block_point(
+ self.wraps_snapshot.from_tab_point(
+ self.tabs_snapshot
+ .to_tab_point(point.to_fold_point(&self.folds_snapshot, bias)),
+ ),
+ ),
+ )
+ }
+
+ fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point {
+ let unblocked_point = self.blocks_snapshot.to_wrap_point(point.0);
+ let unwrapped_point = self.wraps_snapshot.to_tab_point(unblocked_point);
+ let unexpanded_point = self.tabs_snapshot.to_fold_point(unwrapped_point, bias).0;
+ unexpanded_point.to_buffer_point(&self.folds_snapshot)
+ }
+
pub fn max_point(&self) -> DisplayPoint {
- DisplayPoint(self.wraps_snapshot.max_point())
+ DisplayPoint(self.blocks_snapshot.max_point())
}
- pub fn chunks_at(&self, display_row: u32) -> wrap_map::Chunks {
- self.wraps_snapshot.chunks_at(display_row)
+ pub fn text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> {
+ self.blocks_snapshot
+ .chunks(display_row..self.max_point().row() + 1, None, None)
+ .map(|h| h.text)
}
- pub fn highlighted_chunks_for_rows(
- &mut self,
+ pub fn chunks<'a>(
+ &'a self,
display_rows: Range<u32>,
- ) -> wrap_map::HighlightedChunks {
- self.wraps_snapshot
- .highlighted_chunks_for_rows(display_rows)
+ theme: Option<&'a SyntaxTheme>,
+ cx: &'a AppContext,
+ ) -> block_map::Chunks<'a> {
+ self.blocks_snapshot.chunks(display_rows, theme, Some(cx))
}
pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator<Item = char> + 'a {
let mut column = 0;
- let mut chars = self.chunks_at(point.row()).flat_map(str::chars);
+ let mut chars = self.text_chunks(point.row()).flat_map(str::chars);
while column < point.column() {
if let Some(c) = chars.next() {
column += c.len_utf8() as u32;
@@ -215,7 +297,7 @@ impl DisplayMapSnapshot {
}
pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
- DisplayPoint(self.wraps_snapshot.clip_point(point.0, bias))
+ DisplayPoint(self.blocks_snapshot.clip_point(point.0, bias))
}
pub fn folds_in_range<'a, T>(
@@ -233,22 +315,31 @@ impl DisplayMapSnapshot {
}
pub fn is_line_folded(&self, display_row: u32) -> bool {
- let wrap_point = DisplayPoint::new(display_row, 0).0;
- let row = self.wraps_snapshot.to_tab_point(wrap_point).row();
- self.folds_snapshot.is_line_folded(row)
+ let block_point = BlockPoint(Point::new(display_row, 0));
+ let wrap_point = self.blocks_snapshot.to_wrap_point(block_point);
+ let tab_point = self.wraps_snapshot.to_tab_point(wrap_point);
+ self.folds_snapshot.is_line_folded(tab_point.row())
+ }
+
+ pub fn is_block_line(&self, display_row: u32) -> bool {
+ self.blocks_snapshot.is_block_line(display_row)
}
pub fn soft_wrap_indent(&self, display_row: u32) -> Option<u32> {
- self.wraps_snapshot.soft_wrap_indent(display_row)
+ let wrap_row = self
+ .blocks_snapshot
+ .to_wrap_point(BlockPoint::new(display_row, 0))
+ .row();
+ self.wraps_snapshot.soft_wrap_indent(wrap_row)
}
pub fn text(&self) -> String {
- self.chunks_at(0).collect()
+ self.text_chunks(0).collect()
}
pub fn line(&self, display_row: u32) -> String {
let mut result = String::new();
- for chunk in self.chunks_at(display_row) {
+ for chunk in self.text_chunks(display_row) {
if let Some(ix) = chunk.find('\n') {
result.push_str(&chunk[0..ix]);
break;
@@ -274,30 +365,20 @@ impl DisplayMapSnapshot {
}
pub fn line_len(&self, row: u32) -> u32 {
- self.wraps_snapshot.line_len(row)
+ self.blocks_snapshot.line_len(row)
}
pub fn longest_row(&self) -> u32 {
- self.wraps_snapshot.longest_row()
- }
-
- pub fn anchor_before(&self, point: DisplayPoint, bias: Bias) -> Anchor {
- self.buffer_snapshot
- .anchor_before(point.to_buffer_point(self, bias))
- }
-
- pub fn anchor_after(&self, point: DisplayPoint, bias: Bias) -> Anchor {
- self.buffer_snapshot
- .anchor_after(point.to_buffer_point(self, bias))
+ self.blocks_snapshot.longest_row()
}
}
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
-pub struct DisplayPoint(wrap_map::WrapPoint);
+pub struct DisplayPoint(BlockPoint);
impl DisplayPoint {
pub fn new(row: u32, column: u32) -> Self {
- Self(wrap_map::WrapPoint::new(row, column))
+ Self(BlockPoint(Point::new(row, column)))
}
pub fn zero() -> Self {
@@ -310,50 +391,52 @@ impl DisplayPoint {
}
pub fn row(self) -> u32 {
- self.0.row()
+ self.0.row
}
pub fn column(self) -> u32 {
- self.0.column()
+ self.0.column
}
pub fn row_mut(&mut self) -> &mut u32 {
- self.0.row_mut()
+ &mut self.0.row
}
pub fn column_mut(&mut self) -> &mut u32 {
- self.0.column_mut()
+ &mut self.0.column
}
- pub fn to_buffer_point(self, map: &DisplayMapSnapshot, bias: Bias) -> Point {
- let unwrapped_point = map.wraps_snapshot.to_tab_point(self.0);
- let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0;
- unexpanded_point.to_buffer_point(&map.folds_snapshot)
+ pub fn to_point(self, map: &DisplayMapSnapshot) -> Point {
+ map.display_point_to_point(self, Bias::Left)
}
- pub fn to_buffer_offset(self, map: &DisplayMapSnapshot, bias: Bias) -> usize {
- let unwrapped_point = map.wraps_snapshot.to_tab_point(self.0);
+ pub fn to_offset(self, map: &DisplayMapSnapshot, bias: Bias) -> usize {
+ let unblocked_point = map.blocks_snapshot.to_wrap_point(self.0);
+ let unwrapped_point = map.wraps_snapshot.to_tab_point(unblocked_point);
let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0;
unexpanded_point.to_buffer_offset(&map.folds_snapshot)
}
}
impl ToDisplayPoint for Point {
- fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint {
- let fold_point = self.to_fold_point(&map.folds_snapshot, bias);
- let tab_point = map.tabs_snapshot.to_tab_point(fold_point);
- let wrap_point = map.wraps_snapshot.to_wrap_point(tab_point);
- DisplayPoint(wrap_point)
+ fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint {
+ map.point_to_display_point(*self, Bias::Left)
}
}
impl ToDisplayPoint for Anchor {
- fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint {
- self.to_point(&map.buffer_snapshot)
- .to_display_point(map, bias)
+ fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint {
+ self.to_point(&map.buffer_snapshot).to_display_point(map)
}
}
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum DisplayRow {
+ Buffer(u32),
+ Block(BlockId, Option<BlockStyle>),
+ Wrap,
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -472,28 +555,28 @@ mod tests {
assert_eq!(
prev_display_bound,
- prev_buffer_bound.to_display_point(&snapshot, Left),
+ prev_buffer_bound.to_display_point(&snapshot),
"row boundary before {:?}. reported buffer row boundary: {:?}",
point,
prev_buffer_bound
);
assert_eq!(
next_display_bound,
- next_buffer_bound.to_display_point(&snapshot, Right),
+ next_buffer_bound.to_display_point(&snapshot),
"display row boundary after {:?}. reported buffer row boundary: {:?}",
point,
next_buffer_bound
);
assert_eq!(
prev_buffer_bound,
- prev_display_bound.to_buffer_point(&snapshot, Left),
+ prev_display_bound.to_point(&snapshot),
"row boundary before {:?}. reported display row boundary: {:?}",
point,
prev_display_bound
);
assert_eq!(
next_buffer_bound,
- next_display_bound.to_buffer_point(&snapshot, Right),
+ next_display_bound.to_point(&snapshot),
"row boundary after {:?}. reported display row boundary: {:?}",
point,
next_display_bound
@@ -559,7 +642,7 @@ mod tests {
let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
assert_eq!(
- snapshot.chunks_at(0).collect::<String>(),
+ snapshot.text_chunks(0).collect::<String>(),
"one two \nthree four \nfive\nsix seven \neight"
);
assert_eq!(
@@ -608,7 +691,7 @@ mod tests {
let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
assert_eq!(
- snapshot.chunks_at(1).collect::<String>(),
+ snapshot.text_chunks(1).collect::<String>(),
"three four \nfive\nsix and \nseven eight"
);
@@ -617,13 +700,13 @@ mod tests {
let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
assert_eq!(
- snapshot.chunks_at(1).collect::<String>(),
+ snapshot.text_chunks(1).collect::<String>(),
"three \nfour five\nsix and \nseven \neight"
)
}
#[gpui::test]
- fn test_chunks_at(cx: &mut gpui::MutableAppContext) {
+ fn test_text_chunks(cx: &mut gpui::MutableAppContext) {
let text = sample_text(6, 6);
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
let tab_size = 4;
@@ -650,7 +733,7 @@ mod tests {
assert_eq!(
map.update(cx, |map, cx| map.snapshot(cx))
- .chunks_at(1)
+ .text_chunks(1)
.collect::<String>()
.lines()
.next(),
@@ -658,7 +741,7 @@ mod tests {
);
assert_eq!(
map.update(cx, |map, cx| map.snapshot(cx))
- .chunks_at(2)
+ .text_chunks(2)
.collect::<String>()
.lines()
.next(),
@@ -667,7 +750,7 @@ mod tests {
}
#[gpui::test]
- async fn test_highlighted_chunks_at(mut cx: gpui::TestAppContext) {
+ async fn test_chunks(mut cx: gpui::TestAppContext) {
use unindent::Unindent as _;
let text = r#"
@@ -679,8 +762,8 @@ mod tests {
.unindent();
let theme = SyntaxTheme::new(vec![
- ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
- ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
+ ("mod.body".to_string(), Color::red().into()),
+ ("fn.name".to_string(), Color::blue().into()),
]);
let lang = Arc::new(
Language::new(
@@ -716,22 +799,22 @@ mod tests {
let map =
cx.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, cx));
assert_eq!(
- cx.update(|cx| highlighted_chunks(0..5, &map, &theme, cx)),
+ cx.update(|cx| chunks(0..5, &map, &theme, cx)),
vec![
("fn ".to_string(), None),
- ("outer".to_string(), Some("fn.name")),
+ ("outer".to_string(), Some(Color::blue())),
("() {}\n\nmod module ".to_string(), None),
- ("{\n fn ".to_string(), Some("mod.body")),
- ("inner".to_string(), Some("fn.name")),
- ("() {}\n}".to_string(), Some("mod.body")),
+ ("{\n fn ".to_string(), Some(Color::red())),
+ ("inner".to_string(), Some(Color::blue())),
+ ("() {}\n}".to_string(), Some(Color::red())),
]
);
assert_eq!(
- cx.update(|cx| highlighted_chunks(3..5, &map, &theme, cx)),
+ cx.update(|cx| chunks(3..5, &map, &theme, cx)),
vec![
- (" fn ".to_string(), Some("mod.body")),
- ("inner".to_string(), Some("fn.name")),
- ("() {}\n}".to_string(), Some("mod.body")),
+ (" fn ".to_string(), Some(Color::red())),
+ ("inner".to_string(), Some(Color::blue())),
+ ("() {}\n}".to_string(), Some(Color::red())),
]
);
@@ -739,20 +822,20 @@ mod tests {
map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
});
assert_eq!(
- cx.update(|cx| highlighted_chunks(0..2, &map, &theme, cx)),
+ cx.update(|cx| chunks(0..2, &map, &theme, cx)),
vec![
("fn ".to_string(), None),
- ("out".to_string(), Some("fn.name")),
+ ("out".to_string(), Some(Color::blue())),
("ā¦".to_string(), None),
- (" fn ".to_string(), Some("mod.body")),
- ("inner".to_string(), Some("fn.name")),
- ("() {}\n}".to_string(), Some("mod.body")),
+ (" fn ".to_string(), Some(Color::red())),
+ ("inner".to_string(), Some(Color::blue())),
+ ("() {}\n}".to_string(), Some(Color::red())),
]
);
}
#[gpui::test]
- async fn test_highlighted_chunks_with_soft_wrapping(mut cx: gpui::TestAppContext) {
+ async fn test_chunks_with_soft_wrapping(mut cx: gpui::TestAppContext) {
use unindent::Unindent as _;
cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
@@ -766,8 +849,8 @@ mod tests {
.unindent();
let theme = SyntaxTheme::new(vec![
- ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
- ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
+ ("mod.body".to_string(), Color::red().into()),
+ ("fn.name".to_string(), Color::blue().into()),
]);
let lang = Arc::new(
Language::new(
@@ -804,15 +887,15 @@ mod tests {
let map = cx
.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, Some(40.0), cx));
assert_eq!(
- cx.update(|cx| highlighted_chunks(0..5, &map, &theme, cx)),
+ cx.update(|cx| chunks(0..5, &map, &theme, cx)),
[
("fn \n".to_string(), None),
- ("oute\nr".to_string(), Some("fn.name")),
+ ("oute\nr".to_string(), Some(Color::blue())),
("() \n{}\n\n".to_string(), None),
]
);
assert_eq!(
- cx.update(|cx| highlighted_chunks(3..5, &map, &theme, cx)),
+ cx.update(|cx| chunks(3..5, &map, &theme, cx)),
[("{}\n\n".to_string(), None)]
);
@@ -820,12 +903,12 @@ mod tests {
map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
});
assert_eq!(
- cx.update(|cx| highlighted_chunks(1..4, &map, &theme, cx)),
+ cx.update(|cx| chunks(1..4, &map, &theme, cx)),
[
- ("out".to_string(), Some("fn.name")),
+ ("out".to_string(), Some(Color::blue())),
("ā¦\n".to_string(), None),
- (" \nfn ".to_string(), Some("mod.body")),
- ("i\n".to_string(), Some("fn.name"))
+ (" \nfn ".to_string(), Some(Color::red())),
+ ("i\n".to_string(), Some(Color::blue()))
]
);
}
@@ -895,42 +978,34 @@ mod tests {
let map = map.update(cx, |map, cx| map.snapshot(cx));
assert_eq!(map.text(), "ā
α\nβ \nšĪ² γ");
assert_eq!(
- map.chunks_at(0).collect::<String>(),
+ map.text_chunks(0).collect::<String>(),
"ā
α\nβ \nšĪ² γ"
);
- assert_eq!(map.chunks_at(1).collect::<String>(), "β \nšĪ² γ");
- assert_eq!(map.chunks_at(2).collect::<String>(), "šĪ² γ");
+ assert_eq!(map.text_chunks(1).collect::<String>(), "β \nšĪ² γ");
+ assert_eq!(map.text_chunks(2).collect::<String>(), "šĪ² γ");
let point = Point::new(0, "ā
\t\t".len() as u32);
let display_point = DisplayPoint::new(0, "ā
".len() as u32);
- assert_eq!(point.to_display_point(&map, Left), display_point);
- assert_eq!(display_point.to_buffer_point(&map, Left), point,);
+ assert_eq!(point.to_display_point(&map), display_point);
+ assert_eq!(display_point.to_point(&map), point);
let point = Point::new(1, "β\t".len() as u32);
let display_point = DisplayPoint::new(1, "β ".len() as u32);
- assert_eq!(point.to_display_point(&map, Left), display_point);
- assert_eq!(display_point.to_buffer_point(&map, Left), point,);
+ assert_eq!(point.to_display_point(&map), display_point);
+ assert_eq!(display_point.to_point(&map), point,);
let point = Point::new(2, "šĪ²\t\t".len() as u32);
let display_point = DisplayPoint::new(2, "šĪ² ".len() as u32);
- assert_eq!(point.to_display_point(&map, Left), display_point);
- assert_eq!(display_point.to_buffer_point(&map, Left), point,);
+ assert_eq!(point.to_display_point(&map), display_point);
+ assert_eq!(display_point.to_point(&map), point,);
// Display points inside of expanded tabs
assert_eq!(
- DisplayPoint::new(0, "ā
".len() as u32).to_buffer_point(&map, Right),
- Point::new(0, "ā
\t\t".len() as u32),
- );
- assert_eq!(
- DisplayPoint::new(0, "ā
".len() as u32).to_buffer_point(&map, Left),
- Point::new(0, "ā
\t".len() as u32),
- );
- assert_eq!(
- DisplayPoint::new(0, "ā
".len() as u32).to_buffer_point(&map, Right),
+ DisplayPoint::new(0, "ā
".len() as u32).to_point(&map),
Point::new(0, "ā
\t".len() as u32),
);
assert_eq!(
- DisplayPoint::new(0, "ā
".len() as u32).to_buffer_point(&map, Left),
+ DisplayPoint::new(0, "ā
".len() as u32).to_point(&map),
Point::new(0, "ā
".len() as u32),
);
@@ -964,24 +1039,24 @@ mod tests {
)
}
- fn highlighted_chunks<'a>(
+ fn chunks<'a>(
rows: Range<u32>,
map: &ModelHandle<DisplayMap>,
theme: &'a SyntaxTheme,
cx: &mut MutableAppContext,
- ) -> Vec<(String, Option<&'a str>)> {
- let mut snapshot = map.update(cx, |map, cx| map.snapshot(cx));
- let mut chunks: Vec<(String, Option<&str>)> = Vec::new();
- for chunk in snapshot.highlighted_chunks_for_rows(rows) {
- let style_name = chunk.highlight_id.name(theme);
- if let Some((last_chunk, last_style_name)) = chunks.last_mut() {
- if style_name == *last_style_name {
+ ) -> Vec<(String, Option<Color>)> {
+ let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
+ let mut chunks: Vec<(String, Option<Color>)> = Vec::new();
+ for chunk in snapshot.chunks(rows, Some(theme), cx) {
+ let color = chunk.highlight_style.map(|s| s.color);
+ if let Some((last_chunk, last_color)) = chunks.last_mut() {
+ if color == *last_color {
last_chunk.push_str(chunk.text);
} else {
- chunks.push((chunk.text.to_string(), style_name));
+ chunks.push((chunk.text.to_string(), color));
}
} else {
- chunks.push((chunk.text.to_string(), style_name));
+ chunks.push((chunk.text.to_string(), color));
}
}
chunks
@@ -0,0 +1,1600 @@
+use super::{
+ wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, WrapPoint},
+ BlockStyle, DisplayRow,
+};
+use buffer::{rope, Anchor, Bias, Edit, Point, Rope, ToOffset, ToPoint as _};
+use gpui::{fonts::HighlightStyle, AppContext, ModelHandle};
+use language::{Buffer, Chunk};
+use parking_lot::Mutex;
+use std::{
+ cmp::{self, Ordering},
+ collections::{HashMap, HashSet},
+ fmt::Debug,
+ iter,
+ ops::{Deref, Range},
+ sync::{
+ atomic::{AtomicUsize, Ordering::SeqCst},
+ Arc,
+ },
+ vec,
+};
+use sum_tree::SumTree;
+use theme::SyntaxTheme;
+
+pub struct BlockMap {
+ buffer: ModelHandle<Buffer>,
+ next_block_id: AtomicUsize,
+ wrap_snapshot: Mutex<WrapSnapshot>,
+ blocks: Vec<Arc<Block>>,
+ transforms: Mutex<SumTree<Transform>>,
+}
+
+pub struct BlockMapWriter<'a>(&'a mut BlockMap);
+
+pub struct BlockSnapshot {
+ wrap_snapshot: WrapSnapshot,
+ transforms: SumTree<Transform>,
+}
+
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct BlockId(usize);
+
+#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
+pub struct BlockPoint(pub super::Point);
+
+#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
+struct BlockRow(u32);
+
+#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
+struct WrapRow(u32);
+
+pub struct Block {
+ id: BlockId,
+ position: Anchor,
+ text: Rope,
+ build_runs: Mutex<Option<Arc<dyn Fn(&AppContext) -> Vec<(usize, HighlightStyle)>>>>,
+ build_style: Mutex<Option<Arc<dyn Fn(&AppContext) -> BlockStyle>>>,
+ disposition: BlockDisposition,
+}
+
+#[derive(Clone)]
+pub struct BlockProperties<P, T>
+where
+ P: Clone,
+ T: Clone,
+{
+ pub position: P,
+ pub text: T,
+ pub build_runs: Option<Arc<dyn Fn(&AppContext) -> Vec<(usize, HighlightStyle)>>>,
+ pub build_style: Option<Arc<dyn Fn(&AppContext) -> BlockStyle>>,
+ pub disposition: BlockDisposition,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub enum BlockDisposition {
+ Above,
+ Below,
+}
+
+#[derive(Clone, Debug)]
+struct Transform {
+ summary: TransformSummary,
+ block: Option<AlignedBlock>,
+}
+
+#[derive(Clone, Debug)]
+struct AlignedBlock {
+ block: Arc<Block>,
+ column: u32,
+}
+
+#[derive(Clone, Debug, Default)]
+struct TransformSummary {
+ input_rows: u32,
+ output_rows: u32,
+ longest_row_in_block: u32,
+ longest_row_in_block_chars: u32,
+}
+
+pub struct Chunks<'a> {
+ transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
+ input_chunks: wrap_map::Chunks<'a>,
+ input_chunk: Chunk<'a>,
+ block_chunks: Option<BlockChunks<'a>>,
+ output_row: u32,
+ max_output_row: u32,
+ cx: Option<&'a AppContext>,
+}
+
+struct BlockChunks<'a> {
+ chunks: rope::Chunks<'a>,
+ runs: iter::Peekable<vec::IntoIter<(usize, HighlightStyle)>>,
+ chunk: Option<&'a str>,
+ remaining_padding: u32,
+ padding_column: u32,
+ run_start: usize,
+ offset: usize,
+}
+
+pub struct BufferRows<'a> {
+ transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
+ input_buffer_rows: wrap_map::BufferRows<'a>,
+ output_row: u32,
+ cx: Option<&'a AppContext>,
+ started: bool,
+}
+
+impl BlockMap {
+ pub fn new(buffer: ModelHandle<Buffer>, wrap_snapshot: WrapSnapshot) -> Self {
+ Self {
+ buffer,
+ next_block_id: AtomicUsize::new(0),
+ blocks: Vec::new(),
+ transforms: Mutex::new(SumTree::from_item(
+ Transform::isomorphic(wrap_snapshot.text_summary().lines.row + 1),
+ &(),
+ )),
+ wrap_snapshot: Mutex::new(wrap_snapshot),
+ }
+ }
+
+ pub fn read(
+ &self,
+ wrap_snapshot: WrapSnapshot,
+ edits: Vec<WrapEdit>,
+ cx: &AppContext,
+ ) -> BlockSnapshot {
+ self.sync(&wrap_snapshot, edits, cx);
+ *self.wrap_snapshot.lock() = wrap_snapshot.clone();
+ BlockSnapshot {
+ wrap_snapshot,
+ transforms: self.transforms.lock().clone(),
+ }
+ }
+
+ pub fn write(
+ &mut self,
+ wrap_snapshot: WrapSnapshot,
+ edits: Vec<WrapEdit>,
+ cx: &AppContext,
+ ) -> BlockMapWriter {
+ self.sync(&wrap_snapshot, edits, cx);
+ *self.wrap_snapshot.lock() = wrap_snapshot;
+ BlockMapWriter(self)
+ }
+
+ fn sync(&self, wrap_snapshot: &WrapSnapshot, edits: Vec<WrapEdit>, cx: &AppContext) {
+ if edits.is_empty() {
+ return;
+ }
+
+ let buffer = self.buffer.read(cx);
+ let mut transforms = self.transforms.lock();
+ let mut new_transforms = SumTree::new();
+ let old_row_count = transforms.summary().input_rows;
+ let new_row_count = wrap_snapshot.max_point().row() + 1;
+ let mut cursor = transforms.cursor::<WrapRow>();
+ let mut last_block_ix = 0;
+ let mut blocks_in_edit = Vec::new();
+ let mut edits = edits.into_iter().peekable();
+
+ while let Some(edit) = edits.next() {
+ // Preserve any old transforms that precede this edit.
+ let old_start = WrapRow(edit.old.start);
+ let new_start = WrapRow(edit.new.start);
+ new_transforms.push_tree(cursor.slice(&old_start, Bias::Left, &()), &());
+ if let Some(transform) = cursor.item() {
+ if transform.is_isomorphic() && old_start == cursor.end(&()) {
+ new_transforms.push(transform.clone(), &());
+ cursor.next(&());
+ while let Some(transform) = cursor.item() {
+ if transform
+ .block
+ .as_ref()
+ .map_or(false, |b| b.disposition.is_below())
+ {
+ new_transforms.push(transform.clone(), &());
+ cursor.next(&());
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ // Preserve any portion of an old transform that precedes this edit.
+ let extent_before_edit = old_start.0 - cursor.start().0;
+ push_isomorphic(&mut new_transforms, extent_before_edit);
+
+ // Skip over any old transforms that intersect this edit.
+ let mut old_end = WrapRow(edit.old.end);
+ let mut new_end = WrapRow(edit.new.end);
+ cursor.seek(&old_end, Bias::Left, &());
+ cursor.next(&());
+ if old_end == *cursor.start() {
+ while let Some(transform) = cursor.item() {
+ if transform
+ .block
+ .as_ref()
+ .map_or(false, |b| b.disposition.is_below())
+ {
+ cursor.next(&());
+ } else {
+ break;
+ }
+ }
+ }
+
+ // Combine this edit with any subsequent edits that intersect the same transform.
+ while let Some(next_edit) = edits.peek() {
+ if next_edit.old.start <= cursor.start().0 {
+ old_end = WrapRow(next_edit.old.end);
+ new_end = WrapRow(next_edit.new.end);
+ cursor.seek(&old_end, Bias::Left, &());
+ cursor.next(&());
+ if old_end == *cursor.start() {
+ while let Some(transform) = cursor.item() {
+ if transform
+ .block
+ .as_ref()
+ .map_or(false, |b| b.disposition.is_below())
+ {
+ cursor.next(&());
+ } else {
+ break;
+ }
+ }
+ }
+ edits.next();
+ } else {
+ break;
+ }
+ }
+
+ // Find the blocks within this edited region.
+ let new_start = wrap_snapshot.to_point(WrapPoint::new(new_start.0, 0), Bias::Left);
+ let start_anchor = buffer.anchor_before(new_start);
+ let start_block_ix = match self.blocks[last_block_ix..].binary_search_by(|probe| {
+ probe
+ .position
+ .cmp(&start_anchor, buffer)
+ .unwrap()
+ .then(Ordering::Greater)
+ }) {
+ Ok(ix) | Err(ix) => last_block_ix + ix,
+ };
+ let end_block_ix = if new_end.0 > wrap_snapshot.max_point().row() {
+ self.blocks.len()
+ } else {
+ let new_end = wrap_snapshot.to_point(WrapPoint::new(new_end.0, 0), Bias::Left);
+ let end_anchor = buffer.anchor_before(new_end);
+ match self.blocks[start_block_ix..].binary_search_by(|probe| {
+ probe
+ .position
+ .cmp(&end_anchor, buffer)
+ .unwrap()
+ .then(Ordering::Greater)
+ }) {
+ Ok(ix) | Err(ix) => start_block_ix + ix,
+ }
+ };
+ last_block_ix = end_block_ix;
+ blocks_in_edit.clear();
+ blocks_in_edit.extend(
+ self.blocks[start_block_ix..end_block_ix]
+ .iter()
+ .map(|block| {
+ let mut position = block.position.to_point(buffer);
+ let column = wrap_snapshot.from_point(position, Bias::Left).column();
+ match block.disposition {
+ BlockDisposition::Above => position.column = 0,
+ BlockDisposition::Below => {
+ position.column = buffer.line_len(position.row)
+ }
+ }
+ let position = wrap_snapshot.from_point(position, Bias::Left);
+ (position.row(), column, block)
+ }),
+ );
+ blocks_in_edit
+ .sort_unstable_by_key(|(row, _, block)| (*row, block.disposition, block.id));
+
+ // For each of these blocks, insert a new isomorphic transform preceding the block,
+ // and then insert the block itself.
+ for (block_row, column, block) in blocks_in_edit.iter().copied() {
+ let insertion_row = match block.disposition {
+ BlockDisposition::Above => block_row,
+ BlockDisposition::Below => block_row + 1,
+ };
+ let extent_before_block = insertion_row - new_transforms.summary().input_rows;
+ push_isomorphic(&mut new_transforms, extent_before_block);
+ new_transforms.push(Transform::block(block.clone(), column), &());
+ }
+
+ old_end = WrapRow(old_end.0.min(old_row_count));
+ new_end = WrapRow(new_end.0.min(new_row_count));
+
+ // Insert an isomorphic transform after the final block.
+ let extent_after_last_block = new_end.0 - new_transforms.summary().input_rows;
+ push_isomorphic(&mut new_transforms, extent_after_last_block);
+
+ // Preserve any portion of the old transform after this edit.
+ let extent_after_edit = cursor.start().0 - old_end.0;
+ push_isomorphic(&mut new_transforms, extent_after_edit);
+ }
+
+ new_transforms.push_tree(cursor.suffix(&()), &());
+ debug_assert_eq!(
+ new_transforms.summary().input_rows,
+ wrap_snapshot.max_point().row() + 1
+ );
+
+ drop(cursor);
+ *transforms = new_transforms;
+ }
+
+ pub fn restyle<F1, F2>(&mut self, mut styles: HashMap<BlockId, (Option<F1>, Option<F2>)>)
+ where
+ F1: 'static + Fn(&AppContext) -> Vec<(usize, HighlightStyle)>,
+ F2: 'static + Fn(&AppContext) -> BlockStyle,
+ {
+ for block in &self.blocks {
+ if let Some((build_runs, build_style)) = styles.remove(&block.id) {
+ *block.build_runs.lock() = build_runs.map(|build_runs| {
+ Arc::new(build_runs) as Arc<dyn Fn(&AppContext) -> Vec<(usize, HighlightStyle)>>
+ });
+ *block.build_style.lock() = build_style.map(|build_style| {
+ Arc::new(build_style) as Arc<dyn Fn(&AppContext) -> BlockStyle>
+ });
+ }
+ }
+ }
+}
+
+fn push_isomorphic(tree: &mut SumTree<Transform>, rows: u32) {
+ if rows == 0 {
+ return;
+ }
+
+ let mut extent = Some(rows);
+ tree.update_last(
+ |last_transform| {
+ if last_transform.is_isomorphic() {
+ let extent = extent.take().unwrap();
+ last_transform.summary.input_rows += extent;
+ last_transform.summary.output_rows += extent;
+ }
+ },
+ &(),
+ );
+ if let Some(extent) = extent {
+ tree.push(Transform::isomorphic(extent), &());
+ }
+}
+
+impl BlockPoint {
+ pub fn new(row: u32, column: u32) -> Self {
+ Self(Point::new(row, column))
+ }
+}
+
+impl Deref for BlockPoint {
+ type Target = Point;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl std::ops::DerefMut for BlockPoint {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
+impl<'a> BlockMapWriter<'a> {
+ pub fn insert<P, T>(
+ &mut self,
+ blocks: impl IntoIterator<Item = BlockProperties<P, T>>,
+ cx: &AppContext,
+ ) -> Vec<BlockId>
+ where
+ P: ToOffset + Clone,
+ T: Into<Rope> + Clone,
+ {
+ let buffer = self.0.buffer.read(cx);
+ let mut ids = Vec::new();
+ let mut edits = Vec::<Edit<u32>>::new();
+ let wrap_snapshot = &*self.0.wrap_snapshot.lock();
+
+ for block in blocks {
+ let id = BlockId(self.0.next_block_id.fetch_add(1, SeqCst));
+ ids.push(id);
+
+ let position = buffer.anchor_after(block.position);
+ let point = position.to_point(buffer);
+ let start_row = wrap_snapshot
+ .from_point(Point::new(point.row, 0), Bias::Left)
+ .row();
+ let end_row = if point.row == buffer.max_point().row {
+ wrap_snapshot.max_point().row() + 1
+ } else {
+ wrap_snapshot
+ .from_point(Point::new(point.row + 1, 0), Bias::Left)
+ .row()
+ };
+
+ let block_ix = match self
+ .0
+ .blocks
+ .binary_search_by(|probe| probe.position.cmp(&position, buffer).unwrap())
+ {
+ Ok(ix) | Err(ix) => ix,
+ };
+ self.0.blocks.insert(
+ block_ix,
+ Arc::new(Block {
+ id,
+ position,
+ text: block.text.into(),
+ build_runs: Mutex::new(block.build_runs),
+ build_style: Mutex::new(block.build_style),
+ disposition: block.disposition,
+ }),
+ );
+
+ if let Err(edit_ix) = edits.binary_search_by_key(&start_row, |edit| edit.old.start) {
+ edits.insert(
+ edit_ix,
+ Edit {
+ old: start_row..end_row,
+ new: start_row..end_row,
+ },
+ );
+ }
+ }
+
+ self.0.sync(wrap_snapshot, edits, cx);
+ ids
+ }
+
+ pub fn remove(&mut self, block_ids: HashSet<BlockId>, cx: &AppContext) {
+ let buffer = self.0.buffer.read(cx);
+ let wrap_snapshot = &*self.0.wrap_snapshot.lock();
+ let mut edits = Vec::new();
+ let mut last_block_buffer_row = None;
+ self.0.blocks.retain(|block| {
+ if block_ids.contains(&block.id) {
+ let buffer_row = block.position.to_point(buffer).row;
+ if last_block_buffer_row != Some(buffer_row) {
+ last_block_buffer_row = Some(buffer_row);
+ let start_row = wrap_snapshot
+ .from_point(Point::new(buffer_row, 0), Bias::Left)
+ .row();
+ let end_row = wrap_snapshot
+ .from_point(
+ Point::new(buffer_row, buffer.line_len(buffer_row)),
+ Bias::Left,
+ )
+ .row()
+ + 1;
+ edits.push(Edit {
+ old: start_row..end_row,
+ new: start_row..end_row,
+ })
+ }
+ false
+ } else {
+ true
+ }
+ });
+ self.0.sync(wrap_snapshot, edits, cx);
+ }
+}
+
+impl BlockSnapshot {
+ #[cfg(test)]
+ fn text(&mut self) -> String {
+ self.chunks(0..self.transforms.summary().output_rows, None, None)
+ .map(|chunk| chunk.text)
+ .collect()
+ }
+
+ pub fn chunks<'a>(
+ &'a self,
+ rows: Range<u32>,
+ theme: Option<&'a SyntaxTheme>,
+ cx: Option<&'a AppContext>,
+ ) -> Chunks<'a> {
+ let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
+ let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
+ let input_end = {
+ cursor.seek(&BlockRow(rows.end), Bias::Right, &());
+ let overshoot = if cursor
+ .item()
+ .map_or(false, |transform| transform.is_isomorphic())
+ {
+ rows.end - cursor.start().0 .0
+ } else {
+ 0
+ };
+ cursor.start().1 .0 + overshoot
+ };
+ let input_start = {
+ cursor.seek(&BlockRow(rows.start), Bias::Right, &());
+ let overshoot = if cursor
+ .item()
+ .map_or(false, |transform| transform.is_isomorphic())
+ {
+ rows.start - cursor.start().0 .0
+ } else {
+ 0
+ };
+ cursor.start().1 .0 + overshoot
+ };
+ Chunks {
+ input_chunks: self.wrap_snapshot.chunks(input_start..input_end, theme),
+ input_chunk: Default::default(),
+ block_chunks: None,
+ transforms: cursor,
+ output_row: rows.start,
+ max_output_row,
+ cx,
+ }
+ }
+
+ pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: Option<&'a AppContext>) -> BufferRows<'a> {
+ let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
+ cursor.seek(&BlockRow(start_row), Bias::Right, &());
+ let (output_start, input_start) = cursor.start();
+ let overshoot = if cursor.item().map_or(false, |t| t.is_isomorphic()) {
+ start_row - output_start.0
+ } else {
+ 0
+ };
+ let input_start_row = input_start.0 + overshoot;
+ BufferRows {
+ cx,
+ transforms: cursor,
+ input_buffer_rows: self.wrap_snapshot.buffer_rows(input_start_row),
+ output_row: start_row,
+ started: false,
+ }
+ }
+
+ pub fn max_point(&self) -> BlockPoint {
+ let row = self.transforms.summary().output_rows - 1;
+ BlockPoint::new(row, self.line_len(row))
+ }
+
+ pub fn longest_row(&self) -> u32 {
+ let input_row = self.wrap_snapshot.longest_row();
+ let input_row_chars = self.wrap_snapshot.line_char_count(input_row);
+ let TransformSummary {
+ longest_row_in_block: block_row,
+ longest_row_in_block_chars: block_row_chars,
+ ..
+ } = &self.transforms.summary();
+
+ if *block_row_chars > input_row_chars {
+ *block_row
+ } else {
+ self.to_block_point(WrapPoint::new(input_row, 0)).row
+ }
+ }
+
+ pub fn line_len(&self, row: u32) -> u32 {
+ let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
+ cursor.seek(&BlockRow(row), Bias::Right, &());
+ if let Some(transform) = cursor.item() {
+ let (output_start, input_start) = cursor.start();
+ let overshoot = row - output_start.0;
+ if let Some(block) = &transform.block {
+ let mut len = block.text.line_len(overshoot);
+ if len > 0 {
+ len += block.column;
+ }
+ len
+ } else {
+ self.wrap_snapshot.line_len(input_start.0 + overshoot)
+ }
+ } else {
+ panic!("row out of range");
+ }
+ }
+
+ pub fn is_block_line(&self, row: u32) -> bool {
+ let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
+ cursor.seek(&BlockRow(row), Bias::Right, &());
+ cursor.item().map_or(false, |t| t.block.is_some())
+ }
+
+ pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
+ let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
+ cursor.seek(&BlockRow(point.row), Bias::Right, &());
+
+ let max_input_row = WrapRow(self.transforms.summary().input_rows);
+ let search_left =
+ (bias == Bias::Left && cursor.start().1 .0 > 0) || cursor.end(&()).1 == max_input_row;
+
+ loop {
+ if let Some(transform) = cursor.item() {
+ if transform.is_isomorphic() {
+ let (output_start_row, input_start_row) = cursor.start();
+ let (output_end_row, input_end_row) = cursor.end(&());
+
+ if point.row >= output_end_row.0 {
+ return BlockPoint::new(
+ output_end_row.0 - 1,
+ self.wrap_snapshot.line_len(input_end_row.0 - 1),
+ );
+ }
+
+ let output_start = Point::new(output_start_row.0, 0);
+ if point.0 > output_start {
+ let output_overshoot = point.0 - output_start;
+ let input_start = Point::new(input_start_row.0, 0);
+ let input_point = self
+ .wrap_snapshot
+ .clip_point(WrapPoint(input_start + output_overshoot), bias);
+ let input_overshoot = input_point.0 - input_start;
+ return BlockPoint(output_start + input_overshoot);
+ } else {
+ return BlockPoint(output_start);
+ }
+ } else if search_left {
+ cursor.prev(&());
+ } else {
+ cursor.next(&());
+ }
+ } else {
+ return self.max_point();
+ }
+ }
+ }
+
+ pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
+ let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
+ cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
+ if let Some(transform) = cursor.item() {
+ debug_assert!(transform.is_isomorphic());
+ } else {
+ return self.max_point();
+ }
+
+ let (input_start_row, output_start_row) = cursor.start();
+ let input_start = Point::new(input_start_row.0, 0);
+ let output_start = Point::new(output_start_row.0, 0);
+ let input_overshoot = wrap_point.0 - input_start;
+ BlockPoint(output_start + input_overshoot)
+ }
+
+ pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint {
+ let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
+ cursor.seek(&BlockRow(block_point.row), Bias::Right, &());
+ if let Some(transform) = cursor.item() {
+ match transform.block.as_ref().map(|b| b.disposition) {
+ Some(BlockDisposition::Above) => WrapPoint::new(cursor.start().1 .0, 0),
+ Some(BlockDisposition::Below) => {
+ let wrap_row = cursor.start().1 .0 - 1;
+ WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
+ }
+ None => {
+ let overshoot = block_point.row - cursor.start().0 .0;
+ let wrap_row = cursor.start().1 .0 + overshoot;
+ WrapPoint::new(wrap_row, block_point.column)
+ }
+ }
+ } else {
+ self.wrap_snapshot.max_point()
+ }
+ }
+}
+
+impl Transform {
+ fn isomorphic(rows: u32) -> Self {
+ Self {
+ summary: TransformSummary {
+ input_rows: rows,
+ output_rows: rows,
+ longest_row_in_block: 0,
+ longest_row_in_block_chars: 0,
+ },
+ block: None,
+ }
+ }
+
+ fn block(block: Arc<Block>, column: u32) -> Self {
+ let text_summary = block.text.summary();
+ Self {
+ summary: TransformSummary {
+ input_rows: 0,
+ output_rows: text_summary.lines.row + 1,
+ longest_row_in_block: text_summary.longest_row,
+ longest_row_in_block_chars: column + text_summary.longest_row_chars,
+ },
+ block: Some(AlignedBlock { block, column }),
+ }
+ }
+
+ fn is_isomorphic(&self) -> bool {
+ self.block.is_none()
+ }
+}
+
+impl<'a> Iterator for Chunks<'a> {
+ type Item = Chunk<'a>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.output_row >= self.max_output_row {
+ return None;
+ }
+
+ if let Some(block_chunks) = self.block_chunks.as_mut() {
+ if let Some(block_chunk) = block_chunks.next() {
+ self.output_row += block_chunk.text.matches('\n').count() as u32;
+ return Some(block_chunk);
+ } else {
+ self.block_chunks.take();
+ self.output_row += 1;
+ if self.output_row < self.max_output_row {
+ return Some(Chunk {
+ text: "\n",
+ ..Default::default()
+ });
+ } else {
+ return None;
+ }
+ }
+ }
+
+ let transform = self.transforms.item()?;
+ if let Some(block) = transform.block.as_ref() {
+ let block_start = self.transforms.start().0 .0;
+ let block_end = self.transforms.end(&()).0 .0;
+ let start_in_block = self.output_row - block_start;
+ let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
+ self.transforms.next(&());
+ self.block_chunks = Some(BlockChunks::new(
+ block,
+ start_in_block..end_in_block,
+ self.cx,
+ ));
+ return self.next();
+ }
+
+ if self.input_chunk.text.is_empty() {
+ if let Some(input_chunk) = self.input_chunks.next() {
+ self.input_chunk = input_chunk;
+ } else {
+ self.output_row += 1;
+ if self.output_row < self.max_output_row {
+ self.transforms.next(&());
+ return Some(Chunk {
+ text: "\n",
+ ..Default::default()
+ });
+ } else {
+ return None;
+ }
+ }
+ }
+
+ let transform_end = self.transforms.end(&()).0 .0;
+ let (prefix_rows, prefix_bytes) =
+ offset_for_row(self.input_chunk.text, transform_end - self.output_row);
+ self.output_row += prefix_rows;
+ let (prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
+ self.input_chunk.text = suffix;
+ if self.output_row == transform_end {
+ self.transforms.next(&());
+ }
+
+ Some(Chunk {
+ text: prefix,
+ ..self.input_chunk
+ })
+ }
+}
+
+impl<'a> BlockChunks<'a> {
+ fn new(block: &'a AlignedBlock, rows: Range<u32>, cx: Option<&'a AppContext>) -> Self {
+ let offset_range = block.text.point_to_offset(Point::new(rows.start, 0))
+ ..block.text.point_to_offset(Point::new(rows.end, 0));
+
+ let mut runs = block
+ .build_runs
+ .lock()
+ .as_ref()
+ .zip(cx)
+ .map(|(build_runs, cx)| build_runs(cx))
+ .unwrap_or_default()
+ .into_iter()
+ .peekable();
+ let mut run_start = 0;
+ while let Some((run_len, _)) = runs.peek() {
+ let run_end = run_start + run_len;
+ if run_end <= offset_range.start {
+ run_start = run_end;
+ runs.next();
+ } else {
+ break;
+ }
+ }
+
+ Self {
+ chunk: None,
+ run_start,
+ padding_column: block.column,
+ remaining_padding: block.column,
+ chunks: block.text.chunks_in_range(offset_range.clone()),
+ runs,
+ offset: offset_range.start,
+ }
+ }
+}
+
+impl<'a> Iterator for BlockChunks<'a> {
+ type Item = Chunk<'a>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.chunk.is_none() {
+ self.chunk = self.chunks.next();
+ }
+
+ let chunk = self.chunk?;
+
+ if chunk.starts_with('\n') {
+ self.remaining_padding = 0;
+ }
+
+ if self.remaining_padding > 0 {
+ const PADDING: &'static str = " ";
+ let padding_len = self.remaining_padding.min(PADDING.len() as u32);
+ self.remaining_padding -= padding_len;
+ return Some(Chunk {
+ text: &PADDING[..padding_len as usize],
+ ..Default::default()
+ });
+ }
+
+ let mut chunk_len = if let Some(ix) = chunk.find('\n') {
+ ix + 1
+ } else {
+ chunk.len()
+ };
+
+ let mut highlight_style = None;
+ if let Some((run_len, style)) = self.runs.peek() {
+ highlight_style = Some(style.clone());
+ let run_end_in_chunk = self.run_start + run_len - self.offset;
+ if run_end_in_chunk <= chunk_len {
+ chunk_len = run_end_in_chunk;
+ self.run_start += run_len;
+ self.runs.next();
+ }
+ }
+
+ self.offset += chunk_len;
+ let (chunk, suffix) = chunk.split_at(chunk_len);
+
+ if chunk.ends_with('\n') {
+ self.remaining_padding = self.padding_column;
+ }
+
+ self.chunk = if suffix.is_empty() {
+ None
+ } else {
+ Some(suffix)
+ };
+
+ Some(Chunk {
+ text: chunk,
+ highlight_style,
+ diagnostic: None,
+ })
+ }
+}
+
+impl<'a> Iterator for BufferRows<'a> {
+ type Item = DisplayRow;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.started {
+ self.output_row += 1;
+ } else {
+ self.started = true;
+ }
+
+ if self.output_row >= self.transforms.end(&()).0 .0 {
+ self.transforms.next(&());
+ }
+
+ let transform = self.transforms.item()?;
+ if let Some(block) = &transform.block {
+ let style = self
+ .cx
+ .and_then(|cx| block.build_style.lock().as_ref().map(|f| f(cx)));
+ Some(DisplayRow::Block(block.id, style))
+ } else {
+ Some(self.input_buffer_rows.next().unwrap())
+ }
+ }
+}
+
+impl sum_tree::Item for Transform {
+ type Summary = TransformSummary;
+
+ fn summary(&self) -> Self::Summary {
+ self.summary.clone()
+ }
+}
+
+impl sum_tree::Summary for TransformSummary {
+ type Context = ();
+
+ fn add_summary(&mut self, summary: &Self, _: &()) {
+ if summary.longest_row_in_block_chars > self.longest_row_in_block_chars {
+ self.longest_row_in_block_chars = summary.longest_row_in_block_chars;
+ self.longest_row_in_block = self.output_rows + summary.longest_row_in_block;
+ }
+
+ self.input_rows += summary.input_rows;
+ self.output_rows += summary.output_rows;
+ }
+}
+
+impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
+ fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
+ self.0 += summary.input_rows;
+ }
+}
+
+impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
+ fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
+ self.0 += summary.output_rows;
+ }
+}
+
+impl BlockDisposition {
+ fn is_below(&self) -> bool {
+ matches!(self, BlockDisposition::Below)
+ }
+}
+
+impl Deref for AlignedBlock {
+ type Target = Block;
+
+ fn deref(&self) -> &Self::Target {
+ self.block.as_ref()
+ }
+}
+
+impl Debug for Block {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("Block")
+ .field("id", &self.id)
+ .field("position", &self.position)
+ .field("text", &self.text)
+ .field("disposition", &self.disposition)
+ .finish()
+ }
+}
+
+// Count the number of bytes prior to a target point. If the string doesn't contain the target
+// point, return its total extent. Otherwise return the target point itself.
+fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
+ let mut row = 0;
+ let mut offset = 0;
+ for (ix, line) in s.split('\n').enumerate() {
+ if ix > 0 {
+ row += 1;
+ offset += 1;
+ }
+ if row >= target {
+ break;
+ }
+ offset += line.len() as usize;
+ }
+ (row, offset)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
+ use buffer::RandomCharIter;
+ use gpui::color::Color;
+ use language::Buffer;
+ use rand::prelude::*;
+ use std::env;
+
+ #[gpui::test]
+ fn test_offset_for_row() {
+ assert_eq!(offset_for_row("", 0), (0, 0));
+ assert_eq!(offset_for_row("", 1), (0, 0));
+ assert_eq!(offset_for_row("abcd", 0), (0, 0));
+ assert_eq!(offset_for_row("abcd", 1), (0, 4));
+ assert_eq!(offset_for_row("\n", 0), (0, 0));
+ assert_eq!(offset_for_row("\n", 1), (1, 1));
+ assert_eq!(offset_for_row("abc\ndef\nghi", 0), (0, 0));
+ assert_eq!(offset_for_row("abc\ndef\nghi", 1), (1, 4));
+ assert_eq!(offset_for_row("abc\ndef\nghi", 2), (2, 8));
+ assert_eq!(offset_for_row("abc\ndef\nghi", 3), (2, 11));
+ }
+
+ #[gpui::test]
+ fn test_block_chunks(cx: &mut gpui::MutableAppContext) {
+ let red = Color::red();
+ let blue = Color::blue();
+ let clear = Color::default();
+
+ let block = AlignedBlock {
+ column: 5,
+ block: Arc::new(Block {
+ id: BlockId(0),
+ position: Anchor::min(),
+ text: "one!\ntwo three\nfour".into(),
+ build_style: Mutex::new(None),
+ build_runs: Mutex::new(Some(Arc::new(move |_| {
+ vec![(3, red.into()), (6, Default::default()), (5, blue.into())]
+ }))),
+ disposition: BlockDisposition::Above,
+ }),
+ };
+
+ assert_eq!(
+ colored_chunks(&block, 0..3, cx),
+ &[
+ (" ", clear),
+ ("one", red),
+ ("!\n", clear),
+ (" ", clear),
+ ("two ", clear),
+ ("three", blue),
+ ("\n", clear),
+ (" ", clear),
+ ("four", clear)
+ ]
+ );
+ assert_eq!(
+ colored_chunks(&block, 0..1, cx),
+ &[
+ (" ", clear), //
+ ("one", red),
+ ("!\n", clear),
+ ]
+ );
+ assert_eq!(
+ colored_chunks(&block, 1..3, cx),
+ &[
+ (" ", clear),
+ ("two ", clear),
+ ("three", blue),
+ ("\n", clear),
+ (" ", clear),
+ ("four", clear)
+ ]
+ );
+
+ fn colored_chunks<'a>(
+ block: &'a AlignedBlock,
+ row_range: Range<u32>,
+ cx: &'a AppContext,
+ ) -> Vec<(&'a str, Color)> {
+ BlockChunks::new(block, row_range, Some(cx))
+ .map(|c| {
+ (
+ c.text,
+ c.highlight_style.map_or(Color::default(), |s| s.color),
+ )
+ })
+ .collect()
+ }
+ }
+
+ #[gpui::test]
+ fn test_basic_blocks(cx: &mut gpui::MutableAppContext) {
+ let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
+ let font_id = cx
+ .font_cache()
+ .select_font(family_id, &Default::default())
+ .unwrap();
+
+ let text = "aaa\nbbb\nccc\nddd";
+
+ let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
+ let (fold_map, folds_snapshot) = FoldMap::new(buffer.clone(), cx);
+ let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), 1);
+ let (wrap_map, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, None, cx);
+ let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone());
+
+ let mut writer = block_map.write(wraps_snapshot.clone(), vec![], cx);
+ let block_ids = writer.insert(
+ vec![
+ BlockProperties {
+ position: Point::new(1, 0),
+ text: "BLOCK 1",
+ disposition: BlockDisposition::Above,
+ build_runs: None,
+ build_style: None,
+ },
+ BlockProperties {
+ position: Point::new(1, 2),
+ text: "BLOCK 2",
+ disposition: BlockDisposition::Above,
+ build_runs: None,
+ build_style: None,
+ },
+ BlockProperties {
+ position: Point::new(3, 2),
+ text: "BLOCK 3",
+ disposition: BlockDisposition::Below,
+ build_runs: None,
+ build_style: None,
+ },
+ ],
+ cx,
+ );
+
+ let mut snapshot = block_map.read(wraps_snapshot, vec![], cx);
+ assert_eq!(
+ snapshot.text(),
+ "aaa\nBLOCK 1\n BLOCK 2\nbbb\nccc\nddd\n BLOCK 3"
+ );
+ assert_eq!(
+ snapshot.to_block_point(WrapPoint::new(0, 3)),
+ BlockPoint::new(0, 3)
+ );
+ assert_eq!(
+ snapshot.to_block_point(WrapPoint::new(1, 0)),
+ BlockPoint::new(3, 0)
+ );
+ assert_eq!(
+ snapshot.to_block_point(WrapPoint::new(3, 3)),
+ BlockPoint::new(5, 3)
+ );
+
+ assert_eq!(
+ snapshot.to_wrap_point(BlockPoint::new(0, 3)),
+ WrapPoint::new(0, 3)
+ );
+ assert_eq!(
+ snapshot.to_wrap_point(BlockPoint::new(1, 0)),
+ WrapPoint::new(1, 0)
+ );
+ assert_eq!(
+ snapshot.to_wrap_point(BlockPoint::new(3, 0)),
+ WrapPoint::new(1, 0)
+ );
+ assert_eq!(
+ snapshot.to_wrap_point(BlockPoint::new(6, 0)),
+ WrapPoint::new(3, 3)
+ );
+
+ assert_eq!(
+ snapshot.clip_point(BlockPoint::new(1, 0), Bias::Left),
+ BlockPoint::new(0, 3)
+ );
+ assert_eq!(
+ snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right),
+ BlockPoint::new(3, 0)
+ );
+ assert_eq!(
+ snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left),
+ BlockPoint::new(0, 3)
+ );
+ assert_eq!(
+ snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right),
+ BlockPoint::new(3, 0)
+ );
+ assert_eq!(
+ snapshot.clip_point(BlockPoint::new(3, 0), Bias::Left),
+ BlockPoint::new(3, 0)
+ );
+ assert_eq!(
+ snapshot.clip_point(BlockPoint::new(3, 0), Bias::Right),
+ BlockPoint::new(3, 0)
+ );
+ assert_eq!(
+ snapshot.clip_point(BlockPoint::new(5, 3), Bias::Left),
+ BlockPoint::new(5, 3)
+ );
+ assert_eq!(
+ snapshot.clip_point(BlockPoint::new(5, 3), Bias::Right),
+ BlockPoint::new(5, 3)
+ );
+ assert_eq!(
+ snapshot.clip_point(BlockPoint::new(6, 0), Bias::Left),
+ BlockPoint::new(5, 3)
+ );
+ assert_eq!(
+ snapshot.clip_point(BlockPoint::new(6, 0), Bias::Right),
+ BlockPoint::new(5, 3)
+ );
+
+ assert_eq!(
+ snapshot.buffer_rows(0, None).collect::<Vec<_>>(),
+ &[
+ DisplayRow::Buffer(0),
+ DisplayRow::Block(block_ids[0], None),
+ DisplayRow::Block(block_ids[1], None),
+ DisplayRow::Buffer(1),
+ DisplayRow::Buffer(2),
+ DisplayRow::Buffer(3),
+ DisplayRow::Block(block_ids[2], None)
+ ]
+ );
+
+ // Insert a line break, separating two block decorations into separate
+ // lines.
+ buffer.update(cx, |buffer, cx| {
+ buffer.edit([Point::new(1, 1)..Point::new(1, 1)], "!!!\n", cx)
+ });
+
+ let (folds_snapshot, fold_edits) = fold_map.read(cx);
+ let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
+ let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
+ wrap_map.sync(tabs_snapshot, tab_edits, cx)
+ });
+ let mut snapshot = block_map.read(wraps_snapshot, wrap_edits, cx);
+ assert_eq!(
+ snapshot.text(),
+ "aaa\nBLOCK 1\nb!!!\n BLOCK 2\nbb\nccc\nddd\n BLOCK 3"
+ );
+ }
+
+ #[gpui::test]
+ fn test_blocks_on_wrapped_lines(cx: &mut gpui::MutableAppContext) {
+ let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
+ let font_id = cx
+ .font_cache()
+ .select_font(family_id, &Default::default())
+ .unwrap();
+
+ let text = "one two three\nfour five six\nseven eight";
+
+ let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
+ let (_, folds_snapshot) = FoldMap::new(buffer.clone(), cx);
+ let (_, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), 1);
+ let (_, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, Some(60.), cx);
+ let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone());
+
+ let mut writer = block_map.write(wraps_snapshot.clone(), vec![], cx);
+ writer.insert(
+ vec![
+ BlockProperties {
+ position: Point::new(1, 12),
+ text: "<BLOCK 1",
+ disposition: BlockDisposition::Above,
+ build_runs: None,
+ build_style: None,
+ },
+ BlockProperties {
+ position: Point::new(1, 1),
+ text: ">BLOCK 2",
+ disposition: BlockDisposition::Below,
+ build_runs: None,
+ build_style: None,
+ },
+ ],
+ cx,
+ );
+
+ // Blocks with an 'above' disposition go above their corresponding buffer line.
+ // Blocks with a 'below' disposition go below their corresponding buffer line.
+ let mut snapshot = block_map.read(wraps_snapshot, vec![], cx);
+ assert_eq!(
+ snapshot.text(),
+ "one two \nthree\n <BLOCK 1\nfour five \nsix\n >BLOCK 2\nseven \neight"
+ );
+ }
+
+ #[gpui::test(iterations = 100)]
+ fn test_random_blocks(cx: &mut gpui::MutableAppContext, mut rng: StdRng) {
+ let operations = env::var("OPERATIONS")
+ .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
+ .unwrap_or(10);
+
+ let wrap_width = if rng.gen_bool(0.2) {
+ None
+ } else {
+ Some(rng.gen_range(0.0..=100.0))
+ };
+ let tab_size = 1;
+ let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
+ let font_id = cx
+ .font_cache()
+ .select_font(family_id, &Default::default())
+ .unwrap();
+ let font_size = 14.0;
+
+ log::info!("Wrap width: {:?}", wrap_width);
+
+ let buffer = cx.add_model(|cx| {
+ let len = rng.gen_range(0..10);
+ let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
+ log::info!("initial buffer text: {:?}", text);
+ Buffer::new(0, text, cx)
+ });
+ let (fold_map, folds_snapshot) = FoldMap::new(buffer.clone(), cx);
+ let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size);
+ let (wrap_map, wraps_snapshot) =
+ WrapMap::new(tabs_snapshot, font_id, font_size, wrap_width, cx);
+ let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot);
+ let mut expected_blocks = Vec::new();
+
+ for _ in 0..operations {
+ match rng.gen_range(0..=100) {
+ 0..=19 => {
+ let wrap_width = if rng.gen_bool(0.2) {
+ None
+ } else {
+ Some(rng.gen_range(0.0..=100.0))
+ };
+ log::info!("Setting wrap width to {:?}", wrap_width);
+ wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
+ }
+ 20..=39 => {
+ let block_count = rng.gen_range(1..=1);
+ let block_properties = (0..block_count)
+ .map(|_| {
+ let buffer = buffer.read(cx);
+ let position = buffer.anchor_after(
+ buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Left),
+ );
+
+ let len = rng.gen_range(0..10);
+ let mut text = Rope::from(
+ RandomCharIter::new(&mut rng)
+ .take(len)
+ .collect::<String>()
+ .to_uppercase()
+ .as_str(),
+ );
+ let disposition = if rng.gen() {
+ text.push_front("<");
+ BlockDisposition::Above
+ } else {
+ text.push_front(">");
+ BlockDisposition::Below
+ };
+ log::info!(
+ "inserting block {:?} {:?} with text {:?}",
+ disposition,
+ position.to_point(buffer),
+ text.to_string()
+ );
+ BlockProperties {
+ position,
+ text,
+ disposition,
+ build_runs: None,
+ build_style: None,
+ }
+ })
+ .collect::<Vec<_>>();
+
+ let (folds_snapshot, fold_edits) = fold_map.read(cx);
+ let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
+ let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
+ wrap_map.sync(tabs_snapshot, tab_edits, cx)
+ });
+ let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx);
+ let block_ids = block_map.insert(block_properties.clone(), cx);
+ for (block_id, props) in block_ids.into_iter().zip(block_properties) {
+ expected_blocks.push((block_id, props));
+ }
+ }
+ 40..=59 if !expected_blocks.is_empty() => {
+ let block_count = rng.gen_range(1..=4.min(expected_blocks.len()));
+ let block_ids_to_remove = (0..block_count)
+ .map(|_| {
+ expected_blocks
+ .remove(rng.gen_range(0..expected_blocks.len()))
+ .0
+ })
+ .collect();
+
+ let (folds_snapshot, fold_edits) = fold_map.read(cx);
+ let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
+ let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
+ wrap_map.sync(tabs_snapshot, tab_edits, cx)
+ });
+ let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx);
+ block_map.remove(block_ids_to_remove, cx);
+ }
+ _ => {
+ buffer.update(cx, |buffer, _| {
+ buffer.randomly_edit(&mut rng, 1);
+ log::info!("buffer text: {:?}", buffer.text());
+ });
+ }
+ }
+
+ let (folds_snapshot, fold_edits) = fold_map.read(cx);
+ let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
+ let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
+ wrap_map.sync(tabs_snapshot, tab_edits, cx)
+ });
+ let mut blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, cx);
+ assert_eq!(
+ blocks_snapshot.transforms.summary().input_rows,
+ wraps_snapshot.max_point().row() + 1
+ );
+ log::info!("blocks text: {:?}", blocks_snapshot.text());
+
+ let buffer = buffer.read(cx);
+ let mut sorted_blocks = expected_blocks
+ .iter()
+ .cloned()
+ .map(|(id, block)| {
+ let mut position = block.position.to_point(buffer);
+ let column = wraps_snapshot.from_point(position, Bias::Left).column();
+ match block.disposition {
+ BlockDisposition::Above => {
+ position.column = 0;
+ }
+ BlockDisposition::Below => {
+ position.column = buffer.line_len(position.row);
+ }
+ };
+ let row = wraps_snapshot.from_point(position, Bias::Left).row();
+ (
+ id,
+ BlockProperties {
+ position: BlockPoint::new(row, column),
+ text: block.text,
+ build_runs: block.build_runs.clone(),
+ build_style: None,
+ disposition: block.disposition,
+ },
+ )
+ })
+ .collect::<Vec<_>>();
+ sorted_blocks
+ .sort_unstable_by_key(|(id, block)| (block.position.row, block.disposition, *id));
+ let mut sorted_blocks = sorted_blocks.into_iter().peekable();
+
+ let mut expected_buffer_rows = Vec::new();
+ let mut expected_text = String::new();
+ let input_text = wraps_snapshot.text();
+ for (row, input_line) in input_text.split('\n').enumerate() {
+ let row = row as u32;
+ if row > 0 {
+ expected_text.push('\n');
+ }
+
+ let buffer_row = wraps_snapshot
+ .to_point(WrapPoint::new(row, 0), Bias::Left)
+ .row;
+
+ while let Some((block_id, block)) = sorted_blocks.peek() {
+ if block.position.row == row && block.disposition == BlockDisposition::Above {
+ let text = block.text.to_string();
+ let padding = " ".repeat(block.position.column as usize);
+ for line in text.split('\n') {
+ if !line.is_empty() {
+ expected_text.push_str(&padding);
+ expected_text.push_str(line);
+ }
+ expected_text.push('\n');
+ expected_buffer_rows.push(DisplayRow::Block(*block_id, None));
+ }
+ sorted_blocks.next();
+ } else {
+ break;
+ }
+ }
+
+ let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0;
+ expected_buffer_rows.push(if soft_wrapped {
+ DisplayRow::Wrap
+ } else {
+ DisplayRow::Buffer(buffer_row)
+ });
+ expected_text.push_str(input_line);
+
+ while let Some((block_id, block)) = sorted_blocks.peek() {
+ if block.position.row == row && block.disposition == BlockDisposition::Below {
+ let text = block.text.to_string();
+ let padding = " ".repeat(block.position.column as usize);
+ for line in text.split('\n') {
+ expected_text.push('\n');
+ if !line.is_empty() {
+ expected_text.push_str(&padding);
+ expected_text.push_str(line);
+ }
+ expected_buffer_rows.push(DisplayRow::Block(*block_id, None));
+ }
+ sorted_blocks.next();
+ } else {
+ break;
+ }
+ }
+ }
+
+ let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
+ let expected_row_count = expected_lines.len();
+ for start_row in 0..expected_row_count {
+ let expected_text = expected_lines[start_row..].join("\n");
+ let actual_text = blocks_snapshot
+ .chunks(start_row as u32..expected_row_count as u32, None, None)
+ .map(|chunk| chunk.text)
+ .collect::<String>();
+ assert_eq!(
+ actual_text, expected_text,
+ "incorrect text starting from row {}",
+ start_row
+ );
+ assert_eq!(
+ blocks_snapshot
+ .buffer_rows(start_row as u32, None)
+ .collect::<Vec<_>>(),
+ &expected_buffer_rows[start_row..]
+ );
+ }
+
+ let mut expected_longest_rows = Vec::new();
+ let mut longest_line_len = -1_isize;
+ for (row, line) in expected_lines.iter().enumerate() {
+ let row = row as u32;
+
+ assert_eq!(
+ blocks_snapshot.line_len(row),
+ line.len() as u32,
+ "invalid line len for row {}",
+ row
+ );
+
+ let line_char_count = line.chars().count() as isize;
+ match line_char_count.cmp(&longest_line_len) {
+ Ordering::Less => {}
+ Ordering::Equal => expected_longest_rows.push(row),
+ Ordering::Greater => {
+ longest_line_len = line_char_count;
+ expected_longest_rows.clear();
+ expected_longest_rows.push(row);
+ }
+ }
+ }
+
+ log::info!("getting longest row >>>>>>>>>>>>>>>>>>>>>>>>");
+ let longest_row = blocks_snapshot.longest_row();
+ assert!(
+ expected_longest_rows.contains(&longest_row),
+ "incorrect longest row {}. expected {:?} with length {}",
+ longest_row,
+ expected_longest_rows,
+ longest_line_len,
+ );
+
+ for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() {
+ let wrap_point = WrapPoint::new(row, 0);
+ let block_point = blocks_snapshot.to_block_point(wrap_point);
+ assert_eq!(blocks_snapshot.to_wrap_point(block_point), wrap_point);
+ }
+
+ let mut block_point = BlockPoint::new(0, 0);
+ for c in expected_text.chars() {
+ let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
+ let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
+
+ assert_eq!(
+ blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(left_point)),
+ left_point
+ );
+ assert_eq!(
+ blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(right_point)),
+ right_point
+ );
+
+ if c == '\n' {
+ block_point.0 += Point::new(1, 0);
+ } else {
+ block_point.column += c.len_utf8() as u32;
+ }
+ }
+ }
+ }
+}
@@ -1,8 +1,5 @@
use gpui::{AppContext, ModelHandle};
-use language::{
- Anchor, AnchorRangeExt, Buffer, HighlightId, HighlightedChunk, Point, PointUtf16, TextSummary,
- ToOffset,
-};
+use language::{Anchor, AnchorRangeExt, Buffer, Chunk, Point, PointUtf16, TextSummary, ToOffset};
use parking_lot::Mutex;
use std::{
cmp::{self, Ordering},
@@ -11,6 +8,7 @@ use std::{
sync::atomic::{AtomicUsize, Ordering::SeqCst},
};
use sum_tree::{Bias, Cursor, FilterCursor, SumTree};
+use theme::SyntaxTheme;
pub trait ToFoldPoint {
fn to_fold_point(&self, snapshot: &Snapshot, bias: Bias) -> FoldPoint;
@@ -499,7 +497,9 @@ pub struct Snapshot {
impl Snapshot {
#[cfg(test)]
pub fn text(&self) -> String {
- self.chunks_at(FoldOffset(0)).collect()
+ self.chunks(FoldOffset(0)..self.len(), None)
+ .map(|c| c.text)
+ .collect()
}
#[cfg(test)]
@@ -551,7 +551,6 @@ impl Snapshot {
summary
}
- #[cfg(test)]
pub fn len(&self) -> FoldOffset {
FoldOffset(self.transforms.summary().output.bytes)
}
@@ -628,21 +627,17 @@ impl Snapshot {
false
}
- pub fn chunks_at(&self, offset: FoldOffset) -> Chunks {
- let mut transform_cursor = self.transforms.cursor::<(FoldOffset, usize)>();
- transform_cursor.seek(&offset, Bias::Right, &());
- let overshoot = offset.0 - transform_cursor.start().0 .0;
- let buffer_offset = transform_cursor.start().1 + overshoot;
- Chunks {
- transform_cursor,
- buffer_offset,
- buffer_chunks: self
- .buffer_snapshot
- .text_for_range(buffer_offset..self.buffer_snapshot.len()),
- }
+ pub fn chars_at(&self, start: FoldPoint) -> impl '_ + Iterator<Item = char> {
+ let start = start.to_offset(self);
+ self.chunks(start..self.len(), None)
+ .flat_map(|chunk| chunk.text.chars())
}
- pub fn highlighted_chunks(&mut self, range: Range<FoldOffset>) -> HighlightedChunks {
+ pub fn chunks<'a>(
+ &'a self,
+ range: Range<FoldOffset>,
+ theme: Option<&'a SyntaxTheme>,
+ ) -> Chunks<'a> {
let mut transform_cursor = self.transforms.cursor::<(FoldOffset, usize)>();
transform_cursor.seek(&range.end, Bias::Right, &());
@@ -653,21 +648,16 @@ impl Snapshot {
let overshoot = range.start.0 - transform_cursor.start().0 .0;
let buffer_start = transform_cursor.start().1 + overshoot;
- HighlightedChunks {
+ Chunks {
transform_cursor,
- buffer_offset: buffer_start,
- buffer_chunks: self
- .buffer_snapshot
- .highlighted_text_for_range(buffer_start..buffer_end),
+ buffer_chunks: self.buffer_snapshot.chunks(buffer_start..buffer_end, theme),
buffer_chunk: None,
+ buffer_offset: buffer_start,
+ output_offset: range.start.0,
+ max_output_offset: range.end.0,
}
}
- pub fn chars_at<'a>(&'a self, point: FoldPoint) -> impl Iterator<Item = char> + 'a {
- let offset = point.to_offset(self);
- self.chunks_at(offset).flat_map(str::chars)
- }
-
#[cfg(test)]
pub fn clip_offset(&self, offset: FoldOffset, bias: Bias) -> FoldOffset {
let mut cursor = self.transforms.cursor::<(FoldOffset, usize)>();
@@ -948,68 +938,21 @@ impl<'a> Iterator for BufferRows<'a> {
pub struct Chunks<'a> {
transform_cursor: Cursor<'a, Transform, (FoldOffset, usize)>,
- buffer_chunks: buffer::Chunks<'a>,
+ buffer_chunks: language::Chunks<'a>,
+ buffer_chunk: Option<(usize, Chunk<'a>)>,
buffer_offset: usize,
+ output_offset: usize,
+ max_output_offset: usize,
}
impl<'a> Iterator for Chunks<'a> {
- type Item = &'a str;
+ type Item = Chunk<'a>;
fn next(&mut self) -> Option<Self::Item> {
- let transform = if let Some(item) = self.transform_cursor.item() {
- item
- } else {
+ if self.output_offset >= self.max_output_offset {
return None;
- };
-
- // If we're in a fold, then return the fold's display text and
- // advance the transform and buffer cursors to the end of the fold.
- if let Some(output_text) = transform.output_text {
- self.buffer_offset += transform.summary.input.bytes;
- self.buffer_chunks.seek(self.buffer_offset);
-
- while self.buffer_offset >= self.transform_cursor.end(&()).1
- && self.transform_cursor.item().is_some()
- {
- self.transform_cursor.next(&());
- }
-
- return Some(output_text);
- }
-
- // Otherwise, take a chunk from the buffer's text.
- if let Some(mut chunk) = self.buffer_chunks.peek() {
- let offset_in_chunk = self.buffer_offset - self.buffer_chunks.offset();
- chunk = &chunk[offset_in_chunk..];
-
- // Truncate the chunk so that it ends at the next fold.
- let region_end = self.transform_cursor.end(&()).1 - self.buffer_offset;
- if chunk.len() >= region_end {
- chunk = &chunk[0..region_end];
- self.transform_cursor.next(&());
- } else {
- self.buffer_chunks.next();
- }
-
- self.buffer_offset += chunk.len();
- return Some(chunk);
}
- None
- }
-}
-
-pub struct HighlightedChunks<'a> {
- transform_cursor: Cursor<'a, Transform, (FoldOffset, usize)>,
- buffer_chunks: language::HighlightedChunks<'a>,
- buffer_chunk: Option<(usize, HighlightedChunk<'a>)>,
- buffer_offset: usize,
-}
-
-impl<'a> Iterator for HighlightedChunks<'a> {
- type Item = HighlightedChunk<'a>;
-
- fn next(&mut self) -> Option<Self::Item> {
let transform = if let Some(item) = self.transform_cursor.item() {
item
} else {
@@ -1029,9 +972,10 @@ impl<'a> Iterator for HighlightedChunks<'a> {
self.transform_cursor.next(&());
}
- return Some(HighlightedChunk {
+ self.output_offset += output_text.len();
+ return Some(Chunk {
text: output_text,
- highlight_id: HighlightId::default(),
+ highlight_style: None,
diagnostic: None,
});
}
@@ -1057,6 +1001,7 @@ impl<'a> Iterator for HighlightedChunks<'a> {
}
self.buffer_offset += chunk.text.len();
+ self.output_offset += chunk.text.len();
return Some(chunk);
}
@@ -1352,7 +1297,7 @@ mod tests {
}
let buffer = map.buffer.read(cx).snapshot();
- let mut expected_text: String = buffer.text().into();
+ let mut expected_text: String = buffer.text().to_string();
let mut expected_buffer_rows = Vec::new();
let mut next_row = buffer.max_point().row;
for fold_range in map.merged_fold_ranges(cx.as_ref()).into_iter().rev() {
@@ -1428,11 +1373,22 @@ mod tests {
}
for _ in 0..5 {
- let offset = snapshot
+ let mut start = snapshot
+ .clip_offset(FoldOffset(rng.gen_range(0..=snapshot.len().0)), Bias::Left);
+ let mut end = snapshot
.clip_offset(FoldOffset(rng.gen_range(0..=snapshot.len().0)), Bias::Right);
+ if start > end {
+ mem::swap(&mut start, &mut end);
+ }
+
+ let text = &expected_text[start.0..end.0];
+ log::info!("slicing {:?}..{:?} (text: {:?})", start, end, text);
assert_eq!(
- snapshot.chunks_at(offset).collect::<String>(),
- &expected_text[offset.0..],
+ snapshot
+ .chunks(start..end, None)
+ .map(|c| c.text)
+ .collect::<String>(),
+ text,
);
}
@@ -0,0 +1,511 @@
+use std::{cmp, mem};
+
+type Edit = buffer::Edit<u32>;
+
+#[derive(Default, Debug, PartialEq, Eq)]
+pub struct Patch(Vec<Edit>);
+
+impl Patch {
+ pub unsafe fn new_unchecked(edits: Vec<Edit>) -> Self {
+ Self(edits)
+ }
+
+ pub fn into_inner(self) -> Vec<Edit> {
+ self.0
+ }
+
+ pub fn compose(&self, other: &Self) -> Self {
+ let mut old_edits_iter = self.0.iter().cloned().peekable();
+ let mut new_edits_iter = other.0.iter().cloned().peekable();
+ let mut composed = Patch(Vec::new());
+
+ let mut old_start = 0;
+ let mut new_start = 0;
+ loop {
+ let old_edit = old_edits_iter.peek_mut();
+ let new_edit = new_edits_iter.peek_mut();
+
+ // Push the old edit if its new end is before the new edit's old start.
+ if let Some(old_edit) = old_edit.as_ref() {
+ let new_edit = new_edit.as_ref();
+ if new_edit.map_or(true, |new_edit| old_edit.new.end < new_edit.old.start) {
+ let catchup = old_edit.old.start - old_start;
+ old_start += catchup;
+ new_start += catchup;
+
+ let old_end = old_start + old_edit.old.len() as u32;
+ let new_end = new_start + old_edit.new.len() as u32;
+ composed.push(Edit {
+ old: old_start..old_end,
+ new: new_start..new_end,
+ });
+ old_start = old_end;
+ new_start = new_end;
+ old_edits_iter.next();
+ continue;
+ }
+ }
+
+ // Push the new edit if its old end is before the old edit's new start.
+ if let Some(new_edit) = new_edit.as_ref() {
+ let old_edit = old_edit.as_ref();
+ if old_edit.map_or(true, |old_edit| new_edit.old.end < old_edit.new.start) {
+ let catchup = new_edit.new.start - new_start;
+ old_start += catchup;
+ new_start += catchup;
+
+ let old_end = old_start + new_edit.old.len() as u32;
+ let new_end = new_start + new_edit.new.len() as u32;
+ composed.push(Edit {
+ old: old_start..old_end,
+ new: new_start..new_end,
+ });
+ old_start = old_end;
+ new_start = new_end;
+ new_edits_iter.next();
+ continue;
+ }
+ }
+
+ // If we still have edits by this point then they must intersect, so we compose them.
+ if let Some((old_edit, new_edit)) = old_edit.zip(new_edit) {
+ if old_edit.new.start < new_edit.old.start {
+ let catchup = old_edit.old.start - old_start;
+ old_start += catchup;
+ new_start += catchup;
+
+ let overshoot = new_edit.old.start - old_edit.new.start;
+ let old_end = cmp::min(old_start + overshoot, old_edit.old.end);
+ let new_end = new_start + overshoot;
+ composed.push(Edit {
+ old: old_start..old_end,
+ new: new_start..new_end,
+ });
+
+ old_edit.old.start += overshoot;
+ old_edit.new.start += overshoot;
+ old_start = old_end;
+ new_start = new_end;
+ } else {
+ let catchup = new_edit.new.start - new_start;
+ old_start += catchup;
+ new_start += catchup;
+
+ let overshoot = old_edit.new.start - new_edit.old.start;
+ let old_end = old_start + overshoot;
+ let new_end = cmp::min(new_start + overshoot, new_edit.new.end);
+ composed.push(Edit {
+ old: old_start..old_end,
+ new: new_start..new_end,
+ });
+
+ new_edit.old.start += overshoot;
+ new_edit.new.start += overshoot;
+ old_start = old_end;
+ new_start = new_end;
+ }
+
+ if old_edit.new.end > new_edit.old.end {
+ let old_end =
+ old_start + cmp::min(old_edit.old.len() as u32, new_edit.old.len() as u32);
+ let new_end = new_start + new_edit.new.len() as u32;
+ composed.push(Edit {
+ old: old_start..old_end,
+ new: new_start..new_end,
+ });
+
+ old_edit.old.start = old_end;
+ old_edit.new.start = new_edit.old.end;
+ old_start = old_end;
+ new_start = new_end;
+ new_edits_iter.next();
+ } else {
+ let old_end = old_start + old_edit.old.len() as u32;
+ let new_end =
+ new_start + cmp::min(old_edit.new.len() as u32, new_edit.new.len() as u32);
+ composed.push(Edit {
+ old: old_start..old_end,
+ new: new_start..new_end,
+ });
+
+ new_edit.old.start = old_edit.new.end;
+ new_edit.new.start = new_end;
+ old_start = old_end;
+ new_start = new_end;
+ old_edits_iter.next();
+ }
+ } else {
+ break;
+ }
+ }
+
+ 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();
+ }
+
+ fn push(&mut self, edit: Edit) {
+ if edit.old.len() == 0 && edit.new.len() == 0 {
+ 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);
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use rand::prelude::*;
+ use std::env;
+
+ #[gpui::test]
+ fn test_one_disjoint_edit() {
+ assert_patch_composition(
+ Patch(vec![Edit {
+ old: 1..3,
+ new: 1..4,
+ }]),
+ Patch(vec![Edit {
+ old: 0..0,
+ new: 0..4,
+ }]),
+ Patch(vec![
+ Edit {
+ old: 0..0,
+ new: 0..4,
+ },
+ Edit {
+ old: 1..3,
+ new: 5..8,
+ },
+ ]),
+ );
+
+ assert_patch_composition(
+ Patch(vec![Edit {
+ old: 1..3,
+ new: 1..4,
+ }]),
+ Patch(vec![Edit {
+ old: 5..9,
+ new: 5..7,
+ }]),
+ Patch(vec![
+ Edit {
+ old: 1..3,
+ new: 1..4,
+ },
+ Edit {
+ old: 4..8,
+ new: 5..7,
+ },
+ ]),
+ );
+ }
+
+ #[gpui::test]
+ fn test_one_overlapping_edit() {
+ assert_patch_composition(
+ Patch(vec![Edit {
+ old: 1..3,
+ new: 1..4,
+ }]),
+ Patch(vec![Edit {
+ old: 3..5,
+ new: 3..6,
+ }]),
+ Patch(vec![Edit {
+ old: 1..4,
+ new: 1..6,
+ }]),
+ );
+ }
+
+ #[gpui::test]
+ fn test_two_disjoint_and_overlapping() {
+ assert_patch_composition(
+ Patch(vec![
+ Edit {
+ old: 1..3,
+ new: 1..4,
+ },
+ Edit {
+ old: 8..12,
+ new: 9..11,
+ },
+ ]),
+ Patch(vec![
+ Edit {
+ old: 0..0,
+ new: 0..4,
+ },
+ Edit {
+ old: 3..10,
+ new: 7..9,
+ },
+ ]),
+ Patch(vec![
+ Edit {
+ old: 0..0,
+ new: 0..4,
+ },
+ Edit {
+ old: 1..12,
+ new: 5..10,
+ },
+ ]),
+ );
+ }
+
+ #[gpui::test]
+ fn test_two_new_edits_overlapping_one_old_edit() {
+ assert_patch_composition(
+ Patch(vec![Edit {
+ old: 0..0,
+ new: 0..3,
+ }]),
+ Patch(vec![
+ Edit {
+ old: 0..0,
+ new: 0..1,
+ },
+ Edit {
+ old: 1..2,
+ new: 2..2,
+ },
+ ]),
+ Patch(vec![Edit {
+ old: 0..0,
+ new: 0..3,
+ }]),
+ );
+
+ assert_patch_composition(
+ Patch(vec![Edit {
+ old: 2..3,
+ new: 2..4,
+ }]),
+ Patch(vec![
+ Edit {
+ old: 0..2,
+ new: 0..1,
+ },
+ Edit {
+ old: 3..3,
+ new: 2..5,
+ },
+ ]),
+ Patch(vec![Edit {
+ old: 0..3,
+ new: 0..6,
+ }]),
+ );
+
+ assert_patch_composition(
+ Patch(vec![Edit {
+ old: 0..0,
+ new: 0..2,
+ }]),
+ Patch(vec![
+ Edit {
+ old: 0..0,
+ new: 0..2,
+ },
+ Edit {
+ old: 2..5,
+ new: 4..4,
+ },
+ ]),
+ Patch(vec![Edit {
+ old: 0..3,
+ new: 0..4,
+ }]),
+ );
+ }
+
+ // #[test]
+ // fn test_compose_edits() {
+ // assert_eq!(
+ // compose_edits(
+ // &Edit {
+ // old: 3..3,
+ // new: 3..6,
+ // },
+ // &Edit {
+ // old: 2..7,
+ // new: 2..4,
+ // },
+ // ),
+ // Edit {
+ // old: 2..4,
+ // new: 2..4
+ // }
+ // );
+ // }
+
+ #[gpui::test]
+ fn test_two_new_edits_touching_one_old_edit() {
+ assert_patch_composition(
+ Patch(vec![
+ Edit {
+ old: 2..3,
+ new: 2..4,
+ },
+ Edit {
+ old: 7..7,
+ new: 8..11,
+ },
+ ]),
+ Patch(vec![
+ Edit {
+ old: 2..3,
+ new: 2..2,
+ },
+ Edit {
+ old: 4..4,
+ new: 3..4,
+ },
+ ]),
+ Patch(vec![
+ Edit {
+ old: 2..3,
+ new: 2..4,
+ },
+ Edit {
+ old: 7..7,
+ new: 8..11,
+ },
+ ]),
+ );
+ }
+
+ #[gpui::test(iterations = 100)]
+ fn test_random_patch_compositions(mut rng: StdRng) {
+ let operations = env::var("OPERATIONS")
+ .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
+ .unwrap_or(20);
+
+ let initial_chars = (0..rng.gen_range(0..=100))
+ .map(|_| rng.gen_range(b'a'..=b'z') as char)
+ .collect::<Vec<_>>();
+ log::info!("initial chars: {:?}", initial_chars);
+
+ // Generate two sequential patches
+ let mut patches = Vec::new();
+ let mut expected_chars = initial_chars.clone();
+ for i in 0..2 {
+ log::info!("patch {}:", i);
+
+ let mut delta = 0i32;
+ let mut last_edit_end = 0;
+ let mut edits = Vec::new();
+
+ for _ in 0..operations {
+ if last_edit_end >= expected_chars.len() {
+ break;
+ }
+
+ let end = rng.gen_range(last_edit_end..=expected_chars.len());
+ let start = rng.gen_range(last_edit_end..=end);
+ let old_len = end - start;
+
+ let mut new_len = rng.gen_range(0..=3);
+ if start == end && new_len == 0 {
+ new_len += 1;
+ }
+
+ last_edit_end = start + new_len + 1;
+
+ let new_chars = (0..new_len)
+ .map(|_| rng.gen_range(b'A'..=b'Z') as char)
+ .collect::<Vec<_>>();
+ log::info!(
+ " editing {:?}: {:?}",
+ start..end,
+ new_chars.iter().collect::<String>()
+ );
+ edits.push(Edit {
+ old: (start as i32 - delta) as u32..(end as i32 - delta) as u32,
+ new: start as u32..(start + new_len) as u32,
+ });
+ expected_chars.splice(start..end, new_chars);
+
+ delta += new_len as i32 - old_len as i32;
+ }
+
+ patches.push(Patch(edits));
+ }
+
+ log::info!("old patch: {:?}", &patches[0]);
+ log::info!("new patch: {:?}", &patches[1]);
+ log::info!("initial chars: {:?}", initial_chars);
+ log::info!("final chars: {:?}", expected_chars);
+
+ // Compose the patches, and verify that it has the same effect as applying the
+ // two patches separately.
+ let composed = patches[0].compose(&patches[1]);
+ log::info!("composed patch: {:?}", &composed);
+
+ let mut actual_chars = initial_chars.clone();
+ for edit in composed.0 {
+ actual_chars.splice(
+ edit.new.start as usize..edit.new.start as usize + edit.old.len(),
+ expected_chars[edit.new.start as usize..edit.new.end as usize]
+ .iter()
+ .copied(),
+ );
+ }
+
+ assert_eq!(actual_chars, expected_chars);
+ }
+
+ #[track_caller]
+ fn assert_patch_composition(old: Patch, new: Patch, composed: Patch) {
+ let original = ('a'..'z').collect::<Vec<_>>();
+ let inserted = ('A'..'Z').collect::<Vec<_>>();
+
+ let mut expected = original.clone();
+ apply_patch(&mut expected, &old, &inserted);
+ apply_patch(&mut expected, &new, &inserted);
+
+ let mut actual = original.clone();
+ apply_patch(&mut actual, &composed, &expected);
+ assert_eq!(
+ actual.into_iter().collect::<String>(),
+ expected.into_iter().collect::<String>(),
+ "expected patch is incorrect"
+ );
+
+ assert_eq!(old.compose(&new), composed);
+ }
+
+ fn apply_patch(text: &mut Vec<char>, patch: &Patch, new_text: &[char]) {
+ for edit in patch.0.iter().rev() {
+ text.splice(
+ edit.old.start as usize..edit.old.end as usize,
+ new_text[edit.new.start as usize..edit.new.end as usize]
+ .iter()
+ .copied(),
+ );
+ }
+ }
+}
@@ -1,8 +1,10 @@
-use super::fold_map::{self, FoldEdit, FoldPoint, Snapshot as FoldSnapshot};
-use language::{rope, HighlightedChunk};
+use super::fold_map::{self, FoldEdit, FoldPoint, Snapshot as FoldSnapshot, ToFoldPoint};
+use buffer::Point;
+use language::{rope, Chunk};
use parking_lot::Mutex;
-use std::{mem, ops::Range};
+use std::{cmp, mem, ops::Range};
use sum_tree::Bias;
+use theme::SyntaxTheme;
pub struct TabMap(Mutex<Snapshot>);
@@ -21,6 +23,7 @@ impl TabMap {
mut fold_edits: Vec<FoldEdit>,
) -> (Snapshot, Vec<Edit>) {
let mut old_snapshot = self.0.lock();
+ let max_offset = old_snapshot.fold_snapshot.len();
let new_snapshot = Snapshot {
fold_snapshot,
tab_size: old_snapshot.tab_size,
@@ -31,11 +34,11 @@ impl TabMap {
let mut delta = 0;
for chunk in old_snapshot
.fold_snapshot
- .chunks_at(fold_edit.old_bytes.end)
+ .chunks(fold_edit.old_bytes.end..max_offset, None)
{
let patterns: &[_] = &['\t', '\n'];
- if let Some(ix) = chunk.find(patterns) {
- if &chunk[ix..ix + 1] == "\t" {
+ if let Some(ix) = chunk.text.find(patterns) {
+ if &chunk.text[ix..ix + 1] == "\t" {
fold_edit.old_bytes.end.0 += delta + ix + 1;
fold_edit.new_bytes.end.0 += delta + ix + 1;
}
@@ -43,7 +46,7 @@ impl TabMap {
break;
}
- delta += chunk.len();
+ delta += chunk.text.len();
}
}
@@ -108,28 +111,31 @@ impl Snapshot {
.text_summary_for_range(input_start..input_end);
let mut first_line_chars = 0;
- let mut first_line_bytes = 0;
- for c in self.chunks_at(range.start).flat_map(|chunk| chunk.chars()) {
- if c == '\n'
- || (range.start.row() == range.end.row() && first_line_bytes == range.end.column())
- {
+ let line_end = if range.start.row() == range.end.row() {
+ range.end
+ } else {
+ self.max_point()
+ };
+ for c in self
+ .chunks(range.start..line_end, None)
+ .flat_map(|chunk| chunk.text.chars())
+ {
+ if c == '\n' {
break;
}
first_line_chars += 1;
- first_line_bytes += c.len_utf8() as u32;
}
let mut last_line_chars = 0;
- let mut last_line_bytes = 0;
- for c in self
- .chunks_at(TabPoint::new(range.end.row(), 0).max(range.start))
- .flat_map(|chunk| chunk.chars())
- {
- if last_line_bytes == range.end.column() {
- break;
+ if range.start.row() == range.end.row() {
+ last_line_chars = first_line_chars;
+ } else {
+ for _ in self
+ .chunks(TabPoint::new(range.end.row(), 0)..range.end, None)
+ .flat_map(|chunk| chunk.text.chars())
+ {
+ last_line_chars += 1;
}
- last_line_chars += 1;
- last_line_bytes += c.len_utf8() as u32;
}
TextSummary {
@@ -145,21 +151,11 @@ impl Snapshot {
self.fold_snapshot.version
}
- pub fn chunks_at(&self, point: TabPoint) -> Chunks {
- let (point, expanded_char_column, to_next_stop) = self.to_fold_point(point, Bias::Left);
- let fold_chunks = self
- .fold_snapshot
- .chunks_at(point.to_offset(&self.fold_snapshot));
- Chunks {
- fold_chunks,
- column: expanded_char_column,
- tab_size: self.tab_size,
- chunk: &SPACES[0..to_next_stop],
- skip_leading_tab: to_next_stop > 0,
- }
- }
-
- pub fn highlighted_chunks(&mut self, range: Range<TabPoint>) -> HighlightedChunks {
+ pub fn chunks<'a>(
+ &'a self,
+ range: Range<TabPoint>,
+ theme: Option<&'a SyntaxTheme>,
+ ) -> Chunks<'a> {
let (input_start, expanded_char_column, to_next_stop) =
self.to_fold_point(range.start, Bias::Left);
let input_start = input_start.to_offset(&self.fold_snapshot);
@@ -167,13 +163,19 @@ impl Snapshot {
.to_fold_point(range.end, Bias::Right)
.0
.to_offset(&self.fold_snapshot);
- HighlightedChunks {
- fold_chunks: self
- .fold_snapshot
- .highlighted_chunks(input_start..input_end),
+ let to_next_stop = if range.start.0 + Point::new(0, to_next_stop as u32) > range.end.0 {
+ (range.end.column() - range.start.column()) as usize
+ } else {
+ to_next_stop
+ };
+
+ Chunks {
+ fold_chunks: self.fold_snapshot.chunks(input_start..input_end, theme),
column: expanded_char_column,
+ output_position: range.start.0,
+ max_output_position: range.end.0,
tab_size: self.tab_size,
- chunk: HighlightedChunk {
+ chunk: Chunk {
text: &SPACES[0..to_next_stop],
..Default::default()
},
@@ -187,7 +189,9 @@ impl Snapshot {
#[cfg(test)]
pub fn text(&self) -> String {
- self.chunks_at(Default::default()).collect()
+ self.chunks(TabPoint::zero()..self.max_point(), None)
+ .map(|chunk| chunk.text)
+ .collect()
}
pub fn max_point(&self) -> TabPoint {
@@ -207,6 +211,10 @@ impl Snapshot {
TabPoint::new(input.row(), expanded as u32)
}
+ pub fn from_point(&self, point: Point, bias: Bias) -> TabPoint {
+ self.to_tab_point(point.to_fold_point(&self.fold_snapshot, bias))
+ }
+
pub fn to_fold_point(&self, output: TabPoint, bias: Bias) -> (FoldPoint, usize, usize) {
let chars = self.fold_snapshot.chars_at(FoldPoint::new(output.row(), 0));
let expanded = output.column() as usize;
@@ -219,6 +227,12 @@ impl Snapshot {
)
}
+ pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point {
+ self.to_fold_point(point, bias)
+ .0
+ .to_buffer_point(&self.fold_snapshot)
+ }
+
fn expand_tabs(chars: impl Iterator<Item = char>, column: usize, tab_size: usize) -> usize {
let mut expanded_chars = 0;
let mut expanded_bytes = 0;
@@ -368,63 +382,16 @@ const SPACES: &'static str = " ";
pub struct Chunks<'a> {
fold_chunks: fold_map::Chunks<'a>,
- chunk: &'a str,
+ chunk: Chunk<'a>,
column: usize,
+ output_position: Point,
+ max_output_position: Point,
tab_size: usize,
skip_leading_tab: bool,
}
impl<'a> Iterator for Chunks<'a> {
- type Item = &'a str;
-
- fn next(&mut self) -> Option<Self::Item> {
- if self.chunk.is_empty() {
- if let Some(chunk) = self.fold_chunks.next() {
- self.chunk = chunk;
- if self.skip_leading_tab {
- self.chunk = &self.chunk[1..];
- self.skip_leading_tab = false;
- }
- } else {
- return None;
- }
- }
-
- for (ix, c) in self.chunk.char_indices() {
- match c {
- '\t' => {
- if ix > 0 {
- let (prefix, suffix) = self.chunk.split_at(ix);
- self.chunk = suffix;
- return Some(prefix);
- } else {
- self.chunk = &self.chunk[1..];
- let len = self.tab_size - self.column % self.tab_size;
- self.column += len;
- return Some(&SPACES[0..len]);
- }
- }
- '\n' => self.column = 0,
- _ => self.column += 1,
- }
- }
-
- let result = Some(self.chunk);
- self.chunk = "";
- result
- }
-}
-
-pub struct HighlightedChunks<'a> {
- fold_chunks: fold_map::HighlightedChunks<'a>,
- chunk: HighlightedChunk<'a>,
- column: usize,
- tab_size: usize,
- skip_leading_tab: bool,
-}
-
-impl<'a> Iterator for HighlightedChunks<'a> {
- type Item = HighlightedChunk<'a>;
+ type Item = Chunk<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.chunk.text.is_empty() {
@@ -445,22 +412,34 @@ impl<'a> Iterator for HighlightedChunks<'a> {
if ix > 0 {
let (prefix, suffix) = self.chunk.text.split_at(ix);
self.chunk.text = suffix;
- return Some(HighlightedChunk {
+ return Some(Chunk {
text: prefix,
..self.chunk
});
} else {
self.chunk.text = &self.chunk.text[1..];
- let len = self.tab_size - self.column % self.tab_size;
+ let mut len = self.tab_size - self.column % self.tab_size;
+ let next_output_position = cmp::min(
+ self.output_position + Point::new(0, len as u32),
+ self.max_output_position,
+ );
+ len = (next_output_position.column - self.output_position.column) as usize;
self.column += len;
- return Some(HighlightedChunk {
+ self.output_position = next_output_position;
+ return Some(Chunk {
text: &SPACES[0..len],
..self.chunk
});
}
}
- '\n' => self.column = 0,
- _ => self.column += 1,
+ '\n' => {
+ self.column = 0;
+ self.output_position += Point::new(1, 0);
+ }
+ _ => {
+ self.column += 1;
+ self.output_position.column += c.len_utf8() as u32;
+ }
}
}
@@ -471,6 +450,10 @@ impl<'a> Iterator for HighlightedChunks<'a> {
#[cfg(test)]
mod tests {
use super::*;
+ use crate::display_map::fold_map::FoldMap;
+ use buffer::{RandomCharIter, Rope};
+ use language::Buffer;
+ use rand::{prelude::StdRng, Rng};
#[test]
fn test_expand_tabs() {
@@ -478,4 +461,62 @@ mod tests {
assert_eq!(Snapshot::expand_tabs("\t".chars(), 1, 4), 4);
assert_eq!(Snapshot::expand_tabs("\ta".chars(), 2, 4), 5);
}
+
+ #[gpui::test(iterations = 100)]
+ fn test_random(cx: &mut gpui::MutableAppContext, mut rng: StdRng) {
+ let tab_size = rng.gen_range(1..=4);
+ let buffer = cx.add_model(|cx| {
+ let len = rng.gen_range(0..30);
+ let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
+ Buffer::new(0, text, cx)
+ });
+ log::info!("Buffer text: {:?}", buffer.read(cx).text());
+
+ let (mut fold_map, _) = FoldMap::new(buffer.clone(), cx);
+ fold_map.randomly_mutate(&mut rng, cx);
+ let (folds_snapshot, _) = fold_map.read(cx);
+ log::info!("FoldMap text: {:?}", folds_snapshot.text());
+
+ let (_, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size);
+ let text = Rope::from(tabs_snapshot.text().as_str());
+ log::info!(
+ "TabMap text (tab size: {}): {:?}",
+ tab_size,
+ tabs_snapshot.text(),
+ );
+
+ for _ in 0..5 {
+ let end_row = rng.gen_range(0..=text.max_point().row);
+ let end_column = rng.gen_range(0..=text.line_len(end_row));
+ let mut end = TabPoint(text.clip_point(Point::new(end_row, end_column), Bias::Right));
+ let start_row = rng.gen_range(0..=text.max_point().row);
+ let start_column = rng.gen_range(0..=text.line_len(start_row));
+ let mut start =
+ TabPoint(text.clip_point(Point::new(start_row, start_column), Bias::Left));
+ if start > end {
+ mem::swap(&mut start, &mut end);
+ }
+
+ let expected_text = text
+ .chunks_in_range(text.point_to_offset(start.0)..text.point_to_offset(end.0))
+ .collect::<String>();
+ let expected_summary = TextSummary::from(expected_text.as_str());
+ log::info!("slicing {:?}..{:?} (text: {:?})", start, end, text);
+ assert_eq!(
+ expected_text,
+ tabs_snapshot
+ .chunks(start..end, None)
+ .map(|c| c.text)
+ .collect::<String>()
+ );
+
+ let mut actual_summary = tabs_snapshot.text_summary_for_range(start..end);
+ if tab_size > 1 && folds_snapshot.text().contains('\t') {
+ actual_summary.longest_row = expected_summary.longest_row;
+ actual_summary.longest_row_chars = expected_summary.longest_row_chars;
+ }
+
+ assert_eq!(actual_summary, expected_summary,);
+ }
+ }
}
@@ -1,17 +1,28 @@
use super::{
fold_map,
- tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint, TextSummary},
+ patch::Patch,
+ tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint},
+ DisplayRow,
};
-use gpui::{fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, Task};
-use language::{HighlightedChunk, Point};
+use gpui::{
+ fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, ModelHandle, MutableAppContext,
+ Task,
+};
+use language::{Chunk, Point};
use lazy_static::lazy_static;
use smol::future::yield_now;
-use std::{collections::VecDeque, ops::Range, time::Duration};
+use std::{collections::VecDeque, mem, ops::Range, time::Duration};
use sum_tree::{Bias, Cursor, SumTree};
+use theme::SyntaxTheme;
+
+pub use super::tab_map::TextSummary;
+pub type Edit = buffer::Edit<u32>;
pub struct WrapMap {
snapshot: Snapshot,
pending_edits: VecDeque<(TabSnapshot, Vec<TabEdit>)>,
+ interpolated_edits: Patch,
+ edits_since_sync: Patch,
wrap_width: Option<f32>,
background_task: Option<Task<()>>,
font: (FontId, f32),
@@ -41,18 +52,11 @@ struct TransformSummary {
}
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
-pub struct WrapPoint(super::Point);
+pub struct WrapPoint(pub super::Point);
pub struct Chunks<'a> {
input_chunks: tab_map::Chunks<'a>,
- input_chunk: &'a str,
- output_position: WrapPoint,
- transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>,
-}
-
-pub struct HighlightedChunks<'a> {
- input_chunks: tab_map::HighlightedChunks<'a>,
- input_chunk: HighlightedChunk<'a>,
+ input_chunk: Chunk<'a>,
output_position: WrapPoint,
max_output_row: u32,
transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>,
@@ -73,18 +77,24 @@ impl WrapMap {
font_id: FontId,
font_size: f32,
wrap_width: Option<f32>,
- cx: &mut ModelContext<Self>,
- ) -> Self {
- let mut this = Self {
- font: (font_id, font_size),
- wrap_width: None,
- pending_edits: Default::default(),
- snapshot: Snapshot::new(tab_snapshot),
- background_task: None,
- };
- this.set_wrap_width(wrap_width, cx);
-
- this
+ cx: &mut MutableAppContext,
+ ) -> (ModelHandle<Self>, Snapshot) {
+ let handle = cx.add_model(|cx| {
+ let mut this = Self {
+ font: (font_id, font_size),
+ wrap_width: None,
+ pending_edits: Default::default(),
+ interpolated_edits: Default::default(),
+ edits_since_sync: Default::default(),
+ snapshot: Snapshot::new(tab_snapshot),
+ background_task: None,
+ };
+ this.set_wrap_width(wrap_width, cx);
+ mem::take(&mut this.edits_since_sync);
+ this
+ });
+ let snapshot = handle.read(cx).snapshot.clone();
+ (handle, snapshot)
}
#[cfg(test)]
@@ -97,10 +107,13 @@ impl WrapMap {
tab_snapshot: TabSnapshot,
edits: Vec<TabEdit>,
cx: &mut ModelContext<Self>,
- ) -> Snapshot {
+ ) -> (Snapshot, Vec<Edit>) {
self.pending_edits.push_back((tab_snapshot, edits));
self.flush_edits(cx);
- self.snapshot.clone()
+ (
+ self.snapshot.clone(),
+ mem::take(&mut self.edits_since_sync).into_inner(),
+ )
}
pub fn set_font(&mut self, font_id: FontId, font_size: f32, cx: &mut ModelContext<Self>) {
@@ -122,6 +135,8 @@ impl WrapMap {
fn rewrap(&mut self, cx: &mut ModelContext<Self>) {
self.background_task.take();
+ self.interpolated_edits.clear();
+ self.pending_edits.clear();
if let Some(wrap_width) = self.wrap_width {
let mut new_snapshot = self.snapshot.clone();
@@ -131,7 +146,7 @@ impl WrapMap {
let mut line_wrapper = font_cache.line_wrapper(font_id, font_size);
let tab_snapshot = new_snapshot.tab_snapshot.clone();
let range = TabPoint::zero()..tab_snapshot.max_point();
- new_snapshot
+ let edits = new_snapshot
.update(
tab_snapshot,
&[TabEdit {
@@ -142,22 +157,27 @@ impl WrapMap {
&mut line_wrapper,
)
.await;
- new_snapshot
+ (new_snapshot, edits)
});
match cx
.background()
.block_with_timeout(Duration::from_millis(5), task)
{
- Ok(snapshot) => {
+ Ok((snapshot, edits)) => {
self.snapshot = snapshot;
+ self.edits_since_sync = self.edits_since_sync.compose(&edits);
cx.notify();
}
Err(wrap_task) => {
self.background_task = Some(cx.spawn(|this, mut cx| async move {
- let snapshot = wrap_task.await;
+ let (snapshot, edits) = wrap_task.await;
this.update(&mut cx, |this, cx| {
this.snapshot = snapshot;
+ this.edits_since_sync = this
+ .edits_since_sync
+ .compose(mem::take(&mut this.interpolated_edits).invert())
+ .compose(&edits);
this.background_task = None;
this.flush_edits(cx);
cx.notify();
@@ -166,6 +186,7 @@ impl WrapMap {
}
}
} else {
+ let old_rows = self.snapshot.transforms.summary().output.lines.row + 1;
self.snapshot.transforms = SumTree::new();
let summary = self.snapshot.tab_snapshot.text_summary();
if !summary.lines.is_zero() {
@@ -173,6 +194,14 @@ impl WrapMap {
.transforms
.push(Transform::isomorphic(summary), &());
}
+ let new_rows = self.snapshot.transforms.summary().output.lines.row + 1;
+ self.snapshot.interpolated = false;
+ self.edits_since_sync = self.edits_since_sync.compose(&unsafe {
+ Patch::new_unchecked(vec![Edit {
+ old: 0..old_rows,
+ new: 0..new_rows,
+ }])
+ });
}
}
@@ -202,26 +231,33 @@ impl WrapMap {
let update_task = cx.background().spawn(async move {
let mut line_wrapper = font_cache.line_wrapper(font_id, font_size);
- for (tab_snapshot, edits) in pending_edits {
- snapshot
- .update(tab_snapshot, &edits, wrap_width, &mut line_wrapper)
+ let mut edits = Patch::default();
+ for (tab_snapshot, tab_edits) in pending_edits {
+ let wrap_edits = snapshot
+ .update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper)
.await;
+ edits = edits.compose(&wrap_edits);
}
- snapshot
+ (snapshot, edits)
});
match cx
.background()
.block_with_timeout(Duration::from_millis(1), update_task)
{
- Ok(snapshot) => {
+ Ok((snapshot, output_edits)) => {
self.snapshot = snapshot;
+ self.edits_since_sync = self.edits_since_sync.compose(&output_edits);
}
Err(update_task) => {
self.background_task = Some(cx.spawn(|this, mut cx| async move {
- let snapshot = update_task.await;
+ let (snapshot, edits) = update_task.await;
this.update(&mut cx, |this, cx| {
this.snapshot = snapshot;
+ this.edits_since_sync = this
+ .edits_since_sync
+ .compose(mem::take(&mut this.interpolated_edits).invert())
+ .compose(&edits);
this.background_task = None;
this.flush_edits(cx);
cx.notify();
@@ -238,7 +274,9 @@ impl WrapMap {
if tab_snapshot.version() <= self.snapshot.tab_snapshot.version() {
to_remove_len += 1;
} else {
- self.snapshot.interpolate(tab_snapshot.clone(), &edits);
+ let interpolated_edits = self.snapshot.interpolate(tab_snapshot.clone(), &edits);
+ self.edits_since_sync = self.edits_since_sync.compose(&interpolated_edits);
+ self.interpolated_edits = self.interpolated_edits.compose(&interpolated_edits);
}
}
@@ -262,17 +300,21 @@ impl Snapshot {
}
}
- fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, edits: &[TabEdit]) {
+ fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, tab_edits: &[TabEdit]) -> Patch {
let mut new_transforms;
- if edits.is_empty() {
+ if tab_edits.is_empty() {
new_transforms = self.transforms.clone();
} else {
let mut old_cursor = self.transforms.cursor::<TabPoint>();
- let mut edits = edits.into_iter().peekable();
- new_transforms =
- old_cursor.slice(&edits.peek().unwrap().old_lines.start, Bias::Right, &());
- while let Some(edit) = edits.next() {
+ let mut tab_edits_iter = tab_edits.iter().peekable();
+ new_transforms = old_cursor.slice(
+ &tab_edits_iter.peek().unwrap().old_lines.start,
+ Bias::Right,
+ &(),
+ );
+
+ while let Some(edit) = tab_edits_iter.next() {
if edit.new_lines.start > TabPoint::from(new_transforms.summary().input.lines) {
let summary = new_tab_snapshot.text_summary_for_range(
TabPoint::from(new_transforms.summary().input.lines)..edit.new_lines.start,
@@ -287,7 +329,7 @@ impl Snapshot {
}
old_cursor.seek_forward(&edit.old_lines.end, Bias::Right, &());
- if let Some(next_edit) = edits.peek() {
+ if let Some(next_edit) = tab_edits_iter.peek() {
if next_edit.old_lines.start > old_cursor.end(&()) {
if old_cursor.end(&()) > edit.old_lines.end {
let summary = self
@@ -295,6 +337,7 @@ impl Snapshot {
.text_summary_for_range(edit.old_lines.end..old_cursor.end(&()));
new_transforms.push_or_extend(Transform::isomorphic(summary));
}
+
old_cursor.next(&());
new_transforms.push_tree(
old_cursor.slice(&next_edit.old_lines.start, Bias::Right, &()),
@@ -314,38 +357,44 @@ impl Snapshot {
}
}
- self.transforms = new_transforms;
- self.tab_snapshot = new_tab_snapshot;
- self.interpolated = true;
+ let old_snapshot = mem::replace(
+ self,
+ Snapshot {
+ tab_snapshot: new_tab_snapshot,
+ transforms: new_transforms,
+ interpolated: true,
+ },
+ );
self.check_invariants();
+ old_snapshot.compute_edits(tab_edits, self)
}
async fn update(
&mut self,
new_tab_snapshot: TabSnapshot,
- edits: &[TabEdit],
+ tab_edits: &[TabEdit],
wrap_width: f32,
line_wrapper: &mut LineWrapper,
- ) {
+ ) -> Patch {
#[derive(Debug)]
struct RowEdit {
old_rows: Range<u32>,
new_rows: Range<u32>,
}
- let mut edits = edits.into_iter().peekable();
+ let mut tab_edits_iter = tab_edits.into_iter().peekable();
let mut row_edits = Vec::new();
- while let Some(edit) = edits.next() {
+ while let Some(edit) = tab_edits_iter.next() {
let mut row_edit = RowEdit {
old_rows: edit.old_lines.start.row()..edit.old_lines.end.row() + 1,
new_rows: edit.new_lines.start.row()..edit.new_lines.end.row() + 1,
};
- while let Some(next_edit) = edits.peek() {
+ while let Some(next_edit) = tab_edits_iter.peek() {
if next_edit.old_lines.start.row() <= row_edit.old_rows.end {
row_edit.old_rows.end = next_edit.old_lines.end.row() + 1;
row_edit.new_rows.end = next_edit.new_lines.end.row() + 1;
- edits.next();
+ tab_edits_iter.next();
} else {
break;
}
@@ -370,7 +419,7 @@ impl Snapshot {
while let Some(edit) = row_edits.next() {
if edit.new_rows.start > new_transforms.summary().input.lines.row {
let summary = new_tab_snapshot.text_summary_for_range(
- TabPoint::new(new_transforms.summary().input.lines.row, 0)
+ TabPoint(new_transforms.summary().input.lines)
..TabPoint::new(edit.new_rows.start, 0),
);
new_transforms.push_or_extend(Transform::isomorphic(summary));
@@ -378,10 +427,15 @@ impl Snapshot {
let mut line = String::new();
let mut remaining = None;
- let mut chunks = new_tab_snapshot.chunks_at(TabPoint::new(edit.new_rows.start, 0));
+ let mut chunks = new_tab_snapshot.chunks(
+ TabPoint::new(edit.new_rows.start, 0)..new_tab_snapshot.max_point(),
+ None,
+ );
let mut edit_transforms = Vec::<Transform>::new();
for _ in edit.new_rows.start..edit.new_rows.end {
- while let Some(chunk) = remaining.take().or_else(|| chunks.next()) {
+ while let Some(chunk) =
+ remaining.take().or_else(|| chunks.next().map(|c| c.text))
+ {
if let Some(ix) = chunk.find('\n') {
line.push_str(&chunk[..ix + 1]);
remaining = Some(&chunk[ix + 1..]);
@@ -452,30 +506,60 @@ impl Snapshot {
}
}
- self.transforms = new_transforms;
- self.tab_snapshot = new_tab_snapshot;
- self.interpolated = false;
+ let old_snapshot = mem::replace(
+ self,
+ Snapshot {
+ tab_snapshot: new_tab_snapshot,
+ transforms: new_transforms,
+ interpolated: false,
+ },
+ );
self.check_invariants();
+ old_snapshot.compute_edits(tab_edits, self)
}
- pub fn chunks_at(&self, wrap_row: u32) -> Chunks {
- let point = WrapPoint::new(wrap_row, 0);
- let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>();
- transforms.seek(&point, Bias::Right, &());
- let mut input_position = TabPoint(transforms.start().1 .0);
- if transforms.item().map_or(false, |t| t.is_isomorphic()) {
- input_position.0 += point.0 - transforms.start().0 .0;
- }
- let input_chunks = self.tab_snapshot.chunks_at(input_position);
- Chunks {
- input_chunks,
- transforms,
- output_position: point,
- input_chunk: "",
+ fn compute_edits(&self, tab_edits: &[TabEdit], new_snapshot: &Snapshot) -> Patch {
+ let mut wrap_edits = Vec::new();
+ let mut old_cursor = self.transforms.cursor::<TransformSummary>();
+ let mut new_cursor = new_snapshot.transforms.cursor::<TransformSummary>();
+ for mut tab_edit in tab_edits.iter().cloned() {
+ tab_edit.old_lines.start.0.column = 0;
+ tab_edit.old_lines.end.0 += Point::new(1, 0);
+ tab_edit.new_lines.start.0.column = 0;
+ tab_edit.new_lines.end.0 += Point::new(1, 0);
+
+ old_cursor.seek(&tab_edit.old_lines.start, Bias::Right, &());
+ let mut old_start = old_cursor.start().output.lines;
+ old_start += tab_edit.old_lines.start.0 - old_cursor.start().input.lines;
+
+ old_cursor.seek(&tab_edit.old_lines.end, Bias::Right, &());
+ let mut old_end = old_cursor.start().output.lines;
+ old_end += tab_edit.old_lines.end.0 - old_cursor.start().input.lines;
+
+ new_cursor.seek(&tab_edit.new_lines.start, Bias::Right, &());
+ let mut new_start = new_cursor.start().output.lines;
+ new_start += tab_edit.new_lines.start.0 - new_cursor.start().input.lines;
+
+ new_cursor.seek(&tab_edit.new_lines.end, Bias::Right, &());
+ let mut new_end = new_cursor.start().output.lines;
+ new_end += tab_edit.new_lines.end.0 - new_cursor.start().input.lines;
+
+ wrap_edits.push(Edit {
+ old: old_start.row..old_end.row,
+ new: new_start.row..new_end.row,
+ });
}
+
+ consolidate_wrap_edits(&mut wrap_edits);
+ unsafe { Patch::new_unchecked(wrap_edits) }
+ }
+
+ pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator<Item = &str> {
+ self.chunks(wrap_row..self.max_point().row() + 1, None)
+ .map(|h| h.text)
}
- pub fn highlighted_chunks_for_rows(&mut self, rows: Range<u32>) -> HighlightedChunks {
+ pub fn chunks<'a>(&'a self, rows: Range<u32>, theme: Option<&'a SyntaxTheme>) -> Chunks<'a> {
let output_start = WrapPoint::new(rows.start, 0);
let output_end = WrapPoint::new(rows.end, 0);
let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>();
@@ -487,8 +571,8 @@ impl Snapshot {
let input_end = self
.to_tab_point(output_end)
.min(self.tab_snapshot.max_point());
- HighlightedChunks {
- input_chunks: self.tab_snapshot.highlighted_chunks(input_start..input_end),
+ Chunks {
+ input_chunks: self.tab_snapshot.chunks(input_start..input_end, theme),
input_chunk: Default::default(),
output_position: output_start,
max_output_row: rows.end,
@@ -496,13 +580,17 @@ impl Snapshot {
}
}
+ pub fn text_summary(&self) -> TextSummary {
+ self.transforms.summary().output
+ }
+
pub fn max_point(&self) -> WrapPoint {
- self.to_wrap_point(self.tab_snapshot.max_point())
+ WrapPoint(self.transforms.summary().output.lines)
}
pub fn line_len(&self, row: u32) -> u32 {
let mut len = 0;
- for chunk in self.chunks_at(row) {
+ for chunk in self.text_chunks(row) {
if let Some(newline_ix) = chunk.find('\n') {
len += newline_ix;
break;
@@ -513,6 +601,13 @@ impl Snapshot {
len as u32
}
+ pub fn line_char_count(&self, row: u32) -> u32 {
+ self.text_chunks(row)
+ .flat_map(|c| c.chars())
+ .take_while(|c| *c != '\n')
+ .count() as u32
+ }
+
pub fn soft_wrap_indent(&self, row: u32) -> Option<u32> {
let mut cursor = self.transforms.cursor::<WrapPoint>();
cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Right, &());
@@ -559,7 +654,15 @@ impl Snapshot {
TabPoint(tab_point)
}
- pub fn to_wrap_point(&self, point: TabPoint) -> WrapPoint {
+ pub fn to_point(&self, point: WrapPoint, bias: Bias) -> Point {
+ self.tab_snapshot.to_point(self.to_tab_point(point), bias)
+ }
+
+ pub fn from_point(&self, point: Point, bias: Bias) -> WrapPoint {
+ self.from_tab_point(self.tab_snapshot.from_point(point, bias))
+ }
+
+ pub fn from_tab_point(&self, point: TabPoint) -> WrapPoint {
let mut cursor = self.transforms.cursor::<(TabPoint, WrapPoint)>();
cursor.seek(&point, Bias::Right, &());
WrapPoint(cursor.start().1 .0 + (point.0 - cursor.start().0 .0))
@@ -575,7 +678,7 @@ impl Snapshot {
}
}
- self.to_wrap_point(self.tab_snapshot.clip_point(self.to_tab_point(point), bias))
+ self.from_tab_point(self.tab_snapshot.clip_point(self.to_tab_point(point), bias))
}
fn check_invariants(&self) {
@@ -610,7 +713,11 @@ impl Snapshot {
prev_tab_row = tab_point.row();
soft_wrapped = false;
}
- expected_buffer_rows.push((buffer_row, soft_wrapped));
+ expected_buffer_rows.push(if soft_wrapped {
+ DisplayRow::Wrap
+ } else {
+ DisplayRow::Buffer(buffer_row)
+ });
}
for start_display_row in 0..expected_buffer_rows.len() {
@@ -627,52 +734,7 @@ impl Snapshot {
}
impl<'a> Iterator for Chunks<'a> {
- type Item = &'a str;
-
- fn next(&mut self) -> Option<Self::Item> {
- let transform = self.transforms.item()?;
- if let Some(display_text) = transform.display_text {
- if self.output_position > self.transforms.start().0 {
- self.output_position.0.column += transform.summary.output.lines.column;
- self.transforms.next(&());
- return Some(&display_text[1..]);
- } else {
- self.output_position.0 += transform.summary.output.lines;
- self.transforms.next(&());
- return Some(display_text);
- }
- }
-
- if self.input_chunk.is_empty() {
- self.input_chunk = self.input_chunks.next().unwrap();
- }
-
- let mut input_len = 0;
- let transform_end = self.transforms.end(&()).0;
- for c in self.input_chunk.chars() {
- let char_len = c.len_utf8();
- input_len += char_len;
- if c == '\n' {
- *self.output_position.row_mut() += 1;
- *self.output_position.column_mut() = 0;
- } else {
- *self.output_position.column_mut() += char_len as u32;
- }
-
- if self.output_position >= transform_end {
- self.transforms.next(&());
- break;
- }
- }
-
- let (prefix, suffix) = self.input_chunk.split_at(input_len);
- self.input_chunk = suffix;
- Some(prefix)
- }
-}
-
-impl<'a> Iterator for HighlightedChunks<'a> {
- type Item = HighlightedChunk<'a>;
+ type Item = Chunk<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.output_position.row() >= self.max_output_row {
@@ -697,7 +759,7 @@ impl<'a> Iterator for HighlightedChunks<'a> {
self.output_position.0 += summary;
self.transforms.next(&());
- return Some(HighlightedChunk {
+ return Some(Chunk {
text: &display_text[start_ix..end_ix],
..self.input_chunk
});
@@ -727,7 +789,7 @@ impl<'a> Iterator for HighlightedChunks<'a> {
let (prefix, suffix) = self.input_chunk.text.split_at(input_len);
self.input_chunk.text = suffix;
- Some(HighlightedChunk {
+ Some(Chunk {
text: prefix,
..self.input_chunk
})
@@ -735,7 +797,7 @@ impl<'a> Iterator for HighlightedChunks<'a> {
}
impl<'a> Iterator for BufferRows<'a> {
- type Item = (u32, bool);
+ type Item = DisplayRow;
fn next(&mut self) -> Option<Self::Item> {
if self.output_row > self.max_output_row {
@@ -755,7 +817,11 @@ impl<'a> Iterator for BufferRows<'a> {
self.soft_wrapped = true;
}
- Some((buffer_row, soft_wrapped))
+ Some(if soft_wrapped {
+ DisplayRow::Wrap
+ } else {
+ DisplayRow::Buffer(buffer_row)
+ })
}
}
@@ -851,23 +917,18 @@ impl WrapPoint {
Self(super::Point::new(row, column))
}
- #[cfg(test)]
- pub fn is_zero(&self) -> bool {
- self.0.is_zero()
- }
-
pub fn row(self) -> u32 {
self.0.row
}
- pub fn column(self) -> u32 {
- self.0.column
- }
-
pub fn row_mut(&mut self) -> &mut u32 {
&mut self.0.row
}
+ pub fn column(&self) -> u32 {
+ self.0.column
+ }
+
pub fn column_mut(&mut self) -> &mut u32 {
&mut self.0.column
}
@@ -888,12 +949,33 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for TabPoint {
}
}
+impl<'a> sum_tree::SeekTarget<'a, TransformSummary, TransformSummary> for TabPoint {
+ fn cmp(&self, cursor_location: &TransformSummary, _: &()) -> std::cmp::Ordering {
+ Ord::cmp(&self.0, &cursor_location.input.lines)
+ }
+}
+
impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint {
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
self.0 += summary.output.lines;
}
}
+fn consolidate_wrap_edits(edits: &mut Vec<Edit>) {
+ let mut i = 1;
+ while i < edits.len() {
+ let edit = edits[i].clone();
+ let prev_edit = &mut edits[i - 1];
+ if prev_edit.old.end >= edit.old.start {
+ prev_edit.old.end = edit.old.end;
+ prev_edit.new.end = edit.new.end;
+ edits.remove(i);
+ continue;
+ }
+ i += 1;
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -901,9 +983,10 @@ mod tests {
display_map::{fold_map::FoldMap, tab_map::TabMap},
test::Observer,
};
+ use buffer::Rope;
use language::{Buffer, RandomCharIter};
use rand::prelude::*;
- use std::env;
+ use std::{cmp, env};
#[gpui::test(iterations = 100)]
async fn test_random_wraps(mut cx: gpui::TestAppContext, mut rng: StdRng) {
@@ -951,17 +1034,20 @@ mod tests {
let unwrapped_text = tabs_snapshot.text();
let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);
- let wrap_map = cx.add_model(|cx| {
- WrapMap::new(tabs_snapshot.clone(), font_id, font_size, wrap_width, cx)
- });
+ let (wrap_map, _) =
+ cx.update(|cx| WrapMap::new(tabs_snapshot.clone(), font_id, font_size, wrap_width, cx));
let (_observer, notifications) = Observer::new(&wrap_map, &mut cx);
if wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) {
notifications.recv().await.unwrap();
}
- let snapshot = wrap_map.update(&mut cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx));
- let actual_text = snapshot.text();
+ let (initial_snapshot, _) = wrap_map.update(&mut cx, |map, cx| {
+ assert!(!map.is_rewrapping());
+ map.sync(tabs_snapshot.clone(), Vec::new(), cx)
+ });
+
+ let actual_text = initial_snapshot.text();
assert_eq!(
actual_text, expected_text,
"unwrapped text is: {:?}",
@@ -969,7 +1055,10 @@ mod tests {
);
log::info!("Wrapped text: {:?}", actual_text);
+ let mut edits = Vec::new();
for _i in 0..operations {
+ log::info!("{} ==============================================", _i);
+
match rng.gen_range(0..=100) {
0..=19 => {
wrap_width = if rng.gen_bool(0.2) {
@@ -981,14 +1070,15 @@ mod tests {
wrap_map.update(&mut cx, |map, cx| map.set_wrap_width(wrap_width, cx));
}
20..=39 => {
- for (folds_snapshot, edits) in
+ for (folds_snapshot, fold_edits) in
cx.read(|cx| fold_map.randomly_mutate(&mut rng, cx))
{
- let (tabs_snapshot, edits) = tab_map.sync(folds_snapshot, edits);
- let mut snapshot =
- wrap_map.update(&mut cx, |map, cx| map.sync(tabs_snapshot, edits, cx));
+ let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
+ let (mut snapshot, wrap_edits) = wrap_map
+ .update(&mut cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
snapshot.check_invariants();
snapshot.verify_chunks(&mut rng);
+ edits.push((snapshot, wrap_edits));
}
}
_ => {
@@ -1000,21 +1090,22 @@ mod tests {
"Unwrapped text (no folds): {:?}",
buffer.read_with(&cx, |buf, _| buf.text())
);
- let (folds_snapshot, edits) = cx.read(|cx| fold_map.read(cx));
+ let (folds_snapshot, fold_edits) = cx.read(|cx| fold_map.read(cx));
log::info!(
"Unwrapped text (unexpanded tabs): {:?}",
folds_snapshot.text()
);
- let (tabs_snapshot, edits) = tab_map.sync(folds_snapshot, edits);
+ let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
log::info!("Unwrapped text (expanded tabs): {:?}", tabs_snapshot.text());
let unwrapped_text = tabs_snapshot.text();
let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);
- let mut snapshot = wrap_map.update(&mut cx, |map, cx| {
- map.sync(tabs_snapshot.clone(), edits, cx)
+ let (mut snapshot, wrap_edits) = wrap_map.update(&mut cx, |map, cx| {
+ map.sync(tabs_snapshot.clone(), tab_edits, cx)
});
snapshot.check_invariants();
snapshot.verify_chunks(&mut rng);
+ edits.push((snapshot, wrap_edits));
if wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) && rng.gen_bool(0.4) {
log::info!("Waiting for wrapping to finish");
@@ -1024,18 +1115,83 @@ mod tests {
}
if !wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) {
- let mut wrapped_snapshot =
+ let (mut wrapped_snapshot, wrap_edits) =
wrap_map.update(&mut cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx));
let actual_text = wrapped_snapshot.text();
+ let actual_longest_row = wrapped_snapshot.longest_row();
log::info!("Wrapping finished: {:?}", actual_text);
wrapped_snapshot.check_invariants();
wrapped_snapshot.verify_chunks(&mut rng);
+ edits.push((wrapped_snapshot.clone(), wrap_edits));
assert_eq!(
actual_text, expected_text,
"unwrapped text is: {:?}",
unwrapped_text
);
+
+ let mut summary = TextSummary::default();
+ for (ix, item) in wrapped_snapshot
+ .transforms
+ .items(&())
+ .into_iter()
+ .enumerate()
+ {
+ summary += &item.summary.output;
+ log::info!("{} summary: {:?}", ix, item.summary.output,);
+ }
+
+ if tab_size == 1
+ || !wrapped_snapshot
+ .tab_snapshot
+ .fold_snapshot
+ .text()
+ .contains('\t')
+ {
+ let mut expected_longest_rows = Vec::new();
+ let mut longest_line_len = -1;
+ for (row, line) in expected_text.split('\n').enumerate() {
+ let line_char_count = line.chars().count() as isize;
+ if line_char_count > longest_line_len {
+ expected_longest_rows.clear();
+ longest_line_len = line_char_count;
+ }
+ if line_char_count >= longest_line_len {
+ expected_longest_rows.push(row as u32);
+ }
+ }
+
+ assert!(
+ expected_longest_rows.contains(&actual_longest_row),
+ "incorrect longest row {}. expected {:?} with length {}",
+ actual_longest_row,
+ expected_longest_rows,
+ longest_line_len,
+ )
+ }
+ }
+ }
+
+ let mut initial_text = Rope::from(initial_snapshot.text().as_str());
+ 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_end = initial_text.point_to_offset(cmp::min(
+ Point::new(edit.new.start + edit.old.len() as u32, 0),
+ initial_text.max_point(),
+ ));
+ let new_start = snapshot_text.point_to_offset(Point::new(edit.new.start, 0));
+ let new_end = snapshot_text.point_to_offset(cmp::min(
+ Point::new(edit.new.end, 0),
+ snapshot_text.max_point(),
+ ));
+ let new_text = snapshot_text
+ .chunks_in_range(new_start..new_end)
+ .collect::<String>();
+
+ initial_text.replace(old_start..old_end, &new_text);
}
+ assert_eq!(initial_text.to_string(), snapshot_text.to_string());
}
}
@@ -1067,8 +1223,8 @@ mod tests {
}
impl Snapshot {
- fn text(&self) -> String {
- self.chunks_at(0).collect()
+ pub fn text(&self) -> String {
+ self.text_chunks(0).collect()
}
fn verify_chunks(&mut self, rng: &mut impl Rng) {
@@ -1077,7 +1233,7 @@ mod tests {
let start_row = rng.gen_range(0..=end_row);
end_row += 1;
- let mut expected_text = self.chunks_at(start_row).collect::<String>();
+ let mut expected_text = self.text_chunks(start_row).collect::<String>();
if expected_text.ends_with("\n") {
expected_text.push('\n');
}
@@ -1091,7 +1247,7 @@ mod tests {
}
let actual_text = self
- .highlighted_chunks_for_rows(start_row..end_row)
+ .chunks(start_row..end_row, None)
.map(|c| c.text)
.collect::<String>();
assert_eq!(
@@ -1,6 +1,6 @@
use super::{
- DisplayPoint, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll, Select,
- SelectPhase, Snapshot, MAX_LINE_LEN,
+ DisplayPoint, DisplayRow, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll,
+ Select, SelectPhase, Snapshot, MAX_LINE_LEN,
};
use clock::ReplicaId;
use gpui::{
@@ -17,7 +17,7 @@ use gpui::{
MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle,
};
use json::json;
-use language::{DiagnosticSeverity, HighlightedChunk};
+use language::Chunk;
use smallvec::SmallVec;
use std::{
cmp::{self, Ordering},
@@ -25,6 +25,7 @@ use std::{
fmt::Write,
ops::Range,
};
+use theme::BlockStyle;
pub struct EditorElement {
view: WeakViewHandle<Editor>,
@@ -195,6 +196,7 @@ impl EditorElement {
) {
let bounds = gutter_bounds.union_rect(text_bounds);
let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height;
+ let start_row = layout.snapshot.scroll_position().y() as u32;
let editor = self.view(cx.app);
let style = &self.settings.style;
cx.scene.push_quad(Quad {
@@ -239,6 +241,51 @@ impl EditorElement {
}
}
}
+
+ // Draw block backgrounds
+ for (ixs, block_style) in &layout.block_layouts {
+ let row = start_row + ixs.start;
+ let offset = vec2f(0., row as f32 * layout.line_height - scroll_top);
+ let height = ixs.len() as f32 * layout.line_height;
+ cx.scene.push_quad(Quad {
+ bounds: RectF::new(
+ text_bounds.origin() + offset,
+ vec2f(text_bounds.width(), height),
+ ),
+ background: block_style.background,
+ border: block_style
+ .border
+ .map_or(Default::default(), |color| Border {
+ width: 1.,
+ color,
+ overlay: true,
+ top: true,
+ right: false,
+ bottom: true,
+ left: false,
+ }),
+ corner_radius: 0.,
+ });
+ cx.scene.push_quad(Quad {
+ bounds: RectF::new(
+ gutter_bounds.origin() + offset,
+ vec2f(gutter_bounds.width(), height),
+ ),
+ background: block_style.gutter_background,
+ border: block_style
+ .gutter_border
+ .map_or(Default::default(), |color| Border {
+ width: 1.,
+ color,
+ overlay: true,
+ top: true,
+ right: false,
+ bottom: true,
+ left: false,
+ }),
+ corner_radius: 0.,
+ });
+ }
}
fn paint_gutter(
@@ -401,18 +448,24 @@ impl EditorElement {
.width()
}
- fn layout_line_numbers(
+ fn layout_rows(
&self,
rows: Range<u32>,
active_rows: &BTreeMap<u32, bool>,
snapshot: &Snapshot,
cx: &LayoutContext,
- ) -> Vec<Option<text_layout::Line>> {
+ ) -> (
+ Vec<Option<text_layout::Line>>,
+ Vec<(Range<u32>, BlockStyle)>,
+ ) {
let style = &self.settings.style;
- let mut layouts = Vec::with_capacity(rows.len());
+ let include_line_numbers = snapshot.mode == EditorMode::Full;
+ let mut last_block_id = None;
+ let mut blocks = Vec::<(Range<u32>, BlockStyle)>::new();
+ let mut line_number_layouts = Vec::with_capacity(rows.len());
let mut line_number = String::new();
- for (ix, (buffer_row, soft_wrapped)) in snapshot
- .buffer_rows(rows.start)
+ for (ix, row) in snapshot
+ .buffer_rows(rows.start, cx)
.take((rows.end - rows.start) as usize)
.enumerate()
{
@@ -422,27 +475,46 @@ impl EditorElement {
} else {
style.line_number
};
- if soft_wrapped {
- layouts.push(None);
- } else {
- line_number.clear();
- write!(&mut line_number, "{}", buffer_row + 1).unwrap();
- layouts.push(Some(cx.text_layout_cache.layout_str(
- &line_number,
- style.text.font_size,
- &[(
- line_number.len(),
- RunStyle {
- font_id: style.text.font_id,
- color,
- underline: None,
- },
- )],
- )));
+ match row {
+ DisplayRow::Buffer(buffer_row) => {
+ if include_line_numbers {
+ line_number.clear();
+ write!(&mut line_number, "{}", buffer_row + 1).unwrap();
+ line_number_layouts.push(Some(cx.text_layout_cache.layout_str(
+ &line_number,
+ style.text.font_size,
+ &[(
+ line_number.len(),
+ RunStyle {
+ font_id: style.text.font_id,
+ color,
+ underline: None,
+ },
+ )],
+ )));
+ }
+ last_block_id = None;
+ }
+ DisplayRow::Block(block_id, style) => {
+ let ix = ix as u32;
+ if last_block_id == Some(block_id) {
+ if let Some((row_range, _)) = blocks.last_mut() {
+ row_range.end += 1;
+ }
+ } else if let Some(style) = style {
+ blocks.push((ix..ix + 1, style));
+ }
+ line_number_layouts.push(None);
+ last_block_id = Some(block_id);
+ }
+ DisplayRow::Wrap => {
+ line_number_layouts.push(None);
+ last_block_id = None;
+ }
}
}
- layouts
+ (line_number_layouts, blocks)
}
fn layout_lines(
@@ -493,9 +565,9 @@ impl EditorElement {
let mut styles = Vec::new();
let mut row = rows.start;
let mut line_exceeded_max_len = false;
- let chunks = snapshot.highlighted_chunks_for_rows(rows.clone());
+ let chunks = snapshot.chunks(rows.clone(), Some(&style.syntax), cx);
- let newline_chunk = HighlightedChunk {
+ let newline_chunk = Chunk {
text: "\n",
..Default::default()
};
@@ -517,10 +589,8 @@ impl EditorElement {
}
if !line_chunk.is_empty() && !line_exceeded_max_len {
- let highlight_style = chunk
- .highlight_id
- .style(&style.syntax)
- .unwrap_or(style.text.clone().into());
+ let highlight_style =
+ chunk.highlight_style.unwrap_or(style.text.clone().into());
// Avoid a lookup if the font properties match the previous ones.
let font_id = if highlight_style.font_properties == prev_font_properties {
prev_font_id
@@ -543,13 +613,7 @@ impl EditorElement {
}
let underline = if let Some(severity) = chunk.diagnostic {
- match severity {
- DiagnosticSeverity::ERROR => Some(style.error_underline),
- DiagnosticSeverity::WARNING => Some(style.warning_underline),
- DiagnosticSeverity::INFORMATION => Some(style.information_underline),
- DiagnosticSeverity::HINT => Some(style.hint_underline),
- _ => highlight_style.underline,
- }
+ Some(super::diagnostic_style(severity, true, style).text)
} else {
highlight_style.underline
};
@@ -677,11 +741,8 @@ impl Element for EditorElement {
}
});
- let line_number_layouts = if snapshot.mode == EditorMode::Full {
- self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx)
- } else {
- Vec::new()
- };
+ let (line_number_layouts, block_layouts) =
+ self.layout_rows(start_row..end_row, &active_rows, &snapshot, cx);
let mut max_visible_line_width = 0.0;
let line_layouts = self.layout_lines(start_row..end_row, &mut snapshot, cx);
@@ -703,6 +764,7 @@ impl Element for EditorElement {
active_rows,
line_layouts,
line_number_layouts,
+ block_layouts,
line_height,
em_width,
selections,
@@ -825,6 +887,7 @@ pub struct LayoutState {
active_rows: BTreeMap<u32, bool>,
line_layouts: Vec<text_layout::Line>,
line_number_layouts: Vec<Option<text_layout::Line>>,
+ block_layouts: Vec<(Range<u32>, BlockStyle)>,
line_height: f32,
em_width: f32,
selections: HashMap<ReplicaId, Vec<Range<DisplayPoint>>>,
@@ -1079,11 +1142,11 @@ mod tests {
});
let element = EditorElement::new(editor.downgrade(), settings);
- let layouts = editor.update(cx, |editor, cx| {
+ let (layouts, _) = editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
let mut presenter = cx.build_presenter(window_id, 30.);
let mut layout_cx = presenter.build_layout_context(false, cx);
- element.layout_line_numbers(0..6, &Default::default(), &snapshot, &mut layout_cx)
+ element.layout_rows(0..6, &Default::default(), &snapshot, &mut layout_cx)
});
assert_eq!(layouts.len(), 6);
}
@@ -7,13 +7,15 @@ mod test;
use buffer::rope::TextDimension;
use clock::ReplicaId;
-pub use display_map::DisplayPoint;
use display_map::*;
+pub use display_map::{DisplayPoint, DisplayRow};
pub use element::*;
use gpui::{
- action, geometry::vector::Vector2F, keymap::Binding, text_layout, AppContext, ClipboardItem,
- Element, ElementBox, Entity, ModelHandle, MutableAppContext, RenderContext, View, ViewContext,
- WeakViewHandle,
+ action,
+ geometry::vector::{vec2f, Vector2F},
+ keymap::Binding,
+ text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle,
+ MutableAppContext, RenderContext, View, ViewContext, WeakViewHandle,
};
use language::*;
use serde::{Deserialize, Serialize};
@@ -22,6 +24,7 @@ use smol::Timer;
use std::{
cell::RefCell,
cmp::{self, Ordering},
+ collections::HashMap,
iter, mem,
ops::{Range, RangeInclusive},
rc::Rc,
@@ -29,7 +32,7 @@ use std::{
time::Duration,
};
use sum_tree::Bias;
-use theme::EditorStyle;
+use theme::{DiagnosticStyle, EditorStyle, SyntaxTheme};
use util::post_inc;
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
@@ -83,6 +86,7 @@ action!(AddSelectionBelow);
action!(SelectLargerSyntaxNode);
action!(SelectSmallerSyntaxNode);
action!(MoveToEnclosingBracket);
+action!(ShowNextDiagnostic);
action!(PageUp);
action!(PageDown);
action!(Fold);
@@ -184,6 +188,7 @@ pub fn init(cx: &mut MutableAppContext) {
Binding::new("ctrl-w", SelectLargerSyntaxNode, Some("Editor")),
Binding::new("alt-down", SelectSmallerSyntaxNode, Some("Editor")),
Binding::new("ctrl-shift-W", SelectSmallerSyntaxNode, Some("Editor")),
+ Binding::new("f8", ShowNextDiagnostic, Some("Editor")),
Binding::new("ctrl-m", MoveToEnclosingBracket, Some("Editor")),
Binding::new("pageup", PageUp, Some("Editor")),
Binding::new("pagedown", PageDown, Some("Editor")),
@@ -242,6 +247,7 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(Editor::select_larger_syntax_node);
cx.add_action(Editor::select_smaller_syntax_node);
cx.add_action(Editor::move_to_enclosing_bracket);
+ cx.add_action(Editor::show_next_diagnostic);
cx.add_action(Editor::page_up);
cx.add_action(Editor::page_down);
cx.add_action(Editor::fold);
@@ -299,6 +305,7 @@ pub struct Editor {
add_selections_state: Option<AddSelectionsState>,
autoclose_stack: Vec<BracketPairState>,
select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
+ active_diagnostics: Option<ActiveDiagnosticGroup>,
scroll_position: Vector2F,
scroll_top_anchor: Anchor,
autoscroll_requested: bool,
@@ -331,6 +338,14 @@ struct BracketPairState {
pair: BracketPair,
}
+#[derive(Debug)]
+struct ActiveDiagnosticGroup {
+ primary_range: Range<Anchor>,
+ primary_message: String,
+ blocks: HashMap<BlockId, Diagnostic>,
+ is_valid: bool,
+}
+
#[derive(Serialize, Deserialize)]
struct ClipboardSelection {
len: usize,
@@ -418,6 +433,7 @@ impl Editor {
add_selections_state: None,
autoclose_stack: Default::default(),
select_larger_syntax_node_stack: Vec::new(),
+ active_diagnostics: None,
build_settings,
scroll_position: Vector2F::zero(),
scroll_top_anchor: Anchor::min(),
@@ -466,16 +482,24 @@ impl Editor {
cx.notify();
}
- fn set_scroll_position(&mut self, mut scroll_position: Vector2F, cx: &mut ViewContext<Self>) {
+ fn set_scroll_position(&mut self, scroll_position: Vector2F, cx: &mut ViewContext<Self>) {
let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let scroll_top_buffer_offset =
- DisplayPoint::new(scroll_position.y() as u32, 0).to_buffer_offset(&map, Bias::Right);
+ DisplayPoint::new(scroll_position.y() as u32, 0).to_offset(&map, Bias::Right);
self.scroll_top_anchor = self
.buffer
.read(cx)
.anchor_at(scroll_top_buffer_offset, Bias::Right);
- scroll_position.set_y(scroll_position.y().fract());
- self.scroll_position = scroll_position;
+ self.scroll_position = vec2f(
+ scroll_position.x(),
+ scroll_position.y() - self.scroll_top_anchor.to_display_point(&map).row() as f32,
+ );
+
+ debug_assert_eq!(
+ compute_scroll_position(&map, self.scroll_position, &self.scroll_top_anchor),
+ scroll_position
+ );
+
cx.notify();
}
@@ -519,13 +543,13 @@ impl Editor {
.peek()
.unwrap()
.head()
- .to_display_point(&display_map, Bias::Left)
+ .to_display_point(&display_map)
.row() as f32;
let last_cursor_bottom = selections
.last()
.unwrap()
.head()
- .to_display_point(&display_map, Bias::Right)
+ .to_display_point(&display_map)
.row() as f32
+ 1.0;
@@ -570,7 +594,7 @@ impl Editor {
let mut target_left = std::f32::INFINITY;
let mut target_right = 0.0_f32;
for selection in selections {
- let head = selection.head().to_display_point(&display_map, Bias::Left);
+ let head = selection.head().to_display_point(&display_map);
let start_column = head.column().saturating_sub(3);
let end_column = cmp::min(display_map.line_len(head.row()), head.column() + 3);
target_left = target_left
@@ -620,7 +644,7 @@ impl Editor {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = self.buffer.read(cx);
- let cursor = buffer.anchor_before(position.to_buffer_point(&display_map, Bias::Left));
+ let cursor = buffer.anchor_before(position.to_point(&display_map));
let selection = Selection {
id: post_inc(&mut self.next_selection_id),
start: cursor.clone(),
@@ -646,7 +670,7 @@ impl Editor {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
if let Some(pending_selection) = self.pending_selection.as_mut() {
let buffer = self.buffer.read(cx);
- let cursor = buffer.anchor_before(position.to_buffer_point(&display_map, Bias::Left));
+ let cursor = buffer.anchor_before(position.to_point(&display_map));
if cursor.cmp(&pending_selection.tail(), buffer).unwrap() < Ordering::Equal {
if !pending_selection.reversed {
pending_selection.end = pending_selection.start.clone();
@@ -681,7 +705,9 @@ impl Editor {
}
pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
- if let Some(pending_selection) = self.pending_selection.take() {
+ if self.active_diagnostics.is_some() {
+ self.dismiss_diagnostics(cx);
+ } else if let Some(pending_selection) = self.pending_selection.take() {
let buffer = self.buffer.read(cx);
let pending_selection = Selection {
id: pending_selection.id,
@@ -694,16 +720,8 @@ impl Editor {
self.update_selections(vec![pending_selection], true, cx);
}
} else {
- let selections = self.selections::<Point>(cx);
- let mut selection_count = 0;
- let mut oldest_selection = selections
- .min_by_key(|s| {
- selection_count += 1;
- s.id
- })
- .unwrap()
- .clone();
- if selection_count == 1 {
+ let mut oldest_selection = self.oldest_selection::<usize>(cx);
+ if self.selection_count(cx) == 1 {
oldest_selection.start = oldest_selection.head().clone();
oldest_selection.end = oldest_selection.head().clone();
}
@@ -763,8 +781,8 @@ impl Editor {
};
Selection {
id: post_inc(&mut self.next_selection_id),
- start: start.to_buffer_point(&display_map, Bias::Left),
- end: end.to_buffer_point(&display_map, Bias::Left),
+ start: start.to_point(&display_map),
+ end: end.to_point(&display_map),
reversed,
goal: SelectionGoal::None,
}
@@ -1052,10 +1070,10 @@ impl Editor {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
for selection in &mut selections {
if selection.is_empty() {
- let head = selection.head().to_display_point(&display_map, Bias::Left);
+ let head = selection.head().to_display_point(&display_map);
let cursor = movement::left(&display_map, head)
.unwrap()
- .to_buffer_point(&display_map, Bias::Left);
+ .to_point(&display_map);
selection.set_head(cursor);
selection.goal = SelectionGoal::None;
}
@@ -1071,10 +1089,10 @@ impl Editor {
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
for selection in &mut selections {
if selection.is_empty() {
- let head = selection.head().to_display_point(&display_map, Bias::Left);
+ let head = selection.head().to_display_point(&display_map);
let cursor = movement::right(&display_map, head)
.unwrap()
- .to_buffer_point(&display_map, Bias::Right);
+ .to_point(&display_map);
selection.set_head(cursor);
selection.goal = SelectionGoal::None;
}
@@ -1138,10 +1156,7 @@ impl Editor {
let mut selections = selections.iter().peekable();
while let Some(selection) = selections.next() {
let mut rows = selection.spanned_rows(false, &display_map).buffer_rows;
- let goal_display_column = selection
- .head()
- .to_display_point(&display_map, Bias::Left)
- .column();
+ let goal_display_column = selection.head().to_display_point(&display_map).column();
// Accumulate contiguous regions of rows that we want to delete.
while let Some(next_selection) = selections.peek() {
@@ -1170,16 +1185,13 @@ impl Editor {
cursor_buffer_row = rows.start.saturating_sub(1);
}
- let mut cursor = Point::new(cursor_buffer_row - row_delta, 0)
- .to_display_point(&display_map, Bias::Left);
+ let mut cursor =
+ Point::new(cursor_buffer_row - row_delta, 0).to_display_point(&display_map);
*cursor.column_mut() =
cmp::min(goal_display_column, display_map.line_len(cursor.row()));
row_delta += rows.len() as u32;
- new_cursors.push((
- selection.id,
- cursor.to_buffer_point(&display_map, Bias::Left),
- ));
+ new_cursors.push((selection.id, cursor.to_point(&display_map)));
edit_ranges.push(edit_start..edit_end);
}
@@ -1566,15 +1578,15 @@ impl Editor {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
for selection in &mut selections {
- let start = selection.start.to_display_point(&display_map, Bias::Left);
- let end = selection.end.to_display_point(&display_map, Bias::Left);
+ let start = selection.start.to_display_point(&display_map);
+ let end = selection.end.to_display_point(&display_map);
if start != end {
selection.end = selection.start.clone();
} else {
let cursor = movement::left(&display_map, start)
.unwrap()
- .to_buffer_point(&display_map, Bias::Left);
+ .to_point(&display_map);
selection.start = cursor.clone();
selection.end = cursor;
}
@@ -1588,10 +1600,10 @@ impl Editor {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
for selection in &mut selections {
- let head = selection.head().to_display_point(&display_map, Bias::Left);
+ let head = selection.head().to_display_point(&display_map);
let cursor = movement::left(&display_map, head)
.unwrap()
- .to_buffer_point(&display_map, Bias::Left);
+ .to_point(&display_map);
selection.set_head(cursor);
selection.goal = SelectionGoal::None;
}
@@ -1602,15 +1614,15 @@ impl Editor {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
for selection in &mut selections {
- let start = selection.start.to_display_point(&display_map, Bias::Left);
- let end = selection.end.to_display_point(&display_map, Bias::Left);
+ let start = selection.start.to_display_point(&display_map);
+ let end = selection.end.to_display_point(&display_map);
if start != end {
selection.start = selection.end.clone();
} else {
let cursor = movement::right(&display_map, end)
.unwrap()
- .to_buffer_point(&display_map, Bias::Right);
+ .to_point(&display_map);
selection.start = cursor;
selection.end = cursor;
}
@@ -1624,10 +1636,10 @@ impl Editor {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
for selection in &mut selections {
- let head = selection.head().to_display_point(&display_map, Bias::Left);
+ let head = selection.head().to_display_point(&display_map);
let cursor = movement::right(&display_map, head)
.unwrap()
- .to_buffer_point(&display_map, Bias::Right);
+ .to_point(&display_map);
selection.set_head(cursor);
selection.goal = SelectionGoal::None;
}
@@ -1643,14 +1655,14 @@ impl Editor {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
for selection in &mut selections {
- let start = selection.start.to_display_point(&display_map, Bias::Left);
- let end = selection.end.to_display_point(&display_map, Bias::Left);
+ let start = selection.start.to_display_point(&display_map);
+ let end = selection.end.to_display_point(&display_map);
if start != end {
selection.goal = SelectionGoal::None;
}
let (start, goal) = movement::up(&display_map, start, selection.goal).unwrap();
- let cursor = start.to_buffer_point(&display_map, Bias::Left);
+ let cursor = start.to_point(&display_map);
selection.start = cursor;
selection.end = cursor;
selection.goal = goal;
@@ -1663,9 +1675,9 @@ impl Editor {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
for selection in &mut selections {
- let head = selection.head().to_display_point(&display_map, Bias::Left);
+ let head = selection.head().to_display_point(&display_map);
let (head, goal) = movement::up(&display_map, head, selection.goal).unwrap();
- let cursor = head.to_buffer_point(&display_map, Bias::Left);
+ let cursor = head.to_point(&display_map);
selection.set_head(cursor);
selection.goal = goal;
}
@@ -1681,14 +1693,14 @@ impl Editor {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
for selection in &mut selections {
- let start = selection.start.to_display_point(&display_map, Bias::Left);
- let end = selection.end.to_display_point(&display_map, Bias::Left);
+ let start = selection.start.to_display_point(&display_map);
+ let end = selection.end.to_display_point(&display_map);
if start != end {
selection.goal = SelectionGoal::None;
}
let (start, goal) = movement::down(&display_map, end, selection.goal).unwrap();
- let cursor = start.to_buffer_point(&display_map, Bias::Right);
+ let cursor = start.to_point(&display_map);
selection.start = cursor;
selection.end = cursor;
selection.goal = goal;
@@ -1701,9 +1713,9 @@ impl Editor {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
for selection in &mut selections {
- let head = selection.head().to_display_point(&display_map, Bias::Left);
+ let head = selection.head().to_display_point(&display_map);
let (head, goal) = movement::down(&display_map, head, selection.goal).unwrap();
- let cursor = head.to_buffer_point(&display_map, Bias::Right);
+ let cursor = head.to_point(&display_map);
selection.set_head(cursor);
selection.goal = goal;
}
@@ -1718,9 +1730,9 @@ impl Editor {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
for selection in &mut selections {
- let head = selection.head().to_display_point(&display_map, Bias::Left);
+ let head = selection.head().to_display_point(&display_map);
let new_head = movement::prev_word_boundary(&display_map, head).unwrap();
- let cursor = new_head.to_buffer_point(&display_map, Bias::Left);
+ let cursor = new_head.to_point(&display_map);
selection.start = cursor.clone();
selection.end = cursor;
selection.reversed = false;
@@ -1737,9 +1749,9 @@ impl Editor {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
for selection in &mut selections {
- let head = selection.head().to_display_point(&display_map, Bias::Left);
+ let head = selection.head().to_display_point(&display_map);
let new_head = movement::prev_word_boundary(&display_map, head).unwrap();
- let cursor = new_head.to_buffer_point(&display_map, Bias::Left);
+ let cursor = new_head.to_point(&display_map);
selection.set_head(cursor);
selection.goal = SelectionGoal::None;
}
@@ -1756,9 +1768,9 @@ impl Editor {
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
for selection in &mut selections {
if selection.is_empty() {
- let head = selection.head().to_display_point(&display_map, Bias::Left);
+ let head = selection.head().to_display_point(&display_map);
let new_head = movement::prev_word_boundary(&display_map, head).unwrap();
- let cursor = new_head.to_buffer_point(&display_map, Bias::Right);
+ let cursor = new_head.to_point(&display_map);
selection.set_head(cursor);
selection.goal = SelectionGoal::None;
}
@@ -1776,9 +1788,9 @@ impl Editor {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
for selection in &mut selections {
- let head = selection.head().to_display_point(&display_map, Bias::Left);
+ let head = selection.head().to_display_point(&display_map);
let new_head = movement::next_word_boundary(&display_map, head).unwrap();
- let cursor = new_head.to_buffer_point(&display_map, Bias::Left);
+ let cursor = new_head.to_point(&display_map);
selection.start = cursor;
selection.end = cursor;
selection.reversed = false;
@@ -1795,9 +1807,9 @@ impl Editor {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
for selection in &mut selections {
- let head = selection.head().to_display_point(&display_map, Bias::Left);
+ let head = selection.head().to_display_point(&display_map);
let new_head = movement::next_word_boundary(&display_map, head).unwrap();
- let cursor = new_head.to_buffer_point(&display_map, Bias::Left);
+ let cursor = new_head.to_point(&display_map);
selection.set_head(cursor);
selection.goal = SelectionGoal::None;
}
@@ -1814,9 +1826,9 @@ impl Editor {
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
for selection in &mut selections {
if selection.is_empty() {
- let head = selection.head().to_display_point(&display_map, Bias::Left);
+ let head = selection.head().to_display_point(&display_map);
let new_head = movement::next_word_boundary(&display_map, head).unwrap();
- let cursor = new_head.to_buffer_point(&display_map, Bias::Right);
+ let cursor = new_head.to_point(&display_map);
selection.set_head(cursor);
selection.goal = SelectionGoal::None;
}
@@ -1834,9 +1846,9 @@ impl Editor {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
for selection in &mut selections {
- let head = selection.head().to_display_point(&display_map, Bias::Left);
+ let head = selection.head().to_display_point(&display_map);
let new_head = movement::line_beginning(&display_map, head, true).unwrap();
- let cursor = new_head.to_buffer_point(&display_map, Bias::Left);
+ let cursor = new_head.to_point(&display_map);
selection.start = cursor;
selection.end = cursor;
selection.reversed = false;
@@ -1853,9 +1865,9 @@ impl Editor {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
for selection in &mut selections {
- let head = selection.head().to_display_point(&display_map, Bias::Left);
+ let head = selection.head().to_display_point(&display_map);
let new_head = movement::line_beginning(&display_map, head, *toggle_indent).unwrap();
- selection.set_head(new_head.to_buffer_point(&display_map, Bias::Left));
+ selection.set_head(new_head.to_point(&display_map));
selection.goal = SelectionGoal::None;
}
self.update_selections(selections, true, cx);
@@ -1877,9 +1889,9 @@ impl Editor {
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
{
for selection in &mut selections {
- let head = selection.head().to_display_point(&display_map, Bias::Left);
+ let head = selection.head().to_display_point(&display_map);
let new_head = movement::line_end(&display_map, head).unwrap();
- let anchor = new_head.to_buffer_point(&display_map, Bias::Left);
+ let anchor = new_head.to_point(&display_map);
selection.start = anchor.clone();
selection.end = anchor;
selection.reversed = false;
@@ -1893,9 +1905,9 @@ impl Editor {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
for selection in &mut selections {
- let head = selection.head().to_display_point(&display_map, Bias::Left);
+ let head = selection.head().to_display_point(&display_map);
let new_head = movement::line_end(&display_map, head).unwrap();
- selection.set_head(new_head.to_buffer_point(&display_map, Bias::Left));
+ selection.set_head(new_head.to_point(&display_map));
selection.goal = SelectionGoal::None;
}
self.update_selections(selections, true, cx);
@@ -2205,6 +2217,197 @@ impl Editor {
self.update_selections(selections, true, cx);
}
+ pub fn show_next_diagnostic(&mut self, _: &ShowNextDiagnostic, cx: &mut ViewContext<Self>) {
+ let selection = self.newest_selection::<usize>(cx);
+ let buffer = self.buffer.read(cx.as_ref());
+ let active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
+ active_diagnostics
+ .primary_range
+ .to_offset(buffer)
+ .to_inclusive()
+ });
+ let mut search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
+ if active_primary_range.contains(&selection.head()) {
+ *active_primary_range.end()
+ } else {
+ selection.head()
+ }
+ } else {
+ selection.head()
+ };
+
+ loop {
+ let next_group = buffer
+ .diagnostics_in_range::<_, usize>(search_start..buffer.len())
+ .find_map(|(range, diagnostic)| {
+ if diagnostic.is_primary
+ && !range.is_empty()
+ && Some(range.end) != active_primary_range.as_ref().map(|r| *r.end())
+ {
+ Some((range, diagnostic.group_id))
+ } else {
+ None
+ }
+ });
+
+ if let Some((primary_range, group_id)) = next_group {
+ self.activate_diagnostics(group_id, cx);
+ self.update_selections(
+ vec![Selection {
+ id: selection.id,
+ start: primary_range.start,
+ end: primary_range.start,
+ reversed: false,
+ goal: SelectionGoal::None,
+ }],
+ true,
+ cx,
+ );
+ break;
+ } else if search_start == 0 {
+ break;
+ } else {
+ // Cycle around to the start of the buffer.
+ search_start = 0;
+ }
+ }
+ }
+
+ fn refresh_active_diagnostics(&mut self, cx: &mut ViewContext<Editor>) {
+ if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
+ let buffer = self.buffer.read(cx);
+ let primary_range_start = active_diagnostics.primary_range.start.to_offset(buffer);
+ let is_valid = buffer
+ .diagnostics_in_range::<_, usize>(active_diagnostics.primary_range.clone())
+ .any(|(range, diagnostic)| {
+ diagnostic.is_primary
+ && !range.is_empty()
+ && range.start == primary_range_start
+ && diagnostic.message == active_diagnostics.primary_message
+ });
+
+ if is_valid != active_diagnostics.is_valid {
+ active_diagnostics.is_valid = is_valid;
+ let mut new_styles = HashMap::new();
+ for (block_id, diagnostic) in &active_diagnostics.blocks {
+ let severity = diagnostic.severity;
+ let message_len = diagnostic.message.len();
+ new_styles.insert(
+ *block_id,
+ (
+ Some({
+ let build_settings = self.build_settings.clone();
+ move |cx: &AppContext| {
+ let settings = build_settings.borrow()(cx);
+ vec![(
+ message_len,
+ diagnostic_style(severity, is_valid, &settings.style)
+ .text
+ .into(),
+ )]
+ }
+ }),
+ Some({
+ let build_settings = self.build_settings.clone();
+ move |cx: &AppContext| {
+ let settings = build_settings.borrow()(cx);
+ diagnostic_style(severity, is_valid, &settings.style).block
+ }
+ }),
+ ),
+ );
+ }
+ self.display_map
+ .update(cx, |display_map, _| display_map.restyle_blocks(new_styles));
+ }
+ }
+ }
+
+ fn activate_diagnostics(&mut self, group_id: usize, cx: &mut ViewContext<Self>) {
+ self.dismiss_diagnostics(cx);
+ self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
+ let buffer = self.buffer.read(cx);
+
+ let mut primary_range = None;
+ let mut primary_message = None;
+ let mut group_end = Point::zero();
+ let diagnostic_group = buffer
+ .diagnostic_group::<Point>(group_id)
+ .map(|(range, diagnostic)| {
+ if range.end > group_end {
+ group_end = range.end;
+ }
+ if diagnostic.is_primary {
+ primary_range = Some(range.clone());
+ primary_message = Some(diagnostic.message.clone());
+ }
+ (range, diagnostic.clone())
+ })
+ .collect::<Vec<_>>();
+ let primary_range = primary_range.unwrap();
+ let primary_message = primary_message.unwrap();
+ let primary_range =
+ buffer.anchor_after(primary_range.start)..buffer.anchor_before(primary_range.end);
+
+ let blocks = display_map
+ .insert_blocks(
+ diagnostic_group.iter().map(|(range, diagnostic)| {
+ let build_settings = self.build_settings.clone();
+ let message_len = diagnostic.message.len();
+ let severity = diagnostic.severity;
+ BlockProperties {
+ position: range.start,
+ text: diagnostic.message.as_str(),
+ build_runs: Some(Arc::new({
+ let build_settings = build_settings.clone();
+ move |cx| {
+ let settings = build_settings.borrow()(cx);
+ vec![(
+ message_len,
+ diagnostic_style(severity, true, &settings.style)
+ .text
+ .into(),
+ )]
+ }
+ })),
+ build_style: Some(Arc::new({
+ let build_settings = build_settings.clone();
+ move |cx| {
+ let settings = build_settings.borrow()(cx);
+ diagnostic_style(severity, true, &settings.style).block
+ }
+ })),
+ disposition: BlockDisposition::Below,
+ }
+ }),
+ cx,
+ )
+ .into_iter()
+ .zip(
+ diagnostic_group
+ .into_iter()
+ .map(|(_, diagnostic)| diagnostic),
+ )
+ .collect();
+
+ Some(ActiveDiagnosticGroup {
+ primary_range,
+ primary_message,
+ blocks,
+ is_valid: true,
+ })
+ });
+ }
+
+ fn dismiss_diagnostics(&mut self, cx: &mut ViewContext<Self>) {
+ if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
+ self.display_map.update(cx, |display_map, cx| {
+ display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
+ });
+ cx.notify();
+ }
+ }
+
fn build_columnar_selection(
&mut self,
display_map: &DisplayMapSnapshot,
@@ -2219,8 +2422,8 @@ impl Editor {
let end = DisplayPoint::new(row, cmp::min(columns.end, line_len));
Some(Selection {
id: post_inc(&mut self.next_selection_id),
- start: start.to_buffer_point(display_map, Bias::Left),
- end: end.to_buffer_point(display_map, Bias::Left),
+ start: start.to_point(display_map),
+ end: end.to_point(display_map),
reversed,
goal: SelectionGoal::ColumnRange {
start: columns.start,
@@ -2254,17 +2457,16 @@ impl Editor {
) -> impl 'a + Iterator<Item = Range<DisplayPoint>> {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = self.buffer.read(cx);
- let selections = buffer
- .selection_set(set_id)
- .unwrap()
+ let selections = self
+ .selection_set(cx)
.selections::<Point, _>(buffer)
.collect::<Vec<_>>();
- let start = range.start.to_buffer_point(&display_map, Bias::Left);
+ let start = range.start.to_point(&display_map);
let start_index = self.selection_insertion_index(&selections, start);
let pending_selection = if set_id.replica_id == self.buffer.read(cx).replica_id() {
self.pending_selection.as_ref().and_then(|pending| {
- let mut selection_start = pending.start.to_display_point(&display_map, Bias::Left);
- let mut selection_end = pending.end.to_display_point(&display_map, Bias::Left);
+ let mut selection_start = pending.start.to_display_point(&display_map);
+ let mut selection_end = pending.end.to_display_point(&display_map);
if pending.reversed {
mem::swap(&mut selection_start, &mut selection_end);
}
@@ -2303,18 +2505,8 @@ impl Editor {
D: 'a + TextDimension<'a> + Ord,
{
let buffer = self.buffer.read(cx);
- let mut selections = buffer
- .selection_set(self.selection_set_id)
- .unwrap()
- .selections::<D, _>(buffer)
- .peekable();
- let mut pending_selection = self.pending_selection.clone().map(|selection| Selection {
- id: selection.id,
- start: selection.start.summary::<D, _>(buffer),
- end: selection.end.summary::<D, _>(buffer),
- reversed: selection.reversed,
- goal: selection.goal,
- });
+ let mut selections = self.selection_set(cx).selections::<D, _>(buffer).peekable();
+ let mut pending_selection = self.pending_selection(cx);
iter::from_fn(move || {
if let Some(pending) = pending_selection.as_mut() {
while let Some(next_selection) = selections.peek() {
@@ -2340,6 +2532,56 @@ impl Editor {
})
}
+ fn pending_selection<'a, D>(&self, cx: &'a AppContext) -> Option<Selection<D>>
+ where
+ D: 'a + TextDimension<'a>,
+ {
+ let buffer = self.buffer.read(cx);
+ self.pending_selection.as_ref().map(|selection| Selection {
+ id: selection.id,
+ start: selection.start.summary::<D, _>(buffer),
+ end: selection.end.summary::<D, _>(buffer),
+ reversed: selection.reversed,
+ goal: selection.goal,
+ })
+ }
+
+ fn selection_count<'a>(&self, cx: &'a AppContext) -> usize {
+ let mut selection_count = self.selection_set(cx).len();
+ if self.pending_selection.is_some() {
+ selection_count += 1;
+ }
+ selection_count
+ }
+
+ pub fn oldest_selection<'a, T>(&self, cx: &'a AppContext) -> Selection<T>
+ where
+ T: 'a + TextDimension<'a>,
+ {
+ let buffer = self.buffer.read(cx);
+ self.selection_set(cx)
+ .oldest_selection(buffer)
+ .or_else(|| self.pending_selection(cx))
+ .unwrap()
+ }
+
+ pub fn newest_selection<'a, T>(&self, cx: &'a AppContext) -> Selection<T>
+ where
+ T: 'a + TextDimension<'a>,
+ {
+ let buffer = self.buffer.read(cx);
+ self.pending_selection(cx)
+ .or_else(|| self.selection_set(cx).newest_selection(buffer))
+ .unwrap()
+ }
+
+ fn selection_set<'a>(&self, cx: &'a AppContext) -> &'a SelectionSet {
+ self.buffer
+ .read(cx)
+ .selection_set(self.selection_set_id)
+ .unwrap()
+ }
+
fn update_selections<T>(
&mut self,
mut selections: Vec<Selection<T>>,
@@ -2438,7 +2680,7 @@ impl Editor {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
for selection in selections {
let range = selection.display_range(&display_map).sorted();
- let buffer_start_row = range.start.to_buffer_point(&display_map, Bias::Left).row;
+ let buffer_start_row = range.start.to_point(&display_map).row;
for row in (0..=range.end.row()).rev() {
if self.is_line_foldable(&display_map, row) && !display_map.is_line_folded(row) {
@@ -2464,8 +2706,8 @@ impl Editor {
.iter()
.map(|s| {
let range = s.display_range(&display_map).sorted();
- let mut start = range.start.to_buffer_point(&display_map, Bias::Left);
- let mut end = range.end.to_buffer_point(&display_map, Bias::Left);
+ let mut start = range.start.to_point(&display_map);
+ let mut end = range.end.to_point(&display_map);
start.column = 0;
end.column = buffer.line_len(end.row);
start..end
@@ -2513,8 +2755,7 @@ impl Editor {
}
let end = end.unwrap_or(max_point);
- return start.to_buffer_point(display_map, Bias::Left)
- ..end.to_buffer_point(display_map, Bias::Left);
+ return start.to_point(display_map)..end.to_point(display_map);
}
pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext<Self>) {
@@ -2624,6 +2865,7 @@ impl Editor {
}
fn on_buffer_changed(&mut self, _: ModelHandle<Buffer>, cx: &mut ViewContext<Self>) {
+ self.refresh_active_diagnostics(cx);
cx.notify();
}
@@ -2666,16 +2908,17 @@ impl Snapshot {
self.display_snapshot.buffer_row_count()
}
- pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
- self.display_snapshot.buffer_rows(start_row)
+ pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: &'a AppContext) -> BufferRows<'a> {
+ self.display_snapshot.buffer_rows(start_row, Some(cx))
}
- pub fn highlighted_chunks_for_rows(
- &mut self,
+ pub fn chunks<'a>(
+ &'a self,
display_rows: Range<u32>,
- ) -> display_map::HighlightedChunks {
- self.display_snapshot
- .highlighted_chunks_for_rows(display_rows)
+ theme: Option<&'a SyntaxTheme>,
+ cx: &'a AppContext,
+ ) -> display_map::Chunks<'a> {
+ self.display_snapshot.chunks(display_rows, theme, cx)
}
pub fn scroll_position(&self) -> Vector2F {
@@ -2743,10 +2986,14 @@ impl EditorSettings {
selection: Default::default(),
guest_selections: Default::default(),
syntax: Default::default(),
- error_underline: Default::default(),
- warning_underline: Default::default(),
- information_underline: Default::default(),
- hint_underline: Default::default(),
+ error_diagnostic: Default::default(),
+ invalid_error_diagnostic: Default::default(),
+ warning_diagnostic: Default::default(),
+ invalid_warning_diagnostic: Default::default(),
+ information_diagnostic: Default::default(),
+ invalid_information_diagnostic: Default::default(),
+ hint_diagnostic: Default::default(),
+ invalid_hint_diagnostic: Default::default(),
}
},
}
@@ -2758,9 +3005,7 @@ fn compute_scroll_position(
mut scroll_position: Vector2F,
scroll_top_anchor: &Anchor,
) -> Vector2F {
- let scroll_top = scroll_top_anchor
- .to_display_point(snapshot, Bias::Left)
- .row() as f32;
+ let scroll_top = scroll_top_anchor.to_display_point(snapshot).row() as f32;
scroll_position.set_y(scroll_top + scroll_position.y());
scroll_position
}
@@ -2838,8 +3083,8 @@ impl View for Editor {
impl SelectionExt for Selection<Point> {
fn display_range(&self, map: &DisplayMapSnapshot) -> Range<DisplayPoint> {
- let start = self.start.to_display_point(map, Bias::Left);
- let end = self.end.to_display_point(map, Bias::Left);
+ let start = self.start.to_display_point(map);
+ let end = self.end.to_display_point(map);
if self.reversed {
end..start
} else {
@@ -2852,8 +3097,8 @@ impl SelectionExt for Selection<Point> {
include_end_if_at_line_start: bool,
map: &DisplayMapSnapshot,
) -> SpannedRows {
- let display_start = self.start.to_display_point(map, Bias::Left);
- let mut display_end = self.end.to_display_point(map, Bias::Right);
+ let display_start = self.start.to_display_point(map);
+ let mut display_end = self.end.to_display_point(map);
if !include_end_if_at_line_start
&& display_end.row() != map.max_point().row()
&& display_start.row() != display_end.row()
@@ -2872,6 +3117,24 @@ impl SelectionExt for Selection<Point> {
}
}
+pub fn diagnostic_style(
+ severity: DiagnosticSeverity,
+ valid: bool,
+ style: &EditorStyle,
+) -> DiagnosticStyle {
+ match (severity, valid) {
+ (DiagnosticSeverity::ERROR, true) => style.error_diagnostic,
+ (DiagnosticSeverity::ERROR, false) => style.invalid_error_diagnostic,
+ (DiagnosticSeverity::WARNING, true) => style.warning_diagnostic,
+ (DiagnosticSeverity::WARNING, false) => style.invalid_warning_diagnostic,
+ (DiagnosticSeverity::INFORMATION, true) => style.information_diagnostic,
+ (DiagnosticSeverity::INFORMATION, false) => style.invalid_information_diagnostic,
+ (DiagnosticSeverity::HINT, true) => style.hint_diagnostic,
+ (DiagnosticSeverity::HINT, false) => style.invalid_hint_diagnostic,
+ _ => Default::default(),
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -33,11 +33,17 @@ pub fn up(
map.column_to_chars(point.row(), point.column())
};
- if point.row() > 0 {
- *point.row_mut() -= 1;
- *point.column_mut() = map.column_from_chars(point.row(), goal_column);
- } else {
- point = DisplayPoint::new(0, 0);
+ loop {
+ if point.row() > 0 {
+ *point.row_mut() -= 1;
+ *point.column_mut() = map.column_from_chars(point.row(), goal_column);
+ if !map.is_block_line(point.row()) {
+ break;
+ }
+ } else {
+ point = DisplayPoint::new(0, 0);
+ break;
+ }
}
let clip_bias = if point.column() == map.line_len(point.row()) {
@@ -64,11 +70,17 @@ pub fn down(
map.column_to_chars(point.row(), point.column())
};
- if point.row() < max_point.row() {
- *point.row_mut() += 1;
- *point.column_mut() = map.column_from_chars(point.row(), goal_column);
- } else {
- point = max_point;
+ loop {
+ if point.row() < max_point.row() {
+ *point.row_mut() += 1;
+ *point.column_mut() = map.column_from_chars(point.row(), goal_column);
+ if !map.is_block_line(point.row()) {
+ break;
+ }
+ } else {
+ point = max_point;
+ break;
+ }
}
let clip_bias = if point.column() == map.line_len(point.row()) {
@@ -2,6 +2,13 @@ use gpui::{Entity, ModelHandle};
use smol::channel;
use std::marker::PhantomData;
+#[cfg(test)]
+#[ctor::ctor]
+fn init_logger() {
+ // std::env::set_var("RUST_LOG", "info");
+ env_logger::init();
+}
+
pub fn sample_text(rows: usize, cols: usize) -> String {
let mut text = String::new();
for row in 0..rows {
@@ -30,7 +30,7 @@ pub struct TextStyle {
pub underline: Option<Color>,
}
-#[derive(Clone, Debug, Default)]
+#[derive(Copy, Clone, Debug, Default)]
pub struct HighlightStyle {
pub color: Color,
pub font_properties: Properties,
@@ -1,10 +1,4 @@
use crate::{geometry::vector::vec2f, keymap::Keystroke, platform::Event};
-use cocoa::appkit::{
- NSDeleteFunctionKey as DELETE_KEY, NSDownArrowFunctionKey as ARROW_DOWN_KEY,
- NSLeftArrowFunctionKey as ARROW_LEFT_KEY, NSPageDownFunctionKey as PAGE_DOWN_KEY,
- NSPageUpFunctionKey as PAGE_UP_KEY, NSRightArrowFunctionKey as ARROW_RIGHT_KEY,
- NSUpArrowFunctionKey as ARROW_UP_KEY,
-};
use cocoa::{
appkit::{NSEvent, NSEventModifierFlags, NSEventType},
base::{id, nil, YES},
@@ -12,11 +6,6 @@ use cocoa::{
};
use std::{ffi::CStr, os::raw::c_char};
-const BACKSPACE_KEY: u16 = 0x7f;
-const ENTER_KEY: u16 = 0x0d;
-const ESCAPE_KEY: u16 = 0x1b;
-const TAB_KEY: u16 = 0x09;
-
impl Event {
pub unsafe fn from_native(native_event: id, window_height: Option<f32>) -> Option<Self> {
let event_type = native_event.eventType();
@@ -39,18 +28,39 @@ impl Event {
.unwrap();
let unmodified_chars = if let Some(first_char) = unmodified_chars.chars().next() {
+ use cocoa::appkit::*;
+ const BACKSPACE_KEY: u16 = 0x7f;
+ const ENTER_KEY: u16 = 0x0d;
+ const ESCAPE_KEY: u16 = 0x1b;
+ const TAB_KEY: u16 = 0x09;
+
+ #[allow(non_upper_case_globals)]
match first_char as u16 {
- ARROW_UP_KEY => "up",
- ARROW_DOWN_KEY => "down",
- ARROW_LEFT_KEY => "left",
- ARROW_RIGHT_KEY => "right",
- PAGE_UP_KEY => "pageup",
- PAGE_DOWN_KEY => "pagedown",
BACKSPACE_KEY => "backspace",
ENTER_KEY => "enter",
- DELETE_KEY => "delete",
ESCAPE_KEY => "escape",
TAB_KEY => "tab",
+
+ NSUpArrowFunctionKey => "up",
+ NSDownArrowFunctionKey => "down",
+ NSLeftArrowFunctionKey => "left",
+ NSRightArrowFunctionKey => "right",
+ NSPageUpFunctionKey => "pageup",
+ NSPageDownFunctionKey => "pagedown",
+ NSDeleteFunctionKey => "delete",
+ NSF1FunctionKey => "f1",
+ NSF2FunctionKey => "f2",
+ NSF3FunctionKey => "f3",
+ NSF4FunctionKey => "f4",
+ NSF5FunctionKey => "f5",
+ NSF6FunctionKey => "f6",
+ NSF7FunctionKey => "f7",
+ NSF8FunctionKey => "f8",
+ NSF9FunctionKey => "f9",
+ NSF10FunctionKey => "f10",
+ NSF11FunctionKey => "f11",
+ NSF12FunctionKey => "f12",
+
_ => unmodified_chars,
}
} else {
@@ -12,7 +12,7 @@ use anyhow::{anyhow, Result};
pub use buffer::{Buffer as TextBuffer, Operation as _, *};
use clock::ReplicaId;
use futures::FutureExt as _;
-use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task};
+use gpui::{fonts::HighlightStyle, AppContext, Entity, ModelContext, MutableAppContext, Task};
use lazy_static::lazy_static;
use lsp::LanguageServer;
use parking_lot::Mutex;
@@ -34,6 +34,7 @@ use std::{
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
vec,
};
+use theme::SyntaxTheme;
use tree_sitter::{InputEdit, Parser, QueryCursor, Tree};
use util::{post_inc, TryFutureExt as _};
@@ -78,13 +79,14 @@ pub struct Snapshot {
diagnostics: AnchorRangeMultimap<Diagnostic>,
is_parsing: bool,
language: Option<Arc<Language>>,
- query_cursor: QueryCursorHandle,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Diagnostic {
pub severity: DiagnosticSeverity,
pub message: String,
+ pub group_id: usize,
+ pub is_primary: bool,
}
struct LanguageServerState {
@@ -190,11 +192,13 @@ struct Highlights<'a> {
next_capture: Option<(tree_sitter::QueryMatch<'a, 'a>, usize)>,
stack: Vec<(usize, HighlightId)>,
highlight_map: HighlightMap,
+ theme: &'a SyntaxTheme,
+ _query_cursor: QueryCursorHandle,
}
-pub struct HighlightedChunks<'a> {
+pub struct Chunks<'a> {
range: Range<usize>,
- chunks: Chunks<'a>,
+ chunks: rope::Chunks<'a>,
diagnostic_endpoints: Peekable<vec::IntoIter<DiagnosticEndpoint>>,
error_depth: usize,
warning_depth: usize,
@@ -204,9 +208,9 @@ pub struct HighlightedChunks<'a> {
}
#[derive(Clone, Copy, Debug, Default)]
-pub struct HighlightedChunk<'a> {
+pub struct Chunk<'a> {
pub text: &'a str,
- pub highlight_id: HighlightId,
+ pub highlight_style: Option<HighlightStyle>,
pub diagnostic: Option<DiagnosticSeverity>,
}
@@ -341,7 +345,6 @@ impl Buffer {
diagnostics: self.diagnostics.clone(),
is_parsing: self.parsing_in_background,
language: self.language.clone(),
- query_cursor: QueryCursorHandle::new(),
}
}
@@ -438,7 +441,7 @@ impl Buffer {
uri,
Default::default(),
snapshot.version as i32,
- snapshot.buffer_snapshot.text().into(),
+ snapshot.buffer_snapshot.text().to_string(),
),
},
)
@@ -699,6 +702,7 @@ impl Buffer {
} else {
self.content()
};
+ let abs_path = self.file.as_ref().and_then(|f| f.abs_path());
let empty_set = HashSet::new();
let disk_based_sources = self
@@ -714,56 +718,82 @@ impl Buffer {
.peekable();
let mut last_edit_old_end = PointUtf16::zero();
let mut last_edit_new_end = PointUtf16::zero();
-
- content.anchor_range_multimap(
- Bias::Left,
- Bias::Right,
- diagnostics.into_iter().filter_map(|diagnostic| {
- let mut start = PointUtf16::new(
- diagnostic.range.start.line,
- diagnostic.range.start.character,
- );
- let mut end =
- PointUtf16::new(diagnostic.range.end.line, diagnostic.range.end.character);
- if diagnostic
- .source
- .as_ref()
- .map_or(false, |source| disk_based_sources.contains(source))
- {
- while let Some(edit) = edits_since_save.peek() {
- if edit.old.end <= start {
- last_edit_old_end = edit.old.end;
- last_edit_new_end = edit.new.end;
- edits_since_save.next();
- } else if edit.old.start <= end && edit.old.end >= start {
- return None;
- } else {
- break;
- }
+ let mut group_ids_by_diagnostic_range = HashMap::new();
+ let mut diagnostics_by_group_id = HashMap::new();
+ let mut next_group_id = 0;
+ 'outer: for diagnostic in &diagnostics {
+ let mut start = diagnostic.range.start.to_point_utf16();
+ let mut end = diagnostic.range.end.to_point_utf16();
+ let source = diagnostic.source.as_ref();
+ let code = diagnostic.code.as_ref();
+ let group_id = diagnostic_ranges(&diagnostic, abs_path.as_deref())
+ .find_map(|range| group_ids_by_diagnostic_range.get(&(source, code, range)))
+ .copied()
+ .unwrap_or_else(|| {
+ let group_id = post_inc(&mut next_group_id);
+ for range in diagnostic_ranges(&diagnostic, abs_path.as_deref()) {
+ group_ids_by_diagnostic_range.insert((source, code, range), group_id);
+ }
+ group_id
+ });
+
+ if diagnostic
+ .source
+ .as_ref()
+ .map_or(false, |source| disk_based_sources.contains(source))
+ {
+ while let Some(edit) = edits_since_save.peek() {
+ if edit.old.end <= start {
+ last_edit_old_end = edit.old.end;
+ last_edit_new_end = edit.new.end;
+ edits_since_save.next();
+ } else if edit.old.start <= end && edit.old.end >= start {
+ continue 'outer;
+ } else {
+ break;
}
-
- start = last_edit_new_end + (start - last_edit_old_end);
- end = last_edit_new_end + (end - last_edit_old_end);
}
- let mut range = content.clip_point_utf16(start, Bias::Left)
- ..content.clip_point_utf16(end, Bias::Right);
- if range.start == range.end {
- range.end.column += 1;
- range.end = content.clip_point_utf16(range.end, Bias::Right);
- if range.start == range.end && range.end.column > 0 {
- range.start.column -= 1;
- range.start = content.clip_point_utf16(range.start, Bias::Left);
- }
+ start = last_edit_new_end + (start - last_edit_old_end);
+ end = last_edit_new_end + (end - last_edit_old_end);
+ }
+
+ let mut range = content.clip_point_utf16(start, Bias::Left)
+ ..content.clip_point_utf16(end, Bias::Right);
+ if range.start == range.end {
+ range.end.column += 1;
+ range.end = content.clip_point_utf16(range.end, Bias::Right);
+ if range.start == range.end && range.end.column > 0 {
+ range.start.column -= 1;
+ range.start = content.clip_point_utf16(range.start, Bias::Left);
}
- Some((
+ }
+
+ diagnostics_by_group_id
+ .entry(group_id)
+ .or_insert(Vec::new())
+ .push((
range,
Diagnostic {
severity: diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR),
- message: diagnostic.message,
+ message: diagnostic.message.clone(),
+ group_id,
+ is_primary: false,
},
- ))
- }),
+ ));
+ }
+
+ content.anchor_range_multimap(
+ Bias::Left,
+ Bias::Right,
+ diagnostics_by_group_id
+ .into_values()
+ .flat_map(|mut diagnostics| {
+ let primary_diagnostic =
+ diagnostics.iter_mut().min_by_key(|d| d.1.severity).unwrap();
+ primary_diagnostic.1.is_primary = true;
+ diagnostics
+ }),
)
};
@@ -786,7 +816,7 @@ impl Buffer {
pub fn diagnostics_in_range<'a, T, O>(
&'a self,
- range: Range<T>,
+ search_range: Range<T>,
) -> impl Iterator<Item = (Range<O>, &Diagnostic)> + 'a
where
T: 'a + ToOffset,
@@ -794,7 +824,20 @@ impl Buffer {
{
let content = self.content();
self.diagnostics
- .intersecting_ranges(range, content, true)
+ .intersecting_ranges(search_range, content, true)
+ .map(move |(_, range, diagnostic)| (range, diagnostic))
+ }
+
+ pub fn diagnostic_group<'a, O>(
+ &'a self,
+ group_id: usize,
+ ) -> impl Iterator<Item = (Range<O>, &Diagnostic)> + 'a
+ where
+ O: 'a + FromAnchor,
+ {
+ let content = self.content();
+ self.diagnostics
+ .filter(content, move |diagnostic| diagnostic.group_id == group_id)
.map(move |(_, range, diagnostic)| (range, diagnostic))
}
@@ -1608,51 +1651,61 @@ impl Snapshot {
.all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none())
}
- pub fn highlighted_text_for_range<T: ToOffset>(
- &mut self,
+ pub fn chunks<'a, T: ToOffset>(
+ &'a self,
range: Range<T>,
- ) -> HighlightedChunks {
+ theme: Option<&'a SyntaxTheme>,
+ ) -> Chunks<'a> {
let range = range.start.to_offset(&*self)..range.end.to_offset(&*self);
+ let mut highlights = None;
let mut diagnostic_endpoints = Vec::<DiagnosticEndpoint>::new();
- for (_, range, diagnostic) in
- self.diagnostics
- .intersecting_ranges(range.clone(), self.content(), true)
- {
- diagnostic_endpoints.push(DiagnosticEndpoint {
- offset: range.start,
- is_start: true,
- severity: diagnostic.severity,
- });
- diagnostic_endpoints.push(DiagnosticEndpoint {
- offset: range.end,
- is_start: false,
- severity: diagnostic.severity,
- });
- }
- diagnostic_endpoints.sort_unstable_by_key(|endpoint| (endpoint.offset, !endpoint.is_start));
- let diagnostic_endpoints = diagnostic_endpoints.into_iter().peekable();
+ if let Some(theme) = theme {
+ for (_, range, diagnostic) in
+ self.diagnostics
+ .intersecting_ranges(range.clone(), self.content(), true)
+ {
+ diagnostic_endpoints.push(DiagnosticEndpoint {
+ offset: range.start,
+ is_start: true,
+ severity: diagnostic.severity,
+ });
+ diagnostic_endpoints.push(DiagnosticEndpoint {
+ offset: range.end,
+ is_start: false,
+ severity: diagnostic.severity,
+ });
+ }
+ diagnostic_endpoints
+ .sort_unstable_by_key(|endpoint| (endpoint.offset, !endpoint.is_start));
- let chunks = self.text.as_rope().chunks_in_range(range.clone());
- let highlights =
if let Some((language, tree)) = self.language.as_ref().zip(self.tree.as_ref()) {
- let captures = self.query_cursor.set_byte_range(range.clone()).captures(
+ let mut query_cursor = QueryCursorHandle::new();
+
+ // TODO - add a Tree-sitter API to remove the need for this.
+ let cursor = unsafe {
+ std::mem::transmute::<_, &'static mut QueryCursor>(query_cursor.deref_mut())
+ };
+ let captures = cursor.set_byte_range(range.clone()).captures(
&language.highlights_query,
tree.root_node(),
TextProvider(self.text.as_rope()),
);
-
- Some(Highlights {
+ highlights = Some(Highlights {
captures,
next_capture: None,
stack: Default::default(),
highlight_map: language.highlight_map(),
+ _query_cursor: query_cursor,
+ theme,
})
- } else {
- None
- };
+ }
+ }
- HighlightedChunks {
+ let diagnostic_endpoints = diagnostic_endpoints.into_iter().peekable();
+ let chunks = self.text.as_rope().chunks_in_range(range.clone());
+
+ Chunks {
range,
chunks,
diagnostic_endpoints,
@@ -1673,7 +1726,6 @@ impl Clone for Snapshot {
diagnostics: self.diagnostics.clone(),
is_parsing: self.is_parsing,
language: self.language.clone(),
- query_cursor: QueryCursorHandle::new(),
}
}
}
@@ -1704,7 +1756,9 @@ impl<'a> Iterator for ByteChunks<'a> {
}
}
-impl<'a> HighlightedChunks<'a> {
+unsafe impl<'a> Send for Chunks<'a> {}
+
+impl<'a> Chunks<'a> {
pub fn seek(&mut self, offset: usize) {
self.range.start = offset;
self.chunks.seek(self.range.start);
@@ -1763,8 +1817,8 @@ impl<'a> HighlightedChunks<'a> {
}
}
-impl<'a> Iterator for HighlightedChunks<'a> {
- type Item = HighlightedChunk<'a>;
+impl<'a> Iterator for Chunks<'a> {
+ type Item = Chunk<'a>;
fn next(&mut self) -> Option<Self::Item> {
let mut next_capture_start = usize::MAX;
@@ -1813,12 +1867,12 @@ impl<'a> Iterator for HighlightedChunks<'a> {
let mut chunk_end = (self.chunks.offset() + chunk.len())
.min(next_capture_start)
.min(next_diagnostic_endpoint);
- let mut highlight_id = HighlightId::default();
- if let Some((parent_capture_end, parent_highlight_id)) =
- self.highlights.as_ref().and_then(|h| h.stack.last())
- {
- chunk_end = chunk_end.min(*parent_capture_end);
- highlight_id = *parent_highlight_id;
+ let mut highlight_style = None;
+ if let Some(highlights) = self.highlights.as_ref() {
+ if let Some((parent_capture_end, parent_highlight_id)) = highlights.stack.last() {
+ chunk_end = chunk_end.min(*parent_capture_end);
+ highlight_style = parent_highlight_id.style(highlights.theme);
+ }
}
let slice =
@@ -1828,9 +1882,9 @@ impl<'a> Iterator for HighlightedChunks<'a> {
self.chunks.next().unwrap();
}
- Some(HighlightedChunk {
+ Some(Chunk {
text: slice,
- highlight_id,
+ highlight_style,
diagnostic: self.current_diagnostic_severity(),
})
} else {
@@ -1888,6 +1942,44 @@ impl ToTreeSitterPoint for Point {
}
}
+trait ToPointUtf16 {
+ fn to_point_utf16(self) -> PointUtf16;
+}
+
+impl ToPointUtf16 for lsp::Position {
+ fn to_point_utf16(self) -> PointUtf16 {
+ PointUtf16::new(self.line, self.character)
+ }
+}
+
+fn diagnostic_ranges<'a>(
+ diagnostic: &'a lsp::Diagnostic,
+ abs_path: Option<&'a Path>,
+) -> impl 'a + Iterator<Item = Range<PointUtf16>> {
+ diagnostic
+ .related_information
+ .iter()
+ .flatten()
+ .filter_map(move |info| {
+ if info.location.uri.to_file_path().ok()? == abs_path? {
+ let info_start = PointUtf16::new(
+ info.location.range.start.line,
+ info.location.range.start.character,
+ );
+ let info_end = PointUtf16::new(
+ info.location.range.end.line,
+ info.location.range.end.character,
+ );
+ Some(info_start..info_end)
+ } else {
+ None
+ }
+ })
+ .chain(Some(
+ diagnostic.range.start.to_point_utf16()..diagnostic.range.end.to_point_utf16(),
+ ))
+}
+
fn contiguous_ranges(
values: impl IntoIterator<Item = u32>,
max_len: usize,
@@ -141,6 +141,8 @@ pub fn serialize_diagnostics(map: &AnchorRangeMultimap<Diagnostic>) -> proto::Di
DiagnosticSeverity::HINT => proto::diagnostic::Severity::Hint,
_ => proto::diagnostic::Severity::None,
} as i32,
+ group_id: diagnostic.group_id as u64,
+ is_primary: diagnostic.is_primary,
})
.collect(),
}
@@ -308,6 +310,8 @@ pub fn deserialize_diagnostics(message: proto::DiagnosticSet) -> AnchorRangeMult
proto::diagnostic::Severity::None => return None,
},
message: diagnostic.message,
+ group_id: diagnostic.group_id as usize,
+ is_primary: diagnostic.is_primary,
},
))
}),
@@ -482,14 +482,18 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) {
Point::new(3, 9)..Point::new(3, 11),
&Diagnostic {
severity: DiagnosticSeverity::ERROR,
- message: "undefined variable 'BB'".to_string()
+ message: "undefined variable 'BB'".to_string(),
+ group_id: 1,
+ is_primary: true,
},
),
(
Point::new(4, 9)..Point::new(4, 12),
&Diagnostic {
severity: DiagnosticSeverity::ERROR,
- message: "undefined variable 'CCC'".to_string()
+ message: "undefined variable 'CCC'".to_string(),
+ group_id: 2,
+ is_primary: true,
}
)
]
@@ -545,14 +549,18 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) {
Point::new(2, 9)..Point::new(2, 12),
&Diagnostic {
severity: DiagnosticSeverity::WARNING,
- message: "unreachable statement".to_string()
+ message: "unreachable statement".to_string(),
+ group_id: 1,
+ is_primary: true,
}
),
(
Point::new(2, 9)..Point::new(2, 10),
&Diagnostic {
severity: DiagnosticSeverity::ERROR,
- message: "undefined variable 'A'".to_string()
+ message: "undefined variable 'A'".to_string(),
+ group_id: 0,
+ is_primary: true,
},
)
]
@@ -620,14 +628,18 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) {
Point::new(2, 21)..Point::new(2, 22),
&Diagnostic {
severity: DiagnosticSeverity::ERROR,
- message: "undefined variable 'A'".to_string()
+ message: "undefined variable 'A'".to_string(),
+ group_id: 0,
+ is_primary: true,
}
),
(
Point::new(3, 9)..Point::new(3, 11),
&Diagnostic {
severity: DiagnosticSeverity::ERROR,
- message: "undefined variable 'BB'".to_string()
+ message: "undefined variable 'BB'".to_string(),
+ group_id: 1,
+ is_primary: true,
},
)
]
@@ -694,12 +706,223 @@ async fn test_empty_diagnostic_ranges(mut cx: gpui::TestAppContext) {
});
}
-fn chunks_with_diagnostics<T: ToOffset>(
+#[gpui::test]
+async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) {
+ cx.add_model(|cx| {
+ let text = "
+ fn foo(mut v: Vec<usize>) {
+ for x in &v {
+ v.push(1);
+ }
+ }
+ "
+ .unindent();
+
+ let file = FakeFile::new("/example.rs");
+ let mut buffer = Buffer::from_file(0, text, Box::new(file.clone()), cx);
+ buffer.set_language(Some(Arc::new(rust_lang())), None, cx);
+ let diagnostics = vec![
+ lsp::Diagnostic {
+ range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
+ severity: Some(DiagnosticSeverity::WARNING),
+ message: "error 1".to_string(),
+ related_information: Some(vec![lsp::DiagnosticRelatedInformation {
+ location: lsp::Location {
+ uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
+ range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
+ },
+ message: "error 1 hint 1".to_string(),
+ }]),
+ ..Default::default()
+ },
+ lsp::Diagnostic {
+ range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
+ severity: Some(DiagnosticSeverity::HINT),
+ message: "error 1 hint 1".to_string(),
+ related_information: Some(vec![lsp::DiagnosticRelatedInformation {
+ location: lsp::Location {
+ uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
+ range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
+ },
+ message: "original diagnostic".to_string(),
+ }]),
+ ..Default::default()
+ },
+ lsp::Diagnostic {
+ range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
+ severity: Some(DiagnosticSeverity::ERROR),
+ message: "error 2".to_string(),
+ related_information: Some(vec![
+ lsp::DiagnosticRelatedInformation {
+ location: lsp::Location {
+ uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
+ range: lsp::Range::new(
+ lsp::Position::new(1, 13),
+ lsp::Position::new(1, 15),
+ ),
+ },
+ message: "error 2 hint 1".to_string(),
+ },
+ lsp::DiagnosticRelatedInformation {
+ location: lsp::Location {
+ uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
+ range: lsp::Range::new(
+ lsp::Position::new(1, 13),
+ lsp::Position::new(1, 15),
+ ),
+ },
+ message: "error 2 hint 2".to_string(),
+ },
+ ]),
+ ..Default::default()
+ },
+ lsp::Diagnostic {
+ range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
+ severity: Some(DiagnosticSeverity::HINT),
+ message: "error 2 hint 1".to_string(),
+ related_information: Some(vec![lsp::DiagnosticRelatedInformation {
+ location: lsp::Location {
+ uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
+ range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
+ },
+ message: "original diagnostic".to_string(),
+ }]),
+ ..Default::default()
+ },
+ lsp::Diagnostic {
+ range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
+ severity: Some(DiagnosticSeverity::HINT),
+ message: "error 2 hint 2".to_string(),
+ related_information: Some(vec![lsp::DiagnosticRelatedInformation {
+ location: lsp::Location {
+ uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
+ range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
+ },
+ message: "original diagnostic".to_string(),
+ }]),
+ ..Default::default()
+ },
+ ];
+ buffer.update_diagnostics(None, diagnostics, cx).unwrap();
+ assert_eq!(
+ buffer
+ .diagnostics_in_range::<_, Point>(0..buffer.len())
+ .collect::<Vec<_>>(),
+ &[
+ (
+ Point::new(1, 8)..Point::new(1, 9),
+ &Diagnostic {
+ severity: DiagnosticSeverity::WARNING,
+ message: "error 1".to_string(),
+ group_id: 0,
+ is_primary: true,
+ }
+ ),
+ (
+ Point::new(1, 8)..Point::new(1, 9),
+ &Diagnostic {
+ severity: DiagnosticSeverity::HINT,
+ message: "error 1 hint 1".to_string(),
+ group_id: 0,
+ is_primary: false,
+ }
+ ),
+ (
+ Point::new(1, 13)..Point::new(1, 15),
+ &Diagnostic {
+ severity: DiagnosticSeverity::HINT,
+ message: "error 2 hint 1".to_string(),
+ group_id: 1,
+ is_primary: false,
+ }
+ ),
+ (
+ Point::new(1, 13)..Point::new(1, 15),
+ &Diagnostic {
+ severity: DiagnosticSeverity::HINT,
+ message: "error 2 hint 2".to_string(),
+ group_id: 1,
+ is_primary: false,
+ }
+ ),
+ (
+ Point::new(2, 8)..Point::new(2, 17),
+ &Diagnostic {
+ severity: DiagnosticSeverity::ERROR,
+ message: "error 2".to_string(),
+ group_id: 1,
+ is_primary: true,
+ }
+ )
+ ]
+ );
+
+ assert_eq!(
+ buffer.diagnostic_group(0).collect::<Vec<_>>(),
+ &[
+ (
+ Point::new(1, 8)..Point::new(1, 9),
+ &Diagnostic {
+ severity: DiagnosticSeverity::WARNING,
+ message: "error 1".to_string(),
+ group_id: 0,
+ is_primary: true,
+ }
+ ),
+ (
+ Point::new(1, 8)..Point::new(1, 9),
+ &Diagnostic {
+ severity: DiagnosticSeverity::HINT,
+ message: "error 1 hint 1".to_string(),
+ group_id: 0,
+ is_primary: false,
+ }
+ ),
+ ]
+ );
+ assert_eq!(
+ buffer.diagnostic_group(1).collect::<Vec<_>>(),
+ &[
+ (
+ Point::new(1, 13)..Point::new(1, 15),
+ &Diagnostic {
+ severity: DiagnosticSeverity::HINT,
+ message: "error 2 hint 1".to_string(),
+ group_id: 1,
+ is_primary: false,
+ }
+ ),
+ (
+ Point::new(1, 13)..Point::new(1, 15),
+ &Diagnostic {
+ severity: DiagnosticSeverity::HINT,
+ message: "error 2 hint 2".to_string(),
+ group_id: 1,
+ is_primary: false,
+ }
+ ),
+ (
+ Point::new(2, 8)..Point::new(2, 17),
+ &Diagnostic {
+ severity: DiagnosticSeverity::ERROR,
+ message: "error 2".to_string(),
+ group_id: 1,
+ is_primary: true,
+ }
+ )
+ ]
+ );
+
+ buffer
+ });
+}
+
+fn chunks_with_diagnostics<T: ToOffset + ToPoint>(
buffer: &Buffer,
range: Range<T>,
) -> Vec<(String, Option<DiagnosticSeverity>)> {
let mut chunks: Vec<(String, Option<DiagnosticSeverity>)> = Vec::new();
- for chunk in buffer.snapshot().highlighted_text_for_range(range) {
+ for chunk in buffer.snapshot().chunks(range, Some(&Default::default())) {
if chunks
.last()
.map_or(false, |prev_chunk| prev_chunk.1 == chunk.diagnostic)
@@ -765,3 +988,80 @@ fn rust_lang() -> Language {
fn empty(point: Point) -> Range<Point> {
point..point
}
+
+#[derive(Clone)]
+struct FakeFile {
+ abs_path: PathBuf,
+}
+
+impl FakeFile {
+ fn new(abs_path: impl Into<PathBuf>) -> Self {
+ Self {
+ abs_path: abs_path.into(),
+ }
+ }
+}
+
+impl File for FakeFile {
+ fn worktree_id(&self) -> usize {
+ todo!()
+ }
+
+ fn entry_id(&self) -> Option<usize> {
+ todo!()
+ }
+
+ fn mtime(&self) -> SystemTime {
+ SystemTime::now()
+ }
+
+ fn path(&self) -> &Arc<Path> {
+ todo!()
+ }
+
+ fn abs_path(&self) -> Option<PathBuf> {
+ Some(self.abs_path.clone())
+ }
+
+ fn full_path(&self) -> PathBuf {
+ todo!()
+ }
+
+ fn file_name(&self) -> Option<OsString> {
+ todo!()
+ }
+
+ fn is_deleted(&self) -> bool {
+ todo!()
+ }
+
+ fn save(
+ &self,
+ _: u64,
+ _: Rope,
+ _: clock::Global,
+ _: &mut MutableAppContext,
+ ) -> Task<Result<(clock::Global, SystemTime)>> {
+ todo!()
+ }
+
+ fn load_local(&self, _: &AppContext) -> Option<Task<Result<String>>> {
+ todo!()
+ }
+
+ fn buffer_updated(&self, _: u64, _: super::Operation, _: &mut MutableAppContext) {
+ todo!()
+ }
+
+ fn buffer_removed(&self, _: u64, _: &mut MutableAppContext) {
+ todo!()
+ }
+
+ fn boxed_clone(&self) -> Box<dyn File> {
+ todo!()
+ }
+
+ fn as_any(&self) -> &dyn Any {
+ todo!()
+ }
+}
@@ -3633,7 +3633,9 @@ mod tests {
Point::new(0, 9)..Point::new(0, 10),
&Diagnostic {
severity: lsp::DiagnosticSeverity::ERROR,
- message: "undefined variable 'A'".to_string()
+ message: "undefined variable 'A'".to_string(),
+ group_id: 0,
+ is_primary: true
}
)]
)
@@ -256,6 +256,8 @@ message Diagnostic {
uint64 end = 2;
Severity severity = 3;
string message = 4;
+ uint64 group_id = 5;
+ bool is_primary = 6;
enum Severity {
None = 0;
Error = 1;
@@ -1713,15 +1713,19 @@ mod tests {
(
Point::new(0, 4)..Point::new(0, 7),
&Diagnostic {
+ group_id: 0,
message: "message 1".to_string(),
severity: lsp::DiagnosticSeverity::ERROR,
+ is_primary: true
}
),
(
Point::new(0, 10)..Point::new(0, 13),
&Diagnostic {
+ group_id: 1,
severity: lsp::DiagnosticSeverity::WARNING,
- message: "message 2".to_string()
+ message: "message 2".to_string(),
+ is_primary: true
}
)
]
@@ -31,6 +31,12 @@ pub trait Summary: Default + Clone + fmt::Debug {
pub trait Dimension<'a, S: Summary>: Clone + fmt::Debug + Default {
fn add_summary(&mut self, _summary: &'a S, _: &S::Context);
+
+ fn from_summary(summary: &'a S, cx: &S::Context) -> Self {
+ let mut dimension = Self::default();
+ dimension.add_summary(summary, cx);
+ dimension
+ }
}
impl<'a, T: Summary> Dimension<'a, T> for T {
@@ -227,12 +227,21 @@ pub struct EditorStyle {
pub line_number_active: Color,
pub guest_selections: Vec<SelectionStyle>,
pub syntax: Arc<SyntaxTheme>,
- pub error_underline: Color,
- pub warning_underline: Color,
- #[serde(default)]
- pub information_underline: Color,
- #[serde(default)]
- pub hint_underline: Color,
+ pub error_diagnostic: DiagnosticStyle,
+ pub invalid_error_diagnostic: DiagnosticStyle,
+ pub warning_diagnostic: DiagnosticStyle,
+ pub invalid_warning_diagnostic: DiagnosticStyle,
+ pub information_diagnostic: DiagnosticStyle,
+ pub invalid_information_diagnostic: DiagnosticStyle,
+ pub hint_diagnostic: DiagnosticStyle,
+ pub invalid_hint_diagnostic: DiagnosticStyle,
+}
+
+#[derive(Copy, Clone, Deserialize, Default)]
+pub struct DiagnosticStyle {
+ pub text: Color,
+ #[serde(flatten)]
+ pub block: BlockStyle,
}
#[derive(Clone, Copy, Default, Deserialize)]
@@ -251,6 +260,14 @@ pub struct InputEditorStyle {
pub selection: SelectionStyle,
}
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
+pub struct BlockStyle {
+ pub background: Option<Color>,
+ pub border: Option<Color>,
+ pub gutter_background: Option<Color>,
+ pub gutter_border: Option<Color>,
+}
+
impl EditorStyle {
pub fn placeholder_text(&self) -> &TextStyle {
self.placeholder_text.as_ref().unwrap_or(&self.text)
@@ -273,10 +290,14 @@ impl InputEditorStyle {
line_number_active: Default::default(),
guest_selections: Default::default(),
syntax: Default::default(),
- error_underline: Default::default(),
- warning_underline: Default::default(),
- information_underline: Default::default(),
- hint_underline: Default::default(),
+ error_diagnostic: Default::default(),
+ invalid_error_diagnostic: Default::default(),
+ warning_diagnostic: Default::default(),
+ invalid_warning_diagnostic: Default::default(),
+ information_diagnostic: Default::default(),
+ invalid_information_diagnostic: Default::default(),
+ hint_diagnostic: Default::default(),
+ invalid_hint_diagnostic: Default::default(),
}
}
}
@@ -258,15 +258,12 @@ impl DiagnosticMessage {
fn update(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
let editor = editor.read(cx);
- let cursor_position = editor
- .selections::<usize>(cx)
- .max_by_key(|selection| selection.id)
- .unwrap()
- .head();
+ let cursor_position = editor.newest_selection(cx).head();
let new_diagnostic = editor
.buffer()
.read(cx)
.diagnostics_in_range::<usize, usize>(cursor_position..cursor_position)
+ .filter(|(range, _)| !range.is_empty())
.min_by_key(|(range, diagnostic)| (diagnostic.severity, range.len()))
.map(|(_, diagnostic)| diagnostic.clone());
if new_diagnostic != self.diagnostic {
@@ -173,7 +173,7 @@ corner_radius = 6
[project_panel]
extends = "$panel"
-padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2
+padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2
[project_panel.entry]
text = "$text.1"
@@ -235,7 +235,12 @@ line_number = "$text.2.color"
line_number_active = "$text.0.color"
selection = "$selection.host"
guest_selections = "$selection.guests"
-error_underline = "$status.bad"
-warning_underline = "$status.warn"
-info_underline = "$status.info"
-hint_underline = "$status.info"
+error_color = "$status.bad"
+error_diagnostic = { text = "$status.bad" }
+invalid_error_diagnostic = { text = "$text.3.color" }
+warning_diagnostic = { text = "$status.warn" }
+invalid_warning_diagnostic = { text = "$text.3.color" }
+information_diagnostic = { text = "$status.info" }
+invalid_information_diagnostic = { text = "$text.3.color" }
+hint_diagnostic = { text = "$status.info" }
+invalid_hint_diagnostic = { text = "$text.3.color" }