diff --git a/crates/tui/src/app.rs b/crates/tui/src/app.rs index 74664c7..bf3dd7a 100644 --- a/crates/tui/src/app.rs +++ b/crates/tui/src/app.rs @@ -3,6 +3,45 @@ use std::time::Instant; use log_viewer_core::io::file_reader::FileReader; +/// Split a line into chunks of exactly `width` characters (display columns). +/// For a log viewer, we want character-level wrapping, not word-level. +fn wrap_line_chars(line: &str, width: usize) -> Vec { + if width == 0 { + return vec![String::new()]; + } + if line.is_empty() { + return vec![String::new()]; + } + let mut result = Vec::new(); + let mut row = String::new(); + let mut col = 0; + for ch in line.chars() { + let w = if ch == '\t' { 4 } else { 1 }; + if col + w > width && !row.is_empty() { + result.push(std::mem::take(&mut row)); + col = 0; + } + if ch == '\t' { + row.push_str(" "); + col += 4; + } else { + row.push(ch); + col += w; + } + if col >= width { + result.push(std::mem::take(&mut row)); + col = 0; + } + } + if !row.is_empty() { + result.push(row); + } + if result.is_empty() { + result.push(String::new()); + } + result +} + pub struct App { pub should_quit: bool, @@ -77,8 +116,7 @@ impl App { for i in 0..line_count { let line = self.get_line(i).unwrap_or(""); - let wrapped: Vec> = textwrap::wrap(line, width); - let wrapped: Vec = wrapped.into_iter().map(|c| c.into_owned()).collect(); + let wrapped = wrap_line_chars(line, width); let height = wrapped.len().max(1); self.wrap_cache.push(wrapped); self.visual_heights.push(height);