mirror of
				https://github.com/MaskRay/ccls.git
				synced 2025-11-03 22:04:24 +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