Skip to content

Commit 664f952

Browse files
committed
Implement textDocument/switchSourceheader
When the current file is X.cc, there might be multiple X.h. Use a heuristic to find the best X.h. Vote for each interesting symbol's definitions (for header) or declarations (for non-header). Select the file with the most votes. If `file_id2cnt` is empty, use a simpler heuristic.
1 parent cc13ced commit 664f952

File tree

4 files changed

+72
-1
lines changed

4 files changed

+72
-1
lines changed

src/message_handler.cc

+1
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ MessageHandler::MessageHandler() {
216216
bind("textDocument/semanticTokens/full", &MessageHandler::textDocument_semanticTokensFull);
217217
bind("textDocument/semanticTokens/range", &MessageHandler::textDocument_semanticTokensRange);
218218
bind("textDocument/signatureHelp", &MessageHandler::textDocument_signatureHelp);
219+
bind("textDocument/switchSourceHeader", &MessageHandler::textDocument_switchSourceHeader);
219220
bind("textDocument/typeDefinition", &MessageHandler::textDocument_typeDefinition);
220221
bind("workspace/didChangeConfiguration", &MessageHandler::workspace_didChangeConfiguration);
221222
bind("workspace/didChangeWatchedFiles", &MessageHandler::workspace_didChangeWatchedFiles);

src/message_handler.hh

+1
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ private:
311311
void textDocument_semanticTokensFull(TextDocumentParam &, ReplyOnce &);
312312
void textDocument_semanticTokensRange(SemanticTokensRangeParams &, ReplyOnce &);
313313
void textDocument_signatureHelp(TextDocumentPositionParam &, ReplyOnce &);
314+
void textDocument_switchSourceHeader(TextDocumentIdentifier &, ReplyOnce &);
314315
void textDocument_typeDefinition(TextDocumentPositionParam &, ReplyOnce &);
315316
void workspace_didChangeConfiguration(EmptyParam &);
316317
void workspace_didChangeWatchedFiles(DidChangeWatchedFilesParam &);

src/messages/textDocument_document.cc

+69
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,17 @@
33

44
#include "message_handler.hh"
55
#include "pipeline.hh"
6+
#include "project.hh"
67
#include "query.hh"
78

9+
#include <llvm/ADT/STLExtras.h>
10+
#include <llvm/ADT/StringRef.h>
11+
#include <llvm/Support/Path.h>
12+
813
#include <algorithm>
914

15+
using namespace llvm;
16+
1017
MAKE_HASHABLE(ccls::SymbolIdx, t.usr, t.kind);
1118

1219
namespace ccls {
@@ -226,4 +233,66 @@ void MessageHandler::textDocument_documentSymbol(JsonReader &reader,
226233
reply(result);
227234
}
228235
}
236+
237+
void MessageHandler::textDocument_switchSourceHeader(TextDocumentIdentifier &param, ReplyOnce &reply) {
238+
QueryFile *file;
239+
WorkingFile *wf;
240+
std::tie(file, wf) = findOrFail(param.uri.getPath(), reply);
241+
if (!wf)
242+
return reply(JsonNull{});
243+
int file_id = file->id;
244+
245+
DocumentUri result;
246+
const std::string &path = wf->filename;
247+
bool is_hdr = lookupExtension(path).second;
248+
249+
// Vote for each interesting symbol's definitions (for header) or declarations (for non-header).
250+
// Select the file with the most votes.
251+
// Ignore Type symbols to skip class forward declarations and namespaces.
252+
std::unordered_map<int, int> file_id2cnt;
253+
for (auto [sym, refcnt] : file->symbol2refcnt) {
254+
if (refcnt <= 0 || !sym.extent.valid() || sym.kind == Kind::Type)
255+
continue;
256+
257+
if (is_hdr) {
258+
withEntity(db, sym, [&](const auto &entity) {
259+
for (auto &def : entity.def)
260+
if (def.spell && def.file_id != file_id)
261+
++file_id2cnt[def.file_id];
262+
});
263+
} else {
264+
for (DeclRef dr : getNonDefDeclarations(db, sym))
265+
if (dr.file_id != file_id)
266+
++file_id2cnt[dr.file_id];
267+
}
268+
}
269+
if (file_id2cnt.size()) {
270+
auto best = file_id2cnt.begin();
271+
for (auto it = file_id2cnt.begin(); it != file_id2cnt.end(); ++it)
272+
if (it->second > best->second || (it->second == best->second && it->first < best->first))
273+
best = it;
274+
if (auto &def = db->files[best->first].def)
275+
return reply(DocumentUri::fromPath(def->path));
276+
}
277+
278+
if (is_hdr) {
279+
// Check if `path` is in a #include entry.
280+
for (QueryFile &file1 : db->files) {
281+
auto &def = file1.def;
282+
if (!def || lookupExtension(def->path).second)
283+
continue;
284+
for (IndexInclude &include : def->includes)
285+
if (path == include.resolved_path)
286+
return reply(DocumentUri::fromPath(def->path));
287+
}
288+
return reply(JsonNull{});
289+
}
290+
291+
// Otherwise, find the #include with the same stem.
292+
StringRef stem = sys::path::stem(path);
293+
for (IndexInclude &include : file->def->includes)
294+
if (sys::path::stem(include.resolved_path) == stem)
295+
return reply(DocumentUri::fromPath(std::string(include.resolved_path)));
296+
reply(JsonNull{});
297+
}
229298
} // namespace ccls

src/query.hh

+1-1
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ using Lid2file_id = std::unordered_map<int, int>;
144144
// The query database is heavily optimized for fast queries. It is stored
145145
// in-memory.
146146
struct DB {
147-
std::vector<QueryFile> files;
147+
llvm::SmallVector<QueryFile, 0> files;
148148
llvm::StringMap<int> name2file_id;
149149
llvm::DenseMap<Usr, int, DenseMapInfoForUsr> func_usr, type_usr, var_usr;
150150
llvm::SmallVector<QueryFunc, 0> funcs;

0 commit comments

Comments
 (0)