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

tstack / lnav / 19954566052-2734

05 Dec 2025 06:13AM UTC coverage: 68.901% (-0.005%) from 68.906%
19954566052-2734

push

github

tstack
[filters] block creating a duplicate filter

Related to #1046

1 of 6 new or added lines in 1 file covered. (16.67%)

1 existing line in 1 file now uncovered.

51481 of 74717 relevant lines covered (68.9%)

435600.16 hits per line

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

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

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

54
filter_sub_source::filter_sub_source(std::shared_ptr<textinput_curses> editor)
2✔
55
    : fss_editor(std::move(editor)),
2✔
56
      fss_regexp_history(
57
          lnav::textinput::history::for_context("regexp-filter"_frag)),
2✔
58
      fss_sql_history(lnav::textinput::history::for_context("sql-filter"_frag))
4✔
59
{
60
    this->fss_editor->set_visible(false);
2✔
61
    this->fss_editor->set_x(28);
2✔
62
    this->fss_editor->tc_popup.set_title("Pattern");
4✔
63
    this->fss_editor->tc_height = 1;
2✔
64
    this->fss_editor->tc_on_change
2✔
65
        = bind_mem(&filter_sub_source::rl_change, this);
4✔
66
    this->fss_editor->tc_on_history_search
2✔
67
        = bind_mem(&filter_sub_source::rl_history, this);
4✔
68
    this->fss_editor->tc_on_history_list
2✔
69
        = bind_mem(&filter_sub_source::rl_history, this);
4✔
70
    this->fss_editor->tc_on_completion_request
2✔
71
        = bind_mem(&filter_sub_source::rl_completion_request, this);
4✔
72
    this->fss_editor->tc_on_completion
2✔
73
        = bind_mem(&filter_sub_source::rl_completion, this);
4✔
74
    this->fss_editor->tc_on_perform
2✔
75
        = bind_mem(&filter_sub_source::rl_perform, this);
4✔
76
    this->fss_editor->tc_on_blur = bind_mem(&filter_sub_source::rl_blur, this);
2✔
77
    this->fss_editor->tc_on_abort
2✔
78
        = bind_mem(&filter_sub_source::rl_abort, this);
4✔
79
}
2✔
80

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

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

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

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

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

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

147
                this->fss_editing = true;
×
148
                this->tss_view->set_enabled(false);
×
149
                this->fss_view_text_possibilities
150
                    = view_text_possibilities(*top_view);
×
151
                (*row_iter)->prime_text_input(
×
152
                    top_view, *this->fss_editor, *this);
×
153
                this->fss_editor->set_y(lv.get_y_for_selection());
×
154
                this->fss_editor->set_visible(true);
×
155
                this->fss_editor->focus();
×
156
                this->fss_filter_state = true;
×
157
            } else {
×
158
            }
159
            return true;
×
160
        }
161
        case 'm': {
×
162
            auto* top_view = *lnav_data.ld_view_stack.top();
×
163
            auto* ttt = dynamic_cast<text_time_translator*>(
×
164
                top_view->get_sub_source());
×
165
            if (ttt != nullptr) {
×
166
                auto curr_min = ttt->get_min_row_time();
×
167
                this->fss_filter_state = curr_min.has_value();
×
168
                if (curr_min) {
×
169
                    this->fss_min_time = curr_min;
×
170
                    ttt->set_min_row_time(text_time_translator::min_time_init);
×
171
                    ttt->ttt_preview_min_time = curr_min;
×
172
                    top_view->get_sub_source()->text_filters_changed();
×
173
                } else {
174
                    auto sel = top_view->get_selection();
×
175
                    if (sel) {
×
176
                        auto ri_opt = ttt->time_for_row(sel.value());
×
177
                        if (ri_opt) {
×
178
                            auto ri = ri_opt.value();
×
179

180
                            this->fss_min_time = ri.ri_time;
×
181
                        }
182
                    }
183
                    if (!this->fss_min_time) {
×
184
                        this->fss_min_time = current_timeval();
×
185
                    }
186
                    ttt->ttt_preview_min_time = this->fss_min_time;
×
187
                }
188

189
                lv.set_selection(0_vl);
×
190
                lv.reload_data();
×
191

192
                auto rows = this->rows_for(top_view);
×
193
                auto& row = rows[0];
×
194
                this->fss_editing = true;
×
195
                this->tss_view->set_enabled(false);
×
196
                row->prime_text_input(top_view, *this->fss_editor, *this);
×
197
                this->fss_editor->set_y(lv.get_y_for_selection());
×
198
                this->fss_editor->set_visible(true);
×
199
                this->fss_editor->focus();
×
200
                this->tss_view->reload_data();
×
201
                return true;
×
202
            }
203
            break;
×
204
        }
205
        case 'M': {
×
206
            auto* top_view = *lnav_data.ld_view_stack.top();
×
207
            auto* ttt = dynamic_cast<text_time_translator*>(
×
208
                top_view->get_sub_source());
×
209
            if (ttt != nullptr) {
×
210
                auto curr_max = ttt->get_max_row_time();
×
211
                this->fss_filter_state = curr_max.has_value();
×
212
                if (curr_max) {
×
213
                    this->fss_max_time = curr_max;
×
214
                    ttt->ttt_preview_max_time = curr_max;
×
215
                    ttt->set_max_row_time(text_time_translator::max_time_init);
×
216
                    top_view->get_sub_source()->text_filters_changed();
×
217
                } else {
218
                    auto sel = top_view->get_selection();
×
219
                    if (sel) {
×
220
                        auto ri_opt = ttt->time_for_row(sel.value());
×
221
                        if (ri_opt) {
×
222
                            auto ri = ri_opt.value();
×
223

224
                            this->fss_max_time = ri.ri_time;
×
225
                        }
226
                    }
227
                    if (!this->fss_max_time) {
×
228
                        this->fss_max_time = current_timeval();
×
229
                    }
230
                    ttt->ttt_preview_max_time = this->fss_max_time;
×
231
                }
232

233
                auto new_sel = ttt->get_min_row_time() ? 1_vl : 0_vl;
×
234
                lv.set_selection(new_sel);
×
235
                lv.reload_data();
×
236

237
                auto rows = this->rows_for(top_view);
×
238
                auto& row = rows[new_sel];
×
239
                this->fss_editing = true;
×
240
                this->tss_view->set_enabled(false);
×
241
                row->prime_text_input(top_view, *this->fss_editor, *this);
×
242
                this->fss_editor->set_y(lv.get_y_for_selection());
×
243
                this->fss_editor->set_visible(true);
×
244
                this->fss_editor->focus();
×
245
                this->tss_view->reload_data();
×
246
                return true;
×
247
            }
248
            break;
×
249
        }
250
        case 'i':
1✔
251
        case 'o': {
252
            auto* top_view = *lnav_data.ld_view_stack.top();
1✔
253
            auto* tss = top_view->get_sub_source();
1✔
254
            auto& fs = tss->get_filters();
1✔
255
            auto filter_index = fs.next_index();
1✔
256

257
            if (!filter_index) {
1✔
258
                lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
259
                    "error: too many filters");
260
                return true;
×
261
            }
262

263
            auto filter_type = ch.eff_text[0] == 'i'
1✔
264
                ? text_filter::type_t::INCLUDE
1✔
265
                : text_filter::type_t::EXCLUDE;
266
            auto ef = std::make_shared<empty_filter>(
267
                filter_type, filter_lang_t::REGEX, *filter_index);
1✔
268
            fs.add_filter(ef);
1✔
269

270
            auto rows = this->rows_for(top_view);
1✔
271
            lv.set_selection(vis_line_t(rows.size()) - 1_vl);
1✔
272
            auto& row = rows.back();
1✔
273
            lv.reload_data();
1✔
274

275
            this->fss_editing = true;
1✔
276
            this->tss_view->set_enabled(false);
1✔
277
            this->fss_view_text_possibilities
278
                = view_text_possibilities(*top_view);
1✔
279
            row->prime_text_input(top_view, *this->fss_editor, *this);
1✔
280
            this->fss_editor->set_y(lv.get_y_for_selection());
1✔
281
            this->fss_editor->set_visible(true);
1✔
282
            this->fss_editor->focus();
1✔
283
            this->fss_filter_state = true;
1✔
284
            return true;
1✔
285
        }
1✔
286
        case '\r':
×
287
        case NCKEY_ENTER: {
288
            auto* top_view = *lnav_data.ld_view_stack.top();
×
289
            auto rows = this->rows_for(top_view);
×
290

291
            if (rows.empty() || !lv.get_selection()) {
×
292
                return true;
×
293
            }
294

295
            auto& row = rows[lv.get_selection().value()];
×
296
            this->fss_editing = true;
×
297
            this->tss_view->set_enabled(false);
×
298
            this->fss_editor->set_y(lv.get_y_for_selection());
×
299
            this->fss_editor->set_visible(true);
×
300
            this->fss_editor->tc_suggestion.clear();
×
301
            this->fss_view_text_possibilities
302
                = view_text_possibilities(*top_view);
×
303
            this->fss_editor->focus();
×
304
            this->fss_filter_state
305
                = row->prime_text_input(top_view, *this->fss_editor, *this);
×
306
            top_view->get_sub_source()->text_filters_changed();
×
307
            return true;
×
308
        }
309
        case 'n': {
×
310
            lnav_data.ld_exec_context.execute(INTERNAL_SRC_LOC,
×
311
                                              ":next-mark search");
312
            return true;
×
313
        }
314
        case 'N': {
×
315
            lnav_data.ld_exec_context.execute(INTERNAL_SRC_LOC,
×
316
                                              ":prev-mark search");
317
            return true;
×
318
        }
319
        case '/': {
×
320
            lnav_data.ld_exec_context.execute(INTERNAL_SRC_LOC,
×
321
                                              ":prompt search-filters");
322
            return true;
×
323
        }
324
        default:
1✔
325
            log_debug("unhandled %x", ch.eff_text[0]);
1✔
326
            break;
1✔
327
    }
328

329
    return false;
1✔
330
}
331

332
size_t
333
filter_sub_source::text_line_count()
79✔
334
{
335
    return (lnav_data.ld_view_stack.top() |
×
336
                [this](auto tc) -> std::optional<size_t> {
59✔
337
               auto* tss = tc->get_sub_source();
59✔
338

339
               if (tss == nullptr) {
59✔
340
                   return std::nullopt;
×
341
               }
342
               auto rows = this->rows_for(tc);
59✔
343
               return std::make_optional(rows.size());
59✔
344
           })
138✔
345
        .value_or(0);
79✔
346
}
347

348
size_t
349
filter_sub_source::text_line_width(textview_curses& curses)
×
350
{
351
    auto* top_view = *lnav_data.ld_view_stack.top();
×
352
    auto* tss = top_view->get_sub_source();
×
353
    auto& fs = tss->get_filters();
×
354
    size_t retval = 0;
×
355

356
    for (auto& filter : fs) {
×
357
        retval = std::max(filter->get_id().size() + 8, retval);
×
358
    }
359

360
    return retval;
×
361
}
362

363
line_info
364
filter_sub_source::text_value_for_line(textview_curses& tc,
1✔
365
                                       int line,
366
                                       std::string& value_out,
367
                                       line_flags_t flags)
368
{
369
    auto* top_view = *lnav_data.ld_view_stack.top();
1✔
370
    auto selected
371
        = lnav_data.ld_mode == ln_mode_t::FILTER && line == tc.get_selection();
1✔
372
    auto rows = this->rows_for(top_view);
1✔
373
    auto rs = render_state{top_view};
1✔
374
    auto& al = this->fss_curr_line;
1✔
375

376
    al.clear();
1✔
377
    if (selected && this->fss_editing) {
1✔
378
        rs.rs_editing = true;
1✔
379
    }
380
    if (selected) {
1✔
381
        al.append(" ", VC_GRAPHIC.value(NCACS_RARROW));
1✔
382
    } else {
383
        al.append(" ");
×
384
    }
385
    al.append(" ");
1✔
386

387
    auto& row = rows[line];
1✔
388

389
    row->value_for(rs, al);
1✔
390
    if (selected) {
1✔
391
        al.with_attr_for_all(VC_ROLE.value(role_t::VCR_FOCUSED));
1✔
392
    }
393

394
    value_out = al.al_string;
1✔
395
    return {};
2✔
396
}
1✔
397

398
void
399
filter_sub_source::text_attrs_for_line(textview_curses& tc,
1✔
400
                                       int line,
401
                                       string_attrs_t& value_out)
402
{
403
    value_out = this->fss_curr_line.get_attrs();
1✔
404
}
1✔
405

406
size_t
407
filter_sub_source::text_size_for_line(textview_curses& tc,
×
408
                                      int line,
409
                                      text_sub_source::line_flags_t raw)
410
{
411
    // XXX
412
    return 40;
×
413
}
414

415
void
416
filter_sub_source::rl_change(textinput_curses& rc)
3✔
417
{
418
    auto* top_view = *lnav_data.ld_view_stack.top();
3✔
419
    auto rows = this->rows_for(top_view);
3✔
420
    auto& row = rows[this->tss_view->get_selection().value()];
3✔
421

422
    row->ti_change(top_view, rc);
3✔
423
}
3✔
424

425
void
426
filter_sub_source::rl_history(textinput_curses& tc)
×
427
{
428
    switch (tc.tc_text_format) {
×
429
        case text_format_t::TF_PCRE: {
×
430
            std::vector<attr_line_t> poss;
×
431
            this->fss_regexp_history.query_entries(
×
432
                tc.get_content(),
×
433
                [&poss](const auto& e) { poss.emplace_back(e.e_content); });
×
434
            tc.open_popup_for_history(poss);
×
435
            break;
×
436
        }
437
        case text_format_t::TF_UNKNOWN:
×
438
        case text_format_t::TF_SQL: {
439
            break;
×
440
        }
441
        default:
×
442
            ensure(false);
×
443
            break;
444
    }
445
}
446

447
void
448
filter_sub_source::rl_completion_request_int(textinput_curses& tc,
×
449
                                             completion_request_type_t crt)
450
{
451
    auto* top_view = *lnav_data.ld_view_stack.top();
×
452
    auto rows = this->rows_for(top_view);
×
453
    auto& row = rows[this->tss_view->get_selection().value()];
×
454

455
    row->ti_completion_request(top_view, tc, crt);
×
456
}
457

458
void
459
filter_sub_source::rl_completion_request(textinput_curses& tc)
×
460
{
461
    this->rl_completion_request_int(tc, completion_request_type_t::full);
×
462
}
463

464
void
465
filter_sub_source::rl_completion(textinput_curses& tc)
×
466
{
467
    static auto& prompt = lnav::prompt::get();
468

469
    prompt.rl_completion(tc);
×
470
}
471

472
void
473
filter_sub_source::rl_perform(textinput_curses& rc)
1✔
474
{
475
    auto* top_view = *lnav_data.ld_view_stack.top();
1✔
476
    auto rows = this->rows_for(top_view);
1✔
477
    auto& row = rows[this->tss_view->get_selection().value()];
1✔
478

479
    row->ti_perform(top_view, rc, *this);
1✔
480
    this->fss_min_time = std::nullopt;
1✔
481
    this->fss_max_time = std::nullopt;
1✔
482
    this->tss_view->reload_data();
1✔
483
}
1✔
484

485
void
486
filter_sub_source::rl_blur(textinput_curses& tc)
1✔
487
{
488
    auto* top_view = *lnav_data.ld_view_stack.top();
1✔
489
    top_view->clear_preview();
1✔
490
    lnav_data.ld_filter_help_status_source.fss_prompt.clear();
1✔
491
    lnav_data.ld_filter_help_status_source.fss_error.clear();
1✔
492
    this->fss_editing = false;
1✔
493
    tc.set_visible(false);
1✔
494
    this->tss_view->set_enabled(true);
1✔
495
}
1✔
496

497
void
498
filter_sub_source::rl_abort(textinput_curses& rc)
×
499
{
500
    auto* top_view = *lnav_data.ld_view_stack.top();
×
501
    auto rows = this->rows_for(top_view);
×
502
    auto& row = rows[this->tss_view->get_selection().value()];
×
503

504
    row->ti_abort(top_view, rc, *this);
×
505
    this->fss_min_time = std::nullopt;
×
506
    this->fss_max_time = std::nullopt;
×
507
    this->tss_view->reload_data();
×
508
}
509

510
void
511
filter_sub_source::list_input_handle_scroll_out(listview_curses& lv)
×
512
{
513
    set_view_mode(ln_mode_t::PAGING);
×
514
    lnav_data.ld_filter_view.reload_data();
×
515
}
516

517
bool
518
filter_sub_source::text_handle_mouse(
×
519
    textview_curses& tc,
520
    const listview_curses::display_line_content_t&,
521
    mouse_event& me)
522
{
523
    if (this->fss_editing) {
×
524
        return true;
×
525
    }
526
    auto nci = ncinput{};
×
527
    if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 1, 3)) {
×
528
        nci.id = ' ';
×
529
        nci.eff_text[0] = ' ';
×
530
        this->list_input_handle_key(tc, nci);
×
531
    }
532
    if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 4, 7)) {
×
533
        nci.id = 't';
×
534
        nci.eff_text[0] = 't';
×
535
        this->list_input_handle_key(tc, nci);
×
536
    }
537
    if (me.is_double_click_in(mouse_button_t::BUTTON_LEFT, line_range{25, -1}))
×
538
    {
539
        nci.id = '\r';
×
540
        nci.eff_text[0] = '\r';
×
541
        this->list_input_handle_key(tc, nci);
×
542
    }
543
    return true;
×
544
}
545

546
bool
547
filter_sub_source::min_time_filter_row::handle_key(textview_curses* top_view,
×
548
                                                   const ncinput& ch)
549
{
550
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
551
    switch (ch.eff_text[0]) {
×
552
        case 'D':
×
553
            ttt->set_min_row_time(text_time_translator::min_time_init);
×
554
            top_view->get_sub_source()->text_filters_changed();
×
555
            return true;
×
556
        default:
×
557
            return false;
×
558
    }
559
}
560

561
void
562
filter_sub_source::min_time_filter_row::value_for(const render_state& rs,
×
563
                                                  attr_line_t& al)
564
{
565
    al.append("Min Time"_table_header).append(" ");
×
566
    if (rs.rs_editing) {
×
567
        al.append(fmt::format(FMT_STRING("{:>9}"), "-"),
×
568
                  VC_ROLE.value(role_t::VCR_NUMBER));
×
569
    } else {
570
        al.append(fmt::format(
×
571
                      FMT_STRING("{:>9}"),
×
572
                      rs.rs_top_view->get_sub_source()->get_filtered_before()),
×
573
                  VC_ROLE.value(role_t::VCR_NUMBER));
×
574
    }
575
    al.append(" hits ").append("|", VC_GRAPHIC.value(NCACS_VLINE)).append(" ");
×
576
    al.append(lnav::to_rfc3339_string(this->tfr_time, 'T'));
×
577
}
578

579
Result<timeval, std::string>
580
filter_sub_source::time_filter_row::parse_time(textview_curses* top_view,
×
581
                                               textinput_curses& tc)
582
{
583
    auto content = tc.get_content();
×
584
    if (content.empty()) {
×
585
        return Err(std::string("expecting a timestamp or relative time"));
×
586
    }
587

588
    auto parse_res = relative_time::from_str(content);
×
589
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
590
    date_time_scanner dts;
×
591

592
    if (top_view->get_inner_height() > 0_vl) {
×
593
        auto top_time_opt = ttt->time_for_row(
×
594
            top_view->get_selection().value_or(top_view->get_top()));
×
595

596
        if (top_time_opt) {
×
597
            auto top_time_tv = top_time_opt.value().ri_time;
×
598
            tm top_tm;
599

600
            localtime_r(&top_time_tv.tv_sec, &top_tm);
×
601
            dts.set_base_time(top_time_tv.tv_sec, top_tm);
×
602
        }
603
    }
604
    if (parse_res.isOk()) {
×
605
        auto rt = parse_res.unwrap();
×
606
        auto tv_opt
607
            = ttt->time_for_row(top_view->get_selection().value_or(0_vl));
×
608
        auto tv = current_timeval();
×
609
        if (tv_opt) {
×
610
            tv = tv_opt.value().ri_time;
×
611
        }
612
        auto tm = rt.adjust(tv);
×
613
        return Ok(tm.to_timeval());
×
614
    }
615
    auto time_str = tc.get_content();
×
616
    exttm tm;
×
617
    timeval tv;
618
    const auto* scan_end
619
        = dts.scan(time_str.c_str(), time_str.size(), nullptr, &tm, tv);
×
620
    if (scan_end != nullptr) {
×
621
        auto matched_size = scan_end - time_str.c_str();
×
622
        if (matched_size == time_str.size()) {
×
623
            return Ok(tv);
×
624
        }
625

626
        return Err(fmt::format(FMT_STRING("extraneous input '{}'"), scan_end));
×
627
    }
628

629
    auto pe = parse_res.unwrapErr();
×
630
    return Err(pe.pe_msg);
×
631
}
632

633
void
634
filter_sub_source::min_time_filter_row::ti_perform(textview_curses* top_view,
×
635
                                                   textinput_curses& tc,
636
                                                   filter_sub_source& parent)
637
{
638
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
639
    auto parse_res = this->parse_time(top_view, tc);
×
640
    if (parse_res.isOk()) {
×
641
        auto tv = parse_res.unwrap();
×
642

643
        ttt->set_min_row_time(tv);
×
644
    } else {
645
        auto msg = parse_res.unwrapErr();
×
646
        if (parent.fss_filter_state) {
×
647
            ttt->set_min_row_time(parent.fss_min_time.value());
×
648
        }
649

650
        auto um
651
            = lnav::console::user_message::error("could not parse timestamp")
×
652
                  .with_reason(msg);
×
653
        lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
654
    }
655
    parent.fss_min_time = std::nullopt;
×
656
    top_view->get_sub_source()->text_filters_changed();
×
657
}
658

659
void
660
filter_sub_source::min_time_filter_row::ti_abort(textview_curses* top_view,
×
661
                                                 textinput_curses& tc,
662
                                                 filter_sub_source& parent)
663
{
664
    auto* tss = top_view->get_sub_source();
×
665
    auto* ttt = dynamic_cast<text_time_translator*>(tss);
×
666

667
    if (parent.fss_filter_state) {
×
668
        ttt->set_min_row_time(parent.fss_min_time.value());
×
669
    }
670
    tss->text_filters_changed();
×
671
}
672

673
bool
674
filter_sub_source::time_filter_row::prime_text_input(textview_curses* top_view,
×
675
                                                     textinput_curses& ti,
676
                                                     filter_sub_source& parent)
677
{
678
    ti.tc_text_format = text_format_t::TF_UNKNOWN;
×
679
    ti.set_content(lnav::to_rfc3339_string(this->tfr_time, 'T'));
×
680
    return true;
×
681
}
682

683
void
684
filter_sub_source::min_time_filter_row::ti_change(textview_curses* top_view,
×
685
                                                  textinput_curses& rc)
686
{
687
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
688
    auto parse_res = this->parse_time(top_view, rc);
×
689

690
    if (parse_res.isOk()) {
×
691
        auto tv = parse_res.unwrap();
×
692
        ttt->ttt_preview_min_time = tv;
×
693
        lnav_data.ld_filter_help_status_source.fss_error.clear();
×
694
    } else {
695
        ttt->ttt_preview_min_time = std::nullopt;
×
696
        auto msg = parse_res.unwrapErr();
×
697
        lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
698
            "error: could not parse timestamp -- %s", msg.c_str());
699
    }
700
}
701

702
void
703
filter_sub_source::max_time_filter_row::ti_change(textview_curses* top_view,
×
704
                                                  textinput_curses& rc)
705
{
706
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
707
    auto parse_res = this->parse_time(top_view, rc);
×
708

709
    if (parse_res.isOk()) {
×
710
        auto tv = parse_res.unwrap();
×
711
        ttt->ttt_preview_max_time = tv;
×
712
        lnav_data.ld_filter_help_status_source.fss_error.clear();
×
713
    } else {
714
        ttt->ttt_preview_max_time = std::nullopt;
×
715
        auto msg = parse_res.unwrapErr();
×
716
        lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
717
            "error: could not parse timestamp -- %s", msg.c_str());
718
    }
719
}
720

721
void
722
filter_sub_source::time_filter_row::ti_completion_request(
×
723
    textview_curses* top_view,
724
    textinput_curses& tc,
725
    completion_request_type_t crt)
726
{
727
}
728

729
bool
730
filter_sub_source::max_time_filter_row::handle_key(textview_curses* top_view,
×
731
                                                   const ncinput& ch)
732
{
733
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
734
    switch (ch.eff_text[0]) {
×
735
        case 'D':
×
736
            ttt->set_max_row_time(text_time_translator::max_time_init);
×
737
            top_view->get_sub_source()->text_filters_changed();
×
738
            return true;
×
739
        default:
×
740
            return false;
×
741
    }
742
}
743

744
void
745
filter_sub_source::max_time_filter_row::value_for(const render_state& rs,
×
746
                                                  attr_line_t& al)
747
{
748
    al.append("Max Time"_table_header).append(" ");
×
749
    if (rs.rs_editing) {
×
750
        al.append(fmt::format(FMT_STRING("{:>9}"), "-"),
×
751
                  VC_ROLE.value(role_t::VCR_NUMBER));
×
752
    } else {
753
        al.append(
×
754
            fmt::format(FMT_STRING("{:>9}"),
×
755
                        rs.rs_top_view->get_sub_source()->get_filtered_after()),
×
756
            VC_ROLE.value(role_t::VCR_NUMBER));
×
757
    }
758
    al.append(" hits ").append("|", VC_GRAPHIC.value(NCACS_VLINE)).append(" ");
×
759
    al.append(lnav::to_rfc3339_string(this->tfr_time, 'T'));
×
760
}
761

762
void
763
filter_sub_source::max_time_filter_row::ti_perform(textview_curses* top_view,
×
764
                                                   textinput_curses& tc,
765
                                                   filter_sub_source& parent)
766
{
767
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
768
    auto parse_res = this->parse_time(top_view, tc);
×
769
    if (parse_res.isOk()) {
×
770
        auto tv = parse_res.unwrap();
×
771

772
        ttt->set_max_row_time(tv);
×
773
    } else {
774
        auto msg = parse_res.unwrapErr();
×
775
        if (parent.fss_filter_state) {
×
776
            ttt->set_max_row_time(parent.fss_max_time.value());
×
777
        }
778
        auto um
779
            = lnav::console::user_message::error("could not parse timestamp")
×
780
                  .with_reason(msg);
×
781
        lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
782
    }
783
    parent.fss_max_time = std::nullopt;
×
784
    top_view->get_sub_source()->text_filters_changed();
×
785
}
786

787
void
788
filter_sub_source::max_time_filter_row::ti_abort(textview_curses* top_view,
×
789
                                                 textinput_curses& tc,
790
                                                 filter_sub_source& parent)
791
{
792
    auto* tss = top_view->get_sub_source();
×
793
    auto* ttt = dynamic_cast<text_time_translator*>(tss);
×
794

795
    if (parent.fss_filter_state) {
×
796
        ttt->set_max_row_time(parent.fss_max_time.value());
×
797
    }
798
    tss->text_filters_changed();
×
799
}
800

801
void
802
filter_sub_source::text_filter_row::value_for(const render_state& rs,
1✔
803
                                              attr_line_t& al)
804
{
805
    attr_line_builder alb(al);
1✔
806
    if (this->tfr_filter->is_enabled()) {
1✔
807
        al.append("\u25c6"_ok);
×
808
    } else {
809
        al.append("\u25c7"_comment);
1✔
810
    }
811
    al.append(" ");
1✔
812
    switch (this->tfr_filter->get_type()) {
1✔
813
        case text_filter::INCLUDE:
×
814
            al.append(" ").append(lnav::roles::ok("IN")).append(" ");
×
815
            break;
×
816
        case text_filter::EXCLUDE:
1✔
817
            if (this->tfr_filter->get_lang() == filter_lang_t::REGEX) {
1✔
818
                al.append(lnav::roles::error("OUT")).append(" ");
1✔
819
            } else {
820
                al.append("    ");
×
821
            }
822
            break;
1✔
823
        default:
×
824
            ensure(0);
×
825
            break;
826
    }
827
    al.append("   ");
1✔
828

829
    {
830
        auto ag = alb.with_attr(VC_ROLE.value(role_t::VCR_NUMBER));
1✔
831
        if (rs.rs_editing) {
1✔
832
            alb.appendf(FMT_STRING("{:>9}"), "-");
3✔
833
        } else {
834
            alb.appendf(
×
835
                FMT_STRING("{:>9L}"),
×
836
                rs.rs_top_view->get_sub_source()->get_filtered_count_for(
×
837
                    this->tfr_filter->get_index()));
×
838
        }
839
    }
1✔
840

841
    al.append(" hits ").append("|", VC_GRAPHIC.value(NCACS_VLINE)).append(" ");
1✔
842

843
    attr_line_t content{this->tfr_filter->get_id()};
2✔
844
    switch (this->tfr_filter->get_lang()) {
1✔
845
        case filter_lang_t::REGEX:
1✔
846
            readline_regex_highlighter(content, std::nullopt);
1✔
847
            break;
1✔
848
        case filter_lang_t::SQL:
×
849
            readline_sql_highlighter(
×
850
                content, lnav::sql::dialect::sqlite, std::nullopt);
851
            break;
×
852
        case filter_lang_t::NONE:
×
853
            break;
×
854
    }
855
    al.append(content);
1✔
856
}
1✔
857

858
bool
859
filter_sub_source::text_filter_row::handle_key(textview_curses* top_view,
×
860
                                               const ncinput& ch)
861
{
862
    switch (ch.eff_text[0]) {
×
863
        case ' ': {
×
864
            auto& fs = top_view->get_sub_source()->get_filters();
×
865
            fs.set_filter_enabled(this->tfr_filter,
×
866
                                  !this->tfr_filter->is_enabled());
×
867
            return true;
×
868
        }
869
        case 't': {
×
870
            if (this->tfr_filter->get_type() == text_filter::INCLUDE) {
×
871
                this->tfr_filter->set_type(text_filter::EXCLUDE);
×
872
            } else {
873
                this->tfr_filter->set_type(text_filter::INCLUDE);
×
874
            }
875
            return true;
×
876
        }
877
        case 'D': {
×
878
            auto& fs = top_view->get_sub_source()->get_filters();
×
879

880
            fs.delete_filter(this->tfr_filter->get_id());
×
881
            return true;
×
882
        }
883
        default:
×
884
            return false;
×
885
    }
886
}
887

888
bool
889
filter_sub_source::text_filter_row::prime_text_input(textview_curses* top_view,
1✔
890
                                                     textinput_curses& ti,
891
                                                     filter_sub_source& parent)
892
{
893
    static auto& prompt = lnav::prompt::get();
1✔
894

895
    ti.tc_text_format = this->tfr_filter->get_lang() == filter_lang_t::SQL
1✔
896
        ? text_format_t::TF_SQL
1✔
897
        : text_format_t::TF_PCRE;
898
    if (this->tfr_filter->get_lang() == filter_lang_t::SQL) {
1✔
899
        prompt.refresh_sql_completions(*top_view);
×
900
        prompt.refresh_sql_expr_completions(*top_view);
×
901
    }
902
    ti.set_content(this->tfr_filter->get_id());
1✔
903
    if (this->tfr_filter->get_id().empty()) {
1✔
904
        ti.tc_suggestion = top_view->get_input_suggestion();
1✔
905
    }
906
    auto retval = this->tfr_filter->is_enabled();
1✔
907
    this->tfr_filter->disable();
1✔
908

909
    return retval;
1✔
910
}
911

912
void
913
filter_sub_source::text_filter_row::ti_change(textview_curses* top_view,
3✔
914
                                              textinput_curses& rc)
915
{
916
    auto new_value = rc.get_content();
3✔
917
    auto* tss = top_view->get_sub_source();
3✔
918
    auto& fs = tss->get_filters();
3✔
919

920
    top_view->get_highlights().erase({highlight_source_t::PREVIEW, "preview"});
3✔
921
    top_view->set_needs_update();
3✔
922
    switch (this->tfr_filter->get_lang()) {
3✔
923
        case filter_lang_t::NONE:
×
924
            break;
×
925
        case filter_lang_t::REGEX: {
3✔
926
            if (new_value.empty()) {
3✔
927
                auto sugg = top_view->get_current_search();
×
928
                if (top_view->tc_selected_text) {
×
929
                    sugg = top_view->tc_selected_text->sti_value;
×
930
                }
931
                if (fs.get_filter(sugg) == nullptr) {
×
932
                    rc.tc_suggestion = sugg;
×
933
                } else {
934
                    rc.tc_suggestion.clear();
×
935
                }
936
            } else {
×
937
                auto regex_res
938
                    = lnav::pcre2pp::code::from(new_value, PCRE2_CASELESS);
3✔
939

940
                this->ti_completion_request(
3✔
941
                    top_view, rc, completion_request_type_t::partial);
942
                if (regex_res.isErr()) {
3✔
943
                    auto pe = regex_res.unwrapErr();
×
944
                    lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
945
                        "error: %s", pe.get_message().c_str());
×
946
                } else {
×
947
                    auto& hm = top_view->get_highlights();
3✔
948
                    highlighter hl(regex_res.unwrap().to_shared());
3✔
949
                    auto role
950
                        = this->tfr_filter->get_type() == text_filter::EXCLUDE
3✔
951
                        ? role_t::VCR_DIFF_DELETE
3✔
952
                        : role_t::VCR_DIFF_ADD;
3✔
953
                    hl.with_role(role);
3✔
954
                    hl.with_attrs(text_attrs::with_styles(
3✔
955
                        text_attrs::style::blink, text_attrs::style::reverse));
956
                    hm[{highlight_source_t::PREVIEW, "preview"}] = hl;
3✔
957
                    top_view->set_needs_update();
3✔
958
                    lnav_data.ld_filter_help_status_source.fss_error.clear();
3✔
959
                }
3✔
960
            }
3✔
961
            break;
3✔
962
        }
963
        case filter_lang_t::SQL: {
×
964
            this->ti_completion_request(
×
965
                top_view, rc, completion_request_type_t::partial);
966

967
            auto full_sql
968
                = fmt::format(FMT_STRING("SELECT 1 WHERE {}"), new_value);
×
969
            auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
×
970
#ifdef SQLITE_PREPARE_PERSISTENT
971
            auto retcode = sqlite3_prepare_v3(lnav_data.ld_db.in(),
×
972
                                              full_sql.c_str(),
973
                                              full_sql.size(),
×
974
                                              SQLITE_PREPARE_PERSISTENT,
975
                                              stmt.out(),
976
                                              nullptr);
977
#else
978
            auto retcode = sqlite3_prepare_v2(lnav_data.ld_db.in(),
979
                                              full_sql.c_str(),
980
                                              full_sql.size(),
981
                                              stmt.out(),
982
                                              nullptr);
983
#endif
984
            if (retcode != SQLITE_OK) {
×
985
                lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
986
                    "error: %s", sqlite3_errmsg(lnav_data.ld_db));
987
            } else {
988
                auto set_res = lnav_data.ld_log_source.set_preview_sql_filter(
989
                    stmt.release());
×
990

991
                if (set_res.isErr()) {
×
992
                    lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
993
                        "error: %s",
994
                        set_res.unwrapErr()
×
995
                            .to_attr_line()
×
996
                            .get_string()
×
997
                            .c_str());
998
                } else {
999
                    top_view->set_needs_update();
×
1000
                    lnav_data.ld_filter_help_status_source.fss_error.clear();
×
1001
                }
1002
            }
1003
            break;
×
1004
        }
1005
    }
1006
}
3✔
1007

1008
void
1009
filter_sub_source::text_filter_row::ti_completion_request(
3✔
1010
    textview_curses* top_view,
1011
    textinput_curses& tc,
1012
    completion_request_type_t crt)
1013
{
1014
    static const auto FILTER_HELP
1015
        = help_text("filter", "filter the view by a pattern")
1✔
1016
              .with_parameter(
1✔
1017
                  help_text("pattern", "The pattern to filter by")
2✔
1018
                      .with_format(help_parameter_format_t::HPF_REGEX));
5✔
1019
    static const auto FILTER_EXPR_HELP
1020
        = help_text("filter-expr", "filter the view by a SQL expression")
1✔
1021
              .with_parameter(
1✔
1022
                  help_text("expr", "The expression to evaluate")
2✔
1023
                      .with_format(help_parameter_format_t::HPF_SQL_EXPR));
5✔
1024
    static auto& prompt = lnav::prompt::get();
3✔
1025

1026
    auto& al = tc.tc_lines[tc.tc_cursor.y];
3✔
1027
    auto al_sf = al.to_string_fragment().sub_cell_range(0, tc.tc_cursor.x);
3✔
1028
    auto is_regex = tc.tc_text_format == text_format_t::TF_PCRE;
3✔
1029
    auto parse_res = lnav::command::parse_for_prompt(
1030
        lnav_data.ld_exec_context,
1031
        al_sf,
1032
        is_regex ? FILTER_HELP : FILTER_EXPR_HELP);
3✔
1033

1034
    switch (crt) {
3✔
1035
        case completion_request_type_t::partial: {
3✔
1036
            if (al_sf.endswith(" ")) {
3✔
1037
                if (tc.is_cursor_at_end_of_line()) {
×
1038
                    tc.tc_suggestion
1039
                        = prompt.get_regex_suggestion(*top_view, al.al_string);
×
1040
                }
1041
                return;
×
1042
            }
1043
            break;
3✔
1044
        }
1045
        case completion_request_type_t::full:
×
1046
            break;
×
1047
    }
1048

1049
    auto byte_x = al_sf.column_to_byte_index(tc.tc_cursor.x);
3✔
1050
    auto arg_res_opt = parse_res.arg_at(byte_x);
3✔
1051
    if (arg_res_opt) {
3✔
1052
        auto arg_pair = arg_res_opt.value();
3✔
1053
        if (crt == completion_request_type_t::full
3✔
1054
            || tc.tc_popup_type != textinput_curses::popup_type_t::none)
3✔
1055
        {
1056
            auto poss = prompt.get_cmd_parameter_completion(
1057
                *top_view,
1058
                &FILTER_HELP,
1059
                arg_pair.aar_help,
1060
                arg_pair.aar_element.se_value);
×
1061
            auto left = arg_pair.aar_element.se_value.empty()
×
1062
                ? tc.tc_cursor.x
×
1063
                : al_sf.byte_to_column_index(
×
1064
                      arg_pair.aar_element.se_origin.sf_begin);
×
1065
            tc.open_popup_for_completion(left, poss);
×
1066
            tc.tc_popup.set_title(arg_pair.aar_help->ht_name);
×
1067
        } else if (arg_pair.aar_element.se_value.empty()
3✔
1068
                   && tc.is_cursor_at_end_of_line())
3✔
1069
        {
1070
            tc.tc_suggestion
1071
                = prompt.get_regex_suggestion(*top_view, al.al_string);
×
1072
        } else {
1073
            log_debug("not at end of line");
3✔
1074
            tc.tc_suggestion.clear();
3✔
1075
        }
1076
    } else {
3✔
1077
        log_debug("no arg");
×
1078
    }
1079
}
3✔
1080

1081
void
1082
filter_sub_source::text_filter_row::ti_perform(textview_curses* top_view,
1✔
1083
                                               textinput_curses& ti,
1084
                                               filter_sub_source& parent)
1085
{
1086
    static const intern_string_t INPUT_SRC = intern_string::lookup("input");
3✔
1087

1088
    auto* tss = top_view->get_sub_source();
1✔
1089
    auto& fs = tss->get_filters();
1✔
1090
    auto new_value = ti.get_content();
1✔
1091

1092
    fs.fs_generation += 1;
1✔
1093
    if (new_value.empty()) {
1✔
1094
        this->ti_abort(top_view, ti, parent);
×
1095
    } else {
1096
        switch (this->tfr_filter->get_lang()) {
1✔
1097
            case filter_lang_t::NONE:
1✔
1098
            case filter_lang_t::REGEX: {
1099
                auto compile_res
1100
                    = lnav::pcre2pp::code::from(new_value, PCRE2_CASELESS);
1✔
1101

1102
                if (compile_res.isErr()) {
1✔
1103
                    auto ce = compile_res.unwrapErr();
×
1104
                    auto um = lnav::console::to_user_message(INPUT_SRC, ce);
×
1105
                    lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
1106
                    this->ti_abort(top_view, ti, parent);
×
1107
                } else if (fs.get_filter(new_value) != nullptr) {
1✔
1108
                    auto snip
NEW
1109
                        = lnav::console::snippet::from(INPUT_SRC, new_value);
×
NEW
1110
                    auto um = lnav::console::user_message::error(
×
1111
                                  "Filter already exists")
NEW
1112
                                  .with_snippet(snip);
×
NEW
1113
                    lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
NEW
1114
                    this->ti_abort(top_view, ti, parent);
×
UNCOV
1115
                } else {
×
1116
                    auto code_ptr = compile_res.unwrap().to_shared();
1✔
1117
                    this->tfr_filter->lf_deleted = true;
1✔
1118
                    tss->text_filters_changed();
1✔
1119

1120
                    auto pf = std::make_shared<pcre_filter>(
1121
                        this->tfr_filter->get_type(),
1✔
1122
                        new_value,
1123
                        this->tfr_filter->get_index(),
1✔
1124
                        code_ptr);
1✔
1125

1126
                    parent.fss_regexp_history.insert_plain_content(new_value);
1✔
1127

1128
                    auto iter
1129
                        = std::find(fs.begin(), fs.end(), this->tfr_filter);
1✔
1130

1131
                    *iter = pf;
1✔
1132
                    tss->text_filters_changed();
1✔
1133
                }
1✔
1134
                break;
1✔
1135
            }
1✔
1136
            case filter_lang_t::SQL: {
×
1137
                auto full_sql
1138
                    = fmt::format(FMT_STRING("SELECT 1 WHERE {}"), new_value);
×
1139
                auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
×
1140
#ifdef SQLITE_PREPARE_PERSISTENT
1141
                auto retcode = sqlite3_prepare_v3(lnav_data.ld_db.in(),
×
1142
                                                  full_sql.c_str(),
1143
                                                  full_sql.size(),
×
1144
                                                  SQLITE_PREPARE_PERSISTENT,
1145
                                                  stmt.out(),
1146
                                                  nullptr);
1147
#else
1148
                auto retcode = sqlite3_prepare_v2(lnav_data.ld_db.in(),
1149
                                                  full_sql.c_str(),
1150
                                                  full_sql.size(),
1151
                                                  stmt.out(),
1152
                                                  nullptr);
1153
#endif
1154
                if (retcode != SQLITE_OK) {
×
1155
                    auto sqlerr = annotate_sql_with_error(
1156
                        lnav_data.ld_db.in(), full_sql.c_str(), nullptr);
×
1157
                    auto um
1158
                        = lnav::console::user_message::error(
×
1159
                              "invalid SQL expression")
1160
                              .with_reason(sqlite3_errmsg(lnav_data.ld_db.in()))
×
1161
                              .with_snippet(lnav::console::snippet::from(
×
1162
                                  INPUT_SRC, sqlerr));
×
1163
                    lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
1164
                    this->ti_abort(top_view, ti, parent);
×
1165
                } else {
×
1166
                    lnav_data.ld_log_source.set_sql_filter(new_value,
×
1167
                                                           stmt.release());
1168
                    tss->text_filters_changed();
×
1169
                }
1170
                break;
×
1171
            }
1172
        }
1173
    }
1174

1175
    top_view->reload_data();
1✔
1176
}
1✔
1177

1178
void
1179
filter_sub_source::text_filter_row::ti_abort(textview_curses* top_view,
×
1180
                                             textinput_curses& tc,
1181
                                             filter_sub_source& parent)
1182
{
1183
    auto* tss = top_view->get_sub_source();
×
1184
    auto& fs = tss->get_filters();
×
1185

1186
    top_view->reload_data();
×
1187
    fs.delete_filter("");
×
1188
    this->tfr_filter->set_enabled(parent.fss_filter_state);
×
1189
    tss->text_filters_changed();
×
1190
}
1191

1192
std::vector<std::unique_ptr<filter_sub_source::filter_row>>
1193
filter_sub_source::rows_for(textview_curses* tc) const
69✔
1194
{
1195
    auto retval = row_vector{};
69✔
1196
    auto* tss = tc->get_sub_source();
69✔
1197
    if (tss == nullptr) {
69✔
1198
        return retval;
×
1199
    }
1200

1201
    const auto* ttt = dynamic_cast<text_time_translator*>(tss);
69✔
1202

1203
    if (ttt != nullptr) {
69✔
1204
        if (this->fss_min_time) {
69✔
1205
            retval.emplace_back(std::make_unique<min_time_filter_row>(
×
1206
                this->fss_min_time.value()));
1207
        } else {
1208
            auto min_time = ttt->get_min_row_time();
69✔
1209
            if (min_time) {
69✔
1210
                retval.emplace_back(
×
1211
                    std::make_unique<min_time_filter_row>(min_time.value()));
×
1212
            }
1213
        }
1214
        if (this->fss_max_time) {
69✔
1215
            retval.emplace_back(std::make_unique<max_time_filter_row>(
×
1216
                this->fss_max_time.value()));
1217
        } else {
1218
            auto max_time = ttt->get_max_row_time();
69✔
1219
            if (max_time) {
69✔
1220
                retval.emplace_back(
×
1221
                    std::make_unique<max_time_filter_row>(max_time.value()));
×
1222
            }
1223
        }
1224
    }
1225

1226
    auto& fs = tss->get_filters();
69✔
1227
    for (auto& tf : fs) {
126✔
1228
        retval.emplace_back(std::make_unique<text_filter_row>(tf));
57✔
1229
    }
1230

1231
    return retval;
69✔
1232
}
×
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