From 3a37e8c56fc9b30166cb6c1ff76c274fea4b26c2 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Tue, 23 Jun 2020 19:10:41 +0430 Subject: [PATCH] Shell: Provide completions to Tilde and its Juxtaposition. This commit also removes the ExecutionInputType and directly uses RefPtr instead, since nothing else is needed for execution purposes, and also makes the shell refuse to evaluate commands with any sort of syntax error. --- Shell/AST.cpp | 294 ++++++++++++++++++++++++++++++----------------- Shell/AST.h | 102 ++++++++-------- Shell/Parser.cpp | 79 +++++++++---- Shell/Shell.cpp | 68 ++++++++--- Shell/Shell.h | 3 +- 5 files changed, 350 insertions(+), 196 deletions(-) diff --git a/Shell/AST.cpp b/Shell/AST.cpp index eac2501ee4..9fd98a74fd 100644 --- a/Shell/AST.cpp +++ b/Shell/AST.cpp @@ -96,7 +96,7 @@ Vector Node::complete_for_editor(Shell& shell, size_ if (corrected_offset > node->text().length()) return {}; - return shell.complete_path(node->text(), corrected_offset); + return shell.complete_path("", node->text(), corrected_offset); } return {}; } @@ -129,11 +129,9 @@ void And::dump(int level) const m_right->dump(level + 1); } -RefPtr And::run(TheExecutionInputType input_value) +RefPtr And::run(RefPtr shell) { - auto shell = input_value; - - auto left = m_left->run(input_value); + auto left = m_left->run(shell); ASSERT(left->is_job()); auto* job_value = static_cast(left.ptr()); @@ -146,7 +144,7 @@ RefPtr And::run(TheExecutionInputType input_value) shell->block_on_job(job); if (job->exit_code() == 0) - return m_right->run(input_value); + return m_right->run(shell); return job_value; } @@ -174,6 +172,8 @@ And::And(Position position, RefPtr left, RefPtr right) , m_left(move(left)) , m_right(move(right)) { + if (m_left->is_syntax_error() || m_right->is_syntax_error()) + set_is_syntax_error(); } And::~And() @@ -187,13 +187,13 @@ void ListConcatenate::dump(int level) const m_list->dump(level + 1); } -RefPtr ListConcatenate::run(TheExecutionInputType input_value) +RefPtr ListConcatenate::run(RefPtr shell) { - auto list = m_list->run(input_value)->resolve_without_cast(input_value); - auto element = m_element->run(input_value)->resolve_without_cast(input_value); + auto list = m_list->run(shell)->resolve_without_cast(shell); + auto element = m_element->run(shell)->resolve_without_cast(shell); if (list->is_command() || element->is_command()) { - auto joined_commands = join_commands(element->resolve_as_commands(input_value), list->resolve_as_commands(input_value)); + auto joined_commands = join_commands(element->resolve_as_commands(shell), list->resolve_as_commands(shell)); if (joined_commands.size() == 1) return create(joined_commands[0]); @@ -231,6 +231,8 @@ ListConcatenate::ListConcatenate(Position position, RefPtr element, RefPtr , m_element(move(element)) , m_list(move(list)) { + if (m_element->is_syntax_error() || m_list->is_syntax_error()) + set_is_syntax_error(); } ListConcatenate::~ListConcatenate() @@ -243,9 +245,9 @@ void Background::dump(int level) const m_command->dump(level + 1); } -RefPtr Background::run(TheExecutionInputType input_value) +RefPtr Background::run(RefPtr shell) { - auto commands = m_command->run(input_value)->resolve_as_commands(input_value); + auto commands = m_command->run(shell)->resolve_as_commands(shell); auto& last = commands.last(); last.should_wait = false; @@ -269,6 +271,8 @@ Background::Background(Position position, RefPtr command) : Node(move(position)) , m_command(move(command)) { + if (m_command->is_syntax_error()) + set_is_syntax_error(); } Background::~Background() @@ -281,7 +285,7 @@ void BarewordLiteral::dump(int level) const print_indented(m_text, level + 1); } -RefPtr BarewordLiteral::run(TheExecutionInputType) +RefPtr BarewordLiteral::run(RefPtr) { return create(m_text); } @@ -331,17 +335,16 @@ void CastToCommand::dump(int level) const m_inner->dump(level + 1); } -RefPtr CastToCommand::run(TheExecutionInputType input_value) +RefPtr CastToCommand::run(RefPtr shell) { if (m_inner->is_command()) - return m_inner->run(input_value); + return m_inner->run(shell); - auto shell = input_value; - auto value = m_inner->run(input_value)->resolve_without_cast(input_value); + auto value = m_inner->run(shell)->resolve_without_cast(shell); if (value->is_command()) return value; - auto argv = value->resolve_as_list(input_value); + auto argv = value->resolve_as_list(shell); return create(move(argv)); } @@ -380,6 +383,8 @@ CastToCommand::CastToCommand(Position position, RefPtr inner) : Node(move(position)) , m_inner(move(inner)) { + if (m_inner->is_syntax_error()) + set_is_syntax_error(); } CastToCommand::~CastToCommand() @@ -395,18 +400,17 @@ void CastToList::dump(int level) const print_indented("(empty)", level + 1); } -RefPtr CastToList::run(TheExecutionInputType input_value) +RefPtr CastToList::run(RefPtr shell) { if (!m_inner) return create({}); - auto shell = input_value; - auto inner_value = m_inner->run(input_value); + auto inner_value = m_inner->run(shell); if (inner_value->is_command()) return inner_value; - auto values = inner_value->resolve_as_list(input_value); + auto values = inner_value->resolve_as_list(shell); Vector> cast_values; for (auto& value : values) cast_values.append(create(value)); @@ -435,6 +439,8 @@ CastToList::CastToList(Position position, RefPtr inner) : Node(move(position)) , m_inner(move(inner)) { + if (m_inner && m_inner->is_syntax_error()) + set_is_syntax_error(); } CastToList::~CastToList() @@ -447,7 +453,7 @@ void CloseFdRedirection::dump(int level) const print_indented(String::format("%d -> Close", m_fd), level); } -RefPtr CloseFdRedirection::run(TheExecutionInputType) +RefPtr CloseFdRedirection::run(RefPtr) { Command command; command.redirections.append(*new CloseRedirection(m_fd)); @@ -476,7 +482,7 @@ void CommandLiteral::dump(int level) const print_indented("(Generated command literal)", level + 1); } -RefPtr CommandLiteral::run(TheExecutionInputType) +RefPtr CommandLiteral::run(RefPtr) { return create(m_command); } @@ -497,7 +503,7 @@ void Comment::dump(int level) const print_indented(m_text, level + 1); } -RefPtr Comment::run(TheExecutionInputType) +RefPtr Comment::run(RefPtr) { return create(""); } @@ -523,11 +529,10 @@ void DoubleQuotedString::dump(int level) const m_inner->dump(level + 1); } -RefPtr DoubleQuotedString::run(TheExecutionInputType input_value) +RefPtr DoubleQuotedString::run(RefPtr shell) { StringBuilder builder; - auto shell = input_value; - auto values = m_inner->run(input_value)->resolve_as_list(input_value); + auto values = m_inner->run(shell)->resolve_as_list(shell); builder.join("", values); @@ -557,6 +562,8 @@ DoubleQuotedString::DoubleQuotedString(Position position, RefPtr inner) : Node(move(position)) , m_inner(move(inner)) { + if (m_inner->is_syntax_error()) + set_is_syntax_error(); } DoubleQuotedString::~DoubleQuotedString() @@ -569,19 +576,19 @@ void DynamicEvaluate::dump(int level) const m_inner->dump(level + 1); } -RefPtr DynamicEvaluate::run(TheExecutionInputType input_value) +RefPtr DynamicEvaluate::run(RefPtr shell) { - auto result = m_inner->run(input_value)->resolve_without_cast(input_value); + auto result = m_inner->run(shell)->resolve_without_cast(shell); // Dynamic Evaluation behaves differently between strings and lists. // Strings are treated as variables, and Lists are treated as commands. if (result->is_string()) { - auto name_part = result->resolve_as_list(input_value); + auto name_part = result->resolve_as_list(shell); ASSERT(name_part.size() == 1); return create(name_part[0]); } // If it's anything else, we're just gonna cast it to a list. - auto list = result->resolve_as_list(input_value); + auto list = result->resolve_as_list(shell); return create(move(list)); } @@ -603,6 +610,8 @@ DynamicEvaluate::DynamicEvaluate(Position position, RefPtr inner) : Node(move(position)) , m_inner(move(inner)) { + if (m_inner->is_syntax_error()) + set_is_syntax_error(); } DynamicEvaluate::~DynamicEvaluate() @@ -615,7 +624,7 @@ void Fd2FdRedirection::dump(int level) const print_indented(String::format("%d -> %d", source_fd, dest_fd), level); } -RefPtr Fd2FdRedirection::run(TheExecutionInputType) +RefPtr Fd2FdRedirection::run(RefPtr) { Command command; command.redirections.append(*new FdRedirection(source_fd, dest_fd, Rewiring::Close::None)); @@ -644,7 +653,7 @@ void Glob::dump(int level) const print_indented(m_text, level + 1); } -RefPtr Glob::run(TheExecutionInputType) +RefPtr Glob::run(RefPtr) { return create(m_text); } @@ -675,12 +684,11 @@ void Execute::dump(int level) const m_command->dump(level + 1); } -RefPtr Execute::run(TheExecutionInputType input_value) +RefPtr Execute::run(RefPtr shell) { RefPtr job; - auto shell = input_value; - auto initial_commands = m_command->run(input_value)->resolve_as_commands(input_value); + auto initial_commands = m_command->run(shell)->resolve_as_commands(shell); decltype(initial_commands) commands; for (auto& command : initial_commands) { @@ -695,7 +703,7 @@ RefPtr Execute::run(TheExecutionInputType input_value) subcommand_ast = ast->command(); } RefPtr substitute = create(position(), move(subcommand_ast), create(position(), command)); - commands.append(substitute->run(input_value)->resolve_as_commands(input_value)); + commands.append(substitute->run(shell)->resolve_as_commands(shell)); } else { commands.append(command); } @@ -851,6 +859,8 @@ Execute::Execute(Position position, RefPtr command, bool capture_stdout) , m_command(move(command)) , m_capture_stdout(capture_stdout) { + if (m_command->is_syntax_error()) + set_is_syntax_error(); } Execute::~Execute() @@ -864,10 +874,10 @@ void Join::dump(int level) const m_right->dump(level + 1); } -RefPtr Join::run(TheExecutionInputType input_value) +RefPtr Join::run(RefPtr shell) { - auto left = m_left->run(input_value)->resolve_as_commands(input_value); - auto right = m_right->run(input_value)->resolve_as_commands(input_value); + auto left = m_left->run(shell)->resolve_as_commands(shell); + auto right = m_right->run(shell)->resolve_as_commands(shell); return create(join_commands(move(left), move(right))); } @@ -896,6 +906,8 @@ Join::Join(Position position, RefPtr left, RefPtr right) , m_left(move(left)) , m_right(move(right)) { + if (m_left->is_syntax_error() || m_right->is_syntax_error()) + set_is_syntax_error(); } Join::~Join() @@ -909,18 +921,17 @@ void Or::dump(int level) const m_right->dump(level + 1); } -RefPtr Or::run(TheExecutionInputType input_value) +RefPtr Or::run(RefPtr shell) { - auto shell = input_value; - auto left = m_left->run(input_value); + auto left = m_left->run(shell); ASSERT(left->is_job()); auto* job_value = static_cast(left.ptr()); const auto job = job_value->job(); if (!job) { // Something has gone wrong, let's just pretend that the job failed. - return m_right->run(input_value); + return m_right->run(shell); } shell->block_on_job(job); @@ -928,7 +939,7 @@ RefPtr Or::run(TheExecutionInputType input_value) if (job->exit_code() == 0) return job_value; - return m_right->run(input_value); + return m_right->run(shell); } void Or::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) @@ -953,6 +964,8 @@ Or::Or(Position position, RefPtr left, RefPtr right) , m_left(move(left)) , m_right(move(right)) { + if (m_left->is_syntax_error() || m_right->is_syntax_error()) + set_is_syntax_error(); } Or::~Or() @@ -966,10 +979,10 @@ void Pipe::dump(int level) const m_right->dump(level + 1); } -RefPtr Pipe::run(TheExecutionInputType input_value) +RefPtr Pipe::run(RefPtr shell) { - auto left = m_left->run(input_value)->resolve_as_commands(input_value); - auto right = m_right->run(input_value)->resolve_as_commands(input_value); + auto left = m_left->run(shell)->resolve_as_commands(shell); + auto right = m_right->run(shell)->resolve_as_commands(shell); auto last_in_left = left.take_last(); auto first_in_right = right.take_first(); @@ -1012,6 +1025,8 @@ Pipe::Pipe(Position position, RefPtr left, RefPtr right) , m_left(move(left)) , m_right(move(right)) { + if (m_left->is_syntax_error() || m_right->is_syntax_error()) + set_is_syntax_error(); } Pipe::~Pipe() @@ -1067,7 +1082,7 @@ Vector PathRedirectionNode::complete_for_editor(Shel if (corrected_offset > node->text().length()) return {}; - return shell.complete_path(node->text(), corrected_offset); + return shell.complete_path("", node->text(), corrected_offset); } PathRedirectionNode::~PathRedirectionNode() @@ -1081,10 +1096,10 @@ void ReadRedirection::dump(int level) const print_indented(String::format("To %d", m_fd), level + 1); } -RefPtr ReadRedirection::run(TheExecutionInputType input_value) +RefPtr ReadRedirection::run(RefPtr shell) { Command command; - auto path_segments = m_path->run(input_value)->resolve_as_list(input_value); + auto path_segments = m_path->run(shell)->resolve_as_list(shell); StringBuilder builder; builder.join(" ", path_segments); @@ -1108,10 +1123,10 @@ void ReadWriteRedirection::dump(int level) const print_indented(String::format("To/From %d", m_fd), level + 1); } -RefPtr ReadWriteRedirection::run(TheExecutionInputType input_value) +RefPtr ReadWriteRedirection::run(RefPtr shell) { Command command; - auto path_segments = m_path->run(input_value)->resolve_as_list(input_value); + auto path_segments = m_path->run(shell)->resolve_as_list(shell); StringBuilder builder; builder.join(" ", path_segments); @@ -1135,10 +1150,10 @@ void Sequence::dump(int level) const m_right->dump(level + 1); } -RefPtr Sequence::run(TheExecutionInputType input_value) +RefPtr Sequence::run(RefPtr shell) { - auto left = m_left->run(input_value)->resolve_as_commands(input_value); - auto right = m_right->run(input_value)->resolve_as_commands(input_value); + auto left = m_left->run(shell)->resolve_as_commands(shell); + auto right = m_right->run(shell)->resolve_as_commands(shell); Vector commands; commands.append(left); @@ -1169,6 +1184,8 @@ Sequence::Sequence(Position position, RefPtr left, RefPtr right) , m_left(move(left)) , m_right(move(right)) { + if (m_left->is_syntax_error() || m_right->is_syntax_error()) + set_is_syntax_error(); } Sequence::~Sequence() @@ -1181,7 +1198,7 @@ void SimpleVariable::dump(int level) const print_indented(m_name, level + 1); } -RefPtr SimpleVariable::run(TheExecutionInputType) +RefPtr SimpleVariable::run(RefPtr) { return create(m_name); } @@ -1234,7 +1251,7 @@ void SpecialVariable::dump(int level) const print_indented(String { &m_name, 1 }, level + 1); } -RefPtr SpecialVariable::run(TheExecutionInputType) +RefPtr SpecialVariable::run(RefPtr) { return create(m_name); } @@ -1274,13 +1291,13 @@ void Juxtaposition::dump(int level) const m_right->dump(level + 1); } -RefPtr Juxtaposition::run(TheExecutionInputType input_value) +RefPtr Juxtaposition::run(RefPtr shell) { - auto left_value = m_left->run(input_value)->resolve_without_cast(input_value); - auto right_value = m_right->run(input_value)->resolve_without_cast(input_value); + auto left_value = m_left->run(shell)->resolve_without_cast(shell); + auto right_value = m_right->run(shell)->resolve_without_cast(shell); - auto left = left_value->resolve_as_list(input_value); - auto right = right_value->resolve_as_list(input_value); + auto left = left_value->resolve_as_list(shell); + auto right = right_value->resolve_as_list(shell); if (left_value->is_string() && right_value->is_string()) { @@ -1318,9 +1335,50 @@ void Juxtaposition::highlight_in_editor(Line::Editor& editor, Shell& shell, High { m_left->highlight_in_editor(editor, shell, metadata); - // Do not highlight '/foo/bar' in '~/foo/bar' - if (!(m_right->is_bareword() && m_left->is_tilde())) + // '~/foo/bar' is special, we have to actually resolve the tilde + // since that resolution is a pure operation, we can just go ahead + // and do it to get the value :) + if (m_right->is_bareword() && m_left->is_tilde()) { + auto tilde_value = m_left->run(shell)->resolve_as_list(shell)[0]; + auto bareword_value = m_right->run(shell)->resolve_as_list(shell)[0]; + + StringBuilder path_builder; + path_builder.append(tilde_value); + path_builder.append("/"); + path_builder.append(bareword_value); + auto path = path_builder.to_string(); + + if (Core::File::exists(path)) { + auto realpath = shell.resolve_path(path); + auto url = URL::create_with_file_protocol(realpath); + url.set_host(shell.hostname); + editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Hyperlink(url.to_string()) }); + } + + } else { m_right->highlight_in_editor(editor, shell, metadata); + } +} + +Vector Juxtaposition::complete_for_editor(Shell& shell, size_t offset, RefPtr 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()) { + auto tilde_value = m_left->run(shell)->resolve_as_list(shell)[0]; + + auto corrected_offset = offset - matching_node->position().start_offset; + auto* node = static_cast(matching_node.ptr()); + + if (corrected_offset > node->text().length()) + return {}; + + auto text = node->text().substring(1, node->text().length() - 1); + + return shell.complete_path(tilde_value, text, corrected_offset - 1); + } + + return Node::complete_for_editor(shell, offset, matching_node); } HitTestResult Juxtaposition::hit_test_position(size_t offset) @@ -1329,9 +1387,15 @@ HitTestResult Juxtaposition::hit_test_position(size_t offset) return {}; auto result = m_left->hit_test_position(offset); + if (!result.closest_node_with_semantic_meaning) + result.closest_node_with_semantic_meaning = this; if (result.matching_node) return result; - return m_right->hit_test_position(offset); + + result = m_right->hit_test_position(offset); + if (!result.closest_node_with_semantic_meaning) + result.closest_node_with_semantic_meaning = this; + return result; } Juxtaposition::Juxtaposition(Position position, RefPtr left, RefPtr right) @@ -1339,6 +1403,8 @@ Juxtaposition::Juxtaposition(Position position, RefPtr left, RefPtr , m_left(move(left)) , m_right(move(right)) { + if (m_left->is_syntax_error() || m_right->is_syntax_error()) + set_is_syntax_error(); } Juxtaposition::~Juxtaposition() @@ -1351,7 +1417,7 @@ void StringLiteral::dump(int level) const print_indented(m_text, level + 1); } -RefPtr StringLiteral::run(TheExecutionInputType) +RefPtr StringLiteral::run(RefPtr) { return create(m_text); } @@ -1381,10 +1447,10 @@ void StringPartCompose::dump(int level) const m_right->dump(level + 1); } -RefPtr StringPartCompose::run(TheExecutionInputType input_value) +RefPtr StringPartCompose::run(RefPtr shell) { - auto left = m_left->run(input_value)->resolve_as_list(input_value); - auto right = m_right->run(input_value)->resolve_as_list(input_value); + auto left = m_left->run(shell)->resolve_as_list(shell); + auto right = m_right->run(shell)->resolve_as_list(shell); StringBuilder builder; builder.join(" ", left); @@ -1415,6 +1481,8 @@ StringPartCompose::StringPartCompose(Position position, RefPtr left, RefPt , m_left(move(left)) , m_right(move(right)) { + if (m_left->is_syntax_error() || m_right->is_syntax_error()) + set_is_syntax_error(); } StringPartCompose::~StringPartCompose() @@ -1426,7 +1494,7 @@ void SyntaxError::dump(int level) const Node::dump(level); } -RefPtr SyntaxError::run(TheExecutionInputType) +RefPtr SyntaxError::run(RefPtr) { dbg() << "SYNTAX ERROR AAAA"; return create(""); @@ -1440,6 +1508,7 @@ void SyntaxError::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMet SyntaxError::SyntaxError(Position position) : Node(move(position)) { + set_is_syntax_error(); } SyntaxError::~SyntaxError() @@ -1452,14 +1521,13 @@ void Tilde::dump(int level) const print_indented(m_username, level + 1); } -RefPtr Tilde::run(TheExecutionInputType) +RefPtr Tilde::run(RefPtr) { return create(m_username); } -void Tilde::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMetadata) +void Tilde::highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata) { - editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Cyan) }); } HitTestResult Tilde::hit_test_position(size_t offset) @@ -1470,9 +1538,20 @@ HitTestResult Tilde::hit_test_position(size_t offset) return { this, this }; } -Vector Tilde::complete_for_editor(Shell&, size_t, RefPtr) +Vector Tilde::complete_for_editor(Shell& shell, size_t offset, RefPtr matching_node) { - return {}; + if (!matching_node) + return {}; + + if (matching_node != this) + return {}; + + auto corrected_offset = offset - matching_node->position().start_offset - 1; + + if (corrected_offset > m_username.length() + 1) + return {}; + + return shell.complete_user(m_username, corrected_offset); } String Tilde::text() const @@ -1500,10 +1579,10 @@ void WriteAppendRedirection::dump(int level) const print_indented(String::format("From %d", m_fd), level + 1); } -RefPtr WriteAppendRedirection::run(TheExecutionInputType input_value) +RefPtr WriteAppendRedirection::run(RefPtr shell) { Command command; - auto path_segments = m_path->run(input_value)->resolve_as_list(input_value); + auto path_segments = m_path->run(shell)->resolve_as_list(shell); StringBuilder builder; builder.join(" ", path_segments); @@ -1527,10 +1606,10 @@ void WriteRedirection::dump(int level) const print_indented(String::format("From %d", m_fd), level + 1); } -RefPtr WriteRedirection::run(TheExecutionInputType input_value) +RefPtr WriteRedirection::run(RefPtr shell) { Command command; - auto path_segments = m_path->run(input_value)->resolve_as_list(input_value); + auto path_segments = m_path->run(shell)->resolve_as_list(shell); StringBuilder builder; builder.join(" ", path_segments); @@ -1557,21 +1636,20 @@ void VariableDeclarations::dump(int level) const } } -RefPtr VariableDeclarations::run(TheExecutionInputType input_value) +RefPtr VariableDeclarations::run(RefPtr shell) { - auto shell = input_value; for (auto& var : m_variables) { - auto name_value = var.name->run(input_value)->resolve_as_list(input_value); + auto name_value = var.name->run(shell)->resolve_as_list(shell); ASSERT(name_value.size() == 1); auto name = name_value[0]; - auto value = var.value->run(input_value); + auto value = var.value->run(shell); if (value->is_list()) { - auto parts = value->resolve_as_list(input_value); + auto parts = value->resolve_as_list(shell); shell->set_local_variable(name, adopt(*new ListValue(move(parts)))); } else if (value->is_command()) { shell->set_local_variable(name, value); } else { - auto part = value->resolve_as_list(input_value); + auto part = value->resolve_as_list(shell); shell->set_local_variable(name, adopt(*new StringValue(part[0]))); } } @@ -1608,6 +1686,12 @@ VariableDeclarations::VariableDeclarations(Position position, Vector v : Node(move(position)) , m_variables(move(variables)) { + for (auto& decl : m_variables) { + if (decl.name->is_syntax_error() || decl.value->is_syntax_error()) { + set_is_syntax_error(); + break; + } + } } VariableDeclarations::~VariableDeclarations() @@ -1617,10 +1701,10 @@ VariableDeclarations::~VariableDeclarations() Value::~Value() { } -Vector Value::resolve_as_commands(TheExecutionInputType input_value) +Vector Value::resolve_as_commands(RefPtr shell) { Command command; - command.argv = resolve_as_list(input_value); + command.argv = resolve_as_list(shell); return { command }; } @@ -1635,11 +1719,11 @@ ListValue::~ListValue() { } -Vector ListValue::resolve_as_list(TheExecutionInputType input_value) +Vector ListValue::resolve_as_list(RefPtr shell) { Vector values; for (auto& value : m_contained_values) - values.append(value->resolve_as_list(input_value)); + values.append(value->resolve_as_list(shell)); return values; } @@ -1652,24 +1736,24 @@ CommandSequenceValue::~CommandSequenceValue() { } -Vector CommandSequenceValue::resolve_as_list(TheExecutionInputType) +Vector CommandSequenceValue::resolve_as_list(RefPtr) { // TODO: Somehow raise an "error". return {}; } -Vector CommandSequenceValue::resolve_as_commands(TheExecutionInputType) +Vector CommandSequenceValue::resolve_as_commands(RefPtr) { return m_contained_values; } -Vector CommandValue::resolve_as_list(TheExecutionInputType) +Vector CommandValue::resolve_as_list(RefPtr) { // TODO: Somehow raise an "error". return {}; } -Vector CommandValue::resolve_as_commands(TheExecutionInputType) +Vector CommandValue::resolve_as_commands(RefPtr) { return { m_command }; } @@ -1681,7 +1765,7 @@ JobValue::~JobValue() StringValue::~StringValue() { } -Vector StringValue::resolve_as_list(TheExecutionInputType) +Vector StringValue::resolve_as_list(RefPtr) { if (is_list()) { auto parts = StringView(m_string).split_view(m_split); @@ -1698,19 +1782,18 @@ Vector StringValue::resolve_as_list(TheExecutionInputType) GlobValue::~GlobValue() { } -Vector GlobValue::resolve_as_list(TheExecutionInputType input_value) +Vector GlobValue::resolve_as_list(RefPtr shell) { - auto shell = input_value; return shell->expand_globs(m_glob, shell->cwd); } SimpleVariableValue::~SimpleVariableValue() { } -Vector SimpleVariableValue::resolve_as_list(TheExecutionInputType input_value) +Vector SimpleVariableValue::resolve_as_list(RefPtr shell) { - if (auto value = resolve_without_cast(input_value); value != this) - return value->resolve_as_list(input_value); + if (auto value = resolve_without_cast(shell); value != this) + return value->resolve_as_list(shell); char* env_value = getenv(m_name.characters()); if (env_value == nullptr) @@ -1724,9 +1807,8 @@ Vector SimpleVariableValue::resolve_as_list(TheExecutionInputType input_ return res; } -RefPtr SimpleVariableValue::resolve_without_cast(TheExecutionInputType input_value) +RefPtr SimpleVariableValue::resolve_without_cast(RefPtr shell) { - auto shell = input_value; if (auto value = shell->lookup_local_variable(m_name)) return value; @@ -1737,9 +1819,8 @@ RefPtr SimpleVariableValue::resolve_without_cast(TheExecutionInputType in SpecialVariableValue::~SpecialVariableValue() { } -Vector SpecialVariableValue::resolve_as_list(TheExecutionInputType input_value) +Vector SpecialVariableValue::resolve_as_list(RefPtr shell) { - auto shell = input_value; switch (m_name) { case '?': return { String::number(shell->last_return_code) }; @@ -1753,9 +1834,8 @@ Vector SpecialVariableValue::resolve_as_list(TheExecutionInputType input TildeValue::~TildeValue() { } -Vector TildeValue::resolve_as_list(TheExecutionInputType input_value) +Vector TildeValue::resolve_as_list(RefPtr shell) { - auto shell = input_value; StringBuilder builder; builder.append("~"); builder.append(m_username); diff --git a/Shell/AST.h b/Shell/AST.h index 9094437bad..581a85ed89 100644 --- a/Shell/AST.h +++ b/Shell/AST.h @@ -36,8 +36,6 @@ #include #include -using TheExecutionInputType = RefPtr; - namespace AST { struct HighlightMetadata { @@ -156,9 +154,9 @@ struct HitTestResult { class Value : public RefCounted { public: - virtual Vector resolve_as_list(TheExecutionInputType) = 0; - virtual Vector resolve_as_commands(TheExecutionInputType); - virtual RefPtr resolve_without_cast(TheExecutionInputType) { return this; } + virtual Vector resolve_as_list(RefPtr) = 0; + virtual Vector resolve_as_commands(RefPtr); + virtual RefPtr resolve_without_cast(RefPtr) { return this; } virtual ~Value(); virtual bool is_command() const { return false; } virtual bool is_glob() const { return false; } @@ -169,8 +167,8 @@ public: class CommandValue final : public Value { public: - virtual Vector resolve_as_list(TheExecutionInputType) override; - virtual Vector resolve_as_commands(TheExecutionInputType) override; + virtual Vector resolve_as_list(RefPtr) override; + virtual Vector resolve_as_commands(RefPtr) override; virtual ~CommandValue(); virtual bool is_command() const override { return true; } CommandValue(Command command) @@ -189,8 +187,8 @@ private: class CommandSequenceValue final : public Value { public: - virtual Vector resolve_as_list(TheExecutionInputType) override; - virtual Vector resolve_as_commands(TheExecutionInputType) override; + virtual Vector resolve_as_list(RefPtr) override; + virtual Vector resolve_as_commands(RefPtr) override; virtual ~CommandSequenceValue(); virtual bool is_command() const override { return true; } CommandSequenceValue(Vector commands) @@ -204,8 +202,8 @@ private: class JobValue final : public Value { public: - virtual Vector resolve_as_list(TheExecutionInputType) override { ASSERT_NOT_REACHED(); } - virtual Vector resolve_as_commands(TheExecutionInputType) override { ASSERT_NOT_REACHED(); } + virtual Vector resolve_as_list(RefPtr) override { ASSERT_NOT_REACHED(); } + virtual Vector resolve_as_commands(RefPtr) override { ASSERT_NOT_REACHED(); } virtual ~JobValue(); virtual bool is_job() const override { return true; } JobValue(RefPtr job) @@ -221,7 +219,7 @@ private: class ListValue final : public Value { public: - virtual Vector resolve_as_list(TheExecutionInputType) override; + virtual Vector resolve_as_list(RefPtr) override; virtual ~ListValue(); virtual bool is_list() const override { return true; } ListValue(Vector values); @@ -236,7 +234,7 @@ private: class StringValue final : public Value { public: - virtual Vector resolve_as_list(TheExecutionInputType) override; + virtual Vector resolve_as_list(RefPtr) override; virtual ~StringValue(); virtual bool is_string() const override { return m_split.is_null(); } virtual bool is_list() const override { return !m_split.is_null(); } @@ -253,7 +251,7 @@ private: class GlobValue final : public Value { public: - virtual Vector resolve_as_list(TheExecutionInputType) override; + virtual Vector resolve_as_list(RefPtr) override; virtual ~GlobValue(); virtual bool is_glob() const override { return true; } GlobValue(String glob) @@ -267,8 +265,8 @@ private: class SimpleVariableValue final : public Value { public: - virtual Vector resolve_as_list(TheExecutionInputType) override; - RefPtr resolve_without_cast(TheExecutionInputType) override; + virtual Vector resolve_as_list(RefPtr) override; + RefPtr resolve_without_cast(RefPtr) override; virtual ~SimpleVariableValue(); SimpleVariableValue(String name) : m_name(name) @@ -281,7 +279,7 @@ private: class SpecialVariableValue final : public Value { public: - virtual Vector resolve_as_list(TheExecutionInputType) override; + virtual Vector resolve_as_list(RefPtr) override; virtual ~SpecialVariableValue(); SpecialVariableValue(char name) : m_name(name) @@ -294,7 +292,7 @@ private: class TildeValue final : public Value { public: - virtual Vector resolve_as_list(TheExecutionInputType) override; + virtual Vector resolve_as_list(RefPtr) override; virtual ~TildeValue(); virtual bool is_string() const override { return true; } TildeValue(String name) @@ -308,8 +306,8 @@ private: class Node : public RefCounted { public: - virtual void dump(int level) = const 0; - virtual RefPtr run(TheExecutionInputType) = 0; + virtual void dump(int level) const = 0; + virtual RefPtr run(RefPtr) = 0; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) = 0; virtual Vector complete_for_editor(Shell&, size_t, RefPtr matching_node); Vector complete_for_editor(Shell& shell, size_t offset); @@ -329,14 +327,16 @@ public: virtual bool is_glob() const { return false; } virtual bool is_tilde() const { return false; } virtual bool is_variable_decls() const { return false; } - virtual bool is_syntax_error() const { return false; } + virtual bool is_syntax_error() const { return m_is_syntax_error; } virtual bool is_list() const { return false; } const Position& position() const { return m_position; } + void set_is_syntax_error() { m_is_syntax_error = true; } protected: Position m_position; + bool m_is_syntax_error { false }; }; class PathRedirectionNode : public Node { @@ -361,7 +361,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual HitTestResult hit_test_position(size_t) override; virtual String class_name() const override { return "And"; } @@ -377,6 +377,7 @@ public: private: virtual void dump(int level) const override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual HitTestResult hit_test_position(size_t) override; virtual String class_name() const override { return "ListConcatenate"; } @@ -393,7 +394,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual HitTestResult hit_test_position(size_t) override; virtual String class_name() const override { return "Background"; } @@ -409,7 +410,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; 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; } @@ -424,7 +425,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual HitTestResult hit_test_position(size_t) override; virtual Vector complete_for_editor(Shell&, size_t, RefPtr matching_node) override; @@ -442,7 +443,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual HitTestResult hit_test_position(size_t) override; virtual String class_name() const override { return "CastToList"; } @@ -458,7 +459,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual String class_name() const override { return "CloseFdRedirection"; } virtual bool is_command() const override { return true; } @@ -473,7 +474,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override { ASSERT_NOT_REACHED(); } virtual String class_name() const override { return "CommandLiteral"; } virtual bool is_command() const override { return true; } @@ -490,7 +491,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual String class_name() const override { return "Comment"; } @@ -504,7 +505,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual HitTestResult hit_test_position(size_t) override; virtual String class_name() const override { return "DynamicEvaluate"; } @@ -528,7 +529,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual HitTestResult hit_test_position(size_t) override; virtual String class_name() const override { return "DoubleQuotedString"; } @@ -543,7 +544,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual String class_name() const override { return "Fd2FdRedirection"; } virtual bool is_command() const override { return true; } @@ -560,7 +561,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual String class_name() const override { return "Glob"; } virtual bool is_glob() const override { return true; } @@ -578,7 +579,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual HitTestResult hit_test_position(size_t) override; virtual Vector complete_for_editor(Shell&, size_t, RefPtr matching_node) override; @@ -596,7 +597,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual HitTestResult hit_test_position(size_t) override; virtual String class_name() const override { return "Join"; } @@ -614,7 +615,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual HitTestResult hit_test_position(size_t) override; virtual String class_name() const override { return "Or"; } @@ -631,7 +632,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual HitTestResult hit_test_position(size_t) override; virtual String class_name() const override { return "Pipe"; } @@ -648,7 +649,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual String class_name() const override { return "ReadRedirection"; } }; @@ -659,7 +660,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual String class_name() const override { return "ReadWriteRedirection"; } }; @@ -670,7 +671,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual HitTestResult hit_test_position(size_t) override; virtual String class_name() const override { return "Sequence"; } @@ -687,7 +688,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual Vector complete_for_editor(Shell&, size_t, RefPtr matching_node) override; virtual HitTestResult hit_test_position(size_t) override; @@ -703,7 +704,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual Vector complete_for_editor(Shell&, size_t, RefPtr matching_node) override; virtual HitTestResult hit_test_position(size_t) override; @@ -719,9 +720,10 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual HitTestResult hit_test_position(size_t) override; + virtual Vector complete_for_editor(Shell&, size_t, RefPtr matching_node) override; virtual String class_name() const override { return "Juxtaposition"; } RefPtr m_left; @@ -736,7 +738,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual String class_name() const override { return "StringLiteral"; } @@ -750,7 +752,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual HitTestResult hit_test_position(size_t) override; virtual String class_name() const override { return "StringPartCompose"; } @@ -766,7 +768,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual HitTestResult hit_test_position(size_t) override { return { nullptr, nullptr }; } virtual String class_name() const override { return "SyntaxError"; } @@ -781,7 +783,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual Vector complete_for_editor(Shell&, size_t, RefPtr matching_node) override; virtual HitTestResult hit_test_position(size_t) override; @@ -804,7 +806,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual HitTestResult hit_test_position(size_t) override; virtual String class_name() const override { return "VariableDeclarations"; } @@ -820,7 +822,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual String class_name() const override { return "WriteAppendRedirection"; } }; @@ -831,7 +833,7 @@ public: private: virtual void dump(int level) const override; - virtual RefPtr run(TheExecutionInputType) override; + virtual RefPtr run(RefPtr) override; virtual String class_name() const override { return "WriteRedirection"; } }; diff --git a/Shell/Parser.cpp b/Shell/Parser.cpp index c06c51577c..ef7d23c79b 100644 --- a/Shell/Parser.cpp +++ b/Shell/Parser.cpp @@ -112,7 +112,16 @@ RefPtr Parser::parse() { m_offset = 0; - return parse_toplevel(); + auto toplevel = parse_toplevel(); + + if (m_offset < m_input.length()) { + // Parsing stopped midway, this is a syntax error. + auto error_start = push_start(); + m_offset = m_input.length(); + return create(move(toplevel), create()); + } + + return toplevel; } RefPtr Parser::parse_toplevel() @@ -207,9 +216,10 @@ RefPtr Parser::parse_variable_decls() if (peek() == '(') { consume(); auto command = parse_pipe_sequence(); - expect(')'); if (!command) m_offset = start->offset; + else if (!expect(')')) + command->set_is_syntax_error(); expression = command; } } @@ -504,18 +514,26 @@ RefPtr Parser::parse_string() if (peek() == '"') { consume(); auto inner = parse_doublequoted_string_inner(); - if (!expect('"') || !inner) - return create(); + if (!inner) + inner = create(); + if (!expect('"')) { + inner = create(move(inner)); + inner->set_is_syntax_error(); + return inner; + } return create(move(inner)); // Double Quoted String } if (peek() == '\'') { consume(); auto text = consume_while(is_not('\'')); + bool is_error = false; if (!expect('\'')) - return create(); - - return create(move(text)); // String Literal + is_error = true; + auto result = create(move(text)); // String Literal + if (is_error) + result->set_is_syntax_error(); + return move(result); } return nullptr; @@ -648,11 +666,11 @@ RefPtr Parser::parse_evaluate() if (peek() == '(') { consume(); auto inner = parse_pipe_sequence(); - if (!inner || !expect(')')) + if (!inner) inner = create(); - else - inner = create(move(inner), true); - return inner; + if (!expect(')')) + inner->set_is_syntax_error(); + return create(move(inner), true); } auto inner = parse_expression(); @@ -718,9 +736,12 @@ RefPtr Parser::parse_bareword() if (builder.is_empty()) return nullptr; + auto current_end = m_offset; auto string = builder.to_string(); if (string.starts_with('~')) { String username; + RefPtr tilde, text; + auto first_slash_index = string.index_of("/"); if (first_slash_index.has_value()) { username = string.substring_view(1, first_slash_index.value() - 1); @@ -729,21 +750,33 @@ RefPtr Parser::parse_bareword() username = string.substring_view(1, string.length() - 1); string = ""; } - auto current_end = m_offset; - m_offset -= string.length(); - auto tilde = create(move(username)); - auto text_start = push_start(); - m_offset = current_end; + + // Synthesize a Tilde Node with the correct positioning information. + { + m_offset -= string.length(); + tilde = create(move(username)); + } + if (string.is_empty()) return tilde; - return create(move(tilde), create(move(string))); // Compose Varible Bareword + + // Synthesize a BarewordLiteral Node with the correct positioning information. + { + m_offset = tilde->position().end_offset; + auto text_start = push_start(); + m_offset = current_end; + text = create(move(string)); + } + + return create(move(tilde), move(text)); // Juxtaposition Varible Bareword } if (string.starts_with("\\~")) { // Un-escape the tilde, but only at the start (where it would be an expansion) + string = string.substring(1, string.length() - 1); } - return create(builder.to_string()); // Bareword Literal + return create(move(string)); // Bareword Literal } RefPtr Parser::parse_glob() @@ -757,17 +790,17 @@ RefPtr Parser::parse_glob() char ch = peek(); if (ch == '*' || ch == '?') { consume(); - // FIXME: Join all parts before making AST nodes StringBuilder textbuilder; if (bareword_part) { - ASSERT(bareword_part->is_bareword() || bareword_part->is_tilde()); StringView text; - if (bareword_part->is_tilde()) { + if (bareword_part->is_bareword()) { auto bareword = static_cast(bareword_part.ptr()); text = bareword->text(); } else { - auto tilde = static_cast(bareword_part.ptr()); - text = tilde->text(); + // FIXME: Allow composition of tilde+bareword with globs: '~/foo/bar/baz*' + putback(); + bareword_part->set_is_syntax_error(); + return bareword_part; } textbuilder.append(text); } diff --git a/Shell/Shell.cpp b/Shell/Shell.cpp index e18f1995bb..398d8fe631 100644 --- a/Shell/Shell.cpp +++ b/Shell/Shell.cpp @@ -324,6 +324,12 @@ int Shell::run_command(const StringView& cmd) if (!command) return 0; + if (command->is_syntax_error()) { + // FIXME: Provide descriptive messages for syntax errors. + fprintf(stderr, "Shell: Syntax error in command\n"); + return 1; + } + #ifdef SH_DEBUG dbg() << "Command follows"; command->dump(0); @@ -664,9 +670,9 @@ Vector Shell::complete(const Line::Editor& editor) return ast->complete_for_editor(*this, line.length()); } -Vector Shell::complete_path(const String& part, size_t offset) +Vector Shell::complete_path(const String& base, const String& part, size_t offset) { - auto token = part.substring_view(0, offset); + auto token = offset ? part.substring_view(0, offset) : ""; StringView original_token = token; String path; @@ -674,19 +680,30 @@ Vector Shell::complete_path(const String& part, size while (last_slash >= 0 && token[last_slash] != '/') --last_slash; - if (last_slash >= 0) { - // Split on the last slash. We'll use the first part as the directory - // to search and the second part as the token to complete. - path = token.substring_view(0, last_slash + 1); - if (path[0] != '/') - path = String::format("%s/%s", cwd.characters(), path.characters()); - path = LexicalPath::canonicalized_path(path); - token = token.substring_view(last_slash + 1, token.length() - last_slash - 1); + StringBuilder path_builder; + auto init_slash_part = token.substring_view(0, last_slash + 1); + auto last_slash_part = token.substring_view(last_slash + 1, token.length() - last_slash - 1); + + // Depending on the base, we will have to prepend cwd. + if (base.is_empty()) { + // '' /foo -> absolute + // '' foo -> relative + if (!token.starts_with('/')) + path_builder.append(cwd); + path_builder.append('/'); + path_builder.append(init_slash_part); } else { - // We have no slashes, so the directory to search is the current - // directory and the token to complete is just the original token. - path = cwd; + // /foo * -> absolute + // foo * -> relative + if (!base.starts_with('/')) + path_builder.append(cwd); + path_builder.append('/'); + path_builder.append(base); + path_builder.append('/'); + path_builder.append(init_slash_part); } + path = path_builder.build(); + token = last_slash_part; // the invariant part of the token is actually just the last segment // e. in `cd /foo/bar', 'bar' is the invariant @@ -727,7 +744,7 @@ Vector Shell::complete_program_name(const String& na }); if (!match) - return complete_path(name, offset); + return complete_path("", name, offset); String completion = *match; editor->suggest(escape_token(name).length(), 0); @@ -753,7 +770,7 @@ Vector Shell::complete_program_name(const String& na Vector Shell::complete_variable(const String& name, size_t offset) { Vector suggestions; - auto pattern = name.substring_view(0, offset); + auto pattern = offset ? name.substring_view(0, offset) : ""; editor->suggest(offset); @@ -780,6 +797,27 @@ Vector Shell::complete_variable(const String& name, return suggestions; } +Vector Shell::complete_user(const String& name, size_t offset) +{ + Vector suggestions; + auto pattern = offset ? name.substring_view(0, offset) : ""; + + editor->suggest(offset); + + Core::DirIterator di("/home", Core::DirIterator::SkipParentAndBaseDir); + + if (di.has_error()) + return suggestions; + + while (di.has_next()) { + String name = di.next_path(); + if (name.starts_with(pattern)) + suggestions.append(name); + } + + return suggestions; +} + bool Shell::read_single_line() { take_back_stdin(); diff --git a/Shell/Shell.h b/Shell/Shell.h index 6be974027b..18b1c275a7 100644 --- a/Shell/Shell.h +++ b/Shell/Shell.h @@ -92,9 +92,10 @@ public: void highlight(Line::Editor&) const; Vector complete(const Line::Editor&); - Vector complete_path(const String&, size_t offset); + Vector complete_path(const String& base, const String&, size_t offset); Vector complete_program_name(const String&, size_t offset); Vector complete_variable(const String&, size_t offset); + Vector complete_user(const String&, size_t offset); void take_back_stdin();