Skip to main content

imp_core/
ui.rs

1use async_trait::async_trait;
2use serde::{Deserialize, Serialize};
3
4/// Abstraction over user interaction. Tools and extensions use this
5/// without knowing whether they're in a TUI, headless, or print mode.
6#[async_trait]
7pub trait UserInterface: Send + Sync {
8    /// Whether this interface can show interactive UI.
9    fn has_ui(&self) -> bool;
10
11    /// Non-blocking notification.
12    async fn notify(&self, message: &str, level: NotifyLevel);
13
14    /// Yes/no confirmation. Returns None if no UI or cancelled.
15    async fn confirm(&self, title: &str, message: &str) -> Option<bool>;
16
17    /// Select from options. Returns None if no UI or cancelled.
18    async fn select(&self, title: &str, options: &[SelectOption]) -> Option<usize> {
19        self.select_with_context(title, "", options).await
20    }
21
22    /// Select from options with context. Returns None if no UI or cancelled.
23    async fn select_with_context(
24        &self,
25        title: &str,
26        context: &str,
27        options: &[SelectOption],
28    ) -> Option<usize>;
29
30    /// Text input. Returns None if no UI or cancelled.
31    async fn input(&self, title: &str, placeholder: &str) -> Option<String> {
32        self.input_with_context(title, "", placeholder).await
33    }
34
35    /// Text input with context. Returns None if no UI or cancelled.
36    async fn input_with_context(
37        &self,
38        title: &str,
39        context: &str,
40        placeholder: &str,
41    ) -> Option<String>;
42
43    /// Persistent status in footer.
44    async fn set_status(&self, key: &str, text: Option<&str>);
45
46    /// Widget above/below editor.
47    async fn set_widget(&self, key: &str, content: Option<WidgetContent>);
48
49    /// Full declarative custom component. Returns the serialized result.
50    async fn custom(&self, component: ComponentSpec) -> Option<serde_json::Value>;
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
54pub enum NotifyLevel {
55    Info,
56    Warning,
57    Error,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct SelectOption {
62    pub label: String,
63    pub description: Option<String>,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub enum WidgetContent {
68    Lines(Vec<String>),
69    Component(ComponentSpec),
70}
71
72/// Declarative component specification (from Lua tables or native code).
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct ComponentSpec {
75    pub component_type: String,
76    pub props: serde_json::Value,
77    pub children: Vec<ComponentSpec>,
78}
79
80/// Null interface for print mode — returns None for everything.
81pub struct NullInterface;
82
83#[async_trait]
84impl UserInterface for NullInterface {
85    fn has_ui(&self) -> bool {
86        false
87    }
88    async fn notify(&self, _message: &str, _level: NotifyLevel) {}
89    async fn confirm(&self, _title: &str, _message: &str) -> Option<bool> {
90        None
91    }
92    async fn select_with_context(
93        &self,
94        _title: &str,
95        _context: &str,
96        _options: &[SelectOption],
97    ) -> Option<usize> {
98        None
99    }
100    async fn input_with_context(
101        &self,
102        _title: &str,
103        _context: &str,
104        _placeholder: &str,
105    ) -> Option<String> {
106        None
107    }
108    async fn set_status(&self, _key: &str, _text: Option<&str>) {}
109    async fn set_widget(&self, _key: &str, _content: Option<WidgetContent>) {}
110    async fn custom(&self, _component: ComponentSpec) -> Option<serde_json::Value> {
111        None
112    }
113}