diff --git a/include/Feature/DocumentSymbol.h b/include/Feature/DocumentSymbol.h index 1d200a35..32b51fa6 100644 --- a/include/Feature/DocumentSymbol.h +++ b/include/Feature/DocumentSymbol.h @@ -29,10 +29,10 @@ struct DocumentSymbol { using DocumentSymbols = std::vector; /// Generate document symbols for only interested file. -DocumentSymbols documentSymbols(CompilationUnit& unit); +DocumentSymbols document_symbols(CompilationUnit& unit); /// Generate document symbols for all file in unit. -index::Shared indexDocumentSymbol(CompilationUnit& unit); +index::Shared index_document_symbol(CompilationUnit& unit); } // namespace clice::feature diff --git a/include/Protocol/Feature/DocumentSymbol.h b/include/Protocol/Feature/DocumentSymbol.h index af53921e..34ffcf70 100644 --- a/include/Protocol/Feature/DocumentSymbol.h +++ b/include/Protocol/Feature/DocumentSymbol.h @@ -4,8 +4,109 @@ namespace clice::proto { -struct DocumentSymbolClientCapabilities {}; +enum class SymbolKind : std::uint8_t { + File = 1, + Module = 2, + Namespace = 3, + Package = 4, + Class = 5, + Method = 6, + Property = 7, + Field = 8, + Constructor = 9, + Enum = 10, + Interface = 11, + Function = 12, + Variable = 13, + Constant = 14, + String = 15, + Number = 16, + Boolean = 17, + Array = 18, + Object = 19, + Key = 20, + Null = 21, + EnumMember = 22, + Struct = 23, + Event = 24, + Operator = 25, + TypeParameter = 26, +}; + +enum class SymbolTag { + /// Render a symbol as obsolete, usually using a strike-out. + Deprecated = 1, +}; + +struct DocumentSymbolClientCapabilities { + /// Specific capabilities for the `SymbolKind` in the + /// `textDocument/documentSymbol` request. + struct { + /// The symbol kind values the client supports. When this + /// property exists the client also guarantees that it will + /// handle values outside its set gracefully and falls back + /// to a default value when unknown. + // + /// If this property is not present the client only supports + /// the symbol kinds from `File` to `Array` as defined in + /// the initial version of the protocol. + array valueSet; + } symbolKind; + + /// The client supports hierarchical document symbols. + bool hierarchicalDocumentSymbolSupport; + + /// The client supports tags on `SymbolInformation`. Tags are supported on + /// `DocumentSymbol` if `hierarchicalDocumentSymbolSupport` is set to true. + /// Clients supporting tags have to handle unknown tags gracefully. + struct { + /// The tags supported by the client. + array valueSet; + } tagSupport; + + /// The client supports an additional label presented in the UI when + /// registering a document symbol provider. + bool labelSupport; +}; struct DocumentSymbolOptions {}; +struct DocumentSymbolParams { + /// The text document. + TextDocumentIdentifier textDocument; +}; + +/// Represents programming constructs like variables, classes, interfaces etc. +/// that appear in a document. Document symbols can be hierarchical and they +/// have two ranges: one that encloses its definition and one that points to its +/// most interesting range, e.g. the range of an identifier. +struct DocumentSymbol { + /// The name of this symbol. Will be displayed in the user interface and + /// therefore must not be an empty string or a string only consisting of + /// white spaces. + string name; + + /// More detail for this symbol, e.g the signature of a function. + string detail; + + /// The kind of this symbol. + SymbolKind kind; + + /// Tags for this document symbol. + array tags; + + /// The range enclosing this symbol not including leading/trailing whitespace + /// but everything else like comments. This information is typically used to + /// determine if the clients cursor is inside the symbol to reveal it in the + /// UI. + Range range; + + /// The range that should be selected and revealed when this symbol is being + /// picked, e.g. the name of a function. Must be contained by the `range`. + Range selectionRange; + + /// Children of this symbol, e.g. properties of a class. + array children; +}; + } // namespace clice::proto diff --git a/include/Protocol/Initialize.h b/include/Protocol/Initialize.h index 0dd913ab..3714eb21 100644 --- a/include/Protocol/Initialize.h +++ b/include/Protocol/Initialize.h @@ -106,7 +106,7 @@ struct ServerCapabilities { /// FIXME: DocumentHighlightOptions documentHighlightProvider; /// The server provides document symbol support. - /// FIXME: DocumentSymbolOptions documentSymbolProvider; + DocumentSymbolOptions documentSymbolProvider; /// The server provides code actions. The `CodeActionOptions` return type is /// only valid if the client signals code action literal support via the diff --git a/include/Server/Server.h b/include/Server/Server.h index bbce3d2b..644bf06f 100644 --- a/include/Server/Server.h +++ b/include/Server/Server.h @@ -109,6 +109,8 @@ private: async::Task on_hover(proto::HoverParams params); + async::Task on_document_symbol(proto::DocumentSymbolParams params); + async::Task on_document_link(proto::DocumentLinkParams params); async::Task on_folding_range(proto::FoldingRangeParams params); diff --git a/src/Feature/DocumentSymbol.cpp b/src/Feature/DocumentSymbol.cpp index 5c0c022a..4d1504e2 100644 --- a/src/Feature/DocumentSymbol.cpp +++ b/src/Feature/DocumentSymbol.cpp @@ -86,7 +86,7 @@ public: } // namespace -DocumentSymbols documentSymbols(CompilationUnit& unit) { +DocumentSymbols document_symbols(CompilationUnit& unit) { DocumentSymbolCollector collector(unit, true); collector.TraverseDecl(unit.tu()); @@ -95,7 +95,7 @@ DocumentSymbols documentSymbols(CompilationUnit& unit) { return std::move(frame.symbols); } -index::Shared indexDocumentSymbol(CompilationUnit& unit) { +index::Shared index_document_symbol(CompilationUnit& unit) { DocumentSymbolCollector collector(unit, true); collector.TraverseDecl(unit.tu()); diff --git a/src/Index/FeatureIndex.cpp b/src/Index/FeatureIndex.cpp index bb64d7af..fa3e9f9b 100644 --- a/src/Index/FeatureIndex.cpp +++ b/src/Index/FeatureIndex.cpp @@ -73,7 +73,7 @@ Shared> FeatureIndex::build(CompilationUnit& unit) { indices[fid].links = std::move(result); } - for(auto&& [fid, result]: feature::indexDocumentSymbol(unit)) { + for(auto&& [fid, result]: feature::index_document_symbol(unit)) { indices[fid].symbols = std::move(result); } diff --git a/src/Server/Feature.cpp b/src/Server/Feature.cpp index 6026e462..274ff273 100644 --- a/src/Server/Feature.cpp +++ b/src/Server/Feature.cpp @@ -1,3 +1,4 @@ +#include "Feature/DocumentSymbol.h" #include "Feature/FoldingRange.h" #include "Server/Server.h" #include "Server/Convert.h" @@ -63,6 +64,56 @@ async::Task Server::on_hover(proto::HoverParams params) { }); } +async::Task Server::on_document_symbol(proto::DocumentSymbolParams params) { + auto path = mapping.to_path(params.textDocument.uri); + + auto opening_file = &opening_files[path]; + auto guard = co_await opening_file->ast_built_lock.try_lock(); + + opening_file = &opening_files[path]; + auto content = opening_file->content; + auto ast = opening_file->ast; + if(!ast) { + co_return json::Value(nullptr); + } + + auto to_range = [&](LocalSourceRange range) { + auto c = PositionConverter(content, kind); + auto begin = c.toPosition(range.begin); + auto end = c.toPosition(range.end); + return proto::Range{begin, end}; + }; + + auto transform = [&to_range](this auto& self, + feature::DocumentSymbol& symbol) -> proto::DocumentSymbol { + 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.range = to_range(symbol.range); + result.selectionRange = to_range(symbol.selectionRange); + + for(auto& child: symbol.children) { + result.children.emplace_back(self(child)); + } + + return result; + }; + + co_return co_await async::submit([&ast, &transform] { + auto symbols = feature::document_symbols(*ast); + + std::vector result; + for(auto& symbol: symbols) { + result.emplace_back(transform(symbol)); + } + + return json::serialize(result); + }); +} + async::Task Server::on_document_link(proto::DocumentLinkParams params) { auto path = mapping.to_path(params.textDocument.uri); diff --git a/src/Server/Lifecycle.cpp b/src/Server/Lifecycle.cpp index 49df9f9c..57cbc12c 100644 --- a/src/Server/Lifecycle.cpp +++ b/src/Server/Lifecycle.cpp @@ -47,6 +47,9 @@ async::Task Server::on_initialize(proto::InitializeParams params) { /// Hover capabilities.hoverProvider = true; + /// DocumentSymbol + capabilities.documentSymbolProvider = {}; + /// DocumentLink capabilities.documentLinkProvider.resolveProvider = false; diff --git a/src/Server/Server.cpp b/src/Server/Server.cpp index 93e6ddd0..5d3c2a14 100644 --- a/src/Server/Server.cpp +++ b/src/Server/Server.cpp @@ -65,6 +65,7 @@ Server::Server() { register_callback<&Server::on_completion>("textDocument/completion"); register_callback<&Server::on_hover>("textDocument/hover"); + register_callback<&Server::on_document_symbol>("textDocument/documentSymbol"); register_callback<&Server::on_document_link>("textDocument/documentLink"); register_callback<&Server::on_folding_range>("textDocument/foldingRange"); register_callback<&Server::on_semantic_token>("textDocument/semanticTokens/full"); diff --git a/tests/unit/Feature/DocumentSymbol.cpp b/tests/unit/Feature/DocumentSymbol.cpp index 7e451135..87ca11f4 100644 --- a/tests/unit/Feature/DocumentSymbol.cpp +++ b/tests/unit/Feature/DocumentSymbol.cpp @@ -6,14 +6,13 @@ namespace clice::testing { namespace { struct DocumentSymbol : TestFixture { - protected: auto run(llvm::StringRef code) { add_main("main.cpp", code); Tester::compile(); EXPECT_TRUE(unit.has_value()); - return feature::documentSymbols(*unit); + return feature::document_symbols(*unit); } static void total_size(const std::vector& result, size_t& size) { @@ -52,8 +51,8 @@ namespace _1::_2{ )cpp"; - auto res = run(main); - EXPECT_EQ(total_size(res), 8); + auto symbols = run(main); + EXPECT_EQ(total_size(symbols), 8); } TEST_F(DocumentSymbol, Struct) { @@ -70,10 +69,10 @@ struct _3 { )cpp"; - auto res = run(main); - EXPECT_EQ(total_size(res), 5); + auto symbols = run(main); + EXPECT_EQ(total_size(symbols), 5); // tester->info->tu()->dump(); - // println("{}", pretty_dump(res)); + // println("{}", pretty_dump(symbols)); } TEST_F(DocumentSymbol, Field) { @@ -93,8 +92,8 @@ struct x { )cpp"; - auto res = run(main); - EXPECT_EQ(total_size(res), 7); + auto symbols = run(main); + EXPECT_EQ(total_size(symbols), 7); } TEST_F(DocumentSymbol, Constructor) { @@ -108,8 +107,8 @@ struct S { ~S() {} }; )cpp"; - auto res = run(main); - EXPECT_EQ(total_size(res), 6); + auto symbols = run(main); + EXPECT_EQ(total_size(symbols), 6); } TEST_F(DocumentSymbol, Method) { @@ -126,8 +125,8 @@ struct _0 { )cpp"; - auto res = run(main); - EXPECT_EQ(total_size(res), 7); + auto symbols = run(main); + EXPECT_EQ(total_size(symbols), 7); } TEST_F(DocumentSymbol, Enum) { @@ -147,8 +146,8 @@ enum B { )cpp"; - auto res = run(main); - EXPECT_EQ(total_size(res), 8); + auto symbols = run(main); + EXPECT_EQ(total_size(symbols), 8); } TEST_F(DocumentSymbol, TopLevelVariable) { @@ -157,8 +156,8 @@ constexpr auto x = 1; int y = 2; )cpp"; - auto res = run(main); - EXPECT_EQ(total_size(res), 2); + auto symbols = run(main); + EXPECT_EQ(total_size(symbols), 2); } TEST_F(DocumentSymbol, Macro) { @@ -174,14 +173,14 @@ VAR(test) )cpp"; - auto res = run(main); + auto symbols = run(main); // clang-format off /// FIXME: /// Fix range for macro expansion. Current out put is: // -// debug(res); +// debug(symbols); // // kind: Class, name:test, detail:, deprecated:false, range: {"end":{"character":0,"line":5},"start":{"character":0,"line":3}}, children_num:1 // kind: Field, name:x, detail:int, deprecated:false, range: {"end":{"character":3,"line":4},"start":{"character":4,"line":4}}, children_num:0 @@ -189,7 +188,7 @@ VAR(test) // clang-format on - /// EXPECT_EQ(total_size(res), 3); + /// EXPECT_EQ(total_size(symbols), 3); } TEST_F(DocumentSymbol, WithHeader) {