From 99f2977031706dfef6730764d359b9e5d0f673b4 Mon Sep 17 00:00:00 2001 From: Esteban Küber Date: Mon, 21 Jun 2021 19:07:19 -0700 Subject: Modify structured suggestion output * On suggestions that include deletions, use a diff inspired output format * When suggesting addition, use `+` as underline * Color highlight modified span --- compiler/rustc_errors/src/lib.rs | 83 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 7 deletions(-) (limited to 'compiler/rustc_errors/src/lib.rs') diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index fc0924ac5f9..ec29d8016dd 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -160,32 +160,77 @@ pub struct SubstitutionPart { 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, bool)> { + pub fn splice_lines( + &self, + sm: &SourceMap, + ) -> Vec<(String, Vec, Vec>, bool)> { + // For the `Vec>` 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()); @@ -220,6 +265,7 @@ impl CodeSuggestion { 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) @@ -236,17 +282,29 @@ impl CodeSuggestion { lines.lines.get(0).and_then(|line0| sf.get_line(line0.line_index)); let mut buf = String::new(); + let mut line_highlight = vec![]; 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)); + 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)); + 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)); } } if let Some(cur_line) = sf.get_line(cur_lo.line - 1) { @@ -257,10 +315,21 @@ impl CodeSuggestion { buf.push_str(&cur_line[..end]); } } + // Add a whole line highlight per line in the snippet. + line_highlight.push(SubstitutionHighlight { + start: cur_lo.col.0, + end: cur_lo.col.0 + + part.snippet.split('\n').next().unwrap_or(&part.snippet).len(), + }); + for line in part.snippet.split('\n').skip(1) { + highlights.push(std::mem::take(&mut line_highlight)); + line_highlight.push(SubstitutionHighlight { start: 0, end: line.len() }); + } buf.push_str(&part.snippet); prev_hi = sm.lookup_char_pos(part.span.hi()); prev_line = sf.get_line(prev_hi.line - 1); } + 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') { @@ -270,7 +339,7 @@ impl CodeSuggestion { while buf.ends_with('\n') { buf.pop(); } - Some((buf, substitution.parts, only_capitalization)) + Some((buf, substitution.parts, highlights, only_capitalization)) }) .collect() } -- cgit 1.4.1-3-g733a5