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 font parameters
250    pub fn set_font(&mut self, font: Font, font_size: Pixels, cx: &mut App) {
251        self.wrap_map.set_font(font, font_size, cx);
252        self.rebuild_fold_projection();
253    }
254
255    /// Ensure text is prepared (initializes wrapper if needed)
256    pub fn ensure_text_prepared(&mut self, text: &Rope, cx: &mut App) {
257        let did_initialize = self.wrap_map.ensure_text_prepared(text, cx);
258        if did_initialize {
259            self.rebuild_fold_projection();
260        }
261    }
262
263    /// Initialize with text
264    pub fn set_text(&mut self, text: &Rope, cx: &mut App) {
265        self.wrap_map.set_text(text, cx);
266        self.rebuild_fold_projection();
267    }
268
269    // ==================== Internal Helpers ====================
270
271    /// Rebuild fold projection after wrap_map or fold state changes
272    /// Only rebuilds if there are actually folded ranges
273    fn rebuild_fold_projection(&mut self) {
274        if !self.fold_map.folded_ranges().is_empty() {
275            self.fold_map.rebuild(&self.wrap_map);
276        } else {
277            // No active folds: identity mapping (wrap_row == display_row).
278            // Just update cached count so query methods work without Vec allocation.
279            self.fold_map
280                .mark_dirty_with_wrap_count(self.wrap_map.wrap_row_count());
281        }
282    }
283
284    // ==================== Wrap Display Point Operations ====================
285
286    /// Convert byte offset to wrap display point (with soft wrap info).
287    #[inline]
288    pub(crate) fn offset_to_wrap_display_point(&self, offset: usize) -> WrapDisplayPoint {
289        self.wrap_map.wrapper().offset_to_display_point(offset)
290    }
291
292    /// Like [`Self::offset_to_wrap_display_point`], but honours the caret's line-end affinity so
293    /// an offset on a soft wrap boundary resolves to the row the caret is drawn on.
294    #[inline]
295    pub(crate) fn offset_to_wrap_display_point_with_affinity(
296        &self,
297        offset: usize,
298        line_end_affinity: bool,
299    ) -> WrapDisplayPoint {
300        self.wrap_map
301            .wrapper()
302            .offset_to_display_point_with_affinity(offset, line_end_affinity)
303    }
304
305    /// Convert wrap display point to byte offset.
306    #[inline]
307    pub(crate) fn wrap_display_point_to_offset(&self, point: WrapDisplayPoint) -> usize {
308        self.wrap_map.wrapper().display_point_to_offset(point)
309    }
310
311    /// Convert wrap display point to TreeSitterPoint (buffer line/col).
312    #[inline]
313    pub(crate) fn wrap_display_point_to_point(&self, point: WrapDisplayPoint) -> TreeSitterPoint {
314        self.wrap_map.wrapper().display_point_to_point(point)
315    }
316
317    /// Convert a wrap row to a display row (skipping folded rows).
318    /// Returns None if the wrap row is folded.
319    #[inline]
320    pub fn wrap_row_to_display_row(&self, wrap_row: usize) -> Option<usize> {
321        self.fold_map.wrap_row_to_display_row(wrap_row)
322    }
323
324    /// Find the nearest visible display row for a given wrap row.
325    #[inline]
326    pub fn nearest_visible_display_row(&self, wrap_row: usize) -> usize {
327        self.fold_map.nearest_visible_display_row(wrap_row)
328    }
329
330    /// Convert a display row to a wrap row.
331    #[inline]
332    pub fn display_row_to_wrap_row(&self, display_row: usize) -> Option<usize> {
333        self.fold_map.display_row_to_wrap_row(display_row)
334    }
335
336    pub(crate) fn display_row_column_to_offset(&self, display_row: usize, column: usize) -> usize {
337        let wrap_row = self
338            .fold_map
339            .display_row_to_wrap_row(display_row)
340            .unwrap_or(0);
341        self.wrap_map
342            .wrapper()
343            .display_point_to_offset(WrapDisplayPoint::new(wrap_row, 0, column))
344    }
345
346    /// Get the longest row index (by byte length).
347    #[inline]
348    pub(crate) fn longest_row(&self) -> usize {
349        self.wrap_map.wrapper().longest_row()
350    }
351
352    // ==================== Access Methods ====================
353
354    /// Get the line item by buffer row index.
355    #[inline]
356    pub(crate) fn line(&self, row: usize) -> Option<&LineItem> {
357        self.wrap_map.line(row)
358    }
359
360    /// Get the rope text
361    #[inline]
362    pub fn text(&self) -> &Rope {
363        self.wrap_map.text()
364    }
365
366    /// Calculate how many wrap rows of a buffer line are visible (not folded)
367    #[inline]
368    pub fn visible_wrap_row_count_for_buffer_line(&self, line: usize) -> usize {
369        self.wrap_map
370            .visible_wrap_row_count_for_line(line, &self.fold_map)
371    }
372
373    /// Get the wrap row count (before folding)
374    #[inline]
375    pub fn wrap_row_count(&self) -> usize {
376        self.wrap_map.wrap_row_count()
377    }
378
379    /// Get the buffer line count (logical lines)
380    #[inline]
381    pub fn buffer_line_count(&self) -> usize {
382        self.wrap_map.buffer_line_count()
383    }
384}