repeat x times
This commit is contained in:
+147
-40
@@ -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();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if should_reset_partial {
|
||||
self.partial_action = None;
|
||||
self.partial_action = None;
|
||||
self.partial_replace = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_other_modes(
|
||||
&mut self,
|
||||
event: KeyEvent,
|
||||
config: &Config,
|
||||
window_size: WindowSize
|
||||
) -> Option<AppAction> {
|
||||
let mut result = 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 {
|
||||
|
||||
Reference in New Issue
Block a user