/* * Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com) * * 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 3 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, see . */ #include "codecompletionpopup.h" #include "../utils.h" #include "../mainwindow.h" #include "../editor.h" #include "../editorlist.h" #include "../symbolusagemanager.h" #include "../colorscheme.h" #include "../iconsmanager.h" #include "../settings.h" #include #include #include #include #include CodeCompletionPopup::CodeCompletionPopup(QWidget *parent) : QWidget(parent), mMutex() { setWindowFlags(Qt::Popup); mListView = new CodeCompletionListView(this); mModel=new CodeCompletionListModel(&mCompletionStatementList); mDelegate = new CodeCompletionListItemDelegate(mModel,this); QItemSelectionModel *m=mListView->selectionModel(); mListView->setModel(mModel); delete m; mListView->setItemDelegate(mDelegate); setLayout(new QVBoxLayout()); layout()->addWidget(mListView); layout()->setMargin(0); mListView->setFocus(); mShowKeywords=true; mRecordUsage = false; mSortByScope = true; mShowCount = 1000; mShowCodeSnippets = true; mIgnoreCase = false; mHideSymbolsStartWithTwoUnderline = false; mHideSymbolsStartWithUnderline = false; } CodeCompletionPopup::~CodeCompletionPopup() { delete mListView; delete mModel; } void CodeCompletionPopup::setKeypressedCallback(const KeyPressedCallback &newKeypressedCallback) { mListView->setKeypressedCallback(newKeypressedCallback); } void CodeCompletionPopup::prepareSearch( const QString &preWord, const QStringList &ownerExpression, const QString& memberOperator, const QStringList& memberExpression, const QString &filename, int line, CodeCompletionType type, const QSet& customKeywords) { QMutexLocker locker(&mMutex); if (!isEnabled()) return; QCursor oldCursor = cursor(); setCursor(Qt::CursorShape::WaitCursor); mMemberPhrase = memberExpression.join(""); mMemberOperator = memberOperator; switch(type) { case CodeCompletionType::ComplexKeyword: getCompletionListForComplexKeyword(preWord); break; case CodeCompletionType::Types: mIncludedFiles = mParser->getIncludedFiles(filename); getCompletionListForTypes(preWord,filename,line); break; case CodeCompletionType::FunctionWithoutDefinition: mIncludedFiles = mParser->getIncludedFiles(filename); getCompletionForFunctionWithoutDefinition(preWord, ownerExpression,memberOperator,memberExpression, filename,line); break; case CodeCompletionType::Namespaces: mIncludedFiles = mParser->getIncludedFiles(filename); getCompletionListForNamespaces(preWord,filename,line); break; case CodeCompletionType::KeywordsOnly: mIncludedFiles.clear(); getKeywordCompletionFor(customKeywords); break; case CodeCompletionType::Macros: mIncludedFiles = mParser->getIncludedFiles(filename); getMacroCompletionList(filename, line); break; default: mIncludedFiles = mParser->getIncludedFiles(filename); getCompletionFor(ownerExpression,memberOperator,memberExpression, filename,line, customKeywords); } setCursor(oldCursor); } bool CodeCompletionPopup::search(const QString &memberPhrase, bool autoHideOnSingleResult) { QMutexLocker locker(&mMutex); mMemberPhrase = memberPhrase; if (!isEnabled()) { hide(); return false; } QCursor oldCursor = cursor(); setCursor(Qt::CursorShape::WaitCursor); // filter fFullCompletionStatementList to fCompletionStatementList filterList(memberPhrase); //if can't find a destructor, maybe '~' is only an operator // if (mCompletionStatementList.isEmpty() && phrase.startsWith('~')) { // symbol = phrase.mid(1); // filterList(symbol); // } mModel->notifyUpdated(); setCursor(oldCursor); if (!mCompletionStatementList.isEmpty()) { QString schemaName = pSettings->editor().colorScheme(); PColorSchemeItem item = pColorManager->getItem(schemaName, COLOR_SCHEME_ACTIVE_LINE); if (item) mDelegate->setCurrentSelectionColor(item->background()); else mDelegate->setCurrentSelectionColor(palette().highlight().color()); item = pColorManager->getItem(schemaName, COLOR_SCHEME_TEXT); if (item) mDelegate->setNormalColor(item->foreground()); else mDelegate->setNormalColor(palette().color(QPalette::Text)); item = pColorManager->getItem(schemaName, SYNS_AttrReserveWord_Type); if (item) mDelegate->setMatchedColor(item->foreground()); else mDelegate->setMatchedColor(palette().color(QPalette::HighlightedText)); mListView->setCurrentIndex(mModel->index(0,0)); // if only one suggestion, and is exactly the symbol to search, hide the frame (the search is over) // if only one suggestion and auto hide , don't show the frame if(mCompletionStatementList.count() == 1) if (autoHideOnSingleResult || (memberPhrase == mCompletionStatementList.front()->command)) { return true; } } else { hide(); } return false; } PStatement CodeCompletionPopup::selectedStatement() { if (isEnabled()) { int index = mListView->currentIndex().row(); if (mListView->currentIndex().isValid() && (indexfileName) && !isIncluded(scopeStatement->definitionFileName)) return; const StatementMap& children = mParser->statementList().childrenStatements(scopeStatement); if (children.isEmpty()) return; if (onlyTypes) { if (!scopeStatement) { //Global scope for (const PStatement& childStatement: children) { if (!isTypeKind(childStatement->kind)) continue; if (childStatement->fileName.isEmpty()) { // hard defines addStatement(childStatement,fileName,-1); } else if ( isIncluded(childStatement->fileName) || isIncluded(childStatement->definitionFileName) ) { //we must check if the statement is included by the file addStatement(childStatement,fileName,line); } } } else { for (const PStatement& childStatement: children) { if (!isTypeKind(childStatement->kind)) continue; addStatement(childStatement,fileName,line); } } } else { if (!scopeStatement) { //Global scope for (const PStatement& childStatement: children) { if (childStatement->fileName.isEmpty()) { // hard defines addStatement(childStatement,fileName,-1); } else if ( isIncluded(childStatement->fileName) || isIncluded(childStatement->definitionFileName) ) { //we must check if the statement is included by the file addStatement(childStatement,fileName,line); } } } else { for (const PStatement& childStatement: children) { addStatement(childStatement,fileName,line); } } } } void CodeCompletionPopup::addFunctionWithoutDefinitionChildren(const PStatement& scopeStatement, const QString &fileName, int line) { if (scopeStatement && !isIncluded(scopeStatement->fileName) && !isIncluded(scopeStatement->definitionFileName)) return; const StatementMap& children = mParser->statementList().childrenStatements(scopeStatement); if (children.isEmpty()) return; for (const PStatement& childStatement: children) { if (childStatement->inSystemHeader()) continue; if (childStatement->fileName.isEmpty()) { // hard defines, do nothing continue; } switch(childStatement->kind) { case StatementKind::Constructor: case StatementKind::Function: case StatementKind::Destructor: if (!childStatement->hasDefinition()) addStatement(childStatement,fileName,line); break; case StatementKind::Class: case StatementKind::Namespace: if (isIncluded(childStatement->fileName)) addStatement(childStatement,fileName,line); break; default: break; } } } void CodeCompletionPopup::addStatement(const PStatement& statement, const QString &fileName, int line) { if (mAddedStatements.contains(statement->command)) return; if (statement->kind == StatementKind::Constructor || statement->kind == StatementKind::Destructor || statement->kind == StatementKind::Block || statement->kind == StatementKind::Lambda || statement->properties.testFlag(StatementProperty::OperatorOverloading) || statement->properties.testFlag(StatementProperty::DummyStatement) ) return; if ((line!=-1) && (line < statement->line) && (fileName == statement->fileName)) return; mAddedStatements.insert(statement->command); if (statement->kind == StatementKind::UserCodeSnippet || !statement->command.contains("<")) mFullCompletionStatementList.append(statement); } static bool nameComparator(PStatement statement1,PStatement statement2) { return statement1->command < statement2->command; } static bool defaultComparator(PStatement statement1,PStatement statement2) { if (statement1->matchPosSpan!=statement2->matchPosSpan) return statement1->matchPosSpan < statement2->matchPosSpan; if (statement1->firstMatchLength != statement2->firstMatchLength) return statement1->firstMatchLength > statement2->firstMatchLength; if (statement1->matchPosTotal != statement2->matchPosTotal) return statement1->matchPosTotal < statement2->matchPosTotal; if (statement1->caseMatched != statement2->caseMatched) return statement1->caseMatched > statement2->caseMatched; // Show user template first if (statement1->kind == StatementKind::UserCodeSnippet) { if (statement2->kind != StatementKind::UserCodeSnippet) return true; else return statement1->command < statement2->command; } else if (statement2->kind == StatementKind::UserCodeSnippet) { return false; // show keywords first } else if ((statement1->kind == StatementKind::Keyword) && (statement2->kind != StatementKind::Keyword)) { return true; } else if ((statement1->kind != StatementKind::Keyword) && (statement2->kind == StatementKind::Keyword)) { return false; } else return nameComparator(statement1,statement2); } static bool sortByScopeComparator(PStatement statement1,PStatement statement2){ if (statement1->matchPosSpan!=statement2->matchPosSpan) return statement1->matchPosSpan < statement2->matchPosSpan; if (statement1->firstMatchLength != statement2->firstMatchLength) return statement1->firstMatchLength > statement2->firstMatchLength; if (statement1->matchPosTotal != statement2->matchPosTotal) return statement1->matchPosTotal < statement2->matchPosTotal; if (statement1->caseMatched != statement2->caseMatched) return statement1->caseMatched > statement2->caseMatched; // Show user template first if (statement1->kind == StatementKind::UserCodeSnippet) { if (statement2->kind != StatementKind::UserCodeSnippet) return true; else return statement1->command < statement2->command; } else if (statement2->kind == StatementKind::UserCodeSnippet) { return false; // show non-system defines before keyword } else if (statement1->kind == StatementKind::Keyword) { if (statement2->kind != StatementKind::Keyword) { //s1 keyword / s2 system defines, s1 < s2, should return true //s1 keyword / s2 not system defines, s2 < s1, should return false; return statement2->inSystemHeader(); } else return statement1->command < statement2->command; } else if (statement2->kind == StatementKind::Keyword) { //s1 system defines / s2 keyword, s2 < s1, should return false; //s1 not system defines / s2 keyword, s1 < s2, should return true; return (!statement1->inSystemHeader()); } // Show stuff from local headers first if (statement1->inSystemHeader() != statement2->inSystemHeader()) return !(statement1->inSystemHeader()); // Show local statements first if (statement1->scope != StatementScope::Global && statement2->scope == StatementScope::Global ) { return true; } else if (statement1->scope == StatementScope::Global && statement2->scope != StatementScope::Global ) { return false; } else return nameComparator(statement1,statement2); } static bool sortWithUsageComparator(PStatement statement1,PStatement statement2) { if (statement1->matchPosSpan!=statement2->matchPosSpan) return statement1->matchPosSpan < statement2->matchPosSpan; if (statement1->firstMatchLength != statement2->firstMatchLength) return statement1->firstMatchLength > statement2->firstMatchLength; if (statement1->matchPosTotal != statement2->matchPosTotal) return statement1->matchPosTotal < statement2->matchPosTotal; if (statement1->caseMatched != statement2->caseMatched) return statement1->caseMatched > statement2->caseMatched; // Show user template first if (statement1->kind == StatementKind::UserCodeSnippet) { if (statement2->kind != StatementKind::UserCodeSnippet) return true; else return statement1->command < statement2->command; } else if (statement2->kind == StatementKind::UserCodeSnippet) { return false; //show most freq first } if (statement1->usageCount != statement2->usageCount) return statement1->usageCount > statement2->usageCount; if ((statement1->kind != StatementKind::Keyword) && (statement2->kind == StatementKind::Keyword)) { return true; } else if ((statement1->kind == StatementKind::Keyword) && (statement2->kind != StatementKind::Keyword)) { return false; } else return nameComparator(statement1,statement2); } static bool sortByScopeWithUsageComparator(PStatement statement1,PStatement statement2){ if (statement1->matchPosSpan!=statement2->matchPosSpan) return statement1->matchPosSpan < statement2->matchPosSpan; if (statement1->firstMatchLength != statement2->firstMatchLength) return statement1->firstMatchLength > statement2->firstMatchLength; if (statement1->matchPosTotal != statement2->matchPosTotal) return statement1->matchPosTotal < statement2->matchPosTotal; if (statement1->caseMatched != statement2->caseMatched) return statement1->caseMatched > statement2->caseMatched; // Show user template first if (statement1->kind == StatementKind::UserCodeSnippet) { if (statement2->kind != StatementKind::UserCodeSnippet) return true; else return statement1->command < statement2->command; } else if (statement2->kind == StatementKind::UserCodeSnippet) { return false; //show most freq first } if (statement1->usageCount != statement2->usageCount) return statement1->usageCount > statement2->usageCount; // show non-system defines before keyword if (statement1->kind == StatementKind::Keyword) { if (statement2->kind != StatementKind::Keyword) { //s1 keyword / s2 system defines, s1 < s2, should return true //s1 keyword / s2 not system defines, s2 < s1, should return false; return statement2->inSystemHeader(); } else return statement1->command < statement2->command; } else if (statement2->kind == StatementKind::Keyword) { //s1 system defines / s2 keyword, s2 < s1, should return false; //s1 not system defines / s2 keyword, s1 < s2, should return true; return (!statement1->inSystemHeader()); } // Show stuff from local headers first if (statement1->inSystemHeader() != statement2->inSystemHeader()) return !(statement1->inSystemHeader()); // Show local statements first if (statement1->scope != StatementScope::Global && statement2->scope == StatementScope::Global ) { return true; } else if (statement1->scope == StatementScope::Global && statement2->scope != StatementScope::Global ) { return false; } else return nameComparator(statement1,statement2); } void CodeCompletionPopup::filterList(const QString &member) { QMutexLocker locker(&mMutex); mCompletionStatementList.clear(); // if (!mParser) // return; // if (!mParser->enabled()) // return; //we don't need to freeze here since we use smart pointers // and data have been retrieved from the parser mCompletionStatementList.clear(); mCompletionStatementList.reserve(mFullCompletionStatementList.size()); bool hideSymbolsTwoUnderline = mHideSymbolsStartWithTwoUnderline && !member.startsWith("__") ; bool hideSymbolsUnderline = mHideSymbolsStartWithUnderline && !member.startsWith("_") ; int len = member.length(); foreach (const PStatement& statement, mFullCompletionStatementList) { int matched = 0; int caseMatched = 0; QString command = statement->command; int pos = 0; int lastPos = -10; int totalPos = 0; statement->matchPositions.clear(); if (hideSymbolsTwoUnderline && statement->command.startsWith("__")) { continue; } else if (hideSymbolsUnderline && statement->command.startsWith("_")) { continue; } else { foreach (const QChar& ch, member) { if (mIgnoreCase) pos = command.indexOf(ch,pos,Qt::CaseInsensitive); else pos = command.indexOf(ch,pos,Qt::CaseSensitive); if (pos<0) { break; } if (pos == lastPos+1) { statement->matchPositions.last()->end++; } else { PStatementMathPosition matchPosition=std::make_shared(); matchPosition->start = pos; matchPosition->end = pos+1; statement->matchPositions.append(matchPosition); } if (ch==command[pos]) caseMatched++; matched++; totalPos += pos; lastPos = pos; pos+=1; } } if (mIgnoreCase && matched== len) { statement->caseMatched = caseMatched; statement->matchPosTotal = totalPos; if (member.length()>0) { statement->firstMatchLength = statement->matchPositions.front()->end - statement->matchPositions.front()->start; statement->matchPosSpan = statement->matchPositions.last()->end - statement->matchPositions.front()->start; } else statement->firstMatchLength = 0; mCompletionStatementList.append(statement); } else if (caseMatched == len) { statement->caseMatched = caseMatched; statement->matchPosTotal = totalPos; if (member.length()>0) { statement->firstMatchLength = statement->matchPositions.front()->end - statement->matchPositions.front()->start; statement->matchPosSpan = statement->matchPositions.last()->end - statement->matchPositions.front()->start; } else statement->firstMatchLength = 0; mCompletionStatementList.append(statement); } else { statement->matchPositions.clear(); statement->caseMatched = 0; statement->matchPosTotal = 0; statement->firstMatchLength = 0; statement->matchPosSpan = 0; } } if (mRecordUsage) { int usageCount; foreach (const PStatement& statement,mCompletionStatementList) { if (statement->usageCount == -1) { PSymbolUsage usage = pMainWindow->symbolUsageManager()->findUsage(statement->fullName); if (usage) { usageCount = usage->count; } else { usageCount = 0; } statement->usageCount = usageCount; } } if (mSortByScope) { std::sort(mCompletionStatementList.begin(), mCompletionStatementList.end(), sortByScopeWithUsageComparator); } else { std::sort(mCompletionStatementList.begin(), mCompletionStatementList.end(), sortWithUsageComparator); } } else if (mSortByScope) { std::sort(mCompletionStatementList.begin(), mCompletionStatementList.end(), sortByScopeComparator); } else { std::sort(mCompletionStatementList.begin(), mCompletionStatementList.end(), defaultComparator); } // } } void CodeCompletionPopup::getKeywordCompletionFor(const QSet &customKeywords) { //add keywords if (!customKeywords.isEmpty()) { foreach (const QString& keyword,customKeywords) { addKeyword(keyword); } } } void CodeCompletionPopup::getMacroCompletionList(const QString &fileName, int line) { const StatementMap& statementMap = mParser->statementList().childrenStatements(nullptr); foreach(const PStatement& statement, statementMap.values()) { if (statement->kind==StatementKind::Preprocessor) addStatement(statement,fileName,line); } } void CodeCompletionPopup::getCompletionFor( QStringList ownerExpression, const QString& memberOperator, const QStringList& memberExpression, const QString &fileName, int line, const QSet& customKeywords) { if (memberOperator.isEmpty() && ownerExpression.isEmpty() && memberExpression.isEmpty()) return; bool isLambdaReturnType = ( memberOperator=="->" && ownerExpression.startsWith("[") && ownerExpression.endsWith(")")); if (memberOperator.isEmpty()) { //C++ preprocessor directives if (mMemberPhrase.startsWith('#')) { if (mShowKeywords) { foreach (const QString& keyword, CppDirectives) { addKeyword(keyword); } } return; } //docstring tags (javadoc style) if (mMemberPhrase.startsWith('@')) { if (mShowKeywords) { foreach (const QString& keyword,JavadocTags) { addKeyword(keyword); } } return; } //the identifier to be completed is not a member of variable/class if (mShowCodeSnippets) { //add custom code templates foreach (const PCodeSnippet& codeIn,mCodeSnippets) { if (!codeIn->code.isEmpty()) { PStatement statement = std::make_shared(); statement->command = codeIn->prefix; statement->value = codeIn->code; statement->kind = StatementKind::UserCodeSnippet; statement->fullName = codeIn->prefix; statement->usageCount = 0; mFullCompletionStatementList.append(statement); } } } if (mShowKeywords) { //add keywords if (!customKeywords.isEmpty()) { foreach (const QString& keyword,customKeywords) { addKeyword(keyword); } } } } else if (isLambdaReturnType) { foreach (const QString& keyword,CppTypeKeywords) { addKeyword(keyword); } } if (!mParser || !mParser->enabled()) return; if (!mParser->freeze()) return; { auto action = finally([this]{ mParser->unFreeze(); }); if (memberOperator.isEmpty() || isLambdaReturnType) { PStatement scopeStatement = mCurrentScope; // repeat until reach global while (scopeStatement) { //add members of current scope that not added before if (scopeStatement->kind == StatementKind::Namespace) { PStatementList namespaceStatementsList = mParser->findNamespace(scopeStatement->fullName); foreach (const PStatement& namespaceStatement,*namespaceStatementsList) { addChildren(namespaceStatement, fileName, line, isLambdaReturnType); } } else if (scopeStatement->kind == StatementKind::Class) { addChildren(scopeStatement, fileName, -1, isLambdaReturnType); } else { addChildren(scopeStatement, fileName, line, isLambdaReturnType); } // add members of all usings (in current scope ) and not added before foreach (const QString& namespaceName,scopeStatement->usingList) { PStatementList namespaceStatementsList = mParser->findNamespace(namespaceName); if (!namespaceStatementsList) continue; foreach (const PStatement& namespaceStatement,*namespaceStatementsList) { addChildren(namespaceStatement, fileName, line, isLambdaReturnType); } } if (scopeStatement->kind == StatementKind::Lambda) { foreach (const QString& phrase, scopeStatement->lambdaCaptures) { if (phrase=="&" || phrase == "=" || phrase =="this") continue; PStatement statement = mParser->findStatementOf( scopeStatement->fileName, phrase,scopeStatement->line); if (statement) addStatement(statement,scopeStatement->fileName, scopeStatement->line); } if (scopeStatement->lambdaCaptures.contains("&") || scopeStatement->lambdaCaptures.contains("=")) { scopeStatement = scopeStatement->parentScope.lock(); } else if (scopeStatement->lambdaCaptures.contains("this")) { do { scopeStatement = scopeStatement->parentScope.lock(); } while (scopeStatement && scopeStatement->kind!=StatementKind::Class && scopeStatement->kind!=StatementKind::Namespace); } else { do { scopeStatement = scopeStatement->parentScope.lock(); } while (scopeStatement && scopeStatement->kind!=StatementKind::Namespace); } continue; } scopeStatement=scopeStatement->parentScope.lock(); } // add all global members and not added before addChildren(nullptr, fileName, line, isLambdaReturnType); // add members of all fusings mUsings = mParser->getFileUsings(fileName); foreach (const QString& namespaceName, mUsings) { PStatementList namespaceStatementsList = mParser->findNamespace(namespaceName); if (!namespaceStatementsList) continue; foreach (const PStatement& namespaceStatement, *namespaceStatementsList) { addChildren(namespaceStatement, fileName, line, isLambdaReturnType); } } } else { //the identifier to be completed is a member of variable/class if (memberOperator == "::" && ownerExpression.isEmpty()) { // start with '::', we only find in global // add all global members and not added before addChildren(nullptr, fileName, line); return; } if (memberExpression.length()==2 && memberExpression.front()!="~") return; if (memberExpression.length()>2) return; PStatement scope = mCurrentScope;//the scope the expression in PStatement parentTypeStatement; // QString scopeName = ownerExpression.join(""); // PStatement ownerStatement = mParser->findStatementOf( // fileName, // scopeName, // mCurrentStatement, // parentTypeStatement); PEvalStatement ownerStatement = mParser->evalExpression(fileName, ownerExpression, scope); if(!ownerStatement || !ownerStatement->effectiveTypeStatement) { return; } if (memberOperator == "::") { if (ownerStatement->kind==EvalStatementKind::Namespace) { //there might be many statements corresponding to one namespace; PStatementList namespaceStatementsList = mParser->findNamespace(ownerStatement->baseType); if (namespaceStatementsList) { foreach (const PStatement& namespaceStatement, *namespaceStatementsList) { addChildren(namespaceStatement, fileName, line); } } return; } } // find the most inner scope statement that has a name (not a block) PStatement scopeTypeStatement = mCurrentScope; while (scopeTypeStatement && !isScopeTypeKind(scopeTypeStatement->kind)) { scopeTypeStatement = scopeTypeStatement->parentScope.lock(); } if ( (memberOperator != "::") && ( ownerStatement->kind == EvalStatementKind::Variable) ) { // Get type statement of current (scope) statement PStatement classTypeStatement = ownerStatement->effectiveTypeStatement; if (!classTypeStatement) return; // It's a iterator if (ownerStatement && ownerStatement->typeStatement && STLIterators.contains(ownerStatement->typeStatement->command) && (memberOperator == "->" || memberOperator == "->*") ) { PStatement parentScope = ownerStatement->typeStatement->parentScope.lock(); if (STLContainers.contains(parentScope->fullName)) { QString typeName=mParser->findFirstTemplateParamOf(fileName,ownerStatement->templateParams, parentScope); // qDebug()<<"typeName"<baseStatement->type<baseStatement->command; classTypeStatement=mParser->findTypeDefinitionOf(fileName, typeName,parentScope); if (!classTypeStatement) return; } else if (STLMaps.contains(parentScope->fullName)) { QString typeName="std::pair"; classTypeStatement=mParser->findTypeDefinitionOf(fileName, typeName,parentScope); if (!classTypeStatement) return; } } else if (STLPointers.contains(classTypeStatement->fullName) && (memberOperator == "->" || memberOperator == "->*") && ownerStatement->baseStatement) { //is a smart pointer QString typeName= mParser->findFirstTemplateParamOf( fileName, ownerStatement->baseStatement->type, scope); classTypeStatement = mParser->findTypeDefinitionOf( fileName, typeName, scope); if (!classTypeStatement) return; } else { //normal member access if (memberOperator=="." && ownerStatement->pointerLevel !=0) return; if (memberOperator=="->" && ownerStatement->pointerLevel!=1) return; } if (!isIncluded(classTypeStatement->fileName) && !isIncluded(classTypeStatement->definitionFileName)) return; if ((classTypeStatement == scopeTypeStatement) || (ownerStatement->effectiveTypeStatement->command == "this")) { //we can use all members addChildren(classTypeStatement,fileName,-1); } else { // we can only use public members const StatementMap& children = mParser->statementList().childrenStatements(classTypeStatement); if (children.isEmpty()) return; foreach (const PStatement& childStatement, children) { if ((childStatement->accessibility==StatementAccessibility::Public) && !( childStatement->kind == StatementKind::Constructor || childStatement->kind == StatementKind::Destructor) && !mAddedStatements.contains(childStatement->command)) { addStatement(childStatement,fileName,-1); } } } //todo friend } else if ((memberOperator == "::") && (ownerStatement->kind == EvalStatementKind::Type)) { //we can add all child enum definess PStatement classTypeStatement = ownerStatement->effectiveTypeStatement; if (!classTypeStatement) return; if (!isIncluded(classTypeStatement->fileName) && !isIncluded(classTypeStatement->definitionFileName)) return; if (classTypeStatement->kind == StatementKind::EnumType || classTypeStatement->kind == StatementKind::EnumClassType) { const StatementMap& children = mParser->statementList().childrenStatements(classTypeStatement); foreach (const PStatement& child,children) { addStatement(child,fileName,line); } } else { //class if (classTypeStatement == scopeTypeStatement) { //we can use all static members const StatementMap& children = mParser->statementList().childrenStatements(classTypeStatement); foreach (const PStatement& childStatement, children) { if ( (childStatement->isStatic()) || (childStatement->kind == StatementKind::Typedef || childStatement->kind == StatementKind::Class || childStatement->kind == StatementKind::Enum || childStatement->kind == StatementKind::EnumClassType || childStatement->kind == StatementKind::EnumType )) { addStatement(childStatement,fileName,-1); } } } else { // we can only use public static members const StatementMap& children = mParser->statementList().childrenStatements(classTypeStatement); foreach (const PStatement& childStatement,children) { if ( (childStatement->isStatic()) || (childStatement->kind == StatementKind::Typedef || childStatement->kind == StatementKind::Class || childStatement->kind == StatementKind::Enum || childStatement->kind == StatementKind::EnumClassType || childStatement->kind == StatementKind::EnumType )) { if (childStatement->accessibility == StatementAccessibility::Public) addStatement(childStatement,fileName,-1); } } } } } } } } void CodeCompletionPopup::getCompletionForFunctionWithoutDefinition(const QString& preWord, QStringList ownerExpression, const QString &memberOperator, const QStringList &memberExpression, const QString &fileName, int line) { if(!mParser) { return; } if (!mParser->enabled()) return; if (memberOperator.isEmpty() && ownerExpression.isEmpty() && memberExpression.isEmpty()) return; if (!mParser->freeze()) return; { auto action = finally([this]{ mParser->unFreeze(); }); if (memberOperator.isEmpty()) { getCompletionListForComplexKeyword(preWord); PStatement scopeStatement = mCurrentScope; //add members of current scope that not added before while (scopeStatement && scopeStatement->kind!=StatementKind::Namespace && scopeStatement->kind!=StatementKind::Class) { scopeStatement = scopeStatement->parentScope.lock(); } if (scopeStatement) { if (scopeStatement->kind == StatementKind::Namespace) { //namespace; PStatementList namespaceStatementsList = mParser->findNamespace(scopeStatement->fullName); if (namespaceStatementsList) { foreach (const PStatement& namespaceStatement, *namespaceStatementsList) { addFunctionWithoutDefinitionChildren(namespaceStatement, fileName, line); } } } else { //class addKeyword("operator"); addFunctionWithoutDefinitionChildren(scopeStatement, fileName, line); } } else { //global addFunctionWithoutDefinitionChildren(scopeStatement, fileName, line); } } else { if (memberOperator != "::") return; //the identifier to be completed is a member of variable/class if (ownerExpression.isEmpty()) { // start with '::', we only find in global // add all global members and not added before addFunctionWithoutDefinitionChildren(nullptr, fileName, line); return; } if (memberExpression.length()==2 && memberExpression.front()!="~") return; if (memberExpression.length()>2) return; PStatement scope = mCurrentScope;//the scope the expression in PStatement parentTypeStatement; PEvalStatement ownerStatement = mParser->evalExpression(fileName, ownerExpression, scope); if(!ownerStatement || !ownerStatement->effectiveTypeStatement) { return; } if (ownerStatement->kind==EvalStatementKind::Namespace) { //there might be many statements corresponding to one namespace; PStatementList namespaceStatementsList = mParser->findNamespace(ownerStatement->baseType); if (namespaceStatementsList) { foreach (const PStatement& namespaceStatement, *namespaceStatementsList) { addFunctionWithoutDefinitionChildren(namespaceStatement, fileName, line); } } return; } else if (ownerStatement->effectiveTypeStatement->kind == StatementKind::Class) { addKeyword("operator"); addFunctionWithoutDefinitionChildren(ownerStatement->effectiveTypeStatement, fileName, line); } } } } void CodeCompletionPopup::getCompletionListForComplexKeyword(const QString &preWord) { mFullCompletionStatementList.clear(); if (preWord == "long") { addKeyword("long"); addKeyword("double"); addKeyword("int"); } else if (preWord == "short") { addKeyword("int"); } else if (preWord == "signed") { addKeyword("long"); addKeyword("short"); addKeyword("int"); addKeyword("char"); } else if (preWord == "unsigned") { addKeyword("long"); addKeyword("short"); addKeyword("int"); addKeyword("char"); } else if (preWord == "using") { addKeyword("namespace"); } } void CodeCompletionPopup::getCompletionListForNamespaces(const QString &/*preWord*/, const QString& fileName, int line) { if (!mParser->enabled()) return; if (!mParser->freeze()) return; { auto action = finally([this]{ mParser->unFreeze(); }); QList namespaceNames = mParser->namespaces(); foreach (const QString& name, namespaceNames) { PStatementList namespaces = mParser->findNamespace(name); foreach(const PStatement& statement, *namespaces) { if (isIncluded(statement->fileName) || isIncluded(statement->definitionFileName)) { addStatement(statement,fileName,line); continue; } } } } } void CodeCompletionPopup::getCompletionListForTypes(const QString &preWord, const QString &fileName, int line) { if (preWord=="typedef") { addKeyword("const"); addKeyword("struct"); addKeyword("class"); } if (mShowKeywords) { //add keywords foreach (const QString& keyword,CppTypeKeywords) { addKeyword(keyword); } } if (!mParser->enabled()) return; if (!mParser->freeze()) return; { auto action = finally([this]{ mParser->unFreeze(); }); QList statements = mParser->listTypeStatements(fileName,line); foreach(const PStatement& statement, statements) { if (isIncluded(statement->fileName) || isIncluded(statement->definitionFileName)) { addStatement(statement,fileName,line); } } } } void CodeCompletionPopup::addKeyword(const QString &keyword) { PStatement statement = std::make_shared(); statement->command = keyword; statement->kind = StatementKind::Keyword; statement->fullName = keyword; statement->usageCount = 0; mFullCompletionStatementList.append(statement); } bool CodeCompletionPopup::isIncluded(const QString &fileName) { return mIncludedFiles.contains(fileName); } void CodeCompletionPopup::setHideSymbolsStartWithTwoUnderline(bool newHideSymbolsStartWithTwoUnderline) { mHideSymbolsStartWithTwoUnderline = newHideSymbolsStartWithTwoUnderline; } void CodeCompletionPopup::setLineHeightFactor(float factor) { mDelegate->setLineHeightFactor(factor); } bool CodeCompletionPopup::hideSymbolsStartWithTwoUnderline() const { return mHideSymbolsStartWithTwoUnderline; } bool CodeCompletionPopup::hideSymbolsStartWithUnderline() const { return mHideSymbolsStartWithUnderline; } void CodeCompletionPopup::setHideSymbolsStartWithUnderline(bool newHideSymbolsStartWithUnderline) { mHideSymbolsStartWithUnderline = newHideSymbolsStartWithUnderline; } const QString &CodeCompletionPopup::memberOperator() const { return mMemberOperator; } const QList &CodeCompletionPopup::codeSnippets() const { return mCodeSnippets; } void CodeCompletionPopup::setCodeSnippets(const QList &newCodeSnippets) { mCodeSnippets = newCodeSnippets; } void CodeCompletionPopup::setColors(const std::shared_ptr > > &newColors) { mColors = newColors; } const QString &CodeCompletionPopup::memberPhrase() const { return mMemberPhrase; } const PStatement &CodeCompletionPopup::currentScope() const { return mCurrentScope; } void CodeCompletionPopup::setCurrentScope(const PStatement &newCurrentStatement) { mCurrentScope = newCurrentStatement; } const std::shared_ptr > >& CodeCompletionPopup::colors() const { return mColors; } bool CodeCompletionPopup::sortByScope() const { return mSortByScope; } void CodeCompletionPopup::setSortByScope(bool newSortByScope) { mSortByScope = newSortByScope; } bool CodeCompletionPopup::ignoreCase() const { return mIgnoreCase; } void CodeCompletionPopup::setIgnoreCase(bool newIgnoreCase) { mIgnoreCase = newIgnoreCase; } bool CodeCompletionPopup::showCodeSnippets() const { return mShowCodeSnippets; } void CodeCompletionPopup::setShowCodeSnippets(bool newShowCodeIns) { mShowCodeSnippets = newShowCodeIns; } bool CodeCompletionPopup::showKeywords() const { return mShowKeywords; } void CodeCompletionPopup::setShowKeywords(bool newShowKeywords) { mShowKeywords = newShowKeywords; } bool CodeCompletionPopup::recordUsage() const { return mRecordUsage; } void CodeCompletionPopup::setRecordUsage(bool newRecordUsage) { mRecordUsage = newRecordUsage; } int CodeCompletionPopup::showCount() const { return mShowCount; } void CodeCompletionPopup::setShowCount(int newShowCount) { mShowCount = newShowCount; } const PCppParser &CodeCompletionPopup::parser() const { return mParser; } void CodeCompletionPopup::setParser(const PCppParser &newParser) { mParser = newParser; } void CodeCompletionPopup::hideEvent(QHideEvent *event) { QMutexLocker locker(&mMutex); mListView->setKeypressedCallback(nullptr); mCompletionStatementList.clear(); // foreach (PStatement statement, mFullCompletionStatementList) { // statement->matchPositions.clear(); // } mFullCompletionStatementList.clear(); mIncludedFiles.clear(); mUsings.clear(); mAddedStatements.clear(); mCurrentScope = nullptr; mParser = nullptr; QWidget::hideEvent(event); } bool CodeCompletionPopup::event(QEvent *event) { bool result = QWidget::event(event); if (event->type() == QEvent::FontChange) { mListView->setFont(font()); mDelegate->setFont(font()); } return result; } CodeCompletionListModel::CodeCompletionListModel(const StatementList *statements, QObject *parent): QAbstractListModel(parent), mStatements(statements) { } int CodeCompletionListModel::rowCount(const QModelIndex &) const { return mStatements->count(); } QVariant CodeCompletionListModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); if (index.row()>=mStatements->count()) return QVariant(); switch(role) { case Qt::DisplayRole: { PStatement statement = mStatements->at(index.row()); return statement->command; } } return QVariant(); } PStatement CodeCompletionListModel::statement(const QModelIndex &index) const { if (!index.isValid()) return PStatement(); if (index.row()>=mStatements->count()) return PStatement(); return mStatements->at(index.row()); } QPixmap CodeCompletionListModel::statementIcon(const QModelIndex &index, int size) const { if (!index.isValid()) return QPixmap(); if (index.row()>=mStatements->count()) return QPixmap(); PStatement statement = mStatements->at(index.row()); return pIconsManager->getPixmapForStatement(statement, size); } void CodeCompletionListModel::notifyUpdated() { beginResetModel(); endResetModel(); } void CodeCompletionListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { PStatement statement; if (mModel && (statement = mModel->statement(index)) ) { QFont normalFont{font()}; QFont matchedFont{font()}; normalFont.setBold(false); matchedFont.setBold(true); painter->save(); painter->setFont(normalFont); QColor normalColor = mNormalColor; QColor matchedColor = mMatchedColor; if (option.state & QStyle::State_Selected) { painter->fillRect(option.rect, mCurrentSelectionColor); } QFontMetrics fm{font()}; int iconSize = fm.height()*0.8; QPixmap icon = mModel->statementIcon(index, iconSize); int x=option.rect.left(); if (!icon.isNull()) { qreal dpr=icon.devicePixelRatioF(); int x1=x+(option.rect.height()-icon.width()/dpr)/2; int y1=option.rect.top()+(option.rect.height()-icon.height()/dpr)/2; painter->drawPixmap(x1,y1,icon); x+=option.rect.height(); } QString text = statement->command; int pos=0; int padding = (option.rect.height()-painter->fontMetrics().height())/2; int y=option.rect.bottom()-painter->fontMetrics().descent()-padding; foreach (const PStatementMathPosition& matchPosition, statement->matchPositions) { if (posstart) { QString t = text.mid(pos,matchPosition->start-pos); painter->setPen(normalColor); painter->setFont(normalFont); painter->drawText(x,y,t); x+=painter->fontMetrics().horizontalAdvance(t); } QString t = text.mid(matchPosition->start, matchPosition->end-matchPosition->start); painter->setPen(matchedColor); painter->setFont(matchedFont); painter->drawText(x,y,t); x+=painter->fontMetrics().horizontalAdvance(t); pos=matchPosition->end; } if (possetPen(normalColor); painter->setFont(normalFont); painter->drawText(x,y,t); x+=painter->fontMetrics().horizontalAdvance(t); } painter->restore(); } else { QStyledItemDelegate::paint(painter, option, index); } } const QColor &CodeCompletionListItemDelegate::normalColor() const { return mNormalColor; } void CodeCompletionListItemDelegate::setNormalColor(const QColor &newNormalColor) { mNormalColor = newNormalColor; } const QColor &CodeCompletionListItemDelegate::matchedColor() const { return mMatchedColor; } void CodeCompletionListItemDelegate::setMatchedColor(const QColor &newMatchedColor) { mMatchedColor = newMatchedColor; } const QFont &CodeCompletionListItemDelegate::font() const { return mFont; } void CodeCompletionListItemDelegate::setFont(const QFont &newFont) { mFont = newFont; } QSize CodeCompletionListItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QSize size = QStyledItemDelegate::sizeHint(option, index); size.setHeight(QFontMetrics(mFont).height()*mLineHeightFactor); return size; } float CodeCompletionListItemDelegate::lineHeightFactor() const { return mLineHeightFactor; } void CodeCompletionListItemDelegate::setLineHeightFactor(float newLineHeightFactor) { mLineHeightFactor = newLineHeightFactor; } QColor CodeCompletionListItemDelegate::currentSelectionColor() const { return mCurrentSelectionColor; } void CodeCompletionListItemDelegate::setCurrentSelectionColor(const QColor &newCurrentSelectionColor) { mCurrentSelectionColor = newCurrentSelectionColor; } CodeCompletionListItemDelegate::CodeCompletionListItemDelegate(CodeCompletionListModel *model, QWidget *parent) : QStyledItemDelegate(parent), mModel(model) { mNormalColor = qApp->palette().color(QPalette::Text); mMatchedColor = qApp->palette().color(QPalette::BrightText); mLineHeightFactor = 1.0; }