Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 64f8944e59 | |||
| 9c6f273703 | |||
| 01a0ab5978 | |||
| 6517de6b61 | |||
| 42a83b1245 | |||
| 906a152a21 | |||
| b721091c3c | |||
| a69409a36f | |||
| 1f77123cb0 | |||
| c879cdb271 | |||
| 596f3d5c12 | |||
| 7b5d56dd92 | |||
| e94c70fa9e | |||
| 8f38d07385 | |||
| e437cecb48 | |||
| 2198c9f787 | |||
| bd67084b87 | |||
| ebdf1061cf | |||
| b755f4a603 | |||
| 5bdb44ffba | |||
| d320e286c1 | |||
| f8f6f7e3ef | |||
| 150ff05048 | |||
| 4f283ca983 | |||
| d7bdccfddb |
@@ -1,20 +0,0 @@
|
|||||||
name: publish
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
publish:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
id-token: write # Required for OIDC token exchange
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
- name: check version
|
|
||||||
# cut off the v part of the tag to only search for the number
|
|
||||||
run: grep -q "$(echo "${{ github.ref_name }}" | cut -c2-)" Cargo.toml
|
|
||||||
- uses: rust-lang/crates-io-auth-action@v1
|
|
||||||
id: auth
|
|
||||||
- run: cargo publish
|
|
||||||
env:
|
|
||||||
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
|
|
||||||
@@ -6,40 +6,87 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- "v*"
|
- "v*"
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ${{ matrix.info.runs-on }}
|
runs-on: ${{ matrix.info.runs-on }}
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
info:
|
info:
|
||||||
- os: "macOS"
|
- os: "macOS-arm"
|
||||||
runs-on: "macos-latest"
|
runs-on: "macos-latest"
|
||||||
|
package-extension: "tar.gz"
|
||||||
executable-extension: ""
|
executable-extension: ""
|
||||||
- os: "linux-x86"
|
- os: "macOS-intel"
|
||||||
|
runs-on: "macos-26-intel"
|
||||||
|
package-extension: "tar.gz"
|
||||||
|
executable-extension: ""
|
||||||
|
- os: "linux-x86_64"
|
||||||
runs-on: "ubuntu-latest"
|
runs-on: "ubuntu-latest"
|
||||||
|
package-extension: "tar.gz"
|
||||||
executable-extension: ""
|
executable-extension: ""
|
||||||
- os: "linux-arm"
|
- os: "linux-arm"
|
||||||
runs-on: "ubuntu-24.04-arm"
|
runs-on: "ubuntu-24.04-arm"
|
||||||
|
package-extension: "tar.gz"
|
||||||
executable-extension: ""
|
executable-extension: ""
|
||||||
- os: "Windows"
|
- os: "Windows-x86_64"
|
||||||
runs-on: "windows-latest"
|
runs-on: "windows-latest"
|
||||||
|
package-extension: "zip"
|
||||||
executable-extension: ".exe"
|
executable-extension: ".exe"
|
||||||
|
- os: "Windows-arm"
|
||||||
|
runs-on: "windows-11-arm"
|
||||||
|
package-extension: "zip"
|
||||||
|
executable-extension: ".exe"
|
||||||
|
steps:
|
||||||
|
- name: checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
- name: check version
|
||||||
|
# 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@v5
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
- name: test
|
||||||
|
run: cargo test --release
|
||||||
|
- name: make completion/manpage folders
|
||||||
|
run: mkdir completions; mkdir manpage
|
||||||
|
- name: build
|
||||||
|
run: cargo build --release --locked
|
||||||
|
env:
|
||||||
|
HEXAPODA_COMPLETIONS: completions
|
||||||
|
HEXAPODA_MANPAGE: manpage
|
||||||
|
- name: package-tar-gz
|
||||||
|
if: ${{ matrix.info.package-extension == 'tar.gz' }}
|
||||||
|
run: tar --create --gzip --file "hexapoda-${{ matrix.info.os }}-${{ github.ref_name }}.tar.gz" "completions" "manpage" -C "target/release/" "hexapoda${{ matrix.info.executable-extension }}"
|
||||||
|
- name: package-zip
|
||||||
|
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@v3
|
||||||
|
with:
|
||||||
|
draft: true
|
||||||
|
name: "${{ github.ref_name }}"
|
||||||
|
files: "hexapoda-${{ matrix.info.os }}-${{ github.ref_name }}.${{ matrix.info.package-extension }}"
|
||||||
|
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: release
|
||||||
|
permissions:
|
||||||
|
id-token: write # Required for OIDC token exchange
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
- name: check version
|
- name: check version
|
||||||
# cut off the v part of the tag to only search for the number
|
# cut off the v part of the tag to only search for the number
|
||||||
run: grep -q "$(echo "${{ github.ref_name }}" | cut -c2-)" Cargo.toml
|
# include the " = " to not match on main, only v* tags
|
||||||
- run: cargo test --release
|
run: grep -q " = \"$(echo "${{ github.ref_name }}" | cut -c2-)\"" Cargo.toml
|
||||||
- run: cargo build --release --locked
|
- uses: rust-lang/crates-io-auth-action@v1
|
||||||
- name: package
|
id: auth
|
||||||
# TODO: include completions/man page
|
- run: cargo publish
|
||||||
run: tar -azcf "hexapoda-${{ matrix.info.os }}-${{ github.ref_name }}.zip" -C "target/release/" "hexapoda${{ matrix.info.executable-extension }}"
|
env:
|
||||||
- name: release
|
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
draft: true
|
|
||||||
name: "${{ github.ref_name }}"
|
|
||||||
files: hexapoda-${{ matrix.info.os }}-${{ github.ref_name }}.zip
|
|
||||||
|
|||||||
Generated
+1
-1
@@ -568,7 +568,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hexapoda"
|
name = "hexapoda"
|
||||||
version = "0.2.0"
|
version = "0.2.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
|
|||||||
+25
-1
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "hexapoda"
|
name = "hexapoda"
|
||||||
# if run manually, CI will check for the string "ain" (lol), so here you go :)
|
# if run manually, CI will check for the string "ain" (lol), so here you go :)
|
||||||
version = "0.2.0"
|
version = "0.2.3"
|
||||||
description = "a colorful modal hex editor"
|
description = "a colorful modal hex editor"
|
||||||
repository = "https://github.com/simonomi/hexapoda"
|
repository = "https://github.com/simonomi/hexapoda"
|
||||||
keywords = ["cli", "tui", "hex", "tool", "editor"]
|
keywords = ["cli", "tui", "hex", "tool", "editor"]
|
||||||
@@ -23,3 +23,27 @@ clap = { version = "4.6.0", features = ["derive"] }
|
|||||||
clap_complete = "4.6.3"
|
clap_complete = "4.6.3"
|
||||||
clap_complete_nushell = "4.6.0"
|
clap_complete_nushell = "4.6.0"
|
||||||
clap_mangen = "0.3.0"
|
clap_mangen = "0.3.0"
|
||||||
|
|
||||||
|
[package.metadata.binstall.overrides.'cfg(all(target_os = "macos", target_arch = "aarch64" ))']
|
||||||
|
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-macOS-arm-v{ version }{ archive-suffix }"
|
||||||
|
pkg-fmt = "tgz"
|
||||||
|
|
||||||
|
[package.metadata.binstall.overrides.'cfg(all(target_os = "macos", target_arch = "x86_64" ))']
|
||||||
|
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-macOS-intel-v{ version }{ archive-suffix }"
|
||||||
|
pkg-fmt = "tgz"
|
||||||
|
|
||||||
|
[package.metadata.binstall.overrides.'cfg(all(target_os = "linux", target_arch = "x86_64" ))']
|
||||||
|
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-linux-x86_64-v{ version }{ archive-suffix }"
|
||||||
|
pkg-fmt = "tgz"
|
||||||
|
|
||||||
|
[package.metadata.binstall.overrides.'cfg(all(target_os = "linux", target_arch = "aarch64" ))']
|
||||||
|
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-linux-arm-v{ version }{ archive-suffix }"
|
||||||
|
pkg-fmt = "tgz"
|
||||||
|
|
||||||
|
[package.metadata.binstall.overrides.'cfg(all(target_os = "windows", target_arch = "x86_64" ))']
|
||||||
|
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-Windows-x86_64-v{ version }{ archive-suffix }"
|
||||||
|
pkg-fmt = "zip"
|
||||||
|
|
||||||
|
[package.metadata.binstall.overrides.'cfg(all(target_os = "windows", target_arch = "aarch64" ))']
|
||||||
|
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-Windows-arm-v{ version }{ archive-suffix }"
|
||||||
|
pkg-fmt = "zip"
|
||||||
|
|||||||
@@ -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
|
a colorful modal hex editor
|
||||||
|
|
||||||
@@ -8,24 +8,13 @@ a colorful modal hex editor
|
|||||||
|
|
||||||
## status
|
## 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
|
- short answer: `brew install hexapoda`, `cargo binstall hexapoda`, or `cargo install hexapoda`
|
||||||
- modal editing
|
- [slightly longer answer](https://simonomi.dev/hexapoda/install)
|
||||||
- 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
|
|
||||||
|
|
||||||
### notable features that are missing (for now)
|
## config
|
||||||
|
|
||||||
- search
|
see [https://simonomi.dev/hexapoda/config](https://simonomi.dev/hexapoda/config)
|
||||||
- diffing
|
|
||||||
- inserting bytes
|
|
||||||
- only replacing and deleting right now
|
|
||||||
|
|||||||
@@ -7,20 +7,26 @@ use std::io::Error;
|
|||||||
include!("src/arguments.rs");
|
include!("src/arguments.rs");
|
||||||
|
|
||||||
fn main() -> Result<(), Error> {
|
fn main() -> Result<(), Error> {
|
||||||
let output_folder = match env::var_os("OUT_DIR") {
|
let completions_folder = match env::var_os("HEXAPODA_COMPLETIONS") {
|
||||||
None => return Ok(()),
|
Some(folder) if !folder.is_empty() => folder,
|
||||||
Some(output_folder) => output_folder,
|
_ => return Ok(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let manpage_folder = match env::var_os("HEXAPODA_MANPAGE") {
|
||||||
|
Some(folder) if !folder.is_empty() => folder,
|
||||||
|
_ => return Ok(()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut command = Arguments::command();
|
let mut command = Arguments::command();
|
||||||
for &shell in Shell::value_variants() {
|
for &shell in Shell::value_variants() {
|
||||||
generate_to(shell, &mut command, "hexapoda", &output_folder)?;
|
generate_to(shell, &mut command, "hexapoda", &completions_folder)?;
|
||||||
}
|
}
|
||||||
generate_to(Nushell, &mut command, "hexapoda", &output_folder)?;
|
generate_to(Nushell, &mut command, "hexapoda", &completions_folder)?;
|
||||||
|
|
||||||
clap_mangen::generate_to(command, &output_folder)?;
|
clap_mangen::generate_to(command, &manpage_folder)?;
|
||||||
|
|
||||||
println!("cargo:warning=completions and manpage generated in {output_folder:?}");
|
println!("cargo:warning=completions generated in {completions_folder:?}");
|
||||||
|
println!("cargo:warning=manpage generated in {manpage_folder:?}");
|
||||||
println!("cargo:rerun-if-changed=src/arguments.rs");
|
println!("cargo:rerun-if-changed=src/arguments.rs");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
+22
-13
@@ -29,6 +29,11 @@ func camelCaseToSnakeCase(_ string: Substring) -> String {
|
|||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cargoTOMLPath = URL(filePath: "Cargo.toml")
|
||||||
|
let cargoTOML = try String(contentsOf: cargoTOMLPath, encoding: .utf8)
|
||||||
|
|
||||||
|
let versionNumber = cargoTOML.matches(of: #/version = "(?'number'[\d\.]+)"/#).first!.output.number
|
||||||
|
|
||||||
let defaultConfigPath = URL(filePath: "src/config/default.rs")
|
let defaultConfigPath = URL(filePath: "src/config/default.rs")
|
||||||
|
|
||||||
let lines = try String(contentsOf: defaultConfigPath, encoding: .utf8)
|
let lines = try String(contentsOf: defaultConfigPath, encoding: .utf8)
|
||||||
@@ -38,31 +43,35 @@ let lines = try String(contentsOf: defaultConfigPath, encoding: .utf8)
|
|||||||
|
|
||||||
precondition(lines.first!.contains("Mode::Normal"))
|
precondition(lines.first!.contains("Mode::Normal"))
|
||||||
|
|
||||||
|
var output = """
|
||||||
|
{%- highlight toml -%}
|
||||||
|
#:schema https://simonomi.dev/hexapoda/config/schema-v\(versionNumber).json
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
var mode: String?
|
var mode: String?
|
||||||
|
|
||||||
for line in lines {
|
for line in lines {
|
||||||
if let match = line.wholeMatch(of: #/.*Mode::(?'mode'\w*),.*/#) {
|
if let match = line.wholeMatch(of: #/.*Mode::(?'mode'\w*),.*/#) {
|
||||||
mode = match.output.mode.lowercased()
|
mode = match.output.mode.lowercased()
|
||||||
} else if line.contains("None") {
|
} else if line.contains("None") {
|
||||||
print("[\(mode!)]")
|
output += "[\(mode!)]\n"
|
||||||
} else if let match = line.wholeMatch(of: #/.*PartialAction::(?'partialAction'\w*).*/#) {
|
} else if let match = line.wholeMatch(of: #/.*PartialAction::(?'partialAction'\w*).*/#) {
|
||||||
let partialAction = match.output.partialAction.lowercased()
|
let partialAction = match.output.partialAction.lowercased()
|
||||||
print("[\(mode!).\(partialAction)]")
|
output += "[\(mode!).\(partialAction)]\n"
|
||||||
} else if let match = line.wholeMatch(of: #/.*\(keypress\("(?'keypress'.*?)"\), (?'action'.*?)\.into\(\)\).*/#) {
|
} else if let match = line.wholeMatch(of: #/.*\(keypress\("(?'keypress'.*?)"\), (?'action'.*?)\.into\(\)\).*/#) {
|
||||||
if match.output.keypress.contains(where: { !($0.isLetter || $0.isNumber) }) {
|
if match.output.keypress.contains(where: { !($0.isLetter || $0.isNumber) }) {
|
||||||
print(
|
output += "\"\(match.output.keypress)\" = \"\(camelCaseToSnakeCase(match.output.action))\"\n"
|
||||||
"\"\(match.output.keypress)\"",
|
|
||||||
"=",
|
|
||||||
"\"\(camelCaseToSnakeCase(match.output.action))\""
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
print(
|
output += "\(match.output.keypress) = \"\(camelCaseToSnakeCase(match.output.action))\"\n"
|
||||||
match.output.keypress,
|
|
||||||
"=",
|
|
||||||
"\"\(camelCaseToSnakeCase(match.output.action))\""
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
print()
|
output += "\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
output += "{%- endhighlight -%}\n"
|
||||||
|
|
||||||
|
let outputPath = URL(filePath: "~/Documents/programming/websites/simonomi.dev/_includes/hexapoda/hexapoda v\(versionNumber).toml")
|
||||||
|
try Data(output.utf8).write(to: outputPath)
|
||||||
|
|
||||||
|
print("wrote config to \(outputPath.path(percentEncoded: false))")
|
||||||
|
|||||||
@@ -190,6 +190,8 @@ pub enum BufferAction {
|
|||||||
|
|
||||||
InspectSelection,
|
InspectSelection,
|
||||||
InspectSelectionColor,
|
InspectSelectionColor,
|
||||||
|
|
||||||
|
StopInspecting,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BufferAction {
|
impl BufferAction {
|
||||||
@@ -264,6 +266,8 @@ impl BufferAction {
|
|||||||
|
|
||||||
InspectSelection => true,
|
InspectSelection => true,
|
||||||
InspectSelectionColor => true,
|
InspectSelectionColor => true,
|
||||||
|
|
||||||
|
StopInspecting => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -339,6 +343,8 @@ impl From<BufferAction> for &str {
|
|||||||
|
|
||||||
InspectSelection => "inspect_selection",
|
InspectSelection => "inspect_selection",
|
||||||
InspectSelectionColor => "inspect_selection_color",
|
InspectSelectionColor => "inspect_selection_color",
|
||||||
|
|
||||||
|
StopInspecting => "stop_inspecting",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -423,6 +429,8 @@ impl TryFrom<&str> for BufferAction {
|
|||||||
"inspect_selection" => Ok(InspectSelection),
|
"inspect_selection" => Ok(InspectSelection),
|
||||||
"inspect_selection_color" => Ok(InspectSelectionColor),
|
"inspect_selection_color" => Ok(InspectSelectionColor),
|
||||||
|
|
||||||
|
"stop_inspecting" => Ok(StopInspecting),
|
||||||
|
|
||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-15
@@ -57,7 +57,7 @@ impl App {
|
|||||||
let mut buffers: Vec<Buffer> = files
|
let mut buffers: Vec<Buffer> = files
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|path| {
|
.filter_map(|path| {
|
||||||
Buffer::from_file_at(path.clone())
|
Buffer::from_file_at(path)
|
||||||
.inspect_err(|error| {
|
.inspect_err(|error| {
|
||||||
error_alert = Some(
|
error_alert = Some(
|
||||||
Span::raw(format!("error reading '{}': {error}", path.display())).red()
|
Span::raw(format!("error reading '{}': {error}", path.display())).red()
|
||||||
@@ -198,6 +198,7 @@ impl App {
|
|||||||
|
|
||||||
fn handle_mouse(&mut self, mouse_event: MouseEvent) -> bool {
|
fn handle_mouse(&mut self, mouse_event: MouseEvent) -> bool {
|
||||||
let position = self.mouse_event_position(mouse_event);
|
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];
|
let current_buffer = &mut self.buffers[self.current_buffer_index];
|
||||||
|
|
||||||
match mouse_event.kind {
|
match mouse_event.kind {
|
||||||
@@ -207,6 +208,8 @@ impl App {
|
|||||||
current_buffer.cursors.clear();
|
current_buffer.cursors.clear();
|
||||||
current_buffer.clamp_screen_to_primary_cursor(self.window_size);
|
current_buffer.clamp_screen_to_primary_cursor(self.window_size);
|
||||||
self.is_dragging_mouse = true;
|
self.is_dragging_mouse = true;
|
||||||
|
} else if mouse_event.row < tab_bar_row_count {
|
||||||
|
self.switch_to_tab_at(mouse_event.column);
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
@@ -248,9 +251,10 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_event_position(&self, mouse_event: MouseEvent) -> Option<usize> {
|
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 >= 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;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,20 +286,23 @@ impl App {
|
|||||||
|
|
||||||
byte_column.map(|byte_column| {
|
byte_column.map(|byte_column| {
|
||||||
current_buffer.scroll_position +
|
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
|
byte_column
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// if let Some(byte_column) = byte_column &&
|
fn switch_to_tab_at(&mut self, column: u16) {
|
||||||
// mouse_event.row as usize - tab_bar_rows < self.window_size.hex_rows()
|
let mut column: usize = column.into();
|
||||||
// {
|
|
||||||
// Some(
|
for buffer_index in 0..self.buffers.len() {
|
||||||
// current_buffer.scroll_position +
|
let tab_width = self.buffers[buffer_index].file_name.len() + 2;
|
||||||
// (mouse_event.row as usize - tab_bar_rows) * BYTES_PER_LINE +
|
|
||||||
// byte_column
|
if column < tab_width {
|
||||||
// )
|
self.current_buffer_index = buffer_index;
|
||||||
// } else {
|
return;
|
||||||
// None
|
}
|
||||||
// }
|
|
||||||
|
column -= tab_width;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ impl App {
|
|||||||
self.quit();
|
self.quit();
|
||||||
} else {
|
} else {
|
||||||
self.buffers[self.current_buffer_index].alert_message = Span::from(
|
self.buffers[self.current_buffer_index].alert_message = Span::from(
|
||||||
"there are unsaved changes, use Q to override"
|
"unsaved changes, use <space>w to save or Q to override"
|
||||||
).red();
|
).red();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+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 crossterm::event::KeyEvent;
|
||||||
use ratatui::{style::Stylize, text::Span};
|
use ratatui::{style::Stylize, text::Span};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -75,7 +75,9 @@ impl TryFrom<&str> for PartialAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Buffer {
|
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 file = File::open(&file_path)?;
|
||||||
let mut contents = Vec::new();
|
let mut contents = Vec::new();
|
||||||
file.read_to_end(&mut contents)?;
|
file.read_to_end(&mut contents)?;
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ impl Buffer {
|
|||||||
|
|
||||||
BufferAction::InspectSelection => self.inspect_selection(),
|
BufferAction::InspectSelection => self.inspect_selection(),
|
||||||
BufferAction::InspectSelectionColor => self.inspect_selection_color(),
|
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)
|
(u8::try_from((color555 & 0b11111) * 255 / 31).unwrap()),
|
||||||
(color555 & 0b11111) as u8 * 8,
|
(u8::try_from((color555 >> 5 & 0b11111) * 255 / 31).unwrap()),
|
||||||
(color555 >> 5 & 0b11111) as u8 * 8,
|
(u8::try_from((color555 >> 10 & 0b11111) * 255 / 31).unwrap())
|
||||||
(color555 >> 10 & 0b11111) as u8 * 8
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ mod extra_statuses;
|
|||||||
|
|
||||||
impl Widget for &Buffer {
|
impl Widget for &Buffer {
|
||||||
fn render(self, area: Rect, buf: &mut ratatui::buffer::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 screen_end = self.scroll_position + BYTES_PER_LINE * (area.height as usize - 1);
|
||||||
let bytes_end = min(screen_end, self.contents.len());
|
let bytes_end = min(screen_end, self.contents.len());
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
use crate::{buffer::{Buffer, Mode}, utilities::CustomGreys};
|
use crate::{buffer::{Buffer, Mode}, utilities::CustomGreys};
|
||||||
use ratatui::{style::{Color, Stylize}, text::{Line, Span, Text}};
|
use ratatui::{style::{Color, Stylize}, text::{Line, Span}};
|
||||||
|
|
||||||
impl Buffer {
|
impl Buffer {
|
||||||
pub fn render_status_line(&self) -> Text<'_> {
|
pub fn render_status_line(&self) -> Line<'_> {
|
||||||
Text::from(
|
|
||||||
Line::from_iter([
|
Line::from_iter([
|
||||||
self.render_mode(),
|
self.render_mode(),
|
||||||
" ".into(),
|
" ".into(),
|
||||||
@@ -12,7 +11,6 @@ impl Buffer {
|
|||||||
" ".into(),
|
" ".into(),
|
||||||
self.alert_message.clone()
|
self.alert_message.clone()
|
||||||
])
|
])
|
||||||
)
|
|
||||||
.bg(Color::ui_grey())
|
.bg(Color::ui_grey())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,12 +92,19 @@ impl Default for Config {
|
|||||||
|
|
||||||
(keypress("C- "), InspectSelection.into()),
|
(keypress("C- "), InspectSelection.into()),
|
||||||
(keypress("A- "), InspectSelectionColor.into()),
|
(keypress("A- "), InspectSelectionColor.into()),
|
||||||
|
|
||||||
|
(keypress("escape"), StopInspecting.into()),
|
||||||
].into()),
|
].into()),
|
||||||
(Some(PartialAction::Goto), [
|
(Some(PartialAction::Goto), [
|
||||||
(keypress("j"), GotoLineStart.into()),
|
(keypress("j"), GotoLineStart.into()),
|
||||||
(keypress("l"), GotoLineEnd.into()),
|
(keypress("l"), GotoLineEnd.into()),
|
||||||
|
|
||||||
(keypress("g"), GotoFileStart.into()),
|
(keypress("g"), GotoFileStart.into()),
|
||||||
|
(keypress("i"), GotoFileStart.into()),
|
||||||
|
(keypress("k"), GotoFileEnd.into()),
|
||||||
|
|
||||||
|
(keypress("p"), PreviousBuffer.into()),
|
||||||
|
(keypress("n"), NextBuffer.into()),
|
||||||
].into()),
|
].into()),
|
||||||
(Some(PartialAction::View), [
|
(Some(PartialAction::View), [
|
||||||
(keypress("z"), AlignViewCenter.into()),
|
(keypress("z"), AlignViewCenter.into()),
|
||||||
@@ -153,6 +160,7 @@ impl Default for Config {
|
|||||||
(keypress("Q"), Quit.into()),
|
(keypress("Q"), Quit.into()),
|
||||||
|
|
||||||
(keypress("v"), NormalMode.into()),
|
(keypress("v"), NormalMode.into()),
|
||||||
|
(keypress("escape"), NormalMode.into()),
|
||||||
|
|
||||||
(keypress("g"), Goto.into()),
|
(keypress("g"), Goto.into()),
|
||||||
(keypress("z"), View.into()),
|
(keypress("z"), View.into()),
|
||||||
@@ -197,6 +205,9 @@ impl Default for Config {
|
|||||||
(keypress("u"), Undo.into()),
|
(keypress("u"), Undo.into()),
|
||||||
(keypress("U"), Redo.into()),
|
(keypress("U"), Redo.into()),
|
||||||
|
|
||||||
|
(keypress("C-j"), PreviousBuffer.into()),
|
||||||
|
(keypress("C-l"), NextBuffer.into()),
|
||||||
|
|
||||||
(keypress("C"), CopySelectionOnNextLine.into()),
|
(keypress("C"), CopySelectionOnNextLine.into()),
|
||||||
|
|
||||||
(keypress("("), RotateSelectionsBackward.into()),
|
(keypress("("), RotateSelectionsBackward.into()),
|
||||||
@@ -230,6 +241,11 @@ impl Default for Config {
|
|||||||
(keypress("l"), ExtendLineEnd.into()),
|
(keypress("l"), ExtendLineEnd.into()),
|
||||||
|
|
||||||
(keypress("g"), ExtendFileStart.into()),
|
(keypress("g"), ExtendFileStart.into()),
|
||||||
|
(keypress("i"), ExtendFileStart.into()),
|
||||||
|
(keypress("k"), ExtendFileEnd.into()),
|
||||||
|
|
||||||
|
(keypress("p"), PreviousBuffer.into()),
|
||||||
|
(keypress("n"), NextBuffer.into()),
|
||||||
].into()),
|
].into()),
|
||||||
(Some(PartialAction::View), [
|
(Some(PartialAction::View), [
|
||||||
(keypress("z"), AlignViewCenter.into()),
|
(keypress("z"), AlignViewCenter.into()),
|
||||||
|
|||||||
-28
@@ -25,34 +25,6 @@ const CHUNKS_PER_LINE: usize = BYTES_PER_LINE / BYTES_PER_CHUNK;
|
|||||||
const LINES_OF_PADDING: usize = 5;
|
const LINES_OF_PADDING: usize = 5;
|
||||||
const BYTES_OF_PADDING: usize = LINES_OF_PADDING * BYTES_PER_LINE;
|
const BYTES_OF_PADDING: usize = LINES_OF_PADDING * BYTES_PER_LINE;
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// - `go` goto entered offset
|
|
||||||
// - search
|
|
||||||
// - `/` hex, `A-/` ascii
|
|
||||||
// - if non-hex-digit typed, search ascii
|
|
||||||
// - inspector translations for varint
|
|
||||||
// - M mark at selected offset? (like Jm)
|
|
||||||
// - diffing
|
|
||||||
// - doesn't have to be anything fancy, just compare each byte 1:1
|
|
||||||
// - sync scroll ? sync selections ??
|
|
||||||
// - s/A-k/A-K
|
|
||||||
// - sm select marks
|
|
||||||
// - C-a/C-x
|
|
||||||
// - +/- to edit selected bytes by amount ?
|
|
||||||
// - operate on entire selection (u16/u32/etc)
|
|
||||||
// - hex or decimal ?
|
|
||||||
// - modifications
|
|
||||||
// - insert/append
|
|
||||||
// - mode
|
|
||||||
// - add to edit history when *leaving* insert mode
|
|
||||||
// - replace-and-keep-going
|
|
||||||
// - mode
|
|
||||||
// - change
|
|
||||||
// - A-r replaces with ASCII
|
|
||||||
// - jumplist
|
|
||||||
// - p
|
|
||||||
// - [/] to cycle view offset?
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let arguments = Arguments::parse();
|
let arguments = Arguments::parse();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# todo
|
||||||
|
- v1.0
|
||||||
|
- `T` Till
|
||||||
|
- `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
|
||||||
|
- doesn't have to be anything fancy, just compare each byte 1:1
|
||||||
|
- sync scroll ? sync selections ??
|
||||||
|
- `s`/`A-k`/`A-K`
|
||||||
|
- sm select marks
|
||||||
|
- `C-a`/`C-x`
|
||||||
|
- `+`/`-` to edit selected bytes by amount ?
|
||||||
|
- operate on entire selection (u16/u32/etc)
|
||||||
|
- hex or decimal ?
|
||||||
|
- modifications
|
||||||
|
- insert/append
|
||||||
|
- mode
|
||||||
|
- add to edit history when *leaving* insert mode
|
||||||
|
- replace-and-keep-going
|
||||||
|
- mode
|
||||||
|
- change (basically `dh`)
|
||||||
|
- `p` put
|
||||||
|
- `A-r` replaces with ASCII
|
||||||
|
- jumplist (`C-o`/`C-i`)
|
||||||
|
- `[`/`]` to cycle view offset?
|
||||||
Reference in New Issue
Block a user