mirror of
https://github.com/tstack/lnav
synced 2024-11-11 13:10:36 +00:00
[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:
parent
0b822739ea
commit
db6c619e4e
4
NEWS
4
NEWS
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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());
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user