summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAli Mohammad Pur <ali.mpfard@gmail.com>2022-04-12 02:27:16 +0430
committerAli Mohammad Pur <Ali.mpfard@gmail.com>2022-04-18 19:53:10 +0430
commit4ea9ca06b4fdd710a72bc244f2f0d23c2d52e8de (patch)
treee0bb46d31a3976543587eeb6c8f4343bea357b52
parent4ede121d31f43a787b32fc4c137582638ea305e1 (diff)
downloadserenity-4ea9ca06b4fdd710a72bc244f2f0d23c2d52e8de.zip
Shell: Allow completions to request further action from the shell
The shell now expects a JSON object of the form {"kind":<kind>,...} per line in the standard output of the completion process, where 'kind' is one of: - "plain": Just a plain suggestion. - "program": Prompts the shell to complete a program name starting with the given "name". - "proxy": Prompts the shell to act as if a completion for "argv" was requested. - "path": Prompts the shell to complete a path given the "base" and "part" (same as completing part in cwd=base).
-rw-r--r--Userland/Shell/Shell.cpp74
-rw-r--r--Userland/Shell/Shell.h4
2 files changed, 55 insertions, 23 deletions
diff --git a/Userland/Shell/Shell.cpp b/Userland/Shell/Shell.cpp
index 33f7983011..fdcaa68e9b 100644
--- a/Userland/Shell/Shell.cpp
+++ b/Userland/Shell/Shell.cpp
@@ -1409,8 +1409,12 @@ void Shell::highlight(Line::Editor& editor) const
Vector<Line::CompletionSuggestion> Shell::complete()
{
- auto line = m_editor->line(m_editor->cursor());
+ m_completion_stack_info = {};
+ return complete(m_editor->line(m_editor->cursor()));
+}
+Vector<Line::CompletionSuggestion> Shell::complete(StringView line)
+{
Parser parser(line, m_is_interactive);
auto ast = parser.parse();
@@ -1850,29 +1854,53 @@ ErrorOr<Vector<Line::CompletionSuggestion>> Shell::complete_via_program_itself(s
{ "/", "rx" },
},
});
- execute_node->for_each_entry(*this, [&](NonnullRefPtr<AST::Value> entry) -> IterationDecision {
- auto result = entry->resolve_as_string(*this);
- JsonParser parser(result);
- auto parsed_result = parser.parse();
- if (parsed_result.is_error())
- return IterationDecision::Continue;
- auto parsed = parsed_result.release_value();
- if (parsed.is_object()) {
- auto& object = parsed.as_object();
- Line::CompletionSuggestion suggestion {
- object.get("completion").as_string_or(""),
- object.get("trailing_trivia").as_string_or(""),
- object.get("display_trivia").as_string_or(""),
- };
- suggestion.static_offset = object.get("static_offset").to_u64(0);
- suggestion.invariant_offset = object.get("invariant_offset").to_u64(0);
- suggestions.append(move(suggestion));
- } else {
- suggestions.append(parsed.to_string());
- }
+ {
+ TemporaryChange change(m_is_interactive, false);
+ execute_node->for_each_entry(*this, [&](NonnullRefPtr<AST::Value> entry) -> IterationDecision {
+ auto result = entry->resolve_as_string(*this);
+ JsonParser parser(result);
+ auto parsed_result = parser.parse();
+ if (parsed_result.is_error())
+ return IterationDecision::Continue;
+ auto parsed = parsed_result.release_value();
+ if (parsed.is_object()) {
+ auto& object = parsed.as_object();
+ auto kind = object.get("kind").as_string_or("plain");
+ if (kind == "path") {
+ auto base = object.get("base").as_string_or("");
+ auto part = object.get("part").as_string_or("");
+ auto executable_only = object.get("executable_only").to_bool(false) ? ExecutableOnly::Yes : ExecutableOnly::No;
+ suggestions.extend(complete_path(base, part, part.length(), executable_only, nullptr, nullptr));
+ } else if (kind == "program") {
+ auto name = object.get("name").as_string_or("");
+ suggestions.extend(complete_program_name(name, name.length()));
+ } else if (kind == "proxy") {
+ if (m_completion_stack_info.size_free() < 4 * KiB) {
+ dbgln("Not enough stack space, recursion?");
+ return IterationDecision::Continue;
+ }
+ auto argv = object.get("argv").as_string_or("");
+ dbgln("Proxy completion for {}", argv);
+ suggestions.extend(complete(argv));
+ } else if (kind == "plain") {
+ Line::CompletionSuggestion suggestion {
+ object.get("completion").as_string_or(""),
+ object.get("trailing_trivia").as_string_or(""),
+ object.get("display_trivia").as_string_or(""),
+ };
+ suggestion.static_offset = object.get("static_offset").to_u64(0);
+ suggestion.invariant_offset = object.get("invariant_offset").to_u64(0);
+ suggestions.append(move(suggestion));
+ } else {
+ dbgln("LibLine: Unhandled completion kind: {}", kind);
+ }
+ } else {
+ suggestions.append(parsed.to_string());
+ }
- return IterationDecision::Continue;
- });
+ return IterationDecision::Continue;
+ });
+ }
auto pgid = getpgrp();
tcsetpgrp(STDOUT_FILENO, pgid);
diff --git a/Userland/Shell/Shell.h b/Userland/Shell/Shell.h
index 322043c400..e26ed5ae9e 100644
--- a/Userland/Shell/Shell.h
+++ b/Userland/Shell/Shell.h
@@ -12,6 +12,7 @@
#include <AK/CircularQueue.h>
#include <AK/HashMap.h>
#include <AK/NonnullOwnPtrVector.h>
+#include <AK/StackInfo.h>
#include <AK/String.h>
#include <AK/StringBuilder.h>
#include <AK/StringView.h>
@@ -223,6 +224,7 @@ public:
void highlight(Line::Editor&) const;
Vector<Line::CompletionSuggestion> complete();
+ Vector<Line::CompletionSuggestion> complete(StringView);
Vector<Line::CompletionSuggestion> complete_program_name(StringView, size_t offset, EscapeMode = EscapeMode::Bareword);
Vector<Line::CompletionSuggestion> complete_variable(StringView, size_t offset);
Vector<Line::CompletionSuggestion> complete_user(StringView, size_t offset);
@@ -419,6 +421,8 @@ private:
mutable bool m_last_continuation_state { false }; // false == not needed.
Optional<size_t> m_history_autosave_time;
+
+ StackInfo m_completion_stack_info;
};
[[maybe_unused]] static constexpr bool is_word_character(char c)