File indexing completed on 2024-04-14 04:31:08
0001 /* 0002 * This file is part of KDevelop 0003 * Copyright (C) 2012-2015 Miquel Sabaté Solà <mikisabate@gmail.com> 0004 * 0005 * This program is free software: you can redistribute it and/or modify 0006 * it under the terms of the GNU General Public License as published by 0007 * the Free Software Foundation, either version 3 of the License, or 0008 * (at your option) any later version. 0009 * 0010 * This program is distributed in the hope that it will be useful, 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0013 * GNU General Public License for more details. 0014 * 0015 * You should have received a copy of the GNU General Public License 0016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0017 */ 0018 0019 // KDE + KDevelop 0020 #include <language/duchain/duchainlock.h> 0021 #include <language/duchain/duchain.h> 0022 #include <language/duchain/types/unsuretype.h> 0023 0024 // Ruby 0025 #include <completiondebug.h> 0026 #include <parser/parser.h> 0027 #include <duchain/loader.h> 0028 #include <duchain/editorintegrator.h> 0029 #include <duchain/expressionvisitor.h> 0030 #include <duchain/declarations/moduledeclaration.h> 0031 #include <duchain/declarations/methoddeclaration.h> 0032 #include <completion/context.h> 0033 #include <completion/items/keyworditem.h> 0034 #include <completion/items/normalitem.h> 0035 #include <completion/items/requirefileitem.h> 0036 0037 #define ADD_KEYWORD(name) list << CompletionTreeItemPointer(new KeywordItem(KDevelop::CodeCompletionContext::Ptr(this), name)) 0038 #define ADD_KEYWORD2(name, desc) list << CompletionTreeItemPointer(new KeywordItem(KDevelop::CodeCompletionContext::Ptr(this), name, desc)) 0039 #define ADD_ONE_LINER(name, desc) list << CompletionTreeItemPointer(new KeywordItem(KDevelop::CodeCompletionContext::Ptr(this), name, desc, true)) 0040 #define ADD_NORMAL(decl, depth) list << CompletionTreeItemPointer(new NormalItem(DeclarationPointer(decl), KDevelop::CodeCompletionContext::Ptr(this), depth)); 0041 0042 using namespace KDevelop; 0043 0044 namespace ruby 0045 { 0046 0047 const QSet<QString> MEMBER_STRINGS = QString(". :: < include extend").split(' ').toSet(); 0048 const int MEMBER_STRINGS_MAX = 7; // include 0049 0050 /** 0051 * The diferent types of contexts in code completion. 0052 */ 0053 enum class ContextType { 0054 NoMemberAccess, /// A global completion should be done. 0055 MemberAccess, /// obj. 0056 ModuleMemberAccess, /// MyModule:: 0057 BaseClassAccess, /// After "class Klass <". 0058 ModuleMixinAccess, /// After "include" or "extend". 0059 FileChoose /// Autocompletion for files. 0060 }; 0061 0062 /** 0063 * It compresses the given @p text by removing spaces. 0064 */ 0065 void compressText(QString &text) 0066 { 0067 for (int i = text.length() - 1; i >= 0; --i) { 0068 if (!text[i].isSpace()) { 0069 break; 0070 } 0071 text.remove(i, 1); 0072 } 0073 } 0074 0075 /** 0076 * Get the ending string from the set of members for the given text. 0077 * 0078 * @param str The completion context text. 0079 * @returns the required ending string. 0080 */ 0081 QString getEndingFromSet(const QString &str) 0082 { 0083 QString end; 0084 0085 for (int i = qMin(str.length(), MEMBER_STRINGS_MAX); i > 0; --i) { 0086 end = str.right(i); 0087 if (MEMBER_STRINGS.contains(end)) { 0088 return end; 0089 } 0090 } 0091 return QString(); 0092 } 0093 0094 /** 0095 * Ugly method that tells if the given text is inside a class. 0096 * 0097 * @param text The given text. 0098 * @returns true if we're inside a class, false otherwise. 0099 */ 0100 bool insideClass(const QString &text) 0101 { 0102 int idx = text.lastIndexOf(QStringLiteral("<")); 0103 int classIdx = text.lastIndexOf(QStringLiteral("class"), idx); 0104 int semicolon = text.lastIndexOf(QStringLiteral(";"), idx); 0105 return (classIdx != -1 && (classIdx > semicolon)); 0106 } 0107 0108 /** 0109 * Returns the last n lines from the given text. 0110 * 0111 * @param str The given text. 0112 * @param n The number of lines to retrieve. 0113 */ 0114 QString lastNLines(const QString &str, int n) 0115 { 0116 int curNewLine = str.lastIndexOf('\n'); 0117 int nthLine = curNewLine; 0118 0119 for (int i = 0; i < n; ++i) { 0120 if (curNewLine == -1) { 0121 break; 0122 } else { 0123 nthLine = curNewLine; 0124 } 0125 curNewLine = str.lastIndexOf('\n', curNewLine - 1); 0126 } 0127 0128 // return the position after the newline, or whole str if no newline 0129 return str.mid(nthLine + 1); 0130 } 0131 0132 /** 0133 * Guess the access kind depending on the given parameter. 0134 * 0135 * @param original The original text from the completion context. 0136 * @returns the proper CodeCompletionContext::CompletionContextType. 0137 */ 0138 ContextType findAccessKind(const QString &original) 0139 { 0140 const QString text = getEndingFromSet(original); 0141 0142 if (text == QStringLiteral(".")) { 0143 return ContextType::MemberAccess; 0144 } 0145 if (text == QStringLiteral("::")) { 0146 return ContextType::ModuleMemberAccess; 0147 } 0148 if (text == QStringLiteral("<") && insideClass(original)) { 0149 return ContextType::BaseClassAccess; 0150 } 0151 if (text == QStringLiteral("include") || text == QStringLiteral("extend")) { 0152 return ContextType::ModuleMixinAccess; 0153 } 0154 return ContextType::NoMemberAccess; 0155 } 0156 0157 CodeCompletionContext::CodeCompletionContext(DUContextPointer ctxt, const QString &text, 0158 const QString &followingText, 0159 const CursorInRevision &pos, int depth) 0160 : KDevelop::CodeCompletionContext(ctxt, text, pos, depth) 0161 , m_valid(true), m_kind(ContextType::NoMemberAccess) 0162 { 0163 if (!m_duContext || !isValidPosition()) { 0164 m_valid = false; 0165 return; 0166 } 0167 0168 m_following = followingText; 0169 m_closing = 0; 0170 if (doRequireCompletion()) { 0171 return; 0172 } 0173 0174 compressText(m_text); 0175 m_kind = findAccessKind(m_text); 0176 } 0177 0178 CodeCompletionContext::~CodeCompletionContext() 0179 { 0180 } 0181 0182 QList<KDevelop::CompletionTreeItemPointer> CodeCompletionContext::completionItems(bool &abort, bool fullCompletion) 0183 { 0184 QList<CompletionTreeItemPointer> items; 0185 if (!m_valid) { 0186 return items; 0187 } 0188 0189 switch(m_kind) { 0190 case ContextType::MemberAccess: 0191 items += memberAccessItems(); 0192 break; 0193 case ContextType::ModuleMemberAccess: 0194 items += moduleMemberAccessItems(); 0195 break; 0196 case ContextType::BaseClassAccess: 0197 items += baseClassItems(); 0198 break; 0199 case ContextType::ModuleMixinAccess: 0200 items += moduleMixinItems(); 0201 break; 0202 case ContextType::FileChoose: 0203 items += fileChooseItems(); 0204 break; 0205 default: 0206 items += standardAccessItems(); 0207 addRubyKeywords(); 0208 addRubySpecialBuiltins(); 0209 } 0210 0211 if (shouldAddParentItems(fullCompletion)) { 0212 items.append(parentContext()->completionItems(abort, fullCompletion)); 0213 } 0214 return items; 0215 } 0216 0217 QList<CompletionTreeElementPointer> CodeCompletionContext::ungroupedElements() 0218 { 0219 return m_ungroupedItems; 0220 } 0221 0222 bool CodeCompletionContext::isValidPosition() 0223 { 0224 if (m_text.isEmpty()) { 0225 return true; 0226 } 0227 0228 for (QString::iterator it = m_text.end(); it != m_text.begin(); --it) { 0229 if (*it == '\n') { 0230 break; 0231 } 0232 if (*it == '#') { 0233 return false; 0234 } 0235 } 0236 return true; 0237 } 0238 0239 bool CodeCompletionContext::doRequireCompletion() 0240 { 0241 QString line = lastNLines(m_text, 1).trimmed(); 0242 KDevelop::Path relative; 0243 int idx = 8; 0244 0245 if (!line.startsWith(QStringLiteral("require "))) { 0246 if (!line.startsWith(QStringLiteral("require_relative "))) { 0247 return false; 0248 } 0249 idx += 9; 0250 relative = KDevelop::Path(m_duContext->url().toUrl()).parent(); 0251 } 0252 line = line.mid(idx).trimmed(); 0253 m_closing = '\''; 0254 if ((idx = line.indexOf(QStringLiteral("'"))) < 0) { 0255 m_closing = '"'; 0256 if ((idx = line.indexOf(QStringLiteral("\")")) < 0)) { 0257 m_closing = 0; 0258 return false; 0259 } 0260 } 0261 line = line.mid(idx + 1); 0262 0263 m_includeItems = Loader::getFilesInSearchPath(line, m_following, relative); 0264 m_kind = ContextType::FileChoose; 0265 return true; 0266 } 0267 0268 AbstractType::Ptr CodeCompletionContext::getExpressionType(const QString &token) 0269 { 0270 AbstractType::Ptr res; 0271 QString expr = m_text.left(m_text.lastIndexOf(token)); 0272 EditorIntegrator e; 0273 ExpressionVisitor ev(m_duContext.data(), &e); 0274 0275 DUChainReadLocker lock; 0276 Parser parser(IndexedString(), expr.toUtf8()); 0277 Ast *ast = parser.parse(); 0278 if (!ast || !ast->tree) { 0279 return AbstractType::Ptr(nullptr); 0280 } 0281 lock.unlock(); 0282 ev.visitCode(ast); 0283 res = ev.lastType(); 0284 lock.lock(); 0285 0286 return res; 0287 } 0288 0289 QList<CompletionTreeItemPointer> CodeCompletionContext::getCompletionItemsFromType(AbstractType::Ptr type, bool scoped) 0290 { 0291 QList<CompletionTreeItemPointer> res; 0292 if (type->whichType() == AbstractType::TypeUnsure) { 0293 auto unsure = type.staticCast<UnsureType>(); 0294 int count = unsure->typesSize(); 0295 for (int i = 0; i < count; i++) { 0296 res.append(getCompletionItemsForOneType(unsure->types()[i].abstractType(), scoped)); 0297 } 0298 } else { 0299 res = getCompletionItemsForOneType(type, scoped); 0300 } 0301 return res; 0302 } 0303 0304 QList<CompletionTreeItemPointer> CodeCompletionContext::getCompletionItemsForOneType(AbstractType::Ptr type, bool scoped) 0305 { 0306 QList<CompletionTreeItemPointer> list; 0307 QVector<DeclarationPair> decls; 0308 auto sType = type.dynamicCast<StructureType>(); 0309 0310 { 0311 DUChainReadLocker lock; 0312 if (!sType || !sType->internalContext(m_duContext->topContext())) { 0313 return QList<CompletionTreeItemPointer>(); 0314 } 0315 DUContext *current = sType->internalContext(m_duContext->topContext()); 0316 decls = current->allDeclarations(CursorInRevision::invalid(), m_duContext->topContext(), false); 0317 } 0318 0319 foreach (DeclarationPair d, decls) { 0320 MethodDeclaration *md = dynamic_cast<MethodDeclaration *>(d.first); 0321 if (md && md->accessPolicy() == Declaration::Public) { 0322 if (!scoped || (scoped && md->isClassMethod())) { 0323 0324 ADD_NORMAL(d.first, d.second); 0325 } 0326 } else if (scoped && dynamic_cast<ModuleDeclaration *>(d.first)) { 0327 ADD_NORMAL(d.first, d.second); 0328 } 0329 } 0330 return list; 0331 } 0332 0333 bool CodeCompletionContext::shouldAddParentItems(bool fullCompletion) 0334 { 0335 return (m_parentContext && fullCompletion); 0336 } 0337 0338 QList<CompletionTreeItemPointer> CodeCompletionContext::memberAccessItems() 0339 { 0340 QList<CompletionTreeItemPointer> list; 0341 AbstractType::Ptr type = getExpressionType(QStringLiteral(".")); 0342 if (type) { 0343 list << getCompletionItemsFromType(type); 0344 } else { 0345 qCDebug(COMPLETION) << "Oops: cannot access at the member"; 0346 } 0347 return list; 0348 } 0349 0350 QList<CompletionTreeItemPointer> CodeCompletionContext::moduleMemberAccessItems() 0351 { 0352 QList<CompletionTreeItemPointer> list; 0353 AbstractType::Ptr type = getExpressionType(QStringLiteral("::")); 0354 if (type) { 0355 list << getCompletionItemsFromType(type, true); 0356 } else { 0357 qCDebug(COMPLETION) << "Oops: cannot access at the member"; 0358 } 0359 0360 return list; 0361 } 0362 0363 QList<CompletionTreeItemPointer> CodeCompletionContext::baseClassItems() 0364 { 0365 QList<CompletionTreeItemPointer> list; 0366 QVector<DeclarationPair> decls; 0367 0368 { 0369 DUChainReadLocker lock; 0370 decls = m_duContext->allDeclarations(m_position, m_duContext->topContext()); 0371 } 0372 0373 foreach (DeclarationPair d, decls) { 0374 ModuleDeclaration *mDecl = dynamic_cast<ModuleDeclaration *>(d.first); 0375 if (mDecl && !mDecl->isModule()) { 0376 ADD_NORMAL(d.first, d.second); 0377 } 0378 } 0379 return list; 0380 } 0381 0382 QList<CompletionTreeItemPointer> CodeCompletionContext::moduleMixinItems() 0383 { 0384 QList<CompletionTreeItemPointer> list; 0385 QVector<DeclarationPair> decls; 0386 0387 { 0388 DUChainReadLocker lock; 0389 decls = m_duContext->allDeclarations(m_position, m_duContext->topContext()); 0390 } 0391 0392 foreach (DeclarationPair d, decls) { 0393 if (dynamic_cast<ModuleDeclaration *>(d.first)) { 0394 ADD_NORMAL(d.first, d.second); 0395 } 0396 } 0397 return list; 0398 } 0399 0400 QList<CompletionTreeItemPointer> CodeCompletionContext::fileChooseItems() 0401 { 0402 QList<CompletionTreeItemPointer> list; 0403 0404 foreach (const KDevelop::IncludeItem &item, m_includeItems) { 0405 list << CompletionTreeItemPointer(new RequireFileItem(item, m_closing)); 0406 } 0407 return list; 0408 } 0409 0410 QList<CompletionTreeItemPointer> CodeCompletionContext::standardAccessItems() 0411 { 0412 QList<CompletionTreeItemPointer> list; 0413 QVector<DeclarationPair> decls; 0414 0415 // Add one-liners (i.e. shebang) 0416 if (m_position.line == 0 && (m_text.startsWith(QStringLiteral("#")) 0417 || m_text.isEmpty())) { 0418 ADD_ONE_LINER(QStringLiteral("#!/usr/bin/env ruby"), i18n("insert Shebang line")); 0419 ADD_ONE_LINER(QStringLiteral("# encoding: UTF-8"), i18n("insert encoding line")); 0420 } 0421 0422 // Find everything that is accessible at this point 0423 { 0424 DUChainReadLocker lock; 0425 decls = m_duContext->allDeclarations(m_position, m_duContext->topContext()); 0426 } 0427 foreach (DeclarationPair d, decls) { 0428 ADD_NORMAL(d.first, d.second); 0429 } 0430 return list; 0431 } 0432 0433 void CodeCompletionContext::eventuallyAddGroup(const QString &name, int priority, 0434 QList<CompletionTreeItemPointer> items) 0435 { 0436 KDevelop::CompletionCustomGroupNode *node = new KDevelop::CompletionCustomGroupNode(name, priority); 0437 node->appendChildren(items); 0438 m_ungroupedItems << CompletionTreeElementPointer(node); 0439 } 0440 0441 void CodeCompletionContext::addRubyKeywords() 0442 { 0443 QList<CompletionTreeItemPointer> list; 0444 0445 // "Ultra-simple" statements. Some of them may not be *that* useful. 0446 ADD_KEYWORD("next"); 0447 ADD_KEYWORD("break"); 0448 ADD_KEYWORD("true"); 0449 ADD_KEYWORD("false"); 0450 ADD_KEYWORD("self"); 0451 ADD_KEYWORD("then"); 0452 ADD_KEYWORD("redo"); 0453 ADD_KEYWORD("retry"); 0454 ADD_KEYWORD("yield"); 0455 ADD_KEYWORD("super"); 0456 ADD_KEYWORD("return"); 0457 ADD_KEYWORD("defined?"); 0458 ADD_KEYWORD("__FILE__"); 0459 ADD_KEYWORD("__LINE__"); 0460 ADD_KEYWORD("__ENCODING__"); 0461 0462 // Simple statements 0463 ADD_KEYWORD2("alias", "alias "); 0464 ADD_KEYWORD2("undef", "undef "); 0465 ADD_KEYWORD2("rescue", "%UNINDENT%rescue "); 0466 ADD_KEYWORD2("ensure", "%UNINDENT%ensure%INDENT%%CURSOR%\n"); 0467 ADD_KEYWORD2("BEGIN", "BEGIN {\n%INDENT%%CURSOR%\n}"); 0468 0469 // Take care of complex statements that can be just statement modifiers 0470 if (lastNLines(m_text, 1).isEmpty()) { 0471 ADD_KEYWORD2("if", "if %SELECT%condition%ENDSELECT%\n%END%"); 0472 ADD_KEYWORD2("unless", "unless %SELECT%condition%ENDSELECT%\n%END%"); 0473 ADD_KEYWORD2("while", "while %SELECT%condition%ENDSELECT%\n%END%"); 0474 ADD_KEYWORD2("until", "until %SELECT%condition%ENDSELECT%\n%END%"); 0475 } else { 0476 ADD_KEYWORD2("if", "if %SELECT%condition%ENDSELECT%"); 0477 ADD_KEYWORD2("unless", "unless %SELECT%condition%ENDSELECT%"); 0478 ADD_KEYWORD2("while", "while %SELECT%condition%ENDSELECT%"); 0479 ADD_KEYWORD2("until", "until %SELECT%condition%ENDSELECT%"); 0480 } 0481 0482 // Complex constructions 0483 ADD_KEYWORD2("elsif", "%UNINDENT%elsif %SELECT%condition%ENDSELECT%"); 0484 ADD_KEYWORD2("else", "%UNINDENT%else\n%INDENT%%CURSOR%"); 0485 ADD_KEYWORD2("for", "for %SELECT%condition%ENDSELECT% in \n%END%"); 0486 ADD_KEYWORD2("def", "def %SELECT%name%ENDSELECT%\n%END%"); 0487 ADD_KEYWORD2("class", "class %SELECT%Name%ENDSELECT%\n%END%"); 0488 ADD_KEYWORD2("module", "module %SELECT%Name%ENDSELECT%\n%END%"); 0489 ADD_KEYWORD2("case", "case %SELECT%condition%ENDSELECT%\n%END%"); 0490 ADD_KEYWORD2("when", "%UNINDENT%when %SELECT%condition%ENDSELECT%"); 0491 ADD_KEYWORD2("begin", "begin\n%INDENT%%CURSOR%\n%END%"); 0492 ADD_KEYWORD2("do", "do %SELECT%||%ENDSELECT%\n%END%"); 0493 0494 // Group all these keywords into the "Ruby Keyword" group. 0495 eventuallyAddGroup(i18n("Ruby Keyword"), 800, list); 0496 } 0497 0498 void CodeCompletionContext::addRubySpecialBuiltins() 0499 { 0500 QList<CompletionTreeItemPointer> list; 0501 0502 // Not really keywords, but who cares? ;) 0503 ADD_KEYWORD2("include", "include %SELECT%MyModule%ENDSELECT%"); 0504 ADD_KEYWORD2("extend", "extend %SELECT%MyModule%ENDSELECT%"); 0505 ADD_KEYWORD2("require", "require '%CURSOR%'"); 0506 ADD_KEYWORD2("require_relative", "require_relative '%CURSOR%'"); 0507 0508 // Group all these special builtins 0509 eventuallyAddGroup(i18n("Ruby Builtins"), 800, list); 0510 } 0511 0512 }