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

tstack / lnav / 20281835752-2752

16 Dec 2025 08:31PM UTC coverage: 68.903% (+0.03%) from 68.87%
20281835752-2752

push

github

tstack
[tests] update test data

51677 of 75000 relevant lines covered (68.9%)

434192.37 hits per line

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

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

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

55
json_string extract(const char* str);
56

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

65
    this->fos_lines.clear();
1,745✔
66
    this->fos_row_to_field_meta.clear();
1,745✔
67

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

71
        return;
1,736✔
72
    }
73

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

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

89
    if (!display) {
1,745✔
90
        return;
1,726✔
91
    }
92

93
    if (!this->fos_log_helper.load_line(row)) {
19✔
94
        return;
9✔
95
    }
96

97
    if (ll->get_msg_level() == LEVEL_INVALID) {
10✔
98
        for (const auto& sattr : this->fos_log_helper.ldh_line_attrs) {
2✔
99
            if (sattr.sa_type != &SA_INVALID) {
1✔
100
                continue;
×
101
            }
102

103
            auto emsg = fmt::format(
104
                FMT_STRING("   Invalid log message: {}"),
2✔
105
                sattr.sa_value.get<decltype(SA_INVALID)::value_type>());
1✔
106
            auto al
107
                = attr_line_t(emsg)
2✔
108
                      .with_attr(string_attr(line_range{1, 2},
1✔
109
                                             VC_GRAPHIC.value(NCACS_LLCORNER)))
2✔
110
                      .with_attr(
1✔
111
                          string_attr(line_range{0, 22},
2✔
112
                                      VC_ROLE.value(role_t::VCR_INVALID_MSG)))
2✔
113
                      .move();
1✔
114
            this->fos_lines.emplace_back(al);
1✔
115
        }
1✔
116
    }
117

118
    char old_timestamp[64], curr_timestamp[64], orig_timestamp[64];
119
    timeval curr_tv, offset_tv, orig_tv, diff_tv = {0, 0};
10✔
120
    attr_line_t time_line;
10✔
121
    auto& time_str = time_line.get_string();
10✔
122
    line_range time_lr;
10✔
123
    off_t ts_len = sql_strftime(curr_timestamp,
10✔
124
                                sizeof(curr_timestamp),
125
                                ll->get_time<std::chrono::microseconds>(),
126
                                'T');
10✔
127
    {
128
        exttm tmptm;
10✔
129

130
        tmptm.et_flags |= ETF_ZONE_SET;
10✔
131
        tmptm.et_gmtoff
132
            = lnav::local_time_to_info(
20✔
133
                  date::local_seconds{ll->get_time<std::chrono::seconds>()})
10✔
134
                  .first.offset.count();
10✔
135
        ftime_z(curr_timestamp, ts_len, sizeof(curr_timestamp), tmptm);
10✔
136
        curr_timestamp[ts_len] = '\0';
10✔
137
    }
138

139
    if (ll->is_time_skewed()) {
10✔
140
        time_lr.lr_start = 1;
×
141
        time_lr.lr_end = 2;
×
142
        time_line.with_attr(
×
143
            string_attr(time_lr, VC_GRAPHIC.value(NCACS_LLCORNER)));
×
144
        time_str.append("   Out-Of-Time-Order Message");
×
145
        time_lr.lr_start = 3;
×
146
        time_lr.lr_end = time_str.length();
×
147
        time_line.with_attr(
×
148
            string_attr(time_lr, VC_ROLE.value(role_t::VCR_SKEWED_TIME)));
×
149
        time_str.append(" --");
×
150
    }
151

152
    time_str.append(" Received Time: ");
10✔
153
    time_lr.lr_start = time_str.length();
10✔
154
    time_str.append(curr_timestamp);
10✔
155
    time_lr.lr_end = time_str.length();
10✔
156
    time_line.with_attr(
10✔
157
        string_attr(time_lr, VC_STYLE.value(text_attrs::with_bold())));
20✔
158
    time_str.append(" \u2014 ");
10✔
159
    time_lr.lr_start = time_str.length();
10✔
160
    time_str.append(humanize::time::point::from_tv(ll->get_timeval())
20✔
161
                        .with_convert_to_local(true)
10✔
162
                        .as_precise_time_ago());
20✔
163
    time_lr.lr_end = time_str.length();
10✔
164
    time_line.with_attr(
10✔
165
        string_attr(time_lr, VC_STYLE.value(text_attrs::with_bold())));
20✔
166

167
    auto time_range = find_string_attr_range(
10✔
168
        this->fos_log_helper.ldh_line_attrs, &L_TIMESTAMP);
10✔
169

170
    curr_tv = this->fos_log_helper.ldh_line->get_timeval();
10✔
171
    if (ll->is_time_skewed() && time_range.lr_end != -1) {
10✔
172
        const char* time_src
173
            = this->fos_log_helper.ldh_line_values.lvv_sbr.get_data()
×
174
            + time_range.lr_start;
×
175
        timeval actual_tv;
176
        date_time_scanner dts;
×
177
        exttm tm;
×
178

179
        dts.set_base_time(format->lf_date_time.dts_base_time,
×
180
                          format->lf_date_time.dts_base_tm.et_tm);
×
181
        dts.dts_zoned_to_local = format->lf_date_time.dts_zoned_to_local;
×
182
        if (format->lf_date_time.scan(time_src,
×
183
                                      time_range.length(),
×
184
                                      format->get_timestamp_formats(),
185
                                      &tm,
186
                                      actual_tv)
187
            || dts.scan(time_src, time_range.length(), nullptr, &tm, actual_tv))
×
188
        {
189
            sql_strftime(
×
190
                orig_timestamp, sizeof(orig_timestamp), actual_tv, 'T');
191
            time_str.append(";  Actual Time: ");
×
192
            time_lr.lr_start = time_str.length();
×
193
            time_str.append(orig_timestamp);
×
194
            time_lr.lr_end = time_str.length();
×
195
            time_line.with_attr(
×
196
                string_attr(time_lr, VC_ROLE.value(role_t::VCR_SKEWED_TIME)));
×
197

198
            timersub(&curr_tv, &actual_tv, &diff_tv);
×
199
            time_str.append(";  Diff: ");
×
200
            time_lr.lr_start = time_str.length();
×
201
            time_str.append(
×
202
                humanize::time::duration::from_tv(diff_tv).to_string());
×
203
            time_lr.lr_end = time_str.length();
×
204
            time_line.with_attr(
×
205
                string_attr(time_lr, VC_STYLE.value(text_attrs::with_bold())));
×
206
        }
207
    }
208

209
    offset_tv = this->fos_log_helper.ldh_file->get_time_offset();
10✔
210
    timersub(&curr_tv, &offset_tv, &orig_tv);
10✔
211
    sql_strftime(old_timestamp, sizeof(old_timestamp), to_us(orig_tv), 'T');
10✔
212
    if (offset_tv.tv_sec || offset_tv.tv_usec) {
10✔
213
        time_str.append("  Pre-adjust Time: ");
×
214
        time_str.append(old_timestamp);
×
215
        fmt::format_to(std::back_inserter(time_str),
×
216
                       FMT_STRING("  Offset: {:+}.{:03}"),
×
217
                       offset_tv.tv_sec,
218
                       std::chrono::duration_cast<std::chrono::milliseconds>(
×
219
                           std::chrono::microseconds(offset_tv.tv_usec))
×
220
                           .count());
×
221
    }
222

223
    if (format->lf_date_time.dts_fmt_lock != -1) {
10✔
224
        const auto* ts_formats = format->get_timestamp_formats();
10✔
225
        if (ts_formats == nullptr) {
10✔
226
            ts_formats = PTIMEC_FORMAT_STR;
10✔
227
        }
228
        time_line.append("  Format: ")
10✔
229
            .append(lnav::roles::symbol(
20✔
230
                ts_formats[format->lf_date_time.dts_fmt_lock]))
10✔
231
            .append("  Default Zone: ");
10✔
232
        if (format->lf_date_time.dts_default_zone != nullptr) {
10✔
233
            time_line.append(lnav::roles::symbol(
×
234
                format->lf_date_time.dts_default_zone->name()));
×
235
        } else {
236
            time_line.append("none"_comment);
10✔
237
        }
238

239
        auto file_opts = file->get_file_options();
10✔
240
        if (file_opts) {
10✔
241
            time_line.append("  File Options: ")
×
242
                .append(lnav::roles::file(file_opts->first));
×
243
        }
244
    }
10✔
245

246
    if ((!this->fos_contexts.empty() && this->fos_contexts.top().c_show)
20✔
247
        || diff_tv.tv_sec > 0 || ll->is_time_skewed())
20✔
248
    {
249
        this->fos_lines.emplace_back(time_line);
9✔
250
    }
251

252
    if (this->fos_contexts.empty() || !this->fos_contexts.top().c_show) {
10✔
253
        return;
1✔
254
    }
255

256
    this->fos_log_helper.parse_body();
9✔
257
    auto anchor_opt = this->fos_lss.anchor_for_row(row);
9✔
258
    if (anchor_opt) {
9✔
259
        auto permalink
260
            = attr_line_t(" ")
9✔
261
                  .append("Permalink:"_h2)
9✔
262
                  .append("    ")
9✔
263
                  .append(lnav::roles::hyperlink(anchor_opt.value()));
9✔
264
        this->fos_row_to_field_meta.emplace(
18✔
265
            this->fos_lines.size(), row_info{std::nullopt, anchor_opt.value()});
9✔
266
        this->fos_lines.emplace_back(permalink);
9✔
267
    }
9✔
268

269
    this->fos_known_key_size = LOG_BODY.length();
9✔
270
    if (!this->fos_contexts.empty()) {
9✔
271
        this->fos_known_key_size += this->fos_contexts.top().c_prefix.length();
9✔
272
    }
273
    this->fos_unknown_key_size = 0;
9✔
274

275
    for (const auto& ldh_line_value :
9✔
276
         this->fos_log_helper.ldh_line_values.lvv_values)
96✔
277
    {
278
        auto& meta = ldh_line_value.lv_meta;
78✔
279
        int this_key_size = meta.lvm_name.size();
78✔
280

281
        if (!meta.lvm_column.is<logline_value_meta::table_column>()) {
78✔
282
            continue;
24✔
283
        }
284

285
        if (!this->fos_contexts.empty()) {
54✔
286
            this_key_size += this->fos_contexts.top().c_prefix.length();
54✔
287
        }
288
        if (meta.lvm_kind == value_kind_t::VALUE_STRUCT) {
54✔
289
            this_key_size += 9;
×
290
        }
291
        if (!meta.lvm_struct_name.empty()) {
54✔
292
            this_key_size += meta.lvm_struct_name.size() + 11;
×
293
        }
294
        this->fos_known_key_size
295
            = std::max(this->fos_known_key_size, this_key_size);
54✔
296
    }
297

298
    if (this->fos_log_helper.ldh_parser) {
9✔
299
        for (auto iter = this->fos_log_helper.ldh_parser->dp_pairs.begin();
9✔
300
             iter != this->fos_log_helper.ldh_parser->dp_pairs.end();
16✔
301
             ++iter)
7✔
302
        {
303
            auto colname = this->fos_log_helper.ldh_parser->get_element_string(
304
                iter->e_sub_elements->front());
7✔
305

306
            colname = this->fos_log_helper.ldh_namer->add_column(colname)
7✔
307
                          .to_string();
7✔
308
            this->fos_unknown_key_size
309
                = std::max(this->fos_unknown_key_size, (int) colname.length());
7✔
310
        }
7✔
311
    }
312

313
    auto lf = this->fos_log_helper.ldh_file->get_format();
9✔
314
    auto lffs = this->fos_log_helper.ldh_file->get_format_file_state();
9✔
315
    if (!lf->get_pattern_regex(lffs.lffs_pattern_locks, cl).empty()) {
9✔
316
        auto pattern_al
317
            = attr_line_t(" ")
8✔
318
                  .append("Pattern:"_h2)
8✔
319
                  .append("      ")
8✔
320
                  .append(lf->get_pattern_path(lffs.lffs_pattern_locks, cl))
16✔
321
                  .append(" = ");
8✔
322
        auto& pattern_str = pattern_al.get_string();
8✔
323
        int skip = pattern_str.length();
8✔
324
        pattern_str += lf->get_pattern_regex(lffs.lffs_pattern_locks, cl);
8✔
325
        lnav::snippets::regex_highlighter(
8✔
326
            pattern_al,
327
            pattern_al.length(),
8✔
328
            line_range{skip, (int) pattern_al.length()});
8✔
329
        this->fos_lines.emplace_back(pattern_al);
8✔
330
    }
8✔
331

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

335
        auto opid_str
336
            = this->fos_log_helper.ldh_line_values.lvv_opid_value.value();
7✔
337
        opid_al.append(opid_str);
7✔
338
        switch (this->fos_log_helper.ldh_line_values.lvv_opid_provenance) {
7✔
339
            case logline_value_vector::opid_provenance::none:
×
340
                break;
×
341
            case logline_value_vector::opid_provenance::file:
7✔
342
                opid_al.append(" (extracted from log message)"_comment);
7✔
343
                break;
7✔
344
            case logline_value_vector::opid_provenance::user:
×
345
                opid_al.append(" (user-provided)"_comment);
×
346
                break;
×
347
        }
348
        this->fos_row_to_field_meta.emplace(this->fos_lines.size(),
7✔
349
                                            row_info{std::nullopt, opid_str});
14✔
350
        this->fos_lines.emplace_back(opid_al);
7✔
351
    }
7✔
352

353
    if (this->fos_log_helper.ldh_line_values.lvv_values.empty()) {
9✔
354
        this->fos_lines.emplace_back(" No known message fields");
×
355
    }
356

357
    const log_format* last_format = nullptr;
9✔
358

359
    for (const auto& lv : this->fos_log_helper.ldh_line_values.lvv_values) {
87✔
360
        const auto& meta = lv.lv_meta;
78✔
361
        if (!meta.lvm_format) {
78✔
362
            continue;
24✔
363
        }
364

365
        if (!meta.lvm_column.is<logline_value_meta::table_column>()) {
78✔
366
            continue;
24✔
367
        }
368

369
        auto* curr_format = meta.lvm_format.value();
54✔
370
        auto* curr_elf = dynamic_cast<external_log_format*>(curr_format);
54✔
371
        const auto format_name = curr_format->get_name().to_string();
54✔
372
        attr_line_t al;
54✔
373
        auto value_str = lv.to_string();
54✔
374

375
        if (curr_format != last_format) {
54✔
376
            this->fos_lines.emplace_back(" Known message fields for table "
9✔
377
                                         + format_name + ":");
18✔
378
            this->fos_lines.back().with_attr(
18✔
379
                string_attr(line_range(32, 32 + format_name.length()),
18✔
380
                            VC_STYLE.value(vc.attrs_for_ident(format_name)
27✔
381
                                           | text_attrs::style::bold)));
9✔
382
            last_format = curr_format;
9✔
383
        }
384

385
        std::string field_name, orig_field_name;
54✔
386
        line_range hl_range;
54✔
387
        al.append(" ").append("|", VC_GRAPHIC.value(NCACS_LTEE)).append(" ");
54✔
388
        if (meta.lvm_struct_name.empty()) {
54✔
389
            if (curr_elf && curr_elf->elf_body_field == meta.lvm_name) {
54✔
390
                field_name = LOG_BODY;
×
391
            } else if (curr_elf
54✔
392
                       && curr_elf->lf_timestamp_field == meta.lvm_name)
54✔
393
            {
394
                field_name = LOG_TIME;
×
395
            } else {
396
                field_name = meta.lvm_name.to_string();
54✔
397
            }
398
            orig_field_name = field_name;
54✔
399
            if (!this->fos_contexts.empty()) {
54✔
400
                field_name = this->fos_contexts.top().c_prefix + field_name;
54✔
401
            }
402
            if (meta.is_hidden()) {
54✔
403
                al.append("\u25c7"_comment);
3✔
404
            } else {
405
                al.append("\u25c6"_ok);
51✔
406
            }
407
            al.append(" ");
54✔
408

409
            switch (meta.to_chart_type()) {
54✔
410
                case chart_type_t::none:
10✔
411
                    al.append("   ");
10✔
412
                    break;
10✔
413
                case chart_type_t::hist:
44✔
414
                case chart_type_t::spectro:
415
                    al.append(":bar_chart:"_emoji).append(" ");
44✔
416
                    break;
44✔
417
            }
418
            auto prefix_len = al.column_width();
54✔
419
            hl_range.lr_start = al.get_string().length();
54✔
420
            al.append(field_name);
54✔
421
            hl_range.lr_end = al.get_string().length();
54✔
422
            al.pad_to(prefix_len + this->fos_known_key_size);
54✔
423

424
            this->fos_row_to_field_meta.emplace(this->fos_lines.size(),
54✔
425
                                                row_info{meta, value_str});
108✔
426
        } else {
427
            auto jget_str = lnav::sql::mprintf("jget(%s, '/%q')",
428
                                               meta.lvm_struct_name.get(),
429
                                               meta.lvm_name.get());
×
430
            hl_range.lr_start = al.get_string().length();
×
431
            al.append(jget_str.in());
×
432
            hl_range.lr_end = al.get_string().length();
×
433

434
            this->fos_row_to_field_meta.emplace(
×
435
                this->fos_lines.size(), row_info{std::nullopt, value_str});
×
436
        }
437
        readline_sql_highlighter_int(
54✔
438
            al, lnav::sql::dialect::sqlite, std::nullopt, hl_range);
439

440
        al.append(" = ").append(scrub_ws(value_str.c_str()));
54✔
441

442
        this->fos_lines.emplace_back(al);
54✔
443

444
        if (meta.lvm_kind == value_kind_t::VALUE_STRUCT) {
54✔
445
            json_string js = extract(value_str.c_str());
×
446

447
            al.clear()
×
448
                .append("   extract(")
×
449
                .append(meta.lvm_name.get(),
×
450
                        VC_STYLE.value(vc.attrs_for_ident(meta.lvm_name)))
×
451
                .append(")")
×
452
                .append(this->fos_known_key_size - meta.lvm_name.size() - 9 + 3,
×
453
                        ' ')
454
                .append(" = ")
×
455
                .append(scrub_ws(string_fragment::from_bytes(js.js_content.in(),
×
456
                                                             js.js_len)));
457
            this->fos_lines.emplace_back(al);
×
458
            this->add_key_line_attrs(this->fos_known_key_size);
×
459
        }
460
    }
54✔
461

462
    if (!this->fos_log_helper.ldh_extra_json.empty()
9✔
463
        || !this->fos_log_helper.ldh_json_pairs.empty())
9✔
464
    {
465
        this->fos_lines.emplace_back(" JSON fields:");
1✔
466
    }
467

468
    for (const auto& extra_pair : this->fos_log_helper.ldh_extra_json) {
13✔
469
        auto qname = lnav::sql::mprintf("%Q", extra_pair.first.c_str());
4✔
470
        auto key_line = attr_line_t("   jget(log_raw_text, ")
4✔
471
                            .append(qname.in())
8✔
472
                            .append(")")
4✔
473
                            .move();
4✔
474
        readline_sql_highlighter(
4✔
475
            key_line, lnav::sql::dialect::sqlite, std::nullopt);
476
        auto key_size = key_line.length();
4✔
477
        key_line.append(" = ").append(scrub_ws(extra_pair.second));
4✔
478
        this->fos_row_to_field_meta.emplace(this->fos_lines.size(),
4✔
479
                                            row_info{
8✔
480
                                                std::nullopt,
481
                                                extra_pair.second,
4✔
482
                                            });
483
        this->fos_lines.emplace_back(key_line);
4✔
484
        this->add_key_line_attrs(key_size - 3);
4✔
485
    }
4✔
486

487
    for (const auto& jpairs_map : this->fos_log_helper.ldh_json_pairs) {
9✔
488
        const auto& jpairs = jpairs_map.second;
×
489

490
        for (size_t lpc = 0; lpc < jpairs.jwc_values.size(); lpc++) {
×
491
            auto key_line = attr_line_t("   ")
×
492
                                .append(this->fos_log_helper.format_json_getter(
×
493
                                    jpairs_map.first, lpc))
494
                                .move();
×
495
            readline_sql_highlighter(
×
496
                key_line, lnav::sql::dialect::sqlite, std::nullopt);
497
            auto key_size = key_line.length();
×
498
            key_line.append(" = ").append(
×
499
                scrub_ws(fmt::to_string(jpairs.jwc_values[lpc].second)));
×
500
            this->fos_row_to_field_meta.emplace(
×
501
                this->fos_lines.size(),
×
502
                row_info{std::nullopt,
×
503
                         fmt::to_string(jpairs.jwc_values[lpc].second)});
×
504
            this->fos_lines.emplace_back(key_line);
×
505
            this->add_key_line_attrs(key_size - 3);
×
506
        }
507
    }
508

509
    if (!this->fos_log_helper.ldh_xml_pairs.empty()) {
9✔
510
        this->fos_lines.emplace_back(" XML fields:");
1✔
511
    }
512

513
    for (const auto& xml_pair : this->fos_log_helper.ldh_xml_pairs) {
13✔
514
        auto qname = sql_quote_ident(xml_pair.first.first.get());
4✔
515
        auto xp_call = lnav::sql::mprintf(
516
            "xpath(%Q, %s.%s)",
517
            xml_pair.first.second.c_str(),
518
            this->fos_log_helper.ldh_file->get_format()->get_name().c_str(),
8✔
519
            qname.in());
8✔
520
        auto key_line = attr_line_t("   ").append(xp_call.in()).move();
4✔
521
        readline_sql_highlighter(
4✔
522
            key_line, lnav::sql::dialect::sqlite, std::nullopt);
523
        auto key_size = key_line.length();
4✔
524
        key_line.append(" = ").append(scrub_ws(xml_pair.second));
4✔
525
        this->fos_row_to_field_meta.emplace(
8✔
526
            this->fos_lines.size(), row_info{std::nullopt, xml_pair.second});
4✔
527
        this->fos_lines.emplace_back(key_line);
4✔
528
        this->add_key_line_attrs(key_size - 3);
4✔
529
    }
4✔
530

531
    if (this->fos_log_helper.ldh_src_ref) {
9✔
532
        auto src_link
533
            = attr_line_t()
×
534
                  .append(lnav::roles::file(
×
535
                      this->fos_log_helper.ldh_src_ref->sr_path.string()))
×
536
                  .append(":")
×
537
                  .append(lnav::roles::number(fmt::to_string(
×
538
                      this->fos_log_helper.ldh_src_ref->sr_line_number)));
×
539
        auto_mem<char> src_href(curl_free);
×
540
        {
541
            auto frag
542
                = fmt::format(FMT_STRING("L{}"),
×
543
                              this->fos_log_helper.ldh_src_ref->sr_line_number);
×
544
            auto_mem<CURLU> cu(curl_url_cleanup);
×
545
            cu = curl_url();
×
546

547
            curl_url_set(cu, CURLUPART_SCHEME, "file", CURLU_URLENCODE);
×
548
            curl_url_set(cu,
×
549
                         CURLUPART_PATH,
550
                         this->fos_log_helper.ldh_src_ref->sr_path.c_str(),
×
551
                         CURLU_URLENCODE);
552
            curl_url_set(cu, CURLUPART_FRAGMENT, frag.c_str(), CURLU_URLENCODE);
×
553
            curl_url_get(cu, CURLUPART_URL, src_href.out(), 0);
×
554
        }
555
        auto src_link_with_href = attr_line_t().append(
×
556
            lnav::string::attrs::href(src_link, src_href.in()));
×
557
        this->fos_lines.emplace_back(
×
558
            attr_line_t(" Variables from ")
×
559
                .append(lnav::roles::hyperlink(src_link_with_href)));
×
560
        for (const auto& [name, value] : this->fos_log_helper.ldh_src_vars) {
×
561
            auto al = attr_line_t("   ")
×
562
                          .append(lnav::roles::variable(name))
×
563
                          .append(" = ")
×
564
                          .append(value);
×
565
            this->fos_row_to_field_meta.emplace(this->fos_lines.size(),
×
566
                                                row_info{
×
567
                                                    std::nullopt,
568
                                                    value,
569
                                                });
570
            this->fos_lines.emplace_back(al);
×
571
        }
572
    } else if (!this->fos_log_helper.ldh_parser
9✔
573
               || this->fos_log_helper.ldh_parser->dp_pairs.empty())
9✔
574
    {
575
        this->fos_lines.emplace_back(
5✔
576
            attr_line_t(" ").append("No discovered message fields"_comment));
10✔
577
    } else {
578
        this->fos_lines.emplace_back(
4✔
579
            " Discovered fields for logline table from message "
580
            "format: ");
581
        this->fos_lines.back().with_attr(
8✔
582
            string_attr(line_range(23, 23 + 7),
8✔
583
                        VC_STYLE.value(vc.attrs_for_ident("logline"))));
8✔
584
        auto& al = this->fos_lines.back();
4✔
585
        auto& disc_str = al.get_string();
4✔
586

587
        al.with_attr(string_attr(line_range(disc_str.length(), -1),
4✔
588
                                 VC_STYLE.value(text_attrs::with_bold())));
8✔
589
        disc_str.append(this->fos_log_helper.ldh_msg_format);
4✔
590

591
        auto iter = this->fos_log_helper.ldh_parser->dp_pairs.begin();
4✔
592
        for (size_t lpc = 0;
4✔
593
             lpc < this->fos_log_helper.ldh_parser->dp_pairs.size();
11✔
594
             lpc++, ++iter)
7✔
595
        {
596
            auto name = this->fos_log_helper.ldh_namer->cn_names[lpc];
7✔
597
            auto val = this->fos_log_helper.ldh_parser->get_element_string(
598
                iter->e_sub_elements->back());
7✔
599
            attr_line_t al(fmt::format(FMT_STRING("   {} = {}"), name, val));
28✔
600

601
            al.with_attr(string_attr(
7✔
602
                line_range(3, 3 + name.length()),
7✔
603
                VC_STYLE.value(vc.attrs_for_ident(name.to_string()))));
14✔
604

605
            this->fos_row_to_field_meta.emplace(this->fos_lines.size(),
7✔
606
                                                row_info{std::nullopt, val});
14✔
607
            this->fos_lines.emplace_back(al);
7✔
608
            this->add_key_line_attrs(
7✔
609
                this->fos_unknown_key_size,
610
                lpc == (this->fos_log_helper.ldh_parser->dp_pairs.size() - 1));
7✔
611
        }
7✔
612
    }
613

614
    std::set<std::string> matching_tables;
18✔
615
    for (const auto& [name_sf, vt] : *vtab_manager) {
851✔
616
        if (vt->matches(this->fos_log_helper.ldh_line_values)) {
842✔
617
            matching_tables.emplace(name_sf.to_string());
3✔
618
        }
619
    }
620
    if (!matching_tables.empty()) {
9✔
621
        this->fos_lines.emplace_back(
3✔
622
            attr_line_t()
6✔
623
                .append(
6✔
624
                    matching_tables.size() == 1
3✔
625
                        ? " The following SQL table contains this line: "_comment
3✔
626
                        : " The following SQL tables contains this line: "_comment)
×
627
                .join(matching_tables,
628
                      VC_ROLE.value(role_t::VCR_VARIABLE),
6✔
629
                      ", "));
630
    }
631
}
3,482✔
632

633
void
634
field_overlay_source::build_meta_line(const listview_curses& lv,
17,927✔
635
                                      std::vector<attr_line_t>& dst,
636
                                      vis_line_t row)
637
{
638
    auto line_meta_opt = this->fos_lss.find_bookmark_metadata(row);
17,927✔
639

640
    if (!this->fos_contexts.empty()
17,927✔
641
        && this->fos_contexts.top().c_show_applicable_annotations)
17,927✔
642
    {
643
        if (this->fos_index_generation != this->fos_lss.lss_index_generation) {
450✔
644
            this->fos_anno_cache.clear();
1✔
645
            this->fos_index_generation = this->fos_lss.lss_index_generation;
1✔
646
        }
647

648
        auto file_and_line = this->fos_lss.find_line_with_file(row);
450✔
649

650
        if (file_and_line && !file_and_line->second->is_continued()) {
450✔
651
            auto get_res = this->fos_anno_cache.get(row);
9✔
652
            if (get_res) {
9✔
653
                auto anno_val = get_res.value();
6✔
654
                if (anno_val) {
6✔
655
                    dst.emplace_back(anno_val.value());
×
656
                }
657
            } else {
6✔
658
                auto applicable_anno = lnav::log::annotate::applicable(row);
3✔
659
                if (!applicable_anno.empty()
3✔
660
                    && (!line_meta_opt
3✔
661
                        || line_meta_opt.value()
×
662
                               ->bm_annotations.la_pairs.empty()))
×
663
                {
664
                    auto anno_msg
665
                        = attr_line_t(" ")
×
666
                              .append(":memo:"_emoji)
×
667
                              .append(" Annotations available, ")
×
668
                              .append(lv.get_selection() == row
×
669
                                          ? "use "
670
                                          : "focus on this line and use ")
671
                              .append(":annotate"_quoted_code)
×
672
                              .append(" to apply them")
×
673
                              .append(lv.get_selection() == row
×
674
                                          ? " to this line"
675
                                          : "")
676
                              .with_attr_for_all(
×
677
                                  VC_ROLE.value(role_t::VCR_COMMENT))
×
678
                              .move();
×
679

680
                    this->fos_anno_cache.put(row, anno_msg);
×
681
                    dst.emplace_back(anno_msg);
×
682
                } else {
×
683
                    this->fos_anno_cache.put(row, std::nullopt);
3✔
684
                }
685
            }
3✔
686
        }
9✔
687
    }
450✔
688

689
    if (!line_meta_opt) {
17,927✔
690
        return;
17,705✔
691
    }
692
    const auto* tc = dynamic_cast<const textview_curses*>(&lv);
222✔
693
    auto& vc = view_colors::singleton();
222✔
694
    const auto& line_meta = *(line_meta_opt.value());
222✔
695
    size_t filename_width = this->fos_lss.get_filename_offset();
222✔
696

697
    auto file_and_line = this->fos_lss.find_line_with_file(row);
222✔
698
    auto* format = file_and_line->first->get_format_ptr();
222✔
699
    auto field_states = format->get_field_states();
222✔
700
    auto show_opid = false;
222✔
701
    auto field_iter = field_states.find(log_format::LOG_OPID_STR);
222✔
702
    if (field_iter != field_states.end() && !field_iter->second.is_hidden()) {
222✔
703
        show_opid = true;
213✔
704
    }
705
    if (row == tc->get_selection() && !this->fos_contexts.empty()
301✔
706
        && this->fos_contexts.top().c_show)
301✔
707
    {
708
        show_opid = true;
1✔
709
    }
710
    if (show_opid && !line_meta.bm_opid.empty()) {
222✔
711
        auto al = attr_line_t()
65✔
712
                      .append(" Op ID: "_table_header)
65✔
713
                      .append(lnav::roles::identifier(line_meta.bm_opid))
130✔
714
                      .move();
65✔
715

716
        dst.emplace_back(al);
65✔
717
    }
65✔
718

719
    if (!line_meta.bm_comment.empty()) {
222✔
720
        const auto* lead = line_meta.bm_tags.empty() ? " \u2514 " : " \u251c ";
40✔
721
        md2attr_line mdal;
40✔
722
        attr_line_t al;
40✔
723

724
        auto comment_id = intern_string::lookup(fmt::format(
40✔
725
            FMT_STRING("{}-line{}-comment"),
120✔
726
            file_and_line->first->get_filename().filename().string(),
80✔
727
            std::distance(file_and_line->first->begin(),
80✔
728
                          file_and_line->second)));
40✔
729
        mdal.with_source_id(comment_id);
40✔
730
        if (tc->tc_interactive) {
40✔
731
            mdal.add_lnav_script_icons();
×
732
        }
733
        auto parse_res = md4cpp::parse(line_meta.bm_comment, mdal);
40✔
734
        if (parse_res.isOk()) {
40✔
735
            al = parse_res.unwrap();
40✔
736
        } else {
737
            log_error("%d: cannot convert comment to markdown: %s",
×
738
                      (int) row,
739
                      parse_res.unwrapErr().c_str());
740
            al = line_meta.bm_comment;
×
741
        }
742

743
        auto comment_lines = al.rtrim().split_lines();
40✔
744
        if (comment_lines.back().empty()) {
40✔
745
            comment_lines.pop_back();
×
746
        }
747
        for (size_t lpc = 0; lpc < comment_lines.size(); lpc++) {
86✔
748
            auto& comment_line = comment_lines[lpc];
46✔
749

750
            if (lpc == 0 && comment_line.empty()) {
46✔
751
                continue;
×
752
            }
753
            comment_line.with_attr_for_all(VC_ROLE.value(role_t::VCR_COMMENT));
46✔
754
            comment_line.insert(
46✔
755
                0, lpc == comment_lines.size() - 1 ? lead : " \u2502 ");
46✔
756
            comment_line.insert(0, filename_width, ' ');
46✔
757
            if (tc != nullptr) {
46✔
758
                const auto& hl = tc->get_highlights();
46✔
759
                auto hl_iter = hl.find({highlight_source_t::PREVIEW, "search"});
46✔
760

761
                if (hl_iter != hl.end()) {
46✔
762
                    hl_iter->second.annotate(comment_line,
12✔
763
                                             line_range{(int) filename_width});
12✔
764
                }
765
            }
766

767
            dst.emplace_back(comment_line);
46✔
768
        }
769
    }
40✔
770
    if (!line_meta.bm_tags.empty()) {
222✔
771
        attr_line_t al;
50✔
772

773
        al.with_string(" \u2514");
50✔
774
        for (const auto& str : line_meta.bm_tags) {
100✔
775
            al.append(1, ' ').append(str,
100✔
776
                                     VC_STYLE.value(vc.attrs_for_ident(str)));
100✔
777
        }
778

779
        al.insert(0, filename_width, ' ');
50✔
780
        if (tc != nullptr) {
50✔
781
            const auto& hl = tc->get_highlights();
50✔
782
            auto hl_iter = hl.find({highlight_source_t::PREVIEW, "search"});
50✔
783

784
            if (hl_iter != hl.end()) {
50✔
785
                hl_iter->second.annotate(al, line_range{(int) filename_width});
11✔
786
            }
787
        }
788
        dst.emplace_back(al);
50✔
789
    }
50✔
790
    if (!line_meta.bm_annotations.la_pairs.empty()) {
222✔
791
        for (const auto& anno_pair : line_meta.bm_annotations.la_pairs) {
32✔
792
            attr_line_t al;
16✔
793
            md2attr_line mdal;
16✔
794

795
            mdal.add_lnav_script_icons();
16✔
796
            dst.push_back(
16✔
797
                attr_line_t()
16✔
798
                    .append(filename_width, ' ')
16✔
799
                    .appendf(FMT_STRING(" \u251c {}:"), anno_pair.first)
64✔
800
                    .with_attr_for_all(VC_ROLE.value(role_t::VCR_COMMENT)));
16✔
801

802
            auto parse_res = md4cpp::parse(anno_pair.second, mdal);
16✔
803
            if (parse_res.isOk()) {
16✔
804
                al.append(parse_res.unwrap());
16✔
805
            } else {
806
                log_error("%d: cannot convert annotation to markdown: %s",
×
807
                          (int) row,
808
                          parse_res.unwrapErr().c_str());
809
                al.append(anno_pair.second);
×
810
            }
811

812
            auto anno_lines = al.rtrim().split_lines();
16✔
813
            if (anno_lines.back().empty()) {
16✔
814
                anno_lines.pop_back();
×
815
            }
816
            for (size_t lpc = 0; lpc < anno_lines.size(); lpc++) {
56✔
817
                auto& anno_line = anno_lines[lpc];
40✔
818

819
                if (lpc == 0 && anno_line.empty()) {
40✔
820
                    continue;
4✔
821
                }
822
                // anno_line.with_attr_for_all(VC_ROLE.value(role_t::VCR_COMMENT));
823
                anno_line.insert(0,
36✔
824
                                 lpc == anno_lines.size() - 1
36✔
825
                                     ? " \u2570 "_comment
36✔
826
                                     : " \u2502 "_comment);
20✔
827
                anno_line.insert(0, filename_width, ' ');
36✔
828
                if (tc != nullptr) {
36✔
829
                    const auto& hl = tc->get_highlights();
36✔
830
                    auto hl_iter
831
                        = hl.find({highlight_source_t::PREVIEW, "search"});
36✔
832

833
                    if (hl_iter != hl.end()) {
36✔
834
                        hl_iter->second.annotate(
×
835
                            anno_line, line_range{(int) filename_width});
×
836
                    }
837
                }
838

839
                dst.emplace_back(anno_line);
36✔
840
            }
841
        }
16✔
842
    }
843
}
222✔
844

845
void
846
field_overlay_source::add_key_line_attrs(int key_size, bool last_line)
15✔
847
{
848
    auto& sa = this->fos_lines.back().get_attrs();
15✔
849
    auto lr = line_range{1, 2};
15✔
850

851
    const auto* graphic = (last_line ? NCACS_LLCORNER : NCACS_LTEE);
15✔
852
    sa.emplace_back(lr, VC_GRAPHIC.value(graphic));
15✔
853

854
    lr.lr_start = 3 + key_size + 3;
15✔
855
    lr.lr_end = -1;
15✔
856
    sa.emplace_back(lr, VC_STYLE.value(text_attrs::with_bold()));
15✔
857
}
15✔
858

859
void
860
field_overlay_source::list_value_for_overlay(
17,927✔
861
    const listview_curses& lv,
862
    vis_line_t row,
863
    std::vector<attr_line_t>& value_out)
864
{
865
    // log_debug("value for overlay %d", row);
866
    if (row == lv.get_selection()) {
17,927✔
867
        this->build_field_lines(lv, row);
1,745✔
868
        value_out = this->fos_lines;
1,745✔
869
    }
870
    this->build_meta_line(lv, value_out, row);
17,927✔
871
}
17,927✔
872

873
static void
874
apply_status_attrs(std::vector<attr_line_t>& lines)
43,390✔
875
{
876
    for (auto& al : lines) {
86,863✔
877
        al.with_attr_for_all(VC_ROLE.value(role_t::VCR_STATUS));
43,473✔
878
    }
879
    lines.back().with_attr_for_all(
86,780✔
880
        VC_STYLE.value(text_attrs::with_underline()));
86,780✔
881
}
43,390✔
882

883
bool
884
field_overlay_source::list_static_overlay(const listview_curses& lv,
43,800✔
885
                                          media_t media,
886
                                          int y,
887
                                          int bottom,
888
                                          attr_line_t& value_out)
889
{
890
    auto& exec_phase = injector::get<lnav::exec_phase&>();
43,800✔
891
    if (media != media_t::display) {
43,800✔
892
        return false;
172✔
893
    }
894

895
    const std::vector<attr_line_t>* lines = nullptr;
43,628✔
896
    if (exec_phase.spinning_up()) {
43,628✔
897
        auto msg
898
            = lnav::console::user_message::info("Files are being indexed...");
43,306✔
899
        this->fos_static_lines = msg.to_attr_line().split_lines();
43,306✔
900
        this->fos_static_lines_state.clear();
43,306✔
901
        apply_status_attrs(this->fos_static_lines);
43,306✔
902
        lines = &this->fos_static_lines;
43,306✔
903
    } else if (this->fos_lss.text_line_count() == 0) {
43,628✔
904
        if (this->fos_lss.is_indexing_in_progress()
83✔
905
            || this->fos_lss.is_rebuild_forced())
83✔
906
        {
907
            auto msg = lnav::console::user_message::info(
908
                "Log messages are being indexed...");
×
909
            this->fos_static_lines = msg.to_attr_line().split_lines();
×
910
            this->fos_static_lines_state.clear();
×
911
        } else if (this->fos_lss.file_count() > 0) {
83✔
912
            hasher h;
×
913
            this->fos_lss.update_filter_hash_state(h);
×
914
            auto curr_state = h.to_array();
×
915
            if (this->fos_static_lines.empty()
×
916
                || curr_state != this->fos_static_lines_state)
×
917
            {
918
                auto msg = lnav::console::user_message::info(
919
                    "All log messages are currently hidden");
×
920
                auto hidden_file_count = size_t{0};
×
921
                for (const auto& ld : this->fos_lss) {
×
922
                    if (ld->get_file_ptr() == nullptr) {
×
923
                        continue;
×
924
                    }
925
                    if (!ld->is_visible()) {
×
926
                        hidden_file_count += 1;
×
927
                    }
928
                }
929
                if (hidden_file_count > 0) {
×
930
                    msg.with_note(attr_line_t()
×
931
                                      .append(lnav::roles::number(
×
932
                                          fmt::to_string(hidden_file_count)))
×
933
                                      .append(" file(s) are hidden"));
×
934
                }
935
                auto min_time = this->fos_lss.get_min_row_time();
×
936
                if (min_time) {
×
937
                    msg.with_note(attr_line_t("Logs before ")
×
938
                                      .append_quoted(lnav::to_rfc3339_string(
×
939
                                          min_time.value()))
×
940
                                      .append(" are not being shown"));
×
941
                }
942
                auto max_time = this->fos_lss.get_max_row_time();
×
943
                if (max_time) {
×
944
                    msg.with_note(attr_line_t("Logs after ")
×
945
                                      .append_quoted(lnav::to_rfc3339_string(
×
946
                                          max_time.value()))
×
947
                                      .append(" are not being shown"));
×
948
                }
949
                if (this->fos_lss.get_min_log_level()
×
950
                    > log_level_t::LEVEL_UNKNOWN)
×
951
                {
952
                    msg.with_note(
×
953
                        attr_line_t("Logs with a level below ")
×
954
                            .append_quoted(
×
955
                                level_names[this->fos_lss.get_min_log_level()])
×
956
                            .append(" are not being shown"));
×
957
                }
958
                auto& fs = this->fos_lss.get_filters();
×
959
                for (const auto& filt : fs) {
×
960
                    auto hits = this->fos_lss.get_filtered_count_for(
×
961
                        filt->get_index());
962
                    if (filt->get_type() == text_filter::EXCLUDE && hits == 0) {
×
963
                        continue;
×
964
                    }
965
                    auto cmd = attr_line_t(":" + filt->to_command());
×
966
                    readline_command_highlighter(cmd, std::nullopt);
×
967
                    msg.with_note(
×
968
                        attr_line_t("Filter ")
×
969
                            .append_quoted(cmd)
×
970
                            .append(" matched ")
×
971
                            .append(lnav::roles::number(fmt::to_string(hits)))
×
972
                            .append(" message(s) "));
×
973
                }
974
                if (this->fos_lss.get_marked_only()) {
×
975
                    msg.with_note(attr_line_t("The ")
×
976
                                      .append_quoted(lnav::roles::keyword(
×
977
                                          ":hide-unmarked-lines"))
978
                                      .append(" command was used and no "
×
979
                                              "unfiltered lines are marked"));
980
                }
981
                this->fos_static_lines = msg.to_attr_line().split_lines();
×
982
                this->fos_static_lines_state = curr_state;
×
983
            }
984

985
        } else if (this->fos_tss.empty()) {
83✔
986
            this->fos_static_lines = *lnav::messages::view::no_files();
83✔
987
        } else {
988
            this->fos_static_lines = *lnav::messages::view::only_text_files();
×
989
        }
990
        apply_status_attrs(this->fos_static_lines);
83✔
991
        lines = &this->fos_static_lines;
83✔
992
    } else if (y == 0) {
239✔
993
        lines = &this->fos_static_lines;
13✔
994
        auto top = lv.get_top();
13✔
995
        if (top < vis_line_t(this->fos_lss.text_line_count())) {
13✔
996
            auto cl = this->fos_lss.at(top);
13✔
997
            if (!this->fos_header_line || this->fos_header_line.value() != cl
25✔
998
                || !this->fos_header_line_context
12✔
999
                || this->fos_header_line_context.value()
24✔
1000
                    != this->fos_lss.get_line_context()
12✔
1001
                || this->fos_header_has_time_offset
12✔
1002
                    != this->fos_lss.is_time_offset_enabled()
12✔
1003
                || this->fos_header_has_time_preview
25✔
1004
                    != (this->fos_lss.ttt_preview_min_time.has_value()
12✔
1005
                        || this->fos_lss.ttt_preview_max_time.has_value()))
12✔
1006
            {
1007
                auto file_and_line_pair
1008
                    = this->fos_lss.find_line_with_file(top);
1✔
1009
                if (file_and_line_pair) {
1✔
1010
                    const auto& [lf, line] = file_and_line_pair.value();
1✔
1011
                    auto first_line = line;
1✔
1012
                    auto header_top = top;
1✔
1013
                    while (first_line->is_continued()) {
3✔
1014
                        --first_line;
2✔
1015
                        header_top -= 1_vl;
2✔
1016
                    }
1017
                    if (header_top == top) {
1✔
1018
                        header_top -= 1_vl;
×
1019
                    }
1020
                    this->fos_static_lines.clear();
1✔
1021
                    if (header_top < 0_vl) {
1✔
1022
                        auto al = attr_line_t();
×
1023
                        auto filtered_before
1024
                            = this->fos_lss.get_filtered_before();
×
1025
                        if (filtered_before > 0) {
×
1026
                            al.append(" ")
×
1027
                                .append(lnav::roles::number(
×
1028
                                    fmt::to_string(filtered_before)))
×
1029
                                .append(
×
1030
                                    " message(s) above here have been filtered "
1031
                                    "out");
1032
                        } else {
1033
                            al.append(" No messages above here");
×
1034
                        }
1035
                        al.with_attr_for_all(
×
1036
                            VC_STYLE.value(text_attrs::with_italic()));
×
1037
                        this->fos_static_lines.emplace_back(al);
×
1038
                        apply_status_attrs(this->fos_static_lines);
×
1039
                    } else {
×
1040
                        this->fos_static_lines.resize(1);
1✔
1041
                        auto& al = this->fos_static_lines.front();
1✔
1042
                        auto& tc = dynamic_cast<textview_curses&>(
1✔
1043
                            const_cast<listview_curses&>(lv));
1✔
1044
                        tc.textview_value_for_row(header_top, al);
1✔
1045
                        if ((top - header_top) > 1 && line->is_continued()) {
1✔
1046
                            apply_status_attrs(this->fos_static_lines);
1✔
1047
                        }
1048
                    }
1049
                    this->fos_header_line = cl;
1✔
1050
                    this->fos_header_line_context
1051
                        = this->fos_lss.get_line_context();
1✔
1052
                    this->fos_header_has_time_offset
1053
                        = this->fos_lss.is_time_offset_enabled();
1✔
1054
                    this->fos_header_has_time_preview
1055
                        = (this->fos_lss.ttt_preview_min_time.has_value()
2✔
1056
                           || this->fos_lss.ttt_preview_max_time.has_value());
1✔
1057
                }
1058
            }
1✔
1059
        } else {
1060
            this->fos_static_lines.resize(1);
×
1061
            this->fos_static_lines[0].clear();
×
1062
        }
1063
    }
1064

1065
    if (lines != nullptr && y < (ssize_t) lines->size()) {
43,628✔
1066
        value_out = lines->at(y);
2,352✔
1067
        return true;
2,352✔
1068
    }
1069

1070
    return false;
41,276✔
1071
}
1072

1073
std::optional<attr_line_t>
1074
field_overlay_source::list_header_for_overlay(const listview_curses& lv,
20✔
1075
                                              media_t media,
1076
                                              vis_line_t vl)
1077
{
1078
    attr_line_t retval;
20✔
1079

1080
    retval.append(this->fos_lss.get_filename_offset(), ' ');
20✔
1081
    if (this->fos_contexts.top().c_show) {
20✔
1082
        retval.appendf(FMT_STRING("\u258C Line {:L} parser details."),
12✔
1083
                       (int) vl);
4✔
1084
        if (media == media_t::display) {
4✔
1085
            retval.append("  Press ")
×
1086
                .append("p"_hotkey)
×
1087
                .append(" to hide this panel.");
×
1088
        }
1089
    } else {
1090
        retval.append("\u258C Line ")
16✔
1091
            .append(
16✔
1092
                lnav::roles::number(fmt::format(FMT_STRING("{:L}"), (int) vl)))
80✔
1093
            .append(" metadata");
16✔
1094
    }
1095

1096
    if (media == media_t::display) {
20✔
1097
        if (lv.get_overlay_selection()) {
×
1098
            retval.append("  ")
×
1099
                .append("SPC"_hotkey)
×
1100
                .append(": hide/show field  ")
×
1101
                .append("c"_hotkey)
×
1102
                .append(": copy field value  ")
×
1103
                .append("Esc"_hotkey)
×
1104
                .append(": exit this panel");
×
1105
        } else {
1106
            retval.append("  Press ")
×
1107
                .append("CTRL-]"_hotkey)
×
1108
                .append(" to focus on this panel");
×
1109
        }
1110
    }
1111

1112
    return retval;
40✔
1113
}
20✔
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