1 // Copyright 2016 The xi-editor Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! The main RPC protocol, for communication between `xi-core` and the client.
16 //!
17 //! We rely on [Serde] for serialization and deserialization between
18 //! the JSON-RPC protocol and the types here.
19 //!
20 //! [Serde]: https://serde.rs
21 use std::path::PathBuf;
22 
23 use serde::de::{self, Deserialize, Deserializer};
24 use serde::ser::{self, Serialize, Serializer};
25 use serde_json::{self, Value};
26 
27 use crate::config::{ConfigDomainExternal, Table};
28 use crate::plugins::PlaceholderRpc;
29 use crate::syntax::LanguageId;
30 use crate::tabs::ViewId;
31 use crate::view::Size;
32 
33 // =============================================================================
34 //  Command types
35 // =============================================================================
36 
37 #[derive(Serialize, Deserialize, Debug, PartialEq)]
38 #[doc(hidden)]
39 pub struct EmptyStruct {}
40 
41 /// The notifications which make up the base of the protocol.
42 ///
43 /// # Note
44 ///
45 /// For serialization, all identifiers are converted to "snake_case".
46 ///
47 /// # Examples
48 ///
49 /// The `close_view` command:
50 ///
51 /// ```
52 /// # extern crate xi_core_lib as xi_core;
53 /// extern crate serde_json;
54 /// # fn main() {
55 /// use crate::xi_core::rpc::CoreNotification;
56 ///
57 /// let json = r#"{
58 ///     "method": "close_view",
59 ///     "params": { "view_id": "view-id-1" }
60 ///     }"#;
61 ///
62 /// let cmd: CoreNotification = serde_json::from_str(&json).unwrap();
63 /// match cmd {
64 ///     CoreNotification::CloseView { .. } => (), // expected
65 ///     other => panic!("Unexpected variant"),
66 /// }
67 /// # }
68 /// ```
69 ///
70 /// The `client_started` command:
71 ///
72 /// ```
73 /// # extern crate xi_core_lib as xi_core;
74 /// extern crate serde_json;
75 /// # fn main() {
76 /// use crate::xi_core::rpc::CoreNotification;
77 ///
78 /// let json = r#"{
79 ///     "method": "client_started",
80 ///     "params": {}
81 ///     }"#;
82 ///
83 /// let cmd: CoreNotification = serde_json::from_str(&json).unwrap();
84 /// match cmd {
85 ///     CoreNotification::ClientStarted { .. }  => (), // expected
86 ///     other => panic!("Unexpected variant"),
87 /// }
88 /// # }
89 /// ```
90 #[derive(Serialize, Deserialize, Debug, PartialEq)]
91 #[serde(rename_all = "snake_case")]
92 #[serde(tag = "method", content = "params")]
93 pub enum CoreNotification {
94     /// The 'edit' namespace, for view-specific editor actions.
95     ///
96     /// The params object has internal `method` and `params` members,
97     /// which are parsed into the appropriate `EditNotification`.
98     ///
99     /// # Note:
100     ///
101     /// All edit commands (notifications and requests) include in their
102     /// inner params object a `view_id` field. On the xi-core side, we
103     /// pull out this value during parsing, and use it for routing.
104     ///
105     /// For more on the edit commands, see [`EditNotification`] and
106     /// [`EditRequest`].
107     ///
108     /// [`EditNotification`]: enum.EditNotification.html
109     /// [`EditRequest`]: enum.EditRequest.html
110     ///
111     /// # Examples
112     ///
113     /// ```
114     /// # extern crate xi_core_lib as xi_core;
115     /// #[macro_use]
116     /// extern crate serde_json;
117     /// use crate::xi_core::rpc::*;
118     /// # fn main() {
119     /// let edit = EditCommand {
120     ///     view_id: 1.into(),
121     ///     cmd: EditNotification::Insert { chars: "hello!".into() },
122     /// };
123     /// let rpc = CoreNotification::Edit(edit);
124     /// let expected = json!({
125     ///     "method": "edit",
126     ///     "params": {
127     ///         "method": "insert",
128     ///         "view_id": "view-id-1",
129     ///         "params": {
130     ///             "chars": "hello!",
131     ///         }
132     ///     }
133     /// });
134     /// assert_eq!(serde_json::to_value(&rpc).unwrap(), expected);
135     /// # }
136     /// ```
137     Edit(EditCommand<EditNotification>),
138     /// The 'plugin' namespace, for interacting with plugins.
139     ///
140     /// As with edit commands, the params object has is a nested RPC,
141     /// with the name of the command included as the `command` field.
142     ///
143     /// (this should be changed to more accurately reflect the behaviour
144     /// of the edit commands).
145     ///
146     /// For the available commands, see [`PluginNotification`].
147     ///
148     /// [`PluginNotification`]: enum.PluginNotification.html
149     ///
150     /// # Examples
151     ///
152     /// ```
153     /// # extern crate xi_core_lib as xi_core;
154     /// #[macro_use]
155     /// extern crate serde_json;
156     /// use crate::xi_core::rpc::*;
157     /// # fn main() {
158     /// let rpc = CoreNotification::Plugin(
159     ///     PluginNotification::Start {
160     ///         view_id: 1.into(),
161     ///         plugin_name: "syntect".into(),
162     ///     });
163     ///
164     /// let expected = json!({
165     ///     "method": "plugin",
166     ///     "params": {
167     ///         "command": "start",
168     ///         "view_id": "view-id-1",
169     ///         "plugin_name": "syntect",
170     ///     }
171     /// });
172     /// assert_eq!(serde_json::to_value(&rpc).unwrap(), expected);
173     /// # }
174     /// ```
175     Plugin(PluginNotification),
176     /// Tells `xi-core` to close the specified view.
177     CloseView { view_id: ViewId },
178     /// Tells `xi-core` to save the contents of the specified view's
179     /// buffer to the specified path.
180     Save { view_id: ViewId, file_path: String },
181     /// Tells `xi-core` to set the theme.
182     SetTheme { theme_name: String },
183     /// Notifies `xi-core` that the client has started.
184     ClientStarted {
185         #[serde(default)]
186         config_dir: Option<PathBuf>,
187         /// Path to additional plugins, included by the client.
188         #[serde(default)]
189         client_extras_dir: Option<PathBuf>,
190     },
191     /// Updates the user's config for the given domain. Where keys in
192     /// `changes` are `null`, those keys are cleared in the user config
193     /// for that domain; otherwise the config is updated with the new
194     /// value.
195     ///
196     /// Note: If the client is using file-based config, the only valid
197     /// domain argument is `ConfigDomain::UserOverride(_)`, which
198     /// represents non-persistent view-specific settings, such as when
199     /// a user manually changes whitespace settings for a given view.
200     ModifyUserConfig { domain: ConfigDomainExternal, changes: Table },
201     /// Control whether the tracing infrastructure is enabled.
202     /// This propagates to all peers that should respond by toggling its own
203     /// infrastructure on/off.
204     TracingConfig { enabled: bool },
205     /// Save trace data to the given path.  The core will first send
206     /// CoreRequest::CollectTrace to all peers to collect the samples.
207     SaveTrace { destination: PathBuf, frontend_samples: Value },
208     /// Tells `xi-core` to set the language id for the view.
209     SetLanguage { view_id: ViewId, language_id: LanguageId },
210 }
211 
212 /// The requests which make up the base of the protocol.
213 ///
214 /// All requests expect a response.
215 ///
216 /// # Examples
217 ///
218 /// The `new_view` command:
219 ///
220 /// ```
221 /// # extern crate xi_core_lib as xi_core;
222 /// extern crate serde_json;
223 /// # fn main() {
224 /// use crate::xi_core::rpc::CoreRequest;
225 ///
226 /// let json = r#"{
227 ///     "method": "new_view",
228 ///     "params": { "file_path": "~/my_very_fun_file.rs" }
229 ///     }"#;
230 ///
231 /// let cmd: CoreRequest = serde_json::from_str(&json).unwrap();
232 /// match cmd {
233 ///     CoreRequest::NewView { .. } => (), // expected
234 ///     other => panic!("Unexpected variant {:?}", other),
235 /// }
236 /// # }
237 /// ```
238 #[derive(Serialize, Deserialize, Debug, PartialEq)]
239 #[serde(rename_all = "snake_case")]
240 #[serde(tag = "method", content = "params")]
241 pub enum CoreRequest {
242     /// The 'edit' namespace, for view-specific requests.
243     Edit(EditCommand<EditRequest>),
244     /// Tells `xi-core` to create a new view. If the `file_path`
245     /// argument is present, `xi-core` should attempt to open the file
246     /// at that location.
247     ///
248     /// Returns the view identifier that should be used to interact
249     /// with the newly created view.
250     NewView { file_path: Option<String> },
251     /// Returns the current collated config object for the given view.
252     GetConfig { view_id: ViewId },
253     /// Returns the contents of the buffer for a given `ViewId`.
254     /// In the future this might also be used to return structured data (such
255     /// as for printing).
256     DebugGetContents { view_id: ViewId },
257 }
258 
259 /// A helper type, which extracts the `view_id` field from edit
260 /// requests and notifications.
261 ///
262 /// Edit requests and notifications have 'method', 'params', and
263 /// 'view_id' param members. We use this wrapper, which has custom
264 /// `Deserialize` and `Serialize` implementations, to pull out the
265 /// `view_id` field.
266 ///
267 /// # Examples
268 ///
269 /// ```
270 /// # extern crate xi_core_lib as xi_core;
271 /// extern crate serde_json;
272 /// # fn main() {
273 /// use crate::xi_core::rpc::*;
274 ///
275 /// let json = r#"{
276 ///     "view_id": "view-id-1",
277 ///     "method": "scroll",
278 ///     "params": [0, 6]
279 ///     }"#;
280 ///
281 /// let cmd: EditCommand<EditNotification> = serde_json::from_str(&json).unwrap();
282 /// match cmd.cmd {
283 ///     EditNotification::Scroll( .. ) => (), // expected
284 ///     other => panic!("Unexpected variant {:?}", other),
285 /// }
286 /// # }
287 /// ```
288 #[derive(Debug, Clone, PartialEq)]
289 pub struct EditCommand<T> {
290     pub view_id: ViewId,
291     pub cmd: T,
292 }
293 
294 /// The smallest unit of text that a gesture can select
295 #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Copy, Clone)]
296 #[serde(rename_all = "snake_case")]
297 pub enum SelectionGranularity {
298     /// Selects any point or character range
299     Point,
300     /// Selects one word at a time
301     Word,
302     /// Selects one line at a time
303     Line,
304 }
305 
306 /// An enum representing touch and mouse gestures applied to the text.
307 #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Copy, Clone)]
308 #[serde(rename_all = "snake_case")]
309 pub enum GestureType {
310     Select { granularity: SelectionGranularity, multi: bool },
311     SelectExtend { granularity: SelectionGranularity },
312     Drag,
313 
314     // Deprecated
315     PointSelect,
316     ToggleSel,
317     RangeSelect,
318     LineSelect,
319     WordSelect,
320     MultiLineSelect,
321     MultiWordSelect,
322 }
323 
324 /// An inclusive range.
325 ///
326 /// # Note:
327 ///
328 /// Several core protocol commands use a params array to pass arguments
329 /// which are named, internally. this type use custom Serialize /
330 /// Deserialize impls to accommodate this.
331 #[derive(PartialEq, Eq, Debug, Clone)]
332 pub struct LineRange {
333     pub first: i64,
334     pub last: i64,
335 }
336 
337 /// A mouse event. See the note for [`LineRange`].
338 ///
339 /// [`LineRange`]: enum.LineRange.html
340 #[derive(PartialEq, Eq, Debug, Clone)]
341 pub struct MouseAction {
342     pub line: u64,
343     pub column: u64,
344     pub flags: u64,
345     pub click_count: Option<u64>,
346 }
347 
348 #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
349 pub struct Position {
350     pub line: usize,
351     pub column: usize,
352 }
353 
354 /// Represents how the current selection is modified (used by find
355 /// operations).
356 #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
357 #[serde(rename_all = "snake_case")]
358 pub enum SelectionModifier {
359     None,
360     Set,
361     Add,
362     AddRemovingCurrent,
363 }
364 
365 impl Default for SelectionModifier {
default() -> SelectionModifier366     fn default() -> SelectionModifier {
367         SelectionModifier::Set
368     }
369 }
370 
371 #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
372 #[serde(rename_all = "snake_case")]
373 pub struct FindQuery {
374     pub id: Option<usize>,
375     pub chars: String,
376     pub case_sensitive: bool,
377     #[serde(default)]
378     pub regex: bool,
379     #[serde(default)]
380     pub whole_words: bool,
381 }
382 
383 /// The edit-related notifications.
384 ///
385 /// Alongside the [`EditRequest`] members, these commands constitute
386 /// the API for interacting with a particular window and document.
387 #[derive(Serialize, Deserialize, Debug, PartialEq)]
388 #[serde(rename_all = "snake_case")]
389 #[serde(tag = "method", content = "params")]
390 pub enum EditNotification {
391     Insert {
392         chars: String,
393     },
394     Paste {
395         chars: String,
396     },
397     DeleteForward,
398     DeleteBackward,
399     DeleteWordForward,
400     DeleteWordBackward,
401     DeleteToEndOfParagraph,
402     DeleteToBeginningOfLine,
403     InsertNewline,
404     InsertTab,
405     MoveUp,
406     MoveUpAndModifySelection,
407     MoveDown,
408     MoveDownAndModifySelection,
409     MoveLeft,
410     // synoynm for `MoveLeft`
411     MoveBackward,
412     MoveLeftAndModifySelection,
413     MoveRight,
414     // synoynm for `MoveRight`
415     MoveForward,
416     MoveRightAndModifySelection,
417     MoveWordLeft,
418     MoveWordLeftAndModifySelection,
419     MoveWordRight,
420     MoveWordRightAndModifySelection,
421     MoveToBeginningOfParagraph,
422     MoveToBeginningOfParagraphAndModifySelection,
423     MoveToEndOfParagraph,
424     MoveToEndOfParagraphAndModifySelection,
425     MoveToLeftEndOfLine,
426     MoveToLeftEndOfLineAndModifySelection,
427     MoveToRightEndOfLine,
428     MoveToRightEndOfLineAndModifySelection,
429     MoveToBeginningOfDocument,
430     MoveToBeginningOfDocumentAndModifySelection,
431     MoveToEndOfDocument,
432     MoveToEndOfDocumentAndModifySelection,
433     ScrollPageUp,
434     PageUpAndModifySelection,
435     ScrollPageDown,
436     PageDownAndModifySelection,
437     SelectAll,
438     AddSelectionAbove,
439     AddSelectionBelow,
440     Scroll(LineRange),
441     Resize(Size),
442     GotoLine {
443         line: u64,
444     },
445     RequestLines(LineRange),
446     Yank,
447     Transpose,
448     Click(MouseAction),
449     Drag(MouseAction),
450     Gesture {
451         line: u64,
452         col: u64,
453         ty: GestureType,
454     },
455     Undo,
456     Redo,
457     Find {
458         chars: String,
459         case_sensitive: bool,
460         #[serde(default)]
461         regex: bool,
462         #[serde(default)]
463         whole_words: bool,
464     },
465     MultiFind {
466         queries: Vec<FindQuery>,
467     },
468     FindNext {
469         #[serde(default)]
470         wrap_around: bool,
471         #[serde(default)]
472         allow_same: bool,
473         #[serde(default)]
474         modify_selection: SelectionModifier,
475     },
476     FindPrevious {
477         #[serde(default)]
478         wrap_around: bool,
479         #[serde(default)]
480         allow_same: bool,
481         #[serde(default)]
482         modify_selection: SelectionModifier,
483     },
484     FindAll,
485     DebugRewrap,
486     DebugWrapWidth,
487     /// Prints the style spans present in the active selection.
488     DebugPrintSpans,
489     DebugToggleComment,
490     Uppercase,
491     Lowercase,
492     Capitalize,
493     Reindent,
494     Indent,
495     Outdent,
496     /// Indicates whether find highlights should be rendered
497     HighlightFind {
498         visible: bool,
499     },
500     SelectionForFind {
501         #[serde(default)]
502         case_sensitive: bool,
503     },
504     Replace {
505         chars: String,
506         #[serde(default)]
507         preserve_case: bool,
508     },
509     ReplaceNext,
510     ReplaceAll,
511     SelectionForReplace,
512     RequestHover {
513         request_id: usize,
514         position: Option<Position>,
515     },
516     SelectionIntoLines,
517     DuplicateLine,
518     IncreaseNumber,
519     DecreaseNumber,
520     ToggleRecording {
521         recording_name: Option<String>,
522     },
523     PlayRecording {
524         recording_name: String,
525     },
526     ClearRecording {
527         recording_name: String,
528     },
529     CollapseSelections,
530 }
531 
532 /// The edit related requests.
533 #[derive(Serialize, Deserialize, Debug, PartialEq)]
534 #[serde(rename_all = "snake_case")]
535 #[serde(tag = "method", content = "params")]
536 pub enum EditRequest {
537     /// Cuts the active selection, returning their contents,
538     /// or `Null` if the selection was empty.
539     Cut,
540     /// Copies the active selection, returning their contents or
541     /// or `Null` if the selection was empty.
542     Copy,
543 }
544 
545 /// The plugin related notifications.
546 #[derive(Serialize, Deserialize, Debug, PartialEq)]
547 #[serde(tag = "command")]
548 #[serde(rename_all = "snake_case")]
549 pub enum PluginNotification {
550     Start { view_id: ViewId, plugin_name: String },
551     Stop { view_id: ViewId, plugin_name: String },
552     PluginRpc { view_id: ViewId, receiver: String, rpc: PlaceholderRpc },
553 }
554 
555 // Serialize / Deserialize
556 
557 impl<T: Serialize> Serialize for EditCommand<T> {
serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer,558     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
559     where
560         S: Serializer,
561     {
562         let mut v = serde_json::to_value(&self.cmd).map_err(ser::Error::custom)?;
563         v["view_id"] = json!(self.view_id);
564         v.serialize(serializer)
565     }
566 }
567 
568 impl<'de, T: Deserialize<'de>> Deserialize<'de> for EditCommand<T> {
deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>,569     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
570     where
571         D: Deserializer<'de>,
572     {
573         #[derive(Deserialize)]
574         struct InnerId {
575             view_id: ViewId,
576         }
577 
578         let mut v = Value::deserialize(deserializer)?;
579         let helper = InnerId::deserialize(&v).map_err(de::Error::custom)?;
580         let InnerId { view_id } = helper;
581 
582         // if params are empty, remove them
583         let remove_params = match v.get("params") {
584             Some(&Value::Object(ref obj)) => obj.is_empty() && T::deserialize(v.clone()).is_err(),
585             Some(&Value::Array(ref arr)) => arr.is_empty() && T::deserialize(v.clone()).is_err(),
586             Some(_) => {
587                 return Err(de::Error::custom(
588                     "'params' field, if present, must be object or array.",
589                 ));
590             }
591             None => false,
592         };
593 
594         if remove_params {
595             v.as_object_mut().map(|v| v.remove("params"));
596         }
597 
598         let cmd = T::deserialize(v).map_err(de::Error::custom)?;
599         Ok(EditCommand { view_id, cmd })
600     }
601 }
602 
603 impl Serialize for MouseAction {
serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer,604     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
605     where
606         S: Serializer,
607     {
608         #[derive(Serialize)]
609         struct Helper(u64, u64, u64, Option<u64>);
610 
611         let as_tup = Helper(self.line, self.column, self.flags, self.click_count);
612         as_tup.serialize(serializer)
613     }
614 }
615 
616 impl<'de> Deserialize<'de> for MouseAction {
deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>,617     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
618     where
619         D: Deserializer<'de>,
620     {
621         let v: Vec<u64> = Vec::deserialize(deserializer)?;
622         let click_count = if v.len() == 4 { Some(v[3]) } else { None };
623         Ok(MouseAction { line: v[0], column: v[1], flags: v[2], click_count })
624     }
625 }
626 
627 impl Serialize for LineRange {
serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer,628     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
629     where
630         S: Serializer,
631     {
632         let as_tup = (self.first, self.last);
633         as_tup.serialize(serializer)
634     }
635 }
636 
637 impl<'de> Deserialize<'de> for LineRange {
deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>,638     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
639     where
640         D: Deserializer<'de>,
641     {
642         #[derive(Deserialize)]
643         struct TwoTuple(i64, i64);
644 
645         let tup = TwoTuple::deserialize(deserializer)?;
646         Ok(LineRange { first: tup.0, last: tup.1 })
647     }
648 }
649 
650 #[cfg(test)]
651 mod tests {
652     use super::*;
653     use crate::tabs::ViewId;
654 
655     #[test]
test_serialize_edit_command()656     fn test_serialize_edit_command() {
657         // Ensure that an EditCommand can be serialized and then correctly deserialized.
658         let message: String = "hello world".into();
659         let edit = EditCommand {
660             view_id: ViewId(1),
661             cmd: EditNotification::Insert { chars: message.clone() },
662         };
663         let json = serde_json::to_string(&edit).unwrap();
664         let cmd: EditCommand<EditNotification> = serde_json::from_str(&json).unwrap();
665         assert_eq!(cmd.view_id, edit.view_id);
666         if let EditNotification::Insert { chars } = cmd.cmd {
667             assert_eq!(chars, message);
668         }
669     }
670 }
671