1 // Copyright 2017-2018 ccls Authors
2 // SPDX-License-Identifier: Apache-2.0
3 
4 #include "message_handler.hh"
5 #include "pipeline.hh"
6 #include "query.hh"
7 
8 #include <llvm/Support/FormatVariadic.h>
9 
10 #include <rapidjson/document.h>
11 #include <rapidjson/writer.h>
12 
13 #include <unordered_set>
14 
15 namespace ccls {
16 namespace {
17 struct CodeAction {
18   std::string title;
19   const char *kind = "quickfix";
20   WorkspaceEdit edit;
21 };
22 REFLECT_STRUCT(CodeAction, title, kind, edit);
23 } // namespace
textDocument_codeAction(CodeActionParam & param,ReplyOnce & reply)24 void MessageHandler::textDocument_codeAction(CodeActionParam &param,
25                                              ReplyOnce &reply) {
26   WorkingFile *wf = findOrFail(param.textDocument.uri.getPath(), reply).second;
27   if (!wf)
28     return;
29   std::vector<CodeAction> result;
30   std::vector<Diagnostic> diagnostics;
31   wfiles->withLock([&]() { diagnostics = wf->diagnostics; });
32   for (Diagnostic &diag : diagnostics)
33     if (diag.fixits_.size() &&
34         (param.range.intersects(diag.range) ||
35          llvm::any_of(diag.fixits_, [&](const TextEdit &edit) {
36            return param.range.intersects(edit.range);
37          }))) {
38       CodeAction &cmd = result.emplace_back();
39       cmd.title = "FixIt: " + diag.message;
40       auto &edit = cmd.edit.documentChanges.emplace_back();
41       edit.textDocument.uri = param.textDocument.uri;
42       edit.textDocument.version = wf->version;
43       edit.edits = diag.fixits_;
44     }
45   reply(result);
46 }
47 
48 namespace {
49 struct Cmd_xref {
50   Usr usr;
51   Kind kind;
52   std::string field;
53 };
54 struct Command {
55   std::string title;
56   std::string command;
57   std::vector<std::string> arguments;
58 };
59 struct CodeLens {
60   lsRange range;
61   std::optional<Command> command;
62 };
63 REFLECT_STRUCT(Cmd_xref, usr, kind, field);
64 REFLECT_STRUCT(Command, title, command, arguments);
65 REFLECT_STRUCT(CodeLens, range, command);
66 
toString(T & v)67 template <typename T> std::string toString(T &v) {
68   rapidjson::StringBuffer output;
69   rapidjson::Writer<rapidjson::StringBuffer> writer(output);
70   JsonWriter json_writer(&writer);
71   reflect(json_writer, v);
72   return output.GetString();
73 }
74 
75 struct CommonCodeLensParams {
76   std::vector<CodeLens> *result;
77   DB *db;
78   WorkingFile *wfile;
79 };
80 } // namespace
81 
textDocument_codeLens(TextDocumentParam & param,ReplyOnce & reply)82 void MessageHandler::textDocument_codeLens(TextDocumentParam &param,
83                                            ReplyOnce &reply) {
84   auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply);
85   if (!wf)
86     return;
87 
88   std::vector<CodeLens> result;
89   auto add = [&, wf = wf](const char *singular, Cmd_xref show, Range range,
90                           int num, bool force_display = false) {
91     if (!num && !force_display)
92       return;
93     std::optional<lsRange> ls_range = getLsRange(wf, range);
94     if (!ls_range)
95       return;
96     CodeLens &code_lens = result.emplace_back();
97     code_lens.range = *ls_range;
98     code_lens.command = Command();
99     code_lens.command->command = std::string(ccls_xref);
100     bool plural = num > 1 && singular[strlen(singular) - 1] != 'd';
101     code_lens.command->title =
102         llvm::formatv("{0} {1}{2}", num, singular, plural ? "s" : "").str();
103     code_lens.command->arguments.push_back(toString(show));
104   };
105 
106   std::unordered_set<Range> seen;
107   for (auto [sym, refcnt] : file->symbol2refcnt) {
108     if (refcnt <= 0 || !sym.extent.valid() || !seen.insert(sym.range).second)
109       continue;
110     switch (sym.kind) {
111     case Kind::Func: {
112       QueryFunc &func = db->getFunc(sym);
113       const QueryFunc::Def *def = func.anyDef();
114       if (!def)
115         continue;
116       std::vector<Use> base_uses = getUsesForAllBases(db, func);
117       std::vector<Use> derived_uses = getUsesForAllDerived(db, func);
118       add("ref", {sym.usr, Kind::Func, "uses"}, sym.range, func.uses.size(),
119           base_uses.empty());
120       if (base_uses.size())
121         add("b.ref", {sym.usr, Kind::Func, "bases uses"}, sym.range,
122             base_uses.size());
123       if (derived_uses.size())
124         add("d.ref", {sym.usr, Kind::Func, "derived uses"}, sym.range,
125             derived_uses.size());
126       if (base_uses.empty())
127         add("base", {sym.usr, Kind::Func, "bases"}, sym.range,
128             def->bases.size());
129       add("derived", {sym.usr, Kind::Func, "derived"}, sym.range,
130           func.derived.size());
131       break;
132     }
133     case Kind::Type: {
134       QueryType &type = db->getType(sym);
135       add("ref", {sym.usr, Kind::Type, "uses"}, sym.range, type.uses.size(),
136           true);
137       add("derived", {sym.usr, Kind::Type, "derived"}, sym.range,
138           type.derived.size());
139       add("var", {sym.usr, Kind::Type, "instances"}, sym.range,
140           type.instances.size());
141       break;
142     }
143     case Kind::Var: {
144       QueryVar &var = db->getVar(sym);
145       const QueryVar::Def *def = var.anyDef();
146       if (!def || (def->is_local() && !g_config->codeLens.localVariables))
147         continue;
148       add("ref", {sym.usr, Kind::Var, "uses"}, sym.range, var.uses.size(),
149           def->kind != SymbolKind::Macro);
150       break;
151     }
152     case Kind::File:
153     case Kind::Invalid:
154       llvm_unreachable("");
155     };
156   }
157 
158   reply(result);
159 }
160 
workspace_executeCommand(JsonReader & reader,ReplyOnce & reply)161 void MessageHandler::workspace_executeCommand(JsonReader &reader,
162                                               ReplyOnce &reply) {
163   Command param;
164   reflect(reader, param);
165   if (param.arguments.empty()) {
166     return;
167   }
168   rapidjson::Document reader1;
169   reader1.Parse(param.arguments[0].c_str());
170   JsonReader json_reader{&reader1};
171   if (param.command == ccls_xref) {
172     Cmd_xref cmd;
173     reflect(json_reader, cmd);
174     std::vector<Location> result;
175     auto map = [&](auto &&uses) {
176       for (auto &use : uses)
177         if (auto loc = getLsLocation(db, wfiles, use))
178           result.push_back(std::move(*loc));
179     };
180     switch (cmd.kind) {
181     case Kind::Func: {
182       QueryFunc &func = db->getFunc(cmd.usr);
183       if (cmd.field == "bases") {
184         if (auto *def = func.anyDef())
185           map(getFuncDeclarations(db, def->bases));
186       } else if (cmd.field == "bases uses") {
187         map(getUsesForAllBases(db, func));
188       } else if (cmd.field == "derived") {
189         map(getFuncDeclarations(db, func.derived));
190       } else if (cmd.field == "derived uses") {
191         map(getUsesForAllDerived(db, func));
192       } else if (cmd.field == "uses") {
193         map(func.uses);
194       }
195       break;
196     }
197     case Kind::Type: {
198       QueryType &type = db->getType(cmd.usr);
199       if (cmd.field == "derived") {
200         map(getTypeDeclarations(db, type.derived));
201       } else if (cmd.field == "instances") {
202         map(getVarDeclarations(db, type.instances, 7));
203       } else if (cmd.field == "uses") {
204         map(type.uses);
205       }
206       break;
207     }
208     case Kind::Var: {
209       QueryVar &var = db->getVar(cmd.usr);
210       if (cmd.field == "uses")
211         map(var.uses);
212       break;
213     }
214     default:
215       break;
216     }
217     reply(result);
218   }
219 }
220 } // namespace ccls
221