use std::{collections::HashMap, path::PathBuf, str::Utf8Error};
use ariadne::{ColorGenerator, Fmt, Label, ReportBuilder, Source};
use lsp_types::Url;
use thiserror::Error;
#[derive(Error, Clone, Debug, PartialEq, Eq)]
pub enum ParseError {
#[error("{error}")]
LexerError {
range: lsp_types::Range,
#[source]
error: LexerError,
},
#[error("{error}")]
AstError {
range: lsp_types::Range,
#[source]
error: AstError,
},
}
impl From<ParseError> for lsp_types::Diagnostic {
fn from(error: ParseError) -> Self {
(&error).into()
}
}
impl From<&ParseError> for lsp_types::Diagnostic {
fn from(error: &ParseError) -> Self {
let (range, message) = match error {
ParseError::AstError { range, error } => (*range, error.to_string()),
ParseError::LexerError { range, error } => (*range, error.to_string()),
};
lsp_types::Diagnostic {
range,
severity: Some(lsp_types::DiagnosticSeverity::ERROR),
message,
code: Some(lsp_types::NumberOrString::String("AUTO_LSP".into())),
..Default::default()
}
}
}
impl ParseError {
pub fn to_label(
&self,
source: &Source<&str>,
colors: &mut ColorGenerator,
report: &mut ReportBuilder<'_, std::ops::Range<usize>>,
) {
let range = match self {
ParseError::LexerError { range, .. } => range,
ParseError::AstError { range, .. } => range,
};
let start_line = source.line(range.start.line as usize).unwrap().offset();
let end_line = source.line(range.end.line as usize).unwrap().offset();
let start = start_line + range.start.character as usize;
let end = end_line + range.end.character as usize;
let curr_color = colors.next();
report.add_label(
Label::new(start..end)
.with_message(format!("{}", self.to_string().fg(curr_color)))
.with_color(curr_color),
);
}
}
#[derive(Error, Clone, Debug, PartialEq, Eq)]
pub enum AstError {
#[error("Unexpected {symbol} in {parent_name}")]
UnexpectedSymbol {
range: tree_sitter::Range,
symbol: &'static str,
parent_name: &'static str,
},
}
impl From<AstError> for ParseError {
fn from(error: AstError) -> Self {
let range = match &error {
AstError::UnexpectedSymbol { range, .. } => lsp_types::Range {
start: lsp_types::Position {
line: range.start_point.row as u32,
character: range.start_point.column as u32,
},
end: lsp_types::Position {
line: range.end_point.row as u32,
character: range.end_point.column as u32,
},
},
};
Self::AstError { range, error }
}
}
#[derive(Error, Clone, Debug, PartialEq, Eq)]
pub enum LexerError {
#[error("{error}")]
Missing {
range: lsp_types::Range,
error: String,
grammar_name: &'static str,
},
#[error("{error}")]
Syntax {
range: lsp_types::Range,
error: String,
affected: String,
},
}
impl From<LexerError> for ParseError {
fn from(error: LexerError) -> Self {
let range = match &error {
LexerError::Missing { range, .. } => *range,
LexerError::Syntax { range, .. } => *range,
};
Self::LexerError { range, error }
}
}
#[derive(Debug)]
#[salsa::accumulator]
pub struct ParseErrorAccumulator(pub ParseError);
impl ParseErrorAccumulator {
pub fn to_label(
&self,
source: &Source<&str>,
colors: &mut ColorGenerator,
report: &mut ReportBuilder<'_, std::ops::Range<usize>>,
) {
self.0.to_label(source, colors, report);
}
}
impl From<&ParseErrorAccumulator> for lsp_types::Diagnostic {
fn from(error: &ParseErrorAccumulator) -> Self {
Self::from(&error.0)
}
}
impl From<&ParseError> for ParseErrorAccumulator {
fn from(diagnostic: &ParseError) -> Self {
Self(diagnostic.clone())
}
}
impl From<ParseError> for ParseErrorAccumulator {
fn from(diagnostic: ParseError) -> Self {
Self(diagnostic)
}
}
impl From<&ParseErrorAccumulator> for ParseError {
fn from(diagnostic: &ParseErrorAccumulator) -> Self {
diagnostic.0.clone()
}
}
impl From<LexerError> for ParseErrorAccumulator {
fn from(error: LexerError) -> Self {
Self(error.into())
}
}
impl From<AstError> for ParseErrorAccumulator {
fn from(error: AstError) -> Self {
Self(ParseError::from(error))
}
}
#[derive(Error, Clone, Debug, PartialEq, Eq)]
pub enum PositionError {
#[error("Failed to find position of offset {offset}, max line length is {length}")]
LineOutOfBound { offset: usize, length: usize },
#[error("Failed to get position of offset {offset}")]
WrongPosition { offset: usize },
#[error("Failed to get range of {range:?}: {position_error}")]
WrongRange {
range: std::ops::Range<usize>,
#[source]
position_error: Box<PositionError>,
},
#[error("Failed to get text in {range:?}")]
WrongTextRange { range: std::ops::Range<usize> },
#[error("Failed to get text in {range:?}: Encountered UTF-8 error {utf8_error}")]
UTF8Error {
range: std::ops::Range<usize>,
utf8_error: Utf8Error,
},
}
#[derive(Error, Clone, Debug, PartialEq, Eq)]
pub enum RuntimeError {
#[error("Document error in {uri}: {error}")]
DocumentError {
uri: Url,
#[source]
error: DocumentError,
},
#[error("Missing initialization options from client")]
MissingOptions,
#[error("Missing perFileParser object from initialization options")]
MissingPerFileParser,
#[error(transparent)]
DataBaseError(#[from] DataBaseError),
#[error(transparent)]
FileSystemError(#[from] FileSystemError),
#[error(transparent)]
ExtensionError(#[from] ExtensionError),
}
impl From<(&Url, DocumentError)> for RuntimeError {
fn from((uri, error): (&Url, DocumentError)) -> Self {
RuntimeError::DocumentError {
uri: uri.clone(),
error,
}
}
}
impl From<(&Url, TreeSitterError)> for RuntimeError {
fn from((uri, error): (&Url, TreeSitterError)) -> Self {
RuntimeError::DocumentError {
uri: uri.clone(),
error: DocumentError::TreeSitter(error),
}
}
}
impl From<(&Url, TexterError)> for RuntimeError {
fn from((uri, error): (&Url, TexterError)) -> Self {
RuntimeError::DocumentError {
uri: uri.clone(),
error: DocumentError::Texter(error),
}
}
}
#[derive(Error, Clone, Debug, PartialEq, Eq)]
pub enum FileSystemError {
#[cfg(windows)]
#[error("Invalid host '{host}' for file path: {path}")]
FileUrlHost { host: String, path: Url },
#[error("Failed to convert url {path} to file path")]
FileUrlToFilePath { path: Url },
#[error("Failed to convert file path {path} to url")]
FilePathToUrl { path: PathBuf },
#[error("Failed to get extension of file {path}")]
FileExtension { path: Url },
#[error("Failed to open file {path}: {error}")]
FileOpen { path: Url, error: String },
#[error("Failed to read file {path}: {error}")]
FileRead { path: Url, error: String },
#[error(transparent)]
ExtensionError(#[from] ExtensionError),
}
#[derive(Error, Clone, Debug, PartialEq, Eq)]
pub enum ExtensionError {
#[error("Unknown file extension {extension}, available extensions are: {available:?}")]
UnknownExtension {
extension: String,
available: HashMap<String, String>,
},
#[error("No parser found for extension {extension}, available parsers are: {available:?}")]
UnknownParser {
extension: String,
available: Vec<&'static str>,
},
}
#[derive(Error, Clone, Debug, PartialEq, Eq)]
pub enum DataBaseError {
#[error("Failed to get file {uri}")]
FileNotFound { uri: Url },
#[error("File {uri} already exists")]
FileAlreadyExists { uri: Url },
#[error("Document error in {uri}: {error}")]
DocumentError {
uri: Url,
#[source]
error: DocumentError,
},
}
impl From<(&Url, DocumentError)> for DataBaseError {
fn from((uri, error): (&Url, DocumentError)) -> Self {
DataBaseError::DocumentError {
uri: uri.clone(),
error,
}
}
}
impl From<(&Url, TreeSitterError)> for DataBaseError {
fn from((uri, error): (&Url, TreeSitterError)) -> Self {
DataBaseError::DocumentError {
uri: uri.clone(),
error: DocumentError::TreeSitter(error),
}
}
}
#[derive(Error, Clone, Debug, PartialEq, Eq)]
pub enum DocumentError {
#[error(transparent)]
TreeSitter(#[from] TreeSitterError),
#[error(transparent)]
Texter(#[from] TexterError),
}
#[derive(Error, Clone, Debug, PartialEq, Eq)]
pub enum TreeSitterError {
#[error("Tree sitter failed to parse tree")]
TreeSitterParser,
}
#[derive(Error, Clone, Debug, PartialEq, Eq)]
pub enum TexterError {
#[error("Texter failed to handle document")]
TexterError(#[from] texter::error::Error),
}