handle_document_symbol.cpp 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. // Part of the Carbon Language project, under the Apache License v2.0 with LLVM
  2. // Exceptions. See /LICENSE for license information.
  3. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  4. #include <optional>
  5. #include <string>
  6. #include <utility>
  7. #include <vector>
  8. #include "common/check.h"
  9. #include "toolchain/language_server/handle.h"
  10. #include "toolchain/lex/token_index.h"
  11. #include "toolchain/lex/token_kind.h"
  12. #include "toolchain/parse/node_ids.h"
  13. #include "toolchain/parse/node_kind.h"
  14. #include "toolchain/parse/tree_and_subtrees.h"
  15. namespace Carbon::LanguageServer {
  16. // Returns the token of first child of kind IdentifierNameMaybeBeforeSignature
  17. // or IdentifierNameNotBeforeSignature.
  18. static auto GetSymbolIdentifier(const Parse::TreeAndSubtrees& tree_and_subtrees,
  19. Parse::NodeId node)
  20. -> std::optional<Lex::TokenIndex> {
  21. const auto& tokens = tree_and_subtrees.tree().tokens();
  22. for (auto child : tree_and_subtrees.children(node)) {
  23. switch (tree_and_subtrees.tree().node_kind(child)) {
  24. case Parse::NodeKind::IdentifierNameMaybeBeforeSignature:
  25. case Parse::NodeKind::IdentifierNameNotBeforeSignature: {
  26. auto token = tree_and_subtrees.tree().node_token(child);
  27. if (tokens.GetKind(token) == Lex::TokenKind::Identifier) {
  28. return token;
  29. }
  30. break;
  31. }
  32. default:
  33. break;
  34. }
  35. }
  36. return std::nullopt;
  37. }
  38. // Helper class to collect all symbols during traversal of AST.
  39. // Symbols are "open" when their signature has been declared but their body is
  40. // still ongoing. New symbols are added to last open symbol, or top level list
  41. // if no symbols are open.
  42. class SymbolStore {
  43. public:
  44. // Adds a symbol with no children.
  45. auto AddSymbol(clang::clangd::DocumentSymbol symbol) -> void {
  46. if (open_symbols_.empty()) {
  47. top_level_symbols_.push_back(std::move(symbol));
  48. } else {
  49. open_symbols_.back().children.push_back(symbol);
  50. }
  51. }
  52. // Starts a symbol potentially with children.
  53. auto StartSymbol(clang::clangd::DocumentSymbol symbol) -> void {
  54. open_symbols_.push_back(std::move(symbol));
  55. }
  56. auto HasOpenSymbol() const -> bool { return !open_symbols_.empty(); }
  57. // Completes a symbol, appending to parent list.
  58. auto EndSymbol() -> void {
  59. CARBON_CHECK(HasOpenSymbol());
  60. AddSymbol(open_symbols_.pop_back_val());
  61. }
  62. // Returns final top level symbols.
  63. auto Collect() -> std::vector<clang::clangd::DocumentSymbol> {
  64. CARBON_CHECK(!HasOpenSymbol());
  65. return std::move(top_level_symbols_);
  66. }
  67. private:
  68. std::vector<clang::clangd::DocumentSymbol> top_level_symbols_;
  69. llvm::SmallVector<clang::clangd::DocumentSymbol> open_symbols_;
  70. };
  71. // Constructs a Range from a closed interval of tokens [start, end].
  72. static auto GetTokenRange(const Lex::TokenizedBuffer& tokens,
  73. Lex::TokenIndex start, Lex::TokenIndex end)
  74. -> clang::clangd::Range {
  75. auto start_line = tokens.GetLine(start);
  76. auto start_col = tokens.GetColumnNumber(start);
  77. auto [end_line, end_col] = tokens.GetEndLoc(end);
  78. return clang::clangd::Range{
  79. .start = {.line = start_line.index, .character = start_col - 1},
  80. .end = {.line = end_line.index, .character = end_col - 1},
  81. };
  82. }
  83. // Finds a spanning range for the provided definition / declaration ast node.
  84. // In the case of a definition start, will include the body as well.
  85. static auto GetSymbolRange(const Parse::TreeAndSubtrees& tree_and_subtrees,
  86. const Parse::NodeId& ast_node)
  87. -> clang::clangd::Range {
  88. const auto& tokens = tree_and_subtrees.tree().tokens();
  89. // The left-most node will always be the first node in postorder traversal.
  90. auto start_node = *tree_and_subtrees.postorder(ast_node).begin();
  91. auto start_token = tree_and_subtrees.tree().node_token(start_node);
  92. auto end_token = tree_and_subtrees.tree().node_token(ast_node);
  93. if (tokens.GetKind(end_token).is_opening_symbol()) {
  94. // DefinitionStart nodes use an opening token, so find its closing token to
  95. // span the entire class/function body.
  96. return GetTokenRange(tokens, start_token,
  97. tokens.GetMatchedClosingToken(end_token));
  98. } else {
  99. return GetTokenRange(tokens, start_token, end_token);
  100. }
  101. }
  102. auto HandleDocumentSymbol(
  103. Context& context, const clang::clangd::DocumentSymbolParams& params,
  104. llvm::function_ref<
  105. auto(llvm::Expected<std::vector<clang::clangd::DocumentSymbol>>)->void>
  106. on_done) -> void {
  107. auto* file = context.LookupFile(params.textDocument.uri.file());
  108. if (!file) {
  109. return;
  110. }
  111. const auto& tree_and_subtrees = file->tree_and_subtrees();
  112. const auto& tree = tree_and_subtrees.tree();
  113. const auto& tokens = tree.tokens();
  114. SymbolStore symbols;
  115. for (const auto& node_id : tree.postorder()) {
  116. auto node_kind = tree.node_kind(node_id);
  117. clang::clangd::SymbolKind symbol_kind;
  118. bool is_leaf = false;
  119. switch (node_kind) {
  120. case Parse::NodeKind::FunctionDecl:
  121. is_leaf = true;
  122. symbol_kind = clang::clangd::SymbolKind::Function;
  123. break;
  124. case Parse::NodeKind::FunctionDefinitionStart:
  125. symbol_kind = clang::clangd::SymbolKind::Function;
  126. break;
  127. case Parse::NodeKind::Namespace:
  128. symbol_kind = clang::clangd::SymbolKind::Namespace;
  129. break;
  130. case Parse::NodeKind::InterfaceDefinitionStart:
  131. case Parse::NodeKind::NamedConstraintDefinitionStart:
  132. symbol_kind = clang::clangd::SymbolKind::Interface;
  133. break;
  134. case Parse::NodeKind::ClassDecl:
  135. is_leaf = true;
  136. symbol_kind = clang::clangd::SymbolKind::Class;
  137. break;
  138. case Parse::NodeKind::ClassDefinitionStart:
  139. symbol_kind = clang::clangd::SymbolKind::Class;
  140. break;
  141. case Parse::NodeKind::FunctionDefinition:
  142. case Parse::NodeKind::NamedConstraintDefinition:
  143. case Parse::NodeKind::InterfaceDefinition:
  144. case Parse::NodeKind::ClassDefinition: {
  145. if (symbols.HasOpenSymbol()) {
  146. // Symbols definition has completed, pop it from stack and add to
  147. // parent/root.
  148. symbols.EndSymbol();
  149. }
  150. continue;
  151. }
  152. default:
  153. continue;
  154. }
  155. if (auto identifier = GetSymbolIdentifier(tree_and_subtrees, node_id)) {
  156. clang::clangd::DocumentSymbol symbol{
  157. .name = std::string(tokens.GetTokenText(*identifier)),
  158. .kind = symbol_kind,
  159. .range = GetSymbolRange(tree_and_subtrees, node_id),
  160. .selectionRange = GetTokenRange(tokens, *identifier, *identifier),
  161. };
  162. if (is_leaf) {
  163. symbols.AddSymbol(std::move(symbol));
  164. } else {
  165. symbols.StartSymbol(std::move(symbol));
  166. }
  167. }
  168. }
  169. on_done(symbols.Collect());
  170. }
  171. } // namespace Carbon::LanguageServer