handle_document_symbol.cpp 6.6 KB

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