mirror of https://github.com/tstack/lnav
[views] add a breadcrumb view
parent
2c4b1d3886
commit
f03f9e704f
@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Copyright (c) 2022, Timothy Stack
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Timothy Stack nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef lnav_breadcrumb_hh
|
||||
#define lnav_breadcrumb_hh
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/attr_line.hh"
|
||||
#include "fmt/format.h"
|
||||
#include "mapbox/variant.hpp"
|
||||
|
||||
namespace breadcrumb {
|
||||
|
||||
struct possibility {
|
||||
possibility(std::string key, attr_line_t value)
|
||||
: p_key(std::move(key)), p_display_value(std::move(value))
|
||||
{
|
||||
}
|
||||
|
||||
explicit possibility(std::string key) : p_key(key), p_display_value(key) {}
|
||||
|
||||
possibility() = default;
|
||||
|
||||
std::string p_key;
|
||||
attr_line_t p_display_value;
|
||||
};
|
||||
|
||||
using crumb_possibilities = std::function<std::vector<possibility>()>;
|
||||
|
||||
struct crumb {
|
||||
using key_t = mapbox::util::variant<std::string, size_t>;
|
||||
|
||||
using perform = std::function<void(const key_t& key)>;
|
||||
|
||||
crumb(std::string key,
|
||||
attr_line_t al,
|
||||
crumb_possibilities cp,
|
||||
perform performer)
|
||||
: c_key(std::move(key)), c_display_value(std::move(al)),
|
||||
c_possibility_provider(std::move(cp)),
|
||||
c_performer(std::move(performer))
|
||||
{
|
||||
}
|
||||
|
||||
crumb(std::string key, crumb_possibilities cp, perform performer)
|
||||
: c_key(key), c_display_value(key),
|
||||
c_possibility_provider(std::move(cp)),
|
||||
c_performer(std::move(performer))
|
||||
{
|
||||
}
|
||||
|
||||
explicit crumb(size_t index, crumb_possibilities cp, perform performer)
|
||||
: c_key(index), c_display_value(fmt::format(FMT_STRING("[{}]"), index)),
|
||||
c_possibility_provider(std::move(cp)),
|
||||
c_performer(std::move(performer))
|
||||
{
|
||||
}
|
||||
|
||||
explicit crumb(key_t key, crumb_possibilities cp, perform performer)
|
||||
: c_key(key), c_display_value(key.match(
|
||||
[](std::string str) { return str; },
|
||||
[](size_t index) {
|
||||
return fmt::format(FMT_STRING("[{}]"), index);
|
||||
})),
|
||||
c_possibility_provider(std::move(cp)),
|
||||
c_performer(std::move(performer))
|
||||
{
|
||||
}
|
||||
|
||||
crumb& with_possible_range(size_t count)
|
||||
{
|
||||
this->c_possible_range = count;
|
||||
this->c_search_placeholder
|
||||
= fmt::format(FMT_STRING("(Enter a number from 0 to {})"),
|
||||
this->c_possible_range.value() - 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
enum class expected_input_t {
|
||||
exact,
|
||||
index,
|
||||
index_or_exact,
|
||||
anything,
|
||||
};
|
||||
|
||||
key_t c_key;
|
||||
attr_line_t c_display_value;
|
||||
crumb_possibilities c_possibility_provider;
|
||||
perform c_performer;
|
||||
nonstd::optional<size_t> c_possible_range;
|
||||
expected_input_t c_expected_input{expected_input_t::exact};
|
||||
std::string c_search_placeholder;
|
||||
};
|
||||
|
||||
} // namespace breadcrumb
|
||||
|
||||
#endif
|
@ -0,0 +1,397 @@
|
||||
/**
|
||||
* Copyright (c) 2022, Timothy Stack
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Timothy Stack nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "breadcrumb_curses.hh"
|
||||
|
||||
#include "base/itertools.hh"
|
||||
#include "itertools.similar.hh"
|
||||
#include "log_format_fwd.hh"
|
||||
#include "logfile.hh"
|
||||
|
||||
using namespace lnav::roles::literals;
|
||||
|
||||
breadcrumb_curses::breadcrumb_curses()
|
||||
{
|
||||
this->bc_match_search_overlay.sos_parent = this;
|
||||
this->bc_match_source.set_reverse_selection(true);
|
||||
this->bc_match_view.set_selectable(true);
|
||||
this->bc_match_view.set_overlay_source(&this->bc_match_search_overlay);
|
||||
this->bc_match_view.set_sub_source(&this->bc_match_source);
|
||||
this->bc_match_view.set_height(0_vl);
|
||||
this->bc_match_view.set_show_scrollbar(true);
|
||||
this->bc_match_view.set_default_role(role_t::VCR_POPUP);
|
||||
this->add_child_view(&this->bc_match_view);
|
||||
}
|
||||
|
||||
void
|
||||
breadcrumb_curses::do_update()
|
||||
{
|
||||
if (!this->bc_line_source) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t crumb_index = 0;
|
||||
size_t sel_crumb_offset = 0;
|
||||
auto width = getmaxx(this->bc_window);
|
||||
auto crumbs = this->bc_focused_crumbs.empty() ? this->bc_line_source()
|
||||
: this->bc_focused_crumbs;
|
||||
auto crumbs_line
|
||||
= crumbs | lnav::itertools::map(&breadcrumb::crumb::c_display_value)
|
||||
| lnav::itertools::fold(
|
||||
[&](const auto& elem, auto& accum) {
|
||||
auto accum_width = utf8_string_length(accum.get_string())
|
||||
.template unwrap();
|
||||
auto elem_width
|
||||
= utf8_string_length(elem.get_string()).template unwrap();
|
||||
auto is_selected = this->bc_selected_crumb
|
||||
&& (crumb_index == this->bc_selected_crumb.value());
|
||||
|
||||
if (is_selected && ((accum_width + elem_width) > width)) {
|
||||
accum.clear();
|
||||
accum.append("\u22ef\u276d"_breadcrumb);
|
||||
accum_width = 2;
|
||||
}
|
||||
|
||||
accum.append(elem);
|
||||
if (is_selected) {
|
||||
sel_crumb_offset = accum_width;
|
||||
accum.get_attrs().emplace_back(
|
||||
line_range{
|
||||
(int) (accum.length() - elem.length()),
|
||||
(int) accum.length(),
|
||||
},
|
||||
VC_STYLE.template value(A_REVERSE));
|
||||
}
|
||||
crumb_index += 1;
|
||||
return accum.append("\u276d"_breadcrumb);
|
||||
},
|
||||
attr_line_t());
|
||||
|
||||
line_range lr{0, width};
|
||||
view_curses::mvwattrline(
|
||||
this->bc_window, this->bc_y, 0, crumbs_line, lr, role_t::VCR_STATUS);
|
||||
|
||||
if (this->bc_selected_crumb) {
|
||||
this->bc_match_view.set_x(sel_crumb_offset);
|
||||
}
|
||||
view_curses::do_update();
|
||||
}
|
||||
|
||||
void
|
||||
breadcrumb_curses::reload_data()
|
||||
{
|
||||
if (!this->bc_selected_crumb) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& selected_crumb_ref
|
||||
= this->bc_focused_crumbs[this->bc_selected_crumb.value()];
|
||||
this->bc_possible_values = selected_crumb_ref.c_possibility_provider()
|
||||
| lnav::itertools::sort_by(&breadcrumb::possibility::p_key);
|
||||
|
||||
nonstd::optional<size_t> selected_value;
|
||||
this->bc_similar_values = this->bc_possible_values
|
||||
| lnav::itertools::similar_to(
|
||||
[](const auto& elem) { return elem.p_key; },
|
||||
this->bc_current_search,
|
||||
INT_MAX);
|
||||
if (selected_crumb_ref.c_key.is<std::string>()) {
|
||||
auto& selected_crumb_key = selected_crumb_ref.c_key.get<std::string>();
|
||||
auto found_poss_opt = this->bc_similar_values
|
||||
| lnav::itertools::find_if([&selected_crumb_key](const auto& elem) {
|
||||
return elem.p_key == selected_crumb_key;
|
||||
});
|
||||
|
||||
if (found_poss_opt) {
|
||||
selected_value = std::distance(this->bc_similar_values.begin(),
|
||||
found_poss_opt.value());
|
||||
} else {
|
||||
selected_value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto matches = attr_line_t().join(
|
||||
this->bc_similar_values
|
||||
| lnav::itertools::map(&breadcrumb::possibility::p_display_value),
|
||||
"\n");
|
||||
this->bc_match_source.replace_with(matches);
|
||||
auto width = this->bc_possible_values
|
||||
| lnav::itertools::fold(
|
||||
[](const auto& match, auto& accum) {
|
||||
auto mlen = match.p_display_value.length();
|
||||
if (mlen > accum) {
|
||||
return mlen;
|
||||
}
|
||||
return accum;
|
||||
},
|
||||
selected_crumb_ref.c_display_value.length());
|
||||
|
||||
if (selected_crumb_ref.c_search_placeholder.size() > width) {
|
||||
width = selected_crumb_ref.c_search_placeholder.size();
|
||||
}
|
||||
this->bc_match_view.set_height(vis_line_t(
|
||||
std::min(this->bc_match_source.get_lines().size() + 1, size_t{4})));
|
||||
this->bc_match_view.set_width(width + 3);
|
||||
this->bc_match_view.set_needs_update();
|
||||
if (selected_value) {
|
||||
this->bc_match_view.set_selection(vis_line_t(selected_value.value()));
|
||||
this->bc_match_view.scroll_selection_into_view();
|
||||
}
|
||||
this->bc_match_view.reload_data();
|
||||
this->set_needs_update();
|
||||
}
|
||||
|
||||
void
|
||||
breadcrumb_curses::focus()
|
||||
{
|
||||
this->bc_focused_crumbs = this->bc_line_source();
|
||||
if (this->bc_focused_crumbs.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->bc_current_search.clear();
|
||||
this->bc_selected_crumb = this->bc_last_selected_crumb.value_or(0);
|
||||
this->reload_data();
|
||||
}
|
||||
|
||||
void
|
||||
breadcrumb_curses::blur()
|
||||
{
|
||||
this->bc_last_selected_crumb = this->bc_selected_crumb;
|
||||
this->bc_focused_crumbs.clear();
|
||||
this->bc_selected_crumb = nonstd::nullopt;
|
||||
this->bc_current_search.clear();
|
||||
this->bc_match_view.set_height(0_vl);
|
||||
this->bc_match_source.clear();
|
||||
this->set_needs_update();
|
||||
}
|
||||
|
||||
bool
|
||||
breadcrumb_curses::handle_key(int ch)
|
||||
{
|
||||
bool retval = false;
|
||||
|
||||
switch (ch) {
|
||||
case KEY_BTAB:
|
||||
case KEY_LEFT:
|
||||
if (this->bc_selected_crumb) {
|
||||
if (this->bc_selected_crumb.value() > 0) {
|
||||
this->bc_selected_crumb
|
||||
= this->bc_selected_crumb.value() - 1;
|
||||
} else {
|
||||
this->bc_selected_crumb
|
||||
= this->bc_focused_crumbs.size() - 1;
|
||||
}
|
||||
this->bc_current_search.clear();
|
||||
this->reload_data();
|
||||
}
|
||||
retval = true;
|
||||
break;
|
||||
case '\t':
|
||||
case KEY_RIGHT:
|
||||
if (this->bc_selected_crumb) {
|
||||
if (this->bc_selected_crumb.value()
|
||||
< this->bc_focused_crumbs.size() - 1) {
|
||||
this->bc_selected_crumb
|
||||
= this->bc_selected_crumb.value() + 1;
|
||||
} else {
|
||||
this->bc_selected_crumb = 0;
|
||||
}
|
||||
this->bc_current_search.clear();
|
||||
this->reload_data();
|
||||
}
|
||||
retval = true;
|
||||
break;
|
||||
case KEY_HOME:
|
||||
this->bc_match_view.set_selection(0_vl);
|
||||
retval = true;
|
||||
break;
|
||||
case KEY_END:
|
||||
this->bc_match_view.set_selection(
|
||||
this->bc_match_view.get_inner_height() - 1_vl);
|
||||
retval = true;
|
||||
break;
|
||||
case KEY_NPAGE:
|
||||
this->bc_match_view.shift_selection(3);
|
||||
retval = true;
|
||||
break;
|
||||
case KEY_PPAGE:
|
||||
this->bc_match_view.shift_selection(-3);
|
||||
retval = true;
|
||||
break;
|
||||
case KEY_UP:
|
||||
this->bc_match_view.shift_selection(-1);
|
||||
retval = true;
|
||||
break;
|
||||
case KEY_DOWN:
|
||||
this->bc_match_view.shift_selection(1);
|
||||
retval = true;
|
||||
break;
|
||||
case 0x7f:
|
||||
case KEY_BACKSPACE:
|
||||
if (!this->bc_current_search.empty()) {
|
||||
this->bc_current_search.pop_back();
|
||||
this->reload_data();
|
||||
}
|
||||
retval = true;
|
||||
break;
|
||||
case KEY_ENTER:
|
||||
case '\r':
|
||||
if (this->bc_selected_crumb) {
|
||||
auto& selected_crumb_ref
|
||||
= this->bc_focused_crumbs[this->bc_selected_crumb.value()];
|
||||
if (this->bc_match_view.get_selection() >= 0
|
||||
&& this->bc_match_view.get_selection()
|
||||
< this->bc_similar_values.size())
|
||||
{
|
||||
const auto& new_value
|
||||
= this->bc_similar_values[this->bc_match_view
|
||||
.get_selection()]
|
||||
.p_key;
|
||||
|
||||
selected_crumb_ref.c_performer(new_value);
|
||||
} else if (!this->bc_current_search.empty()) {
|
||||
if (selected_crumb_ref.c_possible_range) {
|
||||
size_t index;
|
||||
|
||||
if (sscanf(
|
||||
this->bc_current_search.c_str(), "%zu", &index)
|
||||
== 1) {
|
||||
selected_crumb_ref.c_performer(index);
|
||||
}
|
||||
} else {
|
||||
selected_crumb_ref.c_performer(this->bc_current_search);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (isprint(ch)) {
|
||||
this->bc_current_search.push_back(ch);
|
||||
this->reload_data();
|
||||
retval = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!retval) {
|
||||
this->blur();
|
||||
}
|
||||
this->set_needs_update();
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool
|
||||
breadcrumb_curses::search_overlay_source::list_value_for_overlay(
|
||||
const listview_curses& lv,
|
||||
int y,
|
||||
int bottom,
|
||||
vis_line_t line,
|
||||
attr_line_t& value_out)
|
||||
{
|
||||
if (y == 0) {
|
||||
auto* parent = this->sos_parent;
|
||||
auto sel_opt = parent->bc_focused_crumbs
|
||||
| lnav::itertools::nth(parent->bc_selected_crumb);
|
||||
auto exp_input = sel_opt
|
||||
| lnav::itertools::map(&breadcrumb::crumb::c_expected_input)
|
||||
| lnav::itertools::unwrap_or(
|
||||
breadcrumb::crumb::expected_input_t::exact);
|
||||
|
||||
value_out.with_attr_for_all(VC_STYLE.value(A_UNDERLINE));
|
||||
|
||||
if (!parent->bc_current_search.empty()) {
|
||||
value_out = parent->bc_current_search;
|
||||
|
||||
role_t combobox_role = role_t::VCR_STATUS;
|
||||
switch (exp_input) {
|
||||
case breadcrumb::crumb::expected_input_t::exact:
|
||||
if (parent->bc_similar_values.empty()) {
|
||||
combobox_role = role_t::VCR_ALERT_STATUS;
|
||||
}
|
||||
break;
|
||||
case breadcrumb::crumb::expected_input_t::index: {
|
||||
size_t index;
|
||||
|
||||
if (sscanf(parent->bc_current_search.c_str(), "%zu", &index)
|
||||
!= 1
|
||||
|| index < 0
|
||||
|| (index
|
||||
>= (sel_opt
|
||||
| lnav::itertools::map([](const auto& cr) {
|
||||
return cr->c_possible_range.value_or(0);
|
||||
})
|
||||
| lnav::itertools::unwrap_or(size_t{0}))))
|
||||
{
|
||||
combobox_role = role_t::VCR_ALERT_STATUS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case breadcrumb::crumb::expected_input_t::index_or_exact: {
|
||||
size_t index;
|
||||
|
||||
if (sscanf(parent->bc_current_search.c_str(), "%zu", &index)
|
||||
== 1) {
|
||||
if (index < 0
|
||||
|| (index
|
||||
>= (sel_opt
|
||||
| lnav::itertools::map([](const auto& cr) {
|
||||
return cr->c_possible_range.value_or(
|
||||
0);
|
||||
})
|
||||
| lnav::itertools::unwrap_or(size_t{0}))))
|
||||
{
|
||||
combobox_role = role_t::VCR_ALERT_STATUS;
|
||||
}
|
||||
} else if (parent->bc_similar_values.empty()) {
|
||||
combobox_role = role_t::VCR_ALERT_STATUS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case breadcrumb::crumb::expected_input_t::anything:
|
||||
break;
|
||||
}
|
||||
value_out.with_attr_for_all(VC_ROLE.value(combobox_role));
|
||||
return true;
|
||||
}
|
||||
if (parent->bc_selected_crumb) {
|
||||
auto& selected_crumb_ref
|
||||
= parent->bc_focused_crumbs[parent->bc_selected_crumb.value()];
|
||||
|
||||
if (!selected_crumb_ref.c_search_placeholder.empty()) {
|
||||
value_out = selected_crumb_ref.c_search_placeholder;
|
||||
value_out.with_attr_for_all(
|
||||
VC_ROLE.value(role_t::VCR_INACTIVE_STATUS));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Copyright (c) 2022, Timothy Stack
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Timothy Stack nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef lnav_breadcrumb_curses_hh
|
||||
#define lnav_breadcrumb_curses_hh
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "plain_text_source.hh"
|
||||
#include "textview_curses.hh"
|
||||
#include "view_curses.hh"
|
||||
|
||||
class breadcrumb_curses : public view_curses {
|
||||
public:
|
||||
breadcrumb_curses();
|
||||
|
||||
void set_y(int y)
|
||||
{
|
||||
this->bc_y = y;
|
||||
this->bc_match_view.set_y(y + 1);
|
||||
}
|
||||
|
||||
int get_y() const { return this->bc_y; }
|
||||
|
||||
void set_window(WINDOW* win)
|
||||
{
|
||||
this->bc_window = win;
|
||||
this->bc_match_view.set_window(win);
|
||||
}
|
||||
|
||||
void set_line_source(std::function<std::vector<breadcrumb::crumb>()> ls)
|
||||
{
|
||||
this->bc_line_source = std::move(ls);
|
||||
}
|
||||
|
||||
void focus();
|
||||
void blur();
|
||||
|
||||
bool handle_key(int ch);
|
||||
|
||||
void do_update() override;
|
||||
|
||||
void reload_data();
|
||||
|
||||
private:
|
||||
class search_overlay_source : public list_overlay_source {
|
||||
public:
|
||||
bool list_value_for_overlay(const listview_curses& lv,
|
||||
int y,
|
||||
int bottom,
|
||||
vis_line_t line,
|
||||
attr_line_t& value_out) override;
|
||||
|
||||
breadcrumb_curses* sos_parent{nullptr};
|
||||
};
|
||||
|
||||
WINDOW* bc_window{nullptr};
|
||||
std::function<std::vector<breadcrumb::crumb>()> bc_line_source;
|
||||
int bc_y{0};
|
||||
std::vector<breadcrumb::crumb> bc_focused_crumbs;
|
||||
nonstd::optional<size_t> bc_selected_crumb;
|
||||
nonstd::optional<size_t> bc_last_selected_crumb;
|
||||
std::vector<breadcrumb::possibility> bc_possible_values;
|
||||
std::vector<breadcrumb::possibility> bc_similar_values;
|
||||
std::string bc_current_search;
|
||||
|
||||
plain_text_source bc_match_source;
|
||||
search_overlay_source bc_match_search_overlay;
|
||||
textview_curses bc_match_view;
|
||||
};
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,133 @@
|
||||
/**
|
||||
* Copyright (c) 2022, Timothy Stack
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Timothy Stack nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef lnav_itertools_similar_hh
|
||||
#define lnav_itertools_similar_hh
|
||||
|
||||
#include <queue>
|
||||
#include <string>
|
||||
|
||||
#include "base/itertools.hh"
|
||||
#include "fts_fuzzy_match.hh"
|
||||
|
||||
namespace lnav {
|
||||
namespace itertools {
|
||||
|
||||
namespace details {
|
||||
|
||||
template<typename F>
|
||||
struct similar_to {
|
||||
nonstd::optional<F> st_mapper;
|
||||
std::string st_pattern;
|
||||
size_t st_count{5};
|
||||
};
|
||||
|
||||
struct identity {
|
||||
template<typename U>
|
||||
constexpr auto operator()(U&& v) const noexcept
|
||||
-> decltype(std::forward<U>(v))
|
||||
{
|
||||
return std::forward<U>(v);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace details
|
||||
|
||||
template<typename F>
|
||||
inline details::similar_to<F>
|
||||
similar_to(F mapper, std::string pattern, size_t count = 5)
|
||||
{
|
||||
return lnav::itertools::details::similar_to<F>{
|
||||
mapper, std::move(pattern), count};
|
||||
}
|
||||
|
||||
inline auto
|
||||
similar_to(std::string pattern, size_t count = 5)
|
||||
{
|
||||
return similar_to(details::identity{}, std::move(pattern), count);
|
||||
}
|
||||
|
||||
} // namespace itertools
|
||||
} // namespace lnav
|
||||
|
||||
template<typename T, typename F>
|
||||
std::vector<typename T::value_type>
|
||||
operator|(const T& in, const lnav::itertools::details::similar_to<F>& st)
|
||||
{
|
||||
using score_pair = std::pair<int, typename T::value_type>;
|
||||
|
||||
struct score_cmp {
|
||||
bool operator()(const score_pair& lhs, const score_pair& rhs)
|
||||
{
|
||||
return lhs.first > rhs.first;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<std::remove_const_t<typename T::value_type>> retval;
|
||||
|
||||
if (st.st_pattern.empty()) {
|
||||
retval.insert(retval.begin(), in.begin(), in.end());
|
||||
if (retval.size() > st.st_count) {
|
||||
retval.resize(st.st_count);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
std::priority_queue<score_pair, std::vector<score_pair>, score_cmp> pq;
|
||||
|
||||
for (const auto& elem : in) {
|
||||
int score = 0;
|
||||
|
||||
if (!fts::fuzzy_match(
|
||||
st.st_pattern.c_str(),
|
||||
lnav::func::invoke(st.st_mapper.value(), elem).c_str(),
|
||||
score))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (score <= 0) {
|
||||
continue;
|
||||
}
|
||||
pq.push(std::make_pair(score, elem));
|
||||
|
||||
if (pq.size() > st.st_count) {
|
||||
pq.pop();
|
||||
}
|
||||
}
|
||||
|
||||
while (!pq.empty()) {
|
||||
retval.template emplace_back(pq.top().second);
|
||||
pq.pop();
|
||||
}
|
||||
std::reverse(retval.begin(), retval.end());
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,202 @@
|
||||
/**
|
||||
* Copyright (c) 2022, Timothy Stack
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Timothy Stack nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "plain_text_source.hh"
|
||||
|
||||
#include "base/itertools.hh"
|
||||
#include "config.h"
|
||||
|
||||
static std::vector<plain_text_source::text_line>
|
||||
to_text_line(const std::vector<attr_line_t>& lines)
|
||||
{
|
||||
file_off_t off = 0;
|
||||
|
||||
return lines | lnav::itertools::map([&off](const auto& elem) {
|
||||
auto retval = plain_text_source::text_line{
|
||||
off,
|
||||
elem,
|
||||
};
|
||||
|
||||
off += elem.length() + 1;
|
||||
return retval;
|
||||
});
|
||||
}
|
||||
|
||||
plain_text_source::plain_text_source(const std::string& text)
|
||||
{
|
||||
size_t start = 0, end;
|
||||
|
||||
while ((end = text.find('\n', start)) != std::string::npos) {
|
||||
size_t len = (end - start);
|
||||
this->tds_lines.emplace_back(start, text.substr(start, len));
|
||||
start = end + 1;
|
||||
}
|
||||
if (start < text.length()) {
|
||||
this->tds_lines.emplace_back(start, text.substr(start));
|
||||
}
|
||||
this->tds_longest_line = this->compute_longest_line();
|
||||
}
|
||||
|
||||
plain_text_source::plain_text_source(const std::vector<std::string>& text_lines)
|
||||
{
|
||||
this->replace_with(text_lines);
|
||||
}
|
||||
|
||||
plain_text_source::plain_text_source(const std::vector<attr_line_t>& text_lines)
|
||||
: tds_lines(to_text_line(text_lines))
|
||||
{
|
||||
this->tds_longest_line = this->compute_longest_line();
|
||||
}
|
||||
|
||||
plain_text_source&
|
||||
plain_text_source::replace_with(const attr_line_t& text_lines)
|
||||
{
|
||||
this->tds_lines.clear();
|
||||
this->tds_lines = to_text_line(text_lines.split_lines());
|
||||
this->tds_longest_line = this->compute_longest_line();
|
||||
return *this;
|
||||
}
|
||||
|
||||
plain_text_source&
|
||||
plain_text_source::replace_with(const std::vector<std::string>& text_lines)
|
||||
{
|
||||
file_off_t off = 0;
|
||||
for (const auto& str : text_lines) {
|
||||
this->tds_lines.emplace_back(off, str);
|
||||
off += str.length() + 1;
|
||||
}
|
||||
this->tds_longest_line = this->compute_longest_line();
|
||||
return *this;
|
||||
}
|
||||
|
||||
void
|
||||
plain_text_source::clear()
|
||||
{
|
||||
this->tds_lines.clear();
|
||||
this->tds_longest_line = 0;
|
||||
this->tds_text_format = text_format_t::TF_UNKNOWN;
|
||||
}
|
||||
|
||||
plain_text_source&
|
||||
plain_text_source::truncate_to(size_t max_lines)
|
||||
{
|
||||
while (this->tds_lines.size() > max_lines) {
|
||||
this->tds_lines.pop_back();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
size_t
|
||||
plain_text_source::text_line_width(textview_curses& curses)
|
||||
{
|
||||
return this->tds_longest_line;
|
||||
}
|
||||
|
||||
void
|
||||
plain_text_source::text_value_for_line(textview_curses& tc,
|
||||
int row,
|
||||
std::string& value_out,
|
||||
text_sub_source::line_flags_t flags)
|
||||
{
|
||||
value_out = this->tds_lines[row].tl_value.get_string();
|
||||
}
|
||||
|
||||
void
|
||||
plain_text_source::text_attrs_for_line(textview_curses& tc,
|
||||
int line,
|
||||
string_attrs_t& value_out)
|
||||
{
|
||||
value_out = this->tds_lines[line].tl_value.get_attrs();
|
||||
if (this->tds_reverse_selection && tc.is_selectable()
|
||||
&& tc.get_selection() == line)
|
||||
{
|
||||
value_out.emplace_back(line_range{0, -1}, VC_STYLE.value(A_REVERSE));
|
||||
}
|
||||
}
|
||||
|
||||
size_t
|
||||
plain_text_source::text_size_for_line(textview_curses& tc,
|
||||
int row,
|
||||
text_sub_source::line_flags_t flags)
|
||||
{
|
||||
return this->tds_lines[row].tl_value.length();
|
||||
}
|
||||
|
||||
text_format_t
|
||||
plain_text_source::get_text_format() const
|
||||
{
|
||||
return this->tds_text_format;
|
||||
}
|
||||
|
||||
size_t
|
||||
plain_text_source::compute_longest_line()
|
||||
{
|
||||
size_t retval = 0;
|
||||
for (auto& iter : this->tds_lines) {
|
||||
retval = std::max(retval, (size_t) iter.tl_value.length());
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
nonstd::optional<vis_line_t>
|
||||
plain_text_source::line_for_offset(file_off_t off)
|
||||
{
|
||||
struct cmper {
|
||||
bool operator()(const file_off_t& lhs, const text_line& rhs)
|
||||
{
|
||||
return lhs < rhs.tl_offset;
|
||||
}
|
||||
|
||||
bool operator()(const text_line& lhs, const file_off_t& rhs)
|
||||
{
|
||||
return lhs.tl_offset < rhs;
|
||||
}
|
||||
};
|
||||
|
||||
if (this->tds_lines.empty()) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
auto iter = std::lower_bound(
|
||||
this->tds_lines.begin(), this->tds_lines.end(), off, cmper{});
|
||||
if (iter == this->tds_lines.end()) {
|
||||
if (this->tds_lines.back().contains_offset(off)) {
|
||||
return nonstd::make_optional(
|
||||
vis_line_t(std::distance(this->tds_lines.end() - 1, iter)));
|
||||
}
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
if (!iter->contains_offset(off) && iter != this->tds_lines.begin()) {
|
||||
--iter;
|
||||
}
|
||||
|
||||
return nonstd::make_optional(
|
||||
vis_line_t(std::distance(this->tds_lines.begin(), iter)));
|
||||
}
|
@ -0,0 +1,346 @@
|
||||
/**
|
||||
* Origin: https://github.com/ekg/intervaltree
|
||||
*/
|
||||
|
||||
#ifndef __INTERVAL_TREE_H
|
||||
#define __INTERVAL_TREE_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace interval_tree {
|
||||
|
||||
template <class Scalar, typename Value>
|
||||
class Interval {
|
||||
public:
|
||||
Scalar start;
|
||||
Scalar stop;
|
||||
Value value;
|
||||
Interval(const Scalar& s, const Scalar& e, const Value& v)
|
||||
: start(std::min(s, e))
|
||||
, stop(std::max(s, e))
|
||||
, value(v)
|
||||
{}
|
||||
};
|
||||
|
||||
template <class Scalar, typename Value>
|
||||
Value intervalStart(const Interval<Scalar,Value>& i) {
|
||||
return i.start;
|
||||
}
|
||||
|
||||
template <class Scalar, typename Value>
|
||||
Value intervalStop(const Interval<Scalar, Value>& i) {
|
||||
return i.stop;
|
||||
}
|
||||
|
||||
template <class Scalar, typename Value>
|
||||
std::ostream& operator<<(std::ostream& out, const Interval<Scalar, Value>& i) {
|
||||
out << "Interval(" << i.start << ", " << i.stop << "): " << i.value;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <class Scalar, class Value>
|
||||
class IntervalTree {
|
||||
public:
|
||||
typedef Interval<Scalar, Value> interval;
|
||||
typedef std::vector<interval> interval_vector;
|
||||
|
||||
|
||||
struct IntervalStartCmp {
|
||||
bool operator()(const interval& a, const interval& b) {
|
||||
return a.start < b.start;
|
||||
}
|
||||
};
|
||||
|
||||
struct IntervalStopCmp {
|
||||
bool operator()(const interval& a, const interval& b) {
|
||||
return a.stop < b.stop;
|
||||
}
|
||||
};
|
||||
|
||||
IntervalTree()
|
||||
: left(nullptr)
|
||||
, right(nullptr)
|
||||
, center(0)
|
||||
{}
|
||||
|
||||
~IntervalTree() = default;
|
||||
|
||||
std::unique_ptr<IntervalTree> clone() const {
|
||||
return std::unique_ptr<IntervalTree>(new IntervalTree(*this));
|
||||
}
|
||||
|
||||
IntervalTree(const IntervalTree& other)
|
||||
: intervals(other.intervals),
|
||||
left(other.left ? other.left->clone() : nullptr),
|
||||
right(other.right ? other.right->clone() : nullptr),
|
||||
center(other.center)
|
||||
{}
|
||||
|
||||
IntervalTree& operator=(IntervalTree&&) = default;
|
||||
IntervalTree(IntervalTree&&) = default;
|
||||
|
||||
IntervalTree& operator=(const IntervalTree& other) {
|
||||
center = other.center;
|
||||
intervals = other.intervals;
|
||||
left = other.left ? other.left->clone() : nullptr;
|
||||
right = other.right ? other.right->clone() : nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
IntervalTree(
|
||||
interval_vector&& ivals,
|
||||
std::size_t depth = 16,
|
||||
std::size_t minbucket = 64,
|
||||
std::size_t maxbucket = 512,
|
||||
Scalar leftextent = 0,
|
||||
Scalar rightextent = 0)
|
||||
: left(nullptr)
|
||||
, right(nullptr)
|
||||
{
|
||||
--depth;
|
||||
const auto minmaxStop = std::minmax_element(ivals.begin(), ivals.end(),
|
||||
IntervalStopCmp());
|
||||
const auto minmaxStart = std::minmax_element(ivals.begin(), ivals.end(),
|
||||
IntervalStartCmp());
|
||||
if (!ivals.empty()) {
|
||||
center = (minmaxStart.first->start + minmaxStop.second->stop) / 2;
|
||||
}
|
||||
if (leftextent == 0 && rightextent == 0) {
|
||||
// sort intervals by start
|
||||
std::sort(ivals.begin(), ivals.end(), IntervalStartCmp());
|
||||
} else {
|
||||
assert(std::is_sorted(ivals.begin(), ivals.end(), IntervalStartCmp()));
|
||||
}
|
||||
if (depth == 0 || (ivals.size() < minbucket && ivals.size() < maxbucket)) {
|
||||
std::sort(ivals.begin(), ivals.end(), IntervalStartCmp());
|
||||
intervals = std::move(ivals);
|
||||
assert(is_valid().first);
|
||||
return;
|
||||
} else {
|
||||
Scalar leftp = 0;
|
||||
Scalar rightp = 0;
|
||||
|
||||
if (leftextent || rightextent) {
|
||||
leftp = leftextent;
|
||||
rightp = rightextent;
|
||||
} else {
|
||||
leftp = ivals.front().start;
|
||||
rightp = std::max_element(ivals.begin(), ivals.end(),
|
||||
IntervalStopCmp())->stop;
|
||||
}
|
||||
|
||||
interval_vector lefts;
|
||||
interval_vector rights;
|
||||
|
||||
for (typename interval_vector::const_iterator i = ivals.begin();
|
||||
i != ivals.end(); ++i) {
|
||||
const interval& interval = *i;
|
||||
if (interval.stop < center) {
|
||||
lefts.push_back(interval);
|
||||
} else if (interval.start > center) {
|
||||
rights.push_back(interval);
|
||||
} else {
|
||||
assert(interval.start <= center);
|
||||
assert(center <= interval.stop);
|
||||
intervals.push_back(interval);
|
||||
}
|
||||
}
|
||||
|
||||
if (!lefts.empty()) {
|
||||
left.reset(new IntervalTree(std::move(lefts),
|
||||
depth, minbucket, maxbucket,
|
||||
leftp, center));
|
||||
}
|
||||
if (!rights.empty()) {
|
||||
right.reset(new IntervalTree(std::move(rights),
|
||||
depth, minbucket, maxbucket,
|
||||
center, rightp));
|
||||
}
|
||||
}
|
||||
assert(is_valid().first);
|
||||
}
|
||||
|
||||
// Call f on all intervals near the range [start, stop]:
|
||||
template <class UnaryFunction>
|
||||
void visit_near(const Scalar& start, const Scalar& stop, UnaryFunction f) const {
|
||||
if (!intervals.empty() && ! (stop < intervals.front().start)) {
|
||||
for (auto & i : intervals) {
|
||||
f(i);
|
||||
}
|
||||
}
|
||||
if (left && start <= center) {
|
||||
left->visit_near(start, stop, f);
|
||||
}
|
||||
if (right && stop >= center) {
|
||||
right->visit_near(start, stop, f);
|
||||
}
|
||||
}
|
||||
|
||||
// Call f on all intervals crossing pos
|
||||
template <class UnaryFunction>
|
||||
void visit_overlapping(const Scalar& pos, UnaryFunction f) const {
|
||||
visit_overlapping(pos, pos, f);
|
||||
}
|
||||
|
||||
// Call f on all intervals overlapping [start, stop]
|
||||
template <class UnaryFunction>
|
||||
void visit_overlapping(const Scalar& start, const Scalar& stop, UnaryFunction f) const {
|
||||
auto filterF = [&](const interval& interval) {
|
||||
if (interval.stop >= start && interval.start <= stop) {
|
||||
// Only apply f if overlapping
|
||||
f(interval);
|
||||
}
|
||||
};
|
||||
visit_near(start, stop, filterF);
|
||||
}
|
||||
|
||||
// Call f on all intervals contained within [start, stop]
|
||||
template <class UnaryFunction>
|
||||
void visit_contained(const Scalar& start, const Scalar& stop, UnaryFunction f) const {
|
||||
auto filterF = [&](const interval& interval) {
|
||||
if (start <= interval.start && interval.stop <= stop) {
|
||||
f(interval);
|
||||
}
|
||||
};
|
||||
visit_near(start, stop, filterF);
|
||||
}
|
||||
|
||||
interval_vector findOverlapping(const Scalar& start, const Scalar& stop) const {
|
||||
interval_vector result;
|
||||
visit_overlapping(start, stop,
|
||||
[&](const interval& interval) {
|
||||
result.emplace_back(interval);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
interval_vector findContained(const Scalar& start, const Scalar& stop) const {
|
||||
interval_vector result;
|
||||
visit_contained(start, stop,
|
||||
[&](const interval& interval) {
|
||||
result.push_back(interval);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
bool empty() const {
|
||||
if (left && !left->empty()) {
|
||||
return false;
|
||||
}
|
||||
if (!intervals.empty()) {
|
||||
return false;
|
||||
}
|
||||
if (right && !right->empty()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class UnaryFunction>
|
||||
void visit_all(UnaryFunction f) const {
|
||||
if (left) {
|
||||
left->visit_all(f);
|
||||
}
|
||||
std::for_each(intervals.begin(), intervals.end(), f);
|
||||
if (right) {
|
||||
right->visit_all(f);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<Scalar, Scalar> extentBruitForce() const {
|
||||
struct Extent {
|
||||
std::pair<Scalar, Scalar> x = {std::numeric_limits<Scalar>::max(),
|
||||
std::numeric_limits<Scalar>::min() };
|
||||
void operator()(const interval & interval) {
|
||||
x.first = std::min(x.first, interval.start);
|
||||
x.second = std::max(x.second, interval.stop);
|
||||
}
|
||||
};
|
||||
Extent extent;
|
||||
|
||||
visit_all([&](const interval & interval) { extent(interval); });
|
||||
return extent.x;
|
||||
}
|
||||
|
||||
// Check all constraints.
|
||||
// If first is false, second is invalid.
|
||||
std::pair<bool, std::pair<Scalar, Scalar>> is_valid() const {
|
||||
const auto minmaxStop = std::minmax_element(intervals.begin(), intervals.end(),
|
||||
IntervalStopCmp());
|
||||
const auto minmaxStart = std::minmax_element(intervals.begin(), intervals.end(),
|
||||
IntervalStartCmp());
|
||||
|
||||
std::pair<bool, std::pair<Scalar, Scalar>> result = {true, { std::numeric_limits<Scalar>::max(),
|
||||
std::numeric_limits<Scalar>::min() }};
|
||||
if (!intervals.empty()) {
|
||||
result.second.first = std::min(result.second.first, minmaxStart.first->start);
|
||||
result.second.second = std::min(result.second.second, minmaxStop.second->stop);
|
||||
}
|
||||
if (left) {
|
||||
auto valid = left->is_valid();
|
||||
result.first &= valid.first;
|
||||
result.second.first = std::min(result.second.first, valid.second.first);
|
||||
result.second.second = std::min(result.second.second, valid.second.second);
|
||||
if (!result.first) { return result; }
|
||||
if (valid.second.second >= center) {
|
||||
result.first = false;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (right) {
|
||||
auto valid = right->is_valid();
|
||||
result.first &= valid.first;
|
||||
result.second.first = std::min(result.second.first, valid.second.first);
|
||||
result.second.second = std::min(result.second.second, valid.second.second);
|
||||
if (!result.first) { return result; }
|
||||
if (valid.second.first <= center) {
|
||||
result.first = false;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (!std::is_sorted(intervals.begin(), intervals.end(), IntervalStartCmp())) {
|
||||
result.first = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const IntervalTree& itree) {
|
||||
return writeOut(os, itree);
|
||||
}
|
||||
|
||||
friend std::ostream& writeOut(std::ostream& os, const IntervalTree& itree,
|
||||
std::size_t depth = 0) {
|
||||
auto pad = [&]() { for (std::size_t i = 0; i != depth; ++i) { os << ' '; } };
|
||||
pad(); os << "center: " << itree.center << '\n';
|
||||
for (const interval & inter : itree.intervals) {
|
||||
pad(); os << inter << '\n';
|
||||
}
|
||||
if (itree.left) {
|
||||
pad(); os << "left:\n";
|
||||
writeOut(os, *itree.left, depth + 1);
|
||||
} else {
|
||||
pad(); os << "left: nullptr\n";
|
||||
}
|
||||
if (itree.right) {
|
||||
pad(); os << "right:\n";
|
||||
writeOut(os, *itree.right, depth + 1);
|
||||
} else {
|
||||
pad(); os << "right: nullptr\n";
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
private:
|
||||
interval_vector intervals;
|
||||
std::unique_ptr<IntervalTree> left;
|
||||
std::unique_ptr<IntervalTree> right;
|
||||
Scalar center;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2022, Timothy Stack
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Timothy Stack nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef lnav_view_helpers_crumbs_hh
|
||||
#define lnav_view_helpers_crumbs_hh
|
||||
|
||||
#include "breadcrumb_curses.hh"
|
||||
|
||||
std::vector<breadcrumb::crumb> lnav_crumb_source();
|
||||
|
||||
#endif
|
@ -1,13 +1,25 @@
|
||||
[2013-09-06T20:00:48.124] TRACE trace testbork bork bork
|
||||
[2013-09-06T20:00:49.124] INFO Starting up servicebork bork bork
|
||||
[2013-09-06T22:00:49.124] INFO Shutting down servicebork bork bork
|
||||
|
||||
[2013-09-06T20:00:48.124] TRACE trace testbork bork bork
|
||||
|
||||
[2013-09-06T20:00:49.124] INFO Starting up servicebork bork bork
|
||||
|
||||
[2013-09-06T22:00:49.124] INFO Shutting down servicebork bork bork
|
||||
user:mailto:steve@example.com
|
||||
[2013-09-06T22:00:59.124] DEBUG5 Details...bork bork bork
|
||||
[2013-09-06T22:00:59.124] DEBUG4 Details...bork bork bork
|
||||
[2013-09-06T22:00:59.124] DEBUG3 Details...bork bork bork
|
||||
[2013-09-06T22:00:59.124] DEBUG2 Details...bork bork bork
|
||||
[2013-09-06T22:00:59.124] DEBUG Details...bork bork bork
|
||||
[2013-09-06T22:01:49.124] STATS 1 beat per secondbork bork bork
|
||||
[2013-09-06T22:01:49.124] WARNING not looking goodbork bork bork
|
||||
[2013-09-06T22:01:49.124] ERROR looking badbork bork bork
|
||||
[2013-09-06T22:01:49.124] CRITICAL sooo badbork bork bork
|
||||
|
||||
[2013-09-06T22:00:59.124] DEBUG5 Details...bork bork bork
|
||||
|
||||
[2013-09-06T22:00:59.124] DEBUG4 Details...bork bork bork
|
||||
|
||||
[2013-09-06T22:00:59.124] DEBUG3 Details...bork bork bork
|
||||
|
||||
[2013-09-06T22:00:59.124] DEBUG2 Details...bork bork bork
|
||||
|
||||
[2013-09-06T22:00:59.124] DEBUG Details...bork bork bork
|
||||
|
||||
[2013-09-06T22:01:49.124] STATS 1 beat per secondbork bork bork
|
||||
|
||||
[2013-09-06T22:01:49.124] WARNING not looking goodbork bork bork
|
||||
|
||||
[2013-09-06T22:01:49.124] ERROR looking badbork bork bork
|
||||
|
||||
[2013-09-06T22:01:49.124] CRITICAL sooo badbork bork bork
|
||||
|
Loading…
Reference in New Issue