1use bevy::prelude::*;
2use bevy_ratatui::{
3 crossterm::{
4 ExecutableCommand, cursor,
5 terminal::{disable_raw_mode, enable_raw_mode},
6 },
7 error::ErrorPlugin,
8 event::EventPlugin,
9 kitty::{KittyEnabled, KittyPlugin},
10 translation::TranslationPlugin,
11};
12use color_eyre::{
13 self,
14 config::{EyreHook, HookBuilder, PanicHook},
15 eyre,
16};
17use ratatui::{Terminal, backend::CrosstermBackend};
18use std::io::{Stdout, stdout};
19use std::panic;
20
21pub struct ReplContextPlugin;
24
25impl Plugin for ReplContextPlugin {
26 fn build(&self, app: &mut bevy::prelude::App) {
27 if !app.is_plugin_added::<EventPlugin>() {
30 app.add_plugins(EventPlugin::default());
31 }
32 if !app.is_plugin_added::<KittyPlugin>() {
35 app.add_plugins(KittyPlugin);
36 }
37 if !app.is_plugin_added::<TranslationPlugin>() {
40 app.add_plugins(TranslationPlugin);
41 }
42 if !app.is_plugin_added::<ErrorPlugin>() {
45 app.add_systems(Startup, error_setup);
46 }
47 app.add_systems(Startup, context_setup);
49 app.add_observer(context_cleanup);
51 }
52}
53
54#[derive(Resource, Deref, DerefMut, Debug)]
58pub struct ReplContext(Terminal<CrosstermBackend<Stdout>>);
59
60impl Drop for ReplContext {
61 fn drop(&mut self) {
62 if let Err(err) = ReplContext::restore() {
63 eprintln!("Failed to restore terminal: {}", err);
64 }
65 }
66}
67
68impl ReplContext {
69 fn init() -> Result<Self> {
70 let stdout = stdout();
71 enable_raw_mode()?;
72 let backend = CrosstermBackend::new(stdout);
73 let terminal = Terminal::new(backend)?;
74 Ok(Self(terminal))
75 }
76
77 fn restore() -> Result<()> {
78 let mut stdout = stdout();
79 stdout.execute(cursor::Show)?;
80 disable_raw_mode()?;
81 Ok(())
82 }
83}
84
85pub fn context_setup(mut commands: Commands) -> Result {
87 let terminal = ReplContext::init()?;
88 commands.insert_resource(terminal);
89
90 Ok(())
91}
92
93fn context_cleanup(_trigger: Trigger<AppExit>, mut commands: Commands) {
97 commands.remove_resource::<KittyEnabled>();
98 commands.remove_resource::<ReplContext>();
99}
100
101pub fn error_setup() -> Result {
108 let (panic_hook, eyre_hook) = HookBuilder::default().into_hooks();
109 set_panic_hook(panic_hook);
110 set_error_hook(eyre_hook)?;
111 Ok(())
112}
113
114fn set_panic_hook(panic_hook: PanicHook) {
116 let panic_hook = panic_hook.into_panic_hook();
117 panic::set_hook(Box::new(move |panic_info| {
118 let _ = ReplContext::restore();
119 panic_hook(panic_info);
120 }));
121}
122
123fn set_error_hook(eyre_hook: EyreHook) -> Result {
125 let eyre_hook = eyre_hook.into_eyre_hook();
126 eyre::set_hook(Box::new(move |error| {
127 let _ = ReplContext::restore();
128 eyre_hook(error)
129 }))?;
130
131 Ok(())
132}