Skip to main content

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}