From d447eb23127f6be78d91122ac05cfba6529f0e0c Mon Sep 17 00:00:00 2001 From: antifallobst Date: Sat, 22 Jul 2023 16:00:52 +0200 Subject: [PATCH] Feat(App): implemented vim like modes (Normal/Insert) --- src/app/command_interface.rs | 10 ++- .../event_types/event/handlers/command.rs | 17 ++++- .../events/event_types/event/handlers/main.rs | 67 ++++++++++++------- src/app/events/event_types/event/mod.rs | 7 +- src/app/mod.rs | 2 - src/app/status.rs | 18 +++-- src/ui/central/mod.rs | 4 ++ src/ui/central/update/mod.rs | 35 +++++++--- 8 files changed, 113 insertions(+), 47 deletions(-) diff --git a/src/app/command_interface.rs b/src/app/command_interface.rs index 6f74395..e475113 100644 --- a/src/app/command_interface.rs +++ b/src/app/command_interface.rs @@ -1,7 +1,10 @@ // FIXME: This file needs documentation with examples of how the proc macros work. // for now use `cargo expand app::command_interface` for an overview -use std::{io::{Error, ErrorKind}, sync::Arc}; +use std::{ + io::{Error, ErrorKind}, + sync::Arc, +}; use lua_macros::{ci_command, turn_struct_to_ci_command_enum}; @@ -43,6 +46,11 @@ struct Commands { /// Go to the previous plane cycle_planes_rev: fn(), + /// Sets the current app mode to Normal / navigation mode + set_mode_normal: fn(), + /// Sets the current app mode to Insert / editing mode + set_mode_insert: fn(), + /// Send a message to the current room /// The send message is interpreted literally. room_message_send: fn(String) -> String, diff --git a/src/app/events/event_types/event/handlers/command.rs b/src/app/events/event_types/event/handlers/command.rs index f058bd6..4a3f9dc 100644 --- a/src/app/events/event_types/event/handlers/command.rs +++ b/src/app/events/event_types/event/handlers/command.rs @@ -1,4 +1,7 @@ -use crate::app::{command_interface::Command, events::event_types::EventStatus, App}; +use crate::{ + app::{command_interface::Command, events::event_types::EventStatus, status::State, App}, + ui::central::InputPosition, +}; use anyhow::Result; use cli_log::info; @@ -49,6 +52,18 @@ pub async fn handle( (EventStatus::Ok, "".to_owned()) } + Command::SetModeNormal => { + app.status.set_state(State::Normal); + set_status_output!("Set input mode to Normal"); + (EventStatus::Ok, "".to_owned()) + } + Command::SetModeInsert => { + app.status.set_state(State::Insert); + app.ui.set_input_position(InputPosition::MessageCompose); + set_status_output!("Set input mode to Insert"); + (EventStatus::Ok, "".to_owned()) + } + Command::RoomMessageSend(msg) => { if let Some(room) = app.status.room_mut() { room.send(msg.clone()).await?; diff --git a/src/app/events/event_types/event/handlers/main.rs b/src/app/events/event_types/event/handlers/main.rs index 5e076bc..6396ed1 100644 --- a/src/app/events/event_types/event/handlers/main.rs +++ b/src/app/events/event_types/event/handlers/main.rs @@ -10,7 +10,7 @@ use crate::{ ui::central, }; -pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result { +pub async fn handle_normal(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result { match input_event { CrosstermEvent::Key(KeyEvent { code: KeyCode::Esc, .. @@ -35,37 +35,22 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result { app.tx .send(Event::CommandEvent(Command::CommandLineShow, None)) .await?; } + CrosstermEvent::Key(KeyEvent { + code: KeyCode::Char('i'), + .. + }) => { + app.tx + .send(Event::CommandEvent(Command::SetModeInsert, None)) + .await?; + } input => match app.ui.input_position() { - central::InputPosition::MessageCompose => { - match input { - CrosstermEvent::Key(KeyEvent { - code: KeyCode::Enter, - modifiers: KeyModifiers::ALT, - .. - }) => { - app.tx - .send(Event::CommandEvent( - Command::RoomMessageSend(app.ui.message_compose.lines().join("\n")), - None, - )) - .await?; - app.ui.message_compose_clear(); - } - _ => { - app.ui - .message_compose - .input(tui_textarea::Input::from(input.to_owned())); - } - }; - } central::InputPosition::Rooms => { match input { CrosstermEvent::Key(KeyEvent { @@ -186,3 +171,35 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result, event: &CrosstermEvent) -> Result { + match event { + CrosstermEvent::Key(KeyEvent { + code: KeyCode::Esc, .. + }) => { + app.tx + .send(Event::CommandEvent(Command::SetModeNormal, None)) + .await?; + } + CrosstermEvent::Key(KeyEvent { + code: KeyCode::Enter, + modifiers: KeyModifiers::ALT, + .. + }) => { + app.tx + .send(Event::CommandEvent( + Command::RoomMessageSend(app.ui.message_compose.lines().join("\n")), + None, + )) + .await?; + app.ui.message_compose_clear(); + } + _ => { + app.ui + .message_compose + .input(tui_textarea::Input::from(event.to_owned())); + } + }; + + Ok(EventStatus::Ok) +} diff --git a/src/app/events/event_types/event/mod.rs b/src/app/events/event_types/event/mod.rs index 65cc9ec..f1293ef 100644 --- a/src/app/events/event_types/event/mod.rs +++ b/src/app/events/event_types/event/mod.rs @@ -45,13 +45,10 @@ impl Event { .with_context(|| format!("Failed to handle lua code: `{:#?}`", lua_code)), Event::InputEvent(event) => match app.status.state() { - State::None => unreachable!( - "This state should not be available, when we are in the input handling" - ), - State::Main => main::handle(app, event) + State::Normal => main::handle_normal(app, event) .await .with_context(|| format!("Failed to handle input event: `{:#?}`", event)), - State::Setup => setup::handle(app, event) + State::Insert => main::handle_insert(app, event) .await .with_context(|| format!("Failed to handle input event: `{:#?}`", event)), }, diff --git a/src/app/mod.rs b/src/app/mod.rs index e43f46a..e7d97a0 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -82,7 +82,6 @@ impl App<'_> { } loop { - self.status.set_state(State::Main); self.ui.update(&self.status).await?; let event = self.rx.recv().await.context("Failed to get next event")?; @@ -102,7 +101,6 @@ impl App<'_> { self.ui.setup_ui = Some(setup::UI::new()); loop { - self.status.set_state(State::Setup); self.ui.update_setup().await?; let event = self.rx.recv().await.context("Failed to get next event")?; diff --git a/src/app/status.rs b/src/app/status.rs index 2a858f9..f297e3b 100644 --- a/src/app/status.rs +++ b/src/app/status.rs @@ -1,3 +1,5 @@ +use core::fmt; + use anyhow::{Error, Result}; use cli_log::warn; use indexmap::IndexMap; @@ -11,9 +13,8 @@ use matrix_sdk::{ }; pub enum State { - None, - Main, - Setup, + Normal, + Insert, } pub struct Room { @@ -35,6 +36,15 @@ pub struct Status { current_room_id: String, } +impl fmt::Display for State { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Normal => write!(f, "Normal"), + Self::Insert => write!(f, "Insert"), + } + } +} + impl Room { pub fn new(matrix_room: matrix_sdk::room::Joined) -> Self { Self { @@ -124,7 +134,7 @@ impl Status { }; Self { - state: State::None, + state: State::Normal, account_name: "".to_string(), account_user_id: "".to_string(), client, diff --git a/src/ui/central/mod.rs b/src/ui/central/mod.rs index 882584d..b2bf4e1 100644 --- a/src/ui/central/mod.rs +++ b/src/ui/central/mod.rs @@ -112,6 +112,10 @@ impl UI<'_> { }; } + pub fn set_input_position(&mut self, position: InputPosition) { + self.input_position = position; + } + pub fn input_position(&self) -> &InputPosition { &self.input_position } diff --git a/src/ui/central/update/mod.rs b/src/ui/central/update/mod.rs index 412cb8c..2b1ed2d 100644 --- a/src/ui/central/update/mod.rs +++ b/src/ui/central/update/mod.rs @@ -3,7 +3,8 @@ use std::cmp; use anyhow::{Context, Result}; use tui::{ layout::{Constraint, Direction, Layout}, - style::Color, + style::{Color, Style}, + widgets::{Block, Borders, Paragraph}, }; use crate::{ @@ -19,13 +20,21 @@ pub mod widgets; impl UI<'_> { pub async fn update(&mut self, status: &Status) -> Result<()> { - let chunks = match self.cli { - Some(_) => Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Min(10), Constraint::Length(3)].as_ref()) - .split(self.terminal.size()?), - None => vec![self.terminal.size()?], - }; + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Min(10), Constraint::Length(3)].as_ref()) + .split(self.terminal.size()?); + + let bottom_chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints( + [ + Constraint::Length((status.state().to_string().len() + 2) as u16), + Constraint::Min(16), + ] + .as_ref(), + ) + .split(chunks[1]); let main_chunks = Layout::default() .direction(Direction::Horizontal) @@ -143,6 +152,13 @@ impl UI<'_> { }; // initiate the widgets + let mode_indicator = Paragraph::new(status.state().to_string()) + .block( + Block::default() + .borders(Borders::ALL) + .style(Style::default().fg(Color::DarkGray)), + ) + .style(Style::default().fg(Color::LightYellow)); let status_panel = status::init(status, &colors); let rooms_panel = rooms::init(status, &colors); let (messages_panel, mut messages_state) = messages::init(status.room(), &colors) @@ -155,8 +171,9 @@ impl UI<'_> { frame.render_stateful_widget(rooms_panel, left_chunks[1], &mut self.rooms_state); frame.render_stateful_widget(messages_panel, middle_chunks[0], &mut messages_state); frame.render_widget(self.message_compose.widget(), middle_chunks[1]); + frame.render_widget(mode_indicator, bottom_chunks[0]); match &self.cli { - Some(cli) => frame.render_widget(cli.widget(), chunks[1]), + Some(cli) => frame.render_widget(cli.widget(), bottom_chunks[1]), None => (), }; frame.render_widget(room_info_panel, right_chunks[0]);