ClangFormat: Format multiple text ranges at once

'reformat' function in LibFormat accepts mutilple ranges.
Let's provide the ranges for the same file together when
formatting on save and formatting after fix-its.

Change-Id: I27789da83a1efc27beb57acf238508a191562bb9
Reviewed-by: Marco Bubke <marco.bubke@qt.io>
This commit is contained in:
Ivan Donchevskii
2019-02-13 14:17:21 +01:00
parent 660cd8da19
commit 9bcc871ece
7 changed files with 129 additions and 117 deletions

View File

@@ -39,9 +39,6 @@ namespace ClangFormat {
static void adjustFormatStyleForLineBreak(clang::format::FormatStyle &style,
ReplacementsToKeep replacementsToKeep)
{
if (replacementsToKeep == ReplacementsToKeep::All)
return;
style.MaxEmptyLinesToKeep = 2;
style.SortIncludes = false;
style.SortUsingDeclarations = false;
@@ -341,48 +338,49 @@ void ClangFormatBaseIndenter::reindent(const QTextCursor &cursor,
indent(cursor, QChar::Null, cursorPositionInEditor);
}
TextEditor::Replacements ClangFormatBaseIndenter::format(const QTextCursor &cursor,
int cursorPositionInEditor)
TextEditor::Replacements ClangFormatBaseIndenter::format(
const TextEditor::RangesInLines &rangesInLines)
{
int utf8Offset;
int utf8Length;
if (rangesInLines.empty())
return TextEditor::Replacements();
int utf8Offset = -1;
QTextBlock block;
const QByteArray buffer = m_doc->toPlainText().toUtf8();
if (cursor.hasSelection()) {
block = m_doc->findBlock(cursor.selectionStart());
const QTextBlock end = m_doc->findBlock(cursor.selectionEnd());
utf8Offset = Utils::Text::utf8NthLineOffset(m_doc, buffer, block.blockNumber() + 1);
QTC_ASSERT(utf8Offset >= 0, return TextEditor::Replacements(););
utf8Length = selectedLines(m_doc, block, end).toUtf8().size();
} else {
block = cursor.block();
utf8Offset = Utils::Text::utf8NthLineOffset(m_doc, buffer, block.blockNumber() + 1);
QTC_ASSERT(utf8Offset >= 0, return TextEditor::Replacements(););
std::vector<clang::tooling::Range> ranges;
ranges.reserve(rangesInLines.size());
utf8Length = block.text().toUtf8().size();
for (auto &range : rangesInLines) {
const int utf8StartOffset = Utils::Text::utf8NthLineOffset(m_doc, buffer, range.startLine);
const QTextBlock end = m_doc->findBlockByNumber(range.endLine - 1);
int utf8RangeLength = end.text().toUtf8().size();
if (range.endLine > range.startLine) {
utf8RangeLength += Utils::Text::utf8NthLineOffset(m_doc, buffer, range.endLine)
- utf8StartOffset;
}
ranges.emplace_back(static_cast<unsigned int>(utf8StartOffset),
static_cast<unsigned int>(utf8RangeLength));
if (utf8Offset < 0) {
utf8Offset = utf8StartOffset;
block = m_doc->findBlockByNumber(range.startLine - 1);
}
}
const TextEditor::Replacements toReplace = replacements(buffer,
utf8Offset,
utf8Length,
block,
cursorPositionInEditor,
ReplacementsToKeep::All,
QChar::Null);
clang::format::FormatStyle style = styleForFile();
clang::format::FormattingAttemptStatus status;
const clang::tooling::Replacements clangReplacements
= reformat(style, buffer.data(), ranges, m_fileName.toString().toStdString(), &status);
const TextEditor::Replacements toReplace = utf16Replacements(block,
utf8Offset,
buffer,
clangReplacements);
applyReplacements(block, toReplace);
return toReplace;
}
TextEditor::Replacements ClangFormatBaseIndenter::format(
const QTextCursor &cursor,
const TextEditor::TabSettings & /*tabSettings*/,
int cursorPositionInEditor)
{
return format(cursor, cursorPositionInEditor);
}
void ClangFormatBaseIndenter::indentBeforeCursor(const QTextBlock &block,
const QChar &typedChar,
int cursorPositionInEditor)
@@ -535,10 +533,19 @@ void ClangFormatBaseIndenter::formatOrIndent(const QTextCursor &cursor,
const TextEditor::TabSettings & /*tabSettings*/,
int cursorPositionInEditor)
{
if (formatCodeInsteadOfIndent())
format(cursor, cursorPositionInEditor);
else
if (formatCodeInsteadOfIndent()) {
QTextBlock start;
QTextBlock end;
if (cursor.hasSelection()) {
start = m_doc->findBlock(cursor.selectionStart());
end = m_doc->findBlock(cursor.selectionEnd());
} else {
start = end = cursor.block();
}
format({{start.blockNumber() + 1, end.blockNumber() + 1}});
} else {
indent(cursor, QChar::Null, cursorPositionInEditor);
}
}
clang::format::FormatStyle ClangFormatBaseIndenter::styleForFile() const
@@ -580,6 +587,8 @@ TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer
const QChar &typedChar,
bool secondTry) const
{
QTC_ASSERT(replacementsToKeep != ReplacementsToKeep::All, return TextEditor::Replacements());
clang::format::FormatStyle style = styleForFile();
int originalOffsetUtf8 = utf8Offset;
@@ -593,24 +602,21 @@ TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer
= block.text().left(cursorPositionInEditor - block.position()).toUtf8().size();
}
int extraEmptySpaceOffset = 0;
int rangeStart = 0;
if (replacementsToKeep != ReplacementsToKeep::All) {
if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore)
rangeStart = formattingRangeStart(block, buffer, lastSaveRevision());
if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore)
rangeStart = formattingRangeStart(block, buffer, lastSaveRevision());
extraEmptySpaceOffset = previousEmptyLinesLength(block);
utf8Offset -= extraEmptySpaceOffset;
buffer.remove(utf8Offset, extraEmptySpaceOffset);
int extraEmptySpaceOffset = previousEmptyLinesLength(block);
utf8Offset -= extraEmptySpaceOffset;
buffer.remove(utf8Offset, extraEmptySpaceOffset);
adjustFormatStyleForLineBreak(style, replacementsToKeep);
modifyToIndentEmptyLines(buffer, utf8Offset, utf8Length, block, secondTry);
adjustFormatStyleForLineBreak(style, replacementsToKeep);
modifyToIndentEmptyLines(buffer, utf8Offset, utf8Length, block, secondTry);
if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore) {
buffer.insert(utf8Offset - 1, " //");
extraEmptySpaceOffset -= 3;
utf8Offset += 3;
}
if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore) {
buffer.insert(utf8Offset - 1, " //");
extraEmptySpaceOffset -= 3;
utf8Offset += 3;
}
if (replacementsToKeep != ReplacementsToKeep::IndentAndBefore || utf8Offset < rangeStart)

View File

@@ -53,9 +53,8 @@ public:
void formatOrIndent(const QTextCursor &cursor,
const TextEditor::TabSettings &tabSettings,
int cursorPositionInEditor = -1) override;
TextEditor::Replacements format(const QTextCursor &cursor,
const TextEditor::TabSettings &tabSettings,
int cursorPositionInEditor = -1) override;
TextEditor::Replacements format(
const TextEditor::RangesInLines &rangesInLines = TextEditor::RangesInLines()) override;
void indentBlock(const QTextBlock &block,
const QChar &typedChar,
@@ -75,7 +74,6 @@ protected:
virtual int lastSaveRevision() const { return 0; }
private:
TextEditor::Replacements format(const QTextCursor &cursor, int cursorPositionInEditor);
void indent(const QTextCursor &cursor, const QChar &typedChar, int cursorPositionInEditor);
void indentBlock(const QTextBlock &block, const QChar &typedChar, int cursorPositionInEditor);
int indentFor(const QTextBlock &block, int cursorPositionInEditor);

View File

@@ -89,6 +89,10 @@ bool FixitsRefactoringFile::apply()
= CppTools::CppCodeStyleSettings::currentProjectTabSettings();
// Apply changes
std::unique_ptr<TextEditor::Indenter> indenter;
QString lastFilename;
ReplacementOperations operationsForFile;
for (int i=0; i < m_replacementOperations.size(); ++i) {
ReplacementOperation &op = *m_replacementOperations[i];
if (op.apply) {
@@ -103,15 +107,19 @@ bool FixitsRefactoringFile::apply()
// Apply
QTextDocument *doc = document(op.fileName);
std::unique_ptr<TextEditor::Indenter> indenter(factory->createIndenter(doc));
indenter->setFileName(Utils::FileName::fromString(op.fileName));
if (lastFilename != op.fileName) {
if (indenter)
format(*indenter, doc, operationsForFile, i);
operationsForFile.clear();
indenter = std::unique_ptr<TextEditor::Indenter>(factory->createIndenter(doc));
indenter->setFileName(Utils::FileName::fromString(op.fileName));
}
QTextCursor cursor(doc);
cursor.setPosition(op.pos);
cursor.setPosition(op.pos + op.length, QTextCursor::KeepAnchor);
cursor.insertText(op.text);
tryToFormat(*indenter, tabSettings, doc, op, i);
operationsForFile.push_back(&op);
}
}
@@ -130,28 +138,29 @@ bool FixitsRefactoringFile::apply()
return true;
}
void FixitsRefactoringFile::tryToFormat(TextEditor::Indenter &indenter,
const TextEditor::TabSettings &tabSettings,
QTextDocument *doc,
const ReplacementOperation &op,
int currentIndex)
void FixitsRefactoringFile::format(TextEditor::Indenter &indenter,
QTextDocument *doc,
const ReplacementOperations &operationsForFile,
int firstOperationIndex)
{
QTextCursor cursor(doc);
cursor.beginEditBlock();
cursor.setPosition(op.pos);
cursor.movePosition(QTextCursor::Right,
QTextCursor::KeepAnchor,
op.text.length());
const Replacements replacements = indenter.format(cursor, tabSettings);
cursor.endEditBlock();
if (operationsForFile.isEmpty())
return;
TextEditor::RangesInLines ranges;
for (int i = 0; i < operationsForFile.size(); ++i) {
const ReplacementOperation &op = *operationsForFile.at(i);
const int start = doc->findBlock(op.pos).blockNumber() + 1;
const int end = doc->findBlock(op.pos + op.length).blockNumber() + 1;
ranges.push_back({start, end});
}
const Replacements replacements = indenter.format(ranges);
if (replacements.empty())
return;
if (hasIntersection(op.fileName, replacements, currentIndex + 1))
doc->undo(&cursor);
else
shiftAffectedReplacements(op.fileName, replacements, currentIndex + 1);
shiftAffectedReplacements(operationsForFile.front()->fileName,
replacements,
firstOperationIndex + 1);
}
QTextDocument *FixitsRefactoringFile::document(const QString &filePath) const

View File

@@ -67,11 +67,10 @@ private:
QTextDocument *document(const QString &filePath) const;
void shiftAffectedReplacements(const ReplacementOperation &op, int startIndex);
void tryToFormat(TextEditor::Indenter &indenter,
const TextEditor::TabSettings &tabSettings,
QTextDocument *doc,
const ReplacementOperation &op,
int currentIndex);
void format(TextEditor::Indenter &indenter,
QTextDocument *doc,
const ReplacementOperations &operationsForFile,
int firstOperationIndex);
void shiftAffectedReplacements(const QString &fileName,
const TextEditor::Replacements &replacements,
int startIndex);

View File

@@ -446,46 +446,39 @@ TextEditor::TabSettings CppEditorDocument::tabSettings() const
return indenter()->tabSettings().value_or(TextEditor::TextDocument::tabSettings());
}
static int formatRange(QTextDocument *doc,
TextEditor::Indenter *indenter,
std::pair<int, int> editedRange,
const TextEditor::TabSettings &tabSettings)
{
QTextCursor cursor(doc);
cursor.setPosition(editedRange.first);
cursor.setPosition(editedRange.second, QTextCursor::KeepAnchor);
const int oldBlockCount = doc->blockCount();
indenter->format(cursor, tabSettings);
return doc->blockCount() - oldBlockCount;
}
bool CppEditorDocument::save(QString *errorString, const QString &fileName, bool autoSave)
{
if (indenter()->formatOnSave() && !autoSave) {
QTextCursor cursor(document());
cursor.joinPreviousEditBlock();
auto *layout = qobject_cast<TextEditor::TextDocumentLayout *>(document()->documentLayout());
const int documentRevision = layout->lastSaveRevision;
std::pair<int, int> editedRange;
TextEditor::RangesInLines editedRanges;
TextEditor::RangeInLines lastRange{-1, -1};
for (int i = 0; i < document()->blockCount(); ++i) {
const QTextBlock block = document()->findBlockByNumber(i);
if (block.revision() == documentRevision) {
if (editedRange.first != -1)
i += formatRange(document(), indenter(), editedRange, tabSettings());
if (lastRange.startLine != -1)
editedRanges.push_back(lastRange);
editedRange = std::make_pair(-1, -1);
lastRange.startLine = lastRange.endLine = -1;
continue;
}
// block.revision() != documentRevision
if (editedRange.first == -1)
editedRange.first = block.position();
editedRange.second = block.position() + block.length();
if (lastRange.startLine == -1)
lastRange.startLine = block.blockNumber() + 1;
lastRange.endLine = block.blockNumber() + 1;
}
if (lastRange.startLine != -1)
editedRanges.push_back(lastRange);
if (!editedRanges.empty()) {
QTextCursor cursor(document());
cursor.joinPreviousEditBlock();
indenter()->format(editedRanges);
cursor.endEditBlock();
}
if (editedRange.first != -1)
formatRange(document(), indenter(), editedRange, tabSettings());
cursor.endEditBlock();
}
return TextEditor::TextDocument::save(errorString, fileName, autoSave);

View File

@@ -58,6 +58,15 @@ public:
using Replacements = std::vector<Replacement>;
class RangeInLines
{
public:
int startLine;
int endLine;
};
using RangesInLines = std::vector<RangeInLines>;
class Indenter
{
public:
@@ -91,9 +100,7 @@ public:
}
// By default just calls indent with default settings.
virtual Replacements format(const QTextCursor &/*cursor*/,
const TabSettings &/*tabSettings*/,
int /*cursorPositionInEditor*/ = -1)
virtual Replacements format(const RangesInLines & /*rangesInLines*/ = RangesInLines())
{
return Replacements();
}

View File

@@ -415,7 +415,7 @@ TEST_F(ClangFormat, FormatBasicFile)
"int a;",
"}"});
indenter.format(cursor, TextEditor::TabSettings());
indenter.format(cursor);
ASSERT_THAT(documentLines(), ElementsAre("int main()",
"{",
@@ -430,7 +430,7 @@ TEST_F(ClangFormat, FormatEmptyLine)
"",
"}"});
indenter.format(cursor, TextEditor::TabSettings());
indenter.format(cursor);
ASSERT_THAT(documentLines(), ElementsAre("int main() {}"));
}
@@ -441,7 +441,7 @@ TEST_F(ClangFormat, FormatLambda)
"",
"});"});
indenter.format(cursor, TextEditor::TabSettings());
indenter.format(cursor);
ASSERT_THAT(documentLines(), ElementsAre("int b = foo([]() {",
"",
@@ -454,7 +454,7 @@ TEST_F(ClangFormat, FormatInitializerListInArguments)
"args,",
"{1, 2});"});
indenter.format(cursor, TextEditor::TabSettings());
indenter.format(cursor);
ASSERT_THAT(documentLines(), ElementsAre("foo(arg1, args, {1, 2});"));
}
@@ -466,7 +466,7 @@ TEST_F(ClangFormat, FormatFunctionArgumentLambdaWithScope)
"",
"});"});
indenter.format(cursor, TextEditor::TabSettings());
indenter.format(cursor);
ASSERT_THAT(documentLines(),
ElementsAre("foo([]() {",
@@ -481,7 +481,7 @@ TEST_F(ClangFormat, FormatScopeAsFunctionArgument)
"",
"});"});
indenter.format(cursor, TextEditor::TabSettings());
indenter.format(cursor);
ASSERT_THAT(documentLines(),
ElementsAre("foo({",
@@ -494,7 +494,7 @@ TEST_F(ClangFormat, FormatStructuredBinding)
insertLines({"auto [a,",
"b] = c;"});
indenter.format(cursor, TextEditor::TabSettings());
indenter.format(cursor);
ASSERT_THAT(documentLines(), ElementsAre("auto [a, b] = c;"));
}
@@ -504,7 +504,7 @@ TEST_F(ClangFormat, FormatStringLiteralContinuation)
insertLines({"foo(bar, \"foo\"",
"\"bar\");"});
indenter.format(cursor, TextEditor::TabSettings());
indenter.format(cursor);
ASSERT_THAT(documentLines(), ElementsAre("foo(bar,",
" \"foo\"",
@@ -517,7 +517,7 @@ TEST_F(ClangFormat, FormatTemplateparameters)
"B,",
"C>"});
indenter.format(cursor, TextEditor::TabSettings());
indenter.format(cursor);
ASSERT_THAT(documentLines(), ElementsAre("using Alias = Template<A, B, C>"));
}