Enable formatting (#188)

This commit is contained in:
ykiko
2025-08-23 22:52:27 +08:00
committed by GitHub
parent 52e45e1f26
commit 5f8a577e26
10 changed files with 161 additions and 16 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View 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