use std::path::PathBuf;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::{self, Value};
use crate::syntax::{LanguageDefinition, LanguageId};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct PluginDescription {
pub name: String,
pub version: String,
#[serde(default)]
pub scope: PluginScope,
#[serde(deserialize_with = "platform_exec_path")]
pub exec_path: PathBuf,
#[serde(default)]
pub activations: Vec<PluginActivation>,
#[serde(default)]
pub commands: Vec<Command>,
#[serde(default)]
pub languages: Vec<LanguageDefinition>,
}
fn platform_exec_path<'de, D: Deserializer<'de>>(deserializer: D) -> Result<PathBuf, D::Error> {
let exec_path = PathBuf::deserialize(deserializer)?;
if cfg!(windows) {
Ok(exec_path.with_extension("exe"))
} else {
Ok(exec_path)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PluginActivation {
Autorun,
#[allow(dead_code)]
OnSyntax(LanguageId),
#[allow(dead_code)]
OnCommand,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum PluginScope {
Global,
BufferLocal,
SingleInvocation,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Command {
pub title: String,
pub description: String,
pub rpc_cmd: PlaceholderRpc,
pub args: Vec<CommandArgument>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandArgument {
pub title: String,
pub description: String,
pub key: String,
pub arg_type: ArgumentType,
#[serde(skip_serializing_if = "Option::is_none")]
pub options: Option<Vec<ArgumentOption>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ArgumentType {
Number,
Int,
PosInt,
Bool,
String,
Choice,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ArgumentOption {
pub title: String,
pub value: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct PlaceholderRpc {
pub method: String,
pub params: Value,
pub rpc_type: RpcType,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum RpcType {
Notification,
Request,
}
impl Command {
pub fn new<S, V>(title: S, description: S, rpc_cmd: PlaceholderRpc, args: V) -> Self
where
S: AsRef<str>,
V: Into<Option<Vec<CommandArgument>>>,
{
let title = title.as_ref().to_owned();
let description = description.as_ref().to_owned();
let args = args.into().unwrap_or_else(Vec::new);
Command { title, description, rpc_cmd, args }
}
}
impl CommandArgument {
pub fn new<S: AsRef<str>>(
title: S,
description: S,
key: S,
arg_type: ArgumentType,
options: Option<Vec<ArgumentOption>>,
) -> Self {
let key = key.as_ref().to_owned();
let title = title.as_ref().to_owned();
let description = description.as_ref().to_owned();
if arg_type == ArgumentType::Choice {
assert!(options.is_some())
}
CommandArgument { title, description, key, arg_type, options }
}
}
impl ArgumentOption {
pub fn new<S: AsRef<str>, V: Serialize>(title: S, value: V) -> Self {
let title = title.as_ref().to_owned();
let value = serde_json::to_value(value).unwrap();
ArgumentOption { title, value }
}
}
impl PlaceholderRpc {
pub fn new<S, V>(method: S, params: V, request: bool) -> Self
where
S: AsRef<str>,
V: Into<Option<Value>>,
{
let method = method.as_ref().to_owned();
let params = params.into().unwrap_or(json!({}));
let rpc_type = if request { RpcType::Request } else { RpcType::Notification };
PlaceholderRpc { method, params, rpc_type }
}
pub fn is_request(&self) -> bool {
self.rpc_type == RpcType::Request
}
pub fn params_ref(&self) -> &Value {
&self.params
}
pub fn params_ref_mut(&mut self) -> &mut Value {
&mut self.params
}
pub fn method_ref(&self) -> &str {
&self.method
}
}
impl PluginDescription {
pub fn is_global(&self) -> bool {
match self.scope {
PluginScope::Global => true,
_ => false,
}
}
}
impl Default for PluginScope {
fn default() -> Self {
PluginScope::BufferLocal
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json;
#[test]
fn platform_exec_path() {
let json = r#"
{
"name": "test_plugin",
"version": "0.0.0",
"scope": "global",
"exec_path": "path/to/binary",
"activations": [],
"commands": [],
"languages": []
}
"#;
let plugin_desc: PluginDescription = serde_json::from_str(&json).unwrap();
if cfg!(windows) {
assert!(plugin_desc.exec_path.ends_with("binary.exe"));
} else {
assert!(plugin_desc.exec_path.ends_with("binary"));
}
}
#[test]
fn test_serde_command() {
let json = r#"
{
"title": "Test Command",
"description": "Passes the current test",
"rpc_cmd": {
"rpc_type": "notification",
"method": "test.cmd",
"params": {
"view": "",
"non_arg": "plugin supplied value",
"arg_one": "",
"arg_two": ""
}
},
"args": [
{
"title": "First argument",
"description": "Indicates something",
"key": "arg_one",
"arg_type": "Bool"
},
{
"title": "Favourite Number",
"description": "A number used in a test.",
"key": "arg_two",
"arg_type": "Choice",
"options": [
{"title": "Five", "value": 5},
{"title": "Ten", "value": 10}
]
}
]
}
"#;
let command: Command = serde_json::from_str(&json).unwrap();
assert_eq!(command.title, "Test Command");
assert_eq!(command.args[0].arg_type, ArgumentType::Bool);
assert_eq!(command.rpc_cmd.params_ref()["non_arg"], "plugin supplied value");
assert_eq!(command.args[1].options.clone().unwrap()[1].value, json!(10));
}
}