feat: config file, closes #47
Enable use of a config file, with custom keymap and custom colours
This commit is contained in:
+215
-72
@@ -21,6 +21,7 @@ mod message;
|
||||
use crate::{
|
||||
app_data::{AppData, DockerCommand, Header},
|
||||
app_error::AppError,
|
||||
config,
|
||||
docker_data::DockerMessage,
|
||||
exec::{tty_readable, ExecMode},
|
||||
ui::{DeleteButton, GuiState, SelectablePanel, Status, Ui},
|
||||
@@ -32,6 +33,7 @@ pub use message::InputMessages;
|
||||
pub struct InputHandler {
|
||||
app_data: Arc<Mutex<AppData>>,
|
||||
docker_tx: Sender<DockerMessage>,
|
||||
keymap: config::Keymap,
|
||||
gui_state: Arc<Mutex<GuiState>>,
|
||||
is_running: Arc<AtomicBool>,
|
||||
mouse_capture: bool,
|
||||
@@ -47,11 +49,13 @@ impl InputHandler {
|
||||
gui_state: Arc<Mutex<GuiState>>,
|
||||
is_running: Arc<AtomicBool>,
|
||||
) {
|
||||
let keymap = app_data.lock().config.keymap.clone();
|
||||
let mut inner = Self {
|
||||
app_data,
|
||||
docker_tx,
|
||||
gui_state,
|
||||
is_running,
|
||||
keymap,
|
||||
rec,
|
||||
mouse_capture: true,
|
||||
};
|
||||
@@ -67,16 +71,15 @@ impl InputHandler {
|
||||
let status = self.gui_state.lock().get_status();
|
||||
let contains = |s: Status| status.contains(&s);
|
||||
|
||||
if !contains(Status::Error)
|
||||
if contains(Status::DeleteConfirm) {
|
||||
self.button_intersect(mouse_event).await;
|
||||
} else if !contains(Status::Error)
|
||||
| !contains(Status::Help)
|
||||
| !contains(Status::DeleteConfirm)
|
||||
| !contains(Status::Filter)
|
||||
{
|
||||
self.mouse_press(mouse_event);
|
||||
}
|
||||
if contains(Status::DeleteConfirm) {
|
||||
self.button_intersect(mouse_event).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,7 +118,7 @@ impl InputHandler {
|
||||
}
|
||||
|
||||
/// Validate that one can exec into a Docker container
|
||||
async fn e_key(&self) {
|
||||
async fn exec_key(&self) {
|
||||
let is_oxker = self.app_data.lock().is_oxker();
|
||||
if !is_oxker && tty_readable() {
|
||||
let uuid = Uuid::new_v4();
|
||||
@@ -142,7 +145,7 @@ impl InputHandler {
|
||||
}
|
||||
|
||||
/// Toggle the mouse capture (via input of the 'm' key)
|
||||
fn m_key(&mut self) {
|
||||
fn mouse_capture_key(&mut self) {
|
||||
let err = || {
|
||||
self.app_data.lock().set_error(
|
||||
AppError::MouseCapture(!self.mouse_capture),
|
||||
@@ -171,7 +174,7 @@ impl InputHandler {
|
||||
|
||||
/// Save the currently selected containers logs into a `[container_name]_[timestamp].log` file
|
||||
async fn save_logs(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args = self.app_data.lock().args.clone();
|
||||
let args = self.app_data.lock().config.clone();
|
||||
let container = self.app_data.lock().get_selected_container_id_state_name();
|
||||
if let Some((id, _, name)) = container {
|
||||
if let Some(log_path) = args.save_dir {
|
||||
@@ -187,7 +190,7 @@ impl InputHandler {
|
||||
let options = Some(LogsOptions::<String> {
|
||||
stderr: true,
|
||||
stdout: true,
|
||||
timestamps: args.timestamp,
|
||||
timestamps: args.show_timestamp,
|
||||
since: 0,
|
||||
..Default::default()
|
||||
});
|
||||
@@ -230,7 +233,7 @@ impl InputHandler {
|
||||
}
|
||||
|
||||
/// Attempt to save the currently selected container logs to a file
|
||||
async fn s_key(&self) {
|
||||
async fn save_key(&self) {
|
||||
let status = self.gui_state.lock().get_status();
|
||||
let contains = |s: Status| status.contains(&s);
|
||||
|
||||
@@ -284,7 +287,7 @@ impl InputHandler {
|
||||
|
||||
/// Change the the "next" selectable panel
|
||||
/// If no containers, and on Commands panel, skip to next panel, as Commands panel isn't visible in this state
|
||||
fn tab_key(&self) {
|
||||
fn next_panel_key(&self) {
|
||||
self.gui_state.lock().next_panel();
|
||||
if self.app_data.lock().get_container_len() == 0
|
||||
&& self.gui_state.lock().get_selected_panel() == SelectablePanel::Commands
|
||||
@@ -295,7 +298,7 @@ impl InputHandler {
|
||||
|
||||
/// Change to previously selected panel
|
||||
/// Need to skip the commands planel if there no are current containers running
|
||||
fn back_tab_key(&self) {
|
||||
fn previous_panel_key(&self) {
|
||||
self.gui_state.lock().previous_panel();
|
||||
if self.app_data.lock().get_container_len() == 0
|
||||
&& self.gui_state.lock().get_selected_panel() == SelectablePanel::Commands
|
||||
@@ -304,7 +307,7 @@ impl InputHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fn home_key(&self) {
|
||||
fn scroll_start_key(&self) {
|
||||
let selected_panel = self.gui_state.lock().get_selected_panel();
|
||||
match selected_panel {
|
||||
SelectablePanel::Containers => self.app_data.lock().containers_start(),
|
||||
@@ -314,7 +317,7 @@ impl InputHandler {
|
||||
}
|
||||
|
||||
/// Go to end of the list of the currently selected panel
|
||||
fn end_key(&self) {
|
||||
fn scroll_end_key(&self) {
|
||||
let selected_panel = self.gui_state.lock().get_selected_panel();
|
||||
match selected_panel {
|
||||
SelectablePanel::Containers => self.app_data.lock().containers_end(),
|
||||
@@ -325,32 +328,41 @@ impl InputHandler {
|
||||
|
||||
/// Actions to take when in Help status active
|
||||
fn handle_help(&mut self, key_code: KeyCode) {
|
||||
match key_code {
|
||||
KeyCode::Esc | KeyCode::Char('h' | 'H') => {
|
||||
self.gui_state.lock().status_del(Status::Help);
|
||||
}
|
||||
KeyCode::Char('m' | 'M') => self.m_key(),
|
||||
_ => (),
|
||||
if self.keymap.clear.0 == key_code
|
||||
|| self.keymap.clear.1 == Some(key_code)
|
||||
|| self.keymap.toggle_help.0 == key_code
|
||||
|| self.keymap.toggle_help.1 == Some(key_code)
|
||||
{
|
||||
self.gui_state.lock().status_del(Status::Help);
|
||||
}
|
||||
|
||||
if self.keymap.toggle_mouse_capture.0 == key_code
|
||||
|| self.keymap.toggle_mouse_capture.1 == Some(key_code)
|
||||
{
|
||||
self.mouse_capture_key();
|
||||
}
|
||||
}
|
||||
|
||||
/// Actions to take when Error status active
|
||||
fn handle_error(&self, key_code: KeyCode) {
|
||||
match key_code {
|
||||
KeyCode::Esc | KeyCode::Char('c' | 'C') => {
|
||||
self.app_data.lock().remove_error();
|
||||
self.gui_state.lock().status_del(Status::Error);
|
||||
}
|
||||
_ => (),
|
||||
if self.keymap.clear.0 == key_code || self.keymap.clear.1 == Some(key_code) {
|
||||
self.app_data.lock().remove_error();
|
||||
self.gui_state.lock().status_del(Status::Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// Actions to take when Delete status active
|
||||
async fn handle_delete(&self, key_code: KeyCode) {
|
||||
match key_code {
|
||||
KeyCode::Char('y' | 'Y') => self.confirm_delete().await,
|
||||
KeyCode::Esc | KeyCode::Char('n' | 'N') => self.clear_delete(),
|
||||
_ => (),
|
||||
if self.keymap.delete_confirm.0 == key_code
|
||||
|| self.keymap.delete_confirm.1 == Some(key_code)
|
||||
{
|
||||
self.confirm_delete().await;
|
||||
} else if self.keymap.delete_deny.0 == key_code
|
||||
|| self.keymap.delete_deny.1 == Some(key_code)
|
||||
|| self.keymap.clear.0 == key_code
|
||||
|| self.keymap.clear.1 == Some(key_code)
|
||||
{
|
||||
self.clear_delete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,7 +373,10 @@ impl InputHandler {
|
||||
self.app_data.lock().filter_term_clear();
|
||||
self.gui_state.lock().status_del(Status::Filter);
|
||||
}
|
||||
KeyCode::Enter | KeyCode::F(1) | KeyCode::Char('/') => {
|
||||
_ if KeyCode::Enter == key_code
|
||||
|| self.keymap.filter_mode.0 == key_code
|
||||
|| self.keymap.filter_mode.1 == Some(key_code) =>
|
||||
{
|
||||
self.gui_state.lock().status_del(Status::Filter);
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
@@ -380,47 +395,159 @@ impl InputHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle input that refers to the sorting of columns
|
||||
fn handle_sort(&self, key_code: KeyCode) {
|
||||
match key_code {
|
||||
_ if self.keymap.sort_reset.0 == key_code
|
||||
|| self.keymap.sort_reset.1 == Some(key_code) =>
|
||||
{
|
||||
self.app_data.lock().reset_sorted();
|
||||
}
|
||||
|
||||
_ if self.keymap.sort_by_name.0 == key_code
|
||||
|| self.keymap.sort_by_name.1 == Some(key_code) =>
|
||||
{
|
||||
self.sort(Header::Name);
|
||||
}
|
||||
|
||||
_ if self.keymap.sort_by_state.0 == key_code
|
||||
|| self.keymap.sort_by_state.1 == Some(key_code) =>
|
||||
{
|
||||
self.sort(Header::State);
|
||||
}
|
||||
|
||||
_ if self.keymap.sort_by_status.0 == key_code
|
||||
|| self.keymap.sort_by_status.1 == Some(key_code) =>
|
||||
{
|
||||
self.sort(Header::Status);
|
||||
}
|
||||
|
||||
_ if self.keymap.sort_by_cpu.0 == key_code
|
||||
|| self.keymap.sort_by_cpu.1 == Some(key_code) =>
|
||||
{
|
||||
self.sort(Header::Cpu);
|
||||
}
|
||||
_ if self.keymap.sort_by_memory.0 == key_code
|
||||
|| self.keymap.sort_by_memory.1 == Some(key_code) =>
|
||||
{
|
||||
self.sort(Header::Memory);
|
||||
}
|
||||
_ if self.keymap.sort_by_id.0 == key_code
|
||||
|| self.keymap.sort_by_id.1 == Some(key_code) =>
|
||||
{
|
||||
self.sort(Header::Id);
|
||||
}
|
||||
_ if self.keymap.sort_by_image.0 == key_code
|
||||
|| self.keymap.sort_by_image.1 == Some(key_code) =>
|
||||
{
|
||||
self.sort(Header::Image);
|
||||
}
|
||||
|
||||
_ if self.keymap.sort_by_rx.0 == key_code
|
||||
|| self.keymap.sort_by_rx.1 == Some(key_code) =>
|
||||
{
|
||||
self.sort(Header::Rx);
|
||||
}
|
||||
|
||||
_ if self.keymap.sort_by_tx.0 == key_code
|
||||
|| self.keymap.sort_by_tx.1 == Some(key_code) =>
|
||||
{
|
||||
self.sort(Header::Tx);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle button presses in all other scenarios
|
||||
async fn handle_others(&mut self, key_code: KeyCode) {
|
||||
self.handle_sort(key_code);
|
||||
match key_code {
|
||||
KeyCode::Char('0') => self.app_data.lock().reset_sorted(),
|
||||
KeyCode::Char('1') => self.sort(Header::Name),
|
||||
KeyCode::Char('2') => self.sort(Header::State),
|
||||
KeyCode::Char('3') => self.sort(Header::Status),
|
||||
KeyCode::Char('4') => self.sort(Header::Cpu),
|
||||
KeyCode::Char('5') => self.sort(Header::Memory),
|
||||
KeyCode::Char('6') => self.sort(Header::Id),
|
||||
KeyCode::Char('7') => self.sort(Header::Image),
|
||||
KeyCode::Char('8') => self.sort(Header::Rx),
|
||||
KeyCode::Char('9') => self.sort(Header::Tx),
|
||||
KeyCode::Char('e' | 'E') => self.e_key().await,
|
||||
KeyCode::Char('h' | 'H') => self.gui_state.lock().status_push(Status::Help),
|
||||
KeyCode::Char('m' | 'M') => self.m_key(),
|
||||
KeyCode::Char('s' | 'S') => self.s_key().await,
|
||||
KeyCode::Tab => self.tab_key(),
|
||||
KeyCode::BackTab => self.back_tab_key(),
|
||||
KeyCode::Home => self.home_key(),
|
||||
KeyCode::End => self.end_key(),
|
||||
KeyCode::Up | KeyCode::Char('k' | 'K') => self.previous(),
|
||||
KeyCode::PageUp => {
|
||||
_ if self.keymap.exec.0 == key_code || self.keymap.exec.1 == Some(key_code) => {
|
||||
self.exec_key().await;
|
||||
}
|
||||
|
||||
_ if self.keymap.toggle_help.0 == key_code
|
||||
|| self.keymap.toggle_help.1 == Some(key_code) =>
|
||||
{
|
||||
self.gui_state.lock().status_push(Status::Help);
|
||||
}
|
||||
|
||||
_ if self.keymap.toggle_mouse_capture.0 == key_code
|
||||
|| self.keymap.toggle_mouse_capture.1 == Some(key_code) =>
|
||||
{
|
||||
self.mouse_capture_key();
|
||||
}
|
||||
|
||||
_ if self.keymap.save_logs.0 == key_code
|
||||
|| self.keymap.save_logs.1 == Some(key_code) =>
|
||||
{
|
||||
self.save_key().await;
|
||||
}
|
||||
|
||||
_ if self.keymap.select_next_panel.0 == key_code
|
||||
|| self.keymap.select_next_panel.1 == Some(key_code) =>
|
||||
{
|
||||
self.next_panel_key();
|
||||
}
|
||||
|
||||
_ if self.keymap.select_previous_panel.0 == key_code
|
||||
|| self.keymap.select_previous_panel.1 == Some(key_code) =>
|
||||
{
|
||||
self.previous_panel_key();
|
||||
}
|
||||
|
||||
_ if self.keymap.scroll_start.0 == key_code
|
||||
|| self.keymap.scroll_start.1 == Some(key_code) =>
|
||||
{
|
||||
self.scroll_start_key();
|
||||
}
|
||||
|
||||
_ if self.keymap.scroll_end.0 == key_code
|
||||
|| self.keymap.scroll_end.1 == Some(key_code) =>
|
||||
{
|
||||
self.scroll_end_key();
|
||||
}
|
||||
|
||||
_ if self.keymap.scroll_up_one.0 == key_code
|
||||
|| self.keymap.scroll_up_one.1 == Some(key_code) =>
|
||||
{
|
||||
self.previous();
|
||||
}
|
||||
|
||||
_ if self.keymap.scroll_up_many.0 == key_code
|
||||
|| self.keymap.scroll_up_many.1 == Some(key_code) =>
|
||||
{
|
||||
for _ in 0..=6 {
|
||||
self.previous();
|
||||
}
|
||||
}
|
||||
KeyCode::F(1) | KeyCode::Char('/') => {
|
||||
self.gui_state.lock().status_push(Status::Filter);
|
||||
self.docker_tx.send(DockerMessage::Update).await.ok();
|
||||
|
||||
_ if self.keymap.scroll_down_one.0 == key_code
|
||||
|| self.keymap.scroll_down_one.1 == Some(key_code) =>
|
||||
{
|
||||
self.next();
|
||||
}
|
||||
KeyCode::Down | KeyCode::Char('j' | 'J') => self.next(),
|
||||
KeyCode::PageDown => {
|
||||
|
||||
_ if self.keymap.scroll_down_many.0 == key_code
|
||||
|| self.keymap.scroll_down_many.1 == Some(key_code) =>
|
||||
{
|
||||
for _ in 0..=6 {
|
||||
self.next();
|
||||
}
|
||||
}
|
||||
|
||||
_ if self.keymap.filter_mode.0 == key_code
|
||||
|| self.keymap.filter_mode.1 == Some(key_code) =>
|
||||
{
|
||||
self.gui_state.lock().status_push(Status::Filter);
|
||||
self.docker_tx.send(DockerMessage::Update).await.ok();
|
||||
}
|
||||
|
||||
KeyCode::Enter => self.enter_key().await,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle keyboard button events
|
||||
async fn button_press(&mut self, key_code: KeyCode, key_modifier: KeyModifiers) {
|
||||
let status = self.gui_state.lock().get_status();
|
||||
@@ -433,9 +560,10 @@ impl InputHandler {
|
||||
let contains_delete = contains(Status::DeleteConfirm);
|
||||
|
||||
if !contains_exec {
|
||||
let is_c = || key_code == KeyCode::Char('c') || key_code == KeyCode::Char('C');
|
||||
let is_q = || key_code == KeyCode::Char('q') || key_code == KeyCode::Char('Q');
|
||||
if key_modifier == KeyModifiers::CONTROL && is_c() || is_q() && !contains_filter {
|
||||
let is_q = || key_code == self.keymap.quit.0 || Some(key_code) == self.keymap.quit.1;
|
||||
if key_modifier == KeyModifiers::CONTROL && key_code == KeyCode::Char('c')
|
||||
|| is_q() && !contains_filter
|
||||
{
|
||||
// Always just quit on Ctrl + c/C or q/Q, unless in Filter status active
|
||||
self.quit();
|
||||
}
|
||||
@@ -457,7 +585,7 @@ impl InputHandler {
|
||||
/// Check if a button press interacts with either the yes or no buttons in the delete container confirm window
|
||||
async fn button_intersect(&self, mouse_event: MouseEvent) {
|
||||
if mouse_event.kind == MouseEventKind::Down(MouseButton::Left) {
|
||||
let intersect = self.gui_state.lock().button_intersect(Rect::new(
|
||||
let intersect = self.gui_state.lock().get_intersect_button(Rect::new(
|
||||
mouse_event.column,
|
||||
mouse_event.row,
|
||||
1,
|
||||
@@ -466,8 +594,8 @@ impl InputHandler {
|
||||
|
||||
if let Some(button) = intersect {
|
||||
match button {
|
||||
DeleteButton::Yes => self.confirm_delete().await,
|
||||
DeleteButton::No => self.clear_delete(),
|
||||
DeleteButton::Confirm => self.confirm_delete().await,
|
||||
DeleteButton::Cancel => self.clear_delete(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -475,19 +603,34 @@ impl InputHandler {
|
||||
|
||||
/// Handle mouse button events
|
||||
fn mouse_press(&self, mouse_event: MouseEvent) {
|
||||
match mouse_event.kind {
|
||||
MouseEventKind::ScrollUp => self.previous(),
|
||||
MouseEventKind::ScrollDown => self.next(),
|
||||
MouseEventKind::Down(MouseButton::Left) => {
|
||||
let mouse_point = Rect::new(mouse_event.column, mouse_event.row, 1, 1);
|
||||
let header = self.gui_state.lock().header_intersect(mouse_point);
|
||||
if let Some(header) = header {
|
||||
self.sort(header);
|
||||
}
|
||||
// If in help panel, ignore?
|
||||
|
||||
self.gui_state.lock().panel_intersect(mouse_point);
|
||||
let status = self.gui_state.lock().get_status();
|
||||
if status.contains(&Status::Help) {
|
||||
let mouse_point = Rect::new(mouse_event.column, mouse_event.row, 1, 1);
|
||||
let help_intersect = self.gui_state.lock().get_intersect_help(mouse_point);
|
||||
if help_intersect {
|
||||
self.gui_state.lock().status_del(Status::Help);
|
||||
}
|
||||
} else {
|
||||
match mouse_event.kind {
|
||||
MouseEventKind::ScrollUp => self.previous(),
|
||||
MouseEventKind::ScrollDown => self.next(),
|
||||
MouseEventKind::Down(MouseButton::Left) => {
|
||||
let mouse_point = Rect::new(mouse_event.column, mouse_event.row, 1, 1);
|
||||
let header = self.gui_state.lock().get_intersect_header(mouse_point);
|
||||
if let Some(header) = header {
|
||||
self.sort(header);
|
||||
}
|
||||
let help_intersect = self.gui_state.lock().get_intersect_help(mouse_point);
|
||||
if help_intersect {
|
||||
self.gui_state.lock().status_push(Status::Help);
|
||||
}
|
||||
|
||||
self.gui_state.lock().get_intersect_panel(mouse_point);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user