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
35 changes: 11 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,17 @@ ghgrab config unset token
ghgrab config unset path
```

### Theming

ghgrab supports custom color themes via a TOML config file.

- Linux/macOS: `~/.config/ghgrab/theme.toml`
- Windows: `%APPDATA%\ghgrab\theme.toml`

Any missing key falls back to the default Tokyo Night theme. Colors must use `#RRGGBB` hex format.

See [`examples/theme.toml`](examples/theme.toml) for a complete example.

### Keyboard Shortcuts (How to move around)

We've kept it pretty standard, but here's a quick cheat sheet:
Expand Down Expand Up @@ -193,27 +204,3 @@ If you find a bug, have an idea for a cool new feature, or just want to help out
## License

Distributed under the MIT License. It's open, free, and yours to play with. See [LICENSE](LICENSE) for the fine print.

### Theming

ghgrab supports custom color themes via a TOML config file.

**Location:**
- Linux/macOS: `~/.config/ghgrab/theme.toml`
- Windows: `%APPDATA%\ghgrab\theme.toml`

Variables can be changed individually — any missing key falls back to the default Tokyo Night color theme. Colors must be in `#RRGGBB` hex format.
```toml
bg_color = "#24283b" # Main background
fg_color = "#c0caf5" # Primary text
accent_color = "#7aa2f7" # Borders, highlights, active elements
warning_color = "#e0af68" # Warnings
error_color = "#f7768e" # Errors
success_color = "#9ece6a" # Success indicators
folder_color = "#82aaff" # Folder icons
selected_color = "#ff9e64" # Selected items
border_color = "#565f89" # Inactive borders
highlight_bg = "#292e42" # Highlighted row background
```

[`exampletheme.toml`](exampletheme.toml) for a example template.
20 changes: 20 additions & 0 deletions examples/theme.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# ghgrab theme example
#
# Place this file at:
# - Linux/macOS: ~/.config/ghgrab/theme.toml
# - Windows: %APPDATA%\ghgrab\theme.toml
#
# Create the ghgrab directory if it does not exist.
# All values are optional. Missing keys fall back to the default Tokyo Night colors.
# Colors must use #RRGGBB hex format.

bg_color = "#24283b" # Main background
fg_color = "#c0caf5" # Primary text
accent_color = "#7aa2f7" # Borders, highlights, active elements
warning_color = "#e0af68" # Warnings
error_color = "#f7768e" # Errors
success_color = "#9ece6a" # Success indicators
folder_color = "#82aaff" # Folder icons
selected_color = "#ff9e64" # Selected items
border_color = "#565f89" # Inactive borders
highlight_bg = "#292e42" # Highlighted row background
24 changes: 0 additions & 24 deletions exampletheme.toml

This file was deleted.

8 changes: 6 additions & 2 deletions src/ui/components/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ pub fn render(

let desc_text = Line::from(Span::styled(
"Download any file or folder from GitHub. No full clones. Just what you need.",
Style::default().fg(FG_COLOR()).add_modifier(Modifier::ITALIC),
Style::default()
.fg(FG_COLOR())
.add_modifier(Modifier::ITALIC),
));
let desc = Paragraph::new(desc_text)
.alignment(Alignment::Center)
Expand Down Expand Up @@ -208,7 +210,9 @@ pub fn render(
),
Span::styled(
"Works with any public or private GitHub repository",
Style::default().fg(FG_COLOR()).add_modifier(Modifier::ITALIC),
Style::default()
.fg(FG_COLOR())
.add_modifier(Modifier::ITALIC),
),
]),
];
Expand Down
15 changes: 12 additions & 3 deletions src/ui/components/repo_search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,10 @@ pub fn render(f: &mut Frame, area: Rect, state: &RepoSearchState) {
};

let status = Paragraph::new(Line::from(vec![
Span::styled(" Search ", Style::default().fg(BG_COLOR()).bg(SUCCESS_COLOR())),
Span::styled(
" Search ",
Style::default().fg(BG_COLOR()).bg(SUCCESS_COLOR()),
),
Span::raw(" "),
Span::styled(status_text, Style::default().fg(FG_COLOR())),
]))
Expand All @@ -155,7 +158,11 @@ pub fn render(f: &mut Frame, area: Rect, state: &RepoSearchState) {
if state.loading && state.total_results == 0 {
let loading_widget = Paragraph::new("\nSearching GitHub repositories...")
.alignment(Alignment::Center)
.style(Style::default().fg(FG_COLOR()).add_modifier(Modifier::ITALIC))
.style(
Style::default()
.fg(FG_COLOR())
.add_modifier(Modifier::ITALIC),
)
.block(
Block::default()
.borders(Borders::ALL)
Expand Down Expand Up @@ -352,7 +359,9 @@ fn build_result_item(width: u16, is_selected: bool, item: &SearchItem) -> ListIt
Span::raw(" "),
Span::styled(
trimmed_desc,
Style::default().fg(FG_COLOR()).add_modifier(Modifier::ITALIC),
Style::default()
.fg(FG_COLOR())
.add_modifier(Modifier::ITALIC),
),
]),
Line::from(meta_spans),
Expand Down
4 changes: 3 additions & 1 deletion src/ui/components/searching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ pub fn render(f: &mut Frame, area: Rect, frame_count: u64, status_msg: &str) {
};
let status = Paragraph::new(Span::styled(
msg,
Style::default().fg(FG_COLOR()).add_modifier(Modifier::ITALIC),
Style::default()
.fg(FG_COLOR())
.add_modifier(Modifier::ITALIC),
))
.alignment(Alignment::Center)
.style(Style::default().bg(BG_COLOR()));
Expand Down
114 changes: 73 additions & 41 deletions src/ui/theme.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
#![allow(non_snake_case)]

use ratatui::style::Color;
use serde::{Deserialize, Serialize};
use std::fs;
use std::sync::OnceLock;

#[derive(Serialize, Deserialize, Debug, Clone)]
struct ThemeFile {
bg_color: Option<String>,
fg_color: Option<String>,
accent_color: Option<String>,
warning_color: Option<String>,
error_color: Option<String>,
success_color: Option<String>,
folder_color: Option<String>,
bg_color: Option<String>,
fg_color: Option<String>,
accent_color: Option<String>,
warning_color: Option<String>,
error_color: Option<String>,
success_color: Option<String>,
folder_color: Option<String>,
selected_color: Option<String>,
border_color: Option<String>,
highlight_bg: Option<String>,
border_color: Option<String>,
highlight_bg: Option<String>,
}

struct Theme {
pub bg_color: Color,
pub fg_color: Color,
pub accent_color: Color,
pub warning_color: Color,
pub error_color: Color,
pub success_color: Color,
pub folder_color: Color,
pub bg_color: Color,
pub fg_color: Color,
pub accent_color: Color,
pub warning_color: Color,
pub error_color: Color,
pub success_color: Color,
pub folder_color: Color,
pub selected_color: Color,
pub border_color: Color,
pub highlight_bg: Color,
pub border_color: Color,
pub highlight_bg: Color,
}

fn parse_color(val: Option<&str>, default: [u8; 3]) -> Color {
Expand All @@ -38,9 +40,17 @@ fn parse_color(val: Option<&str>, default: [u8; 3]) -> Color {
u8::from_str_radix(&hex[0..2], 16),
u8::from_str_radix(&hex[2..4], 16),
u8::from_str_radix(&hex[4..6], 16),
) { [r, g, b] } else { default }
} else { default }
} else { default };
) {
[r, g, b]
} else {
default
}
} else {
default
}
} else {
default
};
Color::Rgb(r, g, b)
}

Expand All @@ -54,29 +64,51 @@ fn load_theme() -> Theme {
})();
let f = file.as_ref();
Theme {
bg_color: parse_color(f.and_then(|t| t.bg_color.as_deref()), [36, 40, 59]),
fg_color: parse_color(f.and_then(|t| t.fg_color.as_deref()), [192, 202, 245]),
accent_color: parse_color(f.and_then(|t| t.accent_color.as_deref()), [122, 162, 247]),
warning_color: parse_color(f.and_then(|t| t.warning_color.as_deref()), [224, 175, 104]),
error_color: parse_color(f.and_then(|t| t.error_color.as_deref()), [247, 120, 107]),
success_color: parse_color(f.and_then(|t| t.success_color.as_deref()), [158, 206, 106]),
folder_color: parse_color(f.and_then(|t| t.folder_color.as_deref()), [130, 170, 255]),
bg_color: parse_color(f.and_then(|t| t.bg_color.as_deref()), [36, 40, 59]),
fg_color: parse_color(f.and_then(|t| t.fg_color.as_deref()), [192, 202, 245]),
accent_color: parse_color(f.and_then(|t| t.accent_color.as_deref()), [122, 162, 247]),
warning_color: parse_color(f.and_then(|t| t.warning_color.as_deref()), [224, 175, 104]),
error_color: parse_color(f.and_then(|t| t.error_color.as_deref()), [247, 118, 142]),
success_color: parse_color(f.and_then(|t| t.success_color.as_deref()), [158, 206, 106]),
folder_color: parse_color(f.and_then(|t| t.folder_color.as_deref()), [130, 170, 255]),
selected_color: parse_color(f.and_then(|t| t.selected_color.as_deref()), [255, 158, 100]),
border_color: parse_color(f.and_then(|t| t.border_color.as_deref()), [86, 95, 137]),
highlight_bg: parse_color(f.and_then(|t| t.highlight_bg.as_deref()), [41, 46, 66]),
border_color: parse_color(f.and_then(|t| t.border_color.as_deref()), [86, 95, 137]),
highlight_bg: parse_color(f.and_then(|t| t.highlight_bg.as_deref()), [41, 46, 66]),
}
}

static THEME: OnceLock<Theme> = OnceLock::new();
fn t() -> &'static Theme { THEME.get_or_init(load_theme) }
fn t() -> &'static Theme {
THEME.get_or_init(load_theme)
}

pub fn BG_COLOR() -> Color { t().bg_color }
pub fn FG_COLOR() -> Color { t().fg_color }
pub fn ACCENT_COLOR() -> Color { t().accent_color }
pub fn WARNING_COLOR() -> Color { t().warning_color }
pub fn ERROR_COLOR() -> Color { t().error_color }
pub fn SUCCESS_COLOR() -> Color { t().success_color }
pub fn FOLDER_COLOR() -> Color { t().folder_color }
pub fn _SELECTED_COLOR() -> Color { t().selected_color }
pub fn BORDER_COLOR() -> Color { t().border_color }
pub fn HIGHLIGHT_BG() -> Color { t().highlight_bg }
pub fn BG_COLOR() -> Color {
t().bg_color
}
pub fn FG_COLOR() -> Color {
t().fg_color
}
pub fn ACCENT_COLOR() -> Color {
t().accent_color
}
pub fn WARNING_COLOR() -> Color {
t().warning_color
}
pub fn ERROR_COLOR() -> Color {
t().error_color
}
pub fn SUCCESS_COLOR() -> Color {
t().success_color
}
pub fn FOLDER_COLOR() -> Color {
t().folder_color
}
pub fn _SELECTED_COLOR() -> Color {
t().selected_color
}
pub fn BORDER_COLOR() -> Color {
t().border_color
}
pub fn HIGHLIGHT_BG() -> Color {
t().highlight_bg
}
Loading