Make commands for File Name Index locator filter configurable

Change-Id: I4a110ed9184345eb6992f4fda59a76fc843b2f1e
Reviewed-by: Cristian Adam <cristian.adam@qt.io>
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Eike Ziller
2021-01-26 14:47:08 +01:00
parent 9c5b62b226
commit a9cf7685c9
4 changed files with 218 additions and 62 deletions

View File

@@ -180,39 +180,7 @@ void ILocatorFilter::restoreState(const QByteArray &state)
bool ILocatorFilter::openConfigDialog(QWidget *parent, bool &needsRefresh)
{
Q_UNUSED(needsRefresh)
QDialog dialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint);
dialog.setWindowTitle(msgConfigureDialogTitle());
auto vlayout = new QVBoxLayout(&dialog);
auto hlayout = new QHBoxLayout;
QLineEdit *shortcutEdit = new QLineEdit(shortcutString());
QCheckBox *includeByDefault = new QCheckBox(msgIncludeByDefault());
includeByDefault->setToolTip(msgIncludeByDefaultToolTip());
includeByDefault->setChecked(isIncludedByDefault());
auto prefixLabel = new QLabel(msgPrefixLabel());
prefixLabel->setToolTip(msgPrefixToolTip());
hlayout->addWidget(prefixLabel);
hlayout->addWidget(shortcutEdit);
hlayout->addWidget(includeByDefault);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok |
QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
vlayout->addLayout(hlayout);
vlayout->addStretch();
vlayout->addWidget(buttonBox);
if (dialog.exec() == QDialog::Accepted) {
setShortcutString(shortcutEdit->text().trimmed());
setIncludedByDefault(includeByDefault->isChecked());
return true;
}
return false;
return openConfigDialog(parent, nullptr);
}
/*!
@@ -456,6 +424,56 @@ void ILocatorFilter::setConfigurable(bool configurable)
m_isConfigurable = configurable;
}
/*!
Shows the standard configuration dialog with options for the prefix string
and for isIncludedByDefault(). The \a additionalWidget is added at the top.
Ownership of \a additionalWidget stays with the caller, but its parent is
reset to \c nullptr.
Returns \c false if the user canceled the dialog.
*/
bool ILocatorFilter::openConfigDialog(QWidget *parent, QWidget *additionalWidget)
{
QDialog dialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint);
dialog.setWindowTitle(msgConfigureDialogTitle());
auto vlayout = new QVBoxLayout(&dialog);
auto hlayout = new QHBoxLayout;
QLineEdit *shortcutEdit = new QLineEdit(shortcutString());
QCheckBox *includeByDefault = new QCheckBox(msgIncludeByDefault());
includeByDefault->setToolTip(msgIncludeByDefaultToolTip());
includeByDefault->setChecked(isIncludedByDefault());
auto prefixLabel = new QLabel(msgPrefixLabel());
prefixLabel->setToolTip(msgPrefixToolTip());
hlayout->addWidget(prefixLabel);
hlayout->addWidget(shortcutEdit);
hlayout->addWidget(includeByDefault);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok
| QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
if (additionalWidget)
vlayout->addWidget(additionalWidget);
vlayout->addLayout(hlayout);
vlayout->addStretch();
vlayout->addWidget(buttonBox);
bool accepted = false;
if (dialog.exec() == QDialog::Accepted) {
setShortcutString(shortcutEdit->text().trimmed());
setIncludedByDefault(includeByDefault->isChecked());
accepted = true;
}
if (additionalWidget) {
additionalWidget->setVisible(false);
additionalWidget->setParent(nullptr);
}
return accepted;
}
/*!
\fn QList<Core::LocatorFilterEntry> Core::ILocatorFilter::matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future, const QString &entry)

View File

@@ -174,6 +174,7 @@ protected:
void setPriority(Priority priority);
void setDisplayName(const QString &displayString);
void setConfigurable(bool configurable);
bool openConfigDialog(QWidget *parent, QWidget *additionalWidget);
private:
Utils::Id m_id;

View File

@@ -31,9 +31,17 @@
#include <coreplugin/reaper.h>
#include <utils/algorithm.h>
#include <utils/environment.h>
#include <utils/fancylineedit.h>
#include <utils/macroexpander.h>
#include <utils/pathchooser.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
#include <utils/stringutils.h>
#include <utils/variablechooser.h>
#include <QFormLayout>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMutex>
#include <QMutexLocker>
#include <QProcess>
@@ -171,37 +179,75 @@ void SpotlightIterator::ensureNext()
// #pragma mark -- SpotlightLocatorFilter
static QString defaultCommand()
{
if (HostOsInfo::isMacHost())
return "mdfind";
if (HostOsInfo::isWindowsHost())
return "es.exe";
return "locate";
}
static QString defaultArguments()
{
if (HostOsInfo::isMacHost())
return "\"kMDItemFSName = '*%{Query:Escaped}*'c\"";
if (HostOsInfo::isWindowsHost())
return "-n 10000 -r \"%{Query:Regex}\"";
return "-i -l 10000 -r \"%{Query:Regex}\"";
}
static QString defaultCaseSensitiveArguments()
{
if (HostOsInfo::isMacHost())
return "\"kMDItemFSName = '*%{Query:Escaped}*'\"";
if (HostOsInfo::isWindowsHost())
return "-i -n 10000 -r \"%{Query:Regex}\"";
return "-l 10000 -r \"%{Query:Regex}\"";
}
const char kShortcutStringDefault[] = "md";
const bool kIncludedByDefaultDefault = false;
const char kShortcutStringKey[] = "shortcut";
const char kIncludedByDefaultKey[] = "includeByDefault";
const char kCommandKey[] = "command";
const char kArgumentsKey[] = "arguments";
const char kCaseSensitiveKey[] = "caseSensitive";
static MacroExpander *createMacroExpander(const QString &query)
{
MacroExpander *expander = new MacroExpander;
expander->registerVariable("Query",
SpotlightLocatorFilter::tr("Locator query string."),
[query] { return query; });
expander->registerVariable("Query:Escaped",
SpotlightLocatorFilter::tr(
"Locator query string with quotes escaped with backslash."),
[query] {
QString quoted = query;
quoted.replace('\\', "\\\\")
.replace('\'', "\\\'")
.replace('\"', "\\\"");
return quoted;
});
expander->registerVariable("Query:Regex",
SpotlightLocatorFilter::tr(
"Locator query string as regular expression."),
[query] {
QString regex = query;
regex = regex.replace('*', ".*");
return regex;
});
return expander;
}
SpotlightLocatorFilter::SpotlightLocatorFilter()
{
if (HostOsInfo::isMacHost()) {
command = [](const QString &query, Qt::CaseSensitivity sensitivity) {
QString quoted = query;
quoted.replace('\\', "\\\\").replace('\'', "\\\'").replace('\"', "\\\"");
return QStringList(
{"mdfind",
QString("kMDItemFSName = '*%1*'%2")
.arg(quoted, sensitivity == Qt::CaseInsensitive ? QString("c") : QString())});
};
} else if (HostOsInfo::isLinuxHost()) {
command = [](const QString &query, Qt::CaseSensitivity sensitivity) {
QString regex = query;
regex = regex.replace('*', ".*");
return QStringList({"locate"})
+ (sensitivity == Qt::CaseInsensitive ? QStringList({"-i"}) : QStringList())
+ QStringList({"-l", "10000", "-r", regex});
};
} else if (HostOsInfo::isWindowsHost()) {
command = [](const QString &query, Qt::CaseSensitivity sensitivity) {
QString regex = query;
regex = regex.replace('*', ".*");
return QStringList({"es.exe"})
+ (sensitivity == Qt::CaseSensitive ? QStringList({"-i"}) : QStringList())
+ QStringList({"-n", "10000", "-r", regex});
};
}
setId("SpotlightFileNamesLocatorFilter");
setDisplayName(tr("File Name Index"));
setShortcutString("md");
setConfigurable(true);
reset();
}
void SpotlightLocatorFilter::prepareSearch(const QString &entry)
@@ -213,7 +259,12 @@ void SpotlightLocatorFilter::prepareSearch(const QString &entry)
// only pass the file name part to allow searches like "somepath/*foo"
int lastSlash = fp.filePath.lastIndexOf(QLatin1Char('/'));
const QString query = fp.filePath.mid(lastSlash + 1);
setFileIterator(new SpotlightIterator(command(query, caseSensitivity(fp.filePath))));
std::unique_ptr<MacroExpander> expander(createMacroExpander(query));
const QString argumentString = expander->expand(
caseSensitivity(fp.filePath) == Qt::CaseInsensitive ? m_arguments
: m_caseSensitiveArguments);
setFileIterator(
new SpotlightIterator(QStringList(m_command) + QtcProcess::splitArgs(argumentString)));
}
BaseFileFilter::prepareSearch(entry);
}
@@ -223,5 +274,81 @@ void SpotlightLocatorFilter::refresh(QFutureInterface<void> &future)
Q_UNUSED(future)
}
bool SpotlightLocatorFilter::openConfigDialog(QWidget *parent, bool &needsRefresh)
{
Q_UNUSED(needsRefresh)
QWidget configWidget;
QFormLayout *layout = new QFormLayout;
layout->setContentsMargins(0, 0, 0, 0);
layout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
configWidget.setLayout(layout);
PathChooser *commandEdit = new PathChooser;
commandEdit->setExpectedKind(PathChooser::ExistingCommand);
commandEdit->lineEdit()->setText(m_command);
FancyLineEdit *argumentsEdit = new FancyLineEdit;
argumentsEdit->setText(m_arguments);
FancyLineEdit *caseSensitiveArgumentsEdit = new FancyLineEdit;
caseSensitiveArgumentsEdit->setText(m_caseSensitiveArguments);
layout->addRow(tr("Executable:"), commandEdit);
layout->addRow(tr("Arguments:"), argumentsEdit);
layout->addRow(tr("Case sensitive:"), caseSensitiveArgumentsEdit);
std::unique_ptr<MacroExpander> expander(createMacroExpander(""));
auto chooser = new VariableChooser(&configWidget);
chooser->addMacroExpanderProvider([expander = expander.get()] { return expander; });
chooser->addSupportedWidget(argumentsEdit);
chooser->addSupportedWidget(caseSensitiveArgumentsEdit);
const bool accepted = openConfigDialog(parent, &configWidget);
if (accepted) {
m_command = commandEdit->rawFilePath().toString();
m_arguments = argumentsEdit->text();
m_caseSensitiveArguments = caseSensitiveArgumentsEdit->text();
}
return accepted;
}
QByteArray SpotlightLocatorFilter::saveState() const
{
QJsonObject obj;
if (shortcutString() != kShortcutStringDefault)
obj.insert(kShortcutStringKey, shortcutString());
if (isIncludedByDefault() != kIncludedByDefaultDefault)
obj.insert(kIncludedByDefaultKey, isIncludedByDefault());
if (m_command != defaultCommand())
obj.insert(kCommandKey, m_command);
if (m_arguments != defaultArguments())
obj.insert(kArgumentsKey, m_arguments);
if (m_caseSensitiveArguments != defaultCaseSensitiveArguments())
obj.insert(kCaseSensitiveKey, m_caseSensitiveArguments);
QJsonDocument doc;
doc.setObject(obj);
return doc.toJson(QJsonDocument::Compact);
}
void SpotlightLocatorFilter::restoreState(const QByteArray &state)
{
QJsonDocument doc = QJsonDocument::fromJson(state);
if (doc.isNull() || !doc.isObject()) {
reset();
ILocatorFilter::restoreState(state); // legacy settings from Qt Creator < 4.15
} else {
const QJsonObject obj = doc.object();
setShortcutString(obj.value(kShortcutStringKey).toString(kShortcutStringDefault));
setIncludedByDefault(obj.value(kIncludedByDefaultKey).toBool(kIncludedByDefaultDefault));
m_command = obj.value(kCommandKey).toString(defaultCommand());
m_arguments = obj.value(kArgumentsKey).toString(defaultArguments());
m_caseSensitiveArguments = obj.value(kCaseSensitiveKey)
.toString(defaultCaseSensitiveArguments());
}
}
void SpotlightLocatorFilter::reset()
{
setShortcutString(kShortcutStringDefault);
setIncludedByDefault(kIncludedByDefaultDefault);
m_command = defaultCommand();
m_arguments = defaultArguments();
m_caseSensitiveArguments = defaultCaseSensitiveArguments();
}
} // Internal
} // Core

View File

@@ -41,8 +41,18 @@ public:
void prepareSearch(const QString &entry) override;
void refresh(QFutureInterface<void> &future) override;
using ILocatorFilter::openConfigDialog;
bool openConfigDialog(QWidget *parent, bool &needsRefresh) final;
QByteArray saveState() const final;
void restoreState(const QByteArray &state) final;
private:
std::function<QStringList(QString, Qt::CaseSensitivity)> command;
void reset();
QString m_command;
QString m_arguments;
QString m_caseSensitiveArguments;
};
} // Internal