span_lang/line_col.rs
1//! Resolved line/column coordinates.
2
3use core::fmt;
4
5/// A resolved human coordinate: a 1-based line and a 1-based column.
6///
7/// The column counts **Unicode scalar values** (Rust `char`s), not bytes, not
8/// UTF-16 code units, and not grapheme clusters. A column therefore never lands
9/// inside a multi-byte UTF-8 sequence: the third `char` of a line is always
10/// column 3, whether the preceding characters were one byte each or four.
11///
12/// Both fields are 1-based because that is what editors, compilers, and language
13/// servers display — line 1, column 1 is the first character of the source.
14///
15/// `LineCol` is produced by [`LineIndex::line_col`](crate::LineIndex::line_col)
16/// and consumed by [`LineIndex::offset`](crate::LineIndex::offset); the two are
17/// inverses for every valid byte position.
18///
19/// # Examples
20///
21/// ```
22/// use span_lang::LineCol;
23///
24/// let lc = LineCol::new(2, 5);
25/// assert_eq!(lc.line, 2);
26/// assert_eq!(lc.col, 5);
27/// assert_eq!(lc.to_string(), "2:5");
28/// ```
29#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
30pub struct LineCol {
31 /// The 1-based line number.
32 pub line: u32,
33 /// The 1-based column, counted in Unicode scalar values (`char`s).
34 pub col: u32,
35}
36
37impl LineCol {
38 /// Constructs a coordinate from a 1-based line and column.
39 ///
40 /// This is a plain constructor; it does not validate that the coordinate
41 /// exists in any particular source. Resolving a coordinate back to a byte
42 /// offset is [`LineIndex::offset`](crate::LineIndex::offset), which reports a
43 /// position outside the source as `None`.
44 ///
45 /// # Examples
46 ///
47 /// ```
48 /// use span_lang::LineCol;
49 ///
50 /// const ORIGIN: LineCol = LineCol::new(1, 1);
51 /// assert_eq!((ORIGIN.line, ORIGIN.col), (1, 1));
52 /// ```
53 #[inline]
54 #[must_use]
55 pub const fn new(line: u32, col: u32) -> Self {
56 Self { line, col }
57 }
58}
59
60impl fmt::Display for LineCol {
61 /// Formats as `line:col`, the convention editors and compilers use.
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 write!(f, "{}:{}", self.line, self.col)
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn test_line_col_orders_by_line_then_column() {
73 assert!(LineCol::new(1, 9) < LineCol::new(2, 1));
74 assert!(LineCol::new(2, 1) < LineCol::new(2, 2));
75 }
76
77 #[test]
78 fn test_line_col_display_uses_colon() {
79 extern crate alloc;
80 use alloc::string::ToString;
81 assert_eq!(LineCol::new(10, 3).to_string(), "10:3");
82 }
83}