Merge pull request #226 from zed-industries/1d-block-map

Allow full diagnostic messages to be displayed in the editor
This commit is contained in:
Max Brunsfeld 2021-11-19 09:16:49 -08:00 committed by GitHub
commit 447f710570
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 4161 additions and 823 deletions

2
Cargo.lock generated
View file

@ -1622,6 +1622,8 @@ dependencies = [
"anyhow",
"buffer",
"clock",
"ctor",
"env_logger",
"gpui",
"language",
"lazy_static",

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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,
);
}
}

View file

@ -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,
})
}
}

View file

@ -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"

View file

@ -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),
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, Right),
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

File diff suppressed because it is too large Load diff

View file

@ -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,
);
}

View file

@ -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(),
);
}
}
}

View file

@ -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,);
}
}
}

View file

@ -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 highlighted_chunks_for_rows(&mut self, rows: Range<u32>) -> HighlightedChunks {
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 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,19 +1115,84 @@ 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());
}
}
fn wrap_text(
@ -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!(

View file

@ -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);
}

View file

@ -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::*;

View file

@ -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()) {

View file

@ -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 {

View file

@ -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,

View file

@ -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 {

View file

@ -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();
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);
}
}
diagnostics_by_group_id
.entry(group_id)
.or_insert(Vec::new())
.push((
range,
Diagnostic {
severity: diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR),
message: diagnostic.message.clone(),
group_id,
is_primary: false,
},
));
}
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;
}
}
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((
range,
Diagnostic {
severity: diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR),
message: diagnostic.message,
},
))
}),
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,

View file

@ -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,
},
))
}),

View file

@ -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!()
}
}

View file

@ -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
}
)]
)

View file

@ -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;

View file

@ -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
}
)
]

View file

@ -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 {

View file

@ -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(),
}
}
}

View file

@ -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 {

View file

@ -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" }