1#![allow(clippy::cast_precision_loss)]
2#![allow(clippy::cast_possible_truncation)]
3#![allow(clippy::cast_sign_loss)]
4
5pub mod render;
6pub mod render_buffer;
7
8use std::{cmp::Ordering, collections::HashMap, mem};
9
10use crossterm::{
11 ExecutableCommand,
12 event::{self, Event, EventStream, KeyCode, KeyEvent, KeyModifiers},
13 terminal,
14};
15use futures::{StreamExt, future::FutureExt, select};
16use serde::{Deserialize, Serialize};
17use unicode_segmentation::UnicodeSegmentation;
18
19use crate::{
20 action::Action,
21 buffer::Buffer,
22 color::Color,
23 command,
24 config::{Config, KeyAction},
25 debug,
26 editor::render_buffer::RenderBuffer,
27 highlighter::Highlighter,
28 log,
29 theme::{Style, Theme},
30 unicode,
31 unicode::{byte_to_char, char_to_byte, next_grapheme_boundary, prev_grapheme_boundary},
32 window_manager::WindowManager,
33};
34
35#[allow(unused)]
36pub enum GoToLinePosition {
37 Top,
38 Center,
39 Bottom,
40}
41
42#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
43pub enum Mode {
44 Normal,
45 Insert,
46 Command,
47 Visual,
48 Replace,
49}
50
51#[derive(Debug)]
52pub struct StyleInfo {
53 pub start: usize,
54 pub end: usize,
55 pub style: Style,
56}
57
58impl StyleInfo {
59 #[must_use]
60 pub fn contains(&self, pos: usize) -> bool {
61 pos >= self.start && pos < self.end
62 }
63}
64
65#[derive(Debug, Clone, Copy)]
66pub struct Point {
67 pub x: usize,
68 pub y: usize,
69}
70
71impl Point {
72 #[must_use]
73 pub fn new(x: usize, y: usize) -> Self {
74 Self { x, y }
75 }
76}
77
78impl PartialEq for Point {
79 fn eq(&self, other: &Self) -> bool {
80 self.x == other.x && self.y == other.y
81 }
82}
83
84impl PartialOrd for Point {
85 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
86 match self.y.cmp(&other.y) {
87 Ordering::Equal => self.x.partial_cmp(&other.x),
88 ordering => Some(ordering),
89 }
90 }
91}
92
93pub struct Editor {
94 config: Config,
95 pub theme: Theme,
96 highlighter: Highlighter,
97 buffers: Vec<Buffer>,
98 current_buffer_index: usize,
99 size: (u16, u16),
100 window_manager: WindowManager,
101 stdout: std::io::Stdout,
102 vtop: usize,
103 vleft: usize,
104 cursor_x: usize,
105 cursor_y: usize,
106 vx: usize,
107 mode: Mode,
108 waiting_command: Option<String>,
109 command: String,
110 waiting_key_action: Option<KeyAction>,
111 undoable_actions: Vec<Action>,
112 insert_undo_actions: Vec<Action>,
113 last_error: Option<String>,
114}
115
116impl Editor {
117 pub fn with_size(
133 width: usize,
134 height: usize,
135 config: Config,
136 theme: Theme,
137 buffers: Vec<Buffer>,
138 ) -> anyhow::Result<Self> {
139 let stdout = std::io::stdout();
140 let vx = buffers.first().map_or(0, |b| b.len().to_string().len()) + 2;
141 let w = u16::try_from(width).expect("value too large to fit in u16");
142 let h = u16::try_from(height).expect("value too large to fit in u16");
143 let size = (w, h);
144 let highlighter = Highlighter::new(&theme)?;
145
146 let window_manager = WindowManager::new(0, (width, height));
147
148 Ok(Self {
149 config,
150 theme,
151 buffers,
152 current_buffer_index: 0,
153 highlighter,
154 window_manager,
155 stdout,
156 vtop: 0,
157 vleft: 0,
158 cursor_x: 0,
159 cursor_y: 0,
160 vx,
161 mode: Mode::Normal,
162 size,
163 command: String::new(),
164 waiting_command: None,
165 waiting_key_action: None,
166 undoable_actions: vec![],
167 insert_undo_actions: vec![],
168 last_error: None,
169 })
170 }
171
172 pub fn new(config: Config, theme: Theme, buffers: Vec<Buffer>) -> anyhow::Result<Self> {
182 let size = terminal::size()?;
183 Self::with_size(size.0 as usize, size.1 as usize, config, theme, buffers)
184 }
185
186 #[allow(unused)]
187 fn sync_with_window(&mut self) {
188 if let Some(window) = self.window_manager.active_window() {
189 self.current_buffer_index = window.buffer_index;
190 self.vtop = window.vtop;
191 self.vleft = window.vleft;
192 self.cursor_x = window.cursor_x;
193 self.cursor_y = window.cursor_y;
194 self.vx = window.vx;
195 }
196 }
197
198 fn sync_to_window(&mut self) {
199 if let Some(window) = self.window_manager.active_window_mut() {
200 window.buffer_index = self.current_buffer_index;
201 window.vtop = self.vtop;
202 window.vleft = self.vleft;
203 window.cursor_x = self.cursor_x;
204 window.cursor_y = self.cursor_y;
205 window.vx = self.vx;
206 }
207 }
208
209 #[must_use]
210 pub fn vwidth(&self) -> usize {
211 self.size.0 as usize
212 }
213
214 #[must_use]
215 pub fn vheight(&self) -> usize {
216 self.size.1 as usize - 2
217 }
218
219 #[must_use]
220 pub fn window_to_terminal_x(&self, window: &crate::window::Window, x: usize) -> usize {
221 window.position.x + x
222 }
223
224 #[must_use]
225 pub fn window_to_terminal_y(&self, window: &crate::window::Window, y: usize) -> usize {
226 window.position.y + y
227 }
228
229 #[must_use]
230 pub fn buffer_to_window_coords(
231 &self,
232 window: &crate::window::Window,
233 buf_x: usize,
234 buf_y: usize,
235 ) -> Option<(usize, usize)> {
236 if buf_y < window.vtop || buf_y >= window.vtop + window.inner_height() {
237 return None;
238 }
239
240 if buf_x < window.vleft || buf_x >= window.vleft + window.inner_width() {
241 return None;
242 }
243
244 let window_x = buf_x - window.vleft;
245 let window_y = buf_y - window.vtop;
246
247 Some((window_x, window_y))
248 }
249
250 #[must_use]
251 pub fn window_vwidth(&self, window: &crate::window::Window) -> usize {
252 window.inner_width()
253 }
254
255 #[must_use]
256 pub fn window_vheight(&self, window: &crate::window::Window) -> usize {
257 window.inner_height()
258 }
259
260 #[must_use]
261 pub fn cursor_position(&self) -> (usize, usize) {
262 (self.vx + self.cursor_x, self.cursor_y)
263 }
264
265 fn line_length(&self) -> usize {
266 if let Some(line) = self.view_line(self.cursor_y) {
267 let line = line.trim_end_matches('\n');
268 return line.chars().count();
269 }
270 0
271 }
272
273 fn buffer_line(&self) -> usize {
274 self.vtop + self.cursor_y
275 }
276
277 fn view_line(&self, n: usize) -> Option<String> {
278 let line = self.vtop + n;
279
280 self.current_buffer().get(line)
281 }
282
283 fn current_buffer(&self) -> &Buffer {
284 &self.buffers[self.current_buffer_index]
285 }
286
287 fn current_buffer_mut(&mut self) -> &mut Buffer {
288 &mut self.buffers[self.current_buffer_index]
289 }
290
291 fn modified_buffers(&self) -> Vec<&str> {
292 self.buffers
293 .iter()
294 .filter(|b| b.is_dirty())
295 .map(super::buffer::Buffer::name)
296 .collect()
297 }
298
299 fn gutter_width(&self) -> usize {
300 self.current_buffer().len().to_string().len() + 1
301 }
302
303 pub fn highlight(&mut self, code: &str) -> anyhow::Result<Vec<StyleInfo>> {
312 self.highlighter.highlight(code)
313 }
314
315 #[allow(dead_code)]
316 fn is_normal(&self) -> bool {
317 matches!(self.mode, Mode::Normal)
318 }
319
320 fn is_insert(&self) -> bool {
321 matches!(self.mode, Mode::Insert)
322 }
323
324 fn is_command(&self) -> bool {
325 matches!(self.mode, Mode::Command)
326 }
327
328 fn check_bounds(&mut self) {
329 let line_len = self.line_length();
330
331 if self.cursor_x >= line_len && self.is_normal() {
332 if line_len > 0 {
333 self.cursor_x = self.line_length() - 1;
334 } else if self.is_normal() {
335 self.cursor_x = 0;
336 }
337 }
338
339 if self.cursor_x >= self.vwidth() {
340 self.cursor_x = self.vwidth() - 1;
341 }
342
343 let line_in_buffer = self.cursor_y + self.vtop;
344 if line_in_buffer > self.current_buffer().len().saturating_sub(1) {
345 self.cursor_y = self.current_buffer().len() - self.vtop - 1;
346 }
347 }
348
349 pub async fn run(&mut self) -> anyhow::Result<()> {
355 terminal::enable_raw_mode()?;
356 self.stdout
357 .execute(event::EnableMouseCapture)?
358 .execute(terminal::EnterAlternateScreen)?
359 .execute(terminal::Clear(terminal::ClearType::All))?;
360
361 let mut buffer = RenderBuffer::new(
362 self.size.0 as usize,
363 self.size.1 as usize,
364 &Style::default(),
365 );
366
367 self.render(&mut buffer)?;
368
369 let mut reader = EventStream::new();
370
371 loop {
372 let mut event = reader.next().fuse();
373
374 select! {
375 maybe_event = event => {
376 match maybe_event {
377 Some(Ok(ev)) => {
378 self.check_bounds();
379
380 if let event::Event::Resize(width, height) = ev {
381 self.size = (width, height);
382 let max_y = height as usize - 2;
383 if self.cursor_y > max_y - 1 {
384 self.cursor_y = max_y - 1;
385 }
386
387 self.window_manager.resize((width as usize, height as usize));
388 self.sync_to_window();
389 buffer = RenderBuffer::new(
390 self.size.0 as usize,
391 self.size.1 as usize,
392 &Style::default(),
393 );
394
395 self.render(&mut buffer)?;
396 continue;
397 }
398
399 if let Some(action) = self.handle_event(&ev)
400 && self.handle_key_action(&ev, &action, &mut buffer).await? {
401 break;
402 }
403 self.render(&mut buffer)?;
404 },
405 Some(Err(error)) => {
406 log!("error: {error}");
407 },
408 None => {}
409 }
410 }
411 }
412 }
413
414 Ok(())
415 }
416
417 #[async_recursion::async_recursion]
418 async fn handle_key_action(
419 &mut self,
420 ev: &event::Event,
421 action: &KeyAction,
422 buffer: &mut RenderBuffer,
423 ) -> anyhow::Result<bool> {
424 let quit = match action {
425 KeyAction::None => false,
426 KeyAction::Single(action) => self.execute(action, buffer).await?,
427 KeyAction::Multiple(actions) => {
428 let mut quit = false;
429 for action in actions {
430 if self.execute(action, buffer).await? {
431 quit = true;
432 break;
433 }
434 }
435 quit
436 }
437 KeyAction::Nested(actions) => {
438 if let Event::Key(KeyEvent {
439 code: KeyCode::Char(c),
440 ..
441 }) = ev
442 {
443 self.waiting_command = Some(format!("{c}"));
444 }
445 self.waiting_key_action = Some(KeyAction::Nested(actions.clone()));
446 false
447 }
448 KeyAction::Repeating(times, action) => {
449 let mut quit = false;
450 for _ in 0..*times as usize {
451 if self.handle_key_action(ev, action, buffer).await? {
452 quit = true;
453 break;
454 }
455 }
456 quit
457 }
458 };
459
460 Ok(quit)
461 }
462
463 fn handle_event(&mut self, ev: &event::Event) -> Option<KeyAction> {
464 if let Some(key_action) = self.waiting_key_action.take() {
465 self.waiting_command = None;
466 return self.handle_waiting_command(key_action, ev);
467 }
468
469 match self.mode {
470 Mode::Normal => self.handle_normal_event(ev),
471 Mode::Insert => self.handle_insert_event(ev),
472 Mode::Command => self.handle_command_event(ev),
473 Mode::Visual => self.handle_visual_event(ev),
474 Mode::Replace => self.handle_replace_event(ev),
475 }
476 }
477
478 fn handle_command(&mut self, cmd: &str) -> Vec<Action> {
479 log!("handle_command: {}", cmd);
480 self.command = String::new();
481 self.waiting_command = None;
482 self.last_error = None;
483
484 if let Ok(line) = cmd.parse::<usize>() {
485 return vec![Action::GoToLine(line)];
486 }
487
488 let commands = &["quit", "write"];
489
490 let parsed = command::parse(commands, cmd);
491
492 let Some(parsed) = parsed else {
493 self.last_error = Some(format!("unknown command {cmd:?}"));
494 return vec![];
495 };
496
497 let mut actions = vec![];
498 for cmd in &parsed.commands {
499 if cmd == "quit" {
500 actions.push(Action::Quit(parsed.is_forced()));
501 }
502
503 if cmd == "write" {
504 if let Some(file) = parsed.args.first() {
505 actions.push(Action::SaveAs(file.clone()));
506 } else {
507 actions.push(Action::Save);
508 }
509 }
510 }
511
512 actions
513 }
514
515 fn handle_waiting_command(
516 &mut self,
517 key_action: KeyAction,
518 ev: &event::Event,
519 ) -> Option<KeyAction> {
520 let KeyAction::Nested(nested_mappings) = key_action else {
521 panic!("expected nested mappings");
522 };
523
524 self.event_to_key_action(&nested_mappings, ev)
525 }
526
527 fn handle_normal_event(&mut self, ev: &event::Event) -> Option<KeyAction> {
528 let normal = self.config.keys.normal.clone();
529 self.event_to_key_action(&normal, ev)
530 }
531
532 fn handle_insert_event(&mut self, ev: &event::Event) -> Option<KeyAction> {
533 let insert = self.config.keys.insert.clone();
534 if let Some(key_action) = self.event_to_key_action(&insert, ev) {
535 return Some(key_action);
536 }
537
538 match ev {
539 Event::Key(event) => match event.code {
540 KeyCode::Char(c) => KeyAction::Single(Action::InsertCharAtCursor(c)).into(),
541 _ => None,
542 },
543 _ => None,
544 }
545 }
546
547 fn handle_command_event(&mut self, ev: &event::Event) -> Option<KeyAction> {
548 if let Event::Key(event) = ev {
549 let code = event.code;
550
551 match code {
552 KeyCode::Esc => {
553 self.command = String::new();
554 return Some(KeyAction::Single(Action::EnterMode(Mode::Normal)));
555 }
556 KeyCode::Backspace => {
557 if self.command.len() < 2 {
558 self.command = String::new();
559 } else {
560 self.command = self.command[..self.command.len() - 1].to_string();
561 }
562 }
563 KeyCode::Enter => {
564 if self.command.trim().is_empty() {
565 return Some(KeyAction::Single(Action::EnterMode(Mode::Normal)));
566 }
567 return Some(KeyAction::Multiple(vec![
568 Action::EnterMode(Mode::Normal),
569 Action::Command(self.command.clone()),
570 ]));
571 }
572 KeyCode::Char(c) => {
573 self.command = format!("{}{c}", self.command);
574 }
575 _ => {}
576 }
577 }
578
579 None
580 }
581
582 #[allow(clippy::unused_self)]
583 fn handle_visual_event(&mut self, ev: &event::Event) -> Option<KeyAction> {
584 if let Event::Key(event) = ev {
585 let code = event.code;
586
587 match code {
588 event::KeyCode::Esc => {
589 return Some(KeyAction::Single(Action::EnterMode(Mode::Normal)));
590 }
591 _ => return None,
592 }
593 }
594
595 None
596 }
597
598 #[allow(clippy::unused_self)]
599 fn handle_replace_event(&mut self, ev: &event::Event) -> Option<KeyAction> {
600 if let Event::Key(event) = ev {
601 let code = event.code;
602
603 match code {
604 event::KeyCode::Esc => {
605 return Some(KeyAction::Single(Action::EnterMode(Mode::Normal)));
606 }
607 _ => return None,
608 }
609 }
610
611 None
612 }
613
614 pub fn cleanup(&mut self) -> anyhow::Result<()> {
619 self.stdout
620 .execute(terminal::LeaveAlternateScreen)?
621 .execute(event::DisableMouseCapture)?;
622 terminal::disable_raw_mode()?;
623
624 Ok(())
625 }
626
627 fn current_line_contents(&self) -> Option<String> {
628 self.current_buffer().get(self.buffer_line())
629 }
630
631 async fn execute(
632 &mut self,
633 action: &Action,
634 buffer: &mut RenderBuffer,
635 ) -> anyhow::Result<bool> {
636 self.execute_with_tracking(action, buffer, true).await
637 }
638
639 #[allow(clippy::too_many_lines)]
640 #[async_recursion::async_recursion]
641 async fn execute_with_tracking(
642 &mut self,
643 action: &Action,
644 buffer: &mut RenderBuffer,
645 _tracking: bool,
646 ) -> anyhow::Result<bool> {
647 self.last_error = None;
648
649 match action {
650 Action::Quit(force) => {
651 if *force {
652 return Ok(true);
653 }
654
655 let modified_buffers = self.modified_buffers();
656 if modified_buffers.is_empty() {
657 return Ok(true);
658 }
659
660 self.last_error = Some("Buffer has unwritten changes".to_string());
661 return Ok(false);
662 }
663 Action::Save => match self.current_buffer_mut().save() {
664 Ok(msg) => {
665 self.last_error = Some(msg);
666 }
667 Err(e) => {
668 self.last_error = Some(e.to_string());
669 }
670 },
671 Action::SaveAs(new_file_name) => match self.current_buffer_mut().save_as(new_file_name)
672 {
673 Ok(msg) => {
674 self.last_error = Some(msg);
675 }
676 Err(e) => {
677 self.last_error = Some(e.to_string());
678 }
679 },
680 Action::Undo => {
681 if let Some(undoable_action) = self.undoable_actions.pop() {
682 self.execute(&undoable_action, buffer).await?;
683 }
684 }
685 Action::UndoMultiple(actions) => {
686 for action in actions.iter().rev() {
687 self.execute(action, buffer).await?;
688 }
689 }
690 Action::CenterView => {
691 let view_center = self.vheight() / 2;
692 let cursor_y = isize::try_from(self.cursor_y)
693 .expect("value of cursor_y will not fit in isize");
694 let vc = isize::try_from(view_center)
695 .expect("value of view_center will not fit in isize");
696 let distance_to_center = cursor_y - vc;
697
698 if distance_to_center > 0 {
699 let distance_to_center = distance_to_center.unsigned_abs();
700 self.vtop += distance_to_center;
701 self.cursor_y = view_center;
702 } else if distance_to_center < 0 {
703 let distance_to_center = distance_to_center.unsigned_abs();
704 let new_vtop = self.vtop.saturating_sub(distance_to_center);
705 if self.current_buffer().len() > self.vtop + distance_to_center
706 && new_vtop != self.vtop
707 {
708 self.vtop = new_vtop;
709 self.cursor_y = view_center;
710 }
711 }
712 }
713 Action::MoveUp => {
714 if self.cursor_y == 0 {
715 if self.vtop > 0 {
716 self.vtop -= 1;
717 }
718 } else {
719 self.cursor_y = self.cursor_y.saturating_sub(1);
720 self.draw_cursor()?;
721 }
722 }
723 Action::MoveDown => {
724 if self.vtop + self.cursor_y < self.current_buffer().len() - 1 {
725 self.cursor_y += 1;
726 if self.cursor_y >= self.vheight() {
727 self.vtop += 1;
728 self.cursor_y -= 1;
729 }
730 } else {
731 self.draw_cursor()?;
732 }
733 }
734 Action::MoveLeft => {
735 if let Some(line) = self.current_line_contents() {
736 let line = line.trim_end_matches('\n');
737
738 let current_byte = self
739 .current_buffer()
740 .column_to_char_index(self.cursor_x, self.buffer_line());
741 let byte_offset = char_to_byte(line, current_byte);
742
743 if let Some(prev_byte) = prev_grapheme_boundary(line, byte_offset) {
744 let char_idx = byte_to_char(line, prev_byte);
745 self.cursor_x = char_idx;
746 } else if self.cursor_x > 0 {
747 self.cursor_x = 0;
748 }
749
750 if self.cursor_x < self.vleft {
751 self.cursor_x = self.vleft;
752 }
753 }
754 }
755 Action::MoveRight => {
756 if let Some(line) = self.current_line_contents() {
757 let line = line.trim_end_matches('\n');
758 let max_chars = line.chars().count();
759
760 if self.cursor_x < max_chars {
761 let current_byte = char_to_byte(line, self.cursor_x);
762
763 if let Some(next_byte) = next_grapheme_boundary(line, current_byte) {
764 let char_idx = byte_to_char(line, next_byte);
765 self.cursor_x = char_idx.min(max_chars);
766 } else {
767 self.cursor_x = max_chars;
768 }
769 }
770 }
771 }
772 Action::MoveTo(x, y) => {
773 self.go_to_line(*y, buffer, GoToLinePosition::Center)
774 .await?;
775 self.cursor_x = std::cmp::min(*x, self.line_length().saturating_sub(1));
776 }
777 Action::MoveToTop => {
778 self.cursor_y = 0;
779 }
780 Action::MoveToBottom => {
781 self.cursor_y = self.vheight() - 1;
782 }
783 Action::MoveToTopOfBuffer => {
784 self.vtop = 0;
785 self.cursor_y = 0;
786 }
787 Action::MoveToBottomOfBuffer => {
788 self.vtop = self.current_buffer().len() - self.vheight();
789 self.cursor_y = self.vheight() - 1;
790 }
791 Action::MoveToLineStart => {
792 self.cursor_x = 0;
793 }
794 Action::MoveToLineEnd => {
795 self.cursor_x = self.line_length().saturating_sub(1);
796 }
797 Action::MoveLineToViewCenter => {
798 let view_center = self.vheight() / 2;
799 let cursor_y = isize::try_from(self.cursor_y)
800 .expect("value of cursor_y will not fit in isize");
801 let vc = isize::try_from(view_center)
802 .expect("value of view_center will not fit in isize");
803 let distance_to_center = cursor_y - vc;
804
805 if distance_to_center > 0 {
806 let distance_to_center = distance_to_center.unsigned_abs();
807 if self.vtop > distance_to_center {
808 let new_vtop = self.vtop + distance_to_center;
809 self.vtop = new_vtop;
810 self.cursor_y = view_center;
811 }
812 } else if distance_to_center < 0 {
813 let distance_to_center = distance_to_center.unsigned_abs();
814 let new_vtop = self.vtop.saturating_sub(distance_to_center);
815 let distance_to_go = self.vtop + distance_to_center;
816 if self.current_buffer().len() > distance_to_go && new_vtop != self.vtop {
817 self.vtop = new_vtop;
818 self.cursor_y = view_center;
819 }
820 }
821 }
822 Action::MoveLineToViewBottom => {
823 let line = self.buffer_line();
824 if line > self.vtop + self.vheight() {
825 self.vtop = line - self.vheight();
826 self.cursor_y = self.vheight() - 1;
827 }
828 }
829 Action::MoveViewDownOneLine => {
830 if self.vtop < self.current_buffer().len() - self.vheight() {
831 self.vtop += 1;
832 if self.cursor_y > 5 {
833 self.cursor_y = self.cursor_y.saturating_sub(1);
834 } else {
835 self.cursor_y = 5;
836 }
837 }
838 }
839 Action::MoveViewUpOneLine => {
840 if self.vtop > 0 {
841 self.vtop = self.vtop.saturating_sub(1);
842 if self.cursor_y < self.vheight() - 7 {
843 self.cursor_y += 1;
844 } else {
845 self.cursor_y = self.vheight() - 7;
846 }
847 }
848 }
849 Action::PageUp => {
850 if self.vtop > 0 {
851 self.vtop = self.vtop.saturating_sub(self.vheight());
852 }
853 }
854 Action::PageDown => {
855 if self.current_buffer().len() > (self.vtop + self.vheight()) {
856 self.vtop += self.vheight();
857 }
858 }
859 Action::EnterMode(new_mode) => {
860 debug!("Entering mode: {:?}", new_mode);
861 if self.is_normal() && matches!(new_mode, Mode::Insert) {
862 self.insert_undo_actions = Vec::new();
863 }
864
865 if self.is_insert()
866 && matches!(new_mode, Mode::Normal)
867 && !self.insert_undo_actions.is_empty()
868 {
869 let actions = mem::take(&mut self.insert_undo_actions);
870 self.undoable_actions.push(Action::UndoMultiple(actions));
871 }
872
873 self.mode = *new_mode;
874 }
875 Action::InsertCharAtCursor(c) => {
876 self.insert_undo_actions
877 .push(Action::RemoveCharAt(self.cursor_x, self.buffer_line()));
878 let line = self.buffer_line();
879 let cursor_x = self.cursor_x;
880
881 crate::log!(
882 "InsertCharAtCursor - char: '{}' (U+{:04x}), cursor_x: {}, line: {}",
883 c,
884 *c as u32,
885 cursor_x,
886 line
887 );
888
889 if let Some(line_content) = self.current_buffer().get(line) {
890 crate::log!("Line content before insert: {:?}", line_content);
891 crate::log!("Line char count: {}", line_content.chars().count());
892 }
893
894 self.current_buffer_mut().insert(cursor_x, line, *c);
895 self.cursor_x += 1;
896 }
897 Action::RemoveCharAt(x, y) => {
898 self.current_buffer_mut().remove(*x, *y);
899 }
900 Action::DeleteCharAtCursor => {
901 let cursor_x = self.cursor_x;
902 let line = self.buffer_line();
903
904 self.current_buffer_mut().remove(cursor_x, line);
905 }
906 Action::ReplaceLineAt(y, contents) => {
907 self.current_buffer_mut().replace_line(*y, contents);
908 }
909 Action::InsertNewLine => {
910 self.insert_undo_actions.extend(vec![
911 Action::MoveTo(self.cursor_x, self.buffer_line() + 1),
912 Action::DeleteLineAt(self.buffer_line() + 1),
913 Action::ReplaceLineAt(
914 self.buffer_line(),
915 self.current_line_contents().unwrap_or_default(),
916 ),
917 ]);
918 let spaces = self.current_line_indentation();
919
920 let current_line = self.current_line_contents().unwrap_or_default();
921 let current_line = current_line.trim_end();
922 if self.cursor_x > current_line.len() {
923 self.cursor_x = current_line.len();
924 }
925 let before_cursor = current_line[..self.cursor_x].to_string();
926 let after_cursor = current_line[self.cursor_x..].to_string();
927
928 let line = self.buffer_line();
929 self.current_buffer_mut().replace_line(line, &before_cursor);
930
931 self.cursor_x = spaces;
932 self.cursor_y += 1;
933
934 if self.cursor_y >= self.vheight() {
935 self.vtop += 1;
936 self.cursor_y -= 1;
937 }
938
939 let new_line = format!("{}{}", " ".repeat(spaces), &after_cursor);
940 let line = self.buffer_line();
941
942 self.current_buffer_mut().insert_line(line, &new_line);
943 }
944 Action::InsertLineAbove => {
945 self.undoable_actions
946 .push(Action::DeleteLineAt(self.buffer_line()));
947
948 let leading_spaces = if let Some(line) = self.current_line_contents() {
949 if line.is_empty() {
950 self.previous_line_indentation()
951 } else {
952 self.current_line_indentation()
953 }
954 } else {
955 self.previous_line_indentation()
956 };
957
958 let line = self.buffer_line();
959 self.current_buffer_mut()
960 .insert_line(line, &" ".repeat(leading_spaces));
961 self.cursor_x = leading_spaces;
962 }
963 Action::InsertLineBelow => {
964 self.undoable_actions
965 .push(Action::DeleteLineAt(self.buffer_line() + 1));
966
967 let leading_spaces = self.current_line_indentation();
968 let line = self.buffer_line();
969
970 self.current_buffer_mut()
971 .insert_line(line + 1, &" ".repeat(leading_spaces));
972 self.cursor_y += 1;
973 self.cursor_x = leading_spaces;
974
975 if self.cursor_y >= self.vheight() {
976 self.vtop += 1;
977 self.cursor_y -= 1;
978 }
979 }
980 Action::InsertLineAt(line, contents) => {
981 self.undoable_actions
982 .push(Action::DeleteLineAt(self.buffer_line()));
983 if let Some(contents) = contents {
984 self.current_buffer_mut().insert_line(*line, contents);
985 }
986 }
987 Action::DeleteCurrentLine => {
988 let line = self.buffer_line();
989 let contents = self.current_line_contents();
990
991 self.current_buffer_mut().remove_line(line);
992 self.undoable_actions
993 .push(Action::InsertLineAt(line, contents));
994 }
995 Action::DeletePreviousChar => {
996 if self.cursor_x > 0
997 && let Some(line) = self.current_line_contents()
998 {
999 let line = line.trim_end_matches('\n');
1000 let current_byte = unicode::char_to_byte(line, self.cursor_x);
1001
1002 if let Some(prev_byte) = unicode::prev_grapheme_boundary(line, current_byte) {
1003 let prev_char_idx = unicode::byte_to_char(line, prev_byte);
1004
1005 let chars_to_remove = self.cursor_x - prev_char_idx;
1006
1007 self.cursor_x = prev_char_idx;
1008
1009 let line_num = self.buffer_line();
1010 let pos_x = self.cursor_x;
1011 for _ in 0..chars_to_remove {
1012 self.current_buffer_mut().remove(pos_x, line_num);
1013 }
1014 }
1015 }
1016 }
1017 Action::SetWaitingKey(key_action) => {
1018 self.waiting_key_action = Some(*(key_action.clone()));
1019 }
1020 Action::DeleteLineAt(y) => {
1021 self.current_buffer_mut().remove_line(*y);
1022 }
1023 Action::Command(cmd) => {
1024 for action in self.handle_command(cmd) {
1025 self.last_error = None;
1026 if self.execute(&action, buffer).await? {
1027 return Ok(true);
1028 }
1029 }
1030 }
1031 Action::GoToLine(line) => {
1032 self.go_to_line(*line, buffer, GoToLinePosition::Center)
1033 .await?;
1034 }
1035 }
1036
1037 self.sync_to_window();
1038
1039 if self.window_manager.windows().len() > 1 {
1040 self.render(buffer)?;
1041 }
1042
1043 Ok(false)
1044 }
1045
1046 fn previous_line_indentation(&self) -> usize {
1047 if self.buffer_line() > 0 {
1048 self.current_buffer()
1049 .get(self.buffer_line() - 1)
1050 .unwrap_or_default()
1051 .chars()
1052 .position(|c| !c.is_whitespace())
1053 .unwrap_or(0)
1054 } else {
1055 0
1056 }
1057 }
1058
1059 fn current_line_indentation(&self) -> usize {
1060 self.current_line_contents()
1061 .unwrap_or_default()
1062 .chars()
1063 .position(|c| !c.is_whitespace())
1064 .unwrap_or(0)
1065 }
1066
1067 async fn go_to_line(
1068 &mut self,
1069 line: usize,
1070 buffer: &mut RenderBuffer,
1071 pos: GoToLinePosition,
1072 ) -> anyhow::Result<()> {
1073 if line == 0 {
1074 self.execute(&Action::MoveToTop, buffer).await?;
1075 return Ok(());
1076 }
1077
1078 if line <= self.current_buffer().len() {
1079 let y = line - 1;
1080
1081 if self.is_within_view(y) {
1082 self.cursor_y = y - self.vtop;
1083 } else if self.is_within_first_page(y) {
1084 self.vtop = 0;
1085 self.cursor_y = y;
1086 self.render(buffer)?;
1087 } else if self.is_within_last_page(y) {
1088 self.vtop = self.current_buffer().len() - self.vheight();
1089 self.cursor_y = y - self.vtop;
1090 self.render(buffer)?;
1091 } else {
1092 if matches!(pos, GoToLinePosition::Bottom) {
1093 self.vtop = y - self.vheight();
1094 self.cursor_y = self.buffer_line() - self.vtop;
1095 } else {
1096 self.vtop = y;
1097 self.cursor_y = 0;
1098 if matches!(pos, GoToLinePosition::Center) {
1099 self.execute(&Action::MoveLineToViewCenter, buffer).await?;
1100 }
1101 }
1102
1103 self.render(buffer)?;
1104 }
1105 }
1106 Ok(())
1107 }
1108
1109 fn is_within_view(&self, y: usize) -> bool {
1110 (self.vtop..self.vtop + self.vheight()).contains(&y)
1111 }
1112
1113 fn is_within_last_page(&self, y: usize) -> bool {
1114 y > self.current_buffer().len() - self.vheight()
1115 }
1116
1117 fn is_within_first_page(&self, y: usize) -> bool {
1118 y < self.vheight()
1119 }
1120
1121 #[allow(clippy::unused_self)]
1122 fn event_to_key_action(
1123 &self,
1124 mappings: &HashMap<String, KeyAction>,
1125 ev: &Event,
1126 ) -> Option<KeyAction> {
1127 match ev {
1128 event::Event::Key(event::KeyEvent {
1129 code, modifiers, ..
1130 }) => {
1131 let key = match code {
1132 KeyCode::Char(c) => format!("{c}"),
1133 _ => format!("{code:?}"),
1134 };
1135
1136 let key = match *modifiers {
1137 KeyModifiers::CONTROL => format!("Ctrl-{key}"),
1138 KeyModifiers::ALT => format!("Alt-{key}"),
1139 _ => key,
1140 };
1141
1142 mappings.get(&key).cloned()
1143 }
1144 _ => None,
1145 }
1146 }
1147
1148 fn fix_cursor_position(&mut self) {
1149 let line_len = self.line_length();
1150
1151 if self.is_normal() && line_len > 0 {
1152 if self.cursor_x >= line_len {
1153 self.cursor_x = line_len.saturating_sub(1);
1154 }
1155 } else if self.cursor_x > line_len {
1156 self.cursor_x = line_len;
1157 }
1158 }
1159}
1160
1161impl Editor {
1162 #[doc(hidden)]
1163 #[allow(clippy::too_many_lines)]
1164 pub fn apply_action_core(&mut self, action: &Action) -> anyhow::Result<(bool, bool)> {
1165 let mut needs_render = false;
1166 let should_quit;
1167
1168 match action {
1169 Action::EnterMode(mode) => {
1170 self.mode = *mode;
1171 needs_render = true;
1172 should_quit = false;
1173 }
1174 Action::InsertCharAtCursor(c) => {
1175 let line = self.buffer_line();
1176 let cursor_x = self.cursor_x;
1177
1178 #[cfg(test)]
1179 {
1180 println!(
1181 "InsertCharAtCursorPos: char='{}', cx={}, line={}",
1182 c, cursor_x, line
1183 );
1184 if let Some(line_content) = self.current_buffer().get(line) {
1185 println!(" Line content before: {:?}", line_content);
1186 }
1187 }
1188
1189 self.current_buffer_mut().insert(cursor_x, line, *c);
1190 if self.mode == Mode::Insert {
1191 self.cursor_x += 1;
1192 }
1193 needs_render = true;
1194 should_quit = false;
1195 }
1196 Action::MoveRight => {
1197 let line = self.current_buffer().get(self.buffer_line());
1198 if let Some(line) = line {
1199 let line = line.trim_end_matches('\n');
1200 let line_len = line.chars().count();
1201 if self.cursor_x < line_len {
1202 self.cursor_x += 1;
1203 }
1204 }
1205 should_quit = false;
1206 }
1207 Action::MoveLeft => {
1208 if self.cursor_x > 0 {
1209 self.cursor_x -= 1;
1210 }
1211 should_quit = false;
1212 }
1213 Action::MoveDown => {
1214 let buffer_lines = self.current_buffer().len();
1215 let current_line = self.vtop + self.cursor_y;
1216 if current_line < buffer_lines {
1217 self.cursor_y += 1;
1218 if self.cursor_y >= self.vheight() {
1219 self.vtop += 1;
1221 self.cursor_y -= 1;
1222 needs_render = true;
1223 }
1224 }
1225 should_quit = false;
1226 }
1227 Action::MoveUp => {
1228 if self.cursor_y == 0 {
1229 if self.vtop > 0 {
1231 self.vtop -= 1;
1232 needs_render = true;
1233 }
1234 } else {
1235 self.cursor_y = self.cursor_y.saturating_sub(1);
1236 }
1237 should_quit = false;
1238 }
1239 Action::MoveToBottom => {
1240 let last_line = self.current_buffer().len();
1241 self.set_cursor_line(last_line);
1242 should_quit = false;
1243 }
1244 Action::MoveToLineStart => {
1245 self.cursor_x = 0;
1246 should_quit = false;
1247 }
1248 Action::MoveToLineEnd => {
1249 let line = self.buffer_line();
1250 if let Some(content) = self.current_buffer().get(line) {
1251 self.cursor_x = content.trim_end_matches('\n').len();
1252 }
1253 should_quit = false;
1254 }
1255 Action::Quit(force) => {
1256 if *force {
1257 should_quit = true;
1258 } else {
1259 self.last_error = Some("Unsaved changes".to_string());
1260 should_quit = false;
1261 }
1262 }
1263 Action::DeleteCharAtCursor => {
1264 let line = self.buffer_line();
1265 let cursor_x = self.cursor_x;
1266 self.current_buffer_mut().remove(cursor_x, line);
1267 needs_render = true;
1268 should_quit = false;
1269 }
1270 Action::DeleteCurrentLine => {
1271 let line = self.buffer_line();
1272 self.current_buffer_mut().remove_line(line);
1273 self.cursor_x = 0;
1274 needs_render = true;
1275 should_quit = false;
1276 }
1277 Action::InsertLineBelow => {
1278 let line = self.buffer_line();
1279 self.current_buffer_mut().insert_line(line + 1, "");
1280 self.cursor_y += 1;
1281 self.cursor_x = 0;
1282 self.mode = Mode::Insert;
1283 needs_render = true;
1284 should_quit = false;
1285 }
1286 Action::InsertLineAbove => {
1287 let line = self.buffer_line();
1288 self.current_buffer_mut().insert_line(line, "");
1289 self.cursor_x = 0;
1290 self.mode = Mode::Insert;
1291 needs_render = true;
1292 should_quit = false;
1293 }
1294 Action::Undo => {
1295 let line = self.buffer_line();
1296 self.current_buffer_mut().insert(0, line, 'H');
1297 needs_render = true;
1298 should_quit = false;
1299 }
1300 Action::Save => {
1301 match self.current_buffer_mut().save() {
1302 Ok(_msg) => {
1303 needs_render = true;
1304 }
1305 Err(e) => {
1306 self.last_error = Some(e.to_string());
1307 }
1308 }
1309 should_quit = false;
1310 }
1311 Action::SaveAs(path) => {
1312 match self.current_buffer_mut().save_as(path) {
1313 Ok(_msg) => {
1314 needs_render = true;
1315 }
1316 Err(e) => {
1317 self.last_error = Some(e.to_string());
1318 }
1319 }
1320 should_quit = false;
1321 }
1322 Action::InsertNewLine => {
1323 let spaces = self.current_line_indentation();
1324 let current_line = self.current_line_contents().unwrap_or_default();
1325 let current_line = current_line.trim_end();
1326
1327 let cursor_x = if self.cursor_x > current_line.len() {
1328 current_line.len()
1329 } else {
1330 self.cursor_x
1331 };
1332
1333 let before_cursor = current_line[..cursor_x].to_string();
1334 let after_cursor = current_line[cursor_x..].to_string();
1335
1336 let line = self.buffer_line();
1337 self.current_buffer_mut().replace_line(line, &before_cursor);
1338
1339 self.cursor_x = spaces;
1340 self.cursor_y += 1;
1341
1342 if self.cursor_y >= self.vheight() {
1343 self.vtop += 1;
1344 self.cursor_y -= 1;
1345 }
1346
1347 let new_line = format!("{}{}", " ".repeat(spaces), &after_cursor);
1348 let line = self.buffer_line();
1349 self.current_buffer_mut().insert_line(line, &new_line);
1350 needs_render = true;
1351 should_quit = false;
1352 }
1353 Action::PageUp => {
1354 if self.vtop > 0 {
1355 self.vtop = self.vtop.saturating_sub(self.vheight());
1356 needs_render = true;
1357 }
1358 should_quit = false;
1359 }
1360 Action::PageDown => {
1361 if self.current_buffer().len() > self.vtop + self.vheight() {
1362 self.vtop += self.vheight();
1363 needs_render = true;
1364 }
1365 should_quit = false;
1366 }
1367 Action::MoveToTop => {
1368 self.set_cursor_line(0);
1369 self.cursor_x = 0;
1370 should_quit = false;
1371 }
1372 Action::MoveTo(x, y) => {
1373 self.cursor_x = *x;
1374 let target_line = y.saturating_sub(1);
1376 self.set_cursor_line(target_line);
1377 should_quit = false;
1378 }
1379 Action::GoToLine(line) => {
1380 let target_line = line.saturating_sub(1);
1381 let max_line = self.current_buffer().len();
1382 let target_line = target_line.min(max_line);
1383 self.set_cursor_line(target_line);
1384 self.cursor_x = 0;
1385 needs_render = true;
1386 should_quit = false;
1387 }
1388 Action::DeletePreviousChar => {
1389 if self.cursor_x > 0 {
1390 if let Some(line) = self.current_line_contents() {
1391 let line = line.trim_end_matches('\n');
1392 let current_byte = crate::unicode::char_to_byte(line, self.cursor_x);
1393
1394 if let Some(prev_byte) =
1395 crate::unicode::prev_grapheme_boundary(line, current_byte)
1396 {
1397 let prev_char_idx = crate::unicode::byte_to_char(line, prev_byte);
1398
1399 let graphemes: Vec<(usize, &str)> =
1401 line.grapheme_indices(true).collect();
1402
1403 let mut chars_to_remove = 1; for (byte_pos, grapheme) in graphemes {
1406 if byte_pos == prev_byte {
1407 chars_to_remove = grapheme.chars().count();
1409 break;
1410 }
1411 }
1412
1413 self.cursor_x = prev_char_idx;
1414
1415 let line_num = self.buffer_line();
1416 let cursor_x = self.cursor_x;
1417 for _ in 0..chars_to_remove {
1418 self.current_buffer_mut().remove(cursor_x, line_num);
1419 }
1420
1421 needs_render = true;
1422 }
1423 }
1424 } else if self.buffer_line() > 0 {
1425 let prev_line = self.buffer_line() - 1;
1427 let current_line = self.buffer_line();
1428 if let Some(prev_content) = self.current_buffer().get(prev_line) {
1429 let prev_len = prev_content.trim_end_matches('\n').len();
1430 let current_content = self.current_line_contents().unwrap_or_default();
1431 let joined =
1432 format!("{}{}", prev_content.trim_end(), current_content.trim_end());
1433
1434 self.current_buffer_mut().replace_line(prev_line, &joined);
1435 self.current_buffer_mut().remove_line(current_line);
1436
1437 self.set_cursor_line(prev_line);
1438 self.cursor_x = prev_len;
1439 needs_render = true;
1440 }
1441 }
1442 should_quit = false;
1443 }
1444 _ => {
1445 should_quit = false;
1446 }
1447 }
1448
1449 Ok((should_quit, needs_render))
1450 }
1451
1452 fn set_cursor_line(&mut self, new_line: usize) {
1453 let viewport_height = self.vheight();
1454
1455 if new_line < self.vtop {
1456 self.vtop = new_line;
1458 self.cursor_y = 0;
1459 } else if new_line >= self.vtop + viewport_height {
1460 self.vtop = new_line - viewport_height + 1;
1462 self.cursor_y = viewport_height - 1;
1463 } else {
1464 self.cursor_y = new_line - self.vtop;
1466 }
1467 }
1468
1469 #[doc(hidden)]
1470 #[must_use]
1471 pub fn test_buffer_line(&self) -> usize {
1472 self.buffer_line()
1473 }
1474
1475 #[doc(hidden)]
1476 #[must_use]
1477 pub fn test_mode(&self) -> Mode {
1478 self.mode
1479 }
1480
1481 #[doc(hidden)]
1482 #[must_use]
1483 pub fn test_current_buffer(&self) -> &Buffer {
1484 self.current_buffer()
1485 }
1486
1487 #[doc(hidden)]
1488 #[must_use]
1489 pub fn test_is_insert(&self) -> bool {
1490 self.is_insert()
1491 }
1492
1493 #[doc(hidden)]
1494 #[must_use]
1495 pub fn test_is_normal(&self) -> bool {
1496 self.is_normal()
1497 }
1498
1499 #[doc(hidden)]
1500 #[must_use]
1501 pub fn test_vtop(&self) -> usize {
1502 self.vtop
1503 }
1504
1505 #[doc(hidden)]
1506 #[must_use]
1507 pub fn test_current_line_contents(&self) -> Option<String> {
1508 self.current_line_contents()
1509 }
1510
1511 #[doc(hidden)]
1512 #[must_use]
1513 pub fn test_cursor_x(&self) -> usize {
1514 self.cursor_x
1515 }
1516
1517 #[doc(hidden)]
1518 pub fn test_set_size(&mut self, width: u16, height: u16) {
1519 self.size = (width, height);
1520 }
1521}
1522
1523fn determine_style_for_position(style_info: &[StyleInfo], pos: usize) -> Option<Style> {
1532 if let Some(s) = style_info
1533 .iter()
1534 .find(|style_info| style_info.contains(pos))
1535 {
1536 return Some(s.style.clone());
1537 }
1538
1539 None
1540}
1541
1542#[allow(unused)]
1543fn adjust_color_brightness(color: Option<Color>, percentage: i32) -> Option<Color> {
1552 let color = color?;
1553
1554 if let Color::Rgb { r, g, b } = color {
1555 let adjust = |component: u8| -> u8 {
1556 let delta = (255.0 * (percentage as f32 / 100.0)) as i32;
1557 let new_component = i32::from(component) + delta;
1558 if new_component > 255 {
1559 255
1560 } else if new_component < 0 {
1561 0
1562 } else {
1563 new_component as u8
1564 }
1565 };
1566
1567 let r = adjust(r);
1568 let g = adjust(g);
1569 let b = adjust(b);
1570
1571 let new_color = Color::Rgb { r, g, b };
1572
1573 Some(new_color)
1574 } else {
1575 Some(color)
1576 }
1577}