parent
90779527cf
commit
3833ba86db
8 changed files with 127 additions and 29 deletions
|
@ -82,11 +82,12 @@ standardwebhooks = "1.0"
|
|||
|
||||
# boilerplate
|
||||
bon = "3.0"
|
||||
derive_more = { version = "1.0.0-beta", features = [
|
||||
derive_more = { version = "1.0.0", features = [
|
||||
"as_ref",
|
||||
"constructor",
|
||||
"display",
|
||||
"deref",
|
||||
"deref_mut",
|
||||
"from",
|
||||
] }
|
||||
derive-with = "0.5"
|
||||
|
|
|
@ -23,7 +23,7 @@ pub use model::*;
|
|||
|
||||
use crate::tell;
|
||||
|
||||
use super::Tick;
|
||||
use super::{components::key_focus::KeyFocus, Tick};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Tui {
|
||||
|
@ -92,25 +92,42 @@ impl Tui {
|
|||
if key.kind != KeyEventKind::Press {
|
||||
return Ok(());
|
||||
}
|
||||
match key.code {
|
||||
KeyCode::Char('q') => {
|
||||
actor_tui.kill();
|
||||
}
|
||||
KeyCode::Char('j') | KeyCode::Down => self.scroll_view_state.scroll_down(),
|
||||
KeyCode::Char('k') | KeyCode::Up => self.scroll_view_state.scroll_up(),
|
||||
KeyCode::Char('f') | KeyCode::PageDown => {
|
||||
self.scroll_view_state.scroll_page_down();
|
||||
}
|
||||
KeyCode::Char('b') | KeyCode::PageUp => {
|
||||
self.scroll_view_state.scroll_page_up();
|
||||
}
|
||||
KeyCode::Char('g') | KeyCode::Home => {
|
||||
self.scroll_view_state.scroll_to_top();
|
||||
}
|
||||
KeyCode::Char('G') | KeyCode::End => {
|
||||
self.scroll_view_state.scroll_to_bottom();
|
||||
}
|
||||
_ => (),
|
||||
match self.state.key_focus {
|
||||
KeyFocus::None => match key.code {
|
||||
KeyCode::Char('q') => {
|
||||
actor_tui.kill();
|
||||
}
|
||||
KeyCode::Char('j') | KeyCode::Down => self.scroll_view_state.scroll_down(),
|
||||
KeyCode::Char('k') | KeyCode::Up => self.scroll_view_state.scroll_up(),
|
||||
KeyCode::Char('f') | KeyCode::PageDown => {
|
||||
self.scroll_view_state.scroll_page_down();
|
||||
}
|
||||
KeyCode::Char('b') | KeyCode::PageUp => {
|
||||
self.scroll_view_state.scroll_page_up();
|
||||
}
|
||||
KeyCode::Char('g') | KeyCode::Home => {
|
||||
self.scroll_view_state.scroll_to_top();
|
||||
}
|
||||
KeyCode::Char('G') | KeyCode::End => {
|
||||
self.scroll_view_state.scroll_to_bottom();
|
||||
}
|
||||
KeyCode::Char('/') => {
|
||||
self.state.key_focus = KeyFocus::Filter;
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
KeyFocus::Filter => match key.code {
|
||||
KeyCode::Char(char) => self.state.filter.push(char),
|
||||
KeyCode::Backspace => {
|
||||
self.state.filter.pop();
|
||||
}
|
||||
KeyCode::Tab | KeyCode::Enter => self.state.key_focus = KeyFocus::None,
|
||||
KeyCode::Esc => {
|
||||
self.state.filter = UIRepoFilter::default();
|
||||
self.state.key_focus = KeyFocus::None;
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
//
|
||||
use std::{collections::BTreeMap, fmt::Display, time::Instant};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fmt::{Debug, Display},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use derive_more::derive::{DerefMut, Display};
|
||||
use ratatui::{
|
||||
layout::Alignment,
|
||||
prelude::{Buffer, Rect},
|
||||
|
@ -14,16 +19,21 @@ use tui_scrollview::ScrollViewState;
|
|||
|
||||
use git_next_core::{
|
||||
git::{self, graph::Log, Commit},
|
||||
ForgeAlias, RepoAlias, RepoBranches,
|
||||
newtype, s, ForgeAlias, RepoAlias, RepoBranches,
|
||||
};
|
||||
|
||||
use crate::{server::actor::messages::ValidAppConfig, tui::components::ConfiguredAppWidget};
|
||||
use crate::{
|
||||
server::actor::messages::ValidAppConfig,
|
||||
tui::components::{key_focus::KeyFocus, ConfiguredAppWidget},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct State {
|
||||
last_update: Instant,
|
||||
started: Instant,
|
||||
pub mode: ServerState,
|
||||
pub filter: UIRepoFilter,
|
||||
pub key_focus: KeyFocus,
|
||||
}
|
||||
impl State {
|
||||
pub fn initial() -> Self {
|
||||
|
@ -31,6 +41,8 @@ impl State {
|
|||
last_update: Instant::now(),
|
||||
started: Instant::now(),
|
||||
mode: ServerState::Initial { tick: 0 },
|
||||
filter: UIRepoFilter::default(),
|
||||
key_focus: KeyFocus::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,6 +67,15 @@ fn time() -> String {
|
|||
chrono::Local::now().format("%H:%M").to_string()
|
||||
}
|
||||
|
||||
newtype!(
|
||||
UIRepoFilter,
|
||||
String,
|
||||
Default,
|
||||
DerefMut,
|
||||
Display,
|
||||
"Filter to limit repos shown"
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ServerState {
|
||||
/// UI has started but has no information on the state of the server
|
||||
|
@ -232,6 +253,14 @@ pub enum RepoState {
|
|||
},
|
||||
}
|
||||
impl RepoState {
|
||||
pub fn repo_alias(&self) -> &RepoAlias {
|
||||
match self {
|
||||
RepoState::Identified { repo_alias, .. }
|
||||
| RepoState::Configured { repo_alias, .. }
|
||||
| RepoState::Ready { repo_alias, .. } => repo_alias,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn update_branches(&mut self, branches: RepoBranches) {
|
||||
match self {
|
||||
|
@ -359,13 +388,36 @@ impl StatefulWidget for &State {
|
|||
Self: Sized,
|
||||
{
|
||||
let block = Block::bordered()
|
||||
.title_top(
|
||||
Line::from(
|
||||
if self.filter.is_empty() && self.key_focus == KeyFocus::None {
|
||||
s!("")
|
||||
} else {
|
||||
format!(" Filter: {} ", self.filter)
|
||||
},
|
||||
)
|
||||
.alignment(Alignment::Left)
|
||||
.style(if self.key_focus == KeyFocus::Filter {
|
||||
Color::Red
|
||||
} else {
|
||||
Color::Green
|
||||
}),
|
||||
)
|
||||
.title_top(
|
||||
Line::from(format!(" Git-Next v{} ", clap::crate_version!()).bold())
|
||||
.alignment(Alignment::Center),
|
||||
)
|
||||
.title_bottom(
|
||||
Line::from(if self.key_focus == KeyFocus::Filter {
|
||||
s!(" [esc] clear [tab/enter] finish ")
|
||||
} else {
|
||||
s!("")
|
||||
})
|
||||
.alignment(Alignment::Left),
|
||||
)
|
||||
.title_bottom(
|
||||
Line::from(vec![
|
||||
" [q]uit ".into(),
|
||||
" [q]uit [/] filter ".into(),
|
||||
self.beating_heart().into(),
|
||||
" ".into(),
|
||||
])
|
||||
|
@ -380,7 +432,11 @@ impl StatefulWidget for &State {
|
|||
.centered()
|
||||
.render(interior, buf),
|
||||
ServerState::Configured { forges } => {
|
||||
ConfiguredAppWidget { forges }.render(interior, buf, state);
|
||||
ConfiguredAppWidget {
|
||||
forges,
|
||||
filter: &self.filter,
|
||||
}
|
||||
.render(interior, buf, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,12 +8,13 @@ use ratatui::{
|
|||
};
|
||||
use tui_scrollview::{ScrollView, ScrollViewState};
|
||||
|
||||
use crate::tui::actor::ForgeState;
|
||||
use crate::tui::actor::{ForgeState, UIRepoFilter};
|
||||
|
||||
use super::{forge::ForgeWidget, HeightContraintLength};
|
||||
|
||||
pub struct ConfiguredAppWidget<'a> {
|
||||
pub forges: &'a BTreeMap<ForgeAlias, ForgeState>,
|
||||
pub filter: &'a UIRepoFilter,
|
||||
}
|
||||
impl HeightContraintLength for ConfiguredAppWidget<'_> {
|
||||
fn height_constraint_length(&self) -> u16 {
|
||||
|
@ -62,6 +63,7 @@ impl<'a> ConfiguredAppWidget<'a> {
|
|||
forge_alias,
|
||||
repos: &state.repos,
|
||||
view_state: state.view_state,
|
||||
filter: self.filter,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
|
|
@ -10,13 +10,14 @@ use ratatui::{
|
|||
};
|
||||
|
||||
use crate::tui::{
|
||||
actor::RepoState,
|
||||
actor::{RepoState, UIRepoFilter},
|
||||
components::{repo::RepoWidget, HeightContraintLength},
|
||||
};
|
||||
|
||||
pub struct ExpandedForgeWidget<'a> {
|
||||
pub forge_alias: &'a ForgeAlias,
|
||||
pub repos: &'a BTreeMap<RepoAlias, RepoState>,
|
||||
pub filter: &'a UIRepoFilter,
|
||||
}
|
||||
impl HeightContraintLength for ExpandedForgeWidget<'_> {
|
||||
fn height_constraint_length(&self) -> u16 {
|
||||
|
@ -55,6 +56,15 @@ impl<'a> ExpandedForgeWidget<'a> {
|
|||
fn children(&self) -> Vec<RepoWidget<'a>> {
|
||||
self.repos
|
||||
.values()
|
||||
.filter(|repo_state| {
|
||||
if self.filter.is_empty() {
|
||||
true
|
||||
} else {
|
||||
let repo_alias = repo_state.repo_alias();
|
||||
// eprintln!("--> {} ? {}", self.filter.as_str(), repo_alias);
|
||||
repo_alias.contains(self.filter.as_str())
|
||||
}
|
||||
})
|
||||
.map(|repo_state| RepoWidget { repo_state })
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use expanded::ExpandedForgeWidget;
|
|||
use git_next_core::{ForgeAlias, RepoAlias};
|
||||
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
|
||||
|
||||
use crate::tui::actor::{RepoState, ViewState};
|
||||
use crate::tui::actor::{RepoState, UIRepoFilter, ViewState};
|
||||
|
||||
use super::HeightContraintLength;
|
||||
|
||||
|
@ -17,6 +17,7 @@ pub struct ForgeWidget<'a> {
|
|||
pub forge_alias: &'a ForgeAlias,
|
||||
pub repos: &'a BTreeMap<RepoAlias, RepoState>,
|
||||
pub view_state: ViewState,
|
||||
pub filter: &'a UIRepoFilter,
|
||||
}
|
||||
impl HeightContraintLength for ForgeWidget<'_> {
|
||||
fn height_constraint_length(&self) -> u16 {
|
||||
|
@ -28,6 +29,7 @@ impl HeightContraintLength for ForgeWidget<'_> {
|
|||
ViewState::Expanded => ExpandedForgeWidget {
|
||||
forge_alias: self.forge_alias,
|
||||
repos: self.repos,
|
||||
filter: self.filter,
|
||||
}
|
||||
.height_constraint_length(),
|
||||
}
|
||||
|
@ -46,6 +48,7 @@ impl Widget for ForgeWidget<'_> {
|
|||
ViewState::Expanded => ExpandedForgeWidget {
|
||||
forge_alias: self.forge_alias,
|
||||
repos: self.repos,
|
||||
filter: self.filter,
|
||||
}
|
||||
.render(area, buf),
|
||||
}
|
||||
|
|
8
crates/cli/src/tui/components/key_focus.rs
Normal file
8
crates/cli/src/tui/components/key_focus.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum KeyFocus {
|
||||
/// Keyboard is not focused on any input
|
||||
#[default]
|
||||
None,
|
||||
/// Keyboard is focused on editing the filter
|
||||
Filter,
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
mod configured_app;
|
||||
mod forge;
|
||||
mod history;
|
||||
pub mod key_focus;
|
||||
mod repo;
|
||||
|
||||
pub use configured_app::ConfiguredAppWidget;
|
||||
|
|
Loading…
Add table
Reference in a new issue