Skip to content

Commit 4de0e52

Browse files
author
Stephan Dilly
authored
Support more commands (#101)
closes #83
1 parent 91370f8 commit 4de0e52

File tree

8 files changed

+209
-53
lines changed

8 files changed

+209
-53
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ backtrace = "0.3"
3737
ron = "0.6"
3838
serde = "1.0"
3939
anyhow = "1.0.31"
40+
unicode-width = "0.1"
4041

4142
[features]
4243
default=[]

src/app.rs

Lines changed: 18 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::{
22
accessors,
3+
cmdbar::CommandBar,
34
components::{
45
event_pump, CommandBlocking, CommandInfo, CommitComponent,
56
Component, DrawableComponent, HelpComponent, MsgComponent,
@@ -15,15 +16,13 @@ use anyhow::{anyhow, Result};
1516
use asyncgit::{sync, AsyncNotification, CWD};
1617
use crossbeam_channel::Sender;
1718
use crossterm::event::Event;
18-
use itertools::Itertools;
19-
use std::borrow::Cow;
2019
use strings::commands;
2120
use tui::{
2221
backend::Backend,
23-
layout::{Alignment, Constraint, Direction, Layout, Rect},
22+
layout::{Constraint, Direction, Layout, Rect},
2423
style::Modifier,
2524
style::Style,
26-
widgets::{Block, Borders, Paragraph, Tabs, Text},
25+
widgets::{Block, Borders, Tabs},
2726
Frame,
2827
};
2928
///
@@ -34,7 +33,7 @@ pub struct App {
3433
reset: ResetComponent,
3534
commit: CommitComponent,
3635
stashmsg_popup: StashMsgComponent,
37-
current_commands: Vec<CommandInfo>,
36+
cmdbar: CommandBar,
3837
tab: usize,
3938
revlog: Revlog,
4039
status_tab: Status,
@@ -60,7 +59,7 @@ impl App {
6059
&theme,
6160
),
6261
do_quit: false,
63-
current_commands: Vec::new(),
62+
cmdbar: CommandBar::new(&theme),
6463
help: HelpComponent::new(&theme),
6564
msg: MsgComponent::new(&theme),
6665
tab: 0,
@@ -78,17 +77,23 @@ impl App {
7877
&mut self,
7978
f: &mut Frame<B>,
8079
) -> Result<()> {
80+
let fsize = f.size();
81+
82+
self.cmdbar.refresh_width(fsize.width);
83+
8184
let chunks_main = Layout::default()
8285
.direction(Direction::Vertical)
8386
.constraints(
8487
[
8588
Constraint::Length(2),
8689
Constraint::Min(2),
87-
Constraint::Length(1),
90+
Constraint::Length(self.cmdbar.height()),
8891
]
8992
.as_ref(),
9093
)
91-
.split(f.size());
94+
.split(fsize);
95+
96+
self.cmdbar.draw(f, chunks_main[2]);
9297

9398
self.draw_tabs(f, chunks_main[0]);
9499

@@ -101,13 +106,6 @@ impl App {
101106
_ => return Err(anyhow!("unknown tab")),
102107
};
103108

104-
Self::draw_commands(
105-
f,
106-
chunks_main[2],
107-
self.current_commands.as_slice(),
108-
self.theme,
109-
);
110-
111109
self.draw_popups(f)?;
112110

113111
Ok(())
@@ -135,6 +133,10 @@ impl App {
135133
self.toggle_tabs(true)?;
136134
NeedsUpdate::COMMANDS
137135
}
136+
keys::CMD_BAR_TOGGLE => {
137+
self.cmdbar.toggle_more();
138+
NeedsUpdate::empty()
139+
}
138140

139141
_ => NeedsUpdate::empty(),
140142
};
@@ -267,8 +269,7 @@ impl App {
267269

268270
fn update_commands(&mut self) {
269271
self.help.set_cmds(self.commands(true));
270-
self.current_commands = self.commands(false);
271-
self.current_commands.sort_by_key(|e| e.order);
272+
self.cmdbar.set_cmds(self.commands(false));
272273
}
273274

274275
fn process_queue(&mut self) -> Result<NeedsUpdate> {
@@ -417,36 +418,4 @@ impl App {
417418
r,
418419
);
419420
}
420-
421-
fn draw_commands<B: Backend>(
422-
f: &mut Frame<B>,
423-
r: Rect,
424-
cmds: &[CommandInfo],
425-
theme: Theme,
426-
) {
427-
let splitter = Text::Styled(
428-
Cow::from(strings::CMD_SPLITTER),
429-
Style::default(),
430-
);
431-
432-
let texts = cmds
433-
.iter()
434-
.filter_map(|c| {
435-
if c.show_in_quickbar() {
436-
Some(Text::Styled(
437-
Cow::from(c.text.name),
438-
theme.toolbar(c.enabled),
439-
))
440-
} else {
441-
None
442-
}
443-
})
444-
.collect::<Vec<_>>();
445-
446-
f.render_widget(
447-
Paragraph::new(texts.iter().intersperse(&splitter))
448-
.alignment(Alignment::Left),
449-
r,
450-
);
451-
}
452421
}

src/cmdbar.rs

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
use crate::{components::CommandInfo, strings, ui::style::Theme};
2+
use std::borrow::Cow;
3+
use tui::{
4+
backend::Backend,
5+
layout::{Alignment, Rect},
6+
widgets::{Paragraph, Text},
7+
Frame,
8+
};
9+
use unicode_width::UnicodeWidthStr;
10+
11+
enum DrawListEntry {
12+
LineBreak,
13+
Splitter,
14+
Command(Command),
15+
}
16+
17+
struct Command {
18+
txt: String,
19+
enabled: bool,
20+
line: usize,
21+
}
22+
23+
/// helper to be used while drawing
24+
pub struct CommandBar {
25+
draw_list: Vec<DrawListEntry>,
26+
cmd_infos: Vec<CommandInfo>,
27+
theme: Theme,
28+
lines: u16,
29+
width: u16,
30+
expandable: bool,
31+
expanded: bool,
32+
}
33+
34+
const MORE_WIDTH: u16 = 11;
35+
36+
impl CommandBar {
37+
pub const fn new(theme: &Theme) -> Self {
38+
Self {
39+
draw_list: Vec::new(),
40+
cmd_infos: Vec::new(),
41+
theme: *theme,
42+
lines: 0,
43+
width: 0,
44+
expandable: false,
45+
expanded: false,
46+
}
47+
}
48+
49+
pub fn refresh_width(&mut self, width: u16) {
50+
if width != self.width {
51+
self.refresh_list(width);
52+
self.width = width;
53+
}
54+
}
55+
56+
fn is_multiline(&self, width: u16) -> bool {
57+
let mut line_width = 0_usize;
58+
for c in &self.cmd_infos {
59+
let entry_w = UnicodeWidthStr::width(c.text.name);
60+
61+
if line_width + entry_w > width as usize {
62+
return true;
63+
}
64+
65+
line_width += entry_w + 1;
66+
}
67+
68+
false
69+
}
70+
71+
fn refresh_list(&mut self, width: u16) {
72+
self.draw_list.clear();
73+
74+
let width = if self.is_multiline(width) {
75+
width.saturating_sub(MORE_WIDTH)
76+
} else {
77+
width
78+
};
79+
80+
let mut line_width = 0_usize;
81+
let mut lines = 1_u16;
82+
83+
for c in &self.cmd_infos {
84+
let entry_w = UnicodeWidthStr::width(c.text.name);
85+
86+
if line_width + entry_w > width as usize {
87+
self.draw_list.push(DrawListEntry::LineBreak);
88+
line_width = 0;
89+
lines += 1;
90+
} else if line_width > 0 {
91+
self.draw_list.push(DrawListEntry::Splitter);
92+
}
93+
94+
line_width += entry_w + 1;
95+
96+
self.draw_list.push(DrawListEntry::Command(Command {
97+
txt: c.text.name.to_string(),
98+
enabled: c.enabled,
99+
line: lines.saturating_sub(1) as usize,
100+
}));
101+
}
102+
103+
self.expandable = lines > 1;
104+
105+
self.lines = lines;
106+
}
107+
108+
pub fn set_cmds(&mut self, cmds: Vec<CommandInfo>) {
109+
self.cmd_infos = cmds
110+
.into_iter()
111+
.filter(CommandInfo::show_in_quickbar)
112+
.collect::<Vec<_>>();
113+
self.cmd_infos.sort_by_key(|e| e.order);
114+
self.refresh_list(self.width);
115+
}
116+
117+
pub fn height(&self) -> u16 {
118+
if self.expandable && self.expanded {
119+
self.lines
120+
} else {
121+
1_u16
122+
}
123+
}
124+
125+
pub fn toggle_more(&mut self) {
126+
if self.expandable {
127+
self.expanded = !self.expanded;
128+
}
129+
}
130+
131+
pub fn draw<B: Backend>(&self, f: &mut Frame<B>, r: Rect) {
132+
let splitter = Text::Raw(Cow::from(strings::CMD_SPLITTER));
133+
134+
let texts = self
135+
.draw_list
136+
.iter()
137+
.map(|c| match c {
138+
DrawListEntry::Command(c) => Text::Styled(
139+
Cow::from(c.txt.as_str()),
140+
self.theme.commandbar(c.enabled, c.line),
141+
),
142+
DrawListEntry::LineBreak => {
143+
Text::Raw(Cow::from("\n"))
144+
}
145+
DrawListEntry::Splitter => splitter.clone(),
146+
})
147+
.collect::<Vec<_>>();
148+
149+
f.render_widget(
150+
Paragraph::new(texts.iter()).alignment(Alignment::Left),
151+
r,
152+
);
153+
154+
if self.expandable {
155+
let r = Rect::new(
156+
r.width.saturating_sub(MORE_WIDTH),
157+
r.y + r.height.saturating_sub(1),
158+
MORE_WIDTH,
159+
1,
160+
);
161+
162+
f.render_widget(
163+
Paragraph::new(
164+
vec![Text::Raw(Cow::from(if self.expanded {
165+
"less [.]"
166+
} else {
167+
"more [.]"
168+
}))]
169+
.iter(),
170+
)
171+
.alignment(Alignment::Right),
172+
r,
173+
);
174+
}
175+
}
176+
}

src/keys.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,4 @@ pub const STASHING_TOGGLE_INDEX: KeyEvent =
5050
pub const STASH_APPLY: KeyEvent = no_mod(KeyCode::Enter);
5151
pub const STASH_DROP: KeyEvent =
5252
with_mod(KeyCode::Char('D'), KeyModifiers::SHIFT);
53+
pub const CMD_BAR_TOGGLE: KeyEvent = no_mod(KeyCode::Char('.'));

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#![allow(clippy::module_name_repetitions)]
1111

1212
mod app;
13+
mod cmdbar;
1314
mod components;
1415
mod keys;
1516
mod poll;

src/strings.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ pub mod commands {
6363
);
6464
///
6565
pub static DIFF_HOME_END: CommandText = CommandText::new(
66-
"Jump up/down [home,end,shift+up,shift+down]",
66+
"Jump up/down [home,end,\u{11014}up,\u{2191}down]",
6767
"scroll to top or bottom of diff",
6868
CMD_GROUP_DIFF,
6969
);
@@ -155,7 +155,7 @@ pub mod commands {
155155
);
156156
///
157157
pub static QUIT: CommandText = CommandText::new(
158-
"Quit [ctrl+c]",
158+
"Quit [^c]",
159159
"quit gitui application",
160160
CMD_GROUP_GENERAL,
161161
);

0 commit comments

Comments
 (0)