mirror of
https://github.com/zellij-org/zellij.git
synced 2024-12-28 11:42:41 +03:00
92d1bcff4c
* 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
283 lines
10 KiB
Rust
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();
|
|
}
|