mod.rs

  1mod fold_map;
  2
  3use super::{buffer, Anchor, AnchorRangeExt, Buffer, Edit, Point, TextSummary, ToOffset, ToPoint};
  4use anyhow::Result;
  5pub use fold_map::BufferRows;
  6use fold_map::FoldMap;
  7use gpui::{AppContext, Entity, ModelContext, ModelHandle};
  8use std::ops::Range;
  9
 10#[derive(Copy, Clone)]
 11pub enum Bias {
 12    Left,
 13    Right,
 14}
 15
 16pub struct DisplayMap {
 17    buffer: ModelHandle<Buffer>,
 18    fold_map: FoldMap,
 19    tab_size: usize,
 20}
 21
 22impl Entity for DisplayMap {
 23    type Event = ();
 24}
 25
 26impl DisplayMap {
 27    pub fn new(buffer: ModelHandle<Buffer>, tab_size: usize, ctx: &mut ModelContext<Self>) -> Self {
 28        ctx.subscribe(&buffer, Self::handle_buffer_event);
 29
 30        DisplayMap {
 31            buffer: buffer.clone(),
 32            fold_map: FoldMap::new(buffer, ctx.as_ref()),
 33            tab_size,
 34        }
 35    }
 36
 37    pub fn fold<T: ToOffset>(
 38        &mut self,
 39        ranges: impl IntoIterator<Item = Range<T>>,
 40        ctx: &mut ModelContext<Self>,
 41    ) -> Result<()> {
 42        self.fold_map.fold(ranges, ctx.as_ref())?;
 43        ctx.notify();
 44        Ok(())
 45    }
 46
 47    pub fn unfold<T: ToOffset>(
 48        &mut self,
 49        ranges: impl IntoIterator<Item = Range<T>>,
 50        ctx: &mut ModelContext<Self>,
 51    ) -> Result<()> {
 52        self.fold_map.unfold(ranges, ctx.as_ref())?;
 53        ctx.notify();
 54        Ok(())
 55    }
 56
 57    pub fn is_line_folded(&self, display_row: u32) -> bool {
 58        self.fold_map.is_line_folded(display_row)
 59    }
 60
 61    pub fn text(&self, app: &AppContext) -> String {
 62        self.chars_at(DisplayPoint::zero(), app).unwrap().collect()
 63    }
 64
 65    pub fn line(&self, display_row: u32, app: &AppContext) -> Result<String> {
 66        let chars = self.chars_at(DisplayPoint::new(display_row, 0), app)?;
 67        Ok(chars.take_while(|c| *c != '\n').collect())
 68    }
 69
 70    pub fn chars_at<'a>(&'a self, point: DisplayPoint, app: &'a AppContext) -> Result<Chars<'a>> {
 71        let column = point.column() as usize;
 72        let (point, to_next_stop) = point.collapse_tabs(self, Bias::Left, app)?;
 73        let mut fold_chars = self.fold_map.chars_at(point, app)?;
 74        if to_next_stop > 0 {
 75            fold_chars.next();
 76        }
 77
 78        Ok(Chars {
 79            fold_chars,
 80            column,
 81            to_next_stop,
 82            tab_size: self.tab_size,
 83        })
 84    }
 85
 86    pub fn buffer_rows(&self, start_row: u32) -> Result<BufferRows> {
 87        self.fold_map.buffer_rows(start_row)
 88    }
 89
 90    pub fn line_len(&self, row: u32, ctx: &AppContext) -> Result<u32> {
 91        DisplayPoint::new(row, self.fold_map.line_len(row, ctx)?)
 92            .expand_tabs(self, ctx)
 93            .map(|point| point.column())
 94    }
 95
 96    pub fn max_point(&self, app: &AppContext) -> DisplayPoint {
 97        self.fold_map.max_point().expand_tabs(self, app).unwrap()
 98    }
 99
100    pub fn rightmost_point(&self) -> DisplayPoint {
101        self.fold_map.rightmost_point()
102    }
103
104    pub fn anchor_before(
105        &self,
106        point: DisplayPoint,
107        bias: Bias,
108        app: &AppContext,
109    ) -> Result<Anchor> {
110        self.buffer
111            .read(app)
112            .anchor_before(point.to_buffer_point(self, bias, app)?)
113    }
114
115    pub fn anchor_after(
116        &self,
117        point: DisplayPoint,
118        bias: Bias,
119        app: &AppContext,
120    ) -> Result<Anchor> {
121        self.buffer
122            .read(app)
123            .anchor_after(point.to_buffer_point(self, bias, app)?)
124    }
125
126    fn handle_buffer_event(&mut self, event: &buffer::Event, ctx: &mut ModelContext<Self>) {
127        match event {
128            buffer::Event::Edited(edits) => self.fold_map.apply_edits(edits, ctx.as_ref()).unwrap(),
129            _ => {}
130        }
131    }
132}
133
134#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
135pub struct DisplayPoint(Point);
136
137impl DisplayPoint {
138    pub fn new(row: u32, column: u32) -> Self {
139        Self(Point::new(row, column))
140    }
141
142    pub fn zero() -> Self {
143        Self::new(0, 0)
144    }
145
146    pub fn row(self) -> u32 {
147        self.0.row
148    }
149
150    pub fn column(self) -> u32 {
151        self.0.column
152    }
153
154    pub fn row_mut(&mut self) -> &mut u32 {
155        &mut self.0.row
156    }
157
158    pub fn column_mut(&mut self) -> &mut u32 {
159        &mut self.0.column
160    }
161
162    pub fn to_buffer_point(self, map: &DisplayMap, bias: Bias, app: &AppContext) -> Result<Point> {
163        Ok(map
164            .fold_map
165            .to_buffer_point(self.collapse_tabs(map, bias, app)?.0))
166    }
167
168    fn expand_tabs(mut self, map: &DisplayMap, app: &AppContext) -> Result<Self> {
169        let chars = map
170            .fold_map
171            .chars_at(DisplayPoint(Point::new(self.row(), 0)), app)?;
172        let expanded = expand_tabs(chars, self.column() as usize, map.tab_size);
173        *self.column_mut() = expanded as u32;
174
175        Ok(self)
176    }
177
178    fn collapse_tabs(
179        mut self,
180        map: &DisplayMap,
181        bias: Bias,
182        app: &AppContext,
183    ) -> Result<(Self, usize)> {
184        let chars = map
185            .fold_map
186            .chars_at(DisplayPoint(Point::new(self.0.row, 0)), app)?;
187        let expanded = self.column() as usize;
188        let (collapsed, to_next_stop) = collapse_tabs(chars, expanded, bias, map.tab_size);
189        *self.column_mut() = collapsed as u32;
190
191        Ok((self, to_next_stop))
192    }
193}
194
195impl Point {
196    pub fn to_display_point(self, map: &DisplayMap, app: &AppContext) -> Result<DisplayPoint> {
197        let mut display_point = map.fold_map.to_display_point(self);
198        let chars = map
199            .fold_map
200            .chars_at(DisplayPoint::new(display_point.row(), 0), app)?;
201        *display_point.column_mut() =
202            expand_tabs(chars, display_point.column() as usize, map.tab_size) as u32;
203        Ok(display_point)
204    }
205}
206
207impl Anchor {
208    pub fn to_display_point(&self, map: &DisplayMap, app: &AppContext) -> Result<DisplayPoint> {
209        self.to_point(map.buffer.read(app))?
210            .to_display_point(map, app)
211    }
212}
213
214pub struct Chars<'a> {
215    fold_chars: fold_map::Chars<'a>,
216    column: usize,
217    to_next_stop: usize,
218    tab_size: usize,
219}
220
221impl<'a> Iterator for Chars<'a> {
222    type Item = char;
223
224    fn next(&mut self) -> Option<Self::Item> {
225        if self.to_next_stop > 0 {
226            self.to_next_stop -= 1;
227            self.column += 1;
228            Some(' ')
229        } else {
230            self.fold_chars.next().map(|c| match c {
231                '\t' => {
232                    self.to_next_stop = self.tab_size - self.column % self.tab_size - 1;
233                    self.column += 1;
234                    ' '
235                }
236                '\n' => {
237                    self.column = 0;
238                    c
239                }
240                _ => {
241                    self.column += 1;
242                    c
243                }
244            })
245        }
246    }
247}
248
249pub fn expand_tabs(chars: impl Iterator<Item = char>, column: usize, tab_size: usize) -> usize {
250    let mut expanded = 0;
251    for c in chars.take(column) {
252        if c == '\t' {
253            expanded += tab_size - expanded % tab_size;
254        } else {
255            expanded += 1;
256        }
257    }
258    expanded
259}
260
261pub fn collapse_tabs(
262    mut chars: impl Iterator<Item = char>,
263    column: usize,
264    bias: Bias,
265    tab_size: usize,
266) -> (usize, usize) {
267    let mut expanded = 0;
268    let mut collapsed = 0;
269    while let Some(c) = chars.next() {
270        if expanded == column {
271            break;
272        }
273
274        if c == '\t' {
275            expanded += tab_size - (expanded % tab_size);
276            if expanded > column {
277                return match bias {
278                    Bias::Left => (collapsed, expanded - column),
279                    Bias::Right => (collapsed + 1, 0),
280                };
281            }
282            collapsed += 1;
283        } else {
284            expanded += 1;
285            collapsed += 1;
286        }
287    }
288    (collapsed, 0)
289}
290
291#[cfg(test)]
292mod tests {
293    use super::*;
294    use crate::test::*;
295    use gpui::App;
296
297    #[test]
298    fn test_chars_at() {
299        App::test((), |app| {
300            let text = sample_text(6, 6);
301            let buffer = app.add_model(|_| Buffer::new(0, text));
302            let map = app.add_model(|ctx| DisplayMap::new(buffer.clone(), 4, ctx));
303            buffer
304                .update(app, |buffer, ctx| {
305                    buffer.edit(
306                        vec![
307                            Point::new(1, 0)..Point::new(1, 0),
308                            Point::new(1, 1)..Point::new(1, 1),
309                            Point::new(2, 1)..Point::new(2, 1),
310                        ],
311                        "\t",
312                        Some(ctx),
313                    )
314                })
315                .unwrap();
316
317            let map = map.read(app);
318            assert_eq!(
319                map.chars_at(DisplayPoint::new(1, 0), app.as_ref())
320                    .unwrap()
321                    .take(10)
322                    .collect::<String>(),
323                "    b   bb"
324            );
325            assert_eq!(
326                map.chars_at(DisplayPoint::new(1, 2), app.as_ref())
327                    .unwrap()
328                    .take(10)
329                    .collect::<String>(),
330                "  b   bbbb"
331            );
332            assert_eq!(
333                map.chars_at(DisplayPoint::new(1, 6), app.as_ref())
334                    .unwrap()
335                    .take(13)
336                    .collect::<String>(),
337                "  bbbbb\nc   c"
338            );
339        });
340    }
341
342    #[test]
343    fn test_expand_tabs() {
344        assert_eq!(expand_tabs("\t".chars(), 0, 4), 0);
345        assert_eq!(expand_tabs("\t".chars(), 1, 4), 4);
346        assert_eq!(expand_tabs("\ta".chars(), 2, 4), 5);
347    }
348
349    #[test]
350    fn test_collapse_tabs() {
351        assert_eq!(collapse_tabs("\t".chars(), 0, Bias::Left, 4), (0, 0));
352        assert_eq!(collapse_tabs("\t".chars(), 0, Bias::Right, 4), (0, 0));
353        assert_eq!(collapse_tabs("\t".chars(), 1, Bias::Left, 4), (0, 3));
354        assert_eq!(collapse_tabs("\t".chars(), 1, Bias::Right, 4), (1, 0));
355        assert_eq!(collapse_tabs("\t".chars(), 2, Bias::Left, 4), (0, 2));
356        assert_eq!(collapse_tabs("\t".chars(), 2, Bias::Right, 4), (1, 0));
357        assert_eq!(collapse_tabs("\t".chars(), 3, Bias::Left, 4), (0, 1));
358        assert_eq!(collapse_tabs("\t".chars(), 3, Bias::Right, 4), (1, 0));
359        assert_eq!(collapse_tabs("\t".chars(), 4, Bias::Left, 4), (1, 0));
360        assert_eq!(collapse_tabs("\t".chars(), 4, Bias::Right, 4), (1, 0));
361        assert_eq!(collapse_tabs("\ta".chars(), 5, Bias::Left, 4), (2, 0));
362        assert_eq!(collapse_tabs("\ta".chars(), 5, Bias::Right, 4), (2, 0));
363    }
364
365    #[test]
366    fn test_max_point() {
367        App::test((), |app| {
368            let buffer = app.add_model(|_| Buffer::new(0, "aaa\n\t\tbbb"));
369            let map = app.add_model(|ctx| DisplayMap::new(buffer.clone(), 4, ctx));
370            assert_eq!(
371                map.read(app).max_point(app.as_ref()),
372                DisplayPoint::new(1, 11)
373            )
374        });
375    }
376}