Skip to content

Commit 77ca721

Browse files
raaymaxclaude
andcommitted
refactor: code quality improvements from deep review
- Add error logging in history.rs instead of silent failures - Extract apply_selection_style() helper and UI style constants - Remove unused _visible_height parameter from mouse scroll methods - Fix wrap_content to not add redundant continuation indent - Remove event cloning in main loop (pre-compute has_start_filter) - Add MAX_HISTORY_ENTRIES constant - Add ROADMAP.md to repository Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent d01860e commit 77ca721

File tree

8 files changed

+803
-57
lines changed

8 files changed

+803
-57
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/target
22
CLAUDE.md
33
AGENTS.md
4-
ROADMAP.md
54
*.log
65

76
# AUR/makepkg build artifacts

ROADMAP.md

Lines changed: 739 additions & 0 deletions
Large diffs are not rendered by default.

src/app.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ use crate::tab::TabState;
44
#[cfg(test)]
55
use std::path::PathBuf;
66

7+
/// Maximum number of filter history entries to keep
8+
const MAX_HISTORY_ENTRIES: usize = 50;
9+
710
/// Represents the current view mode
811
#[derive(Debug, Clone, PartialEq)]
912
pub enum ViewMode {
@@ -160,14 +163,13 @@ impl App {
160163
}
161164

162165
/// Mouse scroll down
163-
pub fn mouse_scroll_down(&mut self, lines: usize, visible_height: usize) {
164-
self.active_tab_mut()
165-
.mouse_scroll_down(lines, visible_height);
166+
pub fn mouse_scroll_down(&mut self, lines: usize) {
167+
self.active_tab_mut().mouse_scroll_down(lines);
166168
}
167169

168170
/// Mouse scroll up
169-
pub fn mouse_scroll_up(&mut self, lines: usize, visible_height: usize) {
170-
self.active_tab_mut().mouse_scroll_up(lines, visible_height);
171+
pub fn mouse_scroll_up(&mut self, lines: usize) {
172+
self.active_tab_mut().mouse_scroll_up(lines);
171173
}
172174

173175
/// Apply filter results
@@ -227,8 +229,8 @@ impl App {
227229
// Add to history
228230
self.filter_history.push(entry);
229231

230-
// Limit history to 50 entries
231-
if self.filter_history.len() > 50 {
232+
// Limit history size
233+
if self.filter_history.len() > MAX_HISTORY_ENTRIES {
232234
self.filter_history.remove(0);
233235
}
234236

src/filter/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ impl Default for FilterMode {
2626

2727
impl FilterMode {
2828
/// Create a new plain text filter mode (case-insensitive by default)
29-
#[cfg(test)]
29+
#[allow(dead_code)] // Public API for external use and tests
3030
pub fn plain() -> Self {
3131
FilterMode::Plain {
3232
case_sensitive: false,
3333
}
3434
}
3535

3636
/// Create a new regex filter mode (case-insensitive by default)
37-
#[cfg(test)]
37+
#[allow(dead_code)] // Public API for external use and tests
3838
pub fn regex() -> Self {
3939
FilterMode::Regex {
4040
case_sensitive: false,

src/history.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,21 @@ pub fn save_history(history: &[FilterHistoryEntry]) {
3131

3232
// Create parent directory if it doesn't exist
3333
if let Some(parent) = path.parent() {
34-
let _ = fs::create_dir_all(parent);
34+
if let Err(e) = fs::create_dir_all(parent) {
35+
eprintln!("Failed to create config directory: {}", e);
36+
return;
37+
}
3538
}
3639

37-
if let Ok(content) = serde_json::to_string_pretty(history) {
38-
let _ = fs::write(&path, content);
40+
match serde_json::to_string_pretty(history) {
41+
Ok(content) => {
42+
if let Err(e) = fs::write(&path, content) {
43+
eprintln!("Failed to save filter history: {}", e);
44+
}
45+
}
46+
Err(e) => {
47+
eprintln!("Failed to serialize filter history: {}", e);
48+
}
3949
}
4050
}
4151

src/main.rs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -368,8 +368,13 @@ fn run_app<B: ratatui::backend::Backend>(terminal: &mut Terminal<B>, app: &mut A
368368
}
369369
}
370370

371+
// Pre-compute if there's a StartFilter event (affects FileModified handling)
372+
let has_start_filter = events
373+
.iter()
374+
.any(|e| matches!(e, AppEvent::StartFilter { .. }));
375+
371376
// Process all collected events
372-
for event in events.clone() {
377+
for event in events {
373378
// Handle special events that need side effects (like starting filters)
374379
match &event {
375380
AppEvent::StartFilter {
@@ -398,7 +403,7 @@ fn run_app<B: ratatui::backend::Backend>(terminal: &mut Terminal<B>, app: &mut A
398403
| AppEvent::ToggleFilterMode
399404
| AppEvent::ToggleCaseSensitivity => {
400405
// Apply the event first
401-
app.apply_event(event.clone());
406+
app.apply_event(event);
402407

403408
// Then trigger live filter preview
404409
let pattern = app.get_input().to_string();
@@ -416,15 +421,11 @@ fn run_app<B: ratatui::backend::Backend>(terminal: &mut Terminal<B>, app: &mut A
416421
continue; // Event already applied above
417422
}
418423
AppEvent::MouseScrollDown(lines) => {
419-
// Calculate visible height from terminal size
420-
let visible_height = terminal.size()?.height as usize - PAGE_SIZE_OFFSET - 1;
421-
app.mouse_scroll_down(*lines, visible_height);
424+
app.mouse_scroll_down(*lines);
422425
continue; // Event already applied
423426
}
424427
AppEvent::MouseScrollUp(lines) => {
425-
// Calculate visible height from terminal size
426-
let visible_height = terminal.size()?.height as usize - PAGE_SIZE_OFFSET - 1;
427-
app.mouse_scroll_up(*lines, visible_height);
428+
app.mouse_scroll_up(*lines);
428429
continue; // Event already applied
429430
}
430431
AppEvent::ClearFilter => {
@@ -438,7 +439,7 @@ fn run_app<B: ratatui::backend::Backend>(terminal: &mut Terminal<B>, app: &mut A
438439
}
439440
AppEvent::FilterComplete { .. } => {
440441
// Apply the event first
441-
app.apply_event(event.clone());
442+
app.apply_event(event);
442443

443444
// Then handle follow mode jump
444445
if app.active_tab().follow_mode {
@@ -450,11 +451,9 @@ fn run_app<B: ratatui::backend::Backend>(terminal: &mut Terminal<B>, app: &mut A
450451
// For file events, check if we need to jump to end in follow mode
451452
let should_jump_follow = app.active_tab().follow_mode
452453
&& app.active_tab().mode == ViewMode::Normal
453-
&& !events
454-
.iter()
455-
.any(|e| matches!(e, AppEvent::StartFilter { .. }));
454+
&& !has_start_filter;
456455

457-
app.apply_event(event.clone());
456+
app.apply_event(event);
458457

459458
if should_jump_follow {
460459
app.jump_to_end();

src/tab.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,14 +208,14 @@ impl TabState {
208208
}
209209

210210
/// Mouse scroll down - moves viewport and selection together
211-
pub fn mouse_scroll_down(&mut self, lines: usize, _visible_height: usize) {
211+
pub fn mouse_scroll_down(&mut self, lines: usize) {
212212
self.viewport
213213
.scroll_with_selection(lines as i32, &self.line_indices);
214214
self.sync_from_viewport();
215215
}
216216

217217
/// Mouse scroll up - moves viewport and selection together
218-
pub fn mouse_scroll_up(&mut self, lines: usize, _visible_height: usize) {
218+
pub fn mouse_scroll_up(&mut self, lines: usize) {
219219
self.viewport
220220
.scroll_with_selection(-(lines as i32), &self.line_indices);
221221
self.sync_from_viewport();
@@ -552,11 +552,11 @@ mod tests {
552552
assert_eq!(tab.selected_line, 5);
553553

554554
// Mouse scroll down - moves both viewport and selection together
555-
tab.mouse_scroll_down(3, 20);
555+
tab.mouse_scroll_down(3);
556556
assert_eq!(tab.selected_line, 8); // 5 + 3
557557

558558
// Mouse scroll up
559-
tab.mouse_scroll_up(2, 20);
559+
tab.mouse_scroll_up(2);
560560
assert_eq!(tab.selected_line, 6); // 8 - 2
561561
}
562562

src/ui/mod.rs

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,21 @@ use ratatui::{
1010
};
1111
use unicode_width::UnicodeWidthStr;
1212

13+
// UI Style Constants
14+
const SELECTED_BG: Color = Color::DarkGray;
15+
const EXPANDED_BG: Color = Color::Rgb(30, 30, 40);
16+
const LINE_PREFIX_WIDTH: usize = 9; // "{:6} | " = 9 characters
17+
18+
/// Apply selection styling to a span (dark bg, bold, adjust dark foreground colors)
19+
fn apply_selection_style(style: Style) -> Style {
20+
// Adjust foreground if it's too dark to see against DarkGray background
21+
let adjusted = match style.fg {
22+
Some(Color::Gray) | Some(Color::DarkGray) | Some(Color::Black) => style.fg(Color::White),
23+
_ => style,
24+
};
25+
adjusted.bg(SELECTED_BG).add_modifier(Modifier::BOLD)
26+
}
27+
1328
/// Expand tabs to spaces for proper rendering
1429
fn expand_tabs(line: &str) -> String {
1530
if !line.contains('\t') {
@@ -204,10 +219,7 @@ fn render_log_view(f: &mut Frame, area: Rect, app: &mut App) -> Result<()> {
204219

205220
// Calculate available width for content (accounting for borders and line prefix)
206221
let available_width = area.width.saturating_sub(2) as usize; // Account for borders
207-
let prefix_width = 9; // "{:6} | " = 9 characters
208-
209-
// Background color for expanded (non-selected) entries
210-
let expanded_bg = Color::Rgb(30, 30, 40);
222+
let prefix_width = LINE_PREFIX_WIDTH;
211223

212224
// Get reader access and collect expanded_lines snapshot
213225
let mut reader_guard = tab.reader.lock().unwrap();
@@ -228,7 +240,7 @@ fn render_log_view(f: &mut Frame, area: Rect, app: &mut App) -> Result<()> {
228240
if is_expanded && available_width > prefix_width {
229241
// Expanded: wrap content across multiple lines
230242
let content_width = available_width.saturating_sub(prefix_width);
231-
let wrapped_lines = wrap_content(&line_text, content_width, prefix_width);
243+
let wrapped_lines = wrap_content(&line_text, content_width);
232244

233245
let mut item_lines: Vec<Line<'static>> = Vec::new();
234246

@@ -246,17 +258,10 @@ fn render_log_view(f: &mut Frame, area: Rect, app: &mut App) -> Result<()> {
246258
// Apply styling based on selection/expansion state
247259
for span in &mut wrapped_line.spans {
248260
if is_selected {
249-
// Selected: dark gray background + bold
250-
let new_style = match span.style.fg {
251-
Some(Color::Gray) | Some(Color::DarkGray) | Some(Color::Black) => {
252-
span.style.fg(Color::White)
253-
}
254-
_ => span.style,
255-
};
256-
span.style = new_style.bg(Color::DarkGray).add_modifier(Modifier::BOLD);
261+
span.style = apply_selection_style(span.style);
257262
} else {
258263
// Expanded but not selected: subtle dark background
259-
span.style = span.style.bg(expanded_bg);
264+
span.style = span.style.bg(EXPANDED_BG);
260265
}
261266
}
262267

@@ -283,13 +288,7 @@ fn render_log_view(f: &mut Frame, area: Rect, app: &mut App) -> Result<()> {
283288
// Apply selection background if this is the selected line
284289
if is_selected {
285290
for span in &mut final_line.spans {
286-
let new_style = match span.style.fg {
287-
Some(Color::Gray) | Some(Color::DarkGray) | Some(Color::Black) => {
288-
span.style.fg(Color::White)
289-
}
290-
_ => span.style,
291-
};
292-
span.style = new_style.bg(Color::DarkGray).add_modifier(Modifier::BOLD);
291+
span.style = apply_selection_style(span.style);
293292
}
294293
}
295294

@@ -427,8 +426,7 @@ fn render_line_jump_prompt(f: &mut Frame, area: Rect, app: &App) {
427426
/// # Arguments
428427
/// * `content` - The raw content string (may contain ANSI codes)
429428
/// * `available_width` - The width in columns to wrap to
430-
/// * `prefix_width` - The width of the line number prefix (for continuation indent)
431-
fn wrap_content(content: &str, available_width: usize, prefix_width: usize) -> Vec<Line<'static>> {
429+
fn wrap_content(content: &str, available_width: usize) -> Vec<Line<'static>> {
432430
if available_width == 0 {
433431
return vec![Line::default()];
434432
}
@@ -456,10 +454,10 @@ fn wrap_content(content: &str, available_width: usize, prefix_width: usize) -> V
456454
}
457455

458456
// Word wrap the spans
457+
// Note: We don't add continuation indent here - the caller (render_log_view) handles prefixes
459458
let mut result_lines: Vec<Line<'static>> = Vec::new();
460459
let mut current_line_spans: Vec<Span<'static>> = Vec::new();
461460
let mut current_line_width = 0;
462-
let continuation_indent = " ".repeat(prefix_width);
463461

464462
for span in spans {
465463
let span_text = span.content.to_string();
@@ -518,9 +516,8 @@ fn wrap_content(content: &str, available_width: usize, prefix_width: usize) -> V
518516
// Commit current line and start new one
519517
if !current_line_spans.is_empty() {
520518
result_lines.push(Line::from(current_line_spans));
521-
// Add continuation indent for wrapped lines
522-
current_line_spans = vec![Span::raw(continuation_indent.clone())];
523-
current_line_width = prefix_width;
519+
current_line_spans = Vec::new();
520+
current_line_width = 0;
524521
}
525522
}
526523
}

0 commit comments

Comments
 (0)