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

tstack / lnav / 25348852825-3024

04 May 2026 11:18PM UTC coverage: 69.963% (+0.7%) from 69.226%
25348852825-3024

push

github

tstack
[ui] horizontal scroll should work on columns

Related to #1685

7 of 141 new or added lines in 5 files covered. (4.96%)

7760 existing lines in 84 files now uncovered.

57014 of 81492 relevant lines covered (69.96%)

622491.44 hits per line

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

64.92
/src/logfile_sub_source.cc
1
/**
2
 * Copyright (c) 2007-2012, 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 <algorithm>
31
#include <chrono>
32
#include <functional>
33
#include <future>
34
#include <optional>
35
#include <string>
36
#include <unordered_set>
37
#include <vector>
38

39
#include "logfile_sub_source.hh"
40

41
#include <sqlite3.h>
42

43
#include "base/ansi_scrubber.hh"
44
#include "base/ansi_vars.hh"
45
#include "base/distributed_slice.hh"
46
#include "base/injector.hh"
47
#include "base/itertools.hh"
48
#include "base/string_util.hh"
49
#include "bookmarks.hh"
50
#include "bookmarks.json.hh"
51
#include "command_executor.hh"
52
#include "config.h"
53
#include "field_overlay_source.hh"
54
#include "file_collection.hh"
55
#include "hasher.hh"
56
#include "k_merge_tree.h"
57
#include "lnav_util.hh"
58
#include "log_accel.hh"
59
#include "logfile_sub_source.cfg.hh"
60
#include "logline_window.hh"
61
#include "md2attr_line.hh"
62
#include "ptimec.hh"
63
#include "scn/scan.h"
64
#include "shlex.hh"
65
#include "sql_util.hh"
66
#include "sqlitepp.client.hh"
67
#include "sqlitepp.hh"
68
#include "tlx/container/btree_set.hpp"
69
#include "vtab_module.hh"
70
#include "yajlpp/yajlpp.hh"
71
#include "yajlpp/yajlpp_def.hh"
72

73
using namespace std::chrono_literals;
74
using namespace std::string_literals;
75
using namespace lnav::roles::literals;
76

77
const DIST_SLICE(bm_types) bookmark_type_t logfile_sub_source::BM_FILES("file");
78

79
static int
80
pretty_sql_callback(exec_context& ec, sqlite3_stmt* stmt)
69✔
81
{
82
    if (!sqlite3_stmt_busy(stmt)) {
69✔
83
        return 0;
30✔
84
    }
85

86
    const auto ncols = sqlite3_column_count(stmt);
39✔
87

88
    for (int lpc = 0; lpc < ncols; lpc++) {
78✔
89
        if (!ec.ec_accumulator->empty()) {
39✔
90
            ec.ec_accumulator->append(", ");
10✔
91
        }
92

93
        const char* res = (const char*) sqlite3_column_text(stmt, lpc);
39✔
94
        if (res == nullptr) {
39✔
95
            continue;
×
96
        }
97

98
        ec.ec_accumulator->append(res);
39✔
99
    }
100

101
    for (int lpc = 0; lpc < ncols; lpc++) {
78✔
102
        const auto* colname = sqlite3_column_name(stmt, lpc);
39✔
103
        auto* raw_value = sqlite3_column_value(stmt, lpc);
39✔
104
        auto value_type = sqlite3_value_type(raw_value);
39✔
105
        scoped_value_t value;
39✔
106

107
        switch (value_type) {
39✔
108
            case SQLITE_INTEGER:
×
109
                value = (int64_t) sqlite3_value_int64(raw_value);
×
110
                break;
×
111
            case SQLITE_FLOAT:
×
112
                value = sqlite3_value_double(raw_value);
×
113
                break;
×
114
            case SQLITE_NULL:
×
115
                value = null_value_t{};
×
116
                break;
×
117
            default:
39✔
118
                value = string_fragment::from_bytes(
39✔
119
                    sqlite3_value_text(raw_value),
120
                    sqlite3_value_bytes(raw_value));
78✔
121
                break;
39✔
122
        }
123
        if (!ec.ec_local_vars.empty() && !ec.ec_dry_run) {
39✔
124
            if (sql_ident_needs_quote(colname)) {
39✔
125
                continue;
25✔
126
            }
127
            auto& vars = ec.ec_local_vars.top();
14✔
128

129
            if (vars.find(colname) != vars.end()) {
42✔
130
                continue;
10✔
131
            }
132

133
            if (value.is<string_fragment>()) {
4✔
134
                value = value.get<string_fragment>().to_string();
4✔
135
            }
136
            vars[colname] = value;
8✔
137
        }
138
    }
39✔
139
    return 0;
39✔
140
}
141

142
static std::future<std::string>
143
pretty_pipe_callback(exec_context& ec, const std::string& cmdline, auto_fd& fd)
×
144
{
145
    auto retval = std::async(std::launch::async, [&]() {
×
146
        char buffer[1024];
147
        std::ostringstream ss;
×
148
        ssize_t rc;
149

150
        while ((rc = read(fd, buffer, sizeof(buffer))) > 0) {
×
151
            ss.write(buffer, rc);
×
152
        }
153

154
        auto retval = ss.str();
×
155

156
        if (endswith(retval, "\n")) {
×
157
            retval.resize(retval.length() - 1);
×
158
        }
159

160
        return retval;
×
161
    });
×
162

163
    return retval;
×
164
}
165

166
logfile_sub_source::logfile_sub_source()
1,337✔
167
    : text_sub_source(1), lnav_config_listener(__FILE__),
168
      lss_meta_grepper(*this), lss_location_history(*this)
1,337✔
169
{
170
    this->tss_supports_filtering = true;
1,337✔
171
    this->clear_line_size_cache();
1,337✔
172
    this->clear_min_max_row_times();
1,337✔
173
}
1,337✔
174

175
std::shared_ptr<logfile>
176
logfile_sub_source::find(const char* fn, content_line_t& line_base)
35✔
177
{
178
    std::shared_ptr<logfile> retval = nullptr;
35✔
179

180
    line_base = content_line_t(0);
35✔
181
    for (auto iter = this->lss_files.begin();
35✔
182
         iter != this->lss_files.end() && retval == nullptr;
73✔
183
         ++iter)
38✔
184
    {
185
        auto& ld = *(*iter);
38✔
186
        auto* lf = ld.get_file_ptr();
38✔
187

188
        if (lf == nullptr) {
38✔
189
            continue;
×
190
        }
191
        if (strcmp(lf->get_filename_as_string().c_str(), fn) == 0) {
38✔
192
            retval = ld.get_file();
35✔
193
        } else {
194
            line_base += content_line_t(MAX_LINES_PER_FILE);
3✔
195
        }
196
    }
197

198
    return retval;
35✔
199
}
×
200

201
struct filtered_logline_cmp {
202
    filtered_logline_cmp(const logfile_sub_source& lc) : llss_controller(lc) {}
610✔
203

204
    bool operator()(const uint32_t& lhs, const uint32_t& rhs) const
205
    {
206
        auto cl_lhs = llss_controller.lss_index[lhs].value();
207
        auto cl_rhs = llss_controller.lss_index[rhs].value();
208
        const auto* ll_lhs = this->llss_controller.find_line(cl_lhs);
209
        const auto* ll_rhs = this->llss_controller.find_line(cl_rhs);
210

211
        if (ll_lhs == nullptr) {
212
            return true;
213
        }
214
        if (ll_rhs == nullptr) {
215
            return false;
216
        }
217
        return (*ll_lhs) < (*ll_rhs);
218
    }
219

220
    bool operator()(const uint32_t& lhs, const timeval& rhs) const
1,987✔
221
    {
222
        const auto cl_lhs = llss_controller.lss_index[lhs].value();
1,987✔
223
        const auto* ll_lhs = this->llss_controller.find_line(cl_lhs);
1,987✔
224

225
        if (ll_lhs == nullptr) {
1,987✔
226
            return true;
×
227
        }
228
        return (*ll_lhs) < rhs;
1,987✔
229
    }
230

231
    const logfile_sub_source& llss_controller;
232
};
233

234
std::optional<vis_line_t>
235
logfile_sub_source::find_from_time(const timeval& start) const
185✔
236
{
237
    const auto lb = std::lower_bound(this->lss_filtered_index.begin(),
185✔
238
                                     this->lss_filtered_index.end(),
239
                                     start,
240
                                     filtered_logline_cmp(*this));
241
    if (lb != this->lss_filtered_index.end()) {
185✔
242
        auto retval = std::distance(this->lss_filtered_index.begin(), lb);
128✔
243
        return vis_line_t(retval);
128✔
244
    }
245

246
    return std::nullopt;
57✔
247
}
248

249
line_info
250
logfile_sub_source::text_value_for_line(textview_curses& tc,
4,876✔
251
                                        int row,
252
                                        std::string& value_out,
253
                                        line_flags_t flags)
254
{
255
    if (this->lss_indexing_in_progress) {
4,876✔
256
        value_out = "";
×
257
        this->lss_token_al.al_attrs.clear();
×
258
        return {};
×
259
    }
260

261
    line_info retval;
4,876✔
262
    content_line_t line(0);
4,876✔
263

264
    require_ge(row, 0);
4,876✔
265
    require_lt((size_t) row, this->lss_filtered_index.size());
4,876✔
266

267
    line = this->at(vis_line_t(row));
4,876✔
268

269
    if (flags & RF_RAW) {
4,876✔
270
        auto lf = this->find(line);
32✔
271
        // Metric rows render as a composed `<ts> <col>=<val> ...`
272
        // line, not the raw CSV bytes, so returning the raw row
273
        // here would make search (which goes through `RF_RAW`) miss
274
        // every match that lives in the rendered text — e.g. the
275
        // column names aren't in the raw bytes at all.  Fall through
276
        // to the compose path so search sees what the user sees.
277
        if (!lf->get_format_ptr()->lf_is_metric) {
32✔
278
            auto ll = lf->begin() + line;
32✔
279
            retval.li_file_range = lf->get_file_range(ll, false);
32✔
280
            retval.li_level = ll->get_msg_level();
32✔
281
            retval.li_partial = false;
32✔
282
            retval.li_utf8_scan_result.usr_has_ansi = ll->has_ansi();
32✔
283
            retval.li_utf8_scan_result.usr_message
284
                = ll->is_valid_utf() ? nullptr : "bad";
32✔
285
            value_out = lf->read_line(lf->begin() + line)
64✔
286
                            .map([](auto sbr) { return to_string(sbr); })
64✔
287
                            .unwrapOr({});
32✔
288
            return retval;
32✔
289
        }
290
    }
32✔
291

292
    require_false(this->lss_in_value_for_line);
4,844✔
293

294
    this->lss_in_value_for_line = true;
4,844✔
295
    this->lss_token_flags = flags;
4,844✔
296
    this->lss_token_file_data = this->find_data(line);
4,844✔
297
    this->lss_token_file = (*this->lss_token_file_data)->get_file();
4,844✔
298
    this->lss_token_line = this->lss_token_file->begin() + line;
4,844✔
299

300
    this->lss_token_al.clear();
4,844✔
301
    this->lss_token_values.clear();
4,844✔
302
    this->lss_share_manager.invalidate_refs();
4,844✔
303
    if (flags & RF_FULL) {
4,844✔
304
        shared_buffer_ref sbr;
48✔
305

306
        this->lss_token_file->read_full_message(this->lss_token_line, sbr);
48✔
307
        this->lss_token_al.al_string = to_string(sbr);
48✔
308
        if (sbr.get_metadata().m_has_ansi) {
48✔
309
            scrub_ansi_string(this->lss_token_al.al_string,
17✔
310
                              &this->lss_token_al.al_attrs);
311
            sbr.get_metadata().m_has_ansi = false;
17✔
312
        }
313
    } else {
48✔
314
        auto sub_opts = subline_options{};
4,796✔
315
        sub_opts.scrub_invalid_utf8 = false;
4,796✔
316
        this->lss_token_al.al_string
317
            = this->lss_token_file->read_line(this->lss_token_line, sub_opts)
9,592✔
318
                  .map([](auto sbr) { return to_string(sbr); })
9,592✔
319
                  .unwrapOr({});
4,796✔
320
        if (this->lss_token_line->has_ansi()) {
4,796✔
321
            scrub_ansi_string(this->lss_token_al.al_string,
24✔
322
                              &this->lss_token_al.al_attrs);
323
        }
324
    }
325
    for (auto& sa : this->lss_token_al.al_attrs) {
5,325✔
326
        if (sa.sa_type == &VC_HYPERLINK) {
481✔
UNCOV
327
            sa.sa_type = &SAT_UNSUPPORTED;
×
328
        }
329
    }
330
    this->lss_token_shifts.clear();
4,844✔
331

332
    auto format = this->lss_token_file->get_format_ptr();
4,844✔
333

334
    auto& sbr = this->lss_token_values.lvv_sbr;
4,844✔
335

336
    sbr.share(this->lss_share_manager,
4,844✔
337
              (char*) this->lss_token_al.al_string.c_str(),
338
              this->lss_token_al.al_string.size());
339
    format->annotate(this->lss_token_file.get(),
4,844✔
340
                     line,
341
                     this->lss_token_al.al_attrs,
4,844✔
342
                     this->lss_token_values);
4,844✔
343

344
    if (format->lf_is_metric) {
4,844✔
345
        // Compose the LOG-view line from the annotated field values
346
        // rather than the raw CSV text.  Rows from different metric
347
        // files that share a timestamp are merged into one visible
348
        // line so `col=value` pairs from every file at this moment
349
        // appear together.  The source file for each column is
350
        // tagged via the L_METRIC_SOURCE string attr so the
351
        // field_overlay_source can label the columns by file stem
352
        // when the row is focused.
353
        this->lss_token_al.al_attrs.clear();
241✔
354

355
        std::string composed;
241✔
356
        composed.reserve(64 + 24 * this->lss_token_values.lvv_values.size());
241✔
357
        char ts_buf[64];
358
        auto ts_len = sql_strftime(
241✔
359
            ts_buf, sizeof(ts_buf), this->lss_token_line->get_timeval(), 'T');
241✔
360
        composed.append(ts_buf, ts_len);
241✔
361
        this->lss_token_al.al_attrs.emplace_back(
241✔
362
            line_range{0, static_cast<int>(ts_len)}, L_TIMESTAMP.value());
482✔
363

364
        auto emit_value = [&](logfile* src_lf,
889✔
365
                              const logline_value& lv,
366
                              const char* src_data) {
367
            // Respect `:hide-fields metrics_log.<col>` — a user-hidden
368
            // column drops out of the composed line entirely.
369
            if (lv.lv_meta.is_hidden()) {
889✔
370
                return;
45✔
371
            }
372
            // Two-space gap between adjacent cells so column names
373
            // don't crowd the previous cell's value / colored bar.
374
            composed.append("  ");
844✔
375
            const auto col_start = static_cast<int>(composed.size());
844✔
376
            composed.append(lv.lv_meta.lvm_name.to_string_fragment().data(),
844✔
377
                            lv.lv_meta.lvm_name.size());
378
            composed.push_back('=');
844✔
379
            double numeric_value = 0.0;
844✔
380
            bool have_numeric = false;
844✔
381
            switch (lv.lv_meta.lvm_kind) {
844✔
382
                case value_kind_t::VALUE_FLOAT:
225✔
383
                    numeric_value = lv.lv_value.d;
225✔
384
                    have_numeric = true;
225✔
385
                    break;
225✔
386
                case value_kind_t::VALUE_INTEGER:
599✔
387
                    numeric_value = static_cast<double>(lv.lv_value.i);
599✔
388
                    have_numeric = true;
599✔
389
                    break;
599✔
390
                default:
20✔
391
                    break;
20✔
392
            }
393
            // Render the cell using the original file text so
394
            // humanized values ("328.78 GB", "20.0KB") stay as the
395
            // user wrote them rather than collapsing to the parsed
396
            // integer.  Fall back to the numeric formatting if we
397
            // don't have a valid origin range.
398
            std::string rendered;
844✔
399
            if (src_data != nullptr && lv.lv_origin.is_valid()
844✔
400
                && lv.lv_origin.length() > 0)
1,688✔
401
            {
402
                rendered.assign(src_data + lv.lv_origin.lr_start,
844✔
403
                                lv.lv_origin.length());
844✔
UNCOV
404
            } else if (lv.lv_meta.lvm_kind == value_kind_t::VALUE_FLOAT) {
×
UNCOV
405
                rendered = fmt::format(FMT_STRING("{}"), lv.lv_value.d);
×
UNCOV
406
            } else if (lv.lv_meta.lvm_kind == value_kind_t::VALUE_INTEGER) {
×
UNCOV
407
                rendered = fmt::format(FMT_STRING("{}"), lv.lv_value.i);
×
408
            } else {
UNCOV
409
                rendered = lv.to_string();
×
410
            }
411
            constexpr size_t MIN_COLUMN_WIDTH = 10;
844✔
412
            const auto* stats = src_lf->stats_for_value(lv.lv_meta.lvm_name);
844✔
413
            const auto col_width = std::max(
844✔
414
                MIN_COLUMN_WIDTH,
415
                stats != nullptr ? static_cast<size_t>(stats->lvs_width)
844✔
UNCOV
416
                                 : rendered.size());
×
417
            const auto value_start = static_cast<int>(composed.size());
844✔
418
            if (have_numeric && col_width > rendered.size()) {
844✔
419
                composed.append(col_width - rendered.size(), ' ');
814✔
420
            }
421
            composed.append(rendered);
844✔
422
            if (!have_numeric && col_width > rendered.size()) {
844✔
423
                composed.append(col_width - rendered.size(), ' ');
20✔
424
            }
425

426
            if (have_numeric && stats != nullptr && stats->lvs_count > 0
844✔
427
                && col_width > 0)
824✔
428
            {
429
                const auto range = stats->lvs_max_value - stats->lvs_min_value;
824✔
430
                int bar_amount;
431
                if (numeric_value == 0.0) {
824✔
432
                    bar_amount = 0;
92✔
433
                } else if (range <= 0.0) {
732✔
UNCOV
434
                    bar_amount = static_cast<int>(col_width);
×
435
                } else {
436
                    const auto pct
732✔
437
                        = (numeric_value - stats->lvs_min_value) / range;
732✔
438
                    bar_amount = static_cast<int>(
732✔
439
                        std::lround(pct * static_cast<double>(col_width)));
732✔
440
                    bar_amount = std::clamp(
1,464✔
441
                        bar_amount, 1, static_cast<int>(col_width));
732✔
442
                }
443
                if (bar_amount > 0) {
824✔
444
                    const auto bar_range = line_range{
445
                        value_start,
446
                        value_start + bar_amount,
447
                    };
732✔
448
                    this->lss_token_al.al_attrs.emplace_back(
732✔
449
                        bar_range, VC_STYLE.value(text_attrs::with_reverse()));
1,464✔
450
                }
451
            }
452
            // Tag the column span with its source file so the overlay
453
            // can render the file stem above it.  Using the logfile*
454
            // avoids a per-cell string copy of the stem.
455
            this->lss_token_al.al_attrs.emplace_back(
844✔
456
                line_range{col_start, static_cast<int>(composed.size())},
844✔
457
                L_METRIC_SOURCE.value(src_lf));
1,688✔
458
        };
241✔
459

460
        // Fold each row in the fan-out (lead first, then every
461
        // suppressed metric sibling at this timestamp) into the
462
        // composed line.
463
        logline_window::logmsg_info lead_msg{*this, vis_line_t(row)};
241✔
464
        for (const auto& sib : lead_msg.metric_siblings()) {
586✔
465
            const auto& sib_values = sib.get_values();
345✔
466
            const auto* sib_data = sib_values.lvv_sbr.get_data();
345✔
467
            for (const auto& lv : sib_values.lvv_values) {
1,234✔
468
                emit_value(sib.get_file_ptr(), lv, sib_data);
889✔
469
            }
470
        }
241✔
471

472
        this->lss_token_al.al_string = std::move(composed);
241✔
473
        // The sbr and every lv_origin were slices of the previous
474
        // al_string buffer we just replaced, so re-point the sbr at
475
        // the composed line and drop the now-stale per-cell values.
476
        this->lss_token_values.lvv_values.clear();
241✔
477
        sbr.share(this->lss_share_manager,
241✔
478
                  this->lss_token_al.al_string.c_str(),
479
                  this->lss_token_al.al_string.size());
480
    }
241✔
481

482
    auto src_file_attr
483
        = find_string_attr(this->lss_token_al.al_attrs, &SA_SRC_FILE);
4,844✔
484
    if (src_file_attr != this->lss_token_al.al_attrs.end()) {
4,844✔
485
        auto lr = src_file_attr->sa_range;
54✔
486
        lr.lr_end = lr.lr_start + 1;
54✔
487
        auto break_ta = text_attrs::with_underline();
54✔
488
        this->lss_token_al.with_attr({lr, VC_STYLE.value(break_ta)})
108✔
489
            .with_attr({lr,
54✔
490
                        VC_COMMAND.value(ui_command{
216✔
491
                            source_location{},
492
                            "|lnav-src-loc-handler $mouse_button",
493
                        })});
494
        if (!this->lss_breakpoints.empty()) {
54✔
495
            if (this->lss_token_values.lvv_src_line_value) {
6✔
496
                auto h = hasher();
6✔
497
                h.update(format->get_name().to_string_fragment());
6✔
498
                h.update(this->lss_token_values.lvv_src_file_value.value());
6✔
499
                h.update(this->lss_token_values.lvv_src_line_value.value());
6✔
500

501
                auto schema = h.to_string();
6✔
502
                auto& breakpoints = this->lss_breakpoints;
6✔
503
                auto it = breakpoints.find(schema);
6✔
504
                if (it != breakpoints.end()) {
6✔
505
                    lr.lr_end = lr.lr_start;
2✔
506
                    this->lss_token_al.insert(
2✔
507
                        lr.lr_start,
2✔
508
                        it->second.bp_enabled ? ui_icon_t::breakpoint
2✔
509
                                              : ui_icon_t::disabled_breakpoint);
510
                    lr.lr_end += 2;
2✔
511
                    this->lss_token_al.with_attr(
2✔
512
                        {lr,
513
                         VC_COMMAND.value(ui_command{
6✔
514
                             source_location{},
515
                             "|lnav-breakpoint-handler $mouse_button",
516
                         })});
517
                    this->lss_token_values.shift_origins_by(lr, 2);
2✔
518
                }
519
            }
6✔
520
        }
521
    }
522

523
    value_out = this->lss_token_al.al_string;
4,844✔
524

525
    for (const auto& hl : format->lf_highlighters) {
5,802✔
526
        auto hl_range = line_range{0, -1};
958✔
527
        auto value_iter = this->lss_token_values.lvv_values.end();
958✔
528
        if (!hl.h_field.empty()) {
958✔
529
            value_iter = std::find_if(this->lss_token_values.lvv_values.begin(),
206✔
530
                                      this->lss_token_values.lvv_values.end(),
531
                                      logline_value_name_cmp(&hl.h_field));
532
            if (value_iter == this->lss_token_values.lvv_values.end()) {
206✔
533
                continue;
194✔
534
            }
535
            hl_range = value_iter->lv_origin;
12✔
536
        }
537
        if (hl.annotate(this->lss_token_al, hl_range)
764✔
538
            && value_iter != this->lss_token_values.lvv_values.end())
764✔
539
        {
540
            value_iter->lv_highlighted = true;
7✔
541
        }
542
    }
543

544
    for (const auto& hl : this->lss_highlighters) {
4,861✔
545
        auto hl_range = line_range{0, -1};
17✔
546
        auto value_iter = this->lss_token_values.lvv_values.end();
17✔
547
        if (!hl.h_field.empty()) {
17✔
548
            value_iter = std::find_if(this->lss_token_values.lvv_values.begin(),
17✔
549
                                      this->lss_token_values.lvv_values.end(),
550
                                      logline_value_name_cmp(&hl.h_field));
551
            if (value_iter == this->lss_token_values.lvv_values.end()) {
17✔
552
                continue;
3✔
553
            }
554
            hl_range = value_iter->lv_origin;
14✔
555
        }
556
        if (hl.annotate(this->lss_token_al, hl_range)
14✔
557
            && value_iter != this->lss_token_values.lvv_values.end())
14✔
558
        {
559
            value_iter->lv_highlighted = true;
9✔
560
        }
561
    }
562

563
    if (flags & RF_REWRITE) {
4,844✔
564
        exec_context ec(
565
            &this->lss_token_values, pretty_sql_callback, pretty_pipe_callback);
48✔
566
        std::string rewritten_line;
48✔
567
        db_label_source rewrite_label_source;
48✔
568

569
        ec.with_perms(exec_context::perm_t::READ_ONLY);
48✔
570
        ec.ec_local_vars.push(std::map<std::string, scoped_value_t>());
48✔
571
        ec.ec_top_line = vis_line_t(row);
48✔
572
        ec.ec_label_source_stack.push_back(&rewrite_label_source);
48✔
573
        add_ansi_vars(ec.ec_global_vars);
48✔
574
        add_global_vars(ec);
48✔
575
        format->rewrite(ec, sbr, this->lss_token_al.al_attrs, rewritten_line);
48✔
576
        this->lss_token_al.al_string.assign(rewritten_line);
48✔
577
        value_out = this->lss_token_al.al_string;
48✔
578
    }
48✔
579

580
    {
581
        auto lr = line_range{0, (int) this->lss_token_al.al_string.length()};
4,844✔
582
        this->lss_token_al.al_attrs.emplace_back(lr, SA_ORIGINAL_LINE.value());
4,844✔
583
    }
584

585
    // Replace VALUE_TIMESTAMP fields right-to-left so that origins
586
    // for earlier fields remain valid.
587
    if (format->lf_date_time.dts_fmt_lock != -1) {
4,844✔
588
        for (auto lv_iter = this->lss_token_values.lvv_values.rbegin();
4,753✔
589
             lv_iter != this->lss_token_values.lvv_values.rend();
47,001✔
590
             ++lv_iter)
42,248✔
591
        {
592
            if (lv_iter->lv_meta.lvm_kind != value_kind_t::VALUE_TIMESTAMP
42,248✔
593
                || !lv_iter->lv_origin.is_valid())
42,248✔
594
            {
595
                continue;
41,597✔
596
            }
597

598
            auto ts_str = lv_iter->to_string();
651✔
599
            value_out.replace(lv_iter->lv_origin.lr_start,
651✔
600
                              lv_iter->lv_origin.length(),
651✔
601
                              ts_str);
602
            auto shift = (int) (ts_str.size() - lv_iter->lv_origin.length());
651✔
603
            if (shift != 0) {
651✔
604
                this->lss_token_shifts.emplace_back(lv_iter->lv_origin.lr_start,
2✔
605
                                                    shift);
606
            }
607
        }
651✔
608
    }
609

610
    auto lffs = this->lss_token_file->get_format_file_state();
4,844✔
611
    auto ts_flags = format->lf_timestamp_flags;
4,844✔
612
    auto pat_opt = lffs.lffs_pattern_locks.get_pattern_for_line(line);
4,844✔
613
    if (pat_opt) {
4,844✔
614
        ts_flags = pat_opt->pfl_timestamp_flags;
2,375✔
615
    }
616
    std::optional<exttm> adjusted_tm;
4,844✔
617
    auto time_attr
618
        = find_string_attr(this->lss_token_al.al_attrs, &L_TIMESTAMP);
4,844✔
619
    if (!this->lss_token_line->is_continued() && !format->lf_formatted_lines
7,755✔
620
        && (this->lss_token_file->is_time_adjusted()
2,660✔
621
            || ((ts_flags & ETF_ZONE_SET
2,645✔
622
                 || format->lf_date_time.dts_default_zone != nullptr)
1,468✔
623
                && format->lf_date_time.dts_zoned_to_local)
1,265✔
624
            || ts_flags & ETF_MACHINE_ORIENTED || !(ts_flags & ETF_DAY_SET)
1,380✔
625
            || !(ts_flags & ETF_MONTH_SET))
1,359✔
626
        && format->lf_date_time.dts_fmt_lock != -1)
7,755✔
627
    {
628
        if (time_attr != this->lss_token_al.al_attrs.end()) {
1,245✔
629
            const auto time_range = time_attr->sa_range;
1,245✔
630
            const auto time_sf
631
                = string_fragment::from_str_range(this->lss_token_al.al_string,
1,245✔
632
                                                  time_range.lr_start,
1,245✔
633
                                                  time_range.lr_end);
1,245✔
634
            adjusted_tm = format->tm_for_display(this->lss_token_line, time_sf);
1,245✔
635

636
            char buffer[128];
637
            const char* fmt;
638
            ssize_t len;
639

640
            if (ts_flags & ETF_MACHINE_ORIENTED || !(ts_flags & ETF_DAY_SET)
1,245✔
641
                || !(ts_flags & ETF_MONTH_SET))
601✔
642
            {
643
                if (ts_flags & ETF_NANOS_SET) {
644✔
UNCOV
644
                    fmt = "%Y-%m-%d %H:%M:%S.%N";
×
645
                } else if (ts_flags & ETF_MICROS_SET) {
644✔
646
                    fmt = "%Y-%m-%d %H:%M:%S.%f";
635✔
647
                } else if (ts_flags & ETF_MILLIS_SET) {
9✔
648
                    fmt = "%Y-%m-%d %H:%M:%S.%L";
2✔
649
                } else {
650
                    fmt = "%Y-%m-%d %H:%M:%S";
7✔
651
                }
652
                len = ftime_fmt(
644✔
653
                    buffer, sizeof(buffer), fmt, adjusted_tm.value());
644✔
654
            } else {
655
                len = format->lf_date_time.ftime(
601✔
656
                    buffer,
657
                    sizeof(buffer),
658
                    format->get_timestamp_formats(),
659
                    adjusted_tm.value());
601✔
660
            }
661

662
            value_out.replace(
2,490✔
663
                time_range.lr_start, time_range.length(), buffer, len);
1,245✔
664
            this->lss_token_shifts.emplace_back(
2,490✔
665
                time_range.lr_start, (int) (len - time_range.length()));
1,245✔
666
        }
667
    }
668

669
    // Insert space for the file/search-hit markers.
670
    value_out.insert(0, 1, ' ');
4,844✔
671
    this->lss_time_column_size = 0;
4,844✔
672
    if (this->lss_line_context == line_context_t::time_column) {
4,844✔
UNCOV
673
        if (time_attr != this->lss_token_al.al_attrs.end()) {
×
674
            const char* fmt;
UNCOV
675
            if (this->lss_all_timestamp_flags
×
UNCOV
676
                & (ETF_MICROS_SET | ETF_NANOS_SET))
×
677
            {
UNCOV
678
                fmt = "%H:%M:%S.%f";
×
UNCOV
679
            } else if (this->lss_all_timestamp_flags & ETF_MILLIS_SET) {
×
UNCOV
680
                fmt = "%H:%M:%S.%L";
×
681
            } else {
UNCOV
682
                fmt = "%H:%M:%S";
×
683
            }
UNCOV
684
            if (!adjusted_tm) {
×
UNCOV
685
                const auto time_range = time_attr->sa_range;
×
UNCOV
686
                const auto time_sf = string_fragment::from_str_range(
×
UNCOV
687
                    this->lss_token_al.al_string,
×
UNCOV
688
                    time_range.lr_start,
×
UNCOV
689
                    time_range.lr_end);
×
690
                adjusted_tm
UNCOV
691
                    = format->tm_for_display(this->lss_token_line, time_sf);
×
692
            }
UNCOV
693
            adjusted_tm->et_flags |= this->lss_all_timestamp_flags
×
UNCOV
694
                & (ETF_MILLIS_SET | ETF_MICROS_SET | ETF_NANOS_SET);
×
695
            char buffer[128];
696
            this->lss_time_column_size
UNCOV
697
                = ftime_fmt(buffer, sizeof(buffer), fmt, adjusted_tm.value());
×
UNCOV
698
            if (this->tss_view->is_selectable()
×
UNCOV
699
                && this->tss_view->get_selection() == row)
×
700
            {
UNCOV
701
                buffer[this->lss_time_column_size] = ' ';
×
UNCOV
702
                buffer[this->lss_time_column_size + 1] = ' ';
×
UNCOV
703
                this->lss_time_column_size += 2;
×
704
            } else {
UNCOV
705
                constexpr char block[] = "\u258c ";
×
706

UNCOV
707
                strcpy(&buffer[this->lss_time_column_size], block);
×
UNCOV
708
                this->lss_time_column_size += sizeof(block) - 1;
×
709
            }
UNCOV
710
            if (time_attr->sa_range.lr_start != 0) {
×
UNCOV
711
                buffer[this->lss_time_column_size] = ' ';
×
UNCOV
712
                this->lss_time_column_size += 1;
×
UNCOV
713
                this->lss_time_column_padding = 1;
×
714
            } else {
UNCOV
715
                this->lss_time_column_padding = 0;
×
716
            }
UNCOV
717
            value_out.insert(1, buffer, this->lss_time_column_size);
×
UNCOV
718
            this->lss_token_al.al_attrs.emplace_back(time_attr->sa_range,
×
UNCOV
719
                                                     SA_REPLACED.value());
×
720
        }
UNCOV
721
        if (format->lf_level_hideable) {
×
722
            auto level_attr
UNCOV
723
                = find_string_attr(this->lss_token_al.al_attrs, &L_LEVEL);
×
UNCOV
724
            if (level_attr != this->lss_token_al.al_attrs.end()) {
×
UNCOV
725
                this->lss_token_al.al_attrs.emplace_back(level_attr->sa_range,
×
UNCOV
726
                                                         SA_REPLACED.value());
×
727
            }
728
        }
729
    } else if (this->lss_line_context < line_context_t::none) {
4,844✔
730
        size_t file_offset_end;
UNCOV
731
        std::string name;
×
UNCOV
732
        if (this->lss_line_context == line_context_t::filename) {
×
UNCOV
733
            file_offset_end = this->lss_filename_width;
×
UNCOV
734
            name = fmt::to_string(this->lss_token_file->get_filename());
×
UNCOV
735
            if (file_offset_end < name.size()) {
×
UNCOV
736
                file_offset_end = name.size();
×
UNCOV
737
                this->lss_filename_width = name.size();
×
738
            }
739
        } else {
740
            file_offset_end = this->lss_basename_width;
×
UNCOV
741
            name = fmt::to_string(this->lss_token_file->get_unique_path());
×
UNCOV
742
            if (file_offset_end < name.size()) {
×
743
                file_offset_end = name.size();
×
UNCOV
744
                this->lss_basename_width = name.size();
×
745
            }
746
        }
747
        value_out.insert(0, file_offset_end - name.size(), ' ');
×
UNCOV
748
        value_out.insert(0, name);
×
749
    }
750

751
    if (this->tas_display_time_offset) {
4,844✔
752
        auto row_vl = vis_line_t(row);
210✔
753
        auto relstr = this->get_time_offset_for_line(tc, row_vl);
210✔
754
        value_out = fmt::format(FMT_STRING("{: >12}|{}"), relstr, value_out);
840✔
755
    }
210✔
756

757
    this->lss_in_value_for_line = false;
4,844✔
758

759
    return retval;
4,844✔
760
}
761

762
void
763
logfile_sub_source::text_attrs_for_line(textview_curses& lv,
4,844✔
764
                                        int row,
765
                                        string_attrs_t& value_out)
766
{
767
    if (this->lss_indexing_in_progress) {
4,844✔
768
        return;
×
769
    }
770

771
    auto& vc = view_colors::singleton();
4,844✔
772
    logline* next_line = nullptr;
4,844✔
773
    line_range lr;
4,844✔
774
    int time_offset_end = 0;
4,844✔
775
    text_attrs attrs;
4,844✔
776
    auto* format = this->lss_token_file->get_format_ptr();
4,844✔
777

778
    if ((row + 1) < (int) this->lss_filtered_index.size()) {
4,844✔
779
        next_line = this->find_line(this->at(vis_line_t(row + 1)));
4,624✔
780
    }
781

782
    if (next_line != nullptr
4,844✔
783
        && (day_num(next_line->get_time<std::chrono::seconds>().count())
9,468✔
784
            > day_num(this->lss_token_line->get_time<std::chrono::seconds>()
9,468✔
785
                          .count())))
786
    {
787
        attrs |= text_attrs::style::underline;
19✔
788
    }
789

790
    const auto& line_values = this->lss_token_values;
4,844✔
791

792
    lr.lr_start = 0;
4,844✔
793
    lr.lr_end = -1;
4,844✔
794
    this->lss_token_al.al_attrs.emplace_back(
4,844✔
795
        lr, SA_LEVEL.value(this->lss_token_line->get_msg_level()));
9,688✔
796

797
    lr.lr_start = time_offset_end;
4,844✔
798
    lr.lr_end = -1;
4,844✔
799

800
    if (!attrs.empty()) {
4,844✔
801
        this->lss_token_al.al_attrs.emplace_back(lr, VC_STYLE.value(attrs));
19✔
802
    }
803

804
    if (this->lss_token_line->get_msg_level() == log_level_t::LEVEL_INVALID) {
4,844✔
805
        this->lss_token_al.al_attrs.emplace_back(
39✔
806
            line_range{0, -1}, VC_ROLE.value(role_t::VCR_INVALID_MSG));
78✔
807
    }
808

809
    for (const auto& line_value : line_values.lvv_values) {
47,280✔
810
        if ((!(this->lss_token_flags & RF_FULL)
94,913✔
811
             && line_value.lv_sub_offset
84,444✔
812
                 != this->lss_token_line->get_sub_offset())
42,222✔
813
            || !line_value.lv_origin.is_valid())
84,658✔
814
        {
815
            continue;
10,041✔
816
        }
817

818
        if (line_value.lv_meta.is_hidden()) {
32,395✔
819
            this->lss_token_al.al_attrs.emplace_back(
344✔
820
                line_value.lv_origin, SA_HIDDEN.value(ui_icon_t::hidden));
172✔
821
        }
822

823
        if (!line_value.lv_meta.lvm_identifier
83,257✔
824
            || !line_value.lv_origin.is_valid())
32,395✔
825
        {
826
            continue;
18,467✔
827
        }
828

829
        if (line_value.lv_highlighted) {
13,928✔
830
            continue;
7✔
831
        }
832

833
        this->lss_token_al.al_attrs.emplace_back(
27,842✔
834
            line_value.lv_origin, VC_ROLE.value(role_t::VCR_IDENTIFIER));
13,921✔
835
    }
836

837
    // Apply shifts right-to-left so positions remain valid.
838
    std::stable_sort(
4,844✔
839
        this->lss_token_shifts.begin(),
840
        this->lss_token_shifts.end(),
841
        [](const auto& a, const auto& b) { return a.first > b.first; });
2✔
842
    for (const auto& shift : this->lss_token_shifts) {
6,091✔
843
        shift_string_attrs(
1,247✔
844
            this->lss_token_al.al_attrs, shift.first + 1, shift.second);
1,247✔
845
    }
846

847
    shift_string_attrs(this->lss_token_al.al_attrs, 0, 1);
4,844✔
848

849
    lr.lr_start = 0;
4,844✔
850
    lr.lr_end = 1;
4,844✔
851
    {
852
        auto& bm = lv.get_bookmarks();
4,844✔
853
        const auto& bv = bm[&BM_FILES];
4,844✔
854
        bool is_first_for_file = bv.bv_tree.exists(vis_line_t(row));
4,844✔
855
        bool is_last_for_file = bv.bv_tree.exists(vis_line_t(row + 1));
4,844✔
856
        auto graph = NCACS_VLINE;
4,844✔
857
        if (is_first_for_file) {
4,844✔
858
            if (is_last_for_file) {
463✔
859
                graph = NCACS_HLINE;
17✔
860
            } else {
861
                graph = NCACS_ULCORNER;
446✔
862
            }
863
        } else if (is_last_for_file) {
4,381✔
864
            graph = NCACS_LLCORNER;
24✔
865
        }
866
        this->lss_token_al.al_attrs.emplace_back(lr, VC_GRAPHIC.value(graph));
4,844✔
867

868
        if (!(this->lss_token_flags & RF_FULL)) {
4,844✔
869
            const auto& bv_search = bm[&textview_curses::BM_SEARCH];
4,796✔
870

871
            if (bv_search.bv_tree.exists(vis_line_t(row))) {
4,796✔
872
                lr.lr_start = 0;
13✔
873
                lr.lr_end = 1;
13✔
874
                this->lss_token_al.al_attrs.emplace_back(
13✔
875
                    lr, VC_STYLE.value(text_attrs::with_reverse()));
26✔
876
            }
877
        }
878
    }
879

880
    this->lss_token_al.al_attrs.emplace_back(
4,844✔
881
        lr,
882
        VC_STYLE.value(
9,688✔
883
            vc.attrs_for_ident(this->lss_token_file->get_filename())));
9,688✔
884

885
    if (this->lss_line_context < line_context_t::none) {
4,844✔
UNCOV
886
        size_t file_offset_end
×
UNCOV
887
            = (this->lss_line_context == line_context_t::filename)
×
UNCOV
888
            ? this->lss_filename_width
×
889
            : this->lss_basename_width;
890

UNCOV
891
        shift_string_attrs(this->lss_token_al.al_attrs, 0, file_offset_end);
×
892

UNCOV
893
        lr.lr_start = 0;
×
UNCOV
894
        lr.lr_end = file_offset_end + 1;
×
UNCOV
895
        this->lss_token_al.al_attrs.emplace_back(
×
896
            lr,
897
            VC_STYLE.value(
×
898
                vc.attrs_for_ident(this->lss_token_file->get_filename())));
×
899
    } else if (this->lss_time_column_size > 0) {
4,844✔
900
        shift_string_attrs(
×
UNCOV
901
            this->lss_token_al.al_attrs, 1, this->lss_time_column_size);
×
902

903
        ui_icon_t icon;
UNCOV
904
        switch (this->lss_token_line->get_msg_level()) {
×
905
            case LEVEL_TRACE:
×
906
                icon = ui_icon_t::log_level_trace;
×
907
                break;
×
908
            case LEVEL_DEBUG:
×
909
            case LEVEL_DEBUG2:
910
            case LEVEL_DEBUG3:
911
            case LEVEL_DEBUG4:
912
            case LEVEL_DEBUG5:
913
                icon = ui_icon_t::log_level_debug;
×
914
                break;
×
915
            case LEVEL_INFO:
×
916
                icon = ui_icon_t::log_level_info;
×
UNCOV
917
                break;
×
UNCOV
918
            case LEVEL_STATS:
×
UNCOV
919
                icon = ui_icon_t::log_level_stats;
×
920
                break;
×
UNCOV
921
            case LEVEL_NOTICE:
×
UNCOV
922
                icon = ui_icon_t::log_level_notice;
×
UNCOV
923
                break;
×
924
            case LEVEL_WARNING:
×
925
                icon = ui_icon_t::log_level_warning;
×
926
                break;
×
927
            case LEVEL_ERROR:
×
928
                icon = ui_icon_t::log_level_error;
×
929
                break;
×
930
            case LEVEL_CRITICAL:
×
931
                icon = ui_icon_t::log_level_critical;
×
UNCOV
932
                break;
×
933
            case LEVEL_FATAL:
×
UNCOV
934
                icon = ui_icon_t::log_level_fatal;
×
935
                break;
×
936
            default:
×
937
                icon = ui_icon_t::hidden;
×
UNCOV
938
                break;
×
939
        }
940
        auto extra_space_size = this->lss_time_column_padding;
×
941
        lr.lr_start = 1 + this->lss_time_column_size - 1 - extra_space_size;
×
942
        lr.lr_end = 1 + this->lss_time_column_size - extra_space_size;
×
943
        this->lss_token_al.al_attrs.emplace_back(lr, VC_ICON.value(icon));
×
UNCOV
944
        if (this->tss_view->is_selectable()
×
UNCOV
945
            && this->tss_view->get_selection() != row)
×
946
        {
947
            lr.lr_start = 1;
×
UNCOV
948
            lr.lr_end = 1 + this->lss_time_column_size - 2 - extra_space_size;
×
UNCOV
949
            this->lss_token_al.al_attrs.emplace_back(
×
UNCOV
950
                lr, VC_ROLE.value(role_t::VCR_TIME_COLUMN));
×
UNCOV
951
            if (this->lss_token_line->is_time_skewed()) {
×
UNCOV
952
                this->lss_token_al.al_attrs.emplace_back(
×
UNCOV
953
                    lr, VC_ROLE.value(role_t::VCR_SKEWED_TIME));
×
954
            }
UNCOV
955
            lr.lr_start = 1 + this->lss_time_column_size - 2 - extra_space_size;
×
UNCOV
956
            lr.lr_end = 1 + this->lss_time_column_size - 1 - extra_space_size;
×
UNCOV
957
            this->lss_token_al.al_attrs.emplace_back(
×
958
                lr, VC_ROLE.value(role_t::VCR_TIME_COLUMN_TO_TEXT));
×
959
        }
960
    }
961

962
    if (this->tas_display_time_offset) {
4,844✔
963
        time_offset_end = 13;
210✔
964
        lr.lr_start = 0;
210✔
965
        lr.lr_end = time_offset_end;
210✔
966

967
        shift_string_attrs(this->lss_token_al.al_attrs, 0, time_offset_end);
210✔
968

969
        this->lss_token_al.al_attrs.emplace_back(
210✔
970
            lr, VC_ROLE.value(role_t::VCR_OFFSET_TIME));
420✔
971
        this->lss_token_al.al_attrs.emplace_back(line_range(12, 13),
210✔
972
                                                 VC_GRAPHIC.value(NCACS_VLINE));
420✔
973

974
        auto bar_role = role_t::VCR_NONE;
210✔
975

976
        switch (this->get_line_accel_direction(vis_line_t(row))) {
210✔
977
            case log_accel::direction_t::A_STEADY:
126✔
978
                break;
126✔
979
            case log_accel::direction_t::A_DECEL:
42✔
980
                bar_role = role_t::VCR_DIFF_DELETE;
42✔
981
                break;
42✔
982
            case log_accel::direction_t::A_ACCEL:
42✔
983
                bar_role = role_t::VCR_DIFF_ADD;
42✔
984
                break;
42✔
985
        }
986
        if (bar_role != role_t::VCR_NONE) {
210✔
987
            this->lss_token_al.al_attrs.emplace_back(line_range(12, 13),
84✔
988
                                                     VC_ROLE.value(bar_role));
168✔
989
        }
990
    }
991

992
    lr.lr_start = 0;
4,844✔
993
    lr.lr_end = -1;
4,844✔
994
    this->lss_token_al.al_attrs.emplace_back(
4,844✔
995
        lr, L_FILE.value(this->lss_token_file));
9,688✔
996
    this->lss_token_al.al_attrs.emplace_back(
4,844✔
997
        lr, SA_FORMAT.value(format->get_name()));
9,688✔
998

999
    {
1000
        auto line_meta_context = this->get_bookmark_metadata_context(
4,844✔
1001
            vis_line_t(row + 1), bookmark_metadata::categories::partition);
1002
        if (line_meta_context.bmc_current_metadata) {
4,844✔
1003
            lr.lr_start = 0;
11✔
1004
            lr.lr_end = -1;
11✔
1005
            this->lss_token_al.al_attrs.emplace_back(
11✔
1006
                lr,
1007
                L_PARTITION.value(
22✔
1008
                    line_meta_context.bmc_current_metadata.value()));
1009
        }
1010

1011
        auto line_meta_opt = this->find_bookmark_metadata(vis_line_t(row));
4,844✔
1012

1013
        if (line_meta_opt) {
4,844✔
1014
            lr.lr_start = 0;
28✔
1015
            lr.lr_end = -1;
28✔
1016
            this->lss_token_al.al_attrs.emplace_back(
28✔
1017
                lr, L_META.value(line_meta_opt.value()));
56✔
1018
        }
1019
    }
1020

1021
    if (this->lss_time_column_size == 0) {
4,844✔
1022
        if (this->lss_token_file->is_time_adjusted()) {
4,844✔
1023
            auto time_range = find_string_attr_range(
17✔
1024
                this->lss_token_al.al_attrs, &L_TIMESTAMP);
17✔
1025

1026
            if (time_range.lr_end != -1) {
17✔
1027
                this->lss_token_al.al_attrs.emplace_back(
15✔
1028
                    time_range, VC_ROLE.value(role_t::VCR_ADJUSTED_TIME));
30✔
1029
            }
1030
        } else if (this->lss_token_line->is_time_skewed()) {
4,827✔
1031
            auto time_range = find_string_attr_range(
12✔
1032
                this->lss_token_al.al_attrs, &L_TIMESTAMP);
12✔
1033

1034
            if (time_range.lr_end != -1) {
12✔
1035
                this->lss_token_al.al_attrs.emplace_back(
8✔
1036
                    time_range, VC_ROLE.value(role_t::VCR_SKEWED_TIME));
16✔
1037
            }
1038
        }
1039
    }
1040

1041
    if (this->tss_preview_min_log_level
4,844✔
1042
        && this->lss_token_line->get_msg_level()
4,844✔
1043
            < this->tss_preview_min_log_level)
4,844✔
1044
    {
UNCOV
1045
        auto color = styling::color_unit::from_palette(
×
UNCOV
1046
            lnav::enums::to_underlying(ansi_color::red));
×
UNCOV
1047
        this->lss_token_al.al_attrs.emplace_back(line_range{0, 1},
×
UNCOV
1048
                                                 VC_BACKGROUND.value(color));
×
1049
    }
1050
    if (this->ttt_preview_min_time
4,844✔
1051
        && this->lss_token_line->get_timeval() < this->ttt_preview_min_time)
4,844✔
1052
    {
UNCOV
1053
        auto color = styling::color_unit::from_palette(
×
UNCOV
1054
            lnav::enums::to_underlying(ansi_color::red));
×
UNCOV
1055
        this->lss_token_al.al_attrs.emplace_back(line_range{0, 1},
×
UNCOV
1056
                                                 VC_BACKGROUND.value(color));
×
1057
    }
1058
    if (this->ttt_preview_max_time
4,844✔
1059
        && this->ttt_preview_max_time < this->lss_token_line->get_timeval())
4,844✔
1060
    {
UNCOV
1061
        auto color = styling::color_unit::from_palette(
×
UNCOV
1062
            lnav::enums::to_underlying(ansi_color::red));
×
UNCOV
1063
        this->lss_token_al.al_attrs.emplace_back(line_range{0, 1},
×
UNCOV
1064
                                                 VC_BACKGROUND.value(color));
×
1065
    }
1066
    if (!this->lss_token_line->is_continued()) {
4,844✔
1067
        if (this->lss_preview_filter_stmt != nullptr) {
2,911✔
UNCOV
1068
            auto color = styling::color_unit::EMPTY;
×
1069
            auto eval_res
1070
                = this->eval_sql_filter(this->lss_preview_filter_stmt.in(),
1071
                                        this->lss_token_file_data,
UNCOV
1072
                                        this->lss_token_line);
×
UNCOV
1073
            if (eval_res.isErr()) {
×
UNCOV
1074
                color = palette_color{
×
UNCOV
1075
                    lnav::enums::to_underlying(ansi_color::yellow)};
×
UNCOV
1076
                this->lss_token_al.al_attrs.emplace_back(
×
UNCOV
1077
                    line_range{0, -1},
×
UNCOV
1078
                    SA_ERROR.value(
×
UNCOV
1079
                        eval_res.unwrapErr().to_attr_line().get_string()));
×
1080
            } else {
UNCOV
1081
                auto matched = eval_res.unwrap();
×
1082

UNCOV
1083
                if (matched) {
×
UNCOV
1084
                    color = palette_color{
×
UNCOV
1085
                        lnav::enums::to_underlying(ansi_color::green)};
×
1086
                } else {
UNCOV
1087
                    color = palette_color{
×
UNCOV
1088
                        lnav::enums::to_underlying(ansi_color::red)};
×
UNCOV
1089
                    this->lss_token_al.al_attrs.emplace_back(
×
UNCOV
1090
                        line_range{0, 1},
×
UNCOV
1091
                        VC_STYLE.value(text_attrs::with_blink()));
×
1092
                }
1093
            }
UNCOV
1094
            this->lss_token_al.al_attrs.emplace_back(
×
UNCOV
1095
                line_range{0, 1}, VC_BACKGROUND.value(color));
×
1096
        }
1097

1098
        auto sql_filter_opt = this->get_sql_filter();
2,911✔
1099
        if (sql_filter_opt) {
2,911✔
1100
            auto* sf = (sql_filter*) sql_filter_opt.value().get();
36✔
1101
            auto eval_res = this->eval_sql_filter(sf->sf_filter_stmt.in(),
1102
                                                  this->lss_token_file_data,
1103
                                                  this->lss_token_line);
36✔
1104
            if (eval_res.isErr()) {
36✔
1105
                auto msg = fmt::format(
UNCOV
1106
                    FMT_STRING(
×
1107
                        "filter expression evaluation failed with -- {}"),
UNCOV
1108
                    eval_res.unwrapErr().to_attr_line().get_string());
×
UNCOV
1109
                auto cu = styling::color_unit::from_palette(palette_color{
×
1110
                    lnav::enums::to_underlying(ansi_color::yellow)});
UNCOV
1111
                this->lss_token_al.al_attrs.emplace_back(line_range{0, -1},
×
UNCOV
1112
                                                         SA_ERROR.value(msg));
×
UNCOV
1113
                this->lss_token_al.al_attrs.emplace_back(
×
UNCOV
1114
                    line_range{0, 1}, VC_BACKGROUND.value(cu));
×
1115
            }
1116
        }
36✔
1117
    }
2,911✔
1118

1119
    if ((this->tss_context_before > 0 || this->tss_context_after > 0)
4,844✔
1120
        && this->tss_apply_filters)
47✔
1121
    {
1122
        uint32_t ctx_filter_in_mask, ctx_filter_out_mask;
1123
        this->get_filters().get_enabled_mask(ctx_filter_in_mask,
47✔
1124
                                             ctx_filter_out_mask);
1125
        uint64_t line_number;
1126
        auto cl = this->at(vis_line_t(row));
47✔
1127
        auto ld = this->find_data(cl, line_number);
47✔
1128
        if ((*ld)->ld_filter_state.excluded(
47✔
1129
                ctx_filter_in_mask, ctx_filter_out_mask, line_number)
1130
            || !this->check_extra_filters(
92✔
1131
                ld, (*ld)->get_file_ptr()->begin() + line_number))
92✔
1132
        {
1133
            this->lss_token_al.al_attrs.emplace_back(
43✔
1134
                line_range{0, -1}, VC_ROLE.value(role_t::VCR_CONTEXT_LINE));
86✔
1135
        }
1136
    }
1137

1138
    value_out = std::move(this->lss_token_al.al_attrs);
4,844✔
1139
}
1140

1141
void
NEW
1142
logfile_sub_source::text_horiz_columns(textview_curses& tc,
×
1143
                                       vis_line_t start_row,
1144
                                       vis_line_t end_row,
1145
                                       std::set<int>& columns_out)
1146
{
NEW
1147
    if (this->lss_indexing_in_progress) {
×
NEW
1148
        return;
×
1149
    }
1150

NEW
1151
    auto count = vis_line_t((int) this->text_line_count());
×
NEW
1152
    auto begin = std::max(0_vl, start_row);
×
NEW
1153
    auto last = std::min(count, end_row);
×
NEW
1154
    if (last <= begin) {
×
NEW
1155
        return;
×
1156
    }
NEW
1157
    auto win = this->window_at(begin, last);
×
NEW
1158
    std::map<intern_string_t, int> field2left;
×
NEW
1159
    std::optional<int> ts_left_opt;
×
NEW
1160
    std::optional<int> level_left_opt;
×
NEW
1161
    std::optional<int> body_left_opt;
×
NEW
1162
    for (const auto& msg : *win) {
×
NEW
1163
        const auto& values = msg.get_values();
×
NEW
1164
        auto line_sf = values.lvv_sbr.to_string_fragment();
×
NEW
1165
        std::optional<line_range> ts_range;
×
NEW
1166
        std::optional<line_range> level_range;
×
NEW
1167
        auto time_col_size = this->lss_time_column_size;
×
NEW
1168
        if (time_col_size > 0) {
×
NEW
1169
            time_col_size -= 1;
×
1170
        }
NEW
1171
        for (const auto& sa : msg.get_attrs()) {
×
NEW
1172
            if (sa.sa_type != &L_TIMESTAMP && sa.sa_type != &L_LEVEL) {
×
NEW
1173
                continue;
×
1174
            }
NEW
1175
            if (!sa.sa_range.is_valid()) {
×
NEW
1176
                continue;
×
1177
            }
NEW
1178
            if (this->lss_line_context == line_context_t::time_column) {
×
NEW
1179
                if (sa.sa_type == &L_TIMESTAMP) {
×
NEW
1180
                    ts_range = sa.sa_range;
×
1181
                } else {
NEW
1182
                    level_range = sa.sa_range;
×
1183
                }
1184
            } else {
1185
                auto sa_left
NEW
1186
                    = (int) line_sf.byte_to_column_index(sa.sa_range.lr_start);
×
NEW
1187
                if (sa.sa_type == &L_TIMESTAMP) {
×
NEW
1188
                    if (!ts_left_opt || sa_left < ts_left_opt.value()) {
×
NEW
1189
                        ts_left_opt = sa_left;
×
1190
                    }
1191
                } else {
NEW
1192
                    if (!level_left_opt || sa_left < level_left_opt.value()) {
×
NEW
1193
                        level_left_opt = sa_left;
×
1194
                    }
1195
                }
1196
            }
1197
        }
NEW
1198
        for (const auto& sa : msg.get_attrs()) {
×
NEW
1199
            if (sa.sa_type != &SA_BODY) {
×
NEW
1200
                continue;
×
1201
            }
NEW
1202
            if (!sa.sa_range.is_valid()) {
×
NEW
1203
                continue;
×
1204
            }
NEW
1205
            auto curr_range = sa.sa_range;
×
NEW
1206
            if (ts_range && ts_range.value() < sa.sa_range) {
×
NEW
1207
                curr_range.lr_start -= ts_range->length();
×
1208
            }
NEW
1209
            if (level_range && level_range.value() < sa.sa_range) {
×
NEW
1210
                curr_range.lr_start -= level_range->length();
×
1211
            }
NEW
1212
            curr_range.lr_start += time_col_size;
×
1213
            auto body_left
NEW
1214
                = (int) line_sf.byte_to_column_index(curr_range.lr_start);
×
NEW
1215
            if (!body_left_opt || body_left < body_left_opt.value()) {
×
NEW
1216
                body_left_opt = body_left;
×
1217
            }
1218
        }
NEW
1219
        for (const auto& lv : values.lvv_values) {
×
NEW
1220
            if (!lv.lv_origin.is_valid() || lv.lv_origin.lr_start < 0) {
×
NEW
1221
                continue;
×
1222
            }
NEW
1223
            if (lv.lv_meta.lvm_column.is<logline_value_meta::internal_column>())
×
1224
            {
NEW
1225
                continue;
×
1226
            }
NEW
1227
            auto curr_range = lv.lv_origin;
×
NEW
1228
            if (ts_range && ts_range.value() < lv.lv_origin) {
×
NEW
1229
                curr_range.lr_start -= ts_range->length();
×
1230
            }
NEW
1231
            if (level_range && level_range.value() < lv.lv_origin) {
×
NEW
1232
                curr_range.lr_start -= level_range->length();
×
1233
            }
NEW
1234
            curr_range.lr_start += time_col_size;
×
1235

1236
            auto left_for_value
NEW
1237
                = (int) line_sf.byte_to_column_index(curr_range.lr_start);
×
NEW
1238
            auto iter = field2left.find(lv.lv_meta.lvm_name);
×
NEW
1239
            if (iter == field2left.end()) {
×
NEW
1240
                field2left.emplace(lv.lv_meta.lvm_name, left_for_value);
×
NEW
1241
            } else if (left_for_value < iter->second) {
×
NEW
1242
                iter->second = left_for_value;
×
1243
            }
1244
        }
1245
    }
1246

NEW
1247
    for (const auto& [name, left] : field2left) {
×
NEW
1248
        log_debug("inserting %d for %s", left, name.c_str());
×
NEW
1249
        columns_out.insert(left);
×
1250
    }
NEW
1251
    if (body_left_opt) {
×
NEW
1252
        columns_out.insert(body_left_opt.value());
×
1253
    }
NEW
1254
    if (ts_left_opt) {
×
NEW
1255
        columns_out.insert(ts_left_opt.value());
×
1256
    }
NEW
1257
    if (level_left_opt) {
×
NEW
1258
        columns_out.insert(level_left_opt.value());
×
1259
    }
1260
}
1261

1262
struct logline_cmp {
1263
    explicit logline_cmp(logfile_sub_source& lc) : llss_controller(lc) {}
1,448✔
1264

1265
    bool operator()(const logfile_sub_source::indexed_content& lhs,
126,061✔
1266
                    const logfile_sub_source::indexed_content& rhs) const
1267
    {
1268
        const auto* ll_lhs = this->llss_controller.find_line(lhs.value());
126,061✔
1269
        const auto* ll_rhs = this->llss_controller.find_line(rhs.value());
126,061✔
1270

1271
        return (*ll_lhs) < (*ll_rhs);
126,061✔
1272
    }
1273

UNCOV
1274
    bool operator()(const logfile_sub_source::indexed_content& lhs,
×
1275
                    const timeval& rhs) const
1276
    {
UNCOV
1277
        const auto* ll_lhs = this->llss_controller.find_line(lhs.value());
×
1278

UNCOV
1279
        return *ll_lhs < rhs;
×
1280
    }
1281

1282
    logfile_sub_source& llss_controller;
1283
};
1284

1285
logfile_sub_source::rebuild_result
1286
logfile_sub_source::rebuild_index(std::optional<ui_clock::time_point> deadline)
5,571✔
1287
{
1288
    if (this->tss_view == nullptr) {
5,571✔
1289
        return rebuild_result::rr_no_change;
128✔
1290
    }
1291

1292
    this->lss_indexing_in_progress = true;
5,443✔
1293
    auto fin = finally([this]() { this->lss_indexing_in_progress = false; });
5,443✔
1294

1295
    iterator iter;
5,443✔
1296
    size_t total_lines = 0;
5,443✔
1297
    size_t est_remaining_lines = 0;
5,443✔
1298
    auto all_time_ordered_formats = true;
5,443✔
1299
    auto full_sort = this->lss_index.empty();
5,443✔
1300
    int file_count = 0;
5,443✔
1301
    auto force = std::exchange(this->lss_force_rebuild, false);
5,443✔
1302
    auto retval = rebuild_result::rr_no_change;
5,443✔
1303
    std::optional<timeval> lowest_tv = std::nullopt;
5,443✔
1304
    auto search_start = 0_vl;
5,443✔
1305

1306
    if (force) {
5,443✔
1307
        log_debug("forced to full rebuild");
616✔
1308
        retval = rebuild_result::rr_full_rebuild;
616✔
1309
        full_sort = true;
616✔
1310
        this->tss_level_filtered_count = 0;
616✔
1311
        this->lss_index.clear();
616✔
1312
    }
1313

1314
    std::vector<size_t> file_order(this->lss_files.size());
5,443✔
1315

1316
    for (size_t lpc = 0; lpc < file_order.size(); lpc++) {
9,472✔
1317
        file_order[lpc] = lpc;
4,029✔
1318
    }
1319
    if (!this->lss_index.empty()) {
5,443✔
1320
        std::stable_sort(
2,947✔
1321
            file_order.begin(),
1322
            file_order.end(),
1323
            [this](const auto& left, const auto& right) {
421✔
1324
                const auto& left_ld = this->lss_files[left];
421✔
1325
                const auto& right_ld = this->lss_files[right];
421✔
1326

1327
                if (left_ld->get_file_ptr() == nullptr) {
421✔
UNCOV
1328
                    return true;
×
1329
                }
1330
                if (right_ld->get_file_ptr() == nullptr) {
421✔
1331
                    return false;
3✔
1332
                }
1333

1334
                // Group metrics_log files together so the
1335
                // index-build dedup and render-side sibling
1336
                // walk don't get interrupted by a regular
1337
                // log message that happens to share a
1338
                // timestamp with a metric row.
1339
                const auto left_metric
418✔
1340
                    = left_ld->get_file_ptr()->get_format_ptr()->lf_is_metric;
418✔
1341
                const auto right_metric
418✔
1342
                    = right_ld->get_file_ptr()->get_format_ptr()->lf_is_metric;
418✔
1343
                if (left_metric != right_metric) {
418✔
1344
                    return left_metric;
74✔
1345
                }
1346

1347
                return left_ld->get_file_ptr()->back()
344✔
1348
                    < right_ld->get_file_ptr()->back();
688✔
1349
            });
1350
    }
1351

1352
    bool time_left = true;
5,443✔
1353
    this->lss_all_timestamp_flags = 0;
5,443✔
1354
    for (const auto file_index : file_order) {
9,472✔
1355
        auto& ld = *(this->lss_files[file_index]);
4,029✔
1356
        auto* lf = ld.get_file_ptr();
4,029✔
1357

1358
        if (lf == nullptr) {
4,029✔
1359
            if (ld.ld_lines_indexed > 0) {
4✔
1360
                log_debug("%zu: file closed, doing full rebuild",
1✔
1361
                          ld.ld_file_index);
1362
                force = true;
1✔
1363
                retval = rebuild_result::rr_full_rebuild;
1✔
1364
                full_sort = true;
1✔
1365
            }
1366
        } else {
1367
            if (!lf->get_format_ptr()->lf_time_ordered) {
4,025✔
1368
                all_time_ordered_formats = false;
595✔
1369
            }
1370
            if (time_left && deadline && ui_clock::now() > deadline.value()) {
4,025✔
1371
                log_debug("no time left, skipping %s",
2✔
1372
                          lf->get_filename_as_string().c_str());
1373
                time_left = false;
2✔
1374
            }
1375
            this->lss_all_timestamp_flags
4,025✔
1376
                |= lf->get_format_ptr()->lf_timestamp_flags;
4,025✔
1377

1378
            if (!this->tss_view->is_paused() && time_left) {
4,025✔
1379
                auto log_rebuild_res = lf->rebuild_index(deadline);
4,023✔
1380

1381
                if (ld.ld_lines_indexed < lf->size()
4,023✔
1382
                    && log_rebuild_res
4,023✔
1383
                        == logfile::rebuild_result_t::NO_NEW_LINES)
1384
                {
1385
                    // This is a bit awkward... if the logfile indexing was
1386
                    // complete before being added to us, we need to adjust
1387
                    // the rebuild result to make it look like new lines
1388
                    // were added.
1389
                    log_rebuild_res = logfile::rebuild_result_t::NEW_LINES;
52✔
1390
                }
1391
                switch (log_rebuild_res) {
4,023✔
1392
                    case logfile::rebuild_result_t::NO_NEW_LINES:
3,311✔
1393
                        break;
3,311✔
1394
                    case logfile::rebuild_result_t::NEW_LINES:
644✔
1395
                        if (retval == rebuild_result::rr_no_change) {
644✔
1396
                            retval = rebuild_result::rr_appended_lines;
567✔
1397
                        }
1398
                        log_debug("new lines for %s:%zu",
644✔
1399
                                  lf->get_filename_as_string().c_str(),
1400
                                  lf->size());
1401
                        if (!this->lss_index.empty()
644✔
1402
                            && lf->size() > ld.ld_lines_indexed)
644✔
1403
                        {
1404
                            auto& new_file_line = (*lf)[ld.ld_lines_indexed];
×
1405
                            auto cl = this->lss_index.back().value();
×
1406
                            auto* last_indexed_line = this->find_line(cl);
×
1407

1408
                            // If there are new lines that are older than what
1409
                            // we have in the index, we need to resort.
1410
                            if (last_indexed_line == nullptr
×
UNCOV
1411
                                || new_file_line
×
1412
                                    < last_indexed_line->get_timeval())
×
1413
                            {
UNCOV
1414
                                log_debug(
×
1415
                                    "%s:%ld: found older lines, full "
1416
                                    "rebuild: %p  %lld < %lld",
1417
                                    lf->get_filename().c_str(),
1418
                                    ld.ld_lines_indexed,
1419
                                    last_indexed_line,
1420
                                    new_file_line.get_time<>().count(),
1421
                                    last_indexed_line == nullptr
1422
                                        ? (uint64_t) -1
1423
                                        : last_indexed_line->get_time<>()
1424
                                              .count());
UNCOV
1425
                                if (retval <= rebuild_result::rr_partial_rebuild
×
UNCOV
1426
                                    && all_time_ordered_formats)
×
1427
                                {
UNCOV
1428
                                    retval = rebuild_result::rr_partial_rebuild;
×
UNCOV
1429
                                    if (!lowest_tv
×
UNCOV
1430
                                        || new_file_line.get_timeval()
×
UNCOV
1431
                                            < lowest_tv.value())
×
1432
                                    {
UNCOV
1433
                                        lowest_tv = new_file_line.get_timeval();
×
1434
                                    }
1435
                                } else {
UNCOV
1436
                                    log_debug(
×
1437
                                        "already doing full rebuild, doing "
1438
                                        "full_sort as well");
UNCOV
1439
                                    force = true;
×
UNCOV
1440
                                    full_sort = true;
×
1441
                                }
1442
                            }
1443
                        }
1444
                        break;
644✔
1445
                    case logfile::rebuild_result_t::INVALID:
68✔
1446
                    case logfile::rebuild_result_t::NEW_ORDER:
1447
                        log_debug("%s: log file has a new order, full rebuild",
68✔
1448
                                  lf->get_filename().c_str());
1449
                        retval = rebuild_result::rr_full_rebuild;
68✔
1450
                        force = true;
68✔
1451
                        full_sort = true;
68✔
1452
                        break;
68✔
1453
                }
1454
            }
1455
            file_count += 1;
4,025✔
1456
            total_lines += lf->size();
4,025✔
1457

1458
            est_remaining_lines += lf->estimated_remaining_lines();
4,025✔
1459
        }
1460
    }
1461

1462
    if (!all_time_ordered_formats
5,443✔
1463
        && retval == rebuild_result::rr_partial_rebuild)
487✔
1464
    {
UNCOV
1465
        force = true;
×
UNCOV
1466
        full_sort = true;
×
UNCOV
1467
        retval = rebuild_result::rr_full_rebuild;
×
1468
    }
1469

1470
    if (this->lss_index.reserve(total_lines + est_remaining_lines)) {
5,443✔
1471
        // The index array was reallocated, just do a full sort/rebuild since
1472
        // it's been cleared out.
1473
        log_debug("expanding index capacity %zu", this->lss_index.ba_capacity);
796✔
1474
        force = true;
796✔
1475
        retval = rebuild_result::rr_full_rebuild;
796✔
1476
        full_sort = true;
796✔
1477
        this->tss_level_filtered_count = 0;
796✔
1478
    }
1479

1480
    auto& vis_bm = this->tss_view->get_bookmarks();
5,443✔
1481

1482
    if (force) {
5,443✔
1483
        for (iter = this->lss_files.begin(); iter != this->lss_files.end();
2,144✔
1484
             iter++)
709✔
1485
        {
1486
            (*iter)->ld_lines_indexed = 0;
709✔
1487
        }
1488

1489
        this->lss_index.clear();
1,435✔
1490
        this->lss_filtered_index.clear();
1,435✔
1491
        this->tss_level_filtered_count = 0;
1,435✔
1492
        this->lss_longest_line = 0;
1,435✔
1493
        this->lss_basename_width = 0;
1,435✔
1494
        this->lss_filename_width = 0;
1,435✔
1495
        vis_bm[&textview_curses::BM_USER_EXPR].clear();
1,435✔
1496
        if (this->lss_index_delegate) {
1,435✔
1497
            this->lss_index_delegate->index_start(*this);
1,435✔
1498
        }
1499
    } else if (retval == rebuild_result::rr_partial_rebuild) {
4,008✔
UNCOV
1500
        size_t remaining = 0;
×
1501

UNCOV
1502
        log_debug("partial rebuild with lowest time: %ld",
×
1503
                  lowest_tv.value().tv_sec);
UNCOV
1504
        for (iter = this->lss_files.begin(); iter != this->lss_files.end();
×
UNCOV
1505
             iter++)
×
1506
        {
UNCOV
1507
            logfile_data& ld = *(*iter);
×
UNCOV
1508
            auto* lf = ld.get_file_ptr();
×
1509

UNCOV
1510
            if (lf == nullptr) {
×
1511
                continue;
×
1512
            }
1513

1514
            require(lf->get_format_ptr()->lf_time_ordered);
×
1515

1516
            auto line_iter = lf->find_from_time(lowest_tv.value());
×
1517

UNCOV
1518
            if (line_iter) {
×
UNCOV
1519
                log_debug("lowest line time %ld; line %ld; size %ld; path=%s",
×
1520
                          line_iter.value()->get_timeval().tv_sec,
1521
                          std::distance(lf->cbegin(), line_iter.value()),
1522
                          lf->size(),
1523
                          lf->get_filename_as_string().c_str());
1524
            }
1525
            ld.ld_lines_indexed
1526
                = std::distance(lf->cbegin(), line_iter.value_or(lf->cend()));
×
1527
            remaining += lf->size() - ld.ld_lines_indexed;
×
1528
        }
1529

UNCOV
1530
        auto* row_iter = std::lower_bound(this->lss_index.begin(),
×
1531
                                          this->lss_index.end(),
UNCOV
1532
                                          lowest_tv.value(),
×
1533
                                          logline_cmp(*this));
1534
        this->lss_index.shrink_to(
×
1535
            std::distance(this->lss_index.begin(), row_iter));
×
UNCOV
1536
        log_debug("new index size %ld/%ld; remain %ld",
×
1537
                  this->lss_index.ba_size,
1538
                  this->lss_index.ba_capacity,
1539
                  remaining);
1540
        auto filt_row_iter = std::lower_bound(this->lss_filtered_index.begin(),
×
1541
                                              this->lss_filtered_index.end(),
1542
                                              lowest_tv.value(),
×
1543
                                              filtered_logline_cmp(*this));
UNCOV
1544
        this->lss_filtered_index.resize(
×
1545
            std::distance(this->lss_filtered_index.begin(), filt_row_iter));
×
1546
        search_start = vis_line_t(this->lss_filtered_index.size());
×
1547

1548
        // Drop any before-context entries that reference trimmed indices
UNCOV
1549
        auto trimmed_index_size = this->lss_index.size();
×
UNCOV
1550
        while (!this->lss_ctx_before_msgs.empty()
×
1551
               && this->lss_ctx_before_msgs.back().cmr_start
×
1552
                   >= trimmed_index_size)
1553
        {
UNCOV
1554
            this->lss_ctx_before_msgs.pop_back();
×
1555
        }
1556
        // Reset after-context state since the trimmed tail may have
1557
        // been in an after-context sequence
1558
        this->lss_ctx_after_msgs_remaining = 0;
×
UNCOV
1559
        this->lss_ctx_in_after = false;
×
1560

1561
        if (this->lss_index_delegate) {
×
UNCOV
1562
            this->lss_index_delegate->index_start(*this);
×
1563
            for (const auto row_in_full_index : this->lss_filtered_index) {
×
UNCOV
1564
                auto cl = this->lss_index[row_in_full_index].value();
×
1565
                uint64_t line_number;
1566
                auto ld_iter = this->find_data(cl, line_number);
×
1567
                auto& ld = *ld_iter;
×
UNCOV
1568
                auto line_iter = ld->get_file_ptr()->begin() + line_number;
×
1569

1570
                this->lss_index_delegate->index_line(
×
1571
                    *this, ld->get_file_ptr(), line_iter);
1572
            }
1573
        }
1574
    }
1575

1576
    if (this->lss_index.empty() && !time_left) {
5,443✔
1577
        log_info("ran out of time, skipping rebuild");
×
1578
        // need to make sure we rebuild in case no new data comes in
UNCOV
1579
        this->lss_force_rebuild = true;
×
1580
        return rebuild_result::rr_appended_lines;
×
1581
    }
1582

1583
    if (retval != rebuild_result::rr_no_change || force) {
5,443✔
1584
        size_t index_size = 0, start_size = this->lss_index.size();
1,448✔
1585
        logline_cmp line_cmper(*this);
1,448✔
1586

1587
        // Metric files compose their LOG-view line at render time from
1588
        // timestamp + ` name=value` pairs across every sibling metric
1589
        // file that shares a timestamp.  Sum the worst-case per-file
1590
        // widths so hscroll reserves enough room for a fully merged
1591
        // metric row.
1592
        constexpr size_t METRIC_TS_WIDTH = 26;  // 2026-04-14T10:00:00.000000
1,448✔
1593
        constexpr size_t METRIC_MIN_COL_WIDTH = 10;
1,448✔
1594
        size_t metric_total_width = 0;
1,448✔
1595
        for (auto& ld : this->lss_files) {
2,177✔
1596
            auto* lf = ld->get_file_ptr();
729✔
1597

1598
            if (lf == nullptr) {
729✔
1599
                continue;
1✔
1600
            }
1601
            this->lss_longest_line = std::max(
1,456✔
1602
                this->lss_longest_line, lf->get_longest_line_length() + 1);
728✔
1603
            this->lss_basename_width
1604
                = std::max(this->lss_basename_width,
1,456✔
1605
                           lf->get_unique_path().native().size());
728✔
1606
            this->lss_filename_width = std::max(
1,456✔
1607
                this->lss_filename_width, lf->get_filename().native().size());
728✔
1608

1609
            if (lf->get_format_ptr()->lf_is_metric) {
728✔
1610
                if (metric_total_width == 0) {
70✔
1611
                    metric_total_width = METRIC_TS_WIDTH;
52✔
1612
                }
1613
                for (const auto& meta :
70✔
1614
                     lf->get_format_ptr()->get_value_metadata())
314✔
1615
                {
1616
                    const auto* stats = lf->stats_for_value(meta.lvm_name);
174✔
1617
                    const auto width = std::max(
174✔
1618
                        METRIC_MIN_COL_WIDTH,
1619
                        stats != nullptr ? static_cast<size_t>(stats->lvs_width)
174✔
1620
                                         : METRIC_MIN_COL_WIDTH);
174✔
1621
                    // Layout: "  " + name + '=' + padded value.
1622
                    metric_total_width += 3 + meta.lvm_name.size() + width;
174✔
1623
                }
70✔
1624
            }
1625
        }
1626
        this->lss_longest_line
1627
            = std::max(this->lss_longest_line, metric_total_width + 1);
1,448✔
1628

1629
        if (full_sort) {
1,448✔
1630
            log_trace("rebuild_index full sort");
1,448✔
1631
            for (auto& ld : this->lss_files) {
2,177✔
1632
                auto* lf = ld->get_file_ptr();
729✔
1633

1634
                if (lf == nullptr) {
729✔
1635
                    continue;
1✔
1636
                }
1637

1638
                for (size_t line_index = 0; line_index < lf->size();
17,452✔
1639
                     line_index++)
1640
                {
1641
                    const auto lf_iter
1642
                        = ld->get_file_ptr()->begin() + line_index;
16,724✔
1643
                    if (lf_iter->is_ignored()) {
16,724✔
1644
                        continue;
516✔
1645
                    }
1646

1647
                    content_line_t con_line(
1648
                        ld->ld_file_index * MAX_LINES_PER_FILE + line_index);
16,208✔
1649

1650
                    if (lf_iter->is_meta_marked()) {
16,208✔
1651
                        auto start_iter = lf_iter;
44✔
1652
                        while (start_iter->is_continued()) {
44✔
1653
                            --start_iter;
×
1654
                        }
1655
                        int start_index
1656
                            = start_iter - ld->get_file_ptr()->begin();
44✔
1657
                        content_line_t start_con_line(ld->ld_file_index
44✔
1658
                                                          * MAX_LINES_PER_FILE
44✔
1659
                                                      + start_index);
44✔
1660

1661
                        auto& line_meta
1662
                            = ld->get_file_ptr()
1663
                                  ->get_bookmark_metadata()[start_index];
44✔
1664
                        if (line_meta.has(bookmark_metadata::categories::notes))
44✔
1665
                        {
1666
                            this->lss_user_marks[&textview_curses::BM_META]
4✔
1667
                                .insert_once(start_con_line);
4✔
1668
                        }
1669
                        if (line_meta.has(
44✔
1670
                                bookmark_metadata::categories::partition))
1671
                        {
1672
                            this->lss_user_marks[&textview_curses::BM_PARTITION]
40✔
1673
                                .insert_once(start_con_line);
40✔
1674
                        }
1675
                    }
1676
                    this->lss_index.push_back(
16,208✔
1677
                        indexed_content{con_line, lf_iter});
32,416✔
1678
                }
1679
            }
1680

1681
            // XXX get rid of this full sort on the initial run, it's not
1682
            // needed unless the file is not in time-order
1683
            if (this->lss_sorting_observer) {
1,448✔
1684
                this->lss_sorting_observer(*this, 0, this->lss_index.size());
3✔
1685
            }
1686
            std::sort(
1,448✔
1687
                this->lss_index.begin(), this->lss_index.end(), line_cmper);
1688
            if (this->lss_sorting_observer) {
1,448✔
1689
                this->lss_sorting_observer(
6✔
1690
                    *this, this->lss_index.size(), this->lss_index.size());
3✔
1691
            }
1692
        } else {
1693
            kmerge_tree_c<logline, logfile_data, logfile::iterator> merge(
UNCOV
1694
                file_count);
×
1695

UNCOV
1696
            for (iter = this->lss_files.begin(); iter != this->lss_files.end();
×
UNCOV
1697
                 iter++)
×
1698
            {
UNCOV
1699
                auto* ld = iter->get();
×
UNCOV
1700
                auto* lf = ld->get_file_ptr();
×
UNCOV
1701
                if (lf == nullptr) {
×
UNCOV
1702
                    continue;
×
1703
                }
1704

UNCOV
1705
                merge.add(ld, lf->begin() + ld->ld_lines_indexed, lf->end());
×
UNCOV
1706
                index_size += lf->size();
×
1707
            }
1708

1709
            file_off_t index_off = 0;
×
1710
            merge.execute();
×
1711
            if (this->lss_sorting_observer) {
×
1712
                this->lss_sorting_observer(*this, index_off, index_size);
×
1713
            }
1714
            log_trace("k-way merge");
×
1715
            for (;;) {
UNCOV
1716
                logfile::iterator lf_iter;
×
1717
                logfile_data* ld;
1718

UNCOV
1719
                if (!merge.get_top(ld, lf_iter)) {
×
UNCOV
1720
                    break;
×
1721
                }
1722

UNCOV
1723
                if (!lf_iter->is_ignored()) {
×
UNCOV
1724
                    int file_index = ld->ld_file_index;
×
UNCOV
1725
                    int line_index = lf_iter - ld->get_file_ptr()->begin();
×
1726

UNCOV
1727
                    content_line_t con_line(file_index * MAX_LINES_PER_FILE
×
UNCOV
1728
                                            + line_index);
×
1729

UNCOV
1730
                    if (lf_iter->is_meta_marked()) {
×
UNCOV
1731
                        auto start_iter = lf_iter;
×
UNCOV
1732
                        while (start_iter->is_continued()) {
×
UNCOV
1733
                            --start_iter;
×
1734
                        }
1735
                        int start_index
UNCOV
1736
                            = start_iter - ld->get_file_ptr()->begin();
×
1737
                        content_line_t start_con_line(
UNCOV
1738
                            file_index * MAX_LINES_PER_FILE + start_index);
×
1739

1740
                        auto& line_meta
1741
                            = ld->get_file_ptr()
UNCOV
1742
                                  ->get_bookmark_metadata()[start_index];
×
UNCOV
1743
                        if (line_meta.has(bookmark_metadata::categories::notes))
×
1744
                        {
UNCOV
1745
                            this->lss_user_marks[&textview_curses::BM_META]
×
UNCOV
1746
                                .insert_once(start_con_line);
×
1747
                        }
UNCOV
1748
                        if (line_meta.has(
×
1749
                                bookmark_metadata::categories::partition))
1750
                        {
UNCOV
1751
                            this->lss_user_marks[&textview_curses::BM_PARTITION]
×
UNCOV
1752
                                .insert_once(start_con_line);
×
1753
                        }
1754
                    }
UNCOV
1755
                    this->lss_index.push_back(
×
UNCOV
1756
                        indexed_content{con_line, lf_iter});
×
1757
                }
1758

UNCOV
1759
                merge.next();
×
UNCOV
1760
                index_off += 1;
×
UNCOV
1761
                if (index_off % 100000 == 0 && this->lss_sorting_observer) {
×
UNCOV
1762
                    this->lss_sorting_observer(*this, index_off, index_size);
×
1763
                }
1764
            }
UNCOV
1765
            if (this->lss_sorting_observer) {
×
UNCOV
1766
                this->lss_sorting_observer(*this, index_size, index_size);
×
1767
            }
1768
        }
1769

1770
        for (iter = this->lss_files.begin(); iter != this->lss_files.end();
2,177✔
1771
             ++iter)
729✔
1772
        {
1773
            auto* lf = (*iter)->get_file_ptr();
729✔
1774

1775
            if (lf == nullptr) {
729✔
1776
                continue;
1✔
1777
            }
1778

1779
            (*iter)->ld_lines_indexed = lf->size();
728✔
1780
        }
1781

1782
        this->lss_filtered_index.reserve(this->lss_index.size());
1,448✔
1783

1784
        uint32_t filter_in_mask, filter_out_mask;
1785
        this->get_filters().get_enabled_mask(filter_in_mask, filter_out_mask);
1,448✔
1786

1787
        if (start_size == 0 && this->lss_index_delegate != nullptr) {
1,448✔
1788
            this->lss_index_delegate->index_start(*this);
1,448✔
1789
        }
1790

1791
        log_trace("filtered index");
1,448✔
1792

1793
        auto ri_context_before = this->tss_context_before;
1,448✔
1794
        auto ri_context_after = this->tss_context_after;
1,448✔
1795

1796
        if (start_size == 0) {
1,448✔
1797
            this->lss_ctx_before_msgs.clear();
1,448✔
1798
            this->lss_ctx_after_msgs_remaining = 0;
1,448✔
1799
            this->lss_ctx_in_after = false;
1,448✔
1800
        }
1801

1802
        for (size_t index_index = start_size;
17,656✔
1803
             index_index < this->lss_index.size();
17,656✔
1804
             index_index++)
1805
        {
1806
            const auto cl = this->lss_index[index_index].value();
16,208✔
1807
            uint64_t line_number;
1808
            auto ld = this->find_data(cl, line_number);
16,208✔
1809

1810
            if (!(*ld)->is_visible()) {
16,208✔
UNCOV
1811
                continue;
×
1812
            }
1813

1814
            auto* lf = (*ld)->get_file_ptr();
16,208✔
1815
            auto line_iter = lf->begin() + line_number;
16,208✔
1816

1817
            if (line_iter->is_ignored()) {
16,208✔
UNCOV
1818
                continue;
×
1819
            }
1820

1821
            if (!this->tss_apply_filters
32,416✔
1822
                || (!(*ld)->ld_filter_state.excluded(
32,365✔
1823
                        filter_in_mask, filter_out_mask, line_number)
1824
                    && this->check_extra_filters(ld, line_iter)))
16,157✔
1825
            {
1826
                this->flush_context_before_msgs();
16,157✔
1827
                auto eval_res = this->eval_sql_filter(
1828
                    this->lss_marker_stmt.in(), ld, line_iter);
16,157✔
1829
                if (eval_res.isErr()) {
16,157✔
UNCOV
1830
                    line_iter->set_expr_mark(false);
×
1831
                } else {
1832
                    auto matched = eval_res.unwrap();
16,157✔
1833

1834
                    line_iter->set_expr_mark(matched);
16,157✔
1835
                    if (matched) {
16,157✔
UNCOV
1836
                        vis_bm[&textview_curses::BM_USER_EXPR].insert_once(
×
UNCOV
1837
                            vis_line_t(this->lss_filtered_index.size()));
×
UNCOV
1838
                        this->lss_user_marks[&textview_curses::BM_USER_EXPR]
×
UNCOV
1839
                            .insert_once(cl);
×
1840
                    }
1841
                }
1842
                // Metric CSV rows from different files that share a
1843
                // timestamp get collapsed into a single visible line
1844
                // so the LOG view (and, eventually, the `metrics`
1845
                // virtual table) sees one row per timestamp.  The
1846
                // suppressed siblings stay in `lss_index` so the
1847
                // renderer can walk forward to gather their values.
1848
                bool suppress_metric_sibling = false;
16,157✔
1849
                if (lf->get_format_ptr()->lf_is_metric
16,157✔
1850
                    && !this->lss_filtered_index.empty())
16,157✔
1851
                {
1852
                    const auto prev_cl
1853
                        = this->lss_index[this->lss_filtered_index.back()]
342✔
1854
                              .value();
342✔
1855
                    uint64_t prev_line_number;
1856
                    const auto prev_ld
1857
                        = this->find_data(prev_cl, prev_line_number);
342✔
1858
                    const auto* prev_lf = (*prev_ld)->get_file_ptr();
342✔
1859
                    if (prev_lf->get_format_ptr()->lf_is_metric) {
342✔
1860
                        const auto prev_time
1861
                            = (prev_lf->begin() + prev_line_number)
304✔
1862
                                  ->get_time<>();
304✔
1863
                        const auto curr_time = line_iter->get_time<>();
304✔
1864
                        if (prev_time == curr_time) {
304✔
1865
                            suppress_metric_sibling = true;
98✔
1866
                        }
1867
                    }
1868
                }
1869
                if (!suppress_metric_sibling) {
16,157✔
1870
                    this->lss_filtered_index.push_back(index_index);
16,059✔
1871
                }
1872
                if (this->lss_index_delegate != nullptr) {
16,157✔
1873
                    this->lss_index_delegate->index_line(*this, lf, line_iter);
16,157✔
1874
                }
1875
                if (!line_iter->is_continued()) {
16,157✔
1876
                    this->lss_ctx_after_msgs_remaining = ri_context_after;
13,401✔
1877
                }
1878
                this->lss_ctx_in_after = false;
16,157✔
1879
            } else if (this->lss_ctx_in_after && line_iter->is_continued()) {
16,208✔
UNCOV
1880
                this->lss_filtered_index.push_back(index_index);
×
1881
            } else if (this->lss_ctx_after_msgs_remaining > 0
102✔
1882
                       && !line_iter->is_continued())
51✔
1883
            {
UNCOV
1884
                this->lss_ctx_after_msgs_remaining -= 1;
×
UNCOV
1885
                this->lss_ctx_in_after = true;
×
UNCOV
1886
                this->lss_filtered_index.push_back(index_index);
×
1887
            } else {
1888
                this->lss_ctx_in_after = false;
51✔
1889
                if (ri_context_before > 0) {
51✔
UNCOV
1890
                    if (!line_iter->is_continued()
×
UNCOV
1891
                        || this->lss_ctx_before_msgs.empty())
×
1892
                    {
UNCOV
1893
                        if (this->lss_ctx_before_msgs.size()
×
UNCOV
1894
                            >= ri_context_before)
×
1895
                        {
UNCOV
1896
                            this->lss_ctx_before_msgs.pop_front();
×
1897
                        }
UNCOV
1898
                        this->lss_ctx_before_msgs.push_back({index_index, 1});
×
1899
                    } else {
UNCOV
1900
                        this->lss_ctx_before_msgs.back().cmr_count += 1;
×
1901
                    }
1902
                }
1903
            }
1904
        }
1905

1906
        this->lss_indexing_in_progress = false;
1,448✔
1907

1908
        if (this->lss_index_delegate != nullptr) {
1,448✔
1909
            this->lss_index_delegate->index_complete(*this);
1,448✔
1910
        }
1911
    }
1912

1913
    switch (retval) {
5,443✔
1914
        case rebuild_result::rr_no_change:
3,995✔
1915
            break;
3,995✔
1916
        case rebuild_result::rr_full_rebuild:
1,435✔
1917
            log_debug("redoing search");
1,435✔
1918
            this->lss_index_generation += 1;
1,435✔
1919
            this->tss_view->reload_data();
1,435✔
1920
            this->tss_view->redo_search();
1,435✔
1921
            break;
1,435✔
UNCOV
1922
        case rebuild_result::rr_partial_rebuild:
×
UNCOV
1923
            log_debug("redoing search from: %d", (int) search_start);
×
UNCOV
1924
            this->lss_index_generation += 1;
×
UNCOV
1925
            this->tss_view->reload_data();
×
UNCOV
1926
            this->tss_view->search_new_data(search_start);
×
UNCOV
1927
            break;
×
1928
        case rebuild_result::rr_appended_lines:
13✔
1929
            this->tss_view->reload_data();
13✔
1930
            this->tss_view->search_new_data();
13✔
1931
            break;
13✔
1932
    }
1933

1934
    return retval;
5,443✔
1935
}
5,443✔
1936

1937
void
1938
logfile_sub_source::text_update_marks(vis_bookmarks& bm)
2,840✔
1939
{
1940
    logfile* last_file = nullptr;
2,840✔
1941
    vis_line_t vl;
2,840✔
1942

1943
    auto& bm_warnings = bm[&textview_curses::BM_WARNINGS];
2,840✔
1944
    auto& bm_errors = bm[&textview_curses::BM_ERRORS];
2,840✔
1945
    auto& bm_files = bm[&BM_FILES];
2,840✔
1946

1947
    bm_warnings.clear();
2,840✔
1948
    bm_errors.clear();
2,840✔
1949
    bm_files.clear();
2,840✔
1950

1951
    std::vector<const bookmark_type_t*> used_marks;
2,840✔
1952
    for (const auto* bmt :
17,040✔
1953
         {
1954
             &textview_curses::BM_USER,
1955
             &textview_curses::BM_USER_EXPR,
1956
             &textview_curses::BM_PARTITION,
1957
             &textview_curses::BM_META,
1958
             &textview_curses::BM_STICKY,
1959
         })
19,880✔
1960
    {
1961
        bm[bmt].clear();
14,200✔
1962
        if (!this->lss_user_marks[bmt].empty()) {
14,200✔
1963
            used_marks.emplace_back(bmt);
181✔
1964
        }
1965
    }
1966

1967
    for (; vl < (int) this->lss_filtered_index.size(); ++vl) {
24,119✔
1968
        const auto& orig_ic = this->lss_index[this->lss_filtered_index[vl]];
21,279✔
1969
        auto cl = orig_ic.value();
21,279✔
1970
        auto* lf = this->find_file_ptr(cl);
21,279✔
1971

1972
        for (const auto& bmt : used_marks) {
24,485✔
1973
            auto& user_mark = this->lss_user_marks[bmt];
3,206✔
1974
            if (user_mark.bv_tree.exists(orig_ic.value())) {
3,206✔
1975
                bm[bmt].insert_once(vl);
278✔
1976
            }
1977
        }
1978

1979
        if (lf != last_file) {
21,279✔
1980
            bm_files.insert_once(vl);
2,131✔
1981
        }
1982

1983
        switch (orig_ic.level()) {
21,279✔
1984
            case indexed_content::level_t::warning:
201✔
1985
                bm_warnings.insert_once(vl);
201✔
1986
                break;
201✔
1987

1988
            case indexed_content::level_t::error:
3,667✔
1989
                bm_errors.insert_once(vl);
3,667✔
1990
                break;
3,667✔
1991

1992
            default:
17,411✔
1993
                break;
17,411✔
1994
        }
1995

1996
        last_file = lf;
21,279✔
1997
    }
1998
}
2,840✔
1999

2000
void
2001
logfile_sub_source::flush_context_before_msgs()
18,102✔
2002
{
2003
    for (const auto& msg : this->lss_ctx_before_msgs) {
18,105✔
2004
        for (size_t lpc = 0; lpc < msg.cmr_count; lpc++) {
6✔
2005
            this->lss_filtered_index.push_back(msg.cmr_start + lpc);
3✔
2006
        }
2007
    }
2008
    this->lss_ctx_before_msgs.clear();
18,102✔
2009
}
18,102✔
2010

2011
void
2012
logfile_sub_source::text_filters_changed()
232✔
2013
{
2014
    static auto op = lnav_operation{"text_filters_changed"};
232✔
2015

2016
    auto op_guard = lnav_opid_guard::internal(op);
232✔
2017
    this->lss_index_generation += 1;
232✔
2018
    this->tss_level_filtered_count = 0;
232✔
2019

2020
    if (this->lss_line_meta_changed) {
232✔
2021
        this->invalidate_sql_filter();
64✔
2022
        this->lss_line_meta_changed = false;
64✔
2023
    }
2024

2025
    log_debug("filtering files");
232✔
2026
    for (auto& ld : *this) {
421✔
2027
        auto* lf = ld->get_file_ptr();
189✔
2028

2029
        if (lf != nullptr) {
189✔
2030
            ld->ld_filter_state.clear_deleted_filter_state();
189✔
2031
            lf->reobserve_from(lf->begin()
189✔
2032
                               + ld->ld_filter_state.get_min_count(lf->size()));
189✔
2033
        }
2034
    }
2035

2036
    if (this->lss_force_rebuild) {
232✔
2037
        log_debug("skipping update since in the middle of force rebuild");
×
2038
        return;
×
2039
    }
2040

2041
    auto& vis_bm = this->tss_view->get_bookmarks();
232✔
2042
    uint32_t filtered_in_mask, filtered_out_mask;
2043

2044
    this->get_filters().get_enabled_mask(filtered_in_mask, filtered_out_mask);
232✔
2045

2046
    if (this->lss_index_delegate != nullptr) {
232✔
2047
        this->lss_index_delegate->index_start(*this);
232✔
2048
    }
2049
    vis_bm[&textview_curses::BM_USER_EXPR].clear();
232✔
2050

2051
    this->lss_filtered_index.clear();
232✔
2052
    this->lss_ctx_before_msgs.clear();
232✔
2053
    this->lss_ctx_after_msgs_remaining = 0;
232✔
2054
    this->lss_ctx_in_after = false;
232✔
2055

2056
    auto context_before = this->tss_context_before;
232✔
2057
    auto context_after = this->tss_context_after;
232✔
2058

2059
    for (size_t index_index = 0; index_index < this->lss_index.size();
3,094✔
2060
         index_index++)
2061
    {
2062
        auto cl = this->lss_index[index_index].value();
2,862✔
2063
        uint64_t line_number;
2064
        auto ld = this->find_data(cl, line_number);
2,862✔
2065

2066
        if (!(*ld)->is_visible()) {
2,862✔
2067
            continue;
609✔
2068
        }
2069

2070
        auto lf = (*ld)->get_file_ptr();
2,253✔
2071
        auto line_iter = lf->begin() + line_number;
2,253✔
2072

2073
        if (!this->tss_apply_filters
4,506✔
2074
            || (!(*ld)->ld_filter_state.excluded(
4,326✔
2075
                    filtered_in_mask, filtered_out_mask, line_number)
2076
                && this->check_extra_filters(ld, line_iter)))
2,073✔
2077
        {
2078
            this->flush_context_before_msgs();
1,945✔
2079
            auto eval_res = this->eval_sql_filter(
2080
                this->lss_marker_stmt.in(), ld, line_iter);
1,945✔
2081
            if (eval_res.isErr()) {
1,945✔
UNCOV
2082
                line_iter->set_expr_mark(false);
×
2083
            } else {
2084
                auto matched = eval_res.unwrap();
1,945✔
2085

2086
                line_iter->set_expr_mark(matched);
1,945✔
2087
                if (matched) {
1,945✔
UNCOV
2088
                    vis_bm[&textview_curses::BM_USER_EXPR].insert_once(
×
UNCOV
2089
                        vis_line_t(this->lss_filtered_index.size()));
×
UNCOV
2090
                    this->lss_user_marks[&textview_curses::BM_USER_EXPR]
×
UNCOV
2091
                        .insert_once(cl);
×
2092
                }
2093
            }
2094
            // Metric CSV rows from different files that share a
2095
            // timestamp get collapsed into a single visible line —
2096
            // same dedup rule as `rebuild_index`, mirrored here so
2097
            // toggling filters doesn't re-expose the suppressed
2098
            // siblings as separate visible rows.
2099
            bool suppress_metric_sibling = false;
1,945✔
2100
            if (lf->get_format_ptr()->lf_is_metric
1,945✔
2101
                && !this->lss_filtered_index.empty())
1,945✔
2102
            {
2103
                const auto prev_cl
2104
                    = this->lss_index[this->lss_filtered_index.back()].value();
113✔
2105
                uint64_t prev_line_number;
2106
                const auto prev_ld = this->find_data(prev_cl, prev_line_number);
113✔
2107
                const auto* prev_lf = (*prev_ld)->get_file_ptr();
113✔
2108
                if (prev_lf->get_format_ptr()->lf_is_metric) {
113✔
2109
                    const auto prev_time
2110
                        = (prev_lf->begin() + prev_line_number)->get_time<>();
75✔
2111
                    const auto curr_time = line_iter->get_time<>();
75✔
2112
                    if (prev_time == curr_time) {
75✔
2113
                        suppress_metric_sibling = true;
17✔
2114
                    }
2115
                }
2116
            }
2117
            if (!suppress_metric_sibling) {
1,945✔
2118
                this->lss_filtered_index.push_back(index_index);
1,928✔
2119
            }
2120
            if (this->lss_index_delegate != nullptr) {
1,945✔
2121
                this->lss_index_delegate->index_line(*this, lf, line_iter);
1,945✔
2122
            }
2123
            if (!line_iter->is_continued()) {
1,945✔
2124
                this->lss_ctx_after_msgs_remaining = context_after;
1,937✔
2125
            }
2126
            this->lss_ctx_in_after = false;
1,945✔
2127
        } else if (this->lss_ctx_in_after && line_iter->is_continued()) {
2,253✔
2128
            this->lss_filtered_index.push_back(index_index);
38✔
2129
        } else if (this->lss_ctx_after_msgs_remaining > 0
540✔
2130
                   && !line_iter->is_continued())
270✔
2131
        {
2132
            this->lss_ctx_after_msgs_remaining -= 1;
3✔
2133
            this->lss_ctx_in_after = true;
3✔
2134
            this->lss_filtered_index.push_back(index_index);
3✔
2135
        } else {
2136
            this->lss_ctx_in_after = false;
267✔
2137
            if (context_before > 0) {
267✔
2138
                if (!line_iter->is_continued()
8✔
2139
                    || this->lss_ctx_before_msgs.empty())
8✔
2140
                {
2141
                    if (this->lss_ctx_before_msgs.size() >= context_before) {
8✔
2142
                        this->lss_ctx_before_msgs.pop_front();
4✔
2143
                    }
2144
                    this->lss_ctx_before_msgs.push_back({index_index, 1});
8✔
2145
                } else {
UNCOV
2146
                    this->lss_ctx_before_msgs.back().cmr_count += 1;
×
2147
                }
2148
            }
2149
        }
2150
    }
2151

2152
    if (this->lss_index_delegate != nullptr) {
232✔
2153
        this->lss_index_delegate->index_complete(*this);
232✔
2154
    }
2155

2156
    if (this->tss_view != nullptr) {
232✔
2157
        log_debug("reloading view");
232✔
2158
        this->tss_view->reload_data();
232✔
2159
        this->tss_view->redo_search();
232✔
2160
    }
2161
    log_debug("finished filter update");
232✔
2162
}
232✔
2163

2164
std::optional<json_string>
2165
logfile_sub_source::text_row_details(const textview_curses& tc)
35✔
2166
{
2167
    if (this->lss_index.empty()) {
35✔
2168
        log_trace("logfile_sub_source::text_row_details empty");
1✔
2169
        return std::nullopt;
1✔
2170
    }
2171

2172
    auto ov_sel = tc.get_overlay_selection();
34✔
2173
    if (ov_sel.has_value()) {
34✔
2174
        auto* fos
UNCOV
2175
            = dynamic_cast<field_overlay_source*>(tc.get_overlay_source());
×
UNCOV
2176
        auto iter = fos->fos_row_to_field_meta.find(ov_sel.value());
×
UNCOV
2177
        if (iter != fos->fos_row_to_field_meta.end()) {
×
UNCOV
2178
            auto find_res = this->find_line_with_file(tc.get_top());
×
UNCOV
2179
            if (find_res) {
×
UNCOV
2180
                yajlpp_gen gen;
×
2181

2182
                {
UNCOV
2183
                    yajlpp_map root(gen);
×
2184

UNCOV
2185
                    root.gen("value");
×
UNCOV
2186
                    root.gen(iter->second.ri_value);
×
2187
                }
2188

UNCOV
2189
                return json_string(gen);
×
2190
            }
2191
        }
2192
    }
2193

2194
    return std::nullopt;
34✔
2195
}
2196

2197
bool
UNCOV
2198
logfile_sub_source::list_input_handle_key(listview_curses& lv,
×
2199
                                          const ncinput& ch)
2200
{
UNCOV
2201
    switch (ch.eff_text[0]) {
×
UNCOV
2202
        case ' ': {
×
UNCOV
2203
            auto ov_vl = lv.get_overlay_selection();
×
UNCOV
2204
            if (ov_vl) {
×
UNCOV
2205
                auto* fos = dynamic_cast<field_overlay_source*>(
×
UNCOV
2206
                    lv.get_overlay_source());
×
UNCOV
2207
                auto iter = fos->fos_row_to_field_meta.find(ov_vl.value());
×
UNCOV
2208
                if (iter != fos->fos_row_to_field_meta.end()
×
UNCOV
2209
                    && iter->second.ri_meta)
×
2210
                {
2211
                    // Route the toggle through the meta's owning
2212
                    // format rather than the lead file's format so
2213
                    // metric-sibling columns (which live in a
2214
                    // different file's format instance) get handled.
UNCOV
2215
                    const auto& meta = iter->second.ri_meta.value();
×
UNCOV
2216
                    auto* format = meta.lvm_format.value_or(nullptr);
×
UNCOV
2217
                    if (format == nullptr) {
×
UNCOV
2218
                        auto find_res = this->find_line_with_file(lv.get_top());
×
UNCOV
2219
                        if (find_res) {
×
UNCOV
2220
                            format = find_res.value().first->get_format_ptr();
×
2221
                        }
2222
                    }
UNCOV
2223
                    if (format != nullptr) {
×
2224
                        auto fstates = format->get_field_states();
×
UNCOV
2225
                        auto state_iter = fstates.find(meta.lvm_name);
×
2226
                        const bool currently_hidden
2227
                            = state_iter != fstates.end()
×
2228
                            ? state_iter->second.is_hidden()
×
UNCOV
2229
                            : meta.is_hidden();
×
UNCOV
2230
                        format->hide_field(meta.lvm_name, !currently_hidden);
×
UNCOV
2231
                        lv.set_needs_update();
×
2232
                    }
2233
                }
UNCOV
2234
                return true;
×
2235
            }
UNCOV
2236
            return false;
×
2237
        }
UNCOV
2238
        case '#': {
×
UNCOV
2239
            auto ov_vl = lv.get_overlay_selection();
×
UNCOV
2240
            if (ov_vl) {
×
UNCOV
2241
                auto* fos = dynamic_cast<field_overlay_source*>(
×
UNCOV
2242
                    lv.get_overlay_source());
×
UNCOV
2243
                auto iter = fos->fos_row_to_field_meta.find(ov_vl.value());
×
UNCOV
2244
                if (iter != fos->fos_row_to_field_meta.end()
×
UNCOV
2245
                    && iter->second.ri_meta)
×
2246
                {
UNCOV
2247
                    const auto& meta = iter->second.ri_meta.value();
×
UNCOV
2248
                    std::string cmd;
×
2249

UNCOV
2250
                    switch (meta.to_chart_type()) {
×
UNCOV
2251
                        case chart_type_t::none:
×
UNCOV
2252
                            break;
×
UNCOV
2253
                        case chart_type_t::hist: {
×
2254
                            auto prql = fmt::format(
UNCOV
2255
                                FMT_STRING(
×
2256
                                    "from {} | stats.hist {} slice:'1h'"),
UNCOV
2257
                                meta.lvm_format.value()->get_name(),
×
UNCOV
2258
                                meta.lvm_name);
×
UNCOV
2259
                            cmd = fmt::format(FMT_STRING(":prompt sql ; '{}'"),
×
UNCOV
2260
                                              shlex::escape(prql));
×
UNCOV
2261
                            break;
×
2262
                        }
UNCOV
2263
                        case chart_type_t::spectro:
×
UNCOV
2264
                            cmd = fmt::format(FMT_STRING(":spectrogram {}"),
×
UNCOV
2265
                                              meta.lvm_name);
×
UNCOV
2266
                            break;
×
2267
                    }
UNCOV
2268
                    if (!cmd.empty()) {
×
UNCOV
2269
                        this->lss_exec_context
×
UNCOV
2270
                            ->with_provenance(exec_context::mouse_input{})
×
UNCOV
2271
                            ->execute(INTERNAL_SRC_LOC, cmd);
×
2272
                    }
2273
                }
UNCOV
2274
                return true;
×
2275
            }
UNCOV
2276
            return false;
×
2277
        }
UNCOV
2278
        case 'h':
×
2279
        case 'H':
2280
        case NCKEY_LEFT:
2281
            if (lv.get_left() == 0) {
×
UNCOV
2282
                this->increase_line_context();
×
2283
                lv.set_needs_update();
×
UNCOV
2284
                return true;
×
2285
            }
UNCOV
2286
            break;
×
2287
        case 'l':
×
2288
        case 'L':
2289
        case NCKEY_RIGHT:
UNCOV
2290
            if (this->decrease_line_context()) {
×
UNCOV
2291
                lv.set_needs_update();
×
UNCOV
2292
                return true;
×
2293
            }
UNCOV
2294
            break;
×
2295
    }
NEW
2296
    return text_sub_source::list_input_handle_key(lv, ch);
×
2297
}
2298

2299
std::optional<
2300
    std::pair<grep_proc_source<vis_line_t>*, grep_proc_sink<vis_line_t>*>>
2301
logfile_sub_source::get_grepper()
10✔
2302
{
2303
    return std::make_pair(
20✔
2304
        (grep_proc_source<vis_line_t>*) &this->lss_meta_grepper,
×
2305
        (grep_proc_sink<vis_line_t>*) &this->lss_meta_grepper);
10✔
2306
}
2307

2308
/**
2309
 * Functor for comparing the ld_file field of the logfile_data struct.
2310
 */
2311
struct logfile_data_eq {
2312
    explicit logfile_data_eq(std::shared_ptr<logfile> lf)
1,356✔
2313
        : lde_file(std::move(lf))
1,356✔
2314
    {
2315
    }
1,356✔
2316

2317
    bool operator()(
854✔
2318
        const std::unique_ptr<logfile_sub_source::logfile_data>& ld) const
2319
    {
2320
        return this->lde_file == ld->get_file();
854✔
2321
    }
2322

2323
    std::shared_ptr<logfile> lde_file;
2324
};
2325

2326
bool
2327
logfile_sub_source::insert_file(const std::shared_ptr<logfile>& lf)
678✔
2328
{
2329
    iterator existing;
678✔
2330

2331
    require_lt(lf->size(), MAX_LINES_PER_FILE);
678✔
2332

2333
    existing = std::find_if(this->lss_files.begin(),
678✔
2334
                            this->lss_files.end(),
2335
                            logfile_data_eq(nullptr));
1,356✔
2336
    if (existing == this->lss_files.end()) {
678✔
2337
        if (this->lss_files.size() >= MAX_FILES) {
678✔
UNCOV
2338
            return false;
×
2339
        }
2340

2341
        auto ld = std::make_unique<logfile_data>(
2342
            this->lss_files.size(), this->get_filters(), lf);
678✔
2343
        ld->set_visibility(lf->get_open_options().loo_is_visible);
678✔
2344
        this->lss_files.push_back(std::move(ld));
678✔
2345
    } else {
678✔
UNCOV
2346
        (*existing)->set_file(lf);
×
2347
    }
2348

2349
    return true;
678✔
2350
}
2351

2352
Result<void, lnav::console::user_message>
2353
logfile_sub_source::set_sql_filter(std::string stmt_str, sqlite3_stmt* stmt)
946✔
2354
{
2355
    if (stmt != nullptr && !this->lss_filtered_index.empty()) {
946✔
2356
        auto top_cl = this->at(0_vl);
10✔
2357
        auto ld = this->find_data(top_cl);
10✔
2358
        auto eval_res
2359
            = this->eval_sql_filter(stmt, ld, (*ld)->get_file_ptr()->begin());
10✔
2360

2361
        if (eval_res.isErr()) {
10✔
2362
            sqlite3_finalize(stmt);
1✔
2363
            return Err(eval_res.unwrapErr());
1✔
2364
        }
2365
    }
10✔
2366

2367
    for (auto& ld : *this) {
971✔
2368
        ld->ld_filter_state.lfo_filter_state.clear_filter_state(0);
26✔
2369
    }
2370

2371
    auto old_filter_iter = this->tss_filters.find(0);
945✔
2372
    if (stmt != nullptr) {
945✔
2373
        auto new_filter
2374
            = std::make_shared<sql_filter>(*this, std::move(stmt_str), stmt);
9✔
2375

2376
        if (old_filter_iter != this->tss_filters.end()) {
9✔
UNCOV
2377
            *old_filter_iter = new_filter;
×
2378
        } else {
2379
            this->tss_filters.add_filter(new_filter);
9✔
2380
        }
2381
    } else if (old_filter_iter != this->tss_filters.end()) {
945✔
2382
        this->tss_filters.delete_filter((*old_filter_iter)->get_id());
9✔
2383
    }
2384

2385
    return Ok();
945✔
2386
}
2387

2388
Result<void, lnav::console::user_message>
2389
logfile_sub_source::set_sql_marker(std::string stmt_str, sqlite3_stmt* stmt)
941✔
2390
{
2391
    static auto op = lnav_operation{"set_sql_marker"};
941✔
2392
    if (stmt != nullptr && !this->lss_filtered_index.empty()) {
941✔
2393
        auto top_cl = this->at(0_vl);
5✔
2394
        auto ld = this->find_data(top_cl);
5✔
2395
        auto eval_res
2396
            = this->eval_sql_filter(stmt, ld, (*ld)->get_file_ptr()->begin());
5✔
2397

2398
        if (eval_res.isErr()) {
5✔
UNCOV
2399
            sqlite3_finalize(stmt);
×
UNCOV
2400
            return Err(eval_res.unwrapErr());
×
2401
        }
2402
    }
5✔
2403

2404
    auto op_guard = lnav_opid_guard::internal(op);
941✔
2405
    log_info("setting SQL marker: %s", stmt_str.c_str());
941✔
2406
    this->lss_marker_stmt_text = std::move(stmt_str);
941✔
2407
    this->lss_marker_stmt = stmt;
941✔
2408

2409
    if (this->tss_view == nullptr || this->lss_force_rebuild) {
941✔
2410
        log_info("skipping SQL marker update");
139✔
2411
        return Ok();
139✔
2412
    }
2413

2414
    auto& vis_bm = this->tss_view->get_bookmarks();
802✔
2415
    auto& expr_marks_bv = vis_bm[&textview_curses::BM_USER_EXPR];
802✔
2416
    auto& cl_marks_bv = this->lss_user_marks[&textview_curses::BM_USER_EXPR];
802✔
2417

2418
    expr_marks_bv.clear();
802✔
2419
    if (this->lss_index_delegate) {
802✔
2420
        this->lss_index_delegate->index_start(*this);
802✔
2421
    }
2422
    for (auto row = 0_vl; row < vis_line_t(this->lss_filtered_index.size());
1,235✔
2423
         row += 1_vl)
433✔
2424
    {
2425
        auto cl = this->at(row);
433✔
2426
        uint64_t line_number;
2427
        auto ld = this->find_data(cl, line_number);
433✔
2428

2429
        if (!(*ld)->is_visible()) {
433✔
2430
            continue;
1✔
2431
        }
2432
        auto ll = (*ld)->get_file()->begin() + line_number;
433✔
2433
        if (ll->is_continued() || ll->is_ignored()) {
433✔
2434
            continue;
1✔
2435
        }
2436
        auto eval_res
2437
            = this->eval_sql_filter(this->lss_marker_stmt.in(), ld, ll);
432✔
2438

2439
        if (eval_res.isErr()) {
432✔
2440
            ll->set_expr_mark(false);
×
2441
        } else {
2442
            auto matched = eval_res.unwrap();
432✔
2443

2444
            ll->set_expr_mark(matched);
432✔
2445
            if (matched) {
432✔
2446
                expr_marks_bv.insert_once(row);
22✔
2447
                cl_marks_bv.insert_once(cl);
22✔
2448
            }
2449
        }
2450
        if (this->lss_index_delegate) {
432✔
2451
            this->lss_index_delegate->index_line(
432✔
2452
                *this, (*ld)->get_file_ptr(), ll);
432✔
2453
        }
2454
    }
432✔
2455
    if (this->lss_index_delegate) {
802✔
2456
        this->lss_index_delegate->index_complete(*this);
802✔
2457
    }
2458
    log_info("SQL marker update complete");
802✔
2459

2460
    return Ok();
802✔
2461
}
941✔
2462

2463
Result<void, lnav::console::user_message>
2464
logfile_sub_source::set_preview_sql_filter(sqlite3_stmt* stmt)
933✔
2465
{
2466
    if (stmt != nullptr && !this->lss_filtered_index.empty()) {
933✔
2467
        auto top_cl = this->at(0_vl);
×
2468
        auto ld = this->find_data(top_cl);
×
2469
        auto eval_res
2470
            = this->eval_sql_filter(stmt, ld, (*ld)->get_file_ptr()->begin());
×
2471

2472
        if (eval_res.isErr()) {
×
UNCOV
2473
            sqlite3_finalize(stmt);
×
2474
            return Err(eval_res.unwrapErr());
×
2475
        }
2476
    }
2477

2478
    this->lss_preview_filter_stmt = stmt;
933✔
2479

2480
    return Ok();
933✔
2481
}
2482

2483
Result<bool, lnav::console::user_message>
2484
logfile_sub_source::eval_sql_filter(sqlite3_stmt* stmt,
18,725✔
2485
                                    iterator ld,
2486
                                    logfile::const_iterator ll)
2487
{
2488
    if (stmt == nullptr) {
18,725✔
2489
        return Ok(false);
36,210✔
2490
    }
2491

2492
    auto* lf = (*ld)->get_file_ptr();
620✔
2493
    char timestamp_buffer[64];
2494
    shared_buffer_ref raw_sbr;
620✔
2495
    logline_value_vector values;
620✔
2496
    auto& sbr = values.lvv_sbr;
620✔
2497
    lf->read_full_message(ll, sbr);
620✔
2498
    sbr.erase_ansi();
620✔
2499
    auto format = lf->get_format();
620✔
2500
    string_attrs_t sa;
620✔
2501
    auto line_number = std::distance(lf->cbegin(), ll);
620✔
2502
    format->annotate(lf, line_number, sa, values);
620✔
2503
    auto lffs = lf->get_format_file_state();
620✔
2504

2505
    sqlite3_reset(stmt);
620✔
2506
    sqlite3_clear_bindings(stmt);
620✔
2507

2508
    auto count = sqlite3_bind_parameter_count(stmt);
620✔
2509
    for (int lpc = 0; lpc < count; lpc++) {
1,252✔
2510
        const auto* name = sqlite3_bind_parameter_name(stmt, lpc + 1);
632✔
2511

2512
        if (name[0] == '$') {
632✔
2513
            const char* env_value;
2514

2515
            if ((env_value = getenv(&name[1])) != nullptr) {
4✔
2516
                sqlite3_bind_text(stmt, lpc + 1, env_value, -1, SQLITE_STATIC);
1✔
2517
            }
2518
            continue;
4✔
2519
        }
4✔
2520
        if (strcmp(name, ":log_level") == 0) {
628✔
2521
            auto lvl = ll->get_level_name();
6✔
2522
            sqlite3_bind_text(
6✔
2523
                stmt, lpc + 1, lvl.data(), lvl.length(), SQLITE_STATIC);
2524
            continue;
6✔
2525
        }
6✔
2526
        if (strcmp(name, ":log_time") == 0) {
622✔
UNCOV
2527
            auto len = sql_strftime(timestamp_buffer,
×
2528
                                    sizeof(timestamp_buffer),
UNCOV
2529
                                    ll->get_timeval(),
×
2530
                                    'T');
2531
            sqlite3_bind_text(
×
2532
                stmt, lpc + 1, timestamp_buffer, len, SQLITE_STATIC);
2533
            continue;
×
2534
        }
2535
        if (strcmp(name, ":log_time_msecs") == 0) {
622✔
2536
            sqlite3_bind_int64(
1✔
2537
                stmt,
2538
                lpc + 1,
2539
                ll->get_time<std::chrono::milliseconds>().count());
1✔
2540
            continue;
1✔
2541
        }
2542
        if (strcmp(name, ":log_mark") == 0) {
621✔
UNCOV
2543
            sqlite3_bind_int(stmt, lpc + 1, ll->is_marked());
×
UNCOV
2544
            continue;
×
2545
        }
2546
        if (strcmp(name, ":log_comment") == 0) {
621✔
UNCOV
2547
            const auto& bm = lf->get_bookmark_metadata();
×
2548
            auto line_number
UNCOV
2549
                = static_cast<uint32_t>(std::distance(lf->cbegin(), ll));
×
UNCOV
2550
            auto bm_iter = bm.find(line_number);
×
UNCOV
2551
            if (bm_iter != bm.end() && !bm_iter->second.bm_comment.empty()) {
×
UNCOV
2552
                const auto& meta = bm_iter->second;
×
UNCOV
2553
                sqlite3_bind_text(stmt,
×
2554
                                  lpc + 1,
2555
                                  meta.bm_comment.c_str(),
UNCOV
2556
                                  meta.bm_comment.length(),
×
2557
                                  SQLITE_STATIC);
2558
            }
UNCOV
2559
            continue;
×
2560
        }
2561
        if (strcmp(name, ":log_annotations") == 0) {
621✔
UNCOV
2562
            const auto& bm = lf->get_bookmark_metadata();
×
2563
            auto line_number
UNCOV
2564
                = static_cast<uint32_t>(std::distance(lf->cbegin(), ll));
×
UNCOV
2565
            auto bm_iter = bm.find(line_number);
×
UNCOV
2566
            if (bm_iter != bm.end()
×
UNCOV
2567
                && !bm_iter->second.bm_annotations.la_pairs.empty())
×
2568
            {
UNCOV
2569
                const auto& meta = bm_iter->second;
×
2570
                auto anno_str = logmsg_annotations_handlers.to_string(
UNCOV
2571
                    meta.bm_annotations);
×
2572

UNCOV
2573
                sqlite3_bind_text(stmt,
×
2574
                                  lpc + 1,
2575
                                  anno_str.c_str(),
UNCOV
2576
                                  anno_str.length(),
×
2577
                                  SQLITE_TRANSIENT);
2578
            }
2579
            continue;
×
2580
        }
2581
        if (strcmp(name, ":log_tags") == 0) {
621✔
2582
            const auto& bm = lf->get_bookmark_metadata();
20✔
2583
            auto line_number
2584
                = static_cast<uint32_t>(std::distance(lf->cbegin(), ll));
20✔
2585
            auto bm_iter = bm.find(line_number);
20✔
2586
            if (bm_iter != bm.end() && !bm_iter->second.bm_tags.empty()) {
20✔
2587
                const auto& meta = bm_iter->second;
4✔
2588
                yajlpp_gen gen;
4✔
2589

2590
                yajl_gen_config(gen, yajl_gen_beautify, false);
4✔
2591

2592
                {
2593
                    yajlpp_array arr(gen);
4✔
2594

2595
                    for (const auto& entry : meta.bm_tags) {
8✔
2596
                        arr.gen(entry.te_tag);
4✔
2597
                    }
2598
                }
4✔
2599

2600
                string_fragment sf = gen.to_string_fragment();
4✔
2601

2602
                sqlite3_bind_text(
4✔
2603
                    stmt, lpc + 1, sf.data(), sf.length(), SQLITE_TRANSIENT);
2604
            }
4✔
2605
            continue;
20✔
2606
        }
20✔
2607
        if (strcmp(name, ":log_format") == 0) {
601✔
2608
            const auto format_name = format->get_name();
6✔
2609
            sqlite3_bind_text(stmt,
6✔
2610
                              lpc + 1,
2611
                              format_name.get(),
2612
                              format_name.size(),
6✔
2613
                              SQLITE_STATIC);
2614
            continue;
6✔
2615
        }
6✔
2616
        if (strcmp(name, ":log_format_regex") == 0) {
595✔
UNCOV
2617
            const auto pat_name = format->get_pattern_name(
×
2618
                lffs.lffs_pattern_locks, line_number);
UNCOV
2619
            sqlite3_bind_text(
×
UNCOV
2620
                stmt, lpc + 1, pat_name.get(), pat_name.size(), SQLITE_STATIC);
×
UNCOV
2621
            continue;
×
2622
        }
2623
        if (strcmp(name, ":log_path") == 0) {
595✔
UNCOV
2624
            const auto& filename = lf->get_filename();
×
UNCOV
2625
            sqlite3_bind_text(stmt,
×
2626
                              lpc + 1,
2627
                              filename.c_str(),
UNCOV
2628
                              filename.native().length(),
×
2629
                              SQLITE_STATIC);
UNCOV
2630
            continue;
×
2631
        }
2632
        if (strcmp(name, ":log_unique_path") == 0) {
595✔
UNCOV
2633
            const auto& filename = lf->get_unique_path();
×
UNCOV
2634
            sqlite3_bind_text(stmt,
×
2635
                              lpc + 1,
2636
                              filename.c_str(),
UNCOV
2637
                              filename.native().length(),
×
2638
                              SQLITE_STATIC);
UNCOV
2639
            continue;
×
2640
        }
2641
        if (strcmp(name, ":log_text") == 0) {
595✔
2642
            sqlite3_bind_text(
4✔
2643
                stmt, lpc + 1, sbr.get_data(), sbr.length(), SQLITE_STATIC);
4✔
2644
            continue;
4✔
2645
        }
2646
        if (strcmp(name, ":log_body") == 0) {
591✔
2647
            auto body_attr_opt = get_string_attr(sa, SA_BODY);
10✔
2648
            if (body_attr_opt) {
10✔
2649
                const auto& sar
2650
                    = body_attr_opt.value().saw_string_attr->sa_range;
10✔
2651

2652
                sqlite3_bind_text(stmt,
20✔
2653
                                  lpc + 1,
2654
                                  sbr.get_data_at(sar.lr_start),
10✔
2655
                                  sar.length(),
2656
                                  SQLITE_STATIC);
2657
            } else {
UNCOV
2658
                sqlite3_bind_null(stmt, lpc + 1);
×
2659
            }
2660
            continue;
10✔
2661
        }
10✔
2662
        if (strcmp(name, ":log_raw_text") == 0) {
581✔
UNCOV
2663
            auto res = lf->read_raw_message(ll);
×
2664

UNCOV
2665
            if (res.isOk()) {
×
UNCOV
2666
                raw_sbr = res.unwrap();
×
UNCOV
2667
                sqlite3_bind_text(stmt,
×
2668
                                  lpc + 1,
2669
                                  raw_sbr.get_data(),
UNCOV
2670
                                  raw_sbr.length(),
×
2671
                                  SQLITE_STATIC);
2672
            }
UNCOV
2673
            continue;
×
2674
        }
2675
        if (strcmp(name, ":log_opid") == 0) {
581✔
UNCOV
2676
            bind_to_sqlite(stmt, lpc + 1, values.lvv_opid_value);
×
UNCOV
2677
            continue;
×
2678
        }
2679
        if (strcmp(name, ":log_opid_definition") == 0) {
581✔
UNCOV
2680
            if (values.lvv_opid_value) {
×
UNCOV
2681
                auto opids = lf->get_opids().readAccess();
×
2682

2683
                auto iter = opids->los_opid_ranges.find(
×
UNCOV
2684
                    values.lvv_opid_value.value());
×
2685
                if (iter != opids->los_opid_ranges.end()
×
2686
                    && iter->second.otr_description.lod_index)
×
2687
                {
2688
                    const auto& opid_def
2689
                        = (*format->lf_opid_description_def_vec)
×
UNCOV
2690
                            [iter->second.otr_description.lod_index.value()];
×
UNCOV
2691
                    bind_to_sqlite(stmt, lpc + 1, opid_def->od_name);
×
2692
                } else {
2693
                    sqlite3_bind_null(stmt, lpc + 1);
×
2694
                }
UNCOV
2695
            } else {
×
UNCOV
2696
                sqlite3_bind_null(stmt, lpc + 1);
×
2697
            }
UNCOV
2698
            continue;
×
2699
        }
2700
        if (strcmp(name, ":log_src_file") == 0) {
581✔
UNCOV
2701
            bind_to_sqlite(stmt, lpc + 1, values.lvv_src_file_value);
×
UNCOV
2702
            continue;
×
2703
        }
2704
        if (strcmp(name, ":log_src_line") == 0) {
581✔
UNCOV
2705
            bind_to_sqlite(stmt, lpc + 1, values.lvv_src_line_value);
×
UNCOV
2706
            continue;
×
2707
        }
2708
        if (strcmp(name, ":log_thread_id") == 0) {
581✔
UNCOV
2709
            bind_to_sqlite(stmt, lpc + 1, values.lvv_thread_id_value);
×
UNCOV
2710
            continue;
×
2711
        }
2712
        if (strcmp(name, ":log_duration") == 0) {
581✔
UNCOV
2713
            if (values.lvv_duration_value) {
×
UNCOV
2714
                bind_to_sqlite(stmt,
×
2715
                               lpc + 1,
UNCOV
2716
                               values.lvv_duration_value->count() / 1000000.0);
×
2717
            } else {
UNCOV
2718
                sqlite3_bind_null(stmt, lpc + 1);
×
2719
            }
UNCOV
2720
            continue;
×
2721
        }
2722
        for (const auto& lv : values.lvv_values) {
6,782✔
2723
            if (lv.lv_meta.lvm_name != &name[1]) {
6,777✔
2724
                continue;
6,201✔
2725
            }
2726

2727
            switch (lv.lv_meta.lvm_kind) {
576✔
UNCOV
2728
                case value_kind_t::VALUE_BOOLEAN:
×
UNCOV
2729
                    sqlite3_bind_int64(stmt, lpc + 1, lv.lv_value.i);
×
UNCOV
2730
                    break;
×
UNCOV
2731
                case value_kind_t::VALUE_FLOAT:
×
UNCOV
2732
                    sqlite3_bind_double(stmt, lpc + 1, lv.lv_value.d);
×
UNCOV
2733
                    break;
×
2734
                case value_kind_t::VALUE_INTEGER:
436✔
2735
                    sqlite3_bind_int64(stmt, lpc + 1, lv.lv_value.i);
436✔
2736
                    break;
436✔
UNCOV
2737
                case value_kind_t::VALUE_NULL:
×
UNCOV
2738
                    sqlite3_bind_null(stmt, lpc + 1);
×
UNCOV
2739
                    break;
×
2740
                default:
140✔
2741
                    sqlite3_bind_text(stmt,
140✔
2742
                                      lpc + 1,
2743
                                      lv.text_value(),
2744
                                      lv.text_length(),
140✔
2745
                                      SQLITE_TRANSIENT);
2746
                    break;
140✔
2747
            }
2748
            break;
576✔
2749
        }
2750
    }
2751

2752
    auto step_res = sqlite3_step(stmt);
620✔
2753

2754
    sqlite3_reset(stmt);
620✔
2755
    sqlite3_clear_bindings(stmt);
620✔
2756
    switch (step_res) {
620✔
2757
        case SQLITE_OK:
471✔
2758
        case SQLITE_DONE:
2759
            return Ok(false);
942✔
2760
        case SQLITE_ROW:
148✔
2761
            return Ok(true);
296✔
2762
        default:
1✔
2763
            return Err(sqlite3_error_to_user_message(sqlite3_db_handle(stmt)));
1✔
2764
    }
2765
}
620✔
2766

2767
bool
2768
logfile_sub_source::check_extra_filters(iterator ld, logfile::iterator ll)
18,371✔
2769
{
2770
    auto retval = true;
18,371✔
2771

2772
    if (this->lss_marked_only) {
18,371✔
2773
        auto found_mark = ll->is_marked() || ll->is_expr_marked();
6✔
2774
        auto to_start_ll = ll;
6✔
2775
        while (!found_mark && to_start_ll->is_continued()) {
6✔
UNCOV
2776
            if (to_start_ll->is_marked() || to_start_ll->is_expr_marked()) {
×
UNCOV
2777
                found_mark = true;
×
2778
            }
UNCOV
2779
            --to_start_ll;
×
2780
        }
2781
        auto to_end_ll = std::next(ll);
6✔
2782
        while (!found_mark && to_end_ll != (*ld)->get_file_ptr()->end()
10✔
2783
               && to_end_ll->is_continued())
11✔
2784
        {
2785
            if (to_end_ll->is_marked() || to_end_ll->is_expr_marked()) {
1✔
2786
                found_mark = true;
1✔
2787
            }
2788
            ++to_end_ll;
1✔
2789
        }
2790
        if (!found_mark) {
6✔
2791
            retval = false;
3✔
2792
        }
2793
    }
2794

2795
    if (ll->get_msg_level() < this->tss_min_log_level) {
18,371✔
2796
        this->tss_level_filtered_count += 1;
129✔
2797
        retval = false;
129✔
2798
    }
2799

2800
    if (*ll < this->ttt_min_row_time) {
18,371✔
2801
        retval = false;
36✔
2802
    }
2803

2804
    if (!(*ll <= this->ttt_max_row_time)) {
18,371✔
2805
        retval = false;
4✔
2806
    }
2807

2808
    return retval;
18,371✔
2809
}
2810

2811
void
2812
logfile_sub_source::invalidate_sql_filter()
64✔
2813
{
2814
    for (auto& ld : *this) {
139✔
2815
        ld->ld_filter_state.lfo_filter_state.clear_filter_state(0);
75✔
2816
    }
2817
}
64✔
2818

2819
void
2820
logfile_sub_source::text_mark(const bookmark_type_t* bm,
100✔
2821
                              vis_line_t line,
2822
                              bool added)
2823
{
2824
    if (line >= (int) this->lss_index.size()) {
100✔
UNCOV
2825
        return;
×
2826
    }
2827

2828
    auto cl = this->at(line);
100✔
2829

2830
    this->lss_user_marks[bm].apply(cl, added);
100✔
2831
    const auto is_user = (bm == &textview_curses::BM_USER);
100✔
2832
    const auto is_sticky = (bm == &textview_curses::BM_STICKY);
100✔
2833
    if (is_user || is_sticky) {
100✔
2834
        // For metric rows the mark has to fan out to every row in the
2835
        // fan-out group (lead + suppressed siblings) so the state
2836
        // reflects the whole composed line.  `metric_siblings()`
2837
        // yields the lead first; the outer `apply()` above and the
2838
        // loop's apply for the lead are idempotent.
2839
        logline_window::logmsg_info lead_msg{*this, line};
41✔
2840
        if (lead_msg.is_metric_line()) {
41✔
2841
            for (const auto& sib : lead_msg.metric_siblings()) {
15✔
2842
                if (is_user) {
6✔
2843
                    sib.get_logline().set_mark(added);
4✔
2844
                }
2845
                this->lss_user_marks[bm].apply(sib.get_content_line(), added);
6✔
2846
            }
3✔
2847
        } else if (is_user) {
38✔
2848
            auto find_res = this->find_line_with_file(line);
33✔
2849
            if (find_res) {
33✔
2850
                auto& [lf, ll] = find_res.value();
33✔
2851
                ll->set_mark(added);
33✔
2852
            }
2853
        }
33✔
2854
    }
41✔
2855

2856
    if (bm == &textview_curses::BM_META
100✔
2857
        && this->lss_meta_grepper.gps_proc != nullptr)
20✔
2858
    {
2859
        this->tss_view->search_range(
1✔
2860
            line, grep_proc<vis_line_t>::until_line(line + 1_vl));
1✔
2861
        this->tss_view->search_new_data();
1✔
2862
    }
2863
}
2864

2865
void
2866
logfile_sub_source::text_clear_marks(const bookmark_type_t* bm)
54✔
2867
{
2868
    if (bm == &textview_curses::BM_USER) {
54✔
2869
        for (auto iter = this->lss_user_marks[bm].bv_tree.begin();
11✔
2870
             iter != this->lss_user_marks[bm].bv_tree.end();
11✔
UNCOV
2871
             ++iter)
×
2872
        {
2873
            this->find_line(*iter)->set_mark(false);
×
2874
        }
2875
    }
2876
    this->lss_user_marks[bm].clear();
54✔
2877
}
54✔
2878

2879
void
2880
logfile_sub_source::remove_file(std::shared_ptr<logfile> lf)
678✔
2881
{
2882
    auto iter = std::find_if(
678✔
2883
        this->lss_files.begin(), this->lss_files.end(), logfile_data_eq(lf));
1,356✔
2884
    if (iter != this->lss_files.end()) {
678✔
2885
        int file_index = iter - this->lss_files.begin();
678✔
2886

2887
        (*iter)->clear();
678✔
2888
        for (auto& bv : this->lss_user_marks) {
6,780✔
2889
            auto mark_curr = content_line_t(file_index * MAX_LINES_PER_FILE);
6,102✔
2890
            auto mark_end
2891
                = content_line_t((file_index + 1) * MAX_LINES_PER_FILE);
6,102✔
2892
            auto file_range = bv.equal_range(mark_curr, mark_end);
6,102✔
2893

2894
            if (file_range.first != file_range.second) {
6,102✔
2895
                auto to_del = std::vector<content_line_t>{};
94✔
2896
                for (auto file_iter = file_range.first;
94✔
2897
                     file_iter != file_range.second;
257✔
2898
                     ++file_iter)
163✔
2899
                {
2900
                    to_del.emplace_back(*file_iter);
163✔
2901
                }
2902

2903
                for (auto cl : to_del) {
257✔
2904
                    bv.erase(cl);
163✔
2905
                }
2906
            }
94✔
2907
        }
2908

2909
        this->lss_force_rebuild = true;
678✔
2910
    }
2911
    while (!this->lss_files.empty()) {
1,356✔
2912
        if (this->lss_files.back()->get_file_ptr() == nullptr) {
757✔
2913
            this->lss_files.pop_back();
678✔
2914
        } else {
2915
            break;
79✔
2916
        }
2917
    }
2918
    this->lss_token_file = nullptr;
678✔
2919
}
678✔
2920

2921
std::optional<vis_line_t>
2922
logfile_sub_source::find_from_content(content_line_t cl)
5✔
2923
{
2924
    content_line_t line = cl;
5✔
2925
    std::shared_ptr<logfile> lf = this->find(line);
5✔
2926

2927
    if (lf != nullptr) {
5✔
2928
        auto ll_iter = lf->begin() + line;
5✔
2929
        auto& ll = *ll_iter;
5✔
2930
        auto vis_start_opt = this->find_from_time(ll.get_timeval());
5✔
2931

2932
        if (!vis_start_opt) {
5✔
UNCOV
2933
            return std::nullopt;
×
2934
        }
2935

2936
        auto vis_start = *vis_start_opt;
5✔
2937

2938
        while (vis_start < vis_line_t(this->text_line_count())) {
5✔
2939
            content_line_t guess_cl = this->at(vis_start);
5✔
2940

2941
            if (cl == guess_cl) {
5✔
2942
                return vis_start;
5✔
2943
            }
2944

UNCOV
2945
            auto guess_line = this->find_line(guess_cl);
×
2946

UNCOV
2947
            if (!guess_line || ll < *guess_line) {
×
UNCOV
2948
                return std::nullopt;
×
2949
            }
2950

UNCOV
2951
            ++vis_start;
×
2952
        }
2953
    }
2954

UNCOV
2955
    return std::nullopt;
×
2956
}
5✔
2957

2958
void
2959
logfile_sub_source::reload_index_delegate()
804✔
2960
{
2961
    if (this->lss_index_delegate == nullptr) {
804✔
UNCOV
2962
        return;
×
2963
    }
2964

2965
    this->lss_index_delegate->index_start(*this);
804✔
2966
    for (const auto index : this->lss_filtered_index) {
832✔
2967
        auto cl = this->lss_index[index].value();
28✔
2968
        uint64_t line_number;
2969
        auto ld = this->find_data(cl, line_number);
28✔
2970
        std::shared_ptr<logfile> lf = (*ld)->get_file();
28✔
2971

2972
        this->lss_index_delegate->index_line(
28✔
2973
            *this, lf.get(), lf->begin() + line_number);
28✔
2974
    }
28✔
2975
    this->lss_index_delegate->index_complete(*this);
804✔
2976
}
2977

2978
std::optional<std::shared_ptr<text_filter>>
2979
logfile_sub_source::get_sql_filter()
2,917✔
2980
{
2981
    return this->tss_filters | lnav::itertools::find_if([](const auto& filt) {
2,917✔
2982
               return filt->get_index() == 0
290✔
2983
                   && dynamic_cast<sql_filter*>(filt.get()) != nullptr;
290✔
2984
           })
2985
        | lnav::itertools::deref();
5,834✔
2986
}
2987

2988
void
2989
log_location_history::loc_history_append(vis_line_t top)
122✔
2990
{
2991
    if (top < 0_vl || top >= vis_line_t(this->llh_log_source.text_line_count()))
122✔
2992
    {
2993
        return;
3✔
2994
    }
2995

2996
    auto cl = this->llh_log_source.at(top);
119✔
2997

2998
    auto iter = this->llh_history.begin();
119✔
2999
    iter += this->llh_history.size() - this->lh_history_position;
119✔
3000
    this->llh_history.erase_from(iter);
119✔
3001
    this->lh_history_position = 0;
119✔
3002
    this->llh_history.push_back(cl);
119✔
3003
}
3004

3005
std::optional<vis_line_t>
3006
log_location_history::loc_history_back(vis_line_t current_top)
2✔
3007
{
3008
    while (this->lh_history_position < this->llh_history.size()) {
2✔
3009
        auto iter = this->llh_history.rbegin();
2✔
3010

3011
        auto vis_for_pos = this->llh_log_source.find_from_content(*iter);
2✔
3012

3013
        if (this->lh_history_position == 0 && vis_for_pos != current_top) {
2✔
3014
            return vis_for_pos;
2✔
3015
        }
3016

3017
        if ((this->lh_history_position + 1) >= this->llh_history.size()) {
2✔
UNCOV
3018
            break;
×
3019
        }
3020

3021
        this->lh_history_position += 1;
2✔
3022

3023
        iter += this->lh_history_position;
2✔
3024

3025
        vis_for_pos = this->llh_log_source.find_from_content(*iter);
2✔
3026

3027
        if (vis_for_pos) {
2✔
3028
            return vis_for_pos;
2✔
3029
        }
3030
    }
3031

UNCOV
3032
    return std::nullopt;
×
3033
}
3034

3035
std::optional<vis_line_t>
3036
log_location_history::loc_history_forward(vis_line_t current_top)
1✔
3037
{
3038
    while (this->lh_history_position > 0) {
1✔
3039
        this->lh_history_position -= 1;
1✔
3040

3041
        auto iter = this->llh_history.rbegin();
1✔
3042

3043
        iter += this->lh_history_position;
1✔
3044

3045
        auto vis_for_pos = this->llh_log_source.find_from_content(*iter);
1✔
3046

3047
        if (vis_for_pos) {
1✔
3048
            return vis_for_pos;
1✔
3049
        }
3050
    }
3051

UNCOV
3052
    return std::nullopt;
×
3053
}
3054

3055
bool
3056
sql_filter::matches(std::optional<line_source> ls_opt,
175✔
3057
                    const shared_buffer_ref& line)
3058
{
3059
    if (!ls_opt) {
175✔
UNCOV
3060
        return false;
×
3061
    }
3062

3063
    auto ls = ls_opt;
175✔
3064

3065
    if (!ls->ls_line->is_message()) {
175✔
3066
        return false;
45✔
3067
    }
3068
    if (this->sf_filter_stmt == nullptr) {
130✔
UNCOV
3069
        return false;
×
3070
    }
3071

3072
    auto lfp = ls->ls_file.shared_from_this();
130✔
3073
    auto ld = this->sf_log_source.find_data_i(lfp);
130✔
3074
    if (ld == this->sf_log_source.end()) {
130✔
UNCOV
3075
        return false;
×
3076
    }
3077

3078
    auto eval_res = this->sf_log_source.eval_sql_filter(
130✔
3079
        this->sf_filter_stmt, ld, ls->ls_line);
130✔
3080
    if (eval_res.unwrapOr(true)) {
130✔
3081
        return false;
81✔
3082
    }
3083

3084
    return true;
49✔
3085
}
130✔
3086

3087
std::string
3088
sql_filter::to_command() const
1✔
3089
{
3090
    return fmt::format(FMT_STRING("filter-expr {}"), this->lf_id);
4✔
3091
}
3092

3093
std::optional<line_info>
UNCOV
3094
logfile_sub_source::meta_grepper::grep_value_for_line(vis_line_t line,
×
3095
                                                      std::string& value_out)
3096
{
UNCOV
3097
    auto line_meta_opt = this->lmg_source.find_bookmark_metadata(line);
×
UNCOV
3098
    if (!line_meta_opt) {
×
UNCOV
3099
        value_out.clear();
×
3100
    } else {
UNCOV
3101
        auto& bm = *(line_meta_opt.value());
×
3102

3103
        {
UNCOV
3104
            md2attr_line mdal;
×
3105

UNCOV
3106
            auto parse_res = md4cpp::parse(bm.bm_comment, mdal);
×
UNCOV
3107
            if (parse_res.isOk()) {
×
3108
                value_out.append(parse_res.unwrap().get_string());
×
3109
            } else {
UNCOV
3110
                value_out.append(bm.bm_comment);
×
3111
            }
3112
        }
3113

UNCOV
3114
        value_out.append("\x1c");
×
UNCOV
3115
        for (const auto& entry : bm.bm_tags) {
×
UNCOV
3116
            value_out.append(entry.te_tag);
×
UNCOV
3117
            value_out.append("\x1c");
×
3118
        }
UNCOV
3119
        value_out.append("\x1c");
×
UNCOV
3120
        for (const auto& pair : bm.bm_annotations.la_pairs) {
×
UNCOV
3121
            value_out.append(pair.first);
×
3122
            value_out.append("\x1c");
×
3123

UNCOV
3124
            md2attr_line mdal;
×
3125

3126
            auto parse_res = md4cpp::parse(pair.second, mdal);
×
3127
            if (parse_res.isOk()) {
×
UNCOV
3128
                value_out.append(parse_res.unwrap().get_string());
×
3129
            } else {
UNCOV
3130
                value_out.append(pair.second);
×
3131
            }
UNCOV
3132
            value_out.append("\x1c");
×
3133
        }
UNCOV
3134
        value_out.append("\x1c");
×
UNCOV
3135
        value_out.append(bm.bm_opid);
×
3136
    }
3137

UNCOV
3138
    if (!this->lmg_done) {
×
UNCOV
3139
        return line_info{};
×
3140
    }
3141

UNCOV
3142
    return std::nullopt;
×
3143
}
3144

3145
vis_line_t
UNCOV
3146
logfile_sub_source::meta_grepper::grep_initial_line(vis_line_t start,
×
3147
                                                    vis_line_t highest)
3148
{
UNCOV
3149
    auto& bm = this->lmg_source.tss_view->get_bookmarks();
×
UNCOV
3150
    auto& bv = bm[&textview_curses::BM_META];
×
3151

UNCOV
3152
    if (bv.empty()) {
×
3153
        return -1_vl;
×
3154
    }
UNCOV
3155
    return *bv.bv_tree.begin();
×
3156
}
3157

3158
void
UNCOV
3159
logfile_sub_source::meta_grepper::grep_next_line(vis_line_t& line)
×
3160
{
UNCOV
3161
    auto& bm = this->lmg_source.tss_view->get_bookmarks();
×
UNCOV
3162
    auto& bv = bm[&textview_curses::BM_META];
×
3163

UNCOV
3164
    auto line_opt = bv.next(vis_line_t(line));
×
UNCOV
3165
    if (!line_opt) {
×
UNCOV
3166
        this->lmg_done = true;
×
3167
    }
UNCOV
3168
    line = line_opt.value_or(-1_vl);
×
3169
}
3170

3171
void
3172
logfile_sub_source::meta_grepper::grep_begin(grep_proc<vis_line_t>& gp,
24✔
3173
                                             vis_line_t start,
3174
                                             vis_line_t stop)
3175
{
3176
    this->lmg_source.quiesce();
24✔
3177

3178
    this->lmg_source.tss_view->grep_begin(gp, start, stop);
24✔
3179
}
24✔
3180

3181
void
3182
logfile_sub_source::meta_grepper::grep_end(grep_proc<vis_line_t>& gp)
24✔
3183
{
3184
    this->lmg_source.tss_view->grep_end(gp);
24✔
3185
}
24✔
3186

3187
void
3188
logfile_sub_source::meta_grepper::grep_match(grep_proc<vis_line_t>& gp,
3✔
3189
                                             vis_line_t line)
3190
{
3191
    this->lmg_source.tss_view->grep_match(gp, line);
3✔
3192
}
3✔
3193

3194
static std::vector<breadcrumb::possibility>
3195
timestamp_poss()
14✔
3196
{
3197
    const static std::vector<breadcrumb::possibility> retval = {
3198
        breadcrumb::possibility{"-1 day"},
3199
        breadcrumb::possibility{"-1h"},
3200
        breadcrumb::possibility{"-30m"},
3201
        breadcrumb::possibility{"-15m"},
3202
        breadcrumb::possibility{"-5m"},
3203
        breadcrumb::possibility{"-1m"},
3204
        breadcrumb::possibility{"+1m"},
3205
        breadcrumb::possibility{"+5m"},
3206
        breadcrumb::possibility{"+15m"},
3207
        breadcrumb::possibility{"+30m"},
3208
        breadcrumb::possibility{"+1h"},
3209
        breadcrumb::possibility{"+1 day"},
3210
    };
126✔
3211

3212
    return retval;
14✔
3213
}
32✔
3214

3215
static attr_line_t
3216
to_display(const std::shared_ptr<logfile>& lf)
36✔
3217
{
3218
    const auto& loo = lf->get_open_options();
36✔
3219
    attr_line_t retval;
36✔
3220

3221
    if (loo.loo_piper) {
36✔
UNCOV
3222
        if (!lf->get_open_options().loo_piper->is_finished()) {
×
UNCOV
3223
            retval.append("\u21bb "_list_glyph);
×
3224
        }
3225
    } else if (loo.loo_child_poller && loo.loo_child_poller->is_alive()) {
36✔
UNCOV
3226
        retval.append("\u21bb "_list_glyph);
×
3227
    }
3228
    retval.append(lf->get_unique_path());
36✔
3229

3230
    return retval;
36✔
UNCOV
3231
}
×
3232

3233
void
3234
logfile_sub_source::text_crumbs_for_line(int line,
30✔
3235
                                         std::vector<breadcrumb::crumb>& crumbs)
3236
{
3237
    text_sub_source::text_crumbs_for_line(line, crumbs);
30✔
3238

3239
    if (this->lss_filtered_index.empty()) {
30✔
3240
        return;
9✔
3241
    }
3242

3243
    auto vl = vis_line_t(line);
21✔
3244
    auto bmc = this->get_bookmark_metadata_context(
21✔
3245
        vl, bookmark_metadata::categories::partition);
3246
    if (bmc.bmc_current_metadata) {
21✔
3247
        const auto& name = bmc.bmc_current_metadata.value()->bm_name;
1✔
3248
        auto key = to_anchor_string(name);
1✔
3249
        auto display = attr_line_t()
1✔
3250
                           .append("\u2291 "_symbol)
1✔
3251
                           .append(lnav::roles::variable(name))
2✔
3252
                           .move();
1✔
3253
        crumbs.emplace_back(
1✔
3254
            key,
3255
            display,
UNCOV
3256
            [this]() -> std::vector<breadcrumb::possibility> {
×
3257
                auto& vb = this->tss_view->get_bookmarks();
1✔
3258
                const auto& bv = vb[&textview_curses::BM_PARTITION];
1✔
3259
                std::vector<breadcrumb::possibility> retval;
1✔
3260

3261
                for (const auto& vl : bv.bv_tree) {
2✔
3262
                    auto meta_opt = this->find_bookmark_metadata(vl);
1✔
3263
                    if (!meta_opt || meta_opt.value()->bm_name.empty()) {
1✔
UNCOV
3264
                        continue;
×
3265
                    }
3266

3267
                    const auto& name = meta_opt.value()->bm_name;
1✔
3268
                    retval.emplace_back(to_anchor_string(name), name);
1✔
3269
                }
3270

3271
                return retval;
1✔
UNCOV
3272
            },
×
3273
            [ec = this->lss_exec_context](const auto& part) {
1✔
UNCOV
3274
                auto cmd = fmt::format(FMT_STRING(":goto {}"),
×
3275
                                       part.template get<std::string>());
UNCOV
3276
                ec->execute(INTERNAL_SRC_LOC, cmd);
×
UNCOV
3277
            });
×
3278
    }
1✔
3279

3280
    auto line_pair_opt = this->find_line_with_file(vl);
21✔
3281
    if (!line_pair_opt) {
21✔
UNCOV
3282
        return;
×
3283
    }
3284
    auto line_pair = line_pair_opt.value();
21✔
3285
    auto& lf = line_pair.first;
21✔
3286
    auto format = lf->get_format();
21✔
3287
    char ts[64];
3288

3289
    sql_strftime(ts, sizeof(ts), line_pair.second->get_timeval(), 'T');
21✔
3290

3291
    crumbs.emplace_back(std::string(ts),
21✔
3292
                        timestamp_poss,
3293
                        [ec = this->lss_exec_context](const auto& ts) {
21✔
UNCOV
3294
                            auto cmd
×
UNCOV
3295
                                = fmt::format(FMT_STRING(":goto {}"),
×
3296
                                              ts.template get<std::string>());
UNCOV
3297
                            ec->execute(INTERNAL_SRC_LOC, cmd);
×
UNCOV
3298
                        });
×
3299
    crumbs.back().c_expected_input
21✔
3300
        = breadcrumb::crumb::expected_input_t::anything;
21✔
3301
    crumbs.back().c_search_placeholder = "(Enter an absolute or relative time)";
21✔
3302

3303
    auto format_name = format->get_name().to_string();
21✔
3304

3305
    crumbs.emplace_back(
21✔
3306
        format_name,
3307
        attr_line_t().append(format_name),
21✔
UNCOV
3308
        [this]() -> std::vector<breadcrumb::possibility> {
×
3309
            return this->lss_files
14✔
3310
                | lnav::itertools::filter_in([](const auto& file_data) {
28✔
3311
                       return file_data->is_visible();
15✔
3312
                   })
3313
                | lnav::itertools::map(&logfile_data::get_file_ptr)
42✔
3314
                | lnav::itertools::map(&logfile::get_format_name)
42✔
3315
                | lnav::itertools::unique()
42✔
3316
                | lnav::itertools::map([](const auto& elem) {
56✔
3317
                       return breadcrumb::possibility{
3318
                           elem.to_string(),
3319
                       };
14✔
3320
                   })
3321
                | lnav::itertools::to_vector();
42✔
3322
        },
3323
        [ec = this->lss_exec_context](const auto& format_name) {
21✔
3324
            static const std::string MOVE_STMT = R"(;UPDATE lnav_views
3325
     SET selection = ifnull(
3326
         (SELECT log_line FROM all_logs WHERE log_format = $format_name LIMIT 1),
3327
         (SELECT raise_error(
3328
            'Could not find format: ' || $format_name,
3329
            'The corresponding log messages might have been filtered out'))
3330
       )
3331
     WHERE name = 'log'
3332
)";
3333

UNCOV
3334
            ec->execute_with(
×
3335
                INTERNAL_SRC_LOC,
×
3336
                MOVE_STMT,
3337
                std::make_pair("format_name",
3338
                               format_name.template get<std::string>()));
UNCOV
3339
        });
×
3340

3341
    auto msg_start_iter = lf->message_start(line_pair.second);
21✔
3342
    auto file_line_number = std::distance(lf->begin(), msg_start_iter);
21✔
3343
    crumbs.emplace_back(
42✔
3344
        lf->get_unique_path(),
21✔
3345
        to_display(lf).appendf(FMT_STRING("[{:L}]"), file_line_number),
84✔
UNCOV
3346
        [this]() -> std::vector<breadcrumb::possibility> {
×
3347
            return this->lss_files
14✔
3348
                | lnav::itertools::filter_in([](const auto& file_data) {
28✔
3349
                       return file_data->is_visible();
15✔
3350
                   })
3351
                | lnav::itertools::map([](const auto& file_data) {
28✔
3352
                       return breadcrumb::possibility{
3353
                           file_data->get_file_ptr()->get_unique_path(),
15✔
3354
                           to_display(file_data->get_file()),
3355
                       };
15✔
3356
                   });
28✔
3357
        },
3358
        [ec = this->lss_exec_context](const auto& uniq_path) {
21✔
3359
            static const std::string MOVE_STMT = R"(;UPDATE lnav_views
3360
     SET selection = ifnull(
3361
          (SELECT log_line FROM all_logs WHERE log_unique_path = $uniq_path LIMIT 1),
3362
          (SELECT raise_error(
3363
            'Could not find file: ' || $uniq_path,
3364
            'The corresponding log messages might have been filtered out'))
3365
         )
3366
     WHERE name = 'log'
3367
)";
3368

UNCOV
3369
            ec->execute_with(
×
UNCOV
3370
                INTERNAL_SRC_LOC,
×
3371
                MOVE_STMT,
3372
                std::make_pair("uniq_path",
3373
                               uniq_path.template get<std::string>()));
UNCOV
3374
        });
×
3375

3376
    shared_buffer sb;
21✔
3377
    logline_value_vector values;
21✔
3378
    auto& sbr = values.lvv_sbr;
21✔
3379

3380
    lf->read_full_message(msg_start_iter, sbr);
21✔
3381
    attr_line_t al(to_string(sbr));
21✔
3382
    if (!sbr.get_metadata().m_valid_utf) {
21✔
3383
        scrub_to_utf8(al.al_string.data(), al.al_string.length());
×
3384
    }
3385
    if (sbr.get_metadata().m_has_ansi) {
21✔
3386
        // bleh
UNCOV
3387
        scrub_ansi_string(al.get_string(), &al.al_attrs);
×
UNCOV
3388
        sbr.share(sb, al.al_string.data(), al.al_string.size());
×
3389
    }
3390
    format->annotate(lf.get(), file_line_number, al.get_attrs(), values);
21✔
3391

3392
    {
3393
        static const std::string MOVE_STMT = R"(;UPDATE lnav_views
37✔
3394
          SET selection = ifnull(
3395
            (SELECT log_line FROM all_logs WHERE log_thread_id = $tid LIMIT 1),
3396
            (SELECT raise_error('Could not find thread ID: ' || $tid,
3397
                                'The corresponding log messages might have been filtered out')))
3398
          WHERE name = 'log'
3399
        )";
3400
        static const auto ELLIPSIS = "\u22ef"_frag;
3401

3402
        auto tid_display = values.lvv_thread_id_value.has_value()
21✔
3403
            ? lnav::roles::identifier(values.lvv_thread_id_value.value())
21✔
3404
            : lnav::roles::hidden(ELLIPSIS);
21✔
3405
        crumbs.emplace_back(
21✔
3406
            (values.lvv_thread_id_value.has_value()
21✔
3407
                 ? values.lvv_thread_id_value.value()
21✔
3408
                 : ""_frag)
20✔
3409
                .to_string(),
42✔
3410
            attr_line_t()
21✔
3411
                .append(ui_icon_t::thread)
21✔
3412
                .append(" ")
21✔
3413
                .append(tid_display),
UNCOV
3414
            [this]() -> std::vector<breadcrumb::possibility> {
×
3415
                std::set<std::string> poss_strs;
14✔
3416

3417
                for (const auto& file_data : this->lss_files) {
29✔
3418
                    if (file_data->get_file_ptr() == nullptr) {
15✔
UNCOV
3419
                        continue;
×
3420
                    }
3421
                    safe::ReadAccess<logfile::safe_thread_id_state> r_tid_map(
3422
                        file_data->get_file_ptr()->get_thread_ids());
15✔
3423

3424
                    for (const auto& pair : r_tid_map->ltis_tid_ranges) {
30✔
3425
                        poss_strs.emplace(pair.first.to_string());
15✔
3426
                    }
3427
                }
15✔
3428

3429
                std::vector<breadcrumb::possibility> retval;
14✔
3430

3431
                std::transform(poss_strs.begin(),
14✔
3432
                               poss_strs.end(),
3433
                               std::back_inserter(retval),
3434
                               [](const auto& tid_str) {
15✔
3435
                                   return breadcrumb::possibility(tid_str);
15✔
3436
                               });
3437

3438
                return retval;
28✔
3439
            },
14✔
3440
            [ec = this->lss_exec_context](const auto& tid) {
21✔
UNCOV
3441
                ec->execute_with(
×
3442
                    INTERNAL_SRC_LOC,
×
3443
                    MOVE_STMT,
3444
                    std::make_pair("tid", tid.template get<std::string>()));
UNCOV
3445
            });
×
3446
    }
21✔
3447

3448
    {
3449
        static const std::string MOVE_STMT = R"(;UPDATE lnav_views
37✔
3450
          SET selection = ifnull(
3451
            (SELECT log_line FROM all_logs WHERE log_opid = $opid LIMIT 1),
3452
            (SELECT raise_error('Could not find opid: ' || $opid,
3453
                                'The corresponding log messages might have been filtered out')))
3454
          WHERE name = 'log'
3455
        )";
3456
        static const std::string ELLIPSIS = "\u22ef";
37✔
3457

3458
        auto opid_display = values.lvv_opid_value.has_value()
21✔
3459
            ? lnav::roles::identifier(values.lvv_opid_value.value())
11✔
3460
            : lnav::roles::hidden(ELLIPSIS);
32✔
3461
        crumbs.emplace_back(
42✔
3462
            values.lvv_opid_value.has_value() ? values.lvv_opid_value.value()
52✔
3463
                                              : "",
3464
            attr_line_t().append(opid_display),
21✔
UNCOV
3465
            [this]() -> std::vector<breadcrumb::possibility> {
×
3466
                std::unordered_set<std::string> poss_strs;
14✔
3467

3468
                for (const auto& file_data : this->lss_files) {
29✔
3469
                    if (file_data->get_file_ptr() == nullptr) {
15✔
UNCOV
3470
                        continue;
×
3471
                    }
3472
                    safe::ReadAccess<logfile::safe_opid_state> r_opid_map(
3473
                        file_data->get_file_ptr()->get_opids());
15✔
3474

3475
                    poss_strs.reserve(poss_strs.size()
30✔
3476
                                      + r_opid_map->los_opid_ranges.size());
15✔
3477
                    for (const auto& pair : r_opid_map->los_opid_ranges) {
795✔
3478
                        poss_strs.insert(pair.first.to_string());
780✔
3479
                    }
3480
                }
15✔
3481

3482
                std::vector<breadcrumb::possibility> retval;
14✔
3483
                retval.reserve(poss_strs.size());
14✔
3484

3485
                std::transform(poss_strs.begin(),
14✔
3486
                               poss_strs.end(),
3487
                               std::back_inserter(retval),
3488
                               [](const auto& opid_str) {
780✔
3489
                                   return breadcrumb::possibility(opid_str);
780✔
3490
                               });
3491

3492
                return retval;
28✔
3493
            },
14✔
3494
            [ec = this->lss_exec_context](const auto& opid) {
21✔
UNCOV
3495
                ec->execute_with(
×
UNCOV
3496
                    INTERNAL_SRC_LOC,
×
3497
                    MOVE_STMT,
3498
                    std::make_pair("opid", opid.template get<std::string>()));
3499
            });
×
3500
    }
21✔
3501

3502
    auto sf = string_fragment::from_str(al.get_string());
21✔
3503
    auto body_opt = get_string_attr(al.get_attrs(), SA_BODY);
21✔
3504
    auto nl_pos_opt = sf.find('\n');
21✔
3505
    auto msg_line_number = std::distance(msg_start_iter, line_pair.second);
21✔
3506
    auto line_from_top = line - msg_line_number;
21✔
3507
    if (body_opt && nl_pos_opt) {
21✔
3508
        if (this->lss_token_meta_line != file_line_number
18✔
3509
            || this->lss_token_meta_size != sf.length())
9✔
3510
        {
3511
            if (body_opt->saw_string_attr->sa_range.length() < 128 * 1024) {
3✔
3512
                this->lss_token_meta
3513
                    = lnav::document::discover(al)
3✔
3514
                          .over_range(
3✔
3515
                              body_opt.value().saw_string_attr->sa_range)
3✔
3516
                          .perform();
3✔
3517
                // XXX discover_structure() changes `al`, have to recompute
3518
                // stuff
3519
                sf = al.to_string_fragment();
3✔
3520
                body_opt = get_string_attr(al.get_attrs(), SA_BODY);
3✔
3521
            } else {
3522
                this->lss_token_meta = lnav::document::metadata{};
×
3523
            }
3524
            this->lss_token_meta_line = file_line_number;
3✔
3525
            this->lss_token_meta_size = sf.length();
3✔
3526
        }
3527

3528
        const auto initial_size = crumbs.size();
9✔
3529
        auto sf_body
3530
            = sf.sub_range(body_opt->saw_string_attr->sa_range.lr_start,
9✔
3531
                           body_opt->saw_string_attr->sa_range.lr_end);
9✔
3532
        file_off_t line_offset = body_opt->saw_string_attr->sa_range.lr_start;
9✔
3533
        file_off_t line_end_offset = sf.length();
9✔
3534
        ssize_t line_number = 0;
9✔
3535

3536
        for (const auto& sf_line : sf_body.split_lines()) {
18✔
3537
            if (line_number >= msg_line_number) {
12✔
3538
                line_end_offset = line_offset + sf_line.length();
3✔
3539
                break;
3✔
3540
            }
3541
            line_number += 1;
9✔
3542
            line_offset += sf_line.length();
9✔
3543
        }
9✔
3544

3545
        this->lss_token_meta.m_sections_tree.visit_overlapping(
9✔
3546
            line_offset,
3547
            line_end_offset,
3548
            [this,
2✔
3549
             initial_size,
3550
             meta = &this->lss_token_meta,
9✔
3551
             &crumbs,
3552
             line_from_top](const auto& iv) {
3553
                auto path = crumbs | lnav::itertools::skip(initial_size)
6✔
3554
                    | lnav::itertools::map(&breadcrumb::crumb::c_key)
4✔
3555
                    | lnav::itertools::append(iv.value);
2✔
3556
                auto curr_node = lnav::document::hier_node::lookup_path(
2✔
3557
                    meta->m_sections_root.get(), path);
2✔
3558

3559
                crumbs.emplace_back(
4✔
3560
                    iv.value,
2✔
3561
                    [meta, path]() { return meta->possibility_provider(path); },
4✔
3562
                    [this, curr_node, path, line_from_top](const auto& key) {
4✔
UNCOV
3563
                        if (!curr_node) {
×
3564
                            return;
×
3565
                        }
UNCOV
3566
                        auto* parent_node = curr_node.value()->hn_parent;
×
3567
                        if (parent_node == nullptr) {
×
UNCOV
3568
                            return;
×
3569
                        }
3570
                        key.match(
UNCOV
3571
                            [parent_node](const std::string& str) {
×
UNCOV
3572
                                return parent_node->find_line_number(str);
×
3573
                            },
3574
                            [parent_node](size_t index) {
×
3575
                                return parent_node->find_line_number(index);
×
3576
                            })
3577
                            | [this, line_from_top](auto line_number) {
×
UNCOV
3578
                                  this->tss_view->set_selection(
×
UNCOV
3579
                                      vis_line_t(line_from_top + line_number));
×
3580
                              };
3581
                    });
3582
                if (curr_node
2✔
3583
                    && !curr_node.value()->hn_parent->is_named_only()) {
2✔
UNCOV
3584
                    auto node = lnav::document::hier_node::lookup_path(
×
UNCOV
3585
                        meta->m_sections_root.get(), path);
×
3586

UNCOV
3587
                    crumbs.back().c_expected_input
×
UNCOV
3588
                        = curr_node.value()
×
UNCOV
3589
                              ->hn_parent->hn_named_children.empty()
×
UNCOV
3590
                        ? breadcrumb::crumb::expected_input_t::index
×
3591
                        : breadcrumb::crumb::expected_input_t::index_or_exact;
UNCOV
3592
                    crumbs.back().with_possible_range(
×
UNCOV
3593
                        node | lnav::itertools::map([](const auto hn) {
×
UNCOV
3594
                            return hn->hn_parent->hn_children.size();
×
3595
                        })
UNCOV
3596
                        | lnav::itertools::unwrap_or(size_t{0}));
×
3597
                }
3598
            });
2✔
3599

3600
        auto path = crumbs | lnav::itertools::skip(initial_size)
18✔
3601
            | lnav::itertools::map(&breadcrumb::crumb::c_key);
18✔
3602
        auto node = lnav::document::hier_node::lookup_path(
9✔
3603
            this->lss_token_meta.m_sections_root.get(), path);
9✔
3604

3605
        if (node && !node.value()->hn_children.empty()) {
9✔
3606
            auto poss_provider = [curr_node = node.value()]() {
1✔
3607
                std::vector<breadcrumb::possibility> retval;
1✔
3608
                for (const auto& child : curr_node->hn_named_children) {
1✔
3609
                    retval.emplace_back(child.first);
×
3610
                }
3611
                return retval;
1✔
3612
            };
3613
            auto path_performer
3614
                = [this, curr_node = node.value(), line_from_top](
2✔
3615
                      const breadcrumb::crumb::key_t& value) {
3616
                      value.match(
×
3617
                          [curr_node](const std::string& str) {
×
3618
                              return curr_node->find_line_number(str);
×
3619
                          },
3620
                          [curr_node](size_t index) {
×
3621
                              return curr_node->find_line_number(index);
×
3622
                          })
UNCOV
3623
                          | [this, line_from_top](size_t line_number) {
×
UNCOV
3624
                                this->tss_view->set_selection(
×
UNCOV
3625
                                    vis_line_t(line_from_top + line_number));
×
3626
                            };
3627
                  };
1✔
3628
            crumbs.emplace_back("", "\u22ef", poss_provider, path_performer);
1✔
3629
            crumbs.back().c_expected_input
1✔
3630
                = node.value()->hn_named_children.empty()
2✔
3631
                ? breadcrumb::crumb::expected_input_t::index
1✔
3632
                : breadcrumb::crumb::expected_input_t::index_or_exact;
3633
        }
3634
    }
9✔
3635
}
21✔
3636

3637
void
3638
logfile_sub_source::quiesce()
40✔
3639
{
3640
    for (auto& ld : this->lss_files) {
80✔
3641
        auto* lf = ld->get_file_ptr();
40✔
3642

3643
        if (lf == nullptr) {
40✔
UNCOV
3644
            continue;
×
3645
        }
3646

3647
        lf->quiesce();
40✔
3648
    }
3649
}
40✔
3650

3651
bookmark_metadata&
3652
logfile_sub_source::get_bookmark_metadata(content_line_t cl)
38✔
3653
{
3654
    auto line_pair = this->find_line_with_file(cl).value();
38✔
3655
    auto line_number = static_cast<uint32_t>(
3656
        std::distance(line_pair.first->begin(), line_pair.second));
38✔
3657

3658
    return line_pair.first->get_bookmark_metadata()[line_number];
76✔
3659
}
38✔
3660

3661
logfile_sub_source::bookmark_metadata_context
3662
logfile_sub_source::get_bookmark_metadata_context(
5,014✔
3663
    vis_line_t vl, bookmark_metadata::categories desired) const
3664
{
3665
    const auto& vb = this->tss_view->get_bookmarks();
5,014✔
3666
    const auto& bv = vb[desired == bookmark_metadata::categories::partition
3667
                            ? &textview_curses::BM_PARTITION
3668
                            : &textview_curses::BM_META];
5,014✔
3669
    auto vl_iter = bv.bv_tree.lower_bound(vl + 1_vl);
5,014✔
3670

3671
    std::optional<vis_line_t> next_line;
5,014✔
3672
    for (auto next_vl_iter = vl_iter; next_vl_iter != bv.bv_tree.end();
5,014✔
UNCOV
3673
         ++next_vl_iter)
×
3674
    {
3675
        auto bm_opt = this->find_bookmark_metadata(*next_vl_iter);
4✔
3676
        if (!bm_opt) {
4✔
UNCOV
3677
            continue;
×
3678
        }
3679

3680
        if (bm_opt.value()->has(desired)) {
4✔
3681
            next_line = *next_vl_iter;
4✔
3682
            break;
4✔
3683
        }
3684
    }
3685
    if (vl_iter == bv.bv_tree.begin()) {
5,014✔
3686
        return bookmark_metadata_context{std::nullopt, std::nullopt, next_line};
4,995✔
3687
    }
3688

3689
    --vl_iter;
19✔
3690
    while (true) {
3691
        auto bm_opt = this->find_bookmark_metadata(*vl_iter);
19✔
3692
        if (bm_opt) {
19✔
3693
            if (bm_opt.value()->has(desired)) {
16✔
3694
                return bookmark_metadata_context{
3695
                    *vl_iter, bm_opt.value(), next_line};
19✔
3696
            }
3697
        }
3698

3699
        if (vl_iter == bv.bv_tree.begin()) {
3✔
3700
            return bookmark_metadata_context{
3701
                std::nullopt, std::nullopt, next_line};
3✔
3702
        }
UNCOV
3703
        --vl_iter;
×
3704
    }
3705
    return bookmark_metadata_context{std::nullopt, std::nullopt, next_line};
3706
}
3707

3708
std::optional<bookmark_metadata*>
3709
logfile_sub_source::find_bookmark_metadata(content_line_t cl) const
29,251✔
3710
{
3711
    auto line_pair = this->find_line_with_file(cl).value();
29,251✔
3712
    auto line_number = static_cast<uint32_t>(
3713
        std::distance(line_pair.first->begin(), line_pair.second));
29,251✔
3714

3715
    auto& bm = line_pair.first->get_bookmark_metadata();
29,251✔
3716
    auto bm_iter = bm.find(line_number);
29,251✔
3717
    if (bm_iter == bm.end()) {
29,251✔
3718
        return std::nullopt;
28,460✔
3719
    }
3720

3721
    return &bm_iter->second;
791✔
3722
}
29,251✔
3723

3724
void
3725
logfile_sub_source::erase_bookmark_metadata(content_line_t cl)
38✔
3726
{
3727
    auto line_pair = this->find_line_with_file(cl).value();
38✔
3728
    auto line_number = static_cast<uint32_t>(
3729
        std::distance(line_pair.first->begin(), line_pair.second));
38✔
3730

3731
    auto& bm = line_pair.first->get_bookmark_metadata();
38✔
3732
    auto bm_iter = bm.find(line_number);
38✔
3733
    if (bm_iter != bm.end()) {
38✔
3734
        bm.erase(bm_iter);
6✔
3735
    }
3736
}
38✔
3737

3738
void
3739
logfile_sub_source::clear_bookmark_metadata()
11✔
3740
{
3741
    for (auto& ld : *this) {
26✔
3742
        if (ld->get_file_ptr() == nullptr) {
15✔
UNCOV
3743
            continue;
×
3744
        }
3745

3746
        auto& bm_map = ld->get_file_ptr()->get_bookmark_metadata();
15✔
3747
        auto iter = bm_map.begin();
15✔
3748
        while (iter != bm_map.end()) {
24✔
3749
            auto& meta = iter->second;
9✔
3750
            meta.bm_comment.clear();
9✔
3751
            meta.bm_annotations.la_pairs.clear();
9✔
3752
            meta.bm_opid.clear();
9✔
3753
            if (meta.bm_name_source == bookmark_metadata::meta_source::user) {
9✔
3754
                meta.bm_name.clear();
1✔
3755
            }
3756
            auto tag_iter = meta.bm_tags.begin();
9✔
3757
            while (tag_iter != meta.bm_tags.end()) {
9✔
UNCOV
3758
                if (tag_iter->te_source == bookmark_metadata::meta_source::user)
×
3759
                {
UNCOV
3760
                    tag_iter = meta.bm_tags.erase(tag_iter);
×
3761
                } else {
UNCOV
3762
                    ++tag_iter;
×
3763
                }
3764
            }
3765
            if (meta.empty(bookmark_metadata::categories::any)) {
9✔
3766
                iter = bm_map.erase(iter);
1✔
3767
            } else {
3768
                ++iter;
8✔
3769
            }
3770
        }
3771
    }
3772
}
11✔
3773

3774
void
3775
logfile_sub_source::increase_line_context()
×
3776
{
3777
    auto old_context = this->lss_line_context;
×
3778

3779
    switch (this->lss_line_context) {
×
3780
        case line_context_t::filename:
×
3781
            // nothing to do
UNCOV
3782
            break;
×
3783
        case line_context_t::basename:
×
UNCOV
3784
            this->lss_line_context = line_context_t::filename;
×
UNCOV
3785
            break;
×
UNCOV
3786
        case line_context_t::none:
×
UNCOV
3787
            this->lss_line_context = line_context_t::basename;
×
UNCOV
3788
            break;
×
UNCOV
3789
        case line_context_t::time_column:
×
UNCOV
3790
            this->lss_line_context = line_context_t::none;
×
UNCOV
3791
            break;
×
3792
    }
3793
    if (old_context != this->lss_line_context) {
×
3794
        this->clear_line_size_cache();
×
3795
    }
3796
}
3797

3798
bool
UNCOV
3799
logfile_sub_source::decrease_line_context()
×
3800
{
3801
    static const auto& cfg
UNCOV
3802
        = injector::get<const logfile_sub_source_ns::config&>();
×
3803
    auto old_context = this->lss_line_context;
×
3804

UNCOV
3805
    switch (this->lss_line_context) {
×
3806
        case line_context_t::filename:
×
UNCOV
3807
            this->lss_line_context = line_context_t::basename;
×
UNCOV
3808
            break;
×
UNCOV
3809
        case line_context_t::basename:
×
UNCOV
3810
            this->lss_line_context = line_context_t::none;
×
3811
            break;
×
UNCOV
3812
        case line_context_t::none:
×
UNCOV
3813
            if (cfg.c_time_column
×
3814
                != logfile_sub_source_ns::time_column_feature_t::Disabled)
3815
            {
UNCOV
3816
                this->lss_line_context = line_context_t::time_column;
×
3817
            }
UNCOV
3818
            break;
×
UNCOV
3819
        case line_context_t::time_column:
×
UNCOV
3820
            break;
×
3821
    }
UNCOV
3822
    if (old_context != this->lss_line_context) {
×
UNCOV
3823
        this->clear_line_size_cache();
×
3824

3825
        return true;
×
3826
    }
3827

UNCOV
3828
    return false;
×
3829
}
3830

3831
size_t
3832
logfile_sub_source::get_filename_offset() const
545✔
3833
{
3834
    switch (this->lss_line_context) {
545✔
UNCOV
3835
        case line_context_t::filename:
×
UNCOV
3836
            return this->lss_filename_width;
×
UNCOV
3837
        case line_context_t::basename:
×
3838
            return this->lss_basename_width;
×
3839
        default:
545✔
3840
            return 0;
545✔
3841
    }
3842
}
3843

3844
size_t
3845
logfile_sub_source::file_count() const
5,377✔
3846
{
3847
    size_t retval = 0;
5,377✔
3848
    const_iterator iter;
5,377✔
3849

3850
    for (iter = this->cbegin(); iter != this->cend(); ++iter) {
10,379✔
3851
        if (*iter != nullptr && (*iter)->get_file() != nullptr) {
5,002✔
3852
            retval += 1;
4,996✔
3853
        }
3854
    }
3855

3856
    return retval;
5,377✔
3857
}
3858

3859
size_t
UNCOV
3860
logfile_sub_source::text_size_for_line(textview_curses& tc,
×
3861
                                       int row,
3862
                                       text_sub_source::line_flags_t flags)
3863
{
UNCOV
3864
    size_t index = row % LINE_SIZE_CACHE_SIZE;
×
3865

3866
    if (this->lss_line_size_cache[index].first != row) {
×
UNCOV
3867
        std::string value;
×
3868

UNCOV
3869
        this->text_value_for_line(tc, row, value, flags);
×
UNCOV
3870
        scrub_ansi_string(value, nullptr);
×
3871
        auto line_width = string_fragment::from_str(value).column_width();
×
3872
        if (this->lss_line_context == line_context_t::time_column) {
×
3873
            auto time_attr
3874
                = find_string_attr(this->lss_token_al.al_attrs, &L_TIMESTAMP);
×
3875
            if (time_attr != this->lss_token_al.al_attrs.end()) {
×
3876
                line_width -= time_attr->sa_range.length();
×
3877
                auto format = this->lss_token_file->get_format();
×
3878
                if (format->lf_level_hideable) {
×
3879
                    auto level_attr = find_string_attr(
×
3880
                        this->lss_token_al.al_attrs, &L_LEVEL);
×
UNCOV
3881
                    if (level_attr != this->lss_token_al.al_attrs.end()) {
×
UNCOV
3882
                        line_width -= level_attr->sa_range.length();
×
3883
                    }
3884
                }
3885
            }
3886
        }
3887
        this->lss_line_size_cache[index].second = line_width;
×
3888
        this->lss_line_size_cache[index].first = row;
×
3889
    }
3890
    return this->lss_line_size_cache[index].second;
×
3891
}
3892

3893
int
3894
logfile_sub_source::get_filtered_count_for(size_t filter_index) const
2✔
3895
{
3896
    int retval = 0;
2✔
3897

3898
    for (const auto& ld : this->lss_files) {
4✔
3899
        retval += ld->ld_filter_state.lfo_filter_state
2✔
3900
                      .tfs_filter_hits[filter_index];
2✔
3901
    }
3902

3903
    return retval;
2✔
3904
}
3905

3906
bool
3907
logfile_sub_source::row_is_filtered_out(size_t idx,
99✔
3908
                                        uint32_t filter_in_mask,
3909
                                        uint32_t filter_out_mask)
3910
{
3911
    if (!this->tss_apply_filters || idx >= this->lss_index.size()) {
99✔
UNCOV
3912
        return false;
×
3913
    }
3914
    const auto cl = this->lss_index[idx].value();
99✔
3915
    uint64_t line_number;
3916
    auto ld = this->find_data(cl, line_number);
99✔
3917
    if (ld == this->end()) {
99✔
3918
        return false;
×
3919
    }
3920
    if (!(*ld)->is_visible()) {
99✔
UNCOV
3921
        return true;
×
3922
    }
3923
    if ((*ld)->ld_filter_state.excluded(
99✔
3924
            filter_in_mask, filter_out_mask, line_number))
3925
    {
3926
        return true;
3✔
3927
    }
3928
    auto line_iter = (*ld)->get_file_ptr()->begin() + line_number;
96✔
3929
    return !this->check_extra_filters(ld, line_iter);
96✔
3930
}
3931

3932
std::optional<vis_line_t>
3933
logfile_sub_source::row_for(const row_info& ri)
425✔
3934
{
3935
    auto lb = std::lower_bound(this->lss_filtered_index.begin(),
850✔
3936
                               this->lss_filtered_index.end(),
3937
                               ri.ri_time,
425✔
3938
                               filtered_logline_cmp(*this));
3939
    if (lb != this->lss_filtered_index.end()) {
425✔
3940
        auto first_lb = lb;
420✔
3941
        while (true) {
3942
            auto cl = this->lss_index[*lb].value();
438✔
3943
            if (content_line_t(ri.ri_id) == cl) {
438✔
3944
                first_lb = lb;
385✔
3945
                break;
385✔
3946
            }
3947
            auto ll = this->find_line(cl);
53✔
3948
            if (ll->get_timeval() != ri.ri_time) {
53✔
3949
                break;
35✔
3950
            }
3951
            auto next_lb = std::next(lb);
18✔
3952
            if (next_lb == this->lss_filtered_index.end()) {
18✔
3953
                break;
×
3954
            }
3955
            lb = next_lb;
18✔
3956
        }
18✔
3957

3958
        const auto dst
3959
            = std::distance(this->lss_filtered_index.begin(), first_lb);
420✔
3960
        return vis_line_t(dst);
420✔
3961
    }
3962

3963
    return std::nullopt;
5✔
3964
}
3965

3966
std::unique_ptr<logline_window>
3967
logfile_sub_source::window_at(vis_line_t start_vl, vis_line_t end_vl)
86✔
3968
{
3969
    return std::make_unique<logline_window>(*this, start_vl, end_vl);
86✔
3970
}
3971

3972
std::unique_ptr<logline_window>
3973
logfile_sub_source::window_at(vis_line_t start_vl)
446✔
3974
{
3975
    return std::make_unique<logline_window>(*this, start_vl, start_vl + 1_vl);
446✔
3976
}
3977

3978
std::unique_ptr<logline_window>
3979
logfile_sub_source::window_to_end(vis_line_t start_vl)
26✔
3980
{
3981
    return std::make_unique<logline_window>(
3982
        *this, start_vl, vis_line_t(this->text_line_count()));
26✔
3983
}
3984

3985
std::optional<vis_line_t>
3986
logfile_sub_source::row_for_anchor(const std::string& id)
4✔
3987
{
3988
    if (startswith(id, "#msg")) {
4✔
3989
        static const auto ANCHOR_RE
3990
            = lnav::pcre2pp::code::from_const(R"(#msg([0-9a-fA-F]+)-(.+))");
4✔
3991
        thread_local auto md = lnav::pcre2pp::match_data::unitialized();
4✔
3992

3993
        if (ANCHOR_RE.capture_from(id).into(md).found_p()) {
4✔
3994
            auto scan_res = scn::scan<int64_t>(md[1]->to_string_view(), "{:x}");
4✔
3995
            if (scan_res) {
4✔
3996
                auto ts_low = std::chrono::microseconds{scan_res->value()};
4✔
3997
                auto ts_high = ts_low + 1us;
4✔
3998

3999
                auto low_vl = this->row_for_time(to_timeval(ts_low));
4✔
4000
                auto high_vl = this->row_for_time(to_timeval(ts_high));
4✔
4001
                if (low_vl) {
4✔
4002
                    auto lw = this->window_at(
4003
                        low_vl.value(),
4✔
4004
                        high_vl.value_or(low_vl.value() + 1_vl));
8✔
4005

4006
                    for (const auto& li : *lw) {
4✔
4007
                        auto hash_res = li.get_line_hash();
4✔
4008
                        if (hash_res.isErr()) {
4✔
UNCOV
4009
                            auto errmsg = hash_res.unwrapErr();
×
4010

UNCOV
4011
                            log_error("unable to get line hash: %s",
×
4012
                                      errmsg.c_str());
UNCOV
4013
                            continue;
×
4014
                        }
4015

4016
                        auto hash = hash_res.unwrap();
4✔
4017
                        if (hash == md[2]) {
4✔
4018
                            return li.get_vis_line();
4✔
4019
                        }
4020
                    }
16✔
4021
                }
4✔
4022
            }
4023
        }
4024

UNCOV
4025
        return std::nullopt;
×
4026
    }
4027

UNCOV
4028
    auto& vb = this->tss_view->get_bookmarks();
×
UNCOV
4029
    const auto& bv = vb[&textview_curses::BM_PARTITION];
×
4030

UNCOV
4031
    for (const auto& vl : bv.bv_tree) {
×
UNCOV
4032
        auto meta_opt = this->find_bookmark_metadata(vl);
×
UNCOV
4033
        if (!meta_opt || meta_opt.value()->bm_name.empty()) {
×
UNCOV
4034
            continue;
×
4035
        }
4036

UNCOV
4037
        const auto& name = meta_opt.value()->bm_name;
×
UNCOV
4038
        if (id == text_anchors::to_anchor_string(name)) {
×
UNCOV
4039
            return vl;
×
4040
        }
4041
    }
4042

UNCOV
4043
    return std::nullopt;
×
4044
}
4045

4046
std::optional<vis_line_t>
4047
logfile_sub_source::adjacent_anchor(vis_line_t vl, text_anchors::direction dir)
2✔
4048
{
4049
    if (vl < this->lss_filtered_index.size()) {
2✔
4050
        auto file_and_line_pair = this->find_line_with_file(vl);
2✔
4051
        if (file_and_line_pair) {
2✔
4052
            const auto& [lf, line] = file_and_line_pair.value();
2✔
4053
            if (line->is_continued()) {
2✔
UNCOV
4054
                auto retval = vl;
×
UNCOV
4055
                switch (dir) {
×
UNCOV
4056
                    case direction::prev: {
×
UNCOV
4057
                        auto first_line = line;
×
UNCOV
4058
                        while (first_line->is_continued()) {
×
UNCOV
4059
                            --first_line;
×
UNCOV
4060
                            retval -= 1_vl;
×
4061
                        }
UNCOV
4062
                        return retval;
×
4063
                    }
UNCOV
4064
                    case direction::next: {
×
UNCOV
4065
                        auto first_line = line;
×
UNCOV
4066
                        while (first_line->is_continued()) {
×
UNCOV
4067
                            ++first_line;
×
UNCOV
4068
                            retval += 1_vl;
×
4069
                        }
UNCOV
4070
                        return retval;
×
4071
                    }
4072
                }
4073
            }
4074
        }
4075
    }
2✔
4076

4077
    auto bmc = this->get_bookmark_metadata_context(
2✔
4078
        vl, bookmark_metadata::categories::partition);
4079
    switch (dir) {
2✔
UNCOV
4080
        case direction::prev: {
×
UNCOV
4081
            if (bmc.bmc_current && bmc.bmc_current.value() != vl) {
×
UNCOV
4082
                return bmc.bmc_current;
×
4083
            }
UNCOV
4084
            if (!bmc.bmc_current || bmc.bmc_current.value() == 0_vl) {
×
UNCOV
4085
                return 0_vl;
×
4086
            }
UNCOV
4087
            auto prev_bmc = this->get_bookmark_metadata_context(
×
UNCOV
4088
                bmc.bmc_current.value() - 1_vl,
×
4089
                bookmark_metadata::categories::partition);
UNCOV
4090
            if (!prev_bmc.bmc_current) {
×
UNCOV
4091
                return 0_vl;
×
4092
            }
UNCOV
4093
            return prev_bmc.bmc_current;
×
4094
        }
4095
        case direction::next:
2✔
4096
            return bmc.bmc_next_line;
2✔
4097
    }
UNCOV
4098
    return std::nullopt;
×
4099
}
4100

4101
std::optional<std::string>
4102
logfile_sub_source::anchor_for_row(vis_line_t vl)
147✔
4103
{
4104
    auto line_meta = this->get_bookmark_metadata_context(
147✔
4105
        vl, bookmark_metadata::categories::partition);
4106
    if (!line_meta.bmc_current || vl != line_meta.bmc_current
150✔
4107
        || !line_meta.bmc_current_metadata
1✔
4108
        || line_meta.bmc_current_metadata.value()->bm_name.empty())
150✔
4109
    {
4110
        auto lw = window_at(vl);
146✔
4111

4112
        for (const auto& li : *lw) {
146✔
4113
            auto hash_res = li.get_line_hash();
146✔
4114
            if (hash_res.isErr()) {
146✔
UNCOV
4115
                auto errmsg = hash_res.unwrapErr();
×
UNCOV
4116
                log_error("unable to compute line hash: %s", errmsg.c_str());
×
UNCOV
4117
                break;
×
4118
            }
4119
            auto hash = hash_res.unwrap();
146✔
4120
            auto retval = fmt::format(FMT_STRING("#msg{:016x}-{}"),
292✔
4121
                                      li.get_logline().get_time<>().count(),
146✔
UNCOV
4122
                                      hash);
×
4123

4124
            return retval;
146✔
4125
        }
584✔
4126

UNCOV
4127
        return std::nullopt;
×
4128
    }
146✔
4129

4130
    return to_anchor_string(line_meta.bmc_current_metadata.value()->bm_name);
1✔
4131
}
4132

4133
std::unordered_set<std::string>
UNCOV
4134
logfile_sub_source::get_anchors()
×
4135
{
UNCOV
4136
    auto& vb = this->tss_view->get_bookmarks();
×
UNCOV
4137
    const auto& bv = vb[&textview_curses::BM_PARTITION];
×
UNCOV
4138
    std::unordered_set<std::string> retval;
×
4139

UNCOV
4140
    for (const auto& vl : bv.bv_tree) {
×
UNCOV
4141
        auto meta_opt = this->find_bookmark_metadata(vl);
×
UNCOV
4142
        if (!meta_opt || meta_opt.value()->bm_name.empty()) {
×
UNCOV
4143
            continue;
×
4144
        }
4145

UNCOV
4146
        const auto& name = meta_opt.value()->bm_name;
×
UNCOV
4147
        retval.emplace(text_anchors::to_anchor_string(name));
×
4148
    }
4149

UNCOV
4150
    return retval;
×
UNCOV
4151
}
×
4152

4153
bool
UNCOV
4154
logfile_sub_source::text_handle_mouse(
×
4155
    textview_curses& tc,
4156
    const listview_curses::display_line_content_t& mouse_line,
4157
    mouse_event& me)
4158
{
UNCOV
4159
    if (mouse_line.is<listview_curses::static_overlay_content>()
×
UNCOV
4160
        && this->text_line_count() > 0)
×
4161
    {
UNCOV
4162
        auto top = tc.get_top();
×
UNCOV
4163
        if (top > 0) {
×
UNCOV
4164
            auto win = this->window_at(top - 1_vl);
×
UNCOV
4165
            for (const auto& li : *win) {
×
UNCOV
4166
                tc.set_top(li.get_vis_line());
×
UNCOV
4167
                tc.set_selection(li.get_vis_line());
×
UNCOV
4168
                return true;
×
4169
            }
4170
        }
4171
    }
4172

UNCOV
4173
    if (tc.get_overlay_selection()) {
×
UNCOV
4174
        auto nci = ncinput{};
×
UNCOV
4175
        if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 2, 4)) {
×
UNCOV
4176
            nci.id = ' ';
×
UNCOV
4177
            nci.eff_text[0] = ' ';
×
UNCOV
4178
            this->list_input_handle_key(tc, nci);
×
UNCOV
4179
        } else if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 5, 6)) {
×
UNCOV
4180
            nci.id = '#';
×
UNCOV
4181
            nci.eff_text[0] = '#';
×
UNCOV
4182
            this->list_input_handle_key(tc, nci);
×
4183
        }
4184
    }
UNCOV
4185
    return true;
×
4186
}
4187

4188
void
4189
logfile_sub_source::reload_config(error_reporter& reporter)
832✔
4190
{
4191
    static const auto& cfg
4192
        = injector::get<const logfile_sub_source_ns::config&>();
832✔
4193

4194
    switch (cfg.c_time_column) {
832✔
UNCOV
4195
        case logfile_sub_source_ns::time_column_feature_t::Default:
×
UNCOV
4196
            if (this->lss_line_context == line_context_t::none) {
×
UNCOV
4197
                this->lss_line_context = line_context_t::time_column;
×
4198
            }
UNCOV
4199
            break;
×
4200
        case logfile_sub_source_ns::time_column_feature_t::Disabled:
832✔
4201
            if (this->lss_line_context == line_context_t::time_column) {
832✔
UNCOV
4202
                this->lss_line_context = line_context_t::none;
×
4203
            }
4204
            break;
832✔
UNCOV
4205
        case logfile_sub_source_ns::time_column_feature_t::Enabled:
×
UNCOV
4206
            break;
×
4207
    }
4208
}
832✔
4209

4210
void
4211
logfile_sub_source::clear_preview()
1✔
4212
{
4213
    text_sub_source::clear_preview();
1✔
4214

4215
    this->set_preview_sql_filter(nullptr);
1✔
4216
    auto last = std::remove_if(this->lss_highlighters.begin(),
1✔
4217
                               this->lss_highlighters.end(),
UNCOV
4218
                               [](const auto& hl) { return hl.h_preview; });
×
4219
    this->lss_highlighters.erase(last, this->lss_highlighters.end());
1✔
4220
}
1✔
4221

4222
void
4223
logfile_sub_source::add_commands_for_session(
80✔
4224
    const std::function<void(const std::string&)>& receiver)
4225
{
4226
    text_sub_source::add_commands_for_session(receiver);
80✔
4227

4228
    auto mark_expr = this->get_sql_marker_text();
80✔
4229
    if (!mark_expr.empty()) {
80✔
4230
        receiver(fmt::format(FMT_STRING("mark-expr {}"), mark_expr));
4✔
4231
    }
4232

4233
    for (const auto& hl : this->lss_highlighters) {
80✔
UNCOV
4234
        auto cmd = "highlight-field "s;
×
UNCOV
4235
        if (hl.h_attrs.has_style(text_attrs::style::bold)) {
×
UNCOV
4236
            cmd += "--bold ";
×
4237
        }
UNCOV
4238
        if (hl.h_attrs.has_style(text_attrs::style::underline)) {
×
UNCOV
4239
            cmd += "--underline ";
×
4240
        }
UNCOV
4241
        if (hl.h_attrs.has_style(text_attrs::style::blink)) {
×
UNCOV
4242
            cmd += "--blink ";
×
4243
        }
UNCOV
4244
        if (hl.h_attrs.has_style(text_attrs::style::struck)) {
×
UNCOV
4245
            cmd += "--strike ";
×
4246
        }
UNCOV
4247
        if (hl.h_attrs.has_style(text_attrs::style::italic)) {
×
UNCOV
4248
            cmd += "--italic ";
×
4249
        }
UNCOV
4250
        cmd += hl.h_field.to_string() + " " + hl.h_regex->get_pattern();
×
UNCOV
4251
        receiver(cmd);
×
4252
    }
4253

4254
    for (const auto& format : log_format::get_root_formats()) {
6,404✔
4255
        auto field_states = format->get_field_states();
6,324✔
4256

4257
        for (const auto& fs_pair : field_states) {
86,849✔
4258
            if (!fs_pair.second.lvm_user_hidden) {
80,525✔
4259
                continue;
80,503✔
4260
            }
4261

4262
            if (fs_pair.second.lvm_user_hidden.value()) {
22✔
4263
                receiver(fmt::format(FMT_STRING("hide-fields {}.{}"),
16✔
4264
                                     format->get_name().to_string(),
8✔
4265
                                     fs_pair.first.to_string()));
8✔
4266
            } else if (fs_pair.second.lvm_hidden) {
18✔
UNCOV
4267
                receiver(fmt::format(FMT_STRING("show-fields {}.{}"),
×
UNCOV
4268
                                     format->get_name().to_string(),
×
UNCOV
4269
                                     fs_pair.first.to_string()));
×
4270
            }
4271
        }
4272
    }
6,324✔
4273

4274
    for (const auto& [schema_id, bp] : this->lss_breakpoints) {
81✔
4275
        if (bp.bp_source != breakpoint_info::source_type::src_location) {
1✔
UNCOV
4276
            continue;
×
4277
        }
4278
        receiver(fmt::format(FMT_STRING("breakpoint {}"), bp.bp_description));
4✔
4279
    }
4280
}
80✔
4281

4282
void
4283
logfile_sub_source::update_filter_hash_state(hasher& h) const
8✔
4284
{
4285
    text_sub_source::update_filter_hash_state(h);
8✔
4286

4287
    for (const auto& ld : this->lss_files) {
10✔
4288
        if (ld->get_file_ptr() == nullptr || !ld->is_visible()) {
2✔
UNCOV
4289
            h.update(0);
×
4290
        } else {
4291
            h.update(1);
2✔
4292
        }
4293
    }
4294
    h.update(this->tss_min_log_level);
8✔
4295
    h.update(this->lss_marked_only);
8✔
4296
}
8✔
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