Use shell specific quoting for env vars
Add a test case to validate roundtrips between Kakoune and the shell.
This commit is contained in:
parent
373858f9bf
commit
68aba9e353
|
@ -688,7 +688,7 @@ void Buffer::on_option_changed(const Option& option)
|
|||
m_flags &= ~Flags::ReadOnly;
|
||||
}
|
||||
run_hook_in_own_context("BufSetOption",
|
||||
format("{}={}", option.name(), option.get_as_string()));
|
||||
format("{}={}", option.name(), option.get_as_string(Quoting::Kakoune)));
|
||||
}
|
||||
|
||||
void Buffer::run_hook_in_own_context(StringView hook_name, StringView param, String client_name)
|
||||
|
|
|
@ -253,7 +253,7 @@ Token parse_percent_token(Reader& reader, bool throw_on_unterminated)
|
|||
|
||||
auto expand_option(Option& opt, std::true_type)
|
||||
{
|
||||
return opt.get_as_string();
|
||||
return opt.get_as_string(Quoting::Kakoune);
|
||||
}
|
||||
|
||||
auto expand_option(Option& opt, std::false_type)
|
||||
|
@ -314,7 +314,7 @@ expand_token(const Token& token, const Context& context, const ShellContext& she
|
|||
auto it = shell_context.env_vars.find(content);
|
||||
if (it != shell_context.env_vars.end())
|
||||
return {it->value};
|
||||
return {ShellManager::instance().get_val(content, context)};
|
||||
return {ShellManager::instance().get_val(content, context, Quoting::Kakoune)};
|
||||
}
|
||||
case Token::Type::ArgExpand:
|
||||
{
|
||||
|
|
|
@ -1223,7 +1223,8 @@ const CommandDesc debug_cmd = {
|
|||
{
|
||||
write_to_debug_buffer("Options:");
|
||||
for (auto& option : context.options().flatten_options())
|
||||
write_to_debug_buffer(format(" * {}: {}", option->name(), option->get_as_string()));
|
||||
write_to_debug_buffer(format(" * {}: {}", option->name(),
|
||||
option->get_as_string(Quoting::Kakoune)));
|
||||
}
|
||||
else if (parser[0] == "memory")
|
||||
{
|
||||
|
@ -1366,7 +1367,7 @@ const CommandDesc set_option_cmd = {
|
|||
GlobalScope::instance().option_registry().option_exists(params[start + 1]))
|
||||
{
|
||||
OptionManager& options = get_scope(params[start], context).options();
|
||||
return { 0_byte, params[start + 2].length(), { options[params[start + 1]].get_as_string() }, true };
|
||||
return { 0_byte, params[start + 2].length(), { options[params[start + 1]].get_as_string(Quoting::Kakoune) }, true };
|
||||
}
|
||||
return Completions{};
|
||||
},
|
||||
|
|
69
src/main.cc
69
src/main.cc
|
@ -113,131 +113,132 @@ String config_directory()
|
|||
|
||||
static const EnvVarDesc builtin_env_vars[] = { {
|
||||
"bufname", false,
|
||||
[](StringView name, const Context& context) -> String
|
||||
[](StringView name, const Context& context, Quoting quoting) -> String
|
||||
{ return context.buffer().display_name(); }
|
||||
}, {
|
||||
"buffile", false,
|
||||
[](StringView name, const Context& context) -> String
|
||||
[](StringView name, const Context& context, Quoting quoting) -> String
|
||||
{ return context.buffer().name(); }
|
||||
}, {
|
||||
"buflist", false,
|
||||
[](StringView name, const Context& context)
|
||||
[](StringView name, const Context& context, Quoting quoting)
|
||||
{ return join(BufferManager::instance() |
|
||||
transform(&Buffer::display_name) | transform(quote), ' ', false); }
|
||||
transform(&Buffer::display_name) | transform(quoter(quoting)), ' ', false); }
|
||||
}, {
|
||||
"buf_line_count", false,
|
||||
[](StringView name, const Context& context) -> String
|
||||
[](StringView name, const Context& context, Quoting quoting) -> String
|
||||
{ return to_string(context.buffer().line_count()); }
|
||||
}, {
|
||||
"timestamp", false,
|
||||
[](StringView name, const Context& context) -> String
|
||||
[](StringView name, const Context& context, Quoting quoting) -> String
|
||||
{ return to_string(context.buffer().timestamp()); }
|
||||
}, {
|
||||
"history_id", false,
|
||||
[](StringView name, const Context& context) -> String
|
||||
[](StringView name, const Context& context, Quoting quoting) -> String
|
||||
{ return to_string((size_t)context.buffer().current_history_id()); }
|
||||
}, {
|
||||
"selection", false,
|
||||
[](StringView name, const Context& context)
|
||||
[](StringView name, const Context& context, Quoting quoting)
|
||||
{ const Selection& sel = context.selections().main();
|
||||
return content(context.buffer(), sel); }
|
||||
}, {
|
||||
"selections", false,
|
||||
[](StringView name, const Context& context)
|
||||
{ return join(context.selections_content() | transform(quote), ' ', false); }
|
||||
[](StringView name, const Context& context, Quoting quoting)
|
||||
{ return join(context.selections_content() | transform(quoter(quoting)), ' ', false); }
|
||||
}, {
|
||||
"runtime", false,
|
||||
[](StringView name, const Context& context)
|
||||
[](StringView name, const Context& context, Quoting quoting)
|
||||
{ return runtime_directory(); }
|
||||
}, {
|
||||
"config", false,
|
||||
[](StringView name, const Context& context)
|
||||
[](StringView name, const Context& context, Quoting quoting)
|
||||
{ return config_directory(); }
|
||||
}, {
|
||||
"version", false,
|
||||
[](StringView name, const Context& context) -> String
|
||||
[](StringView name, const Context& context, Quoting quoting) -> String
|
||||
{ return version; }
|
||||
}, {
|
||||
"opt_", true,
|
||||
[](StringView name, const Context& context)
|
||||
{ return context.options()[name.substr(4_byte)].get_as_string(); }
|
||||
[](StringView name, const Context& context, Quoting quoting)
|
||||
{ return context.options()[name.substr(4_byte)].get_as_string(quoting); }
|
||||
}, {
|
||||
"main_reg_", true,
|
||||
[](StringView name, const Context& context)
|
||||
[](StringView name, const Context& context, Quoting quoting)
|
||||
{ return context.main_sel_register_value(name.substr(9_byte)).str(); }
|
||||
}, {
|
||||
"reg_", true,
|
||||
[](StringView name, const Context& context)
|
||||
{ return join(RegisterManager::instance()[name.substr(4_byte)].get(context) | transform(quote), ' ', false); }
|
||||
[](StringView name, const Context& context, Quoting quoting)
|
||||
{ return join(RegisterManager::instance()[name.substr(4_byte)].get(context) |
|
||||
transform(quoter(quoting)), ' ', false); }
|
||||
}, {
|
||||
"client_env_", true,
|
||||
[](StringView name, const Context& context)
|
||||
[](StringView name, const Context& context, Quoting quoting)
|
||||
{ return context.client().get_env_var(name.substr(11_byte)).str(); }
|
||||
}, {
|
||||
"session", false,
|
||||
[](StringView name, const Context& context) -> String
|
||||
[](StringView name, const Context& context, Quoting quoting) -> String
|
||||
{ return Server::instance().session(); }
|
||||
}, {
|
||||
"client", false,
|
||||
[](StringView name, const Context& context) -> String
|
||||
[](StringView name, const Context& context, Quoting quoting) -> String
|
||||
{ return context.name(); }
|
||||
}, {
|
||||
"client_pid", false,
|
||||
[](StringView name, const Context& context) -> String
|
||||
[](StringView name, const Context& context, Quoting quoting) -> String
|
||||
{ return to_string(context.client().pid()); }
|
||||
}, {
|
||||
"client_list", false,
|
||||
[](StringView name, const Context& context) -> String
|
||||
[](StringView name, const Context& context, Quoting quoting) -> String
|
||||
{ return join(ClientManager::instance() |
|
||||
transform([](const std::unique_ptr<Client>& c) -> const String&
|
||||
{ return c->context().name(); }), ' ', false); }
|
||||
}, {
|
||||
"modified", false,
|
||||
[](StringView name, const Context& context) -> String
|
||||
[](StringView name, const Context& context, Quoting quoting) -> String
|
||||
{ return context.buffer().is_modified() ? "true" : "false"; }
|
||||
}, {
|
||||
"cursor_line", false,
|
||||
[](StringView name, const Context& context) -> String
|
||||
[](StringView name, const Context& context, Quoting quoting) -> String
|
||||
{ return to_string(context.selections().main().cursor().line + 1); }
|
||||
}, {
|
||||
"cursor_column", false,
|
||||
[](StringView name, const Context& context) -> String
|
||||
[](StringView name, const Context& context, Quoting quoting) -> String
|
||||
{ return to_string(context.selections().main().cursor().column + 1); }
|
||||
}, {
|
||||
"cursor_char_value", false,
|
||||
[](StringView name, const Context& context) -> String
|
||||
[](StringView name, const Context& context, Quoting quoting) -> String
|
||||
{ auto coord = context.selections().main().cursor();
|
||||
auto& buffer = context.buffer();
|
||||
return to_string((size_t)utf8::codepoint(buffer.iterator_at(coord), buffer.end())); }
|
||||
}, {
|
||||
"cursor_char_column", false,
|
||||
[](StringView name, const Context& context) -> String
|
||||
[](StringView name, const Context& context, Quoting quoting) -> String
|
||||
{ auto coord = context.selections().main().cursor();
|
||||
return to_string(context.buffer()[coord.line].char_count_to(coord.column) + 1); }
|
||||
}, {
|
||||
"cursor_byte_offset", false,
|
||||
[](StringView name, const Context& context) -> String
|
||||
[](StringView name, const Context& context, Quoting quoting) -> String
|
||||
{ auto cursor = context.selections().main().cursor();
|
||||
return to_string(context.buffer().distance({0,0}, cursor)); }
|
||||
}, {
|
||||
"selection_desc", false,
|
||||
[](StringView name, const Context& context)
|
||||
[](StringView name, const Context& context, Quoting quoting)
|
||||
{ return selection_to_string(context.selections().main()); }
|
||||
}, {
|
||||
"selections_desc", false,
|
||||
[](StringView name, const Context& context)
|
||||
[](StringView name, const Context& context, Quoting quoting)
|
||||
{ return selection_list_to_string(context.selections()); }
|
||||
}, {
|
||||
"window_width", false,
|
||||
[](StringView name, const Context& context) -> String
|
||||
[](StringView name, const Context& context, Quoting quoting) -> String
|
||||
{ return to_string(context.window().dimensions().column); }
|
||||
}, {
|
||||
"window_height", false,
|
||||
[](StringView name, const Context& context) -> String
|
||||
[](StringView name, const Context& context, Quoting quoting) -> String
|
||||
{ return to_string(context.window().dimensions().line); }
|
||||
}, {
|
||||
"user_modes", false,
|
||||
[](StringView name, const Context& context) -> String
|
||||
[](StringView name, const Context& context, Quoting quoting) -> String
|
||||
{ return join(context.keymaps().user_modes(), ' ', false); }
|
||||
}
|
||||
};
|
||||
|
|
|
@ -53,7 +53,7 @@ public:
|
|||
template<typename T> void set(const T& val, bool notify=true);
|
||||
template<typename T> bool is_of_type() const;
|
||||
|
||||
virtual String get_as_string() const = 0;
|
||||
virtual String get_as_string(Quoting quoting) const = 0;
|
||||
virtual Vector<String> get_as_strings() const = 0;
|
||||
virtual void set_from_strings(ConstArrayView<String> strs) = 0;
|
||||
virtual void add_from_strings(ConstArrayView<String> strs) = 0;
|
||||
|
@ -147,9 +147,9 @@ public:
|
|||
return option_to_strings(m_value);
|
||||
}
|
||||
|
||||
String get_as_string() const override
|
||||
String get_as_string(Quoting quoting) const override
|
||||
{
|
||||
return option_to_string(m_value);
|
||||
return option_to_string(m_value, quoting);
|
||||
}
|
||||
|
||||
void set_from_strings(ConstArrayView<String> strs) override
|
||||
|
|
|
@ -18,6 +18,13 @@
|
|||
namespace Kakoune
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
std::enable_if_t<std::is_same<decltype(option_to_string(std::declval<T>())), String>::value, String>
|
||||
option_to_string(const T& value, Quoting)
|
||||
{
|
||||
return option_to_string(value);
|
||||
}
|
||||
|
||||
template<typename T, typename... Rest>
|
||||
constexpr bool option_needs_quoting(Meta::Type<T> type, Meta::Type<Rest>... rest)
|
||||
{
|
||||
|
@ -25,10 +32,10 @@ constexpr bool option_needs_quoting(Meta::Type<T> type, Meta::Type<Rest>... rest
|
|||
}
|
||||
|
||||
template<typename... Ts>
|
||||
String quote_ifn(String str)
|
||||
String quote_ifn(String str, Quoting quoting)
|
||||
{
|
||||
if (option_needs_quoting(Meta::Type<Ts>{}...))
|
||||
return quote(std::move(str));
|
||||
return quoter(quoting)(std::move(str));
|
||||
return str;
|
||||
}
|
||||
|
||||
|
@ -89,9 +96,9 @@ Vector<String> option_to_strings(const Vector<T, domain>& opt)
|
|||
}
|
||||
|
||||
template<typename T, MemoryDomain domain>
|
||||
String option_to_string(const Vector<T, domain>& opt)
|
||||
String option_to_string(const Vector<T, domain>& opt, Quoting quoting)
|
||||
{
|
||||
return join(opt | transform([](const T& t) { return quote_ifn<T>(option_to_string(t)); }), ' ', false);
|
||||
return join(opt | transform([=](const T& t) { return quote_ifn<T>(option_to_string(t), quoting); }), ' ', false);
|
||||
}
|
||||
|
||||
template<typename T, MemoryDomain domain>
|
||||
|
@ -135,13 +142,13 @@ Vector<String> option_to_strings(const HashMap<Key, Value, domain>& opt)
|
|||
}
|
||||
|
||||
template<typename Key, typename Value, MemoryDomain domain>
|
||||
String option_to_string(const HashMap<Key, Value, domain>& opt)
|
||||
String option_to_string(const HashMap<Key, Value, domain>& opt, Quoting quoting)
|
||||
{
|
||||
return join(opt | transform([](auto&& item) {
|
||||
return join(opt | transform([=](auto&& item) {
|
||||
return quote_ifn<Key, Value>(
|
||||
format("{}={}",
|
||||
escape(option_to_string(item.key), '=', '\\'),
|
||||
escape(option_to_string(item.value), '=', '\\')));
|
||||
escape(option_to_string(item.value), '=', '\\')), quoting);
|
||||
}), ' ', false);
|
||||
}
|
||||
|
||||
|
@ -335,9 +342,9 @@ inline Vector<String> option_to_strings(const PrefixedList<P, T>& opt)
|
|||
}
|
||||
|
||||
template<typename P, typename T>
|
||||
inline String option_to_string(const PrefixedList<P, T>& opt)
|
||||
inline String option_to_string(const PrefixedList<P, T>& opt, Quoting quoting)
|
||||
{
|
||||
return option_to_string(opt.prefix) + " " + option_to_string(opt.list);
|
||||
return option_to_string(opt.prefix, quoting) + " " + option_to_string(opt.list, quoting);
|
||||
}
|
||||
|
||||
template<typename P, typename T>
|
||||
|
|
|
@ -19,7 +19,7 @@ void GlobalScope::on_option_changed(const Option& option)
|
|||
{
|
||||
Context empty_context{Context::EmptyContextFlag{}};
|
||||
hooks().run_hook("GlobalSetOption",
|
||||
format("{}={}", option.name(), option.get_as_string()),
|
||||
format("{}={}", option.name(), option.get_as_string(Quoting::Kakoune)),
|
||||
empty_context);
|
||||
}
|
||||
|
||||
|
|
|
@ -138,7 +138,7 @@ Vector<String> generate_env(StringView cmdline, const Context& context, const Sh
|
|||
try
|
||||
{
|
||||
const String& value = var_it != shell_context.env_vars.end() ?
|
||||
var_it->value : ShellManager::instance().get_val(name, context);
|
||||
var_it->value : ShellManager::instance().get_val(name, context, Quoting::Shell);
|
||||
|
||||
kak_env.push_back(format("kak_{}={}", name, value));
|
||||
} catch (runtime_error&) {}
|
||||
|
@ -312,7 +312,7 @@ std::pair<String, int> ShellManager::eval(
|
|||
return { std::move(stdout_contents), WIFEXITED(status) ? WEXITSTATUS(status) : -1 };
|
||||
}
|
||||
|
||||
String ShellManager::get_val(StringView name, const Context& context) const
|
||||
String ShellManager::get_val(StringView name, const Context& context, Quoting quoting) const
|
||||
{
|
||||
auto env_var = find_if(m_env_vars, [name](const EnvVarDesc& desc) {
|
||||
return desc.prefix ? prefix_match(name, desc.str) : name == desc.str;
|
||||
|
@ -321,7 +321,7 @@ String ShellManager::get_val(StringView name, const Context& context) const
|
|||
if (env_var == m_env_vars.end())
|
||||
throw runtime_error("no such env var: " + name);
|
||||
|
||||
return env_var->func(name, context);
|
||||
return env_var->func(name, context, quoting);
|
||||
}
|
||||
|
||||
CandidateList ShellManager::complete_env_var(StringView prefix,
|
||||
|
|
|
@ -18,10 +18,11 @@ struct ShellContext
|
|||
EnvVarMap env_vars;
|
||||
};
|
||||
|
||||
enum class Quoting;
|
||||
|
||||
struct EnvVarDesc
|
||||
{
|
||||
using Retriever = String (*)(StringView name, const Context&);
|
||||
using Retriever = String (*)(StringView name, const Context&, Quoting quoting);
|
||||
|
||||
StringView str;
|
||||
bool prefix;
|
||||
|
@ -45,7 +46,7 @@ public:
|
|||
Flags flags = Flags::WaitForStdout,
|
||||
const ShellContext& shell_context = {});
|
||||
|
||||
String get_val(StringView name, const Context& context) const;
|
||||
String get_val(StringView name, const Context& context, Quoting quoting) const;
|
||||
|
||||
CandidateList complete_env_var(StringView prefix, ByteCount cursor_pos) const;
|
||||
|
||||
|
|
|
@ -135,6 +135,23 @@ inline String quote(StringView s)
|
|||
return format("'{}'", double_up(s, "'"));
|
||||
}
|
||||
|
||||
inline String shell_quote(StringView s)
|
||||
{
|
||||
return format("'{}'", replace(s, "'", R"('\'')"));
|
||||
}
|
||||
|
||||
enum class Quoting
|
||||
{
|
||||
Kakoune,
|
||||
Shell
|
||||
};
|
||||
|
||||
inline auto quoter(Quoting quoting)
|
||||
{
|
||||
return quoting == Quoting::Kakoune ? "e : &shell_quote;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif // string_utils_hh_INCLUDED
|
||||
|
|
|
@ -340,7 +340,7 @@ void Window::clear_display_buffer()
|
|||
void Window::on_option_changed(const Option& option)
|
||||
{
|
||||
run_hook_in_own_context("WinSetOption", format("{}={}", option.name(),
|
||||
option.get_as_string()));
|
||||
option.get_as_string(Quoting::Kakoune)));
|
||||
// an highlighter might depend on the option, so we need to redraw
|
||||
force_redraw();
|
||||
}
|
||||
|
|
1
test/shell/list-syntax/cmd
Normal file
1
test/shell/list-syntax/cmd
Normal file
|
@ -0,0 +1 @@
|
|||
|
1
test/shell/list-syntax/in
Normal file
1
test/shell/list-syntax/in
Normal file
|
@ -0,0 +1 @@
|
|||
|
4
test/shell/list-syntax/out
Normal file
4
test/shell/list-syntax/out
Normal file
|
@ -0,0 +1,4 @@
|
|||
foo
|
||||
bar
|
||||
'foo'bar'
|
||||
|
7
test/shell/list-syntax/rc
Normal file
7
test/shell/list-syntax/rc
Normal file
|
@ -0,0 +1,7 @@
|
|||
declare-option str-list my_list 'foo' 'bar' '''foo''bar'''
|
||||
evaluate-commands %sh{
|
||||
eval set -- $kak_opt_my_list
|
||||
for elem; do
|
||||
echo exec "'i$(printf %s "$elem" | sed -e s/\'/\'\'/g)<ret><esc>'"
|
||||
done
|
||||
}
|
Loading…
Reference in New Issue
Block a user