mirror of
https://github.com/gitui-org/gitui
synced 2026-05-23 17:08:21 +00:00
Support stash-apply,stash-drop
This commit is contained in:
parent
01a354e171
commit
63d3bf5661
21 changed files with 673 additions and 249 deletions
|
|
@ -3,6 +3,28 @@ use crate::error::Result;
|
|||
use git2::{Commit, Error, Oid};
|
||||
use scopetime::scope_time;
|
||||
|
||||
/// identifies a single commit
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct CommitId(Oid);
|
||||
|
||||
impl CommitId {
|
||||
/// create new CommitId
|
||||
pub fn new(id: Oid) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
|
||||
///
|
||||
pub(crate) fn get_oid(self) -> Oid {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for CommitId {
|
||||
fn to_string(&self) -> String {
|
||||
self.0.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
#[derive(Debug)]
|
||||
pub struct CommitInfo {
|
||||
|
|
@ -13,7 +35,7 @@ pub struct CommitInfo {
|
|||
///
|
||||
pub author: String,
|
||||
///
|
||||
pub hash: String,
|
||||
pub id: CommitId,
|
||||
}
|
||||
|
||||
///
|
||||
|
|
@ -44,7 +66,7 @@ pub fn get_commits_info(
|
|||
message,
|
||||
author,
|
||||
time: c.time().seconds(),
|
||||
hash: c.id().to_string(),
|
||||
id: CommitId(c.id()),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
|
|
|||
|
|
@ -11,14 +11,14 @@ pub mod status;
|
|||
mod tags;
|
||||
pub mod utils;
|
||||
|
||||
pub use commits_info::{get_commits_info, CommitInfo};
|
||||
pub use commits_info::{get_commits_info, CommitId, CommitInfo};
|
||||
pub use hooks::{hooks_commit_msg, hooks_post_commit, HookResult};
|
||||
pub use hunks::{stage_hunk, unstage_hunk};
|
||||
pub use logwalker::LogWalker;
|
||||
pub use reset::{
|
||||
reset_stage, reset_workdir_file, reset_workdir_folder,
|
||||
};
|
||||
pub use stash::stash_save;
|
||||
pub use stash::{get_stashes, stash_apply, stash_drop, stash_save};
|
||||
pub use tags::{get_tags, Tags};
|
||||
pub use utils::{
|
||||
commit, stage_add_all, stage_add_file, stage_addremoved,
|
||||
|
|
|
|||
|
|
@ -1,39 +1,71 @@
|
|||
use super::utils::repo;
|
||||
use crate::error::Result;
|
||||
use git2::{Oid, StashFlags};
|
||||
use super::{utils::repo, CommitId};
|
||||
use crate::error::{Error, Result};
|
||||
use git2::{Oid, Repository, StashFlags};
|
||||
use scopetime::scope_time;
|
||||
|
||||
///
|
||||
#[allow(dead_code)]
|
||||
pub struct StashItem {
|
||||
pub msg: String,
|
||||
index: usize,
|
||||
id: Oid,
|
||||
}
|
||||
|
||||
///
|
||||
#[allow(dead_code)]
|
||||
pub struct StashItems(Vec<StashItem>);
|
||||
|
||||
///
|
||||
#[allow(dead_code)]
|
||||
pub fn get_stashes(repo_path: &str) -> Result<StashItems> {
|
||||
pub fn get_stashes(repo_path: &str) -> Result<Vec<Oid>> {
|
||||
scope_time!("get_stashes");
|
||||
|
||||
let mut repo = repo(repo_path)?;
|
||||
|
||||
let mut list = Vec::new();
|
||||
|
||||
repo.stash_foreach(|index, msg, id| {
|
||||
list.push(StashItem {
|
||||
msg: msg.to_string(),
|
||||
index,
|
||||
id: *id,
|
||||
});
|
||||
repo.stash_foreach(|_index, _msg, id| {
|
||||
list.push(*id);
|
||||
true
|
||||
})?;
|
||||
|
||||
Ok(StashItems(list))
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
///
|
||||
pub fn stash_drop(repo_path: &str, stash_id: CommitId) -> Result<()> {
|
||||
scope_time!("stash_drop");
|
||||
|
||||
let mut repo = repo(repo_path)?;
|
||||
|
||||
let index = get_stash_index(&mut repo, stash_id.get_oid())?;
|
||||
|
||||
repo.stash_drop(index)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
pub fn stash_apply(
|
||||
repo_path: &str,
|
||||
stash_id: CommitId,
|
||||
) -> Result<()> {
|
||||
scope_time!("stash_apply");
|
||||
|
||||
let mut repo = repo(repo_path)?;
|
||||
|
||||
let index = get_stash_index(&mut repo, stash_id.get_oid())?;
|
||||
|
||||
repo.stash_apply(index, None)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_stash_index(
|
||||
repo: &mut Repository,
|
||||
stash_id: Oid,
|
||||
) -> Result<usize> {
|
||||
let mut idx = None;
|
||||
|
||||
repo.stash_foreach(|index, _msg, id| {
|
||||
if *id == stash_id {
|
||||
idx = Some(index);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})?;
|
||||
|
||||
idx.ok_or_else(|| {
|
||||
Error::Generic("stash commit not found".to_string())
|
||||
})
|
||||
}
|
||||
|
||||
///
|
||||
|
|
@ -66,7 +98,10 @@ pub fn stash_save(
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::sync::tests::{get_statuses, repo_init};
|
||||
use crate::sync::{
|
||||
get_commits_info,
|
||||
tests::{get_statuses, repo_init},
|
||||
};
|
||||
use std::{fs::File, io::Write};
|
||||
|
||||
#[test]
|
||||
|
|
@ -80,10 +115,7 @@ mod tests {
|
|||
false
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_stashes(repo_path).unwrap().0.is_empty(),
|
||||
true
|
||||
);
|
||||
assert_eq!(get_stashes(repo_path).unwrap().is_empty(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -117,9 +149,28 @@ mod tests {
|
|||
|
||||
let res = get_stashes(repo_path)?;
|
||||
|
||||
assert_eq!(res.0.len(), 1);
|
||||
assert_eq!(res.0[0].msg, "On master: foo");
|
||||
assert_eq!(res.0[0].index, 0);
|
||||
assert_eq!(res.len(), 1);
|
||||
|
||||
let infos =
|
||||
get_commits_info(repo_path, &[res[0]], 100).unwrap();
|
||||
|
||||
assert_eq!(infos[0].message, "On master: foo");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stash_nothing_untracked() -> Result<()> {
|
||||
let (_td, repo) = repo_init().unwrap();
|
||||
let root = repo.path().parent().unwrap();
|
||||
let repo_path = root.as_os_str().to_str().unwrap();
|
||||
|
||||
File::create(&root.join("foo.txt"))?
|
||||
.write_all(b"test\nfoo")?;
|
||||
|
||||
assert!(
|
||||
stash_save(repo_path, Some("foo"), false, false).is_err()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
use super::utils::repo;
|
||||
use super::{utils::repo, CommitId};
|
||||
use crate::error::Result;
|
||||
use scopetime::scope_time;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// hashmap of tag target commit hash to tag names
|
||||
pub type Tags = HashMap<String, Vec<String>>;
|
||||
pub type Tags = HashMap<CommitId, Vec<String>>;
|
||||
|
||||
/// returns `Tags` type filled with all tags found in repo
|
||||
pub fn get_tags(repo_path: &str) -> Result<Tags> {
|
||||
scope_time!("get_tags");
|
||||
|
||||
let mut res = Tags::new();
|
||||
let mut adder = |key: String, value: String| {
|
||||
let mut adder = |key, value: String| {
|
||||
if let Some(key) = res.get_mut(&key) {
|
||||
key.push(value)
|
||||
} else {
|
||||
|
|
@ -26,9 +26,8 @@ pub fn get_tags(repo_path: &str) -> Result<Tags> {
|
|||
let obj = repo.revparse_single(name)?;
|
||||
|
||||
if let Some(tag) = obj.as_tag() {
|
||||
let target_hash = tag.target_id().to_string();
|
||||
let tag_name = String::from(name);
|
||||
adder(target_hash, tag_name);
|
||||
adder(CommitId::new(tag.target_id()), tag_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -70,7 +69,7 @@ mod tests {
|
|||
repo.tag("b", &target, &sig, "", false).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
get_tags(repo_path).unwrap()[&head_id.to_string()],
|
||||
get_tags(repo_path).unwrap()[&CommitId::new(head_id)],
|
||||
vec!["a", "b"]
|
||||
);
|
||||
}
|
||||
|
|
|
|||
69
src/app.rs
69
src/app.rs
|
|
@ -6,9 +6,9 @@ use crate::{
|
|||
ResetComponent, StashMsgComponent,
|
||||
},
|
||||
keys,
|
||||
queue::{InternalEvent, NeedsUpdate, Queue},
|
||||
queue::{Action, InternalEvent, NeedsUpdate, Queue},
|
||||
strings,
|
||||
tabs::{Revlog, Stashing, Status},
|
||||
tabs::{Revlog, StashList, Stashing, Status},
|
||||
ui::style::Theme,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
|
|
@ -39,6 +39,7 @@ pub struct App {
|
|||
revlog: Revlog,
|
||||
status_tab: Status,
|
||||
stashing_tab: Stashing,
|
||||
stashlist_tab: StashList,
|
||||
queue: Queue,
|
||||
theme: Theme,
|
||||
}
|
||||
|
|
@ -66,6 +67,7 @@ impl App {
|
|||
revlog: Revlog::new(&sender, &theme),
|
||||
status_tab: Status::new(&sender, &queue, &theme),
|
||||
stashing_tab: Stashing::new(&sender, &queue, &theme),
|
||||
stashlist_tab: StashList::new(&queue, &theme),
|
||||
queue,
|
||||
theme,
|
||||
}
|
||||
|
|
@ -95,6 +97,7 @@ impl App {
|
|||
0 => self.status_tab.draw(f, chunks_main[1])?,
|
||||
1 => self.revlog.draw(f, chunks_main[1])?,
|
||||
2 => self.stashing_tab.draw(f, chunks_main[1])?,
|
||||
3 => self.stashlist_tab.draw(f, chunks_main[1])?,
|
||||
_ => return Err(anyhow!("unknown tab")),
|
||||
};
|
||||
|
||||
|
|
@ -158,6 +161,7 @@ impl App {
|
|||
self.status_tab.update()?;
|
||||
self.revlog.update()?;
|
||||
self.stashing_tab.update()?;
|
||||
self.stashlist_tab.update()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -207,7 +211,8 @@ impl App {
|
|||
help,
|
||||
revlog,
|
||||
status_tab,
|
||||
stashing_tab
|
||||
stashing_tab,
|
||||
stashlist_tab
|
||||
]
|
||||
);
|
||||
|
||||
|
|
@ -226,6 +231,7 @@ impl App {
|
|||
&mut self.status_tab,
|
||||
&mut self.revlog,
|
||||
&mut self.stashing_tab,
|
||||
&mut self.stashlist_tab,
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -234,16 +240,21 @@ impl App {
|
|||
{
|
||||
let tabs = self.get_tabs();
|
||||
new_tab %= tabs.len();
|
||||
}
|
||||
self.set_tab(new_tab)
|
||||
}
|
||||
|
||||
for (i, t) in tabs.into_iter().enumerate() {
|
||||
if new_tab == i {
|
||||
t.show()?;
|
||||
} else {
|
||||
t.hide();
|
||||
}
|
||||
fn set_tab(&mut self, tab: usize) -> Result<()> {
|
||||
let tabs = self.get_tabs();
|
||||
for (i, t) in tabs.into_iter().enumerate() {
|
||||
if tab == i {
|
||||
t.show()?;
|
||||
} else {
|
||||
t.hide();
|
||||
}
|
||||
}
|
||||
self.tab = new_tab;
|
||||
|
||||
self.tab = tab;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -275,27 +286,20 @@ impl App {
|
|||
) -> Result<NeedsUpdate> {
|
||||
let mut flags = NeedsUpdate::empty();
|
||||
match ev {
|
||||
InternalEvent::ResetItem(reset_item) => {
|
||||
if reset_item.is_folder {
|
||||
if sync::reset_workdir_folder(
|
||||
CWD,
|
||||
reset_item.path.as_str(),
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
InternalEvent::ConfirmedAction(action) => match action {
|
||||
Action::Reset(r) => {
|
||||
if Status::reset(&r) {
|
||||
flags.insert(NeedsUpdate::ALL);
|
||||
}
|
||||
} else if sync::reset_workdir_file(
|
||||
CWD,
|
||||
reset_item.path.as_str(),
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
flags.insert(NeedsUpdate::ALL);
|
||||
}
|
||||
}
|
||||
InternalEvent::ConfirmResetItem(reset_item) => {
|
||||
self.reset.open_for_path(reset_item)?;
|
||||
Action::StashDrop(s) => {
|
||||
if StashList::drop(s) {
|
||||
flags.insert(NeedsUpdate::ALL);
|
||||
}
|
||||
}
|
||||
},
|
||||
InternalEvent::ConfirmAction(action) => {
|
||||
self.reset.open(action)?;
|
||||
flags.insert(NeedsUpdate::COMMANDS);
|
||||
}
|
||||
InternalEvent::AddHunk(hash) => {
|
||||
|
|
@ -315,13 +319,16 @@ impl App {
|
|||
}
|
||||
InternalEvent::ShowErrorMsg(msg) => {
|
||||
self.msg.show_msg(msg.as_str())?;
|
||||
flags.insert(NeedsUpdate::ALL);
|
||||
flags
|
||||
.insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS);
|
||||
}
|
||||
InternalEvent::Update(u) => flags.insert(u),
|
||||
InternalEvent::OpenCommit => self.commit.show()?,
|
||||
InternalEvent::PopupStashing(_opts) => {
|
||||
InternalEvent::PopupStashing(opts) => {
|
||||
self.stashmsg_popup.options(opts);
|
||||
self.stashmsg_popup.show()?
|
||||
}
|
||||
InternalEvent::TabSwitch => self.set_tab(0)?,
|
||||
};
|
||||
|
||||
Ok(flags)
|
||||
|
|
@ -382,6 +389,7 @@ impl App {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
//TODO: make this dynamic
|
||||
fn draw_tabs<B: Backend>(&self, f: &mut Frame<B>, r: Rect) {
|
||||
f.render_widget(
|
||||
Tabs::default()
|
||||
|
|
@ -390,6 +398,7 @@ impl App {
|
|||
strings::TAB_STATUS,
|
||||
strings::TAB_LOG,
|
||||
strings::TAB_STASHING,
|
||||
"Stashes",
|
||||
])
|
||||
.style(Style::default())
|
||||
.highlight_style(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use super::{
|
|||
use crate::{
|
||||
components::{CommandInfo, Component},
|
||||
keys,
|
||||
queue::{InternalEvent, NeedsUpdate, Queue, ResetItem},
|
||||
queue::{Action, InternalEvent, NeedsUpdate, Queue, ResetItem},
|
||||
strings,
|
||||
ui::style::Theme,
|
||||
};
|
||||
|
|
@ -112,10 +112,12 @@ impl ChangesComponent {
|
|||
let is_folder =
|
||||
matches!(tree_item.kind, FileTreeItemKind::Path(_));
|
||||
self.queue.borrow_mut().push_back(
|
||||
InternalEvent::ConfirmResetItem(ResetItem {
|
||||
path: tree_item.info.full_path,
|
||||
is_folder,
|
||||
}),
|
||||
InternalEvent::ConfirmAction(Action::Reset(
|
||||
ResetItem {
|
||||
path: tree_item.info.full_path,
|
||||
is_folder,
|
||||
},
|
||||
)),
|
||||
);
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ pub struct CommandInfo {
|
|||
pub enabled: bool,
|
||||
/// will show up in the quick bar
|
||||
pub quick_bar: bool,
|
||||
|
||||
/// available in current app state
|
||||
pub available: bool,
|
||||
/// used to order commands in quickbar
|
||||
|
|
|
|||
|
|
@ -1,18 +1,16 @@
|
|||
mod utils;
|
||||
|
||||
use super::utils::logitems::{ItemBatch, LogEntry};
|
||||
use crate::{
|
||||
components::{
|
||||
CommandBlocking, CommandInfo, Component, DrawableComponent,
|
||||
ScrollType,
|
||||
},
|
||||
keys,
|
||||
strings::{self, commands},
|
||||
strings::commands,
|
||||
ui::calc_scroll_top,
|
||||
ui::style::Theme,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use asyncgit::{sync, AsyncLog, AsyncNotification, FetchStatus, CWD};
|
||||
use crossbeam_channel::Sender;
|
||||
use asyncgit::sync;
|
||||
use crossterm::event::Event;
|
||||
use std::{borrow::Cow, cmp, convert::TryFrom, time::Instant};
|
||||
use sync::Tags;
|
||||
|
|
@ -22,18 +20,15 @@ use tui::{
|
|||
widgets::{Block, Borders, Paragraph, Text},
|
||||
Frame,
|
||||
};
|
||||
use utils::{ItemBatch, LogEntry};
|
||||
|
||||
const SLICE_SIZE: usize = 1200;
|
||||
const ELEMENTS_PER_LINE: usize = 10;
|
||||
|
||||
///
|
||||
pub struct Revlog {
|
||||
pub struct CommitList {
|
||||
title: String,
|
||||
selection: usize,
|
||||
count_total: usize,
|
||||
items: ItemBatch,
|
||||
git_log: AsyncLog,
|
||||
visible: bool,
|
||||
scroll_state: (Instant, f32),
|
||||
tags: Tags,
|
||||
current_size: (u16, u16),
|
||||
|
|
@ -41,76 +36,63 @@ pub struct Revlog {
|
|||
theme: Theme,
|
||||
}
|
||||
|
||||
impl Revlog {
|
||||
impl CommitList {
|
||||
///
|
||||
pub fn new(
|
||||
sender: &Sender<AsyncNotification>,
|
||||
theme: &Theme,
|
||||
) -> Self {
|
||||
pub fn new(title: &str, theme: &Theme) -> Self {
|
||||
Self {
|
||||
items: ItemBatch::default(),
|
||||
git_log: AsyncLog::new(sender.clone()),
|
||||
selection: 0,
|
||||
count_total: 0,
|
||||
visible: false,
|
||||
scroll_state: (Instant::now(), 0_f32),
|
||||
tags: Tags::new(),
|
||||
current_size: (0, 0),
|
||||
scroll_top: 0,
|
||||
theme: *theme,
|
||||
title: String::from(title),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub fn any_work_pending(&self) -> bool {
|
||||
self.git_log.is_pending()
|
||||
pub fn items(&mut self) -> &mut ItemBatch {
|
||||
&mut self.items
|
||||
}
|
||||
|
||||
fn selection_max(&self) -> usize {
|
||||
///
|
||||
pub fn selection(&self) -> usize {
|
||||
self.selection
|
||||
}
|
||||
|
||||
///
|
||||
pub fn current_size(&self) -> (u16, u16) {
|
||||
self.current_size
|
||||
}
|
||||
|
||||
///
|
||||
pub fn set_count_total(&mut self, total: usize) {
|
||||
self.count_total = total;
|
||||
}
|
||||
|
||||
///
|
||||
pub fn selection_max(&self) -> usize {
|
||||
self.count_total.saturating_sub(1)
|
||||
}
|
||||
|
||||
///
|
||||
pub fn update(&mut self) -> Result<()> {
|
||||
if self.visible {
|
||||
let log_changed =
|
||||
self.git_log.fetch()? == FetchStatus::Started;
|
||||
|
||||
self.count_total = self.git_log.count()?;
|
||||
|
||||
if self
|
||||
.items
|
||||
.needs_data(self.selection, self.selection_max())
|
||||
|| log_changed
|
||||
{
|
||||
self.fetch_commits()?;
|
||||
}
|
||||
|
||||
if self.tags.is_empty() {
|
||||
self.tags = sync::get_tags(CWD)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
pub fn tags(&self) -> &Tags {
|
||||
&self.tags
|
||||
}
|
||||
|
||||
fn fetch_commits(&mut self) -> Result<()> {
|
||||
let want_min = self.selection.saturating_sub(SLICE_SIZE / 2);
|
||||
|
||||
let commits = sync::get_commits_info(
|
||||
CWD,
|
||||
&self.git_log.get_slice(want_min, SLICE_SIZE)?,
|
||||
self.current_size.0.into(),
|
||||
);
|
||||
|
||||
if let Ok(commits) = commits {
|
||||
self.items.set_items(want_min, commits);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
///
|
||||
pub fn set_tags(&mut self, tags: Tags) {
|
||||
self.tags = tags;
|
||||
}
|
||||
|
||||
fn move_selection(&mut self, scroll: ScrollType) -> Result<()> {
|
||||
///
|
||||
pub fn selected_entry(&self) -> Option<&LogEntry> {
|
||||
self.items.iter().nth(self.selection)
|
||||
}
|
||||
|
||||
fn move_selection(&mut self, scroll: ScrollType) -> Result<bool> {
|
||||
self.update_scroll_speed();
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
|
|
@ -120,7 +102,7 @@ impl Revlog {
|
|||
let page_offset =
|
||||
usize::from(self.current_size.1).saturating_sub(1);
|
||||
|
||||
self.selection = match scroll {
|
||||
let new_selection = match scroll {
|
||||
ScrollType::Up => {
|
||||
self.selection.saturating_sub(speed_int)
|
||||
}
|
||||
|
|
@ -137,12 +119,14 @@ impl Revlog {
|
|||
ScrollType::End => self.selection_max(),
|
||||
};
|
||||
|
||||
self.selection =
|
||||
cmp::min(self.selection, self.selection_max());
|
||||
let new_selection =
|
||||
cmp::min(new_selection, self.selection_max());
|
||||
|
||||
self.update()?;
|
||||
let needs_update = new_selection != self.selection;
|
||||
|
||||
Ok(())
|
||||
self.selection = new_selection;
|
||||
|
||||
Ok(needs_update)
|
||||
}
|
||||
|
||||
fn update_scroll_speed(&mut self) {
|
||||
|
|
@ -184,7 +168,7 @@ impl Revlog {
|
|||
|
||||
// commit hash
|
||||
txt.push(Text::Styled(
|
||||
Cow::from(&e.hash[0..7]),
|
||||
Cow::from(e.hash_short.as_str()),
|
||||
theme.commit_hash(selected),
|
||||
));
|
||||
|
||||
|
|
@ -238,7 +222,7 @@ impl Revlog {
|
|||
.take(height)
|
||||
.enumerate()
|
||||
{
|
||||
let tag = if let Some(tags) = self.tags.get(&e.hash) {
|
||||
let tag = if let Some(tags) = self.tags.get(&e.id) {
|
||||
Some(tags.join(" "))
|
||||
} else {
|
||||
None
|
||||
|
|
@ -261,7 +245,7 @@ impl Revlog {
|
|||
}
|
||||
}
|
||||
|
||||
impl DrawableComponent for Revlog {
|
||||
impl DrawableComponent for CommitList {
|
||||
fn draw<B: Backend>(
|
||||
&mut self,
|
||||
f: &mut Frame<B>,
|
||||
|
|
@ -283,7 +267,7 @@ impl DrawableComponent for Revlog {
|
|||
|
||||
let title = format!(
|
||||
"{} {}/{}",
|
||||
strings::LOG_TITLE,
|
||||
self.title,
|
||||
self.count_total.saturating_sub(self.selection),
|
||||
self.count_total,
|
||||
);
|
||||
|
|
@ -305,38 +289,32 @@ impl DrawableComponent for Revlog {
|
|||
}
|
||||
}
|
||||
|
||||
impl Component for Revlog {
|
||||
impl Component for CommitList {
|
||||
fn event(&mut self, ev: Event) -> Result<bool> {
|
||||
if self.visible {
|
||||
if let Event::Key(k) = ev {
|
||||
return match k {
|
||||
keys::MOVE_UP => {
|
||||
self.move_selection(ScrollType::Up)?;
|
||||
Ok(true)
|
||||
}
|
||||
keys::MOVE_DOWN => {
|
||||
self.move_selection(ScrollType::Down)?;
|
||||
Ok(true)
|
||||
}
|
||||
keys::SHIFT_UP | keys::HOME => {
|
||||
self.move_selection(ScrollType::Home)?;
|
||||
Ok(true)
|
||||
}
|
||||
keys::SHIFT_DOWN | keys::END => {
|
||||
self.move_selection(ScrollType::End)?;
|
||||
Ok(true)
|
||||
}
|
||||
keys::PAGE_UP => {
|
||||
self.move_selection(ScrollType::PageUp)?;
|
||||
Ok(true)
|
||||
}
|
||||
keys::PAGE_DOWN => {
|
||||
self.move_selection(ScrollType::PageDown)?;
|
||||
Ok(true)
|
||||
}
|
||||
_ => Ok(false),
|
||||
};
|
||||
}
|
||||
if let Event::Key(k) = ev {
|
||||
let selection_changed = match k {
|
||||
keys::MOVE_UP => {
|
||||
self.move_selection(ScrollType::Up)?
|
||||
}
|
||||
keys::MOVE_DOWN => {
|
||||
self.move_selection(ScrollType::Down)?
|
||||
}
|
||||
keys::SHIFT_UP | keys::HOME => {
|
||||
self.move_selection(ScrollType::Home)?
|
||||
}
|
||||
keys::SHIFT_DOWN | keys::END => {
|
||||
self.move_selection(ScrollType::End)?
|
||||
}
|
||||
keys::PAGE_UP => {
|
||||
self.move_selection(ScrollType::PageUp)?
|
||||
}
|
||||
keys::PAGE_DOWN => {
|
||||
self.move_selection(ScrollType::PageDown)?
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
return Ok(selection_changed);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
|
|
@ -345,35 +323,13 @@ impl Component for Revlog {
|
|||
fn commands(
|
||||
&self,
|
||||
out: &mut Vec<CommandInfo>,
|
||||
force_all: bool,
|
||||
_force_all: bool,
|
||||
) -> CommandBlocking {
|
||||
out.push(CommandInfo::new(
|
||||
commands::SCROLL,
|
||||
self.visible,
|
||||
self.visible || force_all,
|
||||
self.selected_entry().is_some(),
|
||||
true,
|
||||
));
|
||||
|
||||
if self.visible {
|
||||
CommandBlocking::Blocking
|
||||
} else {
|
||||
CommandBlocking::PassingOn
|
||||
}
|
||||
}
|
||||
|
||||
fn is_visible(&self) -> bool {
|
||||
self.visible
|
||||
}
|
||||
|
||||
fn hide(&mut self) {
|
||||
self.visible = false;
|
||||
self.git_log.set_background();
|
||||
}
|
||||
|
||||
fn show(&mut self) -> Result<()> {
|
||||
self.visible = true;
|
||||
self.items.clear();
|
||||
self.update()?;
|
||||
|
||||
Ok(())
|
||||
CommandBlocking::PassingOn
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
mod changes;
|
||||
mod command;
|
||||
mod commit;
|
||||
mod commitlist;
|
||||
mod diff;
|
||||
mod filetree;
|
||||
mod help;
|
||||
|
|
@ -13,6 +14,7 @@ use anyhow::Result;
|
|||
pub use changes::ChangesComponent;
|
||||
pub use command::{CommandInfo, CommandText};
|
||||
pub use commit::CommitComponent;
|
||||
pub use commitlist::CommitList;
|
||||
use crossterm::event::Event;
|
||||
pub use diff::DiffComponent;
|
||||
pub use filetree::FileTreeComponent;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use super::{
|
|||
};
|
||||
use crate::{
|
||||
components::dialog_paragraph,
|
||||
queue::{InternalEvent, Queue, ResetItem},
|
||||
queue::{Action, InternalEvent, Queue},
|
||||
strings, ui,
|
||||
ui::style::Theme,
|
||||
};
|
||||
|
|
@ -21,7 +21,7 @@ use tui::{
|
|||
|
||||
///
|
||||
pub struct ResetComponent {
|
||||
target: Option<ResetItem>,
|
||||
target: Option<Action>,
|
||||
visible: bool,
|
||||
queue: Queue,
|
||||
theme: Theme,
|
||||
|
|
@ -34,16 +34,17 @@ impl DrawableComponent for ResetComponent {
|
|||
_rect: Rect,
|
||||
) -> Result<()> {
|
||||
if self.visible {
|
||||
let mut txt = Vec::new();
|
||||
txt.push(Text::Styled(
|
||||
Cow::from(strings::RESET_MSG),
|
||||
let (title, msg) = self.get_text();
|
||||
|
||||
let txt = vec![Text::Styled(
|
||||
Cow::from(msg),
|
||||
self.theme.text_danger(),
|
||||
));
|
||||
)];
|
||||
|
||||
let area = ui::centered_rect(30, 20, f.size());
|
||||
f.render_widget(Clear, area);
|
||||
f.render_widget(
|
||||
dialog_paragraph(strings::RESET_TITLE, txt.iter()),
|
||||
dialog_paragraph(title, txt.iter()),
|
||||
area,
|
||||
);
|
||||
}
|
||||
|
|
@ -120,20 +121,37 @@ impl ResetComponent {
|
|||
}
|
||||
}
|
||||
///
|
||||
pub fn open_for_path(&mut self, item: ResetItem) -> Result<()> {
|
||||
self.target = Some(item);
|
||||
pub fn open(&mut self, a: Action) -> Result<()> {
|
||||
self.target = Some(a);
|
||||
self.show()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
///
|
||||
pub fn confirm(&mut self) {
|
||||
if let Some(target) = self.target.take() {
|
||||
if let Some(a) = self.target.take() {
|
||||
self.queue
|
||||
.borrow_mut()
|
||||
.push_back(InternalEvent::ResetItem(target));
|
||||
.push_back(InternalEvent::ConfirmedAction(a));
|
||||
}
|
||||
|
||||
self.hide();
|
||||
}
|
||||
|
||||
fn get_text(&self) -> (&str, &str) {
|
||||
if let Some(ref a) = self.target {
|
||||
return match a {
|
||||
Action::Reset(_) => (
|
||||
strings::CONFIRM_TITLE_RESET,
|
||||
strings::CONFIRM_MSG_RESET,
|
||||
),
|
||||
Action::StashDrop(_) => (
|
||||
strings::CONFIRM_TITLE_STASHDROP,
|
||||
strings::CONFIRM_MSG_STASHDROP,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
("", "")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ impl Component for StashMsgComponent {
|
|||
|
||||
if let Event::Key(e) = ev {
|
||||
if let KeyCode::Enter = e.code {
|
||||
if sync::stash_save(
|
||||
match sync::stash_save(
|
||||
CWD,
|
||||
if self.input.get_text().is_empty() {
|
||||
None
|
||||
|
|
@ -65,15 +65,31 @@ impl Component for StashMsgComponent {
|
|||
},
|
||||
self.options.stash_untracked,
|
||||
self.options.keep_index,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
self.input.clear();
|
||||
self.hide();
|
||||
) {
|
||||
Ok(()) => {
|
||||
self.input.clear();
|
||||
self.hide();
|
||||
|
||||
self.queue.borrow_mut().push_back(
|
||||
InternalEvent::Update(NeedsUpdate::ALL),
|
||||
);
|
||||
self.queue.borrow_mut().push_back(
|
||||
InternalEvent::Update(
|
||||
NeedsUpdate::ALL,
|
||||
),
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
self.hide();
|
||||
log::error!(
|
||||
"e: {} (options: {:?})",
|
||||
e,
|
||||
self.options
|
||||
);
|
||||
self.queue.borrow_mut().push_back(
|
||||
InternalEvent::ShowErrorMsg(format!(
|
||||
"stash error:\n{}\noptions:\n{:?}",
|
||||
e, self.options
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -112,4 +128,9 @@ impl StashMsgComponent {
|
|||
),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub fn options(&mut self, options: StashingOptions) {
|
||||
self.options = options;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
use asyncgit::sync::CommitInfo;
|
||||
use asyncgit::sync::{CommitId, CommitInfo};
|
||||
use chrono::prelude::*;
|
||||
use std::slice::Iter;
|
||||
|
||||
static SLICE_OFFSET_RELOAD_THRESHOLD: usize = 100;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct LogEntry {
|
||||
pub struct LogEntry {
|
||||
pub time: String,
|
||||
pub author: String,
|
||||
pub msg: String,
|
||||
pub hash: String,
|
||||
pub hash_short: String,
|
||||
pub id: CommitId,
|
||||
}
|
||||
|
||||
impl From<CommitInfo> for LogEntry {
|
||||
|
|
@ -19,18 +19,22 @@ impl From<CommitInfo> for LogEntry {
|
|||
NaiveDateTime::from_timestamp(c.time, 0),
|
||||
Utc,
|
||||
));
|
||||
|
||||
let hash = c.id.to_string().chars().take(7).collect();
|
||||
|
||||
Self {
|
||||
author: c.author,
|
||||
msg: c.message,
|
||||
time: time.format("%Y-%m-%d %H:%M:%S").to_string(),
|
||||
hash: c.hash,
|
||||
hash_short: hash,
|
||||
id: c.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
#[derive(Default)]
|
||||
pub(super) struct ItemBatch {
|
||||
pub struct ItemBatch {
|
||||
index_offset: usize,
|
||||
items: Vec<LogEntry>,
|
||||
}
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
pub mod filetree;
|
||||
pub mod logitems;
|
||||
pub mod statustree;
|
||||
|
|
|
|||
|
|
@ -46,3 +46,6 @@ pub const STASHING_TOGGLE_UNTRACKED: KeyEvent =
|
|||
no_mod(KeyCode::Char('u'));
|
||||
pub const STASHING_TOGGLE_INDEX: KeyEvent =
|
||||
no_mod(KeyCode::Char('i'));
|
||||
pub const STASH_APPLY: KeyEvent = no_mod(KeyCode::Enter);
|
||||
pub const STASH_DROP: KeyEvent =
|
||||
with_mod(KeyCode::Char('D'), KeyModifiers::SHIFT);
|
||||
|
|
|
|||
13
src/queue.rs
13
src/queue.rs
|
|
@ -1,4 +1,5 @@
|
|||
use crate::tabs::StashingOptions;
|
||||
use asyncgit::sync::CommitId;
|
||||
use bitflags::bitflags;
|
||||
use std::{cell::RefCell, collections::VecDeque, rc::Rc};
|
||||
|
||||
|
|
@ -22,12 +23,18 @@ pub struct ResetItem {
|
|||
pub is_folder: bool,
|
||||
}
|
||||
|
||||
///
|
||||
pub enum Action {
|
||||
Reset(ResetItem),
|
||||
StashDrop(CommitId),
|
||||
}
|
||||
|
||||
///
|
||||
pub enum InternalEvent {
|
||||
///
|
||||
ConfirmResetItem(ResetItem),
|
||||
ConfirmAction(Action),
|
||||
///
|
||||
ResetItem(ResetItem),
|
||||
ConfirmedAction(Action),
|
||||
///
|
||||
AddHunk(u64),
|
||||
///
|
||||
|
|
@ -38,6 +45,8 @@ pub enum InternalEvent {
|
|||
OpenCommit,
|
||||
///
|
||||
PopupStashing(StashingOptions),
|
||||
///
|
||||
TabSwitch,
|
||||
}
|
||||
|
||||
///
|
||||
|
|
|
|||
|
|
@ -14,10 +14,13 @@ pub static COMMIT_TITLE: &str = "Commit";
|
|||
pub static COMMIT_MSG: &str = "type commit message..";
|
||||
pub static STASH_POPUP_TITLE: &str = "Stash";
|
||||
pub static STASH_POPUP_MSG: &str = "type name (optional)";
|
||||
pub static RESET_TITLE: &str = "Reset";
|
||||
pub static RESET_MSG: &str = "confirm file reset?";
|
||||
pub static CONFIRM_TITLE_RESET: &str = "Reset";
|
||||
pub static CONFIRM_TITLE_STASHDROP: &str = "Drop";
|
||||
pub static CONFIRM_MSG_RESET: &str = "confirm file reset?";
|
||||
pub static CONFIRM_MSG_STASHDROP: &str = "confirm stash drop?";
|
||||
|
||||
pub static LOG_TITLE: &str = "Commit";
|
||||
pub static STASHLIST_TITLE: &str = "Stashes";
|
||||
|
||||
pub static HELP_TITLE: &str = "Help: all commands";
|
||||
|
||||
|
|
@ -32,6 +35,7 @@ pub mod commands {
|
|||
static CMD_GROUP_CHANGES: &str = "Changes";
|
||||
static CMD_GROUP_COMMIT: &str = "Commit";
|
||||
static CMD_GROUP_STASHING: &str = "Stashing";
|
||||
static CMD_GROUP_STASHES: &str = "Stashes";
|
||||
|
||||
///
|
||||
pub static TOGGLE_TABS: CommandText = CommandText::new(
|
||||
|
|
@ -188,4 +192,16 @@ pub mod commands {
|
|||
"save files to stash",
|
||||
CMD_GROUP_STASHING,
|
||||
);
|
||||
///
|
||||
pub static STASHLIST_APPLY: CommandText = CommandText::new(
|
||||
"Apply [enter]",
|
||||
"apply selected stash",
|
||||
CMD_GROUP_STASHES,
|
||||
);
|
||||
///
|
||||
pub static STASHLIST_DROP: CommandText = CommandText::new(
|
||||
"Drop [D]",
|
||||
"drop selected stash",
|
||||
CMD_GROUP_STASHES,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
mod revlog;
|
||||
mod stashing;
|
||||
mod stashlist;
|
||||
mod status;
|
||||
|
||||
pub use revlog::Revlog;
|
||||
pub use stashing::{Stashing, StashingOptions};
|
||||
pub use stashlist::StashList;
|
||||
pub use status::Status;
|
||||
|
|
|
|||
139
src/tabs/revlog.rs
Normal file
139
src/tabs/revlog.rs
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
use crate::{
|
||||
components::{
|
||||
visibility_blocking, CommandBlocking, CommandInfo,
|
||||
CommitList, Component, DrawableComponent,
|
||||
},
|
||||
strings,
|
||||
ui::style::Theme,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use asyncgit::{sync, AsyncLog, AsyncNotification, FetchStatus, CWD};
|
||||
use crossbeam_channel::Sender;
|
||||
use crossterm::event::Event;
|
||||
use tui::{backend::Backend, layout::Rect, Frame};
|
||||
|
||||
const SLICE_SIZE: usize = 1200;
|
||||
|
||||
///
|
||||
pub struct Revlog {
|
||||
list: CommitList,
|
||||
git_log: AsyncLog,
|
||||
visible: bool,
|
||||
}
|
||||
|
||||
impl Revlog {
|
||||
///
|
||||
pub fn new(
|
||||
sender: &Sender<AsyncNotification>,
|
||||
theme: &Theme,
|
||||
) -> Self {
|
||||
Self {
|
||||
list: CommitList::new(strings::LOG_TITLE, theme),
|
||||
git_log: AsyncLog::new(sender.clone()),
|
||||
visible: false,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub fn any_work_pending(&self) -> bool {
|
||||
self.git_log.is_pending()
|
||||
}
|
||||
|
||||
///
|
||||
pub fn update(&mut self) -> Result<()> {
|
||||
if self.visible {
|
||||
let log_changed =
|
||||
self.git_log.fetch()? == FetchStatus::Started;
|
||||
|
||||
self.list.set_count_total(self.git_log.count()?);
|
||||
|
||||
let selection = self.list.selection();
|
||||
let selection_max = self.list.selection_max();
|
||||
if self.list.items().needs_data(selection, selection_max)
|
||||
|| log_changed
|
||||
{
|
||||
self.fetch_commits()?;
|
||||
}
|
||||
|
||||
if self.list.tags().is_empty() {
|
||||
self.list.set_tags(sync::get_tags(CWD)?);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fetch_commits(&mut self) -> Result<()> {
|
||||
let want_min =
|
||||
self.list.selection().saturating_sub(SLICE_SIZE / 2);
|
||||
|
||||
let commits = sync::get_commits_info(
|
||||
CWD,
|
||||
&self.git_log.get_slice(want_min, SLICE_SIZE)?,
|
||||
self.list.current_size().0.into(),
|
||||
);
|
||||
|
||||
if let Ok(commits) = commits {
|
||||
self.list.items().set_items(want_min, commits);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawableComponent for Revlog {
|
||||
fn draw<B: Backend>(
|
||||
&mut self,
|
||||
f: &mut Frame<B>,
|
||||
area: Rect,
|
||||
) -> Result<()> {
|
||||
self.list.draw(f, area)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Revlog {
|
||||
fn event(&mut self, ev: Event) -> Result<bool> {
|
||||
if self.visible {
|
||||
let needs_update = self.list.event(ev)?;
|
||||
|
||||
if needs_update {
|
||||
self.update()?;
|
||||
}
|
||||
|
||||
return Ok(needs_update);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn commands(
|
||||
&self,
|
||||
out: &mut Vec<CommandInfo>,
|
||||
force_all: bool,
|
||||
) -> CommandBlocking {
|
||||
if self.visible || force_all {
|
||||
self.list.commands(out, force_all);
|
||||
}
|
||||
|
||||
visibility_blocking(self)
|
||||
}
|
||||
|
||||
fn is_visible(&self) -> bool {
|
||||
self.visible
|
||||
}
|
||||
|
||||
fn hide(&mut self) {
|
||||
self.visible = false;
|
||||
self.git_log.set_background();
|
||||
}
|
||||
|
||||
fn show(&mut self) -> Result<()> {
|
||||
self.visible = true;
|
||||
self.list.items().clear();
|
||||
self.update()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ use tui::{
|
|||
widgets::{Block, Borders, Paragraph, Text},
|
||||
};
|
||||
|
||||
#[derive(Default, Clone, Copy)]
|
||||
#[derive(Default, Clone, Copy, Debug)]
|
||||
pub struct StashingOptions {
|
||||
pub stash_untracked: bool,
|
||||
pub keep_index: bool,
|
||||
|
|
@ -171,23 +171,29 @@ impl Component for Stashing {
|
|||
out: &mut Vec<CommandInfo>,
|
||||
force_all: bool,
|
||||
) -> CommandBlocking {
|
||||
command_pump(out, force_all, self.components().as_slice());
|
||||
if self.visible || force_all {
|
||||
command_pump(
|
||||
out,
|
||||
force_all,
|
||||
self.components().as_slice(),
|
||||
);
|
||||
|
||||
out.push(CommandInfo::new(
|
||||
commands::STASHING_SAVE,
|
||||
self.visible && !self.index.is_empty(),
|
||||
self.visible || force_all,
|
||||
));
|
||||
out.push(CommandInfo::new(
|
||||
commands::STASHING_TOGGLE_INDEXED,
|
||||
self.visible,
|
||||
self.visible || force_all,
|
||||
));
|
||||
out.push(CommandInfo::new(
|
||||
commands::STASHING_TOGGLE_UNTRACKED,
|
||||
self.visible,
|
||||
self.visible || force_all,
|
||||
));
|
||||
out.push(CommandInfo::new(
|
||||
commands::STASHING_SAVE,
|
||||
self.visible && !self.index.is_empty(),
|
||||
self.visible || force_all,
|
||||
));
|
||||
out.push(CommandInfo::new(
|
||||
commands::STASHING_TOGGLE_INDEXED,
|
||||
self.visible,
|
||||
self.visible || force_all,
|
||||
));
|
||||
out.push(CommandInfo::new(
|
||||
commands::STASHING_TOGGLE_UNTRACKED,
|
||||
self.visible,
|
||||
self.visible || force_all,
|
||||
));
|
||||
}
|
||||
|
||||
visibility_blocking(self)
|
||||
}
|
||||
|
|
|
|||
152
src/tabs/stashlist.rs
Normal file
152
src/tabs/stashlist.rs
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
use crate::{
|
||||
components::{
|
||||
visibility_blocking, CommandBlocking, CommandInfo,
|
||||
CommitList, Component, DrawableComponent,
|
||||
},
|
||||
keys,
|
||||
queue::{Action, InternalEvent, Queue},
|
||||
strings,
|
||||
ui::style::Theme,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use asyncgit::sync;
|
||||
use asyncgit::CWD;
|
||||
use crossterm::event::Event;
|
||||
use strings::commands;
|
||||
use sync::CommitId;
|
||||
|
||||
pub struct StashList {
|
||||
list: CommitList,
|
||||
visible: bool,
|
||||
queue: Queue,
|
||||
}
|
||||
|
||||
impl StashList {
|
||||
///
|
||||
pub fn new(queue: &Queue, theme: &Theme) -> Self {
|
||||
Self {
|
||||
visible: false,
|
||||
list: CommitList::new(strings::STASHLIST_TITLE, theme),
|
||||
queue: queue.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub fn update(&mut self) -> Result<()> {
|
||||
if self.visible {
|
||||
let stashes = sync::get_stashes(CWD)?;
|
||||
let commits =
|
||||
sync::get_commits_info(CWD, stashes.as_slice(), 100)?;
|
||||
|
||||
self.list.set_count_total(commits.len());
|
||||
self.list.items().set_items(0, commits);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_stash(&mut self) {
|
||||
if let Some(e) = self.list.selected_entry() {
|
||||
match sync::stash_apply(CWD, e.id) {
|
||||
Ok(_) => {
|
||||
self.queue
|
||||
.borrow_mut()
|
||||
.push_back(InternalEvent::TabSwitch);
|
||||
}
|
||||
Err(e) => {
|
||||
self.queue.borrow_mut().push_back(
|
||||
InternalEvent::ShowErrorMsg(format!(
|
||||
"stash apply error:\n{}",
|
||||
e,
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn drop_stash(&mut self) {
|
||||
if let Some(e) = self.list.selected_entry() {
|
||||
self.queue.borrow_mut().push_back(
|
||||
InternalEvent::ConfirmAction(Action::StashDrop(e.id)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub fn drop(id: CommitId) -> bool {
|
||||
sync::stash_drop(CWD, id).is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawableComponent for StashList {
|
||||
fn draw<B: tui::backend::Backend>(
|
||||
&mut self,
|
||||
f: &mut tui::Frame<B>,
|
||||
rect: tui::layout::Rect,
|
||||
) -> Result<()> {
|
||||
self.list.draw(f, rect)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for StashList {
|
||||
fn commands(
|
||||
&self,
|
||||
out: &mut Vec<CommandInfo>,
|
||||
force_all: bool,
|
||||
) -> CommandBlocking {
|
||||
if self.visible || force_all {
|
||||
self.list.commands(out, force_all);
|
||||
|
||||
let selection_valid =
|
||||
self.list.selected_entry().is_some();
|
||||
out.push(CommandInfo::new(
|
||||
commands::STASHLIST_APPLY,
|
||||
selection_valid,
|
||||
true,
|
||||
));
|
||||
out.push(CommandInfo::new(
|
||||
commands::STASHLIST_DROP,
|
||||
selection_valid,
|
||||
true,
|
||||
));
|
||||
}
|
||||
|
||||
visibility_blocking(self)
|
||||
}
|
||||
|
||||
fn event(&mut self, ev: crossterm::event::Event) -> Result<bool> {
|
||||
if self.visible {
|
||||
if self.list.event(ev)? {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if let Event::Key(k) = ev {
|
||||
match k {
|
||||
keys::STASH_APPLY => self.apply_stash(),
|
||||
keys::STASH_DROP => self.drop_stash(),
|
||||
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn is_visible(&self) -> bool {
|
||||
self.visible
|
||||
}
|
||||
|
||||
fn hide(&mut self) {
|
||||
self.visible = false;
|
||||
}
|
||||
|
||||
fn show(&mut self) -> Result<()> {
|
||||
self.visible = true;
|
||||
self.update()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -6,14 +6,15 @@ use crate::{
|
|||
FileTreeItemKind,
|
||||
},
|
||||
keys,
|
||||
queue::Queue,
|
||||
queue::{Queue, ResetItem},
|
||||
strings,
|
||||
ui::style::Theme,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use asyncgit::{
|
||||
sync::status::StatusType, AsyncDiff, AsyncNotification,
|
||||
AsyncStatus, DiffParams, StatusParams,
|
||||
sync::{self, status::StatusType},
|
||||
AsyncDiff, AsyncNotification, AsyncStatus, DiffParams,
|
||||
StatusParams, CWD,
|
||||
};
|
||||
use components::{command_pump, visibility_blocking};
|
||||
use crossbeam_channel::Sender;
|
||||
|
|
@ -268,6 +269,16 @@ impl Status {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// called after confirmation
|
||||
pub fn reset(item: &ResetItem) -> bool {
|
||||
if item.is_folder {
|
||||
sync::reset_workdir_folder(CWD, item.path.as_str())
|
||||
.is_ok()
|
||||
} else {
|
||||
sync::reset_workdir_file(CWD, item.path.as_str()).is_ok()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Status {
|
||||
|
|
|
|||
Loading…
Reference in a new issue