diff --git a/src/clang_format.cc b/src/clang_format.cc new file mode 100644 index 00000000..659d38f7 --- /dev/null +++ b/src/clang_format.cc @@ -0,0 +1,50 @@ +#if USE_CLANG_CXX + +#include "clang_format.h" + +#include + +using namespace clang::format; +using namespace clang::tooling; + +ClangFormat::ClangFormat(llvm::StringRef document_filename, + llvm::StringRef document, + llvm::ArrayRef ranges, + int tab_size, + bool insert_spaces) + : document_filename_(document_filename), + document_(document), + ranges_(ranges), + tab_size_(tab_size), + insert_spaces_(insert_spaces) {} +ClangFormat::~ClangFormat(){}; + +static FormatStyle::LanguageKind getLanguageKindFromFilename( + llvm::StringRef filename) { + if (filename.endswith(".m") || filename.endswith(".mm")) { + return FormatStyle::LK_ObjC; + } + return FormatStyle::LK_Cpp; +} + +std::vector ClangFormat::FormatWholeDocument() { + const auto language_kind = getLanguageKindFromFilename(document_filename_); + FormatStyle predefined_style; + getPredefinedStyle("chromium", language_kind, &predefined_style); + llvm::Expected style = + getStyle("file", document_filename_, "chromium"); + if (!style) { + LOG_S(ERROR) << llvm::toString(style.takeError()); + return {}; + } + + if (*style == predefined_style) { + style->UseTab = insert_spaces_ ? FormatStyle::UseTabStyle::UT_Never + : FormatStyle::UseTabStyle::UT_Always; + style->IndentWidth = tab_size_; + } + auto format_result = reformat(*style, document_, ranges_, document_filename_); + return std::vector(format_result.begin(), format_result.end()); +} + +#endif diff --git a/src/clang_format.h b/src/clang_format.h new file mode 100644 index 00000000..dc7b4565 --- /dev/null +++ b/src/clang_format.h @@ -0,0 +1,26 @@ +#if USE_CLANG_CXX + +#pragma once + +#include + +#include + +struct ClangFormat { + llvm::StringRef document_filename_; + llvm::StringRef document_; + llvm::ArrayRef ranges_; + int tab_size_; + bool insert_spaces_; + + ClangFormat(llvm::StringRef document_filename, + llvm::StringRef document, + llvm::ArrayRef ranges, + int tab_size, + bool insert_spaces); + ~ClangFormat(); + + std::vector FormatWholeDocument(); +}; + +#endif diff --git a/src/clang_utils.cc b/src/clang_utils.cc index c92c6993..278e6d22 100644 --- a/src/clang_utils.cc +++ b/src/clang_utils.cc @@ -104,6 +104,30 @@ optional BuildAndDisposeDiagnostic(CXDiagnostic diagnostic, return ls_diagnostic; } +#if USE_CLANG_CXX +static lsPosition OffsetToRange(llvm::StringRef document, size_t offset) { + // TODO: Support Windows line endings, etc. + llvm::StringRef text_before = document.substr(0, offset); + int num_line = text_before.count('\n'); + int num_column = text_before.size() - text_before.rfind('\n') - 1; + return {num_line, num_column}; +} + +std::vector ConvertClangReplacementsIntoTextEdits( + llvm::StringRef document, + const std::vector& clang_replacements) { + std::vector text_edits_result; + for (const auto& replacement : clang_replacements) { + const auto startPosition = OffsetToRange(document, replacement.getOffset()); + const auto endPosition = OffsetToRange( + document, replacement.getOffset() + replacement.getLength()); + text_edits_result.push_back( + {{startPosition, endPosition}, replacement.getReplacementText()}); + } + return text_edits_result; +} +#endif + std::string FileName(CXFile file) { CXString cx_name = clang_getFileName(file); std::string name = ToString(cx_name); diff --git a/src/clang_utils.h b/src/clang_utils.h index d9b3668c..9932d2eb 100644 --- a/src/clang_utils.h +++ b/src/clang_utils.h @@ -3,6 +3,9 @@ #include "language_server_api.h" #include +#if USE_CLANG_CXX +#include +#endif #include #include @@ -17,4 +20,11 @@ std::string FileName(CXFile file); std::string ToString(CXString cx_string); -std::string ToString(CXCursorKind cursor_kind); \ No newline at end of file +std::string ToString(CXCursorKind cursor_kind); + +// Converts Clang formatting replacement operations into LSP text edits. +#if USE_CLANG_CXX +std::vector ConvertClangReplacementsIntoTextEdits( + llvm::StringRef document, + const std::vector& clang_replacements); +#endif diff --git a/src/ipc.cc b/src/ipc.cc index b1463cd9..90d6a12f 100644 --- a/src/ipc.cc +++ b/src/ipc.cc @@ -34,6 +34,8 @@ const char* IpcIdToString(IpcId id) { return "textDocument/documentHighlight"; case IpcId::TextDocumentHover: return "textDocument/hover"; + case IpcId::TextDocumentFormatting: + return "textDocument/formatting"; case IpcId::TextDocumentReferences: return "textDocument/references"; case IpcId::TextDocumentDocumentSymbol: @@ -89,4 +91,4 @@ const char* IpcIdToString(IpcId id) { BaseIpcMessage::BaseIpcMessage(IpcId method_id) : method_id(method_id) {} -BaseIpcMessage::~BaseIpcMessage() = default; \ No newline at end of file +BaseIpcMessage::~BaseIpcMessage() = default; diff --git a/src/ipc.h b/src/ipc.h index aca8a91b..c0860649 100644 --- a/src/ipc.h +++ b/src/ipc.h @@ -22,6 +22,7 @@ enum class IpcId : int { TextDocumentDefinition, TextDocumentDocumentHighlight, TextDocumentHover, + TextDocumentFormatting, TextDocumentReferences, TextDocumentDocumentSymbol, TextDocumentDocumentLink, @@ -76,4 +77,4 @@ struct BaseIpcMessage { template struct IpcMessage : public BaseIpcMessage { IpcMessage() : BaseIpcMessage(T::kIpcId) {} -}; \ No newline at end of file +}; diff --git a/src/messages/text_document_formatting.cc b/src/messages/text_document_formatting.cc new file mode 100644 index 00000000..8b114cf3 --- /dev/null +++ b/src/messages/text_document_formatting.cc @@ -0,0 +1,81 @@ +#include "clang_format.h" +#include "message_handler.h" +#include "queue_manager.h" +#include "working_files.h" + +#include + +namespace { +struct lsFormattingOptions { + // Size of a tab in spaces. + int tabSize; + // Prefer spaces over tabs. + bool insertSpaces; +}; +MAKE_REFLECT_STRUCT(lsFormattingOptions, tabSize, insertSpaces); + +struct lsTextDocumentFormattingParams { + // The text document. + lsTextDocumentIdentifier textDocument; + + // The format options, like tabs or spaces. + lsFormattingOptions formattingOptions; +}; +MAKE_REFLECT_STRUCT(lsTextDocumentFormattingParams, + textDocument, + formattingOptions); + +struct Ipc_TextDocumentFormatting + : public IpcMessage { + const static IpcId kIpcId = IpcId::TextDocumentFormatting; + + lsRequestId id; + lsTextDocumentFormattingParams params; +}; +MAKE_REFLECT_STRUCT(Ipc_TextDocumentFormatting, id, params); +REGISTER_IPC_MESSAGE(Ipc_TextDocumentFormatting); + +struct Out_TextDocumentFormatting + : public lsOutMessage { + lsRequestId id; + std::vector result; +}; +MAKE_REFLECT_STRUCT(Out_TextDocumentFormatting, jsonrpc, id, result); + +struct TextDocumentFormattingHandler + : BaseMessageHandler { + void Run(Ipc_TextDocumentFormatting* request) override { + Out_TextDocumentFormatting response; + response.id = request->id; +#if USE_CLANG_CXX + QueryFile* file; + if (!FindFileOrFail(db, project, request->id, + request->params.textDocument.uri.GetPath(), &file)) { + return; + } + + WorkingFile* working_file = + working_files->GetFileByFilename(file->def->path); + + int tab_size = request->params.formattingOptions.tabSize; + bool insert_spaces = request->params.formattingOptions.insertSpaces; + + const auto clang_format = std::unique_ptr(new ClangFormat( + working_file->filename, working_file->buffer_content, + {clang::tooling::Range(0, working_file->buffer_content.size())}, + tab_size, insert_spaces)); + const auto replacements = clang_format->FormatWholeDocument(); + response.result = ConvertClangReplacementsIntoTextEdits( + working_file->buffer_content, replacements); +#else + LOG_S(WARNING) << "You must compile cquery with --use-clang-cxx to use " + "document formatting."; + // TODO: Fallback to execute the clang-format binary? + response.result = {}; +#endif + + QueueManager::WriteStdout(IpcId::TextDocumentFormatting, response); + } +}; +REGISTER_MESSAGE_HANDLER(TextDocumentFormattingHandler); +} // namespace diff --git a/wscript b/wscript index fb8a8955..23ff2852 100644 --- a/wscript +++ b/wscript @@ -56,8 +56,8 @@ if sys.version_info < (3, 0): if ret == 0: err = ctypes.WinError() ERROR_PRIVILEGE_NOT_HELD = 1314 - # Creating symbolic link on Windows requires a special priviledge SeCreateSymboliclinkPrivilege, - # which an non-elevated process lacks. Starting with Windows 10 build 14972, this got relaxed + # Creating symbolic link on Windows requires a special priviledge SeCreateSymboliclinkPrivilege, + # which an non-elevated process lacks. Starting with Windows 10 build 14972, this got relaxed # when Developer Mode is enabled. Triggering this new behaviour requires a new flag. Try again. if err[0] == ERROR_PRIVILEGE_NOT_HELD: flags |= SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE @@ -256,6 +256,9 @@ def build(bld): lib.append('clangAST') lib.append('clangLex') lib.append('clangBasic') + lib.append('clangFormat') + lib.append('clangToolingCore') + lib.append('clangRewrite') # The order is derived from llvm-config --libs core lib.append('LLVMCore')