1 /**
2 * Copyright (c) 2018, 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 "lnav.hh"
33 #include "sql_util.hh"
34 #include "pretty_printer.hh"
35 #include "environ_vtab.hh"
36 #include "vtab_module.hh"
37 #include "shlex.hh"
38 #include "help-txt.h"
39 #include "view_helpers.hh"
40
41 using namespace std;
42
43 const char *lnav_view_strings[LNV__MAX + 1] = {
44 "log",
45 "text",
46 "help",
47 "histogram",
48 "db",
49 "schema",
50 "pretty",
51 "spectro",
52
53 nullptr
54 };
55
view_from_string(const char * name)56 nonstd::optional<lnav_view_t> view_from_string(const char *name)
57 {
58 if (name == nullptr) {
59 return nonstd::nullopt;
60 }
61
62 auto view_name_iter = find_if(
63 ::begin(lnav_view_strings), ::end(lnav_view_strings),
64 [&](const char *v) {
65 return v != nullptr && strcasecmp(v, name) == 0;
66 });
67
68 if (view_name_iter == ::end(lnav_view_strings)) {
69 return nonstd::nullopt;
70 }
71
72 return lnav_view_t(view_name_iter - lnav_view_strings);
73 }
74
open_schema_view()75 static void open_schema_view()
76 {
77 textview_curses *schema_tc = &lnav_data.ld_views[LNV_SCHEMA];
78 string schema;
79
80 dump_sqlite_schema(lnav_data.ld_db, schema);
81
82 schema += "\n\n-- Virtual Table Definitions --\n\n";
83 schema += ENVIRON_CREATE_STMT;
84 schema += vtab_module_schemas;
85 for (const auto &vtab_iter : *lnav_data.ld_vtab_manager) {
86 schema += "\n" + vtab_iter.second->get_table_statement();
87 }
88
89 delete schema_tc->get_sub_source();
90
91 auto *pts = new plain_text_source(schema);
92 pts->set_text_format(text_format_t::TF_SQL);
93
94 schema_tc->set_sub_source(pts);
95 schema_tc->redo_search();
96 }
97
open_pretty_view()98 static void open_pretty_view()
99 {
100 static const char *NOTHING_MSG =
101 "Nothing to pretty-print";
102
103 textview_curses *top_tc = *lnav_data.ld_view_stack.top();
104 textview_curses *pretty_tc = &lnav_data.ld_views[LNV_PRETTY];
105 textview_curses *log_tc = &lnav_data.ld_views[LNV_LOG];
106 textview_curses *text_tc = &lnav_data.ld_views[LNV_TEXT];
107 attr_line_t full_text;
108
109 delete pretty_tc->get_sub_source();
110 pretty_tc->set_sub_source(nullptr);
111 if (top_tc->get_inner_height() == 0) {
112 pretty_tc->set_sub_source(new plain_text_source(NOTHING_MSG));
113 return;
114 }
115
116 if (top_tc == log_tc) {
117 logfile_sub_source &lss = lnav_data.ld_log_source;
118 bool first_line = true;
119
120 for (vis_line_t vl = log_tc->get_top(); vl <= log_tc->get_bottom(); ++vl) {
121 content_line_t cl = lss.at(vl);
122 shared_ptr<logfile> lf = lss.find(cl);
123 auto ll = lf->begin() + cl;
124 shared_buffer_ref sbr;
125
126 if (!first_line && !ll->is_message()) {
127 continue;
128 }
129 auto ll_start = lf->message_start(ll);
130 attr_line_t al;
131
132 vl -= vis_line_t(distance(ll_start, ll));
133 lss.text_value_for_line(*log_tc, vl, al.get_string(),
134 text_sub_source::RF_FULL|
135 text_sub_source::RF_REWRITE);
136 lss.text_attrs_for_line(*log_tc, vl, al.get_attrs());
137 if (log_tc->get_hide_fields()) {
138 al.apply_hide();
139 }
140
141 line_range orig_lr = find_string_attr_range(
142 al.get_attrs(), &SA_ORIGINAL_LINE);
143 attr_line_t orig_al = al.subline(orig_lr.lr_start, orig_lr.length());
144 attr_line_t prefix_al = al.subline(0, orig_lr.lr_start);
145
146 data_scanner ds(orig_al.get_string());
147 pretty_printer pp(&ds, orig_al.get_attrs());
148 attr_line_t pretty_al;
149 vector<attr_line_t> pretty_lines;
150
151 // TODO: dump more details of the line in the output.
152 pp.append_to(pretty_al);
153 pretty_al.split_lines(pretty_lines);
154
155 for (auto &pretty_line : pretty_lines) {
156 if (pretty_line.empty() && &pretty_line == &pretty_lines.back()) {
157 break;
158 }
159 pretty_line.insert(0, prefix_al);
160 pretty_line.append("\n");
161 full_text.append(pretty_line);
162 }
163
164 first_line = false;
165 }
166
167 if (!full_text.empty()) {
168 full_text.erase(full_text.length() - 1, 1);
169 }
170 }
171 else if (top_tc == text_tc) {
172 shared_ptr<logfile> lf = lnav_data.ld_text_source.current_file();
173
174 for (vis_line_t vl = text_tc->get_top(); vl <= text_tc->get_bottom(); ++vl) {
175 auto ll = lf->begin() + vl;
176 shared_buffer_ref sbr;
177
178 lf->read_full_message(ll, sbr);
179 data_scanner ds(sbr);
180 string_attrs_t sa;
181 pretty_printer pp(&ds, sa);
182
183 pp.append_to(full_text);
184 }
185 }
186 auto *pts = new plain_text_source();
187 pts->replace_with(full_text);
188 pretty_tc->set_sub_source(pts);
189 if (lnav_data.ld_last_pretty_print_top != log_tc->get_top()) {
190 pretty_tc->set_top(vis_line_t(0));
191 }
192 lnav_data.ld_last_pretty_print_top = log_tc->get_top();
193 pretty_tc->redo_search();
194 }
195
build_all_help_text()196 static void build_all_help_text()
197 {
198 if (!lnav_data.ld_help_source.empty()) {
199 return;
200 }
201
202 attr_line_t all_help_text;
203 shlex lexer(help_txt.to_string_fragment());
204 string sub_help_text;
205
206 lexer.with_ignore_quotes(true)
207 .eval(sub_help_text, lnav_data.ld_exec_context.ec_global_vars);
208 all_help_text.with_ansi_string(sub_help_text);
209
210 map<string, help_text *> sql_funcs;
211 map<string, help_text *> sql_keywords;
212
213 for (const auto &iter : sqlite_function_help) {
214 switch (iter.second->ht_context) {
215 case help_context_t::HC_SQL_FUNCTION:
216 case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION:
217 sql_funcs[iter.second->ht_name] = iter.second;
218 break;
219 case help_context_t::HC_SQL_KEYWORD:
220 sql_keywords[iter.second->ht_name] = iter.second;
221 break;
222 default:
223 break;
224 }
225 }
226
227 for (const auto &iter : sql_funcs) {
228 all_help_text.append(2, '\n');
229 format_help_text_for_term(*iter.second, 79, all_help_text);
230 if (!iter.second->ht_example.empty()) {
231 all_help_text.append(1, '\n');
232 format_example_text_for_term(*iter.second, eval_example, 90, all_help_text);
233 }
234 }
235
236 for (const auto &iter : sql_keywords) {
237 all_help_text.append(2, '\n');
238 format_help_text_for_term(*iter.second, 79, all_help_text);
239 if (!iter.second->ht_example.empty()) {
240 all_help_text.append(1, '\n');
241 format_example_text_for_term(*iter.second, eval_example, 79, all_help_text);
242 }
243 }
244
245 lnav_data.ld_help_source.replace_with(all_help_text);
246 lnav_data.ld_views[LNV_HELP].redo_search();
247 }
248
layout_views()249 void layout_views()
250 {
251 unsigned long width, height;
252
253 getmaxyx(lnav_data.ld_window, height, width);
254 int doc_height;
255 bool doc_side_by_side = width > (90 + 60);
256 bool preview_status_open = !lnav_data.ld_preview_status_source
257 .get_description().empty();
258 bool filter_status_open = false;
259
260 lnav_data.ld_view_stack.top() | [&] (auto tc) {
261 text_sub_source *tss = tc->get_sub_source();
262
263 if (tss == nullptr) {
264 return;
265 }
266
267 if (tss->tss_supports_filtering) {
268 filter_status_open = true;
269 }
270 };
271
272 if (doc_side_by_side) {
273 doc_height = std::max(
274 lnav_data.ld_doc_source.text_line_count(),
275 lnav_data.ld_example_source.text_line_count());
276 } else {
277 doc_height =
278 lnav_data.ld_doc_source.text_line_count() +
279 lnav_data.ld_example_source.text_line_count();
280 }
281
282 int preview_height = lnav_data.ld_preview_hidden ? 0 :
283 lnav_data.ld_preview_source.text_line_count();
284 int match_rows = lnav_data.ld_match_source.text_line_count();
285 int match_height = min((unsigned long)match_rows, (height - 4) / 2);
286
287 lnav_data.ld_match_view.set_height(vis_line_t(match_height));
288
289 if (doc_height + 14 > ((int) height - match_height - preview_height - 2)) {
290 preview_height = 0;
291 preview_status_open = false;
292 }
293
294 if (doc_height + 14 > ((int) height - match_height - 2)) {
295 doc_height = lnav_data.ld_doc_source.text_line_count();
296 if (doc_height + 14 > ((int) height - match_height - 2)) {
297 doc_height = 0;
298 }
299 }
300
301 bool doc_open = doc_height > 0;
302 bool filters_open = (lnav_data.ld_mode == LNM_FILTER ||
303 lnav_data.ld_mode == LNM_FILES ||
304 lnav_data.ld_mode == LNM_SEARCH_FILTERS ||
305 lnav_data.ld_mode == LNM_SEARCH_FILES) &&
306 !preview_status_open &&
307 !doc_open;
308 int filter_height = filters_open ? 5 : 0;
309
310 int bottom_height =
311 (doc_open ? 1 : 0)
312 + doc_height
313 + (preview_status_open ? 1 : 0)
314 + preview_height
315 + 1 // bottom status
316 + match_height
317 + lnav_data.ld_rl_view->get_height();
318
319 for (auto &tc : lnav_data.ld_views) {
320 tc.set_height(vis_line_t(-(bottom_height
321 + (filter_status_open ? 1 : 0)
322 + (filters_open ? 1 : 0)
323 + filter_height)));
324 }
325 lnav_data.ld_status[LNS_TOP].set_enabled(!filters_open);
326 lnav_data.ld_status[LNS_FILTER].set_visible(filter_status_open);
327 lnav_data.ld_status[LNS_FILTER].set_enabled(filters_open);
328 lnav_data.ld_status[LNS_FILTER].set_top(
329 -(bottom_height + filter_height + 1 + (filters_open ? 1 : 0)));
330 lnav_data.ld_status[LNS_FILTER_HELP].set_visible(filters_open);
331 lnav_data.ld_status[LNS_FILTER_HELP].set_top(-(bottom_height + filter_height + 1));
332 lnav_data.ld_status[LNS_BOTTOM].set_top(-(match_height + 2));
333 lnav_data.ld_status[LNS_DOC].set_top(height - bottom_height);
334 lnav_data.ld_status[LNS_DOC].set_visible(doc_open);
335 lnav_data.ld_status[LNS_PREVIEW].set_top(height
336 - bottom_height
337 + (doc_open ? 1 : 0)
338 + doc_height);
339 lnav_data.ld_status[LNS_PREVIEW].set_visible(preview_status_open);
340
341 if (!doc_open || doc_side_by_side) {
342 lnav_data.ld_doc_view.set_height(vis_line_t(doc_height));
343 } else {
344 lnav_data.ld_doc_view.set_height(vis_line_t(lnav_data.ld_doc_source.text_line_count()));
345 }
346 lnav_data.ld_doc_view.set_y(height - bottom_height + 1);
347
348 if (!doc_open || doc_side_by_side) {
349 lnav_data.ld_example_view.set_height(vis_line_t(doc_height));
350 lnav_data.ld_example_view.set_x(doc_open ? 90 : 0);
351 lnav_data.ld_example_view.set_y(height - bottom_height + 1);
352 } else {
353 lnav_data.ld_example_view.set_height(vis_line_t(lnav_data.ld_example_source.text_line_count()));
354 lnav_data.ld_example_view.set_x(0);
355 lnav_data.ld_example_view.set_y(height - bottom_height + lnav_data.ld_doc_view.get_height() + 1);
356 }
357
358 lnav_data.ld_filter_view.set_height(vis_line_t(filter_height));
359 lnav_data.ld_filter_view.set_y(height - bottom_height - filter_height);
360 lnav_data.ld_filter_view.set_width(width);
361
362 lnav_data.ld_files_view.set_height(vis_line_t(filter_height));
363 lnav_data.ld_files_view.set_y(height - bottom_height - filter_height);
364 lnav_data.ld_files_view.set_width(width);
365
366 lnav_data.ld_preview_view.set_height(vis_line_t(preview_height));
367 lnav_data.ld_preview_view.set_y(height
368 - bottom_height
369 + 1
370 + (doc_open ? 1 : 0)
371 + doc_height);
372 lnav_data.ld_match_view.set_y(
373 height
374 - lnav_data.ld_rl_view->get_height()
375 - match_height);
376 lnav_data.ld_rl_view->set_width(width);
377 }
378
379 static unordered_map<string, attr_line_t> EXAMPLE_RESULTS;
380
execute_examples()381 void execute_examples()
382 {
383 db_label_source &dls = lnav_data.ld_db_row_source;
384 db_overlay_source &dos = lnav_data.ld_db_overlay;
385 textview_curses &db_tc = lnav_data.ld_views[LNV_DB];
386
387 for (auto &help_iter : sqlite_function_help) {
388 struct help_text &ht = *(help_iter.second);
389
390 for (auto &ex : ht.ht_example) {
391 string alt_msg;
392 attr_line_t result;
393
394 if (!ex.he_cmd) {
395 continue;
396 }
397
398 switch (ht.ht_context) {
399 case help_context_t::HC_SQL_KEYWORD:
400 case help_context_t::HC_SQL_INFIX:
401 case help_context_t::HC_SQL_FUNCTION:
402 case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION: {
403 exec_context ec;
404
405 execute_sql(ec, ex.he_cmd, alt_msg);
406
407 if (dls.dls_rows.size() == 1 &&
408 dls.dls_rows[0].size() == 1) {
409 result.append(dls.dls_rows[0][0]);
410 } else {
411 attr_line_t al;
412 dos.list_value_for_overlay(db_tc,
413 0, 1,
414 0_vl,
415 al);
416 result.append(al);
417 for (int lpc = 0;
418 lpc < (int)dls.text_line_count(); lpc++) {
419 al.clear();
420 dls.text_value_for_line(db_tc, lpc,
421 al.get_string(),
422 false);
423 dls.text_attrs_for_line(db_tc, lpc,
424 al.get_attrs());
425 std::replace(al.get_string().begin(),
426 al.get_string().end(),
427 '\n',
428 ' ');
429 result.append("\n")
430 .append(al);
431 }
432 }
433
434 EXAMPLE_RESULTS[ex.he_cmd] = result;
435
436 log_debug("example: %s", ex.he_cmd);
437 log_debug("example result: %s",
438 result.get_string().c_str());
439 break;
440 }
441 default:
442 log_warning("Not executing example: %s", ex.he_cmd);
443 break;
444 }
445 }
446 }
447
448 dls.clear();
449 }
450
eval_example(const help_text & ht,const help_example & ex)451 attr_line_t eval_example(const help_text &ht, const help_example &ex)
452 {
453 auto iter = EXAMPLE_RESULTS.find(ex.he_cmd);
454
455 if (iter != EXAMPLE_RESULTS.end()) {
456 return iter->second;
457 }
458
459 return "";
460 }
461
toggle_view(textview_curses * toggle_tc)462 bool toggle_view(textview_curses *toggle_tc)
463 {
464 textview_curses *tc = lnav_data.ld_view_stack.top().value_or(nullptr);
465 bool retval = false;
466
467 require(toggle_tc != NULL);
468 require(toggle_tc >= &lnav_data.ld_views[0]);
469 require(toggle_tc < &lnav_data.ld_views[LNV__MAX]);
470
471 if (tc == toggle_tc) {
472 if (lnav_data.ld_view_stack.size() == 1) {
473 return false;
474 }
475 lnav_data.ld_last_view = tc;
476 lnav_data.ld_view_stack.pop_back();
477 }
478 else {
479 if (toggle_tc == &lnav_data.ld_views[LNV_SCHEMA]) {
480 open_schema_view();
481 }
482 else if (toggle_tc == &lnav_data.ld_views[LNV_PRETTY]) {
483 open_pretty_view();
484 }
485 else if (toggle_tc == &lnav_data.ld_views[LNV_HISTOGRAM]) {
486 // Rebuild to reflect changes in marks.
487 rebuild_hist();
488 }
489 else if (toggle_tc == &lnav_data.ld_views[LNV_HELP]) {
490 build_all_help_text();
491 }
492 lnav_data.ld_last_view = nullptr;
493 lnav_data.ld_view_stack.push_back(toggle_tc);
494 retval = true;
495 }
496
497 return retval;
498 }
499
500 /**
501 * Ensure that the view is on the top of the view stack.
502 *
503 * @param expected_tc The text view that should be on top.
504 * @return True if the view was already on the top of the stack.
505 */
ensure_view(textview_curses * expected_tc)506 bool ensure_view(textview_curses *expected_tc)
507 {
508 textview_curses *tc = lnav_data.ld_view_stack.top().value_or(nullptr);
509 bool retval = true;
510
511 if (tc != expected_tc) {
512 toggle_view(expected_tc);
513 retval = false;
514 }
515 return retval;
516 }
517
next_cluster(vis_line_t (bookmark_vector<vis_line_t>::* f)(vis_line_t)const,bookmark_type_t * bt,const vis_line_t top)518 nonstd::optional<vis_line_t> next_cluster(
519 vis_line_t(bookmark_vector<vis_line_t>::*f) (vis_line_t) const,
520 bookmark_type_t *bt,
521 const vis_line_t top)
522 {
523 textview_curses *tc = get_textview_for_mode(lnav_data.ld_mode);
524 vis_bookmarks &bm = tc->get_bookmarks();
525 bookmark_vector<vis_line_t> &bv = bm[bt];
526 bool top_is_marked = binary_search(bv.begin(), bv.end(), top);
527 vis_line_t last_top(top), new_top(top), tc_height;
528 unsigned long tc_width;
529 int hit_count = 0;
530
531 tc->get_dimensions(tc_height, tc_width);
532
533 while ((new_top = (bv.*f)(new_top)) != -1) {
534 int diff = new_top - last_top;
535
536 hit_count += 1;
537 if (!top_is_marked || diff > 1) {
538 return new_top;
539 }
540 else if (hit_count > 1 && std::abs(new_top - top) >= tc_height) {
541 return vis_line_t(new_top - diff);
542 }
543 else if (diff < -1) {
544 last_top = new_top;
545 while ((new_top = (bv.*f)(new_top)) != -1) {
546 if ((std::abs(last_top - new_top) > 1) ||
547 (hit_count > 1 && (std::abs(top - new_top) >= tc_height))) {
548 break;
549 }
550 last_top = new_top;
551 }
552 return last_top;
553 }
554 last_top = new_top;
555 }
556
557 if (last_top != top) {
558 return last_top;
559 }
560
561 return nonstd::nullopt;
562 }
563
moveto_cluster(vis_line_t (bookmark_vector<vis_line_t>::* f)(vis_line_t)const,bookmark_type_t * bt,vis_line_t top)564 bool moveto_cluster(vis_line_t(bookmark_vector<vis_line_t>::*f) (vis_line_t) const,
565 bookmark_type_t *bt,
566 vis_line_t top)
567 {
568 textview_curses *tc = get_textview_for_mode(lnav_data.ld_mode);
569 auto new_top = next_cluster(f, bt, top);
570
571 if (!new_top) {
572 new_top = next_cluster(f, bt,
573 tc->is_selectable() ?
574 tc->get_selection() :
575 tc->get_top());
576 }
577 if (new_top != -1) {
578 tc->get_sub_source()->get_location_history() | [new_top] (auto lh) {
579 lh->loc_history_append(new_top.value());
580 };
581
582 if (tc->is_selectable()) {
583 tc->set_selection(new_top.value());
584 } else {
585 tc->set_top(new_top.value());
586 }
587 return true;
588 }
589
590 alerter::singleton().chime();
591
592 return false;
593 }
594
previous_cluster(bookmark_type_t * bt,textview_curses * tc)595 void previous_cluster(bookmark_type_t *bt, textview_curses *tc)
596 {
597 key_repeat_history &krh = lnav_data.ld_key_repeat_history;
598 vis_line_t height, initial_top;
599 unsigned long width;
600
601 if (tc->is_selectable()) {
602 initial_top = tc->get_selection();
603 } else {
604 initial_top = tc->get_top();
605 }
606 auto new_top = next_cluster(&bookmark_vector<vis_line_t>::prev,
607 bt,
608 initial_top);
609
610 tc->get_dimensions(height, width);
611 if (krh.krh_count > 1 &&
612 initial_top < (krh.krh_start_line - (1.5 * height)) &&
613 (!new_top || ((initial_top - new_top.value()) < height))) {
614 bookmark_vector<vis_line_t> &bv = tc->get_bookmarks()[bt];
615 new_top = bv.next(std::max(0_vl, initial_top - height));
616 }
617
618 if (new_top) {
619 tc->get_sub_source()->get_location_history() | [new_top] (auto lh) {
620 lh->loc_history_append(new_top.value());
621 };
622
623 if (tc->is_selectable()) {
624 tc->set_selection(new_top.value());
625 } else {
626 tc->set_top(new_top.value());
627 }
628 }
629 else {
630 alerter::singleton().chime();
631 }
632 }
633
search_forward_from(textview_curses * tc)634 vis_line_t search_forward_from(textview_curses *tc)
635 {
636 vis_line_t height, retval =
637 tc->is_selectable() ? tc->get_selection() : tc->get_top();
638 key_repeat_history &krh = lnav_data.ld_key_repeat_history;
639 unsigned long width;
640
641 tc->get_dimensions(height, width);
642
643 if (krh.krh_count > 1 &&
644 retval > (krh.krh_start_line + (1.5 * height))) {
645 retval += vis_line_t(0.90 * height);
646 }
647
648 return retval;
649 }
650