use std::path::PathBuf;
use serde::{Deserialize, Serialize};
#[derive(Debug, thiserror::Error)]
pub enum LspError {
#[error("LSP server not found for file type: {0}")]
ServerNotFound(String),
#[error("LSP server failed to start: {0}")]
ServerStartFailed(String),
#[error("LSP communication error: {0}")]
CommunicationError(String),
#[error("LSP server timeout: {0}")]
Timeout(String),
#[error("LSP server not initialized")]
NotInitialized,
#[error("Invalid LSP response: {0}")]
InvalidResponse(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("Tower LSP error: {0}")]
TowerLsp(String),
#[error("File not open: {0}")]
FileNotOpen(PathBuf),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LspDiagnostic {
pub file_path: PathBuf,
pub line: u32,
pub column: u32,
pub end_line: u32,
pub end_column: u32,
pub message: String,
pub severity: DiagnosticSeverity,
pub source: Option<String>,
pub code: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DiagnosticSeverity {
Error,
Warning,
Information,
Hint,
}
impl std::fmt::Display for DiagnosticSeverity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DiagnosticSeverity::Error => write!(f, "ERROR"),
DiagnosticSeverity::Warning => write!(f, "WARNING"),
DiagnosticSeverity::Information => write!(f, "INFO"),
DiagnosticSeverity::Hint => write!(f, "HINT"),
}
}
}
impl From<lsp_types::DiagnosticSeverity> for DiagnosticSeverity {
fn from(severity: lsp_types::DiagnosticSeverity) -> Self {
match severity {
lsp_types::DiagnosticSeverity::ERROR => DiagnosticSeverity::Error,
lsp_types::DiagnosticSeverity::WARNING => DiagnosticSeverity::Warning,
lsp_types::DiagnosticSeverity::INFORMATION => DiagnosticSeverity::Information,
lsp_types::DiagnosticSeverity::HINT => DiagnosticSeverity::Hint,
_ => DiagnosticSeverity::Error, }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Position {
pub line: u32,
pub character: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Range {
pub start: Position,
pub end: Position,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Location {
pub file_path: PathBuf,
pub range: Range,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompletionItem {
pub label: String,
pub kind: Option<CompletionItemKind>,
pub detail: Option<String>,
pub documentation: Option<String>,
pub insert_text: Option<String>,
pub filter_text: Option<String>,
pub sort_text: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CompletionItemKind {
Text,
Method,
Function,
Constructor,
Field,
Variable,
Class,
Interface,
Module,
Property,
Unit,
Value,
Enum,
Keyword,
Snippet,
Color,
File,
Reference,
Folder,
EnumMember,
Constant,
Struct,
Event,
Operator,
TypeParameter,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Hover {
pub contents: String,
pub range: Option<Range>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodeAction {
pub title: String,
pub kind: Option<String>,
pub diagnostics: Vec<LspDiagnostic>,
pub is_preferred: bool,
pub disabled: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SymbolInformation {
pub name: String,
pub kind: SymbolKind,
pub location: Location,
pub container_name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SymbolKind {
File,
Module,
Namespace,
Package,
Class,
Method,
Property,
Field,
Constructor,
Enum,
Interface,
Function,
Variable,
Constant,
String,
Number,
Boolean,
Array,
Object,
Key,
Null,
EnumMember,
Struct,
Event,
Operator,
TypeParameter,
}
impl From<lsp_types::Position> for Position {
fn from(pos: lsp_types::Position) -> Self {
Self {
line: pos.line,
character: pos.character,
}
}
}
impl From<Position> for lsp_types::Position {
fn from(pos: Position) -> Self {
Self {
line: pos.line,
character: pos.character,
}
}
}
impl From<lsp_types::Range> for Range {
fn from(range: lsp_types::Range) -> Self {
Self {
start: range.start.into(),
end: range.end.into(),
}
}
}
impl From<Range> for lsp_types::Range {
fn from(range: Range) -> Self {
Self {
start: range.start.into(),
end: range.end.into(),
}
}
}
impl From<lsp_types::CompletionItemKind> for CompletionItemKind {
fn from(kind: lsp_types::CompletionItemKind) -> Self {
match kind {
lsp_types::CompletionItemKind::TEXT => CompletionItemKind::Text,
lsp_types::CompletionItemKind::METHOD => CompletionItemKind::Method,
lsp_types::CompletionItemKind::FUNCTION => CompletionItemKind::Function,
lsp_types::CompletionItemKind::CONSTRUCTOR => CompletionItemKind::Constructor,
lsp_types::CompletionItemKind::FIELD => CompletionItemKind::Field,
lsp_types::CompletionItemKind::VARIABLE => CompletionItemKind::Variable,
lsp_types::CompletionItemKind::CLASS => CompletionItemKind::Class,
lsp_types::CompletionItemKind::INTERFACE => CompletionItemKind::Interface,
lsp_types::CompletionItemKind::MODULE => CompletionItemKind::Module,
lsp_types::CompletionItemKind::PROPERTY => CompletionItemKind::Property,
lsp_types::CompletionItemKind::UNIT => CompletionItemKind::Unit,
lsp_types::CompletionItemKind::VALUE => CompletionItemKind::Value,
lsp_types::CompletionItemKind::ENUM => CompletionItemKind::Enum,
lsp_types::CompletionItemKind::KEYWORD => CompletionItemKind::Keyword,
lsp_types::CompletionItemKind::SNIPPET => CompletionItemKind::Snippet,
lsp_types::CompletionItemKind::COLOR => CompletionItemKind::Color,
lsp_types::CompletionItemKind::FILE => CompletionItemKind::File,
lsp_types::CompletionItemKind::REFERENCE => CompletionItemKind::Reference,
lsp_types::CompletionItemKind::FOLDER => CompletionItemKind::Folder,
lsp_types::CompletionItemKind::ENUM_MEMBER => CompletionItemKind::EnumMember,
lsp_types::CompletionItemKind::CONSTANT => CompletionItemKind::Constant,
lsp_types::CompletionItemKind::STRUCT => CompletionItemKind::Struct,
lsp_types::CompletionItemKind::EVENT => CompletionItemKind::Event,
lsp_types::CompletionItemKind::OPERATOR => CompletionItemKind::Operator,
lsp_types::CompletionItemKind::TYPE_PARAMETER => CompletionItemKind::TypeParameter,
_ => CompletionItemKind::Text, }
}
}
impl From<lsp_types::SymbolKind> for SymbolKind {
fn from(kind: lsp_types::SymbolKind) -> Self {
match kind {
lsp_types::SymbolKind::FILE => SymbolKind::File,
lsp_types::SymbolKind::MODULE => SymbolKind::Module,
lsp_types::SymbolKind::NAMESPACE => SymbolKind::Namespace,
lsp_types::SymbolKind::PACKAGE => SymbolKind::Package,
lsp_types::SymbolKind::CLASS => SymbolKind::Class,
lsp_types::SymbolKind::METHOD => SymbolKind::Method,
lsp_types::SymbolKind::PROPERTY => SymbolKind::Property,
lsp_types::SymbolKind::FIELD => SymbolKind::Field,
lsp_types::SymbolKind::CONSTRUCTOR => SymbolKind::Constructor,
lsp_types::SymbolKind::ENUM => SymbolKind::Enum,
lsp_types::SymbolKind::INTERFACE => SymbolKind::Interface,
lsp_types::SymbolKind::FUNCTION => SymbolKind::Function,
lsp_types::SymbolKind::VARIABLE => SymbolKind::Variable,
lsp_types::SymbolKind::CONSTANT => SymbolKind::Constant,
lsp_types::SymbolKind::STRING => SymbolKind::String,
lsp_types::SymbolKind::NUMBER => SymbolKind::Number,
lsp_types::SymbolKind::BOOLEAN => SymbolKind::Boolean,
lsp_types::SymbolKind::ARRAY => SymbolKind::Array,
lsp_types::SymbolKind::OBJECT => SymbolKind::Object,
lsp_types::SymbolKind::KEY => SymbolKind::Key,
lsp_types::SymbolKind::NULL => SymbolKind::Null,
lsp_types::SymbolKind::ENUM_MEMBER => SymbolKind::EnumMember,
lsp_types::SymbolKind::STRUCT => SymbolKind::Struct,
lsp_types::SymbolKind::EVENT => SymbolKind::Event,
lsp_types::SymbolKind::OPERATOR => SymbolKind::Operator,
lsp_types::SymbolKind::TYPE_PARAMETER => SymbolKind::TypeParameter,
_ => SymbolKind::File, }
}
}
impl From<lsp_types::Diagnostic> for LspDiagnostic {
fn from(diagnostic: lsp_types::Diagnostic) -> Self {
Self {
file_path: PathBuf::new(), line: diagnostic.range.start.line,
column: diagnostic.range.start.character,
end_line: diagnostic.range.end.line,
end_column: diagnostic.range.end.character,
message: diagnostic.message,
severity: diagnostic.severity
.map(DiagnosticSeverity::from)
.unwrap_or(DiagnosticSeverity::Error),
source: diagnostic.source,
code: diagnostic.code.and_then(|c| match c {
lsp_types::NumberOrString::Number(n) => Some(n.to_string()),
lsp_types::NumberOrString::String(s) => Some(s),
}),
}
}
}
impl LspDiagnostic {
pub fn format(&self) -> String {
let severity_str = match self.severity {
DiagnosticSeverity::Error => "ERROR",
DiagnosticSeverity::Warning => "WARNING",
DiagnosticSeverity::Information => "INFO",
DiagnosticSeverity::Hint => "HINT",
};
let location = format!("{}:{}:{}",
self.file_path.display(),
self.line + 1,
self.column + 1
);
let source_info = self.source.as_ref()
.map(|s| format!(" [{}]", s))
.unwrap_or_default();
format!("{} {}{}: {}", severity_str, location, source_info, self.message)
}
pub fn is_error(&self) -> bool {
self.severity == DiagnosticSeverity::Error
}
pub fn is_warning(&self) -> bool {
self.severity == DiagnosticSeverity::Warning
}
}