forked from qt-creator/qt-creator
Utils: Add formatting information to ChangeSet::EditOp
... and make use of that in TextEditor::RefactoringFile. This allows calling code to have fine-grained control over which parts of a refactoring should get re-formatted, while also providing sensible default values that are "almost always" right, so things typically work as expected out of the box. Change-Id: I9200c2135b7477c33bc5a61c5d410b34853e4b61 Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
@@ -35,7 +35,7 @@ bool ChangeSet::hasOverlap(int pos, int length) const
|
||||
{
|
||||
for (const EditOp &cmd : m_operationList) {
|
||||
|
||||
switch (cmd.type) {
|
||||
switch (cmd.type()) {
|
||||
case EditOp::Replace:
|
||||
if (overlaps(pos, length, cmd.pos1, cmd.length1))
|
||||
return true;
|
||||
@@ -71,9 +71,6 @@ bool ChangeSet::hasOverlap(int pos, int length) const
|
||||
if (cmd.pos2 > pos && cmd.pos2 < pos + length)
|
||||
return true;
|
||||
break;
|
||||
|
||||
case EditOp::Unset:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +83,11 @@ bool ChangeSet::isEmpty() const
|
||||
}
|
||||
|
||||
QList<ChangeSet::EditOp> ChangeSet::operationList() const
|
||||
{
|
||||
return const_cast<ChangeSet *>(this)->operationList();
|
||||
}
|
||||
|
||||
QList<ChangeSet::EditOp> &ChangeSet::operationList()
|
||||
{
|
||||
return m_operationList;
|
||||
}
|
||||
@@ -103,10 +105,9 @@ bool ChangeSet::replace_helper(int pos, int length, const QString &replacement)
|
||||
if (hasOverlap(pos, length))
|
||||
m_error = true;
|
||||
|
||||
EditOp cmd(EditOp::Replace);
|
||||
EditOp cmd(EditOp::Replace, replacement);
|
||||
cmd.pos1 = pos;
|
||||
cmd.length1 = length;
|
||||
cmd.text = replacement;
|
||||
m_operationList += cmd;
|
||||
|
||||
return !m_error;
|
||||
@@ -136,9 +137,8 @@ bool ChangeSet::insert(int pos, const QString &text)
|
||||
if (hasOverlap(pos, 0))
|
||||
m_error = true;
|
||||
|
||||
EditOp cmd(EditOp::Insert);
|
||||
EditOp cmd(EditOp::Insert, text);
|
||||
cmd.pos1 = pos;
|
||||
cmd.text = text;
|
||||
m_operationList += cmd;
|
||||
|
||||
return !m_error;
|
||||
@@ -224,79 +224,90 @@ bool ChangeSet::copy_helper(int pos, int length, int to)
|
||||
|
||||
void ChangeSet::doReplace(const EditOp &op, QList<EditOp> *replaceList)
|
||||
{
|
||||
Q_ASSERT(op.type == EditOp::Replace);
|
||||
Q_ASSERT(op.type() == EditOp::Replace);
|
||||
|
||||
{
|
||||
for (EditOp &c : *replaceList) {
|
||||
if (op.pos1 <= c.pos1)
|
||||
c.pos1 += op.text.size();
|
||||
c.pos1 += op.text().size();
|
||||
if (op.pos1 < c.pos1)
|
||||
c.pos1 -= op.length1;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_string) {
|
||||
m_string->replace(op.pos1, op.length1, op.text);
|
||||
m_string->replace(op.pos1, op.length1, op.text());
|
||||
} else if (m_cursor) {
|
||||
m_cursor->setPosition(op.pos1);
|
||||
m_cursor->setPosition(op.pos1 + op.length1, QTextCursor::KeepAnchor);
|
||||
m_cursor->insertText(op.text);
|
||||
m_cursor->insertText(op.text());
|
||||
}
|
||||
}
|
||||
|
||||
void ChangeSet::convertToReplace(const EditOp &op, QList<EditOp> *replaceList)
|
||||
{
|
||||
EditOp replace1(EditOp::Replace);
|
||||
EditOp replace2(EditOp::Replace);
|
||||
|
||||
switch (op.type) {
|
||||
switch (op.type()) {
|
||||
case EditOp::Replace:
|
||||
replaceList->append(op);
|
||||
break;
|
||||
|
||||
case EditOp::Move:
|
||||
case EditOp::Move: {
|
||||
EditOp replace1(EditOp::Replace);
|
||||
replace1.pos1 = op.pos1;
|
||||
replace1.length1 = op.length1;
|
||||
if (op.hasFormat1())
|
||||
replace1.setFormat1(op.format1());
|
||||
replaceList->append(replace1);
|
||||
|
||||
EditOp replace2(EditOp::Replace, textAt(op.pos1, op.length1));
|
||||
replace2.pos1 = op.pos2;
|
||||
replace2.text = textAt(op.pos1, op.length1);
|
||||
if (op.hasFormat2())
|
||||
replace2.setFormat1(op.format2());
|
||||
replaceList->append(replace2);
|
||||
break;
|
||||
|
||||
case EditOp::Insert:
|
||||
replace1.pos1 = op.pos1;
|
||||
replace1.text = op.text;
|
||||
replaceList->append(replace1);
|
||||
}
|
||||
case EditOp::Insert: {
|
||||
EditOp replace(EditOp::Replace, op.text());
|
||||
replace.pos1 = op.pos1;
|
||||
if (op.hasFormat1())
|
||||
replace.setFormat1(op.format1());
|
||||
replaceList->append(replace);
|
||||
break;
|
||||
|
||||
case EditOp::Remove:
|
||||
}
|
||||
case EditOp::Remove: {
|
||||
EditOp replace(EditOp::Replace);
|
||||
replace.pos1 = op.pos1;
|
||||
replace.length1 = op.length1;
|
||||
if (op.hasFormat1())
|
||||
replace.setFormat1(op.format1());
|
||||
replaceList->append(replace);
|
||||
break;
|
||||
}
|
||||
case EditOp::Flip: {
|
||||
EditOp replace1(EditOp::Replace, textAt(op.pos2, op.length2));
|
||||
replace1.pos1 = op.pos1;
|
||||
replace1.length1 = op.length1;
|
||||
replaceList->append(replace1);
|
||||
break;
|
||||
|
||||
case EditOp::Flip:
|
||||
replace1.pos1 = op.pos1;
|
||||
replace1.length1 = op.length1;
|
||||
replace1.text = textAt(op.pos2, op.length2);
|
||||
if (op.hasFormat1())
|
||||
replace1.setFormat1(op.format1());
|
||||
replaceList->append(replace1);
|
||||
|
||||
EditOp replace2(EditOp::Replace, textAt(op.pos1, op.length1));
|
||||
replace2.pos1 = op.pos2;
|
||||
replace2.length1 = op.length2;
|
||||
replace2.text = textAt(op.pos1, op.length1);
|
||||
if (op.hasFormat2())
|
||||
replace2.setFormat1(op.format2());
|
||||
replaceList->append(replace2);
|
||||
break;
|
||||
|
||||
case EditOp::Copy:
|
||||
replace1.pos1 = op.pos2;
|
||||
replace1.text = textAt(op.pos1, op.length1);
|
||||
replaceList->append(replace1);
|
||||
break;
|
||||
|
||||
case EditOp::Unset:
|
||||
}
|
||||
case EditOp::Copy: {
|
||||
EditOp replace(EditOp::Replace, textAt(op.pos1, op.length1));
|
||||
replace.pos1 = op.pos2;
|
||||
if (op.hasFormat2())
|
||||
replace.setFormat1(op.format2());
|
||||
replaceList->append(replace);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ChangeSet::hadErrors() const
|
||||
@@ -354,4 +365,32 @@ void ChangeSet::apply_helper()
|
||||
m_cursor->endEditBlock();
|
||||
}
|
||||
|
||||
ChangeSet::EditOp::EditOp(Type t, const QString &text) : m_type(t), m_text(text)
|
||||
{
|
||||
// The formatting default values are derived as follows:
|
||||
// 1) Inserted code generally needs to be formatted, because indentation etc is
|
||||
// context-dependent.
|
||||
// 2) Removed code should not be formatted, as it is gone.
|
||||
// 3) Copied code needs to be formatted at the insertion site as per 1).
|
||||
// 4) Moved code needs to be formatted at the insertion site as per 1) and 2).
|
||||
// 5) Replaced code generally does not need to be formatted, as it's typically just a name
|
||||
// change. Exception: The new text contains newlines, in which case we do want to format.
|
||||
// 6) Flipped may or may be formatted at either side, as per 5).
|
||||
switch (t) {
|
||||
case Insert:
|
||||
m_format1 = true;
|
||||
break;
|
||||
case Remove:
|
||||
break;
|
||||
case Copy:
|
||||
case Move:
|
||||
m_format2 = true;
|
||||
break;
|
||||
case Replace:
|
||||
m_format1 = text.contains('\n') || text.contains(QChar::ParagraphSeparator);
|
||||
break;
|
||||
case Flip:
|
||||
break; // Default will be set at conversion to Replace.
|
||||
}
|
||||
}
|
||||
} // end namespace Utils
|
||||
|
@@ -8,6 +8,8 @@
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
|
||||
#include <optional>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QTextCursor;
|
||||
class QTextDocument;
|
||||
@@ -21,7 +23,6 @@ public:
|
||||
struct EditOp {
|
||||
enum Type
|
||||
{
|
||||
Unset,
|
||||
Replace,
|
||||
Move,
|
||||
Insert,
|
||||
@@ -30,15 +31,28 @@ public:
|
||||
Copy
|
||||
};
|
||||
|
||||
EditOp() = default;
|
||||
EditOp(Type t): type(t) {}
|
||||
EditOp(Type t, const QString &text = {});
|
||||
|
||||
Type type() const { return m_type; }
|
||||
QString text() const { return m_text; }
|
||||
|
||||
bool hasFormat1() const { return m_format1.has_value(); }
|
||||
bool format1() const { return hasFormat1() && *m_format1; }
|
||||
void setFormat1(bool f) { m_format1 = f; }
|
||||
bool hasFormat2() const { return m_format2.has_value(); }
|
||||
bool format2() const { return hasFormat2() && *m_format2; }
|
||||
void setFormat2(bool f) { m_format2 = f; }
|
||||
|
||||
Type type = Unset;
|
||||
int pos1 = 0;
|
||||
int pos2 = 0;
|
||||
int length1 = 0;
|
||||
int length2 = 0;
|
||||
QString text;
|
||||
|
||||
private:
|
||||
Type m_type;
|
||||
QString m_text;
|
||||
std::optional<bool> m_format1;
|
||||
std::optional<bool> m_format2;
|
||||
};
|
||||
|
||||
struct Range {
|
||||
@@ -58,6 +72,7 @@ public:
|
||||
bool isEmpty() const;
|
||||
|
||||
QList<EditOp> operationList() const;
|
||||
QList<EditOp> &operationList();
|
||||
|
||||
void clear();
|
||||
|
||||
|
@@ -436,15 +436,15 @@ int indentationForBlock(const Utils::ChangeSet &toReplace,
|
||||
|
||||
auto replacementIt
|
||||
= std::find_if(ops.begin(), ops.end(), [utf8Offset](const Utils::ChangeSet::EditOp &op) {
|
||||
QTC_ASSERT(op.type == Utils::ChangeSet::EditOp::Replace, return false);
|
||||
QTC_ASSERT(op.type() == Utils::ChangeSet::EditOp::Replace, return false);
|
||||
return op.pos1 == utf8Offset - 1;
|
||||
});
|
||||
if (replacementIt == ops.end())
|
||||
return -1;
|
||||
|
||||
int afterLineBreak = replacementIt->text.lastIndexOf('\n');
|
||||
int afterLineBreak = replacementIt->text().lastIndexOf('\n');
|
||||
afterLineBreak = (afterLineBreak < 0) ? 0 : afterLineBreak + 1;
|
||||
return static_cast<int>(replacementIt->text.size() - afterLineBreak);
|
||||
return static_cast<int>(replacementIt->text().size() - afterLineBreak);
|
||||
}
|
||||
|
||||
bool doNotIndentInContext(QTextDocument *doc, int pos)
|
||||
|
@@ -188,10 +188,10 @@ void FixitsRefactoringFile::shiftAffectedReplacements(const FilePath &filePath,
|
||||
continue;
|
||||
|
||||
for (const auto &op : replacements) {
|
||||
QTC_ASSERT(op.type == ChangeSet::EditOp::Replace, continue);
|
||||
QTC_ASSERT(op.type() == ChangeSet::EditOp::Replace, continue);
|
||||
if (op.pos1 > current.pos)
|
||||
break;
|
||||
current.pos += op.text.size() - op.length1;
|
||||
current.pos += op.text().size() - op.length1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2230,7 +2230,7 @@ public:
|
||||
QString description;
|
||||
if (m_change.operationList().size() == 1) {
|
||||
description = Tr::tr(
|
||||
"Reformat to \"%1\"").arg(m_change.operationList().constFirst().text);
|
||||
"Reformat to \"%1\"").arg(m_change.operationList().constFirst().text());
|
||||
} else { // > 1
|
||||
description = Tr::tr("Reformat Pointers or References");
|
||||
}
|
||||
@@ -2732,7 +2732,6 @@ public:
|
||||
ChangeSet localChangeSet;
|
||||
ChangeSet * const target = changeSet ? changeSet : &localChangeSet;
|
||||
target->replace(targetPos - 1, targetPos, QLatin1String("\n {\n\n}")); // replace ';'
|
||||
const ChangeSet::Range indentRange(targetPos, targetPos + 4);
|
||||
|
||||
if (!changeSet) {
|
||||
targetFile->setChangeSet(*target);
|
||||
@@ -2805,13 +2804,10 @@ public:
|
||||
defText.insert(index, inlinePref);
|
||||
defText += QLatin1String("\n{\n\n}");
|
||||
|
||||
const int targetPos = targetFile->position(loc.line(), loc.column());
|
||||
const int targetPos2 = qMax(0, targetFile->position(loc.line(), 1) - 1);
|
||||
|
||||
ChangeSet localChangeSet;
|
||||
ChangeSet * const target = changeSet ? changeSet : &localChangeSet;
|
||||
const int targetPos = targetFile->position(loc.line(), loc.column());
|
||||
target->insert(targetPos, loc.prefix() + defText + loc.suffix());
|
||||
const ChangeSet::Range indentRange(targetPos2, targetPos);
|
||||
|
||||
if (!changeSet) {
|
||||
targetFile->setChangeSet(*target);
|
||||
@@ -6583,7 +6579,6 @@ public:
|
||||
}
|
||||
if (!m_fromFileChangeSet.isEmpty()) {
|
||||
m_fromFile->setChangeSet(m_fromFileChangeSet);
|
||||
m_fromFile->skipFormatting();
|
||||
m_fromFile->apply();
|
||||
}
|
||||
}
|
||||
@@ -7627,13 +7622,6 @@ private:
|
||||
CppRefactoringChanges refactoring(snapshot());
|
||||
CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
|
||||
currentFile->setChangeSet(m_changes);
|
||||
|
||||
// It would probably be unexpected to users if we were to re-format here, though
|
||||
// an argument could also be made for doing it, especially if "format instead of indent"
|
||||
// is enabled, as a difference in line length might trigger a different ClangFormat
|
||||
// re-flowing.
|
||||
currentFile->skipFormatting();
|
||||
|
||||
currentFile->apply();
|
||||
}
|
||||
|
||||
@@ -8242,6 +8230,7 @@ private:
|
||||
m_changeSet.insert(m_file->startOf(destructorName->unqualified_name), m_missingNamespace);
|
||||
else
|
||||
m_changeSet.insert(m_file->startOf(ast->name), m_missingNamespace);
|
||||
m_changeSet.operationList().last().setFormat1(false);
|
||||
}
|
||||
|
||||
bool needMissingNamespaces(QList<const Name *> &&fullName, int currentNameCount)
|
||||
@@ -8411,10 +8400,8 @@ private:
|
||||
processIncludes(refactoring, filePath());
|
||||
}
|
||||
|
||||
for (auto &file : std::as_const(m_changes)) {
|
||||
file->skipFormatting();
|
||||
for (auto &file : std::as_const(m_changes))
|
||||
file->apply();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -294,7 +294,7 @@ void QuickToolBar::setProperty(const QString &propertyName, const QVariant &valu
|
||||
int column;
|
||||
|
||||
int changeSetPos = changeSet.operationList().constLast().pos1;
|
||||
int changeSetLength = changeSet.operationList().constLast().text.length();
|
||||
int changeSetLength = changeSet.operationList().constLast().text().length();
|
||||
QTextCursor tc = m_editorWidget->textCursor();
|
||||
tc.beginEditBlock();
|
||||
changeSet.apply(&tc);
|
||||
|
@@ -595,7 +595,6 @@ FilePaths BaseFileFind::replaceAll(const QString &text, const SearchResultItems
|
||||
changeSet.replace(start, end, replacement);
|
||||
}
|
||||
file->setChangeSet(changeSet);
|
||||
file->skipFormatting();
|
||||
file->apply();
|
||||
}
|
||||
|
||||
|
@@ -288,17 +288,14 @@ bool RefactoringFile::apply()
|
||||
|
||||
void RefactoringFile::setupFormattingRanges(const QList<ChangeSet::EditOp> &replaceList)
|
||||
{
|
||||
if (!m_formattingEnabled)
|
||||
return;
|
||||
|
||||
QTextDocument * const doc = m_editor ? m_editor->document() : m_document;
|
||||
QTC_ASSERT(doc, return);
|
||||
|
||||
for (const ChangeSet::EditOp &op : replaceList) {
|
||||
if (!op.format1())
|
||||
continue;
|
||||
QTextCursor cursor(doc);
|
||||
switch (op.type) {
|
||||
case ChangeSet::EditOp::Unset:
|
||||
break;
|
||||
switch (op.type()) {
|
||||
case ChangeSet::EditOp::Replace:
|
||||
case ChangeSet::EditOp::Insert:
|
||||
case ChangeSet::EditOp::Remove:
|
||||
|
@@ -60,9 +60,6 @@ public:
|
||||
bool apply();
|
||||
bool create(const QString &contents, bool reindent, bool openInEditor);
|
||||
|
||||
// TODO: Per EditOp?
|
||||
void skipFormatting() { m_formattingEnabled = false; }
|
||||
|
||||
protected:
|
||||
// users may only get const access to RefactoringFiles created through
|
||||
// this constructor, because it can't be used to apply changes
|
||||
@@ -93,7 +90,6 @@ private:
|
||||
bool m_activateEditor = false;
|
||||
int m_editorCursorPosition = -1;
|
||||
bool m_appliedOnce = false;
|
||||
bool m_formattingEnabled = true;
|
||||
};
|
||||
|
||||
class TEXTEDITOR_EXPORT RefactoringFileFactory
|
||||
|
Reference in New Issue
Block a user