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

tstack / lnav / 19092286039-2639

05 Nov 2025 05:26AM UTC coverage: 68.913% (-0.007%) from 68.92%
19092286039-2639

push

github

tstack
[session] skip restoring commands

Related to #1450

94 of 116 new or added lines in 8 files covered. (81.03%)

40 existing lines in 8 files now uncovered.

50578 of 73394 relevant lines covered (68.91%)

429603.27 hits per line

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

46.26
/src/files_sub_source.cc
1
/**
2
 * Copyright (c) 2020, 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 "files_sub_source.hh"
31

32
#include "base/ansi_scrubber.hh"
33
#include "base/attr_line.builder.hh"
34
#include "base/fs_util.hh"
35
#include "base/humanize.hh"
36
#include "base/humanize.network.hh"
37
#include "base/humanize.time.hh"
38
#include "base/itertools.enumerate.hh"
39
#include "base/itertools.hh"
40
#include "base/opt_util.hh"
41
#include "base/string_util.hh"
42
#include "config.h"
43
#include "lnav.hh"
44
#include "mapbox/variant.hpp"
45
#include "md4cpp.hh"
46
#include "sql_util.hh"
47

48
using namespace md4cpp::literals;
49
using namespace lnav::roles::literals;
50

51
namespace files_model {
52
files_list_selection
53
from_selection(std::optional<vis_line_t> sel_vis)
21✔
54
{
55
    if (!sel_vis) {
21✔
56
        return no_selection{};
13✔
57
    }
58

59
    auto& fc = lnav_data.ld_active_files;
8✔
60
    int sel = sel_vis.value();
8✔
61

62
    {
63
        safe::ReadAccess<safe_name_to_errors> errs(*fc.fc_name_to_errors);
8✔
64

65
        if (sel < (ssize_t) errs->size()) {
8✔
66
            auto iter = errs->begin();
×
67

68
            std::advance(iter, sel);
69
            return error_selection::build(
×
70
                sel, std::make_pair(iter->first, iter->second.fei_description));
×
71
        }
72

73
        sel -= errs->size();
8✔
74
    }
8✔
75

76
    if (sel < (ssize_t) fc.fc_other_files.size()) {
8✔
77
        auto iter = fc.fc_other_files.begin();
×
78

79
        std::advance(iter, sel);
80
        return other_selection::build(sel, iter);
×
81
    }
82

83
    sel -= fc.fc_other_files.size();
8✔
84

85
    if (sel < (ssize_t) fc.fc_files.size()) {
8✔
86
        auto iter = fc.fc_files.begin();
8✔
87

88
        std::advance(iter, sel);
89
        return file_selection::build(sel, iter);
8✔
90
    }
91

92
    return no_selection{};
×
93
}
94
}  // namespace files_model
95

96
bool
UNCOV
97
files_sub_source::list_input_handle_key(listview_curses& lv, const ncinput& ch)
×
98
{
UNCOV
99
    switch (ch.eff_text[0]) {
×
100
        case NCKEY_ENTER:
×
101
        case '\r': {
102
            auto sel = files_model::from_selection(lv.get_selection());
×
103

104
            sel.match(
×
105
                [](files_model::no_selection) {},
×
106
                [](files_model::error_selection) {},
×
107
                [](files_model::other_selection) {},
×
108
                [&](files_model::file_selection& fs) {
×
109
                    auto& lss = lnav_data.ld_log_source;
×
110
                    auto lf = *fs.sb_iter;
×
111

112
                    lf->set_indexing(true);
×
113
                    lss.find_data(lf) | [](auto ld) {
×
114
                        ld->set_visibility(true);
×
115
                        lnav_data.ld_log_source.text_filters_changed();
×
116
                    };
117

118
                    if (lf->get_format() != nullptr) {
×
119
                        auto& log_view = lnav_data.ld_views[LNV_LOG];
×
120
                        lss.row_for_time(lf->front().get_timeval()) |
×
121
                            [](auto row) {
×
122
                                lnav_data.ld_views[LNV_LOG].set_selection(row);
×
123
                            };
124
                        ensure_view(&log_view);
×
125
                    } else {
126
                        auto& tv = lnav_data.ld_views[LNV_TEXT];
×
127
                        auto& tss = lnav_data.ld_text_source;
×
128

129
                        tss.to_front(lf);
×
130
                        tv.reload_data();
×
131
                        ensure_view(&tv);
×
132
                    }
133

134
                    lv.reload_data();
×
135
                    set_view_mode(ln_mode_t::PAGING);
×
136
                });
×
137

138
            return true;
×
139
        }
140

141
        case ' ': {
×
142
            auto sel = files_model::from_selection(lv.get_selection());
×
143

144
            sel.match([](files_model::no_selection) {},
×
145
                      [](files_model::error_selection) {},
×
146
                      [](files_model::other_selection) {},
×
147
                      [&](files_model::file_selection& fs) {
×
148
                          auto& lss = lnav_data.ld_log_source;
×
149
                          auto lf = *fs.sb_iter;
×
150

151
                          log_debug("toggling visibility of file: %s",
×
152
                                    lf->get_filename().c_str());
153
                          lss.find_data(lf) | [](auto ld) {
×
154
                              ld->get_file_ptr()->set_indexing(!ld->ld_visible);
×
155
                              ld->set_visibility(!ld->ld_visible);
×
156
                          };
157

158
                          auto top_view = *lnav_data.ld_view_stack.top();
×
159
                          auto tss = top_view->get_sub_source();
×
160

161
                          if (tss != nullptr) {
×
162
                              if (tss != &lss) {
×
163
                                  lss.text_filters_changed();
×
164
                                  lnav_data.ld_views[LNV_LOG].reload_data();
×
165
                              }
166
                              tss->text_filters_changed();
×
167
                              top_view->reload_data();
×
168
                          }
169

170
                          lv.reload_data();
×
171
                      });
×
172
            return true;
×
173
        }
174
        case 'n': {
×
175
            execute_command(lnav_data.ld_exec_context, "next-mark search");
×
176
            return true;
×
177
        }
178
        case 'N': {
×
179
            execute_command(lnav_data.ld_exec_context, "prev-mark search");
×
180
            return true;
×
181
        }
182
        case '/': {
×
183
            execute_command(lnav_data.ld_exec_context, "prompt search-files");
×
184
            return true;
×
185
        }
186
        case 'X': {
×
187
            auto sel = files_model::from_selection(lv.get_selection());
×
188

189
            sel.match(
×
190
                [](files_model::no_selection) {},
×
191
                [&](files_model::error_selection& es) {
×
192
                    auto& fc = lnav_data.ld_active_files;
×
193

194
                    fc.fc_file_names.erase(es.sb_iter.first);
×
195

196
                    auto name_iter = fc.fc_file_names.begin();
×
197
                    while (name_iter != fc.fc_file_names.end()) {
×
198
                        if (name_iter->first == es.sb_iter.first) {
×
199
                            name_iter = fc.fc_file_names.erase(name_iter);
×
200
                            continue;
×
201
                        }
202

203
                        auto rp_opt = humanize::network::path::from_str(
204
                            name_iter->first);
×
205

206
                        if (rp_opt) {
×
207
                            auto rp = *rp_opt;
×
208

209
                            if (fmt::to_string(rp.home()) == es.sb_iter.first) {
×
210
                                fc.fc_other_files.erase(name_iter->first);
×
211
                                name_iter = fc.fc_file_names.erase(name_iter);
×
212
                                continue;
×
213
                            }
214
                        }
215
                        ++name_iter;
×
216
                    }
217

218
                    fc.fc_name_to_errors->writeAccess()->erase(
×
219
                        es.sb_iter.first);
×
220
                    fc.fc_invalidate_merge = true;
×
221
                    lv.reload_data();
×
222
                },
×
223
                [](files_model::other_selection) {},
×
224
                [](files_model::file_selection) {});
×
225
            return true;
×
226
        }
227
    }
UNCOV
228
    return false;
×
229
}
230

231
void
232
files_sub_source::list_input_handle_scroll_out(listview_curses& lv)
×
233
{
234
    set_view_mode(ln_mode_t::PAGING);
×
235
    lnav_data.ld_filter_view.reload_data();
×
236
}
237

238
size_t
239
files_sub_source::text_line_count()
3,680✔
240
{
241
    const auto& fc = lnav_data.ld_active_files;
3,680✔
242
    auto retval = fc.fc_name_to_errors->readAccess()->size()
3,680✔
243
        + fc.fc_other_files.size() + fc.fc_files.size();
3,680✔
244

245
    return retval;
3,680✔
246
}
247

248
size_t
249
files_sub_source::text_line_width(textview_curses& curses)
1✔
250
{
251
    return 512;
1✔
252
}
253

254
line_info
255
files_sub_source::text_value_for_line(textview_curses& tc,
1✔
256
                                      int line,
257
                                      std::string& value_out,
258
                                      text_sub_source::line_flags_t flags)
259
{
260
    bool selected = line == tc.get_selection();
1✔
261
    role_t cursor_role = lnav_data.ld_mode == ln_mode_t::FILES
2✔
262
        ? role_t::VCR_CURSOR_LINE
1✔
263
        : role_t::VCR_DISABLED_CURSOR_LINE;
264
    const auto& fc = lnav_data.ld_active_files;
1✔
265
    auto filename_width = std::min(fc.fc_largest_path_length, (size_t) 30);
1✔
266

267
    this->fss_curr_line.clear();
1✔
268
    auto& al = this->fss_curr_line;
1✔
269
    attr_line_builder alb(al);
1✔
270

271
    if (selected) {
1✔
272
        al.append(" ", VC_GRAPHIC.value(NCACS_RARROW));
1✔
273
    } else {
274
        al.append(" ");
×
275
    }
276
    {
277
        safe::ReadAccess<safe_name_to_errors> errs(*fc.fc_name_to_errors);
1✔
278

279
        if (line < (ssize_t) errs->size()) {
1✔
280
            auto iter = std::next(errs->begin(), line);
×
281
            auto path = std::filesystem::path(iter->first);
×
282
            auto fn = fmt::to_string(path.filename());
×
283

284
            truncate_to(fn, filename_width);
×
285
            al.append("   ");
×
286
            {
287
                auto ag = alb.with_attr(VC_ROLE.value(role_t::VCR_ERROR));
×
288

289
                al.appendf(FMT_STRING("{:<{}}"), fn, filename_width);
×
290
            }
291
            al.append("   ").append(iter->second.fei_description);
×
292
            if (selected) {
×
293
                al.with_attr_for_all(VC_ROLE.value(cursor_role));
×
294
            }
295

296
            value_out = al.get_string();
×
297
            return {};
×
298
        }
299

300
        line -= errs->size();
1✔
301
    }
1✔
302

303
    if (line < (ssize_t) fc.fc_other_files.size()) {
1✔
304
        auto iter = std::next(fc.fc_other_files.begin(), line);
×
305
        auto path = std::filesystem::path(iter->first);
×
306
        auto fn = fmt::to_string(path);
×
307

308
        truncate_to(fn, filename_width);
×
309
        al.append("   ");
×
310
        {
311
            auto ag = alb.with_attr(VC_ROLE.value(role_t::VCR_FILE));
×
312

313
            al.appendf(FMT_STRING("{:<{}}"), fn, filename_width);
×
314
        }
315
        al.append("   ")
×
316
            .appendf(FMT_STRING("{:14}"), iter->second.ofd_format)
×
317
            .append("  ")
×
318
            .append(iter->second.ofd_description);
×
319
        if (selected) {
×
320
            al.with_attr_for_all(VC_ROLE.value(cursor_role));
×
321
        }
322
        if (line == (ssize_t) fc.fc_other_files.size() - 1) {
×
323
            al.with_attr_for_all(VC_STYLE.value(text_attrs::with_underline()));
×
324
        }
325

326
        value_out = al.get_string();
×
327
        return {};
×
328
    }
329

330
    line -= fc.fc_other_files.size();
1✔
331

332
    const auto& lf = fc.fc_files[line];
1✔
333
    auto ld_opt = lnav_data.ld_log_source.find_data(lf);
1✔
334
    auto fn = fmt::to_string(std::filesystem::path(lf->get_unique_path()));
1✔
335

336
    truncate_to(fn, filename_width);
1✔
337
    al.append(" ");
1✔
338
    if (ld_opt) {
1✔
339
        if (ld_opt.value()->ld_visible) {
1✔
340
            al.append("\u25c6"_ok);
1✔
341
        } else {
342
            al.append("\u25c7"_comment);
×
343
        }
344
    } else {
345
        al.append("\u25c6"_comment);
×
346
    }
347
    al.append(" ");
1✔
348
    al.appendf(FMT_STRING("{:<{}}"), fn, filename_width);
4✔
349
    al.append("  ");
1✔
350
    {
351
        auto ag = alb.with_attr(VC_ROLE.value(role_t::VCR_NUMBER));
1✔
352

353
        al.appendf(FMT_STRING("{:>8}"),
4✔
354
                   humanize::file_size(lf->get_index_size(),
2✔
355
                                       humanize::alignment::columnar));
356
    }
1✔
357
    al.append(" ");
1✔
358
    auto indexed_size = lf->get_indexed_file_offset();
1✔
359
    auto total_size = lf->get_stat().st_size;
1✔
360
    if (!lf->get_decompress_error().empty()) {
1✔
361
        al.append(" ", VC_ICON.value(ui_icon_t::error));
×
362
    } else if (!lf->get_notes().empty()) {
1✔
363
        al.append(" ", VC_ICON.value(ui_icon_t::warning));
×
364
    } else if (indexed_size < total_size) {
1✔
365
        al.append(humanize::sparkline(indexed_size, total_size));
×
366
    } else {
367
        al.append(" ", VC_ICON.value(ui_icon_t::ok));
1✔
368
    }
369
    if (selected) {
1✔
370
        al.with_attr_for_all(VC_ROLE.value(cursor_role));
1✔
371
    }
372

373
    value_out = al.get_string();
1✔
374
    this->fss_last_line_len = value_out.length();
1✔
375

376
    return {};
1✔
377
}
1✔
378

379
void
380
files_sub_source::text_attrs_for_line(textview_curses& tc,
1✔
381
                                      int line,
382
                                      string_attrs_t& value_out)
383
{
384
    value_out = this->fss_curr_line.get_attrs();
1✔
385
}
1✔
386

387
size_t
388
files_sub_source::text_size_for_line(textview_curses& tc,
×
389
                                     int line,
390
                                     text_sub_source::line_flags_t raw)
391
{
392
    return 0;
×
393
}
394

395
static auto
396
spinner_index()
×
397
{
398
    auto now = ui_clock::now();
×
399

400
    return std::chrono::duration_cast<std::chrono::milliseconds>(
×
401
               now.time_since_epoch())
×
402
               .count()
×
403
        / 100;
×
404
}
405

406
bool
407
files_overlay_source::list_static_overlay(const listview_curses& lv,
15✔
408
                                          media_t media,
409
                                          int y,
410
                                          int bottom,
411
                                          attr_line_t& value_out)
412
{
413
    if (y != 0) {
15✔
414
        return false;
12✔
415
    }
416
    static const char PROG[] = "-\\|/";
417
    constexpr size_t PROG_SIZE = sizeof(PROG) - 1;
3✔
418

419
    auto& fc = lnav_data.ld_active_files;
3✔
420
    auto fc_prog = fc.fc_progress;
3✔
421
    safe::WriteAccess<safe_scan_progress> sp(*fc_prog);
3✔
422

423
    if (!sp->sp_extractions.empty()) {
3✔
424
        const auto& prog = sp->sp_extractions.front();
×
425

426
        value_out.with_ansi_string(fmt::format(
×
427
            "{} Extracting " ANSI_COLOR(COLOR_CYAN) "{}" ANSI_NORM
428
                                                    "... {:>8}/{}",
429
            PROG[spinner_index() % PROG_SIZE],
×
430
            prog.ep_path.filename().string(),
×
431
            humanize::file_size(prog.ep_out_size, humanize::alignment::none),
×
432
            humanize::file_size(prog.ep_total_size,
×
433
                                humanize::alignment::none)));
434
        return true;
×
435
    }
436
    if (!sp->sp_tailers.empty()) {
3✔
437
        auto first_iter = sp->sp_tailers.begin();
×
438

439
        value_out.with_ansi_string(fmt::format(
×
440
            "{} Connecting to " ANSI_COLOR(COLOR_CYAN) "{}" ANSI_NORM ": {}",
441
            PROG[spinner_index() % PROG_SIZE],
×
442
            first_iter->first,
×
443
            first_iter->second.tp_message));
×
444
        return true;
×
445
    }
446

447
    return false;
3✔
448
}
3✔
449

450
bool
451
files_sub_source::text_handle_mouse(
×
452
    textview_curses& tc,
453
    const listview_curses::display_line_content_t&,
454
    mouse_event& me)
455
{
456
    auto nci = ncinput{};
×
457
    if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 1, 3)) {
×
458
        nci.id = ' ';
×
459
        nci.eff_text[0] = ' ';
×
460
        this->list_input_handle_key(tc, nci);
×
461
    }
462
    if (me.is_double_click_in(mouse_button_t::BUTTON_LEFT, line_range{4, -1})) {
×
463
        nci.id = '\r';
×
464
        nci.eff_text[0] = '\r';
×
465
        this->list_input_handle_key(tc, nci);
×
466
    }
467

468
    return false;
×
469
}
470

471
void
472
files_sub_source::text_selection_changed(textview_curses& tc)
21✔
473
{
474
    auto sel = files_model::from_selection(tc.get_selection());
21✔
475
    std::vector<attr_line_t> details;
21✔
476

477
    this->fss_details_mtime = std::chrono::microseconds::zero();
21✔
478
    sel.match(
21✔
479
        [](files_model::no_selection) {},
×
480

481
        [&details](const files_model::error_selection& es) {
×
482
            auto path = std::filesystem::path(es.sb_iter.first);
×
483

484
            details.emplace_back(
×
485
                attr_line_t().appendf(FMT_STRING("Full path: {}"), path));
×
486
            details.emplace_back(attr_line_t("  ").append(
×
487
                lnav::roles::error(es.sb_iter.second)));
×
488
        },
×
489
        [&details](const files_model::other_selection& os) {
×
490
            auto path = std::filesystem::path(os.sb_iter->first);
×
491

492
            details.emplace_back(
×
493
                attr_line_t().appendf(FMT_STRING("Full path: {}"), path));
×
494
            details.emplace_back(attr_line_t("  ").append("Match Details"_h3));
×
495
            for (const auto& msg : os.sb_iter->second.ofd_details) {
×
496
                auto msg_al = msg.to_attr_line();
×
497

498
                for (auto& msg_line : msg_al.rtrim().split_lines()) {
×
499
                    msg_line.insert(0, 4, ' ');
×
500
                    details.emplace_back(msg_line);
×
501
                }
502
            }
503
        },
×
504
        [&details, this](const files_model::file_selection& fs) {
21✔
505
            static constexpr auto NAME_WIDTH = 17;
506

507
            auto lf = *fs.sb_iter;
8✔
508
            this->fss_details_mtime = lf->get_modified_time();
8✔
509
            auto path = lf->get_filename();
8✔
510
            auto actual_path = lf->get_actual_path();
8✔
511
            auto format = lf->get_format();
8✔
512
            const auto& open_opts = lf->get_open_options();
8✔
513

514
            details.emplace_back(attr_line_t()
16✔
515
                                     .appendf(FMT_STRING("{}"), path.filename())
40✔
516
                                     .with_attr_for_all(VC_STYLE.value(
16✔
517
                                         text_attrs::with_bold())));
16✔
518
            details.emplace_back(
8✔
519
                attr_line_t("  ")
8✔
520
                    .append(":open_file_folder:"_emoji)
16✔
521
                    .appendf(FMT_STRING(" {}"), path.parent_path()));
32✔
522

523
            const auto& decompress_err = lf->get_decompress_error();
8✔
524
            if (!decompress_err.empty()) {
8✔
525
                details.emplace_back(
×
526
                    attr_line_t("  ")
×
527
                        .append("Error"_h2)
×
528
                        .append(": ")
×
529
                        .append(lnav::roles::error(decompress_err)));
×
530
            }
531

532
            const auto notes = lf->get_notes();
8✔
533
            if (!notes.empty()) {
8✔
534
                details.emplace_back(
×
535
                    attr_line_t("  ").append("Notes"_h2).append(":"));
×
536
                for (const auto& note_um : notes.values()) {
×
537
                    for (const auto& note_line :
×
538
                         note_um.to_attr_line().split_lines())
×
539
                    {
540
                        details.emplace_back(
×
541
                            attr_line_t("    ").append(note_line));
×
542
                    }
543
                }
544
            }
545
            details.emplace_back(attr_line_t("  ").append("General"_h2));
8✔
546
            if (actual_path) {
8✔
547
                if (path != actual_path.value()) {
8✔
548
                    auto line = attr_line_t()
×
549
                                    .append("Actual Path"_h3)
×
550
                                    .right_justify(NAME_WIDTH)
×
551
                                    .append(": ")
×
552
                                    .append(lnav::roles::file(
×
553
                                        fmt::to_string(actual_path.value())))
×
554
                                    .move();
×
555
                    details.emplace_back(line);
×
556
                }
557
            } else {
558
                details.emplace_back(attr_line_t().append("  Piped"));
×
559
            }
560
            details.emplace_back(
8✔
561
                attr_line_t()
8✔
562
                    .append("MIME Type"_h3)
8✔
563
                    .right_justify(NAME_WIDTH)
8✔
564
                    .append(": ")
8✔
565
                    .append(fmt::to_string(lf->get_text_format())));
16✔
566
            auto ltime = std::chrono::seconds{
567
                convert_log_time_to_local(lf->get_stat().st_mtime)};
8✔
568
            auto ltime_str = lnav::to_rfc3339_string(ltime, 'T');
8✔
569
            details.emplace_back(attr_line_t()
16✔
570
                                     .append("Last Modified"_h3)
8✔
571
                                     .right_justify(NAME_WIDTH)
8✔
572
                                     .append(": ")
8✔
573
                                     .append(ltime_str));
574
            details.emplace_back(
8✔
575
                attr_line_t()
8✔
576
                    .append("Size"_h3)
8✔
577
                    .right_justify(NAME_WIDTH)
8✔
578
                    .append(": ")
8✔
579
                    .append(humanize::file_size(lf->get_index_size(),
16✔
580
                                                humanize::alignment::none)));
581
            if (lf->is_compressed()) {
8✔
582
                details.emplace_back(attr_line_t()
×
583
                                         .append("Compressed Size"_h3)
×
584
                                         .right_justify(NAME_WIDTH)
×
585
                                         .append(": ")
×
586
                                         .append(humanize::file_size(
×
587
                                             lf->get_stat().st_size,
×
588
                                             humanize::alignment::none)));
589
            }
590

591
            details.emplace_back(attr_line_t()
16✔
592
                                     .append("Lines"_h3)
8✔
593
                                     .right_justify(NAME_WIDTH)
8✔
594
                                     .append(": ")
8✔
595
                                     .append(lnav::roles::number(fmt::format(
16✔
596
                                         FMT_STRING("{:L}"), lf->size()))));
32✔
597
            if (format != nullptr && lf->size() > 0) {
8✔
598
                details.emplace_back(attr_line_t()
14✔
599
                                         .append("Time Range"_h3)
7✔
600
                                         .right_justify(NAME_WIDTH)
7✔
601
                                         .append(": ")
7✔
602
                                         .append(lnav::to_rfc3339_string(
14✔
603
                                             lf->front().get_timeval(), 'T'))
7✔
604
                                         .append(" - ")
7✔
605
                                         .append(lnav::to_rfc3339_string(
14✔
606
                                             lf->back().get_timeval(), 'T')));
7✔
607
                details.emplace_back(
7✔
608
                    attr_line_t()
14✔
609
                        .append("Duration"_h3)
7✔
610
                        .right_justify(NAME_WIDTH)
7✔
611
                        .append(": ")
7✔
612
                        .append(humanize::time::duration::from_tv(
7✔
613
                                    lf->back().get_timeval()
7✔
614
                                    - lf->front().get_timeval())
14✔
615
                                    .to_string()));
14✔
616
            }
617

618
            auto file_options_opt = lf->get_file_options();
8✔
619
            if (file_options_opt) {
8✔
620
                auto& [path, file_options] = file_options_opt.value();
×
621

622
                details.emplace_back(attr_line_t()
×
623
                                         .append("Options Path"_h3)
×
624
                                         .right_justify(NAME_WIDTH)
×
625
                                         .append(": ")
×
626
                                         .append(lnav::roles::file(path)));
×
627
                if (file_options.fo_default_zone.pp_value != nullptr) {
×
628
                    details.emplace_back(attr_line_t()
×
629
                                             .append("Timezone"_h3)
×
630
                                             .right_justify(NAME_WIDTH)
×
631
                                             .append(": ")
×
632
                                             .append(lnav::roles::symbol(
×
633
                                                 file_options.fo_default_zone
634
                                                     .pp_value->name())));
×
635
                }
636
            }
637

638
            {
639
                details.emplace_back(
8✔
640
                    attr_line_t()
8✔
641
                        .append("Provenance"_h3)
8✔
642
                        .right_justify(NAME_WIDTH)
8✔
643
                        .append(": ")
8✔
644
                        .append(fmt::to_string(open_opts.loo_source)));
16✔
645
                details.emplace_back(
8✔
646
                    attr_line_t()
8✔
647
                        .append("Flags"_h3)
8✔
648
                        .right_justify(NAME_WIDTH)
8✔
649
                        .append(": ")
8✔
650
                        .append(lnav::roles::for_flag(
16✔
651
                            "\u2022", open_opts.loo_include_in_session))
8✔
652
                        .append("include-in-session")
8✔
653
                        .append(", ")
8✔
654
                        .append(lnav::roles::for_flag(
16✔
655
                            "\u2022", open_opts.loo_detect_format))
8✔
656
                        .append("detect-format"));
657
                if (open_opts.loo_init_location.valid()) {
8✔
658
                    auto loc = open_opts.loo_init_location.match(
659
                        [](default_for_text_format) {
8✔
660
                            return std::string("default");
16✔
661
                        },
662
                        [](file_location_tail) { return std::string("tail"); },
×
663
                        [](int vl) {
×
664
                            return fmt::format(FMT_STRING("L{:L}"), vl);
×
665
                        },
666
                        [](std::string section) { return section; });
8✔
667
                    details.emplace_back(attr_line_t()
16✔
668
                                             .append("Initial Location"_h3)
8✔
669
                                             .right_justify(NAME_WIDTH)
8✔
670
                                             .append(": ")
8✔
671
                                             .append(loc));
672
                }
8✔
673
            }
674

675
            {
676
                auto line = attr_line_t("  ")
16✔
677
                                .append("Log Format"_h2)
8✔
678
                                .append(": ")
8✔
679
                                .move();
8✔
680

681
                if (format != nullptr) {
8✔
682
                    line.append(lnav::roles::identifier(
7✔
683
                        format->get_name().to_string()));
14✔
684
                } else {
685
                    line.append("(none)"_comment);
1✔
686
                }
687
                details.emplace_back(line);
8✔
688
            }
8✔
689

690
            if (lf->get_format_ptr() != nullptr) {
8✔
691
                const auto um = lnav::console::user_message::info(
692
                    attr_line_t("The file contents matched this log format and "
14✔
693
                                "will be shown in the LOG view"));
7✔
694
                um.to_attr_line().rtrim().split_lines()
14✔
695
                    | lnav::itertools::for_each([&details](const auto& al) {
14✔
696
                          details.emplace_back(attr_line_t("    ").append(al));
7✔
697
                      });
7✔
698
            } else {
7✔
699
                auto cmd
700
                    = attr_line_t("lnav -m format ")
1✔
701
                          .append("format-name",
1✔
702
                                  VC_STYLE.value(text_attrs::with_underline()))
2✔
703
                          .append(" test ")
1✔
704
                          .append(lf->get_filename())
1✔
705
                          .with_attr_for_all(
1✔
706
                              VC_ROLE.value(role_t::VCR_QUOTED_CODE));
2✔
707
                const auto um
708
                    = lnav::console::user_message::info(
2✔
709

710
                          "The file contents did not match any log "
711
                          "formats and can be accessed in the TEXT view")
712
                          .with_help(attr_line_t("If you expected this file to "
2✔
713
                                                 "match a particular "
714
                                                 "format, you can run the "
715
                                                 "following to get more "
716
                                                 "details:\n  ")
717
                                         .append(cmd));
1✔
718
                um.to_attr_line().rtrim().split_lines()
2✔
719
                    | lnav::itertools::for_each([&details](const auto& al) {
2✔
720
                          details.emplace_back(attr_line_t("    ").append(al));
3✔
721
                      });
3✔
722
            }
1✔
723

724
            const auto& match_msgs = lf->get_format_match_messages();
8✔
725
            details.emplace_back(
8✔
726
                attr_line_t("    ").append("Match Details"_h3));
16✔
727
            for (const auto& msg : match_msgs) {
95✔
728
                auto msg_al = msg.to_attr_line();
87✔
729

730
                for (auto& msg_line : msg_al.rtrim().split_lines()) {
400✔
731
                    msg_line.insert(0, 6, ' ');
313✔
732
                    details.emplace_back(msg_line);
313✔
733
                }
87✔
734
            }
87✔
735

736
            const auto& ili = lf->get_invalid_line_info();
8✔
737
            if (ili.ili_total > 0) {
8✔
738
                auto dotdot = ili.ili_total
×
739
                    > logfile::invalid_line_info::MAX_INVALID_LINES;
740
                auto um = lnav::console::user_message::error(
×
741
                              attr_line_t()
×
742
                                  .append(lnav::roles::number(
×
743
                                      fmt::to_string(ili.ili_total)))
×
744
                                  .append(" line(s) are not handled by the "
×
745
                                          "format and considered invalid"))
746
                              .with_note(attr_line_t("Lines: ")
×
747
                                             .join(ili.ili_lines, ", ")
×
748
                                             .append(dotdot ? ", ..." : ""))
×
749
                              .with_help(
×
750
                                  "The format may be need to be adjusted to "
751
                                  "capture these lines");
×
752

753
                details.emplace_back(attr_line_t());
×
754
                um.to_attr_line().rtrim().split_lines()
×
755
                    | lnav::itertools::for_each([&details](const auto& al) {
×
756
                          details.emplace_back(attr_line_t("    ").append(al));
×
757
                      });
×
758
            }
759

760
            {
761
                const auto& meta = lf->get_embedded_metadata();
8✔
762

763
                if (!meta.empty()) {
8✔
764
                    details.emplace_back(
×
765
                        attr_line_t("  ").append("Embedded Metadata:"_h2));
×
766
                    for (const auto& [index, meta_pair] :
×
767
                         lnav::itertools::enumerate(meta))
×
768
                    {
769
                        details.emplace_back(
×
770
                            attr_line_t("  ")
×
771
                                .appendf(FMT_STRING("[{}]"), index)
×
772
                                .append(" ")
×
773
                                .append(lnav::roles::h3(meta_pair.first)));
×
774
                        details.emplace_back(
×
775
                            attr_line_t("      MIME Type: ")
×
776
                                .append(lnav::roles::symbol(fmt::to_string(
×
777
                                    meta_pair.second.m_format))));
×
778
                        line_range lr{6, -1};
×
779
                        details.emplace_back(attr_line_t().with_attr(
×
780
                            string_attr{lr, VC_GRAPHIC.value(NCACS_HLINE)}));
×
781
                        const auto val_sf = string_fragment::from_str(
×
782
                            meta_pair.second.m_value);
×
783
                        for (const auto val_line : val_sf.split_lines()) {
×
784
                            details.emplace_back(
×
785
                                attr_line_t("      ").append(val_line));
×
786
                        }
787
                    }
788
                }
789
            }
790
        });
8✔
791

792
    this->fss_details_source->replace_with(details);
21✔
793
}
21✔
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