use std::path::PathBuf;
use serde::de::{self, Deserialize, Deserializer};
use serde::ser::{self, Serialize, Serializer};
use serde_json::{self, Value};
use crate::config::{ConfigDomainExternal, Table};
use crate::plugins::PlaceholderRpc;
use crate::syntax::LanguageId;
use crate::tabs::ViewId;
use crate::view::Size;
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[doc(hidden)]
pub struct EmptyStruct {}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "method", content = "params")]
pub enum CoreNotification {
Edit(EditCommand<EditNotification>),
Plugin(PluginNotification),
CloseView { view_id: ViewId },
Save { view_id: ViewId, file_path: String },
SetTheme { theme_name: String },
ClientStarted {
#[serde(default)]
config_dir: Option<PathBuf>,
#[serde(default)]
client_extras_dir: Option<PathBuf>,
},
ModifyUserConfig { domain: ConfigDomainExternal, changes: Table },
TracingConfig { enabled: bool },
SaveTrace { destination: PathBuf, frontend_samples: Value },
SetLanguage { view_id: ViewId, language_id: LanguageId },
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "method", content = "params")]
pub enum CoreRequest {
Edit(EditCommand<EditRequest>),
NewView { file_path: Option<String> },
GetConfig { view_id: ViewId },
DebugGetContents { view_id: ViewId },
}
#[derive(Debug, Clone, PartialEq)]
pub struct EditCommand<T> {
pub view_id: ViewId,
pub cmd: T,
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Copy, Clone)]
#[serde(rename_all = "snake_case")]
pub enum SelectionGranularity {
Point,
Word,
Line,
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Copy, Clone)]
#[serde(rename_all = "snake_case")]
pub enum GestureType {
Select { granularity: SelectionGranularity, multi: bool },
SelectExtend { granularity: SelectionGranularity },
Drag,
PointSelect,
ToggleSel,
RangeSelect,
LineSelect,
WordSelect,
MultiLineSelect,
MultiWordSelect,
}
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct LineRange {
pub first: i64,
pub last: i64,
}
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct MouseAction {
pub line: u64,
pub column: u64,
pub flags: u64,
pub click_count: Option<u64>,
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
pub struct Position {
pub line: usize,
pub column: usize,
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub enum SelectionModifier {
None,
Set,
Add,
AddRemovingCurrent,
}
impl Default for SelectionModifier {
fn default() -> SelectionModifier {
SelectionModifier::Set
}
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct FindQuery {
pub id: Option<usize>,
pub chars: String,
pub case_sensitive: bool,
#[serde(default)]
pub regex: bool,
#[serde(default)]
pub whole_words: bool,
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "method", content = "params")]
pub enum EditNotification {
Insert {
chars: String,
},
Paste {
chars: String,
},
DeleteForward,
DeleteBackward,
DeleteWordForward,
DeleteWordBackward,
DeleteToEndOfParagraph,
DeleteToBeginningOfLine,
InsertNewline,
InsertTab,
MoveUp,
MoveUpAndModifySelection,
MoveDown,
MoveDownAndModifySelection,
MoveLeft,
MoveBackward,
MoveLeftAndModifySelection,
MoveRight,
MoveForward,
MoveRightAndModifySelection,
MoveWordLeft,
MoveWordLeftAndModifySelection,
MoveWordRight,
MoveWordRightAndModifySelection,
MoveToBeginningOfParagraph,
MoveToBeginningOfParagraphAndModifySelection,
MoveToEndOfParagraph,
MoveToEndOfParagraphAndModifySelection,
MoveToLeftEndOfLine,
MoveToLeftEndOfLineAndModifySelection,
MoveToRightEndOfLine,
MoveToRightEndOfLineAndModifySelection,
MoveToBeginningOfDocument,
MoveToBeginningOfDocumentAndModifySelection,
MoveToEndOfDocument,
MoveToEndOfDocumentAndModifySelection,
ScrollPageUp,
PageUpAndModifySelection,
ScrollPageDown,
PageDownAndModifySelection,
SelectAll,
AddSelectionAbove,
AddSelectionBelow,
Scroll(LineRange),
Resize(Size),
GotoLine {
line: u64,
},
RequestLines(LineRange),
Yank,
Transpose,
Click(MouseAction),
Drag(MouseAction),
Gesture {
line: u64,
col: u64,
ty: GestureType,
},
Undo,
Redo,
Find {
chars: String,
case_sensitive: bool,
#[serde(default)]
regex: bool,
#[serde(default)]
whole_words: bool,
},
MultiFind {
queries: Vec<FindQuery>,
},
FindNext {
#[serde(default)]
wrap_around: bool,
#[serde(default)]
allow_same: bool,
#[serde(default)]
modify_selection: SelectionModifier,
},
FindPrevious {
#[serde(default)]
wrap_around: bool,
#[serde(default)]
allow_same: bool,
#[serde(default)]
modify_selection: SelectionModifier,
},
FindAll,
DebugRewrap,
DebugWrapWidth,
DebugPrintSpans,
DebugToggleComment,
Uppercase,
Lowercase,
Capitalize,
Reindent,
Indent,
Outdent,
HighlightFind {
visible: bool,
},
SelectionForFind {
#[serde(default)]
case_sensitive: bool,
},
Replace {
chars: String,
#[serde(default)]
preserve_case: bool,
},
ReplaceNext,
ReplaceAll,
SelectionForReplace,
RequestHover {
request_id: usize,
position: Option<Position>,
},
SelectionIntoLines,
DuplicateLine,
IncreaseNumber,
DecreaseNumber,
ToggleRecording {
recording_name: Option<String>,
},
PlayRecording {
recording_name: String,
},
ClearRecording {
recording_name: String,
},
CollapseSelections,
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "method", content = "params")]
pub enum EditRequest {
Cut,
Copy,
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(tag = "command")]
#[serde(rename_all = "snake_case")]
pub enum PluginNotification {
Start { view_id: ViewId, plugin_name: String },
Stop { view_id: ViewId, plugin_name: String },
PluginRpc { view_id: ViewId, receiver: String, rpc: PlaceholderRpc },
}
impl<T: Serialize> Serialize for EditCommand<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["view_id"] = json!(self.view_id);
v.serialize(serializer)
}
}
impl<'de, T: Deserialize<'de>> Deserialize<'de> for EditCommand<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct InnerId {
view_id: ViewId,
}
let mut v = Value::deserialize(deserializer)?;
let helper = InnerId::deserialize(&v).map_err(de::Error::custom)?;
let InnerId { view_id } = helper;
let remove_params = match v.get("params") {
Some(&Value::Object(ref obj)) => obj.is_empty() && T::deserialize(v.clone()).is_err(),
Some(&Value::Array(ref arr)) => arr.is_empty() && T::deserialize(v.clone()).is_err(),
Some(_) => {
return Err(de::Error::custom(
"'params' field, if present, must be object or array.",
));
}
None => false,
};
if remove_params {
v.as_object_mut().map(|v| v.remove("params"));
}
let cmd = T::deserialize(v).map_err(de::Error::custom)?;
Ok(EditCommand { view_id, cmd })
}
}
impl Serialize for MouseAction {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[derive(Serialize)]
struct Helper(u64, u64, u64, Option<u64>);
let as_tup = Helper(self.line, self.column, self.flags, self.click_count);
as_tup.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for MouseAction {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let v: Vec<u64> = Vec::deserialize(deserializer)?;
let click_count = if v.len() == 4 { Some(v[3]) } else { None };
Ok(MouseAction { line: v[0], column: v[1], flags: v[2], click_count })
}
}
impl Serialize for LineRange {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let as_tup = (self.first, self.last);
as_tup.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for LineRange {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct TwoTuple(i64, i64);
let tup = TwoTuple::deserialize(deserializer)?;
Ok(LineRange { first: tup.0, last: tup.1 })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tabs::ViewId;
#[test]
fn test_serialize_edit_command() {
let message: String = "hello world".into();
let edit = EditCommand {
view_id: ViewId(1),
cmd: EditNotification::Insert { chars: message.clone() },
};
let json = serde_json::to_string(&edit).unwrap();
let cmd: EditCommand<EditNotification> = serde_json::from_str(&json).unwrap();
assert_eq!(cmd.view_id, edit.view_id);
if let EditNotification::Insert { chars } = cmd.cmd {
assert_eq!(chars, message);
}
}
}