repeat x times
This commit is contained in:
+110
-235
@@ -1,13 +1,34 @@
|
||||
use std::{cmp::min, collections::hash_set::Entry, convert::identity, fs::File, io::Write, iter, mem::{replace, swap}};
|
||||
use ratatui::{style::Stylize, text::Span};
|
||||
|
||||
use crate::{BYTES_OF_PADDING, BYTES_PER_LINE, LINES_OF_PADDING, app::WindowSize, buffer::{Buffer, Mode, PartialAction}, cursor::Cursor, edit_action::EditAction};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Action {
|
||||
App(AppAction),
|
||||
Buffer(BufferAction),
|
||||
Cursor(CursorAction),
|
||||
}
|
||||
|
||||
// actions that act on the app as a whole, not just one buffer
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum AppAction {
|
||||
QuitIfSaved,
|
||||
Quit,
|
||||
|
||||
PreviousBuffer,
|
||||
NextBuffer,
|
||||
|
||||
Yank,
|
||||
}
|
||||
|
||||
impl From<AppAction> for Action {
|
||||
fn from(app_action: AppAction) -> Self {
|
||||
Self::App(app_action)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum BufferAction {
|
||||
NormalMode,
|
||||
SelectMode,
|
||||
|
||||
@@ -15,21 +36,7 @@ pub enum Action {
|
||||
View,
|
||||
Replace,
|
||||
Space,
|
||||
|
||||
MoveByteUp,
|
||||
MoveByteDown,
|
||||
MoveByteLeft,
|
||||
MoveByteRight,
|
||||
|
||||
ExtendByteUp,
|
||||
ExtendByteDown,
|
||||
ExtendByteLeft,
|
||||
ExtendByteRight,
|
||||
|
||||
GotoLineStart,
|
||||
GotoLineEnd,
|
||||
GotoFileStart,
|
||||
GotoFileEnd,
|
||||
Repeat,
|
||||
|
||||
ScrollDown,
|
||||
ScrollUp,
|
||||
@@ -40,20 +47,9 @@ pub enum Action {
|
||||
PageDown,
|
||||
PageUp,
|
||||
|
||||
MoveNextWordStart,
|
||||
MoveNextWordEnd,
|
||||
MovePreviousWordStart,
|
||||
|
||||
ExtendNextWordStart,
|
||||
ExtendNextWordEnd,
|
||||
ExtendPreviousWordStart,
|
||||
|
||||
CollapseSelection,
|
||||
FlipSelections,
|
||||
|
||||
ExtendLineBelow,
|
||||
ExtendLineAbove,
|
||||
|
||||
Delete,
|
||||
|
||||
Undo,
|
||||
@@ -61,9 +57,6 @@ pub enum Action {
|
||||
|
||||
Save,
|
||||
|
||||
PreviousBuffer,
|
||||
NextBuffer,
|
||||
|
||||
CopySelectionOnNextLine,
|
||||
|
||||
RotateSelectionsBackward,
|
||||
@@ -92,106 +85,105 @@ pub enum Action {
|
||||
AlignViewTop,
|
||||
}
|
||||
|
||||
// actions that act on the app as a whole, not just one buffer
|
||||
pub enum AppAction {
|
||||
QuitIfSaved,
|
||||
Quit,
|
||||
impl From<BufferAction> for Action {
|
||||
fn from(buffer_action: BufferAction) -> Self {
|
||||
Self::Buffer(buffer_action)
|
||||
}
|
||||
}
|
||||
|
||||
PreviousBuffer,
|
||||
NextBuffer,
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum CursorAction {
|
||||
MoveByteUp,
|
||||
MoveByteDown,
|
||||
MoveByteLeft,
|
||||
MoveByteRight,
|
||||
|
||||
ExtendByteUp,
|
||||
ExtendByteDown,
|
||||
ExtendByteLeft,
|
||||
ExtendByteRight,
|
||||
|
||||
GotoLineStart,
|
||||
GotoLineEnd,
|
||||
GotoFileStart,
|
||||
GotoFileEnd,
|
||||
|
||||
MoveNextWordStart,
|
||||
MoveNextWordEnd,
|
||||
MovePreviousWordStart,
|
||||
|
||||
ExtendNextWordStart,
|
||||
ExtendNextWordEnd,
|
||||
ExtendPreviousWordStart,
|
||||
|
||||
ExtendLineBelow,
|
||||
ExtendLineAbove,
|
||||
}
|
||||
|
||||
impl From<CursorAction> for Action {
|
||||
fn from(cursor_action: CursorAction) -> Self {
|
||||
Self::Cursor(cursor_action)
|
||||
}
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
pub fn execute(&mut self, action: Action, window_size: WindowSize) -> Option<AppAction> {
|
||||
pub fn execute(&mut self, action: BufferAction, window_size: WindowSize) {
|
||||
match action {
|
||||
Action::QuitIfSaved => return Some(AppAction::QuitIfSaved),
|
||||
Action::Quit => return Some(AppAction::Quit),
|
||||
BufferAction::NormalMode => self.normal_mode(),
|
||||
BufferAction::SelectMode => self.select_mode(),
|
||||
|
||||
Action::NormalMode => self.normal_mode(),
|
||||
Action::SelectMode => self.select_mode(),
|
||||
BufferAction::Goto => self.goto(),
|
||||
BufferAction::View => self.view(),
|
||||
BufferAction::Replace => self.replace(),
|
||||
BufferAction::Space => self.space(),
|
||||
BufferAction::Repeat => self.repeat(),
|
||||
|
||||
Action::Goto => self.goto(),
|
||||
Action::View => self.view(),
|
||||
Action::Replace => self.replace(),
|
||||
Action::Space => self.space(),
|
||||
BufferAction::ScrollDown => self.scroll_down(window_size),
|
||||
BufferAction::ScrollUp => self.scroll_up(window_size),
|
||||
|
||||
Action::MoveByteUp => self.move_byte_up(window_size),
|
||||
Action::MoveByteDown => self.move_byte_down(window_size),
|
||||
Action::MoveByteLeft => self.move_byte_left(window_size),
|
||||
Action::MoveByteRight => self.move_byte_right(window_size),
|
||||
BufferAction::PageCursorHalfDown => self.page_cursor_half_down(window_size),
|
||||
BufferAction::PageCursorHalfUp => self.page_cursor_half_up(window_size),
|
||||
|
||||
Action::ExtendByteUp => self.extend_byte_up(window_size),
|
||||
Action::ExtendByteDown => self.extend_byte_down(window_size),
|
||||
Action::ExtendByteLeft => self.extend_byte_left(window_size),
|
||||
Action::ExtendByteRight => self.extend_byte_right(window_size),
|
||||
BufferAction::PageDown => self.page_down(window_size),
|
||||
BufferAction::PageUp => self.page_up(window_size),
|
||||
|
||||
Action::GotoLineStart => self.goto_line_start(),
|
||||
Action::GotoLineEnd => self.goto_line_end(),
|
||||
Action::GotoFileStart => self.goto_file_start(window_size),
|
||||
Action::GotoFileEnd => self.goto_file_end(window_size),
|
||||
BufferAction::CollapseSelection => self.collapse_selection(),
|
||||
BufferAction::FlipSelections => self.flip_selection(),
|
||||
|
||||
Action::ScrollDown => self.scroll_down(window_size),
|
||||
Action::ScrollUp => self.scroll_up(window_size),
|
||||
BufferAction::Delete => self.delete(window_size),
|
||||
|
||||
Action::PageCursorHalfDown => self.page_cursor_half_down(window_size),
|
||||
Action::PageCursorHalfUp => self.page_cursor_half_up(window_size),
|
||||
BufferAction::Undo => self.undo(window_size),
|
||||
BufferAction::Redo => self.redo(window_size),
|
||||
|
||||
Action::PageDown => self.page_down(window_size),
|
||||
Action::PageUp => self.page_up(window_size),
|
||||
BufferAction::Save => self.save(),
|
||||
|
||||
Action::MoveNextWordStart => self.move_next_word_start(window_size),
|
||||
Action::MoveNextWordEnd => self.move_next_word_end(window_size),
|
||||
Action::MovePreviousWordStart => self.move_previous_word_start(window_size),
|
||||
BufferAction::CopySelectionOnNextLine => self.copy_selection_on_next_line(),
|
||||
|
||||
Action::ExtendNextWordStart => self.extend_next_word_start(window_size),
|
||||
Action::ExtendNextWordEnd => self.extend_next_word_end(window_size),
|
||||
Action::ExtendPreviousWordStart => self.extend_previous_word_start(window_size),
|
||||
BufferAction::RotateSelectionsBackward => self.rotate_selections_backward(),
|
||||
BufferAction::RotateSelectionsForward => self.rotate_selections_forward(),
|
||||
|
||||
Action::CollapseSelection => self.collapse_selection(),
|
||||
Action::FlipSelections => self.flip_selection(),
|
||||
BufferAction::KeepPrimarySelection => self.keep_primary_selection(),
|
||||
BufferAction::RemovePrimarySelection => self.remove_primary_selection(),
|
||||
|
||||
Action::ExtendLineBelow => self.extend_line_below(window_size),
|
||||
Action::ExtendLineAbove => self.extend_line_above(window_size),
|
||||
BufferAction::SplitSelectionsInto1s => self.split_selections_into_size(1),
|
||||
BufferAction::SplitSelectionsInto2s => self.split_selections_into_size(2),
|
||||
BufferAction::SplitSelectionsInto3s => self.split_selections_into_size(3),
|
||||
BufferAction::SplitSelectionsInto4s => self.split_selections_into_size(4),
|
||||
BufferAction::SplitSelectionsInto5s => self.split_selections_into_size(5),
|
||||
BufferAction::SplitSelectionsInto6s => self.split_selections_into_size(6),
|
||||
BufferAction::SplitSelectionsInto7s => self.split_selections_into_size(7),
|
||||
BufferAction::SplitSelectionsInto8s => self.split_selections_into_size(8),
|
||||
BufferAction::SplitSelectionsInto9s => self.split_selections_into_size(9),
|
||||
|
||||
Action::Delete => self.delete(window_size),
|
||||
BufferAction::JumpToSelectedOffset => self.jump_to_selected_offset(window_size),
|
||||
BufferAction::JumpToSelectedOffsetRelativeToMark => self.jump_to_selected_offset_relative_to_mark(window_size),
|
||||
|
||||
Action::Undo => self.undo(window_size),
|
||||
Action::Redo => self.redo(window_size),
|
||||
BufferAction::ToggleMark => self.toggle_mark(),
|
||||
|
||||
Action::Save => self.save(),
|
||||
|
||||
Action::PreviousBuffer => return Some(AppAction::PreviousBuffer),
|
||||
Action::NextBuffer => return Some(AppAction::NextBuffer),
|
||||
|
||||
Action::CopySelectionOnNextLine => self.copy_selection_on_next_line(),
|
||||
|
||||
Action::RotateSelectionsBackward => self.rotate_selections_backward(),
|
||||
Action::RotateSelectionsForward => self.rotate_selections_forward(),
|
||||
|
||||
Action::KeepPrimarySelection => self.keep_primary_selection(),
|
||||
Action::RemovePrimarySelection => self.remove_primary_selection(),
|
||||
|
||||
Action::SplitSelectionsInto1s => self.split_selections_into_size(1),
|
||||
Action::SplitSelectionsInto2s => self.split_selections_into_size(2),
|
||||
Action::SplitSelectionsInto3s => self.split_selections_into_size(3),
|
||||
Action::SplitSelectionsInto4s => self.split_selections_into_size(4),
|
||||
Action::SplitSelectionsInto5s => self.split_selections_into_size(5),
|
||||
Action::SplitSelectionsInto6s => self.split_selections_into_size(6),
|
||||
Action::SplitSelectionsInto7s => self.split_selections_into_size(7),
|
||||
Action::SplitSelectionsInto8s => self.split_selections_into_size(8),
|
||||
Action::SplitSelectionsInto9s => self.split_selections_into_size(9),
|
||||
|
||||
Action::JumpToSelectedOffset => self.jump_to_selected_offset(window_size),
|
||||
Action::JumpToSelectedOffsetRelativeToMark => self.jump_to_selected_offset_relative_to_mark(window_size),
|
||||
|
||||
Action::ToggleMark => self.toggle_mark(),
|
||||
|
||||
Action::AlignViewCenter => self.align_view_center(window_size),
|
||||
Action::AlignViewBottom => self.align_view_bottom(window_size),
|
||||
Action::AlignViewTop => self.align_view_top(),
|
||||
BufferAction::AlignViewCenter => self.align_view_center(window_size),
|
||||
BufferAction::AlignViewBottom => self.align_view_bottom(window_size),
|
||||
BufferAction::AlignViewTop => self.align_view_top(),
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
const fn normal_mode(&mut self) {
|
||||
@@ -220,79 +212,8 @@ impl Buffer {
|
||||
self.partial_action = Some(PartialAction::Space);
|
||||
}
|
||||
|
||||
fn change_all_cursors(&mut self, transform: impl Fn(&mut Cursor)) {
|
||||
transform(&mut self.primary_cursor);
|
||||
|
||||
for cursor in &mut self.cursors {
|
||||
transform(cursor);
|
||||
}
|
||||
self.cursors.sort_by_key(|cursor| cursor.head);
|
||||
|
||||
self.combine_cursors_if_overlapping();
|
||||
}
|
||||
|
||||
fn move_byte_up(&mut self, window_size: WindowSize) {
|
||||
self.change_all_cursors(Cursor::move_byte_up);
|
||||
self.clamp_screen_to_primary_cursor(window_size);
|
||||
}
|
||||
|
||||
fn move_byte_down(&mut self, window_size: WindowSize) {
|
||||
let max_contents_index = self.max_contents_index();
|
||||
self.change_all_cursors(|cursor| cursor.move_byte_down(max_contents_index));
|
||||
self.clamp_screen_to_primary_cursor(window_size);
|
||||
}
|
||||
|
||||
fn move_byte_left(&mut self, window_size: WindowSize) {
|
||||
self.change_all_cursors(Cursor::move_byte_left);
|
||||
self.clamp_screen_to_primary_cursor(window_size);
|
||||
}
|
||||
|
||||
fn move_byte_right(&mut self, window_size: WindowSize) {
|
||||
let max_contents_index = self.max_contents_index();
|
||||
self.change_all_cursors(|cursor| cursor.move_byte_right(max_contents_index));
|
||||
self.clamp_screen_to_primary_cursor(window_size);
|
||||
}
|
||||
|
||||
fn extend_byte_up(&mut self, window_size: WindowSize) {
|
||||
self.change_all_cursors(Cursor::extend_byte_up);
|
||||
self.clamp_screen_to_primary_cursor(window_size);
|
||||
}
|
||||
|
||||
fn extend_byte_down(&mut self, window_size: WindowSize) {
|
||||
let max_contents_index = self.max_contents_index();
|
||||
self.change_all_cursors(|cursor| cursor.extend_byte_down(max_contents_index));
|
||||
self.clamp_screen_to_primary_cursor(window_size);
|
||||
}
|
||||
|
||||
fn extend_byte_left(&mut self, window_size: WindowSize) {
|
||||
self.change_all_cursors(Cursor::extend_byte_left);
|
||||
self.clamp_screen_to_primary_cursor(window_size);
|
||||
}
|
||||
|
||||
fn extend_byte_right(&mut self, window_size: WindowSize) {
|
||||
let max_contents_index = self.max_contents_index();
|
||||
self.change_all_cursors(|cursor| cursor.extend_byte_right(max_contents_index));
|
||||
self.clamp_screen_to_primary_cursor(window_size);
|
||||
}
|
||||
|
||||
fn goto_line_start(&mut self) {
|
||||
self.change_all_cursors(Cursor::goto_line_start);
|
||||
}
|
||||
|
||||
fn goto_line_end(&mut self) {
|
||||
let max_contents_index = self.max_contents_index();
|
||||
self.change_all_cursors(|cursor| cursor.goto_line_end(max_contents_index));
|
||||
}
|
||||
|
||||
fn goto_file_start(&mut self, window_size: WindowSize) {
|
||||
self.change_all_cursors(Cursor::goto_file_start);
|
||||
self.clamp_screen_to_primary_cursor(window_size);
|
||||
}
|
||||
|
||||
fn goto_file_end(&mut self, window_size: WindowSize) {
|
||||
let max_contents_index = self.max_contents_index();
|
||||
self.change_all_cursors(|cursor| cursor.goto_file_end(max_contents_index));
|
||||
self.clamp_screen_to_primary_cursor(window_size);
|
||||
const fn repeat(&mut self) {
|
||||
self.partial_action = Some(PartialAction::Repeat);
|
||||
}
|
||||
|
||||
pub fn scroll_down(&mut self, window_size: WindowSize) {
|
||||
@@ -419,40 +340,6 @@ impl Buffer {
|
||||
self.combine_cursors_if_overlapping();
|
||||
}
|
||||
|
||||
fn move_next_word_start(&mut self, window_size: WindowSize) {
|
||||
let max_contents_index = self.max_contents_index();
|
||||
self.change_all_cursors(|cursor| cursor.move_next_word_start(max_contents_index));
|
||||
self.clamp_screen_to_primary_cursor(window_size);
|
||||
}
|
||||
|
||||
fn move_next_word_end(&mut self, window_size: WindowSize) {
|
||||
let max_contents_index = self.max_contents_index();
|
||||
self.change_all_cursors(|cursor| cursor.move_next_word_end(max_contents_index));
|
||||
self.clamp_screen_to_primary_cursor(window_size);
|
||||
}
|
||||
|
||||
fn move_previous_word_start(&mut self, window_size: WindowSize) {
|
||||
self.change_all_cursors(Cursor::move_previous_word_start);
|
||||
self.clamp_screen_to_primary_cursor(window_size);
|
||||
}
|
||||
|
||||
fn extend_next_word_start(&mut self, window_size: WindowSize) {
|
||||
let max_contents_index = self.max_contents_index();
|
||||
self.change_all_cursors(|cursor| cursor.extend_next_word_start(max_contents_index));
|
||||
self.clamp_screen_to_primary_cursor(window_size);
|
||||
}
|
||||
|
||||
fn extend_next_word_end(&mut self, window_size: WindowSize) {
|
||||
let max_contents_index = self.max_contents_index();
|
||||
self.change_all_cursors(|cursor| cursor.extend_next_word_end(max_contents_index));
|
||||
self.clamp_screen_to_primary_cursor(window_size);
|
||||
}
|
||||
|
||||
fn extend_previous_word_start(&mut self, window_size: WindowSize) {
|
||||
self.change_all_cursors(Cursor::extend_previous_word_start);
|
||||
self.clamp_screen_to_primary_cursor(window_size);
|
||||
}
|
||||
|
||||
fn collapse_selection(&mut self) {
|
||||
self.primary_cursor.collapse();
|
||||
|
||||
@@ -469,18 +356,6 @@ impl Buffer {
|
||||
}
|
||||
}
|
||||
|
||||
fn extend_line_below(&mut self, window_size: WindowSize) {
|
||||
let max_contents_index = self.max_contents_index();
|
||||
self.change_all_cursors(|cursor| cursor.extend_line_below(max_contents_index));
|
||||
self.clamp_screen_to_primary_cursor(window_size);
|
||||
}
|
||||
|
||||
fn extend_line_above(&mut self, window_size: WindowSize) {
|
||||
let max_contents_index = self.max_contents_index();
|
||||
self.change_all_cursors(|cursor| cursor.extend_line_above(max_contents_index));
|
||||
self.clamp_screen_to_primary_cursor(window_size);
|
||||
}
|
||||
|
||||
fn delete(&mut self, window_size: WindowSize) {
|
||||
if !self.contents.is_empty() {
|
||||
self.execute_and_add(
|
||||
@@ -658,7 +533,7 @@ impl Buffer {
|
||||
if !iter::once(&self.primary_cursor)
|
||||
.chain(&self.cursors)
|
||||
.all(|cursor| {
|
||||
bytes_as_nat(&self.contents[cursor.range()])
|
||||
bytes_to_nat(&self.contents[cursor.range()])
|
||||
.is_some_and(|offset| offset < self.contents.len())
|
||||
})
|
||||
{
|
||||
@@ -675,12 +550,12 @@ impl Buffer {
|
||||
}
|
||||
|
||||
self.primary_cursor = Cursor::at(
|
||||
bytes_as_nat(&self.contents[self.primary_cursor.range()]).unwrap()
|
||||
bytes_to_nat(&self.contents[self.primary_cursor.range()]).unwrap()
|
||||
);
|
||||
|
||||
for cursor in &mut self.cursors {
|
||||
*cursor = Cursor::at(
|
||||
bytes_as_nat(&self.contents[cursor.range()]).unwrap()
|
||||
bytes_to_nat(&self.contents[cursor.range()]).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -697,7 +572,7 @@ impl Buffer {
|
||||
if !iter::once(&self.primary_cursor)
|
||||
.chain(&self.cursors)
|
||||
.all(|cursor| {
|
||||
bytes_as_nat(&self.contents[cursor.range()])
|
||||
bytes_to_nat(&self.contents[cursor.range()])
|
||||
.map(|offset| mark_before(cursor.lower_bound(), &sorted_marks) + offset)
|
||||
.is_some_and(|offset| offset < self.contents.len())
|
||||
})
|
||||
@@ -715,7 +590,7 @@ impl Buffer {
|
||||
}
|
||||
|
||||
self.primary_cursor = Cursor::at(
|
||||
bytes_as_nat(&self.contents[self.primary_cursor.range()])
|
||||
bytes_to_nat(&self.contents[self.primary_cursor.range()])
|
||||
.map(|offset| {
|
||||
mark_before(self.primary_cursor.lower_bound(), &sorted_marks) + offset
|
||||
})
|
||||
@@ -724,7 +599,7 @@ impl Buffer {
|
||||
|
||||
for cursor in &mut self.cursors {
|
||||
*cursor = Cursor::at(
|
||||
bytes_as_nat(&self.contents[cursor.range()])
|
||||
bytes_to_nat(&self.contents[cursor.range()])
|
||||
.map(|offset| {
|
||||
mark_before(cursor.lower_bound(), &sorted_marks) + offset
|
||||
})
|
||||
@@ -789,7 +664,7 @@ impl Buffer {
|
||||
}
|
||||
}
|
||||
|
||||
fn bytes_as_nat(bytes: &[u8]) -> Option<usize> {
|
||||
pub fn bytes_to_nat(bytes: &[u8]) -> Option<usize> {
|
||||
bytes
|
||||
.iter()
|
||||
.rev() // little-endian
|
||||
|
||||
@@ -11,6 +11,9 @@ pub struct App {
|
||||
pub buffers: Vec<Buffer>,
|
||||
pub current_buffer_index: usize,
|
||||
|
||||
pub primary_cursor_register: Vec<u8>,
|
||||
pub other_cursor_registers: Vec<Vec<u8>>,
|
||||
|
||||
pub window_size: WindowSize,
|
||||
|
||||
pub should_quit: bool,
|
||||
@@ -52,6 +55,9 @@ impl App {
|
||||
buffers,
|
||||
current_buffer_index: 0,
|
||||
|
||||
primary_cursor_register: Vec::new(),
|
||||
other_cursor_registers: Vec::new(),
|
||||
|
||||
window_size,
|
||||
|
||||
should_quit: false,
|
||||
@@ -84,6 +90,8 @@ impl App {
|
||||
let maybe_app_action = self.buffers[self.current_buffer_index].handle_key(
|
||||
key_event,
|
||||
&self.config,
|
||||
&self.primary_cursor_register,
|
||||
&self.other_cursor_registers,
|
||||
self.window_size
|
||||
);
|
||||
|
||||
@@ -94,6 +102,8 @@ impl App {
|
||||
|
||||
AppAction::PreviousBuffer => self.previous_buffer(),
|
||||
AppAction::NextBuffer => self.next_buffer(),
|
||||
|
||||
AppAction::Yank => self.yank(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,6 +194,19 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
fn yank(&mut self) {
|
||||
self.primary_cursor_register = self.current_buffer()
|
||||
.contents[self.current_buffer().primary_cursor.range()]
|
||||
.to_vec();
|
||||
|
||||
self.other_cursor_registers = self.current_buffer().cursors
|
||||
.iter()
|
||||
.map(|cursor| {
|
||||
self.current_buffer().contents[cursor.range()].to_vec()
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
pub fn current_buffer(&self) -> &Buffer {
|
||||
&self.buffers[self.current_buffer_index]
|
||||
}
|
||||
|
||||
+145
-38
@@ -1,8 +1,8 @@
|
||||
use core::slice::GetDisjointMutIndex;
|
||||
use std::{collections::HashSet, fs::File, io::Read, path::PathBuf};
|
||||
use crossterm::event::KeyEvent;
|
||||
use ratatui::{style::Color, text::Span};
|
||||
use crate::{action::AppAction, app::WindowSize, config::Config, cursor::Cursor, edit_action::EditAction};
|
||||
use ratatui::{style::{Color, Stylize}, text::Span};
|
||||
use crate::{BYTES_PER_LINE, action::{Action, AppAction, bytes_to_nat}, app::WindowSize, config::Config, cursor::Cursor, edit_action::EditAction};
|
||||
|
||||
mod widget;
|
||||
|
||||
@@ -40,7 +40,7 @@ pub enum Mode {
|
||||
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub enum PartialAction {
|
||||
Goto, View, Replace, Space
|
||||
Goto, View, Replace, Space, Repeat
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
@@ -68,6 +68,7 @@ impl PartialAction {
|
||||
Self::View => "z",
|
||||
Self::Replace => "r",
|
||||
Self::Space => "␠",
|
||||
Self::Repeat => "×",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,54 +109,160 @@ impl Buffer {
|
||||
&mut self,
|
||||
event: KeyEvent,
|
||||
config: &Config,
|
||||
primary_cursor_register: &[u8],
|
||||
other_cursor_registers: &[Vec<u8>],
|
||||
window_size: WindowSize
|
||||
) -> Option<AppAction> {
|
||||
self.alert_message = "".into();
|
||||
|
||||
let mut app_action = None;
|
||||
let app_action = match self.partial_action {
|
||||
Some(PartialAction::Replace) => {
|
||||
self.handle_replace(event, window_size);
|
||||
None
|
||||
},
|
||||
Some(PartialAction::Repeat) => {
|
||||
self.handle_repeat(
|
||||
event,
|
||||
config,
|
||||
primary_cursor_register,
|
||||
other_cursor_registers,
|
||||
window_size
|
||||
);
|
||||
None
|
||||
},
|
||||
_ => self.handle_other_modes(event, config, window_size),
|
||||
};
|
||||
|
||||
if self.partial_action == Some(PartialAction::Replace) {
|
||||
if let Some(hex_character) = event.code.as_char() &&
|
||||
let Some(nybble) = nybble_from_hex(hex_character)
|
||||
{
|
||||
if let Some(partial_replace) = self.partial_replace.take() {
|
||||
self.execute_and_add(
|
||||
EditAction::Replace {
|
||||
primary_cursor: self.primary_cursor,
|
||||
cursors: self.cursors.clone(),
|
||||
primary_old_data: self.contents[self.primary_cursor.range()].to_vec(),
|
||||
old_data: self.cursors
|
||||
.iter()
|
||||
.map(|cursor| self.contents[cursor.range()].to_vec())
|
||||
.collect(),
|
||||
new_byte: partial_replace << 4 | nybble
|
||||
},
|
||||
window_size
|
||||
);
|
||||
self.partial_action = None;
|
||||
} else {
|
||||
self.partial_replace = Some(nybble);
|
||||
}
|
||||
} else {
|
||||
assert!(self.scroll_position.is_multiple_of(BYTES_PER_LINE));
|
||||
assert!(self.scroll_position < self.contents.len());
|
||||
assert!(self.primary_cursor.head < self.contents.len());
|
||||
assert!(self.primary_cursor.tail < self.contents.len());
|
||||
assert!(self.scroll_position <= self.primary_cursor.head);
|
||||
assert!(self.primary_cursor.head < self.scroll_position + window_size.visible_byte_count());
|
||||
|
||||
app_action
|
||||
}
|
||||
|
||||
fn handle_replace(&mut self, event: KeyEvent, window_size: WindowSize) {
|
||||
if let Some(hex_character) = event.code.as_char() &&
|
||||
let Some(nybble) = nybble_from_hex(hex_character)
|
||||
{
|
||||
if let Some(partial_replace) = self.partial_replace.take() {
|
||||
self.execute_and_add(
|
||||
EditAction::Replace {
|
||||
primary_cursor: self.primary_cursor,
|
||||
cursors: self.cursors.clone(),
|
||||
primary_old_data: self.contents[self.primary_cursor.range()].to_vec(),
|
||||
old_data: self.cursors
|
||||
.iter()
|
||||
.map(|cursor| self.contents[cursor.range()].to_vec())
|
||||
.collect(),
|
||||
new_byte: partial_replace << 4 | nybble
|
||||
},
|
||||
window_size
|
||||
);
|
||||
self.partial_action = None;
|
||||
self.partial_replace = None;
|
||||
} else {
|
||||
self.partial_replace = Some(nybble);
|
||||
}
|
||||
} else {
|
||||
let should_reset_partial = self.partial_action.is_some();
|
||||
self.partial_action = None;
|
||||
self.partial_replace = None;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mode_config) = config.0.get(&self.mode) &&
|
||||
let Some(keybinds) = mode_config.0.get(&self.partial_action) &&
|
||||
let Some(action) = keybinds.0.get(&event.into())
|
||||
{
|
||||
app_action = self.execute(*action, window_size);
|
||||
}
|
||||
fn handle_other_modes(
|
||||
&mut self,
|
||||
event: KeyEvent,
|
||||
config: &Config,
|
||||
window_size: WindowSize
|
||||
) -> Option<AppAction> {
|
||||
let mut result = None;
|
||||
|
||||
if should_reset_partial {
|
||||
self.partial_action = None;
|
||||
let should_reset_partial = self.partial_action.is_some();
|
||||
|
||||
if let Some(mode_config) = config.0.get(&self.mode) &&
|
||||
let Some(keybinds) = mode_config.0.get(&self.partial_action) &&
|
||||
let Some(action) = keybinds.0.get(&event.into())
|
||||
{
|
||||
match action {
|
||||
Action::App(app_action) => result = Some(*app_action),
|
||||
Action::Buffer(buffer_action) => self.execute(*buffer_action, window_size),
|
||||
Action::Cursor(cursor_action) => {
|
||||
let max_contents_index = self.max_contents_index();
|
||||
|
||||
self.primary_cursor.execute(*cursor_action, max_contents_index);
|
||||
|
||||
for cursor in &mut self.cursors {
|
||||
cursor.execute(*cursor_action, max_contents_index);
|
||||
}
|
||||
self.cursors.sort_by_key(|cursor| cursor.head);
|
||||
|
||||
self.combine_cursors_if_overlapping();
|
||||
self.clamp_screen_to_primary_cursor(window_size);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
app_action
|
||||
if should_reset_partial {
|
||||
self.partial_action = None;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn handle_repeat(
|
||||
&mut self,
|
||||
event: KeyEvent,
|
||||
config: &Config,
|
||||
primary_cursor_register: &[u8],
|
||||
other_cursor_registers: &[Vec<u8>],
|
||||
window_size: WindowSize
|
||||
) {
|
||||
self.partial_action = None;
|
||||
|
||||
if let Some(mode_config) = config.0.get(&self.mode) &&
|
||||
let Some(keybinds) = mode_config.0.get(&Some(PartialAction::Repeat)) &&
|
||||
let Some(action) = keybinds.0.get(&event.into())
|
||||
{
|
||||
match action {
|
||||
Action::Cursor(cursor_action) => {
|
||||
let Some(primary_repeat_count) = bytes_to_nat(primary_cursor_register) else {
|
||||
self.alert_message = Span::from(
|
||||
"repeat count is too large"
|
||||
).red();
|
||||
return;
|
||||
};
|
||||
let other_repeat_counts = other_cursor_registers
|
||||
.iter()
|
||||
.map(|register| bytes_to_nat(register));
|
||||
|
||||
if other_repeat_counts.clone().any(|count| count.is_none()) {
|
||||
self.alert_message = Span::from(
|
||||
"repeat count is too large"
|
||||
).red();
|
||||
return;
|
||||
}
|
||||
|
||||
let max_contents_index = self.max_contents_index();
|
||||
|
||||
for _ in 0..primary_repeat_count {
|
||||
self.primary_cursor.execute(*cursor_action, max_contents_index);
|
||||
}
|
||||
|
||||
for (cursor, repeat_count) in self.cursors.iter_mut().zip(other_repeat_counts) {
|
||||
for _ in 0..repeat_count.unwrap() {
|
||||
cursor.execute(*cursor_action, max_contents_index);
|
||||
}
|
||||
}
|
||||
self.cursors.sort_by_key(|cursor| cursor.head);
|
||||
|
||||
self.combine_cursors_if_overlapping();
|
||||
self.clamp_screen_to_primary_cursor(window_size);
|
||||
},
|
||||
_ => panic!("repeated actions may only be cursor actions"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn has_unsaved_changes(&self) -> bool {
|
||||
|
||||
+161
-103
@@ -1,6 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
use crate::{action::Action, buffer::{Mode, PartialAction}};
|
||||
use crate::{action::{Action, AppAction, BufferAction, CursorAction}, buffer::{Mode, PartialAction}};
|
||||
|
||||
pub struct Config(
|
||||
pub HashMap<Mode, ModeConfig>
|
||||
@@ -97,159 +97,217 @@ impl Default for Config {
|
||||
[
|
||||
(Mode::Normal, [
|
||||
(None, [
|
||||
("q".try_into().unwrap(), Action::QuitIfSaved),
|
||||
("Q".try_into().unwrap(), Action::Quit),
|
||||
("q".try_into().unwrap(), AppAction::QuitIfSaved.into()),
|
||||
("Q".try_into().unwrap(), AppAction::Quit.into()),
|
||||
|
||||
("v".try_into().unwrap(), Action::SelectMode),
|
||||
("v".try_into().unwrap(), BufferAction::SelectMode.into()),
|
||||
|
||||
("g".try_into().unwrap(), Action::Goto),
|
||||
("z".try_into().unwrap(), Action::View),
|
||||
("r".try_into().unwrap(), Action::Replace),
|
||||
(" ".try_into().unwrap(), Action::Space),
|
||||
("g".try_into().unwrap(), BufferAction::Goto.into()),
|
||||
("z".try_into().unwrap(), BufferAction::View.into()),
|
||||
("r".try_into().unwrap(), BufferAction::Replace.into()),
|
||||
(" ".try_into().unwrap(), BufferAction::Space.into()),
|
||||
("*".try_into().unwrap(), BufferAction::Repeat.into()),
|
||||
|
||||
("i".try_into().unwrap(), Action::MoveByteUp),
|
||||
("k".try_into().unwrap(), Action::MoveByteDown),
|
||||
("j".try_into().unwrap(), Action::MoveByteLeft),
|
||||
("l".try_into().unwrap(), Action::MoveByteRight),
|
||||
("i".try_into().unwrap(), CursorAction::MoveByteUp.into()),
|
||||
("k".try_into().unwrap(), CursorAction::MoveByteDown.into()),
|
||||
("j".try_into().unwrap(), CursorAction::MoveByteLeft.into()),
|
||||
("l".try_into().unwrap(), CursorAction::MoveByteRight.into()),
|
||||
|
||||
("G".try_into().unwrap(), Action::GotoFileEnd),
|
||||
("G".try_into().unwrap(), CursorAction::GotoFileEnd.into()),
|
||||
|
||||
("C-e".try_into().unwrap(), Action::ScrollDown),
|
||||
("C-y".try_into().unwrap(), Action::ScrollUp),
|
||||
("C-e".try_into().unwrap(), BufferAction::ScrollDown.into()),
|
||||
("C-y".try_into().unwrap(), BufferAction::ScrollUp.into()),
|
||||
|
||||
("C-d".try_into().unwrap(), Action::PageCursorHalfDown),
|
||||
("C-u".try_into().unwrap(), Action::PageCursorHalfUp),
|
||||
("C-d".try_into().unwrap(), BufferAction::PageCursorHalfDown.into()),
|
||||
("C-u".try_into().unwrap(), BufferAction::PageCursorHalfUp.into()),
|
||||
|
||||
("C-f".try_into().unwrap(), Action::PageDown),
|
||||
("C-b".try_into().unwrap(), Action::PageUp),
|
||||
("C-f".try_into().unwrap(), BufferAction::PageDown.into()),
|
||||
("C-b".try_into().unwrap(), BufferAction::PageUp.into()),
|
||||
|
||||
("w".try_into().unwrap(), Action::MoveNextWordStart),
|
||||
("e".try_into().unwrap(), Action::MoveNextWordEnd),
|
||||
("b".try_into().unwrap(), Action::MovePreviousWordStart),
|
||||
("w".try_into().unwrap(), CursorAction::MoveNextWordStart.into()),
|
||||
("e".try_into().unwrap(), CursorAction::MoveNextWordEnd.into()),
|
||||
("b".try_into().unwrap(), CursorAction::MovePreviousWordStart.into()),
|
||||
|
||||
(";".try_into().unwrap(), Action::CollapseSelection),
|
||||
("A-;".try_into().unwrap(), Action::FlipSelections),
|
||||
(";".try_into().unwrap(), BufferAction::CollapseSelection.into()),
|
||||
("A-;".try_into().unwrap(), BufferAction::FlipSelections.into()),
|
||||
|
||||
("x".try_into().unwrap(), Action::ExtendLineBelow),
|
||||
("X".try_into().unwrap(), Action::ExtendLineAbove),
|
||||
("x".try_into().unwrap(), CursorAction::ExtendLineBelow.into()),
|
||||
("X".try_into().unwrap(), CursorAction::ExtendLineAbove.into()),
|
||||
|
||||
("d".try_into().unwrap(), Action::Delete),
|
||||
("d".try_into().unwrap(), BufferAction::Delete.into()),
|
||||
|
||||
("u".try_into().unwrap(), Action::Undo),
|
||||
("U".try_into().unwrap(), Action::Redo),
|
||||
("u".try_into().unwrap(), BufferAction::Undo.into()),
|
||||
("U".try_into().unwrap(), BufferAction::Redo.into()),
|
||||
|
||||
("C-j".try_into().unwrap(), Action::PreviousBuffer),
|
||||
("C-l".try_into().unwrap(), Action::NextBuffer),
|
||||
("C-j".try_into().unwrap(), AppAction::PreviousBuffer.into()),
|
||||
("C-l".try_into().unwrap(), AppAction::NextBuffer.into()),
|
||||
|
||||
("C".try_into().unwrap(), Action::CopySelectionOnNextLine),
|
||||
("C".try_into().unwrap(), BufferAction::CopySelectionOnNextLine.into()),
|
||||
|
||||
("(".try_into().unwrap(), Action::RotateSelectionsBackward),
|
||||
(")".try_into().unwrap(), Action::RotateSelectionsForward),
|
||||
("(".try_into().unwrap(), BufferAction::RotateSelectionsBackward.into()),
|
||||
(")".try_into().unwrap(), BufferAction::RotateSelectionsForward.into()),
|
||||
|
||||
(",".try_into().unwrap(), Action::KeepPrimarySelection),
|
||||
("A-,".try_into().unwrap(), Action::RemovePrimarySelection),
|
||||
(",".try_into().unwrap(), BufferAction::KeepPrimarySelection.into()),
|
||||
("A-,".try_into().unwrap(), BufferAction::RemovePrimarySelection.into()),
|
||||
|
||||
("1".try_into().unwrap(), Action::SplitSelectionsInto1s),
|
||||
("2".try_into().unwrap(), Action::SplitSelectionsInto2s),
|
||||
("3".try_into().unwrap(), Action::SplitSelectionsInto3s),
|
||||
("4".try_into().unwrap(), Action::SplitSelectionsInto4s),
|
||||
("5".try_into().unwrap(), Action::SplitSelectionsInto5s),
|
||||
("6".try_into().unwrap(), Action::SplitSelectionsInto6s),
|
||||
("7".try_into().unwrap(), Action::SplitSelectionsInto7s),
|
||||
("8".try_into().unwrap(), Action::SplitSelectionsInto8s),
|
||||
("9".try_into().unwrap(), Action::SplitSelectionsInto9s),
|
||||
("1".try_into().unwrap(), BufferAction::SplitSelectionsInto1s.into()),
|
||||
("2".try_into().unwrap(), BufferAction::SplitSelectionsInto2s.into()),
|
||||
("3".try_into().unwrap(), BufferAction::SplitSelectionsInto3s.into()),
|
||||
("4".try_into().unwrap(), BufferAction::SplitSelectionsInto4s.into()),
|
||||
("5".try_into().unwrap(), BufferAction::SplitSelectionsInto5s.into()),
|
||||
("6".try_into().unwrap(), BufferAction::SplitSelectionsInto6s.into()),
|
||||
("7".try_into().unwrap(), BufferAction::SplitSelectionsInto7s.into()),
|
||||
("8".try_into().unwrap(), BufferAction::SplitSelectionsInto8s.into()),
|
||||
("9".try_into().unwrap(), BufferAction::SplitSelectionsInto9s.into()),
|
||||
|
||||
("J".try_into().unwrap(), Action::JumpToSelectedOffsetRelativeToMark),
|
||||
("A-J".try_into().unwrap(), Action::JumpToSelectedOffset),
|
||||
("J".try_into().unwrap(), BufferAction::JumpToSelectedOffsetRelativeToMark.into()),
|
||||
("A-J".try_into().unwrap(), BufferAction::JumpToSelectedOffset.into()),
|
||||
|
||||
("m".try_into().unwrap(), Action::ToggleMark),
|
||||
("m".try_into().unwrap(), BufferAction::ToggleMark.into()),
|
||||
|
||||
("y".try_into().unwrap(), AppAction::Yank.into()),
|
||||
].into()),
|
||||
(Some(PartialAction::Goto), [
|
||||
("j".try_into().unwrap(), Action::GotoLineStart),
|
||||
("l".try_into().unwrap(), Action::GotoLineEnd),
|
||||
("j".try_into().unwrap(), CursorAction::GotoLineStart.into()),
|
||||
("l".try_into().unwrap(), CursorAction::GotoLineEnd.into()),
|
||||
|
||||
("g".try_into().unwrap(), Action::GotoFileStart),
|
||||
("g".try_into().unwrap(), CursorAction::GotoFileStart.into()),
|
||||
].into()),
|
||||
(Some(PartialAction::View), [
|
||||
("z".try_into().unwrap(), Action::AlignViewCenter),
|
||||
("b".try_into().unwrap(), Action::AlignViewBottom),
|
||||
("t".try_into().unwrap(), Action::AlignViewTop),
|
||||
("z".try_into().unwrap(), BufferAction::AlignViewCenter.into()),
|
||||
("b".try_into().unwrap(), BufferAction::AlignViewBottom.into()),
|
||||
("t".try_into().unwrap(), BufferAction::AlignViewTop.into()),
|
||||
].into()),
|
||||
(Some(PartialAction::Space), [
|
||||
("w".try_into().unwrap(), Action::Save),
|
||||
("w".try_into().unwrap(), BufferAction::Save.into()),
|
||||
].into()),
|
||||
(Some(PartialAction::Repeat), [
|
||||
("i".try_into().unwrap(), CursorAction::MoveByteUp.into()),
|
||||
("k".try_into().unwrap(), CursorAction::MoveByteDown.into()),
|
||||
("j".try_into().unwrap(), CursorAction::MoveByteLeft.into()),
|
||||
("l".try_into().unwrap(), CursorAction::MoveByteRight.into()),
|
||||
|
||||
("C-e".try_into().unwrap(), BufferAction::ScrollDown.into()),
|
||||
("C-y".try_into().unwrap(), BufferAction::ScrollUp.into()),
|
||||
|
||||
("C-d".try_into().unwrap(), BufferAction::PageCursorHalfDown.into()),
|
||||
("C-u".try_into().unwrap(), BufferAction::PageCursorHalfUp.into()),
|
||||
|
||||
("C-f".try_into().unwrap(), BufferAction::PageDown.into()),
|
||||
("C-b".try_into().unwrap(), BufferAction::PageUp.into()),
|
||||
|
||||
("w".try_into().unwrap(), CursorAction::MoveNextWordStart.into()),
|
||||
("e".try_into().unwrap(), CursorAction::MoveNextWordEnd.into()),
|
||||
("b".try_into().unwrap(), CursorAction::MovePreviousWordStart.into()),
|
||||
|
||||
("x".try_into().unwrap(), CursorAction::ExtendLineBelow.into()),
|
||||
("X".try_into().unwrap(), CursorAction::ExtendLineAbove.into()),
|
||||
|
||||
("d".try_into().unwrap(), BufferAction::Delete.into()),
|
||||
|
||||
("C".try_into().unwrap(), BufferAction::CopySelectionOnNextLine.into()),
|
||||
].into()),
|
||||
].into()),
|
||||
(Mode::Select, [
|
||||
(None, [
|
||||
("q".try_into().unwrap(), Action::QuitIfSaved),
|
||||
("Q".try_into().unwrap(), Action::Quit),
|
||||
("q".try_into().unwrap(), AppAction::QuitIfSaved.into()),
|
||||
("Q".try_into().unwrap(), AppAction::Quit.into()),
|
||||
|
||||
("v".try_into().unwrap(), Action::NormalMode),
|
||||
("v".try_into().unwrap(), BufferAction::NormalMode.into()),
|
||||
|
||||
("g".try_into().unwrap(), Action::Goto),
|
||||
("z".try_into().unwrap(), Action::View),
|
||||
("r".try_into().unwrap(), Action::Replace),
|
||||
(" ".try_into().unwrap(), Action::Space),
|
||||
("g".try_into().unwrap(), BufferAction::Goto.into()),
|
||||
("z".try_into().unwrap(), BufferAction::View.into()),
|
||||
("r".try_into().unwrap(), BufferAction::Replace.into()),
|
||||
(" ".try_into().unwrap(), BufferAction::Space.into()),
|
||||
("*".try_into().unwrap(), BufferAction::Repeat.into()),
|
||||
|
||||
("i".try_into().unwrap(), Action::ExtendByteUp),
|
||||
("k".try_into().unwrap(), Action::ExtendByteDown),
|
||||
("j".try_into().unwrap(), Action::ExtendByteLeft),
|
||||
("l".try_into().unwrap(), Action::ExtendByteRight),
|
||||
("i".try_into().unwrap(), CursorAction::ExtendByteUp.into()),
|
||||
("k".try_into().unwrap(), CursorAction::ExtendByteDown.into()),
|
||||
("j".try_into().unwrap(), CursorAction::ExtendByteLeft.into()),
|
||||
("l".try_into().unwrap(), CursorAction::ExtendByteRight.into()),
|
||||
|
||||
("C-e".try_into().unwrap(), Action::ScrollDown),
|
||||
("C-y".try_into().unwrap(), Action::ScrollUp),
|
||||
("C-e".try_into().unwrap(), BufferAction::ScrollDown.into()),
|
||||
("C-y".try_into().unwrap(), BufferAction::ScrollUp.into()),
|
||||
|
||||
("C-d".try_into().unwrap(), Action::PageCursorHalfDown),
|
||||
("C-u".try_into().unwrap(), Action::PageCursorHalfUp),
|
||||
("C-d".try_into().unwrap(), BufferAction::PageCursorHalfDown.into()),
|
||||
("C-u".try_into().unwrap(), BufferAction::PageCursorHalfUp.into()),
|
||||
|
||||
("C-f".try_into().unwrap(), Action::PageDown),
|
||||
("C-b".try_into().unwrap(), Action::PageUp),
|
||||
("C-f".try_into().unwrap(), BufferAction::PageDown.into()),
|
||||
("C-b".try_into().unwrap(), BufferAction::PageUp.into()),
|
||||
|
||||
("w".try_into().unwrap(), Action::ExtendNextWordStart),
|
||||
("e".try_into().unwrap(), Action::ExtendNextWordEnd),
|
||||
("b".try_into().unwrap(), Action::ExtendPreviousWordStart),
|
||||
("w".try_into().unwrap(), CursorAction::ExtendNextWordStart.into()),
|
||||
("e".try_into().unwrap(), CursorAction::ExtendNextWordEnd.into()),
|
||||
("b".try_into().unwrap(), CursorAction::ExtendPreviousWordStart.into()),
|
||||
|
||||
(";".try_into().unwrap(), Action::CollapseSelection),
|
||||
("A-;".try_into().unwrap(), Action::FlipSelections),
|
||||
(";".try_into().unwrap(), BufferAction::CollapseSelection.into()),
|
||||
("A-;".try_into().unwrap(), BufferAction::FlipSelections.into()),
|
||||
|
||||
("x".try_into().unwrap(), Action::ExtendLineBelow),
|
||||
("X".try_into().unwrap(), Action::ExtendLineAbove),
|
||||
("x".try_into().unwrap(), CursorAction::ExtendLineBelow.into()),
|
||||
("X".try_into().unwrap(), CursorAction::ExtendLineAbove.into()),
|
||||
|
||||
("d".try_into().unwrap(), Action::Delete),
|
||||
("d".try_into().unwrap(), BufferAction::Delete.into()),
|
||||
|
||||
("u".try_into().unwrap(), Action::Undo),
|
||||
("U".try_into().unwrap(), Action::Redo),
|
||||
("u".try_into().unwrap(), BufferAction::Undo.into()),
|
||||
("U".try_into().unwrap(), BufferAction::Redo.into()),
|
||||
|
||||
("C".try_into().unwrap(), Action::CopySelectionOnNextLine),
|
||||
("C".try_into().unwrap(), BufferAction::CopySelectionOnNextLine.into()),
|
||||
|
||||
("(".try_into().unwrap(), Action::RotateSelectionsBackward),
|
||||
(")".try_into().unwrap(), Action::RotateSelectionsForward),
|
||||
("(".try_into().unwrap(), BufferAction::RotateSelectionsBackward.into()),
|
||||
(")".try_into().unwrap(), BufferAction::RotateSelectionsForward.into()),
|
||||
|
||||
(",".try_into().unwrap(), Action::KeepPrimarySelection),
|
||||
("A-,".try_into().unwrap(), Action::RemovePrimarySelection),
|
||||
(",".try_into().unwrap(), BufferAction::KeepPrimarySelection.into()),
|
||||
("A-,".try_into().unwrap(), BufferAction::RemovePrimarySelection.into()),
|
||||
|
||||
("1".try_into().unwrap(), Action::SplitSelectionsInto1s),
|
||||
("2".try_into().unwrap(), Action::SplitSelectionsInto2s),
|
||||
("3".try_into().unwrap(), Action::SplitSelectionsInto3s),
|
||||
("4".try_into().unwrap(), Action::SplitSelectionsInto4s),
|
||||
("5".try_into().unwrap(), Action::SplitSelectionsInto5s),
|
||||
("6".try_into().unwrap(), Action::SplitSelectionsInto6s),
|
||||
("7".try_into().unwrap(), Action::SplitSelectionsInto7s),
|
||||
("8".try_into().unwrap(), Action::SplitSelectionsInto8s),
|
||||
("9".try_into().unwrap(), Action::SplitSelectionsInto9s),
|
||||
("1".try_into().unwrap(), BufferAction::SplitSelectionsInto1s.into()),
|
||||
("2".try_into().unwrap(), BufferAction::SplitSelectionsInto2s.into()),
|
||||
("3".try_into().unwrap(), BufferAction::SplitSelectionsInto3s.into()),
|
||||
("4".try_into().unwrap(), BufferAction::SplitSelectionsInto4s.into()),
|
||||
("5".try_into().unwrap(), BufferAction::SplitSelectionsInto5s.into()),
|
||||
("6".try_into().unwrap(), BufferAction::SplitSelectionsInto6s.into()),
|
||||
("7".try_into().unwrap(), BufferAction::SplitSelectionsInto7s.into()),
|
||||
("8".try_into().unwrap(), BufferAction::SplitSelectionsInto8s.into()),
|
||||
("9".try_into().unwrap(), BufferAction::SplitSelectionsInto9s.into()),
|
||||
|
||||
("J".try_into().unwrap(), Action::JumpToSelectedOffsetRelativeToMark),
|
||||
("A-J".try_into().unwrap(), Action::JumpToSelectedOffset),
|
||||
("J".try_into().unwrap(), BufferAction::JumpToSelectedOffsetRelativeToMark.into()),
|
||||
("A-J".try_into().unwrap(), BufferAction::JumpToSelectedOffset.into()),
|
||||
|
||||
("m".try_into().unwrap(), Action::ToggleMark),
|
||||
("m".try_into().unwrap(), BufferAction::ToggleMark.into()),
|
||||
|
||||
("y".try_into().unwrap(), AppAction::Yank.into()),
|
||||
].into()),
|
||||
(Some(PartialAction::View), [
|
||||
("z".try_into().unwrap(), Action::AlignViewCenter),
|
||||
("b".try_into().unwrap(), Action::AlignViewBottom),
|
||||
("t".try_into().unwrap(), Action::AlignViewTop),
|
||||
("z".try_into().unwrap(), BufferAction::AlignViewCenter.into()),
|
||||
("b".try_into().unwrap(), BufferAction::AlignViewBottom.into()),
|
||||
("t".try_into().unwrap(), BufferAction::AlignViewTop.into()),
|
||||
].into()),
|
||||
(Some(PartialAction::Space), [
|
||||
("w".try_into().unwrap(), Action::Save),
|
||||
("w".try_into().unwrap(), BufferAction::Save.into()),
|
||||
].into()),
|
||||
(Some(PartialAction::Repeat), [
|
||||
("i".try_into().unwrap(), CursorAction::ExtendByteUp.into()),
|
||||
("k".try_into().unwrap(), CursorAction::ExtendByteDown.into()),
|
||||
("j".try_into().unwrap(), CursorAction::ExtendByteLeft.into()),
|
||||
("l".try_into().unwrap(), CursorAction::ExtendByteRight.into()),
|
||||
|
||||
("C-e".try_into().unwrap(), BufferAction::ScrollDown.into()),
|
||||
("C-y".try_into().unwrap(), BufferAction::ScrollUp.into()),
|
||||
|
||||
("C-d".try_into().unwrap(), BufferAction::PageCursorHalfDown.into()),
|
||||
("C-u".try_into().unwrap(), BufferAction::PageCursorHalfUp.into()),
|
||||
|
||||
("C-f".try_into().unwrap(), BufferAction::PageDown.into()),
|
||||
("C-b".try_into().unwrap(), BufferAction::PageUp.into()),
|
||||
|
||||
("w".try_into().unwrap(), CursorAction::ExtendNextWordStart.into()),
|
||||
("e".try_into().unwrap(), CursorAction::ExtendNextWordEnd.into()),
|
||||
("b".try_into().unwrap(), CursorAction::ExtendPreviousWordStart.into()),
|
||||
|
||||
("x".try_into().unwrap(), CursorAction::ExtendLineBelow.into()),
|
||||
("X".try_into().unwrap(), CursorAction::ExtendLineAbove.into()),
|
||||
|
||||
("d".try_into().unwrap(), BufferAction::Delete.into()),
|
||||
|
||||
("C".try_into().unwrap(), BufferAction::CopySelectionOnNextLine.into()),
|
||||
].into()),
|
||||
].into())
|
||||
].into()
|
||||
|
||||
+35
-2
@@ -1,6 +1,5 @@
|
||||
use std::{cmp::{max, min}, mem::swap, ops::RangeInclusive};
|
||||
|
||||
use crate::BYTES_PER_LINE;
|
||||
use crate::{BYTES_PER_LINE, action::CursorAction};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Cursor {
|
||||
@@ -77,6 +76,40 @@ impl Cursor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute(
|
||||
&mut self,
|
||||
action: CursorAction,
|
||||
max_contents_index: usize
|
||||
) {
|
||||
match action {
|
||||
CursorAction::MoveByteUp => self.move_byte_up(),
|
||||
CursorAction::MoveByteDown => self.move_byte_down(max_contents_index),
|
||||
CursorAction::MoveByteLeft => self.move_byte_left(),
|
||||
CursorAction::MoveByteRight => self.move_byte_right(max_contents_index),
|
||||
|
||||
CursorAction::ExtendByteUp => self.extend_byte_up(),
|
||||
CursorAction::ExtendByteDown => self.extend_byte_down(max_contents_index),
|
||||
CursorAction::ExtendByteLeft => self.extend_byte_left(),
|
||||
CursorAction::ExtendByteRight => self.extend_byte_right(max_contents_index),
|
||||
|
||||
CursorAction::GotoLineStart => self.goto_line_start(),
|
||||
CursorAction::GotoLineEnd => self.goto_line_end(max_contents_index),
|
||||
CursorAction::GotoFileStart => self.goto_file_start(),
|
||||
CursorAction::GotoFileEnd => self.goto_file_end(max_contents_index),
|
||||
|
||||
CursorAction::MoveNextWordStart => self.move_next_word_start(max_contents_index),
|
||||
CursorAction::MoveNextWordEnd => self.move_next_word_end(max_contents_index),
|
||||
CursorAction::MovePreviousWordStart => self.move_previous_word_start(),
|
||||
|
||||
CursorAction::ExtendNextWordStart => self.extend_next_word_start(max_contents_index),
|
||||
CursorAction::ExtendNextWordEnd => self.extend_next_word_end(max_contents_index),
|
||||
CursorAction::ExtendPreviousWordStart => self.extend_previous_word_start(),
|
||||
|
||||
CursorAction::ExtendLineBelow => self.extend_line_below(max_contents_index),
|
||||
CursorAction::ExtendLineAbove => self.extend_line_above(max_contents_index),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn move_byte_up(&mut self) {
|
||||
if self.head >= BYTES_PER_LINE {
|
||||
self.head -= BYTES_PER_LINE;
|
||||
|
||||
+7
-3
@@ -26,6 +26,10 @@ const LINES_OF_PADDING: usize = 5;
|
||||
const BYTES_OF_PADDING: usize = LINES_OF_PADDING * BYTES_PER_LINE;
|
||||
|
||||
// TODO:
|
||||
// - extend to mark (tm?)
|
||||
// - t0 can be to next null
|
||||
// - tf can be to next FF
|
||||
// - inspect selection
|
||||
// - resizing can move the cursor off the screen
|
||||
// - tab bar overflow
|
||||
// - search
|
||||
@@ -46,9 +50,6 @@ const BYTES_OF_PADDING: usize = LINES_OF_PADDING * BYTES_PER_LINE;
|
||||
// - y/p
|
||||
// - [/] to cycle view offset?
|
||||
// - gj jump to entered offset
|
||||
// - repeat X times (dec and hex)
|
||||
// - from register??
|
||||
// - extend to mark (tm?)
|
||||
|
||||
// future directions
|
||||
// - 'views' for bytes (i8/16/etc u8/16/etc 20.12/8.4/etc)
|
||||
@@ -79,6 +80,9 @@ fn main() {
|
||||
|
||||
// dbg!(app.edit_history);
|
||||
|
||||
// dbg!(app.primary_cursor_register);
|
||||
// dbg!(app.other_cursor_registers);
|
||||
|
||||
for log in app.logs {
|
||||
println!("{log}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user