Commit 3ff6e15e authored by Waqar Ahmed's avatar Waqar Ahmed
Browse files

Fix search for unsaved files

Search for unsaved files doesn't work because we don't have QUrl's for
them. Instead use KTextEditor::Document pointer for such files.

Included is a small fix that I noticed while working on this. If you try
to replace checked after searching, the file that you have opened doesn't
get highlights as it should.

Possible further improvements:
- Ask main window for the "tab title" of the doc so that we can use
  that in the search tree view. Otherwise its kind of really ambigous

BUG: 434287

Signed-off-by: Waqar Ahmed's avatarWaqar Ahmed <waqar.17a@gmail.com>
parent 596f85c8
Pipeline #121674 passed with stage
in 3 minutes and 12 seconds
//
// Description: Widget for configuring build targets
//
// SPDX-FileCopyrightText: 2011-2014 Kåre Särs <kare.sars@iki.fi>
//
// SPDX-License-Identifier: LGPL-2.0-only
#ifndef HIGHLIGHTER_H
#define HIGHLIGHTER_H
#include <QSyntaxHighlighter>
class Highlighter : public QSyntaxHighlighter
{
public:
using QSyntaxHighlighter::QSyntaxHighlighter;
private:
};
#endif
......@@ -85,7 +85,14 @@ void MatchModel::setSearchState(MatchModel::SearchState searchState)
return l.fileUrl < r.fileUrl;
});
for (int i = 0; i < m_matchFiles.size(); ++i) {
m_matchFileIndexHash[m_matchFiles[i].fileUrl] = i;
if (m_matchFiles.at(i).fileUrl.isValid()) {
m_matchFileIndexHash[m_matchFiles[i].fileUrl] = i;
} else if (m_matchFiles.at(i).doc) {
m_matchUnsavedFileIndexHash[m_matchFiles.at(i).doc] = i;
} else {
qWarning() << "Trying to setSearchState for invalid doc";
Q_UNREACHABLE();
}
}
endResetModel();
}
......@@ -112,19 +119,24 @@ void MatchModel::clear()
beginResetModel();
m_matchFiles.clear();
m_matchFileIndexHash.clear();
m_matchUnsavedFileIndexHash.clear();
m_lastMatchUrl.clear();
endResetModel();
}
/** This function returns the row index of the specified file.
* If the file does not exist in the model, the file will be added to the model. */
int MatchModel::matchFileRow(const QUrl &fileUrl) const
int MatchModel::matchFileRow(const QUrl &fileUrl, KTextEditor::Document *doc) const
{
return m_matchFileIndexHash.value(fileUrl, -1);
const int ret = m_matchFileIndexHash.value(fileUrl, -1);
if (ret != -1) {
return ret;
}
return m_matchUnsavedFileIndexHash.value(doc, -1);
}
/** This function is used to add a match to a new file */
void MatchModel::addMatches(const QUrl &fileUrl, const QVector<KateSearchMatch> &searchMatches)
void MatchModel::addMatches(const QUrl &fileUrl, const QVector<KateSearchMatch> &searchMatches, KTextEditor::Document *doc)
{
m_lastMatchUrl = fileUrl;
m_searchState = Searching;
......@@ -142,14 +154,24 @@ void MatchModel::addMatches(const QUrl &fileUrl, const QVector<KateSearchMatch>
endInsertRows();
}
int fileIndex = matchFileRow(fileUrl);
int fileIndex = matchFileRow(fileUrl, doc);
if (fileIndex == -1) {
fileIndex = m_matchFiles.size();
m_matchFileIndexHash.insert(fileUrl, fileIndex);
if (fileUrl.isValid()) {
m_matchFileIndexHash.insert(fileUrl, fileIndex);
} else if (doc) {
m_matchUnsavedFileIndexHash.insert(doc, fileIndex);
} else {
qWarning() << "Trying to insert invalid match, url is invalid, doc is null";
Q_UNREACHABLE();
}
beginInsertRows(createIndex(0, 0, InfoItemId), fileIndex, fileIndex);
// We are always starting the insert at the end, so we could optimize by delaying/grouping the signaling of the updates
m_matchFiles.append(MatchFile());
m_matchFiles[fileIndex].fileUrl = fileUrl;
m_matchFiles[fileIndex].doc = doc;
endInsertRows();
}
......@@ -190,12 +212,12 @@ KTextEditor::Range MatchModel::matchRange(const QModelIndex &matchIndex) const
return m_matchFiles[fileRow].matches[matchRow].range;
}
const QVector<KateSearchMatch> &MatchModel::fileMatches(const QUrl &fileUrl) const
const QVector<KateSearchMatch> &MatchModel::fileMatches(KTextEditor::Document *doc) const
{
static const QVector<KateSearchMatch> EmptyDummy;
int row = matchFileRow(fileUrl);
int row = matchFileRow(doc->url(), doc);
if (row < 0 || row >= m_matchFiles.size()) {
qWarning() << "fileMatches, matches requested for invalid doc: " << doc << doc->url();
static const QVector<KateSearchMatch> EmptyDummy;
return EmptyDummy;
}
return m_matchFiles[row].matches;
......@@ -207,11 +229,12 @@ void MatchModel::updateMatchRanges(const QVector<KTextEditor::MovingRange *> &ra
return;
}
const QUrl &fileUrl = ranges.first()->document()->url();
auto *doc = ranges.first()->document();
const QUrl fileUrl = doc->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);
int fileRow = matchFileRow(fileUrl, doc);
if (fileRow < 0 || fileRow >= m_matchFiles.size()) {
// qDebug() << "No such results" << fileRow << fileUrl;
return; // No such document in the results
......@@ -221,7 +244,7 @@ void MatchModel::updateMatchRanges(const QVector<KTextEditor::MovingRange *> &ra
if (ranges.size() != matches.size()) {
// The sizes do not match so we cannot match the ranges easily.. abort
qDebug() << ranges.size() << "!=" << matches.size();
qDebug() << __func__ << ranges.size() << "!=" << matches.size() << fileUrl << doc;
return;
}
......@@ -357,21 +380,28 @@ void MatchModel::doReplaceNextMatch()
}
KTextEditor::Document *doc;
doc = m_docManager->findUrl(matchFile.fileUrl);
if (!doc) {
doc = m_docManager->openUrl(matchFile.fileUrl);
if (matchFile.fileUrl.isValid()) {
doc = m_docManager->findUrl(matchFile.fileUrl);
if (!doc) {
doc = m_docManager->openUrl(matchFile.fileUrl);
}
} else {
doc = matchFile.doc;
}
if (!doc) {
qDebug() << "Failed to open the document" << matchFile.fileUrl;
qDebug() << "Failed to open the document" << matchFile.fileUrl << doc;
m_replaceFile++;
QTimer::singleShot(0, this, &MatchModel::doReplaceNextMatch);
return;
}
if (doc->url() != matchFile.fileUrl) {
if (matchFile.fileUrl.isValid() && doc->url() != matchFile.fileUrl) {
qDebug() << "url differences" << matchFile.fileUrl << doc->url();
matchFile.fileUrl = doc->url();
} else if (matchFile.doc != doc) {
qDebug() << "doc differences" << matchFile.fileUrl << doc->url();
matchFile.doc = doc;
}
auto &matches = matchFile.matches;
......@@ -690,9 +720,9 @@ bool MatchModel::isMatch(const QModelIndex &itemIndex) const
return true;
}
QModelIndex MatchModel::fileIndex(const QUrl &url) const
QModelIndex MatchModel::fileIndex(const QUrl &url, KTextEditor::Document *doc) const
{
int row = matchFileRow(url);
int row = matchFileRow(url, doc);
if (row == -1) {
return QModelIndex();
}
......@@ -717,9 +747,9 @@ QModelIndex MatchModel::lastMatch() const
return createIndex(matchFile.matches.size() - 1, 0, m_matchFiles.size() - 1);
}
QModelIndex MatchModel::firstFileMatch(const QUrl &url) const
QModelIndex MatchModel::firstFileMatch(KTextEditor::Document *doc) const
{
int row = matchFileRow(url);
int row = matchFileRow(doc->url(), doc);
if (row == -1) {
return QModelIndex();
}
......@@ -728,9 +758,9 @@ QModelIndex MatchModel::firstFileMatch(const QUrl &url) const
return createIndex(0, 0, row);
}
QModelIndex MatchModel::closestMatchAfter(const QUrl &url, const KTextEditor::Cursor &cursor) const
QModelIndex MatchModel::closestMatchAfter(KTextEditor::Document *doc, const KTextEditor::Cursor &cursor) const
{
int row = matchFileRow(url);
int row = matchFileRow(doc->url(), doc);
if (row < 0) {
return QModelIndex();
}
......@@ -754,9 +784,9 @@ QModelIndex MatchModel::closestMatchAfter(const QUrl &url, const KTextEditor::Cu
return createIndex(i, 0, row);
}
QModelIndex MatchModel::closestMatchBefore(const QUrl &url, const KTextEditor::Cursor &cursor) const
QModelIndex MatchModel::closestMatchBefore(KTextEditor::Document *doc, const KTextEditor::Cursor &cursor) const
{
int row = matchFileRow(url);
int row = matchFileRow(doc->url(), doc);
if (row < 0) {
return QModelIndex();
}
......@@ -882,6 +912,8 @@ QVariant MatchModel::data(const QModelIndex &index, int role) const
return match.checked ? Qt::Checked : Qt::Unchecked;
case FileUrlRole:
return m_matchFiles[fileRow].fileUrl;
case DocumentRole:
return QVariant::fromValue(m_matchFiles[fileRow].doc.data());
case StartLineRole:
return match.range.start().line();
case StartColumnRole:
......
......@@ -9,6 +9,7 @@
#include <QAbstractItemModel>
#include <QBrush>
#include <QPointer>
#include <QRegularExpression>
#include <QString>
#include <QTimer>
......@@ -46,6 +47,7 @@ public:
enum MatchDataRoles {
FileUrlRole = Qt::UserRole,
DocumentRole,
FileNameRole,
StartLineRole,
StartColumnRole,
......@@ -69,6 +71,7 @@ private:
struct MatchFile {
QUrl fileUrl;
QVector<KateSearchMatch> matches;
QPointer<KTextEditor::Document> doc;
Qt::CheckState checkState = Qt::Checked;
};
......@@ -91,7 +94,12 @@ public:
/** This function clears all matches in all files */
void clear();
const QVector<KateSearchMatch> &fileMatches(const QUrl &fileUrl) const;
bool isEmpty() const
{
return m_matchFiles.isEmpty();
}
const QVector<KateSearchMatch> &fileMatches(KTextEditor::Document *doc) const;
void updateMatchRanges(const QVector<KTextEditor::MovingRange *> &ranges);
......@@ -103,10 +111,11 @@ public Q_SLOTS:
/** This function returns the row index of the specified file.
* If the file does not exist in the model, the file will be added to the model. */
int matchFileRow(const QUrl &fileUrl) const;
int matchFileRow(const QUrl &fileUrl, KTextEditor::Document *doc) const;
/** This function is used to add a new file */
void addMatches(const QUrl &fileUrl, const QVector<KateSearchMatch> &searchMatches);
/** @p doc may be null if we are searching disk files for instance */
void addMatches(const QUrl &fileUrl, const QVector<KateSearchMatch> &searchMatches, KTextEditor::Document *doc);
/** This function is used to set the last added file to the search list.
* This is done to update the match tree when we generate the search file list. */
......@@ -125,12 +134,12 @@ Q_SIGNALS:
// QModelIndex api. Use with care if you are accessing it directly or access through 'Results' instead
public:
bool isMatch(const QModelIndex &itemIndex) const;
QModelIndex fileIndex(const QUrl &url) const;
QModelIndex fileIndex(const QUrl &url, KTextEditor::Document *doc) const;
QModelIndex firstMatch() const;
QModelIndex lastMatch() const;
QModelIndex firstFileMatch(const QUrl &url) const;
QModelIndex closestMatchAfter(const QUrl &url, const KTextEditor::Cursor &cursor) const;
QModelIndex closestMatchBefore(const QUrl &url, const KTextEditor::Cursor &cursor) const;
QModelIndex firstFileMatch(KTextEditor::Document *doc) const;
QModelIndex closestMatchAfter(KTextEditor::Document *doc, const KTextEditor::Cursor &cursor) const;
QModelIndex closestMatchBefore(KTextEditor::Document *doc, const KTextEditor::Cursor &cursor) const;
QModelIndex nextMatch(const QModelIndex &itemIndex) const;
QModelIndex prevMatch(const QModelIndex &itemIndex) const;
......@@ -168,6 +177,8 @@ private:
QVector<MatchFile> m_matchFiles;
QHash<QUrl, int> m_matchFileIndexHash;
// for unsaved documents with no url
QHash<KTextEditor::Document *, int> m_matchUnsavedFileIndexHash;
QString m_searchBackgroundColor;
QString m_foregroundColor;
QString m_replaceHighlightColor;
......
......@@ -73,20 +73,25 @@ MatchProxyModel *Results::model() const
return static_cast<MatchProxyModel *>(treeView->model());
}
bool Results::isEmpty() const
{
return matchModel.isEmpty();
}
bool Results::isMatch(const QModelIndex &index) const
{
Q_ASSERT(index.model() == model());
return matchModel.isMatch(model()->mapToSource(index));
}
QModelIndex Results::firstFileMatch(const QUrl &url) const
QModelIndex Results::firstFileMatch(KTextEditor::Document *doc) const
{
return model()->mapFromSource(matchModel.firstFileMatch(url));
return model()->mapFromSource(matchModel.firstFileMatch(doc));
}
QModelIndex Results::closestMatchAfter(const QUrl &url, const KTextEditor::Cursor &cursor) const
QModelIndex Results::closestMatchAfter(KTextEditor::Document *doc, const KTextEditor::Cursor &cursor) const
{
return model()->mapFromSource(matchModel.closestMatchAfter(url, cursor));
return model()->mapFromSource(matchModel.closestMatchAfter(doc, cursor));
}
QModelIndex Results::firstMatch() const
......@@ -106,9 +111,9 @@ QModelIndex Results::prevMatch(const QModelIndex &itemIndex) const
return model()->mapFromSource(matchModel.prevMatch(model()->mapToSource(itemIndex)));
}
QModelIndex Results::closestMatchBefore(const QUrl &url, const KTextEditor::Cursor &cursor) const
QModelIndex Results::closestMatchBefore(KTextEditor::Document *doc, const KTextEditor::Cursor &cursor) const
{
return model()->mapFromSource(matchModel.closestMatchBefore(url, cursor));
return model()->mapFromSource(matchModel.closestMatchBefore(doc, cursor));
}
QModelIndex Results::lastMatch() const
......
......@@ -20,16 +20,17 @@ public:
QString treeRootText;
MatchModel matchModel;
bool isEmpty() const;
void setFilterLineVisible(bool visible);
void expandRoot();
bool isMatch(const QModelIndex &index) const;
class MatchProxyModel *model() const;
QModelIndex firstFileMatch(const QUrl &url) const;
QModelIndex closestMatchAfter(const QUrl &url, const KTextEditor::Cursor &cursor) const;
QModelIndex firstFileMatch(KTextEditor::Document *doc) const;
QModelIndex closestMatchAfter(KTextEditor::Document *doc, const KTextEditor::Cursor &cursor) const;
QModelIndex firstMatch() const;
QModelIndex nextMatch(const QModelIndex &itemIndex) const;
QModelIndex prevMatch(const QModelIndex &itemIndex) const;
QModelIndex closestMatchBefore(const QUrl &url, const KTextEditor::Cursor &cursor) const;
QModelIndex closestMatchBefore(KTextEditor::Document *doc, const KTextEditor::Cursor &cursor) const;
QModelIndex lastMatch() const;
KTextEditor::Range matchRange(const QModelIndex &matchIndex) const;
bool replaceSingleMatch(KTextEditor::Document *doc, const QModelIndex &matchIndex, const QRegularExpression &regExp, const QString &replaceString);
......
......@@ -171,7 +171,7 @@ public:
void run() override;
Q_SIGNALS:
void matchesFound(const QUrl &url, const QVector<KateSearchMatch> &searchMatches);
void matchesFound(const QUrl &url, const QVector<KateSearchMatch> &searchMatches, KTextEditor::Document *doc = nullptr);
private:
QVector<KateSearchMatch> searchSingleLineRegExp(QFile &file);
......
......@@ -842,13 +842,13 @@ void KatePluginSearchView::searchPlaceChanged()
}
}
void KatePluginSearchView::matchesFound(const QUrl &url, const QVector<KateSearchMatch> &searchMatches)
void KatePluginSearchView::matchesFound(const QUrl &url, const QVector<KateSearchMatch> &searchMatches, KTextEditor::Document *doc)
{
if (!m_curResults) {
return;
}
m_curResults->matchModel.addMatches(url, searchMatches);
m_curResults->matchModel.addMatches(url, searchMatches, doc);
m_curResults->matches += searchMatches.size();
}
......@@ -1369,6 +1369,12 @@ void KatePluginSearchView::replaceChecked()
// Sync the current documents ranges with the model in case it has been edited
syncModelRanges();
// Clear match marks and ranges
// we MUST do this because after we are done replacing, our current moving ranges
// destroy the replace ranges and we don't get the highlights for replace for the
// current open doc
clearMarksAndRanges();
if (m_ui.searchCombo->findText(m_ui.searchCombo->currentText()) == -1) {
m_ui.searchCombo->insertItem(1, m_ui.searchCombo->currentText());
m_ui.searchCombo->setCurrentIndex(1);
......@@ -1494,7 +1500,7 @@ void KatePluginSearchView::addRangeAndMark(KTextEditor::Document *doc,
}
} else {
if (doc->text(match.range) != match.replaceText) {
/// qDebug() << doc->text(range) << "Does not match" << itemIndex.data(MatchModel::ReplaceTextRole).toString();
// qDebug() << doc->text(match.range) << "Does not match" << match.replaceText;
return;
}
}
......@@ -1549,7 +1555,7 @@ void KatePluginSearchView::updateMatchMarks()
}
Results *res = qobject_cast<Results *>(m_ui.resultTabWidget->currentWidget());
if (!res) {
if (!res || res->isEmpty()) {
return;
}
m_curResults = res;
......@@ -1571,7 +1577,7 @@ void KatePluginSearchView::updateMatchMarks()
KTextEditor::MovingInterface *miface = qobject_cast<KTextEditor::MovingInterface *>(doc);
// Add match marks for all matches in the file
const QVector<KateSearchMatch> &fileMatches = res->matchModel.fileMatches(doc->url());
const QVector<KateSearchMatch> &fileMatches = res->matchModel.fileMatches(doc);
for (const KateSearchMatch &match : fileMatches) {
addRangeAndMark(doc, match, m_resultAttr, miface);
}
......@@ -1630,14 +1636,26 @@ void KatePluginSearchView::itemSelected(const QModelIndex &item)
int toLine = matchItem.data(MatchModel::StartLineRole).toInt();
int toColumn = matchItem.data(MatchModel::StartColumnRole).toInt();
QUrl url = matchItem.data(MatchModel::FileUrlRole).toUrl();
KTextEditor::Document *doc = m_kateApp->findUrl(url);
// add the marks to the document if it is not already open
if (!doc) {
doc = m_kateApp->openUrl(url);
// If this url is invalid, it could be that we are searching an unsaved file
// use doc ptr in that case.
KTextEditor::Document *doc = nullptr;
if (url.isValid()) {
doc = m_kateApp->findUrl(url);
// add the marks to the document if it is not already open
if (!doc) {
doc = m_kateApp->openUrl(url);
}
} else {
doc = matchItem.data(MatchModel::DocumentRole).value<KTextEditor::Document *>();
if (!doc) {
// maybe the doc was closed
return;
}
}
if (!doc) {
qDebug() << "Could not open" << url;
qWarning() << "Could not open" << url;
Q_ASSERT(false); // If we get here we have a bug
return;
}
......@@ -1673,17 +1691,17 @@ void KatePluginSearchView::goToNextMatch()
if (!currentIndex.isValid() && focusInView) {
// no item has been visited && focus is not in searchCombo (probably in the view) ->
// jump to the closest match after current cursor position
QUrl docUrl = m_mainWindow->activeView()->document()->url();
auto *doc = m_mainWindow->activeView()->document();
// check if current file is in the file list
currentIndex = res->firstFileMatch(docUrl);
currentIndex = res->firstFileMatch(doc);
if (currentIndex.isValid()) {
// We have the index of the first match in the file
// expand the file item
res->treeView->expand(currentIndex.parent());
// check if we can get the next match after the
currentIndex = res->closestMatchAfter(docUrl, m_mainWindow->activeView()->cursorPosition());
currentIndex = res->closestMatchAfter(doc, m_mainWindow->activeView()->cursorPosition());
if (currentIndex.isValid()) {
itemSelected(currentIndex);
delete m_infoMessage;
......@@ -1750,17 +1768,17 @@ void KatePluginSearchView::goToPreviousMatch()
if (!currentIndex.isValid() && focusInView) {
// no item has been visited && focus is not in the view ->
// jump to the closest match before current cursor position
QUrl docUrl = m_mainWindow->activeView()->document()->url();
auto *doc = m_mainWindow->activeView()->document();
// check if current file is in the file list
currentIndex = res->firstFileMatch(docUrl);
currentIndex = res->firstFileMatch(doc);
if (currentIndex.isValid()) {
// We have the index of the first match in the file
// expand the file item
res->treeView->expand(currentIndex.parent());
// check if we can get the next match after the
currentIndex = res->closestMatchBefore(docUrl, m_mainWindow->activeView()->cursorPosition());
currentIndex = res->closestMatchBefore(doc, m_mainWindow->activeView()->cursorPosition());
if (currentIndex.isValid()) {
itemSelected(currentIndex);
delete m_infoMessage;
......
......@@ -118,7 +118,7 @@ private Q_SLOTS:
void folderFileListChanged();
void matchesFound(const QUrl &url, const QVector<KateSearchMatch> &searchMatches);
void matchesFound(const QUrl &url, const QVector<KateSearchMatch> &searchMatches, KTextEditor::Document *doc);
void addRangeAndMark(KTextEditor::Document *doc, const KateSearchMatch &match, KTextEditor::Attribute::Ptr attr, KTextEditor::MovingInterface *miface);
......
......@@ -130,7 +130,7 @@ int SearchOpenFiles::searchSingleLineRegExp(KTextEditor::Document *doc, const QR
}
// Q_EMIT all matches batched
Q_EMIT matchesFound(doc->url(), matches);
Q_EMIT matchesFound(doc->url(), matches, doc);
return resultLine;
}
......@@ -213,7 +213,7 @@ int SearchOpenFiles::searchMultiLineRegExp(KTextEditor::Document *doc, const QRe
}
// Q_EMIT all matches batched
Q_EMIT matchesFound(doc->url(), matches);
Q_EMIT matchesFound(doc->url(), matches, doc);
return resultLine;
}
......@@ -40,7 +40,7 @@ private:
int searchMultiLineRegExp(KTextEditor::Document *doc, const QRegularExpression &regExp, int startLine);
Q_SIGNALS:
void matchesFound(const QUrl &url, const QVector<KateSearchMatch> &searchMatches);
void matchesFound(const QUrl &url, const QVector<KateSearchMatch> &searchMatches, KTextEditor::Document *doc);
void searchDone();
void searching(const QString &file);
......
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