[mouse] flesh out things more

pull/1265/head
Tim Stack 1 month ago
parent 7341dc1f1a
commit 53ab7b14a6

@ -9,6 +9,28 @@ Features:
of archive contents. of archive contents.
* Added `humanize_id` SQL function that colorizes a string using * Added `humanize_id` SQL function that colorizes a string using
ANSI escape codes. ANSI escape codes.
* Added mouse support that can be toggled with `F2` or enabled
by default with: `:config /ui/mouse/mode enabled`. With
mouse support enabled, many of the UI elements will respond to
mouse inputs:
- clicking on the main view will move the cursor to the given
row and dragging will scroll the view as needed;
- shift + dragging in the main view will highlight lines and
then toggle their bookmark status on release;
- clicking in the scroll area will move the view by a page and
dragging the scrollbar will move the view to the given spot;
- clicking on the breadcrumb bar will select a crumb and
selecting a possibility from the popup will move to that
location in the view;
- clicking on portions of the bottom status bar will trigger
a relevant action (e.g. clicking the line number will open
the command prompt with `:goto <current-line>`);
- clicking on the configuration panel tabs (i.e. Files/Filters)
will open the selected panel and clicking parts of the
display in there will perform the relevant action (e.g.
clicking the diamond will enable/disable the file/filter);
- clicking in a prompt will move the cursor to the location.
This is new work, so there are likely to be some glitches.
Interface changes: Interface changes:
* The bar charts in the DB view have now been moved to their * The bar charts in the DB view have now been moved to their
@ -33,6 +55,7 @@ Bug Fixes:
* A crash during initialization on Apple Silicon and MacOS 12 * A crash during initialization on Apple Silicon and MacOS 12
has been fixed. has been fixed.
* A crash when previewing non-text files. * A crash when previewing non-text files.
* Various fixes to make lnav usable as a `PAGER`.
## lnav v0.12.1 ## lnav v0.12.1

@ -732,6 +732,27 @@
}, },
"additionalProperties": false "additionalProperties": false
}, },
"mouse": {
"description": "Mouse-related settings",
"title": "/ui/mouse",
"type": "object",
"properties": {
"mode": {
"title": "/ui/mouse/mode",
"description": "Overall control for mouse support",
"type": "string",
"enum": [
"disabled",
"enabled"
],
"examples": [
"enabled",
"disabled"
]
}
},
"additionalProperties": false
},
"movement": { "movement": {
"description": "Log file cursor movement mode settings", "description": "Log file cursor movement mode settings",
"title": "/ui/movement", "title": "/ui/movement",

@ -113,16 +113,62 @@ To create or customize a theme, consult the :ref:`themes` section.
Cursor Mode (v0.11.2+) Cursor Mode (v0.11.2+)
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
The default mode for scrolling in **lnav** is to move the contents of the The default mode for scrolling in **lnav** is "cursor" mode where
main view when the arrow keys are pressed. Any interactions, such as there is a cursor line in the view that is moved by the arrow keys
jumping to a search hit, are then focused on the top line in the view. and other interactions. Any interactions, such as jumping to a
Alternatively, you can enable "cursor" mode where there is a cursor line search hit, are then focused on that line.
in the view that is moved by the arrow keys and other interactions. You
can enable cursor mode with the following command: Alternatively, you can enable "top" mode where the contents of the
main view are moved when the arrow keys are pressed. Any
interactions, such as jumping to a search hit, are then focused
on the top line in the view. You can change to "top" mode with
the following command:
.. code-block:: lnav
:config /ui/movement/mode top
Mouse Support (v0.12.2+)
^^^^^^^^^^^^^^^^^^^^^^^^
Mouse support can be enabled temporarily by pressing :kbd:`F2`
and can be set as the default by executing the following command:
.. code-block:: lnav .. code-block:: lnav
:config /ui/movement/mode cursor :config /ui/mouse/mode enabled
With mouse support enabled, many of the UI elements will respond to
mouse inputs:
* clicking on the main view will move the cursor to the given
row and dragging will scroll the view as needed;
* shift + dragging in the main view will highlight lines and
then toggle their bookmark status on release;
* clicking in the scroll area will move the view by a page and
dragging the scrollbar will move the view to the given spot;
* clicking on the breadcrumb bar will select a crumb and
selecting a possibility from the popup will move to that
location in the view;
* clicking on portions of the bottom status bar will trigger
a relevant action (e.g. clicking the line number will open
the command prompt with `:goto <current-line>`);
* clicking on the configuration panel tabs (i.e. Files/Filters)
will open the selected panel and clicking parts of the
display in there will perform the relevant action (e.g.
clicking the diamond will enable/disable the file/filter);
* clicking in a prompt will move the cursor to the location.
.. note::
A downside of enabling mouse support is that normal text
selection and copy will no longer work. You can press
:kbd:`F2` to quickly switch back-and-forth. Or, some
terminals have support for switching using a modifier
key, like `iTerm <https://iterm2.com/documentation-preferences-profiles-terminal.html>_`
where pressing :kbd:`Option` will allow you to select
text and copy.
Log Formats Log Formats
^^^^^^^^^^^ ^^^^^^^^^^^

@ -17,7 +17,7 @@
;SELECT printf('\n%d total requests', count(1)) AS msg FROM access_log ;SELECT printf('\n%d total requests', count(1)) AS msg FROM access_log
:echo $msg :echo $msg
;WITH top_paths AS ( ;WITH top_paths AS MATERIALIZED (
SELECT SELECT
cs_uri_stem, cs_uri_stem,
count(1) AS total_hits, count(1) AS total_hits,
@ -28,7 +28,7 @@
GROUP BY cs_uri_stem GROUP BY cs_uri_stem
ORDER BY total_hits DESC ORDER BY total_hits DESC
LIMIT 50), LIMIT 50),
weekly_hits_with_gaps AS ( weekly_hits_with_gaps AS MATERIALIZED (
SELECT timeslice(log_time_msecs, '1w') AS week, SELECT timeslice(log_time_msecs, '1w') AS week,
cs_uri_stem, cs_uri_stem,
count(1) AS weekly_hits count(1) AS weekly_hits

@ -34,6 +34,11 @@
using namespace lnav::roles::literals; using namespace lnav::roles::literals;
void
breadcrumb_curses::no_op_action(breadcrumb_curses&)
{
}
breadcrumb_curses::breadcrumb_curses() breadcrumb_curses::breadcrumb_curses()
{ {
this->bc_match_search_overlay.sos_parent = this; this->bc_match_search_overlay.sos_parent = this;
@ -65,6 +70,7 @@ breadcrumb_curses::do_update()
{ {
this->bc_last_selected_crumb = crumbs.size() - 1; this->bc_last_selected_crumb = crumbs.size() - 1;
} }
this->bc_displayed_crumbs.clear();
attr_line_t crumbs_line; attr_line_t crumbs_line;
for (const auto& crumb : crumbs) { for (const auto& crumb : crumbs) {
auto accum_width = crumbs_line.column_width(); auto accum_width = crumbs_line.column_width();
@ -78,17 +84,21 @@ breadcrumb_curses::do_update()
accum_width = 2; accum_width = 2;
} }
line_range crumb_range;
crumb_range.lr_start = (int) crumbs_line.length();
crumbs_line.append(crumb.c_display_value); crumbs_line.append(crumb.c_display_value);
crumb_range.lr_end = (int) crumbs_line.length();
if (is_selected) { if (is_selected) {
sel_crumb_offset = accum_width; sel_crumb_offset = accum_width;
crumbs_line.get_attrs().emplace_back( crumbs_line.get_attrs().emplace_back(
line_range{ crumb_range, VC_STYLE.template value(text_attrs{A_REVERSE}));
(int) (crumbs_line.length()
- crumb.c_display_value.length()),
(int) crumbs_line.length(),
},
VC_STYLE.template value(text_attrs{A_REVERSE}));
} }
this->bc_displayed_crumbs.emplace_back(
line_range{(int) accum_width,
(int) (accum_width + elem_width),
line_range::unit::codepoint},
crumb_index);
crumb_index += 1; crumb_index += 1;
crumbs_line.append(" \uff1a"_breadcrumb); crumbs_line.append(" \uff1a"_breadcrumb);
} }
@ -168,6 +178,9 @@ breadcrumb_curses::reload_data()
this->bc_match_view.set_needs_update(); this->bc_match_view.set_needs_update();
this->bc_match_view.set_selection( this->bc_match_view.set_selection(
vis_line_t(selected_value.value_or(-1_vl))); vis_line_t(selected_value.value_or(-1_vl)));
if (selected_value) {
this->bc_match_view.set_top(vis_line_t(selected_value.value()));
}
this->bc_match_view.reload_data(); this->bc_match_view.reload_data();
this->set_needs_update(); this->set_needs_update();
} }
@ -454,3 +467,77 @@ breadcrumb_curses::search_overlay_source::list_static_overlay(
return false; return false;
} }
bool
breadcrumb_curses::handle_mouse(mouse_event& me)
{
if (me.me_state == mouse_button_state_t::BUTTON_STATE_PRESSED
&& this->bc_focused_crumbs.empty())
{
this->focus();
this->on_focus(*this);
this->do_update();
}
auto find_res = this->bc_displayed_crumbs
| lnav::itertools::find_if([&me](const auto& elem) {
return me.me_button == mouse_button_t::BUTTON_LEFT
&& elem.dc_range.contains(me.me_x);
});
if (!this->bc_focused_crumbs.empty()) {
if (me.me_y > 0 || !find_res
|| find_res.value()->dc_index == this->bc_selected_crumb)
{
if (view_curses::handle_mouse(me)) {
if (me.me_y > 0
&& (me.me_state
== mouse_button_state_t::BUTTON_STATE_DOUBLE_CLICK
|| me.me_state
== mouse_button_state_t::BUTTON_STATE_RELEASED))
{
this->perform_selection(perform_behavior_t::if_different);
this->blur();
this->reload_data();
this->on_blur(*this);
this->bc_initial_mouse_event = true;
}
return true;
}
}
if (!this->bc_initial_mouse_event
&& me.me_state == mouse_button_state_t::BUTTON_STATE_RELEASED
&& me.me_y == 0 && find_res
&& find_res.value()->dc_index == this->bc_selected_crumb.value())
{
this->blur();
this->reload_data();
this->on_blur(*this);
this->bc_initial_mouse_event = true;
return true;
}
}
if (me.me_state == mouse_button_state_t::BUTTON_STATE_RELEASED) {
this->bc_initial_mouse_event = false;
}
if (me.me_y != 0) {
return true;
}
if (find_res) {
auto crumb_index = find_res.value()->dc_index;
if (this->bc_selected_crumb) {
this->blur();
this->focus();
this->reload_data();
this->bc_selected_crumb = crumb_index;
this->bc_current_search.clear();
this->reload_data();
}
}
return true;
}

@ -40,6 +40,8 @@
class breadcrumb_curses : public view_curses { class breadcrumb_curses : public view_curses {
public: public:
using action = std::function<void(breadcrumb_curses&)>;
breadcrumb_curses(); breadcrumb_curses();
void set_window(WINDOW* win) void set_window(WINDOW* win)
@ -53,6 +55,8 @@ public:
this->bc_line_source = std::move(ls); this->bc_line_source = std::move(ls);
} }
bool handle_mouse(mouse_event& me) override;
void focus(); void focus();
void blur(); void blur();
@ -62,6 +66,11 @@ public:
void reload_data(); void reload_data();
static void no_op_action(breadcrumb_curses&);
action on_focus{no_op_action};
action on_blur{no_op_action};
private: private:
class search_overlay_source : public list_overlay_source { class search_overlay_source : public list_overlay_source {
public: public:
@ -92,6 +101,19 @@ private:
plain_text_source bc_match_source; plain_text_source bc_match_source;
search_overlay_source bc_match_search_overlay; search_overlay_source bc_match_search_overlay;
textview_curses bc_match_view; textview_curses bc_match_view;
struct displayed_crumb {
displayed_crumb(line_range range, size_t index)
: dc_range(range), dc_index(index)
{
}
line_range dc_range;
size_t dc_index{0};
};
std::vector<displayed_crumb> bc_displayed_crumbs;
bool bc_initial_mouse_event{true};
}; };
#endif #endif

@ -30,6 +30,7 @@
#include "files_sub_source.hh" #include "files_sub_source.hh"
#include "base/ansi_scrubber.hh" #include "base/ansi_scrubber.hh"
#include "base/attr_line.builder.hh"
#include "base/fs_util.hh" #include "base/fs_util.hh"
#include "base/humanize.hh" #include "base/humanize.hh"
#include "base/humanize.network.hh" #include "base/humanize.network.hh"
@ -40,6 +41,8 @@
#include "mapbox/variant.hpp" #include "mapbox/variant.hpp"
#include "sql_util.hh" #include "sql_util.hh"
using namespace lnav::roles::literals;
namespace files_model { namespace files_model {
files_list_selection files_list_selection
from_selection(vis_line_t sel_vis) from_selection(vis_line_t sel_vis)
@ -243,26 +246,45 @@ files_sub_source::text_value_for_line(textview_curses& tc,
std::string& value_out, std::string& value_out,
text_sub_source::line_flags_t flags) text_sub_source::line_flags_t flags)
{ {
bool selected
= lnav_data.ld_mode == ln_mode_t::FILES && line == tc.get_selection();
const auto dim = tc.get_dimensions(); const auto dim = tc.get_dimensions();
const auto& fc = lnav_data.ld_active_files; const auto& fc = lnav_data.ld_active_files;
auto filename_width auto filename_width
= std::min(fc.fc_largest_path_length, = std::min(fc.fc_largest_path_length,
std::max((size_t) 40, (size_t) dim.second - 30)); std::max((size_t) 40, (size_t) dim.second - 30));
this->fss_curr_line.clear();
auto& al = this->fss_curr_line;
attr_line_builder alb(al);
if (selected) {
al.append(" ", VC_GRAPHIC.value(ACS_RARROW));
} else {
al.append(" ");
}
{ {
safe::ReadAccess<safe_name_to_errors> errs(*fc.fc_name_to_errors); safe::ReadAccess<safe_name_to_errors> errs(*fc.fc_name_to_errors);
if (line < errs->size()) { if (line < errs->size()) {
auto iter = errs->begin(); auto iter = std::next(errs->begin(), line);
std::advance(iter, line);
auto path = ghc::filesystem::path(iter->first); auto path = ghc::filesystem::path(iter->first);
auto fn = fmt::to_string(path.filename()); auto fn = fmt::to_string(path.filename());
truncate_to(fn, filename_width); truncate_to(fn, filename_width);
value_out = fmt::format(FMT_STRING(" {:<{}} {}"), al.append(" ");
fn, {
filename_width, auto ag = alb.with_attr(VC_ROLE.value(role_t::VCR_ERROR));
iter->second.fei_description);
al.appendf(FMT_STRING("{:<{}}"), fn, filename_width);
}
al.append(" ").append(iter->second.fei_description);
if (selected) {
al.with_attr_for_all(
VC_ROLE.value(role_t::VCR_DISABLED_FOCUSED));
}
value_out = al.get_string();
return; return;
} }
@ -270,23 +292,36 @@ files_sub_source::text_value_for_line(textview_curses& tc,
} }
if (line < fc.fc_other_files.size()) { if (line < fc.fc_other_files.size()) {
auto iter = fc.fc_other_files.begin(); auto iter = std::next(fc.fc_other_files.begin(), line);
std::advance(iter, line);
auto path = ghc::filesystem::path(iter->first); auto path = ghc::filesystem::path(iter->first);
auto fn = fmt::to_string(path); auto fn = fmt::to_string(path);
truncate_to(fn, filename_width); truncate_to(fn, filename_width);
value_out = fmt::format(FMT_STRING(" {:<{}} {:14} {}"), al.append(" ");
fn, {
filename_width, auto ag = alb.with_attr(VC_ROLE.value(role_t::VCR_FILE));
iter->second.ofd_format,
iter->second.ofd_description); al.appendf(FMT_STRING("{:<{}}"), fn, filename_width);
}
al.append(" ")
.appendf(FMT_STRING("{:14}"), iter->second.ofd_format)
.append(" ")
.append(iter->second.ofd_description);
if (selected) {
al.with_attr_for_all(VC_ROLE.value(role_t::VCR_DISABLED_FOCUSED));
}
if (line == fc.fc_other_files.size() - 1) {
al.with_attr_for_all(VC_STYLE.value(text_attrs{A_UNDERLINE}));
}
value_out = al.get_string();
return; return;
} }
line -= fc.fc_other_files.size(); line -= fc.fc_other_files.size();
const auto& lf = fc.fc_files[line]; const auto& lf = fc.fc_files[line];
auto ld_opt = lnav_data.ld_log_source.find_data(lf);
auto fn = fmt::to_string(ghc::filesystem::path(lf->get_unique_path())); auto fn = fmt::to_string(ghc::filesystem::path(lf->get_unique_path()));
char start_time[64] = "", end_time[64] = ""; char start_time[64] = "", end_time[64] = "";
std::vector<std::string> file_notes; std::vector<std::string> file_notes;
@ -299,94 +334,47 @@ files_sub_source::text_value_for_line(textview_curses& tc,
for (const auto& pair : lf->get_notes()) { for (const auto& pair : lf->get_notes()) {
file_notes.push_back(pair.second); file_notes.push_back(pair.second);
} }
value_out = fmt::format(FMT_STRING(" {:<{}} {:>8} {} \u2014 {} {}"),
fn,
filename_width,
humanize::file_size(lf->get_index_size(),
humanize::alignment::columnar),
start_time,
end_time,
fmt::join(file_notes, "; "));
this->fss_last_line_len
= filename_width + 23 + strlen(start_time) + strlen(end_time);
}
void
files_sub_source::text_attrs_for_line(textview_curses& tc,
int line,
string_attrs_t& value_out)
{
bool selected
= lnav_data.ld_mode == ln_mode_t::FILES && line == tc.get_selection();
const auto& fc = lnav_data.ld_active_files;
const auto dim = tc.get_dimensions();
auto filename_width
= std::min(fc.fc_largest_path_length,
std::max((size_t) 40, (size_t) dim.second - 30));
if (selected) {
value_out.emplace_back(line_range{0, 1}, VC_GRAPHIC.value(ACS_RARROW));
}
{ al.append(" ");
safe::ReadAccess<safe_name_to_errors> errs(*fc.fc_name_to_errors); if (ld_opt) {
if (ld_opt.value()->ld_visible) {
if (line < errs->size()) { al.append("\u25c6"_ok);
if (selected) { } else {
value_out.emplace_back( al.append("\u25c7"_comment);
line_range{0, -1},
VC_ROLE.value(role_t::VCR_DISABLED_FOCUSED));
}
value_out.emplace_back(line_range{4 + (int) filename_width, -1},
VC_ROLE_FG.value(role_t::VCR_ERROR));
return;
} }
line -= errs->size(); } else {
al.append("\u25c6"_comment);
} }
al.append(" ");
al.appendf(FMT_STRING("{:<{}}"), fn, filename_width);
al.append(" ");
{
auto ag = alb.with_attr(VC_ROLE.value(role_t::VCR_NUMBER));
if (line < fc.fc_other_files.size()) { al.appendf(FMT_STRING("{:>8}"),
if (selected) { humanize::file_size(lf->get_index_size(),
value_out.emplace_back(line_range{0, -1}, humanize::alignment::columnar));
VC_ROLE.value(role_t::VCR_DISABLED_FOCUSED));
}
if (line == fc.fc_other_files.size() - 1) {
value_out.emplace_back(line_range{0, -1},
VC_STYLE.value(text_attrs{A_UNDERLINE}));
}
return;
} }
al.append(" ")
line -= fc.fc_other_files.size(); .append(start_time)
.append(" \u2014 ")
.append(end_time)
.appendf(FMT_STRING("{}"), fmt::join(file_notes, "; "));
if (selected) { if (selected) {
value_out.emplace_back(line_range{0, -1}, al.with_attr_for_all(VC_ROLE.value(role_t::VCR_FOCUSED));
VC_ROLE.value(role_t::VCR_FOCUSED));
} }
auto& lss = lnav_data.ld_log_source; value_out = al.get_string();
auto& lf = fc.fc_files[line]; this->fss_last_line_len
auto ld_opt = lss.find_data(lf); = filename_width + 23 + strlen(start_time) + strlen(end_time);
}
chtype visible = ACS_DIAMOND;
if (ld_opt && !ld_opt.value()->ld_visible) {
visible = ' ';
}
value_out.emplace_back(line_range{2, 3}, VC_GRAPHIC.value(visible));
if (visible == ACS_DIAMOND) {
value_out.emplace_back(line_range{2, 3},
VC_FOREGROUND.value(COLOR_GREEN));
}
auto lr = line_range{
(int) filename_width + 3 + 4,
(int) filename_width + 3 + 10,
};
value_out.emplace_back(lr, VC_STYLE.value(text_attrs{A_BOLD}));
lr.lr_start = this->fss_last_line_len; void
lr.lr_end = -1; files_sub_source::text_attrs_for_line(textview_curses& tc,
value_out.emplace_back(lr, VC_FOREGROUND.value(COLOR_YELLOW)); int line,
string_attrs_t& value_out)
{
value_out = this->fss_curr_line.get_attrs();
} }
size_t size_t
@ -450,3 +438,16 @@ files_overlay_source::list_static_overlay(const listview_curses& lv,
return false; return false;
} }
bool
files_sub_source::text_handle_mouse(textview_curses& tc, mouse_event& me)
{
if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 1, 3)) {
this->list_input_handle_key(tc, ' ');
}
if (me.is_double_click_in(mouse_button_t::BUTTON_LEFT, line_range{4, -1})) {
this->list_input_handle_key(tc, '\r');
}
return false;
}

@ -35,7 +35,8 @@
class files_sub_source class files_sub_source
: public text_sub_source : public text_sub_source
, public list_input_delegate { , public list_input_delegate
, public text_delegate {
public: public:
files_sub_source(); files_sub_source();
@ -60,7 +61,10 @@ public:
int line, int line,
line_flags_t raw) override; line_flags_t raw) override;
bool text_handle_mouse(textview_curses& tc, mouse_event& me) override;
size_t fss_last_line_len{0}; size_t fss_last_line_len{0};
attr_line_t fss_curr_line;
}; };
struct files_overlay_source : public list_overlay_source { struct files_overlay_source : public list_overlay_source {

@ -54,6 +54,8 @@ filter_status_source::filter_status_source()
this->tss_fields[TSF_TITLE].set_role(role_t::VCR_STATUS_TITLE); this->tss_fields[TSF_TITLE].set_role(role_t::VCR_STATUS_TITLE);
this->tss_fields[TSF_TITLE].set_value(" " ANSI_ROLE("T") "ext Filters ", this->tss_fields[TSF_TITLE].set_value(" " ANSI_ROLE("T") "ext Filters ",
role_t::VCR_STATUS_TITLE_HOTKEY); role_t::VCR_STATUS_TITLE_HOTKEY);
this->tss_fields[TSF_TITLE].on_click
= [](status_field&) { set_view_mode(ln_mode_t::FILTER); };
this->tss_fields[TSF_STITCH_TITLE].set_width(2); this->tss_fields[TSF_STITCH_TITLE].set_width(2);
this->tss_fields[TSF_STITCH_TITLE].set_stitch_value( this->tss_fields[TSF_STITCH_TITLE].set_stitch_value(
@ -73,6 +75,8 @@ filter_status_source::filter_status_source()
role_t::VCR_STATUS_DISABLED_TITLE); role_t::VCR_STATUS_DISABLED_TITLE);
this->tss_fields[TSF_FILES_TITLE].set_value(" " ANSI_ROLE("F") "iles ", this->tss_fields[TSF_FILES_TITLE].set_value(" " ANSI_ROLE("F") "iles ",
role_t::VCR_STATUS_HOTKEY); role_t::VCR_STATUS_HOTKEY);
this->tss_fields[TSF_FILES_TITLE].on_click
= [](status_field&) { set_view_mode(ln_mode_t::FILES); };
this->tss_fields[TSF_FILES_RIGHT_STITCH].set_width(2); this->tss_fields[TSF_FILES_RIGHT_STITCH].set_width(2);
this->tss_fields[TSF_FILES_RIGHT_STITCH].set_stitch_value( this->tss_fields[TSF_FILES_RIGHT_STITCH].set_stitch_value(

@ -29,6 +29,7 @@
#include "filter_sub_source.hh" #include "filter_sub_source.hh"
#include "base/attr_line.builder.hh"
#include "base/enum_util.hh" #include "base/enum_util.hh"
#include "base/func_util.hh" #include "base/func_util.hh"
#include "base/opt_util.hh" #include "base/opt_util.hh"
@ -70,6 +71,14 @@ filter_sub_source::filter_sub_source(std::shared_ptr<readline_curses> editor)
this->fss_match_view.set_default_role(role_t::VCR_POPUP); this->fss_match_view.set_default_role(role_t::VCR_POPUP);
} }
void
filter_sub_source::register_view(textview_curses* tc)
{
text_sub_source::register_view(tc);
tc->add_child_view(this->fss_editor.get());
tc->add_child_view(&this->fss_match_view);
}
bool bool
filter_sub_source::list_input_handle_key(listview_curses& lv, int ch) filter_sub_source::list_input_handle_key(listview_curses& lv, int ch)
{ {
@ -169,6 +178,7 @@ filter_sub_source::list_input_handle_key(listview_curses& lv, int ch)
lv.reload_data(); lv.reload_data();
this->fss_editing = true; this->fss_editing = true;
this->tss_view->vc_enabled = false;
add_view_text_possibilities(this->fss_editor.get(), add_view_text_possibilities(this->fss_editor.get(),
filter_lang_t::REGEX, filter_lang_t::REGEX,
@ -204,6 +214,7 @@ filter_sub_source::list_input_handle_key(listview_curses& lv, int ch)
lv.reload_data(); lv.reload_data();
this->fss_editing = true; this->fss_editing = true;
this->tss_view->vc_enabled = false;
add_view_text_possibilities(this->fss_editor.get(), add_view_text_possibilities(this->fss_editor.get(),
filter_lang_t::REGEX, filter_lang_t::REGEX,
@ -233,6 +244,7 @@ filter_sub_source::list_input_handle_key(listview_curses& lv, int ch)
auto tf = *(fs.begin() + lv.get_selection()); auto tf = *(fs.begin() + lv.get_selection());
this->fss_editing = true; this->fss_editing = true;
this->tss_view->vc_enabled = false;
auto tq = tf->get_lang() == filter_lang_t::SQL auto tq = tf->get_lang() == filter_lang_t::SQL
? text_quoting::sql ? text_quoting::sql
@ -315,17 +327,34 @@ filter_sub_source::text_value_for_line(textview_curses& tc,
auto* tss = top_view->get_sub_source(); auto* tss = top_view->get_sub_source();
auto& fs = tss->get_filters(); auto& fs = tss->get_filters();
auto tf = *(fs.begin() + line); auto tf = *(fs.begin() + line);
bool selected
= lnav_data.ld_mode == ln_mode_t::FILTER && line == tc.get_selection();
value_out = " "; this->fss_curr_line.clear();
auto& al = this->fss_curr_line;
attr_line_builder alb(al);
if (selected) {
al.append(" ", VC_GRAPHIC.value(ACS_RARROW));
} else {
al.append(" ");
}
al.append(" ");
if (tf->is_enabled()) {
al.append("\u25c6"_ok);
} else {
al.append("\u25c7"_comment);
}
al.append(" ");
switch (tf->get_type()) { switch (tf->get_type()) {
case text_filter::INCLUDE: case text_filter::INCLUDE:
value_out.append(" IN "); al.append(" ").append(lnav::roles::ok("IN")).append(" ");
break; break;
case text_filter::EXCLUDE: case text_filter::EXCLUDE:
if (tf->get_lang() == filter_lang_t::REGEX) { if (tf->get_lang() == filter_lang_t::REGEX) {
value_out.append("OUT "); al.append(lnav::roles::error("OUT")).append(" ");
} else { } else {
value_out.append(" "); al.append(" ");
} }
break; break;
default: default:
@ -333,60 +362,19 @@ filter_sub_source::text_value_for_line(textview_curses& tc,
break; break;
} }
if (this->fss_editing && line == tc.get_selection()) { {
fmt::format_to( auto ag = alb.with_attr(VC_ROLE.value(role_t::VCR_NUMBER));
std::back_inserter(value_out), FMT_STRING("{:>9} hits | "), "-"); if (this->fss_editing && line == tc.get_selection()) {
} else { alb.appendf(FMT_STRING("{:>9}"), "-");
fmt::format_to(std::back_inserter(value_out), } else {
FMT_STRING("{:>9L} hits | "), alb.appendf(FMT_STRING("{:>9}"),
tss->get_filtered_count_for(tf->get_index())); tss->get_filtered_count_for(tf->get_index()));
} }
value_out.append(tf->get_id());
}
void
filter_sub_source::text_attrs_for_line(textview_curses& tc,
int line,
string_attrs_t& value_out)
{
textview_curses* top_view = *lnav_data.ld_view_stack.top();
text_sub_source* tss = top_view->get_sub_source();
filter_stack& fs = tss->get_filters();
auto tf = *(fs.begin() + line);
bool selected
= lnav_data.ld_mode == ln_mode_t::FILTER && line == tc.get_selection();
if (selected) {
value_out.emplace_back(line_range{0, 1}, VC_GRAPHIC.value(ACS_RARROW));
}
chtype enabled = tf->is_enabled() ? ACS_DIAMOND : ' ';
line_range lr{2, 3};
value_out.emplace_back(lr, VC_GRAPHIC.value(enabled));
if (tf->is_enabled()) {
value_out.emplace_back(lr, VC_FOREGROUND.value(COLOR_GREEN));
}
if (selected) {
value_out.emplace_back(line_range{0, -1},
VC_ROLE.value(role_t::VCR_FOCUSED));
} }
role_t fg_role = tf->get_type() == text_filter::INCLUDE ? role_t::VCR_OK al.append(" hits ").append("|", VC_GRAPHIC.value(ACS_VLINE)).append(" ");
: role_t::VCR_ERROR;
value_out.emplace_back(line_range{4, 7}, VC_ROLE.value(fg_role));
value_out.emplace_back(line_range{4, 7},
VC_STYLE.value(text_attrs{A_BOLD}));
value_out.emplace_back(line_range{8, 17},
VC_STYLE.value(text_attrs{A_BOLD}));
value_out.emplace_back(line_range{23, 24}, VC_GRAPHIC.value(ACS_VLINE));
attr_line_t content{tf->get_id()}; attr_line_t content{tf->get_id()};
auto& content_attrs = content.get_attrs();
switch (tf->get_lang()) { switch (tf->get_lang()) {
case filter_lang_t::REGEX: case filter_lang_t::REGEX:
readline_regex_highlighter(content, content.length()); readline_regex_highlighter(content, content.length());
@ -397,10 +385,21 @@ filter_sub_source::text_attrs_for_line(textview_curses& tc,
case filter_lang_t::NONE: case filter_lang_t::NONE:
break; break;
} }
al.append(content);
if (selected) {
al.with_attr_for_all(VC_ROLE.value(role_t::VCR_FOCUSED));
}
value_out = al.get_string();
}
shift_string_attrs(content_attrs, 0, 25); void
value_out.insert( filter_sub_source::text_attrs_for_line(textview_curses& tc,
value_out.end(), content_attrs.begin(), content_attrs.end()); int line,
string_attrs_t& value_out)
{
value_out = this->fss_curr_line.get_attrs();
} }
size_t size_t
@ -592,6 +591,7 @@ filter_sub_source::rl_perform(readline_curses* rc)
lnav_data.ld_log_source.set_preview_sql_filter(nullptr); lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
lnav_data.ld_filter_help_status_source.fss_prompt.clear(); lnav_data.ld_filter_help_status_source.fss_prompt.clear();
this->fss_editing = false; this->fss_editing = false;
this->tss_view->vc_enabled = true;
this->fss_editor->set_visible(false); this->fss_editor->set_visible(false);
top_view->reload_data(); top_view->reload_data();
this->tss_view->reload_data(); this->tss_view->reload_data();
@ -615,6 +615,7 @@ filter_sub_source::rl_abort(readline_curses* rc)
this->tss_view->reload_data(); this->tss_view->reload_data();
this->fss_editor->set_visible(false); this->fss_editor->set_visible(false);
this->fss_editing = false; this->fss_editing = false;
this->tss_view->vc_enabled = true;
this->tss_view->set_needs_update(); this->tss_view->set_needs_update();
tf->set_enabled(this->fss_filter_state); tf->set_enabled(this->fss_filter_state);
tss->text_filters_changed(); tss->text_filters_changed();
@ -680,3 +681,22 @@ filter_sub_source::list_input_handle_scroll_out(listview_curses& lv)
lnav_data.ld_mode = ln_mode_t::PAGING; lnav_data.ld_mode = ln_mode_t::PAGING;
lnav_data.ld_filter_view.reload_data(); lnav_data.ld_filter_view.reload_data();
} }
bool
filter_sub_source::text_handle_mouse(textview_curses& tc, mouse_event& me)
{
if (this->fss_editing) {
return true;
}
if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 1, 3)) {
this->list_input_handle_key(tc, ' ');
}
if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 4, 7)) {
this->list_input_handle_key(tc, 't');
}
if (me.is_double_click_in(mouse_button_t::BUTTON_LEFT, line_range{25, -1}))
{
this->list_input_handle_key(tc, '\r');
}
return true;
}

@ -37,7 +37,8 @@
class filter_sub_source class filter_sub_source
: public text_sub_source : public text_sub_source
, public list_input_delegate { , public list_input_delegate
, public text_delegate {
public: public:
filter_sub_source(std::shared_ptr<readline_curses> editor); filter_sub_source(std::shared_ptr<readline_curses> editor);
@ -52,6 +53,8 @@ public:
void list_input_handle_scroll_out(listview_curses& lv) override; void list_input_handle_scroll_out(listview_curses& lv) override;
void register_view(textview_curses* tc) override;
size_t text_line_count() override; size_t text_line_count() override;
size_t text_line_width(textview_curses& curses) override; size_t text_line_width(textview_curses& curses) override;
@ -69,6 +72,8 @@ public:
int line, int line,
line_flags_t raw) override; line_flags_t raw) override;
bool text_handle_mouse(textview_curses& tc, mouse_event& me) override;
void rl_change(readline_curses* rc); void rl_change(readline_curses* rc);
void rl_perform(readline_curses* rc); void rl_perform(readline_curses* rc);
@ -84,6 +89,7 @@ public:
std::shared_ptr<readline_curses> fss_editor; std::shared_ptr<readline_curses> fss_editor;
plain_text_source fss_match_source; plain_text_source fss_match_source;
textview_curses fss_match_view; textview_curses fss_match_view;
attr_line_t fss_curr_line;
bool fss_editing{false}; bool fss_editing{false};
bool fss_filter_state{false}; bool fss_filter_state{false};

@ -313,7 +313,7 @@ If you are using Xterm, or a compatible terminal, you can use the mouse to
mark lines of text and move the view by grabbing the scrollbar. mark lines of text and move the view by grabbing the scrollbar.
NOTE: You need to manually enable this feature by setting the LNAV_EXP NOTE: You need to manually enable this feature by setting the LNAV_EXP
environment variable to "mouse". F2 toggles mouse support. environment variable to "mouse". `F2` toggles mouse support.
## Log Analysis ## Log Analysis

@ -50,6 +50,14 @@ listview_curses::listview_curses() : lv_scroll(noop_func{}) {}
bool bool
listview_curses::contains(int x, int y) const listview_curses::contains(int x, int y) const
{ {
if (!this->vc_visible) {
return false;
}
if (view_curses::contains(x, y)) {
return true;
}
auto dim = this->get_dimensions(); auto dim = this->get_dimensions();
if (this->vc_x <= x && x < this->vc_x + dim.second && this->vc_y <= y if (this->vc_x <= x && x < this->vc_x + dim.second && this->vc_y <= y
@ -76,26 +84,20 @@ listview_curses::update_top_from_selection()
this->set_top(0_vl); this->set_top(0_vl);
} else if (this->lv_sync_selection_and_top) { } else if (this->lv_sync_selection_and_top) {
this->set_top(this->lv_selection); this->set_top(this->lv_selection);
} else if (this->lv_selection == this->get_inner_height() - 1_vl) {
this->set_top(this->get_top_for_last_row());
} else if (height <= this->lv_tail_space) { } else if (height <= this->lv_tail_space) {
this->set_top(this->lv_selection); this->set_top(this->lv_selection);
} else if (this->lv_selection } else if (this->lv_selection > (this->lv_top + height - 1_vl)) {
>= (this->lv_top + height - this->lv_tail_space - 1_vl)) auto diff = this->lv_selection - (this->lv_top + height - 1_vl);
{
auto diff = this->lv_selection
- (this->lv_top + height - this->lv_tail_space - 1_vl);
if (height < 10 || diff < (height / 8_vl)) { if (height < 10 || diff < (height / 8_vl)) {
// for small differences between the bottom and the // for small differences between the bottom and the
// selection, just move a little bit. // selection, just move a little bit.
this->set_top( this->set_top(this->lv_selection - height + 1_vl, true);
this->lv_selection - height + 1_vl + this->lv_tail_space, true);
} else { } else {
// for large differences, put the focus in the middle // for large differences, put the focus in the middle
this->set_top(this->lv_selection - height / 2_vl, true); this->set_top(this->lv_selection - height / 2_vl, true);
} }
} else if (this->lv_selection <= this->lv_top) { } else if (this->lv_selection < this->lv_top) {
auto diff = this->lv_top - this->lv_selection; auto diff = this->lv_top - this->lv_selection;
if (this->lv_selection > 0 && (height < 10 || diff < (height / 8_vl))) { if (this->lv_selection > 0 && (height < 10 || diff < (height / 8_vl))) {
@ -126,9 +128,10 @@ listview_curses::reload_data()
} }
if (this->lv_selectable) { if (this->lv_selectable) {
if (this->get_inner_height() == 0) { if (this->get_inner_height() == 0) {
this->set_selection(-1_vl); this->set_selection_without_context(-1_vl);
} else if (this->lv_selection >= this->get_inner_height()) { } else if (this->lv_selection >= this->get_inner_height()) {
this->set_selection(this->get_inner_height() - 1_vl); this->set_selection_without_context(this->get_inner_height()
- 1_vl);
} else { } else {
auto curr_sel = this->get_selection(); auto curr_sel = this->get_selection();
@ -136,7 +139,7 @@ listview_curses::reload_data()
curr_sel = 0_vl; curr_sel = 0_vl;
} }
this->lv_selection = -1_vl; this->lv_selection = -1_vl;
this->set_selection(curr_sel); this->set_selection_without_context(curr_sel);
} }
this->update_top_from_selection(); this->update_top_from_selection();
@ -425,7 +428,6 @@ listview_curses::do_update()
} }
size_t row_count = this->get_inner_height(); size_t row_count = this->get_inner_height();
size_t blank_rows = 0;
row = this->lv_top; row = this->lv_top;
bottom = y + height; bottom = y + height;
std::vector<attr_line_t> rows( std::vector<attr_line_t> rows(
@ -586,14 +588,11 @@ listview_curses::do_update()
this->lv_display_lines.push_back(empty_space{}); this->lv_display_lines.push_back(empty_space{});
mvwhline(this->lv_window, y, this->vc_x, ' ', width); mvwhline(this->lv_window, y, this->vc_x, ' ', width);
++y; ++y;
blank_rows += 1;
} }
} }
if (this->lv_selectable && !this->lv_sync_selection_and_top if (this->lv_selectable && !this->lv_sync_selection_and_top
&& this->lv_selection >= 0 && (row > this->lv_tail_space) && this->lv_selection >= 0 && row < this->lv_selection)
&& (blank_rows < this->lv_tail_space)
&& ((row - this->lv_tail_space) < this->lv_selection))
{ {
this->shift_top(this->lv_selection - row + this->lv_tail_space); this->shift_top(this->lv_selection - row + this->lv_tail_space);
continue; continue;
@ -649,13 +648,15 @@ listview_curses::do_update()
if (this->lv_show_bottom_border) { if (this->lv_show_bottom_border) {
cchar_t row_ch[width]; cchar_t row_ch[width];
int y = this->vc_y + height - 1; int bottom_y = this->vc_y + height - 1;
mvwin_wchnstr(this->lv_window, y, this->vc_x, row_ch, width - 1); mvwin_wchnstr(
this->lv_window, bottom_y, this->vc_x, row_ch, width - 1);
for (unsigned long lpc = 0; lpc < width - 1; lpc++) { for (unsigned long lpc = 0; lpc < width - 1; lpc++) {
row_ch[lpc].attr |= A_UNDERLINE; row_ch[lpc].attr |= A_UNDERLINE;
} }
mvwadd_wchnstr(this->lv_window, y, this->vc_x, row_ch, width - 1); mvwadd_wchnstr(
this->lv_window, bottom_y, this->vc_x, row_ch, width - 1);
} }
this->vc_needs_update = false; this->vc_needs_update = false;
@ -763,7 +764,7 @@ listview_curses::shift_selection(shift_amount_t sa)
{ {
this->set_top(top_for_last); this->set_top(top_for_last);
if (this->lv_selection <= top_for_last) { if (this->lv_selection <= top_for_last) {
this->set_selection(top_for_last + 1_vl); new_selection = top_for_last + 1_vl;
} }
} else { } else {
this->shift_top(rows_avail); this->shift_top(rows_avail);
@ -772,7 +773,7 @@ listview_curses::shift_selection(shift_amount_t sa)
if (this->lv_selectable && this->lv_top >= top_for_last if (this->lv_selectable && this->lv_top >= top_for_last
&& inner_height > 0_vl) && inner_height > 0_vl)
{ {
this->set_selection(inner_height - 1_vl); new_selection = inner_height - 1_vl;
} }
} }
} }
@ -794,6 +795,14 @@ listview_curses::handle_mouse(mouse_event& me)
auto GUTTER_REPEAT_DELAY auto GUTTER_REPEAT_DELAY
= std::chrono::duration_cast<std::chrono::microseconds>(100ms).count(); = std::chrono::duration_cast<std::chrono::microseconds>(100ms).count();
if (view_curses::handle_mouse(me)) {
return true;
}
if (!this->vc_enabled) {
return false;
}
vis_line_t inner_height, height; vis_line_t inner_height, height;
struct timeval diff; struct timeval diff;
unsigned long width; unsigned long width;
@ -911,10 +920,10 @@ listview_curses::set_top(vis_line_t top, bool suppress_flash)
this->lv_focused_overlay_selection = 0_vl; this->lv_focused_overlay_selection = 0_vl;
if (this->lv_selectable) { if (this->lv_selectable) {
if (this->lv_selection < 0_vl) { if (this->lv_selection < 0_vl) {
this->set_selection(top); this->set_selection_without_context(top);
} else if (this->lv_selection < top) { } else if (this->lv_selection < top) {
auto sel_diff = this->lv_selection - old_top; auto sel_diff = this->lv_selection - old_top;
this->set_selection(top + sel_diff); this->set_selection_without_context(top + sel_diff);
} else { } else {
auto sel_diff = this->lv_selection - old_top; auto sel_diff = this->lv_selection - old_top;
auto bot = this->get_bottom(); auto bot = this->get_bottom();
@ -924,14 +933,14 @@ listview_curses::set_top(vis_line_t top, bool suppress_flash)
this->get_dimensions(height, width); this->get_dimensions(height, width);
if (bot == -1_vl) { if (bot == -1_vl) {
this->set_selection(this->lv_top); this->set_selection_without_context(this->lv_top);
} else if (this->lv_selection < this->lv_top } else if (this->lv_selection < this->lv_top
|| bot < this->lv_selection) || bot < this->lv_selection)
{ {
if (top + sel_diff > bot) { if (top + sel_diff > bot) {
this->set_selection(bot); this->set_selection_without_context(bot);
} else { } else {
this->set_selection(top + sel_diff); this->set_selection_without_context(top + sel_diff);
} }
} }
} }
@ -996,7 +1005,7 @@ listview_curses::rows_available(vis_line_t line,
} }
void void
listview_curses::set_selection(vis_line_t sel) listview_curses::set_selection_without_context(vis_line_t sel)
{ {
if (this->lv_selectable) { if (this->lv_selectable) {
if (this->lv_selection == sel) { if (this->lv_selection == sel) {
@ -1055,6 +1064,23 @@ listview_curses::set_selection(vis_line_t sel)
} }
} }
void
listview_curses::set_selection(vis_line_t sel)
{
this->set_selection_without_context(sel);
auto dim = this->get_dimensions();
if (this->lv_selection > 0 && this->lv_selection <= this->lv_top) {
this->set_top(this->lv_selection - 1_vl);
} else if (dim.first > this->lv_tail_space
&& (this->lv_selection
> (this->lv_top + (dim.first - 1_vl) - this->lv_tail_space)))
{
this->set_top(this->lv_selection + this->lv_tail_space
- (dim.first - 1_vl));
}
}
vis_line_t vis_line_t
listview_curses::get_top_for_last_row() listview_curses::get_top_for_last_row()
{ {

@ -225,6 +225,8 @@ public:
void set_selection(vis_line_t sel); void set_selection(vis_line_t sel);
void set_selection_without_context(vis_line_t sel);
enum class shift_amount_t { enum class shift_amount_t {
up_line, up_line,
up_page, up_page,

@ -228,6 +228,8 @@ static auto bound_xterm_mouse = injector::bind<xterm_mouse>::to_singleton();
static auto bound_scripts = injector::bind<available_scripts>::to_singleton(); static auto bound_scripts = injector::bind<available_scripts>::to_singleton();
static auto bound_crumbs = injector::bind<breadcrumb_curses>::to_singleton();
static auto bound_curl static auto bound_curl
= injector::bind_multiple<isc::service_base>() = injector::bind_multiple<isc::service_base>()
.add_singleton<curl_looper, services::curl_streamer_t>(); .add_singleton<curl_looper, services::curl_streamer_t>();
@ -274,8 +276,6 @@ force_linking(services::main_t anno)
} }
} // namespace injector } // namespace injector
static breadcrumb_curses breadcrumb_view;
struct lnav_data_t lnav_data; struct lnav_data_t lnav_data;
bool bool
@ -367,6 +367,12 @@ static void
handle_rl_key(int ch) handle_rl_key(int ch)
{ {
switch (ch) { switch (ch) {
case KEY_F(2):
if (xterm_mouse::is_available()) {
auto& mouse_i = injector::get<xterm_mouse&>();
mouse_i.set_enabled(!mouse_i.is_enabled());
}
break;
case KEY_PPAGE: case KEY_PPAGE:
case KEY_NPAGE: case KEY_NPAGE:
case KEY_CTRL('p'): case KEY_CTRL('p'):
@ -670,6 +676,14 @@ handle_config_ui_key(int ch)
{ {
bool retval = false; bool retval = false;
if (ch == KEY_F(2)) {
if (xterm_mouse::is_available()) {
auto& mouse_i = injector::get<xterm_mouse&>();
mouse_i.set_enabled(!mouse_i.is_enabled());
}
return retval;
}
switch (lnav_data.ld_mode) { switch (lnav_data.ld_mode) {
case ln_mode_t::FILES: case ln_mode_t::FILES:
retval = lnav_data.ld_files_view.handle_key(ch); retval = lnav_data.ld_files_view.handle_key(ch);
@ -722,6 +736,8 @@ handle_config_ui_key(int ch)
static bool static bool
handle_key(int ch) handle_key(int ch)
{ {
static auto* breadcrumb_view = injector::get<breadcrumb_curses*>();
lnav_data.ld_input_state.push_back(ch); lnav_data.ld_input_state.push_back(ch);
switch (ch) { switch (ch) {
@ -731,7 +747,7 @@ handle_key(int ch)
switch (lnav_data.ld_mode) { switch (lnav_data.ld_mode) {
case ln_mode_t::PAGING: case ln_mode_t::PAGING:
if (ch == '`') { if (ch == '`') {
breadcrumb_view.focus(); breadcrumb_view->focus();
lnav_data.ld_mode = ln_mode_t::BREADCRUMBS; lnav_data.ld_mode = ln_mode_t::BREADCRUMBS;
return true; return true;
} }
@ -739,7 +755,7 @@ handle_key(int ch)
return handle_paging_key(ch); return handle_paging_key(ch);
case ln_mode_t::BREADCRUMBS: case ln_mode_t::BREADCRUMBS:
if (ch == '`' || !breadcrumb_view.handle_key(ch)) { if (ch == '`' || !breadcrumb_view->handle_key(ch)) {
lnav_data.ld_mode = ln_mode_t::PAGING; lnav_data.ld_mode = ln_mode_t::PAGING;
lnav_data.ld_view_stack.set_needs_update(); lnav_data.ld_view_stack.set_needs_update();
return true; return true;
@ -986,6 +1002,7 @@ looper()
{ {
static auto* ps = injector::get<pollable_supervisor*>(); static auto* ps = injector::get<pollable_supervisor*>();
static auto* filter_source = injector::get<filter_sub_source*>(); static auto* filter_source = injector::get<filter_sub_source*>();
static auto* breadcrumb_view = injector::get<breadcrumb_curses*>();
try { try {
auto* sql_cmd_map = injector::get<readline_context::command_map_t*, auto* sql_cmd_map = injector::get<readline_context::command_map_t*,
@ -1147,10 +1164,12 @@ looper()
ui_periodic_timer::singleton(); ui_periodic_timer::singleton();
auto mouse_i = injector::get<xterm_mouse&>(); auto& mouse_i = injector::get<xterm_mouse&>();
mouse_i.set_behavior(&lb); mouse_i.set_behavior(&lb);
mouse_i.set_enabled(check_experimental("mouse")); mouse_i.set_enabled(check_experimental("mouse")
|| lnav_config.lc_mouse_mode
== lnav_mouse_mode::enabled);
lnav_data.ld_window = sc.get_window(); lnav_data.ld_window = sc.get_window();
keypad(stdscr, TRUE); keypad(stdscr, TRUE);
@ -1219,7 +1238,6 @@ looper()
execute_examples(); execute_examples();
rlc->set_window(lnav_data.ld_window); rlc->set_window(lnav_data.ld_window);
rlc->set_y(-1);
rlc->set_focus_action(rl_focus); rlc->set_focus_action(rl_focus);
rlc->set_change_action(rl_change); rlc->set_change_action(rl_change);
rlc->set_perform_action(rl_callback); rlc->set_perform_action(rl_callback);
@ -1252,9 +1270,15 @@ looper()
vsb.push_back(sb); vsb.push_back(sb);
breadcrumb_view.set_y(1); breadcrumb_view->on_focus
breadcrumb_view.set_window(lnav_data.ld_window); = [](breadcrumb_curses&) { set_view_mode(ln_mode_t::BREADCRUMBS); };
breadcrumb_view.set_line_source(lnav_crumb_source); breadcrumb_view->on_blur = [](breadcrumb_curses&) {
set_view_mode(ln_mode_t::PAGING);
lnav_data.ld_view_stack.set_needs_update();
};
breadcrumb_view->set_y(1);
breadcrumb_view->set_window(lnav_data.ld_window);
breadcrumb_view->set_line_source(lnav_crumb_source);
auto event_handler = [](auto&& tc) { auto event_handler = [](auto&& tc) {
auto top_view = lnav_data.ld_view_stack.top(); auto top_view = lnav_data.ld_view_stack.top();
@ -1274,6 +1298,13 @@ looper()
= role_t::VCR_DISABLED_CURSOR_LINE; = role_t::VCR_DISABLED_CURSOR_LINE;
lnav_data.ld_views[lpc].tc_state_event_handler = event_handler; lnav_data.ld_views[lpc].tc_state_event_handler = event_handler;
} }
lnav_data.ld_views[LNV_DB].set_supports_marks(true);
lnav_data.ld_views[LNV_HELP].set_supports_marks(true);
lnav_data.ld_views[LNV_HISTOGRAM].set_supports_marks(true);
lnav_data.ld_views[LNV_LOG].set_supports_marks(true);
lnav_data.ld_views[LNV_TEXT].set_supports_marks(true);
lnav_data.ld_views[LNV_SCHEMA].set_supports_marks(true);
lnav_data.ld_views[LNV_PRETTY].set_supports_marks(true);
lnav_data.ld_doc_view.set_window(lnav_data.ld_window); lnav_data.ld_doc_view.set_window(lnav_data.ld_window);
lnav_data.ld_doc_view.set_show_scrollbar(false); lnav_data.ld_doc_view.set_show_scrollbar(false);
@ -1288,10 +1319,12 @@ looper()
lnav_data.ld_preview_view[1].set_window(lnav_data.ld_window); lnav_data.ld_preview_view[1].set_window(lnav_data.ld_window);
lnav_data.ld_preview_view[1].set_show_scrollbar(false); lnav_data.ld_preview_view[1].set_show_scrollbar(false);
lnav_data.ld_filter_view.set_title("Text Filters");
lnav_data.ld_filter_view.set_selectable(true); lnav_data.ld_filter_view.set_selectable(true);
lnav_data.ld_filter_view.set_window(lnav_data.ld_window); lnav_data.ld_filter_view.set_window(lnav_data.ld_window);
lnav_data.ld_filter_view.set_show_scrollbar(true); lnav_data.ld_filter_view.set_show_scrollbar(true);
lnav_data.ld_files_view.set_title("Files");
lnav_data.ld_files_view.set_selectable(true); lnav_data.ld_files_view.set_selectable(true);
lnav_data.ld_files_view.set_window(lnav_data.ld_window); lnav_data.ld_files_view.set_window(lnav_data.ld_window);
lnav_data.ld_files_view.set_show_scrollbar(true); lnav_data.ld_files_view.set_show_scrollbar(true);
@ -1332,6 +1365,32 @@ looper()
auto top_source = injector::get<std::shared_ptr<top_status_source>>(); auto top_source = injector::get<std::shared_ptr<top_status_source>>();
lnav_data.ld_bottom_source.get_field(bottom_status_source::BSF_HELP)
.on_click
= [](status_field&) { ensure_view(&lnav_data.ld_views[LNV_HELP]); };
lnav_data.ld_bottom_source
.get_field(bottom_status_source::BSF_LINE_NUMBER)
.on_click
= [](status_field&) {
auto cmd = fmt::format(
FMT_STRING("prompt command : 'goto {}'"),
(int) lnav_data.ld_view_stack.top().value()->get_top());
execute_command(lnav_data.ld_exec_context, cmd);
};
lnav_data.ld_bottom_source
.get_field(bottom_status_source::BSF_SEARCH_TERM)
.on_click
= [](status_field&) {
auto term = lnav_data.ld_view_stack.top()
.value()
->get_current_search();
auto cmd
= fmt::format(FMT_STRING("prompt search / '{}'"), term);
execute_command(lnav_data.ld_exec_context, cmd);
};
lnav_data.ld_status[LNS_TOP].set_y(0); lnav_data.ld_status[LNS_TOP].set_y(0);
lnav_data.ld_status[LNS_TOP].set_default_role( lnav_data.ld_status[LNS_TOP].set_default_role(
role_t::VCR_INACTIVE_STATUS); role_t::VCR_INACTIVE_STATUS);
@ -1551,12 +1610,12 @@ looper()
} }
if (lnav_data.ld_mode == ln_mode_t::BREADCRUMBS if (lnav_data.ld_mode == ln_mode_t::BREADCRUMBS
&& breadcrumb_view.get_needs_update()) && breadcrumb_view->get_needs_update())
{ {
lnav_data.ld_view_stack.set_needs_update(); lnav_data.ld_view_stack.set_needs_update();
} }
if (lnav_data.ld_view_stack.do_update()) { if (lnav_data.ld_view_stack.do_update()) {
breadcrumb_view.set_needs_update(); breadcrumb_view->set_needs_update();
} }
lnav_data.ld_doc_view.do_update(); lnav_data.ld_doc_view.do_update();
lnav_data.ld_example_view.do_update(); lnav_data.ld_example_view.do_update();
@ -1577,7 +1636,7 @@ looper()
if (filter_source->fss_editing) { if (filter_source->fss_editing) {
filter_source->fss_match_view.set_needs_update(); filter_source->fss_match_view.set_needs_update();
} }
breadcrumb_view.do_update(); breadcrumb_view->do_update();
// These updates need to be done last so their readline views can // These updates need to be done last so their readline views can
// put the cursor in the right place. // put the cursor in the right place.
switch (lnav_data.ld_mode) { switch (lnav_data.ld_mode) {
@ -2791,9 +2850,7 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
lnav_data.ld_preview_view[0].set_sub_source( lnav_data.ld_preview_view[0].set_sub_source(
&lnav_data.ld_preview_source[0]); &lnav_data.ld_preview_source[0]);
lnav_data.ld_filter_view.set_sub_source(filter_source) lnav_data.ld_filter_view.set_sub_source(filter_source)
.add_input_delegate(*filter_source) .add_input_delegate(*filter_source);
.add_child_view(&filter_source->fss_match_view)
.add_child_view(filter_source->fss_editor.get());
lnav_data.ld_files_view.set_sub_source(&lnav_data.ld_files_source) lnav_data.ld_files_view.set_sub_source(&lnav_data.ld_files_source)
.add_input_delegate(lnav_data.ld_files_source); .add_input_delegate(lnav_data.ld_files_source);
lnav_data.ld_user_message_view.set_sub_source( lnav_data.ld_user_message_view.set_sub_source(

@ -698,6 +698,7 @@ com_goto(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
std::string all_args = remaining_args(cmdline, args); std::string all_args = remaining_args(cmdline, args);
auto* tc = *lnav_data.ld_view_stack.top(); auto* tc = *lnav_data.ld_view_stack.top();
nonstd::optional<vis_line_t> dst_vl; nonstd::optional<vis_line_t> dst_vl;
auto is_location = false;
if (startswith(all_args, "#")) { if (startswith(all_args, "#")) {
auto* ta = dynamic_cast<text_anchors*>(tc->get_sub_source()); auto* ta = dynamic_cast<text_anchors*>(tc->get_sub_source());
@ -710,6 +711,7 @@ com_goto(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
if (!dst_vl) { if (!dst_vl) {
return ec.make_error("unable to find anchor: {}", all_args); return ec.make_error("unable to find anchor: {}", all_args);
} }
is_location = true;
} }
auto* ttt = dynamic_cast<text_time_translator*>(tc->get_sub_source()); auto* ttt = dynamic_cast<text_time_translator*>(tc->get_sub_source());
@ -878,7 +880,7 @@ com_goto(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
return Err(um); return Err(um);
} }
dst_vl | [&ec, tc, &retval](auto new_top) { dst_vl | [&ec, tc, &retval, is_location](auto new_top) {
if (ec.ec_dry_run) { if (ec.ec_dry_run) {
retval = "info: will move to line " retval = "info: will move to line "
+ std::to_string((int) new_top); + std::to_string((int) new_top);
@ -886,6 +888,9 @@ com_goto(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
tc->get_sub_source()->get_location_history() | tc->get_sub_source()->get_location_history() |
[new_top](auto lh) { lh->loc_history_append(new_top); }; [new_top](auto lh) { lh->loc_history_append(new_top); };
tc->set_selection(new_top); tc->set_selection(new_top);
if (tc->is_selectable() && is_location) {
tc->set_top(new_top - 2_vl, false);
}
retval = ""; retval = "";
} }
@ -1210,7 +1215,12 @@ com_goto_location(exec_context& ec,
? lh->loc_history_back(tc->get_selection()) ? lh->loc_history_back(tc->get_selection())
: lh->loc_history_forward(tc->get_selection()); : lh->loc_history_forward(tc->get_selection());
} }
| [tc](auto new_top) { tc->set_selection(new_top); }; | [tc](auto new_top) {
tc->set_selection(new_top);
if (tc->is_selectable()) {
tc->set_top(new_top - 2_vl, false);
}
};
}; };
} }
@ -1240,6 +1250,9 @@ com_next_section(exec_context& ec,
} }
tc->set_selection(adj_opt.value()); tc->set_selection(adj_opt.value());
if (tc->is_selectable()) {
tc->set_top(adj_opt.value() - 2_vl, false);
}
} }
return Ok(retval); return Ok(retval);
@ -1268,6 +1281,9 @@ com_prev_section(exec_context& ec,
} }
tc->set_selection(adj_opt.value()); tc->set_selection(adj_opt.value());
if (tc->is_selectable()) {
tc->set_top(adj_opt.value() - 2_vl, false);
}
} }
return Ok(retval); return Ok(retval);

@ -382,13 +382,11 @@ update_installs_from_git()
git_dir.string()); git_dir.string());
int ret = system(pull_cmd.c_str()); int ret = system(pull_cmd.c_str());
if (ret == -1) { if (ret == -1) {
std::cerr << "Failed to spawn command " std::cerr << "Failed to spawn command " << "\"" << pull_cmd
<< "\"" << pull_cmd << "\": " << strerror(errno) << "\": " << strerror(errno) << std::endl;
<< std::endl;
retval = false; retval = false;
} else if (ret > 0) { } else if (ret > 0) {
std::cerr << "Command " std::cerr << "Command " << "\"" << pull_cmd
<< "\"" << pull_cmd
<< "\" failed: " << strerror(errno) << std::endl; << "\" failed: " << strerror(errno) << std::endl;
retval = false; retval = false;
} }
@ -561,6 +559,23 @@ static const struct json_path_container movement_handlers = {
.for_field<>(&_lnav_config::lc_ui_movement, &movement_config::mode), .for_field<>(&_lnav_config::lc_ui_movement, &movement_config::mode),
}; };
static const json_path_handler_base::enum_value_t _mouse_mode_values[] = {
{"disabled", lnav_mouse_mode::disabled},
{"enabled", lnav_mouse_mode::enabled},
json_path_handler_base::ENUM_TERMINATOR,
};
static const struct json_path_container mouse_handlers = {
yajlpp::property_handler("mode")
.with_synopsis("enabled|disabled")
.with_enum_values(_mouse_mode_values)
.with_example("enabled")
.with_example("disabled")
.with_description("Overall control for mouse support")
.for_field<>(&_lnav_config::lc_mouse_mode),
};
static const struct json_path_container global_var_handlers = { static const struct json_path_container global_var_handlers = {
yajlpp::pattern_property_handler("(?<var_name>\\w+)") yajlpp::pattern_property_handler("(?<var_name>\\w+)")
.with_synopsis("<name>") .with_synopsis("<name>")
@ -1130,6 +1145,9 @@ static const struct json_path_container ui_handlers = {
yajlpp::property_handler("theme-defs") yajlpp::property_handler("theme-defs")
.with_description("Theme definitions.") .with_description("Theme definitions.")
.with_children(theme_defs_handlers), .with_children(theme_defs_handlers),
yajlpp::property_handler("mouse")
.with_description("Mouse-related settings")
.with_children(mouse_handlers),
yajlpp::property_handler("movement") yajlpp::property_handler("movement")
.with_description("Log file cursor movement mode settings") .with_description("Log file cursor movement mode settings")
.with_children(movement_handlers), .with_children(movement_handlers),

@ -97,6 +97,11 @@ struct movement_config {
config_movement_mode mode{config_movement_mode::TOP}; config_movement_mode mode{config_movement_mode::TOP};
}; };
enum class lnav_mouse_mode {
disabled,
enabled,
};
struct _lnav_config { struct _lnav_config {
top_status_source_cfg lc_top_status_cfg; top_status_source_cfg lc_top_status_cfg;
bool lc_ui_dim_text; bool lc_ui_dim_text;
@ -104,6 +109,7 @@ struct _lnav_config {
std::string lc_ui_keymap; std::string lc_ui_keymap;
std::string lc_ui_theme; std::string lc_ui_theme;
movement_config lc_ui_movement; movement_config lc_ui_movement;
lnav_mouse_mode lc_mouse_mode;
std::map<std::string, key_map> lc_ui_keymaps; std::map<std::string, key_map> lc_ui_keymaps;
std::map<std::string, std::string> lc_ui_key_overrides; std::map<std::string, std::string> lc_ui_key_overrides;
std::map<std::string, std::string> lc_global_vars; std::map<std::string, std::string> lc_global_vars;

@ -1905,11 +1905,6 @@ logfile_sub_source::text_clear_marks(const bookmark_type_t* bm)
for (iter = this->lss_user_marks[bm].begin(); for (iter = this->lss_user_marks[bm].begin();
iter != this->lss_user_marks[bm].end();) iter != this->lss_user_marks[bm].end();)
{ {
auto line_meta_opt = this->find_bookmark_metadata(*iter);
if (line_meta_opt) {
++iter;
continue;
}
this->find_line(*iter)->set_mark(false); this->find_line(*iter)->set_mark(false);
iter = this->lss_user_marks[bm].erase(iter); iter = this->lss_user_marks[bm].erase(iter);
} }

@ -327,6 +327,10 @@ plain_text_source::text_crumbs_for_line(int line,
this->line_for_offset(sib_iter->second->hn_start) | this->line_for_offset(sib_iter->second->hn_start) |
[this](const auto new_top) { [this](const auto new_top) {
this->tss_view->set_selection(new_top); this->tss_view->set_selection(new_top);
if (this->tss_view->is_selectable()) {
this->tss_view->set_top(new_top - 2_vl,
false);
}
}; };
}, },
[this, parent_node](size_t index) { [this, parent_node](size_t index) {
@ -337,6 +341,10 @@ plain_text_source::text_crumbs_for_line(int line,
this->line_for_offset(sib->hn_start) | this->line_for_offset(sib->hn_start) |
[this](const auto new_top) { [this](const auto new_top) {
this->tss_view->set_selection(new_top); this->tss_view->set_selection(new_top);
if (this->tss_view->is_selectable()) {
this->tss_view->set_top(new_top - 2_vl,
false);
}
}; };
}); });
}); });

@ -1037,7 +1037,7 @@ readline_curses::start()
{ {
looping = false; looping = false;
} else { } else {
int context, prompt_start = 0; int context, prompt_start = 0, new_point = 0;
char type[1024]; char type[1024];
msg[rc] = '\0'; msg[rc] = '\0';
@ -1047,6 +1047,14 @@ readline_curses::start()
log_perror(chdir(cwd)); log_perror(chdir(cwd));
} else if (startswith(msg, "sugg:")) { } else if (startswith(msg, "sugg:")) {
rc_local_suggestion = &msg[5]; rc_local_suggestion = &msg[5];
} else if (sscanf(msg, "x:%d", &new_point) == 1) {
if (rl_prompt) {
new_point -= strlen(rl_prompt);
}
if (0 <= new_point && new_point <= rl_end) {
rl_point = new_point;
rl_redisplay();
}
} else if (sscanf(msg, "i:%d:%n", &rl_point, &prompt_start) } else if (sscanf(msg, "i:%d:%n", &rl_point, &prompt_start)
== 1) == 1)
{ {
@ -1671,6 +1679,25 @@ readline_curses::do_update()
return true; return true;
} }
bool
readline_curses::handle_mouse(mouse_event& me)
{
if (this->rc_active_context == -1) {
return false;
}
char buffer[32];
snprintf(buffer, sizeof(buffer), "x:%d", me.me_x);
if (sendstring(
this->rc_command_pipe[RCF_MASTER], buffer, strlen(buffer) + 1)
== -1)
{
perror("handle_mouse: write failed");
}
return true;
}
std::string std::string
readline_curses::get_match_string() const readline_curses::get_match_string() const
{ {

@ -188,6 +188,8 @@ public:
bool do_update() override; bool do_update() override;
bool handle_mouse(mouse_event& me) override;
void window_change(); void window_change();
void line_ready(const char* line); void line_ready(const char* line);

@ -6,6 +6,9 @@
"default-colors": true, "default-colors": true,
"keymap": "default", "keymap": "default",
"theme": "default", "theme": "default",
"mouse": {
"mode": "disabled"
},
"movement": { "movement": {
"mode": "cursor" "mode": "cursor"
} }

@ -216,7 +216,7 @@ shared_buffer_ref::widen(narrow_result old_data_length)
void void
shared_buffer_ref::erase_ansi() shared_buffer_ref::erase_ansi()
{ {
if (!this->sb_metadata.m_has_ansi) { if (!this->sb_metadata.m_valid_utf || !this->sb_metadata.m_has_ansi) {
return; return;
} }

@ -34,10 +34,19 @@
#endif #endif
#include "config.h" #include "config.h"
#include "pcrepp/pcre2pp.hh"
#include "shlex.hh" #include "shlex.hh"
using namespace lnav::roles::literals; using namespace lnav::roles::literals;
std::string
shlex::escape(std::string s)
{
static const auto SH_CHARS = lnav::pcre2pp::code::from_const("'");
return SH_CHARS.replace(s, "\\'");
}
attr_line_t attr_line_t
shlex::to_attr_line(const shlex::tokenize_error_t& te) const shlex::to_attr_line(const shlex::tokenize_error_t& te) const
{ {

@ -59,6 +59,8 @@ enum class shlex_token_t {
class shlex { class shlex {
public: public:
static std::string escape(std::string s);
shlex(const char* str, size_t len) : s_str(str), s_len(len) {} shlex(const char* str, size_t len) : s_str(str), s_len(len) {}
explicit shlex(const string_fragment& sf) explicit shlex(const string_fragment& sf)

@ -35,8 +35,14 @@
#include "statusview_curses.hh" #include "statusview_curses.hh"
#include "base/ansi_scrubber.hh" #include "base/ansi_scrubber.hh"
#include "base/itertools.hh"
#include "config.h" #include "config.h"
void
status_field::no_op_action(status_field&)
{
}
void void
status_field::set_value(std::string value) status_field::set_value(std::string value)
{ {
@ -61,7 +67,7 @@ status_field::do_cylon()
: (this->sf_width - (cycle_pos - this->sf_width) - 1); : (this->sf_width - (cycle_pos - this->sf_width) - 1);
auto stop = std::min(start + 3, this->sf_width); auto stop = std::min(start + 3, this->sf_width);
struct line_range lr(std::max<long>(start, 0L), stop); struct line_range lr(std::max<long>(start, 0L), stop);
auto& vc = view_colors::singleton(); const auto& vc = view_colors::singleton();
auto attrs = vc.attrs_for_role(role_t::VCR_ACTIVE_STATUS); auto attrs = vc.attrs_for_role(role_t::VCR_ACTIVE_STATUS);
attrs.ta_attrs |= A_REVERSE; attrs.ta_attrs |= A_REVERSE;
@ -87,10 +93,11 @@ status_field::set_stitch_value(role_t left, role_t right)
bool bool
statusview_curses::do_update() statusview_curses::do_update()
{ {
int top, field, field_count, left = 0, right; int top, left = 0, right;
auto& vc = view_colors::singleton(); auto& vc = view_colors::singleton();
unsigned long width, height; unsigned long width, height;
this->sc_displayed_fields.clear();
if (!this->vc_visible || this->sc_window == nullptr) { if (!this->vc_visible || this->sc_window == nullptr) {
return false; return false;
} }
@ -110,8 +117,8 @@ statusview_curses::do_update()
whline(this->sc_window, ' ', width); whline(this->sc_window, ' ', width);
if (this->sc_source != nullptr) { if (this->sc_source != nullptr) {
field_count = this->sc_source->statusview_fields(); auto field_count = this->sc_source->statusview_fields();
for (field = 0; field < field_count; field++) { for (size_t field = 0; field < field_count; field++) {
auto& sf = this->sc_source->statusview_value_for_field(field); auto& sf = this->sc_source->statusview_value_for_field(field);
struct line_range lr(0, sf.get_width()); struct line_range lr(0, sf.get_width());
int x; int x;
@ -177,7 +184,11 @@ statusview_curses::do_update()
} }
} }
mvwattrline(this->sc_window, top, x, val, lr, default_role); auto write_res
= mvwattrline(this->sc_window, top, x, val, lr, default_role);
this->sc_displayed_fields.emplace_back(
line_range{x, static_cast<int>(x + write_res.mr_chars_out)},
field);
} }
} }
wmove(this->sc_window, top + 1, 0); wmove(this->sc_window, top + 1, 0);
@ -240,3 +251,23 @@ statusview_curses::window_change()
sf->set_width(actual_width); sf->set_width(actual_width);
} }
} }
bool
statusview_curses::handle_mouse(mouse_event& me)
{
auto find_res = this->sc_displayed_fields
| lnav::itertools::find_if([&me](const auto& elem) {
return me.is_click_in(mouse_button_t::BUTTON_LEFT,
elem.df_range.lr_start,
elem.df_range.lr_end);
});
if (find_res) {
auto& sf = this->sc_source->statusview_value_for_field(
find_res.value()->df_field_index);
sf.on_click(sf);
}
return true;
}

@ -42,6 +42,8 @@
*/ */
class status_field { class status_field {
public: public:
using action = std::function<void(status_field&)>;
/** /**
* @param width The maximum width of the field in characters. * @param width The maximum width of the field in characters.
* @param role The color role for this field, defaults to VCR_STATUS. * @param role The color role for this field, defaults to VCR_STATUS.
@ -121,6 +123,10 @@ public:
int get_share() const { return this->sf_share; } int get_share() const { return this->sf_share; }
static void no_op_action(status_field&);
action on_click{no_op_action};
protected: protected:
ssize_t sf_width; /*< The maximum display width, in chars. */ ssize_t sf_width; /*< The maximum display width, in chars. */
ssize_t sf_min_width{0}; /*< The minimum display width, in chars. */ ssize_t sf_min_width{0}; /*< The minimum display width, in chars. */
@ -175,11 +181,25 @@ public:
bool do_update() override; bool do_update() override;
bool handle_mouse(mouse_event& me) override;
private: private:
status_data_source* sc_source{nullptr}; status_data_source* sc_source{nullptr};
WINDOW* sc_window{nullptr}; WINDOW* sc_window{nullptr};
bool sc_enabled{true}; bool sc_enabled{true};
role_t sc_default_role{role_t::VCR_STATUS}; role_t sc_default_role{role_t::VCR_STATUS};
struct displayed_field {
displayed_field(line_range lr, size_t field_index)
: df_range(lr), df_field_index(field_index)
{
}
line_range df_range;
size_t df_field_index;
};
std::vector<displayed_field> sc_displayed_fields;
}; };
#endif #endif

@ -988,6 +988,10 @@ textfile_sub_source::set_top_from_off(file_off_t off)
if (new_top_opt) { if (new_top_opt) {
this->tss_view->set_selection(vis_line_t(new_top_opt.value())); this->tss_view->set_selection(vis_line_t(new_top_opt.value()));
if (this->tss_view->is_selectable()) {
this->tss_view->set_top(this->tss_view->get_selection() - 2_vl,
false);
}
} }
}; };
} }

@ -396,13 +396,11 @@ textview_curses::handle_mouse(mouse_event& me)
unsigned long width; unsigned long width;
vis_line_t height; vis_line_t height;
if (!this->tc_selection_start && listview_curses::handle_mouse(me)) { if (!this->vc_visible || this->lv_height == 0) {
return true; return false;
} }
if (this->tc_delegate != nullptr if (!this->tc_selection_start && listview_curses::handle_mouse(me)) {
&& this->tc_delegate->text_handle_mouse(*this, me))
{
return true; return true;
} }
@ -410,22 +408,64 @@ textview_curses::handle_mouse(mouse_event& me)
return false; return false;
} }
auto mouse_line = this->lv_display_lines[me.me_y]; auto mouse_line = (me.me_y < 0 || me.me_y >= this->lv_display_lines.size())
? empty_space{}
: this->lv_display_lines[me.me_y];
this->get_dimensions(height, width); this->get_dimensions(height, width);
auto* sub_delegate = dynamic_cast<text_delegate*>(this->tc_sub_source);
switch (me.me_state) { switch (me.me_state) {
case mouse_button_state_t::BUTTON_STATE_PRESSED: { case mouse_button_state_t::BUTTON_STATE_PRESSED: {
if (!this->lv_selectable) { if (!this->lv_selectable) {
this->set_selectable(true); this->set_selectable(true);
} }
mouse_line.match( mouse_line.match(
[this, &me](const main_content& mc) { [this, &me, sub_delegate](const main_content& mc) {
if (me.is_modifier_pressed(mouse_event::modifier_t::shift)) if (this->vc_enabled) {
{ if (this->tc_supports_marks
this->tc_selection_start = mc.mc_line; && me.is_modifier_pressed(
mouse_event::modifier_t::shift))
{
this->tc_selection_start = mc.mc_line;
}
this->set_selection_without_context(mc.mc_line);
this->tc_press_event = me;
}
if (this->tc_delegate != nullptr) {
this->tc_delegate->text_handle_mouse(*this, me);
}
if (sub_delegate != nullptr) {
sub_delegate->text_handle_mouse(*this, me);
}
},
[](const static_overlay_content& soc) {
},
[](const overlay_content& oc) {
},
[](const empty_space& es) {});
break;
}
case mouse_button_state_t::BUTTON_STATE_DOUBLE_CLICK: {
if (!this->lv_selectable) {
this->set_selectable(true);
}
mouse_line.match(
[this, &me, sub_delegate](const main_content& mc) {
if (this->vc_enabled) {
if (this->tc_supports_marks) {
this->toggle_user_mark(&BM_USER, mc.mc_line);
}
this->set_selection_without_context(mc.mc_line);
}
if (this->tc_delegate != nullptr) {
this->tc_delegate->text_handle_mouse(*this, me);
}
if (sub_delegate != nullptr) {
sub_delegate->text_handle_mouse(*this, me);
} }
this->set_selection(mc.mc_line);
this->tc_press_event = me;
}, },
[](const static_overlay_content& soc) { [](const static_overlay_content& soc) {
@ -437,29 +477,35 @@ textview_curses::handle_mouse(mouse_event& me)
break; break;
} }
case mouse_button_state_t::BUTTON_STATE_DRAGGED: { case mouse_button_state_t::BUTTON_STATE_DRAGGED: {
if (me.me_y <= 0) { if (!this->vc_enabled) {
} else if (me.me_y < 0) {
this->shift_selection(listview_curses::shift_amount_t::up_line); this->shift_selection(listview_curses::shift_amount_t::up_line);
me.me_y = 0;
mouse_line = main_content{this->get_top()}; mouse_line = main_content{this->get_top()};
} else if (me.me_y >= height } else if (me.me_y >= height) {
&& this->get_top() < this->get_top_for_last_row())
{
this->shift_selection( this->shift_selection(
listview_curses::shift_amount_t::down_line); listview_curses::shift_amount_t::down_line);
me.me_y = height;
} else if (mouse_line.is<main_content>()) { } else if (mouse_line.is<main_content>()) {
this->set_selection(mouse_line.get<main_content>().mc_line); this->set_selection_without_context(
mouse_line.get<main_content>().mc_line);
} }
break; break;
} }
case mouse_button_state_t::BUTTON_STATE_RELEASED: { case mouse_button_state_t::BUTTON_STATE_RELEASED: {
if (this->tc_selection_start) { if (this->vc_enabled) {
this->toggle_user_mark(&BM_USER, if (this->tc_selection_start) {
this->tc_selection_start.value(), this->toggle_user_mark(&BM_USER,
this->get_selection()); this->tc_selection_start.value(),
this->reload_data(); this->get_selection());
this->reload_data();
}
this->tc_selection_start = nonstd::nullopt;
}
if (this->tc_delegate != nullptr) {
this->tc_delegate->text_handle_mouse(*this, me);
}
if (sub_delegate != nullptr) {
sub_delegate->text_handle_mouse(*this, me);
} }
this->tc_selection_start = nonstd::nullopt;
break; break;
} }
} }

@ -385,7 +385,7 @@ public:
{ {
} }
void register_view(textview_curses* tc) { this->tss_view = tc; } virtual void register_view(textview_curses* tc) { this->tss_view = tc; }
/** /**
* @return The total number of lines available from the source. * @return The total number of lines available from the source.
@ -606,6 +606,12 @@ public:
text_sub_source* get_sub_source() const { return this->tc_sub_source; } text_sub_source* get_sub_source() const { return this->tc_sub_source; }
textview_curses& set_supports_marks(bool m)
{
this->tc_supports_marks = m;
return *this;
}
textview_curses& set_delegate(std::shared_ptr<text_delegate> del) textview_curses& set_delegate(std::shared_ptr<text_delegate> del)
{ {
this->tc_delegate = del; this->tc_delegate = del;
@ -851,6 +857,7 @@ protected:
mouse_event tc_press_event; mouse_event tc_press_event;
bool tc_hide_fields{true}; bool tc_hide_fields{true};
bool tc_paused{false}; bool tc_paused{false};
bool tc_supports_marks{false};
std::string tc_current_search; std::string tc_current_search;
std::string tc_previous_search; std::string tc_previous_search;

@ -40,12 +40,14 @@
#include "base/ansi_scrubber.hh" #include "base/ansi_scrubber.hh"
#include "base/attr_line.hh" #include "base/attr_line.hh"
#include "base/from_trait.hh" #include "base/from_trait.hh"
#include "base/injector.hh"
#include "base/itertools.hh" #include "base/itertools.hh"
#include "base/lnav_log.hh" #include "base/lnav_log.hh"
#include "config.h" #include "config.h"
#include "lnav_config.hh" #include "lnav_config.hh"
#include "shlex.hh" #include "shlex.hh"
#include "view_curses.hh" #include "view_curses.hh"
#include "xterm_mouse.hh"
#if defined HAVE_NCURSESW_CURSES_H #if defined HAVE_NCURSESW_CURSES_H
# include <ncursesw/term.h> # include <ncursesw/term.h>
@ -129,10 +131,86 @@ struct utf_to_display_adjustment {
} }
}; };
bool
mouse_event::is_click_in(mouse_button_t button, int x_start, int x_end) const
{
return this->me_button == button
&& this->me_state == mouse_button_state_t::BUTTON_STATE_RELEASED
&& (x_start <= this->me_x && this->me_x <= x_end)
&& (x_start <= this->me_press_x && this->me_press_x <= x_end)
&& this->me_y == this->me_press_y;
}
bool
mouse_event::is_press_in(mouse_button_t button, line_range lr) const
{
return this->me_button == button
&& this->me_state == mouse_button_state_t::BUTTON_STATE_PRESSED
&& lr.contains(this->me_x);
}
bool
mouse_event::is_drag_in(mouse_button_t button, line_range lr) const
{
return this->me_button == button
&& this->me_state == mouse_button_state_t::BUTTON_STATE_DRAGGED
&& lr.contains(this->me_x);
}
bool
mouse_event::is_double_click_in(mouse_button_t button, line_range lr) const
{
return this->me_button == button
&& this->me_state == mouse_button_state_t::BUTTON_STATE_DOUBLE_CLICK
&& lr.contains(this->me_x) && this->me_y == this->me_press_y;
}
bool
view_curses::handle_mouse(mouse_event& me)
{
if (me.me_state != mouse_button_state_t::BUTTON_STATE_DRAGGED) {
this->vc_last_drag_child = nullptr;
}
for (auto* child : this->vc_children) {
auto x = this->vc_x + me.me_x;
auto y = this->vc_y + me.me_y;
if ((me.me_state == mouse_button_state_t::BUTTON_STATE_DRAGGED
&& child == this->vc_last_drag_child && child->vc_x <= x
&& x < (child->vc_x + child->vc_width))
|| child->contains(x, y))
{
auto sub_me = me;
sub_me.me_x = x - child->vc_x;
sub_me.me_y = y - child->vc_y;
sub_me.me_press_x = this->vc_x + me.me_press_x - child->vc_x;
sub_me.me_press_y = this->vc_y + me.me_press_y - child->vc_y;
if (me.me_state == mouse_button_state_t::BUTTON_STATE_DRAGGED) {
this->vc_last_drag_child = child;
}
return child->handle_mouse(sub_me);
}
}
return false;
}
bool bool
view_curses::contains(int x, int y) const view_curses::contains(int x, int y) const
{ {
if (this->vc_x <= x && x < this->vc_x + this->vc_width && this->vc_y == y) { if (!this->vc_visible) {
return false;
}
for (auto* child : this->vc_children) {
if (child->contains(x, y)) {
return true;
}
}
if (this->vc_x <= x
&& (this->vc_width < 0 || x < this->vc_x + this->vc_width)
&& this->vc_y == y)
{
return true; return true;
} }
return false; return false;
@ -562,9 +640,9 @@ static const std::string COLOR_NAMES[] = {
"white", "white",
}; };
class color_listener : public lnav_config_listener { class ui_listener : public lnav_config_listener {
public: public:
color_listener() : lnav_config_listener(__FILE__) {} ui_listener() : lnav_config_listener(__FILE__) {}
void reload_config(error_reporter& reporter) override void reload_config(error_reporter& reporter) override
{ {
@ -597,11 +675,16 @@ public:
if (view_colors::initialized) { if (view_colors::initialized) {
vc.init_roles(iter->second, reporter); vc.init_roles(iter->second, reporter);
auto& mouse_i = injector::get<xterm_mouse&>();
mouse_i.set_enabled(check_experimental("mouse")
|| lnav_config.lc_mouse_mode
== lnav_mouse_mode::enabled);
} }
} }
}; };
static color_listener _COLOR_LISTENER; static ui_listener _UI_LISTENER;
term_color_palette* view_colors::vc_active_palette; term_color_palette* view_colors::vc_active_palette;
void void
@ -627,7 +710,7 @@ view_colors::init(bool headless)
auto reporter auto reporter
= [](const void*, const lnav::console::user_message& um) {}; = [](const void*, const lnav::console::user_message& um) {};
_COLOR_LISTENER.reload_config(reporter); _UI_LISTENER.reload_config(reporter);
} }
} }

@ -314,6 +314,7 @@ enum class mouse_button_state_t {
BUTTON_STATE_PRESSED, BUTTON_STATE_PRESSED,
BUTTON_STATE_DRAGGED, BUTTON_STATE_DRAGGED,
BUTTON_STATE_RELEASED, BUTTON_STATE_RELEASED,
BUTTON_STATE_DOUBLE_CLICK,
}; };
struct mouse_event { struct mouse_event {
@ -339,12 +340,26 @@ struct mouse_event {
return this->me_modifiers & lnav::enums::to_underlying(mod); return this->me_modifiers & lnav::enums::to_underlying(mod);
} }
bool is_click_in(mouse_button_t button, int x_start, int x_end) const;
bool is_click_in(mouse_button_t button, line_range lr) const
{
return this->is_click_in(button, lr.lr_start, lr.lr_end);
}
bool is_press_in(mouse_button_t button, line_range lr) const;
bool is_drag_in(mouse_button_t button, line_range lr) const;
bool is_double_click_in(mouse_button_t button, line_range lr) const;
mouse_button_t me_button; mouse_button_t me_button;
mouse_button_state_t me_state; mouse_button_state_t me_state;
uint8_t me_modifiers; uint8_t me_modifiers;
struct timeval me_time {}; struct timeval me_time {};
int me_x; int me_x;
int me_y; int me_y;
int me_press_x{-1};
int me_press_y{-1};
}; };
/** /**
@ -373,7 +388,7 @@ public:
return retval; return retval;
} }
virtual bool handle_mouse(mouse_event& me) { return false; } virtual bool handle_mouse(mouse_event& me);
virtual bool contains(int x, int y) const; virtual bool contains(int x, int y) const;
@ -445,6 +460,8 @@ public:
const struct line_range& lr, const struct line_range& lr,
role_t base_role = role_t::VCR_TEXT); role_t base_role = role_t::VCR_TEXT);
bool vc_enabled{true};
protected: protected:
bool vc_visible{true}; bool vc_visible{true};
/** Flag to indicate if a display update is needed. */ /** Flag to indicate if a display update is needed. */
@ -454,6 +471,7 @@ protected:
long vc_width{0}; long vc_width{0};
std::vector<view_curses*> vc_children; std::vector<view_curses*> vc_children;
role_t vc_default_role{role_t::VCR_TEXT}; role_t vc_default_role{role_t::VCR_TEXT};
view_curses* vc_last_drag_child{nullptr};
}; };
template<class T> template<class T>

@ -626,6 +626,7 @@ handle_winch()
void void
layout_views() layout_views()
{ {
static auto* breadcrumb_view = injector::get<breadcrumb_curses*>();
int width, height; int width, height;
getmaxyx(lnav_data.ld_window, height, width); getmaxyx(lnav_data.ld_window, height, width);
@ -694,20 +695,27 @@ layout_views()
auto um_height = std::min(um_rows, (height - 4) / 2); auto um_height = std::min(um_rows, (height - 4) / 2);
lnav_data.ld_user_message_view.set_height(vis_line_t(um_height)); lnav_data.ld_user_message_view.set_height(vis_line_t(um_height));
auto config_panel_open = (lnav_data.ld_mode == ln_mode_t::FILTER
|| lnav_data.ld_mode == ln_mode_t::FILES
|| lnav_data.ld_mode == ln_mode_t::SEARCH_FILTERS
|| lnav_data.ld_mode == ln_mode_t::SEARCH_FILES);
auto filters_open = (lnav_data.ld_mode == ln_mode_t::FILTER auto filters_open = (lnav_data.ld_mode == ln_mode_t::FILTER
|| lnav_data.ld_mode == ln_mode_t::FILES || lnav_data.ld_mode == ln_mode_t::SEARCH_FILTERS);
|| lnav_data.ld_mode == ln_mode_t::SEARCH_FILTERS auto files_open = (lnav_data.ld_mode == ln_mode_t::FILES
|| lnav_data.ld_mode == ln_mode_t::SEARCH_FILES); || lnav_data.ld_mode == ln_mode_t::SEARCH_FILES);
int filter_height = filters_open ? 5 : 0; int filter_height = config_panel_open ? 5 : 0;
bool breadcrumb_open = (lnav_data.ld_mode == ln_mode_t::BREADCRUMBS); bool breadcrumb_open = (lnav_data.ld_mode == ln_mode_t::BREADCRUMBS);
auto bottom_min = std::min(2 + 3, height); auto bottom_min = std::min(2 + 3, height);
auto bottom = clamped<int>::from(height, bottom_min, height); auto bottom = clamped<int>::from(height, bottom_min, height);
lnav_data.ld_rl_view->set_y(height - 1);
bottom -= lnav_data.ld_rl_view->get_height(); bottom -= lnav_data.ld_rl_view->get_height();
lnav_data.ld_rl_view->set_width(width); lnav_data.ld_rl_view->set_width(width);
breadcrumb_view->set_width(width);
bool vis; bool vis;
vis = bottom.try_consume(lnav_data.ld_match_view.get_height()); vis = bottom.try_consume(lnav_data.ld_match_view.get_height());
lnav_data.ld_match_view.set_y(bottom); lnav_data.ld_match_view.set_y(bottom);
@ -719,7 +727,8 @@ layout_views()
bottom -= 1; bottom -= 1;
lnav_data.ld_status[LNS_BOTTOM].set_y(bottom); lnav_data.ld_status[LNS_BOTTOM].set_y(bottom);
lnav_data.ld_status[LNS_BOTTOM].set_enabled(!filters_open lnav_data.ld_status[LNS_BOTTOM].set_width(width);
lnav_data.ld_status[LNS_BOTTOM].set_enabled(!config_panel_open
&& !breadcrumb_open); && !breadcrumb_open);
vis = preview_open1 && bottom.try_consume(preview_height1 + 1); vis = preview_open1 && bottom.try_consume(preview_height1 + 1);
@ -728,6 +737,7 @@ layout_views()
lnav_data.ld_preview_view[1].set_visible(vis); lnav_data.ld_preview_view[1].set_visible(vis);
lnav_data.ld_status[LNS_PREVIEW1].set_y(bottom); lnav_data.ld_status[LNS_PREVIEW1].set_y(bottom);
lnav_data.ld_status[LNS_PREVIEW1].set_width(width);
lnav_data.ld_status[LNS_PREVIEW1].set_visible(vis); lnav_data.ld_status[LNS_PREVIEW1].set_visible(vis);
vis = preview_open0 && bottom.try_consume(preview_height0 + 1); vis = preview_open0 && bottom.try_consume(preview_height0 + 1);
@ -736,6 +746,7 @@ layout_views()
lnav_data.ld_preview_view[0].set_visible(vis); lnav_data.ld_preview_view[0].set_visible(vis);
lnav_data.ld_status[LNS_PREVIEW0].set_y(bottom); lnav_data.ld_status[LNS_PREVIEW0].set_y(bottom);
lnav_data.ld_status[LNS_PREVIEW0].set_width(width);
lnav_data.ld_status[LNS_PREVIEW0].set_visible(vis); lnav_data.ld_status[LNS_PREVIEW0].set_visible(vis);
if (doc_side_by_side && doc_height > 0) { if (doc_side_by_side && doc_height > 0) {
@ -771,6 +782,7 @@ layout_views()
auto has_doc = lnav_data.ld_example_view.get_height() > 0_vl auto has_doc = lnav_data.ld_example_view.get_height() > 0_vl
|| lnav_data.ld_doc_view.get_height() > 0_vl; || lnav_data.ld_doc_view.get_height() > 0_vl;
lnav_data.ld_status[LNS_DOC].set_y(bottom); lnav_data.ld_status[LNS_DOC].set_y(bottom);
lnav_data.ld_status[LNS_DOC].set_width(width);
lnav_data.ld_status[LNS_DOC].set_visible(has_doc && vis); lnav_data.ld_status[LNS_DOC].set_visible(has_doc && vis);
if (is_gantt) { if (is_gantt) {
@ -784,9 +796,10 @@ layout_views()
lnav_data.ld_gantt_details_view.set_visible(vis); lnav_data.ld_gantt_details_view.set_visible(vis);
lnav_data.ld_status[LNS_GANTT].set_y(bottom); lnav_data.ld_status[LNS_GANTT].set_y(bottom);
lnav_data.ld_status[LNS_GANTT].set_width(width);
lnav_data.ld_status[LNS_GANTT].set_visible(vis); lnav_data.ld_status[LNS_GANTT].set_visible(vis);
vis = bottom.try_consume(filter_height + (filters_open ? 1 : 0) vis = bottom.try_consume(filter_height + (config_panel_open ? 1 : 0)
+ (filters_supported ? 1 : 0)); + (filters_supported ? 1 : 0));
lnav_data.ld_filter_view.set_height(vis_line_t(filter_height)); lnav_data.ld_filter_view.set_height(vis_line_t(filter_height));
lnav_data.ld_filter_view.set_y(bottom + 2); lnav_data.ld_filter_view.set_y(bottom + 2);
@ -796,14 +809,16 @@ layout_views()
lnav_data.ld_files_view.set_height(vis_line_t(filter_height)); lnav_data.ld_files_view.set_height(vis_line_t(filter_height));
lnav_data.ld_files_view.set_y(bottom + 2); lnav_data.ld_files_view.set_y(bottom + 2);
lnav_data.ld_files_view.set_width(width); lnav_data.ld_files_view.set_width(width);
lnav_data.ld_files_view.set_visible(filters_open && vis); lnav_data.ld_files_view.set_visible(files_open && vis);
lnav_data.ld_status[LNS_FILTER_HELP].set_visible(filters_open && vis); lnav_data.ld_status[LNS_FILTER_HELP].set_visible(config_panel_open && vis);
lnav_data.ld_status[LNS_FILTER_HELP].set_y(bottom + 1); lnav_data.ld_status[LNS_FILTER_HELP].set_y(bottom + 1);
lnav_data.ld_status[LNS_FILTER_HELP].set_width(width);
lnav_data.ld_status[LNS_FILTER].set_visible(vis); lnav_data.ld_status[LNS_FILTER].set_visible(vis);
lnav_data.ld_status[LNS_FILTER].set_enabled(filters_open); lnav_data.ld_status[LNS_FILTER].set_enabled(config_panel_open);
lnav_data.ld_status[LNS_FILTER].set_y(bottom); lnav_data.ld_status[LNS_FILTER].set_y(bottom);
lnav_data.ld_status[LNS_FILTER].set_width(width);
vis = is_spectro && bottom.try_consume(5 + 1); vis = is_spectro && bottom.try_consume(5 + 1);
lnav_data.ld_spectro_details_view.set_y(bottom + 1); lnav_data.ld_spectro_details_view.set_y(bottom + 1);
@ -812,6 +827,7 @@ layout_views()
lnav_data.ld_spectro_details_view.set_visible(vis); lnav_data.ld_spectro_details_view.set_visible(vis);
lnav_data.ld_status[LNS_SPECTRO].set_y(bottom); lnav_data.ld_status[LNS_SPECTRO].set_y(bottom);
lnav_data.ld_status[LNS_SPECTRO].set_width(width);
lnav_data.ld_status[LNS_SPECTRO].set_visible(vis); lnav_data.ld_status[LNS_SPECTRO].set_visible(vis);
lnav_data.ld_status[LNS_SPECTRO].set_enabled(lnav_data.ld_mode lnav_data.ld_status[LNS_SPECTRO].set_enabled(lnav_data.ld_mode
== ln_mode_t::SPECTRO_DETAILS); == ln_mode_t::SPECTRO_DETAILS);
@ -1402,9 +1418,55 @@ clear_preview()
} }
} }
void
set_view_mode(ln_mode_t mode)
{
static auto* breadcrumb_view = injector::get<breadcrumb_curses*>();
switch (lnav_data.ld_mode) {
case ln_mode_t::BREADCRUMBS: {
breadcrumb_view->blur();
break;
}
default:
break;
}
lnav_data.ld_mode = mode;
}
static std::vector<view_curses*>
all_views()
{
static auto* breadcrumb_view = injector::get<breadcrumb_curses*>();
std::vector<view_curses*> retval;
retval.push_back(breadcrumb_view);
for (auto& sc : lnav_data.ld_status) {
retval.push_back(&sc);
}
retval.push_back(&lnav_data.ld_doc_view);
retval.push_back(&lnav_data.ld_example_view);
retval.push_back(&lnav_data.ld_preview_view[0]);
retval.push_back(&lnav_data.ld_preview_view[1]);
retval.push_back(&lnav_data.ld_files_view);
retval.push_back(&lnav_data.ld_filter_view);
retval.push_back(&lnav_data.ld_user_message_view);
retval.push_back(&lnav_data.ld_spectro_details_view);
retval.push_back(&lnav_data.ld_gantt_details_view);
retval.push_back(lnav_data.ld_rl_view);
return retval;
}
void void
lnav_behavior::mouse_event(int button, bool release, int x, int y) lnav_behavior::mouse_event(int button, bool release, int x, int y)
{ {
static auto* breadcrumb_view = injector::get<breadcrumb_curses*>();
static const std::vector<view_curses*> VIEWS = all_views();
static const auto CLICK_INTERVAL
= std::chrono::milliseconds(mouseinterval(-1) * 2);
struct mouse_event me; struct mouse_event me;
switch (button & xterm_mouse::XT_BUTTON__MASK) { switch (button & xterm_mouse::XT_BUTTON__MASK) {
@ -1425,9 +1487,16 @@ lnav_behavior::mouse_event(int button, bool release, int x, int y)
break; break;
} }
gettimeofday(&me.me_time, nullptr);
me.me_modifiers = button & xterm_mouse::XT_MODIFIER_MASK; me.me_modifiers = button & xterm_mouse::XT_MODIFIER_MASK;
if (button & xterm_mouse::XT_DRAG_FLAG) { if (release
&& (to_mstime(me.me_time)
- to_mstime(this->lb_last_release_event.me_time))
< CLICK_INTERVAL.count())
{
me.me_state = mouse_button_state_t::BUTTON_STATE_DOUBLE_CLICK;
} else if (button & xterm_mouse::XT_DRAG_FLAG) {
me.me_state = mouse_button_state_t::BUTTON_STATE_DRAGGED; me.me_state = mouse_button_state_t::BUTTON_STATE_DRAGGED;
} else if (release) { } else if (release) {
me.me_state = mouse_button_state_t::BUTTON_STATE_RELEASED; me.me_state = mouse_button_state_t::BUTTON_STATE_RELEASED;
@ -1436,8 +1505,9 @@ lnav_behavior::mouse_event(int button, bool release, int x, int y)
} }
auto width = getmaxx(lnav_data.ld_window); auto width = getmaxx(lnav_data.ld_window);
gettimeofday(&me.me_time, nullptr);
me.me_press_x = this->lb_last_event.me_press_x;
me.me_press_y = this->lb_last_event.me_press_y;
me.me_x = x - 1; me.me_x = x - 1;
if (me.me_x >= width) { if (me.me_x >= width) {
me.me_x = width - 1; me.me_x = width - 1;
@ -1445,15 +1515,38 @@ lnav_behavior::mouse_event(int button, bool release, int x, int y)
me.me_y = y - 1; me.me_y = y - 1;
switch (me.me_state) { switch (me.me_state) {
case mouse_button_state_t::BUTTON_STATE_PRESSED: { case mouse_button_state_t::BUTTON_STATE_PRESSED:
case mouse_button_state_t::BUTTON_STATE_DOUBLE_CLICK: {
if (lnav_data.ld_mode == ln_mode_t::BREADCRUMBS) {
if (breadcrumb_view->contains(me.me_x, me.me_y)) {
this->lb_last_view = breadcrumb_view;
break;
} else {
set_view_mode(ln_mode_t::PAGING);
lnav_data.ld_view_stack.set_needs_update();
}
}
auto* tc = *(lnav_data.ld_view_stack.top()); auto* tc = *(lnav_data.ld_view_stack.top());
if (tc->contains(me.me_x, me.me_y)) { if (tc->contains(me.me_x, me.me_y)) {
this->lb_last_view = tc; this->lb_last_view = tc;
} else {
for (auto* vc : VIEWS) {
if (vc->contains(me.me_x, me.me_y)) {
this->lb_last_view = vc;
me.me_press_y = me.me_y - vc->get_y();
me.me_press_x = me.me_x - vc->get_x();
break;
}
}
} }
break; break;
} }
case mouse_button_state_t::BUTTON_STATE_DRAGGED: case mouse_button_state_t::BUTTON_STATE_DRAGGED: {
break;
}
case mouse_button_state_t::BUTTON_STATE_RELEASED: { case mouse_button_state_t::BUTTON_STATE_RELEASED: {
this->lb_last_release_event = me;
break; break;
} }
} }
@ -1465,6 +1558,7 @@ lnav_behavior::mouse_event(int button, bool release, int x, int y)
} }
this->lb_last_event = me; this->lb_last_event = me;
if (me.me_state == mouse_button_state_t::BUTTON_STATE_RELEASED if (me.me_state == mouse_button_state_t::BUTTON_STATE_RELEASED
|| me.me_state == mouse_button_state_t::BUTTON_STATE_DOUBLE_CLICK
|| me.me_button == mouse_button_t::BUTTON_SCROLL_UP || me.me_button == mouse_button_t::BUTTON_SCROLL_UP
|| me.me_button == mouse_button_t::BUTTON_SCROLL_DOWN) || me.me_button == mouse_button_t::BUTTON_SCROLL_DOWN)
{ {

@ -89,6 +89,7 @@ bool handle_winch();
void layout_views(); void layout_views();
void update_hits(textview_curses* tc); void update_hits(textview_curses* tc);
void clear_preview(); void clear_preview();
void set_view_mode(ln_mode_t mode);
nonstd::optional<vis_line_t> next_cluster( nonstd::optional<vis_line_t> next_cluster(
nonstd::optional<vis_line_t> (bookmark_vector<vis_line_t>::*f)(vis_line_t) nonstd::optional<vis_line_t> (bookmark_vector<vis_line_t>::*f)(vis_line_t)
@ -108,6 +109,7 @@ public:
view_curses* lb_last_view{nullptr}; view_curses* lb_last_view{nullptr};
struct mouse_event lb_last_event; struct mouse_event lb_last_event;
struct mouse_event lb_last_release_event;
}; };
#endif #endif

@ -80,11 +80,13 @@ void
xterm_mouse::set_enabled(bool enabled) xterm_mouse::set_enabled(bool enabled)
{ {
if (is_available()) { if (is_available()) {
putp(tparm((char*) XT_TERMCAP, enabled ? 1 : 0)); if (this->xm_enabled != enabled) {
putp(tparm((char*) XT_TERMCAP_TRACKING, enabled ? 1 : 0)); putp(tparm((char*) XT_TERMCAP, enabled ? 1 : 0));
putp(tparm((char*) XT_TERMCAP_SGR, enabled ? 1 : 0)); putp(tparm((char*) XT_TERMCAP_TRACKING, enabled ? 1 : 0));
fflush(stdout); putp(tparm((char*) XT_TERMCAP_SGR, enabled ? 1 : 0));
this->xm_enabled = enabled; fflush(stdout);
this->xm_enabled = enabled;
}
} else { } else {
log_warning("mouse support is not available"); log_warning("mouse support is not available");
} }

@ -91,6 +91,10 @@ main(int argc, char* argv[])
my_source ms; my_source ms;
WINDOW* win; WINDOW* win;
setenv("DUMP_CRASH", "1", 1);
log_install_handlers();
lnav_log_crash_dir = "/tmp";
win = initscr(); win = initscr();
lv.set_data_source(&ms); lv.set_data_source(&ms);
lv.set_window(win); lv.set_window(win);

@ -4644,6 +4644,9 @@
} }
} }
}, },
"mouse": {
"mode": "disabled"
},
"movement": { "movement": {
"mode": "cursor" "mode": "cursor"
}, },

@ -12,48 +12,48 @@
/global/keymap_def_text_view -> default-keymap.json:10 /global/keymap_def_text_view -> default-keymap.json:10
/global/keymap_def_time_offset -> default-keymap.json:17 /global/keymap_def_time_offset -> default-keymap.json:17
/global/keymap_def_zoom -> default-keymap.json:12 /global/keymap_def_zoom -> default-keymap.json:12
/log/annotations/com.vmware.vmacore.backtrace/condition -> root-config.json:20 /log/annotations/com.vmware.vmacore.backtrace/condition -> root-config.json:23
/log/annotations/com.vmware.vmacore.backtrace/description -> root-config.json:19 /log/annotations/com.vmware.vmacore.backtrace/description -> root-config.json:22
/log/annotations/com.vmware.vmacore.backtrace/handler -> root-config.json:21 /log/annotations/com.vmware.vmacore.backtrace/handler -> root-config.json:24
/log/annotations/org.lnav.test/condition -> {test_dir}/configs/installed/anno-test.json:7 /log/annotations/org.lnav.test/condition -> {test_dir}/configs/installed/anno-test.json:7
/log/annotations/org.lnav.test/description -> {test_dir}/configs/installed/anno-test.json:6 /log/annotations/org.lnav.test/description -> {test_dir}/configs/installed/anno-test.json:6
/log/annotations/org.lnav.test/handler -> {test_dir}/configs/installed/anno-test.json:8 /log/annotations/org.lnav.test/handler -> {test_dir}/configs/installed/anno-test.json:8
/log/date-time/convert-zoned-to-local -> root-config.json:15 /log/date-time/convert-zoned-to-local -> root-config.json:18
/tuning/archive-manager/cache-ttl -> root-config.json:28 /tuning/archive-manager/cache-ttl -> root-config.json:31
/tuning/archive-manager/min-free-space -> root-config.json:27 /tuning/archive-manager/min-free-space -> root-config.json:30
/tuning/clipboard/impls/MacOS/find/read -> root-config.json:56 /tuning/clipboard/impls/MacOS/find/read -> root-config.json:59
/tuning/clipboard/impls/MacOS/find/write -> root-config.json:55 /tuning/clipboard/impls/MacOS/find/write -> root-config.json:58
/tuning/clipboard/impls/MacOS/general/read -> root-config.json:52 /tuning/clipboard/impls/MacOS/general/read -> root-config.json:55
/tuning/clipboard/impls/MacOS/general/write -> root-config.json:51 /tuning/clipboard/impls/MacOS/general/write -> root-config.json:54
/tuning/clipboard/impls/MacOS/test -> root-config.json:49 /tuning/clipboard/impls/MacOS/test -> root-config.json:52
/tuning/clipboard/impls/NeoVim/general/read -> root-config.json:84 /tuning/clipboard/impls/NeoVim/general/read -> root-config.json:87
/tuning/clipboard/impls/NeoVim/general/write -> root-config.json:83 /tuning/clipboard/impls/NeoVim/general/write -> root-config.json:86
/tuning/clipboard/impls/NeoVim/test -> root-config.json:81 /tuning/clipboard/impls/NeoVim/test -> root-config.json:84
/tuning/clipboard/impls/Wayland/general/read -> root-config.json:63 /tuning/clipboard/impls/Wayland/general/read -> root-config.json:66
/tuning/clipboard/impls/Wayland/general/write -> root-config.json:62 /tuning/clipboard/impls/Wayland/general/write -> root-config.json:65
/tuning/clipboard/impls/Wayland/test -> root-config.json:60 /tuning/clipboard/impls/Wayland/test -> root-config.json:63
/tuning/clipboard/impls/Windows/general/write -> root-config.json:90 /tuning/clipboard/impls/Windows/general/write -> root-config.json:93
/tuning/clipboard/impls/Windows/test -> root-config.json:88 /tuning/clipboard/impls/Windows/test -> root-config.json:91
/tuning/clipboard/impls/X11-xclip/general/read -> root-config.json:70 /tuning/clipboard/impls/X11-xclip/general/read -> root-config.json:73
/tuning/clipboard/impls/X11-xclip/general/write -> root-config.json:69 /tuning/clipboard/impls/X11-xclip/general/write -> root-config.json:72
/tuning/clipboard/impls/X11-xclip/test -> root-config.json:67 /tuning/clipboard/impls/X11-xclip/test -> root-config.json:70
/tuning/clipboard/impls/tmux/general/read -> root-config.json:77 /tuning/clipboard/impls/tmux/general/read -> root-config.json:80
/tuning/clipboard/impls/tmux/general/write -> root-config.json:76 /tuning/clipboard/impls/tmux/general/write -> root-config.json:79
/tuning/clipboard/impls/tmux/test -> root-config.json:74 /tuning/clipboard/impls/tmux/test -> root-config.json:77
/tuning/piper/max-size -> root-config.json:42 /tuning/piper/max-size -> root-config.json:45
/tuning/piper/rotations -> root-config.json:43 /tuning/piper/rotations -> root-config.json:46
/tuning/piper/ttl -> root-config.json:44 /tuning/piper/ttl -> root-config.json:47
/tuning/remote/ssh/command -> root-config.json:32 /tuning/remote/ssh/command -> root-config.json:35
/tuning/remote/ssh/config/BatchMode -> root-config.json:34 /tuning/remote/ssh/config/BatchMode -> root-config.json:37
/tuning/remote/ssh/config/ConnectTimeout -> root-config.json:35 /tuning/remote/ssh/config/ConnectTimeout -> root-config.json:38
/tuning/remote/ssh/start-command -> root-config.json:37 /tuning/remote/ssh/start-command -> root-config.json:40
/tuning/remote/ssh/transfer-command -> root-config.json:38 /tuning/remote/ssh/transfer-command -> root-config.json:41
/tuning/url-scheme/docker-compose/handler -> root-config.json:100 /tuning/url-scheme/docker-compose/handler -> root-config.json:103
/tuning/url-scheme/docker/handler -> root-config.json:97 /tuning/url-scheme/docker/handler -> root-config.json:100
/tuning/url-scheme/hw/handler -> {test_dir}/configs/installed/hw-url-handler.json:6 /tuning/url-scheme/hw/handler -> {test_dir}/configs/installed/hw-url-handler.json:6
/tuning/url-scheme/journald/handler -> root-config.json:103 /tuning/url-scheme/journald/handler -> root-config.json:106
/tuning/url-scheme/piper/handler -> root-config.json:106 /tuning/url-scheme/piper/handler -> root-config.json:109
/tuning/url-scheme/podman/handler -> root-config.json:109 /tuning/url-scheme/podman/handler -> root-config.json:112
/ui/clock-format -> root-config.json:4 /ui/clock-format -> root-config.json:4
/ui/default-colors -> root-config.json:6 /ui/default-colors -> root-config.json:6
/ui/dim-text -> root-config.json:5 /ui/dim-text -> root-config.json:5
@ -171,7 +171,8 @@
/ui/keymap-defs/us/x38/command -> us-keymap.json:28 /ui/keymap-defs/us/x38/command -> us-keymap.json:28
/ui/keymap-defs/us/x40/command -> us-keymap.json:34 /ui/keymap-defs/us/x40/command -> us-keymap.json:34
/ui/keymap-defs/us/x5e/command -> us-keymap.json:46 /ui/keymap-defs/us/x5e/command -> us-keymap.json:46
/ui/movement/mode -> root-config.json:10 /ui/mouse/mode -> root-config.json:10
/ui/movement/mode -> root-config.json:13
/ui/theme -> root-config.json:8 /ui/theme -> root-config.json:8
/ui/theme-defs/default/highlights/colors/pattern -> default-theme.json:292 /ui/theme-defs/default/highlights/colors/pattern -> default-theme.json:292
/ui/theme-defs/default/highlights/colors/style/color -> default-theme.json:294 /ui/theme-defs/default/highlights/colors/style/color -> default-theme.json:294

@ -467,7 +467,7 @@ mouse to mark lines of text and move the view by grabbing the
scrollbar. scrollbar.
NOTE: You need to manually enable this feature by setting the LNAV_EXP NOTE: You need to manually enable this feature by setting the LNAV_EXP
environment variable to "mouse". F2 toggles mouse support. environment variable to "mouse".  F2  toggles mouse support.
Log Analysis Log Analysis

@ -6,25 +6,25 @@ S -1 ┋
A └ normal A └ normal
CSI Reset Replace mode CSI Reset Replace mode
CSI Erase all CSI Erase all
S 1 ┋17 x┋ S 1 ┋+18 x┋
A └┛ alt A └┛ alt
S 2 ┋+18 x┋ S 2 ┋19 x┋
A └┛ alt A └┛ alt
S 3 ┋19 x┋ S 3 ┋20 x┋
A └┛ alt A └┛ alt
S 4 ┋20 x┋ S 4 ┋21 x┋
A └┛ alt A └┛ alt
S 5 ┋21 x┋ S 5 ┋22 x┋
A └┛ alt A └┛ alt
S 6 ┋22 x┋ S 6 ┋23 x┋
A └┛ alt A └┛ alt
S 7 ┋23 x┋ S 7 ┋24 x┋
A └┛ alt A └┛ alt
S 8 ┋24 x┋ S 8 ┋25 x┋
A └┛ alt A └┛ alt
S 9 ┋25 x┋ S 9 ┋26 x┋
A └┛ alt A └┛ alt
S 10 ┋26 x┋ S 10 ┋27 x┋
A └┛ alt A └┛ alt
CSI Erase all CSI Erase all
CSI Use normal screen buffer CSI Use normal screen buffer

@ -6,25 +6,25 @@ S -1 ┋
A └ normal A └ normal
CSI Reset Replace mode CSI Reset Replace mode
CSI Erase all CSI Erase all
S 1 ┋8 x┋ S 1 ┋9 x┋
A └┛ alt A └┛ alt
S 2 ┋+9 x┋ S 2 ┋10 x┋
A └┛ alt A └┛ alt
S 3 ┋10 x┋ S 3 ┋11 x┋
A └┛ alt A └┛ alt
S 4 ┋11 x┋ S 4 ┋12 x┋
A └┛ alt A └┛ alt
S 5 ┋12 x┋ S 5 ┋13 x┋
A └┛ alt A └┛ alt
S 6 ┋13 x┋ S 6 ┋14 x┋
A └┛ alt A └┛ alt
S 7 ┋14 x┋ S 7 ┋15 x┋
A └┛ alt A └┛ alt
S 8 ┋15 x┋ S 8 ┋16 x┋
A └┛ alt A └┛ alt
S 9 ┋16 x┋ S 9 ┋17 x┋
A └┛ alt A └┛ alt
S 10 ┋17 x┋ S 10 ┋+18 x┋
A └┛ alt A └┛ alt
CSI Erase all CSI Erase all
CSI Use normal screen buffer CSI Use normal screen buffer

Loading…
Cancel
Save