#ifndef option_manager_hh_INCLUDED #define option_manager_hh_INCLUDED #include "completion.hh" #include "containers.hh" #include "exception.hh" #include "flags.hh" #include "option_types.hh" #include "regex.hh" #include "vector.hh" namespace Kakoune { class OptionManager; enum class OptionFlags { None = 0, Hidden = 1, }; template<> struct WithBitOps : std::true_type {}; 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: virtual ~Option() = default; template const T& get() const; template void set(const T& val); template bool is_of_type() const; virtual String get_as_string() const = 0; virtual void set_from_string(StringView str) = 0; virtual void add_from_string(StringView str) = 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 ~OptionManagerWatcher() {} virtual void on_option_changed(const Option& option) = 0; }; class OptionManager : private OptionManagerWatcher { public: OptionManager(OptionManager& parent); ~OptionManager(); Option& operator[] (StringView name); const Option& operator[] (StringView name) const; Option& get_local_option(StringView name); CandidateList complete_option_name(StringView prefix, ByteCount cursor_pos); using OptionList = Vector; OptionList flatten_options() const; void register_watcher(OptionManagerWatcher& watcher); void unregister_watcher(OptionManagerWatcher& watcher); 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; template CandidateList get_matching_names(MatchingFunc func); Vector, MemoryDomain::Options> m_options; OptionManager* m_parent; 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) { if (m_value != value) { m_value = std::move(value); manager().on_option_changed(*this); } } const T& get() const { return m_value; } String get_as_string() const override { return option_to_string(m_value); } void set_from_string(StringView str) override { T val; option_from_string(str, val); set(std::move(val)); } void add_from_string(StringView str) override { T val; option_from_string(str, val); if (option_add(m_value, val)) m_manager.on_option_changed(*this); } Option* clone(OptionManager& manager) const override { return new TypedOption{manager, m_desc, m_value}; } using Alloc = Allocator; static void* operator new (std::size_t sz) { kak_assert(sz == sizeof(TypedOption)); return Alloc{}.allocate(1); } static void operator delete (void* ptr) { return Alloc{}.deallocate(reinterpret_cast(ptr), 1); } private: T m_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(), typeid(T).name())); return typed_opt->get(); } template void Option::set(const T& val) { auto* typed_opt = dynamic_cast*>(this); if (not typed_opt) throw runtime_error(format("option '{}' is not of type '{}'", name(), typeid(T).name())); return typed_opt->set(val); } template bool Option::is_of_type() const { return dynamic_cast*>(this) != nullptr; } template auto find_option(T& container, StringView name) -> decltype(container.begin()) { using ptr_type = decltype(*container.begin()); return find_if(container, [&name](const ptr_type& opt) { return opt->name() == name; }); } class OptionsRegistry { public: OptionsRegistry(OptionManager& global_manager) : m_global_manager(global_manager) {} template Option& declare_option(const String& name, const String& docstring, const T& value, OptionFlags flags = OptionFlags::None) { auto& opts = m_global_manager.m_options; auto it = find_option(opts, name); if (it != opts.end()) { if ((*it)->is_of_type() and (*it)->flags() == flags) return **it; throw runtime_error(format("option '{}' already declared with different type or flags", name)); } m_descs.emplace_back(new OptionDesc{name, docstring, flags}); opts.emplace_back(new TypedOption{m_global_manager, *m_descs.back(), value}); return *opts.back(); } bool option_exists(StringView name) const { return find_if(m_descs, [&name](const std::unique_ptr& opt) { return opt->name() == name; }) != m_descs.end(); } private: OptionManager& m_global_manager; Vector, MemoryDomain::Options> m_descs; }; } #endif // option_manager_hh_INCLUDED