From abc2edf05f6bd47dd478ad695836ac50c5102934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=ADn?= Date: Mon, 25 Dec 2017 14:08:36 +0100 Subject: [PATCH 1/7] Add structures to support document formatting --- src/ipc.cc | 4 +++- src/ipc.h | 3 ++- src/language_server_api.h | 19 +++++++++++++++++++ src/messages/text_document_formatting.cc | 20 ++++++++++++++++++++ 4 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 src/messages/text_document_formatting.cc 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/language_server_api.h b/src/language_server_api.h index cd139ede..c600e74f 100644 --- a/src/language_server_api.h +++ b/src/language_server_api.h @@ -273,6 +273,25 @@ struct lsTextDocumentPositionParams { }; MAKE_REFLECT_STRUCT(lsTextDocumentPositionParams, textDocument, position); +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 lsTextEdit { // The range of the text document to be manipulated. To insert // text into a document create a range where start === end. diff --git a/src/messages/text_document_formatting.cc b/src/messages/text_document_formatting.cc new file mode 100644 index 00000000..ac177f54 --- /dev/null +++ b/src/messages/text_document_formatting.cc @@ -0,0 +1,20 @@ +#include "message_handler.h" + +namespace { +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 edits; +}; +MAKE_REFLECT_STRUCT(Out_TextDocumentFormatting, jsonrpc, id, edits); +} // namespace From 19341c18cdbb79012ca0475ca1473149c82c8558 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=ADn?= Date: Mon, 25 Dec 2017 18:04:10 +0100 Subject: [PATCH 2/7] Basic implementation of document formatting Still some important TODOs to address: - Improve the algorithm that converts between offsets and line/column pairs. Right now it's extremely naive. - Add proper support for a .clang-format file that specifies the coding style. --- src/clang_format.cc | 26 +++++++++++++++++ src/clang_format.h | 20 +++++++++++++ src/clang_utils.cc | 22 +++++++++++++++ src/clang_utils.h | 8 +++++- src/messages/text_document_formatting.cc | 36 ++++++++++++++++++++++-- wscript | 14 +++++++-- 6 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 src/clang_format.cc create mode 100644 src/clang_format.h diff --git a/src/clang_format.cc b/src/clang_format.cc new file mode 100644 index 00000000..07eb5ea5 --- /dev/null +++ b/src/clang_format.cc @@ -0,0 +1,26 @@ +#include "clang_format.h" + +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(){}; + +std::vector ClangFormat::FormatWholeDocument() { + // TODO: Construct a valid style. + FormatStyle style = getChromiumStyle(FormatStyle::LK_Cpp); + 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()); +} diff --git a/src/clang_format.h b/src/clang_format.h new file mode 100644 index 00000000..f2a39569 --- /dev/null +++ b/src/clang_format.h @@ -0,0 +1,20 @@ +#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(); +}; diff --git a/src/clang_utils.cc b/src/clang_utils.cc index c92c6993..60ee39da 100644 --- a/src/clang_utils.cc +++ b/src/clang_utils.cc @@ -104,6 +104,28 @@ optional BuildAndDisposeDiagnostic(CXDiagnostic diagnostic, return ls_diagnostic; } +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; +} + 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..0922643a 100644 --- a/src/clang_utils.h +++ b/src/clang_utils.h @@ -3,6 +3,7 @@ #include "language_server_api.h" #include +#include #include #include @@ -17,4 +18,9 @@ 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. +std::vector ConvertClangReplacementsIntoTextEdits( + llvm::StringRef document, + const std::vector& clang_replacements); diff --git a/src/messages/text_document_formatting.cc b/src/messages/text_document_formatting.cc index ac177f54..e5eaac1e 100644 --- a/src/messages/text_document_formatting.cc +++ b/src/messages/text_document_formatting.cc @@ -1,3 +1,4 @@ +#include "clang_format.h" #include "message_handler.h" namespace { @@ -14,7 +15,38 @@ REGISTER_IPC_MESSAGE(Ipc_TextDocumentFormatting); struct Out_TextDocumentFormatting : public lsOutMessage { lsRequestId id; - std::vector edits; + std::vector result; }; -MAKE_REFLECT_STRUCT(Out_TextDocumentFormatting, jsonrpc, id, edits); +MAKE_REFLECT_STRUCT(Out_TextDocumentFormatting, jsonrpc, id, result); + +struct TextDocumentFormattingHandler + : BaseMessageHandler { + void Run(Ipc_TextDocumentFormatting* request) override { + 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; + + Out_TextDocumentFormatting response; + response.id = request->id; + + 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); + + QueueManager::WriteStdout(IpcId::TextDocumentFormatting, response); + } +}; +REGISTER_MESSAGE_HANDLER(TextDocumentFormattingHandler); } // namespace diff --git a/wscript b/wscript index fb8a8955..44ed66e3 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 @@ -234,6 +234,16 @@ def build(bld): cc_files += bld.path.ant_glob(['src/clang_cxx/*.cc']) lib = [] + # clang-format support + lib.append('clangBasic') + lib.append('clangFormat') + lib.append('clangLex') + lib.append('clangRewrite') + lib.append('clangToolingCore') + lib.append('LLVMCore') + lib.append('LLVMDemangle') + lib.append('LLVMSupport') + lib.append('ncurses') if sys.platform.startswith('linux'): lib.append('rt') lib.append('pthread') From aa4e5e7e5b0459625df1f217294b5e0306dea779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=ADn?= Date: Mon, 25 Dec 2017 18:06:30 +0100 Subject: [PATCH 3/7] Implement .clang-format style management If we can't find a .clang-format file for the given file, default to Chromium style with the tab/spaces configuration that was provided by the client. --- src/clang_format.cc | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/clang_format.cc b/src/clang_format.cc index 07eb5ea5..3c1aeb9a 100644 --- a/src/clang_format.cc +++ b/src/clang_format.cc @@ -15,12 +15,24 @@ ClangFormat::ClangFormat(llvm::StringRef document_filename, 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() { - // TODO: Construct a valid style. - FormatStyle style = getChromiumStyle(FormatStyle::LK_Cpp); - style.UseTab = insert_spaces_ ? FormatStyle::UseTabStyle::UT_Never - : FormatStyle::UseTabStyle::UT_Always; - style.IndentWidth = tab_size_; + const auto language_kind = getLanguageKindFromFilename(document_filename_); + FormatStyle predefined_style; + getPredefinedStyle("chromium", language_kind, &predefined_style); + auto style = getStyle("file", document_filename_, "chromium"); + 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()); } From 0e16899b81c1561005485c33d721e4587eeb8e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=ADn?= Date: Wed, 27 Dec 2017 19:38:50 +0100 Subject: [PATCH 4/7] Move formatting structs to text_document_formatting.cc --- src/language_server_api.h | 19 ------------------- src/messages/text_document_formatting.cc | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/language_server_api.h b/src/language_server_api.h index c600e74f..cd139ede 100644 --- a/src/language_server_api.h +++ b/src/language_server_api.h @@ -273,25 +273,6 @@ struct lsTextDocumentPositionParams { }; MAKE_REFLECT_STRUCT(lsTextDocumentPositionParams, textDocument, position); -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 lsTextEdit { // The range of the text document to be manipulated. To insert // text into a document create a range where start === end. diff --git a/src/messages/text_document_formatting.cc b/src/messages/text_document_formatting.cc index e5eaac1e..d6846983 100644 --- a/src/messages/text_document_formatting.cc +++ b/src/messages/text_document_formatting.cc @@ -2,6 +2,25 @@ #include "message_handler.h" 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; From 05e9bf39661d972b7623fa11cb34b1a5e144d8f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=ADn?= Date: Fri, 29 Dec 2017 22:19:30 +0100 Subject: [PATCH 5/7] Add missing includes --- src/messages/text_document_formatting.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/messages/text_document_formatting.cc b/src/messages/text_document_formatting.cc index d6846983..e8e0e5c5 100644 --- a/src/messages/text_document_formatting.cc +++ b/src/messages/text_document_formatting.cc @@ -1,5 +1,7 @@ #include "clang_format.h" #include "message_handler.h" +#include "queue_manager.h" +#include "working_files.h" namespace { struct lsFormattingOptions { From 4fdc81ae6d6bdabd2fe2792aff9a68a6353a92cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=ADn?= Date: Fri, 29 Dec 2017 22:44:28 +0100 Subject: [PATCH 6/7] Manage Expected in formatting APIs --- src/clang_format.cc | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/clang_format.cc b/src/clang_format.cc index 3c1aeb9a..b460bb2a 100644 --- a/src/clang_format.cc +++ b/src/clang_format.cc @@ -1,5 +1,7 @@ #include "clang_format.h" +#include + using namespace clang::format; using namespace clang::tooling; @@ -27,12 +29,18 @@ std::vector ClangFormat::FormatWholeDocument() { const auto language_kind = getLanguageKindFromFilename(document_filename_); FormatStyle predefined_style; getPredefinedStyle("chromium", language_kind, &predefined_style); - auto style = getStyle("file", document_filename_, "chromium"); - if (style == predefined_style) { - style.UseTab = insert_spaces_ ? FormatStyle::UseTabStyle::UT_Never - : FormatStyle::UseTabStyle::UT_Always; - style.IndentWidth = tab_size_; + llvm::Expected style = + getStyle("file", document_filename_, "chromium"); + if (!style) { + LOG_S(ERROR) << llvm::toString(style.takeError()); + return {}; } - auto format_result = reformat(style, document_, ranges_, document_filename_); + + 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()); } From f389d294b7281fc6435997c098e71b41427e40e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=ADn?= Date: Sun, 31 Dec 2017 23:15:03 +0100 Subject: [PATCH 7/7] Protect formatting code inside USE_CLANG_CXX --- src/clang_format.cc | 4 ++++ src/clang_format.h | 6 ++++++ src/clang_utils.cc | 2 ++ src/clang_utils.h | 4 ++++ src/messages/text_document_formatting.cc | 14 +++++++++++--- wscript | 13 +++---------- 6 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/clang_format.cc b/src/clang_format.cc index b460bb2a..659d38f7 100644 --- a/src/clang_format.cc +++ b/src/clang_format.cc @@ -1,3 +1,5 @@ +#if USE_CLANG_CXX + #include "clang_format.h" #include @@ -44,3 +46,5 @@ std::vector ClangFormat::FormatWholeDocument() { 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 index f2a39569..dc7b4565 100644 --- a/src/clang_format.h +++ b/src/clang_format.h @@ -1,3 +1,7 @@ +#if USE_CLANG_CXX + +#pragma once + #include #include @@ -18,3 +22,5 @@ struct ClangFormat { std::vector FormatWholeDocument(); }; + +#endif diff --git a/src/clang_utils.cc b/src/clang_utils.cc index 60ee39da..278e6d22 100644 --- a/src/clang_utils.cc +++ b/src/clang_utils.cc @@ -104,6 +104,7 @@ 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); @@ -125,6 +126,7 @@ std::vector ConvertClangReplacementsIntoTextEdits( } return text_edits_result; } +#endif std::string FileName(CXFile file) { CXString cx_name = clang_getFileName(file); diff --git a/src/clang_utils.h b/src/clang_utils.h index 0922643a..9932d2eb 100644 --- a/src/clang_utils.h +++ b/src/clang_utils.h @@ -3,7 +3,9 @@ #include "language_server_api.h" #include +#if USE_CLANG_CXX #include +#endif #include #include @@ -21,6 +23,8 @@ std::string ToString(CXString cx_string); 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/messages/text_document_formatting.cc b/src/messages/text_document_formatting.cc index e8e0e5c5..8b114cf3 100644 --- a/src/messages/text_document_formatting.cc +++ b/src/messages/text_document_formatting.cc @@ -3,6 +3,8 @@ #include "queue_manager.h" #include "working_files.h" +#include + namespace { struct lsFormattingOptions { // Size of a tab in spaces. @@ -43,6 +45,9 @@ 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)) { @@ -55,9 +60,6 @@ struct TextDocumentFormattingHandler int tab_size = request->params.formattingOptions.tabSize; bool insert_spaces = request->params.formattingOptions.insertSpaces; - Out_TextDocumentFormatting response; - response.id = request->id; - 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())}, @@ -65,6 +67,12 @@ struct TextDocumentFormattingHandler 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); } diff --git a/wscript b/wscript index 44ed66e3..23ff2852 100644 --- a/wscript +++ b/wscript @@ -234,16 +234,6 @@ def build(bld): cc_files += bld.path.ant_glob(['src/clang_cxx/*.cc']) lib = [] - # clang-format support - lib.append('clangBasic') - lib.append('clangFormat') - lib.append('clangLex') - lib.append('clangRewrite') - lib.append('clangToolingCore') - lib.append('LLVMCore') - lib.append('LLVMDemangle') - lib.append('LLVMSupport') - lib.append('ncurses') if sys.platform.startswith('linux'): lib.append('rt') lib.append('pthread') @@ -266,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')