1use std::collections::HashMap;
16use std::ops::Range;
17use std::sync::Arc;
18
19use serde::{Deserialize, Serialize};
20
21use crate::buffer::Buffer;
22use crate::fold::FoldSet;
23use crate::history::Revision;
24use crate::position::{
25 ByteOffset, VisualCol, Wrapped, byte_to_visual_col, prev_grapheme_boundary, visual_col_to_byte,
26 wrap_line,
27};
28use crate::text::Document;
29use crate::view::View;
30
31#[derive(
34 Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
35)]
36pub struct RowPos {
37 pub line: usize,
38 pub sub: usize,
39}
40
41impl RowPos {
42 pub fn new(line: usize, sub: usize) -> Self {
43 Self { line, sub }
44 }
45}
46
47#[derive(Clone, Debug, Default)]
51pub(crate) struct WrapCache {
52 key: Option<(usize, usize)>,
53 lines: HashMap<usize, WrapEntry>,
54}
55
56#[derive(Clone, Debug)]
57struct WrapEntry {
58 revision: Revision,
60 text: String,
61 wrapped: Arc<Wrapped>,
62}
63
64const WRAP_CACHE_LINES: usize = 4096;
66
67#[derive(Clone, Debug, Default, PartialEq, Eq)]
70pub struct Padding {
71 pub before: Vec<usize>,
72 pub after_last: usize,
73}
74
75static NO_FOLDS: std::sync::LazyLock<FoldSet> = std::sync::LazyLock::new(FoldSet::default);
77
78#[derive(Clone, Copy)]
80pub struct DisplayMap<'a> {
81 doc: &'a Document,
82 revision: Revision,
83 folds: &'a FoldSet,
84 wrap: Option<usize>,
85 cache: &'a std::cell::RefCell<WrapCache>,
86 padding: Option<&'a Padding>,
87 tab_width: usize,
88}
89
90impl<'a> DisplayMap<'a> {
91 pub fn new(buffer: &'a Buffer, view: &'a View, tab_width: usize) -> Self {
92 let padding = view.padding.as_deref();
93 Self {
94 doc: buffer.doc(),
95 revision: buffer.revision(),
96 folds: if padding.is_some() {
97 &NO_FOLDS
98 } else {
99 &view.folds
100 },
101 wrap: view.wrap_cols.filter(|_| padding.is_none()),
102 cache: &view.wrap_cache,
103 padding,
104 tab_width,
105 }
106 }
107
108 pub fn doc(&self) -> &'a Document {
109 self.doc
110 }
111
112 pub fn wraps(&self) -> bool {
114 self.wrap.is_some()
115 }
116
117 fn wrapped(&self, line: usize) -> Option<Arc<Wrapped>> {
119 let cols = self.wrap?;
120 let mut cache = self.cache.borrow_mut();
121 let key = (cols, self.tab_width);
122 if cache.key != Some(key) || cache.lines.len() > WRAP_CACHE_LINES {
123 cache.key = Some(key);
124 cache.lines.clear();
125 }
126 if let Some(e) = cache.lines.get_mut(&line) {
127 if e.revision == self.revision {
128 return Some(e.wrapped.clone());
129 }
130 let text = self.doc.line_text(line);
131 if text == e.text {
132 e.revision = self.revision;
133 return Some(e.wrapped.clone());
134 }
135 }
136 let text = self.doc.line_text(line);
137 let wrapped = Arc::new(wrap_line(&text, cols, self.tab_width));
138 let entry = WrapEntry {
139 revision: self.revision,
140 text,
141 wrapped: wrapped.clone(),
142 };
143 cache.lines.insert(line, entry);
144 Some(wrapped)
145 }
146
147 pub fn rows_in_line(&self, line: usize) -> usize {
149 if self.folds.is_hidden(line) {
150 0
151 } else {
152 self.pad_before(line) + self.text_rows(line) + self.pad_after(line)
153 }
154 }
155
156 fn text_rows(&self, line: usize) -> usize {
158 self.wrapped(line).map_or(1, |w| w.starts.len())
159 }
160
161 fn pad_before(&self, line: usize) -> usize {
162 self.padding
163 .map_or(0, |p| p.before.get(line).copied().unwrap_or(0))
164 }
165
166 fn pad_after(&self, line: usize) -> usize {
167 match self.padding {
168 Some(p) if line + 1 == self.doc.line_count() => p.after_last,
169 _ => 0,
170 }
171 }
172
173 pub fn text_sub(&self, row: RowPos) -> Option<usize> {
176 let sub = row.sub.checked_sub(self.pad_before(row.line))?;
177 (sub < self.text_rows(row.line)).then_some(sub)
178 }
179
180 pub fn is_filler(&self, row: RowPos) -> bool {
182 self.text_sub(row).is_none()
183 }
184
185 pub fn visible_line_count(&self) -> usize {
187 self.doc.line_count() - self.folds.hidden_count()
188 }
189
190 fn shown_line(&self, line: usize) -> usize {
192 match self.folds.hidden_range(line) {
193 Some((start, _)) => start - 1,
194 None => line,
195 }
196 }
197
198 fn visible_index(&self, line: usize) -> usize {
200 line - self.folds.hidden_before(line)
201 }
202
203 fn next_visible_line(&self, line: usize) -> Option<usize> {
204 let next = match self.folds.hidden_range(line + 1) {
205 Some((_, end)) => end + 1,
206 None => line + 1,
207 };
208 (next < self.doc.line_count()).then_some(next)
209 }
210
211 fn prev_visible_line(&self, line: usize) -> Option<usize> {
212 let prev = line.checked_sub(1)?;
213 Some(self.shown_line(prev))
214 }
215
216 pub fn first_row(&self) -> RowPos {
217 RowPos::default()
218 }
219
220 pub fn last_row(&self) -> RowPos {
221 let line = self.shown_line(self.doc.line_count() - 1);
222 RowPos::new(line, self.rows_in_line(line) - 1)
223 }
224
225 pub fn clamp_row(&self, row: RowPos) -> RowPos {
229 let line = self.shown_line(row.line.min(self.doc.line_count() - 1));
230 RowPos::new(line, row.sub.min(self.rows_in_line(line) - 1))
231 }
232
233 pub fn step(&self, row: RowPos, n: isize) -> (RowPos, isize) {
236 if !self.wraps() && self.padding.is_none() {
237 let from = self.visible_index(self.shown_line(row.line)) as isize;
239 let last = self.visible_line_count() as isize - 1;
240 let to = (from + n).clamp(0, last);
241 let line = self.folds.visible_line(to as usize, self.doc.line_count());
242 return (RowPos::new(line, 0), to - from);
243 }
244 let mut row = self.clamp_row(row);
245 let mut moved = 0;
246 while moved < n {
247 if row.sub + 1 < self.rows_in_line(row.line) {
248 row.sub += 1;
249 } else if let Some(line) = self.next_visible_line(row.line) {
250 row = RowPos::new(line, 0);
251 } else {
252 break;
253 }
254 moved += 1;
255 }
256 while moved > n {
257 if row.sub > 0 {
258 row.sub -= 1;
259 } else if let Some(line) = self.prev_visible_line(row.line) {
260 row = RowPos::new(line, self.rows_in_line(line) - 1);
261 } else {
262 break;
263 }
264 moved -= 1;
265 }
266 (row, moved)
267 }
268
269 pub fn advance(&self, row: RowPos, n: isize) -> RowPos {
271 self.step(row, n).0
272 }
273
274 pub fn row_range(&self, row: RowPos) -> Range<ByteOffset> {
277 let start = self.doc.line_start(row.line);
278 let end = self.doc.line_end(row.line);
279 let Some(sub) = self.text_sub(row) else {
280 let at = if row.sub < self.pad_before(row.line) {
282 start
283 } else {
284 end
285 };
286 return at..at;
287 };
288 match self.wrapped(row.line) {
289 None => start..end,
290 Some(w) => {
291 let at = |i: usize| ByteOffset(start.0 + w.starts[i]);
292 let sub = sub.min(w.starts.len() - 1);
293 at(sub)..w.starts.get(sub + 1).map_or(end, |_| at(sub + 1))
294 }
295 }
296 }
297
298 pub fn is_last_in_line(&self, row: RowPos) -> bool {
301 self.text_sub(row)
302 .is_none_or(|s| s + 1 >= self.text_rows(row.line))
303 }
304
305 pub fn row_end(&self, row: RowPos) -> ByteOffset {
309 let range = self.row_range(row);
310 if self.is_last_in_line(row) {
311 range.end
312 } else {
313 prev_grapheme_boundary(self.doc, range.end).max(range.start)
314 }
315 }
316
317 pub fn row_cols(&self, row: RowPos) -> (usize, usize) {
320 match (self.wrapped(row.line), self.text_sub(row)) {
321 (Some(w), Some(sub)) if sub > 0 => {
322 let sub = sub.min(w.starts.len() - 1);
323 (w.start_cols[sub], w.indent)
324 }
325 _ => (0, 0),
326 }
327 }
328
329 pub fn row_indent(&self, row: RowPos) -> usize {
331 self.row_cols(row).1
332 }
333
334 pub fn row_of(&self, pos: ByteOffset) -> RowPos {
337 let line = self.doc.line_of(pos);
338 let shown = self.shown_line(line);
339 if shown != line {
340 return RowPos::new(shown, self.rows_in_line(shown) - 1);
341 }
342 let sub = match self.wrapped(line) {
343 None => 0,
344 Some(w) => {
345 let off = pos.0 - self.doc.line_start(line).0;
346 w.starts.partition_point(|&s| s <= off) - 1
347 }
348 };
349 RowPos::new(line, self.pad_before(line) + sub)
350 }
351
352 pub fn col_in_row(&self, pos: ByteOffset) -> VisualCol {
356 let col = byte_to_visual_col(self.doc, pos, self.tab_width).0;
357 let (start_col, indent) = self.row_cols(self.row_of(pos));
358 VisualCol(indent + col - start_col)
359 }
360
361 pub fn byte_at_col(&self, row: RowPos, col: VisualCol) -> ByteOffset {
364 if self.is_filler(row) {
365 return self.row_range(row).start;
366 }
367 let (start_col, indent) = self.row_cols(row);
368 let target = if start_col > 0 || indent > 0 {
369 start_col + col.0.saturating_sub(indent)
370 } else {
371 col.0
372 };
373 let at = visual_col_to_byte(self.doc, row.line, VisualCol(target), self.tab_width);
374 at.clamp(self.row_range(row).start, self.row_end(row))
375 }
376
377 pub fn offset_of(&self, row: RowPos, frac: f32) -> f64 {
381 let rows = self.rows_in_line(row.line).max(1) as f64;
382 self.visible_index(self.shown_line(row.line)) as f64 + (row.sub as f64 + frac as f64) / rows
383 }
384
385 pub fn pos_at_offset(&self, offset: f64) -> (RowPos, f32) {
388 let max = self.visible_line_count().saturating_sub(1) as f64;
389 let offset = offset.clamp(0.0, max + 0.999_999);
390 let index = offset.floor();
391 let line = self
392 .folds
393 .visible_line(index as usize, self.doc.line_count());
394 let rows = self.rows_in_line(line).max(1) as f64;
395 let within = (offset - index) * rows;
396 let sub = (within.floor() as usize).min(self.rows_in_line(line).max(1) - 1);
397 (RowPos::new(line, sub), (within - sub as f64) as f32)
398 }
399
400 pub fn scroll_by(&self, row: RowPos, frac: f32, rows: f64) -> (RowPos, f32) {
404 let total = frac as f64 + rows;
405 let whole = total.floor();
406 let (to, moved) = self.step(row, whole as isize);
407 if moved as f64 != whole {
408 (to, 0.0)
410 } else {
411 (to, (total - whole) as f32)
412 }
413 }
414
415 pub fn max_top(&self, rows_full: usize) -> RowPos {
418 self.advance(self.last_row(), -(rows_full.saturating_sub(1) as isize))
419 }
420
421 pub fn rows_from(&self, top: RowPos, limit: usize) -> impl Iterator<Item = RowPos> + '_ {
423 let last = self.last_row();
424 std::iter::successors(Some(top), move |&r| (r < last).then(|| self.advance(r, 1)))
425 .take(limit)
426 }
427}
428
429#[cfg(test)]
430mod tests {
431 use super::*;
432 use crate::editor::Editor;
433
434 fn with_map(text: &str, wrap: Option<usize>, f: impl FnOnce(DisplayMap)) {
435 let mut ed = Editor::new();
436 let buffer = ed.add_buffer(text, Default::default());
437 let view = ed.add_view(buffer).unwrap();
438 ed.view_mut(view).unwrap().wrap_cols = wrap;
439 let v = ed.view(view).unwrap();
440 f(DisplayMap::new(ed.buffer(buffer).unwrap(), v, 4));
441 }
442
443 #[test]
444 fn identity_rows_are_lines() {
445 with_map("a\nbb\n\tc", None, |m| {
446 assert_eq!(m.last_row(), RowPos::new(2, 0));
447 assert_eq!(m.row_of(ByteOffset(3)), RowPos::new(1, 0));
448 assert_eq!(m.row_range(RowPos::new(1, 0)), ByteOffset(2)..ByteOffset(4));
449 assert_eq!(m.step(RowPos::new(1, 0), 5), (RowPos::new(2, 0), 1));
450 assert_eq!(m.step(RowPos::new(1, 0), -5), (RowPos::new(0, 0), -1));
451 assert_eq!(m.col_in_row(ByteOffset(6)), VisualCol(4));
452 assert_eq!(
453 m.byte_at_col(RowPos::new(2, 0), VisualCol(2)),
454 ByteOffset(5)
455 );
456 let rows: Vec<_> = m.rows_from(RowPos::new(1, 0), 10).collect();
457 assert_eq!(rows, vec![RowPos::new(1, 0), RowPos::new(2, 0)]);
458 });
459 }
460
461 #[test]
462 fn scrolling_clamps_and_round_trips_offsets() {
463 with_map(&"x\n".repeat(99), None, |m| {
464 assert_eq!(m.max_top(10), RowPos::new(90, 0));
465 let (row, frac) = m.scroll_by(RowPos::new(5, 0), 0.25, 2.5);
466 assert_eq!((row, frac), (RowPos::new(7, 0), 0.75));
467 assert_eq!(
468 m.scroll_by(RowPos::new(1, 0), 0.5, -3.0),
469 (RowPos::new(0, 0), 0.0)
470 );
471 assert_eq!(m.offset_of(row, frac), 7.75);
472 assert_eq!(m.pos_at_offset(7.75), (row, frac));
473 assert_eq!(m.pos_at_offset(-1.0), (RowPos::new(0, 0), 0.0));
474 });
475 }
476
477 #[test]
478 fn wrapped_rows_step_and_map_positions() {
479 with_map("x\n aaaa bbbb cccc\ny", Some(10), |m| {
482 assert_eq!(m.rows_in_line(1), 3);
483 let line = 2; assert_eq!(
485 m.row_range(RowPos::new(1, 1)),
486 ByteOffset(line + 7)..ByteOffset(line + 12)
487 );
488 assert_eq!(
489 m.row_of(ByteOffset(line + 7)),
490 RowPos::new(1, 1),
491 "wrap point"
492 );
493 assert_eq!(m.row_end(RowPos::new(1, 1)), ByteOffset(line + 11));
494 assert_eq!(m.row_end(RowPos::new(1, 2)), ByteOffset(line + 16));
495 assert_eq!(m.row_indent(RowPos::new(1, 2)), 2);
496 assert_eq!(m.col_in_row(ByteOffset(line + 8)), VisualCol(3));
499 assert_eq!(
500 m.byte_at_col(RowPos::new(1, 2), VisualCol(3)),
501 ByteOffset(line + 13)
502 );
503 assert_eq!(m.step(RowPos::new(0, 0), 3), (RowPos::new(1, 2), 3));
504 assert_eq!(m.step(RowPos::new(2, 0), -2), (RowPos::new(1, 1), -2));
505 assert_eq!(m.last_row(), RowPos::new(2, 0));
506 assert_eq!(m.max_top(3), RowPos::new(1, 1));
507 assert_eq!(m.clamp_row(RowPos::new(1, 9)), RowPos::new(1, 2));
508 });
509 }
510}