[keymap] support for /dev/clipboard in :write commands

The copy hotkey is now implemented in the keymap and the
':write-*' commands were enhanced to recognize /dev/clipboard
as a special file name for writing to the system clipboard.
This commit is contained in:
Timothy Stack 2018-09-30 09:49:23 -07:00
parent 0b822739ea
commit db6c619e4e
6 changed files with 68 additions and 82 deletions

4
NEWS
View File

@ -1,5 +1,9 @@
lnav v0.8.5: lnav v0.8.5:
Features:
* The ":write-*" commands will now accept "/dev/clipboard" as a file name
that writes to the system clipboard.
Interface Changes: Interface Changes:
* The auto-complete behavior in the prompt has been modified to fall back * The auto-complete behavior in the prompt has been modified to fall back
to a fuzzy search if the prefix search finds no matches. For example, to a fuzzy search if the prefix search finds no matches. For example,

View File

@ -131,11 +131,14 @@ Output
* append-to <file> - Append any bookmarked lines in the current view to the * append-to <file> - Append any bookmarked lines in the current view to the
given file. given file.
* write-to <file> - Overwrite the given file with any bookmarked lines in * write-to <file> - Overwrite the given file with any bookmarked lines in
the current view. Use '-' to write the lines to the terminal. the current view. Use '-' to write the lines to the terminal and '/dev/clipboard'
to write to the system clipboard.
* write-csv-to <file> - Write SQL query results to the given file in CSV format. * write-csv-to <file> - Write SQL query results to the given file in CSV format.
Use '-' to write the lines to the terminal. Use '-' to write the lines to the terminal and '/dev/clipboard' to write to
the system clipboard.
* write-json-to <file> - Write SQL query results to the given file in JSON * write-json-to <file> - Write SQL query results to the given file in JSON
format. Use '-' to write the lines to the terminal. format. Use '-' to write the lines to the terminal and '/dev/clipboard'
to write to the system clipboard..
* pipe-to <shell-cmd> - Pipe the bookmarked lines in the current view to a * pipe-to <shell-cmd> - Pipe the bookmarked lines in the current view to a
shell command and open the output in lnav. shell command and open the output in lnav.
* pipe-line-to <shell-cmd> - Pipe the top line in the current view to a shell * pipe-line-to <shell-cmd> - Pipe the top line in the current view to a shell

View File

@ -101,45 +101,6 @@ public:
vector<logline_value> lh_line_values; vector<logline_value> lh_line_values;
}; };
/* XXX For one, this code is kinda crappy. For two, we should probably link
* directly with X so we don't need to have xclip installed and it'll work if
* we're ssh'd into a box.
*/
static void copy_to_xclip(void)
{
textview_curses *tc = lnav_data.ld_view_stack.back();
bookmark_vector<vis_line_t> &bv =
tc->get_bookmarks()[&textview_curses::BM_USER];
bookmark_vector<vis_line_t>::iterator iter;
auto_mem<FILE> pfile(pclose);
int line_count = 0;
string line;
pfile = open_clipboard(CT_GENERAL);
if (!pfile.in()) {
alerter::singleton().chime();
lnav_data.ld_rl_view->set_value(
"error: Unable to copy to clipboard. "
"Make sure xclip or pbcopy is installed.");
return;
}
for (iter = bv.begin(); iter != bv.end(); iter++) {
tc->grep_value_for_line(*iter, line);
fprintf(pfile, "%s\n", line.c_str());
line_count += 1;
}
char buffer[128];
snprintf(buffer, sizeof(buffer),
"Copied " ANSI_BOLD("%d") " lines to the clipboard",
line_count);
lnav_data.ld_rl_view->set_value(buffer);
}
void handle_paging_key(int ch) void handle_paging_key(int ch)
{ {
if (lnav_data.ld_view_stack.empty()) { if (lnav_data.ld_view_stack.empty()) {
@ -210,12 +171,6 @@ void handle_paging_key(int ch)
} }
break; break;
case 'c':
copy_to_xclip();
lnav_data.ld_rl_view->set_alt_value(HELP_MSG_1(
C, "to clear marked messages"));
break;
case 'C': case 'C':
if (lss) { if (lss) {
lss->text_clear_marks(&textview_curses::BM_USER); lss->text_clear_marks(&textview_curses::BM_USER);
@ -673,24 +628,20 @@ void handle_paging_key(int ch)
if (tc == &lnav_data.ld_views[LNV_DB]) { if (tc == &lnav_data.ld_views[LNV_DB]) {
db_label_source &dls = lnav_data.ld_db_row_source; db_label_source &dls = lnav_data.ld_db_row_source;
for (vector<db_label_source::header_meta>::iterator iter = dls.dls_headers.begin(); for (auto &dls_header : dls.dls_headers) {
iter != dls.dls_headers.end(); if (!dls_header.hm_graphable) {
++iter) {
if (!iter->hm_graphable) {
continue; continue;
} }
lnav_data.ld_rl_view->add_possibility(LNM_COMMAND, lnav_data.ld_rl_view->add_possibility(LNM_COMMAND,
"numeric-colname", "numeric-colname",
iter->hm_name); dls_header.hm_name);
} }
} }
else { else {
for (vector<logline_value>::iterator iter = ldh.ldh_line_values.begin(); for (auto &ldh_line_value : ldh.ldh_line_values) {
iter != ldh.ldh_line_values.end(); const logline_value_stats *stats = ldh_line_value.lv_format->stats_for_value(
++iter) { ldh_line_value.lv_name);
const logline_value_stats *stats = iter->lv_format->stats_for_value(
iter->lv_name);
if (stats == NULL) { if (stats == NULL) {
continue; continue;
@ -698,14 +649,13 @@ void handle_paging_key(int ch)
lnav_data.ld_rl_view->add_possibility(LNM_COMMAND, lnav_data.ld_rl_view->add_possibility(LNM_COMMAND,
"numeric-colname", "numeric-colname",
iter->lv_name.to_string()); ldh_line_value.lv_name.to_string());
} }
} }
for (vector<string>::iterator iter = ldh.ldh_namer->cn_names.begin(); for (auto &cn_name : ldh.ldh_namer->cn_names) {
iter != ldh.ldh_namer->cn_names.end(); lnav_data.ld_rl_view->add_possibility(LNM_COMMAND, "colname",
++iter) { cn_name);
lnav_data.ld_rl_view->add_possibility(LNM_COMMAND, "colname", *iter);
} }
for (auto iter : ldh.ldh_namer->cn_builtin_names) { for (auto iter : ldh.ldh_namer->cn_builtin_names) {
if (iter == "col") { if (iter == "col") {
@ -834,7 +784,7 @@ void handle_paging_key(int ch)
break; break;
case 't': case 't':
if (lnav_data.ld_text_source.current_file() == NULL) { if (lnav_data.ld_text_source.current_file() == nullptr) {
alerter::singleton().chime(); alerter::singleton().chime();
lnav_data.ld_rl_view->set_value("No text files loaded"); lnav_data.ld_rl_view->set_value("No text files loaded");
} }

View File

@ -8,7 +8,8 @@
"keymap_def_hist_view": "Press ${ansi_bold}i${ansi_norm}/${ansi_bold}I${ansi_norm} to switch to the histogram view", "keymap_def_hist_view": "Press ${ansi_bold}i${ansi_norm}/${ansi_bold}I${ansi_norm} to switch to the histogram view",
"keymap_def_text_view": "Press ${ansi_bold}t${ansi_norm} to switch to the text view", "keymap_def_text_view": "Press ${ansi_bold}t${ansi_norm} to switch to the text view",
"keymap_def_pop_view": "Press ${ansi_bold}q${ansi_norm} to return to the previous view", "keymap_def_pop_view": "Press ${ansi_bold}q${ansi_norm} to return to the previous view",
"keymap_def_zoom": "Press ${ansi_bold}z${ansi_norm}/${ansi_bold}z${ansi_norm} to zoom in/out" "keymap_def_zoom": "Press ${ansi_bold}z${ansi_norm}/${ansi_bold}z${ansi_norm} to zoom in/out",
"keymap_def_clear": "Press ${ansi_bold}C${ansi_norm} to clear marked messages"
}, },
"keymap_def": { "keymap_def": {
"default": { "default": {
@ -49,6 +50,11 @@
":next-mark query" ":next-mark query"
], ],
"x63": [
":eval :alt-msg ${keymap_def_clear}",
":write-to /dev/clipboard"
],
"x67": [":goto 0"], "x67": [":goto 0"],
"x6d": [ "x6d": [
":mark", ":mark",

View File

@ -53,6 +53,7 @@
#include "relative_time.hh" #include "relative_time.hh"
#include "log_search_table.hh" #include "log_search_table.hh"
#include "shlex.hh" #include "shlex.hh"
#include "sysclip.hh"
#include "yajl/api/yajl_parse.h" #include "yajl/api/yajl_parse.h"
#include "db_sub_source.hh" #include "db_sub_source.hh"
@ -500,10 +501,11 @@ static void json_write_row(yajl_gen handle, int row)
static string com_save_to(exec_context &ec, string cmdline, vector<string> &args) static string com_save_to(exec_context &ec, string cmdline, vector<string> &args)
{ {
FILE *outfile = NULL, *toclose = NULL; FILE *outfile = nullptr, *toclose = nullptr;
const char *mode = ""; const char *mode = "";
string fn, retval; string fn, retval;
bool to_term = false; bool to_term = false;
int (*closer)(FILE *) = fclose;
if (args.empty()) { if (args.empty()) {
args.emplace_back("filename"); args.emplace_back("filename");
@ -564,7 +566,7 @@ static string com_save_to(exec_context &ec, string cmdline, vector<string> &args
outfile = tmpfile(); outfile = tmpfile();
toclose = outfile; toclose = outfile;
} }
else if (split_args[0] == "-") { else if (split_args[0] == "-" || split_args[0] == "/dev/stdout") {
if (lnav_data.ld_output_stack.empty()) { if (lnav_data.ld_output_stack.empty()) {
outfile = stdout; outfile = stdout;
nodelay(lnav_data.ld_window, 0); nodelay(lnav_data.ld_window, 0);
@ -586,13 +588,24 @@ static string com_save_to(exec_context &ec, string cmdline, vector<string> &args
lnav_data.ld_stdout_used = true; lnav_data.ld_stdout_used = true;
} }
} }
else if ((outfile = fopen(split_args[0].c_str(), mode)) == NULL) { else if (split_args[0] == "/dev/clipboard") {
toclose = outfile = open_clipboard(CT_GENERAL);
closer = pclose;
if (!outfile) {
alerter::singleton().chime();
return "error: Unable to copy to clipboard. "
"Make sure xclip or pbcopy is installed.";
}
}
else if ((outfile = fopen(split_args[0].c_str(), mode)) == nullptr) {
return "error: unable to open file -- " + split_args[0]; return "error: unable to open file -- " + split_args[0];
} }
else { else {
toclose = outfile; toclose = outfile;
} }
int line_count = 0;
if (args[0] == "write-csv-to") { if (args[0] == "write-csv-to") {
std::vector<std::vector<const char *> >::iterator row_iter; std::vector<std::vector<const char *> >::iterator row_iter;
std::vector<const char *>::iterator iter; std::vector<const char *>::iterator iter;
@ -629,6 +642,8 @@ static string com_save_to(exec_context &ec, string cmdline, vector<string> &args
first = false; first = false;
} }
fprintf(outfile, "\n"); fprintf(outfile, "\n");
line_count += 1;
} }
} }
else if (args[0] == "write-cols-to") { else if (args[0] == "write-cols-to") {
@ -648,6 +663,8 @@ static string com_save_to(exec_context &ec, string cmdline, vector<string> &args
text_sub_source::RF_RAW); text_sub_source::RF_RAW);
fputs(line.c_str(), outfile); fputs(line.c_str(), outfile);
fputc('\n', outfile); fputc('\n', outfile);
line_count += 1;
} }
} }
else if (args[0] == "write-json-to") { else if (args[0] == "write-json-to") {
@ -696,6 +713,8 @@ static string com_save_to(exec_context &ec, string cmdline, vector<string> &args
fputs(*iter, outfile); fputs(*iter, outfile);
} }
fprintf(outfile, "\n"); fprintf(outfile, "\n");
line_count += 1;
} }
} else { } else {
bool wrapped = tc->get_word_wrap(); bool wrapped = tc->get_word_wrap();
@ -714,6 +733,8 @@ static string com_save_to(exec_context &ec, string cmdline, vector<string> &args
log_perror(write(STDOUT_FILENO, lr.substr(al.get_string()), log_perror(write(STDOUT_FILENO, lr.substr(al.get_string()),
lr.sublen(al.get_string()))); lr.sublen(al.get_string())));
log_perror(write(STDOUT_FILENO, "\n", 1)); log_perror(write(STDOUT_FILENO, "\n", 1));
line_count += 1;
} }
tc->set_word_wrap(wrapped); tc->set_word_wrap(wrapped);
@ -731,6 +752,8 @@ static string com_save_to(exec_context &ec, string cmdline, vector<string> &args
} }
tc->grep_value_for_line(*iter, line); tc->grep_value_for_line(*iter, line);
fprintf(outfile, "%s\n", line.c_str()); fprintf(outfile, "%s\n", line.c_str());
line_count += 1;
} }
} }
@ -756,13 +779,15 @@ static string com_save_to(exec_context &ec, string cmdline, vector<string> &args
.truncate_to(10); .truncate_to(10);
lnav_data.ld_preview_status_source.get_description() lnav_data.ld_preview_status_source.get_description()
.set_value("First lines of file: %s", fn.c_str()); .set_value("First lines of file: %s", fn.c_str());
} else {
retval = "Wrote " + to_string(line_count) + " line to " + split_args[0];
} }
if (toclose != NULL) { if (toclose != nullptr) {
fclose(toclose); closer(toclose);
} }
outfile = NULL; outfile = nullptr;
return ""; return retval;
} }
static string com_pipe_to(exec_context &ec, string cmdline, vector<string> &args) static string com_pipe_to(exec_context &ec, string cmdline, vector<string> &args)
@ -834,13 +859,11 @@ static string com_pipe_to(exec_context &ec, string cmdline, vector<string> &args
sql_strftime(tmp_str, sizeof(tmp_str), ldh.ldh_line->get_timeval()); sql_strftime(tmp_str, sizeof(tmp_str), ldh.ldh_line->get_timeval());
setenv("log_time", tmp_str, 1); setenv("log_time", tmp_str, 1);
setenv("log_path", ldh.ldh_file->get_filename().c_str(), 1); setenv("log_path", ldh.ldh_file->get_filename().c_str(), 1);
for (vector<logline_value>::iterator iter = ldh.ldh_line_values.begin(); for (auto &ldh_line_value : ldh.ldh_line_values) {
iter != ldh.ldh_line_values.end(); setenv(ldh_line_value.lv_name.get(),
++iter) { ldh_line_value.to_string().c_str(), 1);
setenv(iter->lv_name.get(), iter->to_string().c_str(), 1);
} }
data_parser::element_list_t::iterator iter = auto iter = ldh.ldh_parser->dp_pairs.begin();
ldh.ldh_parser->dp_pairs.begin();
for (size_t lpc = 0; lpc < ldh.ldh_parser->dp_pairs.size(); lpc++, ++iter) { for (size_t lpc = 0; lpc < ldh.ldh_parser->dp_pairs.size(); lpc++, ++iter) {
std::string colname = ldh.ldh_parser->get_element_string( std::string colname = ldh.ldh_parser->get_element_string(
iter->e_sub_elements->front()); iter->e_sub_elements->front());

View File

@ -51,7 +51,7 @@ static clip_command *get_commands()
static clip_command X_CMDS[] = { static clip_command X_CMDS[] = {
{ { "xclip -i > /dev/null 2>&1", { { "xclip -i > /dev/null 2>&1",
"xclip -o < /dev/null 2>/dev/null" } }, "xclip -o < /dev/null 2>/dev/null" } },
{ { NULL, NULL } }, { { nullptr, nullptr } },
}; };
if (system("which pbcopy > /dev/null 2>&1") == 0) { if (system("which pbcopy > /dev/null 2>&1") == 0) {
return OSX_CMDS; return OSX_CMDS;
@ -59,7 +59,7 @@ static clip_command *get_commands()
if (system("which xclip > /dev/null 2>&1") == 0) { if (system("which xclip > /dev/null 2>&1") == 0) {
return X_CMDS; return X_CMDS;
} }
return NULL; return nullptr;
} }
/* XXX For one, this code is kinda crappy. For two, we should probably link /* XXX For one, this code is kinda crappy. For two, we should probably link
@ -70,9 +70,9 @@ FILE *open_clipboard(clip_type_t type, clip_op_t op)
{ {
const char *mode = op == CO_WRITE ? "w" : "r"; const char *mode = op == CO_WRITE ? "w" : "r";
clip_command *cc = get_commands(); clip_command *cc = get_commands();
FILE *pfile = NULL; FILE *pfile = nullptr;
if (cc != NULL && cc[type].cc_cmd[op] != NULL) { if (cc != nullptr && cc[type].cc_cmd[op] != nullptr) {
pfile = popen(cc[type].cc_cmd[op], mode); pfile = popen(cc[type].cc_cmd[op], mode);
} }