mirror of
https://github.com/gitui-org/gitui
synced 2026-05-24 09:28:21 +00:00
parent
e73cdb67bc
commit
4ec1a4e94b
18 changed files with 470 additions and 231 deletions
|
|
@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
- support for color themes and light mode([#28](https://github.com/extrawurst/gitui/issues/28))
|
||||||
|
|
||||||
## [0.2.6] - 2020-05-18
|
## [0.2.6] - 2020-05-18
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
||||||
50
Cargo.lock
generated
50
Cargo.lock
generated
|
|
@ -59,6 +59,15 @@ dependencies = [
|
||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
|
@ -82,6 +91,12 @@ dependencies = [
|
||||||
"constant_time_eq",
|
"constant_time_eq",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cassowary"
|
name = "cassowary"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
|
@ -299,8 +314,10 @@ dependencies = [
|
||||||
"itertools",
|
"itertools",
|
||||||
"log",
|
"log",
|
||||||
"rayon-core",
|
"rayon-core",
|
||||||
|
"ron",
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
"scopetime",
|
"scopetime",
|
||||||
|
"serde",
|
||||||
"simplelog",
|
"simplelog",
|
||||||
"tui",
|
"tui",
|
||||||
]
|
]
|
||||||
|
|
@ -654,13 +671,24 @@ dependencies = [
|
||||||
"winapi 0.3.8",
|
"winapi 0.3.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ron"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2ece421e0c4129b90e4a35b6f625e472e96c552136f5093a2f4fa2bbb75a62d5"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.10.1",
|
||||||
|
"bitflags",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-argon2"
|
name = "rust-argon2"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017"
|
checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.11.0",
|
||||||
"blake2b_simd",
|
"blake2b_simd",
|
||||||
"constant_time_eq",
|
"constant_time_eq",
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
|
|
@ -685,6 +713,26 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.110"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.110"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook"
|
name = "signal-hook"
|
||||||
version = "0.1.15"
|
version = "0.1.15"
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,10 @@ scopeguard = "1.1"
|
||||||
bitflags = "1.2"
|
bitflags = "1.2"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
backtrace = { version = "0.3" }
|
backtrace = { version = "0.3" }
|
||||||
|
ron = "0.5.1"
|
||||||
scopetime = { path = "./scopetime", version = "0.1" }
|
scopetime = { path = "./scopetime", version = "0.1" }
|
||||||
asyncgit = { path = "./asyncgit", version = "0.2" }
|
asyncgit = { path = "./asyncgit", version = "0.2" }
|
||||||
|
serde = "1.0.110"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default=[]
|
default=[]
|
||||||
|
|
@ -45,6 +47,6 @@ members=[
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
opt-level = 'z' # Optimize for size.
|
opt-level = 'z' # Optimize for size.
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|
@ -80,6 +80,13 @@ this will log to:
|
||||||
* `$XDG_CACHE_HOME/gitui/gitui.log` (linux using `XDG`)
|
* `$XDG_CACHE_HOME/gitui/gitui.log` (linux using `XDG`)
|
||||||
* `$HOME/.cache/gitui/gitui.log` (linux)
|
* `$HOME/.cache/gitui/gitui.log` (linux)
|
||||||
|
|
||||||
|
# color theme
|
||||||
|
|
||||||
|
to change the colors of the program you have to modify `theme.ron` file
|
||||||
|
[Ron format](https://github.com/ron-rs/ron) located at config path (same as log paths). the list of valid
|
||||||
|
colors can be found in [ColorDef](./src/ui/style.rs#ColorDef) struct. note that rgb colors might not be available
|
||||||
|
on some platforms.
|
||||||
|
|
||||||
# inspiration
|
# inspiration
|
||||||
|
|
||||||
* https://github.com/jesseduffield/lazygit
|
* https://github.com/jesseduffield/lazygit
|
||||||
|
|
|
||||||
37
src/app.rs
37
src/app.rs
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::ui::style::Theme;
|
||||||
use crate::{
|
use crate::{
|
||||||
accessors,
|
accessors,
|
||||||
components::{
|
components::{
|
||||||
|
|
@ -17,10 +18,11 @@ use itertools::Itertools;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use strings::commands;
|
use strings::commands;
|
||||||
|
use tui::style::Style;
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||||
style::{Color, Modifier, Style},
|
style::Modifier,
|
||||||
widgets::{Block, Borders, Paragraph, Tabs, Text},
|
widgets::{Block, Borders, Paragraph, Tabs, Text},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
|
@ -37,6 +39,7 @@ pub struct App {
|
||||||
revlog: Revlog,
|
revlog: Revlog,
|
||||||
status_tab: Status,
|
status_tab: Status,
|
||||||
queue: Queue,
|
queue: Queue,
|
||||||
|
theme: Theme,
|
||||||
}
|
}
|
||||||
|
|
||||||
// public interface
|
// public interface
|
||||||
|
|
@ -44,17 +47,21 @@ impl App {
|
||||||
///
|
///
|
||||||
pub fn new(sender: &Sender<AsyncNotification>) -> Self {
|
pub fn new(sender: &Sender<AsyncNotification>) -> Self {
|
||||||
let queue = Queue::default();
|
let queue = Queue::default();
|
||||||
|
|
||||||
|
let theme = Theme::init();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
reset: ResetComponent::new(queue.clone()),
|
reset: ResetComponent::new(queue.clone(), theme),
|
||||||
commit: CommitComponent::new(queue.clone()),
|
commit: CommitComponent::new(queue.clone(), theme),
|
||||||
do_quit: false,
|
do_quit: false,
|
||||||
current_commands: Vec::new(),
|
current_commands: Vec::new(),
|
||||||
help: HelpComponent::default(),
|
help: HelpComponent::new(theme),
|
||||||
msg: MsgComponent::default(),
|
msg: MsgComponent::default(),
|
||||||
tab: 0,
|
tab: 0,
|
||||||
revlog: Revlog::new(&sender),
|
revlog: Revlog::new(&sender, theme),
|
||||||
status_tab: Status::new(&sender, &queue),
|
status_tab: Status::new(&sender, &queue, theme),
|
||||||
queue,
|
queue,
|
||||||
|
theme,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,6 +91,7 @@ impl App {
|
||||||
f,
|
f,
|
||||||
chunks_main[2],
|
chunks_main[2],
|
||||||
self.current_commands.as_slice(),
|
self.current_commands.as_slice(),
|
||||||
|
self.theme,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.draw_popups(f);
|
self.draw_popups(f);
|
||||||
|
|
@ -320,10 +328,9 @@ impl App {
|
||||||
Tabs::default()
|
Tabs::default()
|
||||||
.block(Block::default().borders(Borders::BOTTOM))
|
.block(Block::default().borders(Borders::BOTTOM))
|
||||||
.titles(&[strings::TAB_STATUS, strings::TAB_LOG])
|
.titles(&[strings::TAB_STATUS, strings::TAB_LOG])
|
||||||
.style(Style::default().fg(Color::White))
|
|
||||||
.highlight_style(
|
.highlight_style(
|
||||||
Style::default()
|
self.theme
|
||||||
.fg(Color::Yellow)
|
.tab(true)
|
||||||
.modifier(Modifier::UNDERLINED),
|
.modifier(Modifier::UNDERLINED),
|
||||||
)
|
)
|
||||||
.divider(strings::TAB_DIVIDER)
|
.divider(strings::TAB_DIVIDER)
|
||||||
|
|
@ -336,28 +343,20 @@ impl App {
|
||||||
f: &mut Frame<B>,
|
f: &mut Frame<B>,
|
||||||
r: Rect,
|
r: Rect,
|
||||||
cmds: &[CommandInfo],
|
cmds: &[CommandInfo],
|
||||||
|
theme: Theme,
|
||||||
) {
|
) {
|
||||||
let splitter = Text::Styled(
|
let splitter = Text::Styled(
|
||||||
Cow::from(strings::CMD_SPLITTER),
|
Cow::from(strings::CMD_SPLITTER),
|
||||||
Style::default(),
|
Style::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let style_enabled =
|
|
||||||
Style::default().fg(Color::White).bg(Color::Blue);
|
|
||||||
|
|
||||||
let style_disabled =
|
|
||||||
Style::default().fg(Color::DarkGray).bg(Color::Blue);
|
|
||||||
let texts = cmds
|
let texts = cmds
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|c| {
|
.filter_map(|c| {
|
||||||
if c.show_in_quickbar() {
|
if c.show_in_quickbar() {
|
||||||
Some(Text::Styled(
|
Some(Text::Styled(
|
||||||
Cow::from(c.text.name),
|
Cow::from(c.text.name),
|
||||||
if c.enabled {
|
theme.toolbar(c.enabled),
|
||||||
style_enabled
|
|
||||||
} else {
|
|
||||||
style_disabled
|
|
||||||
},
|
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use super::{
|
||||||
statustree::{MoveSelection, StatusTree},
|
statustree::{MoveSelection, StatusTree},
|
||||||
CommandBlocking, DrawableComponent,
|
CommandBlocking, DrawableComponent,
|
||||||
};
|
};
|
||||||
|
use crate::ui::style::Theme;
|
||||||
use crate::{
|
use crate::{
|
||||||
components::{CommandInfo, Component},
|
components::{CommandInfo, Component},
|
||||||
keys,
|
keys,
|
||||||
|
|
@ -13,13 +14,7 @@ use asyncgit::{hash, sync, StatusItem, StatusItemType, CWD};
|
||||||
use crossterm::event::Event;
|
use crossterm::event::Event;
|
||||||
use std::{borrow::Cow, convert::From, path::Path};
|
use std::{borrow::Cow, convert::From, path::Path};
|
||||||
use strings::commands;
|
use strings::commands;
|
||||||
use tui::{
|
use tui::{backend::Backend, layout::Rect, widgets::Text, Frame};
|
||||||
backend::Backend,
|
|
||||||
layout::Rect,
|
|
||||||
style::{Color, Style},
|
|
||||||
widgets::Text,
|
|
||||||
Frame,
|
|
||||||
};
|
|
||||||
|
|
||||||
///
|
///
|
||||||
pub struct ChangesComponent {
|
pub struct ChangesComponent {
|
||||||
|
|
@ -30,6 +25,7 @@ pub struct ChangesComponent {
|
||||||
show_selection: bool,
|
show_selection: bool,
|
||||||
is_working_dir: bool,
|
is_working_dir: bool,
|
||||||
queue: Queue,
|
queue: Queue,
|
||||||
|
theme: Theme,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChangesComponent {
|
impl ChangesComponent {
|
||||||
|
|
@ -39,6 +35,7 @@ impl ChangesComponent {
|
||||||
focus: bool,
|
focus: bool,
|
||||||
is_working_dir: bool,
|
is_working_dir: bool,
|
||||||
queue: Queue,
|
queue: Queue,
|
||||||
|
theme: Theme,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
title: title.to_string(),
|
title: title.to_string(),
|
||||||
|
|
@ -48,6 +45,7 @@ impl ChangesComponent {
|
||||||
show_selection: focus,
|
show_selection: focus,
|
||||||
is_working_dir,
|
is_working_dir,
|
||||||
queue,
|
queue,
|
||||||
|
theme,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -154,9 +152,8 @@ impl ChangesComponent {
|
||||||
item: &FileTreeItem,
|
item: &FileTreeItem,
|
||||||
width: u16,
|
width: u16,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
|
theme: Theme,
|
||||||
) -> Option<Text> {
|
) -> Option<Text> {
|
||||||
let select_color = Color::Rgb(0, 0, 100);
|
|
||||||
|
|
||||||
let indent_str = if item.info.indent == 0 {
|
let indent_str = if item.info.indent == 0 {
|
||||||
String::from("")
|
String::from("")
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -189,18 +186,14 @@ impl ChangesComponent {
|
||||||
format!("{} {}{}", status_char, indent_str, file)
|
format!("{} {}{}", status_char, indent_str, file)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut style =
|
let status = status_item
|
||||||
Style::default().fg(Self::item_color(
|
.status
|
||||||
status_item
|
.unwrap_or(StatusItemType::Modified);
|
||||||
.status
|
|
||||||
.unwrap_or(StatusItemType::Modified),
|
|
||||||
));
|
|
||||||
|
|
||||||
if selected {
|
Some(Text::Styled(
|
||||||
style = style.bg(select_color);
|
Cow::from(txt),
|
||||||
}
|
theme.item(status, selected),
|
||||||
|
))
|
||||||
Some(Text::Styled(Cow::from(txt), style))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FileTreeItemKind::Path(path_collapsed) => {
|
FileTreeItemKind::Path(path_collapsed) => {
|
||||||
|
|
@ -222,27 +215,14 @@ impl ChangesComponent {
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut style = Style::default();
|
Some(Text::Styled(
|
||||||
|
Cow::from(txt),
|
||||||
if selected {
|
theme.text(true, selected),
|
||||||
style = style.bg(select_color);
|
))
|
||||||
}
|
|
||||||
|
|
||||||
Some(Text::Styled(Cow::from(txt), style))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn item_color(item_type: StatusItemType) -> Color {
|
|
||||||
match item_type {
|
|
||||||
StatusItemType::Modified => Color::LightYellow,
|
|
||||||
StatusItemType::New => Color::LightGreen,
|
|
||||||
StatusItemType::Deleted => Color::LightRed,
|
|
||||||
StatusItemType::Renamed => Color::LightMagenta,
|
|
||||||
_ => Color::White,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn item_status_char(item_type: Option<StatusItemType>) -> char {
|
fn item_status_char(item_type: Option<StatusItemType>) -> char {
|
||||||
if let Some(item_type) = item_type {
|
if let Some(item_type) = item_type {
|
||||||
match item_type {
|
match item_type {
|
||||||
|
|
@ -287,6 +267,7 @@ impl DrawableComponent for ChangesComponent {
|
||||||
.tree
|
.tree
|
||||||
.selection
|
.selection
|
||||||
.map_or(false, |e| e == idx),
|
.map_or(false, |e| e == idx),
|
||||||
|
self.theme,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -298,6 +279,7 @@ impl DrawableComponent for ChangesComponent {
|
||||||
items,
|
items,
|
||||||
self.tree.selection.map(|idx| idx - selection_offset),
|
self.tree.selection.map(|idx| idx - selection_offset),
|
||||||
self.focused,
|
self.focused,
|
||||||
|
self.theme,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ use super::{
|
||||||
visibility_blocking, CommandBlocking, CommandInfo, Component,
|
visibility_blocking, CommandBlocking, CommandInfo, Component,
|
||||||
DrawableComponent,
|
DrawableComponent,
|
||||||
};
|
};
|
||||||
|
use crate::components::dialog_paragraph;
|
||||||
|
use crate::ui::style::Theme;
|
||||||
use crate::{
|
use crate::{
|
||||||
queue::{InternalEvent, NeedsUpdate, Queue},
|
queue::{InternalEvent, NeedsUpdate, Queue},
|
||||||
strings, ui,
|
strings, ui,
|
||||||
|
|
@ -12,11 +14,11 @@ use log::error;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use strings::commands;
|
use strings::commands;
|
||||||
use sync::HookResult;
|
use sync::HookResult;
|
||||||
|
use tui::style::Style;
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
layout::{Alignment, Rect},
|
layout::Rect,
|
||||||
style::{Color, Style},
|
widgets::{Clear, Text},
|
||||||
widgets::{Block, Borders, Clear, Paragraph, Text},
|
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -24,6 +26,7 @@ pub struct CommitComponent {
|
||||||
msg: String,
|
msg: String,
|
||||||
visible: bool,
|
visible: bool,
|
||||||
queue: Queue,
|
queue: Queue,
|
||||||
|
theme: Theme,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DrawableComponent for CommitComponent {
|
impl DrawableComponent for CommitComponent {
|
||||||
|
|
@ -32,22 +35,19 @@ impl DrawableComponent for CommitComponent {
|
||||||
let txt = if self.msg.is_empty() {
|
let txt = if self.msg.is_empty() {
|
||||||
[Text::Styled(
|
[Text::Styled(
|
||||||
Cow::from(strings::COMMIT_MSG),
|
Cow::from(strings::COMMIT_MSG),
|
||||||
Style::default().fg(Color::DarkGray),
|
self.theme.text(false, false),
|
||||||
)]
|
)]
|
||||||
} else {
|
} else {
|
||||||
[Text::Raw(Cow::from(self.msg.clone()))]
|
[Text::Styled(
|
||||||
|
Cow::from(self.msg.clone()),
|
||||||
|
Style::default(),
|
||||||
|
)]
|
||||||
};
|
};
|
||||||
|
|
||||||
let area = ui::centered_rect(60, 20, f.size());
|
let area = ui::centered_rect(60, 20, f.size());
|
||||||
f.render_widget(Clear, area);
|
f.render_widget(Clear, area);
|
||||||
f.render_widget(
|
f.render_widget(
|
||||||
Paragraph::new(txt.iter())
|
dialog_paragraph(strings::COMMIT_TITLE, txt.iter()),
|
||||||
.block(
|
|
||||||
Block::default()
|
|
||||||
.title(strings::COMMIT_TITLE)
|
|
||||||
.borders(Borders::ALL),
|
|
||||||
)
|
|
||||||
.alignment(Alignment::Left),
|
|
||||||
area,
|
area,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -112,11 +112,12 @@ impl Component for CommitComponent {
|
||||||
|
|
||||||
impl CommitComponent {
|
impl CommitComponent {
|
||||||
///
|
///
|
||||||
pub fn new(queue: Queue) -> Self {
|
pub fn new(queue: Queue, theme: Theme) -> Self {
|
||||||
Self {
|
Self {
|
||||||
queue,
|
queue,
|
||||||
msg: String::default(),
|
msg: String::default(),
|
||||||
visible: false,
|
visible: false,
|
||||||
|
theme,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use super::{CommandBlocking, DrawableComponent, ScrollType};
|
use super::{CommandBlocking, DrawableComponent, ScrollType};
|
||||||
|
use crate::ui::style::Theme;
|
||||||
use crate::{
|
use crate::{
|
||||||
components::{CommandInfo, Component},
|
components::{CommandInfo, Component},
|
||||||
keys,
|
keys,
|
||||||
|
|
@ -9,10 +10,11 @@ use asyncgit::{hash, DiffLine, DiffLineType, FileDiff};
|
||||||
use crossterm::event::Event;
|
use crossterm::event::Event;
|
||||||
use std::{borrow::Cow, cmp, convert::TryFrom};
|
use std::{borrow::Cow, cmp, convert::TryFrom};
|
||||||
use strings::commands;
|
use strings::commands;
|
||||||
|
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
layout::{Alignment, Rect},
|
layout::{Alignment, Rect},
|
||||||
style::{Color, Modifier, Style},
|
style::Modifier,
|
||||||
symbols,
|
symbols,
|
||||||
widgets::{Block, Borders, Paragraph, Text},
|
widgets::{Block, Borders, Paragraph, Text},
|
||||||
Frame,
|
Frame,
|
||||||
|
|
@ -34,11 +36,12 @@ pub struct DiffComponent {
|
||||||
current: Current,
|
current: Current,
|
||||||
selected_hunk: Option<u16>,
|
selected_hunk: Option<u16>,
|
||||||
queue: Queue,
|
queue: Queue,
|
||||||
|
theme: Theme,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiffComponent {
|
impl DiffComponent {
|
||||||
///
|
///
|
||||||
pub fn new(queue: Queue) -> Self {
|
pub fn new(queue: Queue, theme: Theme) -> Self {
|
||||||
Self {
|
Self {
|
||||||
focused: false,
|
focused: false,
|
||||||
queue,
|
queue,
|
||||||
|
|
@ -47,6 +50,7 @@ impl DiffComponent {
|
||||||
diff: FileDiff::default(),
|
diff: FileDiff::default(),
|
||||||
scroll: 0,
|
scroll: 0,
|
||||||
current_height: 0,
|
current_height: 0,
|
||||||
|
theme,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
///
|
///
|
||||||
|
|
@ -171,6 +175,7 @@ impl DiffComponent {
|
||||||
selection == line_cursor,
|
selection == line_cursor,
|
||||||
hunk_selected,
|
hunk_selected,
|
||||||
i == hunk_len as usize - 1,
|
i == hunk_len as usize - 1,
|
||||||
|
self.theme,
|
||||||
);
|
);
|
||||||
lines_added += 1;
|
lines_added += 1;
|
||||||
}
|
}
|
||||||
|
|
@ -191,22 +196,10 @@ impl DiffComponent {
|
||||||
selected: bool,
|
selected: bool,
|
||||||
selected_hunk: bool,
|
selected_hunk: bool,
|
||||||
end_of_hunk: bool,
|
end_of_hunk: bool,
|
||||||
|
theme: Theme,
|
||||||
) {
|
) {
|
||||||
let select_color = Color::Rgb(0, 0, 100);
|
|
||||||
let style_default = Style::default().bg(if selected {
|
|
||||||
select_color
|
|
||||||
} else {
|
|
||||||
Color::Reset
|
|
||||||
});
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let style = Style::default()
|
let style = theme.text(false, selected || selected_hunk);
|
||||||
.bg(if selected || selected_hunk {
|
|
||||||
select_color
|
|
||||||
} else {
|
|
||||||
Color::Reset
|
|
||||||
})
|
|
||||||
.fg(Color::DarkGray);
|
|
||||||
|
|
||||||
if end_of_hunk {
|
if end_of_hunk {
|
||||||
text.push(Text::Styled(
|
text.push(Text::Styled(
|
||||||
|
|
@ -227,17 +220,6 @@ impl DiffComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let style_delete = Style::default()
|
|
||||||
.fg(Color::Red)
|
|
||||||
.bg(if selected { select_color } else { Color::Reset });
|
|
||||||
let style_add = Style::default()
|
|
||||||
.fg(Color::Green)
|
|
||||||
.bg(if selected { select_color } else { Color::Reset });
|
|
||||||
let style_header = Style::default()
|
|
||||||
.fg(Color::White)
|
|
||||||
.bg(if selected { select_color } else { Color::Reset })
|
|
||||||
.modifier(Modifier::BOLD);
|
|
||||||
|
|
||||||
let trimmed =
|
let trimmed =
|
||||||
line.content.trim_matches(|c| c == '\n' || c == '\r');
|
line.content.trim_matches(|c| c == '\n' || c == '\r');
|
||||||
|
|
||||||
|
|
@ -251,16 +233,10 @@ impl DiffComponent {
|
||||||
//TODO: allow customize tabsize
|
//TODO: allow customize tabsize
|
||||||
let content = Cow::from(filled.replace("\t", " "));
|
let content = Cow::from(filled.replace("\t", " "));
|
||||||
|
|
||||||
text.push(match line.line_type {
|
text.push(Text::Styled(
|
||||||
DiffLineType::Delete => {
|
content,
|
||||||
Text::Styled(content, style_delete)
|
theme.diff_line(line.line_type, selected),
|
||||||
}
|
));
|
||||||
DiffLineType::Add => Text::Styled(content, style_add),
|
|
||||||
DiffLineType::Header => {
|
|
||||||
Text::Styled(content, style_header)
|
|
||||||
}
|
|
||||||
_ => Text::Styled(content, style_default),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hunk_visible(
|
fn hunk_visible(
|
||||||
|
|
@ -299,13 +275,6 @@ impl DiffComponent {
|
||||||
impl DrawableComponent for DiffComponent {
|
impl DrawableComponent for DiffComponent {
|
||||||
fn draw<B: Backend>(&mut self, f: &mut Frame<B>, r: Rect) {
|
fn draw<B: Backend>(&mut self, f: &mut Frame<B>, r: Rect) {
|
||||||
self.current_height = r.height.saturating_sub(2);
|
self.current_height = r.height.saturating_sub(2);
|
||||||
let mut style_border = Style::default().fg(Color::DarkGray);
|
|
||||||
let mut style_title = Style::default();
|
|
||||||
if self.focused {
|
|
||||||
style_border = style_border.fg(Color::Gray);
|
|
||||||
style_title = style_title.modifier(Modifier::BOLD);
|
|
||||||
}
|
|
||||||
|
|
||||||
let title =
|
let title =
|
||||||
format!("{}{}", strings::TITLE_DIFF, self.current.path);
|
format!("{}{}", strings::TITLE_DIFF, self.current.path);
|
||||||
f.render_widget(
|
f.render_widget(
|
||||||
|
|
@ -314,8 +283,12 @@ impl DrawableComponent for DiffComponent {
|
||||||
Block::default()
|
Block::default()
|
||||||
.title(title.as_str())
|
.title(title.as_str())
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_style(style_border)
|
.border_style(self.theme.block(self.focused))
|
||||||
.title_style(style_title),
|
.title_style(
|
||||||
|
self.theme
|
||||||
|
.text(self.focused, false)
|
||||||
|
.modifier(Modifier::BOLD),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.alignment(Alignment::Left),
|
.alignment(Alignment::Left),
|
||||||
r,
|
r,
|
||||||
|
|
@ -414,7 +387,6 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lineendings() {
|
fn test_lineendings() {
|
||||||
let mut text = Vec::new();
|
let mut text = Vec::new();
|
||||||
|
|
||||||
DiffComponent::add_line(
|
DiffComponent::add_line(
|
||||||
&mut text,
|
&mut text,
|
||||||
10,
|
10,
|
||||||
|
|
@ -425,6 +397,7 @@ mod tests {
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
crate::ui::style::DARK_THEME,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(text.len(), 2);
|
assert_eq!(text.len(), 2);
|
||||||
|
|
|
||||||
|
|
@ -2,26 +2,27 @@ use super::{
|
||||||
visibility_blocking, CommandBlocking, CommandInfo, Component,
|
visibility_blocking, CommandBlocking, CommandInfo, Component,
|
||||||
DrawableComponent,
|
DrawableComponent,
|
||||||
};
|
};
|
||||||
|
use crate::ui::style::Theme;
|
||||||
use crate::{keys, strings, ui, version::Version};
|
use crate::{keys, strings, ui, version::Version};
|
||||||
use asyncgit::hash;
|
use asyncgit::hash;
|
||||||
use crossterm::event::Event;
|
use crossterm::event::Event;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use std::{borrow::Cow, cmp, convert::TryFrom};
|
use std::{borrow::Cow, cmp, convert::TryFrom};
|
||||||
use strings::commands;
|
use strings::commands;
|
||||||
|
use tui::style::{Modifier, Style};
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||||
style::{Color, Style},
|
|
||||||
widgets::{Block, Borders, Clear, Paragraph, Text},
|
widgets::{Block, Borders, Clear, Paragraph, Text},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
|
||||||
///
|
///
|
||||||
#[derive(Default)]
|
|
||||||
pub struct HelpComponent {
|
pub struct HelpComponent {
|
||||||
cmds: Vec<CommandInfo>,
|
cmds: Vec<CommandInfo>,
|
||||||
visible: bool,
|
visible: bool,
|
||||||
selection: u16,
|
selection: u16,
|
||||||
|
theme: Theme,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DrawableComponent for HelpComponent {
|
impl DrawableComponent for HelpComponent {
|
||||||
|
|
@ -68,10 +69,13 @@ impl DrawableComponent for HelpComponent {
|
||||||
|
|
||||||
f.render_widget(
|
f.render_widget(
|
||||||
Paragraph::new(
|
Paragraph::new(
|
||||||
vec![Text::Raw(Cow::from(format!(
|
vec![Text::Styled(
|
||||||
"gitui {}",
|
Cow::from(format!(
|
||||||
Version::new(),
|
"gitui {}",
|
||||||
)))]
|
Version::new(),
|
||||||
|
)),
|
||||||
|
Style::default(),
|
||||||
|
)]
|
||||||
.iter(),
|
.iter(),
|
||||||
)
|
)
|
||||||
.alignment(Alignment::Right),
|
.alignment(Alignment::Right),
|
||||||
|
|
@ -150,6 +154,14 @@ impl Component for HelpComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HelpComponent {
|
impl HelpComponent {
|
||||||
|
pub fn new(theme: Theme) -> Self {
|
||||||
|
Self {
|
||||||
|
cmds: vec![],
|
||||||
|
visible: false,
|
||||||
|
selection: 0,
|
||||||
|
theme,
|
||||||
|
}
|
||||||
|
}
|
||||||
///
|
///
|
||||||
pub fn set_cmds(&mut self, cmds: Vec<CommandInfo>) {
|
pub fn set_cmds(&mut self, cmds: Vec<CommandInfo>) {
|
||||||
self.cmds = cmds
|
self.cmds = cmds
|
||||||
|
|
@ -187,7 +199,7 @@ impl HelpComponent {
|
||||||
{
|
{
|
||||||
txt.push(Text::Styled(
|
txt.push(Text::Styled(
|
||||||
Cow::from(format!(" {}\n", key)),
|
Cow::from(format!(" {}\n", key)),
|
||||||
Style::default().fg(Color::Black).bg(Color::Gray),
|
Style::default().modifier(Modifier::REVERSED),
|
||||||
));
|
));
|
||||||
|
|
||||||
txt.extend(
|
txt.extend(
|
||||||
|
|
@ -216,13 +228,10 @@ impl HelpComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let style = if is_selected {
|
Text::Styled(
|
||||||
Style::default().fg(Color::Yellow)
|
Cow::from(out),
|
||||||
} else {
|
self.theme.text(true, is_selected),
|
||||||
Style::default()
|
)
|
||||||
};
|
|
||||||
|
|
||||||
Text::Styled(Cow::from(out), style)
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ pub use filetree::FileTreeItemKind;
|
||||||
pub use help::HelpComponent;
|
pub use help::HelpComponent;
|
||||||
pub use msg::MsgComponent;
|
pub use msg::MsgComponent;
|
||||||
pub use reset::ResetComponent;
|
pub use reset::ResetComponent;
|
||||||
|
use tui::layout::Alignment;
|
||||||
|
use tui::widgets::{Block, Borders, Paragraph, Text};
|
||||||
|
|
||||||
/// creates accessors for a list of components
|
/// creates accessors for a list of components
|
||||||
///
|
///
|
||||||
|
|
@ -114,3 +116,15 @@ pub trait Component {
|
||||||
///
|
///
|
||||||
fn show(&mut self) {}
|
fn show(&mut self) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dialog_paragraph<'a, 't, T>(
|
||||||
|
title: &'a str,
|
||||||
|
content: T,
|
||||||
|
) -> Paragraph<'a, 't, T>
|
||||||
|
where
|
||||||
|
T: Iterator<Item = &'t Text<'t>>,
|
||||||
|
{
|
||||||
|
Paragraph::new(content)
|
||||||
|
.block(Block::default().title(title).borders(Borders::ALL))
|
||||||
|
.alignment(Alignment::Left)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,15 @@ use super::{
|
||||||
visibility_blocking, CommandBlocking, CommandInfo, Component,
|
visibility_blocking, CommandBlocking, CommandInfo, Component,
|
||||||
DrawableComponent,
|
DrawableComponent,
|
||||||
};
|
};
|
||||||
|
use crate::components::dialog_paragraph;
|
||||||
use crate::{keys, strings, ui};
|
use crate::{keys, strings, ui};
|
||||||
use crossterm::event::Event;
|
use crossterm::event::Event;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use strings::commands;
|
use strings::commands;
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
layout::{Alignment, Rect},
|
layout::Rect,
|
||||||
widgets::{Block, Borders, Clear, Paragraph, Text},
|
widgets::{Clear, Text},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -27,14 +28,8 @@ impl DrawableComponent for MsgComponent {
|
||||||
let area = ui::centered_rect_absolute(65, 25, f.size());
|
let area = ui::centered_rect_absolute(65, 25, f.size());
|
||||||
f.render_widget(Clear, area);
|
f.render_widget(Clear, area);
|
||||||
f.render_widget(
|
f.render_widget(
|
||||||
Paragraph::new(txt.iter())
|
dialog_paragraph(strings::MSG_TITLE, txt.iter())
|
||||||
.block(
|
.wrap(true),
|
||||||
Block::default()
|
|
||||||
.title(strings::MSG_TITLE)
|
|
||||||
.borders(Borders::ALL),
|
|
||||||
)
|
|
||||||
.wrap(true)
|
|
||||||
.alignment(Alignment::Left),
|
|
||||||
area,
|
area,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,15 @@ use crate::{
|
||||||
strings, ui,
|
strings, ui,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::components::dialog_paragraph;
|
||||||
|
use crate::ui::style::Theme;
|
||||||
use crossterm::event::{Event, KeyCode};
|
use crossterm::event::{Event, KeyCode};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use strings::commands;
|
use strings::commands;
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
layout::{Alignment, Rect},
|
layout::Rect,
|
||||||
style::{Color, Style},
|
widgets::{Clear, Text},
|
||||||
widgets::{Block, Borders, Clear, Paragraph, Text},
|
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -23,6 +24,7 @@ pub struct ResetComponent {
|
||||||
target: Option<ResetItem>,
|
target: Option<ResetItem>,
|
||||||
visible: bool,
|
visible: bool,
|
||||||
queue: Queue,
|
queue: Queue,
|
||||||
|
theme: Theme,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DrawableComponent for ResetComponent {
|
impl DrawableComponent for ResetComponent {
|
||||||
|
|
@ -31,19 +33,13 @@ impl DrawableComponent for ResetComponent {
|
||||||
let mut txt = Vec::new();
|
let mut txt = Vec::new();
|
||||||
txt.push(Text::Styled(
|
txt.push(Text::Styled(
|
||||||
Cow::from(strings::RESET_MSG),
|
Cow::from(strings::RESET_MSG),
|
||||||
Style::default().fg(Color::Red),
|
self.theme.text_danger(),
|
||||||
));
|
));
|
||||||
|
|
||||||
let area = ui::centered_rect(30, 20, f.size());
|
let area = ui::centered_rect(30, 20, f.size());
|
||||||
f.render_widget(Clear, area);
|
f.render_widget(Clear, area);
|
||||||
f.render_widget(
|
f.render_widget(
|
||||||
Paragraph::new(txt.iter())
|
dialog_paragraph(strings::RESET_TITLE, txt.iter()),
|
||||||
.block(
|
|
||||||
Block::default()
|
|
||||||
.title(strings::RESET_TITLE)
|
|
||||||
.borders(Borders::ALL),
|
|
||||||
)
|
|
||||||
.alignment(Alignment::Left),
|
|
||||||
area,
|
area,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -106,11 +102,12 @@ impl Component for ResetComponent {
|
||||||
|
|
||||||
impl ResetComponent {
|
impl ResetComponent {
|
||||||
///
|
///
|
||||||
pub fn new(queue: Queue) -> Self {
|
pub fn new(queue: Queue, theme: Theme) -> Self {
|
||||||
Self {
|
Self {
|
||||||
target: None,
|
target: None,
|
||||||
visible: false,
|
visible: false,
|
||||||
queue,
|
queue,
|
||||||
|
theme,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
///
|
///
|
||||||
|
|
|
||||||
13
src/main.rs
13
src/main.rs
|
|
@ -31,6 +31,7 @@ use scopeguard::defer;
|
||||||
use scopetime::scope_time;
|
use scopetime::scope_time;
|
||||||
use simplelog::{Config, LevelFilter, WriteLogger};
|
use simplelog::{Config, LevelFilter, WriteLogger};
|
||||||
use spinner::Spinner;
|
use spinner::Spinner;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::{
|
use std::{
|
||||||
env, fs,
|
env, fs,
|
||||||
fs::File,
|
fs::File,
|
||||||
|
|
@ -175,12 +176,18 @@ fn start_terminal<W: Write>(
|
||||||
Ok(terminal)
|
Ok(terminal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_app_config_path() -> PathBuf {
|
||||||
|
let mut path = dirs::cache_dir().unwrap();
|
||||||
|
path.push("gitui");
|
||||||
|
fs::create_dir_all(&path).unwrap();
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
fn setup_logging() {
|
fn setup_logging() {
|
||||||
if env::var("GITUI_LOGGING").is_ok() {
|
if env::var("GITUI_LOGGING").is_ok() {
|
||||||
let mut path = dirs::cache_dir().unwrap();
|
let mut path = get_app_config_path();
|
||||||
path.push("gitui");
|
|
||||||
path.push("gitui.log");
|
path.push("gitui.log");
|
||||||
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
|
||||||
|
|
||||||
let _ = WriteLogger::init(
|
let _ = WriteLogger::init(
|
||||||
LevelFilter::Trace,
|
LevelFilter::Trace,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
|
use crate::ui::style::Theme;
|
||||||
use crate::{
|
use crate::{
|
||||||
components::{
|
components::{
|
||||||
CommandBlocking, CommandInfo, Component, DrawableComponent,
|
CommandBlocking, CommandInfo, Component, DrawableComponent,
|
||||||
|
|
@ -17,34 +18,13 @@ use sync::Tags;
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
layout::{Alignment, Rect},
|
layout::{Alignment, Rect},
|
||||||
style::{Color, Style},
|
|
||||||
widgets::{Block, Borders, Paragraph, Text},
|
widgets::{Block, Borders, Paragraph, Text},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
use utils::{ItemBatch, LogEntry};
|
use utils::{ItemBatch, LogEntry};
|
||||||
|
|
||||||
const COLOR_SELECTION_BG: Color = Color::Blue;
|
|
||||||
|
|
||||||
const STYLE_TAG: Style = Style::new().fg(Color::Yellow);
|
|
||||||
const STYLE_HASH: Style = Style::new().fg(Color::Magenta);
|
|
||||||
const STYLE_TIME: Style = Style::new().fg(Color::Blue);
|
|
||||||
const STYLE_AUTHOR: Style = Style::new().fg(Color::Green);
|
|
||||||
const STYLE_MSG: Style = Style::new().fg(Color::Reset);
|
|
||||||
|
|
||||||
const STYLE_TAG_SELECTED: Style =
|
|
||||||
Style::new().fg(Color::Yellow).bg(COLOR_SELECTION_BG);
|
|
||||||
const STYLE_HASH_SELECTED: Style =
|
|
||||||
Style::new().fg(Color::Magenta).bg(COLOR_SELECTION_BG);
|
|
||||||
const STYLE_TIME_SELECTED: Style =
|
|
||||||
Style::new().fg(Color::White).bg(COLOR_SELECTION_BG);
|
|
||||||
const STYLE_AUTHOR_SELECTED: Style =
|
|
||||||
Style::new().fg(Color::Green).bg(COLOR_SELECTION_BG);
|
|
||||||
const STYLE_MSG_SELECTED: Style =
|
|
||||||
Style::new().fg(Color::Reset).bg(COLOR_SELECTION_BG);
|
|
||||||
|
|
||||||
static ELEMENTS_PER_LINE: usize = 10;
|
static ELEMENTS_PER_LINE: usize = 10;
|
||||||
static SLICE_SIZE: usize = 1200;
|
static SLICE_SIZE: usize = 1200;
|
||||||
|
|
||||||
///
|
///
|
||||||
pub struct Revlog {
|
pub struct Revlog {
|
||||||
selection: usize,
|
selection: usize,
|
||||||
|
|
@ -57,11 +37,15 @@ pub struct Revlog {
|
||||||
tags: Tags,
|
tags: Tags,
|
||||||
current_size: (u16, u16),
|
current_size: (u16, u16),
|
||||||
scroll_top: usize,
|
scroll_top: usize,
|
||||||
|
theme: Theme,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Revlog {
|
impl Revlog {
|
||||||
///
|
///
|
||||||
pub fn new(sender: &Sender<AsyncNotification>) -> Self {
|
pub fn new(
|
||||||
|
sender: &Sender<AsyncNotification>,
|
||||||
|
theme: Theme,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
items: ItemBatch::default(),
|
items: ItemBatch::default(),
|
||||||
git_log: AsyncLog::new(sender.clone()),
|
git_log: AsyncLog::new(sender.clone()),
|
||||||
|
|
@ -73,6 +57,7 @@ impl Revlog {
|
||||||
tags: Tags::new(),
|
tags: Tags::new(),
|
||||||
current_size: (0, 0),
|
current_size: (0, 0),
|
||||||
scroll_top: 0,
|
scroll_top: 0,
|
||||||
|
theme,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,44 +156,27 @@ impl Revlog {
|
||||||
selected: bool,
|
selected: bool,
|
||||||
txt: &mut Vec<Text<'a>>,
|
txt: &mut Vec<Text<'a>>,
|
||||||
tags: Option<String>,
|
tags: Option<String>,
|
||||||
|
theme: Theme,
|
||||||
) {
|
) {
|
||||||
let count_before = txt.len();
|
let count_before = txt.len();
|
||||||
|
|
||||||
let splitter_txt = Cow::from(" ");
|
let splitter_txt = Cow::from(" ");
|
||||||
let splitter = if selected {
|
let splitter =
|
||||||
Text::Styled(
|
Text::Styled(splitter_txt, theme.text(true, selected));
|
||||||
splitter_txt,
|
|
||||||
Style::new().bg(COLOR_SELECTION_BG),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Text::Raw(splitter_txt)
|
|
||||||
};
|
|
||||||
|
|
||||||
txt.push(Text::Styled(
|
txt.push(Text::Styled(
|
||||||
Cow::from(&e.hash[0..7]),
|
Cow::from(&e.hash[0..7]),
|
||||||
if selected {
|
theme.table(0, selected),
|
||||||
STYLE_HASH_SELECTED
|
|
||||||
} else {
|
|
||||||
STYLE_HASH
|
|
||||||
},
|
|
||||||
));
|
));
|
||||||
txt.push(splitter.clone());
|
txt.push(splitter.clone());
|
||||||
txt.push(Text::Styled(
|
txt.push(Text::Styled(
|
||||||
Cow::from(e.time.as_str()),
|
Cow::from(e.time.as_str()),
|
||||||
if selected {
|
theme.table(1, selected),
|
||||||
STYLE_TIME_SELECTED
|
|
||||||
} else {
|
|
||||||
STYLE_TIME
|
|
||||||
},
|
|
||||||
));
|
));
|
||||||
txt.push(splitter.clone());
|
txt.push(splitter.clone());
|
||||||
txt.push(Text::Styled(
|
txt.push(Text::Styled(
|
||||||
Cow::from(e.author.as_str()),
|
Cow::from(e.author.as_str()),
|
||||||
if selected {
|
theme.table(2, selected),
|
||||||
STYLE_AUTHOR_SELECTED
|
|
||||||
} else {
|
|
||||||
STYLE_AUTHOR
|
|
||||||
},
|
|
||||||
));
|
));
|
||||||
txt.push(splitter.clone());
|
txt.push(splitter.clone());
|
||||||
txt.push(Text::Styled(
|
txt.push(Text::Styled(
|
||||||
|
|
@ -217,20 +185,12 @@ impl Revlog {
|
||||||
} else {
|
} else {
|
||||||
String::from("")
|
String::from("")
|
||||||
}),
|
}),
|
||||||
if selected {
|
theme.tab(true).bg(theme.text(true, selected).bg),
|
||||||
STYLE_TAG_SELECTED
|
|
||||||
} else {
|
|
||||||
STYLE_TAG
|
|
||||||
},
|
|
||||||
));
|
));
|
||||||
txt.push(splitter);
|
txt.push(splitter);
|
||||||
txt.push(Text::Styled(
|
txt.push(Text::Styled(
|
||||||
Cow::from(e.msg.as_str()),
|
Cow::from(e.msg.as_str()),
|
||||||
if selected {
|
theme.text(true, selected),
|
||||||
STYLE_MSG_SELECTED
|
|
||||||
} else {
|
|
||||||
STYLE_MSG
|
|
||||||
},
|
|
||||||
));
|
));
|
||||||
txt.push(Text::Raw(Cow::from("\n")));
|
txt.push(Text::Raw(Cow::from("\n")));
|
||||||
|
|
||||||
|
|
@ -248,7 +208,13 @@ impl Revlog {
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
Self::add_entry(e, idx == selection, &mut txt, tag);
|
Self::add_entry(
|
||||||
|
e,
|
||||||
|
idx == selection,
|
||||||
|
&mut txt,
|
||||||
|
tag,
|
||||||
|
self.theme,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
txt
|
txt
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::ui::style::Theme;
|
||||||
use crate::{
|
use crate::{
|
||||||
accessors,
|
accessors,
|
||||||
components::{
|
components::{
|
||||||
|
|
@ -99,6 +100,7 @@ impl Status {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
sender: &Sender<AsyncNotification>,
|
sender: &Sender<AsyncNotification>,
|
||||||
queue: &Queue,
|
queue: &Queue,
|
||||||
|
theme: Theme,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
visible: true,
|
visible: true,
|
||||||
|
|
@ -109,14 +111,16 @@ impl Status {
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
queue.clone(),
|
queue.clone(),
|
||||||
|
theme,
|
||||||
),
|
),
|
||||||
index: ChangesComponent::new(
|
index: ChangesComponent::new(
|
||||||
strings::TITLE_INDEX,
|
strings::TITLE_INDEX,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
queue.clone(),
|
queue.clone(),
|
||||||
|
theme,
|
||||||
),
|
),
|
||||||
diff: DiffComponent::new(queue.clone()),
|
diff: DiffComponent::new(queue.clone(), theme),
|
||||||
git_diff: AsyncDiff::new(sender.clone()),
|
git_diff: AsyncDiff::new(sender.clone()),
|
||||||
git_status: AsyncStatus::new(sender.clone()),
|
git_status: AsyncStatus::new(sender.clone()),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
mod scrolllist;
|
mod scrolllist;
|
||||||
|
pub(crate) mod style;
|
||||||
|
use crate::ui::style::Theme;
|
||||||
use scrolllist::ScrollableList;
|
use scrolllist::ScrollableList;
|
||||||
|
use tui::style::Modifier;
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
layout::{Constraint, Direction, Layout, Rect},
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
style::{Color, Modifier, Style},
|
|
||||||
widgets::{Block, Borders, Text},
|
widgets::{Block, Borders, Text},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
|
@ -76,24 +77,24 @@ pub fn draw_list<'b, B: Backend, L>(
|
||||||
items: L,
|
items: L,
|
||||||
select: Option<usize>,
|
select: Option<usize>,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
|
theme: Theme,
|
||||||
) where
|
) where
|
||||||
L: Iterator<Item = Text<'b>>,
|
L: Iterator<Item = Text<'b>>,
|
||||||
{
|
{
|
||||||
let mut style_border = Style::default().fg(Color::DarkGray);
|
let style = if selected {
|
||||||
let mut style_title = Style::default();
|
theme.block(selected).modifier(Modifier::BOLD)
|
||||||
if selected {
|
} else {
|
||||||
style_border = style_border.fg(Color::Gray);
|
theme.block(selected)
|
||||||
style_title = style_title.modifier(Modifier::BOLD);
|
};
|
||||||
}
|
|
||||||
let list = ScrollableList::new(items)
|
let list = ScrollableList::new(items)
|
||||||
.block(
|
.block(
|
||||||
Block::default()
|
Block::default()
|
||||||
.title(title)
|
.title(title)
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.title_style(style_title)
|
.title_style(style)
|
||||||
.border_style(style_border),
|
.border_style(theme.block(selected)),
|
||||||
)
|
)
|
||||||
.scroll(select.unwrap_or_default())
|
.scroll(select.unwrap_or_default());
|
||||||
.style(Style::default().fg(Color::White));
|
|
||||||
f.render_widget(list, r)
|
f.render_widget(list, r)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,11 +38,6 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn style(mut self, style: Style) -> Self {
|
|
||||||
self.style = style;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scroll(mut self, index: usize) -> Self {
|
pub fn scroll(mut self, index: usize) -> Self {
|
||||||
self.scroll = index;
|
self.scroll = index;
|
||||||
self
|
self
|
||||||
|
|
|
||||||
237
src/ui/style.rs
Normal file
237
src/ui/style.rs
Normal file
|
|
@ -0,0 +1,237 @@
|
||||||
|
use crate::get_app_config_path;
|
||||||
|
use asyncgit::{DiffLineType, StatusItemType};
|
||||||
|
use ron::de::from_bytes;
|
||||||
|
use ron::ser::{to_string_pretty, PrettyConfig};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use tui::style::{Color, Modifier, Style};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy)]
|
||||||
|
pub struct Theme {
|
||||||
|
selected_tab: ColorDef,
|
||||||
|
command_foreground: ColorDef,
|
||||||
|
command_background: ColorDef,
|
||||||
|
command_disabled: ColorDef,
|
||||||
|
diff_line_add: ColorDef,
|
||||||
|
diff_line_delete: ColorDef,
|
||||||
|
diff_file_added: ColorDef,
|
||||||
|
diff_file_removed: ColorDef,
|
||||||
|
diff_file_moved: ColorDef,
|
||||||
|
diff_file_modified: ColorDef,
|
||||||
|
table_colors: [ColorDef; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const DARK_THEME: Theme = Theme {
|
||||||
|
selected_tab: ColorDef::Yellow,
|
||||||
|
command_foreground: ColorDef::White,
|
||||||
|
command_background: ColorDef::Rgb(0, 0, 100),
|
||||||
|
command_disabled: ColorDef::DarkGray,
|
||||||
|
diff_line_add: ColorDef::Green,
|
||||||
|
diff_line_delete: ColorDef::Red,
|
||||||
|
diff_file_added: ColorDef::LightGreen,
|
||||||
|
diff_file_removed: ColorDef::LightRed,
|
||||||
|
diff_file_moved: ColorDef::LightMagenta,
|
||||||
|
diff_file_modified: ColorDef::Yellow,
|
||||||
|
table_colors: [
|
||||||
|
ColorDef::Magenta,
|
||||||
|
ColorDef::Blue,
|
||||||
|
ColorDef::Green,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Theme {
|
||||||
|
pub fn block(&self, focus: bool) -> Style {
|
||||||
|
if focus {
|
||||||
|
Style::default()
|
||||||
|
} else {
|
||||||
|
Style::default().fg(self.command_disabled.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tab(&self, selected: bool) -> Style {
|
||||||
|
if selected {
|
||||||
|
Style::default().fg(self.selected_tab.into())
|
||||||
|
} else {
|
||||||
|
Style::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn text(&self, enabled: bool, selected: bool) -> Style {
|
||||||
|
match (enabled, selected) {
|
||||||
|
(false, _) => {
|
||||||
|
Style::default().fg(self.command_disabled.into())
|
||||||
|
}
|
||||||
|
(true, false) => Style::default(),
|
||||||
|
(true, true) => {
|
||||||
|
Style::default().bg(self.command_background.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn item(&self, typ: StatusItemType, selected: bool) -> Style {
|
||||||
|
let style = match typ {
|
||||||
|
StatusItemType::New => {
|
||||||
|
Style::default().fg(self.diff_file_added.into())
|
||||||
|
}
|
||||||
|
StatusItemType::Modified => {
|
||||||
|
Style::default().fg(self.diff_file_modified.into())
|
||||||
|
}
|
||||||
|
StatusItemType::Deleted => {
|
||||||
|
Style::default().fg(self.diff_file_removed.into())
|
||||||
|
}
|
||||||
|
StatusItemType::Renamed => {
|
||||||
|
Style::default().fg(self.diff_file_moved.into())
|
||||||
|
}
|
||||||
|
_ => Style::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.apply_select(style, selected)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_select(&self, style: Style, selected: bool) -> Style {
|
||||||
|
if selected {
|
||||||
|
style.bg(self.command_background.into())
|
||||||
|
} else {
|
||||||
|
style
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn diff_line(
|
||||||
|
&self,
|
||||||
|
typ: DiffLineType,
|
||||||
|
selected: bool,
|
||||||
|
) -> Style {
|
||||||
|
let style = match typ {
|
||||||
|
DiffLineType::Add => {
|
||||||
|
Style::default().fg(self.diff_line_add.into())
|
||||||
|
}
|
||||||
|
DiffLineType::Delete => {
|
||||||
|
Style::default().fg(self.diff_line_delete.into())
|
||||||
|
}
|
||||||
|
DiffLineType::Header => {
|
||||||
|
Style::default().modifier(Modifier::BOLD)
|
||||||
|
}
|
||||||
|
_ => Style::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.apply_select(style, selected)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn text_danger(&self) -> Style {
|
||||||
|
Style::default().fg(self.diff_file_removed.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toolbar(&self, enabled: bool) -> Style {
|
||||||
|
if enabled {
|
||||||
|
Style::default().fg(self.command_foreground.into())
|
||||||
|
} else {
|
||||||
|
Style::default().fg(self.command_disabled.into())
|
||||||
|
}
|
||||||
|
.bg(self.command_background.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn table(&self, column: usize, selected: bool) -> Style {
|
||||||
|
self.apply_select(
|
||||||
|
Style::default().fg(self.table_colors[column].into()),
|
||||||
|
selected,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save(&self) -> Result<(), std::io::Error> {
|
||||||
|
let theme_file = Self::get_theme_file();
|
||||||
|
let mut file = File::create(theme_file)?;
|
||||||
|
let data = to_string_pretty(self, PrettyConfig::default())
|
||||||
|
.map_err(|_| std::io::Error::from_raw_os_error(100))?;
|
||||||
|
file.write_all(data.as_bytes())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_theme_file() -> PathBuf {
|
||||||
|
let app_home = get_app_config_path();
|
||||||
|
app_home.join("theme.ron")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_file(
|
||||||
|
theme_file: PathBuf,
|
||||||
|
) -> Result<Theme, std::io::Error> {
|
||||||
|
if theme_file.exists() {
|
||||||
|
let mut f = File::open(theme_file)?;
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
f.read_to_end(&mut buffer)?;
|
||||||
|
|
||||||
|
Ok(from_bytes(&buffer).map_err(|_| {
|
||||||
|
std::io::Error::from_raw_os_error(100)
|
||||||
|
})?)
|
||||||
|
} else {
|
||||||
|
Err(std::io::Error::from_raw_os_error(100))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init() -> Theme {
|
||||||
|
if let Ok(x) = Theme::read_file(Theme::get_theme_file()) {
|
||||||
|
x
|
||||||
|
} else {
|
||||||
|
DARK_THEME.save().unwrap_or_default();
|
||||||
|
DARK_THEME
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// we duplicate the Color definition from `tui` crate to implement Serde serialisation
|
||||||
|
/// this enum can be removed once [tui-#292](https://github.com/fdehau/tui-rs/issues/292) is resolved
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
|
||||||
|
pub enum ColorDef {
|
||||||
|
Reset,
|
||||||
|
Black,
|
||||||
|
Red,
|
||||||
|
Green,
|
||||||
|
Yellow,
|
||||||
|
Blue,
|
||||||
|
Magenta,
|
||||||
|
Cyan,
|
||||||
|
Gray,
|
||||||
|
DarkGray,
|
||||||
|
LightRed,
|
||||||
|
LightGreen,
|
||||||
|
LightYellow,
|
||||||
|
LightBlue,
|
||||||
|
LightMagenta,
|
||||||
|
LightCyan,
|
||||||
|
White,
|
||||||
|
Rgb(u8, u8, u8),
|
||||||
|
Indexed(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ColorDef {
|
||||||
|
fn default() -> Self {
|
||||||
|
ColorDef::Reset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ColorDef> for Color {
|
||||||
|
fn from(def: ColorDef) -> Self {
|
||||||
|
match def {
|
||||||
|
ColorDef::Reset => Color::Reset,
|
||||||
|
ColorDef::Black => Color::Black,
|
||||||
|
ColorDef::Red => Color::Red,
|
||||||
|
ColorDef::Green => Color::Green,
|
||||||
|
ColorDef::Yellow => Color::Yellow,
|
||||||
|
ColorDef::Blue => Color::Blue,
|
||||||
|
ColorDef::Magenta => Color::Magenta,
|
||||||
|
ColorDef::Cyan => Color::Cyan,
|
||||||
|
ColorDef::Gray => Color::Gray,
|
||||||
|
ColorDef::DarkGray => Color::DarkGray,
|
||||||
|
ColorDef::LightRed => Color::LightRed,
|
||||||
|
ColorDef::LightGreen => Color::LightGreen,
|
||||||
|
ColorDef::LightYellow => Color::LightYellow,
|
||||||
|
ColorDef::LightBlue => Color::LightBlue,
|
||||||
|
ColorDef::LightMagenta => Color::LightMagenta,
|
||||||
|
ColorDef::LightCyan => Color::LightCyan,
|
||||||
|
ColorDef::White => Color::White,
|
||||||
|
ColorDef::Rgb(a, b, c) => Color::Rgb(a, b, c),
|
||||||
|
ColorDef::Indexed(x) => Color::Indexed(x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue