forked from qt-creator/qt-creator
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:
@@ -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,
|
||||
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,
|
||||
utf8Length,
|
||||
block,
|
||||
cursorPositionInEditor,
|
||||
ReplacementsToKeep::All,
|
||||
QChar::Null);
|
||||
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,11 +533,20 @@ 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,13 +602,11 @@ 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());
|
||||
|
||||
extraEmptySpaceOffset = previousEmptyLinesLength(block);
|
||||
int extraEmptySpaceOffset = previousEmptyLinesLength(block);
|
||||
utf8Offset -= extraEmptySpaceOffset;
|
||||
buffer.remove(utf8Offset, extraEmptySpaceOffset);
|
||||
|
||||
@@ -611,7 +618,6 @@ TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer
|
||||
extraEmptySpaceOffset -= 3;
|
||||
utf8Offset += 3;
|
||||
}
|
||||
}
|
||||
|
||||
if (replacementsToKeep != ReplacementsToKeep::IndentAndBefore || utf8Offset < rangeStart)
|
||||
rangeStart = utf8Offset;
|
||||
|
@@ -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);
|
||||
|
@@ -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));
|
||||
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,
|
||||
void FixitsRefactoringFile::format(TextEditor::Indenter &indenter,
|
||||
QTextDocument *doc,
|
||||
const ReplacementOperation &op,
|
||||
int currentIndex)
|
||||
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
|
||||
|
@@ -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,
|
||||
void format(TextEditor::Indenter &indenter,
|
||||
QTextDocument *doc,
|
||||
const ReplacementOperation &op,
|
||||
int currentIndex);
|
||||
const ReplacementOperations &operationsForFile,
|
||||
int firstOperationIndex);
|
||||
void shiftAffectedReplacements(const QString &fileName,
|
||||
const TextEditor::Replacements &replacements,
|
||||
int startIndex);
|
||||
|
@@ -446,47 +446,40 @@ 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 (editedRange.first != -1)
|
||||
formatRange(document(), indenter(), editedRange, tabSettings());
|
||||
|
||||
if (lastRange.startLine != -1)
|
||||
editedRanges.push_back(lastRange);
|
||||
|
||||
if (!editedRanges.empty()) {
|
||||
QTextCursor cursor(document());
|
||||
cursor.joinPreviousEditBlock();
|
||||
indenter()->format(editedRanges);
|
||||
cursor.endEditBlock();
|
||||
}
|
||||
}
|
||||
|
||||
return TextEditor::TextDocument::save(errorString, fileName, autoSave);
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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>"));
|
||||
}
|
||||
|
Reference in New Issue
Block a user