zellij/src/input.rs
Kunal Mohan 92d1bcff4c
feat(infrastructure): introduce ErrorContext for tracking errors across threads (#83)
* Implement ErrorContext for tracking errors across threads

* reorder Instruction and ErrorContext

* Add ContextType, AppContext, ScreenContext, PtyContext

* Use ArrayVec in ErrorContext

* increase MAX_THREAD_CALL_STACK to 6

* Custom implement Debug for ErrorContext ad ContextType and color output

* Use os_input instead of println!()

* Use array instead of ArrayVec and some cleanup

* Introduce SenderWithContext

* Keep arrayvec at v0.5.1
2020-12-09 18:01:26 +01:00

283 lines
10 KiB
Rust

/// Module for handling input
use crate::errors::ContextType;
use crate::os_input_output::OsApi;
use crate::pty_bus::PtyInstruction;
use crate::screen::ScreenInstruction;
use crate::CommandIsExecuting;
use crate::{AppInstruction, SenderWithContext, OPENCALLS};
struct InputHandler {
mode: InputMode,
os_input: Box<dyn OsApi>,
command_is_executing: CommandIsExecuting,
send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>,
}
impl InputHandler {
fn new(
os_input: Box<dyn OsApi>,
command_is_executing: CommandIsExecuting,
send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>,
) -> Self {
InputHandler {
mode: InputMode::Normal,
os_input,
command_is_executing,
send_screen_instructions,
send_pty_instructions,
send_app_instructions,
}
}
/// Main event loop
fn get_input(&mut self) {
let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
err_ctx.add_call(ContextType::StdinHandler);
self.send_pty_instructions.update(err_ctx);
self.send_app_instructions.update(err_ctx);
self.send_screen_instructions.update(err_ctx);
loop {
match self.mode {
InputMode::Normal => self.read_normal_mode(),
InputMode::Command => self.read_command_mode(false),
InputMode::CommandPersistent => self.read_command_mode(true),
InputMode::Exiting => {
self.exit();
break;
}
}
}
}
/// Read input to the terminal (or switch to command mode)
fn read_normal_mode(&mut self) {
assert_eq!(self.mode, InputMode::Normal);
loop {
let stdin_buffer = self.os_input.read_from_stdin();
match stdin_buffer.as_slice() {
[7] => {
// ctrl-g
self.mode = InputMode::Command;
return;
}
_ => {
self.send_screen_instructions
.send(ScreenInstruction::ClearScroll)
.unwrap();
self.send_screen_instructions
.send(ScreenInstruction::WriteCharacter(stdin_buffer))
.unwrap();
}
}
}
}
/// Read input and parse it as commands for mosaic
fn read_command_mode(&mut self, persistent: bool) {
//@@@khs26 Add a powerbar type thing that we can write output to
if persistent {
assert_eq!(self.mode, InputMode::CommandPersistent);
} else {
assert_eq!(self.mode, InputMode::Command);
}
loop {
let stdin_buffer = self.os_input.read_from_stdin();
// uncomment this to print the entered character to a log file (/tmp/mosaic/mosaic-log.txt) for debugging
// debug_log_to_file(format!("buffer {:?}", stdin_buffer));
match stdin_buffer.as_slice() {
[7] => {
// Ctrl-g
// If we're in command mode, this will let us switch to persistent command mode, to execute
// multiple commands. If we're already in persistent mode, it'll return us to normal mode.
match self.mode {
InputMode::Command => self.mode = InputMode::CommandPersistent,
InputMode::CommandPersistent => {
self.mode = InputMode::Normal;
return;
}
_ => panic!(),
}
}
[27] => {
// Esc
self.mode = InputMode::Normal;
return;
}
[106] => {
// j
self.send_screen_instructions
.send(ScreenInstruction::ResizeDown)
.unwrap();
}
[107] => {
// k
self.send_screen_instructions
.send(ScreenInstruction::ResizeUp)
.unwrap();
}
[112] => {
// p
self.send_screen_instructions
.send(ScreenInstruction::MoveFocus)
.unwrap();
}
[104] => {
// h
self.send_screen_instructions
.send(ScreenInstruction::ResizeLeft)
.unwrap();
}
[108] => {
// l
self.send_screen_instructions
.send(ScreenInstruction::ResizeRight)
.unwrap();
}
[122] => {
// z
self.command_is_executing.opening_new_pane();
self.send_pty_instructions
.send(PtyInstruction::SpawnTerminal(None))
.unwrap();
self.command_is_executing.wait_until_new_pane_is_opened();
}
[110] => {
// n
self.command_is_executing.opening_new_pane();
self.send_pty_instructions
.send(PtyInstruction::SpawnTerminalVertically(None))
.unwrap();
self.command_is_executing.wait_until_new_pane_is_opened();
}
[98] => {
// b
self.command_is_executing.opening_new_pane();
self.send_pty_instructions
.send(PtyInstruction::SpawnTerminalHorizontally(None))
.unwrap();
self.command_is_executing.wait_until_new_pane_is_opened();
}
[113] => {
// q
self.mode = InputMode::Exiting;
return;
}
[27, 91, 53, 126] => {
// PgUp
self.send_screen_instructions
.send(ScreenInstruction::ScrollUp)
.unwrap();
}
[27, 91, 54, 126] => {
// PgDown
self.send_screen_instructions
.send(ScreenInstruction::ScrollDown)
.unwrap();
}
[120] => {
// x
self.command_is_executing.closing_pane();
self.send_screen_instructions
.send(ScreenInstruction::CloseFocusedPane)
.unwrap();
self.command_is_executing.wait_until_pane_is_closed();
}
[101] => {
// e
self.send_screen_instructions
.send(ScreenInstruction::ToggleActiveTerminalFullscreen)
.unwrap();
}
[121] => {
// y
self.send_screen_instructions
.send(ScreenInstruction::MoveFocusLeft)
.unwrap()
}
[117] => {
// u
self.send_screen_instructions
.send(ScreenInstruction::MoveFocusDown)
.unwrap()
}
[105] => {
// i
self.send_screen_instructions
.send(ScreenInstruction::MoveFocusUp)
.unwrap()
}
[111] => {
// o
self.send_screen_instructions
.send(ScreenInstruction::MoveFocusRight)
.unwrap()
}
//@@@khs26 Write this to the powerbar?
_ => {}
}
if self.mode == InputMode::Command {
self.mode = InputMode::Normal;
return;
}
}
}
/// Routine to be called when the input handler exits (at the moment this is the
/// same as quitting mosaic)
fn exit(&mut self) {
self.send_screen_instructions
.send(ScreenInstruction::Quit)
.unwrap();
self.send_pty_instructions
.send(PtyInstruction::Quit)
.unwrap();
self.send_app_instructions
.send(AppInstruction::Exit)
.unwrap();
}
}
/// Dictates whether we're in command mode, persistent command mode, normal mode or exiting:
/// - Normal mode either writes characters to the terminal, or switches to command mode
/// using a particular key control
/// - Command mode intercepts characters to control mosaic itself, before switching immediately
/// back to normal mode
/// - Persistent command mode is the same as command mode, but doesn't return automatically to
/// normal mode
/// - Exiting means that we should start the shutdown process for mosaic or the given
/// input handler
#[derive(Debug, PartialEq)]
pub enum InputMode {
Normal,
Command,
CommandPersistent,
Exiting,
}
/// Entry point to the module that instantiates a new InputHandler and calls its
/// reading loop
pub fn input_loop(
os_input: Box<dyn OsApi>,
command_is_executing: CommandIsExecuting,
send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>,
) {
let _handler = InputHandler::new(
os_input,
command_is_executing,
send_screen_instructions,
send_pty_instructions,
send_app_instructions,
)
.get_input();
}