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

tstack / lnav / 23113678274-2850

15 Mar 2026 03:44PM UTC coverage: 69.029% (-0.04%) from 69.07%
23113678274-2850

push

github

tstack
[snap] more execstack

52700 of 76345 relevant lines covered (69.03%)

520095.65 hits per line

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

65.01
/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 "tlx/container/btree_set.hpp"
67
#include "vtab_module.hh"
68
#include "yajlpp/yajlpp.hh"
69
#include "yajlpp/yajlpp_def.hh"
70

71
using namespace std::chrono_literals;
72
using namespace std::string_literals;
73
using namespace lnav::roles::literals;
74

75
const DIST_SLICE(bm_types) bookmark_type_t logfile_sub_source::BM_FILES("file");
76

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

84
    const auto ncols = sqlite3_column_count(stmt);
39✔
85

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

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

96
        ec.ec_accumulator->append(res);
39✔
97
    }
98

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

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

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

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

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

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

152
        auto retval = ss.str();
×
153

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

158
        return retval;
×
159
    });
×
160

161
    return retval;
×
162
}
163

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

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

178
    line_base = content_line_t(0);
25✔
179
    for (auto iter = this->lss_files.begin();
25✔
180
         iter != this->lss_files.end() && retval == nullptr;
51✔
181
         ++iter)
26✔
182
    {
183
        auto& ld = *(*iter);
26✔
184
        auto* lf = ld.get_file_ptr();
26✔
185

186
        if (lf == nullptr) {
26✔
187
            continue;
×
188
        }
189
        if (strcmp(lf->get_filename_as_string().c_str(), fn) == 0) {
26✔
190
            retval = ld.get_file();
25✔
191
        } else {
192
            line_base += content_line_t(MAX_LINES_PER_FILE);
1✔
193
        }
194
    }
195

196
    return retval;
25✔
197
}
×
198

199
struct filtered_logline_cmp {
200
    filtered_logline_cmp(const logfile_sub_source& lc) : llss_controller(lc) {}
402✔
201

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

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

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

223
        if (ll_lhs == nullptr) {
1,307✔
224
            return true;
×
225
        }
226
        return (*ll_lhs) < rhs;
1,307✔
227
    }
228

229
    const logfile_sub_source& llss_controller;
230
};
231

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

244
    return std::nullopt;
31✔
245
}
246

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

259
    line_info retval;
4,535✔
260
    content_line_t line(0);
4,535✔
261

262
    require_ge(row, 0);
4,535✔
263
    require_lt((size_t) row, this->lss_filtered_index.size());
4,535✔
264

265
    line = this->at(vis_line_t(row));
4,535✔
266

267
    if (flags & RF_RAW) {
4,535✔
268
        auto lf = this->find(line);
32✔
269
        auto ll = lf->begin() + line;
32✔
270
        retval.li_file_range = lf->get_file_range(ll, false);
32✔
271
        retval.li_level = ll->get_msg_level();
32✔
272
        // retval.li_timestamp = ll->get_timeval();
273
        retval.li_partial = false;
32✔
274
        retval.li_utf8_scan_result.usr_has_ansi = ll->has_ansi();
32✔
275
        retval.li_utf8_scan_result.usr_message = ll->is_valid_utf() ? nullptr
32✔
276
                                                                    : "bad";
277
        // timeval start_time, end_time;
278
        // gettimeofday(&start_time, NULL);
279
        value_out = lf->read_line(lf->begin() + line)
64✔
280
                        .map([](auto sbr) { return to_string(sbr); })
64✔
281
                        .unwrapOr({});
32✔
282
        // gettimeofday(&end_time, NULL);
283
        // timeval diff = end_time - start_time;
284
        // log_debug("read time %d.%06d %s:%d", diff.tv_sec, diff.tv_usec,
285
        // lf->get_filename().c_str(), line);
286
        return retval;
32✔
287
    }
32✔
288

289
    require_false(this->lss_in_value_for_line);
4,503✔
290

291
    this->lss_in_value_for_line = true;
4,503✔
292
    this->lss_token_flags = flags;
4,503✔
293
    this->lss_token_file_data = this->find_data(line);
4,503✔
294
    this->lss_token_file = (*this->lss_token_file_data)->get_file();
4,503✔
295
    this->lss_token_line = this->lss_token_file->begin() + line;
4,503✔
296

297
    this->lss_token_al.clear();
4,503✔
298
    this->lss_token_values.clear();
4,503✔
299
    this->lss_share_manager.invalidate_refs();
4,503✔
300
    if (flags & RF_FULL) {
4,503✔
301
        shared_buffer_ref sbr;
48✔
302

303
        this->lss_token_file->read_full_message(this->lss_token_line, sbr);
48✔
304
        this->lss_token_al.al_string = to_string(sbr);
48✔
305
        if (sbr.get_metadata().m_has_ansi) {
48✔
306
            scrub_ansi_string(this->lss_token_al.al_string,
17✔
307
                              &this->lss_token_al.al_attrs);
308
            sbr.get_metadata().m_has_ansi = false;
17✔
309
        }
310
    } else {
48✔
311
        auto sub_opts = subline_options{};
4,455✔
312
        sub_opts.scrub_invalid_utf8 = false;
4,455✔
313
        this->lss_token_al.al_string
314
            = this->lss_token_file->read_line(this->lss_token_line, sub_opts)
8,910✔
315
                  .map([](auto sbr) { return to_string(sbr); })
8,910✔
316
                  .unwrapOr({});
4,455✔
317
        if (this->lss_token_line->has_ansi()) {
4,455✔
318
            scrub_ansi_string(this->lss_token_al.al_string,
24✔
319
                              &this->lss_token_al.al_attrs);
320
        }
321
    }
322
    this->lss_token_shifts.clear();
4,503✔
323

324
    auto format = this->lss_token_file->get_format();
4,503✔
325

326
    value_out = this->lss_token_al.al_string;
4,503✔
327

328
    auto& sbr = this->lss_token_values.lvv_sbr;
4,503✔
329

330
    sbr.share(this->lss_share_manager,
4,503✔
331
              (char*) this->lss_token_al.al_string.c_str(),
332
              this->lss_token_al.al_string.size());
333
    format->annotate(this->lss_token_file.get(),
9,006✔
334
                     line,
335
                     this->lss_token_al.al_attrs,
4,503✔
336
                     this->lss_token_values);
4,503✔
337

338
    for (const auto& hl : format->lf_highlighters) {
5,530✔
339
        auto hl_range = line_range{0, -1};
1,027✔
340
        auto value_iter = this->lss_token_values.lvv_values.end();
1,027✔
341
        if (!hl.h_field.empty()) {
1,027✔
342
            value_iter = std::find_if(this->lss_token_values.lvv_values.begin(),
215✔
343
                                      this->lss_token_values.lvv_values.end(),
344
                                      logline_value_name_cmp(&hl.h_field));
345
            if (value_iter == this->lss_token_values.lvv_values.end()) {
215✔
346
                continue;
204✔
347
            }
348
            hl_range = value_iter->lv_origin;
11✔
349
        }
350
        if (hl.annotate(this->lss_token_al, hl_range)
823✔
351
            && value_iter != this->lss_token_values.lvv_values.end())
823✔
352
        {
353
            value_iter->lv_highlighted = true;
7✔
354
        }
355
    }
356

357
    for (const auto& hl : this->lss_highlighters) {
4,520✔
358
        auto hl_range = line_range{0, -1};
17✔
359
        auto value_iter = this->lss_token_values.lvv_values.end();
17✔
360
        if (!hl.h_field.empty()) {
17✔
361
            value_iter = std::find_if(this->lss_token_values.lvv_values.begin(),
17✔
362
                                      this->lss_token_values.lvv_values.end(),
363
                                      logline_value_name_cmp(&hl.h_field));
364
            if (value_iter == this->lss_token_values.lvv_values.end()) {
17✔
365
                continue;
3✔
366
            }
367
            hl_range = value_iter->lv_origin;
14✔
368
        }
369
        if (hl.annotate(this->lss_token_al, hl_range)
14✔
370
            && value_iter != this->lss_token_values.lvv_values.end())
14✔
371
        {
372
            value_iter->lv_highlighted = true;
9✔
373
        }
374
    }
375

376
    if (flags & RF_REWRITE) {
4,503✔
377
        exec_context ec(
378
            &this->lss_token_values, pretty_sql_callback, pretty_pipe_callback);
48✔
379
        std::string rewritten_line;
48✔
380
        db_label_source rewrite_label_source;
48✔
381

382
        ec.with_perms(exec_context::perm_t::READ_ONLY);
48✔
383
        ec.ec_local_vars.push(std::map<std::string, scoped_value_t>());
48✔
384
        ec.ec_top_line = vis_line_t(row);
48✔
385
        ec.ec_label_source_stack.push_back(&rewrite_label_source);
48✔
386
        add_ansi_vars(ec.ec_global_vars);
48✔
387
        add_global_vars(ec);
48✔
388
        format->rewrite(ec, sbr, this->lss_token_al.al_attrs, rewritten_line);
48✔
389
        this->lss_token_al.al_string.assign(rewritten_line);
48✔
390
        value_out = this->lss_token_al.al_string;
48✔
391
    }
48✔
392

393
    {
394
        auto lr = line_range{0, (int) this->lss_token_al.al_string.length()};
4,503✔
395
        this->lss_token_al.al_attrs.emplace_back(lr, SA_ORIGINAL_LINE.value());
4,503✔
396
    }
397

398
    // Replace VALUE_TIMESTAMP fields right-to-left so that origins
399
    // for earlier fields remain valid.
400
    if (format->lf_date_time.dts_fmt_lock != -1) {
4,503✔
401
        auto* fmt_ptr = format.get();
4,430✔
402

403
        for (auto lv_iter = this->lss_token_values.lvv_values.rbegin();
4,430✔
404
             lv_iter != this->lss_token_values.lvv_values.rend();
46,130✔
405
             ++lv_iter)
41,700✔
406
        {
407
            if (lv_iter->lv_meta.lvm_kind != value_kind_t::VALUE_TIMESTAMP
41,700✔
408
                || !lv_iter->lv_origin.is_valid())
41,700✔
409
            {
410
                continue;
41,049✔
411
            }
412

413
            auto dts = fmt_ptr->build_time_scanner();
651✔
414
            exttm tm;
651✔
415
            timeval tv;
416
            auto val_sf
417
                = string_fragment::from_str_range(value_out,
1,302✔
418
                                                  lv_iter->lv_origin.lr_start,
651✔
419
                                                  lv_iter->lv_origin.lr_end);
651✔
420

421
            if (dts.scan(val_sf.data(),
1,302✔
422
                         val_sf.length(),
651✔
423
                         fmt_ptr->get_timestamp_formats(),
424
                         &tm,
425
                         tv,
426
                         true))
427
            {
428
                char ts[64];
429
                tm.et_gmtoff = tm.et_orig_gmtoff;
20✔
430
                auto len = dts.ftime(
20✔
431
                    ts, sizeof(ts), fmt_ptr->get_timestamp_formats(), tm);
432
                ts[len] = '\0';
20✔
433
                value_out.replace(lv_iter->lv_origin.lr_start,
20✔
434
                                  lv_iter->lv_origin.length(),
20✔
435
                                  ts,
436
                                  len);
437
                auto shift = (int) (len - lv_iter->lv_origin.length());
20✔
438
                if (shift != 0) {
20✔
439
                    this->lss_token_shifts.emplace_back(
4✔
440
                        lv_iter->lv_origin.lr_start, shift);
2✔
441
                }
442
            }
443
        }
444
    }
445

446
    std::optional<exttm> adjusted_tm;
4,503✔
447
    auto time_attr
448
        = find_string_attr(this->lss_token_al.al_attrs, &L_TIMESTAMP);
4,503✔
449
    if (!this->lss_token_line->is_continued() && !format->lf_formatted_lines
7,113✔
450
        && (this->lss_token_file->is_time_adjusted()
2,365✔
451
            || ((format->lf_timestamp_flags & ETF_ZONE_SET
2,350✔
452
                 || format->lf_date_time.dts_default_zone != nullptr)
1,428✔
453
                && format->lf_date_time.dts_zoned_to_local)
1,006✔
454
            || format->lf_timestamp_flags & ETF_MACHINE_ORIENTED
1,344✔
455
            || !(format->lf_timestamp_flags & ETF_DAY_SET)
1,344✔
456
            || !(format->lf_timestamp_flags & ETF_MONTH_SET))
1,323✔
457
        && format->lf_date_time.dts_fmt_lock != -1)
7,113✔
458
    {
459
        if (time_attr != this->lss_token_al.al_attrs.end()) {
987✔
460
            const auto time_range = time_attr->sa_range;
986✔
461
            const auto time_sf
462
                = string_fragment::from_str_range(this->lss_token_al.al_string,
986✔
463
                                                  time_range.lr_start,
986✔
464
                                                  time_range.lr_end);
986✔
465
            adjusted_tm = format->tm_for_display(this->lss_token_line, time_sf);
986✔
466

467
            char buffer[128];
468
            const char* fmt;
469
            ssize_t len;
470

471
            if (format->lf_timestamp_flags & ETF_MACHINE_ORIENTED
986✔
472
                || !(format->lf_timestamp_flags & ETF_DAY_SET)
352✔
473
                || !(format->lf_timestamp_flags & ETF_MONTH_SET))
1,338✔
474
            {
475
                if (format->lf_timestamp_flags & ETF_NANOS_SET) {
640✔
476
                    fmt = "%Y-%m-%d %H:%M:%S.%N";
×
477
                } else if (format->lf_timestamp_flags & ETF_MICROS_SET) {
640✔
478
                    fmt = "%Y-%m-%d %H:%M:%S.%f";
635✔
479
                } else if (format->lf_timestamp_flags & ETF_MILLIS_SET) {
5✔
480
                    fmt = "%Y-%m-%d %H:%M:%S.%L";
2✔
481
                } else {
482
                    fmt = "%Y-%m-%d %H:%M:%S";
3✔
483
                }
484
                len = ftime_fmt(
640✔
485
                    buffer, sizeof(buffer), fmt, adjusted_tm.value());
640✔
486
            } else {
487
                len = format->lf_date_time.ftime(
692✔
488
                    buffer,
489
                    sizeof(buffer),
490
                    format->get_timestamp_formats(),
491
                    adjusted_tm.value());
346✔
492
            }
493

494
            value_out.replace(
1,972✔
495
                time_range.lr_start, time_range.length(), buffer, len);
986✔
496
            this->lss_token_shifts.emplace_back(
1,972✔
497
                time_range.lr_start, (int) (len - time_range.length()));
986✔
498
        }
499
    }
500

501
    // Insert space for the file/search-hit markers.
502
    value_out.insert(0, 1, ' ');
4,503✔
503
    this->lss_time_column_size = 0;
4,503✔
504
    if (this->lss_line_context == line_context_t::time_column) {
4,503✔
505
        if (time_attr != this->lss_token_al.al_attrs.end()) {
×
506
            const char* fmt;
507
            if (this->lss_all_timestamp_flags
×
508
                & (ETF_MICROS_SET | ETF_NANOS_SET))
×
509
            {
510
                fmt = "%H:%M:%S.%f";
×
511
            } else if (this->lss_all_timestamp_flags & ETF_MILLIS_SET) {
×
512
                fmt = "%H:%M:%S.%L";
×
513
            } else {
514
                fmt = "%H:%M:%S";
×
515
            }
516
            if (!adjusted_tm) {
×
517
                const auto time_range = time_attr->sa_range;
×
518
                const auto time_sf = string_fragment::from_str_range(
×
519
                    this->lss_token_al.al_string,
×
520
                    time_range.lr_start,
×
521
                    time_range.lr_end);
×
522
                adjusted_tm
523
                    = format->tm_for_display(this->lss_token_line, time_sf);
×
524
            }
525
            adjusted_tm->et_flags |= this->lss_all_timestamp_flags
×
526
                & (ETF_MILLIS_SET | ETF_MICROS_SET | ETF_NANOS_SET);
×
527
            char buffer[128];
528
            this->lss_time_column_size
529
                = ftime_fmt(buffer, sizeof(buffer), fmt, adjusted_tm.value());
×
530
            if (this->tss_view->is_selectable()
×
531
                && this->tss_view->get_selection() == row)
×
532
            {
533
                buffer[this->lss_time_column_size] = ' ';
×
534
                buffer[this->lss_time_column_size + 1] = ' ';
×
535
                this->lss_time_column_size += 2;
×
536
            } else {
537
                constexpr char block[] = "\u258c ";
×
538

539
                strcpy(&buffer[this->lss_time_column_size], block);
×
540
                this->lss_time_column_size += sizeof(block) - 1;
×
541
            }
542
            if (time_attr->sa_range.lr_start != 0) {
×
543
                buffer[this->lss_time_column_size] = ' ';
×
544
                this->lss_time_column_size += 1;
×
545
                this->lss_time_column_padding = 1;
×
546
            } else {
547
                this->lss_time_column_padding = 0;
×
548
            }
549
            value_out.insert(1, buffer, this->lss_time_column_size);
×
550
            this->lss_token_al.al_attrs.emplace_back(time_attr->sa_range,
×
551
                                                     SA_REPLACED.value());
×
552
        }
553
        if (format->lf_level_hideable) {
×
554
            auto level_attr
555
                = find_string_attr(this->lss_token_al.al_attrs, &L_LEVEL);
×
556
            if (level_attr != this->lss_token_al.al_attrs.end()) {
×
557
                this->lss_token_al.al_attrs.emplace_back(level_attr->sa_range,
×
558
                                                         SA_REPLACED.value());
×
559
            }
560
        }
561
    } else if (this->lss_line_context < line_context_t::none) {
4,503✔
562
        size_t file_offset_end;
563
        std::string name;
×
564
        if (this->lss_line_context == line_context_t::filename) {
×
565
            file_offset_end = this->lss_filename_width;
×
566
            name = fmt::to_string(this->lss_token_file->get_filename());
×
567
            if (file_offset_end < name.size()) {
×
568
                file_offset_end = name.size();
×
569
                this->lss_filename_width = name.size();
×
570
            }
571
        } else {
572
            file_offset_end = this->lss_basename_width;
×
573
            name = fmt::to_string(this->lss_token_file->get_unique_path());
×
574
            if (file_offset_end < name.size()) {
×
575
                file_offset_end = name.size();
×
576
                this->lss_basename_width = name.size();
×
577
            }
578
        }
579
        value_out.insert(0, file_offset_end - name.size(), ' ');
×
580
        value_out.insert(0, name);
×
581
    }
582

583
    if (this->tas_display_time_offset) {
4,503✔
584
        auto row_vl = vis_line_t(row);
210✔
585
        auto relstr = this->get_time_offset_for_line(tc, row_vl);
210✔
586
        value_out = fmt::format(FMT_STRING("{: >12}|{}"), relstr, value_out);
840✔
587
    }
210✔
588

589
    this->lss_in_value_for_line = false;
4,503✔
590

591
    return retval;
4,503✔
592
}
4,503✔
593

594
void
595
logfile_sub_source::text_attrs_for_line(textview_curses& lv,
4,503✔
596
                                        int row,
597
                                        string_attrs_t& value_out)
598
{
599
    if (this->lss_indexing_in_progress) {
4,503✔
600
        return;
×
601
    }
602

603
    auto& vc = view_colors::singleton();
4,503✔
604
    logline* next_line = nullptr;
4,503✔
605
    line_range lr;
4,503✔
606
    int time_offset_end = 0;
4,503✔
607
    text_attrs attrs;
4,503✔
608
    auto* format = this->lss_token_file->get_format_ptr();
4,503✔
609

610
    if ((row + 1) < (int) this->lss_filtered_index.size()) {
4,503✔
611
        next_line = this->find_line(this->at(vis_line_t(row + 1)));
4,299✔
612
    }
613

614
    if (next_line != nullptr
4,503✔
615
        && (day_num(next_line->get_time<std::chrono::seconds>().count())
8,802✔
616
            > day_num(this->lss_token_line->get_time<std::chrono::seconds>()
8,802✔
617
                          .count())))
618
    {
619
        attrs |= text_attrs::style::underline;
17✔
620
    }
621

622
    const auto& line_values = this->lss_token_values;
4,503✔
623

624
    lr.lr_start = 0;
4,503✔
625
    lr.lr_end = -1;
4,503✔
626
    this->lss_token_al.al_attrs.emplace_back(
4,503✔
627
        lr, SA_LEVEL.value(this->lss_token_line->get_msg_level()));
9,006✔
628

629
    lr.lr_start = time_offset_end;
4,503✔
630
    lr.lr_end = -1;
4,503✔
631

632
    if (!attrs.empty()) {
4,503✔
633
        this->lss_token_al.al_attrs.emplace_back(lr, VC_STYLE.value(attrs));
17✔
634
    }
635

636
    if (this->lss_token_line->get_msg_level() == log_level_t::LEVEL_INVALID) {
4,503✔
637
        for (auto& token_attr : this->lss_token_al.al_attrs) {
82✔
638
            if (token_attr.sa_type != &SA_INVALID) {
59✔
639
                continue;
52✔
640
            }
641

642
            this->lss_token_al.al_attrs.emplace_back(
14✔
643
                token_attr.sa_range, VC_ROLE.value(role_t::VCR_INVALID_MSG));
7✔
644
        }
645
    }
646

647
    for (const auto& line_value : line_values.lvv_values) {
46,325✔
648
        if ((!(this->lss_token_flags & RF_FULL)
93,513✔
649
             && line_value.lv_sub_offset
83,216✔
650
                 != this->lss_token_line->get_sub_offset())
41,608✔
651
            || !line_value.lv_origin.is_valid())
83,430✔
652
        {
653
            continue;
9,869✔
654
        }
655

656
        if (line_value.lv_meta.is_hidden()) {
31,953✔
657
            this->lss_token_al.al_attrs.emplace_back(
288✔
658
                line_value.lv_origin, SA_HIDDEN.value(ui_icon_t::hidden));
144✔
659
        }
660

661
        if (!line_value.lv_meta.lvm_identifier
82,135✔
662
            || !line_value.lv_origin.is_valid())
31,953✔
663
        {
664
            continue;
18,229✔
665
        }
666

667
        if (line_value.lv_highlighted) {
13,724✔
668
            continue;
7✔
669
        }
670

671
        this->lss_token_al.al_attrs.emplace_back(
27,434✔
672
            line_value.lv_origin, VC_ROLE.value(role_t::VCR_IDENTIFIER));
13,717✔
673
    }
674

675
    // Apply shifts right-to-left so positions remain valid.
676
    std::stable_sort(
4,503✔
677
        this->lss_token_shifts.begin(),
678
        this->lss_token_shifts.end(),
679
        [](const auto& a, const auto& b) { return a.first > b.first; });
2✔
680
    for (const auto& shift : this->lss_token_shifts) {
5,491✔
681
        shift_string_attrs(
988✔
682
            this->lss_token_al.al_attrs, shift.first + 1, shift.second);
988✔
683
    }
684

685
    shift_string_attrs(this->lss_token_al.al_attrs, 0, 1);
4,503✔
686

687
    lr.lr_start = 0;
4,503✔
688
    lr.lr_end = 1;
4,503✔
689
    {
690
        auto& bm = lv.get_bookmarks();
4,503✔
691
        const auto& bv = bm[&BM_FILES];
4,503✔
692
        bool is_first_for_file = bv.bv_tree.exists(vis_line_t(row));
4,503✔
693
        bool is_last_for_file = bv.bv_tree.exists(vis_line_t(row + 1));
4,503✔
694
        auto graph = NCACS_VLINE;
4,503✔
695
        if (is_first_for_file) {
4,503✔
696
            if (is_last_for_file) {
236✔
697
                graph = NCACS_HLINE;
8✔
698
            } else {
699
                graph = NCACS_ULCORNER;
228✔
700
            }
701
        } else if (is_last_for_file) {
4,267✔
702
            graph = NCACS_LLCORNER;
23✔
703
        }
704
        this->lss_token_al.al_attrs.emplace_back(lr, VC_GRAPHIC.value(graph));
4,503✔
705

706
        if (!(this->lss_token_flags & RF_FULL)) {
4,503✔
707
            const auto& bv_search = bm[&textview_curses::BM_SEARCH];
4,455✔
708

709
            if (bv_search.bv_tree.exists(vis_line_t(row))) {
4,455✔
710
                lr.lr_start = 0;
9✔
711
                lr.lr_end = 1;
9✔
712
                this->lss_token_al.al_attrs.emplace_back(
9✔
713
                    lr, VC_STYLE.value(text_attrs::with_reverse()));
18✔
714
            }
715
        }
716
    }
717

718
    this->lss_token_al.al_attrs.emplace_back(
4,503✔
719
        lr,
720
        VC_STYLE.value(
9,006✔
721
            vc.attrs_for_ident(this->lss_token_file->get_filename())));
9,006✔
722

723
    if (this->lss_line_context < line_context_t::none) {
4,503✔
724
        size_t file_offset_end
×
725
            = (this->lss_line_context == line_context_t::filename)
×
726
            ? this->lss_filename_width
×
727
            : this->lss_basename_width;
728

729
        shift_string_attrs(this->lss_token_al.al_attrs, 0, file_offset_end);
×
730

731
        lr.lr_start = 0;
×
732
        lr.lr_end = file_offset_end + 1;
×
733
        this->lss_token_al.al_attrs.emplace_back(
×
734
            lr,
735
            VC_STYLE.value(
×
736
                vc.attrs_for_ident(this->lss_token_file->get_filename())));
×
737
    } else if (this->lss_time_column_size > 0) {
4,503✔
738
        shift_string_attrs(
×
739
            this->lss_token_al.al_attrs, 1, this->lss_time_column_size);
×
740

741
        ui_icon_t icon;
742
        switch (this->lss_token_line->get_msg_level()) {
×
743
            case LEVEL_TRACE:
×
744
                icon = ui_icon_t::log_level_trace;
×
745
                break;
×
746
            case LEVEL_DEBUG:
×
747
            case LEVEL_DEBUG2:
748
            case LEVEL_DEBUG3:
749
            case LEVEL_DEBUG4:
750
            case LEVEL_DEBUG5:
751
                icon = ui_icon_t::log_level_debug;
×
752
                break;
×
753
            case LEVEL_INFO:
×
754
                icon = ui_icon_t::log_level_info;
×
755
                break;
×
756
            case LEVEL_STATS:
×
757
                icon = ui_icon_t::log_level_stats;
×
758
                break;
×
759
            case LEVEL_NOTICE:
×
760
                icon = ui_icon_t::log_level_notice;
×
761
                break;
×
762
            case LEVEL_WARNING:
×
763
                icon = ui_icon_t::log_level_warning;
×
764
                break;
×
765
            case LEVEL_ERROR:
×
766
                icon = ui_icon_t::log_level_error;
×
767
                break;
×
768
            case LEVEL_CRITICAL:
×
769
                icon = ui_icon_t::log_level_critical;
×
770
                break;
×
771
            case LEVEL_FATAL:
×
772
                icon = ui_icon_t::log_level_fatal;
×
773
                break;
×
774
            default:
×
775
                icon = ui_icon_t::hidden;
×
776
                break;
×
777
        }
778
        auto extra_space_size = this->lss_time_column_padding;
×
779
        lr.lr_start = 1 + this->lss_time_column_size - 1 - extra_space_size;
×
780
        lr.lr_end = 1 + this->lss_time_column_size - extra_space_size;
×
781
        this->lss_token_al.al_attrs.emplace_back(lr, VC_ICON.value(icon));
×
782
        if (this->tss_view->is_selectable()
×
783
            && this->tss_view->get_selection() != row)
×
784
        {
785
            lr.lr_start = 1;
×
786
            lr.lr_end = 1 + this->lss_time_column_size - 2 - extra_space_size;
×
787
            this->lss_token_al.al_attrs.emplace_back(
×
788
                lr, VC_ROLE.value(role_t::VCR_TIME_COLUMN));
×
789
            if (this->lss_token_line->is_time_skewed()) {
×
790
                this->lss_token_al.al_attrs.emplace_back(
×
791
                    lr, VC_ROLE.value(role_t::VCR_SKEWED_TIME));
×
792
            }
793
            lr.lr_start = 1 + this->lss_time_column_size - 2 - extra_space_size;
×
794
            lr.lr_end = 1 + this->lss_time_column_size - 1 - extra_space_size;
×
795
            this->lss_token_al.al_attrs.emplace_back(
×
796
                lr, VC_ROLE.value(role_t::VCR_TIME_COLUMN_TO_TEXT));
×
797
        }
798
    }
799

800
    if (this->tas_display_time_offset) {
4,503✔
801
        time_offset_end = 13;
210✔
802
        lr.lr_start = 0;
210✔
803
        lr.lr_end = time_offset_end;
210✔
804

805
        shift_string_attrs(this->lss_token_al.al_attrs, 0, time_offset_end);
210✔
806

807
        this->lss_token_al.al_attrs.emplace_back(
210✔
808
            lr, VC_ROLE.value(role_t::VCR_OFFSET_TIME));
420✔
809
        this->lss_token_al.al_attrs.emplace_back(line_range(12, 13),
210✔
810
                                                 VC_GRAPHIC.value(NCACS_VLINE));
420✔
811

812
        auto bar_role = role_t::VCR_NONE;
210✔
813

814
        switch (this->get_line_accel_direction(vis_line_t(row))) {
210✔
815
            case log_accel::direction_t::A_STEADY:
126✔
816
                break;
126✔
817
            case log_accel::direction_t::A_DECEL:
42✔
818
                bar_role = role_t::VCR_DIFF_DELETE;
42✔
819
                break;
42✔
820
            case log_accel::direction_t::A_ACCEL:
42✔
821
                bar_role = role_t::VCR_DIFF_ADD;
42✔
822
                break;
42✔
823
        }
824
        if (bar_role != role_t::VCR_NONE) {
210✔
825
            this->lss_token_al.al_attrs.emplace_back(line_range(12, 13),
84✔
826
                                                     VC_ROLE.value(bar_role));
168✔
827
        }
828
    }
829

830
    lr.lr_start = 0;
4,503✔
831
    lr.lr_end = -1;
4,503✔
832
    this->lss_token_al.al_attrs.emplace_back(
4,503✔
833
        lr, L_FILE.value(this->lss_token_file));
9,006✔
834
    this->lss_token_al.al_attrs.emplace_back(
4,503✔
835
        lr, SA_FORMAT.value(format->get_name()));
9,006✔
836

837
    {
838
        auto line_meta_context = this->get_bookmark_metadata_context(
4,503✔
839
            vis_line_t(row + 1), bookmark_metadata::categories::partition);
840
        if (line_meta_context.bmc_current_metadata) {
4,503✔
841
            lr.lr_start = 0;
9✔
842
            lr.lr_end = -1;
9✔
843
            this->lss_token_al.al_attrs.emplace_back(
9✔
844
                lr,
845
                L_PARTITION.value(
18✔
846
                    line_meta_context.bmc_current_metadata.value()));
847
        }
848

849
        auto line_meta_opt = this->find_bookmark_metadata(vis_line_t(row));
4,503✔
850

851
        if (line_meta_opt) {
4,503✔
852
            lr.lr_start = 0;
26✔
853
            lr.lr_end = -1;
26✔
854
            this->lss_token_al.al_attrs.emplace_back(
26✔
855
                lr, L_META.value(line_meta_opt.value()));
52✔
856
        }
857
    }
858

859
    auto src_file_attr
860
        = get_string_attr(this->lss_token_al.al_attrs, SA_SRC_FILE);
4,503✔
861
    if (src_file_attr) {
4,503✔
862
        auto lr = src_file_attr->saw_string_attr->sa_range;
34✔
863
        lr.lr_end = lr.lr_start + 1;
34✔
864
        this->lss_token_al.al_attrs.emplace_back(
34✔
865
            lr, VC_STYLE.value(text_attrs::with_underline()));
68✔
866
        this->lss_token_al.al_attrs.emplace_back(lr,
34✔
867
                                                 VC_COMMAND.value(ui_command{
136✔
868
                                                     source_location{},
869
                                                     ":toggle-breakpoint",
870
                                                 }));
871
    }
872

873
    if (this->lss_time_column_size == 0) {
4,503✔
874
        if (this->lss_token_file->is_time_adjusted()) {
4,503✔
875
            auto time_range = find_string_attr_range(
17✔
876
                this->lss_token_al.al_attrs, &L_TIMESTAMP);
17✔
877

878
            if (time_range.lr_end != -1) {
17✔
879
                this->lss_token_al.al_attrs.emplace_back(
15✔
880
                    time_range, VC_ROLE.value(role_t::VCR_ADJUSTED_TIME));
30✔
881
            }
882
        } else if (this->lss_token_line->is_time_skewed()) {
4,486✔
883
            auto time_range = find_string_attr_range(
8✔
884
                this->lss_token_al.al_attrs, &L_TIMESTAMP);
8✔
885

886
            if (time_range.lr_end != -1) {
8✔
887
                this->lss_token_al.al_attrs.emplace_back(
8✔
888
                    time_range, VC_ROLE.value(role_t::VCR_SKEWED_TIME));
16✔
889
            }
890
        }
891
    }
892

893
    if (this->tss_preview_min_log_level
4,503✔
894
        && this->lss_token_line->get_msg_level()
4,503✔
895
            < this->tss_preview_min_log_level)
4,503✔
896
    {
897
        auto color = styling::color_unit::from_palette(
×
898
            lnav::enums::to_underlying(ansi_color::red));
×
899
        this->lss_token_al.al_attrs.emplace_back(line_range{0, 1},
×
900
                                                 VC_BACKGROUND.value(color));
×
901
    }
902
    if (this->ttt_preview_min_time
4,503✔
903
        && this->lss_token_line->get_timeval() < this->ttt_preview_min_time)
4,503✔
904
    {
905
        auto color = styling::color_unit::from_palette(
×
906
            lnav::enums::to_underlying(ansi_color::red));
×
907
        this->lss_token_al.al_attrs.emplace_back(line_range{0, 1},
×
908
                                                 VC_BACKGROUND.value(color));
×
909
    }
910
    if (this->ttt_preview_max_time
4,503✔
911
        && this->ttt_preview_max_time < this->lss_token_line->get_timeval())
4,503✔
912
    {
913
        auto color = styling::color_unit::from_palette(
×
914
            lnav::enums::to_underlying(ansi_color::red));
×
915
        this->lss_token_al.al_attrs.emplace_back(line_range{0, 1},
×
916
                                                 VC_BACKGROUND.value(color));
×
917
    }
918
    if (!this->lss_token_line->is_continued()) {
4,503✔
919
        if (this->lss_preview_filter_stmt != nullptr) {
2,610✔
920
            auto color = styling::color_unit::EMPTY;
×
921
            auto eval_res
922
                = this->eval_sql_filter(this->lss_preview_filter_stmt.in(),
923
                                        this->lss_token_file_data,
924
                                        this->lss_token_line);
×
925
            if (eval_res.isErr()) {
×
926
                color = palette_color{
×
927
                    lnav::enums::to_underlying(ansi_color::yellow)};
×
928
                this->lss_token_al.al_attrs.emplace_back(
×
929
                    line_range{0, -1},
×
930
                    SA_ERROR.value(
×
931
                        eval_res.unwrapErr().to_attr_line().get_string()));
×
932
            } else {
933
                auto matched = eval_res.unwrap();
×
934

935
                if (matched) {
×
936
                    color = palette_color{
×
937
                        lnav::enums::to_underlying(ansi_color::green)};
×
938
                } else {
939
                    color = palette_color{
×
940
                        lnav::enums::to_underlying(ansi_color::red)};
×
941
                    this->lss_token_al.al_attrs.emplace_back(
×
942
                        line_range{0, 1},
×
943
                        VC_STYLE.value(text_attrs::with_blink()));
×
944
                }
945
            }
946
            this->lss_token_al.al_attrs.emplace_back(
×
947
                line_range{0, 1}, VC_BACKGROUND.value(color));
×
948
        }
949

950
        auto sql_filter_opt = this->get_sql_filter();
2,610✔
951
        if (sql_filter_opt) {
2,610✔
952
            auto* sf = (sql_filter*) sql_filter_opt.value().get();
36✔
953
            auto eval_res = this->eval_sql_filter(sf->sf_filter_stmt.in(),
954
                                                  this->lss_token_file_data,
955
                                                  this->lss_token_line);
36✔
956
            if (eval_res.isErr()) {
36✔
957
                auto msg = fmt::format(
958
                    FMT_STRING(
×
959
                        "filter expression evaluation failed with -- {}"),
960
                    eval_res.unwrapErr().to_attr_line().get_string());
×
961
                auto cu = styling::color_unit::from_palette(palette_color{
×
962
                    lnav::enums::to_underlying(ansi_color::yellow)});
963
                this->lss_token_al.al_attrs.emplace_back(line_range{0, -1},
×
964
                                                         SA_ERROR.value(msg));
×
965
                this->lss_token_al.al_attrs.emplace_back(
×
966
                    line_range{0, 1}, VC_BACKGROUND.value(cu));
×
967
            }
968
        }
36✔
969
    }
2,610✔
970

971
    value_out = std::move(this->lss_token_al.al_attrs);
4,503✔
972
}
973

974
struct logline_cmp {
975
    explicit logline_cmp(logfile_sub_source& lc) : llss_controller(lc) {}
1,208✔
976

977
    bool operator()(const logfile_sub_source::indexed_content& lhs,
106,522✔
978
                    const logfile_sub_source::indexed_content& rhs) const
979
    {
980
        const auto* ll_lhs = this->llss_controller.find_line(lhs.value());
106,522✔
981
        const auto* ll_rhs = this->llss_controller.find_line(rhs.value());
106,522✔
982

983
        return (*ll_lhs) < (*ll_rhs);
106,522✔
984
    }
985

986
    bool operator()(const logfile_sub_source::indexed_content& lhs,
×
987
                    const timeval& rhs) const
988
    {
989
        const auto* ll_lhs = this->llss_controller.find_line(lhs.value());
×
990

991
        return *ll_lhs < rhs;
×
992
    }
993

994
    logfile_sub_source& llss_controller;
995
};
996

997
logfile_sub_source::rebuild_result
998
logfile_sub_source::rebuild_index(std::optional<ui_clock::time_point> deadline)
4,729✔
999
{
1000
    if (this->tss_view == nullptr) {
4,729✔
1001
        return rebuild_result::rr_no_change;
124✔
1002
    }
1003

1004
    this->lss_indexing_in_progress = true;
4,605✔
1005
    auto fin = finally([this]() { this->lss_indexing_in_progress = false; });
4,605✔
1006

1007
    iterator iter;
4,605✔
1008
    size_t total_lines = 0;
4,605✔
1009
    size_t est_remaining_lines = 0;
4,605✔
1010
    auto all_time_ordered_formats = true;
4,605✔
1011
    auto full_sort = this->lss_index.empty();
4,605✔
1012
    int file_count = 0;
4,605✔
1013
    auto force = std::exchange(this->lss_force_rebuild, false);
4,605✔
1014
    auto retval = rebuild_result::rr_no_change;
4,605✔
1015
    std::optional<timeval> lowest_tv = std::nullopt;
4,605✔
1016
    auto search_start = 0_vl;
4,605✔
1017

1018
    if (force) {
4,605✔
1019
        log_debug("forced to full rebuild");
510✔
1020
        retval = rebuild_result::rr_full_rebuild;
510✔
1021
        full_sort = true;
510✔
1022
        this->tss_level_filtered_count = 0;
510✔
1023
        this->lss_index.clear();
510✔
1024
    }
1025

1026
    std::vector<size_t> file_order(this->lss_files.size());
4,605✔
1027

1028
    for (size_t lpc = 0; lpc < file_order.size(); lpc++) {
7,798✔
1029
        file_order[lpc] = lpc;
3,193✔
1030
    }
1031
    if (!this->lss_index.empty()) {
4,605✔
1032
        std::stable_sort(file_order.begin(),
2,402✔
1033
                         file_order.end(),
1034
                         [this](const auto& left, const auto& right) {
255✔
1035
                             const auto& left_ld = this->lss_files[left];
255✔
1036
                             const auto& right_ld = this->lss_files[right];
255✔
1037

1038
                             if (left_ld->get_file_ptr() == nullptr) {
255✔
1039
                                 return true;
×
1040
                             }
1041
                             if (right_ld->get_file_ptr() == nullptr) {
255✔
1042
                                 return false;
3✔
1043
                             }
1044

1045
                             return left_ld->get_file_ptr()->back()
252✔
1046
                                 < right_ld->get_file_ptr()->back();
504✔
1047
                         });
1048
    }
1049

1050
    bool time_left = true;
4,605✔
1051
    this->lss_all_timestamp_flags = 0;
4,605✔
1052
    for (const auto file_index : file_order) {
7,798✔
1053
        auto& ld = *(this->lss_files[file_index]);
3,193✔
1054
        auto* lf = ld.get_file_ptr();
3,193✔
1055

1056
        if (lf == nullptr) {
3,193✔
1057
            if (ld.ld_lines_indexed > 0) {
4✔
1058
                log_debug("%zu: file closed, doing full rebuild",
1✔
1059
                          ld.ld_file_index);
1060
                force = true;
1✔
1061
                retval = rebuild_result::rr_full_rebuild;
1✔
1062
                full_sort = true;
1✔
1063
            }
1064
        } else {
1065
            if (!lf->get_format_ptr()->lf_time_ordered) {
3,189✔
1066
                all_time_ordered_formats = false;
183✔
1067
            }
1068
            if (time_left && deadline && ui_clock::now() > deadline.value()) {
3,189✔
1069
                log_debug("no time left, skipping %s",
2✔
1070
                          lf->get_filename_as_string().c_str());
1071
                time_left = false;
2✔
1072
            }
1073
            this->lss_all_timestamp_flags
3,189✔
1074
                |= lf->get_format_ptr()->lf_timestamp_flags;
3,189✔
1075

1076
            if (!this->tss_view->is_paused() && time_left) {
3,189✔
1077
                auto log_rebuild_res = lf->rebuild_index(deadline);
3,187✔
1078

1079
                if (ld.ld_lines_indexed < lf->size()
3,187✔
1080
                    && log_rebuild_res
3,187✔
1081
                        == logfile::rebuild_result_t::NO_NEW_LINES)
1082
                {
1083
                    // This is a bit awkward... if the logfile indexing was
1084
                    // complete before being added to us, we need to adjust
1085
                    // the rebuild result to make it look like new lines
1086
                    // were added.
1087
                    log_rebuild_res = logfile::rebuild_result_t::NEW_LINES;
51✔
1088
                }
1089
                switch (log_rebuild_res) {
3,187✔
1090
                    case logfile::rebuild_result_t::NO_NEW_LINES:
2,613✔
1091
                        break;
2,613✔
1092
                    case logfile::rebuild_result_t::NEW_LINES:
515✔
1093
                        if (retval == rebuild_result::rr_no_change) {
515✔
1094
                            retval = rebuild_result::rr_appended_lines;
464✔
1095
                        }
1096
                        log_debug("new lines for %s:%zu",
515✔
1097
                                  lf->get_filename_as_string().c_str(),
1098
                                  lf->size());
1099
                        if (!this->lss_index.empty()
515✔
1100
                            && lf->size() > ld.ld_lines_indexed)
515✔
1101
                        {
1102
                            auto& new_file_line = (*lf)[ld.ld_lines_indexed];
×
1103
                            auto cl = this->lss_index.back().value();
×
1104
                            auto* last_indexed_line = this->find_line(cl);
×
1105

1106
                            // If there are new lines that are older than what
1107
                            // we have in the index, we need to resort.
1108
                            if (last_indexed_line == nullptr
×
1109
                                || new_file_line
×
1110
                                    < last_indexed_line->get_timeval())
×
1111
                            {
1112
                                log_debug(
×
1113
                                    "%s:%ld: found older lines, full "
1114
                                    "rebuild: %p  %lld < %lld",
1115
                                    lf->get_filename().c_str(),
1116
                                    ld.ld_lines_indexed,
1117
                                    last_indexed_line,
1118
                                    new_file_line
1119
                                        .get_time<std::chrono::microseconds>()
1120
                                        .count(),
1121
                                    last_indexed_line == nullptr
1122
                                        ? (uint64_t) -1
1123
                                        : last_indexed_line
1124
                                              ->get_time<
1125
                                                  std::chrono::microseconds>()
1126
                                              .count());
1127
                                if (retval <= rebuild_result::rr_partial_rebuild
×
1128
                                    && all_time_ordered_formats)
×
1129
                                {
1130
                                    retval = rebuild_result::rr_partial_rebuild;
×
1131
                                    if (!lowest_tv
×
1132
                                        || new_file_line.get_timeval()
×
1133
                                            < lowest_tv.value())
×
1134
                                    {
1135
                                        lowest_tv = new_file_line.get_timeval();
×
1136
                                    }
1137
                                } else {
1138
                                    log_debug(
×
1139
                                        "already doing full rebuild, doing "
1140
                                        "full_sort as well");
1141
                                    force = true;
×
1142
                                    full_sort = true;
×
1143
                                }
1144
                            }
1145
                        }
1146
                        break;
515✔
1147
                    case logfile::rebuild_result_t::INVALID:
59✔
1148
                    case logfile::rebuild_result_t::NEW_ORDER:
1149
                        log_debug("%s: log file has a new order, full rebuild",
59✔
1150
                                  lf->get_filename().c_str());
1151
                        retval = rebuild_result::rr_full_rebuild;
59✔
1152
                        force = true;
59✔
1153
                        full_sort = true;
59✔
1154
                        break;
59✔
1155
                }
1156
            }
1157
            file_count += 1;
3,189✔
1158
            total_lines += lf->size();
3,189✔
1159

1160
            est_remaining_lines += lf->estimated_remaining_lines();
3,189✔
1161
        }
1162
    }
1163

1164
    if (!all_time_ordered_formats
4,605✔
1165
        && retval == rebuild_result::rr_partial_rebuild)
177✔
1166
    {
1167
        force = true;
×
1168
        full_sort = true;
×
1169
        retval = rebuild_result::rr_full_rebuild;
×
1170
    }
1171

1172
    if (this->lss_index.reserve(total_lines + est_remaining_lines)) {
4,605✔
1173
        // The index array was reallocated, just do a full sort/rebuild since
1174
        // it's been cleared out.
1175
        log_debug("expanding index capacity %zu", this->lss_index.ba_capacity);
671✔
1176
        force = true;
671✔
1177
        retval = rebuild_result::rr_full_rebuild;
671✔
1178
        full_sort = true;
671✔
1179
        this->tss_level_filtered_count = 0;
671✔
1180
    }
1181

1182
    auto& vis_bm = this->tss_view->get_bookmarks();
4,605✔
1183

1184
    if (force) {
4,605✔
1185
        for (iter = this->lss_files.begin(); iter != this->lss_files.end();
1,764✔
1186
             iter++)
568✔
1187
        {
1188
            (*iter)->ld_lines_indexed = 0;
568✔
1189
        }
1190

1191
        this->lss_index.clear();
1,196✔
1192
        this->lss_filtered_index.clear();
1,196✔
1193
        this->tss_level_filtered_count = 0;
1,196✔
1194
        this->lss_longest_line = 0;
1,196✔
1195
        this->lss_basename_width = 0;
1,196✔
1196
        this->lss_filename_width = 0;
1,196✔
1197
        vis_bm[&textview_curses::BM_USER_EXPR].clear();
1,196✔
1198
        if (this->lss_index_delegate) {
1,196✔
1199
            this->lss_index_delegate->index_start(*this);
1,196✔
1200
        }
1201
    } else if (retval == rebuild_result::rr_partial_rebuild) {
3,409✔
1202
        size_t remaining = 0;
×
1203

1204
        log_debug("partial rebuild with lowest time: %ld",
×
1205
                  lowest_tv.value().tv_sec);
1206
        for (iter = this->lss_files.begin(); iter != this->lss_files.end();
×
1207
             iter++)
×
1208
        {
1209
            logfile_data& ld = *(*iter);
×
1210
            auto* lf = ld.get_file_ptr();
×
1211

1212
            if (lf == nullptr) {
×
1213
                continue;
×
1214
            }
1215

1216
            require(lf->get_format_ptr()->lf_time_ordered);
×
1217

1218
            auto line_iter = lf->find_from_time(lowest_tv.value());
×
1219

1220
            if (line_iter) {
×
1221
                log_debug("lowest line time %ld; line %ld; size %ld; path=%s",
×
1222
                          line_iter.value()->get_timeval().tv_sec,
1223
                          std::distance(lf->cbegin(), line_iter.value()),
1224
                          lf->size(),
1225
                          lf->get_filename_as_string().c_str());
1226
            }
1227
            ld.ld_lines_indexed
1228
                = std::distance(lf->cbegin(), line_iter.value_or(lf->cend()));
×
1229
            remaining += lf->size() - ld.ld_lines_indexed;
×
1230
        }
1231

1232
        auto* row_iter = std::lower_bound(this->lss_index.begin(),
×
1233
                                          this->lss_index.end(),
1234
                                          lowest_tv.value(),
×
1235
                                          logline_cmp(*this));
1236
        this->lss_index.shrink_to(
×
1237
            std::distance(this->lss_index.begin(), row_iter));
×
1238
        log_debug("new index size %ld/%ld; remain %ld",
×
1239
                  this->lss_index.ba_size,
1240
                  this->lss_index.ba_capacity,
1241
                  remaining);
1242
        auto filt_row_iter = std::lower_bound(this->lss_filtered_index.begin(),
×
1243
                                              this->lss_filtered_index.end(),
1244
                                              lowest_tv.value(),
×
1245
                                              filtered_logline_cmp(*this));
1246
        this->lss_filtered_index.resize(
×
1247
            std::distance(this->lss_filtered_index.begin(), filt_row_iter));
×
1248
        search_start = vis_line_t(this->lss_filtered_index.size());
×
1249

1250
        if (this->lss_index_delegate) {
×
1251
            this->lss_index_delegate->index_start(*this);
×
1252
            for (const auto row_in_full_index : this->lss_filtered_index) {
×
1253
                auto cl = this->lss_index[row_in_full_index].value();
×
1254
                uint64_t line_number;
1255
                auto ld_iter = this->find_data(cl, line_number);
×
1256
                auto& ld = *ld_iter;
×
1257
                auto line_iter = ld->get_file_ptr()->begin() + line_number;
×
1258

1259
                this->lss_index_delegate->index_line(
×
1260
                    *this, ld->get_file_ptr(), line_iter);
1261
            }
1262
        }
1263
    }
1264

1265
    if (this->lss_index.empty() && !time_left) {
4,605✔
1266
        log_info("ran out of time, skipping rebuild");
×
1267
        // need to make sure we rebuild in case no new data comes in
1268
        this->lss_force_rebuild = true;
×
1269
        return rebuild_result::rr_appended_lines;
×
1270
    }
1271

1272
    if (retval != rebuild_result::rr_no_change || force) {
4,605✔
1273
        size_t index_size = 0, start_size = this->lss_index.size();
1,208✔
1274
        logline_cmp line_cmper(*this);
1,208✔
1275

1276
        for (auto& ld : this->lss_files) {
1,792✔
1277
            auto* lf = ld->get_file_ptr();
584✔
1278

1279
            if (lf == nullptr) {
584✔
1280
                continue;
1✔
1281
            }
1282
            this->lss_longest_line = std::max(
1,166✔
1283
                this->lss_longest_line, lf->get_longest_line_length() + 1);
583✔
1284
            this->lss_basename_width
1285
                = std::max(this->lss_basename_width,
1,166✔
1286
                           lf->get_unique_path().native().size());
583✔
1287
            this->lss_filename_width = std::max(
1,166✔
1288
                this->lss_filename_width, lf->get_filename().native().size());
583✔
1289
        }
1290

1291
        if (full_sort) {
1,208✔
1292
            log_trace("rebuild_index full sort");
1,208✔
1293
            for (auto& ld : this->lss_files) {
1,792✔
1294
                auto* lf = ld->get_file_ptr();
584✔
1295

1296
                if (lf == nullptr) {
584✔
1297
                    continue;
1✔
1298
                }
1299

1300
                for (size_t line_index = 0; line_index < lf->size();
15,174✔
1301
                     line_index++)
1302
                {
1303
                    const auto lf_iter
1304
                        = ld->get_file_ptr()->begin() + line_index;
14,591✔
1305
                    if (lf_iter->is_ignored()) {
14,591✔
1306
                        continue;
414✔
1307
                    }
1308

1309
                    content_line_t con_line(
1310
                        ld->ld_file_index * MAX_LINES_PER_FILE + line_index);
14,177✔
1311

1312
                    if (lf_iter->is_meta_marked()) {
14,177✔
1313
                        auto start_iter = lf_iter;
12✔
1314
                        while (start_iter->is_continued()) {
12✔
1315
                            --start_iter;
×
1316
                        }
1317
                        int start_index
1318
                            = start_iter - ld->get_file_ptr()->begin();
12✔
1319
                        content_line_t start_con_line(ld->ld_file_index
12✔
1320
                                                          * MAX_LINES_PER_FILE
12✔
1321
                                                      + start_index);
12✔
1322

1323
                        auto& line_meta
1324
                            = ld->get_file_ptr()
1325
                                  ->get_bookmark_metadata()[start_index];
12✔
1326
                        if (line_meta.has(bookmark_metadata::categories::notes))
12✔
1327
                        {
1328
                            this->lss_user_marks[&textview_curses::BM_META]
4✔
1329
                                .insert_once(start_con_line);
4✔
1330
                        }
1331
                        if (line_meta.has(
12✔
1332
                                bookmark_metadata::categories::partition))
1333
                        {
1334
                            this->lss_user_marks[&textview_curses::BM_PARTITION]
8✔
1335
                                .insert_once(start_con_line);
8✔
1336
                        }
1337
                    }
1338
                    this->lss_index.push_back(
14,177✔
1339
                        indexed_content{con_line, lf_iter});
28,354✔
1340
                }
1341
            }
1342

1343
            // XXX get rid of this full sort on the initial run, it's not
1344
            // needed unless the file is not in time-order
1345
            if (this->lss_sorting_observer) {
1,208✔
1346
                this->lss_sorting_observer(*this, 0, this->lss_index.size());
3✔
1347
            }
1348
            std::sort(
1,208✔
1349
                this->lss_index.begin(), this->lss_index.end(), line_cmper);
1350
            if (this->lss_sorting_observer) {
1,208✔
1351
                this->lss_sorting_observer(
6✔
1352
                    *this, this->lss_index.size(), this->lss_index.size());
3✔
1353
            }
1354
        } else {
1355
            kmerge_tree_c<logline, logfile_data, logfile::iterator> merge(
1356
                file_count);
×
1357

1358
            for (iter = this->lss_files.begin(); iter != this->lss_files.end();
×
1359
                 iter++)
×
1360
            {
1361
                auto* ld = iter->get();
×
1362
                auto* lf = ld->get_file_ptr();
×
1363
                if (lf == nullptr) {
×
1364
                    continue;
×
1365
                }
1366

1367
                merge.add(ld, lf->begin() + ld->ld_lines_indexed, lf->end());
×
1368
                index_size += lf->size();
×
1369
            }
1370

1371
            file_off_t index_off = 0;
×
1372
            merge.execute();
×
1373
            if (this->lss_sorting_observer) {
×
1374
                this->lss_sorting_observer(*this, index_off, index_size);
×
1375
            }
1376
            log_trace("k-way merge");
×
1377
            for (;;) {
1378
                logfile::iterator lf_iter;
×
1379
                logfile_data* ld;
1380

1381
                if (!merge.get_top(ld, lf_iter)) {
×
1382
                    break;
×
1383
                }
1384

1385
                if (!lf_iter->is_ignored()) {
×
1386
                    int file_index = ld->ld_file_index;
×
1387
                    int line_index = lf_iter - ld->get_file_ptr()->begin();
×
1388

1389
                    content_line_t con_line(file_index * MAX_LINES_PER_FILE
×
1390
                                            + line_index);
×
1391

1392
                    if (lf_iter->is_meta_marked()) {
×
1393
                        auto start_iter = lf_iter;
×
1394
                        while (start_iter->is_continued()) {
×
1395
                            --start_iter;
×
1396
                        }
1397
                        int start_index
1398
                            = start_iter - ld->get_file_ptr()->begin();
×
1399
                        content_line_t start_con_line(
1400
                            file_index * MAX_LINES_PER_FILE + start_index);
×
1401

1402
                        auto& line_meta
1403
                            = ld->get_file_ptr()
1404
                                  ->get_bookmark_metadata()[start_index];
×
1405
                        if (line_meta.has(bookmark_metadata::categories::notes))
×
1406
                        {
1407
                            this->lss_user_marks[&textview_curses::BM_META]
×
1408
                                .insert_once(start_con_line);
×
1409
                        }
1410
                        if (line_meta.has(
×
1411
                                bookmark_metadata::categories::partition))
1412
                        {
1413
                            this->lss_user_marks[&textview_curses::BM_PARTITION]
×
1414
                                .insert_once(start_con_line);
×
1415
                        }
1416
                    }
1417
                    this->lss_index.push_back(
×
1418
                        indexed_content{con_line, lf_iter});
×
1419
                }
1420

1421
                merge.next();
×
1422
                index_off += 1;
×
1423
                if (index_off % 100000 == 0 && this->lss_sorting_observer) {
×
1424
                    this->lss_sorting_observer(*this, index_off, index_size);
×
1425
                }
1426
            }
1427
            if (this->lss_sorting_observer) {
×
1428
                this->lss_sorting_observer(*this, index_size, index_size);
×
1429
            }
1430
        }
1431

1432
        for (iter = this->lss_files.begin(); iter != this->lss_files.end();
1,792✔
1433
             ++iter)
584✔
1434
        {
1435
            auto* lf = (*iter)->get_file_ptr();
584✔
1436

1437
            if (lf == nullptr) {
584✔
1438
                continue;
1✔
1439
            }
1440

1441
            (*iter)->ld_lines_indexed = lf->size();
583✔
1442
        }
1443

1444
        this->lss_filtered_index.reserve(this->lss_index.size());
1,208✔
1445

1446
        uint32_t filter_in_mask, filter_out_mask;
1447
        this->get_filters().get_enabled_mask(filter_in_mask, filter_out_mask);
1,208✔
1448

1449
        if (start_size == 0 && this->lss_index_delegate != nullptr) {
1,208✔
1450
            this->lss_index_delegate->index_start(*this);
1,208✔
1451
        }
1452

1453
        log_trace("filtered index");
1,208✔
1454
        for (size_t index_index = start_size;
15,385✔
1455
             index_index < this->lss_index.size();
15,385✔
1456
             index_index++)
1457
        {
1458
            const auto cl = this->lss_index[index_index].value();
14,177✔
1459
            uint64_t line_number;
1460
            auto ld = this->find_data(cl, line_number);
14,177✔
1461

1462
            if (!(*ld)->is_visible()) {
14,177✔
1463
                continue;
×
1464
            }
1465

1466
            auto* lf = (*ld)->get_file_ptr();
14,177✔
1467
            auto line_iter = lf->begin() + line_number;
14,177✔
1468

1469
            if (line_iter->is_ignored()) {
14,177✔
1470
                continue;
×
1471
            }
1472

1473
            if (!this->tss_apply_filters
28,354✔
1474
                || (!(*ld)->ld_filter_state.excluded(
28,305✔
1475
                        filter_in_mask, filter_out_mask, line_number)
1476
                    && this->check_extra_filters(ld, line_iter)))
14,128✔
1477
            {
1478
                auto eval_res = this->eval_sql_filter(
1479
                    this->lss_marker_stmt.in(), ld, line_iter);
14,128✔
1480
                if (eval_res.isErr()) {
14,128✔
1481
                    line_iter->set_expr_mark(false);
×
1482
                } else {
1483
                    auto matched = eval_res.unwrap();
14,128✔
1484

1485
                    line_iter->set_expr_mark(matched);
14,128✔
1486
                    if (matched) {
14,128✔
1487
                        vis_bm[&textview_curses::BM_USER_EXPR].insert_once(
×
1488
                            vis_line_t(this->lss_filtered_index.size()));
×
1489
                        this->lss_user_marks[&textview_curses::BM_USER_EXPR]
×
1490
                            .insert_once(cl);
×
1491
                    }
1492
                }
1493
                this->lss_filtered_index.push_back(index_index);
14,128✔
1494
                if (this->lss_index_delegate != nullptr) {
14,128✔
1495
                    this->lss_index_delegate->index_line(*this, lf, line_iter);
14,128✔
1496
                }
1497
            }
14,128✔
1498
        }
1499

1500
        this->lss_indexing_in_progress = false;
1,208✔
1501

1502
        if (this->lss_index_delegate != nullptr) {
1,208✔
1503
            this->lss_index_delegate->index_complete(*this);
1,208✔
1504
        }
1505
    }
1506

1507
    switch (retval) {
4,605✔
1508
        case rebuild_result::rr_no_change:
3,397✔
1509
            break;
3,397✔
1510
        case rebuild_result::rr_full_rebuild:
1,196✔
1511
            log_debug("redoing search");
1,196✔
1512
            this->lss_index_generation += 1;
1,196✔
1513
            this->tss_view->reload_data();
1,196✔
1514
            this->tss_view->redo_search();
1,196✔
1515
            break;
1,196✔
1516
        case rebuild_result::rr_partial_rebuild:
×
1517
            log_debug("redoing search from: %d", (int) search_start);
×
1518
            this->lss_index_generation += 1;
×
1519
            this->tss_view->reload_data();
×
1520
            this->tss_view->search_new_data(search_start);
×
1521
            break;
×
1522
        case rebuild_result::rr_appended_lines:
12✔
1523
            this->tss_view->reload_data();
12✔
1524
            this->tss_view->search_new_data();
12✔
1525
            break;
12✔
1526
    }
1527

1528
    return retval;
4,605✔
1529
}
4,605✔
1530

1531
void
1532
logfile_sub_source::text_update_marks(vis_bookmarks& bm)
2,342✔
1533
{
1534
    logfile* last_file = nullptr;
2,342✔
1535
    vis_line_t vl;
2,342✔
1536

1537
    auto& bm_warnings = bm[&textview_curses::BM_WARNINGS];
2,342✔
1538
    auto& bm_errors = bm[&textview_curses::BM_ERRORS];
2,342✔
1539
    auto& bm_files = bm[&BM_FILES];
2,342✔
1540

1541
    bm_warnings.clear();
2,342✔
1542
    bm_errors.clear();
2,342✔
1543
    bm_files.clear();
2,342✔
1544

1545
    std::vector<const bookmark_type_t*> used_marks;
2,342✔
1546
    for (const auto* bmt :
14,052✔
1547
         {
1548
             &textview_curses::BM_USER,
1549
             &textview_curses::BM_USER_EXPR,
1550
             &textview_curses::BM_PARTITION,
1551
             &textview_curses::BM_META,
1552
             &textview_curses::BM_STICKY,
1553
         })
16,394✔
1554
    {
1555
        bm[bmt].clear();
11,710✔
1556
        if (!this->lss_user_marks[bmt].empty()) {
11,710✔
1557
            used_marks.emplace_back(bmt);
131✔
1558
        }
1559
    }
1560

1561
    for (; vl < (int) this->lss_filtered_index.size(); ++vl) {
20,166✔
1562
        const auto& orig_ic = this->lss_index[this->lss_filtered_index[vl]];
17,824✔
1563
        auto cl = orig_ic.value();
17,824✔
1564
        auto* lf = this->find_file_ptr(cl);
17,824✔
1565

1566
        for (const auto& bmt : used_marks) {
20,079✔
1567
            auto& user_mark = this->lss_user_marks[bmt];
2,255✔
1568
            if (user_mark.bv_tree.exists(orig_ic.value())) {
2,255✔
1569
                bm[bmt].insert_once(vl);
171✔
1570
            }
1571
        }
1572

1573
        if (lf != last_file) {
17,824✔
1574
            bm_files.insert_once(vl);
1,014✔
1575
        }
1576

1577
        switch (orig_ic.level()) {
17,824✔
1578
            case indexed_content::level_t::warning:
109✔
1579
                bm_warnings.insert_once(vl);
109✔
1580
                break;
109✔
1581

1582
            case indexed_content::level_t::error:
3,467✔
1583
                bm_errors.insert_once(vl);
3,467✔
1584
                break;
3,467✔
1585

1586
            default:
14,248✔
1587
                break;
14,248✔
1588
        }
1589

1590
        last_file = lf;
17,824✔
1591
    }
1592
}
2,342✔
1593

1594
void
1595
logfile_sub_source::text_filters_changed()
174✔
1596
{
1597
    this->lss_index_generation += 1;
174✔
1598
    this->tss_level_filtered_count = 0;
174✔
1599

1600
    if (this->lss_line_meta_changed) {
174✔
1601
        this->invalidate_sql_filter();
29✔
1602
        this->lss_line_meta_changed = false;
29✔
1603
    }
1604

1605
    for (auto& ld : *this) {
298✔
1606
        auto* lf = ld->get_file_ptr();
124✔
1607

1608
        if (lf != nullptr) {
124✔
1609
            ld->ld_filter_state.clear_deleted_filter_state();
124✔
1610
            lf->reobserve_from(lf->begin()
124✔
1611
                               + ld->ld_filter_state.get_min_count(lf->size()));
124✔
1612
        }
1613
    }
1614

1615
    if (this->lss_force_rebuild) {
174✔
1616
        return;
×
1617
    }
1618

1619
    auto& vis_bm = this->tss_view->get_bookmarks();
174✔
1620
    uint32_t filtered_in_mask, filtered_out_mask;
1621

1622
    this->get_filters().get_enabled_mask(filtered_in_mask, filtered_out_mask);
174✔
1623

1624
    if (this->lss_index_delegate != nullptr) {
174✔
1625
        this->lss_index_delegate->index_start(*this);
174✔
1626
    }
1627
    vis_bm[&textview_curses::BM_USER_EXPR].clear();
174✔
1628

1629
    this->lss_filtered_index.clear();
174✔
1630
    for (size_t index_index = 0; index_index < this->lss_index.size();
1,554✔
1631
         index_index++)
1632
    {
1633
        auto cl = this->lss_index[index_index].value();
1,380✔
1634
        uint64_t line_number;
1635
        auto ld = this->find_data(cl, line_number);
1,380✔
1636

1637
        if (!(*ld)->is_visible()) {
1,380✔
1638
            continue;
213✔
1639
        }
1640

1641
        auto lf = (*ld)->get_file_ptr();
1,167✔
1642
        auto line_iter = lf->begin() + line_number;
1,167✔
1643

1644
        if (!this->tss_apply_filters
2,334✔
1645
            || (!(*ld)->ld_filter_state.excluded(
2,174✔
1646
                    filtered_in_mask, filtered_out_mask, line_number)
1647
                && this->check_extra_filters(ld, line_iter)))
1,007✔
1648
        {
1649
            auto eval_res = this->eval_sql_filter(
1650
                this->lss_marker_stmt.in(), ld, line_iter);
965✔
1651
            if (eval_res.isErr()) {
965✔
1652
                line_iter->set_expr_mark(false);
×
1653
            } else {
1654
                auto matched = eval_res.unwrap();
965✔
1655

1656
                line_iter->set_expr_mark(matched);
965✔
1657
                if (matched) {
965✔
1658
                    vis_bm[&textview_curses::BM_USER_EXPR].insert_once(
×
1659
                        vis_line_t(this->lss_filtered_index.size()));
×
1660
                    this->lss_user_marks[&textview_curses::BM_USER_EXPR]
×
1661
                        .insert_once(cl);
×
1662
                }
1663
            }
1664
            this->lss_filtered_index.push_back(index_index);
965✔
1665
            if (this->lss_index_delegate != nullptr) {
965✔
1666
                this->lss_index_delegate->index_line(*this, lf, line_iter);
965✔
1667
            }
1668
        }
965✔
1669
    }
1670

1671
    if (this->lss_index_delegate != nullptr) {
174✔
1672
        this->lss_index_delegate->index_complete(*this);
174✔
1673
    }
1674

1675
    if (this->tss_view != nullptr) {
174✔
1676
        this->tss_view->reload_data();
174✔
1677
        this->tss_view->redo_search();
174✔
1678
    }
1679
}
1680

1681
std::optional<json_string>
1682
logfile_sub_source::text_row_details(const textview_curses& tc)
29✔
1683
{
1684
    if (this->lss_index.empty()) {
29✔
1685
        log_trace("logfile_sub_source::text_row_details empty");
1✔
1686
        return std::nullopt;
1✔
1687
    }
1688

1689
    auto ov_sel = tc.get_overlay_selection();
28✔
1690
    if (ov_sel.has_value()) {
28✔
1691
        auto* fos
1692
            = dynamic_cast<field_overlay_source*>(tc.get_overlay_source());
×
1693
        auto iter = fos->fos_row_to_field_meta.find(ov_sel.value());
×
1694
        if (iter != fos->fos_row_to_field_meta.end()) {
×
1695
            auto find_res = this->find_line_with_file(tc.get_top());
×
1696
            if (find_res) {
×
1697
                yajlpp_gen gen;
×
1698

1699
                {
1700
                    yajlpp_map root(gen);
×
1701

1702
                    root.gen("value");
×
1703
                    root.gen(iter->second.ri_value);
×
1704
                }
1705

1706
                return json_string(gen);
×
1707
            }
1708
        }
1709
    }
1710

1711
    return std::nullopt;
28✔
1712
}
1713

1714
bool
1715
logfile_sub_source::list_input_handle_key(listview_curses& lv,
×
1716
                                          const ncinput& ch)
1717
{
1718
    switch (ch.eff_text[0]) {
×
1719
        case ' ': {
×
1720
            auto ov_vl = lv.get_overlay_selection();
×
1721
            if (ov_vl) {
×
1722
                auto* fos = dynamic_cast<field_overlay_source*>(
×
1723
                    lv.get_overlay_source());
×
1724
                auto iter = fos->fos_row_to_field_meta.find(ov_vl.value());
×
1725
                if (iter != fos->fos_row_to_field_meta.end()
×
1726
                    && iter->second.ri_meta)
×
1727
                {
1728
                    auto find_res = this->find_line_with_file(lv.get_top());
×
1729
                    if (find_res) {
×
1730
                        auto file_and_line = find_res.value();
×
1731
                        auto* format = file_and_line.first->get_format_ptr();
×
1732
                        auto fstates = format->get_field_states();
×
1733
                        auto state_iter
1734
                            = fstates.find(iter->second.ri_meta->lvm_name);
×
1735
                        if (state_iter != fstates.end()) {
×
1736
                            format->hide_field(iter->second.ri_meta->lvm_name,
×
1737
                                               !state_iter->second.is_hidden());
×
1738
                            lv.set_needs_update();
×
1739
                        }
1740
                    }
1741
                }
1742
                return true;
×
1743
            }
1744
            return false;
×
1745
        }
1746
        case '#': {
×
1747
            auto ov_vl = lv.get_overlay_selection();
×
1748
            if (ov_vl) {
×
1749
                auto* fos = dynamic_cast<field_overlay_source*>(
×
1750
                    lv.get_overlay_source());
×
1751
                auto iter = fos->fos_row_to_field_meta.find(ov_vl.value());
×
1752
                if (iter != fos->fos_row_to_field_meta.end()
×
1753
                    && iter->second.ri_meta)
×
1754
                {
1755
                    const auto& meta = iter->second.ri_meta.value();
×
1756
                    std::string cmd;
×
1757

1758
                    switch (meta.to_chart_type()) {
×
1759
                        case chart_type_t::none:
×
1760
                            break;
×
1761
                        case chart_type_t::hist: {
×
1762
                            auto prql = fmt::format(
1763
                                FMT_STRING(
×
1764
                                    "from {} | stats.hist {} slice:'1h'"),
1765
                                meta.lvm_format.value()->get_name(),
×
1766
                                meta.lvm_name);
×
1767
                            cmd = fmt::format(FMT_STRING(":prompt sql ; '{}'"),
×
1768
                                              shlex::escape(prql));
×
1769
                            break;
×
1770
                        }
1771
                        case chart_type_t::spectro:
×
1772
                            cmd = fmt::format(FMT_STRING(":spectrogram {}"),
×
1773
                                              meta.lvm_name);
×
1774
                            break;
×
1775
                    }
1776
                    if (!cmd.empty()) {
×
1777
                        this->lss_exec_context
×
1778
                            ->with_provenance(exec_context::mouse_input{})
×
1779
                            ->execute(INTERNAL_SRC_LOC, cmd);
×
1780
                    }
1781
                }
1782
                return true;
×
1783
            }
1784
            return false;
×
1785
        }
1786
        case 'h':
×
1787
        case 'H':
1788
        case NCKEY_LEFT:
1789
            if (lv.get_left() == 0) {
×
1790
                this->increase_line_context();
×
1791
                lv.set_needs_update();
×
1792
                return true;
×
1793
            }
1794
            break;
×
1795
        case 'l':
×
1796
        case 'L':
1797
        case NCKEY_RIGHT:
1798
            if (this->decrease_line_context()) {
×
1799
                lv.set_needs_update();
×
1800
                return true;
×
1801
            }
1802
            break;
×
1803
    }
1804
    return false;
×
1805
}
1806

1807
std::optional<
1808
    std::pair<grep_proc_source<vis_line_t>*, grep_proc_sink<vis_line_t>*>>
1809
logfile_sub_source::get_grepper()
9✔
1810
{
1811
    return std::make_pair(
18✔
1812
        (grep_proc_source<vis_line_t>*) &this->lss_meta_grepper,
×
1813
        (grep_proc_sink<vis_line_t>*) &this->lss_meta_grepper);
9✔
1814
}
1815

1816
/**
1817
 * Functor for comparing the ld_file field of the logfile_data struct.
1818
 */
1819
struct logfile_data_eq {
1820
    explicit logfile_data_eq(std::shared_ptr<logfile> lf)
1,100✔
1821
        : lde_file(std::move(lf))
1,100✔
1822
    {
1823
    }
1,100✔
1824

1825
    bool operator()(
668✔
1826
        const std::unique_ptr<logfile_sub_source::logfile_data>& ld) const
1827
    {
1828
        return this->lde_file == ld->get_file();
668✔
1829
    }
1830

1831
    std::shared_ptr<logfile> lde_file;
1832
};
1833

1834
bool
1835
logfile_sub_source::insert_file(const std::shared_ptr<logfile>& lf)
550✔
1836
{
1837
    iterator existing;
550✔
1838

1839
    require_lt(lf->size(), MAX_LINES_PER_FILE);
550✔
1840

1841
    existing = std::find_if(this->lss_files.begin(),
550✔
1842
                            this->lss_files.end(),
1843
                            logfile_data_eq(nullptr));
1,100✔
1844
    if (existing == this->lss_files.end()) {
550✔
1845
        if (this->lss_files.size() >= MAX_FILES) {
550✔
1846
            return false;
×
1847
        }
1848

1849
        auto ld = std::make_unique<logfile_data>(
1850
            this->lss_files.size(), this->get_filters(), lf);
550✔
1851
        ld->set_visibility(lf->get_open_options().loo_is_visible);
550✔
1852
        this->lss_files.push_back(std::move(ld));
550✔
1853
    } else {
550✔
1854
        (*existing)->set_file(lf);
×
1855
    }
1856

1857
    return true;
550✔
1858
}
1859

1860
Result<void, lnav::console::user_message>
1861
logfile_sub_source::set_sql_filter(std::string stmt_str, sqlite3_stmt* stmt)
810✔
1862
{
1863
    if (stmt != nullptr && !this->lss_filtered_index.empty()) {
810✔
1864
        auto top_cl = this->at(0_vl);
8✔
1865
        auto ld = this->find_data(top_cl);
8✔
1866
        auto eval_res
1867
            = this->eval_sql_filter(stmt, ld, (*ld)->get_file_ptr()->begin());
8✔
1868

1869
        if (eval_res.isErr()) {
8✔
1870
            sqlite3_finalize(stmt);
1✔
1871
            return Err(eval_res.unwrapErr());
1✔
1872
        }
1873
    }
8✔
1874

1875
    for (auto& ld : *this) {
826✔
1876
        ld->ld_filter_state.lfo_filter_state.clear_filter_state(0);
17✔
1877
    }
1878

1879
    auto old_filter_iter = this->tss_filters.find(0);
809✔
1880
    if (stmt != nullptr) {
809✔
1881
        auto new_filter
1882
            = std::make_shared<sql_filter>(*this, std::move(stmt_str), stmt);
7✔
1883

1884
        if (old_filter_iter != this->tss_filters.end()) {
7✔
1885
            *old_filter_iter = new_filter;
×
1886
        } else {
1887
            this->tss_filters.add_filter(new_filter);
7✔
1888
        }
1889
    } else if (old_filter_iter != this->tss_filters.end()) {
809✔
1890
        this->tss_filters.delete_filter((*old_filter_iter)->get_id());
7✔
1891
    }
1892

1893
    return Ok();
809✔
1894
}
1895

1896
Result<void, lnav::console::user_message>
1897
logfile_sub_source::set_sql_marker(std::string stmt_str, sqlite3_stmt* stmt)
807✔
1898
{
1899
    static auto op = lnav_operation{"set_sql_marker"};
807✔
1900
    if (stmt != nullptr && !this->lss_filtered_index.empty()) {
807✔
1901
        auto top_cl = this->at(0_vl);
5✔
1902
        auto ld = this->find_data(top_cl);
5✔
1903
        auto eval_res
1904
            = this->eval_sql_filter(stmt, ld, (*ld)->get_file_ptr()->begin());
5✔
1905

1906
        if (eval_res.isErr()) {
5✔
1907
            sqlite3_finalize(stmt);
×
1908
            return Err(eval_res.unwrapErr());
×
1909
        }
1910
    }
5✔
1911

1912
    auto op_guard = lnav_opid_guard::internal(op);
807✔
1913
    log_info("setting SQL marker: %s", stmt_str.c_str());
807✔
1914
    this->lss_marker_stmt_text = std::move(stmt_str);
807✔
1915
    this->lss_marker_stmt = stmt;
807✔
1916

1917
    if (this->tss_view == nullptr || this->lss_force_rebuild) {
807✔
1918
        log_info("skipping SQL marker update");
130✔
1919
        return Ok();
130✔
1920
    }
1921

1922
    auto& vis_bm = this->tss_view->get_bookmarks();
677✔
1923
    auto& expr_marks_bv = vis_bm[&textview_curses::BM_USER_EXPR];
677✔
1924
    auto& cl_marks_bv = this->lss_user_marks[&textview_curses::BM_USER_EXPR];
677✔
1925

1926
    expr_marks_bv.clear();
677✔
1927
    if (this->lss_index_delegate) {
677✔
1928
        this->lss_index_delegate->index_start(*this);
677✔
1929
    }
1930
    for (auto row = 0_vl; row < vis_line_t(this->lss_filtered_index.size());
1,110✔
1931
         row += 1_vl)
433✔
1932
    {
1933
        auto cl = this->at(row);
433✔
1934
        uint64_t line_number;
1935
        auto ld = this->find_data(cl, line_number);
433✔
1936

1937
        if (!(*ld)->is_visible()) {
433✔
1938
            continue;
1✔
1939
        }
1940
        auto ll = (*ld)->get_file()->begin() + line_number;
433✔
1941
        if (ll->is_continued() || ll->is_ignored()) {
433✔
1942
            continue;
1✔
1943
        }
1944
        auto eval_res
1945
            = this->eval_sql_filter(this->lss_marker_stmt.in(), ld, ll);
432✔
1946

1947
        if (eval_res.isErr()) {
432✔
1948
            ll->set_expr_mark(false);
×
1949
        } else {
1950
            auto matched = eval_res.unwrap();
432✔
1951

1952
            ll->set_expr_mark(matched);
432✔
1953
            if (matched) {
432✔
1954
                expr_marks_bv.insert_once(row);
22✔
1955
                cl_marks_bv.insert_once(cl);
22✔
1956
            }
1957
        }
1958
        if (this->lss_index_delegate) {
432✔
1959
            this->lss_index_delegate->index_line(
432✔
1960
                *this, (*ld)->get_file_ptr(), ll);
432✔
1961
        }
1962
    }
432✔
1963
    if (this->lss_index_delegate) {
677✔
1964
        this->lss_index_delegate->index_complete(*this);
677✔
1965
    }
1966
    log_info("SQL marker update complete");
677✔
1967

1968
    return Ok();
677✔
1969
}
807✔
1970

1971
Result<void, lnav::console::user_message>
1972
logfile_sub_source::set_preview_sql_filter(sqlite3_stmt* stmt)
802✔
1973
{
1974
    if (stmt != nullptr && !this->lss_filtered_index.empty()) {
802✔
1975
        auto top_cl = this->at(0_vl);
×
1976
        auto ld = this->find_data(top_cl);
×
1977
        auto eval_res
1978
            = this->eval_sql_filter(stmt, ld, (*ld)->get_file_ptr()->begin());
×
1979

1980
        if (eval_res.isErr()) {
×
1981
            sqlite3_finalize(stmt);
×
1982
            return Err(eval_res.unwrapErr());
×
1983
        }
1984
    }
1985

1986
    this->lss_preview_filter_stmt = stmt;
802✔
1987

1988
    return Ok();
802✔
1989
}
1990

1991
Result<bool, lnav::console::user_message>
1992
logfile_sub_source::eval_sql_filter(sqlite3_stmt* stmt,
15,711✔
1993
                                    iterator ld,
1994
                                    logfile::const_iterator ll)
1995
{
1996
    if (stmt == nullptr) {
15,711✔
1997
        return Ok(false);
30,192✔
1998
    }
1999

2000
    auto* lf = (*ld)->get_file_ptr();
615✔
2001
    char timestamp_buffer[64];
2002
    shared_buffer_ref raw_sbr;
615✔
2003
    logline_value_vector values;
615✔
2004
    auto& sbr = values.lvv_sbr;
615✔
2005
    lf->read_full_message(ll, sbr);
615✔
2006
    sbr.erase_ansi();
615✔
2007
    auto format = lf->get_format();
615✔
2008
    string_attrs_t sa;
615✔
2009
    auto line_number = std::distance(lf->cbegin(), ll);
615✔
2010
    format->annotate(lf, line_number, sa, values);
615✔
2011
    auto lffs = lf->get_format_file_state();
615✔
2012

2013
    sqlite3_reset(stmt);
615✔
2014
    sqlite3_clear_bindings(stmt);
615✔
2015

2016
    auto count = sqlite3_bind_parameter_count(stmt);
615✔
2017
    for (int lpc = 0; lpc < count; lpc++) {
1,242✔
2018
        const auto* name = sqlite3_bind_parameter_name(stmt, lpc + 1);
627✔
2019

2020
        if (name[0] == '$') {
627✔
2021
            const char* env_value;
2022

2023
            if ((env_value = getenv(&name[1])) != nullptr) {
4✔
2024
                sqlite3_bind_text(stmt, lpc + 1, env_value, -1, SQLITE_STATIC);
1✔
2025
            }
2026
            continue;
4✔
2027
        }
4✔
2028
        if (strcmp(name, ":log_level") == 0) {
623✔
2029
            auto lvl = ll->get_level_name();
6✔
2030
            sqlite3_bind_text(
6✔
2031
                stmt, lpc + 1, lvl.data(), lvl.length(), SQLITE_STATIC);
2032
            continue;
6✔
2033
        }
6✔
2034
        if (strcmp(name, ":log_time") == 0) {
617✔
2035
            auto len = sql_strftime(timestamp_buffer,
×
2036
                                    sizeof(timestamp_buffer),
2037
                                    ll->get_timeval(),
×
2038
                                    'T');
2039
            sqlite3_bind_text(
×
2040
                stmt, lpc + 1, timestamp_buffer, len, SQLITE_STATIC);
2041
            continue;
×
2042
        }
2043
        if (strcmp(name, ":log_time_msecs") == 0) {
617✔
2044
            sqlite3_bind_int64(
1✔
2045
                stmt,
2046
                lpc + 1,
2047
                ll->get_time<std::chrono::milliseconds>().count());
1✔
2048
            continue;
1✔
2049
        }
2050
        if (strcmp(name, ":log_mark") == 0) {
616✔
2051
            sqlite3_bind_int(stmt, lpc + 1, ll->is_marked());
×
2052
            continue;
×
2053
        }
2054
        if (strcmp(name, ":log_comment") == 0) {
616✔
2055
            const auto& bm = lf->get_bookmark_metadata();
×
2056
            auto line_number
2057
                = static_cast<uint32_t>(std::distance(lf->cbegin(), ll));
×
2058
            auto bm_iter = bm.find(line_number);
×
2059
            if (bm_iter != bm.end() && !bm_iter->second.bm_comment.empty()) {
×
2060
                const auto& meta = bm_iter->second;
×
2061
                sqlite3_bind_text(stmt,
×
2062
                                  lpc + 1,
2063
                                  meta.bm_comment.c_str(),
2064
                                  meta.bm_comment.length(),
×
2065
                                  SQLITE_STATIC);
2066
            }
2067
            continue;
×
2068
        }
2069
        if (strcmp(name, ":log_annotations") == 0) {
616✔
2070
            const auto& bm = lf->get_bookmark_metadata();
×
2071
            auto line_number
2072
                = static_cast<uint32_t>(std::distance(lf->cbegin(), ll));
×
2073
            auto bm_iter = bm.find(line_number);
×
2074
            if (bm_iter != bm.end()
×
2075
                && !bm_iter->second.bm_annotations.la_pairs.empty())
×
2076
            {
2077
                const auto& meta = bm_iter->second;
×
2078
                auto anno_str = logmsg_annotations_handlers.to_string(
2079
                    meta.bm_annotations);
×
2080

2081
                sqlite3_bind_text(stmt,
×
2082
                                  lpc + 1,
2083
                                  anno_str.c_str(),
2084
                                  anno_str.length(),
×
2085
                                  SQLITE_TRANSIENT);
2086
            }
2087
            continue;
×
2088
        }
2089
        if (strcmp(name, ":log_tags") == 0) {
616✔
2090
            const auto& bm = lf->get_bookmark_metadata();
9✔
2091
            auto line_number
2092
                = static_cast<uint32_t>(std::distance(lf->cbegin(), ll));
9✔
2093
            auto bm_iter = bm.find(line_number);
9✔
2094
            if (bm_iter != bm.end() && !bm_iter->second.bm_tags.empty()) {
9✔
2095
                const auto& meta = bm_iter->second;
1✔
2096
                yajlpp_gen gen;
1✔
2097

2098
                yajl_gen_config(gen, yajl_gen_beautify, false);
1✔
2099

2100
                {
2101
                    yajlpp_array arr(gen);
1✔
2102

2103
                    for (const auto& str : meta.bm_tags) {
2✔
2104
                        arr.gen(str);
1✔
2105
                    }
2106
                }
1✔
2107

2108
                string_fragment sf = gen.to_string_fragment();
1✔
2109

2110
                sqlite3_bind_text(
1✔
2111
                    stmt, lpc + 1, sf.data(), sf.length(), SQLITE_TRANSIENT);
2112
            }
1✔
2113
            continue;
9✔
2114
        }
9✔
2115
        if (strcmp(name, ":log_format") == 0) {
607✔
2116
            const auto format_name = format->get_name();
6✔
2117
            sqlite3_bind_text(stmt,
6✔
2118
                              lpc + 1,
2119
                              format_name.get(),
2120
                              format_name.size(),
6✔
2121
                              SQLITE_STATIC);
2122
            continue;
6✔
2123
        }
6✔
2124
        if (strcmp(name, ":log_format_regex") == 0) {
601✔
2125
            const auto pat_name = format->get_pattern_name(
×
2126
                lffs.lffs_pattern_locks, line_number);
2127
            sqlite3_bind_text(
×
2128
                stmt, lpc + 1, pat_name.get(), pat_name.size(), SQLITE_STATIC);
×
2129
            continue;
×
2130
        }
2131
        if (strcmp(name, ":log_path") == 0) {
601✔
2132
            const auto& filename = lf->get_filename();
×
2133
            sqlite3_bind_text(stmt,
×
2134
                              lpc + 1,
2135
                              filename.c_str(),
2136
                              filename.native().length(),
×
2137
                              SQLITE_STATIC);
2138
            continue;
×
2139
        }
2140
        if (strcmp(name, ":log_unique_path") == 0) {
601✔
2141
            const auto& filename = lf->get_unique_path();
×
2142
            sqlite3_bind_text(stmt,
×
2143
                              lpc + 1,
2144
                              filename.c_str(),
2145
                              filename.native().length(),
×
2146
                              SQLITE_STATIC);
2147
            continue;
×
2148
        }
2149
        if (strcmp(name, ":log_text") == 0) {
601✔
2150
            sqlite3_bind_text(
4✔
2151
                stmt, lpc + 1, sbr.get_data(), sbr.length(), SQLITE_STATIC);
4✔
2152
            continue;
4✔
2153
        }
2154
        if (strcmp(name, ":log_body") == 0) {
597✔
2155
            auto body_attr_opt = get_string_attr(sa, SA_BODY);
16✔
2156
            if (body_attr_opt) {
16✔
2157
                const auto& sar
2158
                    = body_attr_opt.value().saw_string_attr->sa_range;
16✔
2159

2160
                sqlite3_bind_text(stmt,
32✔
2161
                                  lpc + 1,
2162
                                  sbr.get_data_at(sar.lr_start),
16✔
2163
                                  sar.length(),
2164
                                  SQLITE_STATIC);
2165
            } else {
2166
                sqlite3_bind_null(stmt, lpc + 1);
×
2167
            }
2168
            continue;
16✔
2169
        }
16✔
2170
        if (strcmp(name, ":log_opid") == 0) {
581✔
2171
            if (values.lvv_opid_value) {
×
2172
                sqlite3_bind_text(stmt,
×
2173
                                  lpc + 1,
2174
                                  values.lvv_opid_value->c_str(),
2175
                                  values.lvv_opid_value->length(),
×
2176
                                  SQLITE_STATIC);
2177
            } else {
2178
                sqlite3_bind_null(stmt, lpc + 1);
×
2179
            }
2180
            continue;
×
2181
        }
2182
        if (strcmp(name, ":log_raw_text") == 0) {
581✔
2183
            auto res = lf->read_raw_message(ll);
×
2184

2185
            if (res.isOk()) {
×
2186
                raw_sbr = res.unwrap();
×
2187
                sqlite3_bind_text(stmt,
×
2188
                                  lpc + 1,
2189
                                  raw_sbr.get_data(),
2190
                                  raw_sbr.length(),
×
2191
                                  SQLITE_STATIC);
2192
            }
2193
            continue;
×
2194
        }
2195
        for (const auto& lv : values.lvv_values) {
6,782✔
2196
            if (lv.lv_meta.lvm_name != &name[1]) {
6,777✔
2197
                continue;
6,201✔
2198
            }
2199

2200
            switch (lv.lv_meta.lvm_kind) {
576✔
2201
                case value_kind_t::VALUE_BOOLEAN:
×
2202
                    sqlite3_bind_int64(stmt, lpc + 1, lv.lv_value.i);
×
2203
                    break;
×
2204
                case value_kind_t::VALUE_FLOAT:
×
2205
                    sqlite3_bind_double(stmt, lpc + 1, lv.lv_value.d);
×
2206
                    break;
×
2207
                case value_kind_t::VALUE_INTEGER:
436✔
2208
                    sqlite3_bind_int64(stmt, lpc + 1, lv.lv_value.i);
436✔
2209
                    break;
436✔
2210
                case value_kind_t::VALUE_NULL:
×
2211
                    sqlite3_bind_null(stmt, lpc + 1);
×
2212
                    break;
×
2213
                default:
140✔
2214
                    sqlite3_bind_text(stmt,
140✔
2215
                                      lpc + 1,
2216
                                      lv.text_value(),
2217
                                      lv.text_length(),
140✔
2218
                                      SQLITE_TRANSIENT);
2219
                    break;
140✔
2220
            }
2221
            break;
576✔
2222
        }
2223
    }
2224

2225
    auto step_res = sqlite3_step(stmt);
615✔
2226

2227
    sqlite3_reset(stmt);
615✔
2228
    sqlite3_clear_bindings(stmt);
615✔
2229
    switch (step_res) {
615✔
2230
        case SQLITE_OK:
474✔
2231
        case SQLITE_DONE:
2232
            return Ok(false);
948✔
2233
        case SQLITE_ROW:
140✔
2234
            return Ok(true);
280✔
2235
        default:
1✔
2236
            return Err(sqlite3_error_to_user_message(sqlite3_db_handle(stmt)));
1✔
2237
    }
2238
}
615✔
2239

2240
bool
2241
logfile_sub_source::check_extra_filters(iterator ld, logfile::iterator ll)
15,135✔
2242
{
2243
    auto retval = true;
15,135✔
2244

2245
    if (this->lss_marked_only) {
15,135✔
2246
        auto found_mark = ll->is_marked() || ll->is_expr_marked();
6✔
2247
        auto to_start_ll = ll;
6✔
2248
        while (!found_mark && to_start_ll->is_continued()) {
6✔
2249
            if (to_start_ll->is_marked() || to_start_ll->is_expr_marked()) {
×
2250
                found_mark = true;
×
2251
            }
2252
            --to_start_ll;
×
2253
        }
2254
        auto to_end_ll = std::next(ll);
6✔
2255
        while (!found_mark && to_end_ll != (*ld)->get_file_ptr()->end()
10✔
2256
               && to_end_ll->is_continued())
11✔
2257
        {
2258
            if (to_end_ll->is_marked() || to_end_ll->is_expr_marked()) {
1✔
2259
                found_mark = true;
1✔
2260
            }
2261
            ++to_end_ll;
1✔
2262
        }
2263
        if (!found_mark) {
6✔
2264
            retval = false;
3✔
2265
        }
2266
    }
2267

2268
    if (ll->get_msg_level() < this->tss_min_log_level) {
15,135✔
2269
        this->tss_level_filtered_count += 1;
2✔
2270
        retval = false;
2✔
2271
    }
2272

2273
    if (*ll < this->ttt_min_row_time) {
15,135✔
2274
        retval = false;
36✔
2275
    }
2276

2277
    if (!(*ll <= this->ttt_max_row_time)) {
15,135✔
2278
        retval = false;
4✔
2279
    }
2280

2281
    return retval;
15,135✔
2282
}
2283

2284
void
2285
logfile_sub_source::invalidate_sql_filter()
29✔
2286
{
2287
    for (auto& ld : *this) {
58✔
2288
        ld->ld_filter_state.lfo_filter_state.clear_filter_state(0);
29✔
2289
    }
2290
}
29✔
2291

2292
void
2293
logfile_sub_source::text_mark(const bookmark_type_t* bm,
233✔
2294
                              vis_line_t line,
2295
                              bool added)
2296
{
2297
    if (line >= (int) this->lss_index.size()) {
233✔
2298
        return;
×
2299
    }
2300

2301
    auto cl = this->at(line);
233✔
2302

2303
    if (bm == &textview_curses::BM_USER) {
233✔
2304
        auto* ll = this->find_line(cl);
73✔
2305

2306
        ll->set_mark(added);
73✔
2307
    }
2308
    if (added) {
233✔
2309
        this->lss_user_marks[bm].insert_once(cl);
77✔
2310
    } else {
2311
        this->lss_user_marks[bm].erase(cl);
156✔
2312
    }
2313
    if (bm == &textview_curses::BM_META
233✔
2314
        && this->lss_meta_grepper.gps_proc != nullptr)
18✔
2315
    {
2316
        this->tss_view->search_range(
1✔
2317
            line, grep_proc<vis_line_t>::until_line(line + 1_vl));
1✔
2318
        this->tss_view->search_new_data();
1✔
2319
    }
2320
}
2321

2322
void
2323
logfile_sub_source::text_clear_marks(const bookmark_type_t* bm)
40✔
2324
{
2325
    if (bm == &textview_curses::BM_USER) {
40✔
2326
        for (auto iter = this->lss_user_marks[bm].bv_tree.begin();
6✔
2327
             iter != this->lss_user_marks[bm].bv_tree.end();
6✔
2328
             ++iter)
×
2329
        {
2330
            this->find_line(*iter)->set_mark(false);
×
2331
        }
2332
    }
2333
    this->lss_user_marks[bm].clear();
40✔
2334
}
40✔
2335

2336
void
2337
logfile_sub_source::remove_file(std::shared_ptr<logfile> lf)
550✔
2338
{
2339
    auto iter = std::find_if(
550✔
2340
        this->lss_files.begin(), this->lss_files.end(), logfile_data_eq(lf));
1,100✔
2341
    if (iter != this->lss_files.end()) {
550✔
2342
        int file_index = iter - this->lss_files.begin();
550✔
2343

2344
        (*iter)->clear();
550✔
2345
        for (auto& bv : this->lss_user_marks) {
5,500✔
2346
            auto mark_curr = content_line_t(file_index * MAX_LINES_PER_FILE);
4,950✔
2347
            auto mark_end
2348
                = content_line_t((file_index + 1) * MAX_LINES_PER_FILE);
4,950✔
2349
            auto file_range = bv.equal_range(mark_curr, mark_end);
4,950✔
2350

2351
            if (file_range.first != file_range.second) {
4,950✔
2352
                auto to_del = std::vector<content_line_t>{};
76✔
2353
                for (auto file_iter = file_range.first;
76✔
2354
                     file_iter != file_range.second;
196✔
2355
                     ++file_iter)
120✔
2356
                {
2357
                    to_del.emplace_back(*file_iter);
120✔
2358
                }
2359

2360
                for (auto cl : to_del) {
196✔
2361
                    bv.erase(cl);
120✔
2362
                }
2363
            }
76✔
2364
        }
2365

2366
        this->lss_force_rebuild = true;
550✔
2367
    }
2368
    while (!this->lss_files.empty()) {
1,100✔
2369
        if (this->lss_files.back()->get_file_ptr() == nullptr) {
602✔
2370
            this->lss_files.pop_back();
550✔
2371
        } else {
2372
            break;
52✔
2373
        }
2374
    }
2375
    this->lss_token_file = nullptr;
550✔
2376
}
550✔
2377

2378
std::optional<vis_line_t>
2379
logfile_sub_source::find_from_content(content_line_t cl)
5✔
2380
{
2381
    content_line_t line = cl;
5✔
2382
    std::shared_ptr<logfile> lf = this->find(line);
5✔
2383

2384
    if (lf != nullptr) {
5✔
2385
        auto ll_iter = lf->begin() + line;
5✔
2386
        auto& ll = *ll_iter;
5✔
2387
        auto vis_start_opt = this->find_from_time(ll.get_timeval());
5✔
2388

2389
        if (!vis_start_opt) {
5✔
2390
            return std::nullopt;
×
2391
        }
2392

2393
        auto vis_start = *vis_start_opt;
5✔
2394

2395
        while (vis_start < vis_line_t(this->text_line_count())) {
5✔
2396
            content_line_t guess_cl = this->at(vis_start);
5✔
2397

2398
            if (cl == guess_cl) {
5✔
2399
                return vis_start;
5✔
2400
            }
2401

2402
            auto guess_line = this->find_line(guess_cl);
×
2403

2404
            if (!guess_line || ll < *guess_line) {
×
2405
                return std::nullopt;
×
2406
            }
2407

2408
            ++vis_start;
×
2409
        }
2410
    }
2411

2412
    return std::nullopt;
×
2413
}
5✔
2414

2415
void
2416
logfile_sub_source::reload_index_delegate()
679✔
2417
{
2418
    if (this->lss_index_delegate == nullptr) {
679✔
2419
        return;
×
2420
    }
2421

2422
    this->lss_index_delegate->index_start(*this);
679✔
2423
    for (const auto index : this->lss_filtered_index) {
707✔
2424
        auto cl = this->lss_index[index].value();
28✔
2425
        uint64_t line_number;
2426
        auto ld = this->find_data(cl, line_number);
28✔
2427
        std::shared_ptr<logfile> lf = (*ld)->get_file();
28✔
2428

2429
        this->lss_index_delegate->index_line(
28✔
2430
            *this, lf.get(), lf->begin() + line_number);
28✔
2431
    }
28✔
2432
    this->lss_index_delegate->index_complete(*this);
679✔
2433
}
2434

2435
std::optional<std::shared_ptr<text_filter>>
2436
logfile_sub_source::get_sql_filter()
2,665✔
2437
{
2438
    return this->tss_filters | lnav::itertools::find_if([](const auto& filt) {
2,665✔
2439
               return filt->get_index() == 0
274✔
2440
                   && dynamic_cast<sql_filter*>(filt.get()) != nullptr;
274✔
2441
           })
2442
        | lnav::itertools::deref();
5,330✔
2443
}
2444

2445
void
2446
log_location_history::loc_history_append(vis_line_t top)
115✔
2447
{
2448
    if (top < 0_vl || top >= vis_line_t(this->llh_log_source.text_line_count()))
115✔
2449
    {
2450
        return;
1✔
2451
    }
2452

2453
    auto cl = this->llh_log_source.at(top);
114✔
2454

2455
    auto iter = this->llh_history.begin();
114✔
2456
    iter += this->llh_history.size() - this->lh_history_position;
114✔
2457
    this->llh_history.erase_from(iter);
114✔
2458
    this->lh_history_position = 0;
114✔
2459
    this->llh_history.push_back(cl);
114✔
2460
}
2461

2462
std::optional<vis_line_t>
2463
log_location_history::loc_history_back(vis_line_t current_top)
2✔
2464
{
2465
    while (this->lh_history_position < this->llh_history.size()) {
2✔
2466
        auto iter = this->llh_history.rbegin();
2✔
2467

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

2470
        if (this->lh_history_position == 0 && vis_for_pos != current_top) {
2✔
2471
            return vis_for_pos;
2✔
2472
        }
2473

2474
        if ((this->lh_history_position + 1) >= this->llh_history.size()) {
2✔
2475
            break;
×
2476
        }
2477

2478
        this->lh_history_position += 1;
2✔
2479

2480
        iter += this->lh_history_position;
2✔
2481

2482
        vis_for_pos = this->llh_log_source.find_from_content(*iter);
2✔
2483

2484
        if (vis_for_pos) {
2✔
2485
            return vis_for_pos;
2✔
2486
        }
2487
    }
2488

2489
    return std::nullopt;
×
2490
}
2491

2492
std::optional<vis_line_t>
2493
log_location_history::loc_history_forward(vis_line_t current_top)
1✔
2494
{
2495
    while (this->lh_history_position > 0) {
1✔
2496
        this->lh_history_position -= 1;
1✔
2497

2498
        auto iter = this->llh_history.rbegin();
1✔
2499

2500
        iter += this->lh_history_position;
1✔
2501

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

2504
        if (vis_for_pos) {
1✔
2505
            return vis_for_pos;
1✔
2506
        }
2507
    }
2508

2509
    return std::nullopt;
×
2510
}
2511

2512
bool
2513
sql_filter::matches(std::optional<line_source> ls_opt,
166✔
2514
                    const shared_buffer_ref& line)
2515
{
2516
    if (!ls_opt) {
166✔
2517
        return false;
×
2518
    }
2519

2520
    auto ls = ls_opt;
166✔
2521

2522
    if (!ls->ls_line->is_message()) {
166✔
2523
        return false;
45✔
2524
    }
2525
    if (this->sf_filter_stmt == nullptr) {
121✔
2526
        return false;
×
2527
    }
2528

2529
    auto lfp = ls->ls_file.shared_from_this();
121✔
2530
    auto ld = this->sf_log_source.find_data_i(lfp);
121✔
2531
    if (ld == this->sf_log_source.end()) {
121✔
2532
        return false;
×
2533
    }
2534

2535
    auto eval_res = this->sf_log_source.eval_sql_filter(
121✔
2536
        this->sf_filter_stmt, ld, ls->ls_line);
121✔
2537
    if (eval_res.unwrapOr(true)) {
121✔
2538
        return false;
74✔
2539
    }
2540

2541
    return true;
47✔
2542
}
121✔
2543

2544
std::string
2545
sql_filter::to_command() const
×
2546
{
2547
    return fmt::format(FMT_STRING("filter-expr {}"), this->lf_id);
×
2548
}
2549

2550
std::optional<line_info>
2551
logfile_sub_source::meta_grepper::grep_value_for_line(vis_line_t line,
×
2552
                                                      std::string& value_out)
2553
{
2554
    auto line_meta_opt = this->lmg_source.find_bookmark_metadata(line);
×
2555
    if (!line_meta_opt) {
×
2556
        value_out.clear();
×
2557
    } else {
2558
        auto& bm = *(line_meta_opt.value());
×
2559

2560
        {
2561
            md2attr_line mdal;
×
2562

2563
            auto parse_res = md4cpp::parse(bm.bm_comment, mdal);
×
2564
            if (parse_res.isOk()) {
×
2565
                value_out.append(parse_res.unwrap().get_string());
×
2566
            } else {
2567
                value_out.append(bm.bm_comment);
×
2568
            }
2569
        }
2570

2571
        value_out.append("\x1c");
×
2572
        for (const auto& tag : bm.bm_tags) {
×
2573
            value_out.append(tag);
×
2574
            value_out.append("\x1c");
×
2575
        }
2576
        value_out.append("\x1c");
×
2577
        for (const auto& pair : bm.bm_annotations.la_pairs) {
×
2578
            value_out.append(pair.first);
×
2579
            value_out.append("\x1c");
×
2580

2581
            md2attr_line mdal;
×
2582

2583
            auto parse_res = md4cpp::parse(pair.second, mdal);
×
2584
            if (parse_res.isOk()) {
×
2585
                value_out.append(parse_res.unwrap().get_string());
×
2586
            } else {
2587
                value_out.append(pair.second);
×
2588
            }
2589
            value_out.append("\x1c");
×
2590
        }
2591
        value_out.append("\x1c");
×
2592
        value_out.append(bm.bm_opid);
×
2593
    }
2594

2595
    if (!this->lmg_done) {
×
2596
        return line_info{};
×
2597
    }
2598

2599
    return std::nullopt;
×
2600
}
2601

2602
vis_line_t
2603
logfile_sub_source::meta_grepper::grep_initial_line(vis_line_t start,
×
2604
                                                    vis_line_t highest)
2605
{
2606
    auto& bm = this->lmg_source.tss_view->get_bookmarks();
×
2607
    auto& bv = bm[&textview_curses::BM_META];
×
2608

2609
    if (bv.empty()) {
×
2610
        return -1_vl;
×
2611
    }
2612
    return *bv.bv_tree.begin();
×
2613
}
2614

2615
void
2616
logfile_sub_source::meta_grepper::grep_next_line(vis_line_t& line)
×
2617
{
2618
    auto& bm = this->lmg_source.tss_view->get_bookmarks();
×
2619
    auto& bv = bm[&textview_curses::BM_META];
×
2620

2621
    auto line_opt = bv.next(vis_line_t(line));
×
2622
    if (!line_opt) {
×
2623
        this->lmg_done = true;
×
2624
    }
2625
    line = line_opt.value_or(-1_vl);
×
2626
}
2627

2628
void
2629
logfile_sub_source::meta_grepper::grep_begin(grep_proc<vis_line_t>& gp,
23✔
2630
                                             vis_line_t start,
2631
                                             vis_line_t stop)
2632
{
2633
    this->lmg_source.quiesce();
23✔
2634

2635
    this->lmg_source.tss_view->grep_begin(gp, start, stop);
23✔
2636
}
23✔
2637

2638
void
2639
logfile_sub_source::meta_grepper::grep_end(grep_proc<vis_line_t>& gp)
23✔
2640
{
2641
    this->lmg_source.tss_view->grep_end(gp);
23✔
2642
}
23✔
2643

2644
void
2645
logfile_sub_source::meta_grepper::grep_match(grep_proc<vis_line_t>& gp,
3✔
2646
                                             vis_line_t line)
2647
{
2648
    this->lmg_source.tss_view->grep_match(gp, line);
3✔
2649
}
3✔
2650

2651
static std::vector<breadcrumb::possibility>
2652
timestamp_poss()
11✔
2653
{
2654
    const static std::vector<breadcrumb::possibility> retval = {
2655
        breadcrumb::possibility{"-1 day"},
2656
        breadcrumb::possibility{"-1h"},
2657
        breadcrumb::possibility{"-30m"},
2658
        breadcrumb::possibility{"-15m"},
2659
        breadcrumb::possibility{"-5m"},
2660
        breadcrumb::possibility{"-1m"},
2661
        breadcrumb::possibility{"+1m"},
2662
        breadcrumb::possibility{"+5m"},
2663
        breadcrumb::possibility{"+15m"},
2664
        breadcrumb::possibility{"+30m"},
2665
        breadcrumb::possibility{"+1h"},
2666
        breadcrumb::possibility{"+1 day"},
2667
    };
95✔
2668

2669
    return retval;
11✔
2670
}
24✔
2671

2672
static attr_line_t
2673
to_display(const std::shared_ptr<logfile>& lf)
30✔
2674
{
2675
    const auto& loo = lf->get_open_options();
30✔
2676
    attr_line_t retval;
30✔
2677

2678
    if (loo.loo_piper) {
30✔
2679
        if (!lf->get_open_options().loo_piper->is_finished()) {
×
2680
            retval.append("\u21bb "_list_glyph);
×
2681
        }
2682
    } else if (loo.loo_child_poller && loo.loo_child_poller->is_alive()) {
30✔
2683
        retval.append("\u21bb "_list_glyph);
×
2684
    }
2685
    retval.append(lf->get_unique_path());
30✔
2686

2687
    return retval;
30✔
2688
}
×
2689

2690
void
2691
logfile_sub_source::text_crumbs_for_line(int line,
28✔
2692
                                         std::vector<breadcrumb::crumb>& crumbs)
2693
{
2694
    text_sub_source::text_crumbs_for_line(line, crumbs);
28✔
2695

2696
    if (this->lss_filtered_index.empty()) {
28✔
2697
        return;
9✔
2698
    }
2699

2700
    auto vl = vis_line_t(line);
19✔
2701
    auto bmc = this->get_bookmark_metadata_context(
19✔
2702
        vl, bookmark_metadata::categories::partition);
2703
    if (bmc.bmc_current_metadata) {
19✔
2704
        const auto& name = bmc.bmc_current_metadata.value()->bm_name;
1✔
2705
        auto key = text_anchors::to_anchor_string(name);
1✔
2706
        auto display = attr_line_t()
1✔
2707
                           .append("\u2291 "_symbol)
1✔
2708
                           .append(lnav::roles::variable(name))
2✔
2709
                           .move();
1✔
2710
        crumbs.emplace_back(
1✔
2711
            key,
2712
            display,
2713
            [this]() -> std::vector<breadcrumb::possibility> {
×
2714
                auto& vb = this->tss_view->get_bookmarks();
1✔
2715
                const auto& bv = vb[&textview_curses::BM_PARTITION];
1✔
2716
                std::vector<breadcrumb::possibility> retval;
1✔
2717

2718
                for (const auto& vl : bv.bv_tree) {
2✔
2719
                    auto meta_opt = this->find_bookmark_metadata(vl);
1✔
2720
                    if (!meta_opt || meta_opt.value()->bm_name.empty()) {
1✔
2721
                        continue;
×
2722
                    }
2723

2724
                    const auto& name = meta_opt.value()->bm_name;
1✔
2725
                    retval.emplace_back(text_anchors::to_anchor_string(name),
1✔
2726
                                        name);
2727
                }
2728

2729
                return retval;
1✔
2730
            },
×
2731
            [ec = this->lss_exec_context](const auto& part) {
1✔
2732
                auto cmd = fmt::format(FMT_STRING(":goto {}"),
×
2733
                                       part.template get<std::string>());
2734
                ec->execute(INTERNAL_SRC_LOC, cmd);
×
2735
            });
×
2736
    }
1✔
2737

2738
    auto line_pair_opt = this->find_line_with_file(vl);
19✔
2739
    if (!line_pair_opt) {
19✔
2740
        return;
×
2741
    }
2742
    auto line_pair = line_pair_opt.value();
19✔
2743
    auto& lf = line_pair.first;
19✔
2744
    auto format = lf->get_format();
19✔
2745
    char ts[64];
2746

2747
    sql_strftime(ts, sizeof(ts), line_pair.second->get_timeval(), 'T');
19✔
2748

2749
    crumbs.emplace_back(std::string(ts),
19✔
2750
                        timestamp_poss,
2751
                        [ec = this->lss_exec_context](const auto& ts) {
19✔
2752
                            auto cmd
×
2753
                                = fmt::format(FMT_STRING(":goto {}"),
×
2754
                                              ts.template get<std::string>());
2755
                            ec->execute(INTERNAL_SRC_LOC, cmd);
×
2756
                        });
×
2757
    crumbs.back().c_expected_input
19✔
2758
        = breadcrumb::crumb::expected_input_t::anything;
19✔
2759
    crumbs.back().c_search_placeholder = "(Enter an absolute or relative time)";
19✔
2760

2761
    auto format_name = format->get_name().to_string();
19✔
2762

2763
    crumbs.emplace_back(
19✔
2764
        format_name,
2765
        attr_line_t().append(format_name),
19✔
2766
        [this]() -> std::vector<breadcrumb::possibility> {
×
2767
            return this->lss_files
11✔
2768
                | lnav::itertools::filter_in([](const auto& file_data) {
22✔
2769
                       return file_data->is_visible();
11✔
2770
                   })
2771
                | lnav::itertools::map(&logfile_data::get_file_ptr)
33✔
2772
                | lnav::itertools::map(&logfile::get_format_name)
33✔
2773
                | lnav::itertools::unique()
33✔
2774
                | lnav::itertools::map([](const auto& elem) {
44✔
2775
                       return breadcrumb::possibility{
2776
                           elem.to_string(),
2777
                       };
11✔
2778
                   })
2779
                | lnav::itertools::to_vector();
33✔
2780
        },
2781
        [ec = this->lss_exec_context](const auto& format_name) {
19✔
2782
            static const std::string MOVE_STMT = R"(;UPDATE lnav_views
2783
     SET selection = ifnull(
2784
         (SELECT log_line FROM all_logs WHERE log_format = $format_name LIMIT 1),
2785
         (SELECT raise_error(
2786
            'Could not find format: ' || $format_name,
2787
            'The corresponding log messages might have been filtered out'))
2788
       )
2789
     WHERE name = 'log'
2790
)";
2791

2792
            ec->execute_with(
×
2793
                INTERNAL_SRC_LOC,
×
2794
                MOVE_STMT,
2795
                std::make_pair("format_name",
2796
                               format_name.template get<std::string>()));
2797
        });
×
2798

2799
    auto msg_start_iter = lf->message_start(line_pair.second);
19✔
2800
    auto file_line_number = std::distance(lf->begin(), msg_start_iter);
19✔
2801
    crumbs.emplace_back(
38✔
2802
        lf->get_unique_path(),
19✔
2803
        to_display(lf).appendf(FMT_STRING("[{:L}]"), file_line_number),
76✔
2804
        [this]() -> std::vector<breadcrumb::possibility> {
×
2805
            return this->lss_files
11✔
2806
                | lnav::itertools::filter_in([](const auto& file_data) {
22✔
2807
                       return file_data->is_visible();
11✔
2808
                   })
2809
                | lnav::itertools::map([](const auto& file_data) {
22✔
2810
                       return breadcrumb::possibility{
2811
                           file_data->get_file_ptr()->get_unique_path(),
11✔
2812
                           to_display(file_data->get_file()),
2813
                       };
11✔
2814
                   });
22✔
2815
        },
2816
        [ec = this->lss_exec_context](const auto& uniq_path) {
19✔
2817
            static const std::string MOVE_STMT = R"(;UPDATE lnav_views
2818
     SET selection = ifnull(
2819
          (SELECT log_line FROM all_logs WHERE log_unique_path = $uniq_path LIMIT 1),
2820
          (SELECT raise_error(
2821
            'Could not find file: ' || $uniq_path,
2822
            'The corresponding log messages might have been filtered out'))
2823
         )
2824
     WHERE name = 'log'
2825
)";
2826

2827
            ec->execute_with(
×
2828
                INTERNAL_SRC_LOC,
×
2829
                MOVE_STMT,
2830
                std::make_pair("uniq_path",
2831
                               uniq_path.template get<std::string>()));
2832
        });
×
2833

2834
    shared_buffer sb;
19✔
2835
    logline_value_vector values;
19✔
2836
    auto& sbr = values.lvv_sbr;
19✔
2837

2838
    lf->read_full_message(msg_start_iter, sbr);
19✔
2839
    attr_line_t al(to_string(sbr));
19✔
2840
    if (!sbr.get_metadata().m_valid_utf) {
19✔
2841
        scrub_to_utf8(al.al_string.data(), al.al_string.length());
×
2842
    }
2843
    if (sbr.get_metadata().m_has_ansi) {
19✔
2844
        // bleh
2845
        scrub_ansi_string(al.get_string(), &al.al_attrs);
×
2846
        sbr.share(sb, al.al_string.data(), al.al_string.size());
×
2847
    }
2848
    format->annotate(lf.get(), file_line_number, al.get_attrs(), values);
19✔
2849

2850
    {
2851
        static const std::string MOVE_STMT = R"(;UPDATE lnav_views
31✔
2852
          SET selection = ifnull(
2853
            (SELECT log_line FROM all_logs WHERE log_thread_id = $tid LIMIT 1),
2854
            (SELECT raise_error('Could not find thread ID: ' || $tid,
2855
                                'The corresponding log messages might have been filtered out')))
2856
          WHERE name = 'log'
2857
        )";
2858
        static const std::string ELLIPSIS = "\u22ef";
31✔
2859

2860
        auto tid_display = values.lvv_thread_id_value.has_value()
19✔
2861
            ? lnav::roles::identifier(values.lvv_thread_id_value.value())
1✔
2862
            : lnav::roles::hidden(ELLIPSIS);
20✔
2863
        crumbs.emplace_back(
19✔
2864
            values.lvv_thread_id_value.has_value()
19✔
2865
                ? values.lvv_thread_id_value.value()
56✔
2866
                : "",
2867
            attr_line_t()
19✔
2868
                .append(ui_icon_t::thread)
19✔
2869
                .append(" ")
19✔
2870
                .append(tid_display),
2871
            [this]() -> std::vector<breadcrumb::possibility> {
×
2872
                std::set<std::string> poss_strs;
11✔
2873

2874
                for (const auto& file_data : this->lss_files) {
22✔
2875
                    if (file_data->get_file_ptr() == nullptr) {
11✔
2876
                        continue;
×
2877
                    }
2878
                    safe::ReadAccess<logfile::safe_thread_id_state> r_tid_map(
2879
                        file_data->get_file_ptr()->get_thread_ids());
11✔
2880

2881
                    for (const auto& pair : r_tid_map->ltis_tid_ranges) {
24✔
2882
                        poss_strs.emplace(pair.first.to_string());
13✔
2883
                    }
2884
                }
11✔
2885

2886
                std::vector<breadcrumb::possibility> retval;
11✔
2887

2888
                std::transform(poss_strs.begin(),
11✔
2889
                               poss_strs.end(),
2890
                               std::back_inserter(retval),
2891
                               [](const auto& tid_str) {
13✔
2892
                                   return breadcrumb::possibility(tid_str);
13✔
2893
                               });
2894

2895
                return retval;
22✔
2896
            },
11✔
2897
            [ec = this->lss_exec_context](const auto& tid) {
19✔
2898
                ec->execute_with(
×
2899
                    INTERNAL_SRC_LOC,
×
2900
                    MOVE_STMT,
2901
                    std::make_pair("tid", tid.template get<std::string>()));
2902
            });
×
2903
    }
19✔
2904

2905
    {
2906
        static const std::string MOVE_STMT = R"(;UPDATE lnav_views
31✔
2907
          SET selection = ifnull(
2908
            (SELECT log_line FROM all_logs WHERE log_opid = $opid LIMIT 1),
2909
            (SELECT raise_error('Could not find opid: ' || $opid,
2910
                                'The corresponding log messages might have been filtered out')))
2911
          WHERE name = 'log'
2912
        )";
2913
        static const std::string ELLIPSIS = "\u22ef";
31✔
2914

2915
        auto opid_display = values.lvv_opid_value.has_value()
19✔
2916
            ? lnav::roles::identifier(values.lvv_opid_value.value())
9✔
2917
            : lnav::roles::hidden(ELLIPSIS);
28✔
2918
        crumbs.emplace_back(
38✔
2919
            values.lvv_opid_value.has_value() ? values.lvv_opid_value.value()
48✔
2920
                                              : "",
2921
            attr_line_t().append(opid_display),
19✔
2922
            [this]() -> std::vector<breadcrumb::possibility> {
×
2923
                std::unordered_set<std::string> poss_strs;
11✔
2924

2925
                for (const auto& file_data : this->lss_files) {
22✔
2926
                    if (file_data->get_file_ptr() == nullptr) {
11✔
2927
                        continue;
×
2928
                    }
2929
                    safe::ReadAccess<logfile::safe_opid_state> r_opid_map(
2930
                        file_data->get_file_ptr()->get_opids());
11✔
2931

2932
                    poss_strs.reserve(poss_strs.size()
22✔
2933
                                      + r_opid_map->los_opid_ranges.size());
11✔
2934
                    for (const auto& pair : r_opid_map->los_opid_ranges) {
783✔
2935
                        poss_strs.insert(pair.first.to_string());
772✔
2936
                    }
2937
                }
11✔
2938

2939
                std::vector<breadcrumb::possibility> retval;
11✔
2940
                retval.reserve(poss_strs.size());
11✔
2941

2942
                std::transform(poss_strs.begin(),
11✔
2943
                               poss_strs.end(),
2944
                               std::back_inserter(retval),
2945
                               [](const auto& opid_str) {
772✔
2946
                                   return breadcrumb::possibility(opid_str);
772✔
2947
                               });
2948

2949
                return retval;
22✔
2950
            },
11✔
2951
            [ec = this->lss_exec_context](const auto& opid) {
19✔
2952
                ec->execute_with(
×
2953
                    INTERNAL_SRC_LOC,
×
2954
                    MOVE_STMT,
2955
                    std::make_pair("opid", opid.template get<std::string>()));
2956
            });
×
2957
    }
19✔
2958

2959
    auto sf = string_fragment::from_str(al.get_string());
19✔
2960
    auto body_opt = get_string_attr(al.get_attrs(), SA_BODY);
19✔
2961
    auto nl_pos_opt = sf.find('\n');
19✔
2962
    auto msg_line_number = std::distance(msg_start_iter, line_pair.second);
19✔
2963
    auto line_from_top = line - msg_line_number;
19✔
2964
    if (body_opt && nl_pos_opt) {
19✔
2965
        if (this->lss_token_meta_line != file_line_number
20✔
2966
            || this->lss_token_meta_size != sf.length())
10✔
2967
        {
2968
            if (body_opt->saw_string_attr->sa_range.length() < 128 * 1024) {
3✔
2969
                this->lss_token_meta
2970
                    = lnav::document::discover(al)
3✔
2971
                          .over_range(
3✔
2972
                              body_opt.value().saw_string_attr->sa_range)
3✔
2973
                          .perform();
3✔
2974
                // XXX discover_structure() changes `al`, have to recompute
2975
                // stuff
2976
                sf = al.to_string_fragment();
3✔
2977
                body_opt = get_string_attr(al.get_attrs(), SA_BODY);
3✔
2978
            } else {
2979
                this->lss_token_meta = lnav::document::metadata{};
×
2980
            }
2981
            this->lss_token_meta_line = file_line_number;
3✔
2982
            this->lss_token_meta_size = sf.length();
3✔
2983
        }
2984

2985
        const auto initial_size = crumbs.size();
10✔
2986
        auto sf_body
2987
            = sf.sub_range(body_opt->saw_string_attr->sa_range.lr_start,
10✔
2988
                           body_opt->saw_string_attr->sa_range.lr_end);
10✔
2989
        file_off_t line_offset = body_opt->saw_string_attr->sa_range.lr_start;
10✔
2990
        file_off_t line_end_offset = sf.length();
10✔
2991
        ssize_t line_number = 0;
10✔
2992

2993
        for (const auto& sf_line : sf_body.split_lines()) {
20✔
2994
            if (line_number >= msg_line_number) {
13✔
2995
                line_end_offset = line_offset + sf_line.length();
3✔
2996
                break;
3✔
2997
            }
2998
            line_number += 1;
10✔
2999
            line_offset += sf_line.length();
10✔
3000
        }
10✔
3001

3002
        this->lss_token_meta.m_sections_tree.visit_overlapping(
10✔
3003
            line_offset,
3004
            line_end_offset,
3005
            [this,
2✔
3006
             initial_size,
3007
             meta = &this->lss_token_meta,
10✔
3008
             &crumbs,
3009
             line_from_top](const auto& iv) {
3010
                auto path = crumbs | lnav::itertools::skip(initial_size)
6✔
3011
                    | lnav::itertools::map(&breadcrumb::crumb::c_key)
4✔
3012
                    | lnav::itertools::append(iv.value);
2✔
3013
                auto curr_node = lnav::document::hier_node::lookup_path(
2✔
3014
                    meta->m_sections_root.get(), path);
2✔
3015

3016
                crumbs.emplace_back(
4✔
3017
                    iv.value,
2✔
3018
                    [meta, path]() { return meta->possibility_provider(path); },
4✔
3019
                    [this, curr_node, path, line_from_top](const auto& key) {
4✔
3020
                        if (!curr_node) {
×
3021
                            return;
×
3022
                        }
3023
                        auto* parent_node = curr_node.value()->hn_parent;
×
3024
                        if (parent_node == nullptr) {
×
3025
                            return;
×
3026
                        }
3027
                        key.match(
3028
                            [parent_node](const std::string& str) {
×
3029
                                return parent_node->find_line_number(str);
×
3030
                            },
3031
                            [parent_node](size_t index) {
×
3032
                                return parent_node->find_line_number(index);
×
3033
                            })
3034
                            | [this, line_from_top](auto line_number) {
×
3035
                                  this->tss_view->set_selection(
×
3036
                                      vis_line_t(line_from_top + line_number));
×
3037
                              };
3038
                    });
3039
                if (curr_node
2✔
3040
                    && !curr_node.value()->hn_parent->is_named_only()) {
2✔
3041
                    auto node = lnav::document::hier_node::lookup_path(
×
3042
                        meta->m_sections_root.get(), path);
×
3043

3044
                    crumbs.back().c_expected_input
×
3045
                        = curr_node.value()
×
3046
                              ->hn_parent->hn_named_children.empty()
×
3047
                        ? breadcrumb::crumb::expected_input_t::index
×
3048
                        : breadcrumb::crumb::expected_input_t::index_or_exact;
3049
                    crumbs.back().with_possible_range(
×
3050
                        node | lnav::itertools::map([](const auto hn) {
×
3051
                            return hn->hn_parent->hn_children.size();
×
3052
                        })
3053
                        | lnav::itertools::unwrap_or(size_t{0}));
×
3054
                }
3055
            });
2✔
3056

3057
        auto path = crumbs | lnav::itertools::skip(initial_size)
20✔
3058
            | lnav::itertools::map(&breadcrumb::crumb::c_key);
20✔
3059
        auto node = lnav::document::hier_node::lookup_path(
10✔
3060
            this->lss_token_meta.m_sections_root.get(), path);
10✔
3061

3062
        if (node && !node.value()->hn_children.empty()) {
10✔
3063
            auto poss_provider = [curr_node = node.value()]() {
1✔
3064
                std::vector<breadcrumb::possibility> retval;
1✔
3065
                for (const auto& child : curr_node->hn_named_children) {
1✔
3066
                    retval.emplace_back(child.first);
×
3067
                }
3068
                return retval;
1✔
3069
            };
3070
            auto path_performer
3071
                = [this, curr_node = node.value(), line_from_top](
2✔
3072
                      const breadcrumb::crumb::key_t& value) {
3073
                      value.match(
×
3074
                          [curr_node](const std::string& str) {
×
3075
                              return curr_node->find_line_number(str);
×
3076
                          },
3077
                          [curr_node](size_t index) {
×
3078
                              return curr_node->find_line_number(index);
×
3079
                          })
3080
                          | [this, line_from_top](size_t line_number) {
×
3081
                                this->tss_view->set_selection(
×
3082
                                    vis_line_t(line_from_top + line_number));
×
3083
                            };
3084
                  };
1✔
3085
            crumbs.emplace_back("", "\u22ef", poss_provider, path_performer);
1✔
3086
            crumbs.back().c_expected_input
1✔
3087
                = node.value()->hn_named_children.empty()
2✔
3088
                ? breadcrumb::crumb::expected_input_t::index
1✔
3089
                : breadcrumb::crumb::expected_input_t::index_or_exact;
3090
        }
3091
    }
10✔
3092
}
19✔
3093

3094
void
3095
logfile_sub_source::quiesce()
38✔
3096
{
3097
    for (auto& ld : this->lss_files) {
76✔
3098
        auto* lf = ld->get_file_ptr();
38✔
3099

3100
        if (lf == nullptr) {
38✔
3101
            continue;
×
3102
        }
3103

3104
        lf->quiesce();
38✔
3105
    }
3106
}
38✔
3107

3108
bookmark_metadata&
3109
logfile_sub_source::get_bookmark_metadata(content_line_t cl)
29✔
3110
{
3111
    auto line_pair = this->find_line_with_file(cl).value();
29✔
3112
    auto line_number = static_cast<uint32_t>(
3113
        std::distance(line_pair.first->begin(), line_pair.second));
29✔
3114

3115
    return line_pair.first->get_bookmark_metadata()[line_number];
58✔
3116
}
29✔
3117

3118
logfile_sub_source::bookmark_metadata_context
3119
logfile_sub_source::get_bookmark_metadata_context(
4,612✔
3120
    vis_line_t vl, bookmark_metadata::categories desired) const
3121
{
3122
    const auto& vb = this->tss_view->get_bookmarks();
4,612✔
3123
    const auto& bv = vb[desired == bookmark_metadata::categories::partition
3124
                            ? &textview_curses::BM_PARTITION
3125
                            : &textview_curses::BM_META];
4,612✔
3126
    auto vl_iter = bv.bv_tree.lower_bound(vl + 1_vl);
4,612✔
3127

3128
    std::optional<vis_line_t> next_line;
4,612✔
3129
    for (auto next_vl_iter = vl_iter; next_vl_iter != bv.bv_tree.end();
4,612✔
3130
         ++next_vl_iter)
×
3131
    {
3132
        auto bm_opt = this->find_bookmark_metadata(*next_vl_iter);
3✔
3133
        if (!bm_opt) {
3✔
3134
            continue;
×
3135
        }
3136

3137
        if (bm_opt.value()->has(desired)) {
3✔
3138
            next_line = *next_vl_iter;
3✔
3139
            break;
3✔
3140
        }
3141
    }
3142
    if (vl_iter == bv.bv_tree.begin()) {
4,612✔
3143
        return bookmark_metadata_context{std::nullopt, std::nullopt, next_line};
4,595✔
3144
    }
3145

3146
    --vl_iter;
17✔
3147
    while (true) {
3148
        auto bm_opt = this->find_bookmark_metadata(*vl_iter);
17✔
3149
        if (bm_opt) {
17✔
3150
            if (bm_opt.value()->has(desired)) {
14✔
3151
                return bookmark_metadata_context{
3152
                    *vl_iter, bm_opt.value(), next_line};
17✔
3153
            }
3154
        }
3155

3156
        if (vl_iter == bv.bv_tree.begin()) {
3✔
3157
            return bookmark_metadata_context{
3158
                std::nullopt, std::nullopt, next_line};
3✔
3159
        }
3160
        --vl_iter;
×
3161
    }
3162
    return bookmark_metadata_context{std::nullopt, std::nullopt, next_line};
3163
}
3164

3165
std::optional<bookmark_metadata*>
3166
logfile_sub_source::find_bookmark_metadata(content_line_t cl) const
24,945✔
3167
{
3168
    auto line_pair = this->find_line_with_file(cl).value();
24,945✔
3169
    auto line_number = static_cast<uint32_t>(
3170
        std::distance(line_pair.first->begin(), line_pair.second));
24,945✔
3171

3172
    auto& bm = line_pair.first->get_bookmark_metadata();
24,945✔
3173
    auto bm_iter = bm.find(line_number);
24,945✔
3174
    if (bm_iter == bm.end()) {
24,945✔
3175
        return std::nullopt;
24,533✔
3176
    }
3177

3178
    return &bm_iter->second;
412✔
3179
}
24,945✔
3180

3181
void
3182
logfile_sub_source::erase_bookmark_metadata(content_line_t cl)
26✔
3183
{
3184
    auto line_pair = this->find_line_with_file(cl).value();
26✔
3185
    auto line_number = static_cast<uint32_t>(
3186
        std::distance(line_pair.first->begin(), line_pair.second));
26✔
3187

3188
    auto& bm = line_pair.first->get_bookmark_metadata();
26✔
3189
    auto bm_iter = bm.find(line_number);
26✔
3190
    if (bm_iter != bm.end()) {
26✔
3191
        bm.erase(bm_iter);
6✔
3192
    }
3193
}
26✔
3194

3195
void
3196
logfile_sub_source::clear_bookmark_metadata()
6✔
3197
{
3198
    for (auto& ld : *this) {
14✔
3199
        if (ld->get_file_ptr() == nullptr) {
8✔
3200
            continue;
×
3201
        }
3202

3203
        ld->get_file_ptr()->get_bookmark_metadata().clear();
8✔
3204
    }
3205
}
6✔
3206

3207
void
3208
logfile_sub_source::increase_line_context()
×
3209
{
3210
    auto old_context = this->lss_line_context;
×
3211

3212
    switch (this->lss_line_context) {
×
3213
        case line_context_t::filename:
×
3214
            // nothing to do
3215
            break;
×
3216
        case line_context_t::basename:
×
3217
            this->lss_line_context = line_context_t::filename;
×
3218
            break;
×
3219
        case line_context_t::none:
×
3220
            this->lss_line_context = line_context_t::basename;
×
3221
            break;
×
3222
        case line_context_t::time_column:
×
3223
            this->lss_line_context = line_context_t::none;
×
3224
            break;
×
3225
    }
3226
    if (old_context != this->lss_line_context) {
×
3227
        this->clear_line_size_cache();
×
3228
    }
3229
}
3230

3231
bool
3232
logfile_sub_source::decrease_line_context()
×
3233
{
3234
    static const auto& cfg
3235
        = injector::get<const logfile_sub_source_ns::config&>();
×
3236
    auto old_context = this->lss_line_context;
×
3237

3238
    switch (this->lss_line_context) {
×
3239
        case line_context_t::filename:
×
3240
            this->lss_line_context = line_context_t::basename;
×
3241
            break;
×
3242
        case line_context_t::basename:
×
3243
            this->lss_line_context = line_context_t::none;
×
3244
            break;
×
3245
        case line_context_t::none:
×
3246
            if (cfg.c_time_column
×
3247
                != logfile_sub_source_ns::time_column_feature_t::Disabled)
3248
            {
3249
                this->lss_line_context = line_context_t::time_column;
×
3250
            }
3251
            break;
×
3252
        case line_context_t::time_column:
×
3253
            break;
×
3254
    }
3255
    if (old_context != this->lss_line_context) {
×
3256
        this->clear_line_size_cache();
×
3257

3258
        return true;
×
3259
    }
3260

3261
    return false;
×
3262
}
3263

3264
size_t
3265
logfile_sub_source::get_filename_offset() const
296✔
3266
{
3267
    switch (this->lss_line_context) {
296✔
3268
        case line_context_t::filename:
×
3269
            return this->lss_filename_width;
×
3270
        case line_context_t::basename:
×
3271
            return this->lss_basename_width;
×
3272
        default:
296✔
3273
            return 0;
296✔
3274
    }
3275
}
3276

3277
size_t
3278
logfile_sub_source::file_count() const
4,359✔
3279
{
3280
    size_t retval = 0;
4,359✔
3281
    const_iterator iter;
4,359✔
3282

3283
    for (iter = this->cbegin(); iter != this->cend(); ++iter) {
8,270✔
3284
        if (*iter != nullptr && (*iter)->get_file() != nullptr) {
3,911✔
3285
            retval += 1;
3,905✔
3286
        }
3287
    }
3288

3289
    return retval;
4,359✔
3290
}
3291

3292
size_t
3293
logfile_sub_source::text_size_for_line(textview_curses& tc,
×
3294
                                       int row,
3295
                                       text_sub_source::line_flags_t flags)
3296
{
3297
    size_t index = row % LINE_SIZE_CACHE_SIZE;
×
3298

3299
    if (this->lss_line_size_cache[index].first != row) {
×
3300
        std::string value;
×
3301

3302
        this->text_value_for_line(tc, row, value, flags);
×
3303
        scrub_ansi_string(value, nullptr);
×
3304
        auto line_width = string_fragment::from_str(value).column_width();
×
3305
        if (this->lss_line_context == line_context_t::time_column) {
×
3306
            auto time_attr
3307
                = find_string_attr(this->lss_token_al.al_attrs, &L_TIMESTAMP);
×
3308
            if (time_attr != this->lss_token_al.al_attrs.end()) {
×
3309
                line_width -= time_attr->sa_range.length();
×
3310
                auto format = this->lss_token_file->get_format();
×
3311
                if (format->lf_level_hideable) {
×
3312
                    auto level_attr = find_string_attr(
×
3313
                        this->lss_token_al.al_attrs, &L_LEVEL);
×
3314
                    if (level_attr != this->lss_token_al.al_attrs.end()) {
×
3315
                        line_width -= level_attr->sa_range.length();
×
3316
                    }
3317
                }
3318
            }
3319
        }
3320
        this->lss_line_size_cache[index].second = line_width;
×
3321
        this->lss_line_size_cache[index].first = row;
×
3322
    }
3323
    return this->lss_line_size_cache[index].second;
×
3324
}
3325

3326
int
3327
logfile_sub_source::get_filtered_count_for(size_t filter_index) const
1✔
3328
{
3329
    int retval = 0;
1✔
3330

3331
    for (const auto& ld : this->lss_files) {
2✔
3332
        retval += ld->ld_filter_state.lfo_filter_state
1✔
3333
                      .tfs_filter_hits[filter_index];
1✔
3334
    }
3335

3336
    return retval;
1✔
3337
}
3338

3339
std::optional<vis_line_t>
3340
logfile_sub_source::row_for(const row_info& ri)
291✔
3341
{
3342
    auto lb = std::lower_bound(this->lss_filtered_index.begin(),
582✔
3343
                               this->lss_filtered_index.end(),
3344
                               ri.ri_time,
291✔
3345
                               filtered_logline_cmp(*this));
3346
    if (lb != this->lss_filtered_index.end()) {
291✔
3347
        auto first_lb = lb;
286✔
3348
        while (true) {
3349
            auto cl = this->lss_index[*lb].value();
304✔
3350
            if (content_line_t(ri.ri_id) == cl) {
304✔
3351
                first_lb = lb;
254✔
3352
                break;
254✔
3353
            }
3354
            auto ll = this->find_line(cl);
50✔
3355
            if (ll->get_timeval() != ri.ri_time) {
50✔
3356
                break;
32✔
3357
            }
3358
            auto next_lb = std::next(lb);
18✔
3359
            if (next_lb == this->lss_filtered_index.end()) {
18✔
3360
                break;
×
3361
            }
3362
            lb = next_lb;
18✔
3363
        }
18✔
3364

3365
        const auto dst
3366
            = std::distance(this->lss_filtered_index.begin(), first_lb);
286✔
3367
        return vis_line_t(dst);
286✔
3368
    }
3369

3370
    return std::nullopt;
5✔
3371
}
3372

3373
std::unique_ptr<logline_window>
3374
logfile_sub_source::window_at(vis_line_t start_vl, vis_line_t end_vl)
49✔
3375
{
3376
    return std::make_unique<logline_window>(*this, start_vl, end_vl);
49✔
3377
}
3378

3379
std::unique_ptr<logline_window>
3380
logfile_sub_source::window_at(vis_line_t start_vl)
250✔
3381
{
3382
    return std::make_unique<logline_window>(*this, start_vl, start_vl + 1_vl);
250✔
3383
}
3384

3385
std::unique_ptr<logline_window>
3386
logfile_sub_source::window_to_end(vis_line_t start_vl)
1✔
3387
{
3388
    return std::make_unique<logline_window>(
3389
        *this, start_vl, vis_line_t(this->text_line_count()));
1✔
3390
}
3391

3392
std::optional<vis_line_t>
3393
logfile_sub_source::row_for_anchor(const std::string& id)
3✔
3394
{
3395
    if (startswith(id, "#msg")) {
3✔
3396
        static const auto ANCHOR_RE
3397
            = lnav::pcre2pp::code::from_const(R"(#msg([0-9a-fA-F]+)-(.+))");
3✔
3398
        thread_local auto md = lnav::pcre2pp::match_data::unitialized();
3✔
3399

3400
        if (ANCHOR_RE.capture_from(id).into(md).found_p()) {
3✔
3401
            auto scan_res = scn::scan<int64_t>(md[1]->to_string_view(), "{:x}");
3✔
3402
            if (scan_res) {
3✔
3403
                auto ts_low = std::chrono::microseconds{scan_res->value()};
3✔
3404
                auto ts_high = ts_low + 1us;
3✔
3405

3406
                auto low_vl = this->row_for_time(to_timeval(ts_low));
3✔
3407
                auto high_vl = this->row_for_time(to_timeval(ts_high));
3✔
3408
                if (low_vl) {
3✔
3409
                    auto lw = this->window_at(
3410
                        low_vl.value(),
3✔
3411
                        high_vl.value_or(low_vl.value() + 1_vl));
6✔
3412

3413
                    for (const auto& li : *lw) {
3✔
3414
                        auto hash_res = li.get_line_hash();
3✔
3415
                        if (hash_res.isErr()) {
3✔
3416
                            auto errmsg = hash_res.unwrapErr();
×
3417

3418
                            log_error("unable to get line hash: %s",
×
3419
                                      errmsg.c_str());
3420
                            continue;
×
3421
                        }
3422

3423
                        auto hash = hash_res.unwrap();
3✔
3424
                        if (hash == md[2]) {
3✔
3425
                            return li.get_vis_line();
3✔
3426
                        }
3427
                    }
12✔
3428
                }
3✔
3429
            }
3430
        }
3431

3432
        return std::nullopt;
×
3433
    }
3434

3435
    auto& vb = this->tss_view->get_bookmarks();
×
3436
    const auto& bv = vb[&textview_curses::BM_PARTITION];
×
3437

3438
    for (const auto& vl : bv.bv_tree) {
×
3439
        auto meta_opt = this->find_bookmark_metadata(vl);
×
3440
        if (!meta_opt || meta_opt.value()->bm_name.empty()) {
×
3441
            continue;
×
3442
        }
3443

3444
        const auto& name = meta_opt.value()->bm_name;
×
3445
        if (id == text_anchors::to_anchor_string(name)) {
×
3446
            return vl;
×
3447
        }
3448
    }
3449

3450
    return std::nullopt;
×
3451
}
3452

3453
std::optional<vis_line_t>
3454
logfile_sub_source::adjacent_anchor(vis_line_t vl, text_anchors::direction dir)
2✔
3455
{
3456
    if (vl < this->lss_filtered_index.size()) {
2✔
3457
        auto file_and_line_pair = this->find_line_with_file(vl);
2✔
3458
        if (file_and_line_pair) {
2✔
3459
            const auto& [lf, line] = file_and_line_pair.value();
2✔
3460
            if (line->is_continued()) {
2✔
3461
                auto retval = vl;
×
3462
                switch (dir) {
×
3463
                    case direction::prev: {
×
3464
                        auto first_line = line;
×
3465
                        while (first_line->is_continued()) {
×
3466
                            --first_line;
×
3467
                            retval -= 1_vl;
×
3468
                        }
3469
                        return retval;
×
3470
                    }
3471
                    case direction::next: {
×
3472
                        auto first_line = line;
×
3473
                        while (first_line->is_continued()) {
×
3474
                            ++first_line;
×
3475
                            retval += 1_vl;
×
3476
                        }
3477
                        return retval;
×
3478
                    }
3479
                }
3480
            }
3481
        }
3482
    }
2✔
3483

3484
    auto bmc = this->get_bookmark_metadata_context(
2✔
3485
        vl, bookmark_metadata::categories::partition);
3486
    switch (dir) {
2✔
3487
        case direction::prev: {
×
3488
            if (bmc.bmc_current && bmc.bmc_current.value() != vl) {
×
3489
                return bmc.bmc_current;
×
3490
            }
3491
            if (!bmc.bmc_current || bmc.bmc_current.value() == 0_vl) {
×
3492
                return 0_vl;
×
3493
            }
3494
            auto prev_bmc = this->get_bookmark_metadata_context(
×
3495
                bmc.bmc_current.value() - 1_vl,
×
3496
                bookmark_metadata::categories::partition);
3497
            if (!prev_bmc.bmc_current) {
×
3498
                return 0_vl;
×
3499
            }
3500
            return prev_bmc.bmc_current;
×
3501
        }
3502
        case direction::next:
2✔
3503
            return bmc.bmc_next_line;
2✔
3504
    }
3505
    return std::nullopt;
×
3506
}
3507

3508
std::optional<std::string>
3509
logfile_sub_source::anchor_for_row(vis_line_t vl)
88✔
3510
{
3511
    auto line_meta = this->get_bookmark_metadata_context(
88✔
3512
        vl, bookmark_metadata::categories::partition);
3513
    if (!line_meta.bmc_current_metadata) {
88✔
3514
        auto lw = window_at(vl);
85✔
3515

3516
        for (const auto& li : *lw) {
85✔
3517
            auto hash_res = li.get_line_hash();
85✔
3518
            if (hash_res.isErr()) {
85✔
3519
                auto errmsg = hash_res.unwrapErr();
×
3520
                log_error("unable to compute line hash: %s", errmsg.c_str());
×
3521
                break;
×
3522
            }
3523
            auto hash = hash_res.unwrap();
85✔
3524
            auto retval = fmt::format(
3525
                FMT_STRING("#msg{:016x}-{}"),
170✔
3526
                li.get_logline().get_time<std::chrono::microseconds>().count(),
85✔
3527
                hash);
×
3528

3529
            return retval;
85✔
3530
        }
340✔
3531

3532
        return std::nullopt;
×
3533
    }
85✔
3534

3535
    return text_anchors::to_anchor_string(
3✔
3536
        line_meta.bmc_current_metadata.value()->bm_name);
3✔
3537
}
3538

3539
std::unordered_set<std::string>
3540
logfile_sub_source::get_anchors()
×
3541
{
3542
    auto& vb = this->tss_view->get_bookmarks();
×
3543
    const auto& bv = vb[&textview_curses::BM_PARTITION];
×
3544
    std::unordered_set<std::string> retval;
×
3545

3546
    for (const auto& vl : bv.bv_tree) {
×
3547
        auto meta_opt = this->find_bookmark_metadata(vl);
×
3548
        if (!meta_opt || meta_opt.value()->bm_name.empty()) {
×
3549
            continue;
×
3550
        }
3551

3552
        const auto& name = meta_opt.value()->bm_name;
×
3553
        retval.emplace(text_anchors::to_anchor_string(name));
×
3554
    }
3555

3556
    return retval;
×
3557
}
×
3558

3559
bool
3560
logfile_sub_source::text_handle_mouse(
×
3561
    textview_curses& tc,
3562
    const listview_curses::display_line_content_t& mouse_line,
3563
    mouse_event& me)
3564
{
3565
    if (mouse_line.is<listview_curses::static_overlay_content>()
×
3566
        && this->text_line_count() > 0)
×
3567
    {
3568
        auto top = tc.get_top();
×
3569
        if (top > 0) {
×
3570
            auto win = this->window_at(top - 1_vl);
×
3571
            for (const auto& li : *win) {
×
3572
                tc.set_top(li.get_vis_line());
×
3573
                tc.set_selection(li.get_vis_line());
×
3574
                return true;
×
3575
            }
3576
        }
3577
    }
3578

3579
    if (tc.get_overlay_selection()) {
×
3580
        auto nci = ncinput{};
×
3581
        if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 2, 4)) {
×
3582
            nci.id = ' ';
×
3583
            nci.eff_text[0] = ' ';
×
3584
            this->list_input_handle_key(tc, nci);
×
3585
        } else if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 5, 6)) {
×
3586
            nci.id = '#';
×
3587
            nci.eff_text[0] = '#';
×
3588
            this->list_input_handle_key(tc, nci);
×
3589
        }
3590
    }
3591
    return true;
×
3592
}
3593

3594
void
3595
logfile_sub_source::reload_config(error_reporter& reporter)
700✔
3596
{
3597
    static const auto& cfg
3598
        = injector::get<const logfile_sub_source_ns::config&>();
700✔
3599

3600
    switch (cfg.c_time_column) {
700✔
3601
        case logfile_sub_source_ns::time_column_feature_t::Default:
×
3602
            if (this->lss_line_context == line_context_t::none) {
×
3603
                this->lss_line_context = line_context_t::time_column;
×
3604
            }
3605
            break;
×
3606
        case logfile_sub_source_ns::time_column_feature_t::Disabled:
700✔
3607
            if (this->lss_line_context == line_context_t::time_column) {
700✔
3608
                this->lss_line_context = line_context_t::none;
×
3609
            }
3610
            break;
700✔
3611
        case logfile_sub_source_ns::time_column_feature_t::Enabled:
×
3612
            break;
×
3613
    }
3614
}
700✔
3615

3616
void
3617
logfile_sub_source::clear_preview()
1✔
3618
{
3619
    text_sub_source::clear_preview();
1✔
3620

3621
    this->set_preview_sql_filter(nullptr);
1✔
3622
    auto last = std::remove_if(this->lss_highlighters.begin(),
1✔
3623
                               this->lss_highlighters.end(),
3624
                               [](const auto& hl) { return hl.h_preview; });
×
3625
    this->lss_highlighters.erase(last, this->lss_highlighters.end());
1✔
3626
}
1✔
3627

3628
void
3629
logfile_sub_source::add_commands_for_session(
49✔
3630
    const std::function<void(const std::string&)>& receiver)
3631
{
3632
    text_sub_source::add_commands_for_session(receiver);
49✔
3633

3634
    auto mark_expr = this->get_sql_marker_text();
49✔
3635
    if (!mark_expr.empty()) {
49✔
3636
        receiver(fmt::format(FMT_STRING("mark-expr {}"), mark_expr));
4✔
3637
    }
3638
    auto filter_expr = this->get_sql_filter_text();
49✔
3639
    if (!filter_expr.empty()) {
49✔
3640
        receiver(fmt::format(FMT_STRING("filter-expr {}"), filter_expr));
×
3641
    }
3642

3643
    for (const auto& hl : this->lss_highlighters) {
49✔
3644
        auto cmd = "highlight-field "s;
×
3645
        if (hl.h_attrs.has_style(text_attrs::style::bold)) {
×
3646
            cmd += "--bold ";
×
3647
        }
3648
        if (hl.h_attrs.has_style(text_attrs::style::underline)) {
×
3649
            cmd += "--underline ";
×
3650
        }
3651
        if (hl.h_attrs.has_style(text_attrs::style::blink)) {
×
3652
            cmd += "--blink ";
×
3653
        }
3654
        if (hl.h_attrs.has_style(text_attrs::style::struck)) {
×
3655
            cmd += "--strike ";
×
3656
        }
3657
        if (hl.h_attrs.has_style(text_attrs::style::italic)) {
×
3658
            cmd += "--italic ";
×
3659
        }
3660
        cmd += hl.h_field.to_string() + " " + hl.h_regex->get_pattern();
×
3661
        receiver(cmd);
×
3662
    }
3663

3664
    for (const auto& format : log_format::get_root_formats()) {
3,821✔
3665
        auto field_states = format->get_field_states();
3,772✔
3666

3667
        for (const auto& fs_pair : field_states) {
52,160✔
3668
            if (!fs_pair.second.lvm_user_hidden) {
48,388✔
3669
                continue;
48,386✔
3670
            }
3671

3672
            if (fs_pair.second.lvm_user_hidden.value()) {
2✔
3673
                receiver(fmt::format(FMT_STRING("hide-fields {}.{}"),
8✔
3674
                                     format->get_name().to_string(),
4✔
3675
                                     fs_pair.first.to_string()));
4✔
3676
            } else if (fs_pair.second.lvm_hidden) {
×
3677
                receiver(fmt::format(FMT_STRING("show-fields {}.{}"),
×
3678
                                     format->get_name().to_string(),
×
3679
                                     fs_pair.first.to_string()));
×
3680
            }
3681
        }
3682
    }
3,772✔
3683
}
49✔
3684

3685
void
3686
logfile_sub_source::update_filter_hash_state(hasher& h) const
9✔
3687
{
3688
    text_sub_source::update_filter_hash_state(h);
9✔
3689

3690
    for (const auto& ld : this->lss_files) {
12✔
3691
        if (ld->get_file_ptr() == nullptr || !ld->is_visible()) {
3✔
3692
            h.update(0);
×
3693
        } else {
3694
            h.update(1);
3✔
3695
        }
3696
    }
3697
    h.update(this->tss_min_log_level);
9✔
3698
    h.update(this->lss_marked_only);
9✔
3699
}
9✔
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