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

tstack / lnav / 11927164159-1768

20 Nov 2024 05:11AM UTC coverage: 70.195% (-0.04%) from 70.233%
11927164159-1768

push

github

tstack
skip parsing really long log messages

3 of 4 new or added lines in 2 files covered. (75.0%)

25 existing lines in 5 files now uncovered.

46296 of 65953 relevant lines covered (70.2%)

467535.34 hits per line

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

67.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 <future>
32

33
#include "logfile_sub_source.hh"
34

35
#include <sqlite3.h>
36

37
#include "base/ansi_scrubber.hh"
38
#include "base/ansi_vars.hh"
39
#include "base/fs_util.hh"
40
#include "base/itertools.hh"
41
#include "base/string_util.hh"
42
#include "bookmarks.json.hh"
43
#include "command_executor.hh"
44
#include "config.h"
45
#include "field_overlay_source.hh"
46
#include "k_merge_tree.h"
47
#include "lnav_util.hh"
48
#include "log_accel.hh"
49
#include "md2attr_line.hh"
50
#include "ptimec.hh"
51
#include "shlex.hh"
52
#include "sql_util.hh"
53
#include "vtab_module.hh"
54
#include "yajlpp/yajlpp.hh"
55

56
using namespace lnav::roles::literals;
57

58
const bookmark_type_t logfile_sub_source::BM_ERRORS("error");
59
const bookmark_type_t logfile_sub_source::BM_WARNINGS("warning");
60
const bookmark_type_t logfile_sub_source::BM_FILES("file");
61

62
static int
63
pretty_sql_callback(exec_context& ec, sqlite3_stmt* stmt)
73✔
64
{
65
    if (!sqlite3_stmt_busy(stmt)) {
73✔
66
        return 0;
32✔
67
    }
68

69
    const auto ncols = sqlite3_column_count(stmt);
41✔
70

71
    for (int lpc = 0; lpc < ncols; lpc++) {
82✔
72
        if (!ec.ec_accumulator->empty()) {
41✔
73
            ec.ec_accumulator->append(", ");
10✔
74
        }
75

76
        const char* res = (const char*) sqlite3_column_text(stmt, lpc);
41✔
77
        if (res == nullptr) {
41✔
78
            continue;
×
79
        }
80

81
        ec.ec_accumulator->append(res);
41✔
82
    }
83

84
    for (int lpc = 0; lpc < ncols; lpc++) {
82✔
85
        const auto* colname = sqlite3_column_name(stmt, lpc);
41✔
86
        auto* raw_value = sqlite3_column_value(stmt, lpc);
41✔
87
        auto value_type = sqlite3_value_type(raw_value);
41✔
88
        scoped_value_t value;
41✔
89

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

112
            if (vars.find(colname) != vars.end()) {
14✔
113
                continue;
10✔
114
            }
115

116
            if (value.is<string_fragment>()) {
4✔
117
                value = value.get<string_fragment>().to_string();
4✔
118
            }
119
            vars[colname] = value;
4✔
120
        }
121
    }
41✔
122
    return 0;
41✔
123
}
124

125
static std::future<std::string>
126
pretty_pipe_callback(exec_context& ec, const std::string& cmdline, auto_fd& fd)
×
127
{
128
    auto retval = std::async(std::launch::async, [&]() {
×
129
        char buffer[1024];
130
        std::ostringstream ss;
×
131
        ssize_t rc;
132

133
        while ((rc = read(fd, buffer, sizeof(buffer))) > 0) {
×
134
            ss.write(buffer, rc);
×
135
        }
136

137
        auto retval = ss.str();
×
138

139
        if (endswith(retval, "\n")) {
×
140
            retval.resize(retval.length() - 1);
×
141
        }
142

143
        return retval;
×
144
    });
×
145

146
    return retval;
×
147
}
148

149
logfile_sub_source::
1,021✔
150
logfile_sub_source()
1,021✔
151
    : text_sub_source(1), lss_meta_grepper(*this), lss_location_history(*this)
1,021✔
152
{
153
    this->tss_supports_filtering = true;
1,021✔
154
    this->clear_line_size_cache();
1,021✔
155
    this->clear_min_max_log_times();
1,021✔
156
}
1,021✔
157

158
std::shared_ptr<logfile>
159
logfile_sub_source::find(const char* fn, content_line_t& line_base)
40✔
160
{
161
    iterator iter;
40✔
162
    std::shared_ptr<logfile> retval = nullptr;
40✔
163

164
    line_base = content_line_t(0);
40✔
165
    for (iter = this->lss_files.begin();
40✔
166
         iter != this->lss_files.end() && retval == nullptr;
82✔
167
         iter++)
42✔
168
    {
169
        auto& ld = *(*iter);
42✔
170
        auto* lf = ld.get_file_ptr();
42✔
171

172
        if (lf == nullptr) {
42✔
173
            continue;
×
174
        }
175
        if (strcmp(lf->get_filename().c_str(), fn) == 0) {
42✔
176
            retval = ld.get_file();
40✔
177
        } else {
178
            line_base += content_line_t(MAX_LINES_PER_FILE);
2✔
179
        }
180
    }
181

182
    return retval;
40✔
183
}
184

185
struct filtered_logline_cmp {
186
    filtered_logline_cmp(const logfile_sub_source& lc) : llss_controller(lc) {}
255✔
187

188
    bool operator()(const uint32_t& lhs, const uint32_t& rhs) const
189
    {
190
        content_line_t cl_lhs = (content_line_t) llss_controller.lss_index[lhs];
191
        content_line_t cl_rhs = (content_line_t) llss_controller.lss_index[rhs];
192
        logline* ll_lhs = this->llss_controller.find_line(cl_lhs);
193
        logline* ll_rhs = this->llss_controller.find_line(cl_rhs);
194

195
        if (ll_lhs == nullptr) {
196
            return true;
197
        }
198
        if (ll_rhs == nullptr) {
199
            return false;
200
        }
201
        return (*ll_lhs) < (*ll_rhs);
202
    }
203

204
    bool operator()(const uint32_t& lhs, const struct timeval& rhs) const
751✔
205
    {
206
        content_line_t cl_lhs = (content_line_t) llss_controller.lss_index[lhs];
751✔
207
        logline* ll_lhs = this->llss_controller.find_line(cl_lhs);
751✔
208

209
        if (ll_lhs == nullptr) {
751✔
210
            return true;
×
211
        }
212
        return (*ll_lhs) < rhs;
751✔
213
    }
214

215
    const logfile_sub_source& llss_controller;
216
};
217

218
std::optional<vis_line_t>
219
logfile_sub_source::find_from_time(const struct timeval& start) const
63✔
220
{
221
    auto lb = std::lower_bound(this->lss_filtered_index.begin(),
63✔
222
                               this->lss_filtered_index.end(),
223
                               start,
224
                               filtered_logline_cmp(*this));
225
    if (lb != this->lss_filtered_index.end()) {
63✔
226
        return vis_line_t(lb - this->lss_filtered_index.begin());
55✔
227
    }
228

229
    return std::nullopt;
8✔
230
}
231

232
void
233
logfile_sub_source::text_value_for_line(textview_curses& tc,
1,166✔
234
                                        int row,
235
                                        std::string& value_out,
236
                                        line_flags_t flags)
237
{
238
    if (this->lss_indexing_in_progress) {
1,166✔
239
        value_out = "";
×
240
        return;
32✔
241
    }
242

243
    content_line_t line(0);
1,166✔
244

245
    require_ge(row, 0);
1,166✔
246
    require_lt((size_t) row, this->lss_filtered_index.size());
1,166✔
247

248
    line = this->at(vis_line_t(row));
1,166✔
249

250
    if (flags & RF_RAW) {
1,166✔
251
        auto lf = this->find(line);
32✔
252
        value_out = lf->read_line(lf->begin() + line)
32✔
253
                        .map([](auto sbr) { return to_string(sbr); })
96✔
254
                        .unwrapOr({});
32✔
255
        return;
32✔
256
    }
32✔
257

258
    require_false(this->lss_in_value_for_line);
1,134✔
259

260
    this->lss_in_value_for_line = true;
1,134✔
261
    this->lss_token_flags = flags;
1,134✔
262
    this->lss_token_file_data = this->find_data(line);
1,134✔
263
    this->lss_token_file = (*this->lss_token_file_data)->get_file();
1,134✔
264
    this->lss_token_line = this->lss_token_file->begin() + line;
1,134✔
265

266
    this->lss_token_attrs.clear();
1,134✔
267
    this->lss_token_values.clear();
1,134✔
268
    this->lss_share_manager.invalidate_refs();
1,134✔
269
    if (flags & text_sub_source::RF_FULL) {
1,134✔
270
        shared_buffer_ref sbr;
48✔
271

272
        this->lss_token_file->read_full_message(this->lss_token_line, sbr);
48✔
273
        this->lss_token_value = to_string(sbr);
48✔
274
        if (sbr.get_metadata().m_has_ansi) {
48✔
275
            scrub_ansi_string(this->lss_token_value, &this->lss_token_attrs);
13✔
276
            sbr.get_metadata().m_has_ansi = false;
13✔
277
        }
278
    } else {
48✔
279
        this->lss_token_value
280
            = this->lss_token_file->read_line(this->lss_token_line)
1,086✔
281
                  .map([](auto sbr) { return to_string(sbr); })
3,258✔
282
                  .unwrapOr({});
1,086✔
283
        if (this->lss_token_line->has_ansi()) {
1,086✔
284
            scrub_ansi_string(this->lss_token_value, &this->lss_token_attrs);
14✔
285
        }
286
    }
287
    this->lss_token_shift_start = 0;
1,134✔
288
    this->lss_token_shift_size = 0;
1,134✔
289

290
    auto format = this->lss_token_file->get_format();
1,134✔
291

292
    value_out = this->lss_token_value;
1,134✔
293
    if (this->lss_flags & F_SCRUB) {
1,134✔
294
        format->scrub(value_out);
×
295
    }
296

297
    auto& sbr = this->lss_token_values.lvv_sbr;
1,134✔
298

299
    sbr.share(this->lss_share_manager,
1,134✔
300
              (char*) this->lss_token_value.c_str(),
301
              this->lss_token_value.size());
302
    format->annotate(this->lss_token_file.get(),
2,268✔
303
                     line,
304
                     this->lss_token_attrs,
1,134✔
305
                     this->lss_token_values);
1,134✔
306
    if (flags & RF_REWRITE) {
1,134✔
307
        exec_context ec(
308
            &this->lss_token_values, pretty_sql_callback, pretty_pipe_callback);
48✔
309
        std::string rewritten_line;
48✔
310
        db_label_source rewrite_label_source;
48✔
311

312
        ec.with_perms(exec_context::perm_t::READ_ONLY);
48✔
313
        ec.ec_local_vars.push(std::map<std::string, scoped_value_t>());
48✔
314
        ec.ec_top_line = vis_line_t(row);
48✔
315
        ec.ec_label_source_stack.push_back(&rewrite_label_source);
48✔
316
        add_ansi_vars(ec.ec_global_vars);
48✔
317
        add_global_vars(ec);
48✔
318
        format->rewrite(ec, sbr, this->lss_token_attrs, rewritten_line);
48✔
319
        this->lss_token_value.assign(rewritten_line);
48✔
320
        value_out = this->lss_token_value;
48✔
321
    }
48✔
322

323
    {
324
        auto lr = line_range{0, (int) this->lss_token_value.length()};
1,134✔
325
        this->lss_token_attrs.emplace_back(lr, SA_ORIGINAL_LINE.value());
1,134✔
326
    }
327

328
    if (!this->lss_token_line->is_continued() && !format->lf_formatted_lines
1,978✔
329
        && (this->lss_token_file->is_time_adjusted()
663✔
330
            || ((format->lf_timestamp_flags & ETF_ZONE_SET
648✔
331
                 || format->lf_date_time.dts_default_zone != nullptr)
426✔
332
                && format->lf_date_time.dts_zoned_to_local)
292✔
333
            || format->lf_timestamp_flags & ETF_MACHINE_ORIENTED
356✔
334
            || !(format->lf_timestamp_flags & ETF_DAY_SET)
356✔
335
            || !(format->lf_timestamp_flags & ETF_MONTH_SET))
336✔
336
        && format->lf_date_time.dts_fmt_lock != -1)
1,978✔
337
    {
338
        auto time_attr
339
            = find_string_attr(this->lss_token_attrs, &logline::L_TIMESTAMP);
271✔
340
        if (time_attr != this->lss_token_attrs.end()) {
271✔
341
            const struct line_range time_range = time_attr->sa_range;
271✔
342
            struct timeval adjusted_time;
343
            struct exttm adjusted_tm;
271✔
344
            char buffer[128];
345
            const char* fmt;
346
            ssize_t len;
347

348
            if (format->lf_timestamp_flags & ETF_MACHINE_ORIENTED
271✔
349
                || !(format->lf_timestamp_flags & ETF_DAY_SET)
249✔
350
                || !(format->lf_timestamp_flags & ETF_MONTH_SET))
520✔
351
            {
352
                adjusted_time = this->lss_token_line->get_timeval();
27✔
353
                if (format->lf_timestamp_flags
27✔
354
                    & (ETF_MICROS_SET | ETF_NANOS_SET))
27✔
355
                {
356
                    fmt = "%Y-%m-%d %H:%M:%S.%f";
23✔
357
                    struct timeval actual_tv;
358
                    struct exttm tm;
23✔
359
                    if (format->lf_date_time.scan(
46✔
360
                            this->lss_token_value.data() + time_range.lr_start,
23✔
361
                            time_range.length(),
23✔
362
                            format->get_timestamp_formats(),
363
                            &tm,
364
                            actual_tv,
365
                            false))
366
                    {
367
                        adjusted_time.tv_usec = actual_tv.tv_usec;
23✔
368
                    }
369
                } else if (format->lf_timestamp_flags & ETF_MILLIS_SET) {
4✔
370
                    fmt = "%Y-%m-%d %H:%M:%S.%L";
2✔
371
                } else {
372
                    fmt = "%Y-%m-%d %H:%M:%S";
2✔
373
                }
374
                gmtime_r(&adjusted_time.tv_sec, &adjusted_tm.et_tm);
27✔
375
                adjusted_tm.et_nsec
376
                    = std::chrono::duration_cast<std::chrono::nanoseconds>(
27✔
377
                          std::chrono::microseconds{adjusted_time.tv_usec})
×
378
                          .count();
54✔
379
                len = ftime_fmt(buffer, sizeof(buffer), fmt, adjusted_tm);
27✔
380
            } else {
381
                adjusted_time = this->lss_token_line->get_timeval();
244✔
382
                gmtime_r(&adjusted_time.tv_sec, &adjusted_tm.et_tm);
244✔
383
                adjusted_tm.et_nsec
384
                    = std::chrono::duration_cast<std::chrono::nanoseconds>(
244✔
385
                          std::chrono::microseconds{adjusted_time.tv_usec})
×
386
                          .count();
244✔
387
                adjusted_tm.et_flags = format->lf_timestamp_flags;
244✔
388
                if (format->lf_timestamp_flags & ETF_ZONE_SET
244✔
389
                    && format->lf_date_time.dts_zoned_to_local)
244✔
390
                {
391
                    adjusted_tm.et_flags &= ~ETF_Z_IS_UTC;
172✔
392
                }
393
                adjusted_tm.et_gmtoff
394
                    = format->lf_date_time.dts_local_offset_cache;
244✔
395
                len = format->lf_date_time.ftime(
244✔
396
                    buffer,
397
                    sizeof(buffer),
398
                    format->get_timestamp_formats(),
399
                    adjusted_tm);
400
            }
401

402
            value_out.replace(
542✔
403
                time_range.lr_start, time_range.length(), buffer, len);
271✔
404
            this->lss_token_shift_start = time_range.lr_start;
271✔
405
            this->lss_token_shift_size = len - time_range.length();
271✔
406
        }
407
    }
408

409
    if (this->lss_flags & F_FILENAME || this->lss_flags & F_BASENAME) {
1,134✔
410
        size_t file_offset_end;
411
        std::string name;
×
412
        if (this->lss_flags & F_FILENAME) {
×
413
            file_offset_end = this->lss_filename_width;
×
414
            name = fmt::to_string(this->lss_token_file->get_filename());
×
415
            if (file_offset_end < name.size()) {
×
416
                file_offset_end = name.size();
×
417
                this->lss_filename_width = name.size();
×
418
            }
419
        } else {
420
            file_offset_end = this->lss_basename_width;
×
421
            name = fmt::to_string(this->lss_token_file->get_unique_path());
×
422
            if (file_offset_end < name.size()) {
×
423
                file_offset_end = name.size();
×
424
                this->lss_basename_width = name.size();
×
425
            }
426
        }
427
        value_out.insert(0, 1, '|');
×
428
        value_out.insert(0, file_offset_end - name.size(), ' ');
×
429
        value_out.insert(0, name);
×
430
    } else {
×
431
        // Insert space for the file/search-hit markers.
432
        value_out.insert(0, 1, ' ');
1,134✔
433
    }
434

435
    if (this->tas_display_time_offset) {
1,134✔
436
        auto row_vl = vis_line_t(row);
210✔
437
        auto relstr = this->get_time_offset_for_line(tc, row_vl);
210✔
438
        value_out = fmt::format(FMT_STRING("{: >12}|{}"), relstr, value_out);
840✔
439
    }
210✔
440

210✔
441
    this->lss_in_value_for_line = false;
210✔
442
}
443

1,134✔
444
void
1,134✔
445
logfile_sub_source::text_attrs_for_line(textview_curses& lv,
446
                                        int row,
447
                                        string_attrs_t& value_out)
1,134✔
448
{
449
    if (this->lss_indexing_in_progress) {
450
        return;
451
    }
1,134✔
452

×
453
    view_colors& vc = view_colors::singleton();
454
    logline* next_line = nullptr;
455
    struct line_range lr;
1,134✔
456
    int time_offset_end = 0;
1,134✔
457
    text_attrs attrs;
1,134✔
458

1,134✔
459
    value_out = this->lss_token_attrs;
1,134✔
460

461
    if ((row + 1) < (int) this->lss_filtered_index.size()) {
1,134✔
462
        next_line = this->find_line(this->at(vis_line_t(row + 1)));
463
    }
1,134✔
464

987✔
465
    if (next_line != nullptr
466
        && (day_num(next_line->get_time())
467
            > day_num(this->lss_token_line->get_time())))
1,134✔
468
    {
2,121✔
469
        attrs.ta_attrs |= A_UNDERLINE;
987✔
470
    }
471

14✔
472
    const auto& line_values = this->lss_token_values;
473

474
    lr.lr_start = 0;
1,134✔
475
    lr.lr_end = -1;
476
    value_out.emplace_back(
1,134✔
477
        lr, SA_LEVEL.value(this->lss_token_line->get_msg_level()));
1,134✔
478

1,134✔
479
    lr.lr_start = time_offset_end;
2,268✔
480
    lr.lr_end = -1;
481

1,134✔
482
    if (!attrs.empty()) {
1,134✔
483
        value_out.emplace_back(lr, VC_STYLE.value(attrs));
484
    }
1,134✔
485

14✔
486
    if (this->lss_token_line->get_msg_level() == log_level_t::LEVEL_INVALID) {
487
        for (auto& token_attr : this->lss_token_attrs) {
488
            if (token_attr.sa_type != &SA_INVALID) {
1,134✔
489
                continue;
37✔
490
            }
20✔
491

17✔
492
            value_out.emplace_back(token_attr.sa_range,
493
                                   VC_ROLE.value(role_t::VCR_INVALID_MSG));
494
        }
3✔
495
    }
6✔
496

497
    for (const auto& line_value : line_values.lvv_values) {
498
        if ((!(this->lss_token_flags & RF_FULL)
499
             && line_value.lv_sub_offset
9,351✔
500
                 != this->lss_token_line->get_sub_offset())
17,495✔
501
            || !line_value.lv_origin.is_valid())
15,990✔
502
        {
7,995✔
503
            continue;
16,212✔
504
        }
505

1,061✔
506
        if (line_value.lv_meta.is_hidden()) {
507
            value_out.emplace_back(line_value.lv_origin, SA_HIDDEN.value());
508
        }
7,156✔
509

98✔
510
        if (!line_value.lv_meta.lvm_identifier
511
            || !line_value.lv_origin.is_valid())
512
        {
18,297✔
513
            continue;
7,156✔
514
        }
515

3,985✔
516
        value_out.emplace_back(line_value.lv_origin,
517
                               VC_ROLE.value(role_t::VCR_IDENTIFIER));
518
    }
3,171✔
519

6,342✔
520
    if (this->lss_token_shift_size) {
521
        shift_string_attrs(value_out,
522
                           this->lss_token_shift_start + 1,
1,134✔
523
                           this->lss_token_shift_size);
35✔
524
    }
35✔
525

526
    shift_string_attrs(value_out, 0, 1);
527

528
    lr.lr_start = 0;
1,134✔
529
    lr.lr_end = 1;
530
    {
1,134✔
531
        auto& bm = lv.get_bookmarks();
1,134✔
532
        const auto& bv = bm[&BM_FILES];
533
        bool is_first_for_file
1,134✔
534
            = binary_search(bv.begin(), bv.end(), vis_line_t(row));
1,134✔
535
        bool is_last_for_file
536
            = binary_search(bv.begin(), bv.end(), vis_line_t(row + 1));
1,134✔
537
        chtype graph = ACS_VLINE;
538
        if (is_first_for_file) {
1,134✔
539
            if (is_last_for_file) {
1,134✔
540
                graph = ACS_HLINE;
1,134✔
541
            } else {
153✔
542
                graph = ACS_ULCORNER;
4✔
543
            }
544
        } else if (is_last_for_file) {
149✔
545
            graph = ACS_LLCORNER;
546
        }
981✔
547
        value_out.emplace_back(lr, VC_GRAPHIC.value(graph));
11✔
548

549
        if (!(this->lss_token_flags & RF_FULL)) {
1,134✔
550
            auto& bv_search = bm[&textview_curses::BM_SEARCH];
551

1,134✔
552
            if (binary_search(std::begin(bv_search),
1,086✔
553
                              std::end(bv_search),
554
                              vis_line_t(row)))
1,086✔
555
            {
556
                lr.lr_start = 0;
2,172✔
557
                lr.lr_end = 1;
558
                value_out.emplace_back(lr,
6✔
559
                                       VC_STYLE.value(text_attrs{A_REVERSE}));
6✔
560
            }
6✔
561
        }
12✔
562
    }
563

564
    value_out.emplace_back(lr,
565
                           VC_STYLE.value(vc.attrs_for_ident(
566
                               this->lss_token_file->get_filename())));
1,134✔
567

2,268✔
568
    if (this->lss_flags & F_FILENAME || this->lss_flags & F_BASENAME) {
1,134✔
569
        size_t file_offset_end = (this->lss_flags & F_FILENAME)
570
            ? this->lss_filename_width
1,134✔
571
            : this->lss_basename_width;
×
572

×
573
        shift_string_attrs(value_out, 0, file_offset_end);
574

575
        lr.lr_start = 0;
×
576
        lr.lr_end = file_offset_end + 1;
577
        value_out.emplace_back(lr,
×
578
                               VC_STYLE.value(vc.attrs_for_ident(
×
579
                                   this->lss_token_file->get_filename())));
×
580
    }
×
581

×
582
    if (this->tas_display_time_offset) {
583
        time_offset_end = 13;
584
        lr.lr_start = 0;
1,134✔
585
        lr.lr_end = time_offset_end;
210✔
586

210✔
587
        shift_string_attrs(value_out, 0, time_offset_end);
210✔
588

589
        value_out.emplace_back(lr, VC_ROLE.value(role_t::VCR_OFFSET_TIME));
210✔
590
        value_out.emplace_back(line_range(12, 13), VC_GRAPHIC.value(ACS_VLINE));
591

210✔
592
        role_t bar_role = role_t::VCR_NONE;
210✔
593

594
        switch (this->get_line_accel_direction(vis_line_t(row))) {
210✔
595
            case log_accel::A_STEADY:
596
                break;
210✔
597
            case log_accel::A_DECEL:
126✔
598
                bar_role = role_t::VCR_DIFF_DELETE;
126✔
599
                break;
42✔
600
            case log_accel::A_ACCEL:
42✔
601
                bar_role = role_t::VCR_DIFF_ADD;
42✔
602
                break;
42✔
603
        }
42✔
604
        if (bar_role != role_t::VCR_NONE) {
42✔
605
            value_out.emplace_back(line_range(12, 13), VC_ROLE.value(bar_role));
606
        }
210✔
607
    }
84✔
608

609
    lr.lr_start = 0;
610
    lr.lr_end = -1;
611
    value_out.emplace_back(lr, logline::L_FILE.value(this->lss_token_file));
1,134✔
612
    value_out.emplace_back(
1,134✔
613
        lr, SA_FORMAT.value(this->lss_token_file->get_format()->get_name()));
1,134✔
614

1,134✔
615
    {
2,268✔
616
        auto line_meta_context = this->get_bookmark_metadata_context(
617
            vis_line_t(row + 1), bookmark_metadata::categories::partition);
618
        if (line_meta_context.bmc_current_metadata) {
1,134✔
619
            lr.lr_start = 0;
620
            lr.lr_end = -1;
1,134✔
621
            value_out.emplace_back(
5✔
622
                lr,
5✔
623
                logline::L_PARTITION.value(
5✔
624
                    line_meta_context.bmc_current_metadata.value()));
625
        }
5✔
626

5✔
627
        auto line_meta_opt = this->find_bookmark_metadata(vis_line_t(row));
628

629
        if (line_meta_opt) {
1,134✔
630
            lr.lr_start = 0;
631
            lr.lr_end = -1;
1,134✔
632
            value_out.emplace_back(
28✔
633
                lr, logline::L_META.value(line_meta_opt.value()));
28✔
634
        }
28✔
635
    }
56✔
636

637
    if (this->lss_token_file->is_time_adjusted()) {
638
        struct line_range time_range
639
            = find_string_attr_range(value_out, &logline::L_TIMESTAMP);
1,134✔
640

641
        if (time_range.lr_end != -1) {
17✔
642
            value_out.emplace_back(time_range,
643
                                   VC_ROLE.value(role_t::VCR_ADJUSTED_TIME));
17✔
644
        }
15✔
645
    }
30✔
646

647
    if (this->lss_token_line->is_time_skewed()) {
648
        struct line_range time_range
649
            = find_string_attr_range(value_out, &logline::L_TIMESTAMP);
1,134✔
650

651
        if (time_range.lr_end != -1) {
7✔
652
            value_out.emplace_back(time_range,
653
                                   VC_ROLE.value(role_t::VCR_SKEWED_TIME));
7✔
654
        }
7✔
655
    }
14✔
656

657
    if (!this->lss_token_line->is_continued()) {
658
        if (this->lss_preview_filter_stmt != nullptr) {
659
            int color;
1,134✔
660
            auto eval_res
844✔
661
                = this->eval_sql_filter(this->lss_preview_filter_stmt.in(),
662
                                        this->lss_token_file_data,
663
                                        this->lss_token_line);
664
            if (eval_res.isErr()) {
665
                color = COLOR_YELLOW;
×
666
                value_out.emplace_back(
×
667
                    line_range{0, -1},
×
668
                    SA_ERROR.value(
×
669
                        eval_res.unwrapErr().to_attr_line().get_string()));
×
670
            } else {
×
671
                auto matched = eval_res.unwrap();
×
672

673
                if (matched) {
×
674
                    color = COLOR_GREEN;
675
                } else {
×
676
                    color = COLOR_RED;
×
677
                    value_out.emplace_back(line_range{0, 1},
678
                                           VC_STYLE.value(text_attrs{A_BLINK}));
×
679
                }
×
680
            }
×
681
            value_out.emplace_back(line_range{0, 1},
682
                                   VC_BACKGROUND.value(color));
683
        }
×
684

×
685
        auto sql_filter_opt = this->get_sql_filter();
686
        if (sql_filter_opt) {
687
            auto* sf = (sql_filter*) sql_filter_opt.value().get();
844✔
688
            auto eval_res = this->eval_sql_filter(sf->sf_filter_stmt.in(),
844✔
689
                                                  this->lss_token_file_data,
6✔
690
                                                  this->lss_token_line);
691
            if (eval_res.isErr()) {
692
                auto msg = fmt::format(
6✔
693
                    FMT_STRING(
6✔
694
                        "filter expression evaluation failed with -- {}"),
695
                    eval_res.unwrapErr().to_attr_line().get_string());
×
696
                auto color = COLOR_YELLOW;
×
697
                value_out.emplace_back(line_range{0, -1}, SA_ERROR.value(msg));
×
698
                value_out.emplace_back(line_range{0, 1},
699
                                       VC_BACKGROUND.value(color));
×
700
            }
×
701
        }
×
702
    }
×
703
}
×
704

705
struct logline_cmp {
6✔
706
    logline_cmp(logfile_sub_source& lc) : llss_controller(lc) {}
844✔
707

708
    bool operator()(const content_line_t& lhs, const content_line_t& rhs) const
709
    {
710
        logline* ll_lhs = this->llss_controller.find_line(lhs);
936✔
711
        logline* ll_rhs = this->llss_controller.find_line(rhs);
712

37,303✔
713
        return (*ll_lhs) < (*ll_rhs);
714
    }
37,303✔
715

37,303✔
716
    bool operator()(const uint32_t& lhs, const uint32_t& rhs) const
717
    {
37,303✔
718
        content_line_t cl_lhs = (content_line_t) llss_controller.lss_index[lhs];
719
        content_line_t cl_rhs = (content_line_t) llss_controller.lss_index[rhs];
720
        logline* ll_lhs = this->llss_controller.find_line(cl_lhs);
721
        logline* ll_rhs = this->llss_controller.find_line(cl_rhs);
722

723
        return (*ll_lhs) < (*ll_rhs);
724
    }
725
#if 0
726
        bool operator()(const indexed_content &lhs, const indexed_content &rhs)
727
        {
728
            logline *ll_lhs = this->llss_controller.find_line(lhs.ic_value);
729
            logline *ll_rhs = this->llss_controller.find_line(rhs.ic_value);
730

731
            return (*ll_lhs) < (*ll_rhs);
732
        }
733
#endif
734

735
    bool operator()(const content_line_t& lhs, const time_t& rhs) const
736
    {
737
        logline* ll_lhs = this->llss_controller.find_line(lhs);
738

739
        return *ll_lhs < rhs;
740
    }
741

742
    bool operator()(const content_line_t& lhs, const struct timeval& rhs) const
743
    {
744
        logline* ll_lhs = this->llss_controller.find_line(lhs);
745

746
        return *ll_lhs < rhs;
×
747
    }
748

×
749
    logfile_sub_source& llss_controller;
750
};
×
751

752
logfile_sub_source::rebuild_result
753
logfile_sub_source::rebuild_index(std::optional<ui_clock::time_point> deadline)
754
{
755
    if (this->tss_view == nullptr) {
756
        return rebuild_result::rr_no_change;
757
    }
3,696✔
758

759
    this->lss_indexing_in_progress = true;
3,696✔
760
    auto fin = finally([this]() { this->lss_indexing_in_progress = false; });
118✔
761

762
    iterator iter;
763
    size_t total_lines = 0;
3,578✔
764
    bool full_sort = false;
3,578✔
765
    int file_count = 0;
766
    bool force = this->lss_force_rebuild;
3,578✔
767
    auto retval = rebuild_result::rr_no_change;
3,578✔
768
    std::optional<struct timeval> lowest_tv = std::nullopt;
3,578✔
769
    vis_line_t search_start = 0_vl;
3,578✔
770

3,578✔
771
    this->lss_force_rebuild = false;
3,578✔
772
    if (force) {
3,578✔
773
        log_debug("forced to full rebuild");
3,578✔
774
        retval = rebuild_result::rr_full_rebuild;
775
        full_sort = true;
3,578✔
776
    }
3,578✔
777

782✔
778
    std::vector<size_t> file_order(this->lss_files.size());
782✔
779

782✔
780
    for (size_t lpc = 0; lpc < file_order.size(); lpc++) {
781
        file_order[lpc] = lpc;
782
    }
3,578✔
783
    if (!this->lss_index.empty()) {
784
        std::stable_sort(file_order.begin(),
6,525✔
785
                         file_order.end(),
2,947✔
786
                         [this](const auto& left, const auto& right) {
787
                             const auto& left_ld = this->lss_files[left];
3,578✔
788
                             const auto& right_ld = this->lss_files[right];
2,270✔
789

790
                             if (left_ld->get_file_ptr() == nullptr) {
482✔
791
                                 return true;
241✔
792
                             }
241✔
793
                             if (right_ld->get_file_ptr() == nullptr) {
794
                                 return false;
241✔
795
                             }
44✔
796

797
                             return left_ld->get_file_ptr()->back()
197✔
798
                                 < right_ld->get_file_ptr()->back();
4✔
799
                         });
800
    }
801

193✔
802
    bool time_left = true;
386✔
803
    for (const auto file_index : file_order) {
804
        auto& ld = *(this->lss_files[file_index]);
805
        auto* lf = ld.get_file_ptr();
806

3,578✔
807
        if (lf == nullptr) {
6,525✔
808
            if (ld.ld_lines_indexed > 0) {
2,947✔
809
                log_debug("%d: file closed, doing full rebuild",
2,947✔
810
                          ld.ld_file_index);
811
                force = true;
2,947✔
812
                retval = rebuild_result::rr_full_rebuild;
455✔
813
                full_sort = true;
431✔
814
            }
815
        } else {
431✔
816
            if (time_left && deadline && ui_clock::now() > deadline.value()) {
431✔
817
                log_debug("no time left, skipping %s",
431✔
818
                          lf->get_filename().c_str());
819
                time_left = false;
820
            }
2,492✔
821

×
822
            if (!this->tss_view->is_paused() && time_left) {
823
                switch (lf->rebuild_index(deadline)) {
×
824
                    case logfile::rebuild_result_t::NO_NEW_LINES:
825
                        // No changes
826
                        break;
2,492✔
827
                    case logfile::rebuild_result_t::NEW_LINES:
2,492✔
828
                        if (retval == rebuild_result::rr_no_change) {
2,087✔
829
                            retval = rebuild_result::rr_appended_lines;
830
                        }
2,087✔
831
                        log_debug("new lines for %s:%d",
1✔
832
                                  lf->get_filename().c_str(),
1✔
833
                                  lf->size());
×
834
                        if (!this->lss_index.empty()
835
                            && lf->size() > ld.ld_lines_indexed)
1✔
836
                        {
837
                            logline& new_file_line = (*lf)[ld.ld_lines_indexed];
838
                            content_line_t cl = this->lss_index.back();
1✔
839
                            logline* last_indexed_line = this->find_line(cl);
1✔
840

841
                            // If there are new lines that are older than what
×
842
                            // we have in the index, we need to resort.
×
843
                            if (last_indexed_line == nullptr
×
844
                                || new_file_line
845
                                    < last_indexed_line->get_timeval())
846
                            {
847
                                log_debug(
×
848
                                    "%s:%ld: found older lines, full "
×
849
                                    "rebuild: %p  %lld < %lld",
×
850
                                    lf->get_filename().c_str(),
851
                                    ld.ld_lines_indexed,
×
852
                                    last_indexed_line,
853
                                    new_file_line.get_time_in_millis(),
854
                                    last_indexed_line == nullptr
855
                                        ? (uint64_t) -1
856
                                        : last_indexed_line
857
                                              ->get_time_in_millis());
858
                                if (retval
859
                                    <= rebuild_result::rr_partial_rebuild)
860
                                {
861
                                    retval = rebuild_result::rr_partial_rebuild;
862
                                    if (!lowest_tv) {
×
863
                                        lowest_tv = new_file_line.get_timeval();
864
                                    } else if (new_file_line.get_timeval()
865
                                               < lowest_tv.value())
×
866
                                    {
×
867
                                        lowest_tv = new_file_line.get_timeval();
×
868
                                    }
×
869
                                } else {
×
870
                                    log_debug(
871
                                        "already doing full rebuild, doing "
×
872
                                        "full_sort as well");
873
                                    full_sort = true;
874
                                }
×
875
                            }
876
                        }
877
                        break;
×
878
                    case logfile::rebuild_result_t::INVALID:
879
                    case logfile::rebuild_result_t::NEW_ORDER:
880
                        log_debug("%s: log file has a new order, full rebuild",
881
                                  lf->get_filename().c_str());
1✔
882
                        retval = rebuild_result::rr_full_rebuild;
404✔
883
                        force = true;
884
                        full_sort = true;
404✔
885
                        break;
886
                }
404✔
887
            }
404✔
888
            file_count += 1;
404✔
889
            total_lines += lf->size();
404✔
890
        }
891
    }
892

2,492✔
893
    if (this->lss_index.empty() && !time_left) {
2,492✔
894
        return rebuild_result::rr_appended_lines;
895
    }
896

897
    if (this->lss_index.reserve(total_lines)) {
3,578✔
898
        // The index array was reallocated, just do a full sort/rebuild since
×
899
        // it's been cleared out.
900
        log_debug("expanding index capacity %zu", this->lss_index.ba_capacity);
901
        force = true;
3,578✔
902
        retval = rebuild_result::rr_full_rebuild;
903
        full_sort = true;
904
    }
519✔
905

519✔
906
    auto& vis_bm = this->tss_view->get_bookmarks();
519✔
907

519✔
908
    if (force) {
909
        for (iter = this->lss_files.begin(); iter != this->lss_files.end();
910
             iter++)
3,578✔
911
        {
912
            (*iter)->ld_lines_indexed = 0;
3,578✔
913
        }
1,822✔
914

886✔
915
        this->lss_index.clear();
916
        this->lss_filtered_index.clear();
886✔
917
        this->lss_longest_line = 0;
918
        this->lss_basename_width = 0;
919
        this->lss_filename_width = 0;
936✔
920
        vis_bm[&textview_curses::BM_USER_EXPR].clear();
936✔
921
    } else if (retval == rebuild_result::rr_partial_rebuild) {
936✔
922
        size_t remaining = 0;
936✔
923

936✔
924
        log_debug("partial rebuild with lowest time: %ld",
936✔
925
                  lowest_tv.value().tv_sec);
2,642✔
926
        for (iter = this->lss_files.begin(); iter != this->lss_files.end();
×
927
             iter++)
928
        {
×
929
            logfile_data& ld = *(*iter);
930
            auto* lf = ld.get_file_ptr();
×
931

×
932
            if (lf == nullptr) {
933
                continue;
×
934
            }
×
935

936
            auto line_iter = lf->find_from_time(lowest_tv.value());
×
937

×
938
            if (line_iter) {
939
                log_debug("%s: lowest line time %ld; line %ld; size %ld",
940
                          lf->get_filename().c_str(),
×
941
                          line_iter.value()->get_timeval().tv_sec,
942
                          std::distance(lf->cbegin(), line_iter.value()),
×
943
                          lf->size());
×
944
            }
945
            ld.ld_lines_indexed
946
                = std::distance(lf->cbegin(), line_iter.value_or(lf->cend()));
947
            remaining += lf->size() - ld.ld_lines_indexed;
948
        }
949

950
        auto row_iter = std::lower_bound(this->lss_index.begin(),
×
951
                                         this->lss_index.end(),
×
952
                                         *lowest_tv,
953
                                         logline_cmp(*this));
954
        this->lss_index.shrink_to(
×
955
            std::distance(this->lss_index.begin(), row_iter));
956
        log_debug("new index size %ld/%ld; remain %ld",
×
957
                  this->lss_index.ba_size,
958
                  this->lss_index.ba_capacity,
×
959
                  remaining);
×
960
        auto filt_row_iter = lower_bound(this->lss_filtered_index.begin(),
×
961
                                         this->lss_filtered_index.end(),
962
                                         *lowest_tv,
963
                                         filtered_logline_cmp(*this));
964
        this->lss_filtered_index.resize(
×
965
            std::distance(this->lss_filtered_index.begin(), filt_row_iter));
966
        search_start = vis_line_t(this->lss_filtered_index.size());
×
967

968
        auto bm_range = vis_bm[&textview_curses::BM_USER_EXPR].equal_range(
×
969
            search_start, -1_vl);
×
970
        auto bm_new_size = std::distance(
×
971
            vis_bm[&textview_curses::BM_USER_EXPR].begin(), bm_range.first);
972
        vis_bm[&textview_curses::BM_USER_EXPR].resize(bm_new_size);
×
973

×
974
        if (this->lss_index_delegate) {
×
975
            this->lss_index_delegate->index_start(*this);
×
976
            for (const auto row_in_full_index : this->lss_filtered_index) {
×
977
                auto cl = this->lss_index[row_in_full_index];
978
                uint64_t line_number;
×
979
                auto ld_iter = this->find_data(cl, line_number);
×
980
                auto& ld = *ld_iter;
×
981
                auto line_iter = ld->get_file_ptr()->begin() + line_number;
×
982

983
                this->lss_index_delegate->index_line(
×
984
                    *this, ld->get_file_ptr(), line_iter);
×
985
            }
×
986
        }
987
    }
×
988

989
    if (retval != rebuild_result::rr_no_change || force) {
990
        size_t index_size = 0, start_size = this->lss_index.size();
991
        logline_cmp line_cmper(*this);
992

993
        for (auto& ld : this->lss_files) {
3,578✔
994
            auto* lf = ld->get_file_ptr();
936✔
995

936✔
996
            if (lf == nullptr) {
997
                continue;
1,822✔
998
            }
886✔
999
            this->lss_longest_line = std::max(
1000
                this->lss_longest_line, lf->get_longest_line_length() + 1);
886✔
1001
            this->lss_basename_width
432✔
1002
                = std::max(this->lss_basename_width,
1003
                           lf->get_unique_path().native().size());
908✔
1004
            this->lss_filename_width = std::max(
454✔
1005
                this->lss_filename_width, lf->get_filename().native().size());
1006
        }
908✔
1007

454✔
1008
        if (full_sort) {
908✔
1009
            for (auto& ld : this->lss_files) {
454✔
1010
                auto* lf = ld->get_file_ptr();
1011

1012
                if (lf == nullptr) {
936✔
1013
                    continue;
1,822✔
1014
                }
886✔
1015

1016
                for (size_t line_index = 0; line_index < lf->size();
886✔
1017
                     line_index++)
432✔
1018
                {
1019
                    const auto lf_iter
1020
                        = ld->get_file_ptr()->begin() + line_index;
7,197✔
1021
                    if (lf_iter->is_ignored()) {
1022
                        continue;
1023
                    }
1024

6,743✔
1025
                    content_line_t con_line(
6,743✔
1026
                        ld->ld_file_index * MAX_LINES_PER_FILE + line_index);
224✔
1027

1028
                    if (lf_iter->is_meta_marked()) {
1029
                        auto start_iter = lf_iter;
1030
                        while (start_iter->is_continued()) {
6,519✔
1031
                            --start_iter;
1032
                        }
6,519✔
1033
                        int start_index
12✔
1034
                            = start_iter - ld->get_file_ptr()->begin();
12✔
1035
                        content_line_t start_con_line(ld->ld_file_index
×
1036
                                                          * MAX_LINES_PER_FILE
1037
                                                      + start_index);
1038

12✔
1039
                        auto& line_meta
12✔
1040
                            = ld->get_file_ptr()
12✔
1041
                                  ->get_bookmark_metadata()[start_index];
12✔
1042
                        if (line_meta.has(bookmark_metadata::categories::notes))
1043
                        {
1044
                            this->lss_user_marks[&textview_curses::BM_META]
1045
                                .insert_once(start_con_line);
12✔
1046
                        }
12✔
1047
                        if (line_meta.has(
1048
                                bookmark_metadata::categories::partition))
8✔
1049
                        {
4✔
1050
                            this->lss_user_marks[&textview_curses::BM_PARTITION]
1051
                                .insert_once(start_con_line);
12✔
1052
                        }
1053
                    }
1054
                    this->lss_index.push_back(con_line);
16✔
1055
                }
8✔
1056
            }
1057

1058
            // XXX get rid of this full sort on the initial run, it's not
6,519✔
1059
            // needed unless the file is not in time-order
1060
            if (this->lss_sorting_observer) {
1061
                this->lss_sorting_observer(*this, 0, this->lss_index.size());
1062
            }
1063
            std::sort(
1064
                this->lss_index.begin(), this->lss_index.end(), line_cmper);
936✔
1065
            if (this->lss_sorting_observer) {
7✔
1066
                this->lss_sorting_observer(
1067
                    *this, this->lss_index.size(), this->lss_index.size());
936✔
1068
            }
1069
        } else {
936✔
1070
            kmerge_tree_c<logline, logfile_data, logfile::iterator> merge(
14✔
1071
                file_count);
7✔
1072

1073
            for (iter = this->lss_files.begin(); iter != this->lss_files.end();
1074
                 iter++)
1075
            {
×
1076
                auto* ld = iter->get();
1077
                auto* lf = ld->get_file_ptr();
×
1078
                if (lf == nullptr) {
×
1079
                    continue;
1080
                }
×
1081

×
1082
                merge.add(ld, lf->begin() + ld->ld_lines_indexed, lf->end());
×
1083
                index_size += lf->size();
×
1084
            }
1085

1086
            file_off_t index_off = 0;
×
1087
            merge.execute();
×
1088
            if (this->lss_sorting_observer) {
1089
                this->lss_sorting_observer(*this, index_off, index_size);
1090
            }
×
1091
            for (;;) {
×
1092
                logfile::iterator lf_iter;
×
1093
                logfile_data* ld;
×
1094

1095
                if (!merge.get_top(ld, lf_iter)) {
1096
                    break;
×
1097
                }
1098

1099
                if (!lf_iter->is_ignored()) {
×
1100
                    int file_index = ld->ld_file_index;
×
1101
                    int line_index = lf_iter - ld->get_file_ptr()->begin();
1102

1103
                    content_line_t con_line(file_index * MAX_LINES_PER_FILE
×
1104
                                            + line_index);
×
1105

×
1106
                    if (lf_iter->is_meta_marked()) {
1107
                        auto start_iter = lf_iter;
×
1108
                        while (start_iter->is_continued()) {
×
1109
                            --start_iter;
1110
                        }
×
1111
                        int start_index
×
1112
                            = start_iter - ld->get_file_ptr()->begin();
×
1113
                        content_line_t start_con_line(
×
1114
                            file_index * MAX_LINES_PER_FILE + start_index);
1115

1116
                        auto& line_meta
×
1117
                            = ld->get_file_ptr()
1118
                                  ->get_bookmark_metadata()[start_index];
×
1119
                        if (line_meta.has(bookmark_metadata::categories::notes))
1120
                        {
1121
                            this->lss_user_marks[&textview_curses::BM_META]
1122
                                .insert_once(start_con_line);
×
1123
                        }
×
1124
                        if (line_meta.has(
1125
                                bookmark_metadata::categories::partition))
×
1126
                        {
×
1127
                            this->lss_user_marks[&textview_curses::BM_PARTITION]
1128
                                .insert_once(start_con_line);
×
1129
                        }
1130
                    }
1131
                    this->lss_index.push_back(con_line);
×
1132
                }
×
1133

1134
                merge.next();
1135
                index_off += 1;
×
1136
                if (index_off % 10000 == 0 && this->lss_sorting_observer) {
1137
                    this->lss_sorting_observer(*this, index_off, index_size);
1138
                }
×
1139
            }
×
1140
            if (this->lss_sorting_observer) {
×
1141
                this->lss_sorting_observer(*this, index_size, index_size);
×
1142
            }
1143
        }
1144

×
1145
        for (iter = this->lss_files.begin(); iter != this->lss_files.end();
×
1146
             iter++)
1147
        {
1148
            auto* lf = (*iter)->get_file_ptr();
1149

1,822✔
1150
            if (lf == nullptr) {
886✔
1151
                continue;
1152
            }
886✔
1153

1154
            (*iter)->ld_lines_indexed = lf->size();
886✔
1155
        }
432✔
1156

1157
        this->lss_filtered_index.reserve(this->lss_index.size());
1158

454✔
1159
        uint32_t filter_in_mask, filter_out_mask;
1160
        this->get_filters().get_enabled_mask(filter_in_mask, filter_out_mask);
1161

936✔
1162
        if (start_size == 0 && this->lss_index_delegate != nullptr) {
1163
            this->lss_index_delegate->index_start(*this);
1164
        }
936✔
1165

1166
        for (size_t index_index = start_size;
936✔
1167
             index_index < this->lss_index.size();
936✔
1168
             index_index++)
1169
        {
1170
            const auto cl = (content_line_t) this->lss_index[index_index];
7,455✔
1171
            uint64_t line_number;
7,455✔
1172
            auto ld = this->find_data(cl, line_number);
1173

1174
            if (!(*ld)->is_visible()) {
6,519✔
1175
                continue;
1176
            }
6,519✔
1177

1178
            auto* lf = (*ld)->get_file_ptr();
6,519✔
1179
            auto line_iter = lf->begin() + line_number;
×
1180

1181
            if (line_iter->is_ignored()) {
1182
                continue;
6,519✔
1183
            }
6,519✔
1184

1185
            if (!this->tss_apply_filters
6,519✔
1186
                || (!(*ld)->ld_filter_state.excluded(
×
1187
                        filter_in_mask, filter_out_mask, line_number)
1188
                    && this->check_extra_filters(ld, line_iter)))
1189
            {
13,038✔
1190
                auto eval_res = this->eval_sql_filter(
13,032✔
1191
                    this->lss_marker_stmt.in(), ld, line_iter);
1192
                if (eval_res.isErr()) {
6,513✔
1193
                    line_iter->set_expr_mark(false);
1194
                } else {
1195
                    auto matched = eval_res.unwrap();
6,513✔
1196

6,513✔
1197
                    if (matched) {
×
1198
                        line_iter->set_expr_mark(true);
1199
                        vis_bm[&textview_curses::BM_USER_EXPR].insert_once(
6,513✔
1200
                            vis_line_t(this->lss_filtered_index.size()));
1201
                    } else {
6,513✔
1202
                        line_iter->set_expr_mark(false);
×
1203
                    }
×
1204
                }
×
1205
                this->lss_filtered_index.push_back(index_index);
1206
                if (this->lss_index_delegate != nullptr) {
6,513✔
1207
                    this->lss_index_delegate->index_line(
1208
                        *this, lf, lf->begin() + line_number);
1209
                }
6,513✔
1210
            }
6,513✔
1211
        }
6,513✔
1212

13,026✔
1213
        this->lss_indexing_in_progress = false;
1214

6,513✔
1215
        if (this->lss_index_delegate != nullptr) {
1216
            this->lss_index_delegate->index_complete(*this);
1217
        }
936✔
1218
    }
1219

936✔
1220
    switch (retval) {
936✔
1221
        case rebuild_result::rr_no_change:
1222
            break;
1223
        case rebuild_result::rr_full_rebuild:
1224
            log_debug("redoing search");
3,578✔
1225
            this->lss_index_generation += 1;
2,642✔
1226
            this->tss_view->reload_data();
2,642✔
1227
            this->tss_view->redo_search();
936✔
1228
            break;
936✔
1229
        case rebuild_result::rr_partial_rebuild:
936✔
1230
            log_debug("redoing search from: %d", (int) search_start);
936✔
1231
            this->lss_index_generation += 1;
936✔
1232
            this->tss_view->reload_data();
936✔
1233
            this->tss_view->search_new_data(search_start);
×
1234
            break;
×
1235
        case rebuild_result::rr_appended_lines:
×
1236
            this->tss_view->reload_data();
×
1237
            this->tss_view->search_new_data();
×
1238
            break;
×
1239
    }
×
1240

×
1241
    return retval;
×
1242
}
×
1243

1244
void
1245
logfile_sub_source::text_update_marks(vis_bookmarks& bm)
3,578✔
1246
{
3,578✔
1247
    logfile* last_file = nullptr;
1248
    vis_line_t vl;
1249

2,236✔
1250
    bm[&BM_WARNINGS].clear();
1251
    bm[&BM_ERRORS].clear();
2,236✔
1252
    bm[&BM_FILES].clear();
2,236✔
1253

1254
    for (auto& lss_user_mark : this->lss_user_marks) {
2,236✔
1255
        bm[lss_user_mark.first].clear();
2,236✔
1256
    }
2,236✔
1257

1258
    for (; vl < (int) this->lss_filtered_index.size(); ++vl) {
2,541✔
1259
        const content_line_t orig_cl = this->at(vl);
305✔
1260
        content_line_t cl = orig_cl;
1261
        auto lf = this->find_file_ptr(cl);
1262

10,876✔
1263
        for (auto& lss_user_mark : this->lss_user_marks) {
8,640✔
1264
            if (binary_search(lss_user_mark.second.begin(),
8,640✔
1265
                              lss_user_mark.second.end(),
8,640✔
1266
                              orig_cl))
1267
            {
10,541✔
1268
                bm[lss_user_mark.first].insert_once(vl);
1,901✔
1269

1270
                if (lss_user_mark.first == &textview_curses::BM_USER) {
1271
                    auto ll = lf->begin() + cl;
1272

123✔
1273
                    ll->set_mark(true);
1274
                }
123✔
1275
            }
34✔
1276
        }
1277

34✔
1278
        if (lf != last_file) {
1279
            bm[&BM_FILES].insert_once(vl);
1280
        }
1281

1282
        auto line_iter = lf->begin() + cl;
8,640✔
1283
        if (line_iter->is_message()) {
739✔
1284
            switch (line_iter->get_msg_level()) {
1285
                case LEVEL_WARNING:
1286
                    bm[&BM_WARNINGS].insert_once(vl);
8,640✔
1287
                    break;
8,640✔
1288

7,660✔
1289
                case LEVEL_FATAL:
46✔
1290
                case LEVEL_ERROR:
46✔
1291
                case LEVEL_CRITICAL:
46✔
1292
                    bm[&BM_ERRORS].insert_once(vl);
1293
                    break;
781✔
1294

1295
                default:
1296
                    break;
781✔
1297
            }
781✔
1298
        }
1299

6,833✔
1300
        last_file = lf;
6,833✔
1301
    }
1302
}
1303

1304
void
8,640✔
1305
logfile_sub_source::text_filters_changed()
1306
{
2,236✔
1307
    this->lss_index_generation += 1;
1308

1309
    if (this->lss_line_meta_changed) {
177✔
1310
        this->invalidate_sql_filter();
1311
        this->lss_line_meta_changed = false;
177✔
1312
    }
1313

177✔
1314
    for (auto& ld : *this) {
19✔
1315
        auto* lf = ld->get_file_ptr();
19✔
1316

1317
        if (lf != nullptr) {
1318
            ld->ld_filter_state.clear_deleted_filter_state();
308✔
1319
            lf->reobserve_from(lf->begin()
131✔
1320
                               + ld->ld_filter_state.get_min_count(lf->size()));
1321
        }
131✔
1322
    }
131✔
1323

131✔
1324
    auto& vis_bm = this->tss_view->get_bookmarks();
131✔
1325
    uint32_t filtered_in_mask, filtered_out_mask;
1326

1327
    this->get_filters().get_enabled_mask(filtered_in_mask, filtered_out_mask);
1328

177✔
1329
    if (this->lss_index_delegate != nullptr) {
1330
        this->lss_index_delegate->index_start(*this);
1331
    }
177✔
1332
    vis_bm[&textview_curses::BM_USER_EXPR].clear();
1333

177✔
1334
    this->lss_filtered_index.clear();
177✔
1335
    for (size_t index_index = 0; index_index < this->lss_index.size();
1336
         index_index++)
177✔
1337
    {
1338
        content_line_t cl = (content_line_t) this->lss_index[index_index];
177✔
1339
        uint64_t line_number;
1,725✔
1340
        auto ld = this->find_data(cl, line_number);
1341

1342
        if (!(*ld)->is_visible()) {
1,548✔
1343
            continue;
1344
        }
1,548✔
1345

1346
        auto lf = (*ld)->get_file_ptr();
1,548✔
1347
        auto line_iter = lf->begin() + line_number;
210✔
1348

1349
        if (!this->tss_apply_filters
1350
            || (!(*ld)->ld_filter_state.excluded(
1,338✔
1351
                    filtered_in_mask, filtered_out_mask, line_number)
1,338✔
1352
                && this->check_extra_filters(ld, line_iter)))
1353
        {
2,676✔
1354
            auto eval_res = this->eval_sql_filter(
2,163✔
1355
                this->lss_marker_stmt.in(), ld, line_iter);
1356
            if (eval_res.isErr()) {
825✔
1357
                line_iter->set_expr_mark(false);
1358
            } else {
1359
                auto matched = eval_res.unwrap();
761✔
1360

761✔
1361
                if (matched) {
×
1362
                    line_iter->set_expr_mark(true);
1363
                    vis_bm[&textview_curses::BM_USER_EXPR].insert_once(
761✔
1364
                        vis_line_t(this->lss_filtered_index.size()));
1365
                } else {
761✔
1366
                    line_iter->set_expr_mark(false);
×
1367
                }
×
1368
            }
×
1369
            this->lss_filtered_index.push_back(index_index);
1370
            if (this->lss_index_delegate != nullptr) {
761✔
1371
                this->lss_index_delegate->index_line(*this, lf, line_iter);
1372
            }
1373
        }
761✔
1374
    }
761✔
1375

761✔
1376
    if (this->lss_index_delegate != nullptr) {
1377
        this->lss_index_delegate->index_complete(*this);
761✔
1378
    }
1379

1380
    if (this->tss_view != nullptr) {
177✔
1381
        this->tss_view->reload_data();
177✔
1382
        this->tss_view->redo_search();
1383
    }
1384
}
177✔
1385

177✔
1386
bool
177✔
1387
logfile_sub_source::list_input_handle_key(listview_curses& lv, int ch)
1388
{
177✔
1389
    switch (ch) {
1390
        case ' ': {
1391
            auto ov_vl = lv.get_overlay_selection();
2✔
1392
            if (ov_vl) {
1393
                auto* fos = dynamic_cast<field_overlay_source*>(
2✔
1394
                    lv.get_overlay_source());
×
1395
                auto iter = fos->fos_row_to_field_meta.find(ov_vl.value());
×
1396
                if (iter != fos->fos_row_to_field_meta.end()) {
×
1397
                    auto find_res = this->find_line_with_file(lv.get_top());
×
1398
                    if (find_res) {
×
1399
                        auto file_and_line = find_res.value();
×
1400
                        auto* format = file_and_line.first->get_format_ptr();
×
1401
                        auto fstates = format->get_field_states();
×
1402
                        auto state_iter = fstates.find(iter->second.lvm_name);
×
1403
                        if (state_iter != fstates.end()) {
×
1404
                            format->hide_field(iter->second.lvm_name,
×
1405
                                               !state_iter->second.is_hidden());
×
1406
                            lv.set_needs_update();
×
1407
                        }
×
1408
                    }
×
1409
                }
×
1410
                return true;
×
1411
            }
1412
            return false;
1413
        }
1414
        case '#': {
×
1415
            auto ov_vl = lv.get_overlay_selection();
1416
            if (ov_vl) {
×
1417
                auto* fos = dynamic_cast<field_overlay_source*>(
1418
                    lv.get_overlay_source());
×
1419
                auto iter = fos->fos_row_to_field_meta.find(ov_vl.value());
×
1420
                if (iter != fos->fos_row_to_field_meta.end()) {
×
1421
                    const auto& meta = iter->second;
×
1422
                    std::string cmd;
×
1423

×
1424
                    switch (meta.to_chart_type()) {
×
1425
                        case chart_type_t::none:
×
1426
                            break;
×
1427
                        case chart_type_t::hist: {
1428
                            auto prql = fmt::format(
×
1429
                                FMT_STRING(
×
1430
                                    "from {} | stats.hist {} slice:'1h'"),
×
1431
                                meta.lvm_format.value()->get_name(),
×
1432
                                meta.lvm_name);
1433
                            cmd = fmt::format(FMT_STRING(":prompt sql ; '{}'"),
×
1434
                                              shlex::escape(prql));
×
1435
                            break;
×
1436
                        }
1437
                        case chart_type_t::spectro:
×
1438
                            cmd = fmt::format(FMT_STRING(":spectrogram {}"),
×
1439
                                              meta.lvm_name);
×
1440
                            break;
×
1441
                    }
×
1442
                    if (!cmd.empty()) {
×
1443
                        this->lss_exec_context
×
1444
                            ->with_provenance(exec_context::mouse_input{})
1445
                            ->execute(cmd);
×
1446
                    }
×
1447
                }
×
1448
                return true;
×
1449
            }
×
1450
            return false;
×
1451
        }
1452
        case 'h':
×
1453
        case 'H':
×
1454
        case KEY_SLEFT:
×
1455
        case KEY_LEFT:
×
1456
            if (lv.get_left() == 0) {
1457
                this->increase_line_context();
1458
                lv.set_needs_update();
×
1459
                return true;
1460
            }
×
1461
            break;
1462
        case 'l':
×
1463
        case 'L':
1464
        case KEY_SRIGHT:
1465
        case KEY_RIGHT:
1466
            if (this->decrease_line_context()) {
×
1467
                lv.set_needs_update();
×
1468
                return true;
×
1469
            }
×
1470
            break;
1471
    }
×
1472
    return false;
×
1473
}
1474

1475
std::optional<
1476
    std::pair<grep_proc_source<vis_line_t>*, grep_proc_sink<vis_line_t>*>>
×
1477
logfile_sub_source::get_grepper()
×
1478
{
×
1479
    return std::make_pair(
1480
        (grep_proc_source<vis_line_t>*) &this->lss_meta_grepper,
×
1481
        (grep_proc_sink<vis_line_t>*) &this->lss_meta_grepper);
1482
}
2✔
1483

1484
/**
1485
 * Functor for comparing the ld_file field of the logfile_data struct.
1486
 */
1487
struct logfile_data_eq {
8✔
1488
    explicit logfile_data_eq(std::shared_ptr<logfile> lf)
1489
        : lde_file(std::move(lf))
16✔
1490
    {
×
1491
    }
8✔
1492

1493
    bool operator()(
1494
        const std::unique_ptr<logfile_sub_source::logfile_data>& ld) const
1495
    {
1496
        return this->lde_file == ld->get_file();
1497
    }
1498

862✔
1499
    std::shared_ptr<logfile> lde_file;
862✔
1500
};
1501

862✔
1502
bool
1503
logfile_sub_source::insert_file(const std::shared_ptr<logfile>& lf)
525✔
1504
{
1505
    iterator existing;
1506

525✔
1507
    require_lt(lf->size(), MAX_LINES_PER_FILE);
1508

1509
    existing = std::find_if(this->lss_files.begin(),
1510
                            this->lss_files.end(),
1511
                            logfile_data_eq(nullptr));
1512
    if (existing == this->lss_files.end()) {
1513
        if (this->lss_files.size() >= MAX_FILES) {
431✔
1514
            return false;
1515
        }
431✔
1516

1517
        auto ld = std::make_unique<logfile_data>(
431✔
1518
            this->lss_files.size(), this->get_filters(), lf);
1519
        ld->set_visibility(lf->get_open_options().loo_is_visible);
431✔
1520
        this->lss_files.push_back(std::move(ld));
1521
    } else {
862✔
1522
        (*existing)->set_file(lf);
431✔
1523
    }
429✔
1524
    this->lss_force_rebuild = true;
×
1525

1526
    return true;
1527
}
1528

429✔
1529
Result<void, lnav::console::user_message>
429✔
1530
logfile_sub_source::set_sql_filter(std::string stmt_str, sqlite3_stmt* stmt)
429✔
1531
{
429✔
1532
    if (stmt != nullptr && !this->lss_filtered_index.empty()) {
2✔
1533
        auto top_cl = this->at(0_vl);
1534
        auto ld = this->find_data(top_cl);
431✔
1535
        auto eval_res
1536
            = this->eval_sql_filter(stmt, ld, (*ld)->get_file_ptr()->begin());
431✔
1537

1538
        if (eval_res.isErr()) {
1539
            sqlite3_finalize(stmt);
1540
            return Err(eval_res.unwrapErr());
647✔
1541
        }
1542
    }
647✔
1543

6✔
1544
    for (auto& ld : *this) {
6✔
1545
        ld->ld_filter_state.lfo_filter_state.clear_filter_state(0);
1546
    }
6✔
1547

1548
    auto old_filter = this->get_sql_filter();
6✔
1549
    if (stmt != nullptr) {
1✔
1550
        auto new_filter
2✔
1551
            = std::make_shared<sql_filter>(*this, std::move(stmt_str), stmt);
1552

6✔
1553
        if (old_filter) {
1554
            auto existing_iter = std::find(this->tss_filters.begin(),
1,086✔
1555
                                           this->tss_filters.end(),
440✔
1556
                                           old_filter.value());
1557
            *existing_iter = new_filter;
1558
        } else {
646✔
1559
            this->tss_filters.add_filter(new_filter);
646✔
1560
        }
1561
    } else if (old_filter) {
5✔
1562
        this->tss_filters.delete_filter(old_filter.value()->get_id());
1563
    }
5✔
1564

×
1565
    return Ok();
1566
}
×
1567

×
1568
Result<void, lnav::console::user_message>
1569
logfile_sub_source::set_sql_marker(std::string stmt_str, sqlite3_stmt* stmt)
5✔
1570
{
1571
    if (stmt != nullptr && !this->lss_filtered_index.empty()) {
646✔
1572
        auto top_cl = this->at(0_vl);
5✔
1573
        auto ld = this->find_data(top_cl);
1574
        auto eval_res
1575
            = this->eval_sql_filter(stmt, ld, (*ld)->get_file_ptr()->begin());
646✔
1576

646✔
1577
        if (eval_res.isErr()) {
1578
            sqlite3_finalize(stmt);
1579
            return Err(eval_res.unwrapErr());
643✔
1580
        }
1581
    }
643✔
1582

2✔
1583
    this->lss_marker_stmt_text = std::move(stmt_str);
2✔
1584
    this->lss_marker_stmt = stmt;
1585

2✔
1586
    if (this->tss_view == nullptr) {
1587
        return Ok();
2✔
1588
    }
×
1589

×
1590
    auto& vis_bm = this->tss_view->get_bookmarks();
1591
    auto& expr_marks_bv = vis_bm[&textview_curses::BM_USER_EXPR];
2✔
1592

1593
    expr_marks_bv.clear();
643✔
1594
    if (this->lss_index_delegate) {
643✔
1595
        this->lss_index_delegate->index_start(*this);
1596
    }
643✔
1597
    for (auto row = 0_vl; row < vis_line_t(this->lss_filtered_index.size());
118✔
1598
         row += 1_vl)
1599
    {
1600
        auto cl = this->at(row);
525✔
1601
        auto ld = this->find_data(cl);
525✔
1602

1603
        if (!(*ld)->is_visible()) {
525✔
1604
            continue;
525✔
1605
        }
525✔
1606
        auto ll = (*ld)->get_file()->begin() + cl;
1607
        if (ll->is_continued() || ll->is_ignored()) {
543✔
1608
            continue;
18✔
1609
        }
1610
        auto eval_res
18✔
1611
            = this->eval_sql_filter(this->lss_marker_stmt.in(), ld, ll);
18✔
1612

1613
        if (eval_res.isErr()) {
18✔
1614
            ll->set_expr_mark(false);
1✔
1615
        } else {
1616
            auto matched = eval_res.unwrap();
18✔
1617

18✔
1618
            if (matched) {
1✔
1619
                ll->set_expr_mark(true);
1620
                expr_marks_bv.insert_once(row);
1621
            } else {
17✔
1622
                ll->set_expr_mark(false);
1623
            }
17✔
1624
        }
×
1625
        if (this->lss_index_delegate) {
1626
            this->lss_index_delegate->index_line(
17✔
1627
                *this, (*ld)->get_file_ptr(), ll);
1628
        }
17✔
1629
    }
4✔
1630
    if (this->lss_index_delegate) {
4✔
1631
        this->lss_index_delegate->index_complete(*this);
1632
    }
13✔
1633

1634
    return Ok();
1635
}
17✔
1636

17✔
1637
Result<void, lnav::console::user_message>
17✔
1638
logfile_sub_source::set_preview_sql_filter(sqlite3_stmt* stmt)
1639
{
17✔
1640
    if (stmt != nullptr && !this->lss_filtered_index.empty()) {
525✔
1641
        auto top_cl = this->at(0_vl);
525✔
1642
        auto ld = this->find_data(top_cl);
1643
        auto eval_res
1644
            = this->eval_sql_filter(stmt, ld, (*ld)->get_file_ptr()->begin());
525✔
1645

1646
        if (eval_res.isErr()) {
1647
            sqlite3_finalize(stmt);
1648
            return Err(eval_res.unwrapErr());
682✔
1649
        }
1650
    }
682✔
1651

×
1652
    this->lss_preview_filter_stmt = stmt;
×
1653

1654
    return Ok();
×
1655
}
1656

×
1657
Result<bool, lnav::console::user_message>
×
1658
logfile_sub_source::eval_sql_filter(sqlite3_stmt* stmt,
×
1659
                                    iterator ld,
1660
                                    logfile::const_iterator ll)
1661
{
1662
    if (stmt == nullptr) {
682✔
1663
        return Ok(false);
1664
    }
682✔
1665

1666
    auto* lf = (*ld)->get_file_ptr();
1667
    char timestamp_buffer[64];
1668
    shared_buffer_ref raw_sbr;
7,333✔
1669
    logline_value_vector values;
1670
    auto& sbr = values.lvv_sbr;
1671
    lf->read_full_message(ll, sbr);
1672
    sbr.erase_ansi();
7,333✔
1673
    auto format = lf->get_format();
14,570✔
1674
    string_attrs_t sa;
1675
    auto line_number = std::distance(lf->cbegin(), ll);
1676
    format->annotate(lf, line_number, sa, values);
48✔
1677

1678
    sqlite3_reset(stmt);
48✔
1679
    sqlite3_clear_bindings(stmt);
48✔
1680

48✔
1681
    auto count = sqlite3_bind_parameter_count(stmt);
48✔
1682
    for (int lpc = 0; lpc < count; lpc++) {
48✔
1683
        const auto* name = sqlite3_bind_parameter_name(stmt, lpc + 1);
48✔
1684

48✔
1685
        if (name[0] == '$') {
48✔
1686
            const char* env_value;
48✔
1687

1688
            if ((env_value = getenv(&name[1])) != nullptr) {
48✔
1689
                sqlite3_bind_text(stmt, lpc + 1, env_value, -1, SQLITE_STATIC);
48✔
1690
            }
1691
            continue;
48✔
1692
        }
97✔
1693
        if (strcmp(name, ":log_level") == 0) {
49✔
1694
            sqlite3_bind_text(
1695
                stmt, lpc + 1, ll->get_level_name(), -1, SQLITE_STATIC);
49✔
1696
            continue;
1697
        }
1698
        if (strcmp(name, ":log_time") == 0) {
5✔
1699
            auto len = sql_strftime(timestamp_buffer,
1✔
1700
                                    sizeof(timestamp_buffer),
1701
                                    ll->get_timeval(),
5✔
1702
                                    'T');
5✔
1703
            sqlite3_bind_text(
44✔
1704
                stmt, lpc + 1, timestamp_buffer, len, SQLITE_STATIC);
×
1705
            continue;
1706
        }
×
1707
        if (strcmp(name, ":log_time_msecs") == 0) {
1708
            sqlite3_bind_int64(stmt, lpc + 1, ll->get_time_in_millis());
44✔
1709
            continue;
×
1710
        }
1711
        if (strcmp(name, ":log_mark") == 0) {
×
1712
            sqlite3_bind_int(stmt, lpc + 1, ll->is_marked());
1713
            continue;
×
1714
        }
1715
        if (strcmp(name, ":log_comment") == 0) {
×
1716
            const auto& bm = lf->get_bookmark_metadata();
1717
            auto line_number
44✔
1718
                = static_cast<uint32_t>(std::distance(lf->cbegin(), ll));
1✔
1719
            auto bm_iter = bm.find(line_number);
1✔
1720
            if (bm_iter != bm.end() && !bm_iter->second.bm_comment.empty()) {
1721
                const auto& meta = bm_iter->second;
43✔
1722
                sqlite3_bind_text(stmt,
×
1723
                                  lpc + 1,
×
1724
                                  meta.bm_comment.c_str(),
1725
                                  meta.bm_comment.length(),
43✔
1726
                                  SQLITE_STATIC);
×
1727
            }
1728
            continue;
×
1729
        }
×
1730
        if (strcmp(name, ":log_annotations") == 0) {
×
1731
            const auto& bm = lf->get_bookmark_metadata();
×
1732
            auto line_number
×
1733
                = static_cast<uint32_t>(std::distance(lf->cbegin(), ll));
1734
            auto bm_iter = bm.find(line_number);
1735
            if (bm_iter != bm.end()
×
1736
                && !bm_iter->second.bm_annotations.la_pairs.empty())
1737
            {
1738
                const auto& meta = bm_iter->second;
×
1739
                auto anno_str = logmsg_annotations_handlers.to_string(
1740
                    meta.bm_annotations);
43✔
1741

×
1742
                sqlite3_bind_text(stmt,
1743
                                  lpc + 1,
×
1744
                                  anno_str.c_str(),
×
1745
                                  anno_str.length(),
×
1746
                                  SQLITE_TRANSIENT);
×
1747
            }
1748
            continue;
×
1749
        }
1750
        if (strcmp(name, ":log_tags") == 0) {
×
1751
            const auto& bm = lf->get_bookmark_metadata();
1752
            auto line_number
×
1753
                = static_cast<uint32_t>(std::distance(lf->cbegin(), ll));
1754
            auto bm_iter = bm.find(line_number);
1755
            if (bm_iter != bm.end() && !bm_iter->second.bm_tags.empty()) {
×
1756
                const auto& meta = bm_iter->second;
1757
                yajlpp_gen gen;
1758

×
1759
                yajl_gen_config(gen, yajl_gen_beautify, false);
1760

43✔
1761
                {
9✔
1762
                    yajlpp_array arr(gen);
1763

9✔
1764
                    for (const auto& str : meta.bm_tags) {
9✔
1765
                        arr.gen(str);
9✔
1766
                    }
1✔
1767
                }
1✔
1768

1769
                string_fragment sf = gen.to_string_fragment();
1✔
1770

1771
                sqlite3_bind_text(
1772
                    stmt, lpc + 1, sf.data(), sf.length(), SQLITE_TRANSIENT);
1✔
1773
            }
1774
            continue;
2✔
1775
        }
1✔
1776
        if (strcmp(name, ":log_format") == 0) {
1777
            const auto format_name = format->get_name();
1✔
1778
            sqlite3_bind_text(stmt,
1779
                              lpc + 1,
1✔
1780
                              format_name.get(),
1781
                              format_name.size(),
1✔
1782
                              SQLITE_STATIC);
1783
            continue;
1✔
1784
        }
9✔
1785
        if (strcmp(name, ":log_format_regex") == 0) {
9✔
1786
            const auto pat_name = format->get_pattern_name(line_number);
34✔
1787
            sqlite3_bind_text(
×
1788
                stmt, lpc + 1, pat_name.get(), pat_name.size(), SQLITE_STATIC);
×
1789
            continue;
1790
        }
1791
        if (strcmp(name, ":log_path") == 0) {
×
1792
            const auto& filename = lf->get_filename();
1793
            sqlite3_bind_text(stmt,
×
1794
                              lpc + 1,
1795
                              filename.c_str(),
34✔
1796
                              filename.native().length(),
×
1797
                              SQLITE_STATIC);
×
1798
            continue;
×
1799
        }
×
1800
        if (strcmp(name, ":log_unique_path") == 0) {
1801
            const auto& filename = lf->get_unique_path();
34✔
1802
            sqlite3_bind_text(stmt,
×
1803
                              lpc + 1,
×
1804
                              filename.c_str(),
1805
                              filename.native().length(),
1806
                              SQLITE_STATIC);
×
1807
            continue;
1808
        }
×
1809
        if (strcmp(name, ":log_text") == 0) {
1810
            sqlite3_bind_text(
34✔
1811
                stmt, lpc + 1, sbr.get_data(), sbr.length(), SQLITE_STATIC);
×
1812
            continue;
×
1813
        }
1814
        if (strcmp(name, ":log_body") == 0) {
1815
            auto body_attr_opt = get_string_attr(sa, SA_BODY);
×
1816
            if (body_attr_opt) {
1817
                const auto& sar
×
1818
                    = body_attr_opt.value().saw_string_attr->sa_range;
1819

34✔
1820
                sqlite3_bind_text(stmt,
4✔
1821
                                  lpc + 1,
4✔
1822
                                  sbr.get_data_at(sar.lr_start),
4✔
1823
                                  sar.length(),
1824
                                  SQLITE_STATIC);
30✔
1825
            } else {
9✔
1826
                sqlite3_bind_null(stmt, lpc + 1);
9✔
1827
            }
1828
            continue;
9✔
1829
        }
1830
        if (strcmp(name, ":log_opid") == 0) {
18✔
1831
            if (values.lvv_opid_value) {
1832
                sqlite3_bind_text(stmt,
9✔
1833
                                  lpc + 1,
1834
                                  values.lvv_opid_value->c_str(),
1835
                                  values.lvv_opid_value->length(),
1836
                                  SQLITE_STATIC);
×
1837
            } else {
1838
                sqlite3_bind_null(stmt, lpc + 1);
9✔
1839
            }
9✔
1840
            continue;
21✔
1841
        }
×
1842
        if (strcmp(name, ":log_raw_text") == 0) {
×
1843
            auto res = lf->read_raw_message(ll);
1844

1845
            if (res.isOk()) {
×
1846
                raw_sbr = res.unwrap();
1847
                sqlite3_bind_text(stmt,
1848
                                  lpc + 1,
×
1849
                                  raw_sbr.get_data(),
1850
                                  raw_sbr.length(),
×
1851
                                  SQLITE_STATIC);
1852
            }
21✔
1853
            continue;
×
1854
        }
1855
        for (const auto& lv : values.lvv_values) {
×
1856
            if (lv.lv_meta.lvm_name != &name[1]) {
×
1857
                continue;
×
1858
            }
1859

1860
            switch (lv.lv_meta.lvm_kind) {
×
1861
                case value_kind_t::VALUE_BOOLEAN:
1862
                    sqlite3_bind_int64(stmt, lpc + 1, lv.lv_value.i);
1863
                    break;
×
1864
                case value_kind_t::VALUE_FLOAT:
1865
                    sqlite3_bind_double(stmt, lpc + 1, lv.lv_value.d);
157✔
1866
                    break;
153✔
1867
                case value_kind_t::VALUE_INTEGER:
136✔
1868
                    sqlite3_bind_int64(stmt, lpc + 1, lv.lv_value.i);
1869
                    break;
1870
                case value_kind_t::VALUE_NULL:
17✔
1871
                    sqlite3_bind_null(stmt, lpc + 1);
×
1872
                    break;
×
1873
                default:
×
1874
                    sqlite3_bind_text(stmt,
×
1875
                                      lpc + 1,
×
1876
                                      lv.text_value(),
×
1877
                                      lv.text_length(),
12✔
1878
                                      SQLITE_TRANSIENT);
12✔
1879
                    break;
12✔
1880
            }
×
1881
            break;
×
1882
        }
×
1883
    }
5✔
1884

5✔
1885
    auto step_res = sqlite3_step(stmt);
1886

1887
    sqlite3_reset(stmt);
5✔
1888
    sqlite3_clear_bindings(stmt);
1889
    switch (step_res) {
5✔
1890
        case SQLITE_OK:
1891
        case SQLITE_DONE:
17✔
1892
            return Ok(false);
1893
        case SQLITE_ROW:
1894
            return Ok(true);
1895
        default:
48✔
1896
            return Err(sqlite3_error_to_user_message(sqlite3_db_handle(stmt)));
1897
    }
48✔
1898
}
48✔
1899

48✔
1900
bool
19✔
1901
logfile_sub_source::check_extra_filters(iterator ld, logfile::iterator ll)
1902
{
38✔
1903
    if (this->lss_marked_only && !(ll->is_marked() || ll->is_expr_marked())) {
28✔
1904
        return false;
56✔
1905
    }
1✔
1906

2✔
1907
    if (ll->get_msg_level() < this->lss_min_log_level) {
1908
        return false;
48✔
1909
    }
1910

1911
    if (*ll < this->lss_min_log_time) {
7,338✔
1912
        return false;
1913
    }
7,338✔
1914

2✔
1915
    if (!(*ll <= this->lss_max_log_time)) {
1916
        return false;
1917
    }
7,336✔
1918

2✔
1919
    return true;
1920
}
1921

7,334✔
1922
void
30✔
1923
logfile_sub_source::invalidate_sql_filter()
1924
{
1925
    for (auto& ld : *this) {
7,304✔
1926
        ld->ld_filter_state.lfo_filter_state.clear_filter_state(0);
33✔
1927
    }
1928
}
1929

7,271✔
1930
void
1931
logfile_sub_source::text_mark(const bookmark_type_t* bm,
1932
                              vis_line_t line,
1933
                              bool added)
19✔
1934
{
1935
    if (line >= (int) this->lss_index.size()) {
38✔
1936
        return;
19✔
1937
    }
1938

19✔
1939
    content_line_t cl = this->at(line);
1940
    std::vector<content_line_t>::iterator lb;
1941

117✔
1942
    if (bm == &textview_curses::BM_USER) {
1943
        logline* ll = this->find_line(cl);
1944

1945
        ll->set_mark(added);
117✔
1946
    }
×
1947
    lb = std::lower_bound(
1948
        this->lss_user_marks[bm].begin(), this->lss_user_marks[bm].end(), cl);
1949
    if (added) {
117✔
1950
        if (lb == this->lss_user_marks[bm].end() || *lb != cl) {
117✔
1951
            this->lss_user_marks[bm].insert(lb, cl);
1952
        }
117✔
1953
    } else if (lb != this->lss_user_marks[bm].end() && *lb == cl) {
43✔
1954
        require(lb != this->lss_user_marks[bm].end());
1955

43✔
1956
        this->lss_user_marks[bm].erase(lb);
1957
    }
117✔
1958
    if (bm == &textview_curses::BM_META
117✔
1959
        && this->lss_meta_grepper.gps_proc != nullptr)
117✔
1960
    {
59✔
1961
        this->tss_view->search_range(line, line + 1_vl);
54✔
1962
        this->tss_view->search_new_data();
1963
    }
58✔
1964
}
7✔
1965

1966
void
7✔
1967
logfile_sub_source::text_clear_marks(const bookmark_type_t* bm)
1968
{
117✔
1969
    std::vector<content_line_t>::iterator iter;
16✔
1970

1971
    if (bm == &textview_curses::BM_USER) {
1✔
1972
        for (iter = this->lss_user_marks[bm].begin();
1✔
1973
             iter != this->lss_user_marks[bm].end();)
1974
        {
1975
            this->find_line(*iter)->set_mark(false);
1976
            iter = this->lss_user_marks[bm].erase(iter);
1977
        }
34✔
1978
    } else {
1979
        this->lss_user_marks[bm].clear();
34✔
1980
    }
1981
}
34✔
1982

4✔
1983
void
4✔
1984
logfile_sub_source::remove_file(std::shared_ptr<logfile> lf)
1985
{
×
1986
    iterator iter;
×
1987

1988
    iter = std::find_if(
1989
        this->lss_files.begin(), this->lss_files.end(), logfile_data_eq(lf));
30✔
1990
    if (iter != this->lss_files.end()) {
1991
        bookmarks<content_line_t>::type::iterator mark_iter;
34✔
1992
        int file_index = iter - this->lss_files.begin();
1993

1994
        (*iter)->clear();
431✔
1995
        for (mark_iter = this->lss_user_marks.begin();
1996
             mark_iter != this->lss_user_marks.end();
431✔
1997
             ++mark_iter)
1998
        {
431✔
1999
            auto mark_curr = content_line_t(file_index * MAX_LINES_PER_FILE);
862✔
2000
            auto mark_end
431✔
2001
                = content_line_t((file_index + 1) * MAX_LINES_PER_FILE);
431✔
2002
            auto& bv = mark_iter->second;
431✔
2003
            auto file_range = bv.equal_range(mark_curr, mark_end);
2004

431✔
2005
            if (file_range.first != file_range.second) {
431✔
2006
                bv.erase(file_range.first, file_range.second);
548✔
2007
            }
117✔
2008
        }
2009

117✔
2010
        this->lss_force_rebuild = true;
2011
    }
117✔
2012
    this->lss_token_file = nullptr;
117✔
2013
}
117✔
2014

2015
std::optional<vis_line_t>
117✔
2016
logfile_sub_source::find_from_content(content_line_t cl)
54✔
2017
{
2018
    content_line_t line = cl;
2019
    std::shared_ptr<logfile> lf = this->find(line);
2020

431✔
2021
    if (lf != nullptr) {
2022
        auto ll_iter = lf->begin() + line;
431✔
2023
        auto& ll = *ll_iter;
431✔
2024
        auto vis_start_opt = this->find_from_time(ll.get_timeval());
2025

2026
        if (!vis_start_opt) {
5✔
2027
            return std::nullopt;
2028
        }
5✔
2029

5✔
2030
        auto vis_start = *vis_start_opt;
2031

5✔
2032
        while (vis_start < vis_line_t(this->text_line_count())) {
5✔
2033
            content_line_t guess_cl = this->at(vis_start);
5✔
2034

5✔
2035
            if (cl == guess_cl) {
2036
                return vis_start;
5✔
2037
            }
×
2038

2039
            auto guess_line = this->find_line(guess_cl);
2040

5✔
2041
            if (!guess_line || ll < *guess_line) {
2042
                return std::nullopt;
5✔
2043
            }
5✔
2044

2045
            ++vis_start;
5✔
2046
        }
5✔
2047
    }
2048

2049
    return std::nullopt;
×
2050
}
2051

×
2052
void
×
2053
logfile_sub_source::reload_index_delegate()
2054
{
2055
    if (this->lss_index_delegate == nullptr) {
×
2056
        return;
2057
    }
2058

2059
    this->lss_index_delegate->index_start(*this);
×
2060
    for (unsigned int index : this->lss_filtered_index) {
5✔
2061
        content_line_t cl = (content_line_t) this->lss_index[index];
2062
        uint64_t line_number;
2063
        auto ld = this->find_data(cl, line_number);
527✔
2064
        std::shared_ptr<logfile> lf = (*ld)->get_file();
2065

527✔
2066
        this->lss_index_delegate->index_line(
×
2067
            *this, lf.get(), lf->begin() + line_number);
2068
    }
2069
    this->lss_index_delegate->index_complete(*this);
527✔
2070
}
555✔
2071

28✔
2072
std::optional<std::shared_ptr<text_filter>>
2073
logfile_sub_source::get_sql_filter()
28✔
2074
{
28✔
2075
    return this->tss_filters | lnav::itertools::find_if([](const auto& filt) {
2076
               return filt->get_index() == 0;
28✔
2077
           })
28✔
2078
        | lnav::itertools::deref();
28✔
2079
}
527✔
2080

2081
void
2082
log_location_history::loc_history_append(vis_line_t top)
2083
{
1,496✔
2084
    if (top >= vis_line_t(this->llh_log_source.text_line_count())) {
2085
        return;
1,496✔
2086
    }
332✔
2087

2088
    content_line_t cl = this->llh_log_source.at(top);
4,488✔
2089

2090
    auto iter = this->llh_history.begin();
2091
    iter += this->llh_history.size() - this->lh_history_position;
2092
    this->llh_history.erase_from(iter);
79✔
2093
    this->lh_history_position = 0;
2094
    this->llh_history.push_back(cl);
79✔
2095
}
3✔
2096

2097
std::optional<vis_line_t>
2098
log_location_history::loc_history_back(vis_line_t current_top)
76✔
2099
{
2100
    while (this->lh_history_position < this->llh_history.size()) {
76✔
2101
        auto iter = this->llh_history.rbegin();
76✔
2102

76✔
2103
        auto vis_for_pos = this->llh_log_source.find_from_content(*iter);
76✔
2104

76✔
2105
        if (this->lh_history_position == 0 && vis_for_pos != current_top) {
2106
            return vis_for_pos;
2107
        }
2108

2✔
2109
        if ((this->lh_history_position + 1) >= this->llh_history.size()) {
2110
            break;
2✔
2111
        }
2✔
2112

2113
        this->lh_history_position += 1;
2✔
2114

2115
        iter += this->lh_history_position;
2✔
2116

2✔
2117
        vis_for_pos = this->llh_log_source.find_from_content(*iter);
2118

2119
        if (vis_for_pos) {
2✔
2120
            return vis_for_pos;
×
2121
        }
2122
    }
2123

2✔
2124
    return std::nullopt;
2125
}
2✔
2126

2127
std::optional<vis_line_t>
2✔
2128
log_location_history::loc_history_forward(vis_line_t current_top)
2129
{
2✔
2130
    while (this->lh_history_position > 0) {
2✔
2131
        this->lh_history_position -= 1;
2132

2133
        auto iter = this->llh_history.rbegin();
2134

×
2135
        iter += this->lh_history_position;
2136

2137
        auto vis_for_pos = this->llh_log_source.find_from_content(*iter);
2138

1✔
2139
        if (vis_for_pos) {
2140
            return vis_for_pos;
1✔
2141
        }
1✔
2142
    }
2143

1✔
2144
    return std::nullopt;
2145
}
1✔
2146

2147
bool
1✔
2148
sql_filter::matches(std::optional<line_source> ls_opt,
2149
                    const shared_buffer_ref& line)
1✔
2150
{
1✔
2151
    if (!ls_opt) {
2152
        return false;
2153
    }
2154

×
2155
    auto ls = ls_opt;
2156

2157
    if (!ls->ls_line->is_message()) {
2158
        return false;
19✔
2159
    }
2160
    if (this->sf_filter_stmt == nullptr) {
2161
        return false;
19✔
2162
    }
×
2163

2164
    auto lfp = ls->ls_file.shared_from_this();
2165
    auto ld = this->sf_log_source.find_data_i(lfp);
19✔
2166
    if (ld == this->sf_log_source.end()) {
2167
        return false;
19✔
2168
    }
1✔
2169

2170
    auto eval_res = this->sf_log_source.eval_sql_filter(
18✔
2171
        this->sf_filter_stmt, ld, ls->ls_line);
×
2172
    if (eval_res.unwrapOr(true)) {
2173
        return false;
2174
    }
18✔
2175

18✔
2176
    return true;
18✔
2177
}
×
2178

2179
std::string
2180
sql_filter::to_command() const
18✔
2181
{
18✔
2182
    return fmt::format(FMT_STRING("filter-expr {}"), this->lf_id);
18✔
2183
}
12✔
2184

2185
bool
2186
logfile_sub_source::meta_grepper::grep_value_for_line(vis_line_t line,
6✔
2187
                                                      std::string& value_out)
18✔
2188
{
2189
    auto line_meta_opt = this->lmg_source.find_bookmark_metadata(line);
2190
    if (!line_meta_opt) {
×
2191
        value_out.clear();
2192
    } else {
×
2193
        auto& bm = *(line_meta_opt.value());
×
2194

×
2195
        {
2196
            md2attr_line mdal;
2197

2198
            auto parse_res = md4cpp::parse(bm.bm_comment, mdal);
×
2199
            if (parse_res.isOk()) {
2200
                value_out.append(parse_res.unwrap().get_string());
2201
            } else {
×
2202
                value_out.append(bm.bm_comment);
×
2203
            }
×
2204
        }
2205

×
2206
        value_out.append("\x1c");
2207
        for (const auto& tag : bm.bm_tags) {
2208
            value_out.append(tag);
×
2209
            value_out.append("\x1c");
2210
        }
×
2211
        value_out.append("\x1c");
×
2212
        for (const auto& pair : bm.bm_annotations.la_pairs) {
×
2213
            value_out.append(pair.first);
2214
            value_out.append("\x1c");
×
2215

2216
            md2attr_line mdal;
2217

2218
            auto parse_res = md4cpp::parse(pair.second, mdal);
×
2219
            if (parse_res.isOk()) {
×
2220
                value_out.append(parse_res.unwrap().get_string());
×
2221
            } else {
×
2222
                value_out.append(pair.second);
2223
            }
×
2224
            value_out.append("\x1c");
×
2225
        }
×
2226
        value_out.append("\x1c");
×
2227
        value_out.append(bm.bm_opid);
2228
    }
×
2229

2230
    return !this->lmg_done;
×
2231
}
×
2232

×
2233
vis_line_t
2234
logfile_sub_source::meta_grepper::grep_initial_line(vis_line_t start,
×
2235
                                                    vis_line_t highest)
2236
{
×
2237
    auto& bm = this->lmg_source.tss_view->get_bookmarks();
2238
    auto& bv = bm[&textview_curses::BM_META];
×
2239

×
2240
    if (bv.empty()) {
2241
        return -1_vl;
2242
    }
×
2243
    return *bv.begin();
2244
}
2245

2246
void
×
2247
logfile_sub_source::meta_grepper::grep_next_line(vis_line_t& line)
2248
{
2249
    auto& bm = this->lmg_source.tss_view->get_bookmarks();
×
2250
    auto& bv = bm[&textview_curses::BM_META];
×
2251

2252
    auto line_opt = bv.next(vis_line_t(line));
×
2253
    if (!line_opt) {
×
2254
        this->lmg_done = true;
2255
    }
×
2256
    line = line_opt.value_or(-1_vl);
2257
}
2258

2259
void
×
2260
logfile_sub_source::meta_grepper::grep_begin(grep_proc<vis_line_t>& gp,
2261
                                             vis_line_t start,
×
2262
                                             vis_line_t stop)
×
2263
{
2264
    this->lmg_source.quiesce();
×
2265

×
2266
    this->lmg_source.tss_view->grep_begin(gp, start, stop);
×
2267
}
2268

×
2269
void
2270
logfile_sub_source::meta_grepper::grep_end(grep_proc<vis_line_t>& gp)
2271
{
2272
    this->lmg_source.tss_view->grep_end(gp);
30✔
2273
}
2274

2275
void
2276
logfile_sub_source::meta_grepper::grep_match(grep_proc<vis_line_t>& gp,
30✔
2277
                                             vis_line_t line,
2278
                                             int start,
30✔
2279
                                             int end)
30✔
2280
{
2281
    this->lmg_source.tss_view->grep_match(gp, line, start, end);
2282
}
22✔
2283

2284
logline_window::iterator
22✔
2285
logline_window::begin()
22✔
2286
{
2287
    if (this->lw_start_line < 0_vl) {
2288
        return this->end();
3✔
2289
    }
2290

2291
    return {this->lw_source, this->lw_start_line};
2292
}
2293

3✔
2294
logline_window::iterator
3✔
2295
logline_window::end()
2296
{
2297
    auto vl = this->lw_end_line;
65✔
2298
    while (vl < vis_line_t(this->lw_source.text_line_count())) {
2299
        const auto& line = this->lw_source.find_line(this->lw_source.at(vl));
65✔
2300
        if (line->is_message()) {
×
2301
            break;
2302
        }
2303
        ++vl;
65✔
2304
    }
2305

2306
    return {this->lw_source, vl};
2307
}
26✔
2308

2309
logline_window::logmsg_info::
26✔
2310
logmsg_info(logfile_sub_source& lss, vis_line_t vl)
26✔
2311
    : li_source(lss), li_line(vl)
16✔
2312
{
16✔
2313
    if (this->li_line < vis_line_t(this->li_source.text_line_count())) {
16✔
2314
        while (true) {
2315
            auto pair_opt = this->li_source.find_line_with_file(vl);
×
2316

2317
            if (!pair_opt) {
2318
                break;
52✔
2319
            }
2320

2321
            auto line_pair = pair_opt.value();
91✔
2322
            if (line_pair.second->is_message()) {
91✔
2323
                this->li_file = line_pair.first.get();
91✔
2324
                this->li_logline = line_pair.second;
2325
                this->li_line_number
91✔
2326
                    = std::distance(this->li_file->begin(), this->li_logline);
2327
                break;
81✔
2328
            } else {
2329
                --vl;
81✔
2330
            }
×
2331
        }
2332
    }
2333
}
81✔
2334

81✔
2335
void
81✔
2336
logline_window::logmsg_info::next_msg()
81✔
2337
{
2338
    this->li_file = nullptr;
81✔
2339
    this->li_logline = logfile::iterator{};
81✔
2340
    this->li_string_attrs.clear();
2341
    this->li_line_values.clear();
×
2342
    ++this->li_line;
2343
    while (this->li_line < vis_line_t(this->li_source.text_line_count())) {
162✔
2344
        auto pair_opt = this->li_source.find_line_with_file(this->li_line);
2345

91✔
2346
        if (!pair_opt) {
2347
            break;
2348
        }
615✔
2349

2350
        auto line_pair = pair_opt.value();
615✔
2351
        if (line_pair.second->is_message()) {
615✔
2352
            this->li_file = line_pair.first.get();
615✔
2353
            this->li_logline = line_pair.second;
615✔
2354
            this->li_line_number
615✔
2355
                = std::distance(this->li_file->begin(), this->li_logline);
615✔
2356
            break;
605✔
2357
        } else {
2358
            ++this->li_line;
605✔
2359
        }
×
2360
    }
2361
}
2362

605✔
2363
void
605✔
2364
logline_window::logmsg_info::prev_msg()
605✔
2365
{
605✔
2366
    this->li_file = nullptr;
2367
    this->li_logline = logfile::iterator{};
605✔
2368
    this->li_string_attrs.clear();
605✔
2369
    this->li_line_values.clear();
2370
    while (this->li_line > 0) {
×
2371
        --this->li_line;
2372
        auto pair_opt = this->li_source.find_line_with_file(this->li_line);
1,210✔
2373

615✔
2374
        if (!pair_opt) {
2375
            break;
2376
        }
×
2377

2378
        auto line_pair = pair_opt.value();
×
2379
        if (line_pair.second->is_message()) {
×
2380
            this->li_file = line_pair.first.get();
×
2381
            this->li_logline = line_pair.second;
×
2382
            this->li_line_number
×
2383
                = std::distance(this->li_file->begin(), this->li_logline);
×
2384
            break;
×
2385
        }
2386
    }
×
2387
}
×
2388

2389
std::optional<bookmark_metadata*>
2390
logline_window::logmsg_info::get_metadata() const
×
2391
{
×
2392
    auto line_number = std::distance(this->li_file->begin(), this->li_logline);
×
2393
    auto& bm = this->li_file->get_bookmark_metadata();
×
2394
    auto bm_iter = bm.find(line_number);
2395
    if (bm_iter == bm.end()) {
×
2396
        return std::nullopt;
×
2397
    }
2398
    return &bm_iter->second;
2399
}
2400

2401
logline_window::logmsg_info::metadata_edit_guard::~
2402
metadata_edit_guard()
244✔
2403
{
2404
    auto line_number = std::distance(this->meg_logmsg_info.li_file->begin(),
244✔
2405
                                     this->meg_logmsg_info.li_logline);
244✔
2406
    auto& bm = this->meg_logmsg_info.li_file->get_bookmark_metadata();
244✔
2407
    auto bm_iter = bm.find(line_number);
244✔
2408
    if (bm_iter != bm.end()
244✔
2409
        && bm_iter->second.empty(bookmark_metadata::categories::any))
2410
    {
×
2411
        bm.erase(bm_iter);
2412
    }
2413
}
×
2414

2415
bookmark_metadata&
2416
logline_window::logmsg_info::metadata_edit_guard::operator*()
×
2417
{
×
2418
    auto line_number = std::distance(this->meg_logmsg_info.li_file->begin(),
×
2419
                                     this->meg_logmsg_info.li_logline);
×
2420
    auto& bm = this->meg_logmsg_info.li_file->get_bookmark_metadata();
×
2421
    return bm[line_number];
×
2422
}
2423

×
2424
size_t
2425
logline_window::logmsg_info::get_line_count() const
2426
{
2427
    size_t retval = 1;
2428
    auto iter = std::next(this->li_logline);
×
2429
    while (iter != this->li_file->end() && iter->is_continued()) {
2430
        ++iter;
×
2431
        retval += 1;
×
2432
    }
×
2433

×
2434
    return retval;
2435
}
2436

2437
void
27✔
2438
logline_window::logmsg_info::load_msg() const
2439
{
27✔
2440
    if (!this->li_string_attrs.empty()) {
27✔
2441
        return;
27✔
2442
    }
×
2443

×
2444
    auto format = this->li_file->get_format();
2445
    this->li_file->read_full_message(this->li_logline,
2446
                                     this->li_line_values.lvv_sbr);
27✔
2447
    if (this->li_line_values.lvv_sbr.get_metadata().m_has_ansi) {
2448
        auto* writable_data = this->li_line_values.lvv_sbr.get_writable_data();
2449
        auto str
2450
            = std::string{writable_data, this->li_line_values.lvv_sbr.length()};
315✔
2451
        scrub_ansi_string(str, &this->li_string_attrs);
2452
        this->li_line_values.lvv_sbr.get_metadata().m_has_ansi = false;
315✔
2453
    }
21✔
2454
    format->annotate(this->li_file,
2455
                     std::distance(this->li_file->begin(), this->li_logline),
2456
                     this->li_string_attrs,
294✔
2457
                     this->li_line_values,
294✔
2458
                     false);
294✔
2459

294✔
2460
    if (!this->li_line_values.lvv_opid_value) {
×
2461
        auto bm_opt = this->get_metadata();
2462
        if (bm_opt && !bm_opt.value()->bm_opid.empty()) {
×
2463
            this->li_line_values.lvv_opid_value = bm_opt.value()->bm_opid;
×
2464
            this->li_line_values.lvv_opid_provenance
×
2465
                = logline_value_vector::opid_provenance::user;
2466
        }
294✔
2467
    }
294✔
2468
}
294✔
2469

294✔
2470
std::string
2471
logline_window::logmsg_info::to_string(const struct line_range& lr) const
2472
{
294✔
2473
    this->load_msg();
244✔
2474

244✔
2475
    return this->li_line_values.lvv_sbr
×
2476
        .to_string_fragment(lr.lr_start, lr.length())
2477
        .to_string();
×
2478
}
2479

2480
logline_window::iterator&
294✔
2481
logline_window::iterator::operator++()
2482
{
2483
    this->i_info.next_msg();
×
2484

2485
    return *this;
×
2486
}
2487

2488
logline_window::iterator&
×
2489
logline_window::iterator::operator--()
×
2490
{
2491
    this->i_info.prev_msg();
2492

2493
    return *this;
615✔
2494
}
2495

615✔
2496
static std::vector<breadcrumb::possibility>
2497
timestamp_poss()
615✔
2498
{
2499
    const static std::vector<breadcrumb::possibility> retval = {
2500
        breadcrumb::possibility{"-1 day"},
2501
        breadcrumb::possibility{"-1h"},
×
2502
        breadcrumb::possibility{"-30m"},
2503
        breadcrumb::possibility{"-15m"},
×
2504
        breadcrumb::possibility{"-5m"},
2505
        breadcrumb::possibility{"-1m"},
×
2506
        breadcrumb::possibility{"+1m"},
2507
        breadcrumb::possibility{"+5m"},
2508
        breadcrumb::possibility{"+15m"},
2509
        breadcrumb::possibility{"+30m"},
4✔
2510
        breadcrumb::possibility{"+1h"},
2511
        breadcrumb::possibility{"+1 day"},
2512
    };
2513

2514
    return retval;
2515
}
2516

2517
static attr_line_t
2518
to_display(const std::shared_ptr<logfile>& lf)
2519
{
2520
    attr_line_t retval;
2521

2522
    if (lf->get_open_options().loo_piper) {
2523
        if (!lf->get_open_options().loo_piper->is_finished()) {
2524
            retval.append("\u21bb "_list_glyph);
52✔
2525
        }
2526
    }
4✔
2527
    retval.append(lf->get_unique_path());
2528

2529
    return retval;
2530
}
11✔
2531

2532
void
11✔
2533
logfile_sub_source::text_crumbs_for_line(int line,
2534
                                         std::vector<breadcrumb::crumb>& crumbs)
11✔
2535
{
×
2536
    text_sub_source::text_crumbs_for_line(line, crumbs);
×
2537

2538
    if (this->lss_filtered_index.empty()) {
2539
        return;
11✔
2540
    }
2541

11✔
2542
    auto vl = vis_line_t(line);
×
2543
    auto bmc = this->get_bookmark_metadata_context(
2544
        vl, bookmark_metadata::categories::partition);
2545
    if (bmc.bmc_current_metadata) {
103✔
2546
        const auto& name = bmc.bmc_current_metadata.value()->bm_name;
2547
        auto key = text_anchors::to_anchor_string(name);
2548
        auto display = attr_line_t()
103✔
2549
                           .append("\u2291 "_symbol)
2550
                           .append(lnav::roles::variable(name))
103✔
2551
                           .move();
96✔
2552
        crumbs.emplace_back(
2553
            key,
2554
            display,
7✔
2555
            [this]() -> std::vector<breadcrumb::possibility> {
7✔
2556
                auto& vb = this->tss_view->get_bookmarks();
2557
                const auto& bv = vb[&textview_curses::BM_PARTITION];
7✔
2558
                std::vector<breadcrumb::possibility> retval;
1✔
2559

1✔
2560
                for (const auto& vl : bv) {
1✔
2561
                    auto meta_opt = this->find_bookmark_metadata(vl);
2✔
2562
                    if (!meta_opt || meta_opt.value()->bm_name.empty()) {
2✔
2563
                        continue;
1✔
2564
                    }
×
2565

2566
                    const auto& name = meta_opt.value()->bm_name;
2567
                    retval.emplace_back(text_anchors::to_anchor_string(name),
2✔
2568
                                        name);
1✔
2569
                }
1✔
2570

1✔
2571
                return retval;
2572
            },
2✔
2573
            [ec = this->lss_exec_context](const auto& part) {
1✔
2574
                ec->execute(fmt::format(FMT_STRING(":goto {}"),
1✔
2575
                                        part.template get<std::string>()));
×
2576
            });
2577
    }
2578

1✔
2579
    auto line_pair_opt = this->find_line_with_file(vl);
1✔
2580
    if (!line_pair_opt) {
2581
        return;
2582
    }
2583
    auto line_pair = line_pair_opt.value();
1✔
2584
    auto& lf = line_pair.first;
×
2585
    auto format = lf->get_format();
1✔
2586
    char ts[64];
×
2587

×
2588
    sql_strftime(ts, sizeof(ts), line_pair.second->get_timeval(), 'T');
×
2589

2590
    crumbs.emplace_back(
×
2591
        std::string(ts),
1✔
2592
        timestamp_poss,
2593
        [ec = this->lss_exec_context](const auto& ts) {
7✔
2594
            ec->execute(fmt::format(FMT_STRING(":goto {}"),
7✔
2595
                                    ts.template get<std::string>()));
×
2596
        });
2597
    crumbs.back().c_expected_input
7✔
2598
        = breadcrumb::crumb::expected_input_t::anything;
7✔
2599
    crumbs.back().c_search_placeholder = "(Enter an absolute or relative time)";
7✔
2600

2601
    auto format_name = format->get_name().to_string();
2602
    crumbs.emplace_back(
7✔
2603
        format_name,
2604
        attr_line_t().append(format_name),
7✔
2605
        [this]() -> std::vector<breadcrumb::possibility> {
14✔
2606
            return this->lss_files
2607
                | lnav::itertools::filter_in([](const auto& file_data) {
7✔
2608
                       return file_data->is_visible();
×
2609
                   })
×
2610
                | lnav::itertools::map(&logfile_data::get_file_ptr)
×
2611
                | lnav::itertools::map(&logfile::get_format_name)
2612
                | lnav::itertools::unique()
×
2613
                | lnav::itertools::map([](const auto& elem) {
7✔
2614
                       return breadcrumb::possibility{
7✔
2615
                           elem.to_string(),
7✔
2616
                       };
2617
                   })
7✔
2618
                | lnav::itertools::to_vector();
7✔
2619
        },
2620
        [ec = this->lss_exec_context](const auto& format_name) {
7✔
2621
            static const std::string MOVE_STMT = R"(;UPDATE lnav_views
8✔
2622
     SET selection = ifnull((SELECT log_line FROM all_logs WHERE log_format = $format_name LIMIT 1), top)
4✔
2623
     WHERE name = 'log'
4✔
2624
)";
4✔
2625

2626
            ec->execute_with(
12✔
2627
                MOVE_STMT,
12✔
2628
                std::make_pair("format_name",
12✔
2629
                               format_name.template get<std::string>()));
16✔
2630
        });
2631

2632
    auto msg_start_iter = lf->message_start(line_pair.second);
4✔
2633
    auto file_line_number = std::distance(lf->begin(), msg_start_iter);
2634
    crumbs.emplace_back(
12✔
2635
        lf->get_unique_path(),
2636
        to_display(lf).appendf(FMT_STRING("[{:L}]"), file_line_number),
7✔
2637
        [this]() -> std::vector<breadcrumb::possibility> {
2638
            return this->lss_files
2639
                | lnav::itertools::filter_in([](const auto& file_data) {
2640
                       return file_data->is_visible();
2641
                   })
2642
                | lnav::itertools::map([](const auto& file_data) {
×
2643
                       return breadcrumb::possibility{
2644
                           file_data->get_file_ptr()->get_unique_path(),
2645
                           to_display(file_data->get_file()),
2646
                       };
×
2647
                   });
2648
        },
7✔
2649
        [ec = this->lss_exec_context](const auto& uniq_path) {
7✔
2650
            static const std::string MOVE_STMT = R"(;UPDATE lnav_views
14✔
2651
     SET selection = ifnull((SELECT log_line FROM all_logs WHERE log_unique_path = $uniq_path LIMIT 1), top)
7✔
2652
     WHERE name = 'log'
28✔
2653
)";
7✔
2654

7✔
2655
            ec->execute_with(
8✔
2656
                MOVE_STMT,
4✔
2657
                std::make_pair("uniq_path",
8✔
2658
                               uniq_path.template get<std::string>()));
4✔
2659
        });
2660

8✔
2661
    logline_value_vector values;
2662
    auto& sbr = values.lvv_sbr;
4✔
2663

2664
    lf->read_full_message(msg_start_iter, sbr);
4✔
2665
    attr_line_t al(to_string(sbr));
8✔
2666
    if (sbr.get_metadata().m_has_ansi) {
2667
        // bleh
7✔
2668
        scrub_ansi_string(al.get_string(), &al.al_attrs);
2669
    }
2670
    format->annotate(lf.get(), file_line_number, al.get_attrs(), values);
2671

2672
    if (values.lvv_opid_value) {
2673
        crumbs.emplace_back(
×
2674
            values.lvv_opid_value.value(),
2675
            attr_line_t().append(
2676
                lnav::roles::identifier(values.lvv_opid_value.value())),
2677
            [this]() -> std::vector<breadcrumb::possibility> {
×
2678
                std::vector<breadcrumb::possibility> retval;
2679

7✔
2680
                for (const auto& file_data : this->lss_files) {
7✔
2681
                    if (file_data->get_file_ptr() == nullptr) {
2682
                        continue;
7✔
2683
                    }
7✔
2684
                    safe::ReadAccess<logfile::safe_opid_state> r_opid_map(
7✔
2685
                        file_data->get_file_ptr()->get_opids());
2686

×
2687
                    for (const auto& pair : r_opid_map->los_opid_ranges) {
2688
                        retval.emplace_back(pair.first.to_string());
7✔
2689
                    }
2690
                }
7✔
2691

2✔
2692
                return retval;
2693
            },
2✔
2694
            [ec = this->lss_exec_context](const auto& opid) {
4✔
2695
                static const std::string MOVE_STMT = R"(;UPDATE lnav_views
4✔
2696
                         SET selection = ifnull((SELECT log_line FROM all_logs WHERE log_opid = $opid LIMIT 1), top)
2✔
2697
                         WHERE name = 'log'
2698
                    )";
4✔
2699

2✔
2700
                ec->execute_with(
×
2701
                    MOVE_STMT,
2702
                    std::make_pair("opid", opid.template get<std::string>()));
2703
            });
2✔
2704
    }
2705

4✔
2706
    auto sf = string_fragment::from_str(al.get_string());
2✔
2707
    auto body_opt = get_string_attr(al.get_attrs(), SA_BODY);
2708
    auto nl_pos_opt = sf.find('\n');
2✔
2709
    auto msg_line_number = std::distance(msg_start_iter, line_pair.second);
2710
    auto line_from_top = line - msg_line_number;
2✔
2711
    if (body_opt && nl_pos_opt) {
×
2712
        if (this->lss_token_meta_line != file_line_number
2✔
2713
            || this->lss_token_meta_size != sf.length())
2714
        {
2715
            if (body_opt->saw_string_attr->sa_range.length() < 128 * 1024) {
2716
                this->lss_token_meta = lnav::document::discover_structure(
2717
                    al, body_opt.value().saw_string_attr->sa_range);
NEW
2718
            } else {
×
2719
                this->lss_token_meta = lnav::document::metadata{};
2720
            }
2721
            this->lss_token_meta_line = file_line_number;
×
2722
            this->lss_token_meta_size = sf.length();
2723
        }
2724

7✔
2725
        const auto initial_size = crumbs.size();
7✔
2726
        auto sf_body
7✔
2727
            = sf.sub_range(body_opt->saw_string_attr->sa_range.lr_start,
7✔
2728
                           body_opt->saw_string_attr->sa_range.lr_end);
7✔
2729
        file_off_t line_offset = 0;
7✔
2730
        file_off_t line_end_offset = sf.length();
10✔
2731
        size_t line_number = 0;
5✔
2732

2733
        for (const auto& sf_line : sf_body.split_lines()) {
5✔
2734
            if (line_number >= msg_line_number) {
5✔
2735
                line_end_offset = line_offset + sf_line.length();
5✔
2736
                break;
UNCOV
2737
            }
×
2738
            line_number += 1;
2739
            line_offset += sf_line.length();
5✔
2740
        }
5✔
2741

2742
        this->lss_token_meta.m_sections_tree.visit_overlapping(
2743
            line_offset,
5✔
2744
            line_end_offset,
2745
            [this,
5✔
2746
             initial_size,
5✔
2747
             meta = &this->lss_token_meta,
5✔
2748
             &crumbs,
5✔
2749
             line_from_top](const auto& iv) {
5✔
2750
                auto path = crumbs | lnav::itertools::skip(initial_size)
2751
                    | lnav::itertools::map(&breadcrumb::crumb::c_key)
9✔
2752
                    | lnav::itertools::append(iv.value);
8✔
2753
                auto curr_node = lnav::document::hier_node::lookup_path(
4✔
2754
                    meta->m_sections_root.get(), path);
4✔
2755

2756
                crumbs.emplace_back(
4✔
2757
                    iv.value,
4✔
2758
                    [meta, path]() { return meta->possibility_provider(path); },
5✔
2759
                    [this, curr_node, path, line_from_top](const auto& key) {
2760
                        if (!curr_node) {
5✔
2761
                            return;
2762
                        }
2763
                        auto* parent_node = curr_node.value()->hn_parent;
2✔
2764
                        if (parent_node == nullptr) {
2765
                            return;
5✔
2766
                        }
2767
                        key.match(
10✔
2768
                            [parent_node](const std::string& str) {
8✔
2769
                                return parent_node->find_line_number(str);
4✔
2770
                            },
2✔
2771
                            [parent_node](size_t index) {
2✔
2772
                                return parent_node->find_line_number(index);
2✔
2773
                            })
2774
                            | [this, line_from_top](auto line_number) {
4✔
2775
                                  this->tss_view->set_selection(
2✔
2776
                                      vis_line_t(line_from_top + line_number));
2✔
2777
                              };
×
2778
                    });
×
2779
                if (curr_node && !curr_node.value()->hn_parent->is_named_only())
×
2780
                {
2781
                    auto node = lnav::document::hier_node::lookup_path(
×
2782
                        meta->m_sections_root.get(), path);
×
2783

×
2784
                    crumbs.back().c_expected_input
2785
                        = curr_node.value()
2786
                              ->hn_parent->hn_named_children.empty()
×
2787
                        ? breadcrumb::crumb::expected_input_t::index
×
2788
                        : breadcrumb::crumb::expected_input_t::index_or_exact;
2789
                    crumbs.back().with_possible_range(
×
2790
                        node | lnav::itertools::map([](const auto hn) {
×
2791
                            return hn->hn_parent->hn_children.size();
2792
                        })
×
2793
                        | lnav::itertools::unwrap_or(size_t{0}));
×
2794
                }
×
2795
            });
2796

2797
        auto path = crumbs | lnav::itertools::skip(initial_size)
2✔
2798
            | lnav::itertools::map(&breadcrumb::crumb::c_key);
2799
        auto node = lnav::document::hier_node::lookup_path(
×
2800
            this->lss_token_meta.m_sections_root.get(), path);
×
2801

2802
        if (node && !node.value()->hn_children.empty()) {
×
2803
            auto poss_provider = [curr_node = node.value()]() {
×
2804
                std::vector<breadcrumb::possibility> retval;
×
2805
                for (const auto& child : curr_node->hn_named_children) {
×
2806
                    retval.emplace_back(child.first);
2807
                }
×
2808
                return retval;
×
2809
            };
×
2810
            auto path_performer
2811
                = [this, curr_node = node.value(), line_from_top](
×
2812
                      const breadcrumb::crumb::key_t& value) {
2813
                      value.match(
2✔
2814
                          [curr_node](const std::string& str) {
2815
                              return curr_node->find_line_number(str);
5✔
2816
                          },
10✔
2817
                          [curr_node](size_t index) {
5✔
2818
                              return curr_node->find_line_number(index);
5✔
2819
                          })
2820
                          | [this, line_from_top](size_t line_number) {
5✔
2821
                                this->tss_view->set_selection(
1✔
2822
                                    vis_line_t(line_from_top + line_number));
1✔
2823
                            };
1✔
2824
                  };
×
2825
            crumbs.emplace_back("", "\u22ef", poss_provider, path_performer);
2826
            crumbs.back().c_expected_input
1✔
2827
                = node.value()->hn_named_children.empty()
2828
                ? breadcrumb::crumb::expected_input_t::index
2829
                : breadcrumb::crumb::expected_input_t::index_or_exact;
2✔
2830
        }
×
2831
    }
×
2832
}
×
2833

×
2834
void
2835
logfile_sub_source::quiesce()
×
2836
{
×
2837
    for (auto& ld : this->lss_files) {
2838
        auto* lf = ld->get_file_ptr();
×
2839

×
2840
        if (lf == nullptr) {
×
2841
            continue;
2842
        }
1✔
2843

1✔
2844
        lf->quiesce();
1✔
2845
    }
2✔
2846
}
1✔
2847

2848
bookmark_metadata&
2849
logfile_sub_source::get_bookmark_metadata(content_line_t cl)
5✔
2850
{
7✔
2851
    auto line_pair = this->find_line_with_file(cl).value();
2852
    auto line_number = static_cast<uint32_t>(
2853
        std::distance(line_pair.first->begin(), line_pair.second));
56✔
2854

2855
    return line_pair.first->get_bookmark_metadata()[line_number];
112✔
2856
}
56✔
2857

2858
logfile_sub_source::bookmark_metadata_context
56✔
2859
logfile_sub_source::get_bookmark_metadata_context(
16✔
2860
    vis_line_t vl, bookmark_metadata::categories desired) const
2861
{
2862
    const auto& vb = this->tss_view->get_bookmarks();
40✔
2863
    const auto bv_iter
2864
        = vb.find(desired == bookmark_metadata::categories::partition
56✔
2865
                      ? &textview_curses::BM_PARTITION
2866
                      : &textview_curses::BM_META);
2867
    if (bv_iter == vb.end()) {
25✔
2868
        return bookmark_metadata_context{};
2869
    }
25✔
2870

2871
    const auto& bv = bv_iter->second;
25✔
2872
    auto vl_iter = std::lower_bound(bv.begin(), bv.end(), vl + 1_vl);
2873

50✔
2874
    std::optional<vis_line_t> next_line;
25✔
2875
    for (auto next_vl_iter = vl_iter; next_vl_iter != bv.end(); ++next_vl_iter)
2876
    {
2877
        auto bm_opt = this->find_bookmark_metadata(*next_vl_iter);
1,147✔
2878
        if (!bm_opt) {
2879
            continue;
2880
        }
1,147✔
2881

2882
        if (bm_opt.value()->has(desired)) {
1,147✔
2883
            next_line = *next_vl_iter;
1,147✔
2884
            break;
2885
        }
1,147✔
2886
    }
1,105✔
2887
    if (vl_iter == bv.begin()) {
2888
        return bookmark_metadata_context{std::nullopt, std::nullopt, next_line};
2889
    }
42✔
2890

42✔
2891
    --vl_iter;
2892
    while (true) {
42✔
2893
        auto bm_opt = this->find_bookmark_metadata(*vl_iter);
42✔
2894
        if (bm_opt) {
2895
            if (bm_opt.value()->has(desired)) {
2✔
2896
                return bookmark_metadata_context{
2✔
2897
                    *vl_iter, bm_opt.value(), next_line};
×
2898
            }
2899
        }
2900

2✔
2901
        if (vl_iter == bv.begin()) {
2✔
2902
            return bookmark_metadata_context{
2✔
2903
                std::nullopt, std::nullopt, next_line};
2904
        }
2905
        --vl_iter;
42✔
2906
    }
34✔
2907
    return bookmark_metadata_context{std::nullopt, std::nullopt, next_line};
2908
}
2909

8✔
2910
std::optional<bookmark_metadata*>
2911
logfile_sub_source::find_bookmark_metadata(content_line_t cl) const
8✔
2912
{
8✔
2913
    auto line_pair = this->find_line_with_file(cl).value();
8✔
2914
    auto line_number = static_cast<uint32_t>(
2915
        std::distance(line_pair.first->begin(), line_pair.second));
8✔
2916

2917
    auto& bm = line_pair.first->get_bookmark_metadata();
2918
    auto bm_iter = bm.find(line_number);
2919
    if (bm_iter == bm.end()) {
×
2920
        return std::nullopt;
2921
    }
×
2922

2923
    return &bm_iter->second;
×
2924
}
2925

2926
void
2927
logfile_sub_source::erase_bookmark_metadata(content_line_t cl)
2928
{
2929
    auto line_pair = this->find_line_with_file(cl).value();
3,303✔
2930
    auto line_number = static_cast<uint32_t>(
2931
        std::distance(line_pair.first->begin(), line_pair.second));
3,303✔
2932

2933
    auto& bm = line_pair.first->get_bookmark_metadata();
3,303✔
2934
    auto bm_iter = bm.find(line_number);
2935
    if (bm_iter != bm.end()) {
3,303✔
2936
        bm.erase(bm_iter);
3,303✔
2937
    }
3,303✔
2938
}
3,152✔
2939

2940
void
2941
logfile_sub_source::clear_bookmark_metadata()
151✔
2942
{
3,303✔
2943
    for (auto& ld : *this) {
2944
        if (ld->get_file_ptr() == nullptr) {
2945
            continue;
25✔
2946
        }
2947

25✔
2948
        ld->get_file_ptr()->get_bookmark_metadata().clear();
2949
    }
25✔
2950
}
2951

25✔
2952
void
25✔
2953
logfile_sub_source::increase_line_context()
25✔
2954
{
6✔
2955
    auto old_flags = this->lss_flags;
2956

25✔
2957
    if (this->lss_flags & F_FILENAME) {
2958
        // Nothing to do
2959
    } else if (this->lss_flags & F_BASENAME) {
4✔
2960
        this->lss_flags &= ~F_NAME_MASK;
2961
        this->lss_flags |= F_FILENAME;
9✔
2962
    } else {
5✔
2963
        this->lss_flags |= F_BASENAME;
×
2964
    }
2965
    if (old_flags != this->lss_flags) {
2966
        this->clear_line_size_cache();
5✔
2967
    }
2968
}
4✔
2969

2970
bool
2971
logfile_sub_source::decrease_line_context()
×
2972
{
2973
    auto old_flags = this->lss_flags;
×
2974

2975
    if (this->lss_flags & F_FILENAME) {
×
2976
        this->lss_flags &= ~F_NAME_MASK;
2977
        this->lss_flags |= F_BASENAME;
×
2978
    } else if (this->lss_flags & F_BASENAME) {
×
2979
        this->lss_flags &= ~F_NAME_MASK;
×
2980
    }
2981
    if (old_flags != this->lss_flags) {
×
2982
        this->clear_line_size_cache();
2983

×
2984
        return true;
×
2985
    }
2986

2987
    return false;
2988
}
2989

×
2990
size_t
2991
logfile_sub_source::get_filename_offset() const
×
2992
{
2993
    if (this->lss_flags & F_FILENAME) {
×
2994
        return this->lss_filename_width;
×
2995
    } else if (this->lss_flags & F_BASENAME) {
×
2996
        return this->lss_basename_width;
×
2997
    }
×
2998

2999
    return 0;
×
3000
}
×
3001

3002
void
×
3003
logfile_sub_source::clear_min_max_log_times()
3004
{
3005
    if (this->lss_min_log_time.tv_sec != 0
×
3006
        || this->lss_min_log_time.tv_usec != 0
3007
        || this->lss_max_log_time.tv_sec != std::numeric_limits<time_t>::max()
3008
        || this->lss_max_log_time.tv_usec != 0)
3009
    {
20✔
3010
        memset(&this->lss_min_log_time, 0, sizeof(this->lss_min_log_time));
3011
        this->lss_max_log_time.tv_sec = std::numeric_limits<time_t>::max();
20✔
3012
        this->lss_max_log_time.tv_usec = 0;
×
3013
        this->text_filters_changed();
20✔
3014
    }
×
3015
}
3016

3017
size_t
20✔
3018
logfile_sub_source::file_count() const
3019
{
3020
    size_t retval = 0;
3021
    const_iterator iter;
1,026✔
3022

3023
    for (iter = this->cbegin(); iter != this->cend(); ++iter) {
2,052✔
3024
        if (*iter != nullptr && (*iter)->get_file() != nullptr) {
1,026✔
3025
            retval += 1;
1,026✔
3026
        }
2,052✔
3027
    }
3028

1✔
3029
    return retval;
1✔
3030
}
1✔
3031

1✔
3032
size_t
3033
logfile_sub_source::text_size_for_line(textview_curses& tc,
1,026✔
3034
                                       int row,
3035
                                       text_sub_source::line_flags_t flags)
3036
{
6✔
3037
    size_t index = row % LINE_SIZE_CACHE_SIZE;
3038

6✔
3039
    if (this->lss_line_size_cache[index].first != row) {
6✔
3040
        std::string value;
3041

6✔
3042
        this->text_value_for_line(tc, row, value, flags);
×
3043
        scrub_ansi_string(value, nullptr);
×
3044
        this->lss_line_size_cache[index].second
3045
            = string_fragment::from_str(value).column_width();
3046
        this->lss_line_size_cache[index].first = row;
3047
    }
6✔
3048
    return this->lss_line_size_cache[index].second;
3049
}
3050

3051
int
×
3052
logfile_sub_source::get_filtered_count_for(size_t filter_index) const
3053
{
3054
    int retval = 0;
3055

×
3056
    for (const auto& ld : this->lss_files) {
3057
        retval += ld->ld_filter_state.lfo_filter_state
×
3058
                      .tfs_filter_hits[filter_index];
×
3059
    }
3060

×
3061
    return retval;
×
3062
}
×
3063

×
3064
std::optional<vis_line_t>
×
3065
logfile_sub_source::row_for(const row_info& ri)
3066
{
×
3067
    auto lb = std::lower_bound(this->lss_filtered_index.begin(),
3068
                               this->lss_filtered_index.end(),
3069
                               ri.ri_time,
3070
                               filtered_logline_cmp(*this));
4✔
3071
    if (lb != this->lss_filtered_index.end()) {
3072
        auto first_lb = lb;
4✔
3073
        while (true) {
3074
            auto cl = this->lss_index[*lb];
5✔
3075
            if (content_line_t(ri.ri_id) == cl) {
1✔
3076
                first_lb = lb;
1✔
3077
                break;
3078
            }
3079
            auto ll = this->find_line(cl);
4✔
3080
            if (ll->get_timeval() != ri.ri_time) {
3081
                break;
3082
            }
3083
            ++lb;
192✔
3084
        }
3085

384✔
3086
        return vis_line_t(first_lb - this->lss_filtered_index.begin());
3087
    }
192✔
3088

3089
    return std::nullopt;
192✔
3090
}
189✔
3091

3092
std::optional<vis_line_t>
195✔
3093
logfile_sub_source::row_for_anchor(const std::string& id)
195✔
3094
{
162✔
3095
    auto& vb = this->tss_view->get_bookmarks();
162✔
3096
    const auto& bv = vb[&textview_curses::BM_PARTITION];
3097

33✔
3098
    for (const auto& vl : bv) {
33✔
3099
        auto meta_opt = this->find_bookmark_metadata(vl);
27✔
3100
        if (!meta_opt || meta_opt.value()->bm_name.empty()) {
3101
            continue;
6✔
3102
        }
6✔
3103

3104
        const auto& name = meta_opt.value()->bm_name;
189✔
3105
        if (id == text_anchors::to_anchor_string(name)) {
3106
            return vl;
3107
        }
3✔
3108
    }
3109

3110
    return std::nullopt;
3111
}
×
3112

3113
std::optional<vis_line_t>
×
3114
logfile_sub_source::adjacent_anchor(vis_line_t vl, text_anchors::direction dir)
×
3115
{
3116
    auto bmc = this->get_bookmark_metadata_context(
×
3117
        vl, bookmark_metadata::categories::partition);
×
3118
    switch (dir) {
×
3119
        case text_anchors::direction::prev: {
×
3120
            if (bmc.bmc_current && bmc.bmc_current.value() != vl) {
3121
                return bmc.bmc_current;
3122
            }
×
3123
            if (!bmc.bmc_current || bmc.bmc_current.value() == 0_vl) {
×
3124
                return 0_vl;
×
3125
            }
3126
            auto prev_bmc = this->get_bookmark_metadata_context(
3127
                bmc.bmc_current.value() - 1_vl,
3128
                bookmark_metadata::categories::partition);
×
3129
            if (!prev_bmc.bmc_current) {
3130
                return 0_vl;
3131
            }
3132
            return prev_bmc.bmc_current;
2✔
3133
        }
3134
        case text_anchors::direction::next:
2✔
3135
            return bmc.bmc_next_line;
3136
    }
2✔
3137
    return std::nullopt;
×
3138
}
×
3139

×
3140
std::optional<std::string>
3141
logfile_sub_source::anchor_for_row(vis_line_t vl)
×
3142
{
×
3143
    auto line_meta = this->get_bookmark_metadata_context(
3144
        vl, bookmark_metadata::categories::partition);
×
3145
    if (!line_meta.bmc_current_metadata) {
×
3146
        return std::nullopt;
3147
    }
×
3148

×
3149
    return text_anchors::to_anchor_string(
3150
        line_meta.bmc_current_metadata.value()->bm_name);
×
3151
}
3152

2✔
3153
std::unordered_set<std::string>
2✔
3154
logfile_sub_source::get_anchors()
3155
{
×
3156
    auto& vb = this->tss_view->get_bookmarks();
3157
    const auto& bv = vb[&textview_curses::BM_PARTITION];
3158
    std::unordered_set<std::string> retval;
3159

4✔
3160
    for (const auto& vl : bv) {
3161
        auto meta_opt = this->find_bookmark_metadata(vl);
4✔
3162
        if (!meta_opt || meta_opt.value()->bm_name.empty()) {
3163
            continue;
4✔
3164
        }
3✔
3165

3166
        const auto& name = meta_opt.value()->bm_name;
3167
        retval.emplace(text_anchors::to_anchor_string(name));
1✔
3168
    }
1✔
3169

3170
    return retval;
3171
}
3172

2✔
3173
bool
3174
logfile_sub_source::text_handle_mouse(
2✔
3175
    textview_curses& tc,
2✔
3176
    const listview_curses::display_line_content_t& mouse_line,
2✔
3177
    mouse_event& me)
3178
{
2✔
3179
    if (tc.get_overlay_selection()) {
×
3180
        if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 2, 4)) {
×
3181
            this->list_input_handle_key(tc, ' ');
×
3182
        } else if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 5, 6)) {
3183
            this->list_input_handle_key(tc, '#');
3184
        }
×
3185
    }
×
3186
    return true;
3187
}
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