zellij/zellij-utils/src/errors.rs
Thomas Linford f93308f211
feat(ui): initial mouse support (#448)
* Initial mouse support

* enable mouse support (termion MouseTerminal)
* handle scroll up and down events

* Allow enabling/disabling of mouse reporting

Store the mouse terminal on OsInputOutput

* WIP: switch pane focus with mouse

* testing to get mouse character selection

* wip mouse selection

* wip: mouse selection

- initial handling of mouse events for
  text selection within a pane
- wide characters currently problematic
- selection is not highlighted

* highlight currently selected text

* improve get currently selected text from TerminalPane

* copy selection to clipboard (wayland only for now)

* Add missing set_should_render on selection update

* Improve text selection

- Strip whitespace from end of lines
- Insert newlines when selection spans multiple lines

* Simplify selection logic

- Remove Range struct
- Selection is not an Option anymore, it's empty when start == end

* copy selection to clipboard with OSC-52 sequence

* Improve text selection with multiple panes

- Constrain mouse hold and release events to currently active pane
- Fix calculation of columns with side by side panes

* fix for PositionAndSize changes

* Fix mouse selection with full screen pane.

- Transform mouse event positions to relative when passing to pane.

* Move selection to grid, rework highlighting.

- Mark selected lines for update in grid output buffer, for now in the
  simplest way possible, but can be made more efficient by calculating
  changed lines only.
- Clear selection on pane resize.
- Re-add logic to forward mouse hold and release events only to
  currently active pane.

* Tidy up selection

- add method to get selection line range
- add method to sort the current selection

* Grid: move current selection up/down when scrolling

- Make the selection work with lines in lines_above and lines_below
- Todo: update selection end when scrolling with mouse button being held
- Todo: figure out how to move selection when new characters are added.

* Grid: move selection when new lines are added

* Keep track of selection being active

- Handle the selection growing/shrinking when scrolling with mouse
  button held down but not yet released.

* Improve selection end with multiple panes

* Tidying up

- remove logging statements, unused code

* Add some unit tests for selection, move position to zellij-utils

* Change Position::new to take i32 for line

* Grid: add unit tests for copy from selection

* add basic integration test for mouse pane focus

* Add basic integration test for mouse scroll

* Use color from palette for selection rendering

* Improve performance of selection render

- Try to minimize lines to update on selection start/update/end

* fixes for updated start method

* fix lines not in viewport being marked for rendering

- the previous optimization to grid selection rendering was always
adding the lines at the extremes of previous selection, causing problems
when scrolling up or down
- make sure to only add lines in viewport

* Disable mouse mode on exit

* use saturating_sub for usize subtractions

* copy selection to clipboard on mouse release

* disable mouse on exit after error

* remove left-over comments

* remove copy keybinding

* add default impl for selection methods in Pane

- remove the useless methods from Impl Pane in PluginPane

* move line diff between selections to selection

* add option to disable mouse mode

* Allow scrolling with mouse while selecting.

* move action repeater to os_input_output, change timeout to 10ms

- change repeater to take an action instead of a position with hardcoded
action

* replace mouse mode integration tests with e2e tests

* cleanup

* cleanup

* check if mouse mode is disabled from merged options

* fix missing changes in tests, cleanup
2021-07-02 16:40:50 +02:00

273 lines
8 KiB
Rust

//! 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::{SenderWithContext, ASYNCOPENCALLS, OPENCALLS};
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,
FocusPaneAt,
MoveFocusLeft,
MoveFocusLeftOrPreviousTab,
MoveFocusDown,
MoveFocusUp,
MoveFocusRight,
MoveFocusRightOrNextTab,
Exit,
ScrollUp,
ScrollUpAt,
ScrollDown,
ScrollDownAt,
PageScrollUp,
PageScrollDown,
ClearScroll,
CloseFocusedPane,
ToggleActiveSyncTab,
ToggleActiveTerminalFullscreen,
SetSelectable,
SetInvisibleBorders,
SetFixedHeight,
SetFixedWidth,
ClosePane,
ApplyLayout,
NewTab,
SwitchTabNext,
SwitchTabPrev,
CloseTab,
GoToTab,
UpdateTabName,
TerminalResize,
ChangeMode,
LeftClick,
MouseRelease,
MouseHold,
Copy,
}
/// 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,
DetachSession,
AttachClient,
}