mirror of
https://github.com/tstack/lnav.git
synced 2024-10-05 17:17:37 +03:00
[mouse] flesh out things more
This commit is contained in:
parent
7341dc1f1a
commit
53ab7b14a6
23
NEWS.md
23
NEWS.md
@ -9,6 +9,28 @@ Features:
|
||||
of archive contents.
|
||||
* Added `humanize_id` SQL function that colorizes a string using
|
||||
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:
|
||||
* 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
|
||||
has been fixed.
|
||||
* A crash when previewing non-text files.
|
||||
* Various fixes to make lnav usable as a `PAGER`.
|
||||
|
||||
## lnav v0.12.1
|
||||
|
||||
|
@ -732,6 +732,27 @@
|
||||
},
|
||||
"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": {
|
||||
"description": "Log file cursor movement mode settings",
|
||||
"title": "/ui/movement",
|
||||
|
@ -113,16 +113,62 @@ To create or customize a theme, consult the :ref:`themes` section.
|
||||
Cursor Mode (v0.11.2+)
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The default mode for scrolling in **lnav** is to move the contents of the
|
||||
main view 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.
|
||||
Alternatively, you can enable "cursor" mode where there is a cursor line
|
||||
in the view that is moved by the arrow keys and other interactions. You
|
||||
can enable cursor mode with the following command:
|
||||
The default mode for scrolling in **lnav** is "cursor" mode where
|
||||
there is a cursor line in the view that is moved by the arrow keys
|
||||
and other interactions. Any interactions, such as jumping to a
|
||||
search hit, are then focused on that line.
|
||||
|
||||
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 cursor
|
||||
: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
|
||||
|
||||
: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
|
||||
^^^^^^^^^^^
|
||||
|
@ -17,7 +17,7 @@
|
||||
;SELECT printf('\n%d total requests', count(1)) AS msg FROM access_log
|
||||
:echo $msg
|
||||
|
||||
;WITH top_paths AS (
|
||||
;WITH top_paths AS MATERIALIZED (
|
||||
SELECT
|
||||
cs_uri_stem,
|
||||
count(1) AS total_hits,
|
||||
@ -28,7 +28,7 @@
|
||||
GROUP BY cs_uri_stem
|
||||
ORDER BY total_hits DESC
|
||||
LIMIT 50),
|
||||
weekly_hits_with_gaps AS (
|
||||
weekly_hits_with_gaps AS MATERIALIZED (
|
||||
SELECT timeslice(log_time_msecs, '1w') AS week,
|
||||
cs_uri_stem,
|
||||
count(1) AS weekly_hits
|
||||
|
@ -34,6 +34,11 @@
|
||||
|
||||
using namespace lnav::roles::literals;
|
||||
|
||||
void
|
||||
breadcrumb_curses::no_op_action(breadcrumb_curses&)
|
||||
{
|
||||
}
|
||||
|
||||
breadcrumb_curses::breadcrumb_curses()
|
||||
{
|
||||
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_displayed_crumbs.clear();
|
||||
attr_line_t crumbs_line;
|
||||
for (const auto& crumb : crumbs) {
|
||||
auto accum_width = crumbs_line.column_width();
|
||||
@ -78,17 +84,21 @@ breadcrumb_curses::do_update()
|
||||
accum_width = 2;
|
||||
}
|
||||
|
||||
line_range crumb_range;
|
||||
crumb_range.lr_start = (int) crumbs_line.length();
|
||||
crumbs_line.append(crumb.c_display_value);
|
||||
crumb_range.lr_end = (int) crumbs_line.length();
|
||||
if (is_selected) {
|
||||
sel_crumb_offset = accum_width;
|
||||
crumbs_line.get_attrs().emplace_back(
|
||||
line_range{
|
||||
(int) (crumbs_line.length()
|
||||
- crumb.c_display_value.length()),
|
||||
(int) crumbs_line.length(),
|
||||
},
|
||||
VC_STYLE.template value(text_attrs{A_REVERSE}));
|
||||
crumb_range, 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;
|
||||
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_selection(
|
||||
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->set_needs_update();
|
||||
}
|
||||
@ -454,3 +467,77 @@ breadcrumb_curses::search_overlay_source::list_static_overlay(
|
||||
|
||||
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 {
|
||||
public:
|
||||
using action = std::function<void(breadcrumb_curses&)>;
|
||||
|
||||
breadcrumb_curses();
|
||||
|
||||
void set_window(WINDOW* win)
|
||||
@ -53,6 +55,8 @@ public:
|
||||
this->bc_line_source = std::move(ls);
|
||||
}
|
||||
|
||||
bool handle_mouse(mouse_event& me) override;
|
||||
|
||||
void focus();
|
||||
void blur();
|
||||
|
||||
@ -62,6 +66,11 @@ public:
|
||||
|
||||
void reload_data();
|
||||
|
||||
static void no_op_action(breadcrumb_curses&);
|
||||
|
||||
action on_focus{no_op_action};
|
||||
action on_blur{no_op_action};
|
||||
|
||||
private:
|
||||
class search_overlay_source : public list_overlay_source {
|
||||
public:
|
||||
@ -92,6 +101,19 @@ private:
|
||||
plain_text_source bc_match_source;
|
||||
search_overlay_source bc_match_search_overlay;
|
||||
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
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "files_sub_source.hh"
|
||||
|
||||
#include "base/ansi_scrubber.hh"
|
||||
#include "base/attr_line.builder.hh"
|
||||
#include "base/fs_util.hh"
|
||||
#include "base/humanize.hh"
|
||||
#include "base/humanize.network.hh"
|
||||
@ -40,6 +41,8 @@
|
||||
#include "mapbox/variant.hpp"
|
||||
#include "sql_util.hh"
|
||||
|
||||
using namespace lnav::roles::literals;
|
||||
|
||||
namespace files_model {
|
||||
files_list_selection
|
||||
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,
|
||||
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& fc = lnav_data.ld_active_files;
|
||||
auto filename_width
|
||||
= std::min(fc.fc_largest_path_length,
|
||||
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);
|
||||
|
||||
if (line < errs->size()) {
|
||||
auto iter = errs->begin();
|
||||
std::advance(iter, line);
|
||||
auto iter = std::next(errs->begin(), line);
|
||||
auto path = ghc::filesystem::path(iter->first);
|
||||
auto fn = fmt::to_string(path.filename());
|
||||
|
||||
truncate_to(fn, filename_width);
|
||||
value_out = fmt::format(FMT_STRING(" {:<{}} {}"),
|
||||
fn,
|
||||
filename_width,
|
||||
iter->second.fei_description);
|
||||
al.append(" ");
|
||||
{
|
||||
auto ag = alb.with_attr(VC_ROLE.value(role_t::VCR_ERROR));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -270,23 +292,36 @@ files_sub_source::text_value_for_line(textview_curses& tc,
|
||||
}
|
||||
|
||||
if (line < fc.fc_other_files.size()) {
|
||||
auto iter = fc.fc_other_files.begin();
|
||||
std::advance(iter, line);
|
||||
auto iter = std::next(fc.fc_other_files.begin(), line);
|
||||
auto path = ghc::filesystem::path(iter->first);
|
||||
auto fn = fmt::to_string(path);
|
||||
|
||||
truncate_to(fn, filename_width);
|
||||
value_out = fmt::format(FMT_STRING(" {:<{}} {:14} {}"),
|
||||
fn,
|
||||
filename_width,
|
||||
iter->second.ofd_format,
|
||||
iter->second.ofd_description);
|
||||
al.append(" ");
|
||||
{
|
||||
auto ag = alb.with_attr(VC_ROLE.value(role_t::VCR_FILE));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
line -= fc.fc_other_files.size();
|
||||
|
||||
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()));
|
||||
char start_time[64] = "", end_time[64] = "";
|
||||
std::vector<std::string> file_notes;
|
||||
@ -299,14 +334,37 @@ files_sub_source::text_value_for_line(textview_curses& tc,
|
||||
for (const auto& pair : lf->get_notes()) {
|
||||
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, "; "));
|
||||
|
||||
al.append(" ");
|
||||
if (ld_opt) {
|
||||
if (ld_opt.value()->ld_visible) {
|
||||
al.append("\u25c6"_ok);
|
||||
} else {
|
||||
al.append("\u25c7"_comment);
|
||||
}
|
||||
} 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));
|
||||
|
||||
al.appendf(FMT_STRING("{:>8}"),
|
||||
humanize::file_size(lf->get_index_size(),
|
||||
humanize::alignment::columnar));
|
||||
}
|
||||
al.append(" ")
|
||||
.append(start_time)
|
||||
.append(" \u2014 ")
|
||||
.append(end_time)
|
||||
.appendf(FMT_STRING("{}"), fmt::join(file_notes, "; "));
|
||||
if (selected) {
|
||||
al.with_attr_for_all(VC_ROLE.value(role_t::VCR_FOCUSED));
|
||||
}
|
||||
|
||||
value_out = al.get_string();
|
||||
this->fss_last_line_len
|
||||
= filename_width + 23 + strlen(start_time) + strlen(end_time);
|
||||
}
|
||||
@ -316,77 +374,7 @@ 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));
|
||||
}
|
||||
|
||||
{
|
||||
safe::ReadAccess<safe_name_to_errors> errs(*fc.fc_name_to_errors);
|
||||
|
||||
if (line < errs->size()) {
|
||||
if (selected) {
|
||||
value_out.emplace_back(
|
||||
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();
|
||||
}
|
||||
|
||||
if (line < fc.fc_other_files.size()) {
|
||||
if (selected) {
|
||||
value_out.emplace_back(line_range{0, -1},
|
||||
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;
|
||||
}
|
||||
|
||||
line -= fc.fc_other_files.size();
|
||||
|
||||
if (selected) {
|
||||
value_out.emplace_back(line_range{0, -1},
|
||||
VC_ROLE.value(role_t::VCR_FOCUSED));
|
||||
}
|
||||
|
||||
auto& lss = lnav_data.ld_log_source;
|
||||
auto& lf = fc.fc_files[line];
|
||||
auto ld_opt = lss.find_data(lf);
|
||||
|
||||
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;
|
||||
lr.lr_end = -1;
|
||||
value_out.emplace_back(lr, VC_FOREGROUND.value(COLOR_YELLOW));
|
||||
value_out = this->fss_curr_line.get_attrs();
|
||||
}
|
||||
|
||||
size_t
|
||||
@ -450,3 +438,16 @@ files_overlay_source::list_static_overlay(const listview_curses& lv,
|
||||
|
||||
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
|
||||
: public text_sub_source
|
||||
, public list_input_delegate {
|
||||
, public list_input_delegate
|
||||
, public text_delegate {
|
||||
public:
|
||||
files_sub_source();
|
||||
|
||||
@ -60,7 +61,10 @@ public:
|
||||
int line,
|
||||
line_flags_t raw) override;
|
||||
|
||||
bool text_handle_mouse(textview_curses& tc, mouse_event& me) override;
|
||||
|
||||
size_t fss_last_line_len{0};
|
||||
attr_line_t fss_curr_line;
|
||||
};
|
||||
|
||||
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_value(" " ANSI_ROLE("T") "ext Filters ",
|
||||
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_stitch_value(
|
||||
@ -73,6 +75,8 @@ filter_status_source::filter_status_source()
|
||||
role_t::VCR_STATUS_DISABLED_TITLE);
|
||||
this->tss_fields[TSF_FILES_TITLE].set_value(" " ANSI_ROLE("F") "iles ",
|
||||
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_stitch_value(
|
||||
|
@ -29,6 +29,7 @@
|
||||
|
||||
#include "filter_sub_source.hh"
|
||||
|
||||
#include "base/attr_line.builder.hh"
|
||||
#include "base/enum_util.hh"
|
||||
#include "base/func_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);
|
||||
}
|
||||
|
||||
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
|
||||
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();
|
||||
|
||||
this->fss_editing = true;
|
||||
this->tss_view->vc_enabled = false;
|
||||
|
||||
add_view_text_possibilities(this->fss_editor.get(),
|
||||
filter_lang_t::REGEX,
|
||||
@ -204,6 +214,7 @@ filter_sub_source::list_input_handle_key(listview_curses& lv, int ch)
|
||||
lv.reload_data();
|
||||
|
||||
this->fss_editing = true;
|
||||
this->tss_view->vc_enabled = false;
|
||||
|
||||
add_view_text_possibilities(this->fss_editor.get(),
|
||||
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());
|
||||
|
||||
this->fss_editing = true;
|
||||
this->tss_view->vc_enabled = false;
|
||||
|
||||
auto tq = tf->get_lang() == filter_lang_t::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& fs = tss->get_filters();
|
||||
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()) {
|
||||
case text_filter::INCLUDE:
|
||||
value_out.append(" IN ");
|
||||
al.append(" ").append(lnav::roles::ok("IN")).append(" ");
|
||||
break;
|
||||
case text_filter::EXCLUDE:
|
||||
if (tf->get_lang() == filter_lang_t::REGEX) {
|
||||
value_out.append("OUT ");
|
||||
al.append(lnav::roles::error("OUT")).append(" ");
|
||||
} else {
|
||||
value_out.append(" ");
|
||||
al.append(" ");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@ -333,60 +362,19 @@ filter_sub_source::text_value_for_line(textview_curses& tc,
|
||||
break;
|
||||
}
|
||||
|
||||
if (this->fss_editing && line == tc.get_selection()) {
|
||||
fmt::format_to(
|
||||
std::back_inserter(value_out), FMT_STRING("{:>9} hits | "), "-");
|
||||
} else {
|
||||
fmt::format_to(std::back_inserter(value_out),
|
||||
FMT_STRING("{:>9L} hits | "),
|
||||
tss->get_filtered_count_for(tf->get_index()));
|
||||
{
|
||||
auto ag = alb.with_attr(VC_ROLE.value(role_t::VCR_NUMBER));
|
||||
if (this->fss_editing && line == tc.get_selection()) {
|
||||
alb.appendf(FMT_STRING("{:>9}"), "-");
|
||||
} else {
|
||||
alb.appendf(FMT_STRING("{:>9}"),
|
||||
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
|
||||
: 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));
|
||||
al.append(" hits ").append("|", VC_GRAPHIC.value(ACS_VLINE)).append(" ");
|
||||
|
||||
attr_line_t content{tf->get_id()};
|
||||
auto& content_attrs = content.get_attrs();
|
||||
|
||||
switch (tf->get_lang()) {
|
||||
case filter_lang_t::REGEX:
|
||||
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:
|
||||
break;
|
||||
}
|
||||
al.append(content);
|
||||
|
||||
shift_string_attrs(content_attrs, 0, 25);
|
||||
value_out.insert(
|
||||
value_out.end(), content_attrs.begin(), content_attrs.end());
|
||||
if (selected) {
|
||||
al.with_attr_for_all(VC_ROLE.value(role_t::VCR_FOCUSED));
|
||||
}
|
||||
|
||||
value_out = al.get_string();
|
||||
}
|
||||
|
||||
void
|
||||
filter_sub_source::text_attrs_for_line(textview_curses& tc,
|
||||
int line,
|
||||
string_attrs_t& value_out)
|
||||
{
|
||||
value_out = this->fss_curr_line.get_attrs();
|
||||
}
|
||||
|
||||
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_filter_help_status_source.fss_prompt.clear();
|
||||
this->fss_editing = false;
|
||||
this->tss_view->vc_enabled = true;
|
||||
this->fss_editor->set_visible(false);
|
||||
top_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->fss_editor->set_visible(false);
|
||||
this->fss_editing = false;
|
||||
this->tss_view->vc_enabled = true;
|
||||
this->tss_view->set_needs_update();
|
||||
tf->set_enabled(this->fss_filter_state);
|
||||
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_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
|
||||
: public text_sub_source
|
||||
, public list_input_delegate {
|
||||
, public list_input_delegate
|
||||
, public text_delegate {
|
||||
public:
|
||||
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 register_view(textview_curses* tc) override;
|
||||
|
||||
size_t text_line_count() override;
|
||||
|
||||
size_t text_line_width(textview_curses& curses) override;
|
||||
@ -69,6 +72,8 @@ public:
|
||||
int line,
|
||||
line_flags_t raw) override;
|
||||
|
||||
bool text_handle_mouse(textview_curses& tc, mouse_event& me) override;
|
||||
|
||||
void rl_change(readline_curses* rc);
|
||||
|
||||
void rl_perform(readline_curses* rc);
|
||||
@ -84,6 +89,7 @@ public:
|
||||
std::shared_ptr<readline_curses> fss_editor;
|
||||
plain_text_source fss_match_source;
|
||||
textview_curses fss_match_view;
|
||||
attr_line_t fss_curr_line;
|
||||
|
||||
bool fss_editing{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.
|
||||
|
||||
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
|
||||
|
||||
|
@ -50,6 +50,14 @@ listview_curses::listview_curses() : lv_scroll(noop_func{}) {}
|
||||
bool
|
||||
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();
|
||||
|
||||
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);
|
||||
} else if (this->lv_sync_selection_and_top) {
|
||||
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) {
|
||||
this->set_top(this->lv_selection);
|
||||
} else if (this->lv_selection
|
||||
>= (this->lv_top + height - this->lv_tail_space - 1_vl))
|
||||
{
|
||||
auto diff = this->lv_selection
|
||||
- (this->lv_top + height - this->lv_tail_space - 1_vl);
|
||||
} else if (this->lv_selection > (this->lv_top + height - 1_vl)) {
|
||||
auto diff = this->lv_selection - (this->lv_top + height - 1_vl);
|
||||
|
||||
if (height < 10 || diff < (height / 8_vl)) {
|
||||
// for small differences between the bottom and the
|
||||
// selection, just move a little bit.
|
||||
this->set_top(
|
||||
this->lv_selection - height + 1_vl + this->lv_tail_space, true);
|
||||
this->set_top(this->lv_selection - height + 1_vl, true);
|
||||
} else {
|
||||
// for large differences, put the focus in the middle
|
||||
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;
|
||||
|
||||
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->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()) {
|
||||
this->set_selection(this->get_inner_height() - 1_vl);
|
||||
this->set_selection_without_context(this->get_inner_height()
|
||||
- 1_vl);
|
||||
} else {
|
||||
auto curr_sel = this->get_selection();
|
||||
|
||||
@ -136,7 +139,7 @@ listview_curses::reload_data()
|
||||
curr_sel = 0_vl;
|
||||
}
|
||||
this->lv_selection = -1_vl;
|
||||
this->set_selection(curr_sel);
|
||||
this->set_selection_without_context(curr_sel);
|
||||
}
|
||||
|
||||
this->update_top_from_selection();
|
||||
@ -425,7 +428,6 @@ listview_curses::do_update()
|
||||
}
|
||||
|
||||
size_t row_count = this->get_inner_height();
|
||||
size_t blank_rows = 0;
|
||||
row = this->lv_top;
|
||||
bottom = y + height;
|
||||
std::vector<attr_line_t> rows(
|
||||
@ -586,14 +588,11 @@ listview_curses::do_update()
|
||||
this->lv_display_lines.push_back(empty_space{});
|
||||
mvwhline(this->lv_window, y, this->vc_x, ' ', width);
|
||||
++y;
|
||||
blank_rows += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->lv_selectable && !this->lv_sync_selection_and_top
|
||||
&& this->lv_selection >= 0 && (row > this->lv_tail_space)
|
||||
&& (blank_rows < this->lv_tail_space)
|
||||
&& ((row - this->lv_tail_space) < this->lv_selection))
|
||||
&& this->lv_selection >= 0 && row < this->lv_selection)
|
||||
{
|
||||
this->shift_top(this->lv_selection - row + this->lv_tail_space);
|
||||
continue;
|
||||
@ -649,13 +648,15 @@ listview_curses::do_update()
|
||||
|
||||
if (this->lv_show_bottom_border) {
|
||||
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++) {
|
||||
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;
|
||||
@ -763,7 +764,7 @@ listview_curses::shift_selection(shift_amount_t sa)
|
||||
{
|
||||
this->set_top(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 {
|
||||
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
|
||||
&& 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
|
||||
= 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;
|
||||
struct timeval diff;
|
||||
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;
|
||||
if (this->lv_selectable) {
|
||||
if (this->lv_selection < 0_vl) {
|
||||
this->set_selection(top);
|
||||
this->set_selection_without_context(top);
|
||||
} else if (this->lv_selection < top) {
|
||||
auto sel_diff = this->lv_selection - old_top;
|
||||
this->set_selection(top + sel_diff);
|
||||
this->set_selection_without_context(top + sel_diff);
|
||||
} else {
|
||||
auto sel_diff = this->lv_selection - old_top;
|
||||
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);
|
||||
|
||||
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
|
||||
|| bot < this->lv_selection)
|
||||
{
|
||||
if (top + sel_diff > bot) {
|
||||
this->set_selection(bot);
|
||||
this->set_selection_without_context(bot);
|
||||
} 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
|
||||
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_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
|
||||
listview_curses::get_top_for_last_row()
|
||||
{
|
||||
|
@ -225,6 +225,8 @@ public:
|
||||
|
||||
void set_selection(vis_line_t sel);
|
||||
|
||||
void set_selection_without_context(vis_line_t sel);
|
||||
|
||||
enum class shift_amount_t {
|
||||
up_line,
|
||||
up_page,
|
||||
|
89
src/lnav.cc
89
src/lnav.cc
@ -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_crumbs = injector::bind<breadcrumb_curses>::to_singleton();
|
||||
|
||||
static auto bound_curl
|
||||
= injector::bind_multiple<isc::service_base>()
|
||||
.add_singleton<curl_looper, services::curl_streamer_t>();
|
||||
@ -274,8 +276,6 @@ force_linking(services::main_t anno)
|
||||
}
|
||||
} // namespace injector
|
||||
|
||||
static breadcrumb_curses breadcrumb_view;
|
||||
|
||||
struct lnav_data_t lnav_data;
|
||||
|
||||
bool
|
||||
@ -367,6 +367,12 @@ static void
|
||||
handle_rl_key(int 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_NPAGE:
|
||||
case KEY_CTRL('p'):
|
||||
@ -670,6 +676,14 @@ handle_config_ui_key(int ch)
|
||||
{
|
||||
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) {
|
||||
case ln_mode_t::FILES:
|
||||
retval = lnav_data.ld_files_view.handle_key(ch);
|
||||
@ -722,6 +736,8 @@ handle_config_ui_key(int ch)
|
||||
static bool
|
||||
handle_key(int ch)
|
||||
{
|
||||
static auto* breadcrumb_view = injector::get<breadcrumb_curses*>();
|
||||
|
||||
lnav_data.ld_input_state.push_back(ch);
|
||||
|
||||
switch (ch) {
|
||||
@ -731,7 +747,7 @@ handle_key(int ch)
|
||||
switch (lnav_data.ld_mode) {
|
||||
case ln_mode_t::PAGING:
|
||||
if (ch == '`') {
|
||||
breadcrumb_view.focus();
|
||||
breadcrumb_view->focus();
|
||||
lnav_data.ld_mode = ln_mode_t::BREADCRUMBS;
|
||||
return true;
|
||||
}
|
||||
@ -739,7 +755,7 @@ handle_key(int ch)
|
||||
return handle_paging_key(ch);
|
||||
|
||||
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_view_stack.set_needs_update();
|
||||
return true;
|
||||
@ -986,6 +1002,7 @@ looper()
|
||||
{
|
||||
static auto* ps = injector::get<pollable_supervisor*>();
|
||||
static auto* filter_source = injector::get<filter_sub_source*>();
|
||||
static auto* breadcrumb_view = injector::get<breadcrumb_curses*>();
|
||||
|
||||
try {
|
||||
auto* sql_cmd_map = injector::get<readline_context::command_map_t*,
|
||||
@ -1147,10 +1164,12 @@ looper()
|
||||
|
||||
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_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();
|
||||
keypad(stdscr, TRUE);
|
||||
@ -1219,7 +1238,6 @@ looper()
|
||||
execute_examples();
|
||||
|
||||
rlc->set_window(lnav_data.ld_window);
|
||||
rlc->set_y(-1);
|
||||
rlc->set_focus_action(rl_focus);
|
||||
rlc->set_change_action(rl_change);
|
||||
rlc->set_perform_action(rl_callback);
|
||||
@ -1252,9 +1270,15 @@ looper()
|
||||
|
||||
vsb.push_back(sb);
|
||||
|
||||
breadcrumb_view.set_y(1);
|
||||
breadcrumb_view.set_window(lnav_data.ld_window);
|
||||
breadcrumb_view.set_line_source(lnav_crumb_source);
|
||||
breadcrumb_view->on_focus
|
||||
= [](breadcrumb_curses&) { set_view_mode(ln_mode_t::BREADCRUMBS); };
|
||||
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 top_view = lnav_data.ld_view_stack.top();
|
||||
|
||||
@ -1274,6 +1298,13 @@ looper()
|
||||
= role_t::VCR_DISABLED_CURSOR_LINE;
|
||||
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_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_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_window(lnav_data.ld_window);
|
||||
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_window(lnav_data.ld_window);
|
||||
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>>();
|
||||
|
||||
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_default_role(
|
||||
role_t::VCR_INACTIVE_STATUS);
|
||||
@ -1551,12 +1610,12 @@ looper()
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
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_example_view.do_update();
|
||||
@ -1577,7 +1636,7 @@ looper()
|
||||
if (filter_source->fss_editing) {
|
||||
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
|
||||
// put the cursor in the right place.
|
||||
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_source[0]);
|
||||
lnav_data.ld_filter_view.set_sub_source(filter_source)
|
||||
.add_input_delegate(*filter_source)
|
||||
.add_child_view(&filter_source->fss_match_view)
|
||||
.add_child_view(filter_source->fss_editor.get());
|
||||
.add_input_delegate(*filter_source);
|
||||
lnav_data.ld_files_view.set_sub_source(&lnav_data.ld_files_source)
|
||||
.add_input_delegate(lnav_data.ld_files_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);
|
||||
auto* tc = *lnav_data.ld_view_stack.top();
|
||||
nonstd::optional<vis_line_t> dst_vl;
|
||||
auto is_location = false;
|
||||
|
||||
if (startswith(all_args, "#")) {
|
||||
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) {
|
||||
return ec.make_error("unable to find anchor: {}", all_args);
|
||||
}
|
||||
is_location = true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
dst_vl | [&ec, tc, &retval](auto new_top) {
|
||||
dst_vl | [&ec, tc, &retval, is_location](auto new_top) {
|
||||
if (ec.ec_dry_run) {
|
||||
retval = "info: will move to line "
|
||||
+ 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() |
|
||||
[new_top](auto lh) { lh->loc_history_append(new_top); };
|
||||
tc->set_selection(new_top);
|
||||
if (tc->is_selectable() && is_location) {
|
||||
tc->set_top(new_top - 2_vl, false);
|
||||
}
|
||||
|
||||
retval = "";
|
||||
}
|
||||
@ -1210,7 +1215,12 @@ com_goto_location(exec_context& ec,
|
||||
? lh->loc_history_back(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());
|
||||
if (tc->is_selectable()) {
|
||||
tc->set_top(adj_opt.value() - 2_vl, false);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(retval);
|
||||
@ -1268,6 +1281,9 @@ com_prev_section(exec_context& ec,
|
||||
}
|
||||
|
||||
tc->set_selection(adj_opt.value());
|
||||
if (tc->is_selectable()) {
|
||||
tc->set_top(adj_opt.value() - 2_vl, false);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(retval);
|
||||
|
@ -382,13 +382,11 @@ update_installs_from_git()
|
||||
git_dir.string());
|
||||
int ret = system(pull_cmd.c_str());
|
||||
if (ret == -1) {
|
||||
std::cerr << "Failed to spawn command "
|
||||
<< "\"" << pull_cmd << "\": " << strerror(errno)
|
||||
<< std::endl;
|
||||
std::cerr << "Failed to spawn command " << "\"" << pull_cmd
|
||||
<< "\": " << strerror(errno) << std::endl;
|
||||
retval = false;
|
||||
} else if (ret > 0) {
|
||||
std::cerr << "Command "
|
||||
<< "\"" << pull_cmd
|
||||
std::cerr << "Command " << "\"" << pull_cmd
|
||||
<< "\" failed: " << strerror(errno) << std::endl;
|
||||
retval = false;
|
||||
}
|
||||
@ -561,6 +559,23 @@ static const struct json_path_container movement_handlers = {
|
||||
.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 = {
|
||||
yajlpp::pattern_property_handler("(?<var_name>\\w+)")
|
||||
.with_synopsis("<name>")
|
||||
@ -1130,6 +1145,9 @@ static const struct json_path_container ui_handlers = {
|
||||
yajlpp::property_handler("theme-defs")
|
||||
.with_description("Theme definitions.")
|
||||
.with_children(theme_defs_handlers),
|
||||
yajlpp::property_handler("mouse")
|
||||
.with_description("Mouse-related settings")
|
||||
.with_children(mouse_handlers),
|
||||
yajlpp::property_handler("movement")
|
||||
.with_description("Log file cursor movement mode settings")
|
||||
.with_children(movement_handlers),
|
||||
|
@ -97,6 +97,11 @@ struct movement_config {
|
||||
config_movement_mode mode{config_movement_mode::TOP};
|
||||
};
|
||||
|
||||
enum class lnav_mouse_mode {
|
||||
disabled,
|
||||
enabled,
|
||||
};
|
||||
|
||||
struct _lnav_config {
|
||||
top_status_source_cfg lc_top_status_cfg;
|
||||
bool lc_ui_dim_text;
|
||||
@ -104,6 +109,7 @@ struct _lnav_config {
|
||||
std::string lc_ui_keymap;
|
||||
std::string lc_ui_theme;
|
||||
movement_config lc_ui_movement;
|
||||
lnav_mouse_mode lc_mouse_mode;
|
||||
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_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();
|
||||
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);
|
||||
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](const auto 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) {
|
||||
@ -337,6 +341,10 @@ plain_text_source::text_crumbs_for_line(int line,
|
||||
this->line_for_offset(sib->hn_start) |
|
||||
[this](const auto 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;
|
||||
} else {
|
||||
int context, prompt_start = 0;
|
||||
int context, prompt_start = 0, new_point = 0;
|
||||
char type[1024];
|
||||
|
||||
msg[rc] = '\0';
|
||||
@ -1047,6 +1047,14 @@ readline_curses::start()
|
||||
log_perror(chdir(cwd));
|
||||
} else if (startswith(msg, "sugg:")) {
|
||||
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)
|
||||
== 1)
|
||||
{
|
||||
@ -1671,6 +1679,25 @@ readline_curses::do_update()
|
||||
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
|
||||
readline_curses::get_match_string() const
|
||||
{
|
||||
|
@ -188,6 +188,8 @@ public:
|
||||
|
||||
bool do_update() override;
|
||||
|
||||
bool handle_mouse(mouse_event& me) override;
|
||||
|
||||
void window_change();
|
||||
|
||||
void line_ready(const char* line);
|
||||
|
@ -6,6 +6,9 @@
|
||||
"default-colors": true,
|
||||
"keymap": "default",
|
||||
"theme": "default",
|
||||
"mouse": {
|
||||
"mode": "disabled"
|
||||
},
|
||||
"movement": {
|
||||
"mode": "cursor"
|
||||
}
|
||||
|
@ -216,7 +216,7 @@ shared_buffer_ref::widen(narrow_result old_data_length)
|
||||
void
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -34,10 +34,19 @@
|
||||
#endif
|
||||
|
||||
#include "config.h"
|
||||
#include "pcrepp/pcre2pp.hh"
|
||||
#include "shlex.hh"
|
||||
|
||||
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
|
||||
shlex::to_attr_line(const shlex::tokenize_error_t& te) const
|
||||
{
|
||||
|
@ -59,6 +59,8 @@ enum class shlex_token_t {
|
||||
|
||||
class shlex {
|
||||
public:
|
||||
static std::string escape(std::string s);
|
||||
|
||||
shlex(const char* str, size_t len) : s_str(str), s_len(len) {}
|
||||
|
||||
explicit shlex(const string_fragment& sf)
|
||||
|
@ -35,8 +35,14 @@
|
||||
#include "statusview_curses.hh"
|
||||
|
||||
#include "base/ansi_scrubber.hh"
|
||||
#include "base/itertools.hh"
|
||||
#include "config.h"
|
||||
|
||||
void
|
||||
status_field::no_op_action(status_field&)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
status_field::set_value(std::string value)
|
||||
{
|
||||
@ -61,7 +67,7 @@ status_field::do_cylon()
|
||||
: (this->sf_width - (cycle_pos - this->sf_width) - 1);
|
||||
auto stop = std::min(start + 3, this->sf_width);
|
||||
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);
|
||||
attrs.ta_attrs |= A_REVERSE;
|
||||
@ -87,10 +93,11 @@ status_field::set_stitch_value(role_t left, role_t right)
|
||||
bool
|
||||
statusview_curses::do_update()
|
||||
{
|
||||
int top, field, field_count, left = 0, right;
|
||||
int top, left = 0, right;
|
||||
auto& vc = view_colors::singleton();
|
||||
unsigned long width, height;
|
||||
|
||||
this->sc_displayed_fields.clear();
|
||||
if (!this->vc_visible || this->sc_window == nullptr) {
|
||||
return false;
|
||||
}
|
||||
@ -110,8 +117,8 @@ statusview_curses::do_update()
|
||||
whline(this->sc_window, ' ', width);
|
||||
|
||||
if (this->sc_source != nullptr) {
|
||||
field_count = this->sc_source->statusview_fields();
|
||||
for (field = 0; field < field_count; field++) {
|
||||
auto field_count = this->sc_source->statusview_fields();
|
||||
for (size_t field = 0; field < field_count; field++) {
|
||||
auto& sf = this->sc_source->statusview_value_for_field(field);
|
||||
struct line_range lr(0, sf.get_width());
|
||||
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);
|
||||
@ -240,3 +251,23 @@ statusview_curses::window_change()
|
||||
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 {
|
||||
public:
|
||||
using action = std::function<void(status_field&)>;
|
||||
|
||||
/**
|
||||
* @param width The maximum width of the field in characters.
|
||||
* @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; }
|
||||
|
||||
static void no_op_action(status_field&);
|
||||
|
||||
action on_click{no_op_action};
|
||||
|
||||
protected:
|
||||
ssize_t sf_width; /*< The maximum 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 handle_mouse(mouse_event& me) override;
|
||||
|
||||
private:
|
||||
status_data_source* sc_source{nullptr};
|
||||
WINDOW* sc_window{nullptr};
|
||||
bool sc_enabled{true};
|
||||
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
|
||||
|
@ -988,6 +988,10 @@ textfile_sub_source::set_top_from_off(file_off_t off)
|
||||
|
||||
if (new_top_opt) {
|
||||
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;
|
||||
vis_line_t height;
|
||||
|
||||
if (!this->tc_selection_start && listview_curses::handle_mouse(me)) {
|
||||
return true;
|
||||
if (!this->vc_visible || this->lv_height == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->tc_delegate != nullptr
|
||||
&& this->tc_delegate->text_handle_mouse(*this, me))
|
||||
{
|
||||
if (!this->tc_selection_start && listview_curses::handle_mouse(me)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -410,22 +408,64 @@ textview_curses::handle_mouse(mouse_event& me)
|
||||
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);
|
||||
|
||||
auto* sub_delegate = dynamic_cast<text_delegate*>(this->tc_sub_source);
|
||||
|
||||
switch (me.me_state) {
|
||||
case mouse_button_state_t::BUTTON_STATE_PRESSED: {
|
||||
if (!this->lv_selectable) {
|
||||
this->set_selectable(true);
|
||||
}
|
||||
mouse_line.match(
|
||||
[this, &me](const main_content& mc) {
|
||||
if (me.is_modifier_pressed(mouse_event::modifier_t::shift))
|
||||
{
|
||||
this->tc_selection_start = mc.mc_line;
|
||||
[this, &me, sub_delegate](const main_content& mc) {
|
||||
if (this->vc_enabled) {
|
||||
if (this->tc_supports_marks
|
||||
&& 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) {
|
||||
|
||||
@ -437,29 +477,35 @@ textview_curses::handle_mouse(mouse_event& me)
|
||||
break;
|
||||
}
|
||||
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);
|
||||
me.me_y = 0;
|
||||
mouse_line = main_content{this->get_top()};
|
||||
} else if (me.me_y >= height
|
||||
&& this->get_top() < this->get_top_for_last_row())
|
||||
{
|
||||
} else if (me.me_y >= height) {
|
||||
this->shift_selection(
|
||||
listview_curses::shift_amount_t::down_line);
|
||||
me.me_y = height;
|
||||
} 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;
|
||||
}
|
||||
case mouse_button_state_t::BUTTON_STATE_RELEASED: {
|
||||
if (this->tc_selection_start) {
|
||||
this->toggle_user_mark(&BM_USER,
|
||||
this->tc_selection_start.value(),
|
||||
this->get_selection());
|
||||
this->reload_data();
|
||||
if (this->vc_enabled) {
|
||||
if (this->tc_selection_start) {
|
||||
this->toggle_user_mark(&BM_USER,
|
||||
this->tc_selection_start.value(),
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
@ -606,6 +606,12 @@ public:
|
||||
|
||||
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)
|
||||
{
|
||||
this->tc_delegate = del;
|
||||
@ -851,6 +857,7 @@ protected:
|
||||
mouse_event tc_press_event;
|
||||
bool tc_hide_fields{true};
|
||||
bool tc_paused{false};
|
||||
bool tc_supports_marks{false};
|
||||
|
||||
std::string tc_current_search;
|
||||
std::string tc_previous_search;
|
||||
|
@ -40,12 +40,14 @@
|
||||
#include "base/ansi_scrubber.hh"
|
||||
#include "base/attr_line.hh"
|
||||
#include "base/from_trait.hh"
|
||||
#include "base/injector.hh"
|
||||
#include "base/itertools.hh"
|
||||
#include "base/lnav_log.hh"
|
||||
#include "config.h"
|
||||
#include "lnav_config.hh"
|
||||
#include "shlex.hh"
|
||||
#include "view_curses.hh"
|
||||
#include "xterm_mouse.hh"
|
||||
|
||||
#if defined HAVE_NCURSESW_CURSES_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
|
||||
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 false;
|
||||
@ -562,9 +640,9 @@ static const std::string COLOR_NAMES[] = {
|
||||
"white",
|
||||
};
|
||||
|
||||
class color_listener : public lnav_config_listener {
|
||||
class ui_listener : public lnav_config_listener {
|
||||
public:
|
||||
color_listener() : lnav_config_listener(__FILE__) {}
|
||||
ui_listener() : lnav_config_listener(__FILE__) {}
|
||||
|
||||
void reload_config(error_reporter& reporter) override
|
||||
{
|
||||
@ -597,11 +675,16 @@ public:
|
||||
|
||||
if (view_colors::initialized) {
|
||||
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;
|
||||
|
||||
void
|
||||
@ -627,7 +710,7 @@ view_colors::init(bool headless)
|
||||
auto reporter
|
||||
= [](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_DRAGGED,
|
||||
BUTTON_STATE_RELEASED,
|
||||
BUTTON_STATE_DOUBLE_CLICK,
|
||||
};
|
||||
|
||||
struct mouse_event {
|
||||
@ -339,12 +340,26 @@ struct mouse_event {
|
||||
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_state_t me_state;
|
||||
uint8_t me_modifiers;
|
||||
struct timeval me_time {};
|
||||
int me_x;
|
||||
int me_y;
|
||||
int me_press_x{-1};
|
||||
int me_press_y{-1};
|
||||
};
|
||||
|
||||
/**
|
||||
@ -373,7 +388,7 @@ public:
|
||||
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;
|
||||
|
||||
@ -445,6 +460,8 @@ public:
|
||||
const struct line_range& lr,
|
||||
role_t base_role = role_t::VCR_TEXT);
|
||||
|
||||
bool vc_enabled{true};
|
||||
|
||||
protected:
|
||||
bool vc_visible{true};
|
||||
/** Flag to indicate if a display update is needed. */
|
||||
@ -454,6 +471,7 @@ protected:
|
||||
long vc_width{0};
|
||||
std::vector<view_curses*> vc_children;
|
||||
role_t vc_default_role{role_t::VCR_TEXT};
|
||||
view_curses* vc_last_drag_child{nullptr};
|
||||
};
|
||||
|
||||
template<class T>
|
||||
|
@ -626,6 +626,7 @@ handle_winch()
|
||||
void
|
||||
layout_views()
|
||||
{
|
||||
static auto* breadcrumb_view = injector::get<breadcrumb_curses*>();
|
||||
int width, height;
|
||||
getmaxyx(lnav_data.ld_window, height, width);
|
||||
|
||||
@ -694,20 +695,27 @@ layout_views()
|
||||
auto um_height = std::min(um_rows, (height - 4) / 2);
|
||||
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
|
||||
|| 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);
|
||||
int filter_height = filters_open ? 5 : 0;
|
||||
|| 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);
|
||||
int filter_height = config_panel_open ? 5 : 0;
|
||||
|
||||
bool breadcrumb_open = (lnav_data.ld_mode == ln_mode_t::BREADCRUMBS);
|
||||
|
||||
auto bottom_min = std::min(2 + 3, 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();
|
||||
lnav_data.ld_rl_view->set_width(width);
|
||||
|
||||
breadcrumb_view->set_width(width);
|
||||
|
||||
bool vis;
|
||||
vis = bottom.try_consume(lnav_data.ld_match_view.get_height());
|
||||
lnav_data.ld_match_view.set_y(bottom);
|
||||
@ -719,7 +727,8 @@ layout_views()
|
||||
|
||||
bottom -= 1;
|
||||
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);
|
||||
|
||||
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_status[LNS_PREVIEW1].set_y(bottom);
|
||||
lnav_data.ld_status[LNS_PREVIEW1].set_width(width);
|
||||
lnav_data.ld_status[LNS_PREVIEW1].set_visible(vis);
|
||||
|
||||
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_status[LNS_PREVIEW0].set_y(bottom);
|
||||
lnav_data.ld_status[LNS_PREVIEW0].set_width(width);
|
||||
lnav_data.ld_status[LNS_PREVIEW0].set_visible(vis);
|
||||
|
||||
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
|
||||
|| 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_width(width);
|
||||
lnav_data.ld_status[LNS_DOC].set_visible(has_doc && vis);
|
||||
|
||||
if (is_gantt) {
|
||||
@ -784,9 +796,10 @@ layout_views()
|
||||
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_width(width);
|
||||
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));
|
||||
lnav_data.ld_filter_view.set_height(vis_line_t(filter_height));
|
||||
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_y(bottom + 2);
|
||||
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_width(width);
|
||||
|
||||
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_width(width);
|
||||
|
||||
vis = is_spectro && bottom.try_consume(5 + 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_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_enabled(lnav_data.ld_mode
|
||||
== 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
|
||||
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;
|
||||
|
||||
switch (button & xterm_mouse::XT_BUTTON__MASK) {
|
||||
@ -1425,9 +1487,16 @@ lnav_behavior::mouse_event(int button, bool release, int x, int y)
|
||||
break;
|
||||
}
|
||||
|
||||
gettimeofday(&me.me_time, nullptr);
|
||||
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;
|
||||
} else if (release) {
|
||||
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);
|
||||
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;
|
||||
if (me.me_x >= width) {
|
||||
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;
|
||||
|
||||
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());
|
||||
if (tc->contains(me.me_x, me.me_y)) {
|
||||
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;
|
||||
}
|
||||
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: {
|
||||
this->lb_last_release_event = me;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1465,6 +1558,7 @@ lnav_behavior::mouse_event(int button, bool release, int x, int y)
|
||||
}
|
||||
this->lb_last_event = me;
|
||||
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_DOWN)
|
||||
{
|
||||
|
@ -89,6 +89,7 @@ bool handle_winch();
|
||||
void layout_views();
|
||||
void update_hits(textview_curses* tc);
|
||||
void clear_preview();
|
||||
void set_view_mode(ln_mode_t mode);
|
||||
|
||||
nonstd::optional<vis_line_t> next_cluster(
|
||||
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};
|
||||
struct mouse_event lb_last_event;
|
||||
struct mouse_event lb_last_release_event;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -80,11 +80,13 @@ void
|
||||
xterm_mouse::set_enabled(bool enabled)
|
||||
{
|
||||
if (is_available()) {
|
||||
putp(tparm((char*) XT_TERMCAP, enabled ? 1 : 0));
|
||||
putp(tparm((char*) XT_TERMCAP_TRACKING, enabled ? 1 : 0));
|
||||
putp(tparm((char*) XT_TERMCAP_SGR, enabled ? 1 : 0));
|
||||
fflush(stdout);
|
||||
this->xm_enabled = enabled;
|
||||
if (this->xm_enabled != enabled) {
|
||||
putp(tparm((char*) XT_TERMCAP, enabled ? 1 : 0));
|
||||
putp(tparm((char*) XT_TERMCAP_TRACKING, enabled ? 1 : 0));
|
||||
putp(tparm((char*) XT_TERMCAP_SGR, enabled ? 1 : 0));
|
||||
fflush(stdout);
|
||||
this->xm_enabled = enabled;
|
||||
}
|
||||
} else {
|
||||
log_warning("mouse support is not available");
|
||||
}
|
||||
|
@ -91,6 +91,10 @@ main(int argc, char* argv[])
|
||||
my_source ms;
|
||||
WINDOW* win;
|
||||
|
||||
setenv("DUMP_CRASH", "1", 1);
|
||||
log_install_handlers();
|
||||
lnav_log_crash_dir = "/tmp";
|
||||
|
||||
win = initscr();
|
||||
lv.set_data_source(&ms);
|
||||
lv.set_window(win);
|
||||
|
@ -4644,6 +4644,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"mouse": {
|
||||
"mode": "disabled"
|
||||
},
|
||||
"movement": {
|
||||
"mode": "cursor"
|
||||
},
|
||||
|
@ -12,48 +12,48 @@
|
||||
/global/keymap_def_text_view -> default-keymap.json:10
|
||||
/global/keymap_def_time_offset -> default-keymap.json:17
|
||||
/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/description -> root-config.json:19
|
||||
/log/annotations/com.vmware.vmacore.backtrace/handler -> root-config.json:21
|
||||
/log/annotations/com.vmware.vmacore.backtrace/condition -> root-config.json:23
|
||||
/log/annotations/com.vmware.vmacore.backtrace/description -> root-config.json:22
|
||||
/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/description -> {test_dir}/configs/installed/anno-test.json:6
|
||||
/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
|
||||
/tuning/archive-manager/cache-ttl -> root-config.json:28
|
||||
/tuning/archive-manager/min-free-space -> root-config.json:27
|
||||
/tuning/clipboard/impls/MacOS/find/read -> root-config.json:56
|
||||
/tuning/clipboard/impls/MacOS/find/write -> root-config.json:55
|
||||
/tuning/clipboard/impls/MacOS/general/read -> root-config.json:52
|
||||
/tuning/clipboard/impls/MacOS/general/write -> root-config.json:51
|
||||
/tuning/clipboard/impls/MacOS/test -> root-config.json:49
|
||||
/tuning/clipboard/impls/NeoVim/general/read -> root-config.json:84
|
||||
/tuning/clipboard/impls/NeoVim/general/write -> root-config.json:83
|
||||
/tuning/clipboard/impls/NeoVim/test -> root-config.json:81
|
||||
/tuning/clipboard/impls/Wayland/general/read -> root-config.json:63
|
||||
/tuning/clipboard/impls/Wayland/general/write -> root-config.json:62
|
||||
/tuning/clipboard/impls/Wayland/test -> root-config.json:60
|
||||
/tuning/clipboard/impls/Windows/general/write -> root-config.json:90
|
||||
/tuning/clipboard/impls/Windows/test -> root-config.json:88
|
||||
/tuning/clipboard/impls/X11-xclip/general/read -> root-config.json:70
|
||||
/tuning/clipboard/impls/X11-xclip/general/write -> root-config.json:69
|
||||
/tuning/clipboard/impls/X11-xclip/test -> root-config.json:67
|
||||
/tuning/clipboard/impls/tmux/general/read -> root-config.json:77
|
||||
/tuning/clipboard/impls/tmux/general/write -> root-config.json:76
|
||||
/tuning/clipboard/impls/tmux/test -> root-config.json:74
|
||||
/tuning/piper/max-size -> root-config.json:42
|
||||
/tuning/piper/rotations -> root-config.json:43
|
||||
/tuning/piper/ttl -> root-config.json:44
|
||||
/tuning/remote/ssh/command -> root-config.json:32
|
||||
/tuning/remote/ssh/config/BatchMode -> root-config.json:34
|
||||
/tuning/remote/ssh/config/ConnectTimeout -> root-config.json:35
|
||||
/tuning/remote/ssh/start-command -> root-config.json:37
|
||||
/tuning/remote/ssh/transfer-command -> root-config.json:38
|
||||
/tuning/url-scheme/docker-compose/handler -> root-config.json:100
|
||||
/tuning/url-scheme/docker/handler -> root-config.json:97
|
||||
/log/date-time/convert-zoned-to-local -> root-config.json:18
|
||||
/tuning/archive-manager/cache-ttl -> root-config.json:31
|
||||
/tuning/archive-manager/min-free-space -> root-config.json:30
|
||||
/tuning/clipboard/impls/MacOS/find/read -> root-config.json:59
|
||||
/tuning/clipboard/impls/MacOS/find/write -> root-config.json:58
|
||||
/tuning/clipboard/impls/MacOS/general/read -> root-config.json:55
|
||||
/tuning/clipboard/impls/MacOS/general/write -> root-config.json:54
|
||||
/tuning/clipboard/impls/MacOS/test -> root-config.json:52
|
||||
/tuning/clipboard/impls/NeoVim/general/read -> root-config.json:87
|
||||
/tuning/clipboard/impls/NeoVim/general/write -> root-config.json:86
|
||||
/tuning/clipboard/impls/NeoVim/test -> root-config.json:84
|
||||
/tuning/clipboard/impls/Wayland/general/read -> root-config.json:66
|
||||
/tuning/clipboard/impls/Wayland/general/write -> root-config.json:65
|
||||
/tuning/clipboard/impls/Wayland/test -> root-config.json:63
|
||||
/tuning/clipboard/impls/Windows/general/write -> root-config.json:93
|
||||
/tuning/clipboard/impls/Windows/test -> root-config.json:91
|
||||
/tuning/clipboard/impls/X11-xclip/general/read -> root-config.json:73
|
||||
/tuning/clipboard/impls/X11-xclip/general/write -> root-config.json:72
|
||||
/tuning/clipboard/impls/X11-xclip/test -> root-config.json:70
|
||||
/tuning/clipboard/impls/tmux/general/read -> root-config.json:80
|
||||
/tuning/clipboard/impls/tmux/general/write -> root-config.json:79
|
||||
/tuning/clipboard/impls/tmux/test -> root-config.json:77
|
||||
/tuning/piper/max-size -> root-config.json:45
|
||||
/tuning/piper/rotations -> root-config.json:46
|
||||
/tuning/piper/ttl -> root-config.json:47
|
||||
/tuning/remote/ssh/command -> root-config.json:35
|
||||
/tuning/remote/ssh/config/BatchMode -> root-config.json:37
|
||||
/tuning/remote/ssh/config/ConnectTimeout -> root-config.json:38
|
||||
/tuning/remote/ssh/start-command -> root-config.json:40
|
||||
/tuning/remote/ssh/transfer-command -> root-config.json:41
|
||||
/tuning/url-scheme/docker-compose/handler -> root-config.json:103
|
||||
/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/journald/handler -> root-config.json:103
|
||||
/tuning/url-scheme/piper/handler -> root-config.json:106
|
||||
/tuning/url-scheme/podman/handler -> root-config.json:109
|
||||
/tuning/url-scheme/journald/handler -> root-config.json:106
|
||||
/tuning/url-scheme/piper/handler -> root-config.json:109
|
||||
/tuning/url-scheme/podman/handler -> root-config.json:112
|
||||
/ui/clock-format -> root-config.json:4
|
||||
/ui/default-colors -> root-config.json:6
|
||||
/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/x40/command -> us-keymap.json:34
|
||||
/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-defs/default/highlights/colors/pattern -> default-theme.json:292
|
||||
/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.
|
||||
|
||||
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". [37m[40m F2 [0m toggles mouse support.
|
||||
|
||||
[1mLog Analysis[0m
|
||||
|
||||
|
@ -6,25 +6,25 @@ S -1 ┋
|
||||
A └ normal
|
||||
CSI Reset Replace mode
|
||||
CSI Erase all
|
||||
S 1 ┋17 x┋
|
||||
S 1 ┋+18 x┋
|
||||
A └┛ alt
|
||||
S 2 ┋+18 x┋
|
||||
S 2 ┋19 x┋
|
||||
A └┛ alt
|
||||
S 3 ┋19 x┋
|
||||
S 3 ┋20 x┋
|
||||
A └┛ alt
|
||||
S 4 ┋20 x┋
|
||||
S 4 ┋21 x┋
|
||||
A └┛ alt
|
||||
S 5 ┋21 x┋
|
||||
S 5 ┋22 x┋
|
||||
A └┛ alt
|
||||
S 6 ┋22 x┋
|
||||
S 6 ┋23 x┋
|
||||
A └┛ alt
|
||||
S 7 ┋23 x┋
|
||||
S 7 ┋24 x┋
|
||||
A └┛ alt
|
||||
S 8 ┋24 x┋
|
||||
S 8 ┋25 x┋
|
||||
A └┛ alt
|
||||
S 9 ┋25 x┋
|
||||
S 9 ┋26 x┋
|
||||
A └┛ alt
|
||||
S 10 ┋26 x┋
|
||||
S 10 ┋27 x┋
|
||||
A └┛ alt
|
||||
CSI Erase all
|
||||
CSI Use normal screen buffer
|
||||
|
@ -6,25 +6,25 @@ S -1 ┋
|
||||
A └ normal
|
||||
CSI Reset Replace mode
|
||||
CSI Erase all
|
||||
S 1 ┋8 x┋
|
||||
S 1 ┋9 x┋
|
||||
A └┛ alt
|
||||
S 2 ┋+9 x┋
|
||||
S 2 ┋10 x┋
|
||||
A └┛ alt
|
||||
S 3 ┋10 x┋
|
||||
S 3 ┋11 x┋
|
||||
A └┛ alt
|
||||
S 4 ┋11 x┋
|
||||
S 4 ┋12 x┋
|
||||
A └┛ alt
|
||||
S 5 ┋12 x┋
|
||||
S 5 ┋13 x┋
|
||||
A └┛ alt
|
||||
S 6 ┋13 x┋
|
||||
S 6 ┋14 x┋
|
||||
A └┛ alt
|
||||
S 7 ┋14 x┋
|
||||
S 7 ┋15 x┋
|
||||
A └┛ alt
|
||||
S 8 ┋15 x┋
|
||||
S 8 ┋16 x┋
|
||||
A └┛ alt
|
||||
S 9 ┋16 x┋
|
||||
S 9 ┋17 x┋
|
||||
A └┛ alt
|
||||
S 10 ┋17 x┋
|
||||
S 10 ┋+18 x┋
|
||||
A └┛ alt
|
||||
CSI Erase all
|
||||
CSI Use normal screen buffer
|
||||
|
Loading…
Reference in New Issue
Block a user