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}