Remove old error emitter
This completes the transition to annotate-snippets
This commit is contained in:
parent
838db25382
commit
3ccabc6a8d
13 changed files with 53 additions and 3606 deletions
|
|
@ -17,7 +17,6 @@ rustc_error_messages = { path = "../rustc_error_messages" }
|
|||
rustc_fluent_macro = { path = "../rustc_fluent_macro" }
|
||||
rustc_hashes = { path = "../rustc_hashes" }
|
||||
rustc_index = { path = "../rustc_index" }
|
||||
rustc_lexer = { path = "../rustc_lexer" }
|
||||
rustc_lint_defs = { path = "../rustc_lint_defs" }
|
||||
rustc_macros = { path = "../rustc_macros" }
|
||||
rustc_serialize = { path = "../rustc_serialize" }
|
||||
|
|
|
|||
|
|
@ -15,10 +15,9 @@ use rustc_span::source_map::Spanned;
|
|||
use rustc_span::{DUMMY_SP, Span, Symbol};
|
||||
use tracing::debug;
|
||||
|
||||
use crate::snippet::Style;
|
||||
use crate::{
|
||||
CodeSuggestion, DiagCtxtHandle, DiagMessage, ErrCode, ErrorGuaranteed, ExplicitBug, Level,
|
||||
MultiSpan, StashKey, SubdiagMessage, Substitution, SubstitutionPart, SuggestionStyle,
|
||||
MultiSpan, StashKey, Style, SubdiagMessage, Substitution, SubstitutionPart, SuggestionStyle,
|
||||
Suggestions,
|
||||
};
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -51,7 +51,7 @@ pub use diagnostic_impls::{
|
|||
IndicateAnonymousLifetime, SingleLabelManySpans,
|
||||
};
|
||||
pub use emitter::ColorConfig;
|
||||
use emitter::{ConfusionType, DynEmitter, Emitter, detect_confusion_type, is_different};
|
||||
use emitter::{DynEmitter, Emitter};
|
||||
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
|
||||
use rustc_data_structures::stable_hasher::StableHasher;
|
||||
use rustc_data_structures::sync::{DynSend, Lock};
|
||||
|
|
@ -68,8 +68,7 @@ use rustc_macros::{Decodable, Encodable};
|
|||
pub use rustc_span::ErrorGuaranteed;
|
||||
pub use rustc_span::fatal_error::{FatalError, FatalErrorMarker, catch_fatal_errors};
|
||||
use rustc_span::source_map::SourceMap;
|
||||
use rustc_span::{BytePos, DUMMY_SP, Loc, Span};
|
||||
pub use snippet::Style;
|
||||
use rustc_span::{DUMMY_SP, Span};
|
||||
use tracing::debug;
|
||||
|
||||
use crate::emitter::TimingEvent;
|
||||
|
|
@ -87,8 +86,6 @@ pub mod json;
|
|||
mod lock;
|
||||
pub mod markdown;
|
||||
pub mod registry;
|
||||
mod snippet;
|
||||
mod styled_buffer;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
pub mod timings;
|
||||
|
|
@ -215,43 +212,6 @@ pub struct TrimmedSubstitutionPart {
|
|||
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(crate) struct SubstitutionHighlight {
|
||||
start: usize,
|
||||
end: usize,
|
||||
}
|
||||
|
||||
impl SubstitutionPart {
|
||||
/// Try to turn a replacement into an addition when the span that is being
|
||||
/// overwritten matches either the prefix or suffix of the replacement.
|
||||
fn trim_trivial_replacements(self, sm: &SourceMap) -> TrimmedSubstitutionPart {
|
||||
let mut trimmed_part = TrimmedSubstitutionPart {
|
||||
original_span: self.span,
|
||||
span: self.span,
|
||||
snippet: self.snippet,
|
||||
};
|
||||
if trimmed_part.snippet.is_empty() {
|
||||
return trimmed_part;
|
||||
}
|
||||
let Ok(snippet) = sm.span_to_snippet(trimmed_part.span) else {
|
||||
return trimmed_part;
|
||||
};
|
||||
|
||||
if let Some((prefix, substr, suffix)) = as_substr(&snippet, &trimmed_part.snippet) {
|
||||
trimmed_part.span = Span::new(
|
||||
trimmed_part.span.lo() + BytePos(prefix as u32),
|
||||
trimmed_part.span.hi() - BytePos(suffix as u32),
|
||||
trimmed_part.span.ctxt(),
|
||||
trimmed_part.span.parent(),
|
||||
);
|
||||
trimmed_part.snippet = substr.to_string();
|
||||
}
|
||||
trimmed_part
|
||||
}
|
||||
}
|
||||
|
||||
impl TrimmedSubstitutionPart {
|
||||
pub fn is_addition(&self, sm: &SourceMap) -> bool {
|
||||
!self.snippet.is_empty() && !self.replaces_meaningful_content(sm)
|
||||
|
|
@ -303,229 +263,6 @@ fn as_substr<'a>(original: &'a str, suggestion: &'a str) -> Option<(usize, &'a s
|
|||
}
|
||||
}
|
||||
|
||||
impl CodeSuggestion {
|
||||
/// Returns the assembled code suggestions, whether they should be shown with an underline
|
||||
/// and whether the substitution only differs in capitalization.
|
||||
pub(crate) fn splice_lines(
|
||||
&self,
|
||||
sm: &SourceMap,
|
||||
) -> Vec<(String, Vec<TrimmedSubstitutionPart>, Vec<Vec<SubstitutionHighlight>>, ConfusionType)>
|
||||
{
|
||||
// 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};
|
||||
|
||||
/// Extracts a substring from the provided `line_opt` based on the specified low and high
|
||||
/// indices, appends it to the given buffer `buf`, and returns the count of newline
|
||||
/// characters in the substring for accurate highlighting. If `line_opt` is `None`, a
|
||||
/// newline character is appended to the buffer, and 0 is returned.
|
||||
///
|
||||
/// ## Returns
|
||||
///
|
||||
/// The count of newline characters in the extracted substring.
|
||||
fn push_trailing(
|
||||
buf: &mut String,
|
||||
line_opt: Option<&Cow<'_, str>>,
|
||||
lo: &Loc,
|
||||
hi_opt: Option<&Loc>,
|
||||
) -> usize {
|
||||
let mut line_count = 0;
|
||||
// Convert `CharPos` to `usize`, as `CharPos` is character offset
|
||||
// Extract low index and high index
|
||||
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) {
|
||||
// Get high index while account for rare unicode and emoji with char_indices
|
||||
let hi_opt = hi_opt.and_then(|hi| line.char_indices().map(|(i, _)| i).nth(hi));
|
||||
match hi_opt {
|
||||
// If high index exist, take string from low to high index
|
||||
Some(hi) if hi > lo => {
|
||||
// count how many '\n' exist
|
||||
line_count = line[lo..hi].matches('\n').count();
|
||||
buf.push_str(&line[lo..hi])
|
||||
}
|
||||
Some(_) => (),
|
||||
// If high index absence, take string from low index till end string.len
|
||||
None => {
|
||||
// count how many '\n' exist
|
||||
line_count = line[lo..].matches('\n').count();
|
||||
buf.push_str(&line[lo..])
|
||||
}
|
||||
}
|
||||
}
|
||||
// If high index is None
|
||||
if hi_opt.is_none() {
|
||||
buf.push('\n');
|
||||
}
|
||||
}
|
||||
line_count
|
||||
}
|
||||
|
||||
assert!(!self.substitutions.is_empty());
|
||||
|
||||
self.substitutions
|
||||
.iter()
|
||||
.filter(|subst| {
|
||||
// Suggestions coming from macros can have malformed spans. This is a heavy
|
||||
// handed approach to avoid ICEs by ignoring the suggestion outright.
|
||||
let invalid = subst.parts.iter().any(|item| sm.is_valid_span(item.span).is_err());
|
||||
if invalid {
|
||||
debug!("splice_lines: suggestion contains an invalid span: {:?}", subst);
|
||||
}
|
||||
!invalid
|
||||
})
|
||||
.cloned()
|
||||
.filter_map(|mut substitution| {
|
||||
// Assumption: all spans are in the same file, and all spans
|
||||
// are disjoint. Sort in ascending order.
|
||||
substitution.parts.sort_by_key(|part| part.span.lo());
|
||||
|
||||
// Find the bounding span.
|
||||
let lo = substitution.parts.iter().map(|part| part.span.lo()).min()?;
|
||||
let hi = substitution.parts.iter().map(|part| part.span.hi()).max()?;
|
||||
let bounding_span = Span::with_root_ctxt(lo, hi);
|
||||
// The different spans might belong to different contexts, if so ignore suggestion.
|
||||
let lines = sm.span_to_lines(bounding_span).ok()?;
|
||||
assert!(!lines.lines.is_empty() || bounding_span.is_dummy());
|
||||
|
||||
// We can't splice anything if the source is unavailable.
|
||||
if !sm.ensure_source_file_source_present(&lines.file) {
|
||||
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)
|
||||
// - 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 sf = &lines.file;
|
||||
let mut prev_hi = sm.lookup_char_pos(bounding_span.lo());
|
||||
prev_hi.col = CharPos::from_usize(0);
|
||||
let mut prev_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;
|
||||
let mut confusion_type = ConfusionType::None;
|
||||
|
||||
let trimmed_parts = substitution
|
||||
.parts
|
||||
.into_iter()
|
||||
// If this is a replacement of, e.g. `"a"` into `"ab"`, adjust the
|
||||
// suggestion and snippet to look as if we just suggested to add
|
||||
// `"b"`, which is typically much easier for the user to understand.
|
||||
.map(|part| part.trim_trivial_replacements(sm))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for part in &trimmed_parts {
|
||||
let part_confusion = detect_confusion_type(sm, &part.snippet, part.span);
|
||||
confusion_type = confusion_type.combine(part_confusion);
|
||||
let cur_lo = sm.lookup_char_pos(part.span.lo());
|
||||
if prev_hi.line == cur_lo.line {
|
||||
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 {
|
||||
acc = 0;
|
||||
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) {
|
||||
let end = match cur_line.char_indices().nth(cur_lo.col.to_usize()) {
|
||||
Some((i, _)) => i,
|
||||
None => cur_line.len(),
|
||||
};
|
||||
buf.push_str(&cur_line[..end]);
|
||||
}
|
||||
}
|
||||
// Add a whole line highlight per line in the snippet.
|
||||
let len: isize = part
|
||||
.snippet
|
||||
.split('\n')
|
||||
.next()
|
||||
.unwrap_or(&part.snippet)
|
||||
.chars()
|
||||
.map(|c| match c {
|
||||
'\t' => 4,
|
||||
_ => 1,
|
||||
})
|
||||
.sum();
|
||||
if !is_different(sm, &part.snippet, part.span) {
|
||||
// Account for cases where we are suggesting the same code that's already
|
||||
// there. This shouldn't happen often, but in some cases for multipart
|
||||
// suggestions it's much easier to handle it here than in the origin.
|
||||
} else {
|
||||
line_highlight.push(SubstitutionHighlight {
|
||||
start: (cur_lo.col.0 as isize + acc) as usize,
|
||||
end: (cur_lo.col.0 as isize + acc + len) as usize,
|
||||
});
|
||||
}
|
||||
buf.push_str(&part.snippet);
|
||||
let cur_hi = sm.lookup_char_pos(part.span.hi());
|
||||
// Account for the difference between the width of the current code and the
|
||||
// snippet being suggested, so that the *later* suggestions are correctly
|
||||
// aligned on the screen. Note that cur_hi and cur_lo can be on different
|
||||
// lines, so cur_hi.col can be smaller than cur_lo.col
|
||||
acc += len - (cur_hi.col.0 as isize - cur_lo.col.0 as isize);
|
||||
prev_hi = cur_hi;
|
||||
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));
|
||||
let end: usize = line
|
||||
.chars()
|
||||
.map(|c| match c {
|
||||
'\t' => 4,
|
||||
_ => 1,
|
||||
})
|
||||
.sum();
|
||||
line_highlight.push(SubstitutionHighlight { start: 0, end });
|
||||
}
|
||||
}
|
||||
highlights.push(std::mem::take(&mut line_highlight));
|
||||
// if the replacement already ends with a newline, don't print the next line
|
||||
if !buf.ends_with('\n') {
|
||||
push_trailing(&mut buf, prev_line.as_ref(), &prev_hi, None);
|
||||
}
|
||||
// remove trailing newlines
|
||||
while buf.ends_with('\n') {
|
||||
buf.pop();
|
||||
}
|
||||
if highlights.iter().all(|parts| parts.is_empty()) {
|
||||
None
|
||||
} else {
|
||||
Some((buf, trimmed_parts, highlights, confusion_type))
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Signifies that the compiler died with an explicit call to `.bug`
|
||||
/// or `.span_bug` rather than a failed assertion, etc.
|
||||
pub struct ExplicitBug;
|
||||
|
|
@ -1961,15 +1698,6 @@ impl Level {
|
|||
pub fn is_failure_note(&self) -> bool {
|
||||
matches!(*self, FailureNote)
|
||||
}
|
||||
|
||||
// Can this level be used in a subdiagnostic message?
|
||||
fn can_be_subdiag(&self) -> bool {
|
||||
match self {
|
||||
Bug | DelayedBug | Fatal | Error | ForceWarning | FailureNote | Allow | Expect => false,
|
||||
|
||||
Warning | Note | Help | OnceNote | OnceHelp => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoDiagArg for Level {
|
||||
|
|
@ -1978,6 +1706,24 @@ impl IntoDiagArg for Level {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)]
|
||||
pub enum Style {
|
||||
MainHeaderMsg,
|
||||
HeaderMsg,
|
||||
LineAndColumn,
|
||||
LineNumber,
|
||||
Quotation,
|
||||
UnderlinePrimary,
|
||||
UnderlineSecondary,
|
||||
LabelPrimary,
|
||||
LabelSecondary,
|
||||
NoStyle,
|
||||
Level(Level),
|
||||
Highlight,
|
||||
Addition,
|
||||
Removal,
|
||||
}
|
||||
|
||||
// FIXME(eddyb) this doesn't belong here AFAICT, should be moved to callsite.
|
||||
pub fn elided_lifetime_in_path_suggestion(
|
||||
source_map: &SourceMap,
|
||||
|
|
|
|||
|
|
@ -1,214 +0,0 @@
|
|||
// Code for annotating snippets.
|
||||
|
||||
use rustc_macros::{Decodable, Encodable};
|
||||
|
||||
use crate::{Level, Loc};
|
||||
|
||||
#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub(crate) struct Line {
|
||||
pub line_index: usize,
|
||||
pub annotations: Vec<Annotation>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Default)]
|
||||
pub(crate) struct AnnotationColumn {
|
||||
/// the (0-indexed) column for *display* purposes, counted in characters, not utf-8 bytes
|
||||
pub display: usize,
|
||||
/// the (0-indexed) column in the file, counted in characters, not utf-8 bytes.
|
||||
///
|
||||
/// this may be different from `self.display`,
|
||||
/// e.g. if the file contains hard tabs, because we convert tabs to spaces for error messages.
|
||||
///
|
||||
/// for example:
|
||||
/// ```text
|
||||
/// (hard tab)hello
|
||||
/// ^ this is display column 4, but file column 1
|
||||
/// ```
|
||||
///
|
||||
/// we want to keep around the correct file offset so that column numbers in error messages
|
||||
/// are correct. (motivated by <https://github.com/rust-lang/rust/issues/109537>)
|
||||
pub file: usize,
|
||||
}
|
||||
|
||||
impl AnnotationColumn {
|
||||
pub(crate) fn from_loc(loc: &Loc) -> AnnotationColumn {
|
||||
AnnotationColumn { display: loc.col_display, file: loc.col.0 }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub(crate) struct MultilineAnnotation {
|
||||
pub depth: usize,
|
||||
pub line_start: usize,
|
||||
pub line_end: usize,
|
||||
pub start_col: AnnotationColumn,
|
||||
pub end_col: AnnotationColumn,
|
||||
pub is_primary: bool,
|
||||
pub label: Option<String>,
|
||||
pub overlaps_exactly: bool,
|
||||
}
|
||||
|
||||
impl MultilineAnnotation {
|
||||
pub(crate) fn increase_depth(&mut self) {
|
||||
self.depth += 1;
|
||||
}
|
||||
|
||||
/// Compare two `MultilineAnnotation`s considering only the `Span` they cover.
|
||||
pub(crate) fn same_span(&self, other: &MultilineAnnotation) -> bool {
|
||||
self.line_start == other.line_start
|
||||
&& self.line_end == other.line_end
|
||||
&& self.start_col == other.start_col
|
||||
&& self.end_col == other.end_col
|
||||
}
|
||||
|
||||
pub(crate) fn as_start(&self) -> Annotation {
|
||||
Annotation {
|
||||
start_col: self.start_col,
|
||||
end_col: AnnotationColumn {
|
||||
// these might not correspond to the same place anymore,
|
||||
// but that's okay for our purposes
|
||||
display: self.start_col.display + 1,
|
||||
file: self.start_col.file + 1,
|
||||
},
|
||||
is_primary: self.is_primary,
|
||||
label: None,
|
||||
annotation_type: AnnotationType::MultilineStart(self.depth),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn as_end(&self) -> Annotation {
|
||||
Annotation {
|
||||
start_col: AnnotationColumn {
|
||||
// these might not correspond to the same place anymore,
|
||||
// but that's okay for our purposes
|
||||
display: self.end_col.display.saturating_sub(1),
|
||||
file: self.end_col.file.saturating_sub(1),
|
||||
},
|
||||
end_col: self.end_col,
|
||||
is_primary: self.is_primary,
|
||||
label: self.label.clone(),
|
||||
annotation_type: AnnotationType::MultilineEnd(self.depth),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn as_line(&self) -> Annotation {
|
||||
Annotation {
|
||||
start_col: Default::default(),
|
||||
end_col: Default::default(),
|
||||
is_primary: self.is_primary,
|
||||
label: None,
|
||||
annotation_type: AnnotationType::MultilineLine(self.depth),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub(crate) enum AnnotationType {
|
||||
/// Annotation under a single line of code
|
||||
Singleline,
|
||||
|
||||
// The Multiline type above is replaced with the following three in order
|
||||
// to reuse the current label drawing code.
|
||||
//
|
||||
// Each of these corresponds to one part of the following diagram:
|
||||
//
|
||||
// x | foo(1 + bar(x,
|
||||
// | _________^ < MultilineStart
|
||||
// x | | y), < MultilineLine
|
||||
// | |______________^ label < MultilineEnd
|
||||
// x | z);
|
||||
/// Annotation marking the first character of a fully shown multiline span
|
||||
MultilineStart(usize),
|
||||
/// Annotation marking the last character of a fully shown multiline span
|
||||
MultilineEnd(usize),
|
||||
/// Line at the left enclosing the lines of a fully shown multiline span
|
||||
// Just a placeholder for the drawing algorithm, to know that it shouldn't skip the first 4
|
||||
// and last 2 lines of code. The actual line is drawn in `emit_message_default` and not in
|
||||
// `draw_multiline_line`.
|
||||
MultilineLine(usize),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub(crate) struct Annotation {
|
||||
/// Start column.
|
||||
/// Note that it is important that this field goes
|
||||
/// first, so that when we sort, we sort orderings by start
|
||||
/// column.
|
||||
pub start_col: AnnotationColumn,
|
||||
|
||||
/// End column within the line (exclusive)
|
||||
pub end_col: AnnotationColumn,
|
||||
|
||||
/// Is this annotation derived from primary span
|
||||
pub is_primary: bool,
|
||||
|
||||
/// Optional label to display adjacent to the annotation.
|
||||
pub label: Option<String>,
|
||||
|
||||
/// Is this a single line, multiline or multiline span minimized down to a
|
||||
/// smaller span.
|
||||
pub annotation_type: AnnotationType,
|
||||
}
|
||||
|
||||
impl Annotation {
|
||||
/// Whether this annotation is a vertical line placeholder.
|
||||
pub(crate) fn is_line(&self) -> bool {
|
||||
matches!(self.annotation_type, AnnotationType::MultilineLine(_))
|
||||
}
|
||||
|
||||
/// Length of this annotation as displayed in the stderr output
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
// Account for usize underflows
|
||||
self.end_col.display.abs_diff(self.start_col.display)
|
||||
}
|
||||
|
||||
pub(crate) fn has_label(&self) -> bool {
|
||||
if let Some(ref label) = self.label {
|
||||
// Consider labels with no text as effectively not being there
|
||||
// to avoid weird output with unnecessary vertical lines, like:
|
||||
//
|
||||
// X | fn foo(x: u32) {
|
||||
// | -------^------
|
||||
// | | |
|
||||
// | |
|
||||
// |
|
||||
//
|
||||
// Note that this would be the complete output users would see.
|
||||
!label.is_empty()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn takes_space(&self) -> bool {
|
||||
// Multiline annotations always have to keep vertical space.
|
||||
matches!(
|
||||
self.annotation_type,
|
||||
AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct StyledString {
|
||||
pub text: String,
|
||||
pub style: Style,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)]
|
||||
pub enum Style {
|
||||
MainHeaderMsg,
|
||||
HeaderMsg,
|
||||
LineAndColumn,
|
||||
LineNumber,
|
||||
Quotation,
|
||||
UnderlinePrimary,
|
||||
UnderlineSecondary,
|
||||
LabelPrimary,
|
||||
LabelSecondary,
|
||||
NoStyle,
|
||||
Level(Level),
|
||||
Highlight,
|
||||
Addition,
|
||||
Removal,
|
||||
}
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
// Code for creating styled buffers
|
||||
|
||||
use crate::snippet::{Style, StyledString};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct StyledBuffer {
|
||||
lines: Vec<Vec<StyledChar>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct StyledChar {
|
||||
chr: char,
|
||||
style: Style,
|
||||
}
|
||||
|
||||
impl StyledChar {
|
||||
const SPACE: Self = StyledChar::new(' ', Style::NoStyle);
|
||||
|
||||
const fn new(chr: char, style: Style) -> Self {
|
||||
StyledChar { chr, style }
|
||||
}
|
||||
}
|
||||
|
||||
impl StyledBuffer {
|
||||
pub(crate) fn new() -> StyledBuffer {
|
||||
StyledBuffer { lines: vec![] }
|
||||
}
|
||||
|
||||
/// Returns content of `StyledBuffer` split by lines and line styles
|
||||
pub(crate) fn render(&self) -> Vec<Vec<StyledString>> {
|
||||
// Tabs are assumed to have been replaced by spaces in calling code.
|
||||
debug_assert!(self.lines.iter().all(|r| !r.iter().any(|sc| sc.chr == '\t')));
|
||||
|
||||
let mut output: Vec<Vec<StyledString>> = vec![];
|
||||
let mut styled_vec: Vec<StyledString> = vec![];
|
||||
|
||||
for styled_line in &self.lines {
|
||||
let mut current_style = Style::NoStyle;
|
||||
let mut current_text = String::new();
|
||||
|
||||
for sc in styled_line {
|
||||
if sc.style != current_style {
|
||||
if !current_text.is_empty() {
|
||||
styled_vec.push(StyledString { text: current_text, style: current_style });
|
||||
}
|
||||
current_style = sc.style;
|
||||
current_text = String::new();
|
||||
}
|
||||
current_text.push(sc.chr);
|
||||
}
|
||||
if !current_text.is_empty() {
|
||||
styled_vec.push(StyledString { text: current_text, style: current_style });
|
||||
}
|
||||
|
||||
// We're done with the row, push and keep going
|
||||
output.push(styled_vec);
|
||||
|
||||
styled_vec = vec![];
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn ensure_lines(&mut self, line: usize) {
|
||||
if line >= self.lines.len() {
|
||||
self.lines.resize(line + 1, Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets `chr` with `style` for given `line`, `col`.
|
||||
/// If `line` does not exist in our buffer, adds empty lines up to the given
|
||||
/// and fills the last line with unstyled whitespace.
|
||||
pub(crate) fn putc(&mut self, line: usize, col: usize, chr: char, style: Style) {
|
||||
self.ensure_lines(line);
|
||||
if col >= self.lines[line].len() {
|
||||
self.lines[line].resize(col + 1, StyledChar::SPACE);
|
||||
}
|
||||
self.lines[line][col] = StyledChar::new(chr, style);
|
||||
}
|
||||
|
||||
/// Sets `string` with `style` for given `line`, starting from `col`.
|
||||
/// If `line` does not exist in our buffer, adds empty lines up to the given
|
||||
/// and fills the last line with unstyled whitespace.
|
||||
pub(crate) fn puts(&mut self, line: usize, col: usize, string: &str, style: Style) {
|
||||
let mut n = col;
|
||||
for c in string.chars() {
|
||||
self.putc(line, n, c, style);
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn replace(&mut self, line: usize, start: usize, end: usize, string: &str) {
|
||||
if start == end {
|
||||
return;
|
||||
}
|
||||
if start > self.lines[line].len() || end > self.lines[line].len() {
|
||||
return;
|
||||
}
|
||||
let _ = self.lines[line].drain(start..(end - string.chars().count()));
|
||||
for (i, c) in string.chars().enumerate() {
|
||||
self.lines[line][start + i] = StyledChar::new(c, Style::LineNumber);
|
||||
}
|
||||
}
|
||||
|
||||
/// For given `line` inserts `string` with `style` before old content of that line,
|
||||
/// adding lines if needed
|
||||
pub(crate) fn prepend(&mut self, line: usize, string: &str, style: Style) {
|
||||
self.ensure_lines(line);
|
||||
let string_len = string.chars().count();
|
||||
|
||||
if !self.lines[line].is_empty() {
|
||||
// Push the old content over to make room for new content
|
||||
for _ in 0..string_len {
|
||||
self.lines[line].insert(0, StyledChar::SPACE);
|
||||
}
|
||||
}
|
||||
|
||||
self.puts(line, 0, string, style);
|
||||
}
|
||||
|
||||
/// For given `line` inserts `string` with `style` after old content of that line,
|
||||
/// adding lines if needed
|
||||
pub(crate) fn append(&mut self, line: usize, string: &str, style: Style) {
|
||||
if line >= self.lines.len() {
|
||||
self.puts(line, 0, string, style);
|
||||
} else {
|
||||
let col = self.lines[line].len();
|
||||
self.puts(line, col, string, style);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn num_lines(&self) -> usize {
|
||||
self.lines.len()
|
||||
}
|
||||
|
||||
/// Set `style` for `line`, `col_start..col_end` range if:
|
||||
/// 1. That line and column range exist in `StyledBuffer`
|
||||
/// 2. `overwrite` is `true` or existing style is `Style::NoStyle` or `Style::Quotation`
|
||||
pub(crate) fn set_style_range(
|
||||
&mut self,
|
||||
line: usize,
|
||||
col_start: usize,
|
||||
col_end: usize,
|
||||
style: Style,
|
||||
overwrite: bool,
|
||||
) {
|
||||
for col in col_start..col_end {
|
||||
self.set_style(line, col, style, overwrite);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set `style` for `line`, `col` if:
|
||||
/// 1. That line and column exist in `StyledBuffer`
|
||||
/// 2. `overwrite` is `true` or existing style is `Style::NoStyle` or `Style::Quotation`
|
||||
fn set_style(&mut self, line: usize, col: usize, style: Style, overwrite: bool) {
|
||||
if let Some(ref mut line) = self.lines.get_mut(line)
|
||||
&& let Some(StyledChar { style: s, .. }) = line.get_mut(col)
|
||||
&& (overwrite || matches!(s, Style::NoStyle | Style::Quotation))
|
||||
{
|
||||
*s = style;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,8 +7,7 @@ pub use rustc_error_messages::{FluentArgs, LazyFallbackBundle};
|
|||
use tracing::{debug, trace};
|
||||
|
||||
use crate::error::{TranslateError, TranslateErrorKind};
|
||||
use crate::snippet::Style;
|
||||
use crate::{DiagArg, DiagMessage, FluentBundle};
|
||||
use crate::{DiagArg, DiagMessage, FluentBundle, Style};
|
||||
|
||||
/// Convert diagnostic arguments (a rustc internal type that exists to implement
|
||||
/// `Encodable`/`Decodable`) into `FluentArgs` which is necessary to perform translation.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue