diff options
| author | Namilskyy <alive6863@gmail.com> | 2025-04-01 22:29:47 +0300 |
|---|---|---|
| committer | Namilskyy <alive6863@gmail.com> | 2025-04-01 22:31:49 +0300 |
| commit | c9bca4946f43629e9b5a3f6549dc3851c6e713b3 (patch) | |
| tree | 5548312adc31c3299f1c74d7b0dee6589ff0214e | |
| parent | 0b069b828d117655053bd8972e32424ec78cbdb5 (diff) | |
Implemented the scrolling fucntion
| -rw-r--r-- | Cargo.lock | 556 | ||||
| -rw-r--r-- | render1.rs | 117 | ||||
| -rw-r--r-- | src/hex.rs | 48 | ||||
| -rw-r--r-- | src/input.rs | 25 | ||||
| -rw-r--r-- | src/render.rs | 133 |
5 files changed, 833 insertions, 46 deletions
diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..021dd8e --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,556 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", +] + +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "libc" +version = "0.2.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "mew" +version = "0.2.0" +dependencies = [ + "crossterm", + "ratatui", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ratatui" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" +dependencies = [ + "bitflags", + "cassowary", + "compact_str", + "crossterm", + "itertools 0.12.1", + "lru", + "paste", + "stability", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width", +] + +[[package]] +name = "redox_syscall" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" + +[[package]] +name = "stability" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools 0.13.0", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/render1.rs b/render1.rs new file mode 100644 index 0000000..3f92c20 --- /dev/null +++ b/render1.rs @@ -0,0 +1,117 @@ +use std::io; +use ratatui::{ + layout::{Constraint, Direction, Layout}, + style::{Color, Style}, + text::{Line, Span}, + widgets::{ + Block, Borders, Paragraph, ScrollbarState, Scrollbar, ScrollbarOrientation, + }, + Terminal, + prelude::Alignment, + symbols, +}; + +pub fn draw( + terminal: &mut Terminal<impl ratatui::backend::Backend>, + data: &[u8], + cursor: usize, + filename: &str, + scrollbar_state: &mut ScrollbarState, +) -> io::Result<()> { + terminal.draw(|frame| { + let size = frame.size(); + let layout = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Min(1), Constraint::Length(1)]) + .split(size); + + // Исправление: Устанавливаем длину содержимого скролла + let content_length = data.len() * 2; // 2 символа на байт + scrollbar_state.content_length(content_length); + + let visible_lines = layout[0].height as usize; + scrollbar_state.viewport_content_length(visible_lines); + + let lines: Vec<Line> = data.chunks(16).enumerate().map(|(i, chunk)| { + let addr = Span::raw(format!("{:08x} │ ", i * 16)); + let mut hex: Vec<Span> = Vec::new(); + let mut ascii_repr = String::new(); + + for (j, &byte) in chunk.iter().enumerate() { + let hex_str = format!("{:02x}", byte); + let byte_index = i * 16 + j; // Индекс байта в данных + let cursor_byte = cursor / 2; // Байт, на котором находится курсор + + for (k, ch) in hex_str.chars().enumerate() { + let cur_idx = byte_index * 2 + k; // Индекс символа в hex-строке + let style = if cur_idx == cursor { + Style::default().bg(Color::White).fg(Color::Black) + } else { + Style::default() + }; + hex.push(Span::styled(ch.to_string(), style)); + } + + if j < 15 { + let space = if j % 4 == 3 { " " } else { " " }; + hex.push(Span::raw(space)); + } + + ascii_repr.push(if byte.is_ascii_graphic() || byte == b' ' { + byte as char + } else { + '.' + }); + } + + let missing_bytes = 16 - chunk.len(); + let padding = missing_bytes * 3; + let spaces = " ".repeat(padding); + + let ascii = Span::raw(format!("{} │ {}", spaces, ascii_repr)); + + Line::from( + vec![addr] + .into_iter() + .chain(hex) + .chain(std::iter::once(ascii)) + .collect::<Vec<_>>() + ) + }).collect(); + + frame.render_widget( + Paragraph::new(lines).block(Block::default().borders(Borders::ALL)), + layout[0] + ); + + frame.render_stateful_widget( + Scrollbar::default() + .orientation(ScrollbarOrientation::VerticalRight) + .symbols(symbols::scrollbar::VERTICAL), + layout[0], + scrollbar_state, + ); + + // Исправление: Корректное вычисление прогресса + let progress = if data.is_empty() { + 100 + } else { + let cursor_byte = cursor / 2; + (cursor_byte * 100).saturating_div(data.len()) + }; + + let status_text = format!( + "{} \t 0x{:08x} \t {:3}%", + filename, + cursor / 2, + progress + ); + + let status_bar = Paragraph::new( + Line::from(Span::raw(status_text)) + ).alignment(Alignment::Center); + + frame.render_widget(status_bar, layout[1]); + })?; + Ok(()) +}
\ No newline at end of file @@ -1,27 +1,64 @@ use std::{fs, io}; use std::io::stdout; use ratatui::{backend::CrosstermBackend, Terminal}; +use ratatui::widgets::{ScrollbarState, ScrollDirection}; use crossterm::{execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}}; use crate::{input, render}; - + pub struct HexEditor { filename: String, data: Vec<u8>, cursor: usize, - terminal: Terminal<CrosstermBackend<std::io::Stdout>>, + terminal: Terminal<CrosstermBackend<std::io::Stdout>,>, + scrollbar_state: ScrollbarState, + scroll_position: usize } impl HexEditor { + pub fn new(filename: String, data: Vec<u8>) -> io::Result<Self> { enable_raw_mode()?; execute!(stdout(), EnterAlternateScreen)?; let terminal = Terminal::new(CrosstermBackend::new(stdout()))?; - Ok(Self { filename, data, cursor: 0, terminal }) + let line_count = (data.len() + 15) / 16; + let scrollbar_state = ScrollbarState::default() + .content_length(line_count) + .viewport_content_length(10); + + Ok(Self { + filename, + data, + cursor: 0, + terminal, + scrollbar_state, + scroll_position: 0, + }) } + pub fn run(&mut self) -> io::Result<()> { - while input::handle_input(&mut self.cursor, &mut self.data[..])? { - render::draw(&mut self.terminal, &self.data, self.cursor, &self.filename)?; + while input::handle_input( + &mut self.cursor, + &mut self.data, + &mut self.scroll_position + )? { + let total_lines = (self.data.len() + 15) / 16; + let visible_lines = self.terminal.size()?.height as usize; + + + self.scrollbar_state = ScrollbarState::new(total_lines) + .viewport_content_length(visible_lines) + .position(self.scroll_position); + + + render::draw( + &mut self.terminal, + &self.data, + self.cursor, + &self.filename, + self.scroll_position, + &mut self.scrollbar_state + )?; } self.cleanup() } @@ -32,4 +69,5 @@ impl HexEditor { fs::write(&self.filename, &self.data)?; Ok(()) } + } diff --git a/src/input.rs b/src/input.rs index 0db7e16..1258ac0 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,20 +1,35 @@ use std::{io, time::Duration}; use crossterm::{event::{self, KeyCode, KeyEvent}}; -pub fn handle_input(cursor: &mut usize, data: &mut [u8]) -> io::Result<bool> { +pub fn handle_input( + cursor: &mut usize, + data: &mut [u8], + scroll_position: &mut usize +) -> io::Result<bool> { if event::poll(Duration::from_millis(100))? { if let event::Event::Key(KeyEvent { code, .. }) = event::read()? { match code { KeyCode::Esc => return Ok(false), KeyCode::Left if *cursor > 0 => *cursor -= 1, KeyCode::Right if *cursor < data.len() * 2 - 1 => *cursor += 1, - KeyCode::Up if *cursor >= 32 => *cursor -= 32, - KeyCode::Down if *cursor + 32 < data.len() * 2 => *cursor += 32, + KeyCode::Up => { + if *cursor >= 32 { + *cursor -= 32; + *scroll_position = scroll_position.saturating_sub(1); + } + }, + KeyCode::Down => { + if *cursor + 32 < data.len() * 2 { + *cursor += 32; + *scroll_position = scroll_position.saturating_add(1); + } + }, KeyCode::Char(c) if c.is_ascii_hexdigit() => { let byte_index = *cursor / 2; let shift = if *cursor % 2 == 0 { 4 } else { 0 }; - data[byte_index] = (data[byte_index] & (0x0F << (4 - shift))) | (c.to_digit(16).unwrap() as u8) << shift; - } + data[byte_index] = (data[byte_index] & (0x0f << (4 - shift))) + | (c.to_digit(16).unwrap() as u8) << shift; + }, _ => {} } } diff --git a/src/render.rs b/src/render.rs index a0cb234..061dbfe 100644 --- a/src/render.rs +++ b/src/render.rs @@ -3,12 +3,22 @@ use ratatui::{ layout::{Constraint, Direction, Layout}, style::{Color, Style}, text::{Line, Span}, - widgets::{Block, Borders, Paragraph}, + widgets::{ + Block, Borders, Paragraph, ScrollbarState, Scrollbar, ScrollbarOrientation, + }, Terminal, prelude::Alignment, + symbols, }; -pub fn draw(terminal: &mut Terminal<impl ratatui::backend::Backend>, data: &[u8], cursor: usize, filename: &str) -> io::Result<()> { +pub fn draw( + terminal: &mut Terminal<impl ratatui::backend::Backend>, + data: &[u8], + cursor: usize, + filename: &str, + scroll_position: usize, + scrollbar_state: &mut ScrollbarState, +) -> io::Result<()> { terminal.draw(|frame| { let size = frame.size(); let layout = Layout::default() @@ -16,52 +26,103 @@ pub fn draw(terminal: &mut Terminal<impl ratatui::backend::Backend>, data: &[u8] .constraints([Constraint::Min(1), Constraint::Length(1)]) .split(size); - let lines: Vec<Line> = data.chunks(16).enumerate().map(|(i, chunk)| { - let addr = Span::raw(format!("{:08x} │ ", i * 16)); - let mut hex: Vec<Span> = Vec::new(); - let mut ascii_repr = String::new(); - for (j, &byte) in chunk.iter().enumerate() { - let hex_str = format!("{:02x}", byte); - let index = i * 32 + j * 2; + let total_lines = (data.len() + 15) / 16; + let visible_height = layout[0].height as usize; - for (k, ch) in hex_str.chars().enumerate() { - let cur_idx = index + k; - let style = if cur_idx == cursor { - Style::default().bg(Color::White).fg(Color::Black) + *scrollbar_state = ScrollbarState::new(total_lines) + .viewport_content_length(visible_height) + .position(scroll_position); + + let lines: Vec<Line> = data + .chunks(16) + .enumerate() + .skip(scroll_position) + .take(visible_height) + .map(|(i, chunk)| { + let addr = format!("{:08X} │ ", i * 16); + let mut hex_spans = Vec::new(); + let mut ascii = String::new(); + + for (j, &byte) in chunk.iter().enumerate() { + let global_pos = i * 16 + j; + let nibbles = format!("{:02X}", byte); + + for (k, c) in nibbles.chars().enumerate() { + let is_cursor = (global_pos * 2 + k) == cursor; + let style = if is_cursor { + Style::default().bg(Color::White).fg(Color::Black) + } else { + Style::default() + }; + hex_spans.push(Span::styled(c.to_string(), style)); + } + + + if j < 15 { + hex_spans.push(Span::raw(if j % 4 == 3 { " " } else { " " })); + } + + + ascii.push(if byte.is_ascii_graphic() || byte == b' ' { + byte as char } else { - Style::default() - }; - hex.push(Span::styled(ch.to_string(), style)); + '.' + }); } - if j < 15 { - let space = if j % 4 == 3 { " " } else { " " }; - hex.push(Span::raw(space)); + + if chunk.len() < 16 { + let missing = 16 - chunk.len(); + hex_spans.push(Span::raw(" ".repeat(missing * 3))); + ascii.push_str(&".".repeat(missing)); } - ascii_repr.push(if byte.is_ascii_graphic() || byte == b' ' { byte as char } else { '.' }); - } + Line::from(vec![ + Span::raw(addr), + Span::raw(hex_spans.iter().map(|s| s.content.as_ref()).collect::<String>()), + Span::raw(" │ "), + Span::raw(ascii), + ]) + }) + .collect(); + + + frame.render_widget( + Paragraph::new(lines) + .block(Block::default().borders(Borders::ALL)), + layout[0] + ); + - let missing_bytes = 16 - chunk.len(); - let padding = missing_bytes * 3; - let spaces = " ".repeat(padding); + frame.render_stateful_widget( + Scrollbar::default() + .orientation(ScrollbarOrientation::VerticalRight) + .symbols(symbols::scrollbar::VERTICAL), + layout[0], + scrollbar_state, + ); - let ascii = Span::raw(format!("{} │ {}", spaces, ascii_repr)); - Line::from(vec![addr] - .into_iter() - .chain(hex) - .chain(std::iter::once(ascii)) - .collect::<Vec<_>>()) - }).collect(); + let progress = if data.is_empty() { + 0 + } else { + (cursor / 2 * 100) / data.len() + }; - frame.render_widget(Paragraph::new(lines).block(Block::default().borders(Borders::ALL)), layout[0]); + let status = format!( + "{} │ Offset: 0x{:08X} │ {}%", + filename, + cursor / 2, + progress + ); - let progress = if data.is_empty() { 100 } else { (cursor / 2 * 100) / data.len() }; - let status_text = format!("{} \t 0x{:08x} \t {:3}%", filename, cursor / 2, progress); - let status_bar = Paragraph::new(Line::from(Span::raw(status_text))).alignment(Alignment::Center); - frame.render_widget(status_bar, layout[1]); + frame.render_widget( + Paragraph::new(status) + .alignment(Alignment::Center) + .block(Block::default().borders(Borders::TOP)), + layout[1] + ); })?; Ok(()) } |
