Strongly typed options support

* non builtins options require declaration using the decl command

* At the moment, only int and string options are supported, however
the goal of this change is to provide a consistent way to support
more complex options, namely lists and booleans
This commit is contained in:
Maxime Coste 2013-03-03 17:25:40 +01:00
parent a36befd653
commit fac222a427
16 changed files with 165 additions and 68 deletions

View File

@ -267,7 +267,7 @@ void CommandManager::execute(const String& command_line,
if (it->type() == Token::Type::OptionExpand) if (it->type() == Token::Type::OptionExpand)
{ {
const Option& option = context.options()[it->content()]; const Option& option = context.options()[it->content()];
params.push_back(option.as_string()); params.push_back(option.get_as_string());
} }
if (it->type() == Token::Type::CommandSeparator) if (it->type() == Token::Type::CommandSeparator)
{ {

View File

@ -466,7 +466,24 @@ void set_option(OptionManager& options, const CommandParameters& params,
if (params.size() != 2) if (params.size() != 2)
throw wrong_argument_count(); throw wrong_argument_count();
options.set_option(params[0], Option(params[1])); options.get_local_option(params[0]).set_from_string(params[1]);
}
void declare_option(const CommandParameters& params, Context& context)
{
if (params.size() != 2 and params.size() != 3)
throw wrong_argument_count();
Option* opt = nullptr;
if (params[0] == "int")
opt = &GlobalOptions::instance().declare_option<int>(params[1], 0);
else if (params[0] == "str")
opt = &GlobalOptions::instance().declare_option<String>(params[1], "");
else
throw runtime_error("unknown type " + params[0]);
if (params.size() == 3)
opt->set_from_string(params[2]);
} }
template<typename Func> template<typename Func>
@ -878,6 +895,7 @@ void register_commands()
[](const Context& context, const String& prefix, ByteCount cursor_pos) [](const Context& context, const String& prefix, ByteCount cursor_pos)
{ return context.window().options().complete_option_name(prefix, cursor_pos); } { return context.window().options().complete_option_name(prefix, cursor_pos); }
})); }));
cm.register_command("decl", declare_option);
cm.register_commands({"ca", "colalias"}, define_color_alias); cm.register_commands({"ca", "colalias"}, define_color_alias);
cm.register_commands({"name"}, set_client_name); cm.register_commands({"name"}, set_client_name);

View File

@ -36,7 +36,7 @@ CandidateList complete_filename(const Context& context,
String dirprefix; String dirprefix;
String fileprefix = real_prefix; String fileprefix = real_prefix;
boost::regex ignored_files = make_regex_ifp(context.options()["ignored_files"].as_string()); boost::regex ignored_files = make_regex_ifp(context.options()["ignored_files"].get<String>());
ByteCount dir_end = -1; ByteCount dir_end = -1;
for (ByteCount i = 0; i < real_prefix.length(); ++i) for (ByteCount i = 0; i < real_prefix.length(); ++i)

View File

@ -135,8 +135,8 @@ Buffer* create_buffer_from_file(const String& filename)
Buffer* buffer = new Buffer(filename, Buffer::Flags::File, std::move(lines)); Buffer* buffer = new Buffer(filename, Buffer::Flags::File, std::move(lines));
OptionManager& options = buffer->options(); OptionManager& options = buffer->options();
options.set_option("eolformat", Option(crlf ? "crlf" : "lf")); options.get_local_option("eolformat").set<String>(crlf ? "crlf" : "lf");
options.set_option("BOM", Option(bom ? "utf-8" : "no")); options.get_local_option("BOM").set<String>(bom ? "utf-8" : "no");
return buffer; return buffer;
} }
@ -159,7 +159,7 @@ static void write(int fd, const memoryview<char>& data, const String& filename)
void write_buffer_to_file(const Buffer& buffer, const String& filename) void write_buffer_to_file(const Buffer& buffer, const String& filename)
{ {
String eolformat = buffer.options()["eolformat"].as_string(); String eolformat = buffer.options()["eolformat"].get<String>();
if (eolformat == "crlf") if (eolformat == "crlf")
eolformat = "\r\n"; eolformat = "\r\n";
else else
@ -172,7 +172,7 @@ void write_buffer_to_file(const Buffer& buffer, const String& filename)
throw file_access_error(filename, strerror(errno)); throw file_access_error(filename, strerror(errno));
auto close_fd = on_scope_end([fd]{ close(fd); }); auto close_fd = on_scope_end([fd]{ close(fd); });
if (buffer.options()["BOM"].as_string() == "utf-8") if (buffer.options()["BOM"].get<String>() == "utf-8")
::write(fd, "\xEF\xBB\xBF", 3); ::write(fd, "\xEF\xBB\xBF", 3);
for (LineCount i = 0; i < buffer.line_count(); ++i) for (LineCount i = 0; i < buffer.line_count(); ++i)

View File

@ -37,7 +37,7 @@ void cleanup_whitespaces(Buffer& buffer, Selection& selection, String& content)
void expand_tabulations(Buffer& buffer, Selection& selection, String& content) void expand_tabulations(Buffer& buffer, Selection& selection, String& content)
{ {
const int tabstop = buffer.options()["tabstop"].as_int(); const int tabstop = buffer.options()["tabstop"].get<int>();
if (content == "\t") if (content == "\t")
{ {
int column = 0; int column = 0;

View File

@ -200,7 +200,7 @@ HighlighterAndId highlight_search_factory(const HighlighterParameters params)
void expand_tabulations(const OptionManager& options, DisplayBuffer& display_buffer) void expand_tabulations(const OptionManager& options, DisplayBuffer& display_buffer)
{ {
const int tabstop = options["tabstop"].as_int(); const int tabstop = options["tabstop"].get<int>();
for (auto& line : display_buffer.lines()) for (auto& line : display_buffer.lines())
{ {
for (auto atom_it = line.begin(); atom_it != line.end(); ++atom_it) for (auto atom_it = line.begin(); atom_it != line.end(); ++atom_it)

View File

@ -377,7 +377,7 @@ public:
DisplayCoord menu_pos{ context().ui().dimensions().line, 0_char }; DisplayCoord menu_pos{ context().ui().dimensions().line, 0_char };
context().ui().menu_show(candidates, menu_pos, MenuStyle::Prompt); context().ui().menu_show(candidates, menu_pos, MenuStyle::Prompt);
bool use_common_prefix = context().options()["complete_prefix"].as_int(); bool use_common_prefix = context().options()["complete_prefix"].get<int>();
String prefix = use_common_prefix ? common_prefix(candidates) : String(); String prefix = use_common_prefix ? common_prefix(candidates) : String();
if (m_completions.end - m_completions.start > prefix.length()) if (m_completions.end - m_completions.start > prefix.length())
prefix = line.substr(m_completions.start, prefix = line.substr(m_completions.start,

View File

@ -176,7 +176,7 @@ void do_search(Context& context)
RegisterManager::instance()['/'] = ex; RegisterManager::instance()['/'] = ex;
context.push_jump(); context.push_jump();
} }
else if (ex.empty() or not context.options()["incsearch"].as_int()) else if (ex.empty() or not context.options()["incsearch"].get<int>())
return; return;
context.editor().select(std::bind(select_next_match<forward>, _1, ex), mode); context.editor().select(std::bind(select_next_match<forward>, _1, ex), mode);
@ -338,7 +338,7 @@ void do_join(Context& context)
void do_indent(Context& context) void do_indent(Context& context)
{ {
size_t width = context.options()["indentwidth"].as_int(); size_t width = context.options()["indentwidth"].get<int>();
String indent(' ', width); String indent(' ', width);
Editor& editor = context.editor(); Editor& editor = context.editor();
@ -351,7 +351,7 @@ void do_indent(Context& context)
void do_deindent(Context& context) void do_deindent(Context& context)
{ {
int width = context.options()["indentwidth"].as_int(); int width = context.options()["indentwidth"].get<int>();
Editor& editor = context.editor(); Editor& editor = context.editor();
DynamicSelectionList sels{editor.buffer(), editor.selections()}; DynamicSelectionList sels{editor.buffer(), editor.selections()};
auto restore_sels = on_scope_end([&]{ editor.select((SelectionList)std::move(sels)); }); auto restore_sels = on_scope_end([&]{ editor.select((SelectionList)std::move(sels)); });
@ -745,7 +745,7 @@ void register_env_vars()
{ return runtime_directory(); }); { return runtime_directory(); });
shell_manager.register_env_var("opt_.+", shell_manager.register_env_var("opt_.+",
[](const String& name, const Context& context) [](const String& name, const Context& context)
{ return context.options()[name.substr(4_byte)].as_string(); }); { return context.options()[name.substr(4_byte)].get_as_string(); });
shell_manager.register_env_var("reg_.+", shell_manager.register_env_var("reg_.+",
[](const String& name, const Context& context) [](const String& name, const Context& context)
{ return RegisterManager::instance()[name[4]].values(context)[0]; }); { return RegisterManager::instance()[name[4]].values(context)[0]; });

View File

@ -6,6 +6,54 @@
namespace Kakoune namespace Kakoune
{ {
template<typename T>
class TypedOption : public Option
{
public:
TypedOption(OptionManager& manager, String name, const T& value)
: Option(manager, std::move(name)), m_value(value) {}
void set(const T& value)
{
if (m_value != value)
{
m_value = value;
m_manager.on_option_changed(*this);
}
}
const T& get() const { return m_value; }
String get_as_string() const override;
void set_from_string(const String& str) override;
Option* clone(OptionManager& manager) const override
{
return new TypedOption{manager, name(), m_value};
}
private:
T m_value;
};
Option::Option(OptionManager& manager, String name)
: m_manager(manager), m_name(std::move(name)) {}
template<typename T> const T& Option::get() const { return dynamic_cast<const TypedOption<T>*>(this)->get(); }
template<typename T> void Option::set(const T& val) { return dynamic_cast<TypedOption<T>*>(this)->set(val); }
// TypedOption<String> specializations;
template<> String TypedOption<String>::get_as_string() const { return m_value; }
template<> void TypedOption<String>::set_from_string(const String& str) { set(str); }
template class TypedOption<String>;
template const String& Option::get<String>() const;
template void Option::set<String>(const String&);
// TypedOption<int> specializations;
template<> String TypedOption<int>::get_as_string() const { return int_to_str(m_value); }
template<> void TypedOption<int>::set_from_string(const String& str) { set(str_to_int(str)); }
template class TypedOption<int>;
template const int& Option::get<int>() const;
template void Option::set<int>(const int&);
OptionManager::OptionManager(OptionManager& parent) OptionManager::OptionManager(OptionManager& parent)
: m_parent(&parent) : m_parent(&parent)
{ {
@ -33,23 +81,33 @@ void OptionManager::unregister_watcher(OptionManagerWatcher& watcher)
m_watchers.erase(it); m_watchers.erase(it);
} }
void OptionManager::set_option(const String& name, const Option& value) template<typename T>
auto find_option(T& container, const String& name) -> decltype(container.begin())
{ {
Option old_value = m_options[name]; using ptr_type = decltype(*container.begin());
m_options[name] = value; return find_if(container, [&name](const ptr_type& opt) { return opt->name() == name; });
}
if (old_value != value) Option& OptionManager::get_local_option(const String& name)
{
auto it = find_option(m_options, name);
if (it != m_options.end())
return **it;
else if (m_parent)
{ {
for (auto watcher : m_watchers) m_options.emplace_back((*m_parent)[name].clone(*this));
watcher->on_option_changed(name, value); return *m_options.back();
} }
else
throw option_not_found(name);
} }
const Option& OptionManager::operator[](const String& name) const const Option& OptionManager::operator[](const String& name) const
{ {
auto it = m_options.find(name); auto it = find_option(m_options, name);
if (it != m_options.end()) if (it != m_options.end())
return it->second; return **it;
else if (m_parent) else if (m_parent)
return (*m_parent)[name]; return (*m_parent)[name];
else else
@ -65,42 +123,60 @@ CandidateList OptionManager::complete_option_name(const String& prefix,
result = m_parent->complete_option_name(prefix, cursor_pos); result = m_parent->complete_option_name(prefix, cursor_pos);
for (auto& option : m_options) for (auto& option : m_options)
{ {
if (option.first.substr(0, real_prefix.length()) == real_prefix and const auto& name = option->name();
not contains(result, option.first)) if (name.substr(0, real_prefix.length()) == real_prefix and
result.push_back(option.first); not contains(result, name))
result.push_back(name);
} }
return result; return result;
} }
OptionManager::OptionMap OptionManager::flatten_options() const OptionManager::OptionList OptionManager::flatten_options() const
{ {
OptionMap res = m_parent ? m_parent->flatten_options() : OptionMap(); OptionList res = m_parent ? m_parent->flatten_options() : OptionList{};
for (auto& option : m_options) for (auto& option : m_options)
res.insert(option); {
auto it = find_option(res, option->name());
if (it != res.end())
*it = option.get();
else
res.emplace_back(option.get());
}
return res; return res;
} }
void OptionManager::on_option_changed(const String& name, const Option& value) void OptionManager::on_option_changed(const Option& option)
{ {
// if parent option changed, but we overrided it, it's like nothing happened // if parent option changed, but we overrided it, it's like nothing happened
if (m_options.find(name) != m_options.end()) if (&option.manager() != this and
find_option(m_options, option.name()) != m_options.end())
return; return;
for (auto watcher : m_watchers) for (auto watcher : m_watchers)
watcher->on_option_changed(name, value); watcher->on_option_changed(option);
} }
GlobalOptions::GlobalOptions() GlobalOptions::GlobalOptions()
: OptionManager() : OptionManager()
{ {
set_option("tabstop", Option(8)); declare_option<int>("tabstop", 8);
set_option("indentwidth", Option(4)); declare_option<int>("indentwidth", 4);
set_option("eolformat", Option("lf")); declare_option<String>("eolformat", "lf");
set_option("BOM", Option("no")); declare_option<String>("BOM", "no");
set_option("shell", Option("sh")); declare_option<String>("shell", "sh");
set_option("complete_prefix", Option(1)); declare_option<int>("complete_prefix", 1);
set_option("incsearch", Option(1)); declare_option<int>("incsearch", 1);
set_option("ignored_files", Option(R"(^(\..*|.*\.(o|so|a))$)")); declare_option<String>("ignored_files", R"(^(\..*|.*\.(o|so|a)))$)");
declare_option<String>("filetype", "");
}
template<typename T>
Option& GlobalOptions::declare_option(const String& name, const T& value)
{
if (find_option(m_options, name) != m_options.end())
throw runtime_error("option " + name + " already declared");
m_options.emplace_back(new TypedOption<T>{*this, name, value});
return *m_options.back();
} }
} }

View File

@ -16,23 +16,27 @@ struct option_not_found : public runtime_error
: runtime_error("option not found: " + name) {} : runtime_error("option not found: " + name) {}
}; };
class OptionManager;
class Option class Option
{ {
public: public:
Option() {} Option(OptionManager& manager, String name);
explicit Option(int value) : m_value(int_to_str(value)) {} virtual ~Option() {}
explicit Option(const String& value) : m_value(value) {}
Option& operator=(int value) { m_value = int_to_str(value); return *this; } template<typename T> const T& get() const;
Option& operator=(const String& value) { m_value = value; return *this; } template<typename T> void set(const T& val);
bool operator==(const Option& other) const { return m_value == other.m_value; } virtual String get_as_string() const = 0;
bool operator!=(const Option& other) const { return m_value != other.m_value; } virtual void set_from_string(const String& str) = 0;
int as_int() const { return str_to_int(m_value); } String name() const { return m_name; }
String as_string() const { return m_value; } OptionManager& manager() const { return m_manager; }
private:
String m_value; virtual Option* clone(OptionManager& manager) const = 0;
protected:
OptionManager& m_manager;
String m_name;
}; };
class OptionManagerWatcher class OptionManagerWatcher
@ -40,8 +44,7 @@ class OptionManagerWatcher
public: public:
virtual ~OptionManagerWatcher() {} virtual ~OptionManagerWatcher() {}
virtual void on_option_changed(const String& name, virtual void on_option_changed(const Option& option) = 0;
const Option& option) = 0;
}; };
class OptionManager : private OptionManagerWatcher class OptionManager : private OptionManagerWatcher
@ -51,29 +54,27 @@ public:
~OptionManager(); ~OptionManager();
const Option& operator[] (const String& name) const; const Option& operator[] (const String& name) const;
Option& get_local_option(const String& name);
void set_option(const String& name, const Option& value);
CandidateList complete_option_name(const String& prefix, CandidateList complete_option_name(const String& prefix,
ByteCount cursor_pos); ByteCount cursor_pos);
typedef std::unordered_map<String, Option> OptionMap; using OptionList = std::vector<const Option*>;
OptionMap flatten_options() const; OptionList flatten_options() const;
void register_watcher(OptionManagerWatcher& watcher); void register_watcher(OptionManagerWatcher& watcher);
void unregister_watcher(OptionManagerWatcher& watcher); void unregister_watcher(OptionManagerWatcher& watcher);
void on_option_changed(const Option& option) override;
private: private:
OptionManager() OptionManager()
: m_parent(nullptr) {} : m_parent(nullptr) {}
// the only one allowed to construct a root option manager // the only one allowed to construct a root option manager
friend class GlobalOptions; friend class GlobalOptions;
OptionMap m_options; std::vector<std::unique_ptr<Option>> m_options;
OptionManager* m_parent; OptionManager* m_parent;
void on_option_changed(const String& name, const Option& value);
std::vector<OptionManagerWatcher*> m_watchers; std::vector<OptionManagerWatcher*> m_watchers;
}; };
@ -82,8 +83,10 @@ class GlobalOptions : public OptionManager,
{ {
public: public:
GlobalOptions(); GlobalOptions();
};
template<typename T>
Option& declare_option(const String& name, const T& inital_value);
};
} }

View File

@ -1,4 +1,4 @@
setg termcmd %sh{ decl str termcmd %sh{
if [[ -n "$TMUX" ]]; then if [[ -n "$TMUX" ]]; then
echo "'tmux split-window -h'" echo "'tmux split-window -h'"
else else

View File

@ -1,4 +1,4 @@
setg grepcmd 'grep -RHn' decl str grepcmd 'grep -RHn'
def -shell-params -file-completion \ def -shell-params -file-completion \
grep %{ %sh{ grep %{ %sh{

View File

@ -1,4 +1,4 @@
setg makecmd make decl str makecmd make
def -shell-params make %{ %sh{ def -shell-params make %{ %sh{
output=$(mktemp -d -t kak-make.XXXXXXXX)/fifo output=$(mktemp -d -t kak-make.XXXXXXXX)/fifo

View File

@ -119,7 +119,7 @@ String ShellManager::pipe(const String& input,
++it; ++it;
} }
String shell = context.options()["shell"].as_string(); String shell = context.options()["shell"].get<String>();
std::vector<const char*> execparams = { shell.c_str(), "-c", cmdline.c_str() }; std::vector<const char*> execparams = { shell.c_str(), "-c", cmdline.c_str() };
if (not params.empty()) if (not params.empty())
execparams.push_back(shell.c_str()); execparams.push_back(shell.c_str());

View File

@ -30,7 +30,7 @@ Window::Window(Buffer& buffer)
m_builtin_highlighters.append({"selections", [this](DisplayBuffer& db) { highlight_selections(selections(), db); }}); m_builtin_highlighters.append({"selections", [this](DisplayBuffer& db) { highlight_selections(selections(), db); }});
for (auto& option : m_options.flatten_options()) for (auto& option : m_options.flatten_options())
on_option_changed(option.first, option.second); on_option_changed(*option);
} }
Window::~Window() Window::~Window()
@ -177,9 +177,9 @@ DisplayCoord Window::display_position(const BufferIterator& iterator)
return { 0, 0 }; return { 0, 0 };
} }
void Window::on_option_changed(const String& name, const Option& option) void Window::on_option_changed(const Option& option)
{ {
String desc = name + "=" + option.as_string(); String desc = option.name() + "=" + option.get_as_string();
Context hook_context{*this}; Context hook_context{*this};
m_hooks.run_hook("WinSetOption", desc, hook_context); m_hooks.run_hook("WinSetOption", desc, hook_context);
} }

View File

@ -51,7 +51,7 @@ public:
private: private:
Window(const Window&) = delete; Window(const Window&) = delete;
void on_option_changed(const String& name, const Option& option) override; void on_option_changed(const Option& option) override;
void scroll_to_keep_cursor_visible_ifn(); void scroll_to_keep_cursor_visible_ifn();