File indexing completed on 2024-04-28 04:38:53
0001 /* 0002 SPDX-FileCopyrightText: 1999-2001 Bernd Gehrmann <bernd@kdevelop.org> 0003 SPDX-FileCopyrightText: 2007 Dukju Ahn <dukjuahn@gmail.com> 0004 SPDX-FileCopyrightText: 2008 Hamish Rodda <rodda@kde.org> 0005 SPDX-FileCopyrightText: 2010 Silvère Lestang <silvere.lestang@gmail.com> 0006 SPDX-FileCopyrightText: 2010 Julien Desgats <julien.desgats@gmail.com> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 0011 #include "grepjob.h" 0012 #include "grepfindthread.h" 0013 #include "grepoutputmodel.h" 0014 #include "greputil.h" 0015 0016 #include "debug.h" 0017 0018 #include <QDebug> 0019 #include <QFile> 0020 #include <QTextStream> 0021 0022 #include <KEncodingProber> 0023 #include <KLocalizedString> 0024 0025 #include <serialization/indexedstring.h> 0026 #include <interfaces/icore.h> 0027 #include <interfaces/iuicontroller.h> 0028 0029 using namespace KDevelop; 0030 0031 QDebug operator<<(QDebug debug, const GrepJobSettings& s) 0032 { 0033 const QDebugStateSaver saver(debug); 0034 0035 debug.nospace() << '{'; 0036 0037 bool firstDataMember = true; 0038 const auto printDataMember = [&debug, &firstDataMember](const char* name, const auto& value) { 0039 if (firstDataMember) { 0040 firstDataMember = false; 0041 } else { 0042 // Separate the members with vertical bars, 0043 // because commas and semicolons separate items within string data members. 0044 debug << " | "; 0045 } 0046 debug << name << ": " << value; 0047 }; 0048 0049 #define p(memberName) printDataMember(#memberName, s.memberName) 0050 p(fromHistory); 0051 0052 p(projectFilesOnly); 0053 p(caseSensitive); 0054 p(regexp); 0055 0056 p(depth); 0057 0058 p(pattern); 0059 p(searchTemplate); 0060 p(replacementTemplate); 0061 p(files); 0062 p(exclude); 0063 p(searchPaths); 0064 #undef p 0065 0066 debug << '}'; 0067 0068 return debug; 0069 } 0070 0071 GrepOutputItem::List grepFile(const QString &filename, const QRegExp &re) 0072 { 0073 GrepOutputItem::List res; 0074 QFile file(filename); 0075 0076 if(!file.open(QIODevice::ReadOnly)) 0077 return res; 0078 int lineno = 0; 0079 0080 0081 // detect encoding (unicode files can be feed forever, stops when confidence reachs 99% 0082 KEncodingProber prober; 0083 while(!file.atEnd() && prober.state() == KEncodingProber::Probing && prober.confidence() < 0.99) { 0084 prober.feed(file.read(0xFF)); 0085 } 0086 0087 // reads file with detected encoding 0088 file.seek(0); 0089 QTextStream stream(&file); 0090 if(prober.confidence()>0.7) 0091 stream.setCodec(prober.encoding().constData()); 0092 while( !stream.atEnd() ) 0093 { 0094 QString data = stream.readLine(); 0095 0096 // remove line terminators (in order to not match them) 0097 for (int pos = data.length()-1; pos >= 0 && (data[pos] == QLatin1Char('\r') || data[pos] == QLatin1Char('\n')); pos--) { 0098 data.chop(1); 0099 } 0100 0101 int offset = 0; 0102 // allow empty string matching result in an infinite loop ! 0103 while( re.indexIn(data, offset)!=-1 && re.cap(0).length() > 0 ) 0104 { 0105 int start = re.pos(0); 0106 int end = start + re.cap(0).length(); 0107 0108 DocumentChangePointer change = DocumentChangePointer(new DocumentChange( 0109 IndexedString(filename), 0110 KTextEditor::Range(lineno, start, lineno, end), 0111 re.cap(0), QString())); 0112 0113 res << GrepOutputItem(change, data, false); 0114 offset = end; 0115 } 0116 lineno++; 0117 } 0118 file.close(); 0119 return res; 0120 } 0121 0122 GrepJob::GrepJob( QObject* parent ) 0123 : KJob( parent ) 0124 , m_workState(WorkUnstarted) 0125 , m_fileIndex(0) 0126 , m_findThread(nullptr) 0127 , m_findSomething(false) 0128 { 0129 qRegisterMetaType<GrepOutputItem::List>(); 0130 0131 setCapabilities(Killable); 0132 KDevelop::ICore::self()->uiController()->registerStatus(this); 0133 0134 connect(this, &GrepJob::result, this, &GrepJob::testFinishState); 0135 } 0136 0137 GrepJob::~GrepJob() 0138 { 0139 Q_ASSERT(m_workState == WorkDead); 0140 } 0141 0142 QString GrepJob::statusName() const 0143 { 0144 return i18n("Find in Files"); 0145 } 0146 0147 void GrepJob::slotFindFinished() 0148 { 0149 Q_ASSERT(m_findThread && m_findThread->isFinished()); 0150 0151 if (m_workState == WorkCancelled) { 0152 dieAfterCancellation(); 0153 return; 0154 } 0155 Q_ASSERT(m_workState == WorkCollectFiles); 0156 0157 m_fileList = m_findThread->takeFiles(); 0158 m_findThread->deleteLater(); 0159 m_findThread = nullptr; 0160 0161 if(m_fileList.isEmpty()) 0162 { 0163 m_errorMessage = i18n("No files found matching the wildcard patterns"); 0164 die(); 0165 return; 0166 } 0167 0168 if(!m_settings.regexp) 0169 { 0170 m_settings.pattern = QRegExp::escape(m_settings.pattern); 0171 } 0172 0173 if(m_settings.regexp && QRegExp(m_settings.pattern).captureCount() > 0) 0174 { 0175 m_errorMessage = i18nc("Capture is the text which is \"captured\" with () in regular expressions " 0176 "see https://doc.qt.io/qt-5/qregexp.html#capturedTexts", 0177 "Captures are not allowed in pattern string"); 0178 die(); 0179 return; 0180 } 0181 0182 QString pattern = substitudePattern(m_settings.searchTemplate, m_settings.pattern); 0183 m_regExp.setPattern(pattern); 0184 m_regExp.setPatternSyntax(QRegExp::RegExp2); 0185 m_regExp.setCaseSensitivity( m_settings.caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive ); 0186 if(pattern == QRegExp::escape(pattern)) 0187 { 0188 // enable wildcard mode when possible 0189 // if pattern has already been escaped (raw text serch) a second escape will result in a different string anyway 0190 m_regExp.setPatternSyntax(QRegExp::Wildcard); 0191 } 0192 0193 if (m_outputModel) { 0194 m_outputModel->setRegExp(m_regExp); 0195 m_outputModel->setReplacementTemplate(m_settings.replacementTemplate); 0196 } 0197 0198 emit showMessage(this, i18np("Searching for <b>%2</b> in one file", 0199 "Searching for <b>%2</b> in %1 files", 0200 m_fileList.length(), 0201 m_regExp.pattern().toHtmlEscaped())); 0202 0203 m_workState = WorkGrep; 0204 QMetaObject::invokeMethod( this, "slotWork", Qt::QueuedConnection); 0205 } 0206 0207 void GrepJob::slotWork() 0208 { 0209 Q_ASSERT(!m_findThread); 0210 0211 switch(m_workState) 0212 { 0213 case WorkUnstarted: 0214 case WorkDead: 0215 Q_UNREACHABLE(); 0216 break; 0217 case WorkStarting: 0218 m_workState = WorkCollectFiles; 0219 emit showProgress(this, 0,0,0); 0220 QMetaObject::invokeMethod(this, "slotWork", Qt::QueuedConnection); 0221 break; 0222 case WorkCollectFiles: 0223 m_findThread = new GrepFindFilesThread(this, m_directoryChoice, m_settings.depth, m_settings.files, m_settings.exclude, m_settings.projectFilesOnly); 0224 emit showMessage(this, i18n("Collecting files...")); 0225 connect(m_findThread, &GrepFindFilesThread::finished, this, &GrepJob::slotFindFinished); 0226 m_findThread->start(); 0227 break; 0228 case WorkGrep: 0229 if(m_fileIndex < m_fileList.length()) 0230 { 0231 emit showProgress(this, 0, m_fileList.length(), m_fileIndex); 0232 if(m_fileIndex < m_fileList.length()) { 0233 QString file = m_fileList[m_fileIndex].toLocalFile(); 0234 GrepOutputItem::List items = grepFile(file, m_regExp); 0235 0236 if(!items.isEmpty()) 0237 { 0238 m_findSomething = true; 0239 emit foundMatches(file, items); 0240 } 0241 0242 m_fileIndex++; 0243 } 0244 QMetaObject::invokeMethod(this, "slotWork", Qt::QueuedConnection); 0245 } 0246 else 0247 { 0248 die(); 0249 } 0250 break; 0251 case WorkCancelled: 0252 dieAfterCancellation(); 0253 break; 0254 } 0255 } 0256 0257 void GrepJob::die() 0258 { 0259 emit hideProgress(this); 0260 emit clearMessage(this); 0261 m_workState = WorkDead; 0262 emitResult(); 0263 } 0264 0265 void GrepJob::dieAfterCancellation() 0266 { 0267 Q_ASSERT(m_workState == WorkCancelled); 0268 m_errorMessage = i18n("Search aborted"); 0269 die(); 0270 } 0271 0272 void GrepJob::start() 0273 { 0274 if (m_workState != WorkUnstarted) { 0275 qCWarning(PLUGIN_GREPVIEW) << "cannot start a grep job more than once, current state:" << m_workState; 0276 return; 0277 } 0278 0279 m_workState = WorkStarting; 0280 0281 m_outputModel->clear(); 0282 0283 connect(this, &GrepJob::foundMatches, 0284 m_outputModel, &GrepOutputModel::appendOutputs, Qt::QueuedConnection); 0285 0286 QMetaObject::invokeMethod(this, "slotWork", Qt::QueuedConnection); 0287 } 0288 0289 bool GrepJob::doKill() 0290 { 0291 if (m_workState == WorkUnstarted || m_workState == WorkDead) { 0292 m_workState = WorkDead; 0293 return true; 0294 } 0295 if (m_workState != WorkCancelled) { 0296 if (m_findThread) { 0297 Q_ASSERT(m_workState == WorkCollectFiles); 0298 m_findThread->tryAbort(); 0299 } 0300 m_workState = WorkCancelled; 0301 } 0302 // Do not let KJob finish immediately if the state was neither Unstarted nor Dead: 0303 // * If m_findThread != nullptr, let it finish first. 0304 // * Otherwise, slotWork() is about to be invoked. Don't want this to be destroyed by KJob before that happens. 0305 return false; 0306 } 0307 0308 void GrepJob::testFinishState(KJob *job) 0309 { 0310 Q_ASSERT(m_workState == WorkDead); 0311 if(!job->error()) 0312 { 0313 if (!m_errorMessage.isEmpty()) { 0314 emit showErrorMessage(i18n("Failed: %1", m_errorMessage)); 0315 } 0316 else if (!m_findSomething) { 0317 emit showMessage(this, i18n("No results found")); 0318 } 0319 } 0320 } 0321 0322 void GrepJob::setOutputModel(GrepOutputModel* model) 0323 { 0324 m_outputModel = model; 0325 } 0326 0327 void GrepJob::setDirectoryChoice(const QList<QUrl>& choice) 0328 { 0329 m_directoryChoice = choice; 0330 } 0331 0332 void GrepJob::setSettings(const GrepJobSettings& settings) 0333 { 0334 m_settings = settings; 0335 0336 setObjectName(i18n("Grep: %1", m_settings.pattern)); 0337 } 0338 0339 GrepJobSettings GrepJob::settings() const 0340 { 0341 return m_settings; 0342 } 0343 0344 #include "moc_grepjob.cpp"