Commit 5f1bed66 authored by Kåre Särs's avatar Kåre Särs
Browse files

Update match range & mark handling

- Use KateSearchMatch directly in stead of going through model-data
- Only keep moving ranges and marks for the current document
- Try to update the ranges in the model before replacing and jumps to
next/previous item.
parent fd9b2f20
......@@ -175,6 +175,53 @@ KTextEditor::Range MatchModel::matchRange(const QModelIndex &matchIndex) const
return m_matchFiles[fileRow].matches[matchRow].range;
}
const QVector<KateSearchMatch> &MatchModel::fileMatches(const QUrl& fileUrl) const
{
static const QVector<KateSearchMatch> EmptyDummy;
int row = matchFileRow(fileUrl);
if (row < 0 || row >= m_matchFiles.size()) {
return EmptyDummy;
}
return m_matchFiles[row].matches;
}
void MatchModel::updateMatchRanges(const QVector<KTextEditor::MovingRange *> &ranges)
{
if (ranges.isEmpty()) {
return;
}
const QUrl &fileUrl = ranges.first()->document()->url();
// NOTE: we assume there are only ranges for one document in the provided ranges
// NOTE: we also assume the document is not deleted as we clear the ranges when the document is deleted
int fileRow = matchFileRow(fileUrl);
if (fileRow < 0 || fileRow >= m_matchFiles.size()) {
//qDebug() << "No such results" << fileRow << fileUrl;
return; // No such document in the results
}
QVector<KateSearchMatch> &matches = m_matchFiles[fileRow].matches;
if (ranges.size() != matches.size()) {
// The sizes do not match so we cannot match the ranges easily.. abort
qDebug() << ranges.size() << "!=" << matches.size();
return;
}
if (ranges.size() > 1000) {
// if we have > 1000 matches in a file it could get slow to update it all the time
return;
}
for (int i=0; i<ranges.size(); ++i) {
matches[i].range = ranges[i]->toRange();
}
QModelIndex rootFileIndex = index(fileRow, 0, createIndex(0, 0, InfoItemId));
dataChanged(index(0, 0, rootFileIndex), index(matches.count()-1, 0, rootFileIndex));
}
/** This function is used to replace a match */
bool MatchModel::replaceMatch(KTextEditor::Document *doc, const QModelIndex &matchIndex, const QRegularExpression &regExp, const QString &replaceString)
......@@ -926,7 +973,7 @@ Qt::ItemFlags MatchModel::flags(const QModelIndex &index) const
int MatchModel::rowCount(const QModelIndex &parent) const
{
if (!parent.isValid()) {
return m_matchFiles.isEmpty() && m_searchState == SearchDone ? 0 : 1;
return /*m_matchFiles.isEmpty() && m_searchState == SearchDone ? 0 :*/ 1;
}
if (parent.internalId() == InfoItemId) {
......
......@@ -16,8 +16,9 @@
#include <QRegularExpression>
#include <ktexteditor/application.h>
#include <KTextEditor/Range>
#include <KTextEditor/Cursor>
#include <KTextEditor/Range>
#include <KTextEditor/MovingRange>
/**
......@@ -72,7 +73,7 @@ public:
private:
struct MatchFile {
QUrl fileUrl;
QVector<Match> matches;
QVector<KateSearchMatch> matches;
Qt::CheckState checkState = Qt::Checked;
};
......@@ -97,6 +98,10 @@ public:
KTextEditor::Range matchRange(const QModelIndex &matchIndex) const;
const QVector<KateSearchMatch> &fileMatches(const QUrl& fileUrl) const;
void updateMatchRanges(const QVector<KTextEditor::MovingRange *> &ranges);
public Q_SLOTS:
/** This function returns the row index of the specified file.
......
......@@ -427,11 +427,14 @@ KatePluginSearchView::KatePluginSearchView(KTextEditor::Plugin *plugin, KTextEdi
connect(&m_searchDiskFiles, &SearchDiskFiles::matchesFound, this, &KatePluginSearchView::matchesFound);
connect(&m_searchDiskFiles, &SearchDiskFiles::searchDone, this, &KatePluginSearchView::searchDone);
connect(m_kateApp, &KTextEditor::Application::documentWillBeDeleted, this, &KatePluginSearchView::clearDocMarksAndRanges);
connect(m_kateApp, &KTextEditor::Application::documentWillBeDeleted, &m_searchOpenFiles, &SearchOpenFiles::cancelSearch);
connect(m_kateApp, &KTextEditor::Application::documentWillBeDeleted, this, &KatePluginSearchView::clearDocMarks);
connect(m_kateApp, &KTextEditor::Application::documentWillBeDeleted, this, [this]() {
Results *res = qobject_cast<Results *>(m_ui.resultTabWidget->currentWidget());
if (res) {
res->matchModel.cancelReplace();
}
});
m_ui.searchCombo->lineEdit()->setPlaceholderText(i18n("Find"));
// Hook into line edit context menus
......@@ -517,8 +520,7 @@ KatePluginSearchView::KatePluginSearchView(KTextEditor::Plugin *plugin, KTextEdi
KatePluginSearchView::~KatePluginSearchView()
{
clearMarks();
clearMarksAndRanges();
m_mainWindow->guiFactory()->removeClient(this);
delete m_toolView;
}
......@@ -599,17 +601,10 @@ void KatePluginSearchView::handleEsc(QEvent *e)
}
lastTimeStamp = k->timestamp();
if (!m_matchRanges.isEmpty()) {
clearMarks();
clearMarksAndRanges();
} else if (m_toolView->isVisible()) {
m_mainWindow->hideToolView(m_toolView);
}
// Remove check marks
Results *curResults = qobject_cast<Results *>(m_ui.resultTabWidget->currentWidget());
if (!curResults) {
qWarning() << "This is a bug";
return;
}
}
}
......@@ -755,80 +750,6 @@ void KatePluginSearchView::searchPlaceChanged()
}
}
void KatePluginSearchView::addMatchMark(KTextEditor::Document *doc, const QModelIndex &itemIndex)
{
if (!doc || !itemIndex.isValid()) {
return;
}
KTextEditor::MovingInterface *miface = qobject_cast<KTextEditor::MovingInterface *>(doc);
int line = itemIndex.data(MatchModel::StartLineRole).toInt();
int column = itemIndex.data(MatchModel::StartColumnRole).toInt();
int endLine = itemIndex.data(MatchModel::EndLineRole).toInt();
int endColumn = itemIndex.data(MatchModel::EndColumnRole).toInt();
bool isReplaced = itemIndex.data(MatchModel::ReplacedRole).toBool();
KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute());
if (isReplaced) {
attr->setBackground(m_replaceHighlightColor);
} else {
attr->setBackground(m_searchBackgroundColor);
}
attr->setForeground(m_foregroundColor);
KTextEditor::Range range(line, column, endLine, endColumn);
// Check that the match still matches
if (m_curResults) {
if (!isReplaced) {
// special handling for "(?=\\n)" in multi-line search
QRegularExpression tmpReg = m_curResults->regExp;
if (m_curResults->regExp.pattern().endsWith(QLatin1String("(?=\\n)"))) {
QString newPatern = tmpReg.pattern();
newPatern.replace(QStringLiteral("(?=\\n)"), QStringLiteral("$"));
tmpReg.setPattern(newPatern);
}
// Check that the match still matches ;)
if (tmpReg.match(doc->text(range)).capturedStart() != 0) {
// qDebug() << doc->text(range) << "Does not match" << m_curResults->regExp.pattern();
return;
}
} else {
if (doc->text(range) != itemIndex.data(MatchModel::ReplaceTextRole).toString()) {
///qDebug() << doc->text(range) << "Does not match" << itemIndex.data(MatchModel::ReplaceTextRole).toString();
return;
}
}
}
// Highlight the match
KTextEditor::MovingRange *mr = miface->newMovingRange(range);
mr->setAttribute(attr);
mr->setZDepth(-90000.0); // Set the z-depth to slightly worse than the selection
mr->setAttributeOnlyForViews(true);
m_matchRanges.append(mr);
// Add a match mark
#if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5, 69, 0)
KTextEditor::MarkInterfaceV2 *iface = qobject_cast<KTextEditor::MarkInterfaceV2 *>(doc);
#else
KTextEditor::MarkInterface *iface = qobject_cast<KTextEditor::MarkInterface *>(doc);
#endif
if (!iface)
return;
static const auto description = i18n("Search Match");
iface->setMarkDescription(KTextEditor::MarkInterface::markType32, description);
#if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5, 69, 0)
iface->setMarkIcon(KTextEditor::MarkInterface::markType32, QIcon());
#else
iface->setMarkPixmap(KTextEditor::MarkInterface::markType32, QIcon().pixmap(0, 0));
#endif
iface->addMark(line, KTextEditor::MarkInterface::markType32);
}
void KatePluginSearchView::matchesFound(const QUrl &url, const QVector<KateSearchMatch> &searchMatches)
{
if (!m_curResults || (sender() == &m_searchDiskFiles && m_blockDiskMatchFound)) {
......@@ -839,48 +760,6 @@ void KatePluginSearchView::matchesFound(const QUrl &url, const QVector<KateSearc
m_curResults->matches += searchMatches.size();
}
void KatePluginSearchView::clearMarks()
{
const auto docs = m_kateApp->documents();
for (KTextEditor::Document *doc : docs) {
clearDocMarks(doc);
}
qDeleteAll(m_matchRanges);
m_matchRanges.clear();
}
void KatePluginSearchView::clearDocMarks(KTextEditor::Document *doc)
{
KTextEditor::MarkInterface *iface;
iface = qobject_cast<KTextEditor::MarkInterface *>(doc);
if (iface) {
const QHash<int, KTextEditor::Mark *> marks = iface->marks();
QHashIterator<int, KTextEditor::Mark *> i(marks);
while (i.hasNext()) {
i.next();
if (i.value()->type & KTextEditor::MarkInterface::markType32) {
iface->removeMark(i.value()->line, KTextEditor::MarkInterface::markType32);
}
}
}
int i = 0;
while (i < m_matchRanges.size()) {
if (m_matchRanges.at(i)->document() == doc) {
delete m_matchRanges.at(i);
m_matchRanges.removeAt(i);
} else {
i++;
}
}
m_curResults = qobject_cast<Results *>(m_ui.resultTabWidget->currentWidget());
if (!m_curResults) {
qWarning() << "This is a bug";
return;
}
}
void KatePluginSearchView::stopClicked()
{
m_folderFilesList.cancelSearch();
......@@ -1013,7 +892,7 @@ void KatePluginSearchView::startSearch()
m_ui.expandResults->setDisabled(true);
m_ui.currentFolderButton->setDisabled(true);
clearMarks();
clearMarksAndRanges();
m_curResults->matches = 0;
m_ui.resultTabWidget->setTabText(m_ui.resultTabWidget->currentIndex(), m_ui.searchCombo->currentText());
......@@ -1178,7 +1057,7 @@ void KatePluginSearchView::startSearchWhileTyping()
m_ui.searchCombo->blockSignals(false);
// Prepare for the new search content
clearMarks();
clearMarksAndRanges();
m_resultBaseDir.clear();
m_curResults->matches = 0;
......@@ -1370,6 +1249,9 @@ void KatePluginSearchView::replaceSingleMatch()
void KatePluginSearchView::replaceChecked()
{
// Sync the current documents ranges with the model in case it has been edited
syncModelRanges();
if (m_ui.searchCombo->findText(m_ui.searchCombo->currentText()) == -1) {
m_ui.searchCombo->insertItem(1, m_ui.searchCombo->currentText());
m_ui.searchCombo->setCurrentIndex(1);
......@@ -1424,41 +1306,142 @@ void KatePluginSearchView::replaceDone()
updateMatchMarks();
}
void KatePluginSearchView::updateMatchMarks()
/** Remove all moving ranges and document marks belonging to Search & Replace */
void KatePluginSearchView::clearMarksAndRanges()
{
if (!m_mainWindow->activeView()) {
return;
// If we have a MovingRange we have a corresponding MatchMark
while (!m_matchRanges.isEmpty()) {
clearDocMarksAndRanges(m_matchRanges.first()->document());
}
}
Results *res = qobject_cast<Results *>(m_ui.resultTabWidget->currentWidget());
if (!res) {
// qDebug() << "No res";
return;
void KatePluginSearchView::clearDocMarksAndRanges(KTextEditor::Document *doc)
{
// Before removing the ranges try to update the ranges in the model in case we have document changes.
syncModelRanges();
KTextEditor::MarkInterface *iface;
iface = qobject_cast<KTextEditor::MarkInterface *>(doc);
if (iface) {
const QHash<int, KTextEditor::Mark *> marks = iface->marks();
QHashIterator<int, KTextEditor::Mark *> i(marks);
while (i.hasNext()) {
i.next();
if (i.value()->type & KTextEditor::MarkInterface::markType32) {
iface->removeMark(i.value()->line, KTextEditor::MarkInterface::markType32);
}
}
}
int i = 0;
while (i < m_matchRanges.size()) {
if (m_matchRanges.at(i)->document() == doc) {
delete m_matchRanges.at(i);
m_matchRanges.removeAt(i);
} else {
i++;
}
}
}
void KatePluginSearchView::addRangeAndMark(KTextEditor::Document *doc, const KateSearchMatch &match)
{
if (!doc) return;
bool isReplaced = !match.replaceText.isEmpty();
// Check that the match still matches
if (m_curResults) {
if (!isReplaced) {
// special handling for "(?=\\n)" in multi-line search
QRegularExpression tmpReg = m_curResults->regExp;
if (m_curResults->regExp.pattern().endsWith(QLatin1String("(?=\\n)"))) {
QString newPatern = tmpReg.pattern();
newPatern.replace(QStringLiteral("(?=\\n)"), QStringLiteral("$"));
tmpReg.setPattern(newPatern);
}
// Check that the match still matches ;)
if (tmpReg.match(doc->text(match.range)).capturedStart() != 0) {
// qDebug() << doc->text(range) << "Does not match" << m_curResults->regExp.pattern();
return;
}
} else {
if (doc->text(match.range) != match.replaceText) {
///qDebug() << doc->text(range) << "Does not match" << itemIndex.data(MatchModel::ReplaceTextRole).toString();
return;
}
}
}
// Highlight the match
KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute());
if (isReplaced) {
attr->setBackground(m_replaceHighlightColor);
} else {
attr->setBackground(m_searchBackgroundColor);
}
attr->setForeground(m_foregroundColor);
KTextEditor::MovingInterface *miface = qobject_cast<KTextEditor::MovingInterface *>(doc);
KTextEditor::MovingRange *mr = miface->newMovingRange(match.range);
mr->setAttribute(attr);
mr->setZDepth(-90000.0); // Set the z-depth to slightly worse than the selection
mr->setAttributeOnlyForViews(true);
m_matchRanges.append(mr);
// Add a match mark
#if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5, 69, 0)
KTextEditor::MarkInterfaceV2 *iface = qobject_cast<KTextEditor::MarkInterfaceV2 *>(doc);
#else
KTextEditor::MarkInterface *iface = qobject_cast<KTextEditor::MarkInterface *>(doc);
#endif
if (!iface)
return;
static const auto description = i18n("Search Match");
iface->setMarkDescription(KTextEditor::MarkInterface::markType32, description);
#if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5, 69, 0)
iface->setMarkIcon(KTextEditor::MarkInterface::markType32, QIcon());
#else
iface->setMarkPixmap(KTextEditor::MarkInterface::markType32, QIcon().pixmap(0, 0));
#endif
iface->addMark(match.range.start().line(), KTextEditor::MarkInterface::markType32);
}
void KatePluginSearchView::updateMatchMarks()
{
// We only keep marks & ranges for one document at a time so clear the rest
// This will also update the model ranges corresponding to the cleared ranges.
clearMarksAndRanges();
if (!m_mainWindow->activeView()) return;
Results *res = qobject_cast<Results *>(m_ui.resultTabWidget->currentWidget());
if (!res) return;
m_curResults = res;
// add the marks if it is not already open
KTextEditor::Document *doc = m_mainWindow->activeView()->document();
if (!doc) return;
if (!doc) {
return;
}
clearDocMarks(doc);
connect(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document *)), this, SLOT(clearMarks()), Qt::UniqueConnection);
connect(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document *)), this, SLOT(clearMarksAndRanges()), Qt::UniqueConnection);
// Re-add the highlighting on document reload
connect(doc, &KTextEditor::Document::reloaded, this, &KatePluginSearchView::updateMatchMarks, Qt::UniqueConnection);
// Add match marks for all matches in the file
QModelIndex fileIndex = res->matchModel.fileIndex(doc->url());
int fileMatches = res->matchModel.rowCount(fileIndex);
for (int i=0; i<fileMatches; ++i) {
QModelIndex matchIndex = res->matchModel.index(i, 0, fileIndex);
addMatchMark(doc, matchIndex);
const QVector<KateSearchMatch> &fileMatches = res->matchModel.fileMatches(doc->url());
for (const KateSearchMatch &match: fileMatches) {
addRangeAndMark(doc, match);
}
}
void KatePluginSearchView::syncModelRanges()
{
if (!m_curResults) return;
// NOTE: We assume there are only ranges for one document in the ranges at a time...
m_curResults->matchModel.updateMatchRanges(m_matchRanges);
}
void KatePluginSearchView::expandResults()
{
m_curResults = qobject_cast<Results *>(m_ui.resultTabWidget->currentWidget());
......@@ -1487,6 +1470,10 @@ void KatePluginSearchView::itemSelected(const QModelIndex &item)
return;
}
// Sync the current document matches with the model before jumping
// FIXME do we want to do this on every edit in stead?
syncModelRanges();
// open any children to go to the first match in the file
QModelIndex matchItem = item;
while (m_curResults->matchModel.hasChildren(matchItem)) {
......@@ -1532,6 +1519,7 @@ void KatePluginSearchView::goToNextMatch()
if (!res) {
return;
}
m_curResults = res;
m_ui.displayOptions->setChecked(false);
......@@ -1608,6 +1596,7 @@ void KatePluginSearchView::goToPreviousMatch()
if (!res) {
return;
}
m_curResults = res;
m_ui.displayOptions->setChecked(false);
......
......@@ -136,7 +136,7 @@ private Q_SLOTS:
void matchesFound(const QUrl &url, const QVector<KateSearchMatch> &searchMatches);
void addMatchMark(KTextEditor::Document *doc, const QModelIndex &matchIndex);
void addRangeAndMark(KTextEditor::Document *doc, const KateSearchMatch &match);
void searchDone();
void searchWhileTypingDone();
......@@ -144,8 +144,8 @@ private Q_SLOTS:
void itemSelected(const QModelIndex &item);
void clearMarks();
void clearDocMarks(KTextEditor::Document *doc);
void clearMarksAndRanges();
void clearDocMarksAndRanges(KTextEditor::Document *doc);
void replaceSingleMatch();
void replaceChecked();
......@@ -154,6 +154,8 @@ private Q_SLOTS:
void updateMatchMarks();
void syncModelRanges();
void resultTabChanged(int index);
void expandResults();
......@@ -172,8 +174,6 @@ protected:
bool eventFilter(QObject *obj, QEvent *ev) override;
private:
void addMatchesToRootFileItem(const QUrl &url, const QList<QTreeWidgetItem *> &matchItems);
QStringList filterFiles(const QStringList &files) const;
void updateSearchColors();
......@@ -196,7 +196,7 @@ private:
bool m_isVerticalLayout = false;
bool m_blockDiskMatchFound = false;
QString m_resultBaseDir;
QList<KTextEditor::MovingRange *> m_matchRanges;
QVector<KTextEditor::MovingRange *> m_matchRanges;
QTimer m_changeTimer;
QPointer<KTextEditor::Message> m_infoMessage;
QBrush m_searchBackgroundColor;
......
Markdown is supported
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