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:
parent
1a8b23e118
commit
bef09696f6
2 changed files with 158 additions and 26 deletions
|
@ -8,7 +8,7 @@ use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
iter,
|
iter,
|
||||||
ops::Range,
|
ops::{Deref, Range},
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicUsize, Ordering::SeqCst},
|
atomic::{AtomicUsize, Ordering::SeqCst},
|
||||||
Arc,
|
Arc,
|
||||||
|
@ -74,7 +74,13 @@ pub enum BlockDisposition {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct Transform {
|
struct Transform {
|
||||||
summary: TransformSummary,
|
summary: TransformSummary,
|
||||||
block: Option<Arc<Block>>,
|
block: Option<AlignedBlock>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct AlignedBlock {
|
||||||
|
block: Arc<Block>,
|
||||||
|
column: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
|
@ -99,6 +105,8 @@ struct BlockChunks<'a> {
|
||||||
chunks: rope::Chunks<'a>,
|
chunks: rope::Chunks<'a>,
|
||||||
runs: iter::Peekable<vec::IntoIter<(usize, HighlightStyle)>>,
|
runs: iter::Peekable<vec::IntoIter<(usize, HighlightStyle)>>,
|
||||||
chunk: Option<&'a str>,
|
chunk: Option<&'a str>,
|
||||||
|
remaining_padding: u32,
|
||||||
|
padding_column: u32,
|
||||||
run_start: usize,
|
run_start: usize,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
}
|
}
|
||||||
|
@ -271,6 +279,7 @@ impl BlockMap {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|block| {
|
.map(|block| {
|
||||||
let mut position = block.position.to_point(buffer);
|
let mut position = block.position.to_point(buffer);
|
||||||
|
let column = wrap_snapshot.from_point(position, Bias::Left).column();
|
||||||
match block.disposition {
|
match block.disposition {
|
||||||
BlockDisposition::Above => position.column = 0,
|
BlockDisposition::Above => position.column = 0,
|
||||||
BlockDisposition::Below => {
|
BlockDisposition::Below => {
|
||||||
|
@ -278,21 +287,22 @@ impl BlockMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let position = wrap_snapshot.from_point(position, Bias::Left);
|
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,
|
// For each of these blocks, insert a new isomorphic transform preceding the block,
|
||||||
// and then insert the block itself.
|
// 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 {
|
let insertion_row = match block.disposition {
|
||||||
BlockDisposition::Above => block_row,
|
BlockDisposition::Above => block_row,
|
||||||
BlockDisposition::Below => block_row + 1,
|
BlockDisposition::Below => block_row + 1,
|
||||||
};
|
};
|
||||||
let extent_before_block = insertion_row - new_transforms.summary().input_rows;
|
let extent_before_block = insertion_row - new_transforms.summary().input_rows;
|
||||||
push_isomorphic(&mut new_transforms, extent_before_block);
|
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));
|
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;
|
type Target = Point;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
|
@ -555,7 +565,11 @@ impl BlockSnapshot {
|
||||||
let (output_start, input_start) = cursor.start();
|
let (output_start, input_start) = cursor.start();
|
||||||
let overshoot = row - output_start.0;
|
let overshoot = row - output_start.0;
|
||||||
if let Some(block) = &transform.block {
|
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 {
|
} else {
|
||||||
self.wrap_snapshot.line_len(input_start.0 + overshoot)
|
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();
|
let text_summary = block.text.summary();
|
||||||
Self {
|
Self {
|
||||||
summary: TransformSummary {
|
summary: TransformSummary {
|
||||||
input_rows: 0,
|
input_rows: 0,
|
||||||
output_rows: text_summary.lines.row + 1,
|
output_rows: text_summary.lines.row + 1,
|
||||||
longest_row_in_block: text_summary.longest_row,
|
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> {
|
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))
|
let offset_range = block.text.point_to_offset(Point::new(rows.start, 0))
|
||||||
..block.text.point_to_offset(Point::new(rows.end, 0));
|
..block.text.point_to_offset(Point::new(rows.end, 0));
|
||||||
|
|
||||||
|
@ -785,6 +799,8 @@ impl<'a> BlockChunks<'a> {
|
||||||
Self {
|
Self {
|
||||||
chunk: None,
|
chunk: None,
|
||||||
run_start,
|
run_start,
|
||||||
|
padding_column: block.column,
|
||||||
|
remaining_padding: block.column,
|
||||||
chunks: block.text.chunks_in_range(offset_range.clone()),
|
chunks: block.text.chunks_in_range(offset_range.clone()),
|
||||||
runs,
|
runs,
|
||||||
offset: offset_range.start,
|
offset: offset_range.start,
|
||||||
|
@ -801,7 +817,27 @@ impl<'a> Iterator for BlockChunks<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let chunk = self.chunk?;
|
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;
|
let mut highlight_style = None;
|
||||||
if let Some((run_len, style)) = self.runs.peek() {
|
if let Some((run_len, style)) = self.runs.peek() {
|
||||||
highlight_style = Some(style.clone());
|
highlight_style = Some(style.clone());
|
||||||
|
@ -815,6 +851,11 @@ impl<'a> Iterator for BlockChunks<'a> {
|
||||||
|
|
||||||
self.offset += chunk_len;
|
self.offset += chunk_len;
|
||||||
let (chunk, suffix) = chunk.split_at(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() {
|
self.chunk = if suffix.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} 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 {
|
impl Debug for Block {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("Block")
|
f.debug_struct("Block")
|
||||||
|
@ -926,6 +975,7 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
|
use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
|
||||||
use buffer::RandomCharIter;
|
use buffer::RandomCharIter;
|
||||||
|
use gpui::color::Color;
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
@ -944,6 +994,75 @@ mod tests {
|
||||||
assert_eq!(offset_for_row("abc\ndef\nghi", 3), (2, 11));
|
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]
|
#[gpui::test]
|
||||||
fn test_basic_blocks(cx: &mut gpui::MutableAppContext) {
|
fn test_basic_blocks(cx: &mut gpui::MutableAppContext) {
|
||||||
let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
|
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);
|
let mut snapshot = block_map.read(wraps_snapshot, vec![], cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
snapshot.text(),
|
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!(
|
assert_eq!(
|
||||||
snapshot.to_block_point(WrapPoint::new(0, 3)),
|
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);
|
let mut snapshot = block_map.read(wraps_snapshot, wrap_edits, cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
snapshot.text(),
|
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);
|
let mut snapshot = block_map.read(wraps_snapshot, vec![], cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
snapshot.text(),
|
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()
|
.cloned()
|
||||||
.map(|(id, block)| {
|
.map(|(id, block)| {
|
||||||
let mut position = block.position.to_point(buffer);
|
let mut position = block.position.to_point(buffer);
|
||||||
|
let column = wraps_snapshot.from_point(position, Bias::Left).column();
|
||||||
match block.disposition {
|
match block.disposition {
|
||||||
BlockDisposition::Above => {
|
BlockDisposition::Above => {
|
||||||
position.column = 0;
|
position.column = 0;
|
||||||
|
@ -1279,7 +1399,7 @@ mod tests {
|
||||||
(
|
(
|
||||||
id,
|
id,
|
||||||
BlockProperties {
|
BlockProperties {
|
||||||
position: row,
|
position: BlockPoint::new(row, column),
|
||||||
text: block.text,
|
text: block.text,
|
||||||
build_runs: block.build_runs.clone(),
|
build_runs: block.build_runs.clone(),
|
||||||
disposition: block.disposition,
|
disposition: block.disposition,
|
||||||
|
@ -1288,7 +1408,7 @@ mod tests {
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
sorted_blocks
|
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 sorted_blocks = sorted_blocks.into_iter().peekable();
|
||||||
|
|
||||||
let mut expected_buffer_rows = Vec::new();
|
let mut expected_buffer_rows = Vec::new();
|
||||||
|
@ -1305,11 +1425,15 @@ mod tests {
|
||||||
.row;
|
.row;
|
||||||
|
|
||||||
while let Some((_, block)) = sorted_blocks.peek() {
|
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();
|
let text = block.text.to_string();
|
||||||
expected_text.push_str(&text);
|
let padding = " ".repeat(block.position.column as usize);
|
||||||
expected_text.push('\n');
|
for line in text.split('\n') {
|
||||||
for _ 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);
|
expected_buffer_rows.push(None);
|
||||||
}
|
}
|
||||||
sorted_blocks.next();
|
sorted_blocks.next();
|
||||||
|
@ -1323,11 +1447,15 @@ mod tests {
|
||||||
expected_text.push_str(input_line);
|
expected_text.push_str(input_line);
|
||||||
|
|
||||||
while let Some((_, block)) = sorted_blocks.peek() {
|
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();
|
let text = block.text.to_string();
|
||||||
expected_text.push('\n');
|
let padding = " ".repeat(block.position.column as usize);
|
||||||
expected_text.push_str(&text);
|
for line in text.split('\n') {
|
||||||
for _ 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);
|
expected_buffer_rows.push(None);
|
||||||
}
|
}
|
||||||
sorted_blocks.next();
|
sorted_blocks.next();
|
||||||
|
|
|
@ -916,6 +916,10 @@ impl WrapPoint {
|
||||||
&mut self.0.row
|
&mut self.0.row
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn column(&self) -> u32 {
|
||||||
|
self.0.column
|
||||||
|
}
|
||||||
|
|
||||||
pub fn column_mut(&mut self) -> &mut u32 {
|
pub fn column_mut(&mut self) -> &mut u32 {
|
||||||
&mut self.0.column
|
&mut self.0.column
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue