• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tstack / lnav / 22507085525-2793

27 Feb 2026 10:49PM UTC coverage: 68.948% (-0.02%) from 68.966%
22507085525-2793

push

github

tstack
fix mixup of O_CLOEXEC with FD_CLOEXEC

52007 of 75429 relevant lines covered (68.95%)

440500.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

62.22
/src/lnav_commands.cc
1
/**
2
 * Copyright (c) 2007-2022, 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
25
 * ON 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 <fstream>
31
#include <string>
32
#include <unordered_map>
33
#include <utility>
34
#include <vector>
35

36
#include "lnav.hh"
37

38
#include <fnmatch.h>
39
#include <sys/stat.h>
40
#include <termios.h>
41

42
#include "base/attr_line.builder.hh"
43
#include "base/auto_mem.hh"
44
#include "base/fs_util.hh"
45
#include "base/humanize.hh"
46
#include "base/humanize.network.hh"
47
#include "base/injector.hh"
48
#include "base/isc.hh"
49
#include "base/itertools.hh"
50
#include "base/paths.hh"
51
#include "base/relative_time.hh"
52
#include "base/string_util.hh"
53
#include "bound_tags.hh"
54
#include "breadcrumb_curses.hh"
55
#include "CLI/App.hpp"
56
#include "cmd.parser.hh"
57
#include "command_executor.hh"
58
#include "config.h"
59
#include "curl_looper.hh"
60
#include "date/tz.h"
61
#include "db_sub_source.hh"
62
#include "field_overlay_source.hh"
63
#include "hasher.hh"
64
#include "itertools.similar.hh"
65
#include "lnav.indexing.hh"
66
#include "lnav.prompt.hh"
67
#include "lnav_commands.hh"
68
#include "lnav_config.hh"
69
#include "lnav_util.hh"
70
#include "log.annotate.hh"
71
#include "log_data_helper.hh"
72
#include "log_data_table.hh"
73
#include "log_format_loader.hh"
74
#include "log_search_table.hh"
75
#include "log_search_table_fwd.hh"
76
#include "md4cpp.hh"
77
#include "ptimec.hh"
78
#include "readline_callbacks.hh"
79
#include "readline_highlighters.hh"
80
#include "scn/scan.h"
81
#include "service_tags.hh"
82
#include "session_data.hh"
83
#include "shlex.hh"
84
#include "spectro_impls.hh"
85
#include "sql_util.hh"
86
#include "sqlite-extension-func.hh"
87
#include "url_loader.hh"
88
#include "vtab_module.hh"
89
#include "yajl/api/yajl_parse.h"
90
#include "yajlpp/json_op.hh"
91
#include "yajlpp/yajlpp.hh"
92

93
#ifdef HAVE_RUST_DEPS
94
#    include "lnav_rs_ext.cxx.hh"
95
#endif
96

97
using namespace std::literals::chrono_literals;
98
using namespace lnav::roles::literals;
99

100
constexpr std::chrono::microseconds ZOOM_LEVELS[] = {
101
    1s,
102
    30s,
103
    60s,
104
    5min,
105
    15min,
106
    1h,
107
    4h,
108
    8h,
109
    24h,
110
    7 * 24h,
111
    30 * 24h,
112
    365 * 24h,
113
};
114

115
constexpr std::array<string_fragment, ZOOM_COUNT> lnav_zoom_strings = {
116
    "1-second"_frag,
117
    "30-second"_frag,
118
    "1-minute"_frag,
119
    "5-minute"_frag,
120
    "15-minute"_frag,
121
    "1-hour"_frag,
122
    "4-hour"_frag,
123
    "8-hour"_frag,
124
    "1-day"_frag,
125
    "1-week"_frag,
126
    "1-month"_frag,
127
    "1-year"_frag,
128
};
129

130
inline attr_line_t&
131
symbol_reducer(const std::string& elem, attr_line_t& accum)
5✔
132
{
133
    return accum.append("\n   ").append(lnav::roles::symbol(elem));
5✔
134
}
135

136
std::string
137
remaining_args(const std::string& cmdline,
375✔
138
               const std::vector<std::string>& args,
139
               size_t index)
140
{
141
    size_t start_pos = 0;
375✔
142

143
    require(index > 0);
375✔
144

145
    if (index >= args.size()) {
375✔
146
        return "";
2✔
147
    }
148
    for (size_t lpc = 0; lpc < index; lpc++) {
761✔
149
        start_pos += args[lpc].length();
387✔
150
    }
151

152
    size_t index_in_cmdline = cmdline.find(args[index], start_pos);
374✔
153

154
    require(index_in_cmdline != std::string::npos);
374✔
155

156
    auto retval = cmdline.substr(index_in_cmdline);
374✔
157
    while (!retval.empty() && retval.back() == ' ') {
376✔
158
        retval.pop_back();
2✔
159
    }
160

161
    return retval;
374✔
162
}
374✔
163

164
string_fragment
165
remaining_args_frag(const std::string& cmdline,
43✔
166
                    const std::vector<std::string>& args,
167
                    size_t index)
168
{
169
    size_t start_pos = 0;
43✔
170

171
    require(index > 0);
43✔
172

173
    if (index >= args.size()) {
43✔
174
        return string_fragment{};
×
175
    }
176
    for (size_t lpc = 0; lpc < index; lpc++) {
95✔
177
        start_pos += args[lpc].length();
52✔
178
    }
179

180
    size_t index_in_cmdline = cmdline.find(args[index], start_pos);
43✔
181

182
    require(index_in_cmdline != std::string::npos);
43✔
183

184
    return string_fragment::from_str_range(
43✔
185
        cmdline, index_in_cmdline, cmdline.size());
43✔
186
}
187

188
std::optional<std::string>
189
find_arg(std::vector<std::string>& args, const std::string& flag)
131✔
190
{
191
    auto iter = find_if(args.begin(), args.end(), [&flag](const auto elem) {
131✔
192
        return startswith(elem, flag);
130✔
193
    });
194

195
    if (iter == args.end()) {
131✔
196
        return std::nullopt;
131✔
197
    }
198

199
    auto index = iter->find('=');
×
200
    if (index == std::string::npos) {
×
201
        return "";
×
202
    }
203

204
    auto retval = iter->substr(index + 1);
×
205

206
    args.erase(iter);
×
207

208
    return retval;
×
209
}
210

211
bookmark_vector<vis_line_t>
212
combined_user_marks(vis_bookmarks& vb)
15✔
213
{
214
    const auto& bv = vb[&textview_curses::BM_USER];
15✔
215
    const auto& bv_expr = vb[&textview_curses::BM_USER_EXPR];
15✔
216
    bookmark_vector<vis_line_t> retval;
15✔
217

218
    for (const auto& row : bv.bv_tree) {
29✔
219
        retval.insert_once(row);
14✔
220
    }
221
    for (const auto& row : bv_expr.bv_tree) {
17✔
222
        retval.insert_once(row);
2✔
223
    }
224
    return retval;
15✔
225
}
×
226

227
static Result<std::string, lnav::console::user_message>
228
com_write_debug_log_to(exec_context& ec,
×
229
                       std::string cmdline,
230
                       std::vector<std::string>& args)
231
{
232
    if (args.size() < 2) {
×
233
        return ec.make_error("expecting a file path");
×
234
    }
235

236
    if (lnav_log_file.has_value()) {
×
237
        return ec.make_error("debug log is already being written to a file");
×
238
    }
239

240
    std::string retval;
×
241
    if (ec.ec_dry_run) {
×
242
        return Ok(retval);
×
243
    }
244

245
    auto fp = fopen(args[1].c_str(), "we");
×
246
    if (fp == nullptr) {
×
247
        auto um = lnav::console::user_message::error(
×
248
                      attr_line_t("unable to open file for write: ")
×
249
                          .append(lnav::roles::file(args[1])))
×
250
                      .with_errno_reason();
×
251
        return Err(um);
×
252
    }
253
    auto fd = fileno(fp);
×
254
    fcntl(fd, F_SETFD, FD_CLOEXEC);
×
255
    log_write_ring_to(fd);
×
256
    lnav_log_level = lnav_log_level_t::TRACE;
×
257
    lnav_log_file = fp;
×
258

259
    retval = fmt::format(FMT_STRING("info: wrote debug log to -- {}"), args[1]);
×
260

261
    return Ok(retval);
×
262
}
263

264
static Result<std::string, lnav::console::user_message>
265
com_add_src_path(exec_context& ec,
5✔
266
                 std::string cmdline,
267
                 std::vector<std::string>& args)
268
{
269
    static const intern_string_t SRC = intern_string::lookup("path");
15✔
270
    if (args.size() < 2) {
5✔
271
        return ec.make_error("expecting a path to source code");
1✔
272
    }
273

274
#if !defined(HAVE_RUST_DEPS)
275
    return ec.make_error("source paths are not supported in this build");
276
#else
277
    auto pat = trim(remaining_args(cmdline, args));
4✔
278
    std::string retval;
4✔
279

280
    shlex lexer(pat);
4✔
281
    auto split_args_res = lexer.split(ec.create_resolver());
4✔
282
    if (split_args_res.isErr()) {
4✔
283
        auto split_err = split_args_res.unwrapErr();
×
284
        auto um = lnav::console::user_message::error("unable to parse paths")
×
285
                      .with_reason(split_err.se_error.te_msg)
×
286
                      .with_snippet(lnav::console::snippet::from(
×
287
                          SRC, lexer.to_attr_line(split_err.se_error)))
×
288
                      .move();
×
289

290
        return Err(um);
×
291
    }
292

293
    auto split_args = split_args_res.unwrap()
8✔
294
        | lnav::itertools::map([](const auto& elem) { return elem.se_value; });
12✔
295

296
    for (const auto& path_str : split_args) {
7✔
297
        std::error_code err_co;
4✔
298
        auto path = std::filesystem::canonical(std::filesystem::path(path_str),
8✔
299
                                               err_co);
4✔
300
        if (err_co) {
4✔
301
            auto um = lnav::console::user_message::error(
×
302
                          attr_line_t("invalid path: ")
1✔
303
                              .append(lnav::roles::file(path_str)))
2✔
304
                          .with_reason(err_co.message());
1✔
305
            return Err(um);
1✔
306
        }
1✔
307

308
        if (ec.ec_dry_run) {
3✔
309
            continue;
×
310
        }
311
        auto res = lnav_rs_ext::add_src_root(path.string());
3✔
312
        if (res != nullptr) {
3✔
313
            auto um
314
                = lnav::console::user_message::error((std::string) res->error);
×
315

316
            return Err(um);
×
317
        }
318
    }
4✔
319
    if (!ec.ec_dry_run) {
3✔
320
        lnav_rs_ext::discover_srcs();
3✔
321
    }
322
    return Ok(retval);
3✔
323
#endif
324
}
4✔
325

326
static Result<std::string, lnav::console::user_message>
327
com_adjust_log_time(exec_context& ec,
4✔
328
                    std::string cmdline,
329
                    std::vector<std::string>& args)
330
{
331
    std::string retval;
4✔
332

333
    if (lnav_data.ld_views[LNV_LOG].get_inner_height() == 0) {
4✔
334
        return ec.make_error("no log messages");
×
335
    }
336
    if (args.size() >= 2) {
4✔
337
        auto& lss = lnav_data.ld_log_source;
4✔
338
        struct timeval top_time, time_diff;
339
        struct timeval new_time = {0, 0};
4✔
340
        content_line_t top_content;
4✔
341
        date_time_scanner dts;
4✔
342
        struct exttm tm;
4✔
343
        struct tm base_tm;
344

345
        auto top_line = lnav_data.ld_views[LNV_LOG].get_selection();
4✔
346
        top_content = lss.at(top_line.value_or(0_vl));
4✔
347
        auto lf = lss.find(top_content);
4✔
348

349
        auto& ll = (*lf)[top_content];
4✔
350

351
        top_time = ll.get_timeval();
4✔
352
        localtime_r(&top_time.tv_sec, &base_tm);
4✔
353

354
        dts.set_base_time(top_time.tv_sec, base_tm);
4✔
355
        args[1] = remaining_args(cmdline, args);
4✔
356

357
        auto parse_res = relative_time::from_str(args[1]);
4✔
358
        if (parse_res.isOk()) {
4✔
359
            new_time = parse_res.unwrap().adjust(top_time).to_timeval();
2✔
360
        } else if (dts.scan(
4✔
361
                       args[1].c_str(), args[1].size(), nullptr, &tm, new_time)
4✔
362
                   != nullptr)
2✔
363
        {
364
            // nothing to do
365
        } else {
366
            return ec.make_error("could not parse timestamp -- {}", args[1]);
×
367
        }
368

369
        timersub(&new_time, &top_time, &time_diff);
4✔
370
        if (ec.ec_dry_run) {
4✔
371
            char buffer[1024];
372

373
            snprintf(
×
374
                buffer,
375
                sizeof(buffer),
376
                "info: log timestamps will be adjusted by %ld.%06ld seconds",
377
                time_diff.tv_sec,
378
                (long) time_diff.tv_usec);
×
379

380
            retval = buffer;
×
381
        } else {
382
            lf->adjust_content_time(top_content, time_diff, false);
4✔
383

384
            lss.set_force_rebuild();
4✔
385

386
            retval = "info: adjusted time";
4✔
387
        }
388
    } else {
4✔
389
        return ec.make_error("expecting new time value");
×
390
    }
391

392
    return Ok(retval);
4✔
393
}
4✔
394

395
static Result<std::string, lnav::console::user_message>
396
com_clear_adjusted_log_time(exec_context& ec,
1✔
397
                            std::string cmdline,
398
                            std::vector<std::string>& args)
399
{
400
    if (lnav_data.ld_views[LNV_LOG].get_inner_height() == 0) {
1✔
401
        return ec.make_error("no log messages");
×
402
    }
403

404
    auto& lss = lnav_data.ld_log_source;
1✔
405
    auto sel_line = lnav_data.ld_views[LNV_LOG].get_selection();
1✔
406
    auto sel_pair = lss.find_line_with_file(sel_line);
1✔
407
    if (sel_pair) {
1✔
408
        auto lf = sel_pair->first;
1✔
409
        lf->clear_time_offset();
1✔
410
        lss.set_force_rebuild();
1✔
411
    }
1✔
412

413
    return Ok(std::string("info: cleared time offset"));
1✔
414
}
1✔
415

416
static Result<std::string, lnav::console::user_message>
417
com_unix_time(exec_context& ec,
3✔
418
              std::string cmdline,
419
              std::vector<std::string>& args)
420
{
421
    std::string retval;
3✔
422

423
    if (args.size() >= 2) {
3✔
424
        bool parsed = false;
2✔
425
        struct tm log_time;
426
        time_t u_time;
427
        size_t millis;
428
        char* rest;
429

430
        u_time = time(nullptr);
2✔
431
        if (localtime_r(&u_time, &log_time) == nullptr) {
2✔
432
            return ec.make_error(
433
                "invalid epoch time: {} -- {}", u_time, strerror(errno));
×
434
        }
435

436
        log_time.tm_isdst = -1;
2✔
437

438
        args[1] = remaining_args(cmdline, args);
2✔
439
        if ((millis = args[1].find('.')) != std::string::npos
2✔
440
            || (millis = args[1].find(',')) != std::string::npos)
2✔
441
        {
442
            args[1] = args[1].erase(millis, 4);
×
443
        }
444
        if (((rest = strptime(args[1].c_str(), "%b %d %H:%M:%S %Y", &log_time))
2✔
445
                 != nullptr
446
             && (rest - args[1].c_str()) >= 20)
×
447
            || ((rest
4✔
448
                 = strptime(args[1].c_str(), "%Y-%m-%d %H:%M:%S", &log_time))
2✔
449
                    != nullptr
450
                && (rest - args[1].c_str()) >= 19))
×
451
        {
452
            u_time = mktime(&log_time);
×
453
            parsed = true;
×
454
        } else if (sscanf(args[1].c_str(), "%ld", &u_time)) {
2✔
455
            if (localtime_r(&u_time, &log_time) == nullptr) {
1✔
456
                return ec.make_error(
457
                    "invalid epoch time: {} -- {}", args[1], strerror(errno));
×
458
            }
459

460
            parsed = true;
1✔
461
        }
462
        if (parsed) {
2✔
463
            char ftime[128];
464

465
            strftime(ftime,
1✔
466
                     sizeof(ftime),
467
                     "%a %b %d %H:%M:%S %Y  %z %Z",
468
                     localtime_r(&u_time, &log_time));
1✔
469
            retval = fmt::format(FMT_STRING("{} -- {}"), ftime, u_time);
4✔
470
        } else {
471
            return ec.make_error("invalid unix time -- {}", args[1]);
2✔
472
        }
473
    } else {
474
        return ec.make_error("expecting a unix time value");
1✔
475
    }
476

477
    return Ok(retval);
1✔
478
}
3✔
479

480
static Result<std::string, lnav::console::user_message>
481
com_set_file_timezone(exec_context& ec,
4✔
482
                      std::string cmdline,
483
                      std::vector<std::string>& args)
484
{
485
    static const intern_string_t SRC = intern_string::lookup("args");
12✔
486
    std::string retval;
4✔
487

488
    if (args.size() == 1) {
4✔
489
        return ec.make_error("expecting a timezone name");
×
490
    }
491

492
    auto* tc = *lnav_data.ld_view_stack.top();
4✔
493
    auto* lss = dynamic_cast<logfile_sub_source*>(tc->get_sub_source());
4✔
494

495
    if (lss != nullptr) {
4✔
496
        if (lss->text_line_count() == 0) {
4✔
497
            return ec.make_error("no log messages to examine");
×
498
        }
499

500
        auto line_pair = lss->find_line_with_file(tc->get_selection());
4✔
501
        if (!line_pair) {
4✔
502
            return ec.make_error(FMT_STRING("cannot find line"));
×
503
        }
504

505
        shlex lexer(cmdline);
4✔
506
        auto split_res = lexer.split(ec.create_resolver());
4✔
507
        if (split_res.isErr()) {
4✔
508
            auto split_err = split_res.unwrapErr();
×
509
            auto um = lnav::console::user_message::error(
×
510
                          "unable to parse arguments")
511
                          .with_reason(split_err.se_error.te_msg)
×
512
                          .with_snippet(lnav::console::snippet::from(
×
513
                              SRC, lexer.to_attr_line(split_err.se_error)))
×
514
                          .move();
×
515

516
            return Err(um);
×
517
        }
518

519
        auto split_args
520
            = split_res.unwrap() | lnav::itertools::map([](const auto& elem) {
8✔
521
                  return elem.se_value;
8✔
522
              });
4✔
523
        try {
524
            const auto* tz = date::locate_zone(split_args[1]);
4✔
525
            auto pattern = split_args.size() == 2
3✔
526
                ? line_pair->first->get_filename()
3✔
527
                : std::filesystem::path(split_args[2]);
3✔
528

529
            if (!ec.ec_dry_run) {
3✔
530
                static auto& safe_options_hier
531
                    = injector::get<lnav::safe_file_options_hier&>();
3✔
532

533
                safe::WriteAccess<lnav::safe_file_options_hier> options_hier(
534
                    safe_options_hier);
3✔
535

536
                options_hier->foh_generation += 1;
3✔
537
                auto& coll = options_hier->foh_path_to_collection["/"];
3✔
538

539
                log_info("setting timezone for %s to %s (%s)",
3✔
540
                         pattern.c_str(),
541
                         args[1].c_str(),
542
                         tz->name().c_str());
543
                coll.foc_pattern_to_options[pattern] = lnav::file_options{
3✔
544
                    {intern_string_t{}, source_location{}, tz},
3✔
545
                };
546

547
                auto opt_path = lnav::paths::dotlnav() / "file-options.json";
3✔
548
                auto coll_str = coll.to_json();
3✔
549
                lnav::filesystem::write_file(opt_path, coll_str);
3✔
550
            }
3✔
551
        } catch (const std::runtime_error& e) {
4✔
552
            attr_line_t note;
1✔
553

554
            try {
555
                note = (date::get_tzdb().zones
1✔
556
                        | lnav::itertools::map(&date::time_zone::name)
3✔
557
                        | lnav::itertools::similar_to(split_args[1])
4✔
558
                        | lnav::itertools::fold(symbol_reducer, attr_line_t{}))
4✔
559
                           .add_header("did you mean one of the following?");
1✔
560
            } catch (const std::runtime_error& e) {
×
561
                log_error("unable to get timezones: %s", e.what());
×
562
            }
×
563
            auto um = lnav::console::user_message::error(
×
564
                          attr_line_t()
1✔
565
                              .append_quoted(split_args[1])
2✔
566
                              .append(" is not a valid timezone"))
1✔
567
                          .with_reason(e.what())
2✔
568
                          .with_note(note)
1✔
569
                          .move();
1✔
570
            return Err(um);
1✔
571
        }
1✔
572
    } else {
6✔
573
        return ec.make_error(
574
            ":set-file-timezone is only supported for the LOG view");
×
575
    }
576

577
    return Ok(retval);
3✔
578
}
4✔
579

580
static readline_context::prompt_result_t
581
com_set_file_timezone_prompt(exec_context& ec, const std::string& cmdline)
×
582
{
583
    auto* tc = *lnav_data.ld_view_stack.top();
×
584
    auto* lss = dynamic_cast<logfile_sub_source*>(tc->get_sub_source());
×
585

586
    if (lss == nullptr || lss->text_line_count() == 0) {
×
587
        return {};
×
588
    }
589

590
    shlex lexer(cmdline);
×
591
    auto split_res = lexer.split(ec.create_resolver());
×
592
    if (split_res.isErr()) {
×
593
        return {};
×
594
    }
595

596
    auto line_pair = lss->find_line_with_file(tc->get_selection());
×
597
    if (!line_pair) {
×
598
        return {};
×
599
    }
600

601
    auto elems = split_res.unwrap();
×
602
    auto pattern_arg = line_pair->first->get_filename();
×
603
    if (elems.size() == 1) {
×
604
        try {
605
            static auto& safe_options_hier
606
                = injector::get<lnav::safe_file_options_hier&>();
×
607

608
            safe::ReadAccess<lnav::safe_file_options_hier> options_hier(
609
                safe_options_hier);
×
610
            auto file_zone = date::get_tzdb().current_zone()->name();
×
611
            auto match_res = options_hier->match(pattern_arg);
×
612
            if (match_res) {
×
613
                file_zone = match_res->second.fo_default_zone.pp_value->name();
×
614
                pattern_arg = lnav::filesystem::escape_path(
×
615
                    match_res->first, lnav::filesystem::path_type::pattern);
×
616

617
                auto new_prompt = fmt::format(FMT_STRING("{} {} {}"),
×
618
                                              trim(cmdline),
×
619
                                              file_zone,
620
                                              pattern_arg);
×
621

622
                return {new_prompt};
×
623
            }
624

625
            return {"", file_zone + " "};
×
626
        } catch (const std::runtime_error& e) {
×
627
            log_error("cannot get timezones: %s", e.what());
×
628
        }
×
629
    }
630
    auto arg_path = std::filesystem::path(pattern_arg);
×
631
    auto arg_parent = lnav::filesystem::escape_path(arg_path.parent_path());
×
632
    if (!endswith(arg_parent, "/")) {
×
633
        arg_parent += "/";
×
634
    }
635
    if (elems.size() == 2 && endswith(cmdline, " ")) {
×
636
        return {"", arg_parent};
×
637
    }
638
    if (elems.size() == 3 && elems.back().se_value == arg_parent) {
×
639
        return {"", arg_path.filename().string()};
×
640
    }
641

642
    return {};
×
643
}
644

645
static readline_context::prompt_result_t
646
com_clear_file_timezone_prompt(exec_context& ec, const std::string& cmdline)
×
647
{
648
    std::string retval;
×
649

650
    auto* tc = *lnav_data.ld_view_stack.top();
×
651
    auto* lss = dynamic_cast<logfile_sub_source*>(tc->get_sub_source());
×
652

653
    if (lss != nullptr && lss->text_line_count() > 0) {
×
654
        auto line_pair = lss->find_line_with_file(tc->get_selection());
×
655
        if (line_pair) {
×
656
            try {
657
                static auto& safe_options_hier
658
                    = injector::get<lnav::safe_file_options_hier&>();
×
659

660
                safe::ReadAccess<lnav::safe_file_options_hier> options_hier(
661
                    safe_options_hier);
×
662
                auto file_zone = date::get_tzdb().current_zone()->name();
×
663
                auto pattern_arg = line_pair->first->get_filename();
×
664
                auto match_res
665
                    = options_hier->match(line_pair->first->get_filename());
×
666
                if (match_res) {
×
667
                    file_zone
668
                        = match_res->second.fo_default_zone.pp_value->name();
×
669
                    pattern_arg = match_res->first;
×
670
                }
671

672
                retval = fmt::format(
×
673
                    FMT_STRING("{} {}"), trim(cmdline), pattern_arg);
×
674
            } catch (const std::runtime_error& e) {
×
675
                log_error("cannot get timezones: %s", e.what());
×
676
            }
×
677
        }
678
    }
679

680
    return {retval};
×
681
}
682

683
static Result<std::string, lnav::console::user_message>
684
com_clear_file_timezone(exec_context& ec,
×
685
                        std::string cmdline,
686
                        std::vector<std::string>& args)
687
{
688
    std::string retval;
×
689

690
    if (args.size() != 2) {
×
691
        return ec.make_error("expecting a single file path or pattern");
×
692
    }
693

694
    auto* tc = *lnav_data.ld_view_stack.top();
×
695
    auto* lss = dynamic_cast<logfile_sub_source*>(tc->get_sub_source());
×
696

697
    if (lss != nullptr) {
×
698
        if (!ec.ec_dry_run) {
×
699
            static auto& safe_options_hier
700
                = injector::get<lnav::safe_file_options_hier&>();
×
701

702
            safe::WriteAccess<lnav::safe_file_options_hier> options_hier(
703
                safe_options_hier);
×
704

705
            options_hier->foh_generation += 1;
×
706
            auto& coll = options_hier->foh_path_to_collection["/"];
×
707
            const auto iter = coll.foc_pattern_to_options.find(args[1]);
×
708

709
            if (iter == coll.foc_pattern_to_options.end()) {
×
710
                return ec.make_error(FMT_STRING("no timezone set for: {}"),
×
711
                                     args[1]);
×
712
            }
713

714
            log_info("clearing timezone for %s", args[1].c_str());
×
715
            iter->second.fo_default_zone.pp_value = nullptr;
×
716
            if (iter->second.empty()) {
×
717
                coll.foc_pattern_to_options.erase(iter);
×
718
            }
719

720
            auto opt_path = lnav::paths::dotlnav() / "file-options.json";
×
721
            auto coll_str = coll.to_json();
×
722
            lnav::filesystem::write_file(opt_path, coll_str);
×
723
        }
724
    } else {
725
        return ec.make_error(
726
            ":clear-file-timezone is only supported for the LOG view");
×
727
    }
728

729
    return Ok(retval);
×
730
}
731

732
static Result<std::string, lnav::console::user_message>
733
com_convert_time_to(exec_context& ec,
2✔
734
                    std::string cmdline,
735
                    std::vector<std::string>& args)
736
{
737
    std::string retval;
2✔
738

739
    if (args.size() == 1) {
2✔
740
        return ec.make_error("expecting a timezone name");
×
741
    }
742

743
    const auto* tc = *lnav_data.ld_view_stack.top();
2✔
744
    auto* lss = dynamic_cast<logfile_sub_source*>(tc->get_sub_source());
2✔
745

746
    if (lss != nullptr) {
2✔
747
        if (lss->text_line_count() == 0) {
2✔
748
            return ec.make_error("no log messages to examine");
×
749
        }
750
        auto sel = tc->get_selection();
2✔
751
        if (!sel) {
2✔
752
            return ec.make_error("no focused message");
×
753
        }
754

755
        const auto* ll = lss->find_line(lss->at(sel.value()));
2✔
756
        try {
757
            auto* dst_tz = date::locate_zone(args[1]);
2✔
758
            auto utime = date::local_time<std::chrono::seconds>{
759
                ll->get_time<std::chrono::seconds>()};
1✔
760
            auto cz_time = lnav::to_sys_time(utime);
1✔
761
            auto dz_time = date::make_zoned(dst_tz, cz_time);
1✔
762
            auto etime = std::chrono::duration_cast<std::chrono::microseconds>(
1✔
763
                dz_time.get_local_time().time_since_epoch());
1✔
764
            etime += ll->get_subsecond_time<std::chrono::milliseconds>();
1✔
765
            char ftime[128];
766
            sql_strftime(ftime, sizeof(ftime), etime, 'T');
1✔
767
            retval = ftime;
1✔
768

769
            off_t off = 0;
1✔
770
            exttm tm;
1✔
771
            tm.et_flags |= ETF_ZONE_SET;
1✔
772
            tm.et_gmtoff = dz_time.get_info().offset.count();
1✔
773
            ftime_Z(ftime, off, sizeof(ftime), tm);
1✔
774
            ftime[off] = '\0';
1✔
775
            retval.append(" ");
1✔
776
            retval.append(ftime);
1✔
777
        } catch (const std::runtime_error& e) {
1✔
778
            return ec.make_error(FMT_STRING("Unable to get timezone: {} -- {}"),
3✔
779
                                 args[1],
1✔
780
                                 e.what());
2✔
781
        }
1✔
782
    } else {
783
        return ec.make_error(
784
            ":convert-time-to is only supported for the LOG view");
×
785
    }
786

787
    return Ok(retval);
1✔
788
}
2✔
789

790
static Result<std::string, lnav::console::user_message>
791
com_current_time(exec_context& ec,
1✔
792
                 std::string cmdline,
793
                 std::vector<std::string>& args)
794
{
795
    char ftime[128];
796
    struct tm localtm;
797
    std::string retval;
1✔
798
    time_t u_time;
799

800
    memset(&localtm, 0, sizeof(localtm));
1✔
801
    u_time = time(nullptr);
1✔
802
    strftime(ftime,
1✔
803
             sizeof(ftime),
804
             "%a %b %d %H:%M:%S %Y  %z %Z",
805
             localtime_r(&u_time, &localtm));
1✔
806
    retval = fmt::format(FMT_STRING("{} -- {}"), ftime, u_time);
4✔
807

808
    return Ok(retval);
2✔
809
}
1✔
810

811
static Result<std::string, lnav::console::user_message>
812
com_goto(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
117✔
813
{
814
    static const char* INTERACTIVE_FMTS[] = {
815
        "%B %e %H:%M:%S",
816
        "%b %e %H:%M:%S",
817
        "%B %e %H:%M",
818
        "%b %e %H:%M",
819
        "%B %e %I:%M%p",
820
        "%b %e %I:%M%p",
821
        "%B %e %I%p",
822
        "%b %e %I%p",
823
        "%B %e",
824
        "%b %e",
825
        nullptr,
826
    };
827

828
    std::string retval;
117✔
829

830
    std::string all_args = trim(remaining_args(cmdline, args));
117✔
831
    auto* tc = *lnav_data.ld_view_stack.top();
117✔
832
    std::optional<vis_line_t> dst_vl;
117✔
833
    auto is_location = false;
117✔
834

835
    if (startswith(all_args, "#")) {
117✔
836
        auto* ta = dynamic_cast<text_anchors*>(tc->get_sub_source());
6✔
837

838
        if (ta == nullptr) {
6✔
839
            return ec.make_error("view does not support anchor links");
×
840
        }
841

842
        dst_vl = ta->row_for_anchor(all_args);
6✔
843
        if (!dst_vl) {
6✔
844
            return ec.make_error("unable to find anchor: {}", all_args);
1✔
845
        }
846
        is_location = true;
5✔
847
    }
848

849
    auto* ttt = dynamic_cast<text_time_translator*>(tc->get_sub_source());
116✔
850
    int line_number, consumed;
851
    date_time_scanner dts;
116✔
852
    const char* scan_end = nullptr;
116✔
853
    struct timeval tv;
854
    struct exttm tm;
116✔
855
    float value;
856
    auto parse_res = relative_time::from_str(all_args);
116✔
857

858
    if (ttt != nullptr && tc->get_inner_height() > 0_vl) {
116✔
859
        auto top_time_opt
860
            = ttt->time_for_row(tc->get_selection().value_or(0_vl));
110✔
861

862
        if (top_time_opt) {
110✔
863
            auto top_time_tv = top_time_opt.value().ri_time;
93✔
864
            struct tm top_tm;
865

866
            localtime_r(&top_time_tv.tv_sec, &top_tm);
93✔
867
            dts.set_base_time(top_time_tv.tv_sec, top_tm);
93✔
868
        }
869
    }
870

871
    if (all_args.empty() || dst_vl) {
116✔
872
    } else if (parse_res.isOk()) {
111✔
873
        if (ttt == nullptr) {
4✔
874
            return ec.make_error(
875
                "relative time values only work in a time-indexed view");
×
876
        }
877
        if (tc->get_inner_height() == 0_vl) {
4✔
878
            return ec.make_error("view is empty");
×
879
        }
880
        auto tv_opt = ttt->time_for_row(tc->get_selection().value_or(0_vl));
4✔
881
        if (!tv_opt) {
4✔
882
            return ec.make_error("cannot get time for the top row");
×
883
        }
884
        tv = tv_opt.value().ri_time;
4✔
885

886
        vis_line_t vl = tc->get_selection().value_or(0_vl), new_vl;
4✔
887
        bool done = false;
4✔
888
        auto rt = parse_res.unwrap();
4✔
889
        log_info("  goto relative time: %s", rt.to_string().c_str());
4✔
890

891
        if (rt.is_relative()) {
4✔
892
            injector::get<relative_time&, last_relative_time_tag>() = rt;
3✔
893
        }
894

895
        do {
896
            auto orig_tv = tv;
4✔
897
            auto tm = rt.adjust(tv);
4✔
898

899
            tv = tm.to_timeval();
4✔
900
            if (tv == orig_tv) {
4✔
901
                break;
1✔
902
            }
903
            auto new_vl_opt = ttt->row_for_time(tv);
3✔
904
            if (!new_vl_opt) {
3✔
905
                break;
×
906
            }
907

908
            new_vl = new_vl_opt.value();
3✔
909
            if (new_vl == 0_vl || new_vl != vl || !rt.is_relative()) {
3✔
910
                vl = new_vl;
3✔
911
                done = true;
3✔
912
            }
913
        } while (!done);
3✔
914

915
        dst_vl = vl;
4✔
916

917
#if 0
918
            if (!ec.ec_dry_run && !rt.is_absolute()
919
                && lnav_data.ld_rl_view != nullptr)
920
            {
921
                lnav_data.ld_rl_view->set_alt_value(HELP_MSG_2(
922
                    r, R, "to move forward/backward the same amount of time"));
923
            }
924
#endif
925
    } else if ((scan_end
4✔
926
                = dts.scan(all_args.c_str(), all_args.size(), nullptr, &tm, tv))
107✔
927
                   != nullptr
928
               || (scan_end = dts.scan(all_args.c_str(),
107✔
929
                                       all_args.size(),
930
                                       INTERACTIVE_FMTS,
931
                                       &tm,
932
                                       tv))
933
                   != nullptr)
934
    {
935
        if (ttt == nullptr) {
3✔
936
            return ec.make_error(
937
                "time values only work in a time-indexed view");
×
938
        }
939

940
        size_t matched_size = scan_end - all_args.c_str();
3✔
941
        if (matched_size != all_args.size()) {
3✔
942
            auto um = lnav::console::user_message::error(
×
943
                          attr_line_t("invalid timestamp: ").append(all_args))
4✔
944
                          .with_reason(
4✔
945
                              attr_line_t("the leading part of the timestamp "
4✔
946
                                          "was matched, however, the trailing "
947
                                          "text ")
948
                                  .append_quoted(scan_end)
2✔
949
                                  .append(" was not"))
2✔
950
                          .with_snippets(ec.ec_source)
4✔
951
                          .with_note(
4✔
952
                              attr_line_t("input matched time format ")
4✔
953
                                  .append_quoted(
2✔
954
                                      PTIMEC_FORMATS[dts.dts_fmt_lock].pf_fmt))
2✔
955
                          .with_help(
4✔
956
                              "fix the timestamp or remove the trailing text")
957
                          .move();
2✔
958

959
            auto unmatched_size = all_args.size() - matched_size;
2✔
960
            auto& snippet_copy = um.um_snippets.back();
2✔
961
            attr_line_builder alb(snippet_copy.s_content);
2✔
962

963
            alb.append("\n")
2✔
964
                .append(1 + cmdline.find(all_args), ' ')
2✔
965
                .append(matched_size, ' ');
2✔
966
            {
967
                auto attr_guard
968
                    = alb.with_attr(VC_ROLE.value(role_t::VCR_COMMENT));
2✔
969

970
                alb.append("^");
2✔
971
                if (unmatched_size > 1) {
2✔
972
                    if (unmatched_size > 2) {
1✔
973
                        alb.append(unmatched_size - 2, '-');
1✔
974
                    }
975
                    alb.append("^");
1✔
976
                }
977
                alb.append(" unrecognized input");
2✔
978
            }
2✔
979
            return Err(um);
2✔
980
        }
2✔
981

982
        if (!(tm.et_flags & ETF_DAY_SET)) {
1✔
983
            tm.et_tm.tm_yday = -1;
×
984
            tm.et_tm.tm_mday = 1;
×
985
        }
986
        if (!(tm.et_flags & ETF_HOUR_SET)) {
1✔
987
            tm.et_tm.tm_hour = 0;
×
988
        }
989
        if (!(tm.et_flags & ETF_MINUTE_SET)) {
1✔
990
            tm.et_tm.tm_min = 0;
×
991
        }
992
        if (!(tm.et_flags & ETF_SECOND_SET)) {
1✔
993
            tm.et_tm.tm_sec = 0;
×
994
        }
995
        if (!(tm.et_flags & ETF_MICROS_SET) && !(tm.et_flags & ETF_MILLIS_SET))
1✔
996
        {
997
            tm.et_nsec = 0;
1✔
998
        }
999
        tv = tm.to_timeval();
1✔
1000
        dst_vl = ttt->row_for_time(tv);
1✔
1001
    } else if (sscanf(args[1].c_str(), "%f%n", &value, &consumed) == 1) {
104✔
1002
        if (args[1][consumed] == '%') {
103✔
1003
            if (value < 0) {
1✔
1004
                return ec.make_error("negative percentages are not allowed");
1✔
1005
            }
1006
            line_number
1007
                = (int) ((double) tc->get_inner_height() * (value / 100.0));
×
1008
        } else {
1009
            line_number = (int) value;
102✔
1010
            if (line_number < 0) {
102✔
1011
                log_info("negative goto: %d height=%d",
6✔
1012
                         line_number,
1013
                         (int) tc->get_inner_height());
1014
                line_number = tc->get_inner_height() + line_number;
6✔
1015
                if (line_number < 0) {
6✔
1016
                    line_number = 0;
1✔
1017
                }
1018
            }
1019
        }
1020

1021
        dst_vl = vis_line_t(line_number);
102✔
1022
    } else {
1023
        auto um = lnav::console::user_message::error(
×
1024
                      attr_line_t("invalid argument: ").append(args[1]))
2✔
1025
                      .with_reason(
2✔
1026
                          "expecting line number/percentage, timestamp, or "
1027
                          "relative time")
1028
                      .move();
1✔
1029
        ec.add_error_context(um);
1✔
1030
        return Err(um);
1✔
1031
    }
1✔
1032

1033
    dst_vl | [&ec, tc, &retval, is_location](auto new_top) {
112✔
1034
        if (ec.ec_dry_run) {
112✔
1035
            retval = "info: will move to line " + std::to_string((int) new_top);
×
1036
        } else {
1037
            tc->get_sub_source()->get_location_history() |
112✔
1038
                [new_top](auto lh) { lh->loc_history_append(new_top); };
111✔
1039
            tc->set_selection(new_top);
112✔
1040
            if (tc->is_selectable() && is_location) {
112✔
1041
                tc->set_top(new_top - 2_vl, false);
×
1042
            }
1043

1044
            retval = "";
112✔
1045
        }
1046
    };
112✔
1047

1048
    return Ok(retval);
112✔
1049
}
117✔
1050

1051
static Result<std::string, lnav::console::user_message>
1052
com_relative_goto(exec_context& ec,
1✔
1053
                  std::string cmdline,
1054
                  std::vector<std::string>& args)
1055
{
1056
    std::string retval = "error: ";
1✔
1057

1058
    if (args.empty()) {
1✔
1059
    } else if (args.size() > 1) {
1✔
1060
        auto* tc = *lnav_data.ld_view_stack.top();
1✔
1061
        int line_offset, consumed;
1062
        float value;
1063

1064
        auto sel = tc->get_selection();
1✔
1065
        if (!sel) {
1✔
1066
            return ec.make_error("no focused message");
×
1067
        }
1068
        if (sscanf(args[1].c_str(), "%f%n", &value, &consumed) == 1) {
1✔
1069
            if (args[1][consumed] == '%') {
1✔
1070
                line_offset
1071
                    = (int) ((double) tc->get_inner_height() * (value / 100.0));
×
1072
            } else {
1073
                line_offset = (int) value;
1✔
1074
            }
1075

1076
            auto new_sel = sel.value() + vis_line_t(line_offset);
1✔
1077
            if (ec.ec_dry_run) {
1✔
1078
                retval = "info: shifting top by " + std::to_string(line_offset)
×
1079
                    + " lines";
×
1080
            } else if (new_sel < 0) {
1✔
1081
                retval = "";
×
1082
            } else {
1083
                tc->set_selection(new_sel);
1✔
1084

1085
                retval = "";
1✔
1086
            }
1087
        } else {
1088
            return ec.make_error("invalid line number -- {}", args[1]);
×
1089
        }
1090
    } else {
1091
        return ec.make_error("expecting line number/percentage");
×
1092
    }
1093

1094
    return Ok(retval);
1✔
1095
}
1✔
1096

1097
static Result<std::string, lnav::console::user_message>
1098
com_mark_expr(exec_context& ec,
7✔
1099
              std::string cmdline,
1100
              std::vector<std::string>& args)
1101
{
1102
    std::string retval;
7✔
1103

1104
    if (args.size() < 2) {
7✔
1105
        return ec.make_error("expecting an SQL expression");
1✔
1106
    }
1107
    if (*lnav_data.ld_view_stack.top() != &lnav_data.ld_views[LNV_LOG]) {
6✔
1108
        return ec.make_error(":mark-expr is only supported for the LOG view");
×
1109
    }
1110
    auto expr = remaining_args(cmdline, args);
6✔
1111
    auto stmt_str = fmt::format(FMT_STRING("SELECT 1 WHERE {}"), expr);
18✔
1112

1113
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
6✔
1114
#ifdef SQLITE_PREPARE_PERSISTENT
1115
    auto retcode = sqlite3_prepare_v3(lnav_data.ld_db.in(),
12✔
1116
                                      stmt_str.c_str(),
1117
                                      stmt_str.size(),
6✔
1118
                                      SQLITE_PREPARE_PERSISTENT,
1119
                                      stmt.out(),
1120
                                      nullptr);
1121
#else
1122
    auto retcode = sqlite3_prepare_v2(lnav_data.ld_db.in(),
1123
                                      stmt_str.c_str(),
1124
                                      stmt_str.size(),
1125
                                      stmt.out(),
1126
                                      nullptr);
1127
#endif
1128
    if (retcode != SQLITE_OK) {
6✔
1129
        const char* errmsg = sqlite3_errmsg(lnav_data.ld_db);
1✔
1130
        auto expr_al
1131
            = attr_line_t(expr)
2✔
1132
                  .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE))
2✔
1133
                  .move();
1✔
1134
        readline_sql_highlighter(
1✔
1135
            expr_al, lnav::sql::dialect::sqlite, std::nullopt);
1136
        auto um = lnav::console::user_message::error(
×
1137
                      attr_line_t("invalid mark expression: ").append(expr_al))
2✔
1138
                      .with_reason(errmsg)
2✔
1139
                      .with_snippets(ec.ec_source)
2✔
1140
                      .move();
1✔
1141

1142
        return Err(um);
1✔
1143
    }
1✔
1144

1145
    auto& lss = lnav_data.ld_log_source;
5✔
1146
    if (ec.ec_dry_run) {
5✔
1147
        auto set_res = lss.set_preview_sql_filter(stmt.release());
×
1148

1149
        if (set_res.isErr()) {
×
1150
            return Err(set_res.unwrapErr());
×
1151
        }
1152
        lnav_data.ld_preview_status_source[0].get_description().set_value(
×
1153
            "Matches are highlighted in the text view"_frag);
×
1154
        lnav_data.ld_status[LNS_PREVIEW0].set_needs_update();
×
1155
    } else {
×
1156
        auto set_res = lss.set_sql_marker(expr, stmt.release());
5✔
1157

1158
        if (set_res.isErr()) {
5✔
1159
            return Err(set_res.unwrapErr());
×
1160
        }
1161
        lnav_data.ld_views[LNV_LOG].reload_data();
5✔
1162
    }
5✔
1163

1164
    return Ok(retval);
5✔
1165
}
7✔
1166

1167
static readline_context::prompt_result_t
1168
com_mark_expr_prompt(exec_context& ec, const std::string& cmdline)
×
1169
{
1170
    textview_curses* tc = *lnav_data.ld_view_stack.top();
×
1171

1172
    if (tc != &lnav_data.ld_views[LNV_LOG]) {
×
1173
        return {""};
×
1174
    }
1175

1176
    return {
1177
        fmt::format(FMT_STRING("{} {}"),
×
1178
                    trim(cmdline),
×
1179
                    trim(lnav_data.ld_log_source.get_sql_marker_text())),
×
1180
    };
1181
}
1182

1183
static Result<std::string, lnav::console::user_message>
1184
com_clear_mark_expr(exec_context& ec,
1✔
1185
                    std::string cmdline,
1186
                    std::vector<std::string>& args)
1187
{
1188
    std::string retval;
1✔
1189

1190
    if (args.empty()) {
1✔
1191
    } else {
1192
        if (!ec.ec_dry_run) {
1✔
1193
            lnav_data.ld_log_source.set_sql_marker("", nullptr);
2✔
1194
        }
1195
    }
1196

1197
    return Ok(retval);
2✔
1198
}
1✔
1199

1200
static Result<std::string, lnav::console::user_message>
1201
com_goto_location(exec_context& ec,
3✔
1202
                  std::string cmdline,
1203
                  std::vector<std::string>& args)
1204
{
1205
    std::string retval;
3✔
1206

1207
    if (args.empty()) {
3✔
1208
    } else if (!ec.ec_dry_run) {
3✔
1209
        lnav_data.ld_view_stack.top() | [&args](auto tc) {
3✔
1210
            tc->get_sub_source()->get_location_history() |
3✔
1211
                [tc, &args](auto lh) {
3✔
1212
                    return args[0] == "prev-location"
3✔
1213
                        ? lh->loc_history_back(
6✔
1214
                              tc->get_selection().value_or(0_vl))
2✔
1215
                        : lh->loc_history_forward(
1216
                              tc->get_selection().value_or(0_vl));
4✔
1217
                }
1218
                | [tc](auto new_top) { tc->set_selection(new_top); };
9✔
1219
        };
3✔
1220
    }
1221

1222
    return Ok(retval);
6✔
1223
}
3✔
1224

1225
static Result<std::string, lnav::console::user_message>
1226
com_next_section(exec_context& ec,
6✔
1227
                 std::string cmdline,
1228
                 std::vector<std::string>& args)
1229
{
1230
    std::string retval;
6✔
1231

1232
    if (args.empty()) {
6✔
1233
    } else if (!ec.ec_dry_run) {
6✔
1234
        auto* tc = *lnav_data.ld_view_stack.top();
6✔
1235
        auto* ta = dynamic_cast<text_anchors*>(tc->get_sub_source());
6✔
1236

1237
        if (ta == nullptr) {
6✔
1238
            return ec.make_error("view does not support sections");
×
1239
        }
1240

1241
        auto old_sel = tc->get_selection().value_or(0_vl);
6✔
1242
        auto adj_opt
1243
            = ta->adjacent_anchor(old_sel, text_anchors::direction::next);
6✔
1244
        if (!adj_opt) {
6✔
1245
            return ec.make_error("no next section found");
×
1246
        }
1247

1248
        tc->get_sub_source()->get_location_history() |
6✔
1249
            [old_sel](auto lh) { lh->loc_history_append(old_sel); };
6✔
1250
        tc->set_selection(adj_opt.value());
6✔
1251
    }
1252

1253
    return Ok(retval);
6✔
1254
}
6✔
1255

1256
static Result<std::string, lnav::console::user_message>
1257
com_prev_section(exec_context& ec,
1✔
1258
                 std::string cmdline,
1259
                 std::vector<std::string>& args)
1260
{
1261
    std::string retval;
1✔
1262

1263
    if (args.empty()) {
1✔
1264
    } else if (!ec.ec_dry_run) {
1✔
1265
        auto* tc = *lnav_data.ld_view_stack.top();
1✔
1266
        auto* ta = dynamic_cast<text_anchors*>(tc->get_sub_source());
1✔
1267

1268
        if (ta == nullptr) {
1✔
1269
            return ec.make_error("view does not support sections");
×
1270
        }
1271

1272
        auto old_sel = tc->get_selection().value_or(0_vl);
1✔
1273
        auto adj_opt
1274
            = ta->adjacent_anchor(old_sel, text_anchors::direction::prev);
1✔
1275
        if (!adj_opt) {
1✔
1276
            return ec.make_error("no previous section found");
×
1277
        }
1278

1279
        tc->get_sub_source()->get_location_history() |
1✔
1280
            [old_sel](auto lh) { lh->loc_history_append(old_sel); };
1✔
1281
        tc->set_selection(adj_opt.value());
1✔
1282
    }
1283

1284
    return Ok(retval);
1✔
1285
}
1✔
1286

1287
static Result<std::string, lnav::console::user_message>
1288
com_highlight(exec_context& ec,
2✔
1289
              std::string cmdline,
1290
              std::vector<std::string>& args)
1291
{
1292
    std::string retval;
2✔
1293

1294
    if (args.size() > 1) {
2✔
1295
        const static intern_string_t PATTERN_SRC
1296
            = intern_string::lookup("pattern");
6✔
1297

1298
        auto* tc = *lnav_data.ld_view_stack.top();
2✔
1299
        auto& hm = tc->get_highlights();
2✔
1300
        auto re_frag = remaining_args_frag(cmdline, args);
2✔
1301
        args[1] = re_frag.to_string();
2✔
1302
        if (hm.find({highlight_source_t::INTERACTIVE, args[1]}) != hm.end()) {
2✔
1303
            return ec.make_error("highlight already exists -- {}", args[1]);
×
1304
        }
1305

1306
        auto compile_res = lnav::pcre2pp::code::from(args[1], PCRE2_CASELESS);
2✔
1307

1308
        if (compile_res.isErr()) {
2✔
1309
            auto ce = compile_res.unwrapErr();
×
1310
            auto um = lnav::console::to_user_message(PATTERN_SRC, ce);
×
1311
            return Err(um);
×
1312
        }
1313
        highlighter hl(compile_res.unwrap().to_shared());
2✔
1314
        auto hl_attrs = view_colors::singleton().attrs_for_ident(args[1]);
2✔
1315

1316
        if (ec.ec_dry_run) {
2✔
1317
            hl_attrs |= text_attrs::style::blink;
×
1318
        }
1319

1320
        hl.with_attrs(hl_attrs);
2✔
1321

1322
        if (ec.ec_dry_run) {
2✔
1323
            hm[{highlight_source_t::PREVIEW, "preview"}] = hl;
×
1324

1325
            lnav_data.ld_preview_status_source[0].get_description().set_value(
×
1326
                "Matches are highlighted in the view"_frag);
×
1327
            lnav_data.ld_status[LNS_PREVIEW0].set_needs_update();
×
1328

1329
            retval = "";
×
1330
        } else {
1331
            hm[{highlight_source_t::INTERACTIVE, args[1]}] = hl;
2✔
1332
            retval = "info: highlight pattern now active";
2✔
1333
        }
1334
        tc->reload_data();
2✔
1335
    } else {
2✔
1336
        return ec.make_error("expecting a regular expression to highlight");
×
1337
    }
1338

1339
    return Ok(retval);
2✔
1340
}
2✔
1341

1342
static Result<std::string, lnav::console::user_message>
1343
com_highlight_field(exec_context& ec,
9✔
1344
                    std::string cmdline,
1345
                    std::vector<std::string>& args)
1346
{
1347
    static auto& vc = view_colors::singleton();
9✔
1348

1349
    CLI::App app{"highlight-field"};
27✔
1350
    std::string retval;
9✔
1351
    std::string field_name;
9✔
1352
    std::string fg_color;
9✔
1353
    std::string bg_color;
9✔
1354
    bool style_bold = false;
9✔
1355
    bool style_underline = false;
9✔
1356
    bool style_strike = false;
9✔
1357
    bool style_blink = false;
9✔
1358
    bool style_italic = false;
9✔
1359

1360
    app.add_option("--color", fg_color);
36✔
1361
    app.add_option("--bg-color", bg_color);
36✔
1362
    app.add_flag("--bold", style_bold);
36✔
1363
    app.add_flag("--underline", style_underline);
36✔
1364
    app.add_flag("--strike", style_strike);
36✔
1365
    app.add_flag("--blink", style_blink);
36✔
1366
    app.add_flag("--italic", style_italic);
36✔
1367
    app.add_option("field", field_name);
27✔
1368

1369
    size_t name_index = 1;
9✔
1370
    for (; name_index < args.size(); name_index++) {
16✔
1371
        if (!startswith(args[name_index], "-")) {
16✔
1372
            break;
9✔
1373
        }
1374
    }
1375
    if (name_index >= args.size()) {
9✔
1376
        return ec.make_error("expecting field name");
×
1377
    }
1378

1379
    auto pat = name_index + 1 < args.size()
9✔
1380
        ? trim(remaining_args(cmdline, args, name_index + 1))
14✔
1381
        : std::string(".*");
20✔
1382

1383
    log_debug("pat %s", pat.c_str());
9✔
1384
    std::vector<std::string> cli_args(args.begin() + 1,
9✔
1385
                                      args.begin() + name_index + 1);
18✔
1386
    std::vector<lnav::console::user_message> errors;
9✔
1387

1388
    text_attrs attrs;
9✔
1389
    app.parse(cli_args);
9✔
1390
    if (!fg_color.empty()) {
9✔
1391
        attrs.ta_fg_color = vc.match_color(
1✔
1392
            styling::color_unit::from_str(fg_color).unwrapOrElse(
2✔
1393
                [&](const auto& msg) {
1✔
1394
                    errors.emplace_back(
1✔
1395
                        lnav::console::user_message::error(
1396
                            attr_line_t().append_quoted(fg_color).append(
2✔
1397
                                " is not a valid color value"))
1398
                            .with_reason(msg));
1399
                    return styling::color_unit::EMPTY;
1✔
1400
                }));
1401
    }
1402
    if (!bg_color.empty()) {
9✔
1403
        attrs.ta_bg_color = vc.match_color(
2✔
1404
            styling::color_unit::from_str(bg_color).unwrapOrElse(
4✔
1405
                [&](const auto& msg) {
×
1406
                    errors.emplace_back(
×
1407
                        lnav::console::user_message::error(
1408
                            attr_line_t().append_quoted(bg_color).append(
×
1409
                                " is not a valid background color value"))
1410
                            .with_reason(msg));
1411
                    return styling::color_unit::EMPTY;
×
1412
                }));
1413
    }
1414
    if (style_bold) {
9✔
1415
        attrs |= text_attrs::style::bold;
×
1416
    }
1417
    if (style_underline) {
9✔
1418
        attrs |= text_attrs::style::underline;
4✔
1419
    }
1420
    if (style_blink) {
9✔
1421
        attrs |= text_attrs::style::blink;
×
1422
    }
1423
    if (style_strike) {
9✔
1424
        attrs |= text_attrs::style::struck;
×
1425
    }
1426
    if (style_italic) {
9✔
1427
        attrs |= text_attrs::style::italic;
×
1428
    }
1429
    if (!errors.empty()) {
9✔
1430
        return Err(errors[0]);
1✔
1431
    }
1432
    if (field_name.empty()) {
8✔
1433
        return ec.make_error("field name cannot be empty");
×
1434
    }
1435
    if (attrs.empty()) {
8✔
1436
        attrs = vc.attrs_for_ident(pat);
2✔
1437
    }
1438

1439
    auto compile_res = lnav::pcre2pp::code::from(pat, PCRE2_CASELESS);
8✔
1440

1441
    if (compile_res.isErr()) {
8✔
1442
        const static intern_string_t PATTERN_SRC
1443
            = intern_string::lookup("pattern");
3✔
1444
        auto ce = compile_res.unwrapErr();
1✔
1445
        auto um = lnav::console::to_user_message(PATTERN_SRC, ce);
1✔
1446
        return Err(um);
1✔
1447
    }
1✔
1448

1449
    intern_string_t field_name_i = intern_string::lookup(field_name);
7✔
1450

1451
    auto re = compile_res.unwrap().to_shared();
7✔
1452
    lnav_data.ld_log_source.lss_highlighters.emplace_back(
7✔
1453
        highlighter(re)
14✔
1454
            .with_field(field_name_i)
7✔
1455
            .with_attrs(attrs)
7✔
1456
            .with_preview(ec.ec_dry_run));
7✔
1457

1458
    return Ok(retval);
7✔
1459
}
9✔
1460

1461
static Result<std::string, lnav::console::user_message>
1462
com_clear_highlight(exec_context& ec,
2✔
1463
                    std::string cmdline,
1464
                    std::vector<std::string>& args)
1465
{
1466
    std::string retval;
2✔
1467

1468
    if (args.size() > 1 && args[1][0] != '$') {
2✔
1469
        auto* tc = *lnav_data.ld_view_stack.top();
2✔
1470
        auto& hm = tc->get_highlights();
2✔
1471

1472
        args[1] = remaining_args(cmdline, args);
2✔
1473
        auto hm_iter = hm.find({highlight_source_t::INTERACTIVE, args[1]});
2✔
1474
        if (hm_iter == hm.end()) {
2✔
1475
            return ec.make_error("highlight does not exist -- {}", args[1]);
2✔
1476
        }
1477
        if (ec.ec_dry_run) {
1✔
1478
            retval = "";
×
1479
        } else {
1480
            hm.erase(hm_iter);
1✔
1481
            retval = "info: highlight pattern cleared";
1✔
1482
            tc->reload_data();
1✔
1483
        }
1484
    } else {
1485
        return ec.make_error("expecting highlight expression to clear");
×
1486
    }
1487

1488
    return Ok(retval);
1✔
1489
}
2✔
1490

1491
static Result<std::string, lnav::console::user_message>
1492
com_clear_highlight_field(exec_context& ec,
1✔
1493
                          std::string cmdline,
1494
                          std::vector<std::string>& args)
1495
{
1496
    std::string retval;
1✔
1497

1498
    if (args.size() > 1) {
1✔
1499
        auto& tc = lnav_data.ld_views[LNV_LOG];
1✔
1500
        auto& log_hlv = lnav_data.ld_log_source.lss_highlighters;
1✔
1501
        auto new_end
1502
            = std::remove_if(log_hlv.begin(), log_hlv.end(), [&](auto& kv) {
1✔
1503
                  return kv.h_field == args[1];
2✔
1504
              });
1505
        if (new_end != log_hlv.end()) {
1✔
1506
            if (!ec.ec_dry_run) {
1✔
1507
                log_hlv.erase(new_end, log_hlv.end());
1✔
1508
                tc.reload_data();
1✔
1509
                retval = "info: removed field highlight";
1✔
1510
            }
1511
            return Ok(retval);
1✔
1512
        }
1513

1514
        return ec.make_error("highlight does not exist -- {}", args[1]);
×
1515
    }
1516
    return ec.make_error("expecting highlighted field to clear");
×
1517
}
1✔
1518

1519
static Result<std::string, lnav::console::user_message>
1520
com_help(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
×
1521
{
1522
    std::string retval;
×
1523

1524
    if (!ec.ec_dry_run) {
×
1525
        ensure_view(&lnav_data.ld_views[LNV_HELP]);
×
1526
    }
1527

1528
    return Ok(retval);
×
1529
}
1530

1531
static Result<std::string, lnav::console::user_message>
1532
com_filter_expr(exec_context& ec,
7✔
1533
                std::string cmdline,
1534
                std::vector<std::string>& args)
1535
{
1536
    std::string retval;
7✔
1537

1538
    if (args.size() > 1) {
7✔
1539
        textview_curses* tc = *lnav_data.ld_view_stack.top();
7✔
1540

1541
        if (tc != &lnav_data.ld_views[LNV_LOG]) {
7✔
1542
            return ec.make_error(
1543
                "The :filter-expr command only works in the log view");
×
1544
        }
1545

1546
        auto expr = remaining_args(cmdline, args);
7✔
1547
        args[1] = fmt::format(FMT_STRING("SELECT 1 WHERE {}"), expr);
28✔
1548

1549
        auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
7✔
1550
#ifdef SQLITE_PREPARE_PERSISTENT
1551
        auto retcode = sqlite3_prepare_v3(lnav_data.ld_db.in(),
14✔
1552
                                          args[1].c_str(),
7✔
1553
                                          args[1].size(),
7✔
1554
                                          SQLITE_PREPARE_PERSISTENT,
1555
                                          stmt.out(),
1556
                                          nullptr);
1557
#else
1558
        auto retcode = sqlite3_prepare_v2(lnav_data.ld_db.in(),
1559
                                          args[1].c_str(),
1560
                                          args[1].size(),
1561
                                          stmt.out(),
1562
                                          nullptr);
1563
#endif
1564
        if (retcode != SQLITE_OK) {
7✔
1565
            const char* errmsg = sqlite3_errmsg(lnav_data.ld_db);
1✔
1566
            auto expr_al
1567
                = attr_line_t(expr)
2✔
1568
                      .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE))
2✔
1569
                      .move();
1✔
1570
            readline_sql_highlighter(
1✔
1571
                expr_al, lnav::sql::dialect::sqlite, std::nullopt);
1572
            auto um = lnav::console::user_message::error(
×
1573
                          attr_line_t("invalid filter expression: ")
2✔
1574
                              .append(expr_al))
1✔
1575
                          .with_reason(errmsg)
2✔
1576
                          .with_snippets(ec.ec_source)
2✔
1577
                          .move();
1✔
1578

1579
            return Err(um);
1✔
1580
        }
1✔
1581

1582
        if (ec.ec_dry_run) {
6✔
1583
            auto set_res = lnav_data.ld_log_source.set_preview_sql_filter(
1584
                stmt.release());
×
1585

1586
            if (set_res.isErr()) {
×
1587
                return Err(set_res.unwrapErr());
×
1588
            }
1589
            lnav_data.ld_preview_status_source[0].get_description().set_value(
×
1590
                "Matches are highlighted in the text view"_frag);
×
1591
            lnav_data.ld_status[LNS_PREVIEW0].set_needs_update();
×
1592
        } else {
×
1593
            lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
6✔
1594
            auto set_res
1595
                = lnav_data.ld_log_source.set_sql_filter(expr, stmt.release());
6✔
1596

1597
            if (set_res.isErr()) {
6✔
1598
                return Err(set_res.unwrapErr());
1✔
1599
            }
1600
        }
6✔
1601
        lnav_data.ld_log_source.text_filters_changed();
5✔
1602
        tc->reload_data();
5✔
1603
    } else {
9✔
1604
        return ec.make_error("expecting an SQL expression");
×
1605
    }
1606

1607
    return Ok(retval);
5✔
1608
}
7✔
1609

1610
static readline_context::prompt_result_t
1611
com_filter_expr_prompt(exec_context& ec, const std::string& cmdline)
×
1612
{
1613
    auto* tc = *lnav_data.ld_view_stack.top();
×
1614

1615
    if (tc != &lnav_data.ld_views[LNV_LOG]) {
×
1616
        return {""};
×
1617
    }
1618

1619
    return {
1620
        fmt::format(FMT_STRING("{} {}"),
×
1621
                    trim(cmdline),
×
1622
                    trim(lnav_data.ld_log_source.get_sql_filter_text())),
×
1623
    };
1624
}
1625

1626
static Result<std::string, lnav::console::user_message>
1627
com_clear_filter_expr(exec_context& ec,
1✔
1628
                      std::string cmdline,
1629
                      std::vector<std::string>& args)
1630
{
1631
    std::string retval;
1✔
1632

1633
    if (!ec.ec_dry_run) {
1✔
1634
        lnav_data.ld_log_source.set_sql_filter("", nullptr);
1✔
1635
        lnav_data.ld_log_source.text_filters_changed();
1✔
1636
    }
1637

1638
    return Ok(retval);
2✔
1639
}
1✔
1640

1641
static Result<std::string, lnav::console::user_message>
1642
com_enable_word_wrap(exec_context& ec,
×
1643
                     std::string cmdline,
1644
                     std::vector<std::string>& args)
1645
{
1646
    std::string retval;
×
1647

1648
    if (!ec.ec_dry_run) {
×
1649
        lnav_data.ld_views[LNV_LOG].set_word_wrap(true);
×
1650
        lnav_data.ld_views[LNV_TEXT].set_word_wrap(true);
×
1651
        lnav_data.ld_views[LNV_PRETTY].set_word_wrap(true);
×
1652
        retval = "info: enabled word-wrap";
×
1653
    }
1654

1655
    return Ok(retval);
×
1656
}
1657

1658
static Result<std::string, lnav::console::user_message>
1659
com_disable_word_wrap(exec_context& ec,
×
1660
                      std::string cmdline,
1661
                      std::vector<std::string>& args)
1662
{
1663
    std::string retval;
×
1664

1665
    if (!ec.ec_dry_run) {
×
1666
        lnav_data.ld_views[LNV_LOG].set_word_wrap(false);
×
1667
        lnav_data.ld_views[LNV_TEXT].set_word_wrap(false);
×
1668
        lnav_data.ld_views[LNV_PRETTY].set_word_wrap(false);
×
1669
        retval = "info: disabled word-wrap";
×
1670
    }
1671

1672
    return Ok(retval);
×
1673
}
1674

1675
static std::set<std::string> custom_logline_tables;
1676

1677
static Result<std::string, lnav::console::user_message>
1678
com_create_logline_table(exec_context& ec,
2✔
1679
                         std::string cmdline,
1680
                         std::vector<std::string>& args)
1681
{
1682
    auto* vtab_manager = injector::get<log_vtab_manager*>();
2✔
1683
    std::string retval;
2✔
1684

1685
    if (args.size() == 2) {
2✔
1686
        auto& log_view = lnav_data.ld_views[LNV_LOG];
2✔
1687

1688
        if (log_view.get_inner_height() == 0) {
2✔
1689
            return ec.make_error("no log data available");
×
1690
        }
1691
        auto vl = log_view.get_selection();
2✔
1692
        if (!vl) {
2✔
1693
            return ec.make_error("no focused line");
×
1694
        }
1695
        auto cl = lnav_data.ld_log_source.at_base(vl.value());
2✔
1696
        auto ldt
1697
            = std::make_shared<log_data_table>(lnav_data.ld_log_source,
1698
                                               *vtab_manager,
1699
                                               cl,
1700
                                               intern_string::lookup(args[1]));
2✔
1701
        ldt->vi_provenance = log_vtab_impl::provenance_t::user;
2✔
1702
        if (ec.ec_dry_run) {
2✔
1703
            attr_line_t al(ldt->get_table_statement());
×
1704

1705
            lnav_data.ld_preview_status_source[0].get_description().set_value(
×
1706
                "The following table will be created:"_frag);
×
1707
            lnav_data.ld_status[LNS_PREVIEW0].set_needs_update();
×
1708
            lnav_data.ld_preview_view[0].set_sub_source(
×
1709
                &lnav_data.ld_preview_source[0]);
1710
            lnav_data.ld_preview_source[0].replace_with(al).set_text_format(
×
1711
                text_format_t::TF_SQL);
×
1712

1713
            return Ok(std::string());
×
1714
        }
1715

1716
        auto errmsg = vtab_manager->register_vtab(ldt);
2✔
1717
        if (errmsg.empty()) {
2✔
1718
            custom_logline_tables.insert(args[1]);
2✔
1719
#if 0
1720
                    if (lnav_data.ld_rl_view != NULL) {
1721
                        lnav_data.ld_rl_view->add_possibility(
1722
                            ln_mode_t::COMMAND, "custom-table", args[1]);
1723
                    }
1724
#endif
1725
            retval = "info: created new log table -- " + args[1];
2✔
1726
        } else {
1727
            return ec.make_error("unable to create table -- {}", errmsg);
×
1728
        }
1729
    } else {
2✔
1730
        return ec.make_error("expecting a table name");
×
1731
    }
1732

1733
    return Ok(retval);
2✔
1734
}
2✔
1735

1736
static Result<std::string, lnav::console::user_message>
1737
com_delete_logline_table(exec_context& ec,
×
1738
                         std::string cmdline,
1739
                         std::vector<std::string>& args)
1740
{
1741
    auto* vtab_manager = injector::get<log_vtab_manager*>();
×
1742
    std::string retval;
×
1743

1744
    if (args.size() == 2) {
×
1745
        if (custom_logline_tables.find(args[1]) == custom_logline_tables.end())
×
1746
        {
1747
            return ec.make_error("unknown logline table -- {}", args[1]);
×
1748
        }
1749

1750
        if (ec.ec_dry_run) {
×
1751
            return Ok(std::string());
×
1752
        }
1753

1754
        std::string rc = vtab_manager->unregister_vtab(args[1]);
×
1755

1756
        if (rc.empty()) {
×
1757
#if 0
1758
            if (lnav_data.ld_rl_view != NULL) {
1759
                lnav_data.ld_rl_view->rem_possibility(
1760
                    ln_mode_t::COMMAND, "custom-table", args[1]);
1761
            }
1762
#endif
1763
            retval = "info: deleted logline table";
×
1764
        } else {
1765
            return ec.make_error("{}", rc);
×
1766
        }
1767
    } else {
×
1768
        return ec.make_error("expecting a table name");
×
1769
    }
1770

1771
    return Ok(retval);
×
1772
}
1773

1774
static Result<std::string, lnav::console::user_message>
1775
com_create_search_table(exec_context& ec,
9✔
1776
                        std::string cmdline,
1777
                        std::vector<std::string>& args)
1778
{
1779
    auto* vtab_manager = injector::get<log_vtab_manager*>();
9✔
1780
    std::string retval;
9✔
1781

1782
    if (args.size() >= 2) {
9✔
1783
        const static intern_string_t PATTERN_SRC
1784
            = intern_string::lookup("pattern");
27✔
1785
        string_fragment regex_frag;
9✔
1786
        std::string regex;
9✔
1787

1788
        if (args.size() >= 3) {
9✔
1789
            regex_frag = remaining_args_frag(cmdline, args, 2);
9✔
1790
            regex = regex_frag.to_string();
9✔
1791
        } else {
1792
            regex = lnav_data.ld_views[LNV_LOG].get_current_search();
×
1793
        }
1794

1795
        auto compile_res = lnav::pcre2pp::code::from(
1796
            regex, log_search_table_ns::PATTERN_OPTIONS);
9✔
1797

1798
        if (compile_res.isErr()) {
9✔
1799
            auto re_err = compile_res.unwrapErr();
1✔
1800
            auto um = lnav::console::to_user_message(PATTERN_SRC, re_err)
1✔
1801
                          .with_snippets(ec.ec_source);
1✔
1802
            return Err(um);
1✔
1803
        }
1✔
1804

1805
        auto re = compile_res.unwrap().to_shared();
8✔
1806
        auto tab_name = intern_string::lookup(args[1]);
8✔
1807
        auto lst = std::make_shared<log_search_table>(re, tab_name);
8✔
1808
        if (ec.ec_dry_run) {
8✔
1809
            auto* tc = &lnav_data.ld_views[LNV_LOG];
×
1810
            auto& hm = tc->get_highlights();
×
1811
            highlighter hl(re);
×
1812

1813
            hl.with_role(role_t::VCR_INFO);
×
1814
            hl.with_attrs(text_attrs::with_blink());
×
1815

1816
            hm[{highlight_source_t::PREVIEW, "preview"}] = hl;
×
1817
            tc->reload_data();
×
1818

1819
            attr_line_t al(lst->get_table_statement());
×
1820

1821
            lnav_data.ld_preview_status_source[0].get_description().set_value(
×
1822
                "The following table will be created:"_frag);
×
1823
            lnav_data.ld_status[LNS_PREVIEW0].set_needs_update();
×
1824

1825
            lnav_data.ld_preview_view[0].set_sub_source(
×
1826
                &lnav_data.ld_preview_source[0]);
1827
            lnav_data.ld_preview_source[0].replace_with(al).set_text_format(
×
1828
                text_format_t::TF_SQL);
×
1829

1830
            return Ok(std::string());
×
1831
        }
1832

1833
        lst->vi_provenance = log_vtab_impl::provenance_t::user;
8✔
1834
        auto existing = vtab_manager->lookup_impl(tab_name);
8✔
1835
        if (existing != nullptr) {
8✔
1836
            if (existing->vi_provenance != log_vtab_impl::provenance_t::user) {
×
1837
                return ec.make_error(
1838
                    FMT_STRING("a table with the name '{}' already exists"),
×
1839
                    tab_name->to_string_fragment());
×
1840
            }
1841
            vtab_manager->unregister_vtab(tab_name->to_string_fragment());
×
1842
        }
1843

1844
        auto errmsg = vtab_manager->register_vtab(lst);
8✔
1845
        if (errmsg.empty()) {
8✔
1846
            retval = "info: created new search table -- " + args[1];
8✔
1847
        } else {
1848
            return ec.make_error("unable to create table -- {}", errmsg);
×
1849
        }
1850
    } else {
10✔
1851
        return ec.make_error("expecting a table name");
×
1852
    }
1853

1854
    return Ok(retval);
8✔
1855
}
9✔
1856

1857
static Result<std::string, lnav::console::user_message>
1858
com_delete_search_table(exec_context& ec,
2✔
1859
                        std::string cmdline,
1860
                        std::vector<std::string>& args)
1861
{
1862
    auto* vtab_manager = injector::get<log_vtab_manager*>();
2✔
1863
    std::string retval;
2✔
1864

1865
    if (args.size() < 2) {
2✔
1866
        return ec.make_error("expecting a table name");
×
1867
    }
1868
    for (auto lpc = size_t{1}; lpc < args.size(); lpc++) {
2✔
1869
        auto& table_name = args[lpc];
2✔
1870
        auto tab = vtab_manager->lookup_impl(table_name);
2✔
1871
        if (tab == nullptr
2✔
1872
            || dynamic_cast<log_search_table*>(tab.get()) == nullptr
1✔
1873
            || tab->vi_provenance != log_vtab_impl::provenance_t::user)
3✔
1874
        {
1875
            return ec.make_error("unknown search table -- {}", table_name);
2✔
1876
        }
1877

1878
        if (ec.ec_dry_run) {
×
1879
            continue;
×
1880
        }
1881

1882
        auto rc = vtab_manager->unregister_vtab(args[1]);
×
1883
        if (rc.empty()) {
×
1884
            retval = "info: deleted search table";
×
1885
        } else {
1886
            return ec.make_error("{}", rc);
×
1887
        }
1888
    }
2✔
1889

1890
    return Ok(retval);
×
1891
}
2✔
1892

1893
static Result<std::string, lnav::console::user_message>
1894
com_session(exec_context& ec,
×
1895
            std::string cmdline,
1896
            std::vector<std::string>& args)
1897
{
1898
    std::string retval;
×
1899

1900
    if (ec.ec_dry_run) {
×
1901
        retval = "";
×
1902
    } else if (args.size() >= 2) {
×
1903
        /* XXX put these in a map */
1904
        if (args[1] != "highlight" && args[1] != "enable-word-wrap"
×
1905
            && args[1] != "disable-word-wrap" && args[1] != "filter-in"
×
1906
            && args[1] != "filter-out" && args[1] != "enable-filter"
×
1907
            && args[1] != "disable-filter")
×
1908
        {
1909
            return ec.make_error(
1910
                "only the highlight, filter, and word-wrap commands are "
1911
                "supported");
×
1912
        }
1913
        if (getenv("HOME") == NULL) {
×
1914
            return ec.make_error("the HOME environment variable is not set");
×
1915
        }
1916
        auto saved_cmd = trim(remaining_args(cmdline, args));
×
1917
        auto old_file_name = lnav::paths::dotlnav() / "session";
×
1918
        auto new_file_name = lnav::paths::dotlnav() / "session.tmp";
×
1919

1920
        std::ifstream session_file(old_file_name.string());
×
1921
        std::ofstream new_session_file(new_file_name.string());
×
1922

1923
        if (!new_session_file) {
×
1924
            return ec.make_error("cannot write to session file");
×
1925
        } else {
1926
            bool added = false;
×
1927
            std::string line;
×
1928

1929
            if (session_file.is_open()) {
×
1930
                while (getline(session_file, line)) {
×
1931
                    if (line == saved_cmd) {
×
1932
                        added = true;
×
1933
                        break;
×
1934
                    }
1935
                    new_session_file << line << std::endl;
×
1936
                }
1937
            }
1938
            if (!added) {
×
1939
                new_session_file << saved_cmd << std::endl;
×
1940

1941
                log_perror(
×
1942
                    rename(new_file_name.c_str(), old_file_name.c_str()));
1943
            } else {
1944
                log_perror(remove(new_file_name.c_str()));
×
1945
            }
1946

1947
            retval = "info: session file saved";
×
1948
        }
1949
    } else {
×
1950
        return ec.make_error("expecting a command to save to the session file");
×
1951
    }
1952

1953
    return Ok(retval);
×
1954
}
1955

1956
static Result<std::string, lnav::console::user_message>
1957
com_file_visibility(exec_context& ec,
4✔
1958
                    std::string cmdline,
1959
                    std::vector<std::string>& args)
1960
{
1961
    static const intern_string_t SRC = intern_string::lookup("path");
12✔
1962
    bool only_this_file = false;
4✔
1963
    bool make_visible;
1964
    std::string retval;
4✔
1965

1966
    if (args[0] == "show-file") {
4✔
1967
        make_visible = true;
×
1968
    } else if (args[0] == "show-only-this-file") {
4✔
1969
        make_visible = true;
×
1970
        only_this_file = true;
×
1971
    } else {
1972
        make_visible = false;
4✔
1973
    }
1974

1975
    if (args.size() == 1 || only_this_file) {
4✔
1976
        auto* tc = *lnav_data.ld_view_stack.top();
2✔
1977
        std::shared_ptr<logfile> lf;
2✔
1978

1979
        if (tc == &lnav_data.ld_views[LNV_TEXT]) {
2✔
1980
            const auto& tss = lnav_data.ld_text_source;
×
1981

1982
            if (tss.empty()) {
×
1983
                return ec.make_error("no text files are opened");
×
1984
            }
1985
            lf = tss.current_file();
×
1986
        } else if (tc == &lnav_data.ld_views[LNV_LOG]) {
2✔
1987
            if (tc->get_inner_height() == 0) {
2✔
1988
                return ec.make_error("no log files loaded");
×
1989
            }
1990
            auto& lss = lnav_data.ld_log_source;
2✔
1991
            auto vl = tc->get_selection().value_or(0_vl);
2✔
1992
            auto cl = lss.at(vl);
2✔
1993
            lf = lss.find(cl);
2✔
1994
        } else {
1995
            return ec.make_error(
1996
                ":{} must be run in the log or text file views", args[0]);
×
1997
        }
1998

1999
        if (!ec.ec_dry_run) {
2✔
2000
            if (only_this_file) {
2✔
2001
                for (const auto& ld : lnav_data.ld_log_source) {
×
2002
                    ld->set_visibility(false);
×
2003
                    ld->get_file_ptr()->set_indexing(false);
×
2004
                }
2005
            }
2006
            lf->set_indexing(make_visible);
2✔
2007
            lnav_data.ld_log_source.find_data(lf) |
2✔
2008
                [make_visible](auto ld) { ld->set_visibility(make_visible); };
2✔
2009
            tc->get_sub_source()->text_filters_changed();
2✔
2010
        }
2011
        retval = fmt::format(FMT_STRING("info: {} file -- {}"),
8✔
2012
                             make_visible ? "showing" : "hiding",
2✔
2013
                             lf->get_filename());
2✔
2014
    } else {
2✔
2015
        auto* top_tc = *lnav_data.ld_view_stack.top();
2✔
2016
        int text_file_count = 0, log_file_count = 0;
2✔
2017
        auto lexer = shlex(cmdline);
2✔
2018

2019
        auto split_args_res = lexer.split(ec.create_resolver());
2✔
2020
        if (split_args_res.isErr()) {
2✔
2021
            auto split_err = split_args_res.unwrapErr();
×
2022
            auto um = lnav::console::user_message::error(
×
2023
                          "unable to parse file name")
2024
                          .with_reason(split_err.se_error.te_msg)
×
2025
                          .with_snippet(lnav::console::snippet::from(
×
2026
                              SRC, lexer.to_attr_line(split_err.se_error)))
×
2027
                          .move();
×
2028

2029
            return Err(um);
×
2030
        }
2031

2032
        auto args = split_args_res.unwrap()
4✔
2033
            | lnav::itertools::map(
2✔
2034
                        [](const auto& elem) { return elem.se_value; });
6✔
2035
        args.erase(args.begin());
2✔
2036

2037
        for (const auto& lf : lnav_data.ld_active_files.fc_files) {
5✔
2038
            if (lf.get() == nullptr) {
3✔
2039
                continue;
1✔
2040
            }
2041

2042
            auto ld_opt = lnav_data.ld_log_source.find_data(lf);
3✔
2043

2044
            if (!ld_opt || ld_opt.value()->ld_visible == make_visible) {
3✔
2045
                continue;
×
2046
            }
2047

2048
            auto find_iter
2049
                = find_if(args.begin(), args.end(), [&lf](const auto& arg) {
3✔
2050
                      return fnmatch(arg.c_str(), lf->get_filename().c_str(), 0)
3✔
2051
                          == 0;
3✔
2052
                  });
2053

2054
            if (find_iter == args.end()) {
3✔
2055
                continue;
1✔
2056
            }
2057

2058
            if (!ec.ec_dry_run) {
2✔
2059
                ld_opt | [make_visible](auto ld) {
2✔
2060
                    ld->get_file_ptr()->set_indexing(make_visible);
2✔
2061
                    ld->set_visibility(make_visible);
2✔
2062
                };
2✔
2063
            }
2064
            if (lf->get_format() != nullptr) {
2✔
2065
                log_file_count += 1;
2✔
2066
            } else {
2067
                text_file_count += 1;
×
2068
            }
2069
        }
2070
        if (!ec.ec_dry_run && log_file_count > 0) {
2✔
2071
            lnav_data.ld_views[LNV_LOG]
2072
                .get_sub_source()
2✔
2073
                ->text_filters_changed();
2✔
2074
            if (top_tc == &lnav_data.ld_views[LNV_TIMELINE]) {
2✔
2075
                lnav_data.ld_views[LNV_TIMELINE]
2076
                    .get_sub_source()
1✔
2077
                    ->text_filters_changed();
1✔
2078
            }
2079
        }
2080
        if (!ec.ec_dry_run && text_file_count > 0) {
2✔
2081
            lnav_data.ld_views[LNV_TEXT]
2082
                .get_sub_source()
×
2083
                ->text_filters_changed();
×
2084
        }
2085
        retval = fmt::format(
2✔
2086
            FMT_STRING("info: {} {:L} log files and {:L} text files"),
4✔
2087
            make_visible ? "showing" : "hiding",
2✔
2088
            log_file_count,
2089
            text_file_count);
2✔
2090
    }
2✔
2091

2092
    return Ok(retval);
4✔
2093
}
4✔
2094

2095
static Result<std::string, lnav::console::user_message>
2096
com_summarize(exec_context& ec,
1✔
2097
              std::string cmdline,
2098
              std::vector<std::string>& args)
2099
{
2100
    std::string retval;
1✔
2101

2102
    if (!setup_logline_table(ec)) {
1✔
2103
        return ec.make_error("no log data available");
×
2104
    }
2105
    if (args.size() == 1) {
1✔
2106
        return ec.make_error("no columns specified");
×
2107
    }
2108
    auto_mem<char, sqlite3_free> query_frag;
1✔
2109
    std::vector<std::string> other_columns;
1✔
2110
    std::vector<std::string> num_columns;
1✔
2111
    const auto& top_source = ec.ec_source.back();
1✔
2112
    sql_progress_guard progress_guard(sql_progress,
2113
                                      sql_progress_finished,
2114
                                      top_source.s_location,
2115
                                      top_source.s_content,
1✔
2116
                                      true);
1✔
2117
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1✔
2118
    int retcode;
2119
    std::string query;
1✔
2120

2121
    query = "SELECT ";
1✔
2122
    for (size_t lpc = 1; lpc < args.size(); lpc++) {
2✔
2123
        if (lpc > 1)
1✔
2124
            query += ", ";
×
2125
        query += args[lpc];
1✔
2126
    }
2127
    query += " FROM logline ";
1✔
2128

2129
    retcode = sqlite3_prepare_v2(
1✔
2130
        lnav_data.ld_db.in(), query.c_str(), -1, stmt.out(), nullptr);
2131
    if (retcode != SQLITE_OK) {
1✔
2132
        const char* errmsg = sqlite3_errmsg(lnav_data.ld_db);
×
2133

2134
        return ec.make_error("{}", errmsg);
×
2135
    }
2136

2137
    switch (sqlite3_step(stmt.in())) {
1✔
2138
        case SQLITE_OK:
×
2139
        case SQLITE_DONE: {
2140
            return ec.make_error("no data");
×
2141
        } break;
2142
        case SQLITE_ROW:
1✔
2143
            break;
1✔
2144
        default: {
×
2145
            const char* errmsg;
2146

2147
            errmsg = sqlite3_errmsg(lnav_data.ld_db);
×
2148
            return ec.make_error("{}", errmsg);
×
2149
        } break;
2150
    }
2151

2152
    if (ec.ec_dry_run) {
1✔
2153
        return Ok(std::string());
×
2154
    }
2155

2156
    for (int lpc = 0; lpc < sqlite3_column_count(stmt.in()); lpc++) {
2✔
2157
        switch (sqlite3_column_type(stmt.in(), lpc)) {
1✔
2158
            case SQLITE_INTEGER:
×
2159
            case SQLITE_FLOAT:
2160
                num_columns.push_back(args[lpc + 1]);
×
2161
                break;
×
2162
            default:
1✔
2163
                other_columns.push_back(args[lpc + 1]);
1✔
2164
                break;
1✔
2165
        }
2166
    }
2167

2168
    query = "SELECT";
1✔
2169
    for (auto iter = other_columns.begin(); iter != other_columns.end(); ++iter)
2✔
2170
    {
2171
        if (iter != other_columns.begin()) {
1✔
2172
            query += ",";
×
2173
        }
2174
        query_frag
2175
            = sqlite3_mprintf(" %s as \"c_%s\", count(*) as \"count_%s\"",
2176
                              iter->c_str(),
2177
                              iter->c_str(),
2178
                              iter->c_str());
1✔
2179
        query += query_frag;
1✔
2180
    }
2181

2182
    if (!other_columns.empty() && !num_columns.empty()) {
1✔
2183
        query += ", ";
×
2184
    }
2185

2186
    for (auto iter = num_columns.begin(); iter != num_columns.end(); ++iter) {
1✔
2187
        if (iter != num_columns.begin()) {
×
2188
            query += ",";
×
2189
        }
2190
        query_frag = sqlite3_mprintf(
2191
            " sum(\"%s\"), "
2192
            " min(\"%s\"), "
2193
            " avg(\"%s\"), "
2194
            " median(\"%s\"), "
2195
            " stddev(\"%s\"), "
2196
            " max(\"%s\") ",
2197
            iter->c_str(),
2198
            iter->c_str(),
2199
            iter->c_str(),
2200
            iter->c_str(),
2201
            iter->c_str(),
2202
            iter->c_str());
×
2203
        query += query_frag;
×
2204
    }
2205

2206
    query
2207
        += (" FROM logline "
2208
            "WHERE (logline.log_part is null or "
2209
            "startswith(logline.log_part, '.') = 0) ");
1✔
2210

2211
    for (auto iter = other_columns.begin(); iter != other_columns.end(); ++iter)
2✔
2212
    {
2213
        if (iter == other_columns.begin()) {
1✔
2214
            query += " GROUP BY ";
1✔
2215
        } else {
2216
            query += ",";
×
2217
        }
2218
        query_frag = sqlite3_mprintf(" \"c_%s\"", iter->c_str());
1✔
2219
        query += query_frag;
1✔
2220
    }
2221

2222
    for (auto iter = other_columns.begin(); iter != other_columns.end(); ++iter)
2✔
2223
    {
2224
        if (iter == other_columns.begin()) {
1✔
2225
            query += " ORDER BY ";
1✔
2226
        } else {
2227
            query += ",";
×
2228
        }
2229
        query_frag = sqlite3_mprintf(
2230
            " \"count_%s\" desc, \"c_%s\" collate "
2231
            "naturalnocase asc",
2232
            iter->c_str(),
2233
            iter->c_str());
1✔
2234
        query += query_frag;
1✔
2235
    }
2236
    log_debug("query %s", query.c_str());
1✔
2237

2238
    auto& dls = *ec.ec_label_source_stack.back();
1✔
2239

2240
    dls.clear();
1✔
2241
    retcode = sqlite3_prepare_v2(
1✔
2242
        lnav_data.ld_db.in(), query.c_str(), -1, stmt.out(), nullptr);
2243

2244
    if (retcode != SQLITE_OK) {
1✔
2245
        const char* errmsg = sqlite3_errmsg(lnav_data.ld_db);
×
2246

2247
        return ec.make_error("{}", errmsg);
×
2248
    } else if (stmt == nullptr) {
1✔
2249
        retval = "";
×
2250
    } else {
2251
        bool done = false;
1✔
2252

2253
        ec.ec_sql_callback(ec, stmt.in());
1✔
2254
        while (!done) {
3✔
2255
            retcode = sqlite3_step(stmt.in());
2✔
2256

2257
            switch (retcode) {
2✔
2258
                case SQLITE_OK:
1✔
2259
                case SQLITE_DONE:
2260
                    done = true;
1✔
2261
                    break;
1✔
2262

2263
                case SQLITE_ROW:
1✔
2264
                    ec.ec_sql_callback(ec, stmt.in());
1✔
2265
                    break;
1✔
2266

2267
                default: {
×
2268
                    const char* errmsg;
2269

2270
                    errmsg = sqlite3_errmsg(lnav_data.ld_db);
×
2271
                    return ec.make_error("{}", errmsg);
×
2272
                }
2273
            }
2274
        }
2275

2276
        if (retcode == SQLITE_DONE) {
1✔
2277
            lnav_data.ld_views[LNV_LOG].reload_data();
1✔
2278
            lnav_data.ld_views[LNV_DB].reload_data();
1✔
2279
            lnav_data.ld_views[LNV_DB].set_left(0);
1✔
2280

2281
            if (dls.dls_row_cursors.size() > 0) {
1✔
2282
                ensure_view(&lnav_data.ld_views[LNV_DB]);
1✔
2283
            }
2284
        }
2285

2286
        lnav_data.ld_bottom_source.update_loading(0, 0);
1✔
2287
        lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
1✔
2288
    }
2289

2290
    return Ok(retval);
1✔
2291
}
1✔
2292

2293
static Result<std::string, lnav::console::user_message>
2294
com_add_test(exec_context& ec,
×
2295
             std::string cmdline,
2296
             std::vector<std::string>& args)
2297
{
2298
    std::string retval;
×
2299

2300
    if (args.size() > 1) {
×
2301
        return ec.make_error("not expecting any arguments");
×
2302
    }
2303
    if (!ec.ec_dry_run) {
×
2304
        auto* tc = *lnav_data.ld_view_stack.top();
×
2305

2306
        auto& bv = tc->get_bookmarks()[&textview_curses::BM_USER];
×
2307
        for (auto iter = bv.bv_tree.begin(); iter != bv.bv_tree.end(); ++iter) {
×
2308
            auto_mem<FILE> file(fclose);
×
2309
            char path[PATH_MAX];
2310
            std::string line;
×
2311

2312
            tc->grep_value_for_line(*iter, line);
×
2313

2314
            line.insert(0, 13, ' ');
×
2315

2316
            snprintf(path,
×
2317
                     sizeof(path),
2318
                     "%s/test/log-samples/sample-%s.txt",
2319
                     getenv("LNAV_SRC"),
2320
                     hasher().update(line).to_string().c_str());
×
2321

2322
            if ((file = fopen(path, "w")) == nullptr) {
×
2323
                perror("fopen failed");
×
2324
            } else {
2325
                fprintf(file, "%s\n", line.c_str());
×
2326
            }
2327
        }
2328
    }
2329

2330
    return Ok(retval);
×
2331
}
2332

2333
static Result<std::string, lnav::console::user_message>
2334
com_switch_to_view(exec_context& ec,
76✔
2335
                   std::string cmdline,
2336
                   std::vector<std::string>& args)
2337
{
2338
    std::string retval;
76✔
2339

2340
    if (args.size() > 1) {
76✔
2341
        auto view_index_opt = view_from_string(args[1].c_str());
76✔
2342
        if (!view_index_opt) {
76✔
2343
            return ec.make_error("invalid view name -- {}", args[1]);
×
2344
        }
2345
        if (!ec.ec_dry_run) {
76✔
2346
            auto* tc = &lnav_data.ld_views[view_index_opt.value()];
76✔
2347
            if (args[0] == "switch-to-view") {
76✔
2348
                ensure_view(tc);
76✔
2349
            } else {
2350
                toggle_view(tc);
×
2351
            }
2352
        }
2353
    }
2354

2355
    return Ok(retval);
76✔
2356
}
76✔
2357

2358
static Result<std::string, lnav::console::user_message>
2359
com_toggle_filtering(exec_context& ec,
1✔
2360
                     std::string cmdline,
2361
                     std::vector<std::string>& args)
2362
{
2363
    std::string retval;
1✔
2364

2365
    if (!ec.ec_dry_run) {
1✔
2366
        auto* tc = *lnav_data.ld_view_stack.top();
1✔
2367
        auto* tss = tc->get_sub_source();
1✔
2368

2369
        tss->toggle_apply_filters();
1✔
2370
        lnav_data.ld_filter_status_source.update_filtered(tss);
1✔
2371
        lnav_data.ld_status[LNS_FILTER].set_needs_update();
1✔
2372
    }
2373

2374
    return Ok(retval);
2✔
2375
}
1✔
2376

2377
static Result<std::string, lnav::console::user_message>
2378
com_zoom_to(exec_context& ec,
5✔
2379
            std::string cmdline,
2380
            std::vector<std::string>& args)
2381
{
2382
    std::string retval;
5✔
2383
    std::optional<int> zoom_level;
5✔
2384

2385
    if (args.size() == 1) {
5✔
2386
        auto um = lnav::console::user_message::error("expecting a zoom level")
×
2387
                      .with_snippets(ec.ec_source)
×
2388
                      .with_help(attr_line_t("available levels: ")
×
2389
                                     .join(lnav_zoom_strings, ", "))
×
2390
                      .move();
×
2391
        return Err(um);
×
2392
    }
2393

2394
    for (size_t lpc = 0; lpc < lnav_zoom_strings.size() && !zoom_level; lpc++) {
94✔
2395
        if (lnav_zoom_strings[lpc].iequal(args[1])) {
42✔
2396
            zoom_level = lpc;
4✔
2397
        }
2398
    }
2399

2400
    if (!zoom_level) {
5✔
2401
        auto um = lnav::console::user_message::error(
×
2402
                      attr_line_t("invalid zoom level: ")
1✔
2403
                          .append(lnav::roles::symbol(args[1])))
2✔
2404
                      .with_snippets(ec.ec_source)
2✔
2405
                      .with_help(attr_line_t("available levels: ")
2✔
2406
                                     .join(lnav_zoom_strings, ", "))
1✔
2407
                      .move();
1✔
2408
        return Err(um);
1✔
2409
    }
1✔
2410
    if (!ec.ec_dry_run) {
4✔
2411
        auto& ss = *lnav_data.ld_spectro_source;
4✔
2412
        timeval old_time;
2413

2414
        lnav_data.ld_zoom_level = zoom_level.value();
4✔
2415

2416
        auto& hist_view = lnav_data.ld_views[LNV_HISTOGRAM];
4✔
2417

2418
        if (hist_view.get_inner_height() > 0) {
4✔
2419
            auto old_time_opt = lnav_data.ld_hist_source2.time_for_row(
3✔
2420
                lnav_data.ld_views[LNV_HISTOGRAM].get_top());
2421
            if (old_time_opt) {
3✔
2422
                old_time = old_time_opt.value().ri_time;
3✔
2423
                rebuild_hist();
3✔
2424
                lnav_data.ld_hist_source2.row_for_time(old_time) |
3✔
2425
                    [](auto new_top) {
3✔
2426
                        lnav_data.ld_views[LNV_HISTOGRAM].set_top(new_top);
3✔
2427
                    };
3✔
2428
            }
2429
        }
2430

2431
        auto& spectro_view = lnav_data.ld_views[LNV_SPECTRO];
4✔
2432

2433
        if (spectro_view.get_inner_height() > 0) {
4✔
2434
            auto old_time_opt = lnav_data.ld_spectro_source->time_for_row(
×
2435
                lnav_data.ld_views[LNV_SPECTRO].get_selection().value_or(0_vl));
×
2436
            ss.ss_granularity = ZOOM_LEVELS[lnav_data.ld_zoom_level];
×
2437
            ss.invalidate();
×
2438
            spectro_view.reload_data();
×
2439
            if (old_time_opt) {
×
2440
                lnav_data.ld_spectro_source->row_for_time(
×
2441
                    old_time_opt.value().ri_time)
×
2442
                    |
×
2443
                    [](auto new_top) {
×
2444
                        lnav_data.ld_views[LNV_SPECTRO].set_selection(new_top);
×
2445
                    };
2446
            }
2447
        }
2448

2449
        lnav_data.ld_view_stack.set_needs_update();
4✔
2450

2451
        retval = fmt::format(FMT_STRING("info: set zoom-level to {}"),
16✔
2452
                             lnav_zoom_strings[zoom_level.value()]);
8✔
2453
    }
2454

2455
    return Ok(retval);
4✔
2456
}
5✔
2457

2458
static Result<std::string, lnav::console::user_message>
2459
com_reset_session(exec_context& ec,
6✔
2460
                  std::string cmdline,
2461
                  std::vector<std::string>& args)
2462
{
2463
    if (!ec.ec_dry_run) {
6✔
2464
        reset_session();
6✔
2465
        lnav_data.ld_views[LNV_LOG].reload_data();
6✔
2466
    }
2467

2468
    return Ok(std::string());
6✔
2469
}
2470

2471
static Result<std::string, lnav::console::user_message>
2472
com_load_session(exec_context& ec,
23✔
2473
                 std::string cmdline,
2474
                 std::vector<std::string>& args)
2475
{
2476
    if (!ec.ec_dry_run) {
23✔
2477
        load_session();
23✔
2478
        lnav::session::apply_view_commands();
23✔
2479
        lnav::session::restore_view_states();
23✔
2480
        load_time_bookmarks();
23✔
2481
        lnav_data.ld_views[LNV_LOG].reload_data();
23✔
2482
    }
2483

2484
    return Ok(std::string());
23✔
2485
}
2486

2487
static Result<std::string, lnav::console::user_message>
2488
com_save_session(exec_context& ec,
15✔
2489
                 std::string cmdline,
2490
                 std::vector<std::string>& args)
2491
{
2492
    if (!ec.ec_dry_run) {
15✔
2493
        save_session();
15✔
2494
    }
2495

2496
    return Ok(std::string());
15✔
2497
}
2498

2499
static Result<std::string, lnav::console::user_message>
2500
com_set_min_log_level(exec_context& ec,
3✔
2501
                      std::string cmdline,
2502
                      std::vector<std::string>& args)
2503
{
2504
    std::string retval;
3✔
2505

2506
    if (args.size() == 2) {
3✔
2507
        auto& lss = lnav_data.ld_log_source;
3✔
2508
        auto new_level = string2level(args[1].c_str(), args[1].size(), false);
3✔
2509
        if (ec.ec_dry_run) {
3✔
2510
            lss.tss_preview_min_log_level = new_level;
×
2511
            retval = fmt::format(
×
2512
                FMT_STRING("info: previewing with min log level -- {}"),
×
2513
                level_names[new_level]);
×
2514
        } else {
2515
            lss.set_min_log_level(new_level);
3✔
2516
            retval = fmt::format(
3✔
2517
                FMT_STRING("info: minimum log level is now -- {}"),
12✔
2518
                level_names[new_level]);
6✔
2519
        }
2520
    } else {
2521
        return ec.make_error("expecting a log level name");
×
2522
    }
2523

2524
    return Ok(retval);
3✔
2525
}
3✔
2526

2527
static Result<std::string, lnav::console::user_message>
2528
com_hide_unmarked(exec_context& ec,
2✔
2529
                  std::string cmdline,
2530
                  std::vector<std::string>& args)
2531
{
2532
    std::string retval = "info: hid unmarked lines";
2✔
2533

2534
    if (ec.ec_dry_run) {
2✔
2535
        retval = "";
×
2536
    } else {
2537
        auto* tc = *lnav_data.ld_view_stack.top();
2✔
2538
        const auto& bv = tc->get_bookmarks()[&textview_curses::BM_USER];
2✔
2539
        const auto& bv_expr
2540
            = tc->get_bookmarks()[&textview_curses::BM_USER_EXPR];
2✔
2541

2542
        if (bv.empty() && bv_expr.empty()) {
2✔
2543
            return ec.make_error("no lines have been marked");
×
2544
        } else {
2545
            lnav_data.ld_log_source.set_marked_only(true);
2✔
2546
        }
2547
    }
2548

2549
    return Ok(retval);
2✔
2550
}
2✔
2551

2552
static Result<std::string, lnav::console::user_message>
2553
com_show_unmarked(exec_context& ec,
×
2554
                  std::string cmdline,
2555
                  std::vector<std::string>& args)
2556
{
2557
    std::string retval = "info: showing unmarked lines";
×
2558

2559
    if (ec.ec_dry_run) {
×
2560
        retval = "";
×
2561
    } else {
2562
        lnav_data.ld_log_source.set_marked_only(false);
×
2563
    }
2564

2565
    return Ok(retval);
×
2566
}
2567

2568
static Result<std::string, lnav::console::user_message>
2569
com_shexec(exec_context& ec,
2✔
2570
           std::string cmdline,
2571
           std::vector<std::string>& args)
2572
{
2573
    if (!ec.ec_dry_run) {
2✔
2574
        log_perror(system(cmdline.substr(args[0].size()).c_str()));
2✔
2575
    }
2576

2577
    return Ok(std::string());
2✔
2578
}
2579

2580
static Result<std::string, lnav::console::user_message>
2581
com_poll_now(exec_context& ec,
×
2582
             std::string cmdline,
2583
             std::vector<std::string>& args)
2584
{
2585
    if (!ec.ec_dry_run) {
×
2586
        isc::to<curl_looper&, services::curl_streamer_t>().send_and_wait(
×
2587
            [](auto& clooper) { clooper.process_all(); });
×
2588
    }
2589

2590
    return Ok(std::string());
×
2591
}
2592

2593
static Result<std::string, lnav::console::user_message>
2594
com_test_comment(exec_context& ec,
8✔
2595
                 std::string cmdline,
2596
                 std::vector<std::string>& args)
2597
{
2598
    return Ok(std::string());
8✔
2599
}
2600

2601
static Result<std::string, lnav::console::user_message>
2602
com_redraw(exec_context& ec,
×
2603
           std::string cmdline,
2604
           std::vector<std::string>& args)
2605
{
2606
    if (ec.ec_dry_run) {
×
2607
    } else if (ec.ec_ui_callbacks.uc_redraw) {
×
2608
        ec.ec_ui_callbacks.uc_redraw();
×
2609
    }
2610

2611
    return Ok(std::string());
×
2612
}
2613

2614
static auto CONFIG_HELP
2615
    = help_text(":config")
2616
          .with_summary("Read or write a configuration option")
2617
          .with_parameter(
2618
              help_text{"option", "The path to the option to read or write"}
2619
                  .with_format(help_parameter_format_t::HPF_CONFIG_PATH))
2620
          .with_parameter(
2621
              help_text("value",
2622
                        "The value to write.  If not given, the "
2623
                        "current value is returned")
2624
                  .optional()
2625
                  .with_format(help_parameter_format_t::HPF_CONFIG_VALUE))
2626
          .with_example({"To read the configuration of the "
2627
                         "'/ui/clock-format' option",
2628
                         "/ui/clock-format"})
2629
          .with_example({"To set the '/ui/dim-text' option to 'false'",
2630
                         "/ui/dim-text false"})
2631
          .with_tags({"configuration"});
2632

2633
static Result<std::string, lnav::console::user_message>
2634
com_config(exec_context& ec,
20✔
2635
           std::string cmdline,
2636
           std::vector<std::string>& args)
2637
{
2638
    static auto& prompt = lnav::prompt::get();
20✔
2639

2640
    std::string retval;
20✔
2641

2642
    if (args.size() > 1) {
20✔
2643
        static const intern_string_t INPUT_SRC = intern_string::lookup("input");
54✔
2644

2645
        auto cmdline_sf = string_fragment::from_str(cmdline);
20✔
2646
        auto parse_res = lnav::command::parse_for_call(
2647
            ec,
2648
            cmdline_sf.split_pair(string_fragment::tag1{' '})->second,
20✔
2649
            CONFIG_HELP);
20✔
2650
        if (parse_res.isErr()) {
20✔
2651
            return Err(parse_res.unwrapErr());
×
2652
        }
2653
        auto parsed_args = parse_res.unwrap();
20✔
2654

2655
        log_debug("config dry run %zu %d",
20✔
2656
                  args.size(),
2657
                  prompt.p_editor.tc_popup.is_visible());
2658
        if (ec.ec_dry_run && args.size() == 2
×
2659
            && prompt.p_editor.tc_popup.is_visible())
20✔
2660
        {
2661
            prompt.p_editor.tc_popup.map_top_row(
×
2662
                [&parsed_args](const attr_line_t& al) {
×
2663
                    auto sub_opt = get_string_attr(al.al_attrs,
×
2664
                                                   lnav::prompt::SUBST_TEXT);
2665
                    if (sub_opt) {
×
2666
                        auto sub = sub_opt->get();
×
2667

2668
                        log_debug("doing dry run with popup value");
×
2669
                        auto& value_arg = parsed_args.p_args["value"];
×
2670
                        value_arg.a_help = &CONFIG_HELP.ht_parameters[1];
×
2671
                        value_arg.a_values.emplace_back(
×
2672
                            shlex::split_element_t{{}, sub});
×
2673
                    } else {
×
2674
                        log_debug("completion does not have attr");
×
2675
                    }
2676
                });
×
2677
        }
2678

2679
        yajlpp_parse_context ypc(INPUT_SRC, &lnav_config_handlers);
20✔
2680
        std::vector<lnav::console::user_message> errors, errors_ignored;
20✔
2681
        const auto& option = parsed_args.p_args["option"].a_values[0].se_value;
20✔
2682

2683
        lnav_config = rollback_lnav_config;
20✔
2684
        ypc.set_path(option)
20✔
2685
            .with_obj(lnav_config)
20✔
2686
            .with_error_reporter([&errors](const auto& ypc, auto msg) {
20✔
2687
                if (msg.um_level == lnav::console::user_message::level::error) {
×
2688
                    errors.push_back(msg);
×
2689
                }
2690
            });
×
2691
        ypc.ypc_active_paths[option] = 0;
20✔
2692
        ypc.update_callbacks();
20✔
2693

2694
        const auto* jph = ypc.ypc_current_handler;
20✔
2695

2696
        if (jph == nullptr && !ypc.ypc_handler_stack.empty()) {
20✔
2697
            jph = ypc.ypc_handler_stack.back();
2✔
2698
        }
2699

2700
        if (jph != nullptr) {
20✔
2701
            yajlpp_gen gen;
19✔
2702
            yajlpp_gen_context ygc(gen, lnav_config_handlers);
19✔
2703
            yajl_gen_config(gen, yajl_gen_beautify, 1);
19✔
2704
            ygc.with_context(ypc);
19✔
2705

2706
            if (ypc.ypc_current_handler == nullptr) {
19✔
2707
                ygc.gen();
2✔
2708
            } else {
2709
                jph->gen(ygc, gen);
17✔
2710
            }
2711

2712
            auto old_value = gen.to_string_fragment().to_string();
19✔
2713
            const auto& option_value = parsed_args.p_args["value"];
19✔
2714

2715
            if (option_value.a_values.empty()
19✔
2716
                || ypc.ypc_current_handler == nullptr)
19✔
2717
            {
2718
                lnav_config = rollback_lnav_config;
7✔
2719
                reload_config(errors);
7✔
2720

2721
                if (ec.ec_dry_run) {
7✔
2722
                    attr_line_t al(old_value);
×
2723

2724
                    lnav_data.ld_preview_view[0].set_sub_source(
×
2725
                        &lnav_data.ld_preview_source[0]);
2726
                    lnav_data.ld_preview_source[0]
2727
                        .replace_with(al)
×
2728
                        .set_text_format(detect_text_format(old_value))
×
2729
                        .truncate_to(10);
×
2730
                    lnav_data.ld_preview_status_source[0]
2731
                        .get_description()
×
2732
                        .set_value("Value of option: %s", option.c_str());
×
2733
                    lnav_data.ld_status[LNS_PREVIEW0].set_needs_update();
×
2734

2735
                    auto help_text = fmt::format(
2736
                        FMT_STRING(
×
2737
                            ANSI_BOLD("{}") " " ANSI_UNDERLINE("{}") " -- {}"),
2738
                        jph->jph_property.c_str(),
×
2739
                        jph->jph_synopsis,
×
2740
                        jph->jph_description);
×
2741

2742
                    retval = help_text;
×
2743
                } else {
×
2744
                    retval = fmt::format(
7✔
2745
                        FMT_STRING("{} = {}"), option, trim(old_value));
35✔
2746
                }
2747
            } else if (lnav_data.ld_flags.is_set<lnav_flags::secure_mode>()
12✔
2748
                       && !startswith(option, "/ui/"))
12✔
2749
            {
2750
                return ec.make_error(":config {} -- unavailable in secure mode",
2751
                                     option);
×
2752
            } else {
2753
                const auto& value = option_value.a_values[0].se_value;
12✔
2754
                bool changed = false;
12✔
2755

2756
                if (ec.ec_dry_run) {
12✔
2757
                    char help_text[1024];
2758

2759
                    snprintf(help_text,
×
2760
                             sizeof(help_text),
2761
                             ANSI_BOLD("%s %s") " -- %s",
2762
                             jph->jph_property.c_str(),
2763
                             jph->jph_synopsis,
×
2764
                             jph->jph_description);
×
2765

2766
                    retval = help_text;
×
2767
                }
2768

2769
                if (ypc.ypc_current_handler->jph_callbacks.yajl_string) {
12✔
2770
                    yajl_string_props_t props{};
8✔
2771
                    ypc.ypc_callbacks.yajl_string(
16✔
2772
                        &ypc,
2773
                        (const unsigned char*) value.c_str(),
8✔
2774
                        value.size(),
2775
                        &props);
2776
                    changed = true;
8✔
2777
                } else if (ypc.ypc_current_handler->jph_callbacks.yajl_integer)
4✔
2778
                {
2779
                    auto scan_res = scn::scan_value<int64_t>(value);
4✔
2780
                    if (!scan_res || !scan_res->range().empty()) {
4✔
2781
                        return ec.make_error("expecting an integer, found: {}",
2782
                                             value);
1✔
2783
                    }
2784
                    ypc.ypc_callbacks.yajl_integer(&ypc, scan_res->value());
3✔
2785
                    changed = true;
3✔
2786
                } else if (ypc.ypc_current_handler->jph_callbacks.yajl_boolean)
×
2787
                {
2788
                    bool bvalue = false;
×
2789

2790
                    if (strcasecmp(value.c_str(), "true") == 0) {
×
2791
                        bvalue = true;
×
2792
                    }
2793
                    ypc.ypc_callbacks.yajl_boolean(&ypc, bvalue);
×
2794
                    changed = true;
×
2795
                } else {
2796
                    return ec.make_error("unhandled type");
×
2797
                }
2798

2799
                while (!errors.empty()) {
11✔
2800
                    if (errors.back().um_level
×
2801
                        == lnav::console::user_message::level::error)
×
2802
                    {
2803
                        break;
×
2804
                    }
2805
                    errors.pop_back();
×
2806
                }
2807

2808
                if (!errors.empty()) {
11✔
2809
                    return Err(errors.back());
×
2810
                }
2811

2812
                if (changed) {
11✔
2813
                    intern_string_t path = intern_string::lookup(option);
11✔
2814

2815
                    lnav_config_locations[path]
11✔
2816
                        = ec.ec_source.back().s_location;
11✔
2817
                    reload_config(errors);
11✔
2818

2819
                    while (!errors.empty()) {
11✔
2820
                        if (errors.back().um_level
3✔
2821
                            == lnav::console::user_message::level::error)
3✔
2822
                        {
2823
                            break;
3✔
2824
                        }
2825
                        errors.pop_back();
×
2826
                    }
2827

2828
                    if (!errors.empty()) {
11✔
2829
                        lnav_config = rollback_lnav_config;
3✔
2830
                        reload_config(errors_ignored);
3✔
2831
                        return Err(errors.back());
3✔
2832
                    }
2833
                    if (!ec.ec_dry_run) {
8✔
2834
                        retval = "info: changed config option -- " + option;
8✔
2835
                        rollback_lnav_config = lnav_config;
8✔
2836
                        if (!lnav_data.ld_flags
8✔
2837
                                 .is_set<lnav_flags::secure_mode>())
8✔
2838
                        {
2839
                            save_config();
8✔
2840
                        }
2841
                    }
2842
                }
2843
            }
2844
        } else {
27✔
2845
            return ec.make_error("unknown configuration option -- {}", option);
1✔
2846
        }
2847
    } else {
40✔
2848
        return ec.make_error(
2849
            "expecting a configuration option to read or write");
×
2850
    }
2851

2852
    return Ok(retval);
15✔
2853
}
20✔
2854

2855
static Result<std::string, lnav::console::user_message>
2856
com_reset_config(exec_context& ec,
3✔
2857
                 std::string cmdline,
2858
                 std::vector<std::string>& args)
2859
{
2860
    std::string retval;
3✔
2861

2862
    if (args.size() == 1) {
3✔
2863
        return ec.make_error("expecting a configuration option to reset");
×
2864
    }
2865
    static const auto INPUT_SRC = intern_string::lookup("input");
9✔
2866

2867
    yajlpp_parse_context ypc(INPUT_SRC, &lnav_config_handlers);
3✔
2868
    std::string option = args[1];
3✔
2869

2870
    while (!option.empty() && option.back() == '/') {
4✔
2871
        option.pop_back();
1✔
2872
    }
2873
    lnav_config = rollback_lnav_config;
3✔
2874
    ypc.set_path(option).with_obj(lnav_config);
3✔
2875
    ypc.ypc_active_paths[option] = 0;
3✔
2876
    ypc.update_callbacks();
3✔
2877

2878
    if (option == "*"
3✔
2879
        || (ypc.ypc_current_handler != nullptr
5✔
2880
            || !ypc.ypc_handler_stack.empty()))
2✔
2881
    {
2882
        if (!ec.ec_dry_run) {
2✔
2883
            reset_config(option);
2✔
2884
            rollback_lnav_config = lnav_config;
2✔
2885
            if (!lnav_data.ld_flags.is_set<lnav_flags::secure_mode>()) {
2✔
2886
                save_config();
2✔
2887
            }
2888
        }
2889
        if (option == "*") {
2✔
2890
            retval = "info: reset all options";
×
2891
        } else {
2892
            retval = "info: reset option -- " + option;
2✔
2893
        }
2894
    } else {
2895
        return ec.make_error("unknown configuration option -- {}", option);
1✔
2896
    }
2897

2898
    return Ok(retval);
2✔
2899
}
3✔
2900

2901
static Result<std::string, lnav::console::user_message>
2902
com_spectrogram(exec_context& ec,
7✔
2903
                std::string cmdline,
2904
                std::vector<std::string>& args)
2905
{
2906
    std::string retval;
7✔
2907

2908
    if (ec.ec_dry_run) {
7✔
2909
        retval = "";
×
2910
    } else if (args.size() == 2) {
7✔
2911
        auto colname = remaining_args(cmdline, args);
7✔
2912
        auto& ss = *lnav_data.ld_spectro_source;
7✔
2913
        bool found = false;
7✔
2914

2915
        ss.ss_granularity = ZOOM_LEVELS[lnav_data.ld_zoom_level];
7✔
2916
        if (ss.ss_value_source != nullptr) {
7✔
2917
            delete std::exchange(ss.ss_value_source, nullptr);
×
2918
        }
2919
        ss.invalidate();
7✔
2920

2921
        if (*lnav_data.ld_view_stack.top() == &lnav_data.ld_views[LNV_DB]) {
7✔
2922
            auto dsvs = std::make_unique<db_spectro_value_source>(colname);
4✔
2923

2924
            if (dsvs->dsvs_error_msg) {
4✔
2925
                return Err(
8✔
2926
                    dsvs->dsvs_error_msg.value().with_snippets(ec.ec_source));
8✔
2927
            }
2928
            ss.ss_value_source = dsvs.release();
×
2929
            found = true;
×
2930
        } else {
4✔
2931
            auto lsvs = std::make_unique<log_spectro_value_source>(
2932
                intern_string::lookup(colname));
3✔
2933

2934
            if (!lsvs->lsvs_found) {
3✔
2935
                return ec.make_error("unknown numeric message field -- {}",
2936
                                     colname);
×
2937
            }
2938
            ss.ss_value_source = lsvs.release();
3✔
2939
            found = true;
3✔
2940
        }
3✔
2941

2942
        if (found) {
3✔
2943
            lnav_data.ld_views[LNV_SPECTRO].reload_data();
3✔
2944
            ss.text_selection_changed(lnav_data.ld_views[LNV_SPECTRO]);
3✔
2945
            ensure_view(&lnav_data.ld_views[LNV_SPECTRO]);
3✔
2946

2947
#if 0
2948
            if (lnav_data.ld_rl_view != nullptr) {
2949
                lnav_data.ld_rl_view->set_alt_value(
2950
                    HELP_MSG_2(z, Z, "to zoom in/out"));
2951
            }
2952
#endif
2953

2954
            retval = "info: visualizing field -- " + colname;
3✔
2955
        }
2956
    } else {
7✔
2957
        return ec.make_error("expecting a message field name");
×
2958
    }
2959

2960
    return Ok(retval);
3✔
2961
}
7✔
2962

2963
static Result<std::string, lnav::console::user_message>
2964
com_quit(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
×
2965
{
2966
    if (!ec.ec_dry_run) {
×
2967
        lnav_data.ld_looping = false;
×
2968
    }
2969
    return Ok(std::string());
×
2970
}
2971

2972
static void
2973
breadcrumb_prompt(std::vector<std::string>& args)
×
2974
{
2975
    set_view_mode(ln_mode_t::BREADCRUMBS);
×
2976
}
2977

2978
static void
2979
command_prompt(std::vector<std::string>& args)
×
2980
{
2981
    static auto& prompt = lnav::prompt::get();
2982
    auto* tc = *lnav_data.ld_view_stack.top();
×
2983

2984
    rollback_lnav_config = lnav_config;
×
2985
    lnav_data.ld_doc_status_source.set_title("Command Help"_frag);
×
2986
    lnav_data.ld_doc_status_source.set_description(
×
2987
        " See " ANSI_BOLD("https://docs.lnav.org/en/latest/"
2988
                          "commands.html") " for more details");
2989

2990
    set_view_mode(ln_mode_t::COMMAND);
×
2991
    lnav_data.ld_exec_context.ec_top_line = tc->get_selection().value_or(0_vl);
×
2992
    prompt.focus_for(
×
2993
        *tc, prompt.p_editor, lnav::prompt::context_t::cmd, ':', args);
×
2994

2995
    rl_set_help();
×
2996
}
2997

2998
static void
2999
script_prompt(std::vector<std::string>& args)
×
3000
{
3001
    static auto& prompt = lnav::prompt::get();
3002

3003
    auto* tc = *lnav_data.ld_view_stack.top();
×
3004

3005
    set_view_mode(ln_mode_t::EXEC);
×
3006

3007
    lnav_data.ld_exec_context.ec_top_line = tc->get_selection().value_or(0_vl);
×
3008
    prompt.focus_for(
×
3009
        *tc, prompt.p_editor, lnav::prompt::context_t::script, '|', args);
×
3010
    lnav_data.ld_bottom_source.set_prompt(
×
3011
        "Enter a script to execute: (Press " ANSI_BOLD("Esc") " to abort)");
3012
    lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
3013
}
3014

3015
static void
3016
search_prompt(std::vector<std::string>& args)
×
3017
{
3018
    static auto& prompt = lnav::prompt::get();
3019

3020
    auto* tc = *lnav_data.ld_view_stack.top();
×
3021

3022
    log_debug("search prompt");
×
3023
    set_view_mode(ln_mode_t::SEARCH);
×
3024
    lnav_data.ld_exec_context.ec_top_line = tc->get_selection().value_or(0_vl);
×
3025
    lnav_data.ld_search_start_line = tc->get_selection().value_or(0_vl);
×
3026
    prompt.focus_for(
×
3027
        *tc, prompt.p_editor, lnav::prompt::context_t::search, '/', args);
×
3028
    lnav_data.ld_doc_status_source.set_title("Syntax Help"_frag);
×
3029
    lnav_data.ld_doc_status_source.set_description("");
×
3030
    rl_set_help();
×
3031
    lnav_data.ld_bottom_source.set_prompt(
×
3032
        "Search for:  "
3033
        "(Press " ANSI_BOLD("CTRL+J") " to jump to a previous hit and "
3034
        ANSI_BOLD("Esc") " to abort)");
3035
    lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
3036
}
3037

3038
static void
3039
search_filters_prompt(std::vector<std::string>& args)
×
3040
{
3041
    static auto& prompt = lnav::prompt::get();
3042

3043
    set_view_mode(ln_mode_t::SEARCH_FILTERS);
×
3044
    auto* tc = get_textview_for_mode(lnav_data.ld_mode);
×
3045
    lnav_data.ld_filter_view.reload_data();
×
3046
    prompt.focus_for(
×
3047
        *tc, prompt.p_editor, lnav::prompt::context_t::search, '/', args);
×
3048
    lnav_data.ld_bottom_source.set_prompt(
×
3049
        "Search for:  "
3050
        "(Press " ANSI_BOLD("CTRL+J") " to jump to a previous hit and "
3051
        ANSI_BOLD("Esc") " to abort)");
3052
    lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
3053
}
3054

3055
static void
3056
search_files_prompt(std::vector<std::string>& args)
×
3057
{
3058
    static auto& prompt = lnav::prompt::get();
3059

3060
    set_view_mode(ln_mode_t::SEARCH_FILES);
×
3061
    auto* tc = get_textview_for_mode(lnav_data.ld_mode);
×
3062
    prompt.focus_for(
×
3063
        *tc, prompt.p_editor, lnav::prompt::context_t::search, '/', args);
×
3064
    lnav_data.ld_bottom_source.set_prompt(
×
3065
        "Search for:  "
3066
        "(Press " ANSI_BOLD("CTRL+J") " to jump to a previous hit and "
3067
        ANSI_BOLD("Esc") " to abort)");
3068
    lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
3069
}
3070

3071
static void
3072
search_spectro_details_prompt(std::vector<std::string>& args)
×
3073
{
3074
    static auto& prompt = lnav::prompt::get();
3075

3076
    set_view_mode(ln_mode_t::SEARCH_SPECTRO_DETAILS);
×
3077
    auto* tc = get_textview_for_mode(lnav_data.ld_mode);
×
3078
    prompt.focus_for(
×
3079
        *tc, prompt.p_editor, lnav::prompt::context_t::search, '/', args);
×
3080

3081
    lnav_data.ld_bottom_source.set_prompt(
×
3082
        "Search for:  "
3083
        "(Press " ANSI_BOLD("CTRL+J") " to jump to a previous hit and "
3084
        ANSI_BOLD("Esc") " to abort)");
3085
    lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
3086
}
3087

3088
static void
3089
sql_prompt(std::vector<std::string>& args)
×
3090
{
3091
    static auto& prompt = lnav::prompt::get();
3092

3093
    auto* tc = *lnav_data.ld_view_stack.top();
×
3094
    auto& log_view = lnav_data.ld_views[LNV_LOG];
×
3095

3096
    lnav_data.ld_exec_context.ec_top_line = tc->get_selection().value_or(0_vl);
×
3097

3098
    set_view_mode(ln_mode_t::SQL);
×
3099
    setup_logline_table(lnav_data.ld_exec_context);
×
3100
    prompt.focus_for(
×
3101
        *tc, prompt.p_editor, lnav::prompt::context_t::sql, ';', args);
×
3102

3103
    lnav_data.ld_doc_status_source.set_title("Query Help"_frag);
×
3104
    lnav_data.ld_doc_status_source.set_description(
×
3105
        "See " ANSI_BOLD("https://docs.lnav.org/en/latest/"
3106
                         "sqlext.html") " for more details");
3107
    rl_set_help();
×
3108
    lnav_data.ld_bottom_source.update_loading(0, 0);
×
3109
    lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
3110

3111
    auto* fos = (field_overlay_source*) log_view.get_overlay_source();
×
3112
    fos->fos_contexts.top().c_show = true;
×
3113
    tc->set_sync_selection_and_top(true);
×
3114
    tc->reload_data();
×
3115
    tc->set_overlay_selection(3_vl);
×
3116
    lnav_data.ld_bottom_source.set_prompt(
×
3117
        "Enter an SQL query: (Press " ANSI_BOLD(
3118
            "CTRL+L") " for multi-line mode and " ANSI_BOLD("Esc") " to "
3119
                                                                   "abort)");
3120
}
3121

3122
static void
3123
user_prompt(std::vector<std::string>& args)
×
3124
{
3125
    static auto& prompt = lnav::prompt::get();
3126

3127
    auto* tc = *lnav_data.ld_view_stack.top();
×
3128
    lnav_data.ld_exec_context.ec_top_line = tc->get_selection().value_or(0_vl);
×
3129

3130
    set_view_mode(ln_mode_t::USER);
×
3131
    setup_logline_table(lnav_data.ld_exec_context);
×
3132
    prompt.focus_for(
×
3133
        *tc, prompt.p_editor, lnav::prompt::context_t::cmd, '\0', args);
×
3134

3135
    lnav_data.ld_bottom_source.update_loading(0, 0);
×
3136
    lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
3137
}
3138

3139
static Result<std::string, lnav::console::user_message>
3140
com_prompt(exec_context& ec,
×
3141
           std::string cmdline,
3142
           std::vector<std::string>& args)
3143
{
3144
    static auto& prompt = lnav::prompt::get();
3145
    static const std::map<std::string,
3146
                          std::function<void(std::vector<std::string>&)>>
3147
        PROMPT_TYPES = {
3148
            {"breadcrumb", breadcrumb_prompt},
3149
            {"command", command_prompt},
3150
            {"script", script_prompt},
3151
            {"search", search_prompt},
3152
            {"search-filters", search_filters_prompt},
3153
            {"search-files", search_files_prompt},
3154
            {"search-spectro-details", search_spectro_details_prompt},
3155
            {"sql", sql_prompt},
3156
            {"user", user_prompt},
3157
        };
3158

3159
    if (!ec.ec_dry_run) {
×
3160
        static const intern_string_t SRC = intern_string::lookup("flags");
3161

3162
        auto lexer = shlex(cmdline);
×
3163
        auto split_args_res = lexer.split(ec.create_resolver());
×
3164
        if (split_args_res.isErr()) {
×
3165
            auto split_err = split_args_res.unwrapErr();
×
3166
            auto um
3167
                = lnav::console::user_message::error("unable to parse prompt")
×
3168
                      .with_reason(split_err.se_error.te_msg)
×
3169
                      .with_snippet(lnav::console::snippet::from(
×
3170
                          SRC, lexer.to_attr_line(split_err.se_error)))
×
3171
                      .move();
×
3172

3173
            return Err(um);
×
3174
        }
3175

3176
        auto split_args = split_args_res.unwrap()
×
3177
            | lnav::itertools::map(
×
3178
                              [](const auto& elem) { return elem.se_value; });
×
3179

3180
        auto alt_flag
3181
            = std::find(split_args.begin(), split_args.end(), "--alt");
×
3182
        prompt.p_alt_mode = alt_flag != split_args.end();
×
3183
        if (prompt.p_alt_mode) {
×
3184
            split_args.erase(alt_flag);
×
3185
        }
3186

3187
        auto prompter = PROMPT_TYPES.find(split_args[1]);
×
3188

3189
        if (prompter == PROMPT_TYPES.end()) {
×
3190
            return ec.make_error("Unknown prompt type: {}", split_args[1]);
×
3191
        }
3192

3193
        prompter->second(split_args);
×
3194
    }
3195
    return Ok(std::string());
×
3196
}
3197

3198
readline_context::command_t STD_COMMANDS[] = {
3199
    {
3200
        "prompt",
3201
        com_prompt,
3202

3203
        help_text(":prompt")
3204
            .with_summary("Open the given prompt")
3205
            .with_parameter(
3206
                help_text{"type", "The type of prompt"}.with_enum_values({
3207
                    "breadcrumb"_frag,
3208
                    "command"_frag,
3209
                    "script"_frag,
3210
                    "search"_frag,
3211
                    "sql"_frag,
3212
                }))
3213
            .with_parameter(help_text("--alt",
3214
                                      "Perform the alternate action "
3215
                                      "for this prompt by default")
3216
                                .with_format(help_parameter_format_t::HPF_NONE)
3217
                                .optional())
3218
            .with_parameter(
3219
                help_text("prompt", "The prompt to display").optional())
3220
            .with_parameter(
3221
                help_text("initial-value",
3222
                          "The initial value to fill in for the prompt")
3223
                    .optional())
3224
            .with_example({
3225
                "To open the command prompt with 'filter-in' already filled in",
3226
                "command : 'filter-in '",
3227
            })
3228
            .with_example({
3229
                "To ask the user a question",
3230
                "user 'Are you sure? '",
3231
            }),
3232
    },
3233
    {
3234
        "add-source-path",
3235
        com_add_src_path,
3236
        help_text(":add-source-path")
3237
            .with_summary(
3238
                "Add a path to the source code that generated log messages.  "
3239
                "Adding source allows lnav to more accurately extract values "
3240
                "from log messages")
3241
            .with_parameter(
3242
                help_text("path")
3243
                    .with_summary("The path to the source code to index")
3244
                    .with_format(help_parameter_format_t::HPF_DIRECTORY)
3245
                    .one_or_more()),
3246
    },
3247

3248
    {
3249
        "adjust-log-time",
3250
        com_adjust_log_time,
3251

3252
        help_text(":adjust-log-time")
3253
            .with_summary(
3254
                "Change the timestamps of the focused file to be relative "
3255
                "to the given date")
3256
            .with_parameter(
3257
                help_text("timestamp",
3258
                          "The new timestamp for the focused line in the view")
3259
                    .with_format(help_parameter_format_t::HPF_ADJUSTED_TIME))
3260
            .with_example({"To set the focused timestamp to a given date",
3261
                           "2017-01-02T05:33:00"})
3262
            .with_example({"To set the focused timestamp back an hour", "-1h"})
3263
            .with_opposites({"clear-adjusted-log-time"}),
3264
    },
3265
    {
3266
        "clear-adjusted-log-time",
3267
        com_clear_adjusted_log_time,
3268

3269
        help_text(":clear-adjusted-log-time")
3270
            .with_summary(
3271
                "Clear the adjusted time for the focused line in the view")
3272
            .with_opposites({":adjust-log-time"}),
3273
    },
3274

3275
    {
3276
        "unix-time",
3277
        com_unix_time,
3278

3279
        help_text(":unix-time")
3280
            .with_summary("Convert epoch time to a human-readable form")
3281
            .with_parameter(
3282
                help_text("seconds", "The epoch timestamp to convert")
3283
                    .with_format(help_parameter_format_t::HPF_INTEGER))
3284
            .with_example(
3285
                {"To convert the epoch time 1490191111", "1490191111"}),
3286
    },
3287
    {
3288
        "convert-time-to",
3289
        com_convert_time_to,
3290
        help_text(":convert-time-to")
3291
            .with_summary("Convert the focused timestamp to the "
3292
                          "given timezone")
3293
            .with_parameter(
3294
                help_text("zone", "The timezone name")
3295
                    .with_format(help_parameter_format_t::HPF_TIMEZONE)),
3296
    },
3297
    {
3298
        "set-file-timezone",
3299
        com_set_file_timezone,
3300
        help_text(":set-file-timezone")
3301
            .with_summary("Set the timezone to use for log messages that do "
3302
                          "not include a timezone.  The timezone is applied "
3303
                          "to "
3304
                          "the focused file or the given glob pattern.")
3305
            .with_parameter(help_text{"zone", "The timezone name"}.with_format(
3306
                help_parameter_format_t::HPF_TIMEZONE))
3307
            .with_parameter(help_text{"pattern",
3308
                                      "The glob pattern to match against "
3309
                                      "files that should use this timezone"}
3310
                                .optional())
3311
            .with_tags({"file-options"}),
3312
        com_set_file_timezone_prompt,
3313
    },
3314
    {
3315
        "clear-file-timezone",
3316
        com_clear_file_timezone,
3317
        help_text(":clear-file-timezone")
3318
            .with_summary("Clear the timezone setting for the "
3319
                          "focused file or "
3320
                          "the given glob pattern.")
3321
            .with_parameter(
3322
                help_text{"pattern",
3323
                          "The glob pattern to match against files "
3324
                          "that should no longer use this timezone"}
3325
                    .with_format(help_parameter_format_t::HPF_FILE_WITH_ZONE))
3326
            .with_tags({"file-options"}),
3327
        com_clear_file_timezone_prompt,
3328
    },
3329
    {"current-time",
3330
     com_current_time,
3331

3332
     help_text(":current-time")
3333
         .with_summary("Print the current time in human-readable form and "
3334
                       "seconds since the epoch")},
3335
    {
3336
        "goto",
3337
        com_goto,
3338

3339
        help_text(":goto")
3340
            .with_summary("Go to the given location in the top view")
3341
            .with_parameter(
3342
                help_text("line#|N%|timestamp|#anchor",
3343
                          "A line number, percent into the file, timestamp, "
3344
                          "or an anchor in a text file")
3345
                    .with_format(help_parameter_format_t::HPF_LOCATION))
3346
            .with_examples(
3347
                {{"To go to line 22", "22"},
3348
                 {"To go to the line 75% of the way into the view", "75%"},
3349
                 {"To go to the first message on the first day of "
3350
                  "2017",
3351
                  "2017-01-01"},
3352
                 {"To go to the Screenshots section", "#screenshots"}})
3353
            .with_tags({"navigation"}),
3354
    },
3355
    {
3356
        "relative-goto",
3357
        com_relative_goto,
3358
        help_text(":relative-goto")
3359
            .with_summary(
3360
                "Move the current view up or down by the given amount")
3361
            .with_parameter(
3362
                {"line-count|N%", "The amount to move the view by."})
3363
            .with_examples({
3364
                {"To move 22 lines down in the view", "+22"},
3365
                {"To move 10 percent back in the view", "-10%"},
3366
            })
3367
            .with_tags({"navigation"}),
3368
    },
3369

3370
    {
3371
        "mark-expr",
3372
        com_mark_expr,
3373

3374
        help_text(":mark-expr")
3375
            .with_summary("Set the bookmark expression")
3376
            .with_parameter(
3377
                help_text("expr",
3378
                          "The SQL expression to evaluate for each "
3379
                          "log message.  "
3380
                          "The message values can be accessed "
3381
                          "using column names "
3382
                          "prefixed with a colon")
3383
                    .with_format(help_parameter_format_t::HPF_SQL_EXPR))
3384
            .with_opposites({"clear-mark-expr"})
3385
            .with_tags({"bookmarks"})
3386
            .with_example({"To mark lines from 'dhclient' that "
3387
                           "mention 'eth0'",
3388
                           ":log_procname = 'dhclient' AND "
3389
                           ":log_body LIKE '%eth0%'"}),
3390

3391
        com_mark_expr_prompt,
3392
    },
3393
    {"clear-mark-expr",
3394
     com_clear_mark_expr,
3395

3396
     help_text(":clear-mark-expr")
3397
         .with_summary("Clear the mark expression")
3398
         .with_opposites({"mark-expr"})
3399
         .with_tags({"bookmarks"})},
3400
    {"next-location",
3401
     com_goto_location,
3402

3403
     help_text(":next-location")
3404
         .with_summary("Move to the next position in the location history")
3405
         .with_tags({"navigation"})},
3406
    {"prev-location",
3407
     com_goto_location,
3408

3409
     help_text(":prev-location")
3410
         .with_summary("Move to the previous position in the "
3411
                       "location history")
3412
         .with_tags({"navigation"})},
3413

3414
    {
3415
        "next-section",
3416
        com_next_section,
3417

3418
        help_text(":next-section")
3419
            .with_summary("Move to the next section in the document")
3420
            .with_tags({"navigation"}),
3421
    },
3422
    {
3423
        "prev-section",
3424
        com_prev_section,
3425

3426
        help_text(":prev-section")
3427
            .with_summary("Move to the previous section in the document")
3428
            .with_tags({"navigation"}),
3429
    },
3430

3431
    {
3432
        "help",
3433
        com_help,
3434

3435
        help_text(":help").with_summary("Open the help text view"),
3436
    },
3437
    {
3438
        "hide-unmarked-lines",
3439
        com_hide_unmarked,
3440

3441
        help_text(":hide-unmarked-lines")
3442
            .with_summary("Hide lines that have not been bookmarked")
3443
            .with_tags({"filtering", "bookmarks"}),
3444
    },
3445
    {
3446
        "show-unmarked-lines",
3447
        com_show_unmarked,
3448

3449
        help_text(":show-unmarked-lines")
3450
            .with_summary("Show lines that have not been bookmarked")
3451
            .with_opposites({"show-unmarked-lines"})
3452
            .with_tags({"filtering", "bookmarks"}),
3453
    },
3454
    {
3455
        "highlight",
3456
        com_highlight,
3457

3458
        help_text(":highlight")
3459
            .with_summary("Add coloring to log messages fragments "
3460
                          "that match the "
3461
                          "given regular expression")
3462
            .with_parameter(
3463
                help_text("pattern", "The regular expression to match")
3464
                    .with_format(help_parameter_format_t::HPF_REGEX))
3465
            .with_tags({"display"})
3466
            .with_opposites({"clear-highlight"})
3467
            .with_example({"To highlight numbers with three or more digits",
3468
                           R"(\d{3,})"}),
3469
    },
3470
    {
3471
        "highlight-field",
3472
        com_highlight_field,
3473

3474
        help_text(":highlight-field")
3475
            .with_summary("Highlight a field that matches the given pattern")
3476
            .with_parameter(
3477
                help_text("--color", "The foreground color to apply")
3478
                    .with_format(help_parameter_format_t::HPF_STRING)
3479
                    .optional())
3480
            .with_parameter(help_text("--bold", "Make the text bold")
3481
                                .with_format(help_parameter_format_t::HPF_NONE)
3482
                                .optional())
3483
            .with_parameter(help_text("--underline", "Underline the text")
3484
                                .with_format(help_parameter_format_t::HPF_NONE)
3485
                                .optional())
3486
            .with_parameter(help_text("--italic", "Italicize the text")
3487
                                .with_format(help_parameter_format_t::HPF_NONE)
3488
                                .optional())
3489
            .with_parameter(help_text("--strike", "Strikethrough the text")
3490
                                .with_format(help_parameter_format_t::HPF_NONE)
3491
                                .optional())
3492
            .with_parameter(help_text("--blink", "Make the text blink")
3493
                                .with_format(help_parameter_format_t::HPF_NONE)
3494
                                .optional())
3495
            .with_parameter(
3496
                help_text("field", "The name of the field to highlight")
3497
                    .with_format(help_parameter_format_t::HPF_FORMAT_FIELD))
3498
            .with_parameter(
3499
                help_text("pattern", "The regular expression to match")
3500
                    .with_format(help_parameter_format_t::HPF_REGEX)
3501
                    .optional())
3502
            .with_tags({"display"})
3503
            .with_opposites({"clear-highlight-field"})
3504
            .with_example({"To color status values that start with '2' green",
3505
                           R"(--color=green sc_status ^2.*)"}),
3506
    },
3507
    {
3508
        "clear-highlight-field",
3509
        com_clear_highlight_field,
3510

3511
        help_text(":clear-highlight-field")
3512
            .with_summary("Remove a field highlight")
3513
            .with_parameter(
3514
                help_text("field", "The name of highlighted field")
3515
                    .with_format(
3516
                        help_parameter_format_t::HPF_HIGHLIGHTED_FIELD))
3517
            .with_tags({"display"})
3518
            .with_opposites({"highlight-field"})
3519
            .with_example({"To clear the highlights for the 'sc_status' field",
3520
                           "sc_status"}),
3521
    },
3522
    {
3523
        "clear-highlight",
3524
        com_clear_highlight,
3525

3526
        help_text(":clear-highlight")
3527
            .with_summary(
3528
                "Remove a previously set highlight regular expression")
3529
            .with_parameter(
3530
                help_text("pattern",
3531
                          "The regular expression previously used "
3532
                          "with :highlight")
3533
                    .with_format(help_parameter_format_t::HPF_HIGHLIGHTS))
3534
            .with_tags({"display"})
3535
            .with_opposites({"highlight"})
3536
            .with_example(
3537
                {"To clear the highlight with the pattern 'foobar'", "foobar"}),
3538
    },
3539
    {
3540
        "filter-expr",
3541
        com_filter_expr,
3542

3543
        help_text(":filter-expr")
3544
            .with_summary("Set the filter expression")
3545
            .with_parameter(
3546
                help_text("expr",
3547
                          "The SQL expression to evaluate for each "
3548
                          "log message.  "
3549
                          "The message values can be accessed "
3550
                          "using column names "
3551
                          "prefixed with a colon")
3552
                    .with_format(help_parameter_format_t::HPF_SQL_EXPR))
3553
            .with_opposites({"clear-filter-expr"})
3554
            .with_tags({"filtering"})
3555
            .with_example({"To set a filter expression that matched syslog "
3556
                           "messages from 'syslogd'",
3557
                           ":log_procname = 'syslogd'"})
3558
            .with_example(
3559
                {"To set a filter expression that matches log "
3560
                 "messages where 'id' is followed by a number and contains the "
3561
                 "string 'foo'",
3562
                 ":log_body REGEXP 'id\\d+' AND :log_body REGEXP 'foo'"}),
3563

3564
        com_filter_expr_prompt,
3565
    },
3566
    {"clear-filter-expr",
3567
     com_clear_filter_expr,
3568

3569
     help_text(":clear-filter-expr")
3570
         .with_summary("Clear the filter expression")
3571
         .with_opposites({"filter-expr"})
3572
         .with_tags({"filtering"})},
3573
    {"enable-word-wrap",
3574
     com_enable_word_wrap,
3575

3576
     help_text(":enable-word-wrap")
3577
         .with_summary("Enable word-wrapping for the current view")
3578
         .with_tags({"display"})},
3579
    {"disable-word-wrap",
3580
     com_disable_word_wrap,
3581

3582
     help_text(":disable-word-wrap")
3583
         .with_summary("Disable word-wrapping for the current view")
3584
         .with_opposites({"enable-word-wrap"})
3585
         .with_tags({"display"})},
3586
    {"create-logline-table",
3587
     com_create_logline_table,
3588

3589
     help_text(":create-logline-table")
3590
         .with_summary("Create an SQL table using the focused line of "
3591
                       "the log view "
3592
                       "as a template")
3593
         .with_parameter(help_text("table-name", "The name for the new table"))
3594
         .with_tags({"vtables", "sql"})
3595
         .with_example({"To create a logline-style table named "
3596
                        "'task_durations'",
3597
                        "task_durations"})},
3598
    {"delete-logline-table",
3599
     com_delete_logline_table,
3600

3601
     help_text(":delete-logline-table")
3602
         .with_summary("Delete a table created with create-logline-table")
3603
         .with_parameter(
3604
             help_text("table-name", "The name of the table to delete")
3605
                 .with_format(help_parameter_format_t::HPF_LOGLINE_TABLE))
3606
         .with_opposites({"delete-logline-table"})
3607
         .with_tags({"vtables", "sql"})
3608
         .with_example({"To delete the logline-style table named "
3609
                        "'task_durations'",
3610
                        "task_durations"})},
3611
    {"create-search-table",
3612
     com_create_search_table,
3613

3614
     help_text(":create-search-table")
3615
         .with_summary("Create an SQL table based on a regex search")
3616
         .with_parameter(
3617
             help_text("table-name", "The name of the table to create"))
3618
         .with_parameter(
3619
             help_text("pattern",
3620
                       "The regular expression used to capture the table "
3621
                       "columns.  "
3622
                       "If not given, the current search pattern is "
3623
                       "used.")
3624
                 .optional()
3625
                 .with_format(help_parameter_format_t::HPF_REGEX))
3626
         .with_tags({"vtables", "sql"})
3627
         .with_example({"To create a table named 'task_durations' that "
3628
                        "matches log "
3629
                        "messages with the pattern "
3630
                        "'duration=(?<duration>\\d+)'",
3631
                        R"(task_durations duration=(?<duration>\d+))"})},
3632
    {"delete-search-table",
3633
     com_delete_search_table,
3634

3635
     help_text(":delete-search-table")
3636
         .with_summary("Delete a search table")
3637
         .with_parameter(
3638
             help_text("table-name", "The name of the table to delete")
3639
                 .one_or_more()
3640
                 .with_format(help_parameter_format_t::HPF_SEARCH_TABLE))
3641
         .with_opposites({"create-search-table"})
3642
         .with_tags({"vtables", "sql"})
3643
         .with_example({"To delete the search table named 'task_durations'",
3644
                        "task_durations"})},
3645
    {
3646
        "hide-file",
3647
        com_file_visibility,
3648

3649
        help_text(":hide-file")
3650
            .with_summary("Hide the given file(s) and skip indexing until it "
3651
                          "is shown again.  If no path is given, the current "
3652
                          "file in the view is hidden")
3653
            .with_parameter(
3654
                help_text{"path",
3655
                          "A path or glob pattern that "
3656
                          "specifies the files to hide"}
3657
                    .with_format(help_parameter_format_t::HPF_VISIBLE_FILES)
3658
                    .zero_or_more())
3659
            .with_opposites({"show-file"}),
3660
    },
3661
    {
3662
        "show-file",
3663
        com_file_visibility,
3664

3665
        help_text(":show-file")
3666
            .with_summary("Show the given file(s) and resume indexing.")
3667
            .with_parameter(
3668
                help_text{"path",
3669
                          "The path or glob pattern that "
3670
                          "specifies the files to show"}
3671
                    .with_format(help_parameter_format_t::HPF_HIDDEN_FILES)
3672
                    .zero_or_more())
3673
            .with_opposites({"hide-file"}),
3674
    },
3675
    {
3676
        "show-only-this-file",
3677
        com_file_visibility,
3678

3679
        help_text(":show-only-this-file")
3680
            .with_summary("Show only the file for the focused line in the view")
3681
            .with_opposites({"hide-file"}),
3682
    },
3683
    {"session",
3684
     com_session,
3685

3686
     help_text(":session")
3687
         .with_summary("Add the given command to the session file "
3688
                       "(~/.lnav/session)")
3689
         .with_parameter(help_text("lnav-command", "The lnav command to save."))
3690
         .with_example({"To add the command ':highlight foobar' to "
3691
                        "the session file",
3692
                        ":highlight foobar"})},
3693
    {
3694
        "summarize",
3695
        com_summarize,
3696

3697
        help_text(":summarize")
3698
            .with_summary("Execute a SQL query that computes the "
3699
                          "characteristics "
3700
                          "of the values in the given column")
3701
            .with_parameter(
3702
                help_text("column-name", "The name of the column to analyze.")
3703
                    .with_format(help_parameter_format_t::HPF_FORMAT_FIELD))
3704
            .with_example({"To get a summary of the sc_bytes column in the "
3705
                           "access_log table",
3706
                           "sc_bytes"}),
3707
    },
3708
    {"switch-to-view",
3709
     com_switch_to_view,
3710

3711
     help_text(":switch-to-view")
3712
         .with_summary("Switch to the given view")
3713
         .with_parameter(
3714
             help_text("view-name", "The name of the view to switch to.")
3715
                 .with_enum_values(lnav_view_strings))
3716
         .with_example({"To switch to the 'schema' view", "schema"})},
3717
    {"toggle-view",
3718
     com_switch_to_view,
3719

3720
     help_text(":toggle-view")
3721
         .with_summary("Switch to the given view or, if it is "
3722
                       "already displayed, "
3723
                       "switch to the previous view")
3724
         .with_parameter(
3725
             help_text("view-name",
3726
                       "The name of the view to toggle the display of.")
3727
                 .with_enum_values(lnav_view_strings))
3728
         .with_example({"To switch to the 'schema' view if it is "
3729
                        "not displayed "
3730
                        "or switch back to the previous view",
3731
                        "schema"})},
3732
    {"toggle-filtering",
3733
     com_toggle_filtering,
3734

3735
     help_text(":toggle-filtering")
3736
         .with_summary("Toggle the filtering flag for the current view")
3737
         .with_tags({"filtering"})},
3738
    {"reset-session",
3739
     com_reset_session,
3740

3741
     help_text(":reset-session")
3742
         .with_summary("Reset the session state, clearing all filters, "
3743
                       "highlights, and bookmarks")},
3744
    {"load-session",
3745
     com_load_session,
3746

3747
     help_text(":load-session").with_summary("Load the latest session state")},
3748
    {"save-session",
3749
     com_save_session,
3750

3751
     help_text(":save-session")
3752
         .with_summary("Save the current state as a session")},
3753
    {
3754
        "set-min-log-level",
3755
        com_set_min_log_level,
3756

3757
        help_text(":set-min-log-level")
3758
            .with_summary(
3759
                "Set the minimum log level to display in the log view")
3760
            .with_parameter(help_text("log-level", "The new minimum log level")
3761
                                .with_enum_values(level_names))
3762
            .with_example(
3763
                {"To set the minimum log level displayed to error", "error"}),
3764
    },
3765
    {"redraw",
3766
     com_redraw,
3767

3768
     help_text(":redraw").with_summary("Do a full redraw of the screen")},
3769
    {
3770
        "zoom-to",
3771
        com_zoom_to,
3772

3773
        help_text(":zoom-to")
3774
            .with_summary("Zoom the histogram view to the given level")
3775
            .with_parameter(help_text("zoom-level", "The zoom level")
3776
                                .with_enum_values(lnav_zoom_strings))
3777
            .with_example({"To set the zoom level to '1-week'", "1-week"}),
3778
    },
3779
    {
3780
        "config",
3781
        com_config,
3782
        CONFIG_HELP,
3783
    },
3784
    {"reset-config",
3785
     com_reset_config,
3786

3787
     help_text(":reset-config")
3788
         .with_summary("Reset the configuration option to its default value")
3789
         .with_parameter(
3790
             help_text("option", "The path to the option to reset")
3791
                 .with_format(help_parameter_format_t::HPF_CONFIG_PATH))
3792
         .with_example({"To reset the '/ui/clock-format' option back to the "
3793
                        "builtin default",
3794
                        "/ui/clock-format"})
3795
         .with_tags({"configuration"})},
3796
    {
3797
        "spectrogram",
3798
        com_spectrogram,
3799

3800
        help_text(":spectrogram")
3801
            .with_summary(
3802
                "Visualize the given message field or database column "
3803
                "using a spectrogram")
3804
            .with_parameter(
3805
                help_text("field-name",
3806
                          "The name of the numeric field to visualize.")
3807
                    .with_format(help_parameter_format_t::HPF_NUMERIC_FIELD))
3808
            .with_example({"To visualize the sc_bytes field in the "
3809
                           "access_log format",
3810
                           "sc_bytes"}),
3811
    },
3812
    {
3813
        "quit",
3814
        com_quit,
3815

3816
        help_text(":quit").with_summary("Quit lnav"),
3817
    },
3818
    {
3819
        "write-debug-log-to",
3820
        com_write_debug_log_to,
3821
        help_text(":write-debug-log-to")
3822
            .with_summary(
3823
                "Write lnav's internal debug log to the given path.  This can "
3824
                "be useful if the `-d` flag was not passed on the command line")
3825
            .with_parameter(
3826
                help_text("path", "The destination path for the debug log")
3827
                    .with_format(help_parameter_format_t::HPF_LOCAL_FILENAME)),
3828
    },
3829
};
3830

3831
static Result<std::string, lnav::console::user_message>
3832
com_crash(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
×
3833
{
3834
    if (args.empty()) {
×
3835
    } else if (!ec.ec_dry_run) {
×
3836
        int* nums = nullptr;
×
3837

3838
        return ec.make_error(FMT_STRING("oops... {}"), nums[0]);
×
3839
    }
3840
    return Ok(std::string());
×
3841
}
3842

3843
void
3844
init_lnav_commands(readline_context::command_map_t& cmd_map)
633✔
3845
{
3846
    for (auto& cmd : STD_COMMANDS) {
32,916✔
3847
        cmd.c_help.index_tags();
32,283✔
3848
        cmd_map[cmd.c_name] = &cmd;
32,283✔
3849
    }
3850
    cmd_map["q"_frag] = cmd_map["q!"_frag] = cmd_map["quit"_frag];
633✔
3851

3852
    if (getenv("LNAV_SRC") != nullptr) {
633✔
3853
        static readline_context::command_t add_test(com_add_test);
3854

3855
        cmd_map["add-test"_frag] = &add_test;
×
3856
    }
3857
    if (getenv("lnav_test") != nullptr) {
633✔
3858
        static readline_context::command_t shexec(com_shexec),
631✔
3859
            poll_now(com_poll_now), test_comment(com_test_comment),
631✔
3860
            crasher(com_crash);
631✔
3861

3862
        cmd_map["shexec"_frag] = &shexec;
631✔
3863
        cmd_map["poll-now"_frag] = &poll_now;
631✔
3864
        cmd_map["test-comment"_frag] = &test_comment;
631✔
3865
        cmd_map["crash"_frag] = &crasher;
631✔
3866
    }
3867
}
633✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc