Start on a new FragmentList
Here I'm exploring a new approach to the project-wide diagnostics view that can exactly mirror the contents of cargo check. The `FragmentList` composes an arbitrary list of fragments from other buffers and presents them as if they were a single buffer.
This commit is contained in:
parent
3426d46b69
commit
811696670a
12 changed files with 329 additions and 31 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2587,6 +2587,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"similar",
|
"similar",
|
||||||
"smol",
|
"smol",
|
||||||
|
"sum_tree",
|
||||||
"text",
|
"text",
|
||||||
"theme",
|
"theme",
|
||||||
"tree-sitter",
|
"tree-sitter",
|
||||||
|
|
|
@ -11,6 +11,7 @@ test-support = [
|
||||||
"text/test-support",
|
"text/test-support",
|
||||||
"language/test-support",
|
"language/test-support",
|
||||||
"gpui/test-support",
|
"gpui/test-support",
|
||||||
|
"util/test-support",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -37,6 +38,7 @@ smol = "1.2"
|
||||||
text = { path = "../text", features = ["test-support"] }
|
text = { path = "../text", features = ["test-support"] }
|
||||||
language = { path = "../language", features = ["test-support"] }
|
language = { path = "../language", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
|
util = { path = "../util", features = ["test-support"] }
|
||||||
ctor = "0.1"
|
ctor = "0.1"
|
||||||
env_logger = "0.8"
|
env_logger = "0.8"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
|
|
@ -459,6 +459,7 @@ mod tests {
|
||||||
use rand::{prelude::StdRng, Rng};
|
use rand::{prelude::StdRng, Rng};
|
||||||
use std::{env, sync::Arc};
|
use std::{env, sync::Arc};
|
||||||
use theme::SyntaxTheme;
|
use theme::SyntaxTheme;
|
||||||
|
use util::test::sample_text;
|
||||||
use Bias::*;
|
use Bias::*;
|
||||||
|
|
||||||
#[gpui::test(iterations = 100)]
|
#[gpui::test(iterations = 100)]
|
||||||
|
@ -720,7 +721,7 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_text_chunks(cx: &mut gpui::MutableAppContext) {
|
fn test_text_chunks(cx: &mut gpui::MutableAppContext) {
|
||||||
let text = sample_text(6, 6);
|
let text = sample_text(6, 6, 'a');
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
|
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
|
||||||
let tab_size = 4;
|
let tab_size = 4;
|
||||||
let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
|
let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
|
||||||
|
|
|
@ -1064,16 +1064,17 @@ impl FoldEdit {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{test::sample_text, ToPoint};
|
use crate::ToPoint;
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use std::{env, mem};
|
use std::{env, mem};
|
||||||
use text::RandomCharIter;
|
use text::RandomCharIter;
|
||||||
|
use util::test::sample_text;
|
||||||
use Bias::{Left, Right};
|
use Bias::{Left, Right};
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_basic_folds(cx: &mut gpui::MutableAppContext) {
|
fn test_basic_folds(cx: &mut gpui::MutableAppContext) {
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(5, 6), cx));
|
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(5, 6, 'a'), cx));
|
||||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||||
let mut map = FoldMap::new(buffer_snapshot.clone()).0;
|
let mut map = FoldMap::new(buffer_snapshot.clone()).0;
|
||||||
|
|
||||||
|
@ -1187,7 +1188,7 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_overlapping_folds(cx: &mut gpui::MutableAppContext) {
|
fn test_overlapping_folds(cx: &mut gpui::MutableAppContext) {
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(5, 6), cx));
|
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(5, 6, 'a'), cx));
|
||||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||||
let mut map = FoldMap::new(buffer_snapshot.clone()).0;
|
let mut map = FoldMap::new(buffer_snapshot.clone()).0;
|
||||||
let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]);
|
let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]);
|
||||||
|
@ -1203,7 +1204,7 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_merging_folds_via_edit(cx: &mut gpui::MutableAppContext) {
|
fn test_merging_folds_via_edit(cx: &mut gpui::MutableAppContext) {
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(5, 6), cx));
|
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(5, 6, 'a'), cx));
|
||||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||||
let mut map = FoldMap::new(buffer_snapshot.clone()).0;
|
let mut map = FoldMap::new(buffer_snapshot.clone()).0;
|
||||||
|
|
||||||
|
@ -1226,7 +1227,7 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_folds_in_range(cx: &mut gpui::MutableAppContext) {
|
fn test_folds_in_range(cx: &mut gpui::MutableAppContext) {
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(5, 6), cx));
|
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(5, 6, 'a'), cx));
|
||||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||||
let mut map = FoldMap::new(buffer_snapshot.clone()).0;
|
let mut map = FoldMap::new(buffer_snapshot.clone()).0;
|
||||||
let buffer = buffer.read(cx);
|
let buffer = buffer.read(cx);
|
||||||
|
@ -1471,7 +1472,7 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_buffer_rows(cx: &mut gpui::MutableAppContext) {
|
fn test_buffer_rows(cx: &mut gpui::MutableAppContext) {
|
||||||
let text = sample_text(6, 6) + "\n";
|
let text = sample_text(6, 6, 'a') + "\n";
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
|
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
|
||||||
|
|
||||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||||
|
|
|
@ -3676,9 +3676,9 @@ pub fn diagnostic_style(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::test::sample_text;
|
|
||||||
use text::Point;
|
use text::Point;
|
||||||
use unindent::Unindent;
|
use unindent::Unindent;
|
||||||
|
use util::test::sample_text;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_selection_with_mouse(cx: &mut gpui::MutableAppContext) {
|
fn test_selection_with_mouse(cx: &mut gpui::MutableAppContext) {
|
||||||
|
@ -3912,7 +3912,7 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_move_cursor(cx: &mut gpui::MutableAppContext) {
|
fn test_move_cursor(cx: &mut gpui::MutableAppContext) {
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx));
|
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'a'), cx));
|
||||||
let settings = EditorSettings::test(&cx);
|
let settings = EditorSettings::test(&cx);
|
||||||
let (_, view) = cx.add_window(Default::default(), |cx| {
|
let (_, view) = cx.add_window(Default::default(), |cx| {
|
||||||
build_editor(buffer.clone(), settings, cx)
|
build_editor(buffer.clone(), settings, cx)
|
||||||
|
@ -4708,7 +4708,7 @@ mod tests {
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) {
|
fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) {
|
||||||
let settings = EditorSettings::test(&cx);
|
let settings = EditorSettings::test(&cx);
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(10, 5), cx));
|
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(10, 5, 'a'), cx));
|
||||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||||
view.update(cx, |view, cx| {
|
view.update(cx, |view, cx| {
|
||||||
view.fold_ranges(
|
view.fold_ranges(
|
||||||
|
@ -4954,7 +4954,7 @@ mod tests {
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_select_line(cx: &mut gpui::MutableAppContext) {
|
fn test_select_line(cx: &mut gpui::MutableAppContext) {
|
||||||
let settings = EditorSettings::test(&cx);
|
let settings = EditorSettings::test(&cx);
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 5), cx));
|
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 5, 'a'), cx));
|
||||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||||
view.update(cx, |view, cx| {
|
view.update(cx, |view, cx| {
|
||||||
view.select_display_ranges(
|
view.select_display_ranges(
|
||||||
|
@ -5000,7 +5000,7 @@ mod tests {
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_split_selection_into_lines(cx: &mut gpui::MutableAppContext) {
|
fn test_split_selection_into_lines(cx: &mut gpui::MutableAppContext) {
|
||||||
let settings = EditorSettings::test(&cx);
|
let settings = EditorSettings::test(&cx);
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(9, 5), cx));
|
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(9, 5, 'a'), cx));
|
||||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||||
view.update(cx, |view, cx| {
|
view.update(cx, |view, cx| {
|
||||||
view.fold_ranges(
|
view.fold_ranges(
|
||||||
|
|
|
@ -1164,17 +1164,15 @@ fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{Editor, EditorSettings};
|
||||||
test::sample_text,
|
|
||||||
{Editor, EditorSettings},
|
|
||||||
};
|
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
|
use util::test::sample_text;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_layout_line_numbers(cx: &mut gpui::MutableAppContext) {
|
fn test_layout_line_numbers(cx: &mut gpui::MutableAppContext) {
|
||||||
let settings = EditorSettings::test(cx);
|
let settings = EditorSettings::test(cx);
|
||||||
|
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx));
|
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'a'), cx));
|
||||||
let (window_id, editor) = cx.add_window(Default::default(), |cx| {
|
let (window_id, editor) = cx.add_window(Default::default(), |cx| {
|
||||||
Editor::for_buffer(
|
Editor::for_buffer(
|
||||||
buffer,
|
buffer,
|
||||||
|
|
|
@ -9,19 +9,6 @@ fn init_logger() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sample_text(rows: usize, cols: usize) -> String {
|
|
||||||
let mut text = String::new();
|
|
||||||
for row in 0..rows {
|
|
||||||
let c: char = ('a' as u32 + row as u32) as u8 as char;
|
|
||||||
let mut line = c.to_string().repeat(cols);
|
|
||||||
if row < rows - 1 {
|
|
||||||
line.push('\n');
|
|
||||||
}
|
|
||||||
text += &line;
|
|
||||||
}
|
|
||||||
text
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Observer<T>(PhantomData<T>);
|
pub struct Observer<T>(PhantomData<T>);
|
||||||
|
|
||||||
impl<T: 'static> Entity for Observer<T> {
|
impl<T: 'static> Entity for Observer<T> {
|
||||||
|
|
|
@ -12,6 +12,7 @@ test-support = [
|
||||||
"text/test-support",
|
"text/test-support",
|
||||||
"lsp/test-support",
|
"lsp/test-support",
|
||||||
"tree-sitter-rust",
|
"tree-sitter-rust",
|
||||||
|
"util/test-support",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -20,6 +21,7 @@ clock = { path = "../clock" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
lsp = { path = "../lsp" }
|
lsp = { path = "../lsp" }
|
||||||
rpc = { path = "../rpc" }
|
rpc = { path = "../rpc" }
|
||||||
|
sum_tree = { path = "../sum_tree" }
|
||||||
theme = { path = "../theme" }
|
theme = { path = "../theme" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
|
@ -39,6 +41,7 @@ tree-sitter-rust = { version = "0.19.0", optional = true }
|
||||||
text = { path = "../text", features = ["test-support"] }
|
text = { path = "../text", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
lsp = { path = "../lsp", features = ["test-support"] }
|
lsp = { path = "../lsp", features = ["test-support"] }
|
||||||
|
util = { path = "../util", features = ["test-support"] }
|
||||||
rand = "0.8.3"
|
rand = "0.8.3"
|
||||||
tree-sitter-rust = "0.19.0"
|
tree-sitter-rust = "0.19.0"
|
||||||
unindent = "0.1.7"
|
unindent = "0.1.7"
|
||||||
|
|
291
crates/language/src/fragment_list.rs
Normal file
291
crates/language/src/fragment_list.rs
Normal file
|
@ -0,0 +1,291 @@
|
||||||
|
use std::{
|
||||||
|
cmp,
|
||||||
|
ops::{Deref, Range},
|
||||||
|
};
|
||||||
|
use sum_tree::{Bias, Cursor, SumTree};
|
||||||
|
use text::TextSummary;
|
||||||
|
use theme::SyntaxTheme;
|
||||||
|
use util::post_inc;
|
||||||
|
|
||||||
|
use crate::{buffer, Buffer, Chunk};
|
||||||
|
use gpui::{Entity, ModelContext, ModelHandle};
|
||||||
|
|
||||||
|
const NEWLINES: &'static [u8] = &[b'\n'; u8::MAX as usize];
|
||||||
|
|
||||||
|
pub trait ToOffset {
|
||||||
|
fn to_offset<'a>(&self, content: &Snapshot) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type FragmentId = usize;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct FragmentList {
|
||||||
|
snapshot: Snapshot,
|
||||||
|
next_fragment_id: FragmentId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct Snapshot {
|
||||||
|
entries: SumTree<Entry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FragmentProperties<'a, T> {
|
||||||
|
buffer: &'a ModelHandle<Buffer>,
|
||||||
|
range: Range<T>,
|
||||||
|
header_height: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Entry {
|
||||||
|
buffer: buffer::Snapshot,
|
||||||
|
buffer_id: usize,
|
||||||
|
buffer_range: Range<usize>,
|
||||||
|
text_summary: TextSummary,
|
||||||
|
header_height: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
struct EntrySummary {
|
||||||
|
min_buffer_id: usize,
|
||||||
|
max_buffer_id: usize,
|
||||||
|
text: TextSummary,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Chunks<'a> {
|
||||||
|
range: Range<usize>,
|
||||||
|
cursor: Cursor<'a, Entry, usize>,
|
||||||
|
header_height: u8,
|
||||||
|
entry_chunks: Option<buffer::Chunks<'a>>,
|
||||||
|
theme: Option<&'a SyntaxTheme>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FragmentList {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push<'a, O: text::ToOffset>(
|
||||||
|
&mut self,
|
||||||
|
props: FragmentProperties<'a, O>,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> FragmentId {
|
||||||
|
let id = post_inc(&mut self.next_fragment_id);
|
||||||
|
|
||||||
|
let buffer = props.buffer.read(cx);
|
||||||
|
let buffer_range = props.range.start.to_offset(buffer)..props.range.end.to_offset(buffer);
|
||||||
|
let mut text_summary =
|
||||||
|
buffer.text_summary_for_range::<TextSummary, _>(buffer_range.clone());
|
||||||
|
if props.header_height > 0 {
|
||||||
|
text_summary.first_line_chars = 0;
|
||||||
|
text_summary.lines.row += props.header_height as u32;
|
||||||
|
text_summary.lines_utf16.row += props.header_height as u32;
|
||||||
|
text_summary.bytes += props.header_height as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.snapshot.entries.push(
|
||||||
|
Entry {
|
||||||
|
buffer: props.buffer.read(cx).snapshot(),
|
||||||
|
buffer_id: props.buffer.id(),
|
||||||
|
buffer_range,
|
||||||
|
text_summary,
|
||||||
|
header_height: props.header_height,
|
||||||
|
},
|
||||||
|
&(),
|
||||||
|
);
|
||||||
|
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for FragmentList {
|
||||||
|
type Target = Snapshot;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.snapshot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entity for FragmentList {
|
||||||
|
type Event = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Snapshot {
|
||||||
|
pub fn text(&self) -> String {
|
||||||
|
self.chunks(0..self.len(), None)
|
||||||
|
.map(|chunk| chunk.text)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.entries.summary().text.bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn chunks<'a, T: ToOffset>(
|
||||||
|
&'a self,
|
||||||
|
range: Range<T>,
|
||||||
|
theme: Option<&'a SyntaxTheme>,
|
||||||
|
) -> Chunks<'a> {
|
||||||
|
let range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||||
|
let mut cursor = self.entries.cursor::<usize>();
|
||||||
|
cursor.seek(&range.start, Bias::Right, &());
|
||||||
|
|
||||||
|
let entry_chunks = cursor.item().map(|entry| {
|
||||||
|
let buffer_start = entry.buffer_range.start + (range.start - cursor.start());
|
||||||
|
let buffer_end = cmp::min(
|
||||||
|
entry.buffer_range.end,
|
||||||
|
entry.buffer_range.start + (range.end - cursor.start()),
|
||||||
|
);
|
||||||
|
entry.buffer.chunks(buffer_start..buffer_end, theme)
|
||||||
|
});
|
||||||
|
let header_height = cursor.item().map_or(0, |entry| entry.header_height);
|
||||||
|
|
||||||
|
Chunks {
|
||||||
|
range,
|
||||||
|
cursor,
|
||||||
|
header_height,
|
||||||
|
entry_chunks,
|
||||||
|
theme,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl sum_tree::Item for Entry {
|
||||||
|
type Summary = EntrySummary;
|
||||||
|
|
||||||
|
fn summary(&self) -> Self::Summary {
|
||||||
|
EntrySummary {
|
||||||
|
min_buffer_id: self.buffer_id,
|
||||||
|
max_buffer_id: self.buffer_id,
|
||||||
|
text: self.text_summary.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl sum_tree::Summary for EntrySummary {
|
||||||
|
type Context = ();
|
||||||
|
|
||||||
|
fn add_summary(&mut self, summary: &Self, _: &()) {
|
||||||
|
self.min_buffer_id = cmp::min(self.min_buffer_id, summary.min_buffer_id);
|
||||||
|
self.max_buffer_id = cmp::max(self.max_buffer_id, summary.max_buffer_id);
|
||||||
|
self.text.add_summary(&summary.text, &());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> sum_tree::Dimension<'a, EntrySummary> for usize {
|
||||||
|
fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
|
||||||
|
*self += summary.text.bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for Chunks<'a> {
|
||||||
|
type Item = Chunk<'a>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.header_height > 0 {
|
||||||
|
let chunk = Chunk {
|
||||||
|
text: unsafe {
|
||||||
|
std::str::from_utf8_unchecked(&NEWLINES[..self.header_height as usize])
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
self.header_height = 0;
|
||||||
|
return Some(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(entry_chunks) = self.entry_chunks.as_mut() {
|
||||||
|
if let Some(chunk) = entry_chunks.next() {
|
||||||
|
return Some(chunk);
|
||||||
|
} else {
|
||||||
|
self.entry_chunks.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cursor.next(&());
|
||||||
|
let entry = self.cursor.item()?;
|
||||||
|
|
||||||
|
let buffer_end = cmp::min(
|
||||||
|
entry.buffer_range.end,
|
||||||
|
entry.buffer_range.start + (self.range.end - self.cursor.start()),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.header_height = entry.header_height;
|
||||||
|
self.entry_chunks = Some(
|
||||||
|
entry
|
||||||
|
.buffer
|
||||||
|
.chunks(entry.buffer_range.start..buffer_end, self.theme),
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(Chunk {
|
||||||
|
text: "\n",
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToOffset for usize {
|
||||||
|
fn to_offset<'a>(&self, _: &Snapshot) -> usize {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{FragmentList, FragmentProperties};
|
||||||
|
use crate::Buffer;
|
||||||
|
use gpui::MutableAppContext;
|
||||||
|
use text::Point;
|
||||||
|
use util::test::sample_text;
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn test_fragment_buffer(cx: &mut MutableAppContext) {
|
||||||
|
let buffer_1 = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'a'), cx));
|
||||||
|
let buffer_2 = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'g'), cx));
|
||||||
|
|
||||||
|
let list = cx.add_model(|cx| {
|
||||||
|
let mut list = FragmentList::new();
|
||||||
|
|
||||||
|
list.push(
|
||||||
|
FragmentProperties {
|
||||||
|
buffer: &buffer_1,
|
||||||
|
range: Point::new(1, 2)..Point::new(2, 5),
|
||||||
|
header_height: 2,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
list.push(
|
||||||
|
FragmentProperties {
|
||||||
|
buffer: &buffer_1,
|
||||||
|
range: Point::new(3, 3)..Point::new(4, 4),
|
||||||
|
header_height: 1,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
list.push(
|
||||||
|
FragmentProperties {
|
||||||
|
buffer: &buffer_2,
|
||||||
|
range: Point::new(3, 1)..Point::new(3, 3),
|
||||||
|
header_height: 3,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
list
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
list.read(cx).text(),
|
||||||
|
concat!(
|
||||||
|
"\n", // Preserve newlines
|
||||||
|
"\n", //
|
||||||
|
"bbbb\n", //
|
||||||
|
"ccccc\n", //
|
||||||
|
"\n", //
|
||||||
|
"ddd\n", //
|
||||||
|
"eeee\n", //
|
||||||
|
"\n", //
|
||||||
|
"\n", //
|
||||||
|
"\n", //
|
||||||
|
"jj" //
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
mod buffer;
|
mod buffer;
|
||||||
|
mod fragment_list;
|
||||||
mod highlight_map;
|
mod highlight_map;
|
||||||
pub mod proto;
|
pub mod proto;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -49,7 +49,7 @@ pub struct Buffer {
|
||||||
subscriptions: Vec<Weak<Mutex<Vec<Patch<usize>>>>>,
|
subscriptions: Vec<Weak<Mutex<Vec<Patch<usize>>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Snapshot {
|
pub struct Snapshot {
|
||||||
visible_text: Rope,
|
visible_text: Rope,
|
||||||
deleted_text: Rope,
|
deleted_text: Rope,
|
||||||
|
|
|
@ -35,3 +35,16 @@ fn write_tree(path: &Path, tree: serde_json::Value) {
|
||||||
panic!("You must pass a JSON object to this helper")
|
panic!("You must pass a JSON object to this helper")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sample_text(rows: usize, cols: usize, start_char: char) -> String {
|
||||||
|
let mut text = String::new();
|
||||||
|
for row in 0..rows {
|
||||||
|
let c: char = (start_char as u32 + row as u32) as u8 as char;
|
||||||
|
let mut line = c.to_string().repeat(cols);
|
||||||
|
if row < rows - 1 {
|
||||||
|
line.push('\n');
|
||||||
|
}
|
||||||
|
text += &line;
|
||||||
|
}
|
||||||
|
text
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue