mirror of
https://github.com/MaskRay/ccls.git
synced 2024-11-21 15:15:07 +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