MauiKit Terminal

Filter.cpp
1/*
2 SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5
6 This program is distributed in the hope that it will be useful,
7 but WITHOUT ANY WARRANTY; without even the implied warranty of
8 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 GNU General Public License for more details.
10
11 You should have received a copy of the GNU General Public License
12 along with this program; if not, write to the Free Software
13 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
14 02110-1301 USA.
15*/
16
17// Own
18#include "Filter.h"
19
20// System
21#include <iostream>
22
23// Qt
24#include <QAction>
25#include <QApplication>
26#include <QClipboard>
27#include <QDesktopServices>
28#include <QFile>
29#include <QSharedData>
30#include <QString>
31#include <QTextStream>
32#include <QUrl>
33#include <QtAlgorithms>
34
35// KDE
36// #include <KLocale>
37// #include <KRun>
38
39// Konsole
40#include "TerminalCharacterDecoder.h"
41#include "konsole_wcwidth.h"
42
43using namespace Konsole;
44
45FilterChain::~FilterChain() = default;
46
47void FilterChain::addFilter(std::unique_ptr<Filter> &&filter)
48{
49 push_back(std::move(filter));
50}
51
53{
54 std::erase_if(*this, [filter](const auto &f) {
55 return f.get() == filter;
56 });
57}
58
60{
61 return std::find_if(this->cbegin(),
62 this->cend(),
63 [filter](const auto &f) {
64 return f.get() == filter;
65 })
66 != this->cend();
67}
68
70{
71 for (const auto &filter : *this) {
72 filter->reset();
73 }
74}
75
76void FilterChain::setBuffer(const QString *buffer, const QList<int> *linePositions)
77{
78 for (const auto &filter : *this) {
79 filter->setBuffer(buffer, linePositions);
80 }
81}
82
84{
85 for (const auto &filter : *this) {
86 filter->process();
87 }
88}
89
90Filter::HotSpot *FilterChain::hotSpotAt(int line, int column) const
91{
92 for (const auto &filter : *this) {
93 Filter::HotSpot *spot = filter->hotSpotAt(line, column);
94 if (spot) {
95 return spot;
96 }
97 }
98
99 return nullptr;
100}
101
103{
105 for (const auto &filter : *this) {
106 for (const auto &hotSpot : filter->hotSpots()) {
107 list << hotSpot.get();
108 }
109 }
110 return list;
111}
112// QList<Filter::HotSpot*> FilterChain::hotSpotsAtLine(int line) const;
113
114TerminalImageFilterChain::TerminalImageFilterChain()
115 : _buffer(nullptr)
116 , _linePositions(nullptr)
117{
118}
119
120TerminalImageFilterChain::~TerminalImageFilterChain()
121{
122 delete _buffer;
123 delete _linePositions;
124}
125
126void TerminalImageFilterChain::setImage(std::span<const Character> image, int lines, int columns, const QVector<LineProperty> &lineProperties)
127{
128 if (empty())
129 return;
130
131 // reset all filters and hotspots
132 reset();
133
134 PlainTextDecoder decoder;
135 decoder.setTrailingWhitespace(false);
136
137 // setup new shared buffers for the filters to process on
138 QString *newBuffer = new QString();
139 QList<int> *newLinePositions = new QList<int>();
140 setBuffer(newBuffer, newLinePositions);
141
142 // free the old buffers
143 delete _buffer;
144 delete _linePositions;
145
146 _buffer = newBuffer;
147 _linePositions = newLinePositions;
148
149 QTextStream lineStream(_buffer);
150 decoder.begin(&lineStream);
151
152 for (int i = 0; i < lines; i++) {
153 _linePositions->append(_buffer->length());
154 decoder.decodeLine(image.subspan(i * columns, columns), LINE_DEFAULT);
155
156 // pretend that each line ends with a newline character.
157 // this prevents a link that occurs at the end of one line
158 // being treated as part of a link that occurs at the start of the next line
159 //
160 // the downside is that links which are spread over more than one line are not
161 // highlighted.
162 //
163 // TODO - Use the "line wrapped" attribute associated with lines in a
164 // terminal image to avoid adding this imaginary character for wrapped
165 // lines
166 if (!(lineProperties.value(i, LINE_DEFAULT) & LINE_WRAPPED))
167 lineStream << QLatin1Char('\n');
168 }
169 decoder.end();
170}
171
173 : _linePositions(nullptr)
174 , _buffer(nullptr)
175{
176}
177
178Filter::~Filter()
179{
180 _hotspotList.clear();
181}
183{
184 _hotspots.clear();
185 _hotspotList.clear();
186}
187
188void Filter::setBuffer(const QString *buffer, const QList<int> *linePositions)
189{
190 _buffer = buffer;
191 _linePositions = linePositions;
192}
193
194void Filter::getLineColumn(int position, int &startLine, int &startColumn)
195{
196 Q_ASSERT(_linePositions);
197 Q_ASSERT(_buffer);
198
199 for (int i = 0; i < _linePositions->count(); i++) {
200 int nextLine = 0;
201
202 if (i == _linePositions->count() - 1)
203 nextLine = _buffer->length() + 1;
204 else
205 nextLine = _linePositions->value(i + 1);
206
207 if (_linePositions->value(i) <= position && position < nextLine) {
208 startLine = i;
209 startColumn = string_width(buffer()->mid(_linePositions->value(i), position - _linePositions->value(i)));
210 return;
211 }
212 }
213}
214
215/*void Filter::addLine(const QString& text)
216{
217 _linePositions << _buffer.length();
218 _buffer.append(text);
219}*/
220
222{
223 return _buffer;
224}
225
226Filter::HotSpot::~HotSpot() = default;
227
228void Filter::addHotSpot(std::unique_ptr<HotSpot> &&spot)
229{
230 _hotspotList.push_back(std::move(spot));
231
232 for (int line = spot->startLine(); line <= spot->endLine(); line++) {
233 _hotspots.insert({line, std::move(spot)});
234 }
235}
236
237const std::vector<std::unique_ptr<Filter::HotSpot>> &Filter::hotSpots() const
238{
239 return _hotspotList;
240}
241
243{
245 for (auto it = _hotspots.find(line); it != _hotspots.cend(); it++) {
246 filters.push_back(it->second.get());
247 }
248 return filters;
249}
250
251Filter::HotSpot *Filter::hotSpotAt(int line, int column) const
252{
253 const auto hotspots = hotSpotsAtLine(line);
254
255 for (const auto spot : hotspots) {
256 if (spot->startLine() == line && spot->startColumn() > column)
257 continue;
258 if (spot->endLine() == line && spot->endColumn() < column)
259 continue;
260
261 return spot;
262 }
263
264 return nullptr;
265}
266
267Filter::HotSpot::HotSpot(int startLine, int startColumn, int endLine, int endColumn)
268 : _startLine(startLine)
269 , _startColumn(startColumn)
270 , _endLine(endLine)
271 , _endColumn(endColumn)
272 , _type(NotSpecified)
273{
274}
275QList<QAction *> Filter::HotSpot::actions()
276{
277 return QList<QAction *>();
278}
279int Filter::HotSpot::startLine() const
280{
281 return _startLine;
282}
283int Filter::HotSpot::endLine() const
284{
285 return _endLine;
286}
287int Filter::HotSpot::startColumn() const
288{
289 return _startColumn;
290}
291int Filter::HotSpot::endColumn() const
292{
293 return _endColumn;
294}
295Filter::HotSpot::Type Filter::HotSpot::type() const
296{
297 return _type;
298}
299void Filter::HotSpot::setType(Type type)
300{
301 _type = type;
302}
303
307
308RegExpFilter::HotSpot::HotSpot(int startLine, int startColumn, int endLine, int endColumn)
309 : Filter::HotSpot(startLine, startColumn, endLine, endColumn)
310{
311 setType(Marker);
312}
313
317
319{
320 _capturedTexts = texts;
321}
323{
324 return _capturedTexts;
325}
326
327void RegExpFilter::setRegExp(const QRegExp &regExp)
328{
329 _searchText = regExp;
330}
331QRegExp RegExpFilter::regExp() const
332{
333 return _searchText;
334}
335/*void RegExpFilter::reset(int)
336{
337 _buffer = QString();
338}*/
340{
341 int pos = 0;
342 const QString *text = buffer();
343
344 Q_ASSERT(text);
345
346 // ignore any regular expressions which match an empty string.
347 // otherwise the while loop below will run indefinitely
348 static const QString emptyString;
349 if (_searchText.exactMatch(emptyString))
350 return;
351
352 while (pos >= 0) {
353 pos = _searchText.indexIn(*text, pos);
354
355 if (pos >= 0) {
356 int startLine = 0;
357 int endLine = 0;
358 int startColumn = 0;
359 int endColumn = 0;
360
361 getLineColumn(pos, startLine, startColumn);
362 getLineColumn(pos + _searchText.matchedLength(), endLine, endColumn);
363
364 auto spot = newHotSpot(startLine, startColumn, endLine, endColumn);
365 spot->setCapturedTexts(_searchText.capturedTexts());
366
367 addHotSpot(std::move(spot));
368 pos += _searchText.matchedLength();
369
370 // if matchedLength == 0, the program will get stuck in an infinite loop
371 if (_searchText.matchedLength() == 0)
372 pos = -1;
373 }
374 }
375}
376
377std::unique_ptr<RegExpFilter::HotSpot> RegExpFilter::newHotSpot(int startLine, int startColumn, int endLine, int endColumn)
378{
379 return std::make_unique<RegExpFilter::HotSpot>(startLine, startColumn, endLine, endColumn);
380}
381
382std::unique_ptr<RegExpFilter::HotSpot> UrlFilter::newHotSpot(int startLine, int startColumn, int endLine, int endColumn)
383{
384 auto spot = std::make_unique<UrlFilter::HotSpot>(startLine, startColumn, endLine, endColumn);
385 connect(spot->getUrlObject(), &FilterObject::activated, this, &UrlFilter::activated);
386 return spot;
387}
388
389UrlFilter::HotSpot::HotSpot(int startLine, int startColumn, int endLine, int endColumn)
390 : RegExpFilter::HotSpot(startLine, startColumn, endLine, endColumn)
391 , _urlObject(new FilterObject(this))
392{
393 setType(Link);
394}
395
396UrlFilter::HotSpot::UrlType UrlFilter::HotSpot::urlType() const
397{
399
400 if (FullUrlRegExp.exactMatch(url))
401 return StandardUrl;
402 else if (EmailAddressRegExp.exactMatch(url))
403 return Email;
404 else
405 return Unknown;
406}
407
409{
410 QString url = capturedTexts().constFirst();
411
412 const UrlType kind = urlType();
413
414 if (actionName == QLatin1String("copy-action")) {
416 return;
417 }
418
419 if (actionName.isEmpty() || actionName == QLatin1String("open-action") || actionName == QLatin1String("click-action")) {
420 if (kind == StandardUrl) {
421 // if the URL path does not include the protocol ( eg. "www.kde.org" ) then
422 // prepend http:// ( eg. "www.kde.org" --> "http://www.kde.org" )
423 if (!url.contains(QLatin1String("://"))) {
424 url.prepend(QLatin1String("http://"));
425 }
426 } else if (kind == Email) {
427 url.prepend(QLatin1String("mailto:"));
428 }
429
430 _urlObject->emitActivated(QUrl(url, QUrl::StrictMode), actionName != QLatin1String("click-action"));
431 }
432}
433
434// Note: Altering these regular expressions can have a major effect on the performance of the filters
435// used for finding URLs in the text, especially if they are very general and could match very long
436// pieces of text.
437// Please be careful when altering them.
438
439// regexp matches:
440// full url:
441// protocolname:// or www. followed by anything other than whitespaces, <, >, ' or ", and ends before whitespaces, <, >, ', ", ], !, comma and dot
442const QRegExp UrlFilter::FullUrlRegExp(QLatin1String("(www\\.(?!\\.)|[a-z][a-z0-9+.-]*://)[^\\s<>'\"]+[^!,\\.\\s<>'\"\\]]"));
443// email address:
444// [word chars, dots or dashes]@[word chars, dots or dashes].[word chars]
445const QRegExp UrlFilter::EmailAddressRegExp(QLatin1String("\\b(\\w|\\.|-)+@(\\w|\\.|-)+\\.\\w+\\b"));
446
447// matches full url or email address
448const QRegExp UrlFilter::CompleteUrlRegExp(QLatin1Char('(') + FullUrlRegExp.pattern() + QLatin1Char('|') + EmailAddressRegExp.pattern() + QLatin1Char(')'));
449
450UrlFilter::UrlFilter()
451{
452 setRegExp(CompleteUrlRegExp);
453}
454
455UrlFilter::HotSpot::~HotSpot()
456{
457 delete _urlObject;
458}
459
460void FilterObject::emitActivated(const QUrl &url, bool fromContextMenu)
461{
462 Q_EMIT activated(url, fromContextMenu);
463}
464
465void FilterObject::activate()
466{
467 _filter->activate(sender()->objectName());
468}
469
470FilterObject *UrlFilter::HotSpot::getUrlObject() const
471{
472 return _urlObject;
473}
474
476{
477 QList<QAction *> list;
478
479 const UrlType kind = urlType();
480
481 QAction *openAction = new QAction(_urlObject);
482 QAction *copyAction = new QAction(_urlObject);
483 ;
484
485 Q_ASSERT(kind == StandardUrl || kind == Email);
486
487 if (kind == StandardUrl) {
488 openAction->setText(QObject::tr("Open Link"));
489 copyAction->setText(QObject::tr("Copy Link Address"));
490 } else if (kind == Email) {
491 openAction->setText(QObject::tr("Send Email To..."));
492 copyAction->setText(QObject::tr("Copy Email Address"));
493 }
494
495 // object names are set here so that the hotspot performs the
496 // correct action when activated() is called with the triggered
497 // action passed as a parameter.
498 openAction->setObjectName(QLatin1String("open-action"));
499 copyAction->setObjectName(QLatin1String("copy-action"));
500
501 QObject::connect(openAction, &QAction::triggered, _urlObject, &FilterObject::activate);
502 QObject::connect(copyAction, &QAction::triggered, _urlObject, &FilterObject::activate);
503
504 list << openAction;
505 list << copyAction;
506
507 return list;
508}
509
510// #include "Filter.moc"
void process()
Processes each filter in the chain.
Definition Filter.cpp:83
bool containsFilter(Filter *filter)
Returns true if the chain contains filter.
Definition Filter.cpp:59
void addFilter(std::unique_ptr< Filter > &&filter)
Adds a new filter to the chain.
Definition Filter.cpp:47
void removeFilter(Filter *filter)
Removes a filter from the chain.
Definition Filter.cpp:52
void reset()
Resets each filter in the chain.
Definition Filter.cpp:69
Filter::HotSpot * hotSpotAt(int line, int column) const
Returns the first hotspot which occurs at line, column or 0 if no hotspot was found.
Definition Filter.cpp:90
QList< Filter::HotSpot * > hotSpots() const
Returns a list of all the hotspots in all the chain's filters.
Definition Filter.cpp:102
void setBuffer(const QString *buffer, const QList< int > *linePositions)
Sets the buffer for each filter in the chain to process.
Definition Filter.cpp:76
Represents an area of text which matched the pattern a particular filter has been looking for.
Definition Filter.h:69
Type type() const
Returns the type of the hotspot.
Definition Filter.cpp:295
A filter processes blocks of text looking for certain patterns (such as URLs or keywords from a list)...
Definition Filter.h:54
void setBuffer(const QString *buffer, const QList< int > *linePositions)
TODO: Document me.
Definition Filter.cpp:188
const std::vector< std::unique_ptr< HotSpot > > & hotSpots() const
Returns the list of hotspots identified by the filter.
Definition Filter.cpp:237
QList< HotSpot * > hotSpotsAtLine(int line) const
Returns the list of hotspots identified by the filter which occur on a given line.
Definition Filter.cpp:242
HotSpot * hotSpotAt(int line, int column) const
Adds a new line of text to the filter and increments the line count.
Definition Filter.cpp:251
const QString * buffer()
Returns the internal buffer.
Definition Filter.cpp:221
void addHotSpot(std::unique_ptr< HotSpot > &&)
Adds a new hotspot to the list.
Definition Filter.cpp:228
Filter()
Constructs a new filter.
Definition Filter.cpp:172
void reset()
Empties the filters internal buffer and resets the line count back to 0.
Definition Filter.cpp:182
void getLineColumn(int position, int &startLine, int &startColumn)
Converts a character position within buffer() to a line and column.
Definition Filter.cpp:194
A terminal character decoder which produces plain text, ignoring colours and other appearance-related...
void setTrailingWhitespace(bool enable)
Set whether trailing whitespace at the end of lines should be included in the output.
void decodeLine(std::span< const Character > characters, LineProperty properties) override
Converts a line of terminal characters with associated properties into a text string and writes the s...
void end() override
End decoding.
void begin(QTextStream *output) override
Begin decoding characters.
void activate(const QString &action=QString()) override
Causes the an action associated with a hotspot to be triggered.
Definition Filter.cpp:314
QStringList capturedTexts() const
Returns the texts found by the filter when matching the filter's regular expression.
Definition Filter.cpp:322
void setCapturedTexts(const QStringList &texts)
Sets the captured texts associated with this hotspot.
Definition Filter.cpp:318
A filter which searches for sections of text matching a regular expression and creates a new RegExpFi...
Definition Filter.h:181
void setRegExp(const QRegExp &text)
Sets the regular expression which the filter searches for in blocks of text.
Definition Filter.cpp:327
void process() override
Reimplemented to search the filter's text buffer for text matching regExp()
Definition Filter.cpp:339
virtual std::unique_ptr< HotSpot > newHotSpot(int startLine, int startColumn, int endLine, int endColumn)
Called when a match for the regular expression is encountered.
Definition Filter.cpp:377
QRegExp regExp() const
Returns the regular expression which the filter searches for in blocks of text.
Definition Filter.cpp:331
RegExpFilter()
Constructs a new regular expression filter.
Definition Filter.cpp:304
void setImage(std::span< const Character > image, int lines, int columns, const QVector< LineProperty > &lineProperties)
Set the current terminal image to image.
Definition Filter.cpp:126
void activate(const QString &action=QString()) override
Open a web browser at the current URL.
Definition Filter.cpp:408
QList< QAction * > actions() override
Returns a list of actions associated with the hotspot which can be used in a menu or toolbar.
Definition Filter.cpp:475
std::unique_ptr< RegExpFilter::HotSpot > newHotSpot(int, int, int, int) override
Called when a match for the regular expression is encountered.
Definition Filter.cpp:382
void setText(const QString &text)
void triggered(bool checked)
void setText(const QString &text, Mode mode)
QClipboard * clipboard()
void append(QList< T > &&value)
const T & constFirst() const const
qsizetype count() const const
void push_back(parameter_type value)
T value(qsizetype i) const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * sender() const const
void setObjectName(QAnyStringView name)
QString tr(const char *sourceText, const char *disambiguation, int n)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype length() const const
QString & prepend(QChar ch)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:10:32 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.