[view_curses] support for selecting text in mouse mode

pull/1265/head
Tim Stack 2 months ago
parent 53ab7b14a6
commit 96765e3abc

@ -15,8 +15,13 @@ Features:
mouse inputs: mouse inputs:
- clicking on the main view will move the cursor to the given - clicking on the main view will move the cursor to the given
row and dragging will scroll the view as needed; row and dragging will scroll the view as needed;
- shift + dragging in the main view will highlight lines and - shift + clicking/dragging in the main view will highlight
then toggle their bookmark status on release; lines and then toggle their bookmark status on release;
- double-clicking will select the underlying token and
drag-selecting within a line will select the given text;
- when text is selected: pressing `c` will copy the text to
the clipboard; the text will be used as the suggestion for
searching/filtering;
- clicking in the scroll area will move the view by a page and - clicking in the scroll area will move the view by a page and
dragging the scrollbar will move the view to the given spot; dragging the scrollbar will move the view to the given spot;
- clicking on the breadcrumb bar will select a crumb and - clicking on the breadcrumb bar will select a crumb and
@ -31,6 +36,10 @@ Features:
clicking the diamond will enable/disable the file/filter); clicking the diamond will enable/disable the file/filter);
- clicking in a prompt will move the cursor to the location. - clicking in a prompt will move the cursor to the location.
This is new work, so there are likely to be some glitches. This is new work, so there are likely to be some glitches.
* Added a `selected_text` column to the `lnav_views` table that
reports information about text that was selected with a mouse.
This makes it possible to script operations that use the
selected text as an input.
Interface changes: Interface changes:
* The bar charts in the DB view have now been moved to their * The bar charts in the DB view have now been moved to their

@ -305,6 +305,11 @@
"title": "/ui/theme-defs/<theme_name>/styles/text", "title": "/ui/theme-defs/<theme_name>/styles/text",
"$ref": "#/definitions/style" "$ref": "#/definitions/style"
}, },
"selected-text": {
"description": "Styling for text selected in a view",
"title": "/ui/theme-defs/<theme_name>/styles/selected-text",
"$ref": "#/definitions/style"
},
"alt-text": { "alt-text": {
"description": "Styling for plain text when alternating", "description": "Styling for plain text when alternating",
"title": "/ui/theme-defs/<theme_name>/styles/alt-text", "title": "/ui/theme-defs/<theme_name>/styles/alt-text",

@ -144,8 +144,13 @@ mouse inputs:
* clicking on the main view will move the cursor to the given * clicking on the main view will move the cursor to the given
row and dragging will scroll the view as needed; row and dragging will scroll the view as needed;
* shift + dragging in the main view will highlight lines and * shift + clicking/dragging in the main view will highlight
then toggle their bookmark status on release; lines and then toggle their bookmark status on release;
* double-clicking will select the underlying token and
drag-selecting within a line will select the given text;
* with selected text, pressing :kbd:`c` will copy the text to
the clipboard and it will be used as the suggestion for
searching/filtering;
* clicking in the scroll area will move the view by a page and * clicking in the scroll area will move the view by a page and
dragging the scrollbar will move the view to the given spot; dragging the scrollbar will move the view to the given spot;
* clicking on the breadcrumb bar will select a crumb and * clicking on the breadcrumb bar will select a crumb and

@ -387,6 +387,43 @@ string_fragment::codepoint_to_byte_index(ssize_t cp_index) const
return Ok(retval); return Ok(retval);
} }
string_fragment
string_fragment::sub_cell_range(int cell_start, int cell_end) const
{
int byte_index = this->sf_begin;
nonstd::optional<int> byte_start;
nonstd::optional<int> byte_end;
int cell_index = 0;
while (byte_index < this->sf_end) {
if (cell_start == cell_index) {
byte_start = byte_index;
}
if (cell_index == cell_end) {
byte_end = byte_index;
}
auto read_res = ww898::utf::utf8::read(
[this, &byte_index]() { return this->sf_string[byte_index++]; });
if (read_res.isErr()) {
byte_index += 1;
} else {
cell_index += wcwidth(read_res.unwrap());
}
}
if (cell_start == cell_index) {
byte_start = byte_index;
}
if (!byte_end) {
byte_end = byte_index;
}
if (byte_start && byte_end) {
return this->sub_range(byte_start.value(), byte_end.value());
}
return string_fragment{};
}
size_t size_t
string_fragment::column_width() const string_fragment::column_width() const
{ {

@ -178,6 +178,8 @@ struct string_fragment {
Result<ssize_t, const char*> codepoint_to_byte_index( Result<ssize_t, const char*> codepoint_to_byte_index(
ssize_t cp_index) const; ssize_t cp_index) const;
string_fragment sub_cell_range(int cell_start, int cell_end) const;
const char& operator[](int index) const const char& operator[](int index) const
{ {
return this->sf_string[sf_begin + index]; return this->sf_string[sf_begin + index];
@ -281,6 +283,12 @@ struct string_fragment {
this->sf_string, this->sf_begin + begin, this->sf_begin + end}; this->sf_string, this->sf_begin + begin, this->sf_begin + end};
} }
bool contains(const string_fragment& sf) const
{
return this->sf_string == sf.sf_string && this->sf_begin <= sf.sf_begin
&& sf.sf_end <= this->sf_end;
}
size_t count(char ch) const size_t count(char ch) const
{ {
size_t retval = 0; size_t retval = 0;

@ -136,6 +136,7 @@ enum class role_t : int32_t {
VCR_TYPE, VCR_TYPE,
VCR_SEP_REF_ACC, VCR_SEP_REF_ACC,
VCR_SUGGESTION, VCR_SUGGESTION,
VCR_SELECTED_TEXT,
VCR__MAX VCR__MAX
}; };

@ -434,9 +434,12 @@ filter_sub_source::rl_change(readline_curses* rc)
break; break;
case filter_lang_t::REGEX: { case filter_lang_t::REGEX: {
if (new_value.empty()) { if (new_value.empty()) {
if (fs.get_filter(top_view->get_current_search()) == nullptr) { auto sugg = top_view->get_current_search();
this->fss_editor->set_suggestion( if (top_view->tc_selected_text) {
top_view->get_current_search()); sugg = top_view->tc_selected_text->sti_value;
}
if (fs.get_filter(sugg) == nullptr) {
this->fss_editor->set_suggestion(sugg);
} }
} else { } else {
auto regex_res auto regex_res

@ -114,7 +114,7 @@
"alt-msg": "${keymap_def_alt_hour_boundary}" "alt-msg": "${keymap_def_alt_hour_boundary}"
}, },
"x63": { "x63": {
"command": ":write-to /dev/clipboard", "command": "|lnav-copy-text",
"alt-msg": "${keymap_def_clear}" "alt-msg": "${keymap_def_clear}"
}, },
"x67": { "x67": {

@ -2447,6 +2447,10 @@ com_filter_prompt(exec_context& ec, const std::string& cmdline)
return {}; return {};
} }
if (tc->tc_selected_text) {
return {"", tc->tc_selected_text->sti_value};
}
return {"", tc->get_current_search()}; return {"", tc->get_current_search()};
} }
@ -5899,11 +5903,11 @@ com_prompt(exec_context& ec,
auto split_args_res = lexer.split(ec.create_resolver()); auto split_args_res = lexer.split(ec.create_resolver());
if (split_args_res.isErr()) { if (split_args_res.isErr()) {
auto split_err = split_args_res.unwrapErr(); auto split_err = split_args_res.unwrapErr();
auto um = lnav::console::user_message::error( auto um
"unable to parse file name") = lnav::console::user_message::error("unable to parse prompt")
.with_reason(split_err.te_msg) .with_reason(split_err.te_msg)
.with_snippet(lnav::console::snippet::from( .with_snippet(lnav::console::snippet::from(
SRC, lexer.to_attr_line(split_err))); SRC, lexer.to_attr_line(split_err)));
return Err(um); return Err(um);
} }

@ -630,6 +630,10 @@ static const struct json_path_container theme_styles_handlers = {
.with_description("Styling for plain text") .with_description("Styling for plain text")
.for_child(&lnav_theme::lt_style_text) .for_child(&lnav_theme::lt_style_text)
.with_children(style_config_handlers), .with_children(style_config_handlers),
yajlpp::property_handler("selected-text")
.with_description("Styling for text selected in a view")
.for_child(&lnav_theme::lt_style_selected_text)
.with_children(style_config_handlers),
yajlpp::property_handler("alt-text") yajlpp::property_handler("alt-text")
.with_description("Styling for plain text when alternating") .with_description("Styling for plain text when alternating")
.for_child(&lnav_theme::lt_style_alt_text) .for_child(&lnav_theme::lt_style_alt_text)

@ -194,7 +194,7 @@ rl_sql_help(readline_curses* rc)
{ {
auto al = attr_line_t(rc->get_line_buffer()); auto al = attr_line_t(rc->get_line_buffer());
const auto& sa = al.get_attrs(); const auto& sa = al.get_attrs();
size_t x = rc->get_x(); size_t x = rc->get_cursor_x();
bool has_doc = false; bool has_doc = false;
if (x > 0) { if (x > 0) {
@ -308,6 +308,12 @@ rl_change(readline_curses* rc)
lnav_data.ld_user_message_source.clear(); lnav_data.ld_user_message_source.clear();
switch (lnav_data.ld_mode) { switch (lnav_data.ld_mode) {
case ln_mode_t::SEARCH: {
if (rc->get_line_buffer().empty() && tc->tc_selected_text) {
rc->set_suggestion(tc->tc_selected_text->sti_value);
}
break;
}
case ln_mode_t::SQL: { case ln_mode_t::SQL: {
static const auto* sql_cmd_map static const auto* sql_cmd_map
= injector::get<readline_context::command_map_t*, = injector::get<readline_context::command_map_t*,
@ -563,7 +569,7 @@ rl_search_internal(readline_curses* rc, ln_mode_t mode, bool complete = false)
auto orig_prql_stmt = attr_line_t(term_val); auto orig_prql_stmt = attr_line_t(term_val);
orig_prql_stmt.rtrim("| \r\n\t"); orig_prql_stmt.rtrim("| \r\n\t");
annotate_sql_statement(orig_prql_stmt); annotate_sql_statement(orig_prql_stmt);
auto cursor_x = rc->get_x(); auto cursor_x = rc->get_cursor_x();
if (cursor_x > orig_prql_stmt.get_string().length()) { if (cursor_x > orig_prql_stmt.get_string().length()) {
cursor_x = orig_prql_stmt.length() - 1; cursor_x = orig_prql_stmt.length() - 1;
} }

@ -933,6 +933,8 @@ readline_curses::start()
maxfd = std::max(STDIN_FILENO, this->rc_command_pipe[RCF_SLAVE].get()); maxfd = std::max(STDIN_FILENO, this->rc_command_pipe[RCF_SLAVE].get());
static uint64_t last_h1, last_h2;
while (looping) { while (looping) {
fd_set ready_rfds; fd_set ready_rfds;
int rc; int rc;
@ -951,8 +953,6 @@ readline_curses::start()
} }
} else { } else {
if (FD_ISSET(STDIN_FILENO, &ready_rfds)) { if (FD_ISSET(STDIN_FILENO, &ready_rfds)) {
static uint64_t last_h1, last_h2;
struct itimerval itv; struct itimerval itv;
itv.it_value.tv_sec = 0; itv.it_value.tv_sec = 0;
@ -960,7 +960,6 @@ readline_curses::start()
itv.it_interval.tv_sec = 0; itv.it_interval.tv_sec = 0;
itv.it_interval.tv_usec = 0; itv.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &itv, nullptr); setitimer(ITIMER_REAL, &itv, nullptr);
rl_callback_read_char(); rl_callback_read_char();
if (RL_ISSTATE(RL_STATE_DONE) && !got_line) { if (RL_ISSTATE(RL_STATE_DONE) && !got_line) {
got_line = 1; got_line = 1;
@ -969,6 +968,7 @@ readline_curses::start()
} else { } else {
uint64_t h1 = 1, h2 = 2; uint64_t h1 = 1, h2 = 2;
rc_local_suggestion.clear();
if (rl_last_func == readline_context::command_complete) { if (rl_last_func == readline_context::command_complete) {
rl_last_func = rl_menu_complete; rl_last_func = rl_menu_complete;
} }
@ -1304,7 +1304,6 @@ readline_curses::check_poll_set(const std::vector<struct pollfd>& pollfds)
if (rc > 0) { if (rc > 0) {
int old_x = this->vc_cursor_x; int old_x = this->vc_cursor_x;
this->rc_suggestion.clear();
this->map_output(buffer, rc); this->map_output(buffer, rc);
if (this->vc_cursor_x != old_x) { if (this->vc_cursor_x != old_x) {
this->rc_change(this); this->rc_change(this);
@ -1389,7 +1388,7 @@ readline_curses::check_poll_set(const std::vector<struct pollfd>& pollfds)
this->rc_blur(this); this->rc_blur(this);
break; break;
case 'l': case 'l': {
this->rc_line_buffer = &msg[2]; this->rc_line_buffer = &msg[2];
if (this->rc_active_context != -1) { if (this->rc_active_context != -1) {
this->rc_suggestion.clear(); this->rc_suggestion.clear();
@ -1400,6 +1399,7 @@ readline_curses::check_poll_set(const std::vector<struct pollfd>& pollfds)
this->rc_display_match(this); this->rc_display_match(this);
} }
break; break;
}
case 'c': case 'c':
this->rc_line_buffer = &msg[2]; this->rc_line_buffer = &msg[2];
@ -1496,6 +1496,7 @@ readline_curses::set_suggestion(const std::string& value)
perror("set_suggestion: write failed"); perror("set_suggestion: write failed");
} }
this->rc_suggestion = value; this->rc_suggestion = value;
this->set_needs_update();
} }
void void
@ -1653,7 +1654,7 @@ readline_curses::do_update()
this->vc_x, this->vc_x,
this->rc_value, this->rc_value,
lr); lr);
this->set_x(0); this->set_cursor_x(0);
} }
if (this->rc_active_context != -1) { if (this->rc_active_context != -1) {

@ -0,0 +1,15 @@
#
# @synopsis: lnav-copy-text
# @description: Copy text from the top view
#
;SELECT jget(selected_text, '/value') AS content FROM lnav_top_view
;SELECT CASE
WHEN $content IS NULL THEN
':write-to -'
ELSE
':echo -n ${content}'
END AS cmd
:redirect-to /dev/clipboard
:eval ${cmd}

@ -3,6 +3,7 @@ BUILTIN_LNAVSCRIPTS = \
$(srcdir)/scripts/dhclient-summary.lnav \ $(srcdir)/scripts/dhclient-summary.lnav \
$(srcdir)/scripts/docker-url-handler.lnav \ $(srcdir)/scripts/docker-url-handler.lnav \
$(srcdir)/scripts/journald-url-handler.lnav \ $(srcdir)/scripts/journald-url-handler.lnav \
$(srcdir)/scripts/lnav-copy-text.lnav \
$(srcdir)/scripts/lnav-pop-view.lnav \ $(srcdir)/scripts/lnav-pop-view.lnav \
$(srcdir)/scripts/partition-by-boot.lnav \ $(srcdir)/scripts/partition-by-boot.lnav \
$(srcdir)/scripts/piper-url-handler.lnav \ $(srcdir)/scripts/piper-url-handler.lnav \

@ -148,6 +148,7 @@ struct lnav_theme {
positioned_property<style_config> lt_style_type; positioned_property<style_config> lt_style_type;
positioned_property<style_config> lt_style_sep_ref_acc; positioned_property<style_config> lt_style_sep_ref_acc;
positioned_property<style_config> lt_style_suggestion; positioned_property<style_config> lt_style_suggestion;
positioned_property<style_config> lt_style_selected_text;
positioned_property<style_config> lt_style_re_special; positioned_property<style_config> lt_style_re_special;
positioned_property<style_config> lt_style_re_repeat; positioned_property<style_config> lt_style_re_repeat;
positioned_property<style_config> lt_style_diff_delete; positioned_property<style_config> lt_style_diff_delete;

@ -37,6 +37,7 @@
#include "base/injector.hh" #include "base/injector.hh"
#include "base/time_util.hh" #include "base/time_util.hh"
#include "config.h" #include "config.h"
#include "data_scanner.hh"
#include "fmt/format.h" #include "fmt/format.h"
#include "lnav_config.hh" #include "lnav_config.hh"
#include "log_format_fwd.hh" #include "log_format_fwd.hh"
@ -284,9 +285,21 @@ textview_curses::reload_config(error_reporter& reporter)
} }
} }
void
textview_curses::invoke_scroll()
{
this->tc_selected_text = nonstd::nullopt;
if (this->tc_sub_source != nullptr) {
this->tc_sub_source->scroll_invoked(this);
}
listview_curses::invoke_scroll();
}
void void
textview_curses::reload_data() textview_curses::reload_data()
{ {
this->tc_selected_text = nonstd::nullopt;
if (this->tc_sub_source != nullptr) { if (this->tc_sub_source != nullptr) {
this->tc_sub_source->text_update_marks(this->tc_bookmarks); this->tc_sub_source->text_update_marks(this->tc_bookmarks);
} }
@ -415,6 +428,13 @@ textview_curses::handle_mouse(mouse_event& me)
auto* sub_delegate = dynamic_cast<text_delegate*>(this->tc_sub_source); auto* sub_delegate = dynamic_cast<text_delegate*>(this->tc_sub_source);
if (me.me_button != mouse_button_t::BUTTON_LEFT
|| me.me_state != mouse_button_state_t::BUTTON_STATE_RELEASED)
{
this->tc_selected_text = nonstd::nullopt;
this->set_needs_update();
}
switch (me.me_state) { switch (me.me_state) {
case mouse_button_state_t::BUTTON_STATE_PRESSED: { case mouse_button_state_t::BUTTON_STATE_PRESSED: {
if (!this->lv_selectable) { if (!this->lv_selectable) {
@ -456,7 +476,36 @@ textview_curses::handle_mouse(mouse_event& me)
[this, &me, sub_delegate](const main_content& mc) { [this, &me, sub_delegate](const main_content& mc) {
if (this->vc_enabled) { if (this->vc_enabled) {
if (this->tc_supports_marks) { if (this->tc_supports_marks) {
this->toggle_user_mark(&BM_USER, mc.mc_line); attr_line_t al;
this->textview_value_for_row(mc.mc_line, al);
auto line_sf
= string_fragment::from_str(al.get_string());
auto cursor_sf
= line_sf.sub_cell_range(me.me_x, me.me_x);
auto ds = data_scanner(line_sf);
auto tf = this->tc_sub_source->get_text_format();
while (true) {
auto tok_res = ds.tokenize2(tf);
if (!tok_res) {
break;
}
auto tok = tok_res.value();
auto tok_sf = tok.to_string_fragment();
if (tok_sf.contains(cursor_sf)) {
this->tc_selected_text = selected_text_info{
mc.mc_line,
line_range{
tok_sf.sf_begin,
tok_sf.sf_end,
},
tok_sf.to_string(),
};
this->set_needs_update();
break;
}
}
} }
this->set_selection_without_context(mc.mc_line); this->set_selection_without_context(mc.mc_line);
} }
@ -478,6 +527,27 @@ textview_curses::handle_mouse(mouse_event& me)
} }
case mouse_button_state_t::BUTTON_STATE_DRAGGED: { case mouse_button_state_t::BUTTON_STATE_DRAGGED: {
if (!this->vc_enabled) { if (!this->vc_enabled) {
} else if (me.me_y == me.me_press_y) {
if (mouse_line.is<main_content>()) {
auto& mc = mouse_line.get<main_content>();
attr_line_t al;
auto low_x = std::min(me.me_x, me.me_press_x);
auto high_x = std::max(me.me_x, me.me_press_x);
this->textview_value_for_row(mc.mc_line, al);
auto line_sf = string_fragment::from_str(al.get_string());
auto cursor_sf = line_sf.sub_cell_range(low_x, high_x);
if (!cursor_sf.empty()) {
this->tc_selected_text = {
mc.mc_line,
line_range{
cursor_sf.sf_begin,
cursor_sf.sf_end,
},
cursor_sf.to_string(),
};
}
}
} else if (me.me_y < 0) { } else if (me.me_y < 0) {
this->shift_selection(listview_curses::shift_amount_t::up_line); this->shift_selection(listview_curses::shift_amount_t::up_line);
mouse_line = main_content{this->get_top()}; mouse_line = main_content{this->get_top()};
@ -615,6 +685,14 @@ textview_curses::textview_value_for_row(vis_line_t row, attr_line_t& value_out)
sa.emplace_back(line_range{orig_line.lr_start, -1}, sa.emplace_back(line_range{orig_line.lr_start, -1},
VC_STYLE.value(text_attrs{A_REVERSE})); VC_STYLE.value(text_attrs{A_REVERSE}));
} }
if (this->tc_selected_text) {
const auto& sti = this->tc_selected_text.value();
if (sti.sti_line == row) {
sa.emplace_back(sti.sti_range,
VC_ROLE.value(role_t::VCR_SELECTED_TEXT));
}
}
} }
void void

@ -783,14 +783,7 @@ public:
void revert_search() { this->execute_search(this->tc_previous_search); } void revert_search() { this->execute_search(this->tc_previous_search); }
void invoke_scroll() void invoke_scroll();
{
if (this->tc_sub_source != nullptr) {
this->tc_sub_source->scroll_invoked(this);
}
listview_curses::invoke_scroll();
}
textview_curses& set_reload_config_delegate( textview_curses& set_reload_config_delegate(
std::function<void(textview_curses&)> func) std::function<void(textview_curses&)> func)
@ -807,6 +800,14 @@ public:
nonstd::optional<role_t> tc_cursor_role; nonstd::optional<role_t> tc_cursor_role;
nonstd::optional<role_t> tc_disabled_cursor_role; nonstd::optional<role_t> tc_disabled_cursor_role;
struct selected_text_info {
int64_t sti_line;
line_range sti_range;
std::string sti_value;
};
nonstd::optional<selected_text_info> tc_selected_text;
protected: protected:
class grep_highlighter { class grep_highlighter {
public: public:

@ -11,6 +11,9 @@
"color": "Silver", "color": "Silver",
"background-color": "Black" "background-color": "Black"
}, },
"selected-text": {
"background-color": "DarkCyan"
},
"identifier": { "identifier": {
"background-color": "", "background-color": "",
"color": "semantic()" "color": "semantic()"

@ -25,6 +25,9 @@
"color": "#f6f6f6", "color": "#f6f6f6",
"background-color": "$black" "background-color": "$black"
}, },
"selected-text": {
"background-color": "$cyan"
},
"alt-text": { "alt-text": {
"background-color": "#1c1c1c" "background-color": "#1c1c1c"
}, },

@ -24,6 +24,9 @@
"color": "$white", "color": "$white",
"background-color": "" "background-color": ""
}, },
"selected-text": {
"background-color": "$cyan"
},
"alt-text": { "alt-text": {
"bold": true "bold": true
}, },

@ -23,6 +23,9 @@
"color": "#f6f6f6", "color": "#f6f6f6",
"background-color": "$black" "background-color": "$black"
}, },
"selected-text": {
"background-color": "$cyan"
},
"alt-text": { "alt-text": {
"background-color": "#1c1c1c" "background-color": "#1c1c1c"
}, },

@ -23,6 +23,9 @@
"color": "#d6deeb", "color": "#d6deeb",
"background-color": "#011627" "background-color": "#011627"
}, },
"selected-text": {
"background-color": "$cyan"
},
"alt-text": { "alt-text": {
"background-color": "#1c1c1c" "background-color": "#1c1c1c"
}, },

@ -32,6 +32,9 @@
"color": "$base0", "color": "$base0",
"background-color": "$base03" "background-color": "$base03"
}, },
"selected-text": {
"background-color": "$cyan"
},
"alt-text": { "alt-text": {
"background-color": "$base02" "background-color": "$base02"
}, },

@ -31,6 +31,9 @@
"color": "$base00", "color": "$base00",
"background-color": "$base3" "background-color": "$base3"
}, },
"selected-text": {
"background-color": "$cyan"
},
"alt-text": { "alt-text": {
"background-color": "$base2" "background-color": "$base2"
}, },

@ -236,7 +236,7 @@ view_curses::mvwattrline(WINDOW* window,
role_t base_role) role_t base_role)
{ {
auto& sa = al.get_attrs(); auto& sa = al.get_attrs();
auto& line = al.get_string(); const auto& line = al.get_string();
std::vector<utf_to_display_adjustment> utf_adjustments; std::vector<utf_to_display_adjustment> utf_adjustments;
std::string full_line; std::string full_line;
@ -464,6 +464,12 @@ view_curses::mvwattrline(WINDOW* window,
} else if (iter->sa_type == &VC_ROLE) { } else if (iter->sa_type == &VC_ROLE) {
auto role = iter->sa_value.get<role_t>(); auto role = iter->sa_value.get<role_t>();
attrs = vc.attrs_for_role(role); attrs = vc.attrs_for_role(role);
if (role == role_t::VCR_SELECTED_TEXT) {
retval.mr_selected_text
= string_fragment::from_str(line).sub_range(
iter->sa_range.lr_start, iter->sa_range.lr_end);
}
} else if (iter->sa_type == &VC_ROLE_FG) { } else if (iter->sa_type == &VC_ROLE_FG) {
auto role_attrs auto role_attrs
= vc.attrs_for_role(iter->sa_value.get<role_t>()); = vc.attrs_for_role(iter->sa_value.get<role_t>());
@ -1138,6 +1144,8 @@ view_colors::init_roles(const lnav_theme& lt,
= this->to_attrs(lt, lt.lt_style_sep_ref_acc, reporter); = this->to_attrs(lt, lt.lt_style_sep_ref_acc, reporter);
this->vc_role_attrs[lnav::enums::to_underlying(role_t::VCR_SUGGESTION)] this->vc_role_attrs[lnav::enums::to_underlying(role_t::VCR_SUGGESTION)]
= this->to_attrs(lt, lt.lt_style_suggestion, reporter); = this->to_attrs(lt, lt.lt_style_suggestion, reporter);
this->vc_role_attrs[lnav::enums::to_underlying(role_t::VCR_SELECTED_TEXT)]
= this->to_attrs(lt, lt.lt_style_selected_text, reporter);
this->vc_role_attrs[lnav::enums::to_underlying(role_t::VCR_RE_SPECIAL)] this->vc_role_attrs[lnav::enums::to_underlying(role_t::VCR_RE_SPECIAL)]
= this->to_attrs(lt, lt.lt_style_re_special, reporter); = this->to_attrs(lt, lt.lt_style_re_special, reporter);

@ -451,6 +451,7 @@ public:
struct mvwattrline_result { struct mvwattrline_result {
size_t mr_chars_out{0}; size_t mr_chars_out{0};
size_t mr_bytes_remaining{0}; size_t mr_bytes_remaining{0};
string_fragment mr_selected_text;
}; };
static mvwattrline_result mvwattrline(WINDOW* window, static mvwattrline_result mvwattrline(WINDOW* window,

@ -1529,6 +1529,8 @@ lnav_behavior::mouse_event(int button, bool release, int x, int y)
auto* tc = *(lnav_data.ld_view_stack.top()); auto* tc = *(lnav_data.ld_view_stack.top());
if (tc->contains(me.me_x, me.me_y)) { if (tc->contains(me.me_x, me.me_y)) {
me.me_press_y = me.me_y - tc->get_y();
me.me_press_x = me.me_x - tc->get_x();
this->lb_last_view = tc; this->lb_last_view = tc;
} else { } else {
for (auto* vc : VIEWS) { for (auto* vc : VIEWS) {

@ -175,6 +175,22 @@ static const typed_json_path_container<top_line_meta> top_line_meta_handlers = {
.with_children(breadcrumb_crumb_handlers), .with_children(breadcrumb_crumb_handlers),
}; };
static const typed_json_path_container<line_range> line_range_handlers = {
yajlpp::property_handler("start").for_field(&line_range::lr_start),
yajlpp::property_handler("end").for_field(&line_range::lr_end),
};
static const typed_json_path_container<textview_curses::selected_text_info>
selected_text_handlers = {
yajlpp::property_handler("line").for_field(
&textview_curses::selected_text_info::sti_line),
yajlpp::property_handler("range")
.for_child(&textview_curses::selected_text_info::sti_range)
.with_children(line_range_handlers),
yajlpp::property_handler("value").for_field(
&textview_curses::selected_text_info::sti_value),
};
enum class row_details_t { enum class row_details_t {
hide, hide,
show, show,
@ -259,7 +275,8 @@ CREATE TABLE lnav_views (
movement TEXT, -- The movement mode, either 'top' or 'cursor'. movement TEXT, -- The movement mode, either 'top' or 'cursor'.
top_meta TEXT, -- A JSON object that contains metadata related to the top line in the view. top_meta TEXT, -- A JSON object that contains metadata related to the top line in the view.
selection INTEGER, -- The number of the line that is focused for selection. selection INTEGER, -- The number of the line that is focused for selection.
options TEXT -- A JSON object that contains optional settings for this view. options TEXT, -- A JSON object that contains optional settings for this view.
selected_text TEXT -- A JSON object that contains information about the text selected by the mouse in the view.
); );
)"; )";
@ -456,6 +473,16 @@ CREATE TABLE lnav_views (
} }
break; break;
} }
case 14: {
if (tc.tc_selected_text) {
to_sqlite(ctx,
selected_text_handlers.to_json_string(
tc.tc_selected_text.value()));
} else {
sqlite3_result_null(ctx);
}
break;
}
} }
return SQLITE_OK; return SQLITE_OK;
@ -490,7 +517,8 @@ CREATE TABLE lnav_views (
string_fragment movement, string_fragment movement,
const char* top_meta, const char* top_meta,
int64_t selection, int64_t selection,
nonstd::optional<string_fragment> options) nonstd::optional<string_fragment> options,
nonstd::optional<string_fragment> selected_text)
{ {
auto& tc = lnav_data.ld_views[index]; auto& tc = lnav_data.ld_views[index];
auto* time_source auto* time_source

@ -146,6 +146,12 @@
"underline": false, "underline": false,
"bold": false "bold": false
}, },
"selected-text": {
"color": "",
"background-color": "DarkCyan",
"underline": false,
"bold": false
},
"alt-text": { "alt-text": {
"color": "", "color": "",
"background-color": "#262626", "background-color": "#262626",
@ -742,6 +748,12 @@
"underline": false, "underline": false,
"bold": false "bold": false
}, },
"selected-text": {
"color": "",
"background-color": "$cyan",
"underline": false,
"bold": false
},
"alt-text": { "alt-text": {
"color": "", "color": "",
"background-color": "#1c1c1c", "background-color": "#1c1c1c",
@ -1301,6 +1313,12 @@
"underline": false, "underline": false,
"bold": false "bold": false
}, },
"selected-text": {
"color": "",
"background-color": "$cyan",
"underline": false,
"bold": false
},
"alt-text": { "alt-text": {
"color": "", "color": "",
"background-color": "", "background-color": "",
@ -1859,6 +1877,12 @@
"underline": false, "underline": false,
"bold": false "bold": false
}, },
"selected-text": {
"color": "",
"background-color": "",
"underline": false,
"bold": false
},
"alt-text": { "alt-text": {
"color": "", "color": "",
"background-color": "", "background-color": "",
@ -2418,6 +2442,12 @@
"underline": false, "underline": false,
"bold": false "bold": false
}, },
"selected-text": {
"color": "",
"background-color": "$cyan",
"underline": false,
"bold": false
},
"alt-text": { "alt-text": {
"color": "", "color": "",
"background-color": "#1c1c1c", "background-color": "#1c1c1c",
@ -2976,6 +3006,12 @@
"underline": false, "underline": false,
"bold": false "bold": false
}, },
"selected-text": {
"color": "",
"background-color": "$cyan",
"underline": false,
"bold": false
},
"alt-text": { "alt-text": {
"color": "", "color": "",
"background-color": "#1c1c1c", "background-color": "#1c1c1c",
@ -3543,6 +3579,12 @@
"underline": false, "underline": false,
"bold": false "bold": false
}, },
"selected-text": {
"color": "",
"background-color": "$cyan",
"underline": false,
"bold": false
},
"alt-text": { "alt-text": {
"color": "", "color": "",
"background-color": "$base02", "background-color": "$base02",
@ -4110,6 +4152,12 @@
"underline": false, "underline": false,
"bold": false "bold": false
}, },
"selected-text": {
"color": "",
"background-color": "$cyan",
"underline": false,
"bold": false
},
"alt-text": { "alt-text": {
"color": "", "color": "",
"background-color": "$base2", "background-color": "$base2",
@ -4847,7 +4895,7 @@
"alt-msg": "" "alt-msg": ""
}, },
"x63": { "x63": {
"command": ":write-to /dev/clipboard", "command": "|lnav-copy-text",
"alt-msg": "${keymap_def_clear}" "alt-msg": "${keymap_def_clear}"
}, },
"x65": { "x65": {

Loading…
Cancel
Save