revi_core/
window.rs

1/* windows.rs
2*/
3
4use crate::buffer::Buffer;
5use crate::line_number::LineNumbers;
6use crate::mode::Mode;
7use crate::position::Position;
8use crate::text_formater::format_screen;
9use std::cell::{Ref, RefCell, RefMut};
10use std::cmp::{max, min};
11use std::io::BufWriter;
12use std::rc::Rc;
13
14#[derive(Debug)]
15pub struct Window {
16    pub mode: Mode,
17    /// Size Of Window
18    pub dimensions: Position,
19    /// Location of window in Terminal
20    window_offset: Position,
21    /// Location of window in file
22    scroll_offset: Position,
23    /// Cursor Location in File/Window
24    cursor: Position,
25    /// Furthest from 0 the cursor has been.
26    max_cursor: Position,
27    /// Text File Data
28    buffer: Rc<RefCell<Buffer>>,
29    /// Line Number Type.
30    line_number_type: LineNumbers,
31    /// This needs to be removed
32    status_bar_state: bool,
33}
34
35impl Window {
36    pub fn new(width: u16, height: u16, buffer: Rc<RefCell<Buffer>>) -> Self {
37        Self {
38            mode: Mode::Normal,
39            dimensions: Position::new_u16(width, height),
40            window_offset: Position::default(),
41            scroll_offset: Position::default(),
42            cursor: Position::default(),
43            max_cursor: Position::default(),
44            buffer,
45            line_number_type: LineNumbers::None,
46            status_bar_state: false,
47        }
48    }
49
50    #[must_use]
51    pub fn with_position(mut self, pos: Position) -> Self {
52        self.window_offset = pos;
53        self
54    }
55
56    #[must_use]
57    pub fn with_line_numbers(mut self, line_number_type: LineNumbers) -> Self {
58        self.line_number_type = line_number_type;
59        self
60    }
61
62    #[must_use]
63    pub fn with_status_bar(mut self, state: bool) -> Self {
64        self.dimensions.sub_to_y(1);
65        self.status_bar_state = state;
66        self
67    }
68
69    pub fn set_buffer(&mut self, buffer: Rc<RefCell<Buffer>>) {
70        self.scroll_offset = Position::default();
71        self.cursor = Position::default();
72        self.max_cursor = Position::default();
73        self.buffer = buffer;
74    }
75
76    pub fn set_number(&mut self, number_type: LineNumbers) {
77        self.line_number_type = number_type;
78    }
79
80    #[must_use]
81    pub fn buffer(&self) -> Ref<Buffer> {
82        self.buffer.borrow()
83    }
84
85    #[must_use]
86    pub fn buffer_mut(&mut self) -> RefMut<Buffer> {
87        self.buffer.borrow_mut()
88    }
89
90    #[must_use]
91    pub fn dimensions(&self) -> Position {
92        self.dimensions
93    }
94
95    #[must_use]
96    pub fn position(&self) -> Position {
97        self.window_offset
98    }
99
100    #[must_use]
101    pub fn offset(&self) -> Position {
102        self.window_offset + Position::new(self.line_number_width(), 0)
103    }
104
105    pub fn set_cursor(&mut self, pos: Position) {
106        self.cursor = pos;
107    }
108
109    #[must_use]
110    pub fn height(&self) -> usize {
111        self.dimensions.as_usize_y()
112    }
113
114    #[must_use]
115    pub fn width(&self) -> usize {
116        self.dimensions.as_usize_x()
117    }
118
119    #[must_use]
120    pub fn text_width(&self) -> usize {
121        self.dimensions
122            .as_usize_x()
123            .saturating_sub(self.line_number_width())
124    }
125
126    #[must_use]
127    pub fn cursor_file(&self) -> Position {
128        self.cursor + self.scroll_offset
129    }
130
131    #[must_use]
132    pub fn cursor_screen(&self) -> Position {
133        self.cursor + self.offset()
134    }
135
136    pub fn scroll_down(&mut self, lines: usize) {
137        if lines + self.scroll_offset.as_usize_y() + self.cursor.as_usize_y()
138            < self.buffer.borrow().len_lines()
139        {
140            self.scroll_offset.add_to_y(lines);
141            self.adjust_cursor_x();
142        }
143    }
144
145    pub fn scroll_up(&mut self, lines: usize) {
146        self.scroll_offset.sub_to_y(lines);
147        self.adjust_cursor_x()
148    }
149
150    pub fn move_cursor_down(&mut self, lines: usize) {
151        if self.cursor.as_usize_y() >= self.height() - 1 {
152            self.scroll_down(lines);
153        } else if self.cursor_file().as_usize_y() < self.buffer.borrow().len_lines() {
154            self.cursor.add_to_y(lines);
155            self.cursor.set_x(self.max_cursor.as_usize_x());
156            self.adjust_cursor_x()
157        }
158    }
159
160    pub fn move_cursor_up(&mut self, lines: usize) {
161        if self.cursor.as_usize_y() == 0 {
162            self.scroll_up(lines);
163        } else {
164            self.cursor.sub_to_y(lines);
165            self.cursor.set_x(self.max_cursor.as_usize_x());
166            self.adjust_cursor_x()
167        }
168    }
169
170    pub fn adjust_cursor_x(&mut self) {
171        let line = self
172            .buffer
173            .borrow()
174            .line(self.cursor_file().as_usize_y())
175            .chars()
176            .collect::<String>();
177        let mut line_len = line.len();
178        if let Mode::Normal = self.mode {
179            if line.ends_with('\n') {
180                line_len = line_len.saturating_sub(2);
181            } else if self.buffer.borrow().len_lines() == self.cursor_file().as_usize_y() {
182                line_len = line_len.saturating_sub(1);
183            }
184        } else if let Mode::Insert = self.mode {
185            if line.ends_with('\n') {
186                line_len = line_len.saturating_sub(1);
187            }
188        }
189
190        self.cursor.set_x(min(line_len, self.cursor.as_usize_x()));
191    }
192
193    pub fn move_cursor_left(&mut self, cols: usize) {
194        if self.cursor.as_usize_x() == 0 {
195            self.scroll_left(cols);
196        } else {
197            self.cursor.set_x(if cols > self.cursor.as_usize_x() {
198                0
199            } else {
200                self.cursor.as_usize_x() - cols
201            });
202            self.max_cursor.set_x(self.cursor.as_usize_x());
203        }
204    }
205
206    pub fn scroll_left(&mut self, cols: usize) {
207        self.scroll_offset.sub_to_x(cols);
208    }
209
210    pub fn move_forward_by_word(&mut self) {
211        let pos = self.cursor + self.scroll_offset;
212        let buffer = self.buffer.borrow().next_jump_idx(&pos);
213        if let Some(i) = buffer {
214            self.cursor.set_x(i);
215            self.max_cursor.set_x(self.cursor.as_usize_x());
216        } else {
217            self.move_cursor_down(1);
218            self.first_char_in_line();
219        }
220    }
221
222    pub fn move_backward_by_word(&mut self) {
223        let pos = self.cursor + self.scroll_offset;
224        if pos == Position::new(0, 0) {
225            return;
226        }
227        let buffer = self.buffer.borrow().prev_jump_idx(&pos);
228        if let Some(i) = buffer {
229            self.cursor.set_x(i);
230            self.max_cursor.set_x(self.cursor.as_usize_x());
231        } else {
232            self.move_cursor_up(1);
233            self.end();
234            let pos = self.cursor + self.scroll_offset;
235            if let Some(i) = self.buffer.borrow().prev_jump_idx(&pos) {
236                self.cursor.set_x(i);
237                self.max_cursor.set_x(self.cursor.as_usize_x());
238            }
239        }
240    }
241
242    pub fn move_cursor_right(&mut self, cols: usize) {
243        if self.cursor.as_usize_x() >= self.text_width() - 1 {
244            self.scroll_right(cols)
245        } else {
246            self.cursor.add_to_x(cols);
247            self.max_cursor.set_x(self.cursor.as_usize_x());
248            self.adjust_cursor_x();
249        }
250    }
251
252    pub fn scroll_right(&mut self, cols: usize) {
253        self.scroll_offset.add_to_x(cols);
254        self.adjust_cursor_x()
255        // if cols + self.scroll_offset.as_usize_x() + self.cursor.as_usize_x()
256        //     < self
257        //         .buffer
258        //         .borrow()
259        //         .line_len(self.cursor_file().as_usize_y())
260        // {
261        //     self.scroll_offset.add_to_x(cols);
262        //     // self.adjust_cursor_x()
263        // }
264    }
265
266    pub fn insert_newline(&mut self) {
267        self.insert_char('\n');
268        self.move_cursor_down(1);
269        self.cursor.set_x(0);
270        self.scroll_offset.set_x(0);
271    }
272
273    pub fn first_char_in_line(&mut self) {
274        let y = (self.cursor + self.scroll_offset).as_usize_y();
275        for (i, c) in self.buffer.borrow().line(y).chars().enumerate() {
276            if c != ' ' {
277                self.cursor.set_x(i);
278                self.max_cursor.set_x(max(i, self.cursor.as_usize_x()));
279                break;
280            }
281        }
282    }
283
284    pub fn jump_to_first_line_buffer(&mut self) {
285        self.cursor.set_y(0);
286        self.scroll_offset.set_y(0);
287        self.adjust_cursor_x()
288    }
289
290    pub fn jump_to_last_line_buffer(&mut self) {
291        // Gets line count but screen is off by one so we subtract one.
292        let total_y = self.buffer.borrow().len_lines();
293        // Gets screen height but it also is off by one so we subtract one.
294        let screen_y = (self.height() - 1).min(total_y);
295        // Finds Y offset into file but it is off by one as well for indexing so we subtract one as
296        // well
297        let offset_y = total_y.saturating_sub(screen_y).saturating_sub(1);
298        self.cursor.set_y(screen_y);
299        self.scroll_offset.set_y(offset_y);
300        self.adjust_cursor_x()
301    }
302
303    pub fn backspace(&mut self) {
304        if self.cursor_file().as_u16() == (0, 0) {
305            return;
306        }
307
308        let line_index = self
309            .buffer
310            .borrow()
311            .line_to_char(self.cursor_file().as_usize_y());
312        let index = line_index + self.cursor_file().as_usize_x() - 1;
313        self.buffer.borrow_mut().remove(index..index + 1);
314
315        let new_line = self.buffer.borrow().char_to_line(index);
316        if new_line != self.cursor_file().as_usize_y() {
317            self.move_cursor_up(1);
318        }
319        let total = index - self.buffer.borrow().line_to_char(new_line);
320        let cursor = total.min(self.text_width().saturating_sub(1));
321        let offset = total.saturating_sub(cursor);
322        self.scroll_offset.set_x(offset);
323        self.cursor.set_x(cursor);
324    }
325
326    pub fn delete(&mut self) {
327        let index = self
328            .buffer
329            .borrow()
330            .line_to_char(self.cursor_file().as_usize_y())
331            + self.cursor_file().as_usize_x();
332        if index < self.buffer.borrow().len_chars() {
333            self.buffer.borrow_mut().remove(index..index + 1);
334        }
335        self.adjust_cursor_x();
336    }
337
338    pub fn insert_char(&mut self, c: char) {
339        let (x, y) = self.cursor_file().as_usize();
340        let line_index = self.buffer.borrow().line_to_char(y);
341        self.buffer.borrow_mut().insert_char(line_index + x, c);
342        self.move_cursor_right(1);
343    }
344
345    pub fn delete_line(&mut self) {
346        let y = self.cursor_file().as_usize_y();
347        let start_idx = self.buffer.borrow().line_to_char(y);
348        let end_idx = self.buffer.borrow().line_to_char(y + 1);
349
350        // Remove the line...
351        self.buffer.borrow_mut().remove(start_idx..end_idx);
352        self.adjust_cursor_x();
353    }
354
355    pub fn home(&mut self) {
356        self.cursor.set_x(0);
357        self.scroll_offset.set_x(0);
358        self.max_cursor.set_x(0);
359    }
360
361    pub fn end(&mut self) {
362        let y = self.cursor_file().as_usize_y();
363        let line_len = self.buffer.borrow().line_len(y);
364        let cursor = line_len.min(self.text_width() - 1);
365        let offset = if line_len >= self.text_width() - 1 {
366            line_len.saturating_sub(cursor)
367        } else {
368            0
369        };
370        self.cursor.set_x(cursor);
371        self.scroll_offset.set_x(offset);
372        self.max_cursor.set_x(self.cursor.as_usize_x());
373        self.adjust_cursor_x();
374    }
375
376    // This need to return a Result
377    pub fn save(&self) {
378        if let Some(filename) = self.buffer.borrow().name() {
379            let file =
380                std::fs::File::create(filename).expect("Problem opening the file for saving");
381
382            let buff = BufWriter::new(file);
383            self.buffer
384                .borrow_mut()
385                .write_to(buff)
386                .expect("Failed to write to file.");
387        }
388    }
389
390    #[must_use]
391    pub fn get_status_bar(&self) -> Option<((u16, u16), String)> {
392        // FIXME: I hate this so much
393        if self.status_bar_state {
394            let y = self.position().as_usize_y() + self.height();
395            let pos = Position::new(self.position().as_usize_x(), y);
396
397            let left = format!(
398                " {} | {}",
399                self.mode,
400                self.buffer
401                    .borrow()
402                    .name()
403                    .clone()
404                    .unwrap_or_else(|| "N/A".to_string())
405            );
406            let right = format!(
407                "file: {} | window: {} ",
408                self.cursor_file(),
409                self.cursor_screen(),
410            );
411            let middle = (0..(self.text_width().saturating_sub(left.len() + right.len())))
412                .map(|_| ' ')
413                .collect::<String>();
414            return Some((pos.as_u16(), format!("{}{}{}", left, middle, right)));
415        }
416        None
417    }
418
419    #[must_use]
420    pub fn get_line_number(&self) -> Option<((u16, u16), String)> {
421        // TODO: Pull this out of Window
422        if self.line_number_type != LineNumbers::None {
423            let scroll = self.scroll_offset.as_usize_y();
424            let len_lines = self.buffer.borrow().len_lines();
425            let cursor = self.cursor.as_usize_y();
426            let width = self.line_number_width();
427            return Some((
428                self.position().as_u16(),
429                self.line_number_type
430                    .lines(width, self.height(), scroll, cursor, len_lines),
431            ));
432        }
433        None
434    }
435
436    #[must_use]
437    pub fn get_text_feild(&self) -> Option<((u16, u16), String)> {
438        let top = self.scroll_offset.as_usize_y();
439        let bottom = self.dimensions.as_usize_y() + top;
440        let window = self.buffer.borrow().on_screen(top, bottom);
441        let formated_window = format_screen(
442            &window,
443            self.scroll_offset.as_usize_x(),
444            self.text_width(),
445            self.height(),
446        );
447        Some((self.offset().as_u16(), formated_window))
448    }
449}
450
451// Private Methods
452impl Window {
453    /// Width of `LineNumber` Area if populated.
454    fn line_number_width(&self) -> usize {
455        if self.line_number_type == LineNumbers::None {
456            return 0;
457        }
458        self.buffer.borrow().len_lines().to_string().len().max(3) + 2
459    }
460}