Refactor internal suggestion API
This commit is contained in:
parent
525b81d570
commit
443332afaf
5 changed files with 111 additions and 122 deletions
|
@ -9,6 +9,7 @@
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
use CodeSuggestion;
|
use CodeSuggestion;
|
||||||
|
use SubstitutionPart;
|
||||||
use Substitution;
|
use Substitution;
|
||||||
use Level;
|
use Level;
|
||||||
use RenderSpan;
|
use RenderSpan;
|
||||||
|
@ -217,9 +218,11 @@ impl Diagnostic {
|
||||||
/// See `CodeSuggestion` for more information.
|
/// See `CodeSuggestion` for more information.
|
||||||
pub fn span_suggestion_short(&mut self, sp: Span, msg: &str, suggestion: String) -> &mut Self {
|
pub fn span_suggestion_short(&mut self, sp: Span, msg: &str, suggestion: String) -> &mut Self {
|
||||||
self.suggestions.push(CodeSuggestion {
|
self.suggestions.push(CodeSuggestion {
|
||||||
substitution_parts: vec![Substitution {
|
substitutions: vec![Substitution {
|
||||||
span: sp,
|
parts: vec![SubstitutionPart {
|
||||||
substitutions: vec![suggestion],
|
snippet: suggestion,
|
||||||
|
span: sp,
|
||||||
|
}],
|
||||||
}],
|
}],
|
||||||
msg: msg.to_owned(),
|
msg: msg.to_owned(),
|
||||||
show_code_when_inline: false,
|
show_code_when_inline: false,
|
||||||
|
@ -245,9 +248,11 @@ impl Diagnostic {
|
||||||
/// See `CodeSuggestion` for more information.
|
/// See `CodeSuggestion` for more information.
|
||||||
pub fn span_suggestion(&mut self, sp: Span, msg: &str, suggestion: String) -> &mut Self {
|
pub fn span_suggestion(&mut self, sp: Span, msg: &str, suggestion: String) -> &mut Self {
|
||||||
self.suggestions.push(CodeSuggestion {
|
self.suggestions.push(CodeSuggestion {
|
||||||
substitution_parts: vec![Substitution {
|
substitutions: vec![Substitution {
|
||||||
span: sp,
|
parts: vec![SubstitutionPart {
|
||||||
substitutions: vec![suggestion],
|
snippet: suggestion,
|
||||||
|
span: sp,
|
||||||
|
}],
|
||||||
}],
|
}],
|
||||||
msg: msg.to_owned(),
|
msg: msg.to_owned(),
|
||||||
show_code_when_inline: true,
|
show_code_when_inline: true,
|
||||||
|
@ -258,10 +263,12 @@ impl Diagnostic {
|
||||||
/// Prints out a message with multiple suggested edits of the code.
|
/// Prints out a message with multiple suggested edits of the code.
|
||||||
pub fn span_suggestions(&mut self, sp: Span, msg: &str, suggestions: Vec<String>) -> &mut Self {
|
pub fn span_suggestions(&mut self, sp: Span, msg: &str, suggestions: Vec<String>) -> &mut Self {
|
||||||
self.suggestions.push(CodeSuggestion {
|
self.suggestions.push(CodeSuggestion {
|
||||||
substitution_parts: vec![Substitution {
|
substitutions: suggestions.into_iter().map(|snippet| Substitution {
|
||||||
span: sp,
|
parts: vec![SubstitutionPart {
|
||||||
substitutions: suggestions,
|
snippet,
|
||||||
}],
|
span: sp,
|
||||||
|
}],
|
||||||
|
}).collect(),
|
||||||
msg: msg.to_owned(),
|
msg: msg.to_owned(),
|
||||||
show_code_when_inline: true,
|
show_code_when_inline: true,
|
||||||
});
|
});
|
||||||
|
|
|
@ -38,15 +38,15 @@ impl Emitter for EmitterWriter {
|
||||||
|
|
||||||
if let Some((sugg, rest)) = db.suggestions.split_first() {
|
if let Some((sugg, rest)) = db.suggestions.split_first() {
|
||||||
if rest.is_empty() &&
|
if rest.is_empty() &&
|
||||||
// don't display multipart suggestions as labels
|
|
||||||
sugg.substitution_parts.len() == 1 &&
|
|
||||||
// don't display multi-suggestions as labels
|
// don't display multi-suggestions as labels
|
||||||
sugg.substitutions() == 1 &&
|
sugg.substitutions.len() == 1 &&
|
||||||
|
// don't display multipart suggestions as labels
|
||||||
|
sugg.substitutions[0].parts.len() == 1 &&
|
||||||
// don't display long messages as labels
|
// don't display long messages as labels
|
||||||
sugg.msg.split_whitespace().count() < 10 &&
|
sugg.msg.split_whitespace().count() < 10 &&
|
||||||
// don't display multiline suggestions as labels
|
// don't display multiline suggestions as labels
|
||||||
sugg.substitution_parts[0].substitutions[0].find('\n').is_none() {
|
!sugg.substitutions[0].parts[0].snippet.contains('\n') {
|
||||||
let substitution = &sugg.substitution_parts[0].substitutions[0];
|
let substitution = &sugg.substitutions[0].parts[0].snippet;
|
||||||
let msg = if substitution.len() == 0 || !sugg.show_code_when_inline {
|
let msg = if substitution.len() == 0 || !sugg.show_code_when_inline {
|
||||||
// This substitution is only removal or we explicitly don't want to show the
|
// This substitution is only removal or we explicitly don't want to show the
|
||||||
// code inline, don't show it
|
// code inline, don't show it
|
||||||
|
@ -54,7 +54,7 @@ impl Emitter for EmitterWriter {
|
||||||
} else {
|
} else {
|
||||||
format!("help: {}: `{}`", sugg.msg, substitution)
|
format!("help: {}: `{}`", sugg.msg, substitution)
|
||||||
};
|
};
|
||||||
primary_span.push_span_label(sugg.substitution_spans().next().unwrap(), msg);
|
primary_span.push_span_label(sugg.substitutions[0].parts[0].span, msg);
|
||||||
} else {
|
} else {
|
||||||
// if there are multiple suggestions, print them all in full
|
// if there are multiple suggestions, print them all in full
|
||||||
// to be consistent. We could try to figure out if we can
|
// to be consistent. We could try to figure out if we can
|
||||||
|
@ -1098,30 +1098,34 @@ impl EmitterWriter {
|
||||||
-> io::Result<()> {
|
-> io::Result<()> {
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
let primary_sub = &suggestion.substitution_parts[0];
|
|
||||||
if let Some(ref cm) = self.cm {
|
if let Some(ref cm) = self.cm {
|
||||||
let mut buffer = StyledBuffer::new();
|
let mut buffer = StyledBuffer::new();
|
||||||
|
|
||||||
let lines = cm.span_to_lines(primary_sub.span).unwrap();
|
// Render the suggestion message
|
||||||
|
|
||||||
assert!(!lines.lines.is_empty());
|
|
||||||
|
|
||||||
buffer.append(0, &level.to_string(), Style::Level(level.clone()));
|
buffer.append(0, &level.to_string(), Style::Level(level.clone()));
|
||||||
buffer.append(0, ": ", Style::HeaderMsg);
|
buffer.append(0, ": ", Style::HeaderMsg);
|
||||||
self.msg_to_buffer(&mut buffer,
|
self.msg_to_buffer(&mut buffer,
|
||||||
&[(suggestion.msg.to_owned(), Style::NoStyle)],
|
&[(suggestion.msg.to_owned(), Style::NoStyle)],
|
||||||
max_line_num_len,
|
max_line_num_len,
|
||||||
"suggestion",
|
"suggestion",
|
||||||
Some(Style::HeaderMsg));
|
Some(Style::HeaderMsg));
|
||||||
|
|
||||||
|
// Render the replacements for each suggestion
|
||||||
let suggestions = suggestion.splice_lines(cm.borrow());
|
let suggestions = suggestion.splice_lines(cm.borrow());
|
||||||
let span_start_pos = cm.lookup_char_pos(primary_sub.span.lo());
|
|
||||||
let line_start = span_start_pos.line;
|
|
||||||
draw_col_separator_no_space(&mut buffer, 1, max_line_num_len + 1);
|
|
||||||
let mut row_num = 2;
|
let mut row_num = 2;
|
||||||
for (&(ref complete, show_underline), ref sub) in suggestions
|
for &(ref complete, ref parts) in suggestions.iter().take(MAX_SUGGESTIONS) {
|
||||||
.iter().zip(primary_sub.substitutions.iter()).take(MAX_SUGGESTIONS)
|
let show_underline = parts.len() == 1
|
||||||
{
|
&& complete.lines().count() == 1
|
||||||
|
&& parts[0].snippet.trim() != complete.trim();
|
||||||
|
|
||||||
|
let lines = cm.span_to_lines(parts[0].span).unwrap();
|
||||||
|
|
||||||
|
assert!(!lines.lines.is_empty());
|
||||||
|
|
||||||
|
let span_start_pos = cm.lookup_char_pos(parts[0].span.lo());
|
||||||
|
let line_start = span_start_pos.line;
|
||||||
|
draw_col_separator_no_space(&mut buffer, 1, max_line_num_len + 1);
|
||||||
let mut line_pos = 0;
|
let mut line_pos = 0;
|
||||||
// Only show underline if there's a single suggestion and it is a single line
|
// Only show underline if there's a single suggestion and it is a single line
|
||||||
let mut lines = complete.lines();
|
let mut lines = complete.lines();
|
||||||
|
@ -1136,21 +1140,21 @@ impl EmitterWriter {
|
||||||
buffer.append(row_num, line, Style::NoStyle);
|
buffer.append(row_num, line, Style::NoStyle);
|
||||||
line_pos += 1;
|
line_pos += 1;
|
||||||
row_num += 1;
|
row_num += 1;
|
||||||
// Only show an underline in the suggestions if the suggestion is not the
|
}
|
||||||
// entirety of the code being shown and the displayed code is not multiline.
|
// Only show an underline in the suggestions if the suggestion is not the
|
||||||
if show_underline {
|
// entirety of the code being shown and the displayed code is not multiline.
|
||||||
draw_col_separator(&mut buffer, row_num, max_line_num_len + 1);
|
if show_underline {
|
||||||
let sub_len = sub.trim_right().len();
|
draw_col_separator(&mut buffer, row_num, max_line_num_len + 1);
|
||||||
let underline_start = span_start_pos.col.0;
|
let sub_len = parts[0].snippet.len();
|
||||||
let underline_end = span_start_pos.col.0 + sub_len;
|
let underline_start = span_start_pos.col.0;
|
||||||
for p in underline_start..underline_end {
|
let underline_end = span_start_pos.col.0 + sub_len;
|
||||||
buffer.putc(row_num,
|
for p in underline_start..underline_end {
|
||||||
max_line_num_len + 3 + p,
|
buffer.putc(row_num,
|
||||||
'^',
|
max_line_num_len + 3 + p,
|
||||||
Style::UnderlinePrimary);
|
'^',
|
||||||
}
|
Style::UnderlinePrimary);
|
||||||
row_num += 1;
|
|
||||||
}
|
}
|
||||||
|
row_num += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we elided some lines, add an ellipsis
|
// if we elided some lines, add an ellipsis
|
||||||
|
|
|
@ -76,17 +76,20 @@ pub struct CodeSuggestion {
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// vec![
|
/// vec![
|
||||||
/// (0..3, vec!["a", "x"]),
|
/// Substitution { parts: vec![(0..3, "a"), (4..7, "b")] },
|
||||||
/// (4..7, vec!["b", "y"]),
|
/// Substitution { parts: vec![(0..3, "x"), (4..7, "y")] },
|
||||||
/// ]
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// or by replacing the entire span:
|
/// or by replacing the entire span:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// vec![(0..7, vec!["a.b", "x.y"])]
|
/// vec![
|
||||||
|
/// Substitution { parts: vec![(0..7, "a.b")] },
|
||||||
|
/// Substitution { parts: vec![(0..7, "x.y")] },
|
||||||
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
pub substitution_parts: Vec<Substitution>,
|
pub substitutions: Vec<Substitution>,
|
||||||
pub msg: String,
|
pub msg: String,
|
||||||
pub show_code_when_inline: bool,
|
pub show_code_when_inline: bool,
|
||||||
}
|
}
|
||||||
|
@ -94,8 +97,13 @@ pub struct CodeSuggestion {
|
||||||
#[derive(Clone, Debug, PartialEq, Hash, RustcEncodable, RustcDecodable)]
|
#[derive(Clone, Debug, PartialEq, Hash, RustcEncodable, RustcDecodable)]
|
||||||
/// See the docs on `CodeSuggestion::substitutions`
|
/// See the docs on `CodeSuggestion::substitutions`
|
||||||
pub struct Substitution {
|
pub struct Substitution {
|
||||||
|
pub parts: Vec<SubstitutionPart>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Hash, RustcEncodable, RustcDecodable)]
|
||||||
|
pub struct SubstitutionPart {
|
||||||
pub span: Span,
|
pub span: Span,
|
||||||
pub substitutions: Vec<String>,
|
pub snippet: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait CodeMapper {
|
pub trait CodeMapper {
|
||||||
|
@ -109,18 +117,8 @@ pub trait CodeMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CodeSuggestion {
|
impl CodeSuggestion {
|
||||||
/// Returns the number of substitutions
|
/// Returns the assembled code suggestions and whether they should be shown with an underline.
|
||||||
fn substitutions(&self) -> usize {
|
pub fn splice_lines(&self, cm: &CodeMapper) -> Vec<(String, Vec<SubstitutionPart>)> {
|
||||||
self.substitution_parts[0].substitutions.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the number of substitutions
|
|
||||||
fn substitution_spans<'a>(&'a self) -> impl Iterator<Item = Span> + 'a {
|
|
||||||
self.substitution_parts.iter().map(|sub| sub.span)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the assembled code suggestions and wether they should be shown with an underline.
|
|
||||||
pub fn splice_lines(&self, cm: &CodeMapper) -> Vec<(String, bool)> {
|
|
||||||
use syntax_pos::{CharPos, Loc, Pos};
|
use syntax_pos::{CharPos, Loc, Pos};
|
||||||
|
|
||||||
fn push_trailing(buf: &mut String,
|
fn push_trailing(buf: &mut String,
|
||||||
|
@ -142,60 +140,42 @@ impl CodeSuggestion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.substitution_parts.is_empty() {
|
assert!(!self.substitutions.is_empty());
|
||||||
return vec![(String::new(), false)];
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut primary_spans: Vec<_> = self.substitution_parts
|
self.substitutions.iter().cloned().map(|mut substitution| {
|
||||||
.iter()
|
// Assumption: all spans are in the same file, and all spans
|
||||||
.map(|sub| (sub.span, &sub.substitutions))
|
// are disjoint. Sort in ascending order.
|
||||||
.collect();
|
substitution.parts.sort_by_key(|part| part.span.lo());
|
||||||
|
|
||||||
// Assumption: all spans are in the same file, and all spans
|
// Find the bounding span.
|
||||||
// are disjoint. Sort in ascending order.
|
let lo = substitution.parts.iter().map(|part| part.span.lo()).min().unwrap();
|
||||||
primary_spans.sort_by_key(|sp| sp.0.lo());
|
let hi = substitution.parts.iter().map(|part| part.span.hi()).min().unwrap();
|
||||||
|
let bounding_span = Span::new(lo, hi, NO_EXPANSION);
|
||||||
|
let lines = cm.span_to_lines(bounding_span).unwrap();
|
||||||
|
assert!(!lines.lines.is_empty());
|
||||||
|
|
||||||
// Find the bounding span.
|
// To build up the result, we do this for each span:
|
||||||
let lo = primary_spans.iter().map(|sp| sp.0.lo()).min().unwrap();
|
// - push the line segment trailing the previous span
|
||||||
let hi = primary_spans.iter().map(|sp| sp.0.hi()).min().unwrap();
|
// (at the beginning a "phantom" span pointing at the start of the line)
|
||||||
let bounding_span = Span::new(lo, hi, NO_EXPANSION);
|
// - push lines between the previous and current span (if any)
|
||||||
let lines = cm.span_to_lines(bounding_span).unwrap();
|
// - if the previous and current span are not on the same line
|
||||||
assert!(!lines.lines.is_empty());
|
// push the line segment leading up to the current span
|
||||||
|
// - splice in the span substitution
|
||||||
|
//
|
||||||
|
// Finally push the trailing line segment of the last span
|
||||||
|
let fm = &lines.file;
|
||||||
|
let mut prev_hi = cm.lookup_char_pos(bounding_span.lo());
|
||||||
|
prev_hi.col = CharPos::from_usize(0);
|
||||||
|
|
||||||
// To build up the result, we do this for each span:
|
let mut prev_line = fm.get_line(lines.lines[0].line_index);
|
||||||
// - push the line segment trailing the previous span
|
let mut buf = String::new();
|
||||||
// (at the beginning a "phantom" span pointing at the start of the line)
|
|
||||||
// - push lines between the previous and current span (if any)
|
|
||||||
// - if the previous and current span are not on the same line
|
|
||||||
// push the line segment leading up to the current span
|
|
||||||
// - splice in the span substitution
|
|
||||||
//
|
|
||||||
// Finally push the trailing line segment of the last span
|
|
||||||
let fm = &lines.file;
|
|
||||||
let mut prev_hi = cm.lookup_char_pos(bounding_span.lo());
|
|
||||||
prev_hi.col = CharPos::from_usize(0);
|
|
||||||
|
|
||||||
let mut prev_line = fm.get_line(lines.lines[0].line_index);
|
for part in &substitution.parts {
|
||||||
let mut bufs = vec![(String::new(), false); self.substitutions()];
|
let cur_lo = cm.lookup_char_pos(part.span.lo());
|
||||||
|
|
||||||
for (sp, substitutes) in primary_spans {
|
|
||||||
let cur_lo = cm.lookup_char_pos(sp.lo());
|
|
||||||
for (&mut (ref mut buf, ref mut underline), substitute) in bufs.iter_mut()
|
|
||||||
.zip(substitutes) {
|
|
||||||
if prev_hi.line == cur_lo.line {
|
if prev_hi.line == cur_lo.line {
|
||||||
push_trailing(buf, prev_line.as_ref(), &prev_hi, Some(&cur_lo));
|
push_trailing(&mut buf, prev_line.as_ref(), &prev_hi, Some(&cur_lo));
|
||||||
|
|
||||||
// Only show an underline in the suggestions if the suggestion is not the
|
|
||||||
// entirety of the code being shown and the displayed code is not multiline.
|
|
||||||
if prev_line.as_ref().unwrap().trim().len() > 0
|
|
||||||
&& !substitute.ends_with('\n')
|
|
||||||
&& substitute.lines().count() == 1
|
|
||||||
{
|
|
||||||
*underline = true;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
*underline = false;
|
push_trailing(&mut buf, prev_line.as_ref(), &prev_hi, None);
|
||||||
push_trailing(buf, prev_line.as_ref(), &prev_hi, None);
|
|
||||||
// push lines between the previous and current span (if any)
|
// push lines between the previous and current span (if any)
|
||||||
for idx in prev_hi.line..(cur_lo.line - 1) {
|
for idx in prev_hi.line..(cur_lo.line - 1) {
|
||||||
if let Some(line) = fm.get_line(idx) {
|
if let Some(line) = fm.get_line(idx) {
|
||||||
|
@ -207,22 +187,20 @@ impl CodeSuggestion {
|
||||||
buf.push_str(&cur_line[..cur_lo.col.to_usize()]);
|
buf.push_str(&cur_line[..cur_lo.col.to_usize()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buf.push_str(substitute);
|
buf.push_str(&part.snippet);
|
||||||
|
prev_hi = cm.lookup_char_pos(part.span.hi());
|
||||||
|
prev_line = fm.get_line(prev_hi.line - 1);
|
||||||
}
|
}
|
||||||
prev_hi = cm.lookup_char_pos(sp.hi());
|
|
||||||
prev_line = fm.get_line(prev_hi.line - 1);
|
|
||||||
}
|
|
||||||
for &mut (ref mut buf, _) in &mut bufs {
|
|
||||||
// if the replacement already ends with a newline, don't print the next line
|
// if the replacement already ends with a newline, don't print the next line
|
||||||
if !buf.ends_with('\n') {
|
if !buf.ends_with('\n') {
|
||||||
push_trailing(buf, prev_line.as_ref(), &prev_hi, None);
|
push_trailing(&mut buf, prev_line.as_ref(), &prev_hi, None);
|
||||||
}
|
}
|
||||||
// remove trailing newlines
|
// remove trailing newlines
|
||||||
while buf.ends_with('\n') {
|
while buf.ends_with('\n') {
|
||||||
buf.pop();
|
buf.pop();
|
||||||
}
|
}
|
||||||
}
|
(buf, substitution.parts)
|
||||||
bufs
|
}).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -278,17 +278,17 @@ impl DiagnosticSpan {
|
||||||
|
|
||||||
fn from_suggestion(suggestion: &CodeSuggestion, je: &JsonEmitter)
|
fn from_suggestion(suggestion: &CodeSuggestion, je: &JsonEmitter)
|
||||||
-> Vec<DiagnosticSpan> {
|
-> Vec<DiagnosticSpan> {
|
||||||
suggestion.substitution_parts
|
suggestion.substitutions
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|substitution| {
|
.flat_map(|substitution| {
|
||||||
substitution.substitutions.iter().map(move |suggestion| {
|
substitution.parts.iter().map(move |suggestion| {
|
||||||
let span_label = SpanLabel {
|
let span_label = SpanLabel {
|
||||||
span: substitution.span,
|
span: suggestion.span,
|
||||||
is_primary: true,
|
is_primary: true,
|
||||||
label: None,
|
label: None,
|
||||||
};
|
};
|
||||||
DiagnosticSpan::from_span_label(span_label,
|
DiagnosticSpan::from_span_label(span_label,
|
||||||
Some(suggestion),
|
Some(&suggestion.snippet),
|
||||||
je)
|
je)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -13,7 +13,7 @@ help: try adding a semicolon
|
||||||
help: try adding a return type
|
help: try adding a return type
|
||||||
|
|
|
|
||||||
18 | fn bar() -> usize {
|
18 | fn bar() -> usize {
|
||||||
| ^^^^^^^^
|
| ^^^^^^^^^
|
||||||
|
|
||||||
error: aborting due to previous error
|
error: aborting due to previous error
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue