Skip to main content

perl_position_tracking/
wire.rs

1//! LSP wire types for Position and Range.
2use crate::{offset_to_utf16_line_col, utf16_line_col_to_offset};
3use serde::{Deserialize, Serialize};
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
5pub struct WirePosition {
6    pub line: u32,
7    pub character: u32,
8}
9impl WirePosition {
10    pub fn new(line: u32, character: u32) -> Self {
11        Self { line, character }
12    }
13    pub fn from_byte_offset(source: &str, byte_offset: usize) -> Self {
14        let (line, character) = offset_to_utf16_line_col(source, byte_offset);
15        Self { line, character }
16    }
17    pub fn to_byte_offset(&self, source: &str) -> usize {
18        utf16_line_col_to_offset(source, self.line, self.character)
19    }
20}
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
22pub struct WireRange {
23    pub start: WirePosition,
24    pub end: WirePosition,
25}
26impl WireRange {
27    pub fn new(start: WirePosition, end: WirePosition) -> Self {
28        Self { start, end }
29    }
30    pub fn from_byte_offsets(source: &str, start_byte: usize, end_byte: usize) -> Self {
31        Self {
32            start: WirePosition::from_byte_offset(source, start_byte),
33            end: WirePosition::from_byte_offset(source, end_byte),
34        }
35    }
36    pub fn empty(pos: WirePosition) -> Self {
37        Self { start: pos, end: pos }
38    }
39    pub fn whole_document(source: &str) -> Self {
40        Self {
41            start: WirePosition::new(0, 0),
42            end: WirePosition::from_byte_offset(source, source.len()),
43        }
44    }
45}
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
47pub struct WireLocation {
48    pub uri: String,
49    pub range: WireRange,
50}
51impl WireLocation {
52    pub fn new(uri: String, range: WireRange) -> Self {
53        Self { uri, range }
54    }
55}
56#[cfg(feature = "lsp-compat")]
57impl From<WirePosition> for lsp_types::Position {
58    fn from(p: WirePosition) -> Self {
59        Self { line: p.line, character: p.character }
60    }
61}
62#[cfg(feature = "lsp-compat")]
63impl From<lsp_types::Position> for WirePosition {
64    fn from(p: lsp_types::Position) -> Self {
65        Self { line: p.line, character: p.character }
66    }
67}
68#[cfg(feature = "lsp-compat")]
69impl From<WireRange> for lsp_types::Range {
70    fn from(r: WireRange) -> Self {
71        Self { start: r.start.into(), end: r.end.into() }
72    }
73}
74#[cfg(feature = "lsp-compat")]
75impl From<lsp_types::Range> for WireRange {
76    fn from(r: lsp_types::Range) -> Self {
77        Self { start: r.start.into(), end: r.end.into() }
78    }
79}
80#[cfg(feature = "lsp-compat")]
81fn fallback_lsp_uri() -> lsp_types::Uri {
82    for candidate in ["file:///unknown", "file:///", "about:blank", "urn:perl-lsp:unknown"] {
83        if let Ok(uri) = candidate.parse::<lsp_types::Uri>() {
84            return uri;
85        }
86    }
87
88    // Last-resort fallback that avoids panicking if URI parser behavior changes unexpectedly.
89    let mut suffix = 0usize;
90    loop {
91        let candidate = format!("http://localhost/{suffix}");
92        if let Ok(uri) = candidate.parse::<lsp_types::Uri>() {
93            return uri;
94        }
95        suffix = suffix.saturating_add(1);
96    }
97}
98
99#[cfg(feature = "lsp-compat")]
100impl From<WireLocation> for lsp_types::Location {
101    fn from(l: WireLocation) -> Self {
102        let uri = match l.uri.parse::<lsp_types::Uri>() {
103            Ok(u) => u,
104            Err(_) => fallback_lsp_uri(),
105        };
106        Self { uri, range: l.range.into() }
107    }
108}