From c278503166291b4f4add36e65a0d1524a0ea70c0 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 12 Nov 2021 15:02:08 -0800 Subject: [PATCH] Make block insertion work in simple cases Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map.rs | 1 + crates/editor/src/display_map/block_map.rs | 259 ++++++++++++++++++--- 2 files changed, 229 insertions(+), 31 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 984531af98..5ec8d90121 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -55,6 +55,7 @@ impl DisplayMap { let (wraps_snapshot, _) = self .wrap_map .update(cx, |map, cx| map.sync(tabs_snapshot.clone(), edits, cx)); + DisplayMapSnapshot { buffer_snapshot: self.buffer.read(cx).snapshot(), folds_snapshot, diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 4e74dee9ea..21b41e48d1 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1,13 +1,26 @@ use super::wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, WrapPoint}; -use buffer::{rope, Anchor, Bias, Point, Rope, ToOffset}; -use gpui::fonts::HighlightStyle; -use language::HighlightedChunk; +use buffer::{rope, Anchor, Bias, Edit, Point, Rope, ToOffset, ToPoint as _}; +use gpui::{fonts::HighlightStyle, AppContext, ModelHandle}; +use language::{Buffer, HighlightedChunk}; use parking_lot::Mutex; -use std::{cmp, collections::HashSet, iter, ops::Range, slice, sync::Arc}; +use std::{ + cmp, + collections::HashSet, + iter, + ops::Range, + slice, + sync::{ + atomic::{AtomicUsize, Ordering::SeqCst}, + Arc, + }, +}; use sum_tree::SumTree; struct BlockMap { - blocks: Vec<(BlockId, Arc)>, + buffer: ModelHandle, + next_block_id: AtomicUsize, + wrap_snapshot: Mutex, + blocks: Vec>, transforms: Mutex>, } @@ -24,6 +37,7 @@ struct BlockId(usize); #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct BlockPoint(super::Point); +#[derive(Debug)] struct Block { id: BlockId, position: Anchor, @@ -44,13 +58,13 @@ where disposition: BlockDisposition, } -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] enum BlockDisposition { Above, Below, } -#[derive(Clone)] +#[derive(Clone, Debug)] struct Transform { summary: TransformSummary, block: Option>, @@ -86,34 +100,53 @@ struct InputRow(u32); struct OutputRow(u32); impl BlockMap { - fn new(wrap_snapshot: WrapSnapshot) -> Self { + fn new(buffer: ModelHandle, wrap_snapshot: WrapSnapshot) -> Self { Self { + buffer, + next_block_id: AtomicUsize::new(0), blocks: Vec::new(), transforms: Mutex::new(SumTree::from_item( Transform::isomorphic(wrap_snapshot.max_point().row() + 1), &(), )), + wrap_snapshot: Mutex::new(wrap_snapshot), } } - fn read(&self, wrap_snapshot: WrapSnapshot, edits: Vec) -> BlockSnapshot { - self.sync(&wrap_snapshot, edits); + fn read( + &self, + wrap_snapshot: WrapSnapshot, + edits: Vec, + cx: &AppContext, + ) -> BlockSnapshot { + self.apply_edits(&wrap_snapshot, edits, cx); + *self.wrap_snapshot.lock() = wrap_snapshot.clone(); BlockSnapshot { wrap_snapshot, transforms: self.transforms.lock().clone(), } } - fn write(&mut self, wrap_snapshot: WrapSnapshot, edits: Vec) -> BlockMapWriter { - self.sync(&wrap_snapshot, edits); + fn write( + &mut self, + wrap_snapshot: WrapSnapshot, + edits: Vec, + cx: &AppContext, + ) -> BlockMapWriter { + self.apply_edits(&wrap_snapshot, edits, cx); + *self.wrap_snapshot.lock() = wrap_snapshot; BlockMapWriter(self) } - fn sync(&self, wrap_snapshot: &WrapSnapshot, edits: Vec) { + fn apply_edits(&self, wrap_snapshot: &WrapSnapshot, edits: Vec, cx: &AppContext) { + let buffer = self.buffer.read(cx); let mut transforms = self.transforms.lock(); let mut new_transforms = SumTree::new(); let mut cursor = transforms.cursor::(); let mut edits = edits.into_iter().peekable(); + let mut last_block_ix = 0; + let mut blocks_in_edit = Vec::new(); + while let Some(mut edit) = edits.next() { new_transforms.push_tree( cursor.slice(&InputRow(edit.old.start), Bias::Left, &()), @@ -147,7 +180,45 @@ impl BlockMap { } } - // TODO: process injections + let start_anchor = buffer.anchor_before(Point::new(edit.new.start, 0)); + let end_anchor = buffer.anchor_after(Point::new(edit.new.end, 0)); + let start_block_ix = match self.blocks[last_block_ix..] + .binary_search_by(|probe| probe.position.cmp(&start_anchor, buffer).unwrap()) + { + Ok(ix) | Err(ix) => last_block_ix + ix, + }; + let end_block_ix = match self.blocks[start_block_ix..] + .binary_search_by(|probe| probe.position.cmp(&end_anchor, buffer).unwrap()) + { + Ok(ix) | Err(ix) => start_block_ix + ix, + }; + last_block_ix = end_block_ix; + + blocks_in_edit.clear(); + blocks_in_edit.extend( + self.blocks[start_block_ix..end_block_ix] + .iter() + .map(|block| (block.position.to_point(buffer).row, block)), + ); + blocks_in_edit.sort_unstable_by_key(|(row, block)| (*row, block.disposition)); + + for (row, block) in blocks_in_edit.iter().copied() { + let insertion_row = if block.disposition.is_above() { + row + } else { + row + 1 + }; + + let new_transforms_end = new_transforms.summary().input_rows; + if new_transforms_end < insertion_row { + new_transforms.push( + Transform::isomorphic(insertion_row - new_transforms_end), + &(), + ); + } + + new_transforms.push(Transform::block(block.clone()), &()); + } let new_transforms_end = new_transforms.summary().input_rows; if new_transforms_end < edit.new.end { @@ -183,14 +254,58 @@ impl BlockPoint { impl<'a> BlockMapWriter<'a> { pub fn insert( - &self, + &mut self, blocks: impl IntoIterator>, + cx: &AppContext, ) -> Vec where P: ToOffset + Clone, T: Into + Clone, { - vec![] + let buffer = self.0.buffer.read(cx); + let mut ids = Vec::new(); + let mut edits = Vec::>::new(); + + for block in blocks { + let id = BlockId(self.0.next_block_id.fetch_add(1, SeqCst)); + ids.push(id); + + let position = buffer.anchor_before(block.position); + let row = position.to_point(buffer).row; + + let block_ix = match self + .0 + .blocks + .binary_search_by(|probe| probe.position.cmp(&position, buffer).unwrap()) + { + Ok(ix) | Err(ix) => ix, + }; + let mut text = block.text.into(); + text.push("\n"); + self.0.blocks.insert( + block_ix, + Arc::new(Block { + id, + position, + text, + runs: block.runs, + disposition: block.disposition, + }), + ); + + if let Err(edit_ix) = edits.binary_search_by_key(&row, |edit| edit.old.start) { + edits.insert( + edit_ix, + Edit { + old: row..(row + 1), + new: row..(row + 1), + }, + ); + } + } + + self.0.apply_edits(&*self.0.wrap_snapshot.lock(), edits, cx); + ids } pub fn remove(&self, ids: HashSet) { @@ -269,6 +384,16 @@ impl Transform { } } + fn block(block: Arc) -> Self { + Self { + summary: TransformSummary { + input_rows: 0, + output_rows: block.text.summary().lines.row, + }, + block: Some(block), + } + } + fn is_isomorphic(&self) -> bool { self.block.is_none() } @@ -283,7 +408,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { } if let Some(block_chunks) = self.block_chunks.as_mut() { - if let Some(mut block_chunk) = block_chunks.next() { + if let Some(block_chunk) = block_chunks.next() { self.output_row += block_chunk.text.matches('\n').count() as u32; return Some(block_chunk); } else { @@ -306,7 +431,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { } if self.input_chunk.text.is_empty() { - self.input_chunk = self.input_chunks.next().unwrap(); + self.input_chunk = self.input_chunks.next()?; } let transform_end = self.transforms.end(&()).0 .0; @@ -314,8 +439,11 @@ impl<'a> Iterator for HighlightedChunks<'a> { offset_for_row(self.input_chunk.text, transform_end - self.output_row); self.output_row += prefix_rows; let (prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes); - self.input_chunk.text = suffix; + if self.output_row == transform_end { + self.transforms.next(&()); + } + Some(HighlightedChunk { text: prefix, ..self.input_chunk @@ -417,20 +545,25 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for OutputRow { } } +impl BlockDisposition { + fn is_above(&self) -> bool { + matches!(self, BlockDisposition::Above) + } +} + // Count the number of bytes prior to a target row. // If the string doesn't contain the target row, return the total number of rows it does contain. // Otherwise return the target row itself. fn offset_for_row(s: &str, target_row: u32) -> (u32, usize) { - assert!(target_row > 0); let mut row = 0; let mut offset = 0; for (ix, line) in s.split('\n').enumerate() { if ix > 0 { row += 1; offset += 1; - if row as u32 >= target_row { - break; - } + } + if row as u32 >= target_row { + break; } offset += line.len(); } @@ -441,13 +574,78 @@ fn offset_for_row(s: &str, target_row: u32) -> (u32, usize) { mod tests { use super::*; use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap}; - use buffer::{RandomCharIter, ToPoint as _}; + use buffer::RandomCharIter; use language::Buffer; use rand::prelude::*; use std::env; + #[gpui::test] + fn test_basic_blocks(cx: &mut gpui::MutableAppContext) { + let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap(); + let font_id = cx + .font_cache() + .select_font(family_id, &Default::default()) + .unwrap(); + + let text = "aaa\nbbb\nccc\nddd\n"; + + let buffer = cx.add_model(|cx| Buffer::new(0, text, cx)); + let (fold_map, folds_snapshot) = FoldMap::new(buffer.clone(), cx); + let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), 1); + let (wrap_map, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, None, cx); + let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone()); + + let mut writer = block_map.write(wraps_snapshot.clone(), vec![], cx); + writer.insert( + vec![ + BlockProperties { + position: Point::new(1, 0), + text: "BLOCK 1", + disposition: BlockDisposition::Above, + runs: vec![], + }, + BlockProperties { + position: Point::new(1, 2), + text: "BLOCK 2", + disposition: BlockDisposition::Above, + runs: vec![], + }, + BlockProperties { + position: Point::new(3, 2), + text: "BLOCK 3", + disposition: BlockDisposition::Below, + runs: vec![], + }, + ], + cx, + ); + + let mut snapshot = block_map.read(wraps_snapshot, vec![], cx); + assert_eq!( + snapshot.text(), + "aaa\nBLOCK 1\nBLOCK 2\nbbb\nccc\nddd\nBLOCK 3\n" + ); + + // Insert a line break, separating two block decorations into separate + // lines. + buffer.update(cx, |buffer, cx| { + buffer.edit([Point::new(1, 1)..Point::new(1, 1)], "!!!\n", cx) + }); + + let (folds_snapshot, fold_edits) = fold_map.read(cx); + let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits); + let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { + wrap_map.sync(tabs_snapshot, tab_edits, cx) + }); + let mut snapshot = block_map.read(wraps_snapshot, wrap_edits, cx); + assert_eq!( + snapshot.text(), + "aaa\nBLOCK 1\nb!!!\nBLOCK 2\nbb\nccc\nddd\nBLOCK 3\n" + ); + } + #[gpui::test(iterations = 100)] - fn test_random(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { + fn test_random_blocks(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .unwrap_or(10); @@ -461,7 +659,6 @@ mod tests { .unwrap(); let font_size = 14.0; - log::info!("Tab size: {}", tab_size); log::info!("Wrap width: {:?}", wrap_width); let buffer = cx.add_model(|cx| { @@ -473,7 +670,7 @@ mod tests { let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size); let (wrap_map, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, font_size, wrap_width, cx); - let mut block_map = BlockMap::new(wraps_snapshot); + let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot); let mut expected_blocks = Vec::new(); for _ in 0..operations { @@ -519,11 +716,11 @@ mod tests { let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tabs_snapshot, tab_edits, cx) }); - let block_map = block_map.write(wraps_snapshot, wrap_edits); + let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx); expected_blocks.extend( block_map - .insert(block_properties.clone()) + .insert(block_properties.clone(), cx) .into_iter() .zip(block_properties), ); @@ -543,7 +740,7 @@ mod tests { let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tabs_snapshot, tab_edits, cx) }); - let block_map = block_map.write(wraps_snapshot, wrap_edits); + let block_map = block_map.write(wraps_snapshot, wrap_edits, cx); block_map.remove(block_ids_to_remove); } @@ -557,7 +754,7 @@ mod tests { let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tabs_snapshot, tab_edits, cx) }); - let mut blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits); + let mut blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, cx); assert_eq!( blocks_snapshot.transforms.summary().input_rows, wraps_snapshot.max_point().row() + 1