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

tstack / lnav / 30969404283-3079

05 Aug 2026 02:27AM UTC coverage: 70.208% (-0.1%) from 70.346%
30969404283-3079

push

github

tstack
[named-search] validate name, add to filter UI, and add some docs

Related to #1736

161 of 424 new or added lines in 11 files covered. (37.97%)

329 existing lines in 8 files now uncovered.

58269 of 82995 relevant lines covered (70.21%)

680656.69 hits per line

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

78.55
/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 "log_vtab_impl.hh"
37
#include "sqlitepp.client.hh"
38
#include "sqlitepp.hh"
39
#include "textview_curses.hh"
40

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

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

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

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

95
struct named_search_session_state {
96
    std::string nsss_view_name;
97
    bool nsss_enabled;
98
    std::string nsss_name;
99
    std::string nsss_pattern;
100
};
101

102
template<>
103
struct from_sqlite<named_search_session_state> {
104
    named_search_session_state operator()(int argc,
2✔
105
                                          sqlite3_value** argv,
106
                                          int argi)
107
    {
108
        return {
109
            from_sqlite<std::string>()(argc, argv, argi + 0),
2✔
NEW
110
            from_sqlite<bool>()(argc, argv, argi + 1),
×
UNCOV
111
            from_sqlite<std::string>()(argc, argv, argi + 2),
×
112
            from_sqlite<std::string>()(argc, argv, argi + 3),
2✔
113
        };
2✔
114
    }
2✔
115
};
116

117
struct log_file_session_state {
118
    std::string lfss_content_id;
119
    std::string lfss_format;
120
    int64_t lfss_time_offset;
121
};
122

123
template<>
124
struct from_sqlite<log_file_session_state> {
125
    log_file_session_state operator()(int argc, sqlite3_value** argv, int argi)
×
126
    {
127
        return {
128
            from_sqlite<std::string>()(argc, argv, argi + 0),
×
129
            from_sqlite<std::string>()(argc, argv, argi + 1),
×
130
            from_sqlite<int64_t>()(argc, argv, argi + 2),
×
131
        };
132
    }
133
};
134

135
namespace lnav::session {
136

137
static std::optional<std::filesystem::path>
138
find_container_dir(std::filesystem::path file_path)
7✔
139
{
140
    if (!std::filesystem::exists(file_path)) {
7✔
141
        return std::nullopt;
×
142
    }
143

144
    std::optional<std::filesystem::path> dir_with_last_readme;
7✔
145

146
    while (file_path.has_parent_path()
7✔
147
           && file_path != file_path.root_directory())
16✔
148
    {
149
        auto parent = file_path.parent_path();
16✔
150
        bool has_readme_entry = false;
16✔
151
        std::error_code ec;
16✔
152

153
        for (const auto& entry :
16✔
154
             std::filesystem::directory_iterator(parent, ec))
63,282✔
155
        {
156
            if (!entry.is_regular_file()) {
31,633✔
157
                continue;
223✔
158
            }
159

160
            auto entry_filename = tolower(entry.path().filename().string());
31,410✔
161
            if (startswith(entry_filename, "readme")) {
31,410✔
162
                has_readme_entry = true;
9✔
163
                dir_with_last_readme = parent;
9✔
164
            }
165
        }
31,426✔
166
        if (!has_readme_entry && dir_with_last_readme) {
16✔
167
            return dir_with_last_readme;
7✔
168
        }
169

170
        file_path = parent;
9✔
171
    }
16✔
172

173
    return std::nullopt;
×
174
}
7✔
175

176
static std::string
177
replace_home_dir(std::string path)
2✔
178
{
179
    const auto home_dir_opt = getenv_opt("HOME");
2✔
180

181
    if (!home_dir_opt) {
2✔
182
        return path;
×
183
    }
184

185
    const auto* home_dir = home_dir_opt.value();
2✔
186

187
    if (startswith(path, home_dir)) {
2✔
188
        auto retval = path.substr(strlen(home_dir));
×
189

190
        if (retval.front() != '/') {
×
191
            retval.insert(0, "/");
×
192
        }
193
        retval.insert(0, "$HOME");
×
194
        return retval;
×
195
    }
196

197
    return path;
2✔
198
}
199

200
Result<void, lnav::console::user_message>
201
export_to(FILE* file)
7✔
202
{
203
    auto* vtab_manager = injector::get<log_vtab_manager*>();
7✔
204
    static auto& lnav_db = injector::get<auto_sqlite3&>();
7✔
205

206
    static const char* BOOKMARK_QUERY = R"(
207
SELECT log_time_msecs, log_format, log_mark, log_comment, log_tags, log_annotations, log_user_opid, log_line_hash
208
   FROM all_logs
209
   WHERE log_mark = 1 OR
210
         log_comment IS NOT NULL OR
211
         log_tags IS NOT NULL OR
212
         log_annotations IS NOT NULL OR
213
         (log_user_opid IS NOT NULL AND log_user_opid != '')
214
)";
215

216
    static const char* FILTER_QUERY = R"(
217
SELECT view_name, enabled, type, language, pattern FROM lnav_view_filters
218
)";
219

220
    static const char* SEARCH_QUERY = R"(
221
SELECT view_name, enabled, name, pattern FROM lnav_view_searches
222
)";
223

224
    static const char* FILE_QUERY = R"(
225
SELECT content_id, format, time_offset FROM lnav_file
226
  WHERE format IS NOT NULL AND time_offset != 0
227
)";
228

229
    static constexpr char HEADER[] = R"(#!lnav -Nf
230
# This file is an export of an lnav session.  You can type
231
# '|/path/to/this/file' in lnav to execute this file and
232
# restore the state of the session.
233

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

237
# The files loaded into the session were:
238

239
)";
240

241
    static constexpr char LOG_DIR_INSERT[] = R"(
242
# Set this environment variable to override this value or edit this script.
243
;INSERT OR IGNORE INTO environ (name, value) VALUES ('LOG_DIR_{}', {})
244
)";
245

246
    static constexpr char MARK_HEADER[] = R"(
247

248
# The following SQL statements will restore the bookmarks,
249
# comments, and tags that were added in the session.
250

251
;SELECT total_changes() AS before_mark_changes
252
)";
253

254
    static constexpr char MARK_FOOTER[] = R"(
255
;SELECT {} - (total_changes() - $before_mark_changes) AS failed_mark_changes
256
;SELECT echoln(printf('%sERROR%s: failed to restore %d bookmarks',
257
                      $ansi_red, $ansi_norm, $failed_mark_changes))
258
    WHERE $failed_mark_changes != 0
259
)";
260

261
    static const char* FILTER_HEADER = R"(
262

263
# The following SQL statements will restore the filters that
264
# were added in the session.
265

266
)";
267

268
    static const char* SEARCH_HEADER = R"(
269

270
# The following SQL statements will restore the named searches
271
# that were created in the session.
272

273
)";
274

275
    static const char* FILE_HEADER = R"(
276

277
# The following SQL statements will restore the state of the
278
# files in the session.
279

280
;SELECT total_changes() AS before_file_changes
281
)";
282

283
    static const char* FIELD_HEADER = R"(
284
# The following field visibility commands were run by the
285
# original user during this session.  Uncomment them if
286
# desired.
287
)";
288

289
    static const char* HIGHLIGHT_HEADER = R"(
290
# The following highlight commands were run by the
291
# original user during this session.  Uncomment them if
292
# desired.
293
)";
294

295
    static constexpr char FILE_FOOTER[] = R"(
296
;SELECT {} - (total_changes() - $before_file_changes) AS failed_file_changes
297
;SELECT echoln(printf('%sERROR%s: failed to restore the state of %d files',
298
                      $ansi_red, $ansi_norm, $failed_file_changes))
299
   WHERE $failed_file_changes != 0
300
)";
301

302
    static constexpr char VIEW_HEADER[] = R"(
303

304
# The following commands will restore the state of the {} view.
305

306
)";
307

308
    auto prep_mark_res = prepare_stmt(lnav_db.in(), BOOKMARK_QUERY);
7✔
309
    if (prep_mark_res.isErr()) {
7✔
310
        return Err(
×
311
            console::user_message::error("unable to export log bookmarks")
×
312
                .with_reason(prep_mark_res.unwrapErr()));
×
313
    }
314

315
    fmt::print(file, FMT_STRING(HEADER), sqlitepp::quote(PACKAGE_VERSION).in());
35✔
316

317
    std::map<std::string, std::vector<std::string>> file_containers;
7✔
318
    std::set<std::string> raw_files;
7✔
319
    for (const auto& name_pair : lnav_data.ld_active_files.fc_file_names) {
15✔
320
        const auto& open_opts = name_pair.second;
8✔
321

322
        if (!open_opts.loo_is_visible || !open_opts.loo_include_in_session
8✔
323
            || !open_opts.loo_filename.empty()
7✔
324
            || open_opts.loo_source != logfile_name_source::USER)
16✔
325
        {
326
            continue;
2✔
327
        }
328

329
        const auto& file_path_str = name_pair.first;
6✔
330
        auto file_path = std::filesystem::path(file_path_str);
6✔
331
        auto container_path_opt = find_container_dir(file_path);
6✔
332
        if (container_path_opt) {
6✔
333
            auto container_parent = container_path_opt.value().parent_path();
6✔
334
            auto file_container_path
335
                = std::filesystem::relative(file_path, container_parent)
12✔
336
                      .string();
6✔
337
            file_containers[container_parent.string()].push_back(
6✔
338
                file_container_path);
339
        } else {
6✔
340
            raw_files.insert(file_path_str);
×
341
        }
342
    }
6✔
343
    for (const auto& lf : lnav_data.ld_active_files.fc_files) {
15✔
344
        if (lf->is_valid_filename()) {
8✔
345
            continue;
6✔
346
        }
347
        if (!lf->get_open_options().loo_include_in_session) {
2✔
348
            continue;
×
349
        }
350

351
        const auto& open_options = lf->get_open_options();
2✔
352
        if (open_options.loo_piper) {
2✔
353
            raw_files.emplace(open_options.loo_piper->get_url());
2✔
354
        }
355
    }
356
    for (const auto& file_path_str : raw_files) {
9✔
357
        fmt::print(
2✔
358
            file, FMT_STRING(":open {}\n"), replace_home_dir(file_path_str));
8✔
359
    }
360
    size_t container_index = 0;
7✔
361
    for (const auto& container_pair : file_containers) {
12✔
362
        fmt::print(file,
5✔
363
                   FMT_STRING(LOG_DIR_INSERT),
10✔
364
                   container_index,
365
                   sqlitepp::quote(container_pair.first).in());
10✔
366
        for (const auto& file_path_str : container_pair.second) {
11✔
367
            fmt::print(file,
6✔
368
                       FMT_STRING(":open $LOG_DIR_{}/{}\n"),
24✔
369
                       container_index,
370
                       file_path_str);
371
        }
372
        container_index += 1;
5✔
373
    }
374

375
    fmt::print(file, FMT_STRING("\n:rebuild\n"));
28✔
376

377
    auto mark_count = 0;
7✔
378
    auto each_mark_res
379
        = prep_mark_res.unwrap().for_each_row<log_message_session_state>(
14✔
380
            [file, &mark_count](const log_message_session_state& lmss) {
3✔
381
                if (mark_count == 0) {
3✔
382
                    fmt::print(file, FMT_STRING(MARK_HEADER));
15✔
383
                }
384
                mark_count += 1;
3✔
385
                fmt::print(file,
6✔
386
                           FMT_STRING(";UPDATE all_logs "
9✔
387
                                      "SET log_mark = {}, "
388
                                      "log_comment = {}, "
389
                                      "log_tags = {}, "
390
                                      "log_annotations = {}, "
391
                                      "log_opid = {} "
392
                                      "WHERE log_time_msecs = {} AND "
393
                                      "log_format = {} AND "
394
                                      "log_line_hash = {}\n"),
395
                           lmss.lmss_mark ? "1" : "0",
3✔
396
                           sqlitepp::quote(lmss.lmss_comment).in(),
6✔
397
                           sqlitepp::quote(lmss.lmss_tags).in(),
6✔
398
                           sqlitepp::quote(lmss.lmss_annotations).in(),
6✔
399
                           sqlitepp::quote(lmss.lmss_opid).in(),
6✔
400
                           lmss.lmss_time_msecs,
3✔
401
                           sqlitepp::quote(lmss.lmss_format).in(),
6✔
402
                           sqlitepp::quote(lmss.lmss_hash).in());
6✔
403
                return false;
3✔
404
            });
7✔
405

406
    if (each_mark_res.isErr()) {
7✔
407
        return Err(console::user_message::error(
×
408
                       "failed to fetch bookmark metadata for log message")
409
                       .with_reason(each_mark_res.unwrapErr().fe_msg));
×
410
    }
411

412
    if (mark_count > 0) {
7✔
413
        fmt::print(file, FMT_STRING(MARK_FOOTER), mark_count);
15✔
414
    }
415

416
    auto prep_filter_res = prepare_stmt(lnav_db.in(), FILTER_QUERY);
7✔
417
    if (prep_filter_res.isErr()) {
7✔
418
        return Err(console::user_message::error("unable to export filter state")
×
419
                       .with_reason(prep_filter_res.unwrapErr()));
×
420
    }
421

422
    auto added_filter_header = false;
7✔
423
    auto each_filter_res
424
        = prep_filter_res.unwrap().for_each_row<log_filter_session_state>(
14✔
425
            [file, &added_filter_header](const log_filter_session_state& lfss) {
4✔
426
                if (!added_filter_header) {
4✔
427
                    fmt::print(file, FMT_STRING("{}"), FILTER_HEADER);
16✔
428
                    added_filter_header = true;
4✔
429
                }
430
                fmt::print(
4✔
431
                    file,
432
                    FMT_STRING(";REPLACE INTO lnav_view_filters "
8✔
433
                               "(view_name, enabled, type, language, pattern) "
434
                               "VALUES ({}, {}, {}, {}, {})\n"),
435
                    sqlitepp::quote(lfss.lfss_name).in(),
8✔
436
                    lfss.lfss_enabled ? 1 : 0,
4✔
437
                    sqlitepp::quote(lfss.lfss_type).in(),
8✔
438
                    sqlitepp::quote(lfss.lfss_language).in(),
8✔
439
                    sqlitepp::quote(lfss.lfss_pattern).in());
8✔
440
                return false;
4✔
441
            });
7✔
442

443
    if (each_filter_res.isErr()) {
7✔
444
        return Err(console::user_message::error(
×
445
                       "failed to fetch filter state for views")
446
                       .with_reason(each_filter_res.unwrapErr().fe_msg));
×
447
    }
448

449
    auto prep_search_res = prepare_stmt(lnav_db.in(), SEARCH_QUERY);
7✔
450
    if (prep_search_res.isErr()) {
7✔
451
        return Err(
×
452
            console::user_message::error("unable to export named searches")
×
453
                .with_reason(prep_search_res.unwrapErr()));
×
454
    }
455

456
    auto added_search_header = false;
7✔
457
    auto each_search_res
458
        = prep_search_res.unwrap().for_each_row<named_search_session_state>(
14✔
459
            [file, &added_search_header](
2✔
460
                const named_search_session_state& nsss) {
461
                if (!added_search_header) {
2✔
462
                    fmt::print(file, FMT_STRING("{}"), SEARCH_HEADER);
4✔
463
                    added_search_header = true;
1✔
464
                }
465
                fmt::print(file,
2✔
466
                           FMT_STRING(";INSERT INTO lnav_view_searches "
4✔
467
                                      "(view_name, enabled, name, pattern) "
468
                                      "VALUES ({}, {}, {}, {})\n"),
469
                           sqlitepp::quote(nsss.nsss_view_name).in(),
4✔
470
                           nsss.nsss_enabled ? 1 : 0,
2✔
471
                           sqlitepp::quote(nsss.nsss_name).in(),
4✔
472
                           sqlitepp::quote(nsss.nsss_pattern).in());
4✔
473
                return false;
2✔
474
            });
7✔
475

476
    if (each_search_res.isErr()) {
7✔
477
        return Err(console::user_message::error(
×
478
                       "failed to fetch named searches for views")
479
                       .with_reason(each_search_res.unwrapErr().fe_msg));
×
480
    }
481

482
    auto prep_file_res = prepare_stmt(lnav_db.in(), FILE_QUERY);
7✔
483
    if (prep_file_res.isErr()) {
7✔
484
        return Err(console::user_message::error("unable to export file state")
×
485
                       .with_reason(prep_file_res.unwrapErr()));
×
486
    }
487

488
    auto file_count = 0;
7✔
489
    auto file_stmt = prep_file_res.unwrap();
7✔
490
    auto each_file_res = file_stmt.for_each_row<log_file_session_state>(
491
        [file, &file_count](const log_file_session_state& lfss) {
×
492
            if (file_count == 0) {
×
493
                fmt::print(file, FMT_STRING("{}"), FILE_HEADER);
×
494
            }
495
            file_count += 1;
×
496
            fmt::print(file,
×
497
                       FMT_STRING(";UPDATE lnav_file "
×
498
                                  "SET time_offset = {} "
499
                                  "WHERE content_id = {} AND format = {}\n"),
500
                       lfss.lfss_time_offset,
×
501
                       sqlitepp::quote(lfss.lfss_content_id).in(),
×
502
                       sqlitepp::quote(lfss.lfss_format).in());
×
503
            return false;
×
504
        });
7✔
505

506
    if (each_file_res.isErr()) {
7✔
507
        return Err(console::user_message::error("failed to fetch file state")
×
508
                       .with_reason(each_file_res.unwrapErr().fe_msg));
×
509
    }
510

511
    if (file_count > 0) {
7✔
512
        fmt::print(file, FMT_STRING(FILE_FOOTER), file_count);
×
513
    }
514

515
    for (auto view_index : {LNV_LOG, LNV_TEXT}) {
21✔
516
        auto& tc = lnav_data.ld_views[view_index];
14✔
517
        if (tc.get_inner_height() == 0_vl) {
14✔
518
            continue;
7✔
519
        }
520

521
        fmt::print(file, FMT_STRING(VIEW_HEADER), lnav_view_titles[view_index]);
28✔
522
        fmt::print(file,
7✔
523
                   FMT_STRING(":switch-to-view {}\n"),
21✔
524
                   lnav_view_strings[view_index]);
7✔
525

526
        if (view_index == LNV_LOG) {
7✔
527
            std::vector<std::string> field_cmds;
7✔
528
            for (const auto& format : log_format::get_root_formats()) {
582✔
529
                auto field_states = format->get_field_states();
575✔
530

531
                for (const auto& fs_pair : field_states) {
8,105✔
532
                    if (!fs_pair.second.lvm_user_hidden) {
7,530✔
533
                        continue;
7,529✔
534
                    }
535

536
                    if (fs_pair.second.lvm_user_hidden.value()) {
1✔
537
                        field_cmds.emplace_back(
1✔
538
                            fmt::format(FMT_STRING("# :hide-fields {}.{}\n"),
4✔
539

540
                                        format->get_name(),
2✔
541
                                        fs_pair.first));
1✔
542
                    } else if (fs_pair.second.lvm_hidden) {
×
543
                        field_cmds.emplace_back(
×
544
                            fmt::format(FMT_STRING("# :show-fields {}.{}\n"),
×
545
                                        format->get_name().to_string(),
×
546
                                        fs_pair.first));
×
547
                    }
548
                }
549
            }
575✔
550

551
            if (!field_cmds.empty()) {
7✔
552
                fmt::print(file, FMT_STRING("{}"), FIELD_HEADER);
4✔
553
                field_cmds
554
                    | lnav::itertools::for_each([&file](const auto& cmd) {
1✔
555
                          fmt::print(file, FMT_STRING("{}"), cmd);
4✔
556
                      });
1✔
557
            }
558
        }
7✔
559

560
        {
561
            std::vector<std::string> hl_cmds;
7✔
562
            const auto& hm = tc.get_highlights();
7✔
563
            for (const auto& hl_pair : hm) {
39✔
564
                if (hl_pair.first.first != highlight_source_t::INTERACTIVE) {
32✔
565
                    continue;
31✔
566
                }
567

568
                auto cmd = fmt::format(FMT_STRING("# :highlight {}\n"),
3✔
569
                                       hl_pair.second.h_regex->get_pattern());
1✔
570
                hl_cmds.emplace_back(cmd);
1✔
571
            }
1✔
572

573
            if (!hl_cmds.empty()) {
7✔
574
                fmt::print(file, FMT_STRING("{}"), HIGHLIGHT_HEADER);
4✔
575
                hl_cmds | lnav::itertools::for_each([&file](const auto& cmd) {
1✔
576
                    fmt::print(file, FMT_STRING("{}"), cmd);
4✔
577
                });
1✔
578
                fmt::println(file, FMT_STRING(""));
5✔
579
            }
580
        }
7✔
581

582
        auto* tss = tc.get_sub_source();
7✔
583
        auto* ttt = dynamic_cast<text_time_translator*>(tss);
7✔
584
        if (ttt != nullptr) {
7✔
585
            char tsbuf[128];
586
            auto min_time_opt = ttt->get_min_row_time();
7✔
587
            if (min_time_opt) {
7✔
588
                sql_strftime(tsbuf, sizeof(tsbuf), min_time_opt.value(), 'T');
1✔
589
                fmt::print(file, FMT_STRING(":hide-lines-before {}\n"), tsbuf);
5✔
590
            }
591
            auto max_time_opt = ttt->get_max_row_time();
7✔
592
            if (max_time_opt) {
7✔
593
                sql_strftime(tsbuf, sizeof(tsbuf), max_time_opt.value(), 'T');
1✔
594
                fmt::print(file, FMT_STRING(":hide-lines-after {}\n"), tsbuf);
5✔
595
            }
596
        }
597

598
        auto* lss = dynamic_cast<logfile_sub_source*>(tss);
7✔
599
        if (lss != nullptr) {
7✔
600
            auto min_level = lss->get_min_log_level();
7✔
601

602
            if (min_level != LEVEL_UNKNOWN) {
7✔
603
                fmt::print(file,
1✔
604
                           FMT_STRING(":set-min-log-level {}\n"),
4✔
605
                           level_names[min_level]);
1✔
606
            }
607

608
            for (const auto& ld : *lss) {
15✔
609
                if (ld->is_visible()) {
8✔
610
                    continue;
7✔
611
                }
612

613
                if (ld->get_file_ptr()->get_open_options().loo_source
1✔
614
                    == logfile_name_source::ARCHIVE)
1✔
615
                {
616
                    continue;
×
617
                }
618

619
                auto container_path_opt
620
                    = find_container_dir(ld->get_file_ptr()->get_path());
1✔
621
                if (!container_path_opt) {
1✔
622
                    fmt::print(file,
×
623
                               FMT_STRING(":hide-file {}\n"),
×
624
                               ld->get_file_ptr()->get_path().string());
×
625
                    continue;
×
626
                }
627
                auto container_parent
628
                    = container_path_opt.value().parent_path();
1✔
629
                auto file_container_path = std::filesystem::relative(
630
                    ld->get_file_ptr()->get_path(), container_parent);
1✔
631
                fmt::print(file,
1✔
632
                           FMT_STRING(":hide-file */{}\n"),
3✔
633
                           file_container_path.string());
2✔
634
            }
1✔
635
        }
636

637
        if (!tc.get_current_search().empty()) {
7✔
638
            fmt::print(file, FMT_STRING("/{}\n"), tc.get_current_search());
5✔
639
        }
640

641
        fmt::print(file,
7✔
642
                   FMT_STRING(":goto {}\n"),
14✔
643
                   (int) tc.get_selection().value_or(tc.get_top()));
14✔
644
    }
645

646
    for (const auto& [name, vi] : *vtab_manager) {
669✔
647
        if (vi->vi_provenance != log_vtab_impl::provenance_t::user) {
662✔
648
            continue;
661✔
649
        }
650

651
        auto cmd_opt = vi->get_command();
1✔
652
        if (!cmd_opt) {
1✔
653
            continue;
×
654
        }
655

656
        fmt::print(file, FMT_STRING("\n{}\n"), cmd_opt.value());
4✔
657
    }
1✔
658

659
    return Ok();
7✔
660
}
7✔
661

662
}  // namespace lnav::session
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc