Skip to content

Commit

Permalink
refact: split state.rs
Browse files Browse the repository at this point in the history
  • Loading branch information
jeevithakannan2 committed Oct 17, 2024
1 parent 79eb752 commit 74028bf
Show file tree
Hide file tree
Showing 8 changed files with 797 additions and 754 deletions.
754 changes: 0 additions & 754 deletions tui/src/state.rs

This file was deleted.

80 changes: 80 additions & 0 deletions tui/src/state/app_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#[cfg(feature = "tips")]
use super::tips::get_random_tip;
use crate::{
confirmation::ConfirmPrompt,
filter::Filter,
float::{Float, FloatContent},
theme::Theme,
};

use ego_tree::NodeId;
use linutil_core::{ListNode, Tab};
use ratatui::widgets::ListState;
use std::rc::Rc;
use temp_dir::TempDir;

pub enum Focus {
ConfirmationPrompt(Float<ConfirmPrompt>),
FloatingWindow(Float<dyn FloatContent>),
List,
Search,
TabList,
}

pub struct AppState {
/// This must be passed to retain the temp dir until the end of the program
_temp_dir: TempDir,
/// Current tab
pub(super) current_tab: ListState,
/// Enough size to draw terminal
pub(super) drawable: bool,
pub(super) filter: Filter,
/// Currently focused area
pub(super) focus: Focus,
/// Selected theme
pub(super) multi_select: bool,
pub(super) selected_commands: Vec<Rc<ListNode>>,
/// This is the state associated with the list widget, used to display the selection in the
/// widget
pub(super) selection: ListState,
/// List of tabs
pub(super) tabs: Vec<Tab>,
pub(super) theme: Theme,
#[cfg(feature = "tips")]
pub(super) tip: &'static str,
/// This stack keeps track of our "current directory". You can think of it as `pwd`. but not
/// just the current directory, all paths that took us here, so we can "cd .."
pub(super) visit_stack: Vec<NodeId>,
}

pub struct ListEntry {
pub has_children: bool,
pub id: NodeId,
pub node: Rc<ListNode>,
}

impl AppState {
pub fn new(theme: Theme, override_validation: bool) -> Self {
let (temp_dir, tabs) = linutil_core::get_tabs(!override_validation);
let root_id = tabs[0].tree.root().id();

let mut state = Self {
_temp_dir: temp_dir,
current_tab: ListState::default().with_selected(Some(0)),
drawable: false,
filter: Filter::new(),
focus: Focus::List,
multi_select: false,
selected_commands: Vec::new(),
selection: ListState::default().with_selected(Some(0)),
tabs,
theme,
#[cfg(feature = "tips")]
tip: get_random_tip(),
visit_stack: vec![root_id],
};

state.update_items();
state
}
}
77 changes: 77 additions & 0 deletions tui/src/state/hints.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use crate::hint::Shortcut;

use super::{AppState, Focus};

impl AppState {
pub(super) fn get_keybinds(&self) -> (&str, Box<[Shortcut]>) {
match self.focus {
Focus::Search => (
"Search bar",
Box::new([Shortcut::new("Finish search", ["Enter"])]),
),

Focus::List => {
let mut hints = Vec::new();
hints.push(Shortcut::new("Exit linutil", ["q", "CTRL-c"]));

if self.at_root() {
hints.push(Shortcut::new("Focus tab list", ["h", "Left"]));
hints.extend(self.get_list_item_shortcut());
} else if self.selected_item_is_up_dir() {
hints.push(Shortcut::new(
"Go to parent directory",
["l", "Right", "Enter", "h", "Left"],
));
} else {
hints.push(Shortcut::new("Go to parent directory", ["h", "Left"]));
hints.extend(self.get_list_item_shortcut());
}

hints.push(Shortcut::new("Select item above", ["k", "Up"]));
hints.push(Shortcut::new("Select item below", ["j", "Down"]));
hints.push(Shortcut::new("Next theme", ["t"]));
hints.push(Shortcut::new("Previous theme", ["T"]));

if self.is_current_tab_multi_selectable() {
hints.push(Shortcut::new("Toggle multi-selection mode", ["v"]));
hints.push(Shortcut::new("Select multiple commands", ["Space"]));
}

hints.push(Shortcut::new("Next tab", ["Tab"]));
hints.push(Shortcut::new("Previous tab", ["Shift-Tab"]));
hints.push(Shortcut::new("Important actions guide", ["g"]));

("Command list", hints.into_boxed_slice())
}

Focus::TabList => (
"Tab list",
Box::new([
Shortcut::new("Exit linutil", ["q", "CTRL-c"]),
Shortcut::new("Focus action list", ["l", "Right", "Enter"]),
Shortcut::new("Select item above", ["k", "Up"]),
Shortcut::new("Select item below", ["j", "Down"]),
Shortcut::new("Next theme", ["t"]),
Shortcut::new("Previous theme", ["T"]),
Shortcut::new("Next tab", ["Tab"]),
Shortcut::new("Previous tab", ["Shift-Tab"]),
]),
),

Focus::FloatingWindow(ref float) => float.get_shortcut_list(),
Focus::ConfirmationPrompt(ref prompt) => prompt.get_shortcut_list(),
}
}

fn get_list_item_shortcut(&self) -> Box<[Shortcut]> {
if self.selected_item_is_dir() {
Box::new([Shortcut::new("Go to selected dir", ["l", "Right", "Enter"])])
} else {
Box::new([
Shortcut::new("Run selected command", ["l", "Right", "Enter"]),
Shortcut::new("Enable preview", ["p"]),
Shortcut::new("Command Description", ["d"]),
])
}
}
}
122 changes: 122 additions & 0 deletions tui/src/state/keybindings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use super::{AppState, Focus};
use crate::{confirmation::ConfirmStatus, filter::SearchAction, float::FloatContent};

use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};

impl AppState {
pub fn handle_key(&mut self, key: &KeyEvent) -> bool {
// This should be defined first to allow closing
// the application even when not drawable ( If terminal is small )
// Exit on 'q' or 'Ctrl-c' input
if matches!(
self.focus,
Focus::TabList | Focus::List | Focus::ConfirmationPrompt(_)
) && (key.code == KeyCode::Char('q')
|| key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('c'))
{
return false;
}

// If UI is not drawable returning true will mark as the key handled
if !self.drawable {
return true;
}

// Handle key only when Tablist or List is focused
// Prevents exiting the application even when a command is running
// Add keys here which should work on both TabList and List
if matches!(self.focus, Focus::TabList | Focus::List) {
match key.code {
KeyCode::BackTab => {
if self.current_tab.selected().unwrap() == 0 {
self.current_tab.select(Some(self.tabs.len() - 1));
} else {
self.current_tab.select_previous();
}
self.refresh_tab();
}

KeyCode::Tab => {
if self.current_tab.selected().unwrap() == self.tabs.len() - 1 {
self.current_tab.select_first();
} else {
self.current_tab.select_next();
}
self.refresh_tab();
}
_ => {}
}
}

match &mut self.focus {
Focus::FloatingWindow(command) => {
if command.handle_key_event(key) {
self.focus = Focus::List;
}
}

Focus::ConfirmationPrompt(confirm) => {
confirm.content.handle_key_event(key);
match confirm.content.status {
ConfirmStatus::Abort => {
self.focus = Focus::List;
// selected command was pushed to selection list if multi-select was
// enabled, need to clear it to prevent state corruption
if !self.multi_select {
self.selected_commands.clear()
}
}
ConfirmStatus::Confirm => self.handle_confirm_command(),
ConfirmStatus::None => {}
}
}

Focus::Search => match self.filter.handle_key(key) {
SearchAction::Exit => self.exit_search(),
SearchAction::Update => self.update_items(),
SearchAction::None => {}
},

Focus::TabList => match key.code {
KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right => self.focus = Focus::List,

KeyCode::Char('j') | KeyCode::Down
if self.current_tab.selected().unwrap() + 1 < self.tabs.len() =>
{
self.current_tab.select_next();
self.refresh_tab();
}

KeyCode::Char('k') | KeyCode::Up => {
self.current_tab.select_previous();
self.refresh_tab();
}

KeyCode::Char('/') => self.enter_search(),
KeyCode::Char('t') => self.theme.next(),
KeyCode::Char('T') => self.theme.prev(),
KeyCode::Char('g') => self.toggle_task_list_guide(),
_ => {}
},

Focus::List if key.kind != KeyEventKind::Release => match key.code {
KeyCode::Char('j') | KeyCode::Down => self.selection.select_next(),
KeyCode::Char('k') | KeyCode::Up => self.selection.select_previous(),
KeyCode::Char('p') | KeyCode::Char('P') => self.enable_preview(),
KeyCode::Char('d') | KeyCode::Char('D') => self.enable_description(),
KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right => self.handle_enter(),
KeyCode::Char('h') | KeyCode::Left => self.go_back(),
KeyCode::Char('/') => self.enter_search(),
KeyCode::Char('t') => self.theme.next(),
KeyCode::Char('T') => self.theme.prev(),
KeyCode::Char('g') => self.toggle_task_list_guide(),
KeyCode::Char('v') | KeyCode::Char('V') => self.toggle_multi_select(),
KeyCode::Char(' ') if self.multi_select => self.toggle_selection(),
_ => {}
},

_ => (),
};
true
}
}
Loading

0 comments on commit 74028bf

Please sign in to comment.