#ifndef option_manager_hh_INCLUDED #define option_manager_hh_INCLUDED #include "completion.hh" #include "exception.hh" #include "hash_map.hh" #include "option.hh" #include "ranges.hh" #include "utils.hh" #include "vector.hh" #include "string_utils.hh" #include #include namespace Kakoune { class OptionManager; class Context; enum class OptionFlags { None = 0, Hidden = 1, }; constexpr bool with_bit_ops(Meta::Type) { return true; } class OptionDesc { public: OptionDesc(String name, String docstring, OptionFlags flags); const String& name() const { return m_name; } const String& docstring() const { return m_docstring; } OptionFlags flags() const { return m_flags; } private: String m_name; String m_docstring; OptionFlags m_flags; }; class Option : public UseMemoryDomain { public: virtual ~Option() = default; template const T& get() const; template T& get_mutable(); template void set(const T& val, bool notify=true); template bool is_of_type() const; virtual String get_as_string(Quoting quoting) const = 0; virtual Vector get_as_strings() const = 0; virtual String get_desc_string() const = 0; virtual void set_from_strings(ConstArrayView strs) = 0; virtual void add_from_strings(ConstArrayView strs) = 0; virtual void remove_from_strings(ConstArrayView strs) = 0; virtual void update(const Context& context) = 0; virtual bool has_same_value(const Option& other) const = 0; virtual Option* clone(OptionManager& manager) const = 0; OptionManager& manager() const { return m_manager; } const String& name() const { return m_desc.name(); } const String& docstring() const { return m_desc.docstring(); } OptionFlags flags() const { return m_desc.flags(); } protected: Option(const OptionDesc& desc, OptionManager& manager); OptionManager& m_manager; const OptionDesc& m_desc; }; class OptionManagerWatcher { public: virtual void on_option_changed(const Option& option) = 0; }; class OptionManager final : private OptionManagerWatcher { public: OptionManager(OptionManager& parent); ~OptionManager(); void reparent(OptionManager& parent); Option& operator[] (StringView name); const Option& operator[] (StringView name) const; Option& get_local_option(StringView name); void unset_option(StringView name); auto flatten_options() const { auto merge = [](auto&& first, const OptionMap& second) { return concatenated(std::forward(first) | filter([&second](auto& i) { return not second.contains(i.key); }), second); }; static const OptionMap empty; auto& parent = m_parent ? m_parent->m_options : empty; auto& grand_parent = (m_parent and m_parent->m_parent) ? m_parent->m_parent->m_options : empty; return merge(merge(grand_parent, parent), m_options) | transform(&OptionMap::Item::value); } void register_watcher(OptionManagerWatcher& watcher) const; void unregister_watcher(OptionManagerWatcher& watcher) const; void on_option_changed(const Option& option) override; private: OptionManager() : m_parent(nullptr) {} // the only one allowed to construct a root option manager friend class Scope; friend class OptionsRegistry; using OptionMap = HashMap, MemoryDomain::Options>; OptionMap m_options; OptionManager* m_parent; mutable Vector m_watchers; }; template class TypedOption : public Option { public: TypedOption(OptionManager& manager, const OptionDesc& desc, const T& value) : Option(desc, manager), m_value(value) {} void set(T value, bool notify = true) { validate(value); if (m_value != value) { m_value = std::move(value); if (notify) manager().on_option_changed(*this); } } const T& get() const { return m_value; } T& get_mutable() { return m_value; } Vector get_as_strings() const override { return option_to_strings(m_value); } String get_as_string(Quoting quoting) const override { return option_to_string(m_value, quoting); } String get_desc_string() const override { if constexpr (std::is_same_v or std::is_same_v or std::is_same_v) return option_to_string(m_value, Quoting::Raw); else return "..."; } void set_from_strings(ConstArrayView strs) override { set(option_from_strings(Meta::Type{}, strs)); } void add_from_strings(ConstArrayView strs) override { if (option_add_from_strings(m_value, strs)) m_manager.on_option_changed(*this); } void remove_from_strings(ConstArrayView strs) override { if (option_remove_from_strings(m_value, strs)) m_manager.on_option_changed(*this); } void update(const Context& context) override { option_update(m_value, context); } bool has_same_value(const Option& other) const override { return other.is_of_type() and other.get() == m_value; } private: virtual void validate(const T& value) const {} T m_value; }; template class TypedCheckedOption : public TypedOption { using TypedOption::TypedOption; Option* clone(OptionManager& manager) const override { return new TypedCheckedOption{manager, this->m_desc, this->get()}; } void validate(const T& value) const override { if (validator != nullptr) validator(value); } }; template const T& Option::get() const { auto* typed_opt = dynamic_cast*>(this); if (not typed_opt) throw runtime_error(format("option '{}' is not of type '{}'", name(), option_type_name(Meta::Type{}))); return typed_opt->get(); } template T& Option::get_mutable() { return const_cast(get()); } template void Option::set(const T& val, bool notify) { auto* typed_opt = dynamic_cast*>(this); if (not typed_opt) throw runtime_error(format("option '{}' is not of type '{}'", name(), option_type_name(Meta::Type{}))); return typed_opt->set(val, notify); } template bool Option::is_of_type() const { return dynamic_cast*>(this) != nullptr; } class OptionsRegistry { public: OptionsRegistry(OptionManager& global_manager) : m_global_manager(global_manager) {} template Option& declare_option(StringView name, StringView docstring, const T& value, OptionFlags flags = OptionFlags::None) { auto is_option_identifier = [](char c) { return is_basic_alpha(c) or is_basic_digit(c) or c == '_'; }; if (not all_of(name, is_option_identifier)) throw runtime_error{format("name '{}' contains char out of [a-zA-Z0-9_]", name)}; auto& opts = m_global_manager.m_options; auto it = opts.find(name); if (it != opts.end()) { if (it->value->is_of_type() and it->value->flags() == flags) return *it->value; throw runtime_error{format("option '{}' already declared with different type or flags", name)}; } String doc = docstring.empty() ? format("[{}]", option_type_name(Meta::Type{})) : format("[{}] - {}", option_type_name(Meta::Type{}), docstring); m_descs.emplace_back(new OptionDesc{name.str(), std::move(doc), flags}); return *opts.insert({m_descs.back()->name(), std::make_unique>(m_global_manager, *m_descs.back(), value)}); } const OptionDesc* option_desc(StringView name) const { auto it = find_if(m_descs, [&name](const std::unique_ptr& opt) { return opt->name() == name; }); return it != m_descs.end() ? it->get() : nullptr; } bool option_exists(StringView name) const { return option_desc(name) != nullptr; } CandidateList complete_option_name(StringView prefix, ByteCount cursor_pos) const; void clear_option_trash() { m_option_trash.clear(); } void move_to_trash(std::unique_ptr