File indexing completed on 2024-05-05 05:51:20

0001 /*
0002     SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 #include "cmakecompletion.h"
0007 
0008 #include "hostprocess.h"
0009 
0010 #include <KTextEditor/Document>
0011 #include <KTextEditor/View>
0012 
0013 #include <QProcess>
0014 #include <QStandardPaths>
0015 
0016 struct CMakeComplData {
0017     std::vector<QByteArray> m_commands;
0018     std::vector<QByteArray> m_vars;
0019     std::vector<QByteArray> m_props;
0020 };
0021 
0022 static QByteArray runCMake(const QString &arg)
0023 {
0024     static const auto cmakeExecutable = safeExecutableName(QStringLiteral("cmake"));
0025     if (cmakeExecutable.isEmpty()) {
0026         return {};
0027     }
0028 
0029     QProcess p;
0030     startHostProcess(p, cmakeExecutable, {arg});
0031     if (p.waitForStarted() && p.waitForFinished()) {
0032         if (p.exitCode() == 0 && p.exitStatus() == QProcess::NormalExit) {
0033             return p.readAllStandardOutput();
0034         }
0035     }
0036     return {};
0037 }
0038 
0039 std::vector<QByteArray> parseList(const QByteArray &ba, int reserve)
0040 {
0041     std::vector<QByteArray> ret;
0042     ret.reserve(reserve);
0043     int start = 0;
0044     int next = ba.indexOf('\n', start);
0045 
0046     while (next > 0) {
0047         ret.push_back(ba.mid(start, next - start));
0048         start = next + 1;
0049         next = ba.indexOf('\n', start);
0050     }
0051     return ret;
0052 }
0053 
0054 static CMakeComplData fetchData()
0055 {
0056     CMakeComplData ret;
0057 
0058     auto cmds = runCMake(QStringLiteral("--help-command-list"));
0059     auto vars = runCMake(QStringLiteral("--help-variable-list"));
0060     auto props = runCMake(QStringLiteral("--help-property-list"));
0061 
0062     // The numbers are from counting the number of props/vars/cmds
0063     // from the output of cmake --help-*
0064     ret.m_commands = parseList(cmds, 125);
0065     ret.m_vars = parseList(vars, 627);
0066     ret.m_props = parseList(props, 497);
0067 
0068     return ret;
0069 }
0070 
0071 bool CMakeCompletion::isCMakeFile(const QUrl &url)
0072 {
0073     auto urlString = url.fileName();
0074     return urlString == QStringLiteral("CMakeLists.txt") || urlString.endsWith(QStringLiteral(".cmake"));
0075 }
0076 
0077 static void append(std::vector<CMakeCompletion::Completion> &out, std::vector<QByteArray> &&in, CMakeCompletion::Completion::Kind kind)
0078 {
0079     for (auto &&s : in) {
0080         out.push_back({kind, std::move(s)});
0081     }
0082 }
0083 
0084 CMakeCompletion::CMakeCompletion(QObject *parent)
0085     : KTextEditor::CodeCompletionModel(parent)
0086 {
0087 }
0088 
0089 void CMakeCompletion::completionInvoked(KTextEditor::View *view, const KTextEditor::Range &, InvocationType)
0090 {
0091     if (!m_hasData && isCMakeFile(view->document()->url())) {
0092         CMakeComplData data = fetchData();
0093         append(m_matches, std::move(data.m_commands), Completion::Compl_COMMAND);
0094         append(m_matches, std::move(data.m_vars), Completion::Compl_VARIABLE);
0095         append(m_matches, std::move(data.m_props), Completion::Compl_PROPERTY);
0096         setRowCount(m_matches.size());
0097         m_hasData = true;
0098     }
0099 }
0100 
0101 bool CMakeCompletion::shouldStartCompletion(KTextEditor::View *view, const QString &insertedText, bool userInsertion, const KTextEditor::Cursor &position)
0102 {
0103     if (!userInsertion) {
0104         return false;
0105     }
0106     if (insertedText.isEmpty()) {
0107         return false;
0108     }
0109     // Dont invoke for comments, wont handle everything of course
0110     if (view->document()->line(position.line()).startsWith(QLatin1Char('#'))) {
0111         return false;
0112     }
0113 
0114     return isCMakeFile(view->document()->url());
0115 }
0116 
0117 int CMakeCompletion::rowCount(const QModelIndex &parent) const
0118 {
0119     return parent.isValid() ? 0 : m_matches.size();
0120 }
0121 
0122 static QIcon getIcon(CMakeCompletion::Completion::Kind type)
0123 {
0124     using Kind = CMakeCompletion::Completion::Kind;
0125     if (type == Kind::Compl_PROPERTY) {
0126         static const QIcon icon(QIcon::fromTheme(QStringLiteral("code-block")));
0127         return icon;
0128     } else if (type == Kind::Compl_COMMAND) {
0129         static const QIcon icon(QIcon::fromTheme(QStringLiteral("code-function")));
0130         return icon;
0131     } else if (type == Kind::Compl_VARIABLE) {
0132         static const QIcon icon(QIcon::fromTheme(QStringLiteral("code-variable")));
0133         return icon;
0134     } else {
0135         Q_UNREACHABLE();
0136         return {};
0137     }
0138 }
0139 
0140 QVariant CMakeCompletion::data(const QModelIndex &index, int role) const
0141 {
0142     if (!index.isValid())
0143         return {};
0144 
0145     if (role != Qt::DisplayRole && role != Qt::DecorationRole)
0146         return {};
0147 
0148     if (index.column() == KTextEditor::CodeCompletionModel::Name && role == Qt::DisplayRole) {
0149         return m_matches.at(index.row()).text;
0150     } else if (role == Qt::DecorationRole && index.column() == KTextEditor::CodeCompletionModel::Icon) {
0151         return getIcon(m_matches.at(index.row()).kind);
0152     }
0153 
0154     return {};
0155 }
0156 
0157 #include "moc_cmakecompletion.cpp"