• 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

32.92
/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
static constexpr auto TOGGLE_MSG = "Press " ANSI_BOLD("TAB") " to edit "_frag;
40
static constexpr auto EXIT_MSG = "Press " ANSI_BOLD("ESC") " to exit "_frag;
41

42
static constexpr auto CREATE_HELP
43
    = ANSI_BOLD("i") "/" ANSI_BOLD("o") ": Create in/out";
44
static constexpr auto ENABLE_HELP = ANSI_BOLD("SPC") ": ";
45
static constexpr auto EDIT_HELP = ANSI_BOLD("ENTER") ": Edit";
46
static constexpr auto TOGGLE_HELP = ANSI_BOLD("t") ": To ";
47
static constexpr auto DELETE_HELP = ANSI_BOLD("D") ": Delete";
48
static constexpr auto FILTERING_HELP = ANSI_BOLD("f") ": ";
49
static constexpr auto JUMP_HELP = ANSI_BOLD("ENTER") ": Jump To";
50
static constexpr auto CLOSE_HELP = ANSI_BOLD("X") ": Close";
51
static constexpr auto FOCUS_DETAILS_HELP
52
    = ANSI_BOLD("CTRL-]") ": Focus on details view";
53

54
filter_status_source::filter_status_source()
8,728✔
55
{
56
    this->tss_fields[TSF_TITLE].set_width(14);
1,091✔
57
    this->tss_fields[TSF_TITLE].set_role(role_t::VCR_STATUS_TITLE);
1,091✔
58
    this->tss_fields[TSF_TITLE].set_value(" " ANSI_ROLE("T") "ext Filters ",
1,091✔
59
                                          role_t::VCR_STATUS_TITLE_HOTKEY);
60
    this->tss_fields[TSF_TITLE].on_click
61
        = [](status_field&) { set_view_mode(ln_mode_t::FILTER); };
1,091✔
62

63
    this->tss_fields[TSF_STITCH_TITLE].set_width(2);
1,091✔
64
    this->tss_fields[TSF_STITCH_TITLE].set_stitch_value(
1,091✔
65
        role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL,
66
        role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE);
67

68
    this->tss_fields[TSF_COUNT].set_min_width(16);
1,091✔
69
    this->tss_fields[TSF_COUNT].set_share(1);
1,091✔
70
    this->tss_fields[TSF_COUNT].set_role(role_t::VCR_STATUS);
1,091✔
71

72
    this->tss_fields[TSF_FILTERED].set_min_width(20);
1,091✔
73
    this->tss_fields[TSF_FILTERED].set_share(1);
1,091✔
74
    this->tss_fields[TSF_FILTERED].set_role(role_t::VCR_STATUS);
1,091✔
75

76
    this->tss_fields[TSF_FILES_TITLE].set_width(7);
1,091✔
77
    this->tss_fields[TSF_FILES_TITLE].set_role(
1,091✔
78
        role_t::VCR_STATUS_DISABLED_TITLE);
79
    this->tss_fields[TSF_FILES_TITLE].set_value(" " ANSI_ROLE("F") "iles ",
1,091✔
80
                                                role_t::VCR_STATUS_HOTKEY);
81
    this->tss_fields[TSF_FILES_TITLE].on_click
82
        = [](status_field&) { set_view_mode(ln_mode_t::FILES); };
1,091✔
83

84
    this->tss_fields[TSF_FILES_RIGHT_STITCH].set_width(2);
1,091✔
85
    this->tss_fields[TSF_FILES_RIGHT_STITCH].set_stitch_value(
1,091✔
86
        role_t::VCR_STATUS, role_t::VCR_STATUS);
87

88
    this->tss_fields[TSF_HELP].right_justify(true);
1,091✔
89
    this->tss_fields[TSF_HELP].set_width(20);
1,091✔
90
    this->tss_fields[TSF_HELP].set_value(TOGGLE_MSG);
1,091✔
91
    this->tss_fields[TSF_HELP].set_left_pad(1);
1,091✔
92

93
    this->tss_error.set_min_width(20);
1,091✔
94
    this->tss_error.set_share(1);
1,091✔
95
    this->tss_error.set_role(role_t::VCR_ALERT_STATUS);
1,091✔
96
}
1,091✔
97

98
size_t
UNCOV
99
filter_status_source::statusview_fields()
×
100
{
101
    switch (lnav_data.ld_mode) {
×
UNCOV
102
        case ln_mode_t::SEARCH_FILTERS:
×
103
        case ln_mode_t::SEARCH_FILES:
104
            this->tss_fields[TSF_HELP].set_value("");
×
UNCOV
105
            break;
×
UNCOV
106
        case ln_mode_t::FILTER:
×
107
        case ln_mode_t::FILES:
108
        case ln_mode_t::FILE_DETAILS:
UNCOV
109
            this->tss_fields[TSF_HELP].set_value(EXIT_MSG);
×
UNCOV
110
            break;
×
UNCOV
111
        default:
×
UNCOV
112
            this->tss_fields[TSF_HELP].set_value(TOGGLE_MSG);
×
UNCOV
113
            break;
×
114
    }
115

UNCOV
116
    if (lnav_data.ld_mode == ln_mode_t::FILES
×
UNCOV
117
        || lnav_data.ld_mode == ln_mode_t::FILE_DETAILS
×
UNCOV
118
        || lnav_data.ld_mode == ln_mode_t::SEARCH_FILES)
×
119
    {
UNCOV
120
        this->tss_fields[TSF_FILES_TITLE].set_value(
×
121
            " " ANSI_ROLE("F") "iles ", role_t::VCR_STATUS_TITLE_HOTKEY);
UNCOV
122
        this->tss_fields[TSF_FILES_TITLE].set_role(role_t::VCR_STATUS_TITLE);
×
UNCOV
123
        this->tss_fields[TSF_FILES_RIGHT_STITCH].set_stitch_value(
×
124
            role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL,
125
            role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE);
UNCOV
126
        this->tss_fields[TSF_TITLE].set_value(" " ANSI_ROLE("T") "ext Filters ",
×
127
                                              role_t::VCR_STATUS_HOTKEY);
UNCOV
128
        this->tss_fields[TSF_TITLE].set_role(role_t::VCR_STATUS_DISABLED_TITLE);
×
UNCOV
129
        this->tss_fields[TSF_STITCH_TITLE].set_stitch_value(role_t::VCR_STATUS,
×
130
                                                            role_t::VCR_STATUS);
131
    } else {
UNCOV
132
        this->tss_fields[TSF_FILES_TITLE].set_value(" " ANSI_ROLE("F") "iles ",
×
133
                                                    role_t::VCR_STATUS_HOTKEY);
UNCOV
134
        if (lnav_data.ld_active_files.fc_name_to_errors->readAccess()->empty())
×
135
        {
UNCOV
136
            this->tss_fields[TSF_FILES_TITLE].set_role(
×
137
                role_t::VCR_STATUS_DISABLED_TITLE);
138
        } else {
UNCOV
139
            this->tss_fields[TSF_FILES_TITLE].set_role(
×
140
                role_t::VCR_ALERT_STATUS);
141

142
            auto& fc = lnav_data.ld_active_files;
×
143
            if (fc.fc_name_to_errors->readAccess()->size() == 1) {
×
UNCOV
144
                this->tss_error.set_value(
×
145
                    " error: a file cannot be opened "_frag);
×
146
            } else {
147
                this->tss_error.set_value(
×
148
                    " error: %u files cannot be opened ",
UNCOV
149
                    lnav_data.ld_active_files.fc_name_to_errors->readAccess()
×
150
                        ->size());
151
            }
152
        }
UNCOV
153
        this->tss_fields[TSF_FILES_RIGHT_STITCH].set_stitch_value(
×
154
            role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE,
155
            role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL);
UNCOV
156
        this->tss_fields[TSF_TITLE].set_value(" " ANSI_ROLE("T") "ext Filters ",
×
157
                                              role_t::VCR_STATUS_TITLE_HOTKEY);
UNCOV
158
        this->tss_fields[TSF_TITLE].set_role(role_t::VCR_STATUS_TITLE);
×
UNCOV
159
        this->tss_fields[TSF_STITCH_TITLE].set_stitch_value(
×
160
            role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL,
161
            role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE);
162
    }
163

UNCOV
164
    lnav_data.ld_view_stack.top() | [this](auto tc) {
×
165
        text_sub_source* tss = tc->get_sub_source();
×
UNCOV
166
        if (tss == nullptr) {
×
UNCOV
167
            return;
×
168
        }
169

UNCOV
170
        filter_stack& fs = tss->get_filters();
×
UNCOV
171
        auto enabled_count = 0, filter_count = 0;
×
172

UNCOV
173
        for (const auto& tf : fs) {
×
UNCOV
174
            if (tf->is_enabled()) {
×
UNCOV
175
                enabled_count += 1;
×
176
            }
UNCOV
177
            filter_count += 1;
×
178
        }
UNCOV
179
        if (filter_count == 0) {
×
UNCOV
180
            this->tss_fields[TSF_COUNT].set_value(""_frag);
×
181
        } else {
UNCOV
182
            this->tss_fields[TSF_COUNT].set_value(
×
183
                " " ANSI_BOLD("%d") " of " ANSI_BOLD("%d") " enabled ",
184
                enabled_count,
185
                filter_count);
186
        }
187
    };
188

UNCOV
189
    return TSF__MAX;
×
190
}
191

192
status_field&
UNCOV
193
filter_status_source::statusview_value_for_field(int field)
×
194
{
UNCOV
195
    if (field == TSF_FILTERED
×
196
        && !lnav_data.ld_active_files.fc_name_to_errors->readAccess()->empty())
×
197
    {
UNCOV
198
        return this->tss_error;
×
199
    }
200

UNCOV
201
    return this->tss_fields[field];
×
202
}
203

204
void
205
filter_status_source::update_filtered(text_sub_source* tss)
2,762✔
206
{
207
    if (tss == nullptr) {
2,762✔
208
        return;
9✔
209
    }
210

211
    auto& sf = this->tss_fields[TSF_FILTERED];
2,753✔
212

213
    if (tss->get_filtered_count() == 0) {
2,753✔
214
        if (tss->tss_apply_filters) {
2,627✔
215
            sf.clear();
2,624✔
216
        } else {
217
            sf.set_value(
3✔
218
                " \u2718 Filtering disabled, re-enable with " ANSI_BOLD_START
219
                ":toggle-filtering" ANSI_NORM);
220
        }
221
    } else {
222
        auto& timer = ui_periodic_timer::singleton();
126✔
223
        auto& al = sf.get_value();
126✔
224

225
        if (tss->get_filtered_count() == this->bss_last_filtered_count) {
126✔
226
            if (timer.fade_diff(this->bss_filter_counter) == 0) {
70✔
227
                this->tss_fields[TSF_FILTERED].set_role(role_t::VCR_STATUS);
×
UNCOV
228
                al.with_attr(
×
UNCOV
229
                    string_attr(line_range{0, -1},
×
UNCOV
230
                                VC_STYLE.value(text_attrs::with_bold())));
×
231
            }
232
        } else {
233
            this->tss_fields[TSF_FILTERED].set_role(role_t::VCR_ALERT_STATUS);
56✔
234
            this->bss_last_filtered_count = tss->get_filtered_count();
56✔
235
            timer.start_fade(this->bss_filter_counter, 3);
56✔
236
        }
237
        sf.set_value("%'9d Lines not shown ", tss->get_filtered_count());
126✔
238
    }
239
}
240

241
filter_help_status_source::filter_help_status_source()
1,091✔
242
{
243
    this->fss_help.set_min_width(10);
1,091✔
244
    this->fss_help.set_share(1);
1,091✔
245
    this->fss_prompt.set_left_pad(1);
1,091✔
246
    this->fss_prompt.set_min_width(35);
1,091✔
247
    this->fss_prompt.set_share(1);
1,091✔
248
    this->fss_error.set_left_pad(25);
1,091✔
249
    this->fss_error.set_min_width(35);
1,091✔
250
    this->fss_error.set_share(1);
1,091✔
251
}
1,091✔
252

253
size_t
UNCOV
254
filter_help_status_source::statusview_fields()
×
255
{
UNCOV
256
    lnav_data.ld_view_stack.top() | [this](auto tc) {
×
257
        text_sub_source* tss = tc->get_sub_source();
×
UNCOV
258
        if (tss == nullptr) {
×
UNCOV
259
            return;
×
260
        }
261

UNCOV
262
        if (lnav_data.ld_mode == ln_mode_t::FILTER) {
×
263
            static auto* editor = injector::get<filter_sub_source*>();
UNCOV
264
            auto& lv = lnav_data.ld_filter_view;
×
UNCOV
265
            auto sel = lv.get_selection();
×
UNCOV
266
            auto& fs = tss->get_filters();
×
267

UNCOV
268
            if (editor->fss_editing) {
×
UNCOV
269
                auto tf = *(fs.begin() + sel.value());
×
UNCOV
270
                auto lang = tf->get_lang() == filter_lang_t::SQL ? "an SQL"
×
271
                                                                 : "a regular";
272

UNCOV
273
                if (tf->get_type() == text_filter::type_t::INCLUDE) {
×
UNCOV
274
                    this->fss_help.set_value(
×
275
                        "                        "
276
                        "Enter %s expression to match lines to filter in:",
277
                        lang);
278
                } else {
UNCOV
279
                    this->fss_help.set_value(
×
280
                        "                        "
281
                        "Enter %s expression to match lines to filter out:",
282
                        lang);
283
                }
284
            } else if (fs.empty()) {
×
UNCOV
285
                this->fss_help.set_value("  %s", CREATE_HELP);
×
286
            } else if (sel) {
×
UNCOV
287
                auto tf = *(fs.begin() + sel.value());
×
288

UNCOV
289
                this->fss_help.set_value(
×
290
                    "  %s  %s%s  %s  %s%s  %s  %s%s",
291
                    CREATE_HELP,
292
                    ENABLE_HELP,
293
                    tf->is_enabled() ? "Disable" : "Enable ",
×
294
                    EDIT_HELP,
295
                    TOGGLE_HELP,
UNCOV
296
                    tf->get_type() == text_filter::type_t::INCLUDE ? "OUT"
×
297
                                                                   : "IN ",
298
                    DELETE_HELP,
299
                    FILTERING_HELP,
UNCOV
300
                    tss->tss_apply_filters ? "Disable Filtering"
×
301
                                           : "Enable Filtering");
302
            }
UNCOV
303
        } else if ((lnav_data.ld_mode == ln_mode_t::FILES
×
UNCOV
304
                    || lnav_data.ld_mode == ln_mode_t::FILE_DETAILS)
×
UNCOV
305
                   && lnav_data.ld_session_loaded)
×
306
        {
UNCOV
307
            const auto& lv = lnav_data.ld_files_view;
×
UNCOV
308
            auto sel = files_model::from_selection(lv.get_selection());
×
309

310
            sel.match(
×
UNCOV
311
                [this](files_model::no_selection) { this->fss_help.clear(); },
×
312
                [this](files_model::error_selection) {
×
313
                    this->fss_help.set_value("  %s", CLOSE_HELP);
×
314
                },
315
                [this](files_model::other_selection) {
×
316
                    this->fss_help.clear();
×
317
                },
318
                [this](files_model::file_selection& fs) {
×
319
                    auto& lss = lnav_data.ld_log_source;
×
320
                    auto vis_help = "Hide";
×
UNCOV
321
                    auto ld_opt = lss.find_data(*fs.sb_iter);
×
322
                    if (ld_opt && !ld_opt.value()->ld_visible) {
×
323
                        vis_help = "Show";
×
324
                    }
UNCOV
325
                    const auto* focus_details_help
×
UNCOV
326
                        = lnav_data.ld_mode == ln_mode_t::FILES
×
327
                        ? FOCUS_DETAILS_HELP
×
328
                        : "";
329

UNCOV
330
                    this->fss_help.set_value("  %s%s  %s  %s",
×
331
                                             ENABLE_HELP,
332
                                             vis_help,
333
                                             JUMP_HELP,
334
                                             focus_details_help);
335
                });
336
        }
337
    };
338

UNCOV
339
    return 1;
×
340
}
341

342
status_field&
343
filter_help_status_source::statusview_value_for_field(int field)
×
344
{
UNCOV
345
    if (!this->fss_error.empty()) {
×
UNCOV
346
        return this->fss_error;
×
347
    }
348

UNCOV
349
    if (!this->fss_prompt.empty()) {
×
UNCOV
350
        return this->fss_prompt;
×
351
    }
352

UNCOV
353
    return this->fss_help;
×
354
}
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