nvim_gtk/ui_model/
model_layout.rs

1use std::cmp::max;
2use std::rc::Rc;
3
4use unicode_width::UnicodeWidthStr;
5
6use crate::highlight::Highlight;
7use crate::ui_model::UiModel;
8
9pub struct ModelLayout {
10    pub model: UiModel,
11    rows_filled: usize,
12    cols_filled: usize,
13    lines: Vec<Vec<(Rc<Highlight>, Vec<String>)>>,
14}
15
16impl ModelLayout {
17    const ROWS_STEP: usize = 10;
18
19    pub fn new(columns: u64) -> Self {
20        ModelLayout {
21            model: UiModel::new(ModelLayout::ROWS_STEP as u64, columns),
22            rows_filled: 0,
23            cols_filled: 0,
24            lines: Vec::new(),
25        }
26    }
27
28    pub fn layout_append(&mut self, mut lines: Vec<Vec<(Rc<Highlight>, Vec<String>)>>) {
29        let rows_filled = self.rows_filled;
30        let take_from = self.lines.len();
31
32        self.lines.append(&mut lines);
33
34        self.layout_replace(rows_filled, take_from);
35    }
36
37    pub fn layout(&mut self, lines: Vec<Vec<(Rc<Highlight>, Vec<String>)>>) {
38        self.lines = lines;
39        self.layout_replace(0, 0);
40    }
41
42    pub fn set_cursor(&mut self, col: usize) {
43        let row = if self.rows_filled > 0 {
44            self.rows_filled - 1
45        } else {
46            0
47        };
48
49        self.model.set_cursor(row, col);
50    }
51
52    pub fn size(&self) -> (usize, usize) {
53        (
54            max(self.cols_filled, self.model.get_cursor().1 + 1),
55            self.rows_filled,
56        )
57    }
58
59    fn check_model_size(&mut self, rows: usize) {
60        if rows > self.model.rows {
61            let model_cols = self.model.columns;
62            let model_rows = ((rows / (ModelLayout::ROWS_STEP + 1)) + 1) * ModelLayout::ROWS_STEP;
63            let (cur_row, cur_col) = self.model.get_cursor();
64
65            let mut model = UiModel::new(model_rows as u64, model_cols as u64);
66            self.model.swap_rows(&mut model, self.rows_filled - 1);
67            model.set_cursor(cur_row, cur_col);
68            self.model = model;
69        }
70    }
71
72    pub fn insert_char(&mut self, ch: String, shift: bool, hl: Rc<Highlight>) {
73        if ch.is_empty() {
74            return;
75        }
76
77        let (row, col) = self.model.get_cursor();
78
79        if shift {
80            self.insert_into_lines(ch);
81            self.layout_replace(0, 0);
82        } else {
83            self.model.put_one(row, col, &ch, false, hl);
84        }
85    }
86
87    fn insert_into_lines(&mut self, ch: String) {
88        let line = &mut self.lines[0];
89
90        let cur_col = self.model.cur_col;
91
92        let mut col_idx = 0;
93        for &mut (_, ref mut chars) in line {
94            if cur_col < col_idx + chars.len() {
95                let col_sub_idx = cur_col - col_idx;
96                chars.insert(col_sub_idx, ch);
97                break;
98            } else {
99                col_idx += chars.len();
100            }
101        }
102    }
103
104    /// Wrap all lines into model
105    ///
106    /// returns actual width
107    fn layout_replace(&mut self, row_offset: usize, take_from: usize) {
108        let rows = ModelLayout::count_lines(&self.lines[take_from..], self.model.columns);
109
110        self.check_model_size(rows + row_offset);
111        self.rows_filled = rows + row_offset;
112
113        let lines = &self.lines[take_from..];
114
115        let mut max_col_idx = 0;
116        let mut col_idx = 0;
117        let mut row_idx = row_offset;
118        for content in lines {
119            for &(ref hl, ref ch_list) in content {
120                for ch in ch_list {
121                    let ch_width = max(1, ch.width());
122
123                    if col_idx + ch_width > self.model.columns {
124                        col_idx = 0;
125                        row_idx += 1;
126                    }
127
128                    self.model.put_one(row_idx, col_idx, ch, false, hl.clone());
129                    if ch_width > 1 {
130                        self.model.put_one(row_idx, col_idx, "", true, hl.clone());
131                    }
132
133                    if max_col_idx < col_idx {
134                        max_col_idx = col_idx + ch_width - 1;
135                    }
136
137                    col_idx += ch_width;
138                }
139
140                if col_idx < self.model.columns {
141                    self.model.model[row_idx].clear(
142                        col_idx,
143                        self.model.columns - 1,
144                        &Rc::new(Highlight::new()),
145                    );
146                }
147            }
148            col_idx = 0;
149            row_idx += 1;
150        }
151
152        if self.rows_filled == 1 {
153            self.cols_filled = max_col_idx + 1;
154        } else {
155            self.cols_filled = max(self.cols_filled, max_col_idx + 1);
156        }
157    }
158
159    fn count_lines(lines: &[Vec<(Rc<Highlight>, Vec<String>)>], max_columns: usize) -> usize {
160        let mut row_count = 0;
161
162        for line in lines {
163            let len: usize = line.iter().map(|c| c.1.len()).sum();
164            row_count += len / (max_columns + 1) + 1;
165        }
166
167        row_count
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174
175    #[test]
176    fn test_count_lines() {
177        let lines = vec![vec![(Rc::new(Highlight::new()), vec!["a".to_owned(); 5])]];
178
179        let rows = ModelLayout::count_lines(&lines, 4);
180        assert_eq!(2, rows);
181    }
182
183    #[test]
184    fn test_resize() {
185        let lines = vec![
186            vec![(Rc::new(Highlight::new()), vec!["a".to_owned(); 5])];
187            ModelLayout::ROWS_STEP
188        ];
189        let mut model = ModelLayout::new(5);
190
191        model.layout(lines.clone());
192        let (cols, rows) = model.size();
193        assert_eq!(5, cols);
194        assert_eq!(ModelLayout::ROWS_STEP, rows);
195
196        model.layout_append(lines);
197        let (cols, rows) = model.size();
198        assert_eq!(5, cols);
199        assert_eq!(ModelLayout::ROWS_STEP * 2, rows);
200        assert_eq!(ModelLayout::ROWS_STEP * 2, model.model.rows);
201    }
202
203    #[test]
204    fn test_cols_filled() {
205        let lines = vec![vec![(Rc::new(Highlight::new()), vec!["a".to_owned(); 3])]; 1];
206        let mut model = ModelLayout::new(5);
207
208        model.layout(lines);
209        // cursor is not moved by newgrid api
210        // so set it manual
211        model.set_cursor(3);
212        let (cols, _) = model.size();
213        assert_eq!(4, cols); // size is 3 and 4 - is with cursor position
214
215        let lines = vec![vec![(Rc::new(Highlight::new()), vec!["a".to_owned(); 2])]; 1];
216
217        model.layout_append(lines);
218        model.set_cursor(2);
219        let (cols, _) = model.size();
220        assert_eq!(3, cols);
221    }
222
223    #[test]
224    fn test_insert_shift() {
225        let lines = vec![vec![(Rc::new(Highlight::new()), vec!["a".to_owned(); 3])]; 1];
226        let mut model = ModelLayout::new(5);
227        model.layout(lines);
228        model.set_cursor(1);
229
230        model.insert_char("b".to_owned(), true, Rc::new(Highlight::new()));
231
232        let (cols, _) = model.size();
233        assert_eq!(4, cols);
234        assert_eq!("b", model.model.model()[0].line[1].ch);
235    }
236
237    #[test]
238    fn test_insert_no_shift() {
239        let lines = vec![vec![(Rc::new(Highlight::new()), vec!["a".to_owned(); 3])]; 1];
240        let mut model = ModelLayout::new(5);
241        model.layout(lines);
242        model.set_cursor(1);
243
244        model.insert_char("b".to_owned(), false, Rc::new(Highlight::new()));
245
246        let (cols, _) = model.size();
247        assert_eq!(3, cols);
248        assert_eq!("b", model.model.model()[0].line[1].ch);
249    }
250
251    #[test]
252    fn test_double_width() {
253        let lines = vec![vec![(Rc::new(Highlight::new()), vec!["あ".to_owned(); 3])]; 1];
254        let mut model = ModelLayout::new(7);
255        model.layout(lines);
256        model.set_cursor(1);
257
258        let (cols, rows) = model.size();
259        assert_eq!(1, rows);
260        assert_eq!(6, cols);
261    }
262}