mirror of
https://github.com/MaskRay/ccls.git
synced 2024-11-22 07:35:08 +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_completion.cc
|
||||||
src/messages/textDocument_definition.cc
|
src/messages/textDocument_definition.cc
|
||||||
src/messages/textDocument_did.cc
|
src/messages/textDocument_did.cc
|
||||||
|
src/messages/textDocument_formatting.cc
|
||||||
src/messages/textDocument_documentHighlight.cc
|
src/messages/textDocument_documentHighlight.cc
|
||||||
src/messages/textDocument_documentSymbol.cc
|
src/messages/textDocument_documentSymbol.cc
|
||||||
src/messages/textDocument_hover.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)
|
* code completion (with both signature help and snippets)
|
||||||
* [definition](src/messages/textDocument_definition.cc)/[references](src/messages/textDcument_references.cc), and other cross references
|
* [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` ...
|
* 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)
|
* 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)
|
* [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)
|
* [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.
|
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.
|
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:
|
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)
|
LLVM_INCLUDE_DIR LLVM_BUILD_INCLUDE_DIR)
|
||||||
|
|
||||||
_Clang_find_library(Clang_LIBRARY clangIndex)
|
_Clang_find_library(Clang_LIBRARY clangIndex)
|
||||||
|
_Clang_find_add_library(clangFormat)
|
||||||
_Clang_find_add_library(clangTooling)
|
_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(clangFrontend)
|
||||||
_Clang_find_add_library(clangParse)
|
_Clang_find_add_library(clangParse)
|
||||||
_Clang_find_add_library(clangSerialization)
|
_Clang_find_add_library(clangSerialization)
|
||||||
|
@ -54,7 +54,7 @@ MAKE_REFLECT_STRUCT(lsCompletionOptions, resolveProvider, triggerCharacters);
|
|||||||
// Format document on type options
|
// Format document on type options
|
||||||
struct lsDocumentOnTypeFormattingOptions {
|
struct lsDocumentOnTypeFormattingOptions {
|
||||||
// A character on which formatting should be triggered, like `}`.
|
// A character on which formatting should be triggered, like `}`.
|
||||||
std::string firstTriggerCharacter;
|
std::string firstTriggerCharacter = "}";
|
||||||
|
|
||||||
// More trigger characters.
|
// More trigger characters.
|
||||||
std::vector<std::string> moreTriggerCharacter;
|
std::vector<std::string> moreTriggerCharacter;
|
||||||
@ -65,7 +65,7 @@ MAKE_REFLECT_STRUCT(lsDocumentOnTypeFormattingOptions, firstTriggerCharacter,
|
|||||||
// Document link options
|
// Document link options
|
||||||
struct lsDocumentLinkOptions {
|
struct lsDocumentLinkOptions {
|
||||||
// Document links have a resolve provider as well.
|
// Document links have a resolve provider as well.
|
||||||
bool resolveProvider = true;
|
bool resolveProvider = false;
|
||||||
};
|
};
|
||||||
MAKE_REFLECT_STRUCT(lsDocumentLinkOptions, resolveProvider);
|
MAKE_REFLECT_STRUCT(lsDocumentLinkOptions, resolveProvider);
|
||||||
|
|
||||||
@ -158,12 +158,11 @@ struct lsServerCapabilities {
|
|||||||
// The server provides code lens.
|
// The server provides code lens.
|
||||||
lsCodeLensOptions codeLensProvider;
|
lsCodeLensOptions codeLensProvider;
|
||||||
// The server provides document formatting.
|
// The server provides document formatting.
|
||||||
bool documentFormattingProvider = false;
|
bool documentFormattingProvider = true;
|
||||||
// The server provides document range formatting.
|
// The server provides document range formatting.
|
||||||
bool documentRangeFormattingProvider = false;
|
bool documentRangeFormattingProvider = true;
|
||||||
// The server provides document formatting on typing.
|
// The server provides document formatting on typing.
|
||||||
std::optional<lsDocumentOnTypeFormattingOptions>
|
lsDocumentOnTypeFormattingOptions documentOnTypeFormattingProvider;
|
||||||
documentOnTypeFormattingProvider;
|
|
||||||
// The server provides rename support.
|
// The server provides rename support.
|
||||||
bool renameProvider = true;
|
bool renameProvider = true;
|
||||||
// The server provides document link support.
|
// 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