Skip to main content

lex_extension/wire/
range.rs

1//! Source-range types.
2//!
3//! The wire format encodes positions as `[line, column]` arrays — two-element
4//! tuples — to keep payload size small. Both values are 0-indexed; ranges
5//! are start-inclusive and end-exclusive.
6//!
7//! # Column semantics
8//!
9//! `column` is a **0-indexed UTF-8 byte offset** within the line, matching
10//! lex-core's internal source representation (`byte_offset - line_start`).
11//! It is *not* an LSP-style UTF-16 code unit count and *not* a Unicode
12//! scalar count; multi-byte characters occupy more than one column. The
13//! `lex-lsp` server converts to UTF-16 code units at the LSP protocol
14//! boundary; that conversion is not the wire format's concern.
15
16use serde::{Deserialize, Serialize};
17
18/// A `(line, column)` position. Wire form: `[line, column]`.
19///
20/// `column` is a 0-indexed UTF-8 byte offset within the line — see the
21/// module-level docs.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
23pub struct Position(pub u32, pub u32);
24
25impl Position {
26    /// Construct a position from a line and column (0-indexed UTF-8 byte
27    /// offset).
28    pub fn new(line: u32, column: u32) -> Self {
29        Self(line, column)
30    }
31
32    /// 0-indexed line.
33    pub fn line(&self) -> u32 {
34        self.0
35    }
36
37    /// 0-indexed UTF-8 byte offset within the line.
38    pub fn column(&self) -> u32 {
39        self.1
40    }
41}
42
43/// A half-open source range. `start` inclusive, `end` exclusive.
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
45pub struct Range {
46    pub start: Position,
47    pub end: Position,
48}
49
50impl Range {
51    pub fn new(start: Position, end: Position) -> Self {
52        Self { start, end }
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn position_serialises_as_array() {
62        let p = Position::new(12, 4);
63        let s = serde_json::to_string(&p).unwrap();
64        assert_eq!(s, "[12,4]");
65    }
66
67    #[test]
68    fn position_deserialises_from_array() {
69        let p: Position = serde_json::from_str("[12,4]").unwrap();
70        assert_eq!(p, Position::new(12, 4));
71    }
72
73    #[test]
74    fn range_round_trips() {
75        let r = Range::new(Position::new(1, 2), Position::new(3, 4));
76        let s = serde_json::to_string(&r).unwrap();
77        assert_eq!(s, r#"{"start":[1,2],"end":[3,4]}"#);
78        let back: Range = serde_json::from_str(&s).unwrap();
79        assert_eq!(back, r);
80    }
81}