• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Utilities for translating from codespan types into Language Server Protocol (LSP) types
2 
3 use std::ops::Range;
4 
5 use codespan_reporting::files::{Error, Files};
6 
7 // WARNING: Be extremely careful when adding new imports here, as it could break
8 // the compatible version range that we claim in our `Cargo.toml`. This could
9 // potentially break down-stream builds on a `cargo update`. This is an
10 // absolute no-no, breaking much of what we enjoy about Cargo!
11 use lsp_types::{Position as LspPosition, Range as LspRange};
12 
location_to_position( line_str: &str, line: usize, column: usize, byte_index: usize, ) -> Result<LspPosition, Error>13 fn location_to_position(
14     line_str: &str,
15     line: usize,
16     column: usize,
17     byte_index: usize,
18 ) -> Result<LspPosition, Error> {
19     if column > line_str.len() {
20         let max = line_str.len();
21         let given = column;
22 
23         Err(Error::ColumnTooLarge { given, max })
24     } else if !line_str.is_char_boundary(column) {
25         let given = byte_index;
26 
27         Err(Error::InvalidCharBoundary { given })
28     } else {
29         let line_utf16 = line_str[..column].encode_utf16();
30         let character = line_utf16.count() as u32;
31         let line = line as u32;
32 
33         Ok(LspPosition { line, character })
34     }
35 }
36 
byte_index_to_position<'a, F>( files: &'a F, file_id: F::FileId, byte_index: usize, ) -> Result<LspPosition, Error> where F: Files<'a> + ?Sized,37 pub fn byte_index_to_position<'a, F>(
38     files: &'a F,
39     file_id: F::FileId,
40     byte_index: usize,
41 ) -> Result<LspPosition, Error>
42 where
43     F: Files<'a> + ?Sized,
44 {
45     let source = files.source(file_id)?;
46     let source = source.as_ref();
47 
48     let line_index = files.line_index(file_id, byte_index)?;
49     let line_span = files.line_range(file_id, line_index).unwrap();
50 
51     let line_str = source
52         .get(line_span.clone())
53         .ok_or_else(|| Error::IndexTooLarge {
54             given: if line_span.start >= source.len() {
55                 line_span.start
56             } else {
57                 line_span.end
58             },
59             max: source.len() - 1,
60         })?;
61     let column = byte_index - line_span.start;
62 
63     location_to_position(line_str, line_index, column, byte_index)
64 }
65 
byte_span_to_range<'a, F>( files: &'a F, file_id: F::FileId, span: Range<usize>, ) -> Result<LspRange, Error> where F: Files<'a> + ?Sized,66 pub fn byte_span_to_range<'a, F>(
67     files: &'a F,
68     file_id: F::FileId,
69     span: Range<usize>,
70 ) -> Result<LspRange, Error>
71 where
72     F: Files<'a> + ?Sized,
73 {
74     Ok(LspRange {
75         start: byte_index_to_position(files, file_id, span.start)?,
76         end: byte_index_to_position(files, file_id, span.end)?,
77     })
78 }
79 
character_to_line_offset(line: &str, character: u32) -> Result<usize, Error>80 fn character_to_line_offset(line: &str, character: u32) -> Result<usize, Error> {
81     let line_len = line.len();
82     let mut character_offset = 0;
83 
84     let mut chars = line.chars();
85     while let Some(ch) = chars.next() {
86         if character_offset == character {
87             let chars_off = chars.as_str().len();
88             let ch_off = ch.len_utf8();
89 
90             return Ok(line_len - chars_off - ch_off);
91         }
92 
93         character_offset += ch.len_utf16() as u32;
94     }
95 
96     // Handle positions after the last character on the line
97     if character_offset == character {
98         Ok(line_len)
99     } else {
100         Err(Error::ColumnTooLarge {
101             given: character_offset as usize,
102             max: line.len(),
103         })
104     }
105 }
106 
position_to_byte_index<'a, F>( files: &'a F, file_id: F::FileId, position: &LspPosition, ) -> Result<usize, Error> where F: Files<'a> + ?Sized,107 pub fn position_to_byte_index<'a, F>(
108     files: &'a F,
109     file_id: F::FileId,
110     position: &LspPosition,
111 ) -> Result<usize, Error>
112 where
113     F: Files<'a> + ?Sized,
114 {
115     let source = files.source(file_id)?;
116     let source = source.as_ref();
117 
118     let line_span = files.line_range(file_id, position.line as usize).unwrap();
119     let line_str = source.get(line_span.clone()).unwrap();
120 
121     let byte_offset = character_to_line_offset(line_str, position.character)?;
122 
123     Ok(line_span.start + byte_offset)
124 }
125 
range_to_byte_span<'a, F>( files: &'a F, file_id: F::FileId, range: &LspRange, ) -> Result<Range<usize>, Error> where F: Files<'a> + ?Sized,126 pub fn range_to_byte_span<'a, F>(
127     files: &'a F,
128     file_id: F::FileId,
129     range: &LspRange,
130 ) -> Result<Range<usize>, Error>
131 where
132     F: Files<'a> + ?Sized,
133 {
134     Ok(position_to_byte_index(files, file_id, &range.start)?
135         ..position_to_byte_index(files, file_id, &range.end)?)
136 }
137 
138 #[cfg(test)]
139 mod tests {
140     use codespan_reporting::files::{Location, SimpleFiles};
141 
142     use super::*;
143 
144     #[test]
position()145     fn position() {
146         let text = r#"
147 let test = 2
148 let test1 = ""
149 test
150 "#;
151         let mut files = SimpleFiles::new();
152         let file_id = files.add("test", text);
153         let pos = position_to_byte_index(
154             &files,
155             file_id,
156             &LspPosition {
157                 line: 3,
158                 character: 2,
159             },
160         )
161         .unwrap();
162         assert_eq!(
163             Location {
164                 // One-based
165                 line_number: 3 + 1,
166                 column_number: 2 + 1,
167             },
168             files.location(file_id, pos).unwrap()
169         );
170     }
171 
172     // The protocol specifies that each `character` in position is a UTF-16 character.
173     // This means that `å` and `ä` here counts as 1 while `��` counts as 2.
174     const UNICODE: &str = "åä t��b";
175 
176     #[test]
unicode_get_byte_index()177     fn unicode_get_byte_index() {
178         let mut files = SimpleFiles::new();
179         let file_id = files.add("unicode", UNICODE);
180 
181         let result = position_to_byte_index(
182             &files,
183             file_id,
184             &LspPosition {
185                 line: 0,
186                 character: 3,
187             },
188         );
189         assert_eq!(result.unwrap(), 5);
190 
191         let result = position_to_byte_index(
192             &files,
193             file_id,
194             &LspPosition {
195                 line: 0,
196                 character: 6,
197             },
198         );
199         assert_eq!(result.unwrap(), 10);
200     }
201 
202     #[test]
unicode_get_position()203     fn unicode_get_position() {
204         let mut files = SimpleFiles::new();
205         let file_id = files.add("unicode", UNICODE.to_string());
206         let file_id2 = files.add("unicode newline", "\n".to_string() + UNICODE);
207 
208         let result = byte_index_to_position(&files, file_id, 5);
209         assert_eq!(
210             result.unwrap(),
211             LspPosition {
212                 line: 0,
213                 character: 3,
214             }
215         );
216 
217         let result = byte_index_to_position(&files, file_id, 10);
218         assert_eq!(
219             result.unwrap(),
220             LspPosition {
221                 line: 0,
222                 character: 6,
223             }
224         );
225 
226         let result = byte_index_to_position(&files, file_id2, 11);
227         assert_eq!(
228             result.unwrap(),
229             LspPosition {
230                 line: 1,
231                 character: 6,
232             }
233         );
234     }
235 }
236