Support textDocument/declaration & LocationLink

textDocument/{declaration,definition,typeDefinition} return either LocationLink[] or Location[]
Add an initialization option client.linkSupport . When it is false, ccls will return Location[] disregarding client's linkSupport.
`struct LocationLink` does not include originSelectionRange as it is wasteful.
This commit is contained in:
Fangrui Song 2018-12-16 19:53:00 -08:00
parent 37a9ad3f81
commit fc38442967
10 changed files with 152 additions and 99 deletions

View File

@ -85,6 +85,8 @@ struct Config {
struct ClientCapability {
// TextDocumentClientCapabilities.documentSymbol.hierarchicalDocumentSymbolSupport
bool hierarchicalDocumentSymbolSupport = true;
// TextDocumentClientCapabilities.definition.linkSupport
bool linkSupport = true;
// TextDocumentClientCapabilities.completion.completionItem.snippetSupport
bool snippetSupport = true;
} client;
@ -260,30 +262,28 @@ struct Config {
} xref;
};
REFLECT_STRUCT(Config::Clang, excludeArgs, extraArgs, pathMappings,
resourceDir);
resourceDir);
REFLECT_STRUCT(Config::ClientCapability, hierarchicalDocumentSymbolSupport,
snippetSupport);
linkSupport, snippetSupport);
REFLECT_STRUCT(Config::CodeLens, localVariables);
REFLECT_STRUCT(Config::Completion::Include, blacklist, maxPathSize,
suffixWhitelist, whitelist);
suffixWhitelist, whitelist);
REFLECT_STRUCT(Config::Completion, caseSensitivity, detailedLabel,
dropOldRequests, duplicateOptional, filterAndSort, include,
maxNum);
dropOldRequests, duplicateOptional, filterAndSort, include,
maxNum);
REFLECT_STRUCT(Config::Diagnostics, blacklist, onChange, onOpen, onSave,
spellChecking, whitelist)
REFLECT_STRUCT(Config::Highlight, largeFileSize, lsRanges, blacklist,
whitelist)
spellChecking, whitelist)
REFLECT_STRUCT(Config::Highlight, largeFileSize, lsRanges, blacklist, whitelist)
REFLECT_STRUCT(Config::Index, blacklist, comments, initialBlacklist,
initialWhitelist, multiVersion, multiVersionBlacklist,
multiVersionWhitelist, onChange, threads, trackDependency,
whitelist);
initialWhitelist, multiVersion, multiVersionBlacklist,
multiVersionWhitelist, onChange, threads, trackDependency,
whitelist);
REFLECT_STRUCT(Config::Session, maxNum);
REFLECT_STRUCT(Config::WorkspaceSymbol, caseSensitivity, maxNum, sort);
REFLECT_STRUCT(Config::Xref, maxNum);
REFLECT_STRUCT(Config, compilationDatabaseCommand,
compilationDatabaseDirectory, cacheDirectory, cacheFormat,
clang, client, codeLens, completion, diagnostics, highlight,
index, session, workspaceSymbol, xref);
REFLECT_STRUCT(Config, compilationDatabaseCommand, compilationDatabaseDirectory,
cacheDirectory, cacheFormat, clang, client, codeLens, completion,
diagnostics, highlight, index, session, workspaceSymbol, xref);
extern Config *g_config;

View File

@ -60,10 +60,6 @@ DocumentUri DocumentUri::FromPath(const std::string &path) {
return result;
}
bool DocumentUri::operator==(const DocumentUri &other) const {
return raw_uri == other.raw_uri;
}
void DocumentUri::SetPath(const std::string &path) {
// file:///c%3A/Users/jacob/Desktop/superindex/indexer/full_tests
raw_uri = path;

View File

@ -72,7 +72,8 @@ constexpr char window_showMessage[] = "window/showMessage";
struct DocumentUri {
static DocumentUri FromPath(const std::string &path);
bool operator==(const DocumentUri &other) const;
bool operator==(const DocumentUri &o) const { return raw_uri == o.raw_uri; }
bool operator<(const DocumentUri &o) const { return raw_uri < o.raw_uri; }
void SetPath(const std::string &path);
std::string GetPath() const;
@ -119,8 +120,26 @@ struct Location {
return uri == o.uri && range == o.range;
}
bool operator<(const Location &o) const {
return !(uri.raw_uri == o.uri.raw_uri) ? uri.raw_uri < o.uri.raw_uri
: range < o.range;
return !(uri == o.uri) ? uri < o.uri : range < o.range;
}
};
struct LocationLink {
std::string targetUri;
lsRange targetRange;
lsRange targetSelectionRange;
explicit operator bool() const { return targetUri.size(); }
explicit operator Location() && {
return {DocumentUri{std::move(targetUri)}, targetSelectionRange};
}
bool operator==(const LocationLink &o) const {
return targetUri == o.targetUri &&
targetSelectionRange == o.targetSelectionRange;
}
bool operator<(const LocationLink &o) const {
return !(targetUri == o.targetUri)
? targetUri < o.targetUri
: targetSelectionRange < o.targetSelectionRange;
}
};

View File

@ -116,6 +116,21 @@ void ReplyOnce::NotReady(bool file) {
Error(ErrorCode::InternalError, "not indexed");
}
void ReplyOnce::ReplyLocationLink(std::vector<LocationLink> &result) {
std::sort(result.begin(), result.end());
result.erase(std::unique(result.begin(), result.end()), result.end());
if (result.size() > g_config->xref.maxNum)
result.resize(g_config->xref.maxNum);
if (g_config->client.linkSupport) {
(*this)(result);
} else {
std::vector<Location> result1;
for (auto &loc : result)
result1.emplace_back(std::move(loc));
(*this)(result1);
}
}
void MessageHandler::Bind(const char *method,
void (MessageHandler::*handler)(JsonReader &)) {
method2notification[method] = [this, handler](JsonReader &reader) {
@ -155,6 +170,7 @@ void MessageHandler::Bind(const char *method,
}
MessageHandler::MessageHandler() {
// clang-format off
Bind("$ccls/call", &MessageHandler::ccls_call);
Bind("$ccls/fileInfo", &MessageHandler::ccls_fileInfo);
Bind("$ccls/info", &MessageHandler::ccls_info);
@ -169,39 +185,31 @@ MessageHandler::MessageHandler() {
Bind("textDocument/codeAction", &MessageHandler::textDocument_codeAction);
Bind("textDocument/codeLens", &MessageHandler::textDocument_codeLens);
Bind("textDocument/completion", &MessageHandler::textDocument_completion);
Bind("textDocument/declaration", &MessageHandler::textDocument_declaration);
Bind("textDocument/definition", &MessageHandler::textDocument_definition);
Bind("textDocument/didChange", &MessageHandler::textDocument_didChange);
Bind("textDocument/didClose", &MessageHandler::textDocument_didClose);
Bind("textDocument/didOpen", &MessageHandler::textDocument_didOpen);
Bind("textDocument/didSave", &MessageHandler::textDocument_didSave);
Bind("textDocument/documentHighlight",
&MessageHandler::textDocument_documentHighlight);
Bind("textDocument/documentHighlight", &MessageHandler::textDocument_documentHighlight);
Bind("textDocument/documentLink", &MessageHandler::textDocument_documentLink);
Bind("textDocument/documentSymbol",
&MessageHandler::textDocument_documentSymbol);
Bind("textDocument/documentSymbol", &MessageHandler::textDocument_documentSymbol);
Bind("textDocument/foldingRange", &MessageHandler::textDocument_foldingRange);
Bind("textDocument/formatting", &MessageHandler::textDocument_formatting);
Bind("textDocument/hover", &MessageHandler::textDocument_hover);
Bind("textDocument/implementation",
&MessageHandler::textDocument_implementation);
Bind("textDocument/onTypeFormatting",
&MessageHandler::textDocument_onTypeFormatting);
Bind("textDocument/rangeFormatting",
&MessageHandler::textDocument_rangeFormatting);
Bind("textDocument/implementation", &MessageHandler::textDocument_implementation);
Bind("textDocument/onTypeFormatting", &MessageHandler::textDocument_onTypeFormatting);
Bind("textDocument/rangeFormatting", &MessageHandler::textDocument_rangeFormatting);
Bind("textDocument/references", &MessageHandler::textDocument_references);
Bind("textDocument/rename", &MessageHandler::textDocument_rename);
Bind("textDocument/signatureHelp",
&MessageHandler::textDocument_signatureHelp);
Bind("textDocument/typeDefinition",
&MessageHandler::textDocument_typeDefinition);
Bind("workspace/didChangeConfiguration",
&MessageHandler::workspace_didChangeConfiguration);
Bind("workspace/didChangeWatchedFiles",
&MessageHandler::workspace_didChangeWatchedFiles);
Bind("workspace/didChangeWorkspaceFolders",
&MessageHandler::workspace_didChangeWorkspaceFolders);
Bind("textDocument/signatureHelp", &MessageHandler::textDocument_signatureHelp);
Bind("textDocument/typeDefinition", &MessageHandler::textDocument_typeDefinition);
Bind("workspace/didChangeConfiguration", &MessageHandler::workspace_didChangeConfiguration);
Bind("workspace/didChangeWatchedFiles", &MessageHandler::workspace_didChangeWatchedFiles);
Bind("workspace/didChangeWorkspaceFolders", &MessageHandler::workspace_didChangeWorkspaceFolders);
Bind("workspace/executeCommand", &MessageHandler::workspace_executeCommand);
Bind("workspace/symbol", &MessageHandler::workspace_symbol);
// clang-format on
}
void MessageHandler::Run(InMessage &msg) {

View File

@ -189,6 +189,7 @@ REFLECT_STRUCT(ResponseError, code, message);
REFLECT_STRUCT(Position, line, character);
REFLECT_STRUCT(lsRange, start, end);
REFLECT_STRUCT(Location, uri, range);
REFLECT_STRUCT(LocationLink, targetUri, targetRange, targetSelectionRange);
REFLECT_UNDERLYING_B(SymbolKind);
REFLECT_STRUCT(TextDocumentIdentifier, uri);
REFLECT_STRUCT(TextDocumentItem, uri, languageId, version, text);
@ -210,6 +211,7 @@ struct ReplyOnce {
pipeline::ReplyError(id, [&](JsonWriter &w) { Reflect(w, err); });
}
void NotReady(bool file);
void ReplyLocationLink(std::vector<LocationLink> &result);
};
struct MessageHandler {
@ -252,6 +254,7 @@ private:
void textDocument_codeAction(CodeActionParam &, ReplyOnce &);
void textDocument_codeLens(TextDocumentParam &, ReplyOnce &);
void textDocument_completion(CompletionParam &, ReplyOnce &);
void textDocument_declaration(TextDocumentPositionParam &, ReplyOnce &);
void textDocument_definition(TextDocumentPositionParam &, ReplyOnce &);
void textDocument_didChange(TextDocumentDidChangeParam &);
void textDocument_didClose(TextDocumentParam &);

View File

@ -51,12 +51,14 @@ void MessageHandler::ccls_vars(JsonReader &reader, ReplyOnce &reply) {
usr = def->type;
[[fallthrough]];
}
case Kind::Type:
result = GetLsLocations(
db, wfiles,
GetVarDeclarations(db, db->Type(usr).instances, param.kind));
case Kind::Type: {
for (DeclRef dr :
GetVarDeclarations(db, db->Type(usr).instances, param.kind))
if (auto loc = GetLocationLink(db, wfiles, dr))
result.push_back(Location(std::move(loc)));
break;
}
}
}
reply(result);
}

View File

@ -173,6 +173,11 @@ struct TextDocumentClientCap {
} completionItem;
} completion;
// Ignore declaration, implementation, typeDefinition
struct LinkSupport {
bool linkSupport = false;
} definition;
struct DocumentSymbol {
bool hierarchicalDocumentSymbolSupport = false;
} documentSymbol;
@ -183,7 +188,8 @@ REFLECT_STRUCT(TextDocumentClientCap::Completion::CompletionItem,
REFLECT_STRUCT(TextDocumentClientCap::Completion, completionItem);
REFLECT_STRUCT(TextDocumentClientCap::DocumentSymbol,
hierarchicalDocumentSymbolSupport);
REFLECT_STRUCT(TextDocumentClientCap, completion, documentSymbol);
REFLECT_STRUCT(TextDocumentClientCap::LinkSupport, linkSupport);
REFLECT_STRUCT(TextDocumentClientCap, completion, definition, documentSymbol);
struct ClientCap {
WorkspaceClientCap workspace;
@ -284,11 +290,13 @@ void Initialize(MessageHandler *m, InitializeParam &param, ReplyOnce &reply) {
// Client capabilities
const auto &capabilities = param.capabilities;
g_config->client.snippetSupport &=
capabilities.textDocument.completion.completionItem.snippetSupport;
g_config->client.hierarchicalDocumentSymbolSupport &=
capabilities.textDocument.documentSymbol
.hierarchicalDocumentSymbolSupport;
g_config->client.linkSupport &=
capabilities.textDocument.definition.linkSupport;
g_config->client.snippetSupport &=
capabilities.textDocument.completion.completionItem.snippetSupport;
// Ensure there is a resource directory.
if (g_config->clang.resourceDir.empty())

View File

@ -45,6 +45,27 @@ std::vector<DeclRef> GetNonDefDeclarationTargets(DB *db, SymbolRef sym) {
}
} // namespace
void MessageHandler::textDocument_declaration(TextDocumentPositionParam &param,
ReplyOnce &reply) {
int file_id;
QueryFile *file = FindFile(param.textDocument.uri.GetPath(), &file_id);
WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr;
if (!wf) {
reply.NotReady(file);
return;
}
std::vector<LocationLink> result;
Position &ls_pos = param.position;
for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position))
for (DeclRef dr : GetNonDefDeclarations(db, sym))
if (!(dr.file_id == file_id &&
dr.range.Contains(ls_pos.line, ls_pos.character)))
if (auto loc = GetLocationLink(db, wfiles, dr))
result.push_back(loc);
reply.ReplyLocationLink(result);
}
void MessageHandler::textDocument_definition(TextDocumentPositionParam &param,
ReplyOnce &reply) {
int file_id;
@ -55,54 +76,52 @@ void MessageHandler::textDocument_definition(TextDocumentPositionParam &param,
return;
}
std::vector<Location> result;
Maybe<Use> on_def;
std::vector<LocationLink> result;
Maybe<DeclRef> on_def;
Position &ls_pos = param.position;
for (SymbolRef sym : FindSymbolsAtLocation(wf, file, ls_pos, true)) {
// Special cases which are handled:
// - symbol has declaration but no definition (ie, pure virtual)
// - goto declaration while in definition of recursive type
std::vector<Use> uses;
std::vector<DeclRef> drs;
EachEntityDef(db, sym, [&](const auto &def) {
if (def.spell) {
Use spell = *def.spell;
DeclRef spell = *def.spell;
if (spell.file_id == file_id &&
spell.range.Contains(ls_pos.line, ls_pos.character)) {
on_def = spell;
uses.clear();
drs.clear();
return false;
}
uses.push_back(spell);
drs.push_back(spell);
}
return true;
});
// |uses| is empty if on a declaration/definition, otherwise it includes
// all declarations/definitions.
if (uses.empty()) {
for (Use use : GetNonDefDeclarationTargets(db, sym))
if (!(use.file_id == file_id &&
use.range.Contains(ls_pos.line, ls_pos.character)))
uses.push_back(use);
if (drs.empty()) {
for (DeclRef dr : GetNonDefDeclarationTargets(db, sym))
if (!(dr.file_id == file_id &&
dr.range.Contains(ls_pos.line, ls_pos.character)))
drs.push_back(dr);
// There is no declaration but the cursor is on a definition.
if (uses.empty() && on_def)
uses.push_back(*on_def);
if (drs.empty() && on_def)
drs.push_back(*on_def);
}
auto locs = GetLsLocations(db, wfiles, uses);
result.insert(result.end(), locs.begin(), locs.end());
for (DeclRef dr : drs)
if (auto loc = GetLocationLink(db, wfiles, dr))
result.push_back(loc);
}
if (result.size()) {
std::sort(result.begin(), result.end());
result.erase(std::unique(result.begin(), result.end()), result.end());
} else {
if (result.empty()) {
Maybe<Range> range;
// Check #include
for (const IndexInclude &include : file->def->includes) {
if (include.line == ls_pos.line) {
result.push_back(
Location{DocumentUri::FromPath(include.resolved_path)});
{DocumentUri::FromPath(include.resolved_path).raw_uri});
range = {{0, 0}, {0, 0}};
break;
}
@ -160,13 +179,13 @@ void MessageHandler::textDocument_definition(TextDocumentPositionParam &param,
if (best_sym.kind != Kind::Invalid) {
Maybe<DeclRef> dr = GetDefinitionSpell(db, best_sym);
assert(dr);
if (auto loc = GetLsLocation(db, wfiles, *dr))
result.push_back(*loc);
if (auto loc = GetLocationLink(db, wfiles, *dr))
result.push_back(loc);
}
}
}
reply(result);
reply.ReplyLocationLink(result);
}
void MessageHandler::textDocument_typeDefinition(
@ -178,17 +197,16 @@ void MessageHandler::textDocument_typeDefinition(
return;
}
std::vector<Location> result;
std::vector<LocationLink> result;
auto Add = [&](const QueryType &type) {
for (const auto &def : type.def)
if (def.spell) {
if (auto ls_loc = GetLsLocation(db, wfiles, *def.spell))
result.push_back(*ls_loc);
}
if (def.spell)
if (auto loc = GetLocationLink(db, wfiles, *def.spell))
result.push_back(loc);
if (result.empty())
for (const DeclRef &dr : type.declarations)
if (auto ls_loc = GetLsLocation(db, wfiles, dr))
result.push_back(*ls_loc);
if (auto loc = GetLocationLink(db, wfiles, dr))
result.push_back(loc);
};
for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position)) {
switch (sym.kind) {
@ -211,8 +229,6 @@ void MessageHandler::textDocument_typeDefinition(
}
}
std::sort(result.begin(), result.end());
result.erase(std::unique(result.begin(), result.end()), result.end());
reply(result);
reply.ReplyLocationLink(result);
}
} // namespace ccls

View File

@ -532,9 +532,9 @@ std::vector<Use> GetFuncDeclarations(DB *db, const std::vector<Usr> &usrs) {
std::vector<Use> GetTypeDeclarations(DB *db, const std::vector<Usr> &usrs) {
return GetDeclarations(db->type_usr, db->types, usrs);
}
std::vector<Use> GetVarDeclarations(DB *db, const std::vector<Usr> &usrs,
unsigned kind) {
std::vector<Use> ret;
std::vector<DeclRef> GetVarDeclarations(DB *db, const std::vector<Usr> &usrs,
unsigned kind) {
std::vector<DeclRef> ret;
ret.reserve(usrs.size());
for (Usr usr : usrs) {
QueryVar &var = db->Var(usr);
@ -681,17 +681,18 @@ std::optional<Location> GetLsLocation(DB *db, WorkingFiles *wfiles,
return GetLsLocation(db, wfiles, Use{{sym.range, sym.role}, file_id});
}
std::vector<Location> GetLsLocations(DB *db, WorkingFiles *wfiles,
const std::vector<Use> &uses) {
std::vector<Location> ret;
for (Use use : uses)
if (auto loc = GetLsLocation(db, wfiles, use))
ret.push_back(*loc);
std::sort(ret.begin(), ret.end());
ret.erase(std::unique(ret.begin(), ret.end()), ret.end());
if (ret.size() > g_config->xref.maxNum)
ret.resize(g_config->xref.maxNum);
return ret;
LocationLink GetLocationLink(DB *db, WorkingFiles *wfiles, DeclRef dr) {
std::string path;
DocumentUri uri = GetLsDocumentUri(db, dr.file_id, &path);
if (auto range = GetLsRange(wfiles->GetFile(path), dr.range))
if (auto extent = GetLsRange(wfiles->GetFile(path), dr.extent)) {
LocationLink ret;
ret.targetUri = uri.raw_uri;
ret.targetSelectionRange = *range;
ret.targetRange = extent->Includes(*range) ? *extent : *range;
return ret;
}
return {};
}
SymbolKind GetSymbolKind(DB *db, SymbolIdx sym) {

View File

@ -200,7 +200,7 @@ Maybe<DeclRef> GetDefinitionSpell(DB *db, SymbolIdx sym);
// for each id.
std::vector<Use> GetFuncDeclarations(DB *, const std::vector<Usr> &);
std::vector<Use> GetTypeDeclarations(DB *, const std::vector<Usr> &);
std::vector<Use> GetVarDeclarations(DB *, const std::vector<Usr> &, unsigned);
std::vector<DeclRef> GetVarDeclarations(DB *, const std::vector<Usr> &, unsigned);
// Get non-defining declarations.
std::vector<DeclRef> &GetNonDefDeclarations(DB *db, SymbolIdx sym);
@ -215,8 +215,8 @@ DocumentUri GetLsDocumentUri(DB *db, int file_id);
std::optional<Location> GetLsLocation(DB *db, WorkingFiles *wfiles, Use use);
std::optional<Location> GetLsLocation(DB *db, WorkingFiles *wfiles,
SymbolRef sym, int file_id);
std::vector<Location> GetLsLocations(DB *db, WorkingFiles *wfiles,
const std::vector<Use> &uses);
LocationLink GetLocationLink(DB *db, WorkingFiles *wfiles, DeclRef dr);
// Returns a symbol. The symbol will *NOT* have a location assigned.
std::optional<SymbolInformation> GetSymbolInfo(DB *db, SymbolIdx sym,
bool detailed);