mirror of
https://github.com/zellij-org/zellij.git
synced 2024-12-24 01:34:38 +03:00
docs: first documentation step (#185)
* added some documentation to the input module * added a bunch of documentation already, doing this non-linearly * added more comments * forgot cargo ftm again oop * PR change requests applied, some forgotten/imcomplete doc added
This commit is contained in:
parent
8be470fbc0
commit
6e5607ba26
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
/target
|
||||
*.new
|
||||
.vscode
|
||||
.vim
|
||||
|
@ -18,6 +18,9 @@ pub enum PaneId {
|
||||
Terminal(RawFd),
|
||||
Plugin(u32), // FIXME: Drop the trait object, make this a wrapper for the struct?
|
||||
}
|
||||
|
||||
/// Contains the position and size of a [`Pane`], or more generally of any terminal, measured
|
||||
/// in character rows and columns.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct PositionAndSize {
|
||||
pub x: usize,
|
||||
|
@ -1,3 +1,5 @@
|
||||
//! `Tab`s holds multiple panes. It tracks their coordinates (x/y) and size, as well as how they should be resized
|
||||
|
||||
use crate::common::{AppInstruction, SenderWithContext};
|
||||
use crate::panes::{PaneId, PositionAndSize, TerminalPane};
|
||||
use crate::pty_bus::{PtyInstruction, VteEvent};
|
||||
@ -13,15 +15,6 @@ use std::{io::Write, sync::mpsc::channel};
|
||||
|
||||
use crate::utils::logging::debug_log_to_file;
|
||||
|
||||
/*
|
||||
* Tab
|
||||
*
|
||||
* this holds multiple panes (currently terminal panes) which are currently displayed
|
||||
* when this tab is active.
|
||||
* it tracks their coordinates (x/y) and size, as well as how they should be resized
|
||||
*
|
||||
*/
|
||||
|
||||
const CURSOR_HEIGHT_WIDTH_RATIO: usize = 4; // this is not accurate and kind of a magic number, TODO: look into this
|
||||
const MIN_TERMINAL_HEIGHT: usize = 2;
|
||||
const MIN_TERMINAL_WIDTH: usize = 4;
|
||||
|
@ -1,15 +1,21 @@
|
||||
//! Error context system based on a thread-local representation of the call stack, itself based on
|
||||
//! the instructions that are sent between threads.
|
||||
|
||||
use super::{AppInstruction, OPENCALLS};
|
||||
use crate::pty_bus::PtyInstruction;
|
||||
use crate::screen::ScreenInstruction;
|
||||
|
||||
use std::fmt::{Display, Error, Formatter};
|
||||
|
||||
/// The maximum amount of calls an [`ErrorContext`] will keep track
|
||||
/// of in its stack representation. This is a per-thread maximum.
|
||||
const MAX_THREAD_CALL_STACK: usize = 6;
|
||||
|
||||
#[cfg(not(test))]
|
||||
use super::SenderWithContext;
|
||||
#[cfg(not(test))]
|
||||
use std::panic::PanicInfo;
|
||||
/// Custom panic handler/hook. Prints the [`ErrorContext`].
|
||||
#[cfg(not(test))]
|
||||
pub fn handle_panic(
|
||||
info: &PanicInfo<'_>,
|
||||
@ -66,18 +72,22 @@ pub fn handle_panic(
|
||||
}
|
||||
}
|
||||
|
||||
/// A representation of the call stack.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ErrorContext {
|
||||
calls: [ContextType; MAX_THREAD_CALL_STACK],
|
||||
}
|
||||
|
||||
impl ErrorContext {
|
||||
/// Returns a new, blank [`ErrorContext`] containing only [`Empty`](ContextType::Empty)
|
||||
/// calls.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
calls: [ContextType::Empty; MAX_THREAD_CALL_STACK],
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a call to this [`ErrorContext`]'s call stack representation.
|
||||
pub fn add_call(&mut self, call: ContextType) {
|
||||
for ctx in self.calls.iter_mut() {
|
||||
if *ctx == ContextType::Empty {
|
||||
@ -108,19 +118,30 @@ impl Display for ErrorContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// Different types of calls that form an [`ErrorContext`] call stack.
|
||||
///
|
||||
/// Complex variants store a variant of a related enum, whose variants can be built from
|
||||
/// the corresponding Zellij MSPC instruction enum variants ([`ScreenInstruction`],
|
||||
/// [`PtyInstruction`], [`AppInstruction`], etc).
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum ContextType {
|
||||
/// A screen-related call.
|
||||
Screen(ScreenContext),
|
||||
/// A PTY-related call.
|
||||
Pty(PtyContext),
|
||||
|
||||
/// A plugin-related call.
|
||||
Plugin(PluginContext),
|
||||
/// An app-related call.
|
||||
App(AppContext),
|
||||
IPCServer,
|
||||
StdinHandler,
|
||||
AsyncTask,
|
||||
/// An empty, placeholder call. This should be thought of as representing no call at all.
|
||||
/// A call stack representation filled with these is the representation of an empty call stack.
|
||||
Empty,
|
||||
}
|
||||
|
||||
// TODO use the `colored` crate for color formatting
|
||||
impl Display for ContextType {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||
let purple = "\u{1b}[1;35m";
|
||||
@ -143,6 +164,7 @@ impl Display for ContextType {
|
||||
}
|
||||
}
|
||||
|
||||
/// Stack call representations corresponding to the different types of [`ScreenInstruction`]s.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum ScreenContext {
|
||||
HandlePtyEvent,
|
||||
@ -216,6 +238,7 @@ impl From<&ScreenInstruction> for ScreenContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum PtyContext {
|
||||
SpawnTerminal,
|
||||
@ -245,6 +268,7 @@ impl From<&PtyInstruction> for PtyContext {
|
||||
|
||||
use crate::wasm_vm::PluginInstruction;
|
||||
|
||||
/// Stack call representations corresponding to the different types of [`PluginInstruction`]s.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum PluginContext {
|
||||
Load,
|
||||
@ -268,6 +292,7 @@ impl From<&PluginInstruction> for PluginContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// Stack call representations corresponding to the different types of [`AppInstruction`]s.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum AppContext {
|
||||
GetState,
|
||||
|
@ -1,8 +1,8 @@
|
||||
/// This module is for defining the set of actions that can be taken in
|
||||
/// response to a keybind and also passing actions back to the handler
|
||||
/// for dispatch.
|
||||
//! Definition of the actions that can be bound to keys.
|
||||
|
||||
use super::handler;
|
||||
|
||||
/// The four directions (left, right, up, down).
|
||||
#[derive(Clone)]
|
||||
pub enum Direction {
|
||||
Left,
|
||||
@ -11,6 +11,7 @@ pub enum Direction {
|
||||
Down,
|
||||
}
|
||||
|
||||
/// Actions that can be bound to keys.
|
||||
#[derive(Clone)]
|
||||
pub enum Action {
|
||||
/// Quit Zellij.
|
||||
|
@ -1,7 +1,8 @@
|
||||
//! Main input logic.
|
||||
|
||||
use super::actions::Action;
|
||||
use super::keybinds::get_default_keybinds;
|
||||
use crate::common::{update_state, AppInstruction, AppState, SenderWithContext, OPENCALLS};
|
||||
/// Module for handling input
|
||||
use crate::errors::ContextType;
|
||||
use crate::os_input_output::OsApi;
|
||||
use crate::pty_bus::PtyInstruction;
|
||||
@ -16,8 +17,9 @@ use termion::input::TermReadEventsAndRaw;
|
||||
use super::keybinds::key_to_actions;
|
||||
|
||||
/// Handles the dispatching of [`Action`]s according to the current
|
||||
/// [`InputMode`], as well as changes to that mode.
|
||||
/// [`InputMode`], and keep tracks of the current [`InputMode`].
|
||||
struct InputHandler {
|
||||
/// The current input mode
|
||||
mode: InputMode,
|
||||
os_input: Box<dyn OsApi>,
|
||||
command_is_executing: CommandIsExecuting,
|
||||
@ -28,6 +30,7 @@ struct InputHandler {
|
||||
}
|
||||
|
||||
impl InputHandler {
|
||||
/// Returns a new [`InputHandler`] with the attributes specified as arguments.
|
||||
fn new(
|
||||
os_input: Box<dyn OsApi>,
|
||||
command_is_executing: CommandIsExecuting,
|
||||
@ -47,10 +50,9 @@ impl InputHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Main event loop. Interprets the terminal [`Event`](termion::event::Event)s
|
||||
/// as [`Action`]s according to the current [`InputMode`], and dispatches those
|
||||
/// actions.
|
||||
fn get_input(&mut self) {
|
||||
/// Main input event loop. Interprets the terminal [`Event`](termion::event::Event)s
|
||||
/// as [`Action`]s according to the current [`InputMode`], and dispatches those actions.
|
||||
fn handle_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);
|
||||
@ -99,6 +101,17 @@ impl InputHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Dispatches an [`Action`].
|
||||
///
|
||||
/// This function's body dictates what each [`Action`] actually does when
|
||||
/// dispatched.
|
||||
///
|
||||
/// # Return value
|
||||
/// Currently, this function returns a boolean that indicates whether
|
||||
/// [`Self::handle_input()`] should break after this action is dispatched.
|
||||
/// This is a temporary measure that is only necessary due to the way that the
|
||||
/// framework works, and shouldn't be necessary anymore once the test framework
|
||||
/// is revised. See [issue#183](https://github.com/zellij-org/zellij/issues/183).
|
||||
fn dispatch_action(&mut self, action: Action) -> bool {
|
||||
let mut should_break = false;
|
||||
|
||||
@ -220,7 +233,7 @@ impl InputHandler {
|
||||
}
|
||||
|
||||
/// Routine to be called when the input handler exits (at the moment this is the
|
||||
/// same as quitting zellij)
|
||||
/// same as quitting Zellij).
|
||||
fn exit(&mut self) {
|
||||
self.send_app_instructions
|
||||
.send(AppInstruction::Exit)
|
||||
@ -228,28 +241,29 @@ impl InputHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Dictates the input mode, which is the way that keystrokes will be interpreted:
|
||||
/// - Normal mode either writes characters to the terminal, or switches to Command mode
|
||||
/// using a particular key control
|
||||
/// - Command mode is a menu that allows choosing another mode, like Resize or Pane
|
||||
/// - Resize mode is for resizing the different panes already present
|
||||
/// - Pane mode is for creating and closing panes in different directions
|
||||
/// - Tab mode is for creating tabs and moving between them
|
||||
/// - Scroll mode is for scrolling up and down within a pane
|
||||
/// Describes the different input modes, which change the way that keystrokes will be interpreted.
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, EnumIter, Serialize, Deserialize)]
|
||||
pub enum InputMode {
|
||||
/// In `Normal` mode, input is always written to the terminal, except for one special input that
|
||||
/// triggers the switch to [`InputMode::Command`] mode.
|
||||
Normal,
|
||||
/// In `Command` mode, input is bound to actions (more precisely, sequences of actions).
|
||||
/// `Command` mode gives access to the other modes non-`InputMode::Normal` modes.
|
||||
/// etc.
|
||||
Command,
|
||||
/// `Resize` mode allows resizing the different existing panes.
|
||||
Resize,
|
||||
/// `Pane` mode allows creating and closing panes, as well as moving between them.
|
||||
Pane,
|
||||
/// `Tab` mode allows creating and closing tabs, as well as moving between them.
|
||||
Tab,
|
||||
/// `Scroll` mode allows scrolling up and down within a pane.
|
||||
Scroll,
|
||||
Exiting,
|
||||
}
|
||||
|
||||
/// Represents the help message that is printed in the status bar, indicating
|
||||
/// the current [`InputMode`], whether that mode is persistent, and what the
|
||||
/// keybinds for that mode are.
|
||||
/// Represents the contents of the help message that is printed in the status bar,
|
||||
/// which indicates the current [`InputMode`] and what the keybinds for that mode
|
||||
/// are. Related to the default `status-bar` plugin.
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Help {
|
||||
pub mode: InputMode,
|
||||
@ -262,12 +276,13 @@ impl Default for InputMode {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`Help`] struct holding the current [`InputMode`] and its keybinds.
|
||||
/// Creates a [`Help`] struct indicating the current [`InputMode`] and its keybinds
|
||||
/// (as pairs of [`String`]s).
|
||||
// TODO this should probably be automatically generated in some way
|
||||
pub fn get_help(mode: InputMode) -> Help {
|
||||
let mut keybinds: Vec<(String, String)> = vec![];
|
||||
match mode {
|
||||
InputMode::Normal | InputMode::Command | InputMode::Exiting => {
|
||||
InputMode::Normal | InputMode::Command => {
|
||||
keybinds.push((format!("p"), format!("PANE")));
|
||||
keybinds.push((format!("t"), format!("TAB")));
|
||||
keybinds.push((format!("r"), format!("RESIZE")));
|
||||
@ -299,8 +314,8 @@ pub fn get_help(mode: InputMode) -> Help {
|
||||
Help { mode, keybinds }
|
||||
}
|
||||
|
||||
/// Entry point to the module. Instantiates a new InputHandler and calls its
|
||||
/// input loop.
|
||||
/// Entry point to the module. Instantiates an [`InputHandler`] and starts
|
||||
/// its [`InputHandler::handle_input()`] loop.
|
||||
pub fn input_loop(
|
||||
os_input: Box<dyn OsApi>,
|
||||
command_is_executing: CommandIsExecuting,
|
||||
@ -317,5 +332,5 @@ pub fn input_loop(
|
||||
send_plugin_instructions,
|
||||
send_app_instructions,
|
||||
)
|
||||
.get_input();
|
||||
.handle_input();
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! Mapping of inputs to sequences of actions
|
||||
//! Mapping of inputs to sequences of actions.
|
||||
|
||||
use super::actions::{Action, Direction};
|
||||
use super::handler::InputMode;
|
||||
@ -152,7 +152,6 @@ fn get_defaults_for_mode(mode: &InputMode) -> Result<ModeKeybinds, String> {
|
||||
);
|
||||
defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Command)]);
|
||||
}
|
||||
InputMode::Exiting => {}
|
||||
}
|
||||
|
||||
Ok(defaults)
|
||||
|
@ -1,3 +1,5 @@
|
||||
//! The way terminal iput is handled.
|
||||
|
||||
pub mod actions;
|
||||
pub mod handler;
|
||||
pub mod keybinds;
|
||||
|
@ -1,4 +1,5 @@
|
||||
// IPC stuff for starting to split things into a client and server model
|
||||
//! IPC stuff for starting to split things into a client and server model.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
|
||||
|
@ -11,7 +11,7 @@ pub mod wasm_vm;
|
||||
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::mpsc::{channel, sync_channel, Receiver, SendError, Sender, SyncSender};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::{cell::RefCell, sync::mpsc::TrySendError};
|
||||
use std::{collections::HashMap, fs};
|
||||
@ -55,7 +55,7 @@ pub fn update_state(
|
||||
app_tx: &SenderWithContext<AppInstruction>,
|
||||
update_fn: impl FnOnce(AppState) -> AppState,
|
||||
) {
|
||||
let (state_tx, state_rx) = channel();
|
||||
let (state_tx, state_rx) = mpsc::channel();
|
||||
|
||||
drop(app_tx.send(AppInstruction::GetState(state_tx)));
|
||||
let state = state_rx.recv().unwrap();
|
||||
@ -63,15 +63,28 @@ pub fn update_state(
|
||||
drop(app_tx.send(AppInstruction::SetState(update_fn(state))))
|
||||
}
|
||||
|
||||
pub type ChannelWithContext<T> = (Sender<(T, ErrorContext)>, Receiver<(T, ErrorContext)>);
|
||||
pub type SyncChannelWithContext<T> = (SyncSender<(T, ErrorContext)>, Receiver<(T, ErrorContext)>);
|
||||
/// An [MPSC](mpsc) asynchronous channel with added error context.
|
||||
pub type ChannelWithContext<T> = (
|
||||
mpsc::Sender<(T, ErrorContext)>,
|
||||
mpsc::Receiver<(T, ErrorContext)>,
|
||||
);
|
||||
/// An [MPSC](mpsc) synchronous channel with added error context.
|
||||
pub type SyncChannelWithContext<T> = (
|
||||
mpsc::SyncSender<(T, ErrorContext)>,
|
||||
mpsc::Receiver<(T, ErrorContext)>,
|
||||
);
|
||||
|
||||
/// Wrappers around the two standard [MPSC](mpsc) sender types, [`mpsc::Sender`] and [`mpsc::SyncSender`], with an additional [`ErrorContext`].
|
||||
#[derive(Clone)]
|
||||
enum SenderType<T: Clone> {
|
||||
Sender(Sender<(T, ErrorContext)>),
|
||||
SyncSender(SyncSender<(T, ErrorContext)>),
|
||||
/// A wrapper around an [`mpsc::Sender`], adding an [`ErrorContext`].
|
||||
Sender(mpsc::Sender<(T, ErrorContext)>),
|
||||
/// A wrapper around an [`mpsc::SyncSender`], adding an [`ErrorContext`].
|
||||
SyncSender(mpsc::SyncSender<(T, ErrorContext)>),
|
||||
}
|
||||
|
||||
/// Sends messages on an [MPSC](std::sync::mpsc) channel, along with an [`ErrorContext`],
|
||||
/// synchronously or asynchronously depending on the underlying [`SenderType`].
|
||||
#[derive(Clone)]
|
||||
pub struct SenderWithContext<T: Clone> {
|
||||
err_ctx: ErrorContext,
|
||||
@ -83,13 +96,20 @@ impl<T: Clone> SenderWithContext<T> {
|
||||
Self { err_ctx, sender }
|
||||
}
|
||||
|
||||
pub fn send(&self, event: T) -> Result<(), SendError<(T, ErrorContext)>> {
|
||||
/// Sends an event, along with the current [`ErrorContext`], on this
|
||||
/// [`SenderWithContext`]'s channel.
|
||||
pub fn send(&self, event: T) -> Result<(), mpsc::SendError<(T, ErrorContext)>> {
|
||||
match self.sender {
|
||||
SenderType::Sender(ref s) => s.send((event, self.err_ctx)),
|
||||
SenderType::SyncSender(ref s) => s.send((event, self.err_ctx)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to send an event on this sender's channel, terminating instead of blocking
|
||||
/// if the event could not be sent (buffer full or connection closed).
|
||||
///
|
||||
/// This can only be called on [`SyncSender`](SenderType::SyncSender)s, and will
|
||||
/// panic if called on an asynchronous [`Sender`](SenderType::Sender).
|
||||
pub fn try_send(&self, event: T) -> Result<(), TrySendError<(T, ErrorContext)>> {
|
||||
if let SenderType::SyncSender(ref s) = self.sender {
|
||||
s.try_send((event, self.err_ctx))
|
||||
@ -98,6 +118,11 @@ impl<T: Clone> SenderWithContext<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates this [`SenderWithContext`]'s [`ErrorContext`]. This is the way one adds
|
||||
/// a call to the error context.
|
||||
///
|
||||
/// Updating [`ErrorContext`]s works in this way so that these contexts are only ever
|
||||
/// allocated on the stack (which is thread-specific), and not on the heap.
|
||||
pub fn update(&mut self, new_ctx: ErrorContext) {
|
||||
self.err_ctx = new_ctx;
|
||||
}
|
||||
@ -106,16 +131,23 @@ impl<T: Clone> SenderWithContext<T> {
|
||||
unsafe impl<T: Clone> Send for SenderWithContext<T> {}
|
||||
unsafe impl<T: Clone> Sync for SenderWithContext<T> {}
|
||||
|
||||
thread_local!(static OPENCALLS: RefCell<ErrorContext> = RefCell::default());
|
||||
thread_local!(
|
||||
/// A key to some thread local storage (TLS) that holds a representation of the thread's call
|
||||
/// stack in the form of an [`ErrorContext`].
|
||||
static OPENCALLS: RefCell<ErrorContext> = RefCell::default()
|
||||
);
|
||||
|
||||
/// Instructions related to the entire application.
|
||||
#[derive(Clone)]
|
||||
pub enum AppInstruction {
|
||||
GetState(Sender<AppState>),
|
||||
GetState(mpsc::Sender<AppState>),
|
||||
SetState(AppState),
|
||||
Exit,
|
||||
Error(String),
|
||||
}
|
||||
|
||||
/// Start Zellij with the specified [`OsApi`] and command-line arguments.
|
||||
// FIXME this should definitely be modularized and split into different functions.
|
||||
pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
|
||||
let take_snapshot = "\u{1b}[?1049h";
|
||||
os_input.unset_raw_mode(0);
|
||||
@ -131,24 +163,24 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
|
||||
os_input.set_raw_mode(0);
|
||||
let (send_screen_instructions, receive_screen_instructions): ChannelWithContext<
|
||||
ScreenInstruction,
|
||||
> = channel();
|
||||
> = mpsc::channel();
|
||||
let err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
|
||||
let mut send_screen_instructions =
|
||||
SenderWithContext::new(err_ctx, SenderType::Sender(send_screen_instructions));
|
||||
|
||||
let (send_pty_instructions, receive_pty_instructions): ChannelWithContext<PtyInstruction> =
|
||||
channel();
|
||||
mpsc::channel();
|
||||
let mut send_pty_instructions =
|
||||
SenderWithContext::new(err_ctx, SenderType::Sender(send_pty_instructions));
|
||||
|
||||
let (send_plugin_instructions, receive_plugin_instructions): ChannelWithContext<
|
||||
PluginInstruction,
|
||||
> = channel();
|
||||
> = mpsc::channel();
|
||||
let send_plugin_instructions =
|
||||
SenderWithContext::new(err_ctx, SenderType::Sender(send_plugin_instructions));
|
||||
|
||||
let (send_app_instructions, receive_app_instructions): SyncChannelWithContext<AppInstruction> =
|
||||
sync_channel(0);
|
||||
mpsc::sync_channel(0);
|
||||
let send_app_instructions =
|
||||
SenderWithContext::new(err_ctx, SenderType::SyncSender(send_app_instructions));
|
||||
|
||||
|
@ -2,11 +2,12 @@ use crate::panes::PositionAndSize;
|
||||
use nix::fcntl::{fcntl, FcntlArg, OFlag};
|
||||
use nix::pty::{forkpty, Winsize};
|
||||
use nix::sys::signal::{kill, Signal};
|
||||
use nix::sys::termios::{cfmakeraw, tcdrain, tcgetattr, tcsetattr, SetArg, Termios};
|
||||
use nix::sys::termios;
|
||||
use nix::sys::wait::waitpid;
|
||||
use nix::unistd::{read, write, ForkResult, Pid};
|
||||
use nix::unistd;
|
||||
use nix::unistd::{ForkResult, Pid};
|
||||
use std::io;
|
||||
use std::io::prelude::*;
|
||||
use std::io::{stdin, Write};
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Child, Command};
|
||||
@ -15,16 +16,16 @@ use std::sync::{Arc, Mutex};
|
||||
use std::env;
|
||||
|
||||
fn into_raw_mode(pid: RawFd) {
|
||||
let mut tio = tcgetattr(pid).expect("could not get terminal attribute");
|
||||
cfmakeraw(&mut tio);
|
||||
match tcsetattr(pid, SetArg::TCSANOW, &tio) {
|
||||
let mut tio = termios::tcgetattr(pid).expect("could not get terminal attribute");
|
||||
termios::cfmakeraw(&mut tio);
|
||||
match termios::tcsetattr(pid, termios::SetArg::TCSANOW, &tio) {
|
||||
Ok(_) => {}
|
||||
Err(e) => panic!("error {:?}", e),
|
||||
};
|
||||
}
|
||||
|
||||
fn unset_raw_mode(pid: RawFd, orig_termios: Termios) {
|
||||
match tcsetattr(pid, SetArg::TCSANOW, &orig_termios) {
|
||||
fn unset_raw_mode(pid: RawFd, orig_termios: termios::Termios) {
|
||||
match termios::tcsetattr(pid, termios::SetArg::TCSANOW, &orig_termios) {
|
||||
Ok(_) => {}
|
||||
Err(e) => panic!("error {:?}", e),
|
||||
};
|
||||
@ -60,13 +61,19 @@ pub fn set_terminal_size_using_fd(fd: RawFd, columns: u16, rows: u16) {
|
||||
unsafe { ioctl(fd, TIOCSWINSZ, &winsize) };
|
||||
}
|
||||
|
||||
/// Handle some signals for the child process. This will loop until the child
|
||||
/// process exits.
|
||||
fn handle_command_exit(mut child: Child) {
|
||||
// register the SIGINT signal (TODO handle more signals)
|
||||
let signals = ::signal_hook::iterator::Signals::new(&[::signal_hook::SIGINT]).unwrap();
|
||||
'handle_exit: loop {
|
||||
// test whether the child process has exited
|
||||
match child.try_wait() {
|
||||
Ok(Some(_status)) => {
|
||||
// if the child process has exited, break outside of the loop
|
||||
// and exit this function
|
||||
// TODO: handle errors?
|
||||
break;
|
||||
break 'handle_exit;
|
||||
}
|
||||
Ok(None) => {
|
||||
::std::thread::sleep(::std::time::Duration::from_millis(100));
|
||||
@ -88,7 +95,21 @@ fn handle_command_exit(mut child: Child) {
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_terminal(file_to_open: Option<PathBuf>, orig_termios: Termios) -> (RawFd, RawFd) {
|
||||
/// Spawns a new terminal from the parent terminal with [`termios`](termios::Termios)
|
||||
/// `orig_termios`.
|
||||
///
|
||||
/// If a `file_to_open` is given, the text editor specified by environment variable `EDITOR`
|
||||
/// (or `VISUAL`, if `EDITOR` is not set) will be started in the new terminal, with the given
|
||||
/// file open. If no file is given, the shell specified by environment variable `SHELL` will
|
||||
/// be started in the new terminal.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if both the `EDITOR` and `VISUAL` environment variables are not
|
||||
/// set.
|
||||
// FIXME this should probably be split into different functions, or at least have less levels
|
||||
// of indentation in some way
|
||||
fn spawn_terminal(file_to_open: Option<PathBuf>, orig_termios: termios::Termios) -> (RawFd, RawFd) {
|
||||
let (pid_primary, pid_secondary): (RawFd, RawFd) = {
|
||||
match forkpty(None, Some(&orig_termios)) {
|
||||
Ok(fork_pty_res) => {
|
||||
@ -136,56 +157,75 @@ fn spawn_terminal(file_to_open: Option<PathBuf>, orig_termios: Termios) -> (RawF
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OsInputOutput {
|
||||
orig_termios: Arc<Mutex<Termios>>,
|
||||
orig_termios: Arc<Mutex<termios::Termios>>,
|
||||
}
|
||||
|
||||
/// The `OsApi` trait represents an abstract interface to the features of an operating system that
|
||||
/// Zellij requires.
|
||||
pub trait OsApi: Send + Sync {
|
||||
fn get_terminal_size_using_fd(&self, pid: RawFd) -> PositionAndSize;
|
||||
fn set_terminal_size_using_fd(&mut self, pid: RawFd, cols: u16, rows: u16);
|
||||
fn set_raw_mode(&mut self, pid: RawFd);
|
||||
fn unset_raw_mode(&mut self, pid: RawFd);
|
||||
/// Returns the size of the terminal associated to file descriptor `fd`.
|
||||
fn get_terminal_size_using_fd(&self, fd: RawFd) -> PositionAndSize;
|
||||
/// Sets the size of the terminal associated to file descriptor `fd`.
|
||||
fn set_terminal_size_using_fd(&mut self, fd: RawFd, cols: u16, rows: u16);
|
||||
/// Set the terminal associated to file descriptor `fd` to
|
||||
/// [raw mode](https://en.wikipedia.org/wiki/Terminal_mode).
|
||||
fn set_raw_mode(&mut self, fd: RawFd);
|
||||
/// Set the terminal associated to file descriptor `fd` to
|
||||
/// [cooked mode](https://en.wikipedia.org/wiki/Terminal_mode).
|
||||
fn unset_raw_mode(&mut self, fd: RawFd);
|
||||
/// Spawn a new terminal, with an optional file to open in a terminal program.
|
||||
fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> (RawFd, RawFd);
|
||||
fn read_from_tty_stdout(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>;
|
||||
fn write_to_tty_stdin(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>;
|
||||
fn tcdrain(&mut self, pid: RawFd) -> Result<(), nix::Error>;
|
||||
/// Read bytes from the standard output of the virtual terminal referred to by `fd`.
|
||||
fn read_from_tty_stdout(&mut self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>;
|
||||
/// Write bytes to the standard input of the virtual terminal referred to by `fd`.
|
||||
fn write_to_tty_stdin(&mut self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>;
|
||||
/// Wait until all output written to the object referred to by `fd` has been transmitted.
|
||||
fn tcdrain(&mut self, fd: RawFd) -> Result<(), nix::Error>;
|
||||
/// Terminate the process with process ID `pid`.
|
||||
// FIXME `RawFd` is semantically the wrong type here. It should either be a raw libc::pid_t,
|
||||
// or a nix::unistd::Pid. See `man kill.3`, nix::sys::signal::kill (both take an argument
|
||||
// called `pid` and of type `pid_t`, and not `fd`)
|
||||
fn kill(&mut self, pid: RawFd) -> Result<(), nix::Error>;
|
||||
/// Returns the raw contents of standard input.
|
||||
fn read_from_stdin(&self) -> Vec<u8>;
|
||||
fn get_stdout_writer(&self) -> Box<dyn Write>;
|
||||
/// Returns the writer that allows writing to standard output.
|
||||
fn get_stdout_writer(&self) -> Box<dyn io::Write>;
|
||||
/// Returns a [`Box`] pointer to this [`OsApi`] struct.
|
||||
fn box_clone(&self) -> Box<dyn OsApi>;
|
||||
}
|
||||
|
||||
impl OsApi for OsInputOutput {
|
||||
fn get_terminal_size_using_fd(&self, pid: RawFd) -> PositionAndSize {
|
||||
get_terminal_size_using_fd(pid)
|
||||
fn get_terminal_size_using_fd(&self, fd: RawFd) -> PositionAndSize {
|
||||
get_terminal_size_using_fd(fd)
|
||||
}
|
||||
fn set_terminal_size_using_fd(&mut self, pid: RawFd, cols: u16, rows: u16) {
|
||||
set_terminal_size_using_fd(pid, cols, rows);
|
||||
fn set_terminal_size_using_fd(&mut self, fd: RawFd, cols: u16, rows: u16) {
|
||||
set_terminal_size_using_fd(fd, cols, rows);
|
||||
}
|
||||
fn set_raw_mode(&mut self, pid: RawFd) {
|
||||
into_raw_mode(pid);
|
||||
fn set_raw_mode(&mut self, fd: RawFd) {
|
||||
into_raw_mode(fd);
|
||||
}
|
||||
fn unset_raw_mode(&mut self, pid: RawFd) {
|
||||
fn unset_raw_mode(&mut self, fd: RawFd) {
|
||||
let orig_termios = self.orig_termios.lock().unwrap();
|
||||
unset_raw_mode(pid, orig_termios.clone());
|
||||
unset_raw_mode(fd, orig_termios.clone());
|
||||
}
|
||||
fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> (RawFd, RawFd) {
|
||||
let orig_termios = self.orig_termios.lock().unwrap();
|
||||
spawn_terminal(file_to_open, orig_termios.clone())
|
||||
}
|
||||
fn read_from_tty_stdout(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
|
||||
read(pid, buf)
|
||||
fn read_from_tty_stdout(&mut self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
|
||||
unistd::read(fd, buf)
|
||||
}
|
||||
fn write_to_tty_stdin(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
|
||||
write(pid, buf)
|
||||
fn write_to_tty_stdin(&mut self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
|
||||
unistd::write(fd, buf)
|
||||
}
|
||||
fn tcdrain(&mut self, pid: RawFd) -> Result<(), nix::Error> {
|
||||
tcdrain(pid)
|
||||
fn tcdrain(&mut self, fd: RawFd) -> Result<(), nix::Error> {
|
||||
termios::tcdrain(fd)
|
||||
}
|
||||
fn box_clone(&self) -> Box<dyn OsApi> {
|
||||
Box::new((*self).clone())
|
||||
}
|
||||
fn read_from_stdin(&self) -> Vec<u8> {
|
||||
let stdin = stdin();
|
||||
let stdin = std::io::stdin();
|
||||
let mut stdin = stdin.lock();
|
||||
let buffer = stdin.fill_buf().unwrap();
|
||||
let length = buffer.len();
|
||||
@ -193,13 +233,13 @@ impl OsApi for OsInputOutput {
|
||||
stdin.consume(length);
|
||||
read_bytes
|
||||
}
|
||||
fn get_stdout_writer(&self) -> Box<dyn Write> {
|
||||
fn get_stdout_writer(&self) -> Box<dyn io::Write> {
|
||||
let stdout = ::std::io::stdout();
|
||||
Box::new(stdout)
|
||||
}
|
||||
fn kill(&mut self, fd: RawFd) -> Result<(), nix::Error> {
|
||||
kill(Pid::from_raw(fd), Some(Signal::SIGINT)).unwrap();
|
||||
waitpid(Pid::from_raw(fd), None).unwrap();
|
||||
fn kill(&mut self, pid: RawFd) -> Result<(), nix::Error> {
|
||||
kill(Pid::from_raw(pid), Some(Signal::SIGINT)).unwrap();
|
||||
waitpid(Pid::from_raw(pid), None).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -211,7 +251,7 @@ impl Clone for Box<dyn OsApi> {
|
||||
}
|
||||
|
||||
pub fn get_os_input() -> OsInputOutput {
|
||||
let current_termios = tcgetattr(0).unwrap();
|
||||
let current_termios = termios::tcgetattr(0).unwrap();
|
||||
let orig_termios = Arc::new(Mutex::new(current_termios));
|
||||
OsInputOutput { orig_termios }
|
||||
}
|
||||
|
@ -145,6 +145,7 @@ impl vte::Perform for VteEventSender {
|
||||
}
|
||||
}
|
||||
|
||||
/// Instructions related to PTYs (pseudoterminals).
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PtyInstruction {
|
||||
SpawnTerminal(Option<PathBuf>),
|
||||
|
@ -1,3 +1,5 @@
|
||||
//! Things related to [`Screen`]s.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::sync::mpsc::Receiver;
|
||||
@ -10,15 +12,7 @@ use crate::tab::Tab;
|
||||
use crate::{errors::ErrorContext, wasm_vm::PluginInstruction};
|
||||
use crate::{layout::Layout, panes::PaneId};
|
||||
|
||||
/*
|
||||
* Screen
|
||||
*
|
||||
* this holds multiple tabs, each one holding multiple panes
|
||||
* it tracks the active tab and controls tab switching, all the rest
|
||||
* is performed in Tab
|
||||
*
|
||||
*/
|
||||
|
||||
/// Instructions that can be sent to the [`Screen`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ScreenInstruction {
|
||||
Pty(RawFd, VteEvent),
|
||||
@ -53,19 +47,31 @@ pub enum ScreenInstruction {
|
||||
CloseTab,
|
||||
}
|
||||
|
||||
/// A [`Screen`] holds multiple [`Tab`]s, each one holding multiple [`panes`](crate::client::panes).
|
||||
/// It only directly controls which tab is active, delegating the rest to the individual `Tab`.
|
||||
pub struct Screen {
|
||||
/// A [`ScreenInstruction`] and [`ErrorContext`] receiver.
|
||||
pub receiver: Receiver<(ScreenInstruction, ErrorContext)>,
|
||||
/// An optional maximal amount of panes allowed per [`Tab`] in this [`Screen`] instance.
|
||||
max_panes: Option<usize>,
|
||||
/// A map between this [`Screen`]'s tabs and their ID/key.
|
||||
tabs: BTreeMap<usize, Tab>,
|
||||
/// A [`PtyInstruction`] and [`ErrorContext`] sender.
|
||||
pub send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||
/// A [`PluginInstruction`] and [`ErrorContext`] sender.
|
||||
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||
/// An [`AppInstruction`] and [`ErrorContext`] sender.
|
||||
pub send_app_instructions: SenderWithContext<AppInstruction>,
|
||||
/// The full size of this [`Screen`].
|
||||
full_screen_ws: PositionAndSize,
|
||||
/// The index of this [`Screen`]'s active [`Tab`].
|
||||
active_tab_index: Option<usize>,
|
||||
/// The [`OsApi`] this [`Screen`] uses.
|
||||
os_api: Box<dyn OsApi>,
|
||||
}
|
||||
|
||||
impl Screen {
|
||||
/// Creates and returns a new [`Screen`].
|
||||
pub fn new(
|
||||
receive_screen_instructions: Receiver<(ScreenInstruction, ErrorContext)>,
|
||||
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||
@ -87,8 +93,11 @@ impl Screen {
|
||||
os_api,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`Tab`] in this [`Screen`], containing a single
|
||||
/// [pane](crate::client::panes) with PTY file descriptor `pane_id`.
|
||||
pub fn new_tab(&mut self, pane_id: RawFd) {
|
||||
let tab_index = self.get_next_tab_index();
|
||||
let tab_index = self.get_new_tab_index();
|
||||
let tab = Tab::new(
|
||||
tab_index,
|
||||
&self.full_screen_ws,
|
||||
@ -103,13 +112,19 @@ impl Screen {
|
||||
self.tabs.insert(tab_index, tab);
|
||||
self.render();
|
||||
}
|
||||
fn get_next_tab_index(&self) -> usize {
|
||||
|
||||
/// Returns the index where a new [`Tab`] should be created in this [`Screen`].
|
||||
/// Currently, this is right after the last currently existing tab, or `0` if
|
||||
/// no tabs exist in this screen yet.
|
||||
fn get_new_tab_index(&self) -> usize {
|
||||
if let Some(index) = self.tabs.keys().last() {
|
||||
*index + 1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets this [`Screen`]'s active [`Tab`] to the next tab.
|
||||
pub fn switch_tab_next(&mut self) {
|
||||
let active_tab_id = self.get_active_tab().unwrap().index;
|
||||
let tab_ids: Vec<usize> = self.tabs.keys().copied().collect();
|
||||
@ -122,6 +137,8 @@ impl Screen {
|
||||
}
|
||||
self.render();
|
||||
}
|
||||
|
||||
/// Sets this [`Screen`]'s active [`Tab`] to the previous tab.
|
||||
pub fn switch_tab_prev(&mut self) {
|
||||
let active_tab_id = self.get_active_tab().unwrap().index;
|
||||
let tab_ids: Vec<usize> = self.tabs.keys().copied().collect();
|
||||
@ -136,6 +153,9 @@ impl Screen {
|
||||
}
|
||||
self.render();
|
||||
}
|
||||
|
||||
/// Closes this [`Screen`]'s active [`Tab`], exiting the application if it happens
|
||||
/// to be the last tab.
|
||||
pub fn close_tab(&mut self) {
|
||||
let active_tab_index = self.active_tab_index.unwrap();
|
||||
if self.tabs.len() > 1 {
|
||||
@ -156,6 +176,8 @@ impl Screen {
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders this [`Screen`], which amounts to rendering its active [`Tab`].
|
||||
pub fn render(&mut self) {
|
||||
if let Some(active_tab) = self.get_active_tab_mut() {
|
||||
if active_tab.get_active_pane().is_some() {
|
||||
@ -166,24 +188,31 @@ impl Screen {
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to this [`Screen`]'s tabs.
|
||||
pub fn get_tabs_mut(&mut self) -> &mut BTreeMap<usize, Tab> {
|
||||
&mut self.tabs
|
||||
}
|
||||
|
||||
/// Returns an immutable reference to this [`Screen`]'s active [`Tab`].
|
||||
pub fn get_active_tab(&self) -> Option<&Tab> {
|
||||
match self.active_tab_index {
|
||||
Some(tab) => self.tabs.get(&tab),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
pub fn get_tabs_mut(&mut self) -> &mut BTreeMap<usize, Tab> {
|
||||
&mut self.tabs
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to this [`Screen`]'s active [`Tab`].
|
||||
pub fn get_active_tab_mut(&mut self) -> Option<&mut Tab> {
|
||||
let tab = match self.active_tab_index {
|
||||
match self.active_tab_index {
|
||||
Some(tab) => self.get_tabs_mut().get_mut(&tab),
|
||||
None => None,
|
||||
};
|
||||
tab
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`Tab`] in this [`Screen`], applying the specified [`Layout`]
|
||||
/// and switching to it.
|
||||
pub fn apply_layout(&mut self, layout: Layout, new_pids: Vec<RawFd>) {
|
||||
let tab_index = self.get_next_tab_index();
|
||||
let tab_index = self.get_new_tab_index();
|
||||
let mut tab = Tab::new(
|
||||
tab_index,
|
||||
&self.full_screen_ws,
|
||||
|
@ -1,3 +1,5 @@
|
||||
//! Zellij program-wide constants.
|
||||
|
||||
pub const ZELLIJ_TMP_DIR: &str = "/tmp/zellij";
|
||||
pub const ZELLIJ_TMP_LOG_DIR: &str = "/tmp/zellij/zellij-log";
|
||||
pub const ZELLIJ_TMP_LOG_FILE: &str = "/tmp/zellij/zellij-log/log.txt";
|
||||
|
@ -1,3 +1,5 @@
|
||||
//! Zellij logging utility functions.
|
||||
|
||||
use std::{
|
||||
fs,
|
||||
io::{self, prelude::*},
|
||||
|
@ -1,3 +1,5 @@
|
||||
//! Zellij utilities.
|
||||
|
||||
pub mod consts;
|
||||
pub mod logging;
|
||||
pub mod shared;
|
||||
|
@ -1,8 +1,10 @@
|
||||
//! Some general utility functions.
|
||||
|
||||
use std::{iter, str::from_utf8};
|
||||
|
||||
use strip_ansi_escapes::strip;
|
||||
|
||||
pub fn ansi_len(s: &str) -> usize {
|
||||
fn ansi_len(s: &str) -> usize {
|
||||
from_utf8(&strip(s.as_bytes()).unwrap())
|
||||
.unwrap()
|
||||
.chars()
|
||||
|
Loading…
Reference in New Issue
Block a user