Skip to content

Commit c6bf42a

Browse files
committed
Add popup for tags
This closes #483.
1 parent 5ba657c commit c6bf42a

File tree

8 files changed

+312
-2
lines changed

8 files changed

+312
-2
lines changed

Diff for: src/app.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::{
99
InspectCommitComponent, MsgComponent, PullComponent,
1010
PushComponent, PushTagsComponent, RenameBranchComponent,
1111
ResetComponent, RevisionFilesComponent, StashMsgComponent,
12-
TagCommitComponent,
12+
TagCommitComponent, TagListComponent,
1313
},
1414
input::{Input, InputEvent, InputState},
1515
keys::{KeyConfig, SharedKeyConfig},
@@ -54,6 +54,7 @@ pub struct App {
5454
create_branch_popup: CreateBranchComponent,
5555
rename_branch_popup: RenameBranchComponent,
5656
select_branch_popup: BranchListComponent,
57+
tags_popup: TagListComponent,
5758
cmdbar: RefCell<CommandBar>,
5859
tab: usize,
5960
revlog: Revlog,
@@ -162,6 +163,10 @@ impl App {
162163
theme.clone(),
163164
key_config.clone(),
164165
),
166+
tags_popup: TagListComponent::new(
167+
theme.clone(),
168+
key_config.clone(),
169+
),
165170
do_quit: false,
166171
cmdbar: RefCell::new(CommandBar::new(
167172
theme.clone(),
@@ -395,6 +400,7 @@ impl App {
395400
rename_branch_popup,
396401
select_branch_popup,
397402
revision_files_popup,
403+
tags_popup,
398404
help,
399405
revlog,
400406
status_tab,
@@ -548,6 +554,9 @@ impl App {
548554
InternalEvent::SelectBranch => {
549555
self.select_branch_popup.open()?;
550556
}
557+
InternalEvent::Tags => {
558+
self.tags_popup.open()?;
559+
}
551560
InternalEvent::TabSwitch => self.set_tab(0)?,
552561
InternalEvent::InspectCommit(id, tags) => {
553562
self.inspect_commit_popup.open(id, tags)?;
@@ -694,6 +703,7 @@ impl App {
694703
|| self.push_tags_popup.is_visible()
695704
|| self.pull_popup.is_visible()
696705
|| self.select_branch_popup.is_visible()
706+
|| self.tags_popup.is_visible()
697707
|| self.rename_branch_popup.is_visible()
698708
|| self.revision_files_popup.is_visible()
699709
}
@@ -721,6 +731,7 @@ impl App {
721731
self.external_editor_popup.draw(f, size)?;
722732
self.tag_commit_popup.draw(f, size)?;
723733
self.select_branch_popup.draw(f, size)?;
734+
self.tags_popup.draw(f, size)?;
724735
self.create_branch_popup.draw(f, size)?;
725736
self.rename_branch_popup.draw(f, size)?;
726737
self.revision_files_popup.draw(f, size)?;

Diff for: src/components/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ mod reset;
2121
mod revision_files;
2222
mod stashmsg;
2323
mod tag_commit;
24+
mod taglist;
2425
mod textinput;
2526
mod utils;
2627

@@ -46,6 +47,7 @@ pub use reset::ResetComponent;
4647
pub use revision_files::RevisionFilesComponent;
4748
pub use stashmsg::StashMsgComponent;
4849
pub use tag_commit::TagCommitComponent;
50+
pub use taglist::TagListComponent;
4951
pub use textinput::{InputType, TextInputComponent};
5052
pub use utils::filetree::FileTreeItemKind;
5153

Diff for: 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+
}

Diff for: src/keys.rs

+2
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ pub struct KeyConfig {
7070
pub select_branch: KeyEvent,
7171
pub delete_branch: KeyEvent,
7272
pub merge_branch: KeyEvent,
73+
pub tags: KeyEvent,
7374
pub push: KeyEvent,
7475
pub open_file_tree: KeyEvent,
7576
pub force_push: KeyEvent,
@@ -130,6 +131,7 @@ impl Default for KeyConfig {
130131
select_branch: KeyEvent { code: KeyCode::Char('b'), modifiers: KeyModifiers::empty()},
131132
delete_branch: KeyEvent { code: KeyCode::Char('D'), modifiers: KeyModifiers::SHIFT},
132133
merge_branch: KeyEvent { code: KeyCode::Char('m'), modifiers: KeyModifiers::empty()},
134+
tags: KeyEvent { code: KeyCode::Char('T'), modifiers: KeyModifiers::SHIFT},
133135
push: KeyEvent { code: KeyCode::Char('p'), modifiers: KeyModifiers::empty()},
134136
force_push: KeyEvent { code: KeyCode::Char('P'), modifiers: KeyModifiers::SHIFT},
135137
pull: KeyEvent { code: KeyCode::Char('f'), modifiers: KeyModifiers::empty()},

Diff for: src/queue.rs

+2
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ pub enum InternalEvent {
6161
///
6262
TagCommit(CommitId),
6363
///
64+
Tags,
65+
///
6466
BlameFile(String),
6567
///
6668
CreateBranch,

Diff for: src/strings.rs

+16
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ pub static PUSH_TAGS_STATES_DONE: &str = "done";
2323
pub fn title_branches() -> String {
2424
"Branches".to_string()
2525
}
26+
pub fn title_tags() -> String {
27+
"Tags".to_string()
28+
}
2629
pub fn title_status(_key_config: &SharedKeyConfig) -> String {
2730
"Unstaged Changes".to_string()
2831
}
@@ -999,6 +1002,19 @@ pub mod commands {
9991002
)
10001003
}
10011004

1005+
pub fn open_tags_popup(
1006+
key_config: &SharedKeyConfig,
1007+
) -> CommandText {
1008+
CommandText::new(
1009+
format!(
1010+
"Tags [{}]",
1011+
key_config.get_hint(key_config.tags),
1012+
),
1013+
"open tags popup",
1014+
CMD_GROUP_GENERAL,
1015+
)
1016+
}
1017+
10021018
pub fn status_push(key_config: &SharedKeyConfig) -> CommandText {
10031019
CommandText::new(
10041020
format!(

0 commit comments

Comments
 (0)