forked from ChrisTitusTech/linutil
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
79eb752
commit 74028bf
Showing
8 changed files
with
797 additions
and
754 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"]), | ||
]) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
Oops, something went wrong.