use std::sync::Arc;
use swc_common::input::StringInput;
pub use text_lines::LineAndColumnIndex;
pub use text_lines::TextLines;
use super::pos::*;
use super::text_encoding::strip_bom_mut;
use super::text_encoding::BOM_CHAR;
use super::LineAndColumnDisplay;
#[derive(Clone)]
pub struct SourceTextInfo {
start_pos: StartSourcePos,
text: Arc<str>,
text_lines: Arc<TextLines>,
}
impl SourceTextInfo {
pub fn new(text: Arc<str>) -> Self {
Self::new_with_pos(StartSourcePos::START_SOURCE_POS.as_source_pos(), text)
}
pub fn new_with_pos(start_pos: SourcePos, text: Arc<str>) -> Self {
let text = if text.starts_with(BOM_CHAR) {
let mut text = text.to_string();
strip_bom_mut(&mut text);
text.into()
} else {
text
};
Self::new_with_indent_width(start_pos, text, 2)
}
pub fn new_with_indent_width(start_pos: SourcePos, text: Arc<str>, indent_width: usize) -> Self {
Self {
start_pos: StartSourcePos(start_pos),
text_lines: Arc::new(TextLines::with_indent_width(&text, indent_width)),
text,
}
}
pub fn from_string(text: String) -> Self {
Self::new(text.into())
}
pub fn as_string_input(&self) -> StringInput {
let range = self.range();
StringInput::new(self.text_str(), range.start.as_byte_pos(), range.end.as_byte_pos())
}
pub fn text(&self) -> Arc<str> {
self.text.clone()
}
pub fn text_str(&self) -> &str {
&self.text
}
pub fn range(&self) -> SourceRange<StartSourcePos> {
SourceRange::new(self.start_pos, self.start_pos + self.text.len())
}
pub fn lines_count(&self) -> usize {
self.text_lines.lines_count()
}
pub fn line_index(&self, pos: SourcePos) -> usize {
self.assert_pos(pos);
self.text_lines.line_index(self.get_relative_index_from_pos(pos))
}
pub fn line_start(&self, line_index: usize) -> SourcePos {
self.assert_line_index(line_index);
self.get_pos_from_relative_index(self.text_lines.line_start(line_index))
}
pub fn line_end(&self, line_index: usize) -> SourcePos {
self.assert_line_index(line_index);
self.get_pos_from_relative_index(self.text_lines.line_end(line_index))
}
pub fn line_and_column_index(&self, pos: SourcePos) -> LineAndColumnIndex {
self.assert_pos(pos);
self.text_lines.line_and_column_index(self.get_relative_index_from_pos(pos))
}
pub fn line_and_column_display(&self, pos: SourcePos) -> LineAndColumnDisplay {
self.assert_pos(pos);
self.text_lines.line_and_column_display(self.get_relative_index_from_pos(pos))
}
pub fn line_and_column_display_with_indent_width(&self, pos: SourcePos, indent_width: usize) -> LineAndColumnDisplay {
self.assert_pos(pos);
self
.text_lines
.line_and_column_display_with_indent_width(self.get_relative_index_from_pos(pos), indent_width)
}
pub fn loc_to_source_pos(&self, line_and_column_index: LineAndColumnIndex) -> SourcePos {
self.assert_line_index(line_and_column_index.line_index);
self.get_pos_from_relative_index(self.text_lines.byte_index(line_and_column_index))
}
pub fn line_text(&self, line_index: usize) -> &str {
let range = SourceRange {
start: self.line_start(line_index),
end: self.line_end(line_index),
};
self.range_text(&range)
}
pub fn range_text(&self, range: &SourceRange) -> &str {
let start = self.get_relative_index_from_pos(range.start);
let end = self.get_relative_index_from_pos(range.end);
&self.text_str()[start..end]
}
fn assert_pos(&self, pos: SourcePos) {
let range = self.range();
if pos < range.start {
panic!("The provided position {} was less than the start position {}.", pos, range.start,);
} else if pos > range.end {
panic!("The provided position {} was greater than the end position {}.", pos, range.end,);
}
}
fn assert_line_index(&self, line_index: usize) {
if line_index >= self.lines_count() {
panic!(
"The specified line index {} was greater or equal to the number of lines of {}.",
line_index,
self.lines_count()
);
}
}
fn get_relative_index_from_pos(&self, pos: SourcePos) -> usize {
pos - self.start_pos
}
fn get_pos_from_relative_index(&self, relative_index: usize) -> SourcePos {
self.start_pos + relative_index
}
}
impl std::fmt::Debug for SourceTextInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SourceTextInfo")
.field("start_pos", &self.start_pos)
.field("text", &self.text)
.finish()
}
}
pub trait SourceTextInfoProvider<'a> {
fn text_info(&self) -> &'a SourceTextInfo;
}
impl<'a> SourceTextInfoProvider<'a> for &'a SourceTextInfo {
fn text_info(&self) -> &'a SourceTextInfo {
self
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn line_and_column_index() {
let text = "12\n3\r\nβ\n5";
for i in 0..10 {
run_with_text_info(SourceTextInfo::new_with_pos(SourcePos::new(i), text.to_string().into()), i);
}
fn run_with_text_info(text_info: SourceTextInfo, i: usize) {
assert_pos_line_and_col(&text_info, i, 0, 0); assert_pos_line_and_col(&text_info, 1 + i, 0, 1); assert_pos_line_and_col(&text_info, 2 + i, 0, 2); assert_pos_line_and_col(&text_info, 3 + i, 1, 0); assert_pos_line_and_col(&text_info, 4 + i, 1, 1); assert_pos_line_and_col(&text_info, 5 + i, 1, 2); assert_pos_line_and_col(&text_info, 6 + i, 2, 0); assert_pos_line_and_col(&text_info, 7 + i, 2, 0); assert_pos_line_and_col(&text_info, 8 + i, 2, 1); assert_pos_line_and_col(&text_info, 9 + i, 3, 0); assert_pos_line_and_col(&text_info, 10 + i, 3, 1); }
}
fn assert_pos_line_and_col(text_info: &SourceTextInfo, pos: usize, line_index: usize, column_index: usize) {
assert_eq!(
text_info.line_and_column_index(SourcePos::new(pos)),
LineAndColumnIndex { line_index, column_index }
);
}
#[test]
#[should_panic(expected = "The provided position 0 was less than the start position 1.")]
fn line_and_column_index_panic_less_than() {
let info = SourceTextInfo::new_with_pos(SourcePos::new(1), "test".to_string().into());
info.line_and_column_index(SourcePos::new(0));
}
#[test]
#[should_panic(expected = "The provided position 6 was greater than the end position 5.")]
fn line_and_column_index_panic_greater_than() {
let info = SourceTextInfo::new_with_pos(SourcePos::new(1), "test".to_string().into());
info.line_and_column_index(SourcePos::new(6));
}
#[test]
fn line_start() {
let text = "12\n3\r\n4\n5";
for i in 0..10 {
run_with_text_info(SourceTextInfo::new_with_pos(SourcePos::new(i), text.to_string().into()), i);
}
fn run_with_text_info(text_info: SourceTextInfo, i: usize) {
assert_line_start(&text_info, 0, i);
assert_line_start(&text_info, 1, 3 + i);
assert_line_start(&text_info, 2, 6 + i);
assert_line_start(&text_info, 3, 8 + i);
}
}
fn assert_line_start(text_info: &SourceTextInfo, line_index: usize, line_end: usize) {
assert_eq!(text_info.line_start(line_index), SourcePos::new(line_end));
}
#[test]
#[should_panic(expected = "The specified line index 1 was greater or equal to the number of lines of 1.")]
fn line_start_equal_number_lines() {
let info = SourceTextInfo::new_with_pos(SourcePos::new(1), "test".to_string().into());
info.line_start(1);
}
#[test]
fn line_end() {
let text = "12\n3\r\n4\n5";
for i in 0..10 {
run_with_text_info(SourceTextInfo::new_with_pos(SourcePos::new(i), text.to_string().into()), i);
}
fn run_with_text_info(text_info: SourceTextInfo, i: usize) {
assert_line_end(&text_info, 0, 2 + i);
assert_line_end(&text_info, 1, 4 + i);
assert_line_end(&text_info, 2, 7 + i);
assert_line_end(&text_info, 3, 9 + i);
}
}
fn assert_line_end(text_info: &SourceTextInfo, line_index: usize, line_end: usize) {
assert_eq!(text_info.line_end(line_index), SourcePos::new(line_end));
}
#[test]
#[should_panic(expected = "The specified line index 1 was greater or equal to the number of lines of 1.")]
fn line_end_equal_number_lines() {
let info = SourceTextInfo::new_with_pos(SourcePos::new(1), "test".to_string().into());
info.line_end(1);
}
}