summaryrefslogtreecommitdiffstats
path: root/src/base
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/base/CMakeLists.txt15
-rw-r--r--src/base/Makefile.am16
-rw-r--r--src/base/ansi_scrubber.cc325
-rw-r--r--src/base/ansi_scrubber.hh7
-rw-r--r--src/base/ansi_vars.hh41
-rw-r--r--src/base/attr_line.builder.cc54
-rw-r--r--src/base/attr_line.builder.hh19
-rw-r--r--src/base/attr_line.cc201
-rw-r--r--src/base/attr_line.hh300
-rw-r--r--src/base/attr_line.tests.cc24
-rw-r--r--src/base/auto_fd.cc248
-rw-r--r--src/base/auto_fd.hh168
-rw-r--r--src/base/auto_mem.hh12
-rw-r--r--src/base/auto_pid.hh21
-rw-r--r--src/base/color_spaces.cc176
-rw-r--r--src/base/color_spaces.hh96
-rw-r--r--src/base/date_time_scanner.cc117
-rw-r--r--src/base/date_time_scanner.cfg.hh43
-rw-r--r--src/base/date_time_scanner.hh19
-rw-r--r--src/base/from_trait.hh40
-rw-r--r--src/base/fs_util.cc62
-rw-r--r--src/base/fs_util.hh34
-rw-r--r--src/base/future_util.hh43
-rw-r--r--src/base/humanize.time.cc21
-rw-r--r--src/base/humanize.time.hh10
-rw-r--r--src/base/intern_string.cc166
-rw-r--r--src/base/intern_string.hh144
-rw-r--r--src/base/intern_string.tests.cc44
-rw-r--r--src/base/is_utf8.cc13
-rw-r--r--src/base/is_utf8.hh1
-rw-r--r--src/base/itertools.hh96
-rw-r--r--src/base/keycodes.hh42
-rw-r--r--src/base/line_range.hh164
-rw-r--r--src/base/lnav.console.cc143
-rw-r--r--src/base/lnav.console.hh28
-rw-r--r--src/base/lnav.gzip.hh15
-rw-r--r--src/base/lnav_log.cc2
-rw-r--r--src/base/map_util.hh126
-rw-r--r--src/base/math_util.hh82
-rw-r--r--src/base/network.tcp.hh1
-rw-r--r--src/base/piper.file.cc81
-rw-r--r--src/base/piper.file.hh (renamed from src/piper_proc.hh)78
-rw-r--r--src/base/string_attr_type.cc3
-rw-r--r--src/base/string_attr_type.hh83
-rw-r--r--src/base/string_util.cc124
-rw-r--r--src/base/string_util.hh68
-rw-r--r--src/base/string_util.tests.cc43
-rw-r--r--src/base/strnatcmp.c4
-rw-r--r--src/base/time_util.cc116
-rw-r--r--src/base/time_util.hh64
-rw-r--r--src/base/types.hh35
51 files changed, 3181 insertions, 697 deletions
diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt
index aa4143f..2ff04c6 100644
--- a/src/base/CMakeLists.txt
+++ b/src/base/CMakeLists.txt
@@ -4,7 +4,9 @@ add_library(
ansi_scrubber.cc
attr_line.cc
attr_line.builder.cc
+ auto_fd.cc
auto_pid.cc
+ color_spaces.cc
date_time_scanner.cc
fs_util.cc
humanize.cc
@@ -18,6 +20,7 @@ add_library(
lnav_log.cc
network.tcp.cc
paths.cc
+ piper.file.cc
snippet_highlighters.cc
string_attr_type.cc
string_util.cc
@@ -25,14 +28,18 @@ add_library(
time_util.cc
ansi_scrubber.hh
+ ansi_vars.hh
attr_line.hh
attr_line.builder.hh
auto_fd.hh
auto_mem.hh
auto_pid.hh
bus.hh
+ color_spaces.hh
+ date_time_scanner.cfg.hh
date_time_scanner.hh
enum_util.hh
+ from_trait.hh
fs_util.hh
func_util.hh
future_util.hh
@@ -45,26 +52,32 @@ add_library(
is_utf8.hh
isc.hh
itertools.hh
+ keycodes.hh
+ line_range.hh
lnav.console.hh
lnav.console.into.hh
log_level_enum.hh
lrucache.hpp
+ map_util.hh
math_util.hh
network.tcp.hh
paths.hh
+ piper.file.hh
result.h
snippet_highlighters.hh
string_attr_type.hh
strnatcmp.h
time_util.hh
+ types.hh
../third-party/xxHash/xxhash.h
../third-party/xxHash/xxhash.c
)
target_include_directories(base PUBLIC . .. ../third-party
+ ../third-party/date/include
${CMAKE_CURRENT_BINARY_DIR}/..)
-target_link_libraries(base cppfmt cppscnlib pcrepp ncurses::libcurses pthread)
+target_link_libraries(base cppfmt cppscnlib pcrepp ncurses::libcurses pthread lnavdt datepp)
add_executable(
test_base
diff --git a/src/base/Makefile.am b/src/base/Makefile.am
index 4a459a6..105913f 100644
--- a/src/base/Makefile.am
+++ b/src/base/Makefile.am
@@ -7,6 +7,7 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/src/ \
-I$(top_srcdir)/src/third-party \
-I$(top_srcdir)/src/fmtlib \
+ -I$(top_srcdir)/src/third-party/date/include \
-I$(top_srcdir)/src/third-party/scnlib/include \
$(LIBARCHIVE_CFLAGS) \
$(READLINE_CFLAGS) \
@@ -22,15 +23,19 @@ noinst_LIBRARIES = libbase.a
noinst_HEADERS = \
ansi_scrubber.hh \
+ ansi_vars.hh \
attr_line.hh \
attr_line.builder.hh \
auto_fd.hh \
auto_mem.hh \
auto_pid.hh \
bus.hh \
+ color_spaces.hh \
+ date_time_scanner.cfg.hh \
date_time_scanner.hh \
enum_util.hh \
file_range.hh \
+ from_trait.hh \
fs_util.hh \
func_util.hh \
future_util.hh \
@@ -43,28 +48,35 @@ noinst_HEADERS = \
is_utf8.hh \
isc.hh \
itertools.hh \
+ keycodes.hh \
+ line_range.hh \
lnav_log.hh \
lnav.console.hh \
lnav.console.into.hh \
lnav.gzip.hh \
log_level_enum.hh \
lrucache.hpp \
+ map_util.hh \
math_util.hh \
network.tcp.hh \
opt_util.hh \
paths.hh \
+ piper.file.hh \
result.h \
snippet_highlighters.hh \
string_attr_type.hh \
string_util.hh \
strnatcmp.h \
- time_util.hh
+ time_util.hh \
+ types.hh
libbase_a_SOURCES = \
ansi_scrubber.cc \
attr_line.cc \
attr_line.builder.cc \
+ auto_fd.cc \
auto_pid.cc \
+ color_spaces.cc \
date_time_scanner.cc \
fs_util.cc \
humanize.cc \
@@ -78,6 +90,7 @@ libbase_a_SOURCES = \
lnav_log.cc \
network.tcp.cc \
paths.cc \
+ piper.file.cc \
snippet_highlighters.cc \
string_attr_type.cc \
string_util.cc \
@@ -103,6 +116,7 @@ test_base_SOURCES = \
test_base_LDADD = \
libbase.a \
../fmtlib/libcppfmt.a \
+ ../third-party/date/src/libdatepp.a \
../third-party/scnlib/src/libscnlib.a \
../pcrepp/libpcrepp.a
diff --git a/src/base/ansi_scrubber.cc b/src/base/ansi_scrubber.cc
index 98f6c96..ec34328 100644
--- a/src/base/ansi_scrubber.cc
+++ b/src/base/ansi_scrubber.cc
@@ -33,6 +33,8 @@
#include "ansi_scrubber.hh"
+#include "ansi_vars.hh"
+#include "base/lnav_log.hh"
#include "base/opt_util.hh"
#include "config.h"
#include "pcrepp/pcre2pp.hh"
@@ -43,7 +45,7 @@ static const lnav::pcre2pp::code&
ansi_regex()
{
static const auto retval = lnav::pcre2pp::code::from_const(
- "\x1b\\[([\\d=;\\?]*)([a-zA-Z])|(?:\\X\x08\\X)+");
+ R"(\x1b\[([\d=;\?]*)([a-zA-Z])|\x1b\](\d+);(.*?)(?:\x07|\x1b\\)|(?:\X\x08\X)+|(\x16+))");
return retval;
}
@@ -120,11 +122,18 @@ void
scrub_ansi_string(std::string& str, string_attrs_t* sa)
{
static thread_local auto md = lnav::pcre2pp::match_data::unitialized();
- const auto& regex = ansi_regex();
- int64_t origin_offset = 0;
- int last_origin_offset_end = 0;
+ static const auto semi_pred = string_fragment::tag1{';'};
- replace(str.begin(), str.end(), '\0', ' ');
+ const auto& regex = ansi_regex();
+ nonstd::optional<std::string> href;
+ size_t href_start = 0;
+ string_attrs_t tmp_sa;
+ size_t cp_dst = std::string::npos;
+ size_t cp_start = std::string::npos;
+ int last_origin_end = 0;
+ int erased = 0;
+
+ std::replace(str.begin(), str.end(), '\0', ' ');
auto matcher = regex.capture_from(str).into(md);
while (true) {
auto match_res = matcher.matches(PCRE2_NO_UTF_CHECK);
@@ -140,13 +149,22 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa)
const auto sf = md[0].value();
auto bs_index_res = sf.codepoint_to_byte_index(1);
+ if (cp_dst != std::string::npos) {
+ auto cp_len = sf.sf_begin - cp_start;
+ memmove(&str[cp_dst], &str[cp_start], cp_len);
+ cp_dst += cp_len;
+ } else {
+ cp_dst = sf.sf_begin;
+ }
+
if (sf.length() >= 3 && bs_index_res.isOk()
&& sf[bs_index_res.unwrap()] == '\b')
{
- ssize_t fill_index = sf.sf_begin;
+ ssize_t fill_index = cp_dst;
line_range bold_range;
line_range ul_range;
auto sub_sf = sf;
+ auto mid_sf = string_fragment();
while (!sub_sf.empty()) {
auto lhs_opt = sub_sf.consume_codepoint();
@@ -164,12 +182,13 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa)
return;
}
auto rhs_pair = rhs_opt.value();
- sub_sf = rhs_pair.second;
if (lhs_pair.first == '_' || rhs_pair.first == '_') {
if (sa != nullptr && bold_range.is_valid()) {
- sa->emplace_back(bold_range,
- VC_STYLE.value(text_attrs{A_BOLD}));
+ shift_string_attrs(
+ *sa, bold_range.lr_start, -bold_range.length() * 2);
+ tmp_sa.emplace_back(bold_range,
+ VC_STYLE.value(text_attrs{A_BOLD}));
bold_range.clear();
}
if (ul_range.is_valid()) {
@@ -183,9 +202,13 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa)
ww898::utf::utf8::write(cp, [&str, &fill_index](auto ch) {
str[fill_index++] = ch;
});
- } else {
+ } else if (lhs_pair.first == rhs_pair.first
+ && !fmt::v10::detail::needs_escape(lhs_pair.first))
+ {
if (sa != nullptr && ul_range.is_valid()) {
- sa->emplace_back(
+ shift_string_attrs(
+ *sa, ul_range.lr_start, -ul_range.length() * 2);
+ tmp_sa.emplace_back(
ul_range, VC_STYLE.value(text_attrs{A_UNDERLINE}));
ul_range.clear();
}
@@ -204,57 +227,91 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa)
log_error("invalid UTF-8 at %d", sf.sf_begin);
return;
}
+ } else {
+ mid_sf = mid_pair.second;
+ break;
}
+ sub_sf = rhs_pair.second;
}
- auto output_size = fill_index - sf.sf_begin;
- auto erased_size = sf.length() - output_size;
-
- if (sa != nullptr) {
-#if 0
- shift_string_attrs(
- *sa, caps->c_begin + sf.length() / 3, -erased_size);
-#endif
- sa->emplace_back(line_range{last_origin_offset_end,
- sf.sf_begin + (int) output_size},
- SA_ORIGIN_OFFSET.value(origin_offset));
- }
-
+ auto output_size = fill_index - cp_dst;
if (sa != nullptr && ul_range.is_valid()) {
- sa->emplace_back(ul_range,
- VC_STYLE.value(text_attrs{A_UNDERLINE}));
+ shift_string_attrs(
+ *sa, ul_range.lr_start, -ul_range.length() * 2);
+ tmp_sa.emplace_back(ul_range,
+ VC_STYLE.value(text_attrs{A_UNDERLINE}));
ul_range.clear();
}
if (sa != nullptr && bold_range.is_valid()) {
- sa->emplace_back(bold_range,
- VC_STYLE.value(text_attrs{A_BOLD}));
+ shift_string_attrs(
+ *sa, bold_range.lr_start, -bold_range.length() * 2);
+ tmp_sa.emplace_back(bold_range,
+ VC_STYLE.value(text_attrs{A_BOLD}));
bold_range.clear();
}
-
- str.erase(str.begin() + fill_index, str.begin() + sf.sf_end);
- last_origin_offset_end = sf.sf_begin + output_size;
- origin_offset += erased_size;
- matcher.reload_input(str, last_origin_offset_end);
+ if (sa != nullptr && output_size > 0 && cp_dst > 0) {
+ tmp_sa.emplace_back(
+ line_range{
+ (int) last_origin_end,
+ (int) cp_dst + (int) output_size,
+ },
+ SA_ORIGIN_OFFSET.value(erased));
+ }
+ last_origin_end = cp_dst + output_size;
+ cp_dst = fill_index;
+ cp_start = sub_sf.sf_begin;
+ erased += sf.length() - output_size;
continue;
}
- auto seq = md[1].value();
- auto terminator = md[2].value();
struct line_range lr;
- bool has_attrs = false;
text_attrs attrs;
+ bool has_attrs = false;
nonstd::optional<role_t> role;
- size_t lpc;
-
- switch (terminator[0]) {
- case 'm':
- for (lpc = seq.sf_begin;
- lpc != std::string::npos && lpc < (size_t) seq.sf_end;)
- {
- auto ansi_code_res = scn::scan_value<int>(
- scn::string_view{&str[lpc], &str[seq.sf_end]});
- if (ansi_code_res) {
+ if (md[3]) {
+ auto osc_id = scn::scan_value<int32_t>(md[3]->to_string_view());
+
+ if (osc_id) {
+ switch (osc_id.value()) {
+ case 8:
+ auto split_res = md[4]->split_pair(semi_pred);
+ if (split_res) {
+ // auto params = split_res->first;
+ auto uri = split_res->second;
+
+ if (href) {
+ if (sa != nullptr) {
+ tmp_sa.emplace_back(
+ line_range{
+ (int) href_start,
+ (int) cp_dst,
+ },
+ VC_HYPERLINK.value(href.value()));
+ }
+ href = nonstd::nullopt;
+ }
+ if (!uri.empty()) {
+ href = uri.to_string();
+ href_start = cp_dst;
+ }
+ }
+ break;
+ }
+ }
+ } else if (md[1]) {
+ auto seq = md[1].value();
+ auto terminator = md[2].value();
+
+ switch (terminator[0]) {
+ case 'm':
+ while (!seq.empty()) {
+ auto ansi_code_res
+ = scn::scan_value<int>(seq.to_string_view());
+
+ if (!ansi_code_res) {
+ break;
+ }
auto ansi_code = ansi_code_res.value();
if (90 <= ansi_code && ansi_code <= 97) {
ansi_code -= 60;
@@ -266,6 +323,39 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa)
if (40 <= ansi_code && ansi_code <= 47) {
attrs.ta_bg_color = ansi_code - 40;
}
+ if (ansi_code == 38 || ansi_code == 48) {
+ auto color_code_pair
+ = seq.split_when(semi_pred).second.split_pair(
+ semi_pred);
+ if (!color_code_pair) {
+ break;
+ }
+ auto color_type = scn::scan_value<int>(
+ color_code_pair->first.to_string_view());
+ if (!color_type.has_value()) {
+ break;
+ }
+ if (color_type.value() == 2) {
+ } else if (color_type.value() == 5) {
+ auto color_index_pair
+ = color_code_pair->second.split_when(
+ semi_pred);
+ auto color_index = scn::scan_value<short>(
+ color_index_pair.first.to_string_view());
+ if (!color_index.has_value()
+ || color_index.value() < 0
+ || color_index.value() > 255)
+ {
+ break;
+ }
+ if (ansi_code == 38) {
+ attrs.ta_fg_color = color_index.value();
+ } else {
+ attrs.ta_bg_color = color_index.value();
+ }
+ seq = color_index_pair.second;
+ }
+ }
switch (ansi_code) {
case 1:
attrs.ta_attrs |= A_BOLD;
@@ -283,90 +373,107 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa)
attrs.ta_attrs |= A_REVERSE;
break;
}
+ auto split_pair = seq.split_when(semi_pred);
+ seq = split_pair.second;
}
- lpc = str.find(';', lpc);
- if (lpc != std::string::npos) {
- lpc += 1;
- }
- }
- has_attrs = true;
- break;
+ has_attrs = true;
+ break;
- case 'C': {
- auto spaces_res
- = scn::scan_value<unsigned int>(seq.to_string_view());
+#if 0
+ case 'C': {
+ auto spaces_res
+ = scn::scan_value<unsigned int>(seq.to_string_view());
- if (spaces_res && spaces_res.value() > 0) {
- str.insert((std::string::size_type) sf.sf_end,
- spaces_res.value(),
- ' ');
+ if (spaces_res && spaces_res.value() > 0) {
+ str.insert((std::string::size_type) sf.sf_end,
+ spaces_res.value(),
+ ' ');
+ }
+ break;
}
- break;
- }
- case 'H': {
- unsigned int row = 0, spaces = 0;
+ case 'H': {
+ unsigned int row = 0, spaces = 0;
- if (scn::scan(seq.to_string_view(), "{};{}", row, spaces)
- && spaces > 1)
- {
- int ispaces = spaces - 1;
- if (ispaces > sf.sf_begin) {
- str.insert((unsigned long) sf.sf_end,
- ispaces - sf.sf_begin,
- ' ');
+ if (scn::scan(seq.to_string_view(), "{};{}", row, spaces)
+ && spaces > 1)
+ {
+ int ispaces = spaces - 1;
+ if (ispaces > sf.sf_begin) {
+ str.insert((unsigned long) sf.sf_end,
+ ispaces - sf.sf_begin,
+ ' ');
+ }
}
+ break;
}
- break;
- }
+#endif
- case 'O': {
- auto role_res = scn::scan_value<int>(seq.to_string_view());
+ case 'O': {
+ auto role_res = scn::scan_value<int>(seq.to_string_view());
- if (role_res) {
- role_t role_tmp = (role_t) role_res.value();
- if (role_tmp > role_t::VCR_NONE
- && role_tmp < role_t::VCR__MAX)
- {
- role = role_tmp;
- has_attrs = true;
+ if (role_res) {
+ role_t role_tmp = (role_t) role_res.value();
+ if (role_tmp > role_t::VCR_NONE
+ && role_tmp < role_t::VCR__MAX)
+ {
+ role = role_tmp;
+ has_attrs = true;
+ }
}
+ break;
}
- break;
}
}
- str.erase(str.begin() + sf.sf_begin, str.begin() + sf.sf_end);
- if (sa != nullptr) {
- shift_string_attrs(*sa, sf.sf_begin, -sf.length());
-
- if (has_attrs) {
- for (auto rit = sa->rbegin(); rit != sa->rend(); rit++) {
- if (rit->sa_range.lr_end != -1) {
- continue;
+ if (md[1] || md[3] || md[5]) {
+ if (sa != nullptr) {
+ shift_string_attrs(*sa, sf.sf_begin, -sf.length());
+
+ if (has_attrs) {
+ for (auto rit = tmp_sa.rbegin(); rit != tmp_sa.rend();
+ rit++)
+ {
+ if (rit->sa_range.lr_end != -1) {
+ continue;
+ }
+ rit->sa_range.lr_end = cp_dst;
+ }
+ lr.lr_start = cp_dst;
+ lr.lr_end = -1;
+ if (!attrs.empty()) {
+ tmp_sa.emplace_back(lr, VC_STYLE.value(attrs));
}
- rit->sa_range.lr_end = sf.sf_begin;
+ role | [&lr, &tmp_sa](role_t r) {
+ tmp_sa.emplace_back(lr, VC_ROLE.value(r));
+ };
}
- lr.lr_start = sf.sf_begin;
- lr.lr_end = -1;
- if (attrs.ta_attrs || attrs.ta_fg_color || attrs.ta_bg_color) {
- sa->emplace_back(lr, VC_STYLE.value(attrs));
+ if (cp_dst > 0) {
+ tmp_sa.emplace_back(
+ line_range{
+ (int) last_origin_end,
+ (int) cp_dst,
+ },
+ SA_ORIGIN_OFFSET.value(erased));
}
- role | [&lr, &sa](role_t r) {
- sa->emplace_back(lr, VC_ROLE.value(r));
- };
+ last_origin_end = cp_dst;
}
- sa->emplace_back(line_range{last_origin_offset_end, sf.sf_begin},
- SA_ORIGIN_OFFSET.value(origin_offset));
- last_origin_offset_end = sf.sf_begin;
- origin_offset += sf.length();
+ erased += sf.length();
}
-
- matcher.reload_input(str, sf.sf_begin);
+ cp_start = sf.sf_end;
}
- if (sa != nullptr && last_origin_offset_end > 0) {
- sa->emplace_back(line_range{last_origin_offset_end, (int) str.size()},
- SA_ORIGIN_OFFSET.value(origin_offset));
+ if (cp_dst != std::string::npos) {
+ auto cp_len = str.size() - cp_start;
+ memmove(&str[cp_dst], &str[cp_start], cp_len);
+ cp_dst += cp_len;
+ str.resize(cp_dst);
+ }
+ if (sa != nullptr && last_origin_end > 0 && last_origin_end != str.size()) {
+ tmp_sa.emplace_back(line_range{(int) last_origin_end, (int) str.size()},
+ SA_ORIGIN_OFFSET.value(erased));
+ }
+ if (sa != nullptr) {
+ sa->insert(sa->end(), tmp_sa.begin(), tmp_sa.end());
}
}
diff --git a/src/base/ansi_scrubber.hh b/src/base/ansi_scrubber.hh
index b832e17..ce4eadf 100644
--- a/src/base/ansi_scrubber.hh
+++ b/src/base/ansi_scrubber.hh
@@ -36,7 +36,6 @@
#include <string>
#include "attr_line.hh"
-#include "shlex.resolver.hh"
#define ANSI_CSI "\x1b["
#define ANSI_CHAR_ATTR "m"
@@ -66,10 +65,4 @@ void scrub_ansi_string(std::string& str, string_attrs_t* sa);
size_t erase_ansi_escapes(string_fragment input);
-/**
- * Populate a variable map with strings that contain escape sequences that
- * might be useful to script writers.
- */
-void add_ansi_vars(std::map<std::string, scoped_value_t>& vars);
-
#endif
diff --git a/src/base/ansi_vars.hh b/src/base/ansi_vars.hh
new file mode 100644
index 0000000..e6d8f03
--- /dev/null
+++ b/src/base/ansi_vars.hh
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2023, 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.
+ */
+
+#ifndef lnav_ansi_vars_hh
+#define lnav_ansi_vars_hh
+
+#include "shlex.resolver.hh"
+
+/**
+ * Populate a variable map with strings that contain escape sequences that
+ * might be useful to script writers.
+ */
+void add_ansi_vars(std::map<std::string, scoped_value_t>& vars);
+
+#endif
diff --git a/src/base/attr_line.builder.cc b/src/base/attr_line.builder.cc
index 95416dc..a6071a4 100644
--- a/src/base/attr_line.builder.cc
+++ b/src/base/attr_line.builder.cc
@@ -28,3 +28,57 @@
*/
#include "attr_line.builder.hh"
+
+attr_line_builder&
+attr_line_builder::append_as_hexdump(const string_fragment& sf)
+{
+ auto byte_off = size_t{0};
+ for (auto ch : sf) {
+ if (byte_off == 8) {
+ this->append(" ");
+ }
+ nonstd::optional<role_t> ro;
+ if (ch == '\0') {
+ ro = role_t::VCR_NULL;
+ } else if (isspace(ch) || iscntrl(ch)) {
+ ro = role_t::VCR_ASCII_CTRL;
+ } else if (!isprint(ch)) {
+ ro = role_t::VCR_NON_ASCII;
+ }
+ auto ag = ro.has_value() ? this->with_attr(VC_ROLE.value(ro.value()))
+ : this->with_default();
+ this->appendf(FMT_STRING(" {:0>2x}"), ch);
+ byte_off += 1;
+ }
+ for (; byte_off < 16; byte_off++) {
+ if (byte_off == 8) {
+ this->append(" ");
+ }
+ this->append(" ");
+ }
+ this->append(" ");
+ byte_off = 0;
+ for (auto ch : sf) {
+ if (byte_off == 8) {
+ this->append(" ");
+ }
+ if (ch == '\0') {
+ auto ag = this->with_attr(VC_ROLE.value(role_t::VCR_NULL));
+ this->append("\u22c4");
+ } else if (isspace(ch)) {
+ auto ag = this->with_attr(VC_ROLE.value(role_t::VCR_ASCII_CTRL));
+ this->append("_");
+ } else if (iscntrl(ch)) {
+ auto ag = this->with_attr(VC_ROLE.value(role_t::VCR_ASCII_CTRL));
+ this->append("\u2022");
+ } else if (isprint(ch)) {
+ this->alb_line.get_string().push_back(ch);
+ } else {
+ auto ag = this->with_attr(VC_ROLE.value(role_t::VCR_NON_ASCII));
+ this->append("\u00d7");
+ }
+ byte_off += 1;
+ }
+
+ return *this;
+}
diff --git a/src/base/attr_line.builder.hh b/src/base/attr_line.builder.hh
index 1e62532..9ae2caa 100644
--- a/src/base/attr_line.builder.hh
+++ b/src/base/attr_line.builder.hh
@@ -40,6 +40,11 @@ public:
class attr_guard {
public:
+ explicit attr_guard(attr_line_t& al)
+ : ag_line(al), ag_start(nonstd::nullopt)
+ {
+ }
+
attr_guard(attr_line_t& al, string_attr_pair sap)
: ag_line(al), ag_start(al.get_string().length()),
ag_attr(std::move(sap))
@@ -51,7 +56,7 @@ public:
attr_guard& operator=(const attr_guard&) = delete;
attr_guard(attr_guard&& other) noexcept
- : ag_line(other.ag_line), ag_start(other.ag_start),
+ : ag_line(other.ag_line), ag_start(std::move(other.ag_start)),
ag_attr(std::move(other.ag_attr))
{
other.ag_start = nonstd::nullopt;
@@ -75,6 +80,8 @@ public:
string_attr_pair ag_attr;
};
+ attr_guard with_default() { return attr_guard{this->alb_line}; }
+
attr_guard with_attr(string_attr_pair sap)
{
return {this->alb_line, std::move(sap)};
@@ -103,6 +110,14 @@ public:
return *this;
}
+ template<typename... Args>
+ attr_line_builder& appendf(Args... args)
+ {
+ this->alb_line.appendf(args...);
+
+ return *this;
+ }
+
attr_line_builder& indent(size_t amount)
{
auto pre = this->with_attr(SA_PREFORMATTED.value());
@@ -112,6 +127,8 @@ public:
return *this;
}
+ attr_line_builder& append_as_hexdump(const string_fragment& sf);
+
private:
attr_line_t& alb_line;
};
diff --git a/src/base/attr_line.cc b/src/base/attr_line.cc
index b65f4ba..c406525 100644
--- a/src/base/attr_line.cc
+++ b/src/base/attr_line.cc
@@ -200,7 +200,9 @@ attr_line_t::insert(size_t index,
this->al_string, starting_line_index, this->al_string.length());
string_fragment last_word;
ssize_t line_ch_count = 0;
+ ssize_t line_indent_count = 0;
auto needs_indent = false;
+ auto last_was_pre = false;
while (!text_to_wrap.empty()) {
if (needs_indent) {
@@ -214,6 +216,7 @@ attr_line_t::insert(size_t index,
split_attrs(*this, indent_lr);
indent_lr.lr_end += tws->tws_padding_indent;
line_ch_count += tws->tws_padding_indent;
+ line_indent_count += tws->tws_padding_indent;
if (!indent_lr.empty()) {
this->al_attrs.emplace_back(indent_lr, SA_PREFORMATTED.value());
}
@@ -222,18 +225,34 @@ attr_line_t::insert(size_t index,
tws->tws_indent + tws->tws_padding_indent);
needs_indent = false;
}
- auto chunk = text_stream::consume(text_to_wrap);
- text_to_wrap = chunk.match(
+ text_stream::chunk next_chunk(mapbox::util::no_init{});
+ auto pre_iter = find_string_attr_containing(
+ this->al_attrs, &SA_PREFORMATTED, text_to_wrap.sf_begin);
+ if (pre_iter != this->al_attrs.end()) {
+ auto pre_len = pre_iter->sa_range.lr_end - text_to_wrap.sf_begin;
+ auto pre_lf = text_to_wrap.find('\n');
+ if (pre_lf && pre_lf.value() < pre_len) {
+ pre_len = pre_lf.value() + 1;
+ }
+
+ auto pre_pair = text_to_wrap.split_n(pre_len);
+ next_chunk = text_stream::word{
+ pre_pair->first,
+ pre_pair->second,
+ };
+ }
+ if (!next_chunk.valid()) {
+ next_chunk = text_stream::consume(text_to_wrap);
+ }
+
+ text_to_wrap = next_chunk.match(
[&](text_stream::word word) {
auto ch_count
= word.w_word.utf8_length().unwrapOr(word.w_word.length());
- if ((line_ch_count + ch_count) > usable_width
- && find_string_attr_containing(this->al_attrs,
- &SA_PREFORMATTED,
- text_to_wrap.sf_begin)
- == this->al_attrs.end())
+ if (line_ch_count > line_indent_count && !last_was_pre
+ && (line_ch_count + ch_count) > usable_width)
{
this->insert(word.w_word.sf_begin, 1, '\n');
this->insert(word.w_word.sf_begin + 1,
@@ -250,6 +269,7 @@ attr_line_t::insert(size_t index,
SA_PREFORMATTED.value());
}
line_ch_count = tws->tws_padding_indent + ch_count;
+ line_indent_count = tws->tws_padding_indent;
auto trailing_space_count = 0;
if (!last_word.empty()) {
trailing_space_count
@@ -263,12 +283,18 @@ attr_line_t::insert(size_t index,
1 + tws->tws_indent + tws->tws_padding_indent);
}
line_ch_count += ch_count;
+ if (word.w_word.endswith("\n")) {
+ line_ch_count = 0;
+ line_indent_count = 0;
+ needs_indent = true;
+ }
return word.w_remaining;
},
[&](text_stream::space space) {
if (space.s_value == "\n") {
line_ch_count = 0;
+ line_indent_count = 0;
needs_indent = true;
return space.s_remaining;
}
@@ -287,6 +313,7 @@ attr_line_t::insert(size_t index,
space.s_value.length());
this->insert(space.s_value.sf_begin, "\n");
line_ch_count = 0;
+ line_indent_count = 0;
needs_indent = true;
auto trailing_space_count = 0;
@@ -318,9 +345,10 @@ attr_line_t::insert(size_t index,
[](text_stream::corrupt corrupt) { return corrupt.c_remaining; },
[](text_stream::eof eof) { return eof.e_remaining; });
- if (chunk.is<text_stream::word>()) {
+ if (next_chunk.is<text_stream::word>()) {
last_word = text_to_wrap;
}
+ last_was_pre = (pre_iter != this->al_attrs.end());
ensure(this->al_string.data() == text_to_wrap.sf_string);
ensure(text_to_wrap.sf_begin <= text_to_wrap.sf_end);
@@ -429,7 +457,7 @@ attr_line_t::apply_hide()
}
attr_line_t&
-attr_line_t::rtrim()
+attr_line_t::rtrim(nonstd::optional<const char*> chars)
{
auto index = this->al_string.length();
@@ -440,7 +468,12 @@ attr_line_t::rtrim()
{
break;
}
- if (!isspace(this->al_string[index - 1])) {
+ if (chars
+ && strchr(chars.value(), this->al_string[index - 1]) == nullptr)
+ {
+ break;
+ }
+ if (!chars && !isspace(this->al_string[index - 1])) {
break;
}
}
@@ -462,7 +495,9 @@ attr_line_t::erase(size_t pos, size_t len)
this->al_string.erase(pos, len);
- shift_string_attrs(this->al_attrs, pos, -((int32_t) len));
+ shift_string_attrs(this->al_attrs,
+ line_range{(int) pos, (int) (pos + len)},
+ -((int32_t) len));
auto new_end = std::remove_if(
this->al_attrs.begin(), this->al_attrs.end(), [](const auto& attr) {
return attr.sa_range.empty();
@@ -506,6 +541,33 @@ line_range::intersection(const line_range& other) const
}
line_range&
+line_range::shift_range(const line_range& cover, int32_t amount)
+{
+ if (cover.lr_end <= this->lr_start) {
+ this->lr_start = std::max(0, this->lr_start + amount);
+ if (this->lr_end != -1) {
+ this->lr_end = std::max(0, this->lr_end + amount);
+ }
+ } else {
+ if (amount < 0 && cover.contains(*this)) {
+ this->lr_start = cover.lr_start;
+ }
+ if (this->lr_end != -1) {
+ if (cover.lr_start < this->lr_end) {
+ if (amount < 0 && amount < (cover.lr_start - this->lr_end)) {
+ this->lr_end = cover.lr_start;
+ } else {
+ this->lr_end
+ = std::max(this->lr_start, this->lr_end + amount);
+ }
+ }
+ }
+ }
+
+ return *this;
+}
+
+line_range&
line_range::shift(int32_t start, int32_t amount)
{
if (start == this->lr_start) {
@@ -535,3 +597,120 @@ line_range::shift(int32_t start, int32_t amount)
return *this;
}
+
+string_attrs_t::const_iterator
+find_string_attr(const string_attrs_t& sa, size_t near)
+{
+ auto nearest = sa.end();
+ ssize_t last_diff = INT_MAX;
+
+ for (auto iter = sa.begin(); iter != sa.end(); ++iter) {
+ const auto& lr = iter->sa_range;
+
+ if (!lr.is_valid() || !lr.contains(near)) {
+ continue;
+ }
+
+ ssize_t diff = near - lr.lr_start;
+ if (diff < last_diff) {
+ last_diff = diff;
+ nearest = iter;
+ }
+ }
+
+ return nearest;
+}
+
+void
+shift_string_attrs(string_attrs_t& sa, int32_t start, int32_t amount)
+{
+ for (auto& iter : sa) {
+ iter.sa_range.shift(start, amount);
+ }
+}
+
+void
+shift_string_attrs(string_attrs_t& sa, const line_range& cover, int32_t amount)
+{
+ for (auto& iter : sa) {
+ iter.sa_range.shift_range(cover, amount);
+ }
+}
+
+struct line_range
+find_string_attr_range(const string_attrs_t& sa, string_attr_type_base* type)
+{
+ auto iter = find_string_attr(sa, type);
+
+ if (iter != sa.end()) {
+ return iter->sa_range;
+ }
+
+ return line_range();
+}
+
+void
+remove_string_attr(string_attrs_t& sa, const line_range& lr)
+{
+ string_attrs_t::iterator iter;
+
+ while ((iter = find_string_attr(sa, lr)) != sa.end()) {
+ sa.erase(iter);
+ }
+}
+
+void
+remove_string_attr(string_attrs_t& sa, string_attr_type_base* type)
+{
+ for (auto iter = sa.begin(); iter != sa.end();) {
+ if (iter->sa_type == type) {
+ iter = sa.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+}
+
+string_attrs_t::iterator
+find_string_attr(string_attrs_t& sa, const line_range& lr)
+{
+ string_attrs_t::iterator iter;
+
+ for (iter = sa.begin(); iter != sa.end(); ++iter) {
+ if (lr.contains(iter->sa_range)) {
+ break;
+ }
+ }
+
+ return iter;
+}
+
+string_attrs_t::const_iterator
+find_string_attr(const string_attrs_t& sa,
+ const string_attr_type_base* type,
+ int start)
+{
+ string_attrs_t::const_iterator iter;
+
+ for (iter = sa.begin(); iter != sa.end(); ++iter) {
+ if (iter->sa_type == type && iter->sa_range.lr_start >= start) {
+ break;
+ }
+ }
+
+ return iter;
+}
+
+nonstd::optional<const string_attr*>
+get_string_attr(const string_attrs_t& sa,
+ const string_attr_type_base* type,
+ int start)
+{
+ auto iter = find_string_attr(sa, type, start);
+
+ if (iter == sa.end()) {
+ return nonstd::nullopt;
+ }
+
+ return nonstd::make_optional(&(*iter));
+}
diff --git a/src/base/attr_line.hh b/src/base/attr_line.hh
index c9cb6a8..c294321 100644
--- a/src/base/attr_line.hh
+++ b/src/base/attr_line.hh
@@ -40,130 +40,10 @@
#include "fmt/format.h"
#include "intern_string.hh"
+#include "line_range.hh"
#include "string_attr_type.hh"
#include "string_util.hh"
-/**
- * Encapsulates a range in a string.
- */
-struct line_range {
- enum class unit {
- bytes,
- codepoint,
- };
-
- int lr_start;
- int lr_end;
- unit lr_unit;
-
- explicit line_range(int start = -1, int end = -1, unit u = unit::bytes)
- : lr_start(start), lr_end(end), lr_unit(u)
- {
- }
-
- bool is_valid() const { return this->lr_start != -1; }
-
- int length() const
- {
- return this->lr_end == -1 ? INT_MAX : this->lr_end - this->lr_start;
- }
-
- bool empty() const { return this->length() == 0; }
-
- void clear()
- {
- this->lr_start = -1;
- this->lr_end = -1;
- }
-
- int end_for_string(const std::string& str) const
- {
- return this->lr_end == -1 ? str.length() : this->lr_end;
- }
-
- bool contains(int pos) const
- {
- return this->lr_start <= pos
- && (this->lr_end == -1 || pos < this->lr_end);
- }
-
- bool contains(const struct line_range& other) const
- {
- return this->contains(other.lr_start)
- && (this->lr_end == -1 || other.lr_end <= this->lr_end);
- }
-
- bool intersects(const struct line_range& other) const
- {
- if (this->contains(other.lr_start)) {
- return true;
- }
- if (other.lr_end > 0 && this->contains(other.lr_end - 1)) {
- return true;
- }
- if (other.contains(this->lr_start)) {
- return true;
- }
-
- return false;
- }
-
- line_range intersection(const struct line_range& other) const;
-
- line_range& shift(int32_t start, int32_t amount);
-
- void ltrim(const char* str)
- {
- while (this->lr_start < this->lr_end && isspace(str[this->lr_start])) {
- this->lr_start += 1;
- }
- }
-
- bool operator<(const struct line_range& rhs) const
- {
- if (this->lr_start < rhs.lr_start) {
- return true;
- }
- if (this->lr_start > rhs.lr_start) {
- return false;
- }
-
- // this->lr_start == rhs.lr_start
- if (this->lr_end == rhs.lr_end) {
- return false;
- }
-
- if (this->lr_end < rhs.lr_end) {
- return false;
- }
- return true;
- }
-
- bool operator==(const struct line_range& rhs) const
- {
- return (this->lr_start == rhs.lr_start && this->lr_end == rhs.lr_end);
- }
-
- const char* substr(const std::string& str) const
- {
- if (this->lr_start == -1) {
- return str.c_str();
- }
- return &(str.c_str()[this->lr_start]);
- }
-
- size_t sublen(const std::string& str) const
- {
- if (this->lr_start == -1) {
- return str.length();
- }
- if (this->lr_end == -1) {
- return str.length() - this->lr_start;
- }
- return this->length();
- }
-};
-
inline line_range
to_line_range(const string_fragment& frag)
{
@@ -214,35 +94,11 @@ struct string_attr_wrapper {
/** A map of line ranges to attributes for that range. */
using string_attrs_t = std::vector<string_attr>;
-inline string_attrs_t::const_iterator
-find_string_attr(const string_attrs_t& sa,
- const string_attr_type_base* type,
- int start = 0)
-{
- string_attrs_t::const_iterator iter;
-
- for (iter = sa.begin(); iter != sa.end(); ++iter) {
- if (iter->sa_type == type && iter->sa_range.lr_start >= start) {
- break;
- }
- }
-
- return iter;
-}
-
-inline nonstd::optional<const string_attr*>
-get_string_attr(const string_attrs_t& sa,
- const string_attr_type_base* type,
- int start = 0)
-{
- auto iter = find_string_attr(sa, type, start);
-
- if (iter == sa.end()) {
- return nonstd::nullopt;
- }
+string_attrs_t::const_iterator find_string_attr(
+ const string_attrs_t& sa, const string_attr_type_base* type, int start = 0);
- return nonstd::make_optional(&(*iter));
-}
+nonstd::optional<const string_attr*> get_string_attr(
+ const string_attrs_t& sa, const string_attr_type_base* type, int start = 0);
template<typename T>
inline nonstd::optional<string_attr_wrapper<T>>
@@ -276,42 +132,11 @@ find_string_attr_containing(const string_attrs_t& sa,
return iter;
}
-inline string_attrs_t::iterator
-find_string_attr(string_attrs_t& sa, const struct line_range& lr)
-{
- string_attrs_t::iterator iter;
-
- for (iter = sa.begin(); iter != sa.end(); ++iter) {
- if (lr.contains(iter->sa_range)) {
- break;
- }
- }
-
- return iter;
-}
-
-inline string_attrs_t::const_iterator
-find_string_attr(const string_attrs_t& sa, size_t near)
-{
- auto nearest = sa.end();
- ssize_t last_diff = INT_MAX;
-
- for (auto iter = sa.begin(); iter != sa.end(); ++iter) {
- const auto& lr = iter->sa_range;
-
- if (!lr.is_valid() || !lr.contains(near)) {
- continue;
- }
-
- ssize_t diff = near - lr.lr_start;
- if (diff < last_diff) {
- last_diff = diff;
- nearest = iter;
- }
- }
+string_attrs_t::iterator find_string_attr(string_attrs_t& sa,
+ const struct line_range& lr);
- return nearest;
-}
+string_attrs_t::const_iterator find_string_attr(const string_attrs_t& sa,
+ size_t near);
template<typename T>
inline string_attrs_t::const_iterator
@@ -341,47 +166,18 @@ rfind_string_attr_if(const string_attrs_t& sa, ssize_t near, T predicate)
return nearest;
}
-inline struct line_range
-find_string_attr_range(const string_attrs_t& sa, string_attr_type_base* type)
-{
- auto iter = find_string_attr(sa, type);
+struct line_range find_string_attr_range(const string_attrs_t& sa,
+ string_attr_type_base* type);
- if (iter != sa.end()) {
- return iter->sa_range;
- }
+void remove_string_attr(string_attrs_t& sa, const struct line_range& lr);
- return line_range();
-}
+void remove_string_attr(string_attrs_t& sa, string_attr_type_base* type);
-inline void
-remove_string_attr(string_attrs_t& sa, const struct line_range& lr)
-{
- string_attrs_t::iterator iter;
-
- while ((iter = find_string_attr(sa, lr)) != sa.end()) {
- sa.erase(iter);
- }
-}
+void shift_string_attrs(string_attrs_t& sa, int32_t start, int32_t amount);
-inline void
-remove_string_attr(string_attrs_t& sa, string_attr_type_base* type)
-{
- for (auto iter = sa.begin(); iter != sa.end();) {
- if (iter->sa_type == type) {
- iter = sa.erase(iter);
- } else {
- ++iter;
- }
- }
-}
-
-inline void
-shift_string_attrs(string_attrs_t& sa, int32_t start, int32_t amount)
-{
- for (auto& iter : sa) {
- iter.sa_range.shift(start, amount);
- }
-}
+void shift_string_attrs(string_attrs_t& sa,
+ const line_range& cover,
+ int32_t amount);
struct text_wrap_settings {
text_wrap_settings& with_indent(int indent)
@@ -425,6 +221,13 @@ public:
return retval.with_ansi_string("%s", str);
}
+ static inline attr_line_t from_ansi_str(const std::string& str)
+ {
+ attr_line_t retval;
+
+ return retval.with_ansi_string(str);
+ }
+
/** @return The string itself. */
std::string& get_string() { return this->al_string; }
@@ -517,15 +320,6 @@ public:
return *this;
}
- attr_line_t& append_quoted(const attr_line_t& al)
- {
- this->al_string.append("\u201c");
- this->append(al);
- this->al_string.append("\u201d");
-
- return *this;
- }
-
template<typename S>
attr_line_t& append_quoted(S s)
{
@@ -548,18 +342,31 @@ public:
return *this;
}
- template<typename S>
- attr_line_t& append(S str)
+ attr_line_t& append(const std::string& str)
{
this->al_string.append(str);
return *this;
}
+ attr_line_t& append(const char* str)
+ {
+ this->al_string.append(str);
+ return *this;
+ }
+
+ template<typename V>
+ attr_line_t& append(const V& v)
+ {
+ this->al_string.append(fmt::to_string(v));
+ return *this;
+ }
+
template<typename... Args>
attr_line_t& appendf(fmt::format_string<Args...> fstr, Args&&... args)
{
- this->template append(
- fmt::vformat(fstr, fmt::make_format_args(args...)));
+ fmt::vformat_to(std::back_inserter(this->al_string),
+ fstr,
+ fmt::make_format_args(args...));
return *this;
}
@@ -635,6 +442,24 @@ public:
return *this;
}
+ template<typename S>
+ attr_line_t& insert(size_t index,
+ const std::pair<S, string_attr_pair>& value)
+ {
+ size_t start_len = this->al_string.length();
+
+ this->insert(index, std::move(value.first));
+
+ line_range lr{
+ (int) index,
+ (int) (index + (this->al_string.length() - start_len)),
+ };
+
+ this->al_attrs.emplace_back(lr, value.second);
+
+ return *this;
+ }
+
template<typename... Args>
attr_line_t& add_header(Args... args)
{
@@ -657,7 +482,7 @@ public:
attr_line_t& erase(size_t pos, size_t len = std::string::npos);
- attr_line_t& rtrim();
+ attr_line_t& rtrim(nonstd::optional<const char*> chars = nonstd::nullopt);
attr_line_t& erase_utf8_chars(size_t start)
{
@@ -695,6 +520,11 @@ public:
return utf8_string_length(this->al_string).unwrapOr(this->length());
}
+ size_t column_width() const
+ {
+ return string_fragment::from_str(this->al_string).column_width();
+ }
+
std::string get_substring(const line_range& lr) const
{
if (!lr.is_valid()) {
diff --git a/src/base/attr_line.tests.cc b/src/base/attr_line.tests.cc
index 53b338e..8e23480 100644
--- a/src/base/attr_line.tests.cc
+++ b/src/base/attr_line.tests.cc
@@ -36,6 +36,15 @@
using namespace lnav::roles::literals;
+TEST_CASE("line_range")
+{
+ line_range lr1{0, 95};
+ line_range lr2{0, -1};
+
+ CHECK(lr2 < lr1);
+ CHECK(!(lr1 < lr2));
+}
+
TEST_CASE("attr_line_t::basic-wrapping")
{
text_wrap_settings tws = {3, 21};
@@ -89,3 +98,18 @@ TEST_CASE("attr_line_t::unicode-wrap")
" be wrapped and\n"
" indented");
}
+
+TEST_CASE("attr_line_t::pre-wrap")
+{
+ auto pre_al = attr_line_t(" Hello, World! ")
+ .with_attr_for_all(SA_PREFORMATTED.value());
+ auto al = attr_line_t("This is a pre-formatted inline -- ")
+ .append(pre_al)
+ .append(" -- that should be wrapped");
+
+ text_wrap_settings tws = {0, 36};
+
+ auto body = attr_line_t().append(al, &tws);
+
+ printf("body\n%s\n", body.get_string().c_str());
+}
diff --git a/src/base/auto_fd.cc b/src/base/auto_fd.cc
new file mode 100644
index 0000000..0853c3a
--- /dev/null
+++ b/src/base/auto_fd.cc
@@ -0,0 +1,248 @@
+/**
+ * Copyright (c) 2023, 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.
+ *
+ * @file auto_fd.cc
+ */
+
+#include "auto_fd.hh"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "lnav_log.hh"
+
+int
+auto_fd::pipe(auto_fd* af)
+{
+ int retval, fd[2];
+
+ require(af != nullptr);
+
+ if ((retval = ::pipe(fd)) == 0) {
+ af[0] = fd[0];
+ af[1] = fd[1];
+ }
+
+ return retval;
+}
+
+auto_fd
+auto_fd::dup_of(int fd)
+{
+ if (fd == -1) {
+ return auto_fd{};
+ }
+
+ auto new_fd = ::dup(fd);
+
+ if (new_fd == -1) {
+ throw std::bad_alloc();
+ }
+
+ return auto_fd(new_fd);
+}
+
+Result<auto_fd, std::string>
+auto_fd::openpt(int flags)
+{
+ auto rc = posix_openpt(flags);
+ if (rc == -1) {
+ return Err(fmt::format(FMT_STRING("posix_openpt() failed: {}"),
+ strerror(errno)));
+ }
+
+ return Ok(auto_fd{rc});
+}
+
+auto_fd::auto_fd(int fd) : af_fd(fd)
+{
+ require(fd >= -1);
+}
+
+auto_fd::auto_fd(auto_fd&& af) noexcept : af_fd(af.release()) {}
+
+auto_fd
+auto_fd::dup() const
+{
+ int new_fd;
+
+ if (this->af_fd == -1 || (new_fd = ::dup(this->af_fd)) == -1) {
+ throw std::bad_alloc();
+ }
+
+ return auto_fd{new_fd};
+}
+
+auto_fd::~auto_fd()
+{
+ this->reset();
+}
+
+void
+auto_fd::reset(int fd)
+{
+ require(fd >= -1);
+
+ if (this->af_fd != fd) {
+ if (this->af_fd != -1) {
+ switch (this->af_fd) {
+ case STDIN_FILENO:
+ case STDOUT_FILENO:
+ case STDERR_FILENO:
+ break;
+ default:
+ close(this->af_fd);
+ break;
+ }
+ }
+ this->af_fd = fd;
+ }
+}
+
+void
+auto_fd::close_on_exec() const
+{
+ if (this->af_fd == -1) {
+ return;
+ }
+ log_perror(fcntl(this->af_fd, F_SETFD, FD_CLOEXEC));
+}
+
+void
+auto_fd::non_blocking() const
+{
+ auto fl = fcntl(this->af_fd, F_GETFL, 0);
+ if (fl < 0) {
+ return;
+ }
+
+ log_perror(fcntl(this->af_fd, F_SETFL, fl | O_NONBLOCK));
+}
+
+auto_fd&
+auto_fd::operator=(int fd)
+{
+ require(fd >= -1);
+
+ this->reset(fd);
+ return *this;
+}
+
+Result<void, std::string>
+auto_fd::write_fully(string_fragment sf)
+{
+ while (!sf.empty()) {
+ auto rc = write(this->af_fd, sf.data(), sf.length());
+
+ if (rc < 0) {
+ return Err(
+ fmt::format(FMT_STRING("failed to write {} bytes to FD {}"),
+ sf.length(),
+ this->af_fd));
+ }
+
+ sf = sf.substr(rc);
+ }
+
+ return Ok();
+}
+
+Result<auto_pipe, std::string>
+auto_pipe::for_child_fd(int child_fd)
+{
+ auto_pipe retval(child_fd);
+
+ if (retval.open() == -1) {
+ return Err(std::string(strerror(errno)));
+ }
+
+ return Ok(std::move(retval));
+}
+
+auto_pipe::auto_pipe(int child_fd, int child_flags)
+ : ap_child_flags(child_flags), ap_child_fd(child_fd)
+{
+ switch (child_fd) {
+ case STDIN_FILENO:
+ this->ap_child_flags = O_RDONLY;
+ break;
+ case STDOUT_FILENO:
+ case STDERR_FILENO:
+ this->ap_child_flags = O_WRONLY;
+ break;
+ }
+}
+
+void
+auto_pipe::after_fork(pid_t child_pid)
+{
+ int new_fd;
+
+ switch (child_pid) {
+ case -1:
+ this->close();
+ break;
+ case 0:
+ if (this->ap_child_flags == O_RDONLY) {
+ this->write_end().reset();
+ if (this->read_end().get() == -1) {
+ this->read_end() = ::open("/dev/null", O_RDONLY);
+ }
+ new_fd = this->read_end().get();
+ } else {
+ this->read_end().reset();
+ if (this->write_end().get() == -1) {
+ this->write_end() = ::open("/dev/null", O_WRONLY);
+ }
+ new_fd = this->write_end().get();
+ }
+ if (this->ap_child_fd != -1) {
+ if (new_fd != this->ap_child_fd) {
+ dup2(new_fd, this->ap_child_fd);
+ this->close();
+ }
+ }
+ break;
+ default:
+ if (this->ap_child_flags == O_RDONLY) {
+ this->read_end().reset();
+ } else {
+ this->write_end().reset();
+ }
+ break;
+ }
+}
+
+int
+auto_pipe::open()
+{
+ int retval = auto_fd::pipe(this->ap_fd);
+ this->ap_fd[0].close_on_exec();
+ this->ap_fd[1].close_on_exec();
+ return retval;
+}
diff --git a/src/base/auto_fd.hh b/src/base/auto_fd.hh
index da4a582..9fb3c91 100644
--- a/src/base/auto_fd.hh
+++ b/src/base/auto_fd.hh
@@ -32,16 +32,11 @@
#ifndef auto_fd_hh
#define auto_fd_hh
-#include <exception>
-#include <new>
#include <string>
-#include <errno.h>
#include <fcntl.h>
-#include <sys/select.h>
-#include <unistd.h>
-#include "base/lnav_log.hh"
+#include "base/intern_string.hh"
#include "base/result.h"
/**
@@ -59,19 +54,7 @@ public:
* contains the reader end of the pipe and the second contains the writer.
* @return The result of the pipe(2) function.
*/
- static int pipe(auto_fd* af)
- {
- int retval, fd[2];
-
- require(af != nullptr);
-
- if ((retval = ::pipe(fd)) == 0) {
- af[0] = fd[0];
- af[1] = fd[1];
- }
-
- return retval;
- }
+ static int pipe(auto_fd* af);
/**
* dup(2) the given file descriptor and wrap it in an auto_fd.
@@ -79,27 +62,16 @@ public:
* @param fd The file descriptor to duplicate.
* @return A new auto_fd that contains the duplicated file descriptor.
*/
- static auto_fd dup_of(int fd)
- {
- if (fd == -1) {
- return auto_fd{};
- }
+ static auto_fd dup_of(int fd);
- auto new_fd = ::dup(fd);
-
- if (new_fd == -1) {
- throw std::bad_alloc();
- }
-
- return auto_fd(new_fd);
- }
+ static Result<auto_fd, std::string> openpt(int flags);
/**
* Construct an auto_fd to manage the given file descriptor.
*
* @param fd The file descriptor to be managed.
*/
- explicit auto_fd(int fd = -1) : af_fd(fd) { require(fd >= -1); }
+ explicit auto_fd(int fd = -1);
/**
* Non-const copy constructor. Management of the file descriptor will be
@@ -108,7 +80,7 @@ public:
*
* @param af The source of the file descriptor.
*/
- auto_fd(auto_fd&& af) noexcept : af_fd(af.release()) {}
+ auto_fd(auto_fd&& af) noexcept;
/**
* Const copy constructor. The file descriptor from the source will be
@@ -118,21 +90,12 @@ public:
*/
auto_fd(const auto_fd& af) = delete;
- auto_fd dup() const
- {
- int new_fd;
-
- if (this->af_fd == -1 || (new_fd = ::dup(this->af_fd)) == -1) {
- throw std::bad_alloc();
- }
-
- return auto_fd{new_fd};
- }
+ auto_fd dup() const;
/**
* Destructor that will close the file descriptor managed by this object.
*/
- ~auto_fd() { this->reset(); }
+ ~auto_fd();
/** @return The file descriptor as a plain integer. */
operator int() const { return this->af_fd; }
@@ -144,13 +107,7 @@ public:
* @param fd The file descriptor to store in this object.
* @return *this
*/
- auto_fd& operator=(int fd)
- {
- require(fd >= -1);
-
- this->reset(fd);
- return *this;
- }
+ auto_fd& operator=(int fd);
/**
* Transfer management of the given file descriptor to this object.
@@ -194,39 +151,21 @@ public:
*/
int get() const { return this->af_fd; }
+ bool has_value() const { return this->af_fd != -1; }
+
/**
* Closes the current file descriptor and replaces its value with the given
* one.
*
* @param fd The new file descriptor to be managed.
*/
- void reset(int fd = -1)
- {
- require(fd >= -1);
-
- if (this->af_fd != fd) {
- if (this->af_fd != -1) {
- switch (this->af_fd) {
- case STDIN_FILENO:
- case STDOUT_FILENO:
- case STDERR_FILENO:
- break;
- default:
- close(this->af_fd);
- break;
- }
- }
- this->af_fd = fd;
- }
- }
+ void reset(int fd = -1);
- void close_on_exec() const
- {
- if (this->af_fd == -1) {
- return;
- }
- log_perror(fcntl(this->af_fd, F_SETFD, FD_CLOEXEC));
- }
+ Result<void, std::string> write_fully(string_fragment sf);
+
+ void close_on_exec() const;
+
+ void non_blocking() const;
private:
int af_fd; /*< The managed file descriptor. */
@@ -234,32 +173,30 @@ private:
class auto_pipe {
public:
- static Result<auto_pipe, std::string> for_child_fd(int child_fd)
+ static Result<auto_pipe, std::string> for_child_fd(int child_fd);
+
+ template<typename... ARGS>
+ static Result<std::array<auto_pipe, sizeof...(ARGS)>, std::string>
+ for_child_fds(ARGS... args)
{
- auto_pipe retval(child_fd);
+ std::array<auto_pipe, sizeof...(ARGS)> retval;
- if (retval.open() == -1) {
- return Err(std::string(strerror(errno)));
+ size_t index = 0;
+ for (const auto child_fd : {args...}) {
+ auto open_res = for_child_fd(child_fd);
+ if (open_res.isErr()) {
+ return Err(open_res.unwrapErr());
+ }
+
+ retval[index++] = open_res.unwrap();
}
return Ok(std::move(retval));
}
- explicit auto_pipe(int child_fd = -1, int child_flags = O_RDONLY)
- : ap_child_flags(child_flags), ap_child_fd(child_fd)
- {
- switch (child_fd) {
- case STDIN_FILENO:
- this->ap_child_flags = O_RDONLY;
- break;
- case STDOUT_FILENO:
- case STDERR_FILENO:
- this->ap_child_flags = O_WRONLY;
- break;
- }
- }
+ explicit auto_pipe(int child_fd = -1, int child_flags = O_RDONLY);
- int open() { return auto_fd::pipe(this->ap_fd); }
+ int open();
void close()
{
@@ -271,44 +208,7 @@ public:
auto_fd& write_end() { return this->ap_fd[1]; }
- void after_fork(pid_t child_pid)
- {
- int new_fd;
-
- switch (child_pid) {
- case -1:
- this->close();
- break;
- case 0:
- if (this->ap_child_flags == O_RDONLY) {
- this->write_end().reset();
- if (this->read_end().get() == -1) {
- this->read_end() = ::open("/dev/null", O_RDONLY);
- }
- new_fd = this->read_end().get();
- } else {
- this->read_end().reset();
- if (this->write_end().get() == -1) {
- this->write_end() = ::open("/dev/null", O_WRONLY);
- }
- new_fd = this->write_end().get();
- }
- if (this->ap_child_fd != -1) {
- if (new_fd != this->ap_child_fd) {
- dup2(new_fd, this->ap_child_fd);
- this->close();
- }
- }
- break;
- default:
- if (this->ap_child_flags == O_RDONLY) {
- this->read_end().reset();
- } else {
- this->write_end().reset();
- }
- break;
- }
- }
+ void after_fork(pid_t child_pid);
int ap_child_flags;
int ap_child_fd;
diff --git a/src/base/auto_mem.hh b/src/base/auto_mem.hh
index e6b456c..b404a1b 100644
--- a/src/base/auto_mem.hh
+++ b/src/base/auto_mem.hh
@@ -66,6 +66,16 @@ public:
return retval;
}
+ static auto_mem calloc(size_t count)
+ {
+ return auto_mem(static_cast<T*>(::calloc(count, sizeof(T))));
+ }
+
+ static auto_mem malloc(size_t sz)
+ {
+ return auto_mem(static_cast<T*>(::malloc(sz)));
+ }
+
explicit auto_mem(T* ptr = nullptr)
: am_ptr(ptr), am_free_func(default_free)
{
@@ -241,6 +251,8 @@ public:
const char* begin() const { return this->ab_buffer; }
+ char* next_available() { return &this->ab_buffer[this->ab_size]; }
+
auto_buffer& push_back(char ch)
{
if (this->ab_size == this->ab_capacity) {
diff --git a/src/base/auto_pid.hh b/src/base/auto_pid.hh
index 702af1e..ff44b99 100644
--- a/src/base/auto_pid.hh
+++ b/src/base/auto_pid.hh
@@ -62,10 +62,7 @@ public:
{
}
- ~auto_pid() noexcept
- {
- this->reset();
- }
+ ~auto_pid() noexcept { this->reset(); }
auto_pid& operator=(auto_pid&& other) noexcept
{
@@ -77,10 +74,7 @@ public:
auto_pid& operator=(const auto_pid& other) = delete;
- pid_t in() const
- {
- return this->ap_child;
- }
+ pid_t in() const { return this->ap_child; }
bool in_child() const
{
@@ -89,9 +83,7 @@ public:
return this->ap_child == 0;
}
- pid_t release() &&
- {
- return std::exchange(this->ap_child, -1); }
+ pid_t release() && { return std::exchange(this->ap_child, -1); }
int status() const
{
@@ -107,6 +99,13 @@ public:
return WIFEXITED(this->ap_status);
}
+ int term_signal() const
+ {
+ static_assert(ProcState == process_state::finished,
+ "wait_for_child() must be called first");
+ return WTERMSIG(this->ap_status);
+ }
+
int exit_status() const
{
static_assert(ProcState == process_state::finished,
diff --git a/src/base/color_spaces.cc b/src/base/color_spaces.cc
new file mode 100644
index 0000000..74d9fe6
--- /dev/null
+++ b/src/base/color_spaces.cc
@@ -0,0 +1,176 @@
+/**
+ * Copyright (c) 2024, 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 <cmath>
+
+#include "color_spaces.hh"
+
+#include "config.h"
+
+bool
+rgb_color::operator<(const rgb_color& rhs) const
+{
+ if (rc_r < rhs.rc_r)
+ return true;
+ if (rhs.rc_r < rc_r)
+ return false;
+ if (rc_g < rhs.rc_g)
+ return true;
+ if (rhs.rc_g < rc_g)
+ return false;
+ return rc_b < rhs.rc_b;
+}
+
+bool
+rgb_color::operator>(const rgb_color& rhs) const
+{
+ return rhs < *this;
+}
+
+bool
+rgb_color::operator<=(const rgb_color& rhs) const
+{
+ return !(rhs < *this);
+}
+
+bool
+rgb_color::operator>=(const rgb_color& rhs) const
+{
+ return !(*this < rhs);
+}
+
+bool
+rgb_color::operator==(const rgb_color& rhs) const
+{
+ return rc_r == rhs.rc_r && rc_g == rhs.rc_g && rc_b == rhs.rc_b;
+}
+
+bool
+rgb_color::operator!=(const rgb_color& rhs) const
+{
+ return !(rhs == *this);
+}
+
+lab_color::lab_color(const rgb_color& rgb)
+{
+ double r = rgb.rc_r / 255.0, g = rgb.rc_g / 255.0, b = rgb.rc_b / 255.0, x,
+ y, z;
+
+ r = (r > 0.04045) ? pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
+ g = (g > 0.04045) ? pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
+ b = (b > 0.04045) ? pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
+
+ x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047;
+ y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 1.00000;
+ z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883;
+
+ x = (x > 0.008856) ? pow(x, 1.0 / 3.0) : (7.787 * x) + 16.0 / 116.0;
+ y = (y > 0.008856) ? pow(y, 1.0 / 3.0) : (7.787 * y) + 16.0 / 116.0;
+ z = (z > 0.008856) ? pow(z, 1.0 / 3.0) : (7.787 * z) + 16.0 / 116.0;
+
+ this->lc_l = (116.0 * y) - 16;
+ this->lc_a = 500.0 * (x - y);
+ this->lc_b = 200.0 * (y - z);
+}
+
+double
+lab_color::deltaE(const lab_color& other) const
+{
+ double deltaL = this->lc_l - other.lc_l;
+ double deltaA = this->lc_a - other.lc_a;
+ double deltaB = this->lc_b - other.lc_b;
+ double c1 = sqrt(this->lc_a * this->lc_a + this->lc_b * this->lc_b);
+ double c2 = sqrt(other.lc_a * other.lc_a + other.lc_b * other.lc_b);
+ double deltaC = c1 - c2;
+ double deltaH = deltaA * deltaA + deltaB * deltaB - deltaC * deltaC;
+ deltaH = deltaH < 0.0 ? 0.0 : sqrt(deltaH);
+ double sc = 1.0 + 0.045 * c1;
+ double sh = 1.0 + 0.015 * c1;
+ double deltaLKlsl = deltaL / (1.0);
+ double deltaCkcsc = deltaC / (sc);
+ double deltaHkhsh = deltaH / (sh);
+ double i = deltaLKlsl * deltaLKlsl + deltaCkcsc * deltaCkcsc
+ + deltaHkhsh * deltaHkhsh;
+ return i < 0.0 ? 0.0 : sqrt(i);
+}
+
+bool
+lab_color::operator<(const lab_color& rhs) const
+{
+ if (lc_l < rhs.lc_l)
+ return true;
+ if (rhs.lc_l < lc_l)
+ return false;
+ if (lc_a < rhs.lc_a)
+ return true;
+ if (rhs.lc_a < lc_a)
+ return false;
+ return lc_b < rhs.lc_b;
+}
+
+bool
+lab_color::operator>(const lab_color& rhs) const
+{
+ return rhs < *this;
+}
+
+bool
+lab_color::operator<=(const lab_color& rhs) const
+{
+ return !(rhs < *this);
+}
+
+bool
+lab_color::operator>=(const lab_color& rhs) const
+{
+ return !(*this < rhs);
+}
+
+bool
+lab_color::operator==(const lab_color& rhs) const
+{
+ return lc_l == rhs.lc_l && lc_a == rhs.lc_a && lc_b == rhs.lc_b;
+}
+
+bool
+lab_color::operator!=(const lab_color& rhs) const
+{
+ return !(rhs == *this);
+}
+
+bool
+lab_color::sufficient_contrast(const lab_color& other) const
+{
+ if (std::abs(this->lc_l - other.lc_l) > 15) {
+ return true;
+ }
+
+ return (std::signbit(this->lc_a) != std::signbit(other.lc_a)
+ || std::signbit(this->lc_b) != std::signbit(other.lc_b));
+}
diff --git a/src/base/color_spaces.hh b/src/base/color_spaces.hh
new file mode 100644
index 0000000..22c4afc
--- /dev/null
+++ b/src/base/color_spaces.hh
@@ -0,0 +1,96 @@
+/**
+ * Copyright (c) 2024, 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.
+ */
+
+#ifndef color_spaces_hh
+#define color_spaces_hh
+
+struct rgb_color {
+ explicit rgb_color(short r = -1, short g = -1, short b = -1)
+ : rc_r(r), rc_g(g), rc_b(b)
+ {
+ }
+
+ bool empty() const
+ {
+ return this->rc_r == -1 && this->rc_g == -1 && this->rc_b == -1;
+ }
+
+ bool operator==(const rgb_color& rhs) const;
+
+ bool operator!=(const rgb_color& rhs) const;
+
+ bool operator<(const rgb_color& rhs) const;
+
+ bool operator>(const rgb_color& rhs) const;
+
+ bool operator<=(const rgb_color& rhs) const;
+
+ bool operator>=(const rgb_color& rhs) const;
+
+ short rc_r;
+ short rc_g;
+ short rc_b;
+};
+
+struct lab_color {
+ lab_color() : lc_l(0), lc_a(0), lc_b(0) {}
+
+ explicit lab_color(const rgb_color& rgb);
+
+ double deltaE(const lab_color& other) const;
+
+ bool sufficient_contrast(const lab_color& other) const;
+
+ lab_color& operator=(const lab_color& other)
+ {
+ this->lc_l = other.lc_l;
+ this->lc_a = other.lc_a;
+ this->lc_b = other.lc_b;
+
+ return *this;
+ }
+
+ bool operator==(const lab_color& rhs) const;
+
+ bool operator!=(const lab_color& rhs) const;
+
+ bool operator<(const lab_color& rhs) const;
+
+ bool operator>(const lab_color& rhs) const;
+
+ bool operator<=(const lab_color& rhs) const;
+
+ bool operator>=(const lab_color& rhs) const;
+
+ double lc_l;
+ double lc_a;
+ double lc_b;
+};
+
+#endif
diff --git a/src/base/date_time_scanner.cc b/src/base/date_time_scanner.cc
index 72b7e5d..c3a904b 100644
--- a/src/base/date_time_scanner.cc
+++ b/src/base/date_time_scanner.cc
@@ -34,6 +34,8 @@
#include "date_time_scanner.hh"
#include "config.h"
+#include "date_time_scanner.cfg.hh"
+#include "injector.hh"
#include "ptimec.hh"
#include "scn/scn.h"
@@ -45,17 +47,28 @@ date_time_scanner::ftime(char* dst,
{
off_t off = 0;
- if (time_fmt == nullptr) {
- PTIMEC_FORMATS[this->dts_fmt_lock].pf_ffunc(dst, off, len, tm);
- if (tm.et_flags & ETF_MILLIS_SET) {
- dst[off++] = '.';
- ftime_L(dst, off, len, tm);
- } else if (tm.et_flags & ETF_MICROS_SET) {
- dst[off++] = '.';
- ftime_f(dst, off, len, tm);
- } else if (tm.et_flags & ETF_NANOS_SET) {
- dst[off++] = '.';
- ftime_N(dst, off, len, tm);
+ if (time_fmt == nullptr || this->dts_fmt_lock == -1
+ || (tm.et_flags & ETF_MACHINE_ORIENTED))
+ {
+ auto index
+ = this->dts_fmt_lock != -1 && !(tm.et_flags & ETF_MACHINE_ORIENTED)
+ ? this->dts_fmt_lock
+ : PTIMEC_DEFAULT_FMT_INDEX;
+ PTIMEC_FORMATS[index].pf_ffunc(dst, off, len, tm);
+ if (tm.et_flags & ETF_SUB_NOT_IN_FORMAT) {
+ if (tm.et_flags & ETF_MILLIS_SET) {
+ dst[off++] = '.';
+ ftime_L(dst, off, len, tm);
+ } else if (tm.et_flags & ETF_MICROS_SET) {
+ dst[off++] = '.';
+ ftime_f(dst, off, len, tm);
+ } else if (tm.et_flags & ETF_NANOS_SET) {
+ dst[off++] = '.';
+ ftime_N(dst, off, len, tm);
+ }
+ }
+ if (index == PTIMEC_DEFAULT_FMT_INDEX && tm.et_flags & ETF_ZONE_SET) {
+ ftime_z(dst, off, len, tm);
}
dst[off] = '\0';
} else {
@@ -92,6 +105,9 @@ date_time_scanner::scan(const char* time_dest,
struct timeval& tv_out,
bool convert_local)
{
+ static const auto& cfg
+ = injector::get<const date_time_scanner_ns::config&>();
+
int curr_time_fmt = -1;
bool found = false;
const char* retval = nullptr;
@@ -100,32 +116,34 @@ date_time_scanner::scan(const char* time_dest,
time_fmt = PTIMEC_FORMAT_STR;
}
+ this->dts_zoned_to_local = cfg.c_zoned_to_local;
while (next_format(time_fmt, curr_time_fmt, this->dts_fmt_lock)) {
*tm_out = this->dts_base_tm;
tm_out->et_flags = 0;
if (time_len > 1 && time_dest[0] == '+' && isdigit(time_dest[1])) {
retval = nullptr;
- auto epoch_scan_res = scn::scan_value<int64_t>(
- scn::string_view{time_dest, time_len});
+ auto sv = scn::string_view{time_dest, time_len};
+ auto epoch_scan_res = scn::scan_value<int64_t>(sv);
if (epoch_scan_res) {
time_t gmt = epoch_scan_res.value();
- if (convert_local && this->dts_local_time) {
+ if (convert_local
+ && (this->dts_local_time || this->dts_zoned_to_local))
+ {
localtime_r(&gmt, &tm_out->et_tm);
#ifdef HAVE_STRUCT_TM_TM_ZONE
tm_out->et_tm.tm_zone = nullptr;
#endif
tm_out->et_tm.tm_isdst = 0;
- gmt = tm2sec(&tm_out->et_tm);
+ gmt = tm_out->to_timeval().tv_sec;
}
tv_out.tv_sec = gmt;
tv_out.tv_usec = 0;
tm_out->et_flags = ETF_DAY_SET | ETF_MONTH_SET | ETF_YEAR_SET
- | ETF_MACHINE_ORIENTED | ETF_EPOCH_TIME;
+ | ETF_MACHINE_ORIENTED | ETF_EPOCH_TIME | ETF_ZONE_SET;
this->dts_fmt_lock = curr_time_fmt;
- this->dts_fmt_len = std::distance(epoch_scan_res.begin(),
- epoch_scan_res.end());
+ this->dts_fmt_len = sv.length() - epoch_scan_res.range().size();
retval = time_dest + this->dts_fmt_len;
found = true;
break;
@@ -147,10 +165,25 @@ date_time_scanner::scan(const char* time_dest,
}
if (convert_local
&& (this->dts_local_time
- || tm_out->et_flags & ETF_EPOCH_TIME))
+ || tm_out->et_flags & ETF_EPOCH_TIME
+ || ((tm_out->et_flags & ETF_ZONE_SET
+ || this->dts_default_zone != nullptr)
+ && this->dts_zoned_to_local)))
{
- time_t gmt = tm2sec(&tm_out->et_tm);
-
+ time_t gmt = tm_out->to_timeval().tv_sec;
+
+ if (!(tm_out->et_flags & ETF_ZONE_SET)
+ && !(tm_out->et_flags & ETF_EPOCH_TIME)
+ && this->dts_default_zone != nullptr)
+ {
+ date::local_seconds stime;
+ stime += std::chrono::seconds{gmt};
+ auto ztime
+ = date::make_zoned(this->dts_default_zone, stime);
+ gmt = std::chrono::duration_cast<std::chrono::seconds>(
+ ztime.get_sys_time().time_since_epoch())
+ .count();
+ }
this->to_localtime(gmt, *tm_out);
}
const auto& last_tm = this->dts_last_tm.et_tm;
@@ -167,8 +200,7 @@ date_time_scanner::scan(const char* time_dest,
tv_out.tv_sec += sec_diff;
tm_out->et_tm.tm_wday = last_tm.tm_wday;
} else {
- // log_debug("doing tm2sec");
- tv_out.tv_sec = tm2sec(&tm_out->et_tm);
+ tv_out = tm_out->to_timeval();
secs2wday(tv_out, &tm_out->et_tm);
}
tv_out.tv_usec = tm_out->et_nsec / 1000;
@@ -198,9 +230,11 @@ date_time_scanner::scan(const char* time_dest,
}
if (convert_local
&& (this->dts_local_time
- || tm_out->et_flags & ETF_EPOCH_TIME))
+ || tm_out->et_flags & ETF_EPOCH_TIME
+ || (tm_out->et_flags & ETF_ZONE_SET
+ && this->dts_zoned_to_local)))
{
- time_t gmt = tm2sec(&tm_out->et_tm);
+ time_t gmt = tm_out->to_timeval().tv_sec;
this->to_localtime(gmt, *tm_out);
#ifdef HAVE_STRUCT_TM_TM_ZONE
@@ -209,8 +243,7 @@ date_time_scanner::scan(const char* time_dest,
tm_out->et_tm.tm_isdst = 0;
}
- tv_out.tv_sec = tm2sec(&tm_out->et_tm);
- tv_out.tv_usec = tm_out->et_nsec / 1000;
+ tv_out = tm_out->to_timeval();
secs2wday(tv_out, &tm_out->et_tm);
this->dts_fmt_lock = curr_time_fmt;
@@ -234,7 +267,10 @@ date_time_scanner::scan(const char* time_dest,
if (retval != nullptr && static_cast<size_t>(retval - time_dest) < time_len)
{
/* Try to pull out the milli/micro-second value. */
- if (retval[0] == '.' || retval[0] == ',') {
+ if (!(tm_out->et_flags
+ & (ETF_MILLIS_SET | ETF_MICROS_SET | ETF_NANOS_SET))
+ && (retval[0] == '.' || retval[0] == ','))
+ {
off_t off = (retval - time_dest) + 1;
if (ptime_N(tm_out, time_dest, off, time_len)) {
@@ -243,7 +279,7 @@ date_time_scanner::scan(const char* time_dest,
std::chrono::nanoseconds{tm_out->et_nsec})
.count();
this->dts_fmt_len += 10;
- tm_out->et_flags |= ETF_NANOS_SET;
+ tm_out->et_flags |= ETF_NANOS_SET | ETF_SUB_NOT_IN_FORMAT;
retval += 10;
} else if (ptime_f(tm_out, time_dest, off, time_len)) {
tv_out.tv_usec
@@ -251,7 +287,7 @@ date_time_scanner::scan(const char* time_dest,
std::chrono::nanoseconds{tm_out->et_nsec})
.count();
this->dts_fmt_len += 7;
- tm_out->et_flags |= ETF_MICROS_SET;
+ tm_out->et_flags |= ETF_MICROS_SET | ETF_SUB_NOT_IN_FORMAT;
retval += 7;
} else if (ptime_L(tm_out, time_dest, off, time_len)) {
tv_out.tv_usec
@@ -259,7 +295,7 @@ date_time_scanner::scan(const char* time_dest,
std::chrono::nanoseconds{tm_out->et_nsec})
.count();
this->dts_fmt_len += 4;
- tm_out->et_flags |= ETF_MILLIS_SET;
+ tm_out->et_flags |= ETF_MILLIS_SET | ETF_SUB_NOT_IN_FORMAT;
retval += 4;
}
}
@@ -287,22 +323,27 @@ date_time_scanner::to_localtime(time_t t, exttm& tm_out)
if (t < this->dts_local_offset_valid || t >= this->dts_local_offset_expiry)
{
- time_t new_gmt;
-
localtime_r(&t, &tm_out.et_tm);
+ // Clear the gmtoff set by localtime_r() otherwise tm2sec() will
+ // convert the time back again.
#ifdef HAVE_STRUCT_TM_TM_ZONE
+ tm_out.et_tm.tm_gmtoff = 0;
tm_out.et_tm.tm_zone = nullptr;
#endif
tm_out.et_tm.tm_isdst = 0;
-
- new_gmt = tm2sec(&tm_out.et_tm);
- this->dts_local_offset_cache = t - new_gmt;
+ auto new_gmt = tm2sec(&tm_out.et_tm);
+ this->dts_local_offset_cache = new_gmt - t;
this->dts_local_offset_valid = t;
this->dts_local_offset_expiry = t + (EXPIRE_TIME - 1);
this->dts_local_offset_expiry
-= this->dts_local_offset_expiry % EXPIRE_TIME;
} else {
- time_t adjust_gmt = t - this->dts_local_offset_cache;
- gmtime_r(&adjust_gmt, &tm_out.et_tm);
+ time_t adjust_gmt = t + this->dts_local_offset_cache;
+ secs2tm(adjust_gmt, &tm_out.et_tm);
}
+ tm_out.et_gmtoff = 0;
+#ifdef HAVE_STRUCT_TM_TM_ZONE
+ tm_out.et_tm.tm_gmtoff = 0;
+ tm_out.et_tm.tm_zone = nullptr;
+#endif
}
diff --git a/src/base/date_time_scanner.cfg.hh b/src/base/date_time_scanner.cfg.hh
new file mode 100644
index 0000000..7921aee
--- /dev/null
+++ b/src/base/date_time_scanner.cfg.hh
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2020, 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.
+ *
+ * @file date_time_scanner.hh
+ */
+
+#ifndef lnav_date_time_scanner_cfg_hh
+#define lnav_date_time_scanner_cfg_hh
+
+namespace date_time_scanner_ns {
+
+struct config {
+ bool c_zoned_to_local{true};
+};
+
+} // namespace date_time_scanner_ns
+
+#endif
diff --git a/src/base/date_time_scanner.hh b/src/base/date_time_scanner.hh
index 90eaffa..cf8a0da 100644
--- a/src/base/date_time_scanner.hh
+++ b/src/base/date_time_scanner.hh
@@ -37,6 +37,7 @@
#include <sys/types.h>
+#include "date/tz.h"
#include "time_util.hh"
/**
@@ -59,13 +60,27 @@ struct date_time_scanner {
this->dts_last_tm = exttm{};
}
+ struct lock_state {
+ int ls_fmt_index{-1};
+ int ls_fmt_len{-1};
+ };
+
/**
* Unlock this scanner so that the format is rediscovered.
*/
- void unlock()
+ lock_state unlock()
{
+ auto retval = lock_state{this->dts_fmt_lock, this->dts_fmt_len};
+
this->dts_fmt_lock = -1;
this->dts_fmt_len = -1;
+ return retval;
+ }
+
+ void relock(const lock_state& ls)
+ {
+ this->dts_fmt_lock = ls.ls_fmt_index;
+ this->dts_fmt_len = ls.ls_fmt_len;
}
void set_base_time(time_t base_time, const tm& local_tm);
@@ -81,6 +96,7 @@ struct date_time_scanner {
bool dts_keep_base_tz{false};
bool dts_local_time{false};
+ bool dts_zoned_to_local{true};
time_t dts_base_time{0};
struct exttm dts_base_tm;
int dts_fmt_lock{-1};
@@ -90,6 +106,7 @@ struct date_time_scanner {
time_t dts_local_offset_cache{0};
time_t dts_local_offset_valid{0};
time_t dts_local_offset_expiry{0};
+ const date::time_zone* dts_default_zone{nullptr};
static const int EXPIRE_TIME = 15 * 60;
diff --git a/src/base/from_trait.hh b/src/base/from_trait.hh
new file mode 100644
index 0000000..05c426e
--- /dev/null
+++ b/src/base/from_trait.hh
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2024, 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.
+ */
+
+#ifndef from_trait_hh
+#define from_trait_hh
+
+#include <string>
+
+#include "result.h"
+
+template<typename R, typename S>
+Result<R, std::string> from(S v);
+
+#endif
diff --git a/src/base/fs_util.cc b/src/base/fs_util.cc
index f72aaa6..7cbc228 100644
--- a/src/base/fs_util.cc
+++ b/src/base/fs_util.cc
@@ -29,14 +29,30 @@
#include "fs_util.hh"
+#include <stdlib.h>
+
#include "config.h"
#include "fmt/format.h"
#include "itertools.hh"
+#include "lnav_log.hh"
#include "opt_util.hh"
namespace lnav {
namespace filesystem {
+Result<ghc::filesystem::path, std::string>
+realpath(const ghc::filesystem::path& path)
+{
+ char resolved[PATH_MAX];
+ auto rc = ::realpath(path.c_str(), resolved);
+
+ if (rc == nullptr) {
+ return Err(std::string(strerror(errno)));
+ }
+
+ return Ok(ghc::filesystem::path(resolved));
+}
+
Result<auto_fd, std::string>
create_file(const ghc::filesystem::path& path, int flags, mode_t mode)
{
@@ -73,7 +89,7 @@ open_temp_file(const ghc::filesystem::path& pattern)
int fd;
strcpy(pattern_copy, pattern_str.c_str());
- if ((fd = mkstemp(pattern_copy)) == -1) {
+ if ((fd = mkostemp(pattern_copy, O_CLOEXEC)) == -1) {
return Err(
fmt::format(FMT_STRING("unable to create temporary file: {} -- {}"),
pattern.string(),
@@ -102,9 +118,12 @@ read_file(const ghc::filesystem::path& path)
}
}
-Result<void, std::string>
-write_file(const ghc::filesystem::path& path, const string_fragment& content)
+Result<write_file_result, std::string>
+write_file(const ghc::filesystem::path& path,
+ const string_fragment& content,
+ std::set<write_file_options> options)
{
+ write_file_result retval;
auto tmp_pattern = path;
tmp_pattern += ".XXXXXX";
@@ -123,7 +142,25 @@ write_file(const ghc::filesystem::path& path, const string_fragment& content)
bytes_written,
content.length()));
}
+
std::error_code ec;
+ if (options.count(write_file_options::backup_existing)) {
+ if (ghc::filesystem::exists(path, ec)) {
+ auto backup_path = path;
+
+ backup_path += ".bak";
+ ghc::filesystem::rename(path, backup_path, ec);
+ if (ec) {
+ return Err(
+ fmt::format(FMT_STRING("unable to backup file {}: {}"),
+ path.string(),
+ ec.message()));
+ }
+
+ retval.wfr_backup_path = backup_path;
+ }
+ }
+
ghc::filesystem::rename(tmp_pair.first, path, ec);
if (ec) {
return Err(
@@ -132,7 +169,7 @@ write_file(const ghc::filesystem::path& path, const string_fragment& content)
ec.message()));
}
- return Ok();
+ return Ok(retval);
}
std::string
@@ -181,3 +218,20 @@ file_lock::file_lock(const ghc::filesystem::path& archive_path)
} // namespace filesystem
} // namespace lnav
+
+namespace fmt {
+
+auto
+formatter<ghc::filesystem::path>::format(const ghc::filesystem::path& p,
+ format_context& ctx)
+ -> decltype(ctx.out()) const
+{
+ auto esc_res = fmt::v10::detail::find_escape(&(*p.native().begin()),
+ &(*p.native().end()));
+ if (esc_res.end == nullptr) {
+ return formatter<string_view>::format(p.native(), ctx);
+ }
+
+ return format_to(ctx.out(), FMT_STRING("{:?}"), p.native());
+}
+} // namespace fmt
diff --git a/src/base/fs_util.hh b/src/base/fs_util.hh
index b9253ff..7553075 100644
--- a/src/base/fs_util.hh
+++ b/src/base/fs_util.hh
@@ -30,6 +30,7 @@
#ifndef lnav_fs_util_hh
#define lnav_fs_util_hh
+#include <set>
#include <string>
#include <vector>
@@ -41,6 +42,14 @@
namespace lnav {
namespace filesystem {
+inline bool
+is_glob(const std::string& fn)
+{
+ return (fn.find('*') != std::string::npos
+ || fn.find('?') != std::string::npos
+ || fn.find('[') != std::string::npos);
+}
+
inline int
statp(const ghc::filesystem::path& path, struct stat* buf)
{
@@ -59,6 +68,9 @@ openp(const ghc::filesystem::path& path, int flags, mode_t mode)
return open(path.c_str(), flags, mode);
}
+Result<ghc::filesystem::path, std::string> realpath(
+ const ghc::filesystem::path& path);
+
Result<auto_fd, std::string> create_file(const ghc::filesystem::path& path,
int flags,
mode_t mode);
@@ -73,8 +85,18 @@ Result<std::pair<ghc::filesystem::path, auto_fd>, std::string> open_temp_file(
Result<std::string, std::string> read_file(const ghc::filesystem::path& path);
-Result<void, std::string> write_file(const ghc::filesystem::path& path,
- const string_fragment& content);
+enum class write_file_options {
+ backup_existing,
+};
+
+struct write_file_result {
+ nonstd::optional<ghc::filesystem::path> wfr_backup_path;
+};
+
+Result<write_file_result, std::string> write_file(
+ const ghc::filesystem::path& path,
+ const string_fragment& content,
+ std::set<write_file_options> options = {});
std::string build_path(const std::vector<ghc::filesystem::path>& paths);
@@ -119,4 +141,12 @@ public:
} // namespace filesystem
} // namespace lnav
+namespace fmt {
+template<>
+struct formatter<ghc::filesystem::path> : formatter<string_view> {
+ auto format(const ghc::filesystem::path& p, format_context& ctx)
+ -> decltype(ctx.out()) const;
+};
+} // namespace fmt
+
#endif
diff --git a/src/base/future_util.hh b/src/base/future_util.hh
index 8797faa..ad57459 100644
--- a/src/base/future_util.hh
+++ b/src/base/future_util.hh
@@ -57,22 +57,32 @@ make_ready_future(T&& t)
* A queue used to limit the number of futures that are running concurrently.
*
* @tparam T The result of the futures.
- * @tparam MAX_QUEUE_SIZE The maximum number of futures that can be in flight.
*/
-template<typename T, int MAX_QUEUE_SIZE = 8>
+template<typename T>
class future_queue {
public:
+ enum class processor_result_t {
+ ok,
+ interrupt,
+ };
+
/**
* @param processor The function to execute with the result of a future.
+ * @param max_queue_size The maximum number of futures that can be in
+ * flight.
*/
- explicit future_queue(std::function<void(T&)> processor)
- : fq_processor(processor){};
-
- ~future_queue()
+ explicit future_queue(
+ std::function<processor_result_t(std::future<T>&)> processor,
+ size_t max_queue_size = 8)
+ : fq_processor(processor), fq_max_queue_size(max_queue_size)
{
- this->pop_to();
}
+ future_queue(const future_queue&) = delete;
+ future_queue& operator=(const future_queue&) = delete;
+
+ ~future_queue() { this->pop_to(); }
+
/**
* Add a future to the queue. If the size of the queue is greater than the
* MAX_QUEUE_SIZE, this call will block waiting for the first queued
@@ -80,10 +90,10 @@ public:
*
* @param f The future to add to the queue.
*/
- void push_back(std::future<T>&& f)
+ processor_result_t push_back(std::future<T>&& f)
{
this->fq_deque.emplace_back(std::move(f));
- this->pop_to(MAX_QUEUE_SIZE);
+ return this->pop_to(this->fq_max_queue_size);
}
/**
@@ -92,17 +102,24 @@ public:
*
* @param size The new desired size of the queue.
*/
- void pop_to(size_t size = 0)
+ processor_result_t pop_to(size_t size = 0)
{
+ processor_result_t retval = processor_result_t::ok;
+
while (this->fq_deque.size() > size) {
- auto v = this->fq_deque.front().get();
- this->fq_processor(v);
+ if (this->fq_processor(this->fq_deque.front())
+ == processor_result_t::interrupt)
+ {
+ retval = processor_result_t::interrupt;
+ }
this->fq_deque.pop_front();
}
+ return retval;
}
- std::function<void(T&)> fq_processor;
+ std::function<processor_result_t(std::future<T>&)> fq_processor;
std::deque<std::future<T>> fq_deque;
+ size_t fq_max_queue_size;
};
} // namespace futures
diff --git a/src/base/humanize.time.cc b/src/base/humanize.time.cc
index 68cfe0f..a791a3d 100644
--- a/src/base/humanize.time.cc
+++ b/src/base/humanize.time.cc
@@ -33,6 +33,7 @@
#include "config.h"
#include "fmt/format.h"
+#include "math_util.hh"
#include "time_util.hh"
namespace humanize {
@@ -161,18 +162,21 @@ duration::to_string() const
millis = -millis;
}
- uint64_t remaining;
+ uint64_t remaining = millis.count();
+ uint64_t scale = 1;
+ if (this->d_msecs_resolution > 0) {
+ remaining = roundup(remaining, this->d_msecs_resolution);
+ }
if (millis >= 10min) {
- remaining
- = std::chrono::duration_cast<std::chrono::seconds>(millis).count();
+ remaining /= curr_interval->length;
+ scale *= curr_interval->length;
curr_interval += 1;
- } else {
- remaining = millis.count();
}
for (; curr_interval != std::end(intervals); curr_interval++) {
uint64_t amount;
char segment[32];
+ auto skip = scale < this->d_msecs_resolution;
if (curr_interval->length) {
amount = remaining % curr_interval->length;
@@ -181,11 +185,16 @@ duration::to_string() const
amount = remaining;
remaining = 0;
}
+ scale *= curr_interval->length;
- if (!amount && !remaining) {
+ if (amount == 0 && remaining == 0) {
break;
}
+ if (skip) {
+ continue;
+ }
+
snprintf(segment,
sizeof(segment),
curr_interval->format,
diff --git a/src/base/humanize.time.hh b/src/base/humanize.time.hh
index 96edebd..e1ebf67 100644
--- a/src/base/humanize.time.hh
+++ b/src/base/humanize.time.hh
@@ -74,12 +74,22 @@ class duration {
public:
static duration from_tv(const struct timeval& tv);
+ template<class Rep, class Period>
+ duration& with_resolution(const std::chrono::duration<Rep, Period>& res)
+ {
+ this->d_msecs_resolution
+ = std::chrono::duration_cast<std::chrono::milliseconds>(res)
+ .count();
+ return *this;
+ }
+
std::string to_string() const;
private:
explicit duration(const struct timeval& tv) : d_timeval(tv) {}
struct timeval d_timeval;
+ uint64_t d_msecs_resolution{1};
};
} // namespace time
diff --git a/src/base/intern_string.cc b/src/base/intern_string.cc
index 676d2bc..010da52 100644
--- a/src/base/intern_string.cc
+++ b/src/base/intern_string.cc
@@ -37,6 +37,7 @@
#include "config.h"
#include "pcrepp/pcre2pp.hh"
+#include "ww898/cp_utf8.hpp"
#include "xxHash/xxhash.h"
const static int TABLE_SIZE = 4095;
@@ -224,7 +225,9 @@ string_fragment::split_lines() const
start = index + 1;
}
}
- retval.emplace_back(this->sf_string, start, this->sf_end);
+ if (retval.empty() || start < this->sf_end) {
+ retval.emplace_back(this->sf_string, start, this->sf_end);
+ }
return retval;
}
@@ -301,3 +304,164 @@ string_fragment::to_string_with_case_style(case_style style) const
return retval;
}
+
+std::string
+string_fragment::to_unquoted_string() const
+{
+ auto sub_sf = *this;
+
+ if (sub_sf.startswith("r") || sub_sf.startswith("u")) {
+ sub_sf = sub_sf.consume_n(1).value();
+ }
+ if (sub_sf.length() >= 2
+ && ((sub_sf.startswith("\"") && sub_sf.endswith("\""))
+ || (sub_sf.startswith("'") && sub_sf.endswith("'"))))
+ {
+ std::string retval;
+
+ sub_sf.sf_begin += 1;
+ sub_sf.sf_end -= 1;
+ retval.reserve(this->length());
+
+ auto in_escape = false;
+ for (auto ch : sub_sf) {
+ if (in_escape) {
+ switch (ch) {
+ case 'n':
+ retval.push_back('\n');
+ break;
+ case 't':
+ retval.push_back('\t');
+ break;
+ case 'r':
+ retval.push_back('\r');
+ break;
+ default:
+ retval.push_back(ch);
+ break;
+ }
+ in_escape = false;
+ } else if (ch == '\\') {
+ in_escape = true;
+ } else {
+ retval.push_back(ch);
+ }
+ }
+
+ return retval;
+ }
+
+ return this->to_string();
+}
+
+uint32_t
+string_fragment::front_codepoint() const
+{
+ size_t index = 0;
+ auto read_res = ww898::utf::utf8::read(
+ [this, &index]() { return this->data()[index++]; });
+ if (read_res.isErr()) {
+ return this->data()[0];
+ }
+ return read_res.unwrap();
+}
+
+Result<ssize_t, const char*>
+string_fragment::codepoint_to_byte_index(ssize_t cp_index) const
+{
+ ssize_t retval = 0;
+
+ while (cp_index > 0) {
+ if (retval >= this->length()) {
+ return Err("index is beyond the end of the string");
+ }
+ auto ch_len = TRY(ww898::utf::utf8::char_size([this, retval]() {
+ return std::make_pair(this->data()[retval],
+ this->length() - retval - 1);
+ }));
+
+ retval += ch_len;
+ cp_index -= 1;
+ }
+
+ return Ok(retval);
+}
+
+string_fragment
+string_fragment::sub_cell_range(int cell_start, int cell_end) const
+{
+ int byte_index = this->sf_begin;
+ nonstd::optional<int> byte_start;
+ nonstd::optional<int> byte_end;
+ int cell_index = 0;
+
+ while (byte_index < this->sf_end) {
+ if (cell_start == cell_index) {
+ byte_start = byte_index;
+ }
+ if (!byte_end && cell_index >= cell_end) {
+ byte_end = byte_index;
+ break;
+ }
+ auto read_res = ww898::utf::utf8::read(
+ [this, &byte_index]() { return this->sf_string[byte_index++]; });
+ if (read_res.isErr()) {
+ byte_index += 1;
+ } else {
+ auto ch = read_res.unwrap();
+
+ switch (ch) {
+ case '\t':
+ do {
+ cell_index += 1;
+ } while (cell_index % 8);
+ break;
+ default:
+ cell_index += wcwidth(read_res.unwrap());
+ break;
+ }
+ }
+ }
+ if (cell_start == cell_index) {
+ byte_start = byte_index;
+ }
+ if (!byte_end) {
+ byte_end = byte_index;
+ }
+
+ if (byte_start && byte_end) {
+ return this->sub_range(byte_start.value(), byte_end.value());
+ }
+
+ return string_fragment{};
+}
+
+size_t
+string_fragment::column_width() const
+{
+ auto index = this->sf_begin;
+ size_t retval = 0;
+
+ while (index < this->sf_end) {
+ auto read_res = ww898::utf::utf8::read(
+ [this, &index]() { return this->sf_string[index++]; });
+ if (read_res.isErr()) {
+ retval += 1;
+ } else {
+ auto ch = read_res.unwrap();
+
+ switch (ch) {
+ case '\t':
+ do {
+ retval += 1;
+ } while (retval % 8);
+ break;
+ default:
+ retval += wcwidth(read_res.unwrap());
+ break;
+ }
+ }
+ }
+
+ return retval;
+}
diff --git a/src/base/intern_string.hh b/src/base/intern_string.hh
index 4ae40da..f77dc49 100644
--- a/src/base/intern_string.hh
+++ b/src/base/intern_string.hh
@@ -33,7 +33,6 @@
#define intern_string_hh
#include <ostream>
-#include <string>
#include <vector>
#include <assert.h>
@@ -42,9 +41,9 @@
#include "fmt/format.h"
#include "optional.hpp"
+#include "result.h"
#include "scn/util/string_view.h"
#include "strnatcmp.h"
-#include "ww898/cp_utf8.hpp"
struct string_fragment {
using iterator = const char*;
@@ -143,6 +142,8 @@ struct string_fragment {
Result<ssize_t, const char*> utf8_length() const;
+ size_t column_width() const;
+
const char* data() const { return &this->sf_string[this->sf_begin]; }
const unsigned char* udata() const
@@ -157,16 +158,7 @@ struct string_fragment {
char front() const { return this->sf_string[this->sf_begin]; }
- uint32_t front_codepoint() const
- {
- size_t index = 0;
- try {
- return ww898::utf::utf8::read(
- [this, &index]() { return this->data()[index++]; });
- } catch (const std::runtime_error& e) {
- return this->data()[0];
- }
- }
+ uint32_t front_codepoint() const;
char back() const { return this->sf_string[this->sf_end - 1]; }
@@ -183,25 +175,10 @@ struct string_fragment {
bool empty() const { return !this->is_valid() || length() == 0; }
- Result<ssize_t, const char*> codepoint_to_byte_index(ssize_t cp_index) const
- {
- ssize_t retval = 0;
-
- while (cp_index > 0) {
- if (retval >= this->length()) {
- return Err("index is beyond the end of the string");
- }
- auto ch_len = TRY(ww898::utf::utf8::char_size([this, retval]() {
- return std::make_pair(this->data()[retval],
- this->length() - retval - 1);
- }));
-
- retval += ch_len;
- cp_index -= 1;
- }
+ Result<ssize_t, const char*> codepoint_to_byte_index(
+ ssize_t cp_index) const;
- return Ok(retval);
- }
+ string_fragment sub_cell_range(int cell_start, int cell_end) const;
const char& operator[](int index) const
{
@@ -228,6 +205,22 @@ struct string_fragment {
return memcmp(this->data(), sf.data(), sf.length()) == 0;
}
+ bool operator!=(const string_fragment& rhs) const
+ {
+ return !(*this == rhs);
+ }
+
+ bool operator<(const string_fragment& rhs) const
+ {
+ auto rc = strncmp(
+ this->data(), rhs.data(), std::min(this->length(), rhs.length()));
+ if (rc < 0 || (rc == 0 && this->length() < rhs.length())) {
+ return true;
+ }
+
+ return false;
+ }
+
bool iequal(const string_fragment& sf) const
{
if (this->length() != sf.length()) {
@@ -290,6 +283,12 @@ struct string_fragment {
this->sf_string, this->sf_begin + begin, this->sf_begin + end};
}
+ bool contains(const string_fragment& sf) const
+ {
+ return this->sf_string == sf.sf_string && this->sf_begin <= sf.sf_begin
+ && sf.sf_end <= this->sf_end;
+ }
+
size_t count(char ch) const
{
size_t retval = 0;
@@ -315,7 +314,9 @@ struct string_fragment {
}
template<typename P>
- string_fragment find_left_boundary(size_t start, P&& predicate) const
+ string_fragment find_left_boundary(size_t start,
+ P&& predicate,
+ size_t count = 1) const
{
assert((int) start <= this->length());
@@ -324,25 +325,33 @@ struct string_fragment {
}
while (start > 0) {
if (predicate(this->data()[start])) {
- start += 1;
- break;
+ count -= 1;
+ if (count == 0) {
+ start += 1;
+ break;
+ }
}
start -= 1;
}
return string_fragment{
this->sf_string,
- (int) start,
+ this->sf_begin + (int) start,
this->sf_end,
};
}
template<typename P>
- string_fragment find_right_boundary(size_t start, P&& predicate) const
+ string_fragment find_right_boundary(size_t start,
+ P&& predicate,
+ size_t count = 1) const
{
while ((int) start < this->length()) {
if (predicate(this->data()[start])) {
- break;
+ count -= 1;
+ if (count == 0) {
+ break;
+ }
}
start += 1;
}
@@ -355,10 +364,14 @@ struct string_fragment {
}
template<typename P>
- string_fragment find_boundaries_around(size_t start, P&& predicate) const
+ string_fragment find_boundaries_around(size_t start,
+ P&& predicate,
+ size_t count = 1) const
{
- return this->template find_left_boundary(start, predicate)
- .find_right_boundary(0, predicate);
+ auto left = this->template find_left_boundary(start, predicate, count);
+
+ return left.find_right_boundary(
+ start - left.sf_begin, predicate, count);
}
nonstd::optional<std::pair<uint32_t, string_fragment>> consume_codepoint()
@@ -446,8 +459,10 @@ struct string_fragment {
});
}
+ using split_when_result = std::pair<string_fragment, string_fragment>;
+
template<typename P>
- split_result split_when(P&& predicate) const
+ split_when_result split_when(P&& predicate) const
{
int consumed = 0;
while (consumed < this->length()) {
@@ -458,7 +473,33 @@ struct string_fragment {
consumed += 1;
}
- if (consumed == 0) {
+ return std::make_pair(
+ string_fragment{
+ this->sf_string,
+ this->sf_begin,
+ this->sf_begin + consumed,
+ },
+ string_fragment{
+ this->sf_string,
+ this->sf_begin + consumed
+ + ((consumed == this->length()) ? 0 : 1),
+ this->sf_end,
+ });
+ }
+
+ template<typename P>
+ split_result split_pair(P&& predicate) const
+ {
+ int consumed = 0;
+ while (consumed < this->length()) {
+ if (predicate(this->data()[consumed])) {
+ break;
+ }
+
+ consumed += 1;
+ }
+
+ if (consumed == this->length()) {
return nonstd::nullopt;
}
@@ -517,6 +558,8 @@ struct string_fragment {
return {this->data(), (size_t) this->length()};
}
+ std::string to_unquoted_string() const;
+
void clear()
{
this->sf_begin = 0;
@@ -823,6 +866,12 @@ operator==(const string_fragment& left, const intern_string_t& right)
&& (memcmp(left.data(), right.get(), left.length()) == 0);
}
+inline string_fragment
+operator"" _frag(const char* str, std::size_t len)
+{
+ return string_fragment::from_byte_range(str, 0, len);
+}
+
namespace std {
inline string
to_string(const string_fragment& s)
@@ -855,6 +904,12 @@ to_string_fragment(const std::string& s)
return string_fragment(s.c_str(), 0, s.length());
}
+inline string_fragment
+to_string_fragment(const scn::string_view& sv)
+{
+ return string_fragment::from_bytes(sv.data(), sv.length());
+}
+
struct frag_hasher {
size_t operator()(const string_fragment& sf) const
{
@@ -862,4 +917,11 @@ struct frag_hasher {
}
};
+struct intern_hasher {
+ size_t operator()(const intern_string_t& is) const
+ {
+ return hash_str(is.c_str(), is.size());
+ }
+};
+
#endif
diff --git a/src/base/intern_string.tests.cc b/src/base/intern_string.tests.cc
index 8816803..71ff7c2 100644
--- a/src/base/intern_string.tests.cc
+++ b/src/base/intern_string.tests.cc
@@ -43,6 +43,15 @@ TEST_CASE("string_fragment::startswith")
CHECK_FALSE(sf.startswith("abc"));
}
+TEST_CASE("string_fragment::lt")
+{
+ auto sf1 = string_fragment::from_const("abc");
+ auto sf2 = string_fragment::from_const("abcdef");
+
+ CHECK(sf1 < sf2);
+ CHECK_FALSE(sf2 < sf1);
+}
+
TEST_CASE("split_lines")
{
std::string in1 = "Hello, World!";
@@ -129,6 +138,12 @@ TEST_CASE("find_left_boundary")
auto world_sf = sf.find_left_boundary(
in1.length() - 3, [](auto ch) { return ch == '\n'; });
CHECK(world_sf.to_string() == "World!\n");
+ auto world_sf2 = sf.find_left_boundary(
+ in1.length() - 3, [](auto ch) { return ch == '\n'; }, 2);
+ CHECK(world_sf2.to_string() == "Hello,\nWorld!\n");
+ auto world_sf3 = sf.find_left_boundary(
+ in1.length() - 3, [](auto ch) { return ch == '\n'; }, 3);
+ CHECK(world_sf3.to_string() == "Hello,\nWorld!\n");
auto full_sf
= sf.find_left_boundary(3, [](auto ch) { return ch == '\n'; });
CHECK(full_sf.to_string() == in1);
@@ -137,16 +152,35 @@ TEST_CASE("find_left_boundary")
TEST_CASE("find_right_boundary")
{
- std::string in1 = "Hello,\nWorld!\n";
-
{
- auto sf = string_fragment{in1};
+ const auto sf = string_fragment::from_const("Hello,\nWorld!\n");
- auto world_sf = sf.find_right_boundary(
- in1.length() - 3, [](auto ch) { return ch == '\n'; });
+ auto world_sf = sf.find_right_boundary(sf.length() - 3,
+ string_fragment::tag1{'\n'});
CHECK(world_sf.to_string() == "Hello,\nWorld!");
auto hello_sf
= sf.find_right_boundary(3, [](auto ch) { return ch == '\n'; });
CHECK(hello_sf.to_string() == "Hello,");
+ auto hello_sf2
+ = sf.find_right_boundary(3, string_fragment::tag1{'\n'}, 2);
+ CHECK(hello_sf2.to_string() == "Hello,\nWorld!");
+ }
+}
+
+TEST_CASE("find_boundaries_around")
+{
+ {
+ const auto sf = string_fragment::from_const(
+ R"(Hello,
+World!
+Goodbye,
+World!)");
+
+ auto all_sf1
+ = sf.find_boundaries_around(3, string_fragment::tag1{'\n'});
+ CHECK(all_sf1 == "Hello,");
+ auto all_sf2
+ = sf.find_boundaries_around(3, string_fragment::tag1{'\n'}, 2);
+ CHECK(all_sf2 == "Hello,\nWorld!");
}
}
diff --git a/src/base/is_utf8.cc b/src/base/is_utf8.cc
index f55dfe0..b8fbab0 100644
--- a/src/base/is_utf8.cc
+++ b/src/base/is_utf8.cc
@@ -64,7 +64,7 @@ is_utf8(string_fragment str, nonstd::optional<unsigned char> terminator)
{
const auto* ustr = str.udata();
utf8_scan_result retval;
- ssize_t i = 0;
+ ssize_t i = 0, valid_end = 0;
while (i < str.length()) {
if (ustr[i] == '\x1b') {
@@ -72,20 +72,21 @@ is_utf8(string_fragment str, nonstd::optional<unsigned char> terminator)
}
if (terminator && ustr[i] == terminator.value()) {
- if (retval.usr_message == nullptr) {
- retval.usr_valid_frag = str.sub_range(0, i);
- }
retval.usr_remaining = str.substr(i + 1);
break;
}
+ retval.usr_column_width_guess += 1;
if (retval.usr_message != nullptr) {
i += 1;
continue;
}
- retval.usr_valid_frag = str.sub_range(0, i);
+ valid_end = i;
if (ustr[i] <= 0x7F) /* 00..7F */ {
+ if (ustr[i] == '\t') {
+ retval.usr_column_width_guess += 7;
+ }
i += 1;
} else if (ustr[i] >= 0xC2 && ustr[i] <= 0xDF) /* C2..DF 80..BF */ {
if (i + 1 < str.length()) /* Expect a 2nd byte */ {
@@ -308,6 +309,8 @@ is_utf8(string_fragment str, nonstd::optional<unsigned char> terminator)
}
if (retval.usr_message == nullptr) {
retval.usr_valid_frag = str.sub_range(0, i);
+ } else {
+ retval.usr_valid_frag = str.sub_range(0, valid_end);
}
return retval;
}
diff --git a/src/base/is_utf8.hh b/src/base/is_utf8.hh
index 56a959f..81d09c6 100644
--- a/src/base/is_utf8.hh
+++ b/src/base/is_utf8.hh
@@ -40,6 +40,7 @@ struct utf8_scan_result {
string_fragment usr_valid_frag{string_fragment::invalid()};
nonstd::optional<string_fragment> usr_remaining;
bool usr_has_ansi{false};
+ size_t usr_column_width_guess{0};
const char* remaining_ptr(const string_fragment& frag) const
{
diff --git a/src/base/itertools.hh b/src/base/itertools.hh
index 058ceb8..70286fb 100644
--- a/src/base/itertools.hh
+++ b/src/base/itertools.hh
@@ -31,6 +31,8 @@
#define lnav_itertools_hh
#include <algorithm>
+#include <deque>
+#include <map>
#include <memory>
#include <set>
#include <type_traits>
@@ -138,8 +140,16 @@ struct max_with_init {
struct sum {};
+struct to_vector {};
+
} // namespace details
+inline details::to_vector
+to_vector()
+{
+ return details::to_vector{};
+}
+
template<typename T>
inline details::unwrap_or<T>
unwrap_or(T value)
@@ -619,12 +629,32 @@ operator|(nonstd::optional<T> in,
template<typename T, typename F>
auto
-operator|(const T& in, const lnav::itertools::details::mapper<F>& mapper)
- -> std::vector<std::remove_const_t<std::remove_reference_t<
- decltype(mapper.m_func(std::declval<typename T::value_type>()))>>>
+operator|(const std::set<T>& in,
+ const lnav::itertools::details::mapper<F>& mapper)
+ -> std::set<std::remove_const_t<
+ std::remove_reference_t<decltype(mapper.m_func(std::declval<T>()))>>>
{
- using return_type = std::vector<std::remove_const_t<std::remove_reference_t<
- decltype(mapper.m_func(std::declval<typename T::value_type>()))>>>;
+ using return_type = std::set<std::remove_const_t<
+ std::remove_reference_t<decltype(mapper.m_func(std::declval<T>()))>>>;
+ return_type retval;
+
+ std::transform(in.begin(),
+ in.end(),
+ std::inserter(retval, retval.begin()),
+ mapper.m_func);
+
+ return retval;
+}
+
+template<typename T, typename F>
+auto
+operator|(const std::vector<T>& in,
+ const lnav::itertools::details::mapper<F>& mapper)
+ -> std::vector<std::remove_const_t<
+ std::remove_reference_t<decltype(mapper.m_func(std::declval<T>()))>>>
+{
+ using return_type = std::vector<std::remove_const_t<
+ std::remove_reference_t<decltype(mapper.m_func(std::declval<T>()))>>>;
return_type retval;
retval.reserve(in.size());
@@ -636,12 +666,50 @@ operator|(const T& in, const lnav::itertools::details::mapper<F>& mapper)
template<typename T, typename F>
auto
-operator|(const T& in, const lnav::itertools::details::mapper<F>& mapper)
+operator|(const std::deque<T>& in,
+ const lnav::itertools::details::mapper<F>& mapper)
+ -> std::vector<std::remove_const_t<
+ std::remove_reference_t<decltype(mapper.m_func(std::declval<T>()))>>>
+{
+ using return_type = std::vector<std::remove_const_t<
+ std::remove_reference_t<decltype(mapper.m_func(std::declval<T>()))>>>;
+ return_type retval;
+
+ retval.reserve(in.size());
+ std::transform(
+ in.begin(), in.end(), std::back_inserter(retval), mapper.m_func);
+
+ return retval;
+}
+
+template<typename K, typename V, typename F>
+auto
+operator|(const std::map<K, V>& in,
+ const lnav::itertools::details::mapper<F>& mapper)
-> std::vector<
- std::remove_const_t<decltype(((*in.begin()).*mapper.m_func)())>>
+ std::remove_const_t<std::remove_reference_t<decltype(mapper.m_func(
+ std::declval<typename std::map<K, V>::value_type>()))>>>
{
using return_type = std::vector<
- std::remove_const_t<decltype(((*in.begin()).*mapper.m_func)())>>;
+ std::remove_const_t<std::remove_reference_t<decltype(mapper.m_func(
+ std::declval<typename std::map<K, V>::value_type>()))>>>;
+ return_type retval;
+
+ retval.reserve(in.size());
+ std::transform(
+ in.begin(), in.end(), std::back_inserter(retval), mapper.m_func);
+
+ return retval;
+}
+
+template<typename T, typename F>
+auto
+operator|(const T& in, const lnav::itertools::details::mapper<F>& mapper)
+ -> std::vector<std::remove_const_t<
+ std::remove_reference_t<decltype(((*in.begin()).*mapper.m_func)())>>>
+{
+ using return_type = std::vector<std::remove_const_t<
+ std::remove_reference_t<decltype(((*in.begin()).*mapper.m_func)())>>>;
return_type retval;
retval.reserve(in.size());
@@ -782,4 +850,16 @@ operator|(nonstd::optional<T> in,
return in.value_or(unwrapper.uo_value);
}
+template<typename T>
+std::vector<T>
+operator|(std::set<T>&& in, lnav::itertools::details::to_vector tv)
+{
+ std::vector<T> retval;
+
+ retval.reserve(in.size());
+ std::copy(in.begin(), in.end(), std::back_inserter(retval));
+
+ return retval;
+}
+
#endif
diff --git a/src/base/keycodes.hh b/src/base/keycodes.hh
new file mode 100644
index 0000000..7f3c3e2
--- /dev/null
+++ b/src/base/keycodes.hh
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2023, 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.
+ */
+
+#ifndef lnav_keycodes_hh
+#define lnav_keycodes_hh
+
+inline constexpr int
+KEY_CTRL(char k)
+{
+ return k & 0x1f;
+}
+
+#define KEY_ESCAPE 0x1b
+#define KEY_DELETE 0x7f
+
+#endif
diff --git a/src/base/line_range.hh b/src/base/line_range.hh
new file mode 100644
index 0000000..70deb25
--- /dev/null
+++ b/src/base/line_range.hh
@@ -0,0 +1,164 @@
+/**
+ * Copyright (c) 2023, 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.
+ */
+
+#ifndef lnav_base_line_range_hh
+#define lnav_base_line_range_hh
+
+#include <string>
+
+/**
+ * Encapsulates a range in a string.
+ */
+struct line_range {
+ enum class unit {
+ bytes,
+ codepoint,
+ };
+
+ int lr_start;
+ int lr_end;
+ unit lr_unit;
+
+ explicit line_range(int start = -1, int end = -1, unit u = unit::bytes)
+ : lr_start(start), lr_end(end), lr_unit(u)
+ {
+ }
+
+ bool is_valid() const { return this->lr_start != -1; }
+
+ int length() const
+ {
+ return this->lr_end == -1 ? INT_MAX : this->lr_end - this->lr_start;
+ }
+
+ bool empty() const { return this->length() == 0; }
+
+ void clear()
+ {
+ this->lr_start = -1;
+ this->lr_end = -1;
+ }
+
+ int end_for_string(const std::string& str) const
+ {
+ return this->lr_end == -1 ? str.length() : this->lr_end;
+ }
+
+ bool contains(int pos) const
+ {
+ return this->lr_start <= pos
+ && (this->lr_end == -1 || pos < this->lr_end);
+ }
+
+ bool contains(const struct line_range& other) const
+ {
+ return this->contains(other.lr_start)
+ && (this->lr_end == -1 || other.lr_end <= this->lr_end);
+ }
+
+ bool intersects(const struct line_range& other) const
+ {
+ if (this->contains(other.lr_start)) {
+ return true;
+ }
+ if (other.lr_end > 0 && this->contains(other.lr_end - 1)) {
+ return true;
+ }
+ if (other.contains(this->lr_start)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ line_range intersection(const struct line_range& other) const;
+
+ line_range& shift(int32_t start, int32_t amount);
+
+ line_range& shift_range(const line_range& cover, int32_t amount);
+
+ void ltrim(const char* str)
+ {
+ while (this->lr_start < this->lr_end && isspace(str[this->lr_start])) {
+ this->lr_start += 1;
+ }
+ }
+
+ bool operator<(const struct line_range& rhs) const
+ {
+ if (this->lr_start < rhs.lr_start) {
+ return true;
+ }
+ if (this->lr_start > rhs.lr_start) {
+ return false;
+ }
+
+ // this->lr_start == rhs.lr_start
+ if (this->lr_end == rhs.lr_end) {
+ return false;
+ }
+
+ // When the start is the same, the longer range has a lower priority
+ // than the shorter range.
+ if (rhs.lr_end == -1) {
+ return false;
+ }
+
+ if ((this->lr_end == -1) || (this->lr_end > rhs.lr_end)) {
+ return true;
+ }
+ return false;
+ }
+
+ bool operator==(const struct line_range& rhs) const
+ {
+ return (this->lr_start == rhs.lr_start && this->lr_end == rhs.lr_end);
+ }
+
+ const char* substr(const std::string& str) const
+ {
+ if (this->lr_start == -1) {
+ return str.c_str();
+ }
+ return &(str.c_str()[this->lr_start]);
+ }
+
+ size_t sublen(const std::string& str) const
+ {
+ if (this->lr_start == -1) {
+ return str.length();
+ }
+ if (this->lr_end == -1) {
+ return str.length() - this->lr_start;
+ }
+ return this->length();
+ }
+};
+
+#endif
diff --git a/src/base/lnav.console.cc b/src/base/lnav.console.cc
index a34ebac..b9d2d6c 100644
--- a/src/base/lnav.console.cc
+++ b/src/base/lnav.console.cc
@@ -45,6 +45,45 @@ using namespace lnav::roles::literals;
namespace lnav {
namespace console {
+snippet
+snippet::from_content_with_offset(intern_string_t src,
+ const attr_line_t& content,
+ size_t offset,
+ const std::string& errmsg)
+{
+ auto content_sf = string_fragment::from_str(content.get_string());
+ auto line_with_error = content_sf.find_boundaries_around(
+ offset, string_fragment::tag1{'\n'});
+ auto line_with_context = content_sf.find_boundaries_around(
+ offset, string_fragment::tag1{'\n'}, 3);
+ auto line_number = content_sf.sub_range(0, offset).count('\n');
+ auto erroff_in_line = offset - line_with_error.sf_begin;
+
+ attr_line_t pointer;
+
+ pointer.append(erroff_in_line, ' ')
+ .append("^ "_snippet_border)
+ .append(lnav::roles::error(errmsg))
+ .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
+
+ snippet retval;
+ retval.s_content
+ = content.subline(line_with_context.sf_begin,
+ line_with_error.sf_end - line_with_context.sf_begin);
+ if (line_with_error.sf_end >= retval.s_content.get_string().size()) {
+ retval.s_content.append("\n");
+ }
+ retval.s_content.append(pointer).append(
+ content.subline(line_with_error.sf_end,
+ line_with_context.sf_end - line_with_error.sf_end));
+ retval.s_location = source_location{
+ src,
+ static_cast<int32_t>(1 + line_number),
+ };
+
+ return retval;
+}
+
user_message
user_message::raw(const attr_line_t& al)
{
@@ -238,13 +277,38 @@ curses_color_to_terminal_color(int curses_color)
}
}
+static bool
+get_no_color()
+{
+ return getenv("NO_COLOR") != nullptr;
+}
+
+static bool
+get_yes_color()
+{
+ return getenv("YES_COLOR") != nullptr;
+}
+
+static bool
+get_fd_tty(int fd)
+{
+ return isatty(fd);
+}
+
void
println(FILE* file, const attr_line_t& al)
{
+ static const auto IS_NO_COLOR = get_no_color();
+ static const auto IS_YES_COLOR = get_yes_color();
+ static const auto IS_STDOUT_TTY = get_fd_tty(STDOUT_FILENO);
+ static const auto IS_STDERR_TTY = get_fd_tty(STDERR_FILENO);
+
const auto& str = al.get_string();
- if (getenv("NO_COLOR") != nullptr
- || (!isatty(fileno(file)) && getenv("YES_COLOR") == nullptr))
+ if (IS_NO_COLOR || (file != stdout && file != stderr)
+ || (((file == stdout && !IS_STDOUT_TTY)
+ || (file == stderr && !IS_STDERR_TTY))
+ && !IS_YES_COLOR))
{
fmt::print(file, "{}\n", str);
return;
@@ -273,6 +337,7 @@ println(FILE* file, const attr_line_t& al)
auto line_style = fmt::text_style{};
auto fg_style = fmt::text_style{};
auto start = last_point.value();
+ nonstd::optional<std::string> href;
for (const auto& attr : al.get_attrs()) {
if (!attr.sa_range.contains(start)
@@ -282,7 +347,10 @@ println(FILE* file, const attr_line_t& al)
}
try {
- if (attr.sa_type == &VC_BACKGROUND) {
+ if (attr.sa_type == &VC_HYPERLINK) {
+ auto saw = string_attr_wrapper<std::string>(&attr);
+ href = saw.get();
+ } else if (attr.sa_type == &VC_BACKGROUND) {
auto saw = string_attr_wrapper<int64_t>(&attr);
auto color_opt = curses_color_to_terminal_color(saw.get());
@@ -349,6 +417,9 @@ println(FILE* file, const attr_line_t& al)
case role_t::VCR_TEXT:
case role_t::VCR_IDENTIFIER:
break;
+ case role_t::VCR_ALT_ROW:
+ line_style |= fmt::emphasis::bold;
+ break;
case role_t::VCR_SEARCH:
line_style |= fmt::emphasis::reverse;
break;
@@ -407,6 +478,7 @@ println(FILE* file, const attr_line_t& al)
case role_t::VCR_LIST_GLYPH:
line_style |= fmt::fg(fmt::terminal_color::yellow);
break;
+ case role_t::VCR_INLINE_CODE:
case role_t::VCR_QUOTED_CODE:
default_fg_style
= fmt::fg(fmt::terminal_color::white);
@@ -442,12 +514,69 @@ println(FILE* file, const attr_line_t& al)
line_style |= default_bg_style;
}
+ if (line_style.has_foreground() && line_style.has_background()
+ && !line_style.get_foreground().is_rgb
+ && !line_style.get_background().is_rgb
+ && line_style.get_foreground().value.term_color
+ == line_style.get_background().value.term_color)
+ {
+ auto new_style = fmt::text_style{};
+
+ if (line_style.has_emphasis()) {
+ new_style |= line_style.get_emphasis();
+ }
+ new_style |= fmt::fg(line_style.get_foreground());
+ if (line_style.get_background().value.term_color
+ == lnav::enums::to_underlying(fmt::terminal_color::black))
+ {
+ new_style |= fmt::bg(fmt::terminal_color::white);
+ } else {
+ new_style |= fmt::bg(fmt::terminal_color::black);
+ }
+ line_style = new_style;
+ }
+
+ if (href) {
+ fmt::print(file, FMT_STRING("\x1b]8;;{}\x1b\\"), href.value());
+ }
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));
+ auto sub = std::string{};
+
+ for (auto lpc = start; lpc < actual_end;) {
+ auto cp_start = lpc;
+ auto read_res = ww898::utf::utf8::read(
+ [&str, &lpc]() { return str[lpc++]; });
+
+ if (read_res.isErr()) {
+ sub.append(fmt::format(
+ FMT_STRING("{:?}"),
+ fmt::string_view{&str[cp_start], lpc - cp_start}));
+ continue;
+ }
+
+ auto ch = read_res.unwrap();
+ switch (ch) {
+ case '\b':
+ sub.append("\u232b");
+ break;
+ case '\x1b':
+ sub.append("\u238b");
+ break;
+ case '\x07':
+ sub.append("\U0001F514");
+ break;
+
+ default:
+ sub.append(&str[cp_start], lpc - cp_start);
+ break;
+ }
+ }
+
+ fmt::print(file, line_style, FMT_STRING("{}"), sub);
+ }
+ if (href) {
+ fmt::print(file, FMT_STRING("\x1b]8;;\x1b\\"));
}
last_point = point;
}
diff --git a/src/base/lnav.console.hh b/src/base/lnav.console.hh
index ac4c2b0..591093e 100644
--- a/src/base/lnav.console.hh
+++ b/src/base/lnav.console.hh
@@ -42,6 +42,11 @@ namespace console {
void println(FILE* file, const attr_line_t& al);
struct snippet {
+ static snippet from_content_with_offset(intern_string_t src,
+ const attr_line_t& content,
+ size_t offset,
+ const std::string& errmsg);
+
static snippet from(intern_string_t src, const attr_line_t& content)
{
snippet retval;
@@ -114,6 +119,26 @@ struct user_message {
}
template<typename C>
+ user_message& with_context_snippets(C snippets)
+ {
+ this->um_snippets.insert(this->um_snippets.begin(),
+ std::make_move_iterator(std::begin(snippets)),
+ std::make_move_iterator(std::end(snippets)));
+ if (this->um_snippets.size() > 1) {
+ for (auto iter = this->um_snippets.begin();
+ iter != this->um_snippets.end();)
+ {
+ if (iter->s_content.empty()) {
+ iter = this->um_snippets.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+ }
+ return *this;
+ }
+
+ template<typename C>
user_message& with_snippets(C snippets)
{
this->um_snippets.insert(this->um_snippets.end(),
@@ -121,7 +146,8 @@ struct user_message {
std::make_move_iterator(std::end(snippets)));
if (this->um_snippets.size() > 1) {
for (auto iter = this->um_snippets.begin();
- iter != this->um_snippets.end();) {
+ iter != this->um_snippets.end();)
+ {
if (iter->s_content.empty()) {
iter = this->um_snippets.erase(iter);
} else {
diff --git a/src/base/lnav.gzip.hh b/src/base/lnav.gzip.hh
index bd73965..060aad4 100644
--- a/src/base/lnav.gzip.hh
+++ b/src/base/lnav.gzip.hh
@@ -34,12 +34,27 @@
#include <string>
+#include <sys/time.h>
+
#include "auto_mem.hh"
#include "result.h"
namespace lnav {
namespace gzip {
+struct header {
+ timeval h_mtime{};
+ auto_buffer h_extra{auto_buffer::alloc(0)};
+ std::string h_name;
+ std::string h_comment;
+
+ bool empty() const
+ {
+ return this->h_mtime.tv_sec == 0 && this->h_extra.empty()
+ && this->h_name.empty() && this->h_comment.empty();
+ }
+};
+
bool is_gzipped(const char* buffer, size_t len);
Result<auto_buffer, std::string> compress(const void* input, size_t len);
diff --git a/src/base/lnav_log.cc b/src/base/lnav_log.cc
index 2e5dbba..b770698 100644
--- a/src/base/lnav_log.cc
+++ b/src/base/lnav_log.cc
@@ -190,7 +190,7 @@ log_argv(int argc, char* argv[])
const char* log_path = getenv("LNAV_LOG_PATH");
if (log_path != nullptr) {
- lnav_log_file = make_optional_from_nullable(fopen(log_path, "a"));
+ lnav_log_file = make_optional_from_nullable(fopen(log_path, "ae"));
}
log_info("argv[%d] =", argc);
diff --git a/src/base/map_util.hh b/src/base/map_util.hh
new file mode 100644
index 0000000..a3e565e
--- /dev/null
+++ b/src/base/map_util.hh
@@ -0,0 +1,126 @@
+/**
+ * Copyright (c) 2023, 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.
+ */
+
+#ifndef lnav_map_util_hh
+#define lnav_map_util_hh
+
+#include <functional>
+#include <map>
+#include <type_traits>
+#include <vector>
+
+#include "optional.hpp"
+
+namespace lnav {
+namespace map {
+
+template<typename C>
+nonstd::optional<
+ std::reference_wrapper<std::conditional_t<std::is_const<C>::value,
+ const typename C::mapped_type,
+ typename C::mapped_type>>>
+find(C& container, const typename C::key_type& key)
+{
+ auto iter = container.find(key);
+ if (iter != container.end()) {
+ return nonstd::make_optional(std::ref(iter->second));
+ }
+
+ return nonstd::nullopt;
+}
+
+template<typename K, typename V, typename M = std::map<K, V>>
+M
+from_vec(const std::vector<std::pair<K, V>>& container)
+{
+ M retval;
+
+ for (const auto& elem : container) {
+ retval[elem.first] = elem.second;
+ }
+
+ return retval;
+}
+
+template<typename K, typename V>
+class small : public std::vector<std::pair<K, V>> {
+public:
+ auto insert(const K& key, const V& value)
+ {
+ auto pos = this->begin();
+
+ while (pos != this->end() && pos->first < key) {
+ ++pos;
+ }
+ return this->emplace(pos, std::make_pair(key, value));
+ }
+
+ auto find(const K& key)
+ {
+ auto retval = this->begin();
+
+ while (retval != this->end()
+ && (retval->first < key || key < retval->first))
+ {
+ ++retval;
+ }
+
+ return retval;
+ }
+
+ auto find(const K& key) const
+ {
+ auto retval = this->begin();
+
+ while (retval != this->end()
+ && (retval->first < key || key < retval->first))
+ {
+ ++retval;
+ }
+
+ return retval;
+ }
+
+ V& operator[](const K& key)
+ {
+ auto iter = this->find(key);
+ if (iter != this->end()) {
+ return iter->second;
+ }
+
+ this->emplace_back(key, V{});
+
+ return this->back().second;
+ }
+};
+
+} // namespace map
+} // namespace lnav
+
+#endif
diff --git a/src/base/math_util.hh b/src/base/math_util.hh
index 842b319..fcd507c 100644
--- a/src/base/math_util.hh
+++ b/src/base/math_util.hh
@@ -33,6 +33,7 @@
#include <sys/types.h>
#undef rounddown
+#undef roundup
/**
* Round down a number based on a given granularity.
@@ -47,6 +48,17 @@ rounddown(Size size, Step step)
return size - (size % step);
}
+template<typename Size, typename Step>
+inline auto
+roundup(Size size, Step step)
+{
+ auto retval = size + (step - 1);
+
+ retval -= (retval % step);
+
+ return retval;
+}
+
inline int
rounddown_offset(size_t size, int step, int offset)
{
@@ -70,4 +82,74 @@ abs_diff(T a, T b)
return a > b ? a - b : b - a;
}
+template<typename T>
+class clamped {
+public:
+ static clamped from(T value, T min, T max) { return {value, min, max}; }
+
+ clamped& operator+=(T rhs)
+ {
+ if (rhs < 0) {
+ return this->operator-=(-rhs);
+ }
+
+ if (this->c_value + rhs < this->c_max) {
+ this->c_value += rhs;
+ } else {
+ this->c_value = this->c_max;
+ }
+
+ return *this;
+ }
+
+ clamped& operator-=(T rhs)
+ {
+ if (rhs < 0) {
+ return this->operator+=(-rhs);
+ }
+
+ if (this->c_value - rhs > this->c_min) {
+ this->c_value -= rhs;
+ } else {
+ this->c_value = this->c_min;
+ }
+
+ return *this;
+ }
+
+ bool available_to_consume(T rhs) const
+ {
+ return (this->c_value - rhs > this->c_min);
+ }
+
+ bool try_consume(T rhs)
+ {
+ if (rhs == 0) {
+ return false;
+ }
+
+ if (this->c_value - rhs > this->c_min) {
+ this->c_value -= rhs;
+ return true;
+ }
+
+ return false;
+ }
+
+ operator T() const { return this->c_value; }
+
+ bool is_min() const { return this->c_value == this->c_min; }
+
+ T get_min() const { return this->c_min; }
+
+ T get_max() const { return this->c_max; }
+
+private:
+ clamped(T value, T min, T max) : c_value(value), c_min(min), c_max(max) {}
+
+ T c_value;
+ T c_min;
+ T c_max;
+};
+
#endif
diff --git a/src/base/network.tcp.hh b/src/base/network.tcp.hh
index 49fc392..c046bb6 100644
--- a/src/base/network.tcp.hh
+++ b/src/base/network.tcp.hh
@@ -33,6 +33,7 @@
#include <string>
#include "auto_fd.hh"
+#include "optional.hpp"
#include "result.h"
namespace network {
diff --git a/src/base/piper.file.cc b/src/base/piper.file.cc
new file mode 100644
index 0000000..1443604
--- /dev/null
+++ b/src/base/piper.file.cc
@@ -0,0 +1,81 @@
+/**
+ * Copyright (c) 2023, 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 "piper.file.hh"
+
+#include <arpa/inet.h>
+#include <unistd.h>
+
+#include "base/lnav_log.hh"
+#include "base/paths.hh"
+
+namespace lnav {
+namespace piper {
+
+const char HEADER_MAGIC[4] = {'L', 0, 'N', 1};
+
+const ghc::filesystem::path&
+storage_path()
+{
+ static auto INSTANCE = lnav::paths::workdir() / "piper";
+
+ return INSTANCE;
+}
+
+nonstd::optional<auto_buffer>
+read_header(int fd, const char* first8)
+{
+ if (memcmp(first8, HEADER_MAGIC, sizeof(HEADER_MAGIC)) != 0) {
+ log_trace("first 4 bytes are not a piper header: %02x%02x%02x%02x",
+ first8[0],
+ first8[1],
+ first8[2],
+ first8[3]);
+ return nonstd::nullopt;
+ }
+
+ uint32_t meta_size = ntohl(*((uint32_t*) &first8[4]));
+
+ auto meta_buf = auto_buffer::alloc(meta_size);
+ if (meta_buf.in() == nullptr) {
+ log_error("failed to alloc %d bytes for header", meta_size);
+ return nonstd::nullopt;
+ }
+ auto meta_prc = pread(fd, meta_buf.in(), meta_size, 8);
+ if (meta_prc != meta_size) {
+ log_error("failed to read piper header: %s", strerror(errno));
+ return nonstd::nullopt;
+ }
+ meta_buf.resize(meta_size);
+
+ return meta_buf;
+}
+
+} // namespace piper
+} // namespace lnav
diff --git a/src/piper_proc.hh b/src/base/piper.file.hh
index 0caf29e..7a263ea 100644
--- a/src/piper_proc.hh
+++ b/src/base/piper.file.hh
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2007-2012, Timothy Stack
+ * Copyright (c) 2023, Timothy Stack
*
* All rights reserved.
*
@@ -25,62 +25,52 @@
* 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.
- *
- * @file piper_proc.hh
*/
-#ifndef piper_proc_hh
-#define piper_proc_hh
+#ifndef lnav_piper_file_hh
+#define lnav_piper_file_hh
+#include <map>
#include <string>
-#include <sys/types.h>
+#include <sys/time.h>
-#include "base/auto_fd.hh"
+#include "auto_mem.hh"
+#include "ghc/filesystem.hpp"
+#include "optional.hpp"
+#include "time_util.hh"
-/**
- * Creates a subprocess that reads data from a pipe and writes it to a file so
- * lnav can treat it like any other file and do preads.
- *
- * TODO: Add support for gzipped files.
- */
-class piper_proc {
-public:
- class error : public std::exception {
- public:
- error(int err) : e_err(err) {}
+namespace lnav {
+namespace piper {
- int e_err;
- };
+struct header {
+ timeval h_ctime{};
+ std::string h_name;
+ std::string h_cwd;
+ std::map<std::string, std::string> h_env;
- /**
- * Forks a subprocess that will read data from the given file descriptor
- * and write it to a temporary file.
- *
- * @param pipefd The file descriptor to read the file contents from.
- * @param timestamp True if an ISO 8601 timestamp should be prepended onto
- * the lines read from pipefd.
- * @param filefd The descriptor for the backing file.
- */
- piper_proc(auto_fd pipefd, bool timestamp, auto_fd filefd);
+ bool operator<(const header& rhs) const
+ {
+ if (this->h_ctime < rhs.h_ctime) {
+ return true;
+ }
- bool has_exited();
+ if (this->h_ctime == rhs.h_ctime) {
+ return this->h_name < rhs.h_name;
+ }
- /**
- * Terminates the child process.
- */
- virtual ~piper_proc();
+ return false;
+ }
+};
- /** @return The file descriptor for the temporary file. */
- auto_fd get_fd() { return this->pp_fd.dup(); }
+const ghc::filesystem::path& storage_path();
- pid_t get_child_pid() const { return this->pp_child; }
+constexpr size_t HEADER_SIZE = 8;
+extern const char HEADER_MAGIC[4];
-private:
- /** A file descriptor that refers to the temporary file. */
- auto_fd pp_fd;
+nonstd::optional<auto_buffer> read_header(int fd, const char* first8);
+
+} // namespace piper
+} // namespace lnav
- /** The child process' pid. */
- pid_t pp_child;
-};
#endif
diff --git a/src/base/string_attr_type.cc b/src/base/string_attr_type.cc
index 9a3950b..fb31036 100644
--- a/src/base/string_attr_type.cc
+++ b/src/base/string_attr_type.cc
@@ -40,12 +40,13 @@ string_attr_type<void> SA_PREFORMATTED("preformatted");
string_attr_type<std::string> SA_INVALID("invalid");
string_attr_type<std::string> SA_ERROR("error");
string_attr_type<int64_t> SA_LEVEL("level");
-string_attr_type<string_fragment> SA_ORIGIN("origin");
string_attr_type<int64_t> SA_ORIGIN_OFFSET("origin-offset");
string_attr_type<role_t> VC_ROLE("role");
string_attr_type<role_t> VC_ROLE_FG("role-fg");
string_attr_type<text_attrs> VC_STYLE("style");
string_attr_type<int64_t> VC_GRAPHIC("graphic");
+string_attr_type<block_elem_t> VC_BLOCK_ELEM("block-elem");
string_attr_type<int64_t> VC_FOREGROUND("foreground");
string_attr_type<int64_t> VC_BACKGROUND("background");
+string_attr_type<std::string> VC_HYPERLINK("hyperlink");
diff --git a/src/base/string_attr_type.hh b/src/base/string_attr_type.hh
index 5bff345..0b8cd85 100644
--- a/src/base/string_attr_type.hh
+++ b/src/base/string_attr_type.hh
@@ -55,9 +55,11 @@ enum class role_t : int32_t {
VCR_ALT_ROW, /*< Highlight for alternating rows in a list */
VCR_HIDDEN,
VCR_CURSOR_LINE,
+ VCR_DISABLED_CURSOR_LINE,
VCR_ADJUSTED_TIME,
VCR_SKEWED_TIME,
VCR_OFFSET_TIME,
+ VCR_FILE_OFFSET,
VCR_INVALID_MSG,
VCR_STATUS, /*< Normal status line text. */
VCR_WARN_STATUS,
@@ -94,6 +96,9 @@ enum class role_t : int32_t {
VCR_DOC_DIRECTIVE,
VCR_VARIABLE,
VCR_SYMBOL,
+ VCR_NULL,
+ VCR_ASCII_CTRL,
+ VCR_NON_ASCII,
VCR_NUMBER,
VCR_RE_SPECIAL,
VCR_RE_REPEAT,
@@ -125,6 +130,13 @@ enum class role_t : int32_t {
VCR_FOOTNOTE_BORDER,
VCR_FOOTNOTE_TEXT,
VCR_SNIPPET_BORDER,
+ VCR_INDENT_GUIDE,
+ VCR_INLINE_CODE,
+ VCR_FUNCTION,
+ VCR_TYPE,
+ VCR_SEP_REF_ACC,
+ VCR_SUGGESTION,
+ VCR_SELECTED_TEXT,
VCR__MAX
};
@@ -156,6 +168,11 @@ struct text_attrs {
nonstd::optional<short> ta_bg_color;
};
+struct block_elem_t {
+ wchar_t value;
+ role_t role;
+};
+
using string_attr_value = mapbox::util::variant<int64_t,
role_t,
text_attrs,
@@ -164,7 +181,8 @@ using string_attr_value = mapbox::util::variant<int64_t,
std::shared_ptr<logfile>,
bookmark_metadata*,
timespec,
- string_fragment>;
+ string_fragment,
+ block_elem_t>;
class string_attr_type_base {
public:
@@ -211,15 +229,16 @@ extern string_attr_type<void> SA_PREFORMATTED;
extern string_attr_type<std::string> SA_INVALID;
extern string_attr_type<std::string> SA_ERROR;
extern string_attr_type<int64_t> SA_LEVEL;
-extern string_attr_type<string_fragment> SA_ORIGIN;
extern string_attr_type<int64_t> SA_ORIGIN_OFFSET;
extern string_attr_type<role_t> VC_ROLE;
extern string_attr_type<role_t> VC_ROLE_FG;
extern string_attr_type<text_attrs> VC_STYLE;
extern string_attr_type<int64_t> VC_GRAPHIC;
+extern string_attr_type<block_elem_t> VC_BLOCK_ELEM;
extern string_attr_type<int64_t> VC_FOREGROUND;
extern string_attr_type<int64_t> VC_BACKGROUND;
+extern string_attr_type<std::string> VC_HYPERLINK;
namespace lnav {
@@ -233,6 +252,14 @@ preformatted(S str)
return std::make_pair(std::move(str), SA_PREFORMATTED.template value());
}
+template<typename S>
+inline std::pair<S, string_attr_pair>
+href(S str, std::string href)
+{
+ return std::make_pair(std::move(str),
+ VC_HYPERLINK.template value(std::move(href)));
+}
+
} // namespace attrs
} // namespace string
@@ -288,6 +315,14 @@ ok(S str)
template<typename S>
inline std::pair<S, string_attr_pair>
+hidden(S str)
+{
+ return std::make_pair(std::move(str),
+ VC_ROLE.template value(role_t::VCR_HIDDEN));
+}
+
+template<typename S>
+inline std::pair<S, string_attr_pair>
file(S str)
{
return std::make_pair(std::move(str),
@@ -344,6 +379,14 @@ identifier(S str)
template<typename S>
inline std::pair<S, string_attr_pair>
+string(S str)
+{
+ return std::make_pair(std::move(str),
+ VC_ROLE.template value(role_t::VCR_STRING));
+}
+
+template<typename S>
+inline std::pair<S, string_attr_pair>
hr(S str)
{
return std::make_pair(std::move(str),
@@ -494,6 +537,14 @@ h6(S str)
VC_ROLE.template value(role_t::VCR_H6));
}
+template<typename S>
+inline std::pair<S, string_attr_pair>
+suggestion(S str)
+{
+ return std::make_pair(std::move(str),
+ VC_ROLE.template value(role_t::VCR_SUGGESTION));
+}
+
namespace literals {
inline std::pair<std::string, string_attr_pair> operator"" _ok(const char* str,
@@ -510,6 +561,13 @@ inline std::pair<std::string, string_attr_pair> operator"" _error(
VC_ROLE.template value(role_t::VCR_ERROR));
}
+inline std::pair<std::string, string_attr_pair> operator"" _warning(
+ const char* str, std::size_t len)
+{
+ return std::make_pair(std::string(str, len),
+ VC_ROLE.template value(role_t::VCR_WARNING));
+}
+
inline std::pair<std::string, string_attr_pair> operator"" _info(
const char* str, std::size_t len)
{
@@ -517,6 +575,13 @@ inline std::pair<std::string, string_attr_pair> operator"" _info(
VC_ROLE.template value(role_t::VCR_INFO));
}
+inline std::pair<std::string, string_attr_pair> operator"" _status_title(
+ const char* str, std::size_t len)
+{
+ return std::make_pair(std::string(str, len),
+ VC_ROLE.template value(role_t::VCR_STATUS_TITLE));
+}
+
inline std::pair<std::string, string_attr_pair> operator"" _symbol(
const char* str, std::size_t len)
{
@@ -629,6 +694,13 @@ inline std::pair<std::string, string_attr_pair> operator"" _code_border(
VC_ROLE.template value(role_t::VCR_CODE_BORDER));
}
+inline std::pair<std::string, string_attr_pair> operator"" _table_header(
+ const char* str, std::size_t len)
+{
+ return std::make_pair(std::string(str, len),
+ VC_ROLE.template value(role_t::VCR_TABLE_HEADER));
+}
+
inline std::pair<std::string, string_attr_pair> operator"" _table_border(
const char* str, std::size_t len)
{
@@ -671,6 +743,13 @@ inline std::pair<std::string, string_attr_pair> operator"" _snippet_border(
VC_ROLE.template value(role_t::VCR_SNIPPET_BORDER));
}
+inline std::pair<std::string, string_attr_pair> operator"" _link(
+ const char* str, std::size_t len)
+{
+ return std::make_pair(std::string(str, len),
+ VC_HYPERLINK.template value(std::string(str, len)));
+}
+
} // namespace literals
} // namespace roles
diff --git a/src/base/string_util.cc b/src/base/string_util.cc
index 8af686c..39e80f6 100644
--- a/src/base/string_util.cc
+++ b/src/base/string_util.cc
@@ -199,6 +199,34 @@ is_url(const std::string& fn)
}
size_t
+last_word_str(char* str, size_t len, size_t max_len)
+{
+ if (len < max_len) {
+ return len;
+ }
+
+ size_t last_start = 0;
+
+ for (size_t index = 0; index < len; index++) {
+ switch (str[index]) {
+ case '.':
+ case '-':
+ case '/':
+ case ':':
+ last_start = index + 1;
+ break;
+ }
+ }
+
+ if (last_start == 0) {
+ return len;
+ }
+
+ memmove(&str[0], &str[last_start], len - last_start);
+ return len - last_start;
+}
+
+size_t
abbreviate_str(char* str, size_t len, size_t max_len)
{
size_t last_start = 1;
@@ -273,7 +301,7 @@ is_blank(const std::string& str)
}
std::string
-scrub_ws(const char* in)
+scrub_ws(const char* in, ssize_t len)
{
static const std::string TAB_SYMBOL = "\u21e5";
static const std::string LF_SYMBOL = "\u240a";
@@ -281,7 +309,9 @@ scrub_ws(const char* in)
std::string retval;
- for (size_t lpc = 0; in[lpc]; lpc++) {
+ for (size_t lpc = 0; (len == -1 && in[lpc]) || (len >= 0 && lpc < len);
+ lpc++)
+ {
auto ch = in[lpc];
switch (ch) {
@@ -302,3 +332,93 @@ scrub_ws(const char* in)
return retval;
}
+
+namespace fmt {
+auto
+formatter<lnav::tainted_string>::format(const lnav::tainted_string& ts,
+ format_context& ctx)
+ -> decltype(ctx.out()) const
+{
+ auto esc_res = fmt::v10::detail::find_escape(&(*ts.ts_str.begin()),
+ &(*ts.ts_str.end()));
+ if (esc_res.end == nullptr) {
+ return formatter<string_view>::format(ts.ts_str, ctx);
+ }
+
+ return format_to(ctx.out(), FMT_STRING("{:?}"), ts.ts_str);
+}
+} // namespace fmt
+
+namespace lnav {
+namespace pcre2pp {
+
+static bool
+is_meta(char ch)
+{
+ switch (ch) {
+ case '\\':
+ case '^':
+ case '$':
+ case '.':
+ case '[':
+ case ']':
+ case '(':
+ case ')':
+ case '*':
+ case '+':
+ case '?':
+ case '{':
+ case '}':
+ return true;
+ default:
+ return false;
+ }
+}
+
+static nonstd::optional<const char*>
+char_escape_seq(char ch)
+{
+ switch (ch) {
+ case '\t':
+ return "\\t";
+ case '\n':
+ return "\\n";
+ }
+
+ return nonstd::nullopt;
+}
+
+std::string
+quote(string_fragment str)
+{
+ std::string retval;
+
+ while (true) {
+ auto cp_pair_opt = str.consume_codepoint();
+ if (!cp_pair_opt) {
+ break;
+ }
+
+ auto cp_pair = cp_pair_opt.value();
+ if ((cp_pair.first & ~0xff) == 0) {
+ if (is_meta(cp_pair.first)) {
+ retval.push_back('\\');
+ } else {
+ auto esc_seq = char_escape_seq(cp_pair.first);
+ if (esc_seq) {
+ retval.append(esc_seq.value());
+ str = cp_pair_opt->second;
+ continue;
+ }
+ }
+ }
+ ww898::utf::utf8::write(cp_pair.first,
+ [&retval](char ch) { retval.push_back(ch); });
+ str = cp_pair_opt->second;
+ }
+
+ return retval;
+}
+
+} // namespace pcre2pp
+} // namespace lnav
diff --git a/src/base/string_util.hh b/src/base/string_util.hh
index 73a8b87..886fb6c 100644
--- a/src/base/string_util.hh
+++ b/src/base/string_util.hh
@@ -100,7 +100,12 @@ endswith(const std::string& str, const char (&suffix)[N])
void truncate_to(std::string& str, size_t max_char_len);
-std::string scrub_ws(const char* in);
+std::string scrub_ws(const char* in, ssize_t len = -1);
+inline std::string
+scrub_ws(const string_fragment& sf)
+{
+ return scrub_ws(sf.data(), sf.length());
+}
inline std::string
trim(const std::string& str)
@@ -115,6 +120,16 @@ trim(const std::string& str)
return str.substr(start, end - start);
}
+inline const char*
+ltrim(const char* str)
+{
+ while (isspace(*str)) {
+ str += 1;
+ }
+
+ return str;
+}
+
inline std::string
rtrim(const std::string& str)
{
@@ -213,6 +228,8 @@ bool is_blank(const std::string& str);
size_t abbreviate_str(char* str, size_t len, size_t max_len);
+size_t last_word_str(char* str, size_t len, size_t max_len);
+
void split_ws(const std::string& str, std::vector<std::string>& toks_out);
std::string repeat(const std::string& input, size_t num);
@@ -229,4 +246,53 @@ on_blank(const std::string& str, const std::string& def)
return str;
}
+namespace lnav {
+class tainted_string {
+public:
+ explicit tainted_string(std::string s) : ts_str(std::move(s)) {}
+
+ bool operator==(const tainted_string& other) const
+ {
+ return this->ts_str == other.ts_str;
+ }
+
+ bool operator!=(const tainted_string& other) const
+ {
+ return this->ts_str != other.ts_str;
+ }
+
+ bool operator<(const tainted_string& other) const
+ {
+ return this->ts_str < other.ts_str;
+ }
+
+ bool empty() const { return this->ts_str.empty(); }
+
+ size_t length() const { return this->ts_str.length(); }
+
+ size_t size() const { return this->ts_str.size(); }
+
+ friend fmt::formatter<lnav::tainted_string>;
+
+private:
+ const std::string ts_str;
+};
+} // namespace lnav
+
+namespace fmt {
+template<>
+struct formatter<lnav::tainted_string> : formatter<string_view> {
+ auto format(const lnav::tainted_string& ts,
+ format_context& ctx) -> decltype(ctx.out()) const;
+};
+} // namespace fmt
+
+namespace lnav {
+namespace pcre2pp {
+
+std::string quote(string_fragment sf);
+
+}
+} // namespace lnav
+
#endif
diff --git a/src/base/string_util.tests.cc b/src/base/string_util.tests.cc
index 98cf5c8..c27f2a2 100644
--- a/src/base/string_util.tests.cc
+++ b/src/base/string_util.tests.cc
@@ -87,4 +87,47 @@ TEST_CASE("strnatcmp")
CHECK(strnatcmp(strlen(n1), n1, strlen(n2), n2) < 0);
}
+ {
+ constexpr const char* n1 = "servers";
+ constexpr const char* n2 = "servers.alpha";
+
+ CHECK(strnatcasecmp(strlen(n1), n1, strlen(n2), n2) < 0);
+ }
+ {
+ static constexpr const char* TOKENS = "[](){}";
+ const std::string n1 = "[servers]";
+ const std::string n2 = "[servers.alpha]";
+
+ auto lhs = string_fragment::from_str(n1).trim(TOKENS);
+ auto rhs = string_fragment::from_str(n2).trim(TOKENS);
+ CHECK(strnatcasecmp(lhs.length(), lhs.data(), rhs.length(), rhs.data())
+ < 0);
+ }
+
+ {
+ const std::string a = "10.112.81.15";
+ const std::string b = "192.168.202.254";
+
+ int ipcmp = 0;
+ auto rc = ipv4cmp(a.length(), a.c_str(), b.length(), b.c_str(), &ipcmp);
+ CHECK(rc == 1);
+ CHECK(ipcmp == -1);
+ }
+}
+
+TEST_CASE("last_word_str")
+{
+ {
+ std::string s = "foobar baz";
+
+ auto rc = last_word_str(&s[0], s.length(), 6);
+ CHECK(s.length() == rc);
+ }
+ {
+ std::string s = "com.example.foo";
+
+ auto rc = last_word_str(&s[0], s.length(), 6);
+ s.resize(rc);
+ CHECK(s == "foo");
+ }
}
diff --git a/src/base/strnatcmp.c b/src/base/strnatcmp.c
index 6773164..0ea332d 100644
--- a/src/base/strnatcmp.c
+++ b/src/base/strnatcmp.c
@@ -275,13 +275,13 @@ int ipv4cmp(int a_len, nat_char const *a,
}
for (; ai < a_len; ai++) {
- if (!isdigit((unsigned char)a[ai]) || a[ai] != '.') {
+ if (!isdigit((unsigned char)a[ai]) && a[ai] != '.') {
return 0;
}
}
for (; bi < b_len; bi++) {
- if (!isdigit((unsigned char)b[bi]) || b[bi] != '.') {
+ if (!isdigit((unsigned char)b[bi]) && b[bi] != '.') {
return 0;
}
}
diff --git a/src/base/time_util.cc b/src/base/time_util.cc
index 0d46107..5aee017 100644
--- a/src/base/time_util.cc
+++ b/src/base/time_util.cc
@@ -30,10 +30,15 @@
*/
#include <chrono>
+#include <map>
#include "time_util.hh"
+#include <date/ptz.h>
+
#include "config.h"
+#include "lnav_log.hh"
+#include "optional.hpp"
namespace lnav {
@@ -75,8 +80,66 @@ strftime_rfc3339(
return index;
}
+static nonstd::optional<Posix::time_zone>
+get_posix_zone(const char* name)
+{
+ if (name == nullptr) {
+ return nonstd::nullopt;
+ }
+
+ try {
+ return date::zoned_traits<Posix::time_zone>::locate_zone(name);
+ } catch (const std::runtime_error& e) {
+ log_error("invalid TZ value: %s -- %s", name, e.what());
+ return nonstd::nullopt;
+ }
+}
+
+static const date::time_zone*
+get_date_zone(const char* name)
+{
+ if (name == nullptr) {
+ return date::current_zone();
+ }
+
+ try {
+ return date::locate_zone(name);
+ } catch (const std::runtime_error& e) {
+ log_error("invalid TZ value: %s -- %s", name, e.what());
+ return date::current_zone();
+ }
+}
+
+date::sys_seconds
+to_sys_time(date::local_seconds secs)
+{
+ static const auto* TZ = getenv("TZ");
+ static const auto TZ_POSIX_ZONE = get_posix_zone(TZ);
+ static const auto* TZ_DATE_ZONE = get_date_zone(TZ);
+
+ if (TZ_POSIX_ZONE) {
+ return TZ_POSIX_ZONE.value().to_sys(secs);
+ }
+
+ return TZ_DATE_ZONE->to_sys(secs);
+}
+
+date::local_seconds
+to_local_time(date::sys_seconds secs)
+{
+ static const auto* TZ = getenv("TZ");
+ static const auto TZ_POSIX_ZONE = get_posix_zone(TZ);
+ static const auto* TZ_DATE_ZONE = get_date_zone(TZ);
+
+ if (TZ_POSIX_ZONE) {
+ return TZ_POSIX_ZONE.value().to_local(secs);
+ }
+
+ return TZ_DATE_ZONE->to_local(secs);
}
+} // namespace lnav
+
static time_t BAD_DATE = -1;
time_t
@@ -225,15 +288,68 @@ secs2tm(lnav::time64_t tim, struct tm* res)
return (res);
}
+exttm
+exttm::from_tv(const timeval& tv)
+{
+ auto retval = exttm{};
+
+ retval.et_tm = *gmtime(&tv.tv_sec);
+ retval.et_nsec = tv.tv_usec * 1000;
+
+ return retval;
+}
+
struct timeval
exttm::to_timeval() const
{
struct timeval retval;
retval.tv_sec = tm2sec(&this->et_tm);
+ retval.tv_sec -= this->et_gmtoff;
retval.tv_usec = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::nanoseconds(this->et_nsec))
.count();
return retval;
}
+
+time_range&
+time_range::operator|=(const time_range& rhs)
+{
+ if (rhs.tr_begin < this->tr_begin) {
+ this->tr_begin = rhs.tr_begin;
+ }
+ if (this->tr_end < rhs.tr_end) {
+ this->tr_end = rhs.tr_end;
+ }
+
+ return *this;
+}
+
+void
+time_range::extend_to(const timeval& tv)
+{
+ if (tv < this->tr_begin) {
+ // logs aren't always in time-order
+ this->tr_begin = tv;
+ } else if (this->tr_end < tv) {
+ this->tr_end = tv;
+ }
+}
+
+std::chrono::milliseconds
+time_range::duration() const
+{
+ auto diff = this->tr_end - this->tr_begin;
+
+ return std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::seconds(diff.tv_sec))
+ + std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::microseconds(diff.tv_usec));
+}
+
+bool
+time_range::contains_inclusive(const timeval& tv) const
+{
+ return (this->tr_begin <= tv) && (tv <= this->tr_end);
+}
diff --git a/src/base/time_util.hh b/src/base/time_util.hh
index ef9687f..5640463 100644
--- a/src/base/time_util.hh
+++ b/src/base/time_util.hh
@@ -30,6 +30,8 @@
#ifndef lnav_time_util_hh
#define lnav_time_util_hh
+#include <chrono>
+
#include <inttypes.h>
#include <string.h>
#include <sys/time.h>
@@ -37,6 +39,7 @@
#include <time.h>
#include "config.h"
+#include "date/date.h"
namespace lnav {
@@ -48,6 +51,10 @@ ssize_t strftime_rfc3339(char* buffer,
int millis,
char sep = ' ');
+date::sys_seconds to_sys_time(date::local_seconds secs);
+
+date::local_seconds to_local_time(date::sys_seconds secs);
+
} // namespace lnav
struct tm* secs2tm(lnav::time64_t tim, struct tm* res);
@@ -84,9 +91,15 @@ enum exttm_bits_t {
ETB_SECOND_SET,
ETB_MACHINE_ORIENTED,
ETB_EPOCH_TIME,
+ ETB_SUB_NOT_IN_FORMAT,
ETB_MILLIS_SET,
ETB_MICROS_SET,
ETB_NANOS_SET,
+ ETB_ZONE_SET,
+ ETB_Z_FOR_UTC,
+ ETB_Z_COLON,
+ ETB_Z_IS_UTC,
+ ETB_Z_IS_GMT,
};
enum exttm_flags_t {
@@ -98,12 +111,20 @@ enum exttm_flags_t {
ETF_SECOND_SET = (1UL << ETB_SECOND_SET),
ETF_MACHINE_ORIENTED = (1UL << ETB_MACHINE_ORIENTED),
ETF_EPOCH_TIME = (1UL << ETB_EPOCH_TIME),
+ ETF_SUB_NOT_IN_FORMAT = (1UL << ETB_SUB_NOT_IN_FORMAT),
ETF_MILLIS_SET = (1UL << ETB_MILLIS_SET),
ETF_MICROS_SET = (1UL << ETB_MICROS_SET),
ETF_NANOS_SET = (1UL << ETB_NANOS_SET),
+ ETF_ZONE_SET = (1UL << ETB_ZONE_SET),
+ ETF_Z_FOR_UTC = (1UL << ETB_Z_FOR_UTC),
+ ETF_Z_COLON = (1UL << ETB_Z_COLON),
+ ETF_Z_IS_UTC = (1UL << ETB_Z_IS_UTC),
+ ETF_Z_IS_GMT = (1UL << ETB_Z_IS_GMT),
};
struct exttm {
+ static exttm from_tv(const timeval& tv);
+
struct tm et_tm {};
int32_t et_nsec{0};
unsigned int et_flags{0};
@@ -139,6 +160,13 @@ operator<(const struct timeval& left, const struct timeval& right)
}
inline bool
+operator<=(const struct timeval& left, const struct timeval& right)
+{
+ return left.tv_sec < right.tv_sec
+ || ((left.tv_sec == right.tv_sec) && (left.tv_usec <= right.tv_usec));
+}
+
+inline bool
operator!=(const struct timeval& left, const struct timeval& right)
{
return left.tv_sec != right.tv_sec || left.tv_usec != right.tv_usec;
@@ -147,7 +175,7 @@ operator!=(const struct timeval& left, const struct timeval& right)
inline bool
operator==(const struct timeval& left, const struct timeval& right)
{
- return left.tv_sec == right.tv_sec || left.tv_usec == right.tv_usec;
+ return left.tv_sec == right.tv_sec && left.tv_usec == right.tv_usec;
}
inline struct timeval
@@ -159,16 +187,31 @@ operator-(const struct timeval& lhs, const struct timeval& rhs)
return diff;
}
+inline struct timeval
+operator+(const struct timeval& lhs, const struct timeval& rhs)
+{
+ struct timeval retval;
+
+ timeradd(&lhs, &rhs, &retval);
+ return retval;
+}
+
typedef int64_t mstime_t;
inline mstime_t
+to_mstime(const timeval& tv)
+{
+ return tv.tv_sec * 1000ULL + tv.tv_usec / 1000ULL;
+}
+
+inline mstime_t
getmstime()
{
struct timeval tv;
gettimeofday(&tv, nullptr);
- return tv.tv_sec * 1000ULL + tv.tv_usec / 1000ULL;
+ return to_mstime(tv);
}
inline struct timeval
@@ -203,4 +246,21 @@ hour_num(time_t ti)
return ti / (60 * 60);
}
+struct time_range {
+ struct timeval tr_begin;
+ struct timeval tr_end;
+
+ bool operator<(const time_range& rhs) const
+ {
+ return this->tr_begin < rhs.tr_begin;
+ }
+
+ time_range& operator|=(const time_range& rhs);
+
+ bool contains_inclusive(const timeval& tv) const;
+
+ void extend_to(const timeval& tv);
+ std::chrono::milliseconds duration() const;
+};
+
#endif
diff --git a/src/base/types.hh b/src/base/types.hh
new file mode 100644
index 0000000..ba6f914
--- /dev/null
+++ b/src/base/types.hh
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2023, 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.
+ */
+
+#ifndef lnav_base_types_hh
+#define lnav_base_types_hh
+
+struct null_value_t {};
+
+#endif