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

tstack / lnav / 18882433814-2607

28 Oct 2025 04:45PM UTC coverage: 68.89% (-1.1%) from 70.002%
18882433814-2607

push

github

tstack
[hasher] fixup hasher::to_string() for sse2

5 of 5 new or added lines in 1 file covered. (100.0%)

2926 existing lines in 57 files now uncovered.

50208 of 72881 relevant lines covered (68.89%)

424460.54 hits per line

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

45.2
/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 <utility>
32

33
#include "filter_sub_source.hh"
34

35
#include "base/attr_line.builder.hh"
36
#include "base/auto_mem.hh"
37
#include "base/func_util.hh"
38
#include "base/itertools.hh"
39
#include "base/opt_util.hh"
40
#include "cmd.parser.hh"
41
#include "config.h"
42
#include "data_scanner.hh"
43
#include "lnav.hh"
44
#include "lnav.prompt.hh"
45
#include "readline_highlighters.hh"
46
#include "readline_possibilities.hh"
47
#include "sql_util.hh"
48
#include "textinput_curses.hh"
49

50
using namespace lnav::roles::literals;
51

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

79
void
80
filter_sub_source::register_view(textview_curses* tc)
2✔
81
{
82
    text_sub_source::register_view(tc);
2✔
83
    tc->add_child_view(this->fss_editor.get());
2✔
84
}
2✔
85

86
bool
87
filter_sub_source::list_input_handle_key(listview_curses& lv, const ncinput& ch)
6✔
88
{
89
    static auto& prompt = lnav::prompt::get();
6✔
90

91
    if (this->fss_editing) {
6✔
92
        return this->fss_editor->handle_key(ch);
4✔
93
    }
94

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

100
            tss->toggle_apply_filters();
×
101
            top_view->reload_data();
×
102
            break;
×
103
        }
104
        case ' ': {
×
105
            auto* top_view = *lnav_data.ld_view_stack.top();
×
106
            auto* tss = top_view->get_sub_source();
×
107
            auto& fs = tss->get_filters();
×
108

109
            if (fs.empty() || !lv.get_selection()) {
×
110
                return true;
×
111
            }
112

113
            auto tf = *(fs.begin() + lv.get_selection().value());
×
114

115
            fs.set_filter_enabled(tf, !tf->is_enabled());
×
116
            tss->text_filters_changed();
×
117
            lv.reload_data();
×
118
            top_view->reload_data();
×
119
            return true;
×
120
        }
121
        case 't': {
×
122
            auto* top_view = *lnav_data.ld_view_stack.top();
×
123
            auto* tss = top_view->get_sub_source();
×
124
            auto& fs = tss->get_filters();
×
125

126
            if (fs.empty() || !lv.get_selection()) {
×
127
                return true;
×
128
            }
129

130
            auto tf = *(fs.begin() + lv.get_selection().value());
×
131

132
            if (tf->get_type() == text_filter::INCLUDE) {
×
133
                tf->set_type(text_filter::EXCLUDE);
×
134
            } else {
135
                tf->set_type(text_filter::INCLUDE);
×
136
            }
137

138
            tss->text_filters_changed();
×
139
            lv.reload_data();
×
140
            top_view->reload_data();
×
141
            return true;
×
142
        }
143
        case 'D': {
×
144
            auto* top_view = *lnav_data.ld_view_stack.top();
×
145
            auto* tss = top_view->get_sub_source();
×
146
            auto& fs = tss->get_filters();
×
147

148
            if (fs.empty() || !lv.get_selection()) {
×
149
                return true;
×
150
            }
151

152
            auto tf = *(fs.begin() + lv.get_selection().value());
×
153

154
            fs.delete_filter(tf->get_id());
×
155
            lv.reload_data();
×
156
            tss->text_filters_changed();
×
157
            top_view->reload_data();
×
158
            return true;
×
159
        }
160
        case 'i': {
×
161
            auto* top_view = *lnav_data.ld_view_stack.top();
×
162
            auto* tss = top_view->get_sub_source();
×
163
            auto& fs = tss->get_filters();
×
164
            auto filter_index = fs.next_index();
×
165

166
            if (!filter_index) {
×
167
                lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
168
                    "error: too many filters");
169
                return true;
×
170
            }
171

172
            auto ef = std::make_shared<empty_filter>(
173
                text_filter::type_t::INCLUDE, *filter_index);
×
174
            fs.add_filter(ef);
×
175
            lv.set_selection(vis_line_t(fs.size() - 1));
×
176
            lv.reload_data();
×
177

178
            this->fss_editing = true;
×
179
            this->tss_view->set_enabled(false);
×
180
            this->fss_view_text_possibilities
181
                = view_text_possibilities(*top_view);
×
182
            this->fss_editor->tc_text_format = text_format_t::TF_PCRE;
×
183
            this->fss_editor->set_y(lv.get_y_for_selection());
×
184
            this->fss_editor->set_content("");
×
185
            this->fss_editor->tc_suggestion = top_view->get_input_suggestion();
×
186
            this->fss_editor->set_visible(true);
×
187
            this->fss_editor->focus();
×
188
            this->fss_filter_state = true;
×
189
            ef->disable();
×
190
            return true;
×
191
        }
192
        case 'o': {
1✔
193
            auto* top_view = *lnav_data.ld_view_stack.top();
1✔
194
            auto* tss = top_view->get_sub_source();
1✔
195
            auto& fs = tss->get_filters();
1✔
196
            auto filter_index = fs.next_index();
1✔
197

198
            if (!filter_index) {
1✔
199
                lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
200
                    "error: too many filters");
201
                return true;
×
202
            }
203

204
            auto ef = std::make_shared<empty_filter>(
205
                text_filter::type_t::EXCLUDE, *filter_index);
1✔
206
            fs.add_filter(ef);
1✔
207
            lv.set_selection(vis_line_t(fs.size() - 1));
1✔
208
            lv.reload_data();
1✔
209

210
            this->fss_editing = true;
1✔
211
            this->tss_view->set_enabled(false);
1✔
212

213
            this->fss_editor->tc_text_format = text_format_t::TF_PCRE;
1✔
214
            this->fss_editor->set_y(lv.get_y_for_selection());
1✔
215
            this->fss_editor->set_visible(true);
1✔
216
            this->fss_editor->set_content("");
2✔
217
            this->fss_view_text_possibilities
218
                = view_text_possibilities(*top_view);
1✔
219
            this->fss_editor->tc_suggestion = top_view->get_input_suggestion();
1✔
220
            this->fss_editor->focus();
1✔
221
            this->fss_filter_state = true;
1✔
222
            ef->disable();
1✔
223
            return true;
1✔
224
        }
1✔
225
        case '\r':
×
226
        case NCKEY_ENTER: {
227
            auto* top_view = *lnav_data.ld_view_stack.top();
×
228
            auto* tss = top_view->get_sub_source();
×
229
            auto& fs = tss->get_filters();
×
230

231
            if (fs.empty() || !lv.get_selection()) {
×
232
                return true;
×
233
            }
234

235
            auto tf = *(fs.begin() + lv.get_selection().value());
×
236

237
            this->fss_editing = true;
×
238
            this->tss_view->set_enabled(false);
×
239

240
            this->fss_editor->tc_text_format
×
241
                = tf->get_lang() == filter_lang_t::SQL ? text_format_t::TF_SQL
×
242
                                                       : text_format_t::TF_PCRE;
243
            if (tf->get_lang() == filter_lang_t::SQL) {
×
244
                prompt.refresh_sql_completions(*top_view);
×
245
                prompt.refresh_sql_expr_completions(*top_view);
×
246
            }
247
            this->fss_editor->set_y(lv.get_y_for_selection());
×
248
            this->fss_editor->set_visible(true);
×
249
            this->fss_editor->tc_suggestion.clear();
×
250
            this->fss_editor->set_content(tf->get_id());
×
251
            this->fss_view_text_possibilities
252
                = view_text_possibilities(*top_view);
×
253
            this->fss_editor->focus();
×
254
            this->fss_filter_state = tf->is_enabled();
×
255
            tf->disable();
×
256
            tss->text_filters_changed();
×
257
            return true;
×
258
        }
259
        case 'n': {
×
260
            lnav_data.ld_exec_context.execute(INTERNAL_SRC_LOC,
×
261
                                              ":next-mark search");
262
            return true;
×
263
        }
264
        case 'N': {
×
265
            lnav_data.ld_exec_context.execute(INTERNAL_SRC_LOC,
×
266
                                              ":prev-mark search");
267
            return true;
×
268
        }
269
        case '/': {
×
270
            lnav_data.ld_exec_context.execute(INTERNAL_SRC_LOC,
×
271
                                              ":prompt search-filters");
272
            return true;
×
273
        }
274
        default:
1✔
275
            log_debug("unhandled %x", ch);
1✔
276
            break;
1✔
277
    }
278

279
    return false;
1✔
280
}
281

282
size_t
283
filter_sub_source::text_line_count()
79✔
284
{
285
    return (lnav_data.ld_view_stack.top() |
79✔
286
                [](auto tc) -> std::optional<size_t> {
59✔
287
               text_sub_source* tss = tc->get_sub_source();
59✔
288

289
               if (tss == nullptr) {
59✔
290
                   return std::nullopt;
×
291
               }
292
               auto& fs = tss->get_filters();
59✔
293
               return std::make_optional(fs.size());
59✔
294
           })
79✔
295
        .value_or(0);
79✔
296
}
297

298
size_t
299
filter_sub_source::text_line_width(textview_curses& curses)
×
300
{
301
    textview_curses* top_view = *lnav_data.ld_view_stack.top();
×
302
    text_sub_source* tss = top_view->get_sub_source();
×
303
    filter_stack& fs = tss->get_filters();
×
304
    size_t retval = 0;
×
305

306
    for (auto& filter : fs) {
×
307
        retval = std::max(filter->get_id().size() + 8, retval);
×
308
    }
309

310
    return retval;
×
311
}
312

313
line_info
314
filter_sub_source::text_value_for_line(textview_curses& tc,
1✔
315
                                       int line,
316
                                       std::string& value_out,
317
                                       line_flags_t flags)
318
{
319
    auto* top_view = *lnav_data.ld_view_stack.top();
1✔
320
    auto* tss = top_view->get_sub_source();
1✔
321
    auto& fs = tss->get_filters();
1✔
322
    auto tf = *(fs.begin() + line);
1✔
323
    bool selected
324
        = lnav_data.ld_mode == ln_mode_t::FILTER && line == tc.get_selection();
1✔
325

326
    this->fss_curr_line.clear();
1✔
327
    auto& al = this->fss_curr_line;
1✔
328
    attr_line_builder alb(al);
1✔
329

330
    if (selected) {
1✔
331
        al.append(" ", VC_GRAPHIC.value(NCACS_RARROW));
1✔
332
    } else {
333
        al.append(" ");
×
334
    }
335
    al.append(" ");
1✔
336
    if (tf->is_enabled()) {
1✔
UNCOV
337
        al.append("\u25c6"_ok);
×
338
    } else {
339
        al.append("\u25c7"_comment);
1✔
340
    }
341
    al.append(" ");
1✔
342
    switch (tf->get_type()) {
1✔
343
        case text_filter::INCLUDE:
×
344
            al.append(" ").append(lnav::roles::ok("IN")).append(" ");
×
345
            break;
×
346
        case text_filter::EXCLUDE:
1✔
347
            if (tf->get_lang() == filter_lang_t::REGEX) {
1✔
348
                al.append(lnav::roles::error("OUT")).append(" ");
1✔
349
            } else {
350
                al.append("    ");
×
351
            }
352
            break;
1✔
353
        default:
×
354
            ensure(0);
×
355
            break;
356
    }
357

358
    {
359
        auto ag = alb.with_attr(VC_ROLE.value(role_t::VCR_NUMBER));
1✔
360
        if (this->fss_editing && line == tc.get_selection()) {
1✔
361
            alb.appendf(FMT_STRING("{:>9}"), "-");
3✔
362
        } else {
UNCOV
363
            alb.appendf(FMT_STRING("{:>9L}"),
×
UNCOV
364
                        tss->get_filtered_count_for(tf->get_index()));
×
365
        }
366
    }
1✔
367

368
    al.append(" hits ").append("|", VC_GRAPHIC.value(NCACS_VLINE)).append(" ");
1✔
369

370
    attr_line_t content{tf->get_id()};
2✔
371
    switch (tf->get_lang()) {
1✔
372
        case filter_lang_t::REGEX:
1✔
373
            readline_regex_highlighter(content, std::nullopt);
1✔
374
            break;
1✔
375
        case filter_lang_t::SQL:
×
376
            readline_sql_highlighter(
×
377
                content, lnav::sql::dialect::sqlite, std::nullopt);
378
            break;
×
379
        case filter_lang_t::NONE:
×
380
            break;
×
381
    }
382
    al.append(content);
1✔
383

384
    if (selected) {
1✔
385
        al.with_attr_for_all(VC_ROLE.value(role_t::VCR_FOCUSED));
1✔
386
    }
387

388
    value_out = al.get_string();
1✔
389

390
    return {};
2✔
391
}
1✔
392

393
void
394
filter_sub_source::text_attrs_for_line(textview_curses& tc,
1✔
395
                                       int line,
396
                                       string_attrs_t& value_out)
397
{
398
    value_out = this->fss_curr_line.get_attrs();
1✔
399
}
1✔
400

401
size_t
402
filter_sub_source::text_size_for_line(textview_curses& tc,
×
403
                                      int line,
404
                                      text_sub_source::line_flags_t raw)
405
{
406
    auto* top_view = *lnav_data.ld_view_stack.top();
×
407
    auto* tss = top_view->get_sub_source();
×
408
    auto& fs = tss->get_filters();
×
409
    auto tf = *(fs.begin() + line);
×
410

411
    return 8 + tf->get_id().size();
×
412
}
413

414
void
415
filter_sub_source::rl_change(textinput_curses& rc)
3✔
416
{
417
    auto* top_view = *lnav_data.ld_view_stack.top();
3✔
418
    auto* tss = top_view->get_sub_source();
3✔
419
    auto& fs = tss->get_filters();
3✔
420
    if (fs.empty()) {
3✔
421
        return;
×
422
    }
423

424
    auto iter = fs.begin() + this->tss_view->get_selection().value();
3✔
425
    auto tf = *iter;
3✔
426
    auto new_value = rc.get_content();
3✔
427

428
    top_view->get_highlights().erase({highlight_source_t::PREVIEW, "preview"});
3✔
429
    top_view->set_needs_update();
3✔
430
    switch (tf->get_lang()) {
3✔
431
        case filter_lang_t::NONE:
×
432
            break;
×
433
        case filter_lang_t::REGEX: {
3✔
434
            if (new_value.empty()) {
3✔
435
                auto sugg = top_view->get_current_search();
×
436
                if (top_view->tc_selected_text) {
×
437
                    sugg = top_view->tc_selected_text->sti_value;
×
438
                }
439
                if (fs.get_filter(sugg) == nullptr) {
×
440
                    this->fss_editor->tc_suggestion = sugg;
×
441
                } else {
442
                    this->fss_editor->tc_suggestion.clear();
×
443
                }
444
            } else {
×
445
                auto regex_res
446
                    = lnav::pcre2pp::code::from(new_value, PCRE2_CASELESS);
3✔
447

448
                this->rl_completion_request_int(
3✔
449
                    rc, completion_request_type_t::partial);
450
                if (regex_res.isErr()) {
3✔
451
                    auto pe = regex_res.unwrapErr();
×
452
                    lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
453
                        "error: %s", pe.get_message().c_str());
×
454
                } else {
×
455
                    auto& hm = top_view->get_highlights();
3✔
456
                    highlighter hl(regex_res.unwrap().to_shared());
3✔
457
                    auto role = tf->get_type() == text_filter::EXCLUDE
3✔
458
                        ? role_t::VCR_DIFF_DELETE
3✔
459
                        : role_t::VCR_DIFF_ADD;
3✔
460
                    hl.with_role(role);
3✔
461
                    hl.with_attrs(text_attrs::with_styles(
3✔
462
                        text_attrs::style::blink, text_attrs::style::reverse));
463
                    hm[{highlight_source_t::PREVIEW, "preview"}] = hl;
3✔
464
                    top_view->set_needs_update();
3✔
465
                    lnav_data.ld_filter_help_status_source.fss_error.clear();
3✔
466
                }
3✔
467
            }
3✔
468
            break;
3✔
469
        }
470
        case filter_lang_t::SQL: {
×
471
            this->rl_completion_request_int(rc,
×
472
                                            completion_request_type_t::partial);
473

474
            auto full_sql
475
                = fmt::format(FMT_STRING("SELECT 1 WHERE {}"), new_value);
×
476
            auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
×
477
#ifdef SQLITE_PREPARE_PERSISTENT
478
            auto retcode = sqlite3_prepare_v3(lnav_data.ld_db.in(),
×
479
                                              full_sql.c_str(),
480
                                              full_sql.size(),
×
481
                                              SQLITE_PREPARE_PERSISTENT,
482
                                              stmt.out(),
483
                                              nullptr);
484
#else
485
            auto retcode = sqlite3_prepare_v2(lnav_data.ld_db.in(),
486
                                              full_sql.c_str(),
487
                                              full_sql.size(),
488
                                              stmt.out(),
489
                                              nullptr);
490
#endif
491
            if (retcode != SQLITE_OK) {
×
492
                lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
493
                    "error: %s", sqlite3_errmsg(lnav_data.ld_db));
494
            } else {
495
                auto set_res = lnav_data.ld_log_source.set_preview_sql_filter(
496
                    stmt.release());
×
497

498
                if (set_res.isErr()) {
×
499
                    lnav_data.ld_filter_help_status_source.fss_error.set_value(
×
500
                        "error: %s",
501
                        set_res.unwrapErr()
×
502
                            .to_attr_line()
×
503
                            .get_string()
×
504
                            .c_str());
505
                } else {
506
                    top_view->set_needs_update();
×
507
                    lnav_data.ld_filter_help_status_source.fss_error.clear();
×
508
                }
509
            }
510
            break;
×
511
        }
512
    }
513
}
3✔
514

515
void
516
filter_sub_source::rl_history(textinput_curses& tc)
×
517
{
518
    switch (tc.tc_text_format) {
×
519
        case text_format_t::TF_PCRE: {
×
520
            std::vector<attr_line_t> poss;
×
521
            this->fss_regexp_history.query_entries(
×
522
                tc.get_content(),
×
523
                [&poss](const auto& e) { poss.emplace_back(e.e_content); });
×
524
            tc.open_popup_for_history(poss);
×
525
            break;
×
526
        }
527
        case text_format_t::TF_SQL: {
×
528
            break;
×
529
        }
530
        default:
×
531
            ensure(false);
×
532
            break;
533
    }
534
}
535

536
void
537
filter_sub_source::rl_completion_request_int(textinput_curses& tc,
3✔
538
                                             completion_request_type_t crt)
539
{
540
    static const auto FILTER_HELP
541
        = help_text("filter", "filter the view by a pattern")
1✔
542
              .with_parameter(
1✔
543
                  help_text("pattern", "The pattern to filter by")
2✔
544
                      .with_format(help_parameter_format_t::HPF_REGEX));
5✔
545
    static const auto FILTER_EXPR_HELP
546
        = help_text("filter-expr", "filter the view by a SQL expression")
1✔
547
              .with_parameter(
1✔
548
                  help_text("expr", "The expression to evaluate")
2✔
549
                      .with_format(help_parameter_format_t::HPF_SQL_EXPR));
5✔
550
    static auto& prompt = lnav::prompt::get();
3✔
551

552
    auto* top_view = *lnav_data.ld_view_stack.top();
3✔
553
    auto& al = tc.tc_lines[tc.tc_cursor.y];
3✔
554
    auto al_sf = al.to_string_fragment().sub_cell_range(0, tc.tc_cursor.x);
3✔
555
    std::string prefix;
3✔
556
    auto is_regex = tc.tc_text_format == text_format_t::TF_PCRE;
3✔
557
    auto parse_res = lnav::command::parse_for_prompt(
558
        lnav_data.ld_exec_context,
559
        al_sf,
560
        is_regex ? FILTER_HELP : FILTER_EXPR_HELP);
3✔
561

562
    switch (crt) {
3✔
563
        case completion_request_type_t::partial: {
3✔
564
            if (al_sf.endswith(" ")) {
3✔
565
                if (tc.is_cursor_at_end_of_line()) {
×
566
                    tc.tc_suggestion
567
                        = prompt.get_regex_suggestion(*top_view, al.al_string);
×
568
                }
569
                return;
×
570
            }
571
            break;
3✔
572
        }
573
        case completion_request_type_t::full:
×
574
            break;
×
575
    }
576

577
    auto byte_x = al_sf.column_to_byte_index(tc.tc_cursor.x);
3✔
578
    auto arg_res_opt = parse_res.arg_at(byte_x);
3✔
579
    if (arg_res_opt) {
3✔
580
        auto arg_pair = arg_res_opt.value();
3✔
581
        if (crt == completion_request_type_t::full
3✔
582
            || tc.tc_popup_type != textinput_curses::popup_type_t::none)
3✔
583
        {
584
            auto poss = prompt.get_cmd_parameter_completion(
585
                *top_view,
586
                &FILTER_HELP,
587
                arg_pair.aar_help,
588
                arg_pair.aar_element.se_value);
×
589
            auto left = arg_pair.aar_element.se_value.empty()
×
590
                ? tc.tc_cursor.x
×
591
                : al_sf.byte_to_column_index(
×
592
                      arg_pair.aar_element.se_origin.sf_begin);
×
593
            tc.open_popup_for_completion(left, poss);
×
594
            tc.tc_popup.set_title(arg_pair.aar_help->ht_name);
×
595
        } else if (arg_pair.aar_element.se_value.empty()
3✔
596
                   && tc.is_cursor_at_end_of_line())
3✔
597
        {
598
            tc.tc_suggestion
599
                = prompt.get_regex_suggestion(*top_view, al.al_string);
×
600
        } else {
601
            log_debug("not at end of line");
3✔
602
            tc.tc_suggestion.clear();
3✔
603
        }
604
    } else {
3✔
605
        log_debug("no arg");
×
606
    }
607
}
3✔
608

609
void
610
filter_sub_source::rl_completion_request(textinput_curses& tc)
×
611
{
612
    this->rl_completion_request_int(tc, completion_request_type_t::full);
×
613
}
614

615
void
616
filter_sub_source::rl_completion(textinput_curses& tc)
×
617
{
618
    static auto& prompt = lnav::prompt::get();
619

620
    prompt.rl_completion(tc);
×
621
}
622

623
void
624
filter_sub_source::rl_perform(textinput_curses& rc)
1✔
625
{
626
    static const intern_string_t INPUT_SRC = intern_string::lookup("input");
3✔
627

628
    auto* top_view = *lnav_data.ld_view_stack.top();
1✔
629
    auto* tss = top_view->get_sub_source();
1✔
630
    auto& fs = tss->get_filters();
1✔
631
    auto iter = fs.begin() + this->tss_view->get_selection().value();
1✔
632
    auto tf = *iter;
1✔
633
    auto new_value = rc.get_content();
1✔
634

635
    fs.fs_generation += 1;
1✔
636
    if (new_value.empty()) {
1✔
637
        this->rl_abort(rc);
×
638
    } else {
639
        switch (tf->get_lang()) {
1✔
640
            case filter_lang_t::NONE:
1✔
641
            case filter_lang_t::REGEX: {
642
                auto compile_res
643
                    = lnav::pcre2pp::code::from(new_value, PCRE2_CASELESS);
1✔
644

645
                if (compile_res.isErr()) {
1✔
646
                    auto ce = compile_res.unwrapErr();
×
647
                    auto um = lnav::console::to_user_message(INPUT_SRC, ce);
×
648
                    lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
649
                    this->rl_abort(rc);
×
650
                } else {
×
651
                    auto code_ptr = compile_res.unwrap().to_shared();
1✔
652
                    tf->lf_deleted = true;
1✔
653
                    tss->text_filters_changed();
1✔
654

655
                    auto pf = std::make_shared<pcre_filter>(
656
                        tf->get_type(), new_value, tf->get_index(), code_ptr);
1✔
657

658
                    this->fss_regexp_history.insert_plain_content(new_value);
1✔
659

660
                    *iter = pf;
1✔
661
                    tss->text_filters_changed();
1✔
662
                }
1✔
663
                break;
1✔
664
            }
1✔
665
            case filter_lang_t::SQL: {
×
666
                auto full_sql
667
                    = fmt::format(FMT_STRING("SELECT 1 WHERE {}"), new_value);
×
668
                auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
×
669
#ifdef SQLITE_PREPARE_PERSISTENT
670
                auto retcode = sqlite3_prepare_v3(lnav_data.ld_db.in(),
×
671
                                                  full_sql.c_str(),
672
                                                  full_sql.size(),
×
673
                                                  SQLITE_PREPARE_PERSISTENT,
674
                                                  stmt.out(),
675
                                                  nullptr);
676
#else
677
                auto retcode = sqlite3_prepare_v2(lnav_data.ld_db.in(),
678
                                                  full_sql.c_str(),
679
                                                  full_sql.size(),
680
                                                  stmt.out(),
681
                                                  nullptr);
682
#endif
683
                if (retcode != SQLITE_OK) {
×
684
                    auto sqlerr = annotate_sql_with_error(
685
                        lnav_data.ld_db.in(), full_sql.c_str(), nullptr);
×
686
                    auto um
687
                        = lnav::console::user_message::error(
×
688
                              "invalid SQL expression")
689
                              .with_reason(sqlite3_errmsg(lnav_data.ld_db.in()))
×
690
                              .with_snippet(lnav::console::snippet::from(
×
691
                                  INPUT_SRC, sqlerr));
×
692
                    lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
693
                    this->rl_abort(rc);
×
694
                } else {
×
695
                    lnav_data.ld_log_source.set_sql_filter(new_value,
×
696
                                                           stmt.release());
697
                    tss->text_filters_changed();
×
698
                }
699
                break;
×
700
            }
701
        }
702
    }
703

704
    top_view->reload_data();
1✔
705
    this->tss_view->reload_data();
1✔
706
}
1✔
707

708
void
709
filter_sub_source::rl_blur(textinput_curses& tc)
1✔
710
{
711
    auto* top_view = *lnav_data.ld_view_stack.top();
1✔
712
    top_view->get_highlights().erase({highlight_source_t::PREVIEW, "preview"});
1✔
713
    lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
1✔
714
    lnav_data.ld_filter_help_status_source.fss_prompt.clear();
1✔
715
    lnav_data.ld_filter_help_status_source.fss_error.clear();
1✔
716
    this->fss_editing = false;
1✔
717
    tc.set_visible(false);
1✔
718
    this->tss_view->set_enabled(true);
1✔
719
}
1✔
720

721
void
722
filter_sub_source::rl_abort(textinput_curses& rc)
×
723
{
724
    auto* top_view = *lnav_data.ld_view_stack.top();
×
725
    auto* tss = top_view->get_sub_source();
×
726
    auto& fs = tss->get_filters();
×
727
    auto iter = fs.begin() + this->tss_view->get_selection().value();
×
728
    auto tf = *iter;
×
729

730
    top_view->reload_data();
×
731
    fs.delete_filter("");
×
732
    this->tss_view->reload_data();
×
733
    this->tss_view->set_needs_update();
×
734
    tf->set_enabled(this->fss_filter_state);
×
735
    tss->text_filters_changed();
×
736
    this->tss_view->reload_data();
×
737
}
738

739
void
740
filter_sub_source::list_input_handle_scroll_out(listview_curses& lv)
×
741
{
742
    set_view_mode(ln_mode_t::PAGING);
×
743
    lnav_data.ld_filter_view.reload_data();
×
744
}
745

746
bool
747
filter_sub_source::text_handle_mouse(
×
748
    textview_curses& tc,
749
    const listview_curses::display_line_content_t&,
750
    mouse_event& me)
751
{
752
    if (this->fss_editing) {
×
753
        return true;
×
754
    }
755
    auto nci = ncinput{};
×
756
    if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 1, 3)) {
×
757
        nci.id = ' ';
×
758
        nci.eff_text[0] = ' ';
×
759
        this->list_input_handle_key(tc, nci);
×
760
    }
761
    if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 4, 7)) {
×
762
        nci.id = 't';
×
763
        nci.eff_text[0] = 't';
×
764
        this->list_input_handle_key(tc, nci);
×
765
    }
766
    if (me.is_double_click_in(mouse_button_t::BUTTON_LEFT, line_range{25, -1}))
×
767
    {
768
        nci.id = '\r';
×
769
        nci.eff_text[0] = '\r';
×
770
        this->list_input_handle_key(tc, nci);
×
771
    }
772
    return true;
×
773
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc