File indexing completed on 2024-04-28 05:49:07

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 "kateprojectindex.h"
0009 
0010 #include <QDir>
0011 #include <QProcess>
0012 #include <QStandardPaths>
0013 
0014 #include "hostprocess.h"
0015 
0016 /**
0017  * include ctags reading
0018  */
0019 #include "ctags/readtags.c"
0020 
0021 KateProjectIndex::KateProjectIndex(const QString &baseDir, const QString &indexDir, const QStringList &files, const QVariantMap &ctagsMap, bool force)
0022     : m_ctagsIndexHandle(nullptr)
0023 {
0024     // allow project to override and specify a (re-usable) indexfile
0025     // otherwise fall-back to a temporary file if nothing specified
0026     auto ctagsFile = ctagsMap.value(QStringLiteral("index_file"));
0027     if (ctagsFile.userType() == QMetaType::QString) {
0028         auto path = ctagsFile.toString();
0029         if (!QDir::isAbsolutePath(path)) {
0030             path = QDir(baseDir).absoluteFilePath(path);
0031         }
0032         m_ctagsIndexFile.reset(new QFile(path));
0033     } else {
0034         // indexDir is typically QDir::tempPath() or otherwise specified in configuration
0035         m_ctagsIndexFile.reset(new QTemporaryFile(indexDir + QStringLiteral("/kate.project.ctags")));
0036     }
0037 
0038     /**
0039      * load ctags
0040      */
0041     loadCtags(files, ctagsMap, force);
0042 }
0043 
0044 KateProjectIndex::~KateProjectIndex()
0045 {
0046     /**
0047      * delete ctags handle if any
0048      */
0049     if (m_ctagsIndexHandle) {
0050         tagsClose(m_ctagsIndexHandle);
0051         m_ctagsIndexHandle = nullptr;
0052     }
0053 }
0054 
0055 void KateProjectIndex::loadCtags(const QStringList &files, const QVariantMap &ctagsMap, bool force)
0056 {
0057     /**
0058      * only overwrite existing index upon reload
0059      * (a temporary index file will never exist)
0060      */
0061     if (m_ctagsIndexFile->exists() && !force) {
0062         openCtags();
0063         return;
0064     }
0065 
0066     /**
0067      * create temporary file
0068      * if not possible, fail
0069      */
0070     if (!m_ctagsIndexFile->open(QIODevice::ReadWrite)) {
0071         return;
0072     }
0073 
0074     /**
0075      * close file again, other process will use it
0076      */
0077     m_ctagsIndexFile->close();
0078 
0079     // only use ctags from PATH
0080     static const auto fullExecutablePath = safeExecutableName(QStringLiteral("ctags"));
0081     if (fullExecutablePath.isEmpty()) {
0082         return;
0083     }
0084 
0085     /**
0086      * try to run ctags for all files in this project
0087      * output to our ctags index file
0088      */
0089     QProcess ctags;
0090     QStringList args;
0091     args << QStringLiteral("-L") << QStringLiteral("-") << QStringLiteral("-f") << m_ctagsIndexFile->fileName() << QStringLiteral("--fields=+K+n");
0092     const QString keyOptions = QStringLiteral("options");
0093     const auto opts = ctagsMap[keyOptions].toList();
0094     for (const QVariant &optVariant : opts) {
0095         args << optVariant.toString();
0096     }
0097     startHostProcess(ctags, fullExecutablePath, args);
0098     if (!ctags.waitForStarted()) {
0099         return;
0100     }
0101 
0102     /**
0103      * write files list and close write channel
0104      */
0105     ctags.write(files.join(QLatin1Char('\n')).toLocal8Bit());
0106     ctags.closeWriteChannel();
0107 
0108     /**
0109      * wait for done
0110      */
0111     if (!ctags.waitForFinished(-1)) {
0112         return;
0113     }
0114 
0115     openCtags();
0116 }
0117 
0118 void KateProjectIndex::openCtags()
0119 {
0120     /**
0121      * file not openable, bad
0122      */
0123     if (!m_ctagsIndexFile->open(QIODevice::ReadOnly)) {
0124         return;
0125     }
0126 
0127     /**
0128      * get size
0129      */
0130     qint64 size = m_ctagsIndexFile->size();
0131 
0132     /**
0133      * close again
0134      */
0135     m_ctagsIndexFile->close();
0136 
0137     /**
0138      * empty file, bad
0139      */
0140     if (!size) {
0141         return;
0142     }
0143 
0144     /**
0145      * close current
0146      */
0147     if (m_ctagsIndexHandle) {
0148         tagsClose(m_ctagsIndexHandle);
0149         m_ctagsIndexHandle = nullptr;
0150     }
0151 
0152     /**
0153      * try to open ctags file
0154      */
0155     tagFileInfo info;
0156     memset(&info, 0, sizeof(tagFileInfo));
0157     m_ctagsIndexHandle = tagsOpen(m_ctagsIndexFile->fileName().toLocal8Bit().constData(), &info);
0158 }
0159 
0160 void KateProjectIndex::findMatches(QStandardItemModel &model, const QString &searchWord, MatchType type, int options)
0161 {
0162     /**
0163      * abort if no ctags index
0164      */
0165     if (!m_ctagsIndexHandle) {
0166         return;
0167     }
0168 
0169     /**
0170      * word to complete
0171      * abort if empty
0172      */
0173     QByteArray word = searchWord.toLocal8Bit();
0174     if (word.isEmpty()) {
0175         return;
0176     }
0177 
0178     /**
0179      * try to search entry
0180      * fail if none found
0181      */
0182     tagEntry entry;
0183     if (options == -1) {
0184         options = TAG_PARTIALMATCH | TAG_OBSERVECASE;
0185     }
0186     if (tagsFind(m_ctagsIndexHandle, &entry, word.constData(), options) != TagSuccess) {
0187         return;
0188     }
0189 
0190     /**
0191      * set to show words only once for completion matches
0192      */
0193     QSet<QString> guard;
0194 
0195     /**
0196      * loop over all found tags
0197      * first one is filled by above find, others by find next
0198      */
0199     do {
0200         /**
0201          * skip if no name
0202          */
0203         if (!entry.name) {
0204             continue;
0205         }
0206 
0207         /**
0208          * get name
0209          */
0210         QString name(QString::fromLocal8Bit(entry.name));
0211 
0212         /**
0213          * construct right items
0214          */
0215         switch (type) {
0216         case CompletionMatches:
0217             /**
0218              * add new completion item, if new name
0219              */
0220             if (!guard.contains(name)) {
0221                 model.appendRow(new QStandardItem(name));
0222                 guard.insert(name);
0223             }
0224             break;
0225 
0226         case FindMatches:
0227             /**
0228              * add new find item, contains of multiple columns
0229              */
0230             QList<QStandardItem *> items;
0231             items << new QStandardItem(name);
0232             items << new QStandardItem(entry.kind ? QString::fromLocal8Bit(entry.kind) : QString());
0233             items << new QStandardItem(entry.file ? QString::fromLocal8Bit(entry.file) : QString());
0234             items << new QStandardItem(QString::number(entry.address.lineNumber));
0235             model.appendRow(items);
0236             break;
0237         }
0238     } while (tagsFindNext(m_ctagsIndexHandle, &entry) == TagSuccess);
0239 }