grit_util/
position.rs

1use serde::{Deserialize, Serialize};
2use std::fmt::Display;
3
4#[cfg_attr(feature = "napi", napi_derive::napi(object))]
5#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
6#[serde(rename_all = "camelCase")]
7
8pub struct Position {
9    /// 1-based line number in the source file.
10    pub line: u32,
11
12    /// 1-based column number in the source file.
13    pub column: u32,
14}
15
16impl Position {
17    pub fn new(line: u32, column: u32) -> Self {
18        Self { line, column }
19    }
20
21    /// Returns the first position in any source string.
22    pub fn first() -> Self {
23        Self::new(1, 1)
24    }
25
26    /// Returns the last position in the given `source`.
27    pub fn last(source: &str) -> Self {
28        Self::from_byte_index(source, source.len())
29    }
30
31    /// Adds another position to this one.
32    pub fn add(&mut self, other: Position) {
33        self.line += other.line - 1;
34        self.column += other.column - 1;
35    }
36
37    /// Creates a position for the given `byte_index` in the given `source`.
38    pub fn from_byte_index(source: &str, byte_index: usize) -> Self {
39        Self::from_relative_byte_index(Self::first(), 0, source, byte_index)
40    }
41
42    /// Create a position for the given `byte_index` in the given `source`,
43    /// counting from the given other position. This avoids double work in case
44    /// one position lies after another.
45    pub fn from_relative_byte_index(
46        mut prev: Self,
47        prev_byte_index: usize,
48        source: &str,
49        byte_index: usize,
50    ) -> Self {
51        for c in source[prev_byte_index..byte_index].chars() {
52            if c == '\n' {
53                prev.line += 1;
54                prev.column = 1;
55            } else {
56                prev.column += c.len_utf8() as u32;
57            }
58        }
59        prev
60    }
61
62    /// Returns the byte index for this `Position` within the given `source`.
63    pub fn byte_index(&self, source: &str) -> usize {
64        let line_start_index: usize = source
65            .split('\n')
66            .take((self.line as usize).saturating_sub(1))
67            .map(|line| line.len() + 1)
68            .sum();
69        line_start_index + (self.column as usize) - 1
70    }
71}
72
73impl Display for Position {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        write!(f, "{}:{}", self.line, self.column)
76    }
77}