Extract tab expansion logic into TabMap

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Antonio Scandurra
2021-07-19 17:29:03 +02:00
co-authored by Nathan Sobo
parent 5a0d156cc5
commit 359132ac19
3 changed files with 369 additions and 243 deletions
+24 -242
View File
@@ -1,19 +1,20 @@
mod fold_map;
mod tab_map;
// mod wrap_map;
use super::{buffer, Anchor, Bias, Buffer, Point, ToOffset, ToPoint};
use crate::settings::StyleId;
use fold_map::FoldMap;
pub use fold_map::InputRows;
use gpui::{AppContext, ModelHandle};
use std::{mem, ops::Range};
use std::ops::Range;
use tab_map::TabMap;
// use wrap_map::WrapMap;
pub struct DisplayMap {
buffer: ModelHandle<Buffer>,
fold_map: FoldMap,
tab_map: TabMap,
// wrap_map: WrapMap,
tab_size: usize,
}
impl DisplayMap {
@@ -21,23 +22,25 @@ impl DisplayMap {
let fold_map = FoldMap::new(buffer.clone(), cx);
let (snapshot, edits) = fold_map.read(cx);
assert_eq!(edits.len(), 0);
let tab_map = TabMap::new(snapshot, tab_size);
// TODO: take `wrap_width` as a parameter.
// let config = { todo!() };
// let wrap_map = WrapMap::new(snapshot, config, cx);
DisplayMap {
buffer,
fold_map,
tab_map,
// wrap_map,
tab_size,
}
}
pub fn snapshot(&self, cx: &AppContext) -> DisplayMapSnapshot {
let (folds_snapshot, edits) = self.fold_map.read(cx);
let (tabs_snapshot, edits) = self.tab_map.sync(folds_snapshot.clone(), edits);
DisplayMapSnapshot {
buffer_snapshot: self.buffer.read(cx).snapshot(),
folds_snapshot,
tab_size: self.tab_size,
tabs_snapshot,
}
}
@@ -63,7 +66,7 @@ impl DisplayMap {
pub struct DisplayMapSnapshot {
buffer_snapshot: buffer::Snapshot,
folds_snapshot: fold_map::Snapshot,
tab_size: usize,
tabs_snapshot: tab_map::Snapshot,
}
impl DisplayMapSnapshot {
@@ -72,35 +75,15 @@ impl DisplayMapSnapshot {
}
pub fn max_point(&self) -> DisplayPoint {
self.expand_tabs(self.folds_snapshot.max_point())
DisplayPoint(self.tabs_snapshot.max_point())
}
pub fn chunks_at(&self, point: DisplayPoint) -> Chunks {
let (point, expanded_char_column, to_next_stop) = self.collapse_tabs(point, Bias::Left);
let fold_chunks = self
.folds_snapshot
.chunks_at(self.folds_snapshot.to_output_offset(point));
Chunks {
fold_chunks,
column: expanded_char_column,
tab_size: self.tab_size,
chunk: &SPACES[0..to_next_stop],
skip_leading_tab: to_next_stop > 0,
}
pub fn chunks_at(&self, point: DisplayPoint) -> tab_map::Chunks {
self.tabs_snapshot.chunks_at(point.0)
}
pub fn highlighted_chunks_for_rows(&mut self, rows: Range<u32>) -> HighlightedChunks {
let start = DisplayPoint::new(rows.start, 0);
let start = self.folds_snapshot.to_output_offset(start.0);
let end = DisplayPoint::new(rows.end, 0).min(self.max_point());
let end = self.folds_snapshot.to_output_offset(end.0);
HighlightedChunks {
fold_chunks: self.folds_snapshot.highlighted_chunks(start..end),
column: 0,
tab_size: self.tab_size,
chunk: "",
style_id: Default::default(),
}
pub fn highlighted_chunks_for_rows(&mut self, rows: Range<u32>) -> tab_map::HighlightedChunks {
self.tabs_snapshot.highlighted_chunks_for_rows(rows)
}
pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator<Item = char> + 'a {
@@ -134,10 +117,7 @@ impl DisplayMapSnapshot {
}
pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
self.expand_tabs(
self.folds_snapshot
.clip_point(self.collapse_tabs(point, bias).0, bias),
)
DisplayPoint(self.tabs_snapshot.clip_point(point.0, bias))
}
pub fn folds_in_range<'a, T>(
@@ -190,15 +170,11 @@ impl DisplayMapSnapshot {
}
pub fn line_len(&self, row: u32) -> u32 {
self.expand_tabs(fold_map::OutputPoint::new(
row,
self.folds_snapshot.line_len(row),
))
.column()
self.tabs_snapshot.line_len(row)
}
pub fn longest_row(&self) -> u32 {
self.folds_snapshot.longest_row()
self.tabs_snapshot.longest_row()
}
pub fn anchor_before(&self, point: DisplayPoint, bias: Bias) -> Anchor {
@@ -210,40 +186,14 @@ impl DisplayMapSnapshot {
self.buffer_snapshot
.anchor_after(point.to_buffer_point(self, bias))
}
fn expand_tabs(&self, point: fold_map::OutputPoint) -> DisplayPoint {
let chars = self
.folds_snapshot
.chars_at(fold_map::OutputPoint::new(point.row(), 0));
let expanded = expand_tabs(chars, point.column() as usize, self.tab_size);
DisplayPoint::new(point.row(), expanded as u32)
}
fn collapse_tabs(
&self,
point: DisplayPoint,
bias: Bias,
) -> (fold_map::OutputPoint, usize, usize) {
let chars = self
.folds_snapshot
.chars_at(fold_map::OutputPoint::new(point.row(), 0));
let expanded = point.column() as usize;
let (collapsed, expanded_char_column, to_next_stop) =
collapse_tabs(chars, expanded, bias, self.tab_size);
(
fold_map::OutputPoint::new(point.row(), collapsed as u32),
expanded_char_column,
to_next_stop,
)
}
}
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
pub struct DisplayPoint(fold_map::OutputPoint);
pub struct DisplayPoint(tab_map::OutputPoint);
impl DisplayPoint {
pub fn new(row: u32, column: u32) -> Self {
Self(fold_map::OutputPoint::new(row, column))
Self(tab_map::OutputPoint::new(row, column))
}
pub fn zero() -> Self {
@@ -268,24 +218,20 @@ impl DisplayPoint {
pub fn to_buffer_point(self, map: &DisplayMapSnapshot, bias: Bias) -> Point {
map.folds_snapshot
.to_input_point(map.collapse_tabs(self, bias).0)
.to_input_point(map.tabs_snapshot.to_input_point(self.0, bias).0)
}
pub fn to_buffer_offset(self, map: &DisplayMapSnapshot, bias: Bias) -> usize {
map.folds_snapshot
.to_input_offset(map.collapse_tabs(self, bias).0)
.to_input_offset(map.tabs_snapshot.to_input_point(self.0, bias).0)
}
}
impl Point {
pub fn to_display_point(self, map: &DisplayMapSnapshot) -> DisplayPoint {
let folded_point = map.folds_snapshot.to_output_point(self);
let chars = map
.folds_snapshot
.chars_at(fold_map::OutputPoint::new(folded_point.row(), 0));
DisplayPoint::new(
folded_point.row(),
expand_tabs(chars, folded_point.column() as usize, map.tab_size) as u32,
DisplayPoint(
map.tabs_snapshot
.to_output_point(map.folds_snapshot.to_output_point(self)),
)
}
}
@@ -296,163 +242,6 @@ impl Anchor {
}
}
// Handles a tab width <= 16
const SPACES: &'static str = " ";
pub struct Chunks<'a> {
fold_chunks: fold_map::Chunks<'a>,
chunk: &'a str,
column: usize,
tab_size: usize,
skip_leading_tab: bool,
}
impl<'a> Iterator for Chunks<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
if self.chunk.is_empty() {
if let Some(chunk) = self.fold_chunks.next() {
self.chunk = chunk;
if self.skip_leading_tab {
self.chunk = &self.chunk[1..];
self.skip_leading_tab = false;
}
} else {
return None;
}
}
for (ix, c) in self.chunk.char_indices() {
match c {
'\t' => {
if ix > 0 {
let (prefix, suffix) = self.chunk.split_at(ix);
self.chunk = suffix;
return Some(prefix);
} else {
self.chunk = &self.chunk[1..];
let len = self.tab_size - self.column % self.tab_size;
self.column += len;
return Some(&SPACES[0..len]);
}
}
'\n' => self.column = 0,
_ => self.column += 1,
}
}
let result = Some(self.chunk);
self.chunk = "";
result
}
}
pub struct HighlightedChunks<'a> {
fold_chunks: fold_map::HighlightedChunks<'a>,
chunk: &'a str,
style_id: StyleId,
column: usize,
tab_size: usize,
}
impl<'a> Iterator for HighlightedChunks<'a> {
type Item = (&'a str, StyleId);
fn next(&mut self) -> Option<Self::Item> {
if self.chunk.is_empty() {
if let Some((chunk, style_id)) = self.fold_chunks.next() {
self.chunk = chunk;
self.style_id = style_id;
} else {
return None;
}
}
for (ix, c) in self.chunk.char_indices() {
match c {
'\t' => {
if ix > 0 {
let (prefix, suffix) = self.chunk.split_at(ix);
self.chunk = suffix;
return Some((prefix, self.style_id));
} else {
self.chunk = &self.chunk[1..];
let len = self.tab_size - self.column % self.tab_size;
self.column += len;
return Some((&SPACES[0..len], self.style_id));
}
}
'\n' => self.column = 0,
_ => self.column += 1,
}
}
Some((mem::take(&mut self.chunk), mem::take(&mut self.style_id)))
}
}
pub fn expand_tabs(chars: impl Iterator<Item = char>, column: usize, tab_size: usize) -> usize {
let mut expanded_chars = 0;
let mut expanded_bytes = 0;
let mut collapsed_bytes = 0;
for c in chars {
if collapsed_bytes == column {
break;
}
if c == '\t' {
let tab_len = tab_size - expanded_chars % tab_size;
expanded_bytes += tab_len;
expanded_chars += tab_len;
} else {
expanded_bytes += c.len_utf8();
expanded_chars += 1;
}
collapsed_bytes += c.len_utf8();
}
expanded_bytes
}
pub fn collapse_tabs(
mut chars: impl Iterator<Item = char>,
column: usize,
bias: Bias,
tab_size: usize,
) -> (usize, usize, usize) {
let mut expanded_bytes = 0;
let mut expanded_chars = 0;
let mut collapsed_bytes = 0;
while let Some(c) = chars.next() {
if expanded_bytes >= column {
break;
}
if c == '\t' {
let tab_len = tab_size - (expanded_chars % tab_size);
expanded_chars += tab_len;
expanded_bytes += tab_len;
if expanded_bytes > column {
expanded_chars -= expanded_bytes - column;
return match bias {
Bias::Left => (collapsed_bytes, expanded_chars, expanded_bytes - column),
Bias::Right => (collapsed_bytes + 1, expanded_chars, 0),
};
}
} else {
expanded_chars += 1;
expanded_bytes += c.len_utf8();
}
if expanded_bytes > column && matches!(bias, Bias::Left) {
expanded_chars -= 1;
break;
}
collapsed_bytes += c.len_utf8();
}
(collapsed_bytes, expanded_chars, 0)
}
#[cfg(test)]
mod tests {
use super::*;
@@ -640,13 +429,6 @@ mod tests {
}
}
#[test]
fn test_expand_tabs() {
assert_eq!(expand_tabs("\t".chars(), 0, 4), 0);
assert_eq!(expand_tabs("\t".chars(), 1, 4), 4);
assert_eq!(expand_tabs("\ta".chars(), 2, 4), 5);
}
#[gpui::test]
fn test_tabs_with_multibyte_chars(cx: &mut gpui::MutableAppContext) {
let text = "\t\tα\nβ\t\n🏀β\t\tγ";
+1 -1
View File
@@ -1041,7 +1041,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for OutputPoint {
pub struct OutputOffset(pub usize);
impl OutputOffset {
pub fn to_output_point(&self, snapshot: &Snapshot) -> OutputPoint {
pub fn to_point(&self, snapshot: &Snapshot) -> OutputPoint {
let mut cursor = snapshot
.transforms
.cursor::<OutputOffset, TransformSummary>();
+344
View File
@@ -0,0 +1,344 @@
use parking_lot::Mutex;
use super::fold_map::{
Chunks as InputChunks, Edit as InputEdit, HighlightedChunks as InputHighlightedChunks,
OutputOffset as InputOffset, OutputPoint as InputPoint, Snapshot as InputSnapshot,
};
use crate::{settings::StyleId, util::Bias};
use std::{mem, ops::Range};
pub struct TabMap(Mutex<Snapshot>);
impl TabMap {
pub fn new(input: InputSnapshot, tab_size: usize) -> Self {
Self(Mutex::new(Snapshot { input, tab_size }))
}
pub fn sync(
&self,
snapshot: InputSnapshot,
input_edits: Vec<InputEdit>,
) -> (Snapshot, Vec<Edit>) {
let mut old_snapshot = self.0.lock();
let new_snapshot = Snapshot {
input: snapshot,
tab_size: old_snapshot.tab_size,
};
let mut output_edits = Vec::with_capacity(input_edits.len());
for input_edit in input_edits {
output_edits.push(Edit {
old_bytes: old_snapshot.to_output_offset(input_edit.old_bytes.start)
..old_snapshot.to_output_offset(input_edit.old_bytes.end),
new_bytes: new_snapshot.to_output_offset(input_edit.new_bytes.start)
..new_snapshot.to_output_offset(input_edit.new_bytes.end),
});
}
*old_snapshot = new_snapshot;
(old_snapshot.clone(), output_edits)
}
}
#[derive(Clone)]
pub struct Snapshot {
input: InputSnapshot,
tab_size: usize,
}
impl Snapshot {
pub fn chunks_at(&self, point: OutputPoint) -> Chunks {
let (point, expanded_char_column, to_next_stop) = self.to_input_point(point, Bias::Left);
let fold_chunks = self.input.chunks_at(self.input.to_output_offset(point));
Chunks {
fold_chunks,
column: expanded_char_column,
tab_size: self.tab_size,
chunk: &SPACES[0..to_next_stop],
skip_leading_tab: to_next_stop > 0,
}
}
pub fn highlighted_chunks_for_rows(&mut self, rows: Range<u32>) -> HighlightedChunks {
let start = self.input.to_output_offset(InputPoint::new(rows.start, 0));
let end = self
.input
.to_output_offset(InputPoint::new(rows.end, 0).min(self.input.max_point()));
HighlightedChunks {
input_chunks: self.input.highlighted_chunks(start..end),
column: 0,
tab_size: self.tab_size,
chunk: "",
style_id: Default::default(),
}
}
pub fn line_len(&self, row: u32) -> u32 {
self.to_output_point(InputPoint::new(row, self.input.line_len(row)))
.column()
}
pub fn longest_row(&self) -> u32 {
// TODO: Account for tab expansion.
self.input.longest_row()
}
pub fn max_point(&self) -> OutputPoint {
self.to_output_point(self.input.max_point())
}
pub fn clip_point(&self, point: OutputPoint, bias: Bias) -> OutputPoint {
self.to_output_point(
self.input
.clip_point(self.to_input_point(point, bias).0, bias),
)
}
pub fn to_output_offset(&self, input_offset: InputOffset) -> OutputOffset {
let input_point = input_offset.to_point(&self.input);
let input_row_start_offset = self
.input
.to_output_offset(InputPoint::new(input_point.row(), 0));
let output_point = self.to_output_point(input_point);
OutputOffset(input_row_start_offset.0 + output_point.column() as usize)
}
pub fn to_output_point(&self, input: InputPoint) -> OutputPoint {
let chars = self.input.chars_at(InputPoint::new(input.row(), 0));
let expanded = Self::expand_tabs(chars, input.column() as usize, self.tab_size);
OutputPoint::new(input.row(), expanded as u32)
}
pub fn to_input_point(&self, output: OutputPoint, bias: Bias) -> (InputPoint, usize, usize) {
let chars = self.input.chars_at(InputPoint::new(output.row(), 0));
let expanded = output.column() as usize;
let (collapsed, expanded_char_column, to_next_stop) =
Self::collapse_tabs(chars, expanded, bias, self.tab_size);
(
InputPoint::new(output.row(), collapsed as u32),
expanded_char_column,
to_next_stop,
)
}
fn expand_tabs(chars: impl Iterator<Item = char>, column: usize, tab_size: usize) -> usize {
let mut expanded_chars = 0;
let mut expanded_bytes = 0;
let mut collapsed_bytes = 0;
for c in chars {
if collapsed_bytes == column {
break;
}
if c == '\t' {
let tab_len = tab_size - expanded_chars % tab_size;
expanded_bytes += tab_len;
expanded_chars += tab_len;
} else {
expanded_bytes += c.len_utf8();
expanded_chars += 1;
}
collapsed_bytes += c.len_utf8();
}
expanded_bytes
}
fn collapse_tabs(
mut chars: impl Iterator<Item = char>,
column: usize,
bias: Bias,
tab_size: usize,
) -> (usize, usize, usize) {
let mut expanded_bytes = 0;
let mut expanded_chars = 0;
let mut collapsed_bytes = 0;
while let Some(c) = chars.next() {
if expanded_bytes >= column {
break;
}
if c == '\t' {
let tab_len = tab_size - (expanded_chars % tab_size);
expanded_chars += tab_len;
expanded_bytes += tab_len;
if expanded_bytes > column {
expanded_chars -= expanded_bytes - column;
return match bias {
Bias::Left => (collapsed_bytes, expanded_chars, expanded_bytes - column),
Bias::Right => (collapsed_bytes + 1, expanded_chars, 0),
};
}
} else {
expanded_chars += 1;
expanded_bytes += c.len_utf8();
}
if expanded_bytes > column && matches!(bias, Bias::Left) {
expanded_chars -= 1;
break;
}
collapsed_bytes += c.len_utf8();
}
(collapsed_bytes, expanded_chars, 0)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct OutputOffset(usize);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Edit {
pub old_bytes: Range<OutputOffset>,
pub new_bytes: Range<OutputOffset>,
}
impl Edit {
pub fn delta(&self) -> isize {
self.inserted_bytes() as isize - self.deleted_bytes() as isize
}
pub fn deleted_bytes(&self) -> usize {
self.old_bytes.end.0 - self.old_bytes.start.0
}
pub fn inserted_bytes(&self) -> usize {
self.new_bytes.end.0 - self.new_bytes.start.0
}
}
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
pub struct OutputPoint(super::Point);
impl OutputPoint {
pub fn new(row: u32, column: u32) -> Self {
Self(super::Point::new(row, column))
}
pub fn zero() -> Self {
Self::new(0, 0)
}
pub fn row(self) -> u32 {
self.0.row
}
pub fn column(self) -> u32 {
self.0.column
}
pub fn row_mut(&mut self) -> &mut u32 {
&mut self.0.row
}
pub fn column_mut(&mut self) -> &mut u32 {
&mut self.0.column
}
}
// Handles a tab width <= 16
const SPACES: &'static str = " ";
pub struct Chunks<'a> {
fold_chunks: InputChunks<'a>,
chunk: &'a str,
column: usize,
tab_size: usize,
skip_leading_tab: bool,
}
impl<'a> Iterator for Chunks<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
if self.chunk.is_empty() {
if let Some(chunk) = self.fold_chunks.next() {
self.chunk = chunk;
if self.skip_leading_tab {
self.chunk = &self.chunk[1..];
self.skip_leading_tab = false;
}
} else {
return None;
}
}
for (ix, c) in self.chunk.char_indices() {
match c {
'\t' => {
if ix > 0 {
let (prefix, suffix) = self.chunk.split_at(ix);
self.chunk = suffix;
return Some(prefix);
} else {
self.chunk = &self.chunk[1..];
let len = self.tab_size - self.column % self.tab_size;
self.column += len;
return Some(&SPACES[0..len]);
}
}
'\n' => self.column = 0,
_ => self.column += 1,
}
}
let result = Some(self.chunk);
self.chunk = "";
result
}
}
pub struct HighlightedChunks<'a> {
input_chunks: InputHighlightedChunks<'a>,
chunk: &'a str,
style_id: StyleId,
column: usize,
tab_size: usize,
}
impl<'a> Iterator for HighlightedChunks<'a> {
type Item = (&'a str, StyleId);
fn next(&mut self) -> Option<Self::Item> {
if self.chunk.is_empty() {
if let Some((chunk, style_id)) = self.input_chunks.next() {
self.chunk = chunk;
self.style_id = style_id;
} else {
return None;
}
}
for (ix, c) in self.chunk.char_indices() {
match c {
'\t' => {
if ix > 0 {
let (prefix, suffix) = self.chunk.split_at(ix);
self.chunk = suffix;
return Some((prefix, self.style_id));
} else {
self.chunk = &self.chunk[1..];
let len = self.tab_size - self.column % self.tab_size;
self.column += len;
return Some((&SPACES[0..len], self.style_id));
}
}
'\n' => self.column = 0,
_ => self.column += 1,
}
}
Some((mem::take(&mut self.chunk), mem::take(&mut self.style_id)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_expand_tabs() {
assert_eq!(Snapshot::expand_tabs("\t".chars(), 0, 4), 0);
assert_eq!(Snapshot::expand_tabs("\t".chars(), 1, 4), 4);
assert_eq!(Snapshot::expand_tabs("\ta".chars(), 2, 4), 5);
}
}