first shot at commands and better seperation of logic

This commit is contained in:
Stephan Dilly 2020-03-18 14:58:09 +01:00
parent a5a45e7e24
commit 783f2ddb1b
6 changed files with 220 additions and 80 deletions

12
Cargo.lock generated
View File

@ -141,6 +141,7 @@ version = "0.1.0"
dependencies = [
"crossterm 0.15.0",
"git2",
"itertools 0.9.0",
"tui",
]
@ -173,6 +174,15 @@ dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
dependencies = [
"either",
]
[[package]]
name = "jobserver"
version = "0.1.21"
@ -420,7 +430,7 @@ dependencies = [
"cassowary",
"crossterm 0.14.2",
"either",
"itertools",
"itertools 0.8.2",
"log",
"unicode-segmentation",
"unicode-width",

View File

@ -10,4 +10,5 @@ edition = "2018"
[dependencies]
git2 = "0.10"
crossterm = "0.15"
itertools = "0.9"
tui = { version = "0.8", default-features = false, features = ['crossterm'] }

View File

@ -1,10 +1,12 @@
use crate::commit::CommandInfo;
use crate::commit::{UICommit, UIElement};
use crate::{
clear::Clear,
git_status::StatusLists,
git_utils::{self, Diff, DiffLine, DiffLineType},
};
use crossterm::event::{Event, KeyCode, MouseEvent};
use git2::IndexAddOption;
use itertools::Itertools;
use std::{borrow::Cow, cmp, path::Path};
use tui::{
backend::Backend,
@ -21,8 +23,7 @@ pub struct App {
diff: Diff,
offset: u16,
do_quit: bool,
show_popup: bool,
commit_msg: String,
commit: UICommit,
}
impl App {
@ -146,54 +147,38 @@ impl App {
.scroll(self.offset)
.render(f, chunks[1]);
// commands
{
let splitter = Text::Styled(Cow::from(" "), Style::default().bg(Color::Black));
let t1 = Text::Styled(
Cow::from("Commit [c]"),
Style::default()
.fg(if self.index_empty() {
Color::DarkGray
} else {
Color::White
})
.bg(Color::Blue),
);
let t2 = Text::Styled(
Cow::from("Help [h]"),
Style::default().fg(Color::White).bg(Color::Blue),
);
let t3 = Text::Styled(
Cow::from("Quit [q]"),
Style::default().fg(Color::White).bg(Color::Blue),
);
Paragraph::new(vec![t1, splitter.clone(), t2, splitter.clone(), t3].iter())
.alignment(Alignment::Left)
.render(f, chunks_main[2]);
}
let mut cmds = self.commit.commands();
cmds.extend(self.commands());
if self.show_popup {
let txt = if self.commit_msg.len() > 0 {
[Text::Raw(Cow::from(self.commit_msg.clone()))]
} else {
[Text::Styled(
Cow::from("type commit message.."),
Style::default().fg(Color::DarkGray),
)]
};
self.draw_commands(f, chunks_main[2], cmds);
Clear::new(
Paragraph::new(txt.iter())
.block(Block::default().title("Commit").borders(Borders::ALL))
.alignment(Alignment::Left),
)
.render(f, git_utils::centered_rect(60, 20, f.size()));
self.commit.draw(f, f.size());
}
fn commands(&self) -> Vec<CommandInfo> {
if !self.commit.is_visible() {
vec![
CommandInfo {
name: "Scroll [↑↓]".to_string(),
enabled: true,
},
CommandInfo {
name: "Quit [esc,q]".to_string(),
enabled: true,
},
]
} else {
Vec::new()
}
}
///
pub fn event(&mut self, ev: Event) {
if !self.show_popup {
if self.commit.event(ev) {
return;
}
if !self.commit.is_visible() {
if ev == Event::Key(KeyCode::Esc.into()) || ev == Event::Key(KeyCode::Char('q').into())
{
self.do_quit = true;
@ -222,48 +207,39 @@ impl App {
if ev == Event::Key(KeyCode::Enter.into()) {
self.index_add();
}
if ev == Event::Key(KeyCode::Char('c').into()) {
if !self.index_empty() {
self.show_popup = true;
}
}
} else {
if let Event::Key(e) = ev {
match e.code {
KeyCode::Char(c) => self.commit_msg.push(c),
KeyCode::Enter if self.commit_msg.len() > 0 => self.commit(),
KeyCode::Backspace if self.commit_msg.len() > 0 => {
self.commit_msg.pop().unwrap();
()
}
_ => (),
};
}
if ev == Event::Key(KeyCode::Esc.into()) || ev == Event::Key(KeyCode::Char('q').into())
{
self.show_popup = false;
}
}
}
fn draw_commands<B: Backend>(&self, f: &mut Frame<B>, r: Rect, cmds: Vec<CommandInfo>) {
let splitter = Text::Styled(Cow::from(" "), Style::default().bg(Color::Black));
let style_enabled = Style::default().fg(Color::White).bg(Color::Blue);
let style_disabled = Style::default().fg(Color::DarkGray).bg(Color::Blue);
let texts = cmds
.iter()
.map(|c| {
Text::Styled(
Cow::from(c.name.clone()),
if c.enabled {
style_enabled
} else {
style_disabled
},
)
})
.collect::<Vec<_>>();
Paragraph::new(texts.iter().intersperse(&splitter))
.alignment(Alignment::Left)
.render(f, r);
}
///
pub fn update(&mut self) {
self.fetch_status();
}
fn index_empty(&self) -> bool {
self.status.index_items.len() == 0
}
fn commit(&mut self) {
git_utils::commit(&self.commit_msg);
self.show_popup = false;
self.commit_msg.clear();
}
fn index_add(&mut self) {
if let Some(i) = self.status_select {
let repo = git_utils::repo();

141
src/commit.rs Normal file
View File

@ -0,0 +1,141 @@
use crate::{clear::Clear, git_utils};
use crossterm::event::{Event, KeyCode};
use std::borrow::Cow;
use tui::backend::Backend;
use tui::layout::{Alignment, Rect};
use tui::{
style::{Color, Style},
widgets::{Block, Borders, Paragraph, Text, Widget},
Frame,
};
///
pub struct CommandInfo {
pub name: String,
pub enabled: bool,
}
///
pub trait UIElement {
///
fn draw<B: Backend>(&self, f: &mut Frame<B>, rect: Rect);
///
fn commands(&self) -> Vec<CommandInfo>;
///
fn event(&mut self, ev: Event) -> bool;
///
fn is_visible(&self) -> bool;
///
fn hide(&mut self);
///
fn show(&mut self);
}
#[derive(Default)]
pub struct UICommit {
msg: String,
// focused: bool,
visible: bool,
}
impl UIElement for UICommit {
fn draw<B: Backend>(&self, f: &mut Frame<B>, _rect: Rect) {
if self.visible {
let txt = if self.msg.len() > 0 {
[Text::Raw(Cow::from(self.msg.clone()))]
} else {
[Text::Styled(
Cow::from("type commit message.."),
Style::default().fg(Color::DarkGray),
)]
};
Clear::new(
Paragraph::new(txt.iter())
.block(Block::default().title("Commit").borders(Borders::ALL))
.alignment(Alignment::Left),
)
.render(f, git_utils::centered_rect(60, 20, f.size()));
}
}
fn commands(&self) -> Vec<CommandInfo> {
if !self.visible {
vec![CommandInfo {
name: "Commit [c]".to_string(),
enabled: !git_utils::index_empty(),
}]
} else {
vec![
CommandInfo {
name: "Commit [enter]".to_string(),
enabled: self.can_commit(),
},
CommandInfo {
name: "Close [esc]".to_string(),
enabled: true,
},
]
}
}
fn event(&mut self, ev: Event) -> bool {
if self.visible {
if let Event::Key(e) = ev {
return match e.code {
KeyCode::Esc => {
self.hide();
true
}
KeyCode::Char(c) => {
self.msg.push(c);
true
}
KeyCode::Enter if self.can_commit() => {
self.commit();
true
}
KeyCode::Backspace if self.msg.len() > 0 => {
self.msg.pop().unwrap();
true
}
_ => false,
};
}
} else {
if ev == Event::Key(KeyCode::Char('c').into()) {
if !git_utils::index_empty() {
self.show();
return true;
}
}
}
false
}
fn is_visible(&self) -> bool {
self.visible
}
fn hide(&mut self) {
self.visible = false
}
fn show(&mut self) {
self.visible = true
}
}
impl UICommit {
fn commit(&mut self) {
git_utils::commit(&self.msg);
self.msg.clear();
self.hide();
}
fn can_commit(&self) -> bool {
self.msg.len() > 0
}
}

View File

@ -1,4 +1,4 @@
use git2::{DiffFormat, DiffOptions, Repository};
use git2::{DiffFormat, DiffOptions, Repository, StatusOptions, StatusShow};
use std::path::Path;
use tui::layout::{Constraint, Direction, Layout, Rect};
@ -132,3 +132,14 @@ pub fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
)
.split(popup_layout[1])[1]
}
///
pub fn index_empty() -> bool {
let repo = repo();
let statuses = repo
.statuses(Some(StatusOptions::default().show(StatusShow::Index)))
.unwrap();
statuses.is_empty()
}

View File

@ -1,5 +1,6 @@
mod app;
mod clear;
mod commit;
mod git_status;
mod git_utils;
mod poll;