hcl: highlight the subject when printing a diagnostic source snippet

In complex expressions it can be hard to determine which portion is
relevant when we print a diagnostic message. To address this, when color
is enabled we bold and underline the "subject" portion of the source code,
which then makes it stand out within the full lines of code we print
in the snippet.
This commit is contained in:
Martin Atkins 2018-01-14 12:25:35 -08:00
parent 14cfe59a52
commit 883a81b490
1 changed files with 39 additions and 10 deletions

View File

@ -42,7 +42,7 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error {
return errors.New("nil diagnostic")
}
var colorCode, resetCode string
var colorCode, highlightCode, resetCode string
if w.color {
switch diag.Severity {
case DiagError:
@ -51,6 +51,7 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error {
colorCode = "\x1b[33m"
}
resetCode = "\x1b[0m"
highlightCode = "\x1b[1;4m"
}
var severityStr string
@ -67,18 +68,31 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error {
fmt.Fprintf(w.wr, "%s%s%s: %s\n\n", colorCode, severityStr, resetCode, diag.Summary)
if diag.Subject != nil {
snipRange := *diag.Subject
highlightRange := snipRange
if diag.Context != nil {
// Show enough of the source code to include both the subject
// and context ranges, which overlap in all reasonable
// situations.
snipRange = RangeOver(snipRange, *diag.Context)
}
// We can't illustrate an empty range, so we'll turn such ranges into
// single-character ranges, which might not be totally valid (may point
// off the end of a line, or off the end of the file) but are good
// enough for the bounds checks we do below.
if snipRange.Empty() {
snipRange.End.Byte++
snipRange.End.Column++
}
if highlightRange.Empty() {
highlightRange.End.Byte++
highlightRange.End.Column++
}
file := w.files[diag.Subject.Filename]
if file == nil || file.Bytes == nil || diag.Subject.Empty() {
if file == nil || file.Bytes == nil {
fmt.Fprintf(w.wr, " on %s line %d:\n (source code not available)\n\n", diag.Subject.Filename, diag.Subject.Start.Line)
} else {
snipRange := *diag.Subject
if diag.Context != nil {
// Show enough of the source code to include both the subject
// and context ranges, which overlap in all reasonable
// situations.
snipRange = RangeOver(snipRange, *diag.Context)
}
var contextLine string
if diag.Subject != nil {
@ -99,7 +113,22 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error {
continue
}
fmt.Fprintf(w.wr, "%4d: %s\n", lineRange.Start.Line, sc.Bytes())
beforeRange, highlightedRange, afterRange := lineRange.PartitionAround(highlightRange)
if highlightedRange.Empty() {
fmt.Fprintf(w.wr, "%4d: %s\n", lineRange.Start.Line, sc.Bytes())
} else {
before := beforeRange.SliceBytes(src)
highlighted := highlightedRange.SliceBytes(src)
after := afterRange.SliceBytes(src)
fmt.Fprintf(
w.wr, "%4d: %s%s%s%s%s\n",
lineRange.Start.Line,
before,
highlightCode, highlighted, resetCode,
after,
)
}
}
w.wr.Write([]byte{'\n'})