From 1bdae3f9c4e554c62100560d48957d42f5ae17e0 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Fri, 7 Jun 2024 18:59:30 +1000 Subject: [PATCH] Also check shell parameters for kak_* references This makes it easier to pass shell fragments as arguments so that %sh{ eval "$@" } just works even if arguments refer to Kakoune's vars. --- doc/pages/changelog.asciidoc | 4 +++ doc/pages/expansions.asciidoc | 17 +++++++++--- src/main.cc | 3 +++ src/shell_manager.cc | 49 +++++++++++++++++++---------------- 4 files changed, 48 insertions(+), 25 deletions(-) diff --git a/doc/pages/changelog.asciidoc b/doc/pages/changelog.asciidoc index ee5e374f6..a553433ae 100644 --- a/doc/pages/changelog.asciidoc +++ b/doc/pages/changelog.asciidoc @@ -3,6 +3,10 @@ This changelog contains major and/or breaking changes to Kakoune between released versions. +== Development version + +* Expose env vars that are mentionned in the arguments passed to shell expansions + == Kakoune 2024.05.18 * Fixed tests on Alpine Linux and *BSD diff --git a/doc/pages/expansions.asciidoc b/doc/pages/expansions.asciidoc index 7bfd38948..6277c0dcc 100644 --- a/doc/pages/expansions.asciidoc +++ b/doc/pages/expansions.asciidoc @@ -146,14 +146,16 @@ and unquote them, then execute the resulting `set` command, which sets the shell's argument variables to the items from `$kak_selections`. The `while` loop with `shift` iterates through the arguments one by one. -Only variables actually mentioned in the body of the shell expansion will -be exported into the shell's environment. For example: +Only variables actually mentioned in the body of the shell expansion or +in passed arguments will be exported into the shell's environment. + +For example: ---- echo %sh{ env | grep ^kak_ } ---- -... will find none of Kakoune's special environment variables, but: +... will not find any of Kakoune's special environment variables, but: ---- echo %sh{ env | grep ^kak_ # kak_session } @@ -162,6 +164,15 @@ echo %sh{ env | grep ^kak_ # kak_session } ... will find the `$kak_session` variable because it was mentioned by name in a comment, even though it wasn't directly used. +---- +define-command -params .. eval-shell %{ echo %sh{ eval "$@" } } +eval-shell 'echo $kak_session' +---- + +... will also find the `$kak_session` variable because it was mentioned by name +in the command arguments, which are automatically made available to shell blocks +as "$@" + TIP: These environment variables are also available in other contexts where Kakoune uses a shell command, such as the `|`, `!` or `$` normal mode commands (See <>). diff --git a/src/main.cc b/src/main.cc index 5b316eaa5..0c63403bd 100644 --- a/src/main.cc +++ b/src/main.cc @@ -45,6 +45,9 @@ struct { unsigned int version; StringView notes; } constexpr version_notes[] = { { + 0, + "» kak_* appearing in shell arguments will be added to the environment\n" + }, { 20240518, "» Fix tests failing on some platforms\n" }, { diff --git a/src/shell_manager.cc b/src/shell_manager.cc index e505fdef1..47432df50 100644 --- a/src/shell_manager.cc +++ b/src/shell_manager.cc @@ -141,30 +141,35 @@ Shell spawn_shell(const char* shell, StringView cmdline, } template -Vector generate_env(StringView cmdline, const Context& context, GetValue&& get_value) +Vector generate_env(StringView cmdline, ConstArrayView params, const Context& context, GetValue&& get_value) { static const Regex re(R"(\bkak_(quoted_)?(\w+)\b)"); Vector env; - for (auto&& match : RegexIterator{cmdline.begin(), cmdline.end(), re}) - { - StringView name{match[2].first, match[2].second}; - StringView shell_name{match[0].first, match[0].second}; - - auto match_name = [&](const String& s) { - return s.substr(0_byte, shell_name.length()) == shell_name and - s.substr(shell_name.length(), 1_byte) == "="; - }; - if (any_of(env, match_name)) - continue; - - try + auto add_matches = [&](StringView s) { + for (auto&& match : RegexIterator{s.begin(), s.end(), re}) { - StringView quoted{match[1].first, match[1].second}; - Quoting quoting = match[1].matched ? Quoting::Shell : Quoting::Raw; - env.push_back(format("kak_{}{}={}", quoted, name, get_value(name, quoting))); - } catch (runtime_error&) {} - } + StringView name{match[2].first, match[2].second}; + StringView shell_name{match[0].first, match[0].second}; + + auto match_name = [&](const String& s) { + return s.substr(0_byte, shell_name.length()) == shell_name and + s.substr(shell_name.length(), 1_byte) == "="; + }; + if (any_of(env, match_name)) + continue; + + try + { + StringView quoted{match[1].first, match[1].second}; + Quoting quoting = match[1].matched ? Quoting::Shell : Quoting::Raw; + env.push_back(format("kak_{}{}={}", quoted, name, get_value(name, quoting))); + } catch (runtime_error&) {} + } + }; + add_matches(cmdline); + for (auto&& param : params) + add_matches(param); return env; } @@ -265,13 +270,13 @@ std::pair ShellManager::eval( const DebugFlags debug_flags = context.options()["debug"].get(); const bool profile = debug_flags & DebugFlags::Profile; if (debug_flags & DebugFlags::Shell) - write_to_debug_buffer(format("shell:\n{}\n----\n", cmdline)); + write_to_debug_buffer(format("shell:\n{}\n----\nargs: {}\n----\n", cmdline, join(shell_context.params | transform(shell_quote), ' '))); auto start_time = profile ? Clock::now() : Clock::time_point{}; Optional command_fifos; - auto kak_env = generate_env(cmdline, context, [&](StringView name, Quoting quoting) { + auto kak_env = generate_env(cmdline, shell_context.params, context, [&](StringView name, Quoting quoting) { if (name == "command_fifo" or name == "response_fifo") { if (not command_fifos) @@ -377,7 +382,7 @@ std::pair ShellManager::eval( Shell ShellManager::spawn(StringView cmdline, const Context& context, bool open_stdin, const ShellContext& shell_context) { - auto kak_env = generate_env(cmdline, context, [&](StringView name, Quoting quoting) { + auto kak_env = generate_env(cmdline, shell_context.params, context, [&](StringView name, Quoting quoting) { if (auto it = shell_context.env_vars.find(name); it != shell_context.env_vars.end()) return it->value; return join(get_val(name, context) | transform(quoter(quoting)), ' ', false);