mirror of
https://github.com/MaskRay/ccls.git
synced 2024-11-25 09:05:10 +00:00
Implement code actions using clang FixIts
- Also make server check client version and show an error message if they do not match.
This commit is contained in:
parent
440c6c5325
commit
f3d00dea23
@ -26,6 +26,7 @@ be productive with cquery. Here's a list of implemented features:
|
||||
* global symbol search
|
||||
* hover
|
||||
* diagnostics
|
||||
* code actions (clang FixIts)
|
||||
|
||||
# Setup - build cquery, install extension, setup project
|
||||
|
||||
|
@ -2,6 +2,24 @@
|
||||
|
||||
#include "libclangmm/Utility.h"
|
||||
|
||||
namespace {
|
||||
|
||||
lsRange GetLsRangeForFixIt(const CXSourceRange& range) {
|
||||
CXSourceLocation start = clang_getRangeStart(range);
|
||||
CXSourceLocation end = clang_getRangeEnd(range);
|
||||
|
||||
unsigned int start_line, start_column;
|
||||
clang_getSpellingLocation(start, nullptr, &start_line, &start_column, nullptr);
|
||||
unsigned int end_line, end_column;
|
||||
clang_getSpellingLocation(end, nullptr, &end_line, &end_column, nullptr);
|
||||
|
||||
return lsRange(
|
||||
lsPosition(start_line - 1, start_column - 1) /*start*/,
|
||||
lsPosition(end_line - 1, end_column) /*end*/);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
optional<lsDiagnostic> BuildDiagnostic(CXDiagnostic diagnostic) {
|
||||
// Skip diagnostics in system headers.
|
||||
CXSourceLocation diag_loc = clang_getDiagnosticLocation(diagnostic);
|
||||
@ -45,7 +63,19 @@ optional<lsDiagnostic> BuildDiagnostic(CXDiagnostic diagnostic) {
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: integrate FixIts (ie, textDocument/codeAction)
|
||||
// Report fixits
|
||||
unsigned num_fixits = clang_getDiagnosticNumFixIts(diagnostic);
|
||||
if (num_fixits > 0)
|
||||
std::cerr << "!!!! Got " << num_fixits << " fixits" << std::endl;
|
||||
for (unsigned i = 0; i < num_fixits; ++i) {
|
||||
CXSourceRange replacement_range;
|
||||
CXString text = clang_getDiagnosticFixIt(diagnostic, i, &replacement_range);
|
||||
|
||||
lsTextEdit edit;
|
||||
edit.newText = clang::ToString(text);
|
||||
edit.range = GetLsRangeForFixIt(replacement_range);
|
||||
ls_diagnostic.fixits_.push_back(edit);
|
||||
}
|
||||
|
||||
clang_disposeDiagnostic(diagnostic);
|
||||
|
||||
|
@ -31,11 +31,15 @@
|
||||
// ie, a fully linear view of a function with inline function calls expanded.
|
||||
// We can probably use vscode decorators to achieve it.
|
||||
|
||||
// TODO: implement ThreadPool type which monitors CPU usage / number of work items
|
||||
// per second completed and scales up/down number of running threads.
|
||||
|
||||
namespace {
|
||||
|
||||
std::vector<std::string> kEmptyArgs;
|
||||
|
||||
|
||||
// Expected client version. We show an error if this doesn't match.
|
||||
const int kExpectedClientVersion = 1;
|
||||
|
||||
|
||||
|
||||
@ -898,6 +902,7 @@ void RegisterMessageTypes() {
|
||||
MessageRegistry::instance()->Register<Ipc_TextDocumentHover>();
|
||||
MessageRegistry::instance()->Register<Ipc_TextDocumentReferences>();
|
||||
MessageRegistry::instance()->Register<Ipc_TextDocumentDocumentSymbol>();
|
||||
MessageRegistry::instance()->Register<Ipc_TextDocumentCodeAction>();
|
||||
MessageRegistry::instance()->Register<Ipc_TextDocumentCodeLens>();
|
||||
MessageRegistry::instance()->Register<Ipc_CodeLensResolve>();
|
||||
MessageRegistry::instance()->Register<Ipc_WorkspaceSymbol>();
|
||||
@ -1103,6 +1108,11 @@ void ParseFile(IndexerConfig* config,
|
||||
diag.params.uri = lsDocumentUri::FromPath(new_index->path);
|
||||
diag.params.diagnostics = new_index->diagnostics;
|
||||
IpcManager::instance()->SendOutMessageToClient(IpcId::TextDocumentPublishDiagnostics, diag);
|
||||
|
||||
// Cache diagnostics so we can show fixits.
|
||||
WorkingFile* working_file = working_files->GetFileByFilename(new_index->path);
|
||||
if (working_file)
|
||||
working_file->diagnostics = new_index->diagnostics;
|
||||
}
|
||||
|
||||
|
||||
@ -1418,6 +1428,19 @@ bool QueryDbMainLoop(
|
||||
|
||||
*config = *request->params.initializationOptions;
|
||||
|
||||
// Check client version.
|
||||
if (config->clientVersion != kExpectedClientVersion) {
|
||||
Out_ShowLogMessage out;
|
||||
out.display_type = Out_ShowLogMessage::DisplayType::Show;
|
||||
out.params.type = lsMessageType::Error;
|
||||
out.params.message = "cquery client (v" + std::to_string(config->clientVersion) + ") and server (v" + std::to_string(kExpectedClientVersion) + ") version mismatch. Please update ";
|
||||
if (config->clientVersion > kExpectedClientVersion)
|
||||
out.params.message += "the cquery binary.";
|
||||
else
|
||||
out.params.message += "your extension client (VSIX file).";
|
||||
out.Write(std::cout);
|
||||
}
|
||||
|
||||
// Make sure cache directory is valid.
|
||||
if (config->cacheDirectory.empty()) {
|
||||
std::cerr << "No cache directory" << std::endl;
|
||||
@ -1486,6 +1509,8 @@ bool QueryDbMainLoop(
|
||||
response.result.capabilities.hoverProvider = true;
|
||||
response.result.capabilities.referencesProvider = true;
|
||||
|
||||
response.result.capabilities.codeActionProvider = true;
|
||||
|
||||
response.result.capabilities.documentSymbolProvider = true;
|
||||
response.result.capabilities.workspaceSymbolProvider = true;
|
||||
|
||||
@ -1716,7 +1741,7 @@ bool QueryDbMainLoop(
|
||||
if (file)
|
||||
params.position = file->FindStableCompletionSource(params.position);
|
||||
|
||||
CompletionManager::OnComplete callback = std::bind([code_complete_cache](BaseIpcMessage* message, NonElidedVector<lsCompletionItem> results, NonElidedVector<lsDiagnostic> diagnostics) {
|
||||
CompletionManager::OnComplete callback = std::bind([working_files, code_complete_cache](BaseIpcMessage* message, NonElidedVector<lsCompletionItem> results, NonElidedVector<lsDiagnostic> diagnostics) {
|
||||
auto msg = static_cast<Ipc_TextDocumentComplete*>(message);
|
||||
auto ipc = IpcManager::instance();
|
||||
|
||||
@ -1725,13 +1750,21 @@ bool QueryDbMainLoop(
|
||||
complete_response.result.isIncomplete = false;
|
||||
complete_response.result.items = results;
|
||||
|
||||
// Emit completion results.
|
||||
ipc->SendOutMessageToClient(IpcId::TextDocumentCompletion, complete_response);
|
||||
|
||||
// Emit diagnostics.
|
||||
Out_TextDocumentPublishDiagnostics diagnostic_response;
|
||||
diagnostic_response.params.uri = msg->params.textDocument.uri;
|
||||
diagnostic_response.params.diagnostics = diagnostics;
|
||||
ipc->SendOutMessageToClient(IpcId::TextDocumentPublishDiagnostics, diagnostic_response);
|
||||
|
||||
// Cache diagnostics so we can show fixits.
|
||||
WorkingFile* working_file = working_files->GetFileByFilename(msg->params.textDocument.uri.GetPath());
|
||||
if (working_file)
|
||||
working_file->diagnostics = diagnostics;
|
||||
|
||||
// Cache completion results so if the user types backspace we can respond faster.
|
||||
code_complete_cache->cached_path = msg->params.textDocument.uri.GetPath();
|
||||
code_complete_cache->cached_completion_position = msg->params.position;
|
||||
code_complete_cache->cached_results = results;
|
||||
@ -2021,6 +2054,50 @@ bool QueryDbMainLoop(
|
||||
break;
|
||||
}
|
||||
|
||||
case IpcId::TextDocumentCodeAction: {
|
||||
// NOTE: This code snippet will generate some FixIts for testing:
|
||||
//
|
||||
// struct origin { int x, int y };
|
||||
// void foo() {
|
||||
// point origin = {
|
||||
// x: 0.0,
|
||||
// y: 0.0
|
||||
// };
|
||||
// }
|
||||
//
|
||||
auto msg = static_cast<Ipc_TextDocumentCodeAction*>(message.get());
|
||||
|
||||
WorkingFile* working_file = working_files->GetFileByFilename(msg->params.textDocument.uri.GetPath());
|
||||
if (!working_file) {
|
||||
// TODO: send error response.
|
||||
std::cerr << "[error] textDocument/codeAction could not find working file" << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
int target_line = msg->params.range.start.line;
|
||||
|
||||
Out_TextDocumentCodeAction response;
|
||||
response.id = msg->id;
|
||||
|
||||
for (lsDiagnostic& diag : working_file->diagnostics) {
|
||||
// clang does not provide accurate ennough column reporting for
|
||||
// diagnostics to do good column filtering, so report all
|
||||
// diagnostics on the line.
|
||||
if (!diag.fixits_.empty() && diag.range.start.line == target_line) {
|
||||
Out_TextDocumentCodeAction::Command command;
|
||||
command.title = "FixIt: " + diag.message;
|
||||
command.command = "cquery._applyFixIt";
|
||||
command.arguments.textDocumentUri = msg->params.textDocument.uri;
|
||||
command.arguments.edits = diag.fixits_;
|
||||
response.result.push_back(command);
|
||||
}
|
||||
}
|
||||
|
||||
response.Write(std::cerr);
|
||||
ipc->SendOutMessageToClient(IpcId::TextDocumentCodeAction, response);
|
||||
break;
|
||||
}
|
||||
|
||||
case IpcId::TextDocumentCodeLens: {
|
||||
auto msg = static_cast<Ipc_TextDocumentCodeLens*>(message.get());
|
||||
|
||||
@ -2374,6 +2451,7 @@ void LanguageServerStdinLoop(IndexerConfig* config, std::unordered_map<IpcId, Ti
|
||||
case IpcId::TextDocumentHover:
|
||||
case IpcId::TextDocumentReferences:
|
||||
case IpcId::TextDocumentDocumentSymbol:
|
||||
case IpcId::TextDocumentCodeAction:
|
||||
case IpcId::TextDocumentCodeLens:
|
||||
case IpcId::WorkspaceSymbol:
|
||||
case IpcId::CqueryFreshenIndex:
|
||||
|
@ -38,6 +38,8 @@ const char* IpcIdToString(IpcId id) {
|
||||
return "textDocument/references";
|
||||
case IpcId::TextDocumentDocumentSymbol:
|
||||
return "textDocument/documentSymbol";
|
||||
case IpcId::TextDocumentCodeAction:
|
||||
return "textDocument/codeAction";
|
||||
case IpcId::TextDocumentCodeLens:
|
||||
return "textDocument/codeLens";
|
||||
case IpcId::CodeLensResolve:
|
||||
|
@ -24,6 +24,7 @@ enum class IpcId : int {
|
||||
TextDocumentHover,
|
||||
TextDocumentReferences,
|
||||
TextDocumentDocumentSymbol,
|
||||
TextDocumentCodeAction,
|
||||
TextDocumentCodeLens,
|
||||
CodeLensResolve,
|
||||
WorkspaceSymbol,
|
||||
|
@ -73,6 +73,9 @@ struct IndexerConfig {
|
||||
bool enableCacheWrite = true;
|
||||
// If false, the index will not be loaded from a previous run.
|
||||
bool enableCacheRead = true;
|
||||
|
||||
// Version of the client.
|
||||
int clientVersion = 0;
|
||||
};
|
||||
MAKE_REFLECT_STRUCT(IndexerConfig,
|
||||
cacheDirectory,
|
||||
@ -81,8 +84,9 @@ MAKE_REFLECT_STRUCT(IndexerConfig,
|
||||
|
||||
maxWorkspaceSearchResults,
|
||||
indexerCount,
|
||||
enableIndexing, enableCacheWrite, enableCacheRead);
|
||||
enableIndexing, enableCacheWrite, enableCacheRead,
|
||||
|
||||
clientVersion);
|
||||
|
||||
|
||||
|
||||
@ -330,6 +334,8 @@ struct lsCommand {
|
||||
// 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>
|
||||
@ -591,6 +597,9 @@ struct lsDiagnostic {
|
||||
|
||||
// The diagnostic's message.
|
||||
std::string message;
|
||||
|
||||
// Non-serialized set of fixits.
|
||||
NonElidedVector<lsTextEdit> fixits_;
|
||||
};
|
||||
MAKE_REFLECT_STRUCT(lsDiagnostic, range, severity, source, message);
|
||||
|
||||
@ -1422,6 +1431,44 @@ struct Out_TextDocumentReferences : public lsOutMessage<Out_TextDocumentReferenc
|
||||
};
|
||||
MAKE_REFLECT_STRUCT(Out_TextDocumentReferences, jsonrpc, id, result);
|
||||
|
||||
// Code action
|
||||
struct Ipc_TextDocumentCodeAction : public IpcMessage<Ipc_TextDocumentCodeAction> {
|
||||
const static IpcId kIpcId = IpcId::TextDocumentCodeAction;
|
||||
// Contains additional diagnostic information about the context in which
|
||||
// a code action is run.
|
||||
struct lsCodeActionContext {
|
||||
// An array of diagnostics.
|
||||
NonElidedVector<lsDiagnostic> diagnostics;
|
||||
};
|
||||
// Params for the CodeActionRequest
|
||||
struct lsCodeActionParams {
|
||||
// The document in which the command was invoked.
|
||||
lsTextDocumentIdentifier textDocument;
|
||||
// The range for which the command was invoked.
|
||||
lsRange range;
|
||||
// Context carrying additional information.
|
||||
lsCodeActionContext context;
|
||||
};
|
||||
|
||||
lsRequestId id;
|
||||
lsCodeActionParams params;
|
||||
};
|
||||
MAKE_REFLECT_STRUCT(Ipc_TextDocumentCodeAction::lsCodeActionContext, diagnostics);
|
||||
MAKE_REFLECT_STRUCT(Ipc_TextDocumentCodeAction::lsCodeActionParams, textDocument, range, context);
|
||||
MAKE_REFLECT_STRUCT(Ipc_TextDocumentCodeAction, id, params);
|
||||
struct Out_TextDocumentCodeAction : public lsOutMessage<Out_TextDocumentCodeAction> {
|
||||
struct CommandArgs {
|
||||
lsDocumentUri textDocumentUri;
|
||||
NonElidedVector<lsTextEdit> edits;
|
||||
};
|
||||
using Command = lsCommand<CommandArgs>;
|
||||
|
||||
lsRequestId id;
|
||||
NonElidedVector<Command> result;
|
||||
};
|
||||
MAKE_REFLECT_STRUCT_WRITER_AS_ARRAY(Out_TextDocumentCodeAction::CommandArgs, textDocumentUri, edits);
|
||||
MAKE_REFLECT_STRUCT(Out_TextDocumentCodeAction, jsonrpc, id, result);
|
||||
|
||||
// List symbols in a document.
|
||||
struct lsDocumentSymbolParams {
|
||||
lsTextDocumentIdentifier textDocument;
|
||||
|
@ -56,7 +56,17 @@ struct IndexFile;
|
||||
REFLECT_MEMBER_END(); \
|
||||
}
|
||||
|
||||
#define _MAPPABLE_REFLECT_ARRAY(name) \
|
||||
Reflect(visitor, value.name);
|
||||
|
||||
// Reflects the struct so it is serialized as an array instead of an object.
|
||||
// This currently only supports writers.
|
||||
#define MAKE_REFLECT_STRUCT_WRITER_AS_ARRAY(type, ...) \
|
||||
inline void Reflect(Writer& visitor, type& value) { \
|
||||
visitor.StartArray(); \
|
||||
MACRO_MAP(_MAPPABLE_REFLECT_ARRAY, __VA_ARGS__) \
|
||||
visitor.EndArray(); \
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -27,6 +27,8 @@ struct WorkingFile {
|
||||
// This map goes from buffer-line -> indices+1 in all_buffer_lines.
|
||||
// Note: The items in the value entry are 1-based liness.
|
||||
std::unordered_map<std::string, std::vector<int>> all_buffer_lines_lookup;
|
||||
// A set of diagnostics that have been reported for this file.
|
||||
std::vector<lsDiagnostic> diagnostics;
|
||||
|
||||
WorkingFile(const std::string& filename, const std::string& buffer_content);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user