1use std::marker::PhantomData;
2
3use super::{ByteOffset, CharOffset, OffsetPosition, PositionKind, StringPosition, Utf16Offset};
4
5#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
7#[derive(Clone, Copy, PartialEq, Eq, Default, Hash)]
8pub struct LineCol<Off: OffsetPosition = ByteOffset>
9where
10 Self: StringPosition,
11{
12 pub line: u32,
13 pub col: u32,
14 #[cfg_attr(feature = "serde", serde(skip))]
15 pub(crate) __phantom: PhantomData<Off>,
16}
17impl<Off: OffsetPosition> std::fmt::Display for LineCol<Off>
18where
19 Self: StringPosition,
20{
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 write!(f, "({},{})", self.line, self.col)
23 }
24}
25impl<Off: OffsetPosition> std::fmt::Debug for LineCol<Off>
26where
27 Self: StringPosition,
28{
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 f.debug_tuple("LineCol")
31 .field(&self.line)
32 .field(&self.col)
33 .finish()
34 }
35}
36impl<Off: OffsetPosition> LineCol<Off>
37where
38 Self: StringPosition,
39{
40 #[inline]
41 #[must_use]
42 pub const fn new(line: u32, col: u32) -> Self {
43 Self {
44 line,
45 col,
46 __phantom: PhantomData,
47 }
48 }
49}
50impl<Off: OffsetPosition> crate::__private::Sealed for LineCol<Off> where Self: StringPosition {}
51impl<Off: OffsetPosition> std::ops::Add for LineCol<Off>
52where
53 Self: StringPosition,
54{
55 type Output = Self;
56 fn add(self, rhs: Self) -> Self::Output {
57 Self {
58 line: self.line + rhs.line,
59 col: if rhs.line == 0 {
60 self.col + rhs.col
61 } else {
62 rhs.col
63 },
64 __phantom: PhantomData,
65 }
66 }
67}
68impl<Off: OffsetPosition> std::ops::AddAssign for LineCol<Off>
69where
70 Self: StringPosition,
71{
72 fn add_assign(&mut self, rhs: Self) {
73 self.line += rhs.line;
74 if rhs.line == 0 {
75 self.col += rhs.col;
76 } else {
77 self.col = rhs.col;
78 }
79 }
80}
81impl<Off: OffsetPosition> std::ops::Sub for LineCol<Off>
82where
83 Self: StringPosition,
84{
85 type Output = Self;
86 fn sub(self, rhs: Self) -> Self::Output {
87 if rhs.line == self.line {
88 Self {
89 line: 0,
90 col: self.col - rhs.col,
91 __phantom: PhantomData,
92 }
93 } else {
94 Self {
95 line: self.line.saturating_sub(rhs.line),
96 col: self.col,
97 __phantom: PhantomData,
98 }
99 }
100 }
101}
102impl<Off: OffsetPosition> std::ops::SubAssign for LineCol<Off>
103where
104 Self: StringPosition,
105{
106 fn sub_assign(&mut self, rhs: Self) {
107 if rhs.line == self.line {
108 self.line = 0;
109 } else {
110 self.line = self.line.saturating_sub(rhs.line);
111 self.col -= rhs.col;
112 }
113 }
114}
115impl<Off: OffsetPosition> Ord for LineCol<Off>
116where
117 Self: StringPosition,
118{
119 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
120 match self.line.cmp(&other.line) {
121 std::cmp::Ordering::Equal => self.col.cmp(&other.col),
122 o => o,
123 }
124 }
125}
126
127#[allow(clippy::inline_always)]
128impl<Off: OffsetPosition> PartialOrd for LineCol<Off>
129where
130 Self: StringPosition,
131{
132 #[inline(always)]
133 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
134 Some(self.cmp(other))
135 }
136}
137
138impl StringPosition for LineCol<ByteOffset> {
139 const KIND: PositionKind = PositionKind::LineColByte;
140
141 #[inline]
142 #[allow(clippy::cast_possible_truncation)]
143 fn inc_by(&mut self, c: char) {
144 self.col += c.len_utf8() as u32;
145 }
146 fn inc_newline(&mut self, _: bool) {
147 self.col = 0;
148 self.line += 1;
149 }
150
151 #[allow(clippy::cast_possible_truncation)]
152 fn inc_offset_by(&mut self, text: &str) {
153 self.col += text.len() as u32;
154 }
155
156 #[inline]
157 #[allow(clippy::cast_possible_truncation)]
158 fn len(s: &str) -> Self {
159 let (line, off) = super::convert::count_lines(s);
160 Self {
161 line: line as _,
162 col: (s.len() - (off as usize)) as _,
163 __phantom: PhantomData,
164 }
165 }
166
167 #[inline(always)]
168 fn from_other<P: StringPosition>(p: P, source: &str) -> Self {
169 use PositionKind as K;
170 unsafe {
171 match P::KIND {
172 K::None => Self::default(),
173 K::Byte => {
174 super::convert::byte_to_linecol_byte(std::mem::transmute_copy(&p), source)
175 }
176 K::Char => {
177 super::convert::char_to_linecol_byte(std::mem::transmute_copy(&p), source)
178 }
179 K::Utf16 => {
180 super::convert::utf16_to_linecol_byte(std::mem::transmute_copy(&p), source)
181 }
182 K::LineColByte => std::mem::transmute_copy(&p),
183 K::LineColChar => super::convert::linecol_to_linecol::<CharOffset, _>(
184 std::mem::transmute_copy(&p),
185 source,
186 ),
187 K::LineColUtf16 => super::convert::linecol_to_linecol::<Utf16Offset, _>(
188 std::mem::transmute_copy(&p),
189 source,
190 ),
191 }
192 }
193 }
194}
195
196impl StringPosition for LineCol<CharOffset> {
197 const KIND: PositionKind = PositionKind::LineColChar;
198
199 #[inline]
200 fn inc_by(&mut self, _: char) {
201 self.col += 1;
202 }
203 fn inc_newline(&mut self, _: bool) {
204 self.col = 0;
205 self.line += 1;
206 }
207
208 #[allow(clippy::cast_possible_truncation)]
209 fn inc_offset_by(&mut self, text: &str) {
210 self.col += text.chars().count() as u32;
211 }
212
213 #[inline]
214 #[allow(clippy::cast_possible_truncation)]
215 fn len(s: &str) -> Self {
216 let (line, off) = super::convert::count_lines(s);
217 Self {
218 line: line as _,
219 col: s[off as usize..].chars().count() as _,
220 __phantom: PhantomData,
221 }
222 }
223
224 #[inline(always)]
225 fn from_other<P: StringPosition>(p: P, source: &str) -> Self {
226 use PositionKind as K;
227 unsafe {
228 match P::KIND {
229 K::None => Self::default(),
230 K::Byte => {
231 super::convert::byte_to_linecol_char(std::mem::transmute_copy(&p), source)
232 }
233 K::Char => {
234 super::convert::char_to_linecol_char(std::mem::transmute_copy(&p), source)
235 }
236 K::Utf16 => {
237 super::convert::utf16_to_linecol_char(std::mem::transmute_copy(&p), source)
238 }
239 K::LineColChar => std::mem::transmute_copy(&p),
240 K::LineColByte => super::convert::linecol_to_linecol::<ByteOffset, _>(
241 std::mem::transmute_copy(&p),
242 source,
243 ),
244 K::LineColUtf16 => super::convert::linecol_to_linecol::<Utf16Offset, _>(
245 std::mem::transmute_copy(&p),
246 source,
247 ),
248 }
249 }
250 }
251}
252
253impl StringPosition for LineCol<Utf16Offset> {
254 const KIND: PositionKind = PositionKind::LineColUtf16;
255
256 #[inline]
257 #[allow(clippy::cast_possible_truncation)]
258 fn inc_by(&mut self, c: char) {
259 self.col += c.len_utf16() as u32;
260 }
261 fn inc_newline(&mut self, _: bool) {
262 self.col = 0;
263 self.line += 1;
264 }
265
266 #[allow(clippy::cast_possible_truncation)]
267 fn inc_offset_by(&mut self, text: &str) {
268 self.col += text.chars().map(|c| c.len_utf16() as u32).sum::<u32>();
269 }
270
271 #[inline]
272 #[allow(clippy::cast_possible_truncation)]
273 fn len(s: &str) -> Self {
274 let (line, off) = super::convert::count_lines(s);
275 Self {
276 line: line as _,
277 col: s[off as usize..]
278 .chars()
279 .map(|c| char::len_utf16(c) as u32)
280 .sum::<u32>(),
281 __phantom: PhantomData,
282 }
283 }
284
285 #[inline(always)]
286 fn from_other<P: StringPosition>(p: P, source: &str) -> Self {
287 use PositionKind as K;
288 unsafe {
289 match P::KIND {
290 K::None => Self::default(),
291 K::Byte => {
292 super::convert::byte_to_linecol_utf16(std::mem::transmute_copy(&p), source)
293 }
294 K::Char => {
295 super::convert::char_to_linecol_utf16(std::mem::transmute_copy(&p), source)
296 }
297 K::Utf16 => {
298 super::convert::utf16_to_linecol_utf16(std::mem::transmute_copy(&p), source)
299 }
300 K::LineColUtf16 => std::mem::transmute_copy(&p),
301 K::LineColByte => super::convert::linecol_to_linecol::<ByteOffset, _>(
302 std::mem::transmute_copy(&p),
303 source,
304 ),
305 K::LineColChar => super::convert::linecol_to_linecol::<CharOffset, _>(
306 std::mem::transmute_copy(&p),
307 source,
308 ),
309 }
310 }
311 }
312}