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}