use std::{collections::HashMap, path::PathBuf, str::Utf8Error};
use ariadne::{ColorGenerator, Fmt, Label, ReportBuilder, Source};
use lsp_types_max::Uri as Url;
use thiserror::Error;
use crate::core::document::Document;
#[derive(Error, Clone, Debug, PartialEq, Eq)]
pub enum ParseError {
#[error("{error}")]
LexerError {
span: tree_sitter::Range,
#[source]
error: LexerError,
},
#[error("{error}")]
AstError {
span: tree_sitter::Range,
#[source]
error: AstError,
},
}
impl ParseError {
pub fn to_lsp_diagnostic(
&self,
doc: &Document,
) -> Result<lsp_types_max::Diagnostic, DocumentError> {
let (range, message) = match self {
ParseError::AstError { span: range, error } => (range, error.to_string()),
ParseError::LexerError { span: range, error } => (range, error.to_string()),
};
Ok(lsp_types_max::Diagnostic {
range: doc.denormalize_range(range)?,
severity: Some(lsp_types_max::DiagnosticSeverity::ERROR),
message,
code: Some(lsp_types_max::NumberOrString::String("AUTO_LSP".into())),
..Default::default()
})
}
pub fn to_label(
&self,
source: &Source<&str>,
colors: &mut ColorGenerator,
report: &mut ReportBuilder<'_, std::ops::Range<usize>>,
) {
let range = match self {
ParseError::LexerError { span: range, .. } => range,
ParseError::AstError { span: range, .. } => range,
};
let start_line = source.line(range.start_point.column).unwrap().offset();
let end_line = source.line(range.end_point.row).unwrap().offset();
let start = start_line + range.start_point.column;
let end = end_line + range.end_point.column;
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, .. } => *range,
};
Self::AstError { span: range, error }
}
}
#[derive(Error, Clone, Debug, PartialEq, Eq)]
pub enum LexerError {
#[error("{error}")]
Missing {
range: tree_sitter::Range,
error: String,
grammar_name: &'static str,
},
#[error("{error}")]
Syntax {
range: tree_sitter::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 { span: range, error }
}
}
#[derive(Debug)]
#[salsa::accumulator]
pub struct ParseErrorAccumulator(pub ParseError);
impl ParseErrorAccumulator {
pub fn to_lsp_diagnostic(
&self,
doc: &Document,
) -> Result<lsp_types_max::Diagnostic, DocumentError> {
self.0.to_lsp_diagnostic(doc)
}
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<&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 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(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),
}
impl From<texter::error::Error> for DocumentError {
fn from(error: texter::error::Error) -> Self {
DocumentError::Texter(TexterError::from(error))
}
}