1use camino::Utf8Path;
2
3use crate::error::LineColumn;
4use crate::primitives::{SourceId, Span};
5
6#[derive(Debug, Clone, Copy)]
24pub struct SourceText<'src> {
25 pub id: SourceId,
27 pub text: &'src str,
29 pub filename: Option<&'src Utf8Path>,
31}
32
33impl<'src> SourceText<'src> {
34 pub fn new(id: SourceId, text: &'src str, filename: Option<&'src Utf8Path>) -> Self {
36 Self { id, text, filename }
37 }
38
39 pub fn len(self) -> usize {
41 self.text.len()
42 }
43
44 pub fn is_empty(self) -> bool {
46 self.text.is_empty()
47 }
48
49 pub fn span_all(self) -> Span {
51 Span::from_offsets(0, self.text.len()).unwrap_or(Span::EMPTY)
52 }
53
54 pub fn slice(self, span: Span) -> Option<&'src str> {
56 self.text.get(span.start.as_usize()..span.end.as_usize())
57 }
58
59 pub fn utf16_offset(self, offset: usize) -> usize {
64 let bounded = offset.min(self.text.len());
65 self.text[..bounded]
66 .chars()
67 .filter(|&ch| ch != '\r')
68 .map(char::len_utf16)
69 .sum()
70 }
71
72 pub fn line_column_at_offset(self, offset: usize) -> (usize, usize) {
74 let mut line = 1usize;
75 let mut column = 0usize;
76 let limit = offset.min(self.text.len());
77 for ch in self.text[..limit].chars() {
78 match ch {
79 '\n' => {
80 line += 1;
81 column = 0;
82 }
83 '\r' => {}
84 _ => {
85 column += ch.len_utf16();
86 }
87 }
88 }
89 (line, column)
90 }
91
92 pub fn location_at_offset(self, offset: usize) -> LineColumn {
94 let (line, column) = self.line_column_at_offset(offset);
95 LineColumn {
96 line,
97 column,
98 character: self.utf16_offset(offset),
99 }
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use camino::Utf8Path;
106
107 use super::{SourceId, SourceText};
108
109 #[test]
110 fn source_text_reports_utf16_locations() {
111 let source = SourceText::new(
112 SourceId::new(1),
113 "a\nšb",
114 Some(Utf8Path::new("input.svelte")),
115 );
116
117 assert_eq!(source.utf16_offset(0), 0);
118 assert_eq!(source.utf16_offset(2), 2);
119 assert_eq!(source.utf16_offset("a\nš".len()), 4);
120
121 let location = source.location_at_offset("a\nš".len());
122 assert_eq!(location.line, 2);
123 assert_eq!(location.column, 2);
124 assert_eq!(location.character, 4);
125 }
126
127 #[test]
128 fn source_text_normalizes_crlf_offsets() {
129 let source = SourceText::new(
130 SourceId::new(2),
131 "a\r\nb\r\nšc",
132 Some(Utf8Path::new("input.svelte")),
133 );
134
135 let offset = "a\r\nb\r\nš".len();
136 assert_eq!(source.utf16_offset(offset), 6);
137
138 let location = source.location_at_offset(offset);
139 assert_eq!(location.line, 3);
140 assert_eq!(location.column, 2);
141 assert_eq!(location.character, 6);
142 }
143}