mirror of
				https://github.com/MaskRay/ccls.git
				synced 2025-11-04 06:15:20 +00:00 
			
		
		
		
	Support textDocument/{formatting,onTypeFormatting,rangeFormatting}
This commit is contained in:
		
							parent
							
								
									ae19826411
								
							
						
					
					
						commit
						7eacd2664f
					
				@ -222,6 +222,7 @@ target_sources(ccls PRIVATE
 | 
			
		||||
  src/messages/textDocument_completion.cc
 | 
			
		||||
  src/messages/textDocument_definition.cc
 | 
			
		||||
  src/messages/textDocument_did.cc
 | 
			
		||||
  src/messages/textDocument_formatting.cc
 | 
			
		||||
  src/messages/textDocument_documentHighlight.cc
 | 
			
		||||
  src/messages/textDocument_documentSymbol.cc
 | 
			
		||||
  src/messages/textDocument_hover.cc
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ ccls, which originates from [cquery](https://github.com/cquery-project/cquery),
 | 
			
		||||
  * code completion (with both signature help and snippets)
 | 
			
		||||
  * [definition](src/messages/textDocument_definition.cc)/[references](src/messages/textDcument_references.cc), and other cross references
 | 
			
		||||
  * cross reference extensions: `$ccls/call` `$ccls/inheritance` `$ccls/member` `$ccls/vars` ...
 | 
			
		||||
  * formatting
 | 
			
		||||
  * hierarchies: [call (caller/callee) hierarchy](src/messages/ccls_call.cc), [inheritance (base/derived) hierarchy](src/messages/ccls_inheritance.cc), [member hierarchy](src/messages/ccls_member.cc)
 | 
			
		||||
  * [symbol rename](src/messages/textDocument_rename.cc)
 | 
			
		||||
  * [document symbols](src/messages/textDocument_documentSymbol.cc) and approximate search of [workspace symbol](src/messages/workspace_symbol.cc)
 | 
			
		||||
@ -22,7 +23,7 @@ Saving files will incrementally update the index.
 | 
			
		||||
 | 
			
		||||
Compared with cquery, it makes use of C++17 features, has less third-party dependencies and slimmed-down code base.
 | 
			
		||||
It leverages Clang C++ API as [clangd](https://clang.llvm.org/extra/clangd.html) does, which provides better support for code completion and diagnostics.
 | 
			
		||||
Refactoring and formatting are non-goals as they can be provided by clang-format, clang-include-fixer and other Clang based tools.
 | 
			
		||||
Refactoring is a non-goal as it can be provided by clang-include-fixer and other Clang based tools.
 | 
			
		||||
 | 
			
		||||
The comparison with cquery as noted on 2018-07-15:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -69,7 +69,17 @@ set(_Clang_REQUIRED_VARS Clang_LIBRARY Clang_INCLUDE_DIR Clang_EXECUTABLE
 | 
			
		||||
                         LLVM_INCLUDE_DIR LLVM_BUILD_INCLUDE_DIR)
 | 
			
		||||
 | 
			
		||||
_Clang_find_library(Clang_LIBRARY clangIndex)
 | 
			
		||||
_Clang_find_add_library(clangFormat)
 | 
			
		||||
_Clang_find_add_library(clangTooling)
 | 
			
		||||
 | 
			
		||||
# TODO Remove after dropping clang 6
 | 
			
		||||
_Clang_find_library(clangToolingInclusions_LIBRARY clangToolingInclusions)
 | 
			
		||||
if (clangToolingInclusions_LIBRARY)
 | 
			
		||||
  list(APPEND _Clang_LIBRARIES ${clangToolingInclusions_LIBRARY})
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
_Clang_find_add_library(clangToolingCore)
 | 
			
		||||
_Clang_find_add_library(clangRewrite)
 | 
			
		||||
_Clang_find_add_library(clangFrontend)
 | 
			
		||||
_Clang_find_add_library(clangParse)
 | 
			
		||||
_Clang_find_add_library(clangSerialization)
 | 
			
		||||
 | 
			
		||||
@ -54,7 +54,7 @@ MAKE_REFLECT_STRUCT(lsCompletionOptions, resolveProvider, triggerCharacters);
 | 
			
		||||
// Format document on type options
 | 
			
		||||
struct lsDocumentOnTypeFormattingOptions {
 | 
			
		||||
  // A character on which formatting should be triggered, like `}`.
 | 
			
		||||
  std::string firstTriggerCharacter;
 | 
			
		||||
  std::string firstTriggerCharacter = "}";
 | 
			
		||||
 | 
			
		||||
  // More trigger characters.
 | 
			
		||||
  std::vector<std::string> moreTriggerCharacter;
 | 
			
		||||
@ -65,7 +65,7 @@ MAKE_REFLECT_STRUCT(lsDocumentOnTypeFormattingOptions, firstTriggerCharacter,
 | 
			
		||||
// Document link options
 | 
			
		||||
struct lsDocumentLinkOptions {
 | 
			
		||||
  // Document links have a resolve provider as well.
 | 
			
		||||
  bool resolveProvider = true;
 | 
			
		||||
  bool resolveProvider = false;
 | 
			
		||||
};
 | 
			
		||||
MAKE_REFLECT_STRUCT(lsDocumentLinkOptions, resolveProvider);
 | 
			
		||||
 | 
			
		||||
@ -158,12 +158,11 @@ struct lsServerCapabilities {
 | 
			
		||||
  // The server provides code lens.
 | 
			
		||||
  lsCodeLensOptions codeLensProvider;
 | 
			
		||||
  // The server provides document formatting.
 | 
			
		||||
  bool documentFormattingProvider = false;
 | 
			
		||||
  bool documentFormattingProvider = true;
 | 
			
		||||
  // The server provides document range formatting.
 | 
			
		||||
  bool documentRangeFormattingProvider = false;
 | 
			
		||||
  bool documentRangeFormattingProvider = true;
 | 
			
		||||
  // The server provides document formatting on typing.
 | 
			
		||||
  std::optional<lsDocumentOnTypeFormattingOptions>
 | 
			
		||||
      documentOnTypeFormattingProvider;
 | 
			
		||||
  lsDocumentOnTypeFormattingOptions documentOnTypeFormattingProvider;
 | 
			
		||||
  // The server provides rename support.
 | 
			
		||||
  bool renameProvider = true;
 | 
			
		||||
  // The server provides document link support.
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										192
									
								
								src/messages/textDocument_formatting.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								src/messages/textDocument_formatting.cc
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,192 @@
 | 
			
		||||
// Copyright 2017-2018 ccls Authors
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
#include "message_handler.h"
 | 
			
		||||
#include "pipeline.hh"
 | 
			
		||||
#include "working_files.h"
 | 
			
		||||
 | 
			
		||||
#include <clang/Format/Format.h>
 | 
			
		||||
#include <clang/Tooling/Core/Replacement.h>
 | 
			
		||||
 | 
			
		||||
using namespace ccls;
 | 
			
		||||
using namespace clang;
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
const MethodType formatting = "textDocument/formatting",
 | 
			
		||||
                 onTypeFormatting = "textDocument/onTypeFormatting",
 | 
			
		||||
                 rangeFormatting = "textDocument/rangeFormatting";
 | 
			
		||||
 | 
			
		||||
struct lsFormattingOptions {
 | 
			
		||||
  // Size of a tab in spaces.
 | 
			
		||||
  int tabSize;
 | 
			
		||||
  // Prefer spaces over tabs.
 | 
			
		||||
  bool insertSpaces;
 | 
			
		||||
};
 | 
			
		||||
MAKE_REFLECT_STRUCT(lsFormattingOptions, tabSize, insertSpaces);
 | 
			
		||||
 | 
			
		||||
struct Out_TextDocumentFormatting
 | 
			
		||||
    : public lsOutMessage<Out_TextDocumentFormatting> {
 | 
			
		||||
  lsRequestId id;
 | 
			
		||||
  std::vector<lsTextEdit> result;
 | 
			
		||||
};
 | 
			
		||||
MAKE_REFLECT_STRUCT(Out_TextDocumentFormatting, jsonrpc, id, result);
 | 
			
		||||
 | 
			
		||||
llvm::Expected<tooling::Replacements>
 | 
			
		||||
FormatCode(std::string_view code, std::string_view file, tooling::Range Range) {
 | 
			
		||||
  StringRef Code(code.data(), code.size()), File(file.data(), file.size());
 | 
			
		||||
  auto Style = format::getStyle("file", File, "LLVM", Code, nullptr);
 | 
			
		||||
  if (!Style)
 | 
			
		||||
    return Style.takeError();
 | 
			
		||||
  tooling::Replacements IncludeReplaces =
 | 
			
		||||
      format::sortIncludes(*Style, Code, {Range}, File);
 | 
			
		||||
  auto Changed = tooling::applyAllReplacements(Code, IncludeReplaces);
 | 
			
		||||
  if (!Changed)
 | 
			
		||||
    return Changed.takeError();
 | 
			
		||||
  return IncludeReplaces.merge(format::reformat(
 | 
			
		||||
      *Style, *Changed,
 | 
			
		||||
      tooling::calculateRangesAfterReplacements(IncludeReplaces, {Range}),
 | 
			
		||||
      File));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<lsTextEdit>
 | 
			
		||||
ReplacementsToEdits(std::string_view code, const tooling::Replacements &Repls) {
 | 
			
		||||
  std::vector<lsTextEdit> ret;
 | 
			
		||||
  int i = 0, line = 0, col = 0;
 | 
			
		||||
  auto move = [&](int p) {
 | 
			
		||||
    for (; i < p; i++)
 | 
			
		||||
      if (code[i] == '\n')
 | 
			
		||||
        line++, col = 0;
 | 
			
		||||
      else {
 | 
			
		||||
        if ((uint8_t)code[i] >= 128) {
 | 
			
		||||
          while (128 <= (uint8_t)code[++i] && (uint8_t)code[i] < 192)
 | 
			
		||||
            ;
 | 
			
		||||
          i--;
 | 
			
		||||
        }
 | 
			
		||||
        col++;
 | 
			
		||||
      }
 | 
			
		||||
  };
 | 
			
		||||
  for (const auto &R : Repls) {
 | 
			
		||||
    move(R.getOffset());
 | 
			
		||||
    int l = line, c = col;
 | 
			
		||||
    move(R.getOffset() + R.getLength());
 | 
			
		||||
    ret.push_back({{{l, c}, {line, col}}, R.getReplacementText().str()});
 | 
			
		||||
  }
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Format(WorkingFile *wfile, tooling::Range range, lsRequestId id) {
 | 
			
		||||
  std::string_view code = wfile->buffer_content;
 | 
			
		||||
  auto ReplsOrErr =
 | 
			
		||||
      FormatCode(code, wfile->filename, range);
 | 
			
		||||
  if (ReplsOrErr) {
 | 
			
		||||
    Out_TextDocumentFormatting out;
 | 
			
		||||
    out.id = id;
 | 
			
		||||
    out.result = ReplacementsToEdits(code, *ReplsOrErr);
 | 
			
		||||
    pipeline::WriteStdout(formatting, out);
 | 
			
		||||
  } else {
 | 
			
		||||
    Out_Error err;
 | 
			
		||||
    err.id = id;
 | 
			
		||||
    err.error.code = lsErrorCodes::UnknownErrorCode;
 | 
			
		||||
    err.error.message = llvm::toString(ReplsOrErr.takeError());
 | 
			
		||||
    pipeline::WriteStdout(kMethodType_Unknown, err);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct In_TextDocumentFormatting : public RequestInMessage {
 | 
			
		||||
  MethodType GetMethodType() const override { return formatting; }
 | 
			
		||||
  struct Params {
 | 
			
		||||
    lsTextDocumentIdentifier textDocument;
 | 
			
		||||
    lsFormattingOptions options;
 | 
			
		||||
  } params;
 | 
			
		||||
};
 | 
			
		||||
MAKE_REFLECT_STRUCT(In_TextDocumentFormatting::Params, textDocument, options);
 | 
			
		||||
MAKE_REFLECT_STRUCT(In_TextDocumentFormatting, id, params);
 | 
			
		||||
REGISTER_IN_MESSAGE(In_TextDocumentFormatting);
 | 
			
		||||
 | 
			
		||||
struct Handler_TextDocumentFormatting
 | 
			
		||||
    : BaseMessageHandler<In_TextDocumentFormatting> {
 | 
			
		||||
  MethodType GetMethodType() const override { return formatting; }
 | 
			
		||||
  void Run(In_TextDocumentFormatting *request) override {
 | 
			
		||||
    auto ¶ms = request->params;
 | 
			
		||||
    QueryFile *file;
 | 
			
		||||
    if (!FindFileOrFail(db, project, request->id,
 | 
			
		||||
                        params.textDocument.uri.GetPath(), &file))
 | 
			
		||||
      return;
 | 
			
		||||
    WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
 | 
			
		||||
    if (!wfile)
 | 
			
		||||
      return;
 | 
			
		||||
    Format(wfile, {0, (unsigned)wfile->buffer_content.size()}, request->id);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentFormatting);
 | 
			
		||||
 | 
			
		||||
struct In_TextDocumentOnTypeFormatting : public RequestInMessage {
 | 
			
		||||
  MethodType GetMethodType() const override { return onTypeFormatting; }
 | 
			
		||||
  struct Params {
 | 
			
		||||
    lsTextDocumentIdentifier textDocument;
 | 
			
		||||
    lsPosition position;
 | 
			
		||||
    std::string ch;
 | 
			
		||||
    lsFormattingOptions options;
 | 
			
		||||
  } params;
 | 
			
		||||
};
 | 
			
		||||
MAKE_REFLECT_STRUCT(In_TextDocumentOnTypeFormatting::Params, textDocument,
 | 
			
		||||
                    position, ch, options);
 | 
			
		||||
MAKE_REFLECT_STRUCT(In_TextDocumentOnTypeFormatting, id, params);
 | 
			
		||||
REGISTER_IN_MESSAGE(In_TextDocumentOnTypeFormatting);
 | 
			
		||||
 | 
			
		||||
struct Handler_TextDocumentOnTypeFormatting
 | 
			
		||||
    : BaseMessageHandler<In_TextDocumentOnTypeFormatting> {
 | 
			
		||||
  MethodType GetMethodType() const override { return onTypeFormatting; }
 | 
			
		||||
  void Run(In_TextDocumentOnTypeFormatting *request) override {
 | 
			
		||||
    auto ¶ms = request->params;
 | 
			
		||||
    QueryFile *file;
 | 
			
		||||
    if (!FindFileOrFail(db, project, request->id,
 | 
			
		||||
                        params.textDocument.uri.GetPath(), &file))
 | 
			
		||||
      return;
 | 
			
		||||
    WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
 | 
			
		||||
    if (!wfile)
 | 
			
		||||
      return;
 | 
			
		||||
    std::string_view code = wfile->buffer_content;
 | 
			
		||||
    int pos = GetOffsetForPosition(params.position, code);
 | 
			
		||||
    auto lbrace = code.find_last_of('{', pos);
 | 
			
		||||
    if (lbrace == std::string::npos)
 | 
			
		||||
      lbrace = pos;
 | 
			
		||||
    Format(wfile, {(unsigned)lbrace, unsigned(pos - lbrace)}, request->id);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentOnTypeFormatting);
 | 
			
		||||
 | 
			
		||||
struct In_TextDocumentRangeFormatting : public RequestInMessage {
 | 
			
		||||
  MethodType GetMethodType() const override { return rangeFormatting; }
 | 
			
		||||
  struct Params {
 | 
			
		||||
    lsTextDocumentIdentifier textDocument;
 | 
			
		||||
    lsRange range;
 | 
			
		||||
    lsFormattingOptions options;
 | 
			
		||||
  } params;
 | 
			
		||||
};
 | 
			
		||||
MAKE_REFLECT_STRUCT(In_TextDocumentRangeFormatting::Params, textDocument, range,
 | 
			
		||||
                    options);
 | 
			
		||||
MAKE_REFLECT_STRUCT(In_TextDocumentRangeFormatting, id, params);
 | 
			
		||||
REGISTER_IN_MESSAGE(In_TextDocumentRangeFormatting);
 | 
			
		||||
 | 
			
		||||
struct Handler_TextDocumentRangeFormatting
 | 
			
		||||
    : BaseMessageHandler<In_TextDocumentRangeFormatting> {
 | 
			
		||||
  MethodType GetMethodType() const override { return rangeFormatting; }
 | 
			
		||||
 | 
			
		||||
  void Run(In_TextDocumentRangeFormatting *request) override {
 | 
			
		||||
    auto ¶ms = request->params;
 | 
			
		||||
    QueryFile *file;
 | 
			
		||||
    if (!FindFileOrFail(db, project, request->id,
 | 
			
		||||
                        params.textDocument.uri.GetPath(), &file))
 | 
			
		||||
      return;
 | 
			
		||||
    WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
 | 
			
		||||
    if (!wfile)
 | 
			
		||||
      return;
 | 
			
		||||
    std::string_view code = wfile->buffer_content;
 | 
			
		||||
    int begin = GetOffsetForPosition(params.range.start, code),
 | 
			
		||||
        end = GetOffsetForPosition(params.range.end, code);
 | 
			
		||||
    Format(wfile, {(unsigned)begin, unsigned(end - begin)}, request->id);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentRangeFormatting);
 | 
			
		||||
} // namespace
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user