mirror of
https://github.com/mawww/kakoune.git
synced 2024-12-24 03:56:32 +03:00
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:
parent
a36befd653
commit
fac222a427
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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]; });
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
setg termcmd %sh{
|
||||
decl str termcmd %sh{
|
||||
if [[ -n "$TMUX" ]]; then
|
||||
echo "'tmux split-window -h'"
|
||||
else
|
||||
|
@ -1,4 +1,4 @@
|
||||
setg grepcmd 'grep -RHn'
|
||||
decl str grepcmd 'grep -RHn'
|
||||
|
||||
def -shell-params -file-completion \
|
||||
grep %{ %sh{
|
||||
|
@ -1,4 +1,4 @@
|
||||
setg makecmd make
|
||||
decl str makecmd make
|
||||
|
||||
def -shell-params make %{ %sh{
|
||||
output=$(mktemp -d -t kak-make.XXXXXXXX)/fifo
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user