mirror of
https://github.com/gitui-org/gitui
synced 2026-05-24 01:18:21 +00:00
first shot at commands and better seperation of logic
This commit is contained in:
parent
a5a45e7e24
commit
783f2ddb1b
6 changed files with 220 additions and 80 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
|
@ -141,6 +141,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossterm 0.15.0",
|
"crossterm 0.15.0",
|
||||||
"git2",
|
"git2",
|
||||||
|
"itertools 0.9.0",
|
||||||
"tui",
|
"tui",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -173,6 +174,15 @@ dependencies = [
|
||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jobserver"
|
name = "jobserver"
|
||||||
version = "0.1.21"
|
version = "0.1.21"
|
||||||
|
|
@ -420,7 +430,7 @@ dependencies = [
|
||||||
"cassowary",
|
"cassowary",
|
||||||
"crossterm 0.14.2",
|
"crossterm 0.14.2",
|
||||||
"either",
|
"either",
|
||||||
"itertools",
|
"itertools 0.8.2",
|
||||||
"log",
|
"log",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,5 @@ edition = "2018"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
git2 = "0.10"
|
git2 = "0.10"
|
||||||
crossterm = "0.15"
|
crossterm = "0.15"
|
||||||
|
itertools = "0.9"
|
||||||
tui = { version = "0.8", default-features = false, features = ['crossterm'] }
|
tui = { version = "0.8", default-features = false, features = ['crossterm'] }
|
||||||
132
src/app.rs
132
src/app.rs
|
|
@ -1,10 +1,12 @@
|
||||||
|
use crate::commit::CommandInfo;
|
||||||
|
use crate::commit::{UICommit, UIElement};
|
||||||
use crate::{
|
use crate::{
|
||||||
clear::Clear,
|
|
||||||
git_status::StatusLists,
|
git_status::StatusLists,
|
||||||
git_utils::{self, Diff, DiffLine, DiffLineType},
|
git_utils::{self, Diff, DiffLine, DiffLineType},
|
||||||
};
|
};
|
||||||
use crossterm::event::{Event, KeyCode, MouseEvent};
|
use crossterm::event::{Event, KeyCode, MouseEvent};
|
||||||
use git2::IndexAddOption;
|
use git2::IndexAddOption;
|
||||||
|
use itertools::Itertools;
|
||||||
use std::{borrow::Cow, cmp, path::Path};
|
use std::{borrow::Cow, cmp, path::Path};
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
|
|
@ -21,8 +23,7 @@ pub struct App {
|
||||||
diff: Diff,
|
diff: Diff,
|
||||||
offset: u16,
|
offset: u16,
|
||||||
do_quit: bool,
|
do_quit: bool,
|
||||||
show_popup: bool,
|
commit: UICommit,
|
||||||
commit_msg: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
|
|
@ -146,54 +147,38 @@ impl App {
|
||||||
.scroll(self.offset)
|
.scroll(self.offset)
|
||||||
.render(f, chunks[1]);
|
.render(f, chunks[1]);
|
||||||
|
|
||||||
// commands
|
let mut cmds = self.commit.commands();
|
||||||
{
|
cmds.extend(self.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]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.show_popup {
|
self.draw_commands(f, chunks_main[2], cmds);
|
||||||
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),
|
|
||||||
)]
|
|
||||||
};
|
|
||||||
|
|
||||||
Clear::new(
|
self.commit.draw(f, f.size());
|
||||||
Paragraph::new(txt.iter())
|
}
|
||||||
.block(Block::default().title("Commit").borders(Borders::ALL))
|
|
||||||
.alignment(Alignment::Left),
|
fn commands(&self) -> Vec<CommandInfo> {
|
||||||
)
|
if !self.commit.is_visible() {
|
||||||
.render(f, git_utils::centered_rect(60, 20, f.size()));
|
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) {
|
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())
|
if ev == Event::Key(KeyCode::Esc.into()) || ev == Event::Key(KeyCode::Char('q').into())
|
||||||
{
|
{
|
||||||
self.do_quit = true;
|
self.do_quit = true;
|
||||||
|
|
@ -222,48 +207,39 @@ impl App {
|
||||||
if ev == Event::Key(KeyCode::Enter.into()) {
|
if ev == Event::Key(KeyCode::Enter.into()) {
|
||||||
self.index_add();
|
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) {
|
pub fn update(&mut self) {
|
||||||
self.fetch_status();
|
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) {
|
fn index_add(&mut self) {
|
||||||
if let Some(i) = self.status_select {
|
if let Some(i) = self.status_select {
|
||||||
let repo = git_utils::repo();
|
let repo = git_utils::repo();
|
||||||
|
|
|
||||||
141
src/commit.rs
Normal file
141
src/commit.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use git2::{DiffFormat, DiffOptions, Repository};
|
use git2::{DiffFormat, DiffOptions, Repository, StatusOptions, StatusShow};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use tui::layout::{Constraint, Direction, Layout, Rect};
|
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]
|
.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()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
mod app;
|
mod app;
|
||||||
mod clear;
|
mod clear;
|
||||||
|
mod commit;
|
||||||
mod git_status;
|
mod git_status;
|
||||||
mod git_utils;
|
mod git_utils;
|
||||||
mod poll;
|
mod poll;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue