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 }