mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 05:38:11 +00:00
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" ```
This commit is contained in:
parent
9a4ee9aa1a
commit
77039e5354
3 changed files with 46 additions and 25 deletions
|
@ -33,12 +33,13 @@ void Parser::commit_token(Token::Type type, AllowEmptyToken allow_empty)
|
||||||
{
|
{
|
||||||
if (allow_empty == AllowEmptyToken::No && m_token.is_empty())
|
if (allow_empty == AllowEmptyToken::No && m_token.is_empty())
|
||||||
return;
|
return;
|
||||||
|
Token token { String::copy(m_token), m_position, m_token.size(), type };
|
||||||
if (state() == InRedirectionPath) {
|
if (state() == InRedirectionPath) {
|
||||||
m_redirections.last().path = String::copy(m_token);
|
m_redirections.last().path = move(token);
|
||||||
m_token.clear_with_capacity();
|
m_token.clear_with_capacity();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_tokens.append({ String::copy(m_token), m_position, m_token.size(), type });
|
m_tokens.append(token);
|
||||||
m_token.clear_with_capacity();
|
m_token.clear_with_capacity();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -333,13 +334,15 @@ Vector<Command> Parser::parse()
|
||||||
|
|
||||||
while (m_state_stack.size() > 1) {
|
while (m_state_stack.size() > 1) {
|
||||||
if (state() == State::InDoubleQuotes) {
|
if (state() == State::InDoubleQuotes) {
|
||||||
|
pop_state();
|
||||||
commit_token(Token::UnterminatedDoubleQuoted, AllowEmptyToken::Yes);
|
commit_token(Token::UnterminatedDoubleQuoted, AllowEmptyToken::Yes);
|
||||||
} else if (state() == State::InSingleQuotes) {
|
} else if (state() == State::InSingleQuotes) {
|
||||||
|
pop_state();
|
||||||
commit_token(Token::UnterminatedSingleQuoted, AllowEmptyToken::Yes);
|
commit_token(Token::UnterminatedSingleQuoted, AllowEmptyToken::Yes);
|
||||||
} else {
|
} else {
|
||||||
commit_token(Token::Bare, AllowEmptyToken::No);
|
commit_token(Token::Bare, AllowEmptyToken::No);
|
||||||
|
pop_state();
|
||||||
}
|
}
|
||||||
pop_state();
|
|
||||||
}
|
}
|
||||||
ASSERT(state() == State::Free);
|
ASSERT(state() == State::Free);
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ struct Redirection {
|
||||||
Type type;
|
Type type;
|
||||||
int fd { -1 };
|
int fd { -1 };
|
||||||
int rewire_fd { -1 };
|
int rewire_fd { -1 };
|
||||||
String path {};
|
Token path {};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Rewiring {
|
struct Rewiring {
|
||||||
|
|
|
@ -1140,13 +1140,13 @@ ExitCodeOrContinuationRequest Shell::run_command(const StringView& cmd)
|
||||||
dbgprintf("Pipe\n");
|
dbgprintf("Pipe\n");
|
||||||
break;
|
break;
|
||||||
case Redirection::FileRead:
|
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;
|
break;
|
||||||
case Redirection::FileWrite:
|
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;
|
break;
|
||||||
case Redirection::FileWriteAppend:
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -1206,7 +1206,7 @@ ExitCodeOrContinuationRequest Shell::run_command(const StringView& cmd)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Redirection::FileWriteAppend: {
|
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) {
|
if (fd < 0) {
|
||||||
perror("open");
|
perror("open");
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -1216,7 +1216,7 @@ ExitCodeOrContinuationRequest Shell::run_command(const StringView& cmd)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Redirection::FileWrite: {
|
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) {
|
if (fd < 0) {
|
||||||
perror("open");
|
perror("open");
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -1226,7 +1226,7 @@ ExitCodeOrContinuationRequest Shell::run_command(const StringView& cmd)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Redirection::FileRead: {
|
case Redirection::FileRead: {
|
||||||
int fd = open(redirection.path.characters(), O_RDONLY);
|
int fd = open(redirection.path.text.characters(), O_RDONLY);
|
||||||
if (fd < 0) {
|
if (fd < 0) {
|
||||||
perror("open");
|
perror("open");
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -1580,10 +1580,12 @@ Vector<Line::CompletionSuggestion> Shell::complete(const Line::Editor& editor)
|
||||||
if (commands.size() == 0)
|
if (commands.size() == 0)
|
||||||
return {};
|
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;
|
String token;
|
||||||
bool is_first_in_subcommand = false;
|
bool is_first_in_subcommand = false;
|
||||||
auto& subcommand = commands.last().subcommands;
|
auto& subcommand = commands.last().subcommands;
|
||||||
|
String file_token_trail = " ";
|
||||||
|
String directory_token_trail = "/";
|
||||||
|
|
||||||
if (subcommand.size() == 0) {
|
if (subcommand.size() == 0) {
|
||||||
// foo bar; <tab>
|
// foo bar; <tab>
|
||||||
|
@ -1591,22 +1593,38 @@ Vector<Line::CompletionSuggestion> Shell::complete(const Line::Editor& editor)
|
||||||
is_first_in_subcommand = true;
|
is_first_in_subcommand = true;
|
||||||
} else {
|
} else {
|
||||||
auto& last_command = subcommand.last();
|
auto& last_command = subcommand.last();
|
||||||
if (last_command.args.size() == 0) {
|
if (!last_command.redirections.is_empty() && last_command.redirections.last().type != Redirection::Pipe) {
|
||||||
// foo bar | <tab>
|
// foo > bar<tab>
|
||||||
token = "";
|
const auto& redirection = last_command.redirections.last();
|
||||||
is_first_in_subcommand = true;
|
const auto& path = redirection.path;
|
||||||
} else {
|
|
||||||
auto& args = last_command.args;
|
if (path.end != line.length())
|
||||||
if (args.last().type == Token::Comment) // we cannot complete comments
|
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
if (args.last().end != line.length()) {
|
token = path.text;
|
||||||
// There was a token separator at the end
|
is_first_in_subcommand = false;
|
||||||
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 = "";
|
token = "";
|
||||||
|
is_first_in_subcommand = true;
|
||||||
} else {
|
} else {
|
||||||
is_first_in_subcommand = args.size() == 1;
|
auto& args = last_command.args;
|
||||||
token = last_command.args.last().text;
|
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 (!stat_error) {
|
||||||
if (S_ISDIR(program_status.st_mode)) {
|
if (S_ISDIR(program_status.st_mode)) {
|
||||||
if (!should_suggest_only_executables)
|
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 {
|
} 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 } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue