Support textDocument/{formatting,onTypeFormatting,rangeFormatting}

This commit is contained in:
Fangrui Song 2018-09-27 22:19:26 -07:00
parent ae19826411
commit 7eacd2664f
5 changed files with 210 additions and 7 deletions

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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.

View 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 &params = 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 &params = 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 &params = 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