git_record/
cursive_utils.rs1use std::panic::{self, AssertUnwindSafe, RefUnwindSafe, UnwindSafe};
4use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError};
5
6use cursive::event::Event;
7use cursive::{CursiveRunnable, CursiveRunner};
8
9pub trait EventDrivenCursiveApp
10where
11 Self: UnwindSafe,
12{
13 type Message: Clone + std::fmt::Debug + UnwindSafe + 'static;
14 type Return;
15
16 fn get_init_message(&self) -> Self::Message;
17 fn get_key_bindings(&self) -> Vec<(Event, Self::Message)>;
18 fn handle_message(
19 &mut self,
20 siv: &mut CursiveRunner<CursiveRunnable>,
21 main_tx: Sender<Self::Message>,
22 message: Self::Message,
23 );
24 fn finish(self) -> Self::Return;
25}
26
27pub trait EventDrivenCursiveAppExt: EventDrivenCursiveApp {
28 fn run(self, siv: CursiveRunner<CursiveRunnable>) -> Self::Return;
29}
30
31impl<T: EventDrivenCursiveApp + UnwindSafe + RefUnwindSafe> EventDrivenCursiveAppExt for T {
32 fn run(mut self, mut siv: CursiveRunner<CursiveRunnable>) -> T::Return {
33 let (main_tx, main_rx): (Sender<T::Message>, Receiver<T::Message>) = channel();
34
35 self.get_key_bindings().iter().cloned().for_each(
36 |(event, message): (cursive::event::Event, T::Message)| {
37 siv.add_global_callback(event, {
38 let main_tx = main_tx.clone();
39 move |_siv| main_tx.send(message.clone()).unwrap()
40 });
41 },
42 );
43
44 main_tx.send(self.get_init_message()).unwrap();
45 while siv.is_running() {
46 let message = main_rx.try_recv();
47 if message.is_err() {
48 siv.step();
53 }
54
55 match message {
56 Err(TryRecvError::Disconnected) => break,
57
58 Err(TryRecvError::Empty) => {
59 continue;
62 }
63
64 Ok(message) => {
65 let maybe_panic = panic::catch_unwind({
66 let mut siv = AssertUnwindSafe(&mut siv);
67 let mut self_ = AssertUnwindSafe(&mut self);
68 let main_tx = AssertUnwindSafe(main_tx.clone());
69 move || {
70 self_.handle_message(*siv, main_tx.clone(), message);
71 }
72 });
73 match maybe_panic {
74 Ok(()) => {
75 siv.refresh();
76 }
77 Err(panic) => {
78 drop(siv);
80 if let Some(payload) = panic.downcast_ref::<String>() {
81 panic!("panic occurred: {}", payload);
82 } else if let Some(payload) = panic.downcast_ref::<&str>() {
83 panic!("panic occurred: {}", payload);
84 } else {
85 panic!("panic occurred (message not available)",);
86 }
87 }
88 }
89 }
90 };
91 }
92
93 self.finish()
94 }
95}
96
97pub mod testing {
99 use std::borrow::Borrow;
100 use std::cell::RefCell;
101 use std::rc::Rc;
102
103 use cursive::backend::Backend;
104 use cursive::theme::Color;
105
106 pub type Screen = Vec<Vec<char>>;
108
109 #[derive(Clone, Debug)]
111 pub enum CursiveTestingEvent {
112 Event(cursive::event::Event),
114
115 TakeScreenshot(Rc<RefCell<Screen>>),
118 }
119
120 #[derive(Debug)]
123 pub struct CursiveTestingBackend {
124 events: Vec<CursiveTestingEvent>,
125 event_index: usize,
126 just_emitted_event: bool,
127 screen: RefCell<Screen>,
128 }
129
130 impl CursiveTestingBackend {
131 pub fn init(events: Vec<CursiveTestingEvent>) -> Box<dyn Backend> {
133 Box::new(CursiveTestingBackend {
134 events,
135 event_index: 0,
136 just_emitted_event: false,
137 screen: RefCell::new(vec![vec![' '; 120]; 24]),
138 })
139 }
140 }
141
142 impl Backend for CursiveTestingBackend {
143 fn poll_event(&mut self) -> Option<cursive::event::Event> {
144 if self.just_emitted_event {
147 self.just_emitted_event = false;
148 return None;
149 }
150
151 let event_index = self.event_index;
152 self.event_index += 1;
153 match self.events.get(event_index)?.to_owned() {
154 CursiveTestingEvent::TakeScreenshot(screen_target) => {
155 let mut screen_target = (*screen_target).borrow_mut();
156 *screen_target = self.screen.borrow().clone();
157 self.poll_event()
158 }
159 CursiveTestingEvent::Event(event) => {
160 self.just_emitted_event = true;
161 Some(event)
162 }
163 }
164 }
165
166 fn refresh(&mut self) {}
167
168 fn has_colors(&self) -> bool {
169 false
170 }
171
172 fn screen_size(&self) -> cursive::Vec2 {
173 let screen = self.screen.borrow();
174 (screen[0].len(), screen.len()).into()
175 }
176
177 fn print_at(&self, pos: cursive::Vec2, text: &str) {
178 for (i, c) in text.chars().enumerate() {
179 let mut screen = self.screen.borrow_mut();
180 let screen_width = screen[0].len();
181 if pos.x + i < screen_width {
182 screen[pos.y][pos.x + i] = c;
183 } else {
184 screen[pos.y][screen_width - 1] = '$';
186 }
187 }
188 }
189
190 fn clear(&self, _color: Color) {
191 let mut screen = self.screen.borrow_mut();
192 for i in 0..screen.len() {
193 for j in 0..screen[i].len() {
194 screen[i][j] = ' ';
195 }
196 }
197 }
198
199 fn set_color(&self, colors: cursive::theme::ColorPair) -> cursive::theme::ColorPair {
200 colors
201 }
202
203 fn set_effect(&self, _effect: cursive::theme::Effect) {}
204
205 fn unset_effect(&self, _effect: cursive::theme::Effect) {}
206
207 fn set_title(&mut self, _title: String) {}
208 }
209
210 pub fn screen_to_string(screen: &Rc<RefCell<Screen>>) -> String {
213 let screen = Rc::borrow(screen);
214 let screen = RefCell::borrow(screen);
215 screen
216 .iter()
217 .map(|row| {
218 let line: String = row.iter().collect();
219 line.trim_end().to_owned() + "\n"
220 })
221 .collect::<String>()
222 .trim()
223 .to_owned()
224 }
225}