blob: 7c9f2271eb6f318e9b285be6bae373604cef8339 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <map>
#include "garnet/bin/zxdb/console/output_buffer.h"
#include "garnet/bin/zxdb/common/err.h"
#include "garnet/bin/zxdb/console/string_util.h"
#include "garnet/public/lib/fxl/strings/split_string.h"
namespace zxdb {
namespace {
// The color codes are taken from the vte 256 colorscheme, which is pretty
// common. If needed, some fallback colors could be established to support
// some old terminal scheme.
// Syntax color codes ----------------------------------------------------------
const char kNormalEscapeCode[] = "\x1b[0m"; // "[0m" = Normal.
const char kBoldEscapeCode[] = "\x1b[1m"; // "[1m" = Bold.
const char kCommentEscapeCode[] = "\x1b[2m"; // "[2m" = Faint.
const char kErrorEscapeCode[] = "\x1b[31m"; // "[31m" = Red.
const char kWarningEscapeCode[] = "\x1b[33m"; // "[33m" = Yellow.
const char kSpecialEscapeCode[] = "\x1b[34m"; // "[34m" = Blue.
const char kReversedEscapeCode[] = "\x1b[7m"; // "[7m' = Reverse video.
const char kVariableEscapeCode[] = "\x1b[36m"; // "[36m' = Cyan.
using SyntaxColorMap = std::map<Syntax, std::pair<const char*, size_t>>;
static const SyntaxColorMap& GetSyntaxColorMap() {
static SyntaxColorMap syntax_color_map = {
{Syntax::kHeading, {kBoldEscapeCode, sizeof(kBoldEscapeCode) - 1}},
{Syntax::kComment, {kCommentEscapeCode, sizeof(kCommentEscapeCode) - 1}},
{Syntax::kError, {kErrorEscapeCode, sizeof(kErrorEscapeCode) - 1}},
{Syntax::kWarning, {kWarningEscapeCode, sizeof(kWarningEscapeCode) - 1}},
{Syntax::kSpecial, {kSpecialEscapeCode, sizeof(kSpecialEscapeCode) - 1}},
{Syntax::kReversed,
{kReversedEscapeCode, sizeof(kReversedEscapeCode) - 1}},
{Syntax::kVariable,
{kVariableEscapeCode, sizeof(kVariableEscapeCode) - 1}},
};
return syntax_color_map;
}
// Background color codes ------------------------------------------------------
const char kBackgroundBlack[] = "\x1b[48;5;0m";
const char kBackgroundBlue[] = "\x1b[48;5;4m";
const char kBackgroundCyan[] = "\x1b[48;5;6m";
const char kBackgroundGray[] = "\x1b[48;5;245m";
const char kBackgroundGreen[] = "\x1b[48;5;2m";
const char kBackgroundMagenta[] = "\x1b[48;5;5m";
const char kBackgroundRed[] = "\x1b[48;5;1m";
const char kBackgroundWhite[] = "\x1b[48;5;15m";
const char kBackgroundYellow[] = "\x1b[48;5;11m";
const char kBackgroundLightBlue[] = "\x1b[48;5;45m";
const char kBackgroundLightCyan[] = "\x1b[48;5;87m";
const char kBackgroundLightGray[] = "\x1b[48;5;250m";
const char kBackgroundLightGreen[] = "\x1b[48;5;10m";
const char kBackgroundLightMagenta[] = "\x1b[48;5;170m";
const char kBackgroundLightRed[] = "\x1b[48;5;166m";
const char kBackgroundLightYellow[] = "\x1b[48;5;190m";
using BackgroundColorMap =
std::map<TextBackgroundColor, std::pair<const char*, size_t>>;
static const BackgroundColorMap& GetBackgroundColorMap() {
// We substract 1 from the sizeof the strings to avoid the end null char.
static BackgroundColorMap background_color_map = {
{TextBackgroundColor::kBlack,
{kBackgroundBlack, sizeof(kBackgroundBlack) - 1}},
{TextBackgroundColor::kBlue,
{kBackgroundBlue, sizeof(kBackgroundBlue) - 1}},
{TextBackgroundColor::kCyan,
{kBackgroundCyan, sizeof(kBackgroundCyan) - 1}},
{TextBackgroundColor::kGray,
{kBackgroundGray, sizeof(kBackgroundGray) - 1}},
{TextBackgroundColor::kGreen,
{kBackgroundGreen, sizeof(kBackgroundGreen) - 1}},
{TextBackgroundColor::kMagenta,
{kBackgroundMagenta, sizeof(kBackgroundMagenta) - 1}},
{TextBackgroundColor::kRed, {kBackgroundRed, sizeof(kBackgroundRed) - 1}},
{TextBackgroundColor::kWhite,
{kBackgroundWhite, sizeof(kBackgroundWhite) - 1}},
{TextBackgroundColor::kYellow,
{kBackgroundYellow, sizeof(kBackgroundYellow) - 1}},
{TextBackgroundColor::kLightBlue,
{kBackgroundLightBlue, sizeof(kBackgroundLightBlue) - 1}},
{TextBackgroundColor::kLightCyan,
{kBackgroundLightCyan, sizeof(kBackgroundLightCyan) - 1}},
{TextBackgroundColor::kLightGray,
{kBackgroundLightGray, sizeof(kBackgroundLightGray) - 1}},
{TextBackgroundColor::kLightGreen,
{kBackgroundLightGreen, sizeof(kBackgroundLightGreen) - 1}},
{TextBackgroundColor::kLightMagenta,
{kBackgroundLightMagenta, sizeof(kBackgroundLightMagenta) - 1}},
{TextBackgroundColor::kLightRed,
{kBackgroundLightRed, sizeof(kBackgroundLightRed) - 1}},
{TextBackgroundColor::kLightYellow,
{kBackgroundLightYellow, sizeof(kBackgroundLightYellow) - 1}},
};
return background_color_map;
}
// Foreground color codes ------------------------------------------------------
const char kForegroundBlack[] = "\x1b[38;5;0m";
const char kForegroundBlue[] = "\x1b[38;5;4m";
const char kForegroundCyan[] = "\x1b[38;5;6m";
const char kForegroundGray[] = "\x1b[38;5;245m";
const char kForegroundGreen[] = "\x1b[38;5;2m";
const char kForegroundMagenta[] = "\x1b[38;5;5m";
const char kForegroundRed[] = "\x1b[38;5;1m";
const char kForegroundWhite[] = "\x1b[38;5;15m";
const char kForegroundYellow[] = "\x1b[38;5;11m";
const char kForegroundLightBlue[] = "\x1b[38;5;45m";
const char kForegroundLightCyan[] = "\x1b[38;5;87m";
const char kForegroundLightGray[] = "\x1b[38;5;250m";
const char kForegroundLightGreen[] = "\x1b[38;5;10m";
const char kForegroundLightMagenta[] = "\x1b[38;5;170m";
const char kForegroundLightRed[] = "\x1b[38;5;166m";
const char kForegroundLightYellow[] = "\x1b[38;5;190m";
using ForegroundColorMap =
std::map<TextForegroundColor, std::pair<const char*, size_t>>;
static const ForegroundColorMap& GetForegroundColorMap() {
// We subtract 1 from the sizeof the strings to avoid the end null char.
static ForegroundColorMap foreground_color_map = {
{TextForegroundColor::kBlack,
{kForegroundBlack, sizeof(kForegroundBlack) - 1}},
{TextForegroundColor::kBlue,
{kForegroundBlue, sizeof(kForegroundBlue) - 1}},
{TextForegroundColor::kCyan,
{kForegroundCyan, sizeof(kForegroundCyan) - 1}},
{TextForegroundColor::kGray,
{kForegroundGray, sizeof(kForegroundGray) - 1}},
{TextForegroundColor::kGreen,
{kForegroundGreen, sizeof(kForegroundGreen) - 1}},
{TextForegroundColor::kMagenta,
{kForegroundMagenta, sizeof(kForegroundMagenta) - 1}},
{TextForegroundColor::kRed, {kForegroundRed, sizeof(kForegroundRed) - 1}},
{TextForegroundColor::kWhite,
{kForegroundWhite, sizeof(kForegroundWhite) - 1}},
{TextForegroundColor::kYellow,
{kForegroundYellow, sizeof(kForegroundYellow) - 1}},
{TextForegroundColor::kLightBlue,
{kForegroundLightBlue, sizeof(kForegroundLightBlue) - 1}},
{TextForegroundColor::kLightCyan,
{kForegroundLightCyan, sizeof(kForegroundLightCyan) - 1}},
{TextForegroundColor::kLightGray,
{kForegroundLightGray, sizeof(kForegroundLightGray) - 1}},
{TextForegroundColor::kLightGreen,
{kForegroundLightGreen, sizeof(kForegroundLightGreen) - 1}},
{TextForegroundColor::kLightMagenta,
{kForegroundLightMagenta, sizeof(kForegroundLightMagenta) - 1}},
{TextForegroundColor::kLightRed,
{kForegroundLightRed, sizeof(kForegroundLightRed) - 1}},
{TextForegroundColor::kLightYellow,
{kForegroundLightYellow, sizeof(kForegroundLightYellow) - 1}},
};
return foreground_color_map;
}
} // namespace
const char* SyntaxToString(Syntax syntax) {
switch (syntax) {
case Syntax::kNormal: return "kNormal";
case Syntax::kComment: return "kComment";
case Syntax::kHeading: return "kHeading";
case Syntax::kError: return "kError";
case Syntax::kWarning: return "kWarning";
case Syntax::kSpecial: return "kSpecial";
case Syntax::kReversed: return "kReversed";
case Syntax::kVariable: return "kVariable";
}
return nullptr;
}
const char* TextBackgroundColorToString(TextBackgroundColor color) {
switch (color) {
case TextBackgroundColor::kDefault: return "kDefault";
case TextBackgroundColor::kBlack: return "kBlack";
case TextBackgroundColor::kBlue: return "kBlue";
case TextBackgroundColor::kCyan: return "kCyan";
case TextBackgroundColor::kGray: return "kGray";
case TextBackgroundColor::kGreen: return "kGreen";
case TextBackgroundColor::kMagenta: return "kMagenta";
case TextBackgroundColor::kRed: return "kRed";
case TextBackgroundColor::kYellow: return "kYellow";
case TextBackgroundColor::kWhite: return "kWhite";
case TextBackgroundColor::kLightBlue: return "kLightBlue";
case TextBackgroundColor::kLightCyan: return "kLightCyan";
case TextBackgroundColor::kLightGray: return "kLightGray";
case TextBackgroundColor::kLightGreen: return "kLightGreen";
case TextBackgroundColor::kLightMagenta: return "kLightMagenta";
case TextBackgroundColor::kLightRed: return "kLightRed";
case TextBackgroundColor::kLightYellow: return "kLightYellow";
}
return nullptr;
}
const char* TextForegroundColorToString(TextForegroundColor color) {
switch (color) {
case TextForegroundColor::kDefault: return "kDefault";
case TextForegroundColor::kBlack: return "kBlack";
case TextForegroundColor::kBlue: return "kBlue";
case TextForegroundColor::kCyan: return "kCyan";
case TextForegroundColor::kGray: return "kGray";
case TextForegroundColor::kGreen: return "kGreen";
case TextForegroundColor::kMagenta: return "kMagenta";
case TextForegroundColor::kRed: return "kRed";
case TextForegroundColor::kYellow: return "kYellow";
case TextForegroundColor::kWhite: return "kWhite";
case TextForegroundColor::kLightBlue: return "kLightBlue";
case TextForegroundColor::kLightCyan: return "kLightCyan";
case TextForegroundColor::kLightGray: return "kLightGray";
case TextForegroundColor::kLightGreen: return "kLightGreen";
case TextForegroundColor::kLightMagenta: return "kLightMagenta";
case TextForegroundColor::kLightRed: return "kLightRed";
case TextForegroundColor::kLightYellow: return "kLightYellow";
}
return nullptr;
}
OutputBuffer::Span::Span(Syntax s, std::string t) : syntax(s), text(std::move(t)) {}
OutputBuffer::Span::Span(TextForegroundColor fg, std::string t) : foreground(fg), text(std::move(t)) {}
OutputBuffer::OutputBuffer() = default;
OutputBuffer::OutputBuffer(std::string str) { Append(std::move(str)); }
OutputBuffer::OutputBuffer(Syntax syntax, std::string str) {
Append(syntax, std::move(str));
}
OutputBuffer::OutputBuffer(TextForegroundColor color, std::string str) {
spans_.emplace_back(color, std::move(str));
}
OutputBuffer::~OutputBuffer() = default;
// static
OutputBuffer OutputBuffer::WithContents(std::string str) {
OutputBuffer result;
result.Append(std::move(str));
return result;
}
// static
OutputBuffer OutputBuffer::WithContents(Syntax syntax, std::string str) {
OutputBuffer result;
result.Append(syntax, str);
return result;
}
void OutputBuffer::Append(std::string str) {
spans_.emplace_back(Syntax::kNormal, std::move(str));
}
void OutputBuffer::Append(Syntax syntax, std::string str) {
spans_.emplace_back(syntax, std::move(str));
}
void OutputBuffer::Append(OutputBuffer buf) {
for (Span& span : buf.spans_)
spans_.push_back(std::move(span));
}
void OutputBuffer::Append(const Err& err) {
spans_.push_back(Span(Syntax::kNormal, err.msg()));
}
void OutputBuffer::FormatHelp(const std::string& str) {
for (fxl::StringView line :
fxl::SplitString(str, "\n", fxl::kKeepWhitespace, fxl::kSplitWantAll)) {
Syntax syntax;
if (!line.empty() && line[0] != ' ') {
// Nonempty lines beginning with non-whitespace are headings.
syntax = Syntax::kHeading;
} else {
syntax = Syntax::kNormal;
}
spans_.push_back(Span(syntax, line.ToString()));
spans_.push_back(Span(Syntax::kNormal, "\n"));
}
}
void OutputBuffer::WriteToStdout() const {
bool ended_in_newline = false;
for (const Span& span : spans_) {
// We apply syntax first. If normal, we see if any color are to be set.
if (span.syntax != Syntax::kNormal) {
auto syntax_pair = GetSyntaxColorMap().at(span.syntax);
fwrite(syntax_pair.first, 1, syntax_pair.second, stdout);
} else {
if (span.background != TextBackgroundColor::kDefault) {
auto color_pair = GetBackgroundColorMap().at(span.background);
fwrite(color_pair.first, 1, color_pair.second, stdout);
}
if (span.foreground != TextForegroundColor::kDefault) {
auto color_pair = GetForegroundColorMap().at(span.foreground);
fwrite(color_pair.first, 1, color_pair.second, stdout);
}
}
// The actual raw data to be outputted.
fwrite(span.text.data(), 1, span.text.size(), stdout);
// If any formatting was done, reset the attributes.
if ((span.syntax != Syntax::kNormal) ||
(span.background != TextBackgroundColor::kDefault) ||
(span.foreground != TextForegroundColor::kDefault)) {
fwrite(kNormalEscapeCode, 1, strlen(kNormalEscapeCode), stdout);
}
if (!span.text.empty())
ended_in_newline = span.text.back() == '\n';
}
if (!ended_in_newline)
fwrite("\n", 1, 1, stdout);
}
std::string OutputBuffer::AsString() const {
std::string result;
for (const Span& span : spans_)
result.append(span.text);
return result;
}
size_t OutputBuffer::UnicodeCharWidth() const {
size_t result = 0;
for (const Span& span : spans_)
result += ::zxdb::UnicodeCharWidth(span.text);
return result;
}
void OutputBuffer::SetBackgroundColor(TextBackgroundColor color) {
for (Span& span : spans_)
span.background = color;
}
void OutputBuffer::SetForegroundColor(TextForegroundColor color) {
for (Span& span : spans_)
span.foreground = color;
}
void OutputBuffer::Clear() {
spans_.clear();
}
std::string OutputBuffer::GetDebugString() const {
// Normalize so the output is the same even if it was built with different
// sequences of spans.
std::vector<Span> normalized;
for (const auto& cur : spans_) {
if (normalized.empty()) {
normalized.push_back(cur);
} else {
Span& prev = normalized.back();
if (prev.syntax == cur.syntax &&
prev.background == cur.background &&
prev.foreground == cur.foreground)
prev.text.append(cur.text); // Merge: continuation of same format.
else
normalized.push_back(cur); // New format.
}
}
std::string result;
for (size_t i = 0; i < normalized.size(); i++) {
if (i > 0)
result += ", ";
result += SyntaxToString(normalized[i].syntax);
if (normalized[i].background != TextBackgroundColor::kDefault ||
normalized[i].foreground != TextForegroundColor::kDefault) {
result += " ";
result += TextBackgroundColorToString(normalized[i].background);
result += " ";
result += TextForegroundColorToString(normalized[i].foreground);
}
result += " \"";
result += normalized[i].text;
result.push_back('"');
}
return result;
}
} // namespace zxdb