diff options
author | Ali Mohammad Pur <ali.mpfard@gmail.com> | 2022-04-12 02:27:16 +0430 |
---|---|---|
committer | Ali Mohammad Pur <Ali.mpfard@gmail.com> | 2022-04-18 19:53:10 +0430 |
commit | 4ea9ca06b4fdd710a72bc244f2f0d23c2d52e8de (patch) | |
tree | e0bb46d31a3976543587eeb6c8f4343bea357b52 | |
parent | 4ede121d31f43a787b32fc4c137582638ea305e1 (diff) | |
download | serenity-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.cpp | 74 | ||||
-rw-r--r-- | Userland/Shell/Shell.h | 4 |
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) |