Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "lumberjack"
version = "0.2.4"
version = "0.3.0"
edition = "2024"

[dependencies]
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ Built in **Rust**, powered by **ratatui**, **crossterm**, and the **AWS SDK for
- `1/2/3/4` for time presets
- `t` to tail
- `y` to copy all results
- `T` to cycle color themes (Dark → Light → Green CRT)
- 🎨 Theme support
- Dark (default)
- Light
- Retro Green CRT (phosphor-style, neon green on black)
- 🌑 Focus-aware panes (Groups / Filter / Results) with clear borders and styles

---
Expand Down Expand Up @@ -79,6 +84,7 @@ cargo run -- --profile=<aws-profile> --region=<aws-region>
- `s` – Save current filter (opens name popup; persists to `~/.config/lumberjack/filters.json`)
- `F` – Load saved filter (opens popup with saved filter names)
- `t` – Toggle tail/stream mode for results
- `T` – Cycle color themes (Dark → Light → Green CRT)
- `Esc` – Cancel editing, group search, or close popups
- `y` – Copy all Results to clipboard (when Results pane is focused)
- `q` – Quit (except while editing or in group search)
9 changes: 5 additions & 4 deletions src/app/clipboard.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use std::time::Instant;

use arboard::Clipboard;

use crate::app::App;
use arboard::Clipboard;
use std::time::Instant;

impl App {
pub fn results_text(&self) -> String {
Expand All @@ -28,6 +26,7 @@ impl App {
#[cfg(test)]
mod tests {
use crate::app::{App, FilterField, Focus};
use crate::ui::styles::Theme;
use std::sync::mpsc;
use std::time::Instant as StdInstant;

Expand All @@ -36,6 +35,8 @@ mod tests {

App {
app_title: "Test".to_string(),
theme: Theme::default_dark(),
theme_name: "dark".to_string(),
exit: false,
lines: lines.into_iter().map(|s| s.to_string()).collect(),
filter_cursor_pos: 0,
Expand Down
3 changes: 3 additions & 0 deletions src/app/filters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ impl App {
mod tests {
use super::*;
use crate::app::{App, Focus};
use crate::ui::styles::Theme;
use std::sync::mpsc;
use std::time::Instant as StdInstant;

Expand All @@ -195,6 +196,8 @@ mod tests {

App {
app_title: "Test".to_string(),
theme: Theme::default_dark(),
theme_name: "dark".to_string(),
exit: false,
lines: Vec::new(),
filter_cursor_pos: 0,
Expand Down
36 changes: 36 additions & 0 deletions src/app/keymap.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::{App, FilterField, Focus};
use crate::ui::styles::Theme;
use ratatui::crossterm::event::{KeyCode, KeyEventKind};
use std::io;

Expand Down Expand Up @@ -192,6 +193,19 @@ impl App {
self.apply_time_preset("-24m");
}

KeyCode::Char('T') if !self.editing => {
if self.theme_name == "dark" {
self.theme = Theme::light();
self.theme_name = "light".to_string();
} else if self.theme_name == "light" {
self.theme = Theme::green();
self.theme_name = "green".to_string();
} else {
self.theme = Theme::default_dark();
self.theme_name = "dark".to_string();
}
}

_ => {}
}

Expand All @@ -202,6 +216,7 @@ impl App {
#[cfg(test)]
mod tests {
use crate::app::{App, FilterField, Focus};
use crate::ui::styles::Theme;
use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use std::sync::mpsc;
use std::time::Instant as StdInstant;
Expand All @@ -215,6 +230,8 @@ mod tests {

App {
app_title: "Test".to_string(),
theme: Theme::default_dark(),
theme_name: "dark".to_string(),
exit: false,
lines: Vec::new(),
filter_cursor_pos: 0,
Expand Down Expand Up @@ -284,4 +301,23 @@ mod tests {
assert_eq!(app.filter_query, "abc");
assert_eq!(app.filter_cursor_pos, 2); // back between 'b' and 'c'
}

#[test]
fn theme_cycles_dark_light_green_on_t() {
let mut app = app_with_filter_query("");
// Ensure starting theme is dark
assert_eq!(app.theme_name, "dark");

// First T: dark -> light
app.handle_key_event(key(KeyCode::Char('T'))).unwrap();
assert_eq!(app.theme_name, "light");

// Second T: light -> green
app.handle_key_event(key(KeyCode::Char('T'))).unwrap();
assert_eq!(app.theme_name, "green");

// Third T: green -> dark
app.handle_key_event(key(KeyCode::Char('T'))).unwrap();
assert_eq!(app.theme_name, "dark");
}
}
5 changes: 5 additions & 0 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::sync::atomic::Ordering;
use std::sync::mpsc::{Receiver, Sender};
use std::time::{Duration, Instant};

use crate::ui::styles::Theme;
use chrono::Utc;
use ratatui::crossterm::event;
use ratatui::prelude::Rect;
Expand Down Expand Up @@ -43,6 +44,8 @@ pub struct SavedFilter {

pub struct App {
pub app_title: String,
pub theme: Theme,
pub theme_name: String,
pub exit: bool,
pub lines: Vec<String>,
pub filter_cursor_pos: usize,
Expand Down Expand Up @@ -493,6 +496,8 @@ mod tests {

App {
app_title: "Test".to_string(),
theme: Theme::default_dark(),
theme_name: "dark".to_string(),
exit: false,
lines: Vec::new(),
filter_cursor_pos: 0,
Expand Down
3 changes: 3 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod app;
mod aws;
mod ui;

use crate::ui::styles::Theme;
use app::{App, FilterField, Focus};
use aws::fetch_log_groups;

Expand Down Expand Up @@ -35,6 +36,8 @@ fn main() -> io::Result<()> {

let mut app = App {
app_title: APP_TITLE.to_string(),
theme: Theme::default_dark(),
theme_name: "dark".to_string(),
exit: false,
lines: Vec::new(),
filter_cursor_pos: 0,
Expand Down
Loading