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