Enable formatting (#188)
This commit is contained in:
@@ -46,6 +46,10 @@ struct LocalSourceRange {
|
||||
|
||||
constexpr bool operator== (const LocalSourceRange& other) const = default;
|
||||
|
||||
constexpr auto length() {
|
||||
return end - begin;
|
||||
}
|
||||
|
||||
constexpr bool contains(uint32_t offset) const {
|
||||
return offset >= begin && offset <= end;
|
||||
}
|
||||
|
||||
@@ -1 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "AST/SourceCode.h"
|
||||
#include "Protocol/Feature/Formatting.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
|
||||
namespace clice::feature {
|
||||
|
||||
std::vector<proto::TextEdit> document_format(llvm::StringRef file,
|
||||
llvm::StringRef content,
|
||||
std::optional<LocalSourceRange>);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,19 @@ struct DocumentFormattingClientCapabilities {};
|
||||
|
||||
using DocumentFormattingOptions = bool;
|
||||
|
||||
struct DocumentFormattingParams {
|
||||
/// The document to format.
|
||||
TextDocumentIdentifier textDocument;
|
||||
};
|
||||
|
||||
struct DocumentRangeFormattingParams {
|
||||
/// The document to format.
|
||||
TextDocumentIdentifier textDocument;
|
||||
|
||||
/// The range to format
|
||||
Range range;
|
||||
};
|
||||
|
||||
struct DocumentRangeFormattingClientCapabilities {};
|
||||
|
||||
using DocumentRangeFormattingOptions = bool;
|
||||
|
||||
@@ -123,10 +123,10 @@ struct ServerCapabilities {
|
||||
/// FIXME: DocumentColorOptions colorProvider;
|
||||
|
||||
/// The server provides document formatting.
|
||||
/// FIXME: DocumentFormattingOptions documentFormattingProvider;
|
||||
DocumentFormattingOptions documentFormattingProvider;
|
||||
|
||||
/// The server provides document range formatting.
|
||||
/// FIXME: DocumentRangeFormattingOptions documentRangeFormattingProvider;
|
||||
DocumentRangeFormattingOptions documentRangeFormattingProvider;
|
||||
|
||||
/// The server provides document formatting on typing.
|
||||
/// FIXME: DocumentOnTypeFormattingOptions documentOnTypeFormattingProvider;
|
||||
|
||||
@@ -196,21 +196,27 @@ private:
|
||||
async::Task<> on_did_close(proto::DidCloseTextDocumentParams params);
|
||||
|
||||
private:
|
||||
async::Task<json::Value> on_completion(proto::CompletionParams params);
|
||||
using Result = async::Task<json::Value>;
|
||||
|
||||
async::Task<json::Value> on_hover(proto::HoverParams params);
|
||||
auto on_completion(proto::CompletionParams params) -> Result;
|
||||
|
||||
async::Task<json::Value> on_signature_help(proto::SignatureHelpParams params);
|
||||
auto on_hover(proto::HoverParams params) -> Result;
|
||||
|
||||
async::Task<json::Value> on_document_symbol(proto::DocumentSymbolParams params);
|
||||
auto on_signature_help(proto::SignatureHelpParams params) -> Result;
|
||||
|
||||
async::Task<json::Value> on_document_link(proto::DocumentLinkParams params);
|
||||
auto on_document_symbol(proto::DocumentSymbolParams params) -> Result;
|
||||
|
||||
async::Task<json::Value> on_folding_range(proto::FoldingRangeParams params);
|
||||
auto on_document_link(proto::DocumentLinkParams params) -> Result;
|
||||
|
||||
async::Task<json::Value> on_semantic_token(proto::SemanticTokensParams params);
|
||||
auto on_document_format(proto::DocumentFormattingParams params) -> Result;
|
||||
|
||||
async::Task<json::Value> on_inlay_hint(proto::InlayHintParams params);
|
||||
auto on_document_range_format(proto::DocumentRangeFormattingParams params) -> Result;
|
||||
|
||||
auto on_folding_range(proto::FoldingRangeParams params) -> Result;
|
||||
|
||||
auto on_semantic_token(proto::SemanticTokensParams params) -> Result;
|
||||
|
||||
auto on_inlay_hint(proto::InlayHintParams params) -> Result;
|
||||
|
||||
private:
|
||||
/// The current request id.
|
||||
|
||||
60
src/Feature/Formatting.cpp
Normal file
60
src/Feature/Formatting.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#include "Feature/Formatting.h"
|
||||
#include "Support/Logger.h"
|
||||
#include "Server/Convert.h"
|
||||
#include "clang/Format/Format.h"
|
||||
|
||||
namespace clice::feature {
|
||||
|
||||
namespace tooling = clang::tooling;
|
||||
|
||||
static auto format(llvm::StringRef file, llvm::StringRef content, tooling::Range range)
|
||||
-> std::expected<tooling::Replacements, std::string> {
|
||||
/// Set the code to empty to avoid meaningless file type guess.
|
||||
/// See https://github.com/llvm/llvm-project/issues/48863.
|
||||
auto style = clang::format::getStyle(clang::format::DefaultFormatStyle,
|
||||
file,
|
||||
clang::format::DefaultFallbackStyle,
|
||||
"");
|
||||
if(!style) {
|
||||
return std::unexpected(std::format("{}", style.takeError()));
|
||||
}
|
||||
|
||||
std::vector<tooling::Range> ranges = {range};
|
||||
auto include_replacements = clang::format::sortIncludes(*style, content, ranges, file);
|
||||
auto changed = tooling::applyAllReplacements(content, include_replacements);
|
||||
if(!changed) {
|
||||
return std::unexpected(std::format("{}", changed.takeError()));
|
||||
}
|
||||
|
||||
return include_replacements.merge(clang::format::reformat(
|
||||
*style,
|
||||
*changed,
|
||||
tooling::calculateRangesAfterReplacements(include_replacements, ranges)));
|
||||
}
|
||||
|
||||
std::vector<proto::TextEdit> document_format(llvm::StringRef file,
|
||||
llvm::StringRef content,
|
||||
std::optional<LocalSourceRange> range) {
|
||||
std::vector<proto::TextEdit> edits;
|
||||
|
||||
auto selection =
|
||||
range ? tooling::Range(range->begin, range->length()) : tooling::Range(0, content.size());
|
||||
auto replacements = format(file, content, selection);
|
||||
if(!replacements) {
|
||||
log::info("Fail to format for {}\n{}", file, replacements.error());
|
||||
return edits;
|
||||
}
|
||||
|
||||
for(auto& replacement: *replacements) {
|
||||
proto::TextEdit edit;
|
||||
PositionConverter converter(content, PositionEncodingKind::UTF8);
|
||||
edit.range.start = converter.toPosition(replacement.getOffset());
|
||||
edit.range.end = converter.toPosition(replacement.getOffset() + replacement.getLength());
|
||||
edit.newText = replacement.getReplacementText();
|
||||
edits.emplace_back(std::move(edit));
|
||||
}
|
||||
|
||||
return edits;
|
||||
}
|
||||
|
||||
} // namespace clice::feature
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "Feature/Formatting.h"
|
||||
#include "Server/Server.h"
|
||||
#include "Server/Convert.h"
|
||||
#include "Compiler/Compilation.h"
|
||||
@@ -12,7 +13,7 @@
|
||||
|
||||
namespace clice {
|
||||
|
||||
async::Task<json::Value> Server::on_completion(proto::CompletionParams params) {
|
||||
auto Server::on_completion(proto::CompletionParams params) -> Result {
|
||||
auto path = mapping.to_path(params.textDocument.uri);
|
||||
auto opening_file = opening_files.get_or_add(path);
|
||||
|
||||
@@ -38,7 +39,7 @@ async::Task<json::Value> Server::on_completion(proto::CompletionParams params) {
|
||||
}
|
||||
}
|
||||
|
||||
async::Task<json::Value> Server::on_hover(proto::HoverParams params) {
|
||||
auto Server::on_hover(proto::HoverParams params) -> Result {
|
||||
auto path = mapping.to_path(params.textDocument.uri);
|
||||
auto opening_file = opening_files.get_or_add(path);
|
||||
auto guard = co_await opening_file->ast_built_lock.try_lock();
|
||||
@@ -86,7 +87,7 @@ async::Task<json::Value> Server::on_signature_help(proto::SignatureHelpParams pa
|
||||
}
|
||||
}
|
||||
|
||||
async::Task<json::Value> Server::on_document_symbol(proto::DocumentSymbolParams params) {
|
||||
auto Server::on_document_symbol(proto::DocumentSymbolParams params) -> Result {
|
||||
auto path = mapping.to_path(params.textDocument.uri);
|
||||
auto opening_file = opening_files.get_or_add(path);
|
||||
|
||||
@@ -134,7 +135,7 @@ async::Task<json::Value> Server::on_document_symbol(proto::DocumentSymbolParams
|
||||
});
|
||||
}
|
||||
|
||||
async::Task<json::Value> Server::on_document_link(proto::DocumentLinkParams params) {
|
||||
auto Server::on_document_link(proto::DocumentLinkParams params) -> Result {
|
||||
auto path = mapping.to_path(params.textDocument.uri);
|
||||
auto opening_file = opening_files.get_or_add(path);
|
||||
auto guard = co_await opening_file->ast_built_lock.try_lock();
|
||||
@@ -163,6 +164,32 @@ async::Task<json::Value> Server::on_document_link(proto::DocumentLinkParams para
|
||||
});
|
||||
}
|
||||
|
||||
auto Server::on_document_format(proto::DocumentFormattingParams params) -> Result {
|
||||
auto path = mapping.to_path(params.textDocument.uri);
|
||||
auto opening_file = opening_files.get_or_add(path);
|
||||
|
||||
auto content = opening_file->content;
|
||||
co_return co_await async::submit([&, kind = this->kind] {
|
||||
auto edits = feature::document_format(path, content, std::nullopt);
|
||||
/// FIXME: adjust position encoding...
|
||||
return json::serialize(edits);
|
||||
});
|
||||
}
|
||||
|
||||
auto Server::on_document_range_format(proto::DocumentRangeFormattingParams params) -> Result {
|
||||
auto path = mapping.to_path(params.textDocument.uri);
|
||||
auto opening_file = opening_files.get_or_add(path);
|
||||
|
||||
auto content = opening_file->content;
|
||||
co_return co_await async::submit([&, kind = this->kind] {
|
||||
auto begin = to_offset(kind, content, params.range.start);
|
||||
auto end = to_offset(kind, content, params.range.end);
|
||||
auto edits = feature::document_format(path, content, LocalSourceRange(begin, end));
|
||||
/// FIXME: adjust position encoding...
|
||||
return json::serialize(edits);
|
||||
});
|
||||
}
|
||||
|
||||
async::Task<json::Value> Server::on_folding_range(proto::FoldingRangeParams params) {
|
||||
auto path = mapping.to_path(params.textDocument.uri);
|
||||
auto opening_file = opening_files.get_or_add(path);
|
||||
@@ -201,7 +228,7 @@ async::Task<json::Value> Server::on_folding_range(proto::FoldingRangeParams para
|
||||
});
|
||||
}
|
||||
|
||||
async::Task<json::Value> Server::on_semantic_token(proto::SemanticTokensParams params) {
|
||||
auto Server::on_semantic_token(proto::SemanticTokensParams params) -> Result {
|
||||
auto path = mapping.to_path(params.textDocument.uri);
|
||||
auto opening_file = opening_files.get_or_add(path);
|
||||
auto guard = co_await opening_file->ast_built_lock.try_lock();
|
||||
@@ -217,7 +244,7 @@ async::Task<json::Value> Server::on_semantic_token(proto::SemanticTokensParams p
|
||||
});
|
||||
}
|
||||
|
||||
async::Task<json::Value> Server::on_inlay_hint(proto::InlayHintParams params) {
|
||||
auto Server::on_inlay_hint(proto::InlayHintParams params) -> Result {
|
||||
auto path = mapping.to_path(params.textDocument.uri);
|
||||
auto opening_file = opening_files.get_or_add(path);
|
||||
auto guard = co_await opening_file->ast_built_lock.try_lock();
|
||||
|
||||
@@ -62,6 +62,10 @@ async::Task<json::Value> Server::on_initialize(proto::InitializeParams params) {
|
||||
/// DocumentLink
|
||||
capabilities.documentLinkProvider.resolveProvider = false;
|
||||
|
||||
/// Formatting
|
||||
capabilities.documentFormattingProvider = true;
|
||||
capabilities.documentRangeFormattingProvider = true;
|
||||
|
||||
/// FoldingRange
|
||||
capabilities.foldingRangeProvider = true;
|
||||
|
||||
|
||||
@@ -111,6 +111,8 @@ Server::Server() {
|
||||
register_callback<&Server::on_signature_help>("textDocument/signatureHelp");
|
||||
register_callback<&Server::on_document_symbol>("textDocument/documentSymbol");
|
||||
register_callback<&Server::on_document_link>("textDocument/documentLink");
|
||||
register_callback<&Server::on_document_format>("textDocument/formatting");
|
||||
register_callback<&Server::on_document_range_format>("textDocument/rangeFormatting");
|
||||
register_callback<&Server::on_folding_range>("textDocument/foldingRange");
|
||||
register_callback<&Server::on_semantic_token>("textDocument/semanticTokens/full");
|
||||
register_callback<&Server::on_inlay_hint>("textDocument/inlayHint");
|
||||
|
||||
17
tests/unit/Feature/Formatting.cpp
Normal file
17
tests/unit/Feature/Formatting.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#include "Test/Test.h"
|
||||
#include "Feature/Formatting.h"
|
||||
|
||||
namespace clice::testing {
|
||||
|
||||
namespace {
|
||||
|
||||
suite<"Formatting"> suite = [] {
|
||||
test("Simple") = [] {
|
||||
auto edits = feature::document_format("main.cpp", "int main() { return 0; }", std::nullopt);
|
||||
expect(ne(edits.size(), 0));
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace clice::testing
|
||||
Reference in New Issue
Block a user