diff options
| author | Maxime Coste <mawww@kakoune.org> | 2024-10-22 21:18:23 +1100 |
|---|---|---|
| committer | Maxime Coste <mawww@kakoune.org> | 2024-10-22 21:18:23 +1100 |
| commit | ca7bc55cf5134b11451ca6b88d9fa0205bbaac66 (patch) | |
| tree | c03386754bb30876a09bd322a8d4433d02bbb1c7 /src/commands.cc | |
| parent | 63ccc61c1db8e7dd06bfff0641088b6656ff62c3 (diff) | |
Run shell-script-completions asynchronously
Share most logic with shell-script-candidates. Now that we do not
block we can run the completion script implicitely instead of waiting
for an explicit completion request with <tab>.
Fixes #5245
Diffstat (limited to 'src/commands.cc')
| -rw-r--r-- | src/commands.cc | 145 |
1 files changed, 81 insertions, 64 deletions
diff --git a/src/commands.cc b/src/commands.cc index 78193e57..90f4520d 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -259,47 +259,101 @@ static Completions complete_command_name(const Context& context, CompletionFlags context, prefix.substr(0, cursor_pos)); } -struct ShellScriptCompleter +struct AsyncShellScript { - ShellScriptCompleter(String shell_script, - Completions::Flags flags = Completions::Flags::None) + AsyncShellScript(String shell_script, + Completions::Flags flags = Completions::Flags::None) : m_shell_script{std::move(shell_script)}, m_flags(flags) {} + AsyncShellScript(const AsyncShellScript& other) : m_shell_script{other.m_shell_script}, m_flags(other.m_flags) {} + AsyncShellScript& operator=(const AsyncShellScript& other) { m_shell_script = other.m_shell_script; m_flags = other.m_flags; return *this; } + +protected: + void spawn_script(const Context& context, const ShellContext& shell_context, auto&& handle_line) + { + m_handle_line = handle_line; + 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_stdout(input_handler); }); + } + + void read_stdout(InputHandler& input_handler) + { + char buffer[2048]; + bool closed = false; + int fd = (int)m_running_script->out; + while (fd_readable(fd)) + { + 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_handle_line(c); + + m_stdout_buffer.erase(m_stdout_buffer.begin(), end); + + input_handler.refresh_ifn(); + if (closed) + { + m_running_script.reset(); + m_watcher.reset(); + m_handle_line = {}; + } + } + + String m_shell_script; + Optional<Shell> m_running_script; + Optional<FDWatcher> m_watcher; + Vector<char, MemoryDomain::Completion> m_stdout_buffer; + std::function<void (StringView)> m_handle_line; + Completions::Flags m_flags; +}; + +struct ShellScriptCompleter : AsyncShellScript +{ + using AsyncShellScript::AsyncShellScript; + Completions operator()(const Context& context, CompletionFlags flags, CommandParameters params, size_t token_to_complete, ByteCount pos_in_token) { - if (flags & CompletionFlags::Fast) // no shell on fast completion - return Completions{}; - - ShellContext shell_context{ - params, - { { "token_to_complete", to_string(token_to_complete) }, - { "pos_in_token", to_string(pos_in_token) } } - }; - String output = ShellManager::instance().eval(m_shell_script, context, StringView{}, - ShellManager::Flags::WaitForStdout, - shell_context).first; CandidateList candidates; - for (auto&& candidate : output | split<StringView>('\n') - | filter([](auto s) { return not s.empty(); })) - candidates.push_back(candidate.str()); + if (m_last_token != token_to_complete or pos_in_token != m_last_pos_in_token) + { + ShellContext shell_context{ + params, + { { "token_to_complete", to_string(token_to_complete) }, + { "pos_in_token", to_string(pos_in_token) } } + }; + spawn_script(context, shell_context, [this](StringView line) { m_candidates.push_back(line.str()); }); + + candidates = std::move(m_candidates); // avoid completion menu flicker by keeping the previous result visible + m_candidates.clear(); + m_last_token = token_to_complete; + m_last_pos_in_token = pos_in_token; + } + else + candidates = m_candidates; return {0_byte, pos_in_token, std::move(candidates), m_flags}; } + private: - String m_shell_script; - Completions::Flags m_flags; + CandidateList m_candidates; + int m_last_token = -1; + ByteCount m_last_pos_in_token = -1; }; -struct ShellCandidatesCompleter +struct ShellCandidatesCompleter : AsyncShellScript { - ShellCandidatesCompleter(String shell_script, - 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; } + using AsyncShellScript::AsyncShellScript; Completions operator()(const Context& context, CompletionFlags flags, CommandParameters params, size_t token_to_complete, @@ -311,9 +365,7 @@ struct ShellCandidatesCompleter params, { { "token_to_complete", to_string(token_to_complete) } } }; - 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); }); + spawn_script(context, shell_context, [this](StringView line) { m_candidates.emplace_back(line.str(), used_letters(line)); }); m_candidates.clear(); m_last_token = token_to_complete; } @@ -321,36 +373,6 @@ struct ShellCandidatesCompleter } private: - void read_candidates(InputHandler& input_handler) - { - char buffer[2048]; - bool closed = false; - int fd = (int)m_running_script->out; - while (fd_readable(fd)) - { - 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); @@ -377,13 +399,8 @@ private: return Completions{0_byte, query.length(), std::move(res), m_flags}; } - 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_last_token = -1; - Completions::Flags m_flags; }; template<typename Completer> |
