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

tstack / lnav / 20284884426-2753

16 Dec 2025 10:23PM UTC coverage: 68.23% (-0.7%) from 68.903%
20284884426-2753

push

github

tstack
[log] show invalid utf hex dump in log view too

25 of 25 new or added lines in 2 files covered. (100.0%)

503 existing lines in 33 files now uncovered.

51170 of 74996 relevant lines covered (68.23%)

433797.6 hits per line

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

72.06
/src/filter_status_source.cc
1
/**
2
 * Copyright (c) 2018, 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 "filter_status_source.hh"
31

32
#include "base/ansi_scrubber.hh"
33
#include "base/opt_util.hh"
34
#include "config.h"
35
#include "files_sub_source.hh"
36
#include "filter_sub_source.hh"
37
#include "lnav.hh"
38

39
using namespace lnav::roles::literals;
40

41
static constexpr auto TOGGLE_MSG = "Press " ANSI_HOTKEY("TAB") " to edit "_frag;
42
static constexpr auto EXIT_MSG = "Press " ANSI_HOTKEY("ESC") " to exit "_frag;
43

44
static constexpr auto CREATE_HELP
45
    = "Create: " ANSI_HOTKEY("i") "n/" ANSI_HOTKEY("o") "ut";
46
static constexpr auto CREATE_EXPR_HELP = "  SQL " ANSI_HOTKEY("e") "xpr";
47
static constexpr auto CREATE_LEVEL_HELP = "  " ANSI_HOTKEY("l") "evel";
48
static constexpr auto MIN_MAX_TIME_HELP
49
    = "  " ANSI_HOTKEY("m") "in/" ANSI_HOTKEY("M") "ax time";
50
static constexpr auto ENABLE_HELP = ANSI_HOTKEY("SPC") ": ";
51
static constexpr auto EDIT_HELP = ANSI_HOTKEY("ENTER") ": Edit";
52
static constexpr auto TOGGLE_HELP = ANSI_HOTKEY("t") ": To ";
53
static constexpr auto DELETE_HELP = ANSI_HOTKEY("D") ": Delete";
54
static constexpr auto FILTERING_HELP = ANSI_HOTKEY("f") ": ";
55
static constexpr auto JUMP_HELP = ANSI_HOTKEY("ENTER") ": Jump To";
56
static constexpr auto CLOSE_HELP = ANSI_HOTKEY("X") ": Close";
57
static constexpr auto FOCUS_DETAILS_HELP
58
    = ANSI_BOLD("CTRL-]") ": Focus on details view";
59

60
filter_status_source::filter_status_source()
9,352✔
61
{
62
    this->tss_fields[TSF_TITLE].set_width(14);
1,169✔
63
    this->tss_fields[TSF_TITLE].set_role(role_t::VCR_STATUS_TITLE);
1,169✔
64
    this->tss_fields[TSF_TITLE].set_value(" " ANSI_ROLE("T") "ext Filters ",
1,169✔
65
                                          role_t::VCR_STATUS_TITLE_HOTKEY);
66
    this->tss_fields[TSF_TITLE].on_click
67
        = [](status_field&) { set_view_mode(ln_mode_t::FILTER); };
1,169✔
68

69
    this->tss_fields[TSF_STITCH_TITLE].set_width(2);
1,169✔
70
    this->tss_fields[TSF_STITCH_TITLE].set_stitch_value(
1,169✔
71
        role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL,
72
        role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE);
73

74
    this->tss_fields[TSF_COUNT].set_min_width(16);
1,169✔
75
    this->tss_fields[TSF_COUNT].set_share(1);
1,169✔
76
    this->tss_fields[TSF_COUNT].set_role(role_t::VCR_STATUS);
1,169✔
77

78
    this->tss_fields[TSF_FILTERED].set_min_width(20);
1,169✔
79
    this->tss_fields[TSF_FILTERED].set_share(1);
1,169✔
80
    this->tss_fields[TSF_FILTERED].set_role(role_t::VCR_STATUS);
1,169✔
81

82
    this->tss_fields[TSF_FILES_TITLE].set_width(7);
1,169✔
83
    this->tss_fields[TSF_FILES_TITLE].set_role(
1,169✔
84
        role_t::VCR_STATUS_DISABLED_TITLE);
85
    this->tss_fields[TSF_FILES_TITLE].set_value(" " ANSI_ROLE("F") "iles ",
1,169✔
86
                                                role_t::VCR_STATUS_HOTKEY);
87
    this->tss_fields[TSF_FILES_TITLE].on_click
88
        = [](status_field&) { set_view_mode(ln_mode_t::FILES); };
1,169✔
89

90
    this->tss_fields[TSF_FILES_RIGHT_STITCH].set_width(2);
1,169✔
91
    this->tss_fields[TSF_FILES_RIGHT_STITCH].set_stitch_value(
1,169✔
92
        role_t::VCR_STATUS, role_t::VCR_STATUS);
93

94
    this->tss_fields[TSF_HELP].right_justify(true);
1,169✔
95
    this->tss_fields[TSF_HELP].set_width(20);
1,169✔
96
    this->tss_fields[TSF_HELP].set_value(TOGGLE_MSG);
1,169✔
97
    this->tss_fields[TSF_HELP].set_left_pad(1);
1,169✔
98

99
    this->tss_error.set_min_width(20);
1,169✔
100
    this->tss_error.set_share(1);
1,169✔
101
    this->tss_error.set_role(role_t::VCR_ALERT_STATUS);
1,169✔
102
}
1,169✔
103

104
size_t
105
filter_status_source::statusview_fields()
11✔
106
{
107
    switch (lnav_data.ld_mode) {
11✔
108
        case ln_mode_t::SEARCH_FILTERS:
×
109
        case ln_mode_t::SEARCH_FILES:
110
            this->tss_fields[TSF_HELP].set_value("");
×
111
            break;
×
112
        case ln_mode_t::FILTER:
6✔
113
        case ln_mode_t::FILES:
114
        case ln_mode_t::FILE_DETAILS:
115
            this->tss_fields[TSF_HELP].set_value(EXIT_MSG);
6✔
116
            break;
6✔
117
        default:
5✔
118
            this->tss_fields[TSF_HELP].set_value(TOGGLE_MSG);
5✔
119
            break;
5✔
120
    }
121

122
    if (lnav_data.ld_mode == ln_mode_t::FILES
11✔
123
        || lnav_data.ld_mode == ln_mode_t::FILE_DETAILS
9✔
124
        || lnav_data.ld_mode == ln_mode_t::SEARCH_FILES)
9✔
125
    {
126
        this->tss_fields[TSF_FILES_TITLE].set_value(
2✔
127
            " " ANSI_ROLE("F") "iles ", role_t::VCR_STATUS_TITLE_HOTKEY);
128
        this->tss_fields[TSF_FILES_TITLE].set_role(role_t::VCR_STATUS_TITLE);
2✔
129
        this->tss_fields[TSF_FILES_RIGHT_STITCH].set_stitch_value(
2✔
130
            role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL,
131
            role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE);
132
        this->tss_fields[TSF_TITLE].set_value(" " ANSI_ROLE("T") "ext Filters ",
2✔
133
                                              role_t::VCR_STATUS_HOTKEY);
134
        this->tss_fields[TSF_TITLE].set_role(role_t::VCR_STATUS_DISABLED_TITLE);
2✔
135
        this->tss_fields[TSF_STITCH_TITLE].set_stitch_value(role_t::VCR_STATUS,
2✔
136
                                                            role_t::VCR_STATUS);
137
    } else {
138
        this->tss_fields[TSF_FILES_TITLE].set_value(" " ANSI_ROLE("F") "iles ",
9✔
139
                                                    role_t::VCR_STATUS_HOTKEY);
140
        if (lnav_data.ld_active_files.fc_name_to_stubs->readAccess()->empty()) {
9✔
141
            this->tss_fields[TSF_FILES_TITLE].set_role(
9✔
142
                role_t::VCR_STATUS_DISABLED_TITLE);
143
        } else {
144
            size_t error_count = 0;
×
145
            size_t other_count = 0;
×
146

147
            auto& fc = lnav_data.ld_active_files;
×
148
            {
149
                auto stub_map = fc.fc_name_to_stubs->readAccess();
×
150

151
                for (const auto& stub : *stub_map) {
×
152
                    switch (stub.second.fsi_description.um_level) {
×
153
                        case lnav::console::user_message::level::raw:
×
154
                        case lnav::console::user_message::level::ok:
155
                        case lnav::console::user_message::level::info:
156
                        case lnav::console::user_message::level::warning:
157
                            other_count += 1;
×
158
                            break;
×
159
                        case lnav::console::user_message::level::error:
×
160
                            error_count += 1;
×
161
                            break;
×
162
                    }
163
                }
164
            }
165

166
            if (error_count > 0) {
×
167
                this->tss_fields[TSF_FILES_TITLE].set_role(
×
168
                    role_t::VCR_ALERT_STATUS);
169
            }
170
            if (error_count == 1) {
×
171
                this->tss_error.set_value(
×
172
                    " error: a file cannot be opened "_frag);
×
173
            } else if (error_count > 1) {
×
174
                this->tss_error.set_value(" error: %zu files cannot be opened ",
×
175
                                          error_count);
176
            }
177
        }
178
        this->tss_fields[TSF_FILES_RIGHT_STITCH].set_stitch_value(
9✔
179
            role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE,
180
            role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL);
181
        this->tss_fields[TSF_TITLE].set_value(" " ANSI_ROLE("T") "ext Filters ",
9✔
182
                                              role_t::VCR_STATUS_TITLE_HOTKEY);
183
        this->tss_fields[TSF_TITLE].set_role(role_t::VCR_STATUS_TITLE);
9✔
184
        this->tss_fields[TSF_STITCH_TITLE].set_stitch_value(
9✔
185
            role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL,
186
            role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE);
187
    }
188

189
    lnav_data.ld_view_stack.top() | [this](auto tc) {
11✔
190
        text_sub_source* tss = tc->get_sub_source();
11✔
191
        if (tss == nullptr) {
11✔
192
            return;
×
193
        }
194

195
        filter_stack& fs = tss->get_filters();
11✔
196
        auto enabled_count = 0, filter_count = 0;
11✔
197

198
        for (const auto& tf : fs) {
15✔
199
            if (tf->is_enabled()) {
4✔
200
                enabled_count += 1;
2✔
201
            }
202
            filter_count += 1;
4✔
203
        }
204
        if (filter_count == 0) {
11✔
205
            this->tss_fields[TSF_COUNT].set_value(""_frag);
7✔
206
        } else {
207
            this->tss_fields[TSF_COUNT].set_value(
4✔
208
                " " ANSI_BOLD("%d") " of " ANSI_BOLD("%d") " enabled ",
209
                enabled_count,
210
                filter_count);
211
        }
212
    };
213

214
    return TSF__MAX;
11✔
215
}
216

217
status_field&
218
filter_status_source::statusview_value_for_field(int field)
77✔
219
{
220
    if (field == TSF_FILTERED
77✔
221
        && !lnav_data.ld_active_files.fc_name_to_stubs->readAccess()->empty())
77✔
222
    {
223
        return this->tss_error;
×
224
    }
225

226
    return this->tss_fields[field];
77✔
227
}
228

229
bool
230
filter_status_source::update_filtered(text_sub_source* tss)
3,069✔
231
{
232
    if (tss == nullptr) {
3,069✔
233
        return false;
613✔
234
    }
235

236
    auto& sf = this->tss_fields[TSF_FILTERED];
2,456✔
237
    auto retval = false;
2,456✔
238

239
    auto curr_filtered_count = tss->get_filtered_count();
2,456✔
240
    if (curr_filtered_count == 0) {
2,456✔
241
        if (tss->tss_apply_filters) {
2,309✔
242
            if (!sf.empty()) {
2,306✔
243
                sf.clear();
15✔
244
                retval = true;
15✔
245
            }
246
        } else {
247
            auto al = attr_line_t(" ")
3✔
248
                          .append("  ", VC_ICON.value(ui_icon_t::warning))
6✔
249
                          .append(" Filtering disabled, re-enable with ")
3✔
250
                          .append(":toggle-filtering"_symbol);
3✔
251
            retval = sf.set_value(al);
3✔
252
        }
3✔
253
        this->bss_last_filtered_count = curr_filtered_count;
2,309✔
254
    } else {
255
        auto& timer = ui_periodic_timer::singleton();
147✔
256
        auto& al = sf.get_value();
147✔
257

258
        if (curr_filtered_count == this->bss_last_filtered_count) {
147✔
259
            if (timer.fade_diff(this->bss_filter_counter) == 0
83✔
260
                && this->tss_fields[TSF_FILTERED].get_role()
83✔
261
                    != role_t::VCR_STATUS)
262
            {
263
                this->tss_fields[TSF_FILTERED].set_role(role_t::VCR_STATUS);
×
264
                al.with_attr(
×
265
                    string_attr(line_range{0, -1},
×
266
                                VC_STYLE.value(text_attrs::with_bold())));
×
267
                retval = true;
×
268
            }
269
        } else {
270
            this->tss_fields[TSF_FILTERED].set_role(role_t::VCR_ALERT_STATUS);
64✔
271
            this->bss_last_filtered_count = tss->get_filtered_count();
64✔
272
            timer.start_fade(this->bss_filter_counter, 3);
64✔
273
            retval = sf.set_value("%'9d Lines not shown ",
64✔
274
                                  tss->get_filtered_count());
64✔
275
        }
276
    }
277

278
    return retval;
2,456✔
279
}
280

281
filter_help_status_source::filter_help_status_source()
1,169✔
282
{
283
    this->fss_help.set_min_width(10);
1,169✔
284
    this->fss_help.set_share(1);
1,169✔
285
    this->fss_prompt.set_left_pad(1);
1,169✔
286
    this->fss_prompt.set_min_width(35);
1,169✔
287
    this->fss_prompt.set_share(1);
1,169✔
288
    this->fss_error.set_left_pad(25);
1,169✔
289
    this->fss_error.set_min_width(35);
1,169✔
290
    this->fss_error.set_share(1);
1,169✔
291
}
1,169✔
292

293
size_t
294
filter_help_status_source::statusview_fields()
7✔
295
{
296
    lnav_data.ld_view_stack.top() | [this](auto tc) {
7✔
297
        auto* tss = tc->get_sub_source();
7✔
298
        if (tss == nullptr) {
7✔
299
            return;
×
300
        }
301

302
        auto* lss = dynamic_cast<logfile_sub_source*>(tss);
7✔
303
        auto* ttt = dynamic_cast<text_time_translator*>(tss);
7✔
304

305
        if (lnav_data.ld_mode == ln_mode_t::FILTER) {
7✔
306
            static auto* editor = injector::get<filter_sub_source*>();
4✔
307
            auto& lv = lnav_data.ld_filter_view;
4✔
308
            auto sel = lv.get_selection();
4✔
309
            auto* fss = dynamic_cast<filter_sub_source*>(lv.get_sub_source());
4✔
310
            auto rows = fss->rows_for(tc);
4✔
311
            if (rows.empty()) {
4✔
312
                this->fss_help.set_value(
2✔
313
                    "  %s%s%s%s",
314
                    CREATE_HELP,
315
                    lss != nullptr ? CREATE_EXPR_HELP : "",
316
                    lss != nullptr ? CREATE_LEVEL_HELP : "",
317
                    ttt != nullptr ? MIN_MAX_TIME_HELP : "");
318
            } else {
319
                auto& row = rows[sel.value()];
2✔
320
                auto* tfr = dynamic_cast<filter_sub_source::text_filter_row*>(
2✔
321
                    row.get());
2✔
322
                auto* tir = dynamic_cast<filter_sub_source::time_filter_row*>(
2✔
323
                    row.get());
2✔
324
                if (editor->fss_editing) {
2✔
325
                    if (tfr != nullptr) {
2✔
326
                        auto& tf = tfr->tfr_filter;
2✔
327
                        auto lang = tf->get_lang() == filter_lang_t::SQL
2✔
328
                            ? "an SQL"
2✔
329
                            : "a regular";
330

331
                        if (tf->get_type() == text_filter::type_t::INCLUDE) {
2✔
332
                            this->fss_help.set_value(
×
333
                                "                           "
334
                                "Enter %s expression to match lines to filter "
335
                                "in:",
336
                                lang);
337
                        } else {
338
                            this->fss_help.set_value(
2✔
339
                                "                           "
340
                                "Enter %s expression to match lines to filter "
341
                                "out:",
342
                                lang);
343
                        }
344
                    } else if (tir != nullptr) {
×
345
                        this->fss_help.set_value(
×
346
                            "                           "
347
                            "Enter timestamp:");
348
                    } else {
349
                        this->fss_help.set_value(
×
350
                            "                           "
351
                            "Enter level:");
352
                    }
353
                } else if (tfr != nullptr) {
×
354
                    auto& tf = tfr->tfr_filter;
×
355
                    this->fss_help.set_value(
×
356
                        "  %s%s%s%s  %s%s  %s  %s%s  %s  %s%s",
357
                        CREATE_HELP,
358
                        lss != nullptr ? CREATE_EXPR_HELP : "",
359
                        lss != nullptr ? CREATE_LEVEL_HELP : "",
360
                        ttt != nullptr ? MIN_MAX_TIME_HELP : "",
361
                        ENABLE_HELP,
362
                        tf->is_enabled() ? "Disable" : "Enable ",
×
363
                        EDIT_HELP,
364
                        TOGGLE_HELP,
365
                        tf->get_type() == text_filter::type_t::INCLUDE ? "OUT"
×
366
                                                                       : "IN ",
367
                        DELETE_HELP,
368
                        FILTERING_HELP,
369
                        tss->tss_apply_filters ? "Disable Filtering"
×
370
                                               : "Enable Filtering");
371
                } else {
372
                    this->fss_help.set_value(
×
373
                        "  %s%s%s%s  %s  %s  %s%s",
374
                        CREATE_HELP,
375
                        lss != nullptr ? CREATE_EXPR_HELP : "",
376
                        lss != nullptr ? CREATE_LEVEL_HELP : "",
377
                        ttt != nullptr ? MIN_MAX_TIME_HELP : "",
378
                        EDIT_HELP,
379
                        DELETE_HELP,
380
                        FILTERING_HELP,
381
                        tss->tss_apply_filters ? "Disable Filtering"
×
382
                                               : "Enable Filtering");
383
                }
384
            }
385
        } else if ((lnav_data.ld_mode == ln_mode_t::FILES
7✔
386
                    || lnav_data.ld_mode == ln_mode_t::FILE_DETAILS)
1✔
387
                   && lnav_data.ld_session_loaded)
2✔
388
        {
389
            const auto& lv = lnav_data.ld_files_view;
2✔
390
            auto sel = files_model::from_selection(lv.get_selection());
2✔
391

392
            sel.match(
2✔
393
                [this](files_model::no_selection) { this->fss_help.clear(); },
2✔
394
                [this](files_model::stub_selection) {
×
395
                    this->fss_help.set_value("  %s", CLOSE_HELP);
×
396
                },
397
                [this](files_model::other_selection) {
×
398
                    this->fss_help.clear();
×
399
                },
400
                [this](files_model::file_selection& fs) {
2✔
UNCOV
401
                    auto& lss = lnav_data.ld_log_source;
×
UNCOV
402
                    auto vis_help = "Hide";
×
UNCOV
403
                    auto ld_opt = lss.find_data(*fs.sb_iter);
×
UNCOV
404
                    if (ld_opt && !ld_opt.value()->ld_visible) {
×
405
                        vis_help = "Show";
×
406
                    }
UNCOV
407
                    const auto* focus_details_help
×
UNCOV
408
                        = lnav_data.ld_mode == ln_mode_t::FILES
×
UNCOV
409
                        ? FOCUS_DETAILS_HELP
×
410
                        : "";
411

UNCOV
412
                    this->fss_help.set_value("  %s%s  %s  %s",
×
413
                                             ENABLE_HELP,
414
                                             vis_help,
415
                                             JUMP_HELP,
416
                                             focus_details_help);
417
                });
418
        }
2✔
419
    };
420

421
    return 1;
7✔
422
}
423

424
status_field&
425
filter_help_status_source::statusview_value_for_field(int field)
7✔
426
{
427
    if (!this->fss_error.empty()) {
7✔
428
        return this->fss_error;
×
429
    }
430

431
    if (!this->fss_prompt.empty()) {
7✔
432
        return this->fss_prompt;
×
433
    }
434

435
    return this->fss_help;
7✔
436
}
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