Commit 4e38cc0a authored by Markus Ebner's avatar Markus Ebner Committed by Christoph Cullmann
Browse files

Search Plugin: Implement Match Export Dialog

Implemented a very simple and bare-bones export dialog that allows generating
a copy-able text containing only the regex-matched text regions.
The dialog allows specifying a export pattern for the matched regular expression,
making use of capture groups.
The dialog is modeled after the Export Matches function of https://regex101.com/
parent a7399383
Pipeline #77532 passed with stage
in 5 minutes and 59 seconds
......@@ -18,7 +18,7 @@ target_link_libraries(
KF5::TextEditor
)
ki18n_wrap_ui(UI_SOURCES search.ui results.ui)
ki18n_wrap_ui(UI_SOURCES search.ui results.ui MatchExportDialog.ui)
target_sources(katesearchplugin PRIVATE ${UI_SOURCES})
target_sources(
......@@ -26,6 +26,7 @@ target_sources(
PRIVATE
FolderFilesList.cpp
KateSearchCommand.cpp
MatchExportDialog.cpp
MatchModel.cpp
SearchDiskFiles.cpp
htmldelegate.cpp
......
/*
SPDX-FileCopyrightText: 2021 Markus Ebner <info@ebner-markus.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "MatchExportDialog.h"
#include "plugin_search.h"
#include <QAction>
#include <QMenu>
#include <QRegularExpression>
MatchExportDialog::MatchExportDialog(QWidget *parent, MatchModel *matchModel, QRegularExpression *regExp)
: QDialog(parent)
, m_matchModel(matchModel)
, m_regExp(regExp)
{
setupUi(this);
setWindowTitle(i18n("Export Search Result Matches"));
QAction *exportPatternTextActionForInsertRegexButton =
exportPatternText->addAction(QIcon::fromTheme(QStringLiteral("code-context")), QLineEdit::TrailingPosition);
connect(exportPatternTextActionForInsertRegexButton, &QAction::triggered, this, [this]() {
QMenu menu;
QSet<QAction *> actionList;
KatePluginSearchView::addRegexHelperActionsForReplace(&actionList, &menu);
auto &&action = menu.exec(QCursor::pos());
KatePluginSearchView::regexHelperActOnAction(action, actionList, exportPatternText);
});
}
MatchExportDialog::~MatchExportDialog()
{
}
void MatchExportDialog::generateMatchExport()
{
QString exportPattern = this->exportPatternText->text();
QString exportResult;
QModelIndex rootIndex = m_matchModel->index(0, 0);
int fileCount = m_matchModel->rowCount(rootIndex);
for (int i = 0; i < fileCount; ++i) {
QModelIndex fileIndex = m_matchModel->index(i, 0, rootIndex);
int matchCount = m_matchModel->rowCount(fileIndex);
for (int j = 0; j < matchCount; ++j) {
QModelIndex matchIndex = m_matchModel->index(j, 0, fileIndex);
QRegularExpressionMatch match = m_regExp->match(matchIndex.data(MatchModel::MatchRole).toString());
exportResult += MatchModel::generateReplaceString(match, exportPattern) + QLatin1String("\n");
}
}
this->exportResultText->setPlainText(exportResult);
}
/*
SPDX-FileCopyrightText: 2021 Markus Ebner <info@ebner-markus.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#pragma once
#include <QDialog>
#include "MatchModel.h"
#include "ui_MatchExportDialog.h"
class MatchExportDialog : public QDialog, public Ui::MatchExportDialog
{
Q_OBJECT
public:
MatchExportDialog(QWidget *parent, MatchModel *matchModel, QRegularExpression *regExp);
virtual ~MatchExportDialog();
protected Q_SLOTS:
void generateMatchExport();
private:
MatchModel *m_matchModel;
QRegularExpression *m_regExp;
};
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MatchExportDialog</class>
<widget class="QWidget" name="MatchExportDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>758</width>
<height>475</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="exportPatternText">
<property name="placeholderText">
<string>Export Pattern</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Generate</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPlainTextEdit" name="exportResultText">
<property name="readOnly">
<bool>true</bool>
</property>
<property name="placeholderText">
<string>Result</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>pushButton</sender>
<signal>clicked()</signal>
<receiver>MatchExportDialog</receiver>
<slot>generateMatchExport()</slot>
<hints>
<hint type="sourcelabel">
<x>707</x>
<y>22</y>
</hint>
<hint type="destinationlabel">
<x>378</x>
<y>237</y>
</hint>
</hints>
</connection>
</connections>
</ui>
......@@ -268,32 +268,7 @@ bool MatchModel::replaceMatch(KTextEditor::Document *doc, const QModelIndex &mat
}
// Modify the replace string according to this match
QString replaceText = replaceString;
replaceText.replace(QLatin1String("\\\\"), QLatin1String("¤Search&Replace¤"));
// allow captures \0 .. \9
for (int j = qMin(9, match.lastCapturedIndex()); j >= 0; --j) {
QString captureLX = QStringLiteral("\\L\\%1").arg(j);
QString captureUX = QStringLiteral("\\U\\%1").arg(j);
QString captureX = QStringLiteral("\\%1").arg(j);
replaceText.replace(captureLX, match.captured(j).toLower());
replaceText.replace(captureUX, match.captured(j).toUpper());
replaceText.replace(captureX, match.captured(j));
}
// allow captures \{0} .. \{9999999}...
for (int j = match.lastCapturedIndex(); j >= 0; --j) {
QString captureLX = QStringLiteral("\\L\\{%1}").arg(j);
QString captureUX = QStringLiteral("\\U\\{%1}").arg(j);
QString captureX = QStringLiteral("\\{%1}").arg(j);
replaceText.replace(captureLX, match.captured(j).toLower());
replaceText.replace(captureUX, match.captured(j).toUpper());
replaceText.replace(captureX, match.captured(j));
}
replaceText.replace(QLatin1String("\\n"), QLatin1String("\n"));
replaceText.replace(QLatin1String("\\t"), QLatin1String("\t"));
replaceText.replace(QLatin1String("¤Search&Replace¤"), QLatin1String("\\"));
QString replaceText = MatchModel::generateReplaceString(match, replaceString);
// Replace the string
doc->replaceText(matchItem->range, replaceText);
......@@ -1039,6 +1014,39 @@ void MatchModel::uncheckAll()
m_infoCheckState = Qt::Unchecked;
}
QString MatchModel::generateReplaceString(const QRegularExpressionMatch &match, const QString &replaceString)
{
// Modify the replace string according to this match
QString replaceText = replaceString;
replaceText.replace(QLatin1String("\\\\"), QLatin1String("¤Search&Replace¤"));
// allow captures \0 .. \9
for (int j = qMin(9, match.lastCapturedIndex()); j >= 0; --j) {
QString captureLX = QStringLiteral("\\L\\%1").arg(j);
QString captureUX = QStringLiteral("\\U\\%1").arg(j);
QString captureX = QStringLiteral("\\%1").arg(j);
replaceText.replace(captureLX, match.captured(j).toLower());
replaceText.replace(captureUX, match.captured(j).toUpper());
replaceText.replace(captureX, match.captured(j));
}
// allow captures \{0} .. \{9999999}...
for (int j = match.lastCapturedIndex(); j >= 0; --j) {
QString captureLX = QStringLiteral("\\L\\{%1}").arg(j);
QString captureUX = QStringLiteral("\\U\\{%1}").arg(j);
QString captureX = QStringLiteral("\\{%1}").arg(j);
replaceText.replace(captureLX, match.captured(j).toLower());
replaceText.replace(captureUX, match.captured(j).toUpper());
replaceText.replace(captureX, match.captured(j));
}
replaceText.replace(QLatin1String("\\n"), QLatin1String("\n"));
replaceText.replace(QLatin1String("\\t"), QLatin1String("\t"));
replaceText.replace(QLatin1String("¤Search&Replace¤"), QLatin1String("\\"));
return replaceText;
}
Qt::ItemFlags MatchModel::flags(const QModelIndex &index) const
{
if (!index.isValid()) {
......
......@@ -99,6 +99,8 @@ public:
void uncheckAll();
static QString generateReplaceString(const QRegularExpressionMatch &match, const QString &replaceString);
public Q_SLOTS:
/** This function returns the row index of the specified file.
......
......@@ -6,6 +6,7 @@
#include "plugin_search.h"
#include "KateSearchCommand.h"
#include "MatchExportDialog.h"
#include "htmldelegate.h"
#include <ktexteditor/configinterface.h>
......@@ -130,7 +131,7 @@ static void addRegexHelperActionsForSearch(QSet<QAction *> *actionList, QMenu *m
/**
* adds items and separators for regex in "replace" field
*/
static void addRegexHelperActionsForReplace(QSet<QAction *> *actionList, QMenu *menu)
void KatePluginSearchView::addRegexHelperActionsForReplace(QSet<QAction *> *actionList, QMenu *menu)
{
QSet<QAction *> &actionPointers = *actionList;
QString emptyQSTring;
......@@ -149,7 +150,7 @@ static void addRegexHelperActionsForReplace(QSet<QAction *> *actionList, QMenu *
/**
* inserts text and sets cursor position
*/
static void regexHelperActOnAction(QAction *resultAction, const QSet<QAction *> &actionList, QLineEdit *lineEdit)
void KatePluginSearchView::regexHelperActOnAction(QAction *resultAction, const QSet<QAction *> &actionList, QLineEdit *lineEdit)
{
if (resultAction && actionList.contains(resultAction)) {
const int cursorPos = lineEdit->cursorPosition();
......@@ -2093,6 +2094,11 @@ void KatePluginSearchView::customResMenuRequested(const QPoint &pos)
QAction *copyExpanded = new QAction(i18n("Copy expanded"), tree);
menu->addAction(copyExpanded);
QAction *exportMatches = new QAction(i18n("Export matches"), tree);
if (m_curResults->useRegExp) {
menu->addAction(exportMatches);
}
menu->popup(tree->viewport()->mapToGlobal(pos));
connect(copyAll, &QAction::triggered, this, [this](bool) {
......@@ -2101,6 +2107,9 @@ void KatePluginSearchView::customResMenuRequested(const QPoint &pos)
connect(copyExpanded, &QAction::triggered, this, [this](bool) {
copySearchToClipboard(AllExpanded);
});
connect(exportMatches, &QAction::triggered, this, [this](bool) {
showExportMatchesDialog();
});
}
void KatePluginSearchView::copySearchToClipboard(CopyResultType copyType)
......@@ -2135,6 +2144,16 @@ void KatePluginSearchView::copySearchToClipboard(CopyResultType copyType)
QApplication::clipboard()->setText(clipboard);
}
void KatePluginSearchView::showExportMatchesDialog()
{
Results *res = qobject_cast<Results *>(m_ui.resultTabWidget->currentWidget());
if (!res) {
return;
}
MatchExportDialog matchExportDialog(m_mainWindow->window(), &m_curResults->matchModel, &m_curResults->regExp);
matchExportDialog.exec();
}
bool KatePluginSearchView::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::ShortcutOverride) {
......
......@@ -98,6 +98,9 @@ public:
void readSessionConfig(const KConfigGroup &config) override;
void writeSessionConfig(KConfigGroup &config) override;
static void addRegexHelperActionsForReplace(QSet<QAction *> *actionList, QMenu *menu);
static void regexHelperActOnAction(QAction *resultAction, const QSet<QAction *> &actionList, QLineEdit *lineEdit);
public Q_SLOTS:
void stopClicked();
void startSearch();
......@@ -168,6 +171,7 @@ private Q_SLOTS:
void slotProjectFileNameChanged();
void copySearchToClipboard(CopyResultType type);
void showExportMatchesDialog();
void customResMenuRequested(const QPoint &pos);
Q_SIGNALS:
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment