1use std::path::PathBuf;
18
19use serde::{Deserialize, Deserializer, Serialize};
20use serde_json::{self, Value};
21
22use crate::syntax::{LanguageDefinition, LanguageId};
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
28#[serde(rename_all = "snake_case")]
29pub struct PluginDescription {
30 pub name: String,
31 pub version: String,
32 #[serde(default)]
33 pub scope: PluginScope,
34 #[serde(deserialize_with = "platform_exec_path")]
37 pub exec_path: PathBuf,
38 #[serde(default)]
40 pub activations: Vec<PluginActivation>,
41 #[serde(default)]
42 pub commands: Vec<Command>,
43 #[serde(default)]
44 pub languages: Vec<LanguageDefinition>,
45}
46
47fn platform_exec_path<'de, D: Deserializer<'de>>(deserializer: D) -> Result<PathBuf, D::Error> {
48 let exec_path = PathBuf::deserialize(deserializer)?;
49 if cfg!(windows) {
50 Ok(exec_path.with_extension("exe"))
51 } else {
52 Ok(exec_path)
53 }
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58#[serde(rename_all = "snake_case")]
59pub enum PluginActivation {
60 Autorun,
62 #[allow(dead_code)]
64 OnSyntax(LanguageId),
65 #[allow(dead_code)]
67 OnCommand,
68}
69
70#[derive(Debug, Clone, Deserialize, Serialize)]
72#[serde(rename_all = "snake_case")]
73pub enum PluginScope {
74 Global,
76 BufferLocal,
78 SingleInvocation,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct Command {
86 pub title: String,
88 pub description: String,
90 pub rpc_cmd: PlaceholderRpc,
92 pub args: Vec<CommandArgument>,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct CommandArgument {
99 pub title: String,
102 pub description: String,
104 pub key: String,
105 pub arg_type: ArgumentType,
106 #[serde(skip_serializing_if = "Option::is_none")]
107 pub options: Option<Vec<ArgumentOption>>,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
112pub enum ArgumentType {
113 Number,
114 Int,
115 PosInt,
116 Bool,
117 String,
118 Choice,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
122pub struct ArgumentOption {
124 pub title: String,
125 pub value: Value,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
129#[serde(rename_all = "snake_case")]
130pub struct PlaceholderRpc {
135 pub method: String,
136 pub params: Value,
137 pub rpc_type: RpcType,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
141#[serde(rename_all = "snake_case")]
142pub enum RpcType {
143 Notification,
144 Request,
145}
146
147impl Command {
148 pub fn new<S, V>(title: S, description: S, rpc_cmd: PlaceholderRpc, args: V) -> Self
149 where
150 S: AsRef<str>,
151 V: Into<Option<Vec<CommandArgument>>>,
152 {
153 let title = title.as_ref().to_owned();
154 let description = description.as_ref().to_owned();
155 let args = args.into().unwrap_or_else(Vec::new);
156 Command { title, description, rpc_cmd, args }
157 }
158}
159
160impl CommandArgument {
161 pub fn new<S: AsRef<str>>(
162 title: S,
163 description: S,
164 key: S,
165 arg_type: ArgumentType,
166 options: Option<Vec<ArgumentOption>>,
167 ) -> Self {
168 let key = key.as_ref().to_owned();
169 let title = title.as_ref().to_owned();
170 let description = description.as_ref().to_owned();
171 if arg_type == ArgumentType::Choice {
172 assert!(options.is_some())
173 }
174 CommandArgument { title, description, key, arg_type, options }
175 }
176}
177
178impl ArgumentOption {
179 pub fn new<S: AsRef<str>, V: Serialize>(title: S, value: V) -> Self {
180 let title = title.as_ref().to_owned();
181 let value = serde_json::to_value(value).unwrap();
182 ArgumentOption { title, value }
183 }
184}
185
186impl PlaceholderRpc {
187 pub fn new<S, V>(method: S, params: V, request: bool) -> Self
188 where
189 S: AsRef<str>,
190 V: Into<Option<Value>>,
191 {
192 let method = method.as_ref().to_owned();
193 let params = params.into().unwrap_or(json!({}));
194 let rpc_type = if request { RpcType::Request } else { RpcType::Notification };
195
196 PlaceholderRpc { method, params, rpc_type }
197 }
198
199 pub fn is_request(&self) -> bool {
200 self.rpc_type == RpcType::Request
201 }
202
203 pub fn params_ref(&self) -> &Value {
205 &self.params
206 }
207
208 pub fn params_ref_mut(&mut self) -> &mut Value {
210 &mut self.params
211 }
212
213 pub fn method_ref(&self) -> &str {
215 &self.method
216 }
217}
218
219impl PluginDescription {
220 pub fn is_global(&self) -> bool {
222 match self.scope {
223 PluginScope::Global => true,
224 _ => false,
225 }
226 }
227}
228
229impl Default for PluginScope {
230 fn default() -> Self {
231 PluginScope::BufferLocal
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238 use serde_json;
239
240 #[test]
241 fn platform_exec_path() {
242 let json = r#"
243 {
244 "name": "test_plugin",
245 "version": "0.0.0",
246 "scope": "global",
247 "exec_path": "path/to/binary",
248 "activations": [],
249 "commands": [],
250 "languages": []
251 }
252 "#;
253
254 let plugin_desc: PluginDescription = serde_json::from_str(&json).unwrap();
255 if cfg!(windows) {
256 assert!(plugin_desc.exec_path.ends_with("binary.exe"));
257 } else {
258 assert!(plugin_desc.exec_path.ends_with("binary"));
259 }
260 }
261
262 #[test]
263 fn test_serde_command() {
264 let json = r#"
265 {
266 "title": "Test Command",
267 "description": "Passes the current test",
268 "rpc_cmd": {
269 "rpc_type": "notification",
270 "method": "test.cmd",
271 "params": {
272 "view": "",
273 "non_arg": "plugin supplied value",
274 "arg_one": "",
275 "arg_two": ""
276 }
277 },
278 "args": [
279 {
280 "title": "First argument",
281 "description": "Indicates something",
282 "key": "arg_one",
283 "arg_type": "Bool"
284 },
285 {
286 "title": "Favourite Number",
287 "description": "A number used in a test.",
288 "key": "arg_two",
289 "arg_type": "Choice",
290 "options": [
291 {"title": "Five", "value": 5},
292 {"title": "Ten", "value": 10}
293 ]
294 }
295 ]
296 }
297 "#;
298
299 let command: Command = serde_json::from_str(&json).unwrap();
300 assert_eq!(command.title, "Test Command");
301 assert_eq!(command.args[0].arg_type, ArgumentType::Bool);
302 assert_eq!(command.rpc_cmd.params_ref()["non_arg"], "plugin supplied value");
303 assert_eq!(command.args[1].options.clone().unwrap()[1].value, json!(10));
304 }
305}