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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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