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

tstack / lnav / 19766971443-2719

28 Nov 2025 02:35PM UTC coverage: 68.93% (+0.08%) from 68.854%
19766971443-2719

push

github

tstack
[vtab] add search table to exported sessions

Related to #1589

103 of 129 new or added lines in 6 files covered. (79.84%)

51282 of 74397 relevant lines covered (68.93%)

434276.45 hits per line

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

78.75
/src/session.export.cc
1
/**
2
 * Copyright (c) 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 "session.export.hh"
31

32
#include "base/injector.hh"
33
#include "base/itertools.hh"
34
#include "bound_tags.hh"
35
#include "lnav.hh"
36
#include "sqlitepp.client.hh"
37
#include "sqlitepp.hh"
38
#include "textview_curses.hh"
39

40
struct log_message_session_state {
41
    int64_t lmss_time_msecs;
42
    std::string lmss_format;
43
    bool lmss_mark;
44
    std::optional<std::string> lmss_comment;
45
    std::optional<std::string> lmss_tags;
46
    std::optional<std::string> lmss_annotations;
47
    std::optional<std::string> lmss_opid;
48
    std::string lmss_hash;
49
};
50

51
template<>
52
struct from_sqlite<log_message_session_state> {
53
    log_message_session_state operator()(int argc,
3✔
54
                                         sqlite3_value** argv,
55
                                         int argi)
56
    {
57
        return {
58
            from_sqlite<int64_t>()(argc, argv, argi + 0),
3✔
59
            from_sqlite<std::string>()(argc, argv, argi + 1),
×
60
            from_sqlite<bool>()(argc, argv, argi + 2),
×
61
            from_sqlite<std::optional<std::string>>()(argc, argv, argi + 3),
×
62
            from_sqlite<std::optional<std::string>>()(argc, argv, argi + 4),
×
63
            from_sqlite<std::optional<std::string>>()(argc, argv, argi + 5),
×
64
            from_sqlite<std::optional<std::string>>()(argc, argv, argi + 6),
×
65
            from_sqlite<std::string>()(argc, argv, argi + 7),
3✔
66
        };
3✔
67
    }
3✔
68
};
69

70
struct log_filter_session_state {
71
    std::string lfss_name;
72
    bool lfss_enabled;
73
    std::string lfss_type;
74
    std::string lfss_language;
75
    std::string lfss_pattern;
76
};
77

78
template<>
79
struct from_sqlite<log_filter_session_state> {
80
    log_filter_session_state operator()(int argc,
4✔
81
                                        sqlite3_value** argv,
82
                                        int argi)
83
    {
84
        return {
85
            from_sqlite<std::string>()(argc, argv, argi + 0),
4✔
86
            from_sqlite<bool>()(argc, argv, argi + 1),
×
87
            from_sqlite<std::string>()(argc, argv, argi + 2),
×
88
            from_sqlite<std::string>()(argc, argv, argi + 3),
×
89
            from_sqlite<std::string>()(argc, argv, argi + 4),
4✔
90
        };
4✔
91
    }
4✔
92
};
93

94
struct log_file_session_state {
95
    std::string lfss_content_id;
96
    std::string lfss_format;
97
    int64_t lfss_time_offset;
98
};
99

100
template<>
101
struct from_sqlite<log_file_session_state> {
102
    log_file_session_state operator()(int argc, sqlite3_value** argv, int argi)
×
103
    {
104
        return {
105
            from_sqlite<std::string>()(argc, argv, argi + 0),
×
106
            from_sqlite<std::string>()(argc, argv, argi + 1),
×
107
            from_sqlite<int64_t>()(argc, argv, argi + 2),
×
108
        };
109
    }
110
};
111

112
namespace lnav::session {
113

114
static std::optional<std::filesystem::path>
115
find_container_dir(std::filesystem::path file_path)
6✔
116
{
117
    if (!std::filesystem::exists(file_path)) {
6✔
118
        return std::nullopt;
×
119
    }
120

121
    std::optional<std::filesystem::path> dir_with_last_readme;
6✔
122

123
    while (file_path.has_parent_path()
6✔
124
           && file_path != file_path.root_directory())
14✔
125
    {
126
        auto parent = file_path.parent_path();
14✔
127
        bool has_readme_entry = false;
14✔
128
        std::error_code ec;
14✔
129

130
        for (const auto& entry :
14✔
131
             std::filesystem::directory_iterator(parent, ec))
37,156✔
132
        {
133
            if (!entry.is_regular_file()) {
18,571✔
134
                continue;
184✔
135
            }
136

137
            auto entry_filename = tolower(entry.path().filename().string());
18,387✔
138
            if (startswith(entry_filename, "readme")) {
18,387✔
139
                has_readme_entry = true;
8✔
140
                dir_with_last_readme = parent;
8✔
141
            }
142
        }
18,401✔
143
        if (!has_readme_entry && dir_with_last_readme) {
14✔
144
            return dir_with_last_readme;
6✔
145
        }
146

147
        file_path = parent;
8✔
148
    }
14✔
149

150
    return std::nullopt;
×
151
}
6✔
152

153
static std::string
154
replace_home_dir(std::string path)
2✔
155
{
156
    const auto home_dir_opt = getenv_opt("HOME");
2✔
157

158
    if (!home_dir_opt) {
2✔
159
        return path;
×
160
    }
161

162
    const auto* home_dir = home_dir_opt.value();
2✔
163

164
    if (startswith(path, home_dir)) {
2✔
165
        auto retval = path.substr(strlen(home_dir));
×
166

167
        if (retval.front() != '/') {
×
168
            retval.insert(0, "/");
×
169
        }
170
        retval.insert(0, "$HOME");
×
171
        return retval;
×
172
    }
173

174
    return path;
2✔
175
}
176

177
Result<void, lnav::console::user_message>
178
export_to(FILE* file)
6✔
179
{
180
    static auto& lnav_db = injector::get<auto_sqlite3&>();
6✔
181

182
    static const char* BOOKMARK_QUERY = R"(
183
SELECT log_time_msecs, log_format, log_mark, log_comment, log_tags, log_annotations, log_user_opid, log_line_hash
184
   FROM all_logs
185
   WHERE log_mark = 1 OR
186
         log_comment IS NOT NULL OR
187
         log_tags IS NOT NULL OR
188
         log_annotations IS NOT NULL OR
189
         (log_user_opid IS NOT NULL AND log_user_opid != '')
190
)";
191

192
    static const char* FILTER_QUERY = R"(
193
SELECT view_name, enabled, type, language, pattern FROM lnav_view_filters
194
)";
195

196
    static const char* FILE_QUERY = R"(
197
SELECT content_id, format, time_offset FROM lnav_file
198
  WHERE format IS NOT NULL AND time_offset != 0
199
)";
200

201
    static constexpr char HEADER[] = R"(#!lnav -Nf
202
# This file is an export of an lnav session.  You can type
203
# '|/path/to/this/file' in lnav to execute this file and
204
# restore the state of the session.
205

206
;SELECT raise_error('This session export was made with a newer version of lnav, please upgrade to ' || {0} || ' or later')
207
   WHERE lnav_version() < {0} COLLATE naturalcase
208

209
# The files loaded into the session were:
210

211
)";
212

213
    static constexpr char LOG_DIR_INSERT[] = R"(
214
# Set this environment variable to override this value or edit this script.
215
;INSERT OR IGNORE INTO environ (name, value) VALUES ('LOG_DIR_{}', {})
216
)";
217

218
    static constexpr char MARK_HEADER[] = R"(
219

220
# The following SQL statements will restore the bookmarks,
221
# comments, and tags that were added in the session.
222

223
;SELECT total_changes() AS before_mark_changes
224
)";
225

226
    static constexpr char MARK_FOOTER[] = R"(
227
;SELECT {} - (total_changes() - $before_mark_changes) AS failed_mark_changes
228
;SELECT echoln(printf('%sERROR%s: failed to restore %d bookmarks',
229
                      $ansi_red, $ansi_norm, $failed_mark_changes))
230
    WHERE $failed_mark_changes != 0
231
)";
232

233
    static const char* FILTER_HEADER = R"(
234

235
# The following SQL statements will restore the filters that
236
# were added in the session.
237

238
)";
239

240
    static const char* FILE_HEADER = R"(
241

242
# The following SQL statements will restore the state of the
243
# files in the session.
244

245
;SELECT total_changes() AS before_file_changes
246
)";
247

248
    static const char* FIELD_HEADER = R"(
249
# The following field visibility commands were run by the
250
# original user during this session.  Uncomment them if
251
# desired.
252
)";
253

254
    static const char* HIGHLIGHT_HEADER = R"(
255
# The following highlight commands were run by the
256
# original user during this session.  Uncomment them if
257
# desired.
258
)";
259

260
    static constexpr char FILE_FOOTER[] = R"(
261
;SELECT {} - (total_changes() - $before_file_changes) AS failed_file_changes
262
;SELECT echoln(printf('%sERROR%s: failed to restore the state of %d files',
263
                      $ansi_red, $ansi_norm, $failed_file_changes))
264
   WHERE $failed_file_changes != 0
265
)";
266

267
    static constexpr char VIEW_HEADER[] = R"(
268

269
# The following commands will restore the state of the {} view.
270

271
)";
272

273
    auto prep_mark_res = prepare_stmt(lnav_db.in(), BOOKMARK_QUERY);
6✔
274
    if (prep_mark_res.isErr()) {
6✔
275
        return Err(
×
276
            console::user_message::error("unable to export log bookmarks")
×
277
                .with_reason(prep_mark_res.unwrapErr()));
×
278
    }
279

280
    fmt::print(file, FMT_STRING(HEADER), sqlitepp::quote(PACKAGE_VERSION).in());
30✔
281

282
    std::map<std::string, std::vector<std::string>> file_containers;
6✔
283
    std::set<std::string> raw_files;
6✔
284
    for (const auto& name_pair : lnav_data.ld_active_files.fc_file_names) {
13✔
285
        const auto& open_opts = name_pair.second;
7✔
286

287
        if (!open_opts.loo_is_visible || !open_opts.loo_include_in_session
7✔
288
            || !open_opts.loo_filename.empty()
6✔
289
            || open_opts.loo_source != logfile_name_source::USER)
14✔
290
        {
291
            continue;
2✔
292
        }
293

294
        const auto& file_path_str = name_pair.first;
5✔
295
        auto file_path = std::filesystem::path(file_path_str);
5✔
296
        auto container_path_opt = find_container_dir(file_path);
5✔
297
        if (container_path_opt) {
5✔
298
            auto container_parent = container_path_opt.value().parent_path();
5✔
299
            auto file_container_path
300
                = std::filesystem::relative(file_path, container_parent)
10✔
301
                      .string();
5✔
302
            file_containers[container_parent.string()].push_back(
5✔
303
                file_container_path);
304
        } else {
5✔
305
            raw_files.insert(file_path_str);
×
306
        }
307
    }
5✔
308
    for (const auto& lf : lnav_data.ld_active_files.fc_files) {
13✔
309
        if (lf->is_valid_filename()) {
7✔
310
            continue;
5✔
311
        }
312
        if (!lf->get_open_options().loo_include_in_session) {
2✔
313
            continue;
×
314
        }
315

316
        const auto& open_options = lf->get_open_options();
2✔
317
        if (open_options.loo_piper) {
2✔
318
            raw_files.emplace(open_options.loo_piper->get_url());
2✔
319
        }
320
    }
321
    for (const auto& file_path_str : raw_files) {
8✔
322
        fmt::print(
2✔
323
            file, FMT_STRING(":open {}\n"), replace_home_dir(file_path_str));
8✔
324
    }
325
    size_t container_index = 0;
6✔
326
    for (const auto& container_pair : file_containers) {
10✔
327
        fmt::print(file,
4✔
328
                   FMT_STRING(LOG_DIR_INSERT),
8✔
329
                   container_index,
330
                   sqlitepp::quote(container_pair.first).in());
8✔
331
        for (const auto& file_path_str : container_pair.second) {
9✔
332
            fmt::print(file,
5✔
333
                       FMT_STRING(":open $LOG_DIR_{}/{}\n"),
20✔
334
                       container_index,
335
                       file_path_str);
336
        }
337
        container_index += 1;
4✔
338
    }
339

340
    fmt::print(file, FMT_STRING("\n:rebuild\n"));
24✔
341

342
    auto mark_count = 0;
6✔
343
    auto each_mark_res
344
        = prep_mark_res.unwrap().for_each_row<log_message_session_state>(
12✔
345
            [file, &mark_count](const log_message_session_state& lmss) {
3✔
346
                if (mark_count == 0) {
3✔
347
                    fmt::print(file, FMT_STRING(MARK_HEADER));
15✔
348
                }
349
                mark_count += 1;
3✔
350
                fmt::print(file,
6✔
351
                           FMT_STRING(";UPDATE all_logs "
9✔
352
                                      "SET log_mark = {}, "
353
                                      "log_comment = {}, "
354
                                      "log_tags = {}, "
355
                                      "log_annotations = {}, "
356
                                      "log_opid = {} "
357
                                      "WHERE log_time_msecs = {} AND "
358
                                      "log_format = {} AND "
359
                                      "log_line_hash = {}\n"),
360
                           lmss.lmss_mark ? "1" : "0",
3✔
361
                           sqlitepp::quote(lmss.lmss_comment).in(),
6✔
362
                           sqlitepp::quote(lmss.lmss_tags).in(),
6✔
363
                           sqlitepp::quote(lmss.lmss_annotations).in(),
6✔
364
                           sqlitepp::quote(lmss.lmss_opid).in(),
6✔
365
                           lmss.lmss_time_msecs,
3✔
366
                           sqlitepp::quote(lmss.lmss_format).in(),
6✔
367
                           sqlitepp::quote(lmss.lmss_hash).in());
6✔
368
                return false;
3✔
369
            });
6✔
370

371
    if (each_mark_res.isErr()) {
6✔
372
        return Err(console::user_message::error(
×
373
                       "failed to fetch bookmark metadata for log message")
374
                       .with_reason(each_mark_res.unwrapErr().fe_msg));
×
375
    }
376

377
    if (mark_count > 0) {
6✔
378
        fmt::print(file, FMT_STRING(MARK_FOOTER), mark_count);
15✔
379
    }
380

381
    auto prep_filter_res = prepare_stmt(lnav_db.in(), FILTER_QUERY);
6✔
382
    if (prep_filter_res.isErr()) {
6✔
383
        return Err(console::user_message::error("unable to export filter state")
×
384
                       .with_reason(prep_filter_res.unwrapErr()));
×
385
    }
386

387
    auto added_filter_header = false;
6✔
388
    auto each_filter_res
389
        = prep_filter_res.unwrap().for_each_row<log_filter_session_state>(
12✔
390
            [file, &added_filter_header](const log_filter_session_state& lfss) {
4✔
391
                if (!added_filter_header) {
4✔
392
                    fmt::print(file, FMT_STRING("{}"), FILTER_HEADER);
16✔
393
                    added_filter_header = true;
4✔
394
                }
395
                fmt::print(
4✔
396
                    file,
397
                    FMT_STRING(";REPLACE INTO lnav_view_filters "
8✔
398
                               "(view_name, enabled, type, language, pattern) "
399
                               "VALUES ({}, {}, {}, {}, {})\n"),
400
                    sqlitepp::quote(lfss.lfss_name).in(),
8✔
401
                    lfss.lfss_enabled ? 1 : 0,
4✔
402
                    sqlitepp::quote(lfss.lfss_type).in(),
8✔
403
                    sqlitepp::quote(lfss.lfss_language).in(),
8✔
404
                    sqlitepp::quote(lfss.lfss_pattern).in());
8✔
405
                return false;
4✔
406
            });
6✔
407

408
    if (each_filter_res.isErr()) {
6✔
409
        return Err(console::user_message::error(
×
410
                       "failed to fetch filter state for views")
411
                       .with_reason(each_filter_res.unwrapErr().fe_msg));
×
412
    }
413

414
    auto prep_file_res = prepare_stmt(lnav_db.in(), FILE_QUERY);
6✔
415
    if (prep_file_res.isErr()) {
6✔
416
        return Err(console::user_message::error("unable to export file state")
×
417
                       .with_reason(prep_file_res.unwrapErr()));
×
418
    }
419

420
    auto file_count = 0;
6✔
421
    auto file_stmt = prep_file_res.unwrap();
6✔
422
    auto each_file_res = file_stmt.for_each_row<log_file_session_state>(
423
        [file, &file_count](const log_file_session_state& lfss) {
×
424
            if (file_count == 0) {
×
425
                fmt::print(file, FMT_STRING("{}"), FILE_HEADER);
×
426
            }
427
            file_count += 1;
×
428
            fmt::print(file,
×
429
                       FMT_STRING(";UPDATE lnav_file "
×
430
                                  "SET time_offset = {} "
431
                                  "WHERE content_id = {} AND format = {}\n"),
432
                       lfss.lfss_time_offset,
×
433
                       sqlitepp::quote(lfss.lfss_content_id).in(),
×
434
                       sqlitepp::quote(lfss.lfss_format).in());
×
435
            return false;
×
436
        });
6✔
437

438
    if (each_file_res.isErr()) {
6✔
439
        return Err(console::user_message::error("failed to fetch file state")
×
440
                       .with_reason(each_file_res.unwrapErr().fe_msg));
×
441
    }
442

443
    if (file_count > 0) {
6✔
444
        fmt::print(file, FMT_STRING(FILE_FOOTER), file_count);
×
445
    }
446

447
    for (auto view_index : {LNV_LOG, LNV_TEXT}) {
18✔
448
        auto& tc = lnav_data.ld_views[view_index];
12✔
449
        if (tc.get_inner_height() == 0_vl) {
12✔
450
            continue;
6✔
451
        }
452

453
        fmt::print(file, FMT_STRING(VIEW_HEADER), lnav_view_titles[view_index]);
24✔
454
        fmt::print(file,
6✔
455
                   FMT_STRING(":switch-to-view {}\n"),
18✔
456
                   lnav_view_strings[view_index]);
6✔
457

458
        if (view_index == LNV_LOG) {
6✔
459
            std::vector<std::string> field_cmds;
6✔
460
            for (const auto& format : log_format::get_root_formats()) {
449✔
461
                auto field_states = format->get_field_states();
443✔
462

463
                for (const auto& fs_pair : field_states) {
5,981✔
464
                    if (!fs_pair.second.lvm_user_hidden) {
5,538✔
465
                        continue;
5,537✔
466
                    }
467

468
                    if (fs_pair.second.lvm_user_hidden.value()) {
1✔
469
                        field_cmds.emplace_back(
1✔
470
                            fmt::format(FMT_STRING("# :hide-fields {}.{}\n"),
4✔
471

472
                                        format->get_name(),
2✔
473
                                        fs_pair.first));
1✔
474
                    } else if (fs_pair.second.lvm_hidden) {
×
475
                        field_cmds.emplace_back(
×
476
                            fmt::format(FMT_STRING("# :show-fields {}.{}\n"),
×
477
                                        format->get_name().to_string(),
×
478
                                        fs_pair.first));
×
479
                    }
480
                }
481
            }
443✔
482

483
            if (!field_cmds.empty()) {
6✔
484
                fmt::print(file, FMT_STRING("{}"), FIELD_HEADER);
4✔
485
                field_cmds
486
                    | lnav::itertools::for_each([&file](const auto& cmd) {
1✔
487
                          fmt::print(file, FMT_STRING("{}"), cmd);
4✔
488
                      });
1✔
489
            }
490
        }
6✔
491

492
        {
493
            std::vector<std::string> hl_cmds;
6✔
494
            const auto& hm = tc.get_highlights();
6✔
495
            for (const auto& hl_pair : hm) {
32✔
496
                if (hl_pair.first.first != highlight_source_t::INTERACTIVE) {
26✔
497
                    continue;
25✔
498
                }
499

500
                auto cmd = fmt::format(FMT_STRING("# :highlight {}\n"),
3✔
501
                                       hl_pair.second.h_regex->get_pattern());
1✔
502
                hl_cmds.emplace_back(cmd);
1✔
503
            }
1✔
504

505
            if (!hl_cmds.empty()) {
6✔
506
                fmt::print(file, FMT_STRING("{}"), HIGHLIGHT_HEADER);
4✔
507
                hl_cmds | lnav::itertools::for_each([&file](const auto& cmd) {
1✔
508
                    fmt::print(file, FMT_STRING("{}"), cmd);
4✔
509
                });
1✔
510
                fmt::println(file, FMT_STRING(""));
5✔
511
            }
512
        }
6✔
513

514
        auto* tss = tc.get_sub_source();
6✔
515
        auto* ttt = dynamic_cast<text_time_translator*>(tss);
6✔
516
        if (ttt != nullptr) {
6✔
517
            char tsbuf[128];
518
            auto min_time_opt = ttt->get_min_row_time();
6✔
519
            if (min_time_opt) {
6✔
520
                sql_strftime(tsbuf, sizeof(tsbuf), min_time_opt.value(), 'T');
1✔
521
                fmt::print(file, FMT_STRING(":hide-lines-before {}\n"), tsbuf);
5✔
522
            }
523
            auto max_time_opt = ttt->get_max_row_time();
6✔
524
            if (max_time_opt) {
6✔
525
                sql_strftime(tsbuf, sizeof(tsbuf), max_time_opt.value(), 'T');
1✔
526
                fmt::print(file, FMT_STRING(":hide-lines-after {}\n"), tsbuf);
5✔
527
            }
528
        }
529

530
        auto* lss = dynamic_cast<logfile_sub_source*>(tss);
6✔
531
        if (lss != nullptr) {
6✔
532
            auto min_level = lss->get_min_log_level();
6✔
533

534
            if (min_level != LEVEL_UNKNOWN) {
6✔
535
                fmt::print(file,
1✔
536
                           FMT_STRING(":set-min-log-level {}\n"),
4✔
537
                           level_names[min_level]);
1✔
538
            }
539

540
            for (const auto& ld : *lss) {
13✔
541
                if (ld->is_visible()) {
7✔
542
                    continue;
6✔
543
                }
544

545
                if (ld->get_file_ptr()->get_open_options().loo_source
1✔
546
                    == logfile_name_source::ARCHIVE)
1✔
547
                {
548
                    continue;
×
549
                }
550

551
                auto container_path_opt
552
                    = find_container_dir(ld->get_file_ptr()->get_path());
1✔
553
                if (!container_path_opt) {
1✔
554
                    fmt::print(file,
×
555
                               FMT_STRING(":hide-file {}\n"),
×
556
                               ld->get_file_ptr()->get_path().string());
×
557
                    continue;
×
558
                }
559
                auto container_parent
560
                    = container_path_opt.value().parent_path();
1✔
561
                auto file_container_path = std::filesystem::relative(
562
                    ld->get_file_ptr()->get_path(), container_parent);
1✔
563
                fmt::print(file,
1✔
564
                           FMT_STRING(":hide-file */{}\n"),
3✔
565
                           file_container_path.string());
2✔
566
            }
1✔
567
        }
568

569
        if (!tc.get_current_search().empty()) {
6✔
570
            fmt::print(file, FMT_STRING("/{}\n"), tc.get_current_search());
5✔
571
        }
572

573
        fmt::print(file,
6✔
574
                   FMT_STRING(":goto {}\n"),
12✔
575
                   (int) tc.get_selection().value_or(tc.get_top()));
12✔
576
    }
577

578
    for (const auto& [name, vi] : *lnav_data.ld_vtab_manager) {
530✔
579
        if (vi->vi_provenance != log_vtab_impl::provenance_t::user) {
524✔
580
            continue;
523✔
581
        }
582

583
        auto cmd_opt = vi->get_command();
1✔
584
        if (!cmd_opt) {
1✔
NEW
585
            continue;
×
586
        }
587

588
        fmt::print(file, FMT_STRING("\n{}\n"), cmd_opt.value());
4✔
589
    }
1✔
590

591
    return Ok();
6✔
592
}
6✔
593

594
}  // namespace lnav::session
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