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

tstack / lnav / 30969404283-3079

05 Aug 2026 02:27AM UTC coverage: 70.208% (-0.1%) from 70.346%
30969404283-3079

push

github

tstack
[named-search] validate name, add to filter UI, and add some docs

Related to #1736

161 of 424 new or added lines in 11 files covered. (37.97%)

329 existing lines in 8 files now uncovered.

58269 of 82995 relevant lines covered (70.21%)

680656.69 hits per line

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

26.5
/src/filter_sub_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 <memory>
31
#include <optional>
32
#include <utility>
33

34
#include "filter_sub_source.hh"
35

36
#include "base/attr_line.builder.hh"
37
#include "base/auto_mem.hh"
38
#include "base/func_util.hh"
39
#include "base/itertools.hh"
40
#include "base/opt_util.hh"
41
#include "base/relative_time.hh"
42
#include "base/text_format_enum.hh"
43
#include "cmd.parser.hh"
44
#include "config.h"
45
#include "base/itertools.similar.hh"
46
#include "lnav.hh"
47
#include "lnav.prompt.hh"
48
#include "readline_highlighters.hh"
49
#include "readline_possibilities.hh"
50
#include "sql_util.hh"
51
#include "textinput_curses.hh"
52
#include "textview_curses.hh"
53

54
using namespace lnav::roles::literals;
55

56
filter_sub_source::filter_sub_source(std::shared_ptr<textinput_curses> editor)
2✔
57
    : fss_editor(std::move(editor))
2✔
58
{
59
    static auto& prompt = lnav::prompt::get();
2✔
60

61
    this->fss_editor->set_visible(false);
2✔
62
    this->fss_editor->set_x(28);
2✔
63
    this->fss_editor->tc_popup.set_title("Pattern");
4✔
64
    this->fss_editor->tc_height = 1;
2✔
65
    this->fss_editor->tc_on_change
2✔
66
        = bind_mem(&filter_sub_source::rl_change, this);
4✔
67
    this->fss_editor->tc_on_history_search
2✔
68
        = bind_mem(&lnav::prompt::rl_history_search, &prompt);
4✔
69
    this->fss_editor->tc_on_history_list
2✔
70
        = bind_mem(&lnav::prompt::rl_history_list, &prompt);
4✔
71
    this->fss_editor->tc_on_completion_request
2✔
72
        = bind_mem(&filter_sub_source::rl_completion_request, this);
4✔
73
    this->fss_editor->tc_on_completion
2✔
74
        = bind_mem(&lnav::prompt::rl_completion, &prompt);
4✔
75
    this->fss_editor->tc_on_perform
2✔
76
        = bind_mem(&filter_sub_source::rl_perform, this);
4✔
77
    this->fss_editor->tc_on_blur = bind_mem(&filter_sub_source::rl_blur, this);
2✔
78
    this->fss_editor->tc_on_abort
2✔
79
        = bind_mem(&filter_sub_source::rl_abort, this);
4✔
80
}
2✔
81

82
void
83
filter_sub_source::register_view(textview_curses* tc)
2✔
84
{
85
    text_sub_source::register_view(tc);
2✔
86
    tc->add_child_view(this->fss_editor.get());
2✔
87
    tc->set_ensure_selection(true);
2✔
88
}
2✔
89

90
bool
91
filter_sub_source::list_input_handle_key(listview_curses& lv, const ncinput& ch)
7✔
92
{
93
    if (this->fss_editing) {
7✔
94
        return this->fss_editor->handle_key(ch);
4✔
95
    }
96

97
    switch (ch.eff_text[0]) {
3✔
98
        case 'f': {
×
99
            auto* top_view = *lnav_data.ld_view_stack.top();
×
100
            auto* tss = top_view->get_sub_source();
×
101

102
            tss->toggle_apply_filters();
×
103
            top_view->reload_data();
×
104
            break;
×
105
        }
106
        case ' ':
×
107
        case 't':
108
        case 'D': {
109
            auto* top_view = *lnav_data.ld_view_stack.top();
×
110
            auto rows = this->rows_for(top_view);
×
111
            if (rows.empty() || !lv.get_selection()) {
×
112
                return true;
×
113
            }
114

115
            auto& tf = rows[lv.get_selection().value()];
×
116
            tf->handle_key(top_view, ch);
×
117
            lv.reload_data();
×
NEW
118
            if (tf->affects_filtering()) {
×
NEW
119
                top_view->get_sub_source()->text_filters_changed();
×
120
            }
121
            top_view->reload_data();
×
122
            return true;
×
123
        }
124
        case 'e': {
×
125
            auto* top_view = *lnav_data.ld_view_stack.top();
×
126
            auto* lss
127
                = dynamic_cast<logfile_sub_source*>(top_view->get_sub_source());
×
128
            if (lss != nullptr) {
×
129
                auto curr_filter = lss->get_sql_filter();
×
130
                auto& fs = lss->get_filters();
×
131
                if (!curr_filter) {
×
132
                    auto ef = std::make_shared<empty_filter>(
133
                        text_filter::EXCLUDE, filter_lang_t::SQL, 0);
×
134
                    fs.add_filter(ef);
×
135
                }
136

137
                auto rows = this->rows_for(top_view);
×
138
                auto row_iter = std::find_if(
×
139
                    rows.begin(), rows.end(), [](const auto& fr) {
×
140
                        auto* tfr = dynamic_cast<text_filter_row*>(fr.get());
×
141
                        if (tfr == nullptr) {
×
142
                            return false;
×
143
                        }
144
                        return tfr->tfr_filter->get_index() == 0;
×
145
                    });
146
                auto row_idx = std::distance(rows.begin(), row_iter);
×
147
                lv.set_selection(vis_line_t(row_idx));
×
148
                lv.reload_data();
×
149

150
                this->fss_editing = true;
×
151
                this->tss_view->set_enabled(false);
×
152
                this->fss_view_text_possibilities
153
                    = view_text_possibilities(*top_view);
×
154
                (*row_iter)->prime_text_input(
×
155
                    top_view, *this->fss_editor, *this);
×
156
                this->fss_editor->set_y(lv.get_y_for_selection());
×
157
                this->fss_editor->set_visible(true);
×
158
                this->fss_editor->focus();
×
159
                this->fss_filter_state = true;
×
160
            } else {
×
161
            }
162
            return true;
×
163
        }
164
        case 'l': {
×
165
            auto* top_view = *lnav_data.ld_view_stack.top();
×
166
            auto* tss = top_view->get_sub_source();
×
167
            if (tss->get_min_log_level() == LEVEL_UNKNOWN) {
×
168
                tss->set_min_log_level(LEVEL_TRACE);
×
169
            }
170
            const auto& [index, row]
×
171
                = this->find_row<level_filter_row>(top_view);
×
172
            lv.set_selection(index);
×
173
            lv.reload_data();
×
174
            this->fss_editing = true;
×
175
            this->tss_view->set_enabled(false);
×
176
            row->prime_text_input(top_view, *this->fss_editor, *this);
×
177
            this->fss_editor->set_y(lv.get_y_for_selection());
×
178
            this->fss_editor->set_visible(true);
×
179
            this->fss_editor->focus();
×
180
            this->tss_view->reload_data();
×
181
            return true;
×
182
        }
183
        case 'm': {
×
184
            auto* top_view = *lnav_data.ld_view_stack.top();
×
185
            auto* ttt = dynamic_cast<text_time_translator*>(
×
186
                top_view->get_sub_source());
×
187
            if (ttt != nullptr) {
×
188
                auto curr_min = ttt->get_min_row_time();
×
189
                this->fss_filter_state = curr_min.has_value();
×
190
                if (curr_min) {
×
191
                    this->fss_min_time = curr_min;
×
192
                    ttt->set_min_row_time(text_time_translator::min_time_init);
×
193
                    ttt->ttt_preview_min_time = curr_min;
×
194
                    top_view->get_sub_source()->text_filters_changed();
×
195
                } else {
196
                    auto sel = top_view->get_selection();
×
197
                    if (sel) {
×
198
                        auto ri_opt = ttt->time_for_row(sel.value());
×
199
                        if (ri_opt) {
×
200
                            auto ri = ri_opt.value();
×
201

202
                            this->fss_min_time = ri.ri_time;
×
203
                        }
204
                    }
205
                    if (!this->fss_min_time) {
×
206
                        this->fss_min_time = current_timeval();
×
207
                    }
208
                    ttt->ttt_preview_min_time = this->fss_min_time;
×
209
                }
210

211
                const auto& [index, row]
×
212
                    = this->find_row<min_time_filter_row>(top_view);
×
213
                lv.set_selection(index);
×
214
                lv.reload_data();
×
215
                this->fss_editing = true;
×
216
                this->tss_view->set_enabled(false);
×
217
                row->prime_text_input(top_view, *this->fss_editor, *this);
×
218
                this->fss_editor->set_y(lv.get_y_for_selection());
×
219
                this->fss_editor->set_visible(true);
×
220
                this->fss_editor->focus();
×
221
                this->tss_view->reload_data();
×
222
                return true;
×
223
            }
224
            break;
×
225
        }
226
        case 'M': {
×
227
            auto* top_view = *lnav_data.ld_view_stack.top();
×
228
            auto* ttt = dynamic_cast<text_time_translator*>(
×
229
                top_view->get_sub_source());
×
230
            if (ttt != nullptr) {
×
231
                auto curr_max = ttt->get_max_row_time();
×
232
                this->fss_filter_state = curr_max.has_value();
×
233
                if (curr_max) {
×
234
                    this->fss_max_time = curr_max;
×
235
                    ttt->ttt_preview_max_time = curr_max;
×
236
                    ttt->set_max_row_time(text_time_translator::max_time_init);
×
237
                    top_view->get_sub_source()->text_filters_changed();
×
238
                } else {
239
                    auto sel = top_view->get_selection();
×
240
                    if (sel) {
×
241
                        auto ri_opt = ttt->time_for_row(sel.value());
×
242
                        if (ri_opt) {
×
243
                            auto ri = ri_opt.value();
×
244

245
                            this->fss_max_time = ri.ri_time;
×
246
                        }
247
                    }
248
                    if (!this->fss_max_time) {
×
249
                        this->fss_max_time = current_timeval();
×
250
                    }
251
                    ttt->ttt_preview_max_time = this->fss_max_time;
×
252
                }
253

254
                const auto& [index, row]
×
255
                    = this->find_row<max_time_filter_row>(top_view);
×
256
                lv.set_selection(index);
×
257
                lv.reload_data();
×
258
                this->fss_editing = true;
×
259
                this->tss_view->set_enabled(false);
×
260
                row->prime_text_input(top_view, *this->fss_editor, *this);
×
261
                this->fss_editor->set_y(lv.get_y_for_selection());
×
262
                this->fss_editor->set_visible(true);
×
263
                this->fss_editor->focus();
×
264
                this->tss_view->reload_data();
×
265
                return true;
×
266
            }
267
            break;
×
268
        }
269
        case 'i':
1✔
270
        case 'o': {
271
            auto* top_view = *lnav_data.ld_view_stack.top();
1✔
272
            auto* tss = top_view->get_sub_source();
1✔
273
            auto& fs = tss->get_filters();
1✔
274
            auto filter_index = fs.next_index();
1✔
275

276
            if (!filter_index) {
1✔
277
                lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
278
                    "error: too many filters");
279
                return true;
×
280
            }
281

282
            auto filter_type = ch.eff_text[0] == 'i'
1✔
283
                ? text_filter::type_t::INCLUDE
1✔
284
                : text_filter::type_t::EXCLUDE;
285
            auto ef = std::make_shared<empty_filter>(
286
                filter_type, filter_lang_t::REGEX, *filter_index);
1✔
287
            fs.add_filter(ef);
1✔
288

289
            auto rows = this->rows_for(top_view);
1✔
290
            lv.set_selection(vis_line_t(rows.size()) - 1_vl);
1✔
291
            auto& row = rows.back();
1✔
292
            lv.reload_data();
1✔
293

294
            this->fss_editing = true;
1✔
295
            this->tss_view->set_enabled(false);
1✔
296
            this->fss_view_text_possibilities
297
                = view_text_possibilities(*top_view);
1✔
298
            row->prime_text_input(top_view, *this->fss_editor, *this);
1✔
299
            this->fss_editor->set_y(lv.get_y_for_selection());
1✔
300
            this->fss_editor->set_visible(true);
1✔
301
            this->fss_editor->focus();
1✔
302
            this->fss_filter_state = true;
1✔
303
            return true;
1✔
304
        }
1✔
NEW
305
        case 's': {
×
NEW
306
            auto* top_view = *lnav_data.ld_view_stack.top();
×
307

NEW
308
            this->fss_new_named_search = true;
×
309

NEW
310
            auto rows = this->rows_for(top_view);
×
NEW
311
            lv.set_selection(vis_line_t(rows.size()) - 1_vl);
×
NEW
312
            auto& row = rows.back();
×
NEW
313
            lv.reload_data();
×
314

NEW
315
            this->fss_editing = true;
×
NEW
316
            this->tss_view->set_enabled(false);
×
317
            this->fss_view_text_possibilities
NEW
318
                = view_text_possibilities(*top_view);
×
NEW
319
            row->prime_text_input(top_view, *this->fss_editor, *this);
×
NEW
320
            this->fss_editor->set_y(lv.get_y_for_selection());
×
NEW
321
            this->fss_editor->set_visible(true);
×
NEW
322
            this->fss_editor->focus();
×
NEW
323
            this->fss_filter_state = true;
×
NEW
324
            return true;
×
325
        }
UNCOV
326
        case '\r':
×
327
        case NCKEY_ENTER: {
328
            auto* top_view = *lnav_data.ld_view_stack.top();
×
329
            auto rows = this->rows_for(top_view);
×
330

331
            if (rows.empty() || !lv.get_selection()) {
×
332
                return true;
×
333
            }
334

335
            auto& row = rows[lv.get_selection().value()];
×
336
            this->fss_editing = true;
×
337
            this->tss_view->set_enabled(false);
×
338
            this->fss_editor->set_y(lv.get_y_for_selection());
×
339
            this->fss_editor->set_visible(true);
×
340
            this->fss_editor->tc_suggestion.clear();
×
341
            this->fss_view_text_possibilities
342
                = view_text_possibilities(*top_view);
×
343
            this->fss_editor->focus();
×
344
            this->fss_filter_state
345
                = row->prime_text_input(top_view, *this->fss_editor, *this);
×
346
            top_view->get_sub_source()->text_filters_changed();
×
347
            return true;
×
348
        }
349
        case 'n': {
×
350
            lnav_data.ld_exec_context.execute(INTERNAL_SRC_LOC,
×
351
                                              ":next-mark search");
352
            return true;
×
353
        }
354
        case 'N': {
×
355
            lnav_data.ld_exec_context.execute(INTERNAL_SRC_LOC,
×
356
                                              ":prev-mark search");
357
            return true;
×
358
        }
359
        case '/': {
×
360
            lnav_data.ld_exec_context.execute(INTERNAL_SRC_LOC,
×
361
                                              ":prompt search-filters");
362
            return true;
×
363
        }
364
        default:
2✔
365
            log_debug("unhandled %x", ch.eff_text[0]);
2✔
366
            break;
2✔
367
    }
368

369
    return false;
2✔
370
}
371

372
size_t
373
filter_sub_source::text_line_count()
79✔
374
{
375
    return (lnav_data.ld_view_stack.top() |
×
376
                [this](auto tc) -> std::optional<size_t> {
59✔
377
               auto* tss = tc->get_sub_source();
59✔
378

379
               if (tss == nullptr) {
59✔
380
                   return std::nullopt;
×
381
               }
382
               auto rows = this->rows_for(tc);
59✔
383
               return std::make_optional(rows.size());
59✔
384
           })
138✔
385
        .value_or(0);
79✔
386
}
387

388
size_t
389
filter_sub_source::text_line_width(textview_curses& curses)
×
390
{
391
    auto* top_view = *lnav_data.ld_view_stack.top();
×
392
    auto* tss = top_view->get_sub_source();
×
393
    auto& fs = tss->get_filters();
×
394
    size_t retval = 0;
×
395

396
    for (auto& filter : fs) {
×
397
        retval = std::max(filter->get_id().size() + 8, retval);
×
398
    }
NEW
399
    for (const auto& ns : top_view->get_named_searches()) {
×
NEW
400
        retval = std::max(ns.ns_name.size() + ns.ns_pattern.size() + 9, retval);
×
401
    }
402

403
    return retval;
×
404
}
405

406
line_info
407
filter_sub_source::text_value_for_line(textview_curses& tc,
1✔
408
                                       int line,
409
                                       std::string& value_out,
410
                                       line_flags_t flags)
411
{
412
    auto* top_view = *lnav_data.ld_view_stack.top();
1✔
413
    auto selected
414
        = lnav_data.ld_mode == ln_mode_t::FILTER && line == tc.get_selection();
1✔
415
    auto rows = this->rows_for(top_view);
1✔
416
    auto rs = render_state{top_view};
1✔
417
    auto& al = this->fss_curr_line;
1✔
418

419
    al.clear();
1✔
420
    if (selected && this->fss_editing) {
1✔
421
        rs.rs_editing = true;
1✔
422
    }
423
    if (selected) {
1✔
424
        al.append(" ", VC_GRAPHIC.value(NCACS_RARROW));
1✔
425
    } else {
426
        al.append(" ");
×
427
    }
428
    al.append(" ");
1✔
429

430
    auto& row = rows[line];
1✔
431

432
    row->value_for(rs, al);
1✔
433
    if (selected) {
1✔
434
        al.with_attr_for_all(VC_ROLE.value(role_t::VCR_FOCUSED));
1✔
435
    }
436

437
    value_out = al.al_string;
1✔
438
    return {};
2✔
439
}
1✔
440

441
void
442
filter_sub_source::text_attrs_for_line(textview_curses& tc,
1✔
443
                                       int line,
444
                                       string_attrs_t& value_out)
445
{
446
    value_out = this->fss_curr_line.get_attrs();
1✔
447
}
1✔
448

449
size_t
450
filter_sub_source::text_size_for_line(textview_curses& tc,
×
451
                                      int line,
452
                                      text_sub_source::line_flags_t raw)
453
{
454
    // XXX
455
    return 40;
×
456
}
457

458
void
459
filter_sub_source::rl_change(textinput_curses& rc)
3✔
460
{
461
    static auto& prompt = lnav::prompt::get();
3✔
462

463
    if (rc.tc_popup_type == textinput_curses::popup_type_t::history
3✔
464
        && !prompt.p_replace_from_history)
×
465
    {
466
        rc.tc_on_history_search(rc);
×
467
        return;
×
468
    }
469

470
    auto* top_view = *lnav_data.ld_view_stack.top();
3✔
471
    auto rows = this->rows_for(top_view);
3✔
472
    auto& row = rows[this->tss_view->get_selection().value()];
3✔
473

474
    row->ti_change(top_view, rc);
3✔
475
}
3✔
476

477
void
478
filter_sub_source::rl_completion_request_int(textinput_curses& tc,
×
479
                                             completion_request_type_t crt)
480
{
481
    auto* top_view = *lnav_data.ld_view_stack.top();
×
482
    auto rows = this->rows_for(top_view);
×
483
    auto& row = rows[this->tss_view->get_selection().value()];
×
484

485
    row->ti_completion_request(top_view, tc, crt);
×
486
}
487

488
void
489
filter_sub_source::rl_completion_request(textinput_curses& tc)
×
490
{
491
    this->rl_completion_request_int(tc, completion_request_type_t::full);
×
492
}
493

494
void
495
filter_sub_source::rl_completion(textinput_curses& tc)
×
496
{
497
    static auto& prompt = lnav::prompt::get();
498

499
    prompt.rl_completion(tc);
×
500
}
501

502
void
503
filter_sub_source::rl_perform(textinput_curses& rc)
1✔
504
{
505
    auto* top_view = *lnav_data.ld_view_stack.top();
1✔
506
    auto rows = this->rows_for(top_view);
1✔
507
    auto& row = rows[this->tss_view->get_selection().value()];
1✔
508

509
    row->ti_perform(top_view, rc, *this);
1✔
510
    this->fss_min_time = std::nullopt;
1✔
511
    this->fss_max_time = std::nullopt;
1✔
512
    this->fss_new_named_search = false;
1✔
513
    this->tss_view->reload_data();
1✔
514
}
1✔
515

516
void
517
filter_sub_source::rl_blur(textinput_curses& tc)
1✔
518
{
519
    auto* top_view = *lnav_data.ld_view_stack.top();
1✔
520
    top_view->clear_preview();
1✔
521
    lnav_data.ld_filter_help_status_source.fss_prompt.clear();
1✔
522
    lnav_data.ld_filter_help_status_source.fss_error.clear();
1✔
523
    this->fss_editing = false;
1✔
524
    tc.set_visible(false);
1✔
525
    this->tss_view->set_enabled(true);
1✔
526
}
1✔
527

528
void
529
filter_sub_source::rl_abort(textinput_curses& rc)
×
530
{
531
    auto* top_view = *lnav_data.ld_view_stack.top();
×
532
    auto rows = this->rows_for(top_view);
×
533
    auto& row = rows[this->tss_view->get_selection().value()];
×
534

535
    row->ti_abort(top_view, rc, *this);
×
536
    this->fss_min_time = std::nullopt;
×
537
    this->fss_max_time = std::nullopt;
×
NEW
538
    this->fss_new_named_search = false;
×
UNCOV
539
    this->tss_view->reload_data();
×
540
}
541

542
void
543
filter_sub_source::list_input_handle_scroll_out(listview_curses& lv)
×
544
{
545
    set_view_mode(ln_mode_t::PAGING);
×
546
    lnav_data.ld_filter_view.reload_data();
×
547
}
548

549
bool
550
filter_sub_source::text_handle_mouse(
×
551
    textview_curses& tc,
552
    const listview_curses::display_line_content_t&,
553
    mouse_event& me)
554
{
555
    if (this->fss_editing) {
×
556
        return true;
×
557
    }
558
    auto nci = ncinput{};
×
559
    if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 1, 3)) {
×
560
        nci.id = ' ';
×
561
        nci.eff_text[0] = ' ';
×
562
        this->list_input_handle_key(tc, nci);
×
563
    }
564
    if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 4, 7)) {
×
565
        nci.id = 't';
×
566
        nci.eff_text[0] = 't';
×
567
        this->list_input_handle_key(tc, nci);
×
568
    }
569
    if (me.is_double_click_in(mouse_button_t::BUTTON_LEFT, line_range{25, -1}))
×
570
    {
571
        nci.id = '\r';
×
572
        nci.eff_text[0] = '\r';
×
573
        this->list_input_handle_key(tc, nci);
×
574
    }
575
    return true;
×
576
}
577

578
bool
579
filter_sub_source::min_time_filter_row::handle_key(textview_curses* top_view,
×
580
                                                   const ncinput& ch)
581
{
582
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
583
    switch (ch.eff_text[0]) {
×
584
        case 'D':
×
585
            ttt->set_min_row_time(text_time_translator::min_time_init);
×
586
            top_view->get_sub_source()->text_filters_changed();
×
587
            return true;
×
588
        default:
×
589
            return false;
×
590
    }
591
}
592

593
void
594
filter_sub_source::min_time_filter_row::value_for(const render_state& rs,
×
595
                                                  attr_line_t& al)
596
{
597
    al.append("Min Time"_table_header).append(" ");
×
598
    if (rs.rs_editing) {
×
599
        al.append(fmt::format(FMT_STRING("{:>9}"), "-"),
×
600
                  VC_ROLE.value(role_t::VCR_NUMBER));
×
601
    } else {
602
        al.append(fmt::format(
×
603
                      FMT_STRING("{:>9}"),
×
604
                      rs.rs_top_view->get_sub_source()->get_filtered_before()),
×
605
                  VC_ROLE.value(role_t::VCR_NUMBER));
×
606
    }
607
    al.append(" hits ").append("|", VC_GRAPHIC.value(NCACS_VLINE)).append(" ");
×
608
    al.append(lnav::to_rfc3339_string(this->tfr_time, 'T'));
×
609
}
610

611
Result<timeval, std::string>
612
filter_sub_source::time_filter_row::parse_time(textview_curses* top_view,
×
613
                                               textinput_curses& tc)
614
{
615
    auto content = tc.get_content();
×
616
    if (content.empty()) {
×
617
        return Err(std::string("expecting a timestamp or relative time"));
×
618
    }
619

620
    auto parse_res = relative_time::from_str(content);
×
621
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
622
    date_time_scanner dts;
×
623

624
    if (top_view->get_inner_height() > 0_vl) {
×
625
        auto top_time_opt = ttt->time_for_row(
×
626
            top_view->get_selection().value_or(top_view->get_top()));
×
627

628
        if (top_time_opt) {
×
629
            auto top_time_tv = top_time_opt.value().ri_time;
×
630
            tm top_tm;
631

632
            localtime_r(&top_time_tv.tv_sec, &top_tm);
×
633
            dts.set_base_time(top_time_tv.tv_sec, top_tm);
×
634
        }
635
    }
636
    if (parse_res.isOk()) {
×
637
        auto rt = parse_res.unwrap();
×
638
        auto tv_opt
639
            = ttt->time_for_row(top_view->get_selection().value_or(0_vl));
×
640
        auto tv = current_timeval();
×
641
        if (tv_opt) {
×
642
            tv = tv_opt.value().ri_time;
×
643
        }
644
        auto tm = rt.adjust(tv);
×
645
        return Ok(tm.to_timeval());
×
646
    }
647
    auto time_str = tc.get_content();
×
648
    exttm tm;
×
649
    timeval tv;
650
    const auto* scan_end
651
        = dts.scan(time_str.c_str(), time_str.size(), nullptr, &tm, tv);
×
652
    if (scan_end != nullptr) {
×
653
        auto matched_size = scan_end - time_str.c_str();
×
654
        if (matched_size == time_str.size()) {
×
655
            return Ok(tv);
×
656
        }
657

658
        return Err(fmt::format(FMT_STRING("extraneous input '{}'"), scan_end));
×
659
    }
660

661
    auto pe = parse_res.unwrapErr();
×
662
    return Err(pe.pe_msg);
×
663
}
664

665
void
666
filter_sub_source::min_time_filter_row::ti_perform(textview_curses* top_view,
×
667
                                                   textinput_curses& tc,
668
                                                   filter_sub_source& parent)
669
{
670
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
671
    auto parse_res = this->parse_time(top_view, tc);
×
672
    if (parse_res.isOk()) {
×
673
        auto tv = parse_res.unwrap();
×
674

675
        ttt->set_min_row_time(tv);
×
676
    } else {
677
        auto msg = parse_res.unwrapErr();
×
678
        if (parent.fss_filter_state) {
×
679
            ttt->set_min_row_time(parent.fss_min_time.value());
×
680
        }
681

682
        auto um
683
            = lnav::console::user_message::error("could not parse timestamp")
×
684
                  .with_reason(msg);
×
685
        lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
686
    }
687
    parent.fss_min_time = std::nullopt;
×
688
    top_view->get_sub_source()->text_filters_changed();
×
689
}
690

691
void
692
filter_sub_source::min_time_filter_row::ti_abort(textview_curses* top_view,
×
693
                                                 textinput_curses& tc,
694
                                                 filter_sub_source& parent)
695
{
696
    auto* tss = top_view->get_sub_source();
×
697
    auto* ttt = dynamic_cast<text_time_translator*>(tss);
×
698

699
    if (parent.fss_filter_state) {
×
700
        ttt->set_min_row_time(parent.fss_min_time.value());
×
701
    }
702
    tss->text_filters_changed();
×
703
}
704

705
void
706
filter_sub_source::level_filter_row::value_for(const render_state& rs,
×
707
                                               attr_line_t& al)
708
{
709
    auto lev = rs.rs_top_view->get_sub_source()->get_min_log_level();
×
710

711
    al.append("  ").append("Level"_table_header).append("  ");
×
712
    if (rs.rs_editing) {
×
713
        al.append(fmt::format(FMT_STRING("{:>9}"), "-"),
×
714
                  VC_ROLE.value(role_t::VCR_NUMBER));
×
715
    } else {
716
        al.append(
×
717
            fmt::format(
×
718
                FMT_STRING("{:>9}"),
×
719
                rs.rs_top_view->get_sub_source()->tss_level_filtered_count),
×
720
            VC_ROLE.value(role_t::VCR_NUMBER));
×
721
    }
722
    al.append(" hits ")
×
723
        .append("|", VC_GRAPHIC.value(NCACS_VLINE))
×
724
        .append(" ")
×
725
        .append(level_names[lev]);
×
726
}
727

728
bool
729
filter_sub_source::level_filter_row::handle_key(textview_curses* top_view,
×
730
                                                const ncinput& ch)
731
{
732
    switch (ch.eff_text[0]) {
×
733
        case 'D':
×
734
            top_view->get_sub_source()->set_min_log_level(LEVEL_UNKNOWN);
×
735
            return true;
×
736
    }
737
    return false;
×
738
}
739

740
bool
741
filter_sub_source::level_filter_row::prime_text_input(textview_curses* top_view,
×
742
                                                      textinput_curses& ti,
743
                                                      filter_sub_source& parent)
744
{
745
    auto* tss = top_view->get_sub_source();
×
746
    auto lev = tss->get_min_log_level();
×
747
    parent.fss_curr_level = lev;
×
748
    tss->set_min_log_level(LEVEL_TRACE);
×
749
    tss->text_filters_changed();
×
750
    ti.set_content(level_names[lev].to_string());
×
751
    ti.tc_selection
752
        = ti.clamp_selection(textinput_curses::selected_range::from_mouse(
×
753
            textinput_curses::input_point::home(),
754
            textinput_curses::input_point::end()));
×
755
    return true;
×
756
}
757

758
void
759
filter_sub_source::level_filter_row::ti_change(textview_curses* top_view,
×
760
                                               textinput_curses& rc)
761
{
762
    auto lev = rc.get_content();
×
763
    top_view->get_sub_source()->tss_preview_min_log_level
×
764
        = string2level(lev.c_str(), lev.size(), false);
×
765
    this->ti_completion_request(
×
766
        top_view, rc, completion_request_type_t::partial);
767
}
768

769
void
770
filter_sub_source::level_filter_row::ti_completion_request(
×
771
    textview_curses* top_view,
772
    textinput_curses& tc,
773
    completion_request_type_t crt)
774
{
775
    auto lev = tc.get_content();
×
776
    auto poss = level_names | lnav::itertools::similar_to(lev)
×
777
        | lnav::itertools::map([](const auto& elem) {
×
778
                    return attr_line_t().append(elem).with_attr_for_all(
×
779
                        lnav::prompt::SUBST_TEXT.value(elem.to_string()));
×
780
                });
×
781
    tc.open_popup_for_completion(0, poss);
×
782
}
783

784
void
785
filter_sub_source::level_filter_row::ti_perform(textview_curses* top_view,
×
786
                                                textinput_curses& tc,
787
                                                filter_sub_source& parent)
788
{
789
    auto lev = tc.get_content();
×
790
    auto new_level = string2level(lev.c_str(), lev.size(), false);
×
791
    top_view->get_sub_source()->set_min_log_level(new_level);
×
792
}
793

794
void
795
filter_sub_source::level_filter_row::ti_abort(textview_curses* top_view,
×
796
                                              textinput_curses& tc,
797
                                              filter_sub_source& parent)
798
{
799
    top_view->get_sub_source()->set_min_log_level(parent.fss_curr_level);
×
800
}
801

802
bool
803
filter_sub_source::time_filter_row::prime_text_input(textview_curses* top_view,
×
804
                                                     textinput_curses& ti,
805
                                                     filter_sub_source& parent)
806
{
807
    ti.tc_text_format = text_format_t::TF_PLAINTEXT;
×
808
    ti.set_content(lnav::to_rfc3339_string(this->tfr_time, 'T'));
×
809
    return true;
×
810
}
811

812
void
813
filter_sub_source::min_time_filter_row::ti_change(textview_curses* top_view,
×
814
                                                  textinput_curses& rc)
815
{
816
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
817
    auto parse_res = this->parse_time(top_view, rc);
×
818

819
    if (parse_res.isOk()) {
×
820
        auto tv = parse_res.unwrap();
×
821
        ttt->ttt_preview_min_time = tv;
×
822
        lnav_data.ld_filter_help_status_source.fss_error.clear();
×
823
    } else {
824
        ttt->ttt_preview_min_time = std::nullopt;
×
825
        auto msg = parse_res.unwrapErr();
×
826
        lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
827
            "error: could not parse timestamp -- %s", msg.c_str());
828
    }
829
}
830

831
void
832
filter_sub_source::max_time_filter_row::ti_change(textview_curses* top_view,
×
833
                                                  textinput_curses& rc)
834
{
835
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
836
    auto parse_res = this->parse_time(top_view, rc);
×
837

838
    if (parse_res.isOk()) {
×
839
        auto tv = parse_res.unwrap();
×
840
        ttt->ttt_preview_max_time = tv;
×
841
        lnav_data.ld_filter_help_status_source.fss_error.clear();
×
842
    } else {
843
        ttt->ttt_preview_max_time = std::nullopt;
×
844
        auto msg = parse_res.unwrapErr();
×
845
        lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
846
            "error: could not parse timestamp -- %s", msg.c_str());
847
    }
848
}
849

850
void
851
filter_sub_source::time_filter_row::ti_completion_request(
×
852
    textview_curses* top_view,
853
    textinput_curses& tc,
854
    completion_request_type_t crt)
855
{
856
}
857

858
bool
859
filter_sub_source::max_time_filter_row::handle_key(textview_curses* top_view,
×
860
                                                   const ncinput& ch)
861
{
862
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
863
    switch (ch.eff_text[0]) {
×
864
        case 'D':
×
865
            ttt->set_max_row_time(text_time_translator::max_time_init);
×
866
            top_view->get_sub_source()->text_filters_changed();
×
867
            return true;
×
868
        default:
×
869
            return false;
×
870
    }
871
}
872

873
void
874
filter_sub_source::max_time_filter_row::value_for(const render_state& rs,
×
875
                                                  attr_line_t& al)
876
{
877
    al.append("Max Time"_table_header).append(" ");
×
878
    if (rs.rs_editing) {
×
879
        al.append(fmt::format(FMT_STRING("{:>9}"), "-"),
×
880
                  VC_ROLE.value(role_t::VCR_NUMBER));
×
881
    } else {
882
        al.append(
×
883
            fmt::format(FMT_STRING("{:>9}"),
×
884
                        rs.rs_top_view->get_sub_source()->get_filtered_after()),
×
885
            VC_ROLE.value(role_t::VCR_NUMBER));
×
886
    }
887
    al.append(" hits ").append("|", VC_GRAPHIC.value(NCACS_VLINE)).append(" ");
×
888
    al.append(lnav::to_rfc3339_string(this->tfr_time, 'T'));
×
889
}
890

891
void
892
filter_sub_source::max_time_filter_row::ti_perform(textview_curses* top_view,
×
893
                                                   textinput_curses& tc,
894
                                                   filter_sub_source& parent)
895
{
896
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
897
    auto parse_res = this->parse_time(top_view, tc);
×
898
    if (parse_res.isOk()) {
×
899
        auto tv = parse_res.unwrap();
×
900

901
        ttt->set_max_row_time(tv);
×
902
    } else {
903
        auto msg = parse_res.unwrapErr();
×
904
        if (parent.fss_filter_state) {
×
905
            ttt->set_max_row_time(parent.fss_max_time.value());
×
906
        }
907
        auto um
908
            = lnav::console::user_message::error("could not parse timestamp")
×
909
                  .with_reason(msg);
×
910
        lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
911
    }
912
    parent.fss_max_time = std::nullopt;
×
913
    top_view->get_sub_source()->text_filters_changed();
×
914
}
915

916
void
917
filter_sub_source::max_time_filter_row::ti_abort(textview_curses* top_view,
×
918
                                                 textinput_curses& tc,
919
                                                 filter_sub_source& parent)
920
{
921
    auto* tss = top_view->get_sub_source();
×
922
    auto* ttt = dynamic_cast<text_time_translator*>(tss);
×
923

924
    if (parent.fss_filter_state) {
×
925
        ttt->set_max_row_time(parent.fss_max_time.value());
×
926
    }
927
    tss->text_filters_changed();
×
928
}
929

930
void
931
filter_sub_source::text_filter_row::value_for(const render_state& rs,
1✔
932
                                              attr_line_t& al)
933
{
934
    attr_line_builder alb(al);
1✔
935
    if (this->tfr_filter->is_enabled()) {
1✔
936
        al.append("\u25c6"_ok);
×
937
    } else {
938
        al.append("\u25c7"_comment);
1✔
939
    }
940
    al.append(" ");
1✔
941
    switch (this->tfr_filter->get_type()) {
1✔
942
        case text_filter::INCLUDE:
×
943
            al.append(" ").append(lnav::roles::ok("IN")).append(" ");
×
944
            break;
×
945
        case text_filter::EXCLUDE:
1✔
946
            if (this->tfr_filter->get_lang() == filter_lang_t::REGEX) {
1✔
947
                al.append(lnav::roles::error("OUT")).append(" ");
1✔
948
            } else {
949
                al.append("    ");
×
950
            }
951
            break;
1✔
952
        default:
×
953
            ensure(0);
×
954
            break;
955
    }
956
    al.append("   ");
1✔
957

958
    {
959
        auto ag = alb.with_attr(VC_ROLE.value(role_t::VCR_NUMBER));
1✔
960
        if (rs.rs_editing) {
1✔
961
            alb.appendf(FMT_STRING("{:>9}"), "-");
3✔
962
        } else {
963
            alb.appendf(
×
964
                FMT_STRING("{:>9L}"),
×
965
                rs.rs_top_view->get_sub_source()->get_filtered_count_for(
×
966
                    this->tfr_filter->get_index()));
×
967
        }
968
    }
1✔
969

970
    al.append(" hits ").append("|", VC_GRAPHIC.value(NCACS_VLINE)).append(" ");
1✔
971

972
    attr_line_t content{this->tfr_filter->get_id()};
2✔
973
    switch (this->tfr_filter->get_lang()) {
1✔
974
        case filter_lang_t::REGEX:
1✔
975
            readline_regex_highlighter(content, std::nullopt);
1✔
976
            break;
1✔
977
        case filter_lang_t::SQL:
×
978
            readline_sql_highlighter(
×
979
                content, lnav::sql::dialect::sqlite, std::nullopt);
980
            break;
×
981
        case filter_lang_t::NONE:
×
982
            break;
×
983
    }
984
    al.append(content);
1✔
985
}
1✔
986

987
bool
988
filter_sub_source::text_filter_row::handle_key(textview_curses* top_view,
×
989
                                               const ncinput& ch)
990
{
991
    switch (ch.eff_text[0]) {
×
992
        case ' ': {
×
993
            auto& fs = top_view->get_sub_source()->get_filters();
×
994
            fs.set_filter_enabled(this->tfr_filter,
×
995
                                  !this->tfr_filter->is_enabled());
×
996
            return true;
×
997
        }
998
        case 't': {
×
999
            if (this->tfr_filter->get_type() == text_filter::INCLUDE) {
×
1000
                this->tfr_filter->set_type(text_filter::EXCLUDE);
×
1001
            } else {
1002
                this->tfr_filter->set_type(text_filter::INCLUDE);
×
1003
            }
1004
            return true;
×
1005
        }
1006
        case 'D': {
×
1007
            auto& fs = top_view->get_sub_source()->get_filters();
×
1008

1009
            fs.delete_filter(this->tfr_filter->get_id());
×
1010
            return true;
×
1011
        }
1012
        default:
×
1013
            return false;
×
1014
    }
1015
}
1016

1017
bool
1018
filter_sub_source::text_filter_row::prime_text_input(textview_curses* top_view,
1✔
1019
                                                     textinput_curses& ti,
1020
                                                     filter_sub_source& parent)
1021
{
1022
    static auto& prompt = lnav::prompt::get();
1✔
1023

1024
    auto context = this->tfr_filter->get_lang() == filter_lang_t::SQL
1✔
1025
        ? lnav::prompt::context_t::sql_filter
1✔
1026
        : lnav::prompt::context_t::regex_filter;
1✔
1027
    prompt.focus_for(*top_view, ti, context, '\0', {});
1✔
1028
    ti.tc_text_format = this->tfr_filter->get_lang() == filter_lang_t::SQL
1✔
1029
        ? text_format_t::TF_SQL
1✔
1030
        : text_format_t::TF_PCRE;
1031
    if (this->tfr_filter->get_lang() == filter_lang_t::SQL) {
1✔
1032
        prompt.refresh_sql_completions(*top_view);
×
1033
        prompt.refresh_sql_expr_completions(*top_view);
×
1034
    }
1035
    ti.set_content(this->tfr_filter->get_id());
1✔
1036
    if (this->tfr_filter->get_id().empty()) {
1✔
1037
        ti.tc_suggestion = top_view->get_input_suggestion();
1✔
1038
    }
1039
    auto retval = this->tfr_filter->is_enabled();
1✔
1040
    this->tfr_filter->disable();
1✔
1041

1042
    return retval;
1✔
1043
}
1044

1045
void
1046
filter_sub_source::text_filter_row::ti_change(textview_curses* top_view,
3✔
1047
                                              textinput_curses& rc)
1048
{
1049
    auto new_value = rc.get_content();
3✔
1050
    auto* tss = top_view->get_sub_source();
3✔
1051
    auto& fs = tss->get_filters();
3✔
1052

1053
    top_view->get_highlights().erase({highlight_source_t::PREVIEW, "preview"});
3✔
1054
    top_view->set_needs_update();
3✔
1055
    switch (this->tfr_filter->get_lang()) {
3✔
1056
        case filter_lang_t::NONE:
×
1057
            break;
×
1058
        case filter_lang_t::REGEX: {
3✔
1059
            if (new_value.empty()) {
3✔
1060
                auto sugg = top_view->get_current_search();
×
1061
                if (top_view->tc_selected_text) {
×
1062
                    sugg = top_view->tc_selected_text->sti_value;
×
1063
                }
1064
                if (fs.get_filter(sugg) == nullptr) {
×
1065
                    rc.tc_suggestion = sugg;
×
1066
                } else {
1067
                    rc.tc_suggestion.clear();
×
1068
                }
1069
            } else if (new_value == this->tfr_filter->get_id()) {
3✔
1070
            } else {
1071
                auto regex_res
1072
                    = lnav::pcre2pp::code::from(new_value, PCRE2_CASELESS);
3✔
1073

1074
                this->ti_completion_request(
3✔
1075
                    top_view, rc, completion_request_type_t::partial);
1076
                if (regex_res.isErr()) {
3✔
1077
                    auto pe = regex_res.unwrapErr();
×
1078
                    lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
1079
                        "error: %s", pe.get_message().c_str());
×
1080
                } else if (fs.get_filter(new_value) != nullptr) {
3✔
1081
                    lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
1082
                        "error: filter already exists");
1083
                } else {
1084
                    auto& hm = top_view->get_highlights();
3✔
1085
                    highlighter hl(regex_res.unwrap().to_shared());
3✔
1086
                    auto role
1087
                        = this->tfr_filter->get_type() == text_filter::EXCLUDE
3✔
1088
                        ? role_t::VCR_DIFF_DELETE
3✔
1089
                        : role_t::VCR_DIFF_ADD;
3✔
1090
                    hl.with_role(role);
3✔
1091
                    hl.with_attrs(text_attrs::with_styles(
3✔
1092
                        text_attrs::style::blink, text_attrs::style::reverse));
1093
                    hm[{highlight_source_t::PREVIEW, "preview"}] = hl;
3✔
1094
                    top_view->set_needs_update();
3✔
1095
                    lnav_data.ld_filter_help_status_source.fss_error.clear();
3✔
1096
                }
3✔
1097
            }
3✔
1098
            break;
3✔
1099
        }
1100
        case filter_lang_t::SQL: {
×
1101
            this->ti_completion_request(
×
1102
                top_view, rc, completion_request_type_t::partial);
1103

1104
            auto full_sql
1105
                = fmt::format(FMT_STRING("SELECT 1 WHERE {}"), new_value);
×
1106
            auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
×
1107
#ifdef SQLITE_PREPARE_PERSISTENT
1108
            auto retcode = sqlite3_prepare_v3(lnav_data.ld_db.in(),
×
1109
                                              full_sql.c_str(),
1110
                                              full_sql.size(),
×
1111
                                              SQLITE_PREPARE_PERSISTENT,
1112
                                              stmt.out(),
1113
                                              nullptr);
1114
#else
1115
            auto retcode = sqlite3_prepare_v2(lnav_data.ld_db.in(),
1116
                                              full_sql.c_str(),
1117
                                              full_sql.size(),
1118
                                              stmt.out(),
1119
                                              nullptr);
1120
#endif
1121
            if (retcode != SQLITE_OK) {
×
1122
                lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
1123
                    "error: %s", sqlite3_errmsg(lnav_data.ld_db));
1124
            } else {
1125
                auto set_res = lnav_data.ld_log_source.set_preview_sql_filter(
1126
                    stmt.release());
×
1127

1128
                if (set_res.isErr()) {
×
1129
                    lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
1130
                        "error: %s",
1131
                        set_res.unwrapErr()
×
1132
                            .to_attr_line()
×
1133
                            .get_string()
×
1134
                            .c_str());
1135
                } else {
1136
                    top_view->set_needs_update();
×
1137
                    lnav_data.ld_filter_help_status_source.fss_error.clear();
×
1138
                }
1139
            }
1140
            break;
×
1141
        }
1142
    }
1143
}
3✔
1144

1145
void
1146
filter_sub_source::text_filter_row::ti_completion_request(
3✔
1147
    textview_curses* top_view,
1148
    textinput_curses& tc,
1149
    completion_request_type_t crt)
1150
{
1151
    static const auto FILTER_HELP
1152
        = help_text("filter", "filter the view by a pattern")
1✔
1153
              .with_parameter(
1✔
1154
                  help_text("pattern", "The pattern to filter by")
2✔
1155
                      .with_format(help_parameter_format_t::HPF_REGEX));
5✔
1156
    static const auto FILTER_EXPR_HELP
1157
        = help_text("filter-expr", "filter the view by a SQL expression")
1✔
1158
              .with_parameter(
1✔
1159
                  help_text("expr", "The expression to evaluate")
2✔
1160
                      .with_format(help_parameter_format_t::HPF_SQL_EXPR));
5✔
1161
    static auto& prompt = lnav::prompt::get();
3✔
1162

1163
    auto& al = tc.tc_lines[tc.tc_cursor.y];
3✔
1164
    auto al_sf = al.to_string_fragment().sub_cell_range(0, tc.tc_cursor.x);
3✔
1165
    auto is_regex = tc.tc_text_format == text_format_t::TF_PCRE;
3✔
1166
    auto parse_res = lnav::command::parse_for_prompt(
1167
        lnav_data.ld_exec_context,
1168
        al_sf,
1169
        is_regex ? FILTER_HELP : FILTER_EXPR_HELP);
3✔
1170

1171
    switch (crt) {
3✔
1172
        case completion_request_type_t::partial: {
3✔
1173
            if (al_sf.endswith(" ")) {
3✔
1174
                if (tc.is_cursor_at_end_of_line()) {
×
1175
                    tc.tc_suggestion
1176
                        = prompt.get_regex_suggestion(*top_view, al.al_string);
×
1177
                }
1178
                return;
×
1179
            }
1180
            break;
3✔
1181
        }
1182
        case completion_request_type_t::full:
×
1183
            break;
×
1184
    }
1185

1186
    auto byte_x = al_sf.column_to_byte_index(tc.tc_cursor.x);
3✔
1187
    auto arg_res_opt = parse_res.arg_at(byte_x);
3✔
1188
    if (arg_res_opt) {
3✔
1189
        auto arg_pair = arg_res_opt.value();
3✔
1190
        if (crt == completion_request_type_t::full
3✔
1191
            || tc.tc_popup_type != textinput_curses::popup_type_t::none)
3✔
1192
        {
1193
            auto poss = prompt.get_cmd_parameter_completion(
1194
                *top_view,
1195
                &FILTER_HELP,
1196
                arg_pair.aar_help,
1197
                arg_pair.aar_element.se_value);
×
1198
            auto left = arg_pair.aar_element.se_value.empty()
×
1199
                ? tc.tc_cursor.x
×
1200
                : al_sf.byte_to_column_index(
×
1201
                      arg_pair.aar_element.se_origin.sf_begin);
×
1202
            tc.open_popup_for_completion(left, poss);
×
1203
            tc.tc_popup.set_title(arg_pair.aar_help->ht_name);
×
1204
        } else if (arg_pair.aar_element.se_value.empty()
3✔
1205
                   && tc.is_cursor_at_end_of_line())
3✔
1206
        {
1207
            tc.tc_suggestion
1208
                = prompt.get_regex_suggestion(*top_view, al.al_string);
×
1209
        } else {
1210
            log_debug("not at end of line");
3✔
1211
            tc.tc_suggestion.clear();
3✔
1212
        }
1213
    } else {
3✔
1214
        log_debug("no arg");
×
1215
    }
1216
}
3✔
1217

1218
void
1219
filter_sub_source::text_filter_row::ti_perform(textview_curses* top_view,
1✔
1220
                                               textinput_curses& ti,
1221
                                               filter_sub_source& parent)
1222
{
1223
    static const intern_string_t INPUT_SRC = intern_string::lookup("input");
3✔
1224
    static auto& prompt = lnav::prompt::get();
1✔
1225

1226
    auto* tss = top_view->get_sub_source();
1✔
1227
    auto& fs = tss->get_filters();
1✔
1228
    auto new_value = ti.get_content();
1✔
1229

1230
    fs.fs_generation += 1;
1✔
1231
    if (new_value.empty() || new_value == this->tfr_filter->get_id()) {
1✔
1232
        this->ti_abort(top_view, ti, parent);
×
1233
    } else {
1234
        switch (this->tfr_filter->get_lang()) {
1✔
1235
            case filter_lang_t::NONE:
1✔
1236
            case filter_lang_t::REGEX: {
1237
                auto compile_res
1238
                    = lnav::pcre2pp::code::from(new_value, PCRE2_CASELESS);
1✔
1239

1240
                if (compile_res.isErr()) {
1✔
1241
                    auto ce = compile_res.unwrapErr();
×
1242
                    auto um = lnav::console::to_user_message(INPUT_SRC, ce);
×
1243
                    lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
1244
                    this->ti_abort(top_view, ti, parent);
×
1245
                } else if (fs.get_filter(new_value) != nullptr) {
1✔
1246
                    auto snip
1247
                        = lnav::console::snippet::from(INPUT_SRC, new_value);
×
1248
                    auto um = lnav::console::user_message::error(
×
1249
                                  "Filter already exists")
1250
                                  .with_snippet(snip);
×
1251
                    lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
1252
                    this->ti_abort(top_view, ti, parent);
×
1253
                } else {
×
1254
                    auto code_ptr = compile_res.unwrap().to_shared();
1✔
1255
                    this->tfr_filter->lf_deleted = true;
1✔
1256
                    tss->text_filters_changed();
1✔
1257

1258
                    auto pf = std::make_shared<pcre_filter>(
1259
                        this->tfr_filter->get_type(),
1✔
1260
                        new_value,
1261
                        this->tfr_filter->get_index(),
1✔
1262
                        code_ptr);
1✔
1263

1264
                    prompt.get_history_for().insert_plain_content(new_value);
1✔
1265

1266
                    auto iter
1267
                        = std::find(fs.begin(), fs.end(), this->tfr_filter);
1✔
1268

1269
                    *iter = pf;
1✔
1270
                    tss->text_filters_changed();
1✔
1271
                }
1✔
1272
                break;
1✔
1273
            }
1✔
1274
            case filter_lang_t::SQL: {
×
1275
                auto full_sql
1276
                    = fmt::format(FMT_STRING("SELECT 1 WHERE {}"), new_value);
×
1277
                auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
×
1278
#ifdef SQLITE_PREPARE_PERSISTENT
1279
                auto retcode = sqlite3_prepare_v3(lnav_data.ld_db.in(),
×
1280
                                                  full_sql.c_str(),
1281
                                                  full_sql.size(),
×
1282
                                                  SQLITE_PREPARE_PERSISTENT,
1283
                                                  stmt.out(),
1284
                                                  nullptr);
1285
#else
1286
                auto retcode = sqlite3_prepare_v2(lnav_data.ld_db.in(),
1287
                                                  full_sql.c_str(),
1288
                                                  full_sql.size(),
1289
                                                  stmt.out(),
1290
                                                  nullptr);
1291
#endif
1292
                if (retcode != SQLITE_OK) {
×
1293
                    auto sqlerr = annotate_sql_with_error(
1294
                        lnav_data.ld_db.in(), full_sql.c_str(), nullptr);
×
1295
                    auto um
1296
                        = lnav::console::user_message::error(
×
1297
                              "invalid SQL expression")
1298
                              .with_reason(sqlite3_errmsg(lnav_data.ld_db.in()))
×
1299
                              .with_snippet(lnav::console::snippet::from(
×
1300
                                  INPUT_SRC, sqlerr));
×
1301
                    lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
1302
                    this->ti_abort(top_view, ti, parent);
×
1303
                } else {
×
1304
                    prompt.get_history_for().insert_plain_content(new_value);
×
1305
                    lnav_data.ld_log_source.set_sql_filter(new_value,
×
1306
                                                           stmt.release());
1307
                    tss->text_filters_changed();
×
1308
                }
1309
                break;
×
1310
            }
1311
        }
1312
    }
1313

1314
    top_view->reload_data();
1✔
1315
}
1✔
1316

1317
void
1318
filter_sub_source::text_filter_row::ti_abort(textview_curses* top_view,
×
1319
                                             textinput_curses& tc,
1320
                                             filter_sub_source& parent)
1321
{
1322
    auto* tss = top_view->get_sub_source();
×
1323
    auto& fs = tss->get_filters();
×
1324

1325
    top_view->reload_data();
×
1326
    fs.delete_filter("");
×
1327
    this->tfr_filter->set_enabled(parent.fss_filter_state);
×
1328
    tss->text_filters_changed();
×
1329
}
1330

1331
/**
1332
 * The edit line for a named search is the argument text of
1333
 * :create-named-search.  A name cannot contain whitespace, so the first word
1334
 * is the name and whatever follows it is the pattern.
1335
 */
1336
static std::pair<std::string, std::string>
NEW
1337
split_named_search_input(const std::string& content)
×
1338
{
NEW
1339
    auto [name, pattern]
×
NEW
1340
        = string_fragment::from_str(content).trim().split_when(
×
NEW
1341
            [](char ch) { return isspace((unsigned char) ch) != 0; });
×
1342

NEW
1343
    return {name.to_string(), pattern.trim().to_string()};
×
1344
}
1345

1346
void
NEW
1347
filter_sub_source::named_search_row::value_for(const render_state& rs,
×
1348
                                               attr_line_t& al)
1349
{
NEW
1350
    attr_line_builder alb(al);
×
NEW
1351
    const auto* ns = rs.rs_top_view->find_named_search(this->nsr_name);
×
1352

NEW
1353
    if (ns == nullptr || ns->ns_enabled) {
×
NEW
1354
        al.append("\u25c6"_ok);
×
1355
    } else {
NEW
1356
        al.append("\u25c7"_comment);
×
1357
    }
1358
    // The label is as wide as the IN/OUT field of a filter row so that the
1359
    // hit counts in the two kinds of row line up.
NEW
1360
    al.append(" ").append("SRCH"_table_header).append("   ");
×
1361

1362
    {
NEW
1363
        auto ag = alb.with_attr(VC_ROLE.value(role_t::VCR_NUMBER));
×
NEW
1364
        if (rs.rs_editing || ns == nullptr) {
×
NEW
1365
            alb.appendf(FMT_STRING("{:>9}"), "-");
×
NEW
1366
        } else {
×
NEW
1367
            alb.appendf(
×
NEW
1368
                FMT_STRING("{:>9L}"),
×
NEW
1369
                rs.rs_top_view->search_matches_for_slot(ns->ns_slot).size());
×
1370
        }
1371
    }
1372

NEW
1373
    al.append(" hits ").append("|", VC_GRAPHIC.value(NCACS_VLINE)).append(" ");
×
1374

NEW
1375
    if (ns == nullptr) {
×
NEW
1376
        return;
×
1377
    }
1378

1379
    // The name is shown with the background that its matches wear in the
1380
    // view, so the row and the highlighting read as the same thing.
NEW
1381
    auto name_attrs = text_attrs{};
×
NEW
1382
    name_attrs.ta_bg_color = view_colors::singleton().color_for_ident(
×
NEW
1383
        string_fragment::from_str(ns->ns_name));
×
NEW
1384
    al.append(ns->ns_name, VC_STYLE.value(name_attrs)).append(" ");
×
1385

NEW
1386
    attr_line_t content{ns->ns_pattern};
×
NEW
1387
    readline_regex_highlighter(content, std::nullopt);
×
NEW
1388
    al.append(content);
×
1389
}
1390

1391
bool
NEW
1392
filter_sub_source::named_search_row::handle_key(textview_curses* top_view,
×
1393
                                                const ncinput& ch)
1394
{
NEW
1395
    const auto* ns = top_view->find_named_search(this->nsr_name);
×
1396

NEW
1397
    if (ns == nullptr) {
×
NEW
1398
        return false;
×
1399
    }
1400

NEW
1401
    switch (ch.eff_text[0]) {
×
NEW
1402
        case ' ': {
×
NEW
1403
            top_view->set_named_search_enabled(this->nsr_name,
×
NEW
1404
                                               !ns->ns_enabled);
×
NEW
1405
            return true;
×
1406
        }
NEW
1407
        case 'D': {
×
NEW
1408
            top_view->delete_named_search(this->nsr_name);
×
NEW
1409
            return true;
×
1410
        }
NEW
1411
        default:
×
NEW
1412
            return false;
×
1413
    }
1414
}
1415

1416
bool
NEW
1417
filter_sub_source::named_search_row::prime_text_input(
×
1418
    textview_curses* top_view, textinput_curses& ti, filter_sub_source& parent)
1419
{
1420
    static auto& prompt = lnav::prompt::get();
1421

NEW
1422
    prompt.focus_for(
×
1423
        *top_view, ti, lnav::prompt::context_t::regex_filter, '\0', {});
NEW
1424
    ti.tc_text_format = text_format_t::TF_PCRE;
×
1425

NEW
1426
    const auto* ns = top_view->find_named_search(this->nsr_name);
×
NEW
1427
    if (ns == nullptr) {
×
NEW
1428
        ti.set_content("");
×
NEW
1429
        return true;
×
1430
    }
1431

NEW
1432
    ti.set_content(
×
NEW
1433
        fmt::format(FMT_STRING("{} {}"), ns->ns_name, ns->ns_pattern));
×
1434

1435
    // Hide the search while it is being edited so that the preview is the
1436
    // only highlighting in the view.
NEW
1437
    auto retval = ns->ns_enabled;
×
NEW
1438
    top_view->set_named_search_enabled(this->nsr_name, false);
×
1439

NEW
1440
    return retval;
×
1441
}
1442

1443
void
NEW
1444
filter_sub_source::named_search_row::ti_change(textview_curses* top_view,
×
1445
                                               textinput_curses& rc)
1446
{
NEW
1447
    auto& err = lnav_data.ld_filter_help_status_source.fss_error;
×
NEW
1448
    auto [name, pattern] = split_named_search_input(rc.get_content());
×
1449

NEW
1450
    top_view->get_highlights().erase({highlight_source_t::PREVIEW, "preview"});
×
NEW
1451
    top_view->set_needs_update();
×
1452

NEW
1453
    if (name.empty()) {
×
NEW
1454
        err.clear();
×
NEW
1455
        return;
×
1456
    }
1457

NEW
1458
    auto name_res = textview_curses::validate_search_name(name);
×
NEW
1459
    if (name_res.isErr()) {
×
NEW
1460
        err.set_value(
×
1461
            "error: %s",
NEW
1462
            name_res.unwrapErr().um_message.get_string().c_str());
×
NEW
1463
        return;
×
1464
    }
1465

1466
    // A name on its own adopts the active search, the same as leaving the
1467
    // pattern off of :create-named-search.
NEW
1468
    if (pattern.empty()) {
×
NEW
1469
        pattern = top_view->get_current_search();
×
1470
    }
NEW
1471
    if (pattern.empty()) {
×
NEW
1472
        err.set_value("error: no active search to adopt, enter a pattern");
×
NEW
1473
        return;
×
1474
    }
1475

NEW
1476
    this->ti_completion_request(
×
1477
        top_view, rc, completion_request_type_t::partial);
1478

NEW
1479
    auto regex_res = lnav::pcre2pp::code::from(pattern, PCRE2_CASELESS);
×
NEW
1480
    if (regex_res.isErr()) {
×
NEW
1481
        auto pe = regex_res.unwrapErr();
×
NEW
1482
        err.set_value("error: %s", pe.get_message().c_str());
×
NEW
1483
        return;
×
1484
    }
1485

NEW
1486
    const auto* ns = top_view->find_named_search(name);
×
NEW
1487
    if (ns != nullptr && name != this->nsr_name) {
×
NEW
1488
        err.set_value("error: named search already exists");
×
NEW
1489
        return;
×
1490
    }
1491

1492
    // Preview with the background that the search itself will use.
NEW
1493
    highlighter hl(regex_res.unwrap().to_shared());
×
NEW
1494
    auto hl_attrs = text_attrs{};
×
NEW
1495
    hl_attrs.ta_bg_color = view_colors::singleton().color_for_ident(
×
NEW
1496
        string_fragment::from_str(name));
×
NEW
1497
    hl_attrs |= text_attrs::style::blink;
×
NEW
1498
    hl.with_attrs(hl_attrs);
×
NEW
1499
    top_view->get_highlights()[{highlight_source_t::PREVIEW, "preview"}] = hl;
×
NEW
1500
    top_view->set_needs_update();
×
NEW
1501
    err.clear();
×
1502
}
1503

1504
void
NEW
1505
filter_sub_source::named_search_row::ti_completion_request(
×
1506
    textview_curses* top_view,
1507
    textinput_curses& tc,
1508
    completion_request_type_t crt)
1509
{
1510
    static const auto SEARCH_HELP
NEW
1511
        = help_text("named-search", "give a name to a search")
×
NEW
1512
              .with_parameter(help_text("name", "The name for the search"))
×
NEW
1513
              .with_parameter(
×
NEW
1514
                  help_text("pattern", "The regular expression to search for")
×
NEW
1515
                      .with_format(help_parameter_format_t::HPF_REGEX));
×
1516
    static auto& prompt = lnav::prompt::get();
1517

NEW
1518
    auto& al = tc.tc_lines[tc.tc_cursor.y];
×
NEW
1519
    auto al_sf = al.to_string_fragment().sub_cell_range(0, tc.tc_cursor.x);
×
1520
    auto parse_res = lnav::command::parse_for_prompt(
NEW
1521
        lnav_data.ld_exec_context, al_sf, SEARCH_HELP);
×
1522

NEW
1523
    switch (crt) {
×
NEW
1524
        case completion_request_type_t::partial: {
×
NEW
1525
            if (al_sf.endswith(" ")) {
×
NEW
1526
                if (tc.is_cursor_at_end_of_line()) {
×
1527
                    tc.tc_suggestion
NEW
1528
                        = prompt.get_regex_suggestion(*top_view, al.al_string);
×
1529
                }
NEW
1530
                return;
×
1531
            }
NEW
1532
            break;
×
1533
        }
NEW
1534
        case completion_request_type_t::full:
×
NEW
1535
            break;
×
1536
    }
1537

NEW
1538
    auto byte_x = al_sf.column_to_byte_index(tc.tc_cursor.x);
×
NEW
1539
    auto arg_res_opt = parse_res.arg_at(byte_x);
×
NEW
1540
    if (!arg_res_opt) {
×
NEW
1541
        return;
×
1542
    }
1543

NEW
1544
    auto arg_pair = arg_res_opt.value();
×
NEW
1545
    if (crt == completion_request_type_t::full
×
NEW
1546
        || tc.tc_popup_type != textinput_curses::popup_type_t::none)
×
1547
    {
1548
        auto poss = prompt.get_cmd_parameter_completion(
1549
            *top_view,
1550
            &SEARCH_HELP,
1551
            arg_pair.aar_help,
NEW
1552
            arg_pair.aar_element.se_value);
×
NEW
1553
        auto left = arg_pair.aar_element.se_value.empty()
×
NEW
1554
            ? tc.tc_cursor.x
×
NEW
1555
            : al_sf.byte_to_column_index(
×
NEW
1556
                  arg_pair.aar_element.se_origin.sf_begin);
×
NEW
1557
        tc.open_popup_for_completion(left, poss);
×
NEW
1558
        tc.tc_popup.set_title(arg_pair.aar_help->ht_name);
×
NEW
1559
    } else if (arg_pair.aar_element.se_value.empty()
×
NEW
1560
               && tc.is_cursor_at_end_of_line())
×
1561
    {
NEW
1562
        tc.tc_suggestion = prompt.get_regex_suggestion(*top_view, al.al_string);
×
1563
    } else {
NEW
1564
        tc.tc_suggestion.clear();
×
1565
    }
1566
}
1567

1568
void
NEW
1569
filter_sub_source::named_search_row::ti_perform(textview_curses* top_view,
×
1570
                                                textinput_curses& ti,
1571
                                                filter_sub_source& parent)
1572
{
1573
    static const intern_string_t INPUT_SRC = intern_string::lookup("input");
1574

NEW
1575
    auto report_error = [](const lnav::console::user_message& um) {
×
NEW
1576
        lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
1577
    };
NEW
1578
    auto [name, pattern] = split_named_search_input(ti.get_content());
×
1579

NEW
1580
    if (name.empty()) {
×
NEW
1581
        this->ti_abort(top_view, ti, parent);
×
NEW
1582
        return;
×
1583
    }
1584

NEW
1585
    auto name_res = textview_curses::validate_search_name(name);
×
NEW
1586
    if (name_res.isErr()) {
×
NEW
1587
        report_error(name_res.unwrapErr());
×
NEW
1588
        this->ti_abort(top_view, ti, parent);
×
NEW
1589
        return;
×
1590
    }
1591

NEW
1592
    if (pattern.empty()) {
×
NEW
1593
        pattern = top_view->get_current_search();
×
1594
    }
NEW
1595
    if (pattern.empty()) {
×
NEW
1596
        report_error(lnav::console::user_message::error(
×
1597
                         "expecting a pattern for the search")
NEW
1598
                         .with_reason("there is no active search to adopt"));
×
NEW
1599
        this->ti_abort(top_view, ti, parent);
×
NEW
1600
        return;
×
1601
    }
1602

1603
    // The pattern is compiled here, before anything is torn down, so that a
1604
    // typo cannot take the original search with it.
NEW
1605
    auto compile_res = lnav::pcre2pp::code::from(pattern, PCRE2_CASELESS);
×
NEW
1606
    if (compile_res.isErr()) {
×
NEW
1607
        report_error(lnav::console::to_user_message(
×
NEW
1608
            INPUT_SRC, compile_res.unwrapErr()));
×
NEW
1609
        this->ti_abort(top_view, ti, parent);
×
NEW
1610
        return;
×
1611
    }
1612

NEW
1613
    if (name != this->nsr_name
×
NEW
1614
        && top_view->find_named_search(name) != nullptr)
×
1615
    {
NEW
1616
        report_error(
×
NEW
1617
            lnav::console::user_message::error(
×
NEW
1618
                attr_line_t()
×
NEW
1619
                    .append_quoted(name)
×
NEW
1620
                    .append(" is already a named search"))
×
NEW
1621
                .with_snippet(lnav::console::snippet::from(INPUT_SRC, name)));
×
NEW
1622
        this->ti_abort(top_view, ti, parent);
×
NEW
1623
        return;
×
1624
    }
1625

1626
    // There is no way to change a search in place: the slot has to be given
1627
    // up and the view scanned again, which is the same trade-off that the
1628
    // lnav_view_searches table makes for DELETE followed by INSERT.
NEW
1629
    if (!this->nsr_name.empty()) {
×
NEW
1630
        top_view->delete_named_search(this->nsr_name);
×
1631
    }
1632

NEW
1633
    auto create_res = top_view->create_named_search(name, pattern);
×
NEW
1634
    if (create_res.isErr()) {
×
NEW
1635
        report_error(create_res.unwrapErr());
×
NEW
1636
    } else if (!parent.fss_filter_state) {
×
1637
        // A search that was disabled before the edit stays that way.
NEW
1638
        top_view->set_named_search_enabled(name, false);
×
1639
    }
1640

NEW
1641
    top_view->reload_data();
×
1642
}
1643

1644
void
NEW
1645
filter_sub_source::named_search_row::ti_abort(textview_curses* top_view,
×
1646
                                              textinput_curses& tc,
1647
                                              filter_sub_source& parent)
1648
{
NEW
1649
    if (!this->nsr_name.empty()) {
×
NEW
1650
        top_view->set_named_search_enabled(this->nsr_name,
×
NEW
1651
                                           parent.fss_filter_state);
×
1652
    }
NEW
1653
    top_view->reload_data();
×
1654
}
1655

1656
std::vector<std::unique_ptr<filter_sub_source::filter_row>>
1657
filter_sub_source::rows_for(textview_curses* tc) const
69✔
1658
{
1659
    auto retval = row_vector{};
69✔
1660
    auto* tss = tc->get_sub_source();
69✔
1661
    if (tss == nullptr) {
69✔
1662
        return retval;
×
1663
    }
1664

1665
    const auto* ttt = dynamic_cast<text_time_translator*>(tss);
69✔
1666

1667
    if (ttt != nullptr) {
69✔
1668
        if (this->fss_min_time) {
69✔
1669
            retval.emplace_back(std::make_unique<min_time_filter_row>(
×
1670
                this->fss_min_time.value()));
1671
        } else {
1672
            auto min_time = ttt->get_min_row_time();
69✔
1673
            if (min_time) {
69✔
1674
                retval.emplace_back(
×
1675
                    std::make_unique<min_time_filter_row>(min_time.value()));
×
1676
            }
1677
        }
1678
        if (this->fss_max_time) {
69✔
1679
            retval.emplace_back(std::make_unique<max_time_filter_row>(
×
1680
                this->fss_max_time.value()));
1681
        } else {
1682
            auto max_time = ttt->get_max_row_time();
69✔
1683
            if (max_time) {
69✔
1684
                retval.emplace_back(
×
1685
                    std::make_unique<max_time_filter_row>(max_time.value()));
×
1686
            }
1687
        }
1688
    }
1689

1690
    if (tss->get_min_log_level() != LEVEL_UNKNOWN) {
69✔
1691
        retval.emplace_back(std::make_unique<level_filter_row>());
×
1692
    }
1693

1694
    auto& fs = tss->get_filters();
69✔
1695
    for (auto& tf : fs) {
126✔
1696
        retval.emplace_back(std::make_unique<text_filter_row>(tf));
57✔
1697
    }
1698

1699
    for (const auto& ns : tc->get_named_searches()) {
69✔
NEW
1700
        retval.emplace_back(std::make_unique<named_search_row>(ns.ns_name));
×
1701
    }
1702
    if (this->fss_new_named_search) {
69✔
NEW
1703
        retval.emplace_back(std::make_unique<named_search_row>(std::string()));
×
1704
    }
1705

1706
    return retval;
69✔
1707
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc