lla_plugin_utils/
lib.rs

1pub mod actions;
2pub mod config;
3pub mod format;
4pub mod syntax;
5pub mod ui;
6
7pub use actions::{Action, ActionHelp, ActionRegistry};
8pub use config::{ConfigManager, PluginConfig};
9pub use syntax::CodeHighlighter;
10pub use ui::{
11    components::{BoxComponent, BoxStyle, HelpFormatter, KeyValue, List, Spinner},
12    TextBlock, TextStyle,
13};
14
15use lla_plugin_interface::{proto, PluginRequest, PluginResponse};
16
17pub struct BasePlugin<C: PluginConfig> {
18    config_manager: ConfigManager<C>,
19}
20
21impl<C: PluginConfig + Default> BasePlugin<C> {
22    pub fn new() -> Self {
23        let plugin_name = env!("CARGO_PKG_NAME");
24        Self {
25            config_manager: ConfigManager::new(plugin_name),
26        }
27    }
28
29    pub fn with_name(plugin_name: &str) -> Self {
30        Self {
31            config_manager: ConfigManager::new(plugin_name),
32        }
33    }
34
35    pub fn config(&self) -> &C {
36        self.config_manager.get()
37    }
38
39    pub fn config_mut(&mut self) -> &mut C {
40        self.config_manager.get_mut()
41    }
42
43    pub fn save_config(&self) -> Result<(), String> {
44        self.config_manager.save()
45    }
46}
47
48pub trait ConfigurablePlugin {
49    type Config: PluginConfig;
50
51    fn config(&self) -> &Self::Config;
52    fn config_mut(&mut self) -> &mut Self::Config;
53}
54
55pub trait ProtobufHandler {
56    fn decode_request(&self, request: &[u8]) -> Result<PluginRequest, String> {
57        use prost::Message;
58        let proto_msg = proto::PluginMessage::decode(request)
59            .map_err(|e| format!("Failed to decode request: {}", e))?;
60
61        match proto_msg.message {
62            Some(proto::plugin_message::Message::GetName(_)) => Ok(PluginRequest::GetName),
63            Some(proto::plugin_message::Message::GetVersion(_)) => Ok(PluginRequest::GetVersion),
64            Some(proto::plugin_message::Message::GetDescription(_)) => {
65                Ok(PluginRequest::GetDescription)
66            }
67            Some(proto::plugin_message::Message::GetSupportedFormats(_)) => {
68                Ok(PluginRequest::GetSupportedFormats)
69            }
70            Some(proto::plugin_message::Message::Decorate(entry)) => {
71                let metadata = entry
72                    .metadata
73                    .map(|m| lla_plugin_interface::EntryMetadata {
74                        size: m.size,
75                        modified: m.modified,
76                        accessed: m.accessed,
77                        created: m.created,
78                        is_dir: m.is_dir,
79                        is_file: m.is_file,
80                        is_symlink: m.is_symlink,
81                        permissions: m.permissions,
82                        uid: m.uid,
83                        gid: m.gid,
84                    })
85                    .ok_or("Missing metadata in decorated entry")?;
86
87                let decorated = lla_plugin_interface::DecoratedEntry {
88                    path: std::path::PathBuf::from(entry.path),
89                    metadata,
90                    custom_fields: entry.custom_fields,
91                };
92                Ok(PluginRequest::Decorate(decorated))
93            }
94            Some(proto::plugin_message::Message::FormatField(req)) => {
95                let entry = req.entry.ok_or("Missing entry in format field request")?;
96                let metadata = entry
97                    .metadata
98                    .map(|m| lla_plugin_interface::EntryMetadata {
99                        size: m.size,
100                        modified: m.modified,
101                        accessed: m.accessed,
102                        created: m.created,
103                        is_dir: m.is_dir,
104                        is_file: m.is_file,
105                        is_symlink: m.is_symlink,
106                        permissions: m.permissions,
107                        uid: m.uid,
108                        gid: m.gid,
109                    })
110                    .ok_or("Missing metadata in decorated entry")?;
111
112                let decorated = lla_plugin_interface::DecoratedEntry {
113                    path: std::path::PathBuf::from(entry.path),
114                    metadata,
115                    custom_fields: entry.custom_fields,
116                };
117                Ok(PluginRequest::FormatField(decorated, req.format))
118            }
119            Some(proto::plugin_message::Message::Action(req)) => {
120                Ok(PluginRequest::PerformAction(req.action, req.args))
121            }
122            _ => Err("Invalid request type".to_string()),
123        }
124    }
125
126    fn encode_response(&self, response: PluginResponse) -> Vec<u8> {
127        use prost::Message;
128        let response_msg = match response {
129            PluginResponse::Name(name) => proto::plugin_message::Message::NameResponse(name),
130            PluginResponse::Version(version) => {
131                proto::plugin_message::Message::VersionResponse(version)
132            }
133            PluginResponse::Description(desc) => {
134                proto::plugin_message::Message::DescriptionResponse(desc)
135            }
136            PluginResponse::SupportedFormats(formats) => {
137                proto::plugin_message::Message::FormatsResponse(proto::SupportedFormatsResponse {
138                    formats,
139                })
140            }
141            PluginResponse::Decorated(entry) => {
142                let proto_metadata = proto::EntryMetadata {
143                    size: entry.metadata.size,
144                    modified: entry.metadata.modified,
145                    accessed: entry.metadata.accessed,
146                    created: entry.metadata.created,
147                    is_dir: entry.metadata.is_dir,
148                    is_file: entry.metadata.is_file,
149                    is_symlink: entry.metadata.is_symlink,
150                    permissions: entry.metadata.permissions,
151                    uid: entry.metadata.uid,
152                    gid: entry.metadata.gid,
153                };
154
155                let proto_entry = proto::DecoratedEntry {
156                    path: entry.path.to_string_lossy().to_string(),
157                    metadata: Some(proto_metadata),
158                    custom_fields: entry.custom_fields,
159                };
160                proto::plugin_message::Message::DecoratedResponse(proto_entry)
161            }
162            PluginResponse::FormattedField(field) => {
163                proto::plugin_message::Message::FieldResponse(proto::FormattedFieldResponse {
164                    field,
165                })
166            }
167            PluginResponse::ActionResult(result) => match result {
168                Ok(()) => proto::plugin_message::Message::ActionResponse(proto::ActionResponse {
169                    success: true,
170                    error: None,
171                }),
172                Err(e) => proto::plugin_message::Message::ActionResponse(proto::ActionResponse {
173                    success: false,
174                    error: Some(e),
175                }),
176            },
177            PluginResponse::Error(e) => proto::plugin_message::Message::ErrorResponse(e),
178        };
179
180        let proto_msg = proto::PluginMessage {
181            message: Some(response_msg),
182        };
183        let mut buf = bytes::BytesMut::with_capacity(proto_msg.encoded_len());
184        proto_msg.encode(&mut buf).unwrap();
185        buf.to_vec()
186    }
187
188    fn encode_error(&self, error: &str) -> Vec<u8> {
189        use prost::Message;
190        let error_msg = proto::PluginMessage {
191            message: Some(proto::plugin_message::Message::ErrorResponse(
192                error.to_string(),
193            )),
194        };
195        let mut buf = bytes::BytesMut::with_capacity(error_msg.encoded_len());
196        error_msg.encode(&mut buf).unwrap();
197        buf.to_vec()
198    }
199}
200
201#[macro_export]
202macro_rules! plugin_action {
203    ($registry:expr, $name:expr, $usage:expr, $description:expr, $examples:expr, $handler:expr) => {
204        $crate::define_action!($registry, $name, $usage, $description, $examples, $handler);
205    };
206}
207
208#[macro_export]
209macro_rules! create_plugin {
210    ($plugin:ty) => {
211        impl Default for $plugin {
212            fn default() -> Self {
213                Self::new()
214            }
215        }
216
217        impl $crate::ConfigurablePlugin for $plugin {
218            type Config = <$plugin as std::ops::Deref>::Target;
219
220            fn config(&self) -> &Self::Config {
221                self.base.config()
222            }
223
224            fn config_mut(&mut self) -> &mut Self::Config {
225                self.base.config_mut()
226            }
227        }
228
229        impl $crate::ProtobufHandler for $plugin {}
230
231        lla_plugin_interface::declare_plugin!($plugin);
232    };
233}