summaryrefslogtreecommitdiff
path: root/stag/src/text_range.rs
blob: 5b1ec67e5d1ad9a40d2388881ea606387f02081d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use std::cmp::{Ord, Ordering};

use serde::{Deserialize, Serialize};

/// A singular position in a text document
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Point {
    /// The byte index
    pub byte: usize,

    /// 0-indexed line number
    pub line: usize,

    /// Position within the line
    pub column: usize,
}

impl PartialOrd for Point {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Point {
    fn cmp(&self, other: &Self) -> Ordering {
        self.byte.cmp(&other.byte)
    }
}

impl Point {
    pub fn new(byte: usize, line: usize, column: usize) -> Self {
        Self { byte, line, column }
    }

    pub fn from_byte(byte: usize, line_end_indices: &[u32]) -> Self {
        let line = line_end_indices
            .iter()
            .position(|&line_end_byte| (line_end_byte as usize) > byte)
            .unwrap_or(0);

        let column = line
            .checked_sub(1)
            .and_then(|idx| line_end_indices.get(idx))
            .map(|&prev_line_end| byte.saturating_sub(prev_line_end as usize))
            .unwrap_or(byte);

        Self::new(byte, line, column)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TextRange {
    pub start: Point,
    pub end: Point,
}

impl PartialOrd for TextRange {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for TextRange {
    fn cmp(&self, other: &Self) -> Ordering {
        let compare_start_byte = self.start.byte.cmp(&other.start.byte);
        let compare_size = self.size().cmp(&other.size());

        compare_start_byte.then(compare_size)
    }
}

impl TextRange {
    pub fn new(start: Point, end: Point) -> Self {
        assert!(start <= end);
        Self { start, end }
    }

    pub fn contains(&self, other: &TextRange) -> bool {
        // (self.start ... [other.start ... other.end] ... self.end)
        self.start <= other.start && other.end <= self.end
    }

    #[allow(unused)]
    pub fn contains_strict(&self, other: TextRange) -> bool {
        // (self.start ... (other.start ... other.end) ... self.end)
        self.start < other.start && other.end <= self.end
    }

    pub fn size(&self) -> usize {
        self.end.byte.saturating_sub(self.start.byte)
    }

    pub fn from_byte_range(range: std::ops::Range<usize>, line_end_indices: &[u32]) -> Self {
        let start = Point::from_byte(range.start, line_end_indices);
        let end = Point::from_byte(range.end, line_end_indices);
        Self::new(start, end)
    }
}

impl From<tree_sitter::Range> for TextRange {
    fn from(r: tree_sitter::Range) -> Self {
        Self {
            start: Point {
                byte: r.start_byte,
                line: r.start_point.row,
                column: r.start_point.column,
            },
            end: Point {
                byte: r.end_byte,
                line: r.end_point.row,
                column: r.end_point.column,
            },
        }
    }
}

impl From<TextRange> for std::ops::Range<usize> {
    fn from(r: TextRange) -> std::ops::Range<usize> {
        r.start.byte..r.end.byte
    }
}