inspect selection

This commit is contained in:
alice pellerin
2026-03-22 00:40:16 -05:00
parent 9e61bf396e
commit ab5b7d4720
7 changed files with 1415 additions and 148 deletions
+69 -1
View File
@@ -1,7 +1,7 @@
use core::slice::GetDisjointMutIndex;
use std::{collections::HashSet, fs::File, io::Read, path::PathBuf};
use crossterm::event::KeyEvent;
use ratatui::{style::{Color, Stylize}, text::Span};
use ratatui::{layout::Rect, style::{Color, Stylize}, text::Span, widgets::Widget};
use crate::{BYTES_PER_LINE, action::{Action, AppAction, bytes_to_nat}, app::WindowSize, config::Config, cursor::Cursor, edit_action::EditAction};
mod widget;
@@ -23,6 +23,9 @@ pub struct Buffer {
pub partial_replace: Option<u8>,
pub alert_message: Span<'static>,
pub popups: Vec<Popup>,
pub inspecting_selection: bool,
pub edit_history: Vec<EditAction>,
// the index *after* the latest edit action
@@ -43,6 +46,14 @@ pub enum PartialAction {
Goto, View, Replace, Space, Repeat, To
}
#[derive(Clone)]
pub struct Popup {
at: usize,
width: u16,
lines: Vec<String>,
color: Option<[u8; 3]>
}
impl Mode {
pub const fn label(self) -> &'static str {
match self {
@@ -74,6 +85,53 @@ impl PartialAction {
}
}
impl Popup {
pub fn new(at: usize, lines: Vec<String>, color: Option<[u8; 3]>) -> Self {
Self {
at,
width: lines
.iter()
.map(|line| line.len() as u16)
.chain(color.map(|_| 7))
.max()
.unwrap_or(0),
lines,
color
}
}
fn area_at(&self, x: u16, y: u16) -> Rect {
Rect {
x,
y,
width: self.width + 2,
height: self.lines.len() as u16 + u16::from(self.color.is_some())
}
}
}
impl Widget for Popup {
fn render(self, area: Rect, buf: &mut ratatui::prelude::Buffer) {
let width = self.width as usize;
for (line, area) in self.lines.iter().zip(area.rows()) {
Span::from(format!(" {line:^width$} "))
.white()
.on_dark_gray()
.render(area, buf);
}
if let Some([red, green, blue]) = self.color {
let color_code = format!("#{red:02X}{green:02X}{blue:02X}");
Span::from(format!(" {color_code:^width$} "))
.fg(Color::Rgb(red, green, blue))
.on_dark_gray()
.render(area.rows().next_back().unwrap(), buf);
}
}
}
impl Buffer {
pub fn new(file_path: PathBuf) -> Self {
let file = File::open(&file_path);
@@ -97,6 +155,9 @@ impl Buffer {
partial_replace: None,
alert_message: "".into(),
popups: Vec::new(),
inspecting_selection: false,
edit_history: Vec::new(),
time_traveling: None,
@@ -115,6 +176,9 @@ impl Buffer {
window_size: WindowSize
) -> Option<AppAction> {
self.alert_message = "".into();
self.popups.clear();
let was_inspecting_selection = self.inspecting_selection;
let app_action = match self.partial_action {
Some(PartialAction::Replace) => {
@@ -134,6 +198,10 @@ impl Buffer {
_ => self.handle_other_modes(event, config, window_size),
};
if was_inspecting_selection {
self.inspecting_selection = false;
}
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());
+42
View File
@@ -43,6 +43,22 @@ impl Widget for &Buffer {
.right_aligned()
.render(status_line_area, buf);
for popup in &self.popups {
if self.scroll_position <= popup.at &&
popup.at < self.scroll_position + (hex_area.height.saturating_sub(1) as usize * BYTES_PER_LINE)
{
let position_on_screen = popup.at - self.scroll_position;
let hex_column = position_on_screen % BYTES_PER_LINE;
let popup_area = popup.area_at(
area.x + byte_column_to_screen_column(hex_column) as u16,
area.y + (position_on_screen / BYTES_PER_LINE) as u16 + 1
);
popup.clone().render(popup_area, buf);
}
}
// if self.partial_action == Some(PartialAction::Space) {
// let input_field_area = Rect::new(area.x, area.bottom() - 2, area.width, 1);
// Span::from("/0F673 ")
@@ -459,3 +475,29 @@ mod extra_statuses {
}
}
}
fn byte_column_to_screen_column(byte_column: usize) -> usize {
match byte_column {
0 => 10,
1 => 13,
2 => 16,
3 => 19,
4 => 23,
5 => 26,
6 => 29,
7 => 32,
8 => 36,
9 => 39,
10 => 42,
11 => 45,
12 => 49,
13 => 52,
14 => 55,
15 => 58,
_ => panic!("byte column must be less than 16"),
}
}