summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMaxime Coste <mawww@kakoune.org>2023-11-07 18:01:54 +1100
committerMaxime Coste <mawww@kakoune.org>2023-11-14 21:39:03 +1100
commit11f0ace9b6bf7cf618a42de7e37d9608d2d86d99 (patch)
tree5ad940ddb9c8286a33dbd86514c6d129df346758 /src
parent719512b308f1d5165037775cb314094f1bb870ad (diff)
Make shell-script-candidates completer run in the background
Read output from the script as it comes and update the candidate list progressively. Disable updating of the list when a completion has been explicitely selected.
Diffstat (limited to 'src')
-rw-r--r--src/commands.cc70
-rw-r--r--src/input_handler.cc26
-rw-r--r--src/input_handler.hh2
-rw-r--r--src/main.cc1
-rw-r--r--src/shell_manager.cc64
-rw-r--r--src/shell_manager.hh23
-rw-r--r--src/unique_descriptor.hh23
7 files changed, 150 insertions, 59 deletions
diff --git a/src/commands.cc b/src/commands.cc
index 92e11e1b..55becf4a 100644
--- a/src/commands.cc
+++ b/src/commands.cc
@@ -280,53 +280,85 @@ struct ShellCandidatesCompleter
Completions::Flags flags = Completions::Flags::None)
: m_shell_script{std::move(shell_script)}, m_flags(flags) {}
+ ShellCandidatesCompleter(const ShellCandidatesCompleter& other) : m_shell_script{other.m_shell_script}, m_flags(other.m_flags) {}
+ ShellCandidatesCompleter& operator=(const ShellCandidatesCompleter& other) { m_shell_script = other.m_shell_script; m_flags = other.m_flags; return *this; }
+
Completions operator()(const Context& context, CompletionFlags flags,
CommandParameters params, size_t token_to_complete,
ByteCount pos_in_token)
{
- if (m_token != token_to_complete)
+ if (m_last_token != token_to_complete)
{
ShellContext shell_context{
params,
{ { "token_to_complete", to_string(token_to_complete) } }
};
- String output = ShellManager::instance().eval(m_shell_script, context, {},
- ShellManager::Flags::WaitForStdout,
- shell_context).first;
+ m_running_script.emplace(ShellManager::instance().spawn(m_shell_script, context, false, shell_context));
+ m_watcher.emplace((int)m_running_script->out, FdEvents::Read, EventMode::Urgent,
+ [this, &input_handler=context.input_handler()](auto&&... args) { read_candidates(input_handler); });
m_candidates.clear();
- for (auto c : output | split<StringView>('\n')
- | filter([](auto s) { return not s.empty(); }))
- m_candidates.emplace_back(c.str(), used_letters(c));
- m_token = token_to_complete;
+ m_last_token = token_to_complete;
}
+ return rank_candidates(params[token_to_complete].substr(0, pos_in_token));
+ }
- StringView query = params[token_to_complete].substr(0, pos_in_token);
- UsedLetters query_letters = used_letters(query);
- Vector<RankedMatch> matches;
- for (const auto& candidate : m_candidates)
+private:
+ void read_candidates(InputHandler& input_handler)
+ {
+ char buffer[2048];
+ bool closed = false;
+ int fd = (int)m_running_script->out;
+ while (fd_readable(fd))
{
- if (RankedMatch match{candidate.first, candidate.second, query, query_letters})
- matches.push_back(match);
+ int size = read(fd, buffer, sizeof(buffer));
+ if (size == 0)
+ {
+ closed = true;
+ break;
+ }
+ m_stdout_buffer.insert(m_stdout_buffer.end(), buffer, buffer + size);
}
+ auto end = closed ? m_stdout_buffer.end() : find(m_stdout_buffer | reverse(), '\n').base();
+ for (auto c : ArrayView(m_stdout_buffer.begin(), end) | split<StringView>('\n')
+ | filter([](auto s) { return not s.empty(); }))
+ m_candidates.emplace_back(c.str(), used_letters(c));
+ m_stdout_buffer.erase(m_stdout_buffer.begin(), end);
+
+ input_handler.refresh_ifn();
+ if (not closed)
+ return;
+
+ m_running_script.reset();
+ m_watcher.reset();
+ }
+
+ Completions rank_candidates(StringView query)
+ {
+ UsedLetters query_letters = used_letters(query);
+ auto matches = m_candidates | transform([&](const auto& c) { return RankedMatch{c.first, c.second, query, query_letters}; })
+ | filter([](const auto& m) { return (bool)m; })
+ | gather<Vector<RankedMatch>>();
+
constexpr size_t max_count = 100;
CandidateList res;
// Gather best max_count matches
- for_n_best(matches, max_count, [](auto& lhs, auto& rhs) { return rhs < lhs; },
- [&] (const RankedMatch& m) {
+ for_n_best(matches, max_count, [](auto& lhs, auto& rhs) { return rhs < lhs; }, [&] (const RankedMatch& m) {
if (not res.empty() and res.back() == m.candidate())
return false;
res.push_back(m.candidate().str());
return true;
});
- return Completions{0_byte, pos_in_token, std::move(res), m_flags};
+ return Completions{0_byte, query.length(), std::move(res), m_flags};
}
-private:
String m_shell_script;
+ Vector<char, MemoryDomain::Completion> m_stdout_buffer;
+ Optional<Shell> m_running_script;
+ Optional<FDWatcher> m_watcher;
Vector<std::pair<String, UsedLetters>, MemoryDomain::Completion> m_candidates;
- int m_token = -1;
+ int m_last_token = -1;
Completions::Flags m_flags;
};
diff --git a/src/input_handler.cc b/src/input_handler.cc
index f1eeb58f..9bc495a2 100644
--- a/src/input_handler.cc
+++ b/src/input_handler.cc
@@ -37,6 +37,8 @@ public:
virtual void on_enabled(bool from_pop) {}
virtual void on_disabled(bool from_push) {}
+ virtual void refresh_ifn() {}
+
bool enabled() const { return &m_input_handler.current_mode() == this; }
Context& context() const { return m_input_handler.context(); }
@@ -1048,6 +1050,19 @@ public:
m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context()));
}
+ void refresh_ifn()
+ {
+ bool explicit_completion_selected = m_current_completion != -1 and
+ (not m_prefix_in_completions or m_current_completion != m_completions.candidates.size() - 1);
+ if (not enabled() or (context().flags() & Context::Flags::Draft) or explicit_completion_selected)
+ return;
+
+ if (auto next_date = Clock::now() + get_idle_timeout(context());
+ next_date < m_idle_timer.next_date())
+ m_idle_timer.set_next_date(next_date);
+ m_refresh_completion_pending = true;
+ }
+
void paste(StringView content) override
{
m_line_editor.insert(content);
@@ -1122,14 +1137,12 @@ private:
if (menu)
context().client().menu_select(0);
auto prefix = line.substr(m_completions.start, m_completions.end - m_completions.start);
- if (not menu and not contains(m_completions.candidates, prefix))
+ m_prefix_in_completions = not menu and not contains(m_completions.candidates, prefix);
+ if (m_prefix_in_completions)
{
m_current_completion = m_completions.candidates.size();
m_completions.candidates.push_back(prefix.str());
- m_prefix_in_completions = true;
}
- else
- m_prefix_in_completions = false;
} catch (runtime_error&) {}
}
@@ -1814,6 +1827,11 @@ void InputHandler::handle_key(Key key)
}
}
+void InputHandler::refresh_ifn()
+{
+ current_mode().refresh_ifn();
+}
+
void InputHandler::start_recording(char reg)
{
kak_assert(m_recording_reg == 0);
diff --git a/src/input_handler.hh b/src/input_handler.hh
index afcc24d4..a26d392c 100644
--- a/src/input_handler.hh
+++ b/src/input_handler.hh
@@ -99,6 +99,8 @@ public:
// process the given key
void handle_key(Key key);
+ void refresh_ifn();
+
void start_recording(char reg);
bool is_recording() const;
void stop_recording();
diff --git a/src/main.cc b/src/main.cc
index bc12f335..09df5361 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -46,6 +46,7 @@ struct {
StringView notes;
} constexpr version_notes[] = { {
0,
+ "» asynchronous {+u}shell-script-candidates{} completion\n"
"» {+b}%val{window_range}{} is now emitted as separate strings\n"
"» {+b}+{} only duplicates identical selections a single time\n"
"» {+u}daemonize-session{} command\n"
diff --git a/src/shell_manager.cc b/src/shell_manager.cc
index 0d012538..e505fdef 100644
--- a/src/shell_manager.cc
+++ b/src/shell_manager.cc
@@ -86,26 +86,6 @@ ShellManager::ShellManager(ConstArrayView<EnvVarDesc> builtin_env_vars)
namespace
{
-struct UniqueFd
-{
- UniqueFd(int fd = -1) : fd{fd} {}
- UniqueFd(UniqueFd&& other) : fd{other.fd} { other.fd = -1; }
- UniqueFd& operator=(UniqueFd&& other) { std::swap(fd, other.fd); other.close(); return *this; }
- ~UniqueFd() { close(); }
-
- explicit operator bool() const { return fd != -1; }
- void close() { if (fd != -1) { ::close(fd); fd = -1; } }
- int fd;
-};
-
-struct Shell
-{
- pid_t pid;
- UniqueFd in;
- UniqueFd out;
- UniqueFd err;
-};
-
Shell spawn_shell(const char* shell, StringView cmdline,
ConstArrayView<String> params,
ConstArrayView<String> kak_env,
@@ -145,13 +125,13 @@ Shell spawn_shell(const char* shell, StringView cmdline,
close(oldfd);
};
- renamefd(stdin_pipe[0].fd, 0);
- renamefd(stdout_pipe[1].fd, 1);
- renamefd(stderr_pipe[1].fd, 2);
+ renamefd((int)stdin_pipe[0], 0);
+ renamefd((int)stdout_pipe[1], 1);
+ renamefd((int)stderr_pipe[1], 2);
- close(stdin_pipe[1].fd);
- close(stdout_pipe[0].fd);
- close(stderr_pipe[0].fd);
+ close((int)stdin_pipe[1]);
+ close((int)stdout_pipe[0]);
+ close((int)stderr_pipe[0]);
execve(shell, (char* const*)execparams.data(), (char* const*)envptrs.data());
char buffer[1024];
@@ -215,13 +195,13 @@ FDWatcher make_reader(int fd, String& contents, OnClose&& on_close)
FDWatcher make_pipe_writer(UniqueFd& fd, StringView contents)
{
- int flags = fcntl(fd.fd, F_GETFL, 0);
- fcntl(fd.fd, F_SETFL, flags | O_NONBLOCK);
- return {fd.fd, FdEvents::Write, EventMode::Urgent,
+ int flags = fcntl((int)fd, F_GETFL, 0);
+ fcntl((int)fd, F_SETFL, flags | O_NONBLOCK);
+ return {(int)fd, FdEvents::Write, EventMode::Urgent,
[contents, &fd](FDWatcher& watcher, FdEvents, EventMode) mutable {
- while (fd_writable(fd.fd))
+ while (fd_writable((int)fd))
{
- ssize_t size = ::write(fd.fd, contents.begin(),
+ ssize_t size = ::write((int)fd, contents.begin(),
(size_t)contents.length());
if (size > 0)
contents = contents.substr(ByteCount{(int)size});
@@ -310,8 +290,8 @@ std::pair<String, int> ShellManager::eval(
auto wait_time = Clock::now();
String stdout_contents, stderr_contents;
- auto stdout_reader = make_reader(shell.out.fd, stdout_contents, [&](bool){ shell.out.close(); });
- auto stderr_reader = make_reader(shell.err.fd, stderr_contents, [&](bool){ shell.err.close(); });
+ auto stdout_reader = make_reader((int)shell.out, stdout_contents, [&](bool){ shell.out.close(); });
+ auto stderr_reader = make_reader((int)shell.err, stderr_contents, [&](bool){ shell.err.close(); });
auto stdin_writer = make_pipe_writer(shell.in, input);
// block SIGCHLD to make sure we wont receive it before
@@ -324,7 +304,7 @@ std::pair<String, int> ShellManager::eval(
int status = 0;
// check for termination now that SIGCHLD is blocked
- bool terminated = waitpid(shell.pid, &status, WNOHANG) != 0;
+ bool terminated = waitpid((int)shell.pid, &status, WNOHANG) != 0;
bool failed = false;
using namespace std::chrono;
@@ -357,7 +337,7 @@ std::pair<String, int> ShellManager::eval(
}
catch (cancel&)
{
- kill(shell.pid, SIGINT);
+ kill((int)shell.pid, SIGINT);
cancelling = true;
}
catch (runtime_error& error)
@@ -366,7 +346,7 @@ std::pair<String, int> ShellManager::eval(
failed = true;
}
if (not terminated)
- terminated = waitpid(shell.pid, &status, WNOHANG) == shell.pid;
+ terminated = waitpid((int)shell.pid, &status, WNOHANG) == (int)shell.pid;
}
if (not stderr_contents.empty())
@@ -394,6 +374,18 @@ std::pair<String, int> ShellManager::eval(
return { std::move(stdout_contents), WIFEXITED(status) ? WEXITSTATUS(status) : -1 };
}
+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) {
+ 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);
+ });
+
+ return spawn_shell(m_shell.c_str(), cmdline, shell_context.params, kak_env, open_stdin);
+}
+
Vector<String> ShellManager::get_val(StringView name, const Context& context) const
{
auto env_var = find_if(m_env_vars, [name](const EnvVarDesc& desc) {
diff --git a/src/shell_manager.hh b/src/shell_manager.hh
index 7dc00f83..6b45961a 100644
--- a/src/shell_manager.hh
+++ b/src/shell_manager.hh
@@ -5,8 +5,13 @@
#include "env_vars.hh"
#include "string.hh"
#include "utils.hh"
+#include "unique_descriptor.hh"
#include "completion.hh"
+#include <signal.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
namespace Kakoune
{
@@ -27,6 +32,19 @@ struct EnvVarDesc
Retriever func;
};
+inline void closepid(int pid){ kill(pid, SIGTERM); int status = 0; waitpid(pid, &status, 0); }
+
+using UniqueFd = UniqueDescriptor<::close>;
+using UniquePid = UniqueDescriptor<closepid>;
+
+struct Shell
+{
+ UniquePid pid;
+ UniqueFd in;
+ UniqueFd out;
+ UniqueFd err;
+};
+
class ShellManager : public Singleton<ShellManager>
{
public:
@@ -44,6 +62,11 @@ public:
Flags flags = Flags::WaitForStdout,
const ShellContext& shell_context = {});
+ Shell spawn(StringView cmdline,
+ const Context& context,
+ bool open_stdin,
+ const ShellContext& shell_complete = {});
+
Vector<String> get_val(StringView name, const Context& context) const;
CandidateList complete_env_var(StringView prefix, ByteCount cursor_pos) const;
diff --git a/src/unique_descriptor.hh b/src/unique_descriptor.hh
new file mode 100644
index 00000000..c099c17e
--- /dev/null
+++ b/src/unique_descriptor.hh
@@ -0,0 +1,23 @@
+#ifndef fd_hh_INCLUDED
+#define fd_hh_INCLUDED
+
+namespace Kakoune
+{
+
+template<auto close_fn>
+struct UniqueDescriptor
+{
+ UniqueDescriptor(int descriptor = -1) : descriptor{descriptor} {}
+ UniqueDescriptor(UniqueDescriptor&& other) : descriptor{other.descriptor} { other.descriptor = -1; }
+ UniqueDescriptor& operator=(UniqueDescriptor&& other) { std::swap(descriptor, other.descriptor); other.close(); return *this; }
+ ~UniqueDescriptor() { close(); }
+
+ explicit operator int() const { return descriptor; }
+ explicit operator bool() const { return descriptor != -1; }
+ void close() { if (descriptor != -1) { close_fn(descriptor); descriptor = -1; } }
+ int descriptor;
+};
+
+}
+
+#endif // fd_hh_INCLUDED