Skip to content

Commit

Permalink
Add popup for tags
Browse files Browse the repository at this point in the history
This closes extrawurst#483.
  • Loading branch information
cruessler committed May 18, 2021
1 parent 5ba657c commit c6bf42a
Show file tree
Hide file tree
Showing 8 changed files with 312 additions and 2 deletions.
13 changes: 12 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
InspectCommitComponent, MsgComponent, PullComponent,
PushComponent, PushTagsComponent, RenameBranchComponent,
ResetComponent, RevisionFilesComponent, StashMsgComponent,
TagCommitComponent,
TagCommitComponent, TagListComponent,
},
input::{Input, InputEvent, InputState},
keys::{KeyConfig, SharedKeyConfig},
Expand Down Expand Up @@ -54,6 +54,7 @@ pub struct App {
create_branch_popup: CreateBranchComponent,
rename_branch_popup: RenameBranchComponent,
select_branch_popup: BranchListComponent,
tags_popup: TagListComponent,
cmdbar: RefCell<CommandBar>,
tab: usize,
revlog: Revlog,
Expand Down Expand Up @@ -162,6 +163,10 @@ impl App {
theme.clone(),
key_config.clone(),
),
tags_popup: TagListComponent::new(
theme.clone(),
key_config.clone(),
),
do_quit: false,
cmdbar: RefCell::new(CommandBar::new(
theme.clone(),
Expand Down Expand Up @@ -395,6 +400,7 @@ impl App {
rename_branch_popup,
select_branch_popup,
revision_files_popup,
tags_popup,
help,
revlog,
status_tab,
Expand Down Expand Up @@ -548,6 +554,9 @@ impl App {
InternalEvent::SelectBranch => {
self.select_branch_popup.open()?;
}
InternalEvent::Tags => {
self.tags_popup.open()?;
}
InternalEvent::TabSwitch => self.set_tab(0)?,
InternalEvent::InspectCommit(id, tags) => {
self.inspect_commit_popup.open(id, tags)?;
Expand Down Expand Up @@ -694,6 +703,7 @@ impl App {
|| self.push_tags_popup.is_visible()
|| self.pull_popup.is_visible()
|| self.select_branch_popup.is_visible()
|| self.tags_popup.is_visible()
|| self.rename_branch_popup.is_visible()
|| self.revision_files_popup.is_visible()
}
Expand Down Expand Up @@ -721,6 +731,7 @@ impl App {
self.external_editor_popup.draw(f, size)?;
self.tag_commit_popup.draw(f, size)?;
self.select_branch_popup.draw(f, size)?;
self.tags_popup.draw(f, size)?;
self.create_branch_popup.draw(f, size)?;
self.rename_branch_popup.draw(f, size)?;
self.revision_files_popup.draw(f, size)?;
Expand Down
2 changes: 2 additions & 0 deletions src/components/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ mod reset;
mod revision_files;
mod stashmsg;
mod tag_commit;
mod taglist;
mod textinput;
mod utils;

Expand All @@ -46,6 +47,7 @@ pub use reset::ResetComponent;
pub use revision_files::RevisionFilesComponent;
pub use stashmsg::StashMsgComponent;
pub use tag_commit::TagCommitComponent;
pub use taglist::TagListComponent;
pub use textinput::{InputType, TextInputComponent};
pub use utils::filetree::FileTreeItemKind;

Expand Down
264 changes: 264 additions & 0 deletions src/components/taglist.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
use super::{
visibility_blocking, CommandBlocking, CommandInfo, Component,
DrawableComponent, EventState,
};
use crate::{
components::ScrollType,
keys::SharedKeyConfig,
strings,
ui::{self, Size},
};
use anyhow::Result;
use asyncgit::{sync::get_tags, CWD};
use crossterm::event::Event;
use tui::{
backend::Backend,
layout::{Constraint, Margin, Rect},
text::Span,
widgets::{
Block, BorderType, Borders, Cell, Clear, Row, Table,
TableState,
},
Frame,
};
use ui::style::SharedTheme;

///
pub struct TagListComponent {
theme: SharedTheme,
tags: Option<Vec<String>>,
visible: bool,
table_state: std::cell::Cell<TableState>,
current_height: std::cell::Cell<usize>,
key_config: SharedKeyConfig,
}

impl DrawableComponent for TagListComponent {
fn draw<B: Backend>(
&self,
f: &mut Frame<B>,
rect: Rect,
) -> Result<()> {
if self.visible {
const PERCENT_SIZE: Size = Size::new(80, 50);
const MIN_SIZE: Size = Size::new(60, 20);

let area = ui::centered_rect(
PERCENT_SIZE.width,
PERCENT_SIZE.height,
f.size(),
);
let area =
ui::rect_inside(MIN_SIZE, f.size().into(), area);
let area = area.intersection(rect);

let constraints = [
// tag name
Constraint::Percentage(100),
];

let rows = self.get_rows();
let number_of_rows = rows.len();

let table = Table::new(rows)
.widths(&constraints)
.column_spacing(1)
.highlight_style(self.theme.text(true, true))
.block(
Block::default()
.borders(Borders::ALL)
.title(Span::styled(
strings::title_tags(),
self.theme.title(true),
))
.border_style(self.theme.block(true))
.border_type(BorderType::Thick),
);

let mut table_state = self.table_state.take();

f.render_widget(Clear, area);
f.render_stateful_widget(table, area, &mut table_state);

let area = area.inner(&Margin {
vertical: 1,
horizontal: 0,
});

ui::draw_scrollbar(
f,
area,
&self.theme,
number_of_rows,
table_state.selected().unwrap_or(0),
);

self.table_state.set(table_state);
self.current_height.set(area.height.into());
}

Ok(())
}
}

impl Component for TagListComponent {
fn commands(
&self,
out: &mut Vec<CommandInfo>,
force_all: bool,
) -> CommandBlocking {
if self.visible || force_all {
out.clear();

out.push(CommandInfo::new(
strings::commands::scroll(&self.key_config),
true,
true,
));

out.push(CommandInfo::new(
strings::commands::close_popup(&self.key_config),
true,
true,
));
}
visibility_blocking(self)
}

fn event(&mut self, event: Event) -> Result<EventState> {
if self.visible {
if let Event::Key(key) = event {
if key == self.key_config.exit_popup {
self.hide()
} else if key == self.key_config.move_up {
self.move_selection(ScrollType::Up);
} else if key == self.key_config.move_down {
self.move_selection(ScrollType::Down);
} else if key == self.key_config.shift_up
|| key == self.key_config.home
{
self.move_selection(ScrollType::Home);
} else if key == self.key_config.shift_down
|| key == self.key_config.end
{
self.move_selection(ScrollType::End);
} else if key == self.key_config.page_down {
self.move_selection(ScrollType::PageDown);
} else if key == self.key_config.page_up {
self.move_selection(ScrollType::PageUp);
}
}

Ok(EventState::Consumed)
} else {
Ok(EventState::NotConsumed)
}
}

fn is_visible(&self) -> bool {
self.visible
}

fn hide(&mut self) {
self.visible = false
}

fn show(&mut self) -> Result<()> {
self.visible = true;

Ok(())
}
}

impl TagListComponent {
pub fn new(
theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self {
Self {
theme,
tags: None,
visible: false,
table_state: std::cell::Cell::new(TableState::default()),
current_height: std::cell::Cell::new(0),
key_config,
}
}

///
pub fn open(&mut self) -> Result<()> {
self.table_state.get_mut().select(Some(0));
self.show()?;

self.update_tags()?;

Ok(())
}

/// fetch list of tags
pub fn update_tags(&mut self) -> Result<()> {
let tags_grouped_by_commit_id = get_tags(CWD)?;

let mut tags: Vec<String> = tags_grouped_by_commit_id
.values()
.cloned()
.flatten()
.collect();

tags.sort();

self.tags = Some(tags);

Ok(())
}

///
fn move_selection(&mut self, scroll_type: ScrollType) -> bool {
let mut table_state = self.table_state.take();

let old_selection = table_state.selected().unwrap_or(0);
let max_selection =
self.tags.as_ref().map_or(0, |tags| tags.len() - 1);

let new_selection = match scroll_type {
ScrollType::Up => old_selection.saturating_sub(1),
ScrollType::Down => {
old_selection.saturating_add(1).min(max_selection)
}
ScrollType::Home => 0,
ScrollType::End => max_selection,
ScrollType::PageUp => old_selection.saturating_sub(
self.current_height.get().saturating_sub(2),
),
ScrollType::PageDown => old_selection
.saturating_add(
self.current_height.get().saturating_sub(2),
)
.min(max_selection),
};

let needs_update = new_selection != old_selection;

table_state.select(Some(new_selection));
self.table_state.set(table_state);

needs_update
}

///
fn get_rows(&self) -> Vec<Row> {
if let Some(ref tags) = self.tags {
tags.iter().map(|tag| self.get_row(tag)).collect()
} else {
vec![]
}
}

///
fn get_row(&self, tag: &str) -> Row {
let cells = vec![Cell::from(String::from(tag))
.style(self.theme.text(true, false))];

Row::new(cells)
}
}
2 changes: 2 additions & 0 deletions src/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ pub struct KeyConfig {
pub select_branch: KeyEvent,
pub delete_branch: KeyEvent,
pub merge_branch: KeyEvent,
pub tags: KeyEvent,
pub push: KeyEvent,
pub open_file_tree: KeyEvent,
pub force_push: KeyEvent,
Expand Down Expand Up @@ -130,6 +131,7 @@ impl Default for KeyConfig {
select_branch: KeyEvent { code: KeyCode::Char('b'), modifiers: KeyModifiers::empty()},
delete_branch: KeyEvent { code: KeyCode::Char('D'), modifiers: KeyModifiers::SHIFT},
merge_branch: KeyEvent { code: KeyCode::Char('m'), modifiers: KeyModifiers::empty()},
tags: KeyEvent { code: KeyCode::Char('T'), modifiers: KeyModifiers::SHIFT},
push: KeyEvent { code: KeyCode::Char('p'), modifiers: KeyModifiers::empty()},
force_push: KeyEvent { code: KeyCode::Char('P'), modifiers: KeyModifiers::SHIFT},
pull: KeyEvent { code: KeyCode::Char('f'), modifiers: KeyModifiers::empty()},
Expand Down
2 changes: 2 additions & 0 deletions src/queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ pub enum InternalEvent {
///
TagCommit(CommitId),
///
Tags,
///
BlameFile(String),
///
CreateBranch,
Expand Down
16 changes: 16 additions & 0 deletions src/strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ pub static PUSH_TAGS_STATES_DONE: &str = "done";
pub fn title_branches() -> String {
"Branches".to_string()
}
pub fn title_tags() -> String {
"Tags".to_string()
}
pub fn title_status(_key_config: &SharedKeyConfig) -> String {
"Unstaged Changes".to_string()
}
Expand Down Expand Up @@ -999,6 +1002,19 @@ pub mod commands {
)
}

pub fn open_tags_popup(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!(
"Tags [{}]",
key_config.get_hint(key_config.tags),
),
"open tags popup",
CMD_GROUP_GENERAL,
)
}

pub fn status_push(key_config: &SharedKeyConfig) -> CommandText {
CommandText::new(
format!(
Expand Down
Loading

0 comments on commit c6bf42a

Please sign in to comment.