1use 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 pub dimensions: Position,
19 window_offset: Position,
21 scroll_offset: Position,
23 cursor: Position,
25 max_cursor: Position,
27 buffer: Rc<RefCell<Buffer>>,
29 line_number_type: LineNumbers,
31 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 }
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 let total_y = self.buffer.borrow().len_lines();
293 let screen_y = (self.height() - 1).min(total_y);
295 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 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 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 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 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
451impl Window {
453 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}