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) -> Self {
92 let tab_width = buffer.indent.width;
93 let padding = view.padding.as_deref();
94 Self {
95 doc: buffer.doc(),
96 revision: buffer.revision(),
97 folds: if padding.is_some() {
98 &NO_FOLDS
99 } else {
100 &view.folds
101 },
102 wrap: view.wrap_cols.filter(|_| padding.is_none()),
103 cache: &view.wrap_cache,
104 padding,
105 tab_width,
106 }
107 }
108
109 pub fn doc(&self) -> &'a Document {
110 self.doc
111 }
112
113 pub fn wraps(&self) -> bool {
115 self.wrap.is_some()
116 }
117
118 fn wrapped(&self, line: usize) -> Option<Arc<Wrapped>> {
120 let cols = self.wrap?;
121 let mut cache = self.cache.borrow_mut();
122 let key = (cols, self.tab_width);
123 if cache.key != Some(key) || cache.lines.len() > WRAP_CACHE_LINES {
124 cache.key = Some(key);
125 cache.lines.clear();
126 }
127 if let Some(e) = cache.lines.get_mut(&line) {
128 if e.revision == self.revision {
129 return Some(e.wrapped.clone());
130 }
131 let text = self.doc.line_text(line);
132 if text == e.text {
133 e.revision = self.revision;
134 return Some(e.wrapped.clone());
135 }
136 }
137 let text = self.doc.line_text(line);
138 let wrapped = Arc::new(wrap_line(&text, cols, self.tab_width));
139 let entry = WrapEntry {
140 revision: self.revision,
141 text,
142 wrapped: wrapped.clone(),
143 };
144 cache.lines.insert(line, entry);
145 Some(wrapped)
146 }
147
148 pub fn rows_in_line(&self, line: usize) -> usize {
150 if self.folds.is_hidden(line) {
151 0
152 } else {
153 self.pad_before(line) + self.text_rows(line) + self.pad_after(line)
154 }
155 }
156
157 fn text_rows(&self, line: usize) -> usize {
159 self.wrapped(line).map_or(1, |w| w.starts.len())
160 }
161
162 fn pad_before(&self, line: usize) -> usize {
163 self.padding
164 .map_or(0, |p| p.before.get(line).copied().unwrap_or(0))
165 }
166
167 fn pad_after(&self, line: usize) -> usize {
168 match self.padding {
169 Some(p) if line + 1 == self.doc.line_count() => p.after_last,
170 _ => 0,
171 }
172 }
173
174 pub fn text_sub(&self, row: RowPos) -> Option<usize> {
177 let sub = row.sub.checked_sub(self.pad_before(row.line))?;
178 (sub < self.text_rows(row.line)).then_some(sub)
179 }
180
181 pub fn is_filler(&self, row: RowPos) -> bool {
183 self.text_sub(row).is_none()
184 }
185
186 pub fn visible_line_count(&self) -> usize {
188 self.doc.line_count() - self.folds.hidden_count()
189 }
190
191 pub fn visible_line_at(&self, index: usize) -> usize {
193 self.folds.visible_line(index, self.doc.line_count())
194 }
195
196 fn shown_line(&self, line: usize) -> usize {
200 match self.folds.hidden_range(line) {
201 Some((start, end)) => start
202 .checked_sub(1)
203 .unwrap_or_else(|| (end + 1).min(self.doc.line_count() - 1)),
204 None => line,
205 }
206 }
207
208 fn visible_index(&self, line: usize) -> usize {
210 line - self.folds.hidden_before(line)
211 }
212
213 fn next_visible_line(&self, line: usize) -> Option<usize> {
214 let next = match self.folds.hidden_range(line + 1) {
215 Some((_, end)) => end + 1,
216 None => line + 1,
217 };
218 (next < self.doc.line_count()).then_some(next)
219 }
220
221 fn prev_visible_line(&self, line: usize) -> Option<usize> {
222 let prev = line.checked_sub(1)?;
223 match self.folds.hidden_range(prev) {
224 Some((start, _)) => start.checked_sub(1),
225 None => Some(prev),
226 }
227 }
228
229 pub fn first_row(&self) -> RowPos {
230 RowPos::new(self.shown_line(0), 0)
231 }
232
233 pub fn last_row(&self) -> RowPos {
234 let line = self.shown_line(self.doc.line_count() - 1);
235 RowPos::new(line, self.rows_in_line(line) - 1)
236 }
237
238 pub fn clamp_row(&self, row: RowPos) -> RowPos {
242 let line = self.shown_line(row.line.min(self.doc.line_count() - 1));
243 RowPos::new(line, row.sub.min(self.rows_in_line(line) - 1))
244 }
245
246 pub fn step(&self, row: RowPos, n: isize) -> (RowPos, isize) {
249 if !self.wraps() && self.padding.is_none() {
250 let from = self.visible_index(self.shown_line(row.line)) as isize;
252 let last = self.visible_line_count() as isize - 1;
253 let to = (from + n).clamp(0, last);
254 let line = self.folds.visible_line(to as usize, self.doc.line_count());
255 return (RowPos::new(line, 0), to - from);
256 }
257 let mut row = self.clamp_row(row);
258 let mut moved = 0;
259 while moved < n {
260 if row.sub + 1 < self.rows_in_line(row.line) {
261 row.sub += 1;
262 } else if let Some(line) = self.next_visible_line(row.line) {
263 row = RowPos::new(line, 0);
264 } else {
265 break;
266 }
267 moved += 1;
268 }
269 while moved > n {
270 if row.sub > 0 {
271 row.sub -= 1;
272 } else if let Some(line) = self.prev_visible_line(row.line) {
273 row = RowPos::new(line, self.rows_in_line(line) - 1);
274 } else {
275 break;
276 }
277 moved -= 1;
278 }
279 (row, moved)
280 }
281
282 pub fn advance(&self, row: RowPos, n: isize) -> RowPos {
284 self.step(row, n).0
285 }
286
287 pub fn row_range(&self, row: RowPos) -> Range<ByteOffset> {
290 let start = self.doc.line_start(row.line);
291 let end = self.doc.line_end(row.line);
292 let Some(sub) = self.text_sub(row) else {
293 let at = if row.sub < self.pad_before(row.line) {
295 start
296 } else {
297 end
298 };
299 return at..at;
300 };
301 match self.wrapped(row.line) {
302 None => start..end,
303 Some(w) => {
304 let at = |i: usize| ByteOffset(start.0 + w.starts[i]);
305 let sub = sub.min(w.starts.len() - 1);
306 at(sub)..w.starts.get(sub + 1).map_or(end, |_| at(sub + 1))
307 }
308 }
309 }
310
311 pub fn is_last_in_line(&self, row: RowPos) -> bool {
314 self.text_sub(row)
315 .is_none_or(|s| s + 1 >= self.text_rows(row.line))
316 }
317
318 pub fn row_end(&self, row: RowPos) -> ByteOffset {
322 let range = self.row_range(row);
323 if self.is_last_in_line(row) {
324 range.end
325 } else {
326 prev_grapheme_boundary(self.doc, range.end).max(range.start)
327 }
328 }
329
330 pub fn row_cols(&self, row: RowPos) -> (usize, usize) {
333 match (self.wrapped(row.line), self.text_sub(row)) {
334 (Some(w), Some(sub)) if sub > 0 => {
335 let sub = sub.min(w.starts.len() - 1);
336 (w.start_cols[sub], w.indent)
337 }
338 _ => (0, 0),
339 }
340 }
341
342 pub fn row_indent(&self, row: RowPos) -> usize {
344 self.row_cols(row).1
345 }
346
347 pub fn row_of(&self, pos: ByteOffset) -> RowPos {
350 let line = self.doc.line_of(pos);
351 let shown = self.shown_line(line);
352 if shown != line {
353 return RowPos::new(shown, self.rows_in_line(shown) - 1);
354 }
355 let sub = match self.wrapped(line) {
356 None => 0,
357 Some(w) => {
358 let off = pos.0 - self.doc.line_start(line).0;
359 w.starts.partition_point(|&s| s <= off) - 1
360 }
361 };
362 RowPos::new(line, self.pad_before(line) + sub)
363 }
364
365 pub fn col_in_row(&self, pos: ByteOffset) -> VisualCol {
369 let col = byte_to_visual_col(self.doc, pos, self.tab_width).0;
370 let (start_col, indent) = self.row_cols(self.row_of(pos));
371 VisualCol(indent + col - start_col)
372 }
373
374 pub fn byte_at_col(&self, row: RowPos, col: VisualCol) -> ByteOffset {
377 if self.is_filler(row) {
378 return self.row_range(row).start;
379 }
380 let (start_col, indent) = self.row_cols(row);
381 let target = if start_col > 0 || indent > 0 {
382 start_col + col.0.saturating_sub(indent)
383 } else {
384 col.0
385 };
386 let at = visual_col_to_byte(self.doc, row.line, VisualCol(target), self.tab_width);
387 at.clamp(self.row_range(row).start, self.row_end(row))
388 }
389
390 pub fn offset_of(&self, row: RowPos, frac: f32) -> f64 {
394 let rows = self.rows_in_line(row.line).max(1) as f64;
395 self.visible_index(self.shown_line(row.line)) as f64 + (row.sub as f64 + frac as f64) / rows
396 }
397
398 pub fn pos_at_offset(&self, offset: f64) -> (RowPos, f32) {
401 let max = self.visible_line_count().saturating_sub(1) as f64;
402 let offset = offset.clamp(0.0, max + 0.999_999);
403 let index = offset.floor();
404 let line = self
405 .folds
406 .visible_line(index as usize, self.doc.line_count());
407 let rows = self.rows_in_line(line).max(1) as f64;
408 let within = (offset - index) * rows;
409 let sub = (within.floor() as usize).min(self.rows_in_line(line).max(1) - 1);
410 (RowPos::new(line, sub), (within - sub as f64) as f32)
411 }
412
413 pub fn scroll_by(&self, row: RowPos, frac: f32, rows: f64) -> (RowPos, f32) {
417 let total = frac as f64 + rows;
418 let whole = total.floor();
419 let (to, moved) = self.step(row, whole as isize);
420 if moved as f64 != whole {
421 (to, 0.0)
423 } else {
424 (to, (total - whole) as f32)
425 }
426 }
427
428 pub fn max_top(&self, rows_full: usize) -> RowPos {
431 self.advance(self.last_row(), -(rows_full.saturating_sub(1) as isize))
432 }
433
434 pub fn rows_from(&self, top: RowPos, limit: usize) -> impl Iterator<Item = RowPos> + '_ {
436 let last = self.last_row();
437 std::iter::successors(Some(top), move |&r| (r < last).then(|| self.advance(r, 1)))
438 .take(limit)
439 }
440}
441
442#[cfg(test)]
443mod tests {
444 use super::*;
445 use crate::editor::Editor;
446
447 fn with_map(text: &str, wrap: Option<usize>, f: impl FnOnce(DisplayMap)) {
448 let mut ed = Editor::new();
449 let buffer = ed.add_buffer(text, Default::default());
450 let view = ed.add_view(buffer).unwrap();
451 ed.view_mut(view).unwrap().wrap_cols = wrap;
452 let v = ed.view(view).unwrap();
453 f(DisplayMap::new(ed.buffer(buffer).unwrap(), v));
454 }
455
456 #[test]
457 fn identity_rows_are_lines() {
458 with_map("a\nbb\n\tc", None, |m| {
459 assert_eq!(m.last_row(), RowPos::new(2, 0));
460 assert_eq!(m.row_of(ByteOffset(3)), RowPos::new(1, 0));
461 assert_eq!(m.row_range(RowPos::new(1, 0)), ByteOffset(2)..ByteOffset(4));
462 assert_eq!(m.step(RowPos::new(1, 0), 5), (RowPos::new(2, 0), 1));
463 assert_eq!(m.step(RowPos::new(1, 0), -5), (RowPos::new(0, 0), -1));
464 assert_eq!(m.col_in_row(ByteOffset(6)), VisualCol(4));
465 assert_eq!(
466 m.byte_at_col(RowPos::new(2, 0), VisualCol(2)),
467 ByteOffset(5)
468 );
469 let rows: Vec<_> = m.rows_from(RowPos::new(1, 0), 10).collect();
470 assert_eq!(rows, vec![RowPos::new(1, 0), RowPos::new(2, 0)]);
471 });
472 }
473
474 #[test]
475 fn scrolling_clamps_and_round_trips_offsets() {
476 with_map(&"x\n".repeat(99), None, |m| {
477 assert_eq!(m.max_top(10), RowPos::new(90, 0));
478 let (row, frac) = m.scroll_by(RowPos::new(5, 0), 0.25, 2.5);
479 assert_eq!((row, frac), (RowPos::new(7, 0), 0.75));
480 assert_eq!(
481 m.scroll_by(RowPos::new(1, 0), 0.5, -3.0),
482 (RowPos::new(0, 0), 0.0)
483 );
484 assert_eq!(m.offset_of(row, frac), 7.75);
485 assert_eq!(m.pos_at_offset(7.75), (row, frac));
486 assert_eq!(m.pos_at_offset(-1.0), (RowPos::new(0, 0), 0.0));
487 });
488 }
489
490 #[test]
491 fn wrapped_rows_step_and_map_positions() {
492 with_map("x\n aaaa bbbb cccc\ny", Some(10), |m| {
495 assert_eq!(m.rows_in_line(1), 3);
496 let line = 2; assert_eq!(
498 m.row_range(RowPos::new(1, 1)),
499 ByteOffset(line + 7)..ByteOffset(line + 12)
500 );
501 assert_eq!(
502 m.row_of(ByteOffset(line + 7)),
503 RowPos::new(1, 1),
504 "wrap point"
505 );
506 assert_eq!(m.row_end(RowPos::new(1, 1)), ByteOffset(line + 11));
507 assert_eq!(m.row_end(RowPos::new(1, 2)), ByteOffset(line + 16));
508 assert_eq!(m.row_indent(RowPos::new(1, 2)), 2);
509 assert_eq!(m.col_in_row(ByteOffset(line + 8)), VisualCol(3));
512 assert_eq!(
513 m.byte_at_col(RowPos::new(1, 2), VisualCol(3)),
514 ByteOffset(line + 13)
515 );
516 assert_eq!(m.step(RowPos::new(0, 0), 3), (RowPos::new(1, 2), 3));
517 assert_eq!(m.step(RowPos::new(2, 0), -2), (RowPos::new(1, 1), -2));
518 assert_eq!(m.last_row(), RowPos::new(2, 0));
519 assert_eq!(m.max_top(3), RowPos::new(1, 1));
520 assert_eq!(m.clamp_row(RowPos::new(1, 9)), RowPos::new(1, 2));
521 });
522 }
523}