diff --git a/projectmanagers/cmake/cmakecodecompletionmodel.cpp b/projectmanagers/cmake/cmakecodecompletionmodel.cpp index a719d2d5c5..b25e369ac2 100644 --- a/projectmanagers/cmake/cmakecodecompletionmodel.cpp +++ b/projectmanagers/cmake/cmakecodecompletionmodel.cpp @@ -1,317 +1,318 @@ /* KDevelop CMake Support * * Copyright 2008 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "cmakecodecompletionmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "astfactory.h" #include #include "cmakeutils.h" #include "icmakedocumentation.h" using namespace KTextEditor; using namespace KDevelop; QStringList CMakeCodeCompletionModel::s_commands; CMakeCodeCompletionModel::CMakeCodeCompletionModel(QObject* parent) : CodeCompletionModel(parent) , m_inside(false) {} bool isFunction(const Declaration* decl) { return decl->abstractType().cast(); } bool isPathChar(const QChar& c) { return c.isLetterOrNumber() || c=='/' || c=='.'; } QString escapePath(QString path) { - static const QChar toBeEscaped[] = {'(', ')'}; - for(const QChar &ch : toBeEscaped) - { + // see https://cmake.org/Wiki/CMake/Language_Syntax#Escapes + static const QString toBeEscaped = QStringLiteral("\"()#$^"); + + for(const QChar &ch : toBeEscaped) { path.replace(ch, "\\" + ch); } return path; } void CMakeCodeCompletionModel::completionInvoked(View* view, const Range& range, InvocationType invocationType) { beginResetModel(); if(s_commands.isEmpty()) { ICMakeDocumentation* cmakedoc=CMake::cmakeDocumentation(); if(cmakedoc) s_commands=cmakedoc->names(ICMakeDocumentation::Command); } Q_UNUSED(invocationType); m_declarations.clear(); DUChainReadLocker lock(DUChain::lock()); KTextEditor::Document* d=view->document(); TopDUContext* ctx = DUChain::self()->chainForDocument( d->url() ); QString line=d->line(range.end().line()); // m_inside=line.lastIndexOf('(', range.end().column())>=0; m_inside=line.lastIndexOf('(', range.end().column()-line.size()-1)>=0; for(int l=range.end().line(); l>=0 && !m_inside; --l) { QString cline=d->line(l); QString line=cline.left(cline.indexOf('#')); int close=line.lastIndexOf(')'), open=line.indexOf('('); if(close>=0 && open>=0) { m_inside=open>close; break; } else if(open>=0) { m_inside=true; break; } else if(close>=0) { m_inside=false; break; } } int numRows = 0; if(m_inside) { Cursor start=range.start(); for(; isPathChar(d->characterAt(start)); start-=Cursor(0,1)) {} start+=Cursor(0,1); QString tocomplete=d->text(Range(start, range.end()-Cursor(0,1))); int lastdir=tocomplete.lastIndexOf('/'); QString path = KIO::upUrl(QUrl(d->url())).adjusted(QUrl::StripTrailingSlash).toLocalFile()+'/'; QString basePath; if(lastdir>=0) { basePath=tocomplete.mid(0, lastdir); path+=basePath; } QDir dir(path); QFileInfoList paths=dir.entryInfoList(QStringList() << tocomplete.mid(lastdir+1)+'*', QDir::AllEntries | QDir::NoDotAndDotDot); m_paths.clear(); foreach(const QFileInfo& f, paths) { QString currentPath = f.fileName(); if(f.isDir()) currentPath+='/'; m_paths += currentPath; } numRows += m_paths.count(); } else numRows += s_commands.count(); if(ctx) { typedef QPair DeclPair; QList list=ctx->allDeclarations( ctx->transformToLocalRevision(KTextEditor::Cursor(range.start())), ctx ); foreach(const DeclPair& pair, list) { bool func=isFunction(pair.first); if((func && !m_inside) || (!func && m_inside)) m_declarations.append(pair.first); } numRows+=m_declarations.count(); } setRowCount(numRows); endResetModel(); } CMakeCodeCompletionModel::Type CMakeCodeCompletionModel::indexType(int row) const { if(m_inside) { if(row < m_declarations.count()) { KDevelop::DUChainReadLocker lock; Declaration* dec = m_declarations.at(row).declaration(); if (dec && dec->type()) return Target; else return Variable; } else return Path; } else { if(rowidentifier().toString() : i18n("INVALID"); } } } else if(role==Qt::DisplayRole && index.column()==CodeCompletionModel::Prefix) { switch(type) { case Command: return i18n("Command"); case Variable: return i18n("Variable"); case Macro: return i18n("Macro"); case Path: return i18n("Path"); case Target: return i18n("Target"); } } else if(role==Qt::DecorationRole && index.column()==CodeCompletionModel::Icon) { switch(type) { case Command: return QIcon::fromTheme("code-block"); case Variable: return QIcon::fromTheme("code-variable"); case Macro: return QIcon::fromTheme("code-function"); case Target: return QIcon::fromTheme("system-run"); case Path: { QUrl url = QUrl::fromUserInput(m_paths[index.row()-m_declarations.size()]); QString iconName; if (url.isLocalFile()) { // don't read contents even if it is a local file iconName = QMimeDatabase().mimeTypeForFile(url.toLocalFile(), QMimeDatabase::MatchExtension).iconName(); } else { // remote always only looks at the extension iconName = QMimeDatabase().mimeTypeForUrl(url).iconName(); } return QIcon::fromTheme(iconName); } } } else if(role==Qt::DisplayRole && index.column()==CodeCompletionModel::Arguments) { switch(type) { case Variable: case Command: case Path: case Target: break; case Macro: { DUChainReadLocker lock(DUChain::lock()); int pos=index.row(); FunctionType::Ptr func; if(m_declarations[pos].data()) func = m_declarations[pos].data()->abstractType().cast(); if(!func) return QVariant(); QStringList args; foreach(const AbstractType::Ptr& t, func->arguments()) { DelayedType::Ptr delay = t.cast(); args.append(delay ? delay->identifier().toString() : i18n("wrong")); } return QString('('+args.join(", ")+')'); } } } return QVariant(); } void CMakeCodeCompletionModel::executeCompletionItem(View* view, const Range& word, const QModelIndex& idx) const { Document* document = view->document(); const int row = idx.row(); switch(indexType(row)) { case Path: { Range r=word; for(QChar c=document->characterAt(r.end()); c.isLetterOrNumber() || c=='.'; c=document->characterAt(r.end())) { r.setEnd(KTextEditor::Cursor(r.end().line(), r.end().column()+1)); } QString path = data(index(row, Name, QModelIndex())).toString(); document->replaceText(r, escapePath(path)); } break; case Macro: case Command: { QString code=data(index(row, Name, QModelIndex())).toString(); if(!document->line(word.start().line()).contains('(')) code.append('('); document->replaceText(word, code); } break; case Variable: { Range r=word, prefix(word.start()-Cursor(0,2), word.start()); QString bef=document->text(prefix); if(r.start().column()>=2 && bef=="${") r.setStart(KTextEditor::Cursor(r.start().line(), r.start().column()-2)); QString code="${"+data(index(row, Name, QModelIndex())).toString(); if(document->characterAt(word.end())!='}') code+='}'; document->replaceText(r, code); } break; case Target: document->replaceText(word, data(index(row, Name, QModelIndex())).toString()); break; } }