浏览代码

Shell: Correctly complete paths in redirections

This commit allows the Shell to complete paths in redirections.
A closing quote is added if the path is an unclosed quote.
```
$ foo > "foob<tab>
$ foo > "foobar"
```
AnotherTest 5 年之前
父节点
当前提交
77039e5354
共有 3 个文件被更改,包括 46 次插入25 次删除
  1. 6 3
      Shell/Parser.cpp
  2. 1 1
      Shell/Parser.h
  3. 39 21
      Shell/Shell.cpp

+ 6 - 3
Shell/Parser.cpp

@@ -33,12 +33,13 @@ void Parser::commit_token(Token::Type type, AllowEmptyToken allow_empty)
 {
     if (allow_empty == AllowEmptyToken::No && m_token.is_empty())
         return;
+    Token token { String::copy(m_token), m_position, m_token.size(), type };
     if (state() == InRedirectionPath) {
-        m_redirections.last().path = String::copy(m_token);
+        m_redirections.last().path = move(token);
         m_token.clear_with_capacity();
         return;
     }
-    m_tokens.append({ String::copy(m_token), m_position, m_token.size(), type });
+    m_tokens.append(token);
     m_token.clear_with_capacity();
 };
 
@@ -333,13 +334,15 @@ Vector<Command> Parser::parse()
 
     while (m_state_stack.size() > 1) {
         if (state() == State::InDoubleQuotes) {
+            pop_state();
             commit_token(Token::UnterminatedDoubleQuoted, AllowEmptyToken::Yes);
         } else if (state() == State::InSingleQuotes) {
+            pop_state();
             commit_token(Token::UnterminatedSingleQuoted, AllowEmptyToken::Yes);
         } else {
             commit_token(Token::Bare, AllowEmptyToken::No);
+            pop_state();
         }
-        pop_state();
     }
     ASSERT(state() == State::Free);
 

+ 1 - 1
Shell/Parser.h

@@ -61,7 +61,7 @@ struct Redirection {
     Type type;
     int fd { -1 };
     int rewire_fd { -1 };
-    String path {};
+    Token path {};
 };
 
 struct Rewiring {

+ 39 - 21
Shell/Shell.cpp

@@ -1140,13 +1140,13 @@ ExitCodeOrContinuationRequest Shell::run_command(const StringView& cmd)
                     dbgprintf("Pipe\n");
                     break;
                 case Redirection::FileRead:
-                    dbgprintf("fd:%d = FileRead: %s\n", redirecton.fd, redirecton.path.characters());
+                    dbgprintf("fd:%d = FileRead: %s\n", redirecton.fd, redirecton.path.text.characters());
                     break;
                 case Redirection::FileWrite:
-                    dbgprintf("fd:%d = FileWrite: %s\n", redirecton.fd, redirecton.path.characters());
+                    dbgprintf("fd:%d = FileWrite: %s\n", redirecton.fd, redirecton.path.text.characters());
                     break;
                 case Redirection::FileWriteAppend:
-                    dbgprintf("fd:%d = FileWriteAppend: %s\n", redirecton.fd, redirecton.path.characters());
+                    dbgprintf("fd:%d = FileWriteAppend: %s\n", redirecton.fd, redirecton.path.text.characters());
                     break;
                 default:
                     break;
@@ -1206,7 +1206,7 @@ ExitCodeOrContinuationRequest Shell::run_command(const StringView& cmd)
                     break;
                 }
                 case Redirection::FileWriteAppend: {
-                    int fd = open(redirection.path.characters(), O_WRONLY | O_CREAT | O_APPEND, 0666);
+                    int fd = open(redirection.path.text.characters(), O_WRONLY | O_CREAT | O_APPEND, 0666);
                     if (fd < 0) {
                         perror("open");
                         return 1;
@@ -1216,7 +1216,7 @@ ExitCodeOrContinuationRequest Shell::run_command(const StringView& cmd)
                     break;
                 }
                 case Redirection::FileWrite: {
-                    int fd = open(redirection.path.characters(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
+                    int fd = open(redirection.path.text.characters(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
                     if (fd < 0) {
                         perror("open");
                         return 1;
@@ -1226,7 +1226,7 @@ ExitCodeOrContinuationRequest Shell::run_command(const StringView& cmd)
                     break;
                 }
                 case Redirection::FileRead: {
-                    int fd = open(redirection.path.characters(), O_RDONLY);
+                    int fd = open(redirection.path.text.characters(), O_RDONLY);
                     if (fd < 0) {
                         perror("open");
                         return 1;
@@ -1580,10 +1580,12 @@ Vector<Line::CompletionSuggestion> Shell::complete(const Line::Editor& editor)
     if (commands.size() == 0)
         return {};
 
-    // get the last token and whether it's the first in its subcommand
+    // Get the last token and whether it's the first in its subcommand.
     String token;
     bool is_first_in_subcommand = false;
     auto& subcommand = commands.last().subcommands;
+    String file_token_trail = " ";
+    String directory_token_trail = "/";
 
     if (subcommand.size() == 0) {
         // foo bar; <tab>
@@ -1591,22 +1593,38 @@ Vector<Line::CompletionSuggestion> Shell::complete(const Line::Editor& editor)
         is_first_in_subcommand = true;
     } else {
         auto& last_command = subcommand.last();
-        if (last_command.args.size() == 0) {
-            // foo bar | <tab>
-            token = "";
-            is_first_in_subcommand = true;
-        } else {
-            auto& args = last_command.args;
-            if (args.last().type == Token::Comment) // we cannot complete comments
+        if (!last_command.redirections.is_empty() && last_command.redirections.last().type != Redirection::Pipe) {
+            // foo > bar<tab>
+            const auto& redirection = last_command.redirections.last();
+            const auto& path = redirection.path;
+
+            if (path.end != line.length())
                 return {};
 
-            if (args.last().end != line.length()) {
-                // There was a token separator at the end
-                is_first_in_subcommand = false;
+            token = path.text;
+            is_first_in_subcommand = false;
+            if (path.type == Token::UnterminatedDoubleQuoted)
+                file_token_trail = "\"";
+            else if (path.type == Token::UnterminatedSingleQuoted)
+                file_token_trail = "'";
+        } else {
+            if (last_command.args.size() == 0) {
+                // foo bar | <tab>
                 token = "";
+                is_first_in_subcommand = true;
             } else {
-                is_first_in_subcommand = args.size() == 1;
-                token = last_command.args.last().text;
+                auto& args = last_command.args;
+                if (args.last().type == Token::Comment) // we cannot complete comments
+                    return {};
+
+                if (args.last().end != line.length()) {
+                    // There was a token separator at the end
+                    is_first_in_subcommand = false;
+                    token = "";
+                } else {
+                    is_first_in_subcommand = args.size() == 1;
+                    token = last_command.args.last().text;
+                }
             }
         }
     }
@@ -1685,9 +1703,9 @@ Vector<Line::CompletionSuggestion> Shell::complete(const Line::Editor& editor)
             if (!stat_error) {
                 if (S_ISDIR(program_status.st_mode)) {
                     if (!should_suggest_only_executables)
-                        suggestions.append({ escape_token(file), "/", { Line::Style::Hyperlink(String::format("file://%s", file_path.characters())), Line::Style::Anchored } });
+                        suggestions.append({ escape_token(file), directory_token_trail, { Line::Style::Hyperlink(String::format("file://%s", file_path.characters())), Line::Style::Anchored } });
                 } else {
-                    suggestions.append({ escape_token(file), " ", { Line::Style::Hyperlink(String::format("file://%s", file_path.characters())), Line::Style::Anchored } });
+                    suggestions.append({ escape_token(file), file_token_trail, { Line::Style::Hyperlink(String::format("file://%s", file_path.characters())), Line::Style::Anchored } });
                 }
             }
         }