Feat(App): implemented vim like modes (Normal/Insert)
This commit is contained in:
parent
4856ecc582
commit
d447eb2312
|
@ -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,
|
||||
|
|
|
@ -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?;
|
||||
|
|
|
@ -10,7 +10,7 @@ use crate::{
|
|||
ui::central,
|
||||
};
|
||||
|
||||
pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<EventStatus> {
|
||||
pub async fn handle_normal(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<EventStatus> {
|
||||
match input_event {
|
||||
CrosstermEvent::Key(KeyEvent {
|
||||
code: KeyCode::Esc, ..
|
||||
|
@ -35,37 +35,22 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
|
|||
.await?;
|
||||
}
|
||||
CrosstermEvent::Key(KeyEvent {
|
||||
code: KeyCode::Char('c'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
code: KeyCode::Char(':'),
|
||||
..
|
||||
}) => {
|
||||
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<E
|
|||
};
|
||||
Ok(EventStatus::Ok)
|
||||
}
|
||||
|
||||
pub async fn handle_insert(app: &mut App<'_>, event: &CrosstermEvent) -> Result<EventStatus> {
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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)),
|
||||
},
|
||||
|
|
|
@ -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")?;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
|
|
Reference in New Issue