1use std::cmp::Ordering;
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize, JsonSchema)]
10pub struct Position {
11 pub line: u32,
12 pub column: u32,
13}
14
15impl Position {
16 pub const ZERO: Self = Self { line: 0, column: 0 };
17
18 #[must_use]
19 pub const fn new(line: u32, column: u32) -> Self {
20 Self { line, column }
21 }
22
23 #[must_use]
24 pub const fn line(line: u32) -> Self {
25 Self { line, column: 0 }
26 }
27
28 #[must_use]
30 pub const fn shift_right(self, n: u32) -> Self {
31 Self {
32 line: self.line,
33 column: self.column.saturating_add(n),
34 }
35 }
36
37 #[must_use]
39 pub const fn shift_left(self, n: u32) -> Self {
40 Self {
41 line: self.line,
42 column: self.column.saturating_sub(n),
43 }
44 }
45}
46
47impl PartialOrd for Position {
48 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
49 Some(self.cmp(other))
50 }
51}
52
53impl Ord for Position {
54 fn cmp(&self, other: &Self) -> Ordering {
55 self.line
56 .cmp(&other.line)
57 .then(self.column.cmp(&other.column))
58 }
59}
60
61impl std::fmt::Display for Position {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 write!(f, "{}:{}", self.line + 1, self.column + 1)
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn ord_by_line_then_column() {
73 assert!(Position::new(0, 5) < Position::new(1, 0));
74 assert!(Position::new(3, 2) < Position::new(3, 4));
75 assert_eq!(
76 Position::new(2, 0).cmp(&Position::new(2, 0)),
77 Ordering::Equal
78 );
79 }
80
81 #[test]
82 fn shift_saturates() {
83 assert_eq!(Position::new(0, 0).shift_left(5), Position::new(0, 0));
84 assert_eq!(
85 Position::new(0, u32::MAX).shift_right(5),
86 Position::new(0, u32::MAX)
87 );
88 }
89
90 #[test]
91 fn display_is_one_indexed() {
92 assert_eq!(Position::new(0, 0).to_string(), "1:1");
93 assert_eq!(Position::new(9, 3).to_string(), "10:4");
94 }
95}