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#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
31pub struct LineCol {
32    /// The 1-based line number.
33    pub line: u32,
34    /// The 1-based column, counted in Unicode scalar values (`char`s).
35    pub col: u32,
36}
37
38impl LineCol {
39    /// Constructs a coordinate from a 1-based line and column.
40    ///
41    /// This is a plain constructor; it does not validate that the coordinate
42    /// exists in any particular source. Resolving a coordinate back to a byte
43    /// offset is [`LineIndex::offset`](crate::LineIndex::offset), which reports a
44    /// position outside the source as `None`.
45    ///
46    /// # Examples
47    ///
48    /// ```
49    /// use span_lang::LineCol;
50    ///
51    /// const ORIGIN: LineCol = LineCol::new(1, 1);
52    /// assert_eq!((ORIGIN.line, ORIGIN.col), (1, 1));
53    /// ```
54    #[inline]
55    #[must_use]
56    pub const fn new(line: u32, col: u32) -> Self {
57        Self { line, col }
58    }
59}
60
61impl fmt::Display for LineCol {
62    /// Formats as `line:col`, the convention editors and compilers use.
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        write!(f, "{}:{}", self.line, self.col)
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn test_line_col_orders_by_line_then_column() {
74        assert!(LineCol::new(1, 9) < LineCol::new(2, 1));
75        assert!(LineCol::new(2, 1) < LineCol::new(2, 2));
76    }
77
78    #[test]
79    fn test_line_col_display_uses_colon() {
80        extern crate alloc;
81        use alloc::string::ToString;
82        assert_eq!(LineCol::new(10, 3).to_string(), "10:3");
83    }
84}