use std::borrow::Cow;
use crate::loc::counter::{LineKind, RawSpan, raw_string_spans};
use crate::loc::language::LanguageSpec;
pub fn multi_line_string_mask(lines: &[String], spec: &LanguageSpec) -> Vec<bool> {
let mut mask = vec![false; lines.len()];
if !spec.triple_quote_strings {
return mask;
}
let mut in_triple: Option<&str> = None;
for (idx, line) in lines.iter().enumerate() {
let started_in_string = in_triple.is_some();
scan_triple_quotes(line, &mut in_triple);
mask[idx] = started_in_string && in_triple.is_some();
}
mask
}
pub fn starts_in_string_mask(lines: &[String], spec: &LanguageSpec) -> Vec<bool> {
if !spec.triple_quote_strings {
return vec![false; lines.len()];
}
let mut in_triple: Option<&str> = None;
lines
.iter()
.map(|line| {
let started_in_string = in_triple.is_some();
scan_triple_quotes(line, &mut in_triple);
started_in_string
})
.collect()
}
pub fn demote_multi_line_strings(
lines: &[String],
kinds: &[LineKind],
spec: &LanguageSpec,
) -> Vec<LineKind> {
if !spec.triple_quote_strings {
return kinds.to_vec();
}
let mask = multi_line_string_mask(lines, spec);
kinds
.iter()
.enumerate()
.map(|(i, k)| {
if mask.get(i).copied().unwrap_or(false) {
LineKind::Blank
} else {
*k
}
})
.collect()
}
pub fn code_view<'a>(
lines: &'a [String],
kinds: &[LineKind],
spec: &LanguageSpec,
) -> (Cow<'a, [String]>, Vec<LineKind>) {
let mut kinds = demote_multi_line_strings(lines, kinds, spec);
let may_hold_one = |line: &String| line.contains("r\"") || line.contains("r#");
if !spec.raw_strings || !lines.iter().any(may_hold_one) {
return (Cow::Borrowed(lines), kinds);
}
let mut view = lines.to_vec();
for (at, spans) in raw_string_spans(lines, spec).iter().enumerate() {
if spans.is_empty() {
continue;
}
view[at] = without_raw_text(&lines[at], spans);
if view[at].is_empty() {
kinds[at] = LineKind::Blank;
}
}
(Cow::Owned(view), kinds)
}
fn without_raw_text(line: &str, spans: &[RawSpan]) -> String {
let mut bytes = line.as_bytes().to_vec();
for span in spans {
bytes[span.start..span.end].fill(b' ');
if span.opens {
bytes[span.start..span.start + 2].fill(b'"');
}
}
let text = String::from_utf8(bytes).unwrap_or_default();
text.trim_end().to_string()
}
fn advance_inside_triple(
bytes: &[u8],
i: usize,
delim: &str,
in_triple: &mut Option<&str>,
) -> usize {
if bytes[i] == b'\\' && i + 1 < bytes.len() {
i + 2
} else if bytes[i..].starts_with(delim.as_bytes()) {
*in_triple = None;
i + delim.len()
} else {
i + 1
}
}
fn advance_single_quote(bytes: &[u8], mut i: usize, q: u8) -> usize {
let len = bytes.len();
while i < len && bytes[i] != q {
if bytes[i] == b'\\' {
i += 1;
}
i += 1;
}
if i < len { i + 1 } else { i }
}
fn scan_triple_quotes(line: &str, in_triple: &mut Option<&str>) {
let bytes = line.as_bytes();
let len = bytes.len();
let mut i = 0;
while i < len {
if let Some(delim) = *in_triple {
i = advance_inside_triple(bytes, i, delim, in_triple);
} else if bytes[i] == b'"' || bytes[i] == b'\'' {
let q = bytes[i];
let triple: &str = if q == b'"' { "\"\"\"" } else { "'''" };
if bytes[i..].starts_with(triple.as_bytes()) {
*in_triple = Some(triple);
i += 3;
} else {
i = advance_single_quote(bytes, i + 1, q);
}
} else {
i += 1;
}
}
}
#[cfg(test)]
#[path = "string_mask_test.rs"]
mod tests;