Revamp codeLens & codeAction

b.ref: references of bases
d.ref: references of derived
when b.ref > 0, don't display 0 ref or x bases
This commit is contained in:
Fangrui Song 2018-09-29 01:10:56 -07:00
parent d4871207ed
commit da704521b5
6 changed files with 199 additions and 321 deletions

View File

@ -91,6 +91,8 @@ struct lsResponseError {
void Write(Writer &visitor);
};
constexpr std::string_view ccls_xref("ccls.xref");
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
@ -205,43 +207,6 @@ struct lsLocationEx : lsLocation {
};
MAKE_REFLECT_STRUCT(lsLocationEx, uri, range, containerName, parentKind, role);
template <typename T> struct lsCommand {
// Title of the command (ie, 'save')
std::string title;
// Actual command identifier.
std::string command;
// Arguments to run the command with.
// **NOTE** This must be serialized as an array. Use
// MAKE_REFLECT_STRUCT_WRITER_AS_ARRAY.
T arguments;
};
template <typename TVisitor, typename T>
void Reflect(TVisitor &visitor, lsCommand<T> &value) {
REFLECT_MEMBER_START();
REFLECT_MEMBER(title);
REFLECT_MEMBER(command);
REFLECT_MEMBER(arguments);
REFLECT_MEMBER_END();
}
template <typename TData, typename TCommandArguments> struct lsCodeLens {
// The range in which this code lens is valid. Should only span a single line.
lsRange range;
// The command this code lens represents.
std::optional<lsCommand<TCommandArguments>> command;
// A data entry field that is preserved on a code lens item between
// a code lens and a code lens resolve request.
TData data;
};
template <typename TVisitor, typename TData, typename TCommandArguments>
void Reflect(TVisitor &visitor, lsCodeLens<TData, TCommandArguments> &value) {
REFLECT_MEMBER_START();
REFLECT_MEMBER(range);
REFLECT_MEMBER(command);
REFLECT_MEMBER(data);
REFLECT_MEMBER_END();
}
struct lsTextDocumentIdentifier {
lsDocumentUri uri;
};

View File

@ -1,36 +0,0 @@
/* Copyright 2017-2018 ccls Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#pragma once
#include "lsp.h"
// codeAction
struct CommandArgs {
lsDocumentUri textDocumentUri;
std::vector<lsTextEdit> edits;
};
MAKE_REFLECT_STRUCT_WRITER_AS_ARRAY(CommandArgs, textDocumentUri, edits);
// codeLens
struct lsCodeLensUserData {};
MAKE_REFLECT_EMPTY_STRUCT(lsCodeLensUserData);
struct lsCodeLensCommandArguments {
lsDocumentUri uri;
lsPosition position;
std::vector<lsLocation> locations;
};
MAKE_REFLECT_STRUCT(lsCodeLensCommandArguments, uri, position, locations)

View File

@ -81,13 +81,6 @@ struct lsDocumentLinkOptions {
};
MAKE_REFLECT_STRUCT(lsDocumentLinkOptions, resolveProvider);
// Execute command options.
struct lsExecuteCommandOptions {
// The commands to be executed on the server
std::vector<std::string> commands;
};
MAKE_REFLECT_STRUCT(lsExecuteCommandOptions, commands);
// Save options.
struct lsSaveOptions {
// The client is supposed to include the content on save.
@ -180,8 +173,11 @@ struct lsServerCapabilities {
// The server provides document link support.
lsDocumentLinkOptions documentLinkProvider;
// The server provides execute command support.
lsExecuteCommandOptions executeCommandProvider;
struct ExecuteCommandOptions {
std::vector<std::string> commands{std::string(ccls_xref)};
} executeCommandProvider;
};
MAKE_REFLECT_STRUCT(lsServerCapabilities::ExecuteCommandOptions, commands);
MAKE_REFLECT_STRUCT(lsServerCapabilities, textDocumentSync, hoverProvider,
completionProvider, signatureHelpProvider,
definitionProvider, implementationProvider,

View File

@ -18,12 +18,6 @@ limitations under the License.
#include "working_files.h"
using namespace ccls;
struct CommandArgs {
lsDocumentUri textDocumentUri;
std::vector<lsTextEdit> edits;
};
MAKE_REFLECT_STRUCT_WRITER_AS_ARRAY(CommandArgs, textDocumentUri, edits);
namespace {
MethodType kMethodType = "textDocument/codeAction";
@ -53,10 +47,17 @@ MAKE_REFLECT_STRUCT(In_TextDocumentCodeAction::lsCodeActionParams, textDocument,
MAKE_REFLECT_STRUCT(In_TextDocumentCodeAction, id, params);
REGISTER_IN_MESSAGE(In_TextDocumentCodeAction);
struct lsCodeAction {
std::string title;
const char *kind = "quickfix";
lsWorkspaceEdit edit;
};
MAKE_REFLECT_STRUCT(lsCodeAction, title, kind, edit);
struct Out_TextDocumentCodeAction
: public lsOutMessage<Out_TextDocumentCodeAction> {
lsRequestId id;
std::vector<lsCommand<CommandArgs>> result;
std::vector<lsCodeAction> result;
};
MAKE_REFLECT_STRUCT(Out_TextDocumentCodeAction, jsonrpc, id, result);
@ -76,12 +77,12 @@ struct Handler_TextDocumentCodeAction
working_files->DoAction([&]() { diagnostics = wfile->diagnostics_; });
for (lsDiagnostic &diag : diagnostics)
if (diag.fixits_.size()) {
lsCommand<CommandArgs> command;
command.title = "FixIt: " + diag.message;
command.command = "ccls._applyFixIt";
command.arguments.textDocumentUri = params.textDocument.uri;
command.arguments.edits = diag.fixits_;
out.result.push_back(command);
lsCodeAction &cmd = out.result.emplace_back();
cmd.title = "FixIt: " + diag.message;
auto &edit = cmd.edit.documentChanges.emplace_back();
edit.textDocument.uri = params.textDocument.uri;
edit.textDocument.version = wfile->version;
edit.edits = diag.fixits_;
}
pipeline::WriteStdout(kMethodType, out);
}

View File

@ -13,21 +13,64 @@ See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "clang_complete.hh"
#include "lsp_code_action.h"
#include "message_handler.h"
#include "pipeline.hh"
#include "query_utils.h"
using namespace ccls;
#include "serializers/json.h"
#include <llvm/Support/FormatVariadic.h>
#include <unordered_set>
namespace {
MethodType kMethodType = "textDocument/codeLens";
using namespace ccls;
namespace {
const MethodType codeLens = "textDocument/codeLens",
executeCommand = "workspace/executeCommand";
struct lsCommand {
std::string title;
std::string command;
std::vector<std::string> arguments;
};
MAKE_REFLECT_STRUCT(lsCommand, title, command, arguments);
struct lsCodeLens {
lsRange range;
std::optional<lsCommand> command;
};
MAKE_REFLECT_STRUCT(lsCodeLens, range, command);
struct Cmd_xref {
Usr usr;
SymbolKind kind;
std::string field;
};
MAKE_REFLECT_STRUCT(Cmd_xref, usr, kind, field);
struct Out_xref : public lsOutMessage<Out_xref> {
lsRequestId id;
std::vector<lsLocation> result;
};
MAKE_REFLECT_STRUCT(Out_xref, jsonrpc, id, result);
template <typename T>
std::string ToString(T &v) {
rapidjson::StringBuffer output;
rapidjson::Writer<rapidjson::StringBuffer> writer(output);
JsonWriter json_writer(&writer);
Reflect(json_writer, v);
return output.GetString();
}
struct CommonCodeLensParams {
std::vector<lsCodeLens> *result;
DB *db;
WorkingFile *wfile;
};
using TCodeLens = lsCodeLens<lsCodeLensUserData, lsCodeLensCommandArguments>;
struct In_TextDocumentCodeLens : public RequestInMessage {
MethodType GetMethodType() const override { return kMethodType; }
MethodType GetMethodType() const override { return codeLens; }
struct Params {
lsTextDocumentIdentifier textDocument;
} params;
@ -39,179 +82,85 @@ REGISTER_IN_MESSAGE(In_TextDocumentCodeLens);
struct Out_TextDocumentCodeLens
: public lsOutMessage<Out_TextDocumentCodeLens> {
lsRequestId id;
std::vector<lsCodeLens<lsCodeLensUserData, lsCodeLensCommandArguments>>
result;
std::vector<lsCodeLens> result;
};
MAKE_REFLECT_STRUCT(Out_TextDocumentCodeLens, jsonrpc, id, result);
struct CommonCodeLensParams {
std::vector<TCodeLens> *result;
DB *db;
WorkingFiles *working_files;
WorkingFile *working_file;
};
Use OffsetStartColumn(Use use, int16_t offset) {
use.range.start.column += offset;
return use;
}
void AddCodeLens(const char *singular, const char *plural,
CommonCodeLensParams *common, Use use,
const std::vector<Use> &uses, bool force_display) {
TCodeLens code_lens;
std::optional<lsRange> range = GetLsRange(common->working_file, use.range);
if (!range)
return;
if (use.file_id < 0)
return;
code_lens.range = *range;
code_lens.command = lsCommand<lsCodeLensCommandArguments>();
code_lens.command->command = "ccls.showReferences";
code_lens.command->arguments.uri = GetLsDocumentUri(common->db, use.file_id);
code_lens.command->arguments.position = code_lens.range.start;
// Add unique uses.
std::vector<lsLocation> unique_uses;
for (Use use1 : uses) {
if (auto ls_loc = GetLsLocation(common->db, common->working_files, use1))
unique_uses.push_back(*ls_loc);
}
code_lens.command->arguments.locations.assign(unique_uses.begin(),
unique_uses.end());
// User visible label
size_t num_usages = unique_uses.size();
code_lens.command->title = std::to_string(num_usages) + " ";
if (num_usages == 1)
code_lens.command->title += singular;
else
code_lens.command->title += plural;
if (force_display || unique_uses.size() > 0)
common->result->push_back(code_lens);
}
struct Handler_TextDocumentCodeLens
: BaseMessageHandler<In_TextDocumentCodeLens> {
MethodType GetMethodType() const override { return kMethodType; }
MethodType GetMethodType() const override { return codeLens; }
void Run(In_TextDocumentCodeLens *request) override {
auto &params = request->params;
Out_TextDocumentCodeLens out;
out.id = request->id;
std::string path = params.textDocument.uri.GetPath();
clang_complete->NotifyView(path);
QueryFile *file;
if (!FindFileOrFail(db, project, request->id,
params.textDocument.uri.GetPath(), &file))
if (!FindFileOrFail(db, project, request->id, path, &file))
return;
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
CommonCodeLensParams common;
common.result = &out.result;
common.db = db;
common.working_files = working_files;
common.working_file = working_files->GetFileByFilename(file->def->path);
auto Add = [&](const char *singular, Cmd_xref show, Use use, int num,
bool force_display = false) {
if (!num && !force_display)
return;
std::optional<lsRange> range = GetLsRange(wfile, use.range);
if (!range)
return;
lsCodeLens &code_lens = out.result.emplace_back();
code_lens.range = *range;
code_lens.command = lsCommand();
code_lens.command->command = std::string(ccls_xref);
bool plural = num > 1 && singular[strlen(singular) - 1] != 'd';
code_lens.command->title =
llvm::formatv("{0} {1}{2}", num, singular, plural ? "s" : "").str();
code_lens.command->arguments.push_back(ToString(show));
};
auto ToSpell = [&](Use use) {
Maybe<Use> def = GetDefinitionSpell(db, use);
if (def && def->file_id == use.file_id &&
def->range.start.line == use.range.start.line)
return *def;
return use;
};
std::unordered_set<Range> seen;
for (auto [sym, refcnt] : file->outline2refcnt) {
if (refcnt <= 0 || !seen.insert(sym.range).second)
continue;
// NOTE: We OffsetColumn so that the code lens always show up in a
// predictable order. Otherwise, the client may randomize it.
Use use{{sym.range, sym.usr, sym.kind, sym.role}, file->id};
Use use = ToSpell({{sym.range, sym.usr, sym.kind, sym.role}, file->id});
switch (sym.kind) {
case SymbolKind::Type: {
QueryType &type = db->GetType(sym);
const QueryType::Def *def = type.AnyDef();
if (!def || def->kind == lsSymbolKind::Namespace)
continue;
AddCodeLens("ref", "refs", &common, OffsetStartColumn(use, 0),
type.uses, true /*force_display*/);
AddCodeLens("derived", "derived", &common, OffsetStartColumn(use, 1),
GetTypeDeclarations(db, type.derived),
false /*force_display*/);
AddCodeLens("var", "vars", &common, OffsetStartColumn(use, 2),
GetVarDeclarations(db, type.instances, true),
false /*force_display*/);
break;
}
case SymbolKind::Func: {
QueryFunc &func = db->GetFunc(sym);
const QueryFunc::Def *def = func.AnyDef();
if (!def)
continue;
int16_t offset = 0;
// For functions, the outline will report a location that is using the
// extent since that is better for outline. This tries to convert the
// extent location to the spelling location.
auto try_ensure_spelling = [&](Use use) {
Maybe<Use> def = GetDefinitionSpell(db, use);
if (!def || def->range.start.line != use.range.start.line) {
return use;
}
return *def;
};
std::vector<Use> base_callers = GetUsesForAllBases(db, func);
std::vector<Use> derived_callers = GetUsesForAllDerived(db, func);
if (base_callers.empty() && derived_callers.empty()) {
Use loc = try_ensure_spelling(use);
AddCodeLens("call", "calls", &common,
OffsetStartColumn(loc, offset++), func.uses,
true /*force_display*/);
} else {
Use loc = try_ensure_spelling(use);
AddCodeLens("direct call", "direct calls", &common,
OffsetStartColumn(loc, offset++), func.uses,
false /*force_display*/);
if (!base_callers.empty())
AddCodeLens("base call", "base calls", &common,
OffsetStartColumn(loc, offset++), base_callers,
false /*force_display*/);
if (!derived_callers.empty())
AddCodeLens("derived call", "derived calls", &common,
OffsetStartColumn(loc, offset++), derived_callers,
false /*force_display*/);
}
AddCodeLens(
"derived", "derived", &common, OffsetStartColumn(use, offset++),
GetFuncDeclarations(db, func.derived), false /*force_display*/);
// "Base"
if (def->bases.size() == 1) {
Maybe<Use> base_loc = GetDefinitionSpell(
db, SymbolIdx{def->bases[0], SymbolKind::Func});
if (base_loc) {
std::optional<lsLocation> ls_base =
GetLsLocation(db, working_files, *base_loc);
if (ls_base) {
std::optional<lsRange> range =
GetLsRange(common.working_file, sym.range);
if (range) {
TCodeLens code_lens;
code_lens.range = *range;
code_lens.range.start.character += offset++;
code_lens.command = lsCommand<lsCodeLensCommandArguments>();
code_lens.command->title = "Base";
code_lens.command->command = "ccls.goto";
code_lens.command->arguments.uri = ls_base->uri;
code_lens.command->arguments.position = ls_base->range.start;
out.result.push_back(code_lens);
}
}
}
} else {
AddCodeLens("base", "base", &common, OffsetStartColumn(use, 1),
GetTypeDeclarations(db, def->bases),
false /*force_display*/);
}
std::vector<Use> base_uses = GetUsesForAllBases(db, func);
std::vector<Use> derived_uses = GetUsesForAllDerived(db, func);
Add("ref", {sym.usr, SymbolKind::Func, "uses"}, use, func.uses.size(),
base_uses.empty());
if (base_uses.size())
Add("b.ref", {sym.usr, SymbolKind::Func, "bases uses"}, use,
base_uses.size());
if (derived_uses.size())
Add("d.ref", {sym.usr, SymbolKind::Func, "derived uses"}, use,
derived_uses.size());
if (base_uses.empty())
Add("base", {sym.usr, SymbolKind::Func, "bases"}, use,
def->bases.size());
Add("derived", {sym.usr, SymbolKind::Func, "derived"}, use,
func.derived.size());
break;
}
case SymbolKind::Type: {
QueryType &type = db->GetType(sym);
Add("ref", {sym.usr, SymbolKind::Type, "uses"}, use, type.uses.size(),
true);
Add("derived", {sym.usr, SymbolKind::Type, "derived"}, use,
type.derived.size());
Add("var", {sym.usr, SymbolKind::Type, "instances"}, use,
type.instances.size());
break;
}
case SymbolKind::Var: {
@ -219,27 +168,88 @@ struct Handler_TextDocumentCodeLens
const QueryVar::Def *def = var.AnyDef();
if (!def || (def->is_local() && !g_config->codeLens.localVariables))
continue;
bool force_display = true;
// Do not show 0 refs on macro with no uses, as it is most likely
// a header guard.
if (def->kind == lsSymbolKind::Macro)
force_display = false;
AddCodeLens("ref", "refs", &common, OffsetStartColumn(use, 0), var.uses,
force_display);
Add("ref", {sym.usr, SymbolKind::Var, "uses"}, use, var.uses.size(),
def->kind != lsSymbolKind::Macro);
break;
}
case SymbolKind::File:
case SymbolKind::Invalid: {
assert(false && "unexpected");
break;
}
case SymbolKind::Invalid:
llvm_unreachable("");
};
}
pipeline::WriteStdout(kMethodType, out);
pipeline::WriteStdout(codeLens, out);
}
};
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentCodeLens);
struct In_WorkspaceExecuteCommand : public RequestInMessage {
MethodType GetMethodType() const override { return executeCommand; }
lsCommand params;
};
MAKE_REFLECT_STRUCT(In_WorkspaceExecuteCommand, id, params);
REGISTER_IN_MESSAGE(In_WorkspaceExecuteCommand);
struct Handler_WorkspaceExecuteCommand
: BaseMessageHandler<In_WorkspaceExecuteCommand> {
MethodType GetMethodType() const override { return executeCommand; }
void Run(In_WorkspaceExecuteCommand *request) override {
const auto &params = request->params;
if (params.arguments.empty())
return;
rapidjson::Document reader;
reader.Parse(params.arguments[0].c_str());
JsonReader json_reader{&reader};
if (params.command == ccls_xref) {
Cmd_xref cmd;
Reflect(json_reader, cmd);
Out_xref out;
out.id = request->id;
auto Map = [&](auto &&uses) {
for (auto &use : uses)
if (auto loc = GetLsLocation(db, working_files, use))
out.result.push_back(std::move(*loc));
};
switch (cmd.kind) {
case SymbolKind::Func: {
QueryFunc &func = db->Func(cmd.usr);
if (cmd.field == "bases") {
if (auto *def = func.AnyDef())
Map(GetFuncDeclarations(db, def->bases));
} else if (cmd.field == "bases uses") {
Map(GetUsesForAllBases(db, func));
} else if (cmd.field == "derived") {
Map(GetFuncDeclarations(db, func.derived));
} else if (cmd.field == "derived uses") {
Map(GetUsesForAllDerived(db, func));
} else if (cmd.field == "uses") {
Map(func.uses);
}
break;
}
case SymbolKind::Type: {
QueryType &type = db->Type(cmd.usr);
if (cmd.field == "derived") {
Map(GetTypeDeclarations(db, type.derived));
} else if (cmd.field == "instances") {
Map(GetVarDeclarations(db, type.instances, 7));
} else if (cmd.field == "uses") {
Map(type.uses);
}
break;
}
case SymbolKind::Var: {
QueryVar &var = db->Var(cmd.usr);
if (cmd.field == "uses")
Map(var.uses);
break;
}
default:
break;
}
pipeline::WriteStdout(executeCommand, out);
}
}
};
REGISTER_MESSAGE_HANDLER(Handler_WorkspaceExecuteCommand);
} // namespace

View File

@ -1,58 +0,0 @@
/* Copyright 2017-2018 ccls Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "lsp_code_action.h"
#include "message_handler.h"
#include "pipeline.hh"
#include "query_utils.h"
using namespace ccls;
namespace {
MethodType kMethodType = "workspace/executeCommand";
struct In_WorkspaceExecuteCommand : public RequestInMessage {
MethodType GetMethodType() const override { return kMethodType; }
lsCommand<lsCodeLensCommandArguments> params;
};
MAKE_REFLECT_STRUCT(In_WorkspaceExecuteCommand, id, params);
REGISTER_IN_MESSAGE(In_WorkspaceExecuteCommand);
struct Out_WorkspaceExecuteCommand
: public lsOutMessage<Out_WorkspaceExecuteCommand> {
lsRequestId id;
std::variant<std::vector<lsLocation>, CommandArgs> result;
};
MAKE_REFLECT_STRUCT(Out_WorkspaceExecuteCommand, jsonrpc, id, result);
struct Handler_WorkspaceExecuteCommand
: BaseMessageHandler<In_WorkspaceExecuteCommand> {
MethodType GetMethodType() const override { return kMethodType; }
void Run(In_WorkspaceExecuteCommand *request) override {
const auto &params = request->params;
Out_WorkspaceExecuteCommand out;
out.id = request->id;
if (params.command == "ccls._applyFixIt") {
} else if (params.command == "ccls._autoImplement") {
} else if (params.command == "ccls._insertInclude") {
} else if (params.command == "ccls.showReferences") {
out.result = params.arguments.locations;
}
pipeline::WriteStdout(kMethodType, out);
}
};
REGISTER_MESSAGE_HANDLER(Handler_WorkspaceExecuteCommand);
} // namespace