mirror of
https://github.com/gitui-org/gitui
synced 2026-05-24 01:18:21 +00:00
parent
91370f874b
commit
4de0e52418
8 changed files with 209 additions and 53 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -330,6 +330,7 @@ dependencies = [
|
|||
"serde",
|
||||
"simplelog",
|
||||
"tui",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ backtrace = "0.3"
|
|||
ron = "0.6"
|
||||
serde = "1.0"
|
||||
anyhow = "1.0.31"
|
||||
unicode-width = "0.1"
|
||||
|
||||
[features]
|
||||
default=[]
|
||||
|
|
|
|||
67
src/app.rs
67
src/app.rs
|
|
@ -1,5 +1,6 @@
|
|||
use crate::{
|
||||
accessors,
|
||||
cmdbar::CommandBar,
|
||||
components::{
|
||||
event_pump, CommandBlocking, CommandInfo, CommitComponent,
|
||||
Component, DrawableComponent, HelpComponent, MsgComponent,
|
||||
|
|
@ -15,15 +16,13 @@ use anyhow::{anyhow, Result};
|
|||
use asyncgit::{sync, AsyncNotification, CWD};
|
||||
use crossbeam_channel::Sender;
|
||||
use crossterm::event::Event;
|
||||
use itertools::Itertools;
|
||||
use std::borrow::Cow;
|
||||
use strings::commands;
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
style::Modifier,
|
||||
style::Style,
|
||||
widgets::{Block, Borders, Paragraph, Tabs, Text},
|
||||
widgets::{Block, Borders, Tabs},
|
||||
Frame,
|
||||
};
|
||||
///
|
||||
|
|
@ -34,7 +33,7 @@ pub struct App {
|
|||
reset: ResetComponent,
|
||||
commit: CommitComponent,
|
||||
stashmsg_popup: StashMsgComponent,
|
||||
current_commands: Vec<CommandInfo>,
|
||||
cmdbar: CommandBar,
|
||||
tab: usize,
|
||||
revlog: Revlog,
|
||||
status_tab: Status,
|
||||
|
|
@ -60,7 +59,7 @@ impl App {
|
|||
&theme,
|
||||
),
|
||||
do_quit: false,
|
||||
current_commands: Vec::new(),
|
||||
cmdbar: CommandBar::new(&theme),
|
||||
help: HelpComponent::new(&theme),
|
||||
msg: MsgComponent::new(&theme),
|
||||
tab: 0,
|
||||
|
|
@ -78,17 +77,23 @@ impl App {
|
|||
&mut self,
|
||||
f: &mut Frame<B>,
|
||||
) -> Result<()> {
|
||||
let fsize = f.size();
|
||||
|
||||
self.cmdbar.refresh_width(fsize.width);
|
||||
|
||||
let chunks_main = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(2),
|
||||
Constraint::Min(2),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(self.cmdbar.height()),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(f.size());
|
||||
.split(fsize);
|
||||
|
||||
self.cmdbar.draw(f, chunks_main[2]);
|
||||
|
||||
self.draw_tabs(f, chunks_main[0]);
|
||||
|
||||
|
|
@ -101,13 +106,6 @@ impl App {
|
|||
_ => return Err(anyhow!("unknown tab")),
|
||||
};
|
||||
|
||||
Self::draw_commands(
|
||||
f,
|
||||
chunks_main[2],
|
||||
self.current_commands.as_slice(),
|
||||
self.theme,
|
||||
);
|
||||
|
||||
self.draw_popups(f)?;
|
||||
|
||||
Ok(())
|
||||
|
|
@ -135,6 +133,10 @@ impl App {
|
|||
self.toggle_tabs(true)?;
|
||||
NeedsUpdate::COMMANDS
|
||||
}
|
||||
keys::CMD_BAR_TOGGLE => {
|
||||
self.cmdbar.toggle_more();
|
||||
NeedsUpdate::empty()
|
||||
}
|
||||
|
||||
_ => NeedsUpdate::empty(),
|
||||
};
|
||||
|
|
@ -267,8 +269,7 @@ impl App {
|
|||
|
||||
fn update_commands(&mut self) {
|
||||
self.help.set_cmds(self.commands(true));
|
||||
self.current_commands = self.commands(false);
|
||||
self.current_commands.sort_by_key(|e| e.order);
|
||||
self.cmdbar.set_cmds(self.commands(false));
|
||||
}
|
||||
|
||||
fn process_queue(&mut self) -> Result<NeedsUpdate> {
|
||||
|
|
@ -417,36 +418,4 @@ impl App {
|
|||
r,
|
||||
);
|
||||
}
|
||||
|
||||
fn draw_commands<B: Backend>(
|
||||
f: &mut Frame<B>,
|
||||
r: Rect,
|
||||
cmds: &[CommandInfo],
|
||||
theme: Theme,
|
||||
) {
|
||||
let splitter = Text::Styled(
|
||||
Cow::from(strings::CMD_SPLITTER),
|
||||
Style::default(),
|
||||
);
|
||||
|
||||
let texts = cmds
|
||||
.iter()
|
||||
.filter_map(|c| {
|
||||
if c.show_in_quickbar() {
|
||||
Some(Text::Styled(
|
||||
Cow::from(c.text.name),
|
||||
theme.toolbar(c.enabled),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
f.render_widget(
|
||||
Paragraph::new(texts.iter().intersperse(&splitter))
|
||||
.alignment(Alignment::Left),
|
||||
r,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
176
src/cmdbar.rs
Normal file
176
src/cmdbar.rs
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
use crate::{components::CommandInfo, strings, ui::style::Theme};
|
||||
use std::borrow::Cow;
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Alignment, Rect},
|
||||
widgets::{Paragraph, Text},
|
||||
Frame,
|
||||
};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
enum DrawListEntry {
|
||||
LineBreak,
|
||||
Splitter,
|
||||
Command(Command),
|
||||
}
|
||||
|
||||
struct Command {
|
||||
txt: String,
|
||||
enabled: bool,
|
||||
line: usize,
|
||||
}
|
||||
|
||||
/// helper to be used while drawing
|
||||
pub struct CommandBar {
|
||||
draw_list: Vec<DrawListEntry>,
|
||||
cmd_infos: Vec<CommandInfo>,
|
||||
theme: Theme,
|
||||
lines: u16,
|
||||
width: u16,
|
||||
expandable: bool,
|
||||
expanded: bool,
|
||||
}
|
||||
|
||||
const MORE_WIDTH: u16 = 11;
|
||||
|
||||
impl CommandBar {
|
||||
pub const fn new(theme: &Theme) -> Self {
|
||||
Self {
|
||||
draw_list: Vec::new(),
|
||||
cmd_infos: Vec::new(),
|
||||
theme: *theme,
|
||||
lines: 0,
|
||||
width: 0,
|
||||
expandable: false,
|
||||
expanded: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh_width(&mut self, width: u16) {
|
||||
if width != self.width {
|
||||
self.refresh_list(width);
|
||||
self.width = width;
|
||||
}
|
||||
}
|
||||
|
||||
fn is_multiline(&self, width: u16) -> bool {
|
||||
let mut line_width = 0_usize;
|
||||
for c in &self.cmd_infos {
|
||||
let entry_w = UnicodeWidthStr::width(c.text.name);
|
||||
|
||||
if line_width + entry_w > width as usize {
|
||||
return true;
|
||||
}
|
||||
|
||||
line_width += entry_w + 1;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn refresh_list(&mut self, width: u16) {
|
||||
self.draw_list.clear();
|
||||
|
||||
let width = if self.is_multiline(width) {
|
||||
width.saturating_sub(MORE_WIDTH)
|
||||
} else {
|
||||
width
|
||||
};
|
||||
|
||||
let mut line_width = 0_usize;
|
||||
let mut lines = 1_u16;
|
||||
|
||||
for c in &self.cmd_infos {
|
||||
let entry_w = UnicodeWidthStr::width(c.text.name);
|
||||
|
||||
if line_width + entry_w > width as usize {
|
||||
self.draw_list.push(DrawListEntry::LineBreak);
|
||||
line_width = 0;
|
||||
lines += 1;
|
||||
} else if line_width > 0 {
|
||||
self.draw_list.push(DrawListEntry::Splitter);
|
||||
}
|
||||
|
||||
line_width += entry_w + 1;
|
||||
|
||||
self.draw_list.push(DrawListEntry::Command(Command {
|
||||
txt: c.text.name.to_string(),
|
||||
enabled: c.enabled,
|
||||
line: lines.saturating_sub(1) as usize,
|
||||
}));
|
||||
}
|
||||
|
||||
self.expandable = lines > 1;
|
||||
|
||||
self.lines = lines;
|
||||
}
|
||||
|
||||
pub fn set_cmds(&mut self, cmds: Vec<CommandInfo>) {
|
||||
self.cmd_infos = cmds
|
||||
.into_iter()
|
||||
.filter(CommandInfo::show_in_quickbar)
|
||||
.collect::<Vec<_>>();
|
||||
self.cmd_infos.sort_by_key(|e| e.order);
|
||||
self.refresh_list(self.width);
|
||||
}
|
||||
|
||||
pub fn height(&self) -> u16 {
|
||||
if self.expandable && self.expanded {
|
||||
self.lines
|
||||
} else {
|
||||
1_u16
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_more(&mut self) {
|
||||
if self.expandable {
|
||||
self.expanded = !self.expanded;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw<B: Backend>(&self, f: &mut Frame<B>, r: Rect) {
|
||||
let splitter = Text::Raw(Cow::from(strings::CMD_SPLITTER));
|
||||
|
||||
let texts = self
|
||||
.draw_list
|
||||
.iter()
|
||||
.map(|c| match c {
|
||||
DrawListEntry::Command(c) => Text::Styled(
|
||||
Cow::from(c.txt.as_str()),
|
||||
self.theme.commandbar(c.enabled, c.line),
|
||||
),
|
||||
DrawListEntry::LineBreak => {
|
||||
Text::Raw(Cow::from("\n"))
|
||||
}
|
||||
DrawListEntry::Splitter => splitter.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
f.render_widget(
|
||||
Paragraph::new(texts.iter()).alignment(Alignment::Left),
|
||||
r,
|
||||
);
|
||||
|
||||
if self.expandable {
|
||||
let r = Rect::new(
|
||||
r.width.saturating_sub(MORE_WIDTH),
|
||||
r.y + r.height.saturating_sub(1),
|
||||
MORE_WIDTH,
|
||||
1,
|
||||
);
|
||||
|
||||
f.render_widget(
|
||||
Paragraph::new(
|
||||
vec![Text::Raw(Cow::from(if self.expanded {
|
||||
"less [.]"
|
||||
} else {
|
||||
"more [.]"
|
||||
}))]
|
||||
.iter(),
|
||||
)
|
||||
.alignment(Alignment::Right),
|
||||
r,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -50,3 +50,4 @@ pub const STASHING_TOGGLE_INDEX: KeyEvent =
|
|||
pub const STASH_APPLY: KeyEvent = no_mod(KeyCode::Enter);
|
||||
pub const STASH_DROP: KeyEvent =
|
||||
with_mod(KeyCode::Char('D'), KeyModifiers::SHIFT);
|
||||
pub const CMD_BAR_TOGGLE: KeyEvent = no_mod(KeyCode::Char('.'));
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#![allow(clippy::module_name_repetitions)]
|
||||
|
||||
mod app;
|
||||
mod cmdbar;
|
||||
mod components;
|
||||
mod keys;
|
||||
mod poll;
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ pub mod commands {
|
|||
);
|
||||
///
|
||||
pub static DIFF_HOME_END: CommandText = CommandText::new(
|
||||
"Jump up/down [home,end,shift+up,shift+down]",
|
||||
"Jump up/down [home,end,\u{11014}up,\u{2191}down]",
|
||||
"scroll to top or bottom of diff",
|
||||
CMD_GROUP_DIFF,
|
||||
);
|
||||
|
|
@ -155,7 +155,7 @@ pub mod commands {
|
|||
);
|
||||
///
|
||||
pub static QUIT: CommandText = CommandText::new(
|
||||
"Quit [ctrl+c]",
|
||||
"Quit [^c]",
|
||||
"quit gitui application",
|
||||
CMD_GROUP_GENERAL,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ pub struct Theme {
|
|||
#[serde(with = "ColorDef")]
|
||||
selection_bg: Color,
|
||||
#[serde(with = "ColorDef")]
|
||||
cmdbar_extra_lines_bg: Color,
|
||||
#[serde(with = "ColorDef")]
|
||||
disabled_fg: Color,
|
||||
#[serde(with = "ColorDef")]
|
||||
diff_line_add: Color,
|
||||
|
|
@ -153,13 +155,17 @@ impl Theme {
|
|||
Style::default().fg(self.danger_fg)
|
||||
}
|
||||
|
||||
pub fn toolbar(&self, enabled: bool) -> Style {
|
||||
pub fn commandbar(&self, enabled: bool, line: usize) -> Style {
|
||||
if enabled {
|
||||
Style::default().fg(self.command_fg)
|
||||
} else {
|
||||
Style::default().fg(self.disabled_fg)
|
||||
}
|
||||
.bg(self.selection_bg)
|
||||
.bg(if line == 0 {
|
||||
self.selection_bg
|
||||
} else {
|
||||
self.cmdbar_extra_lines_bg
|
||||
})
|
||||
}
|
||||
|
||||
pub fn commit_hash(&self, selected: bool) -> Style {
|
||||
|
|
@ -225,6 +231,7 @@ impl Default for Theme {
|
|||
selected_tab: Color::Yellow,
|
||||
command_fg: Color::White,
|
||||
selection_bg: Color::Rgb(0, 0, 100),
|
||||
cmdbar_extra_lines_bg: Color::Rgb(0, 0, 80),
|
||||
disabled_fg: Color::DarkGray,
|
||||
diff_line_add: Color::Green,
|
||||
diff_line_delete: Color::Red,
|
||||
|
|
|
|||
Loading…
Reference in a new issue