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