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)
{
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)
{

View File

@ -466,7 +466,24 @@ void set_option(OptionManager& options, const CommandParameters& params,
if (params.size() != 2)
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>
@ -878,6 +895,7 @@ void register_commands()
[](const Context& context, const String& prefix, ByteCount 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({"name"}, set_client_name);

View File

@ -36,7 +36,7 @@ CandidateList complete_filename(const Context& context,
String dirprefix;
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;
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));
OptionManager& options = buffer->options();
options.set_option("eolformat", Option(crlf ? "crlf" : "lf"));
options.set_option("BOM", Option(bom ? "utf-8" : "no"));
options.get_local_option("eolformat").set<String>(crlf ? "crlf" : "lf");
options.get_local_option("BOM").set<String>(bom ? "utf-8" : "no");
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)
{
String eolformat = buffer.options()["eolformat"].as_string();
String eolformat = buffer.options()["eolformat"].get<String>();
if (eolformat == "crlf")
eolformat = "\r\n";
else
@ -172,7 +172,7 @@ void write_buffer_to_file(const Buffer& buffer, const String& filename)
throw file_access_error(filename, strerror(errno));
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);
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)
{
const int tabstop = buffer.options()["tabstop"].as_int();
const int tabstop = buffer.options()["tabstop"].get<int>();
if (content == "\t")
{
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)
{
const int tabstop = options["tabstop"].as_int();
const int tabstop = options["tabstop"].get<int>();
for (auto& line : display_buffer.lines())
{
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 };
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();
if (m_completions.end - m_completions.start > prefix.length())
prefix = line.substr(m_completions.start,

View File

@ -176,7 +176,7 @@ void do_search(Context& context)
RegisterManager::instance()['/'] = ex;
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;
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)
{
size_t width = context.options()["indentwidth"].as_int();
size_t width = context.options()["indentwidth"].get<int>();
String indent(' ', width);
Editor& editor = context.editor();
@ -351,7 +351,7 @@ void do_indent(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();
DynamicSelectionList sels{editor.buffer(), editor.selections()};
auto restore_sels = on_scope_end([&]{ editor.select((SelectionList)std::move(sels)); });
@ -745,7 +745,7 @@ void register_env_vars()
{ return runtime_directory(); });
shell_manager.register_env_var("opt_.+",
[](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_.+",
[](const String& name, const Context& context)
{ return RegisterManager::instance()[name[4]].values(context)[0]; });

View File

@ -6,6 +6,54 @@
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)
: m_parent(&parent)
{
@ -33,23 +81,33 @@ void OptionManager::unregister_watcher(OptionManagerWatcher& watcher)
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];
m_options[name] = value;
using ptr_type = decltype(*container.begin());
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)
watcher->on_option_changed(name, value);
m_options.emplace_back((*m_parent)[name].clone(*this));
return *m_options.back();
}
else
throw option_not_found(name);
}
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())
return it->second;
return **it;
else if (m_parent)
return (*m_parent)[name];
else
@ -65,42 +123,60 @@ CandidateList OptionManager::complete_option_name(const String& prefix,
result = m_parent->complete_option_name(prefix, cursor_pos);
for (auto& option : m_options)
{
if (option.first.substr(0, real_prefix.length()) == real_prefix and
not contains(result, option.first))
result.push_back(option.first);
const auto& name = option->name();
if (name.substr(0, real_prefix.length()) == real_prefix and
not contains(result, name))
result.push_back(name);
}
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)
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;
}
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 (m_options.find(name) != m_options.end())
if (&option.manager() != this and
find_option(m_options, option.name()) != m_options.end())
return;
for (auto watcher : m_watchers)
watcher->on_option_changed(name, value);
watcher->on_option_changed(option);
}
GlobalOptions::GlobalOptions()
: OptionManager()
{
set_option("tabstop", Option(8));
set_option("indentwidth", Option(4));
set_option("eolformat", Option("lf"));
set_option("BOM", Option("no"));
set_option("shell", Option("sh"));
set_option("complete_prefix", Option(1));
set_option("incsearch", Option(1));
set_option("ignored_files", Option(R"(^(\..*|.*\.(o|so|a))$)"));
declare_option<int>("tabstop", 8);
declare_option<int>("indentwidth", 4);
declare_option<String>("eolformat", "lf");
declare_option<String>("BOM", "no");
declare_option<String>("shell", "sh");
declare_option<int>("complete_prefix", 1);
declare_option<int>("incsearch", 1);
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) {}
};
class OptionManager;
class Option
{
public:
Option() {}
explicit Option(int value) : m_value(int_to_str(value)) {}
explicit Option(const String& value) : m_value(value) {}
Option(OptionManager& manager, String name);
virtual ~Option() {}
Option& operator=(int value) { m_value = int_to_str(value); return *this; }
Option& operator=(const String& value) { m_value = value; return *this; }
template<typename T> const T& get() const;
template<typename T> void set(const T& val);
bool operator==(const Option& other) const { return m_value == other.m_value; }
bool operator!=(const Option& other) const { return m_value != other.m_value; }
virtual String get_as_string() const = 0;
virtual void set_from_string(const String& str) = 0;
int as_int() const { return str_to_int(m_value); }
String as_string() const { return m_value; }
private:
String m_value;
String name() const { return m_name; }
OptionManager& manager() const { return m_manager; }
virtual Option* clone(OptionManager& manager) const = 0;
protected:
OptionManager& m_manager;
String m_name;
};
class OptionManagerWatcher
@ -40,8 +44,7 @@ class OptionManagerWatcher
public:
virtual ~OptionManagerWatcher() {}
virtual void on_option_changed(const String& name,
const Option& option) = 0;
virtual void on_option_changed(const Option& option) = 0;
};
class OptionManager : private OptionManagerWatcher
@ -51,29 +54,27 @@ public:
~OptionManager();
const Option& operator[] (const String& name) const;
void set_option(const String& name, const Option& value);
Option& get_local_option(const String& name);
CandidateList complete_option_name(const String& prefix,
ByteCount cursor_pos);
typedef std::unordered_map<String, Option> OptionMap;
OptionMap flatten_options() const;
using OptionList = std::vector<const Option*>;
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;
OptionMap m_options;
std::vector<std::unique_ptr<Option>> m_options;
OptionManager* m_parent;
void on_option_changed(const String& name, const Option& value);
std::vector<OptionManagerWatcher*> m_watchers;
};
@ -82,8 +83,10 @@ class GlobalOptions : public OptionManager,
{
public:
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
echo "'tmux split-window -h'"
else

View File

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

View File

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

View File

@ -119,7 +119,7 @@ String ShellManager::pipe(const String& input,
++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() };
if (not params.empty())
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); }});
for (auto& option : m_options.flatten_options())
on_option_changed(option.first, option.second);
on_option_changed(*option);
}
Window::~Window()
@ -177,9 +177,9 @@ DisplayCoord Window::display_position(const BufferIterator& iterator)
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};
m_hooks.run_hook("WinSetOption", desc, hook_context);
}

View File

@ -51,7 +51,7 @@ public:
private:
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();