diff --git a/doc/pages/expansions.asciidoc b/doc/pages/expansions.asciidoc index f1b4c802..93aab184 100644 --- a/doc/pages/expansions.asciidoc +++ b/doc/pages/expansions.asciidoc @@ -166,6 +166,24 @@ TIP: These environment variables are also available in other contexts where Kakoune uses a shell command, such as the `|`, `!` or `$` normal mode commands (See <>). +=== Command and Response fifo + +Inside shell expansions, `$kak_command_fifo` refers to a named pipe that +accepts Kakoune commands to be executed as soon as the fifo is closed. This +named pipe can be opened and closed multiple times which makes it possible +to interleave shell and Kakoune commands. `$kak_response_fifo` refers to +a named pipe that can be used to return data from Kakoune. + +--- +%sh{ + echo "write $kak_response_fifo" > $kak_command_fifo + content="$(cat $kak_response_fifo)" +} +--- + +This also makes it possible to pass data bigger than the system environment +size limit. + == File expansions Expansions with the type `file` will expand to the content of the filename diff --git a/src/event_manager.hh b/src/event_manager.hh index b7a9720f..edf52e3e 100644 --- a/src/event_manager.hh +++ b/src/event_manager.hh @@ -46,6 +46,7 @@ public: void run(FdEvents events, EventMode mode); + void reset_fd(int fd) { m_fd = fd; } void close_fd(); void disable() { m_fd = -1; } diff --git a/src/shell_manager.cc b/src/shell_manager.cc index 8e1cefc5..298412b7 100644 --- a/src/shell_manager.cc +++ b/src/shell_manager.cc @@ -4,6 +4,7 @@ #include "client.hh" #include "clock.hh" #include "context.hh" +#include "command_manager.hh" #include "display_buffer.hh" #include "event_manager.hh" #include "face_registry.hh" @@ -163,18 +164,19 @@ Vector generate_env(StringView cmdline, const Context& context, GetValue return env; } -FDWatcher make_pipe_reader(Pipe& pipe, String& contents) +template +FDWatcher make_reader(int fd, String& contents, OnClose&& on_close) { - return {pipe.read_fd(), FdEvents::Read, EventMode::Urgent, - [&contents, &pipe](FDWatcher& watcher, FdEvents, EventMode) { + return {fd, FdEvents::Read, EventMode::Urgent, + [fd, &contents, on_close](FDWatcher& watcher, FdEvents, EventMode) { char buffer[1024]; - while (fd_readable(pipe.read_fd())) + while (fd_readable(fd)) { - size_t size = ::read(pipe.read_fd(), buffer, sizeof(buffer)); + size_t size = ::read(fd, buffer, sizeof(buffer)); if (size <= 0) { - pipe.close_read_fd(); watcher.disable(); + on_close(); return; } contents += StringView{buffer, buffer+size}; @@ -206,6 +208,41 @@ FDWatcher make_pipe_writer(Pipe& pipe, StringView contents) }}; } +struct CommandFifos +{ + String base_dir; + String command; + FDWatcher command_watcher; + + CommandFifos(Context& context, const ShellContext& shell_context) + : base_dir(format("{}/kak-fifo.XXXXXX", tmpdir())), + command_watcher([&] { + mkdtemp(base_dir.data()), + mkfifo(command_fifo_path().c_str(), 0600); + mkfifo(response_fifo_path().c_str(), 0600); + int fd = open(command_fifo_path().c_str(), O_RDONLY | O_NONBLOCK); + return make_reader(fd, command, [&, fd] { + close(fd); + CommandManager::instance().execute(command, context, shell_context); + command.clear(); + command_watcher.reset_fd(open(command_fifo_path().c_str(), O_RDONLY | O_NONBLOCK)); + }); + }()) + { + } + + ~CommandFifos() + { + command_watcher.close_fd(); + unlink(command_fifo_path().c_str()); + unlink(response_fifo_path().c_str()); + rmdir(base_dir.c_str()); + } + + String command_fifo_path() const { return format("{}/command-fifo", base_dir); } + String response_fifo_path() const { return format("{}/response-fifo", base_dir); } +}; + } std::pair ShellManager::eval( @@ -219,7 +256,17 @@ std::pair ShellManager::eval( auto start_time = profile ? Clock::now() : Clock::time_point{}; + Optional command_fifos; + auto kak_env = generate_env(cmdline, context, [&](StringView name, Quoting quoting) { + if (name == "command_fifo" or name == "response_fifo") + { + if (not command_fifos) + command_fifos.emplace(const_cast(context), shell_context); + return name == "command_fifo" ? + command_fifos->command_fifo_path() : command_fifos->response_fifo_path(); + } + 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); @@ -259,8 +306,8 @@ std::pair ShellManager::eval( auto wait_time = Clock::now(); String stdout_contents, stderr_contents; - auto stdout_reader = make_pipe_reader(child_stdout, stdout_contents); - auto stderr_reader = make_pipe_reader(child_stderr, stderr_contents); + auto stdout_reader = make_reader(child_stdout.read_fd(), stdout_contents, [&]{ child_stdout.close_read_fd(); }); + auto stderr_reader = make_reader(child_stderr.read_fd(), stderr_contents, [&]{ child_stderr.close_read_fd(); }); auto stdin_writer = make_pipe_writer(child_stdin, input); // block SIGCHLD to make sure we wont receive it before