Selaa lähdekoodia

CppLanguageServer+LibGUI: Autocomplete #include paths

The C++ language-server can now autocomplete include paths.

Paths that start with '<' will be searched in /usr/include, and paths
that start with '"' will be searched in the project's root directory.
Itamar 4 vuotta sitten
vanhempi
commit
c003c3c76d

+ 50 - 0
Userland/DevTools/HackStudio/LanguageServers/Cpp/CppComprehensionEngine.cpp

@@ -8,6 +8,8 @@
 #include <AK/Assertions.h>
 #include <AK/HashTable.h>
 #include <AK/OwnPtr.h>
+#include <LibCore/DirIterator.h>
+#include <LibCore/File.h>
 #include <LibCpp/AST.h>
 #include <LibCpp/Lexer.h>
 #include <LibCpp/Parser.h>
@@ -64,6 +66,13 @@ Vector<GUI::AutocompleteProvider::Entry> CppComprehensionEngine::get_suggestions
 
     const auto& document = *document_ptr;
     auto containing_token = document.parser().token_at(position);
+
+    if (containing_token.has_value() && containing_token->type() == Token::Type::IncludePath) {
+        auto results = try_autocomplete_include(document, containing_token.value());
+        if (results.has_value())
+            return results.value();
+    }
+
     auto node = document.parser().node_at(position);
     if (!node) {
         dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "no node at position {}:{}", position.line, position.column);
@@ -540,6 +549,7 @@ OwnPtr<CppComprehensionEngine::DocumentData> CppComprehensionEngine::create_docu
     document_data->m_text = move(text);
     document_data->m_preprocessor = make<Preprocessor>(document_data->m_filename, document_data->text());
     document_data->preprocessor().set_ignore_unsupported_keywords(true);
+    document_data->preprocessor().set_keep_include_statements(true);
     document_data->preprocessor().process();
 
     Preprocessor::Definitions preprocessor_definitions;
@@ -590,4 +600,44 @@ String CppComprehensionEngine::scope_of_declaration(const Declaration& decl) con
     return String::formatted("{}::{}", parent_scope, containing_scope);
 }
 
+Optional<Vector<GUI::AutocompleteProvider::Entry>> CppComprehensionEngine::try_autocomplete_include(const DocumentData&, Token include_path_token)
+{
+    VERIFY(include_path_token.type() == Token::Type::IncludePath);
+    auto partial_include = include_path_token.text().trim_whitespace();
+
+    String include_root;
+    auto include_type = GUI::AutocompleteProvider::CompletionKind::ProjectInclude;
+    if (partial_include.starts_with("<")) {
+        include_root = "/usr/include/";
+        include_type = GUI::AutocompleteProvider::CompletionKind::SystemInclude;
+    } else if (partial_include.starts_with("\"")) {
+        include_root = filedb().project_root();
+    } else
+        return {};
+
+    auto last_slash = partial_include.find_last_of("/");
+    auto include_dir = String::empty();
+    auto partial_basename = partial_include.substring_view((last_slash.has_value() ? last_slash.value() : 0) + 1);
+    if (last_slash.has_value()) {
+        include_dir = partial_include.substring_view(1, last_slash.value());
+    }
+
+    auto full_dir = String::formatted("{}{}", include_root, include_dir);
+    dbgln_if(CPP_LANGUAGE_SERVER_DEBUG, "searching path: {}, partial_basename: {}", full_dir, partial_basename);
+
+    Core::DirIterator it(full_dir, Core::DirIterator::Flags::SkipDots);
+    Vector<GUI::AutocompleteProvider::Entry> options;
+
+    while (it.has_next()) {
+        auto path = it.next_path();
+        if (!(path.ends_with(".h") || Core::File::is_directory(LexicalPath::join(full_dir, path).string())))
+            continue;
+        if (path.starts_with(partial_basename)) {
+            options.append({ path, partial_basename.length(), include_type, GUI::AutocompleteProvider::Language::Cpp });
+        }
+    }
+
+    return options;
+}
+
 }

+ 1 - 0
Userland/DevTools/HackStudio/LanguageServers/Cpp/CppComprehensionEngine.h

@@ -103,6 +103,7 @@ private:
     OwnPtr<DocumentData> create_document_data(String&& text, const String& filename);
     Optional<Vector<GUI::AutocompleteProvider::Entry>> try_autocomplete_property(const DocumentData&, const ASTNode&, Optional<Token> containing_token) const;
     Optional<Vector<GUI::AutocompleteProvider::Entry>> try_autocomplete_name(const DocumentData&, const ASTNode&, Optional<Token> containing_token) const;
+    Optional<Vector<GUI::AutocompleteProvider::Entry>> try_autocomplete_include(const DocumentData&, Token include_path_token);
 
     HashMap<String, OwnPtr<DocumentData>> m_documents;
 };

+ 11 - 1
Userland/Libraries/LibGUI/AutocompleteProvider.cpp

@@ -176,7 +176,17 @@ void AutocompleteBox::apply_suggestion()
     size_t partial_length = suggestion_index.data((GUI::ModelRole)AutocompleteSuggestionModel::InternalRole::PartialInputLength).to_i64();
 
     VERIFY(suggestion.length() >= partial_length);
-    auto completion = suggestion.substring_view(partial_length, suggestion.length() - partial_length);
+    auto completion_view = suggestion.substring_view(partial_length, suggestion.length() - partial_length);
+    auto completion_kind = (GUI::AutocompleteProvider::CompletionKind)suggestion_index.data((GUI::ModelRole)AutocompleteSuggestionModel::InternalRole::Kind).as_uint();
+
+    String completion;
+    if (completion_view.ends_with(".h") && completion_kind == GUI::AutocompleteProvider::CompletionKind::SystemInclude)
+        completion = String::formatted("{}{}", completion_view, ">");
+    else if (completion_view.ends_with(".h") && completion_kind == GUI::AutocompleteProvider::CompletionKind::ProjectInclude)
+        completion = String::formatted("{}{}", completion_view, "\"");
+    else
+        completion = completion_view;
+
     m_editor->insert_at_cursor_or_replace_selection(completion);
 }
 

+ 2 - 0
Userland/Libraries/LibGUI/AutocompleteProvider.h

@@ -23,6 +23,8 @@ public:
     enum class CompletionKind {
         Identifier,
         PreprocessorDefinition,
+        SystemInclude,
+        ProjectInclude,
     };
 
     enum class Language {