use std::{convert::TryInto, fmt, ops::Range, str::CharIndices};
use super::SourceId;
#[derive(PartialEq, Eq, Clone)]
#[cfg_attr(feature = "span-serialize", derive(serde::Serialize))]
#[cfg_attr(
feature = "self-rust-tokenize",
derive(self_rust_tokenize::SelfRustTokenize)
)]
pub struct Span {
pub start: u32,
pub end: u32,
pub source_id: SourceId,
}
impl fmt::Debug for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.source_id.is_null() {
f.write_fmt(format_args!(
"{}..{}#{}",
self.start,
self.end,
self.source_id.get_count()
))
} else {
f.write_fmt(format_args!("{}..{}", self.start, self.end,))
}
}
}
impl Span {
pub fn is_adjacent_to(&self, other: &Self) -> bool {
self.source_id == other.source_id && self.end == other.start
}
pub fn union(&self, other: &Self) -> Span {
Span {
start: self.start,
end: other.end,
source_id: self.source_id,
}
}
pub fn into_line_column_span(self, on_slice: &str) -> LineColumnSpan {
let mut char_indices = on_slice.char_indices();
let Span { start, end, .. } = self;
let (line_start, column_start) =
get_line_column_from_string_and_idx(start, &mut char_indices);
let (line_end, column_end) = get_line_column_from_string_and_idx(end, &mut char_indices);
LineColumnSpan {
line_start,
column_start,
line_end: line_end + line_start,
column_end: column_end + column_start + 1,
}
}
}
impl From<Span> for Range<u32> {
fn from(span: Span) -> Range<u32> {
Range {
start: span.start,
end: span.end,
}
}
}
impl From<Span> for Range<usize> {
fn from(span: Span) -> Range<usize> {
Range {
start: span.start.try_into().unwrap(),
end: span.end.try_into().unwrap(),
}
}
}
#[derive(PartialEq, Eq, Clone)]
pub struct Position(pub u32);
impl fmt::Debug for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!("Position({})", &self.0))
}
}
impl Position {
pub fn into_line_column_position(self, on_slice: &str) -> LineColumnPosition {
let (line, column) =
get_line_column_from_string_and_idx(self.0, &mut on_slice.char_indices());
LineColumnPosition { line, column }
}
}
fn get_line_column_from_string_and_idx(end: u32, char_indices: &mut CharIndices) -> (u32, u32) {
let (mut line, mut column) = (0, 0);
for (_, chr) in char_indices.take_while(|(idx, _)| *idx < end.try_into().unwrap()) {
if chr == '\n' {
line += 1;
column = 0;
} else {
column += chr.len_utf8() as u32;
}
}
(line, column)
}
fn line_column_position_to_position(
mut line: u32,
column: u32,
char_indices: &mut CharIndices,
full_length: usize,
) -> usize {
while line > 0 {
if '\n' == char_indices.next().unwrap().1 {
line -= 1;
}
}
char_indices
.next()
.map(|(idx, _)| idx)
.unwrap_or(full_length)
+ column as usize
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct LineColumnPosition {
pub line: u32,
pub column: u32,
}
impl LineColumnPosition {
pub fn into_scalar_position(self, on_slice: &str) -> Position {
Position(
line_column_position_to_position(
self.line,
self.column,
&mut on_slice.char_indices(),
on_slice.len(),
)
.try_into()
.unwrap(),
)
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct LineColumnSpan {
pub line_start: u32,
pub column_start: u32,
pub line_end: u32,
pub column_end: u32,
}
impl LineColumnSpan {
pub fn into_scalar_span(self, on_slice: &str, source_id: SourceId) -> Span {
let mut char_indices = on_slice.char_indices();
let start = line_column_position_to_position(
self.line_start,
self.column_start,
&mut char_indices,
on_slice.len(),
);
let end = if self.line_start == self.line_end {
start + self.column_end as usize - self.column_start as usize
} else {
line_column_position_to_position(
self.line_end - self.line_start,
self.column_end,
&mut char_indices,
on_slice.len(),
)
};
Span {
start: start.try_into().unwrap(),
end: end.try_into().unwrap(),
source_id,
}
}
}
#[cfg(feature = "lsp-types-morphisms")]
impl Into<lsp_types::Position> for LineColumnPosition {
fn into(self) -> lsp_types::Position {
lsp_types::Position {
line: self.line,
character: self.column,
}
}
}
#[cfg(feature = "lsp-types-morphisms")]
impl Into<lsp_types::Range> for LineColumnSpan {
fn into(self) -> lsp_types::Range {
lsp_types::Range {
start: lsp_types::Position {
line: self.line_start,
character: self.column_start,
},
end: lsp_types::Position {
line: self.line_end,
character: self.column_end,
},
}
}
}
#[cfg(feature = "lsp-types-morphisms")]
impl From<lsp_types::Position> for LineColumnPosition {
fn from(lsp_position: lsp_types::Position) -> Self {
LineColumnPosition {
column: lsp_position.character,
line: lsp_position.line,
}
}
}
#[cfg(feature = "lsp-types-morphisms")]
impl From<lsp_types::Range> for LineColumnSpan {
fn from(lsp_range: lsp_types::Range) -> Self {
LineColumnSpan {
line_start: lsp_range.start.line,
column_start: lsp_range.start.character,
line_end: lsp_range.end.line,
column_end: lsp_range.end.character,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const SOURCE: &str = "Hello World
I am a paragraph over two lines
Another line";
#[test]
fn scalar_span_to_line_column() {
let paragraph_span = Span {
start: 19,
end: 28,
source_id: SourceId::NULL,
};
assert_eq!(&SOURCE[Range::from(paragraph_span.clone())], "paragraph");
assert_eq!(
paragraph_span.into_line_column_span(SOURCE),
LineColumnSpan {
line_start: 1,
column_start: 7,
line_end: 1,
column_end: 16,
}
);
}
#[test]
fn scalar_position_to_line_column() {
let l_of_line_position = Position(52);
assert_eq!(&SOURCE[l_of_line_position.0.try_into().unwrap()..], "line");
assert_eq!(
l_of_line_position.into_line_column_position(SOURCE),
LineColumnPosition { line: 2, column: 8 }
);
}
#[test]
fn line_column_position_to_position() {
let start_of_another_position = LineColumnPosition { line: 2, column: 0 };
assert_eq!(
start_of_another_position.into_scalar_position(SOURCE),
Position(44)
);
}
#[test]
fn line_column_span_to_span() {
let line_another_span = LineColumnSpan {
line_start: 1,
column_start: 26,
line_end: 2,
column_end: 12,
};
let line_another_span = line_another_span.into_scalar_span(SOURCE, SourceId::NULL);
assert_eq!(
&SOURCE[Range::from(line_another_span)],
"lines\nAnother line"
);
}
}