Skip to main content

imp_tui/
tui_interface.rs

1use std::sync::Arc;
2
3use async_trait::async_trait;
4use imp_core::ui::{ComponentSpec, NotifyLevel, SelectOption, UserInterface, WidgetContent};
5use tokio::sync::mpsc;
6
7/// Events sent from the TuiInterface to the main App event loop.
8#[derive(Debug)]
9pub enum UiRequest {
10    Notify {
11        message: String,
12        level: NotifyLevel,
13    },
14    Confirm {
15        title: String,
16        message: String,
17        reply: tokio::sync::oneshot::Sender<Option<bool>>,
18    },
19    Select {
20        title: String,
21        context: String,
22        options: Vec<SelectOption>,
23        reply: tokio::sync::oneshot::Sender<Option<usize>>,
24    },
25    Input {
26        title: String,
27        context: String,
28        placeholder: String,
29        reply: tokio::sync::oneshot::Sender<Option<String>>,
30    },
31    SetStatus {
32        key: String,
33        text: Option<String>,
34    },
35    SetWidget {
36        key: String,
37        content: Option<WidgetContent>,
38    },
39    Custom {
40        component: ComponentSpec,
41        reply: tokio::sync::oneshot::Sender<Option<serde_json::Value>>,
42    },
43}
44
45/// UserInterface implementation that sends requests to the TUI event loop.
46///
47/// Tools and extensions call methods on this trait. The implementation
48/// sends a request to the main event loop, which renders the appropriate
49/// UI element and sends back the response.
50pub struct TuiInterface {
51    tx: mpsc::Sender<UiRequest>,
52}
53
54impl TuiInterface {
55    pub fn new(tx: mpsc::Sender<UiRequest>) -> Arc<Self> {
56        Arc::new(Self { tx })
57    }
58}
59
60#[async_trait]
61impl UserInterface for TuiInterface {
62    fn has_ui(&self) -> bool {
63        true
64    }
65
66    async fn notify(&self, message: &str, level: NotifyLevel) {
67        let _ = self
68            .tx
69            .send(UiRequest::Notify {
70                message: message.to_string(),
71                level,
72            })
73            .await;
74    }
75
76    async fn confirm(&self, title: &str, message: &str) -> Option<bool> {
77        let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
78        let _ = self
79            .tx
80            .send(UiRequest::Confirm {
81                title: title.to_string(),
82                message: message.to_string(),
83                reply: reply_tx,
84            })
85            .await;
86        reply_rx.await.ok().flatten()
87    }
88
89    async fn select_with_context(
90        &self,
91        title: &str,
92        context: &str,
93        options: &[SelectOption],
94    ) -> Option<usize> {
95        let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
96        let _ = self
97            .tx
98            .send(UiRequest::Select {
99                title: title.to_string(),
100                context: context.to_string(),
101                options: options.to_vec(),
102                reply: reply_tx,
103            })
104            .await;
105        reply_rx.await.ok().flatten()
106    }
107
108    async fn input_with_context(
109        &self,
110        title: &str,
111        context: &str,
112        placeholder: &str,
113    ) -> Option<String> {
114        let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
115        let _ = self
116            .tx
117            .send(UiRequest::Input {
118                title: title.to_string(),
119                context: context.to_string(),
120                placeholder: placeholder.to_string(),
121                reply: reply_tx,
122            })
123            .await;
124        reply_rx.await.ok().flatten()
125    }
126
127    async fn set_status(&self, key: &str, text: Option<&str>) {
128        let _ = self
129            .tx
130            .send(UiRequest::SetStatus {
131                key: key.to_string(),
132                text: text.map(String::from),
133            })
134            .await;
135    }
136
137    async fn set_widget(&self, key: &str, content: Option<WidgetContent>) {
138        let _ = self
139            .tx
140            .send(UiRequest::SetWidget {
141                key: key.to_string(),
142                content,
143            })
144            .await;
145    }
146
147    async fn custom(&self, component: ComponentSpec) -> Option<serde_json::Value> {
148        let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
149        let _ = self
150            .tx
151            .send(UiRequest::Custom {
152                component,
153                reply: reply_tx,
154            })
155            .await;
156        reply_rx.await.ok().flatten()
157    }
158}