mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-11-10 12:28:25 +00:00
Extend and adjust the terminal API for the diagnostics rendering (#7993)
The rendering we're trying to implement based on `toolchain/docs/diagnostics_rendering.md` needs some features the initial terminal API didn't support: - Heavy stroke weight on lines - Vertical lines that cross earlier lines without joining them - Wrapping that keeps quoted code whole This PR adds support for these and removes the two `buffer.h` TODOs that described them. This PR also tries to improve the ASCII fallback quality a bit by using `^`-lines for "heavy" weight, and using `.` and `'` when drawing tees. Still not perfect of course, but seems reasonable given the constraints of ASCII rendering. Assisted-by: Claude Code
This commit is contained in:
1 parent
39e69f0b4f
commit
fda41783e5
5 files changed
+561
-128
No files matched your search
+193
-77
@@ -7,14 +7,13 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "common/check.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/ADT/Sequence.h"
|
||||
#include "llvm/ADT/SmallString.h"
|
||||
#include "llvm/Support/ConvertUTF.h"
|
||||
#include "llvm/Support/Unicode.h"
|
||||
|
||||
namespace Carbon::Terminal {
|
||||
|
||||
@@ -23,59 +22,77 @@ namespace Carbon::Terminal {
|
||||
// let a single column of output carry unbounded bytes.
|
||||
static constexpr size_t MaxCombiningBytes = 32;
|
||||
|
||||
// Glyphs for every combination of line directions, indexed by the direction
|
||||
// bits.
|
||||
static constexpr std::array<char32_t, 16> Utf8LineGlyphs = {
|
||||
U'·', // (none): a line between one center and itself, which is a point
|
||||
U'╴', // left
|
||||
U'╶', // right
|
||||
U'─', // left, right
|
||||
U'╵', // up
|
||||
U'╯', // left, up
|
||||
U'╰', // right, up
|
||||
U'┴', // left, right, up
|
||||
U'╷', // down
|
||||
U'╮', // left, down
|
||||
U'╭', // right, down
|
||||
U'┬', // left, right, down
|
||||
U'│', // up, down
|
||||
U'┤', // left, up, down
|
||||
U'├', // right, up, down
|
||||
U'┼', // left, right, up, down
|
||||
};
|
||||
|
||||
// The ASCII stand-ins. Each keeps the axis its line runs through, which leaves
|
||||
// `+` meaning a crossing and nothing else:
|
||||
// Glyphs for every combination of line directions and weights, indexed by the
|
||||
// heavy bits and then the direction bits.
|
||||
//
|
||||
// - Running through horizontally is `-`, vertically `|`, and both ways `+`.
|
||||
// - A tee keeps its through-stroke and leaves the branch to what is drawn
|
||||
// beside it: the dashes either side of a `|` are what `├` and `┤` reach, and
|
||||
// the line under a `-` is what a `┬` reaches. Drawing a tee as `+` reads as
|
||||
// the crossing it is not.
|
||||
// The first row is the all-light set, whose corners are the rounded forms; a
|
||||
// corner with any heavy line is square, since Unicode has no rounded heavy
|
||||
// corner. The other rows match the Unicode character names, which list the
|
||||
// weight of the line in each direction. `DrawLine` never sets a heavy bit
|
||||
// without its direction bit, and those entries repeat the glyph with the
|
||||
// direction present.
|
||||
static constexpr std::array<std::u32string_view, 16> Utf8LineGlyphs = {
|
||||
U"·╴╶─╵╯╰┴╷╮╭┬│┤├┼", // All light.
|
||||
U"╸╸╾╾┙┙┵┵┑┑┭┭┥┥┽┽", // Heavy left.
|
||||
U"╺╼╺╼┕┶┕┶┍┮┍┮┝┾┝┾", // Heavy right.
|
||||
U"━━━━┷┷┷┷┯┯┯┯┿┿┿┿", // Heavy left and right.
|
||||
U"╹┚┖┸╹┚┖┸╿┦┞╀╿┦┞╀", // Heavy up.
|
||||
U"┛┛┹┹┛┛┹┹┩┩╃╃┩┩╃╃", // Heavy left and up.
|
||||
U"┗┺┗┺┗┺┗┺┡╄┡╄┡╄┡╄", // Heavy right and up.
|
||||
U"┻┻┻┻┻┻┻┻╇╇╇╇╇╇╇╇", // Heavy left, right, and up.
|
||||
U"╻┒┎┰╽┧┟╁╻┒┎┰╽┧┟╁", // Heavy down.
|
||||
U"┓┓┱┱┪┪╅╅┓┓┱┱┪┪╅╅", // Heavy left and down.
|
||||
U"┏┲┏┲┢╆┢╆┏┲┏┲┢╆┢╆", // Heavy right and down.
|
||||
U"┳┳┳┳╈╈╈╈┳┳┳┳╈╈╈╈", // Heavy left, right, and down.
|
||||
U"┃┨┠╂┃┨┠╂┃┨┠╂┃┨┠╂", // Heavy up and down.
|
||||
U"┫┫╉╉┫┫╉╉┫┫╉╉┫┫╉╉", // Heavy left, up, and down.
|
||||
U"┣╊┣╊┣╊┣╊┣╊┣╊┣╊┣╊", // Heavy right, up, and down.
|
||||
U"╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋╋", // All heavy.
|
||||
};
|
||||
// Every row has a glyph for each set of direction bits.
|
||||
static_assert(std::all_of(Utf8LineGlyphs.begin(), Utf8LineGlyphs.end(),
|
||||
[](std::u32string_view glyphs) {
|
||||
return glyphs.size() == 16;
|
||||
}));
|
||||
|
||||
// The ASCII glyphs, which use `+` only for a crossing:
|
||||
//
|
||||
// - A line running through horizontally is `-`, vertically `|`, and both ways
|
||||
// `+`.
|
||||
// - A corner is `.` where its line leaves downward and `'` where it arrives
|
||||
// from above, which is where those characters sit in their cells.
|
||||
// - A tee where a line leaves a vertical line sideways, `├` or `┤`, is `|`,
|
||||
// and the dash drawn beside it shows the line leaving. A tee where a line
|
||||
// leaves a horizontal line upward or downward, `┴` or `┬`, is the corner
|
||||
// glyph for that direction: the dashes either side show the horizontal line,
|
||||
// and this cell must show the line leaving it, because the rest of that line
|
||||
// can be rows away with only gaps between. `+` would look like a crossing.
|
||||
// - A point, a line between one center and itself, is `.`.
|
||||
//
|
||||
// What a diagnostic draws is then still told apart: the rule closing a snippet
|
||||
// from the one separating two, and the anchor opening a diagnostic from the one
|
||||
// carrying it on.
|
||||
// What a diagnostic draws is then still told apart: the line closing a snippet
|
||||
// from the one separating two, and the line opening a diagnostic from the one
|
||||
// continuing it.
|
||||
//
|
||||
// A cell with a heavy line leaving it is `^` instead, under the rule
|
||||
// `LineWeight` describes. That covers the top cell of a heavy vertical line, so
|
||||
// the line is `^` above `|`.
|
||||
static constexpr std::array<char32_t, 16> AsciiLineGlyphs = {
|
||||
U'.', // (none): a point
|
||||
U'-', // left
|
||||
U'-', // right
|
||||
U'-', // left, right
|
||||
U'|', // up
|
||||
U'\'', // left, up
|
||||
U'\'', // right, up
|
||||
U'-', // left, right, up
|
||||
U'|', // down
|
||||
U'.', // left, down
|
||||
U'.', // right, down
|
||||
U'-', // left, right, down
|
||||
U'|', // up, down
|
||||
U'|', // left, up, down
|
||||
U'|', // right, up, down
|
||||
U'+', // left, right, up, down
|
||||
U'.', // None: a point.
|
||||
U'-', // Left.
|
||||
U'-', // Right.
|
||||
U'-', // Left and right.
|
||||
U'|', // Up.
|
||||
U'\'', // Left and up.
|
||||
U'\'', // Right and up.
|
||||
U'\'', // Left, right, and up.
|
||||
U'|', // Down.
|
||||
U'.', // Left and down.
|
||||
U'.', // Right and down.
|
||||
U'.', // Left, right, and down.
|
||||
U'|', // Up and down.
|
||||
U'|', // Left, up, and down.
|
||||
U'|', // Right, up, and down.
|
||||
U'+', // Left, right, up, and down.
|
||||
};
|
||||
|
||||
// Returns the next tab stop after `x` on a line whose stops are `tab_width`
|
||||
@@ -241,13 +258,8 @@ auto Buffer::PlaceCodePoint(int x, int y, char32_t code_point,
|
||||
return x + width;
|
||||
}
|
||||
|
||||
// Returns the glyphs a cell's directions are read from.
|
||||
static auto LineGlyphs(Charset charset) -> const std::array<char32_t, 16>& {
|
||||
return charset == Charset::Utf8 ? Utf8LineGlyphs : AsciiLineGlyphs;
|
||||
}
|
||||
|
||||
auto Buffer::DrawLine(int x, int y, uint8_t directions, const Style& style)
|
||||
-> void {
|
||||
auto Buffer::DrawLine(int x, int y, uint8_t directions, LineWeight weight,
|
||||
const Style& style) -> void {
|
||||
CARBON_DCHECK(directions <= LineDirections,
|
||||
"Direction bits {0} name no glyph.", directions);
|
||||
EnsureColumn(x);
|
||||
@@ -262,7 +274,22 @@ auto Buffer::DrawLine(int x, int y, uint8_t directions, const Style& style)
|
||||
|
||||
Cell& cell = CellAt(x, y);
|
||||
cell.lines = existing | directions | LineCell;
|
||||
cell.code_point = LineGlyphs(metrics_.charset())[cell.lines & LineDirections];
|
||||
cell.heavy_lines &= ~directions;
|
||||
if (weight == LineWeight::Heavy) {
|
||||
cell.heavy_lines |= directions;
|
||||
}
|
||||
uint8_t cell_directions = cell.lines & LineDirections;
|
||||
bool line_enters_from_above = (cell_directions & LineUp) != 0;
|
||||
bool light_line_leaves_downward =
|
||||
(cell_directions & LineDown) != 0 && (cell.heavy_lines & LineDown) == 0;
|
||||
if (metrics_.charset() == Charset::Utf8) {
|
||||
cell.code_point = Utf8LineGlyphs[cell.heavy_lines][cell_directions];
|
||||
} else if (cell.heavy_lines != 0 && !line_enters_from_above &&
|
||||
!light_line_leaves_downward) {
|
||||
cell.code_point = U'^';
|
||||
} else {
|
||||
cell.code_point = AsciiLineGlyphs[cell_directions];
|
||||
}
|
||||
cell.style = style;
|
||||
}
|
||||
|
||||
@@ -279,7 +306,8 @@ static auto CheckLineFits(int position, int length, int limit) -> void {
|
||||
}
|
||||
|
||||
auto Buffer::DrawHorizontalLine(int x, int y, int length, const Style& style,
|
||||
LineEnd start, LineEnd end) -> DrawEnd {
|
||||
LineEnd start, LineEnd end, LineWeight weight)
|
||||
-> DrawEnd {
|
||||
CheckOrigin(x, y);
|
||||
CheckLineFits(x, length, columns_);
|
||||
for (int i : llvm::seq(length)) {
|
||||
@@ -289,20 +317,76 @@ auto Buffer::DrawHorizontalLine(int x, int y, int length, const Style& style,
|
||||
uint8_t directions =
|
||||
(i > 0 || start == LineEnd::Edge ? LineLeft : 0) |
|
||||
(i + 1 < length || end == LineEnd::Edge ? LineRight : 0);
|
||||
DrawLine(x + i, y, directions, style);
|
||||
DrawLine(x + i, y, directions, weight, style);
|
||||
}
|
||||
return {.x = x + length, .y = y};
|
||||
}
|
||||
|
||||
auto Buffer::DrawVerticalLine(int x, int y, int length, const Style& style,
|
||||
LineEnd start, LineEnd end) -> DrawEnd {
|
||||
LineEnd start, LineEnd end, LineWeight weight)
|
||||
-> DrawEnd {
|
||||
CheckOrigin(x, y);
|
||||
CheckLineFits(y, length, MaxRows);
|
||||
for (int i : llvm::seq(length)) {
|
||||
uint8_t directions =
|
||||
(i > 0 || start == LineEnd::Edge ? LineUp : 0) |
|
||||
(i + 1 < length || end == LineEnd::Edge ? LineDown : 0);
|
||||
DrawLine(x, y + i, directions, style);
|
||||
DrawLine(x, y + i, directions, weight, style);
|
||||
}
|
||||
return {.x = x, .y = y + length};
|
||||
}
|
||||
|
||||
auto Buffer::HoldsText(int x, int y) const -> bool {
|
||||
if (x < 0 || x >= width_ || y >= height()) {
|
||||
return false;
|
||||
}
|
||||
const Cell& cell = CellAt(x, y);
|
||||
if ((cell.lines & LineCell) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (cell.code_point != U' ' || cell.is_continuation) {
|
||||
return true;
|
||||
}
|
||||
return !combining_marks_.empty() &&
|
||||
combining_marks_.contains(CellIndex(x, y));
|
||||
}
|
||||
|
||||
auto Buffer::DrawCrossingVerticalLine(int x, int y, int length,
|
||||
const Style& style, LineEnd start)
|
||||
-> DrawEnd {
|
||||
CheckOrigin(x, y);
|
||||
CheckLineFits(y, length, MaxRows);
|
||||
|
||||
for (int i : llvm::seq(length)) {
|
||||
int cell_y = y + i;
|
||||
bool is_center_start = i == 0 && start == LineEnd::Center;
|
||||
uint8_t directions = is_center_start ? LineDown : (LineUp | LineDown);
|
||||
if (cell_y >= height()) {
|
||||
// Rows below the buffer hold nothing drawn earlier.
|
||||
DrawLine(x, cell_y, directions, LineWeight::Light, style);
|
||||
continue;
|
||||
}
|
||||
const Cell& cell = CellAt(x, cell_y);
|
||||
if (is_center_start) {
|
||||
// A line already leaving this cell downward is left as it is, keeping
|
||||
// its weight. Otherwise this line joins the cell.
|
||||
if ((cell.lines & LineDown) == 0) {
|
||||
DrawLine(x, cell_y, LineDown, LineWeight::Light, style);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ((cell.lines & LineCell) != 0) {
|
||||
// An earlier line keeps its cell: a crossed line stays unbroken, and a
|
||||
// line already running down this column keeps its weight.
|
||||
continue;
|
||||
}
|
||||
// Skip text, and blank cells beside text, where a line could be mistaken
|
||||
// for part of a word.
|
||||
if (HoldsText(x, cell_y) || HoldsText(x - 1, cell_y) ||
|
||||
HoldsText(x + 1, cell_y)) {
|
||||
continue;
|
||||
}
|
||||
DrawLine(x, cell_y, directions, LineWeight::Light, style);
|
||||
}
|
||||
return {.x = x, .y = y + length};
|
||||
}
|
||||
@@ -377,18 +461,49 @@ auto Buffer::MeasureText(int x, int y, int margin, llvm::StringRef text) const
|
||||
|
||||
// Returns whether wrapped text can be broken at `c`.
|
||||
//
|
||||
// This is the one definition of where wrapping may introduce a break, so that
|
||||
// measuring what text wraps into and drawing it wrapped agree about it.
|
||||
// Carriage returns count so that a CRLF ending is whitespace rather than part
|
||||
// of the word before it; what becomes of the `\r` is then up to the drawing.
|
||||
static constexpr auto IsWrapBreak(char c) -> bool {
|
||||
return c == ' ' || c == '\t' || c == '\r';
|
||||
}
|
||||
|
||||
// Removes the word at the front of `text` and returns it: everything up to the
|
||||
// next break or newline, except that with `backticks_are_quotes`, a pair of
|
||||
// backticks on one line keeps everything between them in the word. When
|
||||
// `text` starts with a break or a newline, returns an empty word and leaves
|
||||
// `text` unchanged, so the caller has to step past that character itself.
|
||||
//
|
||||
// With `IsWrapBreak` this is the one definition of where wrapping may
|
||||
// introduce a break, so that measuring what text wraps into and drawing it
|
||||
// wrapped agree about it.
|
||||
static auto TakeWord(llvm::StringRef& text, bool backticks_are_quotes)
|
||||
-> llvm::StringRef {
|
||||
size_t end = 0;
|
||||
while (end < text.size()) {
|
||||
char c = text[end];
|
||||
if (c == '\n' || IsWrapBreak(c)) {
|
||||
break;
|
||||
}
|
||||
if (backticks_are_quotes && c == '`') {
|
||||
size_t close = text.find('`', end + 1);
|
||||
if (close != llvm::StringRef::npos &&
|
||||
text.slice(end + 1, close).find_first_of("\t\n\r") ==
|
||||
llvm::StringRef::npos) {
|
||||
end = close + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
++end;
|
||||
}
|
||||
llvm::StringRef word = text.take_front(end);
|
||||
text = text.drop_front(end);
|
||||
return word;
|
||||
}
|
||||
|
||||
template <typename PlaceFn>
|
||||
auto Buffer::WalkWrappedText(int x, int y, int margin, int max_width,
|
||||
llvm::StringRef text, PlaceFn place) const
|
||||
-> DrawEnd {
|
||||
llvm::StringRef text, bool backticks_are_quotes,
|
||||
PlaceFn place) const -> DrawEnd {
|
||||
CheckTextSize(text);
|
||||
// The block runs from the margin to `margin + max_width`, lies within the
|
||||
// buffer, and holds the column the text starts in, which is every bound on
|
||||
@@ -452,9 +567,7 @@ auto Buffer::WalkWrappedText(int x, int y, int margin, int max_width,
|
||||
continue;
|
||||
}
|
||||
|
||||
llvm::StringRef word =
|
||||
text.take_until([](char c) { return c == '\n' || IsWrapBreak(c); });
|
||||
text = text.drop_front(word.size());
|
||||
llvm::StringRef word = TakeWord(text, backticks_are_quotes);
|
||||
|
||||
// Move a word that doesn't fit down to the next row, which minimizes the
|
||||
// overhang when it doesn't fit there either. The word is drawn into the row
|
||||
@@ -474,9 +587,9 @@ auto Buffer::WalkWrappedText(int x, int y, int margin, int max_width,
|
||||
}
|
||||
|
||||
auto Buffer::DrawWrappedText(int x, int y, int margin, int max_width,
|
||||
llvm::StringRef text, const Style& style)
|
||||
-> DrawEnd {
|
||||
return WalkWrappedText(x, y, margin, max_width, text,
|
||||
llvm::StringRef text, const Style& style,
|
||||
bool backticks_are_quotes) -> DrawEnd {
|
||||
return WalkWrappedText(x, y, margin, max_width, text, backticks_are_quotes,
|
||||
[&](int cur_x, int cur_y, char32_t code_point) {
|
||||
return PlaceCodePoint(cur_x, cur_y, code_point,
|
||||
style);
|
||||
@@ -484,20 +597,23 @@ auto Buffer::DrawWrappedText(int x, int y, int margin, int max_width,
|
||||
}
|
||||
|
||||
auto Buffer::MeasureWrappedText(int x, int y, int margin, int max_width,
|
||||
llvm::StringRef text) const -> DrawEnd {
|
||||
return WalkWrappedText(x, y, margin, max_width, text,
|
||||
llvm::StringRef text,
|
||||
bool backticks_are_quotes) const -> DrawEnd {
|
||||
return WalkWrappedText(x, y, margin, max_width, text, backticks_are_quotes,
|
||||
[&](int cur_x, int /*cur_y*/, char32_t code_point) {
|
||||
return cur_x + metrics_.CodePointWidth(code_point);
|
||||
});
|
||||
}
|
||||
|
||||
auto Buffer::MeasureWrapWidth(llvm::StringRef text) const -> int {
|
||||
auto Buffer::MeasureWrapWidth(llvm::StringRef text,
|
||||
bool backticks_are_quotes) const -> int {
|
||||
int width = 0;
|
||||
while (!text.empty()) {
|
||||
llvm::StringRef word =
|
||||
text.take_until([](char c) { return c == '\n' || IsWrapBreak(c); });
|
||||
llvm::StringRef word = TakeWord(text, backticks_are_quotes);
|
||||
width = std::max(width, metrics_.Width(word));
|
||||
text = text.drop_front(std::max<size_t>(word.size(), 1));
|
||||
if (word.empty()) {
|
||||
text = text.drop_front();
|
||||
}
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
+71
-20
@@ -47,6 +47,20 @@ enum class LineEnd : int8_t {
|
||||
Edge,
|
||||
};
|
||||
|
||||
// The stroke weight a line is drawn with.
|
||||
//
|
||||
// Weight is recorded for each direction a line leaves a cell in, and Unicode
|
||||
// has a glyph for every mix of light and heavy lines meeting in a cell, so
|
||||
// where a heavy line meets a light one each keeps its weight. ASCII has no
|
||||
// weights: a cell with a heavy line leaving it is `^`, the underline `rustc`
|
||||
// uses for a primary range, unless a line enters it from above or a light line
|
||||
// leaves it downward. Those cells keep their light glyph, so the junction or
|
||||
// corner stays visible.
|
||||
enum class LineWeight : int8_t {
|
||||
Light,
|
||||
Heavy,
|
||||
};
|
||||
|
||||
// A grid of styled cells staged for rendering to a terminal.
|
||||
//
|
||||
// Coordinates are 0-based with (0, 0) at the top left, `x` counting terminal
|
||||
@@ -247,14 +261,17 @@ class Buffer {
|
||||
// The block and the origin are checked as drawing checks them, so measuring
|
||||
// answers only for arguments drawing would accept.
|
||||
auto MeasureWrappedText(int x, int y, int margin, int max_width,
|
||||
llvm::StringRef text) const -> DrawEnd;
|
||||
llvm::StringRef text,
|
||||
bool backticks_are_quotes = false) const -> DrawEnd;
|
||||
|
||||
// Returns the fewest columns `text` wraps into without overhanging them,
|
||||
// which is the width of its widest word since wrapping never breaks one.
|
||||
// `backticks_are_quotes` is as for `DrawWrappedText`.
|
||||
//
|
||||
// Wrapping into fewer columns still draws everything; the excess overhangs.
|
||||
// So this is a layout preference rather than a minimum.
|
||||
auto MeasureWrapWidth(llvm::StringRef text) const -> int;
|
||||
auto MeasureWrapWidth(llvm::StringRef text,
|
||||
bool backticks_are_quotes = false) const -> int;
|
||||
|
||||
// Draws `code_point` at (x, y), which must be a non-negative column and a row
|
||||
// inside `MaxRows`, widening the buffer and adding rows as needed to reach
|
||||
@@ -286,22 +303,42 @@ class Buffer {
|
||||
// containing `-` or `+` is never redrawn as line art.
|
||||
//
|
||||
// A cell's style is whatever was drawn there last, so crossing lines of
|
||||
// different styles do depend on order.
|
||||
//
|
||||
// TODO: Every line is drawn with one stroke, and a line meeting another
|
||||
// joins it. Diagnostic rendering needs a heavier stroke, to mark the source
|
||||
// a message is about, and a line that passes another without connecting, to
|
||||
// reach a label past the marks between them. Add a weight to these calls
|
||||
// and a crossing form of `DrawVerticalLine`.
|
||||
// different styles depend on order. Weight is kept per direction instead:
|
||||
// each direction a line leaves a cell in has the weight of the line drawn
|
||||
// there last, so crossing lines keep their own weights in either order, and
|
||||
// a heavy line meeting a light one forms a mixed junction.
|
||||
auto DrawHorizontalLine(int x, int y, int length, const Style& style,
|
||||
LineEnd start = LineEnd::Center,
|
||||
LineEnd end = LineEnd::Center) -> DrawEnd;
|
||||
LineEnd end = LineEnd::Center,
|
||||
LineWeight weight = LineWeight::Light) -> DrawEnd;
|
||||
|
||||
// Draws a vertical line down `length` rows starting at (x, y), with the same
|
||||
// meaning for its ends. Returns the row after it, in the column it ran down.
|
||||
auto DrawVerticalLine(int x, int y, int length, const Style& style,
|
||||
LineEnd start = LineEnd::Center,
|
||||
LineEnd end = LineEnd::Center) -> DrawEnd;
|
||||
LineEnd end = LineEnd::Center,
|
||||
LineWeight weight = LineWeight::Light) -> DrawEnd;
|
||||
|
||||
// Draws a light vertical line down `length` rows starting at (x, y), behind
|
||||
// everything drawn before it. The line runs out through the bottom of its
|
||||
// last cell.
|
||||
//
|
||||
// The line is drawn only into blank cells with no text directly left or
|
||||
// right of them. Everything drawn earlier stays as it was, so where this
|
||||
// line crosses another, the other line is unbroken and this one has a gap,
|
||||
// and the two don't join. A line beside text could be mistaken for part of
|
||||
// the text, which is why those cells are skipped too.
|
||||
//
|
||||
// With `start` at `LineEnd::Center`, the first cell is the exception: the
|
||||
// line leaves it downward and joins what it holds, as `DrawVerticalLine`
|
||||
// would. If a line already leaves that cell downward, the cell is left
|
||||
// unchanged, so that line keeps its weight. With `start` at `LineEnd::Edge`,
|
||||
// the line enters its first cell from above like the rest, which continues
|
||||
// a line broken off above it.
|
||||
//
|
||||
// Returns the row after the line, in the column it ran down.
|
||||
auto DrawCrossingVerticalLine(int x, int y, int length, const Style& style,
|
||||
LineEnd start = LineEnd::Center) -> DrawEnd;
|
||||
|
||||
// Draws the outline of a box with its top-left corner at (x, y).
|
||||
//
|
||||
@@ -365,6 +402,12 @@ class Buffer {
|
||||
// word, and one too long for a row of its own is moved down to one and then
|
||||
// overhangs it rather than being broken.
|
||||
//
|
||||
// With `backticks_are_quotes`, backticks pair up from the left on each line
|
||||
// and no break falls between a pair, so a quoted type with spaces in it
|
||||
// wraps whole or overhangs, as a URL does. A backtick with no partner later
|
||||
// on its line, or with a tab or carriage return before its partner, is an
|
||||
// ordinary character.
|
||||
//
|
||||
// Whitespace stops at the block's edge rather than running past it, so the
|
||||
// spaces between two words stay on the row the first of them ended and the
|
||||
// row the second wraps onto begins at the margin. Spaces the text opens with,
|
||||
@@ -397,12 +440,9 @@ class Buffer {
|
||||
// with such text, since which breaks a reflow may discard -- every newline,
|
||||
// or only those a previous wrapping introduced -- is a question about where
|
||||
// that text came from.
|
||||
//
|
||||
// TODO: A break can fall inside a piece of code the text quotes, splitting a
|
||||
// type written with spaces in it across two rows. Take a quote character and
|
||||
// keep what a pair of them encloses in one word, as a URL already is.
|
||||
auto DrawWrappedText(int x, int y, int margin, int max_width,
|
||||
llvm::StringRef text, const Style& style) -> DrawEnd;
|
||||
llvm::StringRef text, const Style& style,
|
||||
bool backticks_are_quotes = false) -> DrawEnd;
|
||||
|
||||
// Renders the grid, appending the bytes that draw it to `out`.
|
||||
//
|
||||
@@ -444,7 +484,7 @@ class Buffer {
|
||||
|
||||
struct Cell {
|
||||
// The code point rendered here. For a cell with `lines` set, this is
|
||||
// derived from those bits and the charset.
|
||||
// derived from `lines`, `heavy_lines`, and the charset.
|
||||
char32_t code_point = ' ';
|
||||
|
||||
Style style;
|
||||
@@ -453,6 +493,10 @@ class Buffer {
|
||||
// or zero for a cell holding text.
|
||||
uint8_t lines = 0;
|
||||
|
||||
// The directions in `lines` that a heavy line leaves in. Always a subset
|
||||
// of the directions set there.
|
||||
uint8_t heavy_lines = 0;
|
||||
|
||||
// Whether this cell is the right half of a double-width character, and so
|
||||
// renders nothing of its own.
|
||||
bool is_continuation = false;
|
||||
@@ -511,7 +555,8 @@ class Buffer {
|
||||
PlaceFn place) const -> DrawEnd;
|
||||
template <typename PlaceFn>
|
||||
auto WalkWrappedText(int x, int y, int margin, int max_width,
|
||||
llvm::StringRef text, PlaceFn place) const -> DrawEnd;
|
||||
llvm::StringRef text, bool backticks_are_quotes,
|
||||
PlaceFn place) const -> DrawEnd;
|
||||
|
||||
// Adds rows until row `y` exists.
|
||||
auto EnsureRow(int y) -> void;
|
||||
@@ -528,8 +573,14 @@ class Buffer {
|
||||
// Appends `code_point` to the marks rendered with the cell before column `x`.
|
||||
auto AttachCombiningMark(int x, int y, char32_t code_point) -> void;
|
||||
|
||||
// Adds `directions` to the lines through (x, y) and updates its glyph.
|
||||
auto DrawLine(int x, int y, uint8_t directions, const Style& style) -> void;
|
||||
// Adds `directions` to the lines through (x, y) at `weight`, replacing the
|
||||
// weight those directions had, and updates its glyph.
|
||||
auto DrawLine(int x, int y, uint8_t directions, LineWeight weight,
|
||||
const Style& style) -> void;
|
||||
|
||||
// Returns whether the cell at (x, y) holds text. Cells outside what has been
|
||||
// drawn hold nothing.
|
||||
auto HoldsText(int x, int y) const -> bool;
|
||||
|
||||
// Returns the last column in row `y` that renders anything under `mode`, or
|
||||
// -1 when the row renders nothing.
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
@@ -171,18 +170,222 @@ TEST(BufferTest, Tees) {
|
||||
"│ │ │\n"
|
||||
"╰─┴─╯\n");
|
||||
|
||||
// The same table in ASCII, where every tee keeps its through-stroke and only
|
||||
// the crossing in the middle is a `+`.
|
||||
// The same table in ASCII: only the crossing in the middle is a `+`, a tee
|
||||
// where a line leaves sideways is `|`, and a tee where a line leaves upward
|
||||
// or downward is the corner glyph for that direction.
|
||||
Buffer ascii(5, Charset::Ascii);
|
||||
ascii.DrawBox(0, 0, 5, 5, Style());
|
||||
ascii.DrawHorizontalLine(0, 2, 5, Style());
|
||||
ascii.DrawVerticalLine(2, 0, 5, Style());
|
||||
EXPECT_EQ(Render(ascii),
|
||||
".---.\n"
|
||||
".-.-.\n"
|
||||
"| | |\n"
|
||||
"|-+-|\n"
|
||||
"| | |\n"
|
||||
"'---'\n");
|
||||
"'-'-'\n");
|
||||
}
|
||||
|
||||
TEST(BufferTest, HeavyLines) {
|
||||
// A heavy line keeps the same geometry as a light one, drawn in the heavy
|
||||
// glyphs, ends included.
|
||||
Buffer heavy(4, Charset::Utf8);
|
||||
heavy.DrawHorizontalLine(0, 0, 4, Style(), LineEnd::Center, LineEnd::Center,
|
||||
LineWeight::Heavy);
|
||||
heavy.DrawVerticalLine(0, 1, 3, Style(), LineEnd::Center, LineEnd::Center,
|
||||
LineWeight::Heavy);
|
||||
EXPECT_EQ(Render(heavy),
|
||||
"╺━━╸\n"
|
||||
"╻\n"
|
||||
"┃\n"
|
||||
"╹\n");
|
||||
|
||||
// Heavy lines join each other the way light ones do, into heavy junctions.
|
||||
Buffer joined(5, Charset::Utf8);
|
||||
joined.DrawHorizontalLine(0, 0, 5, Style(), LineEnd::Edge, LineEnd::Edge,
|
||||
LineWeight::Heavy);
|
||||
joined.DrawVerticalLine(2, 0, 3, Style(), LineEnd::Center, LineEnd::Center,
|
||||
LineWeight::Heavy);
|
||||
joined.DrawHorizontalLine(2, 2, 3, Style(), LineEnd::Center, LineEnd::Edge,
|
||||
LineWeight::Heavy);
|
||||
EXPECT_EQ(Render(joined),
|
||||
"━━┳━━\n"
|
||||
" ┃\n"
|
||||
" ┗━━\n");
|
||||
}
|
||||
|
||||
TEST(BufferTest, HeavyAndLightLinesMix) {
|
||||
// Weight is recorded per direction, so a heavy line crossing a light one
|
||||
// draws a junction that is heavy left and right and light up and down.
|
||||
Buffer cross(3, Charset::Utf8);
|
||||
cross.DrawHorizontalLine(0, 1, 3, Style(), LineEnd::Edge, LineEnd::Edge,
|
||||
LineWeight::Heavy);
|
||||
cross.DrawVerticalLine(1, 0, 3, Style(), LineEnd::Edge, LineEnd::Edge);
|
||||
EXPECT_EQ(Render(cross),
|
||||
" │\n"
|
||||
"━┿━\n"
|
||||
" │\n");
|
||||
|
||||
// A light line leaving a heavy one downward: each line keeps its own weight
|
||||
// in the tee.
|
||||
Buffer tee(4, Charset::Utf8);
|
||||
tee.DrawHorizontalLine(0, 0, 4, Style(), LineEnd::Edge, LineEnd::Edge,
|
||||
LineWeight::Heavy);
|
||||
tee.DrawVerticalLine(1, 0, 2, Style(), LineEnd::Center, LineEnd::Center);
|
||||
EXPECT_EQ(Render(tee),
|
||||
"━┯━━\n"
|
||||
" ╵\n");
|
||||
|
||||
// A line's weight in a cell is that of whatever drew it last, as a cell's
|
||||
// style is: a heavy line drawn over a light one leaves it heavy, and a light
|
||||
// one drawn over part of a heavy one lightens the stretch it covers and
|
||||
// nothing else.
|
||||
Buffer overdrawn(3, Charset::Utf8);
|
||||
overdrawn.DrawHorizontalLine(0, 0, 3, Style());
|
||||
overdrawn.DrawHorizontalLine(0, 0, 3, Style(), LineEnd::Center,
|
||||
LineEnd::Center, LineWeight::Heavy);
|
||||
EXPECT_EQ(Render(overdrawn), "╺━╸\n");
|
||||
|
||||
Buffer lightened(4, Charset::Utf8);
|
||||
lightened.DrawHorizontalLine(0, 0, 4, Style(), LineEnd::Edge, LineEnd::Edge,
|
||||
LineWeight::Heavy);
|
||||
lightened.DrawHorizontalLine(2, 0, 2, Style(), LineEnd::Edge, LineEnd::Edge);
|
||||
EXPECT_EQ(Render(lightened), "━━──\n");
|
||||
}
|
||||
|
||||
TEST(BufferTest, HeavyLinesInAscii) {
|
||||
// ASCII has no weights, so a heavy horizontal line becomes `^`, including
|
||||
// the tee where a heavy vertical line leaves it.
|
||||
Buffer underline(8, Charset::Ascii);
|
||||
underline.DrawHorizontalLine(0, 0, 7, Style(), LineEnd::Edge, LineEnd::Edge,
|
||||
LineWeight::Heavy);
|
||||
underline.DrawVerticalLine(3, 0, 2, Style(), LineEnd::Center, LineEnd::Center,
|
||||
LineWeight::Heavy);
|
||||
EXPECT_EQ(Render(underline),
|
||||
"^^^^^^^\n"
|
||||
" |\n");
|
||||
|
||||
// So does the top cell of a heavy vertical line, which no line enters from
|
||||
// above, so the line is `^` above `|`.
|
||||
Buffer vertical(3, Charset::Ascii);
|
||||
vertical.DrawVerticalLine(1, 0, 3, Style(), LineEnd::Center, LineEnd::Center,
|
||||
LineWeight::Heavy);
|
||||
EXPECT_EQ(Render(vertical),
|
||||
" ^\n"
|
||||
" |\n"
|
||||
" |\n");
|
||||
|
||||
// A light line leaving the heavy underline downward keeps the light tee's
|
||||
// `.`, which `^` would hide, while a heavy line leaving it downward keeps the
|
||||
// `^`.
|
||||
Buffer tees(7, Charset::Ascii);
|
||||
tees.DrawHorizontalLine(0, 0, 7, Style(), LineEnd::Edge, LineEnd::Edge,
|
||||
LineWeight::Heavy);
|
||||
tees.DrawVerticalLine(2, 0, 2, Style(), LineEnd::Center, LineEnd::Center);
|
||||
tees.DrawVerticalLine(5, 0, 2, Style(), LineEnd::Center, LineEnd::Center,
|
||||
LineWeight::Heavy);
|
||||
EXPECT_EQ(Render(tees),
|
||||
"^^.^^^^\n"
|
||||
" | |\n");
|
||||
|
||||
// A cell a line enters from above keeps its light glyph, so the crossing is
|
||||
// `+`, and the heavy vertical line is drawn as a light one.
|
||||
Buffer crossed(3, Charset::Ascii);
|
||||
crossed.DrawHorizontalLine(0, 1, 3, Style(), LineEnd::Edge, LineEnd::Edge,
|
||||
LineWeight::Heavy);
|
||||
crossed.DrawVerticalLine(1, 0, 3, Style(), LineEnd::Edge, LineEnd::Edge,
|
||||
LineWeight::Heavy);
|
||||
EXPECT_EQ(Render(crossed),
|
||||
" |\n"
|
||||
"^+^\n"
|
||||
" |\n");
|
||||
}
|
||||
|
||||
TEST(BufferTest, CrossingLinePassesBehindEarlierLines) {
|
||||
// A crossing line joins the line its first cell holds. Below that, it has a
|
||||
// gap where it crosses an earlier line, which stays unbroken, and the two
|
||||
// don't join.
|
||||
Buffer buffer(5, Charset::Utf8);
|
||||
buffer.DrawHorizontalLine(0, 0, 5, Style(), LineEnd::Edge, LineEnd::Edge);
|
||||
buffer.DrawHorizontalLine(0, 2, 5, Style(), LineEnd::Edge, LineEnd::Edge);
|
||||
buffer.DrawCrossingVerticalLine(2, 0, 4, Style());
|
||||
EXPECT_EQ(Render(buffer),
|
||||
"──┬──\n"
|
||||
" │\n"
|
||||
"─────\n"
|
||||
" │\n");
|
||||
|
||||
// The same crossing in ASCII, where the tee is the corner glyph for the line
|
||||
// leaving downward and the crossed line is unbroken.
|
||||
Buffer ascii(5, Charset::Ascii);
|
||||
ascii.DrawHorizontalLine(0, 0, 5, Style(), LineEnd::Edge, LineEnd::Edge);
|
||||
ascii.DrawHorizontalLine(0, 2, 5, Style(), LineEnd::Edge, LineEnd::Edge);
|
||||
ascii.DrawCrossingVerticalLine(2, 0, 4, Style());
|
||||
EXPECT_EQ(Render(ascii),
|
||||
"--.--\n"
|
||||
" |\n"
|
||||
"-----\n"
|
||||
" |\n");
|
||||
|
||||
// Weight plays no part in it: a heavy line drawn first stays whole the
|
||||
// same way.
|
||||
Buffer heavy(5, Charset::Utf8);
|
||||
heavy.DrawHorizontalLine(0, 0, 5, Style(), LineEnd::Edge, LineEnd::Edge);
|
||||
heavy.DrawHorizontalLine(0, 1, 5, Style(), LineEnd::Edge, LineEnd::Edge,
|
||||
LineWeight::Heavy);
|
||||
heavy.DrawCrossingVerticalLine(2, 0, 4, Style());
|
||||
EXPECT_EQ(Render(heavy),
|
||||
"──┬──\n"
|
||||
"━━━━━\n"
|
||||
" │\n"
|
||||
" │\n");
|
||||
}
|
||||
|
||||
TEST(BufferTest, CrossingLineResumesFromAnEdge) {
|
||||
// A crossing line starting at an edge enters its first cell from above, so
|
||||
// it continues a line broken off above a stretch it skipped, and it still
|
||||
// has a gap where it crosses an earlier line.
|
||||
Buffer buffer(5, Charset::Utf8);
|
||||
buffer.DrawHorizontalLine(0, 0, 5, Style(), LineEnd::Edge, LineEnd::Edge);
|
||||
buffer.DrawText(0, 1, "words", Style());
|
||||
buffer.DrawHorizontalLine(0, 3, 5, Style(), LineEnd::Edge, LineEnd::Edge);
|
||||
buffer.DrawCrossingVerticalLine(2, 0, 1, Style());
|
||||
buffer.DrawCrossingVerticalLine(2, 2, 3, Style(), LineEnd::Edge);
|
||||
EXPECT_EQ(Render(buffer),
|
||||
"──┬──\n"
|
||||
"words\n"
|
||||
" │\n"
|
||||
"─────\n"
|
||||
" │\n");
|
||||
}
|
||||
|
||||
TEST(BufferTest, CrossingLineSharesAColumnWithoutRedrawing) {
|
||||
// A crossing line leaves the cells of an earlier line in its column as they
|
||||
// are, so that line keeps its weight.
|
||||
Buffer buffer(3, Charset::Utf8);
|
||||
buffer.DrawHorizontalLine(0, 0, 3, Style(), LineEnd::Edge, LineEnd::Edge);
|
||||
buffer.DrawVerticalLine(1, 0, 3, Style(), LineEnd::Center, LineEnd::Center,
|
||||
LineWeight::Heavy);
|
||||
buffer.DrawCrossingVerticalLine(1, 0, 3, Style());
|
||||
EXPECT_EQ(Render(buffer),
|
||||
"─┰─\n"
|
||||
" ┃\n"
|
||||
" ╹\n");
|
||||
}
|
||||
|
||||
TEST(BufferTest, CrossingLineStaysClearOfText) {
|
||||
// The line skips text, and blank cells directly beside text, where a line
|
||||
// could be mistaken for part of a word.
|
||||
Buffer buffer(6, Charset::Utf8);
|
||||
buffer.DrawHorizontalLine(0, 0, 6, Style(), LineEnd::Edge, LineEnd::Edge);
|
||||
buffer.DrawText(0, 1, "ab", Style());
|
||||
buffer.DrawText(0, 3, "abc", Style());
|
||||
buffer.DrawCrossingVerticalLine(2, 0, 5, Style());
|
||||
EXPECT_EQ(Render(buffer),
|
||||
"──┬───\n"
|
||||
"ab\n"
|
||||
" │\n"
|
||||
"abc\n"
|
||||
" │\n");
|
||||
}
|
||||
|
||||
TEST(BufferTest, ALineBetweenOneCenterAndItselfIsAPoint) {
|
||||
@@ -673,6 +876,69 @@ TEST(BufferTest, TabWidthComesFromCapabilities) {
|
||||
EXPECT_EQ(Render(clamped), "a b\n");
|
||||
}
|
||||
|
||||
TEST(BufferTest, WrappedTextKeepsAQuotedRunWhole) {
|
||||
// With backticks as quotes, the spaces between a pair of backticks are not
|
||||
// breaks, so the quoted run wraps to the next row whole.
|
||||
Buffer buffer(20, Charset::Utf8);
|
||||
buffer.DrawWrappedText(0, 0, 0, 20, "type `{.a: i32}` is not", Style(),
|
||||
/*backticks_are_quotes=*/true);
|
||||
EXPECT_EQ(Render(buffer),
|
||||
"type `{.a: i32}` is\n"
|
||||
"not\n");
|
||||
|
||||
Buffer split(14, Charset::Utf8);
|
||||
split.DrawWrappedText(0, 0, 0, 14, "type `{.a: i32}` is not", Style(),
|
||||
/*backticks_are_quotes=*/true);
|
||||
EXPECT_EQ(Render(split),
|
||||
"type\n"
|
||||
"`{.a: i32}` is\n"
|
||||
"not\n");
|
||||
|
||||
// Otherwise, the same text breaks inside the quotes like anywhere else.
|
||||
Buffer unquoted(14, Charset::Utf8);
|
||||
unquoted.DrawWrappedText(0, 0, 0, 14, "type `{.a: i32}` is not", Style());
|
||||
EXPECT_EQ(Render(unquoted),
|
||||
"type `{.a:\n"
|
||||
"i32}` is not\n");
|
||||
|
||||
// A backtick with no partner on its line is an ordinary character, so the
|
||||
// text after it still breaks at its spaces.
|
||||
Buffer stray(14, Charset::Utf8);
|
||||
stray.DrawWrappedText(0, 0, 0, 14, "a `stray quote and more words", Style(),
|
||||
/*backticks_are_quotes=*/true);
|
||||
EXPECT_EQ(Render(stray),
|
||||
"a `stray quote\n"
|
||||
"and more words\n");
|
||||
|
||||
// Nor does a run reach across a newline the caller wrote.
|
||||
Buffer lines(14, Charset::Utf8);
|
||||
lines.DrawWrappedText(0, 0, 0, 14, "a `b\nc d` e f g h", Style(),
|
||||
/*backticks_are_quotes=*/true);
|
||||
EXPECT_EQ(Render(lines),
|
||||
"a `b\n"
|
||||
"c d` e f g h\n");
|
||||
|
||||
// A word can't hold a tab or carriage return, so a quoted run doesn't reach
|
||||
// across one either.
|
||||
Buffer tabbed(14, Charset::Utf8);
|
||||
tabbed.DrawWrappedText(0, 0, 0, 14, "a `b\tc` d", Style(),
|
||||
/*backticks_are_quotes=*/true);
|
||||
EXPECT_EQ(Render(tabbed), "a `b c` d\n");
|
||||
EXPECT_EQ(buffer.MeasureWrapWidth("`b\tc`", /*backticks_are_quotes=*/true),
|
||||
2);
|
||||
EXPECT_EQ(buffer.MeasureWrapWidth("`b\rc`", /*backticks_are_quotes=*/true),
|
||||
2);
|
||||
|
||||
// Measuring agrees: the quoted run is the widest word.
|
||||
EXPECT_EQ(buffer.MeasureWrapWidth("type `{.a: i32}` is not",
|
||||
/*backticks_are_quotes=*/true),
|
||||
11);
|
||||
EXPECT_EQ(buffer.MeasureWrapWidth("type `{.a: i32}` is not"), 5);
|
||||
EXPECT_EQ(buffer.MeasureWrappedText(0, 0, 0, 14, "type `{.a: i32}` is not",
|
||||
/*backticks_are_quotes=*/true),
|
||||
DrawEnd(3, 2));
|
||||
}
|
||||
|
||||
TEST(BufferTest, WrappedTextKeepsTheBreaksItIsGiven) {
|
||||
// Wrapping only adds breaks. Text that arrives wrapped to some other width
|
||||
// keeps that wrapping rather than being reflowed into this one, even where
|
||||
|
||||
@@ -318,7 +318,7 @@ TEST(RendererTest, MergedShowsOneViewOfTheFile) {
|
||||
" .-| foo.carbon:4:3\n"
|
||||
" |\n"
|
||||
" 1 | fn Run0() {}\n"
|
||||
" : ------------\n"
|
||||
" : ------.-----\n"
|
||||
" : '--| declared here\n"
|
||||
" :\n"
|
||||
"-> 4 | Run0(1);\n"
|
||||
@@ -337,7 +337,7 @@ TEST(RendererTest, MergedShowsASingleSkippedLine) {
|
||||
" .-| foo.carbon:3:1\n"
|
||||
" |\n"
|
||||
" 1 | fn Run0() {}\n"
|
||||
" : --\n"
|
||||
" : -.\n"
|
||||
" : '--| here\n"
|
||||
" 2 |\n"
|
||||
"-> 3 | fn Main() {\n"
|
||||
@@ -366,7 +366,7 @@ TEST(RendererTest, NotesInAnotherFileGetTheirOwnAnchor) {
|
||||
" |-| bar.carbon:9:1\n"
|
||||
" :\n"
|
||||
" 9 | class C {\n"
|
||||
" : ---------\n"
|
||||
" : ----.----\n"
|
||||
" : '--| declared here\n"
|
||||
" '------------\n"
|
||||
""));
|
||||
@@ -387,7 +387,7 @@ TEST(RendererTest, APrimaryLabelTeesIntoItsRange) {
|
||||
" .-| foo.carbon:4:3\n"
|
||||
" :\n"
|
||||
"-> 4 | Run0(1);\n"
|
||||
" : -------\n"
|
||||
" : ---.---\n"
|
||||
" : '--| 1 argument passed here\n"
|
||||
" '------------\n"
|
||||
""));
|
||||
@@ -507,7 +507,7 @@ TEST(RendererTest, RepeatedSpansAreOneMark) {
|
||||
" .-| foo.carbon:4:3\n"
|
||||
" :\n"
|
||||
"-> 4 | Run0(1);\n"
|
||||
" : -------\n"
|
||||
" : ---.---\n"
|
||||
" : '--| wrong\n"
|
||||
" '------------\n"
|
||||
""));
|
||||
@@ -528,7 +528,7 @@ TEST(RendererTest, ARangeCoveringOthersIsRepairedRatherThanStacked) {
|
||||
" .-| foo.carbon:4:3\n"
|
||||
" :\n"
|
||||
"-> 4 | Run0(1);\n"
|
||||
" : -------\n"
|
||||
" : --.--.-\n"
|
||||
" : | '--| argument\n"
|
||||
" : '--| callee\n"
|
||||
" '------------\n"
|
||||
@@ -549,7 +549,7 @@ TEST(RendererTest, ALabelOnASingleColumnRangeDropsStraightDown) {
|
||||
" .-| foo.carbon:4:8\n"
|
||||
" :\n"
|
||||
"-> 4 | Run0(1);\n"
|
||||
" : ---- -\n"
|
||||
" : --.- .\n"
|
||||
" : | '--| with this\n"
|
||||
" : '--| indexed here\n"
|
||||
" '------------\n"
|
||||
@@ -851,7 +851,7 @@ TEST(RendererTest, AsciiDrawsAdjacentRangesWithoutTheGap) {
|
||||
" .-| foo.carbon:4:3\n"
|
||||
" :\n"
|
||||
"-> 4 | Run0(1);\n"
|
||||
" : -------\n"
|
||||
" : -----.-\n"
|
||||
" : '--| with these\n"
|
||||
" '------------\n"
|
||||
""));
|
||||
@@ -885,7 +885,7 @@ TEST(RendererTest, TwoSpansOnOneLineShareItsRow) {
|
||||
" .-| foo.carbon:1:4\n"
|
||||
" |\n"
|
||||
"-> 1 | fn Run0() {}\n"
|
||||
" : -- ----\n"
|
||||
" : -. ----\n"
|
||||
" : '--| here\n"
|
||||
" '------------\n"
|
||||
""));
|
||||
@@ -917,7 +917,7 @@ TEST(RendererTest, LabelsOnOneLineHangRightToLeft) {
|
||||
" .-| foo.carbon:3:16\n"
|
||||
" :\n"
|
||||
"-> 3 | return count * flag;\n"
|
||||
" : ----- ----\n"
|
||||
" : --.-- --.-\n"
|
||||
" : | '--| right operand has type `bool`\n"
|
||||
" : '--| left operand has type `i32`\n"
|
||||
" '------------\n"
|
||||
@@ -950,7 +950,7 @@ TEST(RendererTest, SpansOnOneLineShareOneWindow) {
|
||||
"-> 1 | "
|
||||
"...xxxxxxxxxxxxxxxxFIRSTxxxxxxxxxxxxxxxSECONDxxxxxxxxxxxxxxxxxxxxxxx."
|
||||
"..\n"
|
||||
" : ----- ------\n"
|
||||
" : ----- ---.--\n"
|
||||
" : '--| and here\n"
|
||||
" '------------\n"
|
||||
""));
|
||||
@@ -981,7 +981,7 @@ TEST(RendererTest, MultipleFilesEachGetAnAnchor) {
|
||||
" |-| a.carbon:3:1\n"
|
||||
" :\n"
|
||||
" 3 | class Shape {}\n"
|
||||
" : --------------\n"
|
||||
" : -------.------\n"
|
||||
" : '--| previously declared\n"
|
||||
" '------------\n"
|
||||
""));
|
||||
@@ -1016,7 +1016,7 @@ TEST(RendererTest, ContextLeadsIntoTheAnchorItReached) {
|
||||
" |-| a.carbon:3:1\n"
|
||||
" :\n"
|
||||
" 3 | class Shape {}\n"
|
||||
" : --------------\n"
|
||||
" : -------.------\n"
|
||||
" : '--| previously declared\n"
|
||||
" '------------\n"
|
||||
""));
|
||||
@@ -1051,7 +1051,7 @@ TEST(RendererTest, ContextStepsStackInOrder) {
|
||||
" |-| inner.h:7:6\n"
|
||||
" :\n"
|
||||
" 7 | void Use(int, int);\n"
|
||||
" : ---\n"
|
||||
" : -.-\n"
|
||||
" : '--| candidate not viable\n"
|
||||
" '------------\n"
|
||||
""));
|
||||
@@ -1288,7 +1288,7 @@ TEST(RendererTest, AnnotationsTakeTheColorOfTheirMessage) {
|
||||
// color, so each mark matches the words that explain it. Neither is bold: a
|
||||
// mark's weight says nothing a terminal renders reliably.
|
||||
EXPECT_THAT(rendered, HasSubstr("\x1b[93m-------\n"));
|
||||
EXPECT_THAT(rendered, HasSubstr("\x1b[96m------------\n"));
|
||||
EXPECT_THAT(rendered, HasSubstr("\x1b[96m------.-----\n"));
|
||||
// The line the problem is on takes the level's color and its weight. Every
|
||||
// other line number is as light as the frame it belongs to, so the one being
|
||||
// reported is the one that stands out.
|
||||
@@ -1308,7 +1308,7 @@ TEST(RendererTest, TheFrameSeparatorIsFrame) {
|
||||
// color on every row -- including the note's, whose underline and words are
|
||||
// the note color beside it. Coloring it per row made it read as belonging to
|
||||
// whichever range was there rather than to the frame.
|
||||
EXPECT_THAT(rendered, HasSubstr("\x1b[93m:\x1b[0m \x1b[96m------------"));
|
||||
EXPECT_THAT(rendered, HasSubstr("\x1b[93m:\x1b[0m \x1b[96m------.-----"));
|
||||
EXPECT_THAT(rendered, HasSubstr("\x1b[93m:\x1b[0m \x1b[96m'--|"));
|
||||
}
|
||||
|
||||
@@ -1505,7 +1505,7 @@ TEST(RendererTest, LineShownBetweenTwoSpansIsWindowedToo) {
|
||||
" .-| foo.carbon:3:1\n"
|
||||
" |\n"
|
||||
" 1 | short\n"
|
||||
" : ----\n"
|
||||
" : --.-\n"
|
||||
" : '--| and\n"
|
||||
" 2 | "
|
||||
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
@@ -1738,7 +1738,7 @@ TEST(RendererTest, ALabelIsFramedWhenItWraps) {
|
||||
" .-| foo.carbon:2:11\n"
|
||||
" :\n"
|
||||
"-> 2 | NeedsIt(\"definitely not the right type\");\n"
|
||||
" : ------------------------------\n"
|
||||
" : .-----------------------------\n"
|
||||
" : '--| type `str` does not implement interface\n"
|
||||
" : | `Core.ImplicitAs(i32)` at all\n"
|
||||
" '------------\n"
|
||||
@@ -1829,7 +1829,7 @@ TEST(RendererTest, ARoutedLabelUnderAscii) {
|
||||
" .-| foo.carbon:2:11\n"
|
||||
" :\n"
|
||||
"-> 2 | NeedsIt(\"definitely not the right type\");\n"
|
||||
" : ------------------------------\n"
|
||||
" : .-----------------------------\n"
|
||||
" : .---------'\n"
|
||||
" : | type `str` does not implement interface\n"
|
||||
" : | "
|
||||
@@ -2133,7 +2133,7 @@ TEST(RendererTest, ARangeOnAnotherLineDoesNotStandInForTheLocation) {
|
||||
" : --\n"
|
||||
" :\n"
|
||||
"-> 4 | Run0(1);\n"
|
||||
" : -------\n"
|
||||
" : ---.---\n"
|
||||
" : '--| here\n"
|
||||
" '------------\n"
|
||||
""));
|
||||
@@ -2345,7 +2345,7 @@ TEST(RendererTest, AMergedPairWithoutFileTextStillElides) {
|
||||
" : --\n"
|
||||
" :\n"
|
||||
" 3 | third line\n"
|
||||
" : --\n"
|
||||
" : -.\n"
|
||||
" : '--| here\n"
|
||||
" '------------\n"
|
||||
""));
|
||||
@@ -2408,7 +2408,7 @@ TEST(RendererTest, AContextLeadsTheFrameAndTheMessageHangs) {
|
||||
"-> 3 | fn Main() {\n"
|
||||
" : ----\n"
|
||||
"-> 4 | Run0(1);\n"
|
||||
" : -------\n"
|
||||
" : ---.---\n"
|
||||
" : '--| no matching call\n"
|
||||
" '------------\n"
|
||||
""));
|
||||
|
||||
+4
-4
@@ -48,7 +48,7 @@ fn H() {
|
||||
// CHECK:STDERR: .-| fail_note_in_same_file.carbon:[[@LINE+10]]:3
|
||||
// CHECK:STDERR: :
|
||||
// CHECK:STDERR: 4 | fn G() {}
|
||||
// CHECK:STDERR: : --------
|
||||
// CHECK:STDERR: : ----.---
|
||||
// CHECK:STDERR: : '--| calling function declared here [InCallToEntity]
|
||||
// CHECK:STDERR: :
|
||||
// CHECK:STDERR: -> 18 | G(1);
|
||||
@@ -108,7 +108,7 @@ fn Use() {
|
||||
// CHECK:STDERR: |-| declared.carbon:4:1
|
||||
// CHECK:STDERR: :
|
||||
// CHECK:STDERR: 4 | fn Declared();
|
||||
// CHECK:STDERR: : --------------
|
||||
// CHECK:STDERR: : -------.------
|
||||
// CHECK:STDERR: : '--| calling function declared here [InCallToEntity]
|
||||
// CHECK:STDERR: '------------
|
||||
// CHECK:STDERR:
|
||||
@@ -123,7 +123,7 @@ library "[[@TEST_NAME]]";
|
||||
// CHECK:STDERR: .-| fail_two_spans_on_one_line.carbon:[[@LINE+7]]:27
|
||||
// CHECK:STDERR: :
|
||||
// CHECK:STDERR: -> 12 | fn Same() {} fn Twice() { Same(1); }
|
||||
// CHECK:STDERR: : ----------- -------
|
||||
// CHECK:STDERR: : -----.----- -------
|
||||
// CHECK:STDERR: : '--| calling function declared here [InCallToEntity]
|
||||
// CHECK:STDERR: '------------
|
||||
// CHECK:STDERR:
|
||||
@@ -195,7 +195,7 @@ fn CallIt() {
|
||||
// CHECK:STDERR: |-| ./cpp_decl.h:3:6
|
||||
// CHECK:STDERR: :
|
||||
// CHECK:STDERR: 3 | void Use(Widget w, int extra);
|
||||
// CHECK:STDERR: : - -------------------
|
||||
// CHECK:STDERR: : . ---------.---------
|
||||
// CHECK:STDERR: : | '--| requires 2 arguments, but 1 was provided
|
||||
// CHECK:STDERR: : | | [CppInteropParseNoteReason]
|
||||
// CHECK:STDERR: : '--| candidate function not viable [CppInteropParseNote]
|
||||
|
||||
Reference in new issue
Block a user