mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-28 19:02:35 +03:00
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:
commit
447f710570
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1622,6 +1622,8 @@ dependencies = [
|
||||
"anyhow",
|
||||
"buffer",
|
||||
"clock",
|
||||
"ctor",
|
||||
"env_logger",
|
||||
"gpui",
|
||||
"language",
|
||||
"lazy_static",
|
||||
|
@ -194,6 +194,66 @@ impl<T> AnchorRangeMap<T> {
|
||||
.iter()
|
||||
.map(|(range, value)| (range.start.0..range.end.0, value))
|
||||
}
|
||||
|
||||
pub fn min_by_key<'a, C, D, F, K>(
|
||||
&self,
|
||||
content: C,
|
||||
mut extract_key: F,
|
||||
) -> Option<(Range<D>, &T)>
|
||||
where
|
||||
C: Into<Content<'a>>,
|
||||
D: 'a + TextDimension<'a>,
|
||||
F: FnMut(&T) -> K,
|
||||
K: Ord,
|
||||
{
|
||||
let content = content.into();
|
||||
self.entries
|
||||
.iter()
|
||||
.min_by_key(|(_, value)| extract_key(value))
|
||||
.map(|(range, value)| (self.resolve_range(range, &content), value))
|
||||
}
|
||||
|
||||
pub fn max_by_key<'a, C, D, F, K>(
|
||||
&self,
|
||||
content: C,
|
||||
mut extract_key: F,
|
||||
) -> Option<(Range<D>, &T)>
|
||||
where
|
||||
C: Into<Content<'a>>,
|
||||
D: 'a + TextDimension<'a>,
|
||||
F: FnMut(&T) -> K,
|
||||
K: Ord,
|
||||
{
|
||||
let content = content.into();
|
||||
self.entries
|
||||
.iter()
|
||||
.max_by_key(|(_, value)| extract_key(value))
|
||||
.map(|(range, value)| (self.resolve_range(range, &content), value))
|
||||
}
|
||||
|
||||
fn resolve_range<'a, D>(
|
||||
&self,
|
||||
range: &Range<(FullOffset, Bias)>,
|
||||
content: &Content<'a>,
|
||||
) -> Range<D>
|
||||
where
|
||||
D: 'a + TextDimension<'a>,
|
||||
{
|
||||
let (start, start_bias) = range.start;
|
||||
let mut anchor = Anchor {
|
||||
full_offset: start,
|
||||
bias: start_bias,
|
||||
version: self.version.clone(),
|
||||
};
|
||||
let start = content.summary_for_anchor(&anchor);
|
||||
|
||||
let (end, end_bias) = range.end;
|
||||
anchor.full_offset = end;
|
||||
anchor.bias = end_bias;
|
||||
let end = content.summary_for_anchor(&anchor);
|
||||
|
||||
start..end
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq> PartialEq for AnchorRangeMap<T> {
|
||||
@ -354,6 +414,38 @@ impl<T: Clone> AnchorRangeMultimap<T> {
|
||||
.cursor::<()>()
|
||||
.map(|entry| (entry.range.start..entry.range.end, &entry.value))
|
||||
}
|
||||
|
||||
pub fn filter<'a, O, F>(
|
||||
&'a self,
|
||||
content: Content<'a>,
|
||||
mut f: F,
|
||||
) -> impl 'a + Iterator<Item = (usize, Range<O>, &T)>
|
||||
where
|
||||
O: FromAnchor,
|
||||
F: 'a + FnMut(&'a T) -> bool,
|
||||
{
|
||||
let mut endpoint = Anchor {
|
||||
full_offset: FullOffset(0),
|
||||
bias: Bias::Left,
|
||||
version: self.version.clone(),
|
||||
};
|
||||
self.entries
|
||||
.cursor::<()>()
|
||||
.enumerate()
|
||||
.filter_map(move |(ix, entry)| {
|
||||
if f(&entry.value) {
|
||||
endpoint.full_offset = entry.range.start;
|
||||
endpoint.bias = self.start_bias;
|
||||
let start = O::from_anchor(&endpoint, &content);
|
||||
endpoint.full_offset = entry.range.end;
|
||||
endpoint.bias = self.end_bias;
|
||||
let end = O::from_anchor(&endpoint, &content);
|
||||
Some((ix, start..end, &entry.value))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> sum_tree::Item for AnchorRangeMultimapEntry<T> {
|
||||
@ -435,6 +527,7 @@ impl<'a> sum_tree::SeekTarget<'a, AnchorRangeMultimapSummary, FullOffsetRange> f
|
||||
|
||||
pub trait AnchorRangeExt {
|
||||
fn cmp<'a>(&self, b: &Range<Anchor>, buffer: impl Into<Content<'a>>) -> Result<Ordering>;
|
||||
fn to_offset<'a>(&self, content: impl Into<Content<'a>>) -> Range<usize>;
|
||||
}
|
||||
|
||||
impl AnchorRangeExt for Range<Anchor> {
|
||||
@ -445,4 +538,9 @@ impl AnchorRangeExt for Range<Anchor> {
|
||||
ord @ _ => ord,
|
||||
})
|
||||
}
|
||||
|
||||
fn to_offset<'a>(&self, content: impl Into<Content<'a>>) -> Range<usize> {
|
||||
let content = content.into();
|
||||
self.start.to_offset(&content)..self.end.to_offset(&content)
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,15 @@ impl Point {
|
||||
Point::new(0, 0)
|
||||
}
|
||||
|
||||
pub fn from_str(s: &str) -> Self {
|
||||
let mut point = Self::zero();
|
||||
for (row, line) in s.split('\n').enumerate() {
|
||||
point.row = row as u32;
|
||||
point.column = line.len() as u32;
|
||||
}
|
||||
point
|
||||
}
|
||||
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.row == 0 && self.column == 0
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use crate::PointUtf16;
|
||||
use super::Point;
|
||||
use arrayvec::ArrayString;
|
||||
use smallvec::SmallVec;
|
||||
use std::{cmp, ops::Range, str};
|
||||
use std::{cmp, fmt, mem, ops::Range, str};
|
||||
use sum_tree::{Bias, Dimension, SumTree};
|
||||
|
||||
#[cfg(test)]
|
||||
@ -38,6 +38,16 @@ impl Rope {
|
||||
self.check_invariants();
|
||||
}
|
||||
|
||||
pub fn replace(&mut self, range: Range<usize>, text: &str) {
|
||||
let mut new_rope = Rope::new();
|
||||
let mut cursor = self.cursor(0);
|
||||
new_rope.append(cursor.slice(range.start));
|
||||
cursor.seek_forward(range.end);
|
||||
new_rope.push(text);
|
||||
new_rope.append(cursor.suffix());
|
||||
*self = new_rope;
|
||||
}
|
||||
|
||||
pub fn push(&mut self, text: &str) {
|
||||
let mut new_chunks = SmallVec::<[_; 16]>::new();
|
||||
let mut new_chunk = ArrayString::new();
|
||||
@ -79,6 +89,11 @@ impl Rope {
|
||||
self.check_invariants();
|
||||
}
|
||||
|
||||
pub fn push_front(&mut self, text: &str) {
|
||||
let suffix = mem::replace(self, Rope::from(text));
|
||||
self.append(suffix);
|
||||
}
|
||||
|
||||
fn check_invariants(&self) {
|
||||
#[cfg(test)]
|
||||
{
|
||||
@ -139,7 +154,9 @@ impl Rope {
|
||||
}
|
||||
|
||||
pub fn offset_to_point(&self, offset: usize) -> Point {
|
||||
assert!(offset <= self.summary().bytes);
|
||||
if offset >= self.summary().bytes {
|
||||
return self.summary().lines;
|
||||
}
|
||||
let mut cursor = self.chunks.cursor::<(usize, Point)>();
|
||||
cursor.seek(&offset, Bias::Left, &());
|
||||
let overshoot = offset - cursor.start().0;
|
||||
@ -150,7 +167,9 @@ impl Rope {
|
||||
}
|
||||
|
||||
pub fn offset_to_point_utf16(&self, offset: usize) -> PointUtf16 {
|
||||
assert!(offset <= self.summary().bytes);
|
||||
if offset >= self.summary().bytes {
|
||||
return self.summary().lines_utf16;
|
||||
}
|
||||
let mut cursor = self.chunks.cursor::<(usize, PointUtf16)>();
|
||||
cursor.seek(&offset, Bias::Left, &());
|
||||
let overshoot = offset - cursor.start().0;
|
||||
@ -161,7 +180,9 @@ impl Rope {
|
||||
}
|
||||
|
||||
pub fn point_to_offset(&self, point: Point) -> usize {
|
||||
assert!(point <= self.summary().lines);
|
||||
if point >= self.summary().lines {
|
||||
return self.summary().bytes;
|
||||
}
|
||||
let mut cursor = self.chunks.cursor::<(Point, usize)>();
|
||||
cursor.seek(&point, Bias::Left, &());
|
||||
let overshoot = point - cursor.start().0;
|
||||
@ -172,7 +193,9 @@ impl Rope {
|
||||
}
|
||||
|
||||
pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize {
|
||||
assert!(point <= self.summary().lines_utf16);
|
||||
if point >= self.summary().lines_utf16 {
|
||||
return self.summary().bytes;
|
||||
}
|
||||
let mut cursor = self.chunks.cursor::<(PointUtf16, usize)>();
|
||||
cursor.seek(&point, Bias::Left, &());
|
||||
let overshoot = point - cursor.start().0;
|
||||
@ -226,6 +249,11 @@ impl Rope {
|
||||
self.summary().lines_utf16
|
||||
}
|
||||
}
|
||||
|
||||
pub fn line_len(&self, row: u32) -> u32 {
|
||||
self.clip_point(Point::new(row, u32::MAX), Bias::Left)
|
||||
.column
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Rope {
|
||||
@ -236,9 +264,12 @@ impl<'a> From<&'a str> for Rope {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<String> for Rope {
|
||||
fn into(self) -> String {
|
||||
self.chunks().collect()
|
||||
impl fmt::Display for Rope {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for chunk in self.chunks() {
|
||||
write!(f, "{}", chunk)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -303,7 +334,7 @@ impl<'a> Cursor<'a> {
|
||||
if let Some(start_chunk) = self.chunks.item() {
|
||||
let start_ix = self.offset - self.chunks.start();
|
||||
let end_ix = cmp::min(end_offset, self.chunks.end(&())) - self.chunks.start();
|
||||
summary.add_assign(&D::from_summary(&TextSummary::from(
|
||||
summary.add_assign(&D::from_text_summary(&TextSummary::from(
|
||||
&start_chunk.0[start_ix..end_ix],
|
||||
)));
|
||||
}
|
||||
@ -313,7 +344,9 @@ impl<'a> Cursor<'a> {
|
||||
summary.add_assign(&self.chunks.summary(&end_offset, Bias::Right, &()));
|
||||
if let Some(end_chunk) = self.chunks.item() {
|
||||
let end_ix = end_offset - self.chunks.start();
|
||||
summary.add_assign(&D::from_summary(&TextSummary::from(&end_chunk.0[..end_ix])));
|
||||
summary.add_assign(&D::from_text_summary(&TextSummary::from(
|
||||
&end_chunk.0[..end_ix],
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -634,13 +667,16 @@ impl std::ops::AddAssign<Self> for TextSummary {
|
||||
}
|
||||
|
||||
pub trait TextDimension<'a>: Dimension<'a, TextSummary> {
|
||||
fn from_summary(summary: &TextSummary) -> Self;
|
||||
fn from_text_summary(summary: &TextSummary) -> Self;
|
||||
fn add_assign(&mut self, other: &Self);
|
||||
}
|
||||
|
||||
impl<'a, D1: TextDimension<'a>, D2: TextDimension<'a>> TextDimension<'a> for (D1, D2) {
|
||||
fn from_summary(summary: &TextSummary) -> Self {
|
||||
(D1::from_summary(summary), D2::from_summary(summary))
|
||||
fn from_text_summary(summary: &TextSummary) -> Self {
|
||||
(
|
||||
D1::from_text_summary(summary),
|
||||
D2::from_text_summary(summary),
|
||||
)
|
||||
}
|
||||
|
||||
fn add_assign(&mut self, other: &Self) {
|
||||
@ -650,7 +686,7 @@ impl<'a, D1: TextDimension<'a>, D2: TextDimension<'a>> TextDimension<'a> for (D1
|
||||
}
|
||||
|
||||
impl<'a> TextDimension<'a> for TextSummary {
|
||||
fn from_summary(summary: &TextSummary) -> Self {
|
||||
fn from_text_summary(summary: &TextSummary) -> Self {
|
||||
summary.clone()
|
||||
}
|
||||
|
||||
@ -666,7 +702,7 @@ impl<'a> sum_tree::Dimension<'a, TextSummary> for usize {
|
||||
}
|
||||
|
||||
impl<'a> TextDimension<'a> for usize {
|
||||
fn from_summary(summary: &TextSummary) -> Self {
|
||||
fn from_text_summary(summary: &TextSummary) -> Self {
|
||||
summary.bytes
|
||||
}
|
||||
|
||||
@ -682,7 +718,7 @@ impl<'a> sum_tree::Dimension<'a, TextSummary> for Point {
|
||||
}
|
||||
|
||||
impl<'a> TextDimension<'a> for Point {
|
||||
fn from_summary(summary: &TextSummary) -> Self {
|
||||
fn from_text_summary(summary: &TextSummary) -> Self {
|
||||
summary.lines
|
||||
}
|
||||
|
||||
@ -698,7 +734,7 @@ impl<'a> sum_tree::Dimension<'a, TextSummary> for PointUtf16 {
|
||||
}
|
||||
|
||||
impl<'a> TextDimension<'a> for PointUtf16 {
|
||||
fn from_summary(summary: &TextSummary) -> Self {
|
||||
fn from_text_summary(summary: &TextSummary) -> Self {
|
||||
summary.lines_utf16
|
||||
}
|
||||
|
||||
@ -731,7 +767,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::random_char_iter::RandomCharIter;
|
||||
use rand::prelude::*;
|
||||
use std::env;
|
||||
use std::{cmp::Ordering, env};
|
||||
use Bias::{Left, Right};
|
||||
|
||||
#[test]
|
||||
@ -778,7 +814,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
fn test_random(mut rng: StdRng) {
|
||||
fn test_random_rope(mut rng: StdRng) {
|
||||
let operations = env::var("OPERATIONS")
|
||||
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||
.unwrap_or(10);
|
||||
@ -862,6 +898,38 @@ mod tests {
|
||||
TextSummary::from(&expected[start_ix..end_ix])
|
||||
);
|
||||
}
|
||||
|
||||
let mut expected_longest_rows = Vec::new();
|
||||
let mut longest_line_len = -1_isize;
|
||||
for (row, line) in expected.split('\n').enumerate() {
|
||||
let row = row as u32;
|
||||
assert_eq!(
|
||||
actual.line_len(row),
|
||||
line.len() as u32,
|
||||
"invalid line len for row {}",
|
||||
row
|
||||
);
|
||||
|
||||
let line_char_count = line.chars().count() as isize;
|
||||
match line_char_count.cmp(&longest_line_len) {
|
||||
Ordering::Less => {}
|
||||
Ordering::Equal => expected_longest_rows.push(row),
|
||||
Ordering::Greater => {
|
||||
longest_line_len = line_char_count;
|
||||
expected_longest_rows.clear();
|
||||
expected_longest_rows.push(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let longest_row = actual.summary().longest_row;
|
||||
assert!(
|
||||
expected_longest_rows.contains(&longest_row),
|
||||
"incorrect longest row {}. expected {:?} with length {}",
|
||||
longest_row,
|
||||
expected_longest_rows,
|
||||
longest_line_len,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,4 +116,36 @@ impl SelectionSet {
|
||||
goal: state.goal,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn oldest_selection<'a, D, C>(&'a self, content: C) -> Option<Selection<D>>
|
||||
where
|
||||
D: 'a + TextDimension<'a>,
|
||||
C: 'a + Into<Content<'a>>,
|
||||
{
|
||||
self.selections
|
||||
.min_by_key(content, |selection| selection.id)
|
||||
.map(|(range, state)| Selection {
|
||||
id: state.id,
|
||||
start: range.start,
|
||||
end: range.end,
|
||||
reversed: state.reversed,
|
||||
goal: state.goal,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn newest_selection<'a, D, C>(&'a self, content: C) -> Option<Selection<D>>
|
||||
where
|
||||
D: 'a + TextDimension<'a>,
|
||||
C: 'a + Into<Content<'a>>,
|
||||
{
|
||||
self.selections
|
||||
.max_by_key(content, |selection| selection.id)
|
||||
.map(|(range, state)| Selection {
|
||||
id: state.id,
|
||||
start: range.start,
|
||||
end: range.end,
|
||||
reversed: state.reversed,
|
||||
goal: state.goal,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,8 @@ smol = "1.2"
|
||||
buffer = { path = "../buffer", features = ["test-support"] }
|
||||
language = { path = "../language", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
ctor = "0.1"
|
||||
env_logger = "0.8"
|
||||
rand = "0.8"
|
||||
unindent = "0.1.7"
|
||||
tree-sitter = "0.19"
|
||||
|
@ -1,18 +1,29 @@
|
||||
mod block_map;
|
||||
mod fold_map;
|
||||
mod patch;
|
||||
mod tab_map;
|
||||
mod wrap_map;
|
||||
|
||||
pub use block_map::{BlockDisposition, BlockId, BlockProperties, BufferRows, Chunks};
|
||||
use block_map::{BlockMap, BlockPoint};
|
||||
use buffer::Rope;
|
||||
use fold_map::{FoldMap, ToFoldPoint as _};
|
||||
use gpui::{fonts::FontId, Entity, ModelContext, ModelHandle};
|
||||
use gpui::{
|
||||
fonts::{FontId, HighlightStyle},
|
||||
AppContext, Entity, ModelContext, ModelHandle,
|
||||
};
|
||||
use language::{Anchor, Buffer, Point, ToOffset, ToPoint};
|
||||
use std::ops::Range;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
ops::Range,
|
||||
};
|
||||
use sum_tree::Bias;
|
||||
use tab_map::TabMap;
|
||||
use theme::{BlockStyle, SyntaxTheme};
|
||||
use wrap_map::WrapMap;
|
||||
pub use wrap_map::{BufferRows, HighlightedChunks};
|
||||
|
||||
pub trait ToDisplayPoint {
|
||||
fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint;
|
||||
fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint;
|
||||
}
|
||||
|
||||
pub struct DisplayMap {
|
||||
@ -20,6 +31,7 @@ pub struct DisplayMap {
|
||||
fold_map: FoldMap,
|
||||
tab_map: TabMap,
|
||||
wrap_map: ModelHandle<WrapMap>,
|
||||
block_map: BlockMap,
|
||||
}
|
||||
|
||||
impl Entity for DisplayMap {
|
||||
@ -37,28 +49,32 @@ impl DisplayMap {
|
||||
) -> Self {
|
||||
let (fold_map, snapshot) = FoldMap::new(buffer.clone(), cx);
|
||||
let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
|
||||
let wrap_map =
|
||||
cx.add_model(|cx| WrapMap::new(snapshot, font_id, font_size, wrap_width, cx));
|
||||
let (wrap_map, snapshot) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx);
|
||||
let block_map = BlockMap::new(buffer.clone(), snapshot);
|
||||
cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
|
||||
DisplayMap {
|
||||
buffer,
|
||||
fold_map,
|
||||
tab_map,
|
||||
wrap_map,
|
||||
block_map,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn snapshot(&self, cx: &mut ModelContext<Self>) -> DisplayMapSnapshot {
|
||||
let (folds_snapshot, edits) = self.fold_map.read(cx);
|
||||
let (tabs_snapshot, edits) = self.tab_map.sync(folds_snapshot.clone(), edits);
|
||||
let wraps_snapshot = self
|
||||
let (wraps_snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(tabs_snapshot.clone(), edits, cx));
|
||||
let blocks_snapshot = self.block_map.read(wraps_snapshot.clone(), edits, cx);
|
||||
|
||||
DisplayMapSnapshot {
|
||||
buffer_snapshot: self.buffer.read(cx).snapshot(),
|
||||
folds_snapshot,
|
||||
tabs_snapshot,
|
||||
wraps_snapshot,
|
||||
blocks_snapshot,
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,12 +85,16 @@ impl DisplayMap {
|
||||
) {
|
||||
let (mut fold_map, snapshot, edits) = self.fold_map.write(cx);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
|
||||
self.wrap_map
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
self.block_map.read(snapshot, edits, cx);
|
||||
let (snapshot, edits) = fold_map.fold(ranges, cx);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
|
||||
self.wrap_map
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
self.block_map.read(snapshot, edits, cx);
|
||||
}
|
||||
|
||||
pub fn unfold<T: ToOffset>(
|
||||
@ -84,12 +104,52 @@ impl DisplayMap {
|
||||
) {
|
||||
let (mut fold_map, snapshot, edits) = self.fold_map.write(cx);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
|
||||
self.wrap_map
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
self.block_map.read(snapshot, edits, cx);
|
||||
let (snapshot, edits) = fold_map.unfold(ranges, cx);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
|
||||
self.wrap_map
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
self.block_map.read(snapshot, edits, cx);
|
||||
}
|
||||
|
||||
pub fn insert_blocks<P, T>(
|
||||
&mut self,
|
||||
blocks: impl IntoIterator<Item = BlockProperties<P, T>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Vec<BlockId>
|
||||
where
|
||||
P: ToOffset + Clone,
|
||||
T: Into<Rope> + Clone,
|
||||
{
|
||||
let (snapshot, edits) = self.fold_map.read(cx);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
let mut block_map = self.block_map.write(snapshot, edits, cx);
|
||||
block_map.insert(blocks, cx)
|
||||
}
|
||||
|
||||
pub fn restyle_blocks<F1, F2>(&mut self, styles: HashMap<BlockId, (Option<F1>, Option<F2>)>)
|
||||
where
|
||||
F1: 'static + Fn(&AppContext) -> Vec<(usize, HighlightStyle)>,
|
||||
F2: 'static + Fn(&AppContext) -> BlockStyle,
|
||||
{
|
||||
self.block_map.restyle(styles);
|
||||
}
|
||||
|
||||
pub fn remove_blocks(&mut self, ids: HashSet<BlockId>, cx: &mut ModelContext<Self>) {
|
||||
let (snapshot, edits) = self.fold_map.read(cx);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
let mut block_map = self.block_map.write(snapshot, edits, cx);
|
||||
block_map.remove(ids, cx);
|
||||
}
|
||||
|
||||
pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext<Self>) {
|
||||
@ -113,6 +173,7 @@ pub struct DisplayMapSnapshot {
|
||||
folds_snapshot: fold_map::Snapshot,
|
||||
tabs_snapshot: tab_map::Snapshot,
|
||||
wraps_snapshot: wrap_map::Snapshot,
|
||||
blocks_snapshot: block_map::BlockSnapshot,
|
||||
}
|
||||
|
||||
impl DisplayMapSnapshot {
|
||||
@ -125,8 +186,8 @@ impl DisplayMapSnapshot {
|
||||
self.buffer_snapshot.len() == 0
|
||||
}
|
||||
|
||||
pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
|
||||
self.wraps_snapshot.buffer_rows(start_row)
|
||||
pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: Option<&'a AppContext>) -> BufferRows<'a> {
|
||||
self.blocks_snapshot.buffer_rows(start_row, cx)
|
||||
}
|
||||
|
||||
pub fn buffer_row_count(&self) -> u32 {
|
||||
@ -136,9 +197,9 @@ impl DisplayMapSnapshot {
|
||||
pub fn prev_row_boundary(&self, mut display_point: DisplayPoint) -> (DisplayPoint, Point) {
|
||||
loop {
|
||||
*display_point.column_mut() = 0;
|
||||
let mut point = display_point.to_buffer_point(self, Bias::Left);
|
||||
let mut point = display_point.to_point(self);
|
||||
point.column = 0;
|
||||
let next_display_point = point.to_display_point(self, Bias::Left);
|
||||
let next_display_point = self.point_to_display_point(point, Bias::Left);
|
||||
if next_display_point == display_point {
|
||||
return (display_point, point);
|
||||
}
|
||||
@ -149,9 +210,9 @@ impl DisplayMapSnapshot {
|
||||
pub fn next_row_boundary(&self, mut display_point: DisplayPoint) -> (DisplayPoint, Point) {
|
||||
loop {
|
||||
*display_point.column_mut() = self.line_len(display_point.row());
|
||||
let mut point = display_point.to_buffer_point(self, Bias::Right);
|
||||
let mut point = display_point.to_point(self);
|
||||
point.column = self.buffer_snapshot.line_len(point.row);
|
||||
let next_display_point = point.to_display_point(self, Bias::Right);
|
||||
let next_display_point = self.point_to_display_point(point, Bias::Right);
|
||||
if next_display_point == display_point {
|
||||
return (display_point, point);
|
||||
}
|
||||
@ -159,25 +220,46 @@ impl DisplayMapSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint {
|
||||
DisplayPoint(
|
||||
self.blocks_snapshot.to_block_point(
|
||||
self.wraps_snapshot.from_tab_point(
|
||||
self.tabs_snapshot
|
||||
.to_tab_point(point.to_fold_point(&self.folds_snapshot, bias)),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point {
|
||||
let unblocked_point = self.blocks_snapshot.to_wrap_point(point.0);
|
||||
let unwrapped_point = self.wraps_snapshot.to_tab_point(unblocked_point);
|
||||
let unexpanded_point = self.tabs_snapshot.to_fold_point(unwrapped_point, bias).0;
|
||||
unexpanded_point.to_buffer_point(&self.folds_snapshot)
|
||||
}
|
||||
|
||||
pub fn max_point(&self) -> DisplayPoint {
|
||||
DisplayPoint(self.wraps_snapshot.max_point())
|
||||
DisplayPoint(self.blocks_snapshot.max_point())
|
||||
}
|
||||
|
||||
pub fn chunks_at(&self, display_row: u32) -> wrap_map::Chunks {
|
||||
self.wraps_snapshot.chunks_at(display_row)
|
||||
pub fn text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> {
|
||||
self.blocks_snapshot
|
||||
.chunks(display_row..self.max_point().row() + 1, None, None)
|
||||
.map(|h| h.text)
|
||||
}
|
||||
|
||||
pub fn highlighted_chunks_for_rows(
|
||||
&mut self,
|
||||
pub fn chunks<'a>(
|
||||
&'a self,
|
||||
display_rows: Range<u32>,
|
||||
) -> wrap_map::HighlightedChunks {
|
||||
self.wraps_snapshot
|
||||
.highlighted_chunks_for_rows(display_rows)
|
||||
theme: Option<&'a SyntaxTheme>,
|
||||
cx: &'a AppContext,
|
||||
) -> block_map::Chunks<'a> {
|
||||
self.blocks_snapshot.chunks(display_rows, theme, Some(cx))
|
||||
}
|
||||
|
||||
pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator<Item = char> + 'a {
|
||||
let mut column = 0;
|
||||
let mut chars = self.chunks_at(point.row()).flat_map(str::chars);
|
||||
let mut chars = self.text_chunks(point.row()).flat_map(str::chars);
|
||||
while column < point.column() {
|
||||
if let Some(c) = chars.next() {
|
||||
column += c.len_utf8() as u32;
|
||||
@ -215,7 +297,7 @@ impl DisplayMapSnapshot {
|
||||
}
|
||||
|
||||
pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
|
||||
DisplayPoint(self.wraps_snapshot.clip_point(point.0, bias))
|
||||
DisplayPoint(self.blocks_snapshot.clip_point(point.0, bias))
|
||||
}
|
||||
|
||||
pub fn folds_in_range<'a, T>(
|
||||
@ -233,22 +315,31 @@ impl DisplayMapSnapshot {
|
||||
}
|
||||
|
||||
pub fn is_line_folded(&self, display_row: u32) -> bool {
|
||||
let wrap_point = DisplayPoint::new(display_row, 0).0;
|
||||
let row = self.wraps_snapshot.to_tab_point(wrap_point).row();
|
||||
self.folds_snapshot.is_line_folded(row)
|
||||
let block_point = BlockPoint(Point::new(display_row, 0));
|
||||
let wrap_point = self.blocks_snapshot.to_wrap_point(block_point);
|
||||
let tab_point = self.wraps_snapshot.to_tab_point(wrap_point);
|
||||
self.folds_snapshot.is_line_folded(tab_point.row())
|
||||
}
|
||||
|
||||
pub fn is_block_line(&self, display_row: u32) -> bool {
|
||||
self.blocks_snapshot.is_block_line(display_row)
|
||||
}
|
||||
|
||||
pub fn soft_wrap_indent(&self, display_row: u32) -> Option<u32> {
|
||||
self.wraps_snapshot.soft_wrap_indent(display_row)
|
||||
let wrap_row = self
|
||||
.blocks_snapshot
|
||||
.to_wrap_point(BlockPoint::new(display_row, 0))
|
||||
.row();
|
||||
self.wraps_snapshot.soft_wrap_indent(wrap_row)
|
||||
}
|
||||
|
||||
pub fn text(&self) -> String {
|
||||
self.chunks_at(0).collect()
|
||||
self.text_chunks(0).collect()
|
||||
}
|
||||
|
||||
pub fn line(&self, display_row: u32) -> String {
|
||||
let mut result = String::new();
|
||||
for chunk in self.chunks_at(display_row) {
|
||||
for chunk in self.text_chunks(display_row) {
|
||||
if let Some(ix) = chunk.find('\n') {
|
||||
result.push_str(&chunk[0..ix]);
|
||||
break;
|
||||
@ -274,30 +365,20 @@ impl DisplayMapSnapshot {
|
||||
}
|
||||
|
||||
pub fn line_len(&self, row: u32) -> u32 {
|
||||
self.wraps_snapshot.line_len(row)
|
||||
self.blocks_snapshot.line_len(row)
|
||||
}
|
||||
|
||||
pub fn longest_row(&self) -> u32 {
|
||||
self.wraps_snapshot.longest_row()
|
||||
}
|
||||
|
||||
pub fn anchor_before(&self, point: DisplayPoint, bias: Bias) -> Anchor {
|
||||
self.buffer_snapshot
|
||||
.anchor_before(point.to_buffer_point(self, bias))
|
||||
}
|
||||
|
||||
pub fn anchor_after(&self, point: DisplayPoint, bias: Bias) -> Anchor {
|
||||
self.buffer_snapshot
|
||||
.anchor_after(point.to_buffer_point(self, bias))
|
||||
self.blocks_snapshot.longest_row()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||
pub struct DisplayPoint(wrap_map::WrapPoint);
|
||||
pub struct DisplayPoint(BlockPoint);
|
||||
|
||||
impl DisplayPoint {
|
||||
pub fn new(row: u32, column: u32) -> Self {
|
||||
Self(wrap_map::WrapPoint::new(row, column))
|
||||
Self(BlockPoint(Point::new(row, column)))
|
||||
}
|
||||
|
||||
pub fn zero() -> Self {
|
||||
@ -310,50 +391,52 @@ impl DisplayPoint {
|
||||
}
|
||||
|
||||
pub fn row(self) -> u32 {
|
||||
self.0.row()
|
||||
self.0.row
|
||||
}
|
||||
|
||||
pub fn column(self) -> u32 {
|
||||
self.0.column()
|
||||
self.0.column
|
||||
}
|
||||
|
||||
pub fn row_mut(&mut self) -> &mut u32 {
|
||||
self.0.row_mut()
|
||||
&mut self.0.row
|
||||
}
|
||||
|
||||
pub fn column_mut(&mut self) -> &mut u32 {
|
||||
self.0.column_mut()
|
||||
&mut self.0.column
|
||||
}
|
||||
|
||||
pub fn to_buffer_point(self, map: &DisplayMapSnapshot, bias: Bias) -> Point {
|
||||
let unwrapped_point = map.wraps_snapshot.to_tab_point(self.0);
|
||||
let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0;
|
||||
unexpanded_point.to_buffer_point(&map.folds_snapshot)
|
||||
pub fn to_point(self, map: &DisplayMapSnapshot) -> Point {
|
||||
map.display_point_to_point(self, Bias::Left)
|
||||
}
|
||||
|
||||
pub fn to_buffer_offset(self, map: &DisplayMapSnapshot, bias: Bias) -> usize {
|
||||
let unwrapped_point = map.wraps_snapshot.to_tab_point(self.0);
|
||||
pub fn to_offset(self, map: &DisplayMapSnapshot, bias: Bias) -> usize {
|
||||
let unblocked_point = map.blocks_snapshot.to_wrap_point(self.0);
|
||||
let unwrapped_point = map.wraps_snapshot.to_tab_point(unblocked_point);
|
||||
let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0;
|
||||
unexpanded_point.to_buffer_offset(&map.folds_snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDisplayPoint for Point {
|
||||
fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint {
|
||||
let fold_point = self.to_fold_point(&map.folds_snapshot, bias);
|
||||
let tab_point = map.tabs_snapshot.to_tab_point(fold_point);
|
||||
let wrap_point = map.wraps_snapshot.to_wrap_point(tab_point);
|
||||
DisplayPoint(wrap_point)
|
||||
fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint {
|
||||
map.point_to_display_point(*self, Bias::Left)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDisplayPoint for Anchor {
|
||||
fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint {
|
||||
self.to_point(&map.buffer_snapshot)
|
||||
.to_display_point(map, bias)
|
||||
fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint {
|
||||
self.to_point(&map.buffer_snapshot).to_display_point(map)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum DisplayRow {
|
||||
Buffer(u32),
|
||||
Block(BlockId, Option<BlockStyle>),
|
||||
Wrap,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -472,28 +555,28 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
prev_display_bound,
|
||||
prev_buffer_bound.to_display_point(&snapshot, Left),
|
||||
prev_buffer_bound.to_display_point(&snapshot),
|
||||
"row boundary before {:?}. reported buffer row boundary: {:?}",
|
||||
point,
|
||||
prev_buffer_bound
|
||||
);
|
||||
assert_eq!(
|
||||
next_display_bound,
|
||||
next_buffer_bound.to_display_point(&snapshot, Right),
|
||||
next_buffer_bound.to_display_point(&snapshot),
|
||||
"display row boundary after {:?}. reported buffer row boundary: {:?}",
|
||||
point,
|
||||
next_buffer_bound
|
||||
);
|
||||
assert_eq!(
|
||||
prev_buffer_bound,
|
||||
prev_display_bound.to_buffer_point(&snapshot, Left),
|
||||
prev_display_bound.to_point(&snapshot),
|
||||
"row boundary before {:?}. reported display row boundary: {:?}",
|
||||
point,
|
||||
prev_display_bound
|
||||
);
|
||||
assert_eq!(
|
||||
next_buffer_bound,
|
||||
next_display_bound.to_buffer_point(&snapshot, Right),
|
||||
next_display_bound.to_point(&snapshot),
|
||||
"row boundary after {:?}. reported display row boundary: {:?}",
|
||||
point,
|
||||
next_display_bound
|
||||
@ -559,7 +642,7 @@ mod tests {
|
||||
|
||||
let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
|
||||
assert_eq!(
|
||||
snapshot.chunks_at(0).collect::<String>(),
|
||||
snapshot.text_chunks(0).collect::<String>(),
|
||||
"one two \nthree four \nfive\nsix seven \neight"
|
||||
);
|
||||
assert_eq!(
|
||||
@ -608,7 +691,7 @@ mod tests {
|
||||
|
||||
let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
|
||||
assert_eq!(
|
||||
snapshot.chunks_at(1).collect::<String>(),
|
||||
snapshot.text_chunks(1).collect::<String>(),
|
||||
"three four \nfive\nsix and \nseven eight"
|
||||
);
|
||||
|
||||
@ -617,13 +700,13 @@ mod tests {
|
||||
|
||||
let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
|
||||
assert_eq!(
|
||||
snapshot.chunks_at(1).collect::<String>(),
|
||||
snapshot.text_chunks(1).collect::<String>(),
|
||||
"three \nfour five\nsix and \nseven \neight"
|
||||
)
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_chunks_at(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_text_chunks(cx: &mut gpui::MutableAppContext) {
|
||||
let text = sample_text(6, 6);
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
|
||||
let tab_size = 4;
|
||||
@ -650,7 +733,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
map.update(cx, |map, cx| map.snapshot(cx))
|
||||
.chunks_at(1)
|
||||
.text_chunks(1)
|
||||
.collect::<String>()
|
||||
.lines()
|
||||
.next(),
|
||||
@ -658,7 +741,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
map.update(cx, |map, cx| map.snapshot(cx))
|
||||
.chunks_at(2)
|
||||
.text_chunks(2)
|
||||
.collect::<String>()
|
||||
.lines()
|
||||
.next(),
|
||||
@ -667,7 +750,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_highlighted_chunks_at(mut cx: gpui::TestAppContext) {
|
||||
async fn test_chunks(mut cx: gpui::TestAppContext) {
|
||||
use unindent::Unindent as _;
|
||||
|
||||
let text = r#"
|
||||
@ -679,8 +762,8 @@ mod tests {
|
||||
.unindent();
|
||||
|
||||
let theme = SyntaxTheme::new(vec![
|
||||
("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
|
||||
("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
|
||||
("mod.body".to_string(), Color::red().into()),
|
||||
("fn.name".to_string(), Color::blue().into()),
|
||||
]);
|
||||
let lang = Arc::new(
|
||||
Language::new(
|
||||
@ -716,22 +799,22 @@ mod tests {
|
||||
let map =
|
||||
cx.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, cx));
|
||||
assert_eq!(
|
||||
cx.update(|cx| highlighted_chunks(0..5, &map, &theme, cx)),
|
||||
cx.update(|cx| chunks(0..5, &map, &theme, cx)),
|
||||
vec![
|
||||
("fn ".to_string(), None),
|
||||
("outer".to_string(), Some("fn.name")),
|
||||
("outer".to_string(), Some(Color::blue())),
|
||||
("() {}\n\nmod module ".to_string(), None),
|
||||
("{\n fn ".to_string(), Some("mod.body")),
|
||||
("inner".to_string(), Some("fn.name")),
|
||||
("() {}\n}".to_string(), Some("mod.body")),
|
||||
("{\n fn ".to_string(), Some(Color::red())),
|
||||
("inner".to_string(), Some(Color::blue())),
|
||||
("() {}\n}".to_string(), Some(Color::red())),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
cx.update(|cx| highlighted_chunks(3..5, &map, &theme, cx)),
|
||||
cx.update(|cx| chunks(3..5, &map, &theme, cx)),
|
||||
vec![
|
||||
(" fn ".to_string(), Some("mod.body")),
|
||||
("inner".to_string(), Some("fn.name")),
|
||||
("() {}\n}".to_string(), Some("mod.body")),
|
||||
(" fn ".to_string(), Some(Color::red())),
|
||||
("inner".to_string(), Some(Color::blue())),
|
||||
("() {}\n}".to_string(), Some(Color::red())),
|
||||
]
|
||||
);
|
||||
|
||||
@ -739,20 +822,20 @@ mod tests {
|
||||
map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
|
||||
});
|
||||
assert_eq!(
|
||||
cx.update(|cx| highlighted_chunks(0..2, &map, &theme, cx)),
|
||||
cx.update(|cx| chunks(0..2, &map, &theme, cx)),
|
||||
vec![
|
||||
("fn ".to_string(), None),
|
||||
("out".to_string(), Some("fn.name")),
|
||||
("out".to_string(), Some(Color::blue())),
|
||||
("…".to_string(), None),
|
||||
(" fn ".to_string(), Some("mod.body")),
|
||||
("inner".to_string(), Some("fn.name")),
|
||||
("() {}\n}".to_string(), Some("mod.body")),
|
||||
(" fn ".to_string(), Some(Color::red())),
|
||||
("inner".to_string(), Some(Color::blue())),
|
||||
("() {}\n}".to_string(), Some(Color::red())),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_highlighted_chunks_with_soft_wrapping(mut cx: gpui::TestAppContext) {
|
||||
async fn test_chunks_with_soft_wrapping(mut cx: gpui::TestAppContext) {
|
||||
use unindent::Unindent as _;
|
||||
|
||||
cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
|
||||
@ -766,8 +849,8 @@ mod tests {
|
||||
.unindent();
|
||||
|
||||
let theme = SyntaxTheme::new(vec![
|
||||
("mod.body".to_string(), Color::from_u32(0xff0000ff).into()),
|
||||
("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()),
|
||||
("mod.body".to_string(), Color::red().into()),
|
||||
("fn.name".to_string(), Color::blue().into()),
|
||||
]);
|
||||
let lang = Arc::new(
|
||||
Language::new(
|
||||
@ -804,15 +887,15 @@ mod tests {
|
||||
let map = cx
|
||||
.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, Some(40.0), cx));
|
||||
assert_eq!(
|
||||
cx.update(|cx| highlighted_chunks(0..5, &map, &theme, cx)),
|
||||
cx.update(|cx| chunks(0..5, &map, &theme, cx)),
|
||||
[
|
||||
("fn \n".to_string(), None),
|
||||
("oute\nr".to_string(), Some("fn.name")),
|
||||
("oute\nr".to_string(), Some(Color::blue())),
|
||||
("() \n{}\n\n".to_string(), None),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
cx.update(|cx| highlighted_chunks(3..5, &map, &theme, cx)),
|
||||
cx.update(|cx| chunks(3..5, &map, &theme, cx)),
|
||||
[("{}\n\n".to_string(), None)]
|
||||
);
|
||||
|
||||
@ -820,12 +903,12 @@ mod tests {
|
||||
map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
|
||||
});
|
||||
assert_eq!(
|
||||
cx.update(|cx| highlighted_chunks(1..4, &map, &theme, cx)),
|
||||
cx.update(|cx| chunks(1..4, &map, &theme, cx)),
|
||||
[
|
||||
("out".to_string(), Some("fn.name")),
|
||||
("out".to_string(), Some(Color::blue())),
|
||||
("…\n".to_string(), None),
|
||||
(" \nfn ".to_string(), Some("mod.body")),
|
||||
("i\n".to_string(), Some("fn.name"))
|
||||
(" \nfn ".to_string(), Some(Color::red())),
|
||||
("i\n".to_string(), Some(Color::blue()))
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -895,42 +978,34 @@ mod tests {
|
||||
let map = map.update(cx, |map, cx| map.snapshot(cx));
|
||||
assert_eq!(map.text(), "✅ α\nβ \n🏀β γ");
|
||||
assert_eq!(
|
||||
map.chunks_at(0).collect::<String>(),
|
||||
map.text_chunks(0).collect::<String>(),
|
||||
"✅ α\nβ \n🏀β γ"
|
||||
);
|
||||
assert_eq!(map.chunks_at(1).collect::<String>(), "β \n🏀β γ");
|
||||
assert_eq!(map.chunks_at(2).collect::<String>(), "🏀β γ");
|
||||
assert_eq!(map.text_chunks(1).collect::<String>(), "β \n🏀β γ");
|
||||
assert_eq!(map.text_chunks(2).collect::<String>(), "🏀β γ");
|
||||
|
||||
let point = Point::new(0, "✅\t\t".len() as u32);
|
||||
let display_point = DisplayPoint::new(0, "✅ ".len() as u32);
|
||||
assert_eq!(point.to_display_point(&map, Left), display_point);
|
||||
assert_eq!(display_point.to_buffer_point(&map, Left), point,);
|
||||
assert_eq!(point.to_display_point(&map), display_point);
|
||||
assert_eq!(display_point.to_point(&map), point);
|
||||
|
||||
let point = Point::new(1, "β\t".len() as u32);
|
||||
let display_point = DisplayPoint::new(1, "β ".len() as u32);
|
||||
assert_eq!(point.to_display_point(&map, Left), display_point);
|
||||
assert_eq!(display_point.to_buffer_point(&map, Left), point,);
|
||||
assert_eq!(point.to_display_point(&map), display_point);
|
||||
assert_eq!(display_point.to_point(&map), point,);
|
||||
|
||||
let point = Point::new(2, "🏀β\t\t".len() as u32);
|
||||
let display_point = DisplayPoint::new(2, "🏀β ".len() as u32);
|
||||
assert_eq!(point.to_display_point(&map, Left), display_point);
|
||||
assert_eq!(display_point.to_buffer_point(&map, Left), point,);
|
||||
assert_eq!(point.to_display_point(&map), display_point);
|
||||
assert_eq!(display_point.to_point(&map), point,);
|
||||
|
||||
// Display points inside of expanded tabs
|
||||
assert_eq!(
|
||||
DisplayPoint::new(0, "✅ ".len() as u32).to_buffer_point(&map, Right),
|
||||
Point::new(0, "✅\t\t".len() as u32),
|
||||
);
|
||||
assert_eq!(
|
||||
DisplayPoint::new(0, "✅ ".len() as u32).to_buffer_point(&map, Left),
|
||||
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
|
||||
|
1600
crates/editor/src/display_map/block_map.rs
Normal file
1600
crates/editor/src/display_map/block_map.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
511
crates/editor/src/display_map/patch.rs
Normal file
511
crates/editor/src/display_map/patch.rs
Normal 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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
use super::fold_map::{self, FoldEdit, FoldPoint, Snapshot as FoldSnapshot};
|
||||
use language::{rope, HighlightedChunk};
|
||||
use super::fold_map::{self, FoldEdit, FoldPoint, Snapshot as FoldSnapshot, ToFoldPoint};
|
||||
use buffer::Point;
|
||||
use language::{rope, Chunk};
|
||||
use parking_lot::Mutex;
|
||||
use std::{mem, ops::Range};
|
||||
use std::{cmp, mem, ops::Range};
|
||||
use sum_tree::Bias;
|
||||
use theme::SyntaxTheme;
|
||||
|
||||
pub struct TabMap(Mutex<Snapshot>);
|
||||
|
||||
@ -21,6 +23,7 @@ impl TabMap {
|
||||
mut fold_edits: Vec<FoldEdit>,
|
||||
) -> (Snapshot, Vec<Edit>) {
|
||||
let mut old_snapshot = self.0.lock();
|
||||
let max_offset = old_snapshot.fold_snapshot.len();
|
||||
let new_snapshot = Snapshot {
|
||||
fold_snapshot,
|
||||
tab_size: old_snapshot.tab_size,
|
||||
@ -31,11 +34,11 @@ impl TabMap {
|
||||
let mut delta = 0;
|
||||
for chunk in old_snapshot
|
||||
.fold_snapshot
|
||||
.chunks_at(fold_edit.old_bytes.end)
|
||||
.chunks(fold_edit.old_bytes.end..max_offset, None)
|
||||
{
|
||||
let patterns: &[_] = &['\t', '\n'];
|
||||
if let Some(ix) = chunk.find(patterns) {
|
||||
if &chunk[ix..ix + 1] == "\t" {
|
||||
if let Some(ix) = chunk.text.find(patterns) {
|
||||
if &chunk.text[ix..ix + 1] == "\t" {
|
||||
fold_edit.old_bytes.end.0 += delta + ix + 1;
|
||||
fold_edit.new_bytes.end.0 += delta + ix + 1;
|
||||
}
|
||||
@ -43,7 +46,7 @@ impl TabMap {
|
||||
break;
|
||||
}
|
||||
|
||||
delta += chunk.len();
|
||||
delta += chunk.text.len();
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,28 +111,31 @@ impl Snapshot {
|
||||
.text_summary_for_range(input_start..input_end);
|
||||
|
||||
let mut first_line_chars = 0;
|
||||
let mut first_line_bytes = 0;
|
||||
for c in self.chunks_at(range.start).flat_map(|chunk| chunk.chars()) {
|
||||
if c == '\n'
|
||||
|| (range.start.row() == range.end.row() && first_line_bytes == range.end.column())
|
||||
{
|
||||
let line_end = if range.start.row() == range.end.row() {
|
||||
range.end
|
||||
} else {
|
||||
self.max_point()
|
||||
};
|
||||
for c in self
|
||||
.chunks(range.start..line_end, None)
|
||||
.flat_map(|chunk| chunk.text.chars())
|
||||
{
|
||||
if c == '\n' {
|
||||
break;
|
||||
}
|
||||
first_line_chars += 1;
|
||||
first_line_bytes += c.len_utf8() as u32;
|
||||
}
|
||||
|
||||
let mut last_line_chars = 0;
|
||||
let mut last_line_bytes = 0;
|
||||
for c in self
|
||||
.chunks_at(TabPoint::new(range.end.row(), 0).max(range.start))
|
||||
.flat_map(|chunk| chunk.chars())
|
||||
{
|
||||
if last_line_bytes == range.end.column() {
|
||||
break;
|
||||
if range.start.row() == range.end.row() {
|
||||
last_line_chars = first_line_chars;
|
||||
} else {
|
||||
for _ in self
|
||||
.chunks(TabPoint::new(range.end.row(), 0)..range.end, None)
|
||||
.flat_map(|chunk| chunk.text.chars())
|
||||
{
|
||||
last_line_chars += 1;
|
||||
}
|
||||
last_line_chars += 1;
|
||||
last_line_bytes += c.len_utf8() as u32;
|
||||
}
|
||||
|
||||
TextSummary {
|
||||
@ -145,21 +151,11 @@ impl Snapshot {
|
||||
self.fold_snapshot.version
|
||||
}
|
||||
|
||||
pub fn chunks_at(&self, point: TabPoint) -> Chunks {
|
||||
let (point, expanded_char_column, to_next_stop) = self.to_fold_point(point, Bias::Left);
|
||||
let fold_chunks = self
|
||||
.fold_snapshot
|
||||
.chunks_at(point.to_offset(&self.fold_snapshot));
|
||||
Chunks {
|
||||
fold_chunks,
|
||||
column: expanded_char_column,
|
||||
tab_size: self.tab_size,
|
||||
chunk: &SPACES[0..to_next_stop],
|
||||
skip_leading_tab: to_next_stop > 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn highlighted_chunks(&mut self, range: Range<TabPoint>) -> HighlightedChunks {
|
||||
pub fn chunks<'a>(
|
||||
&'a self,
|
||||
range: Range<TabPoint>,
|
||||
theme: Option<&'a SyntaxTheme>,
|
||||
) -> Chunks<'a> {
|
||||
let (input_start, expanded_char_column, to_next_stop) =
|
||||
self.to_fold_point(range.start, Bias::Left);
|
||||
let input_start = input_start.to_offset(&self.fold_snapshot);
|
||||
@ -167,13 +163,19 @@ impl Snapshot {
|
||||
.to_fold_point(range.end, Bias::Right)
|
||||
.0
|
||||
.to_offset(&self.fold_snapshot);
|
||||
HighlightedChunks {
|
||||
fold_chunks: self
|
||||
.fold_snapshot
|
||||
.highlighted_chunks(input_start..input_end),
|
||||
let to_next_stop = if range.start.0 + Point::new(0, to_next_stop as u32) > range.end.0 {
|
||||
(range.end.column() - range.start.column()) as usize
|
||||
} else {
|
||||
to_next_stop
|
||||
};
|
||||
|
||||
Chunks {
|
||||
fold_chunks: self.fold_snapshot.chunks(input_start..input_end, theme),
|
||||
column: expanded_char_column,
|
||||
output_position: range.start.0,
|
||||
max_output_position: range.end.0,
|
||||
tab_size: self.tab_size,
|
||||
chunk: HighlightedChunk {
|
||||
chunk: Chunk {
|
||||
text: &SPACES[0..to_next_stop],
|
||||
..Default::default()
|
||||
},
|
||||
@ -187,7 +189,9 @@ impl Snapshot {
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn text(&self) -> String {
|
||||
self.chunks_at(Default::default()).collect()
|
||||
self.chunks(TabPoint::zero()..self.max_point(), None)
|
||||
.map(|chunk| chunk.text)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn max_point(&self) -> TabPoint {
|
||||
@ -207,6 +211,10 @@ impl Snapshot {
|
||||
TabPoint::new(input.row(), expanded as u32)
|
||||
}
|
||||
|
||||
pub fn from_point(&self, point: Point, bias: Bias) -> TabPoint {
|
||||
self.to_tab_point(point.to_fold_point(&self.fold_snapshot, bias))
|
||||
}
|
||||
|
||||
pub fn to_fold_point(&self, output: TabPoint, bias: Bias) -> (FoldPoint, usize, usize) {
|
||||
let chars = self.fold_snapshot.chars_at(FoldPoint::new(output.row(), 0));
|
||||
let expanded = output.column() as usize;
|
||||
@ -219,6 +227,12 @@ impl Snapshot {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point {
|
||||
self.to_fold_point(point, bias)
|
||||
.0
|
||||
.to_buffer_point(&self.fold_snapshot)
|
||||
}
|
||||
|
||||
fn expand_tabs(chars: impl Iterator<Item = char>, column: usize, tab_size: usize) -> usize {
|
||||
let mut expanded_chars = 0;
|
||||
let mut expanded_bytes = 0;
|
||||
@ -368,63 +382,16 @@ const SPACES: &'static str = " ";
|
||||
|
||||
pub struct Chunks<'a> {
|
||||
fold_chunks: fold_map::Chunks<'a>,
|
||||
chunk: &'a str,
|
||||
chunk: Chunk<'a>,
|
||||
column: usize,
|
||||
output_position: Point,
|
||||
max_output_position: Point,
|
||||
tab_size: usize,
|
||||
skip_leading_tab: bool,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Chunks<'a> {
|
||||
type Item = &'a str;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.chunk.is_empty() {
|
||||
if let Some(chunk) = self.fold_chunks.next() {
|
||||
self.chunk = chunk;
|
||||
if self.skip_leading_tab {
|
||||
self.chunk = &self.chunk[1..];
|
||||
self.skip_leading_tab = false;
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
for (ix, c) in self.chunk.char_indices() {
|
||||
match c {
|
||||
'\t' => {
|
||||
if ix > 0 {
|
||||
let (prefix, suffix) = self.chunk.split_at(ix);
|
||||
self.chunk = suffix;
|
||||
return Some(prefix);
|
||||
} else {
|
||||
self.chunk = &self.chunk[1..];
|
||||
let len = self.tab_size - self.column % self.tab_size;
|
||||
self.column += len;
|
||||
return Some(&SPACES[0..len]);
|
||||
}
|
||||
}
|
||||
'\n' => self.column = 0,
|
||||
_ => self.column += 1,
|
||||
}
|
||||
}
|
||||
|
||||
let result = Some(self.chunk);
|
||||
self.chunk = "";
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HighlightedChunks<'a> {
|
||||
fold_chunks: fold_map::HighlightedChunks<'a>,
|
||||
chunk: HighlightedChunk<'a>,
|
||||
column: usize,
|
||||
tab_size: usize,
|
||||
skip_leading_tab: bool,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for HighlightedChunks<'a> {
|
||||
type Item = HighlightedChunk<'a>;
|
||||
type Item = Chunk<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.chunk.text.is_empty() {
|
||||
@ -445,22 +412,34 @@ impl<'a> Iterator for HighlightedChunks<'a> {
|
||||
if ix > 0 {
|
||||
let (prefix, suffix) = self.chunk.text.split_at(ix);
|
||||
self.chunk.text = suffix;
|
||||
return Some(HighlightedChunk {
|
||||
return Some(Chunk {
|
||||
text: prefix,
|
||||
..self.chunk
|
||||
});
|
||||
} else {
|
||||
self.chunk.text = &self.chunk.text[1..];
|
||||
let len = self.tab_size - self.column % self.tab_size;
|
||||
let mut len = self.tab_size - self.column % self.tab_size;
|
||||
let next_output_position = cmp::min(
|
||||
self.output_position + Point::new(0, len as u32),
|
||||
self.max_output_position,
|
||||
);
|
||||
len = (next_output_position.column - self.output_position.column) as usize;
|
||||
self.column += len;
|
||||
return Some(HighlightedChunk {
|
||||
self.output_position = next_output_position;
|
||||
return Some(Chunk {
|
||||
text: &SPACES[0..len],
|
||||
..self.chunk
|
||||
});
|
||||
}
|
||||
}
|
||||
'\n' => self.column = 0,
|
||||
_ => self.column += 1,
|
||||
'\n' => {
|
||||
self.column = 0;
|
||||
self.output_position += Point::new(1, 0);
|
||||
}
|
||||
_ => {
|
||||
self.column += 1;
|
||||
self.output_position.column += c.len_utf8() as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -471,6 +450,10 @@ impl<'a> Iterator for HighlightedChunks<'a> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::display_map::fold_map::FoldMap;
|
||||
use buffer::{RandomCharIter, Rope};
|
||||
use language::Buffer;
|
||||
use rand::{prelude::StdRng, Rng};
|
||||
|
||||
#[test]
|
||||
fn test_expand_tabs() {
|
||||
@ -478,4 +461,62 @@ mod tests {
|
||||
assert_eq!(Snapshot::expand_tabs("\t".chars(), 1, 4), 4);
|
||||
assert_eq!(Snapshot::expand_tabs("\ta".chars(), 2, 4), 5);
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
fn test_random(cx: &mut gpui::MutableAppContext, mut rng: StdRng) {
|
||||
let tab_size = rng.gen_range(1..=4);
|
||||
let buffer = cx.add_model(|cx| {
|
||||
let len = rng.gen_range(0..30);
|
||||
let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
|
||||
Buffer::new(0, text, cx)
|
||||
});
|
||||
log::info!("Buffer text: {:?}", buffer.read(cx).text());
|
||||
|
||||
let (mut fold_map, _) = FoldMap::new(buffer.clone(), cx);
|
||||
fold_map.randomly_mutate(&mut rng, cx);
|
||||
let (folds_snapshot, _) = fold_map.read(cx);
|
||||
log::info!("FoldMap text: {:?}", folds_snapshot.text());
|
||||
|
||||
let (_, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size);
|
||||
let text = Rope::from(tabs_snapshot.text().as_str());
|
||||
log::info!(
|
||||
"TabMap text (tab size: {}): {:?}",
|
||||
tab_size,
|
||||
tabs_snapshot.text(),
|
||||
);
|
||||
|
||||
for _ in 0..5 {
|
||||
let end_row = rng.gen_range(0..=text.max_point().row);
|
||||
let end_column = rng.gen_range(0..=text.line_len(end_row));
|
||||
let mut end = TabPoint(text.clip_point(Point::new(end_row, end_column), Bias::Right));
|
||||
let start_row = rng.gen_range(0..=text.max_point().row);
|
||||
let start_column = rng.gen_range(0..=text.line_len(start_row));
|
||||
let mut start =
|
||||
TabPoint(text.clip_point(Point::new(start_row, start_column), Bias::Left));
|
||||
if start > end {
|
||||
mem::swap(&mut start, &mut end);
|
||||
}
|
||||
|
||||
let expected_text = text
|
||||
.chunks_in_range(text.point_to_offset(start.0)..text.point_to_offset(end.0))
|
||||
.collect::<String>();
|
||||
let expected_summary = TextSummary::from(expected_text.as_str());
|
||||
log::info!("slicing {:?}..{:?} (text: {:?})", start, end, text);
|
||||
assert_eq!(
|
||||
expected_text,
|
||||
tabs_snapshot
|
||||
.chunks(start..end, None)
|
||||
.map(|c| c.text)
|
||||
.collect::<String>()
|
||||
);
|
||||
|
||||
let mut actual_summary = tabs_snapshot.text_summary_for_range(start..end);
|
||||
if tab_size > 1 && folds_snapshot.text().contains('\t') {
|
||||
actual_summary.longest_row = expected_summary.longest_row;
|
||||
actual_summary.longest_row_chars = expected_summary.longest_row_chars;
|
||||
}
|
||||
|
||||
assert_eq!(actual_summary, expected_summary,);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,28 @@
|
||||
use super::{
|
||||
fold_map,
|
||||
tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint, TextSummary},
|
||||
patch::Patch,
|
||||
tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint},
|
||||
DisplayRow,
|
||||
};
|
||||
use gpui::{fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, Task};
|
||||
use language::{HighlightedChunk, Point};
|
||||
use gpui::{
|
||||
fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, ModelHandle, MutableAppContext,
|
||||
Task,
|
||||
};
|
||||
use language::{Chunk, Point};
|
||||
use lazy_static::lazy_static;
|
||||
use smol::future::yield_now;
|
||||
use std::{collections::VecDeque, ops::Range, time::Duration};
|
||||
use std::{collections::VecDeque, mem, ops::Range, time::Duration};
|
||||
use sum_tree::{Bias, Cursor, SumTree};
|
||||
use theme::SyntaxTheme;
|
||||
|
||||
pub use super::tab_map::TextSummary;
|
||||
pub type Edit = buffer::Edit<u32>;
|
||||
|
||||
pub struct WrapMap {
|
||||
snapshot: Snapshot,
|
||||
pending_edits: VecDeque<(TabSnapshot, Vec<TabEdit>)>,
|
||||
interpolated_edits: Patch,
|
||||
edits_since_sync: Patch,
|
||||
wrap_width: Option<f32>,
|
||||
background_task: Option<Task<()>>,
|
||||
font: (FontId, f32),
|
||||
@ -41,18 +52,11 @@ struct TransformSummary {
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||
pub struct WrapPoint(super::Point);
|
||||
pub struct WrapPoint(pub super::Point);
|
||||
|
||||
pub struct Chunks<'a> {
|
||||
input_chunks: tab_map::Chunks<'a>,
|
||||
input_chunk: &'a str,
|
||||
output_position: WrapPoint,
|
||||
transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>,
|
||||
}
|
||||
|
||||
pub struct HighlightedChunks<'a> {
|
||||
input_chunks: tab_map::HighlightedChunks<'a>,
|
||||
input_chunk: HighlightedChunk<'a>,
|
||||
input_chunk: Chunk<'a>,
|
||||
output_position: WrapPoint,
|
||||
max_output_row: u32,
|
||||
transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>,
|
||||
@ -73,18 +77,24 @@ impl WrapMap {
|
||||
font_id: FontId,
|
||||
font_size: f32,
|
||||
wrap_width: Option<f32>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let mut this = Self {
|
||||
font: (font_id, font_size),
|
||||
wrap_width: None,
|
||||
pending_edits: Default::default(),
|
||||
snapshot: Snapshot::new(tab_snapshot),
|
||||
background_task: None,
|
||||
};
|
||||
this.set_wrap_width(wrap_width, cx);
|
||||
|
||||
this
|
||||
cx: &mut MutableAppContext,
|
||||
) -> (ModelHandle<Self>, Snapshot) {
|
||||
let handle = cx.add_model(|cx| {
|
||||
let mut this = Self {
|
||||
font: (font_id, font_size),
|
||||
wrap_width: None,
|
||||
pending_edits: Default::default(),
|
||||
interpolated_edits: Default::default(),
|
||||
edits_since_sync: Default::default(),
|
||||
snapshot: Snapshot::new(tab_snapshot),
|
||||
background_task: None,
|
||||
};
|
||||
this.set_wrap_width(wrap_width, cx);
|
||||
mem::take(&mut this.edits_since_sync);
|
||||
this
|
||||
});
|
||||
let snapshot = handle.read(cx).snapshot.clone();
|
||||
(handle, snapshot)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -97,10 +107,13 @@ impl WrapMap {
|
||||
tab_snapshot: TabSnapshot,
|
||||
edits: Vec<TabEdit>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Snapshot {
|
||||
) -> (Snapshot, Vec<Edit>) {
|
||||
self.pending_edits.push_back((tab_snapshot, edits));
|
||||
self.flush_edits(cx);
|
||||
self.snapshot.clone()
|
||||
(
|
||||
self.snapshot.clone(),
|
||||
mem::take(&mut self.edits_since_sync).into_inner(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_font(&mut self, font_id: FontId, font_size: f32, cx: &mut ModelContext<Self>) {
|
||||
@ -122,6 +135,8 @@ impl WrapMap {
|
||||
|
||||
fn rewrap(&mut self, cx: &mut ModelContext<Self>) {
|
||||
self.background_task.take();
|
||||
self.interpolated_edits.clear();
|
||||
self.pending_edits.clear();
|
||||
|
||||
if let Some(wrap_width) = self.wrap_width {
|
||||
let mut new_snapshot = self.snapshot.clone();
|
||||
@ -131,7 +146,7 @@ impl WrapMap {
|
||||
let mut line_wrapper = font_cache.line_wrapper(font_id, font_size);
|
||||
let tab_snapshot = new_snapshot.tab_snapshot.clone();
|
||||
let range = TabPoint::zero()..tab_snapshot.max_point();
|
||||
new_snapshot
|
||||
let edits = new_snapshot
|
||||
.update(
|
||||
tab_snapshot,
|
||||
&[TabEdit {
|
||||
@ -142,22 +157,27 @@ impl WrapMap {
|
||||
&mut line_wrapper,
|
||||
)
|
||||
.await;
|
||||
new_snapshot
|
||||
(new_snapshot, edits)
|
||||
});
|
||||
|
||||
match cx
|
||||
.background()
|
||||
.block_with_timeout(Duration::from_millis(5), task)
|
||||
{
|
||||
Ok(snapshot) => {
|
||||
Ok((snapshot, edits)) => {
|
||||
self.snapshot = snapshot;
|
||||
self.edits_since_sync = self.edits_since_sync.compose(&edits);
|
||||
cx.notify();
|
||||
}
|
||||
Err(wrap_task) => {
|
||||
self.background_task = Some(cx.spawn(|this, mut cx| async move {
|
||||
let snapshot = wrap_task.await;
|
||||
let (snapshot, edits) = wrap_task.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.snapshot = snapshot;
|
||||
this.edits_since_sync = this
|
||||
.edits_since_sync
|
||||
.compose(mem::take(&mut this.interpolated_edits).invert())
|
||||
.compose(&edits);
|
||||
this.background_task = None;
|
||||
this.flush_edits(cx);
|
||||
cx.notify();
|
||||
@ -166,6 +186,7 @@ impl WrapMap {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let old_rows = self.snapshot.transforms.summary().output.lines.row + 1;
|
||||
self.snapshot.transforms = SumTree::new();
|
||||
let summary = self.snapshot.tab_snapshot.text_summary();
|
||||
if !summary.lines.is_zero() {
|
||||
@ -173,6 +194,14 @@ impl WrapMap {
|
||||
.transforms
|
||||
.push(Transform::isomorphic(summary), &());
|
||||
}
|
||||
let new_rows = self.snapshot.transforms.summary().output.lines.row + 1;
|
||||
self.snapshot.interpolated = false;
|
||||
self.edits_since_sync = self.edits_since_sync.compose(&unsafe {
|
||||
Patch::new_unchecked(vec![Edit {
|
||||
old: 0..old_rows,
|
||||
new: 0..new_rows,
|
||||
}])
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -202,26 +231,33 @@ impl WrapMap {
|
||||
let update_task = cx.background().spawn(async move {
|
||||
let mut line_wrapper = font_cache.line_wrapper(font_id, font_size);
|
||||
|
||||
for (tab_snapshot, edits) in pending_edits {
|
||||
snapshot
|
||||
.update(tab_snapshot, &edits, wrap_width, &mut line_wrapper)
|
||||
let mut edits = Patch::default();
|
||||
for (tab_snapshot, tab_edits) in pending_edits {
|
||||
let wrap_edits = snapshot
|
||||
.update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper)
|
||||
.await;
|
||||
edits = edits.compose(&wrap_edits);
|
||||
}
|
||||
snapshot
|
||||
(snapshot, edits)
|
||||
});
|
||||
|
||||
match cx
|
||||
.background()
|
||||
.block_with_timeout(Duration::from_millis(1), update_task)
|
||||
{
|
||||
Ok(snapshot) => {
|
||||
Ok((snapshot, output_edits)) => {
|
||||
self.snapshot = snapshot;
|
||||
self.edits_since_sync = self.edits_since_sync.compose(&output_edits);
|
||||
}
|
||||
Err(update_task) => {
|
||||
self.background_task = Some(cx.spawn(|this, mut cx| async move {
|
||||
let snapshot = update_task.await;
|
||||
let (snapshot, edits) = update_task.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.snapshot = snapshot;
|
||||
this.edits_since_sync = this
|
||||
.edits_since_sync
|
||||
.compose(mem::take(&mut this.interpolated_edits).invert())
|
||||
.compose(&edits);
|
||||
this.background_task = None;
|
||||
this.flush_edits(cx);
|
||||
cx.notify();
|
||||
@ -238,7 +274,9 @@ impl WrapMap {
|
||||
if tab_snapshot.version() <= self.snapshot.tab_snapshot.version() {
|
||||
to_remove_len += 1;
|
||||
} else {
|
||||
self.snapshot.interpolate(tab_snapshot.clone(), &edits);
|
||||
let interpolated_edits = self.snapshot.interpolate(tab_snapshot.clone(), &edits);
|
||||
self.edits_since_sync = self.edits_since_sync.compose(&interpolated_edits);
|
||||
self.interpolated_edits = self.interpolated_edits.compose(&interpolated_edits);
|
||||
}
|
||||
}
|
||||
|
||||
@ -262,17 +300,21 @@ impl Snapshot {
|
||||
}
|
||||
}
|
||||
|
||||
fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, edits: &[TabEdit]) {
|
||||
fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, tab_edits: &[TabEdit]) -> Patch {
|
||||
let mut new_transforms;
|
||||
if edits.is_empty() {
|
||||
if tab_edits.is_empty() {
|
||||
new_transforms = self.transforms.clone();
|
||||
} else {
|
||||
let mut old_cursor = self.transforms.cursor::<TabPoint>();
|
||||
let mut edits = edits.into_iter().peekable();
|
||||
new_transforms =
|
||||
old_cursor.slice(&edits.peek().unwrap().old_lines.start, Bias::Right, &());
|
||||
|
||||
while let Some(edit) = edits.next() {
|
||||
let mut tab_edits_iter = tab_edits.iter().peekable();
|
||||
new_transforms = old_cursor.slice(
|
||||
&tab_edits_iter.peek().unwrap().old_lines.start,
|
||||
Bias::Right,
|
||||
&(),
|
||||
);
|
||||
|
||||
while let Some(edit) = tab_edits_iter.next() {
|
||||
if edit.new_lines.start > TabPoint::from(new_transforms.summary().input.lines) {
|
||||
let summary = new_tab_snapshot.text_summary_for_range(
|
||||
TabPoint::from(new_transforms.summary().input.lines)..edit.new_lines.start,
|
||||
@ -287,7 +329,7 @@ impl Snapshot {
|
||||
}
|
||||
|
||||
old_cursor.seek_forward(&edit.old_lines.end, Bias::Right, &());
|
||||
if let Some(next_edit) = edits.peek() {
|
||||
if let Some(next_edit) = tab_edits_iter.peek() {
|
||||
if next_edit.old_lines.start > old_cursor.end(&()) {
|
||||
if old_cursor.end(&()) > edit.old_lines.end {
|
||||
let summary = self
|
||||
@ -295,6 +337,7 @@ impl Snapshot {
|
||||
.text_summary_for_range(edit.old_lines.end..old_cursor.end(&()));
|
||||
new_transforms.push_or_extend(Transform::isomorphic(summary));
|
||||
}
|
||||
|
||||
old_cursor.next(&());
|
||||
new_transforms.push_tree(
|
||||
old_cursor.slice(&next_edit.old_lines.start, Bias::Right, &()),
|
||||
@ -314,38 +357,44 @@ impl Snapshot {
|
||||
}
|
||||
}
|
||||
|
||||
self.transforms = new_transforms;
|
||||
self.tab_snapshot = new_tab_snapshot;
|
||||
self.interpolated = true;
|
||||
let old_snapshot = mem::replace(
|
||||
self,
|
||||
Snapshot {
|
||||
tab_snapshot: new_tab_snapshot,
|
||||
transforms: new_transforms,
|
||||
interpolated: true,
|
||||
},
|
||||
);
|
||||
self.check_invariants();
|
||||
old_snapshot.compute_edits(tab_edits, self)
|
||||
}
|
||||
|
||||
async fn update(
|
||||
&mut self,
|
||||
new_tab_snapshot: TabSnapshot,
|
||||
edits: &[TabEdit],
|
||||
tab_edits: &[TabEdit],
|
||||
wrap_width: f32,
|
||||
line_wrapper: &mut LineWrapper,
|
||||
) {
|
||||
) -> Patch {
|
||||
#[derive(Debug)]
|
||||
struct RowEdit {
|
||||
old_rows: Range<u32>,
|
||||
new_rows: Range<u32>,
|
||||
}
|
||||
|
||||
let mut edits = edits.into_iter().peekable();
|
||||
let mut tab_edits_iter = tab_edits.into_iter().peekable();
|
||||
let mut row_edits = Vec::new();
|
||||
while let Some(edit) = edits.next() {
|
||||
while let Some(edit) = tab_edits_iter.next() {
|
||||
let mut row_edit = RowEdit {
|
||||
old_rows: edit.old_lines.start.row()..edit.old_lines.end.row() + 1,
|
||||
new_rows: edit.new_lines.start.row()..edit.new_lines.end.row() + 1,
|
||||
};
|
||||
|
||||
while let Some(next_edit) = edits.peek() {
|
||||
while let Some(next_edit) = tab_edits_iter.peek() {
|
||||
if next_edit.old_lines.start.row() <= row_edit.old_rows.end {
|
||||
row_edit.old_rows.end = next_edit.old_lines.end.row() + 1;
|
||||
row_edit.new_rows.end = next_edit.new_lines.end.row() + 1;
|
||||
edits.next();
|
||||
tab_edits_iter.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@ -370,7 +419,7 @@ impl Snapshot {
|
||||
while let Some(edit) = row_edits.next() {
|
||||
if edit.new_rows.start > new_transforms.summary().input.lines.row {
|
||||
let summary = new_tab_snapshot.text_summary_for_range(
|
||||
TabPoint::new(new_transforms.summary().input.lines.row, 0)
|
||||
TabPoint(new_transforms.summary().input.lines)
|
||||
..TabPoint::new(edit.new_rows.start, 0),
|
||||
);
|
||||
new_transforms.push_or_extend(Transform::isomorphic(summary));
|
||||
@ -378,10 +427,15 @@ impl Snapshot {
|
||||
|
||||
let mut line = String::new();
|
||||
let mut remaining = None;
|
||||
let mut chunks = new_tab_snapshot.chunks_at(TabPoint::new(edit.new_rows.start, 0));
|
||||
let mut chunks = new_tab_snapshot.chunks(
|
||||
TabPoint::new(edit.new_rows.start, 0)..new_tab_snapshot.max_point(),
|
||||
None,
|
||||
);
|
||||
let mut edit_transforms = Vec::<Transform>::new();
|
||||
for _ in edit.new_rows.start..edit.new_rows.end {
|
||||
while let Some(chunk) = remaining.take().or_else(|| chunks.next()) {
|
||||
while let Some(chunk) =
|
||||
remaining.take().or_else(|| chunks.next().map(|c| c.text))
|
||||
{
|
||||
if let Some(ix) = chunk.find('\n') {
|
||||
line.push_str(&chunk[..ix + 1]);
|
||||
remaining = Some(&chunk[ix + 1..]);
|
||||
@ -452,30 +506,60 @@ impl Snapshot {
|
||||
}
|
||||
}
|
||||
|
||||
self.transforms = new_transforms;
|
||||
self.tab_snapshot = new_tab_snapshot;
|
||||
self.interpolated = false;
|
||||
let old_snapshot = mem::replace(
|
||||
self,
|
||||
Snapshot {
|
||||
tab_snapshot: new_tab_snapshot,
|
||||
transforms: new_transforms,
|
||||
interpolated: false,
|
||||
},
|
||||
);
|
||||
self.check_invariants();
|
||||
old_snapshot.compute_edits(tab_edits, self)
|
||||
}
|
||||
|
||||
pub fn chunks_at(&self, wrap_row: u32) -> Chunks {
|
||||
let point = WrapPoint::new(wrap_row, 0);
|
||||
let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>();
|
||||
transforms.seek(&point, Bias::Right, &());
|
||||
let mut input_position = TabPoint(transforms.start().1 .0);
|
||||
if transforms.item().map_or(false, |t| t.is_isomorphic()) {
|
||||
input_position.0 += point.0 - transforms.start().0 .0;
|
||||
}
|
||||
let input_chunks = self.tab_snapshot.chunks_at(input_position);
|
||||
Chunks {
|
||||
input_chunks,
|
||||
transforms,
|
||||
output_position: point,
|
||||
input_chunk: "",
|
||||
fn compute_edits(&self, tab_edits: &[TabEdit], new_snapshot: &Snapshot) -> Patch {
|
||||
let mut wrap_edits = Vec::new();
|
||||
let mut old_cursor = self.transforms.cursor::<TransformSummary>();
|
||||
let mut new_cursor = new_snapshot.transforms.cursor::<TransformSummary>();
|
||||
for mut tab_edit in tab_edits.iter().cloned() {
|
||||
tab_edit.old_lines.start.0.column = 0;
|
||||
tab_edit.old_lines.end.0 += Point::new(1, 0);
|
||||
tab_edit.new_lines.start.0.column = 0;
|
||||
tab_edit.new_lines.end.0 += Point::new(1, 0);
|
||||
|
||||
old_cursor.seek(&tab_edit.old_lines.start, Bias::Right, &());
|
||||
let mut old_start = old_cursor.start().output.lines;
|
||||
old_start += tab_edit.old_lines.start.0 - old_cursor.start().input.lines;
|
||||
|
||||
old_cursor.seek(&tab_edit.old_lines.end, Bias::Right, &());
|
||||
let mut old_end = old_cursor.start().output.lines;
|
||||
old_end += tab_edit.old_lines.end.0 - old_cursor.start().input.lines;
|
||||
|
||||
new_cursor.seek(&tab_edit.new_lines.start, Bias::Right, &());
|
||||
let mut new_start = new_cursor.start().output.lines;
|
||||
new_start += tab_edit.new_lines.start.0 - new_cursor.start().input.lines;
|
||||
|
||||
new_cursor.seek(&tab_edit.new_lines.end, Bias::Right, &());
|
||||
let mut new_end = new_cursor.start().output.lines;
|
||||
new_end += tab_edit.new_lines.end.0 - new_cursor.start().input.lines;
|
||||
|
||||
wrap_edits.push(Edit {
|
||||
old: old_start.row..old_end.row,
|
||||
new: new_start.row..new_end.row,
|
||||
});
|
||||
}
|
||||
|
||||
consolidate_wrap_edits(&mut wrap_edits);
|
||||
unsafe { Patch::new_unchecked(wrap_edits) }
|
||||
}
|
||||
|
||||
pub fn 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!(
|
||||
|
@ -1,6 +1,6 @@
|
||||
use super::{
|
||||
DisplayPoint, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll, Select,
|
||||
SelectPhase, Snapshot, MAX_LINE_LEN,
|
||||
DisplayPoint, DisplayRow, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll,
|
||||
Select, SelectPhase, Snapshot, MAX_LINE_LEN,
|
||||
};
|
||||
use clock::ReplicaId;
|
||||
use gpui::{
|
||||
@ -17,7 +17,7 @@ use gpui::{
|
||||
MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle,
|
||||
};
|
||||
use json::json;
|
||||
use language::{DiagnosticSeverity, HighlightedChunk};
|
||||
use language::Chunk;
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
cmp::{self, Ordering},
|
||||
@ -25,6 +25,7 @@ use std::{
|
||||
fmt::Write,
|
||||
ops::Range,
|
||||
};
|
||||
use theme::BlockStyle;
|
||||
|
||||
pub struct EditorElement {
|
||||
view: WeakViewHandle<Editor>,
|
||||
@ -195,6 +196,7 @@ impl EditorElement {
|
||||
) {
|
||||
let bounds = gutter_bounds.union_rect(text_bounds);
|
||||
let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height;
|
||||
let start_row = layout.snapshot.scroll_position().y() as u32;
|
||||
let editor = self.view(cx.app);
|
||||
let style = &self.settings.style;
|
||||
cx.scene.push_quad(Quad {
|
||||
@ -239,6 +241,51 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw block backgrounds
|
||||
for (ixs, block_style) in &layout.block_layouts {
|
||||
let row = start_row + ixs.start;
|
||||
let offset = vec2f(0., row as f32 * layout.line_height - scroll_top);
|
||||
let height = ixs.len() as f32 * layout.line_height;
|
||||
cx.scene.push_quad(Quad {
|
||||
bounds: RectF::new(
|
||||
text_bounds.origin() + offset,
|
||||
vec2f(text_bounds.width(), height),
|
||||
),
|
||||
background: block_style.background,
|
||||
border: block_style
|
||||
.border
|
||||
.map_or(Default::default(), |color| Border {
|
||||
width: 1.,
|
||||
color,
|
||||
overlay: true,
|
||||
top: true,
|
||||
right: false,
|
||||
bottom: true,
|
||||
left: false,
|
||||
}),
|
||||
corner_radius: 0.,
|
||||
});
|
||||
cx.scene.push_quad(Quad {
|
||||
bounds: RectF::new(
|
||||
gutter_bounds.origin() + offset,
|
||||
vec2f(gutter_bounds.width(), height),
|
||||
),
|
||||
background: block_style.gutter_background,
|
||||
border: block_style
|
||||
.gutter_border
|
||||
.map_or(Default::default(), |color| Border {
|
||||
width: 1.,
|
||||
color,
|
||||
overlay: true,
|
||||
top: true,
|
||||
right: false,
|
||||
bottom: true,
|
||||
left: false,
|
||||
}),
|
||||
corner_radius: 0.,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_gutter(
|
||||
@ -401,18 +448,24 @@ impl EditorElement {
|
||||
.width()
|
||||
}
|
||||
|
||||
fn layout_line_numbers(
|
||||
fn layout_rows(
|
||||
&self,
|
||||
rows: Range<u32>,
|
||||
active_rows: &BTreeMap<u32, bool>,
|
||||
snapshot: &Snapshot,
|
||||
cx: &LayoutContext,
|
||||
) -> Vec<Option<text_layout::Line>> {
|
||||
) -> (
|
||||
Vec<Option<text_layout::Line>>,
|
||||
Vec<(Range<u32>, BlockStyle)>,
|
||||
) {
|
||||
let style = &self.settings.style;
|
||||
let mut layouts = Vec::with_capacity(rows.len());
|
||||
let include_line_numbers = snapshot.mode == EditorMode::Full;
|
||||
let mut last_block_id = None;
|
||||
let mut blocks = Vec::<(Range<u32>, BlockStyle)>::new();
|
||||
let mut line_number_layouts = Vec::with_capacity(rows.len());
|
||||
let mut line_number = String::new();
|
||||
for (ix, (buffer_row, soft_wrapped)) in snapshot
|
||||
.buffer_rows(rows.start)
|
||||
for (ix, row) in snapshot
|
||||
.buffer_rows(rows.start, cx)
|
||||
.take((rows.end - rows.start) as usize)
|
||||
.enumerate()
|
||||
{
|
||||
@ -422,27 +475,46 @@ impl EditorElement {
|
||||
} else {
|
||||
style.line_number
|
||||
};
|
||||
if soft_wrapped {
|
||||
layouts.push(None);
|
||||
} else {
|
||||
line_number.clear();
|
||||
write!(&mut line_number, "{}", buffer_row + 1).unwrap();
|
||||
layouts.push(Some(cx.text_layout_cache.layout_str(
|
||||
&line_number,
|
||||
style.text.font_size,
|
||||
&[(
|
||||
line_number.len(),
|
||||
RunStyle {
|
||||
font_id: style.text.font_id,
|
||||
color,
|
||||
underline: None,
|
||||
},
|
||||
)],
|
||||
)));
|
||||
match row {
|
||||
DisplayRow::Buffer(buffer_row) => {
|
||||
if include_line_numbers {
|
||||
line_number.clear();
|
||||
write!(&mut line_number, "{}", buffer_row + 1).unwrap();
|
||||
line_number_layouts.push(Some(cx.text_layout_cache.layout_str(
|
||||
&line_number,
|
||||
style.text.font_size,
|
||||
&[(
|
||||
line_number.len(),
|
||||
RunStyle {
|
||||
font_id: style.text.font_id,
|
||||
color,
|
||||
underline: None,
|
||||
},
|
||||
)],
|
||||
)));
|
||||
}
|
||||
last_block_id = None;
|
||||
}
|
||||
DisplayRow::Block(block_id, style) => {
|
||||
let ix = ix as u32;
|
||||
if last_block_id == Some(block_id) {
|
||||
if let Some((row_range, _)) = blocks.last_mut() {
|
||||
row_range.end += 1;
|
||||
}
|
||||
} else if let Some(style) = style {
|
||||
blocks.push((ix..ix + 1, style));
|
||||
}
|
||||
line_number_layouts.push(None);
|
||||
last_block_id = Some(block_id);
|
||||
}
|
||||
DisplayRow::Wrap => {
|
||||
line_number_layouts.push(None);
|
||||
last_block_id = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layouts
|
||||
(line_number_layouts, blocks)
|
||||
}
|
||||
|
||||
fn layout_lines(
|
||||
@ -493,9 +565,9 @@ impl EditorElement {
|
||||
let mut styles = Vec::new();
|
||||
let mut row = rows.start;
|
||||
let mut line_exceeded_max_len = false;
|
||||
let chunks = snapshot.highlighted_chunks_for_rows(rows.clone());
|
||||
let chunks = snapshot.chunks(rows.clone(), Some(&style.syntax), cx);
|
||||
|
||||
let newline_chunk = HighlightedChunk {
|
||||
let newline_chunk = Chunk {
|
||||
text: "\n",
|
||||
..Default::default()
|
||||
};
|
||||
@ -517,10 +589,8 @@ impl EditorElement {
|
||||
}
|
||||
|
||||
if !line_chunk.is_empty() && !line_exceeded_max_len {
|
||||
let highlight_style = chunk
|
||||
.highlight_id
|
||||
.style(&style.syntax)
|
||||
.unwrap_or(style.text.clone().into());
|
||||
let highlight_style =
|
||||
chunk.highlight_style.unwrap_or(style.text.clone().into());
|
||||
// Avoid a lookup if the font properties match the previous ones.
|
||||
let font_id = if highlight_style.font_properties == prev_font_properties {
|
||||
prev_font_id
|
||||
@ -543,13 +613,7 @@ impl EditorElement {
|
||||
}
|
||||
|
||||
let underline = if let Some(severity) = chunk.diagnostic {
|
||||
match severity {
|
||||
DiagnosticSeverity::ERROR => Some(style.error_underline),
|
||||
DiagnosticSeverity::WARNING => Some(style.warning_underline),
|
||||
DiagnosticSeverity::INFORMATION => Some(style.information_underline),
|
||||
DiagnosticSeverity::HINT => Some(style.hint_underline),
|
||||
_ => highlight_style.underline,
|
||||
}
|
||||
Some(super::diagnostic_style(severity, true, style).text)
|
||||
} else {
|
||||
highlight_style.underline
|
||||
};
|
||||
@ -677,11 +741,8 @@ impl Element for EditorElement {
|
||||
}
|
||||
});
|
||||
|
||||
let line_number_layouts = if snapshot.mode == EditorMode::Full {
|
||||
self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx)
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let (line_number_layouts, block_layouts) =
|
||||
self.layout_rows(start_row..end_row, &active_rows, &snapshot, cx);
|
||||
|
||||
let mut max_visible_line_width = 0.0;
|
||||
let line_layouts = self.layout_lines(start_row..end_row, &mut snapshot, cx);
|
||||
@ -703,6 +764,7 @@ impl Element for EditorElement {
|
||||
active_rows,
|
||||
line_layouts,
|
||||
line_number_layouts,
|
||||
block_layouts,
|
||||
line_height,
|
||||
em_width,
|
||||
selections,
|
||||
@ -825,6 +887,7 @@ pub struct LayoutState {
|
||||
active_rows: BTreeMap<u32, bool>,
|
||||
line_layouts: Vec<text_layout::Line>,
|
||||
line_number_layouts: Vec<Option<text_layout::Line>>,
|
||||
block_layouts: Vec<(Range<u32>, BlockStyle)>,
|
||||
line_height: f32,
|
||||
em_width: f32,
|
||||
selections: HashMap<ReplicaId, Vec<Range<DisplayPoint>>>,
|
||||
@ -1079,11 +1142,11 @@ mod tests {
|
||||
});
|
||||
let element = EditorElement::new(editor.downgrade(), settings);
|
||||
|
||||
let layouts = editor.update(cx, |editor, cx| {
|
||||
let (layouts, _) = editor.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let mut presenter = cx.build_presenter(window_id, 30.);
|
||||
let mut layout_cx = presenter.build_layout_context(false, cx);
|
||||
element.layout_line_numbers(0..6, &Default::default(), &snapshot, &mut layout_cx)
|
||||
element.layout_rows(0..6, &Default::default(), &snapshot, &mut layout_cx)
|
||||
});
|
||||
assert_eq!(layouts.len(), 6);
|
||||
}
|
||||
|
@ -7,13 +7,15 @@ mod test;
|
||||
|
||||
use buffer::rope::TextDimension;
|
||||
use clock::ReplicaId;
|
||||
pub use display_map::DisplayPoint;
|
||||
use display_map::*;
|
||||
pub use display_map::{DisplayPoint, DisplayRow};
|
||||
pub use element::*;
|
||||
use gpui::{
|
||||
action, geometry::vector::Vector2F, keymap::Binding, text_layout, AppContext, ClipboardItem,
|
||||
Element, ElementBox, Entity, ModelHandle, MutableAppContext, RenderContext, View, ViewContext,
|
||||
WeakViewHandle,
|
||||
action,
|
||||
geometry::vector::{vec2f, Vector2F},
|
||||
keymap::Binding,
|
||||
text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle,
|
||||
MutableAppContext, RenderContext, View, ViewContext, WeakViewHandle,
|
||||
};
|
||||
use language::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -22,6 +24,7 @@ use smol::Timer;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
cmp::{self, Ordering},
|
||||
collections::HashMap,
|
||||
iter, mem,
|
||||
ops::{Range, RangeInclusive},
|
||||
rc::Rc,
|
||||
@ -29,7 +32,7 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
use sum_tree::Bias;
|
||||
use theme::EditorStyle;
|
||||
use theme::{DiagnosticStyle, EditorStyle, SyntaxTheme};
|
||||
use util::post_inc;
|
||||
|
||||
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
||||
@ -83,6 +86,7 @@ action!(AddSelectionBelow);
|
||||
action!(SelectLargerSyntaxNode);
|
||||
action!(SelectSmallerSyntaxNode);
|
||||
action!(MoveToEnclosingBracket);
|
||||
action!(ShowNextDiagnostic);
|
||||
action!(PageUp);
|
||||
action!(PageDown);
|
||||
action!(Fold);
|
||||
@ -184,6 +188,7 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||
Binding::new("ctrl-w", SelectLargerSyntaxNode, Some("Editor")),
|
||||
Binding::new("alt-down", SelectSmallerSyntaxNode, Some("Editor")),
|
||||
Binding::new("ctrl-shift-W", SelectSmallerSyntaxNode, Some("Editor")),
|
||||
Binding::new("f8", ShowNextDiagnostic, Some("Editor")),
|
||||
Binding::new("ctrl-m", MoveToEnclosingBracket, Some("Editor")),
|
||||
Binding::new("pageup", PageUp, Some("Editor")),
|
||||
Binding::new("pagedown", PageDown, Some("Editor")),
|
||||
@ -242,6 +247,7 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(Editor::select_larger_syntax_node);
|
||||
cx.add_action(Editor::select_smaller_syntax_node);
|
||||
cx.add_action(Editor::move_to_enclosing_bracket);
|
||||
cx.add_action(Editor::show_next_diagnostic);
|
||||
cx.add_action(Editor::page_up);
|
||||
cx.add_action(Editor::page_down);
|
||||
cx.add_action(Editor::fold);
|
||||
@ -299,6 +305,7 @@ pub struct Editor {
|
||||
add_selections_state: Option<AddSelectionsState>,
|
||||
autoclose_stack: Vec<BracketPairState>,
|
||||
select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
|
||||
active_diagnostics: Option<ActiveDiagnosticGroup>,
|
||||
scroll_position: Vector2F,
|
||||
scroll_top_anchor: Anchor,
|
||||
autoscroll_requested: bool,
|
||||
@ -331,6 +338,14 @@ struct BracketPairState {
|
||||
pair: BracketPair,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ActiveDiagnosticGroup {
|
||||
primary_range: Range<Anchor>,
|
||||
primary_message: String,
|
||||
blocks: HashMap<BlockId, Diagnostic>,
|
||||
is_valid: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct ClipboardSelection {
|
||||
len: usize,
|
||||
@ -418,6 +433,7 @@ impl Editor {
|
||||
add_selections_state: None,
|
||||
autoclose_stack: Default::default(),
|
||||
select_larger_syntax_node_stack: Vec::new(),
|
||||
active_diagnostics: None,
|
||||
build_settings,
|
||||
scroll_position: Vector2F::zero(),
|
||||
scroll_top_anchor: Anchor::min(),
|
||||
@ -466,16 +482,24 @@ impl Editor {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn set_scroll_position(&mut self, mut scroll_position: Vector2F, cx: &mut ViewContext<Self>) {
|
||||
fn set_scroll_position(&mut self, scroll_position: Vector2F, cx: &mut ViewContext<Self>) {
|
||||
let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let scroll_top_buffer_offset =
|
||||
DisplayPoint::new(scroll_position.y() as u32, 0).to_buffer_offset(&map, Bias::Right);
|
||||
DisplayPoint::new(scroll_position.y() as u32, 0).to_offset(&map, Bias::Right);
|
||||
self.scroll_top_anchor = self
|
||||
.buffer
|
||||
.read(cx)
|
||||
.anchor_at(scroll_top_buffer_offset, Bias::Right);
|
||||
scroll_position.set_y(scroll_position.y().fract());
|
||||
self.scroll_position = scroll_position;
|
||||
self.scroll_position = vec2f(
|
||||
scroll_position.x(),
|
||||
scroll_position.y() - self.scroll_top_anchor.to_display_point(&map).row() as f32,
|
||||
);
|
||||
|
||||
debug_assert_eq!(
|
||||
compute_scroll_position(&map, self.scroll_position, &self.scroll_top_anchor),
|
||||
scroll_position
|
||||
);
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@ -519,13 +543,13 @@ impl Editor {
|
||||
.peek()
|
||||
.unwrap()
|
||||
.head()
|
||||
.to_display_point(&display_map, Bias::Left)
|
||||
.to_display_point(&display_map)
|
||||
.row() as f32;
|
||||
let last_cursor_bottom = selections
|
||||
.last()
|
||||
.unwrap()
|
||||
.head()
|
||||
.to_display_point(&display_map, Bias::Right)
|
||||
.to_display_point(&display_map)
|
||||
.row() as f32
|
||||
+ 1.0;
|
||||
|
||||
@ -570,7 +594,7 @@ impl Editor {
|
||||
let mut target_left = std::f32::INFINITY;
|
||||
let mut target_right = 0.0_f32;
|
||||
for selection in selections {
|
||||
let head = selection.head().to_display_point(&display_map, Bias::Left);
|
||||
let head = selection.head().to_display_point(&display_map);
|
||||
let start_column = head.column().saturating_sub(3);
|
||||
let end_column = cmp::min(display_map.line_len(head.row()), head.column() + 3);
|
||||
target_left = target_left
|
||||
@ -620,7 +644,7 @@ impl Editor {
|
||||
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let buffer = self.buffer.read(cx);
|
||||
let cursor = buffer.anchor_before(position.to_buffer_point(&display_map, Bias::Left));
|
||||
let cursor = buffer.anchor_before(position.to_point(&display_map));
|
||||
let selection = Selection {
|
||||
id: post_inc(&mut self.next_selection_id),
|
||||
start: cursor.clone(),
|
||||
@ -646,7 +670,7 @@ impl Editor {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
if let Some(pending_selection) = self.pending_selection.as_mut() {
|
||||
let buffer = self.buffer.read(cx);
|
||||
let cursor = buffer.anchor_before(position.to_buffer_point(&display_map, Bias::Left));
|
||||
let cursor = buffer.anchor_before(position.to_point(&display_map));
|
||||
if cursor.cmp(&pending_selection.tail(), buffer).unwrap() < Ordering::Equal {
|
||||
if !pending_selection.reversed {
|
||||
pending_selection.end = pending_selection.start.clone();
|
||||
@ -681,7 +705,9 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
|
||||
if let Some(pending_selection) = self.pending_selection.take() {
|
||||
if self.active_diagnostics.is_some() {
|
||||
self.dismiss_diagnostics(cx);
|
||||
} else if let Some(pending_selection) = self.pending_selection.take() {
|
||||
let buffer = self.buffer.read(cx);
|
||||
let pending_selection = Selection {
|
||||
id: pending_selection.id,
|
||||
@ -694,16 +720,8 @@ impl Editor {
|
||||
self.update_selections(vec![pending_selection], true, cx);
|
||||
}
|
||||
} else {
|
||||
let selections = self.selections::<Point>(cx);
|
||||
let mut selection_count = 0;
|
||||
let mut oldest_selection = selections
|
||||
.min_by_key(|s| {
|
||||
selection_count += 1;
|
||||
s.id
|
||||
})
|
||||
.unwrap()
|
||||
.clone();
|
||||
if selection_count == 1 {
|
||||
let mut oldest_selection = self.oldest_selection::<usize>(cx);
|
||||
if self.selection_count(cx) == 1 {
|
||||
oldest_selection.start = oldest_selection.head().clone();
|
||||
oldest_selection.end = oldest_selection.head().clone();
|
||||
}
|
||||
@ -763,8 +781,8 @@ impl Editor {
|
||||
};
|
||||
Selection {
|
||||
id: post_inc(&mut self.next_selection_id),
|
||||
start: start.to_buffer_point(&display_map, Bias::Left),
|
||||
end: end.to_buffer_point(&display_map, Bias::Left),
|
||||
start: start.to_point(&display_map),
|
||||
end: end.to_point(&display_map),
|
||||
reversed,
|
||||
goal: SelectionGoal::None,
|
||||
}
|
||||
@ -1052,10 +1070,10 @@ impl Editor {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
for selection in &mut selections {
|
||||
if selection.is_empty() {
|
||||
let head = selection.head().to_display_point(&display_map, Bias::Left);
|
||||
let head = selection.head().to_display_point(&display_map);
|
||||
let cursor = movement::left(&display_map, head)
|
||||
.unwrap()
|
||||
.to_buffer_point(&display_map, Bias::Left);
|
||||
.to_point(&display_map);
|
||||
selection.set_head(cursor);
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
@ -1071,10 +1089,10 @@ impl Editor {
|
||||
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
|
||||
for selection in &mut selections {
|
||||
if selection.is_empty() {
|
||||
let head = selection.head().to_display_point(&display_map, Bias::Left);
|
||||
let head = selection.head().to_display_point(&display_map);
|
||||
let cursor = movement::right(&display_map, head)
|
||||
.unwrap()
|
||||
.to_buffer_point(&display_map, Bias::Right);
|
||||
.to_point(&display_map);
|
||||
selection.set_head(cursor);
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
@ -1138,10 +1156,7 @@ impl Editor {
|
||||
let mut selections = selections.iter().peekable();
|
||||
while let Some(selection) = selections.next() {
|
||||
let mut rows = selection.spanned_rows(false, &display_map).buffer_rows;
|
||||
let goal_display_column = selection
|
||||
.head()
|
||||
.to_display_point(&display_map, Bias::Left)
|
||||
.column();
|
||||
let goal_display_column = selection.head().to_display_point(&display_map).column();
|
||||
|
||||
// Accumulate contiguous regions of rows that we want to delete.
|
||||
while let Some(next_selection) = selections.peek() {
|
||||
@ -1170,16 +1185,13 @@ impl Editor {
|
||||
cursor_buffer_row = rows.start.saturating_sub(1);
|
||||
}
|
||||
|
||||
let mut cursor = Point::new(cursor_buffer_row - row_delta, 0)
|
||||
.to_display_point(&display_map, Bias::Left);
|
||||
let mut cursor =
|
||||
Point::new(cursor_buffer_row - row_delta, 0).to_display_point(&display_map);
|
||||
*cursor.column_mut() =
|
||||
cmp::min(goal_display_column, display_map.line_len(cursor.row()));
|
||||
row_delta += rows.len() as u32;
|
||||
|
||||
new_cursors.push((
|
||||
selection.id,
|
||||
cursor.to_buffer_point(&display_map, Bias::Left),
|
||||
));
|
||||
new_cursors.push((selection.id, cursor.to_point(&display_map)));
|
||||
edit_ranges.push(edit_start..edit_end);
|
||||
}
|
||||
|
||||
@ -1566,15 +1578,15 @@ impl Editor {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
|
||||
for selection in &mut selections {
|
||||
let start = selection.start.to_display_point(&display_map, Bias::Left);
|
||||
let end = selection.end.to_display_point(&display_map, Bias::Left);
|
||||
let start = selection.start.to_display_point(&display_map);
|
||||
let end = selection.end.to_display_point(&display_map);
|
||||
|
||||
if start != end {
|
||||
selection.end = selection.start.clone();
|
||||
} else {
|
||||
let cursor = movement::left(&display_map, start)
|
||||
.unwrap()
|
||||
.to_buffer_point(&display_map, Bias::Left);
|
||||
.to_point(&display_map);
|
||||
selection.start = cursor.clone();
|
||||
selection.end = cursor;
|
||||
}
|
||||
@ -1588,10 +1600,10 @@ impl Editor {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
|
||||
for selection in &mut selections {
|
||||
let head = selection.head().to_display_point(&display_map, Bias::Left);
|
||||
let head = selection.head().to_display_point(&display_map);
|
||||
let cursor = movement::left(&display_map, head)
|
||||
.unwrap()
|
||||
.to_buffer_point(&display_map, Bias::Left);
|
||||
.to_point(&display_map);
|
||||
selection.set_head(cursor);
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
@ -1602,15 +1614,15 @@ impl Editor {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
|
||||
for selection in &mut selections {
|
||||
let start = selection.start.to_display_point(&display_map, Bias::Left);
|
||||
let end = selection.end.to_display_point(&display_map, Bias::Left);
|
||||
let start = selection.start.to_display_point(&display_map);
|
||||
let end = selection.end.to_display_point(&display_map);
|
||||
|
||||
if start != end {
|
||||
selection.start = selection.end.clone();
|
||||
} else {
|
||||
let cursor = movement::right(&display_map, end)
|
||||
.unwrap()
|
||||
.to_buffer_point(&display_map, Bias::Right);
|
||||
.to_point(&display_map);
|
||||
selection.start = cursor;
|
||||
selection.end = cursor;
|
||||
}
|
||||
@ -1624,10 +1636,10 @@ impl Editor {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
|
||||
for selection in &mut selections {
|
||||
let head = selection.head().to_display_point(&display_map, Bias::Left);
|
||||
let head = selection.head().to_display_point(&display_map);
|
||||
let cursor = movement::right(&display_map, head)
|
||||
.unwrap()
|
||||
.to_buffer_point(&display_map, Bias::Right);
|
||||
.to_point(&display_map);
|
||||
selection.set_head(cursor);
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
@ -1643,14 +1655,14 @@ impl Editor {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
|
||||
for selection in &mut selections {
|
||||
let start = selection.start.to_display_point(&display_map, Bias::Left);
|
||||
let end = selection.end.to_display_point(&display_map, Bias::Left);
|
||||
let start = selection.start.to_display_point(&display_map);
|
||||
let end = selection.end.to_display_point(&display_map);
|
||||
if start != end {
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
|
||||
let (start, goal) = movement::up(&display_map, start, selection.goal).unwrap();
|
||||
let cursor = start.to_buffer_point(&display_map, Bias::Left);
|
||||
let cursor = start.to_point(&display_map);
|
||||
selection.start = cursor;
|
||||
selection.end = cursor;
|
||||
selection.goal = goal;
|
||||
@ -1663,9 +1675,9 @@ impl Editor {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
|
||||
for selection in &mut selections {
|
||||
let head = selection.head().to_display_point(&display_map, Bias::Left);
|
||||
let head = selection.head().to_display_point(&display_map);
|
||||
let (head, goal) = movement::up(&display_map, head, selection.goal).unwrap();
|
||||
let cursor = head.to_buffer_point(&display_map, Bias::Left);
|
||||
let cursor = head.to_point(&display_map);
|
||||
selection.set_head(cursor);
|
||||
selection.goal = goal;
|
||||
}
|
||||
@ -1681,14 +1693,14 @@ impl Editor {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
|
||||
for selection in &mut selections {
|
||||
let start = selection.start.to_display_point(&display_map, Bias::Left);
|
||||
let end = selection.end.to_display_point(&display_map, Bias::Left);
|
||||
let start = selection.start.to_display_point(&display_map);
|
||||
let end = selection.end.to_display_point(&display_map);
|
||||
if start != end {
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
|
||||
let (start, goal) = movement::down(&display_map, end, selection.goal).unwrap();
|
||||
let cursor = start.to_buffer_point(&display_map, Bias::Right);
|
||||
let cursor = start.to_point(&display_map);
|
||||
selection.start = cursor;
|
||||
selection.end = cursor;
|
||||
selection.goal = goal;
|
||||
@ -1701,9 +1713,9 @@ impl Editor {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
|
||||
for selection in &mut selections {
|
||||
let head = selection.head().to_display_point(&display_map, Bias::Left);
|
||||
let head = selection.head().to_display_point(&display_map);
|
||||
let (head, goal) = movement::down(&display_map, head, selection.goal).unwrap();
|
||||
let cursor = head.to_buffer_point(&display_map, Bias::Right);
|
||||
let cursor = head.to_point(&display_map);
|
||||
selection.set_head(cursor);
|
||||
selection.goal = goal;
|
||||
}
|
||||
@ -1718,9 +1730,9 @@ impl Editor {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
|
||||
for selection in &mut selections {
|
||||
let head = selection.head().to_display_point(&display_map, Bias::Left);
|
||||
let head = selection.head().to_display_point(&display_map);
|
||||
let new_head = movement::prev_word_boundary(&display_map, head).unwrap();
|
||||
let cursor = new_head.to_buffer_point(&display_map, Bias::Left);
|
||||
let cursor = new_head.to_point(&display_map);
|
||||
selection.start = cursor.clone();
|
||||
selection.end = cursor;
|
||||
selection.reversed = false;
|
||||
@ -1737,9 +1749,9 @@ impl Editor {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
|
||||
for selection in &mut selections {
|
||||
let head = selection.head().to_display_point(&display_map, Bias::Left);
|
||||
let head = selection.head().to_display_point(&display_map);
|
||||
let new_head = movement::prev_word_boundary(&display_map, head).unwrap();
|
||||
let cursor = new_head.to_buffer_point(&display_map, Bias::Left);
|
||||
let cursor = new_head.to_point(&display_map);
|
||||
selection.set_head(cursor);
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
@ -1756,9 +1768,9 @@ impl Editor {
|
||||
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
|
||||
for selection in &mut selections {
|
||||
if selection.is_empty() {
|
||||
let head = selection.head().to_display_point(&display_map, Bias::Left);
|
||||
let head = selection.head().to_display_point(&display_map);
|
||||
let new_head = movement::prev_word_boundary(&display_map, head).unwrap();
|
||||
let cursor = new_head.to_buffer_point(&display_map, Bias::Right);
|
||||
let cursor = new_head.to_point(&display_map);
|
||||
selection.set_head(cursor);
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
@ -1776,9 +1788,9 @@ impl Editor {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
|
||||
for selection in &mut selections {
|
||||
let head = selection.head().to_display_point(&display_map, Bias::Left);
|
||||
let head = selection.head().to_display_point(&display_map);
|
||||
let new_head = movement::next_word_boundary(&display_map, head).unwrap();
|
||||
let cursor = new_head.to_buffer_point(&display_map, Bias::Left);
|
||||
let cursor = new_head.to_point(&display_map);
|
||||
selection.start = cursor;
|
||||
selection.end = cursor;
|
||||
selection.reversed = false;
|
||||
@ -1795,9 +1807,9 @@ impl Editor {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
|
||||
for selection in &mut selections {
|
||||
let head = selection.head().to_display_point(&display_map, Bias::Left);
|
||||
let head = selection.head().to_display_point(&display_map);
|
||||
let new_head = movement::next_word_boundary(&display_map, head).unwrap();
|
||||
let cursor = new_head.to_buffer_point(&display_map, Bias::Left);
|
||||
let cursor = new_head.to_point(&display_map);
|
||||
selection.set_head(cursor);
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
@ -1814,9 +1826,9 @@ impl Editor {
|
||||
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
|
||||
for selection in &mut selections {
|
||||
if selection.is_empty() {
|
||||
let head = selection.head().to_display_point(&display_map, Bias::Left);
|
||||
let head = selection.head().to_display_point(&display_map);
|
||||
let new_head = movement::next_word_boundary(&display_map, head).unwrap();
|
||||
let cursor = new_head.to_buffer_point(&display_map, Bias::Right);
|
||||
let cursor = new_head.to_point(&display_map);
|
||||
selection.set_head(cursor);
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
@ -1834,9 +1846,9 @@ impl Editor {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
|
||||
for selection in &mut selections {
|
||||
let head = selection.head().to_display_point(&display_map, Bias::Left);
|
||||
let head = selection.head().to_display_point(&display_map);
|
||||
let new_head = movement::line_beginning(&display_map, head, true).unwrap();
|
||||
let cursor = new_head.to_buffer_point(&display_map, Bias::Left);
|
||||
let cursor = new_head.to_point(&display_map);
|
||||
selection.start = cursor;
|
||||
selection.end = cursor;
|
||||
selection.reversed = false;
|
||||
@ -1853,9 +1865,9 @@ impl Editor {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
|
||||
for selection in &mut selections {
|
||||
let head = selection.head().to_display_point(&display_map, Bias::Left);
|
||||
let head = selection.head().to_display_point(&display_map);
|
||||
let new_head = movement::line_beginning(&display_map, head, *toggle_indent).unwrap();
|
||||
selection.set_head(new_head.to_buffer_point(&display_map, Bias::Left));
|
||||
selection.set_head(new_head.to_point(&display_map));
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
self.update_selections(selections, true, cx);
|
||||
@ -1877,9 +1889,9 @@ impl Editor {
|
||||
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
|
||||
{
|
||||
for selection in &mut selections {
|
||||
let head = selection.head().to_display_point(&display_map, Bias::Left);
|
||||
let head = selection.head().to_display_point(&display_map);
|
||||
let new_head = movement::line_end(&display_map, head).unwrap();
|
||||
let anchor = new_head.to_buffer_point(&display_map, Bias::Left);
|
||||
let anchor = new_head.to_point(&display_map);
|
||||
selection.start = anchor.clone();
|
||||
selection.end = anchor;
|
||||
selection.reversed = false;
|
||||
@ -1893,9 +1905,9 @@ impl Editor {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
|
||||
for selection in &mut selections {
|
||||
let head = selection.head().to_display_point(&display_map, Bias::Left);
|
||||
let head = selection.head().to_display_point(&display_map);
|
||||
let new_head = movement::line_end(&display_map, head).unwrap();
|
||||
selection.set_head(new_head.to_buffer_point(&display_map, Bias::Left));
|
||||
selection.set_head(new_head.to_point(&display_map));
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
self.update_selections(selections, true, cx);
|
||||
@ -2205,6 +2217,197 @@ impl Editor {
|
||||
self.update_selections(selections, true, cx);
|
||||
}
|
||||
|
||||
pub fn show_next_diagnostic(&mut self, _: &ShowNextDiagnostic, cx: &mut ViewContext<Self>) {
|
||||
let selection = self.newest_selection::<usize>(cx);
|
||||
let buffer = self.buffer.read(cx.as_ref());
|
||||
let active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
|
||||
active_diagnostics
|
||||
.primary_range
|
||||
.to_offset(buffer)
|
||||
.to_inclusive()
|
||||
});
|
||||
let mut search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
|
||||
if active_primary_range.contains(&selection.head()) {
|
||||
*active_primary_range.end()
|
||||
} else {
|
||||
selection.head()
|
||||
}
|
||||
} else {
|
||||
selection.head()
|
||||
};
|
||||
|
||||
loop {
|
||||
let next_group = buffer
|
||||
.diagnostics_in_range::<_, usize>(search_start..buffer.len())
|
||||
.find_map(|(range, diagnostic)| {
|
||||
if diagnostic.is_primary
|
||||
&& !range.is_empty()
|
||||
&& Some(range.end) != active_primary_range.as_ref().map(|r| *r.end())
|
||||
{
|
||||
Some((range, diagnostic.group_id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
if let Some((primary_range, group_id)) = next_group {
|
||||
self.activate_diagnostics(group_id, cx);
|
||||
self.update_selections(
|
||||
vec![Selection {
|
||||
id: selection.id,
|
||||
start: primary_range.start,
|
||||
end: primary_range.start,
|
||||
reversed: false,
|
||||
goal: SelectionGoal::None,
|
||||
}],
|
||||
true,
|
||||
cx,
|
||||
);
|
||||
break;
|
||||
} else if search_start == 0 {
|
||||
break;
|
||||
} else {
|
||||
// Cycle around to the start of the buffer.
|
||||
search_start = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_active_diagnostics(&mut self, cx: &mut ViewContext<Editor>) {
|
||||
if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
|
||||
let buffer = self.buffer.read(cx);
|
||||
let primary_range_start = active_diagnostics.primary_range.start.to_offset(buffer);
|
||||
let is_valid = buffer
|
||||
.diagnostics_in_range::<_, usize>(active_diagnostics.primary_range.clone())
|
||||
.any(|(range, diagnostic)| {
|
||||
diagnostic.is_primary
|
||||
&& !range.is_empty()
|
||||
&& range.start == primary_range_start
|
||||
&& diagnostic.message == active_diagnostics.primary_message
|
||||
});
|
||||
|
||||
if is_valid != active_diagnostics.is_valid {
|
||||
active_diagnostics.is_valid = is_valid;
|
||||
let mut new_styles = HashMap::new();
|
||||
for (block_id, diagnostic) in &active_diagnostics.blocks {
|
||||
let severity = diagnostic.severity;
|
||||
let message_len = diagnostic.message.len();
|
||||
new_styles.insert(
|
||||
*block_id,
|
||||
(
|
||||
Some({
|
||||
let build_settings = self.build_settings.clone();
|
||||
move |cx: &AppContext| {
|
||||
let settings = build_settings.borrow()(cx);
|
||||
vec![(
|
||||
message_len,
|
||||
diagnostic_style(severity, is_valid, &settings.style)
|
||||
.text
|
||||
.into(),
|
||||
)]
|
||||
}
|
||||
}),
|
||||
Some({
|
||||
let build_settings = self.build_settings.clone();
|
||||
move |cx: &AppContext| {
|
||||
let settings = build_settings.borrow()(cx);
|
||||
diagnostic_style(severity, is_valid, &settings.style).block
|
||||
}
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
self.display_map
|
||||
.update(cx, |display_map, _| display_map.restyle_blocks(new_styles));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn activate_diagnostics(&mut self, group_id: usize, cx: &mut ViewContext<Self>) {
|
||||
self.dismiss_diagnostics(cx);
|
||||
self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
|
||||
let buffer = self.buffer.read(cx);
|
||||
|
||||
let mut primary_range = None;
|
||||
let mut primary_message = None;
|
||||
let mut group_end = Point::zero();
|
||||
let diagnostic_group = buffer
|
||||
.diagnostic_group::<Point>(group_id)
|
||||
.map(|(range, diagnostic)| {
|
||||
if range.end > group_end {
|
||||
group_end = range.end;
|
||||
}
|
||||
if diagnostic.is_primary {
|
||||
primary_range = Some(range.clone());
|
||||
primary_message = Some(diagnostic.message.clone());
|
||||
}
|
||||
(range, diagnostic.clone())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let primary_range = primary_range.unwrap();
|
||||
let primary_message = primary_message.unwrap();
|
||||
let primary_range =
|
||||
buffer.anchor_after(primary_range.start)..buffer.anchor_before(primary_range.end);
|
||||
|
||||
let blocks = display_map
|
||||
.insert_blocks(
|
||||
diagnostic_group.iter().map(|(range, diagnostic)| {
|
||||
let build_settings = self.build_settings.clone();
|
||||
let message_len = diagnostic.message.len();
|
||||
let severity = diagnostic.severity;
|
||||
BlockProperties {
|
||||
position: range.start,
|
||||
text: diagnostic.message.as_str(),
|
||||
build_runs: Some(Arc::new({
|
||||
let build_settings = build_settings.clone();
|
||||
move |cx| {
|
||||
let settings = build_settings.borrow()(cx);
|
||||
vec![(
|
||||
message_len,
|
||||
diagnostic_style(severity, true, &settings.style)
|
||||
.text
|
||||
.into(),
|
||||
)]
|
||||
}
|
||||
})),
|
||||
build_style: Some(Arc::new({
|
||||
let build_settings = build_settings.clone();
|
||||
move |cx| {
|
||||
let settings = build_settings.borrow()(cx);
|
||||
diagnostic_style(severity, true, &settings.style).block
|
||||
}
|
||||
})),
|
||||
disposition: BlockDisposition::Below,
|
||||
}
|
||||
}),
|
||||
cx,
|
||||
)
|
||||
.into_iter()
|
||||
.zip(
|
||||
diagnostic_group
|
||||
.into_iter()
|
||||
.map(|(_, diagnostic)| diagnostic),
|
||||
)
|
||||
.collect();
|
||||
|
||||
Some(ActiveDiagnosticGroup {
|
||||
primary_range,
|
||||
primary_message,
|
||||
blocks,
|
||||
is_valid: true,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn dismiss_diagnostics(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
|
||||
self.display_map.update(cx, |display_map, cx| {
|
||||
display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn build_columnar_selection(
|
||||
&mut self,
|
||||
display_map: &DisplayMapSnapshot,
|
||||
@ -2219,8 +2422,8 @@ impl Editor {
|
||||
let end = DisplayPoint::new(row, cmp::min(columns.end, line_len));
|
||||
Some(Selection {
|
||||
id: post_inc(&mut self.next_selection_id),
|
||||
start: start.to_buffer_point(display_map, Bias::Left),
|
||||
end: end.to_buffer_point(display_map, Bias::Left),
|
||||
start: start.to_point(display_map),
|
||||
end: end.to_point(display_map),
|
||||
reversed,
|
||||
goal: SelectionGoal::ColumnRange {
|
||||
start: columns.start,
|
||||
@ -2254,17 +2457,16 @@ impl Editor {
|
||||
) -> impl 'a + Iterator<Item = Range<DisplayPoint>> {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let buffer = self.buffer.read(cx);
|
||||
let selections = buffer
|
||||
.selection_set(set_id)
|
||||
.unwrap()
|
||||
let selections = self
|
||||
.selection_set(cx)
|
||||
.selections::<Point, _>(buffer)
|
||||
.collect::<Vec<_>>();
|
||||
let start = range.start.to_buffer_point(&display_map, Bias::Left);
|
||||
let start = range.start.to_point(&display_map);
|
||||
let start_index = self.selection_insertion_index(&selections, start);
|
||||
let pending_selection = if set_id.replica_id == self.buffer.read(cx).replica_id() {
|
||||
self.pending_selection.as_ref().and_then(|pending| {
|
||||
let mut selection_start = pending.start.to_display_point(&display_map, Bias::Left);
|
||||
let mut selection_end = pending.end.to_display_point(&display_map, Bias::Left);
|
||||
let mut selection_start = pending.start.to_display_point(&display_map);
|
||||
let mut selection_end = pending.end.to_display_point(&display_map);
|
||||
if pending.reversed {
|
||||
mem::swap(&mut selection_start, &mut selection_end);
|
||||
}
|
||||
@ -2303,18 +2505,8 @@ impl Editor {
|
||||
D: 'a + TextDimension<'a> + Ord,
|
||||
{
|
||||
let buffer = self.buffer.read(cx);
|
||||
let mut selections = buffer
|
||||
.selection_set(self.selection_set_id)
|
||||
.unwrap()
|
||||
.selections::<D, _>(buffer)
|
||||
.peekable();
|
||||
let mut pending_selection = self.pending_selection.clone().map(|selection| Selection {
|
||||
id: selection.id,
|
||||
start: selection.start.summary::<D, _>(buffer),
|
||||
end: selection.end.summary::<D, _>(buffer),
|
||||
reversed: selection.reversed,
|
||||
goal: selection.goal,
|
||||
});
|
||||
let mut selections = self.selection_set(cx).selections::<D, _>(buffer).peekable();
|
||||
let mut pending_selection = self.pending_selection(cx);
|
||||
iter::from_fn(move || {
|
||||
if let Some(pending) = pending_selection.as_mut() {
|
||||
while let Some(next_selection) = selections.peek() {
|
||||
@ -2340,6 +2532,56 @@ impl Editor {
|
||||
})
|
||||
}
|
||||
|
||||
fn pending_selection<'a, D>(&self, cx: &'a AppContext) -> Option<Selection<D>>
|
||||
where
|
||||
D: 'a + TextDimension<'a>,
|
||||
{
|
||||
let buffer = self.buffer.read(cx);
|
||||
self.pending_selection.as_ref().map(|selection| Selection {
|
||||
id: selection.id,
|
||||
start: selection.start.summary::<D, _>(buffer),
|
||||
end: selection.end.summary::<D, _>(buffer),
|
||||
reversed: selection.reversed,
|
||||
goal: selection.goal,
|
||||
})
|
||||
}
|
||||
|
||||
fn selection_count<'a>(&self, cx: &'a AppContext) -> usize {
|
||||
let mut selection_count = self.selection_set(cx).len();
|
||||
if self.pending_selection.is_some() {
|
||||
selection_count += 1;
|
||||
}
|
||||
selection_count
|
||||
}
|
||||
|
||||
pub fn oldest_selection<'a, T>(&self, cx: &'a AppContext) -> Selection<T>
|
||||
where
|
||||
T: 'a + TextDimension<'a>,
|
||||
{
|
||||
let buffer = self.buffer.read(cx);
|
||||
self.selection_set(cx)
|
||||
.oldest_selection(buffer)
|
||||
.or_else(|| self.pending_selection(cx))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn newest_selection<'a, T>(&self, cx: &'a AppContext) -> Selection<T>
|
||||
where
|
||||
T: 'a + TextDimension<'a>,
|
||||
{
|
||||
let buffer = self.buffer.read(cx);
|
||||
self.pending_selection(cx)
|
||||
.or_else(|| self.selection_set(cx).newest_selection(buffer))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn selection_set<'a>(&self, cx: &'a AppContext) -> &'a SelectionSet {
|
||||
self.buffer
|
||||
.read(cx)
|
||||
.selection_set(self.selection_set_id)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn update_selections<T>(
|
||||
&mut self,
|
||||
mut selections: Vec<Selection<T>>,
|
||||
@ -2438,7 +2680,7 @@ impl Editor {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
for selection in selections {
|
||||
let range = selection.display_range(&display_map).sorted();
|
||||
let buffer_start_row = range.start.to_buffer_point(&display_map, Bias::Left).row;
|
||||
let buffer_start_row = range.start.to_point(&display_map).row;
|
||||
|
||||
for row in (0..=range.end.row()).rev() {
|
||||
if self.is_line_foldable(&display_map, row) && !display_map.is_line_folded(row) {
|
||||
@ -2464,8 +2706,8 @@ impl Editor {
|
||||
.iter()
|
||||
.map(|s| {
|
||||
let range = s.display_range(&display_map).sorted();
|
||||
let mut start = range.start.to_buffer_point(&display_map, Bias::Left);
|
||||
let mut end = range.end.to_buffer_point(&display_map, Bias::Left);
|
||||
let mut start = range.start.to_point(&display_map);
|
||||
let mut end = range.end.to_point(&display_map);
|
||||
start.column = 0;
|
||||
end.column = buffer.line_len(end.row);
|
||||
start..end
|
||||
@ -2513,8 +2755,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
let end = end.unwrap_or(max_point);
|
||||
return start.to_buffer_point(display_map, Bias::Left)
|
||||
..end.to_buffer_point(display_map, Bias::Left);
|
||||
return start.to_point(display_map)..end.to_point(display_map);
|
||||
}
|
||||
|
||||
pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext<Self>) {
|
||||
@ -2624,6 +2865,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
fn on_buffer_changed(&mut self, _: ModelHandle<Buffer>, cx: &mut ViewContext<Self>) {
|
||||
self.refresh_active_diagnostics(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@ -2666,16 +2908,17 @@ impl Snapshot {
|
||||
self.display_snapshot.buffer_row_count()
|
||||
}
|
||||
|
||||
pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
|
||||
self.display_snapshot.buffer_rows(start_row)
|
||||
pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: &'a AppContext) -> BufferRows<'a> {
|
||||
self.display_snapshot.buffer_rows(start_row, Some(cx))
|
||||
}
|
||||
|
||||
pub fn highlighted_chunks_for_rows(
|
||||
&mut self,
|
||||
pub fn chunks<'a>(
|
||||
&'a self,
|
||||
display_rows: Range<u32>,
|
||||
) -> display_map::HighlightedChunks {
|
||||
self.display_snapshot
|
||||
.highlighted_chunks_for_rows(display_rows)
|
||||
theme: Option<&'a SyntaxTheme>,
|
||||
cx: &'a AppContext,
|
||||
) -> display_map::Chunks<'a> {
|
||||
self.display_snapshot.chunks(display_rows, theme, cx)
|
||||
}
|
||||
|
||||
pub fn scroll_position(&self) -> Vector2F {
|
||||
@ -2743,10 +2986,14 @@ impl EditorSettings {
|
||||
selection: Default::default(),
|
||||
guest_selections: Default::default(),
|
||||
syntax: Default::default(),
|
||||
error_underline: Default::default(),
|
||||
warning_underline: Default::default(),
|
||||
information_underline: Default::default(),
|
||||
hint_underline: Default::default(),
|
||||
error_diagnostic: Default::default(),
|
||||
invalid_error_diagnostic: Default::default(),
|
||||
warning_diagnostic: Default::default(),
|
||||
invalid_warning_diagnostic: Default::default(),
|
||||
information_diagnostic: Default::default(),
|
||||
invalid_information_diagnostic: Default::default(),
|
||||
hint_diagnostic: Default::default(),
|
||||
invalid_hint_diagnostic: Default::default(),
|
||||
}
|
||||
},
|
||||
}
|
||||
@ -2758,9 +3005,7 @@ fn compute_scroll_position(
|
||||
mut scroll_position: Vector2F,
|
||||
scroll_top_anchor: &Anchor,
|
||||
) -> Vector2F {
|
||||
let scroll_top = scroll_top_anchor
|
||||
.to_display_point(snapshot, Bias::Left)
|
||||
.row() as f32;
|
||||
let scroll_top = scroll_top_anchor.to_display_point(snapshot).row() as f32;
|
||||
scroll_position.set_y(scroll_top + scroll_position.y());
|
||||
scroll_position
|
||||
}
|
||||
@ -2838,8 +3083,8 @@ impl View for Editor {
|
||||
|
||||
impl SelectionExt for Selection<Point> {
|
||||
fn display_range(&self, map: &DisplayMapSnapshot) -> Range<DisplayPoint> {
|
||||
let start = self.start.to_display_point(map, Bias::Left);
|
||||
let end = self.end.to_display_point(map, Bias::Left);
|
||||
let start = self.start.to_display_point(map);
|
||||
let end = self.end.to_display_point(map);
|
||||
if self.reversed {
|
||||
end..start
|
||||
} else {
|
||||
@ -2852,8 +3097,8 @@ impl SelectionExt for Selection<Point> {
|
||||
include_end_if_at_line_start: bool,
|
||||
map: &DisplayMapSnapshot,
|
||||
) -> SpannedRows {
|
||||
let display_start = self.start.to_display_point(map, Bias::Left);
|
||||
let mut display_end = self.end.to_display_point(map, Bias::Right);
|
||||
let display_start = self.start.to_display_point(map);
|
||||
let mut display_end = self.end.to_display_point(map);
|
||||
if !include_end_if_at_line_start
|
||||
&& display_end.row() != map.max_point().row()
|
||||
&& display_start.row() != display_end.row()
|
||||
@ -2872,6 +3117,24 @@ impl SelectionExt for Selection<Point> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn diagnostic_style(
|
||||
severity: DiagnosticSeverity,
|
||||
valid: bool,
|
||||
style: &EditorStyle,
|
||||
) -> DiagnosticStyle {
|
||||
match (severity, valid) {
|
||||
(DiagnosticSeverity::ERROR, true) => style.error_diagnostic,
|
||||
(DiagnosticSeverity::ERROR, false) => style.invalid_error_diagnostic,
|
||||
(DiagnosticSeverity::WARNING, true) => style.warning_diagnostic,
|
||||
(DiagnosticSeverity::WARNING, false) => style.invalid_warning_diagnostic,
|
||||
(DiagnosticSeverity::INFORMATION, true) => style.information_diagnostic,
|
||||
(DiagnosticSeverity::INFORMATION, false) => style.invalid_information_diagnostic,
|
||||
(DiagnosticSeverity::HINT, true) => style.hint_diagnostic,
|
||||
(DiagnosticSeverity::HINT, false) => style.invalid_hint_diagnostic,
|
||||
_ => Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -33,11 +33,17 @@ pub fn up(
|
||||
map.column_to_chars(point.row(), point.column())
|
||||
};
|
||||
|
||||
if point.row() > 0 {
|
||||
*point.row_mut() -= 1;
|
||||
*point.column_mut() = map.column_from_chars(point.row(), goal_column);
|
||||
} else {
|
||||
point = DisplayPoint::new(0, 0);
|
||||
loop {
|
||||
if point.row() > 0 {
|
||||
*point.row_mut() -= 1;
|
||||
*point.column_mut() = map.column_from_chars(point.row(), goal_column);
|
||||
if !map.is_block_line(point.row()) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
point = DisplayPoint::new(0, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let clip_bias = if point.column() == map.line_len(point.row()) {
|
||||
@ -64,11 +70,17 @@ pub fn down(
|
||||
map.column_to_chars(point.row(), point.column())
|
||||
};
|
||||
|
||||
if point.row() < max_point.row() {
|
||||
*point.row_mut() += 1;
|
||||
*point.column_mut() = map.column_from_chars(point.row(), goal_column);
|
||||
} else {
|
||||
point = max_point;
|
||||
loop {
|
||||
if point.row() < max_point.row() {
|
||||
*point.row_mut() += 1;
|
||||
*point.column_mut() = map.column_from_chars(point.row(), goal_column);
|
||||
if !map.is_block_line(point.row()) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
point = max_point;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let clip_bias = if point.column() == map.line_len(point.row()) {
|
||||
|
@ -2,6 +2,13 @@ use gpui::{Entity, ModelHandle};
|
||||
use smol::channel;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
#[cfg(test)]
|
||||
#[ctor::ctor]
|
||||
fn init_logger() {
|
||||
// std::env::set_var("RUST_LOG", "info");
|
||||
env_logger::init();
|
||||
}
|
||||
|
||||
pub fn sample_text(rows: usize, cols: usize) -> String {
|
||||
let mut text = String::new();
|
||||
for row in 0..rows {
|
||||
|
@ -30,7 +30,7 @@ pub struct TextStyle {
|
||||
pub underline: Option<Color>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct HighlightStyle {
|
||||
pub color: Color,
|
||||
pub font_properties: Properties,
|
||||
|
@ -1,10 +1,4 @@
|
||||
use crate::{geometry::vector::vec2f, keymap::Keystroke, platform::Event};
|
||||
use cocoa::appkit::{
|
||||
NSDeleteFunctionKey as DELETE_KEY, NSDownArrowFunctionKey as ARROW_DOWN_KEY,
|
||||
NSLeftArrowFunctionKey as ARROW_LEFT_KEY, NSPageDownFunctionKey as PAGE_DOWN_KEY,
|
||||
NSPageUpFunctionKey as PAGE_UP_KEY, NSRightArrowFunctionKey as ARROW_RIGHT_KEY,
|
||||
NSUpArrowFunctionKey as ARROW_UP_KEY,
|
||||
};
|
||||
use cocoa::{
|
||||
appkit::{NSEvent, NSEventModifierFlags, NSEventType},
|
||||
base::{id, nil, YES},
|
||||
@ -12,11 +6,6 @@ use cocoa::{
|
||||
};
|
||||
use std::{ffi::CStr, os::raw::c_char};
|
||||
|
||||
const BACKSPACE_KEY: u16 = 0x7f;
|
||||
const ENTER_KEY: u16 = 0x0d;
|
||||
const ESCAPE_KEY: u16 = 0x1b;
|
||||
const TAB_KEY: u16 = 0x09;
|
||||
|
||||
impl Event {
|
||||
pub unsafe fn from_native(native_event: id, window_height: Option<f32>) -> Option<Self> {
|
||||
let event_type = native_event.eventType();
|
||||
@ -39,18 +28,39 @@ impl Event {
|
||||
.unwrap();
|
||||
|
||||
let unmodified_chars = if let Some(first_char) = unmodified_chars.chars().next() {
|
||||
use cocoa::appkit::*;
|
||||
const BACKSPACE_KEY: u16 = 0x7f;
|
||||
const ENTER_KEY: u16 = 0x0d;
|
||||
const ESCAPE_KEY: u16 = 0x1b;
|
||||
const TAB_KEY: u16 = 0x09;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
match first_char as u16 {
|
||||
ARROW_UP_KEY => "up",
|
||||
ARROW_DOWN_KEY => "down",
|
||||
ARROW_LEFT_KEY => "left",
|
||||
ARROW_RIGHT_KEY => "right",
|
||||
PAGE_UP_KEY => "pageup",
|
||||
PAGE_DOWN_KEY => "pagedown",
|
||||
BACKSPACE_KEY => "backspace",
|
||||
ENTER_KEY => "enter",
|
||||
DELETE_KEY => "delete",
|
||||
ESCAPE_KEY => "escape",
|
||||
TAB_KEY => "tab",
|
||||
|
||||
NSUpArrowFunctionKey => "up",
|
||||
NSDownArrowFunctionKey => "down",
|
||||
NSLeftArrowFunctionKey => "left",
|
||||
NSRightArrowFunctionKey => "right",
|
||||
NSPageUpFunctionKey => "pageup",
|
||||
NSPageDownFunctionKey => "pagedown",
|
||||
NSDeleteFunctionKey => "delete",
|
||||
NSF1FunctionKey => "f1",
|
||||
NSF2FunctionKey => "f2",
|
||||
NSF3FunctionKey => "f3",
|
||||
NSF4FunctionKey => "f4",
|
||||
NSF5FunctionKey => "f5",
|
||||
NSF6FunctionKey => "f6",
|
||||
NSF7FunctionKey => "f7",
|
||||
NSF8FunctionKey => "f8",
|
||||
NSF9FunctionKey => "f9",
|
||||
NSF10FunctionKey => "f10",
|
||||
NSF11FunctionKey => "f11",
|
||||
NSF12FunctionKey => "f12",
|
||||
|
||||
_ => unmodified_chars,
|
||||
}
|
||||
} else {
|
||||
|
@ -12,7 +12,7 @@ use anyhow::{anyhow, Result};
|
||||
pub use buffer::{Buffer as TextBuffer, Operation as _, *};
|
||||
use clock::ReplicaId;
|
||||
use futures::FutureExt as _;
|
||||
use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task};
|
||||
use gpui::{fonts::HighlightStyle, AppContext, Entity, ModelContext, MutableAppContext, Task};
|
||||
use lazy_static::lazy_static;
|
||||
use lsp::LanguageServer;
|
||||
use parking_lot::Mutex;
|
||||
@ -34,6 +34,7 @@ use std::{
|
||||
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
|
||||
vec,
|
||||
};
|
||||
use theme::SyntaxTheme;
|
||||
use tree_sitter::{InputEdit, Parser, QueryCursor, Tree};
|
||||
use util::{post_inc, TryFutureExt as _};
|
||||
|
||||
@ -78,13 +79,14 @@ pub struct Snapshot {
|
||||
diagnostics: AnchorRangeMultimap<Diagnostic>,
|
||||
is_parsing: bool,
|
||||
language: Option<Arc<Language>>,
|
||||
query_cursor: QueryCursorHandle,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Diagnostic {
|
||||
pub severity: DiagnosticSeverity,
|
||||
pub message: String,
|
||||
pub group_id: usize,
|
||||
pub is_primary: bool,
|
||||
}
|
||||
|
||||
struct LanguageServerState {
|
||||
@ -190,11 +192,13 @@ struct Highlights<'a> {
|
||||
next_capture: Option<(tree_sitter::QueryMatch<'a, 'a>, usize)>,
|
||||
stack: Vec<(usize, HighlightId)>,
|
||||
highlight_map: HighlightMap,
|
||||
theme: &'a SyntaxTheme,
|
||||
_query_cursor: QueryCursorHandle,
|
||||
}
|
||||
|
||||
pub struct HighlightedChunks<'a> {
|
||||
pub struct Chunks<'a> {
|
||||
range: Range<usize>,
|
||||
chunks: Chunks<'a>,
|
||||
chunks: rope::Chunks<'a>,
|
||||
diagnostic_endpoints: Peekable<vec::IntoIter<DiagnosticEndpoint>>,
|
||||
error_depth: usize,
|
||||
warning_depth: usize,
|
||||
@ -204,9 +208,9 @@ pub struct HighlightedChunks<'a> {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct HighlightedChunk<'a> {
|
||||
pub struct Chunk<'a> {
|
||||
pub text: &'a str,
|
||||
pub highlight_id: HighlightId,
|
||||
pub highlight_style: Option<HighlightStyle>,
|
||||
pub diagnostic: Option<DiagnosticSeverity>,
|
||||
}
|
||||
|
||||
@ -341,7 +345,6 @@ impl Buffer {
|
||||
diagnostics: self.diagnostics.clone(),
|
||||
is_parsing: self.parsing_in_background,
|
||||
language: self.language.clone(),
|
||||
query_cursor: QueryCursorHandle::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -438,7 +441,7 @@ impl Buffer {
|
||||
uri,
|
||||
Default::default(),
|
||||
snapshot.version as i32,
|
||||
snapshot.buffer_snapshot.text().into(),
|
||||
snapshot.buffer_snapshot.text().to_string(),
|
||||
),
|
||||
},
|
||||
)
|
||||
@ -699,6 +702,7 @@ impl Buffer {
|
||||
} else {
|
||||
self.content()
|
||||
};
|
||||
let abs_path = self.file.as_ref().and_then(|f| f.abs_path());
|
||||
|
||||
let empty_set = HashSet::new();
|
||||
let disk_based_sources = self
|
||||
@ -714,56 +718,82 @@ impl Buffer {
|
||||
.peekable();
|
||||
let mut last_edit_old_end = PointUtf16::zero();
|
||||
let mut last_edit_new_end = PointUtf16::zero();
|
||||
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,
|
||||
|
@ -141,6 +141,8 @@ pub fn serialize_diagnostics(map: &AnchorRangeMultimap<Diagnostic>) -> proto::Di
|
||||
DiagnosticSeverity::HINT => proto::diagnostic::Severity::Hint,
|
||||
_ => proto::diagnostic::Severity::None,
|
||||
} as i32,
|
||||
group_id: diagnostic.group_id as u64,
|
||||
is_primary: diagnostic.is_primary,
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
@ -308,6 +310,8 @@ pub fn deserialize_diagnostics(message: proto::DiagnosticSet) -> AnchorRangeMult
|
||||
proto::diagnostic::Severity::None => return None,
|
||||
},
|
||||
message: diagnostic.message,
|
||||
group_id: diagnostic.group_id as usize,
|
||||
is_primary: diagnostic.is_primary,
|
||||
},
|
||||
))
|
||||
}),
|
||||
|
@ -482,14 +482,18 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) {
|
||||
Point::new(3, 9)..Point::new(3, 11),
|
||||
&Diagnostic {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
message: "undefined variable 'BB'".to_string()
|
||||
message: "undefined variable 'BB'".to_string(),
|
||||
group_id: 1,
|
||||
is_primary: true,
|
||||
},
|
||||
),
|
||||
(
|
||||
Point::new(4, 9)..Point::new(4, 12),
|
||||
&Diagnostic {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
message: "undefined variable 'CCC'".to_string()
|
||||
message: "undefined variable 'CCC'".to_string(),
|
||||
group_id: 2,
|
||||
is_primary: true,
|
||||
}
|
||||
)
|
||||
]
|
||||
@ -545,14 +549,18 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) {
|
||||
Point::new(2, 9)..Point::new(2, 12),
|
||||
&Diagnostic {
|
||||
severity: DiagnosticSeverity::WARNING,
|
||||
message: "unreachable statement".to_string()
|
||||
message: "unreachable statement".to_string(),
|
||||
group_id: 1,
|
||||
is_primary: true,
|
||||
}
|
||||
),
|
||||
(
|
||||
Point::new(2, 9)..Point::new(2, 10),
|
||||
&Diagnostic {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
message: "undefined variable 'A'".to_string()
|
||||
message: "undefined variable 'A'".to_string(),
|
||||
group_id: 0,
|
||||
is_primary: true,
|
||||
},
|
||||
)
|
||||
]
|
||||
@ -620,14 +628,18 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) {
|
||||
Point::new(2, 21)..Point::new(2, 22),
|
||||
&Diagnostic {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
message: "undefined variable 'A'".to_string()
|
||||
message: "undefined variable 'A'".to_string(),
|
||||
group_id: 0,
|
||||
is_primary: true,
|
||||
}
|
||||
),
|
||||
(
|
||||
Point::new(3, 9)..Point::new(3, 11),
|
||||
&Diagnostic {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
message: "undefined variable 'BB'".to_string()
|
||||
message: "undefined variable 'BB'".to_string(),
|
||||
group_id: 1,
|
||||
is_primary: true,
|
||||
},
|
||||
)
|
||||
]
|
||||
@ -694,12 +706,223 @@ async fn test_empty_diagnostic_ranges(mut cx: gpui::TestAppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
fn chunks_with_diagnostics<T: ToOffset>(
|
||||
#[gpui::test]
|
||||
async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) {
|
||||
cx.add_model(|cx| {
|
||||
let text = "
|
||||
fn foo(mut v: Vec<usize>) {
|
||||
for x in &v {
|
||||
v.push(1);
|
||||
}
|
||||
}
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let file = FakeFile::new("/example.rs");
|
||||
let mut buffer = Buffer::from_file(0, text, Box::new(file.clone()), cx);
|
||||
buffer.set_language(Some(Arc::new(rust_lang())), None, cx);
|
||||
let diagnostics = vec![
|
||||
lsp::Diagnostic {
|
||||
range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
|
||||
severity: Some(DiagnosticSeverity::WARNING),
|
||||
message: "error 1".to_string(),
|
||||
related_information: Some(vec![lsp::DiagnosticRelatedInformation {
|
||||
location: lsp::Location {
|
||||
uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
|
||||
range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
|
||||
},
|
||||
message: "error 1 hint 1".to_string(),
|
||||
}]),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::Diagnostic {
|
||||
range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
|
||||
severity: Some(DiagnosticSeverity::HINT),
|
||||
message: "error 1 hint 1".to_string(),
|
||||
related_information: Some(vec![lsp::DiagnosticRelatedInformation {
|
||||
location: lsp::Location {
|
||||
uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
|
||||
range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
|
||||
},
|
||||
message: "original diagnostic".to_string(),
|
||||
}]),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::Diagnostic {
|
||||
range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
|
||||
severity: Some(DiagnosticSeverity::ERROR),
|
||||
message: "error 2".to_string(),
|
||||
related_information: Some(vec![
|
||||
lsp::DiagnosticRelatedInformation {
|
||||
location: lsp::Location {
|
||||
uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
|
||||
range: lsp::Range::new(
|
||||
lsp::Position::new(1, 13),
|
||||
lsp::Position::new(1, 15),
|
||||
),
|
||||
},
|
||||
message: "error 2 hint 1".to_string(),
|
||||
},
|
||||
lsp::DiagnosticRelatedInformation {
|
||||
location: lsp::Location {
|
||||
uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
|
||||
range: lsp::Range::new(
|
||||
lsp::Position::new(1, 13),
|
||||
lsp::Position::new(1, 15),
|
||||
),
|
||||
},
|
||||
message: "error 2 hint 2".to_string(),
|
||||
},
|
||||
]),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::Diagnostic {
|
||||
range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
|
||||
severity: Some(DiagnosticSeverity::HINT),
|
||||
message: "error 2 hint 1".to_string(),
|
||||
related_information: Some(vec![lsp::DiagnosticRelatedInformation {
|
||||
location: lsp::Location {
|
||||
uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
|
||||
range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
|
||||
},
|
||||
message: "original diagnostic".to_string(),
|
||||
}]),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::Diagnostic {
|
||||
range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
|
||||
severity: Some(DiagnosticSeverity::HINT),
|
||||
message: "error 2 hint 2".to_string(),
|
||||
related_information: Some(vec![lsp::DiagnosticRelatedInformation {
|
||||
location: lsp::Location {
|
||||
uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
|
||||
range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
|
||||
},
|
||||
message: "original diagnostic".to_string(),
|
||||
}]),
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
buffer.update_diagnostics(None, diagnostics, cx).unwrap();
|
||||
assert_eq!(
|
||||
buffer
|
||||
.diagnostics_in_range::<_, Point>(0..buffer.len())
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
(
|
||||
Point::new(1, 8)..Point::new(1, 9),
|
||||
&Diagnostic {
|
||||
severity: DiagnosticSeverity::WARNING,
|
||||
message: "error 1".to_string(),
|
||||
group_id: 0,
|
||||
is_primary: true,
|
||||
}
|
||||
),
|
||||
(
|
||||
Point::new(1, 8)..Point::new(1, 9),
|
||||
&Diagnostic {
|
||||
severity: DiagnosticSeverity::HINT,
|
||||
message: "error 1 hint 1".to_string(),
|
||||
group_id: 0,
|
||||
is_primary: false,
|
||||
}
|
||||
),
|
||||
(
|
||||
Point::new(1, 13)..Point::new(1, 15),
|
||||
&Diagnostic {
|
||||
severity: DiagnosticSeverity::HINT,
|
||||
message: "error 2 hint 1".to_string(),
|
||||
group_id: 1,
|
||||
is_primary: false,
|
||||
}
|
||||
),
|
||||
(
|
||||
Point::new(1, 13)..Point::new(1, 15),
|
||||
&Diagnostic {
|
||||
severity: DiagnosticSeverity::HINT,
|
||||
message: "error 2 hint 2".to_string(),
|
||||
group_id: 1,
|
||||
is_primary: false,
|
||||
}
|
||||
),
|
||||
(
|
||||
Point::new(2, 8)..Point::new(2, 17),
|
||||
&Diagnostic {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
message: "error 2".to_string(),
|
||||
group_id: 1,
|
||||
is_primary: true,
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
buffer.diagnostic_group(0).collect::<Vec<_>>(),
|
||||
&[
|
||||
(
|
||||
Point::new(1, 8)..Point::new(1, 9),
|
||||
&Diagnostic {
|
||||
severity: DiagnosticSeverity::WARNING,
|
||||
message: "error 1".to_string(),
|
||||
group_id: 0,
|
||||
is_primary: true,
|
||||
}
|
||||
),
|
||||
(
|
||||
Point::new(1, 8)..Point::new(1, 9),
|
||||
&Diagnostic {
|
||||
severity: DiagnosticSeverity::HINT,
|
||||
message: "error 1 hint 1".to_string(),
|
||||
group_id: 0,
|
||||
is_primary: false,
|
||||
}
|
||||
),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
buffer.diagnostic_group(1).collect::<Vec<_>>(),
|
||||
&[
|
||||
(
|
||||
Point::new(1, 13)..Point::new(1, 15),
|
||||
&Diagnostic {
|
||||
severity: DiagnosticSeverity::HINT,
|
||||
message: "error 2 hint 1".to_string(),
|
||||
group_id: 1,
|
||||
is_primary: false,
|
||||
}
|
||||
),
|
||||
(
|
||||
Point::new(1, 13)..Point::new(1, 15),
|
||||
&Diagnostic {
|
||||
severity: DiagnosticSeverity::HINT,
|
||||
message: "error 2 hint 2".to_string(),
|
||||
group_id: 1,
|
||||
is_primary: false,
|
||||
}
|
||||
),
|
||||
(
|
||||
Point::new(2, 8)..Point::new(2, 17),
|
||||
&Diagnostic {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
message: "error 2".to_string(),
|
||||
group_id: 1,
|
||||
is_primary: true,
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
buffer
|
||||
});
|
||||
}
|
||||
|
||||
fn chunks_with_diagnostics<T: ToOffset + ToPoint>(
|
||||
buffer: &Buffer,
|
||||
range: Range<T>,
|
||||
) -> Vec<(String, Option<DiagnosticSeverity>)> {
|
||||
let mut chunks: Vec<(String, Option<DiagnosticSeverity>)> = Vec::new();
|
||||
for chunk in buffer.snapshot().highlighted_text_for_range(range) {
|
||||
for chunk in buffer.snapshot().chunks(range, Some(&Default::default())) {
|
||||
if chunks
|
||||
.last()
|
||||
.map_or(false, |prev_chunk| prev_chunk.1 == chunk.diagnostic)
|
||||
@ -765,3 +988,80 @@ fn rust_lang() -> Language {
|
||||
fn empty(point: Point) -> Range<Point> {
|
||||
point..point
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct FakeFile {
|
||||
abs_path: PathBuf,
|
||||
}
|
||||
|
||||
impl FakeFile {
|
||||
fn new(abs_path: impl Into<PathBuf>) -> Self {
|
||||
Self {
|
||||
abs_path: abs_path.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl File for FakeFile {
|
||||
fn worktree_id(&self) -> usize {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn entry_id(&self) -> Option<usize> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn mtime(&self) -> SystemTime {
|
||||
SystemTime::now()
|
||||
}
|
||||
|
||||
fn path(&self) -> &Arc<Path> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn abs_path(&self) -> Option<PathBuf> {
|
||||
Some(self.abs_path.clone())
|
||||
}
|
||||
|
||||
fn full_path(&self) -> PathBuf {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn file_name(&self) -> Option<OsString> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn is_deleted(&self) -> bool {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn save(
|
||||
&self,
|
||||
_: u64,
|
||||
_: Rope,
|
||||
_: clock::Global,
|
||||
_: &mut MutableAppContext,
|
||||
) -> Task<Result<(clock::Global, SystemTime)>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn load_local(&self, _: &AppContext) -> Option<Task<Result<String>>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn buffer_updated(&self, _: u64, _: super::Operation, _: &mut MutableAppContext) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn buffer_removed(&self, _: u64, _: &mut MutableAppContext) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn boxed_clone(&self) -> Box<dyn File> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
@ -3633,7 +3633,9 @@ mod tests {
|
||||
Point::new(0, 9)..Point::new(0, 10),
|
||||
&Diagnostic {
|
||||
severity: lsp::DiagnosticSeverity::ERROR,
|
||||
message: "undefined variable 'A'".to_string()
|
||||
message: "undefined variable 'A'".to_string(),
|
||||
group_id: 0,
|
||||
is_primary: true
|
||||
}
|
||||
)]
|
||||
)
|
||||
|
@ -256,6 +256,8 @@ message Diagnostic {
|
||||
uint64 end = 2;
|
||||
Severity severity = 3;
|
||||
string message = 4;
|
||||
uint64 group_id = 5;
|
||||
bool is_primary = 6;
|
||||
enum Severity {
|
||||
None = 0;
|
||||
Error = 1;
|
||||
|
@ -1713,15 +1713,19 @@ mod tests {
|
||||
(
|
||||
Point::new(0, 4)..Point::new(0, 7),
|
||||
&Diagnostic {
|
||||
group_id: 0,
|
||||
message: "message 1".to_string(),
|
||||
severity: lsp::DiagnosticSeverity::ERROR,
|
||||
is_primary: true
|
||||
}
|
||||
),
|
||||
(
|
||||
Point::new(0, 10)..Point::new(0, 13),
|
||||
&Diagnostic {
|
||||
group_id: 1,
|
||||
severity: lsp::DiagnosticSeverity::WARNING,
|
||||
message: "message 2".to_string()
|
||||
message: "message 2".to_string(),
|
||||
is_primary: true
|
||||
}
|
||||
)
|
||||
]
|
||||
|
@ -31,6 +31,12 @@ pub trait Summary: Default + Clone + fmt::Debug {
|
||||
|
||||
pub trait Dimension<'a, S: Summary>: Clone + fmt::Debug + Default {
|
||||
fn add_summary(&mut self, _summary: &'a S, _: &S::Context);
|
||||
|
||||
fn from_summary(summary: &'a S, cx: &S::Context) -> Self {
|
||||
let mut dimension = Self::default();
|
||||
dimension.add_summary(summary, cx);
|
||||
dimension
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Summary> Dimension<'a, T> for T {
|
||||
|
@ -227,12 +227,21 @@ pub struct EditorStyle {
|
||||
pub line_number_active: Color,
|
||||
pub guest_selections: Vec<SelectionStyle>,
|
||||
pub syntax: Arc<SyntaxTheme>,
|
||||
pub error_underline: Color,
|
||||
pub warning_underline: Color,
|
||||
#[serde(default)]
|
||||
pub information_underline: Color,
|
||||
#[serde(default)]
|
||||
pub hint_underline: Color,
|
||||
pub error_diagnostic: DiagnosticStyle,
|
||||
pub invalid_error_diagnostic: DiagnosticStyle,
|
||||
pub warning_diagnostic: DiagnosticStyle,
|
||||
pub invalid_warning_diagnostic: DiagnosticStyle,
|
||||
pub information_diagnostic: DiagnosticStyle,
|
||||
pub invalid_information_diagnostic: DiagnosticStyle,
|
||||
pub hint_diagnostic: DiagnosticStyle,
|
||||
pub invalid_hint_diagnostic: DiagnosticStyle,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Deserialize, Default)]
|
||||
pub struct DiagnosticStyle {
|
||||
pub text: Color,
|
||||
#[serde(flatten)]
|
||||
pub block: BlockStyle,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, Deserialize)]
|
||||
@ -251,6 +260,14 @@ pub struct InputEditorStyle {
|
||||
pub selection: SelectionStyle,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
|
||||
pub struct BlockStyle {
|
||||
pub background: Option<Color>,
|
||||
pub border: Option<Color>,
|
||||
pub gutter_background: Option<Color>,
|
||||
pub gutter_border: Option<Color>,
|
||||
}
|
||||
|
||||
impl EditorStyle {
|
||||
pub fn placeholder_text(&self) -> &TextStyle {
|
||||
self.placeholder_text.as_ref().unwrap_or(&self.text)
|
||||
@ -273,10 +290,14 @@ impl InputEditorStyle {
|
||||
line_number_active: Default::default(),
|
||||
guest_selections: Default::default(),
|
||||
syntax: Default::default(),
|
||||
error_underline: Default::default(),
|
||||
warning_underline: Default::default(),
|
||||
information_underline: Default::default(),
|
||||
hint_underline: Default::default(),
|
||||
error_diagnostic: Default::default(),
|
||||
invalid_error_diagnostic: Default::default(),
|
||||
warning_diagnostic: Default::default(),
|
||||
invalid_warning_diagnostic: Default::default(),
|
||||
information_diagnostic: Default::default(),
|
||||
invalid_information_diagnostic: Default::default(),
|
||||
hint_diagnostic: Default::default(),
|
||||
invalid_hint_diagnostic: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -258,15 +258,12 @@ impl DiagnosticMessage {
|
||||
|
||||
fn update(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
|
||||
let editor = editor.read(cx);
|
||||
let cursor_position = editor
|
||||
.selections::<usize>(cx)
|
||||
.max_by_key(|selection| selection.id)
|
||||
.unwrap()
|
||||
.head();
|
||||
let cursor_position = editor.newest_selection(cx).head();
|
||||
let new_diagnostic = editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.diagnostics_in_range::<usize, usize>(cursor_position..cursor_position)
|
||||
.filter(|(range, _)| !range.is_empty())
|
||||
.min_by_key(|(range, diagnostic)| (diagnostic.severity, range.len()))
|
||||
.map(|(_, diagnostic)| diagnostic.clone());
|
||||
if new_diagnostic != self.diagnostic {
|
||||
|
@ -173,7 +173,7 @@ corner_radius = 6
|
||||
|
||||
[project_panel]
|
||||
extends = "$panel"
|
||||
padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2
|
||||
padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2
|
||||
|
||||
[project_panel.entry]
|
||||
text = "$text.1"
|
||||
@ -235,7 +235,12 @@ line_number = "$text.2.color"
|
||||
line_number_active = "$text.0.color"
|
||||
selection = "$selection.host"
|
||||
guest_selections = "$selection.guests"
|
||||
error_underline = "$status.bad"
|
||||
warning_underline = "$status.warn"
|
||||
info_underline = "$status.info"
|
||||
hint_underline = "$status.info"
|
||||
error_color = "$status.bad"
|
||||
error_diagnostic = { text = "$status.bad" }
|
||||
invalid_error_diagnostic = { text = "$text.3.color" }
|
||||
warning_diagnostic = { text = "$status.warn" }
|
||||
invalid_warning_diagnostic = { text = "$text.3.color" }
|
||||
information_diagnostic = { text = "$status.info" }
|
||||
invalid_information_diagnostic = { text = "$text.3.color" }
|
||||
hint_diagnostic = { text = "$status.info" }
|
||||
invalid_hint_diagnostic = { text = "$text.3.color" }
|
||||
|
Loading…
Reference in New Issue
Block a user