Wizards: Offer auto-completion in "New C++ Class" wizard

That is, offer existing namespaces for the class name line edit and
existing classes for the base class line edit.

Fixes: QTCREATORBUG-10066
Change-Id: I276036864626eff92997e40e4e22ab16c4f4d617
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
Christian Kandeler
2020-07-17 11:19:36 +02:00
parent 65b3d9028f
commit 45dd017853
4 changed files with 111 additions and 8 deletions

View File

@@ -35,7 +35,10 @@
"trDisplayName": "Class name:", "trDisplayName": "Class name:",
"mandatory": true, "mandatory": true,
"type": "LineEdit", "type": "LineEdit",
"data": { "validator": "(?:(?:[a-zA-Z_][a-zA-Z_0-9]*::)*[a-zA-Z_][a-zA-Z_0-9]*|)" } "data": {
"validator": "(?:(?:[a-zA-Z_][a-zA-Z_0-9]*::)*[a-zA-Z_][a-zA-Z_0-9]*|)",
"completion": "namespaces"
}
}, },
{ {
"name": "BaseCB", "name": "BaseCB",
@@ -55,7 +58,8 @@
"data": "data":
{ {
"trText": "%{BaseCB}", "trText": "%{BaseCB}",
"trDisabledText": "%{BaseCB}" "trDisabledText": "%{BaseCB}",
"completion": "classes"
} }
}, },

View File

@@ -48,6 +48,6 @@ Core::LocatorFilterEntry CppClassesFilter::filterEntryFromIndexItem(IndexItem::P
filterEntry.extraInfo = info->symbolScope().isEmpty() filterEntry.extraInfo = info->symbolScope().isEmpty()
? info->shortNativeFilePath() ? info->shortNativeFilePath()
: info->symbolScope(); : info->symbolScope();
filterEntry.fileName = info->fileName();
return filterEntry; return filterEntry;
} }

View File

@@ -29,28 +29,37 @@
#include "jsonwizard.h" #include "jsonwizard.h"
#include "jsonwizardfactory.h" #include "jsonwizardfactory.h"
#include "../project.h"
#include "../projecttree.h"
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <coreplugin/locator/ilocatorfilter.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/fancylineedit.h> #include <utils/fancylineedit.h>
#include <utils/fileutils.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/runextensions.h>
#include <utils/stringutils.h> #include <utils/stringutils.h>
#include <utils/theme/theme.h> #include <utils/theme/theme.h>
#include <QApplication>
#include <QComboBox> #include <QComboBox>
#include <QCheckBox> #include <QCheckBox>
#include <QApplication> #include <QCompleter>
#include <QDebug> #include <QDebug>
#include <QDir>
#include <QFormLayout> #include <QFormLayout>
#include <QFutureWatcher>
#include <QItemSelectionModel>
#include <QLabel> #include <QLabel>
#include <QListView>
#include <QRegularExpression>
#include <QRegularExpressionValidator> #include <QRegularExpressionValidator>
#include <QStandardItem>
#include <QTextEdit> #include <QTextEdit>
#include <QVariant> #include <QVariant>
#include <QVariantMap> #include <QVariantMap>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QListView>
#include <QStandardItem>
#include <QItemSelectionModel>
#include <QDir>
using namespace Utils; using namespace Utils;
@@ -510,6 +519,18 @@ bool LineEditField::parseData(const QVariant &data, QString *errorMessage)
} }
m_fixupExpando = consumeValue(tmp, "fixup").toString(); m_fixupExpando = consumeValue(tmp, "fixup").toString();
const QString completion = consumeValue(tmp, "completion").toString();
if (completion == "classes") {
m_completion = Completion::Classes;
} else if (completion == "namespaces") {
m_completion = Completion::Namespaces;
} else if (!completion.isEmpty()) {
*errorMessage = QCoreApplication::translate("ProjectExplorer::JsonFieldPage",
"LineEdit (\"%1\") has an invalid value \"%2\" in \"completion\".")
.arg(name(), completion);
return false;
}
warnAboutUnsupportedKeys(tmp, name(), type()); warnAboutUnsupportedKeys(tmp, name(), type());
return true; return true;
@@ -531,6 +552,7 @@ QWidget *LineEditField::createWidget(const QString &displayName, JsonFieldPage *
w->setEchoMode(m_isPassword ? QLineEdit::Password : QLineEdit::Normal); w->setEchoMode(m_isPassword ? QLineEdit::Password : QLineEdit::Normal);
QObject::connect(w, &FancyLineEdit::textEdited, [this] { setHasUserChanges(); }); QObject::connect(w, &FancyLineEdit::textEdited, [this] { setHasUserChanges(); });
setupCompletion(w);
return w; return w;
} }
@@ -598,6 +620,78 @@ QVariant LineEditField::toSettings() const
return qobject_cast<FancyLineEdit *>(widget())->text(); return qobject_cast<FancyLineEdit *>(widget())->text();
} }
void LineEditField::setupCompletion(FancyLineEdit *lineEdit)
{
using namespace Core;
using namespace Utils;
if (m_completion == Completion::None)
return;
ILocatorFilter * const classesFilter = findOrDefault(
ILocatorFilter::allLocatorFilters(),
equal(&ILocatorFilter::id, Id("Classes")));
if (!classesFilter)
return;
classesFilter->prepareSearch({});
const auto watcher = new QFutureWatcher<LocatorFilterEntry>(lineEdit);
const auto handleResults = [this, lineEdit, watcher](int firstIndex, int endIndex) {
QSet<QString> namespaces;
QStringList classes;
QString projectBaseDir;
Project * const project = ProjectTree::currentProject();
for (int i = firstIndex; i < endIndex; ++i) {
static const auto isReservedName = [](const QString &name) {
static const QRegularExpression rx1("^_[A-Z].*");
static const QRegularExpression rx2(".*::_[A-Z].*");
return name.contains("__") || rx1.match(name).hasMatch()
|| rx2.match(name).hasMatch();
};
const LocatorFilterEntry &entry = watcher->resultAt(i);
const bool hasNamespace = !entry.extraInfo.isEmpty()
&& !entry.extraInfo.startsWith('<') && !entry.extraInfo.contains("::<")
&& !isReservedName(entry.extraInfo)
&& !entry.extraInfo.startsWith('~')
&& !entry.extraInfo.contains("Anonymous:")
&& !FileUtils::isAbsolutePath(entry.extraInfo);
const bool isBaseClassCandidate = !isReservedName(entry.displayName)
&& !entry.displayName.startsWith("Anonymous:");
if (isBaseClassCandidate)
classes << entry.displayName;
if (hasNamespace) {
if (isBaseClassCandidate)
classes << (entry.extraInfo + "::" + entry.displayName);
if (m_completion == Completion::Namespaces) {
if (!project
|| entry.fileName.startsWith(project->projectDirectory().toString())) {
namespaces << entry.extraInfo;
}
}
}
}
QStringList completionList;
if (m_completion == Completion::Namespaces) {
completionList = toList(namespaces);
completionList = filtered(completionList, [&classes](const QString &ns) {
return !classes.contains(ns);
});
completionList = transform(completionList, [](const QString &ns) {
return QString(ns + "::");
});
} else {
completionList = classes;
}
completionList.sort();
lineEdit->setSpecialCompleter(new QCompleter(completionList, lineEdit));
watcher->deleteLater();
};
QObject::connect(watcher, &QFutureWatcher<LocatorFilterEntry>::resultsReadyAt, lineEdit,
handleResults);
watcher->setFuture(runAsync([classesFilter](QFutureInterface<LocatorFilterEntry> &f) {
const QList<LocatorFilterEntry> matches = classesFilter->matchesFor(f, {});
f.reportResults(QVector<LocatorFilterEntry>(matches.cbegin(), matches.cend()));
f.reportFinished();
}));
}
// -------------------------------------------------------------------- // --------------------------------------------------------------------
// TextEditFieldData: // TextEditFieldData:
// -------------------------------------------------------------------- // --------------------------------------------------------------------

View File

@@ -111,6 +111,8 @@ private:
void fromSettings(const QVariant &value) override; void fromSettings(const QVariant &value) override;
QVariant toSettings() const override; QVariant toSettings() const override;
void setupCompletion(Utils::FancyLineEdit *lineEdit);
bool m_isModified = false; bool m_isModified = false;
bool m_isValidating = false; bool m_isValidating = false;
bool m_restoreLastHistoryItem = false; bool m_restoreLastHistoryItem = false;
@@ -122,6 +124,9 @@ private:
QRegularExpression m_validatorRegExp; QRegularExpression m_validatorRegExp;
QString m_fixupExpando; QString m_fixupExpando;
mutable QString m_currentText; mutable QString m_currentText;
enum class Completion { Classes, Namespaces, None };
Completion m_completion = Completion::None;
}; };
class TextEditField : public JsonFieldPage::Field class TextEditField : public JsonFieldPage::Field