forked from qt-creator/qt-creator
TextEditor: Snippet ranges refactoring
Parsed snippets are now reported in chunks of texts and variables. A variable has a index that can be used to identify matching variables and maybe a mangler that can be used to modify the variable when applying the snippet. This effictively moves the variable matching logic from the overlay to the parser of the snippet, which is needed to implement the LSP snippet parser. Task-number: QTCREATORBUG-22406 Change-Id: I6999554c6c6d0f1887c98bf732473f01aa1f230c Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
@@ -170,34 +170,10 @@ bool Snippet::isModified() const
|
|||||||
return m_isModified;
|
return m_isModified;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SnippetReplacement
|
static QString tipPart(const ParsedSnippet::Part &part)
|
||||||
{
|
|
||||||
QString text;
|
|
||||||
int posDelta = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
static SnippetReplacement replacementAt(int pos, ParsedSnippet &parsedSnippet)
|
|
||||||
{
|
{
|
||||||
static const char kOpenBold[] = "<b>";
|
static const char kOpenBold[] = "<b>";
|
||||||
static const char kCloseBold[] = "</b>";
|
static const char kCloseBold[] = "</b>";
|
||||||
|
|
||||||
auto mangledText = [](const QString &text, const ParsedSnippet::Range &range) {
|
|
||||||
if (range.length == 0)
|
|
||||||
return QString("...");
|
|
||||||
if (NameMangler *mangler = range.mangler)
|
|
||||||
return mangler->mangle(text.mid(range.start, range.length));
|
|
||||||
return text.mid(range.start, range.length);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!parsedSnippet.ranges.isEmpty() && parsedSnippet.ranges.first().start == pos) {
|
|
||||||
ParsedSnippet::Range range = parsedSnippet.ranges.takeFirst();
|
|
||||||
return {kOpenBold + mangledText(parsedSnippet.text, range) + kCloseBold, range.length};
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
QString Snippet::generateTip() const
|
|
||||||
{
|
|
||||||
static const QHash<QChar, QString> replacements = {{'\n', "<br>"},
|
static const QHash<QChar, QString> replacements = {{'\n', "<br>"},
|
||||||
{' ', " "},
|
{' ', " "},
|
||||||
{'"', """},
|
{'"', """},
|
||||||
@@ -205,34 +181,30 @@ QString Snippet::generateTip() const
|
|||||||
{'<', "<"},
|
{'<', "<"},
|
||||||
{'>', ">"}};
|
{'>', ">"}};
|
||||||
|
|
||||||
|
QString text;
|
||||||
|
text.reserve(part.text.size());
|
||||||
|
|
||||||
|
for (const QChar &c : part.text)
|
||||||
|
text.append(replacements.value(c, c));
|
||||||
|
|
||||||
|
if (part.variableIndex >= 0)
|
||||||
|
text = kOpenBold + (text.isEmpty() ? QString("...") : part.text) + kCloseBold;
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Snippet::generateTip() const
|
||||||
|
{
|
||||||
SnippetParseResult result = Snippet::parse(m_content);
|
SnippetParseResult result = Snippet::parse(m_content);
|
||||||
|
|
||||||
if (Utils::holds_alternative<SnippetParseError>(result))
|
if (Utils::holds_alternative<SnippetParseError>(result))
|
||||||
return Utils::get<SnippetParseError>(result).htmlMessage();
|
return Utils::get<SnippetParseError>(result).htmlMessage();
|
||||||
QTC_ASSERT(Utils::holds_alternative<ParsedSnippet>(result), return {});
|
QTC_ASSERT(Utils::holds_alternative<ParsedSnippet>(result), return {});
|
||||||
auto parsedSnippet = Utils::get<ParsedSnippet>(result);
|
const ParsedSnippet parsedSnippet = Utils::get<ParsedSnippet>(result);
|
||||||
|
|
||||||
QString tip("<nobr>");
|
QString tip("<nobr>");
|
||||||
int pos = 0;
|
for (const ParsedSnippet::Part &part : parsedSnippet.parts)
|
||||||
for (int end = parsedSnippet.text.count(); pos < end;) {
|
tip.append(tipPart(part));
|
||||||
const SnippetReplacement &replacement = replacementAt(pos, parsedSnippet);
|
|
||||||
if (!replacement.text.isEmpty()) {
|
|
||||||
tip += replacement.text;
|
|
||||||
pos += replacement.posDelta;
|
|
||||||
} else {
|
|
||||||
const QChar ¤tChar = parsedSnippet.text.at(pos);
|
|
||||||
tip += replacements.value(currentChar, currentChar);
|
|
||||||
++pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SnippetReplacement replacement = replacementAt(pos, parsedSnippet);
|
|
||||||
while (!replacement.text.isEmpty()) {
|
|
||||||
tip += replacement.text;
|
|
||||||
pos += replacement.posDelta;
|
|
||||||
replacement = replacementAt(pos, parsedSnippet);
|
|
||||||
}
|
|
||||||
|
|
||||||
QTC_CHECK(parsedSnippet.ranges.isEmpty());
|
|
||||||
return tip;
|
return tip;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,25 +225,35 @@ SnippetParseResult Snippet::parse(const QString &snippet)
|
|||||||
return {SnippetParseError{errorMessage, {}, -1}};
|
return {SnippetParseError{errorMessage, {}, -1}};
|
||||||
|
|
||||||
const int count = preprocessedSnippet.count();
|
const int count = preprocessedSnippet.count();
|
||||||
int start = -1;
|
|
||||||
NameMangler *mangler = nullptr;
|
NameMangler *mangler = nullptr;
|
||||||
|
|
||||||
result.text.reserve(count);
|
QMap<QString, int> variableIndexes;
|
||||||
|
bool inVar = false;
|
||||||
|
|
||||||
|
ParsedSnippet::Part currentPart;
|
||||||
|
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
QChar current = preprocessedSnippet.at(i);
|
QChar current = preprocessedSnippet.at(i);
|
||||||
QChar next = (i + 1) < count ? preprocessedSnippet.at(i + 1) : QChar();
|
|
||||||
|
|
||||||
if (current == Snippet::kVariableDelimiter) {
|
if (current == Snippet::kVariableDelimiter) {
|
||||||
if (start < 0) {
|
if (inVar) {
|
||||||
// start delimiter:
|
const QString variable = currentPart.text;
|
||||||
start = result.text.count();
|
const int index = variableIndexes.value(currentPart.text, result.variables.size());
|
||||||
} else {
|
if (index == result.variables.size()) {
|
||||||
int length = result.text.count() - start;
|
variableIndexes[variable] = index;
|
||||||
result.ranges << ParsedSnippet::Range(start, length, mangler);
|
result.variables.append(QList<int>());
|
||||||
mangler = nullptr;
|
|
||||||
start = -1;
|
|
||||||
}
|
}
|
||||||
|
currentPart.variableIndex = index;
|
||||||
|
currentPart.mangler = mangler;
|
||||||
|
mangler = nullptr;
|
||||||
|
result.variables[index] << result.parts.size() - 1;
|
||||||
|
} else if (currentPart.text.isEmpty()) {
|
||||||
|
inVar = !inVar;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result.parts << currentPart;
|
||||||
|
currentPart = ParsedSnippet::Part();
|
||||||
|
inVar = !inVar;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,12 +263,13 @@ SnippetParseResult Snippet::parse(const QString &snippet)
|
|||||||
i}};
|
i}};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current == QLatin1Char(':') && start >= 0) {
|
if (current == ':' && inVar) {
|
||||||
if (next == QLatin1Char('l')) {
|
QChar next = (i + 1) < count ? preprocessedSnippet.at(i + 1) : QChar();
|
||||||
|
if (next == 'l') {
|
||||||
mangler = &lcMangler;
|
mangler = &lcMangler;
|
||||||
} else if (next == QLatin1Char('u')) {
|
} else if (next == 'u') {
|
||||||
mangler = &ucMangler;
|
mangler = &ucMangler;
|
||||||
} else if (next == QLatin1Char('c')) {
|
} else if (next == 'c') {
|
||||||
mangler = &tcMangler;
|
mangler = &tcMangler;
|
||||||
} else {
|
} else {
|
||||||
return SnippetParseResult{
|
return SnippetParseResult{
|
||||||
@@ -299,20 +282,26 @@ SnippetParseResult Snippet::parse(const QString &snippet)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current == kEscapeChar && (next == kEscapeChar || next == kVariableDelimiter)) {
|
if (current == kEscapeChar){
|
||||||
result.text.append(next);
|
QChar next = (i + 1) < count ? preprocessedSnippet.at(i + 1) : QChar();
|
||||||
|
if (next == kEscapeChar || next == kVariableDelimiter) {
|
||||||
|
currentPart.text.append(next);
|
||||||
++i;
|
++i;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
result.text.append(current);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start >= 0) {
|
currentPart.text.append(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inVar) {
|
||||||
return SnippetParseResult{
|
return SnippetParseResult{
|
||||||
SnippetParseError{tr("Missing closing variable delimiter"), result.text, start}};
|
SnippetParseError{tr("Missing closing variable delimiter for:"), currentPart.text, 0}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!currentPart.text.isEmpty())
|
||||||
|
result.parts << currentPart;
|
||||||
|
|
||||||
return SnippetParseResult(result);
|
return SnippetParseResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,131 +312,130 @@ SnippetParseResult Snippet::parse(const QString &snippet)
|
|||||||
|
|
||||||
const char NOMANGLER_ID[] = "TextEditor::NoMangler";
|
const char NOMANGLER_ID[] = "TextEditor::NoMangler";
|
||||||
|
|
||||||
|
struct SnippetPart
|
||||||
|
{
|
||||||
|
SnippetPart() = default;
|
||||||
|
explicit SnippetPart(const QString &text,
|
||||||
|
int index = -1,
|
||||||
|
const Utils::Id &manglerId = NOMANGLER_ID)
|
||||||
|
: text(text)
|
||||||
|
, variableIndex(index)
|
||||||
|
, manglerId(manglerId)
|
||||||
|
{}
|
||||||
|
QString text;
|
||||||
|
int variableIndex = -1; // if variable index is >= 0 the text is interpreted as a variable
|
||||||
|
Utils::Id manglerId;
|
||||||
|
};
|
||||||
|
Q_DECLARE_METATYPE(SnippetPart);
|
||||||
|
|
||||||
|
using Parts = QList<SnippetPart>;
|
||||||
|
|
||||||
void Internal::TextEditorPlugin::testSnippetParsing_data()
|
void Internal::TextEditorPlugin::testSnippetParsing_data()
|
||||||
{
|
{
|
||||||
QTest::addColumn<QString>("input");
|
QTest::addColumn<QString>("input");
|
||||||
QTest::addColumn<QString>("text");
|
|
||||||
QTest::addColumn<bool>("success");
|
QTest::addColumn<bool>("success");
|
||||||
QTest::addColumn<QList<int> >("ranges_start");
|
QTest::addColumn<Parts>("parts");
|
||||||
QTest::addColumn<QList<int> >("ranges_length");
|
|
||||||
QTest::addColumn<QList<Utils::Id> >("ranges_mangler");
|
|
||||||
|
|
||||||
QTest::newRow("no input")
|
QTest::newRow("no input") << QString() << true << Parts();
|
||||||
<< QString() << QString() << true
|
QTest::newRow("empty input") << QString("") << true << Parts();
|
||||||
<< (QList<int>()) << (QList<int>()) << (QList<Utils::Id>());
|
QTest::newRow("newline only") << QString("\n") << true << Parts{SnippetPart("\n")};
|
||||||
QTest::newRow("empty input")
|
|
||||||
<< QString::fromLatin1("") << QString::fromLatin1("") << true
|
|
||||||
<< (QList<int>()) << (QList<int>()) << (QList<Utils::Id>());
|
|
||||||
QTest::newRow("newline only")
|
|
||||||
<< QString::fromLatin1("\n") << QString::fromLatin1("\n") << true
|
|
||||||
<< (QList<int>()) << (QList<int>()) << (QList<Utils::Id>());
|
|
||||||
|
|
||||||
QTest::newRow("simple identifier")
|
QTest::newRow("simple identifier")
|
||||||
<< QString::fromLatin1("$tESt$") << QString::fromLatin1("tESt") << true
|
<< QString("$tESt$") << true << Parts{SnippetPart("tESt", 0)};
|
||||||
<< (QList<int>() << 0) << (QList<int>() << 4)
|
|
||||||
<< (QList<Utils::Id>() << NOMANGLER_ID);
|
|
||||||
QTest::newRow("simple identifier with lc")
|
QTest::newRow("simple identifier with lc")
|
||||||
<< QString::fromLatin1("$tESt:l$") << QString::fromLatin1("tESt") << true
|
<< QString("$tESt:l$") << true << Parts{SnippetPart("tESt", 0, LCMANGLER_ID)};
|
||||||
<< (QList<int>() << 0) << (QList<int>() << 4)
|
|
||||||
<< (QList<Utils::Id>() << LCMANGLER_ID);
|
|
||||||
QTest::newRow("simple identifier with uc")
|
QTest::newRow("simple identifier with uc")
|
||||||
<< QString::fromLatin1("$tESt:u$") << QString::fromLatin1("tESt") << true
|
<< QString("$tESt:u$") << true << Parts{SnippetPart("tESt", 0, UCMANGLER_ID)};
|
||||||
<< (QList<int>() << 0) << (QList<int>() << 4)
|
|
||||||
<< (QList<Utils::Id>() << UCMANGLER_ID);
|
|
||||||
QTest::newRow("simple identifier with tc")
|
QTest::newRow("simple identifier with tc")
|
||||||
<< QString::fromLatin1("$tESt:c$") << QString::fromLatin1("tESt") << true
|
<< QString("$tESt:c$") << true << Parts{SnippetPart("tESt", 0, TCMANGLER_ID)};
|
||||||
<< (QList<int>() << 0) << (QList<int>() << 4)
|
|
||||||
<< (QList<Utils::Id>() << TCMANGLER_ID);
|
|
||||||
|
|
||||||
QTest::newRow("escaped string")
|
QTest::newRow("escaped string")
|
||||||
<< QString::fromLatin1("\\\\$test\\\\$") << QString::fromLatin1("$test$") << true
|
<< QString("\\\\$test\\\\$") << true << Parts{SnippetPart("$test$")};
|
||||||
<< (QList<int>()) << (QList<int>())
|
QTest::newRow("escaped escape") << QString("\\\\\\\\$test$\\\\\\\\") << true
|
||||||
<< (QList<Utils::Id>());
|
<< Parts{
|
||||||
QTest::newRow("escaped escape")
|
SnippetPart("\\"),
|
||||||
<< QString::fromLatin1("\\\\\\\\$test$\\\\\\\\") << QString::fromLatin1("\\test\\") << true
|
SnippetPart("test", 0),
|
||||||
<< (QList<int>() << 1) << (QList<int>() << 4)
|
SnippetPart("\\"),
|
||||||
<< (QList<Utils::Id>() << NOMANGLER_ID);
|
};
|
||||||
QTest::newRow("broken escape")
|
QTest::newRow("broken escape")
|
||||||
<< QString::fromLatin1("\\\\$test\\\\\\\\$\\\\") << QString::fromLatin1("\\$test\\\\$\\") << false
|
<< QString::fromLatin1("\\\\$test\\\\\\\\$\\\\") << false << Parts();
|
||||||
<< (QList<int>()) << (QList<int>())
|
|
||||||
<< (QList<Utils::Id>());
|
|
||||||
|
|
||||||
QTest::newRow("Q_PROPERTY")
|
QTest::newRow("Q_PROPERTY") << QString(
|
||||||
<< QString::fromLatin1("Q_PROPERTY($type$ $name$ READ $name$ WRITE set$name:c$ NOTIFY $name$Changed)")
|
"Q_PROPERTY($type$ $name$ READ $name$ WRITE set$name:c$ NOTIFY $name$Changed)")
|
||||||
<< QString::fromLatin1("Q_PROPERTY(type name READ name WRITE setname NOTIFY nameChanged)") << true
|
<< true
|
||||||
<< (QList<int>() << 11 << 16 << 26 << 40 << 52)
|
<< Parts{SnippetPart("Q_PROPERTY("),
|
||||||
<< (QList<int>() << 4 << 4 << 4 << 4 << 4)
|
SnippetPart("type", 0),
|
||||||
<< (QList<Utils::Id>() << NOMANGLER_ID << NOMANGLER_ID << NOMANGLER_ID << TCMANGLER_ID << NOMANGLER_ID);
|
SnippetPart(" "),
|
||||||
|
SnippetPart("name", 1),
|
||||||
|
SnippetPart(" READ "),
|
||||||
|
SnippetPart("name", 1),
|
||||||
|
SnippetPart(" WRITE set"),
|
||||||
|
SnippetPart("name", 1, TCMANGLER_ID),
|
||||||
|
SnippetPart(" NOTIFY "),
|
||||||
|
SnippetPart("name", 1),
|
||||||
|
SnippetPart("Changed)")};
|
||||||
|
|
||||||
QTest::newRow("open identifier")
|
QTest::newRow("open identifier") << QString("$test") << false << Parts();
|
||||||
<< QString::fromLatin1("$test") << QString::fromLatin1("$test") << false
|
QTest::newRow("wrong mangler") << QString("$test:X$") << false << Parts();
|
||||||
<< (QList<int>()) << (QList<int>())
|
|
||||||
<< (QList<Utils::Id>());
|
|
||||||
QTest::newRow("wrong mangler")
|
|
||||||
<< QString::fromLatin1("$test:X$") << QString::fromLatin1("$test:X$") << false
|
|
||||||
<< (QList<int>()) << (QList<int>())
|
|
||||||
<< (QList<Utils::Id>());
|
|
||||||
|
|
||||||
QTest::newRow("multiline with :")
|
QTest::newRow("multiline with :") << QString("class $name$\n"
|
||||||
<< QString::fromLatin1("class $name$\n"
|
|
||||||
"{\n"
|
"{\n"
|
||||||
"public:\n"
|
"public:\n"
|
||||||
" $name$() {}\n"
|
" $name$() {}\n"
|
||||||
"};")
|
"};")
|
||||||
<< QString::fromLatin1("class name\n"
|
<< true
|
||||||
|
<< Parts{
|
||||||
|
SnippetPart("class "),
|
||||||
|
SnippetPart("name", 0),
|
||||||
|
SnippetPart("\n"
|
||||||
"{\n"
|
"{\n"
|
||||||
"public:\n"
|
"public:\n"
|
||||||
" name() {}\n"
|
" "),
|
||||||
"};")
|
SnippetPart("name", 0),
|
||||||
<< true
|
SnippetPart("() {}\n"
|
||||||
<< (QList<int>() << 6 << 25)
|
"};"),
|
||||||
<< (QList<int>() << 4 << 4)
|
};
|
||||||
<< (QList<Utils::Id>() << NOMANGLER_ID << NOMANGLER_ID);
|
|
||||||
|
|
||||||
QTest::newRow("escape sequences")
|
QTest::newRow("escape sequences") << QString("class $name$\\n"
|
||||||
<< QString::fromLatin1("class $name$\\n"
|
|
||||||
"{\\n"
|
"{\\n"
|
||||||
"public\\\\:\\n"
|
"public\\\\:\\n"
|
||||||
"\\t$name$() {}\\n"
|
"\\t$name$() {}\\n"
|
||||||
"};")
|
"};")
|
||||||
<< QString::fromLatin1("class name\n"
|
<< true
|
||||||
|
<< Parts{
|
||||||
|
SnippetPart("class "),
|
||||||
|
SnippetPart("name", 0),
|
||||||
|
SnippetPart("\n"
|
||||||
"{\n"
|
"{\n"
|
||||||
"public\\:\n"
|
"public\\:\n"
|
||||||
"\tname() {}\n"
|
"\t"),
|
||||||
"};")
|
SnippetPart("name", 0),
|
||||||
<< true
|
SnippetPart("() {}\n"
|
||||||
<< (QList<int>() << 6 << 23)
|
"};"),
|
||||||
<< (QList<int>() << 4 << 4)
|
};
|
||||||
<< (QList<Utils::Id>() << NOMANGLER_ID << NOMANGLER_ID);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Internal::TextEditorPlugin::testSnippetParsing()
|
void Internal::TextEditorPlugin::testSnippetParsing()
|
||||||
{
|
{
|
||||||
QFETCH(QString, input);
|
QFETCH(QString, input);
|
||||||
QFETCH(QString, text);
|
|
||||||
QFETCH(bool, success);
|
QFETCH(bool, success);
|
||||||
QFETCH(QList<int>, ranges_start);
|
QFETCH(Parts, parts);
|
||||||
QFETCH(QList<int>, ranges_length);
|
|
||||||
QFETCH(QList<Utils::Id>, ranges_mangler);
|
|
||||||
Q_ASSERT(ranges_start.count() == ranges_length.count()); // sanity check for the test data
|
|
||||||
Q_ASSERT(ranges_start.count() == ranges_mangler.count()); // sanity check for the test data
|
|
||||||
|
|
||||||
SnippetParseResult result = Snippet::parse(input);
|
SnippetParseResult result = Snippet::parse(input);
|
||||||
QCOMPARE(Utils::holds_alternative<ParsedSnippet>(result), success);
|
QCOMPARE(Utils::holds_alternative<ParsedSnippet>(result), success);
|
||||||
|
if (!success)
|
||||||
|
return;
|
||||||
|
|
||||||
ParsedSnippet snippet = Utils::get<ParsedSnippet>(result);
|
ParsedSnippet snippet = Utils::get<ParsedSnippet>(result);
|
||||||
|
|
||||||
QCOMPARE(snippet.text, text);
|
auto rangesCompare = [&](const ParsedSnippet::Part &actual, const SnippetPart &expected) {
|
||||||
QCOMPARE(snippet.ranges.count(), ranges_start.count());
|
QCOMPARE(actual.text, expected.text);
|
||||||
for (int i = 0; i < ranges_start.count(); ++i) {
|
QCOMPARE(actual.variableIndex, expected.variableIndex);
|
||||||
QCOMPARE(snippet.ranges.at(i).start, ranges_start.at(i));
|
auto manglerId = actual.mangler ? actual.mangler->id() : NOMANGLER_ID;
|
||||||
QCOMPARE(snippet.ranges.at(i).length, ranges_length.at(i));
|
QCOMPARE(manglerId, expected.manglerId);
|
||||||
Utils::Id id = NOMANGLER_ID;
|
};
|
||||||
if (snippet.ranges.at(i).mangler)
|
|
||||||
id = snippet.ranges.at(i).mangler->id();
|
for (int i = 0; i < parts.count(); ++i)
|
||||||
QCOMPARE(id, ranges_mangler.at(i));
|
rangesCompare(snippet.parts.at(i), parts.at(i));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -49,14 +49,16 @@ public:
|
|||||||
class TEXTEDITOR_EXPORT ParsedSnippet
|
class TEXTEDITOR_EXPORT ParsedSnippet
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
class Part {
|
||||||
|
public:
|
||||||
|
Part() = default;
|
||||||
|
explicit Part(const QString &text) : text(text) {}
|
||||||
QString text;
|
QString text;
|
||||||
struct Range {
|
int variableIndex = -1; // if variable index is >= 0 the text is interpreted as a variable
|
||||||
Range(int s, int l, NameMangler *m) : start(s), length(l), mangler(m) { }
|
NameMangler *mangler = nullptr;
|
||||||
int start;
|
|
||||||
int length;
|
|
||||||
NameMangler *mangler;
|
|
||||||
};
|
};
|
||||||
QList<Range> ranges;
|
QList<Part> parts;
|
||||||
|
QList<QList<int>> variables;
|
||||||
};
|
};
|
||||||
|
|
||||||
class TEXTEDITOR_EXPORT SnippetParseError
|
class TEXTEDITOR_EXPORT SnippetParseError
|
||||||
|
|||||||
@@ -25,88 +25,140 @@
|
|||||||
|
|
||||||
#include "snippetoverlay.h"
|
#include "snippetoverlay.h"
|
||||||
|
|
||||||
#include "snippet.h"
|
#include <utils/algorithm.h>
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
namespace TextEditor {
|
namespace TextEditor {
|
||||||
namespace Internal {
|
namespace Internal {
|
||||||
|
|
||||||
|
|
||||||
void SnippetOverlay::clear()
|
void SnippetOverlay::clear()
|
||||||
{
|
{
|
||||||
TextEditorOverlay::clear();
|
TextEditorOverlay::clear();
|
||||||
m_equivalentSelections.clear();
|
m_selections.clear();
|
||||||
m_manglers.clear();
|
m_variables.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SnippetOverlay::mapEquivalentSelections()
|
void SnippetOverlay::addSnippetSelection(const QTextCursor &cursor,
|
||||||
|
const QColor &color,
|
||||||
|
NameMangler *mangler,
|
||||||
|
int variableIndex)
|
||||||
{
|
{
|
||||||
m_equivalentSelections.clear();
|
m_variables[variableIndex] << selections().size();
|
||||||
m_equivalentSelections.resize(selections().size());
|
SnippetSelection selection;
|
||||||
|
selection.variableIndex = variableIndex;
|
||||||
QMultiMap<QString, int> all;
|
selection.mangler = mangler;
|
||||||
for (int i = 0; i < selections().size(); ++i)
|
m_selections << selection;
|
||||||
all.insert(selectionText(i).toLower(), i);
|
addOverlaySelection(cursor, color, color, TextEditorOverlay::ExpandBegin);
|
||||||
|
|
||||||
const QList<QString> &uniqueKeys = all.uniqueKeys();
|
|
||||||
foreach (const QString &key, uniqueKeys) {
|
|
||||||
QList<int> indexes;
|
|
||||||
const auto cAll = all;
|
|
||||||
QMultiMap<QString, int>::const_iterator lbit = cAll.lowerBound(key);
|
|
||||||
QMultiMap<QString, int>::const_iterator ubit = cAll.upperBound(key);
|
|
||||||
while (lbit != ubit) {
|
|
||||||
indexes.append(lbit.value());
|
|
||||||
++lbit;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (int index, indexes)
|
|
||||||
m_equivalentSelections[index] = indexes;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SnippetOverlay::updateEquivalentSelections(const QTextCursor &cursor)
|
void SnippetOverlay::updateEquivalentSelections(const QTextCursor &cursor)
|
||||||
{
|
{
|
||||||
int selectionIndex = selectionIndexForCursor(cursor);
|
const int ¤tIndex = indexForCursor(cursor);
|
||||||
if (selectionIndex == -1)
|
if (currentIndex < 0)
|
||||||
return;
|
return;
|
||||||
|
const QString ¤tText = cursorForIndex(currentIndex).selectedText();
|
||||||
const QString ¤tText = selectionText(selectionIndex);
|
const QList<int> &equivalents = m_variables.value(m_selections[currentIndex].variableIndex);
|
||||||
const QList<int> &equivalents = m_equivalentSelections.at(selectionIndex);
|
for (int i : equivalents) {
|
||||||
foreach (int i, equivalents) {
|
if (i == currentIndex)
|
||||||
if (i == selectionIndex)
|
|
||||||
continue;
|
continue;
|
||||||
const QString &equivalentText = selectionText(i);
|
QTextCursor cursor = cursorForIndex(i);
|
||||||
|
const QString &equivalentText = cursor.selectedText();
|
||||||
if (currentText != equivalentText) {
|
if (currentText != equivalentText) {
|
||||||
QTextCursor selectionCursor = assembleCursorForSelection(i);
|
cursor.joinPreviousEditBlock();
|
||||||
selectionCursor.joinPreviousEditBlock();
|
cursor.insertText(currentText);
|
||||||
selectionCursor.removeSelectedText();
|
cursor.endEditBlock();
|
||||||
selectionCursor.insertText(currentText);
|
|
||||||
selectionCursor.endEditBlock();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SnippetOverlay::setNameMangler(const QList<NameMangler *> &manglers)
|
|
||||||
{
|
|
||||||
m_manglers = manglers;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SnippetOverlay::mangle()
|
void SnippetOverlay::mangle()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < m_manglers.count(); ++i) {
|
for (int i = 0; i < m_selections.size(); ++i) {
|
||||||
if (!m_manglers.at(i))
|
if (NameMangler *mangler = m_selections[i].mangler) {
|
||||||
continue;
|
QTextCursor cursor = cursorForIndex(i);
|
||||||
|
const QString current = cursor.selectedText();
|
||||||
const QString current = selectionText(i);
|
const QString result = mangler->mangle(current);
|
||||||
const QString result = m_manglers.at(i)->mangle(current);
|
|
||||||
if (result != current) {
|
if (result != current) {
|
||||||
QTextCursor selectionCursor = assembleCursorForSelection(i);
|
cursor.joinPreviousEditBlock();
|
||||||
selectionCursor.joinPreviousEditBlock();
|
cursor.insertText(result);
|
||||||
selectionCursor.removeSelectedText();
|
cursor.endEditBlock();
|
||||||
selectionCursor.insertText(result);
|
|
||||||
selectionCursor.endEditBlock();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SnippetOverlay::hasCursorInSelection(const QTextCursor &cursor) const
|
||||||
|
{
|
||||||
|
return indexForCursor(cursor) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextCursor SnippetOverlay::nextSelectionCursor(const QTextCursor &cursor) const
|
||||||
|
{
|
||||||
|
const QList<OverlaySelection> selections = TextEditorOverlay::selections();
|
||||||
|
if (selections.isEmpty())
|
||||||
|
return {};
|
||||||
|
const SnippetSelection ¤tSelection = selectionForCursor(cursor);
|
||||||
|
if (currentSelection.variableIndex >= 0) {
|
||||||
|
int nextVariableIndex = currentSelection.variableIndex + 1;
|
||||||
|
if (nextVariableIndex >= m_variables.size())
|
||||||
|
nextVariableIndex = 0;
|
||||||
|
|
||||||
|
for (int selectionIndex : m_variables[nextVariableIndex]) {
|
||||||
|
if (selections[selectionIndex].m_cursor_begin.position() > cursor.position())
|
||||||
|
return cursorForIndex(selectionIndex);
|
||||||
|
}
|
||||||
|
return cursorForIndex(m_variables[nextVariableIndex].first());
|
||||||
|
}
|
||||||
|
// currently not over a variable simply select the next available one
|
||||||
|
for (const OverlaySelection &candidate : selections) {
|
||||||
|
if (candidate.m_cursor_begin.position() > cursor.position())
|
||||||
|
return cursorForSelection(candidate);
|
||||||
|
}
|
||||||
|
return cursorForSelection(selections.first());
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextCursor SnippetOverlay::previousSelectionCursor(const QTextCursor &cursor) const
|
||||||
|
{
|
||||||
|
const QList<OverlaySelection> selections = TextEditorOverlay::selections();
|
||||||
|
if (selections.isEmpty())
|
||||||
|
return {};
|
||||||
|
const SnippetSelection ¤tSelection = selectionForCursor(cursor);
|
||||||
|
if (currentSelection.variableIndex >= 0) {
|
||||||
|
int previousVariableIndex = currentSelection.variableIndex - 1;
|
||||||
|
if (previousVariableIndex < 0)
|
||||||
|
previousVariableIndex = m_variables.size();
|
||||||
|
|
||||||
|
auto equivalents = m_variables[previousVariableIndex].toStdList();
|
||||||
|
equivalents.reverse();
|
||||||
|
for (int selectionIndex : equivalents) {
|
||||||
|
if (selections[selectionIndex].m_cursor_end.position() < cursor.position())
|
||||||
|
return cursorForIndex(selectionIndex);
|
||||||
|
}
|
||||||
|
return cursorForIndex(m_variables[previousVariableIndex].last());
|
||||||
|
}
|
||||||
|
// currently not over a variable simply select the previous available one
|
||||||
|
auto reverse = selections.toStdList();
|
||||||
|
reverse.reverse();
|
||||||
|
for (const OverlaySelection &candidate : reverse) {
|
||||||
|
if (candidate.m_cursor_end.position() < cursor.position())
|
||||||
|
return cursorForSelection(candidate);
|
||||||
|
}
|
||||||
|
return cursorForSelection(selections.last());
|
||||||
|
}
|
||||||
|
|
||||||
|
int SnippetOverlay::indexForCursor(const QTextCursor &cursor) const
|
||||||
|
{
|
||||||
|
return Utils::indexOf(selections(),
|
||||||
|
[pos = cursor.position()](const OverlaySelection &selection) {
|
||||||
|
return selection.m_cursor_begin.position() <= pos
|
||||||
|
&& selection.m_cursor_end.position() >= pos;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
SnippetOverlay::SnippetSelection SnippetOverlay::selectionForCursor(const QTextCursor &cursor) const
|
||||||
|
{
|
||||||
|
return m_selections.value(indexForCursor(cursor));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
} // namespace TextEditor
|
} // namespace TextEditor
|
||||||
|
|||||||
@@ -25,8 +25,11 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "snippet.h"
|
||||||
#include "texteditor/texteditoroverlay.h"
|
#include "texteditor/texteditoroverlay.h"
|
||||||
|
|
||||||
|
#include <QTextEdit>
|
||||||
|
|
||||||
namespace TextEditor {
|
namespace TextEditor {
|
||||||
class NameMangler;
|
class NameMangler;
|
||||||
|
|
||||||
@@ -39,14 +42,30 @@ public:
|
|||||||
|
|
||||||
void clear() override;
|
void clear() override;
|
||||||
|
|
||||||
void mapEquivalentSelections();
|
void addSnippetSelection(const QTextCursor &cursor,
|
||||||
|
const QColor &color,
|
||||||
|
NameMangler *mangler,
|
||||||
|
int variableGoup);
|
||||||
void updateEquivalentSelections(const QTextCursor &cursor);
|
void updateEquivalentSelections(const QTextCursor &cursor);
|
||||||
void setNameMangler(const QList<NameMangler *> &manglers);
|
|
||||||
void mangle();
|
void mangle();
|
||||||
|
|
||||||
|
bool hasCursorInSelection(const QTextCursor &cursor) const;
|
||||||
|
|
||||||
|
QTextCursor nextSelectionCursor(const QTextCursor &cursor) const;
|
||||||
|
QTextCursor previousSelectionCursor(const QTextCursor &cursor) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QVector<QList<int> > m_equivalentSelections;
|
struct SnippetSelection
|
||||||
QList<NameMangler *> m_manglers;
|
{
|
||||||
|
int variableIndex = -1;
|
||||||
|
NameMangler *mangler;
|
||||||
|
};
|
||||||
|
|
||||||
|
int indexForCursor(const QTextCursor &cursor) const;
|
||||||
|
SnippetSelection selectionForCursor(const QTextCursor &cursor) const;
|
||||||
|
|
||||||
|
QList<SnippetSelection> m_selections;
|
||||||
|
QMap<int, QList<int>> m_variables;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
|
|||||||
@@ -2699,12 +2699,33 @@ void TextEditorWidget::keyPressEvent(QKeyEvent *e)
|
|||||||
d->m_codeAssistant.process();
|
d->m_codeAssistant.process();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PositionedPart : public ParsedSnippet::Part
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit PositionedPart(const ParsedSnippet::Part &part) : ParsedSnippet::Part(part) {}
|
||||||
|
int start;
|
||||||
|
int end;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CursorPart : public ParsedSnippet::Part
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CursorPart(const PositionedPart &part, QTextDocument *doc)
|
||||||
|
: ParsedSnippet::Part(part)
|
||||||
|
, cursor(doc)
|
||||||
|
{
|
||||||
|
cursor.setPosition(part.start);
|
||||||
|
cursor.setPosition(part.end, QTextCursor::KeepAnchor);
|
||||||
|
}
|
||||||
|
QTextCursor cursor;
|
||||||
|
};
|
||||||
|
|
||||||
void TextEditorWidget::insertCodeSnippet(const QTextCursor &cursor_arg, const QString &snippet)
|
void TextEditorWidget::insertCodeSnippet(const QTextCursor &cursor_arg, const QString &snippet)
|
||||||
{
|
{
|
||||||
SnippetParseResult result = Snippet::parse(snippet);
|
SnippetParseResult result = Snippet::parse(snippet);
|
||||||
if (Utils::holds_alternative<SnippetParseError>(result)) {
|
if (Utils::holds_alternative<SnippetParseError>(result)) {
|
||||||
const auto &error = Utils::get<SnippetParseError>(result);
|
const auto &error = Utils::get<SnippetParseError>(result);
|
||||||
QMessageBox::warning(this, QLatin1String("Snippet Parse Error"), error.htmlMessage());
|
QMessageBox::warning(this, tr("Snippet Parse Error"), error.htmlMessage());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QTC_ASSERT(Utils::holds_alternative<ParsedSnippet>(result), return);
|
QTC_ASSERT(Utils::holds_alternative<ParsedSnippet>(result), return);
|
||||||
@@ -2715,44 +2736,47 @@ void TextEditorWidget::insertCodeSnippet(const QTextCursor &cursor_arg, const QS
|
|||||||
cursor.removeSelectedText();
|
cursor.removeSelectedText();
|
||||||
const int startCursorPosition = cursor.position();
|
const int startCursorPosition = cursor.position();
|
||||||
|
|
||||||
cursor.insertText(data.text);
|
d->m_snippetOverlay->mangle();
|
||||||
QList<QTextEdit::ExtraSelection> selections;
|
d->m_snippetOverlay->clear();
|
||||||
|
|
||||||
QList<NameMangler *> manglers;
|
QList<PositionedPart> positionedParts;
|
||||||
for (int i = 0; i < data.ranges.count(); ++i) {
|
for (const ParsedSnippet::Part &part : qAsConst(data.parts)) {
|
||||||
int position = data.ranges.at(i).start + startCursorPosition;
|
if (part.variableIndex >= 0) {
|
||||||
int length = data.ranges.at(i).length;
|
PositionedPart posPart(part);
|
||||||
|
posPart.start = cursor.position();
|
||||||
QTextCursor tc(document());
|
cursor.insertText(part.text);
|
||||||
tc.setPosition(position);
|
posPart.end = cursor.position();
|
||||||
tc.setPosition(position + length, QTextCursor::KeepAnchor);
|
positionedParts << posPart;
|
||||||
QTextEdit::ExtraSelection selection;
|
} else {
|
||||||
selection.cursor = tc;
|
cursor.insertText(part.text);
|
||||||
selection.format = (length
|
|
||||||
? textDocument()->fontSettings().toTextCharFormat(C_OCCURRENCES)
|
|
||||||
: textDocument()->fontSettings().toTextCharFormat(C_OCCURRENCES_RENAME));
|
|
||||||
selections.append(selection);
|
|
||||||
manglers << data.ranges.at(i).mangler;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<CursorPart> cursorParts = Utils::transform(positionedParts,
|
||||||
|
[doc = document()](const PositionedPart &part) {
|
||||||
|
return CursorPart(part, doc);
|
||||||
|
});
|
||||||
|
|
||||||
cursor.setPosition(startCursorPosition, QTextCursor::KeepAnchor);
|
cursor.setPosition(startCursorPosition, QTextCursor::KeepAnchor);
|
||||||
d->m_document->autoIndent(cursor);
|
d->m_document->autoIndent(cursor);
|
||||||
cursor.endEditBlock();
|
cursor.endEditBlock();
|
||||||
|
|
||||||
setExtraSelections(TextEditorWidget::SnippetPlaceholderSelection, selections);
|
const QColor &occurrencesColor
|
||||||
d->m_snippetOverlay->setNameMangler(manglers);
|
= textDocument()->fontSettings().toTextCharFormat(C_OCCURRENCES).background().color();
|
||||||
|
const QColor &renameColor
|
||||||
|
= textDocument()->fontSettings().toTextCharFormat(C_OCCURRENCES_RENAME).background().color();
|
||||||
|
|
||||||
if (!selections.isEmpty()) {
|
for (const CursorPart &part : cursorParts) {
|
||||||
const QTextEdit::ExtraSelection &selection = selections.first();
|
const QColor &color = part.cursor.hasSelection() ? occurrencesColor : renameColor;
|
||||||
|
d->m_snippetOverlay->addSnippetSelection(part.cursor,
|
||||||
cursor = textCursor();
|
color,
|
||||||
if (selection.cursor.hasSelection()) {
|
part.mangler,
|
||||||
cursor.setPosition(selection.cursor.selectionStart());
|
part.variableIndex);
|
||||||
cursor.setPosition(selection.cursor.selectionEnd(), QTextCursor::KeepAnchor);
|
|
||||||
} else {
|
|
||||||
cursor.setPosition(selection.cursor.position());
|
|
||||||
}
|
}
|
||||||
setTextCursor(cursor);
|
|
||||||
|
if (!cursorParts.isEmpty()) {
|
||||||
|
setTextCursor(cursorParts.first().cursor);
|
||||||
|
d->m_snippetOverlay->setVisible(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3461,36 +3485,8 @@ void TextEditorWidgetPrivate::snippetTabOrBacktab(bool forward)
|
|||||||
if (!m_snippetOverlay->isVisible() || m_snippetOverlay->isEmpty())
|
if (!m_snippetOverlay->isVisible() || m_snippetOverlay->isEmpty())
|
||||||
return;
|
return;
|
||||||
QTextCursor cursor = q->textCursor();
|
QTextCursor cursor = q->textCursor();
|
||||||
OverlaySelection final;
|
q->setTextCursor(forward ? m_snippetOverlay->nextSelectionCursor(cursor)
|
||||||
if (forward) {
|
: m_snippetOverlay->previousSelectionCursor(cursor));
|
||||||
for (int i = 0; i < m_snippetOverlay->selections().count(); ++i){
|
|
||||||
const OverlaySelection &selection = m_snippetOverlay->selections().at(i);
|
|
||||||
if (selection.m_cursor_begin.position() >= cursor.position()
|
|
||||||
&& selection.m_cursor_end.position() > cursor.position()) {
|
|
||||||
final = selection;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (int i = m_snippetOverlay->selections().count()-1; i >= 0; --i){
|
|
||||||
const OverlaySelection &selection = m_snippetOverlay->selections().at(i);
|
|
||||||
if (selection.m_cursor_end.position() < cursor.position()) {
|
|
||||||
final = selection;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
if (final.m_cursor_begin.isNull())
|
|
||||||
final = forward ? m_snippetOverlay->selections().first() : m_snippetOverlay->selections().last();
|
|
||||||
|
|
||||||
if (final.m_cursor_begin.position() == final.m_cursor_end.position()) { // empty tab stop
|
|
||||||
cursor.setPosition(final.m_cursor_end.position());
|
|
||||||
} else {
|
|
||||||
cursor.setPosition(final.m_cursor_begin.position());
|
|
||||||
cursor.setPosition(final.m_cursor_end.position(), QTextCursor::KeepAnchor);
|
|
||||||
}
|
|
||||||
q->setTextCursor(cursor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate global position for a tooltip considering the left extra area.
|
// Calculate global position for a tooltip considering the left extra area.
|
||||||
@@ -7082,17 +7078,6 @@ void TextEditorWidgetPrivate::setExtraSelections(Id kind, const QList<QTextEdit:
|
|||||||
TextEditorOverlay::LockSize);
|
TextEditorOverlay::LockSize);
|
||||||
}
|
}
|
||||||
m_overlay->setVisible(!m_overlay->isEmpty());
|
m_overlay->setVisible(!m_overlay->isEmpty());
|
||||||
} else if (kind == TextEditorWidget::SnippetPlaceholderSelection) {
|
|
||||||
m_snippetOverlay->mangle();
|
|
||||||
m_snippetOverlay->clear();
|
|
||||||
foreach (const QTextEdit::ExtraSelection &selection, m_extraSelections[kind]) {
|
|
||||||
m_snippetOverlay->addOverlaySelection(selection.cursor,
|
|
||||||
selection.format.background().color(),
|
|
||||||
selection.format.background().color(),
|
|
||||||
TextEditorOverlay::ExpandBegin);
|
|
||||||
}
|
|
||||||
m_snippetOverlay->mapEquivalentSelections();
|
|
||||||
m_snippetOverlay->setVisible(!m_snippetOverlay->isEmpty());
|
|
||||||
} else {
|
} else {
|
||||||
QList<QTextEdit::ExtraSelection> all;
|
QList<QTextEdit::ExtraSelection> all;
|
||||||
for (auto i = m_extraSelections.constBegin(); i != m_extraSelections.constEnd(); ++i) {
|
for (auto i = m_extraSelections.constBegin(); i != m_extraSelections.constEnd(); ++i) {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
#include <QTextBlock>
|
#include <QTextBlock>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
using namespace TextEditor;
|
using namespace TextEditor;
|
||||||
using namespace TextEditor::Internal;
|
using namespace TextEditor::Internal;
|
||||||
@@ -416,6 +417,21 @@ void TextEditorOverlay::paint(QPainter *painter, const QRect &clip)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QTextCursor TextEditorOverlay::cursorForSelection(const OverlaySelection &selection) const
|
||||||
|
{
|
||||||
|
QTextCursor cursor = selection.m_cursor_begin;
|
||||||
|
cursor.setPosition(selection.m_cursor_begin.position());
|
||||||
|
cursor.setKeepPositionOnInsert(false);
|
||||||
|
if (!cursor.isNull())
|
||||||
|
cursor.setPosition(selection.m_cursor_end.position(), QTextCursor::KeepAnchor);
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextCursor TextEditorOverlay::cursorForIndex(int selectionIndex) const
|
||||||
|
{
|
||||||
|
return cursorForSelection(m_selections.value(selectionIndex));
|
||||||
|
}
|
||||||
|
|
||||||
void TextEditorOverlay::fill(QPainter *painter, const QColor &color, const QRect &clip)
|
void TextEditorOverlay::fill(QPainter *painter, const QColor &color, const QRect &clip)
|
||||||
{
|
{
|
||||||
Q_UNUSED(clip)
|
Q_UNUSED(clip)
|
||||||
@@ -443,41 +459,6 @@ void TextEditorOverlay::fill(QPainter *painter, const QColor &color, const QRect
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! \returns true if any selection contains \a cursor, where a cursor on the
|
|
||||||
start or end of a selection is counted as contained.
|
|
||||||
*/
|
|
||||||
bool TextEditorOverlay::hasCursorInSelection(const QTextCursor &cursor) const
|
|
||||||
{
|
|
||||||
if (selectionIndexForCursor(cursor) != -1)
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int TextEditorOverlay::selectionIndexForCursor(const QTextCursor &cursor) const
|
|
||||||
{
|
|
||||||
for (int i = 0; i < m_selections.size(); ++i) {
|
|
||||||
const OverlaySelection &selection = m_selections.at(i);
|
|
||||||
if (cursor.position() >= selection.m_cursor_begin.position()
|
|
||||||
&& cursor.position() <= selection.m_cursor_end.position())
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString TextEditorOverlay::selectionText(int selectionIndex) const
|
|
||||||
{
|
|
||||||
return assembleCursorForSelection(selectionIndex).selectedText();
|
|
||||||
}
|
|
||||||
|
|
||||||
QTextCursor TextEditorOverlay::assembleCursorForSelection(int selectionIndex) const
|
|
||||||
{
|
|
||||||
const OverlaySelection &selection = m_selections.at(selectionIndex);
|
|
||||||
QTextCursor cursor(m_editor->document());
|
|
||||||
cursor.setPosition(selection.m_cursor_begin.position());
|
|
||||||
cursor.setPosition(selection.m_cursor_end.position(), QTextCursor::KeepAnchor);
|
|
||||||
return cursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TextEditorOverlay::hasFirstSelectionBeginMoved() const
|
bool TextEditorOverlay::hasFirstSelectionBeginMoved() const
|
||||||
{
|
{
|
||||||
if (m_firstSelectionOriginalBegin == -1 || m_selections.isEmpty())
|
if (m_firstSelectionOriginalBegin == -1 || m_selections.isEmpty())
|
||||||
|
|||||||
@@ -92,14 +92,11 @@ public:
|
|||||||
|
|
||||||
inline int dropShadowWidth() const { return m_dropShadowWidth; }
|
inline int dropShadowWidth() const { return m_dropShadowWidth; }
|
||||||
|
|
||||||
bool hasCursorInSelection(const QTextCursor &cursor) const;
|
|
||||||
|
|
||||||
bool hasFirstSelectionBeginMoved() const;
|
bool hasFirstSelectionBeginMoved() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int selectionIndexForCursor(const QTextCursor &cursor) const;
|
QTextCursor cursorForSelection(const OverlaySelection &selection) const;
|
||||||
QString selectionText(int selectionIndex) const;
|
QTextCursor cursorForIndex(int selectionIndex) const;
|
||||||
QTextCursor assembleCursorForSelection(int selectionIndex) const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPainterPath createSelectionPath(const QTextCursor &begin, const QTextCursor &end, const QRect& clip);
|
QPainterPath createSelectionPath(const QTextCursor &begin, const QTextCursor &end, const QRect& clip);
|
||||||
|
|||||||
Reference in New Issue
Block a user