Serenity Operating System
at master 237 lines 9.2 kB view raw
1/* 2 * Copyright (c) 2020, the SerenityOS developers. 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include "ShellComprehensionEngine.h" 8#include <AK/Assertions.h> 9#include <AK/HashTable.h> 10#include <LibRegex/Regex.h> 11 12namespace CodeComprehension::Shell { 13 14RefPtr<::Shell::Shell> ShellComprehensionEngine::s_shell {}; 15 16ShellComprehensionEngine::ShellComprehensionEngine(FileDB const& filedb) 17 : CodeComprehensionEngine(filedb, true) 18{ 19} 20 21ShellComprehensionEngine::DocumentData const& ShellComprehensionEngine::get_or_create_document_data(DeprecatedString const& file) 22{ 23 auto absolute_path = filedb().to_absolute_path(file); 24 if (!m_documents.contains(absolute_path)) { 25 set_document_data(absolute_path, create_document_data_for(absolute_path)); 26 } 27 return get_document_data(absolute_path); 28} 29 30ShellComprehensionEngine::DocumentData const& ShellComprehensionEngine::get_document_data(DeprecatedString const& file) const 31{ 32 auto absolute_path = filedb().to_absolute_path(file); 33 auto document_data = m_documents.get(absolute_path); 34 VERIFY(document_data.has_value()); 35 return *document_data.value(); 36} 37 38OwnPtr<ShellComprehensionEngine::DocumentData> ShellComprehensionEngine::create_document_data_for(DeprecatedString const& file) 39{ 40 auto document = filedb().get_or_read_from_filesystem(file); 41 if (!document.has_value()) 42 return {}; 43 44 auto content = document.value(); 45 auto document_data = make<DocumentData>(move(content), file); 46 for (auto& path : document_data->sourced_paths()) 47 get_or_create_document_data(path); 48 49 update_declared_symbols(*document_data); 50 return document_data; 51} 52 53void ShellComprehensionEngine::set_document_data(DeprecatedString const& file, OwnPtr<DocumentData>&& data) 54{ 55 m_documents.set(filedb().to_absolute_path(file), move(data)); 56} 57 58ShellComprehensionEngine::DocumentData::DocumentData(DeprecatedString&& _text, DeprecatedString _filename) 59 : filename(move(_filename)) 60 , text(move(_text)) 61 , node(parse()) 62{ 63} 64 65Vector<DeprecatedString> const& ShellComprehensionEngine::DocumentData::sourced_paths() const 66{ 67 if (all_sourced_paths.has_value()) 68 return all_sourced_paths.value(); 69 70 struct : public ::Shell::AST::NodeVisitor { 71 void visit(::Shell::AST::CastToCommand const* node) override 72 { 73 auto& inner = node->inner(); 74 if (inner->is_list()) { 75 if (auto* list = dynamic_cast<::Shell::AST::ListConcatenate const*>(inner.ptr())) { 76 auto& entries = list->list(); 77 if (entries.size() == 2 && entries.first()->is_bareword() && static_ptr_cast<::Shell::AST::BarewordLiteral>(entries.first())->text() == "source") { 78 auto& filename = entries[1]; 79 if (filename->would_execute()) 80 return; 81 auto name_list_node = const_cast<::Shell::AST::Node*>(filename.ptr())->run(nullptr).release_value_but_fixme_should_propagate_errors(); 82 auto name_list = name_list_node->resolve_as_list(nullptr).release_value_but_fixme_should_propagate_errors(); 83 StringBuilder builder; 84 builder.join(' ', name_list); 85 sourced_files.set(builder.to_deprecated_string()); 86 } 87 } 88 } 89 ::Shell::AST::NodeVisitor::visit(node); 90 } 91 92 HashTable<DeprecatedString> sourced_files; 93 } visitor; 94 95 node->visit(visitor); 96 97 Vector<DeprecatedString> sourced_paths; 98 for (auto& entry : visitor.sourced_files) 99 sourced_paths.append(move(entry)); 100 101 all_sourced_paths = move(sourced_paths); 102 return all_sourced_paths.value(); 103} 104 105NonnullRefPtr<::Shell::AST::Node> ShellComprehensionEngine::DocumentData::parse() const 106{ 107 ::Shell::Parser parser { text }; 108 if (auto node = parser.parse()) 109 return node.release_nonnull(); 110 111 return ::Shell::AST::make_ref_counted<::Shell::AST::SyntaxError>(::Shell::AST::Position {}, "Unable to parse file"_string.release_value_but_fixme_should_propagate_errors()); 112} 113 114size_t ShellComprehensionEngine::resolve(ShellComprehensionEngine::DocumentData const& document, const GUI::TextPosition& position) 115{ 116 size_t offset = 0; 117 118 if (position.line() > 0) { 119 auto first = true; 120 size_t line = 0; 121 for (auto& line_view : document.text.split_limit('\n', position.line() + 1, SplitBehavior::KeepEmpty)) { 122 if (line == position.line()) 123 break; 124 if (first) 125 first = false; 126 else 127 ++offset; // For the newline. 128 offset += line_view.length(); 129 ++line; 130 } 131 } 132 133 offset += position.column() + 1; 134 return offset; 135} 136 137Vector<CodeComprehension::AutocompleteResultEntry> ShellComprehensionEngine::get_suggestions(DeprecatedString const& file, const GUI::TextPosition& position) 138{ 139 dbgln_if(SH_LANGUAGE_SERVER_DEBUG, "ShellComprehensionEngine position {}:{}", position.line(), position.column()); 140 141 auto const& document = get_or_create_document_data(file); 142 size_t offset_in_file = resolve(document, position); 143 144 ::Shell::AST::HitTestResult hit_test = document.node->hit_test_position(offset_in_file); 145 if (!hit_test.matching_node) { 146 dbgln_if(SH_LANGUAGE_SERVER_DEBUG, "no node at position {}:{}", position.line(), position.column()); 147 return {}; 148 } 149 150 auto completions = const_cast<::Shell::AST::Node*>(document.node.ptr())->complete_for_editor(shell(), offset_in_file, hit_test).release_value_but_fixme_should_propagate_errors(); 151 Vector<CodeComprehension::AutocompleteResultEntry> entries; 152 for (auto& completion : completions) 153 entries.append({ completion.text_string, completion.input_offset }); 154 155 return entries; 156} 157 158void ShellComprehensionEngine::on_edit(DeprecatedString const& file) 159{ 160 set_document_data(file, create_document_data_for(file)); 161} 162 163void ShellComprehensionEngine::file_opened([[maybe_unused]] DeprecatedString const& file) 164{ 165 set_document_data(file, create_document_data_for(file)); 166} 167 168Optional<CodeComprehension::ProjectLocation> ShellComprehensionEngine::find_declaration_of(DeprecatedString const& filename, const GUI::TextPosition& identifier_position) 169{ 170 dbgln_if(SH_LANGUAGE_SERVER_DEBUG, "find_declaration_of({}, {}:{})", filename, identifier_position.line(), identifier_position.column()); 171 auto const& document = get_or_create_document_data(filename); 172 auto position = resolve(document, identifier_position); 173 auto result = document.node->hit_test_position(position); 174 if (!result.matching_node) { 175 dbgln_if(SH_LANGUAGE_SERVER_DEBUG, "no node at position {}:{}", identifier_position.line(), identifier_position.column()); 176 return {}; 177 } 178 179 if (!result.matching_node->is_bareword()) { 180 dbgln_if(SH_LANGUAGE_SERVER_DEBUG, "no bareword at position {}:{}", identifier_position.line(), identifier_position.column()); 181 return {}; 182 } 183 184 auto name = static_ptr_cast<::Shell::AST::BarewordLiteral const>(result.matching_node)->text(); 185 auto& declarations = all_declarations(); 186 for (auto& entry : declarations) { 187 for (auto& declaration : entry.value) { 188 if (declaration.name.view() == name) 189 return declaration.position; 190 } 191 } 192 193 return {}; 194} 195 196void ShellComprehensionEngine::update_declared_symbols(DocumentData const& document) 197{ 198 struct Visitor : public ::Shell::AST::NodeVisitor { 199 explicit Visitor(DeprecatedString const& filename) 200 : filename(filename) 201 { 202 } 203 204 void visit(::Shell::AST::VariableDeclarations const* node) override 205 { 206 for (auto& entry : node->variables()) { 207 auto literal = entry.name->leftmost_trivial_literal(); 208 if (!literal) 209 continue; 210 211 DeprecatedString name; 212 if (literal->is_bareword()) 213 name = static_ptr_cast<::Shell::AST::BarewordLiteral const>(literal)->text().to_deprecated_string(); 214 215 if (!name.is_empty()) { 216 dbgln("Found variable {}", name); 217 declarations.append({ move(name), { filename, entry.name->position().start_line.line_number, entry.name->position().start_line.line_column }, CodeComprehension::DeclarationType::Variable, {} }); 218 } 219 } 220 ::Shell::AST::NodeVisitor::visit(node); 221 } 222 223 void visit(::Shell::AST::FunctionDeclaration const* node) override 224 { 225 dbgln("Found function {}", node->name().name); 226 declarations.append({ node->name().name.to_deprecated_string(), { filename, node->position().start_line.line_number, node->position().start_line.line_column }, CodeComprehension::DeclarationType::Function, {} }); 227 } 228 229 DeprecatedString const& filename; 230 Vector<CodeComprehension::Declaration> declarations; 231 } visitor { document.filename }; 232 233 document.node->visit(visitor); 234 235 set_declarations_of_document(document.filename, move(visitor.declarations)); 236} 237}