1 /**
2 * Copyright (c) 2015, Timothy Stack
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * * Redistributions of source code must retain the above copyright notice, this
10 * list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 * * Neither the name of Timothy Stack nor the names of its contributors
15 * may be used to endorse or promote products derived from this software
16 * without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include "config.h"
31
32 #include "base/injector.hh"
33 #include "base/math_util.hh"
34 #include "lnav.hh"
35 #include "bookmarks.hh"
36 #include "sql_util.hh"
37 #include "environ_vtab.hh"
38 #include "plain_text_source.hh"
39 #include "pretty_printer.hh"
40 #include "sysclip.hh"
41 #include "log_data_helper.hh"
42 #include "command_executor.hh"
43 #include "termios_guard.hh"
44 #include "readline_highlighters.hh"
45 #include "field_overlay_source.hh"
46 #include "hotkeys.hh"
47 #include "base/opt_util.hh"
48 #include "shlex.hh"
49 #include "lnav_util.hh"
50 #include "lnav_config.hh"
51 #include "bound_tags.hh"
52 #include "xterm_mouse.hh"
53
54 using namespace std;
55
56 class logline_helper {
57
58 public:
logline_helper(logfile_sub_source & lss)59 logline_helper(logfile_sub_source &lss) : lh_sub_source(lss) {
60
61 };
62
move_to_msg_start()63 logline &move_to_msg_start() {
64 content_line_t cl = this->lh_sub_source.at(this->lh_current_line);
65 std::shared_ptr<logfile> lf = this->lh_sub_source.find(cl);
66 auto ll = lf->begin() + cl;
67 while (!ll->is_message()) {
68 --ll;
69 --this->lh_current_line;
70 }
71
72 return (*lf)[cl];
73 };
74
current_line()75 logline ¤t_line() {
76 content_line_t cl = this->lh_sub_source.at(this->lh_current_line);
77 std::shared_ptr<logfile> lf = this->lh_sub_source.find(cl);
78
79 return (*lf)[cl];
80 };
81
annotate()82 void annotate() {
83 this->lh_string_attrs.clear();
84 this->lh_line_values.clear();
85 content_line_t cl = this->lh_sub_source.at(this->lh_current_line);
86 std::shared_ptr<logfile> lf = this->lh_sub_source.find(cl);
87 auto ll = lf->begin() + cl;
88 auto format = lf->get_format();
89 lf->read_full_message(ll, this->lh_msg_buffer);
90 format->annotate(cl,
91 this->lh_msg_buffer,
92 this->lh_string_attrs,
93 this->lh_line_values,
94 false);
95 };
96
to_string(const struct line_range & lr)97 std::string to_string(const struct line_range &lr) {
98 const char *start = this->lh_msg_buffer.get_data();
99
100 return string(&start[lr.lr_start], lr.length());
101 }
102
103 logfile_sub_source &lh_sub_source;
104 vis_line_t lh_current_line;
105 shared_buffer_ref lh_msg_buffer;
106 string_attrs_t lh_string_attrs;
107 vector<logline_value> lh_line_values;
108 };
109
key_sql_callback(exec_context & ec,sqlite3_stmt * stmt)110 static int key_sql_callback(exec_context &ec, sqlite3_stmt *stmt)
111 {
112 if (!sqlite3_stmt_busy(stmt)) {
113 return 0;
114 }
115
116 int ncols = sqlite3_column_count(stmt);
117
118 auto &vars = ec.ec_local_vars.top();
119
120 for (int lpc = 0; lpc < ncols; lpc++) {
121 const char *column_name = sqlite3_column_name(stmt, lpc);
122
123 if (sql_ident_needs_quote(column_name)) {
124 continue;
125 }
126 if (sqlite3_column_type(stmt, lpc) == SQLITE_NULL) {
127 continue;
128 }
129
130 vars[column_name] = string((const char *) sqlite3_column_text(stmt, lpc));
131 }
132
133 return 0;
134 }
135
handle_keyseq(const char * keyseq)136 bool handle_keyseq(const char *keyseq)
137 {
138 key_map &km = lnav_config.lc_active_keymap;
139
140 const auto &iter = km.km_seq_to_cmd.find(keyseq);
141 if (iter == km.km_seq_to_cmd.end()) {
142 return false;
143 }
144
145 vector<logline_value> values;
146 exec_context ec(&values, key_sql_callback, pipe_callback);
147 auto &var_stack = ec.ec_local_vars;
148
149 ec.ec_global_vars = lnav_data.ld_exec_context.ec_global_vars;
150 var_stack.push(map<string, string>());
151 auto &vars = var_stack.top();
152 vars["keyseq"] = keyseq;
153 const auto &kc = iter->second;
154
155 log_debug("executing key sequence %s: %s", keyseq, kc.kc_cmd.c_str());
156 auto result = execute_any(ec, kc.kc_cmd);
157 lnav_data.ld_rl_view->set_value(
158 result.map(ok_prefix).orElse(err_to_ok).unwrap());
159
160 if (!kc.kc_alt_msg.empty()) {
161 shlex lexer(kc.kc_alt_msg);
162 string expanded_msg;
163
164 if (lexer.eval(expanded_msg, {
165 &vars,
166 &ec.ec_global_vars,
167 })) {
168 lnav_data.ld_rl_view->set_alt_value(expanded_msg);
169 }
170 }
171
172 return true;
173 }
174
handle_paging_key(int ch)175 bool handle_paging_key(int ch)
176 {
177 if (lnav_data.ld_view_stack.empty()) {
178 return false;
179 }
180
181 textview_curses *tc = *lnav_data.ld_view_stack.top();
182 exec_context &ec = lnav_data.ld_exec_context;
183 logfile_sub_source *lss = nullptr;
184 text_sub_source *tc_tss = tc->get_sub_source();
185 bookmarks<vis_line_t>::type & bm = tc->get_bookmarks();
186
187 char keyseq[16];
188
189 snprintf(keyseq, sizeof(keyseq), "x%02x", ch);
190
191 if (handle_keyseq(keyseq)) {
192 return true;
193 }
194
195 if (tc->handle_key(ch)) {
196 return true;
197 }
198
199 lss = dynamic_cast<logfile_sub_source *>(tc->get_sub_source());
200
201 /* process the command keystroke */
202 switch (ch) {
203 case 0x7f:
204 case KEY_BACKSPACE:
205 break;
206
207 case 'a':
208 if (lnav_data.ld_last_view == nullptr) {
209 alerter::singleton().chime();
210 }
211 else {
212 textview_curses *tc = lnav_data.ld_last_view;
213
214 lnav_data.ld_last_view = nullptr;
215 ensure_view(tc);
216 }
217 break;
218
219 case 'A':
220 if (lnav_data.ld_last_view == nullptr) {
221 alerter::singleton().chime();
222 }
223 else {
224 textview_curses *tc = lnav_data.ld_last_view;
225 textview_curses *top_tc = *lnav_data.ld_view_stack.top();
226 auto *dst_view = dynamic_cast<text_time_translator *>(tc->get_sub_source());
227 auto *src_view = dynamic_cast<text_time_translator *>(top_tc->get_sub_source());
228
229 lnav_data.ld_last_view = nullptr;
230 if (src_view != nullptr && dst_view != nullptr) {
231 src_view->time_for_row(top_tc->get_top()) | [dst_view, tc](auto top_time) {
232 dst_view->row_for_time(top_time) | [tc](auto row) {
233 tc->set_top(row);
234 };
235 };
236 }
237 ensure_view(tc);
238 }
239 break;
240
241 case KEY_F(2):
242 if (xterm_mouse::is_available()) {
243 auto& mouse_i = injector::get<xterm_mouse&>();
244 mouse_i.set_enabled(!mouse_i.is_enabled());
245 lnav_data.ld_rl_view->set_value(
246 ok_prefix("info: mouse mode -- ") +
247 (mouse_i.is_enabled() ?
248 ANSI_BOLD("enabled") : ANSI_BOLD("disabled")));
249 }
250 else {
251 lnav_data.ld_rl_view->set_value(
252 "error: mouse support is not available, make sure your TERM is set to "
253 "xterm or xterm-256color");
254 }
255 break;
256
257 case 'C':
258 if (lss) {
259 lss->text_clear_marks(&textview_curses::BM_USER);
260 }
261
262 lnav_data.ld_select_start.erase(tc);
263 lnav_data.ld_last_user_mark.erase(tc);
264 tc->get_bookmarks()[&textview_curses::BM_USER].clear();
265 tc->reload_data();
266
267 lnav_data.ld_rl_view->set_value(ok_prefix("Cleared bookmarks"));
268 break;
269
270 case '>':
271 {
272 std::pair<int, int> range;
273
274 tc->horiz_shift(tc->get_top(),
275 tc->get_bottom(),
276 tc->get_left(),
277 range);
278 if (range.second != INT_MAX) {
279 tc->set_left(range.second);
280 lnav_data.ld_rl_view->set_alt_value(
281 HELP_MSG_1(m, "to bookmark a line"));
282 }
283 else{
284 alerter::singleton().chime();
285 }
286 }
287 break;
288
289 case '<':
290 if (tc->get_left() == 0) {
291 alerter::singleton().chime();
292 }
293 else {
294 std::pair<int, int> range;
295
296 tc->horiz_shift(tc->get_top(),
297 tc->get_bottom(),
298 tc->get_left(),
299 range);
300 if (range.first != -1) {
301 tc->set_left(range.first);
302 }
303 else{
304 tc->set_left(0);
305 }
306 lnav_data.ld_rl_view->set_alt_value(
307 HELP_MSG_1(m, "to bookmark a line"));
308 }
309 break;
310
311 case 'f':
312 if (tc == &lnav_data.ld_views[LNV_LOG]) {
313 tc->set_top(bm[&logfile_sub_source::BM_FILES].next(tc->get_top()));
314 }
315 else if (tc == &lnav_data.ld_views[LNV_TEXT]) {
316 textfile_sub_source &tss = lnav_data.ld_text_source;
317
318 if (!tss.empty()) {
319 tss.rotate_left();
320 }
321 }
322 break;
323
324 case 'F':
325 if (tc == &lnav_data.ld_views[LNV_LOG]) {
326 tc->set_top(bm[&logfile_sub_source::BM_FILES].prev(tc->get_top()));
327 }
328 else if (tc == &lnav_data.ld_views[LNV_TEXT]) {
329 textfile_sub_source &tss = lnav_data.ld_text_source;
330
331 if (!tss.empty()) {
332 tss.rotate_right();
333 }
334 }
335 break;
336
337 case 'z':
338 if ((lnav_data.ld_zoom_level - 1) < 0) {
339 alerter::singleton().chime();
340 }
341 else {
342 execute_command(ec, "zoom-to " + string(lnav_zoom_strings[lnav_data.ld_zoom_level - 1]));
343 }
344 break;
345
346 case 'Z':
347 if ((lnav_data.ld_zoom_level + 1) >= ZOOM_COUNT) {
348 alerter::singleton().chime();
349 }
350 else {
351 execute_command(ec, "zoom-to " + string(lnav_zoom_strings[lnav_data.ld_zoom_level + 1]));
352 }
353 break;
354
355 case 'J':
356 if (lnav_data.ld_last_user_mark.find(tc) ==
357 lnav_data.ld_last_user_mark.end() ||
358 !tc->is_line_visible(vis_line_t(lnav_data.ld_last_user_mark[tc]))) {
359 lnav_data.ld_select_start[tc] = tc->get_top();
360 lnav_data.ld_last_user_mark[tc] = tc->get_top();
361 }
362 else {
363 vis_line_t height;
364 unsigned long width;
365
366 tc->get_dimensions(height, width);
367 if (lnav_data.ld_last_user_mark[tc] > (tc->get_bottom() - 2) &&
368 tc->get_top() + height < tc->get_inner_height()) {
369 tc->shift_top(1_vl);
370 }
371 if (lnav_data.ld_last_user_mark[tc] + 1 >=
372 tc->get_inner_height()) {
373 break;
374 }
375 lnav_data.ld_last_user_mark[tc] += 1;
376 }
377 tc->toggle_user_mark(&textview_curses::BM_USER,
378 vis_line_t(lnav_data.ld_last_user_mark[tc]));
379 tc->reload_data();
380
381 lnav_data.ld_rl_view->set_alt_value(HELP_MSG_1(
382 c,
383 "to copy marked lines to the clipboard"));
384 break;
385
386 case 'K':
387 {
388 int new_mark;
389
390 if (lnav_data.ld_last_user_mark.find(tc) ==
391 lnav_data.ld_last_user_mark.end() ||
392 !tc->is_line_visible(vis_line_t(lnav_data.ld_last_user_mark[tc]))) {
393 new_mark = tc->get_top();
394 }
395 else {
396 new_mark = lnav_data.ld_last_user_mark[tc];
397 }
398
399 tc->toggle_user_mark(&textview_curses::BM_USER,
400 vis_line_t(new_mark));
401 if (new_mark == tc->get_top()) {
402 tc->shift_top(-1_vl);
403 }
404 if (new_mark > 0) {
405 lnav_data.ld_last_user_mark[tc] = new_mark - 1;
406 }
407 else {
408 lnav_data.ld_last_user_mark[tc] = new_mark;
409 alerter::singleton().chime();
410 }
411 lnav_data.ld_select_start[tc] = tc->get_top();
412 tc->reload_data();
413
414 lnav_data.ld_rl_view->set_alt_value(HELP_MSG_1(
415 c,
416 "to copy marked lines to the clipboard"));
417 }
418 break;
419
420 case 'M':
421 if (lnav_data.ld_last_user_mark.find(tc) ==
422 lnav_data.ld_last_user_mark.end()) {
423 alerter::singleton().chime();
424 }
425 else {
426 int start_line = min((int)tc->get_top(),
427 lnav_data.ld_last_user_mark[tc] + 1);
428 int end_line = max((int)tc->get_top(),
429 lnav_data.ld_last_user_mark[tc] - 1);
430
431 tc->toggle_user_mark(&textview_curses::BM_USER,
432 vis_line_t(start_line),
433 vis_line_t(end_line));
434 tc->reload_data();
435 }
436 break;
437
438 #if 0
439 case 'S':
440 {
441 bookmark_vector<vis_line_t>::iterator iter;
442
443 for (iter = bm[&textview_curses::BM_SEARCH].begin();
444 iter != bm[&textview_curses::BM_SEARCH].end();
445 ++iter) {
446 tc->toggle_user_mark(&textview_curses::BM_USER, *iter);
447 }
448
449 lnav_data.ld_last_user_mark[tc] = -1;
450 tc->reload_data();
451 }
452 break;
453 #endif
454
455 case 's':
456 if (lss) {
457 vis_line_t next_top = vis_line_t(tc->get_top() + 2);
458
459 if (!lss->is_time_offset_enabled()) {
460 lnav_data.ld_rl_view->set_alt_value(
461 HELP_MSG_1(T, "to disable elapsed-time mode"));
462 }
463 lss->set_time_offset(true);
464 while (next_top < tc->get_inner_height()) {
465 if (!lss->find_line(lss->at(next_top))->is_message()) {
466 }
467 else if (lss->get_line_accel_direction(next_top) ==
468 log_accel::A_DECEL) {
469 --next_top;
470 tc->set_top(next_top);
471 break;
472 }
473
474 ++next_top;
475 }
476 }
477 break;
478
479 case 'S':
480 if (lss) {
481 vis_line_t next_top = tc->get_top();
482
483 if (!lss->is_time_offset_enabled()) {
484 lnav_data.ld_rl_view->set_alt_value(
485 HELP_MSG_1(T, "to disable elapsed-time mode"));
486 }
487 lss->set_time_offset(true);
488 while (0 <= next_top && next_top < tc->get_inner_height()) {
489 if (!lss->find_line(lss->at(next_top))->is_message()) {
490 }
491 else if (lss->get_line_accel_direction(next_top) ==
492 log_accel::A_DECEL) {
493 --next_top;
494 tc->set_top(next_top);
495 break;
496 }
497
498 --next_top;
499 }
500 }
501 break;
502
503 case '9':
504 if (lss) {
505 double tenth = ((double)tc->get_inner_height()) / 10.0;
506
507 tc->shift_top(vis_line_t(tenth));
508 }
509 break;
510
511 case '(':
512 if (lss) {
513 double tenth = ((double)tc->get_inner_height()) / 10.0;
514
515 tc->shift_top(vis_line_t(-tenth));
516 }
517 break;
518
519 case '0':
520 if (lss) {
521 const int step = 24 * 60 * 60;
522 lss->time_for_row(tc->get_top()) | [lss, tc](auto first_time) {
523 lss->find_from_time(roundup_size(first_time.tv_sec, step)) | [tc](auto line) {
524 tc->set_top(line);
525 };
526 };
527 }
528 break;
529
530 case ')':
531 if (lss) {
532 lss->time_for_row(tc->get_top()) | [lss, tc](auto first_time) {
533 time_t day = rounddown(first_time.tv_sec, 24 * 60 * 60);
534 lss->find_from_time(day) | [tc](auto line) {
535 if (line != 0_vl) {
536 --line;
537 }
538 tc->set_top(line);
539 };
540 };
541 }
542 break;
543
544 case 'D':
545 if (tc->get_top() == 0) {
546 alerter::singleton().chime();
547 }
548 else if (lss) {
549 lss->time_for_row(tc->get_top()) | [lss, ch, tc](auto first_time) {
550 int step = ch == 'D' ? (24 * 60 * 60) : (60 * 60);
551 time_t top_time = first_time.tv_sec;
552 lss->find_from_time(top_time - step) | [tc](auto line) {
553 if (line != 0_vl) {
554 --line;
555 }
556 tc->set_top(line);
557 };
558 };
559
560 lnav_data.ld_rl_view->set_alt_value(HELP_MSG_1(/, "to search"));
561 }
562 break;
563
564 case 'd':
565 if (lss) {
566 lss->time_for_row(tc->get_top()) | [ch, lss, tc](auto first_time) {
567 int step = ch == 'd' ? (24 * 60 * 60) : (60 * 60);
568 lss->find_from_time(first_time.tv_sec + step) | [tc](auto line) {
569 tc->set_top(line);
570 };
571 };
572
573 lnav_data.ld_rl_view->set_alt_value(HELP_MSG_1(/, "to search"));
574 }
575 break;
576
577 case 'o':
578 case 'O':
579 if (lss) {
580 logline_helper start_helper(*lss);
581
582 start_helper.lh_current_line = tc->get_top();
583 logline &start_line = start_helper.move_to_msg_start();
584 start_helper.annotate();
585
586 struct line_range opid_range = find_string_attr_range(
587 start_helper.lh_string_attrs, &logline::L_OPID);
588 if (!opid_range.is_valid()) {
589 alerter::singleton().chime();
590 lnav_data.ld_rl_view->set_value(
591 err_prefix("Log message does not contain an opid"));
592 } else {
593 unsigned int opid_hash = start_line.get_opid();
594 logline_helper next_helper(*lss);
595 bool found = false;
596
597 next_helper.lh_current_line = start_helper.lh_current_line;
598
599 while (true) {
600 if (ch == 'o') {
601 if (++next_helper.lh_current_line >= tc->get_inner_height()) {
602 break;
603 }
604 }
605 else {
606 if (--next_helper.lh_current_line <= 0) {
607 break;
608 }
609 }
610 logline &next_line = next_helper.current_line();
611 if (!next_line.is_message()) {
612 continue;
613 }
614 if (next_line.get_opid() != opid_hash) {
615 continue;
616 }
617 next_helper.annotate();
618 struct line_range opid_next_range = find_string_attr_range(
619 next_helper.lh_string_attrs, &logline::L_OPID);
620 const char *start_opid = start_helper.lh_msg_buffer.get_data_at(opid_range.lr_start);
621 const char *next_opid = next_helper.lh_msg_buffer.get_data_at(opid_next_range.lr_start);
622 if (opid_range.length() != opid_next_range.length() ||
623 memcmp(start_opid, next_opid, opid_range.length()) != 0) {
624 continue;
625 }
626 found = true;
627 break;
628 }
629 if (found) {
630 lnav_data.ld_rl_view->set_value("");
631 tc->set_top(next_helper.lh_current_line);
632 }
633 else {
634 string opid_str = start_helper.to_string(opid_range);
635
636 lnav_data.ld_rl_view->set_value(
637 err_prefix("No more messages found with opid: " + opid_str));
638 alerter::singleton().chime();
639 }
640 }
641 }
642 break;
643
644 case 'p':
645 if (tc == &lnav_data.ld_views[LNV_LOG]) {
646 field_overlay_source *fos;
647
648 fos = (field_overlay_source *) tc->get_overlay_source();
649 fos->fos_contexts.top().c_show = !fos->fos_contexts.top().c_show;
650 tc->reload_data();
651 }
652 else if (tc == &lnav_data.ld_views[LNV_DB]) {
653 db_overlay_source *dos = (db_overlay_source *) tc->get_overlay_source();
654
655 dos->dos_active = !dos->dos_active;
656 tc->reload_data();
657 }
658 break;
659
660 case 't':
661 if (lnav_data.ld_text_source.current_file() == nullptr) {
662 alerter::singleton().chime();
663 lnav_data.ld_rl_view->set_value(
664 err_prefix("No text files loaded"));
665 }
666 else if (toggle_view(&lnav_data.ld_views[LNV_TEXT])) {
667 lnav_data.ld_rl_view->set_alt_value(HELP_MSG_2(
668 f, F,
669 "to switch to the next/previous file"));
670 }
671 break;
672
673 case 'T':
674 lnav_data.ld_log_source.toggle_time_offset();
675 if (lss && lss->is_time_offset_enabled()) {
676 lnav_data.ld_rl_view->set_alt_value(
677 HELP_MSG_2(s, S, "to move forward/backward through slow downs"));
678 }
679 tc->reload_data();
680 break;
681
682 case 'I':
683 {
684 auto &hist_tc = lnav_data.ld_views[LNV_HISTOGRAM];
685
686 if (toggle_view(&hist_tc)) {
687 auto *src_view = dynamic_cast<text_time_translator *>(tc->get_sub_source());
688
689 if (src_view != nullptr) {
690 src_view->time_for_row(tc->get_top()) | [](auto log_top) {
691 lnav_data.ld_hist_source2.row_for_time(log_top) | [](auto row) {
692 lnav_data.ld_views[LNV_HISTOGRAM].set_top(row);
693 };
694 };
695 }
696 }
697 else {
698 lnav_data.ld_view_stack.top() | [&](auto top_tc) {
699 auto *dst_view = dynamic_cast<text_time_translator *>(top_tc->get_sub_source());
700
701 if (dst_view != nullptr) {
702 auto& hs = lnav_data.ld_hist_source2;
703 auto hist_top_time_opt = hs.time_for_row(hist_tc.get_top());
704 auto curr_top_time_opt = dst_view->time_for_row(top_tc->get_top());
705 if (hist_top_time_opt && curr_top_time_opt &&
706 hs.row_for_time(hist_top_time_opt.value()) !=
707 hs.row_for_time(curr_top_time_opt.value())) {
708 dst_view->row_for_time(hist_top_time_opt.value()) | [top_tc](auto new_top) {
709 top_tc->set_top(new_top);
710 top_tc->set_needs_update();
711 };
712 }
713 }
714 };
715 }
716 }
717 break;
718
719 case 'V':
720 {
721 textview_curses *db_tc = &lnav_data.ld_views[LNV_DB];
722 db_label_source &dls = lnav_data.ld_db_row_source;
723
724 if (toggle_view(db_tc)) {
725 long log_line_index = dls.column_name_to_index("log_line");
726
727 if (log_line_index == -1) {
728 log_line_index = dls.column_name_to_index("min(log_line)");
729 }
730
731 if (log_line_index != -1) {
732 char linestr[64];
733 int line_number = (int)tc->get_top();
734 unsigned int row;
735
736 snprintf(linestr, sizeof(linestr), "%d", line_number);
737 for (row = 0; row < dls.dls_rows.size(); row++) {
738 if (strcmp(dls.dls_rows[row][log_line_index],
739 linestr) == 0) {
740 vis_line_t db_line(row);
741
742 db_tc->set_top(db_line);
743 db_tc->set_needs_update();
744 break;
745 }
746 }
747 }
748 }
749 else if (db_tc->get_inner_height() > 0) {
750 int db_row = db_tc->get_top();
751 tc = &lnav_data.ld_views[LNV_LOG];
752 long log_line_index = dls.column_name_to_index("log_line");
753
754 if (log_line_index == -1) {
755 log_line_index = dls.column_name_to_index("min(log_line)");
756 }
757
758 if (log_line_index != -1) {
759 unsigned int line_number;
760
761 if (sscanf(dls.dls_rows[db_row][log_line_index],
762 "%d",
763 &line_number) &&
764 line_number < tc->listview_rows(*tc)) {
765 tc->set_top(vis_line_t(line_number));
766 tc->set_needs_update();
767 }
768 }
769 else {
770 for (size_t lpc = 0; lpc < dls.dls_headers.size(); lpc++) {
771 date_time_scanner dts;
772 struct timeval tv;
773 struct exttm tm;
774 const char *col_value = dls.dls_rows[db_row][lpc];
775 size_t col_len = strlen(col_value);
776
777 if (dts.scan(col_value, col_len, nullptr, &tm, tv) != nullptr) {
778 lnav_data.ld_log_source.find_from_time(tv) | [tc](auto vl) {
779 tc->set_top(vl);
780 tc->set_needs_update();
781 };
782 break;
783 }
784 }
785 }
786 }
787 }
788 break;
789
790 case '\t':
791 case KEY_BTAB:
792 if (tc == &lnav_data.ld_views[LNV_DB])
793 {
794 auto &chart = lnav_data.ld_db_row_source.dls_chart;
795 const auto &state = chart.show_next_ident(
796 ch == '\t' ?
797 stacked_bar_chart_base::direction::forward :
798 stacked_bar_chart_base::direction::backward);
799
800 state.match(
801 [&] (stacked_bar_chart_base::show_none) {
802 lnav_data.ld_rl_view->set_value("Graphing no values");
803 },
804 [&] (stacked_bar_chart_base::show_all) {
805 lnav_data.ld_rl_view->set_value("Graphing all values");
806 },
807 [&] (stacked_bar_chart_base::show_one) {
808 string colname;
809
810 chart.get_ident_to_show(colname);
811 lnav_data.ld_rl_view->set_value(
812 "Graphing column " ANSI_BOLD_START +
813 colname + ANSI_NORM);
814 }
815 );
816
817 tc->reload_data();
818 } else if (tc_tss != nullptr && tc_tss->tss_supports_filtering) {
819 lnav_data.ld_mode = lnav_data.ld_last_config_mode;
820 lnav_data.ld_filter_view.reload_data();
821 lnav_data.ld_files_view.reload_data();
822 if (tc->get_inner_height() > 0_vl) {
823 string_attrs_t::const_iterator line_attr;
824 std::vector<attr_line_t> rows(1);
825
826 tc->get_data_source()->
827 listview_value_for_rows(*tc, tc->get_top(), rows);
828 string_attrs_t &sa = rows[0].get_attrs();
829 line_attr = find_string_attr(sa, &logline::L_FILE);
830 if (line_attr != sa.end()) {
831 const auto &fc = lnav_data.ld_active_files;
832 auto lf = ((logfile *)line_attr->sa_value.sav_ptr)->
833 shared_from_this();
834 auto iter = find(fc.fc_files.begin(),
835 fc.fc_files.end(),
836 lf);
837 if (iter != fc.fc_files.end()) {
838 auto index = distance(fc.fc_files.begin(), iter);
839 auto index_vl = vis_line_t(index);
840
841 lnav_data.ld_files_view.set_top(index_vl);
842 lnav_data.ld_files_view.set_selection(index_vl);
843 }
844 }
845 }
846 } else {
847 alerter::singleton().chime();
848 }
849 break;
850
851 case 'x':
852 if (tc->toggle_hide_fields()) {
853 lnav_data.ld_rl_view->set_value("Showing hidden fields");
854 } else {
855 lnav_data.ld_rl_view->set_value("Hiding hidden fields");
856 }
857 tc->set_needs_update();
858 break;
859
860 case 'r':
861 case 'R':
862 if (lss) {
863 auto& last_time =
864 injector::get<const relative_time&, last_relative_time_tag>();
865
866 if (last_time.empty()) {
867 lnav_data.ld_rl_view->set_value(
868 "Use the 'goto' command to set the relative time to move by");
869 }
870 else {
871 vis_line_t vl = tc->get_top(), new_vl;
872 relative_time rt = last_time;
873 content_line_t cl;
874 struct exttm tm;
875 bool done = false;
876
877 if (ch == 'r') {
878 if (rt.is_negative()) {
879 rt.negate();
880 }
881 } else if (ch == 'R') {
882 if (!rt.is_negative()) {
883 rt.negate();
884 }
885 }
886
887 cl = lnav_data.ld_log_source.at(vl);
888 logline *ll = lnav_data.ld_log_source.find_line(cl);
889 ll->to_exttm(tm);
890 do {
891 tm = rt.adjust(tm);
892 auto new_vl_opt = lnav_data.ld_log_source.find_from_time(tm);
893 if (!new_vl_opt) {
894 break;
895 }
896 new_vl = new_vl_opt.value();
897
898 if (new_vl == 0_vl || new_vl != vl || !rt.is_relative()) {
899 vl = new_vl;
900 done = true;
901 }
902 } while (!done);
903 tc->set_top(vl);
904 lnav_data.ld_rl_view->set_value(" " + rt.to_string());
905 }
906 }
907 break;
908
909 case KEY_CTRL_W:
910 execute_command(ec, lnav_data.ld_views[LNV_LOG].get_word_wrap() ?
911 "disable-word-wrap" : "enable-word-wrap");
912 break;
913
914 case KEY_CTRL_P:
915 lnav_data.ld_preview_hidden = !lnav_data.ld_preview_hidden;
916 break;
917
918 default:
919 return false;
920 }
921 return true;
922 }
923