fix scrolling and clamping
This commit is contained in:
@@ -144,9 +144,11 @@ impl Buffer {
|
||||
};
|
||||
|
||||
assert!(self.scroll_position.is_multiple_of(BYTES_PER_LINE));
|
||||
if !self.contents.is_empty() {
|
||||
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());
|
||||
|
||||
|
||||
+87
-104
@@ -1,7 +1,7 @@
|
||||
use std::{cmp::min, collections::hash_set::Entry, convert::identity, fs::File, io::Write, iter, mem::{replace, swap}};
|
||||
use itertools::Itertools;
|
||||
use ratatui::{style::{Color, Stylize}, text::Span};
|
||||
use crate::{BYTES_OF_PADDING, BYTES_PER_LINE, LINES_OF_PADDING, action::BufferAction, buffer::{Buffer, InspectionStatus, Mode, PartialAction}, cursor::Cursor, edit_action::EditAction, popup::Popup, window_size::WindowSize};
|
||||
use crate::{BYTES_OF_PADDING, BYTES_PER_LINE, LINES_OF_PADDING, action::BufferAction, buffer::{Buffer, InspectionStatus, Mode, PartialAction}, cursor::Cursor, edit_action::EditAction, popup::Popup, utilities::{Floorable, SaturatingSubtract}, window_size::WindowSize};
|
||||
|
||||
impl Buffer {
|
||||
pub fn execute(&mut self, action: BufferAction, window_size: WindowSize) {
|
||||
@@ -60,7 +60,7 @@ impl Buffer {
|
||||
|
||||
BufferAction::AlignViewCenter => self.align_view_center(window_size),
|
||||
BufferAction::AlignViewBottom => self.align_view_bottom(window_size),
|
||||
BufferAction::AlignViewTop => self.align_view_top(),
|
||||
BufferAction::AlignViewTop => self.align_view_top(window_size),
|
||||
|
||||
BufferAction::FindTillMark => self.till_mark(false, window_size), // extend: false
|
||||
BufferAction::FindTillNull => self.till_null(false, window_size), // extend: false
|
||||
@@ -110,131 +110,75 @@ impl Buffer {
|
||||
}
|
||||
|
||||
pub fn scroll_down(&mut self, window_size: WindowSize) {
|
||||
if self.contents.len() <= BYTES_OF_PADDING { return; }
|
||||
|
||||
self.scroll_position = min(
|
||||
self.scroll_position + BYTES_PER_LINE,
|
||||
self.contents.len() - BYTES_OF_PADDING - self.contents.len() % BYTES_PER_LINE
|
||||
);
|
||||
|
||||
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.scroll_position += BYTES_PER_LINE;
|
||||
self.clamp_screen_to_contents(window_size);
|
||||
self.clamp_primary_cursor_to_screen(window_size);
|
||||
self.combine_cursors_if_overlapping();
|
||||
}
|
||||
|
||||
pub fn scroll_up(&mut self, window_size: WindowSize) {
|
||||
self.scroll_position = self.scroll_position.saturating_sub(BYTES_PER_LINE);
|
||||
|
||||
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.scroll_position.saturating_subtract(BYTES_PER_LINE);
|
||||
self.clamp_primary_cursor_to_screen(window_size);
|
||||
self.combine_cursors_if_overlapping();
|
||||
}
|
||||
|
||||
fn page_cursor_half_down(&mut self, window_size: WindowSize) {
|
||||
if self.contents.len() <= BYTES_OF_PADDING { return; }
|
||||
let scroll_amount = (window_size.visible_byte_count() / 2).next_multiple_of(BYTES_PER_LINE);
|
||||
|
||||
let old_scroll_position = self.scroll_position;
|
||||
self.scroll_position += scroll_amount;
|
||||
self.clamp_screen_to_contents(window_size);
|
||||
|
||||
self.scroll_position = min(
|
||||
self.scroll_position + (window_size.visible_byte_count() / 2).next_multiple_of(BYTES_PER_LINE),
|
||||
self.contents.len() - BYTES_OF_PADDING - self.contents.len() % BYTES_PER_LINE
|
||||
);
|
||||
self.primary_cursor.head += scroll_amount;
|
||||
if self.mode != Mode::Select {
|
||||
self.primary_cursor.tail += scroll_amount;
|
||||
}
|
||||
self.primary_cursor.clamp(0, self.max_contents_index());
|
||||
self.clamp_screen_to_primary_cursor(window_size);
|
||||
|
||||
let scroll_position_change = self.scroll_position - old_scroll_position;
|
||||
let max_contents_index = self.max_contents_index();
|
||||
|
||||
self.primary_cursor.head = min(
|
||||
self.primary_cursor.head + scroll_position_change,
|
||||
max_contents_index
|
||||
);
|
||||
self.primary_cursor.tail = min(
|
||||
self.primary_cursor.tail + scroll_position_change,
|
||||
max_contents_index
|
||||
);
|
||||
|
||||
for cursor in &mut self.cursors {
|
||||
cursor.head = (cursor.head + scroll_position_change).min(max_contents_index);
|
||||
cursor.tail = (cursor.tail + scroll_position_change).min(max_contents_index);
|
||||
cursor.head += scroll_amount;
|
||||
if self.mode != Mode::Select {
|
||||
cursor.tail += scroll_amount;
|
||||
}
|
||||
cursor.clamp(0, max_contents_index);
|
||||
}
|
||||
|
||||
self.combine_cursors_if_overlapping();
|
||||
}
|
||||
|
||||
fn page_cursor_half_up(&mut self, window_size: WindowSize) {
|
||||
let old_scroll_position = self.scroll_position;
|
||||
let scroll_amount = (window_size.visible_byte_count() / 2).next_multiple_of(BYTES_PER_LINE);
|
||||
|
||||
self.scroll_position = self.scroll_position.saturating_sub(
|
||||
(window_size.visible_byte_count() / 2).next_multiple_of(BYTES_PER_LINE)
|
||||
);
|
||||
self.scroll_position.saturating_subtract(scroll_amount);
|
||||
|
||||
let scroll_position_change = old_scroll_position - self.scroll_position;
|
||||
let max_contents_index = self.max_contents_index();
|
||||
|
||||
self.primary_cursor.head = min(
|
||||
self.primary_cursor.head - scroll_position_change,
|
||||
max_contents_index
|
||||
);
|
||||
self.primary_cursor.tail = min(
|
||||
self.primary_cursor.tail - scroll_position_change,
|
||||
max_contents_index
|
||||
);
|
||||
self.primary_cursor.head.saturating_subtract(scroll_amount);
|
||||
if self.mode != Mode::Select {
|
||||
self.primary_cursor.tail.saturating_subtract(scroll_amount);
|
||||
}
|
||||
|
||||
for cursor in &mut self.cursors {
|
||||
cursor.head = (cursor.head - scroll_position_change).min(max_contents_index);
|
||||
cursor.tail = (cursor.tail - scroll_position_change).min(max_contents_index);
|
||||
cursor.head.saturating_subtract(scroll_amount);
|
||||
if self.mode != Mode::Select {
|
||||
cursor.tail.saturating_subtract(scroll_amount);
|
||||
}
|
||||
}
|
||||
|
||||
self.combine_cursors_if_overlapping();
|
||||
}
|
||||
|
||||
fn page_down(&mut self, window_size: WindowSize) {
|
||||
if self.contents.len() <= BYTES_OF_PADDING { return; }
|
||||
|
||||
self.scroll_position = min(
|
||||
self.scroll_position + window_size.visible_byte_count(),
|
||||
self.max_contents_index() - BYTES_OF_PADDING - self.max_contents_index() % BYTES_PER_LINE
|
||||
);
|
||||
|
||||
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.scroll_position += window_size.visible_byte_count();
|
||||
self.clamp_screen_to_contents(window_size);
|
||||
self.clamp_primary_cursor_to_screen(window_size);
|
||||
self.combine_cursors_if_overlapping();
|
||||
}
|
||||
|
||||
fn page_up(&mut self, window_size: WindowSize) {
|
||||
self.scroll_position = self.scroll_position.saturating_sub(
|
||||
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.scroll_position.saturating_subtract(window_size.visible_byte_count());
|
||||
self.clamp_screen_to_contents(window_size);
|
||||
self.clamp_primary_cursor_to_screen(window_size);
|
||||
self.combine_cursors_if_overlapping();
|
||||
}
|
||||
|
||||
@@ -540,25 +484,25 @@ impl Buffer {
|
||||
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));
|
||||
.floored_to_the_nearest(BYTES_PER_LINE)
|
||||
.saturating_sub(half_a_screen.floored_to_the_nearest(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)
|
||||
.floored_to_the_nearest(BYTES_PER_LINE)
|
||||
.saturating_sub(
|
||||
window_size
|
||||
.visible_byte_count()
|
||||
.saturating_sub(BYTES_PER_LINE + BYTES_OF_PADDING)
|
||||
.saturating_sub(BYTES_PER_LINE + Self::bottom_padding(window_size))
|
||||
)
|
||||
.min(self.max_contents_index() - self.max_contents_index() % BYTES_PER_LINE);
|
||||
.min(self.max_contents_index().floored_to_the_nearest(BYTES_PER_LINE));
|
||||
}
|
||||
|
||||
const fn align_view_top(&mut self) {
|
||||
const fn align_view_top(&mut self, window_size: WindowSize) {
|
||||
self.scroll_position = self.primary_cursor.head
|
||||
.saturating_sub(self.primary_cursor.head % BYTES_PER_LINE)
|
||||
.saturating_sub(BYTES_OF_PADDING);
|
||||
.floored_to_the_nearest(BYTES_PER_LINE)
|
||||
.saturating_sub(self.top_padding(window_size));
|
||||
}
|
||||
|
||||
fn till_mark(&mut self, extend: bool, window_size: WindowSize) {
|
||||
@@ -870,13 +814,52 @@ fn inspect_color(selection: &[u8]) -> Vec<Span<'static>> {
|
||||
|
||||
// MARK: helpers
|
||||
impl Buffer {
|
||||
const fn bottom_padding(window_size: WindowSize) -> usize {
|
||||
if window_size.hex_rows() <= LINES_OF_PADDING * 2 {
|
||||
0
|
||||
} else {
|
||||
BYTES_OF_PADDING
|
||||
}
|
||||
}
|
||||
|
||||
const fn top_padding(&self, window_size: WindowSize) -> usize {
|
||||
if window_size.hex_rows() <= LINES_OF_PADDING * 2 || self.scroll_position == 0 {
|
||||
0
|
||||
} else {
|
||||
BYTES_OF_PADDING
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn clamp_screen_to_contents(&mut self, window_size: WindowSize) {
|
||||
let max_scroll_position = self.max_contents_index()
|
||||
.floored_to_the_nearest(BYTES_PER_LINE)
|
||||
.saturating_sub(Self::bottom_padding(window_size));
|
||||
|
||||
if self.scroll_position > max_scroll_position {
|
||||
self.scroll_position = max_scroll_position;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clamp_screen_to_primary_cursor(&mut self, window_size: WindowSize) {
|
||||
if self.primary_cursor.head < self.scroll_position + BYTES_OF_PADDING {
|
||||
self.align_view_top();
|
||||
} else if self.primary_cursor.head > self.scroll_position + (window_size.visible_byte_count() - 1).saturating_sub(BYTES_OF_PADDING) {
|
||||
if self.primary_cursor.head < self.scroll_position + self.top_padding(window_size) {
|
||||
self.align_view_top(window_size);
|
||||
} else if self.primary_cursor.head > self.scroll_position + (window_size.visible_byte_count() - 1).saturating_sub(Self::bottom_padding(window_size)) {
|
||||
self.align_view_bottom(window_size);
|
||||
}
|
||||
}
|
||||
|
||||
fn clamp_primary_cursor_to_screen(&mut self, window_size: WindowSize) {
|
||||
let min = self.scroll_position + self.top_padding(window_size);
|
||||
let max = self.scroll_position + window_size.visible_byte_count()
|
||||
.saturating_sub(Self::bottom_padding(window_size))
|
||||
.saturating_sub(BYTES_PER_LINE);
|
||||
|
||||
if self.mode == Mode::Select {
|
||||
self.primary_cursor.head = self.primary_cursor.head.clamp(min, max);
|
||||
} else {
|
||||
self.primary_cursor.clamp(min, max);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bytes_to_nat(bytes: &[u8]) -> Option<u64> {
|
||||
|
||||
+3
-6
@@ -59,12 +59,9 @@ impl Cursor {
|
||||
swap(&mut self.head, &mut self.tail);
|
||||
}
|
||||
|
||||
// TODO: in visual mode, should only clamp head
|
||||
pub fn clamp(&mut self, scroll_position: usize, screen_size: usize) {
|
||||
let max_row = scroll_position + screen_size - 1;
|
||||
|
||||
self.head = self.head.clamp(scroll_position, max_row);
|
||||
self.tail = self.tail.clamp(scroll_position, max_row);
|
||||
pub fn clamp(&mut self, min: usize, max: usize) {
|
||||
self.head = self.head.clamp(min, max);
|
||||
self.tail = self.tail.clamp(min, max);
|
||||
}
|
||||
|
||||
pub fn combine_with(&mut self, other: Self) {
|
||||
|
||||
+11
-15
@@ -1,5 +1,5 @@
|
||||
use std::{cmp::min, mem::swap};
|
||||
use crate::{BYTES_PER_LINE, action::CursorAction, cursor::Cursor};
|
||||
use crate::{BYTES_PER_LINE, action::CursorAction, cursor::Cursor, utilities::{Floorable, SaturatingSubtract}};
|
||||
|
||||
impl Cursor {
|
||||
pub fn execute(
|
||||
@@ -114,12 +114,12 @@ impl Cursor {
|
||||
}
|
||||
|
||||
pub const fn extend_line_start(&mut self) {
|
||||
self.head -= self.head % BYTES_PER_LINE;
|
||||
self.head.floor_to_the_nearest(BYTES_PER_LINE);
|
||||
}
|
||||
|
||||
pub fn extend_line_end(&mut self, max: usize) {
|
||||
self.head = min(
|
||||
self.head + BYTES_PER_LINE - 1 - (self.head % BYTES_PER_LINE),
|
||||
self.head.floored_to_the_nearest(BYTES_PER_LINE) + BYTES_PER_LINE - 1,
|
||||
max
|
||||
);
|
||||
}
|
||||
@@ -164,7 +164,7 @@ impl Cursor {
|
||||
self.tail = self.head - 1;
|
||||
self.head -= 4;
|
||||
} else {
|
||||
self.head -= self.head % 4;
|
||||
self.head.floor_to_the_nearest(4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ impl Cursor {
|
||||
if self.head.is_multiple_of(4) { // at the beginning of a word
|
||||
self.head -= 4;
|
||||
} else {
|
||||
self.head -= self.head % 4;
|
||||
self.head.floor_to_the_nearest(4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,9 +208,9 @@ impl Cursor {
|
||||
{
|
||||
self.head = min(self.head + BYTES_PER_LINE, max);
|
||||
} else {
|
||||
self.tail -= self.tail % BYTES_PER_LINE;
|
||||
self.tail.floor_to_the_nearest(BYTES_PER_LINE);
|
||||
self.head = min(
|
||||
self.head + BYTES_PER_LINE - 1 - (self.head % BYTES_PER_LINE),
|
||||
self.head.floored_to_the_nearest(BYTES_PER_LINE) + BYTES_PER_LINE - 1,
|
||||
max
|
||||
);
|
||||
}
|
||||
@@ -225,11 +225,11 @@ impl Cursor {
|
||||
(self.tail % BYTES_PER_LINE == BYTES_PER_LINE - 1 ||
|
||||
self.tail == max)
|
||||
{
|
||||
self.head = self.head.saturating_sub(BYTES_PER_LINE);
|
||||
self.head.saturating_subtract(BYTES_PER_LINE);
|
||||
} else {
|
||||
self.head -= self.head % BYTES_PER_LINE;
|
||||
self.head.floor_to_the_nearest(BYTES_PER_LINE);
|
||||
self.tail = min(
|
||||
self.tail + BYTES_PER_LINE - 1 - (self.tail % BYTES_PER_LINE),
|
||||
self.tail.floored_to_the_nearest(BYTES_PER_LINE) + BYTES_PER_LINE - 1,
|
||||
max
|
||||
);
|
||||
}
|
||||
@@ -237,11 +237,7 @@ impl Cursor {
|
||||
}
|
||||
|
||||
const fn previous_multiple_of(multiple: usize, number: usize) -> usize {
|
||||
if number == 0 {
|
||||
0
|
||||
} else {
|
||||
(number - 1) - ((number - 1) % multiple)
|
||||
}
|
||||
number.saturating_sub(1).floored_to_the_nearest(multiple)
|
||||
}
|
||||
|
||||
mod tests {
|
||||
|
||||
+1
-3
@@ -5,6 +5,7 @@
|
||||
#![feature(exact_bitshifts)]
|
||||
#![feature(hash_set_entry)]
|
||||
#![feature(trim_prefix_suffix)]
|
||||
#![feature(const_trait_impl)]
|
||||
|
||||
use arguments::Arguments;
|
||||
use clap::Parser;
|
||||
@@ -33,9 +34,6 @@ const LINES_OF_PADDING: usize = 5;
|
||||
const BYTES_OF_PADDING: usize = LINES_OF_PADDING * BYTES_PER_LINE;
|
||||
|
||||
// TODO:
|
||||
// - fix scroll clamping
|
||||
// - fix scrolling in select mode
|
||||
// - shouldn't clamp tail
|
||||
// - `go` goto entered offset
|
||||
// - search
|
||||
// - `/` hex, `A-/` ascii
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
mod cardinality;
|
||||
mod empty_span;
|
||||
mod custom_greys;
|
||||
mod floor_to_the_nearest;
|
||||
mod saturating_subtract;
|
||||
|
||||
pub use cardinality::HasCardinality;
|
||||
pub use empty_span::empty_span;
|
||||
pub use custom_greys::CustomGreys;
|
||||
pub use floor_to_the_nearest::Floorable;
|
||||
pub use saturating_subtract::SaturatingSubtract;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
pub const trait Floorable {
|
||||
fn floor_to_the_nearest(&mut self, step: Self);
|
||||
fn floored_to_the_nearest(self, step: Self) -> Self;
|
||||
}
|
||||
|
||||
const impl Floorable for usize {
|
||||
fn floor_to_the_nearest(&mut self, step: Self) {
|
||||
*self -= *self % step;
|
||||
}
|
||||
|
||||
fn floored_to_the_nearest(self, step: Self) -> Self {
|
||||
self - (self % step)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
pub trait SaturatingSubtract {
|
||||
fn saturating_subtract(&mut self, other: Self);
|
||||
}
|
||||
|
||||
impl SaturatingSubtract for usize {
|
||||
fn saturating_subtract(&mut self, other: Self) {
|
||||
*self = self.saturating_sub(other);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user