From 77039e5354e154d7f5333a4cbb22d93e842642b0 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Fri, 29 May 2020 18:58:35 +0430 Subject: [PATCH] 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 $ foo > "foobar" ``` --- Shell/Parser.cpp | 9 +++++--- Shell/Parser.h | 2 +- Shell/Shell.cpp | 60 +++++++++++++++++++++++++++++++----------------- 3 files changed, 46 insertions(+), 25 deletions(-) diff --git a/Shell/Parser.cpp b/Shell/Parser.cpp index 97d3faba81..fad6d5e9a5 100644 --- a/Shell/Parser.cpp +++ b/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 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); diff --git a/Shell/Parser.h b/Shell/Parser.h index b0b7792170..e9fcb5f9f4 100644 --- a/Shell/Parser.h +++ b/Shell/Parser.h @@ -61,7 +61,7 @@ struct Redirection { Type type; int fd { -1 }; int rewire_fd { -1 }; - String path {}; + Token path {}; }; struct Rewiring { diff --git a/Shell/Shell.cpp b/Shell/Shell.cpp index 27f551a855..e0098c8fe1 100644 --- a/Shell/Shell.cpp +++ b/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 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; @@ -1591,22 +1593,38 @@ Vector 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 | - 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 + 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 | 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 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 } }); } } }