From ce4b16ce3088deb06e3a45e60705175ca1e53e1f Mon Sep 17 00:00:00 2001 From: ykiko Date: Sun, 24 Aug 2025 15:03:07 +0800 Subject: [PATCH] Refactor and clean (#189) --- .../workflows/{nightly.yml => release.yml} | 7 +-- include/Feature/InlayHint.h | 6 +-- include/Feature/SemanticToken.h | 2 +- include/Server/Convert.h | 38 +++++++++++++++ include/Server/Server.h | 2 - src/Feature/DocumentSymbol.cpp | 48 +++++++++++++++++-- src/Feature/InlayHint.cpp | 6 +-- src/Feature/SemanticToken.cpp | 2 +- src/Index/FeatureIndex.cpp | 2 +- src/Server/Document.cpp | 37 +++++++------- src/Server/Feature.cpp | 47 +++++++++++------- tests/unit/Feature/InlayHint.cpp | 2 +- 12 files changed, 143 insertions(+), 56 deletions(-) rename .github/workflows/{nightly.yml => release.yml} (97%) diff --git a/.github/workflows/nightly.yml b/.github/workflows/release.yml similarity index 97% rename from .github/workflows/nightly.yml rename to .github/workflows/release.yml index 6bc1985a..2a32c86c 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/release.yml @@ -1,11 +1,12 @@ -name: nightly +name: release permissions: contents: write on: push: - branches: [non-existent-branch] + tags: + - 'v*' jobs: package: @@ -86,5 +87,5 @@ jobs: repo_token: ${{ secrets.GITHUB_TOKEN }} file: build/xpack/clice/${{ matrix.artifact_name }} asset_name: ${{ matrix.asset_name }} - tag: nightly + tag: ${{ github.ref }} overwrite: true diff --git a/include/Feature/InlayHint.h b/include/Feature/InlayHint.h index b1ae5460..fb012f80 100644 --- a/include/Feature/InlayHint.h +++ b/include/Feature/InlayHint.h @@ -50,8 +50,8 @@ struct InlayHint { std::vector parts; }; -auto inlay_hint(CompilationUnit& unit, - LocalSourceRange target, - const config::InlayHintsOptions& options) -> std::vector; +auto inlay_hints(CompilationUnit& unit, + LocalSourceRange target, + const config::InlayHintsOptions& options) -> std::vector; } // namespace clice::feature diff --git a/include/Feature/SemanticToken.h b/include/Feature/SemanticToken.h index 976ba5de..04c66efe 100644 --- a/include/Feature/SemanticToken.h +++ b/include/Feature/SemanticToken.h @@ -24,7 +24,7 @@ using SemanticTokens = std::vector; SemanticTokens semantic_tokens(CompilationUnit& unit); /// Generate semantic tokens for all files. -index::Shared indexSemanticToken(CompilationUnit& unit); +index::Shared index_semantic_token(CompilationUnit& unit); } // namespace clice::feature diff --git a/include/Server/Convert.h b/include/Server/Convert.h index 84934fa1..19fd22c6 100644 --- a/include/Server/Convert.h +++ b/include/Server/Convert.h @@ -256,6 +256,44 @@ inline std::uint32_t to_offset(clice::PositionEncodingKind kind, namespace clice::proto { +inline SymbolKind kind_map(clice::SymbolKind kind) { + switch(kind.kind()) { + case clice::SymbolKind::Comment: return SymbolKind::String; + case clice::SymbolKind::Number: return SymbolKind::Number; + case clice::SymbolKind::Character: return SymbolKind::String; + case clice::SymbolKind::String: return SymbolKind::String; + case clice::SymbolKind::Keyword: return SymbolKind::Variable; + case clice::SymbolKind::Directive: return SymbolKind::Variable; + case clice::SymbolKind::Header: return SymbolKind::String; + case clice::SymbolKind::Module: return SymbolKind::Module; + case clice::SymbolKind::Macro: return SymbolKind::Function; + case clice::SymbolKind::MacroParameter: return SymbolKind::Variable; + case clice::SymbolKind::Namespace: return SymbolKind::Namespace; + case clice::SymbolKind::Class: return SymbolKind::Class; + case clice::SymbolKind::Struct: return SymbolKind::Struct; + case clice::SymbolKind::Union: return SymbolKind::Class; + case clice::SymbolKind::Enum: return SymbolKind::Enum; + case clice::SymbolKind::Type: return SymbolKind::TypeParameter; + case clice::SymbolKind::Field: return SymbolKind::Field; + case clice::SymbolKind::EnumMember: return SymbolKind::EnumMember; + case clice::SymbolKind::Function: return SymbolKind::Function; + case clice::SymbolKind::Method: return SymbolKind::Method; + case clice::SymbolKind::Variable: return SymbolKind::Variable; + case clice::SymbolKind::Parameter: return SymbolKind::Variable; + case clice::SymbolKind::Label: return SymbolKind::Variable; + case clice::SymbolKind::Concept: return SymbolKind::TypeParameter; + case clice::SymbolKind::Attribute: return SymbolKind::Variable; + case clice::SymbolKind::Operator: + case clice::SymbolKind::Paren: + case clice::SymbolKind::Bracket: + case clice::SymbolKind::Brace: + case clice::SymbolKind::Angle: return SymbolKind::Operator; + case clice::SymbolKind::Conflict: + case clice::SymbolKind::Invalid: + default: return SymbolKind::Null; + } +} + json::Value to_json(clice::PositionEncodingKind kind, llvm::StringRef content, llvm::ArrayRef tokens); diff --git a/include/Server/Server.h b/include/Server/Server.h index e7c864e8..c6ef0d0f 100644 --- a/include/Server/Server.h +++ b/include/Server/Server.h @@ -185,8 +185,6 @@ private: async::Task> add_document(std::string path, std::string content); private: - async::Task<> publish_diagnostics(std::string path, std::shared_ptr file); - async::Task<> on_did_open(proto::DidOpenTextDocumentParams params); async::Task<> on_did_change(proto::DidChangeTextDocumentParams params); diff --git a/src/Feature/DocumentSymbol.cpp b/src/Feature/DocumentSymbol.cpp index 8371a9bd..32ce204f 100644 --- a/src/Feature/DocumentSymbol.cpp +++ b/src/Feature/DocumentSymbol.cpp @@ -9,6 +9,41 @@ namespace clice::feature { namespace { +std::string symbol_detail(clang::ASTContext& Ctx, const clang::NamedDecl& ND) { + clang::PrintingPolicy policy(Ctx.getPrintingPolicy()); + policy.SuppressScope = true; + policy.SuppressUnwrittenScope = true; + policy.AnonymousTagLocations = false; + policy.PolishForDeclaration = true; + + std::string detail; + llvm::raw_string_ostream os(detail); + if(ND.getDescribedTemplateParams()) { + os << "template "; + } + + if(const auto* VD = dyn_cast(&ND)) { + // FIXME: better printing for dependent type + if(isa(VD)) { + std::string type = VD->getType().getAsString(policy); + // Print constructor type as "(int)" instead of "void (int)". + llvm::StringRef without_void = type; + without_void.consume_front("void "); + os << without_void; + } else if(!isa(VD)) { + VD->getType().print(os, policy); + } + } else if(const auto* TD = dyn_cast(&ND)) { + os << TD->getKindName(); + } else if(isa(&ND)) { + os << "type alias"; + } else if(isa(&ND)) { + os << "concept"; + } + + return detail; +} + /// Use DFS to traverse the AST and collect document symbols. class DocumentSymbolCollector : public FilteredASTVisitor { @@ -50,8 +85,12 @@ public: } auto ND = llvm::cast(decl); - auto [fid, selectionRange] = + auto [fid, selection_range] = unit.decompose_range(unit.expansion_location(ND->getLocation())); + auto [fid2, range] = unit.decompose_expansion_range(ND->getSourceRange()); + if(fid != fid2) { + return true; + } auto& frame = interested_only ? result : sharedResult[fid]; auto cursor = frame.cursor; @@ -59,9 +98,10 @@ public: /// Add new symbol. auto& symbol = frame.cursor->emplace_back(); symbol.kind = SymbolKind::from(decl); - symbol.name = ast::name_of(ND); - symbol.selectionRange = selectionRange; - symbol.range = selectionRange; + symbol.name = ast::display_name_of(ND); + symbol.detail = symbol_detail(unit.context(), *ND); + symbol.selectionRange = selection_range; + symbol.range = range; /// Adjust the node. frame.cursor = &symbol.children; diff --git a/src/Feature/InlayHint.cpp b/src/Feature/InlayHint.cpp index 5e51e6f8..b944c7b8 100644 --- a/src/Feature/InlayHint.cpp +++ b/src/Feature/InlayHint.cpp @@ -879,9 +879,9 @@ private: } // namespace -auto inlay_hint(CompilationUnit& unit, - LocalSourceRange target, - const config::InlayHintsOptions& options) -> std::vector { +auto inlay_hints(CompilationUnit& unit, + LocalSourceRange target, + const config::InlayHintsOptions& options) -> std::vector { std::vector hints; Builder builder(hints, unit, target, options); diff --git a/src/Feature/SemanticToken.cpp b/src/Feature/SemanticToken.cpp index c3f527c0..3ad9f325 100644 --- a/src/Feature/SemanticToken.cpp +++ b/src/Feature/SemanticToken.cpp @@ -253,7 +253,7 @@ SemanticTokens semantic_tokens(CompilationUnit& unit) { return std::move(collector.result); } -index::Shared indexSemanticToken(CompilationUnit& unit) { +index::Shared index_semantic_token(CompilationUnit& unit) { SemanticTokensCollector collector(unit, false); for(auto fid: unit.files()) { collector.highlight(fid); diff --git a/src/Index/FeatureIndex.cpp b/src/Index/FeatureIndex.cpp index fa3e9f9b..f3831536 100644 --- a/src/Index/FeatureIndex.cpp +++ b/src/Index/FeatureIndex.cpp @@ -61,7 +61,7 @@ feature::DocumentSymbols FeatureIndex::documentSymbols() const { Shared> FeatureIndex::build(CompilationUnit& unit) { Shared indices; - for(auto&& [fid, result]: feature::indexSemanticToken(unit)) { + for(auto&& [fid, result]: feature::index_semantic_token(unit)) { indices[fid].tokens = std::move(result); } diff --git a/src/Server/Document.cpp b/src/Server/Document.cpp index 1436cfeb..db5a2fc7 100644 --- a/src/Server/Document.cpp +++ b/src/Server/Document.cpp @@ -47,8 +47,9 @@ void Server::load_cache_info() { auto mtime = object->getNumber("mtime"); auto deps = object->getArray("deps"); auto arguments = object->getArray("arguments"); + auto includes = object->get("includes"); - if(!file || !path || !preamble || !mtime || !deps || !arguments) { + if(!file || !path || !preamble || !mtime || !deps || !arguments || !includes) { continue; } @@ -67,7 +68,10 @@ void Server::load_cache_info() { } /// Update the PCH info. - opening_files.get_or_add(*file)->pch = std::move(info); + auto opening_file = opening_files.get_or_add(*file); + opening_file->pch = std::move(info); + opening_file->pch_includes = + json::deserializepch_includes)>(*includes); } } @@ -92,6 +96,7 @@ void Server::save_cache_info() { object["mtime"] = pch.mtime; object["deps"] = json::serialize(pch.deps); object["arguments"] = json::serialize(pch.arguments); + object["includes"] = json::serialize(open_file->pch_includes); json["pchs"].getAsArray()->emplace_back(std::move(object)); } @@ -313,6 +318,15 @@ async::Task<> Server::build_ast(std::string path, std::string content) { co_return; } + /// Send diagnostics + auto diagnostics = co_await async::submit( + [&, kind = this->kind] { return feature::diagnostics(kind, mapping, *ast); }); + co_await notify("textDocument/publishDiagnostics", + json::Object{ + {"uri", mapping.to_uri(path) }, + {"diagnostics", std::move(diagnostics)}, + }); + /// FIXME: Index the source file. /// co_await indexer.index(*ast); @@ -327,6 +341,7 @@ async::Task<> Server::build_ast(std::string path, std::string content) { async::Task> Server::add_document(std::string path, std::string content) { auto& openFile = opening_files.get_or_add(path); + openFile->version += 1; openFile->content = content; auto& task = openFile->ast_build_task; @@ -350,33 +365,15 @@ async::Task> Server::add_document(std::string path, st co_return openFile; } -async::Task<> Server::publish_diagnostics(std::string path, std::shared_ptr file) { - auto guard = co_await file->ast_built_lock.try_lock(); - if(file->ast) { - auto diagnostics = feature::diagnostics(kind, mapping, *file->ast); - co_await notify("textDocument/publishDiagnostics", - json::Object{ - {"uri", mapping.to_uri(path) }, - {"diagnostics", std::move(diagnostics)}, - }); - } -} - async::Task<> Server::on_did_open(proto::DidOpenTextDocumentParams params) { auto path = mapping.to_path(params.textDocument.uri); auto file = co_await add_document(path, std::move(params.textDocument.text)); - if(file->diagnostics) { - co_await publish_diagnostics(path, std::move(file)); - } co_return; } async::Task<> Server::on_did_change(proto::DidChangeTextDocumentParams params) { auto path = mapping.to_path(params.textDocument.uri); auto file = co_await add_document(path, std::move(params.contentChanges[0].text)); - if(file->diagnostics) { - co_await publish_diagnostics(path, std::move(file)); - } co_return; } diff --git a/src/Server/Feature.cpp b/src/Server/Feature.cpp index 3f880eb1..1f65cd68 100644 --- a/src/Server/Feature.cpp +++ b/src/Server/Feature.cpp @@ -43,21 +43,26 @@ auto Server::on_completion(proto::CompletionParams params) -> Result { auto Server::on_hover(proto::HoverParams params) -> Result { auto path = mapping.to_path(params.textDocument.uri); auto opening_file = opening_files.get_or_add(path); - auto guard = co_await opening_file->ast_built_lock.try_lock(); + auto version = opening_file->version; - auto offset = to_offset(kind, opening_file->content, params.position); + auto guard = co_await opening_file->ast_built_lock.try_lock(); auto ast = opening_file->ast; - if(!ast) { + + if(opening_file->version != version || !ast) { co_return json::Value(nullptr); } + auto offset = to_offset(kind, opening_file->content, params.position); + co_return co_await async::submit([kind = this->kind, offset, &ast] { auto hover = feature::hover(*ast, offset); + if(hover.kind == SymbolKind::Invalid) { + return json::Value(nullptr); + } proto::Hover result; result.contents.kind = "markdown"; result.contents.value = std::format("{}: {}", hover.kind.name(), hover.name); - return json::serialize(result); }); } @@ -92,10 +97,12 @@ async::Task Server::on_signature_help(proto::SignatureHelpParams pa auto Server::on_document_symbol(proto::DocumentSymbolParams params) -> Result { auto path = mapping.to_path(params.textDocument.uri); auto opening_file = opening_files.get_or_add(path); + auto version = opening_file->version; auto guard = co_await opening_file->ast_built_lock.try_lock(); auto ast = opening_file->ast; - if(!ast) { + + if(opening_file->version != version || !ast) { co_return json::Value(nullptr); } @@ -112,9 +119,7 @@ auto Server::on_document_symbol(proto::DocumentSymbolParams params) -> Result { proto::DocumentSymbol result; result.name = std::move(symbol.name); result.detail = std::move(symbol.detail); - - /// FIXME: Add kind map. - result.kind = static_cast(symbol.kind.value()); + result.kind = proto::kind_map(symbol.kind.kind()); result.range = to_range(symbol.range); result.selectionRange = to_range(symbol.selectionRange); @@ -140,10 +145,12 @@ auto Server::on_document_symbol(proto::DocumentSymbolParams params) -> Result { auto Server::on_document_link(proto::DocumentLinkParams params) -> Result { auto path = mapping.to_path(params.textDocument.uri); auto opening_file = opening_files.get_or_add(path); - auto guard = co_await opening_file->ast_built_lock.try_lock(); + auto version = opening_file->version; + auto guard = co_await opening_file->ast_built_lock.try_lock(); auto ast = opening_file->ast; - if(!ast) { + + if(opening_file->version != version || !ast) { co_return json::Value(nullptr); } @@ -195,10 +202,12 @@ auto Server::on_document_range_format(proto::DocumentRangeFormattingParams param async::Task Server::on_folding_range(proto::FoldingRangeParams params) { auto path = mapping.to_path(params.textDocument.uri); auto opening_file = opening_files.get_or_add(path); - auto guard = co_await opening_file->ast_built_lock.try_lock(); + auto version = opening_file->version; + auto guard = co_await opening_file->ast_built_lock.try_lock(); auto ast = opening_file->ast; - if(!ast) { + + if(opening_file->version != version || !ast) { co_return json::Value(nullptr); } @@ -233,10 +242,12 @@ async::Task Server::on_folding_range(proto::FoldingRangeParams para auto Server::on_semantic_token(proto::SemanticTokensParams params) -> Result { auto path = mapping.to_path(params.textDocument.uri); auto opening_file = opening_files.get_or_add(path); - auto guard = co_await opening_file->ast_built_lock.try_lock(); + auto version = opening_file->version; + auto guard = co_await opening_file->ast_built_lock.try_lock(); auto ast = opening_file->ast; - if(!ast) { + + if(opening_file->version != version || !ast) { co_return json::Value(nullptr); } @@ -249,10 +260,12 @@ auto Server::on_semantic_token(proto::SemanticTokensParams params) -> Result { auto Server::on_inlay_hint(proto::InlayHintParams params) -> Result { auto path = mapping.to_path(params.textDocument.uri); auto opening_file = opening_files.get_or_add(path); - auto guard = co_await opening_file->ast_built_lock.try_lock(); + auto version = opening_file->version; + auto guard = co_await opening_file->ast_built_lock.try_lock(); auto ast = opening_file->ast; - if(!ast) { + + if(opening_file->version != version || !ast) { co_return json::Value(nullptr); } @@ -264,7 +277,7 @@ auto Server::on_inlay_hint(proto::InlayHintParams params) -> Result { to_offset(kind, content, params.range.end), }; - auto hints = feature::inlay_hint(*ast, range, {}); + auto hints = feature::inlay_hints(*ast, range, {}); PositionConverter converter(content, kind); diff --git a/tests/unit/Feature/InlayHint.cpp b/tests/unit/Feature/InlayHint.cpp index f577d119..2a07b2a7 100644 --- a/tests/unit/Feature/InlayHint.cpp +++ b/tests/unit/Feature/InlayHint.cpp @@ -17,7 +17,7 @@ suite<"InlayHint"> inlay_hint = [] { tester.compile_with_pch("-std=c++23"); LocalSourceRange range = LocalSourceRange(0, tester.unit->interested_content().size()); - hints = feature::inlay_hint(*tester.unit, range, {}); + hints = feature::inlay_hints(*tester.unit, range, {}); hints_map.clear(); for(auto& hint: hints) {