Skip to main content

gpui_base/input/editor/display_map/
display_map.rs

1/// DisplayMap: Public facade for Editor/Input display mapping.
2///
3/// This combines WrapMap and FoldMap to provide a unified API:
4/// - BufferPoint ↔ DisplayPoint conversion
5/// - Fold management (candidates, toggle, query)
6/// - Automatic projection updates on text/layout changes
7use std::ops::Range;
8
9use gpui::{App, Font, Pixels};
10use ropey::Rope;
11
12use super::fold_map::FoldMap;
13use super::folding::FoldRange;
14pub use super::text_wrapper::WrappingIndent;
15use super::text_wrapper::{LineItem, WrapDisplayPoint};
16use super::wrap_map::WrapMap;
17use super::{BufferPoint, DisplayPoint};
18use crate::input::Point as TreeSitterPoint;
19use crate::input::display_map::WrapPoint;
20use crate::input::rope_ext::RopeExt as _;
21
22/// DisplayMap is the main interface for Editor/Input coordinate mapping.
23///
24/// It manages the two-layer projection:
25/// 1. Buffer → Wrap (soft-wrapping)
26/// 2. Wrap → Display (folding)
27///
28/// Editor/Input only needs to work with BufferPoint and DisplayPoint.
29pub struct DisplayMap {
30    wrap_map: WrapMap,
31    fold_map: FoldMap,
32}
33
34impl DisplayMap {
35    pub fn new(font: Font, font_size: Pixels, wrap_width: Option<Pixels>) -> Self {
36        Self {
37            wrap_map: WrapMap::new(font, font_size, wrap_width),
38            fold_map: FoldMap::new(),
39        }
40    }
41
42    // ==================== Core Coordinate Mapping ====================
43
44    /// Convert buffer position to display position
45    pub fn buffer_pos_to_display_pos(&self, pos: BufferPoint) -> DisplayPoint {
46        // Buffer → Wrap
47        let wrap_pos = self.wrap_map.buffer_pos_to_wrap_pos(pos);
48
49        // Wrap → Display
50        if let Some(display_row) = self.fold_map.wrap_row_to_display_row(wrap_pos.row) {
51            DisplayPoint::new(display_row, wrap_pos.col)
52        } else {
53            // Cursor is in a folded region, find nearest visible row
54            let display_row = self.fold_map.nearest_visible_display_row(wrap_pos.row);
55            DisplayPoint::new(display_row, 0) // Column 0 at fold boundary
56        }
57    }
58
59    /// Convert display position to buffer position
60    pub fn display_pos_to_buffer_pos(&self, pos: DisplayPoint) -> BufferPoint {
61        // Display → Wrap
62        let wrap_row = self.fold_map.display_row_to_wrap_row(pos.row).unwrap_or(0);
63
64        // Wrap → Buffer
65        let wrap_pos = WrapPoint::new(wrap_row, pos.col);
66        self.wrap_map.wrap_pos_to_buffer_pos(wrap_pos)
67    }
68
69    /// Get total number of visible display rows
70    #[inline]
71    pub fn display_row_count(&self) -> usize {
72        self.fold_map.display_row_count()
73    }
74
75    /// Get the buffer line for a given display row
76    pub fn display_row_to_buffer_line(&self, display_row: usize) -> usize {
77        // Display → Wrap
78        let wrap_row = self
79            .fold_map
80            .display_row_to_wrap_row(display_row)
81            .unwrap_or(0);
82
83        // Wrap → Buffer line
84        self.wrap_map.wrap_row_to_buffer_line(wrap_row)
85    }
86
87    /// Get the display row range for a buffer line: [start, end)
88    /// Returns None if the buffer line is completely hidden
89    pub fn buffer_line_to_display_row_range(&self, line: usize) -> Option<Range<usize>> {
90        // Buffer line → Wrap row range
91        let wrap_row_range = self.wrap_map.buffer_line_to_wrap_row_range(line);
92
93        // Find first and last visible display rows in this range
94        let mut first_display_row = None;
95        let mut last_display_row = None;
96
97        for wrap_row in wrap_row_range {
98            if let Some(display_row) = self.fold_map.wrap_row_to_display_row(wrap_row) {
99                if first_display_row.is_none() {
100                    first_display_row = Some(display_row);
101                }
102                last_display_row = Some(display_row);
103            }
104        }
105
106        if let (Some(start), Some(end)) = (first_display_row, last_display_row) {
107            Some(start..end + 1)
108        } else {
109            None // Completely folded
110        }
111    }
112
113    /// Check if a buffer line is completely hidden
114    #[inline]
115    pub fn is_buffer_line_hidden(&self, line: usize) -> bool {
116        self.buffer_line_to_display_row_range(line).is_none()
117    }
118
119    /// First display row of a buffer line. If the line is fully folded, returns the
120    /// nearest visible display row.
121    pub fn buffer_line_to_display_row(&self, line: usize) -> usize {
122        match self.buffer_line_to_display_row_range(line) {
123            Some(range) => range.start,
124            None => {
125                let wrap_row = self.wrap_map.buffer_line_to_first_wrap_row(line);
126                self.fold_map.nearest_visible_display_row(wrap_row)
127            }
128        }
129    }
130
131    /// Set fold candidates (from tree-sitter/LSP)
132    pub fn set_fold_candidates(&mut self, candidates: Vec<FoldRange>) {
133        self.fold_map.set_candidates(candidates);
134        self.rebuild_fold_projection();
135    }
136
137    /// Set a fold at the given start_line (must be in candidates)
138    pub fn set_folded(&mut self, start_line: usize, folded: bool) {
139        self.fold_map.set_folded(start_line, folded);
140        self.rebuild_fold_projection();
141    }
142
143    /// Toggle fold at the given start_line
144    pub fn toggle_fold(&mut self, start_line: usize) {
145        self.fold_map.toggle_fold(start_line);
146        self.rebuild_fold_projection();
147    }
148
149    /// Check if a line is currently folded
150    #[inline]
151    pub fn is_folded_at(&self, start_line: usize) -> bool {
152        self.fold_map.is_folded_at(start_line)
153    }
154
155    /// Check if a line is a fold candidate
156    #[inline]
157    pub fn is_fold_candidate(&self, start_line: usize) -> bool {
158        self.fold_map.is_fold_candidate(start_line)
159    }
160
161    /// Get all currently folded ranges
162    #[inline]
163    pub fn folded_ranges(&self) -> &[FoldRange] {
164        self.fold_map.folded_ranges()
165    }
166
167    /// Clear all folds
168    pub fn clear_folds(&mut self) {
169        self.fold_map.clear_folds();
170        self.rebuild_fold_projection();
171    }
172
173    // ==================== Text and Layout Updates ====================
174
175    /// Adjust folds and candidates for a text edit before updating the wrap map.
176    ///
177    /// Must be called with the OLD text (before replacement) and the edit range/new_text
178    /// so we can compute which old lines were affected.
179    pub fn adjust_folds_for_edit(&mut self, old_text: &Rope, range: &Range<usize>, new_text: &str) {
180        if self.fold_map.folded_ranges().is_empty() && self.fold_map.fold_candidates().is_empty() {
181            return;
182        }
183
184        let edit_start_line = old_text.offset_to_point(range.start).row;
185        let edit_end_line = old_text.offset_to_point(range.end.min(old_text.len())).row;
186
187        let old_lines_in_range = edit_end_line.saturating_sub(edit_start_line);
188        let new_lines_in_range = new_text.chars().filter(|c| *c == '\n').count();
189        let line_delta = new_lines_in_range as isize - old_lines_in_range as isize;
190
191        self.fold_map
192            .adjust_folds_for_edit(edit_start_line, edit_end_line, line_delta);
193    }
194
195    /// Incrementally update fold candidates after a text edit.
196    ///
197    /// Extracts new fold candidates only within the edited byte range
198    /// and merges them with existing (already adjusted) candidates.
199    pub fn update_fold_candidates_for_edit(
200        &mut self,
201        extract_fold_ranges: impl FnOnce(Range<usize>, &Rope) -> Vec<FoldRange>,
202        edit_byte_range: Range<usize>,
203        new_text: &Rope,
204    ) {
205        let new_start_line = new_text.offset_to_point(edit_byte_range.start).row;
206        let new_end_line = new_text
207            .offset_to_point(edit_byte_range.end.min(new_text.len()))
208            .row;
209
210        let new_candidates = extract_fold_ranges(edit_byte_range, new_text);
211        self.fold_map
212            .merge_candidates_for_edit(new_start_line, new_end_line, new_candidates);
213    }
214
215    /// Update text (incremental or full)
216    pub fn on_text_changed(
217        &mut self,
218        changed_text: &Rope,
219        range: &Range<usize>,
220        new_text: &Rope,
221        cx: &mut App,
222    ) {
223        self.wrap_map
224            .on_text_changed(changed_text, range, new_text, cx);
225        self.rebuild_fold_projection();
226    }
227
228    /// Update layout parameters (wrap width or font)
229    pub(crate) fn set_inline_metrics(
230        &mut self,
231        metrics: std::rc::Rc<[(std::ops::Range<usize>, Pixels)]>,
232        cx: &mut App,
233    ) {
234        self.wrap_map.set_inline_metrics(metrics, cx);
235        self.rebuild_fold_projection();
236    }
237
238    pub fn on_layout_changed(&mut self, wrap_width: Option<Pixels>, cx: &mut App) {
239        self.wrap_map.on_layout_changed(wrap_width, cx);
240        self.rebuild_fold_projection();
241    }
242
243    /// Set the wrapping indent for continuation lines.
244    pub fn set_wrapping_indent(&mut self, wrapping_indent: WrappingIndent, cx: &mut App) {
245        self.wrap_map.set_wrapping_indent(wrapping_indent, cx);
246        self.rebuild_fold_projection();
247    }
248
249    /// Set the number of spaces per tab stop.
250    pub(crate) fn set_tab_size(&mut self, tab_size: usize, cx: &mut App) {
251        if self.wrap_map.set_tab_size(tab_size, cx) {
252            self.rebuild_fold_projection();
253        }
254    }
255
256    /// Set font parameters
257    pub fn set_font(&mut self, font: Font, font_size: Pixels, cx: &mut App) {
258        self.wrap_map.set_font(font, font_size, cx);
259        self.rebuild_fold_projection();
260    }
261
262    /// Ensure text is prepared (initializes wrapper if needed)
263    pub fn ensure_text_prepared(&mut self, text: &Rope, cx: &mut App) {
264        let did_initialize = self.wrap_map.ensure_text_prepared(text, cx);
265        if did_initialize {
266            self.rebuild_fold_projection();
267        }
268    }
269
270    /// Initialize with text
271    pub fn set_text(&mut self, text: &Rope, cx: &mut App) {
272        self.wrap_map.set_text(text, cx);
273        self.rebuild_fold_projection();
274    }
275
276    // ==================== Internal Helpers ====================
277
278    /// Rebuild fold projection after wrap_map or fold state changes
279    /// Only rebuilds if there are actually folded ranges
280    fn rebuild_fold_projection(&mut self) {
281        if !self.fold_map.folded_ranges().is_empty() {
282            self.fold_map.rebuild(&self.wrap_map);
283        } else {
284            // No active folds: identity mapping (wrap_row == display_row).
285            // Just update cached count so query methods work without Vec allocation.
286            self.fold_map
287                .mark_dirty_with_wrap_count(self.wrap_map.wrap_row_count());
288        }
289    }
290
291    // ==================== Wrap Display Point Operations ====================
292
293    /// Convert byte offset to wrap display point (with soft wrap info).
294    #[inline]
295    pub(crate) fn offset_to_wrap_display_point(&self, offset: usize) -> WrapDisplayPoint {
296        self.wrap_map.wrapper().offset_to_display_point(offset)
297    }
298
299    /// Like [`Self::offset_to_wrap_display_point`], but honours the caret's line-end affinity so
300    /// an offset on a soft wrap boundary resolves to the row the caret is drawn on.
301    #[inline]
302    pub(crate) fn offset_to_wrap_display_point_with_affinity(
303        &self,
304        offset: usize,
305        line_end_affinity: bool,
306    ) -> WrapDisplayPoint {
307        self.wrap_map
308            .wrapper()
309            .offset_to_display_point_with_affinity(offset, line_end_affinity)
310    }
311
312    /// Resolve the caret's visual row through both wrapping and folded regions.
313    pub(crate) fn offset_to_display_row(&self, offset: usize, line_end_affinity: bool) -> usize {
314        let point = self.offset_to_wrap_display_point_with_affinity(offset, line_end_affinity);
315        self.wrap_row_to_display_row(point.row)
316            .unwrap_or_else(|| self.nearest_visible_display_row(point.row))
317    }
318
319    /// Convert wrap display point to byte offset.
320    #[inline]
321    pub(crate) fn wrap_display_point_to_offset(&self, point: WrapDisplayPoint) -> usize {
322        self.wrap_map.wrapper().display_point_to_offset(point)
323    }
324
325    /// Convert wrap display point to TreeSitterPoint (buffer line/col).
326    #[inline]
327    pub(crate) fn wrap_display_point_to_point(&self, point: WrapDisplayPoint) -> TreeSitterPoint {
328        self.wrap_map.wrapper().display_point_to_point(point)
329    }
330
331    /// Convert a wrap row to a display row (skipping folded rows).
332    /// Returns None if the wrap row is folded.
333    #[inline]
334    pub fn wrap_row_to_display_row(&self, wrap_row: usize) -> Option<usize> {
335        self.fold_map.wrap_row_to_display_row(wrap_row)
336    }
337
338    /// Find the nearest visible display row for a given wrap row.
339    #[inline]
340    pub fn nearest_visible_display_row(&self, wrap_row: usize) -> usize {
341        self.fold_map.nearest_visible_display_row(wrap_row)
342    }
343
344    /// Convert a display row to a wrap row.
345    #[inline]
346    pub fn display_row_to_wrap_row(&self, display_row: usize) -> Option<usize> {
347        self.fold_map.display_row_to_wrap_row(display_row)
348    }
349
350    pub(crate) fn display_row_column_to_offset(&self, display_row: usize, column: usize) -> usize {
351        let wrap_row = self
352            .fold_map
353            .display_row_to_wrap_row(display_row)
354            .unwrap_or(0);
355        self.wrap_map
356            .wrapper()
357            .display_point_to_offset(WrapDisplayPoint::new(wrap_row, 0, column))
358    }
359
360    /// Get the longest row index (by byte length).
361    #[inline]
362    pub(crate) fn longest_row(&self) -> usize {
363        self.wrap_map.wrapper().longest_row()
364    }
365
366    // ==================== Access Methods ====================
367
368    /// Get the line item by buffer row index.
369    #[inline]
370    pub(crate) fn line(&self, row: usize) -> Option<&LineItem> {
371        self.wrap_map.line(row)
372    }
373
374    /// Get the rope text
375    #[inline]
376    pub fn text(&self) -> &Rope {
377        self.wrap_map.text()
378    }
379
380    /// Calculate how many wrap rows of a buffer line are visible (not folded)
381    #[inline]
382    pub fn visible_wrap_row_count_for_buffer_line(&self, line: usize) -> usize {
383        self.wrap_map
384            .visible_wrap_row_count_for_line(line, &self.fold_map)
385    }
386
387    /// Get the wrap row count (before folding)
388    #[inline]
389    pub fn wrap_row_count(&self) -> usize {
390        self.wrap_map.wrap_row_count()
391    }
392
393    /// Get the buffer line count (logical lines)
394    #[inline]
395    pub fn buffer_line_count(&self) -> usize {
396        self.wrap_map.buffer_line_count()
397    }
398}