use bynkc::span::Span;
use tower_lsp::lsp_types::{Position, Range};
pub fn offset_to_position(source: &str, offset: usize) -> Position {
let mut line: u32 = 0;
let mut column: u32 = 0;
let bytes = source.as_bytes();
let limit = offset.min(bytes.len());
let mut i = 0;
while i < limit {
let b = bytes[i];
if b == b'\n' {
line += 1;
column = 0;
i += 1;
continue;
}
let cp_len = utf8_char_len(b);
column += if cp_len == 4 { 2 } else { 1 };
i += cp_len;
}
Position {
line,
character: column,
}
}
pub fn position_to_offset(source: &str, position: Position) -> Option<usize> {
let target_line = position.line;
let target_char = position.character;
let mut line: u32 = 0;
let mut character: u32 = 0;
let bytes = source.as_bytes();
let mut i = 0;
while i < bytes.len() {
if line == target_line && character == target_char {
return Some(i);
}
let b = bytes[i];
if b == b'\n' {
if line == target_line {
return Some(i);
}
line += 1;
character = 0;
i += 1;
continue;
}
let cp_len = utf8_char_len(b);
character += if cp_len == 4 { 2 } else { 1 };
i += cp_len;
}
if line == target_line && character >= target_char {
Some(i)
} else {
None
}
}
fn utf8_char_len(first: u8) -> usize {
if first < 0x80 {
1
} else if first < 0xC0 {
1
} else if first < 0xE0 {
2
} else if first < 0xF0 {
3
} else {
4
}
}
pub fn span_to_range(source: &str, span: Span) -> Range {
Range {
start: offset_to_position(source, span.start),
end: offset_to_position(source, span.end),
}
}
pub fn end_position(source: &str) -> Position {
offset_to_position(source, source.len())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ascii_offsets_match_columns() {
let src = "abc\ndef";
assert_eq!(offset_to_position(src, 0), Position::new(0, 0));
assert_eq!(offset_to_position(src, 2), Position::new(0, 2));
assert_eq!(offset_to_position(src, 4), Position::new(1, 0));
assert_eq!(offset_to_position(src, 6), Position::new(1, 2));
}
#[test]
fn position_round_trip() {
let src = "alpha\n beta\ngamma";
let p = Position::new(1, 4);
let off = position_to_offset(src, p).unwrap();
assert_eq!(offset_to_position(src, off), p);
}
}