2011-09-07 20:16:56 +02:00
|
|
|
#include "command_manager.hh"
|
|
|
|
|
2014-10-30 00:22:54 +01:00
|
|
|
#include "alias_registry.hh"
|
2011-09-16 11:18:51 +02:00
|
|
|
#include "assert.hh"
|
2017-08-29 10:23:03 +02:00
|
|
|
#include "buffer_utils.hh"
|
2012-02-13 22:38:07 +01:00
|
|
|
#include "context.hh"
|
2017-03-15 19:25:59 +01:00
|
|
|
#include "flags.hh"
|
2019-04-07 01:43:40 +02:00
|
|
|
#include "file.hh"
|
2017-08-29 10:23:03 +02:00
|
|
|
#include "optional.hh"
|
2018-05-26 02:01:26 +02:00
|
|
|
#include "option_types.hh"
|
2017-08-29 10:23:03 +02:00
|
|
|
#include "ranges.hh"
|
2019-11-23 08:59:44 +01:00
|
|
|
#include "regex.hh"
|
2012-08-11 12:13:48 +02:00
|
|
|
#include "register_manager.hh"
|
2013-04-09 20:05:40 +02:00
|
|
|
#include "shell_manager.hh"
|
|
|
|
#include "utils.hh"
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
#include "unit_tests.hh"
|
2011-09-16 11:18:51 +02:00
|
|
|
|
2011-09-07 20:16:56 +02:00
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
namespace Kakoune
|
|
|
|
{
|
|
|
|
|
2017-04-10 22:33:20 +02:00
|
|
|
bool CommandManager::command_defined(StringView command_name) const
|
2012-06-02 17:49:35 +02:00
|
|
|
{
|
2014-10-30 00:22:54 +01:00
|
|
|
return m_commands.find(command_name) != m_commands.end();
|
2012-06-02 17:49:35 +02:00
|
|
|
}
|
|
|
|
|
2012-11-22 14:28:14 +01:00
|
|
|
void CommandManager::register_command(String command_name,
|
2017-11-04 09:00:34 +01:00
|
|
|
CommandFunc func,
|
2014-02-12 10:02:09 +01:00
|
|
|
String docstring,
|
2014-02-08 02:02:58 +01:00
|
|
|
ParameterDesc param_desc,
|
2013-11-12 20:38:19 +01:00
|
|
|
CommandFlags flags,
|
2015-02-08 20:04:20 +01:00
|
|
|
CommandHelper helper,
|
2012-11-22 14:28:14 +01:00
|
|
|
CommandCompleter completer)
|
2011-09-07 20:16:56 +02:00
|
|
|
{
|
2017-11-04 09:00:34 +01:00
|
|
|
m_commands[command_name] = { std::move(func),
|
2014-04-08 20:54:32 +02:00
|
|
|
std::move(docstring),
|
|
|
|
std::move(param_desc),
|
|
|
|
flags,
|
2015-02-08 20:04:20 +01:00
|
|
|
std::move(helper),
|
2014-04-08 20:54:32 +02:00
|
|
|
std::move(completer) };
|
2011-09-07 20:16:56 +02:00
|
|
|
}
|
|
|
|
|
2022-02-22 10:14:47 +01:00
|
|
|
void CommandManager::set_command_completer(StringView command_name, CommandCompleter completer)
|
|
|
|
{
|
|
|
|
auto it = m_commands.find(command_name);
|
|
|
|
if (it == m_commands.end())
|
|
|
|
throw runtime_error(format("no such command '{}'", command_name));
|
|
|
|
|
|
|
|
it->value.completer = std::move(completer);
|
|
|
|
}
|
|
|
|
|
2019-03-12 18:34:30 +01:00
|
|
|
bool CommandManager::module_defined(StringView module_name) const
|
|
|
|
{
|
|
|
|
return m_modules.find(module_name) != m_modules.end();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CommandManager::register_module(String module_name, String commands)
|
|
|
|
{
|
|
|
|
auto module = m_modules.find(module_name);
|
2020-06-15 11:37:46 +02:00
|
|
|
if (module != m_modules.end() and module->value.state != Module::State::Registered)
|
2019-03-12 18:34:30 +01:00
|
|
|
throw runtime_error{format("module already loaded: '{}'", module_name)};
|
|
|
|
|
2020-06-15 11:37:46 +02:00
|
|
|
m_modules[module_name] = { Module::State::Registered, std::move(commands) };
|
2019-03-12 18:34:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void CommandManager::load_module(StringView module_name, Context& context)
|
|
|
|
{
|
|
|
|
auto module = m_modules.find(module_name);
|
|
|
|
if (module == m_modules.end())
|
|
|
|
throw runtime_error{format("no such module: '{}'", module_name)};
|
2020-06-15 11:37:46 +02:00
|
|
|
switch (module->value.state)
|
|
|
|
{
|
|
|
|
case Module::State::Loading:
|
|
|
|
throw runtime_error(format("module '{}' loaded recursively", module_name));
|
|
|
|
case Module::State::Loaded: return;
|
|
|
|
case Module::State::Registered: default: break;
|
|
|
|
}
|
2019-03-12 18:34:30 +01:00
|
|
|
|
2020-06-15 11:37:46 +02:00
|
|
|
{
|
|
|
|
module->value.state = Module::State::Loading;
|
|
|
|
auto restore_state = on_scope_end([&] { module->value.state = Module::State::Registered; });
|
|
|
|
|
|
|
|
Context empty_context{Context::EmptyContextFlag{}};
|
|
|
|
execute(module->value.commands, empty_context);
|
|
|
|
}
|
2019-03-12 18:34:30 +01:00
|
|
|
module->value.commands.clear();
|
2020-06-15 11:37:46 +02:00
|
|
|
module->value.state = Module::State::Loaded;
|
2019-03-13 20:46:53 +01:00
|
|
|
|
2019-06-25 17:48:24 +02:00
|
|
|
context.hooks().run_hook(Hook::ModuleLoaded, module_name, context);
|
2019-03-12 18:34:30 +01:00
|
|
|
}
|
|
|
|
|
2013-07-28 16:40:02 +02:00
|
|
|
struct parse_error : runtime_error
|
|
|
|
{
|
2014-11-15 19:45:56 +01:00
|
|
|
parse_error(StringView error)
|
2015-03-31 00:06:02 +02:00
|
|
|
: runtime_error{format("parse error: {}", error)} {}
|
2013-07-28 16:40:02 +02:00
|
|
|
};
|
2012-11-21 13:56:52 +01:00
|
|
|
|
2018-02-15 11:23:12 +01:00
|
|
|
namespace
|
|
|
|
{
|
2015-08-27 21:48:51 +02:00
|
|
|
|
2021-11-25 02:23:21 +01:00
|
|
|
bool is_command_separator(char c)
|
2012-07-31 14:22:57 +02:00
|
|
|
{
|
|
|
|
return c == ';' or c == '\n';
|
|
|
|
}
|
|
|
|
|
2021-07-09 09:03:22 +02:00
|
|
|
struct ParseResult
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
{
|
|
|
|
String content;
|
|
|
|
bool terminated;
|
|
|
|
};
|
|
|
|
|
2021-11-25 02:23:21 +01:00
|
|
|
template<typename Delimiter>
|
|
|
|
ParseResult parse_quoted(ParseState& state, Delimiter delimiter)
|
2013-06-27 20:07:26 +02:00
|
|
|
{
|
2021-11-25 02:23:21 +01:00
|
|
|
static_assert(std::is_same_v<Delimiter, char> or std::is_same_v<Delimiter, Codepoint>);
|
|
|
|
auto read = [](const char*& it, const char* end) {
|
|
|
|
if constexpr (std::is_same_v<Delimiter, Codepoint>)
|
|
|
|
return utf8::read_codepoint(it, end);
|
|
|
|
else
|
|
|
|
return *it++;
|
|
|
|
};
|
|
|
|
|
2021-07-09 09:03:22 +02:00
|
|
|
const char* beg = state.pos;
|
|
|
|
const char* end = state.str.end();
|
2013-06-27 20:07:26 +02:00
|
|
|
String str;
|
2015-08-27 21:48:51 +02:00
|
|
|
|
2021-07-09 09:03:22 +02:00
|
|
|
while (state.pos != end)
|
2013-06-27 20:07:26 +02:00
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
const char* cur = state.pos;
|
2021-11-25 02:23:21 +01:00
|
|
|
const auto c = read(state.pos, end);
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
if (c == delimiter)
|
2013-06-27 20:07:26 +02:00
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
auto next = state.pos;
|
2022-03-08 17:51:11 +01:00
|
|
|
if (next == end || read(next, end) != delimiter)
|
2015-07-25 09:56:27 +02:00
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
if (str.empty())
|
|
|
|
return {String{String::NoCopy{}, {beg, cur}}, true};
|
|
|
|
|
|
|
|
str += StringView{beg, cur};
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
return {str, true};
|
2018-05-23 15:13:26 +02:00
|
|
|
}
|
2021-07-09 09:03:22 +02:00
|
|
|
str += StringView{beg, state.pos};
|
|
|
|
state.pos = beg = next;
|
2013-06-27 20:07:26 +02:00
|
|
|
}
|
|
|
|
}
|
2021-07-09 09:03:22 +02:00
|
|
|
if (beg < end)
|
|
|
|
str += StringView{beg, end};
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
return {str, false};
|
2013-06-27 20:07:26 +02:00
|
|
|
}
|
|
|
|
|
2021-07-20 11:53:06 +02:00
|
|
|
template<char opening_delimiter, char closing_delimiter>
|
|
|
|
ParseResult parse_quoted_balanced(ParseState& state)
|
2015-08-27 21:48:51 +02:00
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
int level = 1;
|
|
|
|
const char* pos = state.pos;
|
|
|
|
const char* beg = pos;
|
|
|
|
const char* end = state.str.end();
|
|
|
|
while (pos != end)
|
2013-12-29 19:37:48 +01:00
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
const char c = *pos++;
|
2015-08-27 21:48:51 +02:00
|
|
|
if (c == opening_delimiter)
|
2013-12-29 19:37:48 +01:00
|
|
|
++level;
|
2021-07-09 09:03:22 +02:00
|
|
|
else if (c == closing_delimiter and --level == 0)
|
|
|
|
break;
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
}
|
2021-07-09 09:03:22 +02:00
|
|
|
state.pos = pos;
|
|
|
|
const bool terminated = (level == 0);
|
|
|
|
return {String{String::NoCopy{}, {beg, pos - terminated}}, terminated};
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
}
|
|
|
|
|
2021-07-09 09:03:22 +02:00
|
|
|
String parse_unquoted(ParseState& state)
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
const char* beg = state.pos;
|
|
|
|
const char* end = state.str.end();
|
|
|
|
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
String str;
|
|
|
|
|
2021-07-09 09:03:22 +02:00
|
|
|
while (state.pos != end)
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
const char c = *state.pos;
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
if (is_command_separator(c) or is_horizontal_blank(c))
|
2013-12-29 19:37:48 +01:00
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
str += StringView{beg, state.pos};
|
|
|
|
if (state.pos != beg and *(state.pos - 1) == '\\')
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
{
|
|
|
|
str.back() = c;
|
2021-07-09 09:03:22 +02:00
|
|
|
beg = state.pos+1;
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
}
|
2013-12-29 19:37:48 +01:00
|
|
|
else
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
return str;
|
2013-12-29 19:37:48 +01:00
|
|
|
}
|
2021-07-09 09:03:22 +02:00
|
|
|
++state.pos;
|
2013-12-29 19:37:48 +01:00
|
|
|
}
|
2021-07-09 09:03:22 +02:00
|
|
|
if (beg < end)
|
|
|
|
str += StringView{beg, end};
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
return str;
|
2013-12-29 19:37:48 +01:00
|
|
|
}
|
|
|
|
|
2018-02-15 11:23:12 +01:00
|
|
|
Token::Type token_type(StringView type_name, bool throw_on_invalid)
|
2013-12-29 19:37:48 +01:00
|
|
|
{
|
|
|
|
if (type_name == "")
|
2015-09-20 12:19:10 +02:00
|
|
|
return Token::Type::RawQuoted;
|
2013-12-29 19:37:48 +01:00
|
|
|
else if (type_name == "sh")
|
|
|
|
return Token::Type::ShellExpand;
|
|
|
|
else if (type_name == "reg")
|
|
|
|
return Token::Type::RegisterExpand;
|
|
|
|
else if (type_name == "opt")
|
|
|
|
return Token::Type::OptionExpand;
|
2014-06-18 20:28:48 +02:00
|
|
|
else if (type_name == "val")
|
|
|
|
return Token::Type::ValExpand;
|
2015-12-01 21:07:14 +01:00
|
|
|
else if (type_name == "arg")
|
|
|
|
return Token::Type::ArgExpand;
|
2019-04-07 01:43:40 +02:00
|
|
|
else if (type_name == "file")
|
|
|
|
return Token::Type::FileExpand;
|
2014-02-27 07:41:58 +01:00
|
|
|
else if (throw_on_invalid)
|
2017-06-07 20:01:26 +02:00
|
|
|
throw parse_error{format("unknown expand '{}'", type_name)};
|
2014-02-27 07:41:58 +01:00
|
|
|
else
|
2015-09-20 12:19:10 +02:00
|
|
|
return Token::Type::RawQuoted;
|
2013-12-29 19:37:48 +01:00
|
|
|
}
|
|
|
|
|
2021-07-09 09:03:22 +02:00
|
|
|
void skip_blanks_and_comments(ParseState& state)
|
2013-12-29 19:37:48 +01:00
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
while (state)
|
2013-12-29 19:37:48 +01:00
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
const Codepoint c = *state.pos;
|
2015-08-27 21:48:51 +02:00
|
|
|
if (is_horizontal_blank(c))
|
2021-07-09 09:03:22 +02:00
|
|
|
++state.pos;
|
|
|
|
else if (c == '\\' and state.pos + 1 != state.str.end() and
|
|
|
|
state.pos[1] == '\n')
|
|
|
|
state.pos += 2;
|
2015-08-27 21:48:51 +02:00
|
|
|
else if (c == '#')
|
2013-12-29 19:37:48 +01:00
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
while (state and *state.pos != '\n')
|
|
|
|
++state.pos;
|
2013-12-29 19:37:48 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-24 08:42:45 +02:00
|
|
|
BufferCoord compute_coord(StringView s)
|
|
|
|
{
|
|
|
|
BufferCoord coord{0,0};
|
|
|
|
for (auto c : s)
|
|
|
|
{
|
|
|
|
if (c == '\n')
|
|
|
|
{
|
|
|
|
++coord.line;
|
|
|
|
coord.column = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
++coord.column;
|
|
|
|
}
|
|
|
|
return coord;
|
|
|
|
}
|
|
|
|
|
2021-07-09 09:03:22 +02:00
|
|
|
Token parse_percent_token(ParseState& state, bool throw_on_unterminated)
|
2014-04-04 00:57:14 +02:00
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
kak_assert(state.pos[-1] == '%');
|
|
|
|
const auto type_start = state.pos;
|
|
|
|
while (state and *state.pos >= 'a' and *state.pos <= 'z')
|
|
|
|
++state.pos;
|
|
|
|
StringView type_name{type_start, state.pos};
|
2018-05-06 23:29:52 +02:00
|
|
|
|
2021-08-09 22:13:31 +02:00
|
|
|
bool at_end = state.pos == state.str.end();
|
2021-07-09 09:03:22 +02:00
|
|
|
const Codepoint opening_delimiter = utf8::read_codepoint(state.pos, state.str.end());
|
2021-08-09 22:13:31 +02:00
|
|
|
if (at_end or iswalpha(opening_delimiter))
|
2017-06-07 14:09:45 +02:00
|
|
|
{
|
|
|
|
if (throw_on_unterminated)
|
|
|
|
throw parse_error{format("expected a string delimiter after '%{}'",
|
|
|
|
type_name)};
|
2017-04-19 22:52:27 +02:00
|
|
|
return {};
|
2017-06-07 14:09:45 +02:00
|
|
|
}
|
2014-04-04 00:57:14 +02:00
|
|
|
|
2018-02-15 11:23:12 +01:00
|
|
|
Token::Type type = token_type(type_name, throw_on_unterminated);
|
2015-08-27 22:36:49 +02:00
|
|
|
|
2021-07-20 11:53:06 +02:00
|
|
|
constexpr struct CharPair { char opening; char closing; ParseResult (*parse_func)(ParseState&); } matching_pairs[] = {
|
|
|
|
{ '(', ')', parse_quoted_balanced<'(', ')'> },
|
|
|
|
{ '[', ']', parse_quoted_balanced<'[', ']'> },
|
|
|
|
{ '{', '}', parse_quoted_balanced<'{', '}'> },
|
|
|
|
{ '<', '>', parse_quoted_balanced<'<', '>'> }
|
2014-04-04 00:57:14 +02:00
|
|
|
};
|
|
|
|
|
2021-07-09 09:03:22 +02:00
|
|
|
auto start = state.pos;
|
|
|
|
const ByteCount byte_pos = start - state.str.begin();
|
2015-08-27 22:36:49 +02:00
|
|
|
|
2021-07-09 09:03:22 +02:00
|
|
|
if (auto it = find_if(matching_pairs, [=](const CharPair& cp) { return opening_delimiter == cp.opening; });
|
|
|
|
it != std::end(matching_pairs))
|
2014-04-04 00:57:14 +02:00
|
|
|
{
|
2021-07-20 11:53:06 +02:00
|
|
|
auto quoted = it->parse_func(state);
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
if (throw_on_unterminated and not quoted.terminated)
|
2021-06-24 08:42:45 +02:00
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
auto coord = compute_coord({state.str.begin(), start});
|
2015-08-27 21:48:51 +02:00
|
|
|
throw parse_error{format("{}:{}: unterminated string '%{}{}...{}'",
|
2021-06-24 08:42:45 +02:00
|
|
|
coord.line+1, coord.column+1, type_name,
|
2021-07-20 11:53:06 +02:00
|
|
|
it->opening, it->closing)};
|
2021-06-24 08:42:45 +02:00
|
|
|
}
|
2015-08-27 21:48:51 +02:00
|
|
|
|
2021-07-09 09:03:22 +02:00
|
|
|
return {type, byte_pos, std::move(quoted.content), quoted.terminated};
|
2014-04-04 00:57:14 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-25 02:23:21 +01:00
|
|
|
const bool is_ascii = opening_delimiter < 128;
|
|
|
|
auto quoted = is_ascii ? parse_quoted(state, (char)opening_delimiter) : parse_quoted(state, opening_delimiter);
|
2015-08-27 21:48:51 +02:00
|
|
|
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
if (throw_on_unterminated and not quoted.terminated)
|
2021-06-24 08:42:45 +02:00
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
auto coord = compute_coord({state.str.begin(), start});
|
2015-08-27 21:48:51 +02:00
|
|
|
throw parse_error{format("{}:{}: unterminated string '%{}{}...{}'",
|
2021-06-24 08:42:45 +02:00
|
|
|
coord.line+1, coord.column+1, type_name,
|
2015-08-27 21:48:51 +02:00
|
|
|
opening_delimiter, opening_delimiter)};
|
2021-06-24 08:42:45 +02:00
|
|
|
}
|
2015-08-27 21:48:51 +02:00
|
|
|
|
2021-07-09 09:03:22 +02:00
|
|
|
return {type, byte_pos, std::move(quoted.content), quoted.terminated};
|
2014-04-04 00:57:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-21 01:15:08 +01:00
|
|
|
template<typename Target>
|
|
|
|
requires (std::is_same_v<Target, Vector<String>> or std::is_same_v<Target, String>)
|
2021-07-09 09:03:22 +02:00
|
|
|
void expand_token(Token&& token, const Context& context, const ShellContext& shell_context, Target& target)
|
2018-05-30 15:23:38 +02:00
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
constexpr bool single = std::is_same_v<Target, String>;
|
|
|
|
auto set_target = [&](auto&& s) {
|
|
|
|
if constexpr (single)
|
|
|
|
target = std::move(s);
|
2021-11-21 01:15:08 +01:00
|
|
|
else if constexpr (std::is_same_v<std::remove_cvref_t<decltype(s)>, String>)
|
2021-07-09 09:03:22 +02:00
|
|
|
target.push_back(std::move(s));
|
|
|
|
else if constexpr (std::is_same_v<decltype(s), Vector<String>&&>)
|
|
|
|
target.insert(target.end(), std::make_move_iterator(s.begin()), std::make_move_iterator(s.end()));
|
|
|
|
else
|
|
|
|
target.insert(target.end(), s.begin(), s.end());
|
|
|
|
};
|
|
|
|
|
2021-07-09 09:03:22 +02:00
|
|
|
auto&& content = token.content;
|
2017-09-01 12:09:34 +02:00
|
|
|
switch (token.type)
|
2015-12-12 07:50:58 +01:00
|
|
|
{
|
|
|
|
case Token::Type::ShellExpand:
|
2016-06-19 18:01:56 +02:00
|
|
|
{
|
|
|
|
auto str = ShellManager::instance().eval(
|
|
|
|
content, context, {}, ShellManager::Flags::WaitForStdout,
|
|
|
|
shell_context).first;
|
|
|
|
|
2021-02-01 10:16:06 +01:00
|
|
|
if (not str.empty() and str.back() == '\n')
|
2021-01-03 04:45:31 +01:00
|
|
|
str.resize(str.length() - 1, 0);
|
|
|
|
|
2021-07-09 09:03:22 +02:00
|
|
|
return set_target(std::move(str));
|
2016-06-19 18:01:56 +02:00
|
|
|
}
|
2015-12-12 07:50:58 +01:00
|
|
|
case Token::Type::RegisterExpand:
|
2020-06-01 12:46:48 +02:00
|
|
|
if constexpr (single)
|
2021-07-09 09:03:22 +02:00
|
|
|
return set_target(context.main_sel_register_value(content).str());
|
2020-06-01 12:46:48 +02:00
|
|
|
else
|
2021-07-09 09:03:22 +02:00
|
|
|
return set_target(RegisterManager::instance()[content].get(context));
|
2015-12-12 07:50:58 +01:00
|
|
|
case Token::Type::OptionExpand:
|
2020-06-01 12:46:48 +02:00
|
|
|
{
|
|
|
|
auto& opt = context.options()[content];
|
|
|
|
if constexpr (single)
|
2021-07-09 09:03:22 +02:00
|
|
|
return set_target(opt.get_as_string(Quoting::Raw));
|
2020-06-01 12:46:48 +02:00
|
|
|
else
|
2021-07-09 09:03:22 +02:00
|
|
|
return set_target(opt.get_as_strings());
|
2020-06-01 12:46:48 +02:00
|
|
|
}
|
2015-12-12 07:50:58 +01:00
|
|
|
case Token::Type::ValExpand:
|
|
|
|
{
|
|
|
|
auto it = shell_context.env_vars.find(content);
|
|
|
|
if (it != shell_context.env_vars.end())
|
2021-07-09 09:03:22 +02:00
|
|
|
return set_target(it->value);
|
2020-06-01 12:46:48 +02:00
|
|
|
|
|
|
|
auto val = ShellManager::instance().get_val(content, context);
|
2020-03-01 03:28:06 +01:00
|
|
|
if constexpr (single)
|
2021-07-09 09:03:22 +02:00
|
|
|
return set_target(join(val, false, ' '));
|
2020-03-01 03:28:06 +01:00
|
|
|
else
|
2021-07-09 09:03:22 +02:00
|
|
|
return set_target(std::move(val));
|
2015-12-12 07:50:58 +01:00
|
|
|
}
|
|
|
|
case Token::Type::ArgExpand:
|
|
|
|
{
|
|
|
|
auto& params = shell_context.params;
|
|
|
|
if (content == '@')
|
2020-06-01 12:46:48 +02:00
|
|
|
{
|
|
|
|
if constexpr (single)
|
2021-07-09 09:03:22 +02:00
|
|
|
return set_target(join(params, ' ', false));
|
2020-06-01 12:46:48 +02:00
|
|
|
else
|
2021-07-09 09:03:22 +02:00
|
|
|
return set_target(params);
|
2020-06-01 12:46:48 +02:00
|
|
|
}
|
2015-12-12 07:50:58 +01:00
|
|
|
|
2022-05-05 12:04:51 +02:00
|
|
|
const int arg = str_to_int(content);
|
|
|
|
if (arg < 1)
|
2015-12-12 07:50:58 +01:00
|
|
|
throw runtime_error("invalid argument index");
|
2022-05-05 12:04:51 +02:00
|
|
|
return set_target(arg <= params.size() ? params[arg-1] : String{});
|
2015-12-12 07:50:58 +01:00
|
|
|
}
|
2019-04-07 01:43:40 +02:00
|
|
|
case Token::Type::FileExpand:
|
2021-07-09 09:03:22 +02:00
|
|
|
return set_target(read_file(content));
|
2015-12-12 07:50:58 +01:00
|
|
|
case Token::Type::RawEval:
|
2021-07-09 09:03:22 +02:00
|
|
|
return set_target(expand(content, context, shell_context));
|
2015-12-12 07:50:58 +01:00
|
|
|
case Token::Type::Raw:
|
|
|
|
case Token::Type::RawQuoted:
|
2021-07-09 09:03:22 +02:00
|
|
|
return set_target(std::move(content));
|
2015-12-12 07:50:58 +01:00
|
|
|
default: kak_assert(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-07-09 09:03:22 +02:00
|
|
|
CommandParser::CommandParser(StringView command_line) : m_state{command_line, command_line.begin()} {}
|
2011-09-07 20:16:56 +02:00
|
|
|
|
2018-02-15 11:23:12 +01:00
|
|
|
Optional<Token> CommandParser::read_token(bool throw_on_unterminated)
|
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
skip_blanks_and_comments(m_state);
|
|
|
|
if (not m_state)
|
2018-02-15 11:23:12 +01:00
|
|
|
return {};
|
2015-08-27 21:48:51 +02:00
|
|
|
|
2021-07-09 09:03:22 +02:00
|
|
|
const StringView line = m_state.str;
|
|
|
|
const char* start = m_state.pos;
|
2011-11-26 18:20:02 +01:00
|
|
|
|
2021-07-09 09:03:22 +02:00
|
|
|
const char c = *m_state.pos;
|
2018-02-15 11:23:12 +01:00
|
|
|
if (c == '"' or c == '\'')
|
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
start = ++m_state.pos;
|
|
|
|
ParseResult quoted = parse_quoted(m_state, c);
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
if (throw_on_unterminated and not quoted.terminated)
|
2018-02-15 11:23:12 +01:00
|
|
|
throw parse_error{format("unterminated string {0}...{0}", c)};
|
|
|
|
return Token{c == '"' ? Token::Type::RawEval
|
|
|
|
: Token::Type::RawQuoted,
|
2021-06-24 08:42:45 +02:00
|
|
|
start - line.begin(), std::move(quoted.content),
|
2021-05-18 12:46:55 +02:00
|
|
|
quoted.terminated};
|
2018-02-15 11:23:12 +01:00
|
|
|
}
|
|
|
|
else if (c == '%')
|
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
++m_state.pos;
|
|
|
|
return parse_percent_token(m_state, throw_on_unterminated);
|
2011-09-07 20:16:56 +02:00
|
|
|
}
|
2020-03-12 10:30:55 +01:00
|
|
|
else if (is_command_separator(c))
|
2018-02-15 11:23:12 +01:00
|
|
|
return Token{Token::Type::CommandSeparator,
|
2021-07-09 09:03:22 +02:00
|
|
|
++m_state.pos - line.begin(), {}};
|
2018-02-15 11:23:12 +01:00
|
|
|
else
|
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
if (c == '\\' and m_state.pos + 1 != m_state.str.end())
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
const char next = m_state.pos[1];
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
if (next == '%' or next == '\'' or next == '"')
|
2021-07-09 09:03:22 +02:00
|
|
|
++m_state.pos;
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
}
|
2021-07-09 09:03:22 +02:00
|
|
|
return Token{Token::Type::Raw, start - line.begin(), parse_unquoted(m_state)};
|
2018-02-15 11:23:12 +01:00
|
|
|
}
|
|
|
|
return {};
|
2011-11-26 19:32:57 +01:00
|
|
|
}
|
|
|
|
|
2016-12-07 21:07:32 +01:00
|
|
|
template<typename Postprocess>
|
|
|
|
String expand_impl(StringView str, const Context& context,
|
|
|
|
const ShellContext& shell_context,
|
|
|
|
Postprocess postprocess)
|
2015-05-04 18:08:57 +02:00
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
ParseState state{str, str.begin()};
|
2015-05-04 18:08:57 +02:00
|
|
|
String res;
|
2021-07-09 09:03:22 +02:00
|
|
|
auto beg = state.pos;
|
|
|
|
while (state)
|
2015-05-04 18:08:57 +02:00
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
if (*state.pos++ == '%')
|
2015-05-04 18:08:57 +02:00
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
if (state and *state.pos == '%')
|
2018-05-23 15:13:26 +02:00
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
res += StringView{beg, state.pos};
|
|
|
|
beg = ++state.pos;
|
2018-05-23 15:13:26 +02:00
|
|
|
}
|
|
|
|
else
|
2015-07-25 09:56:27 +02:00
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
res += StringView{beg, state.pos-1};
|
2021-07-09 09:03:22 +02:00
|
|
|
String token;
|
2021-07-09 09:03:22 +02:00
|
|
|
expand_token(parse_percent_token(state, true), context, shell_context, token);
|
2021-07-09 09:03:22 +02:00
|
|
|
res += postprocess(token);
|
2021-07-09 09:03:22 +02:00
|
|
|
beg = state.pos;
|
2015-07-25 09:56:27 +02:00
|
|
|
}
|
2015-05-04 18:08:57 +02:00
|
|
|
}
|
|
|
|
}
|
2021-07-09 09:03:22 +02:00
|
|
|
res += StringView{beg, state.pos};
|
2015-05-04 18:08:57 +02:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2016-12-07 21:07:32 +01:00
|
|
|
String expand(StringView str, const Context& context,
|
|
|
|
const ShellContext& shell_context)
|
|
|
|
{
|
2017-01-09 00:26:15 +01:00
|
|
|
return expand_impl(str, context, shell_context, [](String s){ return s; });
|
2016-12-07 21:07:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
String expand(StringView str, const Context& context,
|
|
|
|
const ShellContext& shell_context,
|
2019-09-01 05:03:01 +02:00
|
|
|
const FunctionRef<String (String)>& postprocess)
|
2016-12-07 21:07:32 +01:00
|
|
|
{
|
2019-09-01 05:03:01 +02:00
|
|
|
return expand_impl(str, context, shell_context, postprocess);
|
2016-12-07 21:07:32 +01:00
|
|
|
}
|
|
|
|
|
2020-07-18 06:29:44 +02:00
|
|
|
StringView resolve_alias(const Context& context, StringView name)
|
2013-12-24 03:06:22 +01:00
|
|
|
{
|
2014-10-30 00:22:54 +01:00
|
|
|
auto alias = context.aliases()[name];
|
2020-07-18 06:29:44 +02:00
|
|
|
return alias.empty() ? name : alias;
|
2013-12-24 03:06:22 +01:00
|
|
|
}
|
|
|
|
|
2013-07-26 01:17:12 +02:00
|
|
|
void CommandManager::execute_single_command(CommandParameters params,
|
2014-05-07 21:39:59 +02:00
|
|
|
Context& context,
|
2021-06-24 08:42:45 +02:00
|
|
|
const ShellContext& shell_context)
|
2012-08-06 19:29:51 +02:00
|
|
|
{
|
|
|
|
if (params.empty())
|
|
|
|
return;
|
|
|
|
|
2017-01-29 14:56:05 +01:00
|
|
|
constexpr int max_command_depth = 100;
|
|
|
|
if (m_command_depth > max_command_depth)
|
|
|
|
throw runtime_error("maximum nested command depth hit");
|
|
|
|
|
|
|
|
++m_command_depth;
|
2021-06-24 08:42:45 +02:00
|
|
|
auto pop_depth = on_scope_end([this] { --m_command_depth; });
|
2017-01-29 14:56:05 +01:00
|
|
|
|
2020-07-18 06:29:44 +02:00
|
|
|
auto command_it = m_commands.find(resolve_alias(context, params[0]));
|
2012-08-06 19:29:51 +02:00
|
|
|
if (command_it == m_commands.end())
|
2021-06-24 08:42:45 +02:00
|
|
|
throw runtime_error("no such command");
|
2014-04-08 20:54:32 +02:00
|
|
|
|
2021-03-31 08:17:22 +02:00
|
|
|
auto debug_flags = context.options()["debug"].get<DebugFlags>();
|
2017-06-17 10:27:07 +02:00
|
|
|
if (debug_flags & DebugFlags::Commands)
|
2021-03-31 08:17:22 +02:00
|
|
|
write_to_debug_buffer(format("command {}", join(params, ' ')));
|
|
|
|
|
2021-06-24 08:42:45 +02:00
|
|
|
auto profile = on_scope_end([&, start = (debug_flags & DebugFlags::Profile) ? Clock::now() : Clock::time_point{}] {
|
2021-03-31 08:17:22 +02:00
|
|
|
if (not (debug_flags & DebugFlags::Profile))
|
|
|
|
return;
|
|
|
|
auto full = std::chrono::duration_cast<std::chrono::microseconds>(Clock::now() - start);
|
|
|
|
write_to_debug_buffer(format("command {} took {} us", params[0], full.count()));
|
|
|
|
});
|
2017-06-17 10:27:07 +02:00
|
|
|
|
2021-06-24 08:42:45 +02:00
|
|
|
command_it->value.func({{params.begin()+1, params.end()}, command_it->value.param_desc},
|
|
|
|
context, shell_context);
|
2014-05-07 21:39:59 +02:00
|
|
|
}
|
|
|
|
|
2014-04-21 22:49:25 +02:00
|
|
|
void CommandManager::execute(StringView command_line,
|
2015-10-22 14:48:57 +02:00
|
|
|
Context& context, const ShellContext& shell_context)
|
2011-11-26 19:32:57 +01:00
|
|
|
{
|
2018-02-15 11:23:12 +01:00
|
|
|
CommandParser parser(command_line);
|
2012-01-14 15:02:54 +01:00
|
|
|
|
2021-06-24 08:42:45 +02:00
|
|
|
ByteCount command_pos{};
|
2021-07-09 09:03:22 +02:00
|
|
|
Vector<String> params;
|
2021-06-24 08:42:45 +02:00
|
|
|
while (true)
|
2012-01-15 02:37:35 +01:00
|
|
|
{
|
2021-06-24 08:42:45 +02:00
|
|
|
Optional<Token> token = parser.read_token(true);
|
|
|
|
if (not token or token->type == Token::Type::CommandSeparator)
|
2012-01-15 02:37:35 +01:00
|
|
|
{
|
2021-06-24 08:42:45 +02:00
|
|
|
try
|
|
|
|
{
|
|
|
|
execute_single_command(params, context, shell_context);
|
|
|
|
}
|
|
|
|
catch (failure& error)
|
|
|
|
{
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
catch (runtime_error& error)
|
|
|
|
{
|
|
|
|
auto coord = compute_coord(command_line.substr(0_byte, command_pos));
|
|
|
|
error.set_what(format("{}:{}: '{}': {}", coord.line+1, coord.column+1,
|
|
|
|
params[0], error.what()));
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (not token)
|
|
|
|
return;
|
|
|
|
|
2014-04-04 00:57:14 +02:00
|
|
|
params.clear();
|
2021-06-24 08:42:45 +02:00
|
|
|
continue;
|
2014-04-04 00:57:14 +02:00
|
|
|
}
|
2021-06-24 08:42:45 +02:00
|
|
|
|
|
|
|
if (params.empty())
|
|
|
|
command_pos = token->pos;
|
|
|
|
|
|
|
|
if (token->type == Token::Type::ArgExpand and token->content == '@')
|
2016-12-07 14:26:11 +01:00
|
|
|
params.insert(params.end(), shell_context.params.begin(),
|
|
|
|
shell_context.params.end());
|
2014-04-04 00:57:14 +02:00
|
|
|
else
|
2021-07-09 09:03:22 +02:00
|
|
|
expand_token(*std::move(token), context, shell_context, params);
|
2012-01-15 02:37:35 +01:00
|
|
|
}
|
2011-09-07 20:16:56 +02:00
|
|
|
}
|
|
|
|
|
2016-12-23 17:23:31 +01:00
|
|
|
Optional<CommandInfo> CommandManager::command_info(const Context& context, StringView command_line) const
|
2014-02-11 23:16:17 +01:00
|
|
|
{
|
2018-02-15 11:23:12 +01:00
|
|
|
CommandParser parser{command_line};
|
|
|
|
Vector<Token> tokens;
|
|
|
|
while (auto token = parser.read_token(false))
|
2014-02-11 23:16:17 +01:00
|
|
|
{
|
2018-02-15 11:23:12 +01:00
|
|
|
if (token->type == Token::Type::CommandSeparator)
|
|
|
|
tokens.clear();
|
|
|
|
else
|
|
|
|
tokens.push_back(std::move(*token));
|
2014-02-11 23:16:17 +01:00
|
|
|
}
|
|
|
|
|
2018-02-15 11:23:12 +01:00
|
|
|
if (tokens.empty() or
|
|
|
|
(tokens.front().type != Token::Type::Raw and
|
|
|
|
tokens.front().type != Token::Type::RawQuoted))
|
2016-12-23 17:23:31 +01:00
|
|
|
return {};
|
2014-02-11 23:16:17 +01:00
|
|
|
|
2020-07-18 06:29:44 +02:00
|
|
|
auto cmd = m_commands.find(resolve_alias(context, tokens.front().content));
|
2014-02-11 23:16:17 +01:00
|
|
|
if (cmd == m_commands.end())
|
2016-12-23 17:23:31 +01:00
|
|
|
return {};
|
2014-02-11 23:16:17 +01:00
|
|
|
|
2016-12-23 17:23:31 +01:00
|
|
|
CommandInfo res;
|
2017-03-07 01:30:54 +01:00
|
|
|
res.name = cmd->key;
|
|
|
|
if (not cmd->value.docstring.empty())
|
|
|
|
res.info += cmd->value.docstring + "\n";
|
2014-05-03 16:54:51 +02:00
|
|
|
|
2017-03-07 01:30:54 +01:00
|
|
|
if (cmd->value.helper)
|
2015-02-08 20:04:20 +01:00
|
|
|
{
|
|
|
|
Vector<String> params;
|
2018-02-15 11:23:12 +01:00
|
|
|
for (auto it = tokens.begin() + 1; it != tokens.end(); ++it)
|
2015-02-08 20:04:20 +01:00
|
|
|
{
|
2017-09-01 12:09:34 +02:00
|
|
|
if (it->type == Token::Type::Raw or
|
|
|
|
it->type == Token::Type::RawQuoted or
|
|
|
|
it->type == Token::Type::RawEval)
|
|
|
|
params.push_back(it->content);
|
2015-02-08 20:04:20 +01:00
|
|
|
}
|
2017-03-07 01:30:54 +01:00
|
|
|
String helpstr = cmd->value.helper(context, params);
|
2015-02-08 20:04:20 +01:00
|
|
|
if (not helpstr.empty())
|
2017-06-04 09:37:51 +02:00
|
|
|
res.info += format("{}\n", helpstr);
|
2015-02-08 20:04:20 +01:00
|
|
|
}
|
|
|
|
|
2014-05-03 16:54:51 +02:00
|
|
|
String aliases;
|
2017-03-07 01:30:54 +01:00
|
|
|
for (auto& alias : context.aliases().aliases_for(cmd->key))
|
2014-10-30 00:22:54 +01:00
|
|
|
aliases += " " + alias;
|
2014-05-03 16:54:51 +02:00
|
|
|
if (not aliases.empty())
|
2017-06-04 09:37:51 +02:00
|
|
|
res.info += format("Aliases:{}\n", aliases);
|
2014-05-03 16:54:51 +02:00
|
|
|
|
2017-03-07 01:30:54 +01:00
|
|
|
auto& switches = cmd->value.param_desc.switches;
|
2014-02-11 23:23:44 +01:00
|
|
|
if (not switches.empty())
|
2017-06-04 09:37:51 +02:00
|
|
|
res.info += format("Switches:\n{}", indent(generate_switches_doc(switches)));
|
2014-02-11 23:16:17 +01:00
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2017-06-29 08:43:20 +02:00
|
|
|
Completions CommandManager::complete_command_name(const Context& context, StringView query) const
|
2016-03-08 14:56:37 +01:00
|
|
|
{
|
2016-03-24 01:05:40 +01:00
|
|
|
auto commands = m_commands
|
2017-03-07 01:30:54 +01:00
|
|
|
| filter([](const CommandMap::Item& cmd) { return not (cmd.value.flags & CommandFlags::Hidden); })
|
2018-03-13 04:24:03 +01:00
|
|
|
| transform(&CommandMap::Item::key);
|
2016-03-08 22:35:56 +01:00
|
|
|
|
2018-02-20 12:09:03 +01:00
|
|
|
auto aliases = context.aliases().flatten_aliases()
|
2020-10-19 12:01:18 +02:00
|
|
|
| transform(&HashItem<String, String>::key);
|
2018-02-20 12:09:03 +01:00
|
|
|
|
2020-11-18 09:55:57 +01:00
|
|
|
return {0, query.length(),
|
|
|
|
Kakoune::complete(query, query.length(), concatenated(commands, aliases)),
|
2021-03-02 10:18:19 +01:00
|
|
|
Completions::Flags::Menu | Completions::Flags::NoEmpty};
|
2016-03-08 14:56:37 +01:00
|
|
|
}
|
|
|
|
|
2019-05-13 09:34:04 +02:00
|
|
|
Completions CommandManager::complete_module_name(StringView query) const
|
|
|
|
{
|
|
|
|
return {0, query.length(),
|
2020-06-15 11:37:46 +02:00
|
|
|
Kakoune::complete(query, query.length(), m_modules | filter([](auto&& item) { return item.value.state == Module::State::Registered; })
|
2019-05-13 09:34:04 +02:00
|
|
|
| transform(&ModuleMap::Item::key))};
|
|
|
|
}
|
|
|
|
|
2022-07-21 09:41:18 +02:00
|
|
|
static Completions complete_expansion(const Context& context, CompletionFlags flags,
|
|
|
|
Token token, ByteCount start,
|
|
|
|
ByteCount cursor_pos, ByteCount pos_in_token)
|
|
|
|
{
|
|
|
|
switch (token.type) {
|
|
|
|
case Token::Type::RegisterExpand:
|
|
|
|
return { start, cursor_pos,
|
|
|
|
RegisterManager::instance().complete_register_name(
|
|
|
|
token.content, pos_in_token) };
|
|
|
|
|
|
|
|
case Token::Type::OptionExpand:
|
|
|
|
return { start, cursor_pos,
|
|
|
|
GlobalScope::instance().option_registry().complete_option_name(
|
|
|
|
token.content, pos_in_token) };
|
|
|
|
|
|
|
|
case Token::Type::ShellExpand:
|
|
|
|
return offset_pos(shell_complete(context, flags, token.content,
|
|
|
|
pos_in_token), start);
|
|
|
|
|
|
|
|
case Token::Type::ValExpand:
|
|
|
|
return { start, cursor_pos,
|
|
|
|
ShellManager::instance().complete_env_var(
|
|
|
|
token.content, pos_in_token) };
|
|
|
|
|
|
|
|
case Token::Type::FileExpand:
|
|
|
|
{
|
|
|
|
const auto& ignored_files = context.options()["ignored_files"].get<Regex>();
|
|
|
|
return { start, cursor_pos, complete_filename(
|
|
|
|
token.content, ignored_files, pos_in_token, FilenameFlags::Expand) };
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
kak_assert(false);
|
|
|
|
throw runtime_error("unknown expansion");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static Completions complete_raw_eval(const Context& context, CompletionFlags flags,
|
|
|
|
StringView prefix, ByteCount start,
|
|
|
|
ByteCount cursor_pos, ByteCount pos_in_token)
|
|
|
|
{
|
|
|
|
ParseState state{prefix, prefix.begin()};
|
|
|
|
while (state)
|
|
|
|
{
|
|
|
|
if (*state.pos++ == '%')
|
|
|
|
{
|
|
|
|
if (state and *state.pos == '%')
|
|
|
|
++state.pos;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto token = parse_percent_token(state, false);
|
|
|
|
if (token.terminated)
|
|
|
|
continue;
|
|
|
|
if (token.type == Token::Type::Raw or token.type == Token::Type::RawQuoted)
|
|
|
|
return {};
|
|
|
|
return complete_expansion(context, flags, token,
|
|
|
|
start + token.pos, cursor_pos,
|
|
|
|
pos_in_token - token.pos);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2014-04-08 20:54:32 +02:00
|
|
|
Completions CommandManager::complete(const Context& context,
|
|
|
|
CompletionFlags flags,
|
2014-04-21 22:49:25 +02:00
|
|
|
StringView command_line,
|
2014-04-08 20:54:32 +02:00
|
|
|
ByteCount cursor_pos)
|
2011-09-13 23:16:48 +02:00
|
|
|
{
|
Compute prompt completion only from characters left of the cursor
If I type
:echo -mx
I get no completions, even when I move the cursor on the x.
If I replace the x with a k, I get a completion "-markup".
The odd thing is that if I accept the completion while the cursor is
on the k, then the commandline will be
:echo markupk
Evidently, the characters under cursor (x/k) influence the completion
(actually all letters right of the cursor do), but they are not
incorporated in the final result, which is weird.
I think there are two consistent behaviors:
1. Compute completions only from characters left of the cursor. We already
do this in insert mode completion, and when completing the command name
in a prompt.
2. Compute completions from the whole token, and when accepting a completion,
replace the whole token.
Most readline-style completion systems out there implement 1. A
notable exception is fish's tab-completion. I think we should stick
to 1 because it's more predictable and familiar. Do that.
This allows us to get rid of a special case for completing command
names, because the new behavior subsumes it.
In fact, I think this would allow us to get rid of most "pos_in_token"
or "cursor_pos" completer parameters. I believe the only place where we
it's actually different from the end of the query is in "shell-script"
completers, where we expose "$kak_pos_in_token". I think we can still
remove that parameter and just cut the commandline at the cursor
position before passing it to a "shell-script" completer. Then we
also don't need "$kak_token_to_complete" (but we can still keep
expose them for backwards compatibility).
2022-07-21 10:24:09 +02:00
|
|
|
auto prefix = command_line.substr(0_byte, cursor_pos);
|
|
|
|
CommandParser parser{prefix};
|
|
|
|
const char* cursor = prefix.begin() + (int)cursor_pos;
|
2018-02-15 11:23:12 +01:00
|
|
|
Vector<Token> tokens;
|
2011-09-13 23:16:48 +02:00
|
|
|
|
2018-02-15 11:23:12 +01:00
|
|
|
bool is_last_token = true;
|
|
|
|
while (auto token = parser.read_token(false))
|
2011-09-13 23:16:48 +02:00
|
|
|
{
|
2018-02-15 11:23:12 +01:00
|
|
|
if (token->type == Token::Type::CommandSeparator)
|
|
|
|
{
|
|
|
|
tokens.clear();
|
|
|
|
continue;
|
|
|
|
}
|
2014-01-03 21:07:40 +01:00
|
|
|
|
2018-02-15 11:23:12 +01:00
|
|
|
tokens.push_back(std::move(*token));
|
|
|
|
if (parser.pos() >= cursor)
|
2011-09-13 23:16:48 +02:00
|
|
|
{
|
2018-02-15 11:23:12 +01:00
|
|
|
is_last_token = false;
|
2011-09-13 23:16:48 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-15 11:23:12 +01:00
|
|
|
if (is_last_token)
|
Compute prompt completion only from characters left of the cursor
If I type
:echo -mx
I get no completions, even when I move the cursor on the x.
If I replace the x with a k, I get a completion "-markup".
The odd thing is that if I accept the completion while the cursor is
on the k, then the commandline will be
:echo markupk
Evidently, the characters under cursor (x/k) influence the completion
(actually all letters right of the cursor do), but they are not
incorporated in the final result, which is weird.
I think there are two consistent behaviors:
1. Compute completions only from characters left of the cursor. We already
do this in insert mode completion, and when completing the command name
in a prompt.
2. Compute completions from the whole token, and when accepting a completion,
replace the whole token.
Most readline-style completion systems out there implement 1. A
notable exception is fish's tab-completion. I think we should stick
to 1 because it's more predictable and familiar. Do that.
This allows us to get rid of a special case for completing command
names, because the new behavior subsumes it.
In fact, I think this would allow us to get rid of most "pos_in_token"
or "cursor_pos" completer parameters. I believe the only place where we
it's actually different from the end of the query is in "shell-script"
completers, where we expose "$kak_pos_in_token". I think we can still
remove that parameter and just cut the commandline at the cursor
position before passing it to a "shell-script" completer. Then we
also don't need "$kak_token_to_complete" (but we can still keep
expose them for backwards compatibility).
2022-07-21 10:24:09 +02:00
|
|
|
tokens.push_back({Token::Type::Raw, prefix.length(), {}});
|
2018-02-15 11:23:12 +01:00
|
|
|
kak_assert(not tokens.empty());
|
|
|
|
const auto& token = tokens.back();
|
|
|
|
|
2021-05-18 12:46:55 +02:00
|
|
|
if (token.terminated) // do not complete past explicit token close
|
|
|
|
return Completions{};
|
|
|
|
|
2020-10-19 12:01:18 +02:00
|
|
|
auto requote = [](Completions completions, Token::Type token_type) {
|
2021-03-31 08:15:31 +02:00
|
|
|
if (completions.flags & Completions::Flags::Quoted)
|
2020-10-19 12:01:18 +02:00
|
|
|
return completions;
|
|
|
|
|
|
|
|
if (token_type == Token::Type::Raw)
|
|
|
|
{
|
2021-03-31 08:15:31 +02:00
|
|
|
const bool at_token_start = completions.start == 0;
|
|
|
|
for (auto& candidate : completions.candidates)
|
2020-10-19 12:01:18 +02:00
|
|
|
{
|
2021-03-31 08:15:31 +02:00
|
|
|
const StringView to_escape = ";\n \t";
|
|
|
|
if ((at_token_start and candidate.substr(0_byte, 1_byte) == "%") or
|
|
|
|
any_of(candidate, [&](auto c) { return contains(to_escape, c); }))
|
|
|
|
candidate = at_token_start ? quote(candidate) : escape(candidate, to_escape, '\\');
|
2020-10-19 12:01:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (token_type == Token::Type::RawQuoted)
|
|
|
|
completions.flags |= Completions::Flags::Quoted;
|
|
|
|
else
|
|
|
|
kak_assert(false);
|
|
|
|
|
|
|
|
return completions;
|
|
|
|
};
|
|
|
|
|
|
|
|
const ByteCount start = token.pos;
|
2021-05-18 12:46:55 +02:00
|
|
|
const ByteCount pos_in_token = cursor_pos - start;
|
2020-10-19 12:01:18 +02:00
|
|
|
|
2015-06-26 14:52:01 +02:00
|
|
|
// command name completion
|
2018-02-15 11:23:12 +01:00
|
|
|
if (tokens.size() == 1 and (token.type == Token::Type::Raw or
|
|
|
|
token.type == Token::Type::RawQuoted))
|
2011-09-13 23:16:48 +02:00
|
|
|
{
|
Compute prompt completion only from characters left of the cursor
If I type
:echo -mx
I get no completions, even when I move the cursor on the x.
If I replace the x with a k, I get a completion "-markup".
The odd thing is that if I accept the completion while the cursor is
on the k, then the commandline will be
:echo markupk
Evidently, the characters under cursor (x/k) influence the completion
(actually all letters right of the cursor do), but they are not
incorporated in the final result, which is weird.
I think there are two consistent behaviors:
1. Compute completions only from characters left of the cursor. We already
do this in insert mode completion, and when completing the command name
in a prompt.
2. Compute completions from the whole token, and when accepting a completion,
replace the whole token.
Most readline-style completion systems out there implement 1. A
notable exception is fish's tab-completion. I think we should stick
to 1 because it's more predictable and familiar. Do that.
This allows us to get rid of a special case for completing command
names, because the new behavior subsumes it.
In fact, I think this would allow us to get rid of most "pos_in_token"
or "cursor_pos" completer parameters. I believe the only place where we
it's actually different from the end of the query is in "shell-script"
completers, where we expose "$kak_pos_in_token". I think we can still
remove that parameter and just cut the commandline at the cursor
position before passing it to a "shell-script" completer. Then we
also don't need "$kak_token_to_complete" (but we can still keep
expose them for backwards compatibility).
2022-07-21 10:24:09 +02:00
|
|
|
return offset_pos(requote(complete_command_name(context, prefix), token.type), start);
|
2011-09-13 23:16:48 +02:00
|
|
|
}
|
2011-09-16 11:18:51 +02:00
|
|
|
|
2018-02-15 11:23:12 +01:00
|
|
|
switch (token.type)
|
2013-12-30 15:22:18 +01:00
|
|
|
{
|
2019-11-23 08:47:14 +01:00
|
|
|
case Token::Type::RegisterExpand:
|
2013-12-30 15:22:18 +01:00
|
|
|
case Token::Type::OptionExpand:
|
|
|
|
case Token::Type::ShellExpand:
|
2016-04-17 20:21:43 +02:00
|
|
|
case Token::Type::ValExpand:
|
2019-11-23 08:59:44 +01:00
|
|
|
case Token::Type::FileExpand:
|
2022-07-21 09:41:18 +02:00
|
|
|
return complete_expansion(context, flags, token, start, cursor_pos, pos_in_token);
|
2019-11-23 08:59:44 +01:00
|
|
|
|
2013-12-30 15:22:18 +01:00
|
|
|
case Token::Type::Raw:
|
2015-09-20 12:19:10 +02:00
|
|
|
case Token::Type::RawQuoted:
|
2013-12-30 15:22:18 +01:00
|
|
|
{
|
2018-02-15 11:23:12 +01:00
|
|
|
StringView command_name = tokens.front().content;
|
2016-08-05 14:53:19 +02:00
|
|
|
if (command_name != m_last_complete_command)
|
|
|
|
{
|
2017-04-10 22:33:20 +02:00
|
|
|
m_last_complete_command = command_name.str();
|
2016-08-05 14:53:19 +02:00
|
|
|
flags |= CompletionFlags::Start;
|
|
|
|
}
|
2013-12-30 15:22:18 +01:00
|
|
|
|
2020-07-18 06:29:44 +02:00
|
|
|
auto command_it = m_commands.find(resolve_alias(context, command_name));
|
2018-08-26 04:29:11 +02:00
|
|
|
if (command_it == m_commands.end())
|
|
|
|
return Completions{};
|
|
|
|
|
2021-05-18 12:46:55 +02:00
|
|
|
auto& command = command_it->value;
|
|
|
|
|
2022-05-30 11:27:18 +02:00
|
|
|
const bool has_switches = not command.param_desc.switches.empty();
|
|
|
|
auto is_switch = [=](StringView s) { return has_switches and s.substr(0_byte, 1_byte) == "-"; };
|
2022-05-26 11:42:15 +02:00
|
|
|
|
2022-07-31 07:38:46 +02:00
|
|
|
if (is_switch(token.content)
|
|
|
|
and not contains(tokens | drop(1) | transform(&Token::content), "--"))
|
2018-08-26 04:29:11 +02:00
|
|
|
{
|
2021-05-18 12:46:55 +02:00
|
|
|
auto switches = Kakoune::complete(token.content.substr(1_byte), pos_in_token,
|
2022-07-19 10:04:32 +02:00
|
|
|
concatenated(command.param_desc.switches
|
|
|
|
| transform(&SwitchMap::Item::key),
|
|
|
|
ConstArrayView<String>{"-"}));
|
2022-07-19 09:42:20 +02:00
|
|
|
return switches.empty()
|
|
|
|
? Completions{}
|
|
|
|
: Completions{start+1, cursor_pos, std::move(switches), Completions::Flags::Menu};
|
2018-08-26 04:29:11 +02:00
|
|
|
}
|
2021-05-18 12:46:55 +02:00
|
|
|
if (not command.completer)
|
2018-08-26 04:29:11 +02:00
|
|
|
return Completions{};
|
2013-12-30 15:22:18 +01:00
|
|
|
|
2022-05-26 11:42:15 +02:00
|
|
|
auto params = tokens | skip(1) | transform(&Token::content) | filter(std::not_fn(is_switch)) | gather<Vector>();
|
2021-05-18 12:46:55 +02:00
|
|
|
auto index = params.size() - 1;
|
|
|
|
|
|
|
|
return offset_pos(requote(command.completer(context, flags, params, index, pos_in_token), token.type), start);
|
2013-12-30 15:22:18 +01:00
|
|
|
}
|
2020-10-19 12:01:18 +02:00
|
|
|
case Token::Type::RawEval:
|
2022-07-21 09:41:18 +02:00
|
|
|
return complete_raw_eval(context, flags, token.content, start, cursor_pos, pos_in_token);
|
2013-12-30 15:22:18 +01:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return Completions{};
|
2011-09-16 11:18:51 +02:00
|
|
|
}
|
|
|
|
|
2015-06-26 14:52:01 +02:00
|
|
|
Completions CommandManager::complete(const Context& context,
|
|
|
|
CompletionFlags flags,
|
|
|
|
CommandParameters params,
|
|
|
|
size_t token_to_complete,
|
|
|
|
ByteCount pos_in_token)
|
|
|
|
{
|
|
|
|
StringView prefix = params[token_to_complete].substr(0, pos_in_token);
|
|
|
|
|
|
|
|
if (token_to_complete == 0)
|
2017-06-29 08:43:20 +02:00
|
|
|
return complete_command_name(context, prefix);
|
2015-06-26 14:52:01 +02:00
|
|
|
else
|
|
|
|
{
|
2017-04-10 22:33:20 +02:00
|
|
|
StringView command_name = params[0];
|
2016-08-05 14:53:19 +02:00
|
|
|
if (command_name != m_last_complete_command)
|
|
|
|
{
|
2017-04-10 22:33:20 +02:00
|
|
|
m_last_complete_command = command_name.str();
|
2016-08-05 14:53:19 +02:00
|
|
|
flags |= CompletionFlags::Start;
|
|
|
|
}
|
2015-06-26 14:52:01 +02:00
|
|
|
|
2020-07-18 06:29:44 +02:00
|
|
|
auto command_it = m_commands.find(resolve_alias(context, command_name));
|
2017-03-07 01:30:54 +01:00
|
|
|
if (command_it != m_commands.end() and command_it->value.completer)
|
|
|
|
return command_it->value.completer(
|
2016-11-20 12:15:15 +01:00
|
|
|
context, flags, params.subrange(1),
|
2015-06-26 14:52:01 +02:00
|
|
|
token_to_complete-1, pos_in_token);
|
|
|
|
}
|
|
|
|
return Completions{};
|
|
|
|
}
|
|
|
|
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
UnitTest test_command_parsing{[]
|
|
|
|
{
|
|
|
|
auto check_quoted = [](StringView str, bool terminated, StringView content)
|
|
|
|
{
|
2022-03-08 17:51:11 +01:00
|
|
|
auto check_quoted_impl = [&](auto type_hint) {
|
|
|
|
ParseState state{str, str.begin()};
|
|
|
|
const decltype(type_hint) delimiter = *state.pos++;
|
|
|
|
auto quoted = parse_quoted(state, delimiter);
|
|
|
|
kak_assert(quoted.terminated == terminated);
|
|
|
|
kak_assert(quoted.content == content);
|
|
|
|
};
|
|
|
|
check_quoted_impl(Codepoint{});
|
|
|
|
check_quoted_impl(char{});
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
};
|
|
|
|
check_quoted("'abc'", true, "abc");
|
|
|
|
check_quoted("'abc''def", false, "abc'def");
|
|
|
|
check_quoted("'abc''def'''", true, "abc'def'");
|
2022-03-08 17:51:11 +01:00
|
|
|
check_quoted(StringView("'abc''def'", 5), true, "abc");
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
|
2021-07-20 11:53:06 +02:00
|
|
|
auto check_balanced = [](StringView str, bool terminated, StringView content)
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
ParseState state{str, str.begin()+1};
|
2021-07-20 11:53:06 +02:00
|
|
|
auto quoted = parse_quoted_balanced<'{', '}'>(state);
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
kak_assert(quoted.terminated == terminated);
|
|
|
|
kak_assert(quoted.content == content);
|
|
|
|
};
|
2021-07-20 11:53:06 +02:00
|
|
|
check_balanced("{abc}", true, "abc");
|
|
|
|
check_balanced("{abc{def}}", true, "abc{def}");
|
|
|
|
check_balanced("{{abc}{def}", false, "{abc}{def}");
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
|
|
|
|
auto check_unquoted = [](StringView str, StringView content)
|
|
|
|
{
|
2021-07-09 09:03:22 +02:00
|
|
|
ParseState state{str, str.begin()};
|
|
|
|
kak_assert(parse_unquoted(state) == content);
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
};
|
|
|
|
check_unquoted("abc def", "abc");
|
|
|
|
check_unquoted("abc; def", "abc");
|
|
|
|
check_unquoted("abc\\; def", "abc;");
|
|
|
|
check_unquoted("abc\\;\\ def", "abc; def");
|
2018-05-23 15:13:26 +02:00
|
|
|
|
|
|
|
{
|
|
|
|
CommandParser parser(R"(foo 'bar' "baz" qux)");
|
|
|
|
kak_assert(parser.read_token(false)->content == "foo");
|
|
|
|
kak_assert(parser.read_token(false)->content == "bar");
|
|
|
|
kak_assert(parser.read_token(false)->content == "baz");
|
|
|
|
kak_assert(parser.read_token(false)->content == "qux");
|
|
|
|
kak_assert(not parser.read_token(false));
|
|
|
|
}
|
Refactor command line parsing
Command line parsing now works as follow:
* Quoted strings ('...', "..." and %~...~ with '~' non nestable)
use 'doubling-up' for escaping their delimiter, if the delimiter
appears twice in a row, it is considered as part of the string and
represent one delimiter character. So 'abc''def' == "abc'def". No
other escaping takes place in those strings.
* Balanced strings (%{...}) do not support any kind of escaping, but
finds the matching closing delimiter by taking nesting into account.
So %{abc{def}} == "abc{def}".
* Non quoted words support escaping of `;` and whitespaces with `\`,
`%`, `'` and '"` can be escaped with `\` at the start of the word,
they do not need escaping (and will not be escaped) else where in
a word where they are treated literally. Any other use of '\' is a
literal '\'. So \%abc%\;\ def == "%abc%; def"
As discussed in #2046 this should make our command line syntax more
robust, provide a simple programmatic way to escape a string content
(s/<delim>/<delim><delim>/g), be well defined instead of ad-hoc
undocumented behaviour, and interact nicely with other common
escaping by avoiding escaping hell (:grep <regex> can in most case
be written with the regex unquoted).
2018-05-21 14:22:34 +02:00
|
|
|
}};
|
|
|
|
|
2011-09-07 20:16:56 +02:00
|
|
|
}
|