Skip to content

Commit 65d7549

Browse files
authored
Highlight search results (#775)
Closes #771
1 parent 5453778 commit 65d7549

File tree

5 files changed

+156
-12
lines changed

5 files changed

+156
-12
lines changed

src/NotepadNext/NotepadNext.pro

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ SOURCES += \
7171
EditorHexViewerTableModel.cpp \
7272
EditorManager.cpp \
7373
EditorPrintPreviewRenderer.cpp \
74+
SearchResultHighlighterDelegate.cpp \
7475
widgets/FadingIndicator.cpp \
7576
FileDialogHelpers.cpp \
7677
Finder.cpp \
@@ -149,6 +150,8 @@ HEADERS += \
149150
EditorHexViewerTableModel.h \
150151
EditorManager.h \
151152
EditorPrintPreviewRenderer.h \
153+
SearchResultData.h \
154+
SearchResultHighlighterDelegate.h \
152155
widgets/FadingIndicator.h \
153156
FileDialogHelpers.h \
154157
Finder.h \

src/NotepadNext/SearchResultData.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* This file is part of Notepad Next.
3+
* Copyright 2025 Justin Dailey
4+
*
5+
* Notepad Next is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* Notepad Next is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with Notepad Next. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
#pragma once
20+
21+
#include <Qt>
22+
23+
namespace SearchResultData {
24+
enum Role
25+
{
26+
LineNumber = Qt::UserRole,
27+
LinePosStart,
28+
LinePosEnd
29+
};
30+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* This file is part of Notepad Next.
3+
* Copyright 2025 Justin Dailey
4+
*
5+
* Notepad Next is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* Notepad Next is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with Notepad Next. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
#include "SearchResultHighlighterDelegate.h"
20+
#include "SearchResultData.h"
21+
22+
#include <QPainter>
23+
24+
void SearchResultHighlighterDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
25+
{
26+
// Only custom paint for column 1 with highlight flag set
27+
if (index.column() != 1 || !index.data(SearchResultData::LineNumber).isValid()) {
28+
QStyledItemDelegate::paint(painter, option, index);
29+
return;
30+
}
31+
32+
const QString text = index.data(Qt::DisplayRole).toString();
33+
int start = index.data(SearchResultData::LinePosStart).toInt();
34+
int end = index.data(SearchResultData::LinePosEnd).toInt();
35+
36+
QStyleOptionViewItem opt(option);
37+
initStyleOption(&opt, index);
38+
39+
painter->save();
40+
painter->setClipRect(opt.rect);
41+
42+
// Optional: draw background or selection as usual
43+
if (opt.state & QStyle::State_Selected) {
44+
painter->fillRect(opt.rect, opt.palette.highlight());
45+
} else {
46+
painter->fillRect(opt.rect, opt.backgroundBrush);
47+
}
48+
49+
QRect textRect = opt.rect.adjusted(4, 0, -4, 0); // small padding
50+
QFontMetrics fm(opt.font);
51+
int y = textRect.top() + (textRect.height() + fm.ascent() - fm.descent()) / 2;
52+
int x = textRect.left();
53+
54+
// Split the text
55+
const QString before = text.left(start);
56+
const QString match = text.mid(start, end - start);
57+
const QString after = text.mid(end);
58+
59+
// Draw 'before' text (normal)
60+
painter->setFont(opt.font);
61+
painter->setPen(opt.palette.color(QPalette::Text));
62+
painter->drawText(x, y, before);
63+
x += fm.horizontalAdvance(before);
64+
65+
// Draw highlighted 'match' text (bold, red, yellow bg)
66+
QFont boldFont = opt.font;
67+
boldFont.setBold(true);
68+
painter->setFont(boldFont);
69+
70+
int matchWidth = fm.horizontalAdvance(match);
71+
QRect highlightRect(x, textRect.top(), matchWidth, textRect.height());
72+
painter->fillRect(highlightRect, QColor(Qt::yellow));
73+
74+
painter->setPen(Qt::red);
75+
painter->drawText(x, y, match);
76+
x += matchWidth;
77+
78+
// Draw 'after' text (normal)
79+
painter->setFont(opt.font);
80+
painter->setPen(opt.palette.color(QPalette::Text));
81+
painter->drawText(x, y, after);
82+
83+
painter->restore();
84+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* This file is part of Notepad Next.
3+
* Copyright 2025 Justin Dailey
4+
*
5+
* Notepad Next is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* Notepad Next is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with Notepad Next. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
#pragma once
20+
21+
#include <QStyledItemDelegate>
22+
23+
class SearchResultHighlighterDelegate : public QStyledItemDelegate {
24+
public:
25+
SearchResultHighlighterDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {}
26+
27+
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
28+
};

src/NotepadNext/docks/SearchResultsDock.cpp

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
*/
1818

1919

20+
#include "SearchResultHighlighterDelegate.h"
21+
#include "SearchResultData.h"
2022
#include "SearchResultsDock.h"
2123
#include "ScintillaNext.h"
2224
#include "ui_SearchResultsDock.h"
@@ -27,12 +29,6 @@
2729
#include <QShortcut>
2830
#include <QClipboard>
2931

30-
enum SearchResultData
31-
{
32-
LineNumber = Qt::UserRole,
33-
LinePosStart,
34-
LinePosEnd
35-
};
3632

3733
SearchResultsDock::SearchResultsDock(QWidget *parent) :
3834
QDockWidget(parent),
@@ -71,6 +67,8 @@ SearchResultsDock::SearchResultsDock(QWidget *parent) :
7167

7268
menu.exec(QCursor::pos());
7369
});
70+
71+
ui->treeWidget->setItemDelegate(new SearchResultHighlighterDelegate(ui->treeWidget));
7472
}
7573

7674
SearchResultsDock::~SearchResultsDock()
@@ -125,12 +123,13 @@ void SearchResultsDock::newResultsEntry(const QString line, int lineNumber, int
125123

126124
// Scintilla internally references line numbers starting at 0, however it needs displayed starting at 1
127125
item->setText(0, QString::number(lineNumber + 1));
128-
item->setData(0, SearchResultData::LineNumber, lineNumber);
129-
item->setData(0, SearchResultData::LinePosStart, startPositionFromBeginning);
130-
item->setData(0, SearchResultData::LinePosEnd, endPositionFromBeginning);
131126
item->setBackground(0, QBrush(QColor(220, 220, 220)));
132127
item->setTextAlignment(0, Qt::AlignRight);
133128

129+
item->setData(1, SearchResultData::LineNumber, lineNumber);
130+
item->setData(1, SearchResultData::LinePosStart, startPositionFromBeginning);
131+
item->setData(1, SearchResultData::LinePosEnd, endPositionFromBeginning);
132+
item->setData(1, Qt::UserRole + 3, true); // <- Flag to enable highlight
134133
item->setText(1, line);
135134

136135
totalFileHitCount += hitCount;
@@ -191,9 +190,9 @@ void SearchResultsDock::itemActivated(QTreeWidgetItem *item, int column)
191190

192191
// The editor may no longer exist
193192
if (editor) {
194-
int lineNumber = item->data(0, SearchResultData::LineNumber).toInt();
195-
int startPositionFromBeginning = item->data(0, SearchResultData::LinePosStart).toInt();
196-
int endPositionFromBeginning = item->data(0, SearchResultData::LinePosEnd).toInt();
193+
int lineNumber = item->data(1, SearchResultData::LineNumber).toInt();
194+
int startPositionFromBeginning = item->data(1, SearchResultData::LinePosStart).toInt();
195+
int endPositionFromBeginning = item->data(1, SearchResultData::LinePosEnd).toInt();
197196

198197
emit searchResultActivated(editor, lineNumber, startPositionFromBeginning, endPositionFromBeginning);
199198
}

0 commit comments

Comments
 (0)