mirror of
https://github.com/gitui-org/gitui
synced 2026-05-24 09:28:21 +00:00
tui is not maintained anymore and it seems that ratatui is the successor: https://github.com/fdehau/tui-rs/issues/654
272 lines
5.3 KiB
Rust
272 lines
5.3 KiB
Rust
use super::{
|
|
visibility_blocking, CommandBlocking, CommandInfo, Component,
|
|
DrawableComponent, EventState,
|
|
};
|
|
use crate::{
|
|
keys::{key_match, SharedKeyConfig},
|
|
queue::Queue,
|
|
strings, try_or_popup,
|
|
ui::{self, style::SharedTheme},
|
|
};
|
|
use anyhow::Result;
|
|
use asyncgit::{
|
|
cached,
|
|
sync::{CommitId, RepoPath, RepoPathRef, ResetType},
|
|
};
|
|
use crossterm::event::Event;
|
|
use ratatui::{
|
|
backend::Backend,
|
|
layout::{Alignment, Rect},
|
|
text::{Span, Spans},
|
|
widgets::{Block, Borders, Clear, Paragraph},
|
|
Frame,
|
|
};
|
|
|
|
const fn type_to_string(
|
|
kind: ResetType,
|
|
) -> (&'static str, &'static str) {
|
|
const RESET_TYPE_DESC_SOFT: &str =
|
|
" 🟢 Keep all changes. Stage differences";
|
|
const RESET_TYPE_DESC_MIXED: &str =
|
|
" 🟡 Keep all changes. Unstage differences";
|
|
const RESET_TYPE_DESC_HARD: &str =
|
|
" 🔴 Discard all local changes";
|
|
|
|
match kind {
|
|
ResetType::Soft => ("Soft", RESET_TYPE_DESC_SOFT),
|
|
ResetType::Mixed => ("Mixed", RESET_TYPE_DESC_MIXED),
|
|
ResetType::Hard => ("Hard", RESET_TYPE_DESC_HARD),
|
|
}
|
|
}
|
|
|
|
pub struct ResetPopupComponent {
|
|
queue: Queue,
|
|
repo: RepoPath,
|
|
commit: Option<CommitId>,
|
|
kind: ResetType,
|
|
git_branch_name: cached::BranchName,
|
|
visible: bool,
|
|
key_config: SharedKeyConfig,
|
|
theme: SharedTheme,
|
|
}
|
|
|
|
impl ResetPopupComponent {
|
|
///
|
|
pub fn new(
|
|
queue: &Queue,
|
|
repo: &RepoPathRef,
|
|
theme: SharedTheme,
|
|
key_config: SharedKeyConfig,
|
|
) -> Self {
|
|
Self {
|
|
queue: queue.clone(),
|
|
repo: repo.borrow().clone(),
|
|
commit: None,
|
|
kind: ResetType::Soft,
|
|
git_branch_name: cached::BranchName::new(repo.clone()),
|
|
visible: false,
|
|
key_config,
|
|
theme,
|
|
}
|
|
}
|
|
|
|
fn get_text(&self, _width: u16) -> Vec<Spans> {
|
|
let mut txt: Vec<Spans> = Vec::with_capacity(10);
|
|
|
|
txt.push(Spans::from(vec![
|
|
Span::styled(
|
|
String::from("Branch: "),
|
|
self.theme.text(true, false),
|
|
),
|
|
Span::styled(
|
|
self.git_branch_name.last().unwrap_or_default(),
|
|
self.theme.branch(false, true),
|
|
),
|
|
]));
|
|
|
|
txt.push(Spans::from(vec![
|
|
Span::styled(
|
|
String::from("Reset to: "),
|
|
self.theme.text(true, false),
|
|
),
|
|
Span::styled(
|
|
self.commit
|
|
.map(|c| c.to_string())
|
|
.unwrap_or_default(),
|
|
self.theme.commit_hash(false),
|
|
),
|
|
]));
|
|
|
|
let (kind_name, kind_desc) = type_to_string(self.kind);
|
|
|
|
txt.push(Spans::from(vec![
|
|
Span::styled(
|
|
String::from("How: "),
|
|
self.theme.text(true, false),
|
|
),
|
|
Span::styled(kind_name, self.theme.text(true, true)),
|
|
Span::styled(kind_desc, self.theme.text(true, false)),
|
|
]));
|
|
|
|
txt
|
|
}
|
|
|
|
///
|
|
pub fn open(&mut self, id: CommitId) -> Result<()> {
|
|
self.show()?;
|
|
|
|
self.commit = Some(id);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
///
|
|
#[allow(clippy::unnecessary_wraps)]
|
|
pub fn update(&mut self) -> Result<()> {
|
|
self.git_branch_name.lookup().map(Some).unwrap_or(None);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn reset(&mut self) {
|
|
if let Some(id) = self.commit {
|
|
try_or_popup!(
|
|
self,
|
|
"reset:",
|
|
asyncgit::sync::reset_repo(&self.repo, id, self.kind)
|
|
);
|
|
}
|
|
|
|
self.hide();
|
|
}
|
|
|
|
fn change_kind(&mut self, incr: bool) {
|
|
self.kind = if incr {
|
|
match self.kind {
|
|
ResetType::Soft => ResetType::Mixed,
|
|
ResetType::Mixed => ResetType::Hard,
|
|
ResetType::Hard => ResetType::Soft,
|
|
}
|
|
} else {
|
|
match self.kind {
|
|
ResetType::Soft => ResetType::Hard,
|
|
ResetType::Mixed => ResetType::Soft,
|
|
ResetType::Hard => ResetType::Mixed,
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
impl DrawableComponent for ResetPopupComponent {
|
|
fn draw<B: Backend>(
|
|
&self,
|
|
f: &mut Frame<B>,
|
|
area: Rect,
|
|
) -> Result<()> {
|
|
if self.is_visible() {
|
|
const SIZE: (u16, u16) = (55, 5);
|
|
let area =
|
|
ui::centered_rect_absolute(SIZE.0, SIZE.1, area);
|
|
|
|
let width = area.width;
|
|
|
|
f.render_widget(Clear, area);
|
|
f.render_widget(
|
|
Paragraph::new(self.get_text(width))
|
|
.block(
|
|
Block::default()
|
|
.borders(Borders::ALL)
|
|
.title(Span::styled(
|
|
"Reset",
|
|
self.theme.title(true),
|
|
))
|
|
.border_style(self.theme.block(true)),
|
|
)
|
|
.alignment(Alignment::Left),
|
|
area,
|
|
);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Component for ResetPopupComponent {
|
|
fn commands(
|
|
&self,
|
|
out: &mut Vec<CommandInfo>,
|
|
force_all: bool,
|
|
) -> CommandBlocking {
|
|
if self.is_visible() || force_all {
|
|
out.push(
|
|
CommandInfo::new(
|
|
strings::commands::close_popup(&self.key_config),
|
|
true,
|
|
true,
|
|
)
|
|
.order(1),
|
|
);
|
|
|
|
out.push(
|
|
CommandInfo::new(
|
|
strings::commands::reset_commit(&self.key_config),
|
|
true,
|
|
true,
|
|
)
|
|
.order(1),
|
|
);
|
|
|
|
out.push(
|
|
CommandInfo::new(
|
|
strings::commands::reset_type(&self.key_config),
|
|
true,
|
|
true,
|
|
)
|
|
.order(1),
|
|
);
|
|
}
|
|
|
|
visibility_blocking(self)
|
|
}
|
|
|
|
fn event(
|
|
&mut self,
|
|
event: &crossterm::event::Event,
|
|
) -> Result<EventState> {
|
|
if self.is_visible() {
|
|
if let Event::Key(key) = &event {
|
|
if key_match(key, self.key_config.keys.exit_popup) {
|
|
self.hide();
|
|
} else if key_match(
|
|
key,
|
|
self.key_config.keys.move_down,
|
|
) {
|
|
self.change_kind(true);
|
|
} else if key_match(key, self.key_config.keys.move_up)
|
|
{
|
|
self.change_kind(false);
|
|
} else if key_match(key, self.key_config.keys.enter) {
|
|
self.reset();
|
|
}
|
|
}
|
|
|
|
return Ok(EventState::Consumed);
|
|
}
|
|
|
|
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(())
|
|
}
|
|
}
|