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            .chars_at(DisplayPoint::new(display_row, 0), ctx)
 96        {
 97            if c == ' ' {
 98                indent += 1;
 99            } else {
100                is_blank = c == '\n';
101                break;
102            }
103        }
104        (indent, is_blank)
105    }
106
107    pub fn line_len(&self, row: u32, ctx: &AppContext) -> u32 {
108        DisplayPoint::new(row, self.fold_map.line_len(row, ctx))
109            .expand_tabs(self, ctx)
110            .column()
111    }
112
113    pub fn max_point(&self, ctx: &AppContext) -> DisplayPoint {
114        self.fold_map.max_point(ctx).expand_tabs(self, ctx)
115    }
116
117    pub fn rightmost_point(&self, ctx: &AppContext) -> DisplayPoint {
118        self.fold_map.rightmost_point(ctx)
119    }
120
121    pub fn anchor_before(&self, point: DisplayPoint, bias: Bias, app: &AppContext) -> Anchor {
122        self.buffer
123            .read(app)
124            .anchor_before(point.to_buffer_point(self, bias, app))
125    }
126
127    pub fn anchor_after(&self, point: DisplayPoint, bias: Bias, app: &AppContext) -> Anchor {
128        self.buffer
129            .read(app)
130            .anchor_after(point.to_buffer_point(self, bias, app))
131    }
132}
133
134pub struct DisplayMapSnapshot {
135    folds_snapshot: FoldMapSnapshot,
136    tab_size: usize,
137}
138
139impl DisplayMapSnapshot {
140    pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
141        self.folds_snapshot.buffer_rows(start_row)
142    }
143
144    pub fn chunks_at<'a>(&'a self, point: DisplayPoint, app: &'a AppContext) -> Chunks<'a> {
145        let (point, expanded_char_column, to_next_stop) =
146            self.collapse_tabs(point, Bias::Left, app);
147        let fold_chunks = self
148            .folds_snapshot
149            .chunks_at(self.folds_snapshot.to_display_offset(point, app), app);
150        Chunks {
151            fold_chunks,
152            column: expanded_char_column,
153            tab_size: self.tab_size,
154            chunk: &SPACES[0..to_next_stop],
155            skip_leading_tab: to_next_stop > 0,
156        }
157    }
158
159    pub fn chars_at<'a>(
160        &'a self,
161        point: DisplayPoint,
162        app: &'a AppContext,
163    ) -> impl Iterator<Item = char> + 'a {
164        self.chunks_at(point, app).flat_map(str::chars)
165    }
166
167    pub fn column_to_chars(&self, display_row: u32, target: u32, ctx: &AppContext) -> u32 {
168        let mut count = 0;
169        let mut column = 0;
170        for c in self.chars_at(DisplayPoint::new(display_row, 0), ctx) {
171            count += 1;
172            column += c.len_utf8() as u32;
173            if column >= target {
174                break;
175            }
176        }
177        count
178    }
179
180    pub fn column_from_chars(&self, display_row: u32, char_count: u32, ctx: &AppContext) -> u32 {
181        let mut count = 0;
182        let mut column = 0;
183        for c in self.chars_at(DisplayPoint::new(display_row, 0), ctx) {
184            count += 1;
185            column += c.len_utf8() as u32;
186            if count >= char_count {
187                break;
188            }
189        }
190        column
191    }
192
193    fn expand_tabs(&self, mut point: DisplayPoint, ctx: &AppContext) -> DisplayPoint {
194        let chars = self
195            .folds_snapshot
196            .chars_at(DisplayPoint(Point::new(point.row(), 0)), ctx);
197        let expanded = expand_tabs(chars, point.column() as usize, self.tab_size);
198        *point.column_mut() = expanded as u32;
199        point
200    }
201
202    fn collapse_tabs(
203        &self,
204        mut point: DisplayPoint,
205        bias: Bias,
206        ctx: &AppContext,
207    ) -> (DisplayPoint, usize, usize) {
208        let chars = self
209            .folds_snapshot
210            .chars_at(DisplayPoint(Point::new(point.row(), 0)), ctx);
211        let expanded = point.column() as usize;
212        let (collapsed, expanded_char_column, to_next_stop) =
213            collapse_tabs(chars, expanded, bias, self.tab_size);
214        *point.column_mut() = collapsed as u32;
215        (point, expanded_char_column, to_next_stop)
216    }
217}
218
219#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
220pub struct DisplayPoint(Point);
221
222impl DisplayPoint {
223    pub fn new(row: u32, column: u32) -> Self {
224        Self(Point::new(row, column))
225    }
226
227    pub fn zero() -> Self {
228        Self::new(0, 0)
229    }
230
231    pub fn row(self) -> u32 {
232        self.0.row
233    }
234
235    pub fn column(self) -> u32 {
236        self.0.column
237    }
238
239    pub fn row_mut(&mut self) -> &mut u32 {
240        &mut self.0.row
241    }
242
243    pub fn column_mut(&mut self) -> &mut u32 {
244        &mut self.0.column
245    }
246
247    pub fn to_buffer_point(self, map: &DisplayMap, bias: Bias, ctx: &AppContext) -> Point {
248        map.fold_map
249            .to_buffer_point(self.collapse_tabs(map, bias, ctx), ctx)
250    }
251
252    pub fn to_buffer_offset(self, map: &DisplayMap, bias: Bias, ctx: &AppContext) -> usize {
253        map.fold_map
254            .to_buffer_offset(self.collapse_tabs(&map, bias, ctx), ctx)
255    }
256
257    fn expand_tabs(self, map: &DisplayMap, ctx: &AppContext) -> Self {
258        map.snapshot(ctx).expand_tabs(self, ctx)
259    }
260
261    fn collapse_tabs(self, map: &DisplayMap, bias: Bias, ctx: &AppContext) -> Self {
262        map.snapshot(ctx).collapse_tabs(self, bias, ctx).0
263    }
264}
265
266impl Point {
267    pub fn to_display_point(self, map: &DisplayMap, ctx: &AppContext) -> DisplayPoint {
268        let mut display_point = map.fold_map.to_display_point(self, ctx);
269        let snapshot = map.fold_map.snapshot(ctx);
270        let chars = snapshot.chars_at(DisplayPoint::new(display_point.row(), 0), ctx);
271        *display_point.column_mut() =
272            expand_tabs(chars, display_point.column() as usize, map.tab_size) as u32;
273        display_point
274    }
275}
276
277impl Anchor {
278    pub fn to_display_point(&self, map: &DisplayMap, app: &AppContext) -> DisplayPoint {
279        self.to_point(map.buffer.read(app))
280            .to_display_point(map, app)
281    }
282}
283
284// Handles a tab width <= 16
285const SPACES: &'static str = "                ";
286
287pub struct Chunks<'a> {
288    fold_chunks: fold_map::Chunks<'a>,
289    chunk: &'a str,
290    column: usize,
291    tab_size: usize,
292    skip_leading_tab: bool,
293}
294
295impl<'a> Iterator for Chunks<'a> {
296    type Item = &'a str;
297
298    fn next(&mut self) -> Option<Self::Item> {
299        if self.chunk.is_empty() {
300            if let Some(chunk) = self.fold_chunks.next() {
301                self.chunk = chunk;
302                if self.skip_leading_tab {
303                    self.chunk = &self.chunk[1..];
304                    self.skip_leading_tab = false;
305                }
306            } else {
307                return None;
308            }
309        }
310
311        for (ix, c) in self.chunk.char_indices() {
312            match c {
313                '\t' => {
314                    if ix > 0 {
315                        let (prefix, suffix) = self.chunk.split_at(ix);
316                        self.chunk = suffix;
317                        return Some(prefix);
318                    } else {
319                        self.chunk = &self.chunk[1..];
320                        let len = self.tab_size - self.column % self.tab_size;
321                        self.column += len;
322                        return Some(&SPACES[0..len]);
323                    }
324                }
325                '\n' => self.column = 0,
326                _ => self.column += 1,
327            }
328        }
329
330        let result = Some(self.chunk);
331        self.chunk = "";
332        result
333    }
334}
335
336pub fn expand_tabs(chars: impl Iterator<Item = char>, column: usize, tab_size: usize) -> usize {
337    let mut expanded_chars = 0;
338    let mut expanded_bytes = 0;
339    let mut collapsed_bytes = 0;
340    for c in chars {
341        if collapsed_bytes == column {
342            break;
343        }
344        if c == '\t' {
345            let tab_len = tab_size - expanded_chars % tab_size;
346            expanded_bytes += tab_len;
347            expanded_chars += tab_len;
348        } else {
349            expanded_bytes += c.len_utf8();
350            expanded_chars += 1;
351        }
352        collapsed_bytes += c.len_utf8();
353    }
354    expanded_bytes
355}
356
357pub fn collapse_tabs(
358    mut chars: impl Iterator<Item = char>,
359    column: usize,
360    bias: Bias,
361    tab_size: usize,
362) -> (usize, usize, usize) {
363    let mut expanded_bytes = 0;
364    let mut expanded_chars = 0;
365    let mut collapsed_bytes = 0;
366    while let Some(c) = chars.next() {
367        if expanded_bytes == column {
368            break;
369        }
370
371        if c == '\t' {
372            let tab_len = tab_size - (expanded_chars % tab_size);
373            expanded_chars += tab_len;
374            expanded_bytes += tab_len;
375            if expanded_bytes > column {
376                expanded_chars -= expanded_bytes - column;
377                return match bias {
378                    Bias::Left => (collapsed_bytes, expanded_chars, expanded_bytes - column),
379                    Bias::Right => (collapsed_bytes + 1, expanded_chars, 0),
380                };
381            }
382        } else {
383            expanded_chars += 1;
384            expanded_bytes += c.len_utf8();
385        }
386        collapsed_bytes += c.len_utf8();
387
388        if expanded_bytes > column {
389            panic!("column {} is inside of character {:?}", column, c);
390        }
391    }
392    (collapsed_bytes, expanded_chars, 0)
393}
394
395#[cfg(test)]
396mod tests {
397    use gpui::MutableAppContext;
398
399    use super::*;
400    use crate::test::*;
401
402    #[gpui::test]
403    fn test_chunks_at(app: &mut gpui::MutableAppContext) {
404        let text = sample_text(6, 6);
405        let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
406        let map = DisplayMap::new(buffer.clone(), 4, app.as_ref());
407        buffer
408            .update(app, |buffer, ctx| {
409                buffer.edit(
410                    vec![
411                        Point::new(1, 0)..Point::new(1, 0),
412                        Point::new(1, 1)..Point::new(1, 1),
413                        Point::new(2, 1)..Point::new(2, 1),
414                    ],
415                    "\t",
416                    Some(ctx),
417                )
418            })
419            .unwrap();
420
421        assert_eq!(
422            &map.snapshot(app.as_ref())
423                .chunks_at(DisplayPoint::new(1, 0), app.as_ref())
424                .collect::<String>()[0..10],
425            "    b   bb"
426        );
427        assert_eq!(
428            &map.snapshot(app.as_ref())
429                .chunks_at(DisplayPoint::new(1, 2), app.as_ref())
430                .collect::<String>()[0..10],
431            "  b   bbbb"
432        );
433        assert_eq!(
434            &map.snapshot(app.as_ref())
435                .chunks_at(DisplayPoint::new(1, 6), app.as_ref())
436                .collect::<String>()[0..13],
437            "  bbbbb\nc   c"
438        );
439    }
440
441    #[test]
442    fn test_expand_tabs() {
443        assert_eq!(expand_tabs("\t".chars(), 0, 4), 0);
444        assert_eq!(expand_tabs("\t".chars(), 1, 4), 4);
445        assert_eq!(expand_tabs("\ta".chars(), 2, 4), 5);
446    }
447
448    #[gpui::test]
449    fn test_tabs_with_multibyte_chars(app: &mut MutableAppContext) {
450        let text = "\t\tx\nα\t\n🏀α\t\ty";
451        let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
452        let ctx = app.as_ref();
453        let map = DisplayMap::new(buffer.clone(), 4, ctx);
454        assert_eq!(map.text(ctx), "✅       x\nα   \n🏀α      y");
455
456        let point = Point::new(0, "\t\t".len() as u32);
457        let display_point = DisplayPoint::new(0, "".len() as u32);
458        assert_eq!(point.to_display_point(&map, ctx), display_point);
459        assert_eq!(display_point.to_buffer_point(&map, Bias::Left, ctx), point,);
460
461        let point = Point::new(1, "α\t".len() as u32);
462        let display_point = DisplayPoint::new(1, "α   ".len() as u32);
463        assert_eq!(point.to_display_point(&map, ctx), display_point);
464        assert_eq!(display_point.to_buffer_point(&map, Bias::Left, ctx), point,);
465
466        let point = Point::new(2, "🏀α\t\t".len() as u32);
467        let display_point = DisplayPoint::new(2, "🏀α      ".len() as u32);
468        assert_eq!(point.to_display_point(&map, ctx), display_point);
469        assert_eq!(display_point.to_buffer_point(&map, Bias::Left, ctx), point,);
470
471        // Display points inside of expanded tabs
472        assert_eq!(
473            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Bias::Right, ctx),
474            Point::new(0, "\t\t".len() as u32),
475        );
476        assert_eq!(
477            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Bias::Left, ctx),
478            Point::new(0, "\t".len() as u32),
479        );
480        assert_eq!(
481            map.snapshot(ctx)
482                .chunks_at(DisplayPoint::new(0, "".len() as u32), ctx)
483                .collect::<String>(),
484            " x\nα   \n🏀α      y"
485        );
486        assert_eq!(
487            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Bias::Right, ctx),
488            Point::new(0, "\t".len() as u32),
489        );
490        assert_eq!(
491            DisplayPoint::new(0, "".len() as u32).to_buffer_point(&map, Bias::Left, ctx),
492            Point::new(0, "".len() as u32),
493        );
494        assert_eq!(
495            map.snapshot(ctx)
496                .chunks_at(DisplayPoint::new(0, "".len() as u32), ctx)
497                .collect::<String>(),
498            "      x\nα   \n🏀α      y"
499        );
500    }
501
502    #[gpui::test]
503    fn test_max_point(app: &mut gpui::MutableAppContext) {
504        let buffer = app.add_model(|ctx| Buffer::new(0, "aaa\n\t\tbbb", ctx));
505        let map = DisplayMap::new(buffer.clone(), 4, app.as_ref());
506        assert_eq!(map.max_point(app.as_ref()), DisplayPoint::new(1, 11))
507    }
508}