makepad_code_editor/
layout.rs

1use {
2    crate::{
3        document::{DocumentLayout, IndentState},
4        inlays::{BlockInlay, InlineInlay},
5        selection::Affinity,
6        session::SessionLayout,
7        str::StrExt,
8        text::{Position, Text},
9        widgets::{BlockWidget, InlineWidget},
10        wrap::WrapData,
11        Token,
12    },
13    std::{cell::Ref, slice::Iter},
14};
15
16#[derive(Debug)]
17pub struct Layout<'a> {
18    pub text: Ref<'a, Text>,
19    pub document_layout: Ref<'a, DocumentLayout>,
20    pub session_layout: Ref<'a, SessionLayout>,
21}
22
23impl<'a> Layout<'a> {
24    pub fn as_text(&self) -> &Text {
25        &self.text
26    }
27
28    pub fn width(&self) -> f64 {
29        let mut width: f64 = 0.0;
30        for line in self.lines(0, self.as_text().as_lines().len()) {
31            width = width.max(line.width());
32        }
33        width
34    }
35
36    pub fn height(&self) -> f64 {
37        *self.session_layout.y.last().unwrap()
38    }
39
40    pub fn find_first_line_ending_after_y(&self, y: f64) -> usize {
41        match self.session_layout.y[..self.session_layout.y.len() - 1]
42            .binary_search_by(|current_y| current_y.partial_cmp(&y).unwrap())
43        {
44            Ok(line) => line,
45            Err(line) => line.saturating_sub(1),
46        }
47    }
48
49    pub fn find_first_line_starting_after_y(&self, y: f64) -> usize {
50        match self.session_layout.y[..self.session_layout.y.len() - 1]
51            .binary_search_by(|current_y| current_y.partial_cmp(&y).unwrap())
52        {
53            Ok(line) => line + 1,
54            Err(line) => line,
55        }
56    }
57
58    pub fn logical_to_normalized_position(
59        &self,
60        position: Position,
61        affinity: Affinity,
62    ) -> (f64, f64) {
63        let line = self.line(position.line_index);
64        let (row_index, column_index) =
65            line.logical_to_grid_position(position.byte_index, affinity);
66        let (x, y) = line.grid_to_normalized_position(row_index, column_index);
67        (x, line.y() + y)
68    }
69
70    pub fn line(&self, index: usize) -> Line<'_> {
71        Line {
72            y: self.session_layout.y.get(index).copied(),
73            column_count: self.session_layout.column_count[index],
74            fold: self.session_layout.fold_column[index],
75            scale: self.session_layout.scale[index],
76            text: &self.text.as_lines()[index],
77            indent_state: self.document_layout.indent_state[index],
78            tokens: &self.document_layout.tokens[index],
79            inlays: &self.document_layout.inline_inlays[index],
80            wrap_data: self.session_layout.wrap_data[index].as_ref(),
81        }
82    }
83
84    pub fn lines(&self, start: usize, end: usize) -> Lines<'_> {
85        Lines {
86            y: self.session_layout.y
87                [start.min(self.session_layout.y.len())..end.min(self.session_layout.y.len())]
88                .iter(),
89            column_count: self.session_layout.column_count[start..end].iter(),
90            fold: self.session_layout.fold_column[start..end].iter(),
91            scale: self.session_layout.scale[start..end].iter(),
92            text: self.text.as_lines()[start..end].iter(),
93            indent_state: self.document_layout.indent_state[start..end].iter(),
94            tokens: self.document_layout.tokens[start..end].iter(),
95            inline_inlays: self.document_layout.inline_inlays[start..end].iter(),
96            wrap_data: self.session_layout.wrap_data[start..end].iter(),
97        }
98    }
99
100    pub fn block_elements(&self, line_start: usize, line_end: usize) -> BlockElements<'_> {
101        let mut block_inlays = self.document_layout.block_inlays.iter();
102        while block_inlays
103            .as_slice()
104            .first()
105            .map_or(false, |&(position, _)| position < line_start)
106        {
107            block_inlays.next();
108        }
109        BlockElements {
110            lines: self.lines(line_start, line_end),
111            block_inlays,
112            position: line_start,
113        }
114    }
115}
116
117#[derive(Clone, Debug)]
118pub struct Lines<'a> {
119    y: Iter<'a, f64>,
120    column_count: Iter<'a, Option<usize>>,
121    fold: Iter<'a, usize>,
122    scale: Iter<'a, f64>,
123    text: Iter<'a, String>,
124    indent_state: Iter<'a, Option<IndentState>>,
125    tokens: Iter<'a, Vec<Token>>,
126    inline_inlays: Iter<'a, Vec<(usize, InlineInlay)>>,
127    wrap_data: Iter<'a, Option<WrapData>>,
128}
129
130impl<'a> Iterator for Lines<'a> {
131    type Item = Line<'a>;
132
133    fn next(&mut self) -> Option<Self::Item> {
134        let text = self.text.next()?;
135        Some(Line {
136            y: self.y.next().copied(),
137            column_count: *self.column_count.next().unwrap(),
138            fold: *self.fold.next().unwrap(),
139            scale: *self.scale.next().unwrap(),
140            text,
141            indent_state: *self.indent_state.next().unwrap(),
142            tokens: self.tokens.next().unwrap(),
143            inlays: self.inline_inlays.next().unwrap(),
144            wrap_data: self.wrap_data.next().unwrap().as_ref(),
145        })
146    }
147}
148
149#[derive(Clone, Copy, Debug, PartialEq)]
150pub struct Line<'a> {
151    pub y: Option<f64>,
152    pub column_count: Option<usize>,
153    pub fold: usize,
154    pub scale: f64,
155    pub text: &'a str,
156    pub indent_state: Option<IndentState>,
157    pub tokens: &'a [Token],
158    pub inlays: &'a [(usize, InlineInlay)],
159    pub wrap_data: Option<&'a WrapData>,
160}
161
162impl<'a> Line<'a> {
163    pub fn y(&self) -> f64 {
164        self.y.unwrap()
165    }
166
167    pub fn row_count(&self) -> usize {
168        self.wrap_data.unwrap().wraps.len() + 1
169    }
170
171    pub fn column_count(&self) -> usize {
172        self.column_count.unwrap()
173    }
174
175    pub fn width(&self) -> f64 {
176        let mut width: f64 = 0.0;
177        for row_index in 0..self.row_count() {
178            let (x, _) = self.grid_to_normalized_position(row_index, self.column_count());
179            width = width.max(x);
180        }
181        width
182    }
183
184    pub fn height(&self) -> f64 {
185        self.row_count() as f64 * self.scale
186    }
187
188    pub fn logical_to_grid_position(
189        &self,
190        byte_index: usize,
191        affinity: Affinity,
192    ) -> (usize, usize) {
193        let mut current_byte_index = 0;
194        let mut current_row_index = 0;
195        let mut current_column_index = 0;
196        if current_byte_index == byte_index && affinity == Affinity::Before {
197            return (current_row_index, current_column_index);
198        }
199        for element in self.wrapped_elements() {
200            match element {
201                WrappedElement::Text {
202                    is_inlay: false,
203                    text,
204                } => {
205                    for grapheme in text.graphemes() {
206                        if current_byte_index == byte_index && affinity == Affinity::After {
207                            return (current_row_index, current_column_index);
208                        }
209                        current_byte_index += grapheme.len();
210                        current_column_index += grapheme.column_count();
211                        if current_byte_index == byte_index && affinity == Affinity::Before {
212                            return (current_row_index, current_column_index);
213                        }
214                    }
215                }
216                WrappedElement::Text {
217                    is_inlay: true,
218                    text,
219                } => {
220                    current_column_index += text.column_count();
221                }
222                WrappedElement::Widget(widget) => {
223                    current_column_index += widget.column_count;
224                }
225                WrappedElement::Wrap => {
226                    current_row_index += 1;
227                    current_column_index = self.wrap_indent_column_count();
228                }
229            }
230        }
231        if current_byte_index == byte_index && affinity == Affinity::After {
232            return (current_row_index, current_column_index);
233        }
234        panic!()
235    }
236
237    pub fn grid_to_logical_position(
238        &self,
239        row_index: usize,
240        column_index: usize,
241    ) -> (usize, Affinity) {
242        let mut current_row_index = 0;
243        let mut current_column_index = 0;
244        let mut current_byte_index = 0;
245        for element in self.wrapped_elements() {
246            match element {
247                WrappedElement::Text {
248                    is_inlay: false,
249                    text,
250                } => {
251                    for grapheme in text.graphemes() {
252                        let next_column = current_column_index + grapheme.column_count();
253                        if current_row_index == row_index
254                            && (current_column_index..next_column).contains(&column_index)
255                        {
256                            return (current_byte_index, Affinity::After);
257                        }
258                        current_byte_index += grapheme.len();
259                        current_column_index = next_column;
260                    }
261                }
262                WrappedElement::Text {
263                    is_inlay: true,
264                    text,
265                } => {
266                    let next_column = current_column_index + text.column_count();
267                    if current_row_index == row_index
268                        && (current_column_index..next_column).contains(&column_index)
269                    {
270                        return (current_byte_index, Affinity::Before);
271                    }
272                    current_column_index = next_column;
273                }
274                WrappedElement::Widget(widget) => {
275                    current_column_index += widget.column_count;
276                }
277                WrappedElement::Wrap => {
278                    if current_row_index == row_index {
279                        return (current_byte_index, Affinity::Before);
280                    }
281                    current_row_index += 1;
282                    current_column_index = self.wrap_indent_column_count();
283                }
284            }
285        }
286        if current_row_index == row_index {
287            return (current_byte_index, Affinity::After);
288        }
289        panic!()
290    }
291
292    pub fn grid_to_normalized_position(&self, row_index: usize, column_index: usize) -> (f64, f64) {
293        let before_fold = column_index.min(self.fold);
294        let after_fold = column_index - before_fold;
295        (
296            before_fold as f64 + after_fold as f64 * self.scale,
297            row_index as f64 * self.scale,
298        )
299    }
300
301    pub fn fold(&self) -> usize {
302        self.fold
303    }
304
305    pub fn scale(&self) -> f64 {
306        self.scale
307    }
308
309    pub fn wrap_indent_column_count(self) -> usize {
310        self.wrap_data.unwrap().indent_column_count
311    }
312
313    pub fn text(&self) -> &str {
314        self.text
315    }
316
317    pub fn indent_column_count(&self) -> usize {
318        match self.indent_state.unwrap() {
319            IndentState::Empty(indent_column_count) => indent_column_count,
320            IndentState::NonEmpty(indent_column_count, _) => indent_column_count,
321        }
322    }
323
324    pub fn tokens(&self) -> &[Token] {
325        self.tokens
326    }
327
328    pub fn inline_elements(&self) -> InlineElements<'a> {
329        InlineElements {
330            text: self.text,
331            inlays: self.inlays.iter(),
332            position: 0,
333        }
334    }
335
336    pub fn wrapped_elements(&self) -> WrappedElements<'a> {
337        let mut elements = self.inline_elements();
338        WrappedElements {
339            element: elements.next(),
340            elements,
341            wraps: self.wrap_data.unwrap().wraps.iter(),
342            position: 0,
343        }
344    }
345}
346
347#[derive(Clone, Debug)]
348pub struct InlineElements<'a> {
349    text: &'a str,
350    inlays: Iter<'a, (usize, InlineInlay)>,
351    position: usize,
352}
353
354impl<'a> Iterator for InlineElements<'a> {
355    type Item = InlineElement<'a>;
356
357    fn next(&mut self) -> Option<Self::Item> {
358        if self
359            .inlays
360            .as_slice()
361            .first()
362            .map_or(false, |&(position, _)| position == self.position)
363        {
364            let (_, inline_inlay) = self.inlays.next().unwrap();
365            return Some(match *inline_inlay {
366                InlineInlay::Text(ref text) => InlineElement::Text {
367                    is_inlay: true,
368                    text,
369                },
370                InlineInlay::Widget(widget) => InlineElement::Widget(widget),
371            });
372        }
373        if self.text.is_empty() {
374            return None;
375        }
376        let mut len: usize = self.text.len();
377        if let Some(&(position, _)) = self.inlays.as_slice().first() {
378            len = len.min(position - self.position);
379        }
380        let (text_0, text_1) = self.text.split_at(len);
381        self.text = text_1;
382        self.position += text_0.len();
383        Some(InlineElement::Text {
384            is_inlay: false,
385            text: text_0,
386        })
387    }
388}
389
390#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
391pub enum InlineElement<'a> {
392    Text { is_inlay: bool, text: &'a str },
393    Widget(InlineWidget),
394}
395
396#[derive(Clone, Debug)]
397pub struct WrappedElements<'a> {
398    element: Option<InlineElement<'a>>,
399    elements: InlineElements<'a>,
400    wraps: Iter<'a, usize>,
401    position: usize,
402}
403
404impl<'a> Iterator for WrappedElements<'a> {
405    type Item = WrappedElement<'a>;
406
407    fn next(&mut self) -> Option<Self::Item> {
408        if self
409            .wraps
410            .as_slice()
411            .first()
412            .map_or(false, |&position| position == self.position)
413        {
414            self.wraps.next();
415            return Some(WrappedElement::Wrap);
416        }
417        Some(match self.element.take()? {
418            InlineElement::Text { is_inlay, text } => {
419                let mut len: usize = text.len();
420                if let Some(&position) = self.wraps.as_slice().first() {
421                    len = len.min(position - self.position);
422                }
423                let text = if len < text.len() {
424                    let (text_0, text_1) = text.split_at(len);
425                    self.element = Some(InlineElement::Text {
426                        is_inlay,
427                        text: text_1,
428                    });
429                    text_0
430                } else {
431                    self.element = self.elements.next();
432                    text
433                };
434                self.position += text.len();
435                WrappedElement::Text { is_inlay, text }
436            }
437            InlineElement::Widget(widget) => {
438                self.position += 1;
439                WrappedElement::Widget(widget)
440            }
441        })
442    }
443}
444
445#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
446pub enum WrappedElement<'a> {
447    Text { is_inlay: bool, text: &'a str },
448    Widget(InlineWidget),
449    Wrap,
450}
451
452#[derive(Clone, Debug)]
453pub struct BlockElements<'a> {
454    lines: Lines<'a>,
455    block_inlays: Iter<'a, (usize, BlockInlay)>,
456    position: usize,
457}
458
459impl<'a> Iterator for BlockElements<'a> {
460    type Item = BlockElement<'a>;
461
462    fn next(&mut self) -> Option<Self::Item> {
463        if self
464            .block_inlays
465            .as_slice()
466            .first()
467            .map_or(false, |&(line, _)| line == self.position)
468        {
469            let (_, block_inlay) = self.block_inlays.next().unwrap();
470            return Some(match *block_inlay {
471                BlockInlay::Widget(widget) => BlockElement::Widget(widget),
472            });
473        }
474        let line = self.lines.next()?;
475        self.position += 1;
476        Some(BlockElement::Line {
477            is_inlay: false,
478            line,
479        })
480    }
481}
482
483#[derive(Clone, Copy, Debug, PartialEq)]
484pub enum BlockElement<'a> {
485    Line { is_inlay: bool, line: Line<'a> },
486    Widget(BlockWidget),
487}