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

tstack / lnav / 23066365063-2835

13 Mar 2026 06:12PM UTC coverage: 68.989% (+0.01%) from 68.979%
23066365063-2835

push

github

tstack
[cmds] add sticky headers

Related to #1385

114 of 146 new or added lines in 6 files covered. (78.08%)

13 existing lines in 4 files now uncovered.

52488 of 76082 relevant lines covered (68.99%)

520548.33 hits per line

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

68.93
/src/field_overlay_source.cc
1
/**
2
 * Copyright (c) 2015, Timothy Stack
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 * * Redistributions of source code must retain the above copyright notice, this
10
 * list of conditions and the following disclaimer.
11
 * * Redistributions in binary form must reproduce the above copyright notice,
12
 * this list of conditions and the following disclaimer in the documentation
13
 * and/or other materials provided with the distribution.
14
 * * Neither the name of Timothy Stack nor the names of its contributors
15
 * may be used to endorse or promote products derived from this software
16
 * without specific prior written permission.
17
 *
18
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
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 "field_overlay_source.hh"
31

32
#include <curl/curl.h>
33

34
#include "base/attr_line.builder.hh"
35
#include "base/auto_mem.hh"
36
#include "base/humanize.time.hh"
37
#include "base/injector.hh"
38
#include "base/snippet_highlighters.hh"
39
#include "command_executor.hh"
40
#include "config.h"
41
#include "lnav.exec-phase.hh"
42
#include "log.annotate.hh"
43
#include "log_format_ext.hh"
44
#include "log_vtab_impl.hh"
45
#include "md2attr_line.hh"
46
#include "msg.text.hh"
47
#include "ptimec.hh"
48
#include "readline_highlighters.hh"
49
#include "sql_util.hh"
50
#include "vtab_module.hh"
51
#include "vtab_module_json.hh"
52

53
using namespace md4cpp::literals;
54
using namespace lnav::roles::literals;
55

56
json_string extract(const char* str);
57

58
void
59
field_overlay_source::build_field_lines(const listview_curses& lv,
1,879✔
60
                                        vis_line_t row)
61
{
62
    auto* vtab_manager = injector::get<log_vtab_manager*>();
1,879✔
63
    auto& lss = this->fos_lss;
1,879✔
64
    auto& vc = view_colors::singleton();
1,879✔
65

66
    this->fos_lines.clear();
1,879✔
67
    this->fos_row_to_field_meta.clear();
1,879✔
68

69
    if (lss.text_line_count() == 0) {
1,879✔
70
        this->fos_log_helper.clear();
×
71

72
        return;
1,870✔
73
    }
74

75
    auto cl = lss.at(row);
1,879✔
76
    auto file = lss.find(cl);
1,879✔
77
    auto ll = file->begin() + cl;
1,879✔
78
    auto format = file->get_format();
1,879✔
79
    bool display = false;
1,879✔
80

81
    if (ll->is_time_skewed()
1,879✔
82
        || ll->get_msg_level() == log_level_t::LEVEL_INVALID)
1,879✔
83
    {
84
        display = true;
1✔
85
    }
86
    if (!this->fos_contexts.empty()) {
1,879✔
87
        display = display || this->fos_contexts.top().c_show;
1,879✔
88
    }
89

90
    if (!ll->is_valid_utf()) {
1,879✔
91
        auto sub_opts = subline_options{};
3✔
92
        sub_opts.scrub_invalid_utf8 = false;
3✔
93
        auto read_res = file->read_line(ll, sub_opts);
3✔
94
        if (read_res.isOk()) {
3✔
95
            auto sbr = read_res.unwrap();
3✔
96
            attr_line_t al;
3✔
97
            attr_line_builder alb(al);
3✔
98

99
            alb.append_as_hexdump(sbr.to_string_fragment());
3✔
100
            this->fos_lines.emplace_back(
3✔
101
                attr_line_t("  ")
6✔
102
                    .append(
3✔
103
                        "Hex dump of line with invalid UTF-8 content"_table_header)
104
                    .move());
6✔
105
            al.split_lines(this->fos_lines);
3✔
106
            auto first_line = true;
3✔
107
            for (auto& al_line : this->fos_lines) {
12✔
108
                if (first_line) {
9✔
109
                    first_line = false;
3✔
110
                    continue;
3✔
111
                }
112
                al_line.insert(0, 4, ' ');
6✔
113
            }
114
        }
3✔
115
    }
3✔
116

117
    if (!display) {
1,879✔
118
        return;
1,858✔
119
    }
120

121
    if (!this->fos_log_helper.load_line(row)) {
21✔
122
        return;
11✔
123
    }
124

125
    if (ll->get_msg_level() == LEVEL_INVALID) {
10✔
126
        for (const auto& sattr : this->fos_log_helper.ldh_line_attrs) {
2✔
127
            if (sattr.sa_type != &SA_INVALID) {
1✔
128
                continue;
×
129
            }
130

131
            auto emsg = fmt::format(
132
                FMT_STRING("   Invalid log message: {}"),
2✔
133
                sattr.sa_value.get<decltype(SA_INVALID)::value_type>());
1✔
134
            auto al
135
                = attr_line_t(emsg)
2✔
136
                      .with_attr(string_attr(line_range{1, 2},
1✔
137
                                             VC_GRAPHIC.value(NCACS_LLCORNER)))
2✔
138
                      .with_attr(
1✔
139
                          string_attr(line_range{0, 22},
2✔
140
                                      VC_ROLE.value(role_t::VCR_INVALID_MSG)))
2✔
141
                      .move();
1✔
142
            this->fos_lines.emplace_back(al);
1✔
143
        }
1✔
144
    }
145

146
    char old_timestamp[64], curr_timestamp[64], orig_timestamp[64];
147
    timeval curr_tv, offset_tv, orig_tv, diff_tv = {0, 0};
10✔
148
    attr_line_t time_line;
10✔
149
    auto& time_str = time_line.get_string();
10✔
150
    line_range time_lr;
10✔
151
    off_t ts_len = sql_strftime(curr_timestamp,
10✔
152
                                sizeof(curr_timestamp),
153
                                ll->get_time<std::chrono::microseconds>(),
154
                                'T');
10✔
155
    {
156
        exttm tmptm;
10✔
157

158
        tmptm.et_flags |= ETF_ZONE_SET;
10✔
159
        tmptm.et_gmtoff
160
            = lnav::local_time_to_info(
20✔
161
                  date::local_seconds{ll->get_time<std::chrono::seconds>()})
10✔
162
                  .first.offset.count();
10✔
163
        ftime_z(curr_timestamp, ts_len, sizeof(curr_timestamp), tmptm);
10✔
164
        curr_timestamp[ts_len] = '\0';
10✔
165
    }
166

167
    if (ll->is_time_skewed()) {
10✔
168
        time_lr.lr_start = 1;
×
169
        time_lr.lr_end = 2;
×
170
        time_line.with_attr(
×
171
            string_attr(time_lr, VC_GRAPHIC.value(NCACS_LLCORNER)));
×
172
        time_str.append("   Out-Of-Time-Order Message");
×
173
        time_lr.lr_start = 3;
×
174
        time_lr.lr_end = time_str.length();
×
175
        time_line.with_attr(
×
176
            string_attr(time_lr, VC_ROLE.value(role_t::VCR_SKEWED_TIME)));
×
177
        time_str.append(" --");
×
178
    }
179

180
    time_str.append(" Received Time: ");
10✔
181
    time_lr.lr_start = time_str.length();
10✔
182
    time_str.append(curr_timestamp);
10✔
183
    time_lr.lr_end = time_str.length();
10✔
184
    time_line.with_attr(
10✔
185
        string_attr(time_lr, VC_STYLE.value(text_attrs::with_bold())));
20✔
186
    time_str.append(" \u2014 ");
10✔
187
    time_lr.lr_start = time_str.length();
10✔
188
    time_str.append(humanize::time::point::from_tv(ll->get_timeval())
20✔
189
                        .with_convert_to_local(true)
10✔
190
                        .as_precise_time_ago());
20✔
191
    time_lr.lr_end = time_str.length();
10✔
192
    time_line.with_attr(
10✔
193
        string_attr(time_lr, VC_STYLE.value(text_attrs::with_bold())));
20✔
194

195
    auto time_range = find_string_attr_range(
10✔
196
        this->fos_log_helper.ldh_line_attrs, &L_TIMESTAMP);
10✔
197

198
    curr_tv = this->fos_log_helper.ldh_line->get_timeval();
10✔
199
    if (ll->is_time_skewed() && time_range.lr_end != -1) {
10✔
200
        const char* time_src
201
            = this->fos_log_helper.ldh_line_values.lvv_sbr.get_data()
×
202
            + time_range.lr_start;
×
203
        timeval actual_tv;
204
        date_time_scanner dts;
×
205
        exttm tm;
×
206

207
        dts.set_base_time(format->lf_date_time.dts_base_time,
×
208
                          format->lf_date_time.dts_base_tm.et_tm);
×
209
        dts.dts_zoned_to_local = format->lf_date_time.dts_zoned_to_local;
×
210
        if (format->lf_date_time.scan(time_src,
×
211
                                      time_range.length(),
×
212
                                      format->get_timestamp_formats(),
213
                                      &tm,
214
                                      actual_tv)
215
            || dts.scan(time_src, time_range.length(), nullptr, &tm, actual_tv))
×
216
        {
217
            sql_strftime(
×
218
                orig_timestamp, sizeof(orig_timestamp), actual_tv, 'T');
219
            time_str.append(";  Actual Time: ");
×
220
            time_lr.lr_start = time_str.length();
×
221
            time_str.append(orig_timestamp);
×
222
            time_lr.lr_end = time_str.length();
×
223
            time_line.with_attr(
×
224
                string_attr(time_lr, VC_ROLE.value(role_t::VCR_SKEWED_TIME)));
×
225

226
            timersub(&curr_tv, &actual_tv, &diff_tv);
×
227
            time_str.append(";  Diff: ");
×
228
            time_lr.lr_start = time_str.length();
×
229
            time_str.append(
×
230
                humanize::time::duration::from_tv(diff_tv).to_string());
×
231
            time_lr.lr_end = time_str.length();
×
232
            time_line.with_attr(
×
233
                string_attr(time_lr, VC_STYLE.value(text_attrs::with_bold())));
×
234
        }
235
    }
236

237
    offset_tv = this->fos_log_helper.ldh_file->get_time_offset();
10✔
238
    timersub(&curr_tv, &offset_tv, &orig_tv);
10✔
239
    sql_strftime(old_timestamp, sizeof(old_timestamp), to_us(orig_tv), 'T');
10✔
240
    if (offset_tv.tv_sec || offset_tv.tv_usec) {
10✔
241
        time_str.append("  Pre-adjust Time: ");
×
242
        time_str.append(old_timestamp);
×
243
        fmt::format_to(std::back_inserter(time_str),
×
244
                       FMT_STRING("  Offset: {:+}.{:03}"),
×
245
                       offset_tv.tv_sec,
246
                       std::chrono::duration_cast<std::chrono::milliseconds>(
×
247
                           std::chrono::microseconds(offset_tv.tv_usec))
×
248
                           .count());
×
249
    }
250

251
    if (format->lf_date_time.dts_fmt_lock != -1) {
10✔
252
        const auto* ts_formats = format->get_timestamp_formats();
10✔
253
        if (ts_formats == nullptr) {
10✔
254
            ts_formats = PTIMEC_FORMAT_STR;
10✔
255
        }
256
        time_line.append("  Format: ")
10✔
257
            .append(lnav::roles::symbol(
20✔
258
                ts_formats[format->lf_date_time.dts_fmt_lock]))
10✔
259
            .append("  Default Zone: ");
10✔
260
        if (format->lf_date_time.dts_default_zone != nullptr) {
10✔
261
            time_line.append(lnav::roles::symbol(
×
262
                format->lf_date_time.dts_default_zone->name()));
×
263
        } else {
264
            time_line.append("none"_comment);
10✔
265
        }
266

267
        auto file_opts = file->get_file_options();
10✔
268
        if (file_opts) {
10✔
269
            time_line.append("  File Options: ")
×
270
                .append(lnav::roles::file(file_opts->first));
×
271
        }
272
    }
10✔
273

274
    if ((!this->fos_contexts.empty() && this->fos_contexts.top().c_show)
20✔
275
        || diff_tv.tv_sec > 0 || ll->is_time_skewed())
20✔
276
    {
277
        this->fos_lines.emplace_back(time_line);
9✔
278
    }
279

280
    if (this->fos_contexts.empty() || !this->fos_contexts.top().c_show) {
10✔
281
        return;
1✔
282
    }
283

284
    this->fos_log_helper.parse_body();
9✔
285
    auto anchor_opt = this->fos_lss.anchor_for_row(row);
9✔
286
    if (anchor_opt) {
9✔
287
        auto permalink
288
            = attr_line_t(" ")
9✔
289
                  .append("Permalink:"_h2)
9✔
290
                  .append("    ")
9✔
291
                  .append(lnav::roles::hyperlink(anchor_opt.value()));
9✔
292
        this->fos_row_to_field_meta.emplace(
18✔
293
            this->fos_lines.size(), row_info{std::nullopt, anchor_opt.value()});
9✔
294
        this->fos_lines.emplace_back(permalink);
9✔
295
    }
9✔
296

297
    this->fos_known_key_size = LOG_BODY.length();
9✔
298
    if (!this->fos_contexts.empty()) {
9✔
299
        this->fos_known_key_size += this->fos_contexts.top().c_prefix.length();
9✔
300
    }
301
    this->fos_unknown_key_size = 0;
9✔
302

303
    for (const auto& ldh_line_value :
9✔
304
         this->fos_log_helper.ldh_line_values.lvv_values)
96✔
305
    {
306
        auto& meta = ldh_line_value.lv_meta;
78✔
307
        int this_key_size = meta.lvm_name.size();
78✔
308

309
        if (!meta.lvm_column.is<logline_value_meta::table_column>()) {
78✔
310
            continue;
24✔
311
        }
312

313
        if (!this->fos_contexts.empty()) {
54✔
314
            this_key_size += this->fos_contexts.top().c_prefix.length();
54✔
315
        }
316
        if (meta.lvm_kind == value_kind_t::VALUE_STRUCT) {
54✔
317
            this_key_size += 9;
×
318
        }
319
        if (!meta.lvm_struct_name.empty()) {
54✔
320
            this_key_size += meta.lvm_struct_name.size() + 11;
×
321
        }
322
        this->fos_known_key_size
323
            = std::max(this->fos_known_key_size, this_key_size);
54✔
324
    }
325

326
    if (this->fos_log_helper.ldh_parser) {
9✔
327
        for (auto iter = this->fos_log_helper.ldh_parser->dp_pairs.begin();
9✔
328
             iter != this->fos_log_helper.ldh_parser->dp_pairs.end();
16✔
329
             ++iter)
7✔
330
        {
331
            auto colname = this->fos_log_helper.ldh_parser->get_element_string(
332
                iter->e_sub_elements->front());
7✔
333

334
            colname = this->fos_log_helper.ldh_namer->add_column(colname)
7✔
335
                          .to_string();
7✔
336
            this->fos_unknown_key_size
337
                = std::max(this->fos_unknown_key_size, (int) colname.length());
7✔
338
        }
7✔
339
    }
340

341
    auto lf = this->fos_log_helper.ldh_file->get_format();
9✔
342
    auto lffs = this->fos_log_helper.ldh_file->get_format_file_state();
9✔
343
    if (!lf->get_pattern_regex(lffs.lffs_pattern_locks, cl).empty()) {
9✔
344
        auto pattern_al
345
            = attr_line_t(" ")
8✔
346
                  .append("Pattern:"_h2)
8✔
347
                  .append("      ")
8✔
348
                  .append(lf->get_pattern_path(lffs.lffs_pattern_locks, cl))
16✔
349
                  .append(" = ");
8✔
350
        auto& pattern_str = pattern_al.get_string();
8✔
351
        int skip = pattern_str.length();
8✔
352
        pattern_str += lf->get_pattern_regex(lffs.lffs_pattern_locks, cl);
8✔
353
        lnav::snippets::regex_highlighter(
8✔
354
            pattern_al,
355
            pattern_al.length(),
8✔
356
            line_range{skip, (int) pattern_al.length()});
8✔
357
        this->fos_lines.emplace_back(pattern_al);
8✔
358
    }
8✔
359

360
    if (this->fos_log_helper.ldh_line_values.lvv_opid_value) {
9✔
361
        auto opid_al = attr_line_t(" ").append("Operation ID:"_h2).append(" ");
7✔
362

363
        auto opid_str
364
            = this->fos_log_helper.ldh_line_values.lvv_opid_value.value();
7✔
365
        opid_al.append(opid_str);
7✔
366
        switch (this->fos_log_helper.ldh_line_values.lvv_opid_provenance) {
7✔
367
            case logline_value_vector::opid_provenance::none:
×
368
                break;
×
369
            case logline_value_vector::opid_provenance::file:
7✔
370
                opid_al.append(" (extracted from log message)"_comment);
7✔
371
                break;
7✔
372
            case logline_value_vector::opid_provenance::user:
×
373
                opid_al.append(" (user-provided)"_comment);
×
374
                break;
×
375
        }
376
        this->fos_row_to_field_meta.emplace(this->fos_lines.size(),
7✔
377
                                            row_info{std::nullopt, opid_str});
14✔
378
        this->fos_lines.emplace_back(opid_al);
7✔
379
    }
7✔
380

381
    if (this->fos_log_helper.ldh_line_values.lvv_values.empty()) {
9✔
382
        this->fos_lines.emplace_back(" No known message fields");
×
383
    }
384

385
    const log_format* last_format = nullptr;
9✔
386

387
    for (const auto& lv : this->fos_log_helper.ldh_line_values.lvv_values) {
87✔
388
        const auto& meta = lv.lv_meta;
78✔
389
        if (!meta.lvm_format) {
78✔
390
            continue;
24✔
391
        }
392

393
        if (!meta.lvm_column.is<logline_value_meta::table_column>()) {
78✔
394
            continue;
24✔
395
        }
396

397
        auto* curr_format = meta.lvm_format.value();
54✔
398
        auto* curr_elf = dynamic_cast<external_log_format*>(curr_format);
54✔
399
        const auto format_name = curr_format->get_name().to_string();
54✔
400
        attr_line_t al;
54✔
401
        auto value_str = lv.to_string();
54✔
402

403
        if (curr_format != last_format) {
54✔
404
            this->fos_lines.emplace_back(" Known message fields for table "
9✔
405
                                         + format_name + ":");
18✔
406
            this->fos_lines.back().with_attr(
18✔
407
                string_attr(line_range(32, 32 + format_name.length()),
18✔
408
                            VC_STYLE.value(vc.attrs_for_ident(format_name)
27✔
409
                                           | text_attrs::style::bold)));
9✔
410
            last_format = curr_format;
9✔
411
        }
412

413
        std::string field_name, orig_field_name;
54✔
414
        line_range hl_range;
54✔
415
        al.append(" ").append("|", VC_GRAPHIC.value(NCACS_LTEE)).append(" ");
54✔
416
        if (meta.lvm_struct_name.empty()) {
54✔
417
            if (curr_elf && curr_elf->elf_body_field == meta.lvm_name) {
54✔
418
                field_name = LOG_BODY;
×
419
            } else if (curr_elf
54✔
420
                       && curr_elf->lf_timestamp_field == meta.lvm_name)
54✔
421
            {
422
                field_name = LOG_TIME;
×
423
            } else {
424
                field_name = meta.lvm_name.to_string();
54✔
425
            }
426
            orig_field_name = field_name;
54✔
427
            if (!this->fos_contexts.empty()) {
54✔
428
                field_name = this->fos_contexts.top().c_prefix + field_name;
54✔
429
            }
430
            if (meta.is_hidden()) {
54✔
431
                al.append("\u25c7"_comment);
3✔
432
            } else {
433
                al.append("\u25c6"_ok);
51✔
434
            }
435
            al.append(" ");
54✔
436

437
            switch (meta.to_chart_type()) {
54✔
438
                case chart_type_t::none:
10✔
439
                    al.append("   ");
10✔
440
                    break;
10✔
441
                case chart_type_t::hist:
44✔
442
                case chart_type_t::spectro:
443
                    al.append(":bar_chart:"_emoji).append(" ");
44✔
444
                    break;
44✔
445
            }
446
            auto prefix_len = al.column_width();
54✔
447
            hl_range.lr_start = al.get_string().length();
54✔
448
            al.append(field_name);
54✔
449
            hl_range.lr_end = al.get_string().length();
54✔
450
            al.pad_to(prefix_len + this->fos_known_key_size);
54✔
451

452
            this->fos_row_to_field_meta.emplace(this->fos_lines.size(),
54✔
453
                                                row_info{meta, value_str});
108✔
454
        } else {
455
            auto jget_str = lnav::sql::mprintf("jget(%s, '/%q')",
456
                                               meta.lvm_struct_name.get(),
457
                                               meta.lvm_name.get());
×
458
            hl_range.lr_start = al.get_string().length();
×
459
            al.append(jget_str.in());
×
460
            hl_range.lr_end = al.get_string().length();
×
461

462
            this->fos_row_to_field_meta.emplace(
×
463
                this->fos_lines.size(), row_info{std::nullopt, value_str});
×
464
        }
465
        readline_sql_highlighter_int(
54✔
466
            al, lnav::sql::dialect::sqlite, std::nullopt, hl_range);
467

468
        if (meta.lvm_kind == value_kind_t::VALUE_TIMESTAMP) {
54✔
469
            auto dts = curr_format->build_time_scanner();
×
470
            exttm tm;
×
471
            timeval tv;
472

473
            if (dts.scan(value_str.c_str(),
×
474
                         value_str.size(),
475
                         curr_format->get_timestamp_formats(),
476
                         &tm,
477
                         tv,
478
                         true))
479
            {
480
                char ts[64];
481
                tm.et_gmtoff = tm.et_orig_gmtoff;
×
482
                auto len = dts.ftime(
×
483
                    ts, sizeof(ts), curr_format->get_timestamp_formats(), tm);
484
                ts[len] = '\0';
×
485
                value_str = ts;
×
486
            }
487
        }
488

489
        al.append(" = ").append(scrub_ws(value_str.c_str()));
54✔
490

491
        this->fos_lines.emplace_back(al);
54✔
492

493
        if (meta.lvm_kind == value_kind_t::VALUE_STRUCT) {
54✔
494
            json_string js = extract(value_str.c_str());
×
495

496
            al.clear()
×
497
                .append("   extract(")
×
498
                .append(meta.lvm_name.get(),
×
499
                        VC_STYLE.value(vc.attrs_for_ident(meta.lvm_name)))
×
500
                .append(")")
×
501
                .append(this->fos_known_key_size - meta.lvm_name.size() - 9 + 3,
×
502
                        ' ')
503
                .append(" = ")
×
504
                .append(scrub_ws(string_fragment::from_bytes(js.js_content.in(),
×
505
                                                             js.js_len)));
506
            this->fos_lines.emplace_back(al);
×
507
            this->add_key_line_attrs(this->fos_known_key_size);
×
508
        }
509
    }
54✔
510

511
    if (!this->fos_log_helper.ldh_extra_json.empty()
9✔
512
        || !this->fos_log_helper.ldh_json_pairs.empty())
9✔
513
    {
514
        this->fos_lines.emplace_back(" JSON fields:");
1✔
515
    }
516

517
    for (const auto& extra_pair : this->fos_log_helper.ldh_extra_json) {
13✔
518
        auto qname = lnav::sql::mprintf("%Q", extra_pair.first.c_str());
4✔
519
        auto key_line = attr_line_t("   jget(log_raw_text, ")
4✔
520
                            .append(qname.in())
8✔
521
                            .append(")")
4✔
522
                            .move();
4✔
523
        readline_sql_highlighter(
4✔
524
            key_line, lnav::sql::dialect::sqlite, std::nullopt);
525
        auto key_size = key_line.length();
4✔
526
        key_line.append(" = ").append(scrub_ws(extra_pair.second));
4✔
527
        this->fos_row_to_field_meta.emplace(this->fos_lines.size(),
4✔
528
                                            row_info{
8✔
529
                                                std::nullopt,
530
                                                extra_pair.second,
4✔
531
                                            });
532
        this->fos_lines.emplace_back(key_line);
4✔
533
        this->add_key_line_attrs(key_size - 3);
4✔
534
    }
4✔
535

536
    for (const auto& jpairs_map : this->fos_log_helper.ldh_json_pairs) {
9✔
537
        const auto& jpairs = jpairs_map.second;
×
538

539
        for (size_t lpc = 0; lpc < jpairs.jwc_values.size(); lpc++) {
×
540
            auto key_line = attr_line_t("   ")
×
541
                                .append(this->fos_log_helper.format_json_getter(
×
542
                                    jpairs_map.first, lpc))
543
                                .move();
×
544
            readline_sql_highlighter(
×
545
                key_line, lnav::sql::dialect::sqlite, std::nullopt);
546
            auto key_size = key_line.length();
×
547
            key_line.append(" = ").append(
×
548
                scrub_ws(fmt::to_string(jpairs.jwc_values[lpc].second)));
×
549
            this->fos_row_to_field_meta.emplace(
×
550
                this->fos_lines.size(),
×
551
                row_info{std::nullopt,
×
552
                         fmt::to_string(jpairs.jwc_values[lpc].second)});
×
553
            this->fos_lines.emplace_back(key_line);
×
554
            this->add_key_line_attrs(key_size - 3);
×
555
        }
556
    }
557

558
    if (!this->fos_log_helper.ldh_xml_pairs.empty()) {
9✔
559
        this->fos_lines.emplace_back(" XML fields:");
1✔
560
    }
561

562
    for (const auto& xml_pair : this->fos_log_helper.ldh_xml_pairs) {
13✔
563
        auto qname = sql_quote_ident(xml_pair.first.first.get());
4✔
564
        auto xp_call = lnav::sql::mprintf(
565
            "xpath(%Q, %s.%s)",
566
            xml_pair.first.second.c_str(),
567
            this->fos_log_helper.ldh_file->get_format()->get_name().c_str(),
8✔
568
            qname.in());
8✔
569
        auto key_line = attr_line_t("   ").append(xp_call.in()).move();
4✔
570
        readline_sql_highlighter(
4✔
571
            key_line, lnav::sql::dialect::sqlite, std::nullopt);
572
        auto key_size = key_line.length();
4✔
573
        key_line.append(" = ").append(scrub_ws(xml_pair.second));
4✔
574
        this->fos_row_to_field_meta.emplace(
8✔
575
            this->fos_lines.size(), row_info{std::nullopt, xml_pair.second});
4✔
576
        this->fos_lines.emplace_back(key_line);
4✔
577
        this->add_key_line_attrs(key_size - 3);
4✔
578
    }
4✔
579

580
    if (this->fos_log_helper.ldh_src_ref) {
9✔
581
        auto src_link
582
            = attr_line_t()
×
583
                  .append(lnav::roles::file(
×
584
                      this->fos_log_helper.ldh_src_ref->sr_path.string()))
×
585
                  .append(":")
×
586
                  .append(lnav::roles::number(fmt::to_string(
×
587
                      this->fos_log_helper.ldh_src_ref->sr_line_number)));
×
588
        auto_mem<char> src_href(curl_free);
×
589
        {
590
            auto frag
591
                = fmt::format(FMT_STRING("L{}"),
×
592
                              this->fos_log_helper.ldh_src_ref->sr_line_number);
×
593
            auto_mem<CURLU> cu(curl_url_cleanup);
×
594
            cu = curl_url();
×
595

596
            curl_url_set(cu, CURLUPART_SCHEME, "file", CURLU_URLENCODE);
×
597
            curl_url_set(cu,
×
598
                         CURLUPART_PATH,
599
                         this->fos_log_helper.ldh_src_ref->sr_path.c_str(),
×
600
                         CURLU_URLENCODE);
601
            curl_url_set(cu, CURLUPART_FRAGMENT, frag.c_str(), CURLU_URLENCODE);
×
602
            curl_url_get(cu, CURLUPART_URL, src_href.out(), 0);
×
603
        }
604
        auto src_link_with_href = attr_line_t().append(
×
605
            lnav::string::attrs::href(src_link, src_href.in()));
×
606
        this->fos_lines.emplace_back(
×
607
            attr_line_t(" Variables from ")
×
608
                .append(lnav::roles::hyperlink(src_link_with_href)));
×
609
        for (const auto& [name, value] : this->fos_log_helper.ldh_src_vars) {
×
610
            auto al = attr_line_t("   ")
×
611
                          .append(lnav::roles::variable(name))
×
612
                          .append(" = ")
×
613
                          .append(value);
×
614
            this->fos_row_to_field_meta.emplace(this->fos_lines.size(),
×
615
                                                row_info{
×
616
                                                    std::nullopt,
617
                                                    value,
618
                                                });
619
            this->fos_lines.emplace_back(al);
×
620
        }
621
    } else if (!this->fos_log_helper.ldh_parser
9✔
622
               || this->fos_log_helper.ldh_parser->dp_pairs.empty())
9✔
623
    {
624
        this->fos_lines.emplace_back(
5✔
625
            attr_line_t(" ").append("No discovered message fields"_comment));
10✔
626
    } else {
627
        this->fos_lines.emplace_back(
4✔
628
            " Discovered fields for logline table from message "
629
            "format: ");
630
        this->fos_lines.back().with_attr(
8✔
631
            string_attr(line_range(23, 23 + 7),
8✔
632
                        VC_STYLE.value(vc.attrs_for_ident("logline"))));
8✔
633
        auto& al = this->fos_lines.back();
4✔
634
        auto& disc_str = al.get_string();
4✔
635

636
        al.with_attr(string_attr(line_range(disc_str.length(), -1),
4✔
637
                                 VC_STYLE.value(text_attrs::with_bold())));
8✔
638
        disc_str.append(this->fos_log_helper.ldh_msg_format);
4✔
639

640
        auto iter = this->fos_log_helper.ldh_parser->dp_pairs.begin();
4✔
641
        for (size_t lpc = 0;
4✔
642
             lpc < this->fos_log_helper.ldh_parser->dp_pairs.size();
11✔
643
             lpc++, ++iter)
7✔
644
        {
645
            auto name = this->fos_log_helper.ldh_namer->cn_names[lpc];
7✔
646
            auto val = this->fos_log_helper.ldh_parser->get_element_string(
647
                iter->e_sub_elements->back());
7✔
648
            attr_line_t al(fmt::format(FMT_STRING("   {} = {}"), name, val));
28✔
649

650
            al.with_attr(string_attr(
7✔
651
                line_range(3, 3 + name.length()),
7✔
652
                VC_STYLE.value(vc.attrs_for_ident(name.to_string()))));
14✔
653

654
            this->fos_row_to_field_meta.emplace(this->fos_lines.size(),
7✔
655
                                                row_info{std::nullopt, val});
14✔
656
            this->fos_lines.emplace_back(al);
7✔
657
            this->add_key_line_attrs(
7✔
658
                this->fos_unknown_key_size,
659
                lpc == (this->fos_log_helper.ldh_parser->dp_pairs.size() - 1));
7✔
660
        }
7✔
661
    }
662

663
    std::set<std::string> matching_tables;
18✔
664
    for (const auto& [name_sf, vt] : *vtab_manager) {
883✔
665
        if (vt->matches(this->fos_log_helper.ldh_line_values)) {
874✔
666
            matching_tables.emplace(name_sf.to_string());
3✔
667
        }
668
    }
669
    if (!matching_tables.empty()) {
9✔
670
        this->fos_lines.emplace_back(
3✔
671
            attr_line_t()
6✔
672
                .append(
6✔
673
                    matching_tables.size() == 1
3✔
674
                        ? " The following SQL table contains this line: "_comment
3✔
675
                        : " The following SQL tables contains this line: "_comment)
×
676
                .join(matching_tables,
677
                      VC_ROLE.value(role_t::VCR_VARIABLE),
6✔
678
                      ", "));
679
    }
680
}
3,750✔
681

682
void
683
field_overlay_source::build_meta_line(const listview_curses& lv,
19,023✔
684
                                      std::vector<attr_line_t>& dst,
685
                                      vis_line_t row)
686
{
687
    auto line_meta_opt = this->fos_lss.find_bookmark_metadata(row);
19,023✔
688

689
    if (!this->fos_contexts.empty()
19,023✔
690
        && this->fos_contexts.top().c_show_applicable_annotations)
19,023✔
691
    {
692
        if (this->fos_index_generation != this->fos_lss.lss_index_generation) {
490✔
693
            this->fos_anno_cache.clear();
1✔
694
            this->fos_index_generation = this->fos_lss.lss_index_generation;
1✔
695
        }
696

697
        auto file_and_line = this->fos_lss.find_line_with_file(row);
490✔
698

699
        if (file_and_line && !file_and_line->second->is_continued()) {
490✔
700
            auto get_res = this->fos_anno_cache.get(row);
10✔
701
            if (get_res) {
10✔
702
                auto anno_val = get_res.value();
7✔
703
                if (anno_val) {
7✔
704
                    dst.emplace_back(anno_val.value());
×
705
                }
706
            } else {
7✔
707
                auto applicable_anno = lnav::log::annotate::applicable(row);
3✔
708
                if (!applicable_anno.empty()
3✔
709
                    && (!line_meta_opt
3✔
710
                        || line_meta_opt.value()
×
711
                               ->bm_annotations.la_pairs.empty()))
×
712
                {
713
                    auto anno_msg
714
                        = attr_line_t(" ")
×
715
                              .append(":memo:"_emoji)
×
716
                              .append(" Annotations available, ")
×
717
                              .append(lv.get_selection() == row
×
718
                                          ? "use "
719
                                          : "focus on this line and use ")
720
                              .append(":annotate"_quoted_code)
×
721
                              .append(" to apply them")
×
722
                              .append(lv.get_selection() == row
×
723
                                          ? " to this line"
724
                                          : "")
725
                              .with_attr_for_all(
×
726
                                  VC_ROLE.value(role_t::VCR_COMMENT))
×
727
                              .move();
×
728

729
                    this->fos_anno_cache.put(row, anno_msg);
×
730
                    dst.emplace_back(anno_msg);
×
731
                } else {
×
732
                    this->fos_anno_cache.put(row, std::nullopt);
3✔
733
                }
734
            }
3✔
735
        }
10✔
736
    }
490✔
737

738
    if (!line_meta_opt) {
19,023✔
739
        return;
18,748✔
740
    }
741
    const auto* tc = dynamic_cast<const textview_curses*>(&lv);
275✔
742
    auto& vc = view_colors::singleton();
275✔
743
    const auto& line_meta = *(line_meta_opt.value());
275✔
744
    size_t filename_width = this->fos_lss.get_filename_offset();
275✔
745

746
    auto file_and_line = this->fos_lss.find_line_with_file(row);
275✔
747
    auto* format = file_and_line->first->get_format_ptr();
275✔
748
    auto field_states = format->get_field_states();
275✔
749
    auto show_opid = false;
275✔
750
    auto field_iter = field_states.find(log_format::LOG_OPID_STR);
275✔
751
    if (field_iter != field_states.end() && !field_iter->second.is_hidden()) {
275✔
752
        show_opid = true;
266✔
753
    }
754
    if (row == tc->get_selection() && !this->fos_contexts.empty()
363✔
755
        && this->fos_contexts.top().c_show)
363✔
756
    {
757
        show_opid = true;
1✔
758
    }
759
    if (show_opid && !line_meta.bm_opid.empty()) {
275✔
760
        auto al = attr_line_t()
104✔
761
                      .append(" Op ID: "_table_header)
104✔
762
                      .append(lnav::roles::identifier(line_meta.bm_opid))
208✔
763
                      .move();
104✔
764

765
        dst.emplace_back(al);
104✔
766
    }
104✔
767

768
    if (!line_meta.bm_comment.empty()) {
275✔
769
        const auto* lead = line_meta.bm_tags.empty() ? " \u2514 " : " \u251c ";
44✔
770
        md2attr_line mdal;
44✔
771
        attr_line_t al;
44✔
772

773
        auto comment_id = intern_string::lookup(fmt::format(
44✔
774
            FMT_STRING("{}-line{}-comment"),
132✔
775
            file_and_line->first->get_filename().filename().string(),
88✔
776
            std::distance(file_and_line->first->begin(),
88✔
777
                          file_and_line->second)));
44✔
778
        mdal.with_source_id(comment_id);
44✔
779
        if (tc->tc_interactive) {
44✔
780
            mdal.add_lnav_script_icons();
×
781
        }
782
        auto parse_res = md4cpp::parse(line_meta.bm_comment, mdal);
44✔
783
        if (parse_res.isOk()) {
44✔
784
            al = parse_res.unwrap();
44✔
785
        } else {
786
            log_error("%d: cannot convert comment to markdown: %s",
×
787
                      (int) row,
788
                      parse_res.unwrapErr().c_str());
789
            al = line_meta.bm_comment;
×
790
        }
791

792
        auto comment_lines = al.rtrim().split_lines();
44✔
793
        if (comment_lines.back().empty()) {
44✔
794
            comment_lines.pop_back();
×
795
        }
796
        for (size_t lpc = 0; lpc < comment_lines.size(); lpc++) {
94✔
797
            auto& comment_line = comment_lines[lpc];
50✔
798

799
            if (lpc == 0 && comment_line.empty()) {
50✔
800
                continue;
×
801
            }
802
            comment_line.with_attr_for_all(VC_ROLE.value(role_t::VCR_COMMENT));
50✔
803
            comment_line.insert(
50✔
804
                0, lpc == comment_lines.size() - 1 ? lead : " \u2502 ");
50✔
805
            comment_line.insert(0, filename_width, ' ');
50✔
806
            if (tc != nullptr) {
50✔
807
                const auto& hl = tc->get_highlights();
50✔
808
                auto hl_iter = hl.find({highlight_source_t::PREVIEW, "search"});
50✔
809

810
                if (hl_iter != hl.end()) {
50✔
811
                    hl_iter->second.annotate(comment_line,
12✔
812
                                             line_range{(int) filename_width});
12✔
813
                }
814
            }
815

816
            dst.emplace_back(comment_line);
50✔
817
        }
818
    }
44✔
819
    if (!line_meta.bm_tags.empty()) {
275✔
820
        attr_line_t al;
53✔
821

822
        al.with_string(" \u2514");
53✔
823
        for (const auto& str : line_meta.bm_tags) {
106✔
824
            al.append(1, ' ').append(str,
106✔
825
                                     VC_STYLE.value(vc.attrs_for_ident(str)));
106✔
826
        }
827

828
        al.insert(0, filename_width, ' ');
53✔
829
        if (tc != nullptr) {
53✔
830
            const auto& hl = tc->get_highlights();
53✔
831
            auto hl_iter = hl.find({highlight_source_t::PREVIEW, "search"});
53✔
832

833
            if (hl_iter != hl.end()) {
53✔
834
                hl_iter->second.annotate(al, line_range{(int) filename_width});
11✔
835
            }
836
        }
837
        dst.emplace_back(al);
53✔
838
    }
53✔
839
    if (!line_meta.bm_annotations.la_pairs.empty()) {
275✔
840
        for (const auto& anno_pair : line_meta.bm_annotations.la_pairs) {
32✔
841
            attr_line_t al;
16✔
842
            md2attr_line mdal;
16✔
843

844
            mdal.add_lnav_script_icons();
16✔
845
            dst.push_back(
16✔
846
                attr_line_t()
16✔
847
                    .append(filename_width, ' ')
16✔
848
                    .appendf(FMT_STRING(" \u251c {}:"), anno_pair.first)
64✔
849
                    .with_attr_for_all(VC_ROLE.value(role_t::VCR_COMMENT)));
16✔
850

851
            auto parse_res = md4cpp::parse(anno_pair.second, mdal);
16✔
852
            if (parse_res.isOk()) {
16✔
853
                al.append(parse_res.unwrap());
16✔
854
            } else {
855
                log_error("%d: cannot convert annotation to markdown: %s",
×
856
                          (int) row,
857
                          parse_res.unwrapErr().c_str());
858
                al.append(anno_pair.second);
×
859
            }
860

861
            auto anno_lines = al.rtrim().split_lines();
16✔
862
            if (anno_lines.back().empty()) {
16✔
863
                anno_lines.pop_back();
×
864
            }
865
            for (size_t lpc = 0; lpc < anno_lines.size(); lpc++) {
56✔
866
                auto& anno_line = anno_lines[lpc];
40✔
867

868
                if (lpc == 0 && anno_line.empty()) {
40✔
869
                    continue;
4✔
870
                }
871
                // anno_line.with_attr_for_all(VC_ROLE.value(role_t::VCR_COMMENT));
872
                anno_line.insert(0,
36✔
873
                                 lpc == anno_lines.size() - 1
36✔
874
                                     ? " \u2570 "_comment
36✔
875
                                     : " \u2502 "_comment);
20✔
876
                anno_line.insert(0, filename_width, ' ');
36✔
877
                if (tc != nullptr) {
36✔
878
                    const auto& hl = tc->get_highlights();
36✔
879
                    auto hl_iter
880
                        = hl.find({highlight_source_t::PREVIEW, "search"});
36✔
881

882
                    if (hl_iter != hl.end()) {
36✔
883
                        hl_iter->second.annotate(
×
884
                            anno_line, line_range{(int) filename_width});
×
885
                    }
886
                }
887

888
                dst.emplace_back(anno_line);
36✔
889
            }
890
        }
16✔
891
    }
892
}
275✔
893

894
void
895
field_overlay_source::add_key_line_attrs(int key_size, bool last_line)
15✔
896
{
897
    auto& sa = this->fos_lines.back().get_attrs();
15✔
898
    auto lr = line_range{1, 2};
15✔
899

900
    const auto* graphic = (last_line ? NCACS_LLCORNER : NCACS_LTEE);
15✔
901
    sa.emplace_back(lr, VC_GRAPHIC.value(graphic));
15✔
902

903
    lr.lr_start = 3 + key_size + 3;
15✔
904
    lr.lr_end = -1;
15✔
905
    sa.emplace_back(lr, VC_STYLE.value(text_attrs::with_bold()));
15✔
906
}
15✔
907

908
void
909
field_overlay_source::list_value_for_overlay(
19,023✔
910
    const listview_curses& lv,
911
    vis_line_t row,
912
    std::vector<attr_line_t>& value_out)
913
{
914
    // log_debug("value for overlay %d", row);
915
    if (row == lv.get_selection()) {
19,023✔
916
        this->build_field_lines(lv, row);
1,879✔
917
        value_out = this->fos_lines;
1,879✔
918
    }
919
    this->build_meta_line(lv, value_out, row);
19,023✔
920
}
19,023✔
921

922
static void
923
apply_status_attrs(std::vector<attr_line_t>& lines)
46,479✔
924
{
925
    for (auto& al : lines) {
93,041✔
926
        al.with_attr_for_all(VC_ROLE.value(role_t::VCR_STATUS));
46,562✔
927
    }
928
    lines.back().with_attr_for_all(
92,958✔
929
        VC_STYLE.value(text_attrs::with_underline()));
92,958✔
930
}
46,479✔
931

932
bool
933
field_overlay_source::list_static_overlay(const listview_curses& lv,
46,941✔
934
                                          media_t media,
935
                                          int y,
936
                                          int bottom,
937
                                          attr_line_t& value_out)
938
{
939
    auto& exec_phase = injector::get<lnav::exec_phase&>();
46,941✔
940
    if (media != media_t::display) {
46,941✔
941
        auto& tc = dynamic_cast<textview_curses&>(
187✔
942
            const_cast<listview_curses&>(lv));
187✔
943
        const auto& sticky_bv
944
            = tc.get_bookmarks()[&textview_curses::BM_STICKY];
187✔
945
        if (!sticky_bv.empty()) {
187✔
946
            auto top = lv.get_top();
5✔
947
            int sticky_index = 0;
5✔
948
            for (auto iter = sticky_bv.bv_tree.begin();
5✔
949
                 iter != sticky_bv.bv_tree.end();
9✔
950
                 ++iter)
4✔
951
            {
952
                if (*iter >= top) {
7✔
NEW
953
                    break;
×
954
                }
955
                if (y == sticky_index) {
7✔
956
                    tc.textview_value_for_row(*iter, value_out);
3✔
957
                    value_out.with_attr_for_all(
3✔
958
                        VC_ROLE.value(role_t::VCR_STATUS));
6✔
959
                    auto next_iter = std::next(iter);
3✔
960
                    if (next_iter == sticky_bv.bv_tree.end()
3✔
961
                        || *next_iter >= top)
3✔
962
                    {
963
                        value_out.with_attr_for_all(
2✔
964
                            VC_STYLE.value(text_attrs::with_underline()));
4✔
965
                    }
966
                    return true;
3✔
967
                }
968
                sticky_index++;
4✔
969
            }
970
        }
971
        return false;
184✔
972
    }
973

974
    const std::vector<attr_line_t>* lines = nullptr;
46,754✔
975
    if (exec_phase.spinning_up()) {
46,754✔
976
        auto msg
977
            = lnav::console::user_message::info("Files are being indexed...");
46,395✔
978
        this->fos_static_lines = msg.to_attr_line().split_lines();
46,395✔
979
        this->fos_static_lines_state.clear();
46,395✔
980
        apply_status_attrs(this->fos_static_lines);
46,395✔
981
        lines = &this->fos_static_lines;
46,395✔
982
    } else if (this->fos_lss.text_line_count() == 0) {
46,754✔
983
        if (this->fos_lss.is_indexing_in_progress()
83✔
984
            || this->fos_lss.is_rebuild_forced())
83✔
985
        {
986
            auto msg = lnav::console::user_message::info(
987
                "Log messages are being indexed...");
×
988
            this->fos_static_lines = msg.to_attr_line().split_lines();
×
989
            this->fos_static_lines_state.clear();
×
990
        } else if (this->fos_lss.file_count() > 0) {
83✔
991
            hasher h;
×
992
            this->fos_lss.update_filter_hash_state(h);
×
993
            auto curr_state = h.to_array();
×
994
            if (this->fos_static_lines.empty()
×
995
                || curr_state != this->fos_static_lines_state)
×
996
            {
997
                auto msg = lnav::console::user_message::info(
998
                    "All log messages are currently hidden");
×
999
                auto hidden_file_count = size_t{0};
×
1000
                for (const auto& ld : this->fos_lss) {
×
1001
                    if (ld->get_file_ptr() == nullptr) {
×
1002
                        continue;
×
1003
                    }
1004
                    if (!ld->is_visible()) {
×
1005
                        hidden_file_count += 1;
×
1006
                    }
1007
                }
1008
                if (hidden_file_count > 0) {
×
1009
                    msg.with_note(attr_line_t()
×
1010
                                      .append(lnav::roles::number(
×
1011
                                          fmt::to_string(hidden_file_count)))
×
1012
                                      .append(" file(s) are hidden"));
×
1013
                }
1014
                auto min_time = this->fos_lss.get_min_row_time();
×
1015
                if (min_time) {
×
1016
                    msg.with_note(attr_line_t("Logs before ")
×
1017
                                      .append_quoted(lnav::to_rfc3339_string(
×
1018
                                          min_time.value()))
×
1019
                                      .append(" are not being shown"));
×
1020
                }
1021
                auto max_time = this->fos_lss.get_max_row_time();
×
1022
                if (max_time) {
×
1023
                    msg.with_note(attr_line_t("Logs after ")
×
1024
                                      .append_quoted(lnav::to_rfc3339_string(
×
1025
                                          max_time.value()))
×
1026
                                      .append(" are not being shown"));
×
1027
                }
1028
                if (this->fos_lss.get_min_log_level()
×
1029
                    > log_level_t::LEVEL_UNKNOWN)
×
1030
                {
1031
                    msg.with_note(
×
1032
                        attr_line_t("Logs with a level below ")
×
1033
                            .append_quoted(
×
1034
                                level_names[this->fos_lss.get_min_log_level()])
×
1035
                            .append(" are not being shown"));
×
1036
                }
1037
                auto& fs = this->fos_lss.get_filters();
×
1038
                for (const auto& filt : fs) {
×
1039
                    auto hits = this->fos_lss.get_filtered_count_for(
×
1040
                        filt->get_index());
1041
                    if (filt->get_type() == text_filter::EXCLUDE && hits == 0) {
×
1042
                        continue;
×
1043
                    }
1044
                    auto cmd = attr_line_t(":" + filt->to_command());
×
1045
                    readline_command_highlighter(cmd, std::nullopt);
×
1046
                    msg.with_note(
×
1047
                        attr_line_t("Filter ")
×
1048
                            .append_quoted(cmd)
×
1049
                            .append(" matched ")
×
1050
                            .append(lnav::roles::number(fmt::to_string(hits)))
×
1051
                            .append(" message(s) "));
×
1052
                }
1053
                if (this->fos_lss.get_marked_only()) {
×
1054
                    msg.with_note(attr_line_t("The ")
×
1055
                                      .append_quoted(lnav::roles::keyword(
×
1056
                                          ":hide-unmarked-lines"))
1057
                                      .append(" command was used and no "
×
1058
                                              "unfiltered lines are marked"));
1059
                }
1060
                this->fos_static_lines = msg.to_attr_line().split_lines();
×
1061
                this->fos_static_lines_state = curr_state;
×
1062
            }
1063

1064
        } else if (this->fos_tss.empty()) {
83✔
1065
            this->fos_static_lines = *lnav::messages::view::no_files();
83✔
1066
        } else {
1067
            this->fos_static_lines = *lnav::messages::view::only_text_files();
×
1068
        }
1069
        apply_status_attrs(this->fos_static_lines);
83✔
1070
        lines = &this->fos_static_lines;
83✔
1071
    } else if (y == 0) {
276✔
1072
        lines = &this->fos_static_lines;
15✔
1073
        auto top = lv.get_top();
15✔
1074
        if (top < vis_line_t(this->fos_lss.text_line_count())) {
15✔
1075
            auto cl = this->fos_lss.at(top);
15✔
1076
            auto& tc = dynamic_cast<textview_curses&>(
15✔
1077
                const_cast<listview_curses&>(lv));
15✔
1078
            auto has_sticky
1079
                = !tc.get_bookmarks()[&textview_curses::BM_STICKY].empty();
15✔
1080
            if (has_sticky || !this->fos_header_line
15✔
1081
                || this->fos_header_line.value() != cl
14✔
1082
                || !this->fos_header_line_context
14✔
1083
                || this->fos_header_line_context.value()
28✔
1084
                    != this->fos_lss.get_line_context()
14✔
1085
                || this->fos_header_has_time_offset
14✔
1086
                    != this->fos_lss.is_time_offset_enabled()
14✔
1087
                || this->fos_header_has_time_preview
30✔
1088
                    != (this->fos_lss.ttt_preview_min_time.has_value()
14✔
1089
                        || this->fos_lss.ttt_preview_max_time.has_value()))
14✔
1090
            {
1091
                auto file_and_line_pair
1092
                    = this->fos_lss.find_line_with_file(top);
1✔
1093
                if (file_and_line_pair) {
1✔
1094
                    const auto& [lf, line] = file_and_line_pair.value();
1✔
1095
                    auto first_line = line;
1✔
1096
                    auto header_top = top;
1✔
1097
                    while (first_line->is_continued()) {
3✔
1098
                        --first_line;
2✔
1099
                        header_top -= 1_vl;
2✔
1100
                    }
1101
                    if (header_top == top) {
1✔
1102
                        header_top -= 1_vl;
×
1103
                    }
1104
                    this->fos_static_lines.clear();
1✔
1105

1106
                    // Add sticky header lines that are above the viewport
1107
                    const auto& sticky_bv
1108
                        = tc.get_bookmarks()[&textview_curses::BM_STICKY];
1✔
1109
                    for (const auto& sticky_vl : sticky_bv.bv_tree) {
1✔
NEW
1110
                        if (sticky_vl >= top) {
×
NEW
1111
                            break;
×
1112
                        }
NEW
1113
                        auto al = attr_line_t();
×
NEW
1114
                        tc.textview_value_for_row(sticky_vl, al);
×
NEW
1115
                        this->fos_static_lines.emplace_back(al);
×
1116
                    }
1117

1118
                    if (header_top < 0_vl) {
1✔
1119
                        auto al = attr_line_t();
×
1120
                        auto filtered_before
1121
                            = this->fos_lss.get_filtered_before();
×
1122
                        if (filtered_before > 0) {
×
1123
                            al.append(" ")
×
1124
                                .append(lnav::roles::number(
×
1125
                                    fmt::to_string(filtered_before)))
×
1126
                                .append(
×
1127
                                    " message(s) above here have been filtered "
1128
                                    "out");
1129
                        } else {
1130
                            al.append(" No messages above here");
×
1131
                        }
1132
                        al.with_attr_for_all(
×
1133
                            VC_STYLE.value(text_attrs::with_italic()));
×
1134
                        this->fos_static_lines.emplace_back(al);
×
1135
                    } else {
×
1136
                        auto al = attr_line_t();
1✔
1137
                        tc.textview_value_for_row(header_top, al);
1✔
1138
                        this->fos_static_lines.emplace_back(al);
1✔
1139
                    }
1✔
1140
                    apply_status_attrs(this->fos_static_lines);
1✔
1141
                    this->fos_header_line = cl;
1✔
1142
                    this->fos_header_line_context
1143
                        = this->fos_lss.get_line_context();
1✔
1144
                    this->fos_header_has_time_offset
1145
                        = this->fos_lss.is_time_offset_enabled();
1✔
1146
                    this->fos_header_has_time_preview
1147
                        = (this->fos_lss.ttt_preview_min_time.has_value()
2✔
1148
                           || this->fos_lss.ttt_preview_max_time.has_value());
1✔
1149
                }
1150
            }
1✔
1151
        } else {
1152
            this->fos_static_lines.resize(1);
×
1153
            this->fos_static_lines[0].clear();
×
1154
        }
1155
    }
1156

1157
    if (lines != nullptr && y < (ssize_t) lines->size()) {
46,754✔
1158
        value_out = lines->at(y);
2,517✔
1159
        return true;
2,517✔
1160
    }
1161

1162
    return false;
44,237✔
1163
}
1164

1165
std::optional<attr_line_t>
1166
field_overlay_source::list_header_for_overlay(const listview_curses& lv,
21✔
1167
                                              media_t media,
1168
                                              vis_line_t vl)
1169
{
1170
    attr_line_t retval;
21✔
1171

1172
    retval.append(this->fos_lss.get_filename_offset(), ' ');
21✔
1173
    if (this->fos_contexts.top().c_show) {
21✔
1174
        retval.appendf(FMT_STRING("\u258C Line {:L} parser details."),
12✔
1175
                       (int) vl);
4✔
1176
        if (media == media_t::display) {
4✔
1177
            retval.append("  Press ")
×
1178
                .append("p"_hotkey)
×
1179
                .append(" to hide this panel.");
×
1180
        }
1181
    } else {
1182
        retval.append("\u258C Line ")
17✔
1183
            .append(
17✔
1184
                lnav::roles::number(fmt::format(FMT_STRING("{:L}"), (int) vl)))
85✔
1185
            .append(" metadata");
17✔
1186
    }
1187

1188
    if (media == media_t::display) {
21✔
1189
        if (lv.get_overlay_selection()) {
×
1190
            retval.append("  ")
×
1191
                .append("SPC"_hotkey)
×
1192
                .append(": hide/show field  ")
×
1193
                .append("c"_hotkey)
×
1194
                .append(": copy field value  ")
×
1195
                .append("Esc"_hotkey)
×
1196
                .append(": exit this panel");
×
1197
        } else {
1198
            retval.append("  Press ")
×
1199
                .append("CTRL-]"_hotkey)
×
1200
                .append(" to focus on this panel");
×
1201
        }
1202
    }
1203

1204
    return retval;
42✔
1205
}
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