forked from qt-creator/qt-creator
Editor: add function to guess tab settings for a document
It can be manually triggered from the menu of the tab settings tool button. Task-number: QTCREATORBUG-25628 Change-Id: Icde79f3579a035fddef354cfdd3bf9b594f28bef Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
#include "tabsettings.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QRandomGenerator>
|
||||
#include <QTextCursor>
|
||||
#include <QTextDocument>
|
||||
|
||||
@@ -50,6 +51,98 @@ void TabSettings::fromMap(const Store &map)
|
||||
map.value(paddingModeKey, m_continuationAlignBehavior).toInt();
|
||||
}
|
||||
|
||||
TabSettings TabSettings::autoDetect(const QTextDocument *document) const
|
||||
{
|
||||
QTC_ASSERT(document, return *this);
|
||||
|
||||
const int blockCount = document->blockCount();
|
||||
if (blockCount < 10)
|
||||
return *this;
|
||||
|
||||
int totalIndentations = 0;
|
||||
int indentationWithTabs = 0;
|
||||
QMap<int, int> indentCount;
|
||||
|
||||
auto checkText =
|
||||
[this, &totalIndentations, &indentCount, &indentationWithTabs](const QTextBlock &block) {
|
||||
if (block.length() == 0)
|
||||
return;
|
||||
const QTextDocument *doc = block.document();
|
||||
int pos = block.position();
|
||||
bool hasTabs = false;
|
||||
int indentation = 0;
|
||||
// iterate ove the characters in the document is faster since we do not have to allocate
|
||||
// a string for each block text when we are only interested in the first few characters
|
||||
QChar c = doc->characterAt(pos);
|
||||
while (c.isSpace() && c != QChar::ParagraphSeparator) {
|
||||
if (c == QChar::Tabulation) {
|
||||
hasTabs = true;
|
||||
indentation += m_tabSize;
|
||||
} else {
|
||||
++indentation;
|
||||
}
|
||||
c = doc->characterAt(++pos);
|
||||
}
|
||||
// only track indentations that are at least 2 columns wide
|
||||
if (indentation > 1) {
|
||||
if (hasTabs)
|
||||
++indentationWithTabs;
|
||||
++indentCount[indentation];
|
||||
++totalIndentations;
|
||||
}
|
||||
};
|
||||
|
||||
if (blockCount < 200) {
|
||||
// check the indentation of all blocks if the document is shorter than 200 lines
|
||||
for (QTextBlock block = document->firstBlock(); block.isValid(); block = block.next())
|
||||
checkText(block);
|
||||
} else {
|
||||
// scanning the first and last 25 lines specifically since those most probably contain
|
||||
// different indentations
|
||||
const int startEndDelta = 25;
|
||||
for (int delta = 0; delta < startEndDelta; ++delta) {
|
||||
checkText(document->findBlockByNumber(delta));
|
||||
checkText(document->findBlockByNumber(blockCount - 1 - delta));
|
||||
}
|
||||
|
||||
// scan random lines until we have 100 indentations or checked a maximum of 2000 lines
|
||||
// to limit the number of checks for large documents
|
||||
QRandomGenerator gen(QDateTime::currentDateTime().toMSecsSinceEpoch());
|
||||
int checks = 0;
|
||||
while (totalIndentations < 100) {
|
||||
++checks;
|
||||
if (checks > 2000)
|
||||
break;
|
||||
const int blockNummer = gen.bounded(startEndDelta + 1, blockCount - startEndDelta - 2);
|
||||
checkText(document->findBlockByNumber(blockNummer));
|
||||
}
|
||||
}
|
||||
|
||||
// find the most common indent
|
||||
int mostCommonIndent = 0;
|
||||
int mostCommonIndentCount = 0;
|
||||
for (auto it = indentCount.cbegin(); it != indentCount.cend(); ++it) {
|
||||
if (const int count = it.value(); count > mostCommonIndentCount) {
|
||||
mostCommonIndentCount = count;
|
||||
mostCommonIndent = it.key();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = indentCount.cbegin(); it != indentCount.cend(); ++it) {
|
||||
// check whether the smallest indent is a fraction of the most common indent
|
||||
// to filter out some false positives
|
||||
if (mostCommonIndent % it.key() == 0) {
|
||||
TabSettings result = *this;
|
||||
result.m_indentSize = it.key();
|
||||
double relativeTabCount = double(indentationWithTabs) / double(totalIndentations);
|
||||
result.m_tabPolicy = relativeTabCount > 0.5 ? TabSettings::TabsOnlyTabPolicy
|
||||
: TabSettings::SpacesOnlyTabPolicy;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool TabSettings::cursorIsAtBeginningOfLine(const QTextCursor &cursor)
|
||||
{
|
||||
QString text = cursor.block().text();
|
||||
@@ -80,7 +173,7 @@ int TabSettings::firstNonSpace(const QString &text)
|
||||
return i;
|
||||
}
|
||||
|
||||
QString TabSettings::indentationString(const QString &text) const
|
||||
QString TabSettings::indentationString(const QString &text)
|
||||
{
|
||||
return text.left(firstNonSpace(text));
|
||||
}
|
||||
|
@@ -41,6 +41,8 @@ public:
|
||||
Utils::Store toMap() const;
|
||||
void fromMap(const Utils::Store &map);
|
||||
|
||||
TabSettings autoDetect(const QTextDocument *document) const;
|
||||
|
||||
int lineIndentPosition(const QString &text) const;
|
||||
int columnAt(const QString &text, int position) const;
|
||||
int columnAtCursorPosition(const QTextCursor &cursor) const;
|
||||
@@ -48,7 +50,6 @@ public:
|
||||
int columnCountForText(const QString &text, int startColumn = 0) const;
|
||||
int indentedColumn(int column, bool doIndent = true) const;
|
||||
QString indentationString(int startColumn, int targetColumn, int padding, const QTextBlock ¤tBlock = QTextBlock()) const;
|
||||
QString indentationString(const QString &text) const;
|
||||
int indentationColumn(const QString &text) const;
|
||||
static int maximumPadding(const QString &text);
|
||||
|
||||
@@ -62,6 +63,7 @@ public:
|
||||
friend bool operator!=(const TabSettings &t1, const TabSettings &t2) { return !t1.equals(t2); }
|
||||
|
||||
static int firstNonSpace(const QString &text);
|
||||
static QString indentationString(const QString &text);
|
||||
static inline bool onlySpace(const QString &text) { return firstNonSpace(text) == text.length(); }
|
||||
static int spacesLeftFromPosition(const QString &text, int position);
|
||||
static bool cursorIsAtBeginningOfLine(const QTextCursor &cursor);
|
||||
|
@@ -316,7 +316,6 @@ private:
|
||||
menu->addAction(ActionManager::command(Constants::AUTO_INDENT_SELECTION)->action());
|
||||
auto documentSettings = menu->addMenu(Tr::tr("Document Settings"));
|
||||
|
||||
auto tabSettings = documentSettings->addMenu(Tr::tr("Tab Settings"));
|
||||
auto modifyTabSettings = [this](std::function<void(TabSettings &tabSettings)> modifier) {
|
||||
return [this, modifier]() {
|
||||
auto ts = m_doc->tabSettings();
|
||||
@@ -324,6 +323,12 @@ private:
|
||||
m_doc->setTabSettings(ts);
|
||||
};
|
||||
};
|
||||
documentSettings->addAction(
|
||||
Tr::tr("Auto detect"),
|
||||
modifyTabSettings([doc = m_doc->document()](TabSettings &tabSettings) {
|
||||
tabSettings = tabSettings.autoDetect(doc);
|
||||
}));
|
||||
auto tabSettings = documentSettings->addMenu(Tr::tr("Tab Settings"));
|
||||
tabSettings->addAction(Tr::tr("Spaces"), modifyTabSettings([](TabSettings &tabSettings) {
|
||||
tabSettings.m_tabPolicy = TabSettings::SpacesOnlyTabPolicy;
|
||||
}));
|
||||
|
Reference in New Issue
Block a user