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

tstack / lnav / 17657281416-2508

11 Sep 2025 09:05PM UTC coverage: 64.984% (-0.2%) from 65.166%
17657281416-2508

push

github

tstack
[log2src] show source vars in message details

Improve selection of external editors and preserve
cursor location when opening from prompt.

Add CLion and RustRover as external editors.

Add breakpoint support

262 of 629 new or added lines in 26 files covered. (41.65%)

4 existing lines in 3 files now uncovered.

45653 of 70253 relevant lines covered (64.98%)

404292.82 hits per line

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

63.63
/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 <future>
33

34
#include "logfile_sub_source.hh"
35

36
#include <sqlite3.h>
37

38
#include "base/ansi_scrubber.hh"
39
#include "base/ansi_vars.hh"
40
#include "base/fs_util.hh"
41
#include "base/injector.hh"
42
#include "base/itertools.enumerate.hh"
43
#include "base/itertools.hh"
44
#include "base/string_util.hh"
45
#include "bookmarks.json.hh"
46
#include "command_executor.hh"
47
#include "config.h"
48
#include "field_overlay_source.hh"
49
#include "hasher.hh"
50
#include "k_merge_tree.h"
51
#include "lnav_util.hh"
52
#include "log_accel.hh"
53
#include "logfile_sub_source.cfg.hh"
54
#include "logline_window.hh"
55
#include "md2attr_line.hh"
56
#include "ptimec.hh"
57
#include "scn/scan.h"
58
#include "shlex.hh"
59
#include "sql_util.hh"
60
#include "vtab_module.hh"
61
#include "yajlpp/yajlpp.hh"
62
#include "yajlpp/yajlpp_def.hh"
63

64
using namespace std::chrono_literals;
65
using namespace lnav::roles::literals;
66

67
const DIST_SLICE(bm_types) bookmark_type_t logfile_sub_source::BM_FILES("file");
68

69
static int
70
pretty_sql_callback(exec_context& ec, sqlite3_stmt* stmt)
73✔
71
{
72
    if (!sqlite3_stmt_busy(stmt)) {
73✔
73
        return 0;
32✔
74
    }
75

76
    const auto ncols = sqlite3_column_count(stmt);
41✔
77

78
    for (int lpc = 0; lpc < ncols; lpc++) {
82✔
79
        if (!ec.ec_accumulator->empty()) {
41✔
80
            ec.ec_accumulator->append(", ");
10✔
81
        }
82

83
        const char* res = (const char*) sqlite3_column_text(stmt, lpc);
41✔
84
        if (res == nullptr) {
41✔
85
            continue;
×
86
        }
87

88
        ec.ec_accumulator->append(res);
41✔
89
    }
90

91
    for (int lpc = 0; lpc < ncols; lpc++) {
82✔
92
        const auto* colname = sqlite3_column_name(stmt, lpc);
41✔
93
        auto* raw_value = sqlite3_column_value(stmt, lpc);
41✔
94
        auto value_type = sqlite3_value_type(raw_value);
41✔
95
        scoped_value_t value;
41✔
96

97
        switch (value_type) {
41✔
98
            case SQLITE_INTEGER:
×
99
                value = (int64_t) sqlite3_value_int64(raw_value);
×
100
                break;
×
101
            case SQLITE_FLOAT:
×
102
                value = sqlite3_value_double(raw_value);
×
103
                break;
×
104
            case SQLITE_NULL:
×
105
                value = null_value_t{};
×
106
                break;
×
107
            default:
41✔
108
                value = string_fragment::from_bytes(
41✔
109
                    sqlite3_value_text(raw_value),
110
                    sqlite3_value_bytes(raw_value));
82✔
111
                break;
41✔
112
        }
113
        if (!ec.ec_local_vars.empty() && !ec.ec_dry_run) {
41✔
114
            if (sql_ident_needs_quote(colname)) {
41✔
115
                continue;
27✔
116
            }
117
            auto& vars = ec.ec_local_vars.top();
14✔
118

119
            if (vars.find(colname) != vars.end()) {
42✔
120
                continue;
10✔
121
            }
122

123
            if (value.is<string_fragment>()) {
4✔
124
                value = value.get<string_fragment>().to_string();
4✔
125
            }
126
            vars[colname] = value;
8✔
127
        }
128
    }
41✔
129
    return 0;
41✔
130
}
131

132
static std::future<std::string>
133
pretty_pipe_callback(exec_context& ec, const std::string& cmdline, auto_fd& fd)
×
134
{
135
    auto retval = std::async(std::launch::async, [&]() {
×
136
        char buffer[1024];
137
        std::ostringstream ss;
×
138
        ssize_t rc;
139

140
        while ((rc = read(fd, buffer, sizeof(buffer))) > 0) {
×
141
            ss.write(buffer, rc);
×
142
        }
143

144
        auto retval = ss.str();
×
145

146
        if (endswith(retval, "\n")) {
×
147
            retval.resize(retval.length() - 1);
×
148
        }
149

150
        return retval;
×
151
    });
×
152

153
    return retval;
×
154
}
155

156
logfile_sub_source::logfile_sub_source()
1,095✔
157
    : text_sub_source(1), lnav_config_listener(__FILE__),
158
      lss_meta_grepper(*this), lss_location_history(*this)
1,095✔
159
{
160
    this->tss_supports_filtering = true;
1,095✔
161
    this->clear_line_size_cache();
1,095✔
162
    this->clear_min_max_row_times();
1,095✔
163
}
1,095✔
164

165
std::shared_ptr<logfile>
166
logfile_sub_source::find(const char* fn, content_line_t& line_base)
22✔
167
{
168
    std::shared_ptr<logfile> retval = nullptr;
22✔
169

170
    line_base = content_line_t(0);
22✔
171
    for (auto iter = this->lss_files.begin();
22✔
172
         iter != this->lss_files.end() && retval == nullptr;
45✔
173
         ++iter)
23✔
174
    {
175
        auto& ld = *(*iter);
23✔
176
        auto* lf = ld.get_file_ptr();
23✔
177

178
        if (lf == nullptr) {
23✔
179
            continue;
×
180
        }
181
        if (strcmp(lf->get_filename_as_string().c_str(), fn) == 0) {
23✔
182
            retval = ld.get_file();
22✔
183
        } else {
184
            line_base += content_line_t(MAX_LINES_PER_FILE);
1✔
185
        }
186
    }
187

188
    return retval;
22✔
189
}
×
190

191
struct filtered_logline_cmp {
192
    filtered_logline_cmp(const logfile_sub_source& lc) : llss_controller(lc) {}
240✔
193

194
    bool operator()(const uint32_t& lhs, const uint32_t& rhs) const
195
    {
196
        auto cl_lhs = llss_controller.lss_index[lhs].value();
197
        auto cl_rhs = llss_controller.lss_index[rhs].value();
198
        auto ll_lhs = this->llss_controller.find_line(cl_lhs);
199
        auto ll_rhs = this->llss_controller.find_line(cl_rhs);
200

201
        if (ll_lhs == nullptr) {
202
            return true;
203
        }
204
        if (ll_rhs == nullptr) {
205
            return false;
206
        }
207
        return (*ll_lhs) < (*ll_rhs);
208
    }
209

210
    bool operator()(const uint32_t& lhs, const timeval& rhs) const
578✔
211
    {
212
        const auto cl_lhs = llss_controller.lss_index[lhs].value();
578✔
213
        const auto* ll_lhs = this->llss_controller.find_line(cl_lhs);
578✔
214

215
        if (ll_lhs == nullptr) {
578✔
216
            return true;
×
217
        }
218
        return (*ll_lhs) < rhs;
578✔
219
    }
220

221
    const logfile_sub_source& llss_controller;
222
};
223

224
std::optional<vis_line_t>
225
logfile_sub_source::find_from_time(const timeval& start) const
27✔
226
{
227
    const auto lb = std::lower_bound(this->lss_filtered_index.begin(),
27✔
228
                                     this->lss_filtered_index.end(),
229
                                     start,
230
                                     filtered_logline_cmp(*this));
231
    if (lb != this->lss_filtered_index.end()) {
27✔
232
        auto retval = std::distance(this->lss_filtered_index.begin(), lb);
23✔
233
        return vis_line_t(retval);
23✔
234
    }
235

236
    return std::nullopt;
4✔
237
}
238

239
line_info
240
logfile_sub_source::text_value_for_line(textview_curses& tc,
2,788✔
241
                                        int row,
242
                                        std::string& value_out,
243
                                        line_flags_t flags)
244
{
245
    if (this->lss_indexing_in_progress) {
2,788✔
246
        value_out = "";
×
247
        this->lss_token_attrs.clear();
×
248
        return {};
×
249
    }
250

251
    line_info retval;
2,788✔
252
    content_line_t line(0);
2,788✔
253

254
    require_ge(row, 0);
2,788✔
255
    require_lt((size_t) row, this->lss_filtered_index.size());
2,788✔
256

257
    line = this->at(vis_line_t(row));
2,788✔
258

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

281
    require_false(this->lss_in_value_for_line);
2,756✔
282

283
    this->lss_in_value_for_line = true;
2,756✔
284
    this->lss_token_flags = flags;
2,756✔
285
    this->lss_token_file_data = this->find_data(line);
2,756✔
286
    this->lss_token_file = (*this->lss_token_file_data)->get_file();
2,756✔
287
    this->lss_token_line = this->lss_token_file->begin() + line;
2,756✔
288

289
    this->lss_token_attrs.clear();
2,756✔
290
    this->lss_token_values.clear();
2,756✔
291
    this->lss_share_manager.invalidate_refs();
2,756✔
292
    if (flags & text_sub_source::RF_FULL) {
2,756✔
293
        shared_buffer_ref sbr;
48✔
294

295
        this->lss_token_file->read_full_message(this->lss_token_line, sbr);
48✔
296
        this->lss_token_value = to_string(sbr);
48✔
297
        if (sbr.get_metadata().m_has_ansi) {
48✔
298
            scrub_ansi_string(this->lss_token_value, &this->lss_token_attrs);
17✔
299
            sbr.get_metadata().m_has_ansi = false;
17✔
300
        }
301
    } else {
48✔
302
        this->lss_token_value
303
            = this->lss_token_file->read_line(this->lss_token_line)
5,416✔
304
                  .map([](auto sbr) { return to_string(sbr); })
5,416✔
305
                  .unwrapOr({});
2,708✔
306
        if (this->lss_token_line->has_ansi()) {
2,708✔
307
            scrub_ansi_string(this->lss_token_value, &this->lss_token_attrs);
24✔
308
        }
309
    }
310
    this->lss_token_shift_start = 0;
2,756✔
311
    this->lss_token_shift_size = 0;
2,756✔
312

313
    auto format = this->lss_token_file->get_format();
2,756✔
314

315
    value_out = this->lss_token_value;
2,756✔
316

317
    auto& sbr = this->lss_token_values.lvv_sbr;
2,756✔
318

319
    sbr.share(this->lss_share_manager,
2,756✔
320
              (char*) this->lss_token_value.c_str(),
321
              this->lss_token_value.size());
322
    format->annotate(this->lss_token_file.get(),
5,512✔
323
                     line,
324
                     this->lss_token_attrs,
2,756✔
325
                     this->lss_token_values);
2,756✔
326
    if (flags & RF_REWRITE) {
2,756✔
327
        exec_context ec(
328
            &this->lss_token_values, pretty_sql_callback, pretty_pipe_callback);
48✔
329
        std::string rewritten_line;
48✔
330
        db_label_source rewrite_label_source;
48✔
331

332
        ec.with_perms(exec_context::perm_t::READ_ONLY);
48✔
333
        ec.ec_local_vars.push(std::map<std::string, scoped_value_t>());
48✔
334
        ec.ec_top_line = vis_line_t(row);
48✔
335
        ec.ec_label_source_stack.push_back(&rewrite_label_source);
48✔
336
        add_ansi_vars(ec.ec_global_vars);
48✔
337
        add_global_vars(ec);
48✔
338
        format->rewrite(ec, sbr, this->lss_token_attrs, rewritten_line);
48✔
339
        this->lss_token_value.assign(rewritten_line);
48✔
340
        value_out = this->lss_token_value;
48✔
341
    }
48✔
342

343
    {
344
        auto lr = line_range{0, (int) this->lss_token_value.length()};
2,756✔
345
        this->lss_token_attrs.emplace_back(lr, SA_ORIGINAL_LINE.value());
2,756✔
346
    }
347

348
    std::optional<exttm> adjusted_tm;
2,756✔
349
    auto time_attr = find_string_attr(this->lss_token_attrs, &L_TIMESTAMP);
2,756✔
350
    if (!this->lss_token_line->is_continued() && !format->lf_formatted_lines
3,804✔
351
        && (this->lss_token_file->is_time_adjusted()
848✔
352
            || ((format->lf_timestamp_flags & ETF_ZONE_SET
833✔
353
                 || format->lf_date_time.dts_default_zone != nullptr)
564✔
354
                && format->lf_date_time.dts_zoned_to_local)
353✔
355
            || format->lf_timestamp_flags & ETF_MACHINE_ORIENTED
480✔
356
            || !(format->lf_timestamp_flags & ETF_DAY_SET)
480✔
357
            || !(format->lf_timestamp_flags & ETF_MONTH_SET))
459✔
358
        && format->lf_date_time.dts_fmt_lock != -1)
3,804✔
359
    {
360
        if (time_attr != this->lss_token_attrs.end()) {
333✔
361
            const auto time_range = time_attr->sa_range;
333✔
362
            const auto time_sf = string_fragment::from_str_range(
333✔
363
                this->lss_token_value, time_range.lr_start, time_range.lr_end);
333✔
364
            adjusted_tm = format->tm_for_display(this->lss_token_line, time_sf);
333✔
365

366
            char buffer[128];
367
            const char* fmt;
368
            ssize_t len;
369

370
            if (format->lf_timestamp_flags & ETF_MACHINE_ORIENTED
333✔
371
                || !(format->lf_timestamp_flags & ETF_DAY_SET)
311✔
372
                || !(format->lf_timestamp_flags & ETF_MONTH_SET))
644✔
373
            {
374
                if (format->lf_timestamp_flags & ETF_NANOS_SET) {
28✔
375
                    fmt = "%Y-%m-%d %H:%M:%S.%N";
×
376
                } else if (format->lf_timestamp_flags & ETF_MICROS_SET) {
28✔
377
                    fmt = "%Y-%m-%d %H:%M:%S.%f";
23✔
378
                } else if (format->lf_timestamp_flags & ETF_MILLIS_SET) {
5✔
379
                    fmt = "%Y-%m-%d %H:%M:%S.%L";
2✔
380
                } else {
381
                    fmt = "%Y-%m-%d %H:%M:%S";
3✔
382
                }
383
                len = ftime_fmt(
28✔
384
                    buffer, sizeof(buffer), fmt, adjusted_tm.value());
28✔
385
            } else {
386
                len = format->lf_date_time.ftime(
610✔
387
                    buffer,
388
                    sizeof(buffer),
389
                    format->get_timestamp_formats(),
390
                    adjusted_tm.value());
305✔
391
            }
392

393
            value_out.replace(
666✔
394
                time_range.lr_start, time_range.length(), buffer, len);
333✔
395
            this->lss_token_shift_start = time_range.lr_start;
333✔
396
            this->lss_token_shift_size = len - time_range.length();
333✔
397
        }
398
    }
399

400
    // Insert space for the file/search-hit markers.
401
    value_out.insert(0, 1, ' ');
2,756✔
402
    this->lss_time_column_size = 0;
2,756✔
403
    if (this->lss_line_context == line_context_t::time_column) {
2,756✔
404
        if (time_attr != this->lss_token_attrs.end()) {
×
405
            this->lss_token_attrs.emplace_back(time_attr->sa_range,
×
406
                                               SA_REPLACED.value());
×
407

408
            const char* fmt;
409
            if (this->lss_all_timestamp_flags
×
410
                & (ETF_MICROS_SET | ETF_NANOS_SET))
×
411
            {
412
                fmt = "%H:%M:%S.%f";
×
413
            } else if (this->lss_all_timestamp_flags & ETF_MILLIS_SET) {
×
414
                fmt = "%H:%M:%S.%L";
×
415
            } else {
416
                fmt = "%H:%M:%S";
×
417
            }
418
            if (!adjusted_tm) {
×
419
                const auto time_range = time_attr->sa_range;
×
420
                const auto time_sf
421
                    = string_fragment::from_str_range(this->lss_token_value,
×
422
                                                      time_range.lr_start,
×
423
                                                      time_range.lr_end);
×
424
                adjusted_tm
425
                    = format->tm_for_display(this->lss_token_line, time_sf);
×
426
            }
427
            char buffer[128];
428
            this->lss_time_column_size
429
                = ftime_fmt(buffer, sizeof(buffer), fmt, adjusted_tm.value());
×
430
            if (this->tss_view->is_selectable()
×
431
                && this->tss_view->get_selection() == row)
×
432
            {
433
                buffer[this->lss_time_column_size] = ' ';
×
434
                buffer[this->lss_time_column_size + 1] = ' ';
×
435
                this->lss_time_column_size += 2;
×
436
            } else {
437
                constexpr char block[] = "\u258c ";
×
438

439
                strcpy(&buffer[this->lss_time_column_size], block);
×
440
                this->lss_time_column_size += sizeof(block) - 1;
×
441
            }
442
            if (time_attr->sa_range.lr_start != 0) {
×
443
                buffer[this->lss_time_column_size] = ' ';
×
444
                this->lss_time_column_size += 1;
×
445
                this->lss_time_column_padding = 1;
×
446
            } else {
447
                this->lss_time_column_padding = 0;
×
448
            }
449
            value_out.insert(1, buffer, this->lss_time_column_size);
×
450
        }
451
        if (format->lf_level_hideable) {
×
452
            auto level_attr = find_string_attr(this->lss_token_attrs, &L_LEVEL);
×
453
            if (level_attr != this->lss_token_attrs.end()) {
×
454
                this->lss_token_attrs.emplace_back(level_attr->sa_range,
×
455
                                                   SA_REPLACED.value());
×
456
            }
457
        }
458
    } else if (this->lss_line_context < line_context_t::none) {
2,756✔
459
        size_t file_offset_end;
460
        std::string name;
×
461
        if (this->lss_line_context == line_context_t::filename) {
×
462
            file_offset_end = this->lss_filename_width;
×
463
            name = fmt::to_string(this->lss_token_file->get_filename());
×
464
            if (file_offset_end < name.size()) {
×
465
                file_offset_end = name.size();
×
466
                this->lss_filename_width = name.size();
×
467
            }
468
        } else {
469
            file_offset_end = this->lss_basename_width;
×
470
            name = fmt::to_string(this->lss_token_file->get_unique_path());
×
471
            if (file_offset_end < name.size()) {
×
472
                file_offset_end = name.size();
×
473
                this->lss_basename_width = name.size();
×
474
            }
475
        }
476
        value_out.insert(0, file_offset_end - name.size(), ' ');
×
477
        value_out.insert(0, name);
×
478
    }
479

480
    if (this->tas_display_time_offset) {
2,756✔
481
        auto row_vl = vis_line_t(row);
210✔
482
        auto relstr = this->get_time_offset_for_line(tc, row_vl);
210✔
483
        value_out = fmt::format(FMT_STRING("{: >12}|{}"), relstr, value_out);
840✔
484
    }
210✔
485

486
    this->lss_in_value_for_line = false;
2,756✔
487

488
    return retval;
2,756✔
489
}
2,756✔
490

491
void
492
logfile_sub_source::text_attrs_for_line(textview_curses& lv,
2,756✔
493
                                        int row,
494
                                        string_attrs_t& value_out)
495
{
496
    if (this->lss_indexing_in_progress) {
2,756✔
497
        return;
×
498
    }
499

500
    auto& vc = view_colors::singleton();
2,756✔
501
    logline* next_line = nullptr;
2,756✔
502
    line_range lr;
2,756✔
503
    int time_offset_end = 0;
2,756✔
504
    text_attrs attrs;
2,756✔
505

506
    value_out = this->lss_token_attrs;
2,756✔
507

508
    if ((row + 1) < (int) this->lss_filtered_index.size()) {
2,756✔
509
        next_line = this->find_line(this->at(vis_line_t(row + 1)));
2,589✔
510
    }
511

512
    if (next_line != nullptr
2,756✔
513
        && (day_num(next_line->get_time<std::chrono::seconds>().count())
5,345✔
514
            > day_num(this->lss_token_line->get_time<std::chrono::seconds>()
5,345✔
515
                          .count())))
516
    {
517
        attrs |= text_attrs::style::underline;
15✔
518
    }
519

520
    const auto& line_values = this->lss_token_values;
2,756✔
521

522
    lr.lr_start = 0;
2,756✔
523
    lr.lr_end = -1;
2,756✔
524
    value_out.emplace_back(
2,756✔
525
        lr, SA_LEVEL.value(this->lss_token_line->get_msg_level()));
5,512✔
526

527
    lr.lr_start = time_offset_end;
2,756✔
528
    lr.lr_end = -1;
2,756✔
529

530
    if (!attrs.empty()) {
2,756✔
531
        value_out.emplace_back(lr, VC_STYLE.value(attrs));
15✔
532
    }
533

534
    if (this->lss_token_line->get_msg_level() == log_level_t::LEVEL_INVALID) {
2,756✔
535
        for (auto& token_attr : this->lss_token_attrs) {
37✔
536
            if (token_attr.sa_type != &SA_INVALID) {
20✔
537
                continue;
17✔
538
            }
539

540
            value_out.emplace_back(token_attr.sa_range,
3✔
541
                                   VC_ROLE.value(role_t::VCR_INVALID_MSG));
6✔
542
        }
543
    }
544

545
    for (const auto& line_value : line_values.lvv_values) {
12,197✔
546
        if ((!(this->lss_token_flags & RF_FULL)
20,522✔
547
             && line_value.lv_sub_offset
18,438✔
548
                 != this->lss_token_line->get_sub_offset())
9,219✔
549
            || !line_value.lv_origin.is_valid())
18,660✔
550
        {
551
            continue;
1,640✔
552
        }
553

554
        if (line_value.lv_meta.is_hidden()) {
7,801✔
555
            value_out.emplace_back(line_value.lv_origin,
117✔
556
                                   SA_HIDDEN.value(ui_icon_t::hidden));
234✔
557
        }
558

559
        if (!line_value.lv_meta.lvm_identifier
20,173✔
560
            || !line_value.lv_origin.is_valid())
7,801✔
561
        {
562
            continue;
4,571✔
563
        }
564

565
        value_out.emplace_back(line_value.lv_origin,
3,230✔
566
                               VC_ROLE.value(role_t::VCR_IDENTIFIER));
6,460✔
567
    }
568

569
    if (this->lss_token_shift_size) {
2,756✔
570
        shift_string_attrs(value_out,
56✔
571
                           this->lss_token_shift_start + 1,
56✔
572
                           this->lss_token_shift_size);
573
    }
574

575
    shift_string_attrs(value_out, 0, 1);
2,756✔
576

577
    lr.lr_start = 0;
2,756✔
578
    lr.lr_end = 1;
2,756✔
579
    {
580
        auto& bm = lv.get_bookmarks();
2,756✔
581
        const auto& bv = bm[&BM_FILES];
2,756✔
582
        bool is_first_for_file = bv.bv_tree.exists(vis_line_t(row));
2,756✔
583
        bool is_last_for_file = bv.bv_tree.exists(vis_line_t(row + 1));
2,756✔
584
        auto graph = NCACS_VLINE;
2,756✔
585
        if (is_first_for_file) {
2,756✔
586
            if (is_last_for_file) {
183✔
587
                graph = NCACS_HLINE;
8✔
588
            } else {
589
                graph = NCACS_ULCORNER;
175✔
590
            }
591
        } else if (is_last_for_file) {
2,573✔
592
            graph = NCACS_LLCORNER;
21✔
593
        }
594
        value_out.emplace_back(lr, VC_GRAPHIC.value(graph));
2,756✔
595

596
        if (!(this->lss_token_flags & RF_FULL)) {
2,756✔
597
            const auto& bv_search = bm[&textview_curses::BM_SEARCH];
2,708✔
598

599
            if (bv_search.bv_tree.exists(vis_line_t(row))) {
2,708✔
600
                lr.lr_start = 0;
9✔
601
                lr.lr_end = 1;
9✔
602
                value_out.emplace_back(
9✔
603
                    lr, VC_STYLE.value(text_attrs::with_reverse()));
18✔
604
            }
605
        }
606
    }
607

608
    value_out.emplace_back(lr,
2,756✔
609
                           VC_STYLE.value(vc.attrs_for_ident(
5,512✔
610
                               this->lss_token_file->get_filename())));
2,756✔
611

612
    if (this->lss_line_context < line_context_t::none) {
2,756✔
613
        size_t file_offset_end
×
614
            = (this->lss_line_context == line_context_t::filename)
×
615
            ? this->lss_filename_width
×
616
            : this->lss_basename_width;
617

618
        shift_string_attrs(value_out, 0, file_offset_end);
×
619

620
        lr.lr_start = 0;
×
621
        lr.lr_end = file_offset_end + 1;
×
622
        value_out.emplace_back(lr,
×
623
                               VC_STYLE.value(vc.attrs_for_ident(
×
624
                                   this->lss_token_file->get_filename())));
×
625
    } else if (this->lss_time_column_size > 0) {
2,756✔
626
        shift_string_attrs(value_out, 1, this->lss_time_column_size);
×
627

628
        ui_icon_t icon;
629
        switch (this->lss_token_line->get_msg_level()) {
×
630
            case LEVEL_TRACE:
×
631
                icon = ui_icon_t::log_level_trace;
×
632
                break;
×
633
            case LEVEL_DEBUG:
×
634
            case LEVEL_DEBUG2:
635
            case LEVEL_DEBUG3:
636
            case LEVEL_DEBUG4:
637
            case LEVEL_DEBUG5:
638
                icon = ui_icon_t::log_level_debug;
×
639
                break;
×
640
            case LEVEL_INFO:
×
641
                icon = ui_icon_t::log_level_info;
×
642
                break;
×
643
            case LEVEL_STATS:
×
644
                icon = ui_icon_t::log_level_stats;
×
645
                break;
×
646
            case LEVEL_NOTICE:
×
647
                icon = ui_icon_t::log_level_notice;
×
648
                break;
×
649
            case LEVEL_WARNING:
×
650
                icon = ui_icon_t::log_level_warning;
×
651
                break;
×
652
            case LEVEL_ERROR:
×
653
                icon = ui_icon_t::log_level_error;
×
654
                break;
×
655
            case LEVEL_CRITICAL:
×
656
                icon = ui_icon_t::log_level_critical;
×
657
                break;
×
658
            case LEVEL_FATAL:
×
659
                icon = ui_icon_t::log_level_fatal;
×
660
                break;
×
661
            default:
×
662
                icon = ui_icon_t::hidden;
×
663
                break;
×
664
        }
665
        auto extra_space_size = this->lss_time_column_padding;
×
666
        lr.lr_start = 1 + this->lss_time_column_size - 1 - extra_space_size;
×
667
        lr.lr_end = 1 + this->lss_time_column_size - extra_space_size;
×
668
        value_out.emplace_back(lr, VC_ICON.value(icon));
×
669
        if (this->tss_view->is_selectable()
×
670
            && this->tss_view->get_selection() != row)
×
671
        {
672
            lr.lr_start = 1;
×
673
            lr.lr_end = 1 + this->lss_time_column_size - 2 - extra_space_size;
×
674
            value_out.emplace_back(lr, VC_ROLE.value(role_t::VCR_TIME_COLUMN));
×
675
            if (this->lss_token_line->is_time_skewed()) {
×
676
                value_out.emplace_back(lr,
×
677
                                       VC_ROLE.value(role_t::VCR_SKEWED_TIME));
×
678
            }
679
            lr.lr_start = 1 + this->lss_time_column_size - 2 - extra_space_size;
×
680
            lr.lr_end = 1 + this->lss_time_column_size - 1 - extra_space_size;
×
681
            value_out.emplace_back(
×
682
                lr, VC_ROLE.value(role_t::VCR_TIME_COLUMN_TO_TEXT));
×
683
        }
684
    }
685

686
    if (this->tas_display_time_offset) {
2,756✔
687
        time_offset_end = 13;
210✔
688
        lr.lr_start = 0;
210✔
689
        lr.lr_end = time_offset_end;
210✔
690

691
        shift_string_attrs(value_out, 0, time_offset_end);
210✔
692

693
        value_out.emplace_back(lr, VC_ROLE.value(role_t::VCR_OFFSET_TIME));
210✔
694
        value_out.emplace_back(line_range(12, 13),
210✔
695
                               VC_GRAPHIC.value(NCACS_VLINE));
420✔
696

697
        auto bar_role = role_t::VCR_NONE;
210✔
698

699
        switch (this->get_line_accel_direction(vis_line_t(row))) {
210✔
700
            case log_accel::direction_t::A_STEADY:
126✔
701
                break;
126✔
702
            case log_accel::direction_t::A_DECEL:
42✔
703
                bar_role = role_t::VCR_DIFF_DELETE;
42✔
704
                break;
42✔
705
            case log_accel::direction_t::A_ACCEL:
42✔
706
                bar_role = role_t::VCR_DIFF_ADD;
42✔
707
                break;
42✔
708
        }
709
        if (bar_role != role_t::VCR_NONE) {
210✔
710
            value_out.emplace_back(line_range(12, 13), VC_ROLE.value(bar_role));
84✔
711
        }
712
    }
713

714
    lr.lr_start = 0;
2,756✔
715
    lr.lr_end = -1;
2,756✔
716
    value_out.emplace_back(lr, L_FILE.value(this->lss_token_file));
2,756✔
717
    value_out.emplace_back(
2,756✔
718
        lr, SA_FORMAT.value(this->lss_token_file->get_format()->get_name()));
5,512✔
719

720
    {
721
        auto line_meta_context = this->get_bookmark_metadata_context(
2,756✔
722
            vis_line_t(row + 1), bookmark_metadata::categories::partition);
723
        if (line_meta_context.bmc_current_metadata) {
2,756✔
724
            lr.lr_start = 0;
8✔
725
            lr.lr_end = -1;
8✔
726
            value_out.emplace_back(
8✔
727
                lr,
728
                L_PARTITION.value(
16✔
729
                    line_meta_context.bmc_current_metadata.value()));
730
        }
731

732
        auto line_meta_opt = this->find_bookmark_metadata(vis_line_t(row));
2,756✔
733

734
        if (line_meta_opt) {
2,756✔
735
            lr.lr_start = 0;
24✔
736
            lr.lr_end = -1;
24✔
737
            value_out.emplace_back(lr, L_META.value(line_meta_opt.value()));
24✔
738
        }
739
    }
740

741
    auto src_file_attr = get_string_attr(value_out, SA_SRC_FILE);
2,756✔
742
    if (src_file_attr) {
2,756✔
743
        auto lr = src_file_attr->saw_string_attr->sa_range;
21✔
744
        lr.lr_end = lr.lr_start + 1;
21✔
745
        value_out.emplace_back(lr,
21✔
746
                               VC_STYLE.value(text_attrs::with_underline()));
42✔
747
        value_out.emplace_back(lr,
21✔
748
                               VC_COMMAND.value(ui_command{
84✔
749
                                   source_location{},
750
                                   ":toggle-breakpoint",
751
                               }));
752
    }
753

754
    if (this->lss_time_column_size == 0) {
2,756✔
755
        if (this->lss_token_file->is_time_adjusted()) {
2,756✔
756
            auto time_range = find_string_attr_range(value_out, &L_TIMESTAMP);
17✔
757

758
            if (time_range.lr_end != -1) {
17✔
759
                value_out.emplace_back(
15✔
760
                    time_range, VC_ROLE.value(role_t::VCR_ADJUSTED_TIME));
30✔
761
            }
762
        } else if (this->lss_token_line->is_time_skewed()) {
2,739✔
763
            auto time_range = find_string_attr_range(value_out, &L_TIMESTAMP);
8✔
764

765
            if (time_range.lr_end != -1) {
8✔
766
                value_out.emplace_back(time_range,
8✔
767
                                       VC_ROLE.value(role_t::VCR_SKEWED_TIME));
16✔
768
            }
769
        }
770
    }
771

772
    if (!this->lss_token_line->is_continued()) {
2,756✔
773
        if (this->lss_preview_filter_stmt != nullptr) {
1,048✔
774
            auto color = styling::color_unit::EMPTY;
×
775
            auto eval_res
776
                = this->eval_sql_filter(this->lss_preview_filter_stmt.in(),
777
                                        this->lss_token_file_data,
778
                                        this->lss_token_line);
×
779
            if (eval_res.isErr()) {
×
780
                color = palette_color{
×
781
                    lnav::enums::to_underlying(ansi_color::yellow)};
×
782
                value_out.emplace_back(
×
783
                    line_range{0, -1},
×
784
                    SA_ERROR.value(
×
785
                        eval_res.unwrapErr().to_attr_line().get_string()));
×
786
            } else {
787
                auto matched = eval_res.unwrap();
×
788

789
                if (matched) {
×
790
                    color = palette_color{
×
791
                        lnav::enums::to_underlying(ansi_color::green)};
×
792
                } else {
793
                    color = palette_color{
×
794
                        lnav::enums::to_underlying(ansi_color::red)};
×
795
                    value_out.emplace_back(
×
796
                        line_range{0, 1},
×
797
                        VC_STYLE.value(text_attrs::with_blink()));
×
798
                }
799
            }
800
            value_out.emplace_back(line_range{0, 1},
×
801
                                   VC_BACKGROUND.value(color));
×
802
        }
803

804
        auto sql_filter_opt = this->get_sql_filter();
1,048✔
805
        if (sql_filter_opt) {
1,048✔
806
            auto* sf = (sql_filter*) sql_filter_opt.value().get();
6✔
807
            auto eval_res = this->eval_sql_filter(sf->sf_filter_stmt.in(),
808
                                                  this->lss_token_file_data,
809
                                                  this->lss_token_line);
6✔
810
            if (eval_res.isErr()) {
6✔
811
                auto msg = fmt::format(
812
                    FMT_STRING(
×
813
                        "filter expression evaluation failed with -- {}"),
814
                    eval_res.unwrapErr().to_attr_line().get_string());
×
815
                auto cu = styling::color_unit::from_palette(palette_color{
×
816
                    lnav::enums::to_underlying(ansi_color::yellow)});
×
817
                value_out.emplace_back(line_range{0, -1}, SA_ERROR.value(msg));
×
818
                value_out.emplace_back(line_range{0, 1},
×
819
                                       VC_BACKGROUND.value(cu));
×
820
            }
821
        }
6✔
822
    }
1,048✔
823
}
2,756✔
824

825
struct logline_cmp {
826
    logline_cmp(logfile_sub_source& lc) : llss_controller(lc) {}
1,054✔
827

828
    bool operator()(const logfile_sub_source::indexed_content& lhs,
93,032✔
829
                    const logfile_sub_source::indexed_content& rhs) const
830
    {
831
        const auto* ll_lhs = this->llss_controller.find_line(lhs.value());
93,032✔
832
        const auto* ll_rhs = this->llss_controller.find_line(rhs.value());
93,032✔
833

834
        return (*ll_lhs) < (*ll_rhs);
93,032✔
835
    }
836

837
    bool operator()(const uint32_t& lhs, const uint32_t& rhs) const
838
    {
839
        auto cl_lhs = llss_controller.lss_index[lhs].value();
840
        auto cl_rhs = llss_controller.lss_index[rhs].value();
841
        const auto* ll_lhs = this->llss_controller.find_line(cl_lhs);
842
        const auto* ll_rhs = this->llss_controller.find_line(cl_rhs);
843

844
        return (*ll_lhs) < (*ll_rhs);
845
    }
846
#if 0
847
        bool operator()(const indexed_content &lhs, const indexed_content &rhs)
848
        {
849
            logline *ll_lhs = this->llss_controller.find_line(lhs.ic_value);
850
            logline *ll_rhs = this->llss_controller.find_line(rhs.ic_value);
851

852
            return (*ll_lhs) < (*ll_rhs);
853
        }
854
#endif
855

856
#if 0
857
    bool operator()(const content_line_t& lhs, const time_t& rhs) const
858
    {
859
        logline* ll_lhs = this->llss_controller.find_line(lhs);
860

861
        return *ll_lhs < rhs;
862
    }
863
#endif
864

865
    bool operator()(const logfile_sub_source::indexed_content& lhs,
×
866
                    const struct timeval& rhs) const
867
    {
868
        const auto* ll_lhs = this->llss_controller.find_line(lhs.value());
×
869

870
        return *ll_lhs < rhs;
×
871
    }
872

873
    logfile_sub_source& llss_controller;
874
};
875

876
logfile_sub_source::rebuild_result
877
logfile_sub_source::rebuild_index(std::optional<ui_clock::time_point> deadline)
4,045✔
878
{
879
    if (this->tss_view == nullptr) {
4,045✔
880
        return rebuild_result::rr_no_change;
116✔
881
    }
882

883
    this->lss_indexing_in_progress = true;
3,929✔
884
    auto fin = finally([this]() { this->lss_indexing_in_progress = false; });
3,929✔
885

886
    iterator iter;
3,929✔
887
    size_t total_lines = 0;
3,929✔
888
    size_t est_remaining_lines = 0;
3,929✔
889
    auto all_time_ordered_formats = true;
3,929✔
890
    auto full_sort = this->lss_index.empty();
3,929✔
891
    int file_count = 0;
3,929✔
892
    auto force = std::exchange(this->lss_force_rebuild, false);
3,929✔
893
    auto retval = rebuild_result::rr_no_change;
3,929✔
894
    std::optional<timeval> lowest_tv = std::nullopt;
3,929✔
895
    auto search_start = 0_vl;
3,929✔
896

897
    if (force) {
3,929✔
898
        log_debug("forced to full rebuild");
446✔
899
        retval = rebuild_result::rr_full_rebuild;
446✔
900
        full_sort = true;
446✔
901
    }
902

903
    std::vector<size_t> file_order(this->lss_files.size());
3,929✔
904

905
    for (size_t lpc = 0; lpc < file_order.size(); lpc++) {
6,723✔
906
        file_order[lpc] = lpc;
2,794✔
907
    }
908
    if (!this->lss_index.empty()) {
3,929✔
909
        std::stable_sort(file_order.begin(),
2,527✔
910
                         file_order.end(),
911
                         [this](const auto& left, const auto& right) {
241✔
912
                             const auto& left_ld = this->lss_files[left];
241✔
913
                             const auto& right_ld = this->lss_files[right];
241✔
914

915
                             if (left_ld->get_file_ptr() == nullptr) {
241✔
916
                                 return true;
×
917
                             }
918
                             if (right_ld->get_file_ptr() == nullptr) {
241✔
919
                                 return false;
4✔
920
                             }
921

922
                             return left_ld->get_file_ptr()->back()
237✔
923
                                 < right_ld->get_file_ptr()->back();
474✔
924
                         });
925
    }
926

927
    bool time_left = true;
3,929✔
928
    this->lss_all_timestamp_flags = 0;
3,929✔
929
    for (const auto file_index : file_order) {
6,723✔
930
        auto& ld = *(this->lss_files[file_index]);
2,794✔
931
        auto* lf = ld.get_file_ptr();
2,794✔
932

933
        if (lf == nullptr) {
2,794✔
934
            if (ld.ld_lines_indexed > 0) {
4✔
935
                log_debug("%d: file closed, doing full rebuild",
1✔
936
                          ld.ld_file_index);
937
                force = true;
1✔
938
                retval = rebuild_result::rr_full_rebuild;
1✔
939
                full_sort = true;
1✔
940
            }
941
        } else {
942
            if (!lf->get_format_ptr()->lf_time_ordered) {
2,790✔
943
                all_time_ordered_formats = false;
161✔
944
            }
945
            if (time_left && deadline && ui_clock::now() > deadline.value()) {
2,790✔
946
                log_debug("no time left, skipping %s",
×
947
                          lf->get_filename_as_string().c_str());
948
                time_left = false;
×
949
            }
950
            this->lss_all_timestamp_flags
2,790✔
951
                |= lf->get_format_ptr()->lf_timestamp_flags;
2,790✔
952

953
            if (!this->tss_view->is_paused() && time_left) {
2,790✔
954
                auto log_rebuild_res = lf->rebuild_index(deadline);
2,790✔
955

956
                if (ld.ld_lines_indexed < lf->size()
2,790✔
957
                    && log_rebuild_res
2,790✔
958
                        == logfile::rebuild_result_t::NO_NEW_LINES)
959
                {
960
                    // This is a bit awkward... if the logfile indexing was
961
                    // complete before being added to us, we need to adjust
962
                    // the rebuild result to make it look like new lines
963
                    // were added.
964
                    log_rebuild_res = logfile::rebuild_result_t::NEW_LINES;
50✔
965
                }
966
                switch (log_rebuild_res) {
2,790✔
967
                    case logfile::rebuild_result_t::NO_NEW_LINES:
2,285✔
968
                        break;
2,285✔
969
                    case logfile::rebuild_result_t::NEW_LINES:
454✔
970
                        if (retval == rebuild_result::rr_no_change) {
454✔
971
                            retval = rebuild_result::rr_appended_lines;
405✔
972
                        }
973
                        log_debug("new lines for %s:%d",
454✔
974
                                  lf->get_filename_as_string().c_str(),
975
                                  lf->size());
976
                        if (!this->lss_index.empty()
454✔
977
                            && lf->size() > ld.ld_lines_indexed)
454✔
978
                        {
979
                            auto& new_file_line = (*lf)[ld.ld_lines_indexed];
×
980
                            auto cl = this->lss_index.back().value();
×
981
                            auto* last_indexed_line = this->find_line(cl);
×
982

983
                            // If there are new lines that are older than what
984
                            // we have in the index, we need to resort.
985
                            if (last_indexed_line == nullptr
×
986
                                || new_file_line
×
987
                                    < last_indexed_line->get_timeval())
×
988
                            {
989
                                log_debug(
×
990
                                    "%s:%ld: found older lines, full "
991
                                    "rebuild: %p  %lld < %lld",
992
                                    lf->get_filename().c_str(),
993
                                    ld.ld_lines_indexed,
994
                                    last_indexed_line,
995
                                    new_file_line
996
                                        .get_time<std::chrono::microseconds>()
997
                                        .count(),
998
                                    last_indexed_line == nullptr
999
                                        ? (uint64_t) -1
1000
                                        : last_indexed_line
1001
                                              ->get_time<
1002
                                                  std::chrono::microseconds>()
1003
                                              .count());
1004
                                if (retval <= rebuild_result::rr_partial_rebuild
×
1005
                                    && all_time_ordered_formats)
×
1006
                                {
1007
                                    retval = rebuild_result::rr_partial_rebuild;
×
1008
                                    if (!lowest_tv
×
1009
                                        || new_file_line.get_timeval()
×
1010
                                            < lowest_tv.value())
×
1011
                                    {
1012
                                        lowest_tv = new_file_line.get_timeval();
×
1013
                                    }
1014
                                } else {
1015
                                    log_debug(
×
1016
                                        "already doing full rebuild, doing "
1017
                                        "full_sort as well");
1018
                                    force = true;
×
1019
                                    full_sort = true;
×
1020
                                }
1021
                            }
1022
                        }
1023
                        break;
454✔
1024
                    case logfile::rebuild_result_t::INVALID:
51✔
1025
                    case logfile::rebuild_result_t::NEW_ORDER:
1026
                        log_debug("%s: log file has a new order, full rebuild",
51✔
1027
                                  lf->get_filename().c_str());
1028
                        retval = rebuild_result::rr_full_rebuild;
51✔
1029
                        force = true;
51✔
1030
                        full_sort = true;
51✔
1031
                        break;
51✔
1032
                }
1033
            }
1034
            file_count += 1;
2,790✔
1035
            total_lines += lf->size();
2,790✔
1036

1037
            est_remaining_lines += lf->estimated_remaining_lines();
2,790✔
1038
        }
1039
    }
1040

1041
    if (this->lss_index.reserve(total_lines + est_remaining_lines)) {
3,929✔
1042
        // The index array was reallocated, just do a full sort/rebuild since
1043
        // it's been cleared out.
1044
        log_debug("expanding index capacity %zu", this->lss_index.ba_capacity);
581✔
1045
        force = true;
581✔
1046
        retval = rebuild_result::rr_full_rebuild;
581✔
1047
        full_sort = true;
581✔
1048
    }
1049

1050
    auto& vis_bm = this->tss_view->get_bookmarks();
3,929✔
1051

1052
    if (force) {
3,929✔
1053
        for (iter = this->lss_files.begin(); iter != this->lss_files.end();
1,532✔
1054
             iter++)
492✔
1055
        {
1056
            (*iter)->ld_lines_indexed = 0;
492✔
1057
        }
1058

1059
        this->lss_index.clear();
1,040✔
1060
        this->lss_filtered_index.clear();
1,040✔
1061
        this->lss_longest_line = 0;
1,040✔
1062
        this->lss_basename_width = 0;
1,040✔
1063
        this->lss_filename_width = 0;
1,040✔
1064
        vis_bm[&textview_curses::BM_USER_EXPR].clear();
1,040✔
1065
        if (this->lss_index_delegate) {
1,040✔
1066
            this->lss_index_delegate->index_start(*this);
1,040✔
1067
        }
1068
    } else if (retval == rebuild_result::rr_partial_rebuild) {
2,889✔
1069
        size_t remaining = 0;
×
1070

1071
        log_debug("partial rebuild with lowest time: %ld",
×
1072
                  lowest_tv.value().tv_sec);
1073
        for (iter = this->lss_files.begin(); iter != this->lss_files.end();
×
1074
             iter++)
×
1075
        {
1076
            logfile_data& ld = *(*iter);
×
1077
            auto* lf = ld.get_file_ptr();
×
1078

1079
            if (lf == nullptr) {
×
1080
                continue;
×
1081
            }
1082

1083
            require(lf->get_format_ptr()->lf_time_ordered);
×
1084

1085
            auto line_iter = lf->find_from_time(lowest_tv.value());
×
1086

1087
            if (line_iter) {
×
1088
                log_debug("lowest line time %ld; line %ld; size %ld; path=%s",
×
1089
                          line_iter.value()->get_timeval().tv_sec,
1090
                          std::distance(lf->cbegin(), line_iter.value()),
1091
                          lf->size(),
1092
                          lf->get_filename_as_string().c_str());
1093
            }
1094
            ld.ld_lines_indexed
1095
                = std::distance(lf->cbegin(), line_iter.value_or(lf->cend()));
×
1096
            remaining += lf->size() - ld.ld_lines_indexed;
×
1097
        }
1098

1099
        auto* row_iter = std::lower_bound(this->lss_index.begin(),
×
1100
                                          this->lss_index.end(),
1101
                                          lowest_tv.value(),
×
1102
                                          logline_cmp(*this));
1103
        this->lss_index.shrink_to(
×
1104
            std::distance(this->lss_index.begin(), row_iter));
×
1105
        log_debug("new index size %ld/%ld; remain %ld",
×
1106
                  this->lss_index.ba_size,
1107
                  this->lss_index.ba_capacity,
1108
                  remaining);
1109
        auto filt_row_iter = std::lower_bound(this->lss_filtered_index.begin(),
×
1110
                                              this->lss_filtered_index.end(),
1111
                                              lowest_tv.value(),
×
1112
                                              filtered_logline_cmp(*this));
1113
        this->lss_filtered_index.resize(
×
1114
            std::distance(this->lss_filtered_index.begin(), filt_row_iter));
×
1115
        search_start = vis_line_t(this->lss_filtered_index.size());
×
1116

1117
        if (this->lss_index_delegate) {
×
1118
            this->lss_index_delegate->index_start(*this);
×
1119
            for (const auto row_in_full_index : this->lss_filtered_index) {
×
1120
                auto cl = this->lss_index[row_in_full_index].value();
×
1121
                uint64_t line_number;
1122
                auto ld_iter = this->find_data(cl, line_number);
×
1123
                auto& ld = *ld_iter;
×
1124
                auto line_iter = ld->get_file_ptr()->begin() + line_number;
×
1125

1126
                this->lss_index_delegate->index_line(
×
1127
                    *this, ld->get_file_ptr(), line_iter);
1128
            }
1129
        }
1130
    }
1131

1132
    if (this->lss_index.empty() && !time_left) {
3,929✔
1133
        log_info("ran out of time, skipping rebuild");
×
1134
        // need to make sure we rebuild in case no new data comes in
1135
        this->lss_force_rebuild = true;
×
1136
        return rebuild_result::rr_appended_lines;
×
1137
    }
1138

1139
    if (retval != rebuild_result::rr_no_change || force) {
3,929✔
1140
        size_t index_size = 0, start_size = this->lss_index.size();
1,054✔
1141
        logline_cmp line_cmper(*this);
1,054✔
1142

1143
        for (auto& ld : this->lss_files) {
1,569✔
1144
            auto* lf = ld->get_file_ptr();
515✔
1145

1146
            if (lf == nullptr) {
515✔
1147
                continue;
1✔
1148
            }
1149
            this->lss_longest_line = std::max(
1,028✔
1150
                this->lss_longest_line, lf->get_longest_line_length() + 1);
514✔
1151
            this->lss_basename_width
1152
                = std::max(this->lss_basename_width,
1,028✔
1153
                           lf->get_unique_path().native().size());
514✔
1154
            this->lss_filename_width = std::max(
1,028✔
1155
                this->lss_filename_width, lf->get_filename().native().size());
514✔
1156
        }
1157

1158
        if (full_sort) {
1,054✔
1159
            log_trace("rebuild_index full sort");
1,054✔
1160
            for (auto& ld : this->lss_files) {
1,569✔
1161
                auto* lf = ld->get_file_ptr();
515✔
1162

1163
                if (lf == nullptr) {
515✔
1164
                    continue;
1✔
1165
                }
1166

1167
                for (size_t line_index = 0; line_index < lf->size();
12,745✔
1168
                     line_index++)
1169
                {
1170
                    const auto lf_iter
1171
                        = ld->get_file_ptr()->begin() + line_index;
12,231✔
1172
                    if (lf_iter->is_ignored()) {
12,231✔
1173
                        continue;
233✔
1174
                    }
1175

1176
                    content_line_t con_line(
1177
                        ld->ld_file_index * MAX_LINES_PER_FILE + line_index);
11,998✔
1178

1179
                    if (lf_iter->is_meta_marked()) {
11,998✔
1180
                        auto start_iter = lf_iter;
11✔
1181
                        while (start_iter->is_continued()) {
11✔
1182
                            --start_iter;
×
1183
                        }
1184
                        int start_index
1185
                            = start_iter - ld->get_file_ptr()->begin();
11✔
1186
                        content_line_t start_con_line(ld->ld_file_index
11✔
1187
                                                          * MAX_LINES_PER_FILE
11✔
1188
                                                      + start_index);
11✔
1189

1190
                        auto& line_meta
1191
                            = ld->get_file_ptr()
1192
                                  ->get_bookmark_metadata()[start_index];
11✔
1193
                        if (line_meta.has(bookmark_metadata::categories::notes))
11✔
1194
                        {
1195
                            this->lss_user_marks[&textview_curses::BM_META]
3✔
1196
                                .insert_once(start_con_line);
3✔
1197
                        }
1198
                        if (line_meta.has(
11✔
1199
                                bookmark_metadata::categories::partition))
1200
                        {
1201
                            this->lss_user_marks[&textview_curses::BM_PARTITION]
8✔
1202
                                .insert_once(start_con_line);
8✔
1203
                        }
1204
                    }
1205
                    this->lss_index.push_back(
11,998✔
1206
                        indexed_content{con_line, lf_iter});
23,996✔
1207
                }
1208
            }
1209

1210
            // XXX get rid of this full sort on the initial run, it's not
1211
            // needed unless the file is not in time-order
1212
            if (this->lss_sorting_observer) {
1,054✔
1213
                this->lss_sorting_observer(*this, 0, this->lss_index.size());
×
1214
            }
1215
            std::sort(
1,054✔
1216
                this->lss_index.begin(), this->lss_index.end(), line_cmper);
1217
            if (this->lss_sorting_observer) {
1,054✔
1218
                this->lss_sorting_observer(
×
1219
                    *this, this->lss_index.size(), this->lss_index.size());
×
1220
            }
1221
        } else {
1222
            kmerge_tree_c<logline, logfile_data, logfile::iterator> merge(
1223
                file_count);
×
1224

1225
            for (iter = this->lss_files.begin(); iter != this->lss_files.end();
×
1226
                 iter++)
×
1227
            {
1228
                auto* ld = iter->get();
×
1229
                auto* lf = ld->get_file_ptr();
×
1230
                if (lf == nullptr) {
×
1231
                    continue;
×
1232
                }
1233

1234
                merge.add(ld, lf->begin() + ld->ld_lines_indexed, lf->end());
×
1235
                index_size += lf->size();
×
1236
            }
1237

1238
            file_off_t index_off = 0;
×
1239
            merge.execute();
×
1240
            if (this->lss_sorting_observer) {
×
1241
                this->lss_sorting_observer(*this, index_off, index_size);
×
1242
            }
1243
            log_trace("k-way merge");
×
1244
            for (;;) {
1245
                logfile::iterator lf_iter;
×
1246
                logfile_data* ld;
1247

1248
                if (!merge.get_top(ld, lf_iter)) {
×
1249
                    break;
×
1250
                }
1251

1252
                if (!lf_iter->is_ignored()) {
×
1253
                    int file_index = ld->ld_file_index;
×
1254
                    int line_index = lf_iter - ld->get_file_ptr()->begin();
×
1255

1256
                    content_line_t con_line(file_index * MAX_LINES_PER_FILE
×
1257
                                            + line_index);
×
1258

1259
                    if (lf_iter->is_meta_marked()) {
×
1260
                        auto start_iter = lf_iter;
×
1261
                        while (start_iter->is_continued()) {
×
1262
                            --start_iter;
×
1263
                        }
1264
                        int start_index
1265
                            = start_iter - ld->get_file_ptr()->begin();
×
1266
                        content_line_t start_con_line(
1267
                            file_index * MAX_LINES_PER_FILE + start_index);
×
1268

1269
                        auto& line_meta
1270
                            = ld->get_file_ptr()
1271
                                  ->get_bookmark_metadata()[start_index];
×
1272
                        if (line_meta.has(bookmark_metadata::categories::notes))
×
1273
                        {
1274
                            this->lss_user_marks[&textview_curses::BM_META]
×
1275
                                .insert_once(start_con_line);
×
1276
                        }
1277
                        if (line_meta.has(
×
1278
                                bookmark_metadata::categories::partition))
1279
                        {
1280
                            this->lss_user_marks[&textview_curses::BM_PARTITION]
×
1281
                                .insert_once(start_con_line);
×
1282
                        }
1283
                    }
1284
                    this->lss_index.push_back(
×
1285
                        indexed_content{con_line, lf_iter});
×
1286
                }
1287

1288
                merge.next();
×
1289
                index_off += 1;
×
1290
                if (index_off % 100000 == 0 && this->lss_sorting_observer) {
×
1291
                    this->lss_sorting_observer(*this, index_off, index_size);
×
1292
                }
1293
            }
1294
            if (this->lss_sorting_observer) {
×
1295
                this->lss_sorting_observer(*this, index_size, index_size);
×
1296
            }
1297
        }
1298

1299
        for (iter = this->lss_files.begin(); iter != this->lss_files.end();
1,569✔
1300
             ++iter)
515✔
1301
        {
1302
            auto* lf = (*iter)->get_file_ptr();
515✔
1303

1304
            if (lf == nullptr) {
515✔
1305
                continue;
1✔
1306
            }
1307

1308
            (*iter)->ld_lines_indexed = lf->size();
514✔
1309
        }
1310

1311
        this->lss_filtered_index.reserve(this->lss_index.size());
1,054✔
1312

1313
        uint32_t filter_in_mask, filter_out_mask;
1314
        this->get_filters().get_enabled_mask(filter_in_mask, filter_out_mask);
1,054✔
1315

1316
        if (start_size == 0 && this->lss_index_delegate != nullptr) {
1,054✔
1317
            this->lss_index_delegate->index_start(*this);
1,054✔
1318
        }
1319

1320
        log_trace("filtered index");
1,054✔
1321
        for (size_t index_index = start_size;
13,052✔
1322
             index_index < this->lss_index.size();
13,052✔
1323
             index_index++)
1324
        {
1325
            const auto cl = this->lss_index[index_index].value();
11,998✔
1326
            uint64_t line_number;
1327
            auto ld = this->find_data(cl, line_number);
11,998✔
1328

1329
            if (!(*ld)->is_visible()) {
11,998✔
1330
                continue;
×
1331
            }
1332

1333
            auto* lf = (*ld)->get_file_ptr();
11,998✔
1334
            auto line_iter = lf->begin() + line_number;
11,998✔
1335

1336
            if (line_iter->is_ignored()) {
11,998✔
1337
                continue;
×
1338
            }
1339

1340
            if (!this->tss_apply_filters
23,996✔
1341
                || (!(*ld)->ld_filter_state.excluded(
23,986✔
1342
                        filter_in_mask, filter_out_mask, line_number)
1343
                    && this->check_extra_filters(ld, line_iter)))
11,988✔
1344
            {
1345
                auto eval_res = this->eval_sql_filter(
1346
                    this->lss_marker_stmt.in(), ld, line_iter);
11,988✔
1347
                if (eval_res.isErr()) {
11,988✔
1348
                    line_iter->set_expr_mark(false);
×
1349
                } else {
1350
                    auto matched = eval_res.unwrap();
11,988✔
1351

1352
                    if (matched) {
11,988✔
1353
                        line_iter->set_expr_mark(true);
×
1354
                        vis_bm[&textview_curses::BM_USER_EXPR].insert_once(
×
1355
                            vis_line_t(this->lss_filtered_index.size()));
×
1356
                    } else {
1357
                        line_iter->set_expr_mark(false);
11,988✔
1358
                    }
1359
                }
1360
                this->lss_filtered_index.push_back(index_index);
11,988✔
1361
                if (this->lss_index_delegate != nullptr) {
11,988✔
1362
                    this->lss_index_delegate->index_line(*this, lf, line_iter);
11,988✔
1363
                }
1364
            }
11,988✔
1365
        }
1366

1367
        this->lss_indexing_in_progress = false;
1,054✔
1368

1369
        if (this->lss_index_delegate != nullptr) {
1,054✔
1370
            this->lss_index_delegate->index_complete(*this);
1,054✔
1371
        }
1372
    }
1373

1374
    switch (retval) {
3,929✔
1375
        case rebuild_result::rr_no_change:
2,875✔
1376
            break;
2,875✔
1377
        case rebuild_result::rr_full_rebuild:
1,040✔
1378
            log_debug("redoing search");
1,040✔
1379
            this->lss_index_generation += 1;
1,040✔
1380
            this->tss_view->reload_data();
1,040✔
1381
            this->tss_view->redo_search();
1,040✔
1382
            break;
1,040✔
1383
        case rebuild_result::rr_partial_rebuild:
×
1384
            log_debug("redoing search from: %d", (int) search_start);
×
1385
            this->lss_index_generation += 1;
×
1386
            this->tss_view->reload_data();
×
1387
            this->tss_view->search_new_data(search_start);
×
1388
            break;
×
1389
        case rebuild_result::rr_appended_lines:
14✔
1390
            this->tss_view->reload_data();
14✔
1391
            this->tss_view->search_new_data();
14✔
1392
            break;
14✔
1393
    }
1394

1395
    return retval;
3,929✔
1396
}
3,929✔
1397

1398
void
1399
logfile_sub_source::text_update_marks(vis_bookmarks& bm)
2,466✔
1400
{
1401
    logfile* last_file = nullptr;
2,466✔
1402
    vis_line_t vl;
2,466✔
1403

1404
    auto& bm_warnings = bm[&textview_curses::BM_WARNINGS];
2,466✔
1405
    auto& bm_errors = bm[&textview_curses::BM_ERRORS];
2,466✔
1406
    auto& bm_files = bm[&BM_FILES];
2,466✔
1407

1408
    bm_warnings.clear();
2,466✔
1409
    bm_errors.clear();
2,466✔
1410
    bm_files.clear();
2,466✔
1411

1412
    std::vector<const bookmark_type_t*> used_marks;
2,466✔
1413
    for (const auto* bmt : {
12,330✔
1414
             &textview_curses::BM_USER,
1415
             &textview_curses::BM_USER_EXPR,
1416
             &textview_curses::BM_PARTITION,
1417
             &textview_curses::BM_META,
1418
         })
14,796✔
1419
    {
1420
        bm[bmt].clear();
9,864✔
1421
        if (!this->lss_user_marks[bmt].empty()) {
9,864✔
1422
            used_marks.emplace_back(bmt);
112✔
1423
        }
1424
    }
1425

1426
    for (; vl < (int) this->lss_filtered_index.size(); ++vl) {
16,255✔
1427
        const auto& orig_ic = this->lss_index[this->lss_filtered_index[vl]];
13,789✔
1428
        auto cl = orig_ic.value();
13,789✔
1429
        auto* lf = this->find_file_ptr(cl);
13,789✔
1430

1431
        for (const auto& bmt : used_marks) {
15,251✔
1432
            auto& user_mark = this->lss_user_marks[bmt];
1,462✔
1433
            if (user_mark.bv_tree.exists(orig_ic.value())) {
1,462✔
1434
                bm[bmt].insert_once(vl);
125✔
1435

1436
                if (bmt == &textview_curses::BM_USER) {
125✔
1437
                    auto ll = lf->begin() + cl;
40✔
1438

1439
                    ll->set_mark(true);
40✔
1440
                }
1441
            }
1442
        }
1443

1444
        if (lf != last_file) {
13,789✔
1445
            bm_files.insert_once(vl);
898✔
1446
        }
1447

1448
        switch (orig_ic.level()) {
13,789✔
1449
            case indexed_content::level_t::warning:
60✔
1450
                bm_warnings.insert_once(vl);
60✔
1451
                break;
60✔
1452

1453
            case indexed_content::level_t::error:
999✔
1454
                bm_errors.insert_once(vl);
999✔
1455
                break;
999✔
1456

1457
            default:
12,730✔
1458
                break;
12,730✔
1459
        }
1460

1461
        last_file = lf;
13,789✔
1462
    }
1463
}
2,466✔
1464

1465
void
1466
logfile_sub_source::text_filters_changed()
151✔
1467
{
1468
    this->lss_index_generation += 1;
151✔
1469

1470
    if (this->lss_line_meta_changed) {
151✔
1471
        this->invalidate_sql_filter();
20✔
1472
        this->lss_line_meta_changed = false;
20✔
1473
    }
1474

1475
    for (auto& ld : *this) {
257✔
1476
        auto* lf = ld->get_file_ptr();
106✔
1477

1478
        if (lf != nullptr) {
106✔
1479
            ld->ld_filter_state.clear_deleted_filter_state();
106✔
1480
            lf->reobserve_from(lf->begin()
106✔
1481
                               + ld->ld_filter_state.get_min_count(lf->size()));
106✔
1482
        }
1483
    }
1484

1485
    if (this->lss_force_rebuild) {
151✔
1486
        return;
×
1487
    }
1488

1489
    auto& vis_bm = this->tss_view->get_bookmarks();
151✔
1490
    uint32_t filtered_in_mask, filtered_out_mask;
1491

1492
    this->get_filters().get_enabled_mask(filtered_in_mask, filtered_out_mask);
151✔
1493

1494
    if (this->lss_index_delegate != nullptr) {
151✔
1495
        this->lss_index_delegate->index_start(*this);
151✔
1496
    }
1497
    vis_bm[&textview_curses::BM_USER_EXPR].clear();
151✔
1498

1499
    this->lss_filtered_index.clear();
151✔
1500
    for (size_t index_index = 0; index_index < this->lss_index.size();
964✔
1501
         index_index++)
1502
    {
1503
        auto cl = this->lss_index[index_index].value();
813✔
1504
        uint64_t line_number;
1505
        auto ld = this->find_data(cl, line_number);
813✔
1506

1507
        if (!(*ld)->is_visible()) {
813✔
1508
            continue;
210✔
1509
        }
1510

1511
        auto lf = (*ld)->get_file_ptr();
603✔
1512
        auto line_iter = lf->begin() + line_number;
603✔
1513

1514
        if (!this->tss_apply_filters
1,206✔
1515
            || (!(*ld)->ld_filter_state.excluded(
1,090✔
1516
                    filtered_in_mask, filtered_out_mask, line_number)
1517
                && this->check_extra_filters(ld, line_iter)))
487✔
1518
        {
1519
            auto eval_res = this->eval_sql_filter(
1520
                this->lss_marker_stmt.in(), ld, line_iter);
478✔
1521
            if (eval_res.isErr()) {
478✔
1522
                line_iter->set_expr_mark(false);
×
1523
            } else {
1524
                auto matched = eval_res.unwrap();
478✔
1525

1526
                if (matched) {
478✔
1527
                    line_iter->set_expr_mark(true);
×
1528
                    vis_bm[&textview_curses::BM_USER_EXPR].insert_once(
×
1529
                        vis_line_t(this->lss_filtered_index.size()));
×
1530
                } else {
1531
                    line_iter->set_expr_mark(false);
478✔
1532
                }
1533
            }
1534
            this->lss_filtered_index.push_back(index_index);
478✔
1535
            if (this->lss_index_delegate != nullptr) {
478✔
1536
                this->lss_index_delegate->index_line(*this, lf, line_iter);
478✔
1537
            }
1538
        }
478✔
1539
    }
1540

1541
    if (this->lss_index_delegate != nullptr) {
151✔
1542
        this->lss_index_delegate->index_complete(*this);
151✔
1543
    }
1544

1545
    if (this->tss_view != nullptr) {
151✔
1546
        this->tss_view->reload_data();
151✔
1547
        this->tss_view->redo_search();
151✔
1548
    }
1549
}
1550

1551
std::optional<json_string>
1552
logfile_sub_source::text_row_details(const textview_curses& tc)
25✔
1553
{
1554
    if (this->lss_index.empty()) {
25✔
1555
        log_trace("logfile_sub_source::text_row_details empty");
1✔
1556
        return std::nullopt;
1✔
1557
    }
1558

1559
    auto ov_sel = tc.get_overlay_selection();
24✔
1560
    if (ov_sel.has_value()) {
24✔
1561
        auto* fos
1562
            = dynamic_cast<field_overlay_source*>(tc.get_overlay_source());
×
1563
        auto iter = fos->fos_row_to_field_meta.find(ov_sel.value());
×
1564
        if (iter != fos->fos_row_to_field_meta.end()) {
×
1565
            auto find_res = this->find_line_with_file(tc.get_top());
×
1566
            if (find_res) {
×
1567
                yajlpp_gen gen;
×
1568

1569
                {
1570
                    yajlpp_map root(gen);
×
1571

1572
                    root.gen("value");
×
1573
                    root.gen(iter->second.ri_value);
×
1574
                }
1575

1576
                return json_string(gen);
×
1577
            }
1578
        }
1579
    }
1580

1581
    return std::nullopt;
24✔
1582
}
1583

1584
bool
1585
logfile_sub_source::list_input_handle_key(listview_curses& lv,
×
1586
                                          const ncinput& ch)
1587
{
1588
    switch (ch.eff_text[0]) {
×
1589
        case ' ': {
×
1590
            auto ov_vl = lv.get_overlay_selection();
×
1591
            if (ov_vl) {
×
1592
                auto* fos = dynamic_cast<field_overlay_source*>(
×
1593
                    lv.get_overlay_source());
×
1594
                auto iter = fos->fos_row_to_field_meta.find(ov_vl.value());
×
1595
                if (iter != fos->fos_row_to_field_meta.end()
×
1596
                    && iter->second.ri_meta)
×
1597
                {
1598
                    auto find_res = this->find_line_with_file(lv.get_top());
×
1599
                    if (find_res) {
×
1600
                        auto file_and_line = find_res.value();
×
1601
                        auto* format = file_and_line.first->get_format_ptr();
×
1602
                        auto fstates = format->get_field_states();
×
1603
                        auto state_iter
1604
                            = fstates.find(iter->second.ri_meta->lvm_name);
×
1605
                        if (state_iter != fstates.end()) {
×
1606
                            format->hide_field(iter->second.ri_meta->lvm_name,
×
1607
                                               !state_iter->second.is_hidden());
×
1608
                            lv.set_needs_update();
×
1609
                        }
1610
                    }
1611
                }
1612
                return true;
×
1613
            }
1614
            return false;
×
1615
        }
1616
        case '#': {
×
1617
            auto ov_vl = lv.get_overlay_selection();
×
1618
            if (ov_vl) {
×
1619
                auto* fos = dynamic_cast<field_overlay_source*>(
×
1620
                    lv.get_overlay_source());
×
1621
                auto iter = fos->fos_row_to_field_meta.find(ov_vl.value());
×
1622
                if (iter != fos->fos_row_to_field_meta.end()
×
1623
                    && iter->second.ri_meta)
×
1624
                {
1625
                    const auto& meta = iter->second.ri_meta.value();
×
1626
                    std::string cmd;
×
1627

1628
                    switch (meta.to_chart_type()) {
×
1629
                        case chart_type_t::none:
×
1630
                            break;
×
1631
                        case chart_type_t::hist: {
×
1632
                            auto prql = fmt::format(
1633
                                FMT_STRING(
×
1634
                                    "from {} | stats.hist {} slice:'1h'"),
1635
                                meta.lvm_format.value()->get_name(),
×
1636
                                meta.lvm_name);
×
1637
                            cmd = fmt::format(FMT_STRING(":prompt sql ; '{}'"),
×
1638
                                              shlex::escape(prql));
×
1639
                            break;
×
1640
                        }
1641
                        case chart_type_t::spectro:
×
1642
                            cmd = fmt::format(FMT_STRING(":spectrogram {}"),
×
1643
                                              meta.lvm_name);
×
1644
                            break;
×
1645
                    }
1646
                    if (!cmd.empty()) {
×
1647
                        this->lss_exec_context
×
1648
                            ->with_provenance(exec_context::mouse_input{})
×
1649
                            ->execute(INTERNAL_SRC_LOC, cmd);
×
1650
                    }
1651
                }
1652
                return true;
×
1653
            }
1654
            return false;
×
1655
        }
1656
        case 'h':
×
1657
        case 'H':
1658
        case NCKEY_LEFT:
1659
            if (lv.get_left() == 0) {
×
1660
                this->increase_line_context();
×
1661
                lv.set_needs_update();
×
1662
                return true;
×
1663
            }
1664
            break;
×
1665
        case 'l':
×
1666
        case 'L':
1667
        case NCKEY_RIGHT:
1668
            if (this->decrease_line_context()) {
×
1669
                lv.set_needs_update();
×
1670
                return true;
×
1671
            }
1672
            break;
×
1673
    }
1674
    return false;
×
1675
}
1676

1677
std::optional<
1678
    std::pair<grep_proc_source<vis_line_t>*, grep_proc_sink<vis_line_t>*>>
1679
logfile_sub_source::get_grepper()
9✔
1680
{
1681
    return std::make_pair(
18✔
1682
        (grep_proc_source<vis_line_t>*) &this->lss_meta_grepper,
×
1683
        (grep_proc_sink<vis_line_t>*) &this->lss_meta_grepper);
9✔
1684
}
1685

1686
/**
1687
 * Functor for comparing the ld_file field of the logfile_data struct.
1688
 */
1689
struct logfile_data_eq {
1690
    explicit logfile_data_eq(std::shared_ptr<logfile> lf)
968✔
1691
        : lde_file(std::move(lf))
968✔
1692
    {
1693
    }
968✔
1694

1695
    bool operator()(
598✔
1696
        const std::unique_ptr<logfile_sub_source::logfile_data>& ld) const
1697
    {
1698
        return this->lde_file == ld->get_file();
598✔
1699
    }
1700

1701
    std::shared_ptr<logfile> lde_file;
1702
};
1703

1704
bool
1705
logfile_sub_source::insert_file(const std::shared_ptr<logfile>& lf)
484✔
1706
{
1707
    iterator existing;
484✔
1708

1709
    require_lt(lf->size(), MAX_LINES_PER_FILE);
484✔
1710

1711
    existing = std::find_if(this->lss_files.begin(),
484✔
1712
                            this->lss_files.end(),
1713
                            logfile_data_eq(nullptr));
968✔
1714
    if (existing == this->lss_files.end()) {
484✔
1715
        if (this->lss_files.size() >= MAX_FILES) {
484✔
1716
            return false;
×
1717
        }
1718

1719
        auto ld = std::make_unique<logfile_data>(
1720
            this->lss_files.size(), this->get_filters(), lf);
484✔
1721
        ld->set_visibility(lf->get_open_options().loo_is_visible);
484✔
1722
        this->lss_files.push_back(std::move(ld));
484✔
1723
    } else {
484✔
1724
        (*existing)->set_file(lf);
×
1725
    }
1726

1727
    return true;
484✔
1728
}
1729

1730
Result<void, lnav::console::user_message>
1731
logfile_sub_source::set_sql_filter(std::string stmt_str, sqlite3_stmt* stmt)
709✔
1732
{
1733
    if (stmt != nullptr && !this->lss_filtered_index.empty()) {
709✔
1734
        auto top_cl = this->at(0_vl);
6✔
1735
        auto ld = this->find_data(top_cl);
6✔
1736
        auto eval_res
1737
            = this->eval_sql_filter(stmt, ld, (*ld)->get_file_ptr()->begin());
6✔
1738

1739
        if (eval_res.isErr()) {
6✔
1740
            sqlite3_finalize(stmt);
1✔
1741
            return Err(eval_res.unwrapErr());
1✔
1742
        }
1743
    }
6✔
1744

1745
    for (auto& ld : *this) {
722✔
1746
        ld->ld_filter_state.lfo_filter_state.clear_filter_state(0);
14✔
1747
    }
1748

1749
    auto old_filter = this->get_sql_filter();
708✔
1750
    if (stmt != nullptr) {
708✔
1751
        auto new_filter
1752
            = std::make_shared<sql_filter>(*this, std::move(stmt_str), stmt);
5✔
1753

1754
        if (old_filter) {
5✔
1755
            auto existing_iter = std::find(this->tss_filters.begin(),
×
1756
                                           this->tss_filters.end(),
1757
                                           old_filter.value());
×
1758
            *existing_iter = new_filter;
×
1759
        } else {
1760
            this->tss_filters.add_filter(new_filter);
5✔
1761
        }
1762
    } else if (old_filter) {
708✔
1763
        this->tss_filters.delete_filter(old_filter.value()->get_id());
5✔
1764
    }
1765

1766
    return Ok();
708✔
1767
}
708✔
1768

1769
Result<void, lnav::console::user_message>
1770
logfile_sub_source::set_sql_marker(std::string stmt_str, sqlite3_stmt* stmt)
705✔
1771
{
1772
    if (stmt != nullptr && !this->lss_filtered_index.empty()) {
705✔
1773
        auto top_cl = this->at(0_vl);
2✔
1774
        auto ld = this->find_data(top_cl);
2✔
1775
        auto eval_res
1776
            = this->eval_sql_filter(stmt, ld, (*ld)->get_file_ptr()->begin());
2✔
1777

1778
        if (eval_res.isErr()) {
2✔
1779
            sqlite3_finalize(stmt);
×
1780
            return Err(eval_res.unwrapErr());
×
1781
        }
1782
    }
2✔
1783

1784
    this->lss_marker_stmt_text = std::move(stmt_str);
705✔
1785
    this->lss_marker_stmt = stmt;
705✔
1786

1787
    if (this->tss_view == nullptr || this->lss_force_rebuild) {
705✔
1788
        return Ok();
122✔
1789
    }
1790

1791
    auto& vis_bm = this->tss_view->get_bookmarks();
583✔
1792
    auto& expr_marks_bv = vis_bm[&textview_curses::BM_USER_EXPR];
583✔
1793

1794
    expr_marks_bv.clear();
583✔
1795
    if (this->lss_index_delegate) {
583✔
1796
        this->lss_index_delegate->index_start(*this);
583✔
1797
    }
1798
    for (auto row = 0_vl; row < vis_line_t(this->lss_filtered_index.size());
590✔
1799
         row += 1_vl)
7✔
1800
    {
1801
        auto cl = this->at(row);
7✔
1802
        auto ld = this->find_data(cl);
7✔
1803

1804
        if (!(*ld)->is_visible()) {
7✔
1805
            continue;
1✔
1806
        }
1807
        auto ll = (*ld)->get_file()->begin() + cl;
7✔
1808
        if (ll->is_continued() || ll->is_ignored()) {
7✔
1809
            continue;
1✔
1810
        }
1811
        auto eval_res
1812
            = this->eval_sql_filter(this->lss_marker_stmt.in(), ld, ll);
6✔
1813

1814
        if (eval_res.isErr()) {
6✔
1815
            ll->set_expr_mark(false);
×
1816
        } else {
1817
            auto matched = eval_res.unwrap();
6✔
1818

1819
            if (matched) {
6✔
1820
                ll->set_expr_mark(true);
4✔
1821
                expr_marks_bv.insert_once(row);
4✔
1822
            } else {
1823
                ll->set_expr_mark(false);
2✔
1824
            }
1825
        }
1826
        if (this->lss_index_delegate) {
6✔
1827
            this->lss_index_delegate->index_line(
6✔
1828
                *this, (*ld)->get_file_ptr(), ll);
6✔
1829
        }
1830
    }
6✔
1831
    if (this->lss_index_delegate) {
583✔
1832
        this->lss_index_delegate->index_complete(*this);
583✔
1833
    }
1834

1835
    return Ok();
583✔
1836
}
1837

1838
Result<void, lnav::console::user_message>
1839
logfile_sub_source::set_preview_sql_filter(sqlite3_stmt* stmt)
701✔
1840
{
1841
    if (stmt != nullptr && !this->lss_filtered_index.empty()) {
701✔
1842
        auto top_cl = this->at(0_vl);
×
1843
        auto ld = this->find_data(top_cl);
×
1844
        auto eval_res
1845
            = this->eval_sql_filter(stmt, ld, (*ld)->get_file_ptr()->begin());
×
1846

1847
        if (eval_res.isErr()) {
×
1848
            sqlite3_finalize(stmt);
×
1849
            return Err(eval_res.unwrapErr());
×
1850
        }
1851
    }
1852

1853
    this->lss_preview_filter_stmt = stmt;
701✔
1854

1855
    return Ok();
701✔
1856
}
1857

1858
Result<bool, lnav::console::user_message>
1859
logfile_sub_source::eval_sql_filter(sqlite3_stmt* stmt,
12,511✔
1860
                                    iterator ld,
1861
                                    logfile::const_iterator ll)
1862
{
1863
    if (stmt == nullptr) {
12,511✔
1864
        return Ok(false);
24,932✔
1865
    }
1866

1867
    auto* lf = (*ld)->get_file_ptr();
45✔
1868
    char timestamp_buffer[64];
1869
    shared_buffer_ref raw_sbr;
45✔
1870
    logline_value_vector values;
45✔
1871
    auto& sbr = values.lvv_sbr;
45✔
1872
    lf->read_full_message(ll, sbr);
45✔
1873
    sbr.erase_ansi();
45✔
1874
    auto format = lf->get_format();
45✔
1875
    string_attrs_t sa;
45✔
1876
    auto line_number = std::distance(lf->cbegin(), ll);
45✔
1877
    format->annotate(lf, line_number, sa, values);
45✔
1878

1879
    sqlite3_reset(stmt);
45✔
1880
    sqlite3_clear_bindings(stmt);
45✔
1881

1882
    auto count = sqlite3_bind_parameter_count(stmt);
45✔
1883
    for (int lpc = 0; lpc < count; lpc++) {
93✔
1884
        const auto* name = sqlite3_bind_parameter_name(stmt, lpc + 1);
48✔
1885

1886
        if (name[0] == '$') {
48✔
1887
            const char* env_value;
1888

1889
            if ((env_value = getenv(&name[1])) != nullptr) {
1✔
1890
                sqlite3_bind_text(stmt, lpc + 1, env_value, -1, SQLITE_STATIC);
1✔
1891
            }
1892
            continue;
1✔
1893
        }
1✔
1894
        if (strcmp(name, ":log_level") == 0) {
47✔
1895
            auto lvl = ll->get_level_name();
3✔
1896
            sqlite3_bind_text(
3✔
1897
                stmt, lpc + 1, lvl.data(), lvl.length(), SQLITE_STATIC);
1898
            continue;
3✔
1899
        }
3✔
1900
        if (strcmp(name, ":log_time") == 0) {
44✔
1901
            auto len = sql_strftime(timestamp_buffer,
×
1902
                                    sizeof(timestamp_buffer),
1903
                                    ll->get_timeval(),
×
1904
                                    'T');
1905
            sqlite3_bind_text(
×
1906
                stmt, lpc + 1, timestamp_buffer, len, SQLITE_STATIC);
1907
            continue;
×
1908
        }
1909
        if (strcmp(name, ":log_time_msecs") == 0) {
44✔
1910
            sqlite3_bind_int64(
1✔
1911
                stmt,
1912
                lpc + 1,
1913
                ll->get_time<std::chrono::milliseconds>().count());
1✔
1914
            continue;
1✔
1915
        }
1916
        if (strcmp(name, ":log_mark") == 0) {
43✔
1917
            sqlite3_bind_int(stmt, lpc + 1, ll->is_marked());
×
1918
            continue;
×
1919
        }
1920
        if (strcmp(name, ":log_comment") == 0) {
43✔
1921
            const auto& bm = lf->get_bookmark_metadata();
×
1922
            auto line_number
1923
                = static_cast<uint32_t>(std::distance(lf->cbegin(), ll));
×
1924
            auto bm_iter = bm.find(line_number);
×
1925
            if (bm_iter != bm.end() && !bm_iter->second.bm_comment.empty()) {
×
1926
                const auto& meta = bm_iter->second;
×
1927
                sqlite3_bind_text(stmt,
×
1928
                                  lpc + 1,
1929
                                  meta.bm_comment.c_str(),
1930
                                  meta.bm_comment.length(),
×
1931
                                  SQLITE_STATIC);
1932
            }
1933
            continue;
×
1934
        }
1935
        if (strcmp(name, ":log_annotations") == 0) {
43✔
1936
            const auto& bm = lf->get_bookmark_metadata();
×
1937
            auto line_number
1938
                = static_cast<uint32_t>(std::distance(lf->cbegin(), ll));
×
1939
            auto bm_iter = bm.find(line_number);
×
1940
            if (bm_iter != bm.end()
×
1941
                && !bm_iter->second.bm_annotations.la_pairs.empty())
×
1942
            {
1943
                const auto& meta = bm_iter->second;
×
1944
                auto anno_str = logmsg_annotations_handlers.to_string(
1945
                    meta.bm_annotations);
×
1946

1947
                sqlite3_bind_text(stmt,
×
1948
                                  lpc + 1,
1949
                                  anno_str.c_str(),
1950
                                  anno_str.length(),
×
1951
                                  SQLITE_TRANSIENT);
1952
            }
1953
            continue;
×
1954
        }
1955
        if (strcmp(name, ":log_tags") == 0) {
43✔
1956
            const auto& bm = lf->get_bookmark_metadata();
9✔
1957
            auto line_number
1958
                = static_cast<uint32_t>(std::distance(lf->cbegin(), ll));
9✔
1959
            auto bm_iter = bm.find(line_number);
9✔
1960
            if (bm_iter != bm.end() && !bm_iter->second.bm_tags.empty()) {
9✔
1961
                const auto& meta = bm_iter->second;
1✔
1962
                yajlpp_gen gen;
1✔
1963

1964
                yajl_gen_config(gen, yajl_gen_beautify, false);
1✔
1965

1966
                {
1967
                    yajlpp_array arr(gen);
1✔
1968

1969
                    for (const auto& str : meta.bm_tags) {
2✔
1970
                        arr.gen(str);
1✔
1971
                    }
1972
                }
1✔
1973

1974
                string_fragment sf = gen.to_string_fragment();
1✔
1975

1976
                sqlite3_bind_text(
1✔
1977
                    stmt, lpc + 1, sf.data(), sf.length(), SQLITE_TRANSIENT);
1978
            }
1✔
1979
            continue;
9✔
1980
        }
9✔
1981
        if (strcmp(name, ":log_format") == 0) {
34✔
1982
            const auto format_name = format->get_name();
3✔
1983
            sqlite3_bind_text(stmt,
3✔
1984
                              lpc + 1,
1985
                              format_name.get(),
1986
                              format_name.size(),
3✔
1987
                              SQLITE_STATIC);
1988
            continue;
3✔
1989
        }
3✔
1990
        if (strcmp(name, ":log_format_regex") == 0) {
31✔
1991
            const auto pat_name = format->get_pattern_name(line_number);
×
1992
            sqlite3_bind_text(
×
1993
                stmt, lpc + 1, pat_name.get(), pat_name.size(), SQLITE_STATIC);
×
1994
            continue;
×
1995
        }
1996
        if (strcmp(name, ":log_path") == 0) {
31✔
1997
            const auto& filename = lf->get_filename();
×
1998
            sqlite3_bind_text(stmt,
×
1999
                              lpc + 1,
2000
                              filename.c_str(),
2001
                              filename.native().length(),
×
2002
                              SQLITE_STATIC);
2003
            continue;
×
2004
        }
2005
        if (strcmp(name, ":log_unique_path") == 0) {
31✔
2006
            const auto& filename = lf->get_unique_path();
×
2007
            sqlite3_bind_text(stmt,
×
2008
                              lpc + 1,
2009
                              filename.c_str(),
2010
                              filename.native().length(),
×
2011
                              SQLITE_STATIC);
2012
            continue;
×
2013
        }
2014
        if (strcmp(name, ":log_text") == 0) {
31✔
2015
            sqlite3_bind_text(
4✔
2016
                stmt, lpc + 1, sbr.get_data(), sbr.length(), SQLITE_STATIC);
4✔
2017
            continue;
4✔
2018
        }
2019
        if (strcmp(name, ":log_body") == 0) {
27✔
2020
            auto body_attr_opt = get_string_attr(sa, SA_BODY);
10✔
2021
            if (body_attr_opt) {
10✔
2022
                const auto& sar
2023
                    = body_attr_opt.value().saw_string_attr->sa_range;
10✔
2024

2025
                sqlite3_bind_text(stmt,
20✔
2026
                                  lpc + 1,
2027
                                  sbr.get_data_at(sar.lr_start),
10✔
2028
                                  sar.length(),
2029
                                  SQLITE_STATIC);
2030
            } else {
2031
                sqlite3_bind_null(stmt, lpc + 1);
×
2032
            }
2033
            continue;
10✔
2034
        }
10✔
2035
        if (strcmp(name, ":log_opid") == 0) {
17✔
2036
            if (values.lvv_opid_value) {
×
2037
                sqlite3_bind_text(stmt,
×
2038
                                  lpc + 1,
2039
                                  values.lvv_opid_value->c_str(),
2040
                                  values.lvv_opid_value->length(),
×
2041
                                  SQLITE_STATIC);
2042
            } else {
2043
                sqlite3_bind_null(stmt, lpc + 1);
×
2044
            }
2045
            continue;
×
2046
        }
2047
        if (strcmp(name, ":log_raw_text") == 0) {
17✔
2048
            auto res = lf->read_raw_message(ll);
×
2049

2050
            if (res.isOk()) {
×
2051
                raw_sbr = res.unwrap();
×
2052
                sqlite3_bind_text(stmt,
×
2053
                                  lpc + 1,
2054
                                  raw_sbr.get_data(),
2055
                                  raw_sbr.length(),
×
2056
                                  SQLITE_STATIC);
2057
            }
2058
            continue;
×
2059
        }
2060
        for (const auto& lv : values.lvv_values) {
129✔
2061
            if (lv.lv_meta.lvm_name != &name[1]) {
129✔
2062
                continue;
112✔
2063
            }
2064

2065
            switch (lv.lv_meta.lvm_kind) {
17✔
2066
                case value_kind_t::VALUE_BOOLEAN:
×
2067
                    sqlite3_bind_int64(stmt, lpc + 1, lv.lv_value.i);
×
2068
                    break;
×
2069
                case value_kind_t::VALUE_FLOAT:
×
2070
                    sqlite3_bind_double(stmt, lpc + 1, lv.lv_value.d);
×
2071
                    break;
×
2072
                case value_kind_t::VALUE_INTEGER:
12✔
2073
                    sqlite3_bind_int64(stmt, lpc + 1, lv.lv_value.i);
12✔
2074
                    break;
12✔
2075
                case value_kind_t::VALUE_NULL:
×
2076
                    sqlite3_bind_null(stmt, lpc + 1);
×
2077
                    break;
×
2078
                default:
5✔
2079
                    sqlite3_bind_text(stmt,
5✔
2080
                                      lpc + 1,
2081
                                      lv.text_value(),
2082
                                      lv.text_length(),
5✔
2083
                                      SQLITE_TRANSIENT);
2084
                    break;
5✔
2085
            }
2086
            break;
17✔
2087
        }
2088
    }
2089

2090
    auto step_res = sqlite3_step(stmt);
45✔
2091

2092
    sqlite3_reset(stmt);
45✔
2093
    sqlite3_clear_bindings(stmt);
45✔
2094
    switch (step_res) {
45✔
2095
        case SQLITE_OK:
14✔
2096
        case SQLITE_DONE:
2097
            return Ok(false);
28✔
2098
        case SQLITE_ROW:
30✔
2099
            return Ok(true);
60✔
2100
        default:
1✔
2101
            return Err(sqlite3_error_to_user_message(sqlite3_db_handle(stmt)));
1✔
2102
    }
2103
}
45✔
2104

2105
bool
2106
logfile_sub_source::check_extra_filters(iterator ld, logfile::iterator ll)
12,475✔
2107
{
2108
    if (this->lss_marked_only) {
12,475✔
2109
        auto found_mark = ll->is_marked() || ll->is_expr_marked();
6✔
2110
        auto to_start_ll = ll;
6✔
2111
        while (!found_mark && to_start_ll->is_continued()) {
6✔
2112
            if (to_start_ll->is_marked() || to_start_ll->is_expr_marked()) {
×
2113
                found_mark = true;
×
2114
            }
2115
            --to_start_ll;
×
2116
        }
2117
        auto to_end_ll = std::next(ll);
6✔
2118
        while (!found_mark && to_end_ll != (*ld)->get_file_ptr()->end()
10✔
2119
               && to_end_ll->is_continued())
11✔
2120
        {
2121
            if (to_end_ll->is_marked() || to_end_ll->is_expr_marked()) {
1✔
2122
                found_mark = true;
1✔
2123
            }
2124
            ++to_end_ll;
1✔
2125
        }
2126
        if (!found_mark) {
6✔
2127
            return false;
3✔
2128
        }
2129
    }
2130

2131
    if (ll->get_msg_level() < this->lss_min_log_level) {
12,472✔
2132
        return false;
2✔
2133
    }
2134

2135
    if (*ll < this->ttt_min_row_time) {
12,470✔
2136
        return false;
3✔
2137
    }
2138

2139
    if (!(*ll <= this->ttt_max_row_time)) {
12,467✔
2140
        return false;
4✔
2141
    }
2142

2143
    return true;
12,463✔
2144
}
2145

2146
void
2147
logfile_sub_source::invalidate_sql_filter()
20✔
2148
{
2149
    for (auto& ld : *this) {
40✔
2150
        ld->ld_filter_state.lfo_filter_state.clear_filter_state(0);
20✔
2151
    }
2152
}
20✔
2153

2154
void
2155
logfile_sub_source::text_mark(const bookmark_type_t* bm,
130✔
2156
                              vis_line_t line,
2157
                              bool added)
2158
{
2159
    if (line >= (int) this->lss_index.size()) {
130✔
2160
        return;
×
2161
    }
2162

2163
    auto cl = this->at(line);
130✔
2164

2165
    if (bm == &textview_curses::BM_USER) {
130✔
2166
        auto* ll = this->find_line(cl);
49✔
2167

2168
        ll->set_mark(added);
49✔
2169
    }
2170
    auto lb = this->lss_user_marks[bm].bv_tree.lower_bound(cl);
130✔
2171
    if (added) {
130✔
2172
        if (lb == this->lss_user_marks[bm].bv_tree.end() || *lb != cl) {
65✔
2173
            this->lss_user_marks[bm].bv_tree.insert(cl);
60✔
2174
        }
2175
    } else if (lb != this->lss_user_marks[bm].bv_tree.end() && *lb == cl) {
65✔
2176
        this->lss_user_marks[bm].bv_tree.erase(lb);
7✔
2177
    }
2178
    if (bm == &textview_curses::BM_META
130✔
2179
        && this->lss_meta_grepper.gps_proc != nullptr)
16✔
2180
    {
2181
        this->tss_view->search_range(line, line + 1_vl);
1✔
2182
        this->tss_view->search_new_data();
1✔
2183
    }
2184
}
2185

2186
void
2187
logfile_sub_source::text_clear_marks(const bookmark_type_t* bm)
46✔
2188
{
2189
    if (bm == &textview_curses::BM_USER) {
46✔
2190
        for (auto iter = this->lss_user_marks[bm].bv_tree.begin();
6✔
2191
             iter != this->lss_user_marks[bm].bv_tree.end();
6✔
2192
             ++iter)
×
2193
        {
2194
            this->find_line(*iter)->set_mark(false);
×
2195
        }
2196
    }
2197
    this->lss_user_marks[bm].clear();
46✔
2198
}
46✔
2199

2200
void
2201
logfile_sub_source::remove_file(std::shared_ptr<logfile> lf)
484✔
2202
{
2203
    auto iter = std::find_if(
484✔
2204
        this->lss_files.begin(), this->lss_files.end(), logfile_data_eq(lf));
968✔
2205
    if (iter != this->lss_files.end()) {
484✔
2206
        int file_index = iter - this->lss_files.begin();
484✔
2207

2208
        (*iter)->clear();
484✔
2209
        for (auto& bv : this->lss_user_marks) {
4,356✔
2210
            auto mark_curr = content_line_t(file_index * MAX_LINES_PER_FILE);
3,872✔
2211
            auto mark_end
2212
                = content_line_t((file_index + 1) * MAX_LINES_PER_FILE);
3,872✔
2213
            auto file_range = bv.equal_range(mark_curr, mark_end);
3,872✔
2214

2215
            if (file_range.first != file_range.second) {
3,872✔
2216
                auto to_del = std::vector<content_line_t>{};
61✔
2217
                for (auto file_iter = file_range.first;
61✔
2218
                     file_iter != file_range.second;
145✔
2219
                     ++file_iter)
84✔
2220
                {
2221
                    to_del.emplace_back(*file_iter);
84✔
2222
                }
2223

2224
                for (auto cl : to_del) {
145✔
2225
                    bv.bv_tree.erase(cl);
84✔
2226
                }
2227
            }
61✔
2228
        }
2229

2230
        this->lss_force_rebuild = true;
484✔
2231
    }
2232
    while (!this->lss_files.empty()) {
968✔
2233
        if (this->lss_files.back()->get_file_ptr() == nullptr) {
534✔
2234
            this->lss_files.pop_back();
484✔
2235
        } else {
2236
            break;
50✔
2237
        }
2238
    }
2239
    this->lss_token_file = nullptr;
484✔
2240
}
484✔
2241

2242
std::optional<vis_line_t>
2243
logfile_sub_source::find_from_content(content_line_t cl)
5✔
2244
{
2245
    content_line_t line = cl;
5✔
2246
    std::shared_ptr<logfile> lf = this->find(line);
5✔
2247

2248
    if (lf != nullptr) {
5✔
2249
        auto ll_iter = lf->begin() + line;
5✔
2250
        auto& ll = *ll_iter;
5✔
2251
        auto vis_start_opt = this->find_from_time(ll.get_timeval());
5✔
2252

2253
        if (!vis_start_opt) {
5✔
2254
            return std::nullopt;
×
2255
        }
2256

2257
        auto vis_start = *vis_start_opt;
5✔
2258

2259
        while (vis_start < vis_line_t(this->text_line_count())) {
5✔
2260
            content_line_t guess_cl = this->at(vis_start);
5✔
2261

2262
            if (cl == guess_cl) {
5✔
2263
                return vis_start;
5✔
2264
            }
2265

2266
            auto guess_line = this->find_line(guess_cl);
×
2267

2268
            if (!guess_line || ll < *guess_line) {
×
2269
                return std::nullopt;
×
2270
            }
2271

2272
            ++vis_start;
×
2273
        }
2274
    }
2275

2276
    return std::nullopt;
×
2277
}
5✔
2278

2279
void
2280
logfile_sub_source::reload_index_delegate()
591✔
2281
{
2282
    if (this->lss_index_delegate == nullptr) {
591✔
2283
        return;
×
2284
    }
2285

2286
    this->lss_index_delegate->index_start(*this);
591✔
2287
    for (const auto index : this->lss_filtered_index) {
819✔
2288
        auto cl = this->lss_index[index].value();
228✔
2289
        uint64_t line_number;
2290
        auto ld = this->find_data(cl, line_number);
228✔
2291
        std::shared_ptr<logfile> lf = (*ld)->get_file();
228✔
2292

2293
        this->lss_index_delegate->index_line(
228✔
2294
            *this, lf.get(), lf->begin() + line_number);
228✔
2295
    }
228✔
2296
    this->lss_index_delegate->index_complete(*this);
591✔
2297
}
2298

2299
std::optional<std::shared_ptr<text_filter>>
2300
logfile_sub_source::get_sql_filter()
1,762✔
2301
{
2302
    return this->tss_filters | lnav::itertools::find_if([](const auto& filt) {
1,762✔
2303
               return filt->get_index() == 0;
309✔
2304
           })
2305
        | lnav::itertools::deref();
3,524✔
2306
}
2307

2308
void
2309
log_location_history::loc_history_append(vis_line_t top)
90✔
2310
{
2311
    if (top >= vis_line_t(this->llh_log_source.text_line_count())) {
90✔
2312
        return;
2✔
2313
    }
2314

2315
    content_line_t cl = this->llh_log_source.at(top);
88✔
2316

2317
    auto iter = this->llh_history.begin();
88✔
2318
    iter += this->llh_history.size() - this->lh_history_position;
88✔
2319
    this->llh_history.erase_from(iter);
88✔
2320
    this->lh_history_position = 0;
88✔
2321
    this->llh_history.push_back(cl);
88✔
2322
}
2323

2324
std::optional<vis_line_t>
2325
log_location_history::loc_history_back(vis_line_t current_top)
2✔
2326
{
2327
    while (this->lh_history_position < this->llh_history.size()) {
2✔
2328
        auto iter = this->llh_history.rbegin();
2✔
2329

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

2332
        if (this->lh_history_position == 0 && vis_for_pos != current_top) {
2✔
2333
            return vis_for_pos;
2✔
2334
        }
2335

2336
        if ((this->lh_history_position + 1) >= this->llh_history.size()) {
2✔
2337
            break;
×
2338
        }
2339

2340
        this->lh_history_position += 1;
2✔
2341

2342
        iter += this->lh_history_position;
2✔
2343

2344
        vis_for_pos = this->llh_log_source.find_from_content(*iter);
2✔
2345

2346
        if (vis_for_pos) {
2✔
2347
            return vis_for_pos;
2✔
2348
        }
2349
    }
2350

2351
    return std::nullopt;
×
2352
}
2353

2354
std::optional<vis_line_t>
2355
log_location_history::loc_history_forward(vis_line_t current_top)
1✔
2356
{
2357
    while (this->lh_history_position > 0) {
1✔
2358
        this->lh_history_position -= 1;
1✔
2359

2360
        auto iter = this->llh_history.rbegin();
1✔
2361

2362
        iter += this->lh_history_position;
1✔
2363

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

2366
        if (vis_for_pos) {
1✔
2367
            return vis_for_pos;
1✔
2368
        }
2369
    }
2370

2371
    return std::nullopt;
×
2372
}
2373

2374
bool
2375
sql_filter::matches(std::optional<line_source> ls_opt,
19✔
2376
                    const shared_buffer_ref& line)
2377
{
2378
    if (!ls_opt) {
19✔
2379
        return false;
×
2380
    }
2381

2382
    auto ls = ls_opt;
19✔
2383

2384
    if (!ls->ls_line->is_message()) {
19✔
2385
        return false;
1✔
2386
    }
2387
    if (this->sf_filter_stmt == nullptr) {
18✔
2388
        return false;
×
2389
    }
2390

2391
    auto lfp = ls->ls_file.shared_from_this();
18✔
2392
    auto ld = this->sf_log_source.find_data_i(lfp);
18✔
2393
    if (ld == this->sf_log_source.end()) {
18✔
2394
        return false;
×
2395
    }
2396

2397
    auto eval_res = this->sf_log_source.eval_sql_filter(
18✔
2398
        this->sf_filter_stmt, ld, ls->ls_line);
18✔
2399
    if (eval_res.unwrapOr(true)) {
18✔
2400
        return false;
12✔
2401
    }
2402

2403
    return true;
6✔
2404
}
18✔
2405

2406
std::string
2407
sql_filter::to_command() const
×
2408
{
2409
    return fmt::format(FMT_STRING("filter-expr {}"), this->lf_id);
×
2410
}
2411

2412
std::optional<line_info>
2413
logfile_sub_source::meta_grepper::grep_value_for_line(vis_line_t line,
×
2414
                                                      std::string& value_out)
2415
{
2416
    auto line_meta_opt = this->lmg_source.find_bookmark_metadata(line);
×
2417
    if (!line_meta_opt) {
×
2418
        value_out.clear();
×
2419
    } else {
2420
        auto& bm = *(line_meta_opt.value());
×
2421

2422
        {
2423
            md2attr_line mdal;
×
2424

2425
            auto parse_res = md4cpp::parse(bm.bm_comment, mdal);
×
2426
            if (parse_res.isOk()) {
×
2427
                value_out.append(parse_res.unwrap().get_string());
×
2428
            } else {
2429
                value_out.append(bm.bm_comment);
×
2430
            }
2431
        }
2432

2433
        value_out.append("\x1c");
×
2434
        for (const auto& tag : bm.bm_tags) {
×
2435
            value_out.append(tag);
×
2436
            value_out.append("\x1c");
×
2437
        }
2438
        value_out.append("\x1c");
×
2439
        for (const auto& pair : bm.bm_annotations.la_pairs) {
×
2440
            value_out.append(pair.first);
×
2441
            value_out.append("\x1c");
×
2442

2443
            md2attr_line mdal;
×
2444

2445
            auto parse_res = md4cpp::parse(pair.second, mdal);
×
2446
            if (parse_res.isOk()) {
×
2447
                value_out.append(parse_res.unwrap().get_string());
×
2448
            } else {
2449
                value_out.append(pair.second);
×
2450
            }
2451
            value_out.append("\x1c");
×
2452
        }
2453
        value_out.append("\x1c");
×
2454
        value_out.append(bm.bm_opid);
×
2455
    }
2456

2457
    if (!this->lmg_done) {
×
2458
        return line_info{};
×
2459
    }
2460

2461
    return std::nullopt;
×
2462
}
2463

2464
vis_line_t
2465
logfile_sub_source::meta_grepper::grep_initial_line(vis_line_t start,
×
2466
                                                    vis_line_t highest)
2467
{
2468
    auto& bm = this->lmg_source.tss_view->get_bookmarks();
×
2469
    auto& bv = bm[&textview_curses::BM_META];
×
2470

2471
    if (bv.empty()) {
×
2472
        return -1_vl;
×
2473
    }
2474
    return *bv.bv_tree.begin();
×
2475
}
2476

2477
void
2478
logfile_sub_source::meta_grepper::grep_next_line(vis_line_t& line)
×
2479
{
2480
    auto& bm = this->lmg_source.tss_view->get_bookmarks();
×
2481
    auto& bv = bm[&textview_curses::BM_META];
×
2482

2483
    auto line_opt = bv.next(vis_line_t(line));
×
2484
    if (!line_opt) {
×
2485
        this->lmg_done = true;
×
2486
    }
2487
    line = line_opt.value_or(-1_vl);
×
2488
}
2489

2490
void
2491
logfile_sub_source::meta_grepper::grep_begin(grep_proc<vis_line_t>& gp,
32✔
2492
                                             vis_line_t start,
2493
                                             vis_line_t stop)
2494
{
2495
    this->lmg_source.quiesce();
32✔
2496

2497
    this->lmg_source.tss_view->grep_begin(gp, start, stop);
32✔
2498
}
32✔
2499

2500
void
2501
logfile_sub_source::meta_grepper::grep_end(grep_proc<vis_line_t>& gp)
23✔
2502
{
2503
    this->lmg_source.tss_view->grep_end(gp);
23✔
2504
}
23✔
2505

2506
void
2507
logfile_sub_source::meta_grepper::grep_match(grep_proc<vis_line_t>& gp,
3✔
2508
                                             vis_line_t line)
2509
{
2510
    this->lmg_source.tss_view->grep_match(gp, line);
3✔
2511
}
3✔
2512

2513
static std::vector<breadcrumb::possibility>
2514
timestamp_poss()
10✔
2515
{
2516
    const static std::vector<breadcrumb::possibility> retval = {
2517
        breadcrumb::possibility{"-1 day"},
2518
        breadcrumb::possibility{"-1h"},
2519
        breadcrumb::possibility{"-30m"},
2520
        breadcrumb::possibility{"-15m"},
2521
        breadcrumb::possibility{"-5m"},
2522
        breadcrumb::possibility{"-1m"},
2523
        breadcrumb::possibility{"+1m"},
2524
        breadcrumb::possibility{"+5m"},
2525
        breadcrumb::possibility{"+15m"},
2526
        breadcrumb::possibility{"+30m"},
2527
        breadcrumb::possibility{"+1h"},
2528
        breadcrumb::possibility{"+1 day"},
2529
    };
80✔
2530

2531
    return retval;
10✔
2532
}
20✔
2533

2534
static attr_line_t
2535
to_display(const std::shared_ptr<logfile>& lf)
20✔
2536
{
2537
    attr_line_t retval;
20✔
2538

2539
    if (lf->get_open_options().loo_piper) {
20✔
2540
        if (!lf->get_open_options().loo_piper->is_finished()) {
×
2541
            retval.append("\u21bb "_list_glyph);
×
2542
        }
2543
    }
2544
    retval.append(lf->get_unique_path());
20✔
2545

2546
    return retval;
20✔
2547
}
×
2548

2549
void
2550
logfile_sub_source::text_crumbs_for_line(int line,
10✔
2551
                                         std::vector<breadcrumb::crumb>& crumbs)
2552
{
2553
    text_sub_source::text_crumbs_for_line(line, crumbs);
10✔
2554

2555
    if (this->lss_filtered_index.empty()) {
10✔
2556
        return;
×
2557
    }
2558

2559
    auto vl = vis_line_t(line);
10✔
2560
    auto bmc = this->get_bookmark_metadata_context(
10✔
2561
        vl, bookmark_metadata::categories::partition);
2562
    if (bmc.bmc_current_metadata) {
10✔
2563
        const auto& name = bmc.bmc_current_metadata.value()->bm_name;
1✔
2564
        auto key = text_anchors::to_anchor_string(name);
1✔
2565
        auto display = attr_line_t()
1✔
2566
                           .append("\u2291 "_symbol)
1✔
2567
                           .append(lnav::roles::variable(name))
2✔
2568
                           .move();
1✔
2569
        crumbs.emplace_back(
1✔
2570
            key,
2571
            display,
2572
            [this]() -> std::vector<breadcrumb::possibility> {
×
2573
                auto& vb = this->tss_view->get_bookmarks();
1✔
2574
                const auto& bv = vb[&textview_curses::BM_PARTITION];
1✔
2575
                std::vector<breadcrumb::possibility> retval;
1✔
2576

2577
                for (const auto& vl : bv.bv_tree) {
2✔
2578
                    auto meta_opt = this->find_bookmark_metadata(vl);
1✔
2579
                    if (!meta_opt || meta_opt.value()->bm_name.empty()) {
1✔
2580
                        continue;
×
2581
                    }
2582

2583
                    const auto& name = meta_opt.value()->bm_name;
1✔
2584
                    retval.emplace_back(text_anchors::to_anchor_string(name),
1✔
2585
                                        name);
2586
                }
2587

2588
                return retval;
1✔
2589
            },
×
2590
            [ec = this->lss_exec_context](const auto& part) {
1✔
2591
                auto cmd = fmt::format(FMT_STRING(":goto {}"),
×
2592
                                       part.template get<std::string>());
2593
                ec->execute(INTERNAL_SRC_LOC, cmd);
×
2594
            });
×
2595
    }
1✔
2596

2597
    auto line_pair_opt = this->find_line_with_file(vl);
10✔
2598
    if (!line_pair_opt) {
10✔
2599
        return;
×
2600
    }
2601
    auto line_pair = line_pair_opt.value();
10✔
2602
    auto& lf = line_pair.first;
10✔
2603
    auto format = lf->get_format();
10✔
2604
    char ts[64];
2605

2606
    sql_strftime(ts, sizeof(ts), line_pair.second->get_timeval(), 'T');
10✔
2607

2608
    crumbs.emplace_back(std::string(ts),
10✔
2609
                        timestamp_poss,
2610
                        [ec = this->lss_exec_context](const auto& ts) {
10✔
2611
                            auto cmd
×
2612
                                = fmt::format(FMT_STRING(":goto {}"),
×
2613
                                              ts.template get<std::string>());
2614
                            ec->execute(INTERNAL_SRC_LOC, cmd);
×
2615
                        });
×
2616
    crumbs.back().c_expected_input
10✔
2617
        = breadcrumb::crumb::expected_input_t::anything;
10✔
2618
    crumbs.back().c_search_placeholder = "(Enter an absolute or relative time)";
10✔
2619

2620
    auto format_name = format->get_name().to_string();
10✔
2621

2622
    crumbs.emplace_back(
10✔
2623
        format_name,
2624
        attr_line_t().append(format_name),
10✔
2625
        [this]() -> std::vector<breadcrumb::possibility> {
×
2626
            return this->lss_files
10✔
2627
                | lnav::itertools::filter_in([](const auto& file_data) {
20✔
2628
                       return file_data->is_visible();
10✔
2629
                   })
2630
                | lnav::itertools::map(&logfile_data::get_file_ptr)
30✔
2631
                | lnav::itertools::map(&logfile::get_format_name)
30✔
2632
                | lnav::itertools::unique()
30✔
2633
                | lnav::itertools::map([](const auto& elem) {
40✔
2634
                       return breadcrumb::possibility{
2635
                           elem.to_string(),
2636
                       };
10✔
2637
                   })
2638
                | lnav::itertools::to_vector();
30✔
2639
        },
2640
        [ec = this->lss_exec_context](const auto& format_name) {
10✔
2641
            static const std::string MOVE_STMT = R"(;UPDATE lnav_views
2642
     SET selection = ifnull(
2643
         (SELECT log_line FROM all_logs WHERE log_format = $format_name LIMIT 1),
2644
         (SELECT raise_error(
2645
            'Could not find format: ' || $format_name,
2646
            'The corresponding log messages might have been filtered out'))
2647
       )
2648
     WHERE name = 'log'
2649
)";
2650

2651
            ec->execute_with(
×
2652
                INTERNAL_SRC_LOC,
×
2653
                MOVE_STMT,
2654
                std::make_pair("format_name",
2655
                               format_name.template get<std::string>()));
2656
        });
×
2657

2658
    auto msg_start_iter = lf->message_start(line_pair.second);
10✔
2659
    auto file_line_number = std::distance(lf->begin(), msg_start_iter);
10✔
2660
    crumbs.emplace_back(
20✔
2661
        lf->get_unique_path(),
10✔
2662
        to_display(lf).appendf(FMT_STRING("[{:L}]"), file_line_number),
40✔
2663
        [this]() -> std::vector<breadcrumb::possibility> {
×
2664
            return this->lss_files
10✔
2665
                | lnav::itertools::filter_in([](const auto& file_data) {
20✔
2666
                       return file_data->is_visible();
10✔
2667
                   })
2668
                | lnav::itertools::map([](const auto& file_data) {
20✔
2669
                       return breadcrumb::possibility{
2670
                           file_data->get_file_ptr()->get_unique_path(),
10✔
2671
                           to_display(file_data->get_file()),
2672
                       };
10✔
2673
                   });
20✔
2674
        },
2675
        [ec = this->lss_exec_context](const auto& uniq_path) {
10✔
2676
            static const std::string MOVE_STMT = R"(;UPDATE lnav_views
2677
     SET selection = ifnull(
2678
          (SELECT log_line FROM all_logs WHERE log_unique_path = $uniq_path LIMIT 1),
2679
          (SELECT raise_error(
2680
            'Could not find file: ' || $uniq_path,
2681
            'The corresponding log messages might have been filtered out'))
2682
         )
2683
     WHERE name = 'log'
2684
)";
2685

2686
            ec->execute_with(
×
2687
                INTERNAL_SRC_LOC,
×
2688
                MOVE_STMT,
2689
                std::make_pair("uniq_path",
2690
                               uniq_path.template get<std::string>()));
2691
        });
×
2692

2693
    shared_buffer sb;
10✔
2694
    logline_value_vector values;
10✔
2695
    auto& sbr = values.lvv_sbr;
10✔
2696

2697
    lf->read_full_message(msg_start_iter, sbr);
10✔
2698
    attr_line_t al(to_string(sbr));
10✔
2699
    if (!sbr.get_metadata().m_valid_utf) {
10✔
2700
        scrub_to_utf8(&al.al_string[0], al.al_string.length());
×
2701
    }
2702
    if (sbr.get_metadata().m_has_ansi) {
10✔
2703
        // bleh
2704
        scrub_ansi_string(al.get_string(), &al.al_attrs);
×
2705
        sbr.share(sb, al.al_string.data(), al.al_string.size());
×
2706
    }
2707
    format->annotate(lf.get(), file_line_number, al.get_attrs(), values);
10✔
2708

2709
    {
2710
        static const std::string MOVE_STMT = R"(;UPDATE lnav_views
20✔
2711
          SET selection = ifnull(
2712
            (SELECT log_line FROM all_logs WHERE log_opid = $opid LIMIT 1),
2713
            (SELECT raise_error('Could not find opid: ' || $opid,
2714
                                'The corresponding log messages might have been filtered out')))
2715
          WHERE name = 'log'
2716
        )";
2717
        static const std::string ELLIPSIS = "\u22ef";
20✔
2718

2719
        auto opid_display = values.lvv_opid_value.has_value()
10✔
2720
            ? lnav::roles::identifier(values.lvv_opid_value.value())
9✔
2721
            : lnav::roles::hidden(ELLIPSIS);
19✔
2722
        crumbs.emplace_back(
20✔
2723
            values.lvv_opid_value.has_value() ? values.lvv_opid_value.value()
21✔
2724
                                              : "",
2725
            attr_line_t().append(opid_display),
10✔
2726
            [this]() -> std::vector<breadcrumb::possibility> {
×
2727
                std::set<std::string> poss_strs;
10✔
2728

2729
                for (const auto& file_data : this->lss_files) {
20✔
2730
                    if (file_data->get_file_ptr() == nullptr) {
10✔
2731
                        continue;
×
2732
                    }
2733
                    safe::ReadAccess<logfile::safe_opid_state> r_opid_map(
2734
                        file_data->get_file_ptr()->get_opids());
10✔
2735

2736
                    for (const auto& pair : r_opid_map->los_opid_ranges) {
719✔
2737
                        poss_strs.emplace(pair.first.to_string());
709✔
2738
                    }
2739
                }
10✔
2740

2741
                std::vector<breadcrumb::possibility> retval;
10✔
2742

2743
                std::transform(poss_strs.begin(),
10✔
2744
                               poss_strs.end(),
2745
                               std::back_inserter(retval),
2746
                               [](const auto& opid_str) {
709✔
2747
                                   return breadcrumb::possibility(opid_str);
709✔
2748
                               });
2749

2750
                return retval;
20✔
2751
            },
10✔
2752
            [ec = this->lss_exec_context](const auto& opid) {
10✔
2753
                ec->execute_with(
×
2754
                    INTERNAL_SRC_LOC,
×
2755
                    MOVE_STMT,
2756
                    std::make_pair("opid", opid.template get<std::string>()));
2757
            });
×
2758
    }
10✔
2759

2760
    auto sf = string_fragment::from_str(al.get_string());
10✔
2761
    auto body_opt = get_string_attr(al.get_attrs(), SA_BODY);
10✔
2762
    auto nl_pos_opt = sf.find('\n');
10✔
2763
    auto msg_line_number = std::distance(msg_start_iter, line_pair.second);
10✔
2764
    auto line_from_top = line - msg_line_number;
10✔
2765
    if (body_opt && nl_pos_opt) {
10✔
2766
        if (this->lss_token_meta_line != file_line_number
2✔
2767
            || this->lss_token_meta_size != sf.length())
1✔
2768
        {
2769
            if (body_opt->saw_string_attr->sa_range.length() < 128 * 1024) {
1✔
2770
                this->lss_token_meta
2771
                    = lnav::document::discover(al)
1✔
2772
                          .over_range(
1✔
2773
                              body_opt.value().saw_string_attr->sa_range)
1✔
2774
                          .perform();
1✔
2775
                // XXX discover_structure() changes `al`, have to recompute
2776
                // stuff
2777
                sf = al.to_string_fragment();
1✔
2778
                body_opt = get_string_attr(al.get_attrs(), SA_BODY);
1✔
2779
            } else {
2780
                this->lss_token_meta = lnav::document::metadata{};
×
2781
            }
2782
            this->lss_token_meta_line = file_line_number;
1✔
2783
            this->lss_token_meta_size = sf.length();
1✔
2784
        }
2785

2786
        const auto initial_size = crumbs.size();
1✔
2787
        auto sf_body
2788
            = sf.sub_range(body_opt->saw_string_attr->sa_range.lr_start,
1✔
2789
                           body_opt->saw_string_attr->sa_range.lr_end);
1✔
2790
        file_off_t line_offset = body_opt->saw_string_attr->sa_range.lr_start;
1✔
2791
        file_off_t line_end_offset = sf.length();
1✔
2792
        ssize_t line_number = 0;
1✔
2793

2794
        for (const auto& sf_line : sf_body.split_lines()) {
4✔
2795
            if (line_number >= msg_line_number) {
4✔
2796
                line_end_offset = line_offset + sf_line.length();
1✔
2797
                break;
1✔
2798
            }
2799
            line_number += 1;
3✔
2800
            line_offset += sf_line.length();
3✔
2801
        }
1✔
2802

2803
        this->lss_token_meta.m_sections_tree.visit_overlapping(
1✔
2804
            line_offset,
2805
            line_end_offset,
2806
            [this,
2✔
2807
             initial_size,
2808
             meta = &this->lss_token_meta,
1✔
2809
             &crumbs,
2810
             line_from_top](const auto& iv) {
2811
                auto path = crumbs | lnav::itertools::skip(initial_size)
6✔
2812
                    | lnav::itertools::map(&breadcrumb::crumb::c_key)
4✔
2813
                    | lnav::itertools::append(iv.value);
2✔
2814
                auto curr_node = lnav::document::hier_node::lookup_path(
2✔
2815
                    meta->m_sections_root.get(), path);
2✔
2816

2817
                crumbs.emplace_back(
4✔
2818
                    iv.value,
2✔
2819
                    [meta, path]() { return meta->possibility_provider(path); },
4✔
2820
                    [this, curr_node, path, line_from_top](const auto& key) {
4✔
2821
                        if (!curr_node) {
×
2822
                            return;
×
2823
                        }
2824
                        auto* parent_node = curr_node.value()->hn_parent;
×
2825
                        if (parent_node == nullptr) {
×
2826
                            return;
×
2827
                        }
2828
                        key.match(
2829
                            [parent_node](const std::string& str) {
×
2830
                                return parent_node->find_line_number(str);
×
2831
                            },
2832
                            [parent_node](size_t index) {
×
2833
                                return parent_node->find_line_number(index);
×
2834
                            })
2835
                            | [this, line_from_top](auto line_number) {
×
2836
                                  this->tss_view->set_selection(
×
2837
                                      vis_line_t(line_from_top + line_number));
×
2838
                              };
2839
                    });
2840
                if (curr_node && !curr_node.value()->hn_parent->is_named_only())
2✔
2841
                {
2842
                    auto node = lnav::document::hier_node::lookup_path(
×
2843
                        meta->m_sections_root.get(), path);
×
2844

2845
                    crumbs.back().c_expected_input
×
2846
                        = curr_node.value()
×
2847
                              ->hn_parent->hn_named_children.empty()
×
2848
                        ? breadcrumb::crumb::expected_input_t::index
×
2849
                        : breadcrumb::crumb::expected_input_t::index_or_exact;
2850
                    crumbs.back().with_possible_range(
×
2851
                        node | lnav::itertools::map([](const auto hn) {
×
2852
                            return hn->hn_parent->hn_children.size();
×
2853
                        })
2854
                        | lnav::itertools::unwrap_or(size_t{0}));
×
2855
                }
2856
            });
2✔
2857

2858
        auto path = crumbs | lnav::itertools::skip(initial_size)
2✔
2859
            | lnav::itertools::map(&breadcrumb::crumb::c_key);
2✔
2860
        auto node = lnav::document::hier_node::lookup_path(
1✔
2861
            this->lss_token_meta.m_sections_root.get(), path);
1✔
2862

2863
        if (node && !node.value()->hn_children.empty()) {
1✔
2864
            auto poss_provider = [curr_node = node.value()]() {
1✔
2865
                std::vector<breadcrumb::possibility> retval;
1✔
2866
                for (const auto& child : curr_node->hn_named_children) {
1✔
2867
                    retval.emplace_back(child.first);
×
2868
                }
2869
                return retval;
1✔
2870
            };
2871
            auto path_performer
2872
                = [this, curr_node = node.value(), line_from_top](
2✔
2873
                      const breadcrumb::crumb::key_t& value) {
2874
                      value.match(
×
2875
                          [curr_node](const std::string& str) {
×
2876
                              return curr_node->find_line_number(str);
×
2877
                          },
2878
                          [curr_node](size_t index) {
×
2879
                              return curr_node->find_line_number(index);
×
2880
                          })
2881
                          | [this, line_from_top](size_t line_number) {
×
2882
                                this->tss_view->set_selection(
×
2883
                                    vis_line_t(line_from_top + line_number));
×
2884
                            };
2885
                  };
1✔
2886
            crumbs.emplace_back("", "\u22ef", poss_provider, path_performer);
1✔
2887
            crumbs.back().c_expected_input
1✔
2888
                = node.value()->hn_named_children.empty()
2✔
2889
                ? breadcrumb::crumb::expected_input_t::index
1✔
2890
                : breadcrumb::crumb::expected_input_t::index_or_exact;
2891
        }
2892
    }
1✔
2893
}
10✔
2894

2895
void
2896
logfile_sub_source::quiesce()
60✔
2897
{
2898
    for (auto& ld : this->lss_files) {
102✔
2899
        auto* lf = ld->get_file_ptr();
42✔
2900

2901
        if (lf == nullptr) {
42✔
2902
            continue;
×
2903
        }
2904

2905
        lf->quiesce();
42✔
2906
    }
2907
}
60✔
2908

2909
bookmark_metadata&
2910
logfile_sub_source::get_bookmark_metadata(content_line_t cl)
25✔
2911
{
2912
    auto line_pair = this->find_line_with_file(cl).value();
25✔
2913
    auto line_number = static_cast<uint32_t>(
2914
        std::distance(line_pair.first->begin(), line_pair.second));
25✔
2915

2916
    return line_pair.first->get_bookmark_metadata()[line_number];
50✔
2917
}
25✔
2918

2919
logfile_sub_source::bookmark_metadata_context
2920
logfile_sub_source::get_bookmark_metadata_context(
2,830✔
2921
    vis_line_t vl, bookmark_metadata::categories desired) const
2922
{
2923
    const auto& vb = this->tss_view->get_bookmarks();
2,830✔
2924
    const auto& bv = vb[desired == bookmark_metadata::categories::partition
2925
                            ? &textview_curses::BM_PARTITION
2926
                            : &textview_curses::BM_META];
2,830✔
2927
    auto vl_iter = bv.bv_tree.lower_bound(vl + 1_vl);
2,830✔
2928

2929
    std::optional<vis_line_t> next_line;
2,830✔
2930
    for (auto next_vl_iter = vl_iter; next_vl_iter != bv.bv_tree.end();
2,830✔
2931
         ++next_vl_iter)
×
2932
    {
2933
        auto bm_opt = this->find_bookmark_metadata(*next_vl_iter);
2✔
2934
        if (!bm_opt) {
2✔
2935
            continue;
×
2936
        }
2937

2938
        if (bm_opt.value()->has(desired)) {
2✔
2939
            next_line = *next_vl_iter;
2✔
2940
            break;
2✔
2941
        }
2942
    }
2943
    if (vl_iter == bv.bv_tree.begin()) {
2,830✔
2944
        return bookmark_metadata_context{std::nullopt, std::nullopt, next_line};
2,814✔
2945
    }
2946

2947
    --vl_iter;
16✔
2948
    while (true) {
2949
        auto bm_opt = this->find_bookmark_metadata(*vl_iter);
16✔
2950
        if (bm_opt) {
16✔
2951
            if (bm_opt.value()->has(desired)) {
13✔
2952
                return bookmark_metadata_context{
2953
                    *vl_iter, bm_opt.value(), next_line};
16✔
2954
            }
2955
        }
2956

2957
        if (vl_iter == bv.bv_tree.begin()) {
3✔
2958
            return bookmark_metadata_context{
2959
                std::nullopt, std::nullopt, next_line};
3✔
2960
        }
2961
        --vl_iter;
×
2962
    }
2963
    return bookmark_metadata_context{std::nullopt, std::nullopt, next_line};
2964
}
2965

2966
std::optional<bookmark_metadata*>
2967
logfile_sub_source::find_bookmark_metadata(content_line_t cl) const
22,190✔
2968
{
2969
    auto line_pair = this->find_line_with_file(cl).value();
22,190✔
2970
    auto line_number = static_cast<uint32_t>(
2971
        std::distance(line_pair.first->begin(), line_pair.second));
22,190✔
2972

2973
    auto& bm = line_pair.first->get_bookmark_metadata();
22,190✔
2974
    auto bm_iter = bm.find(line_number);
22,190✔
2975
    if (bm_iter == bm.end()) {
22,190✔
2976
        return std::nullopt;
21,803✔
2977
    }
2978

2979
    return &bm_iter->second;
387✔
2980
}
22,190✔
2981

2982
void
2983
logfile_sub_source::erase_bookmark_metadata(content_line_t cl)
26✔
2984
{
2985
    auto line_pair = this->find_line_with_file(cl).value();
26✔
2986
    auto line_number = static_cast<uint32_t>(
2987
        std::distance(line_pair.first->begin(), line_pair.second));
26✔
2988

2989
    auto& bm = line_pair.first->get_bookmark_metadata();
26✔
2990
    auto bm_iter = bm.find(line_number);
26✔
2991
    if (bm_iter != bm.end()) {
26✔
2992
        bm.erase(bm_iter);
6✔
2993
    }
2994
}
26✔
2995

2996
void
2997
logfile_sub_source::clear_bookmark_metadata()
6✔
2998
{
2999
    for (auto& ld : *this) {
14✔
3000
        if (ld->get_file_ptr() == nullptr) {
8✔
3001
            continue;
×
3002
        }
3003

3004
        ld->get_file_ptr()->get_bookmark_metadata().clear();
8✔
3005
    }
3006
}
6✔
3007

3008
void
3009
logfile_sub_source::increase_line_context()
×
3010
{
3011
    auto old_context = this->lss_line_context;
×
3012

3013
    switch (this->lss_line_context) {
×
3014
        case line_context_t::filename:
×
3015
            // nothing to do
3016
            break;
×
3017
        case line_context_t::basename:
×
3018
            this->lss_line_context = line_context_t::filename;
×
3019
            break;
×
3020
        case line_context_t::none:
×
3021
            this->lss_line_context = line_context_t::basename;
×
3022
            break;
×
3023
        case line_context_t::time_column:
×
3024
            this->lss_line_context = line_context_t::none;
×
3025
            break;
×
3026
    }
3027
    if (old_context != this->lss_line_context) {
×
3028
        this->clear_line_size_cache();
×
3029
    }
3030
}
3031

3032
bool
3033
logfile_sub_source::decrease_line_context()
×
3034
{
3035
    static const auto& cfg
3036
        = injector::get<const logfile_sub_source_ns::config&>();
×
3037
    auto old_context = this->lss_line_context;
×
3038

3039
    switch (this->lss_line_context) {
×
3040
        case line_context_t::filename:
×
3041
            this->lss_line_context = line_context_t::basename;
×
3042
            break;
×
3043
        case line_context_t::basename:
×
3044
            this->lss_line_context = line_context_t::none;
×
3045
            break;
×
3046
        case line_context_t::none:
×
3047
            if (cfg.c_time_column
×
3048
                != logfile_sub_source_ns::time_column_feature_t::Disabled)
3049
            {
3050
                this->lss_line_context = line_context_t::time_column;
×
3051
            }
3052
            break;
×
3053
        case line_context_t::time_column:
×
3054
            break;
×
3055
    }
3056
    if (old_context != this->lss_line_context) {
×
3057
        this->clear_line_size_cache();
×
3058

3059
        return true;
×
3060
    }
3061

3062
    return false;
×
3063
}
3064

3065
size_t
3066
logfile_sub_source::get_filename_offset() const
254✔
3067
{
3068
    switch (this->lss_line_context) {
254✔
3069
        case line_context_t::filename:
×
3070
            return this->lss_filename_width;
×
3071
        case line_context_t::basename:
×
3072
            return this->lss_basename_width;
×
3073
        default:
254✔
3074
            return 0;
254✔
3075
    }
3076
}
3077

3078
size_t
3079
logfile_sub_source::file_count() const
×
3080
{
3081
    size_t retval = 0;
×
3082
    const_iterator iter;
×
3083

3084
    for (iter = this->cbegin(); iter != this->cend(); ++iter) {
×
3085
        if (*iter != nullptr && (*iter)->get_file() != nullptr) {
×
3086
            retval += 1;
×
3087
        }
3088
    }
3089

3090
    return retval;
×
3091
}
3092

3093
size_t
3094
logfile_sub_source::text_size_for_line(textview_curses& tc,
×
3095
                                       int row,
3096
                                       text_sub_source::line_flags_t flags)
3097
{
3098
    size_t index = row % LINE_SIZE_CACHE_SIZE;
×
3099

3100
    if (this->lss_line_size_cache[index].first != row) {
×
3101
        std::string value;
×
3102

3103
        this->text_value_for_line(tc, row, value, flags);
×
3104
        scrub_ansi_string(value, nullptr);
×
3105
        auto line_width = string_fragment::from_str(value).column_width();
×
3106
        if (this->lss_line_context == line_context_t::time_column) {
×
3107
            auto time_attr
3108
                = find_string_attr(this->lss_token_attrs, &L_TIMESTAMP);
×
3109
            if (time_attr != this->lss_token_attrs.end()) {
×
3110
                line_width -= time_attr->sa_range.length();
×
3111
                auto format = this->lss_token_file->get_format();
×
3112
                if (format->lf_level_hideable) {
×
3113
                    auto level_attr
3114
                        = find_string_attr(this->lss_token_attrs, &L_LEVEL);
×
3115
                    if (level_attr != this->lss_token_attrs.end()) {
×
3116
                        line_width -= level_attr->sa_range.length();
×
3117
                    }
3118
                }
3119
            }
3120
        }
3121
        this->lss_line_size_cache[index].second = line_width;
×
3122
        this->lss_line_size_cache[index].first = row;
×
3123
    }
3124
    return this->lss_line_size_cache[index].second;
×
3125
}
3126

3127
int
3128
logfile_sub_source::get_filtered_count_for(size_t filter_index) const
1✔
3129
{
3130
    int retval = 0;
1✔
3131

3132
    for (const auto& ld : this->lss_files) {
2✔
3133
        retval += ld->ld_filter_state.lfo_filter_state
1✔
3134
                      .tfs_filter_hits[filter_index];
1✔
3135
    }
3136

3137
    return retval;
1✔
3138
}
3139

3140
std::optional<vis_line_t>
3141
logfile_sub_source::row_for(const row_info& ri)
213✔
3142
{
3143
    auto lb = std::lower_bound(this->lss_filtered_index.begin(),
426✔
3144
                               this->lss_filtered_index.end(),
3145
                               ri.ri_time,
213✔
3146
                               filtered_logline_cmp(*this));
3147
    if (lb != this->lss_filtered_index.end()) {
213✔
3148
        auto first_lb = lb;
208✔
3149
        while (true) {
3150
            auto cl = this->lss_index[*lb].value();
216✔
3151
            if (content_line_t(ri.ri_id) == cl) {
216✔
3152
                first_lb = lb;
181✔
3153
                break;
181✔
3154
            }
3155
            auto ll = this->find_line(cl);
35✔
3156
            if (ll->get_timeval() != ri.ri_time) {
35✔
3157
                break;
27✔
3158
            }
3159
            auto next_lb = std::next(lb);
8✔
3160
            if (next_lb == this->lss_filtered_index.end()) {
8✔
3161
                break;
×
3162
            }
3163
            lb = next_lb;
8✔
3164
        }
8✔
3165

3166
        const auto dst
3167
            = std::distance(this->lss_filtered_index.begin(), first_lb);
208✔
3168
        return vis_line_t(dst);
208✔
3169
    }
3170

3171
    return std::nullopt;
5✔
3172
}
3173

3174
std::unique_ptr<logline_window>
3175
logfile_sub_source::window_at(vis_line_t start_vl, vis_line_t end_vl)
8✔
3176
{
3177
    return std::make_unique<logline_window>(*this, start_vl, end_vl);
8✔
3178
}
3179

3180
std::unique_ptr<logline_window>
3181
logfile_sub_source::window_at(vis_line_t start_vl)
152✔
3182
{
3183
    return std::make_unique<logline_window>(*this, start_vl, start_vl + 1_vl);
152✔
3184
}
3185

3186
std::unique_ptr<logline_window>
NEW
3187
logfile_sub_source::window_to_end(vis_line_t start_vl)
×
3188
{
3189
    return std::make_unique<logline_window>(
NEW
3190
        *this, start_vl, vis_line_t(this->text_line_count()));
×
3191
}
3192

3193
std::optional<vis_line_t>
3194
logfile_sub_source::row_for_anchor(const std::string& id)
3✔
3195
{
3196
    if (startswith(id, "#msg")) {
3✔
3197
        static const auto ANCHOR_RE
3198
            = lnav::pcre2pp::code::from_const(R"(#msg([0-9a-fA-F]+)-(.+))");
3✔
3199
        thread_local auto md = lnav::pcre2pp::match_data::unitialized();
3✔
3200

3201
        if (ANCHOR_RE.capture_from(id).into(md).found_p()) {
3✔
3202
            auto scan_res = scn::scan<int64_t>(md[1]->to_string_view(), "{:x}");
3✔
3203
            if (scan_res) {
3✔
3204
                auto ts_low = std::chrono::microseconds{scan_res->value()};
3✔
3205
                auto ts_high = ts_low + 1us;
3✔
3206

3207
                auto low_vl = this->row_for_time(to_timeval(ts_low));
3✔
3208
                auto high_vl = this->row_for_time(to_timeval(ts_high));
3✔
3209
                if (low_vl) {
3✔
3210
                    auto lw = this->window_at(
3211
                        low_vl.value(),
3✔
3212
                        high_vl.value_or(low_vl.value() + 1_vl));
6✔
3213

3214
                    for (const auto& li : *lw) {
3✔
3215
                        auto hash_res = li.get_line_hash();
3✔
3216
                        if (hash_res.isErr()) {
3✔
3217
                            auto errmsg = hash_res.unwrapErr();
×
3218

3219
                            log_error("unable to get line hash: %s",
×
3220
                                      errmsg.c_str());
3221
                            continue;
×
3222
                        }
3223

3224
                        auto hash = hash_res.unwrap();
3✔
3225
                        if (hash == md[2]) {
3✔
3226
                            return li.get_vis_line();
3✔
3227
                        }
3228
                    }
12✔
3229
                }
3✔
3230
            }
3231
        }
3232

3233
        return std::nullopt;
×
3234
    }
3235

3236
    auto& vb = this->tss_view->get_bookmarks();
×
3237
    const auto& bv = vb[&textview_curses::BM_PARTITION];
×
3238

3239
    for (const auto& vl : bv.bv_tree) {
×
3240
        auto meta_opt = this->find_bookmark_metadata(vl);
×
3241
        if (!meta_opt || meta_opt.value()->bm_name.empty()) {
×
3242
            continue;
×
3243
        }
3244

3245
        const auto& name = meta_opt.value()->bm_name;
×
3246
        if (id == text_anchors::to_anchor_string(name)) {
×
3247
            return vl;
×
3248
        }
3249
    }
3250

3251
    return std::nullopt;
×
3252
}
3253

3254
std::optional<vis_line_t>
3255
logfile_sub_source::adjacent_anchor(vis_line_t vl, text_anchors::direction dir)
2✔
3256
{
3257
    auto bmc = this->get_bookmark_metadata_context(
2✔
3258
        vl, bookmark_metadata::categories::partition);
3259
    switch (dir) {
2✔
3260
        case text_anchors::direction::prev: {
×
3261
            if (bmc.bmc_current && bmc.bmc_current.value() != vl) {
×
3262
                return bmc.bmc_current;
×
3263
            }
3264
            if (!bmc.bmc_current || bmc.bmc_current.value() == 0_vl) {
×
3265
                return 0_vl;
×
3266
            }
3267
            auto prev_bmc = this->get_bookmark_metadata_context(
×
3268
                bmc.bmc_current.value() - 1_vl,
×
3269
                bookmark_metadata::categories::partition);
3270
            if (!prev_bmc.bmc_current) {
×
3271
                return 0_vl;
×
3272
            }
3273
            return prev_bmc.bmc_current;
×
3274
        }
3275
        case text_anchors::direction::next:
2✔
3276
            return bmc.bmc_next_line;
2✔
3277
    }
3278
    return std::nullopt;
×
3279
}
3280

3281
std::optional<std::string>
3282
logfile_sub_source::anchor_for_row(vis_line_t vl)
62✔
3283
{
3284
    auto line_meta = this->get_bookmark_metadata_context(
62✔
3285
        vl, bookmark_metadata::categories::partition);
3286
    if (!line_meta.bmc_current_metadata) {
62✔
3287
        auto lw = window_at(vl);
59✔
3288

3289
        for (const auto& li : *lw) {
59✔
3290
            auto hash_res = li.get_line_hash();
59✔
3291
            if (hash_res.isErr()) {
59✔
3292
                auto errmsg = hash_res.unwrapErr();
×
3293
                log_error("unable to compute line hash: %s", errmsg.c_str());
×
3294
                break;
×
3295
            }
3296
            auto hash = hash_res.unwrap();
59✔
3297
            auto retval = fmt::format(
3298
                FMT_STRING("#msg{:016x}-{}"),
118✔
3299
                li.get_logline().get_time<std::chrono::microseconds>().count(),
59✔
3300
                hash);
×
3301

3302
            return retval;
59✔
3303
        }
236✔
3304

3305
        return std::nullopt;
×
3306
    }
59✔
3307

3308
    return text_anchors::to_anchor_string(
3✔
3309
        line_meta.bmc_current_metadata.value()->bm_name);
3✔
3310
}
3311

3312
std::unordered_set<std::string>
3313
logfile_sub_source::get_anchors()
×
3314
{
3315
    auto& vb = this->tss_view->get_bookmarks();
×
3316
    const auto& bv = vb[&textview_curses::BM_PARTITION];
×
3317
    std::unordered_set<std::string> retval;
×
3318

3319
    for (const auto& vl : bv.bv_tree) {
×
3320
        auto meta_opt = this->find_bookmark_metadata(vl);
×
3321
        if (!meta_opt || meta_opt.value()->bm_name.empty()) {
×
3322
            continue;
×
3323
        }
3324

3325
        const auto& name = meta_opt.value()->bm_name;
×
3326
        retval.emplace(text_anchors::to_anchor_string(name));
×
3327
    }
3328

3329
    return retval;
×
3330
}
×
3331

3332
bool
3333
logfile_sub_source::text_handle_mouse(
×
3334
    textview_curses& tc,
3335
    const listview_curses::display_line_content_t& mouse_line,
3336
    mouse_event& me)
3337
{
3338
    if (tc.get_overlay_selection()) {
×
3339
        auto nci = ncinput{};
×
3340
        if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 2, 4)) {
×
3341
            nci.id = ' ';
×
3342
            nci.eff_text[0] = ' ';
×
3343
            this->list_input_handle_key(tc, nci);
×
3344
        } else if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 5, 6)) {
×
3345
            nci.id = '#';
×
3346
            nci.eff_text[0] = '#';
×
3347
            this->list_input_handle_key(tc, nci);
×
3348
        }
3349
    }
3350
    return true;
×
3351
}
3352

3353
void
3354
logfile_sub_source::reload_config(error_reporter& reporter)
609✔
3355
{
3356
    static const auto& cfg
3357
        = injector::get<const logfile_sub_source_ns::config&>();
609✔
3358

3359
    switch (cfg.c_time_column) {
609✔
3360
        case logfile_sub_source_ns::time_column_feature_t::Default:
×
3361
            if (this->lss_line_context == line_context_t::none) {
×
3362
                this->lss_line_context = line_context_t::time_column;
×
3363
            }
3364
            break;
×
3365
        case logfile_sub_source_ns::time_column_feature_t::Disabled:
609✔
3366
            if (this->lss_line_context == line_context_t::time_column) {
609✔
3367
                this->lss_line_context = line_context_t::none;
×
3368
            }
3369
            break;
609✔
3370
        case logfile_sub_source_ns::time_column_feature_t::Enabled:
×
3371
            break;
×
3372
    }
3373
}
609✔
3374

3375
void
3376
logfile_sub_source::update_filter_hash_state(hasher& h) const
×
3377
{
3378
    text_sub_source::update_filter_hash_state(h);
×
3379

3380
    for (const auto& ld : this->lss_files) {
×
3381
        if (ld->get_file_ptr() == nullptr || !ld->is_visible()) {
×
3382
            h.update(0);
×
3383
        } else {
3384
            h.update(1);
×
3385
        }
3386
    }
3387
    h.update(this->lss_min_log_level);
×
3388
    h.update(this->lss_marked_only);
×
3389
}
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