diff --git a/src/config.hh b/src/config.hh index 751a712cf..87bf71a4f 100644 --- a/src/config.hh +++ b/src/config.hh @@ -121,6 +121,11 @@ struct Config { // If false, disable snippets and complete just the identifier part. // TextDocumentClientCapabilities.completion.completionItem.snippetSupport bool snippetSupport = true; + + // List of supported CodeActionKinds. If empty, client does not + // have CodeActionLiteralSupport. + // TextDocumentClientCapabilities.codeAction.codeActionLiteralSupport.codeActionKind.valueSet + std::vector codeActionKind; } client; struct CodeLens { diff --git a/src/message_handler.cc b/src/message_handler.cc index 498a72deb..890a90880 100644 --- a/src/message_handler.cc +++ b/src/message_handler.cc @@ -19,7 +19,7 @@ using namespace clang; MAKE_HASHABLE(ccls::SymbolIdx, t.usr, t.kind); namespace ccls { -REFLECT_STRUCT(CodeActionParam::Context, diagnostics); +REFLECT_STRUCT(CodeActionParam::Context, diagnostics, only); REFLECT_STRUCT(CodeActionParam, textDocument, range, context); void reflect(JsonReader &, EmptyParam &) {} REFLECT_STRUCT(TextDocumentParam, textDocument); diff --git a/src/message_handler.hh b/src/message_handler.hh index 038b5bddb..9c487d26c 100644 --- a/src/message_handler.hh +++ b/src/message_handler.hh @@ -30,6 +30,7 @@ struct CodeActionParam { lsRange range; struct Context { std::vector diagnostics; + std::vector only; } context; }; struct EmptyParam {}; diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc index 9b9e2bb5a..8f5110142 100644 --- a/src/messages/initialize.cc +++ b/src/messages/initialize.cc @@ -70,7 +70,7 @@ struct ServerCap { bool documentSymbolProvider = true; bool workspaceSymbolProvider = true; struct CodeActionOptions { - std::vector codeActionKinds = {"quickfix"}; + std::vector codeActionKinds = {"quickfix", "reference"}; } codeActionProvider; struct CodeLensOptions { bool resolveProvider = false; @@ -162,6 +162,15 @@ struct TextDocumentClientCap { bool hierarchicalDocumentSymbolSupport = false; } documentSymbol; + struct CodeAction { + bool dynamicRegistration = false; + struct CodeActionLiteralSupport { + struct CodeActionKind { + std::vector valueSet; + } codeActionKind; + } codeActionLiteralSupport; + } codeAction; + struct PublishDiagnostics { bool relatedInformation = false; } publishDiagnostics; @@ -173,9 +182,14 @@ REFLECT_STRUCT(TextDocumentClientCap::Completion, completionItem); REFLECT_STRUCT(TextDocumentClientCap::DocumentSymbol, hierarchicalDocumentSymbolSupport); REFLECT_STRUCT(TextDocumentClientCap::LinkSupport, linkSupport); +REFLECT_STRUCT(TextDocumentClientCap::CodeAction::CodeActionLiteralSupport::CodeActionKind, + valueSet); +REFLECT_STRUCT(TextDocumentClientCap::CodeAction::CodeActionLiteralSupport, codeActionKind); +REFLECT_STRUCT(TextDocumentClientCap::CodeAction, + dynamicRegistration, codeActionLiteralSupport); REFLECT_STRUCT(TextDocumentClientCap::PublishDiagnostics, relatedInformation); REFLECT_STRUCT(TextDocumentClientCap, completion, definition, documentSymbol, - publishDiagnostics); + codeAction, publishDiagnostics); struct ClientCap { WorkspaceClientCap workspace; @@ -322,6 +336,10 @@ void do_initialize(MessageHandler *m, InitializeParam ¶m, if (!g_config->client.snippetSupport) g_config->completion.duplicateOptional = false; + g_config->client.codeActionKind = + capabilities.textDocument + .codeAction.codeActionLiteralSupport.codeActionKind.valueSet; + // Ensure there is a resource directory. if (g_config->clang.resourceDir.empty()) g_config->clang.resourceDir = getDefaultResourceDirectory(); diff --git a/src/messages/textDocument_code.cc b/src/messages/textDocument_code.cc index c7b249b18..3f52b078e 100644 --- a/src/messages/textDocument_code.cc +++ b/src/messages/textDocument_code.cc @@ -14,34 +14,117 @@ namespace ccls { namespace { +struct Command { + std::string title; + std::string command; + std::vector arguments; +}; struct CodeAction { std::string title; - const char *kind = "quickfix"; + std::string kind; WorkspaceEdit edit; + Command command; +}; +struct ReferenceCommand { + TextDocumentIdentifier textDocument; + Position position; + bool callee; + std::string direction; + bool derived; + int kind; }; -REFLECT_STRUCT(CodeAction, title, kind, edit); +REFLECT_STRUCT(Command, title, command, arguments); +REFLECT_STRUCT(CodeAction, title, kind, edit, command); +REFLECT_STRUCT(ReferenceCommand, textDocument, position, + callee, direction, derived, kind); + +template std::string toString(T &v) { + rapidjson::StringBuffer output; + rapidjson::Writer writer(output); + JsonWriter json_writer(&writer); + reflect(json_writer, v); + return output.GetString(); +} } // namespace + +template bool vec_has(const std::vector &vec, const T &key) { + return std::find(std::begin(vec), std::end(vec), key) != std::end(vec); +} + +bool should_send_action(std::vector available_kinds, + std::vector requested_kinds, + std::string kind) { + if (!requested_kinds.empty() && !vec_has(requested_kinds, kind)) { + return false; + } + if (!available_kinds.empty() && !vec_has(available_kinds, kind)) { + return false; + } + return true; +} + void MessageHandler::textDocument_codeAction(CodeActionParam ¶m, ReplyOnce &reply) { WorkingFile *wf = findOrFail(param.textDocument.uri.getPath(), reply).second; if (!wf) return; + auto available_kinds = g_config->client.codeActionKind; + std::vector requested_kinds = param.context.only; std::vector result; - std::vector diagnostics; - wfiles->withLock([&]() { diagnostics = wf->diagnostics; }); - for (Diagnostic &diag : diagnostics) - if (diag.fixits_.size() && - (param.range.intersects(diag.range) || - llvm::any_of(diag.fixits_, [&](const TextEdit &edit) { - return param.range.intersects(edit.range); - }))) { + + if (should_send_action(available_kinds, requested_kinds, "quickfix")) { + std::vector diagnostics; + wfiles->withLock([&]() { diagnostics = wf->diagnostics; }); + for (Diagnostic &diag : diagnostics) + if (diag.fixits_.size() && + (param.range.intersects(diag.range) || + llvm::any_of(diag.fixits_, [&](const TextEdit &edit) { + return param.range.intersects(edit.range); + }))) { + CodeAction &cmd = result.emplace_back(); + cmd.title = "FixIt: " + diag.message; + cmd.kind = "quickfix"; + auto &edit = cmd.edit.documentChanges.emplace_back(); + edit.textDocument.uri = param.textDocument.uri; + edit.textDocument.version = wf->version; + edit.edits = diag.fixits_; + } + } + + if (should_send_action(available_kinds, requested_kinds, "reference")) { + auto add = [&, param = param] ( + const char *title, const char *command_name, + const bool callee=false, const char *dir="", + const bool derived=false, int kind=0) { CodeAction &cmd = result.emplace_back(); - cmd.title = "FixIt: " + diag.message; - auto &edit = cmd.edit.documentChanges.emplace_back(); - edit.textDocument.uri = param.textDocument.uri; - edit.textDocument.version = wf->version; - edit.edits = diag.fixits_; - } + ReferenceCommand rcmd; + rcmd.textDocument = param.textDocument; + rcmd.position = param.range.start; + rcmd.callee = callee; + rcmd.direction = dir; + rcmd.derived = derived; + rcmd.kind = kind; + cmd.title = title; + cmd.kind = "reference"; + cmd.command.title = title; + cmd.command.command = command_name; + cmd.command.arguments.push_back(toString(rcmd)); + }; + + add("call", "$ccls/call"); + add("callee", "$ccls/call", true); + add("navigate-up", "$ccls/navigate", false, "U"); + add("navigate-down", "$ccls/navigate", false, "D"); + add("navigate-right", "$ccls/navigate", false, "R"); + add("navigate-left", "$ccls/navigate", false, "L"); + add("inheritance", "$ccls/inheritance"); + add("inheritance-derived", "$ccls/inheritance", false, "", true); + add("member-var", "$ccls/member", false, "", false, 4); + add("member-fun", "$ccls/member", false, "", false, 3); + add("member-type", "$ccls/member", false, "", false, 2); + add("vars", "$ccls/vars"); + } + reply(result); } @@ -51,27 +134,13 @@ struct Cmd_xref { Kind kind; std::string field; }; -struct Command { - std::string title; - std::string command; - std::vector arguments; -}; struct CodeLens { lsRange range; std::optional command; }; REFLECT_STRUCT(Cmd_xref, usr, kind, field); -REFLECT_STRUCT(Command, title, command, arguments); REFLECT_STRUCT(CodeLens, range, command); -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; @@ -215,6 +284,16 @@ void MessageHandler::workspace_executeCommand(JsonReader &reader, break; } reply(result); + } else if (param.command == "$ccls/call") { + ccls_call(json_reader, reply); + } else if (param.command == "$ccls/navigate") { + ccls_navigate(json_reader, reply); + } else if (param.command == "$ccls/inheritance") { + ccls_inheritance(json_reader, reply); + } else if (param.command == "$ccls/member") { + ccls_member(json_reader, reply); + } else if (param.command == "$ccls/vars") { + ccls_vars(json_reader, reply); } } } // namespace ccls