partial line rendering

This commit is contained in:
alice pellerin
2026-03-17 16:04:55 -05:00
parent 999964abde
commit 1e4948f61e
2 changed files with 108 additions and 10 deletions
+84 -8
View File
@@ -13,16 +13,24 @@ impl Widget for &App {
let (chunks, remainder) = bytes_to_render let (chunks, remainder) = bytes_to_render
.as_chunks::<BYTES_PER_LINE>(); .as_chunks::<BYTES_PER_LINE>();
assert!(remainder.is_empty());
let hex_lines = chunks let hex_lines = chunks
.iter() .iter()
.zip((self.scroll_position..).step_by(BYTES_PER_LINE)) .zip((self.scroll_position..).step_by(BYTES_PER_LINE))
.map(|(bytes, address)| self.render_line(address, bytes)); .map(|(bytes, address)| self.render_line(address, bytes));
let hex_area_text: Text = hex_lines.collect(); let remainder_address = bytes_end - remainder.len();
let remainder_line = if !remainder.is_empty() {
Some(self.render_partial_line(remainder_address, remainder))
} else {
None
};
let hex_text: Text = hex_lines
.chain(remainder_line)
.collect();
let hex_area = Rect::new(area.x, area.y, area.width, area.height - 1); let hex_area = Rect::new(area.x, area.y, area.width, area.height - 1);
hex_area_text.render(hex_area, buf); hex_text.render(hex_area, buf);
let status_line_area = Rect::new(area.x, area.bottom() - 1, area.width, 1); let status_line_area = Rect::new(area.x, area.bottom() - 1, area.width, 1);
self.render_status_line().render(status_line_area, buf); self.render_status_line().render(status_line_area, buf);
@@ -42,6 +50,15 @@ impl App {
.chain(character_panel::render_character_panel(bytes)) .chain(character_panel::render_character_panel(bytes))
.collect() .collect()
} }
#[allow(mismatched_lifetime_syntaxes)]
fn render_partial_line(&self, address: usize, bytes: &[u8]) -> Line {
iter::once(address::render_address(address))
.chain(self.render_partial_chunks(address, bytes))
.chain(iter::once(" ".into()))
.chain(character_panel::render_character_panel(bytes))
.collect()
}
} }
mod address { mod address {
@@ -57,7 +74,7 @@ mod address {
mod hex { mod hex {
use std::{borrow::Cow, mem}; use std::{borrow::Cow, mem};
use itertools::Itertools; use itertools::{Itertools, repeat_n};
use ratatui::{style::{Color, Style, Stylize}, text::Span}; use ratatui::{style::{Color, Style, Stylize}, text::Span};
use crate::{BYTES_PER_CHUNK, BYTES_PER_LINE, CHUNKS_PER_LINE, app::App, cardinality::HasCardinality, cursor::InCursor, empty_span::empty_span, custom_greys::CustomGreys}; use crate::{BYTES_PER_CHUNK, BYTES_PER_LINE, CHUNKS_PER_LINE, app::App, cardinality::HasCardinality, cursor::InCursor, empty_span::empty_span, custom_greys::CustomGreys};
@@ -70,7 +87,7 @@ mod hex {
) -> impl Iterator<Item=Span<'static>> { ) -> impl Iterator<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(), "BYTES_PER_LINE should be a multiple of BYTES_PER_CHUNK");
#[allow(unstable_name_collisions)] #[allow(unstable_name_collisions)]
chunks chunks
@@ -88,6 +105,46 @@ mod hex {
.flatten() .flatten()
} }
pub fn render_partial_chunks(
&self,
address: usize,
bytes: &[u8]
) -> impl Iterator<Item=Span<'static>> {
let (chunks, remainder) = bytes.as_chunks::<BYTES_PER_CHUNK>();
let remainder_address = address + chunks.len() * BYTES_PER_CHUNK;
let remainder_chunks: Option<Vec<_>> = if !remainder.is_empty() {
Some(self.render_partial_chunk(remainder_address, remainder).collect())
} else {
None
};
let chunks_rendered = chunks.len() + remainder_chunks.iter().len();
let chunks_not_rendered = CHUNKS_PER_LINE - chunks_rendered;
let spaces_per_chunk = BYTES_PER_CHUNK - 1;
let bytes_not_rendered = BYTES_PER_LINE - bytes.len();
let padding_width = 2 * bytes_not_rendered +
spaces_per_chunk * chunks_not_rendered;
#[allow(unstable_name_collisions)]
chunks
.iter()
.copied()
.zip((address..).step_by(BYTES_PER_CHUNK))
.map(|(chunk, address)| self.render_chunk(address, &chunk).collect())
.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()
.chain(repeat_n(" ".into(), padding_width))
}
fn render_chunk( fn render_chunk(
&self, &self,
address: usize, address: usize,
@@ -107,6 +164,25 @@ mod hex {
) )
} }
fn render_partial_chunk(
&self,
address: usize,
bytes: &[u8]
) -> impl Iterator<Item=Span<'static>> {
#[allow(unstable_name_collisions)]
bytes
.iter()
.copied()
.zip(address..)
.map(|(byte, address)| self.render_byte_at(address, byte))
.interleave(
(address..)
.take(BYTES_PER_CHUNK)
.skip(1)
.map(|address| self.render_space_before(address))
)
}
fn render_byte_at( fn render_byte_at(
&self, &self,
address: usize, address: usize,
@@ -201,10 +277,10 @@ mod character_panel {
use std::{borrow::Cow, mem}; use std::{borrow::Cow, mem};
use ratatui::{style::{Color, Style}, text::Span}; use ratatui::{style::{Color, Style}, text::Span};
use crate::{BYTES_PER_LINE, cardinality::HasCardinality, empty_span::empty_span}; use crate::{cardinality::HasCardinality, empty_span::empty_span};
pub fn render_character_panel( pub fn render_character_panel(
bytes: &[u8; BYTES_PER_LINE] bytes: &[u8]
) -> impl Iterator<Item=Span<'static>> { ) -> impl Iterator<Item=Span<'static>> {
bytes bytes
.iter() .iter()
+24 -2
View File
@@ -14,12 +14,13 @@ 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;
// TODO: // TODO:
// - refactor input system
// - undo/redo
// - x/X
// - modes // - modes
// - select // - select
// - insert // - insert
// - zz/zt/zb // - zz/zt/zb
// - search
// - jumplist
// - modifications // - modifications
// - insert/append // - insert/append
// - mode // - mode
@@ -29,6 +30,27 @@ const CHUNKS_PER_LINE: usize = BYTES_PER_LINE / BYTES_PER_CHUNK;
// - mode // - mode
// - delete // - delete
// - change // - change
// - highlight cursor in character panel too (but lighter?)
// - edit too
// - modifier on existing keys like teehee? or jump to panel?
// - search
// - jumplist
// - f/t
// - ascii?
// - [/] to cycle view offset?
// - J jump to offset
// future directions
// - switch between cursor size u8s/u16s/u32s/u64s?
// - +/-
// - multi-cursor
// - s/C
// - split selection by u8/16/32/etc
// - 'views' for bytes (i8/16/etc u8/16/etc 20.12/8.4/etc)
// - how to fit??! `-128` longer than `80`
// - mark offsets?
// - utf8?
// - diffing
fn main() { fn main() {
let mut app = App::init(); let mut app = App::init();