File indexing completed on 2024-05-05 04:39:24

0001 /*
0002     SPDX-FileCopyrightText: 2008 Aleix Pol <aleixpol@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "cmakecodecompletionmodel.h"
0008 #include <QVariant>
0009 #include <QModelIndex>
0010 #include <QMimeDatabase>
0011 #include <QUrl>
0012 #include <language/duchain/duchain.h>
0013 #include <language/duchain/duchainlock.h>
0014 #include <language/duchain/ducontext.h>
0015 #include <language/duchain/declaration.h>
0016 #include <language/duchain/types/functiontype.h>
0017 #include <language/duchain/types/delayedtype.h>
0018 
0019 #include <cmakeduchaintypes.h>
0020 #include "cmakeutils.h"
0021 #include "icmakedocumentation.h"
0022 
0023 #include <KTextEditor/Document>
0024 #include <KTextEditor/View>
0025 #include <KIO/Global>
0026 #include <KLocalizedString>
0027 
0028 using namespace KTextEditor;
0029 using namespace KDevelop;
0030 
0031 QVector<QString> CMakeCodeCompletionModel::s_commands;
0032 
0033 CMakeCodeCompletionModel::CMakeCodeCompletionModel(QObject* parent)
0034     : CodeCompletionModel(parent)
0035     , m_inside(false)
0036 {}
0037 
0038 bool isFunction(const Declaration* decl)
0039 {
0040     return decl->abstractType().dynamicCast<FunctionType>();
0041 }
0042 
0043 bool isPathChar(QChar c)
0044 {
0045     return c.isLetterOrNumber() || c == QLatin1Char('/') || c == QLatin1Char('.');
0046 }
0047 
0048 QString escapePath(QString path)
0049 {
0050     // see https://cmake.org/Wiki/CMake/Language_Syntax#Escapes
0051     const QString toBeEscaped = QStringLiteral("\"()#$^");
0052 
0053     for(const QChar &ch : toBeEscaped) {
0054         path.replace(ch, QLatin1Char('\\') + ch);
0055     }
0056     return path;
0057 }
0058 
0059 void CMakeCodeCompletionModel::completionInvoked(View* view, const Range& range, InvocationType invocationType)
0060 {
0061     beginResetModel();
0062     if(s_commands.isEmpty()) {
0063         ICMakeDocumentation* cmakedoc=CMake::cmakeDocumentation();
0064         
0065         if(cmakedoc)
0066             s_commands=cmakedoc->names(ICMakeDocumentation::Command);
0067     }
0068     
0069     Q_UNUSED(invocationType);
0070     m_declarations.clear();
0071     DUChainReadLocker lock(DUChain::lock());
0072     KTextEditor::Document* d=view->document();
0073     TopDUContext* ctx = DUChain::self()->chainForDocument( d->url() );
0074     
0075     QString line=d->line(range.end().line());
0076 //     m_inside=line.lastIndexOf('(', range.end().column())>=0;
0077     m_inside=line.lastIndexOf(QLatin1Char('('), range.end().column()-line.size()-1)>=0;
0078     
0079     for(int l=range.end().line(); l>=0 && !m_inside; --l)
0080     {
0081         QString cline=d->line(l);
0082         const QStringRef line = cline.leftRef(cline.indexOf(QLatin1Char('#')));
0083         
0084         int close=line.lastIndexOf(QLatin1Char(')')), open=line.indexOf(QLatin1Char('('));
0085         
0086         if(close>=0 && open>=0) {
0087             m_inside=open>close;
0088             break;
0089         } else if(open>=0) {
0090             m_inside=true;
0091             break;
0092         } else if(close>=0) {
0093             m_inside=false;
0094             break;
0095         }
0096     }
0097     
0098     int numRows = 0;
0099     if(m_inside) {
0100         Cursor start=range.start();
0101         for(; isPathChar(d->characterAt(start)); start-=Cursor(0,1))
0102         {}
0103         start+=Cursor(0,1);
0104         
0105         QString tocomplete=d->text(Range(start, range.end()-Cursor(0,1)));
0106         
0107         int lastdir=tocomplete.lastIndexOf(QLatin1Char('/'));
0108         QString path = KIO::upUrl(QUrl(d->url())).adjusted(QUrl::StripTrailingSlash).toLocalFile()+QLatin1Char('/');
0109         if(lastdir>=0) {
0110             const QStringRef basePath = tocomplete.midRef(0, lastdir);
0111             path += basePath;
0112         }
0113         QDir dir(path);
0114         
0115         const QFileInfoList paths = dir.entryInfoList(QStringList(tocomplete.midRef(lastdir+1)+QLatin1Char('*')),
0116                                               QDir::AllEntries | QDir::NoDotAndDotDot);
0117         m_paths.clear();
0118         m_paths.reserve(paths.size());
0119         for (const QFileInfo& f : paths) {
0120             QString currentPath = f.fileName();
0121             if(f.isDir())
0122                 currentPath += QLatin1Char('/');
0123             m_paths += currentPath;
0124         }
0125         
0126         numRows += m_paths.count();
0127     } else
0128         numRows += s_commands.count();
0129     
0130     if(ctx)
0131     {
0132         const auto list = ctx->allDeclarations( ctx->transformToLocalRevision(KTextEditor::Cursor(range.start())), ctx );
0133 
0134         for (const auto& pair : list) {
0135             bool func=isFunction(pair.first);
0136             if((func && !m_inside) || (!func && m_inside))
0137                 m_declarations.append(pair.first);
0138         }
0139         
0140         numRows+=m_declarations.count();
0141     }
0142     setRowCount(numRows);
0143     endResetModel();
0144 }
0145 
0146 CMakeCodeCompletionModel::Type CMakeCodeCompletionModel::indexType(int row) const
0147 {
0148     if(m_inside)
0149     {
0150         if(row < m_declarations.count()) {
0151             KDevelop::DUChainReadLocker lock;
0152             Declaration* dec = m_declarations.at(row).declaration();
0153             if (dec && dec->type<TargetType>())
0154                 return Target;
0155             else
0156                 return Variable;
0157         } else
0158             return Path;
0159     }
0160     else
0161     {
0162         if(row<m_declarations.count())
0163             return Macro;
0164         else
0165             return Command;
0166     }
0167 }
0168 
0169 QVariant CMakeCodeCompletionModel::data (const QModelIndex & index, int role) const
0170 {
0171     if(!index.isValid())
0172         return QVariant();
0173     Type type=indexType(index.row());
0174 
0175     if(role==Qt::DisplayRole && index.column()==CodeCompletionModel::Name)
0176     {
0177         int pos = index.row();
0178         switch(type) {
0179             case Command:
0180                 return s_commands[pos-m_declarations.size()];
0181             case Path:
0182                 return m_paths[pos-m_declarations.size()];
0183             case Target:
0184             case Variable:
0185             case Macro: {
0186                 DUChainReadLocker lock(DUChain::lock());
0187                 Declaration* dec=m_declarations[pos].data();
0188                 
0189                 return dec ? dec->identifier().toString() : i18n("INVALID");
0190             }
0191         }
0192     }
0193     else if(role==Qt::DisplayRole && index.column()==CodeCompletionModel::Prefix)
0194     {
0195         switch(type)
0196         {
0197             case Command:   return i18nc("@item", "Command");
0198             case Variable:  return i18nc("@item", "Variable");
0199             case Macro:     return i18nc("@item", "Macro");
0200             case Path:      return i18nc("@item", "Path");
0201             case Target:    return i18nc("@item", "Target");
0202         }
0203     }
0204     else if(role==Qt::DecorationRole && index.column()==CodeCompletionModel::Icon)
0205     {
0206         switch(type)
0207         {
0208             case Command:   return QIcon::fromTheme(QStringLiteral("code-block"));
0209             case Variable:  return QIcon::fromTheme(QStringLiteral("code-variable"));
0210             case Macro:     return QIcon::fromTheme(QStringLiteral("code-function"));
0211             case Target:    return QIcon::fromTheme(QStringLiteral("system-run"));
0212             case Path: {
0213                 QUrl url = QUrl::fromUserInput(m_paths[index.row()-m_declarations.size()]);
0214                 QString iconName;
0215                 if (url.isLocalFile()) {
0216                     // don't read contents even if it is a local file
0217                     iconName = QMimeDatabase().mimeTypeForFile(url.toLocalFile(), QMimeDatabase::MatchExtension).iconName();
0218                 }
0219                 else {
0220                     // remote always only looks at the extension
0221                     iconName = QMimeDatabase().mimeTypeForUrl(url).iconName();
0222                 }
0223                 return QIcon::fromTheme(iconName);
0224             }
0225         }
0226     }
0227     else if(role==Qt::DisplayRole && index.column()==CodeCompletionModel::Arguments)
0228     {
0229         switch(type) {
0230             case Variable:
0231             case Command:
0232             case Path:
0233             case Target:
0234                 break;
0235             case Macro:
0236             {
0237                 DUChainReadLocker lock(DUChain::lock());
0238                 int pos=index.row();
0239 
0240                 FunctionType::Ptr func;
0241                 
0242                 if(m_declarations[pos].data())
0243                     func = m_declarations[pos].data()->abstractType().dynamicCast<FunctionType>();
0244 
0245                 if(!func)
0246                     return QVariant();
0247 
0248                 QStringList args;
0249                 const auto arguments = func->arguments();
0250                 args.reserve(arguments.size());
0251                 for (const AbstractType::Ptr& t : arguments) {
0252                     auto delay = t.dynamicCast<DelayedType>();
0253                     args.append(delay ? delay->identifier().toString() : i18n("wrong"));
0254                 }
0255                 return QString(QLatin1Char('(') + args.join(QLatin1String(", ")) + QLatin1Char(')'));
0256             }
0257         }
0258         
0259     }
0260     return QVariant();
0261 }
0262 
0263 void CMakeCodeCompletionModel::executeCompletionItem(View* view, const Range& word, const QModelIndex& idx) const
0264 {
0265     Document* document = view->document();
0266     const int row = idx.row();
0267     switch(indexType(row))
0268     {
0269         case Path: {
0270             Range r=word;
0271             for (QChar c=document->characterAt(r.end()); c.isLetterOrNumber() || c==QLatin1Char('.'); c=document->characterAt(r.end())) {
0272                 r.setEnd(KTextEditor::Cursor(r.end().line(), r.end().column()+1));
0273             }
0274             QString path = data(index(row, Name, QModelIndex())).toString();
0275 
0276             document->replaceText(r, escapePath(path));
0277         }   break;
0278         case Macro:
0279         case Command: {
0280             QString code=data(index(row, Name, QModelIndex())).toString();
0281             if (!document->line(word.start().line()).contains(QLatin1Char('(')))
0282                 code.append(QLatin1Char('('));
0283             
0284             document->replaceText(word, code);
0285         }   break;
0286         case Variable: {
0287             Range r=word, prefix(word.start()-Cursor(0,2), word.start());
0288             QString bef=document->text(prefix);
0289             if(r.start().column()>=2 && bef==QLatin1String("${"))
0290                 r.setStart(KTextEditor::Cursor(r.start().line(), r.start().column()-2));
0291             QString code = QLatin1String("${")+data(index(row, Name, QModelIndex())).toString();
0292             if(document->characterAt(word.end())!=QLatin1Char('}'))
0293                 code += QLatin1Char('}');
0294             
0295             document->replaceText(r, code);
0296         }   break;
0297         case Target:
0298             document->replaceText(word, data(index(row, Name, QModelIndex())).toString());
0299             break;
0300     }
0301 }
0302 
0303 #include "moc_cmakecodecompletionmodel.cpp"