1use std::fmt;
71use std::panic::AssertUnwindSafe;
72use std::time::Duration;
73
74use rich::Style;
75use rich_ext::plugin::PluginComponent;
76use rich_ext::plugin::component::{
77 ComponentBinding, ComponentContext, ComponentEvent, ComponentFlow,
78};
79use rich_ext::registry::ExtensionRegistry;
80
81use crate::component::{Component, Context, Flow, View};
82use crate::event::{Event, Key, MouseKind};
83use crate::keymap::{Binding, Keymap};
84use crate::session::catch_panic;
85
86#[derive(Clone, Debug, PartialEq, Eq)]
88pub struct UnknownComponent {
89 pub name: String,
91 pub available: Vec<String>,
93}
94
95impl fmt::Display for UnknownComponent {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 write!(f, "no plugin component {:?}", self.name)?;
98 if self.available.is_empty() {
99 write!(f, "; no plugin registered one")
100 } else {
101 write!(f, "; available: {}", self.available.join(", "))
102 }
103 }
104}
105
106impl std::error::Error for UnknownComponent {}
107
108pub struct PluginView {
111 name: String,
112 inner: Box<dyn PluginComponent>,
113 failed: Option<String>,
115 keymap: Keymap,
117}
118
119impl PluginView {
120 pub fn new(name: impl Into<String>, component: Box<dyn PluginComponent>) -> PluginView {
122 let name = name.into();
123 PluginView {
124 keymap: Keymap::new(name.clone()),
125 name,
126 inner: component,
127 failed: None,
128 }
129 }
130
131 pub fn mount(registry: &ExtensionRegistry, name: &str) -> Result<PluginView, UnknownComponent> {
134 registry
135 .create_component(name)
136 .map(|component| PluginView::new(name, component))
137 .ok_or_else(|| UnknownComponent {
138 name: name.to_string(),
139 available: registry
140 .component_names()
141 .into_iter()
142 .map(|(name, _)| name.to_string())
143 .collect(),
144 })
145 }
146
147 pub fn name(&self) -> &str {
149 &self.name
150 }
151
152 #[must_use]
154 pub fn rebind(mut self, action: &str, keys: impl IntoIterator<Item = Key>) -> Self {
155 self.keymap.rebind(action, keys);
156 self
157 }
158
159 pub fn failure(&self) -> Option<&str> {
161 self.failed.as_deref()
162 }
163
164 fn declared(&self) -> Vec<ComponentBinding> {
166 catch_panic(AssertUnwindSafe(|| self.inner.bindings())).unwrap_or_default()
167 }
168
169 fn bindings(&self) -> Keymap {
171 self.keymap_of(self.declared())
172 }
173
174 fn keymap_of(&self, declared: Vec<ComponentBinding>) -> Keymap {
175 let mut keymap = self.keymap.clone();
176 for binding in declared {
177 let keys = binding.keys.iter().filter_map(|name| Key::parse(name));
178 keymap.add(Binding::new(
179 self.name.clone(),
180 binding.action,
181 keys,
182 binding.description,
183 ));
184 }
185 keymap
186 }
187
188 fn guard<T>(&mut self, f: impl FnOnce(&mut dyn PluginComponent) -> T) -> Option<T> {
190 if self.failed.is_some() {
191 return None;
192 }
193 match catch_panic(AssertUnwindSafe(|| f(&mut *self.inner))) {
194 Ok(value) => Some(value),
195 Err(panic) => {
196 self.failed = Some(panic_message(&*panic));
197 None
198 }
199 }
200 }
201}
202
203fn translate(keymap: &Keymap, declared: &[ComponentBinding], key: Key) -> Option<String> {
210 if let Some(action) = keymap.action(key) {
211 let name = declared
212 .iter()
213 .find(|binding| binding.action == action)
214 .and_then(|binding| binding.keys.iter().find(|name| Key::parse(name).is_some()));
215 return Some(name.cloned().unwrap_or_else(|| key.to_string()));
216 }
217 if keymap
218 .declared()
219 .iter()
220 .any(|binding| key.matches_any(&binding.keys))
221 {
222 return None;
223 }
224 Some(key.to_string())
225}
226
227fn event(value: &Event) -> Option<ComponentEvent> {
228 Some(match value {
229 Event::Key(key) => ComponentEvent::Key(key.to_string()),
230 Event::Paste(text) => ComponentEvent::Paste(text.clone()),
231 Event::Resize { columns, rows } => ComponentEvent::Resize {
232 columns: *columns,
233 rows: *rows,
234 },
235 Event::Tick => ComponentEvent::Tick,
236 Event::Mouse(mouse) => ComponentEvent::Mouse {
237 kind: match mouse.kind {
238 MouseKind::Down(_) => "down",
239 MouseKind::Up(_) => "up",
240 MouseKind::Drag(_) => "drag",
241 MouseKind::Moved => "moved",
242 MouseKind::ScrollUp => "scroll_up",
243 MouseKind::ScrollDown => "scroll_down",
244 }
245 .to_string(),
246 column: mouse.column,
247 row: mouse.row,
248 },
249 Event::Link(url) => ComponentEvent::Link(url.clone()),
250 Event::Returned(_) => return None,
252 Event::KeyUp(_) => return None,
254 })
255}
256
257impl Component for PluginView {
258 type Output = String;
259
260 fn handle(&mut self, value: &Event, context: &Context<'_>) -> Flow<String> {
261 let event = match value {
262 Event::Key(key) => {
263 let declared = self.declared();
264 match translate(&self.keymap_of(declared.clone()), &declared, *key) {
265 Some(name) => ComponentEvent::Key(name),
266 None => return Flow::Ignored,
267 }
268 }
269 other => match event(other) {
270 Some(event) => event,
271 None => return Flow::Ignored,
272 },
273 };
274 let inner = ComponentContext::new(context.console, context.width, context.height);
275 match self.guard(|component| component.handle(&event, &inner)) {
276 Some(ComponentFlow::Continue) => Flow::Continue,
277 Some(ComponentFlow::Done(answer)) => Flow::Done(answer),
278 Some(ComponentFlow::Cancel) => Flow::Cancel,
279 _ => Flow::Ignored,
280 }
281 }
282
283 fn render(&self, context: &Context<'_>) -> View {
284 if let Some(message) = &self.failed {
285 return self.failure_view(message, context);
286 }
287 let inner = ComponentContext::new(context.console, context.width, context.height);
288 match catch_panic(AssertUnwindSafe(|| self.inner.render(&inner))) {
289 Ok(view) => View {
290 lines: view.lines,
291 cursor: view.cursor,
292 },
293 Err(panic) => self.failure_view(&panic_message(&*panic), context),
296 }
297 }
298
299 fn tick(&self) -> Option<Duration> {
300 if self.failed.is_some() {
301 return None;
302 }
303 catch_panic(AssertUnwindSafe(|| self.inner.tick())).unwrap_or(None)
304 }
305
306 fn mouse(&self) -> bool {
307 self.failed.is_none()
308 && catch_panic(AssertUnwindSafe(|| self.inner.mouse())).unwrap_or(false)
309 }
310
311 fn keymap(&self) -> Keymap {
312 if self.failed.is_some() {
313 return Keymap::default();
314 }
315 self.bindings()
316 }
317
318 fn focusable(&self) -> bool {
319 self.failed.is_none()
320 && catch_panic(AssertUnwindSafe(|| self.inner.focusable())).unwrap_or(false)
321 }
322}
323
324impl PluginView {
325 fn failure_view(&self, message: &str, context: &Context<'_>) -> View {
326 let message = format!("plugin component {} failed: {message}", self.name);
327 let mut text = rich::Text::new(&message);
328 let length = text.plain().chars().count();
329 text.stylize(Style::parse("red").unwrap_or_default(), 0, length);
330 View::new(context.lines(&text))
331 }
332}
333
334fn panic_message(panic: &(dyn std::any::Any + Send)) -> String {
336 if let Some(message) = panic.downcast_ref::<&str>() {
337 (*message).to_string()
338 } else if let Some(message) = panic.downcast_ref::<String>() {
339 message.clone()
340 } else {
341 "a panic with no message".to_string()
342 }
343}