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}