| // 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 "src/developer/debug/zxdb/console/output_buffer.h" |
| |
| #include <cstdio> |
| #include <map> |
| #include <string_view> |
| |
| #include "src/developer/debug/zxdb/common/err.h" |
| #include "src/developer/debug/zxdb/console/string_util.h" |
| #include "src/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. |
| |
| using SyntaxColorMap = std::map<Syntax, std::string_view>; |
| static const SyntaxColorMap& GetSyntaxColorMap() { |
| static SyntaxColorMap syntax_color_map = { |
| {Syntax::kHeading, "\x1b[1m"}, // "[1m" = Bold. |
| {Syntax::kComment, "\x1b[2m"}, // "[2m" = Faint. |
| {Syntax::kError, "\x1b[31m"}, // "[31m" = Red. |
| {Syntax::kWarning, "\x1b[33m"}, // "[33m" = Yellow. |
| {Syntax::kSpecial, "\x1b[34m"}, // "[34m" = Blue. |
| {Syntax::kReversed, "\x1b[7m"}, // "[7m" = Reverse video. |
| {Syntax::kVariable, "\x1b[36m"}, // "[36m" = Cyan. |
| {Syntax::kFileName, "\x1b[95m"}, // "[35m" = Bright Magenta. |
| |
| {Syntax::kKeywordBold, "\x1b[1;33m"}, // "[93m" = Bright yellow. |
| {Syntax::kKeywordNormal, "\x1b[33m"}, // |
| {Syntax::kKeywordDim, "\x1b[2;33m"}, // |
| {Syntax::kOperatorBold, "\x1b[1;33m"}, // "[93m" = Bright yellow |
| {Syntax::kOperatorNormal, "\x1b[33m"}, // |
| {Syntax::kOperatorDim, "\x1b[2;33m"}, // |
| {Syntax::kNumberBold, "\x1b[1;95m"}, // "[95m" = Magenta |
| {Syntax::kNumberNormal, "\x1b[95m"}, // |
| {Syntax::kNumberDim, "\x1b[2;95m"}, // |
| {Syntax::kStringBold, "\x1b[1;95m"}, // "[95m" = Magenta |
| {Syntax::kStringNormal, "\x1b[95m"}, // |
| {Syntax::kStringDim, "\x1b[2;95m"}, // |
| }; |
| |
| return syntax_color_map; |
| } |
| |
| // Background color codes ------------------------------------------------------ |
| |
| using BackgroundColorMap = std::map<TextBackgroundColor, std::string_view>; |
| static const BackgroundColorMap& GetBackgroundColorMap() { |
| static BackgroundColorMap background_color_map = { |
| {TextBackgroundColor::kBlack, "\x1b[48;5;0m"}, |
| {TextBackgroundColor::kBlue, "\x1b[48;5;4m"}, |
| {TextBackgroundColor::kCyan, "\x1b[48;5;6m"}, |
| {TextBackgroundColor::kGray, "\x1b[48;5;245m"}, |
| {TextBackgroundColor::kGreen, "\x1b[48;5;2m"}, |
| {TextBackgroundColor::kMagenta, "\x1b[48;5;5m"}, |
| {TextBackgroundColor::kRed, "\x1b[48;5;1m"}, |
| {TextBackgroundColor::kWhite, "\x1b[48;5;15m"}, |
| {TextBackgroundColor::kYellow, "\x1b[48;5;11m"}, |
| |
| {TextBackgroundColor::kLightBlue, "\x1b[48;5;45m"}, |
| {TextBackgroundColor::kLightCyan, "\x1b[48;5;87m"}, |
| {TextBackgroundColor::kLightGray, "\x1b[48;5;250m"}, |
| {TextBackgroundColor::kLightGreen, "\x1b[48;5;10m"}, |
| {TextBackgroundColor::kLightMagenta, "\x1b[48;5;170m"}, |
| {TextBackgroundColor::kLightRed, "\x1b[48;5;166m"}, |
| {TextBackgroundColor::kLightYellow, "\x1b[48;5;190m"}, |
| }; |
| |
| return background_color_map; |
| } |
| |
| // Foreground color codes ------------------------------------------------------ |
| |
| using ForegroundColorMap = std::map<TextForegroundColor, std::string_view>; |
| 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, "\x1b[38;5;0m"}, |
| {TextForegroundColor::kBlue, "\x1b[38;5;4m"}, |
| {TextForegroundColor::kCyan, "\x1b[38;5;6m"}, |
| {TextForegroundColor::kGray, "\x1b[38;5;245m"}, |
| {TextForegroundColor::kGreen, "\x1b[38;5;2m"}, |
| {TextForegroundColor::kMagenta, "\x1b[38;5;5m"}, |
| {TextForegroundColor::kRed, "\x1b[38;5;1m"}, |
| {TextForegroundColor::kWhite, "\x1b[38;5;15m"}, |
| {TextForegroundColor::kYellow, "\x1b[38;5;11m"}, |
| |
| {TextForegroundColor::kLightBlue, "\x1b[38;5;45m"}, |
| {TextForegroundColor::kLightCyan, "\x1b[38;5;87m"}, |
| {TextForegroundColor::kLightGray, "\x1b[38;5;250m"}, |
| {TextForegroundColor::kLightGreen, "\x1b[38;5;10m"}, |
| {TextForegroundColor::kLightMagenta, "\x1b[38;5;170m"}, |
| {TextForegroundColor::kLightRed, "\x1b[38;5;166m"}, |
| {TextForegroundColor::kLightYellow, "\x1b[38;5;190m"}, |
| }; |
| |
| return foreground_color_map; |
| } |
| |
| // Writes the given string to stdout. |
| void FwriteStringView(std::string_view str) { fwrite(str.data(), 1, str.size(), stdout); } |
| |
| } // 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"; |
| case Syntax::kFileName: |
| return "kFileName"; |
| case Syntax::kKeywordBold: |
| return "kKeywordBold"; |
| case Syntax::kKeywordNormal: |
| return "kKeywordNormal"; |
| case Syntax::kKeywordDim: |
| return "kKeywordDim"; |
| case Syntax::kOperatorBold: |
| return "kOperatorBold"; |
| case Syntax::kOperatorNormal: |
| return "kOperatorNormal"; |
| case Syntax::kOperatorDim: |
| return "kOperatorDim"; |
| case Syntax::kNumberBold: |
| return "kNumberBold"; |
| case Syntax::kNumberNormal: |
| return "kNumberNormal"; |
| case Syntax::kNumberDim: |
| return "kNumberDim"; |
| case Syntax::kStringBold: |
| return "kStringBold"; |
| case Syntax::kStringNormal: |
| return "kStringNormal"; |
| case Syntax::kStringDim: |
| return "kStringDim"; |
| } |
| 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(std::string t, TextForegroundColor fg, TextBackgroundColor bg) |
| : foreground(fg), background(bg), text(std::move(t)) {} |
| |
| OutputBuffer::OutputBuffer() = default; |
| |
| OutputBuffer::OutputBuffer(std::string str, TextForegroundColor fg, TextBackgroundColor bg) { |
| spans_.emplace_back(std::move(str), fg, bg); |
| } |
| |
| OutputBuffer::OutputBuffer(Syntax syntax, std::string str) { |
| spans_.emplace_back(syntax, std::move(str)); |
| } |
| |
| OutputBuffer::~OutputBuffer() = default; |
| |
| void OutputBuffer::Append(std::string str, TextForegroundColor fg, TextBackgroundColor bg) { |
| spans_.emplace_back(std::move(str), fg, bg); |
| } |
| |
| 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::Append(Span span) { spans_.push_back(std::move(span)); } |
| |
| void OutputBuffer::FormatHelp(const std::string& str) { |
| for (auto 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, std::string(line))); |
| 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) { |
| FwriteStringView(GetSyntaxColorMap().at(span.syntax)); |
| } else { |
| if (span.background != TextBackgroundColor::kDefault) |
| FwriteStringView(GetBackgroundColorMap().at(span.background)); |
| if (span.foreground != TextForegroundColor::kDefault) |
| FwriteStringView(GetForegroundColorMap().at(span.foreground)); |
| } |
| |
| // The actual raw data to be outputted. |
| FwriteStringView(span.text); |
| |
| // If any formatting was done, reset the attributes. |
| if ((span.syntax != Syntax::kNormal) || (span.background != TextBackgroundColor::kDefault) || |
| (span.foreground != TextForegroundColor::kDefault)) |
| FwriteStringView(kNormalEscapeCode); |
| |
| if (!span.text.empty()) |
| ended_in_newline = span.text.back() == '\n'; |
| } |
| |
| if (!ended_in_newline) |
| FwriteStringView("\n"); |
| |
| std::fflush(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::TrimTrailingNewlines() { |
| while (!spans_.empty()) { |
| std::string& text = spans_.back().text; |
| size_t last_good = text.find_last_not_of("\n"); |
| if (last_good != std::string::npos) { |
| text.resize(last_good + 1); |
| break; |
| } |
| |
| spans_.pop_back(); |
| } |
| } |
| |
| void OutputBuffer::Clear() { spans_.clear(); } |
| |
| std::vector<OutputBuffer::Span> OutputBuffer::NormalizedSpans() const { |
| 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. |
| } |
| } |
| |
| return normalized; |
| } |
| |
| 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 = NormalizedSpans(); |
| |
| 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; |
| } |
| |
| bool OutputBuffer::operator==(const OutputBuffer& other) const { |
| auto spans = NormalizedSpans(); |
| auto other_spans = other.NormalizedSpans(); |
| |
| if (spans.size() != other_spans.size()) { |
| return false; |
| } |
| |
| for (size_t i = 0; i < spans.size(); i++) { |
| auto& ours = spans[i]; |
| auto& theirs = other_spans[i]; |
| |
| if (ours.syntax != theirs.syntax) { |
| return false; |
| } |
| |
| if (ours.syntax == Syntax::kNormal) { |
| if (ours.foreground != theirs.foreground) { |
| return false; |
| } |
| |
| if (ours.background != theirs.background) { |
| return false; |
| } |
| } |
| |
| if (ours.text != theirs.text) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| } // namespace zxdb |