Align block text with the anchor's column

Co-Authored-By: Antonio Scandurra <me@as-cii.com>
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Max Brunsfeld 2021-11-18 12:17:22 -08:00
parent 1a8b23e118
commit bef09696f6
2 changed files with 158 additions and 26 deletions

View file

@ -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<Arc<Block>>,
block: Option<AlignedBlock>,
}
#[derive(Clone, Debug)]
struct AlignedBlock {
block: Arc<Block>,
column: u32,
}
#[derive(Clone, Debug, Default)]
@ -99,6 +105,8 @@ struct BlockChunks<'a> {
chunks: rope::Chunks<'a>,
runs: iter::Peekable<vec::IntoIter<(usize, HighlightStyle)>>,
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<Block>) -> Self {
fn block(block: Arc<Block>, 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<u32>, cx: Option<&'a AppContext>) -> Self {
fn new(block: &'a AlignedBlock, rows: Range<u32>, 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<u32>,
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\n<BLOCK 1\nfour five \nsix\n>BLOCK 2\nseven \neight"
"one two \nthree\n <BLOCK 1\nfour five \nsix\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::<Vec<_>>();
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();

View file

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