code.rs:105 and markdown/render.rs:420 both split the source with
split('\n'), so on a CRLF file every row keeps its trailing \r. That row
goes straight into:
crate::text::selectable_text(crate::text::SelectableText::new(
SharedString::from(line.to_string()),
which means the CR gets painted, selected and copied.
Easy to hit: readFileSync on a CRLF checkout returns CRLF, code?: string is
handed to Rust untouched, and reading a file into <code> is the normal thing
to do with it.
The rest of the code already strips it. from_absolute_spans walks back over
\n and then \r when it works out where each line ends, there's a test called
crlf_line_endings_do_not_leak_into_spans, and diff/mod.rs parses with
.lines(). These two renderers are the odd ones out.
Concretely, on the row // one\r the highlighter reports a span of 0..6 while
the row is 7 bytes, so runs_for_spans falls into its tail branch:
if at < line.len() {
runs.push(plain(line.len() - at, plain_color));
}
and emits a one-byte run for the CR in the plain text colour.
I fixed it with one helper, next to line_starts:
pub fn source_lines(source: &str) -> impl Iterator<Item = &str> {
source
.split('\n')
.map(|line| line.strip_suffix('\r').unwrap_or(line))
}
and both call sites go through it. I kept split('\n') instead of .lines() on
purpose — .lines() drops the trailing empty row, which would change how many
rows a source ending in a newline paints.
Two tests come with it. The useful one pins both layers to the same source:
let rows: Vec<&str> = source_lines(source).collect();
assert_eq!(document.lines[0][0].range, 0..rows[0].len());
cargo test --lib is green on Windows with them (128 passed). I couldn't do the
end-to-end check — getPaintedText() needs the test renderer, so it needs a
Mac.
code.rs:105andmarkdown/render.rs:420both split the source withsplit('\n'), so on a CRLF file every row keeps its trailing\r. That rowgoes straight into:
which means the CR gets painted, selected and copied.
Easy to hit:
readFileSyncon a CRLF checkout returns CRLF,code?: stringishanded to Rust untouched, and reading a file into
<code>is the normal thingto do with it.
The rest of the code already strips it.
from_absolute_spanswalks back over\nand then\rwhen it works out where each line ends, there's a test calledcrlf_line_endings_do_not_leak_into_spans, anddiff/mod.rsparses with.lines(). These two renderers are the odd ones out.Concretely, on the row
// one\rthe highlighter reports a span of0..6whilethe row is 7 bytes, so
runs_for_spansfalls into its tail branch:and emits a one-byte run for the CR in the plain text colour.
I fixed it with one helper, next to
line_starts:and both call sites go through it. I kept
split('\n')instead of.lines()onpurpose —
.lines()drops the trailing empty row, which would change how manyrows a source ending in a newline paints.
Two tests come with it. The useful one pins both layers to the same source:
cargo test --libis green on Windows with them (128 passed). I couldn't do theend-to-end check —
getPaintedText()needs the test renderer, so it needs aMac.