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

tstack / lnav / 18954735303-2618

30 Oct 2025 08:49PM UTC coverage: 68.778% (+0.7%) from 68.076%
18954735303-2618

push

github

tstack
[filters] add min/max time filters to UI

Related to #1275
Related to #1576

175 of 525 new or added lines in 12 files covered. (33.33%)

582 existing lines in 3 files now uncovered.

50384 of 73256 relevant lines covered (68.78%)

426743.35 hits per line

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

36.43
/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/text_format_enum.hh"
42
#include "cmd.parser.hh"
43
#include "config.h"
44
#include "data_scanner.hh"
45
#include "lnav.hh"
46
#include "lnav.prompt.hh"
47
#include "readline_highlighters.hh"
48
#include "readline_possibilities.hh"
49
#include "sql_util.hh"
50
#include "textinput_curses.hh"
51
#include "textview_curses.hh"
52

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

55
filter_sub_source::filter_sub_source(std::shared_ptr<textinput_curses> editor)
2✔
56
    : fss_editor(std::move(editor)),
2✔
57
      fss_regexp_history(
58
          lnav::textinput::history::for_context("regexp-filter"_frag)),
2✔
59
      fss_sql_history(lnav::textinput::history::for_context("sql-filter"_frag))
4✔
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(&filter_sub_source::rl_history, this);
4✔
69
    this->fss_editor->tc_on_history_list
2✔
70
        = bind_mem(&filter_sub_source::rl_history, this);
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(&filter_sub_source::rl_completion, this);
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)
6✔
92
{
93
    if (this->fss_editing) {
6✔
94
        return this->fss_editor->handle_key(ch);
4✔
95
    }
96

97
    switch (ch.eff_text[0]) {
2✔
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
        }
NEW
106
        case ' ':
×
107
        case 't':
108
        case 'D': {
109
            auto* top_view = *lnav_data.ld_view_stack.top();
×
NEW
110
            auto rows = this->rows_for(top_view);
×
NEW
111
            if (rows.empty() || !lv.get_selection()) {
×
UNCOV
112
                return true;
×
113
            }
114

NEW
115
            auto& tf = rows[lv.get_selection().value()];
×
NEW
116
            tf->handle_key(top_view, ch);
×
117
            lv.reload_data();
×
NEW
118
            top_view->get_sub_source()->text_filters_changed();
×
119
            top_view->reload_data();
×
120
            return true;
×
121
        }
NEW
122
        case 'm': {
×
123
            auto* top_view = *lnav_data.ld_view_stack.top();
×
NEW
124
            auto* ttt = dynamic_cast<text_time_translator*>(
×
NEW
125
                top_view->get_sub_source());
×
NEW
126
            if (ttt != nullptr) {
×
NEW
127
                auto curr_min = ttt->get_min_row_time();
×
NEW
128
                this->fss_filter_state = curr_min.has_value();
×
NEW
129
                if (curr_min) {
×
NEW
130
                    this->fss_min_time = curr_min;
×
NEW
131
                    ttt->set_min_row_time(text_time_translator::min_time_init);
×
NEW
132
                    ttt->ttt_preview_min_time = curr_min;
×
NEW
133
                    top_view->get_sub_source()->text_filters_changed();
×
134
                } else {
NEW
135
                    auto sel = top_view->get_selection();
×
NEW
136
                    if (sel) {
×
NEW
137
                        auto ri_opt = ttt->time_for_row(sel.value());
×
NEW
138
                        if (ri_opt) {
×
NEW
139
                            auto ri = ri_opt.value();
×
140

NEW
141
                            this->fss_min_time = ri.ri_time;
×
142
                        }
143
                    }
NEW
144
                    if (!this->fss_min_time) {
×
NEW
145
                        this->fss_min_time = current_timeval();
×
146
                    }
NEW
147
                    ttt->ttt_preview_min_time = this->fss_min_time;
×
148
                }
149

NEW
150
                lv.set_selection(0_vl);
×
NEW
151
                lv.reload_data();
×
152

NEW
153
                auto rows = this->rows_for(top_view);
×
NEW
154
                auto& row = rows[0];
×
NEW
155
                this->fss_editing = true;
×
NEW
156
                this->tss_view->set_enabled(false);
×
NEW
157
                row->prime_text_input(top_view, *this->fss_editor, *this);
×
NEW
158
                this->fss_editor->set_y(lv.get_y_for_selection());
×
NEW
159
                this->fss_editor->set_visible(true);
×
NEW
160
                this->fss_editor->focus();
×
NEW
161
                this->tss_view->reload_data();
×
UNCOV
162
                return true;
×
163
            }
NEW
164
            break;
×
165
        }
NEW
166
        case 'M': {
×
167
            auto* top_view = *lnav_data.ld_view_stack.top();
×
NEW
168
            auto* ttt = dynamic_cast<text_time_translator*>(
×
NEW
169
                top_view->get_sub_source());
×
NEW
170
            if (ttt != nullptr) {
×
NEW
171
                auto curr_max = ttt->get_max_row_time();
×
NEW
172
                this->fss_filter_state = curr_max.has_value();
×
NEW
173
                if (curr_max) {
×
NEW
174
                    this->fss_max_time = curr_max;
×
NEW
175
                    ttt->ttt_preview_max_time = curr_max;
×
NEW
176
                    ttt->set_max_row_time(text_time_translator::max_time_init);
×
NEW
177
                    top_view->get_sub_source()->text_filters_changed();
×
178
                } else {
NEW
179
                    auto sel = top_view->get_selection();
×
NEW
180
                    if (sel) {
×
NEW
181
                        auto ri_opt = ttt->time_for_row(sel.value());
×
NEW
182
                        if (ri_opt) {
×
NEW
183
                            auto ri = ri_opt.value();
×
184

NEW
185
                            this->fss_max_time = ri.ri_time;
×
186
                        }
187
                    }
NEW
188
                    if (!this->fss_max_time) {
×
NEW
189
                        this->fss_max_time = current_timeval();
×
190
                    }
NEW
191
                    ttt->ttt_preview_max_time = this->fss_max_time;
×
192
                }
193

NEW
194
                auto new_sel = ttt->get_min_row_time() ? 1_vl : 0_vl;
×
NEW
195
                lv.set_selection(new_sel);
×
NEW
196
                lv.reload_data();
×
197

NEW
198
                auto rows = this->rows_for(top_view);
×
NEW
199
                auto& row = rows[new_sel];
×
NEW
200
                this->fss_editing = true;
×
NEW
201
                this->tss_view->set_enabled(false);
×
NEW
202
                row->prime_text_input(top_view, *this->fss_editor, *this);
×
NEW
203
                this->fss_editor->set_y(lv.get_y_for_selection());
×
NEW
204
                this->fss_editor->set_visible(true);
×
NEW
205
                this->fss_editor->focus();
×
NEW
206
                this->tss_view->reload_data();
×
UNCOV
207
                return true;
×
208
            }
NEW
209
            break;
×
210
        }
211
        case 'i':
1✔
212
        case 'o': {
213
            auto* top_view = *lnav_data.ld_view_stack.top();
1✔
214
            auto* tss = top_view->get_sub_source();
1✔
215
            auto& fs = tss->get_filters();
1✔
216
            auto filter_index = fs.next_index();
1✔
217

218
            if (!filter_index) {
1✔
219
                lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
220
                    "error: too many filters");
221
                return true;
×
222
            }
223

224
            auto filter_type = ch.eff_text[0] == 'i'
1✔
225
                ? text_filter::type_t::INCLUDE
1✔
226
                : text_filter::type_t::EXCLUDE;
227
            auto ef
228
                = std::make_shared<empty_filter>(filter_type, *filter_index);
1✔
229
            fs.add_filter(ef);
1✔
230

231
            auto rows = this->rows_for(top_view);
1✔
232
            lv.set_selection(vis_line_t(rows.size()) - 1_vl);
1✔
233
            auto& row = rows.back();
1✔
234
            lv.reload_data();
1✔
235

236
            this->fss_editing = true;
1✔
237
            this->tss_view->set_enabled(false);
1✔
238
            this->fss_view_text_possibilities
239
                = view_text_possibilities(*top_view);
1✔
240
            row->prime_text_input(top_view, *this->fss_editor, *this);
1✔
241
            this->fss_editor->set_y(lv.get_y_for_selection());
1✔
242
            this->fss_editor->set_visible(true);
1✔
243
            this->fss_editor->focus();
1✔
244
            this->fss_filter_state = true;
1✔
245
            return true;
1✔
246
        }
1✔
247
        case '\r':
×
248
        case NCKEY_ENTER: {
249
            auto* top_view = *lnav_data.ld_view_stack.top();
×
NEW
250
            auto rows = this->rows_for(top_view);
×
251

NEW
252
            if (rows.empty() || !lv.get_selection()) {
×
253
                return true;
×
254
            }
255

NEW
256
            auto& row = rows[lv.get_selection().value()];
×
UNCOV
257
            this->fss_editing = true;
×
258
            this->tss_view->set_enabled(false);
×
UNCOV
259
            this->fss_editor->set_y(lv.get_y_for_selection());
×
260
            this->fss_editor->set_visible(true);
×
261
            this->fss_editor->tc_suggestion.clear();
×
262
            this->fss_view_text_possibilities
UNCOV
263
                = view_text_possibilities(*top_view);
×
264
            this->fss_editor->focus();
×
265
            this->fss_filter_state
NEW
266
                = row->prime_text_input(top_view, *this->fss_editor, *this);
×
NEW
267
            top_view->get_sub_source()->text_filters_changed();
×
UNCOV
268
            return true;
×
269
        }
270
        case 'n': {
×
271
            lnav_data.ld_exec_context.execute(INTERNAL_SRC_LOC,
×
272
                                              ":next-mark search");
273
            return true;
×
274
        }
275
        case 'N': {
×
276
            lnav_data.ld_exec_context.execute(INTERNAL_SRC_LOC,
×
277
                                              ":prev-mark search");
278
            return true;
×
279
        }
280
        case '/': {
×
281
            lnav_data.ld_exec_context.execute(INTERNAL_SRC_LOC,
×
282
                                              ":prompt search-filters");
283
            return true;
×
284
        }
285
        default:
1✔
286
            log_debug("unhandled %x", ch);
1✔
287
            break;
1✔
288
    }
289

290
    return false;
1✔
291
}
292

293
size_t
294
filter_sub_source::text_line_count()
79✔
295
{
UNCOV
296
    return (lnav_data.ld_view_stack.top() |
×
297
                [this](auto tc) -> std::optional<size_t> {
59✔
298
               auto* tss = tc->get_sub_source();
59✔
299

300
               if (tss == nullptr) {
59✔
301
                   return std::nullopt;
×
302
               }
303
               auto rows = this->rows_for(tc);
59✔
304
               return std::make_optional(rows.size());
59✔
305
           })
138✔
306
        .value_or(0);
79✔
307
}
308

309
size_t
310
filter_sub_source::text_line_width(textview_curses& curses)
×
311
{
NEW
312
    auto* top_view = *lnav_data.ld_view_stack.top();
×
NEW
313
    auto* tss = top_view->get_sub_source();
×
NEW
314
    auto& fs = tss->get_filters();
×
UNCOV
315
    size_t retval = 0;
×
316

317
    for (auto& filter : fs) {
×
318
        retval = std::max(filter->get_id().size() + 8, retval);
×
319
    }
320

321
    return retval;
×
322
}
323

324
line_info
325
filter_sub_source::text_value_for_line(textview_curses& tc,
1✔
326
                                       int line,
327
                                       std::string& value_out,
328
                                       line_flags_t flags)
329
{
330
    auto* top_view = *lnav_data.ld_view_stack.top();
1✔
331
    auto selected
332
        = lnav_data.ld_mode == ln_mode_t::FILTER && line == tc.get_selection();
1✔
333
    auto rows = this->rows_for(top_view);
1✔
334
    auto rs = render_state{top_view};
1✔
335
    auto& al = this->fss_curr_line;
1✔
336

337
    al.clear();
1✔
338
    if (selected && this->fss_editing) {
1✔
339
        rs.rs_editing = true;
1✔
340
    }
341
    if (selected) {
1✔
342
        al.append(" ", VC_GRAPHIC.value(NCACS_RARROW));
1✔
343
    } else {
344
        al.append(" ");
×
345
    }
346
    al.append(" ");
1✔
347

348
    auto& row = rows[line];
1✔
349

350
    row->value_for(rs, al);
1✔
351
    if (selected) {
1✔
352
        al.with_attr_for_all(VC_ROLE.value(role_t::VCR_FOCUSED));
1✔
353
    }
354

355
    value_out = al.al_string;
1✔
356
    return {};
2✔
357
}
1✔
358

359
void
360
filter_sub_source::text_attrs_for_line(textview_curses& tc,
1✔
361
                                       int line,
362
                                       string_attrs_t& value_out)
363
{
364
    value_out = this->fss_curr_line.get_attrs();
1✔
365
}
1✔
366

367
size_t
NEW
368
filter_sub_source::text_size_for_line(textview_curses& tc,
×
369
                                      int line,
370
                                      text_sub_source::line_flags_t raw)
371
{
372
    // XXX
NEW
373
    return 40;
×
374
}
375

376
void
377
filter_sub_source::rl_change(textinput_curses& rc)
3✔
378
{
379
    auto* top_view = *lnav_data.ld_view_stack.top();
3✔
380
    auto rows = this->rows_for(top_view);
3✔
381
    auto& row = rows[this->tss_view->get_selection().value()];
3✔
382

383
    row->ti_change(top_view, rc);
3✔
384
}
3✔
385

386
void
NEW
387
filter_sub_source::rl_history(textinput_curses& tc)
×
388
{
NEW
389
    switch (tc.tc_text_format) {
×
NEW
390
        case text_format_t::TF_PCRE: {
×
NEW
391
            std::vector<attr_line_t> poss;
×
NEW
392
            this->fss_regexp_history.query_entries(
×
NEW
393
                tc.get_content(),
×
NEW
394
                [&poss](const auto& e) { poss.emplace_back(e.e_content); });
×
NEW
395
            tc.open_popup_for_history(poss);
×
NEW
396
            break;
×
397
        }
NEW
398
        case text_format_t::TF_SQL: {
×
NEW
399
            break;
×
400
        }
NEW
401
        default:
×
NEW
402
            ensure(false);
×
403
            break;
404
    }
405
}
406

407
void
NEW
408
filter_sub_source::rl_completion_request_int(textinput_curses& tc,
×
409
                                             completion_request_type_t crt)
410
{
NEW
411
    auto* top_view = *lnav_data.ld_view_stack.top();
×
NEW
412
    auto rows = this->rows_for(top_view);
×
NEW
413
    auto& row = rows[this->tss_view->get_selection().value()];
×
414

NEW
415
    row->ti_completion_request(top_view, tc, crt);
×
416
}
417

418
void
NEW
419
filter_sub_source::rl_completion_request(textinput_curses& tc)
×
420
{
NEW
421
    this->rl_completion_request_int(tc, completion_request_type_t::full);
×
422
}
423

424
void
NEW
425
filter_sub_source::rl_completion(textinput_curses& tc)
×
426
{
427
    static auto& prompt = lnav::prompt::get();
428

NEW
429
    prompt.rl_completion(tc);
×
430
}
431

432
void
433
filter_sub_source::rl_perform(textinput_curses& rc)
1✔
434
{
435
    auto* top_view = *lnav_data.ld_view_stack.top();
1✔
436
    auto rows = this->rows_for(top_view);
1✔
437
    auto& row = rows[this->tss_view->get_selection().value()];
1✔
438

439
    row->ti_perform(top_view, rc, *this);
1✔
440
    this->fss_min_time = std::nullopt;
1✔
441
    this->fss_max_time = std::nullopt;
1✔
442
    this->tss_view->reload_data();
1✔
443
}
1✔
444

445
void
446
filter_sub_source::rl_blur(textinput_curses& tc)
1✔
447
{
448
    auto* top_view = *lnav_data.ld_view_stack.top();
1✔
449
    top_view->clear_preview();
1✔
450
    lnav_data.ld_filter_help_status_source.fss_prompt.clear();
1✔
451
    lnav_data.ld_filter_help_status_source.fss_error.clear();
1✔
452
    this->fss_editing = false;
1✔
453
    tc.set_visible(false);
1✔
454
    this->tss_view->set_enabled(true);
1✔
455
}
1✔
456

457
void
NEW
458
filter_sub_source::rl_abort(textinput_curses& rc)
×
459
{
NEW
460
    auto* top_view = *lnav_data.ld_view_stack.top();
×
NEW
461
    auto rows = this->rows_for(top_view);
×
NEW
462
    auto& row = rows[this->tss_view->get_selection().value()];
×
463

NEW
464
    row->ti_abort(top_view, rc, *this);
×
NEW
465
    this->fss_min_time = std::nullopt;
×
NEW
466
    this->fss_max_time = std::nullopt;
×
NEW
467
    this->tss_view->reload_data();
×
468
}
469

470
void
NEW
471
filter_sub_source::list_input_handle_scroll_out(listview_curses& lv)
×
472
{
NEW
473
    set_view_mode(ln_mode_t::PAGING);
×
NEW
474
    lnav_data.ld_filter_view.reload_data();
×
475
}
476

477
bool
NEW
478
filter_sub_source::text_handle_mouse(
×
479
    textview_curses& tc,
480
    const listview_curses::display_line_content_t&,
481
    mouse_event& me)
482
{
NEW
483
    if (this->fss_editing) {
×
NEW
484
        return true;
×
485
    }
NEW
486
    auto nci = ncinput{};
×
NEW
487
    if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 1, 3)) {
×
NEW
488
        nci.id = ' ';
×
NEW
489
        nci.eff_text[0] = ' ';
×
NEW
490
        this->list_input_handle_key(tc, nci);
×
491
    }
NEW
492
    if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 4, 7)) {
×
NEW
493
        nci.id = 't';
×
NEW
494
        nci.eff_text[0] = 't';
×
NEW
495
        this->list_input_handle_key(tc, nci);
×
496
    }
NEW
497
    if (me.is_double_click_in(mouse_button_t::BUTTON_LEFT, line_range{25, -1}))
×
498
    {
NEW
499
        nci.id = '\r';
×
NEW
500
        nci.eff_text[0] = '\r';
×
NEW
501
        this->list_input_handle_key(tc, nci);
×
502
    }
NEW
503
    return true;
×
504
}
505

506
bool
NEW
507
filter_sub_source::min_time_filter_row::handle_key(textview_curses* top_view,
×
508
                                                   const ncinput& ch)
509
{
NEW
510
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
NEW
511
    switch (ch.eff_text[0]) {
×
NEW
512
        case 'D':
×
NEW
513
            ttt->set_min_row_time(text_time_translator::min_time_init);
×
NEW
514
            top_view->get_sub_source()->text_filters_changed();
×
NEW
515
            return true;
×
NEW
516
        default:
×
NEW
517
            return false;
×
518
    }
519
}
520

521
void
NEW
522
filter_sub_source::min_time_filter_row::value_for(const render_state& rs,
×
523
                                                  attr_line_t& al)
524
{
NEW
525
    al.append("Min Time"_table_header).append(" ");
×
NEW
526
    if (rs.rs_editing) {
×
NEW
527
        al.append(fmt::format(FMT_STRING("{:>9}"), "-"),
×
NEW
528
                  VC_ROLE.value(role_t::VCR_NUMBER));
×
529
    } else {
NEW
530
        al.append(fmt::format(
×
NEW
531
                      FMT_STRING("{:>9}"),
×
NEW
532
                      rs.rs_top_view->get_sub_source()->get_filtered_before()),
×
NEW
533
                  VC_ROLE.value(role_t::VCR_NUMBER));
×
534
    }
NEW
535
    al.append(" hits ").append("|", VC_GRAPHIC.value(NCACS_VLINE)).append(" ");
×
NEW
536
    al.append(lnav::to_rfc3339_string(this->tfr_time));
×
537
}
538

539
Result<timeval, std::string>
NEW
540
filter_sub_source::time_filter_row::parse_time(textview_curses* top_view,
×
541
                                               textinput_curses& tc)
542
{
NEW
543
    auto content = tc.get_content();
×
NEW
544
    if (content.empty()) {
×
NEW
545
        return Err(std::string("expecting a timestamp or relative time"));
×
546
    }
547

NEW
548
    auto parse_res = relative_time::from_str(content);
×
NEW
549
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
NEW
550
    date_time_scanner dts;
×
551

NEW
552
    if (top_view->get_inner_height() > 0_vl) {
×
NEW
553
        auto top_time_opt = ttt->time_for_row(
×
NEW
554
            top_view->get_selection().value_or(top_view->get_top()));
×
555

NEW
556
        if (top_time_opt) {
×
NEW
557
            auto top_time_tv = top_time_opt.value().ri_time;
×
558
            tm top_tm;
559

NEW
560
            localtime_r(&top_time_tv.tv_sec, &top_tm);
×
NEW
561
            dts.set_base_time(top_time_tv.tv_sec, top_tm);
×
562
        }
563
    }
NEW
564
    if (parse_res.isOk()) {
×
NEW
565
        auto rt = parse_res.unwrap();
×
566
        auto tv_opt
NEW
567
            = ttt->time_for_row(top_view->get_selection().value_or(0_vl));
×
NEW
568
        auto tv = current_timeval();
×
NEW
569
        if (tv_opt) {
×
NEW
570
            tv = tv_opt.value().ri_time;
×
571
        }
NEW
572
        auto tm = rt.adjust(tv);
×
NEW
573
        return Ok(tm.to_timeval());
×
574
    }
NEW
575
    auto time_str = tc.get_content();
×
NEW
576
    exttm tm;
×
577
    timeval tv;
578
    const auto* scan_end
NEW
579
        = dts.scan(time_str.c_str(), time_str.size(), nullptr, &tm, tv);
×
NEW
580
    if (scan_end != nullptr) {
×
NEW
581
        auto matched_size = scan_end - time_str.c_str();
×
NEW
582
        if (matched_size == time_str.size()) {
×
NEW
583
            return Ok(tv);
×
584
        }
585

NEW
586
        return Err(fmt::format(FMT_STRING("extraneous input '{}'"), scan_end));
×
587
    }
588

NEW
589
    auto pe = parse_res.unwrapErr();
×
NEW
590
    return Err(pe.pe_msg);
×
591
}
592

593
void
NEW
594
filter_sub_source::min_time_filter_row::ti_perform(textview_curses* top_view,
×
595
                                                   textinput_curses& tc,
596
                                                   filter_sub_source& parent)
597
{
NEW
598
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
NEW
599
    auto parse_res = this->parse_time(top_view, tc);
×
NEW
600
    if (parse_res.isOk()) {
×
NEW
601
        auto tv = parse_res.unwrap();
×
602

NEW
603
        ttt->set_min_row_time(tv);
×
604
    } else {
NEW
605
        auto msg = parse_res.unwrapErr();
×
NEW
606
        if (parent.fss_filter_state) {
×
NEW
607
            ttt->set_min_row_time(parent.fss_min_time.value());
×
608
        }
609

610
        auto um
NEW
611
            = lnav::console::user_message::error("could not parse timestamp")
×
NEW
612
                  .with_reason(msg);
×
NEW
613
        lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
614
    }
NEW
615
    parent.fss_min_time = std::nullopt;
×
NEW
616
    top_view->get_sub_source()->text_filters_changed();
×
617
}
618

619
void
NEW
620
filter_sub_source::min_time_filter_row::ti_abort(textview_curses* top_view,
×
621
                                                 textinput_curses& tc,
622
                                                 filter_sub_source& parent)
623
{
NEW
624
    auto* tss = top_view->get_sub_source();
×
NEW
625
    auto* ttt = dynamic_cast<text_time_translator*>(tss);
×
626

NEW
627
    if (parent.fss_filter_state) {
×
NEW
628
        ttt->set_min_row_time(parent.fss_min_time.value());
×
629
    }
NEW
630
    tss->text_filters_changed();
×
631
}
632

633
bool
NEW
634
filter_sub_source::time_filter_row::prime_text_input(textview_curses* top_view,
×
635
                                                     textinput_curses& ti,
636
                                                     filter_sub_source& parent)
637
{
NEW
638
    ti.tc_text_format = text_format_t::TF_UNKNOWN;
×
NEW
639
    ti.set_content(lnav::to_rfc3339_string(this->tfr_time));
×
NEW
640
    return true;
×
641
}
642

643
void
NEW
644
filter_sub_source::min_time_filter_row::ti_change(textview_curses* top_view,
×
645
                                                  textinput_curses& rc)
646
{
NEW
647
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
NEW
648
    auto parse_res = this->parse_time(top_view, rc);
×
649

NEW
650
    if (parse_res.isOk()) {
×
NEW
651
        auto tv = parse_res.unwrap();
×
NEW
652
        ttt->ttt_preview_min_time = tv;
×
NEW
653
        lnav_data.ld_filter_help_status_source.fss_error.clear();
×
654
    } else {
NEW
655
        ttt->ttt_preview_min_time = std::nullopt;
×
NEW
656
        auto msg = parse_res.unwrapErr();
×
NEW
657
        lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
658
            "error: could not parse timestamp -- %s", msg.c_str());
659
    }
660
}
661

662
void
NEW
663
filter_sub_source::max_time_filter_row::ti_change(textview_curses* top_view,
×
664
                                                  textinput_curses& rc)
665
{
NEW
666
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
NEW
667
    auto parse_res = this->parse_time(top_view, rc);
×
668

NEW
669
    if (parse_res.isOk()) {
×
NEW
670
        auto tv = parse_res.unwrap();
×
NEW
671
        ttt->ttt_preview_max_time = tv;
×
NEW
672
        lnav_data.ld_filter_help_status_source.fss_error.clear();
×
673
    } else {
NEW
674
        ttt->ttt_preview_max_time = std::nullopt;
×
NEW
675
        auto msg = parse_res.unwrapErr();
×
NEW
676
        lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
677
            "error: could not parse timestamp -- %s", msg.c_str());
678
    }
679
}
680

681
void
NEW
682
filter_sub_source::time_filter_row::ti_completion_request(
×
683
    textview_curses* top_view,
684
    textinput_curses& tc,
685
    completion_request_type_t crt)
686
{
687
}
688

689
bool
NEW
690
filter_sub_source::max_time_filter_row::handle_key(textview_curses* top_view,
×
691
                                                   const ncinput& ch)
692
{
NEW
693
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
NEW
694
    switch (ch.eff_text[0]) {
×
NEW
695
        case 'D':
×
NEW
696
            ttt->set_max_row_time(text_time_translator::max_time_init);
×
NEW
697
            top_view->get_sub_source()->text_filters_changed();
×
NEW
698
            return true;
×
NEW
699
        default:
×
NEW
700
            return false;
×
701
    }
702
}
703

704
void
NEW
705
filter_sub_source::max_time_filter_row::value_for(const render_state& rs,
×
706
                                                  attr_line_t& al)
707
{
NEW
708
    al.append("Max Time"_table_header).append(" ");
×
NEW
709
    if (rs.rs_editing) {
×
NEW
710
        al.append(fmt::format(FMT_STRING("{:>9}"), "-"),
×
NEW
711
                  VC_ROLE.value(role_t::VCR_NUMBER));
×
712
    } else {
NEW
713
        al.append(
×
NEW
714
            fmt::format(FMT_STRING("{:>9}"),
×
NEW
715
                        rs.rs_top_view->get_sub_source()->get_filtered_after()),
×
NEW
716
            VC_ROLE.value(role_t::VCR_NUMBER));
×
717
    }
NEW
718
    al.append(" hits ").append("|", VC_GRAPHIC.value(NCACS_VLINE)).append(" ");
×
NEW
719
    al.append(lnav::to_rfc3339_string(this->tfr_time));
×
720
}
721

722
void
NEW
723
filter_sub_source::max_time_filter_row::ti_perform(textview_curses* top_view,
×
724
                                                   textinput_curses& tc,
725
                                                   filter_sub_source& parent)
726
{
NEW
727
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
NEW
728
    auto parse_res = this->parse_time(top_view, tc);
×
NEW
729
    if (parse_res.isOk()) {
×
NEW
730
        auto tv = parse_res.unwrap();
×
731

NEW
732
        ttt->set_max_row_time(tv);
×
733
    } else {
NEW
734
        auto msg = parse_res.unwrapErr();
×
NEW
735
        if (parent.fss_filter_state) {
×
NEW
736
            ttt->set_max_row_time(parent.fss_max_time.value());
×
737
        }
738
        auto um
NEW
739
            = lnav::console::user_message::error("could not parse timestamp")
×
NEW
740
                  .with_reason(msg);
×
NEW
741
        lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
742
    }
NEW
743
    parent.fss_max_time = std::nullopt;
×
NEW
744
    top_view->get_sub_source()->text_filters_changed();
×
745
}
746

747
void
NEW
748
filter_sub_source::max_time_filter_row::ti_abort(textview_curses* top_view,
×
749
                                                 textinput_curses& tc,
750
                                                 filter_sub_source& parent)
751
{
NEW
752
    auto* tss = top_view->get_sub_source();
×
NEW
753
    auto* ttt = dynamic_cast<text_time_translator*>(tss);
×
754

NEW
755
    if (parent.fss_filter_state) {
×
NEW
756
        ttt->set_max_row_time(parent.fss_max_time.value());
×
757
    }
NEW
758
    tss->text_filters_changed();
×
759
}
760

761
void
762
filter_sub_source::text_filter_row::value_for(const render_state& rs,
1✔
763
                                              attr_line_t& al)
764
{
765
    attr_line_builder alb(al);
1✔
766
    if (this->tfr_filter->is_enabled()) {
1✔
UNCOV
767
        al.append("\u25c6"_ok);
×
768
    } else {
769
        al.append("\u25c7"_comment);
1✔
770
    }
771
    al.append(" ");
1✔
772
    switch (this->tfr_filter->get_type()) {
1✔
773
        case text_filter::INCLUDE:
×
774
            al.append(" ").append(lnav::roles::ok("IN")).append(" ");
×
775
            break;
×
776
        case text_filter::EXCLUDE:
1✔
777
            if (this->tfr_filter->get_lang() == filter_lang_t::REGEX) {
1✔
778
                al.append(lnav::roles::error("OUT")).append(" ");
1✔
779
            } else {
780
                al.append("    ");
×
781
            }
782
            break;
1✔
783
        default:
×
784
            ensure(0);
×
785
            break;
786
    }
787
    al.append("   ");
1✔
788

789
    {
790
        auto ag = alb.with_attr(VC_ROLE.value(role_t::VCR_NUMBER));
1✔
791
        if (rs.rs_editing) {
1✔
792
            alb.appendf(FMT_STRING("{:>9}"), "-");
3✔
793
        } else {
NEW
794
            alb.appendf(
×
NEW
795
                FMT_STRING("{:>9L}"),
×
NEW
796
                rs.rs_top_view->get_sub_source()->get_filtered_count_for(
×
NEW
797
                    this->tfr_filter->get_index()));
×
798
        }
799
    }
1✔
800

801
    al.append(" hits ").append("|", VC_GRAPHIC.value(NCACS_VLINE)).append(" ");
1✔
802

803
    attr_line_t content{this->tfr_filter->get_id()};
2✔
804
    switch (this->tfr_filter->get_lang()) {
1✔
805
        case filter_lang_t::REGEX:
1✔
806
            readline_regex_highlighter(content, std::nullopt);
1✔
807
            break;
1✔
808
        case filter_lang_t::SQL:
×
809
            readline_sql_highlighter(
×
810
                content, lnav::sql::dialect::sqlite, std::nullopt);
811
            break;
×
812
        case filter_lang_t::NONE:
×
813
            break;
×
814
    }
815
    al.append(content);
1✔
816
}
1✔
817

818
bool
NEW
819
filter_sub_source::text_filter_row::handle_key(textview_curses* top_view,
×
820
                                               const ncinput& ch)
821
{
NEW
822
    switch (ch.eff_text[0]) {
×
NEW
823
        case ' ': {
×
NEW
824
            auto& fs = top_view->get_sub_source()->get_filters();
×
NEW
825
            fs.set_filter_enabled(this->tfr_filter,
×
NEW
826
                                  !this->tfr_filter->is_enabled());
×
NEW
827
            return true;
×
828
        }
NEW
829
        case 't': {
×
NEW
830
            if (this->tfr_filter->get_type() == text_filter::INCLUDE) {
×
NEW
831
                this->tfr_filter->set_type(text_filter::EXCLUDE);
×
832
            } else {
NEW
833
                this->tfr_filter->set_type(text_filter::INCLUDE);
×
834
            }
NEW
835
            return true;
×
836
        }
NEW
837
        case 'D': {
×
NEW
838
            auto& fs = top_view->get_sub_source()->get_filters();
×
839

NEW
840
            fs.delete_filter(this->tfr_filter->get_id());
×
NEW
841
            return true;
×
842
        }
NEW
843
        default:
×
NEW
844
            return false;
×
845
    }
846
}
847

848
bool
849
filter_sub_source::text_filter_row::prime_text_input(textview_curses* top_view,
1✔
850
                                                     textinput_curses& ti,
851
                                                     filter_sub_source& parent)
852
{
853
    static auto& prompt = lnav::prompt::get();
1✔
854

855
    ti.tc_text_format = this->tfr_filter->get_lang() == filter_lang_t::SQL
1✔
856
        ? text_format_t::TF_SQL
1✔
857
        : text_format_t::TF_PCRE;
858
    if (this->tfr_filter->get_lang() == filter_lang_t::SQL) {
1✔
NEW
859
        prompt.refresh_sql_completions(*top_view);
×
NEW
860
        prompt.refresh_sql_expr_completions(*top_view);
×
861
    }
862
    ti.set_content(this->tfr_filter->get_id());
1✔
863
    if (this->tfr_filter->get_id().empty()) {
1✔
864
        ti.tc_suggestion = top_view->get_input_suggestion();
1✔
865
    }
866
    auto retval = this->tfr_filter->is_enabled();
1✔
867
    this->tfr_filter->disable();
1✔
868

869
    return retval;
1✔
870
}
871

872
void
873
filter_sub_source::text_filter_row::ti_change(textview_curses* top_view,
3✔
874
                                              textinput_curses& rc)
875
{
876
    auto new_value = rc.get_content();
3✔
877
    auto* tss = top_view->get_sub_source();
3✔
878
    auto& fs = tss->get_filters();
3✔
879

880
    top_view->get_highlights().erase({highlight_source_t::PREVIEW, "preview"});
3✔
881
    top_view->set_needs_update();
3✔
882
    switch (this->tfr_filter->get_lang()) {
3✔
883
        case filter_lang_t::NONE:
×
884
            break;
×
885
        case filter_lang_t::REGEX: {
3✔
886
            if (new_value.empty()) {
3✔
887
                auto sugg = top_view->get_current_search();
×
888
                if (top_view->tc_selected_text) {
×
889
                    sugg = top_view->tc_selected_text->sti_value;
×
890
                }
891
                if (fs.get_filter(sugg) == nullptr) {
×
NEW
892
                    rc.tc_suggestion = sugg;
×
893
                } else {
NEW
894
                    rc.tc_suggestion.clear();
×
895
                }
896
            } else {
×
897
                auto regex_res
898
                    = lnav::pcre2pp::code::from(new_value, PCRE2_CASELESS);
3✔
899

900
                this->ti_completion_request(
3✔
901
                    top_view, rc, completion_request_type_t::partial);
902
                if (regex_res.isErr()) {
3✔
903
                    auto pe = regex_res.unwrapErr();
×
904
                    lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
905
                        "error: %s", pe.get_message().c_str());
×
906
                } else {
×
907
                    auto& hm = top_view->get_highlights();
3✔
908
                    highlighter hl(regex_res.unwrap().to_shared());
3✔
909
                    auto role
910
                        = this->tfr_filter->get_type() == text_filter::EXCLUDE
3✔
911
                        ? role_t::VCR_DIFF_DELETE
3✔
912
                        : role_t::VCR_DIFF_ADD;
3✔
913
                    hl.with_role(role);
3✔
914
                    hl.with_attrs(text_attrs::with_styles(
3✔
915
                        text_attrs::style::blink, text_attrs::style::reverse));
916
                    hm[{highlight_source_t::PREVIEW, "preview"}] = hl;
3✔
917
                    top_view->set_needs_update();
3✔
918
                    lnav_data.ld_filter_help_status_source.fss_error.clear();
3✔
919
                }
3✔
920
            }
3✔
921
            break;
3✔
922
        }
923
        case filter_lang_t::SQL: {
×
NEW
924
            this->ti_completion_request(
×
925
                top_view, rc, completion_request_type_t::partial);
926

927
            auto full_sql
928
                = fmt::format(FMT_STRING("SELECT 1 WHERE {}"), new_value);
×
929
            auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
×
930
#ifdef SQLITE_PREPARE_PERSISTENT
931
            auto retcode = sqlite3_prepare_v3(lnav_data.ld_db.in(),
×
932
                                              full_sql.c_str(),
933
                                              full_sql.size(),
×
934
                                              SQLITE_PREPARE_PERSISTENT,
935
                                              stmt.out(),
936
                                              nullptr);
937
#else
938
            auto retcode = sqlite3_prepare_v2(lnav_data.ld_db.in(),
939
                                              full_sql.c_str(),
940
                                              full_sql.size(),
941
                                              stmt.out(),
942
                                              nullptr);
943
#endif
944
            if (retcode != SQLITE_OK) {
×
945
                lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
946
                    "error: %s", sqlite3_errmsg(lnav_data.ld_db));
947
            } else {
948
                auto set_res = lnav_data.ld_log_source.set_preview_sql_filter(
949
                    stmt.release());
×
950

951
                if (set_res.isErr()) {
×
952
                    lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
953
                        "error: %s",
954
                        set_res.unwrapErr()
×
955
                            .to_attr_line()
×
956
                            .get_string()
×
957
                            .c_str());
958
                } else {
959
                    top_view->set_needs_update();
×
960
                    lnav_data.ld_filter_help_status_source.fss_error.clear();
×
961
                }
962
            }
963
            break;
×
964
        }
965
    }
966
}
3✔
967

968
void
969
filter_sub_source::text_filter_row::ti_completion_request(
3✔
970
    textview_curses* top_view,
971
    textinput_curses& tc,
972
    completion_request_type_t crt)
973
{
974
    static const auto FILTER_HELP
975
        = help_text("filter", "filter the view by a pattern")
1✔
976
              .with_parameter(
1✔
977
                  help_text("pattern", "The pattern to filter by")
2✔
978
                      .with_format(help_parameter_format_t::HPF_REGEX));
5✔
979
    static const auto FILTER_EXPR_HELP
980
        = help_text("filter-expr", "filter the view by a SQL expression")
1✔
981
              .with_parameter(
1✔
982
                  help_text("expr", "The expression to evaluate")
2✔
983
                      .with_format(help_parameter_format_t::HPF_SQL_EXPR));
5✔
984
    static auto& prompt = lnav::prompt::get();
3✔
985

986
    auto& al = tc.tc_lines[tc.tc_cursor.y];
3✔
987
    auto al_sf = al.to_string_fragment().sub_cell_range(0, tc.tc_cursor.x);
3✔
988
    auto is_regex = tc.tc_text_format == text_format_t::TF_PCRE;
3✔
989
    auto parse_res = lnav::command::parse_for_prompt(
990
        lnav_data.ld_exec_context,
991
        al_sf,
992
        is_regex ? FILTER_HELP : FILTER_EXPR_HELP);
3✔
993

994
    switch (crt) {
3✔
995
        case completion_request_type_t::partial: {
3✔
996
            if (al_sf.endswith(" ")) {
3✔
997
                if (tc.is_cursor_at_end_of_line()) {
×
998
                    tc.tc_suggestion
999
                        = prompt.get_regex_suggestion(*top_view, al.al_string);
×
1000
                }
1001
                return;
×
1002
            }
1003
            break;
3✔
1004
        }
1005
        case completion_request_type_t::full:
×
1006
            break;
×
1007
    }
1008

1009
    auto byte_x = al_sf.column_to_byte_index(tc.tc_cursor.x);
3✔
1010
    auto arg_res_opt = parse_res.arg_at(byte_x);
3✔
1011
    if (arg_res_opt) {
3✔
1012
        auto arg_pair = arg_res_opt.value();
3✔
1013
        if (crt == completion_request_type_t::full
3✔
1014
            || tc.tc_popup_type != textinput_curses::popup_type_t::none)
3✔
1015
        {
1016
            auto poss = prompt.get_cmd_parameter_completion(
1017
                *top_view,
1018
                &FILTER_HELP,
1019
                arg_pair.aar_help,
1020
                arg_pair.aar_element.se_value);
×
1021
            auto left = arg_pair.aar_element.se_value.empty()
×
1022
                ? tc.tc_cursor.x
×
1023
                : al_sf.byte_to_column_index(
×
1024
                      arg_pair.aar_element.se_origin.sf_begin);
×
1025
            tc.open_popup_for_completion(left, poss);
×
1026
            tc.tc_popup.set_title(arg_pair.aar_help->ht_name);
×
1027
        } else if (arg_pair.aar_element.se_value.empty()
3✔
1028
                   && tc.is_cursor_at_end_of_line())
3✔
1029
        {
1030
            tc.tc_suggestion
1031
                = prompt.get_regex_suggestion(*top_view, al.al_string);
×
1032
        } else {
1033
            log_debug("not at end of line");
3✔
1034
            tc.tc_suggestion.clear();
3✔
1035
        }
1036
    } else {
3✔
1037
        log_debug("no arg");
×
1038
    }
1039
}
3✔
1040

1041
void
1042
filter_sub_source::text_filter_row::ti_perform(textview_curses* top_view,
1✔
1043
                                               textinput_curses& ti,
1044
                                               filter_sub_source& parent)
1045
{
1046
    static const intern_string_t INPUT_SRC = intern_string::lookup("input");
3✔
1047

1048
    auto* tss = top_view->get_sub_source();
1✔
1049
    auto& fs = tss->get_filters();
1✔
1050
    auto new_value = ti.get_content();
1✔
1051

1052
    fs.fs_generation += 1;
1✔
1053
    if (new_value.empty()) {
1✔
NEW
1054
        this->ti_abort(top_view, ti, parent);
×
1055
    } else {
1056
        switch (this->tfr_filter->get_lang()) {
1✔
1057
            case filter_lang_t::NONE:
1✔
1058
            case filter_lang_t::REGEX: {
1059
                auto compile_res
1060
                    = lnav::pcre2pp::code::from(new_value, PCRE2_CASELESS);
1✔
1061

1062
                if (compile_res.isErr()) {
1✔
1063
                    auto ce = compile_res.unwrapErr();
×
1064
                    auto um = lnav::console::to_user_message(INPUT_SRC, ce);
×
1065
                    lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
NEW
1066
                    this->ti_abort(top_view, ti, parent);
×
1067
                } else {
×
1068
                    auto code_ptr = compile_res.unwrap().to_shared();
1✔
1069
                    this->tfr_filter->lf_deleted = true;
1✔
1070
                    tss->text_filters_changed();
1✔
1071

1072
                    auto pf = std::make_shared<pcre_filter>(
1073
                        this->tfr_filter->get_type(),
1✔
1074
                        new_value,
1075
                        this->tfr_filter->get_index(),
1✔
1076
                        code_ptr);
1✔
1077

1078
                    parent.fss_regexp_history.insert_plain_content(new_value);
1✔
1079

1080
                    auto iter
1081
                        = std::find(fs.begin(), fs.end(), this->tfr_filter);
1✔
1082

1083
                    *iter = pf;
1✔
1084
                    tss->text_filters_changed();
1✔
1085
                }
1✔
1086
                break;
1✔
1087
            }
1✔
1088
            case filter_lang_t::SQL: {
×
1089
                auto full_sql
1090
                    = fmt::format(FMT_STRING("SELECT 1 WHERE {}"), new_value);
×
1091
                auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
×
1092
#ifdef SQLITE_PREPARE_PERSISTENT
1093
                auto retcode = sqlite3_prepare_v3(lnav_data.ld_db.in(),
×
1094
                                                  full_sql.c_str(),
1095
                                                  full_sql.size(),
×
1096
                                                  SQLITE_PREPARE_PERSISTENT,
1097
                                                  stmt.out(),
1098
                                                  nullptr);
1099
#else
1100
                auto retcode = sqlite3_prepare_v2(lnav_data.ld_db.in(),
1101
                                                  full_sql.c_str(),
1102
                                                  full_sql.size(),
1103
                                                  stmt.out(),
1104
                                                  nullptr);
1105
#endif
1106
                if (retcode != SQLITE_OK) {
×
1107
                    auto sqlerr = annotate_sql_with_error(
1108
                        lnav_data.ld_db.in(), full_sql.c_str(), nullptr);
×
1109
                    auto um
1110
                        = lnav::console::user_message::error(
×
1111
                              "invalid SQL expression")
1112
                              .with_reason(sqlite3_errmsg(lnav_data.ld_db.in()))
×
1113
                              .with_snippet(lnav::console::snippet::from(
×
1114
                                  INPUT_SRC, sqlerr));
×
1115
                    lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
NEW
1116
                    this->ti_abort(top_view, ti, parent);
×
1117
                } else {
×
1118
                    lnav_data.ld_log_source.set_sql_filter(new_value,
×
1119
                                                           stmt.release());
1120
                    tss->text_filters_changed();
×
1121
                }
1122
                break;
×
1123
            }
1124
        }
1125
    }
1126

1127
    top_view->reload_data();
1✔
1128
}
1✔
1129

1130
void
NEW
1131
filter_sub_source::text_filter_row::ti_abort(textview_curses* top_view,
×
1132
                                             textinput_curses& tc,
1133
                                             filter_sub_source& parent)
1134
{
1135
    auto* tss = top_view->get_sub_source();
×
1136
    auto& fs = tss->get_filters();
×
1137

1138
    top_view->reload_data();
×
1139
    fs.delete_filter("");
×
NEW
1140
    this->tfr_filter->set_enabled(parent.fss_filter_state);
×
1141
    tss->text_filters_changed();
×
1142
}
1143

1144
std::vector<std::unique_ptr<filter_sub_source::filter_row>>
1145
filter_sub_source::rows_for(textview_curses* tc) const
69✔
1146
{
1147
    auto retval = row_vector{};
69✔
1148
    auto* tss = tc->get_sub_source();
69✔
1149
    if (tss == nullptr) {
69✔
NEW
1150
        return retval;
×
1151
    }
1152

1153
    const auto* ttt = dynamic_cast<text_time_translator*>(tss);
69✔
1154

1155
    if (ttt != nullptr) {
69✔
1156
        if (this->fss_min_time) {
69✔
NEW
1157
            retval.emplace_back(std::make_unique<min_time_filter_row>(
×
1158
                this->fss_min_time.value()));
1159
        } else {
1160
            auto min_time = ttt->get_min_row_time();
69✔
1161
            if (min_time) {
69✔
NEW
1162
                retval.emplace_back(
×
NEW
1163
                    std::make_unique<min_time_filter_row>(min_time.value()));
×
1164
            }
1165
        }
1166
        if (this->fss_max_time) {
69✔
NEW
1167
            retval.emplace_back(std::make_unique<max_time_filter_row>(
×
1168
                this->fss_max_time.value()));
1169
        } else {
1170
            auto max_time = ttt->get_max_row_time();
69✔
1171
            if (max_time) {
69✔
NEW
1172
                retval.emplace_back(
×
NEW
1173
                    std::make_unique<max_time_filter_row>(max_time.value()));
×
1174
            }
1175
        }
1176
    }
1177

1178
    auto& fs = tss->get_filters();
69✔
1179
    for (auto& tf : fs) {
126✔
1180
        retval.emplace_back(std::make_unique<text_filter_row>(tf));
57✔
1181
    }
1182

1183
    return retval;
69✔
UNCOV
1184
}
×
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