From bef09696f6dd3d9f0f941487dba5db02273121e6 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 18 Nov 2021 12:17:22 -0800 Subject: [PATCH] Align block text with the anchor's column Co-Authored-By: Antonio Scandurra Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map/block_map.rs | 180 ++++++++++++++++++--- crates/editor/src/display_map/wrap_map.rs | 4 + 2 files changed, 158 insertions(+), 26 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index df45e2932e..cbc207fc5b 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -8,7 +8,7 @@ use std::{ collections::HashSet, fmt::Debug, iter, - ops::Range, + ops::{Deref, Range}, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, Arc, @@ -74,7 +74,13 @@ pub enum BlockDisposition { #[derive(Clone, Debug)] struct Transform { summary: TransformSummary, - block: Option>, + block: Option, +} + +#[derive(Clone, Debug)] +struct AlignedBlock { + block: Arc, + column: u32, } #[derive(Clone, Debug, Default)] @@ -99,6 +105,8 @@ struct BlockChunks<'a> { chunks: rope::Chunks<'a>, runs: iter::Peekable>, chunk: Option<&'a str>, + remaining_padding: u32, + padding_column: u32, run_start: usize, offset: usize, } @@ -271,6 +279,7 @@ impl BlockMap { .iter() .map(|block| { let mut position = block.position.to_point(buffer); + let column = wrap_snapshot.from_point(position, Bias::Left).column(); match block.disposition { BlockDisposition::Above => position.column = 0, BlockDisposition::Below => { @@ -278,21 +287,22 @@ impl BlockMap { } } let position = wrap_snapshot.from_point(position, Bias::Left); - (position.row(), block) + (position.row(), column, block) }), ); - blocks_in_edit.sort_unstable_by_key(|(row, block)| (*row, block.disposition, block.id)); + blocks_in_edit + .sort_unstable_by_key(|(row, _, block)| (*row, block.disposition, block.id)); // For each of these blocks, insert a new isomorphic transform preceding the block, // and then insert the block itself. - for (block_row, block) in blocks_in_edit.iter().copied() { + for (block_row, column, block) in blocks_in_edit.iter().copied() { let insertion_row = match block.disposition { BlockDisposition::Above => block_row, BlockDisposition::Below => block_row + 1, }; let extent_before_block = insertion_row - new_transforms.summary().input_rows; push_isomorphic(&mut new_transforms, extent_before_block); - new_transforms.push(Transform::block(block.clone()), &()); + new_transforms.push(Transform::block(block.clone(), column), &()); } old_end = WrapRow(old_end.0.min(old_row_count)); @@ -345,7 +355,7 @@ impl BlockPoint { } } -impl std::ops::Deref for BlockPoint { +impl Deref for BlockPoint { type Target = Point; fn deref(&self) -> &Self::Target { @@ -555,7 +565,11 @@ impl BlockSnapshot { let (output_start, input_start) = cursor.start(); let overshoot = row - output_start.0; if let Some(block) = &transform.block { - block.text.line_len(overshoot) + let mut len = block.text.line_len(overshoot); + if len > 0 { + len += block.column; + } + len } else { self.wrap_snapshot.line_len(input_start.0 + overshoot) } @@ -665,16 +679,16 @@ impl Transform { } } - fn block(block: Arc) -> Self { + fn block(block: Arc, column: u32) -> Self { let text_summary = block.text.summary(); Self { summary: TransformSummary { input_rows: 0, output_rows: text_summary.lines.row + 1, longest_row_in_block: text_summary.longest_row, - longest_row_in_block_chars: text_summary.longest_row_chars, + longest_row_in_block_chars: column + text_summary.longest_row_chars, }, - block: Some(block), + block: Some(AlignedBlock { block, column }), } } @@ -759,7 +773,7 @@ impl<'a> Iterator for Chunks<'a> { } impl<'a> BlockChunks<'a> { - fn new(block: &'a Block, rows: Range, cx: Option<&'a AppContext>) -> Self { + fn new(block: &'a AlignedBlock, rows: Range, cx: Option<&'a AppContext>) -> Self { let offset_range = block.text.point_to_offset(Point::new(rows.start, 0)) ..block.text.point_to_offset(Point::new(rows.end, 0)); @@ -785,6 +799,8 @@ impl<'a> BlockChunks<'a> { Self { chunk: None, run_start, + padding_column: block.column, + remaining_padding: block.column, chunks: block.text.chunks_in_range(offset_range.clone()), runs, offset: offset_range.start, @@ -801,7 +817,27 @@ impl<'a> Iterator for BlockChunks<'a> { } let chunk = self.chunk?; - let mut chunk_len = chunk.len(); + + if chunk.starts_with('\n') { + self.remaining_padding = 0; + } + + if self.remaining_padding > 0 { + const PADDING: &'static str = " "; + let padding_len = self.remaining_padding.min(PADDING.len() as u32); + self.remaining_padding -= padding_len; + return Some(Chunk { + text: &PADDING[..padding_len as usize], + ..Default::default() + }); + } + + let mut chunk_len = if let Some(ix) = chunk.find('\n') { + ix + 1 + } else { + chunk.len() + }; + let mut highlight_style = None; if let Some((run_len, style)) = self.runs.peek() { highlight_style = Some(style.clone()); @@ -815,6 +851,11 @@ impl<'a> Iterator for BlockChunks<'a> { self.offset += chunk_len; let (chunk, suffix) = chunk.split_at(chunk_len); + + if chunk.ends_with('\n') { + self.remaining_padding = self.padding_column; + } + self.chunk = if suffix.is_empty() { None } else { @@ -892,6 +933,14 @@ impl BlockDisposition { } } +impl Deref for AlignedBlock { + type Target = Block; + + fn deref(&self) -> &Self::Target { + self.block.as_ref() + } +} + impl Debug for Block { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Block") @@ -926,6 +975,7 @@ mod tests { use super::*; use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap}; use buffer::RandomCharIter; + use gpui::color::Color; use language::Buffer; use rand::prelude::*; use std::env; @@ -944,6 +994,75 @@ mod tests { assert_eq!(offset_for_row("abc\ndef\nghi", 3), (2, 11)); } + #[gpui::test] + fn test_block_chunks(cx: &mut gpui::MutableAppContext) { + let red = Color::red(); + let blue = Color::blue(); + let clear = Color::default(); + + let block = AlignedBlock { + column: 5, + block: Arc::new(Block { + id: BlockId(0), + position: Anchor::min(), + text: "one!\ntwo three\nfour".into(), + build_runs: Some(Arc::new(move |_| { + vec![(3, red.into()), (6, Default::default()), (5, blue.into())] + })), + disposition: BlockDisposition::Above, + }), + }; + + assert_eq!( + colored_chunks(&block, 0..3, cx), + &[ + (" ", clear), + ("one", red), + ("!\n", clear), + (" ", clear), + ("two ", clear), + ("three", blue), + ("\n", clear), + (" ", clear), + ("four", clear) + ] + ); + assert_eq!( + colored_chunks(&block, 0..1, cx), + &[ + (" ", clear), // + ("one", red), + ("!\n", clear), + ] + ); + assert_eq!( + colored_chunks(&block, 1..3, cx), + &[ + (" ", clear), + ("two ", clear), + ("three", blue), + ("\n", clear), + (" ", clear), + ("four", clear) + ] + ); + + fn colored_chunks<'a>( + block: &'a AlignedBlock, + row_range: Range, + cx: &'a AppContext, + ) -> Vec<(&'a str, Color)> { + BlockChunks::new(block, row_range, Some(cx)) + .map(|c| { + ( + c.text, + c.highlight_style.map_or(Color::default(), |s| s.color), + ) + }) + .collect() + } + } + #[gpui::test] fn test_basic_blocks(cx: &mut gpui::MutableAppContext) { let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap(); @@ -988,7 +1107,7 @@ mod tests { let mut snapshot = block_map.read(wraps_snapshot, vec![], cx); assert_eq!( snapshot.text(), - "aaa\nBLOCK 1\nBLOCK 2\nbbb\nccc\nddd\nBLOCK 3" + "aaa\nBLOCK 1\n BLOCK 2\nbbb\nccc\nddd\n BLOCK 3" ); assert_eq!( snapshot.to_block_point(WrapPoint::new(0, 3)), @@ -1080,7 +1199,7 @@ mod tests { 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" + "aaa\nBLOCK 1\nb!!!\n BLOCK 2\nbb\nccc\nddd\n BLOCK 3" ); } @@ -1124,7 +1243,7 @@ mod tests { let mut snapshot = block_map.read(wraps_snapshot, vec![], cx); assert_eq!( snapshot.text(), - "one two \nthree\nBLOCK 2\nseven \neight" + "one two \nthree\n BLOCK 2\nseven \neight" ); } @@ -1267,6 +1386,7 @@ mod tests { .cloned() .map(|(id, block)| { let mut position = block.position.to_point(buffer); + let column = wraps_snapshot.from_point(position, Bias::Left).column(); match block.disposition { BlockDisposition::Above => { position.column = 0; @@ -1279,7 +1399,7 @@ mod tests { ( id, BlockProperties { - position: row, + position: BlockPoint::new(row, column), text: block.text, build_runs: block.build_runs.clone(), disposition: block.disposition, @@ -1288,7 +1408,7 @@ mod tests { }) .collect::>(); sorted_blocks - .sort_unstable_by_key(|(id, block)| (block.position, block.disposition, *id)); + .sort_unstable_by_key(|(id, block)| (block.position.row, block.disposition, *id)); let mut sorted_blocks = sorted_blocks.into_iter().peekable(); let mut expected_buffer_rows = Vec::new(); @@ -1305,11 +1425,15 @@ mod tests { .row; while let Some((_, block)) = sorted_blocks.peek() { - if block.position == row && block.disposition == BlockDisposition::Above { + if block.position.row == row && block.disposition == BlockDisposition::Above { let text = block.text.to_string(); - expected_text.push_str(&text); - expected_text.push('\n'); - for _ in text.split('\n') { + let padding = " ".repeat(block.position.column as usize); + for line in text.split('\n') { + if !line.is_empty() { + expected_text.push_str(&padding); + expected_text.push_str(line); + } + expected_text.push('\n'); expected_buffer_rows.push(None); } sorted_blocks.next(); @@ -1323,11 +1447,15 @@ mod tests { expected_text.push_str(input_line); while let Some((_, block)) = sorted_blocks.peek() { - if block.position == row && block.disposition == BlockDisposition::Below { + if block.position.row == row && block.disposition == BlockDisposition::Below { let text = block.text.to_string(); - expected_text.push('\n'); - expected_text.push_str(&text); - for _ in text.split('\n') { + let padding = " ".repeat(block.position.column as usize); + for line in text.split('\n') { + expected_text.push('\n'); + if !line.is_empty() { + expected_text.push_str(&padding); + expected_text.push_str(line); + } expected_buffer_rows.push(None); } sorted_blocks.next(); diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 70afe9d892..6a6327a0b3 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -916,6 +916,10 @@ impl WrapPoint { &mut self.0.row } + pub fn column(&self) -> u32 { + self.0.column + } + pub fn column_mut(&mut self) -> &mut u32 { &mut self.0.column }