1use std::fmt::{self, Write};
2
3use unicode_segmentation::UnicodeSegmentation;
4use unicode_width::UnicodeWidthStr;
5
6mod offsets;
7pub(crate) use offsets::source_offsets;
8
9pub fn safe_text(bytes: &[u8]) -> String {
10 let mut result = String::new();
11 append_bytes(&mut result, bytes, true).expect("Writing to a String cannot fail");
12 result
13}
14
15fn append_bytes(out: &mut impl Write, bytes: &[u8], complete: bool) -> Result<usize, fmt::Error> {
16 let mut rest = bytes;
17 while !rest.is_empty() {
18 match std::str::from_utf8(rest) {
19 Ok(text) => {
20 append_safe(out, text)?;
21 return Ok(bytes.len());
22 }
23 Err(error) => {
24 let n = error.valid_up_to();
25 append_safe(
26 out,
27 std::str::from_utf8(&rest[..n]).expect("valid UTF-8 prefix"),
28 )?;
29 if !complete && error.error_len().is_none() {
30 return Ok(bytes.len() - rest.len() + n);
31 }
32 let end = n + error.error_len().unwrap_or(rest.len() - n);
33 for b in &rest[n..end] {
34 write!(out, "\\x{b:02x}")?;
35 }
36 rest = &rest[end..];
37 }
38 }
39 }
40 Ok(bytes.len())
41}
42
43fn append_safe(out: &mut impl Write, text: &str) -> fmt::Result {
44 for c in text.chars() {
45 if c == '\n' || c == '\t' {
46 out.write_char(c)?;
47 } else if c.is_control() || matches!(c, '\u{202a}'..='\u{202e}' | '\u{2066}'..='\u{2069}') {
48 write!(out, "\\u{{{:x}}}", c as u32)?;
49 } else {
50 out.write_char(c)?;
51 }
52 }
53 Ok(())
54}
55
56pub fn display_text(bytes: &[u8]) -> String {
57 let bytes = if let Some(line) = bytes.strip_suffix(b"\n") {
58 line.strip_suffix(b"\r").unwrap_or(line)
59 } else {
60 bytes
61 };
62 safe_text(bytes)
63}
64
65pub fn safe_label(bytes: &[u8]) -> String {
66 safe_text(bytes).replace('\n', "\\n").replace('\t', "\\t")
67}
68
69pub struct Glyph<'a> {
70 pub text: &'a str,
71 pub byte: usize,
72 pub column: usize,
73 pub width: usize,
74}
75
76pub fn glyphs(text: &str, tab_width: usize) -> impl Iterator<Item = Glyph<'_>> {
77 glyphs_from(text, tab_width, 0, 0)
78}
79
80pub fn glyphs_from(
81 text: &str,
82 tab_width: usize,
83 start_byte: usize,
84 mut column: usize,
85) -> impl Iterator<Item = Glyph<'_>> {
86 text[start_byte..]
87 .grapheme_indices(true)
88 .map(move |(byte, text)| {
89 let width = if text == "\t" {
90 tab_width - column % tab_width
91 } else {
92 text.width().max(1)
93 };
94 let glyph = Glyph {
95 text,
96 byte: start_byte + byte,
97 column,
98 width,
99 };
100 column += width;
101 glyph
102 })
103}