mirror of
https://github.com/tstack/lnav
synced 2024-11-11 13:10:36 +00:00
78119dc9f1
Fixes #255
1201 lines
42 KiB
C++
1201 lines
42 KiB
C++
/**
|
|
* Copyright (c) 2015, Timothy Stack
|
|
*
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright notice, this
|
|
* list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
* * Neither the name of Timothy Stack nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "lnav.hh"
|
|
#include "bookmarks.hh"
|
|
#include "sql_util.hh"
|
|
#include "environ_vtab.hh"
|
|
#include "plain_text_source.hh"
|
|
#include "pretty_printer.hh"
|
|
#include "sysclip.hh"
|
|
#include "log_data_helper.hh"
|
|
#include "session_data.hh"
|
|
#include "command_executor.hh"
|
|
#include "termios_guard.hh"
|
|
#include "readline_possibilities.hh"
|
|
#include "field_overlay_source.hh"
|
|
#include "hotkeys.hh"
|
|
#include "log_format_loader.hh"
|
|
|
|
using namespace std;
|
|
|
|
static bookmark_type_t BM_EXAMPLE("");
|
|
|
|
class logline_helper {
|
|
|
|
public:
|
|
logline_helper(logfile_sub_source &lss) : lh_sub_source(lss) {
|
|
|
|
};
|
|
|
|
logline &move_to_msg_start() {
|
|
content_line_t cl = this->lh_sub_source.at(this->lh_current_line);
|
|
logfile *lf = this->lh_sub_source.find(cl);
|
|
logfile::iterator ll = lf->begin() + cl;
|
|
while (ll->is_continued()) {
|
|
--ll;
|
|
--this->lh_current_line;
|
|
}
|
|
|
|
return (*lf)[cl];
|
|
};
|
|
|
|
logline ¤t_line() {
|
|
content_line_t cl = this->lh_sub_source.at(this->lh_current_line);
|
|
logfile *lf = this->lh_sub_source.find(cl);
|
|
|
|
return (*lf)[cl];
|
|
};
|
|
|
|
void annotate() {
|
|
this->lh_string_attrs.clear();
|
|
this->lh_line_values.clear();
|
|
content_line_t cl = this->lh_sub_source.at(this->lh_current_line);
|
|
logfile *lf = this->lh_sub_source.find(cl);
|
|
logfile::iterator ll = lf->begin() + cl;
|
|
log_format *format = lf->get_format();
|
|
lf->read_full_message(ll, this->lh_msg_buffer);
|
|
format->annotate(this->lh_msg_buffer,
|
|
this->lh_string_attrs,
|
|
this->lh_line_values);
|
|
};
|
|
|
|
std::string to_string(const struct line_range &lr) {
|
|
const char *start = this->lh_msg_buffer.get_data();
|
|
|
|
return string(&start[lr.lr_start], lr.length());
|
|
}
|
|
|
|
logfile_sub_source &lh_sub_source;
|
|
vis_line_t lh_current_line;
|
|
shared_buffer_ref lh_msg_buffer;
|
|
string_attrs_t lh_string_attrs;
|
|
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);
|
|
}
|
|
|
|
static void back_ten(int ten_minute)
|
|
{
|
|
textview_curses * tc = lnav_data.ld_view_stack.back();
|
|
logfile_sub_source *lss;
|
|
|
|
lss = dynamic_cast<logfile_sub_source *>(tc->get_sub_source());
|
|
|
|
if (!lss)
|
|
return;
|
|
|
|
time_t hour = rounddown_offset(lnav_data.ld_top_time,
|
|
60 * 60,
|
|
ten_minute * 10 * 60);
|
|
vis_line_t line = lss->find_from_time(hour);
|
|
|
|
--line;
|
|
lnav_data.ld_view_stack.back()->set_top(line);
|
|
}
|
|
|
|
void handle_paging_key(int ch)
|
|
{
|
|
if (lnav_data.ld_view_stack.empty()) {
|
|
return;
|
|
}
|
|
|
|
textview_curses * tc = lnav_data.ld_view_stack.back();
|
|
exec_context &ec = lnav_data.ld_exec_context;
|
|
logfile_sub_source *lss = NULL;
|
|
bookmarks<vis_line_t>::type & bm = tc->get_bookmarks();
|
|
|
|
if (tc->handle_key(ch)) {
|
|
return;
|
|
}
|
|
|
|
lss = dynamic_cast<logfile_sub_source *>(tc->get_sub_source());
|
|
|
|
/* process the command keystroke */
|
|
switch (ch) {
|
|
case 'a':
|
|
if (lnav_data.ld_last_view == NULL) {
|
|
alerter::singleton().chime();
|
|
}
|
|
else {
|
|
textview_curses *tc = lnav_data.ld_last_view;
|
|
|
|
lnav_data.ld_last_view = NULL;
|
|
ensure_view(tc);
|
|
}
|
|
break;
|
|
|
|
case 'A':
|
|
if (lnav_data.ld_last_view == NULL) {
|
|
alerter::singleton().chime();
|
|
}
|
|
else {
|
|
textview_curses *tc = lnav_data.ld_last_view;
|
|
textview_curses *top_tc = lnav_data.ld_view_stack.back();
|
|
text_time_translator *dst_view = dynamic_cast<text_time_translator *>(tc->get_sub_source());
|
|
text_time_translator *src_view = dynamic_cast<text_time_translator *>(top_tc->get_sub_source());
|
|
|
|
lnav_data.ld_last_view = NULL;
|
|
if (src_view != NULL && dst_view != NULL) {
|
|
time_t top_time = src_view->time_for_row(top_tc->get_top());
|
|
|
|
tc->set_top(vis_line_t(dst_view->row_for_time(top_time)));
|
|
}
|
|
ensure_view(tc);
|
|
}
|
|
break;
|
|
|
|
case KEY_F(2):
|
|
if (xterm_mouse::is_available()) {
|
|
lnav_data.ld_mouse.set_enabled(!lnav_data.ld_mouse.is_enabled());
|
|
lnav_data.ld_rl_view->set_value(
|
|
string("info: mouse mode -- ") +
|
|
(lnav_data.ld_mouse.is_enabled() ?
|
|
ANSI_BOLD("enabled") : ANSI_BOLD("disabled")));
|
|
}
|
|
else {
|
|
lnav_data.ld_rl_view->set_value(
|
|
"error: mouse support is not available, make sure your TERM is set to "
|
|
"xterm or xterm-256color");
|
|
}
|
|
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':
|
|
if (lss) {
|
|
lss->text_clear_marks(&textview_curses::BM_USER);
|
|
}
|
|
|
|
lnav_data.ld_select_start.erase(tc);
|
|
lnav_data.ld_last_user_mark.erase(tc);
|
|
tc->get_bookmarks()[&textview_curses::BM_USER].clear();
|
|
tc->reload_data();
|
|
|
|
lnav_data.ld_rl_view->set_value("Cleared bookmarks");
|
|
break;
|
|
|
|
case 'w':
|
|
moveto_cluster(&bookmark_vector<vis_line_t>::next,
|
|
&logfile_sub_source::BM_WARNINGS,
|
|
tc->get_top());
|
|
lnav_data.ld_rl_view->set_alt_value(HELP_MSG_2(
|
|
6, ^,
|
|
"to move to next/previous hour boundary"));
|
|
break;
|
|
|
|
case 'W':
|
|
moveto_cluster(&bookmark_vector<vis_line_t>::prev,
|
|
&logfile_sub_source::BM_WARNINGS,
|
|
tc->get_top());
|
|
lnav_data.ld_rl_view->set_alt_value(HELP_MSG_2(
|
|
6, ^,
|
|
"to move to next/previous hour boundary"));
|
|
break;
|
|
|
|
case 'y':
|
|
tc->set_top(bm[&BM_QUERY].next(tc->get_top()));
|
|
break;
|
|
|
|
case 'Y':
|
|
tc->set_top(bm[&BM_QUERY].prev(tc->get_top()));
|
|
break;
|
|
|
|
case '>':
|
|
{
|
|
std::pair<int, int> range;
|
|
|
|
tc->horiz_shift(tc->get_top(),
|
|
tc->get_bottom(),
|
|
tc->get_left(),
|
|
"$search",
|
|
range);
|
|
if (range.second != INT_MAX) {
|
|
tc->set_left(range.second);
|
|
lnav_data.ld_rl_view->set_alt_value(
|
|
HELP_MSG_1(m, "to bookmark a line"));
|
|
}
|
|
else{
|
|
alerter::singleton().chime();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case '<':
|
|
if (tc->get_left() == 0) {
|
|
alerter::singleton().chime();
|
|
}
|
|
else {
|
|
std::pair<int, int> range;
|
|
|
|
tc->horiz_shift(tc->get_top(),
|
|
tc->get_bottom(),
|
|
tc->get_left(),
|
|
"$search",
|
|
range);
|
|
if (range.first != -1) {
|
|
tc->set_left(range.first);
|
|
}
|
|
else{
|
|
tc->set_left(0);
|
|
}
|
|
lnav_data.ld_rl_view->set_alt_value(
|
|
HELP_MSG_1(m, "to bookmark a line"));
|
|
}
|
|
break;
|
|
|
|
case 'f':
|
|
if (tc == &lnav_data.ld_views[LNV_LOG]) {
|
|
tc->set_top(bm[&logfile_sub_source::BM_FILES].next(tc->get_top()));
|
|
}
|
|
else if (tc == &lnav_data.ld_views[LNV_TEXT]) {
|
|
textfile_sub_source &tss = lnav_data.ld_text_source;
|
|
|
|
if (!tss.empty()) {
|
|
tss.rotate_left();
|
|
redo_search(LNV_TEXT);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'F':
|
|
if (tc == &lnav_data.ld_views[LNV_LOG]) {
|
|
tc->set_top(bm[&logfile_sub_source::BM_FILES].prev(tc->get_top()));
|
|
}
|
|
else if (tc == &lnav_data.ld_views[LNV_TEXT]) {
|
|
textfile_sub_source &tss = lnav_data.ld_text_source;
|
|
|
|
if (!tss.empty()) {
|
|
tss.rotate_right();
|
|
redo_search(LNV_TEXT);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'z':
|
|
if ((lnav_data.ld_zoom_level - 1) < 0) {
|
|
alerter::singleton().chime();
|
|
}
|
|
else {
|
|
execute_command(ec, "zoom-to " + string(lnav_zoom_strings[lnav_data.ld_zoom_level - 1]));
|
|
}
|
|
break;
|
|
|
|
case 'Z':
|
|
if ((lnav_data.ld_zoom_level + 1) >= ZOOM_COUNT) {
|
|
alerter::singleton().chime();
|
|
}
|
|
else {
|
|
execute_command(ec, "zoom-to " + string(lnav_zoom_strings[lnav_data.ld_zoom_level + 1]));
|
|
}
|
|
break;
|
|
|
|
case 'u': {
|
|
vis_line_t user_top, part_top;
|
|
|
|
lnav_data.ld_rl_view->set_alt_value(
|
|
HELP_MSG_1(c, "to copy marked lines to the clipboard; ")
|
|
HELP_MSG_1(C, "to clear marked lines"));
|
|
|
|
user_top = next_cluster(&bookmark_vector<vis_line_t>::next,
|
|
&textview_curses::BM_USER,
|
|
tc->get_top());
|
|
part_top = next_cluster(&bookmark_vector<vis_line_t>::next,
|
|
&textview_curses::BM_PARTITION,
|
|
tc->get_top());
|
|
if (part_top == -1 && user_top == -1) {
|
|
alerter::singleton().chime();
|
|
}
|
|
else if (part_top == -1) {
|
|
tc->set_top(user_top);
|
|
}
|
|
else if (user_top == -1) {
|
|
tc->set_top(part_top);
|
|
}
|
|
else {
|
|
tc->set_top(min(user_top, part_top));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'U': {
|
|
vis_line_t user_top, part_top;
|
|
|
|
user_top = next_cluster(&bookmark_vector<vis_line_t>::prev,
|
|
&textview_curses::BM_USER,
|
|
tc->get_top());
|
|
part_top = next_cluster(&bookmark_vector<vis_line_t>::prev,
|
|
&textview_curses::BM_PARTITION,
|
|
tc->get_top());
|
|
if (part_top == -1 && user_top == -1) {
|
|
alerter::singleton().chime();
|
|
}
|
|
else if (part_top == -1) {
|
|
tc->set_top(user_top);
|
|
}
|
|
else if (user_top == -1) {
|
|
tc->set_top(part_top);
|
|
}
|
|
else {
|
|
tc->set_top(max(user_top, part_top));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'J':
|
|
if (lnav_data.ld_last_user_mark.find(tc) ==
|
|
lnav_data.ld_last_user_mark.end() ||
|
|
!tc->is_visible(vis_line_t(lnav_data.ld_last_user_mark[tc]))) {
|
|
lnav_data.ld_select_start[tc] = tc->get_top();
|
|
lnav_data.ld_last_user_mark[tc] = tc->get_top();
|
|
}
|
|
else {
|
|
vis_line_t height;
|
|
unsigned long width;
|
|
|
|
tc->get_dimensions(height, width);
|
|
if (lnav_data.ld_last_user_mark[tc] > tc->get_bottom() - 2 &&
|
|
tc->get_top() + height < tc->get_inner_height()) {
|
|
tc->shift_top(vis_line_t(1));
|
|
}
|
|
if (lnav_data.ld_last_user_mark[tc] + 1 >=
|
|
tc->get_inner_height()) {
|
|
break;
|
|
}
|
|
lnav_data.ld_last_user_mark[tc] += 1;
|
|
}
|
|
tc->toggle_user_mark(&textview_curses::BM_USER,
|
|
vis_line_t(lnav_data.ld_last_user_mark[tc]));
|
|
tc->reload_data();
|
|
|
|
lnav_data.ld_rl_view->set_alt_value(HELP_MSG_1(
|
|
c,
|
|
"to copy marked lines to the clipboard"));
|
|
break;
|
|
|
|
case 'K':
|
|
{
|
|
int new_mark;
|
|
|
|
if (lnav_data.ld_last_user_mark.find(tc) ==
|
|
lnav_data.ld_last_user_mark.end() ||
|
|
!tc->is_visible(vis_line_t(lnav_data.ld_last_user_mark[tc]))) {
|
|
new_mark = tc->get_top();
|
|
}
|
|
else {
|
|
new_mark = lnav_data.ld_last_user_mark[tc];
|
|
}
|
|
|
|
tc->toggle_user_mark(&textview_curses::BM_USER,
|
|
vis_line_t(new_mark));
|
|
if (new_mark == tc->get_top()) {
|
|
tc->shift_top(vis_line_t(-1));
|
|
}
|
|
if (new_mark > 0) {
|
|
lnav_data.ld_last_user_mark[tc] = new_mark - 1;
|
|
}
|
|
else {
|
|
lnav_data.ld_last_user_mark[tc] = new_mark;
|
|
alerter::singleton().chime();
|
|
}
|
|
lnav_data.ld_select_start[tc] = tc->get_top();
|
|
tc->reload_data();
|
|
|
|
lnav_data.ld_rl_view->set_alt_value(HELP_MSG_1(
|
|
c,
|
|
"to copy marked lines to the clipboard"));
|
|
}
|
|
break;
|
|
|
|
case KEY_CTRL_L: {
|
|
vis_line_t top = tc->get_top();
|
|
vis_line_t bottom = tc->get_bottom();
|
|
char line_break[120];
|
|
|
|
nodelay(lnav_data.ld_window, 0);
|
|
endwin();
|
|
{
|
|
guard_termios tguard(STDOUT_FILENO);
|
|
struct termios new_termios = *tguard.get_termios();
|
|
new_termios.c_oflag |= ONLCR | OPOST;
|
|
tcsetattr(STDOUT_FILENO, TCSANOW, &new_termios);
|
|
snprintf(line_break, sizeof(line_break),
|
|
"\n---------------- Lines %'d-%'d, "
|
|
"press any key to exit lo-fi mode "
|
|
"----------------\n\n",
|
|
(int) top, (int) bottom);
|
|
log_perror(write(STDOUT_FILENO, line_break, strlen(line_break)));
|
|
vector<attr_line_t> rows(bottom - top);
|
|
tc->listview_value_for_rows(*tc, top, rows);
|
|
for (auto &al : rows) {
|
|
struct line_range lr = find_string_attr_range(
|
|
al.get_attrs(), &textview_curses::SA_ORIGINAL_LINE);
|
|
log_perror(write(STDOUT_FILENO, lr.substr(al.get_string()),
|
|
lr.sublen(al.get_string())));
|
|
log_perror(write(STDOUT_FILENO, "\n", 1));
|
|
}
|
|
}
|
|
cbreak();
|
|
getch();
|
|
refresh();
|
|
nodelay(lnav_data.ld_window, 1);
|
|
break;
|
|
}
|
|
|
|
case 'M':
|
|
if (lnav_data.ld_last_user_mark.find(tc) ==
|
|
lnav_data.ld_last_user_mark.end()) {
|
|
alerter::singleton().chime();
|
|
}
|
|
else {
|
|
int start_line = min((int)tc->get_top(),
|
|
lnav_data.ld_last_user_mark[tc] + 1);
|
|
int end_line = max((int)tc->get_top(),
|
|
lnav_data.ld_last_user_mark[tc] - 1);
|
|
|
|
tc->toggle_user_mark(&textview_curses::BM_USER,
|
|
vis_line_t(start_line),
|
|
vis_line_t(end_line));
|
|
tc->reload_data();
|
|
}
|
|
break;
|
|
|
|
#if 0
|
|
case 'S':
|
|
{
|
|
bookmark_vector<vis_line_t>::iterator iter;
|
|
|
|
for (iter = bm[&textview_curses::BM_SEARCH].begin();
|
|
iter != bm[&textview_curses::BM_SEARCH].end();
|
|
++iter) {
|
|
tc->toggle_user_mark(&textview_curses::BM_USER, *iter);
|
|
}
|
|
|
|
lnav_data.ld_last_user_mark[tc] = -1;
|
|
tc->reload_data();
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
case 's':
|
|
if (lss) {
|
|
vis_line_t next_top = vis_line_t(tc->get_top() + 2);
|
|
|
|
if (!lss->is_time_offset_enabled()) {
|
|
lnav_data.ld_rl_view->set_alt_value(
|
|
HELP_MSG_1(T, "to disable elapsed-time mode"));
|
|
}
|
|
lss->set_time_offset(true);
|
|
while (next_top < tc->get_inner_height()) {
|
|
if (lss->find_line(lss->at(next_top))->is_continued()) {
|
|
}
|
|
else if (lss->get_line_accel_direction(next_top) ==
|
|
log_accel::A_DECEL) {
|
|
--next_top;
|
|
tc->set_top(next_top);
|
|
break;
|
|
}
|
|
|
|
++next_top;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'S':
|
|
if (lss) {
|
|
vis_line_t next_top = tc->get_top();
|
|
|
|
if (!lss->is_time_offset_enabled()) {
|
|
lnav_data.ld_rl_view->set_alt_value(
|
|
HELP_MSG_1(T, "to disable elapsed-time mode"));
|
|
}
|
|
lss->set_time_offset(true);
|
|
while (0 <= next_top && next_top < tc->get_inner_height()) {
|
|
if (lss->find_line(lss->at(next_top))->is_continued()) {
|
|
}
|
|
else if (lss->get_line_accel_direction(next_top) ==
|
|
log_accel::A_DECEL) {
|
|
--next_top;
|
|
tc->set_top(next_top);
|
|
break;
|
|
}
|
|
|
|
--next_top;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
if (lss) {
|
|
int ten_minute = (ch - '0') * 10 * 60;
|
|
time_t hour = rounddown(lnav_data.ld_top_time +
|
|
(60 * 60) -
|
|
ten_minute +
|
|
1,
|
|
60 * 60);
|
|
vis_line_t line = lss->find_from_time(hour + ten_minute);
|
|
|
|
tc->set_top(line);
|
|
}
|
|
break;
|
|
|
|
case '!':
|
|
back_ten(1);
|
|
break;
|
|
|
|
case '@':
|
|
back_ten(2);
|
|
break;
|
|
|
|
case '#':
|
|
back_ten(3);
|
|
break;
|
|
|
|
case '$':
|
|
back_ten(4);
|
|
break;
|
|
|
|
case '%':
|
|
back_ten(5);
|
|
break;
|
|
|
|
case '^':
|
|
back_ten(6);
|
|
break;
|
|
|
|
case '9':
|
|
if (lss) {
|
|
double tenth = ((double)tc->get_inner_height()) / 10.0;
|
|
|
|
tc->shift_top(vis_line_t(tenth));
|
|
}
|
|
break;
|
|
|
|
case '(':
|
|
if (lss) {
|
|
double tenth = ((double)tc->get_inner_height()) / 10.0;
|
|
|
|
tc->shift_top(vis_line_t(-tenth));
|
|
}
|
|
break;
|
|
|
|
case '0':
|
|
if (lss) {
|
|
time_t first_time = lnav_data.ld_top_time;
|
|
int step = 24 * 60 * 60;
|
|
vis_line_t line =
|
|
lss->find_from_time(roundup_size(first_time, step));
|
|
|
|
tc->set_top(line);
|
|
}
|
|
break;
|
|
|
|
case ')':
|
|
if (lss) {
|
|
time_t day = rounddown(lnav_data.ld_top_time, 24 * 60 * 60);
|
|
vis_line_t line = lss->find_from_time(day);
|
|
|
|
--line;
|
|
tc->set_top(line);
|
|
}
|
|
break;
|
|
|
|
case 'D':
|
|
if (tc->get_top() == 0) {
|
|
alerter::singleton().chime();
|
|
}
|
|
else if (lss) {
|
|
int step = ch == 'D' ? (24 * 60 * 60) : (60 * 60);
|
|
time_t top_time = lnav_data.ld_top_time;
|
|
vis_line_t line = lss->find_from_time(top_time - step);
|
|
|
|
if (line != 0) {
|
|
--line;
|
|
}
|
|
tc->set_top(line);
|
|
|
|
lnav_data.ld_rl_view->set_alt_value(HELP_MSG_1(/, "to search"));
|
|
}
|
|
break;
|
|
|
|
case 'd':
|
|
if (lss) {
|
|
int step = ch == 'd' ? (24 * 60 * 60) : (60 * 60);
|
|
vis_line_t line =
|
|
lss->find_from_time(lnav_data.ld_top_time + step);
|
|
|
|
tc->set_top(line);
|
|
|
|
lnav_data.ld_rl_view->set_alt_value(HELP_MSG_1(/, "to search"));
|
|
}
|
|
break;
|
|
|
|
case 'o':
|
|
case 'O':
|
|
if (lss) {
|
|
logline_helper start_helper(*lss);
|
|
|
|
start_helper.lh_current_line = tc->get_top();
|
|
logline &start_line = start_helper.move_to_msg_start();
|
|
start_helper.annotate();
|
|
|
|
struct line_range opid_range = find_string_attr_range(
|
|
start_helper.lh_string_attrs, &logline::L_OPID);
|
|
if (!opid_range.is_valid()) {
|
|
alerter::singleton().chime();
|
|
lnav_data.ld_rl_view->set_value("Log message does not contain an opid");
|
|
} else {
|
|
unsigned int opid_hash = start_line.get_opid();
|
|
logline_helper next_helper(*lss);
|
|
bool found = false;
|
|
|
|
next_helper.lh_current_line = start_helper.lh_current_line;
|
|
|
|
while (true) {
|
|
if (ch == 'o') {
|
|
if (++next_helper.lh_current_line >= tc->get_inner_height()) {
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
if (--next_helper.lh_current_line <= 0) {
|
|
break;
|
|
}
|
|
}
|
|
logline &next_line = next_helper.current_line();
|
|
if (next_line.is_continued()) {
|
|
continue;
|
|
}
|
|
if (next_line.get_opid() != opid_hash) {
|
|
continue;
|
|
}
|
|
next_helper.annotate();
|
|
struct line_range opid_next_range = find_string_attr_range(
|
|
next_helper.lh_string_attrs, &logline::L_OPID);
|
|
const char *start_opid = start_helper.lh_msg_buffer.get_data_at(opid_range.lr_start);
|
|
const char *next_opid = next_helper.lh_msg_buffer.get_data_at(opid_next_range.lr_start);
|
|
if (opid_range.length() != opid_next_range.length() ||
|
|
memcmp(start_opid, next_opid, opid_range.length()) != 0) {
|
|
continue;
|
|
}
|
|
found = true;
|
|
break;
|
|
}
|
|
if (found) {
|
|
lnav_data.ld_rl_view->set_value("");
|
|
tc->set_top(next_helper.lh_current_line);
|
|
}
|
|
else {
|
|
string opid_str = start_helper.to_string(opid_range);
|
|
|
|
lnav_data.ld_rl_view->set_value(
|
|
"No more messages found with opid: " + opid_str);
|
|
alerter::singleton().chime();
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ':':
|
|
if (lnav_data.ld_views[LNV_LOG].get_inner_height() > 0) {
|
|
static const char *MOVE_TIMES[] = {
|
|
"here",
|
|
"now",
|
|
"today",
|
|
"yesterday",
|
|
NULL
|
|
};
|
|
|
|
logfile_sub_source &lss = lnav_data.ld_log_source;
|
|
textview_curses & log_view = lnav_data.ld_views[LNV_LOG];
|
|
content_line_t cl = lss.at(log_view.get_top());
|
|
logfile * lf = lss.find(cl);
|
|
logfile::iterator ll = lf->begin() + cl;
|
|
log_data_helper ldh(lss);
|
|
|
|
lnav_data.ld_exec_context.ec_top_line = tc->get_top();
|
|
|
|
lnav_data.ld_rl_view->clear_possibilities(LNM_COMMAND, "numeric-colname");
|
|
lnav_data.ld_rl_view->clear_possibilities(LNM_COMMAND, "colname");
|
|
|
|
ldh.parse_line(log_view.get_top(), true);
|
|
|
|
if (tc == &lnav_data.ld_views[LNV_DB]) {
|
|
db_label_source &dls = lnav_data.ld_db_row_source;
|
|
|
|
for (vector<db_label_source::header_meta>::iterator iter = dls.dls_headers.begin();
|
|
iter != dls.dls_headers.end();
|
|
++iter) {
|
|
if (!iter->hm_graphable) {
|
|
continue;
|
|
}
|
|
|
|
lnav_data.ld_rl_view->add_possibility(LNM_COMMAND,
|
|
"numeric-colname",
|
|
iter->hm_name);
|
|
}
|
|
}
|
|
else {
|
|
for (vector<logline_value>::iterator iter = ldh.ldh_line_values.begin();
|
|
iter != ldh.ldh_line_values.end();
|
|
++iter) {
|
|
const logline_value_stats *stats = iter->lv_format->stats_for_value(
|
|
iter->lv_name);
|
|
|
|
if (stats == NULL) {
|
|
continue;
|
|
}
|
|
|
|
lnav_data.ld_rl_view->add_possibility(LNM_COMMAND,
|
|
"numeric-colname",
|
|
iter->lv_name.to_string());
|
|
}
|
|
}
|
|
|
|
for (vector<string>::iterator iter = ldh.ldh_namer->cn_names.begin();
|
|
iter != ldh.ldh_namer->cn_names.end();
|
|
++iter) {
|
|
lnav_data.ld_rl_view->add_possibility(LNM_COMMAND, "colname", *iter);
|
|
}
|
|
|
|
ldh.clear();
|
|
|
|
readline_curses *rlc = lnav_data.ld_rl_view;
|
|
|
|
rlc->clear_possibilities(LNM_COMMAND, "move-time");
|
|
rlc->add_possibility(LNM_COMMAND, "move-time", MOVE_TIMES);
|
|
rlc->clear_possibilities(LNM_COMMAND, "line-time");
|
|
{
|
|
struct timeval tv = lf->get_time_offset();
|
|
char buffer[64];
|
|
|
|
sql_strftime(buffer, sizeof(buffer),
|
|
ll->get_time(), ll->get_millis(), 'T');
|
|
rlc->add_possibility(LNM_COMMAND,
|
|
"line-time",
|
|
buffer);
|
|
rlc->add_possibility(LNM_COMMAND,
|
|
"move-time",
|
|
buffer);
|
|
sql_strftime(buffer, sizeof(buffer),
|
|
ll->get_time() - tv.tv_sec,
|
|
ll->get_millis() - (tv.tv_usec / 1000),
|
|
'T');
|
|
rlc->add_possibility(LNM_COMMAND,
|
|
"line-time",
|
|
buffer);
|
|
rlc->add_possibility(LNM_COMMAND,
|
|
"move-time",
|
|
buffer);
|
|
}
|
|
}
|
|
|
|
add_view_text_possibilities(LNM_COMMAND, "filter", tc);
|
|
lnav_data.ld_rl_view->
|
|
add_possibility(LNM_COMMAND, "filter",
|
|
lnav_data.ld_last_search[tc - lnav_data.ld_views]);
|
|
add_filter_possibilities(tc);
|
|
add_mark_possibilities();
|
|
add_config_possibilities();
|
|
add_env_possibilities(LNM_COMMAND);
|
|
lnav_data.ld_mode = LNM_COMMAND;
|
|
lnav_data.ld_rl_view->focus(LNM_COMMAND, ":");
|
|
break;
|
|
|
|
case '/':
|
|
lnav_data.ld_mode = LNM_SEARCH;
|
|
lnav_data.ld_previous_search = lnav_data.ld_last_search[tc - lnav_data.ld_views];
|
|
lnav_data.ld_search_start_line = tc->get_top();
|
|
add_view_text_possibilities(LNM_SEARCH, "*", tc);
|
|
lnav_data.ld_rl_view->focus(LNM_SEARCH, "/");
|
|
lnav_data.ld_bottom_source.set_prompt(
|
|
"Enter a regular expression to search for: "
|
|
"(Press " ANSI_BOLD("CTRL+]") " to abort)");
|
|
break;
|
|
|
|
case ';':
|
|
if (tc == &lnav_data.ld_views[LNV_LOG] ||
|
|
tc == &lnav_data.ld_views[LNV_DB] ||
|
|
tc == &lnav_data.ld_views[LNV_SCHEMA]) {
|
|
textview_curses &log_view = lnav_data.ld_views[LNV_LOG];
|
|
lnav_data.ld_exec_context.ec_top_line = tc->get_top();
|
|
|
|
lnav_data.ld_mode = LNM_SQL;
|
|
setup_logline_table();
|
|
lnav_data.ld_rl_view->focus(LNM_SQL, ";");
|
|
|
|
lnav_data.ld_bottom_source.update_loading(0, 0);
|
|
lnav_data.ld_status[LNS_BOTTOM].do_update();
|
|
|
|
field_overlay_source *fos;
|
|
|
|
fos = (field_overlay_source *)log_view.get_overlay_source();
|
|
fos->fos_active_prev = fos->fos_active;
|
|
if (!fos->fos_active) {
|
|
fos->fos_active = true;
|
|
tc->reload_data();
|
|
}
|
|
lnav_data.ld_bottom_source.set_prompt("Enter an SQL query: (Press "
|
|
ANSI_BOLD("CTRL+]") " to abort)");
|
|
}
|
|
break;
|
|
|
|
case '|': {
|
|
map<string, vector<script_metadata> > &scripts = lnav_data.ld_scripts;
|
|
|
|
lnav_data.ld_mode = LNM_EXEC;
|
|
|
|
lnav_data.ld_exec_context.ec_top_line = tc->get_top();
|
|
lnav_data.ld_rl_view->clear_possibilities(LNM_EXEC, "__command");
|
|
find_format_scripts(lnav_data.ld_config_paths, scripts);
|
|
for (const auto &iter : scripts) {
|
|
lnav_data.ld_rl_view->add_possibility(LNM_EXEC, "__command", iter.first);
|
|
}
|
|
add_view_text_possibilities(LNM_EXEC, "*", tc);
|
|
add_env_possibilities(LNM_EXEC);
|
|
lnav_data.ld_rl_view->focus(LNM_EXEC, "|");
|
|
lnav_data.ld_bottom_source.set_prompt(
|
|
"Enter a script to execute: (Press "
|
|
ANSI_BOLD("CTRL+]") " to abort)");
|
|
break;
|
|
}
|
|
|
|
case 'p':
|
|
if (tc == &lnav_data.ld_views[LNV_LOG]) {
|
|
field_overlay_source *fos;
|
|
|
|
fos = (field_overlay_source *) tc->get_overlay_source();
|
|
fos->fos_active = !fos->fos_active;
|
|
tc->reload_data();
|
|
}
|
|
else if (tc == &lnav_data.ld_views[LNV_DB]) {
|
|
db_overlay_source *dos = (db_overlay_source *) tc->get_overlay_source();
|
|
|
|
dos->dos_active = !dos->dos_active;
|
|
tc->reload_data();
|
|
}
|
|
break;
|
|
|
|
case 'P':
|
|
toggle_view(&lnav_data.ld_views[LNV_PRETTY]);
|
|
break;
|
|
|
|
case 't':
|
|
if (lnav_data.ld_text_source.current_file() == NULL) {
|
|
alerter::singleton().chime();
|
|
lnav_data.ld_rl_view->set_value("No text files loaded");
|
|
}
|
|
else if (toggle_view(&lnav_data.ld_views[LNV_TEXT])) {
|
|
lnav_data.ld_rl_view->set_alt_value(HELP_MSG_2(
|
|
f, F,
|
|
"to switch to the next/previous file"));
|
|
}
|
|
break;
|
|
|
|
case 'T':
|
|
lnav_data.ld_log_source.toggle_time_offset();
|
|
if (lss && lss->is_time_offset_enabled()) {
|
|
lnav_data.ld_rl_view->set_alt_value(
|
|
HELP_MSG_2(s, S, "to move forward/backward through slow downs"));
|
|
}
|
|
tc->reload_data();
|
|
break;
|
|
|
|
case 'i':
|
|
if (toggle_view(&lnav_data.ld_views[LNV_HISTOGRAM])) {
|
|
lnav_data.ld_rl_view->set_alt_value(
|
|
HELP_MSG_2(z, Z, "to zoom in/out"));
|
|
}
|
|
else {
|
|
lnav_data.ld_rl_view->set_alt_value("");
|
|
}
|
|
break;
|
|
|
|
case 'I':
|
|
{
|
|
time_t log_top = lnav_data.ld_top_time;
|
|
hist_source2 &hs = lnav_data.ld_hist_source2;
|
|
|
|
if (toggle_view(&lnav_data.ld_views[LNV_HISTOGRAM])) {
|
|
tc = lnav_data.ld_view_stack.back();
|
|
tc->set_top(vis_line_t(hs.row_for_time(log_top)));
|
|
}
|
|
else {
|
|
textview_curses &hist_tc = lnav_data.ld_views[LNV_HISTOGRAM];
|
|
textview_curses &log_tc = lnav_data.ld_views[LNV_LOG];
|
|
lss = &lnav_data.ld_log_source;
|
|
time_t hist_top_time = hs.time_for_row(hist_tc.get_top());
|
|
time_t curr_top_time = lss->time_for_row(log_tc.get_top());
|
|
if (hs.row_for_time(hist_top_time) != hs.row_for_time(curr_top_time)) {
|
|
vis_line_t new_top = lss->find_from_time(hist_top_time);
|
|
log_tc.set_top(new_top);
|
|
log_tc.set_needs_update();
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case '?':
|
|
toggle_view(&lnav_data.ld_views[LNV_HELP]);
|
|
break;
|
|
|
|
case 'v':
|
|
toggle_view(&lnav_data.ld_views[LNV_DB]);
|
|
break;
|
|
|
|
case 'V':
|
|
{
|
|
textview_curses *db_tc = &lnav_data.ld_views[LNV_DB];
|
|
db_label_source &dls = lnav_data.ld_db_row_source;
|
|
|
|
if (toggle_view(db_tc)) {
|
|
long log_line_index = dls.column_name_to_index("log_line");
|
|
|
|
if (log_line_index == -1) {
|
|
log_line_index = dls.column_name_to_index("min(log_line)");
|
|
}
|
|
|
|
if (log_line_index != -1) {
|
|
char linestr[64];
|
|
int line_number = (int)tc->get_top();
|
|
unsigned int row;
|
|
|
|
snprintf(linestr, sizeof(linestr), "%d", line_number);
|
|
for (row = 0; row < dls.dls_rows.size(); row++) {
|
|
if (strcmp(dls.dls_rows[row][log_line_index],
|
|
linestr) == 0) {
|
|
vis_line_t db_line(row);
|
|
|
|
db_tc->set_top(db_line);
|
|
db_tc->set_needs_update();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (db_tc->get_inner_height() > 0) {
|
|
int db_row = db_tc->get_top();
|
|
tc = &lnav_data.ld_views[LNV_LOG];
|
|
long log_line_index = dls.column_name_to_index("log_line");
|
|
|
|
if (log_line_index == -1) {
|
|
log_line_index = dls.column_name_to_index("min(log_line)");
|
|
}
|
|
|
|
if (log_line_index != -1) {
|
|
unsigned int line_number;
|
|
|
|
if (sscanf(dls.dls_rows[db_row][log_line_index],
|
|
"%d",
|
|
&line_number) &&
|
|
line_number < tc->listview_rows(*tc)) {
|
|
tc->set_top(vis_line_t(line_number));
|
|
tc->set_needs_update();
|
|
}
|
|
}
|
|
else {
|
|
for (size_t lpc = 0; lpc < dls.dls_headers.size(); lpc++) {
|
|
date_time_scanner dts;
|
|
struct timeval tv;
|
|
struct exttm tm;
|
|
const char *col_value = dls.dls_rows[db_row][lpc];
|
|
size_t col_len = strlen(col_value);
|
|
|
|
if (dts.scan(col_value, col_len, NULL, &tm, tv) != NULL) {
|
|
vis_line_t vl;
|
|
|
|
vl = lnav_data.ld_log_source.find_from_time(tv);
|
|
tc->set_top(vl);
|
|
tc->set_needs_update();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case '\t':
|
|
case KEY_BTAB:
|
|
if (tc == &lnav_data.ld_views[LNV_DB])
|
|
{
|
|
stacked_bar_chart<string> &chart = lnav_data.ld_db_row_source.dls_chart;
|
|
|
|
if (chart.show_next_ident(ch == '\t' ? 1 : -1) == -1) {
|
|
lnav_data.ld_rl_view->set_value("Graphing all values");
|
|
}
|
|
else {
|
|
string colname;
|
|
|
|
chart.get_ident_to_show(colname);
|
|
lnav_data.ld_rl_view->set_value(
|
|
"Graphing column " ANSI_BOLD_START +
|
|
colname + ANSI_NORM);
|
|
}
|
|
tc->reload_data();
|
|
}
|
|
break;
|
|
|
|
case 'x':
|
|
if (tc->toggle_hide_fields()) {
|
|
lnav_data.ld_rl_view->set_value("Showing hidden fields");
|
|
} else {
|
|
lnav_data.ld_rl_view->set_value("Hiding hidden fields");
|
|
}
|
|
tc->set_needs_update();
|
|
break;
|
|
|
|
case 'X':
|
|
lnav_data.ld_rl_view->set_value(execute_command(ec, "close"));
|
|
break;
|
|
|
|
case '\\':
|
|
{
|
|
vis_bookmarks &bm = tc->get_bookmarks();
|
|
string ex;
|
|
|
|
for (bookmark_vector<vis_line_t>::iterator iter =
|
|
bm[&BM_EXAMPLE].begin();
|
|
iter != bm[&BM_EXAMPLE].end();
|
|
++iter) {
|
|
string line;
|
|
|
|
tc->get_sub_source()->text_value_for_line(*tc, *iter, line);
|
|
ex += line + "\n";
|
|
}
|
|
lnav_data.ld_views[LNV_EXAMPLE].set_sub_source(new plain_text_source(
|
|
ex));
|
|
ensure_view(&lnav_data.ld_views[LNV_EXAMPLE]);
|
|
}
|
|
break;
|
|
|
|
case 'r':
|
|
case 'R':
|
|
if (lss) {
|
|
if (lnav_data.ld_last_relative_time.empty()) {
|
|
lnav_data.ld_rl_view->set_value(
|
|
"Use the 'goto' command to set the relative time to move by");
|
|
}
|
|
else {
|
|
vis_line_t vl = tc->get_top();
|
|
relative_time rt = lnav_data.ld_last_relative_time;
|
|
struct timeval tv;
|
|
content_line_t cl;
|
|
struct exttm tm;
|
|
|
|
if (ch == 'R') {
|
|
rt.negate();
|
|
}
|
|
|
|
cl = lnav_data.ld_log_source.at(vl);
|
|
logline *ll = lnav_data.ld_log_source.find_line(cl);
|
|
ll->to_exttm(tm);
|
|
rt.add(tm);
|
|
tv.tv_sec = timegm(&tm.et_tm);
|
|
tv.tv_usec = tm.et_nsec / 1000;
|
|
vl = lnav_data.ld_log_source.find_from_time(tv);
|
|
if (rt.is_negative() && (vl > vis_line_t(0))) {
|
|
--vl;
|
|
if (vl == tc->get_top()) {
|
|
vl = vis_line_t(0);
|
|
}
|
|
}
|
|
tc->set_top(vl);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KEY_CTRL_R:
|
|
reset_session();
|
|
rebuild_indexes(true);
|
|
break;
|
|
|
|
case KEY_CTRL_W:
|
|
execute_command(ec, lnav_data.ld_views[LNV_LOG].get_word_wrap() ?
|
|
"disable-word-wrap" : "enable-word-wrap");
|
|
break;
|
|
|
|
default:
|
|
log_warning("unhandled %d", ch);
|
|
lnav_data.ld_rl_view->set_value("Unrecognized keystroke, press "
|
|
ANSI_BOLD("?")
|
|
" to view help");
|
|
alerter::singleton().chime();
|
|
break;
|
|
}
|
|
}
|