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"