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

tstack / lnav / 19898103452-2729

03 Dec 2025 02:48PM UTC coverage: 68.924% (+0.05%) from 68.872%
19898103452-2729

push

github

tstack
[parser-details] mention matching search tables

85 of 94 new or added lines in 15 files covered. (90.43%)

4 existing lines in 3 files now uncovered.

51465 of 74669 relevant lines covered (68.92%)

435706.41 hits per line

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

0.0
/src/readline_callbacks.cc
1
/**
2
 * Copyright (c) 2015, 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 <chrono>
31

32
#include "readline_callbacks.hh"
33

34
#include "base/fs_util.hh"
35
#include "base/humanize.network.hh"
36
#include "base/injector.hh"
37
#include "base/itertools.hh"
38
#include "base/paths.hh"
39
#include "bound_tags.hh"
40
#include "cmd.parser.hh"
41
#include "command_executor.hh"
42
#include "config.h"
43
#include "field_overlay_source.hh"
44
#include "help_text_formatter.hh"
45
#include "itertools.similar.hh"
46
#include "lnav.hh"
47
#include "lnav.prompt.hh"
48
#include "lnav_config.hh"
49
#include "log_format_loader.hh"
50
#include "plain_text_source.hh"
51
#include "readline_highlighters.hh"
52
#include "scn/scan.h"
53
#include "service_tags.hh"
54
#include "sql_help.hh"
55
#include "tailer/tailer.looper.hh"
56
#include "view_helpers.examples.hh"
57
#include "vtab_module.hh"
58
#include "yajlpp/yajlpp.hh"
59

60
using namespace std::chrono_literals;
61
using namespace lnav::roles::literals;
62

63
#define PERFORM_MSG \
64
    "(Press " ANSI_BOLD("CTRL+X") " to perform operation and " ANSI_BOLD( \
65
        "Esc") " to abort)"
66
#define ABORT_MSG "(Press " ANSI_BOLD("Esc") " to abort)"
67

68
#define ANSI_RE(msg) \
69
    ANSI_CSI ANSI_BOLD_PARAM ";" ANSI_COLOR_PARAM(COLOR_CYAN) "m" msg ANSI_NORM
70
#define ANSI_CLS(msg) \
71
    ANSI_CSI ANSI_BOLD_PARAM \
72
        ";" ANSI_COLOR_PARAM(COLOR_MAGENTA) "m" msg ANSI_NORM
73
#define ANSI_KW(msg) \
74
    ANSI_CSI ANSI_BOLD_PARAM ";" ANSI_COLOR_PARAM(COLOR_BLUE) "m" msg ANSI_NORM
75
#define ANSI_REV(msg) ANSI_CSI "7m" msg ANSI_NORM
76
#define ANSI_STR(msg) ANSI_CSI "32m" msg ANSI_NORM
77

78
const char * const RE_HELP =
79
    " "  ANSI_RE(".") "   Any character    "
80
    " "     "a" ANSI_RE("|") "b   a or b        "
81
    " " ANSI_RE("(?-i)") "   Case-sensitive search\n"
82

83
    " " ANSI_CLS("\\w") "  Word character   "
84
    " "     "a" ANSI_RE("?") "    0 or 1 a's    "
85
    " "                 ANSI_RE("$") "       End of string\n"
86

87
    " " ANSI_CLS("\\d") "  Digit            "
88
    " "     "a" ANSI_RE("*") "    0 or more a's "
89
    " " ANSI_RE("(") "..." ANSI_RE(")") "   Capture\n"
90

91
    " " ANSI_CLS("\\s") "  White space      "
92
    " "     "a" ANSI_RE("+") "    1 or more a's "
93
    " "                 ANSI_RE("^") "       Start of string\n"
94

95
    " " ANSI_RE("\\") "   Escape character "
96
    " " ANSI_RE("[^") "ab" ANSI_RE("]") " " ANSI_BOLD("Not") " a or b    "
97
    " " ANSI_RE("[") "ab" ANSI_RE("-") "d" ANSI_RE("]") "  Any of a, b, c, or d"
98
;
99

100
const char * const RE_EXAMPLE =
101
    ANSI_UNDERLINE("Examples") "\n"
102
    "  abc" ANSI_RE("*") "       matches  "
103
    ANSI_STR("'ab'") ", " ANSI_STR("'abc'") ", " ANSI_STR("'abccc'") "\n"
104

105
    "  key=" ANSI_RE("(\\w+)")
106
    "  matches  key=" ANSI_REV("123") ", key=" ANSI_REV("abc") " and captures 123 and abc\n"
107

108
    "  " ANSI_RE("\\") "[abc" ANSI_RE("\\") "]    matches  " ANSI_STR("'[abc]'") "\n"
109

110
    "  " ANSI_RE("(?-i)") "ABC   matches  " ANSI_STR("'ABC'") " and " ANSI_UNDERLINE("not") " " ANSI_STR("'abc'")
111
;
112

113
const char* const CMD_HELP =
114
    " " ANSI_KW(":goto") "              Go to a line #, timestamp, etc...\n"
115
    " " ANSI_KW(":filter-out") "        Filter out lines that match a pattern\n"
116
    " " ANSI_KW(":hide-lines-before") " Hide lines before a timestamp\n"
117
    " " ANSI_KW(":open") "              Open another file/directory\n";
118

119
const char* const CMD_EXAMPLE =
120
    ANSI_UNDERLINE("Examples") "\n"
121
    "  " ANSI_KW(":goto") " 123\n"
122
    "  " ANSI_KW(":filter-out") " spam\n"
123
    "  " ANSI_KW(":hide-lines-before") " here\n";
124

125
const char * const SQL_HELP =
126
    " " ANSI_KW("SELECT") "  Select rows from a table      "
127
    " " ANSI_KW("DELETE") "  Delete rows from a table\n"
128
    " " ANSI_KW("INSERT") "  Insert rows into a table      "
129
    " " ANSI_KW("UPDATE") "  Update rows in a table\n"
130
    " " ANSI_KW("CREATE") "  Create a table/index          "
131
    " " ANSI_KW("DROP") "    Drop a table/index\n"
132
    " " ANSI_KW("ATTACH") "  Attach a SQLite database file "
133
    " " ANSI_KW("DETACH") "  Detach a SQLite database"
134
;
135

136
const char * const SQL_EXAMPLE =
137
    ANSI_UNDERLINE("Examples") "\n"
138
    "  SELECT * FROM %s WHERE log_level >= 'warning' LIMIT 10\n"
139
    "  UPDATE %s SET log_mark = 1 WHERE log_line = log_top_line()\n"
140
    "  SELECT * FROM logline LIMIT 10"
141
;
142

143
const char * const PRQL_HELP =
144
    " " ANSI_KW("from") "    Specify a data source       "
145
    " " ANSI_KW("derive") "     Derive one or more columns\n"
146
    " " ANSI_KW("select") "  Select one or more columns  "
147
    " " ANSI_KW("aggregate") "  Summary many rows into one\n"
148
    " " ANSI_KW("group") "   Partition rows into groups  "
149
    " " ANSI_KW("filter") "     Pick rows based on their values\n"
150
    ;
151

152
const char * const PRQL_EXAMPLE =
153
    ANSI_UNDERLINE("Examples") "\n"
154
        "  from %s | stats.count_by { log_level }\n"
155
        "  from %s | filter log_line == lnav.view.top_line\n"
156
    ;
157

158
static const auto LNAV_MULTILINE_CMD_PROMPT
159
    = "Enter an lnav command: " PERFORM_MSG;
160
static const auto LNAV_CMD_PROMPT = "Enter an lnav command: " ABORT_MSG;
161

162
static attr_line_t
163
format_sql_example(const char* sql_example_fmt)
×
164
{
165
    auto& log_view = lnav_data.ld_views[LNV_LOG];
×
166
    auto* lss = (logfile_sub_source*) log_view.get_sub_source();
×
167
    attr_line_t retval;
×
168

169
    if (log_view.get_inner_height() > 0) {
×
170
        auto cl = lss->at(log_view.get_top());
×
171
        auto lf = lss->find(cl);
×
172
        const auto* format_name = lf->get_format()->get_name().get();
×
173

174
        retval.with_ansi_string(sql_example_fmt, format_name, format_name);
×
175
        readline_sql_highlighter(
×
176
            retval, lnav::sql::dialect::sqlite, std::nullopt);
177
    }
178
    return retval;
×
179
}
×
180

181
void
182
rl_set_help()
×
183
{
184
    switch (lnav_data.ld_mode) {
×
185
        case ln_mode_t::SEARCH: {
×
186
            lnav_data.ld_doc_source.replace_with(RE_HELP);
×
187
            lnav_data.ld_example_source.replace_with(RE_EXAMPLE);
×
188
            break;
×
189
        }
190
        case ln_mode_t::SQL: {
×
191
            auto example_al = format_sql_example(SQL_EXAMPLE);
×
192
            lnav_data.ld_doc_source.replace_with(SQL_HELP);
×
193
            lnav_data.ld_example_source.replace_with(example_al);
×
194
            break;
×
195
        }
196
        case ln_mode_t::COMMAND: {
×
197
            lnav_data.ld_doc_source.replace_with(CMD_HELP);
×
198
            lnav_data.ld_example_source.replace_with(CMD_EXAMPLE);
×
199
            break;
×
200
        }
201
        default:
×
202
            break;
×
203
    }
204
}
205

206
static bool
207
rl_sql_help(textinput_curses& rc)
×
208
{
209
    auto al = attr_line_t(rc.get_content());
×
210
    const auto& sa = al.get_attrs();
×
211
    auto x = rc.get_cursor_offset();
×
212
    bool has_doc = false;
×
213

214
    if (x > 0) {
×
215
        x -= 1;
×
216
    }
217

218
    annotate_sql_statement(al, lnav::sql::dialect::sqlite);
×
219

220
    auto avail_help = find_sql_help_for_line(al, x);
×
221
    auto lang = help_example::language::undefined;
×
222
    if (lnav::sql::is_prql(al.get_string())) {
×
223
        lang = help_example::language::prql;
×
224
    }
225

226
    if (!avail_help.empty()) {
×
227
        size_t help_count = avail_help.size();
×
228
        auto& dtc = lnav_data.ld_doc_view;
×
229
        auto& etc = lnav_data.ld_example_view;
×
230
        unsigned long doc_width, ex_width;
231
        vis_line_t doc_height, ex_height;
×
232
        attr_line_t doc_al, ex_al;
×
233

234
        dtc.get_dimensions(doc_height, doc_width);
×
235
        etc.get_dimensions(ex_height, ex_width);
×
236

237
        for (const auto& ht : avail_help) {
×
238
            format_help_text_for_term(*ht,
×
239
                                      std::min(70UL, doc_width),
×
240
                                      doc_al,
241
                                      help_count > 1
242
                                          ? help_text_content::synopsis
243
                                          : help_text_content::full);
244
            if (help_count == 1) {
×
245
                format_example_text_for_term(
×
246
                    *ht, eval_example, std::min(70UL, ex_width), ex_al, lang);
×
247
            } else {
248
                doc_al.append("\n");
×
249
            }
250
        }
251

252
        if (!doc_al.empty()) {
×
253
            lnav_data.ld_doc_source.replace_with(doc_al);
×
254
            dtc.reload_data();
×
255

256
            lnav_data.ld_example_source.replace_with(ex_al);
×
257
            etc.reload_data();
×
258

259
            has_doc = true;
×
260
        }
261
    }
262

263
    auto ident_iter = find_string_attr_containing(
×
264
        sa, &SQL_IDENTIFIER_ATTR, al.nearest_text(x));
265
    if (ident_iter == sa.end()) {
×
266
        ident_iter = find_string_attr_containing(
×
267
            sa, &lnav::sql::PRQL_FQID_ATTR, al.nearest_text(x));
268
    }
269
    if (ident_iter != sa.end()) {
×
NEW
270
        auto* vtab_manager = injector::get<log_vtab_manager*>();
×
271
        auto ident = al.get_substring(ident_iter->sa_range);
×
272
        const intern_string_t intern_ident = intern_string::lookup(ident);
×
NEW
273
        auto vtab = vtab_manager->lookup_impl(intern_ident);
×
274
        auto vtab_module_iter = vtab_module_ddls.find(intern_ident);
×
275
        std::string ddl;
×
276

277
        if (vtab != nullptr) {
×
278
            ddl = trim(vtab->get_table_statement());
×
279
        } else if (vtab_module_iter != vtab_module_ddls.end()) {
×
280
            ddl = vtab_module_iter->second;
×
281
        } else {
282
            auto table_ddl_iter = lnav_data.ld_table_ddl.find(ident);
×
283

284
            if (table_ddl_iter != lnav_data.ld_table_ddl.end()) {
×
285
                ddl = table_ddl_iter->second;
×
286
            }
287
        }
288

289
        if (!ddl.empty()) {
×
290
            lnav_data.ld_preview_view[0].set_sub_source(
×
291
                &lnav_data.ld_preview_source[0]);
292
            lnav_data.ld_preview_view[0].set_overlay_source(nullptr);
×
293
            lnav_data.ld_preview_source[0].replace_with(ddl).set_text_format(
×
294
                text_format_t::TF_SQL);
295
            lnav_data.ld_preview_status_source[0].get_description().set_value(
×
296
                "Definition for table -- %s", ident.c_str());
297
            lnav_data.ld_status[LNS_PREVIEW0].set_needs_update();
×
298
        }
299
    }
300

301
    return has_doc;
×
302
}
303

304
static void
305
rl_cmd_change(textinput_curses& rc, bool is_req)
×
306
{
307
    static const std::set<std::string> COMMANDS_WITH_SQL = {
308
        "filter-expr",
309
        "mark-expr",
310
    };
311

312
    static const std::set<std::string> COMMANDS_FOR_FIELDS = {
313
        "hide-fields",
314
        "show-fields",
315
    };
316

317
    static auto& prompt = lnav::prompt::get();
318
    auto* tc = get_textview_for_mode(lnav_data.ld_mode);
×
319

320
    clear_preview();
×
321

322
    static std::string last_command;
323
    static int generation = 0;
324

325
    const auto line = rc.get_content();
×
326
    std::vector<std::string> args;
×
327
    auto iter = lnav_commands.end();
×
328

329
    split_ws(line, args);
×
330

331
    if (args.empty()) {
×
332
        generation = 0;
×
333
    } else if (args[0] != last_command) {
×
334
        last_command = args[0];
×
335
        generation = 0;
×
336
    } else if (args.size() > 1) {
×
337
        generation += 1;
×
338
    }
339

340
    auto* os = tc->get_overlay_source();
×
341
    if (!args.empty() && os != nullptr) {
×
342
        auto* fos = dynamic_cast<field_overlay_source*>(os);
×
343

344
        if (fos != nullptr) {
×
345
            if (generation == 0) {
×
346
                auto& top_ctx = fos->fos_contexts.top();
×
347

348
                if (COMMANDS_WITH_SQL.count(args[0]) > 0) {
×
349
                    top_ctx.c_prefix = ":";
×
350
                    top_ctx.c_show = true;
×
351
                    top_ctx.c_show_discovered = false;
×
352
                } else if (COMMANDS_FOR_FIELDS.count(args[0]) > 0) {
×
353
                    top_ctx.c_prefix = "";
×
354
                    top_ctx.c_show = true;
×
355
                    top_ctx.c_show_discovered = false;
×
356
                } else {
357
                    top_ctx.c_prefix = "";
×
358
                    top_ctx.c_show = false;
×
359
                }
360
                tc->set_sync_selection_and_top(top_ctx.c_show);
×
361
            }
362
        }
363
    }
364

365
    if (!args.empty()) {
×
366
        iter = lnav_commands.find(args[0]);
×
367
    }
368
    if (iter == lnav_commands.end()
×
369
        || (args.size() == 1 && !endswith(line, " ") && !endswith(line, "\n")))
×
370
    {
371
        auto poss_str = lnav_commands | lnav::itertools::first()
×
372
            | lnav::itertools::similar_to(args.empty() ? "" : args[0], 10);
×
373
        auto poss_width = poss_str
374
            | lnav::itertools::map(&string_fragment::length)
×
375
            | lnav::itertools::max();
×
376

377
        auto poss = poss_str
378
            | lnav::itertools::map([&args, &poss_width](const auto& x) {
×
379
                        return attr_line_t()
×
380
                            .append(x, VC_ROLE.value(role_t::VCR_KEYWORD))
×
381
                            .highlight_fuzzy_matches(cget(args, 0).value_or(""))
×
382
                            .append(" ")
×
383
                            .pad_to(poss_width.value_or(0) + 1)
×
384
                            .append(lnav_commands[x]->c_help.ht_summary)
×
385
                            .with_attr_for_all(lnav::prompt::SUBST_TEXT.value(
×
386
                                fmt::format(FMT_STRING("{} "), x)));
×
387
                    });
×
388

389
        rc.open_popup_for_completion(0, poss);
×
390
        rc.tc_popup.set_title("Command");
×
391
        prompt.p_editor.tc_height = std::min(
×
392
            prompt.p_editor.tc_height, (int) prompt.p_editor.tc_lines.size());
×
393
        lnav_data.ld_doc_source.replace_with(CMD_HELP);
×
394
        lnav_data.ld_example_source.replace_with(CMD_EXAMPLE);
×
395
        lnav_data.ld_bottom_source.set_prompt(LNAV_CMD_PROMPT);
×
396
        lnav_data.ld_bottom_source.grep_error("");
×
397
        lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
398
    } else if (args[0] == "config" && args.size() > 1) {
×
399
        static const auto INPUT_SRC = intern_string::lookup("input");
400
        yajlpp_parse_context ypc(INPUT_SRC, &lnav_config_handlers);
×
401

402
        ypc.set_path(args[1]).with_obj(lnav_config);
×
403
        ypc.update_callbacks();
×
404

405
        if (ypc.ypc_current_handler != nullptr) {
×
406
            const json_path_handler_base* jph = ypc.ypc_current_handler;
×
407
            auto help_text = fmt::format(
408
                FMT_STRING(ANSI_BOLD("{} {}") " -- {}    " ABORT_MSG),
×
409
                jph->jph_property.c_str(),
×
410
                jph->jph_synopsis,
×
411
                jph->jph_description);
×
412
            lnav_data.ld_bottom_source.set_prompt(help_text);
×
413
            lnav_data.ld_bottom_source.grep_error("");
×
414
        } else {
×
415
            lnav_data.ld_bottom_source.grep_error(
×
416
                "Unknown configuration option: " + args[1]);
×
417
        }
418
        lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
419
    } else if ((args[0] != "filter-expr" && args[0] != "mark-expr")
×
420
               || !rl_sql_help(rc))
×
421
    {
422
        const auto& cmd = *iter->second;
×
423
        const auto& ht = cmd.c_help;
×
424

425
        if (ht.ht_name) {
×
426
            auto& dtc = lnav_data.ld_doc_view;
×
427
            auto& etc = lnav_data.ld_example_view;
×
428
            unsigned long width;
429
            vis_line_t height;
×
430
            attr_line_t al;
×
431

432
            dtc.get_dimensions(height, width);
×
433
            format_help_text_for_term(ht, std::min(70UL, width), al);
×
434
            lnav_data.ld_doc_source.replace_with(al);
×
435
            dtc.set_needs_update();
×
436

437
            al.clear();
×
438
            etc.get_dimensions(height, width);
×
439
            format_example_text_for_term(ht, eval_example, width, al);
×
440
            lnav_data.ld_example_source.replace_with(al);
×
441
            etc.set_needs_update();
×
442
        }
443

444
        if (cmd.c_prompt != nullptr) {
×
445
            const auto prompt_res
446
                = cmd.c_prompt(lnav_data.ld_exec_context, line);
×
447

448
            if (generation == 0 && trim(line) == args[0]
×
449
                && !prompt_res.pr_new_prompt.empty())
×
450
            {
451
                log_debug("replacing prompt with one suggested by command");
×
452
                prompt.p_editor.tc_selection
×
453
                    = textinput_curses::selected_range::from_key(
×
454
                        textinput_curses::input_point::home(),
455
                        prompt.p_editor.tc_cursor);
×
456
                prompt.p_editor.replace_selection(prompt_res.pr_new_prompt);
×
457
                prompt.p_editor.move_cursor_to({(int) args[0].length() + 1, 0});
×
458
                generation += 1;
×
459
                iter = lnav_commands.end();
×
460
            }
461
            rc.tc_suggestion = prompt_res.pr_suggestion;
×
462
        }
463

464
        if (!ht.ht_parameters.empty()
×
465
            && ht.ht_parameters.front().ht_format
×
466
                == help_parameter_format_t::HPF_MULTILINE_TEXT)
467
        {
468
            if (prompt.p_editor.tc_height == 1) {
×
469
                prompt.p_editor.set_height(5);
×
470
            }
471
        } else if (prompt.p_editor.tc_height > 1) {
×
472
            auto ml_content = prompt.p_editor.get_content();
×
473
            std::replace(ml_content.begin(), ml_content.end(), '\n', ' ');
×
474
            prompt.p_editor.set_content(ml_content);
×
475
            prompt.p_editor.tc_height = 1;
×
476
        }
477

478
        lnav_data.ld_bottom_source.set_prompt(prompt.p_editor.tc_height > 1
×
479
                                                  ? LNAV_MULTILINE_CMD_PROMPT
480
                                                  : LNAV_CMD_PROMPT);
481
        lnav_data.ld_bottom_source.grep_error("");
×
482
        lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
483
    }
484

485
    if (iter != lnav_commands.end() && (args.size() > 1 || endswith(line, " ")))
×
486
    {
487
        auto line_sf = string_fragment::from_str(line);
×
488
        auto args_sf = line_sf.split_when(string_fragment::tag1{' '}).second;
×
489
        auto parsed_cmd = lnav::command::parse_for_prompt(
490
            lnav_data.ld_exec_context, args_sf, iter->second->c_help);
×
491
        auto x
492
            = args_sf.column_to_byte_index(rc.tc_cursor.x - args_sf.sf_begin);
×
493
        auto arg_res_opt = parsed_cmd.arg_at(x);
×
494

495
        if (arg_res_opt) {
×
496
            auto arg_res = arg_res_opt.value();
×
497
            log_debug("apair %s [%d:%d) -- %s",
×
498
                      arg_res.aar_help->ht_name,
499
                      arg_res.aar_element.se_origin.sf_begin,
500
                      arg_res.aar_element.se_origin.sf_end,
501
                      arg_res.aar_element.se_value.c_str());
502
            auto start_point = rc.get_point_for_offset(
×
503
                args_sf.sf_begin + arg_res.aar_element.se_origin.sf_begin);
×
504
            auto end_point = rc.get_point_for_offset(
×
505
                args_sf.sf_begin + arg_res.aar_element.se_origin.sf_end);
×
506
            auto crange = arg_res.aar_element.se_origin.empty()
×
507
                ? line_range{rc.tc_cursor.x, rc.tc_cursor.x}
×
508
                : line_range{start_point.x, end_point.x};
×
509
            if (arg_res.aar_help->ht_format
×
510
                == help_parameter_format_t::HPF_CONFIG_VALUE)
511
            {
512
                log_debug(
×
513
                    "arg path %s",
514
                    parsed_cmd.p_args["option"].a_values[0].se_value.c_str());
515
                auto poss = prompt.get_config_value_completion(
516
                    parsed_cmd.p_args["option"].a_values[0].se_value,
×
517
                    arg_res.aar_element.se_origin.to_string());
×
518
                rc.open_popup_for_completion(crange, poss);
×
519
                rc.tc_popup.set_title(arg_res.aar_help->ht_name);
×
520
            } else if (is_req || arg_res.aar_required
×
521
                       || (!arg_res.aar_element.se_origin.empty()
×
522
                           && rc.tc_popup_type
×
523
                               != textinput_curses::popup_type_t::none))
524
            {
525
                log_debug(" req %d %d", is_req, arg_res.aar_required);
×
526
                auto poss = prompt.get_cmd_parameter_completion(
527
                    *tc,
528
                    &iter->second->c_help,
×
529
                    arg_res.aar_help,
530
                    arg_res.aar_element.se_value.empty()
×
531
                        ? arg_res.aar_element.se_origin.to_string()
×
532
                        : arg_res.aar_element.se_value);
×
533
                rc.open_popup_for_completion(crange, poss);
×
534
                rc.tc_popup.set_title(arg_res.aar_help->ht_name);
×
535
            } else if (arg_res.aar_help->ht_format
×
536
                           == help_parameter_format_t::HPF_REGEX
537
                       && arg_res.aar_element.se_value.empty()
×
538
                       && rc.is_cursor_at_end_of_line())
×
539
            {
540
                auto re_arg = parsed_cmd.p_args[arg_res.aar_help->ht_name];
×
541
                if (!re_arg.a_values.empty()) {
×
542
                    rc.tc_suggestion = prompt.get_regex_suggestion(
×
543
                        *tc, re_arg.a_values[0].se_value);
×
544
                } else {
545
                    rc.tc_suggestion.clear();
×
546
                }
547
            } else {
×
548
                rc.tc_suggestion.clear();
×
549
            }
550
        } else {
×
551
            log_info("no arg at %zu", x);
×
552
        }
553
    }
554
}
555

556
static void
557
rl_sql_change(textinput_curses& rc, bool is_req)
×
558
{
559
    static const auto* sql_cmd_map
560
        = injector::get<readline_context::command_map_t*, sql_cmd_map_tag>();
×
561
    static auto& prompt = lnav::prompt::get();
562

563
    auto* tc = get_textview_for_mode(lnav_data.ld_mode);
×
564
    const auto line = rc.get_content();
×
565
    const auto line_sf = string_fragment::from_str(line);
×
566
    std::vector<std::string> args;
×
567
    auto is_prql = lnav::sql::is_prql(line);
×
568

569
    log_debug("rl_sql_change");
×
570

571
    if (line.empty() || line.find(' ') == std::string::npos) {
×
572
        log_debug("completing DB command");
×
573
        auto poss_str = (*sql_cmd_map)
574
            | lnav::itertools::filter_in([](const auto& cmd_pair) {
×
575
                            return cmd_pair.second->c_dependencies.empty();
×
576
                        })
577
            | lnav::itertools::first() | lnav::itertools::similar_to(line, 10);
×
578
        auto poss_width = poss_str
579
            | lnav::itertools::map(&string_fragment::length)
×
580
            | lnav::itertools::max();
×
581

582
        auto poss
583
            = poss_str
584
            | lnav::itertools::map([&args, &poss_width](const auto& x) {
×
585
                  auto x_str = x.to_string();
×
586
                  const auto* summary = sql_cmd_map->at(x)->c_help.ht_summary
×
587
                      ? sql_cmd_map->at(x)->c_help.ht_summary
×
588
                      : sqlite_function_help.find(x_str)->second->ht_summary;
×
589
                  return attr_line_t()
×
590
                      .append(x, VC_ROLE.value(role_t::VCR_KEYWORD))
×
591
                      .highlight_fuzzy_matches(cget(args, 0).value_or(""))
×
592
                      .append(" ")
×
593
                      .pad_to(poss_width.value_or(0) + 1)
×
594
                      .append(summary)
×
595
                      .with_attr_for_all(
×
596
                          lnav::prompt::SUBST_TEXT.value(x_str + " "));
×
597
              });
×
598

599
        rc.open_popup_for_completion(0, poss);
×
600
        rc.tc_popup.set_title("DB Command");
×
601
    } else if (is_prql) {
×
602
        auto anno_line = attr_line_t(line);
×
603
        lnav::sql::annotate_prql_statement(anno_line);
×
604
        auto cursor_offset = prompt.p_editor.get_cursor_offset();
×
605

606
        log_debug("curs %d", cursor_offset);
×
607
        for (const auto& attr : anno_line.al_attrs) {
×
608
            log_debug("attr [%d:%d) %s",
×
609
                      attr.sa_range.lr_start,
610
                      attr.sa_range.lr_end,
611
                      attr.sa_type->sat_name);
612
        }
613

614
        auto attr_iter = rfind_string_attr_if(
×
615
            anno_line.al_attrs, cursor_offset, [](const auto& x) {
×
616
                return x.sa_type != &lnav::sql::PRQL_STAGE_ATTR;
×
617
            });
618
        auto stage_iter = rfind_string_attr_if(
×
619
            anno_line.al_attrs, cursor_offset, [](const auto& x) {
×
620
                return x.sa_type == &lnav::sql::PRQL_STAGE_ATTR;
×
621
            });
622
        if (attr_iter != anno_line.al_attrs.end()) {
×
623
            auto to_complete_sf = anno_line.to_string_fragment(attr_iter);
×
624
            auto to_complete = to_complete_sf.to_string();
×
625
            std::vector<attr_line_t> poss;
×
626
            std::string title;
×
627

628
            log_debug("prql attr [%d:%d) %s",
×
629
                      attr_iter->sa_range.lr_start,
630
                      attr_iter->sa_range.lr_end,
631
                      attr_iter->sa_type->sat_name);
632
            auto prev_attr_iter = std::prev(attr_iter);
×
633
            if (attr_iter->sa_type == &lnav::sql::PRQL_PIPE_ATTR
×
634
                || (attr_iter->sa_type == &lnav::sql::PRQL_FQID_ATTR
×
635
                    && (prev_attr_iter->sa_type == &lnav::sql::PRQL_PIPE_ATTR
×
636
                        || prev_attr_iter->sa_type
×
637
                            == &lnav::sql::PRQL_STAGE_ATTR)))
638
            {
639
                if (attr_iter->sa_type == &lnav::sql::PRQL_PIPE_ATTR) {
×
640
                    to_complete.clear();
×
641
                }
642
                auto poss_str
643
                    = (*sql_cmd_map)
644
                    | lnav::itertools::filter_in(
×
645
                          [](const readline_context::command_map_t::value_type&
×
646
                                 p) {
647
                              return !p.second->c_dependencies.empty();
×
648
                          })
649
                    | lnav::itertools::first()
×
650
                    | lnav::itertools::similar_to(to_complete, 10);
×
651
                auto width = poss_str
652
                    | lnav::itertools::map(&string_fragment::length)
×
653
                    | lnav::itertools::max();
×
654

655
                title = "transform";
×
656
                poss = poss_str
657
                    | lnav::itertools::map([&width,
×
658
                                            &to_complete](const auto& x) {
659
                           const auto& ht = sql_cmd_map->at(x)->c_help;
×
660
                           auto sub_value = fmt::format(FMT_STRING("{} "), x);
×
661
                           if (!ht.ht_parameters.empty()
×
662
                               && ht.ht_parameters[0].ht_group_start)
×
663
                           {
664
                               sub_value.append(
×
665
                                   ht.ht_parameters[0].ht_group_start);
×
666
                               sub_value.push_back(' ');
×
667
                           }
668
                           return attr_line_t()
×
669
                               .append(x, VC_ROLE.value(role_t::VCR_FUNCTION))
×
670
                               .highlight_fuzzy_matches(to_complete)
×
671
                               .append(" ")
×
672
                               .pad_to(width.value_or(0) + 1)
×
673
                               .append(ht.ht_summary)
×
674
                               .with_attr_for_all(
×
675
                                   lnav::prompt::SUBST_TEXT.value(sub_value));
×
676
                       });
×
677
            } else if (attr_iter->sa_type == &lnav::sql::PRQL_FQID_ATTR
×
678
                       && attr_iter->sa_range.lr_end == cursor_offset)
×
679
            {
680
                poss = prompt.p_prql_completions | lnav::itertools::first()
×
681
                    | lnav::itertools::similar_to(to_complete, 10)
×
682
                    | lnav::itertools::map([&to_complete](const auto& x) {
×
683
                           return prompt.get_sql_completion_text(
684
                               to_complete, *prompt.p_prql_completions.find(x));
×
685
                       });
×
686
            }
687
            auto crange
688
                = std::make_from_tuple<line_range>(line_sf.byte_to_column_index(
×
689
                    to_complete_sf.sf_begin, to_complete_sf.sf_end));
×
690
            rc.open_popup_for_completion(crange, poss);
×
691
            rc.tc_popup.set_title(title);
×
692
        }
693
    } else if (!prompt.p_in_completion) {
×
694
        // change was not from a completion
695
        clear_preview();
×
696

697
        if (line[0] == '.') {
×
698
            auto line_sf = string_fragment::from_str(line);
×
699
            auto [sql_cmd, args_sf]
×
700
                = line_sf.split_when(string_fragment::tag1{' '});
×
701
            auto iter = sql_cmd_map->find(sql_cmd.to_string());
×
702
            if (iter != sql_cmd_map->end()) {
×
703
                auto parsed_cmd = lnav::command::parse_for_prompt(
704
                    lnav_data.ld_exec_context, args_sf, iter->second->c_help);
×
705
                auto x = args_sf.column_to_byte_index(rc.tc_cursor.x
×
706
                                                      - args_sf.sf_begin);
×
707
                auto arg_res_opt = parsed_cmd.arg_at(x);
×
708

709
                auto arg_res = arg_res_opt.value();
×
710
                log_debug("apair %s [%d:%d) -- %s",
×
711
                          arg_res.aar_help->ht_name,
712
                          arg_res.aar_element.se_origin.sf_begin,
713
                          arg_res.aar_element.se_origin.sf_end,
714
                          arg_res.aar_element.se_value.c_str());
715
                auto start_point = rc.get_point_for_offset(
×
716
                    args_sf.sf_begin + arg_res.aar_element.se_origin.sf_begin);
×
717
                auto end_point = rc.get_point_for_offset(
×
718
                    args_sf.sf_begin + arg_res.aar_element.se_origin.sf_end);
×
719
                auto crange = arg_res.aar_element.se_origin.empty()
×
720
                    ? line_range{rc.tc_cursor.x, rc.tc_cursor.x}
×
721
                    : line_range{start_point.x, end_point.x};
×
722
                if (is_req || arg_res.aar_required
×
723
                    || (!arg_res.aar_element.se_origin.empty()
×
724
                        && rc.tc_popup_type
×
725
                            != textinput_curses::popup_type_t::none))
726
                {
727
                    log_debug(" req %d %d", is_req, arg_res.aar_required);
×
728
                    auto poss = prompt.get_cmd_parameter_completion(
729
                        *tc,
730
                        &iter->second->c_help,
×
731
                        arg_res.aar_help,
732
                        arg_res.aar_element.se_value.empty()
×
733
                            ? arg_res.aar_element.se_origin.to_string()
×
734
                            : arg_res.aar_element.se_value);
×
735
                    rc.open_popup_for_completion(crange, poss);
×
736
                    rc.tc_popup.set_title(arg_res.aar_help->ht_name);
×
737
                }
738
            }
739
        } else {
740
            auto anno_line = rc.tc_lines[rc.tc_cursor.y];
×
741
            annotate_sql_statement(anno_line, lnav::sql::dialect::sqlite);
×
742
            auto cursor_offset = anno_line.column_to_byte_index(rc.tc_cursor.x);
×
743

744
            auto attr_iter = rfind_string_attr_if(
×
745
                anno_line.al_attrs,
746
                cursor_offset,
747
                [cursor_offset](const string_attr& attr) {
×
748
                    return attr.sa_range.lr_start <= cursor_offset
×
749
                        && cursor_offset <= attr.sa_range.lr_end;
×
750
                });
751
            if (attr_iter != anno_line.al_attrs.end()) {
×
752
                if (!is_req && attr_iter->sa_type == &SQL_HEX_LIT_ATTR
×
753
                    && attr_iter->sa_range.length() == 2)
×
754
                {
755
                } else if (is_req
×
756
                           || (attr_iter->sa_type != &SQL_NUMBER_ATTR
×
757
                               && attr_iter->sa_type != &SQL_HEX_LIT_ATTR)
×
758
                           || rc.tc_popup_type
×
759
                               == textinput_curses::popup_type_t::completion)
760
                {
761
                    auto to_complete_sf
762
                        = anno_line.to_string_fragment(attr_iter);
×
763
                    auto to_complete = to_complete_sf.to_string();
×
764
                    std::vector<std::string> poss_strs;
×
765
                    std::vector<attr_line_t> poss;
×
766

767
                    log_debug("SQL complete: %s -- %s",
×
768
                              attr_iter->sa_type->sat_name,
769
                              to_complete.c_str());
770
                    poss_strs = prompt.p_sql_completion_terms
×
771
                        | lnav::itertools::similar_to(to_complete, 10);
×
772
                    for (const auto& str : poss_strs) {
×
773
                        auto eq_range
774
                            = prompt.p_sql_completions.equal_range(str);
×
775

776
                        for (auto iter = eq_range.first;
×
777
                             iter != eq_range.second;
×
778
                             ++iter)
×
779
                        {
780
                            auto al = prompt.get_sql_completion_text(
781
                                to_complete, *iter);
×
782
                            poss.emplace_back(al);
×
783
                        }
784
                    }
785

786
                    auto crange = std::make_from_tuple<line_range>(
×
787
                        line_sf.byte_to_column_index(to_complete_sf.sf_begin,
×
788
                                                     to_complete_sf.sf_end));
×
789
                    log_debug("sql complete range [%d:%d)",
×
790
                              crange.lr_start,
791
                              crange.lr_end);
792
                    rc.open_popup_for_completion(crange, poss);
×
793
                    rc.tc_popup.set_title("");
×
794
                }
795
            } else if (is_req) {
×
796
                std::vector<attr_line_t> poss;
×
797

798
                for (const auto& sql_pair : prompt.p_sql_completions) {
×
799
                    const auto& item = sql_pair.second;
×
800
                    auto add = item.is<lnav::prompt::sql_table_t>()
×
801
                        || item.is<lnav::prompt::sql_function_t>()
×
802
                        || item.is<lnav::prompt::sql_format_column_t>();
×
803
                    if (!add) {
×
804
                        continue;
×
805
                    }
806

807
                    poss.emplace_back(
×
808
                        prompt.get_sql_completion_text("", sql_pair));
×
809
                }
810
                rc.open_popup_for_completion(rc.tc_cursor.x, poss);
×
811
                rc.tc_popup.set_title("");
×
812
            }
813
        }
814
    }
815

816
    split_ws(line, args);
×
817
    if (!args.empty()) {
×
818
        auto cmd_iter = sql_cmd_map->find(args[0]);
×
819
        if (cmd_iter != sql_cmd_map->end()) {
×
820
            const auto* sql_cmd = cmd_iter->second;
×
821
            if (sql_cmd->c_prompt != nullptr) {
×
822
                const auto prompt_res
823
                    = sql_cmd->c_prompt(lnav_data.ld_exec_context, line);
×
824

825
                rc.tc_suggestion = prompt_res.pr_suggestion;
×
826
            }
827
        }
828
    }
829
}
830

831
static void
832
rl_search_change(textinput_curses& rc, bool is_req)
×
833
{
834
    static const auto SEARCH_HELP
835
        = help_text("search", "search the view for a pattern")
×
836
              .with_parameter(
×
837
                  help_text("pattern", "The pattern to search for")
×
838
                      .with_format(help_parameter_format_t::HPF_REGEX));
×
839
    static auto& prompt = lnav::prompt::get();
840

841
    auto* tc = get_textview_for_mode(lnav_data.ld_mode);
×
842
    auto line = rc.get_content();
×
843
    auto line_sf = string_fragment::from_str(line);
×
844
    if (line.empty() && tc->tc_selected_text) {
×
845
        rc.tc_suggestion = tc->tc_selected_text->sti_value;
×
846
    } else {
847
        rc.tc_suggestion.clear();
×
848
    }
849
    if (rc.tc_suggestion.empty()) {
×
850
        auto parse_res = lnav::command::parse_for_prompt(
851
            lnav_data.ld_exec_context, line, SEARCH_HELP);
×
852

853
        auto byte_x = line_sf.column_to_byte_index(rc.tc_cursor.x);
×
854
        auto arg_res_opt = parse_res.arg_at(byte_x);
×
855
        if (arg_res_opt) {
×
856
            auto arg_pair = arg_res_opt.value();
×
857
            log_debug("apair %s [%d:%d) -- %s",
×
858
                      arg_pair.aar_help->ht_name,
859
                      arg_pair.aar_element.se_origin.sf_begin,
860
                      arg_pair.aar_element.se_origin.sf_end,
861
                      arg_pair.aar_element.se_value.c_str());
862
            if (is_req
×
863
                || (!arg_pair.aar_element.se_origin.empty()
×
864
                    && rc.tc_popup_type
×
865
                        != textinput_curses::popup_type_t::none))
866
            {
867
                auto poss = prompt.get_cmd_parameter_completion(
868
                    *tc,
869
                    &SEARCH_HELP,
870
                    arg_pair.aar_help,
871
                    arg_pair.aar_element.se_value);
×
872

873
                auto crange = arg_pair.aar_element.se_origin.empty()
×
874
                    ? line_range{rc.tc_cursor.x, rc.tc_cursor.x}
×
875
                    : std::make_from_tuple<line_range>(
×
876
                          line_sf.byte_to_column_index(
×
877
                              arg_pair.aar_element.se_origin.sf_begin,
×
878
                              arg_pair.aar_element.se_origin.sf_end));
×
879
                rc.open_popup_for_completion(crange, poss);
×
880
                rc.tc_popup.set_title(arg_pair.aar_help->ht_name);
×
881
            } else if (!line.empty() && arg_pair.aar_element.se_value.empty()
×
882
                       && rc.is_cursor_at_end_of_line())
×
883
            {
884
                rc.tc_suggestion = prompt.get_regex_suggestion(*tc, line);
×
885
            } else {
886
                log_debug("not at end of line %d %d",
×
887
                          arg_pair.aar_element.se_value.empty(),
888
                          rc.is_cursor_at_end_of_line());
889
                rc.tc_suggestion.clear();
×
890
            }
891
        } else {
×
892
            log_debug("no arg");
×
893
        }
894
    }
895
}
896

897
static void
898
rl_exec_change(textinput_curses& rc, bool is_req)
×
899
{
900
    static const auto EXEC_HELP
901
        = help_text("exec", "execute a script")
×
902
              .with_parameter(
×
903
                  help_text("arg", "Arguments for the script")
×
904
                      .with_format(help_parameter_format_t::HPF_TEXT));
×
905

906
    static auto& prompt = lnav::prompt::get();
907
    auto* tc = get_textview_for_mode(lnav_data.ld_mode);
×
908

909
    clear_preview();
×
910

911
    const auto line = rc.get_content();
×
912
    shlex lexer(line);
×
913
    auto split_res = lexer.split(lnav_data.ld_exec_context.create_resolver());
×
914
    if (split_res.isErr()) {
×
915
        lnav_data.ld_bottom_source.grep_error(
×
916
            split_res.unwrapErr().se_error.te_msg);
×
917
        lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
918
    } else {
919
        auto split_args = split_res.unwrap();
×
920
        auto script_name = split_args.empty() ? std::string()
×
921
                                              : split_args[0].se_value;
×
922
        const auto& scripts = prompt.p_scripts;
×
923
        const auto iter = scripts.as_scripts.find(script_name);
×
924

925
        if (iter == scripts.as_scripts.end()
×
926
            || (split_args.size() == 1 && !endswith(line, " ")))
×
927
        {
928
            lnav_data.ld_bottom_source.set_prompt(
×
929
                "Enter a script to execute: " ABORT_MSG);
930
            lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
931

932
            std::vector<attr_line_t> poss;
×
933
            auto width = scripts.as_scripts | lnav::itertools::first()
×
934
                | lnav::itertools::map(&std::string::size)
×
935
                | lnav::itertools::max();
×
936
            if (script_name.empty()) {
×
937
                poss
938
                    = scripts.as_scripts
×
939
                    | lnav::itertools::map([&width](const auto& p) {
×
940
                          return attr_line_t()
×
941
                              .append(p.first,
×
942
                                      VC_ROLE.value(role_t::VCR_VARIABLE))
×
943
                              .append(" ")
×
944
                              .pad_to(width.value_or(0) + 1)
×
945
                              .append(p.second[0].sm_description)
×
946
                              .with_attr_for_all(lnav::prompt::SUBST_TEXT.value(
×
947
                                  p.first + " "));
×
948
                      });
×
949
            } else {
950
                auto x = prompt.p_editor.get_cursor_offset();
×
951
                if (!script_name.empty() && split_args[0].se_origin.sf_end == x)
×
952
                {
953
                    poss = scripts.as_scripts | lnav::itertools::first()
×
954
                        | lnav::itertools::similar_to(script_name, 10)
×
955
                        | lnav::itertools::map([&width, &script_name, &scripts](
×
956
                                                   const auto& x) {
957
                               auto siter = scripts.as_scripts.find(x);
×
958
                               auto desc = siter->second[0].sm_description;
×
959
                               return attr_line_t()
×
960
                                   .append(x,
×
961
                                           VC_ROLE.value(role_t::VCR_VARIABLE))
×
962
                                   .highlight_fuzzy_matches(script_name)
×
963
                                   .append(" ")
×
964
                                   .pad_to(width.value_or(0) + 1)
×
965
                                   .append(desc)
×
966
                                   .with_attr_for_all(
×
967
                                       lnav::prompt::SUBST_TEXT.value(x + " "));
×
968
                           });
×
969
                }
970
            }
971

972
            prompt.p_editor.open_popup_for_completion(0, poss);
×
973
        } else {
×
974
            auto& meta = iter->second[0];
×
975

976
            if (!iter->second[0].sm_description.empty()) {
×
977
                auto help_text = fmt::format(
978
                    FMT_STRING(ANSI_BOLD("{}") " -- {}   " ABORT_MSG),
×
979
                    meta.sm_synopsis,
×
980
                    meta.sm_description);
×
981
                lnav_data.ld_bottom_source.set_prompt(help_text);
×
982
                lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
983
            }
984

985
            auto line_sf = to_string_fragment(line);
×
986
            auto parse_res = lnav::command::parse_for_prompt(
987
                lnav_data.ld_exec_context, line, EXEC_HELP);
×
988
            auto byte_x = line_sf.column_to_byte_index(rc.tc_cursor.x);
×
989
            auto arg_res_opt = parse_res.arg_at(byte_x);
×
990
            if (arg_res_opt) {
×
991
                auto arg_pair = arg_res_opt.value();
×
992
                if (is_req
×
993
                    || rc.tc_popup_type != textinput_curses::popup_type_t::none)
×
994
                {
995
                    auto poss = prompt.get_cmd_parameter_completion(
996
                        *tc,
997
                        &EXEC_HELP,
998
                        arg_pair.aar_help,
999
                        arg_pair.aar_element.se_value);
×
1000
                    auto crange = std::make_from_tuple<line_range>(
×
1001
                        line_sf.byte_to_column_index(
×
1002
                            arg_pair.aar_element.se_origin.sf_begin,
×
1003
                            arg_pair.aar_element.se_origin.sf_end));
×
1004
                    rc.open_popup_for_completion(crange, poss);
×
1005
                    rc.tc_popup.set_title(arg_pair.aar_help->ht_name);
×
1006
                } else if (!line.empty()
×
1007
                           && arg_pair.aar_element.se_value.empty()
×
1008
                           && rc.is_cursor_at_end_of_line())
×
1009
                {
1010
                    rc.tc_suggestion = prompt.get_regex_suggestion(*tc, line);
×
1011
                } else {
1012
                    log_debug("not at end of line %d %d",
×
1013
                              arg_pair.aar_element.se_value.empty(),
1014
                              rc.is_cursor_at_end_of_line());
1015
                    rc.tc_suggestion.clear();
×
1016
                }
1017
            }
1018
        }
1019
    }
1020
}
1021

1022
void
1023
rl_change(textinput_curses& rc)
×
1024
{
1025
    static auto& prompt = lnav::prompt::get();
1026
    auto* tc = get_textview_for_mode(lnav_data.ld_mode);
×
1027

1028
    rc.tc_suggestion.clear();
×
1029
    tc->clear_preview();
×
1030
    lnav_data.ld_user_message_source.clear();
×
1031

1032
    log_debug("rl_change");
×
1033

1034
    if (prompt.p_editor.tc_mode == textinput_curses::mode_t::show_help) {
×
1035
        return;
×
1036
    }
1037

1038
    if (rc.tc_popup_type == textinput_curses::popup_type_t::history
×
1039
        && !prompt.p_replace_from_history)
×
1040
    {
1041
        rc.tc_on_history_search(rc);
×
1042
        return;
×
1043
    }
1044

1045
    switch (lnav_data.ld_mode) {
×
1046
        case ln_mode_t::SEARCH:
×
1047
        case ln_mode_t::SEARCH_FILES:
1048
        case ln_mode_t::SEARCH_FILTERS:
1049
        case ln_mode_t::SEARCH_SPECTRO_DETAILS: {
1050
            rl_search_change(rc, false);
×
1051
            break;
×
1052
        }
1053
        case ln_mode_t::SQL: {
×
1054
            rl_sql_change(rc, false);
×
1055
            break;
×
1056
        }
1057
        case ln_mode_t::COMMAND: {
×
1058
            rl_cmd_change(rc, false);
×
1059
            break;
×
1060
        }
1061
        case ln_mode_t::EXEC: {
×
1062
            rl_exec_change(rc, false);
×
1063
            break;
×
1064
        }
1065
        default:
×
1066
            break;
×
1067
    }
1068
}
1069

1070
static void
1071
rl_search_internal(textinput_curses& rc, ln_mode_t mode, bool complete = false)
×
1072
{
1073
    static const intern_string_t SRC = intern_string::lookup("prompt");
1074
    static auto& prompt = lnav::prompt::get();
1075

1076
    auto* tc = get_textview_for_mode(mode);
×
1077
    std::string term_val;
×
1078
    std::string name;
×
1079

1080
    tc->clear_preview();
×
1081
    tc->reload_data();
×
1082
    lnav_data.ld_user_message_source.clear();
×
1083

1084
    switch (mode) {
×
1085
        case ln_mode_t::SEARCH:
×
1086
        case ln_mode_t::SEARCH_FILTERS:
1087
        case ln_mode_t::SEARCH_FILES:
1088
        case ln_mode_t::SEARCH_SPECTRO_DETAILS:
1089
            name = "$search";
×
1090
            clear_preview();
×
1091
            break;
×
1092

1093
        case ln_mode_t::CAPTURE:
×
1094
            require(0);
×
1095
            name = "$capture";
1096
            break;
1097

1098
        case ln_mode_t::COMMAND: {
×
1099
            auto& ec = lnav_data.ld_exec_context;
×
1100
            ec.ec_dry_run = true;
×
1101

1102
            lnav_data.ld_preview_generation += 1;
×
1103
            clear_preview();
×
1104
            auto src_guard = ec.enter_source(
1105
                SRC, 1, fmt::format(FMT_STRING(":{}"), rc.get_content()));
×
1106
            readline_lnav_highlighter(ec.ec_source.back().s_content, -1);
×
1107
            ec.ec_source.back().s_content.with_attr_for_all(
×
1108
                VC_ROLE.value(role_t::VCR_QUOTED_CODE));
×
1109
            auto result = execute_command(ec, rc.get_content());
×
1110

1111
            if (result.isOk()) {
×
1112
                auto msg = result.unwrap();
×
1113

1114
                if (msg.empty()) {
×
1115
                    lnav_data.ld_bottom_source.set_prompt(
×
1116
                        prompt.p_editor.tc_height > 1
×
1117
                            ? LNAV_MULTILINE_CMD_PROMPT
1118
                            : LNAV_CMD_PROMPT);
1119
                    lnav_data.ld_bottom_source.grep_error("");
×
1120
                } else {
1121
                    lnav_data.ld_bottom_source.set_prompt(msg);
×
1122
                    lnav_data.ld_bottom_source.grep_error("");
×
1123
                }
1124
            } else {
×
1125
                lnav_data.ld_bottom_source.set_prompt("");
×
1126
                lnav_data.ld_bottom_source.grep_error(
×
1127
                    result.unwrapErr().um_message.get_string());
×
1128
            }
1129
            lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
1130
            lnav_data.ld_preview_view[0].reload_data();
×
1131

1132
            ec.ec_dry_run = false;
×
1133
            return;
×
1134
        }
1135

1136
        case ln_mode_t::SQL: {
×
1137
            term_val = trim(rc.get_content());
×
1138

1139
            if (!term_val.empty() && term_val[0] == '.') {
×
1140
                lnav_data.ld_bottom_source.grep_error("");
×
1141
                lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
1142
            } else if (lnav::sql::is_prql(term_val)) {
×
1143
                std::string alt_msg;
×
1144

1145
                lnav_data.ld_doc_source.replace_with(PRQL_HELP);
×
1146
                lnav_data.ld_example_source.replace_with(
×
1147
                    format_sql_example(PRQL_EXAMPLE));
×
1148
                lnav_data.ld_db_preview_source[0].clear();
×
1149
                lnav_data.ld_db_preview_source[1].clear();
×
1150

1151
                auto orig_prql_stmt = attr_line_t(term_val);
×
1152
                orig_prql_stmt.rtrim("| \r\n\t");
×
1153
                lnav::sql::annotate_prql_statement(orig_prql_stmt);
×
1154

1155
                auto cursor_x = rc.get_cursor_offset();
×
1156
                if (cursor_x >= orig_prql_stmt.get_string().length()) {
×
1157
                    cursor_x = orig_prql_stmt.get_string().length() - 1;
×
1158
                }
1159

1160
                auto curr_stage_iter
1161
                    = find_string_attr_containing(orig_prql_stmt.get_attrs(),
×
1162
                                                  &lnav::sql::PRQL_STAGE_ATTR,
1163
                                                  cursor_x);
1164
                ensure(curr_stage_iter != orig_prql_stmt.al_attrs.end());
×
1165
                auto curr_stage_prql = orig_prql_stmt.subline(
1166
                    0, curr_stage_iter->sa_range.lr_end);
×
1167
                for (auto riter = curr_stage_prql.get_attrs().rbegin();
×
1168
                     riter != curr_stage_prql.get_attrs().rend();
×
1169
                     ++riter)
×
1170
                {
1171
                    if (riter->sa_type != &lnav::sql::PRQL_STAGE_ATTR
×
1172
                        || riter->sa_range.lr_start == 0
×
1173
                        || riter->sa_range.empty())
×
1174
                    {
1175
                        continue;
×
1176
                    }
1177
                    auto take10k = std::string("\ntake 10000 ");
×
1178
                    if (curr_stage_prql.al_string[riter->sa_range.lr_start]
×
1179
                        != '|')
×
1180
                    {
1181
                        take10k.append("\n ");
×
1182
                    }
1183
                    curr_stage_prql.insert(riter->sa_range.lr_start, take10k);
×
1184
                }
1185
                curr_stage_prql.rtrim();
×
1186
                curr_stage_prql.append(" | take 5");
×
1187
                log_debug("preview prql: %s",
×
1188
                          curr_stage_prql.get_string().c_str());
1189

1190
                size_t curr_stage_index = 0;
×
1191
                if (curr_stage_iter->sa_range.lr_start > 0) {
×
1192
                    auto prev_stage_iter = find_string_attr_containing(
×
1193
                        orig_prql_stmt.get_attrs(),
×
1194
                        &lnav::sql::PRQL_STAGE_ATTR,
1195
                        curr_stage_iter->sa_range.lr_start - 1);
×
1196
                    auto prev_stage_prql = orig_prql_stmt.subline(
1197
                        0, prev_stage_iter->sa_range.lr_end);
×
1198
                    for (auto riter = prev_stage_prql.get_attrs().rbegin();
×
1199
                         riter != prev_stage_prql.get_attrs().rend();
×
1200
                         ++riter)
×
1201
                    {
1202
                        if (riter->sa_type != &lnav::sql::PRQL_STAGE_ATTR
×
1203
                            || riter->sa_range.lr_start == 0
×
1204
                            || riter->sa_range.empty())
×
1205
                        {
1206
                            continue;
×
1207
                        }
1208
                        auto take10k = std::string("\ntake 10000 ");
×
1209
                        if (prev_stage_prql.al_string[riter->sa_range.lr_start]
×
1210
                            != '|')
×
1211
                        {
1212
                            take10k.append("\n ");
×
1213
                        }
1214
                        prev_stage_prql.insert(riter->sa_range.lr_start,
×
1215
                                               take10k);
1216
                    }
1217
                    prev_stage_prql.append("\ntake 5");
×
1218

1219
                    curr_stage_index = 1;
×
1220
                    auto src_guard = lnav_data.ld_exec_context.enter_source(
1221
                        SRC, 1, prev_stage_prql.get_string());
×
1222
                    auto db_guard = lnav_data.ld_exec_context.enter_db_source(
1223
                        &lnav_data.ld_db_preview_source[0]);
×
1224
                    auto exec_res = execute_sql(lnav_data.ld_exec_context,
1225
                                                prev_stage_prql.get_string(),
×
1226
                                                alt_msg);
×
1227
                    lnav_data.ld_preview_status_source[0]
1228
                        .get_description()
×
1229
                        .set_value("Result for query: %s",
×
1230
                                   prev_stage_prql.get_string().c_str());
×
1231
                    lnav_data.ld_status[LNS_PREVIEW0].set_needs_update();
×
1232
                    if (exec_res.isOk()) {
×
1233
                        lnav_data.ld_preview_view[0].set_sub_source(
×
1234
                            &lnav_data.ld_db_preview_source[0]);
1235
                        lnav_data.ld_preview_view[0].set_overlay_source(
×
1236
                            &lnav_data.ld_db_preview_overlay_source[0]);
1237
                    } else {
1238
                        lnav_data.ld_preview_source[0].replace_with(
×
1239
                            exec_res.unwrapErr().to_attr_line());
×
1240
                        lnav_data.ld_preview_view[0].set_sub_source(
×
1241
                            &lnav_data.ld_preview_source[0]);
1242
                        lnav_data.ld_preview_view[0].set_overlay_source(
×
1243
                            nullptr);
1244
                    }
1245
                }
1246

1247
                auto src_guard = lnav_data.ld_exec_context.enter_source(
1248
                    SRC, 1, curr_stage_prql.get_string());
×
1249
                auto db_guard = lnav_data.ld_exec_context.enter_db_source(
1250
                    &lnav_data.ld_db_preview_source[curr_stage_index]);
×
1251
                auto exec_res = execute_sql(lnav_data.ld_exec_context,
1252
                                            curr_stage_prql.get_string(),
×
1253
                                            alt_msg);
×
1254
                auto err = exec_res.isErr()
×
1255
                    ? exec_res.unwrapErr()
×
1256
                    : lnav::console::user_message::ok({});
×
1257
                if (exec_res.isErr()) {
×
1258
                    lnav_data.ld_bottom_source.grep_error(
×
1259
                        err.um_reason.get_string());
×
1260
                    lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
1261

1262
                    curr_stage_prql.erase(curr_stage_prql.get_string().length()
×
1263
                                          - 8);
1264
                    auto near = curr_stage_prql.get_string().length() - 1;
×
1265
                    while (near > 0) {
×
1266
                        auto paren_iter = rfind_string_attr_if(
×
1267
                            curr_stage_prql.get_attrs(),
×
1268
                            near,
1269
                            [](const string_attr& sa) {
×
1270
                                return sa.sa_type
×
1271
                                    == &lnav::sql::PRQL_UNTERMINATED_PAREN_ATTR;
×
1272
                            });
1273

1274
                        if (paren_iter == curr_stage_prql.get_attrs().end()) {
×
1275
                            break;
×
1276
                        }
1277
                        switch (
×
1278
                            curr_stage_prql
1279
                                .get_string()[paren_iter->sa_range.lr_start])
×
1280
                        {
1281
                            case '(':
×
1282
                                curr_stage_prql.append(")");
×
1283
                                break;
×
1284
                            case '{':
×
1285
                                curr_stage_prql.append("}");
×
1286
                                break;
×
1287
                        }
1288
                        near = paren_iter->sa_range.lr_start - 1;
×
1289
                    }
1290

1291
                    curr_stage_prql.append("\ntake 5");
×
1292
                    auto exec_termed_res
1293
                        = execute_sql(lnav_data.ld_exec_context,
1294
                                      curr_stage_prql.get_string(),
×
1295
                                      alt_msg);
×
1296
                    if (exec_termed_res.isErr()) {
×
1297
                        err = exec_termed_res.unwrapErr();
×
1298
                    }
1299
                } else {
×
1300
                    lnav_data.ld_bottom_source.grep_error("");
×
1301
                    lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
1302
                }
1303

1304
                rl_sql_help(rc);
×
1305

1306
                lnav_data.ld_preview_status_source[curr_stage_index]
1307
                    .get_description()
×
1308
                    .set_value("Result for query: %s",
×
1309
                               curr_stage_prql.get_string().c_str());
×
1310
                lnav_data.ld_status[LNS_PREVIEW0].set_needs_update();
×
1311
                if (!lnav_data.ld_db_preview_source[curr_stage_index]
×
1312
                         .dls_headers.empty())
×
1313
                {
1314
                    if (curr_stage_index == 0) {
1315
#if 0
1316
                        for (const auto& hdr :
1317
                             lnav_data.ld_db_preview_source[curr_stage_index]
1318
                                 .dls_headers)
1319
                        {
1320
                            rc->add_possibility(
1321
                                ln_mode_t::SQL,
1322
                                "prql-expr",
1323
                                lnav::prql::quote_ident(hdr.hm_name));
1324
                        }
1325
#endif
1326
                    }
1327

1328
                    lnav_data.ld_preview_view[curr_stage_index].set_sub_source(
×
1329
                        &lnav_data.ld_db_preview_source[curr_stage_index]);
1330
                    lnav_data.ld_preview_view[curr_stage_index]
1331
                        .set_overlay_source(
×
1332
                            &lnav_data.ld_db_preview_overlay_source
1333
                                 [curr_stage_index]);
1334
                } else if (exec_res.isErr()) {
×
1335
                    lnav_data.ld_preview_source[curr_stage_index].replace_with(
×
1336
                        err.to_attr_line());
×
1337
                    lnav_data.ld_preview_view[curr_stage_index].set_sub_source(
×
1338
                        &lnav_data.ld_preview_source[curr_stage_index]);
1339
                    lnav_data.ld_preview_view[curr_stage_index]
1340
                        .set_overlay_source(nullptr);
×
1341
                }
1342
                return;
×
1343
            }
1344

1345
            term_val += ";";
×
1346
            if (startswith(term_val, ".")) {
×
1347
            } else if (!sqlite3_complete(term_val.c_str())) {
×
1348
                lnav_data.ld_bottom_source.grep_error(
×
1349
                    "SQL error: incomplete statement");
1350
                lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
1351
            } else {
1352
                auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
×
1353
                int retcode;
1354

1355
                retcode = sqlite3_prepare_v2(lnav_data.ld_db,
×
1356
                                             rc.get_content().c_str(),
×
1357
                                             -1,
1358
                                             stmt.out(),
1359
                                             nullptr);
1360
                if (retcode != SQLITE_OK) {
×
1361
                    const char* errmsg = sqlite3_errmsg(lnav_data.ld_db);
×
1362

1363
                    lnav_data.ld_bottom_source.grep_error(
×
1364
                        fmt::format(FMT_STRING("SQL error: {}"), errmsg));
×
1365

1366
#if defined(HAVE_SQLITE3_ERROR_OFFSET)
1367
                    {
1368
                        auto erroff = sqlite3_error_offset(lnav_data.ld_db);
×
1369

1370
                        if (erroff >= 0) {
×
1371
                            auto mark = rc.get_point_for_offset(erroff);
×
1372
                            auto um
1373
                                = lnav::console::user_message::error(errmsg);
×
1374

1375
                            rc.add_mark(mark, um);
×
1376
                        }
1377
                    }
1378
#endif
1379
                } else {
1380
                    lnav_data.ld_bottom_source.grep_error("");
×
1381
                }
1382
                lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
1383
            }
1384

1385
            if (!rl_sql_help(rc)) {
×
1386
                rl_set_help();
×
1387
            }
1388
            return;
×
1389
        }
1390

1391
        case ln_mode_t::BREADCRUMBS:
×
1392
        case ln_mode_t::PAGING:
1393
        case ln_mode_t::FILTER:
1394
        case ln_mode_t::FILES:
1395
        case ln_mode_t::FILE_DETAILS:
1396
        case ln_mode_t::EXEC:
1397
        case ln_mode_t::USER:
1398
        case ln_mode_t::SPECTRO_DETAILS:
1399
        case ln_mode_t::BUSY:
1400
            return;
×
1401
    }
1402

1403
    if (!complete) {
×
1404
        tc->set_selection(lnav_data.ld_search_start_line);
×
1405
    }
1406
    tc->execute_search(rc.get_content());
×
1407
}
1408

1409
void
1410
rl_search(textinput_curses& rc)
×
1411
{
1412
    auto* tc = get_textview_for_mode(lnav_data.ld_mode);
×
1413

1414
    rl_search_internal(rc, lnav_data.ld_mode);
×
1415
    tc->set_follow_search_for(0, {});
×
1416
}
1417

1418
void
1419
lnav_rl_abort(textinput_curses& rc)
×
1420
{
1421
    auto* tc = get_textview_for_mode(lnav_data.ld_mode);
×
1422

1423
    lnav_data.ld_bottom_source.set_prompt("");
×
1424
    lnav_data.ld_example_source.clear();
×
1425
    lnav_data.ld_doc_source.clear();
×
1426
    clear_preview();
×
1427
    tc->clear_preview();
×
1428

1429
    std::vector<lnav::console::user_message> errors;
×
1430
    lnav_config = rollback_lnav_config;
×
1431
    reload_config(errors);
×
1432

1433
    lnav_data.ld_bottom_source.grep_error("");
×
1434
    lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
1435
    switch (lnav_data.ld_mode) {
×
1436
        case ln_mode_t::SEARCH:
×
1437
            tc->set_selection(lnav_data.ld_search_start_line);
×
1438
            tc->revert_search();
×
1439
            break;
×
1440
        case ln_mode_t::SQL:
×
1441
            tc->reload_data();
×
1442
            break;
×
1443
        default:
×
1444
            break;
×
1445
    }
1446
    rc.clear_inactive_value();
×
1447

1448
    set_view_mode(ln_mode_t::PAGING);
×
1449
}
1450

1451
void
1452
rl_callback(textinput_curses& rc)
×
1453
{
1454
    static const intern_string_t SRC = intern_string::lookup("prompt");
1455
    static auto& prompt = lnav::prompt::get();
1456
    auto is_alt = prompt.p_alt_mode;
×
1457

1458
    auto* tc = get_textview_for_mode(lnav_data.ld_mode);
×
1459
    auto& ec = lnav_data.ld_exec_context;
×
1460
    std::string alt_msg;
×
1461

1462
    lnav_data.ld_bottom_source.set_prompt("");
×
1463
    lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
1464
    lnav_data.ld_doc_source.clear();
×
1465
    lnav_data.ld_example_source.clear();
×
1466
    clear_preview();
×
1467
    tc->clear_preview();
×
1468
    layout_views();
×
1469

1470
    auto new_mode = ln_mode_t::PAGING;
×
1471

1472
    switch (lnav_data.ld_mode) {
×
1473
        case ln_mode_t::SEARCH_FILTERS:
×
1474
            new_mode = ln_mode_t::FILTER;
×
1475
            break;
×
1476
        case ln_mode_t::SEARCH_FILES:
×
1477
            new_mode = ln_mode_t::FILES;
×
1478
            break;
×
1479
        case ln_mode_t::SEARCH_SPECTRO_DETAILS:
×
1480
            new_mode = ln_mode_t::SPECTRO_DETAILS;
×
1481
            break;
×
1482
        default:
×
1483
            break;
×
1484
    }
1485

1486
    auto old_mode = lnav_data.ld_mode;
×
1487
    set_view_mode(new_mode);
×
1488
    switch (old_mode) {
×
1489
        case ln_mode_t::BREADCRUMBS:
×
1490
        case ln_mode_t::PAGING:
1491
        case ln_mode_t::FILTER:
1492
        case ln_mode_t::FILES:
1493
        case ln_mode_t::FILE_DETAILS:
1494
        case ln_mode_t::SPECTRO_DETAILS:
1495
        case ln_mode_t::BUSY:
1496
            require(0);
×
1497
            break;
1498

1499
        case ln_mode_t::COMMAND: {
×
1500
            rc.clear_alt_value();
×
1501
            auto cmdline = rc.get_content();
×
1502
            auto src_guard = lnav_data.ld_exec_context.enter_source(
1503
                SRC, 1, fmt::format(FMT_STRING(":{}"), cmdline));
×
1504
            readline_lnav_highlighter(ec.ec_source.back().s_content, -1);
×
1505
            ec.ec_source.back().s_content.with_attr_for_all(
×
1506
                VC_ROLE.value(role_t::VCR_QUOTED_CODE));
×
1507
            auto hist_guard = prompt.p_cmd_history.start_operation(cmdline);
×
1508
            auto exec_res = execute_command(ec, cmdline);
×
1509
            if (exec_res.isOk()) {
×
1510
                rc.set_inactive_value(exec_res.unwrap());
×
1511
            } else {
1512
                auto um = exec_res.unwrapErr();
×
1513

1514
                hist_guard.og_status = log_level_t::LEVEL_ERROR;
×
1515
                lnav_data.ld_user_message_source.replace_with(
×
1516
                    um.to_attr_line().rtrim());
×
1517
                lnav_data.ld_user_message_view.reload_data();
×
1518
                lnav_data.ld_user_message_expiration
1519
                    = std::chrono::steady_clock::now() + 20s;
×
1520
                rc.clear_inactive_value();
×
1521
            }
1522
            ec.ec_source.back().s_content.clear();
×
1523
            break;
×
1524
        }
1525

1526
        case ln_mode_t::USER:
×
1527
            rc.clear_alt_value();
×
1528
            ec.ec_local_vars.top()["value"] = rc.get_content();
×
1529
            rc.clear_inactive_value();
×
1530
            break;
×
1531

1532
        case ln_mode_t::SEARCH:
×
1533
        case ln_mode_t::SEARCH_FILTERS:
1534
        case ln_mode_t::SEARCH_FILES:
1535
        case ln_mode_t::SEARCH_SPECTRO_DETAILS:
1536
        case ln_mode_t::CAPTURE: {
1537
            log_debug("search here!");
×
1538
            auto cmdline = rc.get_content();
×
1539
            rl_search_internal(rc, old_mode, true);
×
1540
            if (!cmdline.empty()) {
×
1541
                auto hist_guard
1542
                    = prompt.p_search_history.start_operation(cmdline);
×
1543
                auto& bm = tc->get_bookmarks();
×
1544
                const auto& bv = bm[&textview_curses::BM_SEARCH];
×
1545
                auto vl = is_alt ? bv.prev(tc->get_selection().value_or(0_vl))
×
1546
                                 : bv.next(tc->get_top());
×
1547

1548
                if (vl) {
×
1549
                    tc->set_selection(vl.value());
×
1550
                } else {
1551
                    tc->set_follow_search_for(2000, [tc, is_alt, &bm]() {
×
1552
                        if (bm[&textview_curses::BM_SEARCH].empty()) {
×
1553
                            return false;
×
1554
                        }
1555

1556
                        if (is_alt && tc->is_searching()) {
×
1557
                            return false;
×
1558
                        }
1559

1560
                        std::optional<vis_line_t> first_hit;
×
1561

1562
                        if (is_alt) {
×
1563
                            first_hit = bm[&textview_curses::BM_SEARCH].prev(
×
1564
                                tc->get_selection().value_or(0_vl));
×
1565
                        } else {
1566
                            first_hit = bm[&textview_curses::BM_SEARCH].next(
×
1567
                                vis_line_t(tc->get_top() - 1));
×
1568
                        }
1569
                        if (first_hit) {
×
1570
                            auto first_hit_vl = first_hit.value();
×
1571
                            if (tc->is_selectable()) {
×
1572
                                tc->set_selection(first_hit_vl);
×
1573
                            } else {
1574
                                if (first_hit_vl > 0_vl) {
×
1575
                                    --first_hit_vl;
×
1576
                                }
1577
                                tc->set_top(first_hit_vl);
×
1578
                            }
1579
                        }
1580

1581
                        return true;
×
1582
                    });
1583
                }
1584
                rc.set_inactive_value(
×
1585
                    attr_line_t("search: ").append(rc.get_content()));
×
1586
                rc.set_alt_value(HELP_MSG_2(
×
1587
                    n, N, "to move forward/backward through search results"));
1588
            }
1589
            break;
×
1590
        }
1591

1592
        case ln_mode_t::SQL: {
×
1593
            auto sql_str = rc.get_content();
×
1594

1595
            if (sql_str.empty()) {
×
1596
                return;
×
1597
            }
1598
            auto src_guard = lnav_data.ld_exec_context.enter_source(
1599
                SRC, 1, fmt::format(FMT_STRING(";{}"), sql_str));
×
1600
            readline_lnav_highlighter(ec.ec_source.back().s_content, -1);
×
1601
            ec.ec_source.back().s_content.with_attr_for_all(
×
1602
                VC_ROLE.value(role_t::VCR_QUOTED_CODE));
×
1603

1604
            auto hist_guard = prompt.p_sql_history.start_operation(sql_str);
×
1605
            auto& dls = lnav_data.ld_db_row_source;
×
1606
            auto before_dls_gen = dls.dls_generation;
×
1607
            auto result = execute_sql(ec, sql_str, alt_msg);
×
1608
            attr_line_t res;
×
1609

1610
            if (result.isOk()) {
×
1611
                auto msg = result.unwrap();
×
1612

1613
                if (!msg.empty()) {
×
1614
                    res = lnav::console::user_message::ok(
×
1615
                              attr_line_t("SQL Result: ")
×
1616
                                  .append(
×
1617
                                      attr_line_t::from_ansi_str(msg.c_str())))
×
1618
                              .to_attr_line();
×
1619
                    if (before_dls_gen != dls.dls_generation
×
1620
                        && dls.dls_row_cursors.size() > 1)
×
1621
                    {
1622
                        ensure_view(&lnav_data.ld_views[LNV_DB]);
×
1623
                    }
1624
                }
1625
            } else {
×
1626
                hist_guard.og_status = log_level_t::LEVEL_ERROR;
×
1627
                auto um = result.unwrapErr();
×
1628
                lnav_data.ld_user_message_source.replace_with(
×
1629
                    um.to_attr_line().rtrim());
×
1630
                lnav_data.ld_user_message_view.reload_data();
×
1631
                lnav_data.ld_user_message_expiration
1632
                    = std::chrono::steady_clock::now() + 20s;
×
1633
            }
1634
            ec.ec_source.back().s_content.clear();
×
1635

1636
            rc.set_inactive_value(res);
×
1637
            rc.set_alt_value(alt_msg);
×
1638
            break;
×
1639
        }
1640

1641
        case ln_mode_t::EXEC: {
×
1642
            std::error_code errc;
×
1643
            std::filesystem::create_directories(lnav::paths::workdir(), errc);
×
1644
            auto open_temp_res = lnav::filesystem::open_temp_file(
1645
                lnav::paths::workdir() / "exec.XXXXXX");
×
1646

1647
            if (open_temp_res.isErr()) {
×
1648
                rc.set_inactive_value(fmt::format(
×
1649
                    FMT_STRING("Unable to open temporary output file: {}"),
×
1650
                    open_temp_res.unwrapErr()));
×
1651
            } else {
1652
                char desc[256], timestamp[32];
1653
                time_t current_time = time(nullptr);
×
1654
                const auto path_and_args = rc.get_content();
×
1655
                auto tmp_pair = open_temp_res.unwrap();
×
1656
                auto fd_copy = tmp_pair.second.dup();
×
1657
                auto tf = text_format_t::TF_UNKNOWN;
×
1658
                auto& dls = lnav_data.ld_db_row_source;
×
1659
                auto before_dls_gen = dls.dls_generation;
×
1660

1661
                {
1662
                    exec_context::output_guard og(
1663
                        ec,
1664
                        "tmp",
1665
                        std::make_pair(fdopen(tmp_pair.second.release(), "w"),
×
1666
                                       fclose));
×
1667
                    auto src_guard = lnav_data.ld_exec_context.enter_source(
1668
                        SRC, 1, fmt::format(FMT_STRING("|{}"), path_and_args));
×
1669
                    auto hist_guard = prompt.p_script_history.start_operation(
×
1670
                        path_and_args);
×
1671
                    auto exec_res = execute_file(ec, path_and_args);
×
1672
                    if (exec_res.isOk()) {
×
1673
                        rc.set_inactive_value(exec_res.unwrap());
×
1674
                        tf = ec.ec_output_stack.back().od_format;
×
1675
                    } else {
1676
                        auto um = exec_res.unwrapErr();
×
1677

1678
                        hist_guard.og_status = log_level_t::LEVEL_ERROR;
×
1679
                        lnav_data.ld_user_message_source.replace_with(
×
1680
                            um.to_attr_line().rtrim());
×
1681
                        lnav_data.ld_user_message_view.reload_data();
×
1682
                        lnav_data.ld_user_message_expiration
1683
                            = std::chrono::steady_clock::now() + 20s;
×
1684
                        rc.clear_inactive_value();
×
1685
                    }
1686
                }
1687

1688
                tm current_tm;
1689
                struct stat st;
1690

1691
                if (fstat(fd_copy, &st) != -1 && st.st_size > 0) {
×
1692
                    strftime(timestamp,
×
1693
                             sizeof(timestamp),
1694
                             "%a %b %d %H:%M:%S %Z",
1695
                             localtime_r(&current_time, &current_tm));
×
1696
                    snprintf(desc,
×
1697
                             sizeof(desc),
1698
                             "Output of %s (%s)",
1699
                             path_and_args.c_str(),
1700
                             timestamp);
1701
                    lnav_data.ld_active_files.fc_file_names[tmp_pair.first]
×
1702
                        .with_filename(desc)
×
1703
                        .with_include_in_session(false)
×
1704
                        .with_detect_format(false)
×
1705
                        .with_text_format(tf)
×
1706
                        .with_init_location(0_vl);
×
1707
                    lnav_data.ld_files_to_front.emplace_back(desc);
×
1708

1709
                    rc.set_alt_value(HELP_MSG_1(X, "to close the file"));
×
1710
                } else if (before_dls_gen != dls.dls_generation
×
1711
                           && dls.dls_row_cursors.size() > 1)
×
1712
                {
1713
                    ensure_view(&lnav_data.ld_views[LNV_DB]);
×
1714
                }
1715
            }
1716
            break;
×
1717
        }
1718
    }
1719
}
1720

1721
void
1722
rl_completion_request(textinput_curses& rc)
×
1723
{
1724
    switch (lnav_data.ld_mode) {
×
1725
        case ln_mode_t::SEARCH:
×
1726
        case ln_mode_t::SEARCH_FILES:
1727
        case ln_mode_t::SEARCH_FILTERS:
1728
        case ln_mode_t::SEARCH_SPECTRO_DETAILS: {
1729
            rl_search_change(rc, true);
×
1730
            break;
×
1731
        }
1732
        case ln_mode_t::COMMAND: {
×
1733
            rl_cmd_change(rc, true);
×
1734
            break;
×
1735
        }
1736
        case ln_mode_t::SQL: {
×
1737
            rl_sql_change(rc, true);
×
1738
            break;
×
1739
        }
1740
        case ln_mode_t::EXEC: {
×
1741
            rl_exec_change(rc, true);
×
1742
            break;
×
1743
        }
1744

1745
        default:
×
1746
            break;
×
1747
    }
1748
}
1749

1750
void
1751
rl_focus(textinput_curses& rc)
×
1752
{
1753
    auto fos = (field_overlay_source*) lnav_data.ld_views[LNV_LOG]
1754
                   .get_overlay_source();
×
1755

1756
    fos->fos_contexts.emplace("", false, true, true);
×
1757

1758
    switch (lnav_data.ld_mode) {
×
1759
        case ln_mode_t::SEARCH:
×
1760
        case ln_mode_t::SEARCH_FILES:
1761
        case ln_mode_t::SEARCH_FILTERS:
1762
        case ln_mode_t::SEARCH_SPECTRO_DETAILS: {
1763
            auto* tc = get_textview_for_mode(lnav_data.ld_mode);
×
1764
            tc->save_current_search();
×
1765
            tc->execute_search("");
×
1766
            break;
×
1767
        }
1768
        default:
×
1769
            break;
×
1770
    }
1771
}
1772

1773
void
1774
rl_blur(textinput_curses& rc)
×
1775
{
1776
    auto* fos = (field_overlay_source*) lnav_data.ld_views[LNV_LOG]
1777
                    .get_overlay_source();
×
1778

1779
    fos->fos_contexts.pop();
×
1780
    ensure(!fos->fos_contexts.empty());
×
1781
    for (auto& tc : lnav_data.ld_views) {
×
1782
        tc.set_sync_selection_and_top(false);
×
1783
    }
1784
    lnav_data.ld_view_stack.top() |
×
1785
        [](auto* tc) { tc->set_overlay_selection(std::nullopt); };
×
1786
    lnav_data.ld_preview_generation += 1;
×
1787
    lnav_data.ld_bottom_source.grep_error("");
×
1788
    lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
1789
}
1790

1791
readline_context::split_result_t
1792
prql_splitter(const attr_line_t& stmt)
×
1793
{
1794
    readline_context::split_result_t retval;
×
1795
    readline_context::stage st;
×
1796

1797
    for (const auto& attr : stmt.get_attrs()) {
×
1798
        if (attr.sa_type == &lnav::sql::PRQL_STAGE_ATTR) {
×
1799
        } else if (attr.sa_type == &lnav::sql::PRQL_PIPE_ATTR) {
×
1800
            retval.sr_stages.emplace_back(st);
×
1801
            st.s_args.clear();
×
1802
        } else {
1803
            st.s_args.emplace_back(attr.sa_range);
×
1804
        }
1805
    }
1806
    if (!stmt.empty() && isspace(stmt.al_string.back())) {
×
1807
        st.s_args.emplace_back(stmt.length(), stmt.length());
×
1808
    }
1809
    retval.sr_stages.emplace_back(st);
×
1810

1811
    return retval;
×
1812
}
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