use std::borrow::Borrow;
use std::path::PathBuf;
use serde::de::{self, Deserialize, Deserializer};
use serde::ser::{self, Serialize, Serializer};
use serde_json::{self, Value};
use super::PluginPid;
use crate::annotations::AnnotationType;
use crate::config::Table;
use crate::syntax::LanguageId;
use crate::tabs::{BufferIdentifier, ViewId};
use xi_rope::{LinesMetric, Rope, RopeDelta};
use xi_rpc::RemoteError;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PluginBufferInfo {
pub buffer_id: BufferIdentifier,
pub views: Vec<ViewId>,
pub rev: u64,
pub buf_size: usize,
pub nb_lines: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
pub syntax: LanguageId,
pub config: Table,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ClientPluginInfo {
pub name: String,
pub running: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PluginUpdate {
pub view_id: ViewId,
pub delta: Option<RopeDelta>,
pub new_len: usize,
pub new_line_count: usize,
pub rev: u64,
pub undo_group: Option<usize>,
pub edit_type: String,
pub author: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmptyStruct {}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "method", content = "params")]
pub enum HostRequest {
Update(PluginUpdate),
CollectTrace(EmptyStruct),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "method", content = "params")]
pub enum HostNotification {
Ping(EmptyStruct),
Initialize { plugin_id: PluginPid, buffer_info: Vec<PluginBufferInfo> },
DidSave { view_id: ViewId, path: PathBuf },
ConfigChanged { view_id: ViewId, changes: Table },
NewBuffer { buffer_info: Vec<PluginBufferInfo> },
DidClose { view_id: ViewId },
GetHover { view_id: ViewId, request_id: usize, position: usize },
Shutdown(EmptyStruct),
TracingConfig { enabled: bool },
LanguageChanged { view_id: ViewId, new_lang: LanguageId },
CustomCommand { view_id: ViewId, method: String, params: Value },
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PluginEdit {
pub rev: u64,
pub delta: RopeDelta,
pub priority: u64,
pub after_cursor: bool,
pub undo_group: Option<usize>,
pub author: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub struct ScopeSpan {
pub start: usize,
pub end: usize,
pub scope_id: u32,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct DataSpan {
pub start: usize,
pub end: usize,
pub data: Value,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct GetDataResponse {
pub chunk: String,
pub offset: usize,
pub first_line: usize,
pub first_line_offset: usize,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
#[serde(rename_all = "snake_case")]
pub enum TextUnit {
Utf8,
Line,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "method", content = "params")]
pub enum PluginRequest {
GetData { start: usize, unit: TextUnit, max_size: usize, rev: u64 },
LineCount,
GetSelections,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "method", content = "params")]
pub enum PluginNotification {
AddScopes {
scopes: Vec<Vec<String>>,
},
UpdateSpans {
start: usize,
len: usize,
spans: Vec<ScopeSpan>,
rev: u64,
},
Edit {
edit: PluginEdit,
},
Alert {
msg: String,
},
AddStatusItem {
key: String,
value: String,
alignment: String,
},
UpdateStatusItem {
key: String,
value: String,
},
RemoveStatusItem {
key: String,
},
ShowHover {
request_id: usize,
result: Result<Hover, RemoteError>,
},
UpdateAnnotations {
start: usize,
len: usize,
spans: Vec<DataSpan>,
annotation_type: AnnotationType,
rev: u64,
},
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct Range {
pub start: usize,
pub end: usize,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct Hover {
pub content: String,
pub range: Option<Range>,
}
pub struct PluginCommand<T> {
pub view_id: ViewId,
pub plugin_id: PluginPid,
pub cmd: T,
}
impl<T: Serialize> Serialize for PluginCommand<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut v = serde_json::to_value(&self.cmd).map_err(ser::Error::custom)?;
v["params"]["view_id"] = json!(self.view_id);
v["params"]["plugin_id"] = json!(self.plugin_id);
v.serialize(serializer)
}
}
impl<'de, T: Deserialize<'de>> Deserialize<'de> for PluginCommand<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct InnerIds {
view_id: ViewId,
plugin_id: PluginPid,
}
#[derive(Deserialize)]
struct IdsWrapper {
params: InnerIds,
}
let v = Value::deserialize(deserializer)?;
let helper = IdsWrapper::deserialize(&v).map_err(de::Error::custom)?;
let InnerIds { view_id, plugin_id } = helper.params;
let cmd = T::deserialize(v).map_err(de::Error::custom)?;
Ok(PluginCommand { view_id, plugin_id, cmd })
}
}
impl PluginBufferInfo {
pub fn new(
buffer_id: BufferIdentifier,
views: &[ViewId],
rev: u64,
buf_size: usize,
nb_lines: usize,
path: Option<PathBuf>,
syntax: LanguageId,
config: Table,
) -> Self {
let path = path.map(|p| p.to_str().unwrap().to_owned());
let views = views.to_owned();
PluginBufferInfo { buffer_id, views, rev, buf_size, nb_lines, path, syntax, config }
}
}
impl PluginUpdate {
pub fn new<D>(
view_id: ViewId,
rev: u64,
delta: D,
new_len: usize,
new_line_count: usize,
undo_group: Option<usize>,
edit_type: String,
author: String,
) -> Self
where
D: Into<Option<RopeDelta>>,
{
let delta = delta.into();
PluginUpdate { view_id, delta, new_len, new_line_count, rev, undo_group, edit_type, author }
}
}
impl TextUnit {
pub fn resolve_offset<T: Borrow<Rope>>(self, text: T, offset: usize) -> Option<usize> {
let text = text.borrow();
match self {
TextUnit::Utf8 => {
if offset > text.len() {
None
} else {
text.at_or_prev_codepoint_boundary(offset)
}
}
TextUnit::Line => {
let max_line_number = text.measure::<LinesMetric>() + 1;
if offset > max_line_number {
None
} else {
text.offset_of_line(offset).into()
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json;
#[test]
fn test_plugin_update() {
let json = r#"{
"view_id": "view-id-42",
"delta": {"base_len": 6, "els": [{"copy": [0,5]}, {"insert":"rofls"}, {"copy": [5,6]}]},
"new_len": 11,
"new_line_count": 1,
"rev": 5,
"undo_group": 6,
"edit_type": "something",
"author": "me"
}"#;
let val: PluginUpdate = match serde_json::from_str(json) {
Ok(val) => val,
Err(err) => panic!("{:?}", err),
};
assert!(val.delta.is_some());
assert!(val.delta.unwrap().as_simple_insert().is_some());
}
#[test]
fn test_deserde_init() {
let json = r#"
{"buffer_id": 42,
"views": ["view-id-4"],
"rev": 1,
"buf_size": 20,
"nb_lines": 5,
"path": "some_path",
"syntax": "toml",
"config": {"some_key": 420}}"#;
let val: PluginBufferInfo = match serde_json::from_str(json) {
Ok(val) => val,
Err(err) => panic!("{:?}", err),
};
assert_eq!(val.rev, 1);
assert_eq!(val.path, Some("some_path".to_owned()));
assert_eq!(val.syntax, "toml".into());
}
#[test]
fn test_de_plugin_rpc() {
let json = r#"{"method": "alert", "params": {"view_id": "view-id-1", "plugin_id": 42, "msg": "ahhh!"}}"#;
let de: PluginCommand<PluginNotification> = serde_json::from_str(json).unwrap();
assert_eq!(de.view_id, ViewId(1));
assert_eq!(de.plugin_id, PluginPid(42));
match de.cmd {
PluginNotification::Alert { ref msg } if msg == "ahhh!" => (),
_ => panic!("{:?}", de.cmd),
}
}
}