mirror of
https://github.com/tstack/lnav
synced 2024-11-08 01:10:29 +00:00
[hotkeys] change curly braces to move to the next/prev section
and... a bunch of other stuff
This commit is contained in:
parent
0eb394f4b8
commit
ee345321f7
8
NEWS.md
8
NEWS.md
@ -122,6 +122,11 @@ Features:
|
||||
* Added a `log_msg_values` column to the `all_logs` SQL
|
||||
table that contains a JSON object with the top 5 values
|
||||
for the fields extracted from the log message.
|
||||
* Added `:next-section` and `:prev-section` commands for
|
||||
moving to the next and previous section of a document.
|
||||
For example, the next section in a man page or JSON
|
||||
array. The default keymap has been changed to bind
|
||||
the curly brace keys to these commands.
|
||||
* Added Nextcloud log format from Adam Monsen.
|
||||
* Added GitHub Event Log format for files from gharchive.org.
|
||||
It makes a good example of a JSON-Lines format.
|
||||
@ -158,6 +163,9 @@ Interface changes:
|
||||
used to draw the overlay contents now as well. (The
|
||||
overlay is used to display the parser details, comments,
|
||||
and annotations.)
|
||||
* The `{` and `}` keys have been changed from moving
|
||||
through the "location history" to moving to the previous
|
||||
and next section in a document.
|
||||
* Added indent guidelines when structured data is detected.
|
||||
|
||||
Breaking changes:
|
||||
|
@ -174,6 +174,8 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa)
|
||||
|
||||
if (lhs_pair.first == '_' || rhs_pair.first == '_') {
|
||||
if (sa != nullptr && bold_range.is_valid()) {
|
||||
shift_string_attrs(
|
||||
*sa, bold_range.lr_start, -bold_range.length() * 2);
|
||||
sa->emplace_back(bold_range,
|
||||
VC_STYLE.value(text_attrs{A_BOLD}));
|
||||
bold_range.clear();
|
||||
@ -191,6 +193,8 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa)
|
||||
});
|
||||
} else {
|
||||
if (sa != nullptr && ul_range.is_valid()) {
|
||||
shift_string_attrs(
|
||||
*sa, ul_range.lr_start, -ul_range.length() * 2);
|
||||
sa->emplace_back(
|
||||
ul_range, VC_STYLE.value(text_attrs{A_UNDERLINE}));
|
||||
ul_range.clear();
|
||||
@ -216,26 +220,25 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa)
|
||||
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));
|
||||
}
|
||||
|
||||
if (sa != nullptr && ul_range.is_valid()) {
|
||||
shift_string_attrs(
|
||||
*sa, ul_range.lr_start, -ul_range.length() * 2);
|
||||
sa->emplace_back(ul_range,
|
||||
VC_STYLE.value(text_attrs{A_UNDERLINE}));
|
||||
ul_range.clear();
|
||||
}
|
||||
if (sa != nullptr && bold_range.is_valid()) {
|
||||
shift_string_attrs(
|
||||
*sa, bold_range.lr_start, -bold_range.length() * 2);
|
||||
sa->emplace_back(bold_range,
|
||||
VC_STYLE.value(text_attrs{A_BOLD}));
|
||||
bold_range.clear();
|
||||
}
|
||||
if (sa != nullptr) {
|
||||
sa->emplace_back(line_range{last_origin_offset_end,
|
||||
sf.sf_begin + (int) output_size},
|
||||
SA_ORIGIN_OFFSET.value(origin_offset));
|
||||
}
|
||||
|
||||
str.erase(str.begin() + fill_index, str.begin() + sf.sf_end);
|
||||
last_origin_offset_end = sf.sf_begin + output_size;
|
||||
|
@ -277,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;
|
||||
|
@ -103,4 +103,14 @@ TEST_CASE("strnatcmp")
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -224,4 +224,21 @@ private:
|
||||
bool ds_units{false};
|
||||
};
|
||||
|
||||
inline data_token_t
|
||||
to_closer(data_token_t dt)
|
||||
{
|
||||
switch (dt) {
|
||||
case DT_XML_OPEN_TAG:
|
||||
return DT_XML_CLOSE_TAG;
|
||||
case DT_LCURLY:
|
||||
return DT_RCURLY;
|
||||
case DT_LSQUARE:
|
||||
return DT_RSQUARE;
|
||||
case DT_LPAREN:
|
||||
return DT_RPAREN;
|
||||
default:
|
||||
ensure(0);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -27,6 +27,7 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@ -60,6 +61,117 @@ hier_node::lookup_child(section_key_t key) const
|
||||
}));
|
||||
}
|
||||
|
||||
nonstd::optional<size_t>
|
||||
hier_node::child_index(const hier_node* hn) const
|
||||
{
|
||||
size_t retval = 0;
|
||||
|
||||
for (const auto& child : this->hn_children) {
|
||||
if (child.get() == hn) {
|
||||
return retval;
|
||||
}
|
||||
retval += 1;
|
||||
}
|
||||
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
nonstd::optional<hier_node::child_neighbors_result>
|
||||
hier_node::child_neighbors(const lnav::document::hier_node* hn,
|
||||
file_off_t offset) const
|
||||
{
|
||||
auto index_opt = this->child_index(hn);
|
||||
if (!index_opt) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
hier_node::child_neighbors_result retval;
|
||||
|
||||
if (index_opt.value() == 0) {
|
||||
if (this->hn_parent != nullptr) {
|
||||
auto parent_neighbors_opt
|
||||
= this->hn_parent->child_neighbors(this, offset);
|
||||
|
||||
if (parent_neighbors_opt) {
|
||||
retval.cnr_previous = parent_neighbors_opt->cnr_previous;
|
||||
}
|
||||
} else {
|
||||
retval.cnr_previous = hn;
|
||||
}
|
||||
} else {
|
||||
const auto* prev_hn = this->hn_children[index_opt.value() - 1].get();
|
||||
|
||||
if (hn->hn_line_number == 0
|
||||
|| (hn->hn_line_number - prev_hn->hn_line_number) > 1)
|
||||
{
|
||||
retval.cnr_previous = prev_hn;
|
||||
} else if (this->hn_parent != nullptr) {
|
||||
auto parent_neighbors_opt
|
||||
= this->hn_parent->child_neighbors(this, offset);
|
||||
|
||||
if (parent_neighbors_opt) {
|
||||
retval.cnr_previous = parent_neighbors_opt->cnr_previous;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (index_opt.value() == this->hn_children.size() - 1) {
|
||||
if (this->hn_parent != nullptr) {
|
||||
auto parent_neighbors_opt
|
||||
= this->hn_parent->child_neighbors(this, offset);
|
||||
|
||||
if (parent_neighbors_opt) {
|
||||
retval.cnr_next = parent_neighbors_opt->cnr_next;
|
||||
}
|
||||
} else if (!hn->hn_children.empty()) {
|
||||
for (const auto& child : hn->hn_children) {
|
||||
if (child->hn_start > offset) {
|
||||
retval.cnr_next = child.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const auto* next_hn = this->hn_children[index_opt.value() + 1].get();
|
||||
|
||||
if (next_hn->hn_start > offset
|
||||
&& (hn->hn_line_number == 0
|
||||
|| (next_hn->hn_line_number - hn->hn_line_number) > 1))
|
||||
{
|
||||
retval.cnr_next = next_hn;
|
||||
} else if (this->hn_parent != nullptr) {
|
||||
auto parent_neighbors_opt
|
||||
= this->hn_parent->child_neighbors(this, offset);
|
||||
|
||||
if (parent_neighbors_opt) {
|
||||
retval.cnr_next = parent_neighbors_opt->cnr_next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
nonstd::optional<hier_node::child_neighbors_result>
|
||||
hier_node::line_neighbors(size_t ln) const
|
||||
{
|
||||
if (this->hn_children.empty()) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
hier_node::child_neighbors_result retval;
|
||||
|
||||
for (const auto& child : this->hn_children) {
|
||||
if (child->hn_line_number > ln) {
|
||||
retval.cnr_next = child.get();
|
||||
break;
|
||||
}
|
||||
retval.cnr_previous = child.get();
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
nonstd::optional<const hier_node*>
|
||||
hier_node::lookup_path(const hier_node* root,
|
||||
const std::vector<section_key_t>& path)
|
||||
@ -81,11 +193,24 @@ hier_node::lookup_path(const hier_node* root,
|
||||
return retval;
|
||||
}
|
||||
|
||||
std::vector<section_key_t>
|
||||
metadata::path_for_range(size_t start, size_t stop)
|
||||
{
|
||||
std::vector<section_key_t> retval;
|
||||
|
||||
this->m_sections_tree.visit_overlapping(
|
||||
start, stop, [&retval](const lnav::document::section_interval_t& iv) {
|
||||
retval.emplace_back(iv.value);
|
||||
});
|
||||
return retval;
|
||||
}
|
||||
|
||||
struct metadata_builder {
|
||||
std::vector<section_interval_t> mb_intervals;
|
||||
std::vector<section_type_interval_t> mb_type_intervals;
|
||||
std::unique_ptr<hier_node> mb_root_node;
|
||||
std::set<size_t> mb_indents;
|
||||
text_format_t mb_text_format{text_format_t::TF_UNKNOWN};
|
||||
|
||||
metadata to_metadata() &&
|
||||
{
|
||||
@ -94,6 +219,7 @@ struct metadata_builder {
|
||||
std::move(this->mb_root_node),
|
||||
std::move(this->mb_type_intervals),
|
||||
std::move(this->mb_indents),
|
||||
this->mb_text_format,
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -238,6 +364,16 @@ discover_metadata_int(const attr_line_t& al, metadata_builder& mb)
|
||||
}
|
||||
});
|
||||
|
||||
hier_node::depth_first(
|
||||
mb.mb_root_node.get(), [&orig_attrs](hier_node* node) {
|
||||
auto off_opt = get_string_attr(
|
||||
orig_attrs, &SA_ORIGIN_OFFSET, node->hn_start);
|
||||
|
||||
if (off_opt) {
|
||||
node->hn_start += off_opt.value()->sa_value.get<int64_t>();
|
||||
}
|
||||
});
|
||||
|
||||
if (!root_node->hn_children.empty()
|
||||
|| !root_node->hn_named_children.empty())
|
||||
{
|
||||
@ -284,6 +420,7 @@ public:
|
||||
{
|
||||
metadata_builder mb;
|
||||
|
||||
mb.mb_text_format = this->sw_text_format;
|
||||
while (true) {
|
||||
auto tokenize_res
|
||||
= this->sw_scanner.tokenize2(this->sw_text_format);
|
||||
@ -334,17 +471,26 @@ public:
|
||||
this->sw_interval_state.resize(this->sw_depth + 1);
|
||||
this->sw_hier_nodes.push_back(
|
||||
std::make_unique<hier_node>());
|
||||
this->sw_container_tokens.push_back(to_closer(dt));
|
||||
break;
|
||||
case DT_XML_CLOSE_TAG: {
|
||||
auto term = this->flush_values();
|
||||
if (this->sw_depth > 0) {
|
||||
if (term) {
|
||||
this->append_child_node(term);
|
||||
}
|
||||
this->sw_interval_state.pop_back();
|
||||
this->sw_hier_stage
|
||||
= std::move(this->sw_hier_nodes.back());
|
||||
this->sw_hier_nodes.pop_back();
|
||||
auto found = false;
|
||||
do {
|
||||
if (this->sw_container_tokens.back() == dt) {
|
||||
found = true;
|
||||
}
|
||||
if (term) {
|
||||
this->append_child_node(term);
|
||||
term = nonstd::nullopt;
|
||||
}
|
||||
this->sw_interval_state.pop_back();
|
||||
this->sw_hier_stage
|
||||
= std::move(this->sw_hier_nodes.back());
|
||||
this->sw_hier_nodes.pop_back();
|
||||
this->sw_container_tokens.pop_back();
|
||||
} while (!found);
|
||||
}
|
||||
this->append_child_node(el.e_capture);
|
||||
if (this->sw_depth > 0) {
|
||||
@ -418,6 +564,7 @@ public:
|
||||
this->sw_interval_state.resize(this->sw_depth + 1);
|
||||
this->sw_hier_nodes.push_back(
|
||||
std::make_unique<hier_node>());
|
||||
this->sw_container_tokens.push_back(to_closer(dt));
|
||||
} else {
|
||||
this->sw_values.emplace_back(el);
|
||||
}
|
||||
@ -426,37 +573,54 @@ public:
|
||||
case DT_RCURLY:
|
||||
case DT_RSQUARE:
|
||||
case DT_RPAREN:
|
||||
if (this->is_structured_text()) {
|
||||
if (this->is_structured_text()
|
||||
&& !this->sw_container_tokens.empty()
|
||||
&& std::find(this->sw_container_tokens.begin(),
|
||||
this->sw_container_tokens.end(),
|
||||
dt)
|
||||
!= this->sw_container_tokens.end())
|
||||
{
|
||||
auto term = this->flush_values();
|
||||
if (this->sw_depth > 0) {
|
||||
this->append_child_node(term);
|
||||
this->sw_depth -= 1;
|
||||
this->sw_interval_state.pop_back();
|
||||
this->sw_hier_stage
|
||||
= std::move(this->sw_hier_nodes.back());
|
||||
this->sw_hier_nodes.pop_back();
|
||||
if (this->sw_interval_state.back().is_start) {
|
||||
data_scanner::capture_t obj_cap = {
|
||||
static_cast<int>(
|
||||
this->sw_interval_state.back()
|
||||
.is_start.value()),
|
||||
el.e_capture.c_end,
|
||||
};
|
||||
auto found = false;
|
||||
do {
|
||||
if (this->sw_container_tokens.back() == dt) {
|
||||
found = true;
|
||||
}
|
||||
this->append_child_node(term);
|
||||
term = nonstd::nullopt;
|
||||
this->sw_depth -= 1;
|
||||
this->sw_interval_state.pop_back();
|
||||
this->sw_hier_stage
|
||||
= std::move(this->sw_hier_nodes.back());
|
||||
this->sw_hier_nodes.pop_back();
|
||||
if (this->sw_interval_state.back().is_start) {
|
||||
data_scanner::capture_t obj_cap = {
|
||||
static_cast<int>(
|
||||
this->sw_interval_state.back()
|
||||
.is_start.value()),
|
||||
el.e_capture.c_end,
|
||||
};
|
||||
|
||||
auto sf = this->sw_scanner.to_string_fragment(
|
||||
obj_cap);
|
||||
if (!sf.find('\n')) {
|
||||
this->sw_hier_stage->hn_named_children
|
||||
.clear();
|
||||
this->sw_hier_stage->hn_children.clear();
|
||||
while (!this->sw_intervals.empty()
|
||||
&& this->sw_intervals.back().start
|
||||
> obj_cap.c_begin)
|
||||
{
|
||||
this->sw_intervals.pop_back();
|
||||
auto sf
|
||||
= this->sw_scanner.to_string_fragment(
|
||||
obj_cap);
|
||||
if (!sf.find('\n')) {
|
||||
this->sw_hier_stage->hn_named_children
|
||||
.clear();
|
||||
this->sw_hier_stage->hn_children
|
||||
.clear();
|
||||
while (
|
||||
!this->sw_intervals.empty()
|
||||
&& this->sw_intervals.back().start
|
||||
> obj_cap.c_begin)
|
||||
{
|
||||
this->sw_intervals.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this->sw_container_tokens.pop_back();
|
||||
} while (!found);
|
||||
}
|
||||
}
|
||||
this->sw_values.emplace_back(el);
|
||||
@ -650,18 +814,22 @@ private:
|
||||
auto new_key = ivstate.is_name.empty()
|
||||
? lnav::document::section_key_t{top_node->hn_children.size()}
|
||||
: lnav::document::section_key_t{ivstate.is_name};
|
||||
this->sw_intervals.emplace_back(iv_start, iv_stop, new_key);
|
||||
auto* retval = new_node.get();
|
||||
new_node->hn_parent = top_node;
|
||||
new_node->hn_start = this->sw_intervals.back().start;
|
||||
new_node->hn_start = iv_start;
|
||||
new_node->hn_line_number = ivstate.is_line_number;
|
||||
if (!ivstate.is_name.empty()) {
|
||||
top_node->hn_named_children.insert({
|
||||
ivstate.is_name,
|
||||
retval,
|
||||
});
|
||||
if (this->sw_depth == 1
|
||||
|| new_node->hn_line_number != top_node->hn_line_number)
|
||||
{
|
||||
this->sw_intervals.emplace_back(iv_start, iv_stop, new_key);
|
||||
if (!ivstate.is_name.empty()) {
|
||||
top_node->hn_named_children.insert({
|
||||
ivstate.is_name,
|
||||
retval,
|
||||
});
|
||||
}
|
||||
top_node->hn_children.emplace_back(std::move(new_node));
|
||||
}
|
||||
top_node->hn_children.emplace_back(std::move(new_node));
|
||||
ivstate.is_start = nonstd::nullopt;
|
||||
ivstate.is_line_number = 0;
|
||||
ivstate.is_name.clear();
|
||||
@ -676,6 +844,7 @@ private:
|
||||
bool sw_at_start{true};
|
||||
std::set<size_t> sw_indents;
|
||||
std::vector<element> sw_values{};
|
||||
std::vector<data_token_t> sw_container_tokens;
|
||||
std::vector<interval_state> sw_interval_state;
|
||||
std::vector<lnav::document::section_interval_t> sw_intervals;
|
||||
std::vector<lnav::document::section_type_interval_t> sw_type_intervals;
|
||||
|
@ -69,6 +69,18 @@ struct hier_node {
|
||||
|
||||
nonstd::optional<hier_node*> lookup_child(section_key_t key) const;
|
||||
|
||||
nonstd::optional<size_t> child_index(const hier_node* hn) const;
|
||||
|
||||
struct child_neighbors_result {
|
||||
nonstd::optional<const hier_node*> cnr_previous;
|
||||
nonstd::optional<const hier_node*> cnr_next;
|
||||
};
|
||||
|
||||
nonstd::optional<child_neighbors_result> child_neighbors(
|
||||
const hier_node* hn, file_off_t offset) const;
|
||||
|
||||
nonstd::optional<child_neighbors_result> line_neighbors(size_t ln) const;
|
||||
|
||||
nonstd::optional<size_t> find_line_number(const std::string& str) const
|
||||
{
|
||||
auto iter = this->hn_named_children.find(str);
|
||||
@ -115,6 +127,9 @@ struct metadata {
|
||||
std::unique_ptr<hier_node> m_sections_root;
|
||||
section_types_tree_t m_section_types_tree;
|
||||
std::set<size_t> m_indents;
|
||||
text_format_t m_text_format{text_format_t::TF_UNKNOWN};
|
||||
|
||||
std::vector<section_key_t> path_for_range(size_t start, size_t stop);
|
||||
|
||||
std::vector<breadcrumb::possibility> possibility_provider(
|
||||
const std::vector<section_key_t>& path);
|
||||
|
@ -476,26 +476,33 @@ field_overlay_source::build_meta_line(const listview_curses& lv,
|
||||
{
|
||||
auto line_meta_opt = this->fos_lss.find_bookmark_metadata(row);
|
||||
|
||||
auto file_and_line = this->fos_lss.find_line_with_file(row);
|
||||
if (file_and_line && !file_and_line->second->is_continued()) {
|
||||
auto applicable_anno = lnav::log::annotate::applicable(row);
|
||||
if (!applicable_anno.empty()
|
||||
&& (!line_meta_opt
|
||||
|| line_meta_opt.value()->bm_annotations.la_pairs.empty()))
|
||||
{
|
||||
auto anno_msg
|
||||
= attr_line_t(" ")
|
||||
.append(":memo:"_emoji)
|
||||
.append(" Annotations available, ")
|
||||
.append(lv.get_selection() == row
|
||||
? "use "
|
||||
: "focus on this line and use ")
|
||||
.append(":annotate"_quoted_code)
|
||||
.append(" to apply them")
|
||||
.append(lv.get_selection() == row ? " to this line" : "")
|
||||
.with_attr_for_all(VC_ROLE.value(role_t::VCR_COMMENT));
|
||||
if (!this->fos_contexts.empty()
|
||||
&& this->fos_contexts.top().c_show_applicable_annotations)
|
||||
{
|
||||
auto file_and_line = this->fos_lss.find_line_with_file(row);
|
||||
|
||||
dst.emplace_back(anno_msg);
|
||||
if (file_and_line && !file_and_line->second->is_continued()) {
|
||||
auto applicable_anno = lnav::log::annotate::applicable(row);
|
||||
if (!applicable_anno.empty()
|
||||
&& (!line_meta_opt
|
||||
|| line_meta_opt.value()->bm_annotations.la_pairs.empty()))
|
||||
{
|
||||
auto anno_msg
|
||||
= attr_line_t(" ")
|
||||
.append(":memo:"_emoji)
|
||||
.append(" Annotations available, ")
|
||||
.append(lv.get_selection() == row
|
||||
? "use "
|
||||
: "focus on this line and use ")
|
||||
.append(":annotate"_quoted_code)
|
||||
.append(" to apply them")
|
||||
.append(lv.get_selection() == row ? " to this line"
|
||||
: "")
|
||||
.with_attr_for_all(
|
||||
VC_ROLE.value(role_t::VCR_COMMENT));
|
||||
|
||||
dst.emplace_back(anno_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,15 +78,20 @@ public:
|
||||
}
|
||||
|
||||
struct context {
|
||||
context(std::string prefix, bool show, bool show_discovered)
|
||||
context(std::string prefix,
|
||||
bool show,
|
||||
bool show_discovered,
|
||||
bool show_applicable_annotations)
|
||||
: c_prefix(std::move(prefix)), c_show(show),
|
||||
c_show_discovered(show_discovered)
|
||||
c_show_discovered(show_discovered),
|
||||
c_show_applicable_annotations(show_applicable_annotations)
|
||||
{
|
||||
}
|
||||
|
||||
std::string c_prefix;
|
||||
bool c_show{false};
|
||||
bool c_show_discovered{true};
|
||||
bool c_show_applicable_annotations{true};
|
||||
};
|
||||
|
||||
std::stack<context> fos_contexts;
|
||||
|
@ -39,6 +39,9 @@
|
||||
},
|
||||
{
|
||||
"line": "2022-06-01T13:23:25.310 [2376]DEBUG:com.vmware.vherd.base.detwist:method = com.vmware.appliance.version1.system.version.get, args = ()"
|
||||
},
|
||||
{
|
||||
"line": "2023-07-19T02:47:11 AM UTC [1670]DEBUG:firewall-reload:Processing system service 'sshd' firewall rules."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -665,7 +665,7 @@
|
||||
:goto #screenshots
|
||||
|
||||
**See Also**
|
||||
:ref:`next_location`, :ref:`next_mark`, :ref:`prev_location`, :ref:`prev_mark`, :ref:`relative_goto`
|
||||
:ref:`next_location`, :ref:`next_mark`, :ref:`next_section`, :ref:`prev_location`, :ref:`prev_mark`, :ref:`prev_section`, :ref:`relative_goto`
|
||||
|
||||
----
|
||||
|
||||
@ -873,7 +873,7 @@
|
||||
Move to the next position in the location history
|
||||
|
||||
**See Also**
|
||||
:ref:`goto`, :ref:`next_mark`, :ref:`prev_location`, :ref:`prev_mark`, :ref:`relative_goto`
|
||||
:ref:`goto`, :ref:`next_mark`, :ref:`next_section`, :ref:`prev_location`, :ref:`prev_mark`, :ref:`prev_section`, :ref:`relative_goto`
|
||||
|
||||
----
|
||||
|
||||
@ -896,7 +896,20 @@
|
||||
:next-mark error
|
||||
|
||||
**See Also**
|
||||
:ref:`goto`, :ref:`hide_unmarked_lines`, :ref:`mark`, :ref:`next_location`, :ref:`prev_location`, :ref:`prev_mark`, :ref:`prev_mark`, :ref:`relative_goto`
|
||||
:ref:`goto`, :ref:`hide_unmarked_lines`, :ref:`mark`, :ref:`next_location`, :ref:`next_section`, :ref:`prev_location`, :ref:`prev_mark`, :ref:`prev_mark`, :ref:`prev_section`, :ref:`relative_goto`
|
||||
|
||||
----
|
||||
|
||||
|
||||
.. _next_section:
|
||||
|
||||
:next-section
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Move to the next section in the document
|
||||
|
||||
**See Also**
|
||||
:ref:`goto`, :ref:`next_location`, :ref:`next_mark`, :ref:`prev_location`, :ref:`prev_mark`, :ref:`prev_section`, :ref:`relative_goto`
|
||||
|
||||
----
|
||||
|
||||
@ -1003,7 +1016,7 @@
|
||||
Move to the previous position in the location history
|
||||
|
||||
**See Also**
|
||||
:ref:`goto`, :ref:`next_location`, :ref:`next_mark`, :ref:`prev_mark`, :ref:`relative_goto`
|
||||
:ref:`goto`, :ref:`next_location`, :ref:`next_mark`, :ref:`next_section`, :ref:`prev_mark`, :ref:`prev_section`, :ref:`relative_goto`
|
||||
|
||||
----
|
||||
|
||||
@ -1026,7 +1039,20 @@
|
||||
:prev-mark error
|
||||
|
||||
**See Also**
|
||||
:ref:`goto`, :ref:`hide_unmarked_lines`, :ref:`mark`, :ref:`next_location`, :ref:`next_mark`, :ref:`next_mark`, :ref:`prev_location`, :ref:`relative_goto`
|
||||
:ref:`goto`, :ref:`hide_unmarked_lines`, :ref:`mark`, :ref:`next_location`, :ref:`next_mark`, :ref:`next_mark`, :ref:`next_section`, :ref:`prev_location`, :ref:`prev_section`, :ref:`relative_goto`
|
||||
|
||||
----
|
||||
|
||||
|
||||
.. _prev_section:
|
||||
|
||||
:prev-section
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Move to the previous section in the document
|
||||
|
||||
**See Also**
|
||||
:ref:`goto`, :ref:`next_location`, :ref:`next_mark`, :ref:`next_section`, :ref:`prev_location`, :ref:`prev_mark`, :ref:`relative_goto`
|
||||
|
||||
----
|
||||
|
||||
@ -1143,7 +1169,7 @@
|
||||
:relative-goto -10%
|
||||
|
||||
**See Also**
|
||||
:ref:`goto`, :ref:`next_location`, :ref:`next_mark`, :ref:`prev_location`, :ref:`prev_mark`
|
||||
:ref:`goto`, :ref:`next_location`, :ref:`next_mark`, :ref:`next_section`, :ref:`prev_location`, :ref:`prev_mark`, :ref:`prev_section`
|
||||
|
||||
----
|
||||
|
||||
|
@ -11,8 +11,8 @@
|
||||
"keymap_def_pop_view": "Press ${ansi_bold}q${ansi_norm} to return to the previous view",
|
||||
"keymap_def_zoom": "Press ${ansi_bold}z${ansi_norm}/${ansi_bold}Z${ansi_norm} to zoom in/out",
|
||||
"keymap_def_clear": "Press ${ansi_bold}C${ansi_norm} to clear marked messages",
|
||||
"keymap_def_prev_location": "Press ${ansi_bold}{${ansi_norm} to move to the previous location in history",
|
||||
"keymap_def_next_location": "Press ${ansi_bold}}${ansi_norm} to move to the next location in history",
|
||||
"keymap_def_prev_section": "Press ${ansi_bold}{${ansi_norm} to move to the previous section in history",
|
||||
"keymap_def_next_section": "Press ${ansi_bold}}${ansi_norm} to move to the next section in history",
|
||||
"keymap_def_next_mark": "Press ${ansi_bold}c${ansi_norm} to copy marked lines to the clipboard; press ${ansi_bold}C${ansi_norm} to clear marked lines",
|
||||
"keymap_def_time_offset": "Press ${ansi_bold}s${ansi_norm}/${ansi_bold}S${ansi_norm} to move forward/backward through slow downs"
|
||||
},
|
||||
@ -153,11 +153,11 @@
|
||||
"command": ":prev-mark"
|
||||
},
|
||||
"x7d": {
|
||||
"command": ":next-location",
|
||||
"command": ":next-section",
|
||||
"alt-msg": "${keymap_def_prev_location}"
|
||||
},
|
||||
"x7b": {
|
||||
"command": ":prev-location",
|
||||
"command": ":prev-section",
|
||||
"alt-msg": "${keymap_def_next_location}"
|
||||
},
|
||||
"x3f": {
|
||||
|
@ -719,6 +719,9 @@ listview_curses::shift_selection(shift_amount_t sa)
|
||||
break;
|
||||
}
|
||||
if (this->is_selectable()) {
|
||||
if (this->lv_selection == -1_vl) {
|
||||
this->lv_selection = this->lv_top;
|
||||
}
|
||||
auto new_selection = this->lv_selection + vis_line_t(offset);
|
||||
|
||||
if (new_selection < 0_vl) {
|
||||
|
@ -2826,7 +2826,7 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
|
||||
.set_word_wrap(false);
|
||||
auto log_fos = new field_overlay_source(lnav_data.ld_log_source,
|
||||
lnav_data.ld_text_source);
|
||||
log_fos->fos_contexts.emplace("", false, true);
|
||||
log_fos->fos_contexts.emplace("", false, true, true);
|
||||
lnav_data.ld_views[LNV_LOG]
|
||||
.set_sub_source(&lnav_data.ld_log_source)
|
||||
#if 0
|
||||
@ -3357,6 +3357,9 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
|
||||
textview_curses *log_tc, *text_tc, *tc;
|
||||
bool output_view = true;
|
||||
|
||||
log_fos->fos_contexts.top().c_show_applicable_annotations
|
||||
= false;
|
||||
|
||||
view_colors::init(true);
|
||||
rescan_files(true);
|
||||
wait_for_pipers();
|
||||
|
@ -1144,6 +1144,62 @@ com_goto_location(exec_context& ec,
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
static Result<std::string, lnav::console::user_message>
|
||||
com_next_section(exec_context& ec,
|
||||
std::string cmdline,
|
||||
std::vector<std::string>& args)
|
||||
{
|
||||
std::string retval;
|
||||
|
||||
if (args.empty()) {
|
||||
} else if (!ec.ec_dry_run) {
|
||||
auto* tc = *lnav_data.ld_view_stack.top();
|
||||
auto* ta = dynamic_cast<text_anchors*>(tc->get_sub_source());
|
||||
|
||||
if (ta == nullptr) {
|
||||
return ec.make_error("view does not support sections");
|
||||
}
|
||||
|
||||
auto adj_opt = ta->adjacent_anchor(tc->get_selection(),
|
||||
text_anchors::direction::next);
|
||||
if (!adj_opt) {
|
||||
return ec.make_error("no next section found");
|
||||
}
|
||||
|
||||
tc->set_selection(adj_opt.value());
|
||||
}
|
||||
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
static Result<std::string, lnav::console::user_message>
|
||||
com_prev_section(exec_context& ec,
|
||||
std::string cmdline,
|
||||
std::vector<std::string>& args)
|
||||
{
|
||||
std::string retval;
|
||||
|
||||
if (args.empty()) {
|
||||
} else if (!ec.ec_dry_run) {
|
||||
auto* tc = *lnav_data.ld_view_stack.top();
|
||||
auto* ta = dynamic_cast<text_anchors*>(tc->get_sub_source());
|
||||
|
||||
if (ta == nullptr) {
|
||||
return ec.make_error("view does not support sections");
|
||||
}
|
||||
|
||||
auto adj_opt = ta->adjacent_anchor(tc->get_selection(),
|
||||
text_anchors::direction::prev);
|
||||
if (!adj_opt) {
|
||||
return ec.make_error("no previous section found");
|
||||
}
|
||||
|
||||
tc->set_selection(adj_opt.value());
|
||||
}
|
||||
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
static bool
|
||||
csv_needs_quoting(const std::string& str)
|
||||
{
|
||||
@ -1738,11 +1794,8 @@ com_save_to(exec_context& ec,
|
||||
size_t count = 0;
|
||||
|
||||
if (fos != nullptr) {
|
||||
fos->fos_contexts.push(field_overlay_source::context{
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
});
|
||||
fos->fos_contexts.push(
|
||||
field_overlay_source::context{"", false, false, false});
|
||||
}
|
||||
|
||||
auto y = 0_vl;
|
||||
@ -5820,6 +5873,24 @@ readline_context::command_t STD_COMMANDS[] = {
|
||||
help_text(":prev-location")
|
||||
.with_summary("Move to the previous position in the location history")
|
||||
.with_tags({"navigation"})},
|
||||
|
||||
{
|
||||
"next-section",
|
||||
com_next_section,
|
||||
|
||||
help_text(":next-section")
|
||||
.with_summary("Move to the next section in the document")
|
||||
.with_tags({"navigation"}),
|
||||
},
|
||||
{
|
||||
"prev-section",
|
||||
com_prev_section,
|
||||
|
||||
help_text(":prev-section")
|
||||
.with_summary("Move to the previous section in the document")
|
||||
.with_tags({"navigation"}),
|
||||
},
|
||||
|
||||
{"help",
|
||||
com_help,
|
||||
|
||||
|
@ -402,6 +402,7 @@ public:
|
||||
|
||||
bro_log_format()
|
||||
{
|
||||
this->lf_structured = true;
|
||||
this->lf_is_self_describing = true;
|
||||
this->lf_time_ordered = false;
|
||||
|
||||
@ -703,7 +704,6 @@ public:
|
||||
if (!this->blf_format_name.empty() && !this->blf_separator.empty()
|
||||
&& !this->blf_field_defs.empty())
|
||||
{
|
||||
dst.clear();
|
||||
return this->scan_int(dst, li, sbr, sbc);
|
||||
}
|
||||
|
||||
@ -1059,6 +1059,7 @@ public:
|
||||
{
|
||||
this->lf_is_self_describing = true;
|
||||
this->lf_time_ordered = false;
|
||||
this->lf_structured = true;
|
||||
}
|
||||
|
||||
const intern_string_t get_name() const override
|
||||
|
@ -310,6 +310,7 @@ logfile::process_prefix(shared_buffer_ref& sbr,
|
||||
this->lf_index.size(),
|
||||
li.li_file_range.fr_offset,
|
||||
li.li_file_range.fr_size);
|
||||
auto starting_index_size = this->lf_index.size();
|
||||
size_t prev_index_size = this->lf_index.size();
|
||||
for (const auto& curr : root_formats) {
|
||||
if (this->lf_index.size()
|
||||
@ -437,13 +438,13 @@ logfile::process_prefix(shared_buffer_ref& sbr,
|
||||
*/
|
||||
const auto& last_line = this->lf_index.back();
|
||||
|
||||
for (size_t lpc = 0; lpc < this->lf_index.size() - 1; lpc++) {
|
||||
require_lt(starting_index_size, this->lf_index.size());
|
||||
for (size_t lpc = 0; lpc < starting_index_size; lpc++) {
|
||||
if (this->lf_format->lf_multiline) {
|
||||
this->lf_index[lpc].set_time(last_line.get_time());
|
||||
this->lf_index[lpc].set_millis(last_line.get_millis());
|
||||
if (this->lf_format->lf_structured) {
|
||||
this->lf_index[lpc].set_ignore(true);
|
||||
} else {
|
||||
this->lf_index[lpc].set_time(last_line.get_time());
|
||||
this->lf_index[lpc].set_millis(last_line.get_millis());
|
||||
}
|
||||
} else {
|
||||
this->lf_index[lpc].set_time(last_line.get_time());
|
||||
|
@ -311,6 +311,11 @@ logfile_sub_source::text_value_for_line(textview_curses& tc,
|
||||
value_out = this->lss_token_value;
|
||||
}
|
||||
|
||||
{
|
||||
auto lr = line_range{0, (int) this->lss_token_value.length()};
|
||||
this->lss_token_attrs.emplace_back(lr, SA_ORIGINAL_LINE.value());
|
||||
}
|
||||
|
||||
if (!this->lss_token_line->is_continued()
|
||||
&& (this->lss_token_file->is_time_adjusted()
|
||||
|| ((format->lf_timestamp_flags & ETF_ZONE_SET
|
||||
@ -425,6 +430,7 @@ logfile_sub_source::text_value_for_line(textview_curses& tc,
|
||||
auto relstr = this->get_time_offset_for_line(tc, row_vl);
|
||||
value_out = fmt::format(FMT_STRING("{: >12}|{}"), relstr, value_out);
|
||||
}
|
||||
|
||||
this->lss_in_value_for_line = false;
|
||||
}
|
||||
|
||||
@ -459,8 +465,7 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv,
|
||||
const auto& line_values = this->lss_token_values;
|
||||
|
||||
lr.lr_start = 0;
|
||||
lr.lr_end = this->lss_token_value.length();
|
||||
value_out.emplace_back(lr, SA_ORIGINAL_LINE.value());
|
||||
lr.lr_end = -1;
|
||||
value_out.emplace_back(
|
||||
lr, SA_LEVEL.value(this->lss_token_line->get_msg_level()));
|
||||
|
||||
|
@ -31,6 +31,7 @@
|
||||
|
||||
#include "base/itertools.hh"
|
||||
#include "config.h"
|
||||
#include "scn/scn.h"
|
||||
|
||||
static std::vector<plain_text_source::text_line>
|
||||
to_text_line(const std::vector<attr_line_t>& lines)
|
||||
@ -79,7 +80,30 @@ plain_text_source::replace_with(const attr_line_t& text_lines)
|
||||
{
|
||||
this->tds_lines.clear();
|
||||
this->tds_doc_sections = lnav::document::discover_metadata(text_lines);
|
||||
file_off_t off = 0;
|
||||
auto lines = text_lines.split_lines();
|
||||
while (!lines.empty() && lines.back().empty()) {
|
||||
lines.pop_back();
|
||||
}
|
||||
for (auto& line : lines) {
|
||||
auto line_len = line.length() + 1;
|
||||
this->tds_lines.emplace_back(off, std::move(line));
|
||||
off += line_len;
|
||||
}
|
||||
this->tds_longest_line = this->compute_longest_line();
|
||||
if (this->tss_view != nullptr) {
|
||||
this->tss_view->set_needs_update();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
plain_text_source&
|
||||
plain_text_source::replace_with_mutable(attr_line_t& text_lines,
|
||||
text_format_t tf)
|
||||
{
|
||||
this->tds_lines.clear();
|
||||
this->tds_doc_sections
|
||||
= lnav::document::discover_structure(text_lines, line_range{0, -1}, tf);
|
||||
file_off_t off = 0;
|
||||
auto lines = text_lines.split_lines();
|
||||
while (!lines.empty() && lines.back().empty()) {
|
||||
@ -368,16 +392,47 @@ plain_text_source::row_for_anchor(const std::string& id)
|
||||
return retval;
|
||||
}
|
||||
|
||||
const auto& meta = this->tds_doc_sections;
|
||||
|
||||
auto is_ptr = startswith(id, "#/");
|
||||
if (is_ptr) {
|
||||
auto hier_sf = string_fragment::from_str(id).consume_n(2).value();
|
||||
std::vector<lnav::document::section_key_t> path;
|
||||
|
||||
while (!hier_sf.empty()) {
|
||||
auto comp_pair = hier_sf.split_when(string_fragment::tag1{'/'});
|
||||
auto scan_res
|
||||
= scn::scan_value<int64_t>(comp_pair.first.to_string_view());
|
||||
if (scan_res && scan_res.empty()) {
|
||||
path.emplace_back(scan_res.value());
|
||||
} else {
|
||||
path.emplace_back(json_ptr::decode(comp_pair.first));
|
||||
}
|
||||
hier_sf = comp_pair.second;
|
||||
}
|
||||
|
||||
auto lookup_res = lnav::document::hier_node::lookup_path(
|
||||
meta.m_sections_root.get(), path);
|
||||
if (lookup_res) {
|
||||
retval = this->line_for_offset(lookup_res.value()->hn_start);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
lnav::document::hier_node::depth_first(
|
||||
this->tds_doc_sections.m_sections_root.get(),
|
||||
meta.m_sections_root.get(),
|
||||
[this, &id, &retval](const lnav::document::hier_node* node) {
|
||||
for (const auto& child_pair : node->hn_named_children) {
|
||||
auto child_anchor
|
||||
const auto& child_anchor
|
||||
= text_anchors::to_anchor_string(child_pair.first);
|
||||
|
||||
if (child_anchor == id) {
|
||||
retval = this->line_for_offset(child_pair.second->hn_start);
|
||||
if (child_anchor != id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
retval = this->line_for_offset(child_pair.second->hn_start);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
@ -413,16 +468,127 @@ plain_text_source::anchor_for_row(vis_line_t vl)
|
||||
}
|
||||
|
||||
const auto& tl = this->tds_lines[vl];
|
||||
auto& md = this->tds_doc_sections;
|
||||
auto path_for_line = md.path_for_range(
|
||||
tl.tl_offset, tl.tl_offset + tl.tl_value.al_string.length());
|
||||
|
||||
this->tds_doc_sections.m_sections_tree.visit_overlapping(
|
||||
tl.tl_offset, [&retval](const lnav::document::section_interval_t& iv) {
|
||||
retval = iv.value.match(
|
||||
[](const std::string& str) {
|
||||
return nonstd::make_optional(
|
||||
text_anchors::to_anchor_string(str));
|
||||
},
|
||||
[](size_t) { return nonstd::nullopt; });
|
||||
});
|
||||
if (path_for_line.empty()) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
return retval;
|
||||
if ((path_for_line.size() == 1
|
||||
|| this->tds_text_format == text_format_t::TF_MARKDOWN)
|
||||
&& path_for_line.back().is<std::string>())
|
||||
{
|
||||
return text_anchors::to_anchor_string(
|
||||
path_for_line.back().get<std::string>());
|
||||
}
|
||||
|
||||
auto comps = path_for_line | lnav::itertools::map([](const auto& elem) {
|
||||
return elem.match(
|
||||
[](const std::string& str) {
|
||||
return json_ptr::encode_str(str);
|
||||
},
|
||||
[](size_t index) { return fmt::to_string(index); });
|
||||
});
|
||||
|
||||
return fmt::format(FMT_STRING("#/{}"),
|
||||
fmt::join(comps.begin(), comps.end(), "/"));
|
||||
}
|
||||
|
||||
nonstd::optional<vis_line_t>
|
||||
plain_text_source::adjacent_anchor(vis_line_t vl, text_anchors::direction dir)
|
||||
{
|
||||
if (vl > this->tds_lines.size()
|
||||
|| this->tds_doc_sections.m_sections_root == nullptr)
|
||||
{
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
const auto& tl = this->tds_lines[vl];
|
||||
auto path_for_line = this->tds_doc_sections.path_for_range(
|
||||
tl.tl_offset, tl.tl_offset + tl.tl_value.al_string.length());
|
||||
|
||||
auto& md = this->tds_doc_sections;
|
||||
if (path_for_line.empty()) {
|
||||
auto neighbors_res = md.m_sections_root->line_neighbors(vl);
|
||||
if (!neighbors_res) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
switch (dir) {
|
||||
case text_anchors::direction::prev: {
|
||||
if (neighbors_res->cnr_previous) {
|
||||
return this->line_for_offset(
|
||||
neighbors_res->cnr_previous.value()->hn_start);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case text_anchors::direction::next: {
|
||||
if (neighbors_res->cnr_next) {
|
||||
return this->line_for_offset(
|
||||
neighbors_res->cnr_next.value()->hn_start);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
auto last_key = path_for_line.back();
|
||||
path_for_line.pop_back();
|
||||
|
||||
auto parent_opt = lnav::document::hier_node::lookup_path(
|
||||
md.m_sections_root.get(), path_for_line);
|
||||
if (!parent_opt) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
auto parent = parent_opt.value();
|
||||
|
||||
auto child_hn = parent->lookup_child(last_key);
|
||||
if (!child_hn) {
|
||||
// XXX "should not happen"
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
auto neighbors_res = parent->child_neighbors(
|
||||
child_hn.value(), tl.tl_offset + tl.tl_value.al_string.length() + 1);
|
||||
if (!neighbors_res) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
if (neighbors_res->cnr_previous && last_key.is<std::string>()) {
|
||||
auto neighbor_sub
|
||||
= neighbors_res->cnr_previous.value()->lookup_child(last_key);
|
||||
if (neighbor_sub) {
|
||||
neighbors_res->cnr_previous = neighbor_sub;
|
||||
}
|
||||
}
|
||||
|
||||
if (neighbors_res->cnr_next && last_key.is<std::string>()) {
|
||||
auto neighbor_sub
|
||||
= neighbors_res->cnr_next.value()->lookup_child(last_key);
|
||||
if (neighbor_sub) {
|
||||
neighbors_res->cnr_next = neighbor_sub;
|
||||
}
|
||||
}
|
||||
|
||||
switch (dir) {
|
||||
case text_anchors::direction::prev: {
|
||||
if (neighbors_res->cnr_previous) {
|
||||
return this->line_for_offset(
|
||||
neighbors_res->cnr_previous.value()->hn_start);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case text_anchors::direction::next: {
|
||||
if (neighbors_res->cnr_next) {
|
||||
return this->line_for_offset(
|
||||
neighbors_res->cnr_next.value()->hn_start);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
@ -73,6 +73,9 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
plain_text_source& replace_with_mutable(attr_line_t& text_lines,
|
||||
text_format_t tf);
|
||||
|
||||
plain_text_source& replace_with(const attr_line_t& text_lines);
|
||||
|
||||
plain_text_source& replace_with(const std::vector<std::string>& text_lines);
|
||||
@ -123,6 +126,8 @@ public:
|
||||
nonstd::optional<vis_line_t> row_for_anchor(const std::string& id) override;
|
||||
nonstd::optional<std::string> anchor_for_row(vis_line_t vl) override;
|
||||
std::unordered_set<std::string> get_anchors() override;
|
||||
nonstd::optional<vis_line_t> adjacent_anchor(vis_line_t vl,
|
||||
direction dir) override;
|
||||
|
||||
protected:
|
||||
size_t compute_longest_line();
|
||||
|
@ -77,14 +77,14 @@ pretty_printer::append_to(attr_line_t& al)
|
||||
= this->pp_stream.tellp();
|
||||
this->pp_interval_state.back().is_name
|
||||
= tok_res->to_string();
|
||||
this->descend();
|
||||
this->descend(DT_XML_CLOSE_TAG);
|
||||
} else {
|
||||
this->pp_values.emplace_back(el);
|
||||
}
|
||||
continue;
|
||||
case DT_XML_CLOSE_TAG:
|
||||
this->flush_values();
|
||||
this->ascend();
|
||||
this->ascend(el.e_token);
|
||||
this->append_child_node();
|
||||
this->write_element(el);
|
||||
this->start_new_line();
|
||||
@ -94,7 +94,7 @@ pretty_printer::append_to(attr_line_t& al)
|
||||
case DT_LPAREN:
|
||||
this->flush_values(true);
|
||||
this->pp_values.emplace_back(el);
|
||||
this->descend();
|
||||
this->descend(to_closer(el.e_token));
|
||||
this->pp_interval_state.back().is_start
|
||||
= this->pp_stream.tellp();
|
||||
continue;
|
||||
@ -105,7 +105,7 @@ pretty_printer::append_to(attr_line_t& al)
|
||||
if (this->pp_body_lines.top()) {
|
||||
this->start_new_line();
|
||||
}
|
||||
this->ascend();
|
||||
this->ascend(el.e_token);
|
||||
this->write_element(el);
|
||||
continue;
|
||||
case DT_COMMA:
|
||||
@ -135,7 +135,7 @@ pretty_printer::append_to(attr_line_t& al)
|
||||
this->pp_values.emplace_back(el);
|
||||
}
|
||||
while (this->pp_depth > 0) {
|
||||
this->ascend();
|
||||
this->ascend(this->pp_container_tokens.back());
|
||||
}
|
||||
this->flush_values();
|
||||
|
||||
@ -341,30 +341,47 @@ pretty_printer::start_new_line()
|
||||
}
|
||||
|
||||
void
|
||||
pretty_printer::ascend()
|
||||
pretty_printer::ascend(data_token_t dt)
|
||||
{
|
||||
if (this->pp_depth > 0) {
|
||||
int lines = this->pp_body_lines.top();
|
||||
this->pp_depth -= 1;
|
||||
this->pp_body_lines.pop();
|
||||
this->pp_body_lines.top() += lines;
|
||||
|
||||
if (!this->pp_is_xml) {
|
||||
this->append_child_node();
|
||||
if (this->pp_container_tokens.back() != dt
|
||||
&& std::find(this->pp_container_tokens.begin(),
|
||||
this->pp_container_tokens.end(),
|
||||
dt)
|
||||
== this->pp_container_tokens.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
this->pp_interval_state.pop_back();
|
||||
this->pp_hier_stage = std::move(this->pp_hier_nodes.back());
|
||||
this->pp_hier_nodes.pop_back();
|
||||
|
||||
auto found = false;
|
||||
do {
|
||||
if (this->pp_container_tokens.back() == dt) {
|
||||
found = true;
|
||||
}
|
||||
int lines = this->pp_body_lines.top();
|
||||
this->pp_depth -= 1;
|
||||
this->pp_body_lines.pop();
|
||||
this->pp_body_lines.top() += lines;
|
||||
|
||||
if (!this->pp_is_xml) {
|
||||
this->append_child_node();
|
||||
}
|
||||
this->pp_interval_state.pop_back();
|
||||
this->pp_hier_stage = std::move(this->pp_hier_nodes.back());
|
||||
this->pp_hier_nodes.pop_back();
|
||||
this->pp_container_tokens.pop_back();
|
||||
} while (!found);
|
||||
} else {
|
||||
this->pp_body_lines.top() = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
pretty_printer::descend()
|
||||
pretty_printer::descend(data_token_t dt)
|
||||
{
|
||||
this->pp_depth += 1;
|
||||
this->pp_body_lines.push(0);
|
||||
this->pp_container_tokens.push_back(dt);
|
||||
this->pp_interval_state.resize(this->pp_depth + 1);
|
||||
this->pp_hier_nodes.push_back(
|
||||
std::make_unique<lnav::document::hier_node>());
|
||||
|
@ -97,9 +97,9 @@ public:
|
||||
std::set<size_t> take_indents() { return std::move(this->pp_indents); }
|
||||
|
||||
private:
|
||||
void descend();
|
||||
void descend(data_token_t dt);
|
||||
|
||||
void ascend();
|
||||
void ascend(data_token_t dt);
|
||||
|
||||
void start_new_line();
|
||||
|
||||
@ -120,6 +120,7 @@ private:
|
||||
int pp_depth{0};
|
||||
int pp_line_length{0};
|
||||
int pp_soft_indent{0};
|
||||
std::vector<data_token_t> pp_container_tokens{};
|
||||
std::stack<int> pp_body_lines{};
|
||||
data_scanner* pp_scanner;
|
||||
string_attrs_t pp_attrs;
|
||||
|
@ -904,7 +904,7 @@ rl_focus(readline_curses* rc)
|
||||
auto fos = (field_overlay_source*) lnav_data.ld_views[LNV_LOG]
|
||||
.get_overlay_source();
|
||||
|
||||
fos->fos_contexts.emplace("", false, true);
|
||||
fos->fos_contexts.emplace("", false, true, true);
|
||||
|
||||
get_textview_for_mode(lnav_data.ld_mode)->save_current_search();
|
||||
}
|
||||
|
@ -560,26 +560,26 @@ textfile_sub_source::text_crumbs_for_line(
|
||||
rend_iter->second.rf_text_source->text_crumbs_for_line(line, crumbs);
|
||||
}
|
||||
|
||||
if (lf->has_line_metadata()) {
|
||||
auto* lfo
|
||||
= dynamic_cast<line_filter_observer*>(lf->get_logline_observer());
|
||||
if (line < 0 || line >= lfo->lfo_filter_state.tfs_index.size()) {
|
||||
return;
|
||||
}
|
||||
auto ll_iter = lf->begin() + lfo->lfo_filter_state.tfs_index[line];
|
||||
char ts[64];
|
||||
|
||||
sql_strftime(ts, sizeof(ts), ll_iter->get_timeval(), 'T');
|
||||
|
||||
crumbs.emplace_back(
|
||||
std::string(ts),
|
||||
[]() -> std::vector<breadcrumb::possibility> { return {}; },
|
||||
[](const auto& key) {});
|
||||
}
|
||||
auto meta_iter = this->tss_doc_metadata.find(lf->get_filename());
|
||||
if (meta_iter == this->tss_doc_metadata.end()
|
||||
|| meta_iter->second.ms_metadata.m_sections_tree.empty())
|
||||
{
|
||||
if (lf->has_line_metadata()) {
|
||||
auto* lfo = dynamic_cast<line_filter_observer*>(
|
||||
lf->get_logline_observer());
|
||||
if (line < 0 || line >= lfo->lfo_filter_state.tfs_index.size()) {
|
||||
return;
|
||||
}
|
||||
auto ll_iter = lf->begin() + lfo->lfo_filter_state.tfs_index[line];
|
||||
char ts[64];
|
||||
|
||||
sql_strftime(ts, sizeof(ts), ll_iter->get_timeval(), 'T');
|
||||
|
||||
crumbs.emplace_back(
|
||||
std::string(ts),
|
||||
[]() -> std::vector<breadcrumb::possibility> { return {}; },
|
||||
[](const auto& key) {});
|
||||
}
|
||||
} else {
|
||||
auto* lfo
|
||||
= dynamic_cast<line_filter_observer*>(lf->get_logline_observer());
|
||||
@ -849,6 +849,7 @@ textfile_sub_source::rescan_files(
|
||||
rf.rf_mtime = st.st_mtime;
|
||||
rf.rf_file_size = st.st_size;
|
||||
rf.rf_text_source = std::make_unique<plain_text_source>();
|
||||
rf.rf_text_source->set_text_format(lf->get_text_format());
|
||||
rf.rf_text_source->register_view(this->tss_view);
|
||||
if (parse_res.isOk()) {
|
||||
auto& lf_meta = lf->get_embedded_metadata();
|
||||
@ -931,7 +932,7 @@ textfile_sub_source::rescan_files(
|
||||
this->tss_doc_metadata[lf->get_filename()]
|
||||
= metadata_state{
|
||||
st.st_mtime,
|
||||
static_cast<file_ssize_t>(content.length()),
|
||||
static_cast<file_ssize_t>(lf->get_index_size()),
|
||||
lnav::document::discover_structure(
|
||||
content,
|
||||
line_range{0, -1},
|
||||
@ -945,7 +946,7 @@ textfile_sub_source::rescan_files(
|
||||
this->tss_doc_metadata[lf->get_filename()]
|
||||
= metadata_state{
|
||||
st.st_mtime,
|
||||
static_cast<file_ssize_t>(st.st_size),
|
||||
static_cast<file_ssize_t>(lf->get_index_size()),
|
||||
{},
|
||||
};
|
||||
}
|
||||
@ -1159,6 +1160,142 @@ textfile_sub_source::get_anchors()
|
||||
return retval;
|
||||
}
|
||||
|
||||
static nonstd::optional<vis_line_t>
|
||||
to_vis_line(const std::shared_ptr<logfile>& lf, file_off_t off)
|
||||
{
|
||||
auto ll_opt = lf->line_for_offset(off);
|
||||
if (ll_opt != lf->end()) {
|
||||
return vis_line_t(std::distance(lf->cbegin(), ll_opt.value()));
|
||||
}
|
||||
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
nonstd::optional<vis_line_t>
|
||||
textfile_sub_source::adjacent_anchor(vis_line_t vl, text_anchors::direction dir)
|
||||
{
|
||||
auto lf = this->current_file();
|
||||
if (!lf) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
log_debug("adjacent_anchor: %s:L%d:%s",
|
||||
lf->get_filename().c_str(),
|
||||
vl,
|
||||
dir == text_anchors::direction::prev ? "prev" : "next");
|
||||
auto rend_iter = this->tss_rendered_files.find(lf->get_filename());
|
||||
if (rend_iter != this->tss_rendered_files.end()) {
|
||||
return rend_iter->second.rf_text_source->adjacent_anchor(vl, dir);
|
||||
}
|
||||
|
||||
auto iter = this->tss_doc_metadata.find(lf->get_filename());
|
||||
if (iter == this->tss_doc_metadata.end()) {
|
||||
log_debug(" no metadata available");
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
auto& md = iter->second.ms_metadata;
|
||||
auto* lfo = dynamic_cast<line_filter_observer*>(lf->get_logline_observer());
|
||||
if (vl >= lfo->lfo_filter_state.tfs_index.size()
|
||||
|| md.m_sections_root == nullptr)
|
||||
{
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
auto ll_iter = lf->begin() + lfo->lfo_filter_state.tfs_index[vl];
|
||||
auto line_offsets = lf->get_file_range(ll_iter, false);
|
||||
log_debug(
|
||||
" range %d:%d", line_offsets.fr_offset, line_offsets.next_offset());
|
||||
auto path_for_line
|
||||
= md.path_for_range(line_offsets.fr_offset, line_offsets.next_offset());
|
||||
|
||||
if (path_for_line.empty()) {
|
||||
log_debug(" no path found");
|
||||
auto neighbors_res = md.m_sections_root->line_neighbors(vl);
|
||||
if (!neighbors_res) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
switch (dir) {
|
||||
case text_anchors::direction::prev: {
|
||||
if (neighbors_res->cnr_previous) {
|
||||
return to_vis_line(
|
||||
lf, neighbors_res->cnr_previous.value()->hn_start);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case text_anchors::direction::next: {
|
||||
if (neighbors_res->cnr_next) {
|
||||
return to_vis_line(
|
||||
lf, neighbors_res->cnr_next.value()->hn_start);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
auto last_key = path_for_line.back();
|
||||
path_for_line.pop_back();
|
||||
|
||||
auto parent_opt = lnav::document::hier_node::lookup_path(
|
||||
md.m_sections_root.get(), path_for_line);
|
||||
if (!parent_opt) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
auto parent = parent_opt.value();
|
||||
|
||||
auto child_hn = parent->lookup_child(last_key);
|
||||
if (!child_hn) {
|
||||
// XXX "should not happen"
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
auto neighbors_res = parent->child_neighbors(
|
||||
child_hn.value(), line_offsets.next_offset() + 1);
|
||||
if (!neighbors_res) {
|
||||
log_debug(" no neighbors found");
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
log_debug(" neighbors p:%d n:%d",
|
||||
neighbors_res->cnr_previous.has_value(),
|
||||
neighbors_res->cnr_next.has_value());
|
||||
if (neighbors_res->cnr_previous && last_key.is<std::string>()) {
|
||||
auto neighbor_sub
|
||||
= neighbors_res->cnr_previous.value()->lookup_child(last_key);
|
||||
if (neighbor_sub) {
|
||||
neighbors_res->cnr_previous = neighbor_sub;
|
||||
}
|
||||
}
|
||||
|
||||
if (neighbors_res->cnr_next && last_key.is<std::string>()) {
|
||||
auto neighbor_sub
|
||||
= neighbors_res->cnr_next.value()->lookup_child(last_key);
|
||||
if (neighbor_sub) {
|
||||
neighbors_res->cnr_next = neighbor_sub;
|
||||
}
|
||||
}
|
||||
|
||||
switch (dir) {
|
||||
case text_anchors::direction::prev: {
|
||||
if (neighbors_res->cnr_previous) {
|
||||
return to_vis_line(
|
||||
lf, neighbors_res->cnr_previous.value()->hn_start);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case text_anchors::direction::next: {
|
||||
if (neighbors_res->cnr_next) {
|
||||
return to_vis_line(lf,
|
||||
neighbors_res->cnr_next.value()->hn_start);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
nonstd::optional<std::string>
|
||||
textfile_sub_source::anchor_for_row(vis_line_t vl)
|
||||
{
|
||||
@ -1181,36 +1318,34 @@ textfile_sub_source::anchor_for_row(vis_line_t vl)
|
||||
if (vl >= lfo->lfo_filter_state.tfs_index.size()) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
auto& md = iter->second.ms_metadata;
|
||||
auto ll_iter = lf->begin() + lfo->lfo_filter_state.tfs_index[vl];
|
||||
auto ll_next_iter = ll_iter + 1;
|
||||
auto end_offset = (ll_next_iter == lf->end())
|
||||
? lf->get_index_size() - 1
|
||||
: ll_next_iter->get_offset() - 1;
|
||||
auto line_offsets = lf->get_file_range(ll_iter, false);
|
||||
auto path_for_line
|
||||
= md.path_for_range(line_offsets.fr_offset, line_offsets.next_offset());
|
||||
|
||||
std::vector<std::string> collector;
|
||||
iter->second.ms_metadata.m_sections_tree.visit_overlapping(
|
||||
ll_iter->get_offset(),
|
||||
end_offset,
|
||||
[&collector](const lnav::document::section_interval_t& iv) {
|
||||
collector.emplace_back(iv.value.match(
|
||||
[](const std::string& str) { return str; },
|
||||
[](size_t index) { return fmt::to_string(index); }));
|
||||
});
|
||||
|
||||
if (collector.empty()) {
|
||||
if (path_for_line.empty()) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
if (collector.size() == 1) {
|
||||
return text_anchors::to_anchor_string(collector.front());
|
||||
if ((path_for_line.size() == 1
|
||||
|| md.m_text_format == text_format_t::TF_MARKDOWN)
|
||||
&& path_for_line.back().is<std::string>())
|
||||
{
|
||||
return text_anchors::to_anchor_string(
|
||||
path_for_line.back().get<std::string>());
|
||||
}
|
||||
|
||||
for (auto& elem : collector) {
|
||||
elem = json_ptr::encode_str(elem);
|
||||
}
|
||||
auto comps = path_for_line | lnav::itertools::map([](const auto& elem) {
|
||||
return elem.match(
|
||||
[](const std::string& str) {
|
||||
return json_ptr::encode_str(str);
|
||||
},
|
||||
[](size_t index) { return fmt::to_string(index); });
|
||||
});
|
||||
|
||||
return fmt::format(FMT_STRING("#/{}"),
|
||||
fmt::join(collector.begin(), collector.end(), "/"));
|
||||
fmt::join(comps.begin(), comps.end(), "/"));
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -147,6 +147,9 @@ public:
|
||||
|
||||
nonstd::optional<std::string> anchor_for_row(vis_line_t vl) override;
|
||||
|
||||
nonstd::optional<vis_line_t> adjacent_anchor(vis_line_t vl,
|
||||
direction dir) override;
|
||||
|
||||
std::unordered_set<std::string> get_anchors() override;
|
||||
|
||||
void quiesce() override;
|
||||
|
@ -314,6 +314,17 @@ public:
|
||||
virtual nonstd::optional<vis_line_t> row_for_anchor(const std::string& id)
|
||||
= 0;
|
||||
|
||||
enum class direction {
|
||||
prev,
|
||||
next,
|
||||
};
|
||||
|
||||
virtual nonstd::optional<vis_line_t> adjacent_anchor(vis_line_t vl,
|
||||
direction dir)
|
||||
{
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
virtual nonstd::optional<std::string> anchor_for_row(vis_line_t vl) = 0;
|
||||
|
||||
virtual std::unordered_set<std::string> get_anchors() = 0;
|
||||
|
@ -234,7 +234,6 @@ sql_timezone(std::string tz_str, string_fragment ts_str)
|
||||
}
|
||||
alb.append(" unrecognized input");
|
||||
}
|
||||
log_debug("wtf %s", ts_attr.get_string().c_str());
|
||||
um.with_note(ts_attr);
|
||||
throw um;
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ TIME_FORMATS = \
|
||||
"%Y-%m-%d %H:%M:%S:%L" \
|
||||
"%Y-%m-%d %H:%M:%S" \
|
||||
"%Y-%m-%d %H:%M" \
|
||||
"%Y-%m-%dT%H:%M:%S %p %Z" \
|
||||
"%Y-%m-%dT%H:%M:%S.%N%z" \
|
||||
"%y-%m-%dT%H:%M:%S.%N%z" \
|
||||
"%Y-%m-%dT%H:%M:%S.%f%z" \
|
||||
|
@ -86,7 +86,7 @@ looper::handler_looper::loop_body()
|
||||
if (exec_res.isErr()) {
|
||||
auto um = exec_res.unwrapErr();
|
||||
log_error(
|
||||
"wtf %s",
|
||||
"%s",
|
||||
um.to_attr_line().get_string().c_str());
|
||||
}
|
||||
});
|
||||
|
@ -192,6 +192,12 @@ view_curses::mvwattrline(WINDOW* window,
|
||||
char_index += 1;
|
||||
break;
|
||||
|
||||
case '\b':
|
||||
expanded_line.append("\u232b");
|
||||
utf_adjustments.emplace_back(lpc, -1);
|
||||
char_index += 1;
|
||||
break;
|
||||
|
||||
case '\r':
|
||||
case '\n':
|
||||
expanded_line.push_back(' ');
|
||||
|
@ -144,6 +144,11 @@ public:
|
||||
this->tds_doc_sections.m_indents = std::move(indents);
|
||||
}
|
||||
|
||||
void set_sections_root(std::unique_ptr<lnav::document::hier_node>&& hn)
|
||||
{
|
||||
this->tds_doc_sections.m_sections_root = std::move(hn);
|
||||
}
|
||||
|
||||
void text_crumbs_for_line(int line,
|
||||
std::vector<breadcrumb::crumb>& crumbs) override
|
||||
{
|
||||
@ -299,8 +304,8 @@ public:
|
||||
= interval_tree::Interval<file_off_t, lnav::document::hier_node*>;
|
||||
|
||||
std::shared_ptr<lnav::document::sections_tree_t> pss_interval_tree;
|
||||
std::vector<std::unique_ptr<lnav::document::hier_node>> pss_hier_nods;
|
||||
std::shared_ptr<hier_tree_t> pss_hier_tree;
|
||||
std::unique_ptr<lnav::document::hier_node> pss_root_node;
|
||||
};
|
||||
|
||||
static void
|
||||
@ -324,7 +329,7 @@ open_pretty_view()
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<attr_line_t> full_text;
|
||||
attr_line_t full_text;
|
||||
|
||||
delete pretty_tc->get_sub_source();
|
||||
pretty_tc->set_sub_source(nullptr);
|
||||
@ -361,6 +366,11 @@ open_pretty_view()
|
||||
al.get_string(),
|
||||
text_sub_source::RF_FULL | text_sub_source::RF_REWRITE);
|
||||
lss.text_attrs_for_line(*log_tc, vl, al.get_attrs());
|
||||
{
|
||||
const auto orig_lr
|
||||
= find_string_attr_range(al.get_attrs(), &SA_ORIGINAL_LINE);
|
||||
require(orig_lr.is_valid());
|
||||
}
|
||||
scrub_ansi_string(al.get_string(), &al.get_attrs());
|
||||
if (log_tc->get_hide_fields()) {
|
||||
al.apply_hide();
|
||||
@ -368,6 +378,7 @@ open_pretty_view()
|
||||
|
||||
const auto orig_lr
|
||||
= find_string_attr_range(al.get_attrs(), &SA_ORIGINAL_LINE);
|
||||
require(orig_lr.is_valid());
|
||||
const auto body_lr
|
||||
= find_string_attr_range(al.get_attrs(), &SA_BODY);
|
||||
auto orig_al = al.subline(orig_lr.lr_start, orig_lr.length());
|
||||
@ -419,7 +430,8 @@ open_pretty_view()
|
||||
}
|
||||
});
|
||||
line_off += pretty_line.get_string().length();
|
||||
full_text.emplace_back(pretty_line);
|
||||
full_text.append(pretty_line);
|
||||
full_text.append("\n");
|
||||
}
|
||||
|
||||
first_line = false;
|
||||
@ -467,29 +479,28 @@ open_pretty_view()
|
||||
|
||||
data_scanner ds(orig_al.get_string());
|
||||
pretty_printer pp(&ds, orig_al.get_attrs());
|
||||
attr_line_t pretty_al;
|
||||
|
||||
pp.append_to(pretty_al);
|
||||
pretty_al.rtrim();
|
||||
pp.append_to(full_text);
|
||||
|
||||
all_intervals = pp.take_intervals();
|
||||
hier_nodes.emplace_back(pp.take_hier_root());
|
||||
hier_tree_vec.emplace_back(
|
||||
0, pretty_al.length(), hier_nodes.back().get());
|
||||
0, full_text.length(), hier_nodes.back().get());
|
||||
pretty_indents = pp.take_indents();
|
||||
|
||||
pretty_al.split_lines(full_text);
|
||||
}
|
||||
}
|
||||
auto* pts = new pretty_sub_source();
|
||||
pts->pss_interval_tree = std::make_shared<lnav::document::sections_tree_t>(
|
||||
std::move(all_intervals));
|
||||
pts->pss_hier_nods = std::move(hier_nodes);
|
||||
auto root_node = std::make_unique<lnav::document::hier_node>();
|
||||
root_node->hn_children = std::move(hier_nodes);
|
||||
pts->pss_hier_tree = std::make_shared<pretty_sub_source::hier_tree_t>(
|
||||
std::move(hier_tree_vec));
|
||||
pts->pss_root_node = std::move(root_node);
|
||||
pts->set_indents(std::move(pretty_indents));
|
||||
|
||||
pts->replace_with(full_text);
|
||||
pts->replace_with_mutable(full_text,
|
||||
top_tc->get_sub_source()->get_text_format());
|
||||
pretty_tc->set_sub_source(pts);
|
||||
if (lnav_data.ld_last_pretty_print_top != log_tc->get_top()) {
|
||||
pretty_tc->set_top(0_vl);
|
||||
@ -498,12 +509,6 @@ open_pretty_view()
|
||||
pretty_tc->redo_search();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void
|
||||
ignore_case(const T&)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
build_all_help_text()
|
||||
{
|
||||
|
@ -253,6 +253,7 @@ dist_noinst_DATA = \
|
||||
bad-config2/formats/invalid-config/config.truncated.json \
|
||||
bad-config-json/formats/invalid-json/format.json \
|
||||
bad-config-json/formats/invalid-key/format.json \
|
||||
books.json \
|
||||
books.xml \
|
||||
file_for_dot_read.sql \
|
||||
datafile_simple.0 \
|
||||
|
14
test/books.json
Normal file
14
test/books.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"catalog": [
|
||||
{
|
||||
"author": "Gambardella, Matthew",
|
||||
"title": "XML Developer's Guide",
|
||||
"description": "An in-depth look at creating applications with XML."
|
||||
},
|
||||
{
|
||||
"author": "Ralls, Kim",
|
||||
"title": "Midnight Rain",
|
||||
"description": "A former architect battles corporate zombies, an evil sorceress, and her own childhood to become queen of the world."
|
||||
}
|
||||
]
|
||||
}
|
@ -1198,10 +1198,14 @@ EXPECTED_FILES = \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_0bba304f34ae07c4fa9e91e0b42f5fe98654a6a8.out \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_11fd274911e45a743b4de616888a64183d07cb76.err \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_11fd274911e45a743b4de616888a64183d07cb76.out \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_143a40164c93c7ec44a66e7940b92b128a421147.err \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_143a40164c93c7ec44a66e7940b92b128a421147.out \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_1ce4056d72b871f8bb844c86aade2a9b1da58030.err \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_1ce4056d72b871f8bb844c86aade2a9b1da58030.out \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_4226123565a53b4e3f80e602c1f294721e8e07bf.err \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_4226123565a53b4e3f80e602c1f294721e8e07bf.out \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_4dd174410d702a7b4be794fb6fa2c8889bd768d6.err \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_4dd174410d702a7b4be794fb6fa2c8889bd768d6.out \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_5b51b55dff7332c5bee2c9b797c401c5614d574a.err \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_5b51b55dff7332c5bee2c9b797c401c5614d574a.out \
|
||||
$(srcdir)/%reldir%/test_text_file.sh_5e9320f18d066e6fc930dbbffc357af64312bd4b.err \
|
||||
|
@ -4839,7 +4839,7 @@
|
||||
"alt-msg": ""
|
||||
},
|
||||
"x7b": {
|
||||
"command": ":prev-location",
|
||||
"command": ":prev-section",
|
||||
"alt-msg": "${keymap_def_next_location}"
|
||||
},
|
||||
"x7c": {
|
||||
@ -4847,7 +4847,7 @@
|
||||
"alt-msg": ""
|
||||
},
|
||||
"x7d": {
|
||||
"command": ":next-location",
|
||||
"command": ":next-section",
|
||||
"alt-msg": "${keymap_def_prev_location}"
|
||||
}
|
||||
},
|
||||
@ -5031,11 +5031,11 @@
|
||||
"keymap_def_clear": "Press ${ansi_bold}C${ansi_norm} to clear marked messages",
|
||||
"keymap_def_db_view": "Press ${ansi_bold}v${ansi_norm}/${ansi_bold}V${ansi_norm} to switch to the SQL result view",
|
||||
"keymap_def_hist_view": "Press ${ansi_bold}i${ansi_norm}/${ansi_bold}I${ansi_norm} to switch to the histogram view",
|
||||
"keymap_def_next_location": "Press ${ansi_bold}}${ansi_norm} to move to the next location in history",
|
||||
"keymap_def_next_mark": "Press ${ansi_bold}c${ansi_norm} to copy marked lines to the clipboard; press ${ansi_bold}C${ansi_norm} to clear marked lines",
|
||||
"keymap_def_next_section": "Press ${ansi_bold}}${ansi_norm} to move to the next section in history",
|
||||
"keymap_def_next_user_mark": "Press ${ansi_bold}u${ansi_norm}/${ansi_bold}U${ansi_norm} to move forward/backward through user bookmarks",
|
||||
"keymap_def_pop_view": "Press ${ansi_bold}q${ansi_norm} to return to the previous view",
|
||||
"keymap_def_prev_location": "Press ${ansi_bold}{${ansi_norm} to move to the previous location in history",
|
||||
"keymap_def_prev_section": "Press ${ansi_bold}{${ansi_norm} to move to the previous section in history",
|
||||
"keymap_def_scroll_horiz": "Press \\'${ansi_bold}>${ansi_norm}\\' or \\'${ansi_bold}<${ansi_norm}\\' to scroll horizontally to a search result",
|
||||
"keymap_def_text_view": "Press ${ansi_bold}t${ansi_norm} to switch to the text view",
|
||||
"keymap_def_time_offset": "Press ${ansi_bold}s${ansi_norm}/${ansi_bold}S${ansi_norm} to move forward/backward through slow downs",
|
||||
|
@ -3,11 +3,11 @@
|
||||
/global/keymap_def_clear -> default-keymap.json:13
|
||||
/global/keymap_def_db_view -> default-keymap.json:8
|
||||
/global/keymap_def_hist_view -> default-keymap.json:9
|
||||
/global/keymap_def_next_location -> default-keymap.json:15
|
||||
/global/keymap_def_next_mark -> default-keymap.json:16
|
||||
/global/keymap_def_next_section -> default-keymap.json:15
|
||||
/global/keymap_def_next_user_mark -> default-keymap.json:7
|
||||
/global/keymap_def_pop_view -> default-keymap.json:11
|
||||
/global/keymap_def_prev_location -> default-keymap.json:14
|
||||
/global/keymap_def_prev_section -> default-keymap.json:14
|
||||
/global/keymap_def_scroll_horiz -> default-keymap.json:6
|
||||
/global/keymap_def_text_view -> default-keymap.json:10
|
||||
/global/keymap_def_time_offset -> default-keymap.json:17
|
||||
|
@ -1092,7 +1092,8 @@ For support questions, email:
|
||||
number, percent into the file,
|
||||
timestamp, or an anchor in a text file
|
||||
[4mSee Also[0m
|
||||
[1m:next-location[0m, [1m:next-mark[0m, [1m:prev-location[0m, [1m:prev-mark[0m, [1m:relative-goto[0m
|
||||
[1m:next-location[0m, [1m:next-mark[0m, [1m:next-section[0m, [1m:prev-location[0m, [1m:prev-mark[0m,
|
||||
[1m:prev-section[0m, [1m:relative-goto[0m
|
||||
[4mExamples[0m
|
||||
#1 To go to line 22:
|
||||
[37m[40m:[0m[1m[36m[40mgoto[0m[37m[40m 22 [0m
|
||||
@ -1232,7 +1233,8 @@ For support questions, email:
|
||||
══════════════════════════════════════════════════════════════════════
|
||||
Move to the next position in the location history
|
||||
[4mSee Also[0m
|
||||
[1m:goto[0m, [1m:next-mark[0m, [1m:prev-location[0m, [1m:prev-mark[0m, [1m:relative-goto[0m
|
||||
[1m:goto[0m, [1m:next-mark[0m, [1m:next-section[0m, [1m:prev-location[0m, [1m:prev-mark[0m,
|
||||
[1m:prev-section[0m, [1m:relative-goto[0m
|
||||
|
||||
[4m:[0m[1m[4mnext-mark[0m[4m [0m[4mtype[0m[4m1[0m[4m [[0m[4m...[0m[4m [0m[4mtype[0m[4mN[0m[4m][0m
|
||||
══════════════════════════════════════════════════════════════════════
|
||||
@ -1241,14 +1243,21 @@ For support questions, email:
|
||||
[4mtype[0m The type of bookmark -- error, warning, search,
|
||||
user, file, meta
|
||||
[4mSee Also[0m
|
||||
[1m:goto[0m, [1m:hide-unmarked-lines[0m, [1m:mark[0m, [1m:next-location[0m, [1m:prev-location[0m,
|
||||
[1m:prev-mark[0m, [1m:prev-mark[0m, [1m:relative-goto[0m
|
||||
[1m:goto[0m, [1m:hide-unmarked-lines[0m, [1m:mark[0m, [1m:next-location[0m, [1m:next-section[0m,
|
||||
[1m:prev-location[0m, [1m:prev-mark[0m, [1m:prev-mark[0m, [1m:prev-section[0m, [1m:relative-goto[0m
|
||||
[4mExample[0m
|
||||
#1 To go to the next error:
|
||||
[37m[40m:[0m[1m[36m[40mnext-mark[0m[37m[40m error [0m
|
||||
|
||||
|
||||
|
||||
[4m:[0m[1m[4mnext-section[0m
|
||||
══════════════════════════════════════════════════════════════════════
|
||||
Move to the next section in the document
|
||||
[4mSee Also[0m
|
||||
[1m:goto[0m, [1m:next-location[0m, [1m:next-mark[0m, [1m:prev-location[0m, [1m:prev-mark[0m,
|
||||
[1m:prev-section[0m, [1m:relative-goto[0m
|
||||
|
||||
[4m:[0m[1m[4mopen[0m[4m [0m[4mpath[0m[4m1[0m[4m [[0m[4m...[0m[4m [0m[4mpath[0m[4mN[0m[4m][0m
|
||||
══════════════════════════════════════════════════════════════════════
|
||||
Open the given file(s) in lnav. Opening files on machines
|
||||
@ -1315,7 +1324,8 @@ For support questions, email:
|
||||
══════════════════════════════════════════════════════════════════════
|
||||
Move to the previous position in the location history
|
||||
[4mSee Also[0m
|
||||
[1m:goto[0m, [1m:next-location[0m, [1m:next-mark[0m, [1m:prev-mark[0m, [1m:relative-goto[0m
|
||||
[1m:goto[0m, [1m:next-location[0m, [1m:next-mark[0m, [1m:next-section[0m, [1m:prev-mark[0m,
|
||||
[1m:prev-section[0m, [1m:relative-goto[0m
|
||||
|
||||
[4m:[0m[1m[4mprev-mark[0m[4m [0m[4mtype[0m[4m1[0m[4m [[0m[4m...[0m[4m [0m[4mtype[0m[4mN[0m[4m][0m
|
||||
══════════════════════════════════════════════════════════════════════
|
||||
@ -1326,13 +1336,21 @@ For support questions, email:
|
||||
user, file, meta
|
||||
[4mSee Also[0m
|
||||
[1m:goto[0m, [1m:hide-unmarked-lines[0m, [1m:mark[0m, [1m:next-location[0m, [1m:next-mark[0m,
|
||||
[1m:next-mark[0m, [1m:prev-location[0m, [1m:relative-goto[0m
|
||||
[1m:next-mark[0m, [1m:next-section[0m, [1m:prev-location[0m, [1m:prev-section[0m,
|
||||
[1m:relative-goto[0m
|
||||
[4mExample[0m
|
||||
#1 To go to the previous error:
|
||||
[37m[40m:[0m[1m[36m[40mprev-mark[0m[37m[40m error [0m
|
||||
|
||||
|
||||
|
||||
[4m:[0m[1m[4mprev-section[0m
|
||||
══════════════════════════════════════════════════════════════════════
|
||||
Move to the previous section in the document
|
||||
[4mSee Also[0m
|
||||
[1m:goto[0m, [1m:next-location[0m, [1m:next-mark[0m, [1m:next-section[0m, [1m:prev-location[0m,
|
||||
[1m:prev-mark[0m, [1m:relative-goto[0m
|
||||
|
||||
[4m:[0m[1m[4mprompt[0m[4m [0m[4mtype[0m[4m [[0m[4m--alt[0m[4m] [[0m[4mprompt[0m[4m] [[0m[4minitial-value[0m[4m][0m
|
||||
══════════════════════════════════════════════════════════════════════
|
||||
Open the given prompt
|
||||
@ -1409,7 +1427,8 @@ For support questions, email:
|
||||
[4mParameter[0m
|
||||
[4mline-count|N%[0m The amount to move the view by.
|
||||
[4mSee Also[0m
|
||||
[1m:goto[0m, [1m:next-location[0m, [1m:next-mark[0m, [1m:prev-location[0m, [1m:prev-mark[0m
|
||||
[1m:goto[0m, [1m:next-location[0m, [1m:next-mark[0m, [1m:next-section[0m, [1m:prev-location[0m,
|
||||
[1m:prev-mark[0m, [1m:prev-section[0m
|
||||
[4mExamples[0m
|
||||
#1 To move 22 lines down in the view:
|
||||
[37m[40m:[0m[1m[36m[40mrelative-goto[0m[37m[40m +22 [0m
|
||||
|
@ -33,5 +33,6 @@ Apr 7 07:32:56 Tim-Stacks-iMac.local logger[234]: Bad data {
|
||||
abc,
|
||||
123,
|
||||
456
|
||||
)
|
||||
}]
|
||||
)
|
||||
}
|
||||
]
|
||||
|
@ -1,8 +1,8 @@
|
||||
{"ts": "2013-09-06T20:00:48.124817Z", "lvl": "TRACE", "msg": "trace test"}
|
||||
{"ts": "2013-09-06T20:00:49.124817Z", "lvl": "INFO", "msg": "Starting up \u001B[0;32mservice\u001B[0m"}
|
||||
{"ts": "2013-09-06T22:00:49.124817Z", "lvl": "INFO", "msg": "Shutting down service", "user": "steve@example.com"}
|
||||
{"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG5", "msg": "Details...\n"}
|
||||
{"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG4", "msg": "Details...\n"}
|
||||
{"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG5", "msg": "D\bDetails...\n"}
|
||||
{"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG4", "msg": "D\bDe\betails...\n"}
|
||||
{"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG3", "msg": "Details...\n"}
|
||||
{"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG2", "msg": "Details...\n"}
|
||||
{"ts": "2013-09-06 22:01:00Z", "lvl": "DEBUG", "msg": "Details..."}
|
||||
|
@ -6,9 +6,9 @@
|
||||
[2013-09-06T22:00:49.124] ⋮ Shutting down service
|
||||
user: steve@example.com
|
||||
|
||||
[2013-09-06T22:00:59.124] ⋮ Details...
|
||||
[2013-09-06T22:00:59.124] ⋮ [1mD[0metails...
|
||||
|
||||
[2013-09-06T22:00:59.124] ⋮ Details...
|
||||
[2013-09-06T22:00:59.124] ⋮ [1mDe[0mtails...
|
||||
|
||||
[2013-09-06T22:00:59.124] ⋮ Details...
|
||||
|
||||
|
@ -6,10 +6,10 @@
|
||||
[2013-09-06T22:00:49.124000Z] ⋮ Shutting down servicebork bork bork
|
||||
user: mailto:steve@example.com
|
||||
|
||||
[2013-09-06T22:00:59.124000Z] ⋮ Details...
|
||||
[2013-09-06T22:00:59.124000Z] ⋮ [1mD[0metails...
|
||||
bork bork bork
|
||||
|
||||
[2013-09-06T22:00:59.124000Z] ⋮ Details...
|
||||
[2013-09-06T22:00:59.124000Z] ⋮ [1mDe[0mtails...
|
||||
bork bork bork
|
||||
|
||||
[2013-09-06T22:00:59.124000Z] ⋮ Details...
|
||||
|
@ -2,6 +2,4 @@
|
||||
├ org.lnav.test:
|
||||
╰ Hello, World!
|
||||
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkboot.gz HTTP/1.0" 404 46210 "-" "gPXE/0.9.7"
|
||||
📝 Annotations available, focus on this line and use :annotate to apply them
|
||||
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkernel.gz HTTP/1.0" 200 78929 "-" "gPXE/0.9.7"
|
||||
📝 Annotations available, focus on this line and use :annotate to apply them
|
||||
|
@ -0,0 +1,5 @@
|
||||
[35m"title"[0m: [35m"Midnight Rain"[0m,
|
||||
[35m"description"[0m: [35m"A former architect battles corporate zombies, an evil sorceress, and her own childhood to become queen of the world."[0m
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
[35m"author"[0m: [35m"Ralls, Kim"[0m,
|
||||
[35m"title"[0m: [35m"Midnight Rain"[0m,
|
||||
[35m"description"[0m: [35m"A former architect battles corporate zombies, an evil sorceress, and her own childhood to become queen of the world."[0m
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
[35m"title"[0m: [35m"Midnight Rain"[0m,
|
||||
[35m"description"[0m: [35m"A former architect battles corporate zombies, an evil sorceress, and her own childhood to become queen of the world."[0m
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
[35m"author"[0m: [35m"Ralls, Kim"[0m,
|
||||
[35m"title"[0m: [35m"Midnight Rain"[0m,
|
||||
[35m"description"[0m: [35m"A former architect battles corporate zombies, an evil sorceress, and her own childhood to become queen of the world."[0m
|
||||
}
|
||||
]
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
{"ts": "2013-09-06T20:00:48.124817Z", "lvl": "TRACE", "msg": "trace test"}
|
||||
{"ts": "2013-09-06T20:00:49.124817Z", "lvl": "INFO", "msg": "Starting up \u001B[0;32mservice\u001B[0m"}
|
||||
{"ts": "2013-09-06T22:00:49.124817Z", "lvl": "INFO", "msg": "Shutting down service", "user": "steve@example.com"}
|
||||
{"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG5", "msg": "Details...\n"}
|
||||
{"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG4", "msg": "Details...\n"}
|
||||
{"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG5", "msg": "D\bDetails...\n"}
|
||||
{"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG4", "msg": "D\bDe\betails...\n"}
|
||||
{"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG3", "msg": "Details...\n"}
|
||||
{"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG2", "msg": "Details...\n"}
|
||||
{"ts": "2013-09-06 22:01:00Z", "lvl": "DEBUG", "msg": "Details..."}
|
||||
|
@ -79,3 +79,21 @@ run_cap_test ${lnav_test} -n \
|
||||
-c ';SELECT top_meta FROM lnav_top_view' \
|
||||
-c ':write-json-to -' \
|
||||
${test_dir}/formats/jsontest/format.json
|
||||
|
||||
run_cap_test ${lnav_test} -n \
|
||||
-c ':goto 3' \
|
||||
-c ':next-section' \
|
||||
${test_dir}/books.json
|
||||
|
||||
run_cap_test ${lnav_test} -n \
|
||||
-c ':goto 3' \
|
||||
-c ':next-section' \
|
||||
< ${test_dir}/books.json
|
||||
|
||||
run_cap_test ${lnav_test} -n \
|
||||
-c ':goto #/catalog/1/title' \
|
||||
${test_dir}/books.json
|
||||
|
||||
run_cap_test ${lnav_test} -n \
|
||||
-c ':goto #/catalog/1/title' \
|
||||
< ${test_dir}/books.json
|
||||
|
Loading…
Reference in New Issue
Block a user