use crate::types::{DocumentUri, Uri};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Position {
pub line: u32,
pub character: u32,
}
impl Position {
pub fn new(line: u32, character: u32) -> Self {
Self { line, character }
}
pub fn start() -> Self {
Self::new(0, 0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Range {
pub start: Position,
pub end: Position,
}
impl Range {
pub fn new(start: Position, end: Position) -> Self {
Self { start, end }
}
pub fn from_coords(start_line: u32, start_char: u32, end_line: u32, end_char: u32) -> Self {
Self::new(
Position::new(start_line, start_char),
Position::new(end_line, end_char),
)
}
pub fn single_char(position: Position) -> Self {
Self::new(
position,
Position::new(position.line, position.character + 1),
)
}
pub fn contains(&self, position: Position) -> bool {
position >= self.start && position < self.end
}
pub fn is_empty(&self) -> bool {
self.start == self.end
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Location {
pub uri: DocumentUri,
pub range: Range,
}
impl Location {
pub fn new(uri: impl Into<DocumentUri>, range: Range) -> Self {
Self {
uri: uri.into(),
range,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct LocationLink {
#[serde(skip_serializing_if = "Option::is_none")]
pub origin_selection_range: Option<Range>,
pub target_uri: DocumentUri,
pub target_range: Range,
pub target_selection_range: Range,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Diagnostic {
pub range: Range,
#[serde(skip_serializing_if = "Option::is_none")]
pub severity: Option<DiagnosticSeverity>,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<DiagnosticCode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub code_description: Option<CodeDescription>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<DiagnosticTag>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub related_information: Option<Vec<DiagnosticRelatedInformation>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum DiagnosticSeverity {
Error = 1,
Warning = 2,
Information = 3,
Hint = 4,
}
impl Serialize for DiagnosticSeverity {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_u8(*self as u8)
}
}
impl<'de> Deserialize<'de> for DiagnosticSeverity {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = u8::deserialize(deserializer)?;
match value {
1 => Ok(DiagnosticSeverity::Error),
2 => Ok(DiagnosticSeverity::Warning),
3 => Ok(DiagnosticSeverity::Information),
4 => Ok(DiagnosticSeverity::Hint),
_ => Err(serde::de::Error::custom(format!(
"Invalid diagnostic severity: {}",
value
))),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(untagged)]
pub enum DiagnosticCode {
Number(i32),
String(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct CodeDescription {
pub href: Uri,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum DiagnosticTag {
Unnecessary = 1,
Deprecated = 2,
}
impl Serialize for DiagnosticTag {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_u8(*self as u8)
}
}
impl<'de> Deserialize<'de> for DiagnosticTag {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = u8::deserialize(deserializer)?;
match value {
1 => Ok(DiagnosticTag::Unnecessary),
2 => Ok(DiagnosticTag::Deprecated),
_ => Err(serde::de::Error::custom(format!(
"Invalid diagnostic tag: {}",
value
))),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct DiagnosticRelatedInformation {
pub location: Location,
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Command {
pub title: String,
pub command: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<Vec<serde_json::Value>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TextEdit {
pub range: Range,
pub new_text: String,
}
impl TextEdit {
pub fn new(range: Range, new_text: impl Into<String>) -> Self {
Self {
range,
new_text: new_text.into(),
}
}
pub fn insert(position: Position, text: impl Into<String>) -> Self {
Self::new(Range::new(position, position), text)
}
pub fn delete(range: Range) -> Self {
Self::new(range, "")
}
pub fn replace(range: Range, new_text: impl Into<String>) -> Self {
Self::new(range, new_text)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ChangeAnnotation {
pub label: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub needs_confirmation: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
pub type ChangeAnnotationIdentifier = String;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct AnnotatedTextEdit {
pub range: Range,
pub new_text: String,
pub annotation_id: ChangeAnnotationIdentifier,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TextDocumentEdit {
pub text_document: OptionalVersionedTextDocumentIdentifier,
pub edits: Vec<OneOf<TextEdit, AnnotatedTextEdit>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(tag = "kind")]
pub enum ResourceOperation {
#[serde(rename = "create")]
Create(CreateFile),
#[serde(rename = "rename")]
Rename(RenameFile),
#[serde(rename = "delete")]
Delete(DeleteFile),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct CreateFile {
pub kind: String, pub uri: DocumentUri,
#[serde(skip_serializing_if = "Option::is_none")]
pub options: Option<CreateFileOptions>,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotation_id: Option<ChangeAnnotationIdentifier>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct CreateFileOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub overwrite: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ignore_if_exists: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct RenameFile {
pub kind: String, pub old_uri: DocumentUri,
pub new_uri: DocumentUri,
#[serde(skip_serializing_if = "Option::is_none")]
pub options: Option<RenameFileOptions>,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotation_id: Option<ChangeAnnotationIdentifier>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct RenameFileOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub overwrite: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ignore_if_exists: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct DeleteFile {
pub kind: String, pub uri: DocumentUri,
#[serde(skip_serializing_if = "Option::is_none")]
pub options: Option<DeleteFileOptions>,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotation_id: Option<ChangeAnnotationIdentifier>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct DeleteFileOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub recursive: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ignore_if_not_exists: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WorkspaceEdit {
#[serde(skip_serializing_if = "Option::is_none")]
pub changes: Option<HashMap<DocumentUri, Vec<TextEdit>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub document_changes: Option<Vec<DocumentChange>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub change_annotations: Option<HashMap<ChangeAnnotationIdentifier, ChangeAnnotation>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum DocumentChange {
TextDocumentEdit(TextDocumentEdit),
ResourceOperation(ResourceOperation),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TextDocumentIdentifier {
pub uri: DocumentUri,
}
impl TextDocumentIdentifier {
pub fn new(uri: impl Into<DocumentUri>) -> Self {
Self { uri: uri.into() }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct VersionedTextDocumentIdentifier {
pub uri: DocumentUri,
pub version: i32,
}
impl VersionedTextDocumentIdentifier {
pub fn new(uri: impl Into<DocumentUri>, version: i32) -> Self {
Self {
uri: uri.into(),
version,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct OptionalVersionedTextDocumentIdentifier {
pub uri: DocumentUri,
pub version: Option<i32>,
}
impl OptionalVersionedTextDocumentIdentifier {
pub fn new(uri: impl Into<DocumentUri>, version: Option<i32>) -> Self {
Self {
uri: uri.into(),
version,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(untagged)]
pub enum OneOf<A, B> {
Left(A),
Right(B),
}
impl<A, B> OneOf<A, B> {
pub fn is_left(&self) -> bool {
matches!(self, OneOf::Left(_))
}
pub fn is_right(&self) -> bool {
matches!(self, OneOf::Right(_))
}
pub fn left(self) -> Option<A> {
match self {
OneOf::Left(a) => Some(a),
OneOf::Right(_) => None,
}
}
pub fn right(self) -> Option<B> {
match self {
OneOf::Right(b) => Some(b),
OneOf::Left(_) => None,
}
}
}
impl<A> From<A> for OneOf<A, ()> {
fn from(a: A) -> Self {
OneOf::Left(a)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_position_ordering() {
let pos1 = Position::new(1, 5);
let pos2 = Position::new(1, 10);
let pos3 = Position::new(2, 0);
assert!(pos1 < pos2);
assert!(pos2 < pos3);
assert!(pos1 < pos3);
}
#[test]
fn test_range_contains() {
let range = Range::new(Position::new(1, 5), Position::new(1, 10));
assert!(range.contains(Position::new(1, 7)));
assert!(!range.contains(Position::new(1, 4)));
assert!(!range.contains(Position::new(1, 10))); assert!(!range.contains(Position::new(2, 0)));
}
#[test]
fn test_range_is_empty() {
let pos = Position::new(1, 5);
let empty_range = Range::new(pos, pos);
let non_empty_range = Range::new(pos, Position::new(1, 6));
assert!(empty_range.is_empty());
assert!(!non_empty_range.is_empty());
}
#[test]
fn test_text_edit_operations() {
let pos = Position::new(1, 5);
let range = Range::new(pos, Position::new(1, 10));
let insert = TextEdit::insert(pos, "text");
assert_eq!(insert.range.start, insert.range.end);
let delete = TextEdit::delete(range);
assert_eq!(delete.new_text, "");
let replace = TextEdit::replace(range, "new");
assert_eq!(replace.new_text, "new");
assert_eq!(replace.range, range);
}
#[test]
fn test_diagnostic_severity_values() {
assert_eq!(DiagnosticSeverity::Error as u8, 1);
assert_eq!(DiagnosticSeverity::Warning as u8, 2);
assert_eq!(DiagnosticSeverity::Information as u8, 3);
assert_eq!(DiagnosticSeverity::Hint as u8, 4);
}
#[test]
fn test_one_of_type() {
let left: OneOf<i32, String> = OneOf::Left(42);
let right: OneOf<i32, String> = OneOf::Right("test".to_string());
assert!(left.is_left());
assert!(!left.is_right());
assert!(!right.is_left());
assert!(right.is_right());
assert_eq!(left.left(), Some(42));
assert_eq!(right.right(), Some("test".to_string()));
}
}