diff --git a/Cargo.lock b/Cargo.lock index 0e505fa..68e9275 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,6 +67,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + [[package]] name = "arboard" version = "3.6.1" @@ -340,6 +346,7 @@ dependencies = [ "encoding_rs", "evalexpr", "hex", + "mmap-io", "ratatui", "regex", "serde", @@ -576,6 +583,15 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "memmap2" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6" +dependencies = [ + "libc", +] + [[package]] name = "mio" version = "1.1.1" @@ -588,6 +604,21 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "mmap-io" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34b926fd173419d630eb9d8ee3a485345237bf699d6f7ea6e5c1ee97cf1fa47" +dependencies = [ + "anyhow", + "cfg-if", + "libc", + "log", + "memmap2", + "parking_lot", + "thiserror", +] + [[package]] name = "objc2" version = "0.6.3" diff --git a/Cargo.toml b/Cargo.toml index d40104c..b377f17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ serde = { version = "1.0.228", features = ["derive"] } shell-words = "1.1.0" toml = "0.9.8" tui-input = "0.14.0" +mmap-io = { version = "0.9.4" } # The profile that 'dist' will build with [profile.dist] diff --git a/src/app.rs b/src/app.rs index 69c15cc..0c3af7e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,11 +1,12 @@ use std::{ - collections::{HashMap, HashSet}, + collections::HashSet, fs::{File, OpenOptions}, - io::{self, Read, Seek, SeekFrom, Write}, + io::{self, Seek, SeekFrom, Write}, path::Path, }; use arboard::Clipboard; +use mmap_io::{MemoryMappedFile, MmapMode}; use ratatui::{Frame, layout::Rect, widgets::ListState}; use crate::{ @@ -26,6 +27,25 @@ pub struct FileInfo { pub name: String, pub r#type: &'static str, pub size: usize, + pub mmap: Option, +} + +impl FileInfo { + /// Get memory mapped file buffer. + /// + /// This slice appears to have all file, but beware it is just a mapping from it and every + /// time you access a page that is not mapped it will load from disk to memory by the OS, + /// which also takes care of unloading it if memory constrained. + pub fn get_buffer(&mut self) -> &[u8] { + if let Some(mmap) = self.mmap.as_mut() { + match mmap.as_slice(0, self.size as u64) { + Ok(slice) => return slice, + Err(_) => (), // TODO: panic ? (file was deleted or changed) + } + } + + return &[]; + } } #[derive(Debug)] @@ -37,7 +57,6 @@ pub struct TextView { } pub struct App { - pub buffer: [u8; APP_CACHE_SIZE], pub calculator: Calculator, pub clipboard: Result, pub command_area: Rect, @@ -62,7 +81,6 @@ pub struct App { impl App { pub fn new() -> Self { App { - buffer: [0u8; APP_CACHE_SIZE], calculator: Calculator::default(), clipboard: Clipboard::new(), command_area: Rect::default(), @@ -107,7 +125,8 @@ impl App { /// this function tries to identify a file type; this is a boilerplate implementation. fn id_file(&mut self) { - self.file_info.r#type = match self.buffer[0] { + let buffer = self.file_info.get_buffer(); + self.file_info.r#type = match buffer[0] { 0x7f => "ELF", 0xca | 0xcf => "Mach-O", 0x4d => "PE", @@ -133,27 +152,31 @@ impl App { let meta = path.metadata()?; - // try to open the file with write permissions. Fallback to read-only otherise. + // We try to open file readwrite to use this later for saving if !read_only && let Ok(file) = OpenOptions::new().read(true).write(true).open(path) { self.file_info.file = Some(file); } else { - let file = OpenOptions::new().read(true).open(path)?; - self.file_info.file = Some(file); self.file_info.is_read_only = true; } - if let Some(f) = self.file_info.file.as_mut() { - self.file_info.size = meta.len() as usize; - self.reader.cache_blocks = self.file_info.size.div_ceil(APP_CACHE_SIZE); - self.reader.pages = self.file_info.size.div_ceil(APP_PAGE_SIZE); - self.reader.page_last = self.reader.pages.saturating_sub(1); - let _bytes_read = f.read(&mut self.buffer)?; - self.id_file(); - self.log(format!( - "filesize: {} (0x{:x})", - self.file_info.size, self.file_info.size - )); + // We map it on memory readonly as changed to mapped memory also changes it on disk + if let Ok(mmap) = MemoryMappedFile::builder(path) + .mode(MmapMode::ReadOnly) + .open() + { + self.file_info.mmap = Some(mmap); + } else { + return Err(std::io::Error::other("could not open file")); } + + self.file_info.size = meta.len() as usize; + + self.id_file(); + self.log(format!( + "filesize: {} (0x{:x})", + self.file_info.size, self.file_info.size + )); + if initial_offset != 0 { self.goto(0); } @@ -172,44 +195,6 @@ impl App { .expect("could not reload the file"); } - /// read one cache page from the file to the cache - pub fn read_chunk_from_file(&mut self, nblock: usize) -> io::Result<()> { - if self.file_info.file.is_none() { - return Err(std::io::Error::other("file not open")); - } - - if let Some(f) = &mut self.file_info.file { - f.seek(SeekFrom::Start((nblock * APP_CACHE_SIZE) as u64))?; - let _ = f.read(&mut self.buffer)?; - self.reader.cache_block_number = nblock; - self.log(format!("read_chunk_from_file({nblock})")); - } - Ok(()) - } - - /// write what's cached to the buffer, but not to the file yet - pub fn write_to_buffer(&mut self, changed: HashMap) { - let ofs = self.hex_view.offset; - let mut total_written = 0usize; - - for (k, v) in &changed { - self.read_chunk_for_offset(*k); - if let Ok(b) = u8::from_str_radix(v, 16) { - let buf_ofs = k % APP_CACHE_SIZE; - self.buffer[buf_ofs] = b; - total_written += 1; - } - } - - App::log( - self, - format!("{} bytes written to buffer: {:?}", total_written, changed), - ); - - // restore state - self.goto(ofs); - } - /// write what's cached to the actual file pub fn write_to_file(&mut self) -> io::Result<()> { if self.file_info.file.is_none() { @@ -240,135 +225,108 @@ impl App { } pub fn read_u8(&mut self, offset: usize) -> Option { - let ofs = offset % APP_CACHE_SIZE; - if offset >= self.file_info.size { return None; } - Some(self.buffer[ofs]) + let buffer = self.file_info.get_buffer(); + Some(buffer[offset]) } pub fn read_i8(&mut self, offset: usize) -> Option { - let ofs = offset % APP_CACHE_SIZE; - if offset >= self.file_info.size { return None; } - Some(self.buffer[ofs] as i8) + let buffer = self.file_info.get_buffer(); + Some(buffer[offset] as i8) } pub fn read_u16(&mut self, offset: usize) -> Option { - let ofs = offset % APP_CACHE_SIZE; - if offset + 1 >= self.file_info.size { return None; } - let b1 = self.buffer[ofs]; - let b2 = self.buffer[ofs + 1]; + let buffer = self.file_info.get_buffer(); + let b1 = buffer[offset]; + let b2 = buffer[offset + 1]; Some(u16::from_le_bytes([b1, b2])) } pub fn read_i16(&mut self, offset: usize) -> Option { - let ofs = offset % APP_CACHE_SIZE; - if offset + 1 >= self.file_info.size { return None; } - let b1 = self.buffer[ofs]; - let b2 = self.buffer[ofs + 1]; + let buffer = self.file_info.get_buffer(); + let b1 = buffer[offset]; + let b2 = buffer[offset + 1]; Some(i16::from_le_bytes([b1, b2])) } pub fn read_u32(&mut self, offset: usize) -> Option { - let ofs = offset % APP_CACHE_SIZE; - if offset + 3 >= self.file_info.size { return None; } - let b1 = self.buffer[ofs]; - let b2 = self.buffer[ofs + 1]; - let b3 = self.buffer[ofs + 2]; - let b4 = self.buffer[ofs + 3]; + let buffer = self.file_info.get_buffer(); + let b1 = buffer[offset]; + let b2 = buffer[offset + 1]; + let b3 = buffer[offset + 2]; + let b4 = buffer[offset + 3]; Some(u32::from_le_bytes([b1, b2, b3, b4])) } pub fn read_i32(&mut self, offset: usize) -> Option { - let ofs = offset % APP_CACHE_SIZE; - if offset + 3 >= self.file_info.size { return None; } - let b1 = self.buffer[ofs]; - let b2 = self.buffer[ofs + 1]; - let b3 = self.buffer[ofs + 2]; - let b4 = self.buffer[ofs + 3]; + let buffer = self.file_info.get_buffer(); + let b1 = buffer[offset]; + let b2 = buffer[offset + 1]; + let b3 = buffer[offset + 2]; + let b4 = buffer[offset + 3]; Some(i32::from_le_bytes([b1, b2, b3, b4])) } pub fn read_u64(&mut self, offset: usize) -> Option { - let ofs = offset % APP_CACHE_SIZE; - if offset + 7 >= self.file_info.size { return None; } - let b1 = self.buffer[ofs]; - let b2 = self.buffer[ofs + 1]; - let b3 = self.buffer[ofs + 2]; - let b4 = self.buffer[ofs + 3]; - let b5 = self.buffer[ofs + 4]; - let b6 = self.buffer[ofs + 5]; - let b7 = self.buffer[ofs + 6]; - let b8 = self.buffer[ofs + 7]; + let buffer = self.file_info.get_buffer(); + let b1 = buffer[offset]; + let b2 = buffer[offset + 1]; + let b3 = buffer[offset + 2]; + let b4 = buffer[offset + 3]; + let b5 = buffer[offset + 4]; + let b6 = buffer[offset + 5]; + let b7 = buffer[offset + 6]; + let b8 = buffer[offset + 7]; Some(u64::from_le_bytes([b1, b2, b3, b4, b5, b6, b7, b8])) } pub fn read_i64(&mut self, offset: usize) -> Option { - let ofs = offset % APP_CACHE_SIZE; - if offset + 7 >= self.file_info.size { return None; } - let b1 = self.buffer[ofs]; - let b2 = self.buffer[ofs + 1]; - let b3 = self.buffer[ofs + 2]; - let b4 = self.buffer[ofs + 3]; - let b5 = self.buffer[ofs + 4]; - let b6 = self.buffer[ofs + 5]; - let b7 = self.buffer[ofs + 6]; - let b8 = self.buffer[ofs + 7]; + let buffer = self.file_info.get_buffer(); + let b1 = buffer[offset]; + let b2 = buffer[offset + 1]; + let b3 = buffer[offset + 2]; + let b4 = buffer[offset + 3]; + let b5 = buffer[offset + 4]; + let b6 = buffer[offset + 5]; + let b7 = buffer[offset + 6]; + let b8 = buffer[offset + 7]; Some(i64::from_le_bytes([b1, b2, b3, b4, b5, b6, b7, b8])) } - - pub fn read_chunk_for_offset(&mut self, offset: usize) { - let nblock = offset / APP_CACHE_SIZE; - - if offset > self.reader.cache_end { - self.read_chunk_from_file(nblock).unwrap(); - self.reader.cache_start += nblock * APP_CACHE_SIZE; - self.reader.cache_end += nblock * APP_CACHE_SIZE; - } else if offset < self.reader.cache_start { - self.read_chunk_from_file(nblock).unwrap(); - self.reader.cache_start -= APP_CACHE_SIZE; - self.reader.cache_end -= APP_CACHE_SIZE; - } else if offset == 0 { - self.reader.cache_start = 0; - self.reader.cache_end = APP_CACHE_SIZE - 1; - self.reader.page_start = 0; - self.reader.page_end = APP_PAGE_SIZE - 1; - } - } } diff --git a/src/config.rs b/src/config.rs index 1af7c2f..269c3c6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,11 +1,5 @@ use crate::themes::*; -// cache block size -pub const APP_CACHE_SIZE: usize = 4096; - -// page size (amount of bytes) -pub const APP_PAGE_SIZE: usize = APP_CACHE_SIZE / 4; - // command input history size pub const CMD_INPUT_HIST_SIZE: usize = 50; diff --git a/src/global/goto.rs b/src/global/goto.rs index 354d975..8cbcd95 100644 --- a/src/global/goto.rs +++ b/src/global/goto.rs @@ -1,37 +1,55 @@ -use crate::config::APP_PAGE_SIZE; -use crate::{app::App, config::*}; +use crate::app::App; impl App { - /// The goto() function checks if the received offset is cached, otherwise - /// it calls .read_chunk_from_file to fill the right cache block. + /// The goto() function handles moving page position and smooth transition between pages pub fn goto(&mut self, offset: usize) { if offset >= self.file_info.size { return; } - // If offset is not cached, read and cache the - // block containing the offset - if offset > self.reader.cache_end { - let nblock = offset / APP_CACHE_SIZE; - self.read_chunk_from_file(nblock).unwrap(); - self.reader.cache_start += nblock * APP_CACHE_SIZE; - self.reader.cache_end += nblock * APP_CACHE_SIZE; - } else if offset < self.reader.cache_start { - self.read_chunk_from_file(offset / APP_CACHE_SIZE).unwrap(); - self.reader.cache_start -= APP_CACHE_SIZE; - self.reader.cache_end -= APP_CACHE_SIZE; - } - - // If offset is zero, go to it (it should be cached anyway) if offset == 0 { - self.reader.cache_start = 0; - self.reader.cache_end = APP_CACHE_SIZE - 1; + // If offset is zero, go to it self.reader.page_start = 0; - self.reader.page_end = APP_PAGE_SIZE - 1; + } else if offset >= self.reader.page_start && offset <= self.reader.page_end { + // No need to change page position + } else if offset > self.hex_view.offset { + // Scrolling down + if offset > self.reader.page_end + && self.reader.page_end + self.config.hex_mode_bytes_per_line > offset + { + self.reader.page_start += self.config.hex_mode_bytes_per_line; + } else if offset - self.hex_view.offset == self.reader.page_current_size { + self.reader.page_start += self.reader.page_current_size; + } else { + self.reader.page_start = + offset / self.reader.page_current_size * self.reader.page_current_size; + } } else { - // Offset is not zero, but is cached. Just go there. - self.reader.page_start = APP_PAGE_SIZE * (offset / APP_PAGE_SIZE); - self.reader.page_end = APP_PAGE_SIZE - 1; + if offset < self.reader.page_start + && offset + self.config.hex_mode_bytes_per_line >= self.reader.page_start + { + self.reader.page_start = offset / self.config.hex_mode_bytes_per_line + * self.config.hex_mode_bytes_per_line; + } else if self.hex_view.offset - offset == self.reader.page_current_size { + if self.reader.page_start > self.reader.page_current_size { + self.reader.page_start -= self.reader.page_current_size; + } else { + self.reader.page_start = 0; + } + } else { + self.reader.page_start = + offset / self.reader.page_current_size * self.reader.page_current_size; + } + } + + self.reader.page_end = self.reader.page_start + self.reader.page_current_size - 1; + + if self.reader.page_end > self.file_info.size { + self.reader.page_start = (self.file_info.size - self.reader.page_current_size + + self.config.hex_mode_bytes_per_line) + / self.config.hex_mode_bytes_per_line + * self.config.hex_mode_bytes_per_line; + self.reader.page_end = self.reader.page_start + self.reader.page_current_size - 1; } // Update the cursor @@ -45,22 +63,6 @@ impl App { // Update offset self.hex_view.offset = offset; - // Update offset location in cache. (offset % APP_CACHE_SIZE) / APP_PAGE_SIZE) - // give the page number within the cache block, then I multiply it by - // the page size to know how much I have to advance in cache to render - self.reader.offset_location_in_cache = - ((offset % APP_CACHE_SIZE) / APP_PAGE_SIZE) * APP_PAGE_SIZE; - - self.reader.page_current = offset / APP_PAGE_SIZE; - - let page_is_aligned = self.file_info.size.is_multiple_of(APP_PAGE_SIZE); - - self.reader.page_current_size = - if self.reader.page_current == self.reader.page_last && !page_is_aligned { - self.file_info.size % APP_PAGE_SIZE - } else { - APP_PAGE_SIZE - }; App::log(self, format!("goto: {:x}", offset)); } } diff --git a/src/hex/draw.rs b/src/hex/draw.rs index ce39aa0..216a0a0 100644 --- a/src/hex/draw.rs +++ b/src/hex/draw.rs @@ -4,19 +4,16 @@ use ratatui::{ widgets::{Cell, Clear, Row, Table}, }; -use crate::{app::App, config::APP_PAGE_SIZE, editor::UIState}; +use crate::{app::App, editor::UIState}; // Left column with offsets pub fn draw_hex_offsets(app: &mut App, frame: &mut Frame, area: Rect) { // Offset lines - let mut rows: Vec = Vec::with_capacity(APP_PAGE_SIZE / app.config.hex_mode_bytes_per_line); + let mut rows: Vec = + Vec::with_capacity(app.reader.page_current_size / app.config.hex_mode_bytes_per_line); let mut ofs = app.reader.page_start; - let num_offsets = app - .reader - .page_current_size - .div_ceil(app.config.hex_mode_bytes_per_line); - for _ in 0..num_offsets { + for _ in 0..frame.area().height { rows.push(Row::new([format!("{ofs:08X}")])); ofs += app.config.hex_mode_bytes_per_line; } @@ -33,16 +30,17 @@ pub fn draw_hex_offsets(app: &mut App, frame: &mut Frame, area: Rect) { // Middle area with the actual hex dump // TODO: refactor this as I did for draw_hex_ascii() pub fn draw_hex_contents(app: &mut App, frame: &mut Frame, area: Rect) { - let mut rows: Vec = Vec::with_capacity(APP_PAGE_SIZE / app.config.hex_mode_bytes_per_line); + let mut rows: Vec = + Vec::with_capacity(app.reader.page_current_size / app.config.hex_mode_bytes_per_line); // A cell for each byte as they need different styles when edited - let mut byte_row: Vec = Vec::with_capacity(APP_PAGE_SIZE); + let mut byte_row: Vec = Vec::with_capacity(app.reader.page_current_size); let mut cell_hl_style = app.config.theme.highlight; let mut byte_style = app.config.theme.main; - for (i, byte) in app - .buffer + let buffer = app.file_info.get_buffer(); + for (i, byte) in buffer .iter() - .skip(app.reader.offset_location_in_cache) + .skip(app.reader.page_start) .take(app.reader.page_current_size) .enumerate() { @@ -149,10 +147,10 @@ pub fn draw_hex_ascii(app: &mut App, frame: &mut Frame, area: Rect) { app.config.theme.highlight }; - for (i, byte) in app - .buffer + let buffer = app.file_info.get_buffer(); + for (i, byte) in buffer .iter() - .skip(app.reader.offset_location_in_cache) + .skip(app.reader.page_start) .take(app.reader.page_current_size) .enumerate() { diff --git a/src/hex/edit.rs b/src/hex/edit.rs index 45817e6..052054f 100644 --- a/src/hex/edit.rs +++ b/src/hex/edit.rs @@ -121,10 +121,6 @@ pub fn edit_events(app: &mut App, key: KeyEvent) -> Result { } } KeyCode::Enter => { - // Escreve no arquivo somente se houver algo para escrever - if !app.hex_view.changed_bytes.is_empty() { - app.write_to_buffer(app.hex_view.changed_bytes.clone()); - } app.state = UIState::Normal; app.hex_view.editing_hex = true; // just in case it was in ASCII before } diff --git a/src/hex/events.rs b/src/hex/events.rs index 308784c..e2b88ee 100644 --- a/src/hex/events.rs +++ b/src/hex/events.rs @@ -1,4 +1,4 @@ -use crate::{app::App, commands::Commands, config::APP_PAGE_SIZE, editor::UIState, hex}; +use crate::{app::App, commands::Commands, editor::UIState, hex}; use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use std::io::Result; @@ -25,11 +25,7 @@ pub fn hex_mode_events(app: &mut App, key: KeyEvent) -> Result { app.goto(0); break; } - app.read_chunk_for_offset(ofs); } - - // restore original chunk - app.read_chunk_for_offset(app.hex_view.offset); } // it is important to call goto as it looks for the offset in the @@ -101,12 +97,12 @@ pub fn hex_mode_events(app: &mut App, key: KeyEvent) -> Result { } // go down one page KeyCode::PageDown => { - app.goto(app.hex_view.offset + APP_PAGE_SIZE); + app.goto(app.hex_view.offset + app.reader.page_current_size); } // go up one page KeyCode::PageUp => { - if app.hex_view.offset > APP_PAGE_SIZE { - app.goto(app.hex_view.offset - APP_PAGE_SIZE); + if app.hex_view.offset > app.reader.page_current_size { + app.goto(app.hex_view.offset - app.reader.page_current_size); } else { app.goto(0); } diff --git a/src/hex/search.rs b/src/hex/search.rs index b89d31c..61ffbdf 100644 --- a/src/hex/search.rs +++ b/src/hex/search.rs @@ -1,5 +1,5 @@ use crate::widgets::{Message, MessageType}; -use crate::{app::App, config::APP_CACHE_SIZE, editor::UIState}; +use crate::{app::App, editor::UIState}; use ratatui::Frame; use ratatui::crossterm::event::{Event, KeyCode}; use ratatui::widgets::Paragraph; @@ -44,18 +44,13 @@ pub fn hex_string_to_u8(hex_string: &str) -> Option> { pub fn search>(app: &mut App, needle: T) -> Option { let text = needle.as_ref(); let siz = text.len(); - let nblock = app.reader.cache_block_number; - for block in nblock..=app.reader.cache_blocks { - for (i, win) in app.buffer.windows(siz).enumerate() { - let ofs = i + app.reader.cache_block_number * APP_CACHE_SIZE; - if win == text && ofs > app.hex_view.offset { - return Some(ofs); - } + let buffer = app.file_info.get_buffer(); + for (i, win) in buffer[app.hex_view.offset + 1..].windows(siz).enumerate() { + if win == text { + return Some(app.hex_view.offset + i + 1); } - let _ = app.read_chunk_from_file(block); } - // restore previous block to buffer - let _ = app.read_chunk_from_file(nblock); + None } diff --git a/src/hex/strings.rs b/src/hex/strings.rs index a15bead..2e5d081 100644 --- a/src/hex/strings.rs +++ b/src/hex/strings.rs @@ -10,9 +10,7 @@ use tui_input::backend::crossterm::EventHandler; use std::io::Result; -use crate::{ - app::App, commands::Commands, config::APP_CACHE_SIZE, editor::UIState, util::center_widget, -}; +use crate::{app::App, commands::Commands, editor::UIState, util::center_widget}; use regex::{Regex, RegexBuilder}; @@ -193,9 +191,6 @@ impl Commands { let mut siz = 0; let mut candidate = String::new(); - // Save the original loaded block number (will be restored later) - let original_block_number = app.reader.cache_block_number; - // Read the entire file by blocks and find strings in them let default_regex = Regex::new(".*").unwrap(); @@ -205,32 +200,26 @@ impl Commands { .build() .unwrap_or(default_regex); - 'outer: for block in 0..app.reader.cache_blocks { - let _ = app.read_chunk_from_file(block); - for (i, byte) in app.buffer.iter().enumerate() { - if byte.is_ascii_graphic() || *byte == b' ' { - candidate.push(*byte as char); - siz += 1; - } else { - if siz >= app.config.minimum_string_length && re.is_match(&candidate) { - let ofs = i + APP_CACHE_SIZE * block - siz; - app.strings.push(FoundString { - offset: ofs, - content: candidate.clone(), - size: siz, - }); - if app.strings.len() >= app.config.maximum_strings_to_show { - // too many strings :( - break 'outer; - } + let buffer = app.file_info.get_buffer(); + for (offset, byte) in buffer.iter().enumerate() { + if byte.is_ascii_graphic() || *byte == b' ' { + candidate.push(*byte as char); + siz += 1; + } else { + if siz >= app.config.minimum_string_length && re.is_match(&candidate) { + app.strings.push(FoundString { + offset: offset - siz, + content: candidate.clone(), + size: siz, + }); + if app.strings.len() >= app.config.maximum_strings_to_show { + // too many strings :( + break; } - candidate.clear(); - siz = 0; } + candidate.clear(); + siz = 0; } } - - // Restore previously loaded block - let _ = app.read_chunk_from_file(original_block_number); } } diff --git a/src/main.rs b/src/main.rs index a1f1b50..6c4038a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,7 +58,16 @@ fn main() { while app.running { terminal - .draw(|f| draw::draw(f, &mut app)) + .draw(|f| { + // Page size is dynamic calculated as: + // frame height - (command line + status line + header) * bytes per line + let page_size = (f.area().height - 3) as usize * app.config.hex_mode_bytes_per_line; + if page_size != app.reader.page_current_size { + app.reader.page_current_size = page_size; + app.reader.page_end = app.reader.page_start + page_size - 1; + } + draw::draw(f, &mut app) + }) .expect("failed to draw frame"); events::handle_events(&mut app).expect("unable to read events"); diff --git a/src/reader.rs b/src/reader.rs index bc25dee..a0c0127 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1,31 +1,20 @@ -use crate::config::{APP_CACHE_SIZE, APP_PAGE_SIZE}; - -/// Reader is the class that implements the file -/// reader and buffering. It reads the file in blocks -/// of APP_CACHE_SIZE, but keep track of the page -/// so drawing functions in hex/draw.rs can render -/// a subset of what's in the cache to speed things up. +/// Reader is the class that keep tracks on current page size +/// and its location in the memory mapped file. #[derive(Default, Debug)] pub struct Reader { - pub cache_block_number: usize, - pub cache_blocks: usize, - pub cache_start: usize, - pub cache_end: usize, - pub offset_location_in_cache: usize, pub page_current_size: usize, - pub page_current: usize, - pub page_last: usize, pub page_start: usize, pub page_end: usize, - pub pages: usize, } impl Reader { pub fn new() -> Self { Reader { - cache_end: APP_CACHE_SIZE - 1, - page_end: APP_PAGE_SIZE - 1, + // We just initialize this to a non zero value to avoid a division by zero + // on application startup + page_current_size: 1, + page_end: 1, ..Default::default() } } diff --git a/src/text/draw.rs b/src/text/draw.rs index 0078feb..bd94aa6 100644 --- a/src/text/draw.rs +++ b/src/text/draw.rs @@ -4,13 +4,17 @@ use ratatui::{ widgets::{Clear, Paragraph, Wrap}, }; -use crate::{app::App, config::APP_CACHE_SIZE}; +use crate::app::App; // FIXME: Show the entire file contents in text view. Currently, // it only shows up to APP_CACHE_SIZE bytes from the file. pub fn text_contents_draw(app: &mut App, frame: &mut Frame, area: Rect) { - let limit = app.file_info.size.min(APP_CACHE_SIZE); - let (mut text, _, had_error) = app.text_view.table.decode(&app.buffer[..limit]); + let buffer = app.file_info.get_buffer(); + let limit = (area.height * area.width) as usize; + let (mut text, _, had_error) = app + .text_view + .table + .decode(&buffer[app.reader.page_start..app.reader.page_start + limit]); if had_error { text = text diff --git a/src/text/events.rs b/src/text/events.rs index a0bea87..ec12006 100644 --- a/src/text/events.rs +++ b/src/text/events.rs @@ -2,7 +2,7 @@ use std::io::Result; use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; -use crate::{app::App, config::*, editor::UIState, text}; +use crate::{app::App, editor::UIState, text}; pub fn text_mode_events(app: &mut App, key: KeyEvent) -> Result { match key.code { @@ -25,14 +25,14 @@ pub fn text_mode_events(app: &mut App, key: KeyEvent) -> Result { App::log(app, format!("{:#?}", app.text_view)); } KeyCode::PageUp => { - if app.hex_view.offset < APP_CACHE_SIZE { + if app.hex_view.offset < app.reader.page_current_size { app.goto(0); } else { - app.goto(app.hex_view.offset - APP_CACHE_SIZE); + app.goto(app.hex_view.offset - app.reader.page_current_size); } } KeyCode::PageDown => { - app.goto(app.hex_view.offset + APP_CACHE_SIZE); + app.goto(app.hex_view.offset + app.reader.page_current_size); } KeyCode::Left => { if app.text_view.scroll_offset.1 > 0 {