align view actions, bug fixes

This commit is contained in:
alice pellerin
2026-03-21 03:33:16 -05:00
parent 348c33143e
commit 90090bab24
7 changed files with 200 additions and 106 deletions
+87 -29
View File
@@ -1,7 +1,7 @@
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_PER_LINE, 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 {
@@ -85,6 +85,10 @@ pub enum Action {
JumpToSelectedOffset, JumpToSelectedOffset,
ToggleMark, ToggleMark,
AlignViewCenter,
AlignViewBottom,
AlignViewTop,
} }
// actions that act on the app as a whole, not just one buffer // actions that act on the app as a whole, not just one buffer
@@ -148,10 +152,10 @@ impl Buffer {
Action::ExtendLineBelow => self.extend_line_below(window_size), Action::ExtendLineBelow => self.extend_line_below(window_size),
Action::ExtendLineAbove => self.extend_line_above(window_size), Action::ExtendLineAbove => self.extend_line_above(window_size),
Action::Delete => self.delete(), Action::Delete => self.delete(window_size),
Action::Undo => self.undo(), Action::Undo => self.undo(window_size),
Action::Redo => self.redo(), Action::Redo => self.redo(window_size),
Action::Save => self.save(), Action::Save => self.save(),
@@ -179,6 +183,10 @@ impl Buffer {
Action::JumpToSelectedOffset => self.jump_to_selected_offset(window_size), Action::JumpToSelectedOffset => self.jump_to_selected_offset(window_size),
Action::ToggleMark => self.toggle_mark(), 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 None
@@ -286,31 +294,45 @@ impl Buffer {
} }
pub fn scroll_down(&mut self, window_size: WindowSize) { pub fn scroll_down(&mut self, window_size: WindowSize) {
if self.contents.len() <= 5 * BYTES_PER_LINE { return; } if self.contents.len() <= BYTES_OF_PADDING { return; }
self.scroll_position = min( self.scroll_position = min(
self.scroll_position + BYTES_PER_LINE, self.scroll_position + BYTES_PER_LINE,
self.contents.len() - (5 * BYTES_PER_LINE) self.contents.len() - BYTES_OF_PADDING - self.contents.len() % BYTES_PER_LINE
); );
self.primary_cursor.clamp(self.scroll_position, window_size.visible_byte_count()); if window_size.hex_rows() > LINES_OF_PADDING * 2 {
self.primary_cursor.clamp(
self.scroll_position + BYTES_OF_PADDING,
window_size.visible_byte_count() - BYTES_OF_PADDING * 2
);
} else {
self.primary_cursor.clamp(self.scroll_position, window_size.visible_byte_count());
}
self.combine_cursors_if_overlapping(); self.combine_cursors_if_overlapping();
} }
pub fn scroll_up(&mut self, window_size: WindowSize) { pub fn scroll_up(&mut self, window_size: WindowSize) {
self.scroll_position = self.scroll_position.saturating_sub(BYTES_PER_LINE); self.scroll_position = self.scroll_position.saturating_sub(BYTES_PER_LINE);
self.primary_cursor.clamp(self.scroll_position, window_size.visible_byte_count()); if window_size.hex_rows() > LINES_OF_PADDING * 2 {
self.primary_cursor.clamp(
self.scroll_position + BYTES_OF_PADDING,
window_size.visible_byte_count() - BYTES_OF_PADDING * 2
);
} else {
self.primary_cursor.clamp(self.scroll_position, window_size.visible_byte_count());
}
self.combine_cursors_if_overlapping(); self.combine_cursors_if_overlapping();
} }
fn page_cursor_half_down(&mut self, window_size: WindowSize) { fn page_cursor_half_down(&mut self, window_size: WindowSize) {
if self.contents.len() <= 5 * BYTES_PER_LINE { return; } if self.contents.len() <= BYTES_OF_PADDING { return; }
let old_scroll_position = self.scroll_position; let old_scroll_position = self.scroll_position;
self.scroll_position = min( self.scroll_position = min(
self.scroll_position + (window_size.visible_byte_count() / 2).next_multiple_of(BYTES_PER_LINE), self.scroll_position + (window_size.visible_byte_count() / 2).next_multiple_of(BYTES_PER_LINE),
self.contents.len() - (5 * BYTES_PER_LINE) self.contents.len() - BYTES_OF_PADDING - self.contents.len() % BYTES_PER_LINE
); );
let scroll_position_change = self.scroll_position - old_scroll_position; let scroll_position_change = self.scroll_position - old_scroll_position;
@@ -361,14 +383,21 @@ impl Buffer {
} }
fn page_down(&mut self, window_size: WindowSize) { fn page_down(&mut self, window_size: WindowSize) {
if self.contents.len() <= 5 * BYTES_PER_LINE { return; } if self.contents.len() <= BYTES_OF_PADDING { return; }
self.scroll_position = min( self.scroll_position = min(
self.scroll_position + window_size.visible_byte_count(), self.scroll_position + window_size.visible_byte_count(),
self.contents.len() - (5 * BYTES_PER_LINE) self.contents.len() - BYTES_OF_PADDING - self.contents.len() % BYTES_PER_LINE
); );
self.primary_cursor.clamp(self.scroll_position, window_size.visible_byte_count()); if window_size.hex_rows() > LINES_OF_PADDING * 2 {
self.primary_cursor.clamp(
self.scroll_position + BYTES_OF_PADDING,
window_size.visible_byte_count() - BYTES_OF_PADDING * 2
);
} else {
self.primary_cursor.clamp(self.scroll_position, window_size.visible_byte_count());
}
self.combine_cursors_if_overlapping(); self.combine_cursors_if_overlapping();
} }
@@ -377,7 +406,14 @@ impl Buffer {
window_size.visible_byte_count() window_size.visible_byte_count()
); );
self.primary_cursor.clamp(self.scroll_position, window_size.visible_byte_count()); if window_size.hex_rows() > LINES_OF_PADDING * 2 {
self.primary_cursor.clamp(
self.scroll_position + BYTES_OF_PADDING,
window_size.visible_byte_count() - BYTES_OF_PADDING * 2
);
} else {
self.primary_cursor.clamp(self.scroll_position, window_size.visible_byte_count());
}
self.combine_cursors_if_overlapping(); self.combine_cursors_if_overlapping();
} }
@@ -443,7 +479,7 @@ impl Buffer {
self.clamp_screen_to_primary_cursor(window_size); self.clamp_screen_to_primary_cursor(window_size);
} }
fn delete(&mut self) { 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(
EditAction::Delete { EditAction::Delete {
@@ -454,7 +490,8 @@ impl Buffer {
.iter() .iter()
.map(|cursor| self.contents[cursor.range()].to_vec()) .map(|cursor| self.contents[cursor.range()].to_vec())
.collect(), .collect(),
} },
window_size
); );
} }
@@ -463,7 +500,7 @@ impl Buffer {
} }
} }
fn undo(&mut self) { fn undo(&mut self, window_size: WindowSize) {
if self.time_traveling == Some(0) || self.edit_history.is_empty() { return; } if self.time_traveling == Some(0) || self.edit_history.is_empty() { return; }
let current_date = self.time_traveling let current_date = self.time_traveling
@@ -476,12 +513,12 @@ impl Buffer {
EditAction::Placeholder EditAction::Placeholder
); );
self.undo_edit(&edit_action); self.undo_edit(&edit_action, window_size);
self.edit_history[current_date] = edit_action; self.edit_history[current_date] = edit_action;
} }
fn redo(&mut self) { fn redo(&mut self, window_size: WindowSize) {
let Some(previous_date) = self.time_traveling else { return; }; let Some(previous_date) = self.time_traveling else { return; };
let current_date = previous_date + 1; let current_date = previous_date + 1;
@@ -497,7 +534,7 @@ impl Buffer {
EditAction::Placeholder EditAction::Placeholder
); );
self.execute_edit(&edit_action); self.execute_edit(&edit_action, window_size);
self.edit_history[previous_date] = edit_action; self.edit_history[previous_date] = edit_action;
} }
@@ -664,19 +701,40 @@ impl Buffer {
} }
} }
} }
const fn align_view_center(&mut self, window_size: WindowSize) {
let half_a_screen = window_size.visible_byte_count() / 2;
self.scroll_position = self.primary_cursor.head
.saturating_sub(self.primary_cursor.head % BYTES_PER_LINE)
.saturating_sub(half_a_screen - (half_a_screen % BYTES_PER_LINE));
}
fn align_view_bottom(&mut self, window_size: WindowSize) {
self.scroll_position = self.primary_cursor.head
.saturating_sub(self.primary_cursor.head % BYTES_PER_LINE)
.saturating_sub(
window_size
.visible_byte_count()
.saturating_sub(BYTES_PER_LINE + BYTES_OF_PADDING)
)
.min(self.max_contents_index() - self.max_contents_index() % BYTES_PER_LINE);
}
const fn align_view_top(&mut self) {
self.scroll_position = self.primary_cursor.head
.saturating_sub(self.primary_cursor.head % BYTES_PER_LINE)
.saturating_sub(BYTES_OF_PADDING);
}
} }
// helpers // helpers
impl Buffer { impl Buffer {
const fn clamp_screen_to_primary_cursor(&mut self, window_size: WindowSize) { pub fn clamp_screen_to_primary_cursor(&mut self, window_size: WindowSize) {
if self.primary_cursor.head < self.scroll_position { if self.primary_cursor.head < self.scroll_position + BYTES_OF_PADDING {
self.scroll_position -= (self.scroll_position - self.primary_cursor.head) self.align_view_top();
.next_multiple_of(BYTES_PER_LINE); } else if self.primary_cursor.head > self.scroll_position + (window_size.visible_byte_count() - 1).saturating_sub(BYTES_OF_PADDING) {
} else if self.primary_cursor.head > self.scroll_position + window_size.visible_byte_count() - 1 { self.align_view_bottom(window_size);
let screen_edge_offset_to_cursor = self.primary_cursor.head - (
self.scroll_position + window_size.visible_byte_count() - 1
);
self.scroll_position += screen_edge_offset_to_cursor.next_multiple_of(BYTES_PER_LINE);
} }
} }
} }
+2 -1
View File
@@ -129,7 +129,8 @@ impl Buffer {
.map(|cursor| self.contents[cursor.range()].to_vec()) .map(|cursor| self.contents[cursor.range()].to_vec())
.collect(), .collect(),
new_byte: partial_replace << 4 | nybble new_byte: partial_replace << 4 | nybble
} },
window_size
); );
self.partial_action = None; self.partial_action = None;
} else { } else {
+5 -11
View File
@@ -125,7 +125,7 @@ mod hex {
let chunks_rendered = chunks.len() + remainder_chunks.iter().len(); let chunks_rendered = chunks.len() + remainder_chunks.iter().len();
let chunks_not_rendered = CHUNKS_PER_LINE - chunks_rendered; let chunks_not_rendered = CHUNKS_PER_LINE - chunks_rendered;
let spaces_per_chunk = BYTES_PER_CHUNK - 1; let spaces_per_chunk = BYTES_PER_CHUNK - 1 + 2;
let bytes_not_rendered = BYTES_PER_LINE - bytes.len(); let bytes_not_rendered = BYTES_PER_LINE - bytes.len();
let padding_width = 2 * bytes_not_rendered + let padding_width = 2 * bytes_not_rendered +
@@ -138,13 +138,6 @@ mod hex {
.zip((address..).step_by(BYTES_PER_CHUNK)) .zip((address..).step_by(BYTES_PER_CHUNK))
.map(|(chunk, address)| self.render_chunk(address, &chunk).collect()) .map(|(chunk, address)| self.render_chunk(address, &chunk).collect())
.chain(remainder_chunks) .chain(remainder_chunks)
.interleave(
(address..)
.step_by(BYTES_PER_CHUNK)
.take(CHUNKS_PER_LINE)
.skip(1)
.map(|address| vec![self.render_large_space_before(address)])
)
.flatten() .flatten()
.chain(repeat_n(" ".into(), padding_width)) .chain(repeat_n(" ".into(), padding_width))
} }
@@ -247,9 +240,10 @@ mod hex {
" ".into() " ".into()
}; };
if iter::once(&self.primary_cursor) if !address.is_multiple_of(BYTES_PER_LINE) &&
.chain(&self.cursors) iter::once(&self.primary_cursor)
.any(|cursor| cursor.contains_space_before(address)) .chain(&self.cursors)
.any(|cursor| cursor.contains_space_before(address))
{ {
span.bg(Color::select_grey()) span.bg(Color::select_grey())
} else { } else {
+10
View File
@@ -172,6 +172,11 @@ impl Default for Config {
("g".try_into().unwrap(), Action::GotoFileStart), ("g".try_into().unwrap(), Action::GotoFileStart),
].into()), ].into()),
(Some(PartialAction::View), [
("z".try_into().unwrap(), Action::AlignViewCenter),
("b".try_into().unwrap(), Action::AlignViewBottom),
("t".try_into().unwrap(), Action::AlignViewTop),
].into()),
(Some(PartialAction::Space), [ (Some(PartialAction::Space), [
("w".try_into().unwrap(), Action::Save), ("w".try_into().unwrap(), Action::Save),
].into()), ].into()),
@@ -239,6 +244,11 @@ impl Default for Config {
("m".try_into().unwrap(), Action::ToggleMark), ("m".try_into().unwrap(), Action::ToggleMark),
].into()), ].into()),
(Some(PartialAction::View), [
("z".try_into().unwrap(), Action::AlignViewCenter),
("b".try_into().unwrap(), Action::AlignViewBottom),
("t".try_into().unwrap(), Action::AlignViewTop),
].into()),
(Some(PartialAction::Space), [ (Some(PartialAction::Space), [
("w".try_into().unwrap(), Action::Save), ("w".try_into().unwrap(), Action::Save),
].into()), ].into()),
+51 -46
View File
@@ -148,8 +148,7 @@ impl Cursor {
} }
pub const fn goto_file_end(&mut self, max: usize) { pub const fn goto_file_end(&mut self, max: usize) {
self.head = previous_multiple_of(BYTES_PER_LINE, max + 1) + self.head += previous_multiple_of(BYTES_PER_LINE, max + 1 - self.head);
(self.head % BYTES_PER_LINE);
self.collapse(); self.collapse();
} }
@@ -230,11 +229,14 @@ impl Cursor {
self.head = min(self.head + BYTES_PER_LINE, max); self.head = min(self.head + BYTES_PER_LINE, max);
} else { } else {
self.tail -= self.tail % BYTES_PER_LINE; self.tail -= self.tail % BYTES_PER_LINE;
self.head += BYTES_PER_LINE - 1 - (self.head % BYTES_PER_LINE); self.head = min(
self.head + BYTES_PER_LINE - 1 - (self.head % BYTES_PER_LINE),
max
);
} }
} }
pub const fn extend_line_above(&mut self, max: usize) { pub fn extend_line_above(&mut self, max: usize) {
if self.head > self.tail { if self.head > self.tail {
swap(&mut self.head, &mut self.tail); swap(&mut self.head, &mut self.tail);
} }
@@ -246,7 +248,10 @@ impl Cursor {
self.head = self.head.saturating_sub(BYTES_PER_LINE); self.head = self.head.saturating_sub(BYTES_PER_LINE);
} else { } else {
self.head -= self.head % BYTES_PER_LINE; self.head -= self.head % BYTES_PER_LINE;
self.tail += BYTES_PER_LINE - 1 - (self.tail % BYTES_PER_LINE); self.tail = min(
self.tail + BYTES_PER_LINE - 1 - (self.tail % BYTES_PER_LINE),
max
);
} }
} }
} }
@@ -267,57 +272,57 @@ mod tests {
fn next_word() { fn next_word() {
// [a]bcd efgh -> abcd [e]fgh // [a]bcd efgh -> abcd [e]fgh
let mut cursor = Cursor::at(0); let mut cursor = Cursor::at(0);
cursor.move_to_next_word(99); cursor.move_next_word_start(99);
assert_eq!(cursor, Cursor::at(4)); assert_eq!(cursor, Cursor::at(4));
// a[b]cd efgh -> abcd [e]fgh // a[b]cd efgh -> abcd [e]fgh
let mut cursor = Cursor::at(1); let mut cursor = Cursor::at(1);
cursor.move_to_next_word(99); cursor.move_next_word_start(99);
assert_eq!(cursor, Cursor::at(4)); assert_eq!(cursor, Cursor::at(4));
// ab[c]d efgh -> abcd [e]fgh // ab[c]d efgh -> abcd [e]fgh
let mut cursor = Cursor::at(2); let mut cursor = Cursor::at(2);
cursor.move_to_next_word(99); cursor.move_next_word_start(99);
assert_eq!(cursor, Cursor::at(4)); assert_eq!(cursor, Cursor::at(4));
// abc[d] efgh -> abcd [e]fgh // abc[d] efgh -> abcd [e]fgh
let mut cursor = Cursor::at(3); let mut cursor = Cursor::at(3);
cursor.move_to_next_word(99); cursor.move_next_word_start(99);
assert_eq!(cursor, Cursor::at(4)); assert_eq!(cursor, Cursor::at(4));
// [a]bcd -> abc[d] // [a]bcd -> abc[d]
let mut cursor = Cursor::at(0); let mut cursor = Cursor::at(0);
cursor.move_to_next_word(3); cursor.move_next_word_start(3);
assert_eq!(cursor, Cursor::at(3)); assert_eq!(cursor, Cursor::at(3));
// [a]bc -> ab[c] // [a]bc -> ab[c]
let mut cursor = Cursor::at(0); let mut cursor = Cursor::at(0);
cursor.move_to_next_word(2); cursor.move_next_word_start(2);
assert_eq!(cursor, Cursor::at(2)); assert_eq!(cursor, Cursor::at(2));
// [a]b -> a[b] // [a]b -> a[b]
let mut cursor = Cursor::at(0); let mut cursor = Cursor::at(0);
cursor.move_to_next_word(1); cursor.move_next_word_start(1);
assert_eq!(cursor, Cursor::at(1)); assert_eq!(cursor, Cursor::at(1));
// [a] -> [a] // [a] -> [a]
let mut cursor = Cursor::at(0); let mut cursor = Cursor::at(0);
cursor.move_to_next_word(0); cursor.move_next_word_start(0);
assert_eq!(cursor, Cursor::at(0)); assert_eq!(cursor, Cursor::at(0));
// ab[c]d -> abc[d] // ab[c]d -> abc[d]
let mut cursor = Cursor::at(2); let mut cursor = Cursor::at(2);
cursor.move_to_next_word(3); cursor.move_next_word_start(3);
assert_eq!(cursor, Cursor::at(3)); assert_eq!(cursor, Cursor::at(3));
// abc[d] -> abc[d] // abc[d] -> abc[d]
let mut cursor = Cursor::at(3); let mut cursor = Cursor::at(3);
cursor.move_to_next_word(3); cursor.move_next_word_start(3);
assert_eq!(cursor, Cursor::at(3)); assert_eq!(cursor, Cursor::at(3));
// ab[c[d] -> ab[c[d] // ab[c[d] -> ab[c[d]
let mut cursor = Cursor { tail: 2, head: 3 }; let mut cursor = Cursor { tail: 2, head: 3 };
cursor.move_to_next_word(3); cursor.move_next_word_start(3);
assert_eq!(cursor, Cursor { tail: 2, head: 3 }); assert_eq!(cursor, Cursor { tail: 2, head: 3 });
} }
@@ -325,97 +330,97 @@ mod tests {
fn next_end() { fn next_end() {
// [a]bcd -> [abcd] // [a]bcd -> [abcd]
let mut cursor = Cursor::at(0); let mut cursor = Cursor::at(0);
cursor.move_to_next_end(99); cursor.move_next_word_end(99);
assert_eq!(cursor, Cursor { tail: 0, head: 3 }); assert_eq!(cursor, Cursor { tail: 0, head: 3 });
// a[b]cd -> [abcd] // a[b]cd -> [abcd]
let mut cursor = Cursor::at(1); let mut cursor = Cursor::at(1);
cursor.move_to_next_end(99); cursor.move_next_word_end(99);
assert_eq!(cursor, Cursor { tail: 1, head: 3 }); assert_eq!(cursor, Cursor { tail: 1, head: 3 });
// ab[c]d -> [abcd] // ab[c]d -> [abcd]
let mut cursor = Cursor::at(2); let mut cursor = Cursor::at(2);
cursor.move_to_next_end(99); cursor.move_next_word_end(99);
assert_eq!(cursor, Cursor { tail: 2, head: 3 }); assert_eq!(cursor, Cursor { tail: 2, head: 3 });
// abc[d] efgh -> abcd [efgh] // abc[d] efgh -> abcd [efgh]
let mut cursor = Cursor::at(3); let mut cursor = Cursor::at(3);
cursor.move_to_next_end(99); cursor.move_next_word_end(99);
assert_eq!(cursor, Cursor { tail: 4, head: 7 }); assert_eq!(cursor, Cursor { tail: 4, head: 7 });
// abcd [e]fgh -> abcd [efgh] // abcd [e]fgh -> abcd [efgh]
let mut cursor = Cursor::at(4); let mut cursor = Cursor::at(4);
cursor.move_to_next_end(99); cursor.move_next_word_end(99);
assert_eq!(cursor, Cursor { tail: 4, head: 7 }); assert_eq!(cursor, Cursor { tail: 4, head: 7 });
// abcd e[f]gh -> abcd e[fgh] // abcd e[f]gh -> abcd e[fgh]
let mut cursor = Cursor::at(5); let mut cursor = Cursor::at(5);
cursor.move_to_next_end(99); cursor.move_next_word_end(99);
assert_eq!(cursor, Cursor { tail: 5, head: 7 }); assert_eq!(cursor, Cursor { tail: 5, head: 7 });
// abcd ef[g]h -> abcd ef[gh] // abcd ef[g]h -> abcd ef[gh]
let mut cursor = Cursor::at(6); let mut cursor = Cursor::at(6);
cursor.move_to_next_end(99); cursor.move_next_word_end(99);
assert_eq!(cursor, Cursor { tail: 6, head: 7 }); assert_eq!(cursor, Cursor { tail: 6, head: 7 });
// abcd efg[h] ijkl -> abcd efgh [ijkl] // abcd efg[h] ijkl -> abcd efgh [ijkl]
let mut cursor = Cursor::at(7); let mut cursor = Cursor::at(7);
cursor.move_to_next_end(99); cursor.move_next_word_end(99);
assert_eq!(cursor, Cursor { tail: 8, head: 11 }); assert_eq!(cursor, Cursor { tail: 8, head: 11 });
// abcd efg[h] -> abcd efg[h] // abcd efg[h] -> abcd efg[h]
let mut cursor = Cursor::at(7); let mut cursor = Cursor::at(7);
cursor.move_to_next_end(7); cursor.move_next_word_end(7);
assert_eq!(cursor, Cursor { tail: 7, head: 7 }); assert_eq!(cursor, Cursor { tail: 7, head: 7 });
// abcd e[fgh] -> abcd e[fgh] // abcd e[fgh] -> abcd e[fgh]
let mut cursor = Cursor { tail: 5, head: 7 }; let mut cursor = Cursor { tail: 5, head: 7 };
cursor.move_to_next_end(7); cursor.move_next_word_end(7);
assert_eq!(cursor, Cursor { tail: 5, head: 7 }); assert_eq!(cursor, Cursor { tail: 5, head: 7 });
// a[b]c -> a[bc] // a[b]c -> a[bc]
let mut cursor = Cursor::at(1); let mut cursor = Cursor::at(1);
cursor.move_to_next_end(2); cursor.move_next_word_end(2);
assert_eq!(cursor, Cursor { tail: 1, head: 2 }); assert_eq!(cursor, Cursor { tail: 1, head: 2 });
// a[bc] -> a[bc] // a[bc] -> a[bc]
let mut cursor = Cursor { tail: 1, head: 2}; let mut cursor = Cursor { tail: 1, head: 2};
cursor.move_to_next_end(2); cursor.move_next_word_end(2);
assert_eq!(cursor, Cursor { tail: 1, head: 2 }); assert_eq!(cursor, Cursor { tail: 1, head: 2 });
// a[b] -> a[b] // a[b] -> a[b]
let mut cursor = Cursor::at(1); let mut cursor = Cursor::at(1);
cursor.move_to_next_end(1); cursor.move_next_word_end(1);
assert_eq!(cursor, Cursor::at(1)); assert_eq!(cursor, Cursor::at(1));
// [a]b -> [ab] // [a]b -> [ab]
let mut cursor = Cursor::at(0); let mut cursor = Cursor::at(0);
cursor.move_to_next_end(1); cursor.move_next_word_end(1);
assert_eq!(cursor, Cursor { tail: 0, head: 1 }); assert_eq!(cursor, Cursor { tail: 0, head: 1 });
// [ab] -> [ab] // [ab] -> [ab]
let mut cursor = Cursor { tail: 0, head: 1}; let mut cursor = Cursor { tail: 0, head: 1};
cursor.move_to_next_end(1); cursor.move_next_word_end(1);
assert_eq!(cursor, Cursor { tail: 0, head: 1 }); assert_eq!(cursor, Cursor { tail: 0, head: 1 });
// [a] -> [a] // [a] -> [a]
let mut cursor = Cursor::at(0); let mut cursor = Cursor::at(0);
cursor.move_to_next_end(0); cursor.move_next_word_end(0);
assert_eq!(cursor, Cursor::at(0)); assert_eq!(cursor, Cursor::at(0));
// [a]bcd] -> [abc[d] // [a]bcd] -> [abc[d]
let mut cursor = Cursor { head: 0, tail: 3 }; let mut cursor = Cursor { head: 0, tail: 3 };
cursor.move_to_next_end(99); cursor.move_next_word_end(99);
assert_eq!(cursor, Cursor { tail: 0, head: 3 }); assert_eq!(cursor, Cursor { tail: 0, head: 3 });
// [a[b]cd -> a[bc[d] // [a[b]cd -> a[bc[d]
let mut cursor = Cursor { tail: 0, head: 1 }; let mut cursor = Cursor { tail: 0, head: 1 };
cursor.move_to_next_end(99); cursor.move_next_word_end(99);
assert_eq!(cursor, Cursor { tail: 1, head: 3 }); assert_eq!(cursor, Cursor { tail: 1, head: 3 });
// abc[d] ef -> abcd [ef] // abc[d] ef -> abcd [ef]
let mut cursor = Cursor::at(3); let mut cursor = Cursor::at(3);
cursor.move_to_next_end(5); cursor.move_next_word_end(5);
assert_eq!(cursor, Cursor { tail: 4, head: 5 }); assert_eq!(cursor, Cursor { tail: 4, head: 5 });
} }
@@ -423,57 +428,57 @@ mod tests {
fn previous_beginning() { fn previous_beginning() {
// abcd efgh [i]jkl -> abcd [efgh] ijkl // abcd efgh [i]jkl -> abcd [efgh] ijkl
let mut cursor = Cursor::at(8); let mut cursor = Cursor::at(8);
cursor.move_to_previous_beginning(); cursor.move_previous_word_start();
assert_eq!(cursor, Cursor { head: 4, tail: 7 }); assert_eq!(cursor, Cursor { head: 4, tail: 7 });
// abcd efg[h] -> abcd [efgh] // abcd efg[h] -> abcd [efgh]
let mut cursor = Cursor::at(7); let mut cursor = Cursor::at(7);
cursor.move_to_previous_beginning(); cursor.move_previous_word_start();
assert_eq!(cursor, Cursor { head: 4, tail: 7 }); assert_eq!(cursor, Cursor { head: 4, tail: 7 });
// abcd ef[g]h -> abcd [efg]h // abcd ef[g]h -> abcd [efg]h
let mut cursor = Cursor::at(6); let mut cursor = Cursor::at(6);
cursor.move_to_previous_beginning(); cursor.move_previous_word_start();
assert_eq!(cursor, Cursor { head: 4, tail: 6 }); assert_eq!(cursor, Cursor { head: 4, tail: 6 });
// abcd e[f]gh -> abcd [ef]gh // abcd e[f]gh -> abcd [ef]gh
let mut cursor = Cursor::at(5); let mut cursor = Cursor::at(5);
cursor.move_to_previous_beginning(); cursor.move_previous_word_start();
assert_eq!(cursor, Cursor { head: 4, tail: 5 }); assert_eq!(cursor, Cursor { head: 4, tail: 5 });
// abcd [e]fgh -> [abcd] efgh // abcd [e]fgh -> [abcd] efgh
let mut cursor = Cursor::at(4); let mut cursor = Cursor::at(4);
cursor.move_to_previous_beginning(); cursor.move_previous_word_start();
assert_eq!(cursor, Cursor { head: 0, tail: 3 }); assert_eq!(cursor, Cursor { head: 0, tail: 3 });
// abc[d] -> [abcd] // abc[d] -> [abcd]
let mut cursor = Cursor::at(3); let mut cursor = Cursor::at(3);
cursor.move_to_previous_beginning(); cursor.move_previous_word_start();
assert_eq!(cursor, Cursor { head: 0, tail: 3 }); assert_eq!(cursor, Cursor { head: 0, tail: 3 });
// ab[c]d -> [abc]d // ab[c]d -> [abc]d
let mut cursor = Cursor::at(2); let mut cursor = Cursor::at(2);
cursor.move_to_previous_beginning(); cursor.move_previous_word_start();
assert_eq!(cursor, Cursor { head: 0, tail: 2 }); assert_eq!(cursor, Cursor { head: 0, tail: 2 });
// a[b]cd -> [ab]cd // a[b]cd -> [ab]cd
let mut cursor = Cursor::at(1); let mut cursor = Cursor::at(1);
cursor.move_to_previous_beginning(); cursor.move_previous_word_start();
assert_eq!(cursor, Cursor { head: 0, tail: 1 }); assert_eq!(cursor, Cursor { head: 0, tail: 1 });
// [a]bcd -> [a]bcd // [a]bcd -> [a]bcd
let mut cursor = Cursor::at(0); let mut cursor = Cursor::at(0);
cursor.move_to_previous_beginning(); cursor.move_previous_word_start();
assert_eq!(cursor, Cursor { head: 0, tail: 0 }); assert_eq!(cursor, Cursor { head: 0, tail: 0 });
// [abc[d] -> [a]bcd] // [abc[d] -> [a]bcd]
let mut cursor = Cursor { tail: 0, head: 3 }; let mut cursor = Cursor { tail: 0, head: 3 };
cursor.move_to_previous_beginning(); cursor.move_previous_word_start();
assert_eq!(cursor, Cursor { head: 0, tail: 3 }); assert_eq!(cursor, Cursor { head: 0, tail: 3 });
// ab[c]d] -> [a]bc]d // ab[c]d] -> [a]bc]d
let mut cursor = Cursor { head: 2, tail: 3 }; let mut cursor = Cursor { head: 2, tail: 3 };
cursor.move_to_previous_beginning(); cursor.move_previous_word_start();
assert_eq!(cursor, Cursor { head: 0, tail: 2 }); assert_eq!(cursor, Cursor { head: 0, tail: 2 });
} }
} }
+38 -18
View File
@@ -1,5 +1,5 @@
use std::{cmp::min, convert::identity, iter}; use std::{cmp::min, convert::identity, iter};
use crate::{buffer::Buffer, cursor::Cursor}; use crate::{app::WindowSize, buffer::Buffer, cursor::Cursor};
#[derive(Debug)] #[derive(Debug)]
pub enum EditAction { pub enum EditAction {
@@ -34,10 +34,10 @@ pub enum EditAction {
} }
impl Buffer { impl Buffer {
pub fn execute_and_add(&mut self, edit_action: EditAction) { pub fn execute_and_add(&mut self, edit_action: EditAction, window_size: WindowSize) {
assert!(!matches!(edit_action, EditAction::Placeholder)); assert!(!matches!(edit_action, EditAction::Placeholder));
self.execute_edit(&edit_action); self.execute_edit(&edit_action, window_size);
if let Some(date) = self.time_traveling { if let Some(date) = self.time_traveling {
self.edit_history.truncate(date); self.edit_history.truncate(date);
@@ -51,44 +51,57 @@ impl Buffer {
self.edit_history.push(edit_action); self.edit_history.push(edit_action);
} }
pub fn execute_edit(&mut self, edit_action: &EditAction) { pub fn execute_edit(&mut self, edit_action: &EditAction, window_size: WindowSize) {
match edit_action { match edit_action {
EditAction::Placeholder => unreachable!(), EditAction::Placeholder => unreachable!(),
EditAction::Delete { primary_cursor, cursors, .. } => self.delete_at(*primary_cursor, cursors), EditAction::Delete { primary_cursor, cursors, .. } => {
self.delete_at(*primary_cursor, cursors, window_size);
},
EditAction::Replace { EditAction::Replace {
primary_cursor, cursors, primary_old_data: _, old_data: _, new_byte primary_cursor, cursors, primary_old_data: _, old_data: _, new_byte
} => self.replace_at_with(*primary_cursor, cursors, *new_byte), } => self.replace_at_with(*primary_cursor, cursors, *new_byte, window_size),
} }
} }
pub fn undo_edit(&mut self, edit_action: &EditAction) { pub fn undo_edit(&mut self, edit_action: &EditAction, window_size: WindowSize) {
match edit_action { match edit_action {
EditAction::Placeholder => unreachable!(), EditAction::Placeholder => unreachable!(),
EditAction::Delete { EditAction::Delete {
primary_cursor, cursors, primary_old_data, old_data primary_cursor, cursors, primary_old_data, old_data
} => self.undo_delete_at(*primary_cursor, cursors, primary_old_data, old_data), } => self.undo_delete_at(
*primary_cursor,
cursors,
primary_old_data,
old_data,
window_size
),
EditAction::Replace { EditAction::Replace {
primary_cursor, cursors, primary_old_data, old_data, .. primary_cursor, cursors, primary_old_data, old_data, ..
} => self.undo_replace_at_with(*primary_cursor, cursors, primary_old_data, old_data), } => self.undo_replace_at_with(
*primary_cursor,
cursors,
primary_old_data,
old_data,
window_size
),
} }
} }
fn delete_at( fn delete_at(
&mut self, &mut self,
primary_cursor: Cursor, primary_cursor: Cursor,
cursors: &[Cursor] cursors: &[Cursor],
window_size: WindowSize
) { ) {
let mut bytes_deleted_so_far = 0; let mut bytes_deleted_so_far = 0;
for cursor in cursors_in_order(primary_cursor, cursors) { for cursor in cursors_in_order(primary_cursor, cursors) {
let range = cursor.range();
self.contents.drain( self.contents.drain(
(range.start() - bytes_deleted_so_far)..=(range.end() - bytes_deleted_so_far) (cursor.lower_bound() - bytes_deleted_so_far)..=
(cursor.upper_bound() - bytes_deleted_so_far)
); );
// RangeInclusive<usize>::len() is unstable/nonexistant :/ bytes_deleted_so_far += cursor.len();
bytes_deleted_so_far += range.end() - range.start() + 1;
} }
self.primary_cursor.head = min( self.primary_cursor.head = min(
@@ -103,6 +116,7 @@ impl Buffer {
.collect(); .collect();
self.combine_cursors_if_overlapping(); self.combine_cursors_if_overlapping();
self.clamp_screen_to_primary_cursor(window_size);
} }
fn undo_delete_at( fn undo_delete_at(
@@ -110,7 +124,8 @@ impl Buffer {
primary_cursor: Cursor, primary_cursor: Cursor,
cursors: &[Cursor], cursors: &[Cursor],
primary_old_data: &[u8], primary_old_data: &[u8],
old_data: &[Vec<u8>] old_data: &[Vec<u8>],
window_size: WindowSize
) { ) {
let primary_cursor_start = primary_cursor.lower_bound(); let primary_cursor_start = primary_cursor.lower_bound();
@@ -130,13 +145,15 @@ impl Buffer {
self.primary_cursor = primary_cursor; self.primary_cursor = primary_cursor;
self.cursors = cursors.to_vec(); self.cursors = cursors.to_vec();
self.clamp_screen_to_primary_cursor(window_size);
} }
fn replace_at_with( fn replace_at_with(
&mut self, &mut self,
primary_cursor: Cursor, primary_cursor: Cursor,
cursors: &[Cursor], cursors: &[Cursor],
new_byte: u8 new_byte: u8,
window_size: WindowSize
) { ) {
self.contents[primary_cursor.range()].fill(new_byte); self.contents[primary_cursor.range()].fill(new_byte);
@@ -146,6 +163,7 @@ impl Buffer {
self.primary_cursor = primary_cursor; self.primary_cursor = primary_cursor;
self.cursors = cursors.to_vec(); self.cursors = cursors.to_vec();
self.clamp_screen_to_primary_cursor(window_size);
} }
fn undo_replace_at_with( fn undo_replace_at_with(
@@ -153,7 +171,8 @@ impl Buffer {
primary_cursor: Cursor, primary_cursor: Cursor,
cursors: &[Cursor], cursors: &[Cursor],
primary_old_data: &[u8], primary_old_data: &[u8],
old_data: &[Vec<u8>] old_data: &[Vec<u8>],
window_size: WindowSize
) { ) {
self.contents.splice( self.contents.splice(
primary_cursor.range(), primary_cursor.range(),
@@ -169,6 +188,7 @@ impl Buffer {
self.primary_cursor = primary_cursor; self.primary_cursor = primary_cursor;
self.cursors = cursors.to_vec(); self.cursors = cursors.to_vec();
self.clamp_screen_to_primary_cursor(window_size);
} }
} }
+7 -1
View File
@@ -22,7 +22,14 @@ const BYTES_PER_LINE: usize = 0x10;
const BYTES_PER_CHUNK: usize = 4; const BYTES_PER_CHUNK: usize = 4;
const CHUNKS_PER_LINE: usize = BYTES_PER_LINE / BYTES_PER_CHUNK; const CHUNKS_PER_LINE: usize = BYTES_PER_LINE / BYTES_PER_CHUNK;
const LINES_OF_PADDING: usize = 5;
const BYTES_OF_PADDING: usize = LINES_OF_PADDING * BYTES_PER_LINE;
// TODO: // TODO:
// - zz/zt/zb
// - resizing can move the cursor off the screen
// - constant for 5 lines of padding
// - pad in clamp function, always
// - search // - search
// - s/A-k/A-K // - s/A-k/A-K
// - C-a/C-x // - C-a/C-x
@@ -36,7 +43,6 @@ const CHUNKS_PER_LINE: usize = BYTES_PER_LINE / BYTES_PER_CHUNK;
// - edit character panel // - edit character panel
// - modifier on existing keys like teehee? or jump to panel? // - modifier on existing keys like teehee? or jump to panel?
// - if jump to panel, space? // - if jump to panel, space?
// - zz/zt/zb
// - visual gg/G // - visual gg/G
// - jumplist // - jumplist
// - y/p // - y/p