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

tstack / lnav / 22507085525-2793

27 Feb 2026 10:49PM UTC coverage: 68.948% (-0.02%) from 68.966%
22507085525-2793

push

github

tstack
fix mixup of O_CLOEXEC with FD_CLOEXEC

52007 of 75429 relevant lines covered (68.95%)

440500.48 hits per line

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

76.9
/src/lnav_config.cc
1
/**
2
 * Copyright (c) 2013, 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
 * @file lnav_config.cc
30
 */
31

32
#include <algorithm>
33
#include <chrono>
34
#include <filesystem>
35
#include <iostream>
36
#include <regex>
37
#include <stdexcept>
38

39
#include "lnav_config.hh"
40

41
#include <fcntl.h>
42
#include <fmt/format.h>
43
#include <glob.h>
44
#include <stdio.h>
45
#include <stdlib.h>
46
#include <string.h>
47
#include <sys/stat.h>
48
#include <unistd.h>
49

50
#include "base/auto_fd.hh"
51
#include "base/auto_mem.hh"
52
#include "base/auto_pid.hh"
53
#include "base/fs_util.hh"
54
#include "base/injector.bind.hh"
55
#include "base/injector.hh"
56
#include "base/lnav_log.hh"
57
#include "base/paths.hh"
58
#include "base/string_util.hh"
59
#include "bin2c.hh"
60
#include "command_executor.hh"
61
#include "config.h"
62
#include "default-config.h"
63
#include "log_level.hh"
64
#include "scn/scan.h"
65
#include "styling.hh"
66
#include "view_curses.hh"
67
#include "yajlpp/yajlpp.hh"
68
#include "yajlpp/yajlpp_def.hh"
69

70
using namespace std::chrono_literals;
71

72
static constexpr int MAX_CRASH_LOG_COUNT = 16;
73
static constexpr auto STDIN_CAPTURE_RETENTION = 24h;
74

75
static auto intern_lifetime = intern_string::get_table_lifetime();
76

77
struct _lnav_config lnav_config;
78
struct _lnav_config rollback_lnav_config;
79
static struct _lnav_config lnav_default_config;
80

81
std::map<intern_string_t, source_location> lnav_config_locations;
82

83
static auto a = injector::bind<archive_manager::config>::to_instance(
84
    +[] { return &lnav_config.lc_archive_manager; });
1,179✔
85

86
static auto dtc = injector::bind<date_time_scanner_ns::config>::to_instance(
87
    +[] { return &lnav_config.lc_log_date_time; });
1,179✔
88

89
static auto fvc = injector::bind<file_vtab::config>::to_instance(
90
    +[] { return &lnav_config.lc_file_vtab; });
1,179✔
91

92
static auto lc = injector::bind<lnav::logfile::config>::to_instance(
93
    +[] { return &lnav_config.lc_logfile; });
1,179✔
94

95
static auto p = injector::bind<lnav::piper::config>::to_instance(
96
    +[] { return &lnav_config.lc_piper; });
1,179✔
97

98
static auto tc = injector::bind<tailer::config>::to_instance(
99
    +[] { return &lnav_config.lc_tailer; });
1,179✔
100

101
static auto scc = injector::bind<sysclip::config>::to_instance(
102
    +[] { return &lnav_config.lc_sysclip; });
1,179✔
103

104
static auto oc = injector::bind<lnav::external_opener::config>::to_instance(
105
    +[] { return &lnav_config.lc_opener; });
1,179✔
106

107
static auto ee = injector::bind<lnav::external_editor::config>::to_instance(
108
    +[] { return &lnav_config.lc_external_editor; });
1,179✔
109

110
static auto uh = injector::bind<lnav::url_handler::config>::to_instance(
111
    +[] { return &lnav_config.lc_url_handlers; });
1,179✔
112

113
static auto lsc = injector::bind<logfile_sub_source_ns::config>::to_instance(
114
    +[] { return &lnav_config.lc_log_source; });
1,179✔
115

116
static auto annoc = injector::bind<lnav::log::annotate::config>::to_instance(
117
    +[] { return &lnav_config.lc_log_annotations; });
1,179✔
118

119
static auto tssc = injector::bind<top_status_source_cfg>::to_instance(
120
    +[] { return &lnav_config.lc_top_status_cfg; });
1,179✔
121

122
static auto ltc = injector::bind<lnav::textfile::config>::to_instance(
123
    +[] { return &lnav_config.lc_textfile; });
1,179✔
124

125
static auto appc = injector::bind<lnav::apps::config>::to_instance(
126
    +[] { return &lnav_config.lc_apps; });
1,179✔
127

128
lnav_config_listener::~lnav_config_listener()
31,901✔
129
{
130
    auto iter = std::find(listener_list().begin(), listener_list().end(), this);
31,901✔
131
    if (iter != listener_list().end()) {
31,901✔
132
        listener_list().erase(iter);
31,901✔
133
    }
134
}
31,901✔
135

136
bool
137
check_experimental(const char* feature_name)
56✔
138
{
139
    const char* env_value = getenv("LNAV_EXP");
56✔
140

141
    require(feature_name != nullptr);
56✔
142

143
    if (env_value && strcasestr(env_value, feature_name)) {
56✔
144
        return true;
×
145
    }
146

147
    return false;
56✔
148
}
149

150
void
151
ensure_dotlnav()
769✔
152
{
153
    static const char* const subdirs[] = {
154
        "",
155
        "configs",
156
        "configs/default",
157
        "configs/installed",
158
        "formats",
159
        "formats/default",
160
        "formats/installed",
161
        "staging",
162
        "crash",
163
    };
164

165
    std::error_code ec;
769✔
166
    auto path = lnav::paths::dotlnav();
769✔
167

168
    for (const auto* sub_path : subdirs) {
7,690✔
169
        auto full_path = path / sub_path;
6,921✔
170

171
        if (mkdir(full_path.c_str(), 0755) == -1 && errno != EEXIST) {
6,921✔
172
            log_error("unable to make directory: %s -- %s",
×
173
                      full_path.c_str(),
174
                      strerror(errno));
175
        }
176
    }
6,921✔
177

178
    auto crash_dir_path = path / "crash";
769✔
179
    lnav_log_crash_dir = strdup(crash_dir_path.c_str());
769✔
180

181
    {
182
        static_root_mem<glob_t, globfree> gl;
769✔
183
        auto crash_glob = path / "crash-*";
769✔
184

185
        if (glob(crash_glob.c_str(), GLOB_NOCHECK, nullptr, gl.inout()) == 0) {
769✔
186
            for (size_t lpc = 0; lpc < gl->gl_pathc; lpc++) {
1,538✔
187
                auto crash_file = std::filesystem::path(gl->gl_pathv[lpc]);
769✔
188

189
                std::filesystem::rename(
769✔
190
                    crash_file, crash_dir_path / crash_file.filename(), ec);
1,538✔
191
            }
769✔
192
        }
193
    }
769✔
194

195
    {
196
        static_root_mem<glob_t, globfree> gl;
769✔
197
        auto crash_glob = path / "crash/*";
769✔
198

199
        if (glob(crash_glob.c_str(), GLOB_NOCHECK, nullptr, gl.inout()) == 0) {
769✔
200
            for (int lpc = 0; lpc < ((int) gl->gl_pathc - MAX_CRASH_LOG_COUNT);
769✔
201
                 lpc++)
202
            {
203
                log_perror(remove(gl->gl_pathv[lpc]));
×
204
            }
205
        }
206
    }
769✔
207

208
    auto old_cap_path = path / "stdin-captures";
769✔
209
    if (std::filesystem::exists(old_cap_path, ec)) {
769✔
210
        static_root_mem<glob_t, globfree> gl;
211
        auto cap_glob = old_cap_path / "*";
×
212

213
        if (glob(cap_glob.c_str(), GLOB_NOCHECK, nullptr, gl.inout()) == 0) {
×
214
            auto old_time
215
                = std::chrono::system_clock::now() - STDIN_CAPTURE_RETENTION;
×
216

217
            for (size_t lpc = 0; lpc < gl->gl_pathc; lpc++) {
×
218
                struct stat st;
219

220
                if (stat(gl->gl_pathv[lpc], &st) == -1) {
×
221
                    continue;
×
222
                }
223

224
                if (std::chrono::system_clock::from_time_t(st.st_mtime)
×
225
                    > old_time)
×
226
                {
227
                    continue;
×
228
                }
229

230
                log_info("Removing old stdin capture: %s", gl->gl_pathv[lpc]);
×
231
                log_perror(remove(gl->gl_pathv[lpc]));
×
232
            }
233
        }
234

235
        if (std::filesystem::is_empty(old_cap_path, ec)) {
×
236
            log_info("removing old stdin-captures directory");
×
237
            std::filesystem::remove(old_cap_path, ec);
×
238
        }
239
    }
240
}
769✔
241

242
bool
243
install_from_git(const std::string& repo)
4✔
244
{
245
    static const std::regex repo_name_converter("[^\\w]");
4✔
246

247
    auto formats_path = lnav::paths::dotlnav() / "formats";
4✔
248
    auto configs_path = lnav::paths::dotlnav() / "configs";
4✔
249
    auto staging_path = lnav::paths::dotlnav() / "staging";
4✔
250
    auto local_name = std::regex_replace(repo, repo_name_converter, "_");
4✔
251

252
    auto local_formats_path = formats_path / local_name;
4✔
253
    auto local_configs_path = configs_path / local_name;
4✔
254
    auto local_staging_path = staging_path / local_name;
4✔
255

256
    auto fork_res = lnav::pid::from_fork();
4✔
257
    if (fork_res.isErr()) {
4✔
258
        fprintf(stderr,
×
259
                "error: cannot fork() to run git: %s\n",
260
                fork_res.unwrapErr().c_str());
×
261
        _exit(1);
×
262
    }
263

264
    auto git_cmd = fork_res.unwrap();
4✔
265
    if (git_cmd.in_child()) {
4✔
266
        if (std::filesystem::is_directory(local_formats_path)) {
×
267
            fmt::print("Updating format repo: {}\n", repo);
×
268
            log_perror(chdir(local_formats_path.c_str()));
×
269
            execlp("git", "git", "pull", nullptr);
×
270
        } else if (std::filesystem::is_directory(local_configs_path)) {
×
271
            fmt::print("Updating config repo: {}\n", repo);
×
272
            log_perror(chdir(local_configs_path.c_str()));
×
273
            execlp("git", "git", "pull", nullptr);
×
274
        } else {
275
            execlp("git",
×
276
                   "git",
277
                   "clone",
278
                   repo.c_str(),
279
                   local_staging_path.c_str(),
280
                   nullptr);
281
        }
282
        _exit(1);
×
283
    }
284

285
    auto finished_child = std::move(git_cmd).wait_for_child();
4✔
286
    if (!finished_child.was_normal_exit() || finished_child.exit_status() != 0)
4✔
287
    {
288
        return false;
×
289
    }
290

291
    if (std::filesystem::is_directory(local_formats_path)
4✔
292
        || std::filesystem::is_directory(local_configs_path))
4✔
293
    {
294
        return false;
×
295
    }
296
    if (!std::filesystem::is_directory(local_staging_path)) {
4✔
297
        auto um
298
            = lnav::console::user_message::error(
×
299
                  attr_line_t("failed to install git repo: ")
×
300
                      .append(lnav::roles::file(repo)))
×
301
                  .with_reason(
×
302
                      attr_line_t("git failed to create the local directory ")
×
303
                          .append(
×
304
                              lnav::roles::file(local_staging_path.string())))
×
305
                  .move();
×
306
        lnav::console::print(stderr, um);
×
307
        return false;
×
308
    }
309

310
    auto config_path = local_staging_path / "*";
4✔
311
    static_root_mem<glob_t, globfree> gl;
4✔
312
    int found_config_file = 0;
4✔
313
    int found_format_file = 0;
4✔
314
    int found_sql_file = 0;
4✔
315
    int found_lnav_file = 0;
4✔
316

317
    if (glob(config_path.c_str(), 0, nullptr, gl.inout()) == 0) {
4✔
318
        for (size_t lpc = 0; lpc < gl->gl_pathc; lpc++) {
69✔
319
            auto file_path = std::filesystem::path{gl->gl_pathv[lpc]};
65✔
320

321
            if (file_path.extension() == ".lnav") {
65✔
322
                found_lnav_file += 1;
×
323
                continue;
×
324
            }
325
            if (file_path.extension() == ".sql") {
65✔
326
                found_sql_file += 1;
×
327
                continue;
×
328
            }
329
            if (file_path.extension() != ".json") {
65✔
330
                found_sql_file += 1;
7✔
331
                continue;
7✔
332
            }
333

334
            auto file_type_result = detect_config_file_type(file_path);
58✔
335

336
            if (file_type_result.isErr()) {
58✔
337
                fprintf(stderr,
×
338
                        "error: %s\n",
339
                        file_type_result.unwrapErr().c_str());
×
340
                return false;
×
341
            }
342
            if (file_type_result.unwrap() == config_file_type::CONFIG) {
58✔
343
                found_config_file += 1;
×
344
            } else {
345
                found_format_file += 1;
58✔
346
            }
347
        }
65✔
348
    }
349

350
    if (found_config_file == 0 && found_format_file == 0 && found_sql_file == 0
4✔
351
        && found_lnav_file == 0)
×
352
    {
353
        auto um = lnav::console::user_message::error(
×
354
                      attr_line_t("invalid lnav repo: ")
×
355
                          .append(lnav::roles::file(repo)))
×
356
                      .with_reason("no .json, .sql, or .lnav files were found")
×
357
                      .move();
×
358
        lnav::console::print(stderr, um);
×
359
        return false;
×
360
    }
361

362
    auto dest_path = local_formats_path;
4✔
363
    attr_line_t notes;
4✔
364
    if (found_format_file > 0) {
4✔
365
        notes.append("found ")
4✔
366
            .append(lnav::roles::number(fmt::to_string(found_format_file)))
8✔
367
            .append(" format file(s)\n");
4✔
368
    }
369
    if (found_config_file > 0) {
4✔
370
        if (found_format_file == 0) {
×
371
            dest_path = local_configs_path;
×
372
        }
373
        notes.append("found ")
×
374
            .append(lnav::roles::number(fmt::to_string(found_config_file)))
×
375
            .append(" configuration file(s)\n");
×
376
    }
377
    if (found_sql_file > 0) {
4✔
378
        notes.append("found ")
3✔
379
            .append(lnav::roles::number(fmt::to_string(found_sql_file)))
6✔
380
            .append(" SQL file(s)\n");
3✔
381
    }
382
    if (found_lnav_file > 0) {
4✔
383
        notes.append("found ")
×
384
            .append(lnav::roles::number(fmt::to_string(found_lnav_file)))
×
385
            .append(" lnav-script file(s)\n");
×
386
    }
387
    rename(local_staging_path.c_str(), dest_path.c_str());
4✔
388
    auto um = lnav::console::user_message::ok(
4✔
389
                  attr_line_t("installed lnav repo at: ")
4✔
390
                      .append(lnav::roles::file(dest_path.string())))
8✔
391
                  .with_note(notes)
4✔
392
                  .move();
4✔
393
    lnav::console::print(stdout, um);
4✔
394

395
    return true;
4✔
396
}
4✔
397

398
bool
399
update_installs_from_git()
×
400
{
401
    static_root_mem<glob_t, globfree> gl;
402
    auto git_formats = lnav::paths::dotlnav() / "formats/*/.git";
×
403
    bool found = false, retval = true;
×
404

405
    if (glob(git_formats.c_str(), 0, nullptr, gl.inout()) == 0) {
×
406
        for (int lpc = 0; lpc < (int) gl->gl_pathc; lpc++) {
×
407
            auto git_dir
408
                = std::filesystem::path(gl->gl_pathv[lpc]).parent_path();
×
409

410
            printf("Updating formats in %s\n", git_dir.c_str());
×
411
            auto pull_cmd = fmt::format(FMT_STRING("cd '{}' && git pull"),
×
412
                                        git_dir.string());
×
413
            int ret = system(pull_cmd.c_str());
×
414
            if (ret == -1) {
×
415
                std::cerr << "Failed to spawn command "
416
                          << "\"" << pull_cmd << "\": " << strerror(errno)
×
417
                          << std::endl;
×
418
                retval = false;
×
419
            } else if (ret > 0) {
×
420
                std::cerr << "Command "
421
                          << "\"" << pull_cmd
422
                          << "\" failed: " << strerror(errno) << std::endl;
×
423
                retval = false;
×
424
            }
425
            found = true;
×
426
        }
427
    }
428

429
    if (!found) {
×
430
        printf(
×
431
            "No formats from git repositories found, "
432
            "use 'lnav -i extra' to install third-party formats\n");
433
    }
434

435
    return retval;
×
436
}
437

438
static int
439
read_repo_path(yajlpp_parse_context* ypc,
4✔
440
               const unsigned char* str,
441
               size_t len,
442
               yajl_string_props_t*)
443
{
444
    auto path = std::string((const char*) str, len);
8✔
445

446
    install_from_git(path.c_str());
4✔
447

448
    return 1;
4✔
449
}
4✔
450

451
static const struct json_path_container format_handlers = {
452
    json_path_handler("format-repos#", read_repo_path),
453
};
454

455
void
456
install_extra_formats()
1✔
457
{
458
    auto config_root = lnav::paths::dotlnav() / "remote-config";
1✔
459
    auto_fd fd;
1✔
460

461
    if (access(config_root.c_str(), R_OK) == 0) {
1✔
462
        printf("Updating lnav remote config repo...\n");
×
463
        auto pull_cmd = fmt::format(FMT_STRING("cd '{}' && git pull"),
×
464
                                    config_root.string());
×
465
        log_perror(system(pull_cmd.c_str()));
×
466
    } else {
×
467
        printf("Cloning lnav remote config repo...\n");
1✔
468
        auto clone_cmd = fmt::format(
469
            FMT_STRING(
3✔
470
                "git clone https://github.com/tstack/lnav-config.git {}"),
471
            config_root.string());
2✔
472
        log_perror(system(clone_cmd.c_str()));
1✔
473
    }
1✔
474

475
    auto config_json = config_root / "remote-config.json";
1✔
476
    if ((fd = lnav::filesystem::openp(config_json, O_RDONLY)) == -1) {
1✔
477
        perror("Unable to open remote-config.json");
×
478
    } else {
479
        yajlpp_parse_context ypc_config(
480
            intern_string::lookup(config_root.string()), &format_handlers);
1✔
481
        auto_mem<yajl_handle_t> jhandle(yajl_free);
1✔
482
        unsigned char buffer[4096];
483
        ssize_t rc;
484

485
        jhandle = yajl_alloc(&ypc_config.ypc_callbacks, nullptr, &ypc_config);
1✔
486
        yajl_config(jhandle, yajl_allow_comments, 1);
1✔
487
        while ((rc = read(fd, buffer, sizeof(buffer))) > 0) {
2✔
488
            if (yajl_parse(jhandle, buffer, rc) != yajl_status_ok) {
1✔
489
                auto* msg = yajl_get_error(jhandle, 1, buffer, rc);
×
490
                fprintf(
×
491
                    stderr, "Unable to parse remote-config.json -- %s\n", msg);
492
                yajl_free_error(jhandle, msg);
×
493
                return;
×
494
            }
495
        }
496
        if (yajl_complete_parse(jhandle) != yajl_status_ok) {
1✔
497
            auto* msg = yajl_get_error(jhandle, 0, nullptr, 0);
×
498

499
            fprintf(stderr, "Unable to parse remote-config.json -- %s\n", msg);
×
500
            yajl_free_error(jhandle, msg);
×
501
        }
502
    }
1✔
503
}
1✔
504

505
struct config_userdata {
506
    explicit config_userdata(std::vector<lnav::console::user_message>& errors)
21,722✔
507
        : ud_errors(errors)
21,722✔
508
    {
509
    }
21,722✔
510

511
    std::vector<lnav::console::user_message>& ud_errors;
512
};
513

514
static void
515
config_error_reporter(const yajlpp_parse_context& ypc,
7✔
516
                      const lnav::console::user_message& msg)
517
{
518
    auto* ud = (config_userdata*) ypc.ypc_userdata;
7✔
519

520
    ud->ud_errors.emplace_back(msg);
7✔
521
}
7✔
522

523
static const struct json_path_container key_command_handlers = {
524
    yajlpp::property_handler("id")
525
        .with_synopsis("<id>")
526
        .with_description(
527
            "The identifier that can be used to refer to this key")
528
        .for_field(&key_command::kc_id),
529
    yajlpp::property_handler("command")
530
        .with_synopsis("<command>")
531
        .with_description(
532
            "The command to execute for the given key sequence.  Use a script "
533
            "to execute more complicated operations.")
534
        .with_pattern("^$|^[:|;].*")
535
        .with_example(":goto next hour"_frag)
536
        .for_field(&key_command::kc_cmd),
537
    yajlpp::property_handler("alt-msg")
538
        .with_synopsis("<msg>")
539
        .with_description(
540
            "The help message to display after the key is pressed.")
541
        .for_field<>(&key_command::kc_alt_msg),
542
};
543

544
static const struct json_path_container keymap_def_handlers = {
545
    yajlpp::pattern_property_handler(
546
        "(?<key_seq>(?:x[0-9a-f]{2}|f[0-9]{1,2})+)")
547
        .with_synopsis("<utf8-key-code-in-hex>")
548
        .with_description(
549
            "Map of key codes to commands to execute.  The field names are "
550
            "the keys to be mapped using as a hexadecimal representation of "
551
            "the UTF-8 encoding.  Each byte of the UTF-8 should start with "
552
            "an 'x' followed by the hexadecimal representation of the byte.")
553
        .with_obj_provider<key_command, key_map>(
554
            [](const yajlpp_provider_context& ypc, key_map* km) {
446,193✔
555
                auto& retval = km->km_seq_to_cmd[ypc.get_substr("key_seq")];
446,193✔
556

557
                if (ypc.ypc_parse_context != nullptr) {
446,193✔
558
                    retval.kc_cmd.pp_path
559
                        = ypc.ypc_parse_context->get_full_path();
441,378✔
560

561
                    retval.kc_cmd.pp_location.sl_source
562
                        = ypc.ypc_parse_context->ypc_source;
441,378✔
563
                    retval.kc_cmd.pp_location.sl_line_number
564
                        = ypc.ypc_parse_context->get_line_number();
441,378✔
565
                }
566
                return &retval;
446,193✔
567
            })
568
        .with_path_provider<key_map>(
569
            [](key_map* km, std::vector<std::string>& paths_out) {
114✔
570
                for (const auto& iter : km->km_seq_to_cmd) {
2,147✔
571
                    paths_out.emplace_back(iter.first);
2,033✔
572
                }
573
            })
114✔
574
        .with_children(key_command_handlers),
575
};
576

577
static const struct json_path_container keymap_defs_handlers = {
578
    yajlpp::pattern_property_handler("(?<keymap_name>[\\w\\-]+)")
579
        .with_description("The keymap definitions")
580
        .with_obj_provider<key_map, _lnav_config>(
581
            [](const yajlpp_provider_context& ypc, _lnav_config* root) {
457,176✔
582
                key_map& retval
583
                    = root->lc_ui_keymaps[ypc.get_substr("keymap_name")];
457,176✔
584
                return &retval;
457,176✔
585
            })
586
        .with_path_provider<_lnav_config>(
587
            [](struct _lnav_config* cfg, std::vector<std::string>& paths_out) {
19✔
588
                for (const auto& iter : cfg->lc_ui_keymaps) {
133✔
589
                    paths_out.emplace_back(iter.first);
114✔
590
                }
591
            })
19✔
592
        .with_children(keymap_def_handlers),
593
};
594

595
static constexpr json_path_handler_base::enum_value_t _movement_values[] = {
596
    {"top"_frag, config_movement_mode::TOP},
597
    {"cursor"_frag, config_movement_mode::CURSOR},
598

599
    json_path_handler_base::ENUM_TERMINATOR,
600
};
601

602
static const json_path_container movement_handlers = {
603
    yajlpp::property_handler("mode")
604
        .with_synopsis("top|cursor")
605
        .with_enum_values(_movement_values)
606
        .with_example("top"_frag)
607
        .with_example("cursor"_frag)
608
        .with_description("The mode of cursor movement to use.")
609
        .for_field<>(&_lnav_config::lc_ui_movement, &movement_config::mode),
610
};
611

612
static constexpr json_path_handler_base::enum_value_t _mouse_mode_values[] = {
613
    {"disabled"_frag, lnav_mouse_mode::disabled},
614
    {"enabled"_frag, lnav_mouse_mode::enabled},
615

616
    json_path_handler_base::ENUM_TERMINATOR,
617
};
618

619
static const struct json_path_container mouse_handlers = {
620
    yajlpp::property_handler("mode")
621
        .with_synopsis("enabled|disabled")
622
        .with_enum_values(_mouse_mode_values)
623
        .with_example("enabled"_frag)
624
        .with_example("disabled"_frag)
625
        .with_description("Overall control for mouse support")
626
        .for_field<>(&_lnav_config::lc_mouse_mode),
627
};
628

629
static const struct json_path_container global_var_handlers = {
630
    yajlpp::pattern_property_handler("(?<var_name>\\w+)")
631
        .with_synopsis("<name>")
632
        .with_description(
633
            "A global variable definition.  Global variables can be referenced "
634
            "in scripts, SQL statements, or commands.")
635
        .with_path_provider<_lnav_config>(
636
            [](struct _lnav_config* cfg, std::vector<std::string>& paths_out) {
×
637
                for (const auto& iter : cfg->lc_global_vars) {
×
638
                    paths_out.emplace_back(iter.first);
×
639
                }
640
            })
×
641
        .for_field(&_lnav_config::lc_global_vars),
642
};
643

644
static const auto icon_config_handlers
645
    = json_path_container{
646
        yajlpp::property_handler("value")
647
            .with_description("The icon.")
648
            .for_field(&icon_config::ic_value),
649
    }.with_definition_id("icon");
650

651
static const json_path_container theme_icons_handlers = {
652
    yajlpp::property_handler("hidden")
653
        .with_description("Icon for hidden fields")
654
        .for_child(&lnav_theme::lt_icon_hidden)
655
        .with_children(icon_config_handlers),
656
    yajlpp::property_handler("ok")
657
        .with_description("Icon for OK")
658
        .for_child(&lnav_theme::lt_icon_ok)
659
        .with_children(icon_config_handlers),
660
    yajlpp::property_handler("info")
661
        .with_description("Icon for informational messages")
662
        .for_child(&lnav_theme::lt_icon_info)
663
        .with_children(icon_config_handlers),
664
    yajlpp::property_handler("warning")
665
        .with_description("Icon for warning messages")
666
        .for_child(&lnav_theme::lt_icon_warning)
667
        .with_children(icon_config_handlers),
668
    yajlpp::property_handler("error")
669
        .with_description("Icon for error messages")
670
        .for_child(&lnav_theme::lt_icon_error)
671
        .with_children(icon_config_handlers),
672

673
    yajlpp::property_handler("log-level-trace")
674
        .with_description("Icon for 'trace' log level")
675
        .for_child(&lnav_theme::lt_icon_log_level_trace)
676
        .with_children(icon_config_handlers),
677
    yajlpp::property_handler("log-level-debug")
678
        .with_description("Icon for 'debug' log level")
679
        .for_child(&lnav_theme::lt_icon_log_level_debug)
680
        .with_children(icon_config_handlers),
681
    yajlpp::property_handler("log-level-info")
682
        .with_description("Icon for 'info' log level")
683
        .for_child(&lnav_theme::lt_icon_log_level_info)
684
        .with_children(icon_config_handlers),
685
    yajlpp::property_handler("log-level-stats")
686
        .with_description("Icon for 'stats' log level")
687
        .for_child(&lnav_theme::lt_icon_log_level_stats)
688
        .with_children(icon_config_handlers),
689
    yajlpp::property_handler("log-level-notice")
690
        .with_description("Icon for 'notice' log level")
691
        .for_child(&lnav_theme::lt_icon_log_level_notice)
692
        .with_children(icon_config_handlers),
693
    yajlpp::property_handler("log-level-warning")
694
        .with_description("Icon for 'warning' log level")
695
        .for_child(&lnav_theme::lt_icon_log_level_warning)
696
        .with_children(icon_config_handlers),
697
    yajlpp::property_handler("log-level-error")
698
        .with_description("Icon for 'error' log level")
699
        .for_child(&lnav_theme::lt_icon_log_level_error)
700
        .with_children(icon_config_handlers),
701
    yajlpp::property_handler("log-level-critical")
702
        .with_description("Icon for 'critical' log level")
703
        .for_child(&lnav_theme::lt_icon_log_level_critical)
704
        .with_children(icon_config_handlers),
705
    yajlpp::property_handler("log-level-fatal")
706
        .with_description("Icon for 'fatal' log level")
707
        .for_child(&lnav_theme::lt_icon_log_level_fatal)
708
        .with_children(icon_config_handlers),
709

710
    yajlpp::property_handler("play")
711
        .with_description("Icon for a 'play' button")
712
        .for_child(&lnav_theme::lt_icon_play)
713
        .with_children(icon_config_handlers),
714
    yajlpp::property_handler("edit")
715
        .with_description("Icon for a 'edit' button")
716
        .for_child(&lnav_theme::lt_icon_edit)
717
        .with_children(icon_config_handlers),
718
    yajlpp::property_handler("file")
719
        .with_description("Icon for files")
720
        .for_child(&lnav_theme::lt_icon_file)
721
        .with_children(icon_config_handlers),
722
    yajlpp::property_handler("thread")
723
        .with_description("Icon for threads")
724
        .for_child(&lnav_theme::lt_icon_thread)
725
        .with_children(icon_config_handlers),
726
    yajlpp::property_handler("busy")
727
        .with_description("Icon for a 'busy' status")
728
        .for_child(&lnav_theme::lt_icon_busy)
729
        .with_children(icon_config_handlers),
730
};
731

732
static const struct json_path_container theme_styles_handlers = {
733
    yajlpp::property_handler("identifier")
734
        .with_description("Styling for identifiers in logs")
735
        .for_child(&lnav_theme::lt_style_identifier)
736
        .with_children(style_config_handlers),
737
    yajlpp::property_handler("text")
738
        .with_description("Styling for plain text")
739
        .for_child(&lnav_theme::lt_style_text)
740
        .with_children(style_config_handlers),
741
    yajlpp::property_handler("selected-text")
742
        .with_description("Styling for text selected in a view")
743
        .for_child(&lnav_theme::lt_style_selected_text)
744
        .with_children(style_config_handlers),
745
    yajlpp::property_handler("fuzzy-match")
746
        .with_description("Styling for characters found in fuzzy match")
747
        .for_child(&lnav_theme::lt_style_fuzzy_match)
748
        .with_children(style_config_handlers),
749
    yajlpp::property_handler("alt-text")
750
        .with_description("Styling for plain text when alternating")
751
        .for_child(&lnav_theme::lt_style_alt_text)
752
        .with_children(style_config_handlers),
753
    yajlpp::property_handler("error")
754
        .with_description("Styling for error messages")
755
        .for_child(&lnav_theme::lt_style_error)
756
        .with_children(style_config_handlers),
757
    yajlpp::property_handler("ok")
758
        .with_description("Styling for success messages")
759
        .for_child(&lnav_theme::lt_style_ok)
760
        .with_children(style_config_handlers),
761
    yajlpp::property_handler("info")
762
        .with_description("Styling for informational messages")
763
        .for_child(&lnav_theme::lt_style_info)
764
        .with_children(style_config_handlers),
765
    yajlpp::property_handler("warning")
766
        .with_description("Styling for warning messages")
767
        .for_child(&lnav_theme::lt_style_warning)
768
        .with_children(style_config_handlers),
769
    yajlpp::property_handler("hidden")
770
        .with_description("Styling for hidden fields in logs")
771
        .for_child(&lnav_theme::lt_style_hidden)
772
        .with_children(style_config_handlers),
773
    yajlpp::property_handler("cursor-line")
774
        .with_description("Styling for the cursor line in the main view")
775
        .for_child(&lnav_theme::lt_style_cursor_line)
776
        .with_children(style_config_handlers),
777
    yajlpp::property_handler("disabled-cursor-line")
778
        .with_description("Styling for the cursor line when it is disabled")
779
        .for_child(&lnav_theme::lt_style_disabled_cursor_line)
780
        .with_children(style_config_handlers),
781
    yajlpp::property_handler("adjusted-time")
782
        .with_description("Styling for timestamps that have been adjusted")
783
        .for_child(&lnav_theme::lt_style_adjusted_time)
784
        .with_children(style_config_handlers),
785
    yajlpp::property_handler("skewed-time")
786
        .with_description(
787
            "Styling for timestamps that are different from the received time")
788
        .for_child(&lnav_theme::lt_style_skewed_time)
789
        .with_children(style_config_handlers),
790
    yajlpp::property_handler("file-offset")
791
        .with_description("Styling for a file offset")
792
        .for_child(&lnav_theme::lt_style_file_offset)
793
        .with_children(style_config_handlers),
794
    yajlpp::property_handler("offset-time")
795
        .with_description("Styling for the elapsed time column")
796
        .for_child(&lnav_theme::lt_style_offset_time)
797
        .with_children(style_config_handlers),
798
    yajlpp::property_handler("time-column")
799
        .with_description("Styling for the time column")
800
        .for_child(&lnav_theme::lt_style_time_column)
801
        .with_children(style_config_handlers),
802
    yajlpp::property_handler("invalid-msg")
803
        .with_description("Styling for invalid log messages")
804
        .for_child(&lnav_theme::lt_style_invalid_msg)
805
        .with_children(style_config_handlers),
806
    yajlpp::property_handler("popup")
807
        .with_description("Styling for popup windows")
808
        .for_child(&lnav_theme::lt_style_popup)
809
        .with_children(style_config_handlers),
810
    yajlpp::property_handler("popup-border")
811
        .with_description("Styling for the borders of a popup window")
812
        .for_child(&lnav_theme::lt_style_popup_border)
813
        .with_children(style_config_handlers),
814
    yajlpp::property_handler("focused")
815
        .with_description("Styling for a focused row in a list view")
816
        .for_child(&lnav_theme::lt_style_focused)
817
        .with_children(style_config_handlers),
818
    yajlpp::property_handler("disabled-focused")
819
        .with_description("Styling for a disabled focused row in a list view")
820
        .for_child(&lnav_theme::lt_style_disabled_focused)
821
        .with_children(style_config_handlers),
822
    yajlpp::property_handler("scrollbar")
823
        .with_description("Styling for scrollbars")
824
        .for_child(&lnav_theme::lt_style_scrollbar)
825
        .with_children(style_config_handlers),
826
    yajlpp::property_handler("h1")
827
        .with_description("Styling for top-level headers")
828
        .with_obj_provider<style_config, lnav_theme>(
829
            [](const yajlpp_provider_context& ypc, lnav_theme* root) {
44,989✔
830
                if (ypc.ypc_parse_context != nullptr
89,978✔
831
                    && root->lt_style_header[0].pp_path.empty())
44,989✔
832
                {
833
                    root->lt_style_header[0].pp_path
834
                        = ypc.ypc_parse_context->get_full_path();
11,718✔
835
                }
836
                return &root->lt_style_header[0].pp_value;
44,989✔
837
            })
838
        .with_children(style_config_handlers),
839
    yajlpp::property_handler("h2")
840
        .with_description("Styling for 2nd-level headers")
841
        .with_obj_provider<style_config, lnav_theme>(
842
            [](const yajlpp_provider_context& ypc, lnav_theme* root) {
43,687✔
843
                if (ypc.ypc_parse_context != nullptr
87,374✔
844
                    && root->lt_style_header[1].pp_path.empty())
43,687✔
845
                {
846
                    root->lt_style_header[1].pp_path
847
                        = ypc.ypc_parse_context->get_full_path();
11,718✔
848
                }
849
                return &root->lt_style_header[1].pp_value;
43,687✔
850
            })
851
        .with_children(style_config_handlers),
852
    yajlpp::property_handler("h3")
853
        .with_description("Styling for 3rd-level headers")
854
        .with_obj_provider<style_config, lnav_theme>(
855
            [](const yajlpp_provider_context& ypc, lnav_theme* root) {
35,875✔
856
                if (ypc.ypc_parse_context != nullptr
71,750✔
857
                    && root->lt_style_header[2].pp_path.empty())
35,875✔
858
                {
859
                    root->lt_style_header[2].pp_path
860
                        = ypc.ypc_parse_context->get_full_path();
11,718✔
861
                }
862
                return &root->lt_style_header[2].pp_value;
35,875✔
863
            })
864
        .with_children(style_config_handlers),
865
    yajlpp::property_handler("h4")
866
        .with_description("Styling for 4th-level headers")
867
        .with_obj_provider<style_config, lnav_theme>(
868
            [](const yajlpp_provider_context& ypc, lnav_theme* root) {
37,177✔
869
                if (ypc.ypc_parse_context != nullptr
74,354✔
870
                    && root->lt_style_header[3].pp_path.empty())
37,177✔
871
                {
872
                    root->lt_style_header[3].pp_path
873
                        = ypc.ypc_parse_context->get_full_path();
11,718✔
874
                }
875
                return &root->lt_style_header[3].pp_value;
37,177✔
876
            })
877
        .with_children(style_config_handlers),
878
    yajlpp::property_handler("h5")
879
        .with_description("Styling for 5th-level headers")
880
        .with_obj_provider<style_config, lnav_theme>(
881
            [](const yajlpp_provider_context& ypc, lnav_theme* root) {
35,875✔
882
                if (ypc.ypc_parse_context != nullptr
71,750✔
883
                    && root->lt_style_header[4].pp_path.empty())
35,875✔
884
                {
885
                    root->lt_style_header[4].pp_path
886
                        = ypc.ypc_parse_context->get_full_path();
11,718✔
887
                }
888
                return &root->lt_style_header[4].pp_value;
35,875✔
889
            })
890
        .with_children(style_config_handlers),
891
    yajlpp::property_handler("h6")
892
        .with_description("Styling for 6th-level headers")
893
        .with_obj_provider<style_config, lnav_theme>(
894
            [](const yajlpp_provider_context& ypc, lnav_theme* root) {
35,875✔
895
                if (ypc.ypc_parse_context != nullptr
71,750✔
896
                    && root->lt_style_header[5].pp_path.empty())
35,875✔
897
                {
898
                    root->lt_style_header[5].pp_path
899
                        = ypc.ypc_parse_context->get_full_path();
11,718✔
900
                }
901
                return &root->lt_style_header[5].pp_value;
35,875✔
902
            })
903
        .with_children(style_config_handlers),
904
    yajlpp::property_handler("hr")
905
        .with_description("Styling for horizontal rules")
906
        .for_child(&lnav_theme::lt_style_hr)
907
        .with_children(style_config_handlers),
908
    yajlpp::property_handler("hyperlink")
909
        .with_description("Styling for hyperlinks")
910
        .for_child(&lnav_theme::lt_style_hyperlink)
911
        .with_children(style_config_handlers),
912
    yajlpp::property_handler("list-glyph")
913
        .with_description("Styling for glyphs that prefix a list item")
914
        .for_child(&lnav_theme::lt_style_list_glyph)
915
        .with_children(style_config_handlers),
916
    yajlpp::property_handler("breadcrumb")
917
        .with_description("Styling for the separator between breadcrumbs")
918
        .for_child(&lnav_theme::lt_style_breadcrumb)
919
        .with_children(style_config_handlers),
920
    yajlpp::property_handler("table-border")
921
        .with_description("Styling for table borders")
922
        .for_child(&lnav_theme::lt_style_table_border)
923
        .with_children(style_config_handlers),
924
    yajlpp::property_handler("table-header")
925
        .with_description("Styling for table headers")
926
        .for_child(&lnav_theme::lt_style_table_header)
927
        .with_children(style_config_handlers),
928
    yajlpp::property_handler("quote-border")
929
        .with_description("Styling for quoted-block borders")
930
        .for_child(&lnav_theme::lt_style_quote_border)
931
        .with_children(style_config_handlers),
932
    yajlpp::property_handler("quoted-text")
933
        .with_description("Styling for quoted text blocks")
934
        .for_child(&lnav_theme::lt_style_quoted_text)
935
        .with_children(style_config_handlers),
936
    yajlpp::property_handler("footnote-border")
937
        .with_description("Styling for footnote borders")
938
        .for_child(&lnav_theme::lt_style_footnote_border)
939
        .with_children(style_config_handlers),
940
    yajlpp::property_handler("footnote-text")
941
        .with_description("Styling for footnote text")
942
        .for_child(&lnav_theme::lt_style_footnote_text)
943
        .with_children(style_config_handlers),
944
    yajlpp::property_handler("snippet-border")
945
        .with_description("Styling for snippet borders")
946
        .for_child(&lnav_theme::lt_style_snippet_border)
947
        .with_children(style_config_handlers),
948
    yajlpp::property_handler("indent-guide")
949
        .with_description("Styling for indent guide lines")
950
        .for_child(&lnav_theme::lt_style_indent_guide)
951
        .with_children(style_config_handlers),
952
};
953

954
static const struct json_path_container theme_syntax_styles_handlers = {
955
    yajlpp::property_handler("inline-code")
956
        .with_description("Styling for inline code blocks")
957
        .for_child(&lnav_theme::lt_style_inline_code)
958
        .with_children(style_config_handlers),
959
    yajlpp::property_handler("quoted-code")
960
        .with_description("Styling for quoted code blocks")
961
        .for_child(&lnav_theme::lt_style_quoted_code)
962
        .with_children(style_config_handlers),
963
    yajlpp::property_handler("code-border")
964
        .with_description("Styling for quoted-code borders")
965
        .for_child(&lnav_theme::lt_style_code_border)
966
        .with_children(style_config_handlers),
967
    yajlpp::property_handler("object-key")
968
        .with_description("Styling for a key in an object")
969
        .for_child(&lnav_theme::lt_style_object_key)
970
        .with_children(style_config_handlers),
971
    yajlpp::property_handler("keyword")
972
        .with_description("Styling for keywords in source files")
973
        .for_child(&lnav_theme::lt_style_keyword)
974
        .with_children(style_config_handlers),
975
    yajlpp::property_handler("string")
976
        .with_description("Styling for single/double-quoted strings in text")
977
        .for_child(&lnav_theme::lt_style_string)
978
        .with_children(style_config_handlers),
979
    yajlpp::property_handler("comment")
980
        .with_description("Styling for comments in source files")
981
        .for_child(&lnav_theme::lt_style_comment)
982
        .with_children(style_config_handlers),
983
    yajlpp::property_handler("doc-directive")
984
        .with_description(
985
            "Styling for documentation directives in source files")
986
        .for_child(&lnav_theme::lt_style_doc_directive)
987
        .with_children(style_config_handlers),
988
    yajlpp::property_handler("variable")
989
        .with_description("Styling for variables in text")
990
        .for_child(&lnav_theme::lt_style_variable)
991
        .with_children(style_config_handlers),
992
    yajlpp::property_handler("symbol")
993
        .with_description("Styling for symbols in source files")
994
        .for_child(&lnav_theme::lt_style_symbol)
995
        .with_children(style_config_handlers),
996
    yajlpp::property_handler("null")
997
        .with_description("Styling for nulls in source files")
998
        .for_child(&lnav_theme::lt_style_null)
999
        .with_children(style_config_handlers),
1000
    yajlpp::property_handler("ascii-control")
1001
        .with_description(
1002
            "Styling for ASCII control characters in source files")
1003
        .for_child(&lnav_theme::lt_style_ascii_ctrl)
1004
        .with_children(style_config_handlers),
1005
    yajlpp::property_handler("non-ascii")
1006
        .with_description("Styling for non-ASCII characters in source files")
1007
        .for_child(&lnav_theme::lt_style_non_ascii)
1008
        .with_children(style_config_handlers),
1009
    yajlpp::property_handler("number")
1010
        .with_description("Styling for numbers in source files")
1011
        .for_child(&lnav_theme::lt_style_number)
1012
        .with_children(style_config_handlers),
1013
    yajlpp::property_handler("type")
1014
        .with_description("Styling for types in source files")
1015
        .for_child(&lnav_theme::lt_style_type)
1016
        .with_children(style_config_handlers),
1017
    yajlpp::property_handler("function")
1018
        .with_description("Styling for functions in source files")
1019
        .for_child(&lnav_theme::lt_style_function)
1020
        .with_children(style_config_handlers),
1021
    yajlpp::property_handler("separators-references-accessors")
1022
        .with_description("Styling for sigils in source files")
1023
        .for_child(&lnav_theme::lt_style_sep_ref_acc)
1024
        .with_children(style_config_handlers),
1025
    yajlpp::property_handler("re-special")
1026
        .with_description(
1027
            "Styling for special characters in regular expressions")
1028
        .for_child(&lnav_theme::lt_style_re_special)
1029
        .with_children(style_config_handlers),
1030
    yajlpp::property_handler("re-repeat")
1031
        .with_description("Styling for repeats in regular expressions")
1032
        .for_child(&lnav_theme::lt_style_re_repeat)
1033
        .with_children(style_config_handlers),
1034

1035
    yajlpp::property_handler("diff-delete")
1036
        .with_description("Styling for deleted lines in diffs")
1037
        .for_child(&lnav_theme::lt_style_diff_delete)
1038
        .with_children(style_config_handlers),
1039
    yajlpp::property_handler("diff-add")
1040
        .with_description("Styling for added lines in diffs")
1041
        .for_child(&lnav_theme::lt_style_diff_add)
1042
        .with_children(style_config_handlers),
1043
    yajlpp::property_handler("diff-section")
1044
        .with_description("Styling for diffs")
1045
        .for_child(&lnav_theme::lt_style_diff_section)
1046
        .with_children(style_config_handlers),
1047

1048
    yajlpp::property_handler("spectrogram-low")
1049
        .with_description(
1050
            "Styling for the lower threshold values in the spectrogram view")
1051
        .for_child(&lnav_theme::lt_style_low_threshold)
1052
        .with_children(style_config_handlers),
1053
    yajlpp::property_handler("spectrogram-medium")
1054
        .with_description(
1055
            "Styling for the medium threshold values in the spectrogram view")
1056
        .for_child(&lnav_theme::lt_style_med_threshold)
1057
        .with_children(style_config_handlers),
1058
    yajlpp::property_handler("spectrogram-high")
1059
        .with_description(
1060
            "Styling for the high threshold values in the spectrogram view")
1061
        .for_child(&lnav_theme::lt_style_high_threshold)
1062
        .with_children(style_config_handlers),
1063

1064
    yajlpp::property_handler("file")
1065
        .with_description("Styling for file names in source files")
1066
        .for_child(&lnav_theme::lt_style_file)
1067
        .with_children(style_config_handlers),
1068
};
1069

1070
static const struct json_path_container theme_status_styles_handlers = {
1071
    yajlpp::property_handler("text")
1072
        .with_description("Styling for status bars")
1073
        .for_child(&lnav_theme::lt_style_status)
1074
        .with_children(style_config_handlers),
1075
    yajlpp::property_handler("warn")
1076
        .with_description("Styling for warnings in status bars")
1077
        .for_child(&lnav_theme::lt_style_warn_status)
1078
        .with_children(style_config_handlers),
1079
    yajlpp::property_handler("alert")
1080
        .with_description("Styling for alerts in status bars")
1081
        .for_child(&lnav_theme::lt_style_alert_status)
1082
        .with_children(style_config_handlers),
1083
    yajlpp::property_handler("active")
1084
        .with_description("Styling for activity in status bars")
1085
        .for_child(&lnav_theme::lt_style_active_status)
1086
        .with_children(style_config_handlers),
1087
    yajlpp::property_handler("inactive-alert")
1088
        .with_description("Styling for inactive alert status bars")
1089
        .for_child(&lnav_theme::lt_style_inactive_alert_status)
1090
        .with_children(style_config_handlers),
1091
    yajlpp::property_handler("inactive")
1092
        .with_description("Styling for inactive status bars")
1093
        .for_child(&lnav_theme::lt_style_inactive_status)
1094
        .with_children(style_config_handlers),
1095
    yajlpp::property_handler("title-hotkey")
1096
        .with_description("Styling for hotkey highlights in titles")
1097
        .for_child(&lnav_theme::lt_style_status_title_hotkey)
1098
        .with_children(style_config_handlers),
1099
    yajlpp::property_handler("title")
1100
        .with_description("Styling for title sections of status bars")
1101
        .for_child(&lnav_theme::lt_style_status_title)
1102
        .with_children(style_config_handlers),
1103
    yajlpp::property_handler("disabled-title")
1104
        .with_description("Styling for title sections of status bars")
1105
        .for_child(&lnav_theme::lt_style_status_disabled_title)
1106
        .with_children(style_config_handlers),
1107
    yajlpp::property_handler("subtitle")
1108
        .with_description("Styling for subtitle sections of status bars")
1109
        .for_child(&lnav_theme::lt_style_status_subtitle)
1110
        .with_children(style_config_handlers),
1111
    yajlpp::property_handler("info")
1112
        .with_description("Styling for informational messages in status bars")
1113
        .for_child(&lnav_theme::lt_style_status_info)
1114
        .with_children(style_config_handlers),
1115
    yajlpp::property_handler("hotkey")
1116
        .with_description("Styling for hotkey highlights of status bars")
1117
        .for_child(&lnav_theme::lt_style_status_hotkey)
1118
        .with_children(style_config_handlers),
1119
    yajlpp::property_handler("suggestion")
1120
        .with_description("Styling for suggested values")
1121
        .for_child(&lnav_theme::lt_style_suggestion)
1122
        .with_children(style_config_handlers),
1123
};
1124

1125
static const struct json_path_container theme_log_level_styles_handlers = {
1126
    yajlpp::pattern_property_handler(
1127
        "(?<level>trace|debug5|debug4|debug3|debug2|debug|info|stats|notice|"
1128
        "warning|error|critical|fatal|invalid)")
1129
        .with_obj_provider<style_config, lnav_theme>(
1130
            [](const yajlpp_provider_context& ypc, lnav_theme* root) {
150,710✔
1131
                auto& sc = root->lt_level_styles[string2level(
150,710✔
1132
                    ypc.get_substr_i("level").get())];
301,420✔
1133

1134
                if (ypc.ypc_parse_context != nullptr && sc.pp_path.empty()) {
150,710✔
1135
                    sc.pp_path = ypc.ypc_parse_context->get_full_path();
46,872✔
1136
                }
1137

1138
                return &sc.pp_value;
150,710✔
1139
            })
1140
        .with_path_provider<lnav_theme>(
1141
            [](struct lnav_theme* cfg, std::vector<std::string>& paths_out) {
175✔
1142
                for (int lpc = LEVEL_TRACE; lpc < LEVEL__MAX; lpc++) {
2,625✔
1143
                    paths_out.emplace_back(level_names[lpc].to_string());
2,450✔
1144
                }
1145
            })
175✔
1146
        .with_children(style_config_handlers),
1147
};
1148

1149
static const struct json_path_container highlighter_handlers = {
1150
    yajlpp::property_handler("pattern")
1151
        .with_synopsis("regular expression")
1152
        .with_description("The regular expression to highlight")
1153
        .for_field(&highlighter_config::hc_regex),
1154

1155
    yajlpp::property_handler("nestable")
1156
        .with_synopsis("<enabled>")
1157
        .with_description("This highlight can be nested in another highlight.")
1158
        .for_field(&highlighter_config::hc_nestable),
1159

1160
    yajlpp::property_handler("style")
1161
        .with_description(
1162
            "The styling for the text that matches the associated pattern")
1163
        .for_child(&highlighter_config::hc_style)
1164
        .with_children(style_config_handlers),
1165
};
1166

1167
static const struct json_path_container theme_highlights_handlers = {
1168
    yajlpp::pattern_property_handler("(?<highlight_name>[\\w\\-]+)")
1169
        .with_obj_provider<highlighter_config,
1170
                           lnav_theme>([](const yajlpp_provider_context& ypc,
32,643✔
1171
                                          lnav_theme* root) {
1172
            highlighter_config& hc
1173
                = root->lt_highlights[ypc.get_substr_i("highlight_name").get()];
65,286✔
1174

1175
            return &hc;
32,643✔
1176
        })
1177
        .with_path_provider<lnav_theme>(
1178
            [](struct lnav_theme* cfg, std::vector<std::string>& paths_out) {
175✔
1179
                for (const auto& pair : cfg->lt_highlights) {
255✔
1180
                    paths_out.emplace_back(pair.first);
80✔
1181
                }
1182
            })
175✔
1183
        .with_children(highlighter_handlers),
1184
};
1185

1186
static const struct json_path_container theme_vars_handlers = {
1187
    yajlpp::pattern_property_handler("(?<var_name>\\w+)")
1188
        .with_synopsis("name")
1189
        .with_description("A theme variable definition")
1190
        .with_path_provider<lnav_theme>(
1191
            [](struct lnav_theme* lt, std::vector<std::string>& paths_out) {
175✔
1192
                for (const auto& iter : lt->lt_vars) {
2,816✔
1193
                    paths_out.emplace_back(iter.first);
2,641✔
1194
                }
1195
            })
175✔
1196
        .for_field(&lnav_theme::lt_vars),
1197
};
1198

1199
static const struct json_path_container theme_def_handlers = {
1200
    yajlpp::property_handler("vars")
1201
        .with_description("Variables definitions that are used in this theme.")
1202
        .with_children(theme_vars_handlers),
1203

1204
    yajlpp::property_handler("icons")
1205
        .with_description("Icons for UI elements.")
1206
        .with_children(theme_icons_handlers),
1207

1208
    yajlpp::property_handler("styles")
1209
        .with_description("Styles for log messages.")
1210
        .with_children(theme_styles_handlers),
1211

1212
    yajlpp::property_handler("syntax-styles")
1213
        .with_description("Styles for syntax highlighting in text files.")
1214
        .with_children(theme_syntax_styles_handlers),
1215

1216
    yajlpp::property_handler("status-styles")
1217
        .with_description("Styles for the user-interface components.")
1218
        .with_children(theme_status_styles_handlers),
1219

1220
    yajlpp::property_handler("log-level-styles")
1221
        .with_description("Styles for each log message level.")
1222
        .with_children(theme_log_level_styles_handlers),
1223

1224
    yajlpp::property_handler("highlights")
1225
        .with_description("Styles for text highlights.")
1226
        .with_children(theme_highlights_handlers),
1227
};
1228

1229
static const struct json_path_container theme_defs_handlers = {
1230
    yajlpp::pattern_property_handler("(?<theme_name>[\\w\\-]+)")
1231
        .with_description("Theme definitions")
1232
        .with_obj_provider<lnav_theme, _lnav_config>(
1233
            [](const yajlpp_provider_context& ypc, _lnav_config* root) {
3,811,987✔
1234
                lnav_theme& lt
1235
                    = root->lc_ui_theme_defs[ypc.get_substr("theme_name")];
3,811,987✔
1236

1237
                return &lt;
3,811,987✔
1238
            })
1239
        .with_path_provider<_lnav_config>(
1240
            [](struct _lnav_config* cfg, std::vector<std::string>& paths_out) {
27✔
1241
                for (const auto& iter : cfg->lc_ui_theme_defs) {
278✔
1242
                    paths_out.emplace_back(iter.first);
251✔
1243
                }
1244
            })
27✔
1245
        .with_obj_deleter(
1246
            +[](const yajlpp_provider_context& ypc, _lnav_config* root) {
×
1247
                root->lc_ui_theme_defs.erase(ypc.get_substr("theme_name"));
×
1248
            })
×
1249
        .with_children(theme_def_handlers),
1250
};
1251

1252
static constexpr json_path_handler_base::enum_value_t _time_column_values[] = {
1253
    {"disabled"_frag, logfile_sub_source_ns::time_column_feature_t::Disabled},
1254
    {"enabled"_frag, logfile_sub_source_ns::time_column_feature_t::Enabled},
1255
    {"default"_frag, logfile_sub_source_ns::time_column_feature_t::Default},
1256

1257
    json_path_handler_base::ENUM_TERMINATOR,
1258
};
1259

1260
static const json_path_container log_view_handlers = {
1261
    yajlpp::property_handler("time-column")
1262
        .with_description(
1263
            "Display a column with the log message time and hide the "
1264
            "timestamp/level in the message.  Possible values: disabled - "
1265
            "never display the column; enabled - display the column when "
1266
            "initially scrolling right; default - display the column "
1267
            "initially.")
1268
        .with_enum_values(_time_column_values)
1269
        .with_example("enabled"_frag)
1270
        .for_field(&_lnav_config::lc_log_source,
1271
                   &logfile_sub_source_ns::config::c_time_column),
1272
};
1273

1274
static const json_path_container views_handlers = {
1275
    yajlpp::property_handler("log")
1276
        .with_description("Log view settings")
1277
        .with_children(log_view_handlers),
1278
};
1279

1280
static const json_path_container ui_handlers = {
1281
    yajlpp::property_handler("clock-format")
1282
        .with_synopsis("format")
1283
        .with_description("The format for the clock displayed in "
1284
                          "the top-left corner using strftime(3) conversions")
1285
        .with_example("%a %b %d %H:%M:%S %Z"_frag)
1286
        .for_field(&_lnav_config::lc_top_status_cfg,
1287
                   &top_status_source_cfg::tssc_clock_format),
1288
    yajlpp::property_handler("dim-text")
1289
        .with_synopsis("bool")
1290
        .with_description("Reduce the brightness of text (useful for xterms). "
1291
                          "This setting can be useful when running in an xterm "
1292
                          "where the white color is very bright.")
1293
        .for_field(&_lnav_config::lc_ui_dim_text),
1294
    yajlpp::property_handler("default-colors")
1295
        .with_synopsis("bool")
1296
        .with_description(
1297
            "Use default terminal background and foreground colors "
1298
            "instead of black and white for all text coloring.  This setting "
1299
            "can be useful when transparent background or alternate color "
1300
            "theme terminal is used.")
1301
        .for_field(&_lnav_config::lc_ui_default_colors),
1302
    yajlpp::property_handler("keymap")
1303
        .with_synopsis("keymap_name")
1304
        .with_description("The name of the keymap to use.")
1305
        .for_field(&_lnav_config::lc_ui_keymap),
1306
    yajlpp::property_handler("theme")
1307
        .with_synopsis("theme_name")
1308
        .with_description("The name of the theme to use.")
1309
        .for_field(&_lnav_config::lc_ui_theme),
1310
    yajlpp::property_handler("theme-defs")
1311
        .with_description("Theme definitions.")
1312
        .with_children(theme_defs_handlers),
1313
    yajlpp::property_handler("mouse")
1314
        .with_description("Mouse-related settings")
1315
        .with_children(mouse_handlers),
1316
    yajlpp::property_handler("movement")
1317
        .with_description("Log file cursor movement mode settings")
1318
        .with_children(movement_handlers),
1319
    yajlpp::property_handler("keymap-defs")
1320
        .with_description("Keymap definitions.")
1321
        .with_children(keymap_defs_handlers),
1322
    yajlpp::property_handler("views")
1323
        .with_description("View-related settings")
1324
        .with_children(views_handlers),
1325
};
1326

1327
static const struct json_path_container archive_handlers = {
1328
    yajlpp::property_handler("min-free-space")
1329
        .with_synopsis("<bytes>")
1330
        .with_description(
1331
            "The minimum free space, in bytes, to maintain when unpacking "
1332
            "archives")
1333
        .with_min_value(0)
1334
        .for_field(&_lnav_config::lc_archive_manager,
1335
                   &archive_manager::config::amc_min_free_space),
1336
    yajlpp::property_handler("cache-ttl")
1337
        .with_synopsis("<duration>")
1338
        .with_description(
1339
            "The time-to-live for unpacked archives, expressed as a duration "
1340
            "(e.g. '3d' for three days)")
1341
        .with_example("3d"_frag)
1342
        .with_example("12h"_frag)
1343
        .for_field(&_lnav_config::lc_archive_manager,
1344
                   &archive_manager::config::amc_cache_ttl),
1345
};
1346

1347
static const typed_json_path_container<lnav::piper::demux_json_def>
1348
    demux_json_def_handlers = {
1349
        yajlpp::property_handler("enabled")
1350
            .with_description("Indicates whether this demuxer will be used at "
1351
                              "the demuxing stage (defaults to 'true')")
1352
            .for_field(&lnav::piper::demux_json_def::djd_enabled),
1353
        yajlpp::property_handler("timestamp")
1354
            .with_synopsis("<json-ptr>")
1355
            .with_description("The pointer to the timestamp of the message")
1356
            .for_field(&lnav::piper::demux_json_def::djd_timestamp),
1357
        yajlpp::property_handler("mux_id")
1358
            .with_synopsis("<json-ptr>")
1359
            .with_description("The pointer to the ID for demultiplexing")
1360
            .for_field(&lnav::piper::demux_json_def::djd_mux_id),
1361
        yajlpp::property_handler("body")
1362
            .with_synopsis("<json-ptr>")
1363
            .with_description(
1364
                "The pointer to the property that contains the log message")
1365
            .for_field(&lnav::piper::demux_json_def::djd_body),
1366
};
1367

1368
static const json_path_container demux_json_defs_handlers = {
1369
    yajlpp::pattern_property_handler("(?<name>[\\w\\-\\.]+)")
1370
        .with_description("The definition of a JSON demultiplexer")
1371
        .with_children(demux_json_def_handlers)
1372
        .for_field(&_lnav_config::lc_piper,
1373
                   &lnav::piper::config::c_demux_json_definitions),
1374
};
1375

1376
static const typed_json_path_container<lnav::piper::demux_def>
1377
    demux_def_handlers = {
1378
        yajlpp::property_handler("enabled")
1379
            .with_description("Indicates whether this demuxer will be used at "
1380
                              "the demuxing stage (defaults to 'true')")
1381
            .for_field(&lnav::piper::demux_def::dd_enabled),
1382
        yajlpp::property_handler("pattern")
1383
            .with_synopsis("<regex>")
1384
            .with_description(
1385
                "A regular expression to match a line in a multiplexed file")
1386
            .for_field(&lnav::piper::demux_def::dd_pattern),
1387
        yajlpp::property_handler("control-pattern")
1388
            .with_synopsis("<regex>")
1389
            .with_description(
1390
                "A regular expression to match a control line in a multiplexed "
1391
                "file")
1392
            .for_field(&lnav::piper::demux_def::dd_control_pattern),
1393
};
1394

1395
static const json_path_container demux_defs_handlers = {
1396
    yajlpp::pattern_property_handler("(?<name>[\\w\\-\\.]+)")
1397
        .with_description("The definition of a demultiplexer")
1398
        .with_children(demux_def_handlers)
1399
        .for_field(&_lnav_config::lc_piper,
1400
                   &lnav::piper::config::c_demux_definitions),
1401
};
1402

1403
static const struct json_path_container piper_handlers = {
1404
    yajlpp::property_handler("max-size")
1405
        .with_synopsis("<bytes>")
1406
        .with_description("The maximum size of a capture file")
1407
        .with_min_value(128)
1408
        .for_field(&_lnav_config::lc_piper, &lnav::piper::config::c_max_size),
1409
    yajlpp::property_handler("rotations")
1410
        .with_synopsis("<count>")
1411
        .with_min_value(2)
1412
        .with_description("The number of rotated files to keep")
1413
        .for_field(&_lnav_config::lc_piper, &lnav::piper::config::c_rotations),
1414
    yajlpp::property_handler("ttl")
1415
        .with_synopsis("<duration>")
1416
        .with_description(
1417
            "The time-to-live for captured data, expressed as a duration "
1418
            "(e.g. '3d' for three days)")
1419
        .with_example("3d"_frag)
1420
        .with_example("12h"_frag)
1421
        .for_field(&_lnav_config::lc_piper, &lnav::piper::config::c_ttl),
1422
};
1423

1424
static const struct json_path_container file_vtab_handlers = {
1425
    yajlpp::property_handler("max-content-size")
1426
        .with_synopsis("<bytes>")
1427
        .with_description(
1428
            "The maximum allowed file size for the content column")
1429
        .with_min_value(0)
1430
        .for_field(&_lnav_config::lc_file_vtab,
1431
                   &file_vtab::config::fvc_max_content_size),
1432
};
1433

1434
static const struct json_path_container textfile_handlers = {
1435
    yajlpp::property_handler("max-unformatted-line-length")
1436
        .with_synopsis("<bytes>")
1437
        .with_description("The maximum allowed length for a line in a text "
1438
                          "file before formatting is automatically applied")
1439
        .with_min_value(0)
1440
        .for_field(&_lnav_config::lc_textfile,
1441
                   &lnav::textfile::config::c_max_unformatted_line_length),
1442
};
1443

1444
static const struct json_path_container logfile_handlers = {
1445
    yajlpp::property_handler("max-unrecognized-lines")
1446
        .with_synopsis("<lines>")
1447
        .with_description("The maximum number of lines in a file to use when "
1448
                          "detecting the format")
1449
        .with_min_value(1)
1450
        .for_field(&_lnav_config::lc_logfile,
1451
                   &lnav::logfile::config::lc_max_unrecognized_lines),
1452
};
1453

1454
static const struct json_path_container ssh_config_handlers = {
1455
    yajlpp::pattern_property_handler("(?<config_name>\\w+)")
1456
        .with_synopsis("name")
1457
        .with_description("Set an SSH configuration value")
1458
        .with_path_provider<_lnav_config>(
1459
            [](auto* m, std::vector<std::string>& paths_out) {
×
1460
                for (const auto& pair : m->lc_tailer.c_ssh_config) {
×
1461
                    paths_out.emplace_back(pair.first);
×
1462
                }
1463
            })
×
1464
        .for_field(&_lnav_config::lc_tailer, &tailer::config::c_ssh_config),
1465
};
1466

1467
static const struct json_path_container ssh_option_handlers = {
1468
    yajlpp::pattern_property_handler("(?<option_name>\\w+)")
1469
        .with_synopsis("name")
1470
        .with_description("Set an option to be passed to the SSH command")
1471
        .for_field(&_lnav_config::lc_tailer, &tailer::config::c_ssh_options),
1472
};
1473

1474
static const struct json_path_container ssh_handlers = {
1475
    yajlpp::property_handler("command")
1476
        .with_synopsis("ssh-command")
1477
        .with_description("The SSH command to execute")
1478
        .for_field(&_lnav_config::lc_tailer, &tailer::config::c_ssh_cmd),
1479
    yajlpp::property_handler("transfer-command")
1480
        .with_synopsis("command")
1481
        .with_description(
1482
            "Command executed on the remote host when transferring the file")
1483
        .for_field(&_lnav_config::lc_tailer, &tailer::config::c_transfer_cmd),
1484
    yajlpp::property_handler("start-command")
1485
        .with_synopsis("command")
1486
        .with_description(
1487
            "Command executed on the remote host to start the tailer")
1488
        .for_field(&_lnav_config::lc_tailer, &tailer::config::c_start_cmd),
1489
    yajlpp::property_handler("flags")
1490
        .with_description("The flags to pass to the SSH command")
1491
        .for_field(&_lnav_config::lc_tailer, &tailer::config::c_ssh_flags),
1492
    yajlpp::property_handler("options")
1493
        .with_description("The options to pass to the SSH command")
1494
        .with_children(ssh_option_handlers),
1495
    yajlpp::property_handler("config")
1496
        .with_description(
1497
            "The ssh_config options to pass to SSH with the -o option")
1498
        .with_children(ssh_config_handlers),
1499
};
1500

1501
static const struct json_path_container remote_handlers = {
1502
    yajlpp::property_handler("cache-ttl")
1503
        .with_synopsis("<duration>")
1504
        .with_description("The time-to-live for files copied from remote "
1505
                          "hosts, expressed as a duration "
1506
                          "(e.g. '3d' for three days)")
1507
        .with_example("3d"_frag)
1508
        .with_example("12h"_frag)
1509
        .for_field(&_lnav_config::lc_tailer, &tailer::config::c_cache_ttl),
1510
    yajlpp::property_handler("ssh")
1511
        .with_description(
1512
            "Settings related to the ssh command used to contact remote "
1513
            "machines")
1514
        .with_children(ssh_handlers),
1515
};
1516

1517
static const struct json_path_container sysclip_impl_cmd_handlers = json_path_container{
1518
    yajlpp::property_handler("write")
1519
        .with_synopsis("<command>")
1520
        .with_description("The command used to write to the clipboard")
1521
        .with_example("pbcopy"_frag)
1522
        .for_field(&sysclip::clip_commands::cc_write),
1523
    yajlpp::property_handler("read")
1524
        .with_synopsis("<command>")
1525
        .with_description("The command used to read from the clipboard")
1526
        .with_example("pbpaste"_frag)
1527
        .for_field(&sysclip::clip_commands::cc_read),
1528
}
1529
    .with_description("Container for the commands used to read from and write to the system clipboard")
1530
    .with_definition_id("clip-commands");
1531

1532
static const struct json_path_container sysclip_impl_handlers = {
1533
    yajlpp::property_handler("test")
1534
        .with_synopsis("<command>")
1535
        .with_description(
1536
            "The command that checks if a clipboard command is available")
1537
        .with_example("command -v pbcopy"_frag)
1538
        .for_field(&sysclip::clipboard::c_test_command),
1539
    yajlpp::property_handler("general")
1540
        .with_description("Commands to work with the general clipboard")
1541
        .for_child(&sysclip::clipboard::c_general)
1542
        .with_children(sysclip_impl_cmd_handlers),
1543
    yajlpp::property_handler("find")
1544
        .with_description("Commands to work with the find clipboard")
1545
        .for_child(&sysclip::clipboard::c_find)
1546
        .with_children(sysclip_impl_cmd_handlers),
1547
};
1548

1549
static const struct json_path_container sysclip_impls_handlers = {
1550
    yajlpp::pattern_property_handler("(?<clipboard_impl_name>[\\w\\-]+)")
1551
        .with_synopsis("<name>")
1552
        .with_description("Clipboard implementation")
1553
        .with_obj_provider<sysclip::clipboard, _lnav_config>(
1554
            [](const yajlpp_provider_context& ypc, _lnav_config* root) {
58,860✔
1555
                auto& retval
1556
                    = root->lc_sysclip.c_clipboard_impls[ypc.get_substr(
×
1557
                        "clipboard_impl_name")];
58,860✔
1558
                return &retval;
58,860✔
1559
            })
1560
        .with_path_provider<_lnav_config>(
1561
            [](struct _lnav_config* cfg, std::vector<std::string>& paths_out) {
19✔
1562
                for (const auto& iter : cfg->lc_sysclip.c_clipboard_impls) {
133✔
1563
                    paths_out.emplace_back(iter.first);
114✔
1564
                }
1565
            })
19✔
1566
        .with_children(sysclip_impl_handlers),
1567
};
1568

1569
static const struct json_path_container sysclip_handlers = {
1570
    yajlpp::property_handler("impls")
1571
        .with_description("Clipboard implementations")
1572
        .with_children(sysclip_impls_handlers),
1573
};
1574

1575
static const json_path_container opener_impl_handlers = {
1576
    yajlpp::property_handler("test")
1577
        .with_synopsis("<command>")
1578
        .with_description(
1579
            "The command that checks if an external opener is available")
1580
        .with_example("command -v open"_frag)
1581
        .for_field(&lnav::external_opener::impl::i_test_command),
1582
    yajlpp::property_handler("command")
1583
        .with_description("The command used to open a file or URL")
1584
        .with_example("open"_frag)
1585
        .for_field(&lnav::external_opener::impl::i_command),
1586
};
1587

1588
static const json_path_container opener_impls_handlers = {
1589
    yajlpp::pattern_property_handler("(?<opener_impl_name>[\\w\\-\\.]+)")
1590
        .with_synopsis("<name>")
1591
        .with_description("External opener implementation")
1592
        .with_obj_provider<lnav::external_opener::impl, _lnav_config>(
1593
            [](const yajlpp_provider_context& ypc, _lnav_config* root) {
10,490✔
1594
                auto& retval = root->lc_opener
1595
                                   .c_impls[ypc.get_substr("opener_impl_name")];
10,490✔
1596
                return &retval;
10,490✔
1597
            })
1598
        .with_path_provider<_lnav_config>(
1599
            [](struct _lnav_config* cfg, std::vector<std::string>& paths_out) {
19✔
1600
                for (const auto& iter : cfg->lc_opener.c_impls) {
57✔
1601
                    paths_out.emplace_back(iter.first);
38✔
1602
                }
1603
            })
19✔
1604
        .with_children(opener_impl_handlers),
1605
};
1606

1607
static const struct json_path_container opener_handlers = {
1608
    yajlpp::property_handler("impls")
1609
        .with_description("External opener implementations")
1610
        .with_children(opener_impls_handlers),
1611
};
1612

1613
static const json_path_container editor_impl_handlers = {
1614
    yajlpp::property_handler("test")
1615
        .with_synopsis("<command>")
1616
        .with_description(
1617
            "The command that checks if an external editor is available")
1618
        .with_example("command -v open"_frag)
1619
        .for_field(&lnav::external_editor::impl::i_test_command),
1620
    yajlpp::property_handler("command")
1621
        .with_description("The command used to open text for editing")
1622
        .with_example("code -"_frag)
1623
        .for_field(&lnav::external_editor::impl::i_command),
1624
    yajlpp::property_handler("config-dir")
1625
        .with_description(
1626
            "The name of the directory where editor configuration is stored")
1627
        .with_example(".idea"_frag)
1628
        .for_field(&lnav::external_editor::impl::i_config_dir),
1629
    yajlpp::property_handler("prefers")
1630
        .with_description("")
1631
        .with_example("^.*(?:\\.cpp)$"_frag)
1632
        .for_field(&lnav::external_editor::impl::i_prefers),
1633
};
1634

1635
static const json_path_container editor_impls_handlers = {
1636
    yajlpp::pattern_property_handler("(?<editor_impl_name>[\\w\\-\\.]+)")
1637
        .with_synopsis("<name>")
1638
        .with_description("External editor implementation")
1639
        .with_obj_provider<lnav::external_editor::impl, _lnav_config>(
1640
            [](const yajlpp_provider_context& ypc, _lnav_config* root) {
33,115✔
1641
                auto& retval = root->lc_external_editor
1642
                                   .c_impls[ypc.get_substr("editor_impl_name")];
33,115✔
1643
                return &retval;
33,115✔
1644
            })
1645
        .with_path_provider<_lnav_config>(
1646
            [](struct _lnav_config* cfg, std::vector<std::string>& paths_out) {
19✔
1647
                for (const auto& iter : cfg->lc_external_editor.c_impls) {
114✔
1648
                    paths_out.emplace_back(iter.first);
95✔
1649
                }
1650
            })
19✔
1651
        .with_children(editor_impl_handlers),
1652
};
1653

1654
static const json_path_container editor_handlers = {
1655
    yajlpp::property_handler("impls")
1656
        .with_description("External editor implementations")
1657
        .with_children(editor_impls_handlers),
1658
};
1659

1660
static const struct json_path_container log_source_watch_expr_handlers = {
1661
    yajlpp::property_handler("expr")
1662
        .with_synopsis("<SQL-expression>")
1663
        .with_description("The SQL expression to execute for each input line. "
1664
                          "If expression evaluates to true, a 'log message "
1665
                          "detected' event will be published.")
1666
        .for_field(&logfile_sub_source_ns::watch_expression::we_expr),
1667
    yajlpp::property_handler("enabled")
1668
        .with_description("Indicates whether or not this expression should be "
1669
                          "evaluated during log processing.")
1670
        .for_field(&logfile_sub_source_ns::watch_expression::we_enabled),
1671
};
1672

1673
static const struct json_path_container log_source_watch_handlers = {
1674
    yajlpp::pattern_property_handler("(?<watch_name>[\\w\\.\\-]+)")
1675
        .with_synopsis("<name>")
1676
        .with_description("A log message watch expression")
1677
        .with_obj_provider<logfile_sub_source_ns::watch_expression,
1678
                           _lnav_config>(
1679
            [](const yajlpp_provider_context& ypc, _lnav_config* root) {
18✔
1680
                auto& retval = root->lc_log_source
1681
                                   .c_watch_exprs[ypc.get_substr("watch_name")];
18✔
1682
                return &retval;
18✔
1683
            })
1684
        .with_path_provider<_lnav_config>(
1685
            [](struct _lnav_config* cfg, std::vector<std::string>& paths_out) {
29✔
1686
                for (const auto& iter : cfg->lc_log_source.c_watch_exprs) {
33✔
1687
                    paths_out.emplace_back(iter.first);
4✔
1688
                }
1689
            })
29✔
1690
        .with_obj_deleter(
1691
            +[](const yajlpp_provider_context& ypc, _lnav_config* root) {
1✔
1692
                root->lc_log_source.c_watch_exprs.erase(
1✔
1693
                    ypc.get_substr("watch_name"));
2✔
1694
            })
1✔
1695
        .with_children(log_source_watch_expr_handlers),
1696
};
1697

1698
static const struct json_path_container annotation_handlers = {
1699
    yajlpp::property_handler("description")
1700
        .with_synopsis("<text>")
1701
        .with_description("A description of this annotation")
1702
        .for_field(&lnav::log::annotate::annotation_def::a_description),
1703
    yajlpp::property_handler("condition")
1704
        .with_synopsis("<SQL-expression>")
1705
        .with_description(
1706
            "The SQLite expression to execute for a log message that "
1707
            "determines whether or not this annotation is applicable.  The "
1708
            "expression is evaluated the same way as a filter expression")
1709
        .with_min_length(1)
1710
        .for_field(&lnav::log::annotate::annotation_def::a_condition),
1711
    yajlpp::property_handler("handler")
1712
        .with_synopsis("<script>")
1713
        .with_description("The script to execute to generate the annotation "
1714
                          "content. A JSON object with the log message content "
1715
                          "will be sent to the script on the standard input")
1716
        .with_min_length(1)
1717
        .for_field(&lnav::log::annotate::annotation_def::a_handler),
1718
};
1719

1720
static const struct json_path_container annotations_handlers = {
1721
    yajlpp::pattern_property_handler(R"((?<annotation_name>[\w\.\-]+))")
1722
        .with_obj_provider<lnav::log::annotate::annotation_def, _lnav_config>(
1723
            [](const yajlpp_provider_context& ypc, _lnav_config* root) {
13,649✔
1724
                auto* retval = &(root->lc_log_annotations
1725
                                     .a_definitions[ypc.get_substr_i(0)]);
13,649✔
1726

1727
                return retval;
13,649✔
1728
            })
1729
        .with_path_provider<_lnav_config>(
1730
            [](struct _lnav_config* cfg, std::vector<std::string>& paths_out) {
19✔
1731
                for (const auto& iter : cfg->lc_log_annotations.a_definitions) {
66✔
1732
                    paths_out.emplace_back(iter.first.to_string());
47✔
1733
                }
1734
            })
19✔
1735
        .with_children(annotation_handlers),
1736
};
1737

1738
static const struct json_path_container log_date_time_handlers = {
1739
    yajlpp::property_handler("convert-zoned-to-local")
1740
        .with_description("Convert timestamps with ")
1741
        .with_pattern(R"(^[\w\-]+(?!\.lnav)$)")
1742
        .for_field(&_lnav_config::lc_log_date_time,
1743
                   &date_time_scanner_ns::config::c_zoned_to_local),
1744
};
1745

1746
static const struct json_path_container log_source_handlers = {
1747
    yajlpp::property_handler("date-time")
1748
        .with_description("Settings related to log message dates and times")
1749
        .with_children(log_date_time_handlers),
1750
    yajlpp::property_handler("watch-expressions")
1751
        .with_description("Log message watch expressions")
1752
        .with_children(log_source_watch_handlers),
1753
    yajlpp::property_handler("annotations").with_children(annotations_handlers),
1754
    yajlpp::property_handler("demux")
1755
        .with_description("Demultiplexer definitions")
1756
        .with_children(demux_defs_handlers),
1757
    yajlpp::property_handler("demux-json")
1758
        .with_description("JSON Demultiplexer definitions")
1759
        .with_children(demux_json_defs_handlers),
1760
};
1761

1762
static const struct json_path_container url_scheme_handlers = {
1763
    yajlpp::property_handler("handler")
1764
        .with_description(
1765
            "The name of the lnav script that can handle URLs "
1766
            "with of this scheme.  This should not include the '.lnav' suffix.")
1767
        .with_pattern(R"(^[\w\-]+(?!\.lnav)$)")
1768
        .for_field(&lnav::url_handler::scheme::p_handler),
1769
};
1770

1771
static const struct json_path_container url_handlers = {
1772
    yajlpp::pattern_property_handler(R"((?<url_scheme>[a-z][\w\-\+\.]+))")
1773
        .with_description("Definition of a custom URL scheme")
1774
        .with_obj_provider<lnav::url_handler::scheme, _lnav_config>(
1775
            [](const yajlpp_provider_context& ypc, _lnav_config* root) {
23,911✔
1776
                auto& retval = root->lc_url_handlers
1777
                                   .c_schemes[ypc.get_substr("url_scheme")];
23,911✔
1778
                return &retval;
23,911✔
1779
            })
1780
        .with_path_provider<_lnav_config>(
1781
            [](struct _lnav_config* cfg, std::vector<std::string>& paths_out) {
19✔
1782
                for (const auto& iter : cfg->lc_url_handlers.c_schemes) {
134✔
1783
                    paths_out.emplace_back(iter.first);
115✔
1784
                }
1785
            })
19✔
1786
        .with_children(url_scheme_handlers),
1787
};
1788

1789
static const struct json_path_container tuning_handlers = {
1790
    yajlpp::property_handler("archive-manager")
1791
        .with_description("Settings related to opening archive files")
1792
        .with_children(archive_handlers),
1793
    yajlpp::property_handler("piper")
1794
        .with_description("Settings related to capturing piped data")
1795
        .with_children(piper_handlers),
1796
    yajlpp::property_handler("file-vtab")
1797
        .with_description("Settings related to the lnav_file virtual-table")
1798
        .with_children(file_vtab_handlers),
1799
    yajlpp::property_handler("logfile")
1800
        .with_description("Settings related to log files")
1801
        .with_children(logfile_handlers),
1802
    yajlpp::property_handler("remote")
1803
        .with_description("Settings related to remote file support")
1804
        .with_children(remote_handlers),
1805
    yajlpp::property_handler("clipboard")
1806
        .with_description("Settings related to the clipboard")
1807
        .with_children(sysclip_handlers),
1808
    yajlpp::property_handler("external-opener")
1809
        .with_description("Settings related to opening external files/URLs")
1810
        .with_children(opener_handlers),
1811
    yajlpp::property_handler("external-editor")
1812
        .with_description(
1813
            "Settings related to opening content in an external editor")
1814
        .with_children(editor_handlers),
1815
    yajlpp::property_handler("textfile")
1816
        .with_description("Settings related to text file handling")
1817
        .with_children(textfile_handlers),
1818
    yajlpp::property_handler("url-scheme")
1819
        .with_description("Settings related to custom URL handling")
1820
        .with_children(url_handlers),
1821
};
1822

1823
static const json_path_container app_def_handlers = {
1824
    yajlpp::property_handler("root")
1825
        .with_description("The path to use as the root for files that are a "
1826
                          "part of this application.  The path is relative to "
1827
                          "the location of the file containing this property.")
1828
        .with_pattern("[\\w\\-\\.]+")
1829
        .for_field(&lnav::apps::app_def::ad_root_path),
1830
    yajlpp::property_handler("description")
1831
        .with_description("A description of this application")
1832
        .for_field(&lnav::apps::app_def::ad_description),
1833
};
1834

1835
static const json_path_container app_defs_handlers = {
1836
    yajlpp::pattern_property_handler("(?<app_name>[\\w\\-]+)")
1837
        .with_description("The application definition")
1838
        .with_obj_provider<lnav::apps::app_def, lnav::apps::pub_def>(
1839
            [](const yajlpp_provider_context& ypc, lnav::apps::pub_def* pd) {
3,943✔
1840
                auto name = ypc.get_substr("app_name");
3,943✔
1841
                auto& def = pd->pd_apps[name];
3,943✔
1842

1843
                return &def;
3,943✔
1844
            })
3,943✔
1845
        .with_path_provider<lnav::apps::pub_def>(
1846
            [](lnav::apps::pub_def* pd, std::vector<std::string>& paths_out) {
19✔
1847
                for (const auto& iter : pd->pd_apps) {
38✔
1848
                    paths_out.emplace_back(iter.first);
19✔
1849
                }
1850
            })
19✔
1851
        .with_children(app_def_handlers),
1852
};
1853

1854
static const json_path_container apps_handlers = {
1855
    yajlpp::pattern_property_handler("(?<publisher>[\\w\\-]+)")
1856
        .with_description("The publisher of the application")
1857
        .with_obj_provider<lnav::apps::pub_def, _lnav_config>(
1858
            [](const yajlpp_provider_context& ypc, _lnav_config* root) {
6,539✔
1859
                auto pub = ypc.get_substr("publisher");
6,539✔
1860
                auto& def = root->lc_apps.c_publishers[pub];
6,539✔
1861

1862
                return &def;
6,539✔
1863
            })
6,539✔
1864
        .with_path_provider<_lnav_config>(
1865
            [](_lnav_config* root, std::vector<std::string>& paths_out) {
19✔
1866
                for (const auto& iter : root->lc_apps.c_publishers) {
38✔
1867
                    paths_out.emplace_back(iter.first);
19✔
1868
                }
1869
            })
19✔
1870
        .with_children(app_defs_handlers),
1871
};
1872

1873
const string_fragment DEFAULT_CONFIG_SCHEMA
1874
    = "https://lnav.org/schemas/config-v1.schema.json"_frag;
1875

1876
static const std::set<string_fragment> SUPPORTED_CONFIG_SCHEMAS = {
1877
    DEFAULT_CONFIG_SCHEMA,
1878
};
1879

1880
const string_fragment DEFAULT_FORMAT_SCHEMA
1881
    = "https://lnav.org/schemas/format-v1.schema.json"_frag;
1882

1883
const std::set<string_fragment> SUPPORTED_FORMAT_SCHEMAS = {
1884
    DEFAULT_FORMAT_SCHEMA,
1885
};
1886

1887
static int
1888
read_id(yajlpp_parse_context* ypc,
21,036✔
1889
        const unsigned char* str,
1890
        size_t len,
1891
        yajl_string_props_t*)
1892
{
1893
    auto file_id = string_fragment::from_bytes(str, len);
21,036✔
1894

1895
    if (SUPPORTED_CONFIG_SCHEMAS.count(file_id) == 0) {
21,036✔
1896
        const auto* handler = ypc->ypc_current_handler;
1✔
1897
        attr_line_t notes{"expecting one of the following $schema values:"};
1✔
1898

1899
        for (const auto& schema : SUPPORTED_CONFIG_SCHEMAS) {
2✔
1900
            notes.append("\n").append(
2✔
1901
                lnav::roles::symbol(fmt::format(FMT_STRING("  {}"), schema)));
5✔
1902
        }
1903
        ypc->report_error(
2✔
1904
            lnav::console::user_message::error(
×
1905
                attr_line_t()
1✔
1906
                    .append_quoted(lnav::roles::symbol(file_id))
2✔
1907
                    .append(
1✔
1908
                        " is not a supported configuration $schema version"))
1909
                .with_snippet(ypc->get_snippet())
2✔
1910
                .with_note(notes)
1✔
1911
                .with_help(handler->get_help_text(ypc)));
1✔
1912
    }
1✔
1913

1914
    return 1;
21,036✔
1915
}
1916

1917
const json_path_container lnav_config_handlers = json_path_container {
1918
    json_path_handler("$schema", read_id)
1919
        .with_synopsis("<schema-uri>")
1920
        .with_description("The URI that specifies the schema that describes this type of file")
1921
        .with_example(DEFAULT_CONFIG_SCHEMA),
1922

1923
    yajlpp::property_handler("apps")
1924
        .with_description("Application definitions")
1925
        .with_children(apps_handlers),
1926

1927
    yajlpp::property_handler("tuning")
1928
        .with_description("Internal settings")
1929
        .with_children(tuning_handlers),
1930

1931
    yajlpp::property_handler("ui")
1932
        .with_description("User-interface settings")
1933
        .with_children(ui_handlers),
1934

1935
    yajlpp::property_handler("log")
1936
        .with_description("Log message settings")
1937
        .with_children(log_source_handlers),
1938

1939
    yajlpp::property_handler("global")
1940
        .with_description("Global variable definitions")
1941
        .with_children(global_var_handlers),
1942
}
1943
    .with_schema_id(*SUPPORTED_CONFIG_SCHEMAS.cbegin());
1944

1945
class active_key_map_listener : public lnav_config_listener {
1946
public:
1947
    active_key_map_listener() : lnav_config_listener(__FILE__) {}
1,179✔
1948

1949
    void reload_config(error_reporter& reporter) override
674✔
1950
    {
1951
        lnav_config.lc_active_keymap = lnav_config.lc_ui_keymaps["default"];
674✔
1952
        for (const auto& pair :
674✔
1953
             lnav_config.lc_ui_keymaps[lnav_config.lc_ui_keymap].km_seq_to_cmd)
38,418✔
1954
        {
1955
            if (pair.second.kc_cmd.pp_value.empty()) {
37,070✔
1956
                lnav_config.lc_active_keymap.km_seq_to_cmd.erase(pair.first);
×
1957
            } else {
1958
                lnav_config.lc_active_keymap.km_seq_to_cmd[pair.first]
74,140✔
1959
                    = pair.second;
37,070✔
1960
            }
1961
        }
1962

1963
        auto& ec = injector::get<exec_context&>();
674✔
1964
        for (const auto& pair : lnav_config.lc_active_keymap.km_seq_to_cmd) {
37,744✔
1965
            if (pair.second.kc_id.empty()) {
37,070✔
1966
                continue;
36,396✔
1967
            }
1968

1969
            auto keyseq_sf = string_fragment::from_str(pair.first);
674✔
1970
            std::string keystr;
674✔
1971
            if (keyseq_sf.startswith("f")) {
674✔
1972
                auto sv = keyseq_sf.to_string_view();
×
1973
                auto scan_res = scn::scan<int32_t>(sv, "f{}");
×
1974
                if (!scan_res) {
×
1975
                    log_error("invalid function key sequence: %.*s",
×
1976
                              keyseq_sf.length(),
1977
                              keyseq_sf.data());
1978
                    continue;
×
1979
                }
1980
                auto value = scan_res->value();
×
1981
                if (value < 0 || value > 64) {
×
1982
                    log_error("invalid function key number: %.*s",
×
1983
                              keyseq_sf.length(),
1984
                              keyseq_sf.data());
1985
                    continue;
×
1986
                }
1987

1988
                keystr = toupper(pair.first);
×
1989
            } else {
1990
                auto sv
1991
                    = string_fragment::from_str(pair.first).to_string_view();
674✔
1992
                while (!sv.empty()) {
1,348✔
1993
                    auto scan_res = scn::scan<int32_t>(sv, "x{:2x}");
674✔
1994
                    if (!scan_res) {
674✔
1995
                        log_error("invalid key sequence: %s",
×
1996
                                  pair.first.c_str());
1997
                        break;
×
1998
                    }
1999
                    auto value = scan_res->value();
674✔
2000
                    auto ch = (char) (value & 0xff);
674✔
2001
                    switch (ch) {
674✔
2002
                        case '\t':
×
2003
                            keystr.append("TAB");
×
2004
                            break;
×
2005
                        case '\r':
×
2006
                            keystr.append("ENTER");
×
2007
                            break;
×
2008
                        default:
674✔
2009
                            keystr.push_back(ch);
674✔
2010
                            break;
674✔
2011
                    }
2012
                    sv = std::string_view{scan_res->range().data(),
1,348✔
2013
                                          scan_res->range().size()};
1,348✔
2014
                }
2015
            }
2016

2017
            if (!keystr.empty()) {
674✔
2018
                ec.ec_global_vars[pair.second.kc_id] = keystr;
674✔
2019
            }
2020
        }
674✔
2021
    }
674✔
2022
};
2023

2024
static active_key_map_listener KEYMAP_LISTENER;
2025

2026
Result<config_file_type, std::string>
2027
detect_config_file_type(const std::filesystem::path& path)
62✔
2028
{
2029
    static const char* id_path[] = {"$schema", nullptr};
2030

2031
    auto content = TRY(lnav::filesystem::read_file(path));
62✔
2032
    if (startswith(content, "#")) {
61✔
2033
        content.insert(0, "//");
×
2034
    }
2035

2036
    char error_buffer[1024];
2037
    auto content_tree = std::unique_ptr<yajl_val_s, decltype(&yajl_tree_free)>(
2038
        yajl_tree_parse(content.c_str(), error_buffer, sizeof(error_buffer)),
2039
        yajl_tree_free);
61✔
2040
    if (content_tree == nullptr) {
61✔
2041
        return Err(
×
2042
            fmt::format(FMT_STRING("JSON parsing failed -- {}"), error_buffer));
×
2043
    }
2044

2045
    auto* id_val = yajl_tree_get(content_tree.get(), id_path, yajl_t_string);
61✔
2046
    if (id_val != nullptr && id_val->u.string != nullptr) {
61✔
2047
        auto id_str = string_fragment::from_c_str(id_val->u.string);
3✔
2048
        if (SUPPORTED_CONFIG_SCHEMAS.count(id_str)) {
3✔
2049
            return Ok(config_file_type::CONFIG);
×
2050
        }
2051
        if (SUPPORTED_FORMAT_SCHEMAS.count(id_str)) {
3✔
2052
            return Ok(config_file_type::FORMAT);
6✔
2053
        }
2054
        return Err(fmt::format(
×
2055
            FMT_STRING("unsupported configuration version in file -- {}"),
×
2056
            id_val->u.string));
×
2057
    }
2058
    return Ok(config_file_type::FORMAT);
116✔
2059
}
61✔
2060

2061
static void
2062
load_config_from(_lnav_config& lconfig,
858✔
2063
                 const std::filesystem::path& path,
2064
                 std::vector<lnav::console::user_message>& errors)
2065
{
2066
    yajlpp_parse_context ypc(intern_string::lookup(path.string()),
1,716✔
2067
                             &lnav_config_handlers);
858✔
2068
    struct config_userdata ud(errors);
858✔
2069
    auto_fd fd;
858✔
2070

2071
    log_info("loading configuration from %s", path.c_str());
858✔
2072
    ypc.ypc_locations = &lnav_config_locations;
858✔
2073
    ypc.with_obj(lconfig);
858✔
2074
    ypc.ypc_userdata = &ud;
858✔
2075
    ypc.with_error_reporter(config_error_reporter);
858✔
2076
    if ((fd = lnav::filesystem::openp(path, O_RDONLY)) == -1) {
858✔
2077
        if (errno != ENOENT) {
551✔
2078
            errors.emplace_back(
×
2079
                lnav::console::user_message::error(
×
2080
                    attr_line_t("unable to open configuration file: ")
×
2081
                        .append(lnav::roles::file(path)))
×
2082
                    .with_errno_reason());
2083
        }
2084
    } else {
2085
        char buffer[2048];
2086
        ssize_t rc = -1;
307✔
2087

2088
        auto handle = yajlpp::alloc_handle(&ypc.ypc_callbacks, &ypc);
307✔
2089
        yajl_config(handle, yajl_allow_comments, 1);
307✔
2090
        yajl_config(handle, yajl_allow_multiple_values, 1);
307✔
2091
        ypc.ypc_handle = handle;
307✔
2092
        while (true) {
2093
            rc = read(fd, buffer, sizeof(buffer));
620✔
2094
            if (rc == 0) {
620✔
2095
                break;
306✔
2096
            }
2097
            if (rc == -1) {
314✔
2098
                errors.emplace_back(
×
2099
                    lnav::console::user_message::error(
×
2100
                        attr_line_t("unable to read format file: ")
×
2101
                            .append(lnav::roles::file(path)))
×
2102
                        .with_errno_reason());
2103
                break;
×
2104
            }
2105
            if (ypc.parse((const unsigned char*) buffer, rc) != yajl_status_ok)
314✔
2106
            {
2107
                break;
1✔
2108
            }
2109
        }
2110
        if (rc == 0) {
307✔
2111
            ypc.complete_parse();
306✔
2112
        }
2113
    }
307✔
2114
}
858✔
2115

2116
static size_t
2117
load_default_config(_lnav_config& config_obj,
20,864✔
2118
                    const std::string& path,
2119
                    const bin_src_file& bsf,
2120
                    std::vector<lnav::console::user_message>& errors)
2121
{
2122
    yajlpp_parse_context ypc_builtin(intern_string::lookup(bsf.get_name()),
20,864✔
2123
                                     &lnav_config_handlers);
20,864✔
2124
    config_userdata ud(errors);
20,864✔
2125

2126
    auto handle
2127
        = yajlpp::alloc_handle(&ypc_builtin.ypc_callbacks, &ypc_builtin);
20,864✔
2128
    ypc_builtin.ypc_locations = &lnav_config_locations;
20,864✔
2129
    ypc_builtin.with_handle(handle);
20,864✔
2130
    ypc_builtin.with_obj(config_obj);
20,864✔
2131
    ypc_builtin.with_error_reporter(config_error_reporter);
20,864✔
2132
    ypc_builtin.ypc_userdata = &ud;
20,864✔
2133

2134
    if (path != "*") {
20,864✔
2135
        ypc_builtin.ypc_ignore_unused = true;
32✔
2136
        ypc_builtin.ypc_active_paths[path] = 0;
32✔
2137
    }
2138

2139
    yajl_config(handle, yajl_allow_comments, 1);
20,864✔
2140
    yajl_config(handle, yajl_allow_multiple_values, 1);
20,864✔
2141
    auto sfp = bsf.to_string_fragment_producer();
20,864✔
2142
    ypc_builtin.parse_doc(*sfp);
20,864✔
2143

2144
    return path == "*" ? 1 : ypc_builtin.ypc_active_paths[path];
41,728✔
2145
}
20,864✔
2146

2147
static size_t
2148
load_default_configs(_lnav_config& config_obj,
1,304✔
2149
                     const std::string& path,
2150
                     std::vector<lnav::console::user_message>& errors)
2151
{
2152
    size_t retval = 0;
1,304✔
2153

2154
    for (auto& bsf : lnav_config_json) {
22,168✔
2155
        retval += load_default_config(config_obj, path, bsf, errors);
20,864✔
2156
    }
2157

2158
    return retval;
1,304✔
2159
}
2160

2161
void
2162
load_config(const std::vector<std::filesystem::path>& extra_paths,
651✔
2163
            std::vector<lnav::console::user_message>& errors)
2164
{
2165
    static auto op = lnav_operation{__FUNCTION__};
651✔
2166

2167
    auto op_guard = lnav_opid_guard::internal(op);
651✔
2168
    auto user_config = lnav::paths::dotlnav() / "config.json";
651✔
2169

2170
    for (const auto& bsf : lnav_config_json) {
11,067✔
2171
        auto sample_path = lnav::paths::dotlnav() / "configs" / "default"
20,832✔
2172
            / fmt::format(FMT_STRING("{}.sample"), bsf.get_name());
62,496✔
2173

2174
        const auto& name_sf = bsf.get_name();
10,416✔
2175
        auto stat_res = lnav::filesystem::stat_file(sample_path);
10,416✔
2176
        if (stat_res.isOk()) {
10,416✔
2177
            auto st = stat_res.unwrap();
9,776✔
2178
            if (st.st_mtime >= lnav::filesystem::self_mtime()) {
9,776✔
2179
                log_debug("skipping writing sample: %.*s (mtimes %ld >= %lld)",
9,776✔
2180
                          name_sf.length(),
2181
                          bsf.get_name().data(),
2182
                          st.st_mtime,
2183
                          lnav::filesystem::self_mtime());
2184
                continue;
9,776✔
2185
            }
2186
            log_debug("sample file needs to be updated: %.*s",
×
2187
                      name_sf.length(),
2188
                      name_sf.data());
2189
        } else {
2190
            log_debug("sample file does not exist: %.*s",
640✔
2191
                      name_sf.length(),
2192
                      name_sf.data());
2193
        }
2194

2195
        auto sfp = bsf.to_string_fragment_producer();
640✔
2196
        auto write_res = lnav::filesystem::write_file(sample_path, *sfp);
640✔
2197
        if (write_res.isErr()) {
640✔
2198
            fprintf(stderr,
×
2199
                    "error:unable to write default config file: %s -- %s\n",
2200
                    sample_path.c_str(),
2201
                    write_res.unwrapErr().c_str());
×
2202
        }
2203
    }
20,192✔
2204

2205
    {
2206
        log_info("loading builtin configuration into default");
651✔
2207
        load_default_configs(lnav_default_config, "*", errors);
651✔
2208
        log_info("loading builtin configuration into base");
651✔
2209
        load_default_configs(lnav_config, "*", errors);
651✔
2210

2211
        log_info("loading installed configuration files");
651✔
2212
        for (const auto& extra_path : extra_paths) {
2,657✔
2213
            auto config_path = extra_path / "configs/*/*.json";
2,006✔
2214
            static_root_mem<glob_t, globfree> gl;
2,006✔
2215

2216
            log_info("loading configuration files in configs directories: %s",
2,006✔
2217
                     config_path.c_str());
2218
            if (glob(config_path.c_str(), 0, nullptr, gl.inout()) == 0) {
2,006✔
2219
                for (size_t lpc = 0; lpc < gl->gl_pathc; lpc++) {
153✔
2220
                    load_config_from(lnav_config, gl->gl_pathv[lpc], errors);
102✔
2221
                    if (errors.empty()) {
102✔
2222
                        load_config_from(
101✔
2223
                            lnav_default_config, gl->gl_pathv[lpc], errors);
101✔
2224
                    }
2225
                }
2226
            }
2227
        }
2,006✔
2228
        for (const auto& extra_path : extra_paths) {
2,657✔
2229
            for (const auto& pat :
6,018✔
2230
                 {"formats/*/config.json", "formats/*/config.*.json"})
8,024✔
2231
            {
2232
                auto config_path = extra_path / pat;
4,012✔
2233
                static_root_mem<glob_t, globfree> gl;
4,012✔
2234

2235
                log_info(
4,012✔
2236
                    "loading configuration files in format directories: %s",
2237
                    config_path.c_str());
2238
                if (glob(config_path.c_str(), 0, nullptr, gl.inout()) == 0) {
4,012✔
2239
                    for (size_t lpc = 0; lpc < gl->gl_pathc; lpc++) {
6✔
2240
                        load_config_from(
4✔
2241
                            lnav_config, gl->gl_pathv[lpc], errors);
4✔
2242
                        if (errors.empty()) {
4✔
2243
                            load_config_from(
×
2244
                                lnav_default_config, gl->gl_pathv[lpc], errors);
×
2245
                        }
2246
                    }
2247
                }
2248
            }
4,012✔
2249
        }
2250

2251
        log_info("loading user configuration");
651✔
2252
        load_config_from(lnav_config, user_config, errors);
651✔
2253
    }
2254

2255
    reload_config(errors);
651✔
2256

2257
    rollback_lnav_config = lnav_config;
651✔
2258
}
651✔
2259

2260
std::string
2261
dump_config()
1✔
2262
{
2263
    yajlpp_gen gen;
1✔
2264
    yajlpp_gen_context ygc(gen, lnav_config_handlers);
1✔
2265

2266
    yajl_gen_config(gen, yajl_gen_beautify, true);
1✔
2267
    ygc.with_obj(lnav_config);
1✔
2268
    ygc.gen();
1✔
2269

2270
    return gen.to_string_fragment().to_string();
2✔
2271
}
1✔
2272

2273
void
2274
reset_config(const std::string& path)
2✔
2275
{
2276
    std::vector<lnav::console::user_message> errors;
2✔
2277

2278
    log_debug("resetting path: %s", path.c_str());
2✔
2279

2280
    auto count = load_default_configs(lnav_config, path, errors);
2✔
2281
    if (path != "*") {
2✔
2282
        static const intern_string_t INPUT_SRC = intern_string::lookup("input");
6✔
2283

2284
        yajlpp_parse_context ypc(INPUT_SRC, &lnav_config_handlers);
2✔
2285
        ypc.set_path(path)
2✔
2286
            .with_obj(lnav_config)
2✔
2287
            .with_error_reporter([&errors](const auto& ypc, auto msg) {
2✔
2288
                errors.push_back(msg);
×
2289
            });
×
2290
        ypc.ypc_active_paths[path] = 0;
2✔
2291
        ypc.update_callbacks();
2✔
2292
        const auto* jph = ypc.ypc_current_handler;
2✔
2293

2294
        if (!ypc.ypc_handler_stack.empty()) {
2✔
2295
            jph = ypc.ypc_handler_stack.back();
2✔
2296
        }
2297

2298
        if (jph != nullptr && jph->jph_children && jph->jph_obj_deleter) {
2✔
2299
            auto key_start = ypc.ypc_path_index_stack.back();
1✔
2300
            auto path_frag = string_fragment::from_byte_range(
2✔
2301
                ypc.ypc_path.data(), key_start + 1, ypc.ypc_path.size());
1✔
2302
            auto md = jph->jph_regex->create_match_data();
1✔
2303
            yajlpp_provider_context provider_ctx{&md, static_cast<size_t>(-1)};
1✔
2304
            jph->jph_regex->capture_from(path_frag).into(md).matches();
1✔
2305

2306
            ypc.ypc_obj_stack.pop();
1✔
2307
            jph->jph_obj_deleter(provider_ctx, ypc.ypc_obj_stack.top());
1✔
2308
        } else if (count == 0 && ypc.ypc_callbacks.yajl_null) {
2✔
2309
            ypc.ypc_callbacks.yajl_null(&ypc);
×
2310
        }
2311
    }
2✔
2312

2313
    reload_config(errors);
2✔
2314

2315
    for (const auto& err : errors) {
2✔
2316
        log_debug("reset %s", err.um_message.get_string().c_str());
×
2317
    }
2318
}
2✔
2319

2320
std::string
2321
save_config()
10✔
2322
{
2323
    auto user_config = lnav::paths::dotlnav() / "config.json";
10✔
2324

2325
    yajlpp_gen gen;
10✔
2326
    yajlpp_gen_context ygc(gen, lnav_config_handlers);
10✔
2327

2328
    ygc.with_default_obj(lnav_default_config).with_obj(lnav_config);
10✔
2329
    ygc.gen();
10✔
2330

2331
    auto config_str = gen.to_string_fragment().to_string();
10✔
2332
    char errbuf[1024];
2333
    auto_mem<yajl_val_s> tree(yajl_tree_free);
10✔
2334

2335
    tree = yajl_tree_parse(config_str.c_str(), errbuf, sizeof(errbuf));
10✔
2336

2337
    if (tree == nullptr) {
10✔
2338
        return fmt::format(
2339
            FMT_STRING("error: unable to save configuration -- {}"), errbuf);
×
2340
    }
2341

2342
    yajl_cleanup_tree(tree);
10✔
2343

2344
    yajlpp_gen clean_gen;
10✔
2345

2346
    yajl_gen_config(clean_gen, yajl_gen_beautify, true);
10✔
2347
    yajl_gen_tree(clean_gen, tree);
10✔
2348

2349
    auto write_res = lnav::filesystem::write_file(
2350
        user_config, clean_gen.to_string_fragment());
10✔
2351
    if (write_res.isErr()) {
10✔
2352
        return fmt::format(
2353
            FMT_STRING("error: unable to write configuration file: {} -- {}"),
×
2354
            user_config.string(),
×
2355
            write_res.unwrapErr());
×
2356
    }
2357

2358
    return "info: configuration saved";
20✔
2359
}
10✔
2360

2361
void
2362
reload_config(std::vector<lnav::console::user_message>& errors)
674✔
2363
{
2364
    auto listeners = lnav_config_listener::listener_list();
674✔
2365
    std::stable_sort(
674✔
2366
        listeners.begin(),
2367
        listeners.end(),
2368
        [](const lnav_config_listener* lhs, const lnav_config_listener* rhs) {
59,708✔
2369
            return lhs->lcl_name < rhs->lcl_name;
59,708✔
2370
        });
2371
    for (auto* curr : listeners) {
18,916✔
2372
        auto reporter = [&errors](const void* cfg_value,
8✔
2373
                                  const lnav::console::user_message& errmsg) {
2374
            log_error("configuration error: %s",
8✔
2375
                      errmsg.to_attr_line().get_string().c_str());
2376
            auto cb = [&cfg_value, &errors, &errmsg](
68,485✔
2377
                          const json_path_handler_base& jph,
2378
                          const std::string& path,
2379
                          const void* mem) {
2380
                if (mem != cfg_value) {
68,485✔
2381
                    return;
68,477✔
2382
                }
2383

2384
                auto loc_iter
2385
                    = lnav_config_locations.find(intern_string::lookup(path));
8✔
2386
                auto has_loc = loc_iter != lnav_config_locations.end();
8✔
2387
                auto um = has_loc
2388
                    ? lnav::console::user_message::error(
8✔
2389
                          attr_line_t()
12✔
2390
                              .append("invalid value for property ")
6✔
2391
                              .append_quoted(lnav::roles::symbol(path)))
14✔
2392
                          .with_reason(errmsg)
6✔
2393
                    : errmsg;
20✔
2394
                um.with_help(jph.get_help_text(path));
8✔
2395

2396
                if (has_loc) {
8✔
2397
                    um.with_snippet(
6✔
2398
                        lnav::console::snippet::from(loc_iter->second.sl_source,
12✔
2399
                                                     "")
2400
                            .with_line(loc_iter->second.sl_line_number));
6✔
2401
                } else {
2402
                    um.um_message
2403
                        = attr_line_t()
2✔
2404
                              .append("missing value for property ")
2✔
2405
                              .append_quoted(lnav::roles::symbol(path))
4✔
2406
                              .move();
4✔
2407
                }
2408

2409
                errors.emplace_back(um);
8✔
2410
            };
8✔
2411

2412
            for (const auto& jph : lnav_config_handlers.jpc_children) {
56✔
2413
                jph.walk(cb, &lnav_config);
144✔
2414
            }
2415
        };
8✔
2416

2417
        curr->reload_config(reporter);
18,242✔
2418
    }
2419
}
674✔
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