forked from mawww/kakoune
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathoption_manager.hh
293 lines (239 loc) · 8.92 KB
/
option_manager.hh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
#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 <memory>
#include <utility>
namespace Kakoune
{
class OptionManager;
class Context;
enum class OptionFlags
{
None = 0,
Hidden = 1,
};
constexpr bool with_bit_ops(Meta::Type<OptionFlags>) { 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<MemoryDomain::Options>
{
public:
virtual ~Option() = default;
template<typename T> const T& get() const;
template<typename T> T& get_mutable();
template<typename T> void set(const T& val, bool notify=true);
template<typename T> bool is_of_type() const;
virtual String get_as_string(Quoting quoting) const = 0;
virtual Vector<String> get_as_strings() const = 0;
virtual String get_desc_string() const = 0;
virtual void set_from_strings(ConstArrayView<String> strs) = 0;
virtual void add_from_strings(ConstArrayView<String> strs) = 0;
virtual void remove_from_strings(ConstArrayView<String> 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();
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<decltype(first)>(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<StringView, std::unique_ptr<Option>, MemoryDomain::Options>;
OptionMap m_options;
OptionManager* m_parent;
mutable Vector<OptionManagerWatcher*, MemoryDomain::Options> m_watchers;
};
template<typename T>
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<String> 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<int, T> or std::is_same_v<bool, T> or std::is_same_v<String, T>)
return option_to_string(m_value, Quoting::Raw);
else
return "...";
}
void set_from_strings(ConstArrayView<String> strs) override
{
set(option_from_strings(Meta::Type<T>{}, strs));
}
void add_from_strings(ConstArrayView<String> strs) override
{
if (option_add_from_strings(m_value, strs))
m_manager.on_option_changed(*this);
}
void remove_from_strings(ConstArrayView<String> 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<T>() and other.get<T>() == m_value;
}
private:
virtual void validate(const T& value) const {}
T m_value;
};
template<typename T, void (*validator)(const T&)>
class TypedCheckedOption : public TypedOption<T>
{
using TypedOption<T>::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<typename T> const T& Option::get() const
{
auto* typed_opt = dynamic_cast<const TypedOption<T>*>(this);
if (not typed_opt)
throw runtime_error(format("option '{}' is not of type '{}'", name(),
option_type_name(Meta::Type<T>{})));
return typed_opt->get();
}
template<typename T> T& Option::get_mutable()
{
return const_cast<T&>(get<T>());
}
template<typename T> void Option::set(const T& val, bool notify)
{
auto* typed_opt = dynamic_cast<TypedOption<T>*>(this);
if (not typed_opt)
throw runtime_error(format("option '{}' is not of type '{}'", name(),
option_type_name(Meta::Type<T>{})));
return typed_opt->set(val, notify);
}
template<typename T> bool Option::is_of_type() const
{
return dynamic_cast<const TypedOption<T>*>(this) != nullptr;
}
class OptionsRegistry
{
public:
OptionsRegistry(OptionManager& global_manager) : m_global_manager(global_manager) {}
template<typename T, void (*validator)(const T&) = nullptr>
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<T>() 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<T>{}))
: format("[{}] - {}", option_type_name(Meta::Type<T>{}), docstring);
m_descs.emplace_back(new OptionDesc{name.str(), std::move(doc), flags});
return *opts.insert({m_descs.back()->name(),
std::make_unique<TypedCheckedOption<T, validator>>(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<const OptionDesc>& 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<Option>&& option) { m_option_trash.push_back(std::move(option)); }
private:
OptionManager& m_global_manager;
Vector<std::unique_ptr<const OptionDesc>, MemoryDomain::Options> m_descs;
Vector<std::unique_ptr<Option>> m_option_trash;
};
}
#endif // option_manager_hh_INCLUDED