vize_l0 0.525.0

L0 - the Davinci foundation level: ids, side tables, keys, dumps, diagnostics, passes and facts
Documentation
//! Explicit line-break conventions at TypeScript CLI and LSP boundaries.

#[derive(Clone, Copy)]
pub enum LineBreaks {
    /// Compiler diagnostics count CR, LF, CRLF, LS and PS.
    TypeScript,
    /// LSP positions count CR, LF and CRLF, not Unicode LS/PS.
    Lsp,
}

impl LineBreaks {
    fn is_break(self, ch: char) -> bool {
        matches!(ch, '\r' | '\n')
            || matches!(self, Self::TypeScript) && matches!(ch, '\u{2028}' | '\u{2029}')
    }

    /// Byte starts under this convention, including a final empty line after a break.
    pub fn line_starts(self, source: &str) -> impl Iterator<Item = usize> + '_ {
        let bytes = source.as_bytes();
        // SIMD searches skip ordinary text without decoding each UTF-8 scalar.
        // LS/PS share the E2 80 prefix; no other E2 sequence is a line break.
        std::iter::once(0).chain(memchr::memchr3_iter(b'\r', b'\n', 0xe2, bytes).filter_map(
            move |at| match bytes.get(at)? {
                b'\r' if bytes.get(at + 1) == Some(&b'\n') => None,
                b'\r' | b'\n' => Some(at + 1),
                _ if matches!(self, Self::TypeScript)
                    && bytes.get(at + 1) == Some(&0x80)
                    && matches!(bytes.get(at + 2), Some(0xa8 | 0xa9)) =>
                {
                    Some(at + 3)
                }
                _ => None,
            },
        ))
    }

    /// Convert line/UTF-16 positions without treating CRLF twice.
    pub fn position_to_offset(self, source: &str, line: u32, character: u32) -> Option<usize> {
        let start = self.line_starts(source).nth(line as usize)?;
        let text = source.get(start..)?.split(|ch| self.is_break(ch)).next()?;
        super::utf16_offset(text, character).map(|offset| start + offset)
    }

    /// Convert a generated byte offset to line/UTF-16 coordinates.
    pub fn offset_to_position(self, source: &str, offset: usize) -> (u32, u32) {
        let offset = offset.min(source.len());
        let (line, start) = self
            .line_starts(source)
            .enumerate()
            .take_while(|(_, start)| *start <= offset)
            .last()
            .unwrap_or((0, 0));
        let tail = source.get(start..).unwrap_or_default();
        let column = super::utf16::prefix_len(tail, offset - start);
        (line as u32, column as u32)
    }
}

#[cfg(test)]
mod tests {
    use super::LineBreaks;

    #[test]
    fn simd_line_starts_match_scalar_rules_for_mixed_unicode() {
        let pieces = [
            "", "a", "\r", "\n", "\r\n", "\u{2028}", "\u{2029}", "€", "😀", "\u{0085}",
        ];
        for mode in [LineBreaks::Lsp, LineBreaks::TypeScript] {
            for left in pieces {
                for right in pieces {
                    let source = crate::cstr!("{left}{right}");
                    let expected: Vec<_> = std::iter::once(0)
                        .chain(source.char_indices().filter_map(|(at, ch)| {
                            if ch == '\r' && source.as_bytes().get(at + 1) == Some(&b'\n') {
                                None
                            } else {
                                mode.is_break(ch).then_some(at + ch.len_utf8())
                            }
                        }))
                        .collect();
                    assert_eq!(
                        mode.line_starts(&source).collect::<Vec<_>>(),
                        expected,
                        "{source:?}"
                    );
                }
            }
        }
    }

    #[test]
    fn native_lines_and_utf16_columns_have_exact_boundaries() {
        let mode = LineBreaks::TypeScript;
        for newline in ["\r", "\n", "\r\n", "\u{2028}", "\u{2029}"] {
            let source = crate::cstr!("\u{1f600}{newline}x{newline}");
            let second = 4 + newline.len();
            assert_eq!(
                mode.line_starts(&source).collect::<Vec<_>>(),
                [0, second, source.len()]
            );
            assert_eq!(mode.position_to_offset(&source, 0, 1), None);
            assert_eq!(mode.position_to_offset(&source, 0, 2), Some(4));
            assert_eq!(mode.position_to_offset(&source, 0, 3), None);
            assert_eq!(mode.position_to_offset(&source, 1, 0), Some(second));
            assert_eq!(mode.position_to_offset(&source, 1, 1), Some(second + 1));
            assert_eq!(mode.position_to_offset(&source, 2, 0), Some(source.len()));
            assert_eq!(mode.position_to_offset(&source, 3, 0), None);
            assert_eq!(mode.offset_to_position(&source, second), (1, 0));
            assert_eq!(mode.offset_to_position(&source, second + 1), (1, 1));
            assert_eq!(mode.offset_to_position(&source, usize::MAX), (2, 0));
        }
        assert_eq!(
            mode.line_starts("\r\r\n\n\u{2028}\u{2029}")
                .collect::<Vec<_>>(),
            [0, 1, 3, 4, 7, 10]
        );
        assert_eq!(
            mode.line_starts("\u{0085}\u{000b}\u{000c}")
                .collect::<Vec<_>>(),
            [0]
        );
    }

    #[test]
    fn lsp_keeps_unicode_line_separators_inside_the_line() {
        let source = "\u{1f600}\u{2028}\u{2029}\r\nx\ry";
        let mode = LineBreaks::Lsp;
        assert_eq!(mode.line_starts(source).collect::<Vec<_>>(), [0, 12, 14]);
        assert_eq!(mode.position_to_offset(source, 0, 4), Some(10));
        assert_eq!(mode.position_to_offset(source, 0, 5), None);
        assert_eq!(mode.offset_to_position(source, 10), (0, 4));
        assert_eq!(mode.position_to_offset(source, 2, 1), Some(15));
    }
}