repeat x times

This commit is contained in:
alice pellerin
2026-03-21 16:41:10 -05:00
parent 823e186acd
commit 9ac66fc074
6 changed files with 483 additions and 383 deletions
+110 -235
View File
@@ -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
+23
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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}");
}