diff --git a/Shell/AST.cpp b/Shell/AST.cpp index 10766edfae..96c74af619 100644 --- a/Shell/AST.cpp +++ b/Shell/AST.cpp @@ -89,8 +89,9 @@ Node::Node(Position position) { } -Vector Node::complete_for_editor(Shell& shell, size_t offset, RefPtr matching_node) +Vector Node::complete_for_editor(Shell& shell, size_t offset, const HitTestResult& hit_test_result) { + auto matching_node = hit_test_result.matching_node; if (matching_node) { if (matching_node->is_bareword()) { auto corrected_offset = offset - matching_node->position().start_offset; @@ -98,7 +99,29 @@ Vector Node::complete_for_editor(Shell& shell, size_ if (corrected_offset > node->text().length()) return {}; - return shell.complete_path("", node->text(), corrected_offset); + auto& text = node->text(); + + // If the literal isn't an option, treat it as a path. + if (!(text.starts_with("-") || text == "--" || text == "-")) + return shell.complete_path("", text, corrected_offset); + + // If the literal is an option, we have to know the program name + // should we have no way to get that, bail early. + + if (!hit_test_result.closest_command_node) + return {}; + + auto program_name_node = hit_test_result.closest_command_node->leftmost_trivial_literal(); + if (!program_name_node) + return {}; + + String program_name; + if (program_name_node->is_bareword()) + program_name = static_cast(program_name_node.ptr())->text(); + else + program_name = static_cast(program_name_node.ptr())->text(); + + return shell.complete_option(program_name, text, corrected_offset); } return {}; } @@ -112,12 +135,12 @@ Vector Node::complete_for_editor(Shell& shell, size_ if (!node) return {}; - return node->complete_for_editor(shell, offset, result.matching_node); + return node->complete_for_editor(shell, offset, result); } Vector Node::complete_for_editor(Shell& shell, size_t offset) { - return Node::complete_for_editor(shell, offset, nullptr); + return Node::complete_for_editor(shell, offset, { nullptr, nullptr, nullptr }); } Node::~Node() @@ -164,9 +187,15 @@ HitTestResult And::hit_test_position(size_t offset) return {}; auto result = m_left->hit_test_position(offset); - if (result.matching_node) + if (result.matching_node) { + if (!result.closest_command_node) + result.closest_command_node = m_right; return result; - return m_right->hit_test_position(offset); + } + result = m_right->hit_test_position(offset); + if (!result.closest_command_node) + result.closest_command_node = m_right; + return result; } And::And(Position position, RefPtr left, RefPtr right) @@ -230,6 +259,11 @@ HitTestResult ListConcatenate::hit_test_position(size_t offset) return result; } +RefPtr ListConcatenate::leftmost_trivial_literal() const +{ + return m_element->leftmost_trivial_literal(); +} + ListConcatenate::ListConcatenate(Position position, RefPtr element, RefPtr list) : Node(move(position)) , m_element(move(element)) @@ -370,8 +404,9 @@ HitTestResult CastToCommand::hit_test_position(size_t offset) return result; } -Vector CastToCommand::complete_for_editor(Shell& shell, size_t offset, RefPtr matching_node) +Vector CastToCommand::complete_for_editor(Shell& shell, size_t offset, const HitTestResult& hit_test_result) { + auto matching_node = hit_test_result.matching_node; if (!matching_node) return {}; @@ -385,6 +420,11 @@ Vector CastToCommand::complete_for_editor(Shell& she return shell.complete_program_name(node->text(), corrected_offset); } +RefPtr CastToCommand::leftmost_trivial_literal() const +{ + return m_inner->leftmost_trivial_literal(); +} + CastToCommand::CastToCommand(Position position, RefPtr inner) : Node(move(position)) , m_inner(move(inner)) @@ -441,6 +481,11 @@ HitTestResult CastToList::hit_test_position(size_t offset) return m_inner->hit_test_position(offset); } +RefPtr CastToList::leftmost_trivial_literal() const +{ + return m_inner->leftmost_trivial_literal(); +} + CastToList::CastToList(Position position, RefPtr inner) : Node(move(position)) , m_inner(move(inner)) @@ -845,11 +890,14 @@ HitTestResult Execute::hit_test_position(size_t offset) auto result = m_command->hit_test_position(offset); if (!result.closest_node_with_semantic_meaning) result.closest_node_with_semantic_meaning = this; + if (!result.closest_command_node) + result.closest_command_node = m_command; return result; } -Vector Execute::complete_for_editor(Shell& shell, size_t offset, RefPtr matching_node) +Vector Execute::complete_for_editor(Shell& shell, size_t offset, const HitTestResult& hit_test_result) { + auto matching_node = hit_test_result.matching_node; if (!matching_node) return {}; @@ -910,6 +958,13 @@ HitTestResult Join::hit_test_position(size_t offset) return m_right->hit_test_position(offset); } +RefPtr Join::leftmost_trivial_literal() const +{ + if (auto value = m_left->leftmost_trivial_literal()) + return value; + return m_right->leftmost_trivial_literal(); +} + Join::Join(Position position, RefPtr left, RefPtr right) : Node(move(position)) , m_left(move(left)) @@ -965,9 +1020,15 @@ HitTestResult Or::hit_test_position(size_t offset) return {}; auto result = m_left->hit_test_position(offset); - if (result.matching_node) + if (result.matching_node) { + if (!result.closest_command_node) + result.closest_command_node = m_right; return result; - return m_right->hit_test_position(offset); + } + result = m_right->hit_test_position(offset); + if (!result.closest_command_node) + result.closest_command_node = m_right; + return result; } Or::Or(Position position, RefPtr left, RefPtr right) @@ -1085,8 +1146,9 @@ HitTestResult PathRedirectionNode::hit_test_position(size_t offset) return result; } -Vector PathRedirectionNode::complete_for_editor(Shell& shell, size_t offset, RefPtr matching_node) +Vector PathRedirectionNode::complete_for_editor(Shell& shell, size_t offset, const HitTestResult& hit_test_result) { + auto matching_node = hit_test_result.matching_node; if (!matching_node) return {}; @@ -1233,11 +1295,12 @@ HitTestResult SimpleVariable::hit_test_position(size_t offset) if (!position().contains(offset)) return {}; - return { this, this }; + return { this, this, nullptr }; } -Vector SimpleVariable::complete_for_editor(Shell& shell, size_t offset, RefPtr matching_node) +Vector SimpleVariable::complete_for_editor(Shell& shell, size_t offset, const HitTestResult& hit_test_result) { + auto matching_node = hit_test_result.matching_node; if (!matching_node) return {}; @@ -1278,7 +1341,7 @@ void SpecialVariable::highlight_in_editor(Line::Editor& editor, Shell&, Highligh editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(214, 112, 214) }); } -Vector SpecialVariable::complete_for_editor(Shell&, size_t, RefPtr) +Vector SpecialVariable::complete_for_editor(Shell&, size_t, const HitTestResult&) { return {}; } @@ -1288,7 +1351,7 @@ HitTestResult SpecialVariable::hit_test_position(size_t offset) if (!position().contains(offset)) return {}; - return { this, this }; + return { this, this, nullptr }; } SpecialVariable::SpecialVariable(Position position, char name) @@ -1377,8 +1440,9 @@ void Juxtaposition::highlight_in_editor(Line::Editor& editor, Shell& shell, High } } -Vector Juxtaposition::complete_for_editor(Shell& shell, size_t offset, RefPtr matching_node) +Vector Juxtaposition::complete_for_editor(Shell& shell, size_t offset, const HitTestResult& hit_test_result) { + auto matching_node = hit_test_result.matching_node; // '~/foo/bar' is special, we have to actually resolve the tilde // then complete the bareword with that path prefix. if (m_right->is_bareword() && m_left->is_tilde()) { @@ -1395,7 +1459,7 @@ Vector Juxtaposition::complete_for_editor(Shell& she return shell.complete_path(tilde_value, text, corrected_offset - 1); } - return Node::complete_for_editor(shell, offset, matching_node); + return Node::complete_for_editor(shell, offset, hit_test_result); } HitTestResult Juxtaposition::hit_test_position(size_t offset) @@ -1562,11 +1626,12 @@ HitTestResult Tilde::hit_test_position(size_t offset) if (!position().contains(offset)) return {}; - return { this, this }; + return { this, this, nullptr }; } -Vector Tilde::complete_for_editor(Shell& shell, size_t offset, RefPtr matching_node) +Vector Tilde::complete_for_editor(Shell& shell, size_t offset, const HitTestResult& hit_test_result) { + auto matching_node = hit_test_result.matching_node; if (!matching_node) return {}; @@ -1706,7 +1771,7 @@ HitTestResult VariableDeclarations::hit_test_position(size_t offset) return result; } - return { nullptr, nullptr }; + return { nullptr, nullptr, nullptr }; } VariableDeclarations::VariableDeclarations(Position position, Vector variables) diff --git a/Shell/AST.h b/Shell/AST.h index 1c01200007..c5f193bf53 100644 --- a/Shell/AST.h +++ b/Shell/AST.h @@ -150,6 +150,7 @@ struct Command { struct HitTestResult { RefPtr matching_node; RefPtr closest_node_with_semantic_meaning; // This is used if matching_node is a bareword + RefPtr closest_command_node; // This is used if matching_node is a bareword, and it is not the first in a list }; class Value : public RefCounted { @@ -311,13 +312,13 @@ public: virtual void dump(int level) const = 0; virtual RefPtr run(RefPtr) = 0; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) = 0; - virtual Vector complete_for_editor(Shell&, size_t, RefPtr matching_node); + virtual Vector complete_for_editor(Shell&, size_t, const HitTestResult&); Vector complete_for_editor(Shell& shell, size_t offset); virtual HitTestResult hit_test_position(size_t offset) { if (m_position.contains(offset)) - return { this, nullptr }; - return { nullptr, nullptr }; + return { this, nullptr, nullptr }; + return { nullptr, nullptr, nullptr }; } virtual String class_name() const { return "Node"; } Node(Position); @@ -346,6 +347,8 @@ public: return *m_syntax_error_node; } + virtual RefPtr leftmost_trivial_literal() const { return nullptr; } + protected: Position m_position; bool m_is_syntax_error { false }; @@ -357,7 +360,7 @@ public: PathRedirectionNode(Position, int, RefPtr); virtual ~PathRedirectionNode(); virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual Vector complete_for_editor(Shell&, size_t, RefPtr matching_node) override; + virtual Vector complete_for_editor(Shell&, size_t, const HitTestResult&) override; virtual HitTestResult hit_test_position(size_t offset) override; virtual bool is_command() const override { return true; } virtual bool is_list() const override { return true; } @@ -396,6 +399,7 @@ private: virtual HitTestResult hit_test_position(size_t) override; virtual String class_name() const override { return "ListConcatenate"; } virtual bool is_list() const override { return true; } + virtual RefPtr leftmost_trivial_literal() const override; RefPtr m_element; RefPtr m_list; @@ -428,6 +432,7 @@ private: virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual String class_name() const override { return "BarewordLiteral"; } virtual bool is_bareword() const override { return true; } + virtual RefPtr leftmost_trivial_literal() const override { return this; } String m_text; }; @@ -442,10 +447,11 @@ private: virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual HitTestResult hit_test_position(size_t) override; - virtual Vector complete_for_editor(Shell&, size_t, RefPtr matching_node) override; + virtual Vector complete_for_editor(Shell&, size_t, const HitTestResult&) override; virtual String class_name() const override { return "CastToCommand"; } virtual bool is_command() const override { return true; } virtual bool is_list() const override { return true; } + virtual RefPtr leftmost_trivial_literal() const override; RefPtr m_inner; }; @@ -462,6 +468,7 @@ private: virtual HitTestResult hit_test_position(size_t) override; virtual String class_name() const override { return "CastToList"; } virtual bool is_list() const override { return true; } + virtual RefPtr leftmost_trivial_literal() const override; RefPtr m_inner; }; @@ -596,7 +603,7 @@ private: virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual HitTestResult hit_test_position(size_t) override; - virtual Vector complete_for_editor(Shell&, size_t, RefPtr matching_node) override; + virtual Vector complete_for_editor(Shell&, size_t, const HitTestResult&) override; virtual String class_name() const override { return "Execute"; } virtual bool is_execute() const override { return true; } virtual bool would_execute() const override { return true; } @@ -618,6 +625,7 @@ private: virtual String class_name() const override { return "Join"; } virtual bool is_command() const override { return true; } virtual bool is_list() const override { return true; } + virtual RefPtr leftmost_trivial_literal() const override; RefPtr m_left; RefPtr m_right; @@ -705,7 +713,7 @@ private: virtual void dump(int level) const override; virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual Vector complete_for_editor(Shell&, size_t, RefPtr matching_node) override; + virtual Vector complete_for_editor(Shell&, size_t, const HitTestResult&) override; virtual HitTestResult hit_test_position(size_t) override; virtual String class_name() const override { return "SimpleVariable"; } @@ -721,7 +729,7 @@ private: virtual void dump(int level) const override; virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual Vector complete_for_editor(Shell&, size_t, RefPtr matching_node) override; + virtual Vector complete_for_editor(Shell&, size_t, const HitTestResult&) override; virtual HitTestResult hit_test_position(size_t) override; virtual String class_name() const override { return "SpecialVariable"; } @@ -738,7 +746,7 @@ private: virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual HitTestResult hit_test_position(size_t) override; - virtual Vector complete_for_editor(Shell&, size_t, RefPtr matching_node) override; + virtual Vector complete_for_editor(Shell&, size_t, const HitTestResult&) override; virtual String class_name() const override { return "Juxtaposition"; } RefPtr m_left; @@ -756,6 +764,7 @@ private: virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual String class_name() const override { return "StringLiteral"; } + virtual RefPtr leftmost_trivial_literal() const override { return this; }; String m_text; }; @@ -787,7 +796,7 @@ private: virtual void dump(int level) const override; virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override { return { nullptr, nullptr }; } + virtual HitTestResult hit_test_position(size_t) override { return { nullptr, nullptr, nullptr }; } virtual String class_name() const override { return "SyntaxError"; } virtual bool is_syntax_error() const override { return true; } virtual const SyntaxError& syntax_error_node() const override; @@ -805,7 +814,7 @@ private: virtual void dump(int level) const override; virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual Vector complete_for_editor(Shell&, size_t, RefPtr matching_node) override; + virtual Vector complete_for_editor(Shell&, size_t, const HitTestResult&) override; virtual HitTestResult hit_test_position(size_t) override; virtual String class_name() const override { return "Tilde"; } virtual bool is_tilde() const override { return true; } diff --git a/Shell/Builtin.cpp b/Shell/Builtin.cpp index ed56065190..8072fd3560 100644 --- a/Shell/Builtin.cpp +++ b/Shell/Builtin.cpp @@ -776,3 +776,16 @@ bool Shell::run_builtin(int argc, const char** argv, int& retval) return false; } + +bool Shell::has_builtin(const StringView& name) const +{ +#define __ENUMERATE_SHELL_BUILTIN(builtin) \ + if (name == #builtin) { \ + return true; \ + } + + ENUMERATE_SHELL_BUILTINS(); + +#undef __ENUMERATE_SHELL_BUILTIN + return false; +} diff --git a/Shell/Shell.cpp b/Shell/Shell.cpp index c268e70606..f10c1b9069 100644 --- a/Shell/Shell.cpp +++ b/Shell/Shell.cpp @@ -828,6 +828,48 @@ Vector Shell::complete_user(const String& name, size return suggestions; } +Vector Shell::complete_option(const String& program_name, const String& option, size_t offset) +{ + size_t start = 0; + while (start < option.length() && option[start] == '-') + ++start; + auto option_pattern = offset > start ? option.substring_view(start, offset - start) : ""; + editor->suggest(offset); + + Vector suggestions; + + dbg() << "Shell::complete_option(" << program_name << ", " << option_pattern << ")"; + + // FIXME: Figure out how to do this stuff. + if (has_builtin(program_name)) { + // Complete builtins. + if (program_name == "setopt") { + bool negate = false; + if (option_pattern.starts_with("no_")) { + negate = true; + option_pattern = option_pattern.substring_view(3, option_pattern.length() - 3); + } + auto maybe_negate = [&](const StringView& view) { + static StringBuilder builder; + builder.clear(); + builder.append("--"); + if (negate) + builder.append("no_"); + builder.append(view); + return builder.to_string(); + }; +#define __ENUMERATE_SHELL_OPTION(name, d_, descr_) \ + if (StringView { #name }.starts_with(option_pattern)) \ + suggestions.append(maybe_negate(#name)); + + ENUMERATE_SHELL_OPTIONS(); +#undef __ENUMERATE_SHELL_OPTION + return suggestions; + } + } + return suggestions; +} + bool Shell::read_single_line() { take_back_stdin(); diff --git a/Shell/Shell.h b/Shell/Shell.h index 4b82d6a9f4..5e9afbb968 100644 --- a/Shell/Shell.h +++ b/Shell/Shell.h @@ -75,6 +75,7 @@ public: RefPtr run_command(AST::Command&); bool run_file(const String&); bool run_builtin(int argc, const char** argv, int& retval); + bool has_builtin(const StringView&) const; void block_on_job(RefPtr); String prompt() const; @@ -101,6 +102,7 @@ public: Vector complete_program_name(const String&, size_t offset); Vector complete_variable(const String&, size_t offset); Vector complete_user(const String&, size_t offset); + Vector complete_option(const String&, const String&, size_t offset); void take_back_stdin();