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