#ifndef option_manager_hh_INCLUDED #define option_manager_hh_INCLUDED #include "completion.hh" #include "exception.hh" #include "option_types.hh" #include "utils.hh" #include namespace Kakoune { struct option_not_found : public runtime_error { option_not_found(const String& name) : runtime_error("option not found: " + name) {} }; class OptionManager; class Option : public SafeCountable { public: enum class Flags { None = 0, Hidden = 1, }; Option(OptionManager& manager, String name, Flags flags); virtual ~Option() {} 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(const String& str) = 0; virtual void add_from_string(const String& str) = 0; String name() const { return m_name; } OptionManager& manager() const { return m_manager; } virtual Option* clone(OptionManager& manager) const = 0; Flags flags() const { return m_flags; } friend constexpr Flags operator|(Flags lhs, Flags rhs) { return (Flags)((int)lhs | (int)rhs); } friend constexpr bool operator&(Flags lhs, Flags rhs) { return (bool)((int)lhs & (int)rhs); } protected: OptionManager& m_manager; String m_name; Flags m_flags; }; class OptionManagerWatcher { public: virtual ~OptionManagerWatcher() {} virtual void on_option_changed(const Option& option) = 0; }; class OptionManager : private OptionManagerWatcher { public: OptionManager(OptionManager& parent); ~OptionManager(); const Option& operator[] (const String& name) const; Option& get_local_option(const String& name); CandidateList complete_option_name(const String& prefix, ByteCount cursor_pos); using OptionList = std::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 GlobalOptions; std::vector> m_options; OptionManager* m_parent; std::vector m_watchers; }; template using OptionChecker = std::function; template class TypedOption : public Option { public: TypedOption(OptionManager& manager, String name, Option::Flags flags, const T& value, OptionChecker checker) : Option(manager, std::move(name), flags), m_value(value), m_checker(std::move(checker)) {} void set(T value) { if (m_value != value) { if (m_checker) m_checker(value); m_value = std::move(value); m_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(const String& str) override { T val; option_from_string(str, val); set(std::move(val)); } void add_from_string(const String& str) override { T val; option_from_string(str, val); if (m_checker) m_checker(val); if (option_add(m_value, val)) m_manager.on_option_changed(*this); } Option* clone(OptionManager& manager) const override { return new TypedOption{manager, name(), flags(), m_value, m_checker}; } private: T m_value; OptionChecker m_checker; }; template const T& Option::get() const { auto* typed_opt = dynamic_cast*>(this); if (not typed_opt) throw runtime_error("option " + name() + " is not of type " + 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("option " + name() + " is not of type " + 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, const String& name) -> decltype(container.begin()) { using ptr_type = decltype(*container.begin()); return find_if(container, [&name](const ptr_type& opt) { return opt->name() == name; }); } class GlobalOptions : public OptionManager, public Singleton { public: GlobalOptions(); template Option& declare_option(const String& name, const T& value, Option::Flags flags = Option::Flags::None, OptionChecker checker = OptionChecker{}) { auto it = find_option(m_options, name); if (it != m_options.end()) { if ((*it)->is_of_type() and (*it)->flags() == flags) return **it; throw runtime_error("option " + name + " already declared with different type or flags"); } m_options.emplace_back(new TypedOption{*this, name, flags, value, std::move(checker)}); return *m_options.back(); } }; struct OptionManagerRegisterFuncs { static void insert(OptionManager& options, OptionManagerWatcher& watcher) { options.register_watcher(watcher); } static void remove(OptionManager& options, OptionManagerWatcher& watcher) { options.unregister_watcher(watcher); } }; class OptionManagerWatcher_AutoRegister : public OptionManagerWatcher, public AutoRegister { public: OptionManagerWatcher_AutoRegister(OptionManager& options) : AutoRegister(options) {} OptionManager& options() { return registry(); } }; } #endif // option_manager_hh_INCLUDED