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)
}
}
#[derive(Clone, Copy)]
pub enum CursorAction {
MoveByteUp,
MoveByteDown,
MoveByteLeft,
MoveByteRight,
PreviousBuffer,
NextBuffer,
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