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

tstack / lnav / 20245728190-2749

15 Dec 2025 07:59PM UTC coverage: 68.864% (-0.07%) from 68.929%
20245728190-2749

push

github

tstack
[text_format] add plaintext type

Related to #1296

85 of 132 new or added lines in 24 files covered. (64.39%)

73 existing lines in 10 files now uncovered.

51605 of 74938 relevant lines covered (68.86%)

434003.35 hits per line

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

32.06
/src/filter_sub_source.cc
1
/**
2
 * Copyright (c) 2018, Timothy Stack
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 * * Redistributions of source code must retain the above copyright notice, this
10
 * list of conditions and the following disclaimer.
11
 * * Redistributions in binary form must reproduce the above copyright notice,
12
 * this list of conditions and the following disclaimer in the documentation
13
 * and/or other materials provided with the distribution.
14
 * * Neither the name of Timothy Stack nor the names of its contributors
15
 * may be used to endorse or promote products derived from this software
16
 * without specific prior written permission.
17
 *
18
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 */
29

30
#include <memory>
31
#include <optional>
32
#include <utility>
33

34
#include "filter_sub_source.hh"
35

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

309
            if (rows.empty() || !lv.get_selection()) {
×
310
                return true;
×
311
            }
312

313
            auto& row = rows[lv.get_selection().value()];
×
314
            this->fss_editing = true;
×
315
            this->tss_view->set_enabled(false);
×
316
            this->fss_editor->set_y(lv.get_y_for_selection());
×
317
            this->fss_editor->set_visible(true);
×
318
            this->fss_editor->tc_suggestion.clear();
×
319
            this->fss_view_text_possibilities
320
                = view_text_possibilities(*top_view);
×
321
            this->fss_editor->focus();
×
322
            this->fss_filter_state
323
                = row->prime_text_input(top_view, *this->fss_editor, *this);
×
324
            top_view->get_sub_source()->text_filters_changed();
×
325
            return true;
×
326
        }
327
        case 'n': {
×
328
            lnav_data.ld_exec_context.execute(INTERNAL_SRC_LOC,
×
329
                                              ":next-mark search");
330
            return true;
×
331
        }
332
        case 'N': {
×
333
            lnav_data.ld_exec_context.execute(INTERNAL_SRC_LOC,
×
334
                                              ":prev-mark search");
335
            return true;
×
336
        }
337
        case '/': {
×
338
            lnav_data.ld_exec_context.execute(INTERNAL_SRC_LOC,
×
339
                                              ":prompt search-filters");
340
            return true;
×
341
        }
342
        default:
1✔
343
            log_debug("unhandled %x", ch.eff_text[0]);
1✔
344
            break;
1✔
345
    }
346

347
    return false;
1✔
348
}
349

350
size_t
351
filter_sub_source::text_line_count()
79✔
352
{
353
    return (lnav_data.ld_view_stack.top() |
×
354
                [this](auto tc) -> std::optional<size_t> {
59✔
355
               auto* tss = tc->get_sub_source();
59✔
356

357
               if (tss == nullptr) {
59✔
358
                   return std::nullopt;
×
359
               }
360
               auto rows = this->rows_for(tc);
59✔
361
               return std::make_optional(rows.size());
59✔
362
           })
138✔
363
        .value_or(0);
79✔
364
}
365

366
size_t
367
filter_sub_source::text_line_width(textview_curses& curses)
×
368
{
369
    auto* top_view = *lnav_data.ld_view_stack.top();
×
370
    auto* tss = top_view->get_sub_source();
×
371
    auto& fs = tss->get_filters();
×
372
    size_t retval = 0;
×
373

374
    for (auto& filter : fs) {
×
375
        retval = std::max(filter->get_id().size() + 8, retval);
×
376
    }
377

378
    return retval;
×
379
}
380

381
line_info
382
filter_sub_source::text_value_for_line(textview_curses& tc,
1✔
383
                                       int line,
384
                                       std::string& value_out,
385
                                       line_flags_t flags)
386
{
387
    auto* top_view = *lnav_data.ld_view_stack.top();
1✔
388
    auto selected
389
        = lnav_data.ld_mode == ln_mode_t::FILTER && line == tc.get_selection();
1✔
390
    auto rows = this->rows_for(top_view);
1✔
391
    auto rs = render_state{top_view};
1✔
392
    auto& al = this->fss_curr_line;
1✔
393

394
    al.clear();
1✔
395
    if (selected && this->fss_editing) {
1✔
396
        rs.rs_editing = true;
1✔
397
    }
398
    if (selected) {
1✔
399
        al.append(" ", VC_GRAPHIC.value(NCACS_RARROW));
1✔
400
    } else {
401
        al.append(" ");
×
402
    }
403
    al.append(" ");
1✔
404

405
    auto& row = rows[line];
1✔
406

407
    row->value_for(rs, al);
1✔
408
    if (selected) {
1✔
409
        al.with_attr_for_all(VC_ROLE.value(role_t::VCR_FOCUSED));
1✔
410
    }
411

412
    value_out = al.al_string;
1✔
413
    return {};
2✔
414
}
1✔
415

416
void
417
filter_sub_source::text_attrs_for_line(textview_curses& tc,
1✔
418
                                       int line,
419
                                       string_attrs_t& value_out)
420
{
421
    value_out = this->fss_curr_line.get_attrs();
1✔
422
}
1✔
423

424
size_t
425
filter_sub_source::text_size_for_line(textview_curses& tc,
×
426
                                      int line,
427
                                      text_sub_source::line_flags_t raw)
428
{
429
    // XXX
430
    return 40;
×
431
}
432

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

440
    row->ti_change(top_view, rc);
3✔
441
}
3✔
442

443
void
444
filter_sub_source::rl_history(textinput_curses& tc)
×
445
{
446
    switch (tc.tc_text_format) {
×
447
        case text_format_t::TF_PCRE: {
×
448
            std::vector<attr_line_t> poss;
×
449
            this->fss_regexp_history.query_entries(
×
450
                tc.get_content(),
×
451
                [&poss](const auto& e) { poss.emplace_back(e.e_content); });
×
452
            tc.open_popup_for_history(poss);
×
453
            break;
×
454
        }
NEW
455
        case text_format_t::TF_PLAINTEXT:
×
456
        case text_format_t::TF_SQL: {
457
            break;
×
458
        }
459
        default:
×
460
            ensure(false);
×
461
            break;
462
    }
463
}
464

465
void
466
filter_sub_source::rl_completion_request_int(textinput_curses& tc,
×
467
                                             completion_request_type_t crt)
468
{
469
    auto* top_view = *lnav_data.ld_view_stack.top();
×
470
    auto rows = this->rows_for(top_view);
×
471
    auto& row = rows[this->tss_view->get_selection().value()];
×
472

473
    row->ti_completion_request(top_view, tc, crt);
×
474
}
475

476
void
477
filter_sub_source::rl_completion_request(textinput_curses& tc)
×
478
{
479
    this->rl_completion_request_int(tc, completion_request_type_t::full);
×
480
}
481

482
void
483
filter_sub_source::rl_completion(textinput_curses& tc)
×
484
{
485
    static auto& prompt = lnav::prompt::get();
486

487
    prompt.rl_completion(tc);
×
488
}
489

490
void
491
filter_sub_source::rl_perform(textinput_curses& rc)
1✔
492
{
493
    auto* top_view = *lnav_data.ld_view_stack.top();
1✔
494
    auto rows = this->rows_for(top_view);
1✔
495
    auto& row = rows[this->tss_view->get_selection().value()];
1✔
496

497
    row->ti_perform(top_view, rc, *this);
1✔
498
    this->fss_min_time = std::nullopt;
1✔
499
    this->fss_max_time = std::nullopt;
1✔
500
    this->tss_view->reload_data();
1✔
501
}
1✔
502

503
void
504
filter_sub_source::rl_blur(textinput_curses& tc)
1✔
505
{
506
    auto* top_view = *lnav_data.ld_view_stack.top();
1✔
507
    top_view->clear_preview();
1✔
508
    lnav_data.ld_filter_help_status_source.fss_prompt.clear();
1✔
509
    lnav_data.ld_filter_help_status_source.fss_error.clear();
1✔
510
    this->fss_editing = false;
1✔
511
    tc.set_visible(false);
1✔
512
    this->tss_view->set_enabled(true);
1✔
513
}
1✔
514

515
void
516
filter_sub_source::rl_abort(textinput_curses& rc)
×
517
{
518
    auto* top_view = *lnav_data.ld_view_stack.top();
×
519
    auto rows = this->rows_for(top_view);
×
520
    auto& row = rows[this->tss_view->get_selection().value()];
×
521

522
    row->ti_abort(top_view, rc, *this);
×
523
    this->fss_min_time = std::nullopt;
×
524
    this->fss_max_time = std::nullopt;
×
525
    this->tss_view->reload_data();
×
526
}
527

528
void
529
filter_sub_source::list_input_handle_scroll_out(listview_curses& lv)
×
530
{
531
    set_view_mode(ln_mode_t::PAGING);
×
532
    lnav_data.ld_filter_view.reload_data();
×
533
}
534

535
bool
536
filter_sub_source::text_handle_mouse(
×
537
    textview_curses& tc,
538
    const listview_curses::display_line_content_t&,
539
    mouse_event& me)
540
{
541
    if (this->fss_editing) {
×
542
        return true;
×
543
    }
544
    auto nci = ncinput{};
×
545
    if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 1, 3)) {
×
546
        nci.id = ' ';
×
547
        nci.eff_text[0] = ' ';
×
548
        this->list_input_handle_key(tc, nci);
×
549
    }
550
    if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 4, 7)) {
×
551
        nci.id = 't';
×
552
        nci.eff_text[0] = 't';
×
553
        this->list_input_handle_key(tc, nci);
×
554
    }
555
    if (me.is_double_click_in(mouse_button_t::BUTTON_LEFT, line_range{25, -1}))
×
556
    {
557
        nci.id = '\r';
×
558
        nci.eff_text[0] = '\r';
×
559
        this->list_input_handle_key(tc, nci);
×
560
    }
561
    return true;
×
562
}
563

564
bool
565
filter_sub_source::min_time_filter_row::handle_key(textview_curses* top_view,
×
566
                                                   const ncinput& ch)
567
{
568
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
569
    switch (ch.eff_text[0]) {
×
570
        case 'D':
×
571
            ttt->set_min_row_time(text_time_translator::min_time_init);
×
572
            top_view->get_sub_source()->text_filters_changed();
×
573
            return true;
×
574
        default:
×
575
            return false;
×
576
    }
577
}
578

579
void
580
filter_sub_source::min_time_filter_row::value_for(const render_state& rs,
×
581
                                                  attr_line_t& al)
582
{
583
    al.append("Min Time"_table_header).append(" ");
×
584
    if (rs.rs_editing) {
×
585
        al.append(fmt::format(FMT_STRING("{:>9}"), "-"),
×
586
                  VC_ROLE.value(role_t::VCR_NUMBER));
×
587
    } else {
588
        al.append(fmt::format(
×
589
                      FMT_STRING("{:>9}"),
×
590
                      rs.rs_top_view->get_sub_source()->get_filtered_before()),
×
591
                  VC_ROLE.value(role_t::VCR_NUMBER));
×
592
    }
593
    al.append(" hits ").append("|", VC_GRAPHIC.value(NCACS_VLINE)).append(" ");
×
594
    al.append(lnav::to_rfc3339_string(this->tfr_time, 'T'));
×
595
}
596

597
Result<timeval, std::string>
598
filter_sub_source::time_filter_row::parse_time(textview_curses* top_view,
×
599
                                               textinput_curses& tc)
600
{
601
    auto content = tc.get_content();
×
602
    if (content.empty()) {
×
603
        return Err(std::string("expecting a timestamp or relative time"));
×
604
    }
605

606
    auto parse_res = relative_time::from_str(content);
×
607
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
608
    date_time_scanner dts;
×
609

610
    if (top_view->get_inner_height() > 0_vl) {
×
611
        auto top_time_opt = ttt->time_for_row(
×
612
            top_view->get_selection().value_or(top_view->get_top()));
×
613

614
        if (top_time_opt) {
×
615
            auto top_time_tv = top_time_opt.value().ri_time;
×
616
            tm top_tm;
617

618
            localtime_r(&top_time_tv.tv_sec, &top_tm);
×
619
            dts.set_base_time(top_time_tv.tv_sec, top_tm);
×
620
        }
621
    }
622
    if (parse_res.isOk()) {
×
623
        auto rt = parse_res.unwrap();
×
624
        auto tv_opt
625
            = ttt->time_for_row(top_view->get_selection().value_or(0_vl));
×
626
        auto tv = current_timeval();
×
627
        if (tv_opt) {
×
628
            tv = tv_opt.value().ri_time;
×
629
        }
630
        auto tm = rt.adjust(tv);
×
631
        return Ok(tm.to_timeval());
×
632
    }
633
    auto time_str = tc.get_content();
×
634
    exttm tm;
×
635
    timeval tv;
636
    const auto* scan_end
637
        = dts.scan(time_str.c_str(), time_str.size(), nullptr, &tm, tv);
×
638
    if (scan_end != nullptr) {
×
639
        auto matched_size = scan_end - time_str.c_str();
×
640
        if (matched_size == time_str.size()) {
×
641
            return Ok(tv);
×
642
        }
643

644
        return Err(fmt::format(FMT_STRING("extraneous input '{}'"), scan_end));
×
645
    }
646

647
    auto pe = parse_res.unwrapErr();
×
648
    return Err(pe.pe_msg);
×
649
}
650

651
void
652
filter_sub_source::min_time_filter_row::ti_perform(textview_curses* top_view,
×
653
                                                   textinput_curses& tc,
654
                                                   filter_sub_source& parent)
655
{
656
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
657
    auto parse_res = this->parse_time(top_view, tc);
×
658
    if (parse_res.isOk()) {
×
659
        auto tv = parse_res.unwrap();
×
660

661
        ttt->set_min_row_time(tv);
×
662
    } else {
663
        auto msg = parse_res.unwrapErr();
×
664
        if (parent.fss_filter_state) {
×
665
            ttt->set_min_row_time(parent.fss_min_time.value());
×
666
        }
667

668
        auto um
669
            = lnav::console::user_message::error("could not parse timestamp")
×
670
                  .with_reason(msg);
×
671
        lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
672
    }
673
    parent.fss_min_time = std::nullopt;
×
674
    top_view->get_sub_source()->text_filters_changed();
×
675
}
676

677
void
678
filter_sub_source::min_time_filter_row::ti_abort(textview_curses* top_view,
×
679
                                                 textinput_curses& tc,
680
                                                 filter_sub_source& parent)
681
{
682
    auto* tss = top_view->get_sub_source();
×
683
    auto* ttt = dynamic_cast<text_time_translator*>(tss);
×
684

685
    if (parent.fss_filter_state) {
×
686
        ttt->set_min_row_time(parent.fss_min_time.value());
×
687
    }
688
    tss->text_filters_changed();
×
689
}
690

691
void
692
filter_sub_source::level_filter_row::value_for(const render_state& rs,
×
693
                                               attr_line_t& al)
694
{
695
    auto lev = rs.rs_top_view->get_sub_source()->get_min_log_level();
×
696

697
    al.append("  ").append("Level"_table_header).append("  ");
×
698
    if (rs.rs_editing) {
×
699
        al.append(fmt::format(FMT_STRING("{:>9}"), "-"),
×
700
                  VC_ROLE.value(role_t::VCR_NUMBER));
×
701
    } else {
702
        al.append(
×
703
            fmt::format(
×
704
                FMT_STRING("{:>9}"),
×
705
                rs.rs_top_view->get_sub_source()->tss_level_filtered_count),
×
706
            VC_ROLE.value(role_t::VCR_NUMBER));
×
707
    }
708
    al.append(" hits ")
×
709
        .append("|", VC_GRAPHIC.value(NCACS_VLINE))
×
710
        .append(" ")
×
711
        .append(level_names[lev]);
×
712
}
713

714
bool
715
filter_sub_source::level_filter_row::handle_key(textview_curses* top_view,
×
716
                                                const ncinput& ch)
717
{
718
    switch (ch.eff_text[0]) {
×
719
        case 'D':
×
720
            top_view->get_sub_source()->set_min_log_level(LEVEL_UNKNOWN);
×
721
            return true;
×
722
    }
723
    return false;
×
724
}
725

726
bool
727
filter_sub_source::level_filter_row::prime_text_input(textview_curses* top_view,
×
728
                                                      textinput_curses& ti,
729
                                                      filter_sub_source& parent)
730
{
731
    auto* tss = top_view->get_sub_source();
×
732
    auto lev = tss->get_min_log_level();
×
733
    parent.fss_curr_level = lev;
×
734
    tss->set_min_log_level(LEVEL_TRACE);
×
735
    tss->text_filters_changed();
×
736
    ti.set_content(level_names[lev].to_string());
×
737
    ti.tc_selection
738
        = ti.clamp_selection(textinput_curses::selected_range::from_mouse(
×
739
            textinput_curses::input_point::home(),
740
            textinput_curses::input_point::end()));
×
741
    return true;
×
742
}
743

744
void
745
filter_sub_source::level_filter_row::ti_change(textview_curses* top_view,
×
746
                                               textinput_curses& rc)
747
{
748
    auto lev = rc.get_content();
×
749
    top_view->get_sub_source()->tss_preview_min_log_level
×
750
        = string2level(lev.c_str(), lev.size(), false);
×
751
    this->ti_completion_request(
×
752
        top_view, rc, completion_request_type_t::partial);
753
}
754

755
void
756
filter_sub_source::level_filter_row::ti_completion_request(
×
757
    textview_curses* top_view,
758
    textinput_curses& tc,
759
    completion_request_type_t crt)
760
{
761
    auto lev = tc.get_content();
×
762
    auto poss = level_names | lnav::itertools::similar_to(lev)
×
763
        | lnav::itertools::map([](const auto& elem) {
×
764
                    return attr_line_t().append(elem).with_attr_for_all(
×
765
                        lnav::prompt::SUBST_TEXT.value(elem.to_string()));
×
766
                });
×
767
    tc.open_popup_for_completion(0, poss);
×
768
}
769

770
void
771
filter_sub_source::level_filter_row::ti_perform(textview_curses* top_view,
×
772
                                                textinput_curses& tc,
773
                                                filter_sub_source& parent)
774
{
775
    auto lev = tc.get_content();
×
776
    auto new_level = string2level(lev.c_str(), lev.size(), false);
×
777
    top_view->get_sub_source()->set_min_log_level(new_level);
×
778
}
779

780
void
781
filter_sub_source::level_filter_row::ti_abort(textview_curses* top_view,
×
782
                                              textinput_curses& tc,
783
                                              filter_sub_source& parent)
784
{
785
    top_view->get_sub_source()->set_min_log_level(parent.fss_curr_level);
×
786
}
787

788
bool
789
filter_sub_source::time_filter_row::prime_text_input(textview_curses* top_view,
×
790
                                                     textinput_curses& ti,
791
                                                     filter_sub_source& parent)
792
{
NEW
793
    ti.tc_text_format = text_format_t::TF_PLAINTEXT;
×
794
    ti.set_content(lnav::to_rfc3339_string(this->tfr_time, 'T'));
×
795
    return true;
×
796
}
797

798
void
799
filter_sub_source::min_time_filter_row::ti_change(textview_curses* top_view,
×
800
                                                  textinput_curses& rc)
801
{
802
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
803
    auto parse_res = this->parse_time(top_view, rc);
×
804

805
    if (parse_res.isOk()) {
×
806
        auto tv = parse_res.unwrap();
×
807
        ttt->ttt_preview_min_time = tv;
×
808
        lnav_data.ld_filter_help_status_source.fss_error.clear();
×
809
    } else {
810
        ttt->ttt_preview_min_time = std::nullopt;
×
811
        auto msg = parse_res.unwrapErr();
×
812
        lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
813
            "error: could not parse timestamp -- %s", msg.c_str());
814
    }
815
}
816

817
void
818
filter_sub_source::max_time_filter_row::ti_change(textview_curses* top_view,
×
819
                                                  textinput_curses& rc)
820
{
821
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
822
    auto parse_res = this->parse_time(top_view, rc);
×
823

824
    if (parse_res.isOk()) {
×
825
        auto tv = parse_res.unwrap();
×
826
        ttt->ttt_preview_max_time = tv;
×
827
        lnav_data.ld_filter_help_status_source.fss_error.clear();
×
828
    } else {
829
        ttt->ttt_preview_max_time = std::nullopt;
×
830
        auto msg = parse_res.unwrapErr();
×
831
        lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
832
            "error: could not parse timestamp -- %s", msg.c_str());
833
    }
834
}
835

836
void
837
filter_sub_source::time_filter_row::ti_completion_request(
×
838
    textview_curses* top_view,
839
    textinput_curses& tc,
840
    completion_request_type_t crt)
841
{
842
}
843

844
bool
845
filter_sub_source::max_time_filter_row::handle_key(textview_curses* top_view,
×
846
                                                   const ncinput& ch)
847
{
848
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
849
    switch (ch.eff_text[0]) {
×
850
        case 'D':
×
851
            ttt->set_max_row_time(text_time_translator::max_time_init);
×
852
            top_view->get_sub_source()->text_filters_changed();
×
853
            return true;
×
854
        default:
×
855
            return false;
×
856
    }
857
}
858

859
void
860
filter_sub_source::max_time_filter_row::value_for(const render_state& rs,
×
861
                                                  attr_line_t& al)
862
{
863
    al.append("Max Time"_table_header).append(" ");
×
864
    if (rs.rs_editing) {
×
865
        al.append(fmt::format(FMT_STRING("{:>9}"), "-"),
×
866
                  VC_ROLE.value(role_t::VCR_NUMBER));
×
867
    } else {
868
        al.append(
×
869
            fmt::format(FMT_STRING("{:>9}"),
×
870
                        rs.rs_top_view->get_sub_source()->get_filtered_after()),
×
871
            VC_ROLE.value(role_t::VCR_NUMBER));
×
872
    }
873
    al.append(" hits ").append("|", VC_GRAPHIC.value(NCACS_VLINE)).append(" ");
×
874
    al.append(lnav::to_rfc3339_string(this->tfr_time, 'T'));
×
875
}
876

877
void
878
filter_sub_source::max_time_filter_row::ti_perform(textview_curses* top_view,
×
879
                                                   textinput_curses& tc,
880
                                                   filter_sub_source& parent)
881
{
882
    auto* ttt = dynamic_cast<text_time_translator*>(top_view->get_sub_source());
×
883
    auto parse_res = this->parse_time(top_view, tc);
×
884
    if (parse_res.isOk()) {
×
885
        auto tv = parse_res.unwrap();
×
886

887
        ttt->set_max_row_time(tv);
×
888
    } else {
889
        auto msg = parse_res.unwrapErr();
×
890
        if (parent.fss_filter_state) {
×
891
            ttt->set_max_row_time(parent.fss_max_time.value());
×
892
        }
893
        auto um
894
            = lnav::console::user_message::error("could not parse timestamp")
×
895
                  .with_reason(msg);
×
896
        lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
897
    }
898
    parent.fss_max_time = std::nullopt;
×
899
    top_view->get_sub_source()->text_filters_changed();
×
900
}
901

902
void
903
filter_sub_source::max_time_filter_row::ti_abort(textview_curses* top_view,
×
904
                                                 textinput_curses& tc,
905
                                                 filter_sub_source& parent)
906
{
907
    auto* tss = top_view->get_sub_source();
×
908
    auto* ttt = dynamic_cast<text_time_translator*>(tss);
×
909

910
    if (parent.fss_filter_state) {
×
911
        ttt->set_max_row_time(parent.fss_max_time.value());
×
912
    }
913
    tss->text_filters_changed();
×
914
}
915

916
void
917
filter_sub_source::text_filter_row::value_for(const render_state& rs,
1✔
918
                                              attr_line_t& al)
919
{
920
    attr_line_builder alb(al);
1✔
921
    if (this->tfr_filter->is_enabled()) {
1✔
922
        al.append("\u25c6"_ok);
×
923
    } else {
924
        al.append("\u25c7"_comment);
1✔
925
    }
926
    al.append(" ");
1✔
927
    switch (this->tfr_filter->get_type()) {
1✔
928
        case text_filter::INCLUDE:
×
929
            al.append(" ").append(lnav::roles::ok("IN")).append(" ");
×
930
            break;
×
931
        case text_filter::EXCLUDE:
1✔
932
            if (this->tfr_filter->get_lang() == filter_lang_t::REGEX) {
1✔
933
                al.append(lnav::roles::error("OUT")).append(" ");
1✔
934
            } else {
935
                al.append("    ");
×
936
            }
937
            break;
1✔
938
        default:
×
939
            ensure(0);
×
940
            break;
941
    }
942
    al.append("   ");
1✔
943

944
    {
945
        auto ag = alb.with_attr(VC_ROLE.value(role_t::VCR_NUMBER));
1✔
946
        if (rs.rs_editing) {
1✔
947
            alb.appendf(FMT_STRING("{:>9}"), "-");
3✔
948
        } else {
949
            alb.appendf(
×
950
                FMT_STRING("{:>9L}"),
×
951
                rs.rs_top_view->get_sub_source()->get_filtered_count_for(
×
952
                    this->tfr_filter->get_index()));
×
953
        }
954
    }
1✔
955

956
    al.append(" hits ").append("|", VC_GRAPHIC.value(NCACS_VLINE)).append(" ");
1✔
957

958
    attr_line_t content{this->tfr_filter->get_id()};
2✔
959
    switch (this->tfr_filter->get_lang()) {
1✔
960
        case filter_lang_t::REGEX:
1✔
961
            readline_regex_highlighter(content, std::nullopt);
1✔
962
            break;
1✔
963
        case filter_lang_t::SQL:
×
964
            readline_sql_highlighter(
×
965
                content, lnav::sql::dialect::sqlite, std::nullopt);
966
            break;
×
967
        case filter_lang_t::NONE:
×
968
            break;
×
969
    }
970
    al.append(content);
1✔
971
}
1✔
972

973
bool
974
filter_sub_source::text_filter_row::handle_key(textview_curses* top_view,
×
975
                                               const ncinput& ch)
976
{
977
    switch (ch.eff_text[0]) {
×
978
        case ' ': {
×
979
            auto& fs = top_view->get_sub_source()->get_filters();
×
980
            fs.set_filter_enabled(this->tfr_filter,
×
981
                                  !this->tfr_filter->is_enabled());
×
982
            return true;
×
983
        }
984
        case 't': {
×
985
            if (this->tfr_filter->get_type() == text_filter::INCLUDE) {
×
986
                this->tfr_filter->set_type(text_filter::EXCLUDE);
×
987
            } else {
988
                this->tfr_filter->set_type(text_filter::INCLUDE);
×
989
            }
990
            return true;
×
991
        }
992
        case 'D': {
×
993
            auto& fs = top_view->get_sub_source()->get_filters();
×
994

995
            fs.delete_filter(this->tfr_filter->get_id());
×
996
            return true;
×
997
        }
998
        default:
×
999
            return false;
×
1000
    }
1001
}
1002

1003
bool
1004
filter_sub_source::text_filter_row::prime_text_input(textview_curses* top_view,
1✔
1005
                                                     textinput_curses& ti,
1006
                                                     filter_sub_source& parent)
1007
{
1008
    static auto& prompt = lnav::prompt::get();
1✔
1009

1010
    ti.tc_text_format = this->tfr_filter->get_lang() == filter_lang_t::SQL
1✔
1011
        ? text_format_t::TF_SQL
1✔
1012
        : text_format_t::TF_PCRE;
1013
    if (this->tfr_filter->get_lang() == filter_lang_t::SQL) {
1✔
1014
        prompt.refresh_sql_completions(*top_view);
×
1015
        prompt.refresh_sql_expr_completions(*top_view);
×
1016
    }
1017
    ti.set_content(this->tfr_filter->get_id());
1✔
1018
    if (this->tfr_filter->get_id().empty()) {
1✔
1019
        ti.tc_suggestion = top_view->get_input_suggestion();
1✔
1020
    }
1021
    auto retval = this->tfr_filter->is_enabled();
1✔
1022
    this->tfr_filter->disable();
1✔
1023

1024
    return retval;
1✔
1025
}
1026

1027
void
1028
filter_sub_source::text_filter_row::ti_change(textview_curses* top_view,
3✔
1029
                                              textinput_curses& rc)
1030
{
1031
    auto new_value = rc.get_content();
3✔
1032
    auto* tss = top_view->get_sub_source();
3✔
1033
    auto& fs = tss->get_filters();
3✔
1034

1035
    top_view->get_highlights().erase({highlight_source_t::PREVIEW, "preview"});
3✔
1036
    top_view->set_needs_update();
3✔
1037
    switch (this->tfr_filter->get_lang()) {
3✔
1038
        case filter_lang_t::NONE:
×
1039
            break;
×
1040
        case filter_lang_t::REGEX: {
3✔
1041
            if (new_value.empty()) {
3✔
1042
                auto sugg = top_view->get_current_search();
×
1043
                if (top_view->tc_selected_text) {
×
1044
                    sugg = top_view->tc_selected_text->sti_value;
×
1045
                }
1046
                if (fs.get_filter(sugg) == nullptr) {
×
1047
                    rc.tc_suggestion = sugg;
×
1048
                } else {
1049
                    rc.tc_suggestion.clear();
×
1050
                }
1051
            } else if (new_value == this->tfr_filter->get_id()) {
3✔
1052
            } else {
1053
                auto regex_res
1054
                    = lnav::pcre2pp::code::from(new_value, PCRE2_CASELESS);
3✔
1055

1056
                this->ti_completion_request(
3✔
1057
                    top_view, rc, completion_request_type_t::partial);
1058
                if (regex_res.isErr()) {
3✔
1059
                    auto pe = regex_res.unwrapErr();
×
1060
                    lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
1061
                        "error: %s", pe.get_message().c_str());
×
1062
                } else if (fs.get_filter(new_value) != nullptr) {
3✔
1063
                    lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
1064
                        "error: filter already exists");
1065
                } else {
1066
                    auto& hm = top_view->get_highlights();
3✔
1067
                    highlighter hl(regex_res.unwrap().to_shared());
3✔
1068
                    auto role
1069
                        = this->tfr_filter->get_type() == text_filter::EXCLUDE
3✔
1070
                        ? role_t::VCR_DIFF_DELETE
3✔
1071
                        : role_t::VCR_DIFF_ADD;
3✔
1072
                    hl.with_role(role);
3✔
1073
                    hl.with_attrs(text_attrs::with_styles(
3✔
1074
                        text_attrs::style::blink, text_attrs::style::reverse));
1075
                    hm[{highlight_source_t::PREVIEW, "preview"}] = hl;
3✔
1076
                    top_view->set_needs_update();
3✔
1077
                    lnav_data.ld_filter_help_status_source.fss_error.clear();
3✔
1078
                }
3✔
1079
            }
3✔
1080
            break;
3✔
1081
        }
1082
        case filter_lang_t::SQL: {
×
1083
            this->ti_completion_request(
×
1084
                top_view, rc, completion_request_type_t::partial);
1085

1086
            auto full_sql
1087
                = fmt::format(FMT_STRING("SELECT 1 WHERE {}"), new_value);
×
1088
            auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
×
1089
#ifdef SQLITE_PREPARE_PERSISTENT
1090
            auto retcode = sqlite3_prepare_v3(lnav_data.ld_db.in(),
×
1091
                                              full_sql.c_str(),
1092
                                              full_sql.size(),
×
1093
                                              SQLITE_PREPARE_PERSISTENT,
1094
                                              stmt.out(),
1095
                                              nullptr);
1096
#else
1097
            auto retcode = sqlite3_prepare_v2(lnav_data.ld_db.in(),
1098
                                              full_sql.c_str(),
1099
                                              full_sql.size(),
1100
                                              stmt.out(),
1101
                                              nullptr);
1102
#endif
1103
            if (retcode != SQLITE_OK) {
×
1104
                lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
1105
                    "error: %s", sqlite3_errmsg(lnav_data.ld_db));
1106
            } else {
1107
                auto set_res = lnav_data.ld_log_source.set_preview_sql_filter(
1108
                    stmt.release());
×
1109

1110
                if (set_res.isErr()) {
×
1111
                    lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
1112
                        "error: %s",
1113
                        set_res.unwrapErr()
×
1114
                            .to_attr_line()
×
1115
                            .get_string()
×
1116
                            .c_str());
1117
                } else {
1118
                    top_view->set_needs_update();
×
1119
                    lnav_data.ld_filter_help_status_source.fss_error.clear();
×
1120
                }
1121
            }
1122
            break;
×
1123
        }
1124
    }
1125
}
3✔
1126

1127
void
1128
filter_sub_source::text_filter_row::ti_completion_request(
3✔
1129
    textview_curses* top_view,
1130
    textinput_curses& tc,
1131
    completion_request_type_t crt)
1132
{
1133
    static const auto FILTER_HELP
1134
        = help_text("filter", "filter the view by a pattern")
1✔
1135
              .with_parameter(
1✔
1136
                  help_text("pattern", "The pattern to filter by")
2✔
1137
                      .with_format(help_parameter_format_t::HPF_REGEX));
5✔
1138
    static const auto FILTER_EXPR_HELP
1139
        = help_text("filter-expr", "filter the view by a SQL expression")
1✔
1140
              .with_parameter(
1✔
1141
                  help_text("expr", "The expression to evaluate")
2✔
1142
                      .with_format(help_parameter_format_t::HPF_SQL_EXPR));
5✔
1143
    static auto& prompt = lnav::prompt::get();
3✔
1144

1145
    auto& al = tc.tc_lines[tc.tc_cursor.y];
3✔
1146
    auto al_sf = al.to_string_fragment().sub_cell_range(0, tc.tc_cursor.x);
3✔
1147
    auto is_regex = tc.tc_text_format == text_format_t::TF_PCRE;
3✔
1148
    auto parse_res = lnav::command::parse_for_prompt(
1149
        lnav_data.ld_exec_context,
1150
        al_sf,
1151
        is_regex ? FILTER_HELP : FILTER_EXPR_HELP);
3✔
1152

1153
    switch (crt) {
3✔
1154
        case completion_request_type_t::partial: {
3✔
1155
            if (al_sf.endswith(" ")) {
3✔
1156
                if (tc.is_cursor_at_end_of_line()) {
×
1157
                    tc.tc_suggestion
1158
                        = prompt.get_regex_suggestion(*top_view, al.al_string);
×
1159
                }
1160
                return;
×
1161
            }
1162
            break;
3✔
1163
        }
1164
        case completion_request_type_t::full:
×
1165
            break;
×
1166
    }
1167

1168
    auto byte_x = al_sf.column_to_byte_index(tc.tc_cursor.x);
3✔
1169
    auto arg_res_opt = parse_res.arg_at(byte_x);
3✔
1170
    if (arg_res_opt) {
3✔
1171
        auto arg_pair = arg_res_opt.value();
3✔
1172
        if (crt == completion_request_type_t::full
3✔
1173
            || tc.tc_popup_type != textinput_curses::popup_type_t::none)
3✔
1174
        {
1175
            auto poss = prompt.get_cmd_parameter_completion(
1176
                *top_view,
1177
                &FILTER_HELP,
1178
                arg_pair.aar_help,
1179
                arg_pair.aar_element.se_value);
×
1180
            auto left = arg_pair.aar_element.se_value.empty()
×
1181
                ? tc.tc_cursor.x
×
1182
                : al_sf.byte_to_column_index(
×
1183
                      arg_pair.aar_element.se_origin.sf_begin);
×
1184
            tc.open_popup_for_completion(left, poss);
×
1185
            tc.tc_popup.set_title(arg_pair.aar_help->ht_name);
×
1186
        } else if (arg_pair.aar_element.se_value.empty()
3✔
1187
                   && tc.is_cursor_at_end_of_line())
3✔
1188
        {
1189
            tc.tc_suggestion
1190
                = prompt.get_regex_suggestion(*top_view, al.al_string);
×
1191
        } else {
1192
            log_debug("not at end of line");
3✔
1193
            tc.tc_suggestion.clear();
3✔
1194
        }
1195
    } else {
3✔
1196
        log_debug("no arg");
×
1197
    }
1198
}
3✔
1199

1200
void
1201
filter_sub_source::text_filter_row::ti_perform(textview_curses* top_view,
1✔
1202
                                               textinput_curses& ti,
1203
                                               filter_sub_source& parent)
1204
{
1205
    static const intern_string_t INPUT_SRC = intern_string::lookup("input");
3✔
1206

1207
    auto* tss = top_view->get_sub_source();
1✔
1208
    auto& fs = tss->get_filters();
1✔
1209
    auto new_value = ti.get_content();
1✔
1210

1211
    fs.fs_generation += 1;
1✔
1212
    if (new_value.empty() || new_value == this->tfr_filter->get_id()) {
1✔
1213
        this->ti_abort(top_view, ti, parent);
×
1214
    } else {
1215
        switch (this->tfr_filter->get_lang()) {
1✔
1216
            case filter_lang_t::NONE:
1✔
1217
            case filter_lang_t::REGEX: {
1218
                auto compile_res
1219
                    = lnav::pcre2pp::code::from(new_value, PCRE2_CASELESS);
1✔
1220

1221
                if (compile_res.isErr()) {
1✔
1222
                    auto ce = compile_res.unwrapErr();
×
1223
                    auto um = lnav::console::to_user_message(INPUT_SRC, ce);
×
1224
                    lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
1225
                    this->ti_abort(top_view, ti, parent);
×
1226
                } else if (fs.get_filter(new_value) != nullptr) {
1✔
1227
                    auto snip
1228
                        = lnav::console::snippet::from(INPUT_SRC, new_value);
×
1229
                    auto um = lnav::console::user_message::error(
×
1230
                                  "Filter already exists")
1231
                                  .with_snippet(snip);
×
1232
                    lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
1233
                    this->ti_abort(top_view, ti, parent);
×
1234
                } else {
×
1235
                    auto code_ptr = compile_res.unwrap().to_shared();
1✔
1236
                    this->tfr_filter->lf_deleted = true;
1✔
1237
                    tss->text_filters_changed();
1✔
1238

1239
                    auto pf = std::make_shared<pcre_filter>(
1240
                        this->tfr_filter->get_type(),
1✔
1241
                        new_value,
1242
                        this->tfr_filter->get_index(),
1✔
1243
                        code_ptr);
1✔
1244

1245
                    parent.fss_regexp_history.insert_plain_content(new_value);
1✔
1246

1247
                    auto iter
1248
                        = std::find(fs.begin(), fs.end(), this->tfr_filter);
1✔
1249

1250
                    *iter = pf;
1✔
1251
                    tss->text_filters_changed();
1✔
1252
                }
1✔
1253
                break;
1✔
1254
            }
1✔
1255
            case filter_lang_t::SQL: {
×
1256
                auto full_sql
1257
                    = fmt::format(FMT_STRING("SELECT 1 WHERE {}"), new_value);
×
1258
                auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
×
1259
#ifdef SQLITE_PREPARE_PERSISTENT
1260
                auto retcode = sqlite3_prepare_v3(lnav_data.ld_db.in(),
×
1261
                                                  full_sql.c_str(),
1262
                                                  full_sql.size(),
×
1263
                                                  SQLITE_PREPARE_PERSISTENT,
1264
                                                  stmt.out(),
1265
                                                  nullptr);
1266
#else
1267
                auto retcode = sqlite3_prepare_v2(lnav_data.ld_db.in(),
1268
                                                  full_sql.c_str(),
1269
                                                  full_sql.size(),
1270
                                                  stmt.out(),
1271
                                                  nullptr);
1272
#endif
1273
                if (retcode != SQLITE_OK) {
×
1274
                    auto sqlerr = annotate_sql_with_error(
1275
                        lnav_data.ld_db.in(), full_sql.c_str(), nullptr);
×
1276
                    auto um
1277
                        = lnav::console::user_message::error(
×
1278
                              "invalid SQL expression")
1279
                              .with_reason(sqlite3_errmsg(lnav_data.ld_db.in()))
×
1280
                              .with_snippet(lnav::console::snippet::from(
×
1281
                                  INPUT_SRC, sqlerr));
×
1282
                    lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
1283
                    this->ti_abort(top_view, ti, parent);
×
1284
                } else {
×
1285
                    lnav_data.ld_log_source.set_sql_filter(new_value,
×
1286
                                                           stmt.release());
1287
                    tss->text_filters_changed();
×
1288
                }
1289
                break;
×
1290
            }
1291
        }
1292
    }
1293

1294
    top_view->reload_data();
1✔
1295
}
1✔
1296

1297
void
1298
filter_sub_source::text_filter_row::ti_abort(textview_curses* top_view,
×
1299
                                             textinput_curses& tc,
1300
                                             filter_sub_source& parent)
1301
{
1302
    auto* tss = top_view->get_sub_source();
×
1303
    auto& fs = tss->get_filters();
×
1304

1305
    top_view->reload_data();
×
1306
    fs.delete_filter("");
×
1307
    this->tfr_filter->set_enabled(parent.fss_filter_state);
×
1308
    tss->text_filters_changed();
×
1309
}
1310

1311
std::vector<std::unique_ptr<filter_sub_source::filter_row>>
1312
filter_sub_source::rows_for(textview_curses* tc) const
69✔
1313
{
1314
    auto retval = row_vector{};
69✔
1315
    auto* tss = tc->get_sub_source();
69✔
1316
    if (tss == nullptr) {
69✔
1317
        return retval;
×
1318
    }
1319

1320
    const auto* ttt = dynamic_cast<text_time_translator*>(tss);
69✔
1321

1322
    if (ttt != nullptr) {
69✔
1323
        if (this->fss_min_time) {
69✔
1324
            retval.emplace_back(std::make_unique<min_time_filter_row>(
×
1325
                this->fss_min_time.value()));
1326
        } else {
1327
            auto min_time = ttt->get_min_row_time();
69✔
1328
            if (min_time) {
69✔
1329
                retval.emplace_back(
×
1330
                    std::make_unique<min_time_filter_row>(min_time.value()));
×
1331
            }
1332
        }
1333
        if (this->fss_max_time) {
69✔
1334
            retval.emplace_back(std::make_unique<max_time_filter_row>(
×
1335
                this->fss_max_time.value()));
1336
        } else {
1337
            auto max_time = ttt->get_max_row_time();
69✔
1338
            if (max_time) {
69✔
1339
                retval.emplace_back(
×
1340
                    std::make_unique<max_time_filter_row>(max_time.value()));
×
1341
            }
1342
        }
1343
    }
1344

1345
    if (tss->get_min_log_level() != LEVEL_UNKNOWN) {
69✔
1346
        retval.emplace_back(std::make_unique<level_filter_row>());
×
1347
    }
1348

1349
    auto& fs = tss->get_filters();
69✔
1350
    for (auto& tf : fs) {
126✔
1351
        retval.emplace_back(std::make_unique<text_filter_row>(tf));
57✔
1352
    }
1353

1354
    return retval;
69✔
1355
}
×
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