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.
This commit is contained in:
Daniel Martín 2017-12-25 18:04:10 +01:00
parent abc2edf05f
commit 19341c18cd
6 changed files with 121 additions and 5 deletions

26
src/clang_format.cc Normal file
View File

@ -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<clang::tooling::Range> 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<Replacement> 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<Replacement>(format_result.begin(), format_result.end());
}

20
src/clang_format.h Normal file
View File

@ -0,0 +1,20 @@
#include <clang/Format/Format.h>
#include <vector>
struct ClangFormat {
llvm::StringRef document_filename_;
llvm::StringRef document_;
llvm::ArrayRef<clang::tooling::Range> ranges_;
int tab_size_;
bool insert_spaces_;
ClangFormat(llvm::StringRef document_filename,
llvm::StringRef document,
llvm::ArrayRef<clang::tooling::Range> ranges,
int tab_size,
bool insert_spaces);
~ClangFormat();
std::vector<clang::tooling::Replacement> FormatWholeDocument();
};

View File

@ -104,6 +104,28 @@ optional<lsDiagnostic> 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<lsTextEdit> ConvertClangReplacementsIntoTextEdits(
llvm::StringRef document,
const std::vector<clang::tooling::Replacement>& clang_replacements) {
std::vector<lsTextEdit> 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);

View File

@ -3,6 +3,7 @@
#include "language_server_api.h"
#include <clang-c/Index.h>
#include <clang/Format/Format.h>
#include <optional.h>
#include <vector>
@ -17,4 +18,9 @@ std::string FileName(CXFile file);
std::string ToString(CXString cx_string);
std::string ToString(CXCursorKind cursor_kind);
std::string ToString(CXCursorKind cursor_kind);
// Converts Clang formatting replacement operations into LSP text edits.
std::vector<lsTextEdit> ConvertClangReplacementsIntoTextEdits(
llvm::StringRef document,
const std::vector<clang::tooling::Replacement>& clang_replacements);

View File

@ -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<Out_TextDocumentFormatting> {
lsRequestId id;
std::vector<lsTextEdit> edits;
std::vector<lsTextEdit> result;
};
MAKE_REFLECT_STRUCT(Out_TextDocumentFormatting, jsonrpc, id, edits);
MAKE_REFLECT_STRUCT(Out_TextDocumentFormatting, jsonrpc, id, result);
struct TextDocumentFormattingHandler
: BaseMessageHandler<Ipc_TextDocumentFormatting> {
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<ClangFormat>(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

14
wscript
View File

@ -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')