From e3d0ed287e85bc453d7f77a7c9e6d39620b8d1ef Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Fri, 6 Oct 2023 16:45:18 +0200 Subject: [PATCH 1/3] Replace `url::Url` with `fluent_uri::Uri` --- Cargo.toml | 2 +- src/call_hierarchy.rs | 5 +- src/document_diagnostic.rs | 12 +-- src/document_link.rs | 5 +- src/lib.rs | 187 ++++++------------------------------ src/lsif.rs | 10 +- src/type_hierarchy.rs | 4 +- src/uri.rs | 80 +++++++++++++++ src/window.rs | 6 +- src/workspace_diagnostic.rs | 9 +- src/workspace_folders.rs | 5 +- src/workspace_symbols.rs | 4 +- 12 files changed, 137 insertions(+), 192 deletions(-) create mode 100644 src/uri.rs diff --git a/Cargo.toml b/Cargo.toml index b6a700e..0b66727 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ bitflags = "1.0.1" serde = { version = "1.0.34", features = ["derive"] } serde_json = "1.0.50" serde_repr = "0.1" -url = {version = "2.0.0", features = ["serde"]} +fluent-uri = "0.1.4" [features] default = [] diff --git a/src/call_hierarchy.rs b/src/call_hierarchy.rs index dea7880..f4a07a1 100644 --- a/src/call_hierarchy.rs +++ b/src/call_hierarchy.rs @@ -1,10 +1,9 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; -use url::Url; use crate::{ DynamicRegistrationClientCapabilities, PartialResultParams, Range, SymbolKind, SymbolTag, - TextDocumentPositionParams, WorkDoneProgressOptions, WorkDoneProgressParams, + TextDocumentPositionParams, Uri, WorkDoneProgressOptions, WorkDoneProgressParams, }; pub type CallHierarchyClientCapabilities = DynamicRegistrationClientCapabilities; @@ -63,7 +62,7 @@ pub struct CallHierarchyItem { pub detail: Option, /// The resource identifier of this item. - pub uri: Url, + pub uri: Uri, /// The range enclosing this symbol not including leading/trailing whitespace but everything else, e.g. comments and code. pub range: Range, diff --git a/src/document_diagnostic.rs b/src/document_diagnostic.rs index a2b5c41..6f40072 100644 --- a/src/document_diagnostic.rs +++ b/src/document_diagnostic.rs @@ -1,11 +1,10 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; -use url::Url; use crate::{ Diagnostic, PartialResultParams, StaticRegistrationOptions, TextDocumentIdentifier, - TextDocumentRegistrationOptions, WorkDoneProgressOptions, WorkDoneProgressParams, + TextDocumentRegistrationOptions, Uri, WorkDoneProgressOptions, WorkDoneProgressParams, }; /// Client capabilities specific to diagnostic pull requests. @@ -158,10 +157,9 @@ pub struct RelatedFullDocumentDiagnosticReport { /// macro definitions in a file `a.cpp` result in errors in a header file `b.hpp`. /// /// @since 3.17.0 - #[serde(with = "crate::url_map")] #[serde(skip_serializing_if = "Option::is_none")] #[serde(default)] - pub related_documents: Option>, + pub related_documents: Option>, // relatedDocuments?: { [uri: string]: FullDocumentDiagnosticReport | UnchangedDocumentDiagnosticReport; }; #[serde(flatten)] pub full_document_diagnostic_report: FullDocumentDiagnosticReport, @@ -180,10 +178,9 @@ pub struct RelatedUnchangedDocumentDiagnosticReport { /// macro definitions in a file `a.cpp` result in errors in a header file `b.hpp`. /// /// @since 3.17.0 - #[serde(with = "crate::url_map")] #[serde(skip_serializing_if = "Option::is_none")] #[serde(default)] - pub related_documents: Option>, + pub related_documents: Option>, // relatedDocuments?: { [uri: string]: FullDocumentDiagnosticReport | UnchangedDocumentDiagnosticReport; }; #[serde(flatten)] pub unchanged_document_diagnostic_report: UnchangedDocumentDiagnosticReport, @@ -223,10 +220,9 @@ impl From for DocumentDiagnosticReport #[derive(Debug, PartialEq, Default, Deserialize, Serialize, Clone)] #[serde(rename_all = "camelCase")] pub struct DocumentDiagnosticReportPartialResult { - #[serde(with = "crate::url_map")] #[serde(skip_serializing_if = "Option::is_none")] #[serde(default)] - pub related_documents: Option>, + pub related_documents: Option>, // relatedDocuments?: { [uri: string]: FullDocumentDiagnosticReport | UnchangedDocumentDiagnosticReport; }; } diff --git a/src/document_link.rs b/src/document_link.rs index 1400dd9..b0f100e 100644 --- a/src/document_link.rs +++ b/src/document_link.rs @@ -1,10 +1,9 @@ use crate::{ - PartialResultParams, Range, TextDocumentIdentifier, WorkDoneProgressOptions, + PartialResultParams, Range, TextDocumentIdentifier, Uri, WorkDoneProgressOptions, WorkDoneProgressParams, }; use serde::{Deserialize, Serialize}; use serde_json::Value; -use url::Url; #[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -50,7 +49,7 @@ pub struct DocumentLink { pub range: Range, /// The uri this link points to. #[serde(skip_serializing_if = "Option::is_none")] - pub target: Option, + pub target: Option, /// The tooltip text when you hover over this link. /// diff --git a/src/lib.rs b/src/lib.rs index 4253c76..ac7b880 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,15 +4,6 @@ Language Server Protocol types for Rust. Based on: -This library uses the URL crate for parsing URIs. Note that there is -some confusion on the meaning of URLs vs URIs: -. According to that -information, on the classical sense of "URLs", "URLs" are a subset of -URIs, But on the modern/new meaning of URLs, they are the same as -URIs. The important take-away aspect is that the URL crate should be -able to parse any URI, such as `urn:isbn:0451450523`. - - */ #![allow(non_upper_case_globals)] #![forbid(unsafe_code)] @@ -21,9 +12,11 @@ extern crate bitflags; use std::{collections::HashMap, fmt::Debug}; -use serde::{de, de::Error as Error_, Deserialize, Serialize}; +use serde::{de, de::Error, Deserialize, Serialize}; use serde_json::Value; -pub use url::Url; + +pub use uri::Uri; +mod uri; // Large enough to contain any enumeration name defined in this crate type PascalCaseBuf = [u8; 32]; @@ -270,12 +263,12 @@ impl Range { /// Represents a location inside a resource, such as a line inside a text file. #[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] pub struct Location { - pub uri: Url, + pub uri: Uri, pub range: Range, } impl Location { - pub fn new(uri: Url, range: Range) -> Location { + pub fn new(uri: Uri, range: Range) -> Location { Location { uri, range } } } @@ -292,7 +285,7 @@ pub struct LocationLink { pub origin_selection_range: Option, /// The target resource identifier of this link. - pub target_uri: Url, + pub target_uri: Uri, /// The full target range of this link. pub target_range: Range, @@ -397,7 +390,7 @@ pub struct Diagnostic { #[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeDescription { - pub href: Url, + pub href: Uri, } impl Diagnostic { @@ -618,7 +611,7 @@ pub struct CreateFileOptions { #[serde(rename_all = "camelCase")] pub struct CreateFile { /// The resource to create. - pub uri: Url, + pub uri: Uri, /// Additional options #[serde(skip_serializing_if = "Option::is_none")] pub options: Option, @@ -647,9 +640,9 @@ pub struct RenameFileOptions { #[serde(rename_all = "camelCase")] pub struct RenameFile { /// The old (existing) location. - pub old_uri: Url, + pub old_uri: Uri, /// The new location. - pub new_uri: Url, + pub new_uri: Uri, /// Rename options. #[serde(skip_serializing_if = "Option::is_none")] pub options: Option, @@ -684,7 +677,7 @@ pub struct DeleteFileOptions { #[serde(rename_all = "camelCase")] pub struct DeleteFile { /// The file to delete. - pub uri: Url, + pub uri: Uri, /// Delete options. #[serde(skip_serializing_if = "Option::is_none")] pub options: Option, @@ -698,10 +691,9 @@ pub struct DeleteFile { #[serde(rename_all = "camelCase")] pub struct WorkspaceEdit { /// Holds changes to existing resources. - #[serde(with = "url_map")] #[serde(skip_serializing_if = "Option::is_none")] #[serde(default)] - pub changes: Option>>, // changes?: { [uri: string]: TextEdit[]; }; + pub changes: Option>>, // changes?: { [uri: string]: TextEdit[]; }; /// Depending on the client capability `workspace.workspaceEdit.resourceOperations` document changes /// are either an array of `TextDocumentEdit`s to express changes to n different text documents @@ -778,132 +770,15 @@ pub struct ConfigurationParams { pub struct ConfigurationItem { /// The scope to get the configuration section for. #[serde(skip_serializing_if = "Option::is_none")] - pub scope_uri: Option, + pub scope_uri: Option, ///The configuration section asked for. #[serde(skip_serializing_if = "Option::is_none")] pub section: Option, } -mod url_map { - use std::fmt; - use std::marker::PhantomData; - - use super::*; - - pub fn deserialize<'de, D, V>(deserializer: D) -> Result>, D::Error> - where - D: serde::Deserializer<'de>, - V: de::DeserializeOwned, - { - struct UrlMapVisitor { - _marker: PhantomData, - } - - impl Default for UrlMapVisitor { - fn default() -> Self { - UrlMapVisitor { - _marker: PhantomData, - } - } - } - impl<'de, V: de::DeserializeOwned> de::Visitor<'de> for UrlMapVisitor { - type Value = HashMap; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("map") - } - - fn visit_map(self, mut visitor: M) -> Result - where - M: de::MapAccess<'de>, - { - let mut values = HashMap::with_capacity(visitor.size_hint().unwrap_or(0)); - - // While there are entries remaining in the input, add them - // into our map. - while let Some((key, value)) = visitor.next_entry::()? { - values.insert(key, value); - } - - Ok(values) - } - } - - struct OptionUrlMapVisitor { - _marker: PhantomData, - } - impl Default for OptionUrlMapVisitor { - fn default() -> Self { - OptionUrlMapVisitor { - _marker: PhantomData, - } - } - } - impl<'de, V: de::DeserializeOwned> de::Visitor<'de> for OptionUrlMapVisitor { - type Value = Option>; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("option") - } - - #[inline] - fn visit_unit(self) -> Result - where - E: serde::de::Error, - { - Ok(None) - } - - #[inline] - fn visit_none(self) -> Result - where - E: serde::de::Error, - { - Ok(None) - } - - #[inline] - fn visit_some(self, deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer - .deserialize_map(UrlMapVisitor::::default()) - .map(Some) - } - } - - // Instantiate our Visitor and ask the Deserializer to drive - // it over the input data, resulting in an instance of MyMap. - deserializer.deserialize_option(OptionUrlMapVisitor::default()) - } - - pub fn serialize( - changes: &Option>, - serializer: S, - ) -> Result - where - S: serde::Serializer, - V: serde::Serialize, - { - use serde::ser::SerializeMap; - - match *changes { - Some(ref changes) => { - let mut map = serializer.serialize_map(Some(changes.len()))?; - for (k, v) in changes { - map.serialize_entry(k.as_str(), v)?; - } - map.end() - } - None => serializer.serialize_none(), - } - } -} - impl WorkspaceEdit { - pub fn new(changes: HashMap>) -> WorkspaceEdit { + pub fn new(changes: HashMap>) -> WorkspaceEdit { WorkspaceEdit { changes: Some(changes), document_changes: None, @@ -920,11 +795,11 @@ pub struct TextDocumentIdentifier { // This modelled by "mixing-in" TextDocumentIdentifier in VersionedTextDocumentIdentifier, // so any changes to this type must be effected in the sub-type as well. /// The text document's URI. - pub uri: Url, + pub uri: Uri, } impl TextDocumentIdentifier { - pub fn new(uri: Url) -> TextDocumentIdentifier { + pub fn new(uri: Uri) -> TextDocumentIdentifier { TextDocumentIdentifier { uri } } } @@ -934,7 +809,7 @@ impl TextDocumentIdentifier { #[serde(rename_all = "camelCase")] pub struct TextDocumentItem { /// The text document's URI. - pub uri: Url, + pub uri: Uri, /// The text document's language identifier. pub language_id: String, @@ -948,7 +823,7 @@ pub struct TextDocumentItem { } impl TextDocumentItem { - pub fn new(uri: Url, language_id: String, version: i32, text: String) -> TextDocumentItem { + pub fn new(uri: Uri, language_id: String, version: i32, text: String) -> TextDocumentItem { TextDocumentItem { uri, language_id, @@ -963,7 +838,7 @@ impl TextDocumentItem { pub struct VersionedTextDocumentIdentifier { // This field was "mixed-in" from TextDocumentIdentifier /// The text document's URI. - pub uri: Url, + pub uri: Uri, /// The version number of this document. /// @@ -973,7 +848,7 @@ pub struct VersionedTextDocumentIdentifier { } impl VersionedTextDocumentIdentifier { - pub fn new(uri: Url, version: i32) -> VersionedTextDocumentIdentifier { + pub fn new(uri: Uri, version: i32) -> VersionedTextDocumentIdentifier { VersionedTextDocumentIdentifier { uri, version } } } @@ -983,7 +858,7 @@ impl VersionedTextDocumentIdentifier { pub struct OptionalVersionedTextDocumentIdentifier { // This field was "mixed-in" from TextDocumentIdentifier /// The text document's URI. - pub uri: Url, + pub uri: Uri, /// The version number of this document. If an optional versioned text document /// identifier is sent from the server to the client and the file is not @@ -998,7 +873,7 @@ pub struct OptionalVersionedTextDocumentIdentifier { } impl OptionalVersionedTextDocumentIdentifier { - pub fn new(uri: Url, version: i32) -> OptionalVersionedTextDocumentIdentifier { + pub fn new(uri: Uri, version: i32) -> OptionalVersionedTextDocumentIdentifier { OptionalVersionedTextDocumentIdentifier { uri, version: Some(version), @@ -1079,7 +954,7 @@ pub struct InitializeParams { /// /// Deprecated in favour of `workspaceFolders` #[serde(default)] - pub root_uri: Option, + pub root_uri: Option, /// User provided initialization options. #[serde(skip_serializing_if = "Option::is_none")] @@ -2378,7 +2253,7 @@ impl FileChangeType { #[derive(Debug, Eq, Hash, PartialEq, Clone, Deserialize, Serialize)] pub struct FileEvent { /// The file's URI. - pub uri: Url, + pub uri: Uri, /// The change type. #[serde(rename = "type")] @@ -2386,7 +2261,7 @@ pub struct FileEvent { } impl FileEvent { - pub fn new(uri: Url, typ: FileChangeType) -> FileEvent { + pub fn new(uri: Uri, typ: FileChangeType) -> FileEvent { FileEvent { uri, typ } } } @@ -2447,7 +2322,7 @@ impl From for GlobPattern { pub struct RelativePattern { /// A workspace folder or a base URI to which this pattern will be matched /// against relatively. - pub base_uri: OneOf, + pub base_uri: OneOf, /// The actual glob pattern. pub pattern: Pattern, @@ -2504,7 +2379,7 @@ impl serde::Serialize for WatchKind { #[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] pub struct PublishDiagnosticsParams { /// The URI for which diagnostic information is reported. - pub uri: Url, + pub uri: Uri, /// An array of diagnostic information items. pub diagnostics: Vec, @@ -2516,7 +2391,7 @@ pub struct PublishDiagnosticsParams { impl PublishDiagnosticsParams { pub fn new( - uri: Url, + uri: Uri, diagnostics: Vec, version: Option, ) -> PublishDiagnosticsParams { @@ -2817,14 +2692,14 @@ mod tests { test_serialization( &WorkspaceEdit { changes: Some( - vec![(Url::parse("file://test").unwrap(), vec![])] + vec![("file://test".parse().unwrap(), vec![])] .into_iter() .collect(), ), document_changes: None, ..Default::default() }, - r#"{"changes":{"file://test/":[]}}"#, + r#"{"changes":{"file://test":[]}}"#, ); } diff --git a/src/lsif.rs b/src/lsif.rs index be6df6e..3651b3d 100644 --- a/src/lsif.rs +++ b/src/lsif.rs @@ -4,7 +4,7 @@ //! //! Based on -use crate::{Range, Url}; +use crate::{Range, Uri}; use serde::{Deserialize, Serialize}; pub type Id = crate::NumberOrString; @@ -268,7 +268,7 @@ pub struct Item { #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Document { - pub uri: Url, + pub uri: Uri, pub language_id: String, } @@ -285,7 +285,7 @@ pub struct ResultSet { #[serde(rename_all = "camelCase")] pub struct Project { #[serde(skip_serializing_if = "Option::is_none")] - pub resource: Option, + pub resource: Option, #[serde(skip_serializing_if = "Option::is_none")] pub content: Option, pub kind: String, @@ -300,7 +300,7 @@ pub struct MetaData { pub version: String, /// The project root (in form of an URI) used to compute this dump. - pub project_root: Url, + pub project_root: Uri, /// The string encoding used to compute line and character values in /// positions and ranges. @@ -326,7 +326,7 @@ pub struct PackageInformation { pub name: String, pub manager: String, #[serde(skip_serializing_if = "Option::is_none")] - pub uri: Option, + pub uri: Option, #[serde(skip_serializing_if = "Option::is_none")] pub content: Option, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/src/type_hierarchy.rs b/src/type_hierarchy.rs index 568a03e..90ad621 100644 --- a/src/type_hierarchy.rs +++ b/src/type_hierarchy.rs @@ -1,7 +1,7 @@ use crate::{ DynamicRegistrationClientCapabilities, LSPAny, PartialResultParams, Range, StaticRegistrationOptions, SymbolKind, SymbolTag, TextDocumentPositionParams, - TextDocumentRegistrationOptions, Url, WorkDoneProgressOptions, WorkDoneProgressParams, + TextDocumentRegistrationOptions, Uri, WorkDoneProgressOptions, WorkDoneProgressParams, }; use serde::{Deserialize, Serialize}; @@ -70,7 +70,7 @@ pub struct TypeHierarchyItem { pub detail: Option, /// The resource identifier of this item. - pub uri: Url, + pub uri: Uri, /// The range enclosing this symbol not including leading/trailing whitespace /// but everything else, e.g. comments and code. diff --git a/src/uri.rs b/src/uri.rs new file mode 100644 index 0000000..6f82d58 --- /dev/null +++ b/src/uri.rs @@ -0,0 +1,80 @@ +use std::{hash::Hash, ops::Deref, str::FromStr}; + +use serde::{de::Error, Deserialize, Serialize}; + +/// Newtype struct around `fluent_uri::Uri` with serialization implementations that use `as_str()` and 'from_str()' respectively. +#[derive(Debug, Clone)] +pub struct Uri(fluent_uri::Uri); + +impl Serialize for Uri { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.as_str().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Uri { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let string = String::deserialize(deserializer)?; + fluent_uri::Uri::::parse_from(string) + .map(Uri) + .map_err(|(_, error)| Error::custom(error.to_string())) + } +} + +impl Ord for Uri { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.as_str().cmp(other.as_str()) + } +} + +impl PartialOrd for Uri { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl FromStr for Uri { + type Err = fluent_uri::ParseError; + + fn from_str(s: &str) -> Result { + // TOUCH-UP: + // Use upstream `FromStr` implementation if and when + // https://github.com/yescallop/fluent-uri-rs/pull/10 + // gets merged. + // fluent_uri::Uri::from_str(s).map(Self) + fluent_uri::Uri::parse(s).map(|uri| Self(uri.to_owned())) + } +} + +impl Deref for Uri { + type Target = fluent_uri::Uri; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/* + TOUCH-UP: `PartialEq`, `Eq` and `Hash` could all be derived + if and when the respective implementations get merged upstream: + https://github.com/yescallop/fluent-uri-rs/pull/9 +*/ +impl PartialEq for Uri { + fn eq(&self, other: &Self) -> bool { + self.as_str() == other.as_str() + } +} + +impl Eq for Uri {} + +impl Hash for Uri { + fn hash(&self, state: &mut H) { + self.as_str().hash(state) + } +} diff --git a/src/window.rs b/src/window.rs index ac45e60..ab7a0aa 100644 --- a/src/window.rs +++ b/src/window.rs @@ -4,9 +4,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; -use url::Url; - -use crate::Range; +use crate::{Range, Uri}; #[derive(Eq, PartialEq, Clone, Copy, Deserialize, Serialize)] #[serde(transparent)] @@ -139,7 +137,7 @@ pub struct ShowDocumentClientCapabilities { #[serde(rename_all = "camelCase")] pub struct ShowDocumentParams { /// The document uri to show. - pub uri: Url, + pub uri: Uri, /// Indicates to show the resource in an external program. /// To show for example `https://code.visualstudio.com/` diff --git a/src/workspace_diagnostic.rs b/src/workspace_diagnostic.rs index e8a7646..cf4650e 100644 --- a/src/workspace_diagnostic.rs +++ b/src/workspace_diagnostic.rs @@ -1,8 +1,7 @@ use serde::{Deserialize, Serialize}; -use url::Url; use crate::{ - FullDocumentDiagnosticReport, PartialResultParams, UnchangedDocumentDiagnosticReport, + FullDocumentDiagnosticReport, PartialResultParams, UnchangedDocumentDiagnosticReport, Uri, WorkDoneProgressParams, }; @@ -29,7 +28,7 @@ pub struct DiagnosticWorkspaceClientCapabilities { #[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] pub struct PreviousResultId { /// The URI for which the client knows a result ID. - pub uri: Url, + pub uri: Uri, /// The value of the previous result ID. pub value: String, @@ -62,7 +61,7 @@ pub struct WorkspaceDiagnosticParams { #[serde(rename_all = "camelCase")] pub struct WorkspaceFullDocumentDiagnosticReport { /// The URI for which diagnostic information is reported. - pub uri: Url, + pub uri: Uri, /// The version number for which the diagnostics are reported. /// @@ -80,7 +79,7 @@ pub struct WorkspaceFullDocumentDiagnosticReport { #[serde(rename_all = "camelCase")] pub struct WorkspaceUnchangedDocumentDiagnosticReport { /// The URI for which diagnostic information is reported. - pub uri: Url, + pub uri: Uri, /// The version number for which the diagnostics are reported. /// diff --git a/src/workspace_folders.rs b/src/workspace_folders.rs index aeca89f..0696764 100644 --- a/src/workspace_folders.rs +++ b/src/workspace_folders.rs @@ -1,7 +1,6 @@ use serde::{Deserialize, Serialize}; -use url::Url; -use crate::OneOf; +use crate::{OneOf, Uri}; #[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -25,7 +24,7 @@ pub struct WorkspaceFoldersServerCapabilities { #[serde(rename_all = "camelCase")] pub struct WorkspaceFolder { /// The associated URI for this workspace folder. - pub uri: Url, + pub uri: Uri, /// The name of the workspace folder. Defaults to the uri's basename. pub name: String, } diff --git a/src/workspace_symbols.rs b/src/workspace_symbols.rs index 9ba8089..3d71eec 100644 --- a/src/workspace_symbols.rs +++ b/src/workspace_symbols.rs @@ -1,6 +1,6 @@ use crate::{ LSPAny, Location, OneOf, PartialResultParams, SymbolInformation, SymbolKind, - SymbolKindCapability, SymbolTag, TagSupport, Url, WorkDoneProgressParams, + SymbolKindCapability, SymbolTag, TagSupport, Uri, WorkDoneProgressParams, }; use serde::{Deserialize, Serialize}; @@ -94,7 +94,7 @@ pub struct WorkspaceSymbol { #[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] pub struct WorkspaceLocation { - pub uri: Url, + pub uri: Uri, } #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] From 45c25dd1309dde58faa4710a9aa609b9792bce05 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sat, 7 Oct 2023 13:47:37 +0200 Subject: [PATCH 2/3] Add notebook support. --- src/lib.rs | 16 ++ src/notebook.rs | 432 ++++++++++++++++++++++++++++++++++++++++++++ src/notification.rs | 41 +++++ 3 files changed, 489 insertions(+) create mode 100644 src/notebook.rs diff --git a/src/lib.rs b/src/lib.rs index ac7b880..aeb0663 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -128,6 +128,9 @@ pub use document_link::*; mod document_symbols; pub use document_symbols::*; +mod notebook; +pub use notebook::*; + mod file_operations; pub use file_operations::*; @@ -1469,6 +1472,12 @@ pub struct ClientCapabilities { #[serde(skip_serializing_if = "Option::is_none")] pub text_document: Option, + /// Capabilities specific to the notebook document support. + /// + /// @since 3.17.0 + #[serde(skip_serializing_if = "Option::is_none")] + pub notebook_document: Option, + /// Window specific client capabilities. #[serde(skip_serializing_if = "Option::is_none")] pub window: Option, @@ -1785,6 +1794,13 @@ pub struct ServerCapabilities { #[serde(skip_serializing_if = "Option::is_none")] pub text_document_sync: Option, + /// Defines how notebook documents are synced. + /// + /// @since 3.17.0 + #[serde(skip_serializing_if = "Option::is_none")] + pub notebook_document_sync: + Option>, + /// Capabilities specific to `textDocument/selectionRange` requests. #[serde(skip_serializing_if = "Option::is_none")] pub selection_range_provider: Option, diff --git a/src/notebook.rs b/src/notebook.rs new file mode 100644 index 0000000..bdd6acc --- /dev/null +++ b/src/notebook.rs @@ -0,0 +1,432 @@ +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; + +use crate::{LSPObject, Uri}; + +pub use notification_params::*; + +/// A notebook document. +/// +/// @since 3.17.0 +#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct NotebookDocument { + /// The notebook document's URI. + pub uri: Uri, + /// The type of the notebook. + pub notebook_type: String, + /// The version number of this document (it will increase after each + /// change, including undo/redo). + pub version: i32, + /// Additional metadata stored with the notebook + /// document. + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + /// The cells of a notebook. + pub cells: Vec, +} + +/// A notebook cell. +/// +/// A cell's document URI must be unique across ALL notebook +/// cells and can therefore be used to uniquely identify a +/// notebook cell or the cell's text document. +/// +/// @since 3.17.0 +#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct NotebookCell { + /// The cell's kind + pub kind: NotebookCellKind, + /// The URI of the cell's text document content. + pub document: Uri, + /// Additional metadata stored with the cell. + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + /// Additional execution summary information + /// if supported by the client. + #[serde(skip_serializing_if = "Option::is_none")] + pub execution_summary: Option, +} + +#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ExecutionSummary { + /// A strict monotonically increasing value + /// indicating the execution order of a cell + /// inside a notebook. + pub execution_order: u32, + /// Whether the execution was successful or + /// not if known by the client. + #[serde(skip_serializing_if = "Option::is_none")] + pub success: Option, +} + +#[derive(Debug, Eq, PartialEq, Clone, Serialize_repr, Deserialize_repr)] +#[repr(u8)] +pub enum NotebookCellKind { + /// A markup-cell is formatted source that is used for display. + Markup = 1, + /// A code-cell is source code. + Code = 2, +} + +/// Capabilities specific to the notebook document support. +/// +/// @since 3.17.0 +#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct NotebookDocumentClientCapabilities { + /// Capabilities specific to notebook document synchronization + /// + /// @since 3.17.0 + pub synchronization: NotebookDocumentSyncClientCapabilities, +} + +/// Notebook specific client capabilities. +/// +/// @since 3.17.0 +#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct NotebookDocumentSyncClientCapabilities { + /// Whether implementation supports dynamic registration. If this is + /// set to `true` the client supports the new + /// `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + /// return value for the corresponding server capability as well. + #[serde(skip_serializing_if = "Option::is_none")] + pub dynamic_registration: Option, + + /// The client supports sending execution summary data per cell. + #[serde(skip_serializing_if = "Option::is_none")] + pub execution_summary_report: Option, +} + +/// Options specific to a notebook plus its cells +/// to be synced to the server. +/// +/// If a selector provides a notebook document +/// filter but no cell selector all cells of a +/// matching notebook document will be synced. +/// +/// If a selector provides no notebook document +/// filter but only a cell selector all notebook +/// documents that contain at least one matching +/// cell will be synced. +/// +/// @since 3.17.0 +#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct NotebookDocumentSyncOptions { + /// The notebooks to be synced + pub notebook_selector: Vec, + /// Whether save notification should be forwarded to + /// the server. Will only be honored if mode === `notebook`. + #[serde(skip_serializing_if = "Option::is_none")] + pub save: Option, +} + +/// Registration options specific to a notebook. +/// +/// @since 3.17.0 +#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct NotebookDocumentSyncRegistrationOptions { + /// The notebooks to be synced + pub notebook_selector: Vec, + /// Whether save notification should be forwarded to + /// the server. Will only be honored if mode === `notebook`. + #[serde(skip_serializing_if = "Option::is_none")] + pub save: Option, + /// The id used to register the request. The id can be used to deregister + /// the request again. See also Registration#id. + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, +} + +/// A notebook cell text document filter denotes a cell text +/// document by different properties. +/// +/// @since 3.17.0 +#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct NotebookCellTextDocumentFilter { + /// A filter that matches against the notebook + /// containing the notebook cell. If a string + /// value is provided it matches against the + /// notebook type. '*' matches every notebook. + pub notebook: Notebook, + /// A language id like `python`. + /// + /// Will be matched against the language id of the + /// notebook cell document. '*' matches every language. + #[serde(skip_serializing_if = "Option::is_none")] + pub language: Option, +} + +#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum NotebookSelector { + ByNotebook(NotebookSelectorByNotebook), + ByCells(NotebookSelectorByCells), +} + +#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct NotebookSelectorByNotebook { + /// The notebook to be synced. If a string + /// value is provided it matches against the + /// notebook type. '*' matches every notebook. + pub notebook: Notebook, + /// The cells of the matching notebook to be synced. + #[serde(skip_serializing_if = "Option::is_none")] + pub cells: Option>, +} + +#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct NotebookSelectorByCells { + /// The notebook to be synced. If a string + /// value is provided it matches against the + /// notebook type. '*' matches every notebook. + #[serde(skip_serializing_if = "Option::is_none")] + pub notebook: Option, + /// The cells of the matching notebook to be synced. + pub cells: Vec, +} + +#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct NotebookCellSelector { + pub language: String, +} + +#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum Notebook { + String(String), + NotebookDocumentFilter(NotebookDocumentFilter), +} + +#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum NotebookDocumentFilter { + ByType(NotebookDocumentFilterByType), + ByScheme(NotebookDocumentFilterByScheme), + ByPattern(NotebookDocumentFilterByPattern), +} + +/// A notebook document filter denotes a notebook document by +/// different properties. +/// +/// @since 3.17.0 +#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct NotebookDocumentFilterByType { + /// The type of the enclosing notebook. + pub notebook_type: String, + /// A Uri [scheme](#Uri.scheme), like `file` or `untitled`. + #[serde(skip_serializing_if = "Option::is_none")] + pub scheme: Option, + /// A glob pattern. + #[serde(skip_serializing_if = "Option::is_none")] + pub pattern: Option, +} + +/// A notebook document filter denotes a notebook document by +/// different properties. +/// +/// @since 3.17.0 +#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct NotebookDocumentFilterByScheme { + /// The type of the enclosing notebook. + #[serde(skip_serializing_if = "Option::is_none")] + pub notebook_type: Option, + /// A Uri [scheme](#Uri.scheme), like `file` or `untitled`. + pub scheme: String, + /// A glob pattern. + #[serde(skip_serializing_if = "Option::is_none")] + pub pattern: Option, +} + +/// A notebook document filter denotes a notebook document by +/// different properties. +/// +/// @since 3.17.0 +#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct NotebookDocumentFilterByPattern { + /// The type of the enclosing notebook. + #[serde(skip_serializing_if = "Option::is_none")] + pub notebook_type: Option, + /// A Uri [scheme](#Uri.scheme), like `file` or `untitled`. + #[serde(skip_serializing_if = "Option::is_none")] + pub scheme: Option, + /// A glob pattern. + pub pattern: String, +} + +mod notification_params { + use serde::{Deserialize, Serialize}; + + use crate::{ + TextDocumentContentChangeEvent, TextDocumentIdentifier, TextDocumentItem, + VersionedTextDocumentIdentifier, + }; + + use super::*; + + /// The params sent in an open notebook document notification. + /// + /// @since 3.17.0 + #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct DidOpenNotebookDocumentParams { + /// The notebook document that got opened. + pub notebook_document: NotebookDocument, + /// The text documents that represent the content + /// of a notebook cell. + pub cell_text_documents: Vec, + } + + /// The params sent in a change notebook document notification. + /// + /// @since 3.17.0 + #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct DidChangeNotebookDocumentParams { + /// The notebook document that did change. The version number points + /// to the version after all provided changes have been applied. + pub notebook_document: VersionedNotebookDocumentIdentifier, + + /// The actual changes to the notebook document. + /// + /// The change describes single state change to the notebook document. + /// So it moves a notebook document, its cells and its cell text document + /// contents from state S to S'. + /// + /// To mirror the content of a notebook using change events use the + /// following approach: + /// - start with the same initial content + /// - apply the 'notebookDocument/didChange' notifications in the order + /// you receive them. + pub change: NotebookDocumentChangeEvent, + } + + /// A versioned notebook document identifier. + /// + /// @since 3.17.0 + #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct VersionedNotebookDocumentIdentifier { + /// The version number of this notebook document. + pub version: i32, + /// The notebook document's URI. + pub uri: Uri, + } + + /// A change event for a notebook document. + /// + /// @since 3.17.0 + #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct NotebookDocumentChangeEvent { + /// The changed meta data if any. + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + + /// Changes to cells + #[serde(skip_serializing_if = "Option::is_none")] + pub cells: Option, + } + + #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct NotebookDocumentCellChange { + /// Changes to the cell structure to add or + /// remove cells. + #[serde(skip_serializing_if = "Option::is_none")] + pub structure: Option, + + /// Changes to notebook cells properties like its + /// kind, execution summary or metadata. + #[serde(skip_serializing_if = "Option::is_none")] + pub data: Option>, + + /// Changes to the text content of notebook cells. + #[serde(skip_serializing_if = "Option::is_none")] + pub text_content: Option>, + } + + #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct NotebookDocumentChangeTextContent { + pub document: VersionedTextDocumentIdentifier, + pub changes: Vec, + } + + #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct NotebookDocumentCellChangeStructure { + /// The change to the cell array. + pub array: NotebookCellArrayChange, + /// Additional opened cell text documents. + #[serde(skip_serializing_if = "Option::is_none")] + pub did_open: Option>, + /// Additional closed cell text documents. + #[serde(skip_serializing_if = "Option::is_none")] + pub did_close: Option>, + } + + /// A change describing how to move a `NotebookCell` + /// array from state S to S'. + /// + /// @since 3.17.0 + #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct NotebookCellArrayChange { + /// The start offset of the cell that changed. + pub start: u32, + + /// The deleted cells + pub delete_count: u32, + + /// The new cells, if any + #[serde(skip_serializing_if = "Option::is_none")] + pub cells: Option>, + } + + /// The params sent in a save notebook document notification. + /// + /// @since 3.17.0 + #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct DidSaveNotebookDocumentParams { + /// The notebook document that got saved. + pub notebook_document: NotebookDocumentIdentifier, + } + + /// A literal to identify a notebook document in the client. + /// + /// @since 3.17.0 + #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct NotebookDocumentIdentifier { + /// The notebook document's URI. + pub uri: Uri, + } + + /// The params sent in a close notebook document notification. + /// + /// @since 3.17.0 + #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct DidCloseNotebookDocumentParams { + /// The notebook document that got closed. + pub notebook_document: NotebookDocumentIdentifier, + + /// The text documents that represent the content + /// of a notebook cell that got closed. + pub cell_text_documents: Vec, + } +} diff --git a/src/notification.rs b/src/notification.rs index 6417707..5a4112b 100644 --- a/src/notification.rs +++ b/src/notification.rs @@ -58,6 +58,19 @@ macro_rules! lsp_notification { $crate::notification::PublishDiagnostics }; + ("notebookDocument/didOpen") => { + $crate::notification::DidOpenNotebookDocument + }; + ("notebookDocument/didChange") => { + $crate::notification::DidChangeNotebookDocument + }; + ("notebookDocument/didSave") => { + $crate::notification::DidSaveNotebookDocument + }; + ("notebookDocument/didClose") => { + $crate::notification::DidCloseNotebookDocument + }; + ("workspace/didChangeConfiguration") => { $crate::notification::DidChangeConfiguration }; @@ -231,6 +244,34 @@ impl Notification for DidSaveTextDocument { const METHOD: &'static str = "textDocument/didSave"; } +#[derive(Debug)] +pub enum DidOpenNotebookDocument {} +impl Notification for DidOpenNotebookDocument { + type Params = DidOpenNotebookDocumentParams; + const METHOD: &'static str = "notebookDocument/didOpen"; +} + +#[derive(Debug)] +pub enum DidChangeNotebookDocument {} +impl Notification for DidChangeNotebookDocument { + type Params = DidChangeNotebookDocumentParams; + const METHOD: &'static str = "notebookDocument/didChange"; +} + +#[derive(Debug)] +pub enum DidSaveNotebookDocument {} +impl Notification for DidSaveNotebookDocument { + type Params = DidSaveNotebookDocumentParams; + const METHOD: &'static str = "notebookDocument/didSave"; +} + +#[derive(Debug)] +pub enum DidCloseNotebookDocument {} +impl Notification for DidCloseNotebookDocument { + type Params = DidCloseNotebookDocumentParams; + const METHOD: &'static str = "notebookDocument/didClose"; +} + /// The watched files notification is sent from the client to the server when the client detects changes to files and folders /// watched by the language client (note although the name suggest that only file events are sent it is about file system events which include folders as well). /// It is recommended that servers register for these file system events using the registration mechanism. From b4dcff4483254b81224352fcb6d363551d9cfa42 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sat, 18 Nov 2023 23:34:49 +0100 Subject: [PATCH 3/3] Inline By* variants. --- src/notebook.rs | 130 +++++++++++++++++++----------------------------- 1 file changed, 51 insertions(+), 79 deletions(-) diff --git a/src/notebook.rs b/src/notebook.rs index bdd6acc..08cd045 100644 --- a/src/notebook.rs +++ b/src/notebook.rs @@ -164,34 +164,26 @@ pub struct NotebookCellTextDocumentFilter { } #[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] -#[serde(untagged)] +#[serde(rename_all = "camelCase", untagged)] pub enum NotebookSelector { - ByNotebook(NotebookSelectorByNotebook), - ByCells(NotebookSelectorByCells), -} - -#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct NotebookSelectorByNotebook { - /// The notebook to be synced. If a string - /// value is provided it matches against the - /// notebook type. '*' matches every notebook. - pub notebook: Notebook, - /// The cells of the matching notebook to be synced. - #[serde(skip_serializing_if = "Option::is_none")] - pub cells: Option>, -} - -#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct NotebookSelectorByCells { - /// The notebook to be synced. If a string - /// value is provided it matches against the - /// notebook type. '*' matches every notebook. - #[serde(skip_serializing_if = "Option::is_none")] - pub notebook: Option, - /// The cells of the matching notebook to be synced. - pub cells: Vec, + ByNotebook { + /// The notebook to be synced. If a string + /// value is provided it matches against the + /// notebook type. '*' matches every notebook. + notebook: Notebook, + /// The cells of the matching notebook to be synced. + #[serde(skip_serializing_if = "Option::is_none")] + cells: Option>, + }, + ByCells { + /// The notebook to be synced. If a string + /// value is provided it matches against the + /// notebook type. '*' matches every notebook. + #[serde(skip_serializing_if = "Option::is_none")] + notebook: Option, + /// The cells of the matching notebook to be synced. + cells: Vec, + }, } #[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] @@ -207,63 +199,43 @@ pub enum Notebook { NotebookDocumentFilter(NotebookDocumentFilter), } -#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] -#[serde(untagged)] -pub enum NotebookDocumentFilter { - ByType(NotebookDocumentFilterByType), - ByScheme(NotebookDocumentFilterByScheme), - ByPattern(NotebookDocumentFilterByPattern), -} - -/// A notebook document filter denotes a notebook document by -/// different properties. -/// -/// @since 3.17.0 -#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct NotebookDocumentFilterByType { - /// The type of the enclosing notebook. - pub notebook_type: String, - /// A Uri [scheme](#Uri.scheme), like `file` or `untitled`. - #[serde(skip_serializing_if = "Option::is_none")] - pub scheme: Option, - /// A glob pattern. - #[serde(skip_serializing_if = "Option::is_none")] - pub pattern: Option, -} - /// A notebook document filter denotes a notebook document by /// different properties. /// /// @since 3.17.0 #[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct NotebookDocumentFilterByScheme { - /// The type of the enclosing notebook. - #[serde(skip_serializing_if = "Option::is_none")] - pub notebook_type: Option, - /// A Uri [scheme](#Uri.scheme), like `file` or `untitled`. - pub scheme: String, - /// A glob pattern. - #[serde(skip_serializing_if = "Option::is_none")] - pub pattern: Option, -} - -/// A notebook document filter denotes a notebook document by -/// different properties. -/// -/// @since 3.17.0 -#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct NotebookDocumentFilterByPattern { - /// The type of the enclosing notebook. - #[serde(skip_serializing_if = "Option::is_none")] - pub notebook_type: Option, - /// A Uri [scheme](#Uri.scheme), like `file` or `untitled`. - #[serde(skip_serializing_if = "Option::is_none")] - pub scheme: Option, - /// A glob pattern. - pub pattern: String, +#[serde(rename_all = "camelCase", untagged)] +pub enum NotebookDocumentFilter { + ByType { + /// The type of the enclosing notebook. + notebook_type: String, + /// A Uri [scheme](#Uri.scheme), like `file` or `untitled`. + #[serde(skip_serializing_if = "Option::is_none")] + scheme: Option, + /// A glob pattern. + #[serde(skip_serializing_if = "Option::is_none")] + pattern: Option, + }, + ByScheme { + /// The type of the enclosing notebook. + #[serde(skip_serializing_if = "Option::is_none")] + notebook_type: Option, + /// A Uri [scheme](#Uri.scheme), like `file` or `untitled`. + scheme: String, + /// A glob pattern. + #[serde(skip_serializing_if = "Option::is_none")] + pattern: Option, + }, + ByPattern { + /// The type of the enclosing notebook. + #[serde(skip_serializing_if = "Option::is_none")] + notebook_type: Option, + /// A Uri [scheme](#Uri.scheme), like `file` or `untitled`. + #[serde(skip_serializing_if = "Option::is_none")] + scheme: Option, + /// A glob pattern. + pattern: String, + }, } mod notification_params {