use crate::vfs::{self, VfsPath};
use crop::Rope;
use lsp_types::{DiagnosticSeverity, NumberOrString};
use solar_interface::{
CharPos, SourceMap, Span,
diagnostics::{Diag, Level},
source_map::SpanLoc,
};
pub(crate) fn vfs_path(url: &lsp_types::Url) -> Option<vfs::VfsPath> {
url.to_file_path().map(VfsPath::from).ok()
}
pub(crate) fn text_range(rope: &Rope, range: lsp_types::Range) -> std::ops::Range<usize> {
let start_line_byte = if range.start.line > rope.line_len() as u32 {
0usize
} else {
rope.byte_of_line(range.start.line as usize)
};
let start_line_utf16 = rope.utf16_code_unit_of_byte(start_line_byte);
let start = rope.byte_of_utf16_code_unit(start_line_utf16 + range.start.character as usize);
let end_line_byte = if range.end.line > rope.line_len() as u32 {
0usize
} else {
rope.byte_of_line(range.end.line as usize)
};
let end_line_utf16 = rope.utf16_code_unit_of_byte(end_line_byte);
let end = rope.byte_of_utf16_code_unit(end_line_utf16 + range.end.character as usize);
start..end
}
pub(crate) fn diagnostic(
source_map: &SourceMap,
diag: &Diag,
) -> Option<(lsp_types::Url, lsp_types::Diagnostic)> {
let lsp_types::Location { uri, range } =
span_to_location(source_map, diag.span.primary_span()?)?;
Some((
uri,
lsp_types::Diagnostic {
range,
severity: Some(severity(diag.level())),
code: diag.code.as_ref().map(|id| NumberOrString::String(id.as_str().to_owned())),
code_description: None,
source: Some("solar".into()),
message: diag.label().into_owned(),
related_information: Some(
diag.children
.iter()
.filter_map(|subdiag| {
Some(lsp_types::DiagnosticRelatedInformation {
location: span_to_location(source_map, subdiag.span.primary_span()?)?,
message: subdiag.label().to_string(),
})
})
.collect(),
),
tags: None,
data: None,
},
))
}
pub(crate) fn span_to_location(source_map: &SourceMap, span: Span) -> Option<lsp_types::Location> {
let (file, SpanLoc { lo, hi }) = source_map.span_to_location_info(span);
let file = file?;
Some(lsp_types::Location {
uri: lsp_types::Url::from_file_path(file.name.as_real().unwrap()).ok()?,
range: lsp_types::Range {
start: lsp_types::Position {
line: lo.line as u32 - 1,
character: utf16_column(lo.col, file.get_line(lo.line - 1)?),
},
end: lsp_types::Position {
line: hi.line as u32 - 1,
character: utf16_column(hi.col, file.get_line(hi.line - 1)?),
},
},
})
}
fn utf16_column(utf8_pos: CharPos, line: &str) -> u32 {
let mut utf16_codepoints = 0;
for (idx, char) in line.chars().enumerate() {
if idx >= utf8_pos.to_usize() {
break;
}
utf16_codepoints += char.len_utf16();
}
utf16_codepoints as u32
}
#[inline]
fn severity(level: Level) -> lsp_types::DiagnosticSeverity {
match level {
Level::FailureNote | Level::Fatal | Level::Bug | Level::Error => DiagnosticSeverity::ERROR,
Level::Warning => DiagnosticSeverity::WARNING,
Level::Help | Level::OnceHelp => DiagnosticSeverity::HINT,
Level::Note | Level::OnceNote | Level::Allow => DiagnosticSeverity::INFORMATION,
}
}