From e1afd4066306fa481aafba78bc8e54e09282cd75 Mon Sep 17 00:00:00 2001 From: ykiko Date: Sun, 27 Oct 2024 13:31:26 +0800 Subject: [PATCH] Some update. --- .clang-format | 2 +- .vscode/launch.json | 11 +++ include/Basic/Basic.h | 39 +++++++++ include/Basic/Location.h | 42 +++++++++ include/Basic/SourceCode.h | 30 +++++++ .../Completion.h => Compiler/CodeComplete.h} | 0 include/Compiler/Compiler.h | 10 +-- include/Feature/CodeCompletion.h | 86 +++++++++++++++++++ src/Compiler/CodeCompletion.cpp | 74 ++++++++++++++++ src/Compiler/Compiler.cpp | 22 ++--- tests/CodeCompletion/test.cpp | 10 ++- tests/CodeCompletion/test2.h | 2 +- unittests/AST/Completion.cpp | 26 ++++++ unittests/Feature/CodeCompletion.cpp | 63 -------------- 14 files changed, 333 insertions(+), 84 deletions(-) create mode 100644 include/Basic/Basic.h create mode 100644 include/Basic/Location.h create mode 100644 include/Basic/SourceCode.h rename include/{Feature/Completion.h => Compiler/CodeComplete.h} (100%) create mode 100644 include/Feature/CodeCompletion.h create mode 100644 src/Compiler/CodeCompletion.cpp create mode 100644 unittests/AST/Completion.cpp delete mode 100644 unittests/Feature/CodeCompletion.cpp diff --git a/.clang-format b/.clang-format index 15d9519b..acf44883 100644 --- a/.clang-format +++ b/.clang-format @@ -2,7 +2,7 @@ # compatible with clang-format 18 UseTab: Never -ColumnLimit: 120 +ColumnLimit: 100 # Indent IndentWidth: 4 diff --git a/.vscode/launch.json b/.vscode/launch.json index a148c8c4..cf8eb338 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -38,6 +38,17 @@ ], "cwd": "${workspaceFolder}" }, + { + "type": "lldb", + "request": "launch", + "name": "CodeCompletion", + "program": "${workspaceFolder}/build/bin/clice-tests", + "args": [ + "--test-dir=/home/ykiko/C++/clice2/tests", + "--gtest_filter=clice.CodeCompletion" + ], + "cwd": "${workspaceFolder}" + }, { "type": "lldb", "request": "launch", diff --git a/include/Basic/Basic.h b/include/Basic/Basic.h new file mode 100644 index 00000000..64d8a780 --- /dev/null +++ b/include/Basic/Basic.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +namespace clice::proto { + +/// range in [-2^31, 2^31- 1] +using integer = std::int32_t; + +/// range in [0, 2^31- 1] +using uinteger = std::uint32_t; + +using string = std::string; + +using string_literal = llvm::StringLiteral; + +template +using array = std::vector; + +using DocumentUri = std::string; + +// TODO: figure out URI. +using URI = std::string; + +/// Beacuse C++ does support string enum, so define `enum_type` for +/// tag when serialize/deserialize. +template +struct enum_type { + T value; + + using underlying_type = T; + + constexpr enum_type(T value) : value(value) {} + + friend bool operator== (const enum_type& lhs, const enum_type& rhs) = default; +}; + +} // namespace clice::proto diff --git a/include/Basic/Location.h b/include/Basic/Location.h new file mode 100644 index 00000000..846362f8 --- /dev/null +++ b/include/Basic/Location.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +namespace clice::proto { + +/// A set of predefined position encoding kinds. +struct PositionEncodingKind : enum_type { + using enum_type::enum_type; + constexpr inline static string_literal UTF8 = "utf-8"; + constexpr inline static string_literal UTF16 = "utf-16"; + constexpr inline static string_literal UTF32 = "utf-32"; +}; + +struct Position { + /// Line position in a document (zero-based). + uinteger line; + + /// Character offset on a line in a document (zero-based). + /// The meaning of this offset is determined by the negotiated `PositionEncodingKind`. + uinteger character; +}; + +struct Range { + /// The range's start position. + Position start; + + /// The range's end position. + Position end; +}; + +struct TextEdit { + /// The range of the text document to be manipulated. To insert + /// text into a document create a range where start === end. + Range range; + + // The string to be inserted. For delete operations use an + // empty string. + string newText; +}; + +} // namespace clice::proto diff --git a/include/Basic/SourceCode.h b/include/Basic/SourceCode.h new file mode 100644 index 00000000..debf8865 --- /dev/null +++ b/include/Basic/SourceCode.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +namespace clice { + +/// Convert a clang::SourceLocation to a proto::Position according to the +/// specified encoding kind. Note that `SourceLocation` in clang is one-based and +/// is always encoded in UTF-8. +proto::Position toPosition(llvm::StringRef content, + clang::SourceLocation location, + proto::PositionEncodingKind kind, + const clang::SourceManager& srcMgr); + +/// Same as above, but for a group of locations. It is more efficient than calling +/// `toLocation` multiple times. Note that the locations must be sorted. +std::vector toPosition(llvm::StringRef content, + llvm::ArrayRef locations, + proto::PositionEncodingKind kind, + const clang::SourceManager& srcMgr); + +/// Convert a proto::Position to a clang::SourceLocation according to the +/// specified encoding kind. If any error occurs, return an invalid location. +clang::SourceLocation toSourceLocation(llvm::StringRef content, + proto::Position position, + proto::PositionEncodingKind kind, + const clang::SourceManager& srcMgr); + +} // namespace clice diff --git a/include/Feature/Completion.h b/include/Compiler/CodeComplete.h similarity index 100% rename from include/Feature/Completion.h rename to include/Compiler/CodeComplete.h diff --git a/include/Compiler/Compiler.h b/include/Compiler/Compiler.h index eca8d636..dfe2189a 100644 --- a/include/Compiler/Compiler.h +++ b/include/Compiler/Compiler.h @@ -27,8 +27,9 @@ public: /// Build AST. void buildAST(); - /// Generate the PCH(PreCompiledHeader) to output path. Generally execute `clang::GeneratePCHAction`. - /// The Header part of the source file is stored in the PCH file. Bound is the size of the header part. + /// Generate the PCH(PreCompiledHeader) to output path. Generally execute + /// `clang::GeneratePCHAction`. The Header part of the source file is stored in the PCH file. + /// Bound is the size of the header part. void generatePCH(llvm::StringRef outpath, std::uint32_t bound, bool endAtStart = false); /// Generate the PCM(PreCompiledModule) to output path. Generally execute @@ -36,10 +37,7 @@ public: void generatePCM(llvm::StringRef outpath); /// Run code complete in given file and location. - void codeCompletion(llvm::StringRef filepath, - std::uint32_t line, - std::uint32_t column, - clang::CodeCompleteConsumer* consumer); + void codeCompletion(llvm::StringRef filepath, std::uint32_t line, std::uint32_t column); clang::Preprocessor& pp() { return instance->getPreprocessor(); diff --git a/include/Feature/CodeCompletion.h b/include/Feature/CodeCompletion.h new file mode 100644 index 00000000..a15ccf77 --- /dev/null +++ b/include/Feature/CodeCompletion.h @@ -0,0 +1,86 @@ +#pragma once + +#include + +namespace clice::proto { + +struct CompletionClientCapabilities {}; + +enum CompletionItemKind { + Text = 1, + Method = 2, + Function = 3, + Constructor = 4, + Field = 5, + Variable = 6, + Class = 7, + Interface = 8, + Module = 9, + Property = 10, + Unit = 11, + Value = 12, + Enum = 13, + Keyword = 14, + Snippet = 15, + Color = 16, + File = 17, + Reference = 18, + Folder = 19, + EnumMember = 20, + Constant = 21, + Struct = 22, + Event = 23, + Operator = 24, + TypeParameter, +}; + +enum CompletionItemTag { + /// Render a completion as obsolete, usually using a strike-out. + Deprecated = 1, +}; + +struct CompletionItem { + /// The label of this completion item. + /// If label details are provided the label itself should + /// be an unqualified name of the completion item. + string label; + + /// FIXME: + /// labelDetails?: CompletionItemLabelDetails; + + // The kind of this completion item. Based of the kind + // an icon is chosen by the editor. The standardized set + // of available values is defined in `CompletionItemKind`. + CompletionItemKind kind; + + /// Tags for this completion item. + std::vector tags; + + // FIXME: + // ... +}; + +} // namespace clice::proto + +namespace clice { +class Compiler; +} + +namespace clice::config { + +struct CodeCompletionOption { + // TODO: +}; + +} // namespace clice::config + +namespace clice::feature { + +/// Run code completion in given file and location. `compiler` should be +/// set properly if any PCH or PCM is needed. +std::vector codeCompletion(Compiler& compiler, + llvm::StringRef filepath, + proto::Position position, + const config::CodeCompletionOption& option); + +} // namespace clice::feature diff --git a/src/Compiler/CodeCompletion.cpp b/src/Compiler/CodeCompletion.cpp new file mode 100644 index 00000000..ade34c1d --- /dev/null +++ b/src/Compiler/CodeCompletion.cpp @@ -0,0 +1,74 @@ +#include +#include + +namespace clice { + +namespace { + +class CodeCompleteConsumer final : public clang::CodeCompleteConsumer { +public: + CodeCompleteConsumer(clang::CodeCompleteOptions options) : + clang::CodeCompleteConsumer(options), allocator(new clang::GlobalCodeCompletionAllocator()), + info(allocator) { + // TODO: + } + + void ProcessCodeCompleteResults(clang::Sema& sema, + clang::CodeCompletionContext context, + clang::CodeCompletionResult* results, + unsigned count) final { + sema.getPreprocessor().getCodeCompletionLoc(); + for(auto& result: llvm::make_range(results, results + count)) { + llvm::outs() << "Kind: " << result.Kind << " "; + switch(result.Kind) { + case clang::CodeCompletionResult::RK_Declaration: { + result.getDeclaration()->dump(); + break; + } + case clang::CodeCompletionResult::RK_Keyword: { + llvm::outs() << result.Keyword << "\n"; + break; + } + case clang::CodeCompletionResult::RK_Macro: { + llvm::outs() << result.Macro << "\n"; + break; + } + case clang::CodeCompletionResult::RK_Pattern: { + llvm::outs() << result.Pattern->getAsString() << "\n"; + break; + } + } + } + } + + void ProcessOverloadCandidates(clang::Sema& sema, + unsigned CurrentArg, + OverloadCandidate* candidates, + unsigned count, + clang::SourceLocation openParLoc, + bool braced) final { + llvm::outs() << "ProcessOverloadCandidates\n"; + auto range = llvm::make_range(candidates, candidates + count); + for(auto& candidate: range) { + switch(candidate.getKind()) { + // case + } + } + } + + clang::CodeCompletionAllocator& getAllocator() final { + return *allocator; + } + + clang::CodeCompletionTUInfo& getCodeCompletionTUInfo() final { + return info; + } + +private: + std::shared_ptr allocator; + clang::CodeCompletionTUInfo info; +}; + +} // namespace + +} // namespace clice diff --git a/src/Compiler/Compiler.cpp b/src/Compiler/Compiler.cpp index 4b9cb189..67aa099b 100644 --- a/src/Compiler/Compiler.cpp +++ b/src/Compiler/Compiler.cpp @@ -1,6 +1,7 @@ #include #include #include +#include namespace clice { @@ -16,7 +17,8 @@ Compiler::Compiler(llvm::StringRef filepath, llvm::StringRef content, llvm::ArrayRef args, clang::DiagnosticConsumer* consumer, - llvm::IntrusiveRefCntPtr vfs) : filepath(filepath), content(content) { + llvm::IntrusiveRefCntPtr vfs) : + filepath(filepath), content(content) { // FIXME: figure out should we use createInvocation? clang::CreateInvocationOptions options; auto invocation = clang::createInvocation(args, options); @@ -29,8 +31,9 @@ Compiler::Compiler(llvm::StringRef filepath, if(consumer) { instance->createDiagnostics(consumer, true); } else { - instance->createDiagnostics(new clang::TextDiagnosticPrinter(llvm::outs(), new clang::DiagnosticOptions()), - true); + instance->createDiagnostics( + new clang::TextDiagnosticPrinter(llvm::outs(), new clang::DiagnosticOptions()), + true); } if(!instance->createTarget()) { @@ -74,15 +77,11 @@ void Compiler::generatePCM(llvm::StringRef outpath) { ExecuteAction(); } -void Compiler::codeCompletion(llvm::StringRef filepath, - std::uint32_t line, - std::uint32_t column, - clang::CodeCompleteConsumer* consumer) { +void Compiler::codeCompletion(llvm::StringRef filepath, std::uint32_t line, std::uint32_t column) { auto& completion = instance->getFrontendOpts().CodeCompletionAt; completion.FileName = filepath; completion.Line = line; completion.Column = column; - instance->setCodeCompletionConsumer(consumer); buildAST(); } @@ -102,7 +101,8 @@ void Compiler::ExecuteAction() { // Beacuse CompilerInstance may create new Preprocessor in `BeginSourceFile`, // So we must need to create TokenCollector here. - clang::syntax::TokenCollector collector{preproc}; + // clang::syntax::TokenCollector collector{preproc}; + CodeCompleteCollector collect2(*instance); // FIXME: clang-tidy, include-fixer, etc? @@ -112,8 +112,8 @@ void Compiler::ExecuteAction() { } // Build TokenBuffer and index expanded tokens for improving performance. - buffer = std::make_unique(std::move(collector).consume()); - buffer->indexExpandedTokens(); + // buffer = std::make_unique(std::move(collector).consume()); + // buffer->indexExpandedTokens(); } Compiler::~Compiler() { diff --git a/tests/CodeCompletion/test.cpp b/tests/CodeCompletion/test.cpp index dad189e4..290ccc6e 100644 --- a/tests/CodeCompletion/test.cpp +++ b/tests/CodeCompletion/test.cpp @@ -1,6 +1,12 @@ -#include "test1.h" -#include "test2.h" +struct X { + int x; +}; + +void foo(int x, int y); int main() { + X xxx; + xxx.x = 2; + foo() return 0; } diff --git a/tests/CodeCompletion/test2.h b/tests/CodeCompletion/test2.h index 79a15557..a52edc9b 100644 --- a/tests/CodeCompletion/test2.h +++ b/tests/CodeCompletion/test2.h @@ -1,3 +1,3 @@ inline void bar() { - fo + // fo } diff --git a/unittests/AST/Completion.cpp b/unittests/AST/Completion.cpp new file mode 100644 index 00000000..2541560e --- /dev/null +++ b/unittests/AST/Completion.cpp @@ -0,0 +1,26 @@ +#include "../Test.h" +#include +#include + +namespace { + +using namespace clice; + +TEST(clice, CodeCompletion) { + foreachFile("CodeCompletion", [](std::string file, llvm::StringRef content) { + if(file.ends_with("test.cpp")) { + std::vector compileArgs = { + "clang++", + "-std=c++20", + file.c_str(), + "-resource-dir", + "/home/ykiko/C++/clice2/build/lib/clang/20", + }; + auto compiler = Compiler(compileArgs); + compiler.codeCompletion(file, 10, 9); + } + }); +} + +} // namespace + diff --git a/unittests/Feature/CodeCompletion.cpp b/unittests/Feature/CodeCompletion.cpp deleted file mode 100644 index 4f9625a4..00000000 --- a/unittests/Feature/CodeCompletion.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include -#include - -namespace { - -using namespace clice; - -std::vector compileArgs = { - "clang++", - "-std=c++20", - "/home/ykiko/C++/clice2/tests/Source/CodeCompletion/test.cpp", - "-resource-dir", - "/home/ykiko/C++/clice2/build/lib/clang/20", -}; - -class CodeCompletionConsumer : public clang::CodeCompleteConsumer { -public: - void ProcessCodeCompleteResults(clang::Sema& S, - clang::CodeCompletionContext Context, - clang::CodeCompletionResult* Results, - unsigned NumResults) override { - for(unsigned i = 0; i < NumResults; ++i) { - auto str = - Results[i].CreateCodeCompletionString(S, Context, getAllocator(), getCodeCompletionTUInfo(), true); - llvm::outs() << str->getAsString() << "\n"; - } - } - - void ProcessOverloadCandidates(clang::Sema& S, - unsigned CurrentArg, - OverloadCandidate* Candidates, - unsigned NumCandidates, - clang::SourceLocation OpenParLoc, - bool Braced) override {} - - clang::CodeCompletionAllocator& getAllocator() override { - return *Allocator; - }; - - clang::CodeCompletionTUInfo& getCodeCompletionTUInfo() override { - return TUInfo; - } - - CodeCompletionConsumer() : - clang::CodeCompleteConsumer(clang::CodeCompleteOptions()), - Allocator(std::make_shared()), TUInfo(Allocator) {} - -private: - std::shared_ptr Allocator; - clang::CodeCompletionTUInfo TUInfo; -}; - -TEST(clice, CodeCompletion) { - auto invocation = clang::createInvocation(compileArgs, {}); - - Compiler compiler(compileArgs); - compiler.codeCompletion("/home/ykiko/C++/clice2/tests/Source/CodeCompletion/test.cpp", - 2, - 7, - new CodeCompletionConsumer()); -} - -} // namespace