File indexing completed on 2024-05-19 05:54:09

0001 /*
0002     SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
0003     SPDX-FileCopyrightText: 2020 Tomaz Canabrava <tcanabrava@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "FileFilter.h"
0009 
0010 #include <QDir>
0011 
0012 #include "profile/Profile.h"
0013 #include "session/Session.h"
0014 #include "session/SessionManager.h"
0015 
0016 #include "FileFilterHotspot.h"
0017 
0018 using namespace Konsole;
0019 
0020 // static
0021 QRegularExpression FileFilter::_regex;
0022 
0023 FileFilter::FileFilter(Session *session, const QString &wordCharacters)
0024     : _session(session)
0025     , _dirPath(QString())
0026     , _currentDirContents()
0027 {
0028     _regex = QRegularExpression(concatRegexPattern(wordCharacters), QRegularExpression::DontCaptureOption);
0029     setRegExp(_regex);
0030 }
0031 
0032 QString FileFilter::concatRegexPattern(QString wordCharacters) const
0033 {
0034     /* The wordCharacters can be a potentially broken regexp,
0035      * so let's fix it manually if it has some troublesome characters.
0036      */
0037     // Add a folder delimiter at the beginning.
0038     if (wordCharacters.contains(QLatin1Char('/'))) {
0039         wordCharacters.remove(QLatin1Char('/'));
0040         wordCharacters.prepend(QStringLiteral("\\/"));
0041     }
0042 
0043     // Add minus at the end.
0044     if (wordCharacters.contains(QLatin1Char('-'))) {
0045         wordCharacters.remove(QLatin1Char('-'));
0046         wordCharacters.append(QLatin1Char('-'));
0047     }
0048 
0049     const QString pattern =
0050         /* First part of the regexp means 'strings with spaces and starting with single quotes'
0051          * Second part means "Strings with double quotes"
0052          * Last part means "Everything else plus some special chars
0053          * This is much smaller, and faster, than the previous regexp
0054          * on the HotSpot creation we verify if this is indeed a file, so there's
0055          * no problem on testing on random words on the screen.
0056          */
0057         QStringLiteral(R"RX('[^'\n]+')RX") // Matches everything between single quotes.
0058         + QStringLiteral(R"RX(|"[^\n"]+")RX") // Matches everything inside double quotes
0059         // Matches a contiguous line of alphanumeric characters plus some special ones
0060         // defined in the profile. With a special case for strings starting with '/' which
0061         // denotes a path on Linux.
0062         // Takes into account line numbers:
0063         // - grep output with line numbers: "/path/to/file:123"
0064         // - compiler error output: ":/path/to/file:123:123"
0065         //
0066         // ([^\n/\[]/) to not match "https://", and urls starting with "[" are matched by the
0067         // next | branch (ctest stuff)
0068         + QStringLiteral(R"RX(|([^\n\s/\[]/)?[\p{L}\w%1]+(:\d+)?(:\d+:)?)RX").arg(wordCharacters)
0069         // - ctest error output: "[/path/to/file(123)]"
0070         + QStringLiteral(R"RX(|\[[/\w%1]+\(\d+\)\])RX").arg(wordCharacters);
0071 
0072     return pattern;
0073 }
0074 
0075 /**
0076  * File Filter - Construct a filter that works on local file paths using the
0077  * posix portable filename character set combined with KDE's mimetype filename
0078  * extension blob patterns.
0079  * https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_267
0080  */
0081 
0082 QSharedPointer<HotSpot> FileFilter::newHotSpot(int startLine, int startColumn, int endLine, int endColumn, const QStringList &capturedTexts)
0083 {
0084     if (_session.isNull()) {
0085         return nullptr;
0086     }
0087 
0088     const QString &filenameRef = capturedTexts.first();
0089     QStringView filename = filenameRef;
0090     if (filename.startsWith(QLatin1Char('\'')) && filename.endsWith(QLatin1Char('\''))) {
0091         filename = filename.mid(1, filename.size() - 2);
0092     }
0093 
0094     // '.' and '..' could be valid hotspots, but '..................' most likely isn't
0095     static const QRegularExpression allDotRe{QRegularExpression::anchoredPattern(QStringLiteral("\\.{3}"))};
0096     if (allDotRe.match(filename).hasMatch()) {
0097         return nullptr;
0098     }
0099 
0100     if (filename.startsWith(QLatin1String("[/"))) { // ctest error output
0101         filename = filename.mid(1);
0102     }
0103 
0104     const bool absolute = filename.startsWith(QLatin1Char('/'));
0105     if (!absolute) {
0106         auto match = std::find_if(_currentDirContents.cbegin(), _currentDirContents.cend(), [filename](const QString &s) {
0107             // early out if first char doesn't match
0108             if (!s.isEmpty() && filename.at(0) != s.at(0)) {
0109                 return false;
0110             }
0111 
0112             const bool startsWith = filename.startsWith(s);
0113             if (startsWith) {
0114                 // are we equal ?
0115                 if (filename.size() == s.size()) {
0116                     return true;
0117                 }
0118                 int onePast = s.size();
0119                 if (onePast < filename.size()) {
0120                     if (filename.at(onePast) == QLatin1Char(':') || filename.at(onePast) == QLatin1Char('/')) {
0121                         return true;
0122                     }
0123                 }
0124             }
0125             return false;
0126         });
0127 
0128         if (match == _currentDirContents.cend()) {
0129             return nullptr;
0130         }
0131     }
0132 
0133     return QSharedPointer<HotSpot>(new FileFilterHotSpot(startLine,
0134                                                          startColumn,
0135                                                          endLine,
0136                                                          endColumn,
0137                                                          capturedTexts,
0138                                                          !absolute ? _dirPath + filename.toString() : filename.toString(),
0139                                                          _session));
0140 }
0141 
0142 void FileFilter::process()
0143 {
0144     const QDir dir(_session->currentWorkingDirectory());
0145     // Do not re-process.
0146     if (_dirPath != dir.canonicalPath() + QLatin1Char('/')) {
0147         _dirPath = dir.canonicalPath() + QLatin1Char('/');
0148 
0149         _currentDirContents = dir.entryList(QDir::Dirs | QDir::Files);
0150     }
0151 
0152     RegExpFilter::process();
0153 }
0154 
0155 void FileFilter::updateRegex(const QString &wordCharacters)
0156 {
0157     _regex.setPattern(concatRegexPattern(wordCharacters));
0158     setRegExp(_regex);
0159 }