]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/src/line_index.rs
Merge #7777
[rust.git] / crates / rust-analyzer / src / line_index.rs
1 //! Enhances `ide::LineIndex` with additional info required to convert offsets
2 //! into lsp positions.
3 //!
4 //! We maintain invariant that all internal strings use `\n` as line separator.
5 //! This module does line ending conversion and detection (so that we can
6 //! convert back to `\r\n` on the way out).
7
8 use std::sync::Arc;
9
10 pub enum OffsetEncoding {
11     Utf8,
12     Utf16,
13 }
14
15 pub(crate) struct LineIndex {
16     pub(crate) index: Arc<ide::LineIndex>,
17     pub(crate) endings: LineEndings,
18     pub(crate) encoding: OffsetEncoding,
19 }
20
21 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
22 pub(crate) enum LineEndings {
23     Unix,
24     Dos,
25 }
26
27 impl LineEndings {
28     /// Replaces `\r\n` with `\n` in-place in `src`.
29     pub(crate) fn normalize(src: String) -> (String, LineEndings) {
30         if !src.as_bytes().contains(&b'\r') {
31             return (src, LineEndings::Unix);
32         }
33
34         // We replace `\r\n` with `\n` in-place, which doesn't break utf-8 encoding.
35         // While we *can* call `as_mut_vec` and do surgery on the live string
36         // directly, let's rather steal the contents of `src`. This makes the code
37         // safe even if a panic occurs.
38
39         let mut buf = src.into_bytes();
40         let mut gap_len = 0;
41         let mut tail = buf.as_mut_slice();
42         loop {
43             let idx = match find_crlf(&tail[gap_len..]) {
44                 None => tail.len(),
45                 Some(idx) => idx + gap_len,
46             };
47             tail.copy_within(gap_len..idx, 0);
48             tail = &mut tail[idx - gap_len..];
49             if tail.len() == gap_len {
50                 break;
51             }
52             gap_len += 1;
53         }
54
55         // Account for removed `\r`.
56         // After `set_len`, `buf` is guaranteed to contain utf-8 again.
57         let new_len = buf.len() - gap_len;
58         let src = unsafe {
59             buf.set_len(new_len);
60             String::from_utf8_unchecked(buf)
61         };
62         return (src, LineEndings::Dos);
63
64         fn find_crlf(src: &[u8]) -> Option<usize> {
65             src.windows(2).position(|it| it == b"\r\n")
66         }
67     }
68 }