diff --git a/src/plugins/texteditor/snippets/snippet.cpp b/src/plugins/texteditor/snippets/snippet.cpp index ea4671596cf..fbe00be39d7 100644 --- a/src/plugins/texteditor/snippets/snippet.cpp +++ b/src/plugins/texteditor/snippets/snippet.cpp @@ -170,34 +170,10 @@ bool Snippet::isModified() const return m_isModified; } -struct SnippetReplacement -{ - QString text; - int posDelta = 0; -}; - -static SnippetReplacement replacementAt(int pos, ParsedSnippet &parsedSnippet) +static QString tipPart(const ParsedSnippet::Part &part) { static const char kOpenBold[] = ""; static const char kCloseBold[] = ""; - - 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 replacements = {{'\n', "
"}, {' ', " "}, {'"', """}, @@ -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); if (Utils::holds_alternative(result)) return Utils::get(result).htmlMessage(); QTC_ASSERT(Utils::holds_alternative(result), return {}); - auto parsedSnippet = Utils::get(result); + const ParsedSnippet parsedSnippet = Utils::get(result); QString tip(""); - int pos = 0; - for (int end = parsedSnippet.text.count(); pos < end;) { - 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()); + for (const ParsedSnippet::Part &part : parsedSnippet.parts) + tip.append(tipPart(part)); return tip; } @@ -253,25 +225,35 @@ SnippetParseResult Snippet::parse(const QString &snippet) return {SnippetParseError{errorMessage, {}, -1}}; const int count = preprocessedSnippet.count(); - int start = -1; NameMangler *mangler = nullptr; - result.text.reserve(count); + QMap variableIndexes; + bool inVar = false; + + ParsedSnippet::Part currentPart; for (int i = 0; i < count; ++i) { QChar current = preprocessedSnippet.at(i); - QChar next = (i + 1) < count ? preprocessedSnippet.at(i + 1) : QChar(); if (current == Snippet::kVariableDelimiter) { - if (start < 0) { - // start delimiter: - start = result.text.count(); - } else { - int length = result.text.count() - start; - result.ranges << ParsedSnippet::Range(start, length, mangler); + if (inVar) { + const QString variable = currentPart.text; + const int index = variableIndexes.value(currentPart.text, result.variables.size()); + if (index == result.variables.size()) { + variableIndexes[variable] = index; + result.variables.append(QList()); + } + currentPart.variableIndex = index; + currentPart.mangler = mangler; mangler = nullptr; - start = -1; + result.variables[index] << result.parts.size() - 1; + } else if (currentPart.text.isEmpty()) { + inVar = !inVar; + continue; } + result.parts << currentPart; + currentPart = ParsedSnippet::Part(); + inVar = !inVar; continue; } @@ -281,12 +263,13 @@ SnippetParseResult Snippet::parse(const QString &snippet) i}}; } - if (current == QLatin1Char(':') && start >= 0) { - if (next == QLatin1Char('l')) { + if (current == ':' && inVar) { + QChar next = (i + 1) < count ? preprocessedSnippet.at(i + 1) : QChar(); + if (next == 'l') { mangler = &lcMangler; - } else if (next == QLatin1Char('u')) { + } else if (next == 'u') { mangler = &ucMangler; - } else if (next == QLatin1Char('c')) { + } else if (next == 'c') { mangler = &tcMangler; } else { return SnippetParseResult{ @@ -299,20 +282,26 @@ SnippetParseResult Snippet::parse(const QString &snippet) continue; } - if (current == kEscapeChar && (next == kEscapeChar || next == kVariableDelimiter)) { - result.text.append(next); - ++i; - continue; + if (current == kEscapeChar){ + QChar next = (i + 1) < count ? preprocessedSnippet.at(i + 1) : QChar(); + if (next == kEscapeChar || next == kVariableDelimiter) { + currentPart.text.append(next); + ++i; + continue; + } } - result.text.append(current); + currentPart.text.append(current); } - if (start >= 0) { + if (inVar) { 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); } @@ -323,131 +312,130 @@ SnippetParseResult Snippet::parse(const QString &snippet) 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; + void Internal::TextEditorPlugin::testSnippetParsing_data() { QTest::addColumn("input"); - QTest::addColumn("text"); QTest::addColumn("success"); - QTest::addColumn >("ranges_start"); - QTest::addColumn >("ranges_length"); - QTest::addColumn >("ranges_mangler"); + QTest::addColumn("parts"); - QTest::newRow("no input") - << QString() << QString() << true - << (QList()) << (QList()) << (QList()); - QTest::newRow("empty input") - << QString::fromLatin1("") << QString::fromLatin1("") << true - << (QList()) << (QList()) << (QList()); - QTest::newRow("newline only") - << QString::fromLatin1("\n") << QString::fromLatin1("\n") << true - << (QList()) << (QList()) << (QList()); + QTest::newRow("no input") << QString() << true << Parts(); + QTest::newRow("empty input") << QString("") << true << Parts(); + QTest::newRow("newline only") << QString("\n") << true << Parts{SnippetPart("\n")}; QTest::newRow("simple identifier") - << QString::fromLatin1("$tESt$") << QString::fromLatin1("tESt") << true - << (QList() << 0) << (QList() << 4) - << (QList() << NOMANGLER_ID); + << QString("$tESt$") << true << Parts{SnippetPart("tESt", 0)}; QTest::newRow("simple identifier with lc") - << QString::fromLatin1("$tESt:l$") << QString::fromLatin1("tESt") << true - << (QList() << 0) << (QList() << 4) - << (QList() << LCMANGLER_ID); + << QString("$tESt:l$") << true << Parts{SnippetPart("tESt", 0, LCMANGLER_ID)}; QTest::newRow("simple identifier with uc") - << QString::fromLatin1("$tESt:u$") << QString::fromLatin1("tESt") << true - << (QList() << 0) << (QList() << 4) - << (QList() << UCMANGLER_ID); + << QString("$tESt:u$") << true << Parts{SnippetPart("tESt", 0, UCMANGLER_ID)}; QTest::newRow("simple identifier with tc") - << QString::fromLatin1("$tESt:c$") << QString::fromLatin1("tESt") << true - << (QList() << 0) << (QList() << 4) - << (QList() << TCMANGLER_ID); + << QString("$tESt:c$") << true << Parts{SnippetPart("tESt", 0, TCMANGLER_ID)}; QTest::newRow("escaped string") - << QString::fromLatin1("\\\\$test\\\\$") << QString::fromLatin1("$test$") << true - << (QList()) << (QList()) - << (QList()); - QTest::newRow("escaped escape") - << QString::fromLatin1("\\\\\\\\$test$\\\\\\\\") << QString::fromLatin1("\\test\\") << true - << (QList() << 1) << (QList() << 4) - << (QList() << NOMANGLER_ID); + << QString("\\\\$test\\\\$") << true << Parts{SnippetPart("$test$")}; + QTest::newRow("escaped escape") << QString("\\\\\\\\$test$\\\\\\\\") << true + << Parts{ + SnippetPart("\\"), + SnippetPart("test", 0), + SnippetPart("\\"), + }; QTest::newRow("broken escape") - << QString::fromLatin1("\\\\$test\\\\\\\\$\\\\") << QString::fromLatin1("\\$test\\\\$\\") << false - << (QList()) << (QList()) - << (QList()); + << QString::fromLatin1("\\\\$test\\\\\\\\$\\\\") << false << Parts(); - QTest::newRow("Q_PROPERTY") - << QString::fromLatin1("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 - << (QList() << 11 << 16 << 26 << 40 << 52) - << (QList() << 4 << 4 << 4 << 4 << 4) - << (QList() << NOMANGLER_ID << NOMANGLER_ID << NOMANGLER_ID << TCMANGLER_ID << NOMANGLER_ID); + QTest::newRow("Q_PROPERTY") << QString( + "Q_PROPERTY($type$ $name$ READ $name$ WRITE set$name:c$ NOTIFY $name$Changed)") + << true + << Parts{SnippetPart("Q_PROPERTY("), + SnippetPart("type", 0), + 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") - << QString::fromLatin1("$test") << QString::fromLatin1("$test") << false - << (QList()) << (QList()) - << (QList()); - QTest::newRow("wrong mangler") - << QString::fromLatin1("$test:X$") << QString::fromLatin1("$test:X$") << false - << (QList()) << (QList()) - << (QList()); + QTest::newRow("open identifier") << QString("$test") << false << Parts(); + QTest::newRow("wrong mangler") << QString("$test:X$") << false << Parts(); - QTest::newRow("multiline with :") - << QString::fromLatin1("class $name$\n" - "{\n" - "public:\n" - " $name$() {}\n" - "};") - << QString::fromLatin1("class name\n" - "{\n" - "public:\n" - " name() {}\n" - "};") - << true - << (QList() << 6 << 25) - << (QList() << 4 << 4) - << (QList() << NOMANGLER_ID << NOMANGLER_ID); - - QTest::newRow("escape sequences") - << QString::fromLatin1("class $name$\\n" - "{\\n" - "public\\\\:\\n" - "\\t$name$() {}\\n" - "};") - << QString::fromLatin1("class name\n" - "{\n" - "public\\:\n" - "\tname() {}\n" - "};") - << true - << (QList() << 6 << 23) - << (QList() << 4 << 4) - << (QList() << NOMANGLER_ID << NOMANGLER_ID); + QTest::newRow("multiline with :") << QString("class $name$\n" + "{\n" + "public:\n" + " $name$() {}\n" + "};") + << true + << Parts{ + SnippetPart("class "), + SnippetPart("name", 0), + SnippetPart("\n" + "{\n" + "public:\n" + " "), + SnippetPart("name", 0), + SnippetPart("() {}\n" + "};"), + }; + QTest::newRow("escape sequences") << QString("class $name$\\n" + "{\\n" + "public\\\\:\\n" + "\\t$name$() {}\\n" + "};") + << true + << Parts{ + SnippetPart("class "), + SnippetPart("name", 0), + SnippetPart("\n" + "{\n" + "public\\:\n" + "\t"), + SnippetPart("name", 0), + SnippetPart("() {}\n" + "};"), + }; } void Internal::TextEditorPlugin::testSnippetParsing() { QFETCH(QString, input); - QFETCH(QString, text); QFETCH(bool, success); - QFETCH(QList, ranges_start); - QFETCH(QList, ranges_length); - QFETCH(QList, 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 + QFETCH(Parts, parts); SnippetParseResult result = Snippet::parse(input); QCOMPARE(Utils::holds_alternative(result), success); + if (!success) + return; ParsedSnippet snippet = Utils::get(result); - QCOMPARE(snippet.text, text); - QCOMPARE(snippet.ranges.count(), ranges_start.count()); - for (int i = 0; i < ranges_start.count(); ++i) { - QCOMPARE(snippet.ranges.at(i).start, ranges_start.at(i)); - QCOMPARE(snippet.ranges.at(i).length, ranges_length.at(i)); - Utils::Id id = NOMANGLER_ID; - if (snippet.ranges.at(i).mangler) - id = snippet.ranges.at(i).mangler->id(); - QCOMPARE(id, ranges_mangler.at(i)); - } + auto rangesCompare = [&](const ParsedSnippet::Part &actual, const SnippetPart &expected) { + QCOMPARE(actual.text, expected.text); + QCOMPARE(actual.variableIndex, expected.variableIndex); + auto manglerId = actual.mangler ? actual.mangler->id() : NOMANGLER_ID; + QCOMPARE(manglerId, expected.manglerId); + }; + + for (int i = 0; i < parts.count(); ++i) + rangesCompare(snippet.parts.at(i), parts.at(i)); } #endif - diff --git a/src/plugins/texteditor/snippets/snippet.h b/src/plugins/texteditor/snippets/snippet.h index 6fe3f8ad0ce..8334a66a492 100644 --- a/src/plugins/texteditor/snippets/snippet.h +++ b/src/plugins/texteditor/snippets/snippet.h @@ -49,14 +49,16 @@ public: class TEXTEDITOR_EXPORT ParsedSnippet { public: - QString text; - struct Range { - Range(int s, int l, NameMangler *m) : start(s), length(l), mangler(m) { } - int start; - int length; - NameMangler *mangler; + class Part { + public: + Part() = default; + explicit Part(const QString &text) : text(text) {} + QString text; + int variableIndex = -1; // if variable index is >= 0 the text is interpreted as a variable + NameMangler *mangler = nullptr; }; - QList ranges; + QList parts; + QList> variables; }; class TEXTEDITOR_EXPORT SnippetParseError diff --git a/src/plugins/texteditor/snippets/snippetoverlay.cpp b/src/plugins/texteditor/snippets/snippetoverlay.cpp index e80cf8cb948..f35c8b6aa91 100644 --- a/src/plugins/texteditor/snippets/snippetoverlay.cpp +++ b/src/plugins/texteditor/snippets/snippetoverlay.cpp @@ -25,88 +25,140 @@ #include "snippetoverlay.h" -#include "snippet.h" +#include +#include namespace TextEditor { namespace Internal { - void SnippetOverlay::clear() { TextEditorOverlay::clear(); - m_equivalentSelections.clear(); - m_manglers.clear(); + m_selections.clear(); + m_variables.clear(); } -void SnippetOverlay::mapEquivalentSelections() +void SnippetOverlay::addSnippetSelection(const QTextCursor &cursor, + const QColor &color, + NameMangler *mangler, + int variableIndex) { - m_equivalentSelections.clear(); - m_equivalentSelections.resize(selections().size()); - - QMultiMap all; - for (int i = 0; i < selections().size(); ++i) - all.insert(selectionText(i).toLower(), i); - - const QList &uniqueKeys = all.uniqueKeys(); - foreach (const QString &key, uniqueKeys) { - QList indexes; - const auto cAll = all; - QMultiMap::const_iterator lbit = cAll.lowerBound(key); - QMultiMap::const_iterator ubit = cAll.upperBound(key); - while (lbit != ubit) { - indexes.append(lbit.value()); - ++lbit; - } - - foreach (int index, indexes) - m_equivalentSelections[index] = indexes; - } + m_variables[variableIndex] << selections().size(); + SnippetSelection selection; + selection.variableIndex = variableIndex; + selection.mangler = mangler; + m_selections << selection; + addOverlaySelection(cursor, color, color, TextEditorOverlay::ExpandBegin); } void SnippetOverlay::updateEquivalentSelections(const QTextCursor &cursor) { - int selectionIndex = selectionIndexForCursor(cursor); - if (selectionIndex == -1) + const int ¤tIndex = indexForCursor(cursor); + if (currentIndex < 0) return; - - const QString ¤tText = selectionText(selectionIndex); - const QList &equivalents = m_equivalentSelections.at(selectionIndex); - foreach (int i, equivalents) { - if (i == selectionIndex) + const QString ¤tText = cursorForIndex(currentIndex).selectedText(); + const QList &equivalents = m_variables.value(m_selections[currentIndex].variableIndex); + for (int i : equivalents) { + if (i == currentIndex) continue; - const QString &equivalentText = selectionText(i); + QTextCursor cursor = cursorForIndex(i); + const QString &equivalentText = cursor.selectedText(); if (currentText != equivalentText) { - QTextCursor selectionCursor = assembleCursorForSelection(i); - selectionCursor.joinPreviousEditBlock(); - selectionCursor.removeSelectedText(); - selectionCursor.insertText(currentText); - selectionCursor.endEditBlock(); + cursor.joinPreviousEditBlock(); + cursor.insertText(currentText); + cursor.endEditBlock(); } } } -void SnippetOverlay::setNameMangler(const QList &manglers) -{ - m_manglers = manglers; -} - void SnippetOverlay::mangle() { - for (int i = 0; i < m_manglers.count(); ++i) { - if (!m_manglers.at(i)) - continue; - - const QString current = selectionText(i); - const QString result = m_manglers.at(i)->mangle(current); - if (result != current) { - QTextCursor selectionCursor = assembleCursorForSelection(i); - selectionCursor.joinPreviousEditBlock(); - selectionCursor.removeSelectedText(); - selectionCursor.insertText(result); - selectionCursor.endEditBlock(); + for (int i = 0; i < m_selections.size(); ++i) { + if (NameMangler *mangler = m_selections[i].mangler) { + QTextCursor cursor = cursorForIndex(i); + const QString current = cursor.selectedText(); + const QString result = mangler->mangle(current); + if (result != current) { + cursor.joinPreviousEditBlock(); + cursor.insertText(result); + cursor.endEditBlock(); + } } } } +bool SnippetOverlay::hasCursorInSelection(const QTextCursor &cursor) const +{ + return indexForCursor(cursor) >= 0; +} + +QTextCursor SnippetOverlay::nextSelectionCursor(const QTextCursor &cursor) const +{ + const QList 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 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 TextEditor diff --git a/src/plugins/texteditor/snippets/snippetoverlay.h b/src/plugins/texteditor/snippets/snippetoverlay.h index 0edf0a8e0d9..a6ca9f64069 100644 --- a/src/plugins/texteditor/snippets/snippetoverlay.h +++ b/src/plugins/texteditor/snippets/snippetoverlay.h @@ -25,8 +25,11 @@ #pragma once +#include "snippet.h" #include "texteditor/texteditoroverlay.h" +#include + namespace TextEditor { class NameMangler; @@ -39,14 +42,30 @@ public: void clear() override; - void mapEquivalentSelections(); + void addSnippetSelection(const QTextCursor &cursor, + const QColor &color, + NameMangler *mangler, + int variableGoup); void updateEquivalentSelections(const QTextCursor &cursor); - void setNameMangler(const QList &manglers); void mangle(); + bool hasCursorInSelection(const QTextCursor &cursor) const; + + QTextCursor nextSelectionCursor(const QTextCursor &cursor) const; + QTextCursor previousSelectionCursor(const QTextCursor &cursor) const; + private: - QVector > m_equivalentSelections; - QList m_manglers; + struct SnippetSelection + { + int variableIndex = -1; + NameMangler *mangler; + }; + + int indexForCursor(const QTextCursor &cursor) const; + SnippetSelection selectionForCursor(const QTextCursor &cursor) const; + + QList m_selections; + QMap> m_variables; }; } // namespace Internal diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 921b3610105..8eaac179e35 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -2699,12 +2699,33 @@ void TextEditorWidget::keyPressEvent(QKeyEvent *e) 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) { SnippetParseResult result = Snippet::parse(snippet); if (Utils::holds_alternative(result)) { const auto &error = Utils::get(result); - QMessageBox::warning(this, QLatin1String("Snippet Parse Error"), error.htmlMessage()); + QMessageBox::warning(this, tr("Snippet Parse Error"), error.htmlMessage()); return; } QTC_ASSERT(Utils::holds_alternative(result), return); @@ -2715,44 +2736,47 @@ void TextEditorWidget::insertCodeSnippet(const QTextCursor &cursor_arg, const QS cursor.removeSelectedText(); const int startCursorPosition = cursor.position(); - cursor.insertText(data.text); - QList selections; + d->m_snippetOverlay->mangle(); + d->m_snippetOverlay->clear(); - QList manglers; - for (int i = 0; i < data.ranges.count(); ++i) { - int position = data.ranges.at(i).start + startCursorPosition; - int length = data.ranges.at(i).length; - - QTextCursor tc(document()); - tc.setPosition(position); - tc.setPosition(position + length, QTextCursor::KeepAnchor); - QTextEdit::ExtraSelection selection; - selection.cursor = tc; - selection.format = (length - ? textDocument()->fontSettings().toTextCharFormat(C_OCCURRENCES) - : textDocument()->fontSettings().toTextCharFormat(C_OCCURRENCES_RENAME)); - selections.append(selection); - manglers << data.ranges.at(i).mangler; + QList positionedParts; + for (const ParsedSnippet::Part &part : qAsConst(data.parts)) { + if (part.variableIndex >= 0) { + PositionedPart posPart(part); + posPart.start = cursor.position(); + cursor.insertText(part.text); + posPart.end = cursor.position(); + positionedParts << posPart; + } else { + cursor.insertText(part.text); + } } + QList cursorParts = Utils::transform(positionedParts, + [doc = document()](const PositionedPart &part) { + return CursorPart(part, doc); + }); + cursor.setPosition(startCursorPosition, QTextCursor::KeepAnchor); d->m_document->autoIndent(cursor); cursor.endEditBlock(); - setExtraSelections(TextEditorWidget::SnippetPlaceholderSelection, selections); - d->m_snippetOverlay->setNameMangler(manglers); + const QColor &occurrencesColor + = textDocument()->fontSettings().toTextCharFormat(C_OCCURRENCES).background().color(); + const QColor &renameColor + = textDocument()->fontSettings().toTextCharFormat(C_OCCURRENCES_RENAME).background().color(); - if (!selections.isEmpty()) { - const QTextEdit::ExtraSelection &selection = selections.first(); + for (const CursorPart &part : cursorParts) { + const QColor &color = part.cursor.hasSelection() ? occurrencesColor : renameColor; + d->m_snippetOverlay->addSnippetSelection(part.cursor, + color, + part.mangler, + part.variableIndex); + } - cursor = textCursor(); - if (selection.cursor.hasSelection()) { - cursor.setPosition(selection.cursor.selectionStart()); - 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()) return; QTextCursor cursor = q->textCursor(); - OverlaySelection final; - if (forward) { - 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); + q->setTextCursor(forward ? m_snippetOverlay->nextSelectionCursor(cursor) + : m_snippetOverlay->previousSelectionCursor(cursor)); } // Calculate global position for a tooltip considering the left extra area. @@ -7082,17 +7078,6 @@ void TextEditorWidgetPrivate::setExtraSelections(Id kind, const QListsetVisible(!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 { QList all; for (auto i = m_extraSelections.constBegin(); i != m_extraSelections.constEnd(); ++i) { diff --git a/src/plugins/texteditor/texteditoroverlay.cpp b/src/plugins/texteditor/texteditoroverlay.cpp index 41107e39d92..4fb7de00a25 100644 --- a/src/plugins/texteditor/texteditoroverlay.cpp +++ b/src/plugins/texteditor/texteditoroverlay.cpp @@ -33,6 +33,7 @@ #include #include +#include using namespace TextEditor; 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) { 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 { if (m_firstSelectionOriginalBegin == -1 || m_selections.isEmpty()) diff --git a/src/plugins/texteditor/texteditoroverlay.h b/src/plugins/texteditor/texteditoroverlay.h index e995f007c5d..d87a0fe7aab 100644 --- a/src/plugins/texteditor/texteditoroverlay.h +++ b/src/plugins/texteditor/texteditoroverlay.h @@ -92,14 +92,11 @@ public: inline int dropShadowWidth() const { return m_dropShadowWidth; } - bool hasCursorInSelection(const QTextCursor &cursor) const; - bool hasFirstSelectionBeginMoved() const; protected: - int selectionIndexForCursor(const QTextCursor &cursor) const; - QString selectionText(int selectionIndex) const; - QTextCursor assembleCursorForSelection(int selectionIndex) const; + QTextCursor cursorForSelection(const OverlaySelection &selection) const; + QTextCursor cursorForIndex(int selectionIndex) const; private: QPainterPath createSelectionPath(const QTextCursor &begin, const QTextCursor &end, const QRect& clip);