summaryrefslogtreecommitdiff
path: root/src/plugins/texteditor
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/texteditor')
-rw-r--r--src/plugins/texteditor/CMakeLists.txt2
-rw-r--r--src/plugins/texteditor/codeassist/assistproposalitem.cpp5
-rw-r--r--src/plugins/texteditor/codeassist/textdocumentmanipulator.cpp6
-rw-r--r--src/plugins/texteditor/codeassist/textdocumentmanipulator.h2
-rw-r--r--src/plugins/texteditor/codeassist/textdocumentmanipulatorinterface.h5
-rw-r--r--src/plugins/texteditor/codestylepool.cpp4
-rw-r--r--src/plugins/texteditor/colorscheme.cpp2
-rw-r--r--src/plugins/texteditor/fontsettings.cpp15
-rw-r--r--src/plugins/texteditor/fontsettingspage.cpp7
-rw-r--r--src/plugins/texteditor/formattexteditor.cpp23
-rw-r--r--src/plugins/texteditor/highlighter.cpp2
-rw-r--r--src/plugins/texteditor/highlightersettings.cpp15
-rw-r--r--src/plugins/texteditor/linenumberfilter.cpp1
-rw-r--r--src/plugins/texteditor/outlinefactory.cpp27
-rw-r--r--src/plugins/texteditor/outlinefactory.h4
-rw-r--r--src/plugins/texteditor/refactoringchanges.cpp16
-rw-r--r--src/plugins/texteditor/snippets/snippet.cpp375
-rw-r--r--src/plugins/texteditor/snippets/snippet.h29
-rw-r--r--src/plugins/texteditor/snippets/snippetoverlay.cpp181
-rw-r--r--src/plugins/texteditor/snippets/snippetoverlay.h78
-rw-r--r--src/plugins/texteditor/snippets/snippetparser.cpp46
-rw-r--r--src/plugins/texteditor/snippets/snippetparser.h73
-rw-r--r--src/plugins/texteditor/snippets/snippetscollection.cpp10
-rw-r--r--src/plugins/texteditor/textdocument.cpp51
-rw-r--r--src/plugins/texteditor/textdocument.h14
-rw-r--r--src/plugins/texteditor/texteditor.cpp180
-rw-r--r--src/plugins/texteditor/texteditor.h8
-rw-r--r--src/plugins/texteditor/texteditor.pro8
-rw-r--r--src/plugins/texteditor/texteditor.qbs8
-rw-r--r--src/plugins/texteditor/texteditorconstants.cpp1
-rw-r--r--src/plugins/texteditor/texteditorconstants.h1
-rw-r--r--src/plugins/texteditor/texteditoroverlay.cpp124
-rw-r--r--src/plugins/texteditor/texteditoroverlay.h19
-rw-r--r--src/plugins/texteditor/texteditorsettings.cpp2
34 files changed, 798 insertions, 546 deletions
diff --git a/src/plugins/texteditor/CMakeLists.txt b/src/plugins/texteditor/CMakeLists.txt
index 93d773ddb5..caaa4846b7 100644
--- a/src/plugins/texteditor/CMakeLists.txt
+++ b/src/plugins/texteditor/CMakeLists.txt
@@ -81,6 +81,8 @@ add_qtc_plugin(TextEditor
snippets/snippet.cpp snippets/snippet.h
snippets/snippetassistcollector.cpp snippets/snippetassistcollector.h
snippets/snippeteditor.cpp snippets/snippeteditor.h
+ snippets/snippetoverlay.cpp snippets/snippetoverlay.h
+ snippets/snippetparser.cpp snippets/snippetparser.h
snippets/snippetprovider.cpp snippets/snippetprovider.h
snippets/snippetscollection.cpp snippets/snippetscollection.h
snippets/snippetssettings.cpp snippets/snippetssettings.h
diff --git a/src/plugins/texteditor/codeassist/assistproposalitem.cpp b/src/plugins/texteditor/codeassist/assistproposalitem.cpp
index cf9cbe6e38..6542c1580a 100644
--- a/src/plugins/texteditor/codeassist/assistproposalitem.cpp
+++ b/src/plugins/texteditor/codeassist/assistproposalitem.cpp
@@ -25,8 +25,9 @@
#include "assistproposalitem.h"
-#include <texteditor/texteditor.h>
#include <texteditor/quickfix.h>
+#include <texteditor/snippets/snippet.h>
+#include <texteditor/texteditor.h>
#include <QTextCursor>
@@ -147,7 +148,7 @@ void AssistProposalItem::applyContextualContent(TextDocumentManipulatorInterface
void AssistProposalItem::applySnippet(TextDocumentManipulatorInterface &manipulator, int basePosition) const
{
- manipulator.insertCodeSnippet(basePosition, data().toString());
+ manipulator.insertCodeSnippet(basePosition, data().toString(), &Snippet::parse);
}
void AssistProposalItem::applyQuickFix(TextDocumentManipulatorInterface &manipulator, int basePosition) const
diff --git a/src/plugins/texteditor/codeassist/textdocumentmanipulator.cpp b/src/plugins/texteditor/codeassist/textdocumentmanipulator.cpp
index 0eee5793db..268d9494a0 100644
--- a/src/plugins/texteditor/codeassist/textdocumentmanipulator.cpp
+++ b/src/plugins/texteditor/codeassist/textdocumentmanipulator.cpp
@@ -85,11 +85,13 @@ bool TextDocumentManipulator::replace(int position, int length, const QString &t
return textWillBeReplaced;
}
-void TextDocumentManipulator::insertCodeSnippet(int position, const QString &text)
+void TextDocumentManipulator::insertCodeSnippet(int position,
+ const QString &text,
+ const SnippetParser &parse)
{
auto cursor = m_textEditorWidget->textCursor();
cursor.setPosition(position, QTextCursor::KeepAnchor);
- m_textEditorWidget->insertCodeSnippet(cursor, text);
+ m_textEditorWidget->insertCodeSnippet(cursor, text, parse);
}
void TextDocumentManipulator::paste()
diff --git a/src/plugins/texteditor/codeassist/textdocumentmanipulator.h b/src/plugins/texteditor/codeassist/textdocumentmanipulator.h
index 46aa99f29d..23c7186d60 100644
--- a/src/plugins/texteditor/codeassist/textdocumentmanipulator.h
+++ b/src/plugins/texteditor/codeassist/textdocumentmanipulator.h
@@ -45,7 +45,7 @@ public:
void setCursorPosition(int position) final;
void setAutoCompleteSkipPosition(int position) final;
bool replace(int position, int length, const QString &text) final;
- void insertCodeSnippet(int position, const QString &text) final;
+ void insertCodeSnippet(int position, const QString &text, const SnippetParser &parse) final;
void paste() final;
void encourageApply() final;
void autoIndent(int position, int length) override;
diff --git a/src/plugins/texteditor/codeassist/textdocumentmanipulatorinterface.h b/src/plugins/texteditor/codeassist/textdocumentmanipulatorinterface.h
index ae803260c7..88404713ef 100644
--- a/src/plugins/texteditor/codeassist/textdocumentmanipulatorinterface.h
+++ b/src/plugins/texteditor/codeassist/textdocumentmanipulatorinterface.h
@@ -25,6 +25,7 @@
#pragma once
+#include <texteditor/snippets/snippetparser.h>
#include <texteditor/texteditor_global.h>
QT_BEGIN_NAMESPACE
@@ -49,7 +50,9 @@ public:
virtual void setCursorPosition(int position) = 0;
virtual void setAutoCompleteSkipPosition(int position) = 0;
virtual bool replace(int position, int length, const QString &text) = 0;
- virtual void insertCodeSnippet(int position, const QString &text) = 0;
+ virtual void insertCodeSnippet(int position,
+ const QString &text,
+ const SnippetParser &parse) = 0;
virtual void paste() = 0;
virtual void encourageApply() = 0;
virtual void autoIndent(int position, int length) = 0;
diff --git a/src/plugins/texteditor/codestylepool.cpp b/src/plugins/texteditor/codestylepool.cpp
index 55377eceae..e92592b348 100644
--- a/src/plugins/texteditor/codestylepool.cpp
+++ b/src/plugins/texteditor/codestylepool.cpp
@@ -94,9 +94,7 @@ QByteArray CodeStylePoolPrivate::generateUniqueId(const QByteArray &id) const
static QString customCodeStylesPath()
{
- QString path = Core::ICore::userResourcePath();
- path.append(QLatin1String("/codestyles/"));
- return path;
+ return Core::ICore::userResourcePath("codestyles").toString();
}
CodeStylePool::CodeStylePool(ICodeStylePreferencesFactory *factory, QObject *parent)
diff --git a/src/plugins/texteditor/colorscheme.cpp b/src/plugins/texteditor/colorscheme.cpp
index d032f3e37b..69d8349734 100644
--- a/src/plugins/texteditor/colorscheme.cpp
+++ b/src/plugins/texteditor/colorscheme.cpp
@@ -238,7 +238,7 @@ void ColorScheme::clear()
bool ColorScheme::save(const QString &fileName, QWidget *parent) const
{
- Utils::FileSaver saver(fileName);
+ Utils::FileSaver saver(Utils::FilePath::fromString(fileName));
if (!saver.hasError()) {
QXmlStreamWriter w(saver.file());
w.setAutoFormatting(true);
diff --git a/src/plugins/texteditor/fontsettings.cpp b/src/plugins/texteditor/fontsettings.cpp
index d74a13b2cc..e35ed5c3b2 100644
--- a/src/plugins/texteditor/fontsettings.cpp
+++ b/src/plugins/texteditor/fontsettings.cpp
@@ -504,20 +504,19 @@ int FontSettings::defaultFontSize()
*/
QString FontSettings::defaultSchemeFileName(const QString &fileName)
{
- QString defaultScheme = Core::ICore::resourcePath();
- defaultScheme += QLatin1String("/styles/");
+ Utils::FilePath defaultScheme = Core::ICore::resourcePath("styles");
- if (!fileName.isEmpty() && QFile::exists(defaultScheme + fileName)) {
- defaultScheme += fileName;
+ if (!fileName.isEmpty() && (defaultScheme / fileName).exists()) {
+ defaultScheme = defaultScheme / fileName;
} else {
const QString themeScheme = Utils::creatorTheme()->defaultTextEditorColorScheme();
- if (!themeScheme.isEmpty() && QFile::exists(defaultScheme + themeScheme))
- defaultScheme += themeScheme;
+ if (!themeScheme.isEmpty() && (defaultScheme / themeScheme).exists())
+ defaultScheme = defaultScheme / themeScheme;
else
- defaultScheme += QLatin1String("default.xml");
+ defaultScheme = defaultScheme / "default.xml";
}
- return defaultScheme;
+ return defaultScheme.toString();
}
} // namespace TextEditor
diff --git a/src/plugins/texteditor/fontsettingspage.cpp b/src/plugins/texteditor/fontsettingspage.cpp
index 42c14549c4..2487e31fff 100644
--- a/src/plugins/texteditor/fontsettingspage.cpp
+++ b/src/plugins/texteditor/fontsettingspage.cpp
@@ -191,9 +191,7 @@ public:
static QString customStylesPath()
{
- QString path = Core::ICore::userResourcePath();
- path.append(QLatin1String("/styles/"));
- return path;
+ return Core::ICore::userResourcePath("styles").toString();
}
static QString createColorSchemeFileName(const QString &pattern)
@@ -558,8 +556,7 @@ void FontSettingsPageWidget::refreshColorSchemeList()
{
QList<ColorSchemeEntry> colorSchemes;
- QString resourcePath = Core::ICore::resourcePath();
- QDir styleDir(resourcePath + QLatin1String("/styles"));
+ QDir styleDir(Core::ICore::resourcePath("styles").toDir());
styleDir.setNameFilters(QStringList() << QLatin1String("*.xml"));
styleDir.setFilter(QDir::Files);
diff --git a/src/plugins/texteditor/formattexteditor.cpp b/src/plugins/texteditor/formattexteditor.cpp
index 54655de390..57b30d39f2 100644
--- a/src/plugins/texteditor/formattexteditor.cpp
+++ b/src/plugins/texteditor/formattexteditor.cpp
@@ -32,11 +32,11 @@
#include <coreplugin/messagemanager.h>
#include <utils/differ.h>
+#include <utils/qtcassert.h>
+#include <utils/qtcprocess.h>
#include <utils/runextensions.h>
-#include <utils/synchronousprocess.h>
#include <utils/temporarydirectory.h>
#include <utils/textutils.h>
-#include <utils/qtcassert.h>
#include <QFileInfo>
#include <QFutureWatcher>
@@ -81,30 +81,31 @@ static FormatTask format(FormatTask task)
if (!sourceFile.finalize()) {
task.error = QString(QT_TRANSLATE_NOOP("TextEditor",
"Cannot create temporary file \"%1\": %2."))
- .arg(sourceFile.fileName(), sourceFile.errorString());
+ .arg(sourceFile.filePath().toUserOutput(), sourceFile.errorString());
return task;
}
// Format temporary file
QStringList options = task.command.options();
- options.replaceInStrings(QLatin1String("%file"), sourceFile.fileName());
- Utils::SynchronousProcess process;
+ options.replaceInStrings(QLatin1String("%file"), sourceFile.filePath().toString());
+ SynchronousProcess process;
process.setTimeoutS(5);
- Utils::SynchronousProcessResponse response = process.runBlocking({executable, options});
- if (response.result != Utils::SynchronousProcessResponse::Finished) {
+ process.setCommand({executable, options});
+ process.runBlocking();
+ if (process.result() != QtcProcess::Finished) {
task.error = QString(QT_TRANSLATE_NOOP("TextEditor", "Failed to format: %1."))
- .arg(response.exitMessage(executable, 5));
+ .arg(process.exitMessage());
return task;
}
- const QString output = response.stdErr();
+ const QString output = process.stdErr();
if (!output.isEmpty())
task.error = executable + QLatin1String(": ") + output;
// Read text back
Utils::FileReader reader;
- if (!reader.fetch(sourceFile.fileName(), QIODevice::Text)) {
+ if (!reader.fetch(sourceFile.filePath(), QIODevice::Text)) {
task.error = QString(QT_TRANSLATE_NOOP("TextEditor", "Cannot read file \"%1\": %2."))
- .arg(sourceFile.fileName(), reader.errorString());
+ .arg(sourceFile.filePath().toUserOutput(), reader.errorString());
return task;
}
task.formattedData = QString::fromUtf8(reader.data());
diff --git a/src/plugins/texteditor/highlighter.cpp b/src/plugins/texteditor/highlighter.cpp
index 51e1a307f1..2a940b3f73 100644
--- a/src/plugins/texteditor/highlighter.cpp
+++ b/src/plugins/texteditor/highlighter.cpp
@@ -59,7 +59,7 @@ KSyntaxHighlighting::Repository *highlightRepository()
if (!repository) {
repository = new KSyntaxHighlighting::Repository();
repository->addCustomSearchPath(TextEditorSettings::highlighterSettings().definitionFilesPath());
- QDir dir(Core::ICore::resourcePath() + QLatin1String("/generic-highlighter/syntax"));
+ QDir dir(Core::ICore::resourcePath("generic-highlighter/syntax").toDir());
if (dir.exists() && dir.cdUp())
repository->addCustomSearchPath(dir.path());
}
diff --git a/src/plugins/texteditor/highlightersettings.cpp b/src/plugins/texteditor/highlightersettings.cpp
index bb224f6345..f954be4996 100644
--- a/src/plugins/texteditor/highlightersettings.cpp
+++ b/src/plugins/texteditor/highlightersettings.cpp
@@ -31,8 +31,8 @@
#include <utils/fileutils.h>
#include <utils/hostosinfo.h>
+#include <utils/qtcprocess.h>
#include <utils/stringutils.h>
-#include <utils/synchronousprocess.h>
#include <QSettings>
#include <QLatin1String>
@@ -74,10 +74,10 @@ QString findFallbackDefinitionsLocation()
for (auto &program : programs) {
Utils::SynchronousProcess process;
process.setTimeoutS(5);
- Utils::SynchronousProcessResponse response
- = process.runBlocking({program, {"--prefix"}});
- if (response.result == Utils::SynchronousProcessResponse::Finished) {
- QString output = response.stdOut();
+ process.setCommand({program, {"--prefix"}});
+ process.runBlocking();
+ if (process.result() == Utils::QtcProcess::Finished) {
+ QString output = process.stdOut();
output.remove(QLatin1Char('\n'));
for (auto &kateSyntaxPath : kateSyntaxPaths) {
dir.setPath(output + kateSyntaxPath);
@@ -88,7 +88,7 @@ QString findFallbackDefinitionsLocation()
}
}
- dir.setPath(Core::ICore::resourcePath() + QLatin1String("/generic-highlighter"));
+ dir.setPath(Core::ICore::resourcePath("generic-highlighter").toString());
if (dir.exists() && !dir.entryInfoList().isEmpty())
return dir.path();
@@ -165,8 +165,7 @@ void HighlighterSettings::assignDefaultIgnoredPatterns()
void HighlighterSettings::assignDefaultDefinitionsPath()
{
- const QString &path =
- Core::ICore::userResourcePath() + QLatin1String("/generic-highlighter");
+ const QString path = Core::ICore::userResourcePath("generic-highlighter").toString();
if (QFile::exists(path) || QDir().mkpath(path))
m_definitionFilesPath = path;
}
diff --git a/src/plugins/texteditor/linenumberfilter.cpp b/src/plugins/texteditor/linenumberfilter.cpp
index dc1f6298c2..7695bbd39d 100644
--- a/src/plugins/texteditor/linenumberfilter.cpp
+++ b/src/plugins/texteditor/linenumberfilter.cpp
@@ -48,6 +48,7 @@ LineNumberFilter::LineNumberFilter(QObject *parent)
{
setId("Line in current document");
setDisplayName(tr("Line in Current Document"));
+ setDescription(tr("Jumps to the given line in the current document."));
setPriority(High);
setDefaultShortcutString("l");
setDefaultIncludedByDefault(true);
diff --git a/src/plugins/texteditor/outlinefactory.cpp b/src/plugins/texteditor/outlinefactory.cpp
index 7c7e644b18..421479ab7a 100644
--- a/src/plugins/texteditor/outlinefactory.cpp
+++ b/src/plugins/texteditor/outlinefactory.cpp
@@ -110,27 +110,17 @@ OutlineWidgetStack::OutlineWidgetStack(OutlineFactory *factory) :
updateCurrentEditor();
}
-OutlineWidgetStack::~OutlineWidgetStack() = default;
-
-QToolButton *OutlineWidgetStack::toggleSyncButton()
-{
- return m_toggleSync;
-}
-
-QToolButton *OutlineWidgetStack::filterButton()
+QList<QToolButton *> OutlineWidgetStack::toolButtons()
{
- return m_filterButton;
+ return {m_filterButton, m_toggleSort, m_toggleSync};
}
-QToolButton *OutlineWidgetStack::sortButton()
-{
- return m_toggleSort;
-}
+OutlineWidgetStack::~OutlineWidgetStack() = default;
void OutlineWidgetStack::saveSettings(QSettings *settings, int position)
{
const QString baseKey = QStringLiteral("Outline.%1.").arg(position);
- settings->setValue(baseKey + QLatin1String("SyncWithEditor"), toggleSyncButton()->isChecked());
+ settings->setValue(baseKey + QLatin1String("SyncWithEditor"), m_toggleSync->isChecked());
for (auto iter = m_widgetSettings.constBegin(); iter != m_widgetSettings.constEnd(); ++iter)
settings->setValue(baseKey + iter.key(), iter.value());
}
@@ -154,7 +144,7 @@ void OutlineWidgetStack::restoreSettings(QSettings *settings, int position)
m_widgetSettings.insert(key, settings->value(longKey));
}
- toggleSyncButton()->setChecked(syncWithEditor);
+ m_toggleSync->setChecked(syncWithEditor);
if (auto outlineWidget = qobject_cast<IOutlineWidget*>(currentWidget()))
outlineWidget->restoreSettings(m_widgetSettings);
}
@@ -241,13 +231,8 @@ OutlineFactory::OutlineFactory()
Core::NavigationView OutlineFactory::createWidget()
{
- Core::NavigationView n;
auto placeHolder = new OutlineWidgetStack(this);
- n.widget = placeHolder;
- n.dockToolBarWidgets.append(placeHolder->filterButton());
- n.dockToolBarWidgets.append(placeHolder->sortButton());
- n.dockToolBarWidgets.append(placeHolder->toggleSyncButton());
- return n;
+ return {placeHolder, placeHolder->toolButtons()};
}
void OutlineFactory::saveSettings(Utils::QtcSettings *settings, int position, QWidget *widget)
diff --git a/src/plugins/texteditor/outlinefactory.h b/src/plugins/texteditor/outlinefactory.h
index 0f72bcf6e5..d231b700de 100644
--- a/src/plugins/texteditor/outlinefactory.h
+++ b/src/plugins/texteditor/outlinefactory.h
@@ -44,9 +44,7 @@ public:
OutlineWidgetStack(OutlineFactory *factory);
~OutlineWidgetStack() override;
- QToolButton *toggleSyncButton();
- QToolButton *filterButton();
- QToolButton *sortButton();
+ QList<QToolButton *> toolButtons();
void saveSettings(QSettings *settings, int position);
void restoreSettings(QSettings *settings, int position);
diff --git a/src/plugins/texteditor/refactoringchanges.cpp b/src/plugins/texteditor/refactoringchanges.cpp
index 9732972b01..b936066b1a 100644
--- a/src/plugins/texteditor/refactoringchanges.cpp
+++ b/src/plugins/texteditor/refactoringchanges.cpp
@@ -98,7 +98,7 @@ bool RefactoringChanges::createFile(const QString &fileName, const QString &cont
TextFileFormat format;
format.codec = EditorManager::defaultTextCodec();
QString error;
- bool saveOk = format.writeFile(fileName, document->toPlainText(), &error);
+ bool saveOk = format.writeFile(Utils::FilePath::fromString(fileName), document->toPlainText(), &error);
delete document;
if (!saveOk)
return false;
@@ -198,10 +198,12 @@ QTextDocument *RefactoringFile::mutableDocument() const
if (!m_fileName.isEmpty()) {
QString error;
QTextCodec *defaultCodec = EditorManager::defaultTextCodec();
- TextFileFormat::ReadResult result = TextFileFormat::readFile(
- m_fileName, defaultCodec,
- &fileContents, &m_textFileFormat,
- &error);
+ TextFileFormat::ReadResult result = TextFileFormat::readFile(FilePath::fromString(
+ m_fileName),
+ defaultCodec,
+ &fileContents,
+ &m_textFileFormat,
+ &error);
if (result != TextFileFormat::ReadSuccess) {
qWarning() << "Could not read " << m_fileName << ". Error: " << error;
m_textFileFormat.codec = nullptr;
@@ -372,7 +374,9 @@ bool RefactoringFile::apply()
QString error;
// suppress "file has changed" warnings if the file is open in a read-only editor
Core::FileChangeBlocker block(m_fileName);
- if (!m_textFileFormat.writeFile(m_fileName, doc->toPlainText(), &error)) {
+ if (!m_textFileFormat.writeFile(FilePath::fromString(m_fileName),
+ doc->toPlainText(),
+ &error)) {
qWarning() << "Could not apply changes to" << m_fileName << ". Error: " << error;
result = false;
}
diff --git a/src/plugins/texteditor/snippets/snippet.cpp b/src/plugins/texteditor/snippets/snippet.cpp
index 986655ba4e..38b481b16f 100644
--- a/src/plugins/texteditor/snippets/snippet.cpp
+++ b/src/plugins/texteditor/snippets/snippet.cpp
@@ -154,34 +154,10 @@ bool Snippet::isModified() const
return m_isModified;
}
-struct SnippetReplacement
-{
- QString text;
- int posDelta = 0;
-};
-
-static SnippetReplacement replacementAt(int pos, Snippet::ParsedSnippet &parsedSnippet)
+static QString tipPart(const ParsedSnippet::Part &part)
{
static const char kOpenBold[] = "<b>";
static const char kCloseBold[] = "</b>";
-
- auto mangledText = [](const QString &text, const Snippet::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) {
- Snippet::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>"},
{' ', "&nbsp;"},
{'"', "&quot;"},
@@ -189,116 +165,128 @@ QString Snippet::generateTip() const
{'<', "&lt;"},
{'>', "&gt;"}};
- ParsedSnippet parsedSnippet = Snippet::parse(m_content);
+ QString text;
+ text.reserve(part.text.size());
- QString tip("<nobr>");
- 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 &currentChar = 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);
- }
+ 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<SnippetParseError>(result))
+ return Utils::get<SnippetParseError>(result).htmlMessage();
+ QTC_ASSERT(Utils::holds_alternative<ParsedSnippet>(result), return {});
+ const ParsedSnippet parsedSnippet = Utils::get<ParsedSnippet>(result);
- QTC_CHECK(parsedSnippet.ranges.isEmpty());
+ QString tip("<nobr>");
+ for (const ParsedSnippet::Part &part : parsedSnippet.parts)
+ tip.append(tipPart(part));
return tip;
}
-Snippet::ParsedSnippet Snippet::parse(const QString &snippet)
+SnippetParseResult Snippet::parse(const QString &snippet)
{
static UppercaseMangler ucMangler;
static LowercaseMangler lcMangler;
static TitlecaseMangler tcMangler;
- Snippet::ParsedSnippet result;
+ ParsedSnippet result;
QString errorMessage;
QString preprocessedSnippet
= Utils::TemplateEngine::processText(Utils::globalMacroExpander(), snippet,
&errorMessage);
- result.success = errorMessage.isEmpty();
- if (!result.success) {
- result.text = snippet;
- result.errorMessage = errorMessage;
- return result;
- }
+ if (!errorMessage.isEmpty())
+ return {SnippetParseError{errorMessage, {}, -1}};
const int count = preprocessedSnippet.count();
- bool success = true;
- int start = -1;
NameMangler *mangler = nullptr;
- result.text.reserve(count);
+ QMap<QString, int> 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<int>());
+ }
+ 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;
}
if (mangler) {
- success = false;
- break;
+ return SnippetParseResult{SnippetParseError{tr("Expected delimiter after mangler id"),
+ preprocessedSnippet,
+ 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 {
- success = false;
- break;
+ return SnippetParseResult{
+ SnippetParseError{tr("Expected mangler id 'l'(lowercase), 'u'(uppercase), "
+ "or 'c'(titlecase) after colon"),
+ preprocessedSnippet,
+ i}};
}
++i;
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)
- success = false;
-
- result.success = success;
-
- if (!success) {
- result.ranges.clear();
- result.text = preprocessedSnippet;
+ if (inVar) {
+ return SnippetParseResult{
+ SnippetParseError{tr("Missing closing variable delimiter for:"), currentPart.text, 0}};
}
- return result;
+ if (!currentPart.text.isEmpty())
+ result.parts << currentPart;
+
+ return SnippetParseResult(result);
}
#ifdef WITH_TESTS
@@ -308,129 +296,130 @@ Snippet::ParsedSnippet 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<SnippetPart>;
+
void Internal::TextEditorPlugin::testSnippetParsing_data()
{
QTest::addColumn<QString>("input");
- QTest::addColumn<QString>("text");
QTest::addColumn<bool>("success");
- QTest::addColumn<QList<int> >("ranges_start");
- QTest::addColumn<QList<int> >("ranges_length");
- QTest::addColumn<QList<Utils::Id> >("ranges_mangler");
-
- QTest::newRow("no input")
- << QString() << QString() << true
- << (QList<int>()) << (QList<int>()) << (QList<Utils::Id>());
- 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::addColumn<Parts>("parts");
+
+ 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<int>() << 0) << (QList<int>() << 4)
- << (QList<Utils::Id>() << NOMANGLER_ID);
+ << QString("$tESt$") << true << Parts{SnippetPart("tESt", 0)};
QTest::newRow("simple identifier with lc")
- << QString::fromLatin1("$tESt:l$") << QString::fromLatin1("tESt") << true
- << (QList<int>() << 0) << (QList<int>() << 4)
- << (QList<Utils::Id>() << 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<int>() << 0) << (QList<int>() << 4)
- << (QList<Utils::Id>() << 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<int>() << 0) << (QList<int>() << 4)
- << (QList<Utils::Id>() << TCMANGLER_ID);
+ << QString("$tESt:c$") << true << Parts{SnippetPart("tESt", 0, TCMANGLER_ID)};
QTest::newRow("escaped string")
- << QString::fromLatin1("\\\\$test\\\\$") << QString::fromLatin1("$test$") << true
- << (QList<int>()) << (QList<int>())
- << (QList<Utils::Id>());
- QTest::newRow("escaped escape")
- << QString::fromLatin1("\\\\\\\\$test$\\\\\\\\") << QString::fromLatin1("\\test\\") << true
- << (QList<int>() << 1) << (QList<int>() << 4)
- << (QList<Utils::Id>() << 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<int>()) << (QList<int>())
- << (QList<Utils::Id>());
-
- 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<int>() << 11 << 16 << 26 << 40 << 52)
- << (QList<int>() << 4 << 4 << 4 << 4 << 4)
- << (QList<Utils::Id>() << NOMANGLER_ID << NOMANGLER_ID << NOMANGLER_ID << TCMANGLER_ID << NOMANGLER_ID);
-
- QTest::newRow("open identifier")
- << QString::fromLatin1("$test") << QString::fromLatin1("$test") << false
- << (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 :")
- << QString::fromLatin1("class $name$\n"
- "{\n"
- "public:\n"
- " $name$() {}\n"
- "};")
- << QString::fromLatin1("class name\n"
- "{\n"
- "public:\n"
- " name() {}\n"
- "};")
- << true
- << (QList<int>() << 6 << 25)
- << (QList<int>() << 4 << 4)
- << (QList<Utils::Id>() << 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<int>() << 6 << 23)
- << (QList<int>() << 4 << 4)
- << (QList<Utils::Id>() << NOMANGLER_ID << NOMANGLER_ID);
-
+ << QString::fromLatin1("\\\\$test\\\\\\\\$\\\\") << false << Parts();
+
+ 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("$test") << false << Parts();
+ QTest::newRow("wrong mangler") << QString("$test:X$") << false << Parts();
+
+ 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<int>, ranges_start);
- 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
-
- Snippet::ParsedSnippet result = Snippet::parse(input);
-
- QCOMPARE(result.text, text);
- QCOMPARE(result.success, success);
- QCOMPARE(result.ranges.count(), ranges_start.count());
- for (int i = 0; i < ranges_start.count(); ++i) {
- QCOMPARE(result.ranges.at(i).start, ranges_start.at(i));
- QCOMPARE(result.ranges.at(i).length, ranges_length.at(i));
- Utils::Id id = NOMANGLER_ID;
- if (result.ranges.at(i).mangler)
- id = result.ranges.at(i).mangler->id();
- QCOMPARE(id, ranges_mangler.at(i));
- }
+ QFETCH(Parts, parts);
+
+ SnippetParseResult result = Snippet::parse(input);
+ QCOMPARE(Utils::holds_alternative<ParsedSnippet>(result), success);
+ if (!success)
+ return;
+
+ ParsedSnippet snippet = Utils::get<ParsedSnippet>(result);
+
+ 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 4689f6af52..a75b3a297e 100644
--- a/src/plugins/texteditor/snippets/snippet.h
+++ b/src/plugins/texteditor/snippets/snippet.h
@@ -27,25 +27,18 @@
#include <texteditor/texteditor_global.h>
-#include <utils/id.h>
+#include "snippetparser.h"
#include <QChar>
+#include <QCoreApplication>
#include <QList>
#include <QString>
namespace TextEditor {
-class TEXTEDITOR_EXPORT NameMangler
-{
-public:
- virtual ~NameMangler();
-
- virtual Utils::Id id() const = 0;
- virtual QString mangle(const QString &unmangled) const = 0;
-};
-
class TEXTEDITOR_EXPORT Snippet
{
+ Q_DECLARE_TR_FUNCTIONS(Snippet)
public:
explicit Snippet(const QString &groupId = QString(), const QString &id = QString());
~Snippet();
@@ -76,21 +69,7 @@ public:
static const QChar kVariableDelimiter;
static const QChar kEscapeChar;
- class ParsedSnippet {
- public:
- QString text;
- QString errorMessage;
- bool success;
- struct Range {
- Range(int s, int l, NameMangler *m) : start(s), length(l), mangler(m) { }
- int start;
- int length;
- NameMangler *mangler;
- };
- QList<Range> ranges;
- };
-
- static ParsedSnippet parse(const QString &snippet);
+ static SnippetParseResult parse(const QString &snippet);
private:
bool m_isRemoved = false;
diff --git a/src/plugins/texteditor/snippets/snippetoverlay.cpp b/src/plugins/texteditor/snippets/snippetoverlay.cpp
new file mode 100644
index 0000000000..b773acfee1
--- /dev/null
+++ b/src/plugins/texteditor/snippets/snippetoverlay.cpp
@@ -0,0 +1,181 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "snippetoverlay.h"
+
+#include <utils/algorithm.h>
+#include <utils/qtcassert.h>
+
+namespace TextEditor {
+namespace Internal {
+
+void SnippetOverlay::clear()
+{
+ TextEditorOverlay::clear();
+ m_selections.clear();
+ m_variables.clear();
+}
+
+void SnippetOverlay::addSnippetSelection(const QTextCursor &cursor,
+ const QColor &color,
+ NameMangler *mangler,
+ int variableIndex)
+{
+ m_variables[variableIndex] << selections().size();
+ SnippetSelection selection;
+ selection.variableIndex = variableIndex;
+ selection.mangler = mangler;
+ m_selections << selection;
+ addOverlaySelection(cursor, color, color, TextEditorOverlay::ExpandBegin);
+}
+
+void SnippetOverlay::setFinalSelection(const QTextCursor &cursor, const QColor &color)
+{
+ m_finalSelectionIndex = selections().size();
+ addOverlaySelection(cursor, color, color, TextEditorOverlay::ExpandBegin);
+}
+
+void SnippetOverlay::updateEquivalentSelections(const QTextCursor &cursor)
+{
+ const int &currentIndex = indexForCursor(cursor);
+ if (currentIndex < 0)
+ return;
+ const QString &currentText = cursorForIndex(currentIndex).selectedText();
+ const QList<int> &equivalents = m_variables.value(m_selections[currentIndex].variableIndex);
+ for (int i : equivalents) {
+ if (i == currentIndex)
+ continue;
+ QTextCursor cursor = cursorForIndex(i);
+ const QString &equivalentText = cursor.selectedText();
+ if (currentText != equivalentText) {
+ cursor.joinPreviousEditBlock();
+ cursor.insertText(currentText);
+ cursor.endEditBlock();
+ }
+ }
+}
+
+void SnippetOverlay::mangle()
+{
+ 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::firstSelectionCursor() const
+{
+ const QList<OverlaySelection> selections = TextEditorOverlay::selections();
+ return selections.isEmpty() ? QTextCursor() : cursorForSelection(selections.first());
+}
+
+QTextCursor SnippetOverlay::nextSelectionCursor(const QTextCursor &cursor) const
+{
+ const QList<OverlaySelection> selections = TextEditorOverlay::selections();
+ if (selections.isEmpty())
+ return {};
+ const SnippetSelection &currentSelection = selectionForCursor(cursor);
+ if (currentSelection.variableIndex >= 0) {
+ int nextVariableIndex = currentSelection.variableIndex + 1;
+ if (nextVariableIndex >= m_variables.size()) {
+ if (m_finalSelectionIndex >= 0)
+ return cursorForIndex(m_finalSelectionIndex);
+ 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 &currentSelection = selectionForCursor(cursor);
+ if (currentSelection.variableIndex >= 0) {
+ int previousVariableIndex = currentSelection.variableIndex - 1;
+ if (previousVariableIndex < 0)
+ previousVariableIndex = m_variables.size() - 1;
+
+ const QList<int> &equivalents = m_variables[previousVariableIndex];
+ for (int i = equivalents.size() - 1; i >= 0; --i) {
+ if (selections.at(equivalents.at(i)).m_cursor_end.position() < cursor.position())
+ return cursorForIndex(equivalents.at(i));
+ }
+ return cursorForIndex(m_variables[previousVariableIndex].last());
+ }
+ // currently not over a variable simply select the previous available one
+ for (int i = selections.size() - 1; i >= 0; --i) {
+ if (selections.at(i).m_cursor_end.position() < cursor.position())
+ return cursorForIndex(i);
+ }
+ return cursorForSelection(selections.last());
+}
+
+bool SnippetOverlay::isFinalSelection(const QTextCursor &cursor) const
+{
+ return m_finalSelectionIndex >= 0 ? cursor == cursorForIndex(m_finalSelectionIndex) : false;
+}
+
+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
new file mode 100644
index 0000000000..9bf80524d6
--- /dev/null
+++ b/src/plugins/texteditor/snippets/snippetoverlay.h
@@ -0,0 +1,78 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "snippet.h"
+#include "texteditor/texteditoroverlay.h"
+
+#include <QTextEdit>
+
+namespace TextEditor {
+class NameMangler;
+
+namespace Internal {
+
+class SnippetOverlay : public TextEditorOverlay
+{
+public:
+ using TextEditorOverlay::TextEditorOverlay;
+
+ void clear() override;
+
+ void addSnippetSelection(const QTextCursor &cursor,
+ const QColor &color,
+ NameMangler *mangler,
+ int variableGoup);
+ void setFinalSelection(const QTextCursor &cursor, const QColor &color);
+
+ void updateEquivalentSelections(const QTextCursor &cursor);
+ void mangle();
+
+ bool hasCursorInSelection(const QTextCursor &cursor) const;
+
+ QTextCursor firstSelectionCursor() const;
+ QTextCursor nextSelectionCursor(const QTextCursor &cursor) const;
+ QTextCursor previousSelectionCursor(const QTextCursor &cursor) const;
+ bool isFinalSelection(const QTextCursor &cursor) const;
+
+private:
+ struct SnippetSelection
+ {
+ int variableIndex = -1;
+ NameMangler *mangler;
+ };
+
+ int indexForCursor(const QTextCursor &cursor) const;
+ SnippetSelection selectionForCursor(const QTextCursor &cursor) const;
+
+ QList<SnippetSelection> m_selections;
+ int m_finalSelectionIndex = -1;
+ QMap<int, QList<int>> m_variables;
+};
+
+} // namespace Internal
+} // namespace TextEditor
+
diff --git a/src/plugins/texteditor/snippets/snippetparser.cpp b/src/plugins/texteditor/snippets/snippetparser.cpp
new file mode 100644
index 0000000000..366489120d
--- /dev/null
+++ b/src/plugins/texteditor/snippets/snippetparser.cpp
@@ -0,0 +1,46 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "snippetparser.h"
+
+namespace TextEditor {
+
+QString SnippetParseError::htmlMessage() const
+{
+ QString message = errorMessage;
+ if (pos < 0 || pos > 50)
+ return message;
+ QString detail = text.left(50);
+ if (detail != text)
+ detail.append("...");
+ detail.replace(QChar::Space, "&nbsp;");
+ message.append("<br><code>" + detail + "<br>");
+ for (int i = 0; i < pos; ++i)
+ message.append("&nbsp;");
+ message.append("^</code>");
+ return message;
+}
+
+} // namespace TextEditor
diff --git a/src/plugins/texteditor/snippets/snippetparser.h b/src/plugins/texteditor/snippets/snippetparser.h
new file mode 100644
index 0000000000..67d0560d02
--- /dev/null
+++ b/src/plugins/texteditor/snippets/snippetparser.h
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <texteditor/texteditor_global.h>
+
+#include <utils/id.h>
+#include <utils/variant.h>
+
+namespace TextEditor {
+
+class TEXTEDITOR_EXPORT NameMangler
+{
+public:
+ virtual ~NameMangler();
+
+ virtual Utils::Id id() const = 0;
+ virtual QString mangle(const QString &unmangled) const = 0;
+};
+
+class TEXTEDITOR_EXPORT ParsedSnippet
+{
+public:
+ 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;
+ bool finalPart = false;
+ };
+ QList<Part> parts;
+ QList<QList<int>> variables;
+};
+
+class TEXTEDITOR_EXPORT SnippetParseError
+{
+public:
+ QString errorMessage;
+ QString text;
+ int pos;
+
+ QString htmlMessage() const;
+};
+
+using SnippetParseResult = Utils::variant<ParsedSnippet, SnippetParseError>;
+using SnippetParser = std::function<SnippetParseResult (const QString &)>;
+
+} // namespace TextEditor
diff --git a/src/plugins/texteditor/snippets/snippetscollection.cpp b/src/plugins/texteditor/snippets/snippetscollection.cpp
index afb6c28eda..e814607a12 100644
--- a/src/plugins/texteditor/snippets/snippetscollection.cpp
+++ b/src/plugins/texteditor/snippets/snippetscollection.cpp
@@ -94,11 +94,11 @@ SnippetsCollection *SnippetsCollection::instance()
}
// SnippetsCollection
-SnippetsCollection::SnippetsCollection() :
- m_userSnippetsPath(Core::ICore::userResourcePath() + QLatin1String("/snippets/")),
- m_userSnippetsFile(QLatin1String("snippets.xml"))
+SnippetsCollection::SnippetsCollection()
+ : m_userSnippetsPath(Core::ICore::userResourcePath().pathAppended("snippets/").toString())
+ , m_userSnippetsFile(QLatin1String("snippets.xml"))
{
- QDir dir(Core::ICore::resourcePath() + QLatin1String("/snippets/"));
+ QDir dir = Core::ICore::resourcePath("snippets").toDir();
dir.setNameFilters(QStringList(QLatin1String("*.xml")));
foreach (const QFileInfo &fi, dir.entryInfoList())
m_builtInSnippetsFiles.append(fi.absoluteFilePath());
@@ -306,7 +306,7 @@ bool SnippetsCollection::synchronize(QString *errorString)
QDir::toNativeSeparators(m_userSnippetsPath));
return false;
}
- Utils::FileSaver saver(m_userSnippetsPath + m_userSnippetsFile);
+ Utils::FileSaver saver(Utils::FilePath::fromString(m_userSnippetsPath + m_userSnippetsFile));
if (!saver.hasError()) {
using GroupIndexByIdConstIt = QHash<QString, int>::ConstIterator;
diff --git a/src/plugins/texteditor/textdocument.cpp b/src/plugins/texteditor/textdocument.cpp
index eb6ae5b0dd..2a52c42575 100644
--- a/src/plugins/texteditor/textdocument.cpp
+++ b/src/plugins/texteditor/textdocument.cpp
@@ -600,7 +600,7 @@ SyntaxHighlighter *TextDocument::syntaxHighlighter() const
* If \a autoSave is true, the cursor will be restored and some signals suppressed
* and we do not clean up the text file (cleanWhitespace(), ensureFinalNewLine()).
*/
-bool TextDocument::save(QString *errorString, const QString &saveFileName, bool autoSave)
+bool TextDocument::save(QString *errorString, const FilePath &filePath, bool autoSave)
{
QTextCursor cursor(&d->m_document);
@@ -638,11 +638,9 @@ bool TextDocument::save(QString *errorString, const QString &saveFileName, bool
if (d->m_storageSettings.m_addFinalNewLine)
ensureFinalNewLine(cursor);
cursor.endEditBlock();
- }
+ }
- QString fName = filePath().toString();
- if (!saveFileName.isEmpty())
- fName = saveFileName;
+ const Utils::FilePath &savePath = filePath.isEmpty() ? this->filePath() : filePath;
// check if UTF8-BOM has to be added or removed
Utils::TextFileFormat saveFormat = format();
@@ -659,7 +657,7 @@ bool TextDocument::save(QString *errorString, const QString &saveFileName, bool
}
}
- const bool ok = write(fName, saveFormat, d->m_document.toPlainText(), errorString);
+ const bool ok = write(savePath, saveFormat, d->m_document.toPlainText(), errorString);
// restore text cursor and scroll bar positions
if (autoSave && undos < d->m_document.availableUndoSteps()) {
@@ -681,9 +679,8 @@ bool TextDocument::save(QString *errorString, const QString &saveFileName, bool
return true;
// inform about the new filename
- const QFileInfo fi(fName);
d->m_document.setModified(false); // also triggers update of the block revisions
- setFilePath(Utils::FilePath::fromUserInput(fi.absoluteFilePath()));
+ setFilePath(savePath.absoluteFilePath());
emit changed();
return true;
}
@@ -715,33 +712,35 @@ bool TextDocument::isModified() const
return d->m_document.isModified();
}
-Core::IDocument::OpenResult TextDocument::open(QString *errorString, const QString &fileName,
- const QString &realFileName)
+Core::IDocument::OpenResult TextDocument::open(QString *errorString,
+ const Utils::FilePath &filePath,
+ const Utils::FilePath &realFilePath)
{
- emit aboutToOpen(fileName, realFileName);
- OpenResult success = openImpl(errorString, fileName, realFileName, /*reload =*/ false);
+ emit aboutToOpen(filePath, realFilePath);
+ OpenResult success = openImpl(errorString, filePath, realFilePath, /*reload =*/ false);
if (success == OpenResult::Success) {
- setMimeType(Utils::mimeTypeForFile(fileName).name());
+ setMimeType(Utils::mimeTypeForFile(filePath.toString()).name());
emit openFinishedSuccessfully();
}
return success;
}
-Core::IDocument::OpenResult TextDocument::openImpl(QString *errorString, const QString &fileName,
- const QString &realFileName, bool reload)
+Core::IDocument::OpenResult TextDocument::openImpl(QString *errorString,
+ const Utils::FilePath &filePath,
+ const Utils::FilePath &realFilePath,
+ bool reload)
{
QStringList content;
ReadResult readResult = Utils::TextFileFormat::ReadIOError;
- if (!fileName.isEmpty()) {
- const QFileInfo fi(fileName);
- readResult = read(realFileName, &content, errorString);
+ if (!filePath.isEmpty()) {
+ readResult = read(realFilePath, &content, errorString);
const int chunks = content.size();
// Don't call setUndoRedoEnabled(true) when reload is true and filenames are different,
// since it will reset the undo's clear index
- if (!reload || fileName == realFileName)
+ if (!reload || filePath == realFilePath)
d->m_document.setUndoRedoEnabled(reload);
QTextCursor c(&d->m_document);
@@ -775,7 +774,7 @@ Core::IDocument::OpenResult TextDocument::openImpl(QString *errorString, const Q
// Don't call setUndoRedoEnabled(true) when reload is true and filenames are different,
// since it will reset the undo's clear index
- if (!reload || fileName == realFileName)
+ if (!reload || filePath == realFilePath)
d->m_document.setUndoRedoEnabled(true);
auto documentLayout =
@@ -783,8 +782,8 @@ Core::IDocument::OpenResult TextDocument::openImpl(QString *errorString, const Q
QTC_ASSERT(documentLayout, return OpenResult::CannotHandle);
documentLayout->lastSaveRevision = d->m_autoSaveRevision = d->m_document.revision();
d->updateRevisions();
- d->m_document.setModified(fileName != realFileName);
- setFilePath(Utils::FilePath::fromUserInput(fi.absoluteFilePath()));
+ d->m_document.setModified(filePath != realFilePath);
+ setFilePath(filePath);
}
if (readResult == Utils::TextFileFormat::ReadIOError)
return OpenResult::ReadError;
@@ -800,10 +799,10 @@ bool TextDocument::reload(QString *errorString, QTextCodec *codec)
bool TextDocument::reload(QString *errorString)
{
- return reload(errorString, filePath().toString());
+ return reload(errorString, filePath());
}
-bool TextDocument::reload(QString *errorString, const QString &realFileName)
+bool TextDocument::reload(QString *errorString, const FilePath &realFilePath)
{
emit aboutToReload();
auto documentLayout =
@@ -812,8 +811,8 @@ bool TextDocument::reload(QString *errorString, const QString &realFileName)
if (documentLayout)
marks = documentLayout->documentClosing(); // removes text marks non-permanently
- const QString &file = filePath().toString();
- bool success = openImpl(errorString, file, realFileName, /*reload =*/ true) == OpenResult::Success;
+ bool success = openImpl(errorString, filePath(), realFilePath, /*reload =*/true)
+ == OpenResult::Success;
if (documentLayout)
documentLayout->documentReloaded(marks, this); // re-adds text marks
diff --git a/src/plugins/texteditor/textdocument.h b/src/plugins/texteditor/textdocument.h
index f67b55fa6e..98e6546045 100644
--- a/src/plugins/texteditor/textdocument.h
+++ b/src/plugins/texteditor/textdocument.h
@@ -112,7 +112,7 @@ public:
void removeMarkFromMarksCache(TextMark *mark);
// IDocument implementation.
- bool save(QString *errorString, const QString &fileName, bool autoSave) override;
+ bool save(QString *errorString, const Utils::FilePath &filePath, bool autoSave) override;
QByteArray contents() const override;
bool setContents(const QByteArray &contents) override;
bool shouldAutoSave() const override;
@@ -127,10 +127,10 @@ public:
void setFallbackSaveAsPath(const QString &fallbackSaveAsPath);
void setFallbackSaveAsFileName(const QString &fallbackSaveAsFileName);
- OpenResult open(QString *errorString, const QString &fileName,
- const QString &realFileName) override;
+ OpenResult open(QString *errorString, const Utils::FilePath &filePath,
+ const Utils::FilePath &realFilePath) override;
virtual bool reload(QString *errorString);
- bool reload(QString *errorString, const QString &realFileName);
+ bool reload(QString *errorString, const Utils::FilePath &realFilePath);
bool setPlainText(const QString &text);
QTextDocument *document() const;
@@ -156,7 +156,7 @@ public:
const std::function<Utils::FilePath()> &filePath);
signals:
- void aboutToOpen(const QString &fileName, const QString &realFileName);
+ void aboutToOpen(const Utils::FilePath &filePath, const Utils::FilePath &realFilePath);
void openFinishedSuccessfully();
void contentsChangedWithPosition(int position, int charsRemoved, int charsAdded);
void tabSettingsChanged();
@@ -167,7 +167,9 @@ protected:
virtual void applyFontSettings();
private:
- OpenResult openImpl(QString *errorString, const QString &fileName, const QString &realFileName,
+ OpenResult openImpl(QString *errorString,
+ const Utils::FilePath &filePath,
+ const Utils::FilePath &realFileName,
bool reload);
void cleanWhitespace(QTextCursor &cursor, bool inEntireDocument, bool cleanIndentation);
void ensureFinalNewLine(QTextCursor &cursor);
diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp
index e9f64c5b3f..033dd2a58a 100644
--- a/src/plugins/texteditor/texteditor.cpp
+++ b/src/plugins/texteditor/texteditor.cpp
@@ -42,6 +42,7 @@
#include "icodestylepreferences.h"
#include "refactoroverlay.h"
#include "snippets/snippet.h"
+#include "snippets/snippetoverlay.h"
#include "storagesettings.h"
#include "syntaxhighlighter.h"
#include "tabsettings.h"
@@ -665,7 +666,7 @@ public:
int extraAreaPreviousMarkTooltipRequestedLine = -1;
TextEditorOverlay *m_overlay = nullptr;
- TextEditorOverlay *m_snippetOverlay = nullptr;
+ SnippetOverlay *m_snippetOverlay = nullptr;
TextEditorOverlay *m_searchResultOverlay = nullptr;
bool snippetCheckCursor(const QTextCursor &cursor);
void snippetTabOrBacktab(bool forward);
@@ -1004,7 +1005,7 @@ void TextEditorWidgetPrivate::ctor(const QSharedPointer<TextDocument> &doc)
q->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
m_overlay = new TextEditorOverlay(q);
- m_snippetOverlay = new TextEditorOverlay(q);
+ m_snippetOverlay = new SnippetOverlay(q);
m_searchResultOverlay = new TextEditorOverlay(q);
m_refactorOverlay = new RefactorOverlay(q);
@@ -1214,11 +1215,7 @@ void TextEditorWidgetPrivate::print(QPrinter *printer)
QAbstractTextDocumentLayout *layout = doc->documentLayout();
layout->setPaintDevice(p.device());
-#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
- int dpiy = p.device()->logicalDpiY();
-#else
int dpiy = qRound(QGuiApplication::primaryScreen()->logicalDotsPerInchY());
-#endif
int margin = int((2/2.54)*dpiy); // 2 cm margins
QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
@@ -1467,10 +1464,10 @@ TextDocument *TextEditorWidget::textDocument() const
return d->m_document.data();
}
-void TextEditorWidget::aboutToOpen(const QString &fileName, const QString &realFileName)
+void TextEditorWidget::aboutToOpen(const Utils::FilePath &filePath, const Utils::FilePath &realFilePath)
{
- Q_UNUSED(fileName)
- Q_UNUSED(realFileName)
+ Q_UNUSED(filePath)
+ Q_UNUSED(realFilePath)
}
void TextEditorWidget::openFinishedSuccessfully()
@@ -2702,61 +2699,97 @@ void TextEditorWidget::keyPressEvent(QKeyEvent *e)
d->m_codeAssistant.process();
}
-void TextEditorWidget::insertCodeSnippet(const QTextCursor &cursor_arg, const QString &snippet)
+class PositionedPart : public ParsedSnippet::Part
+{
+public:
+ explicit PositionedPart(const ParsedSnippet::Part &part) : ParsedSnippet::Part(part) {}
+ int start;
+ int end;
+};
+
+class CursorPart : public ParsedSnippet::Part
{
- Snippet::ParsedSnippet data = Snippet::parse(snippet);
+public:
+ CursorPart(const PositionedPart &part, QTextDocument *doc)
+ : ParsedSnippet::Part(part)
+ , cursor(doc)
+ {
+ cursor.setPosition(part.start);
+ cursor.setPosition(part.end, QTextCursor::KeepAnchor);
+ }
+ QTextCursor cursor;
+};
- if (!data.success) {
- QString message = QString::fromLatin1("Cannot parse snippet \"%1\".").arg(snippet);
- if (!data.errorMessage.isEmpty())
- message += QLatin1String("\nParse error: ") + data.errorMessage;
- QMessageBox::warning(this, QLatin1String("Snippet Parse Error"), message);
+void TextEditorWidget::insertCodeSnippet(const QTextCursor &cursor_arg,
+ const QString &snippet,
+ const SnippetParser &parse)
+{
+ SnippetParseResult result = parse(snippet);
+ if (Utils::holds_alternative<SnippetParseError>(result)) {
+ const auto &error = Utils::get<SnippetParseError>(result);
+ QMessageBox::warning(this, tr("Snippet Parse Error"), error.htmlMessage());
return;
}
+ QTC_ASSERT(Utils::holds_alternative<ParsedSnippet>(result), return);
+ ParsedSnippet data = Utils::get<ParsedSnippet>(result);
QTextCursor cursor = cursor_arg;
cursor.beginEditBlock();
cursor.removeSelectedText();
const int startCursorPosition = cursor.position();
- cursor.insertText(data.text);
- QList<QTextEdit::ExtraSelection> selections;
-
- QList<NameMangler *> 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;
+ d->m_snippetOverlay->mangle();
+ d->m_snippetOverlay->clear();
+
+ QList<PositionedPart> 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<CursorPart> 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();
-
- cursor = textCursor();
- if (selection.cursor.hasSelection()) {
- cursor.setPosition(selection.cursor.selectionStart());
- cursor.setPosition(selection.cursor.selectionEnd(), QTextCursor::KeepAnchor);
+ for (const CursorPart &part : cursorParts) {
+ const QColor &color = part.cursor.hasSelection() ? occurrencesColor : renameColor;
+ if (part.finalPart) {
+ d->m_snippetOverlay->setFinalSelection(part.cursor, color);
} else {
- cursor.setPosition(selection.cursor.position());
+ d->m_snippetOverlay->addSnippetSelection(part.cursor,
+ color,
+ part.mangler,
+ part.variableIndex);
}
+ }
+
+ cursor = d->m_snippetOverlay->firstSelectionCursor();
+ if (!cursor.isNull()) {
setTextCursor(cursor);
+ if (d->m_snippetOverlay->isFinalSelection(cursor)) {
+ d->m_snippetOverlay->mangle();
+ d->m_snippetOverlay->clear();
+ d->m_snippetOverlay->setVisible(false);
+ } else {
+ d->m_snippetOverlay->setVisible(true);
+ }
}
}
@@ -3464,37 +3497,14 @@ 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);
- }
+ QTextCursor cursor = forward ? m_snippetOverlay->nextSelectionCursor(q->textCursor())
+ : m_snippetOverlay->previousSelectionCursor(q->textCursor());
q->setTextCursor(cursor);
+ if (m_snippetOverlay->isFinalSelection(cursor)) {
+ m_snippetOverlay->setVisible(false);
+ m_snippetOverlay->mangle();
+ m_snippetOverlay->clear();
+ }
}
// Calculate global position for a tooltip considering the left extra area.
@@ -6250,7 +6260,7 @@ bool TextEditorWidget::openLink(const Utils::Link &link, bool inNextSplit)
if (!link.hasValidTarget())
return false;
- if (!inNextSplit && textDocument()->filePath().toString() == link.targetFileName) {
+ if (!inNextSplit && textDocument()->filePath() == link.targetFilePath) {
EditorManager::addCurrentPositionToNavigationHistory();
gotoLine(link.targetLine, link.targetColumn, true, true);
setFocus();
@@ -6260,8 +6270,7 @@ bool TextEditorWidget::openLink(const Utils::Link &link, bool inNextSplit)
if (inNextSplit)
flags |= EditorManager::OpenInOtherSplit;
- return EditorManager::openEditorAt(link.targetFileName, link.targetLine, link.targetColumn,
- Id(), flags);
+ return EditorManager::openEditorAt(link, Id(), flags);
}
bool TextEditorWidgetPrivate::isMouseNavigationEvent(QMouseEvent *e) const
@@ -7086,17 +7095,6 @@ void TextEditorWidgetPrivate::setExtraSelections(Id kind, const QList<QTextEdit:
TextEditorOverlay::LockSize);
}
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 {
QList<QTextEdit::ExtraSelection> all;
for (auto i = m_extraSelections.constBegin(); i != m_extraSelections.constEnd(); ++i) {
@@ -8687,7 +8685,7 @@ void TextEditorLinkLabel::mouseMoveEvent(QMouseEvent *event)
return;
auto data = new DropMimeData;
- data->addFile(m_link.targetFileName, m_link.targetLine, m_link.targetColumn);
+ data->addFile(m_link.targetFilePath.toString(), m_link.targetLine, m_link.targetColumn);
auto drag = new QDrag(this);
drag->setMimeData(data);
drag->exec(Qt::CopyAction);
@@ -8699,7 +8697,9 @@ void TextEditorLinkLabel::mouseReleaseEvent(QMouseEvent *event)
if (!m_link.hasValidTarget())
return;
- EditorManager::openEditorAt(m_link.targetFileName, m_link.targetLine, m_link.targetColumn);
+ EditorManager::openEditorAt(m_link.targetFilePath.toString(),
+ m_link.targetLine,
+ m_link.targetColumn);
}
//
diff --git a/src/plugins/texteditor/texteditor.h b/src/plugins/texteditor/texteditor.h
index b48c7b3ee3..ceab8fbb53 100644
--- a/src/plugins/texteditor/texteditor.h
+++ b/src/plugins/texteditor/texteditor.h
@@ -26,10 +26,12 @@
#pragma once
#include "texteditor_global.h"
+
#include "blockrange.h"
#include "codeassist/assistenums.h"
#include "indenter.h"
#include "refactoroverlay.h"
+#include "snippets/snippetparser.h"
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h>
@@ -186,7 +188,7 @@ public:
TextDocument *textDocument() const;
QSharedPointer<TextDocument> textDocumentPtr() const;
- virtual void aboutToOpen(const QString &fileName, const QString &realFileName);
+ virtual void aboutToOpen(const Utils::FilePath &filePath, const Utils::FilePath &realFilePath);
virtual void openFinishedSuccessfully();
// IEditor
QByteArray saveState() const;
@@ -265,7 +267,9 @@ public:
void setReadOnly(bool b);
- void insertCodeSnippet(const QTextCursor &cursor, const QString &snippet);
+ void insertCodeSnippet(const QTextCursor &cursor,
+ const QString &snippet,
+ const SnippetParser &parse);
void setBlockSelection(bool on);
void setBlockSelection(int positionBlock, int positionColumn, int anchhorBlock,
diff --git a/src/plugins/texteditor/texteditor.pro b/src/plugins/texteditor/texteditor.pro
index bbf536459e..c22ed59c59 100644
--- a/src/plugins/texteditor/texteditor.pro
+++ b/src/plugins/texteditor/texteditor.pro
@@ -97,7 +97,9 @@ SOURCES += texteditorplugin.cpp \
commentssettings.cpp \
marginsettings.cpp \
formattexteditor.cpp \
- command.cpp
+ command.cpp \
+ snippets/snippetoverlay.cpp \
+ snippets/snippetparser.cpp
HEADERS += texteditorplugin.h \
plaintexteditorfactory.h \
@@ -192,7 +194,9 @@ HEADERS += texteditorplugin.h \
formattexteditor.h \
command.h \
indenter.h \
- formatter.h
+ formatter.h \
+ snippets/snippetoverlay.h \
+ snippets/snippetparser.h
FORMS += \
displaysettingspage.ui \
diff --git a/src/plugins/texteditor/texteditor.qbs b/src/plugins/texteditor/texteditor.qbs
index 5b36e0c76c..896c9d515e 100644
--- a/src/plugins/texteditor/texteditor.qbs
+++ b/src/plugins/texteditor/texteditor.qbs
@@ -205,8 +205,6 @@ Project {
name: "Snippets"
prefix: "snippets/"
files: [
- "snippetprovider.cpp",
- "snippetprovider.h",
"reuse.h",
"snippet.cpp",
"snippet.h",
@@ -214,6 +212,12 @@ Project {
"snippetassistcollector.h",
"snippeteditor.cpp",
"snippeteditor.h",
+ "snippetoverlay.cpp",
+ "snippetoverlay.h",
+ "snippetparser.cpp",
+ "snippetparser.h",
+ "snippetprovider.cpp",
+ "snippetprovider.h",
"snippetscollection.cpp",
"snippetscollection.h",
"snippetssettings.cpp",
diff --git a/src/plugins/texteditor/texteditorconstants.cpp b/src/plugins/texteditor/texteditorconstants.cpp
index 3902c13665..6063b1c5e8 100644
--- a/src/plugins/texteditor/texteditorconstants.cpp
+++ b/src/plugins/texteditor/texteditorconstants.cpp
@@ -55,6 +55,7 @@ const char *nameForStyle(TextStyle style)
case C_STRING: return "String";
case C_TYPE: return "Type";
case C_LOCAL: return "Local";
+ case C_PARAMETER: return "Parameter";
case C_GLOBAL: return "Global";
case C_FIELD: return "Field";
// TODO: Rename "Static" to "Enumeration" in next major update,
diff --git a/src/plugins/texteditor/texteditorconstants.h b/src/plugins/texteditor/texteditorconstants.h
index 22b602074d..abab979b61 100644
--- a/src/plugins/texteditor/texteditorconstants.h
+++ b/src/plugins/texteditor/texteditorconstants.h
@@ -55,6 +55,7 @@ enum TextStyle : quint8 {
C_STRING,
C_TYPE,
C_LOCAL,
+ C_PARAMETER,
C_GLOBAL,
C_FIELD,
C_ENUMERATION,
diff --git a/src/plugins/texteditor/texteditoroverlay.cpp b/src/plugins/texteditor/texteditoroverlay.cpp
index 863db175c9..4fb7de00a2 100644
--- a/src/plugins/texteditor/texteditoroverlay.cpp
+++ b/src/plugins/texteditor/texteditoroverlay.cpp
@@ -25,7 +25,6 @@
#include "texteditoroverlay.h"
#include "texteditor.h"
-#include "snippets/snippet.h"
#include <QDebug>
#include <QMap>
@@ -34,6 +33,7 @@
#include <QTextBlock>
#include <algorithm>
+#include <utils/qtcassert.h>
using namespace TextEditor;
using namespace TextEditor::Internal;
@@ -72,8 +72,6 @@ void TextEditorOverlay::clear()
return;
m_selections.clear();
m_firstSelectionOriginalBegin = -1;
- m_equivalentSelections.clear();
- m_manglers.clear();
update();
}
@@ -419,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)
@@ -446,111 +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;
-}
-
-void TextEditorOverlay::mapEquivalentSelections()
-{
- m_equivalentSelections.clear();
- m_equivalentSelections.resize(m_selections.size());
-
- QMultiMap<QString, int> all;
- for (int i = 0; i < m_selections.size(); ++i)
- all.insert(selectionText(i).toLower(), i);
-
- 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 TextEditorOverlay::updateEquivalentSelections(const QTextCursor &cursor)
-{
- int selectionIndex = selectionIndexForCursor(cursor);
- if (selectionIndex == -1)
- return;
-
- const QString &currentText = selectionText(selectionIndex);
- const QList<int> &equivalents = m_equivalentSelections.at(selectionIndex);
- foreach (int i, equivalents) {
- if (i == selectionIndex)
- continue;
- const QString &equivalentText = selectionText(i);
- if (currentText != equivalentText) {
- QTextCursor selectionCursor = assembleCursorForSelection(i);
- selectionCursor.joinPreviousEditBlock();
- selectionCursor.removeSelectedText();
- selectionCursor.insertText(currentText);
- selectionCursor.endEditBlock();
- }
- }
-}
-
-void TextEditorOverlay::setNameMangler(const QList<NameMangler *> &manglers)
-{
- m_manglers = manglers;
-}
-
-void TextEditorOverlay::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();
- }
- }
-}
-
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 374bc0ea1e..d87a0fe7aa 100644
--- a/src/plugins/texteditor/texteditoroverlay.h
+++ b/src/plugins/texteditor/texteditoroverlay.h
@@ -35,7 +35,6 @@ QT_FORWARD_DECLARE_CLASS(QWidget)
QT_FORWARD_DECLARE_CLASS(QPainterPath)
namespace TextEditor {
-class NameMangler;
class TextEditorWidget;
namespace Internal {
@@ -74,7 +73,7 @@ public:
void setAlpha(bool enabled) { m_alpha = enabled; }
- void clear();
+ virtual void clear();
enum OverlaySelectionFlags {
LockSize = 1,
@@ -93,22 +92,16 @@ public:
inline int dropShadowWidth() const { return m_dropShadowWidth; }
- bool hasCursorInSelection(const QTextCursor &cursor) const;
-
- void mapEquivalentSelections();
- void updateEquivalentSelections(const QTextCursor &cursor);
- void setNameMangler(const QList<NameMangler *> &manglers);
- void mangle();
-
bool hasFirstSelectionBeginMoved() const;
+protected:
+ QTextCursor cursorForSelection(const OverlaySelection &selection) const;
+ QTextCursor cursorForIndex(int selectionIndex) const;
+
private:
QPainterPath createSelectionPath(const QTextCursor &begin, const QTextCursor &end, const QRect& clip);
void paintSelection(QPainter *painter, const OverlaySelection &selection);
void fillSelection(QPainter *painter, const OverlaySelection &selection, const QColor &color);
- int selectionIndexForCursor(const QTextCursor &cursor) const;
- QString selectionText(int selectionIndex) const;
- QTextCursor assembleCursorForSelection(int selectionIndex) const;
bool m_visible;
bool m_alpha;
@@ -118,8 +111,6 @@ private:
TextEditorWidget *m_editor;
QWidget *m_viewport;
QList<OverlaySelection> m_selections;
- QVector<QList<int> > m_equivalentSelections;
- QList<NameMangler *> m_manglers;
};
} // namespace Internal
diff --git a/src/plugins/texteditor/texteditorsettings.cpp b/src/plugins/texteditor/texteditorsettings.cpp
index 2c72606dea..724e3efedc 100644
--- a/src/plugins/texteditor/texteditorsettings.cpp
+++ b/src/plugins/texteditor/texteditorsettings.cpp
@@ -162,6 +162,8 @@ FormatDescriptions TextEditorSettingsPrivate::initialFormats()
Qt::darkMagenta);
formatDescr.emplace_back(C_LOCAL, tr("Local"),
tr("Local variables."), QColor(9, 46, 100));
+ formatDescr.emplace_back(C_PARAMETER, tr("Parameter"),
+ tr("Function or method parameters."), QColor(9, 46, 100));
formatDescr.emplace_back(C_FIELD, tr("Field"),
tr("Class' data members."), Qt::darkRed);
formatDescr.emplace_back(C_GLOBAL, tr("Global"),