1use anyhow::Context;
13use colored::*;
14use log::error;
15use serde::{Deserialize, Serialize};
16use std::fmt::{Display, Error, Formatter};
17use std::path::PathBuf;
18
19pub mod prelude {
21 pub use super::FatalError;
22 pub use super::LoggableError;
23 #[cfg(not(target_family = "wasm"))]
24 pub use super::ToAnyhow;
25 pub use super::ZellijError;
26 pub use anyhow::anyhow;
27 pub use anyhow::bail;
28 pub use anyhow::Context;
29 pub use anyhow::Error as anyError;
30 pub use anyhow::Result;
31}
32
33pub trait ErrorInstruction {
34 fn error(err: String) -> Self;
35}
36
37pub trait LoggableError<T>: Sized {
46 #[track_caller]
61 fn print_error<F: Fn(&str)>(self, fun: F) -> Self;
62
63 #[track_caller]
73 fn to_log(self) -> Self {
74 let caller = std::panic::Location::caller();
75 self.print_error(|msg| {
76 log::logger().log(
81 &log::Record::builder()
82 .level(log::Level::Error)
83 .args(format_args!("{}", msg))
84 .file(Some(caller.file()))
85 .line(Some(caller.line()))
86 .module_path(None)
87 .build(),
88 );
89 })
90 }
91
92 fn to_stderr(self) -> Self {
94 self.print_error(|msg| eprintln!("{}", msg))
95 }
96
97 fn to_stdout(self) -> Self {
99 self.print_error(|msg| println!("{}", msg))
100 }
101}
102
103impl<T> LoggableError<T> for anyhow::Result<T> {
104 fn print_error<F: Fn(&str)>(self, fun: F) -> Self {
105 if let Err(ref err) = self {
106 fun(&format!("{:?}", err));
107 }
108 self
109 }
110}
111
112pub trait FatalError<T> {
120 #[track_caller]
127 fn non_fatal(self);
128
129 #[track_caller]
138 fn fatal(self) -> T;
139}
140
141fn discard_result<T>(_arg: anyhow::Result<T>) {}
144
145impl<T> FatalError<T> for anyhow::Result<T> {
146 fn non_fatal(self) {
147 if self.is_err() {
148 discard_result(self.context("a non-fatal error occured").to_log());
149 }
150 }
151
152 fn fatal(self) -> T {
153 if let Ok(val) = self {
154 val
155 } else {
156 self.context("a fatal error occured")
157 .expect("Program terminates")
158 }
159 }
160}
161
162#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, Debug)]
168pub enum ContextType {
169 Screen(ScreenContext),
171 Pty(PtyContext),
173 Plugin(PluginContext),
175 Client(ClientContext),
177 IPCServer(ServerContext),
179 StdinHandler,
180 AsyncTask,
181 PtyWrite(PtyWriteContext),
182 BackgroundJob(BackgroundJobContext),
183 Empty,
186}
187
188impl Display for ContextType {
189 fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
190 if let Some((left, right)) = match *self {
191 ContextType::Screen(c) => Some(("screen_thread:", format!("{:?}", c))),
192 ContextType::Pty(c) => Some(("pty_thread:", format!("{:?}", c))),
193 ContextType::Plugin(c) => Some(("plugin_thread:", format!("{:?}", c))),
194 ContextType::Client(c) => Some(("main_thread:", format!("{:?}", c))),
195 ContextType::IPCServer(c) => Some(("ipc_server:", format!("{:?}", c))),
196 ContextType::StdinHandler => Some(("stdin_handler_thread:", "AcceptInput".to_string())),
197 ContextType::AsyncTask => Some(("stream_terminal_bytes:", "AsyncTask".to_string())),
198 ContextType::PtyWrite(c) => Some(("pty_writer_thread:", format!("{:?}", c))),
199 ContextType::BackgroundJob(c) => Some(("background_jobs_thread:", format!("{:?}", c))),
200 ContextType::Empty => None,
201 } {
202 write!(f, "{} {}", left.purple(), right.green())
203 } else {
204 write!(f, "")
205 }
206 }
207}
208
209#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
212pub enum ScreenContext {
213 HandlePtyBytes,
214 PluginBytes,
215 Render,
216 RenderToClients,
217 NewPane,
218 OpenInPlaceEditor,
219 ToggleFloatingPanes,
220 ShowFloatingPanes,
221 HideFloatingPanes,
222 TogglePaneEmbedOrFloating,
223 HorizontalSplit,
224 VerticalSplit,
225 WriteCharacter,
226 ResizeIncreaseAll,
227 ResizeIncreaseLeft,
228 ResizeIncreaseDown,
229 ResizeIncreaseUp,
230 ResizeIncreaseRight,
231 ResizeDecreaseAll,
232 ResizeDecreaseLeft,
233 ResizeDecreaseDown,
234 ResizeDecreaseUp,
235 ResizeDecreaseRight,
236 ResizeLeft,
237 ResizeRight,
238 ResizeDown,
239 ResizeUp,
240 ResizeIncrease,
241 ResizeDecrease,
242 SwitchFocus,
243 FocusNextPane,
244 FocusPreviousPane,
245 FocusPaneAt,
246 MoveFocusLeft,
247 MoveFocusLeftOrPreviousTab,
248 MoveFocusDown,
249 MoveFocusUp,
250 MoveFocusRight,
251 MoveFocusRightOrNextTab,
252 MovePane,
253 MovePaneBackwards,
254 MovePaneDown,
255 MovePaneUp,
256 MovePaneRight,
257 MovePaneLeft,
258 Exit,
259 ClearScreen,
260 DumpScreen,
261 DumpLayout,
262 EditScrollback,
263 ScrollUp,
264 ScrollUpAt,
265 ScrollDown,
266 ScrollDownAt,
267 ScrollToBottom,
268 ScrollToTop,
269 PageScrollUp,
270 PageScrollDown,
271 HalfPageScrollUp,
272 HalfPageScrollDown,
273 ClearScroll,
274 CloseFocusedPane,
275 ToggleActiveSyncTab,
276 ToggleActiveTerminalFullscreen,
277 TogglePaneFrames,
278 SetSelectable,
279 SetInvisibleBorders,
280 SetFixedHeight,
281 SetFixedWidth,
282 ClosePane,
283 HoldPane,
284 UpdatePaneName,
285 UndoRenamePane,
286 NewTab,
287 ApplyLayout,
288 SwitchTabNext,
289 SwitchTabPrev,
290 CloseTab,
291 GoToTab,
292 GoToTabName,
293 UpdateTabName,
294 UndoRenameTab,
295 MoveTabLeft,
296 MoveTabRight,
297 TerminalResize,
298 TerminalPixelDimensions,
299 TerminalBackgroundColor,
300 TerminalForegroundColor,
301 TerminalColorRegisters,
302 ChangeMode,
303 ChangeModeForAllClients,
304 LeftClick,
305 RightClick,
306 MiddleClick,
307 LeftMouseRelease,
308 RightMouseRelease,
309 MiddleMouseRelease,
310 MouseEvent,
311 Copy,
312 ToggleTab,
313 AddClient,
314 RemoveClient,
315 AddOverlay,
316 RemoveOverlay,
317 ConfirmPrompt,
318 DenyPrompt,
319 UpdateSearch,
320 SearchDown,
321 SearchUp,
322 SearchToggleCaseSensitivity,
323 SearchToggleWholeWord,
324 SearchToggleWrap,
325 AddRedPaneFrameColorOverride,
326 ClearPaneFrameColorOverride,
327 PreviousSwapLayout,
328 NextSwapLayout,
329 QueryTabNames,
330 NewTiledPluginPane,
331 StartOrReloadPluginPane,
332 NewFloatingPluginPane,
333 AddPlugin,
334 UpdatePluginLoadingStage,
335 ProgressPluginLoadingOffset,
336 StartPluginLoadingIndication,
337 RequestStateUpdateForPlugins,
338 LaunchOrFocusPlugin,
339 LaunchPlugin,
340 SuppressPane,
341 FocusPaneWithId,
342 RenamePane,
343 RenameTab,
344 RequestPluginPermissions,
345 BreakPane,
346 BreakPaneRight,
347 BreakPaneLeft,
348 UpdateSessionInfos,
349 ReplacePane,
350 NewInPlacePluginPane,
351 SerializeLayoutForResurrection,
352 RenameSession,
353 DumpLayoutToPlugin,
354 ListClientsMetadata,
355 Reconfigure,
356 RerunCommandPane,
357 ResizePaneWithId,
358 EditScrollbackForPaneWithId,
359 WriteToPaneId,
360 MovePaneWithPaneId,
361 MovePaneWithPaneIdInDirection,
362 ClearScreenForPaneId,
363 ScrollUpInPaneId,
364 ScrollDownInPaneId,
365 ScrollToTopInPaneId,
366 ScrollToBottomInPaneId,
367 PageScrollUpInPaneId,
368 PageScrollDownInPaneId,
369 TogglePaneIdFullscreen,
370 TogglePaneEmbedOrEjectForPaneId,
371 CloseTabWithIndex,
372 BreakPanesToNewTab,
373 BreakPanesToTabWithIndex,
374 ListClientsToPlugin,
375 TogglePanePinned,
376 SetFloatingPanePinned,
377 StackPanes,
378 ChangeFloatingPanesCoordinates,
379 AddHighlightPaneFrameColorOverride,
380 GroupAndUngroupPanes,
381 HighlightAndUnhighlightPanes,
382 FloatMultiplePanes,
383 EmbedMultiplePanes,
384 TogglePaneInGroup,
385 ToggleGroupMarking,
386 SessionSharingStatusChange,
387 SetMouseSelectionSupport,
388 InterceptKeyPresses,
389 ClearKeyPressesIntercepts,
390 ReplacePaneWithExistingPane,
391}
392
393#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
395pub enum PtyContext {
396 SpawnTerminal,
397 OpenInPlaceEditor,
398 SpawnTerminalVertically,
399 SpawnTerminalHorizontally,
400 UpdateActivePane,
401 GoToTab,
402 NewTab,
403 ClosePane,
404 CloseTab,
405 ReRunCommandInPane,
406 DropToShellInPane,
407 SpawnInPlaceTerminal,
408 DumpLayout,
409 LogLayoutToHd,
410 FillPluginCwd,
411 DumpLayoutToPlugin,
412 ListClientsMetadata,
413 Reconfigure,
414 ListClientsToPlugin,
415 ReportPluginCwd,
416 Exit,
417}
418
419#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
421pub enum PluginContext {
422 Load,
423 LoadBackgroundPlugin,
424 Update,
425 Render,
426 Unload,
427 Reload,
428 ReloadPluginWithId,
429 Resize,
430 Exit,
431 AddClient,
432 RemoveClient,
433 NewTab,
434 ApplyCachedEvents,
435 ApplyCachedWorkerMessages,
436 PostMessageToPluginWorker,
437 PostMessageToPlugin,
438 PluginSubscribedToEvents,
439 PermissionRequestResult,
440 DumpLayout,
441 LogLayoutToHd,
442 CliPipe,
443 Message,
444 CachePluginEvents,
445 MessageFromPlugin,
446 UnblockCliPipes,
447 WatchFilesystem,
448 KeybindPipe,
449 DumpLayoutToPlugin,
450 ListClientsMetadata,
451 Reconfigure,
452 FailedToWriteConfigToDisk,
453 ListClientsToPlugin,
454 ChangePluginHostDir,
455 WebServerStarted,
456 FailedToStartWebServer,
457}
458
459#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
461pub enum ClientContext {
462 Exit,
463 Error,
464 UnblockInputThread,
465 Render,
466 ServerError,
467 SwitchToMode,
468 Connected,
469 Log,
470 LogError,
471 OwnClientId,
472 StartedParsingStdinQuery,
473 DoneParsingStdinQuery,
474 SwitchSession,
475 SetSynchronisedOutput,
476 UnblockCliPipeInput,
477 CliPipeOutput,
478 QueryTerminalSize,
479 WriteConfigToDisk,
480 StartWebServer,
481 RenamedSession,
482}
483
484#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
486pub enum ServerContext {
487 NewClient,
488 Render,
489 UnblockInputThread,
490 ClientExit,
491 RemoveClient,
492 Error,
493 KillSession,
494 DetachSession,
495 AttachClient,
496 ConnStatus,
497 Log,
498 LogError,
499 SwitchSession,
500 UnblockCliPipeInput,
501 CliPipeOutput,
502 AssociatePipeWithClient,
503 DisconnectAllClientsExcept,
504 ChangeMode,
505 ChangeModeForAllClients,
506 Reconfigure,
507 ConfigWrittenToDisk,
508 FailedToWriteConfigToDisk,
509 RebindKeys,
510 StartWebServer,
511 ShareCurrentSession,
512 StopSharingCurrentSession,
513 WebServerStarted,
514 FailedToStartWebServer,
515 SendWebClientsForbidden,
516}
517
518#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
519pub enum PtyWriteContext {
520 Write,
521 ResizePty,
522 StartCachingResizes,
523 ApplyCachedResizes,
524 Exit,
525}
526
527#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
528pub enum BackgroundJobContext {
529 DisplayPaneError,
530 AnimatePluginLoading,
531 StopPluginLoadingAnimation,
532 ReadAllSessionInfosOnMachine,
533 ReportSessionInfo,
534 ReportLayoutInfo,
535 RunCommand,
536 WebRequest,
537 ReportPluginList,
538 ListWebSessions,
539 RenderToClients,
540 HighlightPanesWithMessage,
541 QueryZellijWebServerStatus,
542 Exit,
543}
544
545use thiserror::Error;
546#[derive(Debug, Error)]
547pub enum ZellijError {
548 #[error("could not find command '{command}' for terminal {terminal_id}")]
549 CommandNotFound { terminal_id: u32, command: String },
550
551 #[error("could not determine default editor")]
552 NoEditorFound,
553
554 #[error("failed to allocate another terminal id")]
555 NoMoreTerminalIds,
556
557 #[error("failed to start PTY")]
558 FailedToStartPty,
559
560 #[error(
561 "This version of zellij was built to load the core plugins from
562the globally configured plugin directory. However, a plugin wasn't found:
563
564 Plugin name: '{plugin_path}'
565 Plugin directory: '{plugin_dir}'
566
567If you're a user:
568 Please report this error to the distributor of your current zellij version
569
570If you're a developer:
571 Either make sure to include the plugins with the application (See feature
572 'disable_automatic_asset_installation'), or make them available in the
573 plugin directory.
574
575Possible fix for your problem:
576 Run `zellij setup --dump-plugins`, and optionally point it to your
577 'DATA DIR', visible in e.g. the output of `zellij setup --check`. Without
578 further arguments, it will use the default 'DATA DIR'.
579"
580 )]
581 BuiltinPluginMissing {
582 plugin_path: PathBuf,
583 plugin_dir: PathBuf,
584 #[source]
585 source: anyhow::Error,
586 },
587
588 #[error(
589 "It seems you tried to load the following builtin plugin:
590
591 Plugin name: '{plugin_path}'
592
593This is not a builtin plugin known to this version of zellij. If you were using
594a custom layout, please refer to the layout documentation at:
595
596 https://zellij.dev/documentation/creating-a-layout.html#plugin
597
598If you think this is a bug and the plugin is indeed an internal plugin, please
599open an issue on GitHub:
600
601 https://github.com/zellij-org/zellij/issues
602"
603 )]
604 BuiltinPluginNonexistent {
605 plugin_path: PathBuf,
606 #[source]
607 source: anyhow::Error,
608 },
609
610 #[error("Cannot resize fixed panes")]
613 CantResizeFixedPanes { pane_ids: Vec<(u32, bool)> }, #[error("Pane size remains unchanged")]
616 PaneSizeUnchanged,
617
618 #[error("an error occured")]
619 GenericError { source: anyhow::Error },
620
621 #[error("Client {client_id} is too slow to handle incoming messages")]
622 ClientTooSlow { client_id: u16 },
623
624 #[error("The plugin does not exist")]
625 PluginDoesNotExist,
626}
627
628#[cfg(not(target_family = "wasm"))]
629pub use not_wasm::*;
630
631#[cfg(not(target_family = "wasm"))]
632mod not_wasm {
633 use super::*;
634 use crate::channels::{SenderWithContext, ASYNCOPENCALLS, OPENCALLS};
635 use miette::{Diagnostic, GraphicalReportHandler, GraphicalTheme, Report};
636 use std::panic::PanicHookInfo;
637 use thiserror::Error as ThisError;
638
639 const MAX_THREAD_CALL_STACK: usize = 6;
642
643 #[derive(Debug, ThisError, Diagnostic)]
644 #[error("{0}{}", self.show_backtrace())]
645 #[diagnostic(help("{}", self.show_help()))]
646 struct Panic(String);
647
648 impl Panic {
649 fn show_backtrace(&self) -> String {
658 if let Ok(var) = std::env::var("RUST_BACKTRACE") {
659 if !var.is_empty() && var != "0" {
660 return format!("\n\nPanic backtrace:\n{:?}", backtrace::Backtrace::new());
661 }
662 }
663 "".into()
664 }
665
666 fn show_help(&self) -> String {
667 format!(
668 "If you are seeing this message, it means that something went wrong.
669
670-> To get additional information, check the log at: {}
671-> To see a backtrace next time, reproduce the error with: RUST_BACKTRACE=1 zellij [...]
672-> To help us fix this, please open an issue: https://github.com/zellij-org/zellij/issues
673
674",
675 crate::consts::ZELLIJ_TMP_LOG_FILE.display().to_string()
676 )
677 }
678 }
679
680 pub fn handle_panic<T>(info: &PanicHookInfo<'_>, sender: &SenderWithContext<T>)
682 where
683 T: ErrorInstruction + Clone,
684 {
685 use std::{process, thread};
686 let thread = thread::current();
687 let thread = thread.name().unwrap_or("unnamed");
688
689 let msg = match info.payload().downcast_ref::<&'static str>() {
690 Some(s) => Some(*s),
691 None => info.payload().downcast_ref::<String>().map(|s| &**s),
692 }
693 .unwrap_or("An unexpected error occurred!");
694
695 let err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
696
697 let mut report: Report = Panic(format!("\u{1b}[0;31m{}\u{1b}[0;0m", msg)).into();
698
699 let mut location_string = String::new();
700 if let Some(location) = info.location() {
701 location_string = format!(
702 "At {}:{}:{}",
703 location.file(),
704 location.line(),
705 location.column()
706 );
707 report = report.wrap_err(location_string.clone());
708 }
709
710 if !err_ctx.is_empty() {
711 report = report.wrap_err(format!("{}", err_ctx));
712 }
713
714 report = report.wrap_err(format!(
715 "Thread '\u{1b}[0;31m{}\u{1b}[0;0m' panicked.",
716 thread
717 ));
718
719 error!(
720 "{}",
721 format!(
722 "Panic occured:
723 thread: {}
724 location: {}
725 message: {}",
726 thread, location_string, msg
727 )
728 );
729
730 if thread == "main" {
731 println!("\u{1b}[2J{}", fmt_report(report));
735 process::exit(1);
736 } else {
737 let _ = sender.send(T::error(fmt_report(report)));
738 }
739 }
740
741 pub fn get_current_ctx() -> ErrorContext {
742 ASYNCOPENCALLS
743 .try_with(|ctx| *ctx.borrow())
744 .unwrap_or_else(|_| OPENCALLS.with(|ctx| *ctx.borrow()))
745 }
746
747 fn fmt_report(diag: Report) -> String {
748 let mut out = String::new();
749 GraphicalReportHandler::new_themed(GraphicalTheme::unicode())
750 .render_report(&mut out, diag.as_ref())
751 .unwrap();
752 out
753 }
754
755 #[derive(Clone, Copy, Serialize, Deserialize, Debug)]
757 pub struct ErrorContext {
758 calls: [ContextType; MAX_THREAD_CALL_STACK],
759 }
760
761 impl ErrorContext {
762 pub fn new() -> Self {
765 Self {
766 calls: [ContextType::Empty; MAX_THREAD_CALL_STACK],
767 }
768 }
769
770 pub fn is_empty(&self) -> bool {
772 self.calls.iter().all(|c| c == &ContextType::Empty)
773 }
774
775 pub fn add_call(&mut self, call: ContextType) {
777 for ctx in &mut self.calls {
778 if let ContextType::Empty = ctx {
779 *ctx = call;
780 break;
781 }
782 }
783 self.update_thread_ctx()
784 }
785
786 pub fn update_thread_ctx(&self) {
788 ASYNCOPENCALLS
789 .try_with(|ctx| *ctx.borrow_mut() = *self)
790 .unwrap_or_else(|_| OPENCALLS.with(|ctx| *ctx.borrow_mut() = *self));
791 }
792 }
793
794 impl Default for ErrorContext {
795 fn default() -> Self {
796 Self::new()
797 }
798 }
799
800 impl Display for ErrorContext {
801 fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
802 writeln!(f, "Originating Thread(s)")?;
803 for (index, ctx) in self.calls.iter().enumerate() {
804 if *ctx == ContextType::Empty {
805 break;
806 }
807 writeln!(f, "\t\u{1b}[0;0m{}. {}", index + 1, ctx)?;
808 }
809 Ok(())
810 }
811 }
812
813 pub trait ToAnyhow<U> {
816 fn to_anyhow(self) -> anyhow::Result<U>;
817 }
818
819 impl<T: std::fmt::Debug, U> ToAnyhow<U>
827 for Result<U, crate::channels::SendError<(T, ErrorContext)>>
828 {
829 fn to_anyhow(self) -> anyhow::Result<U> {
830 match self {
831 Ok(val) => anyhow::Ok(val),
832 Err(e) => {
833 let (msg, context) = e.into_inner();
834 if *crate::consts::DEBUG_MODE.get().unwrap_or(&true) {
835 Err(anyhow::anyhow!(
836 "failed to send message to channel: {:#?}",
837 msg
838 ))
839 .with_context(|| context.to_string())
840 } else {
841 Err(anyhow::anyhow!("failed to send message to channel"))
842 .with_context(|| context.to_string())
843 }
844 },
845 }
846 }
847 }
848
849 impl<U> ToAnyhow<U> for Result<U, std::sync::PoisonError<U>> {
850 fn to_anyhow(self) -> anyhow::Result<U> {
851 match self {
852 Ok(val) => anyhow::Ok(val),
853 Err(e) => {
854 if *crate::consts::DEBUG_MODE.get().unwrap_or(&true) {
855 Err(anyhow::anyhow!("cannot acquire poisoned lock for {e:#?}"))
856 } else {
857 Err(anyhow::anyhow!("cannot acquire poisoned lock"))
858 }
859 },
860 }
861 }
862 }
863}