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}