add cursor, hjklweb;
This commit is contained in:
+65
-8
@@ -1,7 +1,7 @@
|
|||||||
use std::{cmp::min, env, fs::File, io::Read, process::exit};
|
use std::{cmp::min, env, fs::File, io::Read, process::exit};
|
||||||
use crossterm::event::{self, Event, KeyCode, KeyModifiers};
|
use crossterm::event::{self, Event, KeyCode, KeyModifiers};
|
||||||
|
|
||||||
use crate::BYTES_PER_LINE;
|
use crate::{BYTES_PER_LINE, cursor::Cursor};
|
||||||
|
|
||||||
mod widget;
|
mod widget;
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ mod widget;
|
|||||||
pub struct App {
|
pub struct App {
|
||||||
pub contents: Vec<u8>,
|
pub contents: Vec<u8>,
|
||||||
pub scroll_position: usize,
|
pub scroll_position: usize,
|
||||||
// pub cursor_position: usize,
|
pub cursor: Cursor,
|
||||||
pub should_quit: bool
|
pub should_quit: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,25 +33,82 @@ impl App {
|
|||||||
Self {
|
Self {
|
||||||
contents,
|
contents,
|
||||||
scroll_position: 0,
|
scroll_position: 0,
|
||||||
// cursor_position: 0,
|
cursor: Cursor::default(),
|
||||||
should_quit: false,
|
should_quit: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_events(&mut self) {
|
pub fn handle_events(&mut self) {
|
||||||
|
#[allow(clippy::collapsible_match)]
|
||||||
match event::read().unwrap() {
|
match event::read().unwrap() {
|
||||||
Event::Key(key_event) if key_event.code == KeyCode::Char('q') => {
|
Event::Key(key_event) if key_event.code == KeyCode::Char('q') => {
|
||||||
self.should_quit = true;
|
self.should_quit = true;
|
||||||
}
|
}
|
||||||
Event::Key(key_event) if key_event.code == KeyCode::Char('e') &&
|
Event::Key(key_event) if key_event.modifiers.contains(KeyModifiers::CONTROL) &&
|
||||||
key_event.modifiers.contains(KeyModifiers::CONTROL) => {
|
key_event.code == KeyCode::Char('e') => {
|
||||||
let max_scroll_position = self.contents.len() - 0x50;
|
let max_scroll_position = self.contents.len() - 0x50;
|
||||||
self.scroll_position = min(self.scroll_position + BYTES_PER_LINE, max_scroll_position);
|
self.scroll_position = min(self.scroll_position + BYTES_PER_LINE, max_scroll_position);
|
||||||
}
|
}
|
||||||
Event::Key(key_event) if key_event.code == KeyCode::Char('y') &&
|
Event::Key(key_event) if key_event.modifiers.contains(KeyModifiers::CONTROL) &&
|
||||||
key_event.modifiers.contains(KeyModifiers::CONTROL) => {
|
key_event.code == KeyCode::Char('y') => {
|
||||||
self.scroll_position = self.scroll_position.saturating_sub(BYTES_PER_LINE);
|
self.scroll_position = self.scroll_position.saturating_sub(BYTES_PER_LINE);
|
||||||
}
|
}
|
||||||
|
Event::Key(key_event) if key_event.code == KeyCode::Char('i') => {
|
||||||
|
if self.cursor.head >= 0x10 {
|
||||||
|
self.cursor.head -= 0x10;
|
||||||
|
self.cursor.collapse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Key(key_event) if key_event.code == KeyCode::Char('j') => {
|
||||||
|
if self.cursor.head >= 1 {
|
||||||
|
self.cursor.head -= 1;
|
||||||
|
self.cursor.collapse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Key(key_event) if key_event.code == KeyCode::Char('k') => {
|
||||||
|
if self.contents.len() - 1 - self.cursor.head >= 0x10 {
|
||||||
|
self.cursor.head += 0x10;
|
||||||
|
self.cursor.collapse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Key(key_event) if key_event.code == KeyCode::Char('l') => {
|
||||||
|
if self.contents.len() - 1 - self.cursor.head >= 1 {
|
||||||
|
self.cursor.head += 1;
|
||||||
|
self.cursor.collapse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Key(key_event) if key_event.code == KeyCode::Char('I') => {
|
||||||
|
if self.cursor.head >= 0x10 {
|
||||||
|
self.cursor.head -= 0x10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Key(key_event) if key_event.code == KeyCode::Char('J') => {
|
||||||
|
if self.cursor.head >= 1 {
|
||||||
|
self.cursor.head -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Key(key_event) if key_event.code == KeyCode::Char('K') => {
|
||||||
|
if self.contents.len() - 1 - self.cursor.head >= 0x10 {
|
||||||
|
self.cursor.head += 0x10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Key(key_event) if key_event.code == KeyCode::Char('L') => {
|
||||||
|
if self.contents.len() - 1 - self.cursor.head >= 1 {
|
||||||
|
self.cursor.head += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Key(key_event) if key_event.code == KeyCode::Char('w') => {
|
||||||
|
self.cursor.to_next_word(self.contents.len() - 1);
|
||||||
|
}
|
||||||
|
Event::Key(key_event) if key_event.code == KeyCode::Char('e') => {
|
||||||
|
self.cursor.to_next_end(self.contents.len() - 1);
|
||||||
|
}
|
||||||
|
Event::Key(key_event) if key_event.code == KeyCode::Char('b') => {
|
||||||
|
self.cursor.to_previous_beginning();
|
||||||
|
}
|
||||||
|
Event::Key(key_event) if key_event.code == KeyCode::Char(';') => {
|
||||||
|
self.cursor.collapse();
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-14
@@ -19,7 +19,7 @@ impl Widget for &App {
|
|||||||
let lines: Vec<_> = chunks
|
let lines: Vec<_> = chunks
|
||||||
.iter()
|
.iter()
|
||||||
.zip((self.scroll_position..).step_by(BYTES_PER_LINE))
|
.zip((self.scroll_position..).step_by(BYTES_PER_LINE))
|
||||||
.map(|(bytes, address)| render_line(address, bytes))
|
.map(|(bytes, address)| self.render_line(address, bytes))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let text = Text::from(lines);
|
let text = Text::from(lines);
|
||||||
@@ -28,15 +28,17 @@ impl Widget for &App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(mismatched_lifetime_syntaxes)]
|
impl App {
|
||||||
fn render_line(address: usize, bytes: &[u8; BYTES_PER_LINE]) -> Line {
|
#[allow(mismatched_lifetime_syntaxes)]
|
||||||
|
fn render_line(&self, address: usize, bytes: &[u8; BYTES_PER_LINE]) -> Line {
|
||||||
let spans: Vec<Span<'_>> = iter::once(address::render_address(address))
|
let spans: Vec<Span<'_>> = iter::once(address::render_address(address))
|
||||||
.chain(hex::render_chunks(bytes))
|
.chain(self.render_chunks(address, bytes))
|
||||||
.chain(iter::once(" ".into()))
|
.chain(iter::once(" ".into()))
|
||||||
.chain(character_panel::render_character_panel(bytes))
|
.chain(character_panel::render_character_panel(bytes))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Line::from(spans)
|
Line::from(spans)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod address {
|
mod address {
|
||||||
@@ -53,11 +55,16 @@ mod address {
|
|||||||
mod hex {
|
mod hex {
|
||||||
use std::{borrow::Cow, mem};
|
use std::{borrow::Cow, mem};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ratatui::{style::{Color, Style}, text::Span};
|
use ratatui::{style::{Color, Style, Stylize}, text::Span};
|
||||||
|
|
||||||
use crate::{BYTES_PER_LINE, BYTES_PER_CHUNK, cardinality::HasCardinality, empty_span::empty_span};
|
use crate::{BYTES_PER_LINE, BYTES_PER_CHUNK, app::App, cardinality::HasCardinality, empty_span::empty_span, cursor::InCursor};
|
||||||
|
|
||||||
pub fn render_chunks(bytes: &[u8; BYTES_PER_LINE]) -> impl IntoIterator<Item=Span<'static>> {
|
impl App {
|
||||||
|
pub fn render_chunks(
|
||||||
|
&self,
|
||||||
|
address: usize,
|
||||||
|
bytes: &[u8; BYTES_PER_LINE]
|
||||||
|
) -> impl IntoIterator<Item=Span<'static>> {
|
||||||
let (chunks, remainder) = bytes.as_chunks::<BYTES_PER_CHUNK>();
|
let (chunks, remainder) = bytes.as_chunks::<BYTES_PER_CHUNK>();
|
||||||
|
|
||||||
assert!(remainder.is_empty());
|
assert!(remainder.is_empty());
|
||||||
@@ -66,25 +73,42 @@ mod hex {
|
|||||||
chunks
|
chunks
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.map(render_chunk)
|
.zip((address..).step_by(BYTES_PER_CHUNK))
|
||||||
|
.map(|(chunk, address)| self.render_chunk(address, chunk))
|
||||||
.intersperse(vec![" ".into()])
|
.intersperse(vec![" ".into()])
|
||||||
.flatten()
|
.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_chunk(bytes: [u8; BYTES_PER_CHUNK]) -> Vec<Span<'static>> {
|
fn render_chunk(
|
||||||
|
&self,
|
||||||
|
address: usize,
|
||||||
|
bytes: [u8; BYTES_PER_CHUNK]
|
||||||
|
) -> Vec<Span<'static>> {
|
||||||
#[allow(unstable_name_collisions)]
|
#[allow(unstable_name_collisions)]
|
||||||
bytes
|
bytes
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.map(render_byte)
|
.zip(address..)
|
||||||
.intersperse(" ".into())
|
.map(|(byte, address)| self.render_byte_at(address, byte))
|
||||||
|
.intersperse(" ".into()) // TODO: highlight if selected
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_byte(byte: u8) -> Span<'static> {
|
fn render_byte_at(
|
||||||
|
&self,
|
||||||
|
address: usize,
|
||||||
|
byte: u8
|
||||||
|
) -> Span<'static> {
|
||||||
const SPAN_FOR_BYTE: [Span; u8::CARDINALITY] = create_byte_lookup_table();
|
const SPAN_FOR_BYTE: [Span; u8::CARDINALITY] = create_byte_lookup_table();
|
||||||
|
|
||||||
SPAN_FOR_BYTE[byte as usize].clone()
|
let span = SPAN_FOR_BYTE[byte as usize].clone();
|
||||||
|
|
||||||
|
match self.cursor.contains(address) {
|
||||||
|
Some(InCursor::Head) => span.bg(Color::Gray),
|
||||||
|
Some(InCursor::Rest) => span.bg(Color::Indexed(242)),
|
||||||
|
None => span,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn create_byte_lookup_table() -> [Span<'static>; u8::CARDINALITY] {
|
const fn create_byte_lookup_table() -> [Span<'static>; u8::CARDINALITY] {
|
||||||
@@ -144,7 +168,9 @@ mod character_panel {
|
|||||||
|
|
||||||
use crate::{BYTES_PER_LINE, cardinality::HasCardinality, empty_span::empty_span};
|
use crate::{BYTES_PER_LINE, cardinality::HasCardinality, empty_span::empty_span};
|
||||||
|
|
||||||
pub fn render_character_panel(bytes: &[u8; BYTES_PER_LINE]) -> impl IntoIterator<Item=Span<'static>> {
|
pub fn render_character_panel(
|
||||||
|
bytes: &[u8; BYTES_PER_LINE]
|
||||||
|
) -> impl IntoIterator<Item=Span<'static>> {
|
||||||
bytes
|
bytes
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
|
|||||||
+288
@@ -0,0 +1,288 @@
|
|||||||
|
#[derive(Debug, Default, PartialEq, Eq)]
|
||||||
|
pub struct Cursor {
|
||||||
|
pub head: usize,
|
||||||
|
pub tail: usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum InCursor {
|
||||||
|
Head,
|
||||||
|
Rest
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cursor {
|
||||||
|
pub const fn contains(&self, index: usize) -> Option<InCursor> {
|
||||||
|
if index == self.head {
|
||||||
|
Some(InCursor::Head)
|
||||||
|
} else if (self.head < index && index <= self.tail) ||
|
||||||
|
(self.tail <= index && index < self.head)
|
||||||
|
{
|
||||||
|
Some(InCursor::Rest)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn collapse(&mut self) {
|
||||||
|
self.tail = self.head;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_next_word(&mut self, max: usize) {
|
||||||
|
if self.head == max { return }
|
||||||
|
|
||||||
|
if self.head % 4 == 0 { // at the beginning of a word
|
||||||
|
self.head = (self.head + 4).min(max);
|
||||||
|
} {
|
||||||
|
self.head = self.head.next_multiple_of(4).min(max);
|
||||||
|
}
|
||||||
|
self.collapse();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_next_end(&mut self, max: usize) {
|
||||||
|
if self.head == max { return }
|
||||||
|
|
||||||
|
self.collapse();
|
||||||
|
if self.head % 4 == 3 { // at the end of a word
|
||||||
|
self.tail = self.head + 1;
|
||||||
|
self.head = (self.head + 4).min(max);
|
||||||
|
} else {
|
||||||
|
self.head = ((self.head + 1).next_multiple_of(4) - 1).min(max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn to_previous_beginning(&mut self) {
|
||||||
|
if self.head == 0 { return }
|
||||||
|
|
||||||
|
self.collapse();
|
||||||
|
if self.head % 4 == 0 { // at the beginning of a word
|
||||||
|
self.tail = self.head - 1;
|
||||||
|
self.head -= 4;
|
||||||
|
} else {
|
||||||
|
self.head -= self.head % 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod tests {
|
||||||
|
use crate::cursor::Cursor;
|
||||||
|
|
||||||
|
impl Cursor {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
const fn at(index: usize) -> Self {
|
||||||
|
Self { head: index, tail: index }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn next_word() {
|
||||||
|
// [a]bcd efgh -> abcd [e]fgh
|
||||||
|
let mut cursor = Cursor::at(0);
|
||||||
|
cursor.to_next_word(99);
|
||||||
|
assert_eq!(cursor, Cursor::at(4));
|
||||||
|
|
||||||
|
// a[b]cd efgh -> abcd [e]fgh
|
||||||
|
let mut cursor = Cursor::at(1);
|
||||||
|
cursor.to_next_word(99);
|
||||||
|
assert_eq!(cursor, Cursor::at(4));
|
||||||
|
|
||||||
|
// ab[c]d efgh -> abcd [e]fgh
|
||||||
|
let mut cursor = Cursor::at(2);
|
||||||
|
cursor.to_next_word(99);
|
||||||
|
assert_eq!(cursor, Cursor::at(4));
|
||||||
|
|
||||||
|
// abc[d] efgh -> abcd [e]fgh
|
||||||
|
let mut cursor = Cursor::at(3);
|
||||||
|
cursor.to_next_word(99);
|
||||||
|
assert_eq!(cursor, Cursor::at(4));
|
||||||
|
|
||||||
|
// [a]bcd -> abc[d]
|
||||||
|
let mut cursor = Cursor::at(0);
|
||||||
|
cursor.to_next_word(3);
|
||||||
|
assert_eq!(cursor, Cursor::at(3));
|
||||||
|
|
||||||
|
// [a]bc -> ab[c]
|
||||||
|
let mut cursor = Cursor::at(0);
|
||||||
|
cursor.to_next_word(2);
|
||||||
|
assert_eq!(cursor, Cursor::at(2));
|
||||||
|
|
||||||
|
// [a]b -> a[b]
|
||||||
|
let mut cursor = Cursor::at(0);
|
||||||
|
cursor.to_next_word(1);
|
||||||
|
assert_eq!(cursor, Cursor::at(1));
|
||||||
|
|
||||||
|
// [a] -> [a]
|
||||||
|
let mut cursor = Cursor::at(0);
|
||||||
|
cursor.to_next_word(0);
|
||||||
|
assert_eq!(cursor, Cursor::at(0));
|
||||||
|
|
||||||
|
// ab[c]d -> abc[d]
|
||||||
|
let mut cursor = Cursor::at(2);
|
||||||
|
cursor.to_next_word(3);
|
||||||
|
assert_eq!(cursor, Cursor::at(3));
|
||||||
|
|
||||||
|
// abc[d] -> abc[d]
|
||||||
|
let mut cursor = Cursor::at(3);
|
||||||
|
cursor.to_next_word(3);
|
||||||
|
assert_eq!(cursor, Cursor::at(3));
|
||||||
|
|
||||||
|
// ab[c[d] -> ab[c[d]
|
||||||
|
let mut cursor = Cursor { tail: 2, head: 3 };
|
||||||
|
cursor.to_next_word(3);
|
||||||
|
assert_eq!(cursor, Cursor { tail: 2, head: 3 });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn next_end() {
|
||||||
|
// [a]bcd -> [abcd]
|
||||||
|
let mut cursor = Cursor::at(0);
|
||||||
|
cursor.to_next_end(99);
|
||||||
|
assert_eq!(cursor, Cursor { tail: 0, head: 3 });
|
||||||
|
|
||||||
|
// a[b]cd -> [abcd]
|
||||||
|
let mut cursor = Cursor::at(1);
|
||||||
|
cursor.to_next_end(99);
|
||||||
|
assert_eq!(cursor, Cursor { tail: 1, head: 3 });
|
||||||
|
|
||||||
|
// ab[c]d -> [abcd]
|
||||||
|
let mut cursor = Cursor::at(2);
|
||||||
|
cursor.to_next_end(99);
|
||||||
|
assert_eq!(cursor, Cursor { tail: 2, head: 3 });
|
||||||
|
|
||||||
|
// abc[d] efgh -> abcd [efgh]
|
||||||
|
let mut cursor = Cursor::at(3);
|
||||||
|
cursor.to_next_end(99);
|
||||||
|
assert_eq!(cursor, Cursor { tail: 4, head: 7 });
|
||||||
|
|
||||||
|
// abcd [e]fgh -> abcd [efgh]
|
||||||
|
let mut cursor = Cursor::at(4);
|
||||||
|
cursor.to_next_end(99);
|
||||||
|
assert_eq!(cursor, Cursor { tail: 4, head: 7 });
|
||||||
|
|
||||||
|
// abcd e[f]gh -> abcd e[fgh]
|
||||||
|
let mut cursor = Cursor::at(5);
|
||||||
|
cursor.to_next_end(99);
|
||||||
|
assert_eq!(cursor, Cursor { tail: 5, head: 7 });
|
||||||
|
|
||||||
|
// abcd ef[g]h -> abcd ef[gh]
|
||||||
|
let mut cursor = Cursor::at(6);
|
||||||
|
cursor.to_next_end(99);
|
||||||
|
assert_eq!(cursor, Cursor { tail: 6, head: 7 });
|
||||||
|
|
||||||
|
// abcd efg[h] ijkl -> abcd efgh [ijkl]
|
||||||
|
let mut cursor = Cursor::at(7);
|
||||||
|
cursor.to_next_end(99);
|
||||||
|
assert_eq!(cursor, Cursor { tail: 8, head: 11 });
|
||||||
|
|
||||||
|
// abcd efg[h] -> abcd efg[h]
|
||||||
|
let mut cursor = Cursor::at(7);
|
||||||
|
cursor.to_next_end(7);
|
||||||
|
assert_eq!(cursor, Cursor { tail: 7, head: 7 });
|
||||||
|
|
||||||
|
// abcd e[fgh] -> abcd e[fgh]
|
||||||
|
let mut cursor = Cursor { tail: 5, head: 7 };
|
||||||
|
cursor.to_next_end(7);
|
||||||
|
assert_eq!(cursor, Cursor { tail: 5, head: 7 });
|
||||||
|
|
||||||
|
// a[b]c -> a[bc]
|
||||||
|
let mut cursor = Cursor::at(1);
|
||||||
|
cursor.to_next_end(2);
|
||||||
|
assert_eq!(cursor, Cursor { tail: 1, head: 2 });
|
||||||
|
|
||||||
|
// a[bc] -> a[bc]
|
||||||
|
let mut cursor = Cursor { tail: 1, head: 2};
|
||||||
|
cursor.to_next_end(2);
|
||||||
|
assert_eq!(cursor, Cursor { tail: 1, head: 2 });
|
||||||
|
|
||||||
|
// a[b] -> a[b]
|
||||||
|
let mut cursor = Cursor::at(1);
|
||||||
|
cursor.to_next_end(1);
|
||||||
|
assert_eq!(cursor, Cursor::at(1));
|
||||||
|
|
||||||
|
// [a]b -> [ab]
|
||||||
|
let mut cursor = Cursor::at(0);
|
||||||
|
cursor.to_next_end(1);
|
||||||
|
assert_eq!(cursor, Cursor { tail: 0, head: 1 });
|
||||||
|
|
||||||
|
// [ab] -> [ab]
|
||||||
|
let mut cursor = Cursor { tail: 0, head: 1};
|
||||||
|
cursor.to_next_end(1);
|
||||||
|
assert_eq!(cursor, Cursor { tail: 0, head: 1 });
|
||||||
|
|
||||||
|
// [a] -> [a]
|
||||||
|
let mut cursor = Cursor::at(0);
|
||||||
|
cursor.to_next_end(0);
|
||||||
|
assert_eq!(cursor, Cursor::at(0));
|
||||||
|
|
||||||
|
// [a]bcd] -> [abc[d]
|
||||||
|
let mut cursor = Cursor { head: 0, tail: 3 };
|
||||||
|
cursor.to_next_end(99);
|
||||||
|
assert_eq!(cursor, Cursor { tail: 0, head: 3 });
|
||||||
|
|
||||||
|
// [a[b]cd -> a[bc[d]
|
||||||
|
let mut cursor = Cursor { tail: 0, head: 1 };
|
||||||
|
cursor.to_next_end(99);
|
||||||
|
assert_eq!(cursor, Cursor { tail: 1, head: 3 });
|
||||||
|
|
||||||
|
// abc[d] ef -> abcd [ef]
|
||||||
|
let mut cursor = Cursor::at(3);
|
||||||
|
cursor.to_next_end(5);
|
||||||
|
assert_eq!(cursor, Cursor { tail: 4, head: 5 });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn previous_beginning() {
|
||||||
|
// abcd efgh [i]jkl -> abcd [efgh] ijkl
|
||||||
|
let mut cursor = Cursor::at(8);
|
||||||
|
cursor.to_previous_beginning();
|
||||||
|
assert_eq!(cursor, Cursor { head: 4, tail: 7 });
|
||||||
|
|
||||||
|
// abcd efg[h] -> abcd [efgh]
|
||||||
|
let mut cursor = Cursor::at(7);
|
||||||
|
cursor.to_previous_beginning();
|
||||||
|
assert_eq!(cursor, Cursor { head: 4, tail: 7 });
|
||||||
|
|
||||||
|
// abcd ef[g]h -> abcd [efg]h
|
||||||
|
let mut cursor = Cursor::at(6);
|
||||||
|
cursor.to_previous_beginning();
|
||||||
|
assert_eq!(cursor, Cursor { head: 4, tail: 6 });
|
||||||
|
|
||||||
|
// abcd e[f]gh -> abcd [ef]gh
|
||||||
|
let mut cursor = Cursor::at(5);
|
||||||
|
cursor.to_previous_beginning();
|
||||||
|
assert_eq!(cursor, Cursor { head: 4, tail: 5 });
|
||||||
|
|
||||||
|
// abcd [e]fgh -> [abcd] efgh
|
||||||
|
let mut cursor = Cursor::at(4);
|
||||||
|
cursor.to_previous_beginning();
|
||||||
|
assert_eq!(cursor, Cursor { head: 0, tail: 3 });
|
||||||
|
|
||||||
|
// abc[d] -> [abcd]
|
||||||
|
let mut cursor = Cursor::at(3);
|
||||||
|
cursor.to_previous_beginning();
|
||||||
|
assert_eq!(cursor, Cursor { head: 0, tail: 3 });
|
||||||
|
|
||||||
|
// ab[c]d -> [abc]d
|
||||||
|
let mut cursor = Cursor::at(2);
|
||||||
|
cursor.to_previous_beginning();
|
||||||
|
assert_eq!(cursor, Cursor { head: 0, tail: 2 });
|
||||||
|
|
||||||
|
// a[b]cd -> [ab]cd
|
||||||
|
let mut cursor = Cursor::at(1);
|
||||||
|
cursor.to_previous_beginning();
|
||||||
|
assert_eq!(cursor, Cursor { head: 0, tail: 1 });
|
||||||
|
|
||||||
|
// [a]bcd -> [a]bcd
|
||||||
|
let mut cursor = Cursor::at(0);
|
||||||
|
cursor.to_previous_beginning();
|
||||||
|
assert_eq!(cursor, Cursor { head: 0, tail: 0 });
|
||||||
|
|
||||||
|
// [abc[d] -> [a]bcd]
|
||||||
|
let mut cursor = Cursor { tail: 0, head: 3 };
|
||||||
|
cursor.to_previous_beginning();
|
||||||
|
assert_eq!(cursor, Cursor { head: 0, tail: 3 });
|
||||||
|
|
||||||
|
// ab[c]d] -> [a]bc]d
|
||||||
|
let mut cursor = Cursor { head: 2, tail: 3 };
|
||||||
|
cursor.to_previous_beginning();
|
||||||
|
assert_eq!(cursor, Cursor { head: 0, tail: 2 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ use app::App;
|
|||||||
mod cardinality;
|
mod cardinality;
|
||||||
mod empty_span;
|
mod empty_span;
|
||||||
mod app;
|
mod app;
|
||||||
|
mod cursor;
|
||||||
|
|
||||||
const BYTES_PER_LINE: usize = 0x10;
|
const BYTES_PER_LINE: usize = 0x10;
|
||||||
const BYTES_PER_CHUNK: usize = 4;
|
const BYTES_PER_CHUNK: usize = 4;
|
||||||
|
|||||||
Reference in New Issue
Block a user