summaryrefslogtreecommitdiffstats
path: root/zellij-utils/src/errors.rs
diff options
context:
space:
mode:
authorKunal Mohan <kunalmohan99@gmail.com>2021-05-16 16:42:50 +0530
committerKunal Mohan <kunalmohan99@gmail.com>2021-05-16 21:41:56 +0530
commit2038947a14d32d89da2bdbcaa1381c3015ac9c7a (patch)
tree5a332470e32b380816098594a89d3c3db1f6eb53 /zellij-utils/src/errors.rs
parent28212f54309d09263c6daa1801d49e8b4311be12 (diff)
Big refactor: separate crates for client, server and utilities
Diffstat (limited to 'zellij-utils/src/errors.rs')
-rw-r--r--zellij-utils/src/errors.rs263
1 files changed, 263 insertions, 0 deletions
diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs
new file mode 100644
index 000000000..9d26986c5
--- /dev/null
+++ b/zellij-utils/src/errors.rs
@@ -0,0 +1,263 @@
+//! Error context system based on a thread-local representation of the call stack, itself based on
+//! the instructions that are sent between threads.
+
+use crate::channels::{ASYNCOPENCALLS, OPENCALLS, SenderWithContext};
+use serde::{Deserialize, Serialize};
+use std::fmt::{Display, Error, Formatter};
+use std::panic::PanicInfo;
+
+/// 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;
+
+pub trait ErrorInstruction {
+ fn error(err: String) -> Self;
+}
+
+/// Custom panic handler/hook. Prints the [`ErrorContext`].
+pub fn handle_panic<T>(info: &PanicInfo<'_>, sender: &SenderWithContext<T>)
+where
+ T: ErrorInstruction + Clone,
+{
+ use backtrace::Backtrace;
+ use std::{process, thread};
+ let backtrace = Backtrace::new();
+ let thread = thread::current();
+ let thread = thread.name().unwrap_or("unnamed");
+
+ let msg = match info.payload().downcast_ref::<&'static str>() {
+ Some(s) => Some(*s),
+ None => info.payload().downcast_ref::<String>().map(|s| &**s),
+ };
+
+ let err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
+
+ let backtrace = match (info.location(), msg) {
+ (Some(location), Some(msg)) => format!(
+ "{}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked at '{}': {}:{}\n\u{1b}[0;0m{:?}",
+ err_ctx,
+ thread,
+ msg,
+ location.file(),
+ location.line(),
+ backtrace,
+ ),
+ (Some(location), None) => format!(
+ "{}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked: {}:{}\n\u{1b}[0;0m{:?}",
+ err_ctx,
+ thread,
+ location.file(),
+ location.line(),
+ backtrace
+ ),
+ (None, Some(msg)) => format!(
+ "{}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked at '{}'\n\u{1b}[0;0m{:?}",
+ err_ctx, thread, msg, backtrace
+ ),
+ (None, None) => format!(
+ "{}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked\n\u{1b}[0;0m{:?}",
+ err_ctx, thread, backtrace
+ ),
+ };
+
+ if thread == "main" {
+ println!("{}", backtrace);
+ process::exit(1);
+ } else {
+ let _ = sender.send(T::error(backtrace));
+ }
+}
+
+pub fn get_current_ctx() -> ErrorContext {
+ ASYNCOPENCALLS
+ .try_with(|ctx| *ctx.borrow())
+ .unwrap_or_else(|_| OPENCALLS.with(|ctx| *ctx.borrow()))
+}
+
+/// A representation of the call stack.
+#[derive(Clone, Copy, Serialize, Deserialize)]
+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 {
+ *ctx = call;
+ break;
+ }
+ }
+ self.update_thread_ctx()
+ }
+
+ /// Updates the thread local [`ErrorContext`].
+ pub fn update_thread_ctx(&self) {
+ ASYNCOPENCALLS
+ .try_with(|ctx| *ctx.borrow_mut() = *self)
+ .unwrap_or_else(|_| OPENCALLS.with(|ctx| *ctx.borrow_mut() = *self));
+ }
+}
+
+impl Default for ErrorContext {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl Display for ErrorContext {
+ fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
+ writeln!(f, "Originating Thread(s):")?;
+ for (index, ctx) in self.calls.iter().enumerate() {
+ if *ctx == ContextType::Empty {
+ break;
+ }
+ writeln!(f, "\u{1b}[0;0m{}. {}", index + 1, ctx)?;
+ }
+ Ok(())
+ }
+}
+
+/// 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`], [`ClientInstruction`], etc).
+#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)]
+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.
+ Client(ClientContext),
+ /// A server-related call.
+ IPCServer(ServerContext),
+ 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";
+ let green = "\u{1b}[0;32m";
+ match *self {
+ ContextType::Screen(c) => write!(f, "{}screen_thread: {}{:?}", purple, green, c),
+ ContextType::Pty(c) => write!(f, "{}pty_thread: {}{:?}", purple, green, c),
+ ContextType::Plugin(c) => write!(f, "{}plugin_thread: {}{:?}", purple, green, c),
+ ContextType::Client(c) => write!(f, "{}main_thread: {}{:?}", purple, green, c),
+ ContextType::IPCServer(c) => write!(f, "{}ipc_server: {}{:?}", purple, green, c),
+ ContextType::StdinHandler => {
+ write!(f, "{}stdin_handler_thread: {}AcceptInput", purple, green)
+ }
+ ContextType::AsyncTask => {
+ write!(f, "{}stream_terminal_bytes: {}AsyncTask", purple, green)
+ }
+ ContextType::Empty => write!(f, ""),
+ }
+ }
+}
+
+// FIXME: Just deriving EnumDiscriminants from strum will remove the need for any of this!!!
+/// Stack call representations corresponding to the different types of [`ScreenInstruction`]s.
+#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
+pub enum ScreenContext {
+ HandlePtyBytes,
+ Render,
+ NewPane,
+ HorizontalSplit,
+ VerticalSplit,
+ WriteCharacter,
+ ResizeLeft,
+ ResizeRight,
+ ResizeDown,
+ ResizeUp,
+ SwitchFocus,
+ FocusNextPane,
+ FocusPreviousPane,
+ MoveFocusLeft,
+ MoveFocusLeftOrPreviousTab,
+ MoveFocusDown,
+ MoveFocusUp,
+ MoveFocusRight,
+ MoveFocusRightOrNextTab,
+ Exit,
+ ScrollUp,
+ ScrollDown,
+ PageScrollUp,
+ PageScrollDown,
+ ClearScroll,
+ CloseFocusedPane,
+ ToggleActiveSyncTab,
+ ToggleActiveTerminalFullscreen,
+ SetSelectable,
+ SetInvisibleBorders,
+ SetMaxHeight,
+ ClosePane,
+ ApplyLayout,
+ NewTab,
+ SwitchTabNext,
+ SwitchTabPrev,
+ CloseTab,
+ GoToTab,
+ UpdateTabName,
+ TerminalResize,
+ ChangeMode,
+}
+
+/// Stack call representations corresponding to the different types of [`PtyInstruction`]s.
+#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
+pub enum PtyContext {
+ SpawnTerminal,
+ SpawnTerminalVertically,
+ SpawnTerminalHorizontally,
+ NewTab,
+ ClosePane,
+ CloseTab,
+ Exit,
+}
+
+/// Stack call representations corresponding to the different types of [`PluginInstruction`]s.
+#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
+pub enum PluginContext {
+ Load,
+ Update,
+ Render,
+ Unload,
+ Exit,
+}
+
+/// Stack call representations corresponding to the different types of [`ClientInstruction`]s.
+#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
+pub enum ClientContext {
+ Exit,
+ Error,
+ UnblockInputThread,
+ Render,
+ ServerError,
+}
+
+/// Stack call representations corresponding to the different types of [`ServerInstruction`]s.
+#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
+pub enum ServerContext {
+ NewClient,
+ Render,
+ UnblockInputThread,
+ ClientExit,
+ Error,
+}