summaryrefslogtreecommitdiff
path: root/support/texlab/src/protocol
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src/protocol')
-rw-r--r--support/texlab/src/protocol/capabilities.rs145
-rw-r--r--support/texlab/src/protocol/client.rs27
-rw-r--r--support/texlab/src/protocol/codec.rs144
-rw-r--r--support/texlab/src/protocol/edit.rs12
-rw-r--r--support/texlab/src/protocol/mod.rs71
-rw-r--r--support/texlab/src/protocol/options.rs104
-rw-r--r--support/texlab/src/protocol/range.rs97
-rw-r--r--support/texlab/src/protocol/uri.rs97
8 files changed, 697 insertions, 0 deletions
diff --git a/support/texlab/src/protocol/capabilities.rs b/support/texlab/src/protocol/capabilities.rs
new file mode 100644
index 0000000000..c83b205aa8
--- /dev/null
+++ b/support/texlab/src/protocol/capabilities.rs
@@ -0,0 +1,145 @@
+use lsp_types::{ClientCapabilities, MarkupKind};
+
+pub trait ClientCapabilitiesExt {
+ fn has_definition_link_support(&self) -> bool;
+
+ fn has_hierarchical_document_symbol_support(&self) -> bool;
+
+ fn has_work_done_progress_support(&self) -> bool;
+
+ fn has_hover_markdown_support(&self) -> bool;
+
+ fn has_pull_configuration_support(&self) -> bool;
+
+ fn has_push_configuration_support(&self) -> bool;
+}
+
+impl ClientCapabilitiesExt for ClientCapabilities {
+ fn has_definition_link_support(&self) -> bool {
+ self.text_document
+ .as_ref()
+ .and_then(|cap| cap.definition.as_ref())
+ .and_then(|cap| cap.link_support)
+ == Some(true)
+ }
+
+ fn has_hierarchical_document_symbol_support(&self) -> bool {
+ self.text_document
+ .as_ref()
+ .and_then(|cap| cap.document_symbol.as_ref())
+ .and_then(|cap| cap.hierarchical_document_symbol_support)
+ == Some(true)
+ }
+
+ fn has_work_done_progress_support(&self) -> bool {
+ self.window.as_ref().and_then(|cap| cap.work_done_progress) == Some(true)
+ }
+
+ fn has_hover_markdown_support(&self) -> bool {
+ self.text_document
+ .as_ref()
+ .and_then(|cap| cap.hover.as_ref())
+ .and_then(|cap| cap.content_format.as_ref())
+ .filter(|formats| formats.contains(&MarkupKind::Markdown))
+ .is_some()
+ }
+
+ fn has_pull_configuration_support(&self) -> bool {
+ self.workspace.as_ref().and_then(|cap| cap.configuration) == Some(true)
+ }
+
+ fn has_push_configuration_support(&self) -> bool {
+ self.workspace
+ .as_ref()
+ .and_then(|cap| cap.did_change_configuration)
+ .and_then(|cap| cap.dynamic_registration)
+ == Some(true)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use lsp_types::*;
+
+ #[test]
+ fn has_definition_link_support_true() {
+ let capabilities = ClientCapabilities {
+ text_document: Some(TextDocumentClientCapabilities {
+ definition: Some(GotoCapability {
+ link_support: Some(true),
+ ..GotoCapability::default()
+ }),
+ ..TextDocumentClientCapabilities::default()
+ }),
+ ..ClientCapabilities::default()
+ };
+ assert!(capabilities.has_definition_link_support());
+ }
+
+ #[test]
+ fn has_definition_link_support_false() {
+ let capabilities = ClientCapabilities::default();
+ assert!(!capabilities.has_definition_link_support());
+ }
+
+ #[test]
+ fn has_hierarchical_document_symbol_support_true() {
+ let capabilities = ClientCapabilities {
+ text_document: Some(TextDocumentClientCapabilities {
+ document_symbol: Some(DocumentSymbolCapability {
+ hierarchical_document_symbol_support: Some(true),
+ ..DocumentSymbolCapability::default()
+ }),
+ ..TextDocumentClientCapabilities::default()
+ }),
+ ..ClientCapabilities::default()
+ };
+ assert!(capabilities.has_hierarchical_document_symbol_support());
+ }
+
+ #[test]
+ fn has_hierarchical_document_symbol_support_false() {
+ let capabilities = ClientCapabilities::default();
+ assert!(!capabilities.has_hierarchical_document_symbol_support());
+ }
+
+ #[test]
+ fn has_work_done_progress_support_true() {
+ let capabilities = ClientCapabilities {
+ window: Some(WindowClientCapabilities {
+ work_done_progress: Some(true),
+ ..WindowClientCapabilities::default()
+ }),
+ ..ClientCapabilities::default()
+ };
+ assert!(capabilities.has_work_done_progress_support());
+ }
+
+ #[test]
+ fn has_work_done_progress_support_false() {
+ let capabilities = ClientCapabilities::default();
+ assert!(!capabilities.has_work_done_progress_support());
+ }
+
+ #[test]
+ fn has_hover_markdown_support_true() {
+ let capabilities = ClientCapabilities {
+ text_document: Some(TextDocumentClientCapabilities {
+ hover: Some(HoverCapability {
+ content_format: Some(vec![MarkupKind::PlainText, MarkupKind::Markdown]),
+ ..HoverCapability::default()
+ }),
+ ..TextDocumentClientCapabilities::default()
+ }),
+ ..ClientCapabilities::default()
+ };
+ assert!(capabilities.has_hover_markdown_support());
+ }
+
+ #[test]
+ fn has_hover_markdown_support_false() {
+ let capabilities = ClientCapabilities::default();
+ assert!(!capabilities.has_hover_markdown_support());
+ }
+}
diff --git a/support/texlab/src/protocol/client.rs b/support/texlab/src/protocol/client.rs
new file mode 100644
index 0000000000..471f1fa41d
--- /dev/null
+++ b/support/texlab/src/protocol/client.rs
@@ -0,0 +1,27 @@
+use jsonrpc::client::Result;
+use jsonrpc_derive::{jsonrpc_client, jsonrpc_method};
+use lsp_types::*;
+
+#[jsonrpc_client(LatexLspClient)]
+pub trait LspClient {
+ #[jsonrpc_method("workspace/configuration", kind = "request")]
+ async fn configuration(&self, params: ConfigurationParams) -> Result<serde_json::Value>;
+
+ #[jsonrpc_method("window/showMessage", kind = "notification")]
+ async fn show_message(&self, params: ShowMessageParams);
+
+ #[jsonrpc_method("client/registerCapability", kind = "request")]
+ async fn register_capability(&self, params: RegistrationParams) -> Result<()>;
+
+ #[jsonrpc_method("textDocument/publishDiagnostics", kind = "notification")]
+ async fn publish_diagnostics(&self, params: PublishDiagnosticsParams);
+
+ #[jsonrpc_method("$/progress", kind = "notification")]
+ async fn progress(&self, params: ProgressParams);
+
+ #[jsonrpc_method("window/workDoneProgress/create", kind = "request")]
+ async fn work_done_progress_create(&self, params: WorkDoneProgressCreateParams) -> Result<()>;
+
+ #[jsonrpc_method("window/logMessage", kind = "notification")]
+ async fn log_message(&self, params: LogMessageParams);
+}
diff --git a/support/texlab/src/protocol/codec.rs b/support/texlab/src/protocol/codec.rs
new file mode 100644
index 0000000000..980ae1307e
--- /dev/null
+++ b/support/texlab/src/protocol/codec.rs
@@ -0,0 +1,144 @@
+use bytes::{BufMut, BytesMut};
+use log::trace;
+use std::io::{Error, ErrorKind};
+use tokio_util::codec::{Decoder, Encoder};
+
+pub struct LspCodec;
+
+impl Decoder for LspCodec {
+ type Item = String;
+ type Error = Error;
+
+ fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
+ match parser::parse(src) {
+ Ok((remaining, content)) => {
+ trace!("Received message:\n{}\n", content);
+
+ let offset = src.len() - remaining.len();
+ let _ = src.split_to(offset);
+ Ok(Some(content))
+ }
+ Err(error) => {
+ if error.is_incomplete() {
+ Ok(None)
+ } else {
+ Err(ErrorKind::InvalidData.into())
+ }
+ }
+ }
+ }
+}
+
+impl Encoder<String> for LspCodec {
+ type Error = Error;
+
+ fn encode(&mut self, item: String, dst: &mut BytesMut) -> Result<(), Self::Error> {
+ let message = format!("Content-Length: {}\r\n\r\n{}", item.len(), item);
+ trace!("Sent message:\n{}\n", message);
+
+ dst.reserve(message.len());
+ dst.put(message.as_bytes());
+ Ok(())
+ }
+}
+
+mod parser {
+ use nom::{
+ bytes::streaming::{tag, take, take_while},
+ character::{is_digit, streaming::line_ending},
+ combinator::{map_res, opt},
+ IResult,
+ };
+ use std::str;
+
+ pub fn parse(input: &[u8]) -> IResult<&[u8], String> {
+ let (input, _) = opt(content_type)(input)?;
+ let (input, length) = content_length(input)?;
+ let (input, _) = opt(content_type)(input)?;
+ let (input, _) = line_ending(input)?;
+ let (input, content) = map_res(take(length), str::from_utf8)(input)?;
+ Ok((input, content.to_owned()))
+ }
+
+ fn content_type(input: &[u8]) -> IResult<&[u8], &[u8]> {
+ let (input, _) = tag("Content-Type: application/vscode-jsonrpc;charset=utf")(input)?;
+ let (input, _) = opt(tag("-"))(input)?;
+ let (input, _) = tag("8")(input)?;
+ line_ending(input)
+ }
+
+ fn content_length(input: &[u8]) -> IResult<&[u8], usize> {
+ let (input, _) = tag("Content-Length: ")(input)?;
+ let (input, length) = map_res(take_while(is_digit), from_bytes)(input)?;
+ let (input, _) = line_ending(input)?;
+ Ok((input, length))
+ }
+
+ fn from_bytes(input: &[u8]) -> Result<usize, std::num::ParseIntError> {
+ usize::from_str_radix(str::from_utf8(input).unwrap(), 10)
+ }
+
+ #[cfg(test)]
+ mod tests {
+ use super::*;
+
+ #[test]
+ fn parse_content_type() {
+ let result =
+ content_type(b"Content-Type: application/vscode-jsonrpc;charset=utf-8\r\n");
+ assert!(result.is_ok());
+ }
+
+ #[test]
+ fn parse_content_type_utf8() {
+ let result = content_type(b"Content-Type: application/vscode-jsonrpc;charset=utf8\r\n");
+ assert!(result.is_ok());
+ }
+
+ #[test]
+ fn parse_content_length() {
+ let result = content_length(b"Content-Length: 42\r\n");
+ assert_eq!(result.unwrap().1, 42usize);
+ }
+
+ #[test]
+ fn parse_message_full() {
+ let result = parse(
+ b"Content-Length: 2\r\nContent-Type: application/vscode-jsonrpc;charset=utf8\r\n\r\n{}",
+ );
+ assert_eq!(result.unwrap().1, "{}");
+ }
+
+ #[test]
+ fn parse_message_type_first() {
+ let result = parse(
+ b"Content-Type: application/vscode-jsonrpc;charset=utf8\r\nContent-Length: 2\r\n\r\n{}",
+ );
+ assert_eq!(result.unwrap().1, "{}");
+ }
+
+ #[test]
+ fn parse_message_without_type() {
+ let result = parse(b"Content-Length: 2\r\n\r\n{}");
+ assert_eq!(result.unwrap().1, "{}");
+ }
+
+ #[test]
+ fn parse_message_incomplete() {
+ let result = parse(b"Content-Length:");
+ assert!(result.unwrap_err().is_incomplete());
+ }
+
+ #[test]
+ fn parse_message_invalid() {
+ let error = parse(b"foo").unwrap_err();
+ assert!(!error.is_incomplete());
+ }
+
+ #[test]
+ fn parse_message_overflow() {
+ let result = parse(b"Content-Length: 4\r\n\r\n{}");
+ assert!(result.unwrap_err().is_incomplete());
+ }
+ }
+}
diff --git a/support/texlab/src/protocol/edit.rs b/support/texlab/src/protocol/edit.rs
new file mode 100644
index 0000000000..9091f2d393
--- /dev/null
+++ b/support/texlab/src/protocol/edit.rs
@@ -0,0 +1,12 @@
+use lsp_types::{CompletionTextEdit, TextEdit};
+
+pub trait CompletionTextEditExt {
+ fn text_edit(&self) -> Option<&TextEdit>;
+}
+
+impl CompletionTextEditExt for CompletionTextEdit {
+ fn text_edit(&self) -> Option<&TextEdit> {
+ let CompletionTextEdit::Edit(edit) = self;
+ Some(edit)
+ }
+}
diff --git a/support/texlab/src/protocol/mod.rs b/support/texlab/src/protocol/mod.rs
new file mode 100644
index 0000000000..c08a7d82f5
--- /dev/null
+++ b/support/texlab/src/protocol/mod.rs
@@ -0,0 +1,71 @@
+cfg_if::cfg_if! {
+ if #[cfg(feature = "server")] {
+ mod client;
+ mod codec;
+
+ pub use self::{
+ client::{LatexLspClient, LspClient},
+ codec::LspCodec,
+ };
+ }
+}
+
+mod capabilities;
+mod edit;
+mod options;
+mod range;
+mod uri;
+
+pub use self::{
+ capabilities::ClientCapabilitiesExt,
+ edit::*,
+ options::*,
+ range::RangeExt,
+ uri::{AsUri, Uri},
+};
+pub use lsp_types::*;
+
+use serde::{Deserialize, Serialize};
+use serde_repr::*;
+
+#[serde(untagged)]
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub enum DefinitionResponse {
+ Locations(Vec<Location>),
+ LocationLinks(Vec<LocationLink>),
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize_repr, Deserialize_repr)]
+#[repr(i32)]
+pub enum ForwardSearchStatus {
+ Success = 0,
+ Error = 1,
+ Failure = 2,
+ Unconfigured = 3,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct ForwardSearchResult {
+ pub status: ForwardSearchStatus,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BuildParams {
+ pub text_document: TextDocumentIdentifier,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize_repr, Deserialize_repr)]
+#[repr(i32)]
+pub enum BuildStatus {
+ Success = 0,
+ Error = 1,
+ Failure = 2,
+ Cancelled = 3,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BuildResult {
+ pub status: BuildStatus,
+}
diff --git a/support/texlab/src/protocol/options.rs b/support/texlab/src/protocol/options.rs
new file mode 100644
index 0000000000..86718d1e72
--- /dev/null
+++ b/support/texlab/src/protocol/options.rs
@@ -0,0 +1,104 @@
+use serde::{Deserialize, Serialize};
+use std::path::PathBuf;
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub enum BibtexFormatter {
+ Texlab,
+ Latexindent,
+}
+
+impl Default for BibtexFormatter {
+ fn default() -> Self {
+ Self::Texlab
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BibtexFormattingOptions {
+ pub line_length: Option<i32>,
+ pub formatter: Option<BibtexFormatter>,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
+pub struct LatexForwardSearchOptions {
+ pub executable: Option<String>,
+ pub args: Option<Vec<String>>,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct LatexLintOptions {
+ pub on_change: Option<bool>,
+ pub on_save: Option<bool>,
+}
+
+impl LatexLintOptions {
+ pub fn on_change(&self) -> bool {
+ self.on_change.unwrap_or(false)
+ }
+
+ pub fn on_save(&self) -> bool {
+ self.on_save.unwrap_or(false)
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct LatexBuildOptions {
+ pub executable: Option<String>,
+ pub args: Option<Vec<String>>,
+ pub on_save: Option<bool>,
+ pub output_directory: Option<PathBuf>,
+ pub forward_search_after: Option<bool>,
+}
+
+impl LatexBuildOptions {
+ pub fn executable(&self) -> String {
+ self.executable
+ .as_ref()
+ .map(Clone::clone)
+ .unwrap_or_else(|| "latexmk".to_owned())
+ }
+
+ pub fn args(&self) -> Vec<String> {
+ self.args.as_ref().map(Clone::clone).unwrap_or_else(|| {
+ vec![
+ "-pdf".to_owned(),
+ "-interaction=nonstopmode".to_owned(),
+ "-synctex=1".to_owned(),
+ ]
+ })
+ }
+
+ pub fn on_save(&self) -> bool {
+ self.on_save.unwrap_or(false)
+ }
+
+ pub fn forward_search_after(&self) -> bool {
+ self.forward_search_after.unwrap_or(false)
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct LatexOptions {
+ pub forward_search: Option<LatexForwardSearchOptions>,
+ pub lint: Option<LatexLintOptions>,
+ pub build: Option<LatexBuildOptions>,
+ pub root_directory: Option<PathBuf>,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BibtexOptions {
+ pub formatting: Option<BibtexFormattingOptions>,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Options {
+ pub latex: Option<LatexOptions>,
+ pub bibtex: Option<BibtexOptions>,
+}
diff --git a/support/texlab/src/protocol/range.rs b/support/texlab/src/protocol/range.rs
new file mode 100644
index 0000000000..774df1e699
--- /dev/null
+++ b/support/texlab/src/protocol/range.rs
@@ -0,0 +1,97 @@
+use lsp_types::{Position, Range};
+
+pub trait RangeExt {
+ fn new_simple(start_line: u64, start_character: u64, end_line: u64, end_character: u64)
+ -> Self;
+
+ fn contains(&self, pos: Position) -> bool;
+
+ fn contains_exclusive(&self, pos: Position) -> bool;
+}
+
+impl RangeExt for Range {
+ fn new_simple(
+ start_line: u64,
+ start_character: u64,
+ end_line: u64,
+ end_character: u64,
+ ) -> Self {
+ Self {
+ start: Position::new(start_line, start_character),
+ end: Position::new(end_line, end_character),
+ }
+ }
+
+ fn contains(&self, pos: Position) -> bool {
+ pos >= self.start && pos <= self.end
+ }
+
+ fn contains_exclusive(&self, pos: Position) -> bool {
+ pos > self.start && pos < self.end
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn contains_inside() {
+ let range = Range::new_simple(1, 2, 3, 4);
+ assert!(range.contains(Position::new(2, 5)));
+ }
+
+ #[test]
+ fn contains_begin() {
+ let range = Range::new_simple(1, 2, 3, 4);
+ assert!(range.contains(range.start));
+ }
+
+ #[test]
+ fn contains_end() {
+ let range = Range::new_simple(1, 2, 3, 4);
+ assert!(range.contains(range.end));
+ }
+
+ #[test]
+ fn contains_outside_left() {
+ let range = Range::new_simple(1, 2, 3, 4);
+ assert!(!range.contains(Position::new(0, 5)));
+ }
+
+ #[test]
+ fn contains_outside_right() {
+ let range = Range::new_simple(1, 2, 3, 4);
+ assert!(!range.contains(Position::new(5, 1)));
+ }
+
+ #[test]
+ fn contains_exclusive_inside() {
+ let range = Range::new_simple(1, 2, 3, 4);
+ assert!(range.contains_exclusive(Position::new(2, 5)));
+ }
+
+ #[test]
+ fn contains_exclusive_begin() {
+ let range = Range::new_simple(1, 2, 3, 4);
+ assert!(!range.contains_exclusive(range.start));
+ }
+
+ #[test]
+ fn contains_exclusive_end() {
+ let range = Range::new_simple(1, 2, 3, 4);
+ assert!(!range.contains_exclusive(range.end));
+ }
+
+ #[test]
+ fn contains_exclusive_outside_left() {
+ let range = Range::new_simple(1, 2, 3, 4);
+ assert!(!range.contains_exclusive(Position::new(0, 5)));
+ }
+
+ #[test]
+ fn contains_exclusive_outside_right() {
+ let range = Range::new_simple(1, 2, 3, 4);
+ assert!(!range.contains_exclusive(Position::new(5, 1)));
+ }
+}
diff --git a/support/texlab/src/protocol/uri.rs b/support/texlab/src/protocol/uri.rs
new file mode 100644
index 0000000000..e8dd1ba160
--- /dev/null
+++ b/support/texlab/src/protocol/uri.rs
@@ -0,0 +1,97 @@
+use lsp_types::{TextDocumentIdentifier, TextDocumentPositionParams};
+use serde::{Deserialize, Serialize};
+use std::{
+ fmt,
+ hash::{Hash, Hasher},
+ ops::Deref,
+ path::Path,
+};
+use url::{ParseError, Url};
+
+#[derive(Eq, Clone, Serialize, Deserialize)]
+pub struct Uri(Url);
+
+impl Uri {
+ pub fn with_extension(&self, extension: &str) -> Option<Self> {
+ let file_name = self.path_segments()?.last()?;
+ let file_stem = match file_name.rfind('.') {
+ Some(index) => &file_name[..index],
+ None => file_name,
+ };
+ self.join(&format!("{}.{}", file_stem, extension))
+ .ok()
+ .map(Into::into)
+ }
+
+ pub fn parse(input: &str) -> Result<Self, ParseError> {
+ Url::parse(input).map(|url| url.into())
+ }
+
+ pub fn from_file_path<P: AsRef<Path>>(path: P) -> Result<Self, ()> {
+ Url::from_file_path(path).map(|url| url.into())
+ }
+}
+
+impl PartialEq for Uri {
+ fn eq(&self, other: &Self) -> bool {
+ if cfg!(windows) {
+ self.as_str().to_lowercase() == other.as_str().to_lowercase()
+ } else {
+ self.as_str() == other.as_str()
+ }
+ }
+}
+
+impl Hash for Uri {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.as_str().to_lowercase().hash(state);
+ }
+}
+
+impl Deref for Uri {
+ type Target = Url;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl From<Url> for Uri {
+ fn from(url: Url) -> Self {
+ Uri(url)
+ }
+}
+
+impl Into<Url> for Uri {
+ fn into(self) -> Url {
+ self.0
+ }
+}
+
+impl fmt::Debug for Uri {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+impl fmt::Display for Uri {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+pub trait AsUri {
+ fn as_uri(&self) -> Uri;
+}
+
+impl AsUri for TextDocumentIdentifier {
+ fn as_uri(&self) -> Uri {
+ self.uri.clone().into()
+ }
+}
+
+impl AsUri for TextDocumentPositionParams {
+ fn as_uri(&self) -> Uri {
+ self.text_document.as_uri()
+ }
+}