summaryrefslogtreecommitdiffstats
path: root/src/base/lnav.console.cc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/base/lnav.console.cc494
1 files changed, 494 insertions, 0 deletions
diff --git a/src/base/lnav.console.cc b/src/base/lnav.console.cc
new file mode 100644
index 0000000..a34ebac
--- /dev/null
+++ b/src/base/lnav.console.cc
@@ -0,0 +1,494 @@
+/**
+ * Copyright (c) 2022, Timothy Stack
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * * Neither the name of Timothy Stack nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <algorithm>
+
+#include "lnav.console.hh"
+
+#include "config.h"
+#include "fmt/color.h"
+#include "itertools.hh"
+#include "lnav.console.into.hh"
+#include "log_level_enum.hh"
+#include "pcrepp/pcre2pp.hh"
+#include "snippet_highlighters.hh"
+#include "view_curses.hh"
+
+using namespace lnav::roles::literals;
+
+namespace lnav {
+namespace console {
+
+user_message
+user_message::raw(const attr_line_t& al)
+{
+ user_message retval;
+
+ retval.um_level = level::raw;
+ retval.um_message.append(al);
+ return retval;
+}
+
+user_message
+user_message::error(const attr_line_t& al)
+{
+ user_message retval;
+
+ retval.um_level = level::error;
+ retval.um_message.append(al);
+ return retval;
+}
+
+user_message
+user_message::info(const attr_line_t& al)
+{
+ user_message retval;
+
+ retval.um_level = level::info;
+ retval.um_message.append(al);
+ return retval;
+}
+
+user_message
+user_message::ok(const attr_line_t& al)
+{
+ user_message retval;
+
+ retval.um_level = level::ok;
+ retval.um_message.append(al);
+ return retval;
+}
+
+user_message
+user_message::warning(const attr_line_t& al)
+{
+ user_message retval;
+
+ retval.um_level = level::warning;
+ retval.um_message.append(al);
+ return retval;
+}
+
+attr_line_t
+user_message::to_attr_line(std::set<render_flags> flags) const
+{
+ auto indent = 1;
+ attr_line_t retval;
+
+ if (this->um_level == level::warning) {
+ indent = 3;
+ }
+
+ if (flags.count(render_flags::prefix)) {
+ switch (this->um_level) {
+ case level::raw:
+ break;
+ case level::ok:
+ retval.append(lnav::roles::ok("\u2714 "));
+ break;
+ case level::info:
+ retval.append("\u24d8 info"_info).append(": ");
+ break;
+ case level::warning:
+ retval.append(lnav::roles::warning("\u26a0 warning"))
+ .append(": ");
+ break;
+ case level::error:
+ retval.append(lnav::roles::error("\u2718 error")).append(": ");
+ break;
+ }
+ }
+
+ retval.append(this->um_message).append("\n");
+ if (!this->um_reason.empty()) {
+ bool first_line = true;
+ for (const auto& line : this->um_reason.split_lines()) {
+ auto role = this->um_level == level::error ? role_t::VCR_ERROR
+ : role_t::VCR_WARNING;
+ attr_line_t prefix;
+
+ if (first_line) {
+ prefix.append(indent, ' ')
+ .append("reason", VC_ROLE.value(role))
+ .append(": ");
+ first_line = false;
+ } else {
+ prefix.append(" | ", VC_ROLE.value(role))
+ .append(indent, ' ');
+ }
+ retval.append(prefix).append(line).append("\n");
+ }
+ }
+ if (!this->um_snippets.empty()) {
+ for (const auto& snip : this->um_snippets) {
+ attr_line_t header;
+
+ header.append(" --> "_snippet_border)
+ .append(lnav::roles::file(snip.s_location.sl_source.get()));
+ if (snip.s_location.sl_line_number > 0) {
+ header.append(":").appendf(FMT_STRING("{}"),
+ snip.s_location.sl_line_number);
+ }
+ retval.append(header).append("\n");
+ if (!snip.s_content.blank()) {
+ auto snippet_lines = snip.s_content.split_lines();
+ auto longest_line_length = snippet_lines
+ | lnav::itertools::map(&attr_line_t::utf8_length_or_length)
+ | lnav::itertools::max(40);
+
+ for (auto& line : snippet_lines) {
+ line.pad_to(longest_line_length);
+ retval.append(" | "_snippet_border)
+ .append(line)
+ .append("\n");
+ }
+ }
+ }
+ }
+ if (!this->um_notes.empty()) {
+ for (const auto& note : this->um_notes) {
+ bool first_line = true;
+ for (const auto& line : note.split_lines()) {
+ attr_line_t prefix;
+
+ if (first_line) {
+ prefix.append(" ="_snippet_border)
+ .append(indent, ' ')
+ .append("note"_snippet_border)
+ .append(": ");
+ first_line = false;
+ } else {
+ prefix.append(" ").append(indent, ' ');
+ }
+
+ retval.append(prefix).append(line).append("\n");
+ }
+ }
+ }
+ if (!this->um_help.empty()) {
+ bool first_line = true;
+ for (const auto& line : this->um_help.split_lines()) {
+ attr_line_t prefix;
+
+ if (first_line) {
+ prefix.append(" ="_snippet_border)
+ .append(indent, ' ')
+ .append("help"_snippet_border)
+ .append(": ");
+ first_line = false;
+ } else {
+ prefix.append(" ");
+ }
+
+ retval.append(prefix).append(line).append("\n");
+ }
+ }
+
+ return retval;
+}
+
+static nonstd::optional<fmt::terminal_color>
+curses_color_to_terminal_color(int curses_color)
+{
+ switch (curses_color) {
+ case COLOR_BLACK:
+ return fmt::terminal_color::black;
+ case COLOR_CYAN:
+ return fmt::terminal_color::cyan;
+ case COLOR_WHITE:
+ return fmt::terminal_color::white;
+ case COLOR_MAGENTA:
+ return fmt::terminal_color::magenta;
+ case COLOR_BLUE:
+ return fmt::terminal_color::blue;
+ case COLOR_YELLOW:
+ return fmt::terminal_color::yellow;
+ case COLOR_GREEN:
+ return fmt::terminal_color::green;
+ case COLOR_RED:
+ return fmt::terminal_color::red;
+ default:
+ return nonstd::nullopt;
+ }
+}
+
+void
+println(FILE* file, const attr_line_t& al)
+{
+ const auto& str = al.get_string();
+
+ if (getenv("NO_COLOR") != nullptr
+ || (!isatty(fileno(file)) && getenv("YES_COLOR") == nullptr))
+ {
+ fmt::print(file, "{}\n", str);
+ return;
+ }
+
+ std::set<size_t> points = {0, static_cast<size_t>(al.length())};
+
+ for (const auto& attr : al.get_attrs()) {
+ if (!attr.sa_range.is_valid()) {
+ continue;
+ }
+ points.insert(attr.sa_range.lr_start);
+ if (attr.sa_range.lr_end > 0) {
+ points.insert(attr.sa_range.lr_end);
+ }
+ }
+
+ nonstd::optional<size_t> last_point;
+ for (const auto& point : points) {
+ if (!last_point) {
+ last_point = point;
+ continue;
+ }
+ auto default_fg_style = fmt::text_style{};
+ auto default_bg_style = fmt::text_style{};
+ auto line_style = fmt::text_style{};
+ auto fg_style = fmt::text_style{};
+ auto start = last_point.value();
+
+ for (const auto& attr : al.get_attrs()) {
+ if (!attr.sa_range.contains(start)
+ && !attr.sa_range.contains(point - 1))
+ {
+ continue;
+ }
+
+ try {
+ if (attr.sa_type == &VC_BACKGROUND) {
+ auto saw = string_attr_wrapper<int64_t>(&attr);
+ auto color_opt = curses_color_to_terminal_color(saw.get());
+
+ if (color_opt) {
+ line_style |= fmt::bg(color_opt.value());
+ }
+ } else if (attr.sa_type == &VC_FOREGROUND) {
+ auto saw = string_attr_wrapper<int64_t>(&attr);
+ auto color_opt = curses_color_to_terminal_color(saw.get());
+
+ if (color_opt) {
+ fg_style = fmt::fg(color_opt.value());
+ }
+ } else if (attr.sa_type == &VC_STYLE) {
+ auto saw = string_attr_wrapper<text_attrs>(&attr);
+ auto style = saw.get();
+
+ if (style.ta_attrs & A_REVERSE) {
+ line_style |= fmt::emphasis::reverse;
+ }
+ if (style.ta_attrs & A_BOLD) {
+ line_style |= fmt::emphasis::bold;
+ }
+ if (style.ta_attrs & A_UNDERLINE) {
+ line_style |= fmt::emphasis::underline;
+ }
+ if (style.ta_fg_color) {
+ auto color_opt = curses_color_to_terminal_color(
+ style.ta_fg_color.value());
+
+ if (color_opt) {
+ fg_style = fmt::fg(color_opt.value());
+ }
+ }
+ if (style.ta_bg_color) {
+ auto color_opt = curses_color_to_terminal_color(
+ style.ta_bg_color.value());
+
+ if (color_opt) {
+ line_style |= fmt::bg(color_opt.value());
+ }
+ }
+ } else if (attr.sa_type == &SA_LEVEL) {
+ auto level = static_cast<log_level_t>(
+ attr.sa_value.get<int64_t>());
+
+ switch (level) {
+ case LEVEL_FATAL:
+ case LEVEL_CRITICAL:
+ case LEVEL_ERROR:
+ line_style |= fmt::fg(fmt::terminal_color::red);
+ break;
+ case LEVEL_WARNING:
+ line_style |= fmt::fg(fmt::terminal_color::yellow);
+ break;
+ default:
+ break;
+ }
+ } else if (attr.sa_type == &VC_ROLE) {
+ auto saw = string_attr_wrapper<role_t>(&attr);
+ auto role = saw.get();
+
+ switch (role) {
+ case role_t::VCR_TEXT:
+ case role_t::VCR_IDENTIFIER:
+ break;
+ case role_t::VCR_SEARCH:
+ line_style |= fmt::emphasis::reverse;
+ break;
+ case role_t::VCR_ERROR:
+ line_style |= fmt::fg(fmt::terminal_color::red)
+ | fmt::emphasis::bold;
+ break;
+ case role_t::VCR_WARNING:
+ case role_t::VCR_RE_REPEAT:
+ line_style |= fmt::fg(fmt::terminal_color::yellow);
+ break;
+ case role_t::VCR_COMMENT:
+ line_style |= fmt::fg(fmt::terminal_color::green);
+ break;
+ case role_t::VCR_SNIPPET_BORDER:
+ line_style |= fmt::fg(fmt::terminal_color::cyan);
+ break;
+ case role_t::VCR_OK:
+ line_style |= fmt::emphasis::bold
+ | fmt::fg(fmt::terminal_color::green);
+ break;
+ case role_t::VCR_INFO:
+ case role_t::VCR_STATUS:
+ line_style |= fmt::emphasis::bold
+ | fmt::fg(fmt::terminal_color::magenta);
+ break;
+ case role_t::VCR_KEYWORD:
+ case role_t::VCR_RE_SPECIAL:
+ line_style |= fmt::emphasis::bold
+ | fmt::fg(fmt::terminal_color::cyan);
+ break;
+ case role_t::VCR_STRING:
+ line_style |= fmt::fg(fmt::terminal_color::magenta);
+ break;
+ case role_t::VCR_VARIABLE:
+ line_style |= fmt::emphasis::underline;
+ break;
+ case role_t::VCR_SYMBOL:
+ case role_t::VCR_NUMBER:
+ case role_t::VCR_FILE:
+ line_style |= fmt::emphasis::bold;
+ break;
+ case role_t::VCR_H1:
+ line_style |= fmt::emphasis::bold
+ | fmt::fg(fmt::terminal_color::magenta);
+ break;
+ case role_t::VCR_H2:
+ line_style |= fmt::emphasis::bold;
+ break;
+ case role_t::VCR_H3:
+ case role_t::VCR_H4:
+ case role_t::VCR_H5:
+ case role_t::VCR_H6:
+ line_style |= fmt::emphasis::underline;
+ break;
+ case role_t::VCR_LIST_GLYPH:
+ line_style |= fmt::fg(fmt::terminal_color::yellow);
+ break;
+ case role_t::VCR_QUOTED_CODE:
+ default_fg_style
+ = fmt::fg(fmt::terminal_color::white);
+ default_bg_style
+ = fmt::bg(fmt::terminal_color::black);
+ break;
+ case role_t::VCR_LOW_THRESHOLD:
+ line_style |= fmt::bg(fmt::terminal_color::green);
+ break;
+ case role_t::VCR_MED_THRESHOLD:
+ line_style |= fmt::bg(fmt::terminal_color::yellow);
+ break;
+ case role_t::VCR_HIGH_THRESHOLD:
+ line_style |= fmt::bg(fmt::terminal_color::red);
+ break;
+ default:
+ // log_debug("missing role handler %d", (int) role);
+ break;
+ }
+ }
+ } catch (const fmt::format_error& e) {
+ log_error("style error: %s", e.what());
+ }
+ }
+
+ if (!line_style.has_foreground() && fg_style.has_foreground()) {
+ line_style |= fg_style;
+ }
+ if (!line_style.has_foreground() && default_fg_style.has_foreground()) {
+ line_style |= default_fg_style;
+ }
+ if (!line_style.has_background() && default_bg_style.has_background()) {
+ line_style |= default_bg_style;
+ }
+
+ if (start < str.size()) {
+ auto actual_end = std::min(str.size(), static_cast<size_t>(point));
+ fmt::print(file,
+ line_style,
+ FMT_STRING("{}"),
+ str.substr(start, actual_end - start));
+ }
+ last_point = point;
+ }
+ fmt::print(file, "\n");
+}
+
+void
+print(FILE* file, const user_message& um)
+{
+ auto al = um.to_attr_line();
+
+ if (endswith(al.get_string(), "\n")) {
+ al.erase(al.length() - 1);
+ }
+ println(file, al);
+}
+
+user_message
+to_user_message(intern_string_t src, const lnav::pcre2pp::compile_error& ce)
+{
+ attr_line_t pcre_error_content{ce.ce_pattern};
+
+ lnav::snippets::regex_highlighter(pcre_error_content,
+ pcre_error_content.length(),
+ line_range{
+ 0,
+ (int) pcre_error_content.length(),
+ });
+ pcre_error_content.append("\n")
+ .append(ce.ce_offset, ' ')
+ .append(lnav::roles::error("^ "))
+ .append(lnav::roles::error(ce.get_message()))
+ .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
+
+ return user_message::error(
+ attr_line_t()
+ .append_quoted(ce.ce_pattern)
+ .append(" is not a valid regular expression"))
+ .with_reason(ce.get_message())
+ .with_snippet(lnav::console::snippet::from(src, pcre_error_content));
+}
+
+} // namespace console
+} // namespace lnav