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 "tabsettings.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QRandomGenerator>
|
||||||
#include <QTextCursor>
|
#include <QTextCursor>
|
||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
|
|
||||||
@@ -50,6 +51,98 @@ void TabSettings::fromMap(const Store &map)
|
|||||||
map.value(paddingModeKey, m_continuationAlignBehavior).toInt();
|
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)
|
bool TabSettings::cursorIsAtBeginningOfLine(const QTextCursor &cursor)
|
||||||
{
|
{
|
||||||
QString text = cursor.block().text();
|
QString text = cursor.block().text();
|
||||||
@@ -80,7 +173,7 @@ int TabSettings::firstNonSpace(const QString &text)
|
|||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TabSettings::indentationString(const QString &text) const
|
QString TabSettings::indentationString(const QString &text)
|
||||||
{
|
{
|
||||||
return text.left(firstNonSpace(text));
|
return text.left(firstNonSpace(text));
|
||||||
}
|
}
|
||||||
|
@@ -41,6 +41,8 @@ public:
|
|||||||
Utils::Store toMap() const;
|
Utils::Store toMap() const;
|
||||||
void fromMap(const Utils::Store &map);
|
void fromMap(const Utils::Store &map);
|
||||||
|
|
||||||
|
TabSettings autoDetect(const QTextDocument *document) const;
|
||||||
|
|
||||||
int lineIndentPosition(const QString &text) const;
|
int lineIndentPosition(const QString &text) const;
|
||||||
int columnAt(const QString &text, int position) const;
|
int columnAt(const QString &text, int position) const;
|
||||||
int columnAtCursorPosition(const QTextCursor &cursor) const;
|
int columnAtCursorPosition(const QTextCursor &cursor) const;
|
||||||
@@ -48,7 +50,6 @@ public:
|
|||||||
int columnCountForText(const QString &text, int startColumn = 0) const;
|
int columnCountForText(const QString &text, int startColumn = 0) const;
|
||||||
int indentedColumn(int column, bool doIndent = true) const;
|
int indentedColumn(int column, bool doIndent = true) const;
|
||||||
QString indentationString(int startColumn, int targetColumn, int padding, const QTextBlock ¤tBlock = QTextBlock()) 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;
|
int indentationColumn(const QString &text) const;
|
||||||
static int maximumPadding(const QString &text);
|
static int maximumPadding(const QString &text);
|
||||||
|
|
||||||
@@ -62,6 +63,7 @@ public:
|
|||||||
friend bool operator!=(const TabSettings &t1, const TabSettings &t2) { return !t1.equals(t2); }
|
friend bool operator!=(const TabSettings &t1, const TabSettings &t2) { return !t1.equals(t2); }
|
||||||
|
|
||||||
static int firstNonSpace(const QString &text);
|
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 inline bool onlySpace(const QString &text) { return firstNonSpace(text) == text.length(); }
|
||||||
static int spacesLeftFromPosition(const QString &text, int position);
|
static int spacesLeftFromPosition(const QString &text, int position);
|
||||||
static bool cursorIsAtBeginningOfLine(const QTextCursor &cursor);
|
static bool cursorIsAtBeginningOfLine(const QTextCursor &cursor);
|
||||||
|
@@ -316,7 +316,6 @@ private:
|
|||||||
menu->addAction(ActionManager::command(Constants::AUTO_INDENT_SELECTION)->action());
|
menu->addAction(ActionManager::command(Constants::AUTO_INDENT_SELECTION)->action());
|
||||||
auto documentSettings = menu->addMenu(Tr::tr("Document Settings"));
|
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) {
|
auto modifyTabSettings = [this](std::function<void(TabSettings &tabSettings)> modifier) {
|
||||||
return [this, modifier]() {
|
return [this, modifier]() {
|
||||||
auto ts = m_doc->tabSettings();
|
auto ts = m_doc->tabSettings();
|
||||||
@@ -324,6 +323,12 @@ private:
|
|||||||
m_doc->setTabSettings(ts);
|
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->addAction(Tr::tr("Spaces"), modifyTabSettings([](TabSettings &tabSettings) {
|
||||||
tabSettings.m_tabPolicy = TabSettings::SpacesOnlyTabPolicy;
|
tabSettings.m_tabPolicy = TabSettings::SpacesOnlyTabPolicy;
|
||||||
}));
|
}));
|
||||||
|
Reference in New Issue
Block a user