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

tstack / lnav / 17589970077-2502

09 Sep 2025 05:00PM UTC coverage: 65.196% (-5.0%) from 70.225%
17589970077-2502

push

github

tstack
[format] add fields for source file/line

Knowing the source file/line context in a log
message can help find log messages when using
log2src.

56 of 70 new or added lines in 2 files covered. (80.0%)

13954 existing lines in 210 files now uncovered.

45516 of 69814 relevant lines covered (65.2%)

404154.37 hits per line

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

53.32
/src/textview_curses.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 <vector>
32

33
#include "textview_curses.hh"
34

35
#include "base/ansi_scrubber.hh"
36
#include "base/humanize.time.hh"
37
#include "base/injector.hh"
38
#include "base/lnav_log.hh"
39
#include "base/time_util.hh"
40
#include "config.h"
41
#include "data_scanner.hh"
42
#include "fmt/format.h"
43
#include "lnav_config.hh"
44
#include "log_format_fwd.hh"
45
#include "logfile.hh"
46
#include "shlex.hh"
47
#include "view_curses.hh"
48

49
constexpr auto REVERSE_SEARCH_OFFSET = 2000_vl;
50

51
void
52
text_filter::revert_to_last(logfile_filter_state& lfs, size_t rollback_size)
44✔
53
{
54
    require(lfs.tfs_lines_for_message[this->lf_index] == 0);
44✔
55

56
    lfs.tfs_message_matched[this->lf_index]
44✔
57
        = lfs.tfs_last_message_matched[this->lf_index];
44✔
58
    lfs.tfs_lines_for_message[this->lf_index]
44✔
59
        = lfs.tfs_last_lines_for_message[this->lf_index];
44✔
60

61
    for (size_t lpc = 0; lpc < lfs.tfs_lines_for_message[this->lf_index]; lpc++)
189✔
62
    {
63
        if (lfs.tfs_message_matched[this->lf_index]) {
145✔
64
            lfs.tfs_filter_hits[this->lf_index] -= 1;
3✔
65
        }
66
        lfs.tfs_filter_count[this->lf_index] -= 1;
145✔
67
        size_t line_number = lfs.tfs_filter_count[this->lf_index];
145✔
68

69
        lfs.tfs_mask[line_number] &= ~(((uint32_t) 1) << this->lf_index);
145✔
70
    }
71
    if (lfs.tfs_lines_for_message[this->lf_index] > 0) {
44✔
72
        require(lfs.tfs_lines_for_message[this->lf_index] >= rollback_size);
44✔
73

74
        lfs.tfs_lines_for_message[this->lf_index] -= rollback_size;
44✔
75
    }
76
    if (lfs.tfs_lines_for_message[this->lf_index] == 0) {
44✔
77
        lfs.tfs_message_matched[this->lf_index] = false;
44✔
78
    }
79
}
44✔
80

81
bool
82
text_filter::add_line(logfile_filter_state& lfs,
1,201✔
83
                      logfile::const_iterator ll,
84
                      const shared_buffer_ref& line)
85
{
86
    if (ll->is_message()) {
1,201✔
87
        this->end_of_message(lfs);
613✔
88
    }
89
    auto retval = this->matches(line_source{*lfs.tfs_logfile, ll}, line);
1,201✔
90

91
    lfs.tfs_message_matched[this->lf_index]
1,201✔
92
        = lfs.tfs_message_matched[this->lf_index] || retval;
1,201✔
93
    lfs.tfs_lines_for_message[this->lf_index] += 1;
1,201✔
94

95
    return retval;
1,201✔
96
}
97

98
void
99
text_filter::end_of_message(logfile_filter_state& lfs)
758✔
100
{
101
    uint32_t mask = 0;
758✔
102

103
    mask = ((uint32_t) 1U << this->lf_index);
758✔
104

105
    for (size_t lpc = 0; lpc < lfs.tfs_lines_for_message[this->lf_index]; lpc++)
1,959✔
106
    {
107
        require(lfs.tfs_filter_count[this->lf_index]
1,201✔
108
                <= lfs.tfs_logfile->size());
109

110
        size_t line_number = lfs.tfs_filter_count[this->lf_index];
1,201✔
111

112
        if (lfs.tfs_message_matched[this->lf_index]) {
1,201✔
113
            lfs.tfs_mask[line_number] |= mask;
90✔
114
        } else {
115
            lfs.tfs_mask[line_number] &= ~mask;
1,111✔
116
        }
117
        lfs.tfs_filter_count[this->lf_index] += 1;
1,201✔
118
        if (lfs.tfs_message_matched[this->lf_index]) {
1,201✔
119
            lfs.tfs_filter_hits[this->lf_index] += 1;
90✔
120
        }
121
    }
122
    lfs.tfs_last_message_matched[this->lf_index]
758✔
123
        = lfs.tfs_message_matched[this->lf_index];
758✔
124
    lfs.tfs_last_lines_for_message[this->lf_index]
758✔
125
        = lfs.tfs_lines_for_message[this->lf_index];
758✔
126
    lfs.tfs_message_matched[this->lf_index] = false;
758✔
127
    lfs.tfs_lines_for_message[this->lf_index] = 0;
758✔
128
}
758✔
129

130
log_accel::direction_t
131
text_accel_source::get_line_accel_direction(vis_line_t vl)
211✔
132
{
133
    log_accel la;
211✔
134

135
    while (vl >= 0) {
1,864✔
136
        const auto* curr_line = this->text_accel_get_line(vl);
1,855✔
137

138
        if (!curr_line->is_message()) {
1,855✔
139
            --vl;
×
UNCOV
140
            continue;
×
141
        }
142

143
        if (!la.add_point(
1,855✔
144
                curr_line->get_time<std::chrono::milliseconds>().count()))
3,710✔
145
        {
146
            break;
202✔
147
        }
148

149
        --vl;
1,653✔
150
    }
151

152
    return la.get_direction();
422✔
153
}
154

155
std::string
156
text_accel_source::get_time_offset_for_line(textview_curses& tc, vis_line_t vl)
211✔
157
{
158
    auto ll = this->text_accel_get_line(vl);
211✔
159
    auto curr_tv = ll->get_timeval();
211✔
160
    struct timeval diff_tv;
161

162
    auto prev_umark = tc.get_bookmarks()[&textview_curses::BM_USER].prev(vl);
211✔
163
    auto next_umark = tc.get_bookmarks()[&textview_curses::BM_USER].next(vl);
211✔
164
    auto prev_emark
165
        = tc.get_bookmarks()[&textview_curses::BM_USER_EXPR].prev(vl);
211✔
166
    auto next_emark
167
        = tc.get_bookmarks()[&textview_curses::BM_USER_EXPR].next(vl);
211✔
168
    if (!prev_umark && !prev_emark && (next_umark || next_emark)) {
211✔
169
        auto next_line = this->text_accel_get_line(
×
UNCOV
170
            std::max(next_umark.value_or(0_vl), next_emark.value_or(0_vl)));
×
171

UNCOV
172
        diff_tv = curr_tv - next_line->get_timeval();
×
173
    } else {
174
        auto prev_row
175
            = std::max(prev_umark.value_or(0_vl), prev_emark.value_or(0_vl));
211✔
176
        auto* first_line = this->text_accel_get_line(prev_row);
211✔
177
        auto start_tv = first_line->get_timeval();
211✔
178
        diff_tv = curr_tv - start_tv;
211✔
179
    }
180

181
    return humanize::time::duration::from_tv(diff_tv).to_string();
211✔
182
}
183

184
const DIST_SLICE(bm_types) bookmark_type_t textview_curses::BM_ERRORS("error");
185
const DIST_SLICE(bm_types)
186
    bookmark_type_t textview_curses::BM_WARNINGS("warning");
187
const DIST_SLICE(bm_types) bookmark_type_t textview_curses::BM_USER("user");
188
const DIST_SLICE(bm_types)
189
    bookmark_type_t textview_curses::BM_USER_EXPR("user-expr");
190
const DIST_SLICE(bm_types) bookmark_type_t textview_curses::BM_SEARCH("search");
191
const DIST_SLICE(bm_types) bookmark_type_t textview_curses::BM_META("meta");
192
const DIST_SLICE(bm_types)
193
    bookmark_type_t textview_curses::BM_PARTITION("partition");
194

195
textview_curses::textview_curses()
20,785✔
196
    : lnav_config_listener(__FILE__), tc_search_action(noop_func{})
20,785✔
197
{
198
    this->set_data_source(this);
20,785✔
199
}
20,785✔
200

201
textview_curses::~textview_curses()
20,785✔
202
{
203
    this->tc_search_action = noop_func{};
20,785✔
204
}
20,785✔
205

206
void
207
textview_curses::reload_config(error_reporter& reporter)
11,575✔
208
{
209
    const static auto DEFAULT_THEME_NAME = std::string("default");
12,745✔
210
    const auto& vc = view_colors::singleton();
11,575✔
211

212
    for (auto iter = this->tc_highlights.begin();
11,575✔
213
         iter != this->tc_highlights.end();)
14,337✔
214
    {
215
        if (iter->first.first != highlight_source_t::THEME) {
2,762✔
216
            ++iter;
1,034✔
217
            continue;
1,034✔
218
        }
219

220
        iter = this->tc_highlights.erase(iter);
1,728✔
221
    }
222

223
    for (const auto& theme_name : {DEFAULT_THEME_NAME, lnav_config.lc_ui_theme})
57,875✔
224
    {
225
        auto theme_iter = lnav_config.lc_ui_theme_defs.find(theme_name);
23,150✔
226

227
        if (theme_iter == lnav_config.lc_ui_theme_defs.end()) {
23,150✔
228
            continue;
40✔
229
        }
230

231
        auto vars = &theme_iter->second.lt_vars;
23,110✔
232
        for (const auto& hl_pair : theme_iter->second.lt_highlights) {
115,550✔
233
            if (hl_pair.second.hc_regex.pp_value == nullptr) {
92,440✔
UNCOV
234
                continue;
×
235
            }
236

237
            const auto& sc = hl_pair.second.hc_style;
92,440✔
238
            std::string fg_color, bg_color, errmsg;
92,440✔
239
            bool invalid = false;
92,440✔
240
            text_attrs attrs;
92,440✔
241

242
            auto fg1 = sc.sc_color;
92,440✔
243
            auto bg1 = sc.sc_background_color;
92,440✔
244
            shlex(fg1).eval(fg_color, scoped_resolver{vars});
92,440✔
245
            shlex(bg1).eval(bg_color, scoped_resolver{vars});
92,440✔
246

247
            attrs.ta_fg_color = vc.match_color(
184,880✔
248
                styling::color_unit::from_str(fg_color).unwrapOrElse(
184,880✔
UNCOV
249
                    [&](const auto& msg) {
×
UNCOV
250
                        reporter(&sc.sc_color,
×
251
                                 lnav::console::user_message::error(
UNCOV
252
                                     attr_line_t("invalid color -- ")
×
UNCOV
253
                                         .append_quoted(sc.sc_color))
×
UNCOV
254
                                     .with_reason(msg));
×
UNCOV
255
                        invalid = true;
×
UNCOV
256
                        return styling::color_unit::EMPTY;
×
257
                    }));
92,440✔
258
            attrs.ta_bg_color = vc.match_color(
184,880✔
259
                styling::color_unit::from_str(bg_color).unwrapOrElse(
184,880✔
260
                    [&](const auto& msg) {
×
UNCOV
261
                        reporter(&sc.sc_background_color,
×
262
                                 lnav::console::user_message::error(
263
                                     attr_line_t("invalid background color -- ")
×
264
                                         .append_quoted(sc.sc_background_color))
×
265
                                     .with_reason(msg));
×
266
                        invalid = true;
×
UNCOV
267
                        return styling::color_unit::EMPTY;
×
268
                    }));
92,440✔
269
            if (invalid) {
92,440✔
270
                continue;
×
271
            }
272

273
            if (sc.sc_bold) {
92,440✔
274
                attrs |= text_attrs::style::bold;
×
275
            }
276
            if (sc.sc_underline) {
92,440✔
UNCOV
277
                attrs |= text_attrs::style::underline;
×
278
            }
279
            this->tc_highlights[{highlight_source_t::THEME, hl_pair.first}]
184,880✔
280
                = highlighter(hl_pair.second.hc_regex.pp_value)
92,440✔
281
                      .with_attrs(attrs)
184,880✔
282
                      .with_nestable(false);
184,880✔
283
        }
92,440✔
284
    }
34,725✔
285

286
    if (this->tc_reload_config_delegate) {
11,575✔
287
        this->tc_reload_config_delegate(*this);
132✔
288
    }
289
}
11,575✔
290

291
void
292
textview_curses::invoke_scroll()
41,825✔
293
{
294
    this->tc_selected_text = std::nullopt;
41,825✔
295
    if (this->tc_sub_source != nullptr) {
41,825✔
296
        this->tc_sub_source->scroll_invoked(this);
21,040✔
297
    }
298

299
    listview_curses::invoke_scroll();
41,825✔
300
}
41,825✔
301

302
void
303
textview_curses::reload_data()
44,851✔
304
{
305
    this->tc_selected_text = std::nullopt;
44,851✔
306
    if (this->tc_sub_source != nullptr) {
44,851✔
307
        this->tc_sub_source->text_update_marks(this->tc_bookmarks);
20,177✔
308

309
        auto* ttt = dynamic_cast<text_time_translator*>(this->tc_sub_source);
20,177✔
310

311
        if (ttt != nullptr) {
20,177✔
312
            ttt->data_reloaded(this);
14,475✔
313
        }
314
        listview_curses::reload_data();
20,177✔
315
    }
316
}
44,851✔
317

318
void
319
textview_curses::grep_begin(grep_proc<vis_line_t>& gp,
64✔
320
                            vis_line_t start,
321
                            vis_line_t stop)
322
{
323
    require(this->tc_searching >= 0);
64✔
324

325
    this->tc_searching += 1;
64✔
326
    this->tc_search_action(this);
64✔
327

328
    if (start != -1_vl) {
64✔
329
        auto& search_bv = this->tc_bookmarks[&BM_SEARCH];
52✔
330
        auto pair = search_bv.equal_range(start, stop);
52✔
331

332
        if (pair.first != pair.second) {
52✔
333
            this->set_needs_update();
1✔
334
        }
335
        for (auto mark_iter = pair.first; mark_iter != pair.second; ++mark_iter)
53✔
336
        {
337
            if (this->tc_sub_source) {
1✔
338
                this->tc_sub_source->text_mark(&BM_SEARCH, *mark_iter, false);
1✔
339
            }
340
        }
341
        if (pair.first != pair.second) {
52✔
342
            auto to_del = std::vector<vis_line_t>{};
1✔
343
            for (auto file_iter = pair.first; file_iter != pair.second;
2✔
344
                 ++file_iter)
1✔
345
            {
346
                to_del.emplace_back(*file_iter);
1✔
347
            }
348

349
            for (auto cl : to_del) {
2✔
350
                search_bv.bv_tree.erase(cl);
1✔
351
            }
352
        }
1✔
353
    }
354

355
    listview_curses::reload_data();
64✔
356
}
64✔
357

358
void
359
textview_curses::grep_end_batch(grep_proc<vis_line_t>& gp)
69✔
360
{
361
    if (this->tc_follow_deadline.tv_sec
138✔
362
        && this->tc_follow_selection == this->get_selection())
69✔
363
    {
364
        timeval now;
365

UNCOV
366
        gettimeofday(&now, nullptr);
×
UNCOV
367
        if (this->tc_follow_deadline < now) {
×
368
        } else {
369
            if (this->tc_follow_func) {
×
UNCOV
370
                if (this->tc_follow_func()) {
×
371
                    this->tc_follow_deadline = {0, 0};
×
372
                }
373
            } else {
UNCOV
374
                this->tc_follow_deadline = {0, 0};
×
375
            }
376
        }
377
    }
378
    this->tc_search_action(this);
69✔
379
}
69✔
380

381
void
382
textview_curses::grep_end(grep_proc<vis_line_t>& gp)
55✔
383
{
384
    this->tc_searching -= 1;
55✔
385
    this->grep_end_batch(gp);
55✔
386
    if (this->tc_searching == 0 && this->tc_search_start_time) {
55✔
387
        const auto now = std::chrono::steady_clock::now();
9✔
388
        this->tc_search_duration
389
            = std::chrono::duration_cast<std::chrono::milliseconds>(
9✔
390
                now - this->tc_search_start_time.value());
18✔
391
        this->tc_search_start_time = std::nullopt;
9✔
392
        if (this->tc_state_event_handler) {
9✔
UNCOV
393
            this->tc_state_event_handler(*this);
×
394
        }
395
    }
396

397
    ensure(this->tc_searching >= 0);
55✔
398
}
55✔
399

400
void
401
textview_curses::grep_match(grep_proc<vis_line_t>& gp, vis_line_t line)
16✔
402
{
403
    this->tc_bookmarks[&BM_SEARCH].insert_once(vis_line_t(line));
16✔
404
    if (this->tc_sub_source != nullptr) {
16✔
405
        this->tc_sub_source->text_mark(&BM_SEARCH, line, true);
16✔
406
    }
407

408
    if (this->get_top() <= line && line <= this->get_bottom()) {
16✔
409
        listview_curses::reload_data();
16✔
410
    }
411
}
16✔
412

413
void
414
textview_curses::listview_value_for_rows(const listview_curses& lv,
12,434✔
415
                                         vis_line_t row,
416
                                         std::vector<attr_line_t>& rows_out)
417
{
418
    for (auto& al : rows_out) {
24,880✔
419
        this->textview_value_for_row(row, al);
12,446✔
420
        ++row;
12,446✔
421
    }
422
}
12,434✔
423

424
bool
425
textview_curses::handle_mouse(mouse_event& me)
×
426
{
UNCOV
427
    if (!this->vc_visible || this->lv_height == 0) {
×
UNCOV
428
        return false;
×
429
    }
430

UNCOV
431
    if (!this->tc_selection_start && listview_curses::handle_mouse(me)) {
×
UNCOV
432
        return true;
×
433
    }
434

435
    auto mouse_line = (me.me_y < 0 || me.me_y >= this->lv_display_lines.size())
×
436
        ? empty_space{}
×
UNCOV
437
        : this->lv_display_lines[me.me_y];
×
438
    auto [height, width] = this->get_dimensions();
×
439

440
    if (!mouse_line.is<overlay_menu>()
×
UNCOV
441
        && (me.me_button != mouse_button_t::BUTTON_LEFT
×
442
            || me.me_state != mouse_button_state_t::BUTTON_STATE_RELEASED))
×
443
    {
UNCOV
444
        this->tc_selected_text = std::nullopt;
×
UNCOV
445
        this->set_needs_update();
×
446
    }
447

448
    std::optional<int> overlay_content_min_y;
×
UNCOV
449
    std::optional<int> overlay_content_max_y;
×
450
    if (this->tc_press_line.is<overlay_content>()) {
×
451
        auto main_line
452
            = this->tc_press_line.get<overlay_content>().oc_main_line;
×
453
        for (size_t lpc = 0; lpc < this->lv_display_lines.size(); lpc++) {
×
454
            if (overlay_content_min_y
×
UNCOV
455
                && !this->lv_display_lines[lpc].is<static_overlay_content>()
×
456
                && !this->lv_display_lines[lpc].is<overlay_content>())
×
457
            {
UNCOV
458
                overlay_content_max_y = lpc;
×
459
                break;
×
460
            }
461
            if (this->lv_display_lines[lpc].is<main_content>()) {
×
462
                auto& mc = this->lv_display_lines[lpc].get<main_content>();
×
UNCOV
463
                if (mc.mc_line == main_line) {
×
UNCOV
464
                    overlay_content_min_y = lpc;
×
465
                }
466
            }
467
        }
UNCOV
468
        if (overlay_content_min_y && !overlay_content_max_y) {
×
UNCOV
469
            overlay_content_max_y = this->lv_display_lines.size();
×
470
        }
471
    }
472

473
    auto* sub_delegate = dynamic_cast<text_delegate*>(this->tc_sub_source);
×
474

475
    switch (me.me_state) {
×
476
        case mouse_button_state_t::BUTTON_STATE_PRESSED: {
×
477
            this->tc_selection_at_press = this->get_selection();
×
478
            this->tc_press_line = mouse_line;
×
UNCOV
479
            this->tc_press_left = this->lv_left + me.me_press_x;
×
480
            if (!this->lv_selectable) {
×
481
                this->set_selectable(true);
×
482
            }
483
            mouse_line.match(
×
484
                [this, &me, sub_delegate, &mouse_line](const main_content& mc) {
×
485
                    this->tc_text_selection_active = true;
×
486
                    this->tc_press_left = this->lv_left
×
UNCOV
487
                        + mc.mc_line_range.lr_start + me.me_press_x;
×
488
                    if (this->vc_enabled) {
×
UNCOV
489
                        if (this->tc_supports_marks
×
UNCOV
490
                            && me.me_button == mouse_button_t::BUTTON_LEFT
×
491
                            && (me.is_modifier_pressed(
×
492
                                    mouse_event::modifier_t::shift)
493
                                || me.is_modifier_pressed(
×
494
                                    mouse_event::modifier_t::ctrl)))
495
                        {
496
                            this->tc_selection_start = mc.mc_line;
×
497
                        }
UNCOV
498
                        this->set_selection_without_context(mc.mc_line);
×
UNCOV
499
                        this->tc_press_event = me;
×
500
                    }
501
                    if (this->tc_delegate != nullptr) {
×
UNCOV
502
                        this->tc_delegate->text_handle_mouse(
×
503
                            *this, mouse_line, me);
504
                    }
505
                    if (sub_delegate != nullptr) {
×
506
                        sub_delegate->text_handle_mouse(*this, mouse_line, me);
×
507
                    }
508
                },
×
509
                [](const overlay_menu& om) {},
×
510
                [](const static_overlay_content& soc) {},
×
UNCOV
511
                [this](const overlay_content& oc) {
×
512
                    this->set_selection(oc.oc_main_line);
×
513
                    this->set_overlay_selection(oc.oc_line);
×
514
                },
×
UNCOV
515
                [](const empty_space& es) {});
×
516
            break;
×
517
        }
518
        case mouse_button_state_t::BUTTON_STATE_DOUBLE_CLICK: {
×
519
            if (!this->lv_selectable) {
×
520
                this->set_selectable(true);
×
521
            }
UNCOV
522
            this->tc_text_selection_active = false;
×
523
            mouse_line.match(
×
UNCOV
524
                [this, &me, &mouse_line, sub_delegate](const main_content& mc) {
×
525
                    if (this->vc_enabled) {
×
UNCOV
526
                        if (this->tc_supports_marks
×
527
                            && me.me_button == mouse_button_t::BUTTON_LEFT)
×
528
                        {
529
                            attr_line_t al;
×
530

531
                            this->textview_value_for_row(mc.mc_line, al);
×
532
                            auto line_sf
UNCOV
533
                                = string_fragment::from_str(al.get_string());
×
534
                            auto cursor_sf = line_sf.sub_cell_range(
×
535
                                this->lv_left + mc.mc_line_range.lr_start
×
536
                                    + me.me_x,
×
UNCOV
537
                                this->lv_left + mc.mc_line_range.lr_start
×
UNCOV
538
                                    + me.me_x);
×
539
                            auto ds = data_scanner(line_sf);
×
UNCOV
540
                            auto tf = this->tc_sub_source->get_text_format();
×
541
                            while (true) {
UNCOV
542
                                auto tok_res = ds.tokenize2(tf);
×
543
                                if (!tok_res) {
×
544
                                    break;
×
545
                                }
546

547
                                auto tok = tok_res.value();
×
548
                                auto tok_sf
549
                                    = (tok.tr_token
×
550
                                           == data_token_t::DT_QUOTED_STRING
551
                                       && (cursor_sf.sf_begin
×
552
                                               == tok.to_string_fragment()
×
553
                                                      .sf_begin
×
UNCOV
554
                                           || cursor_sf.sf_begin
×
UNCOV
555
                                               == tok.to_string_fragment()
×
556
                                                       .sf_end
×
557
                                                   - 1))
×
558
                                    ? tok.to_string_fragment()
×
559
                                    : tok.inner_string_fragment();
×
UNCOV
560
                                if (tok_sf.contains(cursor_sf)
×
561
                                    && tok.tr_token != data_token_t::DT_WHITE)
×
562
                                {
563
                                    auto group_tok
UNCOV
564
                                        = ds.find_matching_bracket(tf, tok);
×
UNCOV
565
                                    if (group_tok) {
×
UNCOV
566
                                        tok_sf = group_tok.value()
×
UNCOV
567
                                                     .to_string_fragment();
×
568
                                    }
UNCOV
569
                                    this->tc_selected_text = selected_text_info{
×
570
                                        me.me_x,
×
571
                                        mc.mc_line,
×
572
                                        line_range{
573
                                            tok_sf.sf_begin,
574
                                            tok_sf.sf_end,
575
                                        },
576
                                        al.al_attrs,
577
                                        tok_sf.to_string(),
578
                                    };
UNCOV
579
                                    this->set_needs_update();
×
UNCOV
580
                                    break;
×
581
                                }
582
                            }
583
                        }
584
                        this->set_selection_without_context(mc.mc_line);
×
585
                    }
586
                    if (this->tc_delegate != nullptr) {
×
587
                        this->tc_delegate->text_handle_mouse(
×
588
                            *this, mouse_line, me);
589
                    }
UNCOV
590
                    if (sub_delegate != nullptr) {
×
591
                        sub_delegate->text_handle_mouse(*this, mouse_line, me);
×
592
                    }
593
                },
×
594
                [](const static_overlay_content& soc) {},
×
595
                [](const overlay_menu& om) {},
×
596
                [](const overlay_content& oc) {},
×
597
                [](const empty_space& es) {});
×
598
            break;
×
599
        }
600
        case mouse_button_state_t::BUTTON_STATE_DRAGGED: {
×
601
            this->tc_text_selection_active = true;
×
UNCOV
602
            if (!this->vc_enabled) {
×
603
            } else if (me.me_y == me.me_press_y) {
×
604
                if (mouse_line.is<main_content>()) {
×
605
                    auto& mc = mouse_line.get<main_content>();
×
UNCOV
606
                    attr_line_t al;
×
607
                    auto low_x
UNCOV
608
                        = std::min(this->tc_press_left,
×
609
                                   (int) this->lv_left
×
610
                                       + mc.mc_line_range.lr_start + me.me_x);
×
611
                    auto high_x
612
                        = std::max(this->tc_press_left,
×
613
                                   (int) this->lv_left
×
614
                                       + mc.mc_line_range.lr_start + me.me_x);
×
615

616
                    this->set_selection_without_context(mc.mc_line);
×
UNCOV
617
                    if (this->tc_supports_marks
×
618
                        && me.me_button == mouse_button_t::BUTTON_LEFT)
×
619
                    {
UNCOV
620
                        this->textview_value_for_row(mc.mc_line, al);
×
621
                        auto line_sf
UNCOV
622
                            = string_fragment::from_str(al.get_string());
×
UNCOV
623
                        auto cursor_sf = line_sf.sub_cell_range(low_x, high_x);
×
UNCOV
624
                        if (me.me_x <= 1) {
×
UNCOV
625
                            this->set_left(this->lv_left - 1);
×
UNCOV
626
                        } else if (me.me_x >= width - 1) {
×
UNCOV
627
                            this->set_left(this->lv_left + 1);
×
628
                        }
UNCOV
629
                        if (!cursor_sf.empty()) {
×
630
                            this->tc_selected_text = {
631
                                me.me_x,
×
632
                                mc.mc_line,
×
633
                                line_range{
634
                                    cursor_sf.sf_begin,
635
                                    cursor_sf.sf_end,
636
                                },
637
                                al.al_attrs,
638
                                cursor_sf.to_string(),
639
                            };
640
                        }
641
                    }
642
                }
643
            } else {
644
                if (this->tc_press_line.is<main_content>()) {
×
645
                    if (me.me_y < 0) {
×
646
                        this->shift_selection(shift_amount_t::up_line);
×
647
                    } else if (me.me_y >= height) {
×
648
                        this->shift_selection(shift_amount_t::down_line);
×
649
                    } else if (mouse_line.is<main_content>()) {
×
650
                        this->set_selection_without_context(
×
651
                            mouse_line.get<main_content>().mc_line);
×
652
                    }
UNCOV
653
                } else if (this->tc_press_line.is<overlay_content>()
×
UNCOV
654
                           && overlay_content_min_y && overlay_content_max_y)
×
655
                {
656
                    if (me.me_y < overlay_content_min_y.value()) {
×
UNCOV
657
                        this->set_overlay_selection(
×
658
                            this->get_overlay_selection().value_or(0_vl)
×
659
                            - 1_vl);
×
660
                    } else if (me.me_y >= overlay_content_max_y.value()) {
×
661
                        this->set_overlay_selection(
×
UNCOV
662
                            this->get_overlay_selection().value_or(0_vl)
×
663
                            + 1_vl);
×
664
                    } else if (mouse_line.is<overlay_content>()) {
×
UNCOV
665
                        this->set_overlay_selection(
×
666
                            mouse_line.get<overlay_content>().oc_line);
×
667
                    }
668
                }
669
            }
UNCOV
670
            break;
×
671
        }
672
        case mouse_button_state_t::BUTTON_STATE_RELEASED: {
×
UNCOV
673
            auto* ov = this->get_overlay_source();
×
UNCOV
674
            if (ov != nullptr && mouse_line.is<overlay_menu>()
×
UNCOV
675
                && this->tc_selected_text)
×
676
            {
677
                auto& om = mouse_line.get<overlay_menu>();
×
678
                auto& sti = this->tc_selected_text.value();
×
679

680
                for (const auto& mi : ov->los_menu_items) {
×
681
                    if (om.om_line == mi.mi_line
×
UNCOV
682
                        && me.is_click_in(mouse_button_t::BUTTON_LEFT,
×
683
                                          mi.mi_range))
684
                    {
685
                        mi.mi_action(sti.sti_value);
×
686
                        break;
×
687
                    }
688
                }
689
            }
UNCOV
690
            this->tc_text_selection_active = false;
×
691
            if (this->tc_press_line.is<main_content>()
×
UNCOV
692
                && mouse_line.is<main_content>()
×
693
                && me.is_click_in(mouse_button_t::BUTTON_RIGHT, 0, INT_MAX))
×
694
            {
UNCOV
695
                auto* lov = this->get_overlay_source();
×
696
                if (lov != nullptr
×
697
                    && (!lov->get_show_details_in_overlay()
×
UNCOV
698
                        || this->tc_selection_at_press
×
699
                            == this->get_selection()))
×
700
                {
701
                    this->set_show_details_in_overlay(
×
702
                        !lov->get_show_details_in_overlay());
×
703
                }
704
            }
705
            if (this->vc_enabled) {
×
706
                if (this->tc_selection_start) {
×
UNCOV
707
                    this->toggle_user_mark(&BM_USER,
×
708
                                           this->tc_selection_start.value(),
×
709
                                           this->get_selection().value());
×
UNCOV
710
                    this->reload_data();
×
711
                }
712
                this->tc_selection_start = std::nullopt;
×
713
            }
UNCOV
714
            if (mouse_line.is<main_content>()) {
×
UNCOV
715
                const auto mc = mouse_line.get<main_content>();
×
UNCOV
716
                attr_line_t al;
×
717

718
                this->textview_value_for_row(mc.mc_line, al);
×
UNCOV
719
                auto line_sf = string_fragment::from_str(al.get_string());
×
720
                auto cursor_sf = line_sf.sub_cell_range(
×
721
                    this->lv_left + me.me_x, this->lv_left + me.me_x);
×
UNCOV
722
                auto link_iter = find_string_attr_containing(
×
723
                    al.get_attrs(), &VC_HYPERLINK, cursor_sf.sf_begin);
×
724
                if (link_iter != al.get_attrs().end()) {
×
725
                    auto href = link_iter->sa_value.get<std::string>();
×
UNCOV
726
                    auto* ta = dynamic_cast<text_anchors*>(this->tc_sub_source);
×
727

UNCOV
728
                    if (me.me_button == mouse_button_t::BUTTON_LEFT
×
UNCOV
729
                        && ta != nullptr && startswith(href, "#")
×
UNCOV
730
                        && !startswith(href, "#/frontmatter"))
×
731
                    {
UNCOV
732
                        auto row_opt = ta->row_for_anchor(href);
×
733

UNCOV
734
                        if (row_opt.has_value()) {
×
UNCOV
735
                            this->set_selection(row_opt.value());
×
736
                        }
737
                    } else {
UNCOV
738
                        this->tc_selected_text = selected_text_info{
×
UNCOV
739
                            me.me_x,
×
UNCOV
740
                            mc.mc_line,
×
UNCOV
741
                            link_iter->sa_range,
×
UNCOV
742
                            al.get_attrs(),
×
UNCOV
743
                            al.to_string_fragment(link_iter).to_string(),
×
744
                            href,
745
                        };
746
                    }
747
                }
UNCOV
748
                if (this->tc_on_click) {
×
UNCOV
749
                    this->tc_on_click(*this, al, cursor_sf.sf_begin);
×
750
                }
751
            }
UNCOV
752
            if (mouse_line.is<overlay_content>()) {
×
UNCOV
753
                const auto& oc = mouse_line.get<overlay_content>();
×
UNCOV
754
                std::vector<attr_line_t> ov_lines;
×
755

UNCOV
756
                this->lv_overlay_source->list_value_for_overlay(
×
757
                    *this, oc.oc_main_line, ov_lines);
UNCOV
758
                const auto& al = ov_lines[oc.oc_line];
×
UNCOV
759
                auto line_sf = string_fragment::from_str(al.get_string());
×
UNCOV
760
                auto cursor_sf = line_sf.sub_cell_range(
×
UNCOV
761
                    this->lv_left + me.me_x, this->lv_left + me.me_x);
×
UNCOV
762
                auto link_iter = find_string_attr_containing(
×
763
                    al.get_attrs(), &VC_HYPERLINK, cursor_sf.sf_begin);
UNCOV
764
                if (link_iter != al.get_attrs().end()) {
×
UNCOV
765
                    auto href = link_iter->sa_value.get<std::string>();
×
UNCOV
766
                    auto* ta = dynamic_cast<text_anchors*>(this->tc_sub_source);
×
767

UNCOV
768
                    if (me.me_button == mouse_button_t::BUTTON_LEFT
×
UNCOV
769
                        && ta != nullptr && startswith(href, "#")
×
UNCOV
770
                        && !startswith(href, "#/frontmatter"))
×
771
                    {
UNCOV
772
                        auto row_opt = ta->row_for_anchor(href);
×
773

UNCOV
774
                        if (row_opt.has_value()) {
×
UNCOV
775
                            this->tc_sub_source->get_location_history() |
×
776
                                [&oc](auto lh) {
×
777
                                    lh->loc_history_append(oc.oc_main_line);
×
778
                                };
779
                            this->set_selection(row_opt.value());
×
780
                        }
781
                    }
782
                }
UNCOV
783
                if (this->tc_on_click) {
×
UNCOV
784
                    this->tc_on_click(*this, al, cursor_sf.sf_begin);
×
785
                }
786
            }
UNCOV
787
            if (this->tc_delegate != nullptr) {
×
UNCOV
788
                this->tc_delegate->text_handle_mouse(*this, mouse_line, me);
×
789
            }
UNCOV
790
            if (sub_delegate != nullptr) {
×
UNCOV
791
                sub_delegate->text_handle_mouse(*this, mouse_line, me);
×
792
            }
UNCOV
793
            if (mouse_line.is<overlay_menu>()) {
×
UNCOV
794
                this->tc_selected_text = std::nullopt;
×
UNCOV
795
                this->set_needs_update();
×
796
            }
UNCOV
797
            break;
×
798
        }
799
    }
800

UNCOV
801
    return true;
×
802
}
803

804
void
805
textview_curses::apply_highlights(attr_line_t& al,
11,663✔
806
                                  const line_range& body,
807
                                  const line_range& orig_line)
808
{
809
    intern_string_t format_name;
11,663✔
810

811
    auto format_attr_opt = get_string_attr(al.al_attrs, SA_FORMAT);
11,663✔
812
    if (format_attr_opt.has_value()) {
11,663✔
813
        format_name = format_attr_opt.value().get();
2,849✔
814
    }
815

816
    auto source_format = this->tc_sub_source->get_text_format();
11,663✔
817
    if (source_format == text_format_t::TF_BINARY) {
11,663✔
818
        return;
1,430✔
819
    }
820
    for (const auto& tc_highlight : this->tc_highlights) {
111,573✔
821
        bool internal_hl
101,340✔
822
            = tc_highlight.first.first == highlight_source_t::INTERNAL
101,340✔
823
            || tc_highlight.first.first == highlight_source_t::THEME;
101,340✔
824

825
        if (!tc_highlight.second.applies_to_format(source_format)) {
101,340✔
826
            continue;
34,591✔
827
        }
828

829
        if (!tc_highlight.second.h_format_name.empty()
66,749✔
830
            && tc_highlight.second.h_format_name != format_name)
66,749✔
831
        {
UNCOV
832
            continue;
×
833
        }
834

835
        if (this->tc_disabled_highlights.count(tc_highlight.first.first)) {
66,749✔
UNCOV
836
            continue;
×
837
        }
838

839
        // Internal highlights should only apply to the log message body so
840
        // that we don't start highlighting other fields.  User-provided
841
        // highlights should apply only to the line itself and not any of
842
        // the surrounding decorations that are added (for example, the file
843
        // lines that are inserted at the beginning of the log view).
844
        auto lr = internal_hl ? body : orig_line;
66,749✔
845
        tc_highlight.second.annotate(al, lr);
66,749✔
846
    }
847
}
848

849
void
850
textview_curses::textview_value_for_row(vis_line_t row, attr_line_t& value_out)
12,446✔
851
{
852
    auto& sa = value_out.get_attrs();
12,446✔
853
    auto& str = value_out.get_string();
12,446✔
854

855
    this->tc_sub_source->text_value_for_line(*this, row, str);
12,446✔
856
    this->tc_sub_source->text_attrs_for_line(*this, row, sa);
12,446✔
857

858
    for (const auto& attr : sa) {
95,318✔
859
        require_ge(attr.sa_range.lr_start, 0);
82,872✔
860
    }
861

862
    line_range body, orig_line;
12,446✔
863

864
    body = find_string_attr_range(sa, &SA_BODY);
12,446✔
865
    if (!body.is_valid()) {
12,446✔
866
        body.lr_start = 0;
9,556✔
867
        body.lr_end = str.size();
9,556✔
868
    }
869

870
    orig_line = find_string_attr_range(sa, &SA_ORIGINAL_LINE);
12,446✔
871
    if (!orig_line.is_valid()) {
12,446✔
872
        orig_line.lr_start = 0;
8,974✔
873
        orig_line.lr_end = str.size();
8,974✔
874
    }
875

876
    if (this->is_selectable() && this->tc_cursor_role
12,749✔
877
        && this->tc_disabled_cursor_role)
12,749✔
878
    {
UNCOV
879
        vis_line_t sel_start, sel_end;
×
880

UNCOV
881
        sel_start = sel_end = this->get_selection().value_or(0_vl);
×
882
        if (this->tc_selection_start) {
×
UNCOV
883
            if (this->tc_selection_start.value() < sel_end) {
×
UNCOV
884
                sel_start = this->tc_selection_start.value();
×
885
            } else {
UNCOV
886
                sel_end = this->tc_selection_start.value();
×
887
            }
888
        }
889

UNCOV
890
        if (sel_start <= row && row <= sel_end) {
×
UNCOV
891
            auto role = (this->get_overlay_selection() || !this->vc_enabled)
×
UNCOV
892
                ? this->tc_disabled_cursor_role.value()
×
UNCOV
893
                : this->tc_cursor_role.value();
×
894

UNCOV
895
            sa.emplace_back(line_range{0, -1}, VC_ROLE.value(role));
×
896
        }
897
    }
898

899
    if (!body.empty() || !orig_line.empty()) {
12,446✔
900
        this->apply_highlights(value_out, body, orig_line);
10,652✔
901
    }
902

903
    if (this->tc_hide_fields) {
12,446✔
904
        value_out.apply_hide();
12,446✔
905
    }
906

907
    const auto& user_marks = this->tc_bookmarks[&BM_USER];
12,446✔
908
    const auto& user_expr_marks = this->tc_bookmarks[&BM_USER_EXPR];
12,446✔
909
    if (user_marks.bv_tree.exists(row) || user_expr_marks.bv_tree.exists(row)) {
12,446✔
910
        sa.emplace_back(line_range{orig_line.lr_start, -1},
17✔
911
                        VC_STYLE.value(text_attrs::with_reverse()));
34✔
912
    }
913

914
    if (this->tc_selected_text) {
12,446✔
UNCOV
915
        const auto& sti = this->tc_selected_text.value();
×
UNCOV
916
        if (sti.sti_line == row) {
×
UNCOV
917
            sa.emplace_back(sti.sti_range,
×
UNCOV
918
                            VC_ROLE.value(role_t::VCR_SELECTED_TEXT));
×
919
        }
920
    }
921
}
12,446✔
922

923
void
924
textview_curses::execute_search(const std::string& regex_orig)
24✔
925
{
926
    std::string regex = regex_orig;
24✔
927

928
    if ((this->tc_search_child == nullptr)
24✔
929
        || (regex != this->tc_current_search))
24✔
930
    {
931
        std::shared_ptr<lnav::pcre2pp::code> code;
24✔
932
        this->match_reset();
24✔
933

934
        this->tc_search_child.reset();
24✔
935
        this->tc_source_search_child.reset();
24✔
936

937
        log_debug("start search for: '%s'", regex.c_str());
24✔
938

939
        if (regex.empty()) {
24✔
940
        } else {
941
            auto compile_res = lnav::pcre2pp::code::from(regex, PCRE2_CASELESS);
9✔
942

943
            if (compile_res.isErr()) {
9✔
944
                auto ce = compile_res.unwrapErr();
×
UNCOV
945
                regex = lnav::pcre2pp::quote(regex);
×
946

UNCOV
947
                log_info("invalid search regex (%s), using quoted: %s",
×
948
                         ce.get_message().c_str(),
949
                         regex.c_str());
950

951
                auto compile_quote_res
952
                    = lnav::pcre2pp::code::from(regex, PCRE2_CASELESS);
×
953
                if (compile_quote_res.isErr()) {
×
954
                    log_error("Unable to compile quoted regex: %s",
×
955
                              regex.c_str());
956
                } else {
957
                    code = compile_quote_res.unwrap().to_shared();
×
958
                }
959
            } else {
×
960
                code = compile_res.unwrap().to_shared();
9✔
961
            }
962
        }
9✔
963

964
        if (code != nullptr) {
24✔
965
            highlighter hl(code);
9✔
966

967
            hl.with_role(role_t::VCR_SEARCH);
9✔
968

969
            auto& hm = this->get_highlights();
9✔
970
            hm[{highlight_source_t::PREVIEW, "search"}] = hl;
9✔
971

972
            auto gp = injector::get<std::shared_ptr<grep_proc<vis_line_t>>>(
973
                code, *this);
9✔
974

975
            gp->set_sink(this);
9✔
976
            auto top = this->get_top();
9✔
977
            if (top < REVERSE_SEARCH_OFFSET) {
9✔
978
                top = 0_vl;
9✔
979
            } else {
UNCOV
980
                top -= REVERSE_SEARCH_OFFSET;
×
981
            }
982
            gp->queue_request(top);
9✔
983
            if (top > 0) {
9✔
UNCOV
984
                gp->queue_request(0_vl, top);
×
985
            }
986
            this->tc_search_start_time = std::chrono::steady_clock::now();
9✔
987
            this->tc_search_duration = std::nullopt;
9✔
988
            gp->start();
9✔
989

990
            this->tc_search_child = std::make_shared<grep_highlighter>(
9✔
991
                gp, highlight_source_t::PREVIEW, "search", hm);
9✔
992

993
            if (this->tc_sub_source != nullptr) {
9✔
994
                this->tc_sub_source->get_grepper() | [this, code](auto pair) {
9✔
995
                    auto sgp
9✔
996
                        = injector::get<std::shared_ptr<grep_proc<vis_line_t>>>(
997
                            code, *pair.first);
9✔
998

999
                    sgp->set_sink(pair.second);
9✔
1000
                    sgp->queue_request(0_vl);
9✔
1001
                    sgp->start();
9✔
1002

1003
                    this->tc_source_search_child = sgp;
9✔
1004
                };
9✔
1005
            }
1006
        }
9✔
1007
    }
24✔
1008

1009
    this->tc_current_search = regex;
24✔
1010
    if (this->tc_state_event_handler) {
24✔
UNCOV
1011
        this->tc_state_event_handler(*this);
×
1012
    }
1013
}
24✔
1014

1015
std::optional<std::pair<int, int>>
UNCOV
1016
textview_curses::horiz_shift(vis_line_t start, vis_line_t end, int off_start)
×
1017
{
1018
    auto hl_iter
UNCOV
1019
        = this->tc_highlights.find({highlight_source_t::PREVIEW, "search"});
×
UNCOV
1020
    if (hl_iter == this->tc_highlights.end()
×
UNCOV
1021
        || hl_iter->second.h_regex == nullptr)
×
1022
    {
UNCOV
1023
        return std::nullopt;
×
1024
    }
1025
    int prev_hit = -1, next_hit = INT_MAX;
×
1026

UNCOV
1027
    for (; start < end; ++start) {
×
UNCOV
1028
        std::vector<attr_line_t> rows(1);
×
UNCOV
1029
        this->listview_value_for_rows(*this, start, rows);
×
1030

UNCOV
1031
        const auto& str = rows[0].get_string();
×
UNCOV
1032
        hl_iter->second.h_regex->capture_from(str).for_each(
×
UNCOV
1033
            [&](lnav::pcre2pp::match_data& md) {
×
UNCOV
1034
                auto cap = md[0].value();
×
UNCOV
1035
                if (cap.sf_begin < off_start) {
×
UNCOV
1036
                    prev_hit = std::max(prev_hit, cap.sf_begin);
×
UNCOV
1037
                } else if (cap.sf_begin > off_start) {
×
UNCOV
1038
                    next_hit = std::min(next_hit, cap.sf_begin);
×
1039
                }
UNCOV
1040
            });
×
1041
    }
1042

UNCOV
1043
    if (prev_hit == -1 && next_hit == INT_MAX) {
×
UNCOV
1044
        return std::nullopt;
×
1045
    }
UNCOV
1046
    return std::make_pair(prev_hit, next_hit);
×
1047
}
1048

1049
void
1050
textview_curses::set_user_mark(const bookmark_type_t* bm,
106✔
1051
                               vis_line_t vl,
1052
                               bool marked)
1053
{
1054
    auto& bv = this->tc_bookmarks[bm];
106✔
1055

1056
    if (marked) {
106✔
1057
        bv.insert_once(vl);
42✔
1058
    } else {
1059
        bv.bv_tree.erase(vl);
64✔
1060
    }
1061
    if (this->tc_sub_source) {
106✔
1062
        this->tc_sub_source->text_mark(bm, vl, marked);
106✔
1063
    }
1064

1065
    if (marked) {
106✔
1066
        this->search_range(vl, vl + 1_vl);
42✔
1067
        this->search_new_data();
42✔
1068
    }
1069
    this->set_needs_update();
106✔
1070
}
106✔
1071

1072
void
1073
textview_curses::toggle_user_mark(const bookmark_type_t* bm,
7✔
1074
                                  vis_line_t start_line,
1075
                                  vis_line_t end_line)
1076
{
1077
    if (end_line == -1) {
7✔
1078
        end_line = start_line;
7✔
1079
    }
1080
    if (start_line > end_line) {
7✔
UNCOV
1081
        std::swap(start_line, end_line);
×
1082
    }
1083

1084
    if (start_line >= this->get_inner_height()) {
7✔
UNCOV
1085
        return;
×
1086
    }
1087
    if (end_line >= this->get_inner_height()) {
7✔
UNCOV
1088
        end_line = vis_line_t(this->get_inner_height() - 1);
×
1089
    }
1090
    for (auto curr_line = start_line; curr_line <= end_line; ++curr_line) {
14✔
1091
        auto& bv = this->tc_bookmarks[bm];
7✔
1092
        auto [insert_iter, added] = bv.insert_once(curr_line);
7✔
1093
        if (this->tc_sub_source) {
7✔
1094
            this->tc_sub_source->text_mark(bm, curr_line, added);
7✔
1095
        }
1096
    }
1097
    this->search_range(start_line, end_line + 1_vl);
7✔
1098
    this->search_new_data();
7✔
1099
}
1100

1101
void
1102
textview_curses::redo_search()
1,234✔
1103
{
1104
    if (this->tc_search_child) {
1,234✔
1105
        auto* gp = this->tc_search_child->get_grep_proc();
12✔
1106

1107
        gp->invalidate();
12✔
1108
        this->match_reset();
12✔
1109
        gp->queue_request(0_vl).start();
12✔
1110

1111
        if (this->tc_source_search_child) {
12✔
1112
            this->tc_source_search_child->invalidate()
12✔
1113
                .queue_request(0_vl)
24✔
1114
                .start();
12✔
1115
        }
1116
    }
1117
}
1,234✔
1118

1119
bool
1120
textview_curses::listview_is_row_selectable(const listview_curses& lv,
1✔
1121
                                            vis_line_t row)
1122
{
1123
    if (this->tc_sub_source != nullptr) {
1✔
1124
        return this->tc_sub_source->text_is_row_selectable(*this, row);
1✔
1125
    }
1126

UNCOV
1127
    return true;
×
1128
}
1129

1130
void
1131
textview_curses::listview_selection_changed(const listview_curses& lv)
674✔
1132
{
1133
    if (this->tc_sub_source != nullptr) {
674✔
1134
        this->tc_sub_source->text_selection_changed(*this);
674✔
1135
    }
1136
}
674✔
1137

1138
textview_curses&
1139
textview_curses::set_sub_source(text_sub_source* src)
8,887✔
1140
{
1141
    if (this->tc_sub_source != src) {
8,887✔
1142
        this->tc_bookmarks.clear();
8,182✔
1143
        this->tc_sub_source = src;
8,182✔
1144
        if (src) {
8,182✔
1145
            src->register_view(this);
7,601✔
1146
        }
1147
        this->reload_data();
8,182✔
1148
    }
1149
    return *this;
8,887✔
1150
}
1151

1152
std::optional<line_info>
1153
textview_curses::grep_value_for_line(vis_line_t line, std::string& value_out)
3✔
1154
{
1155
    // log_debug("grep line %d", line);
1156
    if (this->tc_sub_source
6✔
1157
        && line < (int) this->tc_sub_source->text_line_count())
3✔
1158
    {
1159
        auto retval = this->tc_sub_source->text_value_for_line(
3✔
1160
            *this, line, value_out, text_sub_source::RF_RAW);
1161
        if (retval.li_utf8_scan_result.is_valid()
3✔
1162
            && retval.li_utf8_scan_result.usr_has_ansi)
3✔
1163
        {
1164
            // log_debug("has ansi %d",
1165
            // retval.li_utf8_scan_result.usr_has_ansi);
UNCOV
1166
            auto new_size = erase_ansi_escapes(value_out);
×
UNCOV
1167
            value_out.resize(new_size);
×
1168
        }
1169
        // log_debug("  line off %lld", retval.li_file_range.fr_offset);
1170
        return retval;
3✔
1171
    }
1172

UNCOV
1173
    return std::nullopt;
×
1174
}
1175

1176
void
1177
text_sub_source::scroll_invoked(textview_curses* tc)
19,284✔
1178
{
1179
    auto* ttt = dynamic_cast<text_time_translator*>(this);
19,284✔
1180

1181
    if (ttt != nullptr) {
19,284✔
1182
        ttt->ttt_scroll_invoked(tc);
13,551✔
1183
    }
1184
}
19,284✔
1185

1186
void
1187
text_time_translator::ttt_scroll_invoked(textview_curses* tc)
13,551✔
1188
{
1189
    if (tc->get_inner_height() > 0 && tc->get_selection()) {
13,551✔
1190
        this->time_for_row(tc->get_selection().value()) |
1,681✔
1191
            [this](auto new_top_ri) { this->ttt_top_row_info = new_top_ri; };
1,449✔
1192
    }
1193
}
13,551✔
1194

1195
void
1196
text_time_translator::data_reloaded(textview_curses* tc)
14,475✔
1197
{
1198
    if (tc->get_inner_height() == 0) {
14,475✔
1199
        this->ttt_top_row_info = std::nullopt;
12,791✔
1200
        return;
12,791✔
1201
    }
1202
    if (this->ttt_top_row_info) {
1,684✔
1203
        this->row_for(this->ttt_top_row_info.value()) |
365✔
1204
            [tc](auto new_top) { tc->set_selection(new_top); };
357✔
1205
    }
1206
}
1207

1208
template class bookmark_vector<vis_line_t>;
1209

1210
bool
UNCOV
1211
empty_filter::matches(std::optional<line_source> ls,
×
1212
                      const shared_buffer_ref& line)
1213
{
1214
    return false;
×
1215
}
1216

1217
std::string
UNCOV
1218
empty_filter::to_command() const
×
1219
{
1220
    return "";
×
1221
}
1222

1223
std::optional<size_t>
1224
filter_stack::next_index()
90✔
1225
{
1226
    bool used[32];
1227

1228
    memset(used, 0, sizeof(used));
90✔
1229
    for (auto& iter : *this) {
94✔
1230
        if (iter->lf_deleted) {
4✔
UNCOV
1231
            continue;
×
1232
        }
1233

1234
        size_t index = iter->get_index();
4✔
1235

1236
        require(used[index] == false);
4✔
1237

1238
        used[index] = true;
4✔
1239
    }
1240
    for (size_t lpc = this->fs_reserved;
94✔
1241
         lpc < logfile_filter_state::MAX_FILTERS;
94✔
1242
         lpc++)
1243
    {
1244
        if (!used[lpc]) {
94✔
1245
            return lpc;
90✔
1246
        }
1247
    }
UNCOV
1248
    return std::nullopt;
×
1249
}
1250

1251
std::shared_ptr<text_filter>
1252
filter_stack::get_filter(const std::string& id)
35✔
1253
{
1254
    auto iter = this->fs_filters.begin();
35✔
1255
    std::shared_ptr<text_filter> retval;
35✔
1256

1257
    for (; iter != this->fs_filters.end() && (*iter)->get_id() != id; iter++) {
37✔
1258
    }
1259
    if (iter != this->fs_filters.end()) {
35✔
1260
        retval = *iter;
4✔
1261
    }
1262

1263
    return retval;
70✔
UNCOV
1264
}
×
1265

1266
bool
1267
filter_stack::delete_filter(const std::string& id)
7✔
1268
{
1269
    auto iter = this->fs_filters.begin();
7✔
1270

1271
    for (; iter != this->fs_filters.end() && (*iter)->get_id() != id; iter++) {
7✔
1272
    }
1273
    if (iter != this->fs_filters.end()) {
7✔
1274
        this->fs_filters.erase(iter);
7✔
1275
        this->fs_generation += 1;
7✔
1276
        return true;
7✔
1277
    }
1278

UNCOV
1279
    return false;
×
1280
}
1281

1282
void
1283
filter_stack::get_mask(uint32_t& filter_mask)
×
1284
{
1285
    filter_mask = 0;
×
1286
    for (auto& iter : *this) {
×
UNCOV
1287
        std::shared_ptr<text_filter> tf = iter;
×
1288

UNCOV
1289
        if (tf->lf_deleted) {
×
1290
            continue;
×
1291
        }
UNCOV
1292
        if (tf->is_enabled()) {
×
UNCOV
1293
            uint32_t bit = (1UL << tf->get_index());
×
1294

UNCOV
1295
            switch (tf->get_type()) {
×
1296
                case text_filter::EXCLUDE:
×
1297
                case text_filter::INCLUDE:
UNCOV
1298
                    filter_mask |= bit;
×
UNCOV
1299
                    break;
×
1300
                default:
×
UNCOV
1301
                    ensure(0);
×
1302
                    break;
1303
            }
1304
        }
1305
    }
1306
}
1307

1308
void
1309
filter_stack::get_enabled_mask(uint32_t& filter_in_mask,
1,737✔
1310
                               uint32_t& filter_out_mask)
1311
{
1312
    filter_in_mask = filter_out_mask = 0;
1,737✔
1313
    for (auto& iter : *this) {
1,984✔
1314
        std::shared_ptr<text_filter> tf = iter;
247✔
1315

1316
        if (tf->lf_deleted) {
247✔
1317
            continue;
5✔
1318
        }
1319
        if (tf->is_enabled()) {
242✔
1320
            uint32_t bit = (1UL << tf->get_index());
99✔
1321

1322
            switch (tf->get_type()) {
99✔
1323
                case text_filter::EXCLUDE:
52✔
1324
                    filter_out_mask |= bit;
52✔
1325
                    break;
52✔
1326
                case text_filter::INCLUDE:
47✔
1327
                    filter_in_mask |= bit;
47✔
1328
                    break;
47✔
UNCOV
1329
                default:
×
UNCOV
1330
                    ensure(0);
×
1331
                    break;
1332
            }
1333
        }
1334
    }
247✔
1335
}
1,737✔
1336

1337
void
1338
filter_stack::add_filter(const std::shared_ptr<text_filter>& filter)
92✔
1339
{
1340
    this->fs_filters.push_back(filter);
92✔
1341
    this->fs_generation += 1;
92✔
1342
}
92✔
1343

1344
void
1345
vis_location_history::loc_history_append(vis_line_t top)
14✔
1346
{
1347
    auto iter = this->vlh_history.begin();
14✔
1348
    iter += this->vlh_history.size() - this->lh_history_position;
14✔
1349
    this->vlh_history.erase_from(iter);
14✔
1350
    this->lh_history_position = 0;
14✔
1351
    this->vlh_history.push_back(top);
14✔
1352
}
14✔
1353

1354
std::optional<vis_line_t>
UNCOV
1355
vis_location_history::loc_history_back(vis_line_t current_top)
×
1356
{
UNCOV
1357
    if (this->lh_history_position == 0) {
×
UNCOV
1358
        vis_line_t history_top = this->current_position();
×
UNCOV
1359
        if (history_top != current_top) {
×
UNCOV
1360
            return history_top;
×
1361
        }
1362
    }
1363

UNCOV
1364
    if (this->lh_history_position + 1 >= this->vlh_history.size()) {
×
UNCOV
1365
        return std::nullopt;
×
1366
    }
1367

UNCOV
1368
    this->lh_history_position += 1;
×
1369

UNCOV
1370
    return this->current_position();
×
1371
}
1372

1373
std::optional<vis_line_t>
UNCOV
1374
vis_location_history::loc_history_forward(vis_line_t current_top)
×
1375
{
UNCOV
1376
    if (this->lh_history_position == 0) {
×
UNCOV
1377
        return std::nullopt;
×
1378
    }
1379

UNCOV
1380
    this->lh_history_position -= 1;
×
1381

UNCOV
1382
    return this->current_position();
×
1383
}
1384

1385
void
UNCOV
1386
text_sub_source::update_filter_hash_state(hasher& h) const
×
1387
{
UNCOV
1388
    h.update(this->tss_filters.fs_generation);
×
1389

UNCOV
1390
    const auto* ttt = dynamic_cast<const text_time_translator*>(this);
×
UNCOV
1391
    if (ttt != nullptr) {
×
UNCOV
1392
        auto min_time = ttt->get_min_row_time();
×
UNCOV
1393
        if (min_time) {
×
UNCOV
1394
            h.update(min_time->tv_sec);
×
UNCOV
1395
            h.update(min_time->tv_usec);
×
1396
        } else {
1397
            h.update(0);
×
UNCOV
1398
            h.update(0);
×
1399
        }
1400
        auto max_time = ttt->get_max_row_time();
×
UNCOV
1401
        if (max_time) {
×
UNCOV
1402
            h.update(max_time->tv_sec);
×
1403
            h.update(max_time->tv_usec);
×
1404
        } else {
UNCOV
1405
            h.update(0);
×
1406
            h.update(0);
×
1407
        }
1408
    }
1409
}
1410

1411
void
1412
text_sub_source::toggle_apply_filters()
1✔
1413
{
1414
    this->tss_apply_filters = !this->tss_apply_filters;
1✔
1415
    this->text_filters_changed();
1✔
1416
}
1✔
1417

1418
void
1419
text_sub_source::text_crumbs_for_line(int line,
24✔
1420
                                      std::vector<breadcrumb::crumb>& crumbs)
1421
{
1422
}
24✔
1423

1424
logfile_filter_state::logfile_filter_state(std::shared_ptr<logfile> lf)
1,057✔
1425
    : tfs_logfile(std::move(lf))
1,057✔
1426
{
1427
    memset(this->tfs_filter_count, 0, sizeof(this->tfs_filter_count));
1,057✔
1428
    memset(this->tfs_filter_hits, 0, sizeof(this->tfs_filter_hits));
1,057✔
1429
    memset(this->tfs_message_matched, 0, sizeof(this->tfs_message_matched));
1,057✔
1430
    memset(this->tfs_lines_for_message, 0, sizeof(this->tfs_lines_for_message));
1,057✔
1431
    memset(this->tfs_last_message_matched,
1,057✔
1432
           0,
1433
           sizeof(this->tfs_last_message_matched));
1434
    memset(this->tfs_last_lines_for_message,
1,057✔
1435
           0,
1436
           sizeof(this->tfs_last_lines_for_message));
1437
    this->tfs_mask.reserve(64 * 1024);
1,057✔
1438
}
1,057✔
1439

1440
void
1441
logfile_filter_state::clear()
482✔
1442
{
1443
    this->tfs_logfile = nullptr;
482✔
1444
    memset(this->tfs_filter_count, 0, sizeof(this->tfs_filter_count));
482✔
1445
    memset(this->tfs_filter_hits, 0, sizeof(this->tfs_filter_hits));
482✔
1446
    memset(this->tfs_message_matched, 0, sizeof(this->tfs_message_matched));
482✔
1447
    memset(this->tfs_lines_for_message, 0, sizeof(this->tfs_lines_for_message));
482✔
1448
    memset(this->tfs_last_message_matched,
482✔
1449
           0,
1450
           sizeof(this->tfs_last_message_matched));
1451
    memset(this->tfs_last_lines_for_message,
482✔
1452
           0,
1453
           sizeof(this->tfs_last_lines_for_message));
1454
    this->tfs_mask.clear();
482✔
1455
    this->tfs_index.clear();
482✔
1456
}
482✔
1457

1458
void
1459
logfile_filter_state::clear_filter_state(size_t index)
3,463✔
1460
{
1461
    this->tfs_filter_count[index] = 0;
3,463✔
1462
    this->tfs_filter_hits[index] = 0;
3,463✔
1463
    this->tfs_message_matched[index] = false;
3,463✔
1464
    this->tfs_lines_for_message[index] = 0;
3,463✔
1465
    this->tfs_last_message_matched[index] = false;
3,463✔
1466
    this->tfs_last_lines_for_message[index] = 0;
3,463✔
1467
}
3,463✔
1468

1469
void
1470
logfile_filter_state::clear_deleted_filter_state(uint32_t used_mask)
109✔
1471
{
1472
    for (int lpc = 0; lpc < MAX_FILTERS; lpc++) {
3,597✔
1473
        if (!(used_mask & (1L << lpc))) {
3,488✔
1474
            this->clear_filter_state(lpc);
3,429✔
1475
        }
1476
    }
1477
    for (size_t lpc = 0; lpc < this->tfs_mask.size(); lpc++) {
953✔
1478
        this->tfs_mask[lpc] &= used_mask;
844✔
1479
    }
1480
}
109✔
1481

1482
void
1483
logfile_filter_state::resize(size_t newsize)
16,500✔
1484
{
1485
    size_t old_mask_size = this->tfs_mask.size();
16,500✔
1486

1487
    this->tfs_mask.resize(newsize);
16,500✔
1488
    if (newsize > old_mask_size) {
16,500✔
1489
        memset(&this->tfs_mask[old_mask_size],
15,657✔
1490
               0,
1491
               sizeof(uint32_t) * (newsize - old_mask_size));
15,657✔
1492
    }
1493
}
16,500✔
1494

1495
void
1496
logfile_filter_state::reserve(size_t expected)
2,169✔
1497
{
1498
    this->tfs_mask.reserve(expected);
2,169✔
1499
}
2,169✔
1500

1501
std::optional<size_t>
UNCOV
1502
logfile_filter_state::content_line_to_vis_line(uint32_t line)
×
1503
{
UNCOV
1504
    if (this->tfs_index.empty()) {
×
UNCOV
1505
        return std::nullopt;
×
1506
    }
1507

UNCOV
1508
    auto iter = std::lower_bound(
×
1509
        this->tfs_index.begin(), this->tfs_index.end(), line);
1510

UNCOV
1511
    if (iter == this->tfs_index.end() || *iter != line) {
×
UNCOV
1512
        return std::nullopt;
×
1513
    }
1514

UNCOV
1515
    return std::make_optional(std::distance(this->tfs_index.begin(), iter));
×
1516
}
1517

1518
std::string
1519
text_anchors::to_anchor_string(const std::string& raw)
53✔
1520
{
1521
    static const auto ANCHOR_RE = lnav::pcre2pp::code::from_const(R"([^\w]+)");
53✔
1522

1523
    return fmt::format(FMT_STRING("#{}"), ANCHOR_RE.replace(tolower(raw), "-"));
212✔
1524
}
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