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 NewPane,
217 OpenInPlaceEditor,
218 ToggleFloatingPanes,
219 ShowFloatingPanes,
220 HideFloatingPanes,
221 TogglePaneEmbedOrFloating,
222 HorizontalSplit,
223 VerticalSplit,
224 WriteCharacter,
225 ResizeIncreaseAll,
226 ResizeIncreaseLeft,
227 ResizeIncreaseDown,
228 ResizeIncreaseUp,
229 ResizeIncreaseRight,
230 ResizeDecreaseAll,
231 ResizeDecreaseLeft,
232 ResizeDecreaseDown,
233 ResizeDecreaseUp,
234 ResizeDecreaseRight,
235 ResizeLeft,
236 ResizeRight,
237 ResizeDown,
238 ResizeUp,
239 ResizeIncrease,
240 ResizeDecrease,
241 SwitchFocus,
242 FocusNextPane,
243 FocusPreviousPane,
244 FocusPaneAt,
245 MoveFocusLeft,
246 MoveFocusLeftOrPreviousTab,
247 MoveFocusDown,
248 MoveFocusUp,
249 MoveFocusRight,
250 MoveFocusRightOrNextTab,
251 MovePane,
252 MovePaneBackwards,
253 MovePaneDown,
254 MovePaneUp,
255 MovePaneRight,
256 MovePaneLeft,
257 Exit,
258 ClearScreen,
259 DumpScreen,
260 DumpLayout,
261 EditScrollback,
262 ScrollUp,
263 ScrollUpAt,
264 ScrollDown,
265 ScrollDownAt,
266 ScrollToBottom,
267 ScrollToTop,
268 PageScrollUp,
269 PageScrollDown,
270 HalfPageScrollUp,
271 HalfPageScrollDown,
272 ClearScroll,
273 CloseFocusedPane,
274 ToggleActiveSyncTab,
275 ToggleActiveTerminalFullscreen,
276 TogglePaneFrames,
277 SetSelectable,
278 SetInvisibleBorders,
279 SetFixedHeight,
280 SetFixedWidth,
281 ClosePane,
282 HoldPane,
283 UpdatePaneName,
284 UndoRenamePane,
285 NewTab,
286 ApplyLayout,
287 SwitchTabNext,
288 SwitchTabPrev,
289 CloseTab,
290 GoToTab,
291 GoToTabName,
292 UpdateTabName,
293 UndoRenameTab,
294 MoveTabLeft,
295 MoveTabRight,
296 TerminalResize,
297 TerminalPixelDimensions,
298 TerminalBackgroundColor,
299 TerminalForegroundColor,
300 TerminalColorRegisters,
301 ChangeMode,
302 ChangeModeForAllClients,
303 LeftClick,
304 RightClick,
305 MiddleClick,
306 LeftMouseRelease,
307 RightMouseRelease,
308 MiddleMouseRelease,
309 MouseHoldLeft,
310 MouseHoldRight,
311 MouseHoldMiddle,
312 Copy,
313 ToggleTab,
314 AddClient,
315 RemoveClient,
316 AddOverlay,
317 RemoveOverlay,
318 ConfirmPrompt,
319 DenyPrompt,
320 UpdateSearch,
321 SearchDown,
322 SearchUp,
323 SearchToggleCaseSensitivity,
324 SearchToggleWholeWord,
325 SearchToggleWrap,
326 AddRedPaneFrameColorOverride,
327 ClearPaneFrameColorOverride,
328 PreviousSwapLayout,
329 NextSwapLayout,
330 QueryTabNames,
331 NewTiledPluginPane,
332 StartOrReloadPluginPane,
333 NewFloatingPluginPane,
334 AddPlugin,
335 UpdatePluginLoadingStage,
336 ProgressPluginLoadingOffset,
337 StartPluginLoadingIndication,
338 RequestStateUpdateForPlugins,
339 LaunchOrFocusPlugin,
340 LaunchPlugin,
341 SuppressPane,
342 FocusPaneWithId,
343 RenamePane,
344 RenameTab,
345 RequestPluginPermissions,
346 BreakPane,
347 BreakPaneRight,
348 BreakPaneLeft,
349 UpdateSessionInfos,
350 ReplacePane,
351 NewInPlacePluginPane,
352 DumpLayoutToHd,
353 RenameSession,
354 DumpLayoutToPlugin,
355 ListClientsMetadata,
356 Reconfigure,
357 RerunCommandPane,
358 ResizePaneWithId,
359 EditScrollbackForPaneWithId,
360 WriteToPaneId,
361 MovePaneWithPaneId,
362 MovePaneWithPaneIdInDirection,
363 ClearScreenForPaneId,
364 ScrollUpInPaneId,
365 ScrollDownInPaneId,
366 ScrollToTopInPaneId,
367 ScrollToBottomInPaneId,
368 PageScrollUpInPaneId,
369 PageScrollDownInPaneId,
370 TogglePaneIdFullscreen,
371 TogglePaneEmbedOrEjectForPaneId,
372 CloseTabWithIndex,
373 BreakPanesToNewTab,
374 BreakPanesToTabWithIndex,
375 ListClientsToPlugin,
376}
377
378#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
380pub enum PtyContext {
381 SpawnTerminal,
382 OpenInPlaceEditor,
383 SpawnTerminalVertically,
384 SpawnTerminalHorizontally,
385 UpdateActivePane,
386 GoToTab,
387 NewTab,
388 ClosePane,
389 CloseTab,
390 ReRunCommandInPane,
391 DropToShellInPane,
392 SpawnInPlaceTerminal,
393 DumpLayout,
394 LogLayoutToHd,
395 FillPluginCwd,
396 DumpLayoutToPlugin,
397 ListClientsMetadata,
398 Reconfigure,
399 ListClientsToPlugin,
400 Exit,
401}
402
403#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
405pub enum PluginContext {
406 Load,
407 LoadBackgroundPlugin,
408 Update,
409 Render,
410 Unload,
411 Reload,
412 ReloadPluginWithId,
413 Resize,
414 Exit,
415 AddClient,
416 RemoveClient,
417 NewTab,
418 ApplyCachedEvents,
419 ApplyCachedWorkerMessages,
420 PostMessageToPluginWorker,
421 PostMessageToPlugin,
422 PluginSubscribedToEvents,
423 PermissionRequestResult,
424 DumpLayout,
425 LogLayoutToHd,
426 CliPipe,
427 Message,
428 CachePluginEvents,
429 MessageFromPlugin,
430 UnblockCliPipes,
431 WatchFilesystem,
432 KeybindPipe,
433 DumpLayoutToPlugin,
434 ListClientsMetadata,
435 Reconfigure,
436 FailedToWriteConfigToDisk,
437 ListClientsToPlugin,
438}
439
440#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
442pub enum ClientContext {
443 Exit,
444 Error,
445 UnblockInputThread,
446 Render,
447 ServerError,
448 SwitchToMode,
449 Connected,
450 ActiveClients,
451 Log,
452 LogError,
453 OwnClientId,
454 StartedParsingStdinQuery,
455 DoneParsingStdinQuery,
456 SwitchSession,
457 SetSynchronisedOutput,
458 UnblockCliPipeInput,
459 CliPipeOutput,
460 QueryTerminalSize,
461 WriteConfigToDisk,
462}
463
464#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
466pub enum ServerContext {
467 NewClient,
468 Render,
469 UnblockInputThread,
470 ClientExit,
471 RemoveClient,
472 Error,
473 KillSession,
474 DetachSession,
475 AttachClient,
476 ConnStatus,
477 ActiveClients,
478 Log,
479 LogError,
480 SwitchSession,
481 UnblockCliPipeInput,
482 CliPipeOutput,
483 AssociatePipeWithClient,
484 DisconnectAllClientsExcept,
485 ChangeMode,
486 ChangeModeForAllClients,
487 Reconfigure,
488 ConfigWrittenToDisk,
489 FailedToWriteConfigToDisk,
490 RebindKeys,
491}
492
493#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
494pub enum PtyWriteContext {
495 Write,
496 ResizePty,
497 StartCachingResizes,
498 ApplyCachedResizes,
499 Exit,
500}
501
502#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
503pub enum BackgroundJobContext {
504 DisplayPaneError,
505 AnimatePluginLoading,
506 StopPluginLoadingAnimation,
507 ReadAllSessionInfosOnMachine,
508 ReportSessionInfo,
509 ReportLayoutInfo,
510 RunCommand,
511 WebRequest,
512 ReportPluginList,
513 Exit,
514}
515
516use thiserror::Error;
517#[derive(Debug, Error)]
518pub enum ZellijError {
519 #[error("could not find command '{command}' for terminal {terminal_id}")]
520 CommandNotFound { terminal_id: u32, command: String },
521
522 #[error("could not determine default editor")]
523 NoEditorFound,
524
525 #[error("failed to allocate another terminal id")]
526 NoMoreTerminalIds,
527
528 #[error("failed to start PTY")]
529 FailedToStartPty,
530
531 #[error(
532 "This version of zellij was built to load the core plugins from
533the globally configured plugin directory. However, a plugin wasn't found:
534
535 Plugin name: '{plugin_path}'
536 Plugin directory: '{plugin_dir}'
537
538If you're a user:
539 Please report this error to the distributor of your current zellij version
540
541If you're a developer:
542 Either make sure to include the plugins with the application (See feature
543 'disable_automatic_asset_installation'), or make them available in the
544 plugin directory.
545
546Possible fix for your problem:
547 Run `zellij setup --dump-plugins`, and optionally point it to your
548 'DATA DIR', visible in e.g. the output of `zellij setup --check`. Without
549 further arguments, it will use the default 'DATA DIR'.
550"
551 )]
552 BuiltinPluginMissing {
553 plugin_path: PathBuf,
554 plugin_dir: PathBuf,
555 #[source]
556 source: anyhow::Error,
557 },
558
559 #[error(
560 "It seems you tried to load the following builtin plugin:
561
562 Plugin name: '{plugin_path}'
563
564This is not a builtin plugin known to this version of zellij. If you were using
565a custom layout, please refer to the layout documentation at:
566
567 https://zellij.dev/documentation/creating-a-layout.html#plugin
568
569If you think this is a bug and the plugin is indeed an internal plugin, please
570open an issue on GitHub:
571
572 https://github.com/zellij-org/zellij/issues
573"
574 )]
575 BuiltinPluginNonexistent {
576 plugin_path: PathBuf,
577 #[source]
578 source: anyhow::Error,
579 },
580
581 #[error("Cannot resize fixed panes")]
584 CantResizeFixedPanes { pane_ids: Vec<(u32, bool)> }, #[error("Pane size remains unchanged")]
587 PaneSizeUnchanged,
588
589 #[error("an error occured")]
590 GenericError { source: anyhow::Error },
591
592 #[error("Client {client_id} is too slow to handle incoming messages")]
593 ClientTooSlow { client_id: u16 },
594
595 #[error("The plugin does not exist")]
596 PluginDoesNotExist,
597}
598
599#[cfg(not(target_family = "wasm"))]
600pub use not_wasm::*;
601
602#[cfg(not(target_family = "wasm"))]
603mod not_wasm {
604 use super::*;
605 use crate::channels::{SenderWithContext, ASYNCOPENCALLS, OPENCALLS};
606 use miette::{Diagnostic, GraphicalReportHandler, GraphicalTheme, Report};
607 use std::panic::PanicInfo;
608 use thiserror::Error as ThisError;
609
610 const MAX_THREAD_CALL_STACK: usize = 6;
613
614 #[derive(Debug, ThisError, Diagnostic)]
615 #[error("{0}{}", self.show_backtrace())]
616 #[diagnostic(help("{}", self.show_help()))]
617 struct Panic(String);
618
619 impl Panic {
620 fn show_backtrace(&self) -> String {
629 if let Ok(var) = std::env::var("RUST_BACKTRACE") {
630 if !var.is_empty() && var != "0" {
631 return format!("\n\nPanic backtrace:\n{:?}", backtrace::Backtrace::new());
632 }
633 }
634 "".into()
635 }
636
637 fn show_help(&self) -> String {
638 format!(
639 "If you are seeing this message, it means that something went wrong.
640
641-> To get additional information, check the log at: {}
642-> To see a backtrace next time, reproduce the error with: RUST_BACKTRACE=1 zellij [...]
643-> To help us fix this, please open an issue: https://github.com/zellij-org/zellij/issues
644
645",
646 crate::consts::ZELLIJ_TMP_LOG_FILE.display().to_string()
647 )
648 }
649 }
650
651 pub fn handle_panic<T>(info: &PanicInfo<'_>, sender: &SenderWithContext<T>)
653 where
654 T: ErrorInstruction + Clone,
655 {
656 use std::{process, thread};
657 let thread = thread::current();
658 let thread = thread.name().unwrap_or("unnamed");
659
660 let msg = match info.payload().downcast_ref::<&'static str>() {
661 Some(s) => Some(*s),
662 None => info.payload().downcast_ref::<String>().map(|s| &**s),
663 }
664 .unwrap_or("An unexpected error occurred!");
665
666 let err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
667
668 let mut report: Report = Panic(format!("\u{1b}[0;31m{}\u{1b}[0;0m", msg)).into();
669
670 let mut location_string = String::new();
671 if let Some(location) = info.location() {
672 location_string = format!(
673 "At {}:{}:{}",
674 location.file(),
675 location.line(),
676 location.column()
677 );
678 report = report.wrap_err(location_string.clone());
679 }
680
681 if !err_ctx.is_empty() {
682 report = report.wrap_err(format!("{}", err_ctx));
683 }
684
685 report = report.wrap_err(format!(
686 "Thread '\u{1b}[0;31m{}\u{1b}[0;0m' panicked.",
687 thread
688 ));
689
690 error!(
691 "{}",
692 format!(
693 "Panic occured:
694 thread: {}
695 location: {}
696 message: {}",
697 thread, location_string, msg
698 )
699 );
700
701 if thread == "main" {
702 println!("\u{1b}[2J{}", fmt_report(report));
706 process::exit(1);
707 } else {
708 let _ = sender.send(T::error(fmt_report(report)));
709 }
710 }
711
712 pub fn get_current_ctx() -> ErrorContext {
713 ASYNCOPENCALLS
714 .try_with(|ctx| *ctx.borrow())
715 .unwrap_or_else(|_| OPENCALLS.with(|ctx| *ctx.borrow()))
716 }
717
718 fn fmt_report(diag: Report) -> String {
719 let mut out = String::new();
720 GraphicalReportHandler::new_themed(GraphicalTheme::unicode())
721 .render_report(&mut out, diag.as_ref())
722 .unwrap();
723 out
724 }
725
726 #[derive(Clone, Copy, Serialize, Deserialize, Debug)]
728 pub struct ErrorContext {
729 calls: [ContextType; MAX_THREAD_CALL_STACK],
730 }
731
732 impl ErrorContext {
733 pub fn new() -> Self {
736 Self {
737 calls: [ContextType::Empty; MAX_THREAD_CALL_STACK],
738 }
739 }
740
741 pub fn is_empty(&self) -> bool {
743 self.calls.iter().all(|c| c == &ContextType::Empty)
744 }
745
746 pub fn add_call(&mut self, call: ContextType) {
748 for ctx in &mut self.calls {
749 if let ContextType::Empty = ctx {
750 *ctx = call;
751 break;
752 }
753 }
754 self.update_thread_ctx()
755 }
756
757 pub fn update_thread_ctx(&self) {
759 ASYNCOPENCALLS
760 .try_with(|ctx| *ctx.borrow_mut() = *self)
761 .unwrap_or_else(|_| OPENCALLS.with(|ctx| *ctx.borrow_mut() = *self));
762 }
763 }
764
765 impl Default for ErrorContext {
766 fn default() -> Self {
767 Self::new()
768 }
769 }
770
771 impl Display for ErrorContext {
772 fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
773 writeln!(f, "Originating Thread(s)")?;
774 for (index, ctx) in self.calls.iter().enumerate() {
775 if *ctx == ContextType::Empty {
776 break;
777 }
778 writeln!(f, "\t\u{1b}[0;0m{}. {}", index + 1, ctx)?;
779 }
780 Ok(())
781 }
782 }
783
784 pub trait ToAnyhow<U> {
787 fn to_anyhow(self) -> crate::anyhow::Result<U>;
788 }
789
790 impl<T: std::fmt::Debug, U> ToAnyhow<U>
798 for Result<U, crate::channels::SendError<(T, ErrorContext)>>
799 {
800 fn to_anyhow(self) -> crate::anyhow::Result<U> {
801 match self {
802 Ok(val) => crate::anyhow::Ok(val),
803 Err(e) => {
804 let (msg, context) = e.into_inner();
805 if *crate::consts::DEBUG_MODE.get().unwrap_or(&true) {
806 Err(crate::anyhow::anyhow!(
807 "failed to send message to channel: {:#?}",
808 msg
809 ))
810 .with_context(|| context.to_string())
811 } else {
812 Err(crate::anyhow::anyhow!("failed to send message to channel"))
813 .with_context(|| context.to_string())
814 }
815 },
816 }
817 }
818 }
819
820 impl<U> ToAnyhow<U> for Result<U, std::sync::PoisonError<U>> {
821 fn to_anyhow(self) -> crate::anyhow::Result<U> {
822 match self {
823 Ok(val) => crate::anyhow::Ok(val),
824 Err(e) => {
825 if *crate::consts::DEBUG_MODE.get().unwrap_or(&true) {
826 Err(crate::anyhow::anyhow!(
827 "cannot acquire poisoned lock for {e:#?}"
828 ))
829 } else {
830 Err(crate::anyhow::anyhow!("cannot acquire poisoned lock"))
831 }
832 },
833 }
834 }
835 }
836}