mod.rs

  1mod fold_map;
  2
  3use super::{buffer, Anchor, Buffer, Edit, Point, ToOffset, ToPoint};
  4pub use fold_map::BufferRows;
  5use fold_map::{FoldMap, FoldMapSnapshot};
  6use gpui::{AppContext, ModelHandle};
  7use std::ops::Range;
  8
  9#[derive(Copy, Clone)]
 10pub enum Bias {
 11    Left,
 12    Right,
 13}
 14
 15pub struct DisplayMap {
 16    buffer: ModelHandle<Buffer>,
 17    fold_map: FoldMap,
 18    tab_size: usize,
 19}
 20
 21impl DisplayMap {
 22    pub fn new(buffer: ModelHandle<Buffer>, tab_size: usize, ctx: &AppContext) -> Self {
 23        DisplayMap {
 24            buffer: buffer.clone(),
 25            fold_map: FoldMap::new(buffer, ctx),
 26            tab_size,
 27        }
 28    }
 29
 30    pub fn snapshot(&self, ctx: &AppContext) -> DisplayMapSnapshot {
 31        DisplayMapSnapshot {
 32            folds_snapshot: self.fold_map.snapshot(ctx),
 33            tab_size: self.tab_size,
 34        }
 35    }
 36
 37    pub fn folds_in_range<'a, T>(
 38        &'a self,
 39        range: Range<T>,
 40        app: &'a AppContext,
 41    ) -> impl Iterator<Item = &'a Range<Anchor>>
 42    where
 43        T: ToOffset,
 44    {
 45        self.fold_map.folds_in_range(range, app)
 46    }
 47
 48    pub fn fold<T: ToOffset>(
 49        &mut self,
 50        ranges: impl IntoIterator<Item = Range<T>>,
 51        ctx: &AppContext,
 52    ) {
 53        self.fold_map.fold(ranges, ctx)
 54    }
 55
 56    pub fn unfold<T: ToOffset>(
 57        &mut self,
 58        ranges: impl IntoIterator<Item = Range<T>>,
 59        ctx: &AppContext,
 60    ) {
 61        self.fold_map.unfold(ranges, ctx)
 62    }
 63
 64    pub fn is_line_folded(&self, display_row: u32, ctx: &AppContext) -> bool {
 65        self.fold_map.is_line_folded(display_row, ctx)
 66    }
 67
 68    pub fn text(&self, ctx: &AppContext) -> String {
 69        self.snapshot(ctx)
 70            .chunks_at(DisplayPoint::zero(), ctx)
 71            .collect()
 72    }
 73
 74    pub fn line(&self, display_row: u32, ctx: &AppContext) -> String {
 75        let mut result = String::new();
 76        for chunk in self
 77            .snapshot(ctx)
 78            .chunks_at(DisplayPoint::new(display_row, 0), ctx)
 79        {
 80            if let Some(ix) = chunk.find('\n') {
 81                result.push_str(&chunk[0..ix]);
 82                break;
 83            } else {
 84                result.push_str(chunk);
 85            }
 86        }
 87        result
 88    }
 89
 90    pub fn line_indent(&self, display_row: u32, ctx: &AppContext) -> (u32, bool) {
 91        let mut indent = 0;
 92        let mut is_blank = true;
 93        for c in self
 94            .snapshot(ctx)
 95            .chunks_at(DisplayPoint::new(display_row, 0), ctx)
 96            .flat_map(str::chars)
 97        {
 98            if c == ' ' {
 99                indent += 1;
100            } else {
101                is_blank = c == '\n';
102                break;
103            }
104        }
105        (indent, is_blank)
106    }
107
108    pub fn line_len(&self, row: u32, ctx: &AppContext) -> u32 {
109        DisplayPoint::new(row, self.fold_map.line_len(row, ctx))
110            .expand_tabs(self, ctx)
111            .column()
112    }
113
114    pub fn max_point(&self, ctx: &AppContext) -> DisplayPoint {
115        self.fold_map.max_point(ctx).expand_tabs(self, ctx)
116    }
117
118    pub fn rightmost_point(&self, ctx: &AppContext) -> DisplayPoint {
119        self.fold_map.rightmost_point(ctx)
120    }
121
122    pub fn anchor_before(&self, point: DisplayPoint, bias: Bias, app: &AppContext) -> Anchor {
123        self.buffer
124            .read(app)
125            .anchor_before(point.to_buffer_point(self, bias, app))
126    }
127
128    pub fn anchor_after(&self, point: DisplayPoint, bias: Bias, app: &AppContext) -> Anchor {
129        self.buffer
130            .read(app)
131            .anchor_after(point.to_buffer_point(self, bias, app))
132    }
133}
134
135pub struct DisplayMapSnapshot {
136    folds_snapshot: FoldMapSnapshot,
137    tab_size: usize,
138}
139
140impl DisplayMapSnapshot {
141    pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
142        self.folds_snapshot.buffer_rows(start_row)
143    }
144
145    pub fn chunks_at<'a>(&'a self, point: DisplayPoint, app: &'a AppContext) -> Chunks<'a> {
146        let (point, expanded_char_column, to_next_stop) =
147            self.collapse_tabs(point, Bias::Left, app);
148        let fold_chunks = self
149            .folds_snapshot
150            .chunks_at(self.folds_snapshot.to_display_offset(point, app), app);
151        Chunks {
152            fold_chunks,
153            column: expanded_char_column,
154            tab_size: self.tab_size,
155            chunk: &SPACES[0..to_next_stop],
156            skip_leading_tab: to_next_stop > 0,
157        }
158    }
159
160    pub fn chars_at<'a>(
161        &'a self,
162        point: DisplayPoint,
163        app: &'a AppContext,
164    ) -> impl Iterator<Item = char> + 'a {
165        self.chunks_at(point, app).flat_map(str::chars)
166    }
167
168    fn expand_tabs(&self, mut point: DisplayPoint, ctx: &AppContext) -> DisplayPoint {
169        let chars = self
170            .folds_snapshot
171            .chars_at(DisplayPoint(Point::new(point.row(), 0)), ctx);
172        let expanded = expand_tabs(chars, point.column() as usize, self.tab_size);
173        *point.column_mut() = expanded as u32;
174        point
175    }
176
177    fn collapse_tabs(
178        &self,
179        mut point: DisplayPoint,
180        bias: Bias,
181        ctx: &AppContext,
182    ) -> (DisplayPoint, usize, usize) {
183        let chars = self
184            .folds_snapshot
185            .chars_at(DisplayPoint(Point::new(point.row(), 0)), ctx);
186        let expanded = point.column() as usize;
187        let (collapsed, expanded_char_column, to_next_stop) =
188            collapse_tabs(chars, expanded, bias, self.tab_size);
189        *point.column_mut() = collapsed as u32;
190
191        (point, expanded_char_column, to_next_stop)
192    }
193}
194
195#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
196pub struct DisplayPoint(Point);
197
198impl DisplayPoint {
199    pub fn new(row: u32, column: u32) -> Self {
200        Self(Point::new(row, column))
201    }
202
203    pub fn zero() -> Self {
204        Self::new(0, 0)
205    }
206
207    pub fn row(self) -> u32 {
208        self.0.row
209    }
210
211    pub fn column(self) -> u32 {
212        self.0.column
213    }
214
215    pub fn row_mut(&mut self) -> &mut u32 {
216        &mut self.0.row
217    }
218
219    pub fn column_mut(&mut self) -> &mut u32 {
220        &mut self.0.column
221    }
222
223    pub fn to_buffer_point(self, map: &DisplayMap, bias: Bias, ctx: &AppContext) -> Point {
224        map.fold_map
225            .to_buffer_point(self.collapse_tabs(map, bias, ctx), ctx)
226    }
227
228    pub fn to_buffer_offset(self, map: &DisplayMap, bias: Bias, ctx: &AppContext) -> usize {
229        map.fold_map
230            .to_buffer_offset(self.collapse_tabs(&map, bias, ctx), ctx)
231    }
232
233    fn expand_tabs(self, map: &DisplayMap, ctx: &AppContext) -> Self {
234        map.snapshot(ctx).expand_tabs(self, ctx)
235    }
236
237    fn collapse_tabs(self, map: &DisplayMap, bias: Bias, ctx: &AppContext) -> Self {
238        map.snapshot(ctx).collapse_tabs(self, bias, ctx).0
239    }
240}
241
242impl Point {
243    pub fn to_display_point(self, map: &DisplayMap, ctx: &AppContext) -> DisplayPoint {
244        let mut display_point = map.fold_map.to_display_point(self, ctx);
245        let snapshot = map.fold_map.snapshot(ctx);
246        let chars = snapshot.chars_at(DisplayPoint::new(display_point.row(), 0), ctx);
247        *display_point.column_mut() =
248            expand_tabs(chars, display_point.column() as usize, map.tab_size) as u32;
249        display_point
250    }
251}
252
253impl Anchor {
254    pub fn to_display_point(&self, map: &DisplayMap, app: &AppContext) -> DisplayPoint {
255        self.to_point(map.buffer.read(app))
256            .to_display_point(map, app)
257    }
258}
259
260// Handles a tab width <= 16
261const SPACES: &'static str = "                ";
262
263pub struct Chunks<'a> {
264    fold_chunks: fold_map::Chunks<'a>,
265    chunk: &'a str,
266    column: usize,
267    tab_size: usize,
268    skip_leading_tab: bool,
269}
270
271impl<'a> Iterator for Chunks<'a> {
272    type Item = &'a str;
273
274    fn next(&mut self) -> Option<Self::Item> {
275        if self.chunk.is_empty() {
276            if let Some(chunk) = self.fold_chunks.next() {
277                self.chunk = chunk;
278                if self.skip_leading_tab {
279                    self.chunk = &self.chunk[1..];
280                    self.skip_leading_tab = false;
281                }
282            } else {
283                return None;
284            }
285        }
286
287        for (ix, c) in self.chunk.char_indices() {
288            match c {
289                '\t' => {
290                    if ix > 0 {
291                        let (prefix, suffix) = self.chunk.split_at(ix);
292                        self.chunk = suffix;
293                        return Some(prefix);
294                    } else {
295                        self.chunk = &self.chunk[1..];
296                        let len = self.tab_size - self.column % self.tab_size;
297                        self.column += len;
298                        return Some(&SPACES[0..len]);
299                    }
300                }
301                '\n' => self.column = 0,
302                _ => self.column += 1,
303            }
304        }
305
306        let result = Some(self.chunk);
307        self.chunk = "";
308        result
309    }
310}
311
312pub fn expand_tabs(chars: impl Iterator<Item = char>, column: usize, tab_size: usize) -> usize {
313    let mut expanded_chars = 0;
314    let mut expanded_bytes = 0;
315    let mut collapsed_bytes = 0;
316    for c in chars {
317        if collapsed_bytes == column {
318            break;
319        }
320        if c == '\t' {
321            let tab_len = tab_size - expanded_chars % tab_size;
322            expanded_bytes += tab_len;
323            expanded_chars += tab_len;
324        } else {
325            expanded_bytes += c.len_utf8();
326            expanded_chars += 1;
327        }
328        collapsed_bytes += c.len_utf8();
329    }
330    expanded_bytes
331}
332
333pub fn collapse_tabs(
334    mut chars: impl Iterator<Item = char>,
335    column: usize,
336    bias: Bias,
337    tab_size: usize,
338) -> (usize, usize, usize) {
339    let mut expanded_bytes = 0;
340    let mut expanded_chars = 0;
341    let mut collapsed_bytes = 0;
342    while let Some(c) = chars.next() {
343        if expanded_bytes == column {
344            break;
345        }
346
347        if c == '\t' {
348            let tab_len = tab_size - (expanded_chars % tab_size);
349            expanded_chars += tab_len;
350            expanded_bytes += tab_len;
351            if expanded_bytes > column {
352                expanded_chars -= expanded_bytes - column;
353                return match bias {
354                    Bias::Left => (collapsed_bytes, expanded_chars, expanded_bytes - column),
355                    Bias::Right => (collapsed_bytes + 1, expanded_chars, 0),
356                };
357            }
358        } else {
359            expanded_chars += 1;
360            expanded_bytes += c.len_utf8();
361        }
362        collapsed_bytes += c.len_utf8();
363    }
364    (collapsed_bytes, expanded_chars, 0)
365}
366
367#[cfg(test)]
368mod tests {
369    use gpui::MutableAppContext;
370
371    use super::*;
372    use crate::test::*;
373
374    #[gpui::test]
375    fn test_chunks_at(app: &mut gpui::MutableAppContext) {
376        let text = sample_text(6, 6);
377        let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
378        let map = DisplayMap::new(buffer.clone(), 4, app.as_ref());
379        buffer
380            .update(app, |buffer, ctx| {
381                buffer.edit(
382                    vec![
383                        Point::new(1, 0)..Point::new(1, 0),
384                        Point::new(1, 1)..Point::new(1, 1),
385                        Point::new(2, 1)..Point::new(2, 1),
386                    ],
387                    "\t",
388                    Some(ctx),
389                )
390            })
391            .unwrap();
392
393        assert_eq!(
394            &map.snapshot(app.as_ref())
395                .chunks_at(DisplayPoint::new(1, 0), app.as_ref())
396                .collect::<String>()[0..10],
397            "    b   bb"
398        );
399        assert_eq!(
400            &map.snapshot(app.as_ref())
401                .chunks_at(DisplayPoint::new(1, 2), app.as_ref())
402                .collect::<String>()[0..10],
403            "  b   bbbb"
404        );
405        assert_eq!(
406            &map.snapshot(app.as_ref())
407                .chunks_at(DisplayPoint::new(1, 6), app.as_ref())
408                .collect::<String>()[0..13],
409            "  bbbbb\nc   c"
410        );
411    }
412
413    #[test]
414    fn test_expand_tabs() {
415        assert_eq!(expand_tabs("\t".chars(), 0, 4), 0);
416        assert_eq!(expand_tabs("\t".chars(), 1, 4), 4);
417        assert_eq!(expand_tabs("\ta".chars(), 2, 4), 5);
418    }
419
420    #[gpui::test]
421    fn test_tabs_with_multibyte_chars(app: &mut MutableAppContext) {
422        let text = "\t\tx\nα\t\n🏀α\t\ty";
423        let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
424        let ctx = app.as_ref();
425        let map = DisplayMap::new(buffer.clone(), 4, ctx);
426        assert_eq!(map.text(ctx), "✅       x\nα   \n🏀α      y");
427
428        let point = Point::new(0, "\t\t".len() as u32);
429        let display_point = DisplayPoint::new(0, "".len() as u32);
430        assert_eq!(point.to_display_point(&map, ctx), display_point);
431        assert_eq!(display_point.to_buffer_point(&map, Bias::Left, ctx), point,);
432
433        let point = Point::new(1, "α\t".len() as u32);
434        let display_point = DisplayPoint::new(1, "α   ".len() as u32);
435        assert_eq!(point.to_display_point(&map, ctx), display_point);
436        assert_eq!(display_point.to_buffer_point(&map, Bias::Left, ctx), point,);
437
438        let point = Point::new(2, "🏀α\t\t".len() as u32);
439        let display_point = DisplayPoint::new(2, "🏀α      ".len() as u32);
440        assert_eq!(point.to_display_point(&map, ctx), display_point);
441        assert_eq!(display_point.to_buffer_point(&map, Bias::Left, ctx), point,);
442
443        // Display points inside of expanded tabs
444        assert_eq!(
445            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Bias::Right, ctx),
446            Point::new(0, "\t\t".len() as u32),
447        );
448        assert_eq!(
449            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Bias::Left, ctx),
450            Point::new(0, "\t".len() as u32),
451        );
452        assert_eq!(
453            map.snapshot(ctx)
454                .chunks_at(DisplayPoint::new(0, "".len() as u32), ctx)
455                .collect::<String>(),
456            " x\nα   \n🏀α      y"
457        );
458        assert_eq!(
459            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Bias::Right, ctx),
460            Point::new(0, "\t".len() as u32),
461        );
462        assert_eq!(
463            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Bias::Left, ctx),
464            Point::new(0, "".len() as u32),
465        );
466        assert_eq!(
467            map.snapshot(ctx)
468                .chunks_at(DisplayPoint::new(0, "".len() as u32), ctx)
469                .collect::<String>(),
470            "      x\nα   \n🏀α      y"
471        );
472    }
473
474    #[gpui::test]
475    fn test_max_point(app: &mut gpui::MutableAppContext) {
476        let buffer = app.add_model(|ctx| Buffer::new(0, "aaa\n\t\tbbb", ctx));
477        let map = DisplayMap::new(buffer.clone(), 4, app.as_ref());
478        assert_eq!(map.max_point(app.as_ref()), DisplayPoint::new(1, 11))
479    }
480}