Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 64f8944e59 | |||
| 9c6f273703 | |||
| 01a0ab5978 | |||
| 6517de6b61 | |||
| 42a83b1245 | |||
| 906a152a21 | |||
| b721091c3c | |||
| a69409a36f | |||
| 1f77123cb0 |
@@ -45,7 +45,7 @@ jobs:
|
||||
# cut off the v part of the tag to only search for the number
|
||||
run: grep --quiet "$(echo "${{ github.ref_name }}" | cut -c2-)" Cargo.toml
|
||||
- name: cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
if: ${{ matrix.info.package-extension == 'zip' }}
|
||||
run: tar --create --auto-compress --file "hexapoda-${{ matrix.info.os }}-${{ github.ref_name }}.zip" "completions" "manpage" -C "target/release/" "hexapoda${{ matrix.info.executable-extension }}"
|
||||
- name: release
|
||||
uses: softprops/action-gh-release@v2
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
draft: true
|
||||
name: "${{ github.ref_name }}"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# <img height=40 align=top src="https://github.com/simonomi/hexapoda/blob/main/icon/bug%20colored%20large.png?raw=true"> hexapoda
|
||||
# <img height=40 align=top src="https://github.com/simonomi/hexapoda/blob/main/icon/bug%20colored%20large.png?raw=true"> [hexapoda](https://simonomi.dev/hexapoda)
|
||||
|
||||
a colorful modal hex editor
|
||||
|
||||
@@ -8,24 +8,13 @@ a colorful modal hex editor
|
||||
|
||||
## status
|
||||
|
||||
currently, hexapoda is very unpolished, and missing some major features. if you'd be interested in using it, please let me know! if enough people want, i'd be willing to make it more accessible and write some docs
|
||||
still missing some notable features (see [todo.md](https://github.com/simonomi/hexapoda/blob/main/todo.md)), but ready for general use. visit [the website](https://simonomi.dev/hexapoda) for more detailed documentation
|
||||
|
||||
## features
|
||||
## installation
|
||||
|
||||
- [color-codes bytes](https://simonomi.dev/blog/color-code-your-bytes) by value
|
||||
- modal editing
|
||||
- selection-first, like [Kakoune](https://kakoune.org) and [Helix](https://helix-editor.com)
|
||||
- multiple selections
|
||||
- split selection(s) into #-byte chunks
|
||||
- undo/redo
|
||||
- inspect the current selection(s)
|
||||
- signed, unsigned, binary, fixed-point, UTF-8, color
|
||||
- mark notable offsets
|
||||
- jump to selected offset
|
||||
- short answer: `brew install hexapoda`, `cargo binstall hexapoda`, or `cargo install hexapoda`
|
||||
- [slightly longer answer](https://simonomi.dev/hexapoda/install)
|
||||
|
||||
### notable features that are missing (for now)
|
||||
## config
|
||||
|
||||
- search
|
||||
- diffing
|
||||
- inserting bytes
|
||||
- only replacing and deleting right now
|
||||
see [https://simonomi.dev/hexapoda/config](https://simonomi.dev/hexapoda/config)
|
||||
|
||||
@@ -190,6 +190,8 @@ pub enum BufferAction {
|
||||
|
||||
InspectSelection,
|
||||
InspectSelectionColor,
|
||||
|
||||
StopInspecting,
|
||||
}
|
||||
|
||||
impl BufferAction {
|
||||
@@ -264,6 +266,8 @@ impl BufferAction {
|
||||
|
||||
InspectSelection => true,
|
||||
InspectSelectionColor => true,
|
||||
|
||||
StopInspecting => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -339,6 +343,8 @@ impl From<BufferAction> for &str {
|
||||
|
||||
InspectSelection => "inspect_selection",
|
||||
InspectSelectionColor => "inspect_selection_color",
|
||||
|
||||
StopInspecting => "stop_inspecting",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -423,6 +429,8 @@ impl TryFrom<&str> for BufferAction {
|
||||
"inspect_selection" => Ok(InspectSelection),
|
||||
"inspect_selection_color" => Ok(InspectSelectionColor),
|
||||
|
||||
"stop_inspecting" => Ok(StopInspecting),
|
||||
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
+22
-16
@@ -57,7 +57,7 @@ impl App {
|
||||
let mut buffers: Vec<Buffer> = files
|
||||
.iter()
|
||||
.filter_map(|path| {
|
||||
Buffer::from_file_at(path.clone())
|
||||
Buffer::from_file_at(path)
|
||||
.inspect_err(|error| {
|
||||
error_alert = Some(
|
||||
Span::raw(format!("error reading '{}': {error}", path.display())).red()
|
||||
@@ -198,6 +198,7 @@ impl App {
|
||||
|
||||
fn handle_mouse(&mut self, mouse_event: MouseEvent) -> bool {
|
||||
let position = self.mouse_event_position(mouse_event);
|
||||
let tab_bar_row_count = u16::from(self.buffers.len() > 1);
|
||||
let current_buffer = &mut self.buffers[self.current_buffer_index];
|
||||
|
||||
match mouse_event.kind {
|
||||
@@ -207,6 +208,8 @@ impl App {
|
||||
current_buffer.cursors.clear();
|
||||
current_buffer.clamp_screen_to_primary_cursor(self.window_size);
|
||||
self.is_dragging_mouse = true;
|
||||
} else if mouse_event.row < tab_bar_row_count {
|
||||
self.switch_to_tab_at(mouse_event.column);
|
||||
}
|
||||
|
||||
true
|
||||
@@ -248,10 +251,10 @@ impl App {
|
||||
}
|
||||
|
||||
fn mouse_event_position(&self, mouse_event: MouseEvent) -> Option<usize> {
|
||||
let tab_bar_rows = usize::from(self.buffers.len() > 1);
|
||||
let tab_bar_row_count = usize::from(self.buffers.len() > 1);
|
||||
|
||||
if usize::from(mouse_event.row) < tab_bar_rows ||
|
||||
usize::from(mouse_event.row) - tab_bar_rows >= self.window_size.hex_rows() {
|
||||
if usize::from(mouse_event.row) < tab_bar_row_count ||
|
||||
usize::from(mouse_event.row) - tab_bar_row_count >= self.window_size.hex_rows() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -283,20 +286,23 @@ impl App {
|
||||
|
||||
byte_column.map(|byte_column| {
|
||||
current_buffer.scroll_position +
|
||||
(mouse_event.row as usize - tab_bar_rows) * BYTES_PER_LINE +
|
||||
(mouse_event.row as usize - tab_bar_row_count) * BYTES_PER_LINE +
|
||||
byte_column
|
||||
})
|
||||
}
|
||||
|
||||
fn switch_to_tab_at(&mut self, column: u16) {
|
||||
let mut column: usize = column.into();
|
||||
|
||||
// if let Some(byte_column) = byte_column &&
|
||||
// mouse_event.row as usize - tab_bar_rows < self.window_size.hex_rows()
|
||||
// {
|
||||
// Some(
|
||||
// current_buffer.scroll_position +
|
||||
// (mouse_event.row as usize - tab_bar_rows) * BYTES_PER_LINE +
|
||||
// byte_column
|
||||
// )
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
for buffer_index in 0..self.buffers.len() {
|
||||
let tab_width = self.buffers[buffer_index].file_name.len() + 2;
|
||||
|
||||
if column < tab_width {
|
||||
self.current_buffer_index = buffer_index;
|
||||
return;
|
||||
}
|
||||
|
||||
column -= tab_width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -1,4 +1,4 @@
|
||||
use std::{collections::HashSet, fs::File, io::{self, Read}, path::PathBuf};
|
||||
use std::{collections::HashSet, fs::File, io::{self, Read}, path::{Path, PathBuf}};
|
||||
use crossterm::event::KeyEvent;
|
||||
use ratatui::{style::Stylize, text::Span};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -75,7 +75,9 @@ impl TryFrom<&str> for PartialAction {
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
pub fn from_file_at(file_path: PathBuf) -> io::Result<Self> {
|
||||
pub fn from_file_at(file_path: &Path) -> io::Result<Self> {
|
||||
let file_path = file_path.canonicalize()?;
|
||||
|
||||
let mut file = File::open(&file_path)?;
|
||||
let mut contents = Vec::new();
|
||||
file.read_to_end(&mut contents)?;
|
||||
|
||||
@@ -72,6 +72,8 @@ impl Buffer {
|
||||
|
||||
BufferAction::InspectSelection => self.inspect_selection(),
|
||||
BufferAction::InspectSelectionColor => self.inspect_selection_color(),
|
||||
|
||||
BufferAction::StopInspecting => {},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -949,11 +951,10 @@ const fn is_illegal_control_character(character: char) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
const fn color555_to_color888(color555: u16) -> [u8; 3] {
|
||||
fn color555_to_color888(color555: u16) -> [u8; 3] {
|
||||
[
|
||||
// 8 is the ratio between the number of colors in 555 vs 888 (32:256)
|
||||
(color555 & 0b11111) as u8 * 8,
|
||||
(color555 >> 5 & 0b11111) as u8 * 8,
|
||||
(color555 >> 10 & 0b11111) as u8 * 8
|
||||
(u8::try_from((color555 & 0b11111) * 255 / 31).unwrap()),
|
||||
(u8::try_from((color555 >> 5 & 0b11111) * 255 / 31).unwrap()),
|
||||
(u8::try_from((color555 >> 10 & 0b11111) * 255 / 31).unwrap())
|
||||
]
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ mod extra_statuses;
|
||||
|
||||
impl Widget for &Buffer {
|
||||
fn render(self, area: Rect, buf: &mut ratatui::buffer::Buffer) {
|
||||
// set OSC 6 (current document)
|
||||
print!("\x1B]6;{}\x07", self.file_path.display());
|
||||
|
||||
let screen_end = self.scroll_position + BYTES_PER_LINE * (area.height as usize - 1);
|
||||
let bytes_end = min(screen_end, self.contents.len());
|
||||
|
||||
|
||||
@@ -92,6 +92,8 @@ impl Default for Config {
|
||||
|
||||
(keypress("C- "), InspectSelection.into()),
|
||||
(keypress("A- "), InspectSelectionColor.into()),
|
||||
|
||||
(keypress("escape"), StopInspecting.into()),
|
||||
].into()),
|
||||
(Some(PartialAction::Goto), [
|
||||
(keypress("j"), GotoLineStart.into()),
|
||||
@@ -203,6 +205,9 @@ impl Default for Config {
|
||||
(keypress("u"), Undo.into()),
|
||||
(keypress("U"), Redo.into()),
|
||||
|
||||
(keypress("C-j"), PreviousBuffer.into()),
|
||||
(keypress("C-l"), NextBuffer.into()),
|
||||
|
||||
(keypress("C"), CopySelectionOnNextLine.into()),
|
||||
|
||||
(keypress("("), RotateSelectionsBackward.into()),
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# todo
|
||||
- v1.0
|
||||
- `<escape>` dismiss inspectors
|
||||
- `T` Till
|
||||
- click on tab to go to buffer
|
||||
- `go` goto entered offset
|
||||
- search
|
||||
- `/` hex, `A-/` ASCII
|
||||
- if non-hex-digit typed, search ASCII
|
||||
- `A-*` repeat (entered number) times
|
||||
- copy/paste (`<space>y`/`<space>p`)
|
||||
- inspector translations for varint [#1](https://github.com/simonomi/hexapoda/issues/1#issue-4232822634)
|
||||
- `M` mark at selected offset? (like `Jm`)
|
||||
- diffing
|
||||
|
||||
Reference in New Issue
Block a user