From 6e5607ba26d8c1daa384d9eb8c6e138b68360d02 Mon Sep 17 00:00:00 2001 From: categorille <69397914+categorille@users.noreply.github.com> Date: Wed, 17 Feb 2021 15:55:09 +0100 Subject: [PATCH] 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 --- .gitignore | 1 + src/client/panes/terminal_pane.rs | 3 + src/client/tab.rs | 11 +-- src/common/errors.rs | 27 ++++++- src/common/input/actions.rs | 7 +- src/common/input/handler.rs | 63 ++++++++++------ src/common/input/keybinds.rs | 3 +- src/common/input/mod.rs | 2 + src/common/ipc.rs | 3 +- src/common/mod.rs | 58 +++++++++++---- src/common/os_input_output.rs | 118 ++++++++++++++++++++---------- src/common/pty_bus.rs | 1 + src/common/screen.rs | 65 +++++++++++----- src/common/utils/consts.rs | 2 + src/common/utils/logging.rs | 2 + src/common/utils/mod.rs | 2 + src/common/utils/shared.rs | 4 +- 17 files changed, 261 insertions(+), 111 deletions(-) diff --git a/.gitignore b/.gitignore index 4c3bd04c5..a5d0f5fc4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target *.new .vscode +.vim diff --git a/src/client/panes/terminal_pane.rs b/src/client/panes/terminal_pane.rs index 21244fab1..e00b323d2 100644 --- a/src/client/panes/terminal_pane.rs +++ b/src/client/panes/terminal_pane.rs @@ -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, diff --git a/src/client/tab.rs b/src/client/tab.rs index 3b508a824..174948c1b 100644 --- a/src/client/tab.rs +++ b/src/client/tab.rs @@ -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; diff --git a/src/common/errors.rs b/src/common/errors.rs index 27fe31039..3d105dbca 100644 --- a/src/common/errors.rs +++ b/src/common/errors.rs @@ -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, diff --git a/src/common/input/actions.rs b/src/common/input/actions.rs index ae212e323..7193b46ef 100644 --- a/src/common/input/actions.rs +++ b/src/common/input/actions.rs @@ -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. diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs index 74235ff7f..14e7876a5 100644 --- a/src/common/input/handler.rs +++ b/src/common/input/handler.rs @@ -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, 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, 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, command_is_executing: CommandIsExecuting, @@ -317,5 +332,5 @@ pub fn input_loop( send_plugin_instructions, send_app_instructions, ) - .get_input(); + .handle_input(); } diff --git a/src/common/input/keybinds.rs b/src/common/input/keybinds.rs index 2e65ab352..7197d2de0 100644 --- a/src/common/input/keybinds.rs +++ b/src/common/input/keybinds.rs @@ -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 { ); defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Command)]); } - InputMode::Exiting => {} } Ok(defaults) diff --git a/src/common/input/mod.rs b/src/common/input/mod.rs index 094054e84..20c30daec 100644 --- a/src/common/input/mod.rs +++ b/src/common/input/mod.rs @@ -1,3 +1,5 @@ +//! The way terminal iput is handled. + pub mod actions; pub mod handler; pub mod keybinds; diff --git a/src/common/ipc.rs b/src/common/ipc.rs index 3fa8c6b29..77d57df3c 100644 --- a/src/common/ipc.rs +++ b/src/common/ipc.rs @@ -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; diff --git a/src/common/mod.rs b/src/common/mod.rs index d1133e271..8f37994f5 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -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, 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 = (Sender<(T, ErrorContext)>, Receiver<(T, ErrorContext)>); -pub type SyncChannelWithContext = (SyncSender<(T, ErrorContext)>, Receiver<(T, ErrorContext)>); +/// An [MPSC](mpsc) asynchronous channel with added error context. +pub type ChannelWithContext = ( + mpsc::Sender<(T, ErrorContext)>, + mpsc::Receiver<(T, ErrorContext)>, +); +/// An [MPSC](mpsc) synchronous channel with added error context. +pub type SyncChannelWithContext = ( + 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 { - 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 { err_ctx: ErrorContext, @@ -83,13 +96,20 @@ impl SenderWithContext { 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 SenderWithContext { } } + /// 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 SenderWithContext { unsafe impl Send for SenderWithContext {} unsafe impl Sync for SenderWithContext {} -thread_local!(static OPENCALLS: RefCell = 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 = RefCell::default() +); +/// Instructions related to the entire application. #[derive(Clone)] pub enum AppInstruction { - GetState(Sender), + GetState(mpsc::Sender), 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, 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, 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 = - 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 = - sync_channel(0); + mpsc::sync_channel(0); let send_app_instructions = SenderWithContext::new(err_ctx, SenderType::SyncSender(send_app_instructions)); diff --git a/src/common/os_input_output.rs b/src/common/os_input_output.rs index 41ee3b3c8..132ff46dc 100644 --- a/src/common/os_input_output.rs +++ b/src/common/os_input_output.rs @@ -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, 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, 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, orig_termios: Termios) -> (RawF #[derive(Clone)] pub struct OsInputOutput { - orig_termios: Arc>, + orig_termios: Arc>, } +/// 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) -> (RawFd, RawFd); - fn read_from_tty_stdout(&mut self, pid: RawFd, buf: &mut [u8]) -> Result; - fn write_to_tty_stdin(&mut self, pid: RawFd, buf: &mut [u8]) -> Result; - 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; + /// 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; + /// 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; - fn get_stdout_writer(&self) -> Box; + /// Returns the writer that allows writing to standard output. + fn get_stdout_writer(&self) -> Box; + /// Returns a [`Box`] pointer to this [`OsApi`] struct. fn box_clone(&self) -> Box; } 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) -> (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 { - read(pid, buf) + fn read_from_tty_stdout(&mut self, fd: RawFd, buf: &mut [u8]) -> Result { + unistd::read(fd, buf) } - fn write_to_tty_stdin(&mut self, pid: RawFd, buf: &mut [u8]) -> Result { - write(pid, buf) + fn write_to_tty_stdin(&mut self, fd: RawFd, buf: &mut [u8]) -> Result { + 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 { Box::new((*self).clone()) } fn read_from_stdin(&self) -> Vec { - 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 { + fn get_stdout_writer(&self) -> Box { 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 { } 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 } } diff --git a/src/common/pty_bus.rs b/src/common/pty_bus.rs index 9a00d1de7..7e8542570 100644 --- a/src/common/pty_bus.rs +++ b/src/common/pty_bus.rs @@ -145,6 +145,7 @@ impl vte::Perform for VteEventSender { } } +/// Instructions related to PTYs (pseudoterminals). #[derive(Clone, Debug)] pub enum PtyInstruction { SpawnTerminal(Option), diff --git a/src/common/screen.rs b/src/common/screen.rs index 6ae89ddc4..1e04e231d 100644 --- a/src/common/screen.rs +++ b/src/common/screen.rs @@ -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, + /// A map between this [`Screen`]'s tabs and their ID/key. tabs: BTreeMap, + /// A [`PtyInstruction`] and [`ErrorContext`] sender. pub send_pty_instructions: SenderWithContext, + /// A [`PluginInstruction`] and [`ErrorContext`] sender. pub send_plugin_instructions: SenderWithContext, + /// An [`AppInstruction`] and [`ErrorContext`] sender. pub send_app_instructions: SenderWithContext, + /// The full size of this [`Screen`]. full_screen_ws: PositionAndSize, + /// The index of this [`Screen`]'s active [`Tab`]. active_tab_index: Option, + /// The [`OsApi`] this [`Screen`] uses. os_api: Box, } impl Screen { + /// Creates and returns a new [`Screen`]. pub fn new( receive_screen_instructions: Receiver<(ScreenInstruction, ErrorContext)>, send_pty_instructions: SenderWithContext, @@ -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 = 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 = 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 { + &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 { - &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) { - 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, diff --git a/src/common/utils/consts.rs b/src/common/utils/consts.rs index c4a75158e..fa9eee0de 100644 --- a/src/common/utils/consts.rs +++ b/src/common/utils/consts.rs @@ -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"; diff --git a/src/common/utils/logging.rs b/src/common/utils/logging.rs index c568cd567..d08639aad 100644 --- a/src/common/utils/logging.rs +++ b/src/common/utils/logging.rs @@ -1,3 +1,5 @@ +//! Zellij logging utility functions. + use std::{ fs, io::{self, prelude::*}, diff --git a/src/common/utils/mod.rs b/src/common/utils/mod.rs index 0ea4c76ff..1cb4df275 100644 --- a/src/common/utils/mod.rs +++ b/src/common/utils/mod.rs @@ -1,3 +1,5 @@ +//! Zellij utilities. + pub mod consts; pub mod logging; pub mod shared; diff --git a/src/common/utils/shared.rs b/src/common/utils/shared.rs index 1d8ca5c34..9717837a0 100644 --- a/src/common/utils/shared.rs +++ b/src/common/utils/shared.rs @@ -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()