File indexing completed on 2024-04-28 05:49:10
0001 /* This file is part of the Kate project. 0002 * 0003 * SPDX-FileCopyrightText: 2012 Christoph Cullmann <cullmann@kde.org> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "kateprojectworker.h" 0009 #include "kateprojectindex.h" 0010 #include "kateprojectitem.h" 0011 0012 #include <bytearraysplitter.h> 0013 #include <gitprocess.h> 0014 0015 #include "hostprocess.h" 0016 #include <QDir> 0017 #include <QDirIterator> 0018 #include <QFile> 0019 #include <QFileInfo> 0020 #include <QProcess> 0021 #include <QRegularExpression> 0022 #include <QSet> 0023 #include <QStandardPaths> 0024 #include <QThread> 0025 #include <QtConcurrent> 0026 0027 #include <algorithm> 0028 #include <tuple> 0029 #include <vector> 0030 0031 KateProjectWorker::KateProjectWorker(const QString &baseDir, const QString &indexDir, const QVariantMap &projectMap, bool force) 0032 : m_baseDir(baseDir) 0033 , m_indexDir(indexDir) 0034 , m_projectMap(projectMap) 0035 , m_force(force) 0036 { 0037 Q_ASSERT(!m_baseDir.isEmpty()); 0038 } 0039 0040 void KateProjectWorker::run() 0041 { 0042 /** 0043 * Create dummy top level parent item and empty map inside shared pointers 0044 * then load the project recursively 0045 */ 0046 KateProjectSharedQStandardItem topLevel(new QStandardItem()); 0047 KateProjectSharedQHashStringItem file2Item(new QHash<QString, KateProjectItem *>()); 0048 loadProject(topLevel.get(), m_projectMap, file2Item.get(), m_baseDir); 0049 0050 /** 0051 * sort the stuff once recursively, this is a LOT faster than once sorting the list 0052 * as we have normally not all stuff in on level of directory 0053 */ 0054 topLevel->sortChildren(0); 0055 0056 /** 0057 * decide if we need to create an index 0058 * if we need to do so, we will need to create a copy of the file list for later use 0059 * before this was default on, which is dangerous for large repositories, e.g. out-of-memory or out-of-disk 0060 * if specified in project map; use that setting, otherwise fall back to global setting 0061 */ 0062 bool indexEnabled = !m_indexDir.isEmpty(); 0063 const QVariantMap ctagsMap = m_projectMap[QStringLiteral("ctags")].toMap(); 0064 auto indexValue = ctagsMap[QStringLiteral("enable")]; 0065 if (!indexValue.isNull()) { 0066 indexEnabled = indexValue.toBool(); 0067 } 0068 0069 /** 0070 * create some local backup of some data we need for further processing! 0071 * this is expensive, therefore only really do this if required! 0072 */ 0073 QStringList files; 0074 if (indexEnabled) { 0075 files = file2Item->keys(); 0076 } 0077 0078 /** 0079 * hand out our model item & mapping to the main thread 0080 * that will let Kate already show the project, even before index processing starts 0081 */ 0082 Q_EMIT loadDone(topLevel, file2Item); 0083 0084 /** 0085 * without indexing, we are even done with all stuff here 0086 */ 0087 if (!indexEnabled) { 0088 Q_EMIT loadIndexDone(KateProjectSharedProjectIndex()); 0089 return; 0090 } 0091 0092 /** 0093 * create new index, this will do the loading in the constructor 0094 * wrap it into shared pointer for transfer to main thread 0095 */ 0096 KateProjectSharedProjectIndex index(new KateProjectIndex(m_baseDir, m_indexDir, files, ctagsMap, m_force)); 0097 Q_EMIT loadIndexDone(index); 0098 } 0099 0100 void KateProjectWorker::loadProject(QStandardItem *parent, const QVariantMap &project, QHash<QString, KateProjectItem *> *file2Item, const QString &baseDir) 0101 { 0102 /** 0103 * recurse to sub-projects FIRST 0104 */ 0105 const QVariantList subGroups = project[QStringLiteral("projects")].toList(); 0106 for (const QVariant &subGroupVariant : subGroups) { 0107 /** 0108 * convert to map and get name, else skip 0109 */ 0110 const QVariantMap subProject = subGroupVariant.toMap(); 0111 const QString keyName = QStringLiteral("name"); 0112 if (subProject[keyName].toString().isEmpty()) { 0113 continue; 0114 } 0115 0116 /** 0117 * recurse 0118 */ 0119 QStandardItem *subProjectItem = new KateProjectItem(KateProjectItem::Project, subProject[keyName].toString()); 0120 loadProject(subProjectItem, subProject, file2Item, baseDir); 0121 parent->appendRow(subProjectItem); 0122 } 0123 0124 /** 0125 * load all specified files 0126 */ 0127 const QString keyFiles = QStringLiteral("files"); 0128 const QVariantList files = project[keyFiles].toList(); 0129 for (const QVariant &fileVariant : files) { 0130 loadFilesEntry(parent, fileVariant.toMap(), file2Item, baseDir); 0131 } 0132 } 0133 0134 /** 0135 * small helper to construct directory parent items 0136 * @param dir2Item map for path => item 0137 * @param path current path we need item for 0138 * @return correct parent item for given path, will reuse existing ones 0139 */ 0140 QStandardItem *KateProjectWorker::directoryParent(const QDir &base, QHash<QString, QStandardItem *> &dir2Item, QString path) 0141 { 0142 /** 0143 * throw away simple / 0144 */ 0145 if (path == QLatin1String("/")) { 0146 path = QString(); 0147 } 0148 0149 /** 0150 * quick check: dir already seen? 0151 */ 0152 const auto existingIt = dir2Item.find(path); 0153 if (existingIt != dir2Item.end()) { 0154 return existingIt.value(); 0155 } 0156 0157 /** 0158 * else: construct recursively 0159 */ 0160 const int slashIndex = path.lastIndexOf(QLatin1Char('/')); 0161 0162 /** 0163 * no slash? 0164 * simple, no recursion, append new item toplevel 0165 */ 0166 if (slashIndex < 0) { 0167 const auto item = new KateProjectItem(KateProjectItem::Directory, path); 0168 item->setData(base.absoluteFilePath(path), Qt::UserRole); 0169 dir2Item[path] = item; 0170 dir2Item[QString()]->appendRow(item); 0171 return item; 0172 } 0173 0174 /** 0175 * else, split and recurse 0176 */ 0177 const QString leftPart = path.left(slashIndex); 0178 const QString rightPart = path.right(path.size() - (slashIndex + 1)); 0179 0180 /** 0181 * special handling if / with nothing on one side are found 0182 */ 0183 if (leftPart.isEmpty() || rightPart.isEmpty()) { 0184 return directoryParent(base, dir2Item, leftPart.isEmpty() ? rightPart : leftPart); 0185 } 0186 0187 /** 0188 * else: recurse on left side 0189 */ 0190 const auto item = new KateProjectItem(KateProjectItem::Directory, rightPart); 0191 item->setData(base.absoluteFilePath(path), Qt::UserRole); 0192 dir2Item[path] = item; 0193 directoryParent(base, dir2Item, leftPart)->appendRow(item); 0194 return item; 0195 } 0196 0197 void KateProjectWorker::loadFilesEntry(QStandardItem *parent, 0198 const QVariantMap &filesEntry, 0199 QHash<QString, KateProjectItem *> *file2Item, 0200 const QString &baseDir) 0201 { 0202 QDir dir(baseDir); 0203 if (!dir.cd(filesEntry[QStringLiteral("directory")].toString())) { 0204 return; 0205 } 0206 0207 /** 0208 * handle linked projects, if any 0209 * one can reference other projects by specifying the path to them 0210 */ 0211 QStringList linkedProjects = filesEntry[QStringLiteral("projects")].toStringList(); 0212 if (!linkedProjects.empty()) { 0213 /** 0214 * ensure project files are made absolute in respect to correct base dir 0215 */ 0216 for (auto &project : linkedProjects) { 0217 project = dir.absoluteFilePath(project); 0218 } 0219 0220 /** 0221 * users might have specified duplicates, this can't happen for the other ways 0222 */ 0223 linkedProjects.removeDuplicates(); 0224 0225 /** 0226 * filter out all directories that have no .kateproject inside! 0227 */ 0228 linkedProjects.erase(std::remove_if(linkedProjects.begin(), 0229 linkedProjects.end(), 0230 [](const QString &item) { 0231 const QFileInfo projectFile(item + QLatin1String("/.kateproject")); 0232 return !projectFile.exists() || !projectFile.isFile(); 0233 }), 0234 linkedProjects.end()); 0235 0236 /** 0237 * we sort the projects, below we require that we walk them in order: 0238 * lala 0239 * lala/test 0240 * mow 0241 * mow/test2 0242 */ 0243 std::sort(linkedProjects.begin(), linkedProjects.end()); 0244 0245 /** 0246 * now add our projects to the current item parent 0247 * later the tree view will e.g. allow to jump to the sub-projects 0248 */ 0249 QHash<QString, QStandardItem *> dir2Item; 0250 dir2Item[QString()] = parent; 0251 for (const auto &filePath : linkedProjects) { 0252 /** 0253 * cheap file name computation 0254 * we do this A LOT, QFileInfo is very expensive just for this operation 0255 */ 0256 const int slashIndex = filePath.lastIndexOf(QLatin1Char('/')); 0257 const QString fileName = (slashIndex < 0) ? filePath : filePath.mid(slashIndex + 1); 0258 const QString filePathName = (slashIndex < 0) ? QString() : filePath.left(slashIndex); 0259 0260 /** 0261 * construct the item with right directory prefix 0262 * already hang in directories in tree 0263 */ 0264 KateProjectItem *fileItem = new KateProjectItem(KateProjectItem::LinkedProject, fileName); 0265 fileItem->setData(filePath, Qt::UserRole); 0266 0267 /** 0268 * projects are directories, register them, we walk in order over the projects 0269 * even if the nest, toplevel ones would have been done before! 0270 */ 0271 dir2Item[dir.relativeFilePath(filePath)] = fileItem; 0272 0273 // get the directory's relative path to the base directory 0274 QString dirRelPath = dir.relativeFilePath(filePathName); 0275 // if the relative path is ".", clean it up 0276 if (dirRelPath == QLatin1Char('.')) { 0277 dirRelPath = QString(); 0278 } 0279 0280 // put in our item to the right directory parent 0281 directoryParent(dir, dir2Item, dirRelPath)->appendRow(fileItem); 0282 } 0283 0284 /** 0285 * files with linked projects will ignore all other stuff inside 0286 */ 0287 return; 0288 } 0289 0290 /** 0291 * get list of files for this directory, might query the VCS 0292 */ 0293 const QList<QString> files = findFiles(dir, filesEntry); 0294 0295 QStringList excludeFolderPatterns = m_projectMap.value(QStringLiteral("exclude_patterns")).toStringList(); 0296 std::vector<QRegularExpression> excludeRegexps; 0297 excludeRegexps.reserve(excludeFolderPatterns.size()); 0298 for (const auto &pattern : excludeFolderPatterns) { 0299 excludeRegexps.push_back(QRegularExpression(pattern, QRegularExpression::DontCaptureOption)); 0300 } 0301 0302 /** 0303 * sort out non-files 0304 * even for git, that just reports non-directories, we need to filter out e.g. sym-links to directories 0305 * we use map, not filter, less locking! 0306 * we compute here already the KateProjectItem items we want to use later 0307 * this happens in the threads, we later skip all nullptr entries 0308 */ 0309 std::vector<std::tuple<QString, QString, KateProjectItem *>> preparedItems; 0310 preparedItems.reserve(files.size()); 0311 for (const auto &item : files) 0312 preparedItems.emplace_back(item, QString(), nullptr); 0313 QtConcurrent::blockingMap(preparedItems, [dir, excludeRegexps](std::tuple<QString, QString, KateProjectItem *> &item) { 0314 /** 0315 * cheap file name computation 0316 * we do this A LOT, QFileInfo is very expensive just for this operation 0317 * we remember fullFilePath for later use and overwrite filePath with the part without the filename for later use, too 0318 */ 0319 auto &[filePath, fullFilePath, projectItem] = item; 0320 const QFileInfo info(dir, filePath); 0321 fullFilePath = info.absoluteFilePath(); 0322 0323 for (const auto &excludePattern : excludeRegexps) { 0324 if (excludePattern.match(filePath).hasMatch()) { 0325 return; 0326 } 0327 } 0328 0329 const int slashIndex = filePath.lastIndexOf(QLatin1Char('/')); 0330 const QString fileName = (slashIndex < 0) ? filePath : filePath.mid(slashIndex + 1); 0331 filePath = (slashIndex < 0) ? QString() : filePath.left(slashIndex); 0332 0333 /** 0334 * construct the item with info about filename + full file path 0335 */ 0336 if (info.isFile()) { 0337 projectItem = new KateProjectItem(KateProjectItem::File, fileName); 0338 projectItem->setData(fullFilePath, Qt::UserRole); 0339 } 0340 else if(info.isDir() && QDir(fullFilePath).isEmpty()) 0341 { 0342 projectItem = new KateProjectItem(KateProjectItem::Directory, fileName); 0343 projectItem->setData(fullFilePath, Qt::UserRole); 0344 } 0345 }); 0346 0347 /** 0348 * put the pre-computed file items in our file2Item hash + create the needed directory items 0349 * all other stuff was already handled inside the worker threads to avoid main thread stalling 0350 */ 0351 QHash<QString, QStandardItem *> dir2Item; 0352 dir2Item[QString()] = parent; 0353 file2Item->reserve(file2Item->size() + preparedItems.size()); 0354 for (const auto &item : preparedItems) { 0355 /** 0356 * skip all entries without an item => that are filtered out non-files 0357 */ 0358 const auto &[filePath, fullFilePath, projectItem] = item; 0359 if (!projectItem) { 0360 continue; 0361 } 0362 0363 /** 0364 * register the item in the full file path => item hash 0365 * create needed directory parents 0366 */ 0367 (*file2Item)[fullFilePath] = projectItem; 0368 directoryParent(dir, dir2Item, filePath)->appendRow(projectItem); 0369 } 0370 } 0371 0372 QList<QString> KateProjectWorker::findFiles(const QDir &dir, const QVariantMap &filesEntry) 0373 { 0374 /** 0375 * shall we collect files recursively or not? 0376 */ 0377 const bool recursive = !filesEntry.contains(QLatin1String("recursive")) || filesEntry[QStringLiteral("recursive")].toBool(); 0378 0379 /** 0380 * try the different version control systems first 0381 */ 0382 0383 if (filesEntry[QStringLiteral("git")].toBool()) { 0384 return filesFromGit(dir, recursive); 0385 } 0386 0387 if (filesEntry[QStringLiteral("svn")].toBool()) { 0388 return filesFromSubversion(dir, recursive); 0389 } 0390 0391 if (filesEntry[QStringLiteral("hg")].toBool()) { 0392 return filesFromMercurial(dir, recursive); 0393 } 0394 0395 if (filesEntry[QStringLiteral("darcs")].toBool()) { 0396 return filesFromDarcs(dir, recursive); 0397 } 0398 0399 if (filesEntry[QStringLiteral("fossil")].toBool()) { 0400 return filesFromFossil(dir, recursive); 0401 } 0402 0403 /** 0404 * if we arrive here, we have some manual specification of files, no VCS 0405 */ 0406 0407 /** 0408 * try explicit list of stuff 0409 */ 0410 QStringList userGivenFilesList = filesEntry[QStringLiteral("list")].toStringList(); 0411 if (!userGivenFilesList.empty()) { 0412 /** 0413 * make the files absolute relative to current dir 0414 * all code later requires this and the filesFrom... routines do this, too, internally 0415 * even without this, the tree views will show them, but opening them will create new elements! 0416 */ 0417 for (auto &file : userGivenFilesList) { 0418 file = dir.absoluteFilePath(file); 0419 } 0420 0421 /** 0422 * users might have specified duplicates, this can't happen for the other ways 0423 */ 0424 userGivenFilesList.removeDuplicates(); 0425 return userGivenFilesList.toVector(); 0426 } 0427 0428 /** 0429 * shall we collect hidden files or not? 0430 */ 0431 const bool hidden = filesEntry.contains(QLatin1String("hidden")) && filesEntry[QStringLiteral("hidden")].toBool(); 0432 0433 /** 0434 * if nothing found for that, try to use filters to scan the directory 0435 * here we only get files 0436 */ 0437 return filesFromDirectory(dir, recursive, hidden, filesEntry[QStringLiteral("filters")].toStringList()); 0438 } 0439 0440 QList<QString> KateProjectWorker::filesFromGit(const QDir &dir, bool recursive) 0441 { 0442 /** 0443 * query files via ls-files and make them absolute afterwards 0444 */ 0445 0446 /** 0447 * git ls-files -z results a bytearray where each entry is \0-terminated. 0448 * NOTE: Without -z, Umlauts such as "Der Bäcker/Das Brötchen.txt" do not work (#389415) 0449 * 0450 * use --recurse-submodules, there since git 2.11 (released 2016) 0451 * our own submodules handling code leads to file duplicates 0452 */ 0453 0454 QStringList lsFilesArgs{QStringLiteral("ls-files"), QStringLiteral("-z"), QStringLiteral("--recurse-submodules"), QStringLiteral(".")}; 0455 0456 /** 0457 * ls-files untracked 0458 */ 0459 QStringList lsFilesUntrackedArgs{QStringLiteral("ls-files"), 0460 QStringLiteral("-z"), 0461 QStringLiteral("--others"), 0462 QStringLiteral("--exclude-standard"), 0463 QStringLiteral(".")}; 0464 0465 /** 0466 * for recent enough git versions ensure we don't show duplicated files 0467 */ 0468 const auto [major, minor] = getGitVersion(dir.absolutePath()); 0469 if (major > 2 || (major == 2 && minor >= 31)) { 0470 lsFilesArgs.insert(3, QStringLiteral("--deduplicate")); 0471 lsFilesUntrackedArgs.insert(4, QStringLiteral("--deduplicate")); 0472 } 0473 0474 // ls-files + ls-files untracked 0475 return gitFiles(dir, recursive, lsFilesArgs) << gitFiles(dir, recursive, lsFilesUntrackedArgs); 0476 } 0477 0478 QList<QString> KateProjectWorker::gitFiles(const QDir &dir, bool recursive, const QStringList &args) 0479 { 0480 QList<QString> files; 0481 QProcess git; 0482 if (!setupGitProcess(git, dir.absolutePath(), args)) { 0483 return files; 0484 } 0485 startHostProcess(git, QProcess::ReadOnly); 0486 if (!git.waitForStarted() || !git.waitForFinished(-1)) { 0487 return files; 0488 } 0489 0490 const QByteArray b = git.readAllStandardOutput(); 0491 for (strview byteArray : ByteArraySplitter(b, '\0')) { 0492 if (byteArray.empty()) { 0493 continue; 0494 } 0495 if (!recursive && (byteArray.find('/') != std::string::npos)) { 0496 continue; 0497 } 0498 files.append(byteArray.toString()); 0499 } 0500 return files; 0501 } 0502 0503 QList<QString> KateProjectWorker::filesFromMercurial(const QDir &dir, bool recursive) 0504 { 0505 // only use version control from PATH 0506 QList<QString> files; 0507 static const auto fullExecutablePath = safeExecutableName(QStringLiteral("hg")); 0508 if (fullExecutablePath.isEmpty()) { 0509 return files; 0510 } 0511 0512 QProcess hg; 0513 hg.setWorkingDirectory(dir.absolutePath()); 0514 QStringList args; 0515 args << QStringLiteral("manifest") << QStringLiteral("."); 0516 startHostProcess(hg, fullExecutablePath, args, QProcess::ReadOnly); 0517 if (!hg.waitForStarted() || !hg.waitForFinished(-1)) { 0518 return files; 0519 } 0520 0521 const QStringList relFiles = QString::fromLocal8Bit(hg.readAllStandardOutput()).split(QRegularExpression(QStringLiteral("[\n\r]")), Qt::SkipEmptyParts); 0522 0523 files.reserve(relFiles.size()); 0524 for (const QString &relFile : relFiles) { 0525 if (!recursive && (relFile.indexOf(QLatin1Char('/')) != -1)) { 0526 continue; 0527 } 0528 0529 files.append(relFile); 0530 } 0531 0532 return files; 0533 } 0534 0535 QList<QString> KateProjectWorker::filesFromSubversion(const QDir &dir, bool recursive) 0536 { 0537 // only use version control from PATH 0538 QList<QString> files; 0539 static const auto fullExecutablePath = safeExecutableName(QStringLiteral("svn")); 0540 if (fullExecutablePath.isEmpty()) { 0541 return files; 0542 } 0543 0544 QProcess svn; 0545 svn.setWorkingDirectory(dir.absolutePath()); 0546 QStringList args; 0547 args << QStringLiteral("status") << QStringLiteral("--verbose") << QStringLiteral("."); 0548 if (recursive) { 0549 args << QStringLiteral("--depth=infinity"); 0550 } else { 0551 args << QStringLiteral("--depth=files"); 0552 } 0553 startHostProcess(svn, fullExecutablePath, args, QProcess::ReadOnly); 0554 if (!svn.waitForStarted() || !svn.waitForFinished(-1)) { 0555 return files; 0556 } 0557 0558 /** 0559 * get output and split up into lines 0560 */ 0561 const QStringList lines = QString::fromLocal8Bit(svn.readAllStandardOutput()).split(QRegularExpression(QStringLiteral("[\n\r]")), Qt::SkipEmptyParts); 0562 0563 /** 0564 * remove start of line that is no filename, sort out unknown and ignore 0565 */ 0566 bool first = true; 0567 int prefixLength = -1; 0568 0569 files.reserve(lines.size()); 0570 for (const QString &line : lines) { 0571 /** 0572 * get length of stuff to cut 0573 */ 0574 if (first) { 0575 /** 0576 * try to find ., else fail 0577 */ 0578 prefixLength = line.lastIndexOf(QLatin1Char('.')); 0579 if (prefixLength < 0) { 0580 break; 0581 } 0582 0583 /** 0584 * skip first 0585 */ 0586 first = false; 0587 continue; 0588 } 0589 0590 /** 0591 * get file, if not unknown or ignored 0592 * prepend directory path 0593 */ 0594 if ((line.size() > prefixLength) && line[0] != QLatin1Char('?') && line[0] != QLatin1Char('I')) { 0595 files.append(line.right(line.size() - prefixLength)); 0596 } 0597 } 0598 0599 return files; 0600 } 0601 0602 QList<QString> KateProjectWorker::filesFromDarcs(const QDir &dir, bool recursive) 0603 { 0604 // only use version control from PATH 0605 QList<QString> files; 0606 static const auto fullExecutablePath = safeExecutableName(QStringLiteral("darcs")); 0607 if (fullExecutablePath.isEmpty()) { 0608 return files; 0609 } 0610 0611 QString root; 0612 { 0613 QProcess darcs; 0614 darcs.setWorkingDirectory(dir.absolutePath()); 0615 QStringList args; 0616 args << QStringLiteral("list") << QStringLiteral("repo"); 0617 0618 startHostProcess(darcs, fullExecutablePath, args, QProcess::ReadOnly); 0619 0620 if (!darcs.waitForStarted() || !darcs.waitForFinished(-1)) { 0621 return files; 0622 } 0623 0624 auto str = QString::fromLocal8Bit(darcs.readAllStandardOutput()); 0625 QRegularExpression exp(QStringLiteral("Root: ([^\\n\\r]*)")); 0626 auto match = exp.match(str); 0627 0628 if (!match.hasMatch()) { 0629 return files; 0630 } 0631 0632 root = match.captured(1); 0633 } 0634 0635 QStringList relFiles; 0636 { 0637 QProcess darcs; 0638 QStringList args; 0639 darcs.setWorkingDirectory(dir.absolutePath()); 0640 args << QStringLiteral("list") << QStringLiteral("files") << QStringLiteral("--no-directories") << QStringLiteral("--pending"); 0641 0642 startHostProcess(darcs, fullExecutablePath, args, QProcess::ReadOnly); 0643 0644 if (!darcs.waitForStarted() || !darcs.waitForFinished(-1)) { 0645 return files; 0646 } 0647 0648 relFiles = QString::fromLocal8Bit(darcs.readAllStandardOutput()).split(QRegularExpression(QStringLiteral("[\n\r]")), Qt::SkipEmptyParts); 0649 } 0650 0651 files.reserve(relFiles.size()); 0652 for (const QString &relFile : qAsConst(relFiles)) { 0653 const QString path = dir.relativeFilePath(root + QLatin1String("/") + relFile); 0654 0655 if ((!recursive && (relFile.indexOf(QLatin1Char('/')) != -1)) || (recursive && (relFile.indexOf(QLatin1String("..")) == 0))) { 0656 continue; 0657 } 0658 0659 files.append(path); 0660 } 0661 0662 return files; 0663 } 0664 0665 QList<QString> KateProjectWorker::filesFromFossil(const QDir &dir, bool recursive) 0666 { 0667 // only use version control from PATH 0668 QList<QString> files; 0669 static const auto fullExecutablePath = safeExecutableName(QStringLiteral("fossil")); 0670 if (fullExecutablePath.isEmpty()) { 0671 return files; 0672 } 0673 0674 QProcess fossil; 0675 fossil.setWorkingDirectory(dir.absolutePath()); 0676 QStringList args; 0677 args << QStringLiteral("ls"); 0678 startHostProcess(fossil, fullExecutablePath, args, QProcess::ReadOnly); 0679 if (!fossil.waitForStarted() || !fossil.waitForFinished(-1)) { 0680 return files; 0681 } 0682 0683 const QStringList relFiles = QString::fromLocal8Bit(fossil.readAllStandardOutput()).split(QRegularExpression(QStringLiteral("[\n\r]")), Qt::SkipEmptyParts); 0684 0685 files.reserve(relFiles.size()); 0686 for (const QString &relFile : relFiles) { 0687 if (!recursive && (relFile.indexOf(QLatin1Char('/')) != -1)) { 0688 continue; 0689 } 0690 0691 files.append(relFile); 0692 } 0693 0694 return files; 0695 } 0696 0697 QList<QString> KateProjectWorker::filesFromDirectory(QDir dir, bool recursive, bool hidden, const QStringList &filters) 0698 { 0699 /** 0700 * setup our filters 0701 */ 0702 QDir::Filters filterFlags = QDir::Files | QDir::Dirs | QDir::NoDot | QDir::NoDotDot; 0703 if(hidden) 0704 filterFlags |= QDir::Hidden; 0705 dir.setFilter(filterFlags); 0706 if (!filters.isEmpty()) { 0707 dir.setNameFilters(filters); 0708 } 0709 0710 /** 0711 * construct flags for iterator 0712 */ 0713 QDirIterator::IteratorFlags flags = QDirIterator::NoIteratorFlags; 0714 if (recursive) { 0715 flags = flags | QDirIterator::Subdirectories | QDirIterator::FollowSymlinks; 0716 } 0717 0718 /** 0719 * create iterator and collect all files 0720 */ 0721 QList<QString> files; 0722 QDirIterator dirIterator(dir, flags); 0723 const QString dirPath = dir.path() + QLatin1Char('/'); 0724 while (dirIterator.hasNext()) { 0725 dirIterator.next(); 0726 // make it relative path 0727 files.append(dirIterator.filePath().remove(dirPath)); 0728 } 0729 return files; 0730 } 0731 0732 #include "moc_kateprojectworker.cpp"