ソースを参照

Shell: Initial support for 'option' completions

Take one small step towards #2357.
Handle completing barewords starting with '-' by piping the requests to
the Shell::complete_option(program_name, option) :^)

Also implements completion for a single builtin (setopt) until we figure out how
to handle #2357.
AnotherTest 5 年 前
コミット
ff857cd358
5 ファイル変更162 行追加31 行削除
  1. 85 20
      Shell/AST.cpp
  2. 20 11
      Shell/AST.h
  3. 13 0
      Shell/Builtin.cpp
  4. 42 0
      Shell/Shell.cpp
  5. 2 0
      Shell/Shell.h

+ 85 - 20
Shell/AST.cpp

@@ -89,8 +89,9 @@ Node::Node(Position position)
 {
 }
 
-Vector<Line::CompletionSuggestion> Node::complete_for_editor(Shell& shell, size_t offset, RefPtr<Node> matching_node)
+Vector<Line::CompletionSuggestion> 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<Line::CompletionSuggestion> 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<BarewordLiteral*>(program_name_node.ptr())->text();
+            else
+                program_name = static_cast<StringLiteral*>(program_name_node.ptr())->text();
+
+            return shell.complete_option(program_name, text, corrected_offset);
         }
         return {};
     }
@@ -112,12 +135,12 @@ Vector<Line::CompletionSuggestion> 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<Line::CompletionSuggestion> 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<Node> left, RefPtr<Node> right)
@@ -230,6 +259,11 @@ HitTestResult ListConcatenate::hit_test_position(size_t offset)
     return result;
 }
 
+RefPtr<Node> ListConcatenate::leftmost_trivial_literal() const
+{
+    return m_element->leftmost_trivial_literal();
+}
+
 ListConcatenate::ListConcatenate(Position position, RefPtr<Node> element, RefPtr<Node> list)
     : Node(move(position))
     , m_element(move(element))
@@ -370,8 +404,9 @@ HitTestResult CastToCommand::hit_test_position(size_t offset)
     return result;
 }
 
-Vector<Line::CompletionSuggestion> CastToCommand::complete_for_editor(Shell& shell, size_t offset, RefPtr<Node> matching_node)
+Vector<Line::CompletionSuggestion> 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<Line::CompletionSuggestion> CastToCommand::complete_for_editor(Shell& she
     return shell.complete_program_name(node->text(), corrected_offset);
 }
 
+RefPtr<Node> CastToCommand::leftmost_trivial_literal() const
+{
+    return m_inner->leftmost_trivial_literal();
+}
+
 CastToCommand::CastToCommand(Position position, RefPtr<Node> 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<Node> CastToList::leftmost_trivial_literal() const
+{
+    return m_inner->leftmost_trivial_literal();
+}
+
 CastToList::CastToList(Position position, RefPtr<Node> 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<Line::CompletionSuggestion> Execute::complete_for_editor(Shell& shell, size_t offset, RefPtr<Node> matching_node)
+Vector<Line::CompletionSuggestion> 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<Node> 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<Node> left, RefPtr<Node> 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<Node> left, RefPtr<Node> right)
@@ -1085,8 +1146,9 @@ HitTestResult PathRedirectionNode::hit_test_position(size_t offset)
     return result;
 }
 
-Vector<Line::CompletionSuggestion> PathRedirectionNode::complete_for_editor(Shell& shell, size_t offset, RefPtr<Node> matching_node)
+Vector<Line::CompletionSuggestion> 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<Line::CompletionSuggestion> SimpleVariable::complete_for_editor(Shell& shell, size_t offset, RefPtr<Node> matching_node)
+Vector<Line::CompletionSuggestion> 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<Line::CompletionSuggestion> SpecialVariable::complete_for_editor(Shell&, size_t, RefPtr<Node>)
+Vector<Line::CompletionSuggestion> 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<Line::CompletionSuggestion> Juxtaposition::complete_for_editor(Shell& shell, size_t offset, RefPtr<Node> matching_node)
+Vector<Line::CompletionSuggestion> 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<Line::CompletionSuggestion> 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<Line::CompletionSuggestion> Tilde::complete_for_editor(Shell& shell, size_t offset, RefPtr<Node> matching_node)
+Vector<Line::CompletionSuggestion> 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<Variable> variables)

+ 20 - 11
Shell/AST.h

@@ -150,6 +150,7 @@ struct Command {
 struct HitTestResult {
     RefPtr<Node> matching_node;
     RefPtr<Node> closest_node_with_semantic_meaning; // This is used if matching_node is a bareword
+    RefPtr<Node> 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<Value> {
@@ -311,13 +312,13 @@ public:
     virtual void dump(int level) const = 0;
     virtual RefPtr<Value> run(RefPtr<Shell>) = 0;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) = 0;
-    virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, RefPtr<Node> matching_node);
+    virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, const HitTestResult&);
     Vector<Line::CompletionSuggestion> 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<Node> 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<Node>);
     virtual ~PathRedirectionNode();
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
-    virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, RefPtr<Node> matching_node) override;
+    virtual Vector<Line::CompletionSuggestion> 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<Node> leftmost_trivial_literal() const override;
 
     RefPtr<Node> m_element;
     RefPtr<Node> 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<Node> leftmost_trivial_literal() const override { return this; }
 
     String m_text;
 };
@@ -442,10 +447,11 @@ private:
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
-    virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, RefPtr<Node> matching_node) override;
+    virtual Vector<Line::CompletionSuggestion> 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<Node> leftmost_trivial_literal() const override;
 
     RefPtr<Node> 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<Node> leftmost_trivial_literal() const override;
 
     RefPtr<Node> m_inner;
 };
@@ -596,7 +603,7 @@ private:
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
-    virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, RefPtr<Node> matching_node) override;
+    virtual Vector<Line::CompletionSuggestion> 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<Node> leftmost_trivial_literal() const override;
 
     RefPtr<Node> m_left;
     RefPtr<Node> m_right;
@@ -705,7 +713,7 @@ private:
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
-    virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, RefPtr<Node> matching_node) override;
+    virtual Vector<Line::CompletionSuggestion> 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<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
-    virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, RefPtr<Node> matching_node) override;
+    virtual Vector<Line::CompletionSuggestion> 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<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual HitTestResult hit_test_position(size_t) override;
-    virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, RefPtr<Node> matching_node) override;
+    virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, const HitTestResult&) override;
     virtual String class_name() const override { return "Juxtaposition"; }
 
     RefPtr<Node> m_left;
@@ -756,6 +764,7 @@ private:
     virtual RefPtr<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
     virtual String class_name() const override { return "StringLiteral"; }
+    virtual RefPtr<Node> leftmost_trivial_literal() const override { return this; };
 
     String m_text;
 };
@@ -787,7 +796,7 @@ private:
     virtual void dump(int level) const override;
     virtual RefPtr<Value> run(RefPtr<Shell>) 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<Value> run(RefPtr<Shell>) override;
     virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
-    virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, RefPtr<Node> matching_node) override;
+    virtual Vector<Line::CompletionSuggestion> 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; }

+ 13 - 0
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;
+}

+ 42 - 0
Shell/Shell.cpp

@@ -828,6 +828,48 @@ Vector<Line::CompletionSuggestion> Shell::complete_user(const String& name, size
     return suggestions;
 }
 
+Vector<Line::CompletionSuggestion> 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<Line::CompletionSuggestion> 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();

+ 2 - 0
Shell/Shell.h

@@ -75,6 +75,7 @@ public:
     RefPtr<Job> 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<Job>);
     String prompt() const;
 
@@ -101,6 +102,7 @@ public:
     Vector<Line::CompletionSuggestion> complete_program_name(const String&, size_t offset);
     Vector<Line::CompletionSuggestion> complete_variable(const String&, size_t offset);
     Vector<Line::CompletionSuggestion> complete_user(const String&, size_t offset);
+    Vector<Line::CompletionSuggestion> complete_option(const String&, const String&, size_t offset);
 
     void take_back_stdin();