Skip to content

Commit c803cea

Browse files
committed
Add popup for tags
This closes #483.
1 parent 908defd commit c803cea

File tree

8 files changed

+314
-3
lines changed

8 files changed

+314
-3
lines changed

src/app.rs

+12
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::{
99
InspectCommitComponent, MsgComponent, PullComponent,
1010
PushComponent, PushTagsComponent, RenameBranchComponent,
1111
ResetComponent, StashMsgComponent, TagCommitComponent,
12+
TagListComponent,
1213
},
1314
input::{Input, InputEvent, InputState},
1415
keys::{KeyConfig, SharedKeyConfig},
@@ -52,6 +53,7 @@ pub struct App {
5253
create_branch_popup: CreateBranchComponent,
5354
rename_branch_popup: RenameBranchComponent,
5455
select_branch_popup: BranchListComponent,
56+
tags_popup: TagListComponent,
5557
cmdbar: RefCell<CommandBar>,
5658
tab: usize,
5759
revlog: Revlog,
@@ -154,6 +156,10 @@ impl App {
154156
theme.clone(),
155157
key_config.clone(),
156158
),
159+
tags_popup: TagListComponent::new(
160+
theme.clone(),
161+
key_config.clone(),
162+
),
157163
do_quit: false,
158164
cmdbar: RefCell::new(CommandBar::new(
159165
theme.clone(),
@@ -386,6 +392,7 @@ impl App {
386392
create_branch_popup,
387393
rename_branch_popup,
388394
select_branch_popup,
395+
tags_popup,
389396
help,
390397
revlog,
391398
status_tab,
@@ -539,6 +546,9 @@ impl App {
539546
InternalEvent::SelectBranch => {
540547
self.select_branch_popup.open()?;
541548
}
549+
InternalEvent::Tags => {
550+
self.tags_popup.open()?;
551+
}
542552
InternalEvent::TabSwitch => self.set_tab(0)?,
543553
InternalEvent::InspectCommit(id, tags) => {
544554
self.inspect_commit_popup.open(id, tags)?;
@@ -678,6 +688,7 @@ impl App {
678688
|| self.push_tags_popup.is_visible()
679689
|| self.pull_popup.is_visible()
680690
|| self.select_branch_popup.is_visible()
691+
|| self.tags_popup.is_visible()
681692
|| self.rename_branch_popup.is_visible()
682693
}
683694

@@ -704,6 +715,7 @@ impl App {
704715
self.external_editor_popup.draw(f, size)?;
705716
self.tag_commit_popup.draw(f, size)?;
706717
self.select_branch_popup.draw(f, size)?;
718+
self.tags_popup.draw(f, size)?;
707719
self.create_branch_popup.draw(f, size)?;
708720
self.rename_branch_popup.draw(f, size)?;
709721
self.push_popup.draw(f, size)?;

src/components/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ mod rename_branch;
2020
mod reset;
2121
mod stashmsg;
2222
mod tag_commit;
23+
mod taglist;
2324
mod textinput;
2425
mod utils;
2526

@@ -44,6 +45,7 @@ pub use rename_branch::RenameBranchComponent;
4445
pub use reset::ResetComponent;
4546
pub use stashmsg::StashMsgComponent;
4647
pub use tag_commit::TagCommitComponent;
48+
pub use taglist::TagListComponent;
4749
pub use textinput::{InputType, TextInputComponent};
4850
pub use utils::filetree::FileTreeItemKind;
4951

src/components/taglist.rs

+264
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
use super::{
2+
visibility_blocking, CommandBlocking, CommandInfo, Component,
3+
DrawableComponent, EventState,
4+
};
5+
use crate::{
6+
components::ScrollType,
7+
keys::SharedKeyConfig,
8+
strings,
9+
ui::{self, Size},
10+
};
11+
use anyhow::Result;
12+
use asyncgit::{sync::get_tags, CWD};
13+
use crossterm::event::Event;
14+
use tui::{
15+
backend::Backend,
16+
layout::{Constraint, Margin, Rect},
17+
text::Span,
18+
widgets::{
19+
Block, BorderType, Borders, Cell, Clear, Row, Table,
20+
TableState,
21+
},
22+
Frame,
23+
};
24+
use ui::style::SharedTheme;
25+
26+
///
27+
pub struct TagListComponent {
28+
theme: SharedTheme,
29+
tags: Option<Vec<String>>,
30+
visible: bool,
31+
table_state: std::cell::Cell<TableState>,
32+
current_height: std::cell::Cell<usize>,
33+
key_config: SharedKeyConfig,
34+
}
35+
36+
impl DrawableComponent for TagListComponent {
37+
fn draw<B: Backend>(
38+
&self,
39+
f: &mut Frame<B>,
40+
rect: Rect,
41+
) -> Result<()> {
42+
if self.visible {
43+
const PERCENT_SIZE: Size = Size::new(80, 50);
44+
const MIN_SIZE: Size = Size::new(60, 20);
45+
46+
let area = ui::centered_rect(
47+
PERCENT_SIZE.width,
48+
PERCENT_SIZE.height,
49+
f.size(),
50+
);
51+
let area =
52+
ui::rect_inside(MIN_SIZE, f.size().into(), area);
53+
let area = area.intersection(rect);
54+
55+
let constraints = [
56+
// tag name
57+
Constraint::Percentage(100),
58+
];
59+
60+
let rows = self.get_rows();
61+
let number_of_rows = rows.len();
62+
63+
let table = Table::new(rows)
64+
.widths(&constraints)
65+
.column_spacing(1)
66+
.highlight_style(self.theme.text(true, true))
67+
.block(
68+
Block::default()
69+
.borders(Borders::ALL)
70+
.title(Span::styled(
71+
strings::title_tags(),
72+
self.theme.title(true),
73+
))
74+
.border_style(self.theme.block(true))
75+
.border_type(BorderType::Thick),
76+
);
77+
78+
let mut table_state = self.table_state.take();
79+
80+
f.render_widget(Clear, area);
81+
f.render_stateful_widget(table, area, &mut table_state);
82+
83+
let area = area.inner(&Margin {
84+
vertical: 1,
85+
horizontal: 0,
86+
});
87+
88+
ui::draw_scrollbar(
89+
f,
90+
area,
91+
&self.theme,
92+
number_of_rows,
93+
table_state.selected().unwrap_or(0),
94+
);
95+
96+
self.table_state.set(table_state);
97+
self.current_height.set(area.height.into());
98+
}
99+
100+
Ok(())
101+
}
102+
}
103+
104+
impl Component for TagListComponent {
105+
fn commands(
106+
&self,
107+
out: &mut Vec<CommandInfo>,
108+
force_all: bool,
109+
) -> CommandBlocking {
110+
if self.visible || force_all {
111+
out.clear();
112+
113+
out.push(CommandInfo::new(
114+
strings::commands::scroll(&self.key_config),
115+
true,
116+
true,
117+
));
118+
119+
out.push(CommandInfo::new(
120+
strings::commands::close_popup(&self.key_config),
121+
true,
122+
true,
123+
));
124+
}
125+
visibility_blocking(self)
126+
}
127+
128+
fn event(&mut self, event: Event) -> Result<EventState> {
129+
if self.visible {
130+
if let Event::Key(key) = event {
131+
if key == self.key_config.exit_popup {
132+
self.hide()
133+
} else if key == self.key_config.move_up {
134+
self.move_selection(ScrollType::Up);
135+
} else if key == self.key_config.move_down {
136+
self.move_selection(ScrollType::Down);
137+
} else if key == self.key_config.shift_up
138+
|| key == self.key_config.home
139+
{
140+
self.move_selection(ScrollType::Home);
141+
} else if key == self.key_config.shift_down
142+
|| key == self.key_config.end
143+
{
144+
self.move_selection(ScrollType::End);
145+
} else if key == self.key_config.page_down {
146+
self.move_selection(ScrollType::PageDown);
147+
} else if key == self.key_config.page_up {
148+
self.move_selection(ScrollType::PageUp);
149+
}
150+
}
151+
152+
Ok(EventState::Consumed)
153+
} else {
154+
Ok(EventState::NotConsumed)
155+
}
156+
}
157+
158+
fn is_visible(&self) -> bool {
159+
self.visible
160+
}
161+
162+
fn hide(&mut self) {
163+
self.visible = false
164+
}
165+
166+
fn show(&mut self) -> Result<()> {
167+
self.visible = true;
168+
169+
Ok(())
170+
}
171+
}
172+
173+
impl TagListComponent {
174+
pub fn new(
175+
theme: SharedTheme,
176+
key_config: SharedKeyConfig,
177+
) -> Self {
178+
Self {
179+
theme,
180+
tags: None,
181+
visible: false,
182+
table_state: std::cell::Cell::new(TableState::default()),
183+
current_height: std::cell::Cell::new(0),
184+
key_config,
185+
}
186+
}
187+
188+
///
189+
pub fn open(&mut self) -> Result<()> {
190+
self.table_state.get_mut().select(Some(0));
191+
self.show()?;
192+
193+
self.update_tags()?;
194+
195+
Ok(())
196+
}
197+
198+
/// fetch list of tags
199+
pub fn update_tags(&mut self) -> Result<()> {
200+
let tags_grouped_by_commit_id = get_tags(CWD)?;
201+
202+
let mut tags: Vec<String> = tags_grouped_by_commit_id
203+
.values()
204+
.cloned()
205+
.flatten()
206+
.collect();
207+
208+
tags.sort();
209+
210+
self.tags = Some(tags);
211+
212+
Ok(())
213+
}
214+
215+
///
216+
fn move_selection(&mut self, scroll_type: ScrollType) -> bool {
217+
let mut table_state = self.table_state.take();
218+
219+
let old_selection = table_state.selected().unwrap_or(0);
220+
let max_selection =
221+
self.tags.as_ref().map_or(0, |tags| tags.len() - 1);
222+
223+
let new_selection = match scroll_type {
224+
ScrollType::Up => old_selection.saturating_sub(1),
225+
ScrollType::Down => {
226+
old_selection.saturating_add(1).min(max_selection)
227+
}
228+
ScrollType::Home => 0,
229+
ScrollType::End => max_selection,
230+
ScrollType::PageUp => old_selection.saturating_sub(
231+
self.current_height.get().saturating_sub(2),
232+
),
233+
ScrollType::PageDown => old_selection
234+
.saturating_add(
235+
self.current_height.get().saturating_sub(2),
236+
)
237+
.min(max_selection),
238+
};
239+
240+
let needs_update = new_selection != old_selection;
241+
242+
table_state.select(Some(new_selection));
243+
self.table_state.set(table_state);
244+
245+
needs_update
246+
}
247+
248+
///
249+
fn get_rows(&self) -> Vec<Row> {
250+
if let Some(ref tags) = self.tags {
251+
tags.iter().map(|tag| self.get_row(tag)).collect()
252+
} else {
253+
vec![]
254+
}
255+
}
256+
257+
///
258+
fn get_row(&self, tag: &str) -> Row {
259+
let cells = vec![Cell::from(String::from(tag))
260+
.style(self.theme.text(true, false))];
261+
262+
Row::new(cells)
263+
}
264+
}

src/keys.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ pub struct KeyConfig {
6969
pub select_branch: KeyEvent,
7070
pub delete_branch: KeyEvent,
7171
pub merge_branch: KeyEvent,
72+
pub tags: KeyEvent,
7273
pub push: KeyEvent,
7374
pub force_push: KeyEvent,
7475
pub pull: KeyEvent,
@@ -126,8 +127,9 @@ impl Default for KeyConfig {
126127
create_branch: KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::empty()},
127128
rename_branch: KeyEvent { code: KeyCode::Char('r'), modifiers: KeyModifiers::empty()},
128129
select_branch: KeyEvent { code: KeyCode::Char('b'), modifiers: KeyModifiers::empty()},
129-
delete_branch: KeyEvent{code: KeyCode::Char('D'), modifiers: KeyModifiers::SHIFT},
130-
merge_branch: KeyEvent{code: KeyCode::Char('m'), modifiers: KeyModifiers::empty()},
130+
delete_branch: KeyEvent { code: KeyCode::Char('D'), modifiers: KeyModifiers::SHIFT},
131+
merge_branch: KeyEvent { code: KeyCode::Char('m'), modifiers: KeyModifiers::empty()},
132+
tags: KeyEvent { code: KeyCode::Char('T'), modifiers: KeyModifiers::SHIFT},
131133
push: KeyEvent { code: KeyCode::Char('p'), modifiers: KeyModifiers::empty()},
132134
force_push: KeyEvent { code: KeyCode::Char('P'), modifiers: KeyModifiers::SHIFT},
133135
pull: KeyEvent { code: KeyCode::Char('f'), modifiers: KeyModifiers::empty()},

src/queue.rs

+2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ pub enum InternalEvent {
5959
///
6060
TagCommit(CommitId),
6161
///
62+
Tags,
63+
///
6264
BlameFile(String),
6365
///
6466
CreateBranch,

0 commit comments

Comments
 (0)