Full Code of crumblingstatue/hexerator for AI

main dce0723ead20 cached
117 files
635.2 KB
147.0k tokens
982 symbols
1 requests
Download .txt
Showing preview only (670K chars total). Download the full file or copy to clipboard to get everything.
Repository: crumblingstatue/hexerator
Branch: main
Commit: dce0723ead20
Files: 117
Total size: 635.2 KB

Directory structure:
gitextract_lra3dm9g/

├── .github/
│   └── workflows/
│       ├── linux.yml
│       └── windows.yml
├── .gitignore
├── CHANGELOG.md
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── build.rs
├── hexerator-plugin-api/
│   ├── Cargo.toml
│   └── src/
│       └── lib.rs
├── lua/
│   ├── color.lua
│   └── fill.lua
├── plugins/
│   └── hello-world/
│       ├── Cargo.toml
│       └── src/
│           └── lib.rs
├── rust-toolchain.toml
├── rustfmt.toml
├── scripts/
│   └── gen-prim-test-file.rs
├── src/
│   ├── app/
│   │   ├── backend_command.rs
│   │   ├── command.rs
│   │   ├── debug.rs
│   │   ├── edit_state.rs
│   │   ├── interact_mode.rs
│   │   └── presentation.rs
│   ├── app.rs
│   ├── args.rs
│   ├── backend/
│   │   └── sfml.rs
│   ├── backend.rs
│   ├── color.rs
│   ├── config.rs
│   ├── damage_region.rs
│   ├── data.rs
│   ├── dec_conv.rs
│   ├── edit_buffer.rs
│   ├── find_util.rs
│   ├── gui/
│   │   ├── bottom_panel.rs
│   │   ├── command.rs
│   │   ├── dialogs/
│   │   │   ├── auto_save_reload.rs
│   │   │   ├── jump.rs
│   │   │   ├── lua_color.rs
│   │   │   ├── lua_fill.rs
│   │   │   ├── pattern_fill.rs
│   │   │   ├── truncate.rs
│   │   │   └── x86_asm.rs
│   │   ├── dialogs.rs
│   │   ├── egui_ui_ext.rs
│   │   ├── file_ops.rs
│   │   ├── inspect_panel.rs
│   │   ├── message_dialog.rs
│   │   ├── ops.rs
│   │   ├── root_ctx_menu.rs
│   │   ├── selection_menu.rs
│   │   ├── top_menu/
│   │   │   ├── analysis.rs
│   │   │   ├── cursor.rs
│   │   │   ├── edit.rs
│   │   │   ├── file.rs
│   │   │   ├── help.rs
│   │   │   ├── meta.rs
│   │   │   ├── perspective.rs
│   │   │   ├── plugins.rs
│   │   │   ├── scripting.rs
│   │   │   └── view.rs
│   │   ├── top_menu.rs
│   │   ├── top_panel.rs
│   │   ├── windows/
│   │   │   ├── about.rs
│   │   │   ├── bookmarks.rs
│   │   │   ├── debug.rs
│   │   │   ├── external_command.rs
│   │   │   ├── file_diff_result.rs
│   │   │   ├── find_dialog.rs
│   │   │   ├── find_memory_pointers.rs
│   │   │   ├── layouts.rs
│   │   │   ├── lua_console.rs
│   │   │   ├── lua_editor.rs
│   │   │   ├── lua_help.rs
│   │   │   ├── lua_watch.rs
│   │   │   ├── meta_diff.rs
│   │   │   ├── open_process.rs
│   │   │   ├── perspectives.rs
│   │   │   ├── preferences.rs
│   │   │   ├── regions.rs
│   │   │   ├── script_manager.rs
│   │   │   ├── structs.rs
│   │   │   ├── vars.rs
│   │   │   ├── views.rs
│   │   │   └── zero_partition.rs
│   │   └── windows.rs
│   ├── gui.rs
│   ├── hex_conv.rs
│   ├── hex_ui.rs
│   ├── input.rs
│   ├── layout.rs
│   ├── main.rs
│   ├── meta/
│   │   ├── perspective.rs
│   │   ├── region.rs
│   │   └── value_type.rs
│   ├── meta.rs
│   ├── meta_state.rs
│   ├── parse_radix.rs
│   ├── plugin.rs
│   ├── result_ext.rs
│   ├── scripting.rs
│   ├── session_prefs.rs
│   ├── shell.rs
│   ├── slice_ext.rs
│   ├── source.rs
│   ├── str_ext.rs
│   ├── struct_meta_item.rs
│   ├── timer.rs
│   ├── update.rs
│   ├── util.rs
│   ├── value_color.rs
│   ├── view/
│   │   └── draw.rs
│   ├── view.rs
│   └── windows.rs
└── test_files/
    ├── empty-file
    └── plaintext.txt

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/linux.yml
================================================
name: Linux

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

env:
  CARGO_TERM_COLOR: always

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    - name: Install deps
      run: |
        sudo apt-get update
        sudo apt-get install libpthread-stubs0-dev libgl1-mesa-dev libx11-dev libx11-xcb-dev libxcb-image0-dev libxrandr-dev libxcb-randr0-dev libudev-dev  libfreetype6-dev libglew-dev libjpeg8-dev libgpgme11-dev libjpeg62 libxcursor-dev cmake libclang-dev clang
    - name: Build
      run: cargo build --verbose
    - name: Run tests
      run: cargo test --verbose


================================================
FILE: .github/workflows/windows.yml
================================================
name: Windows

on:
  push:
    branches: [ "main" ]
    tags:
      - v*
  pull_request:
    branches: [ "main" ]

env:
  CARGO_TERM_COLOR: always

jobs:
  build:

    runs-on: windows-latest

    steps:
    - uses: actions/checkout@v4
    - name: Run tests
      run: cargo test --verbose
    - name: Do a release build
      run: cargo build --release --verbose
    - uses: actions/upload-artifact@v4
      with:
        name: hexerator-win64-build
        path: target/release/hexerator.exe

================================================
FILE: .gitignore
================================================
/target


================================================
FILE: CHANGELOG.md
================================================
# Changelog

## [0.4.0] - 2025-03-29

### Tutorial

There is now a basic tutorial you can find here:
<https://crumblingstatue.github.io/hexerator-book/0.4.0/tutorial/00-aitd.html>

### New features

- [Memory mapped file support][mmap]
- [Allow defining data layouts with Rust struct syntax][struct]
  - `View->Ruler` can use struct definitions
- [Mouse drag selection][mdrag]
  - You can finally select regions by dragging the mouse, rather than having to use shift+1/shift+2
- [Block selection with alt+drag][mblock]
  - You can select non-contiguous sections by holding alt and drawing a rectangle with the mouse

[mmap]: <https://crumblingstatue.github.io/hexerator-book/0.4.0/feature-docs/mmap.html>
[struct]: <https://crumblingstatue.github.io/hexerator-book/0.4.0/feature-docs/structs.html>
[mdrag]: <https://crumblingstatue.github.io/hexerator-book/0.4.0/basic-ops/selecting-data.html#mouse-drag-selection>
[mblock]: <https://crumblingstatue.github.io/hexerator-book/0.4.0/basic-ops/selecting-data.html#mouse-block-multi-selection>

### UI changes

- Add custom right panel to file open dialog
  - Shows information about the highlighted file
  - Allows selecting advanced options
- Backtrace support for error popups
- External command window now provides more options for working directory
- Show information about rows/column positions in more places
- `Home`/`End` now jumps to row begin/end.
  - `ctrl+Home`/`ctrl+End` are now used for view begin/end.
- The selection can now be quickly cleared with a `Clear` button in the top panel
- Add a "quick scroll" slider popup to the bottom panel, to quickly navigate huge files.
- Add Find&Replace for `HexString` find type
- Add a bunch of icons to buttons
- Remove superfluous "Perspectives" menu

### Other Improvements

- Make stream buffer size configurable, use a larger default size
- Hexerator now retries opening a file as read-only if there was a permission error
- Hex strings now accept parsing comma separated, or "packed" (unseparated) hex values
- The command line help on Windows is now functional
- Increase/decrease byte (`ctrl+=`/`ctrl+-`) now works on selections
- Add Windows CI
- Bunch of bug fixes and minor UX improvements, as usual

### CLI
Add `--view` flag to select view to focus on startup

## [0.3.0] - 2024-10-16

### UI changes

**Hex Editor:**

- `Del` key zeroes out the byte at cursor

**Bookmarks window:**

- Jump-to button in detail view
- Value edit input in detail view
- Context menu option to copy a bookmark's offset
- Add right click menu option to reoffset all bookmarks based on a known offset (read help label)

**File diff window:**

- Now takes the value types of bookmarks into account, showing the whole values of
  bookmarks instead of just raw bytes.
- Add "Highlight all" button to highlight all differences
- Add "Open this" and "Diff with..." buttons to speed up diffing
  subsequent versions of a file

**Find dialog:**

- Add help hover popups for the find type dropdown
- Add "string diff" and "pattern equivalence" find types. See the help popups ;)
- Add basic replace functionality to Ascii find

**X86 assembly dialog:**

- Add ability to jump to offset of decoded instructions

**Root context menu:**

- Add "copy selection as utf-8 text"
- Add "zero fill" (Shortcut: `Del`)

**External command window:**

- Now openable with `Ctrl+E`
- Allow closing with `Esc` key
- Add "selection only" toggle to only pass selection to external command

**Open process window:**
- Add UI to launch a child process in order to view its memory (hexerator doesn't have to be root)
- The virtual memory map window now makes it more clear that you're no longer
  looking at the list of processes, but the maps for a process.

**Jump dialog:**

- Replace (broken) "relative" option with "absolute"

**Preferences window:**

- Make the ui tabbed
- Small ui improvements

### Lua scripting

- Replaced LuaJIT with Lua 5.4, because LuaJIT is incompatible with `panic=abort`.
- Add Lua syntax highlighting in most places
- Add Lua API help window (`Scripting - Lua help`)
- Add a bunch more API items (see `Scripting -> Lua help`)
- Allow saving named scripts, and add script manager window to overview them
- Add Lua console window for quick evaluation and "watching" expressions
- Scripts can now take arguments (`args` table, e.g. `args.foo`)

### Plugins

New feature. Allow loading dylib plugins. Documentation to be added.
For now, see the `hexerator_plugin_api` crate inside the repo.

### Command line

- Add `--version` flag
- Add `--debug` flag to start with debug logging enabled and debug window open
- Add `--spawn-command <command>...` flag to spawn a child process and open it in process list (hexerator doesn't have to be root)
- Add `--autosave` and `--autoreload [<interval>]` to enable autosave/autoreaload through CLI
- Add `--layout <name>` to switch to a layout at startup
- Add `--new <length>` option to create a new (zero-filled) buffer

### Fixes

- Loading process memory on windows now correctly sets relative offset
- When failing to load a file via command line arg, error reason is now properly displayed

### Other

- `Analysis -> Zero partition` for "zero-partitioning" files that contain large zeroed out sections (like process memory).
- Add feature to autoreload only visible part (as opposed to whole file)
- Replace blocking file dialog with nonblocking egui file dialog
- Update egui to 0.29
- Experimental support for custom color themes (See `Preferences` -> `Style`)
- Make monochrome and "grayscale" hex text colors customizable
- No more dynamic dependency on SFML. It's statically linked now.
- Various bug fixes and minor improvements, too many to list individually

## [0.2.0] - 2023-01-27

### Added

- Support for common value types in find dialog, in addition to u8
- About dialog with version info + links
- Clickable file size label in bottom right corner
- Functionality to change the length of the data (truncate/extend)
- Context menus in process open menu to copy addresses/sizes/etc. to clipboard
- Right click context menu option on a view to remove it from the current layout
- Layout properties is accessible from right click context menu on the layout
- Error reporting message dialog if the program panics
- Each file can set a metafile association to always load that meta when loaded
- Vsync and fps limit settings in preferences window
- Bookmark names are displayed when mouse hovers over a bookmarked offset
- "Open bookmark" context menu option in hex view for existing bookmarks
- "Save as" action
- Hex string search in find dialog (de ad be ef)
- Window title now includes filename of opened file
- Ability to save/load scripts in lua execute dialog
- `app:bookmark_set_int(name, value)` lua method to set integer value of a bookmark
- `app:region_pattern_fill(name, pattern)` lua method to fill a region
- Context menu to copy bookmark names in bookmarks window
- Make the offsets in the find dialog copiable/pasteable
- Add x86 disassembly

### Changed

- Update to egui 0.20
- Open file dialog opens same directory as current file, if available
- Replace most native message boxes with egui ones
- Inspect panel shows value at edit cursor if mouse pointer is over a window that covers the hex view.
- Make path label in top right corner click-to-copy
- Process name filter in process open dialog is now case-insensitive
- "Diff with file" file prompt will now open in same directory as current file
- Don't insert a tab character for text views in edit mode when tab is pressed to switch focus
- Active selection actions in edit menu are now in a submenu named "Selection"
- "Copy as hex" is now known as "Copy as hex text"
- Bookmarks table is now resizable horizontally
- Bookmarks table is now scrollable vertically
- Native dialog boxes now have a title, and their text is selectable and copyable!
- Bookmarks window name filter is now case insensitive
- Bookmarks window description editor is now monospace
- Bookmark description is now in a scroll area
- Bookmarks window "add new at cursor" button selects newly added bookmark automatically
- Create default metadata for empty documents, allowing creation of binary files from scratch with Hexerator
- File path label has context menu for various options, left clicking opens the file in default application

### Fixed

- Show error message box instead of panic when failing to allocate textures
- Prevent fill dialog and Jump dialog from constantly stealing focus when they are open
- Certain dialog types no longer erroneusly stack on top of themselves if opened multiple times.
- Lua fill dialog with empty selection now has a close button.
- Make regions window scroll properly
- Pattern fill dialog is now closeable
- "Select all" action now doesn't select more data than is available, even if region is bigger than data.

## [0.1.0] - 2022-09-16

Initial release.

[0.1.0]: https://github.com/crumblingstatue/hexerator/releases/tag/v0.1.0
[0.2.0]: https://github.com/crumblingstatue/hexerator/releases/tag/v0.2.0
[0.3.0]: https://github.com/crumblingstatue/hexerator/releases/tag/v0.3.0


================================================
FILE: Cargo.toml
================================================
[package]
name = "hexerator"
version = "0.5.0-dev"
edition = "2024"
license = "MIT OR Apache-2.0"

[features]
backend-sfml = ["dep:egui-sf2g", "dep:sf2g"]
default = ["backend-sfml"]

[dependencies]
gamedebug_core = { git = "https://github.com/crumblingstatue/gamedebug_core.git" }
clap = { version = "4.5.4", features = ["derive"] }
anyhow = "1.0.81"
rand = "0.10.0"
rmp-serde = "1.1.2"
serde = { version = "1.0.197", features = ["derive"] }
directories = "6.0.0"
recently_used_list = { git = "https://github.com/crumblingstatue/recently_used_list.git" }
memchr = "2.7.2"
glu-sys = "0.1.4"
thiserror = "2"
either = "1.10.0"
tree_magic_mini = "3.1.6"
slotmap = { version = "1.0.7", features = ["serde"] }
egui-sf2g = { version = "0.7", optional = true }
sf2g = { version = "0.4", optional = true, features = ["text"] }
num-traits = "0.2.18"
serde-big-array = "0.5.1"
egui = { version = "0.34", features = ["serde"] }
egui_extras = { version = "0.34", default-features = false }
itertools = "0.14"
sysinfo = { version = "0.38", default-features = false, features = ["system"] }
proc-maps = "0.4.0"
open = "5.1.2"
arboard = { version = "3.6.0", default-features = false }
paste = "1.0.14"
iced-x86 = "1.21.0"
strum = { version = "0.27", features = ["derive"] }
egui_code_editor = "0.2.14"
# luajit breaks with panic=abort, because it relies on unwinding for exception handling
mlua = { version = "0.11", features = ["luau", "vendored"] }
egui-file-dialog.git = "https://github.com/jannistpl/egui-file-dialog.git"
human_bytes = "0.4.3"
shlex = "1.3.0"
egui-fontcfg = { git = "https://github.com/crumblingstatue/egui-fontcfg.git" }
egui_colors = "0.11.0"
libloading = "0.9"
hexerator-plugin-api = { path = "hexerator-plugin-api" }
image.version = "0.25"
image.default-features = false
image.features = ["png", "bmp"]
structparse = { git = "https://github.com/crumblingstatue/structparse.git" }
memmap2 = "0.9.5"
egui-phosphor.git = "https://github.com/crumblingstatue/egui-phosphor.git"
egui-phosphor.branch = "egui-034"
constcat = "0.6.0"

[target."cfg(windows)".dependencies.windows-sys]
version = "0.59.0"
features = [
    "Win32_System_Diagnostics_Debug",
    "Win32_Foundation",
    "Win32_System_Threading",
]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

# Uncomment in case incremental compilation breaks things
#[profile.dev]
#incremental = false # Buggy rustc is breaking code with incremental compilation

# Compile deps with optimizations in dev mode
[profile.dev.package."*"]
opt-level = 2

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"
lto = "thin"
codegen-units = 1

[build-dependencies]
vergen-gitcl = { version = "9.1.0", default-features = false, features = [
    "build",
    "cargo",
    "rustc",
] }

[workspace]
members = ["hexerator-plugin-api", "plugins/hello-world"]
exclude = ["scripts"]


================================================
FILE: LICENSE-APACHE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright 2022 crumblingstatue and Hexerator contributors

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: LICENSE-MIT
================================================
MIT License

Copyright (c) 2022 crumblingstatue and Hexerator contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Hexerator
Versatile GUI hex editor focused on binary file exploration and aiding pattern recognition. Written in Rust.

Check out [the Hexerator book](https://crumblingstatue.github.io/hexerator-book/0.4.0) for a detailed list of features, and more!

## Note for contributors:
Hexerator only supports latest nightly Rust.
You need an up-to-date nightly to build Hexerator.

Hexerator doesn't shy away from experimenting with unstable Rust features.
Contributors can use any nightly feature they wish.

Contributors however are free to rewrite code to use stable features if it doesn't result in:

- A loss of features
- Reduced performance
- Significantly worse maintainability


================================================
FILE: build.rs
================================================
use {
    std::error::Error,
    vergen_gitcl::{BuildBuilder, CargoBuilder, Emitter, GitclBuilder, RustcBuilder},
};

fn main() -> Result<(), Box<dyn Error>> {
    let gitcl = GitclBuilder::default().sha(false).commit_timestamp(true).build()?;
    let build = BuildBuilder::default().build_timestamp(true).build()?;
    let cargo = CargoBuilder::default()
        .target_triple(true)
        .debug(true)
        .opt_level(true)
        .build()?;
    let rustc = RustcBuilder::default().semver(true).build()?;
    Emitter::default()
        .add_instructions(&gitcl)?
        .add_instructions(&build)?
        .add_instructions(&cargo)?
        .add_instructions(&rustc)?
        .emit()?;
    Ok(())
}


================================================
FILE: hexerator-plugin-api/Cargo.toml
================================================
[package]
name = "hexerator-plugin-api"
version = "0.1.0"
edition = "2024"

[dependencies]

================================================
FILE: hexerator-plugin-api/src/lib.rs
================================================
pub trait Plugin {
    fn name(&self) -> &str;
    fn desc(&self) -> &str;
    fn methods(&self) -> Vec<PluginMethod>;
    fn on_method_called(
        &mut self,
        name: &str,
        params: &[Option<Value>],
        hx: &mut dyn HexeratorHandle,
    ) -> MethodResult;
}

pub type MethodResult = Result<Option<Value>, String>;

pub struct PluginMethod {
    pub method_name: &'static str,
    pub human_name: Option<&'static str>,
    pub desc: &'static str,
    pub params: &'static [MethodParam],
}

pub struct MethodParam {
    pub name: &'static str,
    pub ty: ValueTy,
}

pub enum ValueTy {
    U64,
    String,
}

pub enum Value {
    U64(u64),
    F64(f64),
    String(String),
}

impl ValueTy {
    pub fn label(&self) -> &'static str {
        match self {
            ValueTy::U64 => "u64",
            ValueTy::String => "string",
        }
    }
}

pub trait HexeratorHandle {
    fn selection_range(&self) -> Option<[usize; 2]>;
    fn get_data(&self, start: usize, end: usize) -> Option<&[u8]>;
    fn get_data_mut(&mut self, start: usize, end: usize) -> Option<&mut [u8]>;
    fn debug_log(&self, msg: &str);
    fn perspective(&self, name: &str) -> Option<PerspectiveHandle>;
    fn perspective_rows(&self, ph: &PerspectiveHandle) -> Vec<&[u8]>;
}

pub struct PerspectiveHandle {
    pub key_data: u64,
}

impl PerspectiveHandle {
    pub fn rows<'hx>(&self, hx: &'hx dyn HexeratorHandle) -> Vec<&'hx [u8]> {
        hx.perspective_rows(self)
    }
}


================================================
FILE: lua/color.lua
================================================
return function(b)
    local r = b
    local g = b
    local b = b
    return {r % 256, g % 256, b % 256}
end

================================================
FILE: lua/fill.lua
================================================
-- Return a byte based on offset `off` and the current byte value `b`
function(off, b)
    return off % 256
end

================================================
FILE: plugins/hello-world/Cargo.toml
================================================
[package]
name = "hello-world"
version = "0.1.0"
edition = "2024"

[lib]
crate-type = ["cdylib"]

[dependencies]
hexerator-plugin-api = { path = "../../hexerator-plugin-api" }

================================================
FILE: plugins/hello-world/src/lib.rs
================================================
//! Hexerator hello world example plugin

use hexerator_plugin_api::{
    HexeratorHandle, MethodParam, MethodResult, Plugin, PluginMethod, Value, ValueTy,
};

struct HelloPlugin;

impl Plugin for HelloPlugin {
    fn name(&self) -> &str {
        "Hello world plugin"
    }

    fn desc(&self) -> &str {
        "Hi! I'm an example plugin for Hexerator"
    }

    fn methods(&self) -> Vec<hexerator_plugin_api::PluginMethod> {
        vec![
            PluginMethod {
                method_name: "say_hello",
                human_name: Some("Say hello"),
                desc: "Write 'hello' to debug log.",
                params: &[],
            },
            PluginMethod {
                method_name: "fill_selection",
                human_name: Some("Fill selection"),
                desc: "Fills the selection with 0x42",
                params: &[],
            },
            PluginMethod {
                method_name: "sum_range",
                human_name: None,
                desc: "Sums up the values in the provided range",
                params: &[
                    MethodParam {
                        name: "from",
                        ty: ValueTy::U64,
                    },
                    MethodParam {
                        name: "to",
                        ty: ValueTy::U64,
                    },
                ],
            },
        ]
    }

    fn on_method_called(
        &mut self,
        name: &str,
        params: &[Option<Value>],
        hx: &mut dyn HexeratorHandle,
    ) -> MethodResult {
        match name {
            "say_hello" => {
                hx.debug_log("Hello world!");
                Ok(None)
            }
            "fill_selection" => match hx.selection_range() {
                Some([start, end]) => match hx.get_data_mut(start, end) {
                    Some(data) => {
                        data.fill(0x42);
                        Ok(None)
                    }
                    None => Err("Selection out of bounds".into()),
                },
                None => Err("Selection unavailable".into()),
            },
            "sum_range" => {
                let &[Some(Value::U64(from)), Some(Value::U64(to))] = params else {
                    return Err("Invalid params".into());
                };
                match hx.get_data_mut(from as usize, to as usize) {
                    Some(data) => {
                        let sum: u64 = data.iter().map(|b| *b as u64).sum();
                        Ok(Some(Value::U64(sum)))
                    }
                    None => Err("Out of bounds".into()),
                }
            }
            _ => Err(format!("Unknown method: {name}")),
        }
    }
}

#[unsafe(no_mangle)]
pub extern "Rust" fn hexerator_plugin_new() -> Box<dyn Plugin> {
    Box::new(HelloPlugin)
}


================================================
FILE: rust-toolchain.toml
================================================
[toolchain]
channel = "nightly"
# Lock to a specific nightly before release
#channel = "nightly-2025-03-22"


================================================
FILE: rustfmt.toml
================================================
imports_granularity = "One"
group_imports = "One"
chain_width = 80

================================================
FILE: scripts/gen-prim-test-file.rs
================================================
#!/usr/bin/env -S cargo +nightly -Zscript

use std::{fs::File, io::Write};

fn main() {
    let mut f = File::create("test_files/primitives.bin").unwrap();
    macro_rules! prim {
        ($t:ident, $val:literal) => {
            let v: $t = $val;
            let mut buf = std::io::Cursor::new([0u8; 48]);
            // Write desc
            write!(&mut buf, "{} = {}", stringify!($t), v).unwrap();
            f.write_all(buf.get_ref()).unwrap();
            // Write byte repr
            // le
            buf.get_mut().fill(0);
            buf.get_mut()[..std::mem::size_of::<$t>()].copy_from_slice(&v.to_le_bytes());
            f.write_all(buf.get_ref()).unwrap();
            // be
            buf.get_mut().fill(0);
            buf.get_mut()[..std::mem::size_of::<$t>()].copy_from_slice(&v.to_be_bytes());
            f.write_all(buf.get_ref()).unwrap();
        };
    }
    prim!(u8, 42);
    prim!(i8, 42);
    prim!(u16, 4242);
    prim!(i16, 4242);
    prim!(u32, 424242);
    prim!(i32, 424242);
    prim!(u64, 424242424242);
    prim!(i64, 424242424242);
    prim!(u128, 424242424242424242424242);
    prim!(i128, 424242424242424242424242);
    prim!(f32, 42.4242);
    prim!(f64, 4242.42424242);
}


================================================
FILE: src/app/backend_command.rs
================================================
//! This module is similar in purpose to [`crate::app::command`].
//!
//! See that module for more information.

use {
    super::App, crate::config::Config, egui_sf2g::sf2g::graphics::RenderWindow,
    std::collections::VecDeque,
};

pub enum BackendCmd {
    SetWindowTitle(String),
    ApplyVsyncCfg,
    ApplyFpsLimit,
}

/// Gui command queue.
///
/// Push operations with `push`, and call [`App::flush_backend_command_queue`] when you have
/// exclusive access to the [`App`].
///
/// [`App::flush_backend_command_queue`] is called automatically every frame, if you don't need to perform the operations sooner.
#[derive(Default)]
pub struct BackendCommandQueue {
    inner: VecDeque<BackendCmd>,
}

impl BackendCommandQueue {
    pub fn push(&mut self, command: BackendCmd) {
        self.inner.push_back(command);
    }
}

impl App {
    /// Flush the [`BackendCommandQueue`] and perform all operations queued up.
    ///
    /// Automatically called every frame, but can be called manually if operations need to be
    /// performed sooner.
    pub fn flush_backend_command_queue(&mut self, rw: &mut RenderWindow) {
        while let Some(cmd) = self.backend_cmd.inner.pop_front() {
            perform_command(cmd, rw, &self.cfg);
        }
    }
}

fn perform_command(cmd: BackendCmd, rw: &mut RenderWindow, cfg: &Config) {
    match cmd {
        BackendCmd::SetWindowTitle(title) => rw.set_title(&title),
        BackendCmd::ApplyVsyncCfg => {
            rw.set_vertical_sync_enabled(cfg.vsync);
        }
        BackendCmd::ApplyFpsLimit => {
            rw.set_framerate_limit(cfg.fps_limit);
        }
    }
}


================================================
FILE: src/app/command.rs
================================================
//! Due to various issues with overlapping borrows, it's not always feasible to do every operation
//! on the application state at the time the action is requested.
//!
//! Sometimes we need to wait until we have exclusive access to the application before we can
//! perform an operation.
//!
//! One possible way to do this is to encode whatever data an operation requires, and save it until
//! we have exclusive access, and then perform it.

use {
    super::{App, backend_command::BackendCmd},
    crate::{
        damage_region::DamageRegion,
        data::Data,
        gui::Gui,
        meta::{NamedView, PerspectiveKey, RegionKey},
        scripting::exec_lua,
        shell::msg_if_fail,
        view::{HexData, View, ViewKind},
    },
    mlua::Lua,
    std::{collections::VecDeque, path::Path},
};

pub enum Cmd {
    CreatePerspective {
        region_key: RegionKey,
        name: String,
    },
    RemovePerspective(PerspectiveKey),
    SetSelection(usize, usize),
    SetAndFocusCursor(usize),
    SetLayout(crate::meta::LayoutKey),
    FocusView(crate::meta::ViewKey),
    CreateView {
        perspective_key: PerspectiveKey,
        name: String,
    },
    /// Finish saving a truncated file
    SaveTruncateFinish,
    /// Extend (or truncate) the data buffer to a new length
    ExtendDocument {
        new_len: usize,
    },
    /// Paste bytes at the requested index
    PasteBytes {
        at: usize,
        bytes: Vec<u8>,
    },
    /// A new source was loaded, process the changes
    ProcessSourceChange,
}

/// Application command queue.
///
/// Push operations with `push`, and call `App::flush_command_queue` when you have
/// exclusive access to the `App`.
///
/// `App::flush_command_queue` is called automatically every frame, if you don't need to perform the operations sooner.
#[derive(Default)]
pub struct CommandQueue {
    inner: VecDeque<Cmd>,
}

impl CommandQueue {
    pub fn push(&mut self, command: Cmd) {
        self.inner.push_back(command);
    }
}

impl App {
    /// Flush the [`CommandQueue`] and perform all operations queued up.
    ///
    /// Automatically called every frame, but can be called manually if operations need to be
    /// performed sooner.
    pub fn flush_command_queue(
        &mut self,
        gui: &mut Gui,
        lua: &Lua,
        font_size: u16,
        line_spacing: u16,
    ) {
        while let Some(cmd) = self.cmd.inner.pop_front() {
            perform_command(self, cmd, gui, lua, font_size, line_spacing);
        }
    }
}

/// Perform a command. Called by `App::flush_command_queue`, but can be called manually if you
/// have a `Cmd` you would like you perform.
pub fn perform_command(
    app: &mut App,
    cmd: Cmd,
    gui: &mut Gui,
    lua: &Lua,
    font_size: u16,
    line_spacing: u16,
) {
    match cmd {
        Cmd::CreatePerspective { region_key, name } => {
            let per_key = app.add_perspective_from_region(region_key, name);
            gui.win.perspectives.open.set(true);
            gui.win.perspectives.rename_idx = per_key;
        }
        Cmd::SetSelection(a, b) => {
            app.hex_ui.select_a = Some(a);
            app.hex_ui.select_b = Some(b);
        }
        Cmd::SetAndFocusCursor(off) => {
            app.edit_state.cursor = off;
            app.center_view_on_offset(off);
            app.hex_ui.flash_cursor();
        }
        Cmd::SetLayout(key) => app.hex_ui.current_layout = key,
        Cmd::FocusView(key) => app.hex_ui.focused_view = Some(key),
        Cmd::RemovePerspective(key) => {
            app.meta_state.meta.low.perspectives.remove(key);
            // TODO: Should probably handle dangling keys somehow.
            // either by not allowing removal in that case, or being robust against dangling keys
            // or removing everything that uses a dangling key.
        }
        Cmd::CreateView {
            perspective_key,
            name,
        } => {
            app.meta_state.meta.views.insert(NamedView {
                view: View::new(
                    ViewKind::Hex(HexData::with_font_size(font_size)),
                    perspective_key,
                ),
                name,
            });
        }
        Cmd::SaveTruncateFinish => {
            msg_if_fail(
                app.save_truncated_file_finish(),
                "Save error",
                &mut gui.msg_dialog,
            );
        }
        Cmd::ExtendDocument { new_len } => {
            app.data.resize(new_len, 0);
        }
        Cmd::PasteBytes { at, bytes } => {
            let range = at..at + bytes.len();
            app.data[range.clone()].copy_from_slice(&bytes);
            app.data.widen_dirty_region(DamageRegion::Range(range));
        }
        Cmd::ProcessSourceChange => {
            // Allocate a clean data buffer for streaming sources
            if app.source.as_ref().is_some_and(|src| src.attr.stream) {
                app.data = Data::clean_from_buf(Vec::new());
            }
            app.backend_cmd.push(BackendCmd::SetWindowTitle(format!(
                "{} - Hexerator",
                app.source_file().map_or("no source", path_filename_as_str)
            )));
            if let Some(key) = &app.meta_state.meta.onload_script {
                let scr = &app.meta_state.meta.scripts[*key];
                let content = scr.content.clone();
                let result = exec_lua(
                    lua,
                    &content,
                    app,
                    gui,
                    "",
                    Some(*key),
                    font_size,
                    line_spacing,
                );
                msg_if_fail(
                    result,
                    "Failed to execute onload lua script",
                    &mut gui.msg_dialog,
                );
            }
        }
    }
}

fn path_filename_as_str(path: &Path) -> &str {
    path.file_name()
        .map_or("<no_filename>", |osstr| osstr.to_str().unwrap_or_default())
}


================================================
FILE: src/app/debug.rs
================================================
#![allow(unused_imports)]
use {
    super::App,
    gamedebug_core::{imm, imm_dbg},
};

impl App {
    /// Central place to put some immediate state debugging (using gamedebug_core)
    pub(crate) fn imm_debug_fun(&self) {
        // Put immediate debugging code here (F12 to open debug console)
    }
}


================================================
FILE: src/app/edit_state.rs
================================================
#[derive(Default, Debug)]
pub struct EditState {
    // The editing byte offset
    pub cursor: usize,
    cursor_history: Vec<usize>,
    cursor_history_current: usize,
}

impl EditState {
    /// Set cursor and save history
    pub fn set_cursor(&mut self, offset: usize) {
        self.cursor_history.truncate(self.cursor_history_current);
        self.cursor_history.push(self.cursor);
        self.cursor = offset;
        self.cursor_history_current += 1;
    }
    /// Set cursor, don't save history
    pub fn set_cursor_no_history(&mut self, offset: usize) {
        self.cursor = offset;
    }
    /// Step cursor forward without saving history
    pub fn step_cursor_forward(&mut self) {
        self.cursor += 1;
    }
    /// Step cursor back without saving history
    pub fn step_cursor_back(&mut self) {
        self.cursor = self.cursor.saturating_sub(1);
    }
    /// Offset cursor by amount, not saving history
    pub fn offset_cursor(&mut self, amount: usize) {
        self.cursor += amount;
    }
    pub fn cursor_history_back(&mut self) -> bool {
        if self.cursor_history_current > 0 {
            self.cursor_history.push(self.cursor);
            self.cursor_history_current -= 1;
            self.cursor = self.cursor_history[self.cursor_history_current];
            true
        } else {
            false
        }
    }
    pub fn cursor_history_forward(&mut self) -> bool {
        if self.cursor_history_current + 1 < self.cursor_history.len() {
            self.cursor_history_current += 1;
            self.cursor = self.cursor_history[self.cursor_history_current];
            true
        } else {
            false
        }
    }
}


================================================
FILE: src/app/interact_mode.rs
================================================
/// User interaction mode
///
/// There are 2 modes: View and Edit
#[derive(PartialEq, Eq, Debug)]
pub enum InteractMode {
    /// Mode optimized for viewing the contents
    ///
    /// For example arrow keys scroll the content
    View,
    /// Mode optimized for editing the contents
    ///
    /// For example arrow keys move the cursor
    Edit,
}


================================================
FILE: src/app/presentation.rs
================================================
use {
    crate::{
        color::{RgbaColor, rgba},
        value_color::ColorMethod,
    },
    serde::{Deserialize, Serialize},
};

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Presentation {
    pub color_method: ColorMethod,
    pub invert_color: bool,
    pub sel_color: RgbaColor,
    pub cursor_color: RgbaColor,
    pub cursor_active_color: RgbaColor,
}

impl Default for Presentation {
    fn default() -> Self {
        Self {
            color_method: ColorMethod::Default,
            invert_color: false,
            sel_color: rgba(75, 75, 75, 255),
            cursor_color: rgba(160, 160, 160, 255),
            cursor_active_color: rgba(255, 255, 255, 255),
        }
    }
}


================================================
FILE: src/app.rs
================================================
use {
    self::{
        backend_command::BackendCommandQueue,
        command::{Cmd, CommandQueue},
        edit_state::EditState,
    },
    crate::{
        args::{Args, SourceArgs},
        config::Config,
        damage_region::DamageRegion,
        data::Data,
        gui::{
            Gui,
            message_dialog::{Icon, MessageDialog},
            windows::FileDiffResultWindow,
        },
        hex_ui::HexUi,
        input::Input,
        layout::{Layout, default_margin, do_auto_layout},
        meta::{
            LayoutKey, Meta, NamedRegion, NamedView, PerspectiveKey, PerspectiveMap, RegionKey,
            RegionMap, ViewKey, perspective::Perspective, region::Region,
        },
        meta_state::MetaState,
        plugin::PluginContainer,
        result_ext::AnyhowConv as _,
        session_prefs::{Autoreload, SessionPrefs},
        shell::{msg_fail, msg_if_fail},
        source::{Source, SourceAttributes, SourcePermissions, SourceProvider, SourceState},
        view::{HexData, TextData, View, ViewKind, ViewportScalar},
    },
    anyhow::Context as _,
    egui_sf2g::sf2g::graphics::RenderWindow,
    gamedebug_core::{per, per_dbg},
    hexerator_plugin_api::MethodResult,
    mlua::Lua,
    slotmap::Key as _,
    std::{
        ffi::OsString,
        fs::{File, OpenOptions},
        io::{Read as _, Seek as _, SeekFrom, Write as _},
        path::{Path, PathBuf},
        sync::mpsc::Receiver,
        thread,
        time::Instant,
    },
};

pub mod backend_command;
pub mod command;
mod debug;
pub mod edit_state;
pub mod interact_mode;
pub mod presentation;

/// The hexerator application state
pub struct App {
    pub data: Data,
    pub edit_state: EditState,
    pub input: Input,
    pub src_args: SourceArgs,
    pub source: Option<Source>,
    stream_read_recv: Option<Receiver<Vec<u8>>>,
    pub cfg: Config,
    last_reload: Instant,
    pub preferences: SessionPrefs,
    pub hex_ui: HexUi,
    pub meta_state: MetaState,
    pub clipboard: arboard::Clipboard,
    /// Command queue for queuing up operations to perform on the application state.
    pub cmd: CommandQueue,
    pub backend_cmd: BackendCommandQueue,
    /// A quit was requested
    pub quit_requested: bool,
    pub plugins: Vec<PluginContainer>,
    /// Size of the buffer used for streaming reads
    pub stream_buffer_size: usize,
}

const DEFAULT_STREAM_BUFFER_SIZE: usize = 65_536;

/// Source management
impl App {
    pub fn reload(&mut self) -> anyhow::Result<()> {
        match &mut self.source {
            Some(src) => match &mut src.provider {
                SourceProvider::File(file) => {
                    self.data.reload_from_file(&self.src_args, file)?;
                }
                SourceProvider::Stdin(_) => {
                    anyhow::bail!("Can't reload streaming sources like standard input")
                }
                #[cfg(windows)]
                SourceProvider::WinProc {
                    handle,
                    start,
                    size,
                } => unsafe {
                    crate::windows::read_proc_memory(*handle, &mut self.data, *start, *size)?;
                },
            },
            None => anyhow::bail!("No file to reload"),
        }
        Ok(())
    }
    pub(crate) fn load_file_args(
        &mut self,
        mut src_args: SourceArgs,
        meta_path: Option<PathBuf>,
        msg: &mut MessageDialog,
        font_size: u16,
        line_spacing: u16,
        column_count: Option<usize>,
    ) {
        if load_file_from_src_args(
            &mut src_args,
            &mut self.cfg,
            &mut self.source,
            &mut self.data,
            msg,
            &mut self.cmd,
        ) {
            // Set up meta
            if !self.preferences.keep_meta {
                if let Some(meta_path) = meta_path {
                    if let Err(e) = self.consume_meta_from_file(meta_path, false) {
                        self.set_new_clean_meta(font_size, line_spacing, column_count);
                        msg_fail(&e, "Failed to load metafile", msg);
                    }
                } else if let Some(src_path) = per_dbg!(&src_args.file)
                    && let Some(meta_path) = per_dbg!(self.cfg.meta_assocs.get(src_path))
                {
                    // We only load if the new meta path is not the same as the old.
                    // Keep the current metafile otherwise
                    if self.meta_state.current_meta_path != *meta_path {
                        per!(
                            "Mismatch: {:?} vs. {:?}",
                            self.meta_state.current_meta_path.display(),
                            meta_path.display()
                        );
                        let meta_path = meta_path.clone();
                        if let Err(e) = self.consume_meta_from_file(meta_path.clone(), false) {
                            self.set_new_clean_meta(font_size, line_spacing, column_count);
                            msg_fail(&e, &format!("Failed to load metafile {meta_path:?}"), msg);
                        }
                    }
                } else {
                    // We didn't load any meta, but we're loading a new file.
                    // Set up a new clean meta for it.
                    self.set_new_clean_meta(font_size, line_spacing, column_count);
                }
            }
            self.src_args = src_args;
            if let Some(offset) = self.src_args.jump {
                self.center_view_on_offset(offset);
                self.edit_state.cursor = offset;
                self.hex_ui.flash_cursor();
            }
        }
    }
    pub fn save(&mut self, msg: &mut MessageDialog) -> anyhow::Result<()> {
        let file = match &mut self.source {
            Some(src) => match &mut src.provider {
                SourceProvider::File(file) => file,
                SourceProvider::Stdin(_) => anyhow::bail!("Standard input doesn't support saving"),
                #[cfg(windows)]
                SourceProvider::WinProc { handle, start, .. } => {
                    if let Some(region) = self.data.dirty_region {
                        let mut n_write = 0;
                        unsafe {
                            if windows_sys::Win32::System::Diagnostics::Debug::WriteProcessMemory(
                                *handle,
                                (*start + region.begin) as _,
                                self.data[region.begin..].as_mut_ptr() as _,
                                region.len(),
                                &mut n_write,
                            ) == 0
                            {
                                anyhow::bail!("Failed to write process memory");
                            }
                        }
                        self.data.dirty_region = None;
                    }
                    return Ok(());
                }
            },
            None => anyhow::bail!("No surce opened, nothing to save"),
        };
        // If the file was truncated, we completely save over it
        if self.data.len() != self.data.orig_data_len {
            msg.open(
                Icon::Warn,
                "File truncated/extended",
                "Data is truncated/extended. Are you sure you want to save?",
            );
            msg.custom_button_row_ui(Box::new(|ui, payload, cmd| {
                if ui
                    .button(egui::RichText::new("Save & Truncate").color(egui::Color32::RED))
                    .clicked()
                {
                    payload.close = true;
                    cmd.push(Cmd::SaveTruncateFinish);
                }
                if ui.button("Cancel").clicked() {
                    payload.close = true;
                }
            }));
            return Ok(());
        }
        let offset = self.src_args.hard_seek.unwrap_or(0);
        file.seek(SeekFrom::Start(offset as u64))?;
        let data_to_write = match self.data.dirty_region {
            Some(region) => {
                #[expect(
                    clippy::cast_possible_wrap,
                    reason = "Files bigger than i64::MAX aren't supported"
                )]
                file.seek(SeekFrom::Current(region.begin as _))?;
                // TODO: We're assuming here that end of the region is the same position as the last dirty byte
                // Make sure to enforce this invariant.
                // Add 1 to the end to write the dirty region even if it's 1 byte
                self.data.get(region.begin..region.end + 1)
            }
            None => Some(&self.data[..]),
        };
        let Some(data_to_write) = data_to_write else {
            anyhow::bail!("No data to write (possibly out of bounds indexing)");
        };
        file.write_all(data_to_write)?;
        self.data.undirty();
        if let Err(e) = self.save_temp_metafile_backup() {
            per!("Failed to save metafile backup: {}", e);
        }
        Ok(())
    }
    pub fn save_truncated_file_finish(&mut self) -> anyhow::Result<()> {
        let Some(source) = &mut self.source else {
            anyhow::bail!("There is no source");
        };
        let SourceProvider::File(file) = &mut source.provider else {
            anyhow::bail!("Source is not a file");
        };
        file.set_len(self.data.len() as u64)?;
        file.rewind()?;
        file.write_all(&self.data)?;
        self.data.undirty();
        Ok(())
    }
    pub(crate) fn source_file(&self) -> Option<&Path> {
        self.src_args.file.as_deref()
    }
    pub(crate) fn load_file(
        &mut self,
        path: PathBuf,
        read_only: bool,
        msg: &mut MessageDialog,
        font_size: u16,
        line_spacing: u16,
    ) {
        self.load_file_args(
            SourceArgs {
                file: Some(path),
                jump: None,
                hard_seek: None,
                take: None,
                read_only,
                stream: false,
                stream_buffer_size: None,
                unsafe_mmap: None,
                mmap_len: None,
            },
            None,
            msg,
            font_size,
            line_spacing,
            None,
        );
    }

    pub fn close_file(&mut self) {
        // We potentially had large data, free it instead of clearing the Vec
        self.data.close();
        self.src_args.file = None;
        self.source = None;
    }

    pub(crate) fn backup_path(&self) -> Option<PathBuf> {
        self.src_args.file.as_ref().map(|file| {
            let mut os_string = OsString::from(file);
            os_string.push(".hexerator_bak");
            os_string.into()
        })
    }

    pub(crate) fn restore_backup(&mut self) -> Result<(), anyhow::Error> {
        std::fs::copy(
            self.backup_path().context("Failed to get backup path")?,
            self.src_args.file.as_ref().context("No file open")?,
        )?;
        self.reload()
    }

    pub(crate) fn create_backup(&self) -> Result<(), anyhow::Error> {
        std::fs::copy(
            self.src_args.file.as_ref().context("No file open")?,
            self.backup_path().context("Failed to get backup path")?,
        )?;
        Ok(())
    }
    /// Reload only what's visible on the screen (current layout)
    fn reload_visible(&mut self) -> anyhow::Result<()> {
        let [lo, hi] = self.visible_byte_range();
        self.reload_range(lo, hi)
    }
    pub fn reload_range(&mut self, lo: usize, hi: usize) -> anyhow::Result<()> {
        let Some(src) = &self.source else {
            anyhow::bail!("No source")
        };
        anyhow::ensure!(lo <= hi);
        match &src.provider {
            SourceProvider::File(file) => {
                let mut file = file;
                let offset = match self.src_args.hard_seek {
                    Some(hs) => hs + lo,
                    None => lo,
                };
                file.seek(SeekFrom::Start(offset as u64))?;
                match self.data.get_mut(lo..=hi) {
                    Some(buf) => file.read_exact(buf)?,
                    None => anyhow::bail!("Reload range out of bounds"),
                }
                Ok(())
            }
            SourceProvider::Stdin(_) => anyhow::bail!("Not implemented"),
            #[cfg(windows)]
            SourceProvider::WinProc { .. } => anyhow::bail!("Not implemented"),
        }
    }
    #[allow(clippy::unnecessary_wraps, reason = "cfg shenanigans")]
    pub(crate) fn load_proc_memory(
        &mut self,
        pid: sysinfo::Pid,
        start: usize,
        size: usize,
        is_write: bool,
        msg: &mut MessageDialog,
        font_size: u16,
        line_spacing: u16,
    ) -> anyhow::Result<()> {
        #[cfg(target_os = "linux")]
        {
            load_proc_memory_linux(
                self,
                pid,
                start,
                size,
                is_write,
                msg,
                font_size,
                line_spacing,
            );
            Ok(())
        }
        #[cfg(windows)]
        return crate::windows::load_proc_memory(
            self,
            pid,
            start,
            size,
            is_write,
            font_size,
            line_spacing,
            msg,
        );
        #[cfg(target_os = "macos")]
        return load_proc_memory_macos(self, pid, start, size, is_write, font, msg);
    }
}

const DEFAULT_COLUMN_COUNT: usize = 48;

/// Metafile
impl App {
    /// Set a new clean meta for the current data, and switch to default layout
    pub fn set_new_clean_meta(
        &mut self,
        font_size: u16,
        line_spacing: u16,
        column_count: Option<usize>,
    ) {
        per!("Setting up new clean meta");
        self.meta_state.current_meta_path.clear();
        self.meta_state.meta = Meta::default();
        let layout_key = setup_empty_meta(
            self.data.len(),
            &mut self.meta_state.meta,
            font_size,
            line_spacing,
            column_count.unwrap_or(DEFAULT_COLUMN_COUNT),
        );
        self.meta_state.clean_meta = self.meta_state.meta.clone();
        Self::switch_layout(&mut self.hex_ui, &self.meta_state.meta, layout_key);
    }
    /// Like `set_new_clean_meta`, but keeps the clean meta intact
    ///
    /// Used for "Clear meta" action.
    pub fn clear_meta(&mut self, font_size: u16, line_spacing: u16) {
        self.meta_state.meta = Meta::default();
        let layout_key = setup_empty_meta(
            self.data.len(),
            &mut self.meta_state.meta,
            font_size,
            line_spacing,
            DEFAULT_COLUMN_COUNT,
        );
        Self::switch_layout(&mut self.hex_ui, &self.meta_state.meta, layout_key);
    }
    pub fn save_temp_metafile_backup(&mut self) -> anyhow::Result<()> {
        // We set the last_meta_backup first, so if save fails, we don't get
        // a never ending stream of constant save failures.
        self.meta_state.last_meta_backup.set(Instant::now());
        self.save_meta_to_file(temp_metafile_backup_path(), true)?;
        per!("Saved temp metafile backup");
        Ok(())
    }
    pub fn save_meta_to_file(&mut self, path: PathBuf, temp: bool) -> Result<(), anyhow::Error> {
        let data = rmp_serde::to_vec(&self.meta_state.meta)?;
        std::fs::write(&path, data)?;
        if !temp {
            self.meta_state.current_meta_path = path;
            self.meta_state.clean_meta = self.meta_state.meta.clone();
        }
        Ok(())
    }
    pub fn save_meta(&mut self) -> Result<(), anyhow::Error> {
        self.save_meta_to_file(self.meta_state.current_meta_path.clone(), false)
    }
    pub fn consume_meta_from_file(
        &mut self,
        path: PathBuf,
        temp: bool,
    ) -> Result<(), anyhow::Error> {
        per!("Consuming metafile: {}", path.display());
        let data = std::fs::read(&path)?;
        let meta = rmp_serde::from_slice(&data).context("Deserialization error")?;
        self.hex_ui.clear_meta_refs();
        self.meta_state.meta = meta;
        if !temp {
            self.meta_state.current_meta_path = path;
            self.meta_state.clean_meta = self.meta_state.meta.clone();
        }
        self.meta_state.meta.post_load_init();
        // Switch to first layout, if there is one
        if let Some(layout_key) = self.meta_state.meta.layouts.keys().next() {
            Self::switch_layout(&mut self.hex_ui, &self.meta_state.meta, layout_key);
        }
        Ok(())
    }

    pub fn add_perspective_from_region(
        &mut self,
        region_key: RegionKey,
        name: String,
    ) -> PerspectiveKey {
        let mut per = Perspective::from_region(region_key, name);
        if let Some(focused_per) = Self::focused_perspective(&self.hex_ui, &self.meta_state.meta) {
            per.cols = focused_per.cols;
        }
        self.meta_state.meta.low.perspectives.insert(per)
    }
}

/// Navigation
impl App {
    pub fn search_focus(&mut self, offset: usize) {
        self.edit_state.cursor = offset;
        self.center_view_on_offset(offset);
        self.hex_ui.flash_cursor();
    }
    pub(crate) fn center_view_on_offset(&mut self, offset: usize) {
        if let Some(key) = self.hex_ui.focused_view {
            self.meta_state.meta.views[key].view.center_on_offset(
                offset,
                &self.meta_state.meta.low.perspectives,
                &self.meta_state.meta.low.regions,
            );
        }
    }
    pub fn cursor_history_back(&mut self) {
        if self.edit_state.cursor_history_back() {
            self.center_view_on_offset(self.edit_state.cursor);
            self.hex_ui.flash_cursor();
        }
    }
    pub fn cursor_history_forward(&mut self) {
        if self.edit_state.cursor_history_forward() {
            self.center_view_on_offset(self.edit_state.cursor);
            self.hex_ui.flash_cursor();
        }
    }

    pub(crate) fn set_cursor_init(&mut self) {
        self.edit_state.cursor = self.src_args.jump.unwrap_or(0);
        self.center_view_on_offset(self.edit_state.cursor);
        self.hex_ui.flash_cursor();
    }
    pub(crate) fn switch_layout(app_hex_ui: &mut HexUi, app_meta: &Meta, k: LayoutKey) {
        app_hex_ui.current_layout = k;
        // Set focused view to the first available view in the layout
        if let Some(view_key) = app_meta.layouts[k].view_grid.first().and_then(|row| row.first()) {
            app_hex_ui.focused_view = Some(*view_key);
        }
    }
    /// Tries to switch to a layout with the given name. Returns `false` if a layout with that name wasn't found.
    #[must_use]
    pub(crate) fn switch_layout_by_name(
        app_hex_ui: &mut HexUi,
        app_meta: &Meta,
        name: &str,
    ) -> bool {
        match app_meta.layouts.iter().find(|(_k, v)| v.name == name) {
            Some((k, _v)) => {
                Self::switch_layout(app_hex_ui, app_meta, k);
                true
            }
            None => false,
        }
    }

    /// Tries to focus a view with the given name. Returns `false` if a view with that name wasn't found.
    #[must_use]
    pub(crate) fn focus_first_view_of_name(
        app_hex_ui: &mut HexUi,
        app_meta: &Meta,
        name: &str,
    ) -> bool {
        match app_meta.views.iter().find(|(_k, v)| v.name == name) {
            Some((k, _v)) => {
                Self::focus_first_view_of_key(app_hex_ui, app_meta, k);
                true
            }
            None => false,
        }
    }

    pub(crate) fn focus_prev_view_in_layout(&mut self) {
        if let Some(focused_view_key) = self.hex_ui.focused_view {
            let layout = &self.meta_state.meta.layouts[self.hex_ui.current_layout];
            if let Some(focused_idx) = layout.iter().position(|k| k == focused_view_key) {
                let new_idx = if focused_idx == 0 {
                    layout.iter().count() - 1
                } else {
                    focused_idx - 1
                };
                if let Some(new_key) = layout.iter().nth(new_idx) {
                    self.hex_ui.focused_view = Some(new_key);
                }
            }
        }
    }

    pub(crate) fn focus_next_view_in_layout(&mut self) {
        if let Some(focused_view_key) = self.hex_ui.focused_view {
            let layout = &self.meta_state.meta.layouts[self.hex_ui.current_layout];
            if let Some(focused_idx) = layout.iter().position(|k| k == focused_view_key) {
                let new_idx = if focused_idx == layout.iter().count() - 1 {
                    0
                } else {
                    focused_idx + 1
                };
                if let Some(new_key) = layout.iter().nth(new_idx) {
                    self.hex_ui.focused_view = Some(new_key);
                }
            }
        }
    }

    pub(crate) fn focus_first_view_of_key(
        app_hex_ui: &mut HexUi,
        app_meta: &Meta,
        view_key: ViewKey,
    ) {
        if let Some(layout_key) = app_meta
            .layouts
            .iter()
            .find_map(|(k, l)| l.contains_view(view_key).then_some(k))
        {
            Self::switch_layout(app_hex_ui, app_meta, layout_key);
            app_hex_ui.focused_view = Some(view_key);
        }
    }
}

/// Perspective manipulation
impl App {
    pub(crate) fn inc_cols(&mut self) {
        self.col_change_impl(|col| *col += 1);
    }
    pub(crate) fn dec_cols(&mut self) {
        self.col_change_impl(|col| *col -= 1);
    }
    pub(crate) fn halve_cols(&mut self) {
        self.col_change_impl(|col| *col /= 2);
    }
    pub(crate) fn double_cols(&mut self) {
        self.col_change_impl(|col| *col *= 2);
    }
    fn col_change_impl(&mut self, f: impl FnOnce(&mut usize)) {
        if let Some(key) = self.hex_ui.focused_view {
            let view = &mut self.meta_state.meta.views[key].view;
            col_change_impl_view_perspective(
                view,
                &mut self.meta_state.meta.low.perspectives,
                &self.meta_state.meta.low.regions,
                f,
                self.preferences.col_change_lock_col,
                self.preferences.col_change_lock_row,
            );
        }
    }
}

/// Finding things
impl App {
    // Byte offset of a pixel position in the viewport
    //
    // Also returns the index of the view the position is from
    pub fn byte_offset_at_pos(&self, x: i16, y: i16) -> Option<(usize, ViewKey)> {
        let layout = self.meta_state.meta.layouts.get(self.hex_ui.current_layout)?;
        for view_key in layout.iter() {
            if let Some(pos) = self.view_byte_offset_at_pos(view_key, x, y) {
                return Some((pos, view_key));
            }
        }
        None
    }
    pub fn view_byte_offset_at_pos(&self, view_key: ViewKey, x: i16, y: i16) -> Option<usize> {
        let NamedView { view, .. } = self.meta_state.meta.views.get(view_key)?;
        view.row_col_offset_of_pos(
            x,
            y,
            &self.meta_state.meta.low.perspectives,
            &self.meta_state.meta.low.regions,
        )
        .map(|[row, col]| {
            self.meta_state.meta.low.perspectives[view.perspective].byte_offset_of_row_col(
                row,
                col,
                &self.meta_state.meta.low.regions,
            )
        })
    }
    pub fn view_at_pos(&self, x: ViewportScalar, y: ViewportScalar) -> Option<ViewKey> {
        let layout = &self.meta_state.meta.layouts[self.hex_ui.current_layout];
        for row in &layout.view_grid {
            for key in row {
                let view = &self.meta_state.meta.views[*key];
                if view.view.viewport_rect.contains_pos(x, y) {
                    return Some(*key);
                }
            }
        }
        None
    }
    pub fn view_idx_at_pos(&self, x: i16, y: i16) -> Option<ViewKey> {
        let layout = &self.meta_state.meta.layouts[self.hex_ui.current_layout];
        for view_key in layout.iter() {
            let view = &self.meta_state.meta.views[view_key];
            if view.view.viewport_rect.contains_pos(x, y) {
                return Some(view_key);
            }
        }
        None
    }
    /// Iterator over the views in the current layout
    fn active_views(&self) -> impl Iterator<Item = &'_ NamedView> {
        let layout = &self.meta_state.meta.layouts[self.hex_ui.current_layout];
        layout.iter().map(|key| &self.meta_state.meta.views[key])
    }
    /// Largest visible byte range in the current perspective
    fn visible_byte_range(&self) -> [usize; 2] {
        let mut min_lo = self.data.len();
        let mut max_hi = 0;
        for view in self.active_views() {
            let offsets = view.view.offsets(
                &self.meta_state.meta.low.perspectives,
                &self.meta_state.meta.low.regions,
            );
            let lo = offsets.byte;
            min_lo = std::cmp::min(min_lo, lo);
            let hi = lo + view.view.bytes_per_page(&self.meta_state.meta.low.perspectives);
            max_hi = std::cmp::max(max_hi, hi);
        }
        [min_lo, max_hi].map(|v| v.clamp(0, self.data.len()))
    }
    pub(crate) fn focused_view_mut(&mut self) -> Option<(ViewKey, &mut View)> {
        self.hex_ui.focused_view.and_then(|key| {
            self.meta_state.meta.views.get_mut(key).map(|view| (key, &mut view.view))
        })
    }
    pub(crate) fn row_region(&self, row: usize) -> Option<Region> {
        let per = Self::focused_perspective(&self.hex_ui, &self.meta_state.meta)?;
        let per_reg = self.meta_state.meta.low.regions.get(per.region)?.region;
        // Beginning of the region
        let beg = per_reg.begin;
        // Number of columns
        let cols = per.cols;
        let row_begin = beg + row * cols;
        // Regions are inclusive, so we subtract 1
        let row_end = (row_begin + cols).saturating_sub(1);
        Some(Region {
            begin: row_begin,
            end: row_end,
        })
    }

    pub(crate) fn col_offsets(&self, col: usize) -> Option<Vec<usize>> {
        let per = Self::focused_perspective(&self.hex_ui, &self.meta_state.meta)?;
        let per_reg = self.meta_state.meta.low.regions.get(per.region)?.region;
        let beg = per_reg.begin;
        let end = per_reg.end;
        let cols = per.cols;
        let offsets = (beg..=end).step_by(cols).map(|off| off + col).collect();
        Some(offsets)
    }

    pub(crate) fn cursor_col_offsets(&self) -> Option<Vec<usize>> {
        self.row_col_of_cursor().and_then(|[_, col]| self.col_offsets(col))
    }
    /// Returns the row and column of the provided byte position, according to focused perspective
    pub(crate) fn row_col_of_byte_pos(&self, pos: usize) -> Option<[usize; 2]> {
        Self::focused_perspective(&self.hex_ui, &self.meta_state.meta)
            .map(|per| calc_perspective_row_col(pos, per, &self.meta_state.meta.low.regions))
    }
    /// Returns the byte position of the provided row and column, according to focused perspective
    pub(crate) fn byte_pos_of_row_col(&self, row: usize, col: usize) -> Option<usize> {
        Self::focused_perspective(&self.hex_ui, &self.meta_state.meta).map(|per| {
            calc_perspective_row_col_offset(row, col, per, &self.meta_state.meta.low.regions)
        })
    }
    /// Returns the row and column of the current cursor, according to focused perspective
    pub(crate) fn row_col_of_cursor(&self) -> Option<[usize; 2]> {
        self.row_col_of_byte_pos(self.edit_state.cursor)
    }
    pub fn focused_perspective<'a>(hex_ui: &HexUi, meta: &'a Meta) -> Option<&'a Perspective> {
        hex_ui.focused_view.map(|view_key| {
            let per_key = meta.views[view_key].view.perspective;
            &meta.low.perspectives[per_key]
        })
    }
    pub fn focused_region<'a>(hex_ui: &HexUi, meta: &'a Meta) -> Option<&'a NamedRegion> {
        Self::focused_perspective(hex_ui, meta).and_then(|per| meta.low.regions.get(per.region))
    }

    pub(crate) fn region_key_for_view(&self, view_key: ViewKey) -> RegionKey {
        let per_key = self.meta_state.meta.views[view_key].view.perspective;
        self.meta_state.meta.low.perspectives[per_key].region
    }
    /// Figure out the byte offset of the row `offset` is on
    pub(crate) fn find_row_start(&self, offset: usize) -> Option<usize> {
        match self.row_col_of_byte_pos(offset) {
            Some([row, _col]) => self.byte_pos_of_row_col(row, 0),
            None => None,
        }
    }
    /// Figure out the byte offset of the row `offset` is on + end
    pub(crate) fn find_row_end(&self, offset: usize) -> Option<usize> {
        Self::focused_perspective(&self.hex_ui, &self.meta_state.meta).map(|per| {
            let [row, _col] =
                calc_perspective_row_col(offset, per, &self.meta_state.meta.low.regions);
            calc_perspective_row_col_offset(
                row,
                per.cols.saturating_sub(1),
                per,
                &self.meta_state.meta.low.regions,
            )
        })
    }
}

fn calc_perspective_row_col(pos: usize, per: &Perspective, regions: &RegionMap) -> [usize; 2] {
    let cols = per.cols;
    let region_begin = regions[per.region].region.begin;
    let byte_pos = pos.saturating_sub(region_begin);
    [byte_pos / cols, byte_pos % cols]
}

fn calc_perspective_row_col_offset(
    row: usize,
    col: usize,
    per: &Perspective,
    regions: &RegionMap,
) -> usize {
    let region_begin = regions[per.region].region.begin;
    row * per.cols + col + region_begin
}

/// Editing
impl App {
    pub(crate) fn mod_byte_at_cursor(&mut self, f: impl FnOnce(&mut u8)) {
        if let Some(byte) = self.data.get_mut(self.edit_state.cursor) {
            f(byte);
            self.data.widen_dirty_region(DamageRegion::Single(self.edit_state.cursor));
        }
    }

    pub(crate) fn inc_byte_at_cursor(&mut self) {
        self.mod_byte_at_cursor(|b| *b = b.wrapping_add(1));
    }

    pub(crate) fn dec_byte_at_cursor(&mut self) {
        self.mod_byte_at_cursor(|b| *b = b.wrapping_sub(1));
    }

    pub(crate) fn inc_byte_or_bytes(&mut self) {
        let mut any = false;
        for region in self.hex_ui.selected_regions() {
            self.data.mod_range(region.to_range(), |byte| {
                *byte = byte.wrapping_add(1);
            });
            any = true;
        }
        if !any {
            self.inc_byte_at_cursor();
        }
    }

    pub(crate) fn dec_byte_or_bytes(&mut self) {
        let mut any = false;
        for region in self.hex_ui.selected_regions() {
            self.data.mod_range(region.to_range(), |byte| {
                *byte = byte.wrapping_sub(1);
            });
            any = true;
        }
        if !any {
            self.dec_byte_at_cursor();
        }
    }
}

/// Etc.
impl App {
    pub(crate) fn new(
        mut args: Args,
        cfg: Config,
        font_size: u16,
        line_spacing: u16,
        gui: &mut Gui,
    ) -> anyhow::Result<Self> {
        if args.recent
            && let Some(recent) = cfg.recent.most_recent()
        {
            args.src = recent.clone();
        }
        let mut this = Self {
            data: Data::default(),
            edit_state: EditState::default(),
            input: Input::default(),
            src_args: SourceArgs::default(),
            source: None,
            stream_read_recv: None,
            cfg,
            last_reload: Instant::now(),
            preferences: SessionPrefs::default(),
            hex_ui: HexUi::default(),
            meta_state: MetaState::default(),
            clipboard: arboard::Clipboard::new()?,
            cmd: Default::default(),
            backend_cmd: Default::default(),
            quit_requested: false,
            plugins: Vec::new(),
            stream_buffer_size: args.src.stream_buffer_size.unwrap_or(DEFAULT_STREAM_BUFFER_SIZE),
        };
        for path in args.load_plugin {
            // Safety: This will cause UB on a bad plugin. Nothing we can do.
            //
            // It's up to the user not to load bad plugins.
            this.plugins.push(unsafe { PluginContainer::new(path)? });
        }
        if args.autosave {
            this.preferences.auto_save = true;
        }
        if let Some(interval_ms) = args.autoreload {
            if args.autoreload_only_visible {
                this.preferences.auto_reload = Autoreload::Visible;
            } else {
                this.preferences.auto_reload = Autoreload::All;
            }
            this.preferences.auto_reload_interval_ms = interval_ms;
        }
        match args.new {
            Some(new_len) => {
                if let Some(path) = args.src.file {
                    if path.exists() {
                        anyhow::bail!("Can't use --new for {path:?}: File already exists");
                    }
                    // Set up source for this new file
                    let f = OpenOptions::new()
                        .create(true)
                        .truncate(false)
                        .read(true)
                        .write(true)
                        .open(&path)?;
                    f.set_len(new_len as u64)?;
                    this.source = Some(Source::file(f));
                    this.src_args.file = Some(path);
                }
                this.data = Data::clean_from_buf(vec![0; new_len]);
                // Set clean meta for the newly allocated buffer
                this.set_new_clean_meta(font_size, line_spacing, args.column_count);
            }
            None => {
                // Set a clean meta, for an empty document
                this.set_new_clean_meta(font_size, line_spacing, args.column_count);
                this.load_file_args(
                    args.src,
                    args.meta,
                    &mut gui.msg_dialog,
                    font_size,
                    line_spacing,
                    args.column_count,
                );
            }
        }
        if let Some(name) = args.layout
            && !Self::switch_layout_by_name(&mut this.hex_ui, &this.meta_state.meta, &name)
        {
            let err = anyhow::anyhow!("No layout with name '{name}' found.");
            msg_fail(&err, "Couldn't switch layout", &mut gui.msg_dialog);
        }
        if let Some(name) = args.view
            && !Self::focus_first_view_of_name(&mut this.hex_ui, &this.meta_state.meta, &name)
        {
            let err = anyhow::anyhow!("No view with name '{name}' found.");
            msg_fail(&err, "Couldn't focus view", &mut gui.msg_dialog);
        }
        // Set cursor to the beginning of the focused region we ended up with
        if let Some(reg) = Self::focused_region(&this.hex_ui, &this.meta_state.meta) {
            this.edit_state.cursor = reg.region.begin;
        }
        // Diff against a file if requested
        if let Some(path) = &args.diff_against {
            let result = this.diff_with_file(path.clone(), &mut gui.win.file_diff_result);
            msg_if_fail(result, "Failed to diff", &mut gui.msg_dialog);
        }
        Ok(this)
    }
    /// Reoffset all bookmarks based on the difference between the cursor and `offset`
    pub(crate) fn reoffset_bookmarks_cursor_diff(&mut self, offset: usize) {
        #[expect(
            clippy::cast_possible_wrap,
            reason = "We assume that the offset is not greater than isize"
        )]
        let difference = self.edit_state.cursor as isize - offset as isize;
        for bm in &mut self.meta_state.meta.bookmarks {
            bm.offset = bm.offset.saturating_add_signed(difference);
        }
    }

    pub(crate) fn try_read_stream(&mut self) {
        let Some(src) = &mut self.source else { return };
        if !src.attr.stream {
            return;
        };
        let Some(view_key) = self.hex_ui.focused_view else {
            return;
        };
        let view = &self.meta_state.meta.views[view_key].view;
        let view_byte_offset = view
            .offsets(
                &self.meta_state.meta.low.perspectives,
                &self.meta_state.meta.low.regions,
            )
            .byte;
        let bytes_per_page = view.bytes_per_page(&self.meta_state.meta.low.perspectives);
        // Don't read past what we need for our current view offset
        if view_byte_offset + bytes_per_page < self.data.len() {
            return;
        }
        if src.state.stream_end {
            return;
        }
        match &self.stream_read_recv {
            Some(recv) => match recv.try_recv() {
                Ok(buf) => {
                    if buf.is_empty() {
                        src.state.stream_end = true;
                    } else {
                        self.data.extend_from_slice(&buf[..]);
                        let perspective = &self.meta_state.meta.low.perspectives[view.perspective];
                        let region =
                            &mut self.meta_state.meta.low.regions[perspective.region].region;
                        region.end = self.data.len().saturating_sub(1);
                    }
                }
                Err(e) => match e {
                    std::sync::mpsc::TryRecvError::Empty => {}
                    std::sync::mpsc::TryRecvError::Disconnected => self.stream_read_recv = None,
                },
            },
            None => {
                let (tx, rx) = std::sync::mpsc::channel();
                let mut src_clone = src.provider.clone();
                self.stream_read_recv = Some(rx);
                let buffer_size = self.stream_buffer_size;
                thread::spawn(move || {
                    let mut buf = vec![0; buffer_size];
                    let result = try {
                        let amount = src_clone.read(&mut buf).how()?;
                        buf.truncate(amount);
                        tx.send(buf).how()?;
                    };
                    if let Err(e) = result {
                        per!("Stream error: {}", e);
                    }
                });
            }
        }
    }

    /// Called every frame
    pub(crate) fn update(
        &mut self,
        gui: &mut Gui,
        rw: &mut RenderWindow,
        lua: &Lua,
        font_size: u16,
        line_spacing: u16,
    ) {
        if !self.hex_ui.current_layout.is_null() {
            let layout = &self.meta_state.meta.layouts[self.hex_ui.current_layout];
            do_auto_layout(
                layout,
                &mut self.meta_state.meta.views,
                &self.hex_ui.hex_iface_rect,
                &self.meta_state.meta.low.perspectives,
                &self.meta_state.meta.low.regions,
            );
        }
        if self.preferences.auto_save
            && self.data.dirty_region.is_some()
            && let Err(e) = self.save(&mut gui.msg_dialog)
        {
            per!("Save fail: {}", e);
        }
        if self.preferences.auto_reload.is_active()
            && self.source.is_some()
            && self.last_reload.elapsed().as_millis()
                >= u128::from(self.preferences.auto_reload_interval_ms)
        {
            match &self.preferences.auto_reload {
                Autoreload::Disabled => {}
                Autoreload::All => {
                    if msg_if_fail(self.reload(), "Auto-reload fail", &mut gui.msg_dialog).is_some()
                    {
                        self.preferences.auto_reload = Autoreload::Disabled;
                    }
                }
                Autoreload::Visible => {
                    if msg_if_fail(
                        self.reload_visible(),
                        "Auto-reload fail",
                        &mut gui.msg_dialog,
                    )
                    .is_some()
                    {
                        self.preferences.auto_reload = Autoreload::Disabled;
                    }
                }
            }
            self.last_reload = Instant::now();
        }
        // Here we perform all queued up `Command`s.
        self.flush_command_queue(gui, lua, font_size, line_spacing);
        self.flush_backend_command_queue(rw);
    }

    pub(crate) fn focused_view_select_all(&mut self) {
        if let Some(view) = self.hex_ui.focused_view {
            let p_key = self.meta_state.meta.views[view].view.perspective;
            let p = &self.meta_state.meta.low.perspectives[p_key];
            let r = &self.meta_state.meta.low.regions[p.region];
            self.hex_ui.select_a = Some(r.region.begin);
            // Don't select more than the data length, even if region is bigger
            self.hex_ui.select_b = Some(r.region.end.min(self.data.len().saturating_sub(1)));
        }
    }

    pub(crate) fn focused_view_select_row(&mut self) {
        if let Some([row, _]) = self.row_col_of_cursor()
            && let Some(reg) = self.row_region(row)
        {
            // To make behavior consistent with "select col", we clear all extra selections beforehand
            self.hex_ui.extra_selections.clear();
            self.hex_ui.select_a = Some(reg.begin);
            self.hex_ui.select_b = Some(reg.end);
        }
    }

    pub(crate) fn focused_view_select_col(&mut self) {
        let Some(offsets) = self.cursor_col_offsets() else {
            return;
        };
        self.hex_ui.extra_selections.clear();
        let mut offsets = offsets.into_iter();
        if let Some(off) = offsets.next() {
            self.hex_ui.select_a = Some(off);
            self.hex_ui.select_b = Some(off);
        }
        for col in offsets {
            self.hex_ui.extra_selections.push(Region {
                begin: col,
                end: col,
            });
        }
    }

    pub(crate) fn diff_with_file(
        &self,
        path: PathBuf,
        file_diff_result_window: &mut FileDiffResultWindow,
    ) -> anyhow::Result<()> {
        // FIXME: Skipping ignores changes to bookmarked values that happen later than the first
        // byte.
        let file_data = read_source_to_buf(&path, &self.src_args)?;
        let mut offs = Vec::new();
        let mut skip = 0;
        for ((offset, &my_byte), &file_byte) in self.data.iter().enumerate().zip(file_data.iter()) {
            if skip > 0 {
                skip -= 1;
                continue;
            }
            if my_byte != file_byte {
                offs.push(offset);
            }
            if let Some((_, bm)) =
                Meta::bookmark_for_offset(&self.meta_state.meta.bookmarks, offset)
            {
                skip = bm.value_type.byte_len() - 1;
            }
        }
        file_diff_result_window.offsets = offs;
        file_diff_result_window.file_data = file_data;
        file_diff_result_window.path = path;
        file_diff_result_window.open.set(true);
        Ok(())
    }

    pub(crate) fn call_plugin_method(
        &mut self,
        plugin_name: &str,
        method_name: &str,
        args: &[Option<hexerator_plugin_api::Value>],
    ) -> MethodResult {
        let mut plugins = std::mem::take(&mut self.plugins);
        let result = 'block: {
            for plugin in &mut plugins {
                if plugin_name == plugin.plugin.name() {
                    break 'block plugin.plugin.on_method_called(method_name, args, self);
                }
            }
            Err(format!("Plugin `{plugin_name}` not found."))
        };
        std::mem::swap(&mut self.plugins, &mut plugins);
        result
    }

    pub(crate) fn remove_dangling(&mut self) {
        self.meta_state.meta.remove_dangling();
        if self
            .hex_ui
            .focused_view
            .is_some_and(|key| !self.meta_state.meta.views.contains_key(key))
        {
            eprintln!("Unset dangling focused view");
            self.hex_ui.focused_view = None;
        }
    }
}

/// Set up an empty meta with the defaults
pub fn setup_empty_meta(
    data_len: usize,
    meta: &mut Meta,
    font_size: u16,
    line_spacing: u16,
    cols: usize,
) -> LayoutKey {
    let def_region = meta.low.regions.insert(NamedRegion {
        name: "default".into(),
        region: Region {
            begin: 0,
            end: data_len.saturating_sub(1),
        },
        desc: String::new(),
    });
    let default_perspective = meta.low.perspectives.insert(Perspective {
        region: def_region,
        cols,
        flip_row_order: false,
        name: "default".to_string(),
    });
    let mut layout = Layout {
        name: "Default layout".into(),
        view_grid: vec![vec![]],
        margin: default_margin(),
    };
    for view in default_views(default_perspective, font_size, line_spacing) {
        let k = meta.views.insert(view);
        layout.view_grid[0].push(k);
    }
    meta.layouts.insert(layout)
}

pub fn get_clipboard_string(cb: &mut arboard::Clipboard, msg: &mut MessageDialog) -> String {
    match cb.get_text() {
        Ok(text) => text,
        Err(e) => {
            msg.open(
                Icon::Error,
                "Failed to get text from clipboard",
                e.to_string(),
            );
            String::new()
        }
    }
}

pub fn set_clipboard_string(cb: &mut arboard::Clipboard, msg: &mut MessageDialog, text: &str) {
    msg_if_fail(cb.set_text(text), "Failed to set clipboard text", msg);
}

#[cfg(target_os = "linux")]
fn load_proc_memory_linux(
    app: &mut App,
    pid: sysinfo::Pid,
    start: usize,
    size: usize,
    is_write: bool,
    msg: &mut MessageDialog,
    font_size: u16,
    line_spacing: u16,
) {
    app.load_file_args(
        SourceArgs {
            file: Some(Path::new("/proc/").join(pid.to_string()).join("mem")),
            jump: None,
            hard_seek: Some(start),
            take: Some(size),
            read_only: !is_write,
            stream: false,
            stream_buffer_size: None,
            unsafe_mmap: None,
            mmap_len: None,
        },
        None,
        msg,
        font_size,
        line_spacing,
        None,
    );
}

#[cfg(target_os = "macos")]
fn load_proc_memory_macos(
    app: &mut App,
    pid: sysinfo::Pid,
    start: usize,
    size: usize,
    is_write: bool,
    font: &Font,
    msg: &mut MessageDialog,
    events: &EventQueue,
) -> anyhow::Result<()> {
    app.load_file_args(
        Args {
            src: SourceArgs {
                file: Some(Path::new("/proc/").join(pid.to_string()).join("mem")),
                jump: None,
                hard_seek: Some(start),
                take: Some(size),
                read_only: !is_write,
                stream: false,
            },
            recent: false,
            meta: None,
        },
        font,
        msg,
        events,
    )
}

pub fn read_source_to_buf(path: &Path, args: &SourceArgs) -> Result<Vec<u8>, anyhow::Error> {
    let mut f = File::open(path)?;
    if let &Some(to) = &args.hard_seek {
        #[expect(
            clippy::cast_possible_wrap,
            reason = "Files bigger than i64::MAX aren't supported"
        )]
        f.seek(SeekFrom::Current(to as i64))?;
    }
    #[expect(
        clippy::cast_possible_truncation,
        reason = "On 32 bit, max supported file size is 4 GB"
    )]
    let len = args.take.unwrap_or(f.metadata()?.len() as usize);
    let mut buf = vec![0; len];
    f.read_exact(&mut buf)?;
    Ok(buf)
}

pub fn temp_metafile_backup_path() -> PathBuf {
    std::env::temp_dir().join("hexerator_meta_backup.meta")
}

pub fn col_change_impl_view_perspective(
    view: &mut View,
    perspectives: &mut PerspectiveMap,
    regions: &RegionMap,
    f: impl FnOnce(&mut usize),
    lock_x: bool,
    lock_y: bool,
) {
    let prev_offset = view.offsets(perspectives, regions);
    f(&mut perspectives[view.perspective].cols);
    perspectives[view.perspective].clamp_cols(regions);
    view.scroll_to_byte_offset(prev_offset.byte, perspectives, regions, lock_x, lock_y);
}

pub fn default_views(
    perspective: PerspectiveKey,
    font_size: u16,
    line_spacing: u16,
) -> Vec<NamedView> {
    vec![
        NamedView {
            view: View::new(
                ViewKind::Hex(HexData::with_font_size(font_size)),
                perspective,
            ),
            name: "Default hex".into(),
        },
        NamedView {
            view: View::new(
                ViewKind::Text(TextData::with_font_info(line_spacing, font_size)),
                perspective,
            ),
            name: "Default text".into(),
        },
        NamedView {
            view: View::new(ViewKind::Block, perspective),
            name: "Default block".into(),
        },
    ]
}

/// Returns if the file was actually loaded.
fn load_file_from_src_args(
    src_args: &mut SourceArgs,
    cfg: &mut Config,
    source: &mut Option<Source>,
    data: &mut Data,
    msg: &mut MessageDialog,
    cmd: &mut CommandQueue,
) -> bool {
    if let Some(file_arg) = &src_args.file {
        if file_arg.as_os_str() == "-" {
            *source = Some(Source {
                provider: SourceProvider::Stdin(std::io::stdin()),
                attr: SourceAttributes {
                    stream: true,
                    permissions: SourcePermissions { write: false },
                },
                state: SourceState::default(),
            });
            cmd.push(Cmd::ProcessSourceChange);
            true
        } else {
            let result = try {
                let mut file = open_file(file_arg, src_args.read_only)?;
                if let Some(path) = &mut src_args.file {
                    match path.canonicalize() {
                        Ok(canon) => *path = canon,
                        Err(e) => msg.open(
                            Icon::Warn,
                            "Warning",
                            format!(
                                "Failed to canonicalize path {}: {}\n\
                             Recent use list might not be able to load it back.",
                                path.display(),
                                e
                            ),
                        ),
                    }
                }
                cfg.recent.use_(src_args.clone());
                if !src_args.stream {
                    if let Some(mmap_mode) = src_args.unsafe_mmap {
                        let mut opts = memmap2::MmapOptions::new();
                        if let Some(len) = src_args.mmap_len {
                            opts.len(len);
                        }
                        // Safety:
                        //
                        // Memory mapped file access cannot be made 100% safe, not much we can do here.
                        //
                        // The command line option is called `--unsafe-mmap` to reflect this.
                        *data = unsafe {
                            match mmap_mode {
                                crate::args::MmapMode::Cow => {
                                    Data::new_mmap_mut(opts.map_copy(&file)?)
                                }
                                crate::args::MmapMode::DangerousMut => {
                                    Data::new_mmap_mut(opts.map_mut(&file)?)
                                }
                                crate::args::MmapMode::Ro => Data::new_mmap_immut(opts.map(&file)?),
                            }
                        };
                    } else {
                        *data = Data::clean_from_buf(read_contents(&*src_args, &mut file)?);
                    }
                }
                *source = Some(Source {
                    provider: SourceProvider::File(file),
                    attr: SourceAttributes {
                        stream: src_args.stream,
                        permissions: SourcePermissions {
                            write: !src_args.read_only,
                        },
                    },
                    state: SourceState::default(),
                });
                cmd.push(Cmd::ProcessSourceChange);
            };
            match result {
                Ok(()) => true,
                Err(e) => {
                    if !src_args.read_only && e.kind() == std::io::ErrorKind::PermissionDenied {
                        eprintln!("Failed to open file: {e}. Retrying read-only.");
                        src_args.read_only = true;
                        return load_file_from_src_args(src_args, cfg, source, data, msg, cmd);
                    }
                    msg_fail(&e, "Failed to open file", msg);
                    false
                }
            }
        }
    } else {
        false
    }
}

fn open_file(path: &Path, read_only: bool) -> std::io::Result<File> {
    OpenOptions::new().read(true).write(!read_only).open(path)
}

pub(crate) fn read_contents(args: &SourceArgs, file: &mut File) -> std::io::Result<Vec<u8>> {
    let seek = args.hard_seek.unwrap_or(0);
    file.seek(SeekFrom::Start(seek as u64))?;
    let mut data = Vec::new();
    match args.take {
        Some(amount) => (&*file).take(amount as u64).read_to_end(&mut data)?,
        None => file.read_to_end(&mut data)?,
    };
    Ok(data)
}


================================================
FILE: src/args.rs
================================================
use {
    crate::parse_radix::parse_guess_radix,
    clap::Parser,
    serde::{Deserialize, Serialize},
    std::path::PathBuf,
};

/// Hexerator: Versatile hex editor
#[derive(Parser, Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)]
pub struct Args {
    /// Arguments relating to the source to open
    #[clap(flatten)]
    pub src: SourceArgs,
    /// Open most recently used file
    #[arg(long)]
    pub recent: bool,
    /// Load this metafile
    #[arg(long, value_name = "path")]
    pub meta: Option<PathBuf>,
    /// Show version information and exit
    #[arg(long)]
    pub version: bool,
    /// Start with debug logging enabled
    #[arg(long)]
    pub debug: bool,
    /// Spawn and open memory of a command with arguments (must be last option)
    #[arg(long, value_name="command", allow_hyphen_values=true, num_args=1..)]
    pub spawn_command: Vec<String>,
    #[arg(long, value_name = "name")]
    /// When spawning a command, open the process list with this filter, rather than selecting a pid
    pub look_for_proc: Option<String>,
    /// Automatically reload the source for the current buffer in millisecond intervals (default:250)
    #[arg(long, value_name="interval", default_missing_value="250", num_args=0..=1)]
    pub autoreload: Option<u32>,
    /// Only autoreload the data visible in the current layout
    #[arg(long)]
    pub autoreload_only_visible: bool,
    /// Automatically save if there is an edited region in the file
    #[arg(long)]
    pub autosave: bool,
    /// Open this layout on startup instead of the default
    #[arg(long, value_name = "name")]
    pub layout: Option<String>,
    /// Focus the first instance of this view on startup
    #[arg(long, value_name = "name")]
    pub view: Option<String>,
    #[arg(long)]
    /// Load a dynamic library plugin at startup
    pub load_plugin: Vec<PathBuf>,
    /// Allocate a new (zero-filled) buffer. Also creates the provided file argument if it doesn't exist.
    #[arg(long, value_name = "length")]
    pub new: Option<usize>,
    /// Diff against this file
    #[arg(long, value_name = "path", alias = "diff-with")]
    pub diff_against: Option<PathBuf>,
    /// Set the initial column count of the default perspective
    #[arg(short = 'c', long = "col")]
    pub column_count: Option<usize>,
}

/// Arguments for opening a source (file/stream/process/etc)
#[derive(Parser, Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)]
pub struct SourceArgs {
    /// The file to read
    pub file: Option<PathBuf>,
    /// Jump to offset on startup
    #[arg(short = 'j', long="jump", value_name="offset", value_parser = parse_guess_radix::<usize>)]
    pub jump: Option<usize>,
    /// Seek to offset, consider it beginning of the file in the editor
    #[arg(long, value_name="offset", value_parser = parse_guess_radix::<usize>)]
    pub hard_seek: Option<usize>,
    /// Read only this many bytes
    #[arg(long, value_name = "bytes", value_parser = parse_guess_radix::<usize>)]
    pub take: Option<usize>,
    /// Open file as read-only, without writing privileges
    #[arg(long)]
    pub read_only: bool,
    /// Specify source as a streaming source (for example, standard streams).
    /// Sets read-only attribute.
    #[arg(long)]
    pub stream: bool,
    /// The buffer size in bytes to use for reading when streaming
    #[arg(long)]
    #[serde(default)]
    pub stream_buffer_size: Option<usize>,
    /// Try to open the source using mmap rather than load into a buffer
    #[serde(default)]
    #[arg(long, value_name = "mode")]
    pub unsafe_mmap: Option<MmapMode>,
    /// Assume the memory mapped file is of this length (might be needed for looking at block devices, etc.)
    #[serde(default)]
    #[arg(long, value_name = "len")]
    pub mmap_len: Option<usize>,
}

/// How the memory mapping should operate
#[derive(
    Clone,
    Copy,
    clap::ValueEnum,
    Debug,
    Serialize,
    Deserialize,
    PartialEq,
    Eq,
    strum::IntoStaticStr,
    strum::EnumIter,
    Default,
)]
pub enum MmapMode {
    /// Read-only memory map.
    /// Note: Some features may not work, as Hexerator was designed for a mutable data buffer.
    #[default]
    Ro,
    /// Copy-on-write memory map.
    /// Changes are only visible locally.
    Cow,
    /// Mutable memory map.
    /// *WARNING*: Any edits will immediately take effect. THEY CANNOT BE UNDONE.
    DangerousMut,
}


================================================
FILE: src/backend/sfml.rs
================================================
use {
    crate::{
        color::{RgbColor, RgbaColor},
        view::{ViewportScalar, ViewportVec},
    },
    egui_sf2g::sf2g::graphics::Color,
};

impl From<Color> for RgbaColor {
    fn from(Color { r, g, b, a }: Color) -> Self {
        Self { r, g, b, a }
    }
}

impl From<RgbaColor> for Color {
    fn from(RgbaColor { r, g, b, a }: RgbaColor) -> Self {
        Self { r, g, b, a }
    }
}

impl From<RgbColor> for Color {
    fn from(src: RgbColor) -> Self {
        Self {
            r: src.r,
            g: src.g,
            b: src.b,
            a: 255,
        }
    }
}

impl TryFrom<sf2g::system::Vector2<i32>> for ViewportVec {
    type Error = <ViewportScalar as TryFrom<i32>>::Error;

    fn try_from(sf_vec: sf2g::system::Vector2<i32>) -> Result<Self, Self::Error> {
        Ok(Self {
            x: sf_vec.x.try_into()?,
            y: sf_vec.y.try_into()?,
        })
    }
}


================================================
FILE: src/backend.rs
================================================
#[cfg(feature = "backend-sfml")]
mod sfml;


================================================
FILE: src/color.rs
================================================
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
pub struct RgbaColor {
    pub r: u8,
    pub g: u8,
    pub b: u8,
    pub a: u8,
}
impl RgbaColor {
    pub(crate) fn with_as_egui_mut(&mut self, f: impl FnOnce(&mut egui::Color32)) {
        let mut ec = self.to_egui();
        f(&mut ec);
        *self = Self::from_egui(ec);
    }
    fn from_egui(c: egui::Color32) -> Self {
        Self {
            r: c.r(),
            g: c.g(),
            b: c.b(),
            a: c.a(),
        }
    }
    fn to_egui(self) -> egui::Color32 {
        egui::Color32::from_rgba_premultiplied(self.r, self.g, self.b, self.a)
    }
}

pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> RgbaColor {
    RgbaColor { r, g, b, a }
}

#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
pub struct RgbColor {
    pub r: u8,
    pub g: u8,
    pub b: u8,
}

impl RgbColor {
    pub const WHITE: Self = rgb(255, 255, 255);

    pub fn invert(&self) -> Self {
        rgb(!self.r, !self.g, !self.b)
    }

    pub(crate) fn cap_brightness(&self, limit: u8) -> Self {
        Self {
            r: self.r.min(limit),
            g: self.g.min(limit),
            b: self.b.min(limit),
        }
    }
}

pub const fn rgb(r: u8, g: u8, b: u8) -> RgbColor {
    RgbColor { r, g, b }
}


================================================
FILE: src/config.rs
================================================
use {
    crate::{args::SourceArgs, result_ext::AnyhowConv as _},
    anyhow::Context as _,
    directories::ProjectDirs,
    egui_fontcfg::CustomFontPaths,
    recently_used_list::RecentlyUsedList,
    serde::{Deserialize, Serialize},
    std::{
        collections::{BTreeMap, HashMap},
        path::PathBuf,
    },
};

#[derive(Serialize, Deserialize)]
pub struct Config {
    pub recent: RecentlyUsedList<SourceArgs>,
    pub style: Style,
    /// filepath->meta associations
    #[serde(default)]
    pub meta_assocs: MetaAssocs,
    #[serde(default = "default_vsync")]
    pub vsync: bool,
    #[serde(default)]
    pub fps_limit: u32,
    #[serde(default)]
    pub pinned_dirs: Vec<PinnedDir>,
    #[serde(default)]
    pub custom_font_paths: CustomFontPaths,
    #[serde(default)]
    pub font_families: BTreeMap<egui::FontFamily, Vec<String>>,
}

#[derive(Serialize, Deserialize)]
pub struct PinnedDir {
    pub path: PathBuf,
    pub label: String,
}

const fn default_vsync() -> bool {
    true
}

pub type MetaAssocs = HashMap<PathBuf, PathBuf>;

#[derive(Serialize, Deserialize, Default)]
pub struct Style {
    pub font_sizes: FontSizes,
}

#[derive(Serialize, Deserialize)]
pub struct FontSizes {
    pub heading: u8,
    pub body: u8,
    pub monospace: u8,
    pub button: u8,
    pub small: u8,
}

impl Default for FontSizes {
    fn default() -> Self {
        Self {
            small: 10,
            body: 14,
            button: 14,
            heading: 16,
            monospace: 14,
        }
    }
}

const DEFAULT_RECENT_CAPACITY: usize = 16;

impl Default for Config {
    fn default() -> Self {
        let mut recent = RecentlyUsedList::default();
        recent.set_capacity(DEFAULT_RECENT_CAPACITY);
        Self {
            recent,
            style: Style::default(),
            meta_assocs: HashMap::default(),
            fps_limit: 0,
            vsync: default_vsync(),
            pinned_dirs: Vec::new(),
            custom_font_paths: Default::default(),
            font_families: Default::default(),
        }
    }
}

pub struct LoadedConfig {
    pub config: Config,
    /// If `Some`, saving this config file will overwrite an old one that couldn't be loaded
    pub old_config_err: Option<anyhow::Error>,
}

impl Config {
    pub fn load_or_default() -> anyhow::Result<LoadedConfig> {
        let proj_dirs = project_dirs().context("Failed to get project dirs")?;
        let cfg_dir = proj_dirs.config_dir();
        if !cfg_dir.exists() {
            std::fs::create_dir_all(cfg_dir)?;
        }
        let cfg_file = cfg_dir.join(FILENAME);
        if !cfg_file.exists() {
            Ok(LoadedConfig {
                config: Self::default(),
                old_config_err: None,
            })
        } else {
            let result = try {
                let cfg_bytes = std::fs::read(cfg_file).how()?;
                rmp_serde::from_slice(&cfg_bytes).how()?
            };
            match result {
                Ok(cfg) => Ok(LoadedConfig {
                    config: cfg,
                    old_config_err: None,
                }),
                Err(e) => Ok(LoadedConfig {
                    config: Self::default(),
                    old_config_err: Some(e),
                }),
            }
        }
    }
    pub fn save(&self) -> anyhow::Result<()> {
        let bytes = rmp_serde::to_vec(self)?;
        let proj_dirs = project_dirs().context("Failed to get project dirs")?;
        let cfg_dir = proj_dirs.config_dir();
        std::fs::write(cfg_dir.join(FILENAME), bytes)?;
        Ok(())
    }
}

pub fn project_dirs() -> Option<ProjectDirs> {
    ProjectDirs::from("", "crumblingstatue", "hexerator")
}

pub trait ProjectDirsExt {
    fn color_theme_path(&self) -> PathBuf;
}

impl ProjectDirsExt for ProjectDirs {
    fn color_theme_path(&self) -> PathBuf {
        self.config_dir().join("egui_colors_theme.pal")
    }
}

const FILENAME: &str = "hexerator.cfg";


================================================
FILE: src/damage_region.rs
================================================
pub enum DamageRegion {
    Single(usize),
    Range(std::ops::Range<usize>),
    RangeInclusive(std::ops::RangeInclusive<usize>),
}

impl DamageRegion {
    pub(crate) fn begin(&self) -> usize {
        match self {
            Self::Single(offset) => *offset,
            Self::Range(range) => range.start,
            Self::RangeInclusive(range) => *range.start(),
        }
    }

    pub(crate) fn end(&self) -> usize {
        match self {
            Self::Single(offset) => *offset,
            Self::Range(range) => range.end - 1,
            Self::RangeInclusive(range) => *range.end(),
        }
    }
}

impl From<std::ops::RangeInclusive<usize>> for DamageRegion {
    fn from(range: std::ops::RangeInclusive<usize>) -> Self {
        Self::RangeInclusive(range)
    }
}


================================================
FILE: src/data.rs
================================================
use {
    crate::{damage_region::DamageRegion, meta::region::Region},
    std::ops::{Deref, DerefMut},
};

/// The data we are viewing/editing
#[derive(Default, Debug)]
pub struct Data {
    data: Option<DataProvider>,
    /// The region that was changed compared to the source
    pub dirty_region: Option<Region>,
    /// Original data length. Compared with current data length to detect truncation.
    pub orig_data_len: usize,
}

enum DataProvider {
    Vec(Vec<u8>),
    MmapMut(memmap2::MmapMut),
    MmapImmut(memmap2::Mmap),
}

impl std::fmt::Debug for DataProvider {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Vec(..) => f.write_str("Vec"),
            Self::MmapMut(..) => f.write_str("MmapMut"),
            Self::MmapImmut(..) => f.write_str("MmapImmut"),
        }
    }
}

impl Data {
    pub(crate) fn clean_from_buf(buf: Vec<u8>) -> Self {
        Self {
            orig_data_len: buf.len(),
            data: Some(DataProvider::Vec(buf)),
            dirty_region: None,
        }
    }
    pub(crate) fn new_mmap_mut(mmap: memmap2::MmapMut) -> Self {
        Self {
            orig_data_len: mmap.len(),
            data: Some(DataProvider::MmapMut(mmap)),
            dirty_region: None,
        }
    }
    pub(crate) fn new_mmap_immut(mmap: memmap2::Mmap) -> Self {
        Self {
            orig_data_len: mmap.len(),
            data: Some(DataProvider::MmapImmut(mmap)),
            dirty_region: None,
        }
    }
    /// Drop any expensive allocations and reset to "empty" state
    pub(crate) fn close(&mut self) {
        self.data = None;
        self.dirty_region = None;
    }
    pub(crate) fn widen_dirty_region(&mut self, damage: DamageRegion) {
        match &mut self.dirty_region {
            Some(dirty_region) => {
                if damage.begin() < dirty_region.begin {
                    dirty_region.begin = damage.begin();
                }
                if damage.begin() > dirty_region.end {
                    dirty_region.end = damage.begin();
                }
                let end = damage.end();
                {
                    if end < dirty_region.begin {
                        gamedebug_core::per!("TODO: logic error in widen_dirty_region");
                        return;
                    }
                    if end > dirty_region.end {
                        dirty_region.end = end;
                    }
                }
            }
            None => {
                self.dirty_region = Some(Region {
                    begin: damage.begin(),
                    end: damage.end(),
                });
            }
        }
    }
    /// Clears the dirty region (asserts data is same as source), and sets length same as source
    pub(crate) fn undirty(&mut self) {
        self.dirty_region = None;
        self.orig_data_len = self.len();
    }

    pub(crate) fn resize(&mut self, new_len: usize, value: u8) {
        match &mut self.data {
            Some(DataProvider::Vec(v)) => v.resize(new_len, value),
            etc => {
                eprintln!("Data::resize: Unimplemented for {etc:?}");
            }
        }
    }

    pub(crate) fn extend_from_slice(&mut self, slice: &[u8]) {
        match &mut self.data {
            Some(DataProvider::Vec(v)) => v.extend_from_slice(slice),
            etc => {
                eprintln!("Data::extend_from_slice: Unimplemented for {etc:?}");
            }
        }
    }

    pub(crate) fn drain(&mut self, range: std::ops::Range<usize>) {
        match &mut self.data {
            Some(DataProvider::Vec(v)) => {
                v.drain(range);
            }
            etc => {
                eprintln!("Data::drain: Unimplemented for {etc:?}");
            }
        }
    }

    pub(crate) fn zero_fill_region(&mut self, region: Region) {
        let range = region.begin..=region.end;
        if let Some(data) = self.get_mut(range.clone()) {
            data.fill(0);
            self.widen_dirty_region(DamageRegion::RangeInclusive(range));
        }
    }

    pub(crate) fn reload_from_file(
        &mut self,
        src_args: &crate::args::SourceArgs,
        file: &mut std::fs::File,
    ) -> anyhow::Result<()> {
        match &mut self.data {
            Some(DataProvider::Vec(buf)) => {
                *buf = crate::app::read_contents(src_args, file)?;
            }
            etc => anyhow::bail!("Reload not supported for {etc:?}"),
        }
        self.dirty_region = None;
        Ok(())
    }

    pub(crate) fn mod_range(
        &mut self,
        range: std::ops::RangeInclusive<usize>,
        mut f: impl FnMut(&mut u8),
    ) {
        for byte in self.get_mut(range.clone()).into_iter().flatten() {
            f(byte);
        }
        self.widen_dirty_region(range.into());
    }
}

impl Deref for Data {
    type Target = [u8];

    fn deref(&self) -> &Self::Target {
        match &self.data {
            Some(DataProvider::Vec(v)) => v,
            Some(DataProvider::MmapMut(map)) => map,
            Some(DataProvider::MmapImmut(map)) => map,
            None => &[],
        }
    }
}

impl DerefMut for Data {
    fn deref_mut(&mut self) -> &mut Self::Target {
        match &mut self.data {
            Some(DataProvider::Vec(v)) => v,
            Some(DataProvider::MmapMut(map)) => map,
            Some(DataProvider::MmapImmut(_)) => &mut [],
            None => &mut [],
        }
    }
}


================================================
FILE: src/dec_conv.rs
================================================
fn byte_10_digits(byte: u8) -> [u8; 3] {
    [byte / 100, (byte % 100) / 10, byte % 10]
}

#[test]
fn test_byte_10_digits() {
    assert_eq!(byte_10_digits(255), [2, 5, 5]);
}

pub fn byte_to_dec_digits(byte: u8) -> [u8; 3] {
    const TABLE: &[u8; 10] = b"0123456789";

    let [a, b, c] = byte_10_digits(byte);
    [TABLE[a as usize], TABLE[b as usize], TABLE[c as usize]]
}

#[test]
fn test_byte_to_dec_digits() {
    let pairs = [
        (255, b"255"),
        (0, b"000"),
        (1, b"001"),
        (15, b"015"),
        (16, b"016"),
        (154, b"154"),
        (167, b"167"),
        (6, b"006"),
        (64, b"064"),
        (127, b"127"),
        (128, b"128"),
        (129, b"129"),
    ];
    for (byte, hex) in pairs {
        assert_eq!(byte_to_dec_digits(byte), *hex);
    }
}


================================================
FILE: src/edit_buffer.rs
================================================
use gamedebug_core::per;

#[derive(Debug, Default, Clone)]
pub struct EditBuffer {
    pub buf: Vec<u8>,
    pub cursor: u16,
    /// Whether this edit buffer has been edited
    pub dirty: bool,
}

impl EditBuffer {
    pub(crate) fn resize(&mut self, new_size: u16) {
        self.buf.resize(usize::from(new_size), 0);
    }
    /// Enter a byte. Returns if editing is "finished" (at end)
    pub(crate) fn enter_byte(&mut self, byte: u8) -> bool {
        self.dirty = true;
        self.buf[self.cursor as usize] = byte;
        self.cursor += 1;
        if usize::from(self.cursor) >= self.buf.len() {
            self.reset();
            true
        } else {
            false
        }
    }

    pub fn reset(&mut self) {
        self.cursor = 0;
        self.dirty = false;
    }

    pub(crate) fn update_from_string(&mut self, s: &str) {
        let bytes = s.as_bytes();
        self.buf[..bytes.len()].copy_from_slice(bytes);
    }
    /// Returns whether the cursor could be moved any further
    pub(crate) fn move_cursor_back(&mut self) -> bool {
        if self.cursor == 0 {
            false
        } else {
            self.cursor -= 1;
            true
        }
    }
    /// Move the cursor to the end
    #[expect(
        clippy::cast_possible_truncation,
        reason = "Buffer is never bigger than u16::MAX"
    )]
    pub(crate) fn move_cursor_end(&mut self) {
        self.cursor = (self.buf.len() - 1) as u16;
    }

    /// Returns whether the cursor could be moved any further
    #[expect(
        clippy::cast_possible_truncation,
        reason = "Buffer is never bigger than u16::MAX"
    )]
    pub(crate) fn move_cursor_forward(&mut self) -> bool {
        if self.cursor >= self.buf.len() as u16 - 1 {
            false
        } else {
            per!("Moving cursor forward, no problem");
            self.cursor += 1;
            true
        }
    }

    pub(crate) fn move_cursor_begin(&mut self) {
        self.cursor = 0;
    }
}


================================================
FILE: src/find_util.rs
================================================
pub fn find_hex_string(
    hex_string: &str,
    haystack: &[u8],
    mut f: impl FnMut(usize),
) -> anyhow::Result<()> {
    let needle = parse_hex_string(hex_string)?;
    for offset in memchr::memmem::find_iter(haystack, &needle) {
        f(offset);
    }
    Ok(())
}

enum HexStringSepKind {
    Comma,
    Whitespace,
    Dense,
}

fn detect_hex_string_sep_kind(hex_string: &str) -> HexStringSepKind {
    if hex_string.contains(',') {
        HexStringSepKind::Comma
    } else if hex_string.contains(char::is_whitespace) {
        HexStringSepKind::Whitespace
    } else {
        HexStringSepKind::Dense
    }
}

fn chunks_2(input: &str) -> impl Iterator<Item = anyhow::Result<&str>> {
    input
        .as_bytes()
        .as_chunks::<2>()
        .0
        .iter()
        .map(|pair| std::str::from_utf8(pair).map_err(anyhow::Error::from))
}

pub fn parse_hex_string(hex_string: &str) -> anyhow::Result<Vec<u8>> {
    match detect_hex_string_sep_kind(hex_string) {
        HexStringSepKind::Comma => {
            hex_string.split(',').map(|tok| parse_hex_token(tok.trim())).collect()
        }
        HexStringSepKind::Whitespace => {
            hex_string.split_whitespace().map(parse_hex_token).collect()
        }
        HexStringSepKind::Dense => chunks_2(hex_string).map(|tok| parse_hex_token(tok?)).collect(),
    }
}

fn parse_hex_token(tok: &str) -> anyhow::Result<u8> {
    Ok(u8::from_str_radix(tok, 16)?)
}

#[test]
fn test_parse_hex_string() {
    assert_eq!(
        parse_hex_string("de ad be ef").unwrap(),
        vec![0xde, 0xad, 0xbe, 0xef]
    );
    assert_eq!(
        parse_hex_string("de, ad, be, ef").unwrap(),
        vec![0xde, 0xad, 0xbe, 0xef]
    );
    assert_eq!(
        parse_hex_string("deadbeef").unwrap(),
        vec![0xde, 0xad, 0xbe, 0xef]
    );
}


================================================
FILE: src/gui/bottom_panel.rs
================================================
use {
    super::{Gui, dialogs::JumpDialog, egui_ui_ext::EguiResponseExt as _},
    crate::{
        app::{App, interact_mode::InteractMode},
        meta::find_most_specific_region_for_offset,
        shell::msg_if_fail,
        util::human_size,
        view::ViewportVec,
    },
    constcat::concat,
    egui::{Align, Color32, DragValue, Stroke, TextFormat, TextStyle, Ui, text::LayoutJob},
    egui_phosphor::regular as ic,
    slotmap::Key as _,
};

const L_SCROLL: &str = concat!(ic::MOUSE_SCROLL, " scroll");

pub fn ui(ui: &mut Ui, app: &mut App, mouse_pos: ViewportVec, gui: &mut Gui) {
    ui.horizontal(|ui| {
        let job = key_label(ui, "F1", "View");
        if ui
            .selectable_label(app.hex_ui.interact_mode == InteractMode::View, job)
            .clicked()
        {
            app.hex_ui.interact_mode = InteractMode::View;
        }
        ui.style_mut().visuals.selection.bg_fill = Color32::from_rgb(168, 150, 32);
        let job = key_label(ui, "F2", "Edit");
        if ui
            .selectable_label(app.hex_ui.interact_mode == InteractMode::Edit, job)
            .clicked()
        {
            app.hex_ui.interact_mode = InteractMode::Edit;
        }
        ui.separator();
        let data_len = app.data.len();
        if data_len != 0
            && let Some(view_key) = app.hex_ui.focused_view
        {
            let view = &mut app.meta_state.meta.views[view_key].view;
            let per = match app.meta_state.meta.low.perspectives.get_mut(view.perspective) {
                Some(per) => per,
                None => {
                    ui.label("Invalid perspective key");
                    return;
                }
            };
            ui.label("offset");
            ui.add(DragValue::new(
                &mut app.meta_state.meta.low.regions[per.region].region.begin,
            ));
            ui.label("columns");
            ui.add(DragValue::new(&mut per.cols));
            let offsets = view.offsets(
                &app.meta_state.meta.low.perspectives,
                &app.meta_state.meta.low.regions,
            );
            let re = ui.button(L_SCROLL);
            if re.clicked() {
                gui.show_quick_scroll_popup ^= true;
            }
            #[expect(
                clippy::cast_precision_loss,
                reason = "Precision is good until 52 bits (more than reasonable)"
            )]
            let mut ratio = offsets.byte as f64 / data_len as f64;
            if gui.show_quick_scroll_popup {
                let avail_w = ui.available_width();
                egui::Window::new("quick_scroll_popup")
                    .resizable(false)
                    .title_bar(false)
                    .fixed_pos(re.rect.right_top())
                    .show(ui.ctx(), |ui| {
                        ui.spacing_mut().slider_width = avail_w * 0.8;
                        let re = ui.add(
                            egui::Slider::new(&mut ratio, 0.0..=1.0)
                                .custom_formatter(|n, _| format!("{:.2}%", n * 100.)),
                        );
                        if re.changed() {
                            // This is used for a rough scroll, so lossy conversion is to be expected
                            #[expect(
                                clippy::cast_possible_truncation,
                                clippy::cast_precision_loss,
                                clippy::cast_sign_loss
                            )]
                            let new_off = (app.data.len() as f64 * ratio) as usize;
                            view.scroll_to_byte_offset(
                                new_off,
                                &app.meta_state.meta.low.perspectives,
                                &app.meta_state.meta.low.regions,
                                false,
                                true,
                            );
                        }
                        ui.horizontal(|ui| {
                            ui.label(human_size(offsets.byte));
                            if ui.button("Close").clicked() {
                                gui.show_quick_scroll_popup = false;
                            }
                        });
                    });
            }
            ui.label(format!(
                "row {} col {} byte {} ({:.2}%)",
                offsets.row,
                offsets.col,
                offsets.byte,
                ratio * 100.0
            ))
            .on_hover_text_deferred(|| human_size(offsets.byte));
        }
        ui.separator();
        let [row, col] = app.row_col_of_cursor().unwrap_or([0, 0]);
        let mut text = egui::RichText::new(format!(
            "cursor: {} ({:x}) [r{row} c{col}]",
            app.edit_state.cursor, app.edit_state.cursor,
        ));
        let out_of_bounds = app.edit_state.cursor >= app.data.len();
        let cursor_end = app.edit_state.cursor == app.data.len().saturating_sub(1);
        let cursor_begin = app.edit_state.cursor == 0;
        if out_of_bounds {
            text = text.color(Color32::RED);
        } else if cursor_end {
            text = text.color(Color32::YELLOW);
        } else if cursor_begin {
            text = text.color(Color32::GREEN);
        }
        let re = ui.label(text);
        re.context_menu(|ui| {
            if ui.button("Copy").clicked() {
                let result = app.clipboard.set_text(app.edit_state.cursor.to_string());
                msg_if_fail(result, "Failed to set clipboard text", &mut gui.msg_dialog);
            }
            if ui.button("Copy absolute").on_hover_text("Hard seek + cursor").clicked() {
                let result = app.clipboard.set_text(
                    (app.edit_state.cursor + app.src_args.hard_seek.unwrap_or(0)).to_string(),
                );
                msg_if_fail(result, "Failed to set clipboard text", &mut gui.msg_dialog);
            }
        });
        if re.clicked() {
            Gui::add_dialog(&mut gui.dialogs, JumpDialog::default());
        }
        if out_of_bounds {
            re.on_hover_text("Cursor is out of bounds");
        } else if cursor_end {
            re.on_hover_text("Cursor is at end of document");
        } else if cursor_begin {
            re.on_hover_text("Cursor is at beginning");
        } else {
            re.on_hover_text_deferred(|| human_size(app.edit_state.cursor));
        }
        if let Some(label) = app
            .meta_state
            .meta
            .bookmarks
            .iter()
            .find_map(|bm| (bm.offset == app.edit_state.cursor).then_some(bm.label.as_str()))
        {
            ui.label(egui::RichText::new(label).color(Color32::from_rgb(150, 170, 40)));
        }
        if let Some(region) = find_most_specific_region_for_offset(
            &app.meta_state.meta.low.regions,
            app.edit_state.cursor,
        ) {
            let reg = &app.meta_state.meta.low.regions[region];
            region_label(ui, &reg.name).context_menu(|ui| {
                if ui.button("Select").clicked() {
                    app.hex_ui.select_a = Some(reg.region.begin);
                    app.hex_ui.select_b = Some(reg.region.end);
                }
            });
        }
        if !app.hex_ui.current_layout.is_null()
            && let Some((offset, _view_idx)) = app.byte_offset_at_pos(mouse_pos.x, mouse_pos.y)
        {
            let [row, col] = app.row_col_of_byte_pos(offset).unwrap_or([0, 0]);
            ui.label(format!("mouse: {offset} ({offset:x}) [r{row} c{col}]"));
            if let Some(region) =
                find_most_specific_region_for_offset(&app.meta_state.meta.low.regions, offset)
            {
                region_label(ui, &app.meta_state.meta.low.regions[region].name);
            }
        }
        ui.with_layout(egui::Layout::right_to_left(Align::Center), |ui| {
            let mut txt = egui::RichText::new(format!("File size: {}", app.data.len()));
            let truncated = app.data.len() != app.data.orig_data_len;
            if truncated {
                txt = txt.color(Color32::RED);
            }
            let label = egui::Label::new(txt).sense(egui::Sense::click());
            let mut label_re = ui.add(label).on_hover_ui(|ui| {
                ui.label("Click to copy");
                ui.label(format!("Human size: {}", human_size(app.data.len())));
            });
            if truncated {
                label_re = label_re.on_hover_text_deferred(|| {
                    format!("Length changed, orig.: {}", app.data.orig_data_len)
                });
            }
            if label_re.clicked() {
                crate::app::set_clipboard_string(
                    &mut app.clipboard,
                    &mut gui.msg_dialog,
                    &app.data.len().to_string(),
                );
            }
        });
    });
}

fn region_label(ui: &mut Ui, name: &str) -> egui::Response {
    let label =
        egui::Label::new(egui::RichText::new(format!("[{name}]")).color(Color32::LIGHT_BLUE))
            .sense(egui::Sense::click());
    ui.add(label)
}

/// A key "box" and then some text. Like `[F1] View`
fn key_label(ui: &Ui, key_text: &str, label_text: &str) -> LayoutJob {
    let mut job = LayoutJob::default();
    let style = ui.style();
    let body_font = TextStyle::Body.resolve(style);
    job.append(
        key_text,
        0.0,
        TextFormat {
            font_id: body_font.clone(),
            color: style.visuals.widgets.active.fg_stroke.color,
            background: style.visuals.code_bg_color,
            italics: false,
            underline: Stroke::NONE,
            strikethrough: Stroke::NONE,
            valign: Align::Center,
            ..Default::default()
        },
    );
    job.append(
        label_text,
        10.0,
        TextFormat::simple(body_font, style.visuals.widgets.active.fg_stroke.color),
    );
    job
}


================================================
FILE: src/gui/command.rs
================================================
//! This module is similar in purpose to [`crate::app::command`].
//!
//! See that module for more information.

use {
    super::Gui,
    crate::shell::msg_fail,
    std::{collections::VecDeque, process::Command},
    sysinfo::ProcessesToUpdate,
};

pub enum GCmd {
    OpenPerspectiveWindow,
    /// Spawn a command with optional arguments. Must not be an empty vector.
    SpawnCommand {
        args: Vec<String>,
        /// If `Some`, don't focus a pid, just filter for this process in the list.
        ///
        /// The idea is that if your command spawns a child process, it might not spawn immediately,
        /// so the user can wait for it to appear on the process list, with the applied filter.
        look_for_proc: Option<String>,
    },
}

/// Gui command queue.
///
/// Push operations with `push`, and call [`Gui::flush_command_queue`] when you have
/// exclusive access to the [`Gui`].
///
/// [`Gui::flush_command_queue`] is called automatically every frame, if you don't need to perform the operations sooner.
#[derive(Default)]
pub struct GCommandQueue {
    inner: VecDeque<GCmd>,
}

impl GCommandQueue {
    pub fn push(&mut self, command: GCmd) {
        self.inner.push_back(command);
    }
}

impl Gui {
    /// Flush the [`GCommandQueue`] and perform all operations queued up.
    ///
    /// Automatically called every frame, but can be called manually if operations need to be
    /// performed sooner.
    pub fn flush_command_queue(&mut self) {
        while let Some(cmd) = self.cmd.inner.pop_front() {
            perform_command(self, cmd);
        }
    }
}

fn perform_command(gui: &mut Gui, cmd: GCmd) {
    match cmd {
        GCmd::OpenPerspectiveWindow => gui.win.perspectives.open.set(true),
        GCmd::SpawnCommand {
            mut args,
            look_for_proc,
        } => {
            let cmd = args.remove(0);
            match Command::new(cmd).args(args).spawn() {
                Ok(child) => {
                    gui.win.open_process.open.set(true);
                    match look_for_proc {
                        Some(procname) => {
                            gui.win
                                .open_process
                                .sys
                                .refresh_processes(ProcessesToUpdate::All, true);
                            gui.win.open_process.filters.proc_name = procname;
                        }
                        None => {
                            gui.win.open_process.selected_pid =
                                Some(sysinfo::Pid::from_u32(child.id()));
                        }
                    }
                }
                Err(e) => {
                    msg_fail(&e, "Failed to spawn command", &mut gui.msg_dialog);
                }
            }
        }
    }
}


================================================
FILE: src/gui/dialogs/auto_save_reload.rs
================================================
use {
    crate::{app::App, gui::Dialog, session_prefs::Autoreload},
    mlua::Lua,
};

#[derive(Debug)]
pub struct AutoSaveReloadDialog;

impl Dialog for AutoSaveReloadDialog {
    fn title(&self) -> &str {
        "Auto save/reload"
    }

    fn ui(
        &mut self,
        ui: &mut egui::Ui,
        app: &mut App,
        _gui: &mut crate::gui::Gui,
        _lua: &Lua,
        _font_size: u16,
        _line_spacing: u16,
    ) -> bool {
        egui::ComboBox::from_label("Auto reload")
            .selected_text(app.preferences.auto_reload.label())
            .show_ui(ui, |ui| {
                ui.selectable_value(
                    &mut app.preferences.auto_reload,
                    Autoreload::Disabled,
                    Autoreload::Disabled.label(),
                );
                ui.selectable_value(
                    &mut app.preferences.auto_reload,
                    Autoreload::All,
                    Autoreload::All.label(),
                );
                ui.selectable_value(
                    &mut app.preferences.auto_reload,
                    Autoreload::Visible,
                    Autoreload::Visible.label(),
                );
            });
        ui.horizontal(|ui| {
            ui.label("Interval (ms)");
            ui.add(egui::DragValue::new(
                &mut app.preferences.auto_reload_interval_ms,
            ));
        });
        ui.separator();
        ui.checkbox(&mut app.preferences.auto_save, "Auto save")
            .on_hover_text("Save every time an editing action is finished");
        ui.separator();
        !(ui.button("Close (enter/esc)").clicked()
            || ui.input(|inp| inp.key_pressed(egui::Key::Escape))
            || ui.input(|inp| inp.key_pressed(egui::Key::Enter)))
    }
}


================================================
FILE: src/gui/dialogs/jump.rs
================================================
use {
    crate::{
        app::App,
        gui::Dialog,
        parse_radix::{Relativity, parse_offset_maybe_relative},
        shell::msg_fail,
    },
    mlua::Lua,
};

#[derive(Debug, Default)]
pub struct JumpDialog {
    string_buf: String,
    absolute: bool,
    just_opened: bool,
}

impl Dialog for JumpDialog {
    fn title(&self) -> &str {
        "Jump"
    }

    fn on_open(&mut self) {
        self.just_opened = true;
    }

    fn ui(
        &mut self,
        ui: &mut egui::Ui,
        app: &mut App,
        gui: &mut crate::gui::Gui,
        _lua: &Lua,
        _font_size: u16,
        _line_spacing: u16,
    ) -> bool {
        ui.horizontal(|ui| {
            ui.label("Offset");
            let re = ui.text_edit_singleline(&mut self.string_buf);
            if self.just_opened {
                re.request_focus();
            }
        });
        self.just_opened = false;
        ui.label(
            "Accepts both decimal and hexadecimal.\nPrefix with `0x` to force hex.\n\
        Prefix with `+` to add to current offset, `-` to subtract",
        );
        if let Some(hard_seek) = app.src_args.hard_seek {
            ui.checkbox(&mut self.absolute, "Absolute")
                .on_hover_text("Subtract the offset from hard-seek");
            let label = format!("hard-seek is at {hard_seek} (0x{hard_seek:X})");
            ui.text_edit_multiline(&mut &label[..]);
        }
        if ui.input(|inp| inp.key_pressed(egui::Key::Enter)) {
            // Just close the dialog without error on empty text input
            if self.string_buf.trim().is_empty() {
                return false;
            }
            match parse_offset_maybe_relative(&self.string_buf) {
                Ok((offset, relativity)) => {
                    let offset = match relativity {
                        Relativity::Absolute => {
                            if let Some(hard_seek) = app.src_args.hard_seek
                                && self.absolute
                            {
                                offset.saturating_sub(hard_seek)
                            } else {
                                offset
                            }
                        }
                        Relativity::RelAdd => app.edit_state.cursor.saturating_add(offset),
                        Relativity::RelSub => app.edit_state.cursor.saturating_sub(offset),
                    };
                    app.edit_state.cursor = offset;
                    app.center_view_on_offset(offset);
                    app.hex_ui.flash_cursor();
                    false
                }
                Err(e) => {
                    msg_fail(&e, "Failed to parse offset", &mut gui.msg_dialog);
                    true
                }
            }
        } else {
            !(ui.input(|inp| inp.key_pressed(egui::Key::Escape)))
        }
    }
}


================================================
FILE: src/gui/dialogs/lua_color.rs
================================================
use {
    crate::{app::App, gui::Dialog, value_color::ColorMethod},
    mlua::{Function, Lua},
};

pub struct LuaColorDialog {
    script: String,
    err_string: String,
    auto_exec: bool,
}

impl Default for LuaColorDialog {
    fn default() -> Self {
        const DEFAULT_SCRIPT: &str =
            include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/lua/color.lua"));
        Self {
            script: DEFAULT_SCRIPT.into(),
            err_string: String::new(),
            auto_exec: Default::default(),
        }
    }
}

impl Dialog for LuaColorDialog {
    fn title(&self) -> &str {
        "Lua color"
    }

    fn ui(
        &mut self,
        ui: &mut egui::Ui,
        app: &mut App,
        _gui: &mut crate::gui::Gui,
        lua: &Lua,
        _font_size: u16,
        _line_spacing: u16,
    ) -> bool {
        let color_data = match app.hex_ui.focused_view {
            Some(view_key) => {
                let view = &mut app.meta_state.meta.views[view_key].view;
                match &mut view.presentation.color_method {
                    ColorMethod::Custom(color_data) => &mut color_data.0,
                    _ => {
                        ui.label("Please select \"Custom\" as color scheme for the current view");
                        return !ui.button("Close").clicked();
                    }
                }
            }
            None => {
                ui.label("No active view");
                return !ui.button("Close").clicked();
            }
        };
        egui::TextEdit::multiline(&mut self.script)
            .code_editor()
            .desired_width(f32::INFINITY)
            .show(ui);
        ui.horizontal(|ui| {
            if ui.button("Execute").clicked() || self.auto_exec {
                let chunk = lua.load(&self.script);
                let res = try {
                    let fun = chunk.eval::<Function>()?;
                    for (i, c) in color_data.iter_mut().enumerate() {
                        let rgb: [u8; 3] = fun.call((i,))?;
                        *c = rgb;
                    }
                };
                if let Err(e) = res {
                    self.err_string = e.to_string();
                } else {
                    self.err_string.clear();
                }
            }
            ui.checkbox(&mut self.auto_exec, "Auto execute");
        });
        if !self.err_string.is_empty() {
            ui.label(egui::RichText::new(&self.err_string).color(egui::Color32::RED));
        }
        if ui.button("Close").clicked() {
            return false;
        }
        true
    }
}


================================================
FILE: src/gui/dialogs/lua_fill.rs
================================================
use {
    crate::{app::App, gui::Dialog, shell::msg_if_fail},
    egui_code_editor::{CodeEditor, Syntax},
    mlua::{Function, Lua},
    std::time::Instant,
};

#[derive(Debug, Default)]
pub struct LuaFillDialog {
    result_info_string: String,
    err: bool,
}

impl Dialog for LuaFillDialog {
    fn title(&self) -> &str {
        "Lua fill"
    }

    fn ui(
        &mut self,
        ui: &mut egui::Ui,
        app: &mut App,
        gui: &mut crate::gui::Gui,
        lua: &Lua,
        _font_size: u16,
        _line_spacing: u16,
    ) -> bool {
        let Some(sel) = app.hex_ui.selection() else {
            ui.heading("No active selection");
            return !ui.button("Close").clicked();
        };
        let ctrl_enter =
            ui.input_mut(|inp| inp.consume_key(egui::Modifiers::CTRL, egui::Key::Enter));

        let ctrl_s = ui.input_mut(|inp| inp.consume_key(egui::Modifiers::CTRL, egui::Key::S));
        if ctrl_s {
            msg_if_fail(
                app.save(&mut gui.msg_dialog),
                "Failed to save",
                &mut gui.msg_dialog,
            );
        }
        egui::ScrollArea::vertical()
            // 100.0 is an estimation of ui size below.
            // If we don't subtract that, the text edit tries to expand
            // beyond window height
            .max_height(ui.available_height() - 100.0)
            .show(ui, |ui| {
                CodeEditor::default()
                    .with_syntax(Syntax::lua())
                    .show(ui, &mut app.meta_state.meta.misc.fill_lua_script);
            });
        if ui.button("Execute").clicked() || ctrl_enter {
            let start_time = Instant::now();
            let chunk = lua.load(&app.meta_state.meta.misc.fill_lua_script);
            let res = try {
                let f = chunk.eval::<Function>()?;
                for (i, b) in app.data[sel.begin..=sel.end].iter_mut().enumerate() {
                    *b = f.call((i, *b))?;
                }
                app.data.dirty_region = Some(sel);
            };
            if let Err(e) = res {
                self.result_info_string = e.to_string();
                self.err = true;
            } else {
                self.result_info_string =
                    format!("Script took {} ms", start_time.elapsed().as_millis());
                self.err = false;
            }
        }
        if app.data.dirty_region.is_some() {
            ui.label(
                egui::RichText::new("Unsaved changes")
                    .italics()
                    .color(egui::Color32::YELLOW)
                    .code(),
            );
        } else {
            ui.label(egui::RichText::new("No unsaved changes").color(egui::Color32::GREEN).code());
        }
        ui.label("ctrl+enter to execute, ctrl+s to save file");
        if !self.result_info_string.is_empty() {
            if self.err {
                ui.label(egui::RichText::new(&self.result_info_string).color(egui::Color32::RED));
            } else {
                ui.label(&self.result_info_string);
            }
        }
        true
    }
    fn has_close_button(&self) -> bool {
        true
    }
}


================================================
FILE: src/gui/dialogs/pattern_fill.rs
================================================
use {
    crate::{
        app::App,
        damage_region::DamageRegion,
        find_util,
        gui::{Dialog, message_dialog::Icon},
        slice_ext::SliceExt as _,
    },
    mlua::Lua,
};

#[derive(Debug, Default)]
pub struct PatternFillDialog {
    pattern_string: String,
    just_opened: bool,
}

impl Dialog for PatternFillDialog {
    fn title(&self) -> &str {
        "Selection pattern fill"
    }

    fn on_open(&mut self) {
        self.just_opened = true;
    }

    fn ui(
        &mut self,
        ui: &mut egui::Ui,
        app: &mut App,
        gui: &mut crate::gui::Gui,
        _lua: &Lua,
        _font_size: u16,
        _line_spacing: u16,
    ) -> bool {
        let re = ui.add(
            egui::TextEdit::singleline(&mut self.pattern_string)
                .hint_text("Hex pattern (e.g. `00 ff 00`)"),
        );
        if self.just_opened {
            re.request_focus();
        }
        self.just_opened = false;
        if ui.input(|inp| inp.key_pressed(egui::Key::Enter)) {
            let values: Result<Vec<u8>, _> = find_util::parse_hex_string(&self.pattern_string);
            match values {
                Ok(values) => {
                    for reg in app.hex_ui.selected_regions() {
                        let range = reg.to_range();
                        let Some(data_slice) = app.data.get_mut(range.clone()) else {
                            gui.msg_dialog.open(Icon::Error, "Pattern fill error", format!("Invalid range for fill.\nRequested range: {range:?}\nData length: {}", app.data.len()));
                            return false;
                        };
                        data_slice.pattern_fill(&values);
                        app.data.widen_dirty_region(DamageRegion::RangeInclusive(range));
                    }
                    false
                }
                Err(e) => {
                    gui.msg_dialog.open(Icon::Error, "Fill parse error", e.to_string());
                    true
                }
            }
        } else {
            true
        }
    }
    fn has_close_button(&self) -> bool {
        true
    }
}


================================================
FILE: src/gui/dialogs/truncate.rs
================================================
use {
    crate::{app::App, gui::Dialog, meta::region::Region},
    egui::{Button, DragValue},
    mlua::Lua,
};

pub struct TruncateDialog {
    begin: usize,
    end: usize,
}

impl TruncateDialog {
    pub fn new(data_len: usize, selection: Option<Region>) -> Self {
        let (begin, end) = match selection {
            Some(region) => (region.begin, region.end),
            None => (0, data_len.saturating_sub(1)),
        };
        Self { begin, end }
    }
}

impl Dialog for TruncateDialog {
    fn title(&self) -> &str {
        "Truncate/Extend"
    }

    fn ui(
        &mut self,
        ui: &mut egui::Ui,
        app: &mut App,
        _gui: &mut crate::gui::Gui,
        _lua: &Lua,
        _font_size: u16,
        _line_spacing: u16,
    ) -> bool {
        ui.horizontal(|ui| {
            ui.label("Begin");
            ui.add(DragValue::new(&mut self.begin).range(0..=self.end.saturating_sub(1)));
            if ui
                .add_enabled(
                    self.begin != app.edit_state.cursor,
                    Button::new("From cursor"),
                )
                .clicked()
            {
                self.begin = app.edit_state.cursor;
            }
        });
        ui.horizontal(|ui| {
            ui.label("End");
            ui.add(DragValue::new(&mut self.end));
            if ui
                .add_enabled(
                    self.end != app.edit_state.cursor,
                    Button::new("From cursor"),
                )
                .clicked()
            {
                self.end = app.edit_state.cursor;
            }
        });
        let new_len = (self.end + 1) - self.begin;
        let mut text = egui::RichText::new(format!("New length: {new_len}"));
        match new_len.cmp(&app.data.orig_data_len) {
            std::cmp::Ordering::Less => text = text.color(egui::Color32::RED),
            std::cmp::Ordering::Equal => {}
            std::cmp::Ordering::Greater => text = text.color(egui::Color32::YELLOW),
        }
        ui.label(text);
        if let Some(sel) = app.hex_ui.selection() {
            if ui
                .add_enabled(
                    !(sel.begin == self.begin && sel.end == self.end),
                    Button::new("From selection"),
                )
                .clicked()
            {
                self.begin = sel.begin;
                self.end = sel.end;
            }
        } else {
            ui.add_enabled(false, Button::new("From selection"));
        }
        ui.separator();
        let text = egui::RichText::new("⚠ Truncate/Extend ⚠").color(egui::Color32::RED);
        let mut retain = true;
        ui.horizontal(|ui| {
            if ui
                .button(text)
                .on_hover_text("This will change the length of the data")
                .clicked()
            {
                app.data.resize(self.end + 1, 0);
                app.data.drain(0..self.begin);
                app.hex_ui.clear_selections();
                app.data.dirty_region = Some(Region {
                    begin: 0,
                    end: app.data.len(),
                });
            }
            if ui.button("Close").clicked() {
                retain = false;
            }
        });
        retain
    }
}


================================================
FILE: src/gui/dialogs/x86_asm.rs
================================================
use {
    crate::{app::App, gui::Dialog},
    egui::Button,
    iced_x86::{Decoder, Formatter as _, NasmFormatter},
    mlua::Lua,
};

pub struct X86AsmDialog {
    decoded: Vec<DecodedInstr>,
    bitness: u32,
}

impl X86AsmDialog {
    pub fn new() -> Self {
        Self {
            decoded: Vec::new(),
            bitness: 64,
        }
    }
}

impl Dialog for X86AsmDialog {
    fn title(&self) -> &str {
        "X86 assembly"
    }

    fn ui(
        &mut self,
        ui: &mut egui::Ui,
        app: &mut App,
        _gui: &mut crate::gui::Gui,
        _lua: &Lua,
        _font_size: u16,
        _line_spacing: u16,
    ) -> bool {
        let mut retain = true;
        egui::ScrollArea::vertical()
            .auto_shrink(false)
            .max_height(320.0)
            .show(ui, |ui| {
                egui::Grid::new("asm_grid").num_columns(2).show(ui, |ui| {
                    for instr in &self.decoded {
                        let Some(sel_begin) = app.hex_ui.selection().map(|sel| sel.begin) else {
                            ui.label("No selection");
                            return;
                        };
                        let instr_off = instr.offset + sel_begin;
                        if ui.link(instr_off.to_string()).clicked() {
                            app.search_focus(instr_off);
                        }
                        ui.label(&instr.string);
                        ui.end_row();
                    }
                });
            });
        ui.separator();
        match app.hex_ui.selection() {
            Some(sel) => {
                if ui.button("Disassemble").clicked() {
                    self.decoded = disasm(&app.data[sel.begin..=sel.end], self.bitness);
                }
            }
            None => {
                ui.add_enabled(false, Button::new("Disassemble"));
            }
        }
        ui.horizontal(|ui| {
            ui.label("Bitness");
            ui.radio_value(&mut self.bitness, 16, "16");
            ui.radio_value(&mut self.bitness, 32, "32");
            ui.radio_value(&mut self.bitness, 64, "64");
        });
        if ui.button("Close").clicked() {
            retain = false;
        }
        retain
    }
}

struct DecodedInstr {
    string: String,
    offset: usize,
}

fn disasm(data: &[u8], bitness: u32) -> Vec<DecodedInstr> {
    let mut decoder = Decoder::new(bitness, data, 0);
    let mut fmt = NasmFormatter::default();
    let mut vec = Vec::new();
    while decoder.can_decode() {
        let offset = decoder.position();
        let instr = decoder.decode();
        let mut string = String::new();
        fmt.format(&instr, &mut string);
        vec.push(DecodedInstr { string, offset });
    }
    vec
}


================================================
FILE: src/gui/dialogs.rs
================================================
mod auto_save_reload;
mod jump;
mod lua_color;
mod lua_fill;
pub mod pattern_fill;
mod truncate;
mod x86_asm;

pub use {
    auto_save_reload::AutoSaveReloadDialog, jump::JumpDialog, lua_color::LuaColorDialog,
    lua_fill::LuaFillDialog, pattern_fill::PatternFillDialog, truncate::TruncateDialog,
    x86_asm::X86AsmDialog,
};


================================================
FILE: src/gui/egui_ui_ext.rs
================================================
pub trait EguiResponseExt {
    fn on_hover_text_deferred<F, R>(self, text_fun: F) -> Self
    where
        F: FnOnce() -> R,
        R: Into<egui::WidgetText>;
}

impl EguiResponseExt for egui::Response {
    fn on_hover_text_deferred<F, R>(self, text_fun: F) -> Self
    where
        F: FnOnce() -> R,
        R: Into<egui::WidgetText>,
    {
        // Yoinked from egui source
        self.on_hover_ui(|ui| {
            // Prevent `Area` auto-sizing from shrinking tooltips with dynamic content.
            // See https://github.com/emilk/egui/issues/5167
            ui.set_max_width(ui.spacing().tooltip_width);

            ui.add(egui::Label::new(text_fun()));
        })
    }
}


================================================
FILE: src/gui/file_ops.rs
================================================
use {
    crate::{
        app::App,
        args::{MmapMode, SourceArgs},
        gui::{message_dialog::MessageDialog, windows::FileDiffResultWindow},
        meta::{ViewKey, region::Region},
        result_ext::AnyhowConv as _,
        shell::{msg_fail, msg_if_fail},
        source::Source,
        util::human_size_u64,
        value_color::{self, ColorMethod},
    },
    anyhow::Context as _,
    egui_file_dialog::FileDialog,
    std::{
        io::Write as _,
        path::{Path, PathBuf},
    },
    strum::IntoEnumIterator as _,
};

struct EntInfo {
    meta: std::io::Result<std::fs::Metadata>,
    mime: Option<&'static str>,
}

type PreviewCache = PathCache<EntInfo>;

pub struct FileOps {
    pub dialog: FileDialog,
    pub op: Option<FileOp>,
    preview_cache: PreviewCache,
    file_dialog_source_args: SourceArgs,
}

impl Default for FileOps {
    fn default() -> Self {
        Self {
            dialog: FileDialog::new()
                .anchor(egui::Align2::CENTER_CENTER, egui::vec2(0., 0.))
                .allow_path_edit_to_save_file_without_extension(true),
            op: Default::default(),
            preview_cache: PathCache::default(),
            file_dialog_source_args: SourceArgs::default(),
        }
    }
}

pub struct PathCache<V> {
    key: PathBuf,
    value: Option<V>,
}

impl<V> Default for PathCache<V> {
    fn default() -> Self {
        Self {
            key: PathBuf::default(),
            value: None,
        }
    }
}

impl<V> PathCache<V> {
    fn get_or_compute<F: FnOnce(&Path) -> V>(&mut self, k: &Path, f: F) -> &V {
        if self.key != k {
            self.key = k.to_path_buf();
            self.value.insert(f(k))
        } else {
            self.value.get_or_insert_with(|| {
                self.key = k.to_path_buf();
                f(k)
            })
        }
    }
}

#[derive(Debug)]
pub enum FileOp {
    LoadMetaFile,
    LoadFile,
    LoadPaletteForView(ViewKey),
    LoadPaletteFromImageForView(ViewKey),
    DiffWithFile,
    LoadLuaScript,
    SavePaletteForView(ViewKey),
    SaveFileAs,
    SaveLuaScript,
    SaveMetaFileAs,
    SaveSelectionToFile(Region),
}

impl FileOps {
    pub fn update(
        &mut self,
        ctx: &egui::Context,
        app: &mut App,
        msg: &mut MessageDialog,
        file_diff_result_window: &mut FileDiffResultWindow,
        font_size: u16,
        line_spacing: u16,
    ) {
        self.dialog.update_with_right_panel_ui(ctx, &mut |ui, dia| {
            let src_args = self
                .op
                .as_ref()
                .is_some_and(|op| matches!(op, FileOp::LoadFile))
                .then_some(&mut self.file_dialog_source_args);
            right_panel_ui(ui, dia, &mut self.preview_cache, src_args);
        });
        if let Some(path) = self.dialog.take_picked()
            && let Some(op) = self.op.take()
        {
            match op {
                FileOp::LoadMetaFile => {
                    msg_if_fail(
                        app.consume_meta_from_file(path, false),
                        "Failed to load metafile",
                        msg,
                    );
                }
                FileOp::LoadFile => {
                    self.file_dialog_source_args.file = Some(path);
                    app.load_file_args(
                        self.file_dialog_source_args.clone(),
                        None,
                        msg,
                        font_size,
                        line_spacing,
                        None,
                    );
                }
                FileOp::LoadPaletteForView(key) => match value_color::load_palette(&path) {
                    Ok(pal) => {
                        let view = &mut app.meta_state.meta.views[key].view;
                        view.presentation.color_method = ColorMethod::Custom(Box::new(pal));
                    }
                    Err(e) => msg_fail(&e, "Failed to load pal", msg),
                },
                FileOp::LoadPaletteFromImageForView(key) => {
                    let view = &mut app.meta_state.meta.views[key].view;
                    let ColorMethod::Custom(pal) = &mut view.presentation.color_method else {
                        return;
                    };
                    let result = try {
                        let img = image::open(path).context("Failed to load image")?.to_rgb8();
                        let (width, height) = (img.width(), img.height());
                        let sel = app.hex_ui.selection().context("Missing app selection")?;
                        let mut i = 0;
                        for y in 0..height {
                            for x in 0..width {
                                let &image::Rgb(rgb) = img.get_pixel(x, y);
                                let Some(byte) = app.data.get(sel.begin + i) else {
                                    break;
                                };
                                pal.0[*byte as usize] = rgb;
                                i += 1;
                            }
                        }
                    };
                    msg_if_fail(result, "Failed to load palette from reference image", msg);
                }
                FileOp::DiffWithFile => {
                    msg_if_fail(
                        app.diff_with_file(path, file_diff_result_window),
                        "Failed to diff",
                        msg,
                    );
                }
                FileOp::LoadLuaScript => {
                    let res = try {
                        app.meta_state.meta.misc.exec_lua_script =
                            std::fs::read_to_string(path).how()?;
                    };
                    msg_if_fail(res, "Failed to load script", msg);
                }
                FileOp::SavePaletteForView(key) => {
                    let view = &mut app.meta_state.meta.views[key].view;
                    let ColorMethod::Custom(pal) = &view.presentation.color_method else {
                        return;
                    };
                    msg_if_fail(
                        value_color::save_palette(pal, &path),
                        "Failed to save pal",
                        msg,
                    );
                }
                FileOp::SaveFileAs => {
                    let result = try {
                        let mut f = std::fs::OpenOptions::new()
                            .create(true)
                            .truncate(true)
                            .read(true)
                            .write(true)
                            .open(&path)
                            .how()?;
                        f.write_all(&app.data).how()?;
                        app.source = Some(Source::file(f));
                        app.src_args.file = Some(path);
                        app.cfg.recent.use_(SourceArgs {
                            file: app.src_args.file.clone(),
                            jump: None,
                            hard_seek: None,
                            take: None,
                            read_only: false,
                            stream: false,
                            stream_buffer_size: None,
                            unsafe_mmap: None,
                            mmap_len: None,
                        });
                    };
                    msg_if_fail(result, "Failed to save as", msg);
                }
                FileOp::SaveLuaScript => {
                    msg_if_fail(
                        std::fs::write(path, &app.meta_state.meta.misc.exec_lua_script),
                        "Failed to save script",
                        msg,
                    );
                }
                FileOp::SaveMetaFileAs => {
                    msg_if_fail(
                        app.save_meta_to_file(path, false),
                        "Failed to save metafile",
                        msg,
                    );
                }
                FileOp::SaveSelectionToFile(sel) => {
                    let result = std::fs::write(path, &app.data[sel.begin..=sel.end]);
                    msg_if_fail(result, "Failed to save selection to file", msg);
                }
            }
        }
    }
    pub fn load_file(&mut self, source_file: Option<&Path>) {
        if let Some(path) = source_file
            && let Some(parent) = path.parent()
        {
            let cfg = self.dialog.config_mut();
            parent.clone_into(&mut cfg.initial_directory);
        }
        self.dialog.pick_file();
        self.op = Some(FileOp::LoadFile);
    }
    pub fn load_meta_file(&mut self) {
        self.dialog.pick_file();
        self.op = Some(FileOp::LoadMetaFile);
    }

    pub fn load_palette_for_view(&mut self, key: ViewKey) {
        self.dialog.pick_file();
        self.op = Some(FileOp::LoadPaletteForView(key));
    }

    pub fn load_palette_from_image_for_view(&mut self, view_key: ViewKey) {
        self.dialog.pick_file();
        self.op = Some(FileOp::LoadPaletteFromImageForView(view_key));
    }

    pub fn diff_with_file(&mut self, source_file: Option<&Path>) {
        if let Some(path) = source_file
            && let Some(parent) = path.parent()
        {
            self.dialog.config_mut().initial_directory = parent.to_owned();
        }
        self.dialog.pick_file();
        self.op = Some(FileOp::DiffWithFile);
    }

    pub fn load_lua_script(&mut self) {
        self.dialog.pick_file();
        self.op = Some(FileOp::LoadLuaScript);
    }

    pub fn save_palette_for_view(&mut self, view_key: ViewKey) {
        self.dialog.save_file();
        self.op = Some(FileOp::SavePaletteForView(view_key));
    }

    pub(crate) fn save_file_as(&mut self) {
        self.dialog.save_file();
        self.op = Some(FileOp::SaveFileAs);
    }

    pub(crate) fn save_lua_script(&mut self) {
        self.dialog.save_file();
        self.op = Some(FileOp::SaveLuaScript);
    }

    pub(crate) fn save_metafile_as(&mut self) {
        self.dialog.save_file();
        self.op = Some(FileOp::SaveMetaFileAs);
    }

    pub(crate) fn save_selection_to_file(&mut self, region: Region) {
        self.dialog.save_file();
        self.op = Some(FileOp::SaveSelectionToFile(region));
    }
}

fn right_panel_ui(
    ui: &mut egui::Ui,
    dia: &FileDialog,
    preview_cache: &mut PreviewCache,
    src_args: Option<&mut SourceArgs>,
) {
    ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate);
    if let Some(highlight) = dia.selected_entry() {
        if let Some(parent) = highlight.as_path().parent() {
            ui.label(egui::RichText::new(parent.display().to_string()).small());
        }
        if let Some(filename) = highlight.as_path().file_name() {
            ui.label(filename.to_string_lossy());
        }
        ui.separator();
        let ent_info = preview_cache.get_or_compute(highlight.as_path(), |path| EntInfo {
            meta: std::fs::metadata(path),
            mime: tree_magic_mini::from_filepath(path),
        });
        if let Some(mime) = ent_info.mime {
            ui.label(mime);
        }
        match &ent_info.meta {
            Ok(meta) => {
                let ft = meta.file_type();
                if ft.is_file() {
                    ui.label(format!("Size: {}", human_size_u64(meta.len())));
                }
                if ft.is_symlink() {
                    ui.label("Symbolic link");
                }
                if !(ft.is_file() || ft.is_dir()) {
                    ui.label(format!("Special (size: {})", meta.len()));
                }
            }
            Err(e) => {
                ui.label(e.to_string());
            }
        }
        ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
        if ui.button("📋 Copy path to clipboard").clicked() {
            ui.ctx().copy_text(highlight.as_path().display().to_string());
        }
    } else {
        ui.heading("Hexerator");
    }
    ui.separator();
    if let Some(src_args) = src_args {
        src_args_ui(ui, src_args);
    }
}

fn src_args_ui(ui: &mut egui::Ui, src_args: &mut SourceArgs) {
    opt(
        ui,
        &mut src_args.jump,
        "jump",
        "Jump to offset on startup",
        |ui, jump| {
            ui.add(egui::DragValue::new(jump));
        },
    );
    opt(
        ui,
        &mut src_args.hard_seek,
        "hard seek",
        "Seek to offset, consider it beginning of the file in the editor",
        |ui, hard_seek| {
            ui.add(egui::DragValue::new(hard_seek));
        },
    );
    opt(
        ui,
        &mut src_args.take,
        "take",
        "Read only this many bytes",
        |ui, take| {
            ui.add(egui::DragValue::new(take));
        },
    );
    ui.checkbox(&mut src_args.read_only, "read-only")
        .on_hover_text("Open file as read-only");
    if ui
        .checkbox(&mut src_args.stream, "stream")
        .on_hover_text(
            "Specify source as a streaming source (for example, standard streams).\n\
             Sets read-only attribute",
        )
        .changed()
    {
        src_args.read_only = src_args.stream;
    }
    ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
    opt(
        ui,
        &mut src_args.unsafe_mmap,
        "⚠ mmap",
        MMAP_LABEL,
        |ui, mode| {
            let label = <&'static str>::from(&*mode);
            egui::ComboBox::new("mmap_cbox", "mode").selected_text(label).show_ui(ui, |ui| {
                for variant in MmapMode::iter() {
                    let label = <&'static str>::from(&variant);
                    ui.selectable_value(mode, variant, label);
                }
            });
        },
    );
    if src_args.unsafe_mmap == Some(MmapMode::DangerousMut) {
        ui.label(DANGEROUS_MUT_LABEL);
    }
}

const MMAP_LABEL: &str = "Open as memory mapped file\n\
\n\
WARNING
\n\
Memory mapped i/o is inherently unsafe.
To ensure no undefined behavior, make sure you have exclusive access to the file.
There is no warranty for any damage you might cause to your system.
";

const DANGEROUS_MUT_LABEL: &str = "⚠ WARNING ⚠\n\
\n\
File will be opened with a direct mutable memory map.
Any changes made to the file will be IMMEDIATE.
THERE IS NO WAY TO UNDO ANY CHANGES.
";

fn opt<V: Default>(
    ui: &mut egui::Ui,
    val: &mut Option<V>,
    label: &str,
    desc: &str,
    f: impl FnOnce(&mut egui::Ui, &mut V),
) {
    ui.horizontal(|ui| {
        let mut checked = val.is_some();
        ui.checkbox(&mut checked, label).on_hover_text(desc);
        if checked {
            f(ui, val.get_or_insert_with(Default::default));
        } else {
            *val = None;
        }
    });
}


================================================
FILE: src/gui/inspect_panel.rs
================================================
use {
    super::message_dialog::{Icon, MessageDialog},
    crate::{
        app::{App, interact_mode::InteractMode},
        damage_region::DamageRegion,
        result_ext::AnyhowConv as _,
        shell::msg_if_fail,
        view::ViewportVec,
    },
    anyhow::bail,
    egui::Ui,
    slotmap::Key as _,
    std::{array::TryFromSliceError, marker::PhantomData},
    thiserror::Error,
};

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum Format {
    Decimal,
    Hex,
    Bin,
}

impl Format {
    fn label(&self) -> &'static str {
        match self {
            Self::Decimal => "Decimal",
            Self::Hex => "Hex",
            Self::Bin => "Binary",
        }
    }
}

pub struct InspectPanel {
    input_thingies: [Box<dyn InputThingyTrait>; 11],
    /// True if an input thingy was changed by the user. Should update the others
    changed_one: bool,
    big_endian: bool,
    format: Format,
    seek_relativity: SeekRelativity,
    /// Edit buffer for user value for seek relative offset
    seek_user_buf: String,
    /// Computed user offset for seek relative offset
    seek_user_offs: usize,
    /// The value of the cursor on the previous frame. Used to determine when the cursor changes
    pub prev_frame_inspect_offset: usize,
}

/// Relativity of seeking to an offset
#[derive(Clone, Copy, PartialEq)]
enum SeekRelativity {
    /// Absolute offset in the file
    Absolute,
    /// Relative to hard-seek
    HardSeek,
    /// Relative to a user-defined offset
    User,
}
impl SeekRelativity {
    fn label(&self) -> &'static str {
        match self {
            Self::Absolute => "Absolute",
            Self::HardSeek => "Hard seek",
            Self::User => "User",
        }
    }
}

impl std::fmt::Debug for InspectPanel {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("InspectPanel").finish()
    }
}

impl Default for InspectPanel {
    fn default() -> Self {
        Self {
            input_thingies: [
                Box::<InputThingy<i8>>::default(),
                Box::<InputThingy<u8>>::default(),
                Box::<InputThingy<i16>>::default(),
                Box::<InputThingy<u16>>::default(),
                Box::<InputThingy<i32>>::default(),
                Box::<InputThingy<u32>>::default(),
                Box::<InputThingy<i64>>::default(),
                Box::<InputThingy<u64>>::default(),
                Box::<InputThingy<f32>>::default(),
                Box::<InputThingy<f64>>::default(),
                Box::<InputThingy<Ascii>>::default(),
            ],
            changed_one: false,
            big_endian: false,
            format: Format::Decimal,
            seek_relativity: SeekRelativity::Absolute,
            prev_frame_inspect_offset: 0,
            seek_user_buf: String::new(),
            seek_user_offs: 0,
        }
    }
}

trait InputThingyTrait {
    fn update(&mut self, data: &[u8], offset: usize, be: bool, format: Format);
    fn label(&self) -> &'static str;
    fn buf_mut(&mut self) -> &mut String;
    fn write_data(
        &self,
        data: &mut [u8],
        offset: usize,
        be: bool,
        format: Format,
        msg: &mut MessageDialog,
    ) -> Option<DamageRegion>;
}

impl<T: BytesManip> InputThingyTrait for InputThingy<T> {
    fn update(&mut self, data: &[u8], offset: usize, be: bool, format: Format) {
        T::update_buf(&mut self.string, data, offset, be, format);
    }
    fn label(&self) -> &'static str {
        T::label()
    }

    fn buf_mut(&mut self) -> &mut String {
        &mut self.string
    }

    fn write_data(
        &self,
        data: &mut [u8],
        offset: usize,
        be: bool,
        format: Format,
        msg: &mut MessageDialog,
    ) -> Option<DamageRegion> {
        T::convert_and_write(&self.string, data, offset, be, format, msg)
    }
}

#[derive(Error, Debug)]
enum FromBytesError {
    #[error("Error converting from slice")]
    TryFromSlice(#[from] TryFromSliceError),
    #[error("Error indexing slice")]
    SliceIndexError,
}

trait NumBytesManip: std::fmt::Display + Sized {
    type ToBytes: AsRef<[u8]>;
    fn label() -> &'static str;
    fn from_le_bytes(bytes: &[u8]) -> Result<Self, FromBytesError>;
    fn from_be_bytes(bytes: &[u8]) -> Result<Self, FromBytesError>;
    fn to_le_bytes(&self) -> Self::ToBytes;
    fn to_be_bytes(&self) -> Self::ToBytes;
    fn to_hex_string(&self) -> String;
    fn to_bin_string(&self) -> String;
    fn from_str(input: &str, format: Format) -> Result<Self, anyhow::Error>;
}

macro_rules! num_bytes_manip_impl {
    ($t:ty) => {
        impl NumBytesManip for $t {
            type ToBytes = [u8; <$t>::BITS as usize / 8];

            fn label() -> &'static str {
                stringify!($t)
            }

            fn from_le_bytes(bytes: &[u8]) -> Result<Self, FromBytesError> {
                match bytes.get(..<$t>::BITS as usize / 8) {
                    Some(slice) => Ok(Self::from_le_bytes(slice.try_into()?)),
                    None => Err(FromBytesError::SliceIndexError),
                }
            }

            fn from_be_bytes(bytes: &[u8]) -> Result<Self, FromBytesError> {
                match bytes.get(..<$t>::BITS as usize / 8) {
                    Some(slice) => Ok(Self::from_be_bytes(slice.try_into()?)),
                    None => Err(FromBytesError::SliceIndexError),
                }
            }

            fn to_le_bytes(&self) -> Self::ToBytes {
                <$t>::to_le_bytes(*self)
            }

            fn to_be_bytes(&self) -> Self::ToBytes {
                <$t>::to_be_bytes(*self)
            }

            fn to_hex_string(&self) -> String {
                format!("{:x}", self)
            }

            fn to_bin_string(&self) -> String {
                format!("{:0w$b}", self, w = <$t>::BITS as usize)
            }

            fn from_str(input: &str, format: Format) -> Result<Self, anyhow::Error> {
                let this = match format {
                    Format::Decimal => input.parse()?,
                    Format::Hex => Self::from_str_radix(input, 16)?,
                    Format::Bin => Self::from_str_radix(input, 2)?,
                };
                Ok(this)
            }
        }
    };
}

num_bytes_manip_impl!(i8);
num_bytes_manip_impl!(u8);
num_bytes_manip_impl!(i16);
num_bytes_manip_impl!(u16);
num_bytes_manip_impl!(i32);
num_bytes_manip_impl!(u32);
num_bytes_manip_impl!(i64);
num_bytes_manip_impl!(u64);

impl NumBytesManip for f32 {
    type ToBytes = [u8; 32 / 8];

    fn label() -> &'static str {
        "f32"
    }

    fn from_le_bytes(bytes: &[u8]) -> Result<Self, FromBytesError> {
        match bytes.get(..32 / 8) {
            Some(slice) => Ok(Self::from_le_bytes(slice.try_into()?)),
            None => Err(FromBytesError::SliceIndexError),
        }
    }

    fn from_be_bytes(bytes: &[u8]) -> Result<Self, FromBytesError> {
        match bytes.get(..32 / 8) {
            Some(slice) => Ok(Self::from_be_bytes(slice.try_into()?)),
            None => Err(FromBytesError::SliceIndexError),
        }
    }

    fn to_le_bytes(&self) -> Self::ToBytes {
        Self::to_le_bytes(*self)
    }

    fn to_be_bytes(&self) -> Self::ToBytes {
        Self::to_be_bytes(*self)
    }

    fn to_hex_string(&self) -> String {
        "<no hex output>".into()
    }

    fn to_bin_string(&self) -> String {
        "<no bin output>".into()
    }

    fn from_str(input: &str, format: Format) -> Result<Self, anyhow::Error> {
        let this = match format {
            Format::Decimal => input.parse()?,
            Format::Hex => bail!("Float doesn't support parsing hex"),
            Format::Bin => bail!("Float doesn't support parsing bin"),
        };
        Ok(this)
    }
}

impl NumBytesManip for f64 {
    type ToBytes = [u8; 8];

    fn label() -> &'static str {
        "f64"
    }

    fn from_le_bytes(bytes: &[u8]) -> Result<Self, FromBytesError> {
        match bytes.get(..8) {
            Some(slice) => Ok(Self::from_le_bytes(slice.try_into()?)),
            None => Err(FromBytesError::SliceIndexError),
        }
    }

    fn from_be_bytes(bytes: &[u8]) -> Result<Self, FromBytesError> {
        match bytes.get(..8) {
            Some(slice) => Ok(Self::from_be_bytes(slice.try_into()?)),
            None => Err(FromBytesError::SliceIndexError),
        }
    }

    fn to_le_bytes(&self) -> Self::ToBytes {
        Self::to_le_bytes(*self)
    }

    fn to_be_bytes(&self) -> Self::ToBytes {
        Self::to_le_bytes(*self)
    }

    fn to_hex_string(&self) -> String {
        "<no hex output>".into()
    }

    fn to_bin_string(&self) -> String {
        "<no bin output>".into()
    }

    fn from_str(input: &str, format: Format) -> Result<Self, anyhow::Error> {
        let this = match format {
            Format::Decimal => input.parse()?,
            Format::Hex => bail!("Float doesn't support parsing hex"),
            Format::Bin => bail!("Float doesn't support parsing bin"),
        };
        Ok(this)
    }
}

impl<T: NumBytesManip> BytesManip for T {
    fn update_buf(buf: &mut String, data: &[u8], offset: usize, be: bool, format: Format) {
        if let Some(slice) = &data.get(offset..) {
            let result = if be {
                T::from_be_bytes(slice)
            } else {
                T::from_le_bytes(slice)
            };
            *buf = match result {
                Ok(value) => match format {
                    Format::Decimal => value.to_string(),
                    Format::Hex => value.to_hex_string(),
                    Format::Bin => value.to_bin_string(),
                },
                Err(e) => e.to_string(),
            }
        }
    }

    fn label() -> &'static str {
        <Self as NumBytesManip>::label()
    }

    fn convert_and_write(
        buf: &str,
        data: &mut [u8],
        offset: usize,
        be: bool,
        format: Format,
        msg: &mut MessageDialog,
    ) -> Option<DamageRegion> {
        match Self::from_str(buf, format) {
            Ok(this) => {
                let bytes = if be {
                    this.to_be_bytes()
                } else {
                    this.to_le_bytes()
                };
                let range = offset..offset + bytes.as_ref().len();
                match data.get_mut(range.clone()) {
                    Some(slice) => {
                        slice.copy_from_slice(bytes.as_ref());
                        Some(DamageRegion::Range(range))
                    }
                    None => None,
                }
            }
            Err(e) => {
                msg.open(Icon::Error, "Convert error", e.to_string());
                None
            }
        }
    }
}

impl BytesManip for Ascii {
    fn update_buf(buf: &mut String, data: &[u8], offset: usize, _be: bool, _format: Format) {
        if let Some(slice) = &data.get(offset..) {
            let valid_ascii_end = find_valid_ascii_end(slice);
            match String::from_utf8(data[offset..offset + valid_ascii_end].to_vec()) {
                Ok(ascii) => *buf = ascii,
                Err(e) => *buf = format!("[ascii error]: {e}"),
            }
        }
    }

    fn label() -> &'static str {
        "ascii"
    }

    fn convert_and_write(
        buf: &str,
        data: &mut [u8],
        offset: usize,
        _be: bool,
        _format: Format,
        msg: &mut MessageDialog,
    ) -> Option<DamageRegion> {
        let len = buf.len();
        let range = offset..offset + len;
        match data.get_mut(range.clone()) {
            Some(slice) => {
                slice.copy_from_slice(buf.as_bytes());
                Some(DamageRegion::Range(range))
            }
            None => {
                msg.open(
                    Icon::Error,
                    "Convert and write error",
                    "Failed to write data: Out of bounds",
                );
                None
            }
        }
    }
}

struct InputThingy<T> {
    string: String,
    _phantom: PhantomData<T>,
}

impl<T> Default for InputThingy<T> {
    fn default() -> Self {
        Self {
            string: Default::default(),
            _phantom: Default::default(),
        }
    }
}

trait BytesManip {
    fn update_buf(buf: &mut String, data: &[u8], offset: usize, be: bool, format: Format);
    fn label() -> &'static str;
    fn convert_and_write(
        buf: &str,
        data: &mut [u8],
        offset: usize,
        be: bool,
        format: Format,
        msg: &mut MessageDialog,
    ) -> Option<DamageRegion>;
}

struct Ascii;

enum Action {
    GoToOffset(usize),
    AddDirty(DamageRegion),
    JumpForward(usize),
}

pub fn ui(ui: &mut Ui, app: &mut App, gui: &mut crate::gui::Gui, mouse_pos: ViewportVec) {
    if app.hex_ui.current_layout.is_null() {
        ui.label("No active layout");
        return;
    }
    let offset = match app.hex_ui.interact_mode {
        InteractMode::View if !ui.egui_wants_pointer_input() => {
            if let Some((off, _view_idx)) = app.byte_offset_at_pos(mouse_pos.x, mouse_pos.y) {
                let mut add = 0;
                match gui.inspect_panel.seek_relativity {
                    SeekRelativity::Absolute => {}
                    SeekRelativity::HardSeek => {
                        add = app.src_args.hard_seek.unwrap_or(0);
                    }
                    SeekRelativity::User => {
                        add = gui.inspect_panel.seek_user_offs;
                    }
                }
                ui.link(format!("offset: {} (0x{:x})", off + add, off + add))
                    .context_menu(|ui| {
                        if ui.button("Copy to clipboard").clicked() {
                            crate::app::set_clipboard_string(
                                &mut app.clipboard,
                                &mut gui.msg_dialog,
                                &format!("{:x}", off + add),
                            );
                        }
                    });
                off
            } else {
                edit_offset(app, gui, ui)
            }
        }
        _ => edit_offset(app, gui, ui),
    };
    egui::ComboBox::new("seek_rela_cb", "Seek relativity")
        .selected_text(gui.inspect_panel.seek_relativity.label())
        .show_ui(ui, |ui| {
            ui.selectable_value(
                &mut gui.inspect_panel.seek_relativity,
                SeekRelativity::Absolute,
                SeekRelativity::Absolute.label(),
            );
            ui.selectable_value(
                &mut gui.inspect_panel.seek_relativity,
                SeekRelativity::HardSeek,
                SeekRelativity::HardSeek.label(),
            );
            ui.selectable_value(
                &mut gui.inspect_panel.seek_relativity,
                SeekRelativity::User,
                SeekRelativity::User.label(),
            );
        });
    let re = ui.add_enabled(
        gui.inspect_panel.seek_relativity == SeekRelativity::User,
        egui::TextEdit::singleline(&mut gui.inspect_panel.seek_user_buf),
    );
    if re.changed()
        && let Ok(num) = gui.inspect_panel.seek_user_buf.parse()
    {
        gui.inspect_panel.seek_user_offs = num;
    }
    if app.data.is_empty() {
        return;
    }
    for thingy in &mut gui.inspect_panel.input_thingies {
        thingy.update(
            &app.data[..],
            offset,
            gui.inspect_panel.big_endian,
            gui.inspect_panel.format,
        );
    }
    gui.inspect_panel.changed_one = false;
    let mut actions = Vec::new();
    for thingy in &mut gui.inspect_panel.input_thingies {
        ui.horizontal(|ui| {
            ui.label(thingy.label());
            if ui.button("📋").on_hover_text("copy to clipboard").clicked() {
                crate::app::set_clipboard_string(
                    &mut app.clipboard,
                    &mut gui.msg_dialog,
                    thingy.buf_mut(),
                );
            }
            if ui.button("⬇").on_hover_text("go to offset").clicked() {
                let result = try {
                    let offset = match gui.inspect_panel.format {
                        Format::Decimal => thingy.buf_mut().parse().how()?,
                        Format::Hex => usize::from_str_radix(thingy.buf_mut(), 16).how()?,
                        Format::Bin => usize::from_str_radix(thingy.buf_mut(), 2).how()?,
                    };
                    actions.push(Action::GoToOffset(offset));
                };
                msg_if_fail(result, "Failed to go to offset", &mut gui.msg_dialog);
            }
            if ui.button("➡").on_hover_text("jump forward").clicked() {
                let result = try {
                    let offset = match gui.inspect_panel.format {
                        Format::Decimal => thingy.buf_mut().parse().how()?,
                        Format::Hex => usize::from_str_radix(thingy.buf_mut(), 16).how()?,
                        Format::Bin => usize::from_str_radix(thingy.buf_mut(), 2).how()?,
                    };
                    actions.push(Action::JumpForward(offset));
                };
                msg_if_fail(result, "Failed to jump forward", &mut gui.msg_dialog);
            }
        });
        if ui.text_edit_singleline(thingy.buf_mut()).lost_focus()
            && ui.input(|inp| inp.key_pressed(egui::Key::Enter))
            && let Some(range) = thingy.write_data(
                &mut app.data,
                offset,
                gui.inspect_panel.big_endian,
                gui.inspect_panel.format,
                &mut gui.msg_dialog,
            )
        {
            gui.inspect_panel.changed_one = true;
            actions.push(Action::AddDirty(range));
        }
    }
    ui.horizontal(|ui| {
        if ui.checkbox(&mut gui.inspect_panel.big_endian, "Big endian").clicked() {
            // Changing this should refresh everything
            gui.inspect_panel.changed_one = true;
        }
        let prev_fmt = gui.inspect_panel.format;
        egui::ComboBox::new("format_combo", "format")
            .selected_text(gui.inspect_panel.format.label())
            .show_ui(ui, |ui| {
                ui.selectable_value(
                    &mut gui.inspect_panel.format,
                    Format::Decimal,
                    Format::Decimal.label(),
                );
                ui.selectable_value(
                    &mut gui.inspect_panel.format,
                    Format::Hex,
                    Format::Hex.label(),
                );
                ui.selectable_value(
                    &mut gui.inspect_panel.format,
                    Format::Bin,
                    Format::Bin.label(),
                );
            });

        if gui.inspect_panel.format != prev_fmt {
            // Changing the format should refresh everything
            gui.inspect_panel.changed_one = true;
        }
    });

    for action in actions {
        match action {
            Action::GoToOffset(offset) => {
                match gui.inspect_panel.seek_relativity {
                    SeekRelativity::Absolute => {
                        app.edit_state.set_cursor(offset);
                    }
                    SeekRelativity::HardSeek => {
                        app.edit_state.set_cursor(offset - app.src_args.hard_seek.unwrap_or(0));
                    }
                    SeekRelativity::User => {
                        app.edit_state.set_cursor(offset - gui.inspect_panel.seek_user_offs);
                    }
                }
                app.center_view_on_offset(app.edit_state.cursor);
                app.hex_ui.flash_cursor();
            }
            Action::AddDirty(damage) => app.data.widen_dirty_region(damage),
            Action::JumpForward(amount) => {
                app.edit_state.set_cursor(app.edit_state.cursor + amount);
                app.center_view_on_offset(app.edit_state.cursor);
                app.hex_ui.flash_cursor();
            }
        }
    }
    gui.inspect_panel.prev_frame_inspect_offset = offset;
}

fn edit_offset(app: &mut App, gui: &mut crate::gui::Gui, ui: &mut Ui) -> usize {
    let mut off = app.edit_state.cursor;
    match gui.inspect_panel.seek_relativity {
        SeekRelativity::Absolute => {}
        SeekRelativity::HardSeek => {
            off += app.src_args.hard_seek.unwrap_or(0);
        }
        SeekRelativity::User => {
            off += gui.inspect_panel.seek_user_offs;
        }
    }
    ui.link(format!("offset: {off} ({off:x}h)")).context_menu(|ui| {
        if ui.button("Copy to clipboard").clicked() {
            crate::app::set_clipboard_string(
                &mut app.clipboard,
                &mut gui.msg_dialog,
                &format!("{off:x}"),
            );
        }
    });
    app.edit_state.cursor
}

fn find_valid_ascii_end(data: &[u8]) -> usize {
    // Don't try to take too many characters, as that degrades performance
    const MAX_TAKE: usize = 50;
    data.iter()
        .take(MAX_TAKE)
        .position(|&b| b == 0 || b > 127)
        .unwrap_or_else(|| std::cmp::min(MAX_TAKE, data.len()))
}


================================================
FILE: src/gui/message_dialog.rs
================================================
use {
    crate::app::command::CommandQueue,
    core::f32,
    egui::Color32,
    std::{backtrace::Backtrace, collections::VecDeque},
};

#[derive(Default)]
pub struct MessageDialog {
    payloads: VecDeque<Payload>,
}

pub struct Payload {
    pub title: String,
    pub desc: String,
    pub icon: Icon,
    pub buttons_ui_fn: Option<Box<UiFn>>,
    pub backtrace: Option<Backtrace>,
    pub show_backtrace: bool,
    pub close: bool,
}

#[derive(Default)]
pub enum Icon {
    #[default]
    None,
    Info,
    Warn,
    Error,
}

pub(crate) type UiFn = dyn FnMut(&mut egui::Ui, &mut Payload, &mut CommandQueue);

// Colors and icon text are copied from egui-toast, for visual consistency
// https://github.com/urholaukkarinen/egui-toast
impl Icon {
    fn color(&self) -> Color32 {
        match self {
            Self::None => Color32::default(),
            Self::Info => Color32::from_rgb(0, 155, 255),
            Self::Warn => Color32::from_rgb(255, 212, 0),
            Self::Error => Color32::from_rgb(255, 32, 0),
        }
    }
    fn utf8(&self) -> &'static str {
        match self {
            Self::None => "",
            Self::Info => "ℹ",
            Self::Warn => "⚠",
            Self::Error => "❗",
        }
    }
    fn hover_text(&self) -> String {
        let label = match self {
            Self::None => "",
            Self::Info => "Info",
            Self::Warn => "Warning",
            Self::Error => "Error",
        };
        format!("{label}\n\nClick to copy message to clipboard")
    }
    fn is_set(&self) -> bool {
        !matches!(self, Self::None)
    }
}

impl MessageDialog {
    pub(crate) fn open(&mut self, icon: Icon, title: impl Into<String>, desc: impl Into<String>) {
        self.payloads.push_back(Payload {
            title: title.into(),
            desc: desc.into(),
            icon,
            buttons_ui_fn: None,
            backtrace: None,
            show_backtrace: false,
            close: false,
        });
    }
    pub(crate) fn custom_button_row_ui(&mut self, f: Box<UiFn>) {
        if let Some(front) = self.payloads.front_mut() {
            front.buttons_ui_fn = Some(f);
        }
    }
    pub(crate) fn show(
        &mut self,
        ctx: &egui::Context,
        cb: &mut arboard::Clipboard,
        cmd: &mut CommandQueue,
    ) {
        let payloads_len = self.payloads.len();
        let Some(payload) = self.payloads.front_mut() else {
            return;
        };
        let mut close = false;
        egui::Modal::new("msg_dialog_popup".into()).show(ctx, |ui| {
            ui.horizontal(|ui| {
                ui.heading(&payload.title);
                if payloads_len > 1 {
                    ui.label(format!("({} more)", payloads_len - 1));
                }
            });
            ui.vertical_centered_justified(|ui| {
                ui.horizontal(|ui| {
                    if payload.icon.is_set()
                        && ui
                            .add(
                                egui::Label::new(
                                    egui::RichText::new(payload.icon.utf8())
                                        .color(payload.icon.color())
                                        .size(32.0),
                                )
                                .sense(egui::Sense::click()),
                            )
                            .on_hover_text(payload.icon.hover_text())
                            .clicked()
                        && let Err(e) = cb.set_text(payload.desc.clone())
                    {
                        gamedebug_core::per!("Clipboard set error: {e:?}");
                    }
                    ui.label(&payload.desc);
                });
                if let Some(bt) = &payload.backtrace {
                    ui.with_layout(egui::Layout::top_down(egui::Align::Min), |ui| {
                        ui.checkbox(&mut payload.show_backtrace, "Show backtrace");
                        if payload.show_backtrace {
                            let bt = bt.to_string();
                            egui::ScrollArea::both().max_height(300.0).show(ui, |ui| {
                                ui.add(
                                    egui::TextEdit::multiline(&mut bt.as_str())
                                        .code_editor()
                                        .desired_width(f32::INFINITY),
                                );
                            });
                        }
                    });
                }
                let (enter_pressed, esc_pressed) = ui.input_mut(|inp| {
                    (
                        // Consume enter and escape, so when the dialog is closed
                        // using these keys, the normal UI won't receive these keys right away.
                        // Receiving the keys could for example cause a text parse box
                        // that parses on enter press to parse again right away with the
                        // same error when the message box is closed with enter.
                        inp.consume_key(egui::Modifiers::default(), egui::Key::Enter),
                        inp.consume_key(egui::Modifiers::default(), egui::Key::Escape),
                    )
                });
                let mut buttons_ui_fn = payload.buttons_ui_fn.take();
                match &mut buttons_ui_fn {
                    Some(f) => f(ui, payload, cmd),
                    None => {
                        if ui.button("Ok").clicked() || enter_pressed || esc_pressed {
                            payload.backtrace = None;
                            close = true;
                        }
                    }
                }
                payload.buttons_ui_fn = buttons_ui_fn;
            });
        });
        if close || payload.close {
            self.payloads.pop_front();
        }
    }
    pub fn set_backtrace_for_top(&mut self, bt: Backtrace) {
        if let Some(front) = self.payloads.front_mut() {
            front.backtrace = Some(bt);
        }
    }
}


================================================
FILE: src/gui/ops.rs
================================================
//! Various common operations that are triggered by gui interactions

use crate::{gui::windows::RegionsWindow, meta::region::Region, meta_state::MetaState};

pub fn add_region_from_selection(
    selection: Region,
    app_meta_state: &mut MetaState,
    gui_regions_window: &mut RegionsWindow,
) {
    let key = app_meta_state.meta.add_region_from_selection(selection);
    gui_regions_window.open.set(true);
    gui_regions_window.selected_key = Some(key);
    gui_regions_window.activate_rename = true;
}


================================================
FILE: src/gui/root_ctx_menu.rs
================================================
use {
    super::Gui
Download .txt
gitextract_lra3dm9g/

├── .github/
│   └── workflows/
│       ├── linux.yml
│       └── windows.yml
├── .gitignore
├── CHANGELOG.md
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── build.rs
├── hexerator-plugin-api/
│   ├── Cargo.toml
│   └── src/
│       └── lib.rs
├── lua/
│   ├── color.lua
│   └── fill.lua
├── plugins/
│   └── hello-world/
│       ├── Cargo.toml
│       └── src/
│           └── lib.rs
├── rust-toolchain.toml
├── rustfmt.toml
├── scripts/
│   └── gen-prim-test-file.rs
├── src/
│   ├── app/
│   │   ├── backend_command.rs
│   │   ├── command.rs
│   │   ├── debug.rs
│   │   ├── edit_state.rs
│   │   ├── interact_mode.rs
│   │   └── presentation.rs
│   ├── app.rs
│   ├── args.rs
│   ├── backend/
│   │   └── sfml.rs
│   ├── backend.rs
│   ├── color.rs
│   ├── config.rs
│   ├── damage_region.rs
│   ├── data.rs
│   ├── dec_conv.rs
│   ├── edit_buffer.rs
│   ├── find_util.rs
│   ├── gui/
│   │   ├── bottom_panel.rs
│   │   ├── command.rs
│   │   ├── dialogs/
│   │   │   ├── auto_save_reload.rs
│   │   │   ├── jump.rs
│   │   │   ├── lua_color.rs
│   │   │   ├── lua_fill.rs
│   │   │   ├── pattern_fill.rs
│   │   │   ├── truncate.rs
│   │   │   └── x86_asm.rs
│   │   ├── dialogs.rs
│   │   ├── egui_ui_ext.rs
│   │   ├── file_ops.rs
│   │   ├── inspect_panel.rs
│   │   ├── message_dialog.rs
│   │   ├── ops.rs
│   │   ├── root_ctx_menu.rs
│   │   ├── selection_menu.rs
│   │   ├── top_menu/
│   │   │   ├── analysis.rs
│   │   │   ├── cursor.rs
│   │   │   ├── edit.rs
│   │   │   ├── file.rs
│   │   │   ├── help.rs
│   │   │   ├── meta.rs
│   │   │   ├── perspective.rs
│   │   │   ├── plugins.rs
│   │   │   ├── scripting.rs
│   │   │   └── view.rs
│   │   ├── top_menu.rs
│   │   ├── top_panel.rs
│   │   ├── windows/
│   │   │   ├── about.rs
│   │   │   ├── bookmarks.rs
│   │   │   ├── debug.rs
│   │   │   ├── external_command.rs
│   │   │   ├── file_diff_result.rs
│   │   │   ├── find_dialog.rs
│   │   │   ├── find_memory_pointers.rs
│   │   │   ├── layouts.rs
│   │   │   ├── lua_console.rs
│   │   │   ├── lua_editor.rs
│   │   │   ├── lua_help.rs
│   │   │   ├── lua_watch.rs
│   │   │   ├── meta_diff.rs
│   │   │   ├── open_process.rs
│   │   │   ├── perspectives.rs
│   │   │   ├── preferences.rs
│   │   │   ├── regions.rs
│   │   │   ├── script_manager.rs
│   │   │   ├── structs.rs
│   │   │   ├── vars.rs
│   │   │   ├── views.rs
│   │   │   └── zero_partition.rs
│   │   └── windows.rs
│   ├── gui.rs
│   ├── hex_conv.rs
│   ├── hex_ui.rs
│   ├── input.rs
│   ├── layout.rs
│   ├── main.rs
│   ├── meta/
│   │   ├── perspective.rs
│   │   ├── region.rs
│   │   └── value_type.rs
│   ├── meta.rs
│   ├── meta_state.rs
│   ├── parse_radix.rs
│   ├── plugin.rs
│   ├── result_ext.rs
│   ├── scripting.rs
│   ├── session_prefs.rs
│   ├── shell.rs
│   ├── slice_ext.rs
│   ├── source.rs
│   ├── str_ext.rs
│   ├── struct_meta_item.rs
│   ├── timer.rs
│   ├── update.rs
│   ├── util.rs
│   ├── value_color.rs
│   ├── view/
│   │   └── draw.rs
│   ├── view.rs
│   └── windows.rs
└── test_files/
    ├── empty-file
    └── plaintext.txt
Download .txt
SYMBOL INDEX (982 symbols across 98 files)

FILE: build.rs
  function main (line 6) | fn main() -> Result<(), Box<dyn Error>> {

FILE: hexerator-plugin-api/src/lib.rs
  type Plugin (line 1) | pub trait Plugin {
    method name (line 2) | fn name(&self) -> &str;
    method desc (line 3) | fn desc(&self) -> &str;
    method methods (line 4) | fn methods(&self) -> Vec<PluginMethod>;
    method on_method_called (line 5) | fn on_method_called(
  type MethodResult (line 13) | pub type MethodResult = Result<Option<Value>, String>;
  type PluginMethod (line 15) | pub struct PluginMethod {
  type MethodParam (line 22) | pub struct MethodParam {
  type ValueTy (line 27) | pub enum ValueTy {
    method label (line 39) | pub fn label(&self) -> &'static str {
  type Value (line 32) | pub enum Value {
  type HexeratorHandle (line 47) | pub trait HexeratorHandle {
    method selection_range (line 48) | fn selection_range(&self) -> Option<[usize; 2]>;
    method get_data (line 49) | fn get_data(&self, start: usize, end: usize) -> Option<&[u8]>;
    method get_data_mut (line 50) | fn get_data_mut(&mut self, start: usize, end: usize) -> Option<&mut [u...
    method debug_log (line 51) | fn debug_log(&self, msg: &str);
    method perspective (line 52) | fn perspective(&self, name: &str) -> Option<PerspectiveHandle>;
    method perspective_rows (line 53) | fn perspective_rows(&self, ph: &PerspectiveHandle) -> Vec<&[u8]>;
  type PerspectiveHandle (line 56) | pub struct PerspectiveHandle {
    method rows (line 61) | pub fn rows<'hx>(&self, hx: &'hx dyn HexeratorHandle) -> Vec<&'hx [u8]> {

FILE: plugins/hello-world/src/lib.rs
  type HelloPlugin (line 7) | struct HelloPlugin;
  method name (line 10) | fn name(&self) -> &str {
  method desc (line 14) | fn desc(&self) -> &str {
  method methods (line 18) | fn methods(&self) -> Vec<hexerator_plugin_api::PluginMethod> {
  method on_method_called (line 50) | fn on_method_called(
  function hexerator_plugin_new (line 89) | pub extern "Rust" fn hexerator_plugin_new() -> Box<dyn Plugin> {

FILE: scripts/gen-prim-test-file.rs
  function main (line 5) | fn main() {

FILE: src/app.rs
  type App (line 57) | pub struct App {
    method reload (line 84) | pub fn reload(&mut self) -> anyhow::Result<()> {
    method load_file_args (line 106) | pub(crate) fn load_file_args(
    method save (line 161) | pub fn save(&mut self, msg: &mut MessageDialog) -> anyhow::Result<()> {
    method save_truncated_file_finish (line 236) | pub fn save_truncated_file_finish(&mut self) -> anyhow::Result<()> {
    method source_file (line 249) | pub(crate) fn source_file(&self) -> Option<&Path> {
    method load_file (line 252) | pub(crate) fn load_file(
    method close_file (line 280) | pub fn close_file(&mut self) {
    method backup_path (line 287) | pub(crate) fn backup_path(&self) -> Option<PathBuf> {
    method restore_backup (line 295) | pub(crate) fn restore_backup(&mut self) -> Result<(), anyhow::Error> {
    method create_backup (line 303) | pub(crate) fn create_backup(&self) -> Result<(), anyhow::Error> {
    method reload_visible (line 311) | fn reload_visible(&mut self) -> anyhow::Result<()> {
    method reload_range (line 315) | pub fn reload_range(&mut self, lo: usize, hi: usize) -> anyhow::Result...
    method load_proc_memory (line 340) | pub(crate) fn load_proc_memory(
    method set_new_clean_meta (line 385) | pub fn set_new_clean_meta(
    method clear_meta (line 407) | pub fn clear_meta(&mut self, font_size: u16, line_spacing: u16) {
    method save_temp_metafile_backup (line 418) | pub fn save_temp_metafile_backup(&mut self) -> anyhow::Result<()> {
    method save_meta_to_file (line 426) | pub fn save_meta_to_file(&mut self, path: PathBuf, temp: bool) -> Resu...
    method save_meta (line 435) | pub fn save_meta(&mut self) -> Result<(), anyhow::Error> {
    method consume_meta_from_file (line 438) | pub fn consume_meta_from_file(
    method add_perspective_from_region (line 460) | pub fn add_perspective_from_region(
    method search_focus (line 475) | pub fn search_focus(&mut self, offset: usize) {
    method center_view_on_offset (line 480) | pub(crate) fn center_view_on_offset(&mut self, offset: usize) {
    method cursor_history_back (line 489) | pub fn cursor_history_back(&mut self) {
    method cursor_history_forward (line 495) | pub fn cursor_history_forward(&mut self) {
    method set_cursor_init (line 502) | pub(crate) fn set_cursor_init(&mut self) {
    method switch_layout (line 507) | pub(crate) fn switch_layout(app_hex_ui: &mut HexUi, app_meta: &Meta, k...
    method switch_layout_by_name (line 516) | pub(crate) fn switch_layout_by_name(
    method focus_first_view_of_name (line 532) | pub(crate) fn focus_first_view_of_name(
    method focus_prev_view_in_layout (line 546) | pub(crate) fn focus_prev_view_in_layout(&mut self) {
    method focus_next_view_in_layout (line 562) | pub(crate) fn focus_next_view_in_layout(&mut self) {
    method focus_first_view_of_key (line 578) | pub(crate) fn focus_first_view_of_key(
    method inc_cols (line 596) | pub(crate) fn inc_cols(&mut self) {
    method dec_cols (line 599) | pub(crate) fn dec_cols(&mut self) {
    method halve_cols (line 602) | pub(crate) fn halve_cols(&mut self) {
    method double_cols (line 605) | pub(crate) fn double_cols(&mut self) {
    method col_change_impl (line 608) | fn col_change_impl(&mut self, f: impl FnOnce(&mut usize)) {
    method byte_offset_at_pos (line 628) | pub fn byte_offset_at_pos(&self, x: i16, y: i16) -> Option<(usize, Vie...
    method view_byte_offset_at_pos (line 637) | pub fn view_byte_offset_at_pos(&self, view_key: ViewKey, x: i16, y: i1...
    method view_at_pos (line 653) | pub fn view_at_pos(&self, x: ViewportScalar, y: ViewportScalar) -> Opt...
    method view_idx_at_pos (line 665) | pub fn view_idx_at_pos(&self, x: i16, y: i16) -> Option<ViewKey> {
    method active_views (line 676) | fn active_views(&self) -> impl Iterator<Item = &'_ NamedView> {
    method visible_byte_range (line 681) | fn visible_byte_range(&self) -> [usize; 2] {
    method focused_view_mut (line 696) | pub(crate) fn focused_view_mut(&mut self) -> Option<(ViewKey, &mut Vie...
    method row_region (line 701) | pub(crate) fn row_region(&self, row: usize) -> Option<Region> {
    method col_offsets (line 717) | pub(crate) fn col_offsets(&self, col: usize) -> Option<Vec<usize>> {
    method cursor_col_offsets (line 727) | pub(crate) fn cursor_col_offsets(&self) -> Option<Vec<usize>> {
    method row_col_of_byte_pos (line 731) | pub(crate) fn row_col_of_byte_pos(&self, pos: usize) -> Option<[usize;...
    method byte_pos_of_row_col (line 736) | pub(crate) fn byte_pos_of_row_col(&self, row: usize, col: usize) -> Op...
    method row_col_of_cursor (line 742) | pub(crate) fn row_col_of_cursor(&self) -> Option<[usize; 2]> {
    method focused_perspective (line 745) | pub fn focused_perspective<'a>(hex_ui: &HexUi, meta: &'a Meta) -> Opti...
    method focused_region (line 751) | pub fn focused_region<'a>(hex_ui: &HexUi, meta: &'a Meta) -> Option<&'...
    method region_key_for_view (line 755) | pub(crate) fn region_key_for_view(&self, view_key: ViewKey) -> RegionK...
    method find_row_start (line 760) | pub(crate) fn find_row_start(&self, offset: usize) -> Option<usize> {
    method find_row_end (line 767) | pub(crate) fn find_row_end(&self, offset: usize) -> Option<usize> {
    method mod_byte_at_cursor (line 800) | pub(crate) fn mod_byte_at_cursor(&mut self, f: impl FnOnce(&mut u8)) {
    method inc_byte_at_cursor (line 807) | pub(crate) fn inc_byte_at_cursor(&mut self) {
    method dec_byte_at_cursor (line 811) | pub(crate) fn dec_byte_at_cursor(&mut self) {
    method inc_byte_or_bytes (line 815) | pub(crate) fn inc_byte_or_bytes(&mut self) {
    method dec_byte_or_bytes (line 828) | pub(crate) fn dec_byte_or_bytes(&mut self) {
    method new (line 844) | pub(crate) fn new(
    method reoffset_bookmarks_cursor_diff (line 950) | pub(crate) fn reoffset_bookmarks_cursor_diff(&mut self, offset: usize) {
    method try_read_stream (line 961) | pub(crate) fn try_read_stream(&mut self) {
    method update (line 1023) | pub(crate) fn update(
    method focused_view_select_all (line 1079) | pub(crate) fn focused_view_select_all(&mut self) {
    method focused_view_select_row (line 1090) | pub(crate) fn focused_view_select_row(&mut self) {
    method focused_view_select_col (line 1101) | pub(crate) fn focused_view_select_col(&mut self) {
    method diff_with_file (line 1119) | pub(crate) fn diff_with_file(
    method call_plugin_method (line 1150) | pub(crate) fn call_plugin_method(
    method remove_dangling (line 1169) | pub(crate) fn remove_dangling(&mut self) {
  constant DEFAULT_STREAM_BUFFER_SIZE (line 80) | const DEFAULT_STREAM_BUFFER_SIZE: usize = 65_536;
  constant DEFAULT_COLUMN_COUNT (line 380) | const DEFAULT_COLUMN_COUNT: usize = 48;
  function calc_perspective_row_col (line 781) | fn calc_perspective_row_col(pos: usize, per: &Perspective, regions: &Reg...
  function calc_perspective_row_col_offset (line 788) | fn calc_perspective_row_col_offset(
  function setup_empty_meta (line 1183) | pub fn setup_empty_meta(
  function get_clipboard_string (line 1216) | pub fn get_clipboard_string(cb: &mut arboard::Clipboard, msg: &mut Messa...
  function set_clipboard_string (line 1230) | pub fn set_clipboard_string(cb: &mut arboard::Clipboard, msg: &mut Messa...
  function load_proc_memory_linux (line 1235) | fn load_proc_memory_linux(
  function load_proc_memory_macos (line 1266) | fn load_proc_memory_macos(
  function read_source_to_buf (line 1295) | pub fn read_source_to_buf(path: &Path, args: &SourceArgs) -> Result<Vec<...
  function temp_metafile_backup_path (line 1314) | pub fn temp_metafile_backup_path() -> PathBuf {
  function col_change_impl_view_perspective (line 1318) | pub fn col_change_impl_view_perspective(
  function default_views (line 1332) | pub fn default_views(
  function load_file_from_src_args (line 1360) | fn load_file_from_src_args(
  function open_file (line 1455) | fn open_file(path: &Path, read_only: bool) -> std::io::Result<File> {
  function read_contents (line 1459) | pub(crate) fn read_contents(args: &SourceArgs, file: &mut File) -> std::...

FILE: src/app/backend_command.rs
  type BackendCmd (line 10) | pub enum BackendCmd {
  type BackendCommandQueue (line 23) | pub struct BackendCommandQueue {
    method push (line 28) | pub fn push(&mut self, command: BackendCmd) {
  method flush_backend_command_queue (line 38) | pub fn flush_backend_command_queue(&mut self, rw: &mut RenderWindow) {
  function perform_command (line 45) | fn perform_command(cmd: BackendCmd, rw: &mut RenderWindow, cfg: &Config) {

FILE: src/app/command.rs
  type Cmd (line 25) | pub enum Cmd {
  type CommandQueue (line 61) | pub struct CommandQueue {
    method push (line 66) | pub fn push(&mut self, command: Cmd) {
  method flush_command_queue (line 76) | pub fn flush_command_queue(
  function perform_command (line 91) | pub fn perform_command(
  function path_filename_as_str (line 181) | fn path_filename_as_str(path: &Path) -> &str {

FILE: src/app/debug.rs
  method imm_debug_fun (line 9) | pub(crate) fn imm_debug_fun(&self) {

FILE: src/app/edit_state.rs
  type EditState (line 2) | pub struct EditState {
    method set_cursor (line 11) | pub fn set_cursor(&mut self, offset: usize) {
    method set_cursor_no_history (line 18) | pub fn set_cursor_no_history(&mut self, offset: usize) {
    method step_cursor_forward (line 22) | pub fn step_cursor_forward(&mut self) {
    method step_cursor_back (line 26) | pub fn step_cursor_back(&mut self) {
    method offset_cursor (line 30) | pub fn offset_cursor(&mut self, amount: usize) {
    method cursor_history_back (line 33) | pub fn cursor_history_back(&mut self) -> bool {
    method cursor_history_forward (line 43) | pub fn cursor_history_forward(&mut self) -> bool {

FILE: src/app/interact_mode.rs
  type InteractMode (line 5) | pub enum InteractMode {

FILE: src/app/presentation.rs
  type Presentation (line 10) | pub struct Presentation {
  method default (line 19) | fn default() -> Self {

FILE: src/args.rs
  type Args (line 10) | pub struct Args {
  type SourceArgs (line 63) | pub struct SourceArgs {
  type MmapMode (line 110) | pub enum MmapMode {

FILE: src/backend/sfml.rs
  method from (line 10) | fn from(Color { r, g, b, a }: Color) -> Self {
  method from (line 16) | fn from(RgbaColor { r, g, b, a }: RgbaColor) -> Self {
  method from (line 22) | fn from(src: RgbColor) -> Self {
  type Error (line 33) | type Error = <ViewportScalar as TryFrom<i32>>::Error;
  method try_from (line 35) | fn try_from(sf_vec: sf2g::system::Vector2<i32>) -> Result<Self, Self::Er...

FILE: src/color.rs
  type RgbaColor (line 4) | pub struct RgbaColor {
    method with_as_egui_mut (line 11) | pub(crate) fn with_as_egui_mut(&mut self, f: impl FnOnce(&mut egui::Co...
    method from_egui (line 16) | fn from_egui(c: egui::Color32) -> Self {
    method to_egui (line 24) | fn to_egui(self) -> egui::Color32 {
  function rgba (line 29) | pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> RgbaColor {
  type RgbColor (line 34) | pub struct RgbColor {
    constant WHITE (line 41) | pub const WHITE: Self = rgb(255, 255, 255);
    method invert (line 43) | pub fn invert(&self) -> Self {
    method cap_brightness (line 47) | pub(crate) fn cap_brightness(&self, limit: u8) -> Self {
  function rgb (line 56) | pub const fn rgb(r: u8, g: u8, b: u8) -> RgbColor {

FILE: src/config.rs
  type Config (line 15) | pub struct Config {
    method load_or_default (line 97) | pub fn load_or_default() -> anyhow::Result<LoadedConfig> {
    method save (line 126) | pub fn save(&self) -> anyhow::Result<()> {
  type PinnedDir (line 34) | pub struct PinnedDir {
  function default_vsync (line 39) | const fn default_vsync() -> bool {
  type MetaAssocs (line 43) | pub type MetaAssocs = HashMap<PathBuf, PathBuf>;
  type Style (line 46) | pub struct Style {
  type FontSizes (line 51) | pub struct FontSizes {
  method default (line 60) | fn default() -> Self {
  constant DEFAULT_RECENT_CAPACITY (line 71) | const DEFAULT_RECENT_CAPACITY: usize = 16;
  method default (line 74) | fn default() -> Self {
  type LoadedConfig (line 90) | pub struct LoadedConfig {
  function project_dirs (line 135) | pub fn project_dirs() -> Option<ProjectDirs> {
  type ProjectDirsExt (line 139) | pub trait ProjectDirsExt {
    method color_theme_path (line 140) | fn color_theme_path(&self) -> PathBuf;
    method color_theme_path (line 144) | fn color_theme_path(&self) -> PathBuf {
  constant FILENAME (line 149) | const FILENAME: &str = "hexerator.cfg";

FILE: src/damage_region.rs
  type DamageRegion (line 1) | pub enum DamageRegion {
    method begin (line 8) | pub(crate) fn begin(&self) -> usize {
    method end (line 16) | pub(crate) fn end(&self) -> usize {
    method from (line 26) | fn from(range: std::ops::RangeInclusive<usize>) -> Self {

FILE: src/data.rs
  type Data (line 8) | pub struct Data {
    method clean_from_buf (line 33) | pub(crate) fn clean_from_buf(buf: Vec<u8>) -> Self {
    method new_mmap_mut (line 40) | pub(crate) fn new_mmap_mut(mmap: memmap2::MmapMut) -> Self {
    method new_mmap_immut (line 47) | pub(crate) fn new_mmap_immut(mmap: memmap2::Mmap) -> Self {
    method close (line 55) | pub(crate) fn close(&mut self) {
    method widen_dirty_region (line 59) | pub(crate) fn widen_dirty_region(&mut self, damage: DamageRegion) {
    method undirty (line 88) | pub(crate) fn undirty(&mut self) {
    method resize (line 93) | pub(crate) fn resize(&mut self, new_len: usize, value: u8) {
    method extend_from_slice (line 102) | pub(crate) fn extend_from_slice(&mut self, slice: &[u8]) {
    method drain (line 111) | pub(crate) fn drain(&mut self, range: std::ops::Range<usize>) {
    method zero_fill_region (line 122) | pub(crate) fn zero_fill_region(&mut self, region: Region) {
    method reload_from_file (line 130) | pub(crate) fn reload_from_file(
    method mod_range (line 145) | pub(crate) fn mod_range(
  type DataProvider (line 16) | enum DataProvider {
    method fmt (line 23) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  type Target (line 158) | type Target = [u8];
  method deref (line 160) | fn deref(&self) -> &Self::Target {
  method deref_mut (line 171) | fn deref_mut(&mut self) -> &mut Self::Target {

FILE: src/dec_conv.rs
  function byte_10_digits (line 1) | fn byte_10_digits(byte: u8) -> [u8; 3] {
  function test_byte_10_digits (line 6) | fn test_byte_10_digits() {
  function byte_to_dec_digits (line 10) | pub fn byte_to_dec_digits(byte: u8) -> [u8; 3] {
  function test_byte_to_dec_digits (line 18) | fn test_byte_to_dec_digits() {

FILE: src/edit_buffer.rs
  type EditBuffer (line 4) | pub struct EditBuffer {
    method resize (line 12) | pub(crate) fn resize(&mut self, new_size: u16) {
    method enter_byte (line 16) | pub(crate) fn enter_byte(&mut self, byte: u8) -> bool {
    method reset (line 28) | pub fn reset(&mut self) {
    method update_from_string (line 33) | pub(crate) fn update_from_string(&mut self, s: &str) {
    method move_cursor_back (line 38) | pub(crate) fn move_cursor_back(&mut self) -> bool {
    method move_cursor_end (line 51) | pub(crate) fn move_cursor_end(&mut self) {
    method move_cursor_forward (line 60) | pub(crate) fn move_cursor_forward(&mut self) -> bool {
    method move_cursor_begin (line 70) | pub(crate) fn move_cursor_begin(&mut self) {

FILE: src/find_util.rs
  function find_hex_string (line 1) | pub fn find_hex_string(
  type HexStringSepKind (line 13) | enum HexStringSepKind {
  function detect_hex_string_sep_kind (line 19) | fn detect_hex_string_sep_kind(hex_string: &str) -> HexStringSepKind {
  function chunks_2 (line 29) | fn chunks_2(input: &str) -> impl Iterator<Item = anyhow::Result<&str>> {
  function parse_hex_string (line 38) | pub fn parse_hex_string(hex_string: &str) -> anyhow::Result<Vec<u8>> {
  function parse_hex_token (line 50) | fn parse_hex_token(tok: &str) -> anyhow::Result<u8> {
  function test_parse_hex_string (line 55) | fn test_parse_hex_string() {

FILE: src/gui.rs
  constant BOOK_URL (line 50) | const BOOK_URL: &str = "https://crumblingstatue.github.io/hexerator-book...
  type Dialogs (line 52) | type Dialogs = HashMap<TypeId, Box<dyn Dialog>>;
  type HighlightSet (line 54) | pub type HighlightSet = HashSet<usize>;
  type Gui (line 57) | pub struct Gui {
    method add_dialog (line 91) | pub fn add_dialog<D: Dialog + 'static>(gui_dialogs: &mut Dialogs, mut ...
  type Dialog (line 71) | pub trait Dialog {
    method title (line 72) | fn title(&self) -> &str;
    method ui (line 74) | fn ui(
    method on_open (line 84) | fn on_open(&mut self) {}
    method has_close_button (line 85) | fn has_close_button(&self) -> bool {
  function do_egui (line 98) | pub fn do_egui(
  function set_font_sizes_ctx (line 197) | pub fn set_font_sizes_ctx(ctx: &egui::Context, style: &Style) {
  function set_font_sizes_style (line 203) | pub fn set_font_sizes_style(egui_style: &mut egui::Style, style: &Style) {
  function add_new_bookmark (line 229) | fn add_new_bookmark(app: &mut App, gui: &mut Gui, byte_off: usize) {

FILE: src/gui/bottom_panel.rs
  constant L_SCROLL (line 16) | const L_SCROLL: &str = concat!(ic::MOUSE_SCROLL, " scroll");
  function ui (line 18) | pub fn ui(ui: &mut Ui, app: &mut App, mouse_pos: ViewportVec, gui: &mut ...
  function region_label (line 212) | fn region_label(ui: &mut Ui, name: &str) -> egui::Response {
  function key_label (line 220) | fn key_label(ui: &Ui, key_text: &str, label_text: &str) -> LayoutJob {

FILE: src/gui/command.rs
  type GCmd (line 12) | pub enum GCmd {
  type GCommandQueue (line 32) | pub struct GCommandQueue {
    method push (line 37) | pub fn push(&mut self, command: GCmd) {
  method flush_command_queue (line 47) | pub fn flush_command_queue(&mut self) {
  function perform_command (line 54) | fn perform_command(gui: &mut Gui, cmd: GCmd) {

FILE: src/gui/dialogs/auto_save_reload.rs
  type AutoSaveReloadDialog (line 7) | pub struct AutoSaveReloadDialog;
  method title (line 10) | fn title(&self) -> &str {
  method ui (line 14) | fn ui(

FILE: src/gui/dialogs/jump.rs
  type JumpDialog (line 12) | pub struct JumpDialog {
  method title (line 19) | fn title(&self) -> &str {
  method on_open (line 23) | fn on_open(&mut self) {
  method ui (line 27) | fn ui(

FILE: src/gui/dialogs/lua_color.rs
  type LuaColorDialog (line 6) | pub struct LuaColorDialog {
  method default (line 13) | fn default() -> Self {
  method title (line 25) | fn title(&self) -> &str {
  method ui (line 29) | fn ui(

FILE: src/gui/dialogs/lua_fill.rs
  type LuaFillDialog (line 9) | pub struct LuaFillDialog {
  method title (line 15) | fn title(&self) -> &str {
  method ui (line 19) | fn ui(
  method has_close_button (line 92) | fn has_close_button(&self) -> bool {

FILE: src/gui/dialogs/pattern_fill.rs
  type PatternFillDialog (line 13) | pub struct PatternFillDialog {
  method title (line 19) | fn title(&self) -> &str {
  method on_open (line 23) | fn on_open(&mut self) {
  method ui (line 27) | fn ui(
  method has_close_button (line 68) | fn has_close_button(&self) -> bool {

FILE: src/gui/dialogs/truncate.rs
  type TruncateDialog (line 7) | pub struct TruncateDialog {
    method new (line 13) | pub fn new(data_len: usize, selection: Option<Region>) -> Self {
  method title (line 23) | fn title(&self) -> &str {
  method ui (line 27) | fn ui(

FILE: src/gui/dialogs/x86_asm.rs
  type X86AsmDialog (line 8) | pub struct X86AsmDialog {
    method new (line 14) | pub fn new() -> Self {
  method title (line 23) | fn title(&self) -> &str {
  method ui (line 27) | fn ui(
  type DecodedInstr (line 80) | struct DecodedInstr {
  function disasm (line 85) | fn disasm(data: &[u8], bitness: u32) -> Vec<DecodedInstr> {

FILE: src/gui/egui_ui_ext.rs
  type EguiResponseExt (line 1) | pub trait EguiResponseExt {
    method on_hover_text_deferred (line 2) | fn on_hover_text_deferred<F, R>(self, text_fun: F) -> Self
    method on_hover_text_deferred (line 9) | fn on_hover_text_deferred<F, R>(self, text_fun: F) -> Self

FILE: src/gui/file_ops.rs
  type EntInfo (line 22) | struct EntInfo {
  type PreviewCache (line 27) | type PreviewCache = PathCache<EntInfo>;
  type FileOps (line 29) | pub struct FileOps {
    method update (line 93) | pub fn update(
    method load_file (line 234) | pub fn load_file(&mut self, source_file: Option<&Path>) {
    method load_meta_file (line 244) | pub fn load_meta_file(&mut self) {
    method load_palette_for_view (line 249) | pub fn load_palette_for_view(&mut self, key: ViewKey) {
    method load_palette_from_image_for_view (line 254) | pub fn load_palette_from_image_for_view(&mut self, view_key: ViewKey) {
    method diff_with_file (line 259) | pub fn diff_with_file(&mut self, source_file: Option<&Path>) {
    method load_lua_script (line 269) | pub fn load_lua_script(&mut self) {
    method save_palette_for_view (line 274) | pub fn save_palette_for_view(&mut self, view_key: ViewKey) {
    method save_file_as (line 279) | pub(crate) fn save_file_as(&mut self) {
    method save_lua_script (line 284) | pub(crate) fn save_lua_script(&mut self) {
    method save_metafile_as (line 289) | pub(crate) fn save_metafile_as(&mut self) {
    method save_selection_to_file (line 294) | pub(crate) fn save_selection_to_file(&mut self, region: Region) {
  method default (line 37) | fn default() -> Self {
  type PathCache (line 49) | pub struct PathCache<V> {
  method default (line 55) | fn default() -> Self {
  function get_or_compute (line 64) | fn get_or_compute<F: FnOnce(&Path) -> V>(&mut self, k: &Path, f: F) -> &V {
  type FileOp (line 78) | pub enum FileOp {
  function right_panel_ui (line 300) | fn right_panel_ui(
  function src_args_ui (line 352) | fn src_args_ui(ui: &mut egui::Ui, src_args: &mut SourceArgs) {
  constant MMAP_LABEL (line 413) | const MMAP_LABEL: &str = "Open as memory mapped file\n\
  constant DANGEROUS_MUT_LABEL (line 422) | const DANGEROUS_MUT_LABEL: &str = "⚠ WARNING ⚠\n\
  function opt (line 429) | fn opt<V: Default>(

FILE: src/gui/inspect_panel.rs
  type Format (line 18) | enum Format {
    method label (line 25) | fn label(&self) -> &'static str {
  type InspectPanel (line 34) | pub struct InspectPanel {
    method fmt (line 70) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  type SeekRelativity (line 51) | enum SeekRelativity {
    method label (line 60) | fn label(&self) -> &'static str {
  method default (line 76) | fn default() -> Self {
  type InputThingyTrait (line 102) | trait InputThingyTrait {
    method update (line 103) | fn update(&mut self, data: &[u8], offset: usize, be: bool, format: For...
    method label (line 104) | fn label(&self) -> &'static str;
    method buf_mut (line 105) | fn buf_mut(&mut self) -> &mut String;
    method write_data (line 106) | fn write_data(
    method update (line 117) | fn update(&mut self, data: &[u8], offset: usize, be: bool, format: For...
    method label (line 120) | fn label(&self) -> &'static str {
    method buf_mut (line 124) | fn buf_mut(&mut self) -> &mut String {
    method write_data (line 128) | fn write_data(
  type FromBytesError (line 141) | enum FromBytesError {
  type NumBytesManip (line 148) | trait NumBytesManip: std::fmt::Display + Sized {
    method label (line 150) | fn label() -> &'static str;
    method from_le_bytes (line 151) | fn from_le_bytes(bytes: &[u8]) -> Result<Self, FromBytesError>;
    method from_be_bytes (line 152) | fn from_be_bytes(bytes: &[u8]) -> Result<Self, FromBytesError>;
    method to_le_bytes (line 153) | fn to_le_bytes(&self) -> Self::ToBytes;
    method to_be_bytes (line 154) | fn to_be_bytes(&self) -> Self::ToBytes;
    method to_hex_string (line 155) | fn to_hex_string(&self) -> String;
    method to_bin_string (line 156) | fn to_bin_string(&self) -> String;
    method from_str (line 157) | fn from_str(input: &str, format: Format) -> Result<Self, anyhow::Error>;
    type ToBytes (line 221) | type ToBytes = [u8; 32 / 8];
    method label (line 223) | fn label() -> &'static str {
    method from_le_bytes (line 227) | fn from_le_bytes(bytes: &[u8]) -> Result<Self, FromBytesError> {
    method from_be_bytes (line 234) | fn from_be_bytes(bytes: &[u8]) -> Result<Self, FromBytesError> {
    method to_le_bytes (line 241) | fn to_le_bytes(&self) -> Self::ToBytes {
    method to_be_bytes (line 245) | fn to_be_bytes(&self) -> Self::ToBytes {
    method to_hex_string (line 249) | fn to_hex_string(&self) -> String {
    method to_bin_string (line 253) | fn to_bin_string(&self) -> String {
    method from_str (line 257) | fn from_str(input: &str, format: Format) -> Result<Self, anyhow::Error> {
    type ToBytes (line 268) | type ToBytes = [u8; 8];
    method label (line 270) | fn label() -> &'static str {
    method from_le_bytes (line 274) | fn from_le_bytes(bytes: &[u8]) -> Result<Self, FromBytesError> {
    method from_be_bytes (line 281) | fn from_be_bytes(bytes: &[u8]) -> Result<Self, FromBytesError> {
    method to_le_bytes (line 288) | fn to_le_bytes(&self) -> Self::ToBytes {
    method to_be_bytes (line 292) | fn to_be_bytes(&self) -> Self::ToBytes {
    method to_hex_string (line 296) | fn to_hex_string(&self) -> String {
    method to_bin_string (line 300) | fn to_bin_string(&self) -> String {
    method from_str (line 304) | fn from_str(input: &str, format: Format) -> Result<Self, anyhow::Error> {
  type InputThingy (line 411) | struct InputThingy<T> {
  method default (line 417) | fn default() -> Self {
  type BytesManip (line 425) | trait BytesManip {
    method update_buf (line 315) | fn update_buf(buf: &mut String, data: &[u8], offset: usize, be: bool, ...
    method label (line 333) | fn label() -> &'static str {
    method convert_and_write (line 337) | fn convert_and_write(
    method update_buf (line 370) | fn update_buf(buf: &mut String, data: &[u8], offset: usize, _be: bool,...
    method label (line 380) | fn label() -> &'static str {
    method convert_and_write (line 384) | fn convert_and_write(
    method update_buf (line 426) | fn update_buf(buf: &mut String, data: &[u8], offset: usize, be: bool, ...
    method label (line 427) | fn label() -> &'static str;
    method convert_and_write (line 428) | fn convert_and_write(
  type Ascii (line 438) | struct Ascii;
  type Action (line 440) | enum Action {
  function ui (line 446) | pub fn ui(ui: &mut Ui, app: &mut App, gui: &mut crate::gui::Gui, mouse_p...
  function edit_offset (line 629) | fn edit_offset(app: &mut App, gui: &mut crate::gui::Gui, ui: &mut Ui) ->...
  function find_valid_ascii_end (line 652) | fn find_valid_ascii_end(data: &[u8]) -> usize {

FILE: src/gui/message_dialog.rs
  type MessageDialog (line 9) | pub struct MessageDialog {
    method open (line 68) | pub(crate) fn open(&mut self, icon: Icon, title: impl Into<String>, de...
    method custom_button_row_ui (line 79) | pub(crate) fn custom_button_row_ui(&mut self, f: Box<UiFn>) {
    method show (line 84) | pub(crate) fn show(
    method set_backtrace_for_top (line 165) | pub fn set_backtrace_for_top(&mut self, bt: Backtrace) {
  type Payload (line 13) | pub struct Payload {
  type Icon (line 24) | pub enum Icon {
    method color (line 37) | fn color(&self) -> Color32 {
    method utf8 (line 45) | fn utf8(&self) -> &'static str {
    method hover_text (line 53) | fn hover_text(&self) -> String {
    method is_set (line 62) | fn is_set(&self) -> bool {
  type UiFn (line 32) | pub(crate) type UiFn = dyn FnMut(&mut egui::Ui, &mut Payload, &mut Comma...

FILE: src/gui/ops.rs
  function add_region_from_selection (line 5) | pub fn add_region_from_selection(

FILE: src/gui/root_ctx_menu.rs
  constant L_SELECTION (line 8) | const L_SELECTION: &str = concat!(ic::SELECTION, " Selection");
  constant L_REGION_PROPS (line 9) | const L_REGION_PROPS: &str = concat!(ic::RULER, " Region properties...");
  constant L_VIEW_PROPS (line 10) | const L_VIEW_PROPS: &str = concat!(ic::EYE, " View properties...");
  constant L_CHANGE_THIS_VIEW (line 11) | const L_CHANGE_THIS_VIEW: &str = concat!(ic::SWAP, " Change this view to");
  constant L_REMOVE_FROM_LAYOUT (line 12) | const L_REMOVE_FROM_LAYOUT: &str = concat!(ic::TRASH, " Remove from layo...
  constant L_OPEN_BOOKMARK (line 13) | const L_OPEN_BOOKMARK: &str = concat!(ic::BOOKMARK, " Open bookmark");
  constant L_ADD_BOOKMARK (line 14) | const L_ADD_BOOKMARK: &str = concat!(ic::BOOKMARK, " Add bookmark");
  constant L_LAYOUT_PROPS (line 15) | const L_LAYOUT_PROPS: &str = concat!(ic::LAYOUT, " Layout properties...");
  constant L_LAYOUTS (line 16) | const L_LAYOUTS: &str = concat!(ic::LAYOUT, " Layouts");
  type ContextMenu (line 18) | pub struct ContextMenu {
    method new (line 24) | pub fn new(mx: ViewportScalar, my: ViewportScalar, data: ContextMenuDa...
  type ContextMenuData (line 32) | pub struct ContextMenuData {
  function set_menu_style (line 38) | fn set_menu_style(style: &mut egui::Style) {
  function show (line 49) | pub(super) fn show(menu: &ContextMenu, ctx: &egui::Context, app: &mut Ap...
  function menu_inner_ui (line 68) | fn menu_inner_ui(

FILE: src/gui/selection_menu.rs
  constant L_UNSELECT (line 21) | const L_UNSELECT: &str = concat!(ic::SELECTION_SLASH, " Unselect");
  constant L_ZERO_FILL (line 22) | const L_ZERO_FILL: &str = concat!(ic::NUMBER_SQUARE_ZERO, " Zero fill");
  constant L_PATTERN_FILL (line 23) | const L_PATTERN_FILL: &str = concat!(ic::BINARY, " Pattern fill...");
  constant L_LUA_FILL (line 24) | const L_LUA_FILL: &str = concat!(ic::MOON, " Lua fill...");
  constant L_RANDOM_FILL (line 25) | const L_RANDOM_FILL: &str = concat!(ic::SHUFFLE, " Random fill");
  constant L_COPY_AS_HEX_TEXT (line 26) | const L_COPY_AS_HEX_TEXT: &str = concat!(ic::COPY, " Copy as hex text");
  constant L_COPY_AS_UTF8 (line 27) | const L_COPY_AS_UTF8: &str = concat!(ic::COPY, " Copy as utf-8 text");
  constant L_ADD_AS_REGION (line 28) | const L_ADD_AS_REGION: &str = concat!(ic::RULER, " Add as region");
  constant L_SAVE_TO_FILE (line 29) | const L_SAVE_TO_FILE: &str = concat!(ic::FLOPPY_DISK, " Save to file");
  constant L_X86_ASM (line 30) | const L_X86_ASM: &str = concat!(ic::PIPE_WRENCH, " X86 asm");
  function selection_menu (line 33) | pub fn selection_menu(

FILE: src/gui/top_menu.rs
  function top_menu (line 19) | pub fn top_menu(
  function try_open_file (line 151) | fn try_open_file(file: &std::path::Path, gui: &mut super::Gui) {

FILE: src/gui/top_menu/analysis.rs
  constant L_DETERMINE_DATA_MIME (line 11) | const L_DETERMINE_DATA_MIME: &str =
  constant L_DETERMINE_DATA_MIME_SEL (line 13) | const L_DETERMINE_DATA_MIME_SEL: &str =
  constant L_DIFF_WITH_FILE (line 15) | const L_DIFF_WITH_FILE: &str = concat!(ic::GIT_DIFF, " Diff with file...");
  constant L_DIFF_WITH_SOURCE_FILE (line 16) | const L_DIFF_WITH_SOURCE_FILE: &str = concat!(ic::GIT_DIFF, " Diff with ...
  constant L_DIFF_WITH_BACKUP (line 17) | const L_DIFF_WITH_BACKUP: &str = concat!(ic::GIT_DIFF, " Diff with backu...
  constant L_FIND_MEMORY_POINTERS (line 18) | const L_FIND_MEMORY_POINTERS: &str = concat!(ic::ARROW_UP_RIGHT, " Find ...
  constant L_ZERO_PARTITION (line 19) | const L_ZERO_PARTITION: &str = concat!(ic::BINARY, " Zero partition...");
  function ui (line 21) | pub fn ui(ui: &mut egui::Ui, gui: &mut Gui, app: &App) {

FILE: src/gui/top_menu/cursor.rs
  constant L_RESET (line 11) | const L_RESET: &str = concat!(ic::ARROW_U_UP_LEFT, " Reset");
  constant L_JUMP (line 12) | const L_JUMP: &str = concat!(ic::SHARE_FAT, " Jump...");
  constant L_FLASH_CURSOR (line 13) | const L_FLASH_CURSOR: &str = concat!(ic::LIGHTBULB, " Flash cursor");
  constant L_CENTER_VIEW_ON_CURSOR (line 14) | const L_CENTER_VIEW_ON_CURSOR: &str = concat!(ic::CROSSHAIR, " Center vi...
  function ui (line 16) | pub fn ui(ui: &mut egui::Ui, gui: &mut Gui, app: &mut App) {

FILE: src/gui/top_menu/edit.rs
  constant L_FIND (line 17) | const L_FIND: &str = concat!(ic::MAGNIFYING_GLASS, " Find...");
  constant L_SELECTION (line 18) | const L_SELECTION: &str = concat!(ic::SELECTION, " Selection");
  constant L_SELECT_A (line 19) | const L_SELECT_A: &str = "🅰 Set select a";
  constant L_SELECT_B (line 20) | const L_SELECT_B: &str = "🅱 Set select b";
  constant L_SELECT_ALL (line 21) | const L_SELECT_ALL: &str = concat!(ic::SELECTION_ALL, " Select all in re...
  constant L_SELECT_ROW (line 22) | const L_SELECT_ROW: &str = concat!(ic::ARROWS_HORIZONTAL, " Select row");
  constant L_SELECT_COL (line 23) | const L_SELECT_COL: &str = concat!(ic::ARROWS_VERTICAL, " Select column");
  constant L_EXTERNAL_COMMAND (line 24) | const L_EXTERNAL_COMMAND: &str = concat!(ic::TERMINAL_WINDOW, " External...
  constant L_INC_BYTE (line 25) | const L_INC_BYTE: &str = concat!(ic::PLUS, " Inc byte(s)");
  constant L_DEC_BYTE (line 26) | const L_DEC_BYTE: &str = concat!(ic::MINUS, " Dec byte(s)");
  constant L_PASTE_AT_CURSOR (line 27) | const L_PASTE_AT_CURSOR: &str = concat!(ic::CLIPBOARD_TEXT, " Paste at c...
  constant L_TRUNCATE_EXTEND (line 28) | const L_TRUNCATE_EXTEND: &str = concat!(ic::SCISSORS, " Truncate/Extend....
  function ui (line 30) | pub fn ui(

FILE: src/gui/top_menu/file.rs
  constant L_LOPEN (line 12) | const L_LOPEN: &str = concat!(ic::FOLDER_OPEN, " Open...");
  constant L_OPEN_PROCESS (line 13) | const L_OPEN_PROCESS: &str = concat!(ic::CPU, " Open process...");
  constant L_OPEN_PREVIOUS (line 14) | const L_OPEN_PREVIOUS: &str = concat!(ic::ARROWS_LEFT_RIGHT, " Open prev...
  constant L_SAVE (line 15) | const L_SAVE: &str = concat!(ic::FLOPPY_DISK, " Save");
  constant L_SAVE_AS (line 16) | const L_SAVE_AS: &str = concat!(ic::FLOPPY_DISK_BACK, " Save as...");
  constant L_RELOAD (line 17) | const L_RELOAD: &str = concat!(ic::ARROW_COUNTER_CLOCKWISE, " Reload");
  constant L_RECENT (line 18) | const L_RECENT: &str = concat!(ic::CLOCK_COUNTER_CLOCKWISE, " Recent");
  constant L_AUTO_SAVE_RELOAD (line 19) | const L_AUTO_SAVE_RELOAD: &str = concat!(ic::MAGNET, " Auto save/reload....
  constant L_CREATE_BACKUP (line 20) | const L_CREATE_BACKUP: &str = concat!(ic::CLOUD_ARROW_UP, " Create backu...
  constant L_RESTORE_BACKUP (line 21) | const L_RESTORE_BACKUP: &str = concat!(ic::CLOUD_ARROW_DOWN, " Restore b...
  constant L_PREFERENCES (line 22) | const L_PREFERENCES: &str = concat!(ic::GEAR_SIX, " Preferences");
  constant L_CLOSE (line 23) | const L_CLOSE: &str = concat!(ic::X_SQUARE, " Close");
  constant L_QUIT (line 24) | const L_QUIT: &str = concat!(ic::SIGN_OUT, " Quit");
  function ui (line 26) | pub fn ui(ui: &mut egui::Ui, gui: &mut Gui, app: &mut App, font_size: u1...

FILE: src/gui/top_menu/help.rs
  constant L_HEXERATOR_BOOK (line 9) | const L_HEXERATOR_BOOK: &str = concat!(ic::BOOK_OPEN_TEXT, " Hexerator b...
  constant L_DEBUG_PANEL (line 10) | const L_DEBUG_PANEL: &str = concat!(ic::BUG, " Debug panel...");
  constant L_ABOUT (line 11) | const L_ABOUT: &str = concat!(ic::QUESTION, " About Hexerator...");
  function ui (line 13) | pub fn ui(ui: &mut Ui, gui: &mut Gui) {

FILE: src/gui/top_menu/meta.rs
  constant L_PERSPECTIVES (line 12) | const L_PERSPECTIVES: &str = concat!(ic::PERSPECTIVE, " Perspectives...");
  constant L_REGIONS (line 13) | const L_REGIONS: &str = concat!(ic::RULER, " Regions...");
  constant L_BOOKMARKS (line 14) | const L_BOOKMARKS: &str = concat!(ic::BOOKMARK, " Bookmarks...");
  constant L_VARIABLES (line 15) | const L_VARIABLES: &str = concat!(ic::CALCULATOR, " Variables...");
  constant L_STRUCTS (line 16) | const L_STRUCTS: &str = concat!(ic::BLUEPRINT, " Structs...");
  constant L_RELOAD (line 17) | const L_RELOAD: &str = concat!(ic::ARROW_COUNTER_CLOCKWISE, " Reload");
  constant L_LOAD_FROM_FILE (line 18) | const L_LOAD_FROM_FILE: &str = concat!(ic::FOLDER_OPEN, " Load from file...
  constant L_LOAD_FROM_BACKUP (line 19) | const L_LOAD_FROM_BACKUP: &str = concat!(ic::CLOUD_ARROW_DOWN, " Load fr...
  constant L_CLEAR (line 20) | const L_CLEAR: &str = concat!(ic::BROOM, " Clear");
  constant L_DIFF_WITH_CLEAN_META (line 21) | const L_DIFF_WITH_CLEAN_META: &str = concat!(ic::GIT_DIFF, " Diff with c...
  constant L_SAVE (line 22) | const L_SAVE: &str = concat!(ic::FLOPPY_DISK, " Save");
  constant L_SAVE_AS (line 23) | const L_SAVE_AS: &str = concat!(ic::FLOPPY_DISK_BACK, " Save as...");
  constant L_ASSOCIATE_WITH_CURRENT (line 24) | const L_ASSOCIATE_WITH_CURRENT: &str = concat!(ic::FLOW_ARROW, " Associa...
  function ui (line 26) | pub fn ui(ui: &mut egui::Ui, gui: &mut Gui, app: &mut App, font_size: u1...

FILE: src/gui/top_menu/plugins.rs
  function ui (line 8) | pub fn ui(ui: &mut egui::Ui, gui: &mut Gui, app: &mut App) {

FILE: src/gui/top_menu/scripting.rs
  function ui (line 6) | pub fn ui(

FILE: src/gui/top_menu/view.rs
  constant L_LAYOUT (line 12) | const L_LAYOUT: &str = concat!(ic::LAYOUT, " Layout");
  constant L_RULER (line 13) | const L_RULER: &str = concat!(ic::RULER, " Ruler");
  constant L_LAYOUTS (line 14) | const L_LAYOUTS: &str = concat!(ic::LAYOUT, " Layouts...");
  constant L_FOCUS_PREV (line 15) | const L_FOCUS_PREV: &str = concat!(ic::ARROW_FAT_LEFT, " Focus previous");
  constant L_FOCUS_NEXT (line 16) | const L_FOCUS_NEXT: &str = concat!(ic::ARROW_FAT_RIGHT, " Focus next");
  constant L_VIEWS (line 17) | const L_VIEWS: &str = concat!(ic::EYE, " Views...");
  function ui (line 19) | pub fn ui(ui: &mut egui::Ui, gui: &mut Gui, app: &mut App) {

FILE: src/gui/top_panel.rs
  function ui (line 17) | pub fn ui(ui: &mut Ui, gui: &mut Gui, app: &mut App, lua: &Lua, font_siz...
  function color_from_hexcode (line 195) | fn color_from_hexcode(mut src: &str) -> anyhow::Result<[u8; 3]> {
  function test_color_from_hexcode (line 207) | fn test_color_from_hexcode() {

FILE: src/gui/windows.rs
  type Windows (line 46) | pub struct Windows {
    method update (line 117) | pub(crate) fn update(
    method add_lua_watch_window (line 187) | pub fn add_lua_watch_window(&mut self) {
  type WindowOpen (line 71) | pub(crate) struct WindowOpen {
    method toggle (line 78) | pub fn toggle(&mut self) {
    method is (line 85) | fn is(&self) -> bool {
    method set (line 89) | pub fn set(&mut self, open: bool) {
    method just_now (line 96) | fn just_now(&self) -> bool {
  type WinCtx (line 101) | struct WinCtx<'a> {
  type Window (line 111) | trait Window {
    method ui (line 112) | fn ui(&mut self, ctx: WinCtx);
    method title (line 113) | fn title(&self) -> &str;

FILE: src/gui/windows/about.rs
  type InfoPair (line 9) | type InfoPair = (&'static str, String);
  type AboutWindow (line 11) | pub struct AboutWindow {
    method ui (line 40) | fn ui(&mut self, WinCtx { ui, gui, app, .. }: WinCtx) {
    method title (line 114) | fn title(&self) -> &str {
  method default (line 20) | fn default() -> Self {
  constant MIB (line 31) | const MIB: u64 = 1_048_576;
  function info_table (line 119) | fn info_table(ui: &mut egui::Ui, info: &[InfoPair]) {
  function clipfmt_info (line 142) | fn clipfmt_info(info: &[InfoPair]) -> String {

FILE: src/gui/windows/bookmarks.rs
  type BookmarksWindow (line 27) | pub struct BookmarksWindow {
    method ui (line 38) | fn ui(&mut self, WinCtx { ui, gui, app, .. }: WinCtx) {
    method title (line 341) | fn title(&self) -> &str {
  function value_ui (line 346) | fn value_ui(
  type ValueTrait (line 382) | trait ValueTrait: EndianedPrimitive {
    method value_change_ui (line 384) | fn value_change_ui(
    method value_ui_for_self (line 391) | fn value_ui_for_self(
    method value_change_ui (line 460) | fn value_change_ui(
    method value_change_ui (line 507) | fn value_change_ui(
  type ValueUiOutput (line 434) | struct ValueUiOutput<T> {
  type DefaultUi (line 439) | trait DefaultUi {}
  type Primitive (line 491) | type Primitive = u8;
  method from_bytes (line 493) | fn from_bytes(bytes: [u8; Self::BYTE_LEN]) -> Self::Primitive {
  method to_bytes (line 497) | fn to_bytes(prim: Self::Primitive) -> [u8; Self::BYTE_LEN] {
  method label (line 501) | fn label(&self) -> &'static str {
  type Action (line 535) | enum Action {
  type UiAction (line 540) | enum UiAction<T> {
  function to_action (line 545) | fn to_action(&self) -> Action {

FILE: src/gui/windows/debug.rs
  function ui (line 6) | pub fn ui(ui: &mut Ui) {

FILE: src/gui/windows/external_command.rs
  type ExternalCommandWindow (line 18) | pub struct ExternalCommandWindow {
    method ui (line 78) | fn ui(&mut self, WinCtx { ui, gui, app, .. }: WinCtx) {
    method title (line 253) | fn title(&self) -> &str {
  type WorkingDir (line 34) | enum WorkingDir {
    method label (line 44) | fn label(&self) -> &'static str {
  method default (line 54) | fn default() -> Self {
  type Arg (line 72) | enum Arg<'src> {
  function resolve_args (line 258) | fn resolve_args<'src>(
  function parse (line 268) | fn parse(input: &'_ str) -> anyhow::Result<(&'_ str, impl Iterator<Item ...

FILE: src/gui/windows/file_diff_result.rs
  type FileDiffResultWindow (line 13) | pub struct FileDiffResultWindow {
    method ui (line 43) | fn ui(
    method title (line 307) | fn title(&self) -> &str {
  method default (line 28) | fn default() -> Self {
  type Action (line 312) | enum Action {

FILE: src/gui/windows/find_dialog.rs
  type FindType (line 30) | pub enum FindType {
    method to_value_type (line 58) | fn to_value_type(&self) -> ValueType {
    method help_str (line 84) | fn help_str(&self) -> &'static str {
  type FindDialog (line 124) | pub struct FindDialog {
    method ui (line 146) | fn ui(&mut self, WinCtx { ui, gui, app, .. }: WinCtx) {
    method title (line 497) | fn title(&self) -> &str {
    method search_region (line 503) | fn search_region(&self, app_hex_ui: &HexUi) -> Option<Region> {
    method data_to_search (line 510) | fn data_to_search<'a>(&self, app: &'a crate::app::App) -> (&'a [u8], u...
    method reload_data (line 517) | fn reload_data(&self, app: &mut crate::app::App, gui: &mut crate::gui:...
  type SliceExt (line 526) | trait SliceExt<T> {
    method get_array (line 527) | fn get_array<const N: usize>(&self, offset: usize) -> Option<&[T; N]>;
    method get_array_mut (line 528) | fn get_array_mut<const N: usize>(&mut self, offset: usize) -> Option<&...
  function get_array (line 532) | fn get_array<const N: usize>(&self, offset: usize) -> Option<&[T; N]> {
  function get_array_mut (line 535) | fn get_array_mut<const N: usize>(&mut self, offset: usize) -> Option<&mu...
  function data_value_label (line 540) | fn data_value_label<N: EndianedPrimitive>(
  type Action (line 564) | enum Action {
  function do_search (line 570) | fn do_search(
  function make_eq_pattern_needle (line 635) | fn make_eq_pattern_needle(pattern: &str) -> Vec<u8> {
  function test_make_eq_pattern_needle (line 651) | fn test_make_eq_pattern_needle() {
  function find_eq_pattern (line 663) | fn find_eq_pattern(pattern: &str, data: &[u8]) -> Option<usize> {
  function find_eq_pattern_needle (line 668) | fn find_eq_pattern_needle(needle: &[u8], data: &[u8]) -> Option<usize> {
  function eq_pattern_needle_matches (line 677) | fn eq_pattern_needle_matches(needle: &[u8], data: &[u8]) -> bool {
  function test_find_eq_pattern (line 689) | fn test_find_eq_pattern() {
  function ascii_to_diff_pattern (line 698) | fn ascii_to_diff_pattern(ascii: &[u8]) -> Vec<i8> {
  function find_diff_pattern (line 703) | fn find_diff_pattern(haystack: &[u8], pat: &[i8]) -> Option<usize> {
  function test_ascii_to_diff_pattern (line 724) | fn test_ascii_to_diff_pattern() {
  function test_find_diff_pattern (line 733) | fn test_find_diff_pattern() {
  function find_num (line 745) | fn find_num<N: EndianedPrimitive>(win: &mut FindDialog, data: &[u8]) -> ...
  function find_num_raw (line 755) | pub(crate) fn find_num_raw<N: EndianedPrimitive>(
  function find_u8 (line 772) | fn find_u8(dia: &mut FindDialog, data: &[u8], initial_offset: usize, msg...
  function eq_filter (line 853) | fn eq_filter(dia: &mut FindDialog, data: &[u8], initial_offset: usize) {
  function u8_search (line 868) | fn u8_search(

FILE: src/gui/windows/find_memory_pointers.rs
  type FindMemoryPointersWindow (line 8) | pub struct FindMemoryPointersWindow {
    method ui (line 25) | fn ui(
    method title (line 153) | fn title(&self) -> &str {
  type PtrEntry (line 16) | struct PtrEntry {
  type Action (line 158) | enum Action {

FILE: src/gui/windows/layouts.rs
  constant L_NEW_FROM_PERSPECTIVE (line 14) | const L_NEW_FROM_PERSPECTIVE: &str = concat!(ic::PLUS, " New from perspe...
  constant L_HEX (line 15) | const L_HEX: &str = concat!(ic::HEXAGON, " Hex");
  constant L_TEXT (line 16) | const L_TEXT: &str = concat!(ic::TEXT_AA, " Text");
  constant L_BLOCK (line 17) | const L_BLOCK: &str = concat!(ic::RECTANGLE, " Block");
  constant L_ADD_TO_NEW_ROW (line 18) | const L_ADD_TO_NEW_ROW: &str = concat!(ic::PLUS, ic::ARROW_BEND_DOWN_RIG...
  constant L_ADD_TO_CURRENT_ROW (line 19) | const L_ADD_TO_CURRENT_ROW: &str = concat!(ic::PLUS, ic::ARROW_LEFT);
  type LayoutsWindow (line 22) | pub struct LayoutsWindow {
    method ui (line 30) | fn ui(
    method title (line 204) | fn title(&self) -> &str {
  function add_new_view_menu (line 209) | fn add_new_view_menu(

FILE: src/gui/windows/lua_console.rs
  type MsgBuf (line 7) | type MsgBuf = Vec<ConMsg>;
  type MsgBufMap (line 8) | type MsgBufMap = HashMap<ScriptKey, MsgBuf>;
  type LuaConsoleWindow (line 11) | pub struct LuaConsoleWindow {
    method msg_buf (line 20) | fn msg_buf(&mut self) -> &mut MsgBuf {
    method msg_buf_for_key (line 26) | pub fn msg_buf_for_key(&mut self, key: Option<ScriptKey>) -> &mut MsgB...
    method ui (line 48) | fn ui(
    method title (line 149) | fn title(&self) -> &str {
  type ConMsg (line 34) | pub enum ConMsg {

FILE: src/gui/windows/lua_editor.rs
  type LuaEditorWindow (line 19) | pub struct LuaEditorWindow {
    method ui (line 29) | fn ui(&mut self, ctx: WinCtx) {
    method title (line 169) | fn title(&self) -> &str {
    method exec_lua (line 175) | fn exec_lua(

FILE: src/gui/windows/lua_help.rs
  type LuaHelpWindow (line 8) | pub struct LuaHelpWindow {
    method ui (line 14) | fn ui(&mut self, WinCtx { ui, .. }: WinCtx) {
    method title (line 43) | fn title(&self) -> &str {

FILE: src/gui/windows/lua_watch.rs
  type LuaWatchWindow (line 3) | pub struct LuaWatchWindow {
    method ui (line 20) | fn ui(
    method title (line 51) | fn title(&self) -> &str {
  method default (line 10) | fn default() -> Self {

FILE: src/gui/windows/meta_diff.rs
  type MetaDiffWindow (line 16) | pub struct MetaDiffWindow {
    method ui (line 20) | fn ui(&mut self, WinCtx { ui, app, .. }: WinCtx) {
    method title (line 33) | fn title(&self) -> &str {
  type SlotmapDiffItem (line 38) | trait SlotmapDiffItem: PartialEq + Eq + Clone + Debug {
    method label (line 41) | fn label(&self) -> &str;
    method sort_key (line 42) | fn sort_key(&self) -> Self::SortKey;
    type Key (line 46) | type Key = RegionKey;
    method label (line 48) | fn label(&self) -> &str {
    type SortKey (line 52) | type SortKey = usize;
    method sort_key (line 54) | fn sort_key(&self) -> Self::SortKey {
    type Key (line 60) | type Key = PerspectiveKey;
    type SortKey (line 62) | type SortKey = String;
    method label (line 64) | fn label(&self) -> &str {
    method sort_key (line 68) | fn sort_key(&self) -> Self::SortKey {
    type Key (line 74) | type Key = ViewKey;
    type SortKey (line 76) | type SortKey = String;
    method label (line 78) | fn label(&self) -> &str {
    method sort_key (line 82) | fn sort_key(&self) -> Self::SortKey {
    type Key (line 88) | type Key = LayoutKey;
    type SortKey (line 90) | type SortKey = String;
    method label (line 92) | fn label(&self) -> &str {
    method sort_key (line 96) | fn sort_key(&self) -> Self::SortKey {
  function diff_slotmap (line 101) | fn diff_slotmap<I: SlotmapDiffItem>(

FILE: src/gui/windows/open_process.rs
  type MapRanges (line 15) | type MapRanges = Vec<MapRange>;
  type OpenProcessWindow (line 18) | pub struct OpenProcessWindow {
    method ui (line 120) | fn ui(
    method title (line 639) | fn title(&self) -> &str {
  type Filters (line 35) | pub struct Filters {
  type FindState (line 43) | struct FindState {
  type MapFindResults (line 49) | struct MapFindResults {
  type PermFilters (line 55) | pub struct PermFilters {
  type Sort (line 62) | enum Sort {
    method flip (line 69) | fn flip(&mut self) {
  function sort_button (line 77) | fn sort_button(ui: &mut egui::Ui, label: &str, active: bool, sort: Sort)...
  type MapsSortColumn (line 93) | enum MapsSortColumn {
  type Modal (line 99) | enum Modal {
    method run_command (line 104) | fn run_command() -> Self {
  type RunCommand (line 113) | struct RunCommand {
  function should_retain_range (line 644) | fn should_retain_range(filters: &Filters, range: &MapRange) -> bool {
  function refresh_proc_maps (line 668) | fn refresh_proc_maps(pid: u32, win_map_ranges: &mut MapRanges, msg: &mut...

FILE: src/gui/windows/perspectives.rs
  type PerspectivesWindow (line 12) | pub struct PerspectivesWindow {
    method ui (line 17) | fn ui(&mut self, WinCtx { ui, gui, app, .. }: WinCtx) {
    method title (line 152) | fn title(&self) -> &str {

FILE: src/gui/windows/preferences.rs
  type PreferencesWindow (line 14) | pub struct PreferencesWindow {
    method ui (line 41) | fn ui(&mut self, WinCtx { ui, gui, app, .. }: WinCtx) {
    method title (line 90) | fn title(&self) -> &str {
  type Tab (line 23) | enum Tab {
    method label (line 31) | fn label(&self) -> &'static str {
  function video_ui (line 95) | fn video_ui(ui: &mut egui::Ui, app: &mut App) {
  function style_ui (line 108) | fn style_ui(
  function fonts_ui (line 208) | fn fonts_ui(

FILE: src/gui/windows/regions.rs
  type RegionsWindow (line 15) | pub struct RegionsWindow {
    method ui (line 62) | fn ui(&mut self, WinCtx { ui, gui, app, .. }: WinCtx) {
    method title (line 271) | fn title(&self) -> &str {
  function region_context_menu (line 24) | pub fn region_context_menu(
  type Action (line 276) | enum Action {

FILE: src/gui/windows/script_manager.rs
  type ScriptManagerWindow (line 15) | pub struct ScriptManagerWindow {
    method ui (line 21) | fn ui(
    method title (line 73) | fn title(&self) -> &str {
    method selected_script_ui (line 79) | fn selected_script_ui(

FILE: src/gui/windows/structs.rs
  type StructsWindow (line 11) | pub struct StructsWindow {
    method ui (line 27) | fn ui(&mut self, super::WinCtx { ui, app, .. }: super::WinCtx) {
    method title (line 51) | fn title(&self) -> &str {
    method refresh (line 57) | fn refresh(&mut self, meta: &Meta) {
    method picker_ui (line 65) | fn picker_ui(&mut self, meta: &Meta, ui: &mut egui::Ui) {
    method editor_ui (line 74) | fn editor_ui(&mut self, ui: &mut egui::Ui) {
    method parsed_struct_ui (line 99) | fn parsed_struct_ui(&mut self, ui: &mut egui::Ui, app: &mut crate::app...
    method bottom_bar_ui (line 136) | fn bottom_bar_ui(&mut self, ui: &mut egui::Ui, app: &mut crate::app::A...
  type Tab (line 21) | enum Tab {
  function fields_ui (line 180) | fn fields_ui(struct_: &mut StructMetaItem, ui: &mut egui::Ui) {
  function at_row_ui (line 197) | fn at_row_ui(struct_: &mut StructMetaItem, ui: &mut egui::Ui, app: &mut ...
  type ToFromBytes (line 223) | trait ToFromBytes: Sized {
    constant LEN (line 224) | const LEN: usize = size_of::<Self>();
    method from_bytes (line 225) | fn from_bytes(bytes: [u8; Self::LEN], endian: Endian) -> Self;
    method to_bytes (line 226) | fn to_bytes(&self, endian: Endian) -> [u8; Self::LEN];
  function with_bytes_as_primitive (line 229) | fn with_bytes_as_primitive<T, F>(bytes: &mut [u8], endian: Endian, mut f...
  function field_edit_ui (line 272) | fn field_edit_ui(

FILE: src/gui/windows/vars.rs
  type VarsWindow (line 9) | pub struct VarsWindow {
    method ui (line 16) | fn ui(&mut self, WinCtx { ui, app, .. }: WinCtx) {
    method title (line 83) | fn title(&self) -> &str {
  function var_val_label (line 88) | fn var_val_label(var_val: &VarVal) -> &str {

FILE: src/gui/windows/views.rs
  type ViewsWindow (line 16) | pub struct ViewsWindow {
    method ui (line 41) | fn ui(
    method title (line 280) | fn title(&self) -> &str {
  constant HEX_NAME (line 23) | const HEX_NAME: &'static str = "Hex";
  constant DEC_NAME (line 24) | const DEC_NAME: &'static str = "Decimal";
  constant TEXT_NAME (line 25) | const TEXT_NAME: &'static str = "Text";
  constant BLOCK_NAME (line 26) | const BLOCK_NAME: &'static str = "Block";
  method name (line 27) | fn name(&self) -> &'static str {
  constant MIN_FONT_SIZE (line 37) | pub const MIN_FONT_SIZE: u16 = 5;
  constant MAX_FONT_SIZE (line 38) | pub const MAX_FONT_SIZE: u16 = 256;
  function new_from_perspective_button (line 285) | fn new_from_perspective_button(ui: &mut egui::Ui, app: &mut App) {
  function view_combo (line 299) | fn view_combo(
  function labelled_drag (line 340) | fn labelled_drag<T: Numeric>(

FILE: src/gui/windows/zero_partition.rs
  type ZeroPartition (line 10) | pub struct ZeroPartition {
    method ui (line 29) | fn ui(&mut self, WinCtx { ui, app, gui, .. }: WinCtx) {
    method title (line 109) | fn title(&self) -> &str {
  method default (line 18) | fn default() -> Self {
  function zero_partition (line 114) | fn zero_partition(data: &[u8], threshold: usize) -> Vec<Region> {
  function test_zero_partition (line 148) | fn test_zero_partition() {

FILE: src/hex_conv.rs
  function byte_16_digits (line 1) | fn byte_16_digits(byte: u8) -> [u8; 2] {
  function test_byte_16_digits (line 6) | fn test_byte_16_digits() {
  function byte_to_hex_digits (line 10) | pub fn byte_to_hex_digits(byte: u8) -> [u8; 2] {
  function test_byte_to_hex_digits (line 18) | fn test_byte_to_hex_digits() {
  function digit_to_byte (line 34) | fn digit_to_byte(digit: u8) -> Option<u8> {
  function merge_hex_halves (line 56) | pub fn merge_hex_halves(first: u8, second: u8) -> Option<u8> {
  function test_merge_halves (line 61) | fn test_merge_halves() {

FILE: src/hex_ui.rs
  type HexUi (line 15) | pub struct HexUi {
    method selection (line 53) | pub fn selection(&self) -> Option<Region> {
    method selected_regions (line 65) | pub fn selected_regions(&self) -> impl Iterator<Item = Region> {
    method clear_selections (line 68) | pub fn clear_selections(&mut self) {
    method clear_meta_refs (line 74) | pub fn clear_meta_refs(&mut self) {
    method flash_cursor (line 79) | pub fn flash_cursor(&mut self) {
    method cursor_flash_timer (line 84) | pub fn cursor_flash_timer(&self) -> Option<u32> {
  type Ruler (line 42) | pub struct Ruler {

FILE: src/input.rs
  type Input (line 7) | pub struct Input {
    method update_from_event (line 12) | pub fn update_from_event(&mut self, event: &Event) {
    method key_down (line 23) | pub fn key_down(&self, key: Key) -> bool {
    method clear (line 27) | pub(crate) fn clear(&mut self) {

FILE: src/layout.rs
  type Layout (line 12) | pub struct Layout {
    method iter (line 26) | pub fn iter(&self) -> impl Iterator<Item = ViewKey> + '_ {
    method idx_of_key (line 30) | pub(crate) fn idx_of_key(&self, key: ViewKey) -> Option<[usize; 2]> {
    method view_containing_region (line 37) | pub(crate) fn view_containing_region(
    method contains_view (line 46) | pub(crate) fn contains_view(&self, key: ViewKey) -> bool {
    method remove_view (line 50) | pub(crate) fn remove_view(&mut self, rem_key: ViewKey) {
    method remove_dangling (line 57) | pub(crate) fn remove_dangling(&mut self, map: &ViewMap) {
    method change_view_type (line 74) | pub(crate) fn change_view_type(&mut self, current: ViewKey, new: ViewK...
  function default_margin (line 20) | pub const fn default_margin() -> ViewportVec {
  function do_auto_layout (line 81) | pub fn do_auto_layout(

FILE: src/main.rs
  constant L_CONTINUE (line 106) | const L_CONTINUE: &str = concat!(ic::WARNING, " Continue");
  constant L_ABORT (line 107) | const L_ABORT: &str = concat!(ic::X_CIRCLE, "Abort");
  function print_version_info (line 109) | fn print_version_info() {
  function try_main (line 119) | fn try_main() -> anyhow::Result<()> {
  function transfer_pinned_folders_to_file_dialog (line 269) | fn transfer_pinned_folders_to_file_dialog(gui: &mut Gui, cfg: &mut Confi...
  function transfer_pinned_folders_to_config (line 281) | fn transfer_pinned_folders_to_config(mut gui: Gui, app: &mut App) {
  function main (line 291) | fn main() {
  function do_fatal_error_report (line 326) | fn do_fatal_error_report(title: &str, mut desc: &str, backtrace: &Backtr...

FILE: src/meta.rs
  type PerspectiveMap (line 21) | pub type PerspectiveMap = SlotMap<PerspectiveKey, Perspective>;
  type RegionMap (line 22) | pub type RegionMap = SlotMap<RegionKey, NamedRegion>;
  type ViewMap (line 23) | pub type ViewMap = SlotMap<ViewKey, NamedView>;
  type LayoutMap (line 24) | pub type LayoutMap = SlotMap<LayoutKey, Layout>;
  type ScriptMap (line 25) | pub type ScriptMap = SlotMap<ScriptKey, Script>;
  type Bookmarks (line 26) | pub type Bookmarks = Vec<Bookmark>;
  type LayoutMapExt (line 28) | pub trait LayoutMapExt {
    method add_new_default (line 29) | fn add_new_default(&mut self) -> LayoutKey;
    method add_new_default (line 33) | fn add_new_default(&mut self) -> LayoutKey {
  type Bookmark (line 44) | pub struct Bookmark {
    method write_int (line 62) | pub(crate) fn write_int(&self, mut data: &mut [u8], val: i64) -> std::...
  type MetaLow (line 90) | pub struct MetaLow {
    method start_offset_of_view (line 96) | pub(crate) fn start_offset_of_view(&self, view: &View) -> usize {
    method end_offset_of_view (line 101) | pub(crate) fn end_offset_of_view(&self, view: &View) -> usize {
  type Meta (line 109) | pub struct Meta {
    method post_load_init (line 185) | pub fn post_load_init(&mut self) {
    method bookmark_for_offset (line 192) | pub fn bookmark_for_offset(
    method add_region_from_selection (line 199) | pub(crate) fn add_region_from_selection(&mut self, sel: Region) -> Reg...
    method remove_view (line 203) | pub(crate) fn remove_view(&mut self, rem_key: ViewKey) {
    method bookmark_by_name_mut (line 211) | pub(crate) fn bookmark_by_name_mut(&mut self, name: &str) -> Option<&m...
    method region_by_name_mut (line 215) | pub(crate) fn region_by_name_mut(&mut self, name: &str) -> Option<&mut...
    method remove_dangling (line 219) | pub(crate) fn remove_dangling(&mut self) {
  type VarEntry (line 127) | pub struct VarEntry {
  type VarVal (line 133) | pub enum VarVal {
  function find_most_specific_region_for_offset (line 138) | pub(crate) fn find_most_specific_region_for_offset(
  type Misc (line 162) | pub struct Misc {
  method default (line 173) | fn default() -> Self {
  constant DEFAULT_FILL (line 181) | const DEFAULT_FILL: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR...
  type NamedRegion (line 243) | pub struct NamedRegion {
    method new (line 257) | pub fn new(name: String, begin: usize, end: usize) -> Self {
    method new_from_selection (line 264) | pub fn new_from_selection(sel: Region) -> Self {
  type NamedView (line 251) | pub struct NamedView {
  type Script (line 274) | pub struct Script {

FILE: src/meta/perspective.rs
  type Perspective (line 9) | pub struct Perspective {
    method last_row_idx (line 25) | pub(crate) fn last_row_idx(&self, rmap: &RegionMap) -> usize {
    method last_col_idx (line 29) | pub(crate) fn last_col_idx(&self, rmap: &RegionMap) -> usize {
    method byte_offset_of_row_col (line 32) | pub(crate) fn byte_offset_of_row_col(&self, row: usize, col: usize, rm...
    method row_col_of_byte_offset (line 35) | pub(crate) fn row_col_of_byte_offset(&self, offset: usize, rmap: &Regi...
    method row_col_within_bound (line 41) | pub(crate) fn row_col_within_bound(&self, row: usize, col: usize, rmap...
    method clamp_cols (line 45) | pub(crate) fn clamp_cols(&mut self, rmap: &RegionMap) {
    method region_row_span (line 49) | pub(crate) fn region_row_span(&self, region: Region) -> [usize; 2] {
    method n_rows (line 52) | pub(crate) fn n_rows(&self, rmap: &RegionMap) -> usize {
    method from_region (line 61) | pub(crate) fn from_region(key: RegionKey, name: String) -> Self {

FILE: src/meta/region.rs
  type Region (line 5) | pub struct Region {
    method len (line 11) | pub fn len(&self) -> usize {
    method contains (line 16) | pub(crate) fn contains(&self, idx: usize) -> bool {
    method contains_region (line 20) | pub(crate) fn contains_region(&self, reg: &Self) -> bool {
    method to_range (line 23) | pub fn to_range(self) -> std::ops::RangeInclusive<usize> {
    method prev_chunk (line 27) | pub fn prev_chunk(self) -> Option<Self> {
    method next_chunk (line 36) | pub fn next_chunk(self) -> Self {

FILE: src/meta/value_type.rs
  type ValueType (line 7) | pub enum ValueType {
    method label (line 40) | pub fn label(&self) -> &str {
    method byte_len (line 65) | pub(crate) fn byte_len(&self) -> usize {
    method read (line 90) | pub fn read(&self, data: &[u8]) -> anyhow::Result<ReadValue> {
  method eq (line 32) | fn eq(&self, other: &Self) -> bool {
  type StringMap (line 37) | pub type StringMap = HashMap<u8, String>;
  function read (line 123) | fn read<P: EndianedPrimitive>(data: &[u8]) -> Result<P::Primitive, anyho...
  type ReadValue (line 130) | pub enum ReadValue {
    method fmt (line 144) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  type EndianedPrimitive (line 160) | pub trait EndianedPrimitive {
    constant BYTE_LEN (line 161) | const BYTE_LEN: usize = size_of::<Self::Primitive>();
    method from_bytes (line 163) | fn from_bytes(bytes: [u8; Self::BYTE_LEN]) -> Self::Primitive;
    method to_bytes (line 164) | fn to_bytes(prim: Self::Primitive) -> [u8; Self::BYTE_LEN];
    method label (line 165) | fn label(&self) -> &'static str;
    method from_byte_slice (line 166) | fn from_byte_slice(slice: &[u8]) -> Option<Self::Primitive>
    type Primitive (line 181) | type Primitive = i8;
    method from_bytes (line 183) | fn from_bytes(bytes: [u8; Self::BYTE_LEN]) -> Self::Primitive {
    method to_bytes (line 187) | fn to_bytes(prim: Self::Primitive) -> [u8; Self::BYTE_LEN] {
    method label (line 191) | fn label(&self) -> &'static str {
    type Primitive (line 200) | type Primitive = u8;
    method from_bytes (line 202) | fn from_bytes(bytes: [u8; Self::BYTE_LEN]) -> Self::Primitive {
    method to_bytes (line 206) | fn to_bytes(prim: Self::Primitive) -> [u8; Self::BYTE_LEN] {
    method label (line 210) | fn label(&self) -> &'static str {
  type I8 (line 178) | pub struct I8;
  type U8 (line 197) | pub struct U8;

FILE: src/meta_state.rs
  type MetaState (line 6) | pub struct MetaState {
  method default (line 15) | fn default() -> Self {

FILE: src/parse_radix.rs
  function parse_guess_radix (line 3) | pub fn parse_guess_radix<T: Num>(input: &str) -> Result<T, <T as Num>::F...
  type Relativity (line 14) | pub enum Relativity {
  function parse_offset_maybe_relative (line 20) | pub fn parse_offset_maybe_relative(

FILE: src/plugin.rs
  type PluginContainer (line 8) | pub struct PluginContainer {
    method new (line 61) | pub unsafe fn new(path: PathBuf) -> anyhow::Result<Self> {
  method debug_log (line 17) | fn debug_log(&self, msg: &str) {
  method get_data (line 21) | fn get_data(&self, start: usize, end: usize) -> Option<&[u8]> {
  method get_data_mut (line 25) | fn get_data_mut(&mut self, start: usize, end: usize) -> Option<&mut [u8]> {
  method selection_range (line 29) | fn selection_range(&self) -> Option<[usize; 2]> {
  method perspective (line 33) | fn perspective(&self, name: &str) -> Option<PerspectiveHandle> {
  method perspective_rows (line 46) | fn perspective_rows(&self, ph: &PerspectiveHandle) -> Vec<&[u8]> {

FILE: src/result_ext.rs
  type AnyhowConv (line 1) | pub trait AnyhowConv<T, E>
    method how (line 5) | fn how(self) -> anyhow::Result<T>;
  function how (line 12) | fn how(self) -> anyhow::Result<T> {

FILE: src/scripting.rs
  type LuaExecContext (line 17) | pub struct LuaExecContext<'app, 'gui> {
  type Method (line 25) | pub(crate) trait Method {
    constant NAME (line 27) | const NAME: &'static str;
    constant HELP (line 29) | const HELP: &'static str;
    constant API_SIG (line 31) | const API_SIG: &'static str;
    method call (line 37) | fn call(lua: &Lua, exec: &mut LuaExecContext, args: Self::Args) -> mlu...
  function lua_plugin_value_conv (line 369) | fn lua_plugin_value_conv(lval: mlua::Value) -> Option<hexerator_plugin_a...
  function plugin_value_lua_conv (line 387) | fn plugin_value_lua_conv(
  method add_methods (line 433) | fn add_methods<T: mlua::UserDataMethods<Self>>(methods: &mut T) {
  type ExecLuaError (line 444) | pub enum ExecLuaError {
  function exec_lua (line 451) | pub fn exec_lua(
  type ScriptArg (line 493) | pub enum ScriptArg {
  constant SCRIPT_ARG_FMT_HELP_STR (line 498) | pub const SCRIPT_ARG_FMT_HELP_STR: &str = "mynum = 4.5, mystring = \"hel...
  type ArgParseError (line 501) | pub enum ArgParseError {
  function parse_script_args (line 513) | pub fn parse_script_args(s: &str) -> Result<HashMap<String, ScriptArg>, ...
  function test_parse_script_args (line 547) | fn test_parse_script_args() {
  function test_parse_script_args_single_quot (line 558) | fn test_parse_script_args_single_quot() {

FILE: src/session_prefs.rs
  type SessionPrefs (line 3) | pub struct SessionPrefs {
  type Autoreload (line 31) | pub enum Autoreload {
    method is_active (line 42) | pub fn is_active(&self) -> bool {
    method label (line 45) | pub fn label(&self) -> &'static str {

FILE: src/shell.rs
  function open_previous (line 9) | pub fn open_previous(app: &App, load: &mut Option<crate::args::SourceArg...
  function msg_if_fail (line 15) | pub fn msg_if_fail<T, E: std::fmt::Display>(
  function msg_fail (line 28) | pub fn msg_fail<E: std::fmt::Display>(e: &E, prefix: &str, msg: &mut Mes...

FILE: src/slice_ext.rs
  type SliceExt (line 1) | pub trait SliceExt {
    method pattern_fill (line 2) | fn pattern_fill(&mut self, pattern: &Self);
    method pattern_fill (line 6) | fn pattern_fill(&mut self, pattern: &Self) {
  function test_pattern_fill (line 14) | fn test_pattern_fill() {

FILE: src/source.rs
  type SourceProvider (line 7) | pub enum SourceProvider {
  type Source (line 23) | pub struct Source {
    method file (line 30) | pub fn file(f: File) -> Self {
  type SourceAttributes (line 43) | pub struct SourceAttributes {
  type SourceState (line 50) | pub struct SourceState {
  type SourcePermissions (line 56) | pub struct SourcePermissions {
  method clone (line 65) | fn clone(&self) -> Self {
  method read (line 84) | fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {

FILE: src/str_ext.rs
  type StrExt (line 1) | pub trait StrExt {
    method is_empty_or_ws_only (line 2) | fn is_empty_or_ws_only(&self) -> bool;
    method is_empty_or_ws_only (line 6) | fn is_empty_or_ws_only(&self) -> bool {

FILE: src/struct_meta_item.rs
  type StructMetaItem (line 4) | pub struct StructMetaItem {
    method new (line 11) | pub fn new(parsed: structparse::Struct, src: String) -> anyhow::Result...
    method fields_with_offsets_mut (line 20) | pub fn fields_with_offsets_mut(&mut self) -> impl Iterator<Item = (usi...
  function try_resolve_field (line 33) | fn try_resolve_field(field: structparse::Field) -> anyhow::Result<Struct...
  function try_resolve_ty (line 40) | fn try_resolve_ty(ty: structparse::Ty) -> anyhow::Result<StructTy> {
  type StructField (line 69) | pub struct StructField {
  type Endian (line 75) | pub enum Endian {
    method label (line 81) | pub fn label(&self) -> &'static str {
    method toggle (line 88) | pub(crate) fn toggle(&mut self) {
  type StructTy (line 97) | pub enum StructTy {
    method size (line 134) | pub fn size(&self) -> usize {
    method endian_mut (line 145) | pub fn endian_mut(&mut self) -> &mut Endian {
    method fmt (line 154) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  type StructPrimitive (line 103) | pub enum StructPrimitive {
    method label (line 117) | fn label(&self) -> &'static str {

FILE: src/timer.rs
  type Timer (line 4) | pub struct Timer {
    method set (line 10) | pub fn set(duration: Duration) -> Self {
    method overtime (line 16) | pub fn overtime(&self) -> Option<Duration> {
  method default (line 27) | fn default() -> Self {

FILE: src/update.rs
  function do_frame (line 32) | pub fn do_frame(
  function handle_post_egui_events (line 113) | fn handle_post_egui_events(
  function update (line 185) | fn update(app: &mut App, egui_wants_kb: bool) {
  function draw (line 255) | fn draw(
  function handle_events (line 278) | fn handle_events(
  function handle_text_entered (line 368) | fn handle_text_entered(app: &mut App, unicode: char, msg: &mut MessageDi...
  type KeyMod (line 391) | struct KeyMod {
  function handle_key_pressed (line 397) | fn handle_key_pressed(
  function keep_cursor_in_view (line 773) | fn keep_cursor_in_view(view: &mut view::View, meta_low: &MetaLow, cursor...
  function block_select (line 797) | fn block_select(app: &mut App, view_key: meta::ViewKey, a: usize, b: usi...

FILE: src/util.rs
  function human_size (line 5) | pub fn human_size(size: usize) -> String {
  function human_size_u64 (line 13) | pub fn human_size_u64(size: u64) -> String {

FILE: src/value_color.rs
  type ColorMethod (line 9) | pub enum ColorMethod {
    method byte_color (line 41) | pub fn byte_color(&self, byte: u8, invert: bool) -> RgbColor {
    method name (line 57) | pub(crate) fn name(&self) -> &str {
  type Palette (line 20) | pub struct Palette(#[serde(with = "BigArray")] pub [[u8; 3]; 256]);
  function load_palette (line 22) | pub fn load_palette(path: &Path) -> anyhow::Result<Palette> {
  function save_palette (line 34) | pub fn save_palette(pal: &Palette, path: &Path) -> anyhow::Result<()> {
  function vga_13h_color (line 70) | fn vga_13h_color(byte: u8) -> RgbColor {
  function rgb332_color (line 82) | fn rgb332_color(byte: u8) -> RgbColor {
  constant VGA_13H_PALETTE (line 89) | const VGA_13H_PALETTE: [u32; 256] = [
  function default_color (line 121) | pub fn default_color(byte: u8) -> RgbColor {
  function hue_color (line 125) | fn hue_color(byte: u8) -> RgbColor {
  function hue_color_tweaked (line 136) | fn hue_color_tweaked(byte: u8) -> RgbColor {
  constant DEFAULT_COLOR_ARRAY (line 148) | const DEFAULT_COLOR_ARRAY: [RgbColor; 256] = [

FILE: src/view.rs
  type View (line 26) | pub struct View {
    method new (line 63) | pub fn new(kind: ViewKind, perspective: PerspectiveKey) -> Self {
    method scroll_x (line 80) | pub fn scroll_x(&mut self, amount: i16) {
    method scroll_y (line 92) | pub fn scroll_y(&mut self, amount: i16) {
    method sync_to (line 105) | pub(crate) fn sync_to(
    method scroll_page_down (line 128) | pub(crate) fn scroll_page_down(&mut self) {
    method scroll_page_up (line 132) | pub(crate) fn scroll_page_up(&mut self) {
    method scroll_page_left (line 136) | pub(crate) fn scroll_page_left(&mut self) {
    method go_home (line 140) | pub(crate) fn go_home(&mut self) {
    method go_home_col (line 146) | pub(crate) fn go_home_col(&mut self) {
    method scroll_to_end (line 152) | pub(crate) fn scroll_to_end(&mut self, meta_low: &MetaLow) {
    method scroll_right_until_bump (line 166) | pub(crate) fn scroll_right_until_bump(&mut self, meta_low: &MetaLow) {
    method row_col_offset_of_pos (line 176) | pub(crate) fn row_col_offset_of_pos(
    method row_col_of_rel_pos (line 191) | fn row_col_of_rel_pos(
    method center_on_offset (line 231) | pub(crate) fn center_on_offset(
    method center_on_row_col (line 241) | fn center_on_row_col(&mut self, row: usize, col: usize) {
    method offsets (line 249) | pub fn offsets(&self, perspectives: &PerspectiveMap, regions: &RegionM...
    method scroll_to_byte_offset (line 259) | pub(crate) fn scroll_to_byte_offset(
    method bytes_per_page (line 280) | pub(crate) fn bytes_per_page(&self, perspectives: &PerspectiveMap) -> ...
    method rows (line 289) | pub(crate) fn rows(&self) -> i16 {
    method cols (line 303) | pub(crate) fn cols(&self) -> i16 {
    method p_cols (line 314) | pub(crate) fn p_cols(&self, perspectives: &PerspectiveMap) -> usize {
    method adjust_block_size (line 321) | pub fn adjust_block_size(&mut self) {
    method adjust_state_to_kind (line 330) | pub fn adjust_state_to_kind(&mut self) {
    method glyph_count (line 341) | fn glyph_count(&self) -> u16 {
    method handle_text_entered (line 349) | pub fn handle_text_entered(
    method max_needed_size (line 401) | pub fn max_needed_size(
    method char_valid (line 417) | fn char_valid(&self, unicode: char) -> bool {
    method finish_editing (line 428) | pub fn finish_editing(
    method cancel_editing (line 473) | pub fn cancel_editing(&mut self) {
    method reset_edit_buf (line 477) | pub fn reset_edit_buf(&mut self) {
    method undirty_edit_buffer (line 483) | pub(crate) fn undirty_edit_buffer(&mut self) {
    method edit_buffer_mut (line 489) | pub(crate) fn edit_buffer_mut(&mut self) -> Option<&mut EditBuffer> {
    method contains_region (line 497) | pub(crate) fn contains_region(&self, reg: &Region, meta: &crate::meta:...
  method eq (line 50) | fn eq(&self, other: &Self) -> bool {
  type SatFrom (line 504) | trait SatFrom<V> {
    method saturating_from (line 505) | fn saturating_from(src: V) -> Self;
  function saturating_from (line 509) | fn saturating_from(src: usize) -> Self {
  function saturating_from (line 515) | fn saturating_from(src: u16) -> Self {
  type Offsets (line 520) | pub struct Offsets {
  function scroll_impl (line 527) | fn scroll_impl(whole: &mut usize, pixel: &mut i16, pixels_per_whole: i16...
  function test_scroll_impl_positive (line 546) | fn test_scroll_impl_positive() {
  function test_scroll_impl_negative (line 570) | fn test_scroll_impl_negative() {
  type ScrollOffset (line 592) | pub struct ScrollOffset {
    method col (line 604) | pub fn col(&self) -> usize {
    method row (line 607) | pub fn row(&self) -> usize {
    method pix_xoff (line 610) | pub fn pix_xoff(&self) -> i16 {
    method pix_yoff (line 613) | pub fn pix_yoff(&self) -> i16 {
    method floor (line 617) | pub(crate) fn floor(&mut self) {
  type ViewportScalar (line 627) | pub type ViewportScalar = i16;
  type ViewportRect (line 630) | pub struct ViewportRect {
    method relative_offset_of_pos (line 754) | fn relative_offset_of_pos(
    method contains_pos (line 762) | pub fn contains_pos(&self, x: ViewportScalar, y: ViewportScalar) -> bo...
  type ViewportVec (line 638) | pub struct ViewportVec {
    type Error (line 644) | type Error = <ViewportScalar as TryFrom<i32>>::Error;
    method try_from (line 646) | fn try_from(src: (i32, i32)) -> Result<Self, Self::Error> {
  type ViewKind (line 656) | pub enum ViewKind {
    method is_text (line 664) | pub(crate) fn is_text(&self) -> bool {
  type TextData (line 670) | pub struct TextData {
    method with_font_info (line 717) | pub fn with_font_info(line_spacing: u16, font_size: u16) -> Self {
  method eq (line 683) | fn eq(&self, other: &Self) -> bool {
  type HexData (line 693) | pub struct HexData {
    method with_font_size (line 708) | pub fn with_font_size(font_size: u16) -> Self {
  method eq (line 700) | fn eq(&self, other: &Self) -> bool {
  type TextKind (line 729) | pub enum TextKind {
    method name (line 736) | pub fn name(&self) -> &'static str {
    method bytes_needed (line 744) | pub(crate) fn bytes_needed(&self) -> u8 {
  function try_conv_mp_zero (line 770) | pub fn try_conv_mp_zero<T: TryInto<ViewportVec>>(src: T) -> ViewportVec

FILE: src/view/draw.rs
  type DrawArgs (line 24) | struct DrawArgs<'vert, 'data> {
  function draw_view (line 34) | fn draw_view<'f>(
  function line_x (line 222) | fn line_x(view: &View, col: usize) -> Option<i16> {
  function draw_text_cursor (line 233) | fn draw_text_cursor(
  function draw_block_cursor (line 254) | fn draw_block_cursor(
  function cursor_color (line 278) | fn cursor_color(active: bool, flash_timer: Option<u32>, presentation: &P...
  function draw_glyph (line 297) | fn draw_glyph(
  function draw_rect (line 344) | fn draw_rect(vertices: &mut Vec<Vertex>, x: f32, y: f32, w: f32, h: f32,...
  function draw_vline (line 369) | fn draw_vline(vertices: &mut Vec<Vertex>, x: f32, y: f32, h: f32, color:...
  function draw_rect_outline (line 394) | fn draw_rect_outline(
  method draw (line 442) | pub fn draw(
  function rect_to_gl_viewport (line 777) | fn rect_to_gl_viewport(x: i16, y: i16, w: i16, h: i16, viewport_h: i16) ...
  function test_rect_to_gl (line 782) | fn test_rect_to_gl() {
  function should_highlight (line 791) | fn should_highlight(

FILE: src/windows.rs
  function load_proc_memory (line 11) | pub fn load_proc_memory(
  function load_proc_memory_inner (line 33) | unsafe fn load_proc_memory_inner(
  function read_proc_memory (line 62) | pub unsafe fn read_proc_memory(
Condensed preview — 117 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (678K chars).
[
  {
    "path": ".github/workflows/linux.yml",
    "chars": 646,
    "preview": "name: Linux\n\non:\n  push:\n    branches: [ \"main\" ]\n  pull_request:\n    branches: [ \"main\" ]\n\nenv:\n  CARGO_TERM_COLOR: alw"
  },
  {
    "path": ".github/workflows/windows.yml",
    "chars": 493,
    "preview": "name: Windows\n\non:\n  push:\n    branches: [ \"main\" ]\n    tags:\n      - v*\n  pull_request:\n    branches: [ \"main\" ]\n\nenv:\n"
  },
  {
    "path": ".gitignore",
    "chars": 8,
    "preview": "/target\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 9121,
    "preview": "# Changelog\n\n## [0.4.0] - 2025-03-29\n\n### Tutorial\n\nThere is now a basic tutorial you can find here:\n<https://crumblings"
  },
  {
    "path": "Cargo.toml",
    "chars": 2881,
    "preview": "[package]\nname = \"hexerator\"\nversion = \"0.5.0-dev\"\nedition = \"2024\"\nlicense = \"MIT OR Apache-2.0\"\n\n[features]\nbackend-sf"
  },
  {
    "path": "LICENSE-APACHE",
    "chars": 11372,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "LICENSE-MIT",
    "chars": 1099,
    "preview": "MIT License\n\nCopyright (c) 2022 crumblingstatue and Hexerator contributors\n\nPermission is hereby granted, free of charge"
  },
  {
    "path": "README.md",
    "chars": 680,
    "preview": "# Hexerator\nVersatile GUI hex editor focused on binary file exploration and aiding pattern recognition. Written in Rust."
  },
  {
    "path": "build.rs",
    "chars": 707,
    "preview": "use {\n    std::error::Error,\n    vergen_gitcl::{BuildBuilder, CargoBuilder, Emitter, GitclBuilder, RustcBuilder},\n};\n\nfn"
  },
  {
    "path": "hexerator-plugin-api/Cargo.toml",
    "chars": 90,
    "preview": "[package]\nname = \"hexerator-plugin-api\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]"
  },
  {
    "path": "hexerator-plugin-api/src/lib.rs",
    "chars": 1478,
    "preview": "pub trait Plugin {\n    fn name(&self) -> &str;\n    fn desc(&self) -> &str;\n    fn methods(&self) -> Vec<PluginMethod>;\n "
  },
  {
    "path": "lua/color.lua",
    "chars": 109,
    "preview": "return function(b)\n    local r = b\n    local g = b\n    local b = b\n    return {r % 256, g % 256, b % 256}\nend"
  },
  {
    "path": "lua/fill.lua",
    "chars": 111,
    "preview": "-- Return a byte based on offset `off` and the current byte value `b`\nfunction(off, b)\n    return off % 256\nend"
  },
  {
    "path": "plugins/hello-world/Cargo.toml",
    "chars": 175,
    "preview": "[package]\nname = \"hello-world\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[dependencies]\nhexerat"
  },
  {
    "path": "plugins/hello-world/src/lib.rs",
    "chars": 2845,
    "preview": "//! Hexerator hello world example plugin\n\nuse hexerator_plugin_api::{\n    HexeratorHandle, MethodParam, MethodResult, Pl"
  },
  {
    "path": "rust-toolchain.toml",
    "chars": 108,
    "preview": "[toolchain]\nchannel = \"nightly\"\n# Lock to a specific nightly before release\n#channel = \"nightly-2025-03-22\"\n"
  },
  {
    "path": "rustfmt.toml",
    "chars": 66,
    "preview": "imports_granularity = \"One\"\ngroup_imports = \"One\"\nchain_width = 80"
  },
  {
    "path": "scripts/gen-prim-test-file.rs",
    "chars": 1217,
    "preview": "#!/usr/bin/env -S cargo +nightly -Zscript\n\nuse std::{fs::File, io::Write};\n\nfn main() {\n    let mut f = File::create(\"te"
  },
  {
    "path": "src/app/backend_command.rs",
    "chars": 1627,
    "preview": "//! This module is similar in purpose to [`crate::app::command`].\n//!\n//! See that module for more information.\n\nuse {\n "
  },
  {
    "path": "src/app/command.rs",
    "chars": 5993,
    "preview": "//! Due to various issues with overlapping borrows, it's not always feasible to do every operation\n//! on the applicatio"
  },
  {
    "path": "src/app/debug.rs",
    "chars": 304,
    "preview": "#![allow(unused_imports)]\nuse {\n    super::App,\n    gamedebug_core::{imm, imm_dbg},\n};\n\nimpl App {\n    /// Central place"
  },
  {
    "path": "src/app/edit_state.rs",
    "chars": 1679,
    "preview": "#[derive(Default, Debug)]\npub struct EditState {\n    // The editing byte offset\n    pub cursor: usize,\n    cursor_histor"
  },
  {
    "path": "src/app/interact_mode.rs",
    "chars": 354,
    "preview": "/// User interaction mode\n///\n/// There are 2 modes: View and Edit\n#[derive(PartialEq, Eq, Debug)]\npub enum InteractMode"
  },
  {
    "path": "src/app/presentation.rs",
    "chars": 723,
    "preview": "use {\n    crate::{\n        color::{RgbaColor, rgba},\n        value_color::ColorMethod,\n    },\n    serde::{Deserialize, S"
  },
  {
    "path": "src/app.rs",
    "chars": 53309,
    "preview": "use {\n    self::{\n        backend_command::BackendCommandQueue,\n        command::{Cmd, CommandQueue},\n        edit_state"
  },
  {
    "path": "src/args.rs",
    "chars": 4415,
    "preview": "use {\n    crate::parse_radix::parse_guess_radix,\n    clap::Parser,\n    serde::{Deserialize, Serialize},\n    std::path::P"
  },
  {
    "path": "src/backend/sfml.rs",
    "chars": 902,
    "preview": "use {\n    crate::{\n        color::{RgbColor, RgbaColor},\n        view::{ViewportScalar, ViewportVec},\n    },\n    egui_sf"
  },
  {
    "path": "src/backend.rs",
    "chars": 43,
    "preview": "#[cfg(feature = \"backend-sfml\")]\nmod sfml;\n"
  },
  {
    "path": "src/color.rs",
    "chars": 1339,
    "preview": "use serde::{Deserialize, Serialize};\n\n#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]\npub struct Rg"
  },
  {
    "path": "src/config.rs",
    "chars": 3953,
    "preview": "use {\n    crate::{args::SourceArgs, result_ext::AnyhowConv as _},\n    anyhow::Context as _,\n    directories::ProjectDirs"
  },
  {
    "path": "src/damage_region.rs",
    "chars": 784,
    "preview": "pub enum DamageRegion {\n    Single(usize),\n    Range(std::ops::Range<usize>),\n    RangeInclusive(std::ops::RangeInclusiv"
  },
  {
    "path": "src/data.rs",
    "chars": 5469,
    "preview": "use {\n    crate::{damage_region::DamageRegion, meta::region::Region},\n    std::ops::{Deref, DerefMut},\n};\n\n/// The data "
  },
  {
    "path": "src/dec_conv.rs",
    "chars": 800,
    "preview": "fn byte_10_digits(byte: u8) -> [u8; 3] {\n    [byte / 100, (byte % 100) / 10, byte % 10]\n}\n\n#[test]\nfn test_byte_10_digit"
  },
  {
    "path": "src/edit_buffer.rs",
    "chars": 1982,
    "preview": "use gamedebug_core::per;\n\n#[derive(Debug, Default, Clone)]\npub struct EditBuffer {\n    pub buf: Vec<u8>,\n    pub cursor:"
  },
  {
    "path": "src/find_util.rs",
    "chars": 1808,
    "preview": "pub fn find_hex_string(\n    hex_string: &str,\n    haystack: &[u8],\n    mut f: impl FnMut(usize),\n) -> anyhow::Result<()>"
  },
  {
    "path": "src/gui/bottom_panel.rs",
    "chars": 9944,
    "preview": "use {\n    super::{Gui, dialogs::JumpDialog, egui_ui_ext::EguiResponseExt as _},\n    crate::{\n        app::{App, interact"
  },
  {
    "path": "src/gui/command.rs",
    "chars": 2804,
    "preview": "//! This module is similar in purpose to [`crate::app::command`].\n//!\n//! See that module for more information.\n\nuse {\n "
  },
  {
    "path": "src/gui/dialogs/auto_save_reload.rs",
    "chars": 1783,
    "preview": "use {\n    crate::{app::App, gui::Dialog, session_prefs::Autoreload},\n    mlua::Lua,\n};\n\n#[derive(Debug)]\npub struct Auto"
  },
  {
    "path": "src/gui/dialogs/jump.rs",
    "chars": 2884,
    "preview": "use {\n    crate::{\n        app::App,\n        gui::Dialog,\n        parse_radix::{Relativity, parse_offset_maybe_relative}"
  },
  {
    "path": "src/gui/dialogs/lua_color.rs",
    "chars": 2600,
    "preview": "use {\n    crate::{app::App, gui::Dialog, value_color::ColorMethod},\n    mlua::{Function, Lua},\n};\n\npub struct LuaColorDi"
  },
  {
    "path": "src/gui/dialogs/lua_fill.rs",
    "chars": 3171,
    "preview": "use {\n    crate::{app::App, gui::Dialog, shell::msg_if_fail},\n    egui_code_editor::{CodeEditor, Syntax},\n    mlua::{Fun"
  },
  {
    "path": "src/gui/dialogs/pattern_fill.rs",
    "chars": 2124,
    "preview": "use {\n    crate::{\n        app::App,\n        damage_region::DamageRegion,\n        find_util,\n        gui::{Dialog, messa"
  },
  {
    "path": "src/gui/dialogs/truncate.rs",
    "chars": 3263,
    "preview": "use {\n    crate::{app::App, gui::Dialog, meta::region::Region},\n    egui::{Button, DragValue},\n    mlua::Lua,\n};\n\npub st"
  },
  {
    "path": "src/gui/dialogs/x86_asm.rs",
    "chars": 2752,
    "preview": "use {\n    crate::{app::App, gui::Dialog},\n    egui::Button,\n    iced_x86::{Decoder, Formatter as _, NasmFormatter},\n    "
  },
  {
    "path": "src/gui/dialogs.rs",
    "chars": 328,
    "preview": "mod auto_save_reload;\nmod jump;\nmod lua_color;\nmod lua_fill;\npub mod pattern_fill;\nmod truncate;\nmod x86_asm;\n\npub use {"
  },
  {
    "path": "src/gui/egui_ui_ext.rs",
    "chars": 692,
    "preview": "pub trait EguiResponseExt {\n    fn on_hover_text_deferred<F, R>(self, text_fun: F) -> Self\n    where\n        F: FnOnce()"
  },
  {
    "path": "src/gui/file_ops.rs",
    "chars": 14742,
    "preview": "use {\n    crate::{\n        app::App,\n        args::{MmapMode, SourceArgs},\n        gui::{message_dialog::MessageDialog, "
  },
  {
    "path": "src/gui/inspect_panel.rs",
    "chars": 21175,
    "preview": "use {\n    super::message_dialog::{Icon, MessageDialog},\n    crate::{\n        app::{App, interact_mode::InteractMode},\n  "
  },
  {
    "path": "src/gui/message_dialog.rs",
    "chars": 6057,
    "preview": "use {\n    crate::app::command::CommandQueue,\n    core::f32,\n    egui::Color32,\n    std::{backtrace::Backtrace, collectio"
  },
  {
    "path": "src/gui/ops.rs",
    "chars": 508,
    "preview": "//! Various common operations that are triggered by gui interactions\n\nuse crate::{gui::windows::RegionsWindow, meta::reg"
  },
  {
    "path": "src/gui/root_ctx_menu.rs",
    "chars": 5550,
    "preview": "use {\n    super::Gui,\n    crate::{app::App, meta::ViewKey, view::ViewportScalar},\n    constcat::concat,\n    egui_phospho"
  },
  {
    "path": "src/gui/selection_menu.rs",
    "chars": 4089,
    "preview": "use {\n    crate::{\n        app::App,\n        damage_region::DamageRegion,\n        gui::{\n            Gui,\n            di"
  },
  {
    "path": "src/gui/top_menu/analysis.rs",
    "chars": 2849,
    "preview": "use {\n    crate::{\n        app::App,\n        gui::{Gui, message_dialog::Icon},\n        shell::msg_if_fail,\n    },\n    co"
  },
  {
    "path": "src/gui/top_menu/cursor.rs",
    "chars": 1281,
    "preview": "use {\n    crate::{\n        app::App,\n        gui::{Gui, dialogs::JumpDialog},\n    },\n    constcat::concat,\n    egui::But"
  },
  {
    "path": "src/gui/top_menu/edit.rs",
    "chars": 5969,
    "preview": "use {\n    crate::{\n        app::{\n            App,\n            command::{Cmd, perform_command},\n        },\n        gui::"
  },
  {
    "path": "src/gui/top_menu/file.rs",
    "chars": 4861,
    "preview": "use {\n    crate::{\n        app::{App, set_clipboard_string},\n        gui::{Gui, dialogs::AutoSaveReloadDialog},\n        "
  },
  {
    "path": "src/gui/top_menu/help.rs",
    "chars": 871,
    "preview": "use {\n    crate::{gui::Gui, shell::msg_if_fail},\n    constcat::concat,\n    egui::{Button, Ui},\n    egui_phosphor::regula"
  },
  {
    "path": "src/gui/top_menu/meta.rs",
    "chars": 4457,
    "preview": "use {\n    crate::{\n        app::App,\n        gui::{Gui, egui_ui_ext::EguiResponseExt as _},\n        shell::msg_if_fail,\n"
  },
  {
    "path": "src/gui/top_menu/perspective.rs",
    "chars": 1,
    "preview": "\n"
  },
  {
    "path": "src/gui/top_menu/plugins.rs",
    "chars": 3453,
    "preview": "use crate::{\n    app::App,\n    gui::{Gui, message_dialog::Icon},\n    plugin::PluginContainer,\n    shell::msg_fail,\n};\n\np"
  },
  {
    "path": "src/gui/top_menu/scripting.rs",
    "chars": 1319,
    "preview": "use {\n    crate::{app::App, gui::Gui, shell::msg_if_fail},\n    mlua::Lua,\n};\n\npub fn ui(\n    ui: &mut egui::Ui,\n    gui:"
  },
  {
    "path": "src/gui/top_menu/view.rs",
    "chars": 4441,
    "preview": "use {\n    crate::{app::App, gui::Gui, hex_ui::Ruler, meta::LayoutMapExt as _},\n    constcat::concat,\n    egui::{\n       "
  },
  {
    "path": "src/gui/top_menu.rs",
    "chars": 7391,
    "preview": "use {crate::shell::msg_if_fail, mlua::Lua};\n\nmod analysis;\nmod cursor;\npub mod edit;\nmod file;\nmod help;\nmod meta;\nmod p"
  },
  {
    "path": "src/gui/top_panel.rs",
    "chars": 9008,
    "preview": "use {\n    super::{\n        Gui, dialogs::LuaColorDialog, egui_ui_ext::EguiResponseExt as _, message_dialog::Icon,\n      "
  },
  {
    "path": "src/gui/windows/about.rs",
    "chars": 4828,
    "preview": "use {\n    super::{WinCtx, WindowOpen},\n    crate::{result_ext::AnyhowConv as _, shell::msg_if_fail},\n    egui_extras::{C"
  },
  {
    "path": "src/gui/windows/bookmarks.rs",
    "chars": 21262,
    "preview": "use {\n    super::{WinCtx, WindowOpen},\n    crate::{\n        app::set_clipboard_string,\n        damage_region::DamageRegi"
  },
  {
    "path": "src/gui/windows/debug.rs",
    "chars": 1366,
    "preview": "use {\n    egui::Ui,\n    gamedebug_core::{IMMEDIATE, PERSISTENT},\n};\n\npub fn ui(ui: &mut Ui) {\n    ui.horizontal(|ui| {\n "
  },
  {
    "path": "src/gui/windows/external_command.rs",
    "chars": 10201,
    "preview": "use {\n    super::{WinCtx, WindowOpen},\n    crate::{\n        result_ext::AnyhowConv as _,\n        shell::{msg_fail, msg_i"
  },
  {
    "path": "src/gui/windows/file_diff_result.rs",
    "chars": 12267,
    "preview": "use {\n    super::{WinCtx, WindowOpen},\n    crate::{\n        app::read_source_to_buf,\n        gui::windows::regions::regi"
  },
  {
    "path": "src/gui/windows/find_dialog.rs",
    "chars": 36884,
    "preview": "use {\n    super::{WinCtx, WindowOpen},\n    crate::{\n        app::{get_clipboard_string, set_clipboard_string},\n        d"
  },
  {
    "path": "src/gui/windows/find_memory_pointers.rs",
    "chars": 5694,
    "preview": "use {\n    super::{WinCtx, WindowOpen},\n    crate::shell::msg_fail,\n    egui_extras::{Column, TableBuilder},\n};\n\n#[derive"
  },
  {
    "path": "src/gui/windows/layouts.rs",
    "chars": 9906,
    "preview": "use {\n    super::{WinCtx, WindowOpen},\n    crate::{\n        app::App,\n        meta::{LayoutKey, LayoutMapExt as _, MetaL"
  },
  {
    "path": "src/gui/windows/lua_console.rs",
    "chars": 4782,
    "preview": "use {\n    super::{WinCtx, WindowOpen},\n    crate::{meta::ScriptKey, scripting::exec_lua, shell::msg_if_fail},\n    std::{"
  },
  {
    "path": "src/gui/windows/lua_editor.rs",
    "chars": 7954,
    "preview": "use {\n    super::{WinCtx, WindowOpen},\n    crate::{\n        app::App,\n        gui::Gui,\n        meta::{Script, ScriptKey"
  },
  {
    "path": "src/gui/windows/lua_help.rs",
    "chars": 1544,
    "preview": "use {\n    super::{WinCtx, WindowOpen},\n    crate::scripting::*,\n    egui::Color32,\n};\n\n#[derive(Default)]\npub struct Lua"
  },
  {
    "path": "src/gui/windows/lua_watch.rs",
    "chars": 1246,
    "preview": "use {super::WinCtx, crate::scripting::exec_lua};\n\npub struct LuaWatchWindow {\n    pub name: String,\n    expr: String,\n  "
  },
  {
    "path": "src/gui/windows/meta_diff.rs",
    "chars": 3802,
    "preview": "use {\n    super::{WinCtx, WindowOpen},\n    crate::{\n        layout::Layout,\n        meta::{\n            LayoutKey, Named"
  },
  {
    "path": "src/gui/windows/open_process.rs",
    "chars": 29275,
    "preview": "use {\n    super::{WinCtx, WindowOpen},\n    crate::{\n        gui::{egui_ui_ext::EguiResponseExt as _, message_dialog::Mes"
  },
  {
    "path": "src/gui/windows/perspectives.rs",
    "chars": 6754,
    "preview": "use {\n    super::{WinCtx, WindowOpen},\n    crate::{\n        app::command::Cmd, gui::windows::regions::region_context_men"
  },
  {
    "path": "src/gui/windows/preferences.rs",
    "chars": 7621,
    "preview": "use {\n    super::{WinCtx, WindowOpen},\n    crate::{\n        app::{App, backend_command::BackendCmd},\n        config::{se"
  },
  {
    "path": "src/gui/windows/regions.rs",
    "chars": 10770,
    "preview": "use {\n    super::{WinCtx, WindowOpen},\n    crate::{\n        app::command::{Cmd, CommandQueue},\n        gui::command::{GC"
  },
  {
    "path": "src/gui/windows/script_manager.rs",
    "chars": 3469,
    "preview": "use {\n    super::{WinCtx, WindowOpen},\n    crate::{\n        app::App,\n        gui::Gui,\n        meta::{ScriptKey, Script"
  },
  {
    "path": "src/gui/windows/structs.rs",
    "chars": 11745,
    "preview": "use {\n    super::WindowOpen,\n    crate::{\n        meta::Meta,\n        struct_meta_item::{Endian, StructMetaItem, StructP"
  },
  {
    "path": "src/gui/windows/vars.rs",
    "chars": 3127,
    "preview": "use {\n    super::{WinCtx, WindowOpen},\n    crate::meta::{VarEntry, VarVal},\n    egui::TextBuffer as _,\n    egui_extras::"
  },
  {
    "path": "src/gui/windows/views.rs",
    "chars": 13662,
    "preview": "use {\n    super::{WinCtx, WindowOpen},\n    crate::{\n        app::{App, command::Cmd},\n        gui::windows::regions::reg"
  },
  {
    "path": "src/gui/windows/zero_partition.rs",
    "chars": 5972,
    "preview": "use {\n    super::{WinCtx, WindowOpen},\n    crate::{\n        gui::egui_ui_ext::EguiResponseExt as _, meta::region::Region"
  },
  {
    "path": "src/gui/windows.rs",
    "chars": 5210,
    "preview": "pub use self::{\n    file_diff_result::FileDiffResultWindow,\n    lua_console::{ConMsg, LuaConsoleWindow},\n    regions::{R"
  },
  {
    "path": "src/gui.rs",
    "chars": 7273,
    "preview": "pub use self::windows::ConMsg;\nuse {\n    self::{\n        command::GCommandQueue, file_ops::FileOps, inspect_panel::Inspe"
  },
  {
    "path": "src/hex_conv.rs",
    "chars": 1565,
    "preview": "fn byte_16_digits(byte: u8) -> [u8; 2] {\n    [byte / 16, byte % 16]\n}\n\n#[test]\nfn test_byte_16_digits() {\n    assert_eq!"
  },
  {
    "path": "src/hex_ui.rs",
    "chars": 3384,
    "preview": "use {\n    crate::{\n        app::interact_mode::InteractMode,\n        color::RgbaColor,\n        meta::{LayoutKey, ViewKey"
  },
  {
    "path": "src/input.rs",
    "chars": 669,
    "preview": "use {\n    egui_sf2g::sf2g::window::{Event, Key},\n    std::collections::HashSet,\n};\n\n#[derive(Default, Debug)]\npub struct"
  },
  {
    "path": "src/layout.rs",
    "chars": 5634,
    "preview": "use {\n    crate::{\n        meta::{PerspectiveMap, RegionMap, ViewKey, ViewMap, region::Region},\n        view::{ViewportR"
  },
  {
    "path": "src/main.rs",
    "chars": 12728,
    "preview": "#![doc(html_no_source)]\n#![feature(\n    try_blocks,\n    generic_const_exprs,\n    macro_metavar_expr_concat,\n    default_"
  },
  {
    "path": "src/meta/perspective.rs",
    "chars": 2642,
    "preview": "use {\n    super::region::Region,\n    crate::meta::{RegionKey, RegionMap},\n    serde::{Deserialize, Serialize},\n};\n\n/// A"
  },
  {
    "path": "src/meta/region.rs",
    "chars": 1217,
    "preview": "use serde::{Deserialize, Serialize};\n\n/// An inclusive region spanning `begin` to `end`\n#[derive(Clone, Copy, Debug, Ser"
  },
  {
    "path": "src/meta/value_type.rs",
    "chars": 6798,
    "preview": "use {\n    serde::{Deserialize, Serialize},\n    std::collections::HashMap,\n};\n\n#[derive(Serialize, Deserialize, Clone, De"
  },
  {
    "path": "src/meta.rs",
    "chars": 9120,
    "preview": "use {\n    self::{perspective::Perspective, region::Region, value_type::ValueType},\n    crate::{layout::Layout, struct_me"
  },
  {
    "path": "src/meta_state.rs",
    "chars": 559,
    "preview": "use {\n    crate::meta::Meta,\n    std::{cell::Cell, path::PathBuf, time::Instant},\n};\n\npub struct MetaState {\n    pub las"
  },
  {
    "path": "src/parse_radix.rs",
    "chars": 927,
    "preview": "use num_traits::Num;\n\npub fn parse_guess_radix<T: Num>(input: &str) -> Result<T, <T as Num>::FromStrRadixErr> {\n    if l"
  },
  {
    "path": "src/plugin.rs",
    "chars": 2387,
    "preview": "use {\n    crate::{app::App, meta::PerspectiveKey},\n    hexerator_plugin_api::{HexeratorHandle, PerspectiveHandle, Plugin"
  },
  {
    "path": "src/result_ext.rs",
    "chars": 276,
    "preview": "pub trait AnyhowConv<T, E>\nwhere\n    anyhow::Error: From<E>,\n{\n    fn how(self) -> anyhow::Result<T>;\n}\n\nimpl<T, E> Anyh"
  },
  {
    "path": "src/scripting.rs",
    "chars": 17469,
    "preview": "use {\n    crate::{\n        app::App,\n        gui::{ConMsg, Gui},\n        meta::{\n            Bookmark, NamedRegion, Scri"
  },
  {
    "path": "src/session_prefs.rs",
    "chars": 1725,
    "preview": "/// Preferences that only last during the current session, they are not saved\n#[derive(Debug, Default)]\npub struct Sessi"
  },
  {
    "path": "src/shell.rs",
    "chars": 778,
    "preview": "use {\n    crate::{\n        app::App,\n        gui::message_dialog::{Icon, MessageDialog},\n    },\n    std::backtrace::Back"
  },
  {
    "path": "src/slice_ext.rs",
    "chars": 478,
    "preview": "pub trait SliceExt {\n    fn pattern_fill(&mut self, pattern: &Self);\n}\n\nimpl<T: Copy> SliceExt for [T] {\n    fn pattern_"
  },
  {
    "path": "src/source.rs",
    "chars": 2193,
    "preview": "use std::{\n    fs::File,\n    io::{Read, Stdin},\n};\n\n#[derive(Debug)]\npub enum SourceProvider {\n    File(File),\n    Stdin"
  },
  {
    "path": "src/str_ext.rs",
    "chars": 170,
    "preview": "pub trait StrExt {\n    fn is_empty_or_ws_only(&self) -> bool;\n}\n\nimpl StrExt for str {\n    fn is_empty_or_ws_only(&self)"
  },
  {
    "path": "src/struct_meta_item.rs",
    "chars": 4581,
    "preview": "use serde::{Deserialize, Serialize};\n\n#[derive(Serialize, Deserialize, Clone)]\npub struct StructMetaItem {\n    pub name:"
  },
  {
    "path": "src/timer.rs",
    "chars": 584,
    "preview": "use std::time::{Duration, Instant};\n\n#[derive(Debug)]\npub struct Timer {\n    init_point: Instant,\n    duration: Duration"
  },
  {
    "path": "src/update.rs",
    "chars": 31679,
    "preview": "use {\n    crate::{\n        app::{App, interact_mode::InteractMode},\n        damage_region::DamageRegion,\n        gui::{\n"
  },
  {
    "path": "src/util.rs",
    "chars": 385,
    "preview": "#[expect(\n    clippy::cast_precision_loss,\n    reason = \"This is just an approximation of data size\"\n)]\npub fn human_siz"
  },
  {
    "path": "src/value_color.rs",
    "chars": 11722,
    "preview": "use {\n    crate::color::{RgbColor, rgb},\n    serde::{Deserialize, Serialize},\n    serde_big_array::BigArray,\n    std::pa"
  },
  {
    "path": "src/view/draw.rs",
    "chars": 28296,
    "preview": "use {\n    super::View,\n    crate::{\n        app::{App, presentation::Presentation},\n        color::RgbColor,\n        dec"
  },
  {
    "path": "src/view.rs",
    "chars": 24304,
    "preview": "use {\n    crate::{\n        app::{edit_state::EditState, presentation::Presentation},\n        damage_region::DamageRegion"
  },
  {
    "path": "src/windows.rs",
    "chars": 2290,
    "preview": "use {\n    crate::{\n        App,\n        gui::message_dialog::MessageDialog,\n        source::{Source, SourceAttributes, S"
  },
  {
    "path": "test_files/empty-file",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "test_files/plaintext.txt",
    "chars": 197,
    "preview": "This is a plain text file used to test text rendering in Hexerator.\nIt uses ISO-8859-1 encoding to fit accented characte"
  }
]

About this extraction

This page contains the full source code of the crumblingstatue/hexerator GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 117 files (635.2 KB), approximately 147.0k tokens, and a symbol index with 982 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!