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/humanize.network.hh"
34 #include "lnav.hh"
35 #include "lnav_util.hh"
36 #include "lnav_config.hh"
37 #include "sysclip.hh"
38 #include "vtab_module.hh"
39 #include "plain_text_source.hh"
40 #include "command_executor.hh"
41 #include "readline_curses.hh"
42 #include "readline_highlighters.hh"
43 #include "log_format_loader.hh"
44 #include "help_text_formatter.hh"
45 #include "sqlite-extension-func.hh"
46 #include "field_overlay_source.hh"
47 #include "yajlpp/yajlpp.hh"
48 #include "tailer/tailer.looper.hh"
49 #include "service_tags.hh"
50
51 using namespace std;
52
53 #define ABORT_MSG "(Press " ANSI_BOLD("CTRL+]") " to abort)"
54
55 #define STR_HELPER(x) #x
56 #define STR(x) STR_HELPER(x)
57
58 #define ANSI_RE(msg) ANSI_CSI "1;3" STR(COLOR_CYAN) "m" msg ANSI_NORM
59 #define ANSI_CLS(msg) ANSI_CSI "1;3" STR(COLOR_MAGENTA) "m" msg ANSI_NORM
60 #define ANSI_KW(msg) ANSI_CSI "3" STR(COLOR_BLUE) "m" msg ANSI_NORM
61 #define ANSI_REV(msg) ANSI_CSI "7m" msg ANSI_NORM
62 #define ANSI_STR(msg) ANSI_CSI "32m" msg ANSI_NORM
63
64 const char *RE_HELP =
65 " " ANSI_RE(".") " Any character "
66 " " "a" ANSI_RE("|") "b a or b "
67 " " ANSI_RE("(?-i)") " Case-sensitive search\n"
68
69 " " ANSI_CLS("\\w") " Word character "
70 " " "a" ANSI_RE("?") " 0 or 1 a's "
71 " " ANSI_RE("$") " End of string\n"
72
73 " " ANSI_CLS("\\d") " Digit "
74 " " "a" ANSI_RE("*") " 0 or more a's "
75 " " ANSI_RE("(") "..." ANSI_RE(")") " Capture\n"
76
77 " " ANSI_CLS("\\s") " White space "
78 " " "a" ANSI_RE("+") " 1 or more a's "
79 " " ANSI_RE("^") " Start of string\n"
80
81 " " ANSI_RE("\\") " Escape character "
82 " " ANSI_RE("[^") "ab" ANSI_RE("]") " " ANSI_BOLD("Not") " a or b "
83 " " ANSI_RE("[") "ab" ANSI_RE("-") "d" ANSI_RE("]") " Any of a, b, c, or d"
84 ;
85
86 const char *RE_EXAMPLE =
87 ANSI_UNDERLINE("Examples") "\n"
88 " abc" ANSI_RE("*") " matches "
89 ANSI_STR("'ab'") ", " ANSI_STR("'abc'") ", " ANSI_STR("'abccc'") "\n"
90
91 " key=" ANSI_RE("(\\w+)")
92 " matches key=" ANSI_REV("123") ", key=" ANSI_REV("abc") " and captures 123 and abc\n"
93
94 " " ANSI_RE("\\") "[abc" ANSI_RE("\\") "] matches " ANSI_STR("'[abc]'") "\n"
95
96 " " ANSI_RE("(?-i)") "ABC matches " ANSI_STR("'ABC'") " and " ANSI_UNDERLINE("not") " " ANSI_STR("'abc'")
97 ;
98
99 const char *SQL_HELP =
100 " " ANSI_KW("SELECT") " Select rows from a table "
101 " " ANSI_KW("DELETE") " Delete rows from a table\n"
102 " " ANSI_KW("INSERT") " Insert rows into a table "
103 " " ANSI_KW("UPDATE") " Update rows in a table\n"
104 " " ANSI_KW("CREATE") " Create a table/index "
105 " " ANSI_KW("DROP") " Drop a table/index\n"
106 " " ANSI_KW("ATTACH") " Attach a SQLite database file "
107 " " ANSI_KW("DETACH") " Detach a SQLite database"
108 ;
109
110 const char *SQL_EXAMPLE =
111 ANSI_UNDERLINE("Examples") "\n"
112 " SELECT * FROM %s WHERE log_level >= 'warning' LIMIT 10\n"
113 " UPDATE %s SET log_mark = 1 WHERE log_line = log_top_line()\n"
114 " SELECT * FROM logline LIMIT 10"
115 ;
116
117 static const char *LNAV_CMD_PROMPT = "Enter an lnav command: " ABORT_MSG;
118
rl_set_help()119 void rl_set_help()
120 {
121 switch (lnav_data.ld_mode) {
122 case LNM_SEARCH: {
123 lnav_data.ld_doc_source.replace_with(RE_HELP);
124 lnav_data.ld_example_source.replace_with(RE_EXAMPLE);
125 break;
126 }
127 case LNM_SQL: {
128 textview_curses &log_view = lnav_data.ld_views[LNV_LOG];
129 auto lss = (logfile_sub_source *) log_view.get_sub_source();
130 char example_txt[1024];
131 attr_line_t example_al;
132
133 if (log_view.get_inner_height() > 0) {
134 auto cl = lss->at(log_view.get_top());
135 auto lf = lss->find(cl);
136 auto format_name = lf->get_format()->get_name().get();
137
138 snprintf(example_txt, sizeof(example_txt),
139 SQL_EXAMPLE,
140 format_name,
141 format_name);
142 example_al.with_ansi_string(example_txt);
143 readline_sqlite_highlighter(example_al, 0);
144 }
145
146 lnav_data.ld_doc_source.replace_with(SQL_HELP);
147 lnav_data.ld_example_source.replace_with(example_al);
148 break;
149 }
150 default:
151 break;
152 }
153 }
154
155 static
rl_sql_help(readline_curses * rc)156 bool rl_sql_help(readline_curses *rc)
157 {
158 attr_line_t al(rc->get_line_buffer());
159 const string_attrs_t &sa = al.get_attrs();
160 size_t x = rc->get_x();
161 bool has_doc = false;
162
163 if (x > 0) {
164 x -= 1;
165 }
166
167 annotate_sql_statement(al);
168
169 auto avail_help = find_sql_help_for_line(al, x);
170
171 if (!avail_help.empty()) {
172 size_t help_count = avail_help.size();
173 textview_curses &dtc = lnav_data.ld_doc_view;
174 textview_curses &etc = lnav_data.ld_example_view;
175 unsigned long doc_width, ex_width;
176 vis_line_t doc_height, ex_height;
177 attr_line_t doc_al, ex_al;
178
179 dtc.get_dimensions(doc_height, doc_width);
180 etc.get_dimensions(ex_height, ex_width);
181
182 for (const auto& ht : avail_help) {
183 format_help_text_for_term(*ht, min(70UL, doc_width), doc_al,
184 help_count > 1);
185 if (help_count == 1) {
186 format_example_text_for_term(*ht,
187 eval_example,
188 min(70UL, ex_width), ex_al);
189 }
190 }
191
192 if (!doc_al.empty()) {
193 lnav_data.ld_doc_source.replace_with(doc_al);
194 dtc.reload_data();
195
196 lnav_data.ld_example_source.replace_with(ex_al);
197 etc.reload_data();
198
199 has_doc = true;
200 }
201 }
202
203 auto ident_iter = find_string_attr_containing(sa, &SQL_IDENTIFIER_ATTR, al.nearest_text(x));
204 if (ident_iter != sa.end()) {
205 auto ident = al.get_substring(ident_iter->sa_range);
206 auto intern_ident = intern_string::lookup(ident);
207 auto vtab = lnav_data.ld_vtab_manager->lookup_impl(intern_ident);
208 auto vtab_module_iter = vtab_module_ddls.find(intern_ident);
209 string ddl;
210
211 if (vtab != nullptr) {
212 ddl = trim(vtab->get_table_statement());
213 } else if (vtab_module_iter != vtab_module_ddls.end()) {
214 ddl = vtab_module_iter->second;
215 } else {
216 auto table_ddl_iter = lnav_data.ld_table_ddl.find(ident);
217
218 if (table_ddl_iter != lnav_data.ld_table_ddl.end()) {
219 ddl = table_ddl_iter->second;
220 }
221 }
222
223 if (!ddl.empty()) {
224 lnav_data.ld_preview_source.replace_with(ddl)
225 .set_text_format(text_format_t::TF_SQL)
226 .truncate_to(30);
227 lnav_data.ld_preview_status_source.get_description()
228 .set_value("Definition for table -- %s",
229 ident.c_str());
230 }
231 }
232
233 return has_doc;
234 }
235
rl_change(readline_curses * rc)236 void rl_change(readline_curses *rc)
237 {
238 static const std::set<std::string> COMMANDS_WITH_SQL = {
239 "filter-expr",
240 "mark-expr",
241 };
242
243 textview_curses *tc = get_textview_for_mode(lnav_data.ld_mode);
244
245 tc->get_highlights().erase({highlight_source_t::PREVIEW, "preview"});
246 tc->get_highlights().erase({highlight_source_t::PREVIEW, "bodypreview"});
247 lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
248 lnav_data.ld_preview_source.clear();
249 lnav_data.ld_preview_status_source.get_description()
250 .set_cylon(false)
251 .clear();
252
253 switch (lnav_data.ld_mode) {
254 case LNM_COMMAND: {
255 static string last_command;
256 static int generation = 0;
257
258 string line = rc->get_line_buffer();
259 vector<string> args;
260 auto iter = lnav_commands.end();
261
262 split_ws(line, args);
263
264 if (args.empty()) {
265 generation = 0;
266 } else if (args[0] != last_command) {
267 last_command = args[0];
268 generation = 0;
269 } else {
270 generation += 1;
271 }
272
273 auto os = tc->get_overlay_source();
274 if (!args.empty() && os != nullptr) {
275 auto fos = dynamic_cast<field_overlay_source *>(os);
276
277 if (fos != nullptr) {
278 if (generation == 0) {
279 auto& top_ctx = fos->fos_contexts.top();
280
281 if (COMMANDS_WITH_SQL.count(args[0]) > 0) {
282 top_ctx.c_prefix = ":";
283 top_ctx.c_show = true;
284 top_ctx.c_show_discovered = false;
285 } else {
286 top_ctx.c_prefix = ":";
287 top_ctx.c_show = false;
288 }
289 }
290 }
291 }
292
293 if (!args.empty()) {
294 iter = lnav_commands.find(args[0]);
295 }
296 if (iter == lnav_commands.end()) {
297 lnav_data.ld_doc_source.clear();
298 lnav_data.ld_example_source.clear();
299 lnav_data.ld_preview_source.clear();
300 lnav_data.ld_preview_status_source.get_description()
301 .set_cylon(false)
302 .clear();
303 lnav_data.ld_bottom_source.set_prompt(LNAV_CMD_PROMPT);
304 lnav_data.ld_bottom_source.grep_error("");
305 }
306 else if (args[0] == "config" && args.size() > 1) {
307 yajlpp_parse_context ypc("input", &lnav_config_handlers);
308
309 ypc.set_path(args[1])
310 .with_obj(lnav_config);
311 ypc.update_callbacks();
312
313 if (ypc.ypc_current_handler != nullptr) {
314 const json_path_handler_base *jph = ypc.ypc_current_handler;
315 char help_text[1024];
316
317 snprintf(help_text, sizeof(help_text),
318 ANSI_BOLD("%s %s") " -- %s " ABORT_MSG,
319 jph->jph_property.c_str(),
320 jph->jph_synopsis,
321 jph->jph_description);
322 lnav_data.ld_bottom_source.set_prompt(help_text);
323 lnav_data.ld_bottom_source.grep_error("");
324 }
325 else {
326 lnav_data.ld_bottom_source.grep_error(
327 "Unknown configuration option: " + args[1]);
328 }
329 }
330 else if ((args[0] != "filter-expr" && args[0] != "mark-expr") ||
331 !rl_sql_help(rc)) {
332 readline_context::command_t &cmd = *iter->second;
333 const help_text &ht = cmd.c_help;
334
335 if (ht.ht_name) {
336 textview_curses &dtc = lnav_data.ld_doc_view;
337 textview_curses &etc = lnav_data.ld_example_view;
338 unsigned long width;
339 vis_line_t height;
340 attr_line_t al;
341
342 dtc.get_dimensions(height, width);
343 format_help_text_for_term(ht, min(70UL, width), al);
344 lnav_data.ld_doc_source.replace_with(al);
345 dtc.set_needs_update();
346
347 al.clear();
348 etc.get_dimensions(height, width);
349 format_example_text_for_term(ht, eval_example, width, al);
350 lnav_data.ld_example_source.replace_with(al);
351 etc.set_needs_update();
352 }
353
354 if (cmd.c_prompt != nullptr && generation == 0 &&
355 trim(line) == args[0]) {
356 string new_prompt = cmd.c_prompt(
357 lnav_data.ld_exec_context, line);
358
359 if (!new_prompt.empty()) {
360 rc->rewrite_line(line.length(), new_prompt);
361 }
362 }
363
364 lnav_data.ld_bottom_source.grep_error("");
365 lnav_data.ld_status[LNS_BOTTOM].window_change();
366 }
367 break;
368 }
369 case LNM_EXEC: {
370 string line = rc->get_line_buffer();
371 size_t name_end = line.find(' ');
372 string script_name = line.substr(0, name_end);
373 auto& scripts = injector::get<available_scripts&>();
374 auto iter = scripts.as_scripts.find(script_name);
375
376 if (iter == scripts.as_scripts.end() ||
377 iter->second[0].sm_description.empty()) {
378 lnav_data.ld_bottom_source.set_prompt(
379 "Enter a script to execute: " ABORT_MSG);
380 }
381 else {
382 struct script_metadata &meta = iter->second[0];
383 char help_text[1024];
384
385 snprintf(help_text, sizeof(help_text),
386 ANSI_BOLD("%s") " -- %s " ABORT_MSG,
387 meta.sm_synopsis.c_str(),
388 meta.sm_description.c_str());
389 lnav_data.ld_bottom_source.set_prompt(help_text);
390 }
391 break;
392 }
393 default:
394 break;
395 }
396 }
397
rl_search_internal(readline_curses * rc,ln_mode_t mode,bool complete=false)398 static void rl_search_internal(readline_curses *rc, ln_mode_t mode, bool complete = false)
399 {
400 textview_curses *tc = get_textview_for_mode(mode);
401 string term_val;
402 string name;
403
404 tc->get_highlights().erase({highlight_source_t::PREVIEW, "preview"});
405 tc->get_highlights().erase({highlight_source_t::PREVIEW, "bodypreview"});
406 lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
407 tc->reload_data();
408
409 switch (mode) {
410 case LNM_SEARCH:
411 case LNM_SEARCH_FILTERS:
412 case LNM_SEARCH_FILES:
413 name = "$search";
414 break;
415
416 case LNM_CAPTURE:
417 require(0);
418 name = "$capture";
419 break;
420
421 case LNM_COMMAND: {
422 lnav_data.ld_exec_context.ec_dry_run = true;
423
424 lnav_data.ld_preview_generation += 1;
425 lnav_data.ld_preview_status_source.get_description()
426 .set_cylon(false)
427 .clear();
428 lnav_data.ld_preview_source.clear();
429 auto result = execute_command(lnav_data.ld_exec_context, rc->get_value());
430
431 if (result.isOk()) {
432 auto msg = result.unwrap();
433
434 if (msg.empty()) {
435 lnav_data.ld_bottom_source.set_prompt(LNAV_CMD_PROMPT);
436 lnav_data.ld_bottom_source.grep_error("");
437 } else {
438 lnav_data.ld_bottom_source.set_prompt(msg);
439 lnav_data.ld_bottom_source.grep_error("");
440 }
441 } else {
442 lnav_data.ld_bottom_source.set_prompt("");
443 lnav_data.ld_bottom_source.grep_error(result.unwrapErr());
444 }
445
446 lnav_data.ld_preview_view.reload_data();
447
448 lnav_data.ld_exec_context.ec_dry_run = false;
449 return;
450 }
451
452 case LNM_SQL: {
453 term_val = trim(rc->get_value() + ";");
454
455 if (!term_val.empty() && term_val[0] == '.') {
456 lnav_data.ld_bottom_source.grep_error("");
457 } else if (!sqlite3_complete(term_val.c_str())) {
458 lnav_data.ld_bottom_source.
459 grep_error("sql error: incomplete statement");
460 } else {
461 auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
462 int retcode;
463
464 retcode = sqlite3_prepare_v2(lnav_data.ld_db,
465 rc->get_value().c_str(),
466 -1,
467 stmt.out(),
468 nullptr);
469 if (retcode != SQLITE_OK) {
470 const char *errmsg = sqlite3_errmsg(lnav_data.ld_db);
471
472 lnav_data.ld_bottom_source.
473 grep_error(fmt::format("sql error: {}", errmsg));
474 } else {
475 lnav_data.ld_bottom_source.grep_error("");
476 }
477 }
478
479 if (!rl_sql_help(rc)) {
480 rl_set_help();
481 lnav_data.ld_preview_source.clear();
482 }
483 return;
484 }
485
486 case LNM_PAGING:
487 case LNM_FILTER:
488 case LNM_FILES:
489 case LNM_EXEC:
490 case LNM_USER:
491 return;
492 }
493
494 if (!complete) {
495 tc->set_top(lnav_data.ld_search_start_line);
496 }
497 tc->execute_search(rc->get_value());
498 }
499
rl_search(readline_curses * rc)500 void rl_search(readline_curses *rc)
501 {
502 textview_curses *tc = get_textview_for_mode(lnav_data.ld_mode);
503
504 rl_search_internal(rc, lnav_data.ld_mode);
505 tc->set_follow_search_for(0, {});
506 }
507
lnav_rl_abort(readline_curses * rc)508 void lnav_rl_abort(readline_curses *rc)
509 {
510 textview_curses *tc = get_textview_for_mode(lnav_data.ld_mode);
511
512 lnav_data.ld_bottom_source.set_prompt("");
513 lnav_data.ld_example_source.clear();
514 lnav_data.ld_doc_source.clear();
515 lnav_data.ld_preview_status_source.get_description()
516 .set_cylon(false)
517 .clear();
518 lnav_data.ld_preview_source.clear();
519 tc->get_highlights().erase({highlight_source_t::PREVIEW, "preview"});
520 tc->get_highlights().erase({highlight_source_t::PREVIEW, "bodypreview"});
521 lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
522
523 vector<string> errors;
524 lnav_config = rollback_lnav_config;
525 reload_config(errors);
526
527 lnav_data.ld_bottom_source.grep_error("");
528 switch (lnav_data.ld_mode) {
529 case LNM_SEARCH:
530 tc->set_top(lnav_data.ld_search_start_line);
531 tc->revert_search();
532 break;
533 case LNM_SQL:
534 tc->reload_data();
535 break;
536 default:
537 break;
538 }
539 lnav_data.ld_rl_view->set_value("");
540 lnav_data.ld_mode = LNM_PAGING;
541 }
542
rl_callback_int(readline_curses * rc,bool is_alt)543 static void rl_callback_int(readline_curses *rc, bool is_alt)
544 {
545 textview_curses *tc = get_textview_for_mode(lnav_data.ld_mode);
546 exec_context &ec = lnav_data.ld_exec_context;
547 string alt_msg;
548
549 lnav_data.ld_bottom_source.set_prompt("");
550 lnav_data.ld_doc_source.clear();
551 lnav_data.ld_example_source.clear();
552 lnav_data.ld_preview_status_source.get_description()
553 .set_cylon(false)
554 .clear();
555 lnav_data.ld_preview_source.clear();
556 tc->get_highlights().erase({highlight_source_t::PREVIEW, "preview"});
557 tc->get_highlights().erase({highlight_source_t::PREVIEW, "bodypreview"});
558 lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
559
560 auto new_mode = LNM_PAGING;
561
562 switch (lnav_data.ld_mode) {
563 case LNM_SEARCH_FILTERS:
564 new_mode = LNM_FILTER;
565 break;
566 case LNM_SEARCH_FILES:
567 new_mode = LNM_FILES;
568 break;
569 default:
570 break;
571 }
572
573 auto old_mode = std::exchange(lnav_data.ld_mode, new_mode);
574 switch (old_mode) {
575 case LNM_PAGING:
576 case LNM_FILTER:
577 case LNM_FILES:
578 require(0);
579 break;
580
581 case LNM_COMMAND:
582 rc->set_alt_value("");
583 rc->set_value(execute_command(ec, rc->get_value())
584 .map(ok_prefix)
585 .orElse(err_to_ok).unwrap());
586 break;
587
588 case LNM_USER:
589 rc->set_alt_value("");
590 ec.ec_local_vars.top()["value"] = rc->get_value();
591 rc->set_value("");
592 break;
593
594 case LNM_SEARCH:
595 case LNM_SEARCH_FILTERS:
596 case LNM_SEARCH_FILES:
597 case LNM_CAPTURE:
598 rl_search_internal(rc, old_mode, true);
599 if (!rc->get_value().empty()) {
600 auto_mem<FILE> pfile(pclose);
601 vis_bookmarks &bm = tc->get_bookmarks();
602 const auto &bv = bm[&textview_curses::BM_SEARCH];
603 vis_line_t vl = is_alt ? bv.prev(tc->get_top()) :
604 bv.next(tc->get_top());
605
606 pfile = sysclip::open(sysclip::type_t::FIND);
607 if (pfile.in() != nullptr) {
608 fprintf(pfile, "%s", rc->get_value().c_str());
609 }
610 if (vl != -1_vl) {
611 tc->set_top(vl);
612 } else {
613 tc->set_follow_search_for(2000, [tc, is_alt, &bm]() {
614 if (bm[&textview_curses::BM_SEARCH].empty()) {
615 return false;
616 }
617
618 if (is_alt && tc->is_searching()) {
619 return false;
620 }
621
622 vis_line_t first_hit;
623
624 if (is_alt) {
625 first_hit = bm[&textview_curses::BM_SEARCH].prev(
626 vis_line_t(tc->get_top()));
627 } else {
628 first_hit = bm[&textview_curses::BM_SEARCH].next(
629 vis_line_t(tc->get_top() - 1));
630 }
631 if (first_hit != -1) {
632 if (first_hit > 0) {
633 --first_hit;
634 }
635 tc->set_top(first_hit);
636 }
637
638 return true;
639 });
640 }
641 rc->set_value("search: " + rc->get_value());
642 rc->set_alt_value(HELP_MSG_2(
643 n, N,
644 "to move forward/backward through search results"));
645 }
646 break;
647
648 case LNM_SQL: {
649 auto result = execute_sql(ec, rc->get_value(), alt_msg);
650 db_label_source &dls = lnav_data.ld_db_row_source;
651 string prompt;
652
653 if (result.isOk()) {
654 auto msg = result.unwrap();
655
656 if (!msg.empty()) {
657 prompt = ok_prefix("SQL Result: " + msg);
658 if (dls.dls_rows.size() > 1) {
659 ensure_view(&lnav_data.ld_views[LNV_DB]);
660 }
661 }
662 } else {
663 prompt = result.orElse(err_to_ok).unwrap();
664 }
665
666 rc->set_value(prompt);
667 rc->set_alt_value(alt_msg);
668 break;
669 }
670
671 case LNM_EXEC: {
672 auto_mem<FILE> tmpout(fclose);
673
674 tmpout = std::tmpfile();
675
676 if (!tmpout) {
677 rc->set_value("Unable to open temporary output file: " +
678 string(strerror(errno)));
679 } else {
680 auto fd_copy = auto_fd::dup_of(fileno(tmpout));
681 char desc[256], timestamp[32];
682 time_t current_time = time(nullptr);
683 string path_and_args = rc->get_value();
684
685 {
686 exec_context::output_guard og(ec, "tmp", std::make_pair(tmpout.release(), fclose));
687
688 string result = execute_file(ec, path_and_args)
689 .map(ok_prefix)
690 .orElse(err_to_ok).unwrap();
691 string::size_type lf_index = result.find('\n');
692 if (lf_index != string::npos) {
693 result = result.substr(0, lf_index);
694 }
695 rc->set_value(result);
696 }
697
698 struct stat st;
699
700 if (fstat(fd_copy, &st) != -1 && st.st_size > 0) {
701 strftime(timestamp, sizeof(timestamp),
702 "%a %b %d %H:%M:%S %Z",
703 localtime(¤t_time));
704 snprintf(desc, sizeof(desc),
705 "Output of %s (%s)",
706 path_and_args.c_str(),
707 timestamp);
708 lnav_data.ld_active_files.fc_file_names[desc]
709 .with_fd(fd_copy)
710 .with_include_in_session(false)
711 .with_detect_format(false);
712 lnav_data.ld_files_to_front.emplace_back(desc, 0);
713
714 if (lnav_data.ld_rl_view != nullptr) {
715 lnav_data.ld_rl_view->set_alt_value(
716 HELP_MSG_1(X, "to close the file"));
717 }
718 }
719 }
720 break;
721 }
722 }
723 }
724
rl_callback(readline_curses * rc)725 void rl_callback(readline_curses *rc)
726 {
727 rl_callback_int(rc, false);
728 }
729
rl_alt_callback(readline_curses * rc)730 void rl_alt_callback(readline_curses *rc)
731 {
732 rl_callback_int(rc, true);
733 }
734
rl_display_matches(readline_curses * rc)735 void rl_display_matches(readline_curses *rc)
736 {
737 const auto &matches = rc->get_matches();
738 auto &tc = lnav_data.ld_match_view;
739 unsigned long width;
740 __attribute((unused))
741 unsigned long height;
742 int max_len, cols;
743
744 getmaxyx(lnav_data.ld_window, height, width);
745
746 max_len = rc->get_max_match_length() + 2;
747 cols = max(1UL, width / max_len);
748
749 if (matches.empty()) {
750 lnav_data.ld_match_source.clear();
751 }
752 else {
753 string current_match = rc->get_match_string();
754 int curr_col = 0;
755 attr_line_t al;
756 bool add_nl = false;
757
758 for (const auto &match : matches) {
759 if (add_nl) {
760 al.append(1, '\n');
761 add_nl = false;
762 }
763 if (match == current_match) {
764 al.append(match, &view_curses::VC_STYLE, A_REVERSE);
765 } else {
766 al.append(match);
767 }
768 curr_col += 1;
769 if (curr_col < cols) {
770 int padding = max_len - match.size();
771
772 al.append(padding, ' ');
773 } else {
774 curr_col = 0;
775 add_nl = true;
776 }
777 }
778 lnav_data.ld_match_source.replace_with(al);
779 }
780
781 tc.reload_data();
782 }
783
rl_display_next(readline_curses * rc)784 void rl_display_next(readline_curses *rc)
785 {
786 textview_curses &tc = lnav_data.ld_match_view;
787
788 if (tc.get_top() >= (tc.get_top_for_last_row() - 1)) {
789 tc.set_top(0_vl);
790 }
791 else {
792 tc.shift_top(tc.get_height());
793 }
794 }
795
rl_completion_request(readline_curses * rc)796 void rl_completion_request(readline_curses *rc)
797 {
798 isc::to<tailer::looper&, services::remote_tailer_t>()
799 .send([rc](auto &tlooper) {
800 auto rp_opt = humanize::network::path::from_str(
801 rc->get_remote_complete_path());
802 if (rp_opt) {
803 tlooper.complete_path(*rp_opt);
804 }
805 });
806 }
807