diff options
Diffstat (limited to '')
-rw-r--r-- | src/lnav_config.cc | 402 |
1 files changed, 363 insertions, 39 deletions
diff --git a/src/lnav_config.cc b/src/lnav_config.cc index e706f75..e8f91d3 100644 --- a/src/lnav_config.cc +++ b/src/lnav_config.cc @@ -56,8 +56,10 @@ #include "base/paths.hh" #include "base/string_util.hh" #include "bin2c.hh" +#include "command_executor.hh" #include "config.h" #include "default-config.h" +#include "scn/scn.h" #include "styling.hh" #include "view_curses.hh" #include "yajlpp/yajlpp.hh" @@ -81,21 +83,33 @@ lnav_config_listener* lnav_config_listener::LISTENER_LIST; static auto a = injector::bind<archive_manager::config>::to_instance( +[]() { return &lnav_config.lc_archive_manager; }); +static auto dtc = injector::bind<date_time_scanner_ns::config>::to_instance( + +[]() { return &lnav_config.lc_log_date_time; }); + static auto fvc = injector::bind<file_vtab::config>::to_instance( +[]() { return &lnav_config.lc_file_vtab; }); static auto lc = injector::bind<lnav::logfile::config>::to_instance( +[]() { return &lnav_config.lc_logfile; }); +static auto p = injector::bind<lnav::piper::config>::to_instance( + +[]() { return &lnav_config.lc_piper; }); + static auto tc = injector::bind<tailer::config>::to_instance( +[]() { return &lnav_config.lc_tailer; }); static auto scc = injector::bind<sysclip::config>::to_instance( +[]() { return &lnav_config.lc_sysclip; }); +static auto uh = injector::bind<lnav::url_handler::config>::to_instance( + +[]() { return &lnav_config.lc_url_handlers; }); + static auto lsc = injector::bind<logfile_sub_source_ns::config>::to_instance( +[]() { return &lnav_config.lc_log_source; }); +static auto annoc = injector::bind<lnav::log::annotate::config>::to_instance( + +[]() { return &lnav_config.lc_log_annotations; }); + static auto tssc = injector::bind<top_status_source_cfg>::to_instance( +[]() { return &lnav_config.lc_top_status_cfg; }); @@ -134,7 +148,11 @@ ensure_dotlnav() for (const auto* sub_path : subdirs) { auto full_path = path / sub_path; - log_perror(mkdir(full_path.c_str(), 0755)); + if (mkdir(full_path.c_str(), 0755) == -1 && errno != EEXIST) { + log_error("unable to make directory: %s -- %s", + full_path.c_str(), + strerror(errno)); + } } auto crash_dir_path = path / "crash"; @@ -240,19 +258,23 @@ install_from_git(const std::string& repo) } auto finished_child = std::move(git_cmd).wait_for_child(); - if (!finished_child.was_normal_exit() || finished_child.exit_status() != 0) { return false; } + if (ghc::filesystem::is_directory(local_formats_path) + || ghc::filesystem::is_directory(local_configs_path)) + { + return false; + } if (!ghc::filesystem::is_directory(local_staging_path)) { auto um = lnav::console::user_message::error( attr_line_t("failed to install git repo: ") .append(lnav::roles::file(repo))) .with_reason( - attr_line_t("git failed to create the local directory") + attr_line_t("git failed to create the local directory ") .append( lnav::roles::file(local_staging_path.string()))); lnav::console::print(stderr, um); @@ -362,13 +384,11 @@ update_installs_from_git() git_dir.string()); int ret = system(pull_cmd.c_str()); if (ret == -1) { - std::cerr << "Failed to spawn command " - << "\"" << pull_cmd << "\": " << strerror(errno) - << std::endl; + std::cerr << "Failed to spawn command " << "\"" << pull_cmd + << "\": " << strerror(errno) << std::endl; retval = false; } else if (ret > 0) { - std::cerr << "Command " - << "\"" << pull_cmd + std::cerr << "Command " << "\"" << pull_cmd << "\" failed: " << strerror(errno) << std::endl; retval = false; } @@ -379,7 +399,7 @@ update_installs_from_git() if (!found) { printf( "No formats from git repositories found, " - "use 'lnav -i extra' to install third-party foramts\n"); + "use 'lnav -i extra' to install third-party formats\n"); } return retval; @@ -468,12 +488,17 @@ config_error_reporter(const yajlpp_parse_context& ypc, } static const struct json_path_container key_command_handlers = { + yajlpp::property_handler("id") + .with_synopsis("<id>") + .with_description( + "The identifier that can be used to refer to this key") + .for_field(&key_command::kc_id), yajlpp::property_handler("command") .with_synopsis("<command>") .with_description( "The command to execute for the given key sequence. Use a script " "to execute more complicated operations.") - .with_pattern("^[:|;].*") + .with_pattern("^$|^[:|;].*") .with_example(":goto next hour") .for_field(&key_command::kc_cmd), yajlpp::property_handler("alt-msg") @@ -495,6 +520,14 @@ static const struct json_path_container keymap_def_handlers = { [](const yajlpp_provider_context& ypc, key_map* km) { auto& retval = km->km_seq_to_cmd[ypc.get_substr("key_seq")]; + if (ypc.ypc_parse_context != nullptr) { + retval.kc_cmd.pp_path + = ypc.ypc_parse_context->get_full_path(); + retval.kc_cmd.pp_location.sl_source + = ypc.ypc_parse_context->ypc_source; + retval.kc_cmd.pp_location.sl_line_number + = ypc.ypc_parse_context->get_line_number(); + } return &retval; }) .with_path_provider<key_map>( @@ -541,6 +574,23 @@ static const struct json_path_container movement_handlers = { .for_field<>(&_lnav_config::lc_ui_movement, &movement_config::mode), }; +static const json_path_handler_base::enum_value_t _mouse_mode_values[] = { + {"disabled", lnav_mouse_mode::disabled}, + {"enabled", lnav_mouse_mode::enabled}, + + json_path_handler_base::ENUM_TERMINATOR, +}; + +static const struct json_path_container mouse_handlers = { + yajlpp::property_handler("mode") + .with_synopsis("enabled|disabled") + .with_enum_values(_mouse_mode_values) + .with_example("enabled") + .with_example("disabled") + .with_description("Overall control for mouse support") + .for_field<>(&_lnav_config::lc_mouse_mode), +}; + static const struct json_path_container global_var_handlers = { yajlpp::pattern_property_handler("(?<var_name>\\w+)") .with_synopsis("<name>") @@ -595,6 +645,10 @@ static const struct json_path_container theme_styles_handlers = { .with_description("Styling for plain text") .for_child(&lnav_theme::lt_style_text) .with_children(style_config_handlers), + yajlpp::property_handler("selected-text") + .with_description("Styling for text selected in a view") + .for_child(&lnav_theme::lt_style_selected_text) + .with_children(style_config_handlers), yajlpp::property_handler("alt-text") .with_description("Styling for plain text when alternating") .for_child(&lnav_theme::lt_style_alt_text) @@ -623,6 +677,10 @@ static const struct json_path_container theme_styles_handlers = { .with_description("Styling for the cursor line in the main view") .for_child(&lnav_theme::lt_style_cursor_line) .with_children(style_config_handlers), + yajlpp::property_handler("disabled-cursor-line") + .with_description("Styling for the cursor line when it is disabled") + .for_child(&lnav_theme::lt_style_disabled_cursor_line) + .with_children(style_config_handlers), yajlpp::property_handler("adjusted-time") .with_description("Styling for timestamps that have been adjusted") .for_child(&lnav_theme::lt_style_adjusted_time) @@ -632,8 +690,12 @@ static const struct json_path_container theme_styles_handlers = { "Styling for timestamps that are different from the received time") .for_child(&lnav_theme::lt_style_skewed_time) .with_children(style_config_handlers), + yajlpp::property_handler("file-offset") + .with_description("Styling for a file offset") + .for_child(&lnav_theme::lt_style_file_offset) + .with_children(style_config_handlers), yajlpp::property_handler("offset-time") - .with_description("Styling for hidden fields") + .with_description("Styling for the elapsed time column") .for_child(&lnav_theme::lt_style_offset_time) .with_children(style_config_handlers), yajlpp::property_handler("invalid-msg") @@ -660,6 +722,12 @@ static const struct json_path_container theme_styles_handlers = { .with_description("Styling for top-level headers") .with_obj_provider<style_config, lnav_theme>( [](const yajlpp_provider_context& ypc, lnav_theme* root) { + if (ypc.ypc_parse_context != nullptr + && root->lt_style_header[0].pp_path.empty()) + { + root->lt_style_header[0].pp_path + = ypc.ypc_parse_context->get_full_path(); + } return &root->lt_style_header[0].pp_value; }) .with_children(style_config_handlers), @@ -667,6 +735,12 @@ static const struct json_path_container theme_styles_handlers = { .with_description("Styling for 2nd-level headers") .with_obj_provider<style_config, lnav_theme>( [](const yajlpp_provider_context& ypc, lnav_theme* root) { + if (ypc.ypc_parse_context != nullptr + && root->lt_style_header[1].pp_path.empty()) + { + root->lt_style_header[1].pp_path + = ypc.ypc_parse_context->get_full_path(); + } return &root->lt_style_header[1].pp_value; }) .with_children(style_config_handlers), @@ -674,6 +748,12 @@ static const struct json_path_container theme_styles_handlers = { .with_description("Styling for 3rd-level headers") .with_obj_provider<style_config, lnav_theme>( [](const yajlpp_provider_context& ypc, lnav_theme* root) { + if (ypc.ypc_parse_context != nullptr + && root->lt_style_header[2].pp_path.empty()) + { + root->lt_style_header[2].pp_path + = ypc.ypc_parse_context->get_full_path(); + } return &root->lt_style_header[2].pp_value; }) .with_children(style_config_handlers), @@ -681,6 +761,12 @@ static const struct json_path_container theme_styles_handlers = { .with_description("Styling for 4th-level headers") .with_obj_provider<style_config, lnav_theme>( [](const yajlpp_provider_context& ypc, lnav_theme* root) { + if (ypc.ypc_parse_context != nullptr + && root->lt_style_header[3].pp_path.empty()) + { + root->lt_style_header[3].pp_path + = ypc.ypc_parse_context->get_full_path(); + } return &root->lt_style_header[3].pp_value; }) .with_children(style_config_handlers), @@ -688,6 +774,12 @@ static const struct json_path_container theme_styles_handlers = { .with_description("Styling for 5th-level headers") .with_obj_provider<style_config, lnav_theme>( [](const yajlpp_provider_context& ypc, lnav_theme* root) { + if (ypc.ypc_parse_context != nullptr + && root->lt_style_header[4].pp_path.empty()) + { + root->lt_style_header[4].pp_path + = ypc.ypc_parse_context->get_full_path(); + } return &root->lt_style_header[4].pp_value; }) .with_children(style_config_handlers), @@ -695,6 +787,12 @@ static const struct json_path_container theme_styles_handlers = { .with_description("Styling for 6th-level headers") .with_obj_provider<style_config, lnav_theme>( [](const yajlpp_provider_context& ypc, lnav_theme* root) { + if (ypc.ypc_parse_context != nullptr + && root->lt_style_header[5].pp_path.empty()) + { + root->lt_style_header[5].pp_path + = ypc.ypc_parse_context->get_full_path(); + } return &root->lt_style_header[5].pp_value; }) .with_children(style_config_handlers), @@ -742,9 +840,17 @@ static const struct json_path_container theme_styles_handlers = { .with_description("Styling for snippet borders") .for_child(&lnav_theme::lt_style_snippet_border) .with_children(style_config_handlers), + yajlpp::property_handler("indent-guide") + .with_description("Styling for indent guide lines") + .for_child(&lnav_theme::lt_style_indent_guide) + .with_children(style_config_handlers), }; static const struct json_path_container theme_syntax_styles_handlers = { + yajlpp::property_handler("inline-code") + .with_description("Styling for inline code blocks") + .for_child(&lnav_theme::lt_style_inline_code) + .with_children(style_config_handlers), yajlpp::property_handler("quoted-code") .with_description("Styling for quoted code blocks") .for_child(&lnav_theme::lt_style_quoted_code) @@ -778,10 +884,35 @@ static const struct json_path_container theme_syntax_styles_handlers = { .with_description("Styling for symbols in source files") .for_child(&lnav_theme::lt_style_symbol) .with_children(style_config_handlers), + yajlpp::property_handler("null") + .with_description("Styling for nulls in source files") + .for_child(&lnav_theme::lt_style_null) + .with_children(style_config_handlers), + yajlpp::property_handler("ascii-control") + .with_description( + "Styling for ASCII control characters in source files") + .for_child(&lnav_theme::lt_style_ascii_ctrl) + .with_children(style_config_handlers), + yajlpp::property_handler("non-ascii") + .with_description("Styling for non-ASCII characters in source files") + .for_child(&lnav_theme::lt_style_non_ascii) + .with_children(style_config_handlers), yajlpp::property_handler("number") .with_description("Styling for numbers in source files") .for_child(&lnav_theme::lt_style_number) .with_children(style_config_handlers), + yajlpp::property_handler("type") + .with_description("Styling for types in source files") + .for_child(&lnav_theme::lt_style_type) + .with_children(style_config_handlers), + yajlpp::property_handler("function") + .with_description("Styling for functions in source files") + .for_child(&lnav_theme::lt_style_function) + .with_children(style_config_handlers), + yajlpp::property_handler("separators-references-accessors") + .with_description("Styling for sigils in source files") + .for_child(&lnav_theme::lt_style_sep_ref_acc) + .with_children(style_config_handlers), yajlpp::property_handler("re-special") .with_description( "Styling for special characters in regular expressions") @@ -876,6 +1007,10 @@ static const struct json_path_container theme_status_styles_handlers = { .with_description("Styling for hotkey highlights of status bars") .for_child(&lnav_theme::lt_style_status_hotkey) .with_children(style_config_handlers), + yajlpp::property_handler("suggestion") + .with_description("Styling for suggested values") + .for_child(&lnav_theme::lt_style_suggestion) + .with_children(style_config_handlers), }; static const struct json_path_container theme_log_level_styles_handlers = { @@ -1029,6 +1164,9 @@ static const struct json_path_container ui_handlers = { yajlpp::property_handler("theme-defs") .with_description("Theme definitions.") .with_children(theme_defs_handlers), + yajlpp::property_handler("mouse") + .with_description("Mouse-related settings") + .with_children(mouse_handlers), yajlpp::property_handler("movement") .with_description("Log file cursor movement mode settings") .with_children(movement_handlers), @@ -1057,6 +1195,27 @@ static const struct json_path_container archive_handlers = { &archive_manager::config::amc_cache_ttl), }; +static const struct json_path_container piper_handlers = { + yajlpp::property_handler("max-size") + .with_synopsis("<bytes>") + .with_description("The maximum size of a capture file") + .with_min_value(128) + .for_field(&_lnav_config::lc_piper, &lnav::piper::config::c_max_size), + yajlpp::property_handler("rotations") + .with_synopsis("<count>") + .with_min_value(2) + .with_description("The number of rotated files to keep") + .for_field(&_lnav_config::lc_piper, &lnav::piper::config::c_rotations), + yajlpp::property_handler("ttl") + .with_synopsis("<duration>") + .with_description( + "The time-to-live for captured data, expressed as a duration " + "(e.g. '3d' for three days)") + .with_example("3d") + .with_example("12h") + .for_field(&_lnav_config::lc_piper, &lnav::piper::config::c_ttl), +}; + static const struct json_path_container file_vtab_handlers = { yajlpp::property_handler("max-content-size") .with_synopsis("<bytes>") @@ -1211,7 +1370,7 @@ static const struct json_path_container log_source_watch_expr_handlers = { }; static const struct json_path_container log_source_watch_handlers = { - yajlpp::pattern_property_handler("(?<watch_name>[\\w\\-]+)") + yajlpp::pattern_property_handler("(?<watch_name>[\\w\\.\\-]+)") .with_synopsis("<name>") .with_description("A log message watch expression") .with_obj_provider<logfile_sub_source_ns::watch_expression, @@ -1235,16 +1394,98 @@ static const struct json_path_container log_source_watch_handlers = { .with_children(log_source_watch_expr_handlers), }; +static const struct json_path_container annotation_handlers = { + yajlpp::property_handler("description") + .with_synopsis("<text>") + .with_description("A description of this annotation") + .for_field(&lnav::log::annotate::annotation_def::a_description), + yajlpp::property_handler("condition") + .with_synopsis("<SQL-expression>") + .with_description( + "The SQLite expression to execute for a log message that " + "determines whether or not this annotation is applicable. The " + "expression is evaluated the same way as a filter expression") + .with_min_length(1) + .for_field(&lnav::log::annotate::annotation_def::a_condition), + yajlpp::property_handler("handler") + .with_synopsis("<script>") + .with_description("The script to execute to generate the annotation " + "content. A JSON object with the log message content " + "will be sent to the script on the standard input") + .with_min_length(1) + .for_field(&lnav::log::annotate::annotation_def::a_handler), +}; + +static const struct json_path_container annotations_handlers = { + yajlpp::pattern_property_handler(R"((?<annotation_name>[\w\.\-]+))") + .with_obj_provider<lnav::log::annotate::annotation_def, _lnav_config>( + [](const yajlpp_provider_context& ypc, _lnav_config* root) { + auto* retval = &(root->lc_log_annotations + .a_definitions[ypc.get_substr_i(0)]); + + return retval; + }) + .with_path_provider<_lnav_config>( + [](struct _lnav_config* cfg, std::vector<std::string>& paths_out) { + for (const auto& iter : cfg->lc_log_annotations.a_definitions) { + paths_out.emplace_back(iter.first.to_string()); + } + }) + .with_children(annotation_handlers), +}; + +static const struct json_path_container log_date_time_handlers = { + yajlpp::property_handler("convert-zoned-to-local") + .with_description("Convert timestamps with ") + .with_pattern(R"(^[\w\-]+(?!\.lnav)$)") + .for_field(&_lnav_config::lc_log_date_time, + &date_time_scanner_ns::config::c_zoned_to_local), +}; + static const struct json_path_container log_source_handlers = { + yajlpp::property_handler("date-time") + .with_description("Settings related to log message dates and times") + .with_children(log_date_time_handlers), yajlpp::property_handler("watch-expressions") .with_description("Log message watch expressions") .with_children(log_source_watch_handlers), + yajlpp::property_handler("annotations").with_children(annotations_handlers), +}; + +static const struct json_path_container url_scheme_handlers = { + yajlpp::property_handler("handler") + .with_description( + "The name of the lnav script that can handle URLs " + "with of this scheme. This should not include the '.lnav' suffix.") + .with_pattern(R"(^[\w\-]+(?!\.lnav)$)") + .for_field(&lnav::url_handler::scheme::p_handler), +}; + +static const struct json_path_container url_handlers = { + yajlpp::pattern_property_handler(R"((?<url_scheme>[a-z][\w\-\+\.]+))") + .with_description("Definition of a custom URL scheme") + .with_obj_provider<lnav::url_handler::scheme, _lnav_config>( + [](const yajlpp_provider_context& ypc, _lnav_config* root) { + auto& retval = root->lc_url_handlers + .c_schemes[ypc.get_substr("url_scheme")]; + return &retval; + }) + .with_path_provider<_lnav_config>( + [](struct _lnav_config* cfg, std::vector<std::string>& paths_out) { + for (const auto& iter : cfg->lc_url_handlers.c_schemes) { + paths_out.emplace_back(iter.first); + } + }) + .with_children(url_scheme_handlers), }; static const struct json_path_container tuning_handlers = { yajlpp::property_handler("archive-manager") .with_description("Settings related to opening archive files") .with_children(archive_handlers), + yajlpp::property_handler("piper") + .with_description("Settings related to capturing piped data") + .with_children(piper_handlers), yajlpp::property_handler("file-vtab") .with_description("Settings related to the lnav_file virtual-table") .with_children(file_vtab_handlers), @@ -1257,6 +1498,9 @@ static const struct json_path_container tuning_handlers = { yajlpp::property_handler("clipboard") .with_description("Settings related to the clipboard") .with_children(sysclip_handlers), + yajlpp::property_handler("url-scheme") + .with_description("Settings related to custom URL handling") + .with_children(url_handlers), }; const char* DEFAULT_CONFIG_SCHEMA @@ -1288,10 +1532,10 @@ read_id(yajlpp_parse_context* ypc, const unsigned char* str, size_t len) } ypc->report_error( lnav::console::user_message::error( - attr_line_t("'") - .append(lnav::roles::symbol(file_id)) + attr_line_t() + .append_quoted(lnav::roles::symbol(file_id)) .append( - "' is not a supported configuration $schema version")) + " is not a supported configuration $schema version")) .with_snippet(ypc->get_snippet()) .with_note(notes) .with_help(handler->get_help_text(ypc))); @@ -1326,14 +1570,74 @@ const json_path_container lnav_config_handlers = json_path_container { class active_key_map_listener : public lnav_config_listener { public: + active_key_map_listener() : lnav_config_listener(__FILE__) {} + void reload_config(error_reporter& reporter) override { lnav_config.lc_active_keymap = lnav_config.lc_ui_keymaps["default"]; for (const auto& pair : lnav_config.lc_ui_keymaps[lnav_config.lc_ui_keymap].km_seq_to_cmd) { - lnav_config.lc_active_keymap.km_seq_to_cmd[pair.first] - = pair.second; + if (pair.second.kc_cmd.pp_value.empty()) { + lnav_config.lc_active_keymap.km_seq_to_cmd.erase(pair.first); + } else { + lnav_config.lc_active_keymap.km_seq_to_cmd[pair.first] + = pair.second; + } + } + + auto& ec = injector::get<exec_context&>(); + for (const auto& pair : lnav_config.lc_active_keymap.km_seq_to_cmd) { + if (pair.second.kc_id.empty()) { + continue; + } + + auto keyseq_sf = string_fragment::from_str(pair.first); + std::string keystr; + if (keyseq_sf.startswith("f")) { + auto sv = keyseq_sf.to_string_view(); + int32_t value; + auto scan_res = scn::scan(sv, "f{}", value); + if (!scan_res) { + log_error("invalid function key sequence: %s", keyseq_sf); + continue; + } + if (value < 0 || value > 64) { + log_error("invalid function key number: %s", keyseq_sf); + continue; + } + + keystr = toupper(pair.first); + } else { + auto sv + = string_fragment::from_str(pair.first).to_string_view(); + while (!sv.empty()) { + int32_t value; + auto scan_res = scn::scan(sv, "x{:2x}", value); + if (!scan_res) { + log_error("invalid key sequence: %s", + pair.first.c_str()); + break; + } + auto ch = (char) (value & 0xff); + switch (ch) { + case '\t': + keystr.append("TAB"); + break; + case '\r': + keystr.append("ENTER"); + break; + default: + keystr.push_back(ch); + break; + } + sv = scan_res.range_as_string_view(); + } + } + + if (!keystr.empty()) { + ec.ec_global_vars[pair.second.kc_id] = keystr; + } } } }; @@ -1398,11 +1702,10 @@ load_config_from(_lnav_config& lconfig, .with_errno_reason()); } } else { - auto_mem<yajl_handle_t> handle(yajl_free); char buffer[2048]; ssize_t rc = -1; - handle = yajl_alloc(&ypc.ypc_callbacks, nullptr, &ypc); + auto handle = yajlpp::alloc_handle(&ypc.ypc_callbacks, &ypc); yajl_config(handle, yajl_allow_comments, 1); yajl_config(handle, yajl_allow_multiple_values, 1); ypc.ypc_handle = handle; @@ -1438,10 +1741,10 @@ load_default_config(struct _lnav_config& config_obj, { yajlpp_parse_context ypc_builtin(intern_string::lookup(bsf.get_name()), &lnav_config_handlers); - auto_mem<yajl_handle_t> handle(yajl_free); struct config_userdata ud(errors); - handle = yajl_alloc(&ypc_builtin.ypc_callbacks, nullptr, &ypc_builtin); + auto handle + = yajlpp::alloc_handle(&ypc_builtin.ypc_callbacks, &ypc_builtin); ypc_builtin.ypc_locations = &lnav_config_locations; ypc_builtin.with_handle(handle); ypc_builtin.with_obj(config_obj); @@ -1455,9 +1758,7 @@ load_default_config(struct _lnav_config& config_obj, yajl_config(handle, yajl_allow_comments, 1); yajl_config(handle, yajl_allow_multiple_values, 1); - if (ypc_builtin.parse(bsf.to_string_fragment()) == yajl_status_ok) { - ypc_builtin.complete_parse(); - } + ypc_builtin.parse_doc(bsf.to_string_fragment()); return path == "*" || ypc_builtin.ypc_active_paths.empty(); } @@ -1541,6 +1842,19 @@ load_config(const std::vector<ghc::filesystem::path>& extra_paths, rollback_lnav_config = lnav_config; } +std::string +dump_config() +{ + yajlpp_gen gen; + yajlpp_gen_context ygc(gen, lnav_config_handlers); + + yajl_gen_config(gen, yajl_gen_beautify, true); + ygc.with_obj(lnav_config); + ygc.gen(); + + return gen.to_string_fragment().to_string(); +} + void reset_config(const std::string& path) { @@ -1627,36 +1941,46 @@ save_config() void reload_config(std::vector<lnav::console::user_message>& errors) { - lnav_config_listener* curr = lnav_config_listener::LISTENER_LIST; + auto* curr = lnav_config_listener::LISTENER_LIST; while (curr != nullptr) { auto reporter = [&errors](const void* cfg_value, const lnav::console::user_message& errmsg) { + log_error("configuration error: %s", + errmsg.to_attr_line().get_string().c_str()); auto cb = [&cfg_value, &errors, &errmsg]( const json_path_handler_base& jph, const std::string& path, - void* mem) { + const void* mem) { if (mem != cfg_value) { return; } auto loc_iter = lnav_config_locations.find(intern_string::lookup(path)); - if (loc_iter == lnav_config_locations.end()) { - return; + auto has_loc = loc_iter != lnav_config_locations.end(); + auto um = has_loc + ? lnav::console::user_message::error( + attr_line_t() + .append("invalid value for property ") + .append_quoted(lnav::roles::symbol(path))) + .with_reason(errmsg) + : errmsg; + um.with_help(jph.get_help_text(path)); + + if (has_loc) { + um.with_snippet( + lnav::console::snippet::from(loc_iter->second.sl_source, + "") + .with_line(loc_iter->second.sl_line_number)); + } else { + um.um_message + = attr_line_t() + .append("missing value for property ") + .append_quoted(lnav::roles::symbol(path)); } - errors.emplace_back( - lnav::console::user_message::error( - attr_line_t() - .append("invalid value for property ") - .append_quoted(lnav::roles::symbol(path))) - .with_reason(errmsg) - .with_snippet( - lnav::console::snippet::from( - loc_iter->second.sl_source, "") - .with_line(loc_iter->second.sl_line_number)) - .with_help(jph.get_help_text(path))); + errors.emplace_back(um); }; for (const auto& jph : lnav_config_handlers.jpc_children) { |