pub use emitter::ColorConfig;
-use tracing::debug;
+use tracing::{debug, info};
use Level::*;
use emitter::{is_case_difference, Emitter, EmitterWriter};
pub snippet: String,
}
+/// Used to translate between `Span`s and byte positions within a single output line in highlighted
+/// code of structured suggestions.
+#[derive(Debug, Clone, Copy)]
+pub struct SubstitutionHighlight {
+ start: usize,
+ end: usize,
+}
+
+impl SubstitutionPart {
+ pub fn is_addition(&self, sm: &SourceMap) -> bool {
+ !self.snippet.is_empty()
+ && sm
+ .span_to_snippet(self.span)
+ .map_or(self.span.is_empty(), |snippet| snippet.trim().is_empty())
+ }
+
+ pub fn is_deletion(&self) -> bool {
+ self.snippet.trim().is_empty()
+ }
+
+ pub fn is_replacement(&self, sm: &SourceMap) -> bool {
+ !self.snippet.is_empty()
+ && sm
+ .span_to_snippet(self.span)
+ .map_or(!self.span.is_empty(), |snippet| !snippet.trim().is_empty())
+ }
+}
+
impl CodeSuggestion {
/// Returns the assembled code suggestions, whether they should be shown with an underline
/// and whether the substitution only differs in capitalization.
- pub fn splice_lines(&self, sm: &SourceMap) -> Vec<(String, Vec<SubstitutionPart>, bool)> {
+ pub fn splice_lines(
+ &self,
+ sm: &SourceMap,
+ ) -> Vec<(String, Vec<SubstitutionPart>, Vec<Vec<SubstitutionHighlight>>, bool)> {
+ // For the `Vec<Vec<SubstitutionHighlight>>` value, the first level of the vector
+ // corresponds to the output snippet's lines, while the second level corresponds to the
+ // substrings within that line that should be highlighted.
+
use rustc_span::{CharPos, Pos};
+ /// Append to a buffer the remainder of the line of existing source code, and return the
+ /// count of lines that have been added for accurate highlighting.
fn push_trailing(
buf: &mut String,
line_opt: Option<&Cow<'_, str>>,
lo: &Loc,
hi_opt: Option<&Loc>,
- ) {
+ ) -> usize {
+ let mut line_count = 0;
let (lo, hi_opt) = (lo.col.to_usize(), hi_opt.map(|hi| hi.col.to_usize()));
if let Some(line) = line_opt {
if let Some(lo) = line.char_indices().map(|(i, _)| i).nth(lo) {
let hi_opt = hi_opt.and_then(|hi| line.char_indices().map(|(i, _)| i).nth(hi));
match hi_opt {
- Some(hi) if hi > lo => buf.push_str(&line[lo..hi]),
+ Some(hi) if hi > lo => {
+ line_count = line[lo..hi].matches('\n').count();
+ buf.push_str(&line[lo..hi])
+ }
Some(_) => (),
- None => buf.push_str(&line[lo..]),
+ None => {
+ line_count = line[lo..].matches('\n').count();
+ buf.push_str(&line[lo..])
+ }
}
}
if hi_opt.is_none() {
buf.push('\n');
}
}
+ line_count
}
assert!(!self.substitutions.is_empty());
return None;
}
+ let mut highlights = vec![];
// To build up the result, we do this for each span:
// - push the line segment trailing the previous span
// (at the beginning a "phantom" span pointing at the start of the line)
lines.lines.get(0).and_then(|line0| sf.get_line(line0.line_index));
let mut buf = String::new();
+ let mut line_highlight = vec![];
+ // We need to keep track of the difference between the existing code and the added
+ // or deleted code in order to point at the correct column *after* substitution.
+ let mut acc = 0;
for part in &substitution.parts {
let cur_lo = sm.lookup_char_pos(part.span.lo());
if prev_hi.line == cur_lo.line {
- push_trailing(&mut buf, prev_line.as_ref(), &prev_hi, Some(&cur_lo));
+ let mut count =
+ push_trailing(&mut buf, prev_line.as_ref(), &prev_hi, Some(&cur_lo));
+ while count > 0 {
+ highlights.push(std::mem::take(&mut line_highlight));
+ acc = 0;
+ count -= 1;
+ }
} else {
- push_trailing(&mut buf, prev_line.as_ref(), &prev_hi, None);
+ highlights.push(std::mem::take(&mut line_highlight));
+ let mut count = push_trailing(&mut buf, prev_line.as_ref(), &prev_hi, None);
+ while count > 0 {
+ highlights.push(std::mem::take(&mut line_highlight));
+ acc = 0;
+ count -= 1;
+ }
// push lines between the previous and current span (if any)
for idx in prev_hi.line..(cur_lo.line - 1) {
if let Some(line) = sf.get_line(idx) {
buf.push_str(line.as_ref());
buf.push('\n');
+ highlights.push(std::mem::take(&mut line_highlight));
+ acc = 0;
}
}
if let Some(cur_line) = sf.get_line(cur_lo.line - 1) {
buf.push_str(&cur_line[..end]);
}
}
+ // Add a whole line highlight per line in the snippet.
+ let len = part.snippet.split('\n').next().unwrap_or(&part.snippet).len();
+ line_highlight.push(SubstitutionHighlight {
+ start: (cur_lo.col.0 as isize + acc) as usize,
+ end: (cur_lo.col.0 as isize + acc + len as isize) as usize,
+ });
buf.push_str(&part.snippet);
prev_hi = sm.lookup_char_pos(part.span.hi());
+ if prev_hi.line == cur_lo.line {
+ acc += len as isize - (prev_hi.col.0 - cur_lo.col.0) as isize;
+ }
prev_line = sf.get_line(prev_hi.line - 1);
+ for line in part.snippet.split('\n').skip(1) {
+ acc = 0;
+ highlights.push(std::mem::take(&mut line_highlight));
+ line_highlight.push(SubstitutionHighlight { start: 0, end: line.len() });
+ }
}
+ highlights.push(std::mem::take(&mut line_highlight));
let only_capitalization = is_case_difference(sm, &buf, bounding_span);
// if the replacement already ends with a newline, don't print the next line
if !buf.ends_with('\n') {
while buf.ends_with('\n') {
buf.pop();
}
- Some((buf, substitution.parts, only_capitalization))
+ info!(?buf, ?highlights);
+ Some((buf, substitution.parts, highlights, only_capitalization))
})
.collect()
}