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