diff --git a/src/lsp.h b/src/lsp.h index 558e69f4..e3e61937 100644 --- a/src/lsp.h +++ b/src/lsp.h @@ -79,6 +79,8 @@ struct lsResponseError { void Write(Writer &visitor); }; +constexpr std::string_view ccls_xref("ccls.xref"); + ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// @@ -193,43 +195,6 @@ struct lsLocationEx : lsLocation { }; MAKE_REFLECT_STRUCT(lsLocationEx, uri, range, containerName, parentKind, role); -template 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 -void Reflect(TVisitor &visitor, lsCommand &value) { - REFLECT_MEMBER_START(); - REFLECT_MEMBER(title); - REFLECT_MEMBER(command); - REFLECT_MEMBER(arguments); - REFLECT_MEMBER_END(); -} - -template 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> 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 -void Reflect(TVisitor &visitor, lsCodeLens &value) { - REFLECT_MEMBER_START(); - REFLECT_MEMBER(range); - REFLECT_MEMBER(command); - REFLECT_MEMBER(data); - REFLECT_MEMBER_END(); -} - struct lsTextDocumentIdentifier { lsDocumentUri uri; }; diff --git a/src/lsp_code_action.h b/src/lsp_code_action.h deleted file mode 100644 index 8ab63c45..00000000 --- a/src/lsp_code_action.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2017-2018 ccls Authors -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#include "lsp.h" - -// codeAction -struct CommandArgs { - lsDocumentUri textDocumentUri; - std::vector 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 locations; -}; -MAKE_REFLECT_STRUCT(lsCodeLensCommandArguments, uri, position, locations) diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc index 74fdaf94..f219d1e6 100644 --- a/src/messages/initialize.cc +++ b/src/messages/initialize.cc @@ -69,13 +69,6 @@ struct lsDocumentLinkOptions { }; MAKE_REFLECT_STRUCT(lsDocumentLinkOptions, resolveProvider); -// Execute command options. -struct lsExecuteCommandOptions { - // The commands to be executed on the server - std::vector commands; -}; -MAKE_REFLECT_STRUCT(lsExecuteCommandOptions, commands); - // Save options. struct lsSaveOptions { // The client is supposed to include the content on save. @@ -168,8 +161,11 @@ struct lsServerCapabilities { // The server provides document link support. lsDocumentLinkOptions documentLinkProvider; // The server provides execute command support. - lsExecuteCommandOptions executeCommandProvider; + struct ExecuteCommandOptions { + std::vector commands{std::string(ccls_xref)}; + } executeCommandProvider; }; +MAKE_REFLECT_STRUCT(lsServerCapabilities::ExecuteCommandOptions, commands); MAKE_REFLECT_STRUCT(lsServerCapabilities, textDocumentSync, hoverProvider, completionProvider, signatureHelpProvider, definitionProvider, implementationProvider, diff --git a/src/messages/textDocument_codeAction.cc b/src/messages/textDocument_codeAction.cc index c0a8a327..d3d6dd22 100644 --- a/src/messages/textDocument_codeAction.cc +++ b/src/messages/textDocument_codeAction.cc @@ -6,12 +6,6 @@ #include "working_files.h" using namespace ccls; -struct CommandArgs { - lsDocumentUri textDocumentUri; - std::vector edits; -}; -MAKE_REFLECT_STRUCT_WRITER_AS_ARRAY(CommandArgs, textDocumentUri, edits); - namespace { MethodType kMethodType = "textDocument/codeAction"; @@ -41,10 +35,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 { lsRequestId id; - std::vector> result; + std::vector result; }; MAKE_REFLECT_STRUCT(Out_TextDocumentCodeAction, jsonrpc, id, result); @@ -64,12 +65,12 @@ struct Handler_TextDocumentCodeAction working_files->DoAction([&]() { diagnostics = wfile->diagnostics_; }); for (lsDiagnostic &diag : diagnostics) if (diag.fixits_.size()) { - lsCommand 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); } diff --git a/src/messages/textDocument_codeLens.cc b/src/messages/textDocument_codeLens.cc index 78a03c4b..33581e82 100644 --- a/src/messages/textDocument_codeLens.cc +++ b/src/messages/textDocument_codeLens.cc @@ -1,21 +1,64 @@ // Copyright 2017-2018 ccls Authors // SPDX-License-Identifier: Apache-2.0 -#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 #include -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 arguments; +}; +MAKE_REFLECT_STRUCT(lsCommand, title, command, arguments); + +struct lsCodeLens { + lsRange range; + std::optional 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 { + lsRequestId id; + std::vector result; +}; +MAKE_REFLECT_STRUCT(Out_xref, jsonrpc, id, result); + +template +std::string ToString(T &v) { + rapidjson::StringBuffer output; + rapidjson::Writer writer(output); + JsonWriter json_writer(&writer); + Reflect(json_writer, v); + return output.GetString(); +} + +struct CommonCodeLensParams { + std::vector *result; + DB *db; + WorkingFile *wfile; +}; -using TCodeLens = lsCodeLens; struct In_TextDocumentCodeLens : public RequestInMessage { - MethodType GetMethodType() const override { return kMethodType; } + MethodType GetMethodType() const override { return codeLens; } struct Params { lsTextDocumentIdentifier textDocument; } params; @@ -27,179 +70,85 @@ REGISTER_IN_MESSAGE(In_TextDocumentCodeLens); struct Out_TextDocumentCodeLens : public lsOutMessage { lsRequestId id; - std::vector> - result; + std::vector result; }; MAKE_REFLECT_STRUCT(Out_TextDocumentCodeLens, jsonrpc, id, result); -struct CommonCodeLensParams { - std::vector *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 &uses, bool force_display) { - TCodeLens code_lens; - std::optional range = GetLsRange(common->working_file, use.range); - if (!range) - return; - if (use.file_id < 0) - return; - code_lens.range = *range; - code_lens.command = lsCommand(); - 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 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 { - MethodType GetMethodType() const override { return kMethodType; } + MethodType GetMethodType() const override { return codeLens; } void Run(In_TextDocumentCodeLens *request) override { auto ¶ms = 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 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 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 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 def = GetDefinitionSpell(db, use); - if (!def || def->range.start.line != use.range.start.line) { - return use; - } - return *def; - }; - - std::vector base_callers = GetUsesForAllBases(db, func); - std::vector 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 base_loc = GetDefinitionSpell( - db, SymbolIdx{def->bases[0], SymbolKind::Func}); - if (base_loc) { - std::optional ls_base = - GetLsLocation(db, working_files, *base_loc); - if (ls_base) { - std::optional 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(); - 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 base_uses = GetUsesForAllBases(db, func); + std::vector 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: { @@ -207,27 +156,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 { + MethodType GetMethodType() const override { return executeCommand; } + void Run(In_WorkspaceExecuteCommand *request) override { + const auto ¶ms = 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 diff --git a/src/messages/workspace_executeCommand.cc b/src/messages/workspace_executeCommand.cc deleted file mode 100644 index cbfeb8d9..00000000 --- a/src/messages/workspace_executeCommand.cc +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2017-2018 ccls Authors -// SPDX-License-Identifier: Apache-2.0 - -#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 params; -}; -MAKE_REFLECT_STRUCT(In_WorkspaceExecuteCommand, id, params); -REGISTER_IN_MESSAGE(In_WorkspaceExecuteCommand); - -struct Out_WorkspaceExecuteCommand - : public lsOutMessage { - lsRequestId id; - std::variant, CommandArgs> result; -}; -MAKE_REFLECT_STRUCT(Out_WorkspaceExecuteCommand, jsonrpc, id, result); - -struct Handler_WorkspaceExecuteCommand - : BaseMessageHandler { - MethodType GetMethodType() const override { return kMethodType; } - void Run(In_WorkspaceExecuteCommand *request) override { - const auto ¶ms = 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