mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 00:27:43 +00:00
Shell: Allow completing StringLiterals as paths
This auto-escapes the token as well :^)
This commit is contained in:
parent
118590325a
commit
0ea775f257
6 changed files with 135 additions and 48 deletions
|
@ -335,17 +335,40 @@ Vector<Line::CompletionSuggestion> Node::complete_for_editor(Shell& shell, size_
|
||||||
{
|
{
|
||||||
auto matching_node = hit_test_result.matching_node;
|
auto matching_node = hit_test_result.matching_node;
|
||||||
if (matching_node) {
|
if (matching_node) {
|
||||||
if (matching_node->is_bareword()) {
|
auto kind = matching_node->kind();
|
||||||
auto* node = static_cast<BarewordLiteral*>(matching_node.ptr());
|
StringLiteral::EnclosureType enclosure_type = StringLiteral::EnclosureType::None;
|
||||||
auto corrected_offset = find_offset_into_node(node->text(), offset - matching_node->position().start_offset);
|
if (kind == Kind::StringLiteral)
|
||||||
|
enclosure_type = static_cast<StringLiteral*>(matching_node.ptr())->enclosure_type();
|
||||||
|
|
||||||
if (corrected_offset > node->text().length())
|
auto set_results_trivia = [enclosure_type](Vector<Line::CompletionSuggestion>&& suggestions) {
|
||||||
|
if (enclosure_type != StringLiteral::EnclosureType::None) {
|
||||||
|
for (auto& entry : suggestions)
|
||||||
|
entry.trailing_trivia = { static_cast<u32>(enclosure_type == StringLiteral::EnclosureType::SingleQuotes ? '\'' : '"') };
|
||||||
|
}
|
||||||
|
return suggestions;
|
||||||
|
};
|
||||||
|
if (kind == Kind::BarewordLiteral || kind == Kind::StringLiteral) {
|
||||||
|
Shell::EscapeMode escape_mode;
|
||||||
|
StringView text;
|
||||||
|
size_t corrected_offset;
|
||||||
|
if (kind == Kind::BarewordLiteral) {
|
||||||
|
auto* node = static_cast<BarewordLiteral*>(matching_node.ptr());
|
||||||
|
text = node->text();
|
||||||
|
escape_mode = Shell::EscapeMode::Bareword;
|
||||||
|
corrected_offset = find_offset_into_node(text, offset - matching_node->position().start_offset, escape_mode);
|
||||||
|
} else {
|
||||||
|
auto* node = static_cast<StringLiteral*>(matching_node.ptr());
|
||||||
|
text = node->text();
|
||||||
|
escape_mode = enclosure_type == StringLiteral::EnclosureType::SingleQuotes ? Shell::EscapeMode::SingleQuotedString : Shell::EscapeMode::DoubleQuotedString;
|
||||||
|
corrected_offset = find_offset_into_node(text, offset - matching_node->position().start_offset + 1, escape_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (corrected_offset > text.length())
|
||||||
return {};
|
return {};
|
||||||
auto& text = node->text();
|
|
||||||
|
|
||||||
// If the literal isn't an option, treat it as a path.
|
// If the literal isn't an option, treat it as a path.
|
||||||
if (!(text.starts_with("-") || text == "--" || text == "-"))
|
if (!(text.starts_with("-") || text == "--" || text == "-"))
|
||||||
return shell.complete_path("", text, corrected_offset, Shell::ExecutableOnly::No);
|
return set_results_trivia(shell.complete_path("", text, corrected_offset, Shell::ExecutableOnly::No, escape_mode));
|
||||||
|
|
||||||
// If the literal is an option, we have to know the program name
|
// If the literal is an option, we have to know the program name
|
||||||
// should we have no way to get that, bail early.
|
// should we have no way to get that, bail early.
|
||||||
|
@ -363,7 +386,7 @@ Vector<Line::CompletionSuggestion> Node::complete_for_editor(Shell& shell, size_
|
||||||
else
|
else
|
||||||
program_name = static_cast<StringLiteral*>(program_name_node.ptr())->text();
|
program_name = static_cast<StringLiteral*>(program_name_node.ptr())->text();
|
||||||
|
|
||||||
return shell.complete_option(program_name, text, corrected_offset);
|
return set_results_trivia(shell.complete_option(program_name, text, corrected_offset));
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -3096,9 +3119,10 @@ void StringLiteral::highlight_in_editor(Line::Editor& editor, Shell&, HighlightM
|
||||||
editor.stylize({ m_position.start_offset, m_position.end_offset }, move(style));
|
editor.stylize({ m_position.start_offset, m_position.end_offset }, move(style));
|
||||||
}
|
}
|
||||||
|
|
||||||
StringLiteral::StringLiteral(Position position, String text)
|
StringLiteral::StringLiteral(Position position, String text, EnclosureType enclosure_type)
|
||||||
: Node(move(position))
|
: Node(move(position))
|
||||||
, m_text(move(text))
|
, m_text(move(text))
|
||||||
|
, m_enclosure_type(enclosure_type)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1344,11 +1344,18 @@ private:
|
||||||
|
|
||||||
class StringLiteral final : public Node {
|
class StringLiteral final : public Node {
|
||||||
public:
|
public:
|
||||||
StringLiteral(Position, String);
|
enum class EnclosureType {
|
||||||
|
None,
|
||||||
|
SingleQuotes,
|
||||||
|
DoubleQuotes,
|
||||||
|
};
|
||||||
|
|
||||||
|
StringLiteral(Position, String, EnclosureType);
|
||||||
virtual ~StringLiteral();
|
virtual ~StringLiteral();
|
||||||
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
|
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
|
||||||
|
|
||||||
const String& text() const { return m_text; }
|
const String& text() const { return m_text; }
|
||||||
|
EnclosureType enclosure_type() const { return m_enclosure_type; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NODE(StringLiteral);
|
NODE(StringLiteral);
|
||||||
|
@ -1358,6 +1365,7 @@ private:
|
||||||
virtual RefPtr<Node> leftmost_trivial_literal() const override { return this; };
|
virtual RefPtr<Node> leftmost_trivial_literal() const override { return this; };
|
||||||
|
|
||||||
String m_text;
|
String m_text;
|
||||||
|
EnclosureType m_enclosure_type;
|
||||||
};
|
};
|
||||||
|
|
||||||
class StringPartCompose final : public Node {
|
class StringPartCompose final : public Node {
|
||||||
|
|
|
@ -228,7 +228,7 @@ RefPtr<AST::Node> Shell::immediate_regex_replace(AST::ImmediateExpression& invok
|
||||||
Regex<PosixExtendedParser> re { pattern->resolve_as_list(this).first() };
|
Regex<PosixExtendedParser> re { pattern->resolve_as_list(this).first() };
|
||||||
auto result = re.replace(value->resolve_as_list(this)[0], replacement->resolve_as_list(this)[0], PosixFlags::Global | PosixFlags::Multiline | PosixFlags::Unicode);
|
auto result = re.replace(value->resolve_as_list(this)[0], replacement->resolve_as_list(this)[0], PosixFlags::Global | PosixFlags::Multiline | PosixFlags::Unicode);
|
||||||
|
|
||||||
return AST::make_ref_counted<AST::StringLiteral>(invoking_node.position(), move(result));
|
return AST::make_ref_counted<AST::StringLiteral>(invoking_node.position(), move(result), AST::StringLiteral::EnclosureType::None);
|
||||||
}
|
}
|
||||||
|
|
||||||
RefPtr<AST::Node> Shell::immediate_remove_suffix(AST::ImmediateExpression& invoking_node, const NonnullRefPtrVector<AST::Node>& arguments)
|
RefPtr<AST::Node> Shell::immediate_remove_suffix(AST::ImmediateExpression& invoking_node, const NonnullRefPtrVector<AST::Node>& arguments)
|
||||||
|
@ -256,7 +256,7 @@ RefPtr<AST::Node> Shell::immediate_remove_suffix(AST::ImmediateExpression& invok
|
||||||
|
|
||||||
if (value_str.ends_with(suffix_str))
|
if (value_str.ends_with(suffix_str))
|
||||||
removed = removed.substring_view(0, value_str.length() - suffix_str.length());
|
removed = removed.substring_view(0, value_str.length() - suffix_str.length());
|
||||||
nodes.append(AST::make_ref_counted<AST::StringLiteral>(invoking_node.position(), removed));
|
nodes.append(AST::make_ref_counted<AST::StringLiteral>(invoking_node.position(), removed, AST::StringLiteral::EnclosureType::None));
|
||||||
}
|
}
|
||||||
|
|
||||||
return AST::make_ref_counted<AST::ListConcatenate>(invoking_node.position(), move(nodes));
|
return AST::make_ref_counted<AST::ListConcatenate>(invoking_node.position(), move(nodes));
|
||||||
|
@ -287,7 +287,7 @@ RefPtr<AST::Node> Shell::immediate_remove_prefix(AST::ImmediateExpression& invok
|
||||||
|
|
||||||
if (value_str.starts_with(prefix_str))
|
if (value_str.starts_with(prefix_str))
|
||||||
removed = removed.substring_view(prefix_str.length());
|
removed = removed.substring_view(prefix_str.length());
|
||||||
nodes.append(AST::make_ref_counted<AST::StringLiteral>(invoking_node.position(), removed));
|
nodes.append(AST::make_ref_counted<AST::StringLiteral>(invoking_node.position(), removed, AST::StringLiteral::EnclosureType::None));
|
||||||
}
|
}
|
||||||
|
|
||||||
return AST::make_ref_counted<AST::ListConcatenate>(invoking_node.position(), move(nodes));
|
return AST::make_ref_counted<AST::ListConcatenate>(invoking_node.position(), move(nodes));
|
||||||
|
@ -375,7 +375,7 @@ RefPtr<AST::Node> Shell::immediate_concat_lists(AST::ImmediateExpression& invoki
|
||||||
} else {
|
} else {
|
||||||
auto values = list_of_values->resolve_as_list(this);
|
auto values = list_of_values->resolve_as_list(this);
|
||||||
for (auto& entry : values)
|
for (auto& entry : values)
|
||||||
result.append(AST::make_ref_counted<AST::StringLiteral>(argument.position(), entry));
|
result.append(AST::make_ref_counted<AST::StringLiteral>(argument.position(), entry, AST::StringLiteral::EnclosureType::None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -333,7 +333,7 @@ RefPtr<AST::Node> Parser::parse_variable_decls()
|
||||||
if (!expression) {
|
if (!expression) {
|
||||||
if (is_whitespace(peek())) {
|
if (is_whitespace(peek())) {
|
||||||
auto string_start = push_start();
|
auto string_start = push_start();
|
||||||
expression = create<AST::StringLiteral>("");
|
expression = create<AST::StringLiteral>("", AST::StringLiteral::EnclosureType::None);
|
||||||
} else {
|
} else {
|
||||||
restore_to(pos_before_name.offset, pos_before_name.line);
|
restore_to(pos_before_name.offset, pos_before_name.line);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -1276,7 +1276,7 @@ RefPtr<AST::Node> Parser::parse_string()
|
||||||
bool is_error = false;
|
bool is_error = false;
|
||||||
if (!expect('\''))
|
if (!expect('\''))
|
||||||
is_error = true;
|
is_error = true;
|
||||||
auto result = create<AST::StringLiteral>(move(text)); // String Literal
|
auto result = create<AST::StringLiteral>(move(text), AST::StringLiteral::EnclosureType::SingleQuotes); // String Literal
|
||||||
if (is_error)
|
if (is_error)
|
||||||
result->set_is_syntax_error(*create<AST::SyntaxError>("Expected a terminating single quote", true));
|
result->set_is_syntax_error(*create<AST::SyntaxError>("Expected a terminating single quote", true));
|
||||||
return result;
|
return result;
|
||||||
|
@ -1358,7 +1358,7 @@ RefPtr<AST::Node> Parser::parse_string_inner(StringEndCondition condition)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (peek() == '$') {
|
if (peek() == '$') {
|
||||||
auto string_literal = create<AST::StringLiteral>(builder.to_string()); // String Literal
|
auto string_literal = create<AST::StringLiteral>(builder.to_string(), AST::StringLiteral::EnclosureType::DoubleQuotes); // String Literal
|
||||||
auto read_concat = [&](auto&& node) {
|
auto read_concat = [&](auto&& node) {
|
||||||
auto inner = create<AST::StringPartCompose>(
|
auto inner = create<AST::StringPartCompose>(
|
||||||
move(string_literal),
|
move(string_literal),
|
||||||
|
@ -1384,7 +1384,7 @@ RefPtr<AST::Node> Parser::parse_string_inner(StringEndCondition condition)
|
||||||
builder.append(consume());
|
builder.append(consume());
|
||||||
}
|
}
|
||||||
|
|
||||||
return create<AST::StringLiteral>(builder.to_string()); // String Literal
|
return create<AST::StringLiteral>(builder.to_string(), AST::StringLiteral::EnclosureType::DoubleQuotes); // String Literal
|
||||||
}
|
}
|
||||||
|
|
||||||
RefPtr<AST::Node> Parser::parse_variable()
|
RefPtr<AST::Node> Parser::parse_variable()
|
||||||
|
@ -1923,7 +1923,7 @@ RefPtr<AST::Node> Parser::parse_brace_expansion_spec()
|
||||||
|
|
||||||
if (next_is(",")) {
|
if (next_is(",")) {
|
||||||
// Note that we don't consume the ',' here.
|
// Note that we don't consume the ',' here.
|
||||||
subexpressions.append(create<AST::StringLiteral>(""));
|
subexpressions.append(create<AST::StringLiteral>("", AST::StringLiteral::EnclosureType::None));
|
||||||
} else {
|
} else {
|
||||||
auto start_expr = parse_expression();
|
auto start_expr = parse_expression();
|
||||||
if (start_expr) {
|
if (start_expr) {
|
||||||
|
@ -1948,7 +1948,7 @@ RefPtr<AST::Node> Parser::parse_brace_expansion_spec()
|
||||||
if (expr) {
|
if (expr) {
|
||||||
subexpressions.append(expr.release_nonnull());
|
subexpressions.append(expr.release_nonnull());
|
||||||
} else {
|
} else {
|
||||||
subexpressions.append(create<AST::StringLiteral>(""));
|
subexpressions.append(create<AST::StringLiteral>("", AST::StringLiteral::EnclosureType::None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2061,7 +2061,7 @@ bool Parser::parse_heredoc_entries()
|
||||||
if (!last_line_offset.has_value())
|
if (!last_line_offset.has_value())
|
||||||
last_line_offset = current_position();
|
last_line_offset = current_position();
|
||||||
// Now just wrap it in a StringLiteral and set it as the node's contents
|
// Now just wrap it in a StringLiteral and set it as the node's contents
|
||||||
auto node = create<AST::StringLiteral>(m_input.substring_view(rule_start->offset, last_line_offset->offset - rule_start->offset));
|
auto node = create<AST::StringLiteral>(m_input.substring_view(rule_start->offset, last_line_offset->offset - rule_start->offset), AST::StringLiteral::EnclosureType::None);
|
||||||
if (!found_key)
|
if (!found_key)
|
||||||
node->set_is_syntax_error(*create<AST::SyntaxError>(String::formatted("Expected to find the heredoc key '{}', but found Eof", record.end), true));
|
node->set_is_syntax_error(*create<AST::SyntaxError>(String::formatted("Expected to find the heredoc key '{}', but found Eof", record.end), true));
|
||||||
record.node->set_contents(move(node));
|
record.node->set_contents(move(node));
|
||||||
|
@ -2110,7 +2110,7 @@ bool Parser::parse_heredoc_entries()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!expr && found_key) {
|
if (!expr && found_key) {
|
||||||
expr = create<AST::StringLiteral>("");
|
expr = create<AST::StringLiteral>("", AST::StringLiteral::EnclosureType::None);
|
||||||
} else if (!expr) {
|
} else if (!expr) {
|
||||||
expr = create<AST::SyntaxError>(String::formatted("Expected to find a valid string inside a heredoc (with end key '{}')", record.end), true);
|
expr = create<AST::SyntaxError>(String::formatted("Expected to find a valid string inside a heredoc (with end key '{}')", record.end), true);
|
||||||
} else if (!found_key) {
|
} else if (!found_key) {
|
||||||
|
|
|
@ -1146,12 +1146,19 @@ String Shell::escape_token_for_double_quotes(StringView token)
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
Shell::SpecialCharacterEscapeMode Shell::special_character_escape_mode(u32 code_point)
|
Shell::SpecialCharacterEscapeMode Shell::special_character_escape_mode(u32 code_point, EscapeMode mode)
|
||||||
{
|
{
|
||||||
switch (code_point) {
|
switch (code_point) {
|
||||||
case '\'':
|
case '\'':
|
||||||
|
if (mode == EscapeMode::DoubleQuotedString)
|
||||||
|
return SpecialCharacterEscapeMode::Untouched;
|
||||||
|
return SpecialCharacterEscapeMode::Escaped;
|
||||||
case '"':
|
case '"':
|
||||||
case '$':
|
case '$':
|
||||||
|
case '\\':
|
||||||
|
if (mode == EscapeMode::SingleQuotedString)
|
||||||
|
return SpecialCharacterEscapeMode::Untouched;
|
||||||
|
return SpecialCharacterEscapeMode::Escaped;
|
||||||
case '|':
|
case '|':
|
||||||
case '>':
|
case '>':
|
||||||
case '<':
|
case '<':
|
||||||
|
@ -1161,8 +1168,9 @@ Shell::SpecialCharacterEscapeMode Shell::special_character_escape_mode(u32 code_
|
||||||
case '}':
|
case '}':
|
||||||
case '&':
|
case '&':
|
||||||
case ';':
|
case ';':
|
||||||
case '\\':
|
|
||||||
case ' ':
|
case ' ':
|
||||||
|
if (mode == EscapeMode::SingleQuotedString || mode == EscapeMode::DoubleQuotedString)
|
||||||
|
return SpecialCharacterEscapeMode::Untouched;
|
||||||
return SpecialCharacterEscapeMode::Escaped;
|
return SpecialCharacterEscapeMode::Escaped;
|
||||||
case '\n':
|
case '\n':
|
||||||
case '\t':
|
case '\t':
|
||||||
|
@ -1176,13 +1184,13 @@ Shell::SpecialCharacterEscapeMode Shell::special_character_escape_mode(u32 code_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String Shell::escape_token(StringView token)
|
String Shell::escape_token(StringView token, EscapeMode escape_mode)
|
||||||
{
|
{
|
||||||
auto do_escape = [](auto& token) {
|
auto do_escape = [escape_mode](auto& token) {
|
||||||
StringBuilder builder;
|
StringBuilder builder;
|
||||||
for (auto c : token) {
|
for (auto c : token) {
|
||||||
static_assert(sizeof(c) == sizeof(u32) || sizeof(c) == sizeof(u8));
|
static_assert(sizeof(c) == sizeof(u32) || sizeof(c) == sizeof(u8));
|
||||||
switch (special_character_escape_mode(c)) {
|
switch (special_character_escape_mode(c, escape_mode)) {
|
||||||
case SpecialCharacterEscapeMode::Untouched:
|
case SpecialCharacterEscapeMode::Untouched:
|
||||||
if constexpr (sizeof(c) == sizeof(u8))
|
if constexpr (sizeof(c) == sizeof(u8))
|
||||||
builder.append(c);
|
builder.append(c);
|
||||||
|
@ -1190,29 +1198,51 @@ String Shell::escape_token(StringView token)
|
||||||
builder.append(Utf32View { &c, 1 });
|
builder.append(Utf32View { &c, 1 });
|
||||||
break;
|
break;
|
||||||
case SpecialCharacterEscapeMode::Escaped:
|
case SpecialCharacterEscapeMode::Escaped:
|
||||||
|
if (escape_mode == EscapeMode::SingleQuotedString)
|
||||||
|
builder.append("'");
|
||||||
builder.append('\\');
|
builder.append('\\');
|
||||||
builder.append(c);
|
builder.append(c);
|
||||||
|
if (escape_mode == EscapeMode::SingleQuotedString)
|
||||||
|
builder.append("'");
|
||||||
break;
|
break;
|
||||||
case SpecialCharacterEscapeMode::QuotedAsEscape:
|
case SpecialCharacterEscapeMode::QuotedAsEscape:
|
||||||
|
if (escape_mode == EscapeMode::SingleQuotedString)
|
||||||
|
builder.append("'");
|
||||||
|
if (escape_mode != EscapeMode::DoubleQuotedString)
|
||||||
|
builder.append("\"");
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '\n':
|
case '\n':
|
||||||
builder.append(R"("\n")");
|
builder.append(R"(\n)");
|
||||||
break;
|
break;
|
||||||
case '\t':
|
case '\t':
|
||||||
builder.append(R"("\t")");
|
builder.append(R"(\t)");
|
||||||
break;
|
break;
|
||||||
case '\r':
|
case '\r':
|
||||||
builder.append(R"("\r")");
|
builder.append(R"(\r)");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
if (escape_mode != EscapeMode::DoubleQuotedString)
|
||||||
|
builder.append("\"");
|
||||||
|
if (escape_mode == EscapeMode::SingleQuotedString)
|
||||||
|
builder.append("'");
|
||||||
break;
|
break;
|
||||||
case SpecialCharacterEscapeMode::QuotedAsHex:
|
case SpecialCharacterEscapeMode::QuotedAsHex:
|
||||||
|
if (escape_mode == EscapeMode::SingleQuotedString)
|
||||||
|
builder.append("'");
|
||||||
|
if (escape_mode != EscapeMode::DoubleQuotedString)
|
||||||
|
builder.append("\"");
|
||||||
|
|
||||||
if (c <= NumericLimits<u8>::max())
|
if (c <= NumericLimits<u8>::max())
|
||||||
builder.appendff(R"("\x{:0>2x}")", static_cast<u8>(c));
|
builder.appendff(R"(\x{:0>2x})", static_cast<u8>(c));
|
||||||
else
|
else
|
||||||
builder.appendff(R"("\u{:0>8x}")", static_cast<u32>(c));
|
builder.appendff(R"(\u{:0>8x})", static_cast<u32>(c));
|
||||||
|
|
||||||
|
if (escape_mode != EscapeMode::DoubleQuotedString)
|
||||||
|
builder.append("\"");
|
||||||
|
if (escape_mode == EscapeMode::SingleQuotedString)
|
||||||
|
builder.append("'");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1372,8 +1402,7 @@ Vector<Line::CompletionSuggestion> Shell::complete()
|
||||||
return ast->complete_for_editor(*this, line.length());
|
return ast->complete_for_editor(*this, line.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector<Line::CompletionSuggestion> Shell::complete_path(StringView base,
|
Vector<Line::CompletionSuggestion> Shell::complete_path(StringView base, StringView part, size_t offset, ExecutableOnly executable_only, EscapeMode escape_mode)
|
||||||
StringView part, size_t offset, ExecutableOnly executable_only)
|
|
||||||
{
|
{
|
||||||
auto token = offset ? part.substring_view(0, offset) : "";
|
auto token = offset ? part.substring_view(0, offset) : "";
|
||||||
String path;
|
String path;
|
||||||
|
@ -1415,7 +1444,7 @@ Vector<Line::CompletionSuggestion> Shell::complete_path(StringView base,
|
||||||
// e. in `cd /foo/bar', 'bar' is the invariant
|
// e. in `cd /foo/bar', 'bar' is the invariant
|
||||||
// since we are not suggesting anything starting with
|
// since we are not suggesting anything starting with
|
||||||
// `/foo/', but rather just `bar...'
|
// `/foo/', but rather just `bar...'
|
||||||
auto token_length = escape_token(token).length();
|
auto token_length = escape_token(token, escape_mode).length();
|
||||||
size_t static_offset = last_slash + 1;
|
size_t static_offset = last_slash + 1;
|
||||||
auto invariant_offset = token_length;
|
auto invariant_offset = token_length;
|
||||||
if (m_editor)
|
if (m_editor)
|
||||||
|
@ -1435,11 +1464,11 @@ Vector<Line::CompletionSuggestion> Shell::complete_path(StringView base,
|
||||||
int stat_error = stat(file_path.characters(), &program_status);
|
int stat_error = stat(file_path.characters(), &program_status);
|
||||||
if (!stat_error && (executable_only == ExecutableOnly::No || access(file_path.characters(), X_OK) == 0)) {
|
if (!stat_error && (executable_only == ExecutableOnly::No || access(file_path.characters(), X_OK) == 0)) {
|
||||||
if (S_ISDIR(program_status.st_mode)) {
|
if (S_ISDIR(program_status.st_mode)) {
|
||||||
suggestions.append({ escape_token(file), "/" });
|
suggestions.append({ escape_token(file, escape_mode), "/" });
|
||||||
} else {
|
} else {
|
||||||
if (!allow_direct_children && !file.contains("/"))
|
if (!allow_direct_children && !file.contains("/"))
|
||||||
continue;
|
continue;
|
||||||
suggestions.append({ escape_token(file), " " });
|
suggestions.append({ escape_token(file, escape_mode), " " });
|
||||||
}
|
}
|
||||||
suggestions.last().input_offset = token_length;
|
suggestions.last().input_offset = token_length;
|
||||||
suggestions.last().invariant_offset = invariant_offset;
|
suggestions.last().invariant_offset = invariant_offset;
|
||||||
|
@ -1451,7 +1480,7 @@ Vector<Line::CompletionSuggestion> Shell::complete_path(StringView base,
|
||||||
return suggestions;
|
return suggestions;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector<Line::CompletionSuggestion> Shell::complete_program_name(StringView name, size_t offset)
|
Vector<Line::CompletionSuggestion> Shell::complete_program_name(StringView name, size_t offset, EscapeMode escape_mode)
|
||||||
{
|
{
|
||||||
auto match = binary_search(
|
auto match = binary_search(
|
||||||
cached_path.span(),
|
cached_path.span(),
|
||||||
|
@ -1465,10 +1494,10 @@ Vector<Line::CompletionSuggestion> Shell::complete_program_name(StringView name,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!match)
|
if (!match)
|
||||||
return complete_path("", name, offset, ExecutableOnly::Yes);
|
return complete_path("", name, offset, ExecutableOnly::Yes, escape_mode);
|
||||||
|
|
||||||
String completion = *match;
|
String completion = *match;
|
||||||
auto token_length = escape_token(name).length();
|
auto token_length = escape_token(name, escape_mode).length();
|
||||||
auto invariant_offset = token_length;
|
auto invariant_offset = token_length;
|
||||||
size_t static_offset = 0;
|
size_t static_offset = 0;
|
||||||
if (m_editor)
|
if (m_editor)
|
||||||
|
|
|
@ -156,9 +156,14 @@ public:
|
||||||
[[nodiscard]] Frame push_frame(String name);
|
[[nodiscard]] Frame push_frame(String name);
|
||||||
void pop_frame();
|
void pop_frame();
|
||||||
|
|
||||||
|
enum class EscapeMode {
|
||||||
|
Bareword,
|
||||||
|
SingleQuotedString,
|
||||||
|
DoubleQuotedString,
|
||||||
|
};
|
||||||
static String escape_token_for_double_quotes(StringView token);
|
static String escape_token_for_double_quotes(StringView token);
|
||||||
static String escape_token_for_single_quotes(StringView token);
|
static String escape_token_for_single_quotes(StringView token);
|
||||||
static String escape_token(StringView token);
|
static String escape_token(StringView token, EscapeMode = EscapeMode::Bareword);
|
||||||
static String unescape_token(StringView token);
|
static String unescape_token(StringView token);
|
||||||
enum class SpecialCharacterEscapeMode {
|
enum class SpecialCharacterEscapeMode {
|
||||||
Untouched,
|
Untouched,
|
||||||
|
@ -166,7 +171,7 @@ public:
|
||||||
QuotedAsEscape,
|
QuotedAsEscape,
|
||||||
QuotedAsHex,
|
QuotedAsHex,
|
||||||
};
|
};
|
||||||
static SpecialCharacterEscapeMode special_character_escape_mode(u32 c);
|
static SpecialCharacterEscapeMode special_character_escape_mode(u32 c, EscapeMode);
|
||||||
|
|
||||||
static bool is_glob(StringView);
|
static bool is_glob(StringView);
|
||||||
static Vector<StringView> split_path(StringView);
|
static Vector<StringView> split_path(StringView);
|
||||||
|
@ -178,8 +183,8 @@ public:
|
||||||
|
|
||||||
void highlight(Line::Editor&) const;
|
void highlight(Line::Editor&) const;
|
||||||
Vector<Line::CompletionSuggestion> complete();
|
Vector<Line::CompletionSuggestion> complete();
|
||||||
Vector<Line::CompletionSuggestion> complete_path(StringView base, StringView, size_t offset, ExecutableOnly executable_only);
|
Vector<Line::CompletionSuggestion> complete_path(StringView base, StringView, size_t offset, ExecutableOnly executable_only, EscapeMode = EscapeMode::Bareword);
|
||||||
Vector<Line::CompletionSuggestion> complete_program_name(StringView, size_t offset);
|
Vector<Line::CompletionSuggestion> complete_program_name(StringView, size_t offset, EscapeMode = EscapeMode::Bareword);
|
||||||
Vector<Line::CompletionSuggestion> complete_variable(StringView, size_t offset);
|
Vector<Line::CompletionSuggestion> complete_variable(StringView, size_t offset);
|
||||||
Vector<Line::CompletionSuggestion> complete_user(StringView, size_t offset);
|
Vector<Line::CompletionSuggestion> complete_user(StringView, size_t offset);
|
||||||
Vector<Line::CompletionSuggestion> complete_option(StringView, StringView, size_t offset);
|
Vector<Line::CompletionSuggestion> complete_option(StringView, StringView, size_t offset);
|
||||||
|
@ -378,7 +383,7 @@ private:
|
||||||
return c == '_' || (c <= 'Z' && c >= 'A') || (c <= 'z' && c >= 'a') || (c <= '9' && c >= '0');
|
return c == '_' || (c <= 'Z' && c >= 'A') || (c <= 'z' && c >= 'a') || (c <= '9' && c >= '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
inline size_t find_offset_into_node(StringView unescaped_text, size_t escaped_offset)
|
inline size_t find_offset_into_node(StringView unescaped_text, size_t escaped_offset, Shell::EscapeMode escape_mode)
|
||||||
{
|
{
|
||||||
size_t unescaped_offset = 0;
|
size_t unescaped_offset = 0;
|
||||||
size_t offset = 0;
|
size_t offset = 0;
|
||||||
|
@ -387,20 +392,41 @@ inline size_t find_offset_into_node(StringView unescaped_text, size_t escaped_of
|
||||||
if (offset == escaped_offset)
|
if (offset == escaped_offset)
|
||||||
return unescaped_offset;
|
return unescaped_offset;
|
||||||
|
|
||||||
switch (Shell::special_character_escape_mode(c)) {
|
switch (Shell::special_character_escape_mode(c, escape_mode)) {
|
||||||
case Shell::SpecialCharacterEscapeMode::Untouched:
|
case Shell::SpecialCharacterEscapeMode::Untouched:
|
||||||
break;
|
break;
|
||||||
case Shell::SpecialCharacterEscapeMode::Escaped:
|
case Shell::SpecialCharacterEscapeMode::Escaped:
|
||||||
++offset; // X -> \X
|
++offset; // X -> \X
|
||||||
break;
|
break;
|
||||||
case Shell::SpecialCharacterEscapeMode::QuotedAsEscape:
|
case Shell::SpecialCharacterEscapeMode::QuotedAsEscape:
|
||||||
offset += 3; // X -> "\Y"
|
switch (escape_mode) {
|
||||||
|
case Shell::EscapeMode::Bareword:
|
||||||
|
offset += 3; // X -> "\Y"
|
||||||
|
break;
|
||||||
|
case Shell::EscapeMode::SingleQuotedString:
|
||||||
|
offset += 5; // X -> '"\Y"'
|
||||||
|
break;
|
||||||
|
case Shell::EscapeMode::DoubleQuotedString:
|
||||||
|
offset += 1; // X -> \Y
|
||||||
|
break;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Shell::SpecialCharacterEscapeMode::QuotedAsHex:
|
case Shell::SpecialCharacterEscapeMode::QuotedAsHex:
|
||||||
|
switch (escape_mode) {
|
||||||
|
case Shell::EscapeMode::Bareword:
|
||||||
|
offset += 2; // X -> "\..."
|
||||||
|
break;
|
||||||
|
case Shell::EscapeMode::SingleQuotedString:
|
||||||
|
offset += 4; // X -> '"\..."'
|
||||||
|
break;
|
||||||
|
case Shell::EscapeMode::DoubleQuotedString:
|
||||||
|
// X -> \...
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (c > NumericLimits<u8>::max())
|
if (c > NumericLimits<u8>::max())
|
||||||
offset += 11; // X -> "\uhhhhhhhh"
|
offset += 8; // X -> "\uhhhhhhhh"
|
||||||
else
|
else
|
||||||
offset += 5; // X -> "\xhh"
|
offset += 3; // X -> "\xhh"
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
++offset;
|
++offset;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue