1 // Copyright 2017-2018 ccls Authors
2 // SPDX-License-Identifier: Apache-2.0
3 
4 #include "message_handler.hh"
5 #include "query.hh"
6 
7 namespace ccls {
8 namespace {
9 struct MarkedString {
10   std::optional<std::string> language;
11   std::string value;
12 };
13 struct Hover {
14   std::vector<MarkedString> contents;
15   std::optional<lsRange> range;
16 };
17 
reflect(JsonWriter & vis,MarkedString & v)18 void reflect(JsonWriter &vis, MarkedString &v) {
19   // If there is a language, emit a `{language:string, value:string}` object. If
20   // not, emit a string.
21   if (v.language) {
22     vis.startObject();
23     REFLECT_MEMBER(language);
24     REFLECT_MEMBER(value);
25     vis.endObject();
26   } else {
27     reflect(vis, v.value);
28   }
29 }
30 REFLECT_STRUCT(Hover, contents, range);
31 
languageIdentifier(LanguageId lang)32 const char *languageIdentifier(LanguageId lang) {
33   switch (lang) {
34   // clang-format off
35   case LanguageId::C: return "c";
36   case LanguageId::Cpp: return "cpp";
37   case LanguageId::ObjC: return "objective-c";
38   case LanguageId::ObjCpp: return "objective-cpp";
39   default: return "";
40     // clang-format on
41   }
42 }
43 
44 // Returns the hover or detailed name for `sym`, if any.
45 std::pair<std::optional<MarkedString>, std::optional<MarkedString>>
getHover(DB * db,LanguageId lang,SymbolRef sym,int file_id)46 getHover(DB *db, LanguageId lang, SymbolRef sym, int file_id) {
47   const char *comments = nullptr;
48   std::optional<MarkedString> ls_comments, hover;
49   withEntity(db, sym, [&](const auto &entity) {
50     for (auto &d : entity.def) {
51       if (!comments && d.comments[0])
52         comments = d.comments;
53       if (d.spell) {
54         if (d.comments[0])
55           comments = d.comments;
56         if (const char *s =
57                 d.hover[0] ? d.hover
58                            : d.detailed_name[0] ? d.detailed_name : nullptr) {
59           if (!hover)
60             hover = {languageIdentifier(lang), s};
61           else if (strlen(s) > hover->value.size())
62             hover->value = s;
63         }
64         if (d.spell->file_id == file_id)
65           break;
66       }
67     }
68     if (!hover && entity.def.size()) {
69       auto &d = entity.def[0];
70       hover = {languageIdentifier(lang)};
71       if (d.hover[0])
72         hover->value = d.hover;
73       else if (d.detailed_name[0])
74         hover->value = d.detailed_name;
75     }
76     if (comments)
77       ls_comments = MarkedString{std::nullopt, comments};
78   });
79   return {hover, ls_comments};
80 }
81 } // namespace
82 
textDocument_hover(TextDocumentPositionParam & param,ReplyOnce & reply)83 void MessageHandler::textDocument_hover(TextDocumentPositionParam &param,
84                                         ReplyOnce &reply) {
85   auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply);
86   if (!wf)
87     return;
88 
89   Hover result;
90   for (SymbolRef sym : findSymbolsAtLocation(wf, file, param.position)) {
91     std::optional<lsRange> ls_range =
92         getLsRange(wfiles->getFile(file->def->path), sym.range);
93     if (!ls_range)
94       continue;
95 
96     auto [hover, comments] = getHover(db, file->def->language, sym, file->id);
97     if (comments || hover) {
98       result.range = *ls_range;
99       if (comments)
100         result.contents.push_back(*comments);
101       if (hover)
102         result.contents.push_back(*hover);
103       break;
104     }
105   }
106 
107   reply(result);
108 }
109 } // namespace ccls
110