/************************************************************************** ** ** This file is part of Qt Creator ** ** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). ** ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** ** GNU Lesser General Public License Usage ** ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this file. ** Please review the following information to ensure the GNU Lesser General ** Public License version 2.1 requirements will be met: ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** Other Usage ** ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** **************************************************************************/ #include "glslcompletionassist.h" #include "glsleditorconstants.h" #include "glsleditorplugin.h" #include "reuse.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace GLSLEditor; using namespace Internal; using namespace TextEditor; namespace { enum CompletionOrder { SpecialMemberOrder = -5 }; bool isActivationChar(const QChar &ch) { return ch == QLatin1Char('(') || ch == QLatin1Char('.') || ch == QLatin1Char(','); } bool isIdentifierChar(QChar ch) { return ch.isLetterOrNumber() || ch == QLatin1Char('_'); } bool isDelimiter(QChar ch) { switch (ch.unicode()) { case '{': case '}': case '[': case ']': case ')': case '?': case '!': case ':': case ';': case ',': case '+': case '-': case '*': case '/': return true; default: return false; } } bool checkStartOfIdentifier(const QString &word) { if (! word.isEmpty()) { const QChar ch = word.at(0); if (ch.isLetter() || ch == QLatin1Char('_')) return true; } return false; } } // Anonymous // ---------------------------- // GLSLCompletionAssistProvider // ---------------------------- bool GLSLCompletionAssistProvider::supportsEditor(const Core::Id &editorId) const { return editorId == Core::Id(Constants::C_GLSLEDITOR_ID); } IAssistProcessor *GLSLCompletionAssistProvider::createProcessor() const { return new GLSLCompletionAssistProcessor; } int GLSLCompletionAssistProvider::activationCharSequenceLength() const { return 1; } bool GLSLCompletionAssistProvider::isActivationCharSequence(const QString &sequence) const { return isActivationChar(sequence.at(0)); } // ----------------------------- // GLSLFunctionHintProposalModel // ----------------------------- class GLSLFunctionHintProposalModel : public TextEditor::IFunctionHintProposalModel { public: GLSLFunctionHintProposalModel(QVector functionSymbols) : m_items(functionSymbols) , m_currentArg(-1) {} virtual void reset() {} virtual int size() const { return m_items.size(); } virtual QString text(int index) const; virtual int activeArgument(const QString &prefix) const; private: QVector m_items; mutable int m_currentArg; }; QString GLSLFunctionHintProposalModel::text(int index) const { return m_items.at(index)->prettyPrint(m_currentArg); } int GLSLFunctionHintProposalModel::activeArgument(const QString &prefix) const { const QByteArray &str = prefix.toLatin1(); int argnr = 0; int parcount = 0; GLSL::Lexer lexer(0, str.constData(), str.length()); GLSL::Token tk; QList tokens; do { lexer.yylex(&tk); tokens.append(tk); } while (tk.isNot(GLSL::Parser::EOF_SYMBOL)); for (int i = 0; i < tokens.count(); ++i) { const GLSL::Token &tk = tokens.at(i); if (tk.is(GLSL::Parser::T_LEFT_PAREN)) ++parcount; else if (tk.is(GLSL::Parser::T_RIGHT_PAREN)) --parcount; else if (! parcount && tk.is(GLSL::Parser::T_COMMA)) ++argnr; } if (parcount < 0) return -1; if (argnr != m_currentArg) m_currentArg = argnr; return argnr; } // ----------------------------- // GLSLCompletionAssistProcessor // ----------------------------- GLSLCompletionAssistProcessor::GLSLCompletionAssistProcessor() : m_startPosition(0) , m_keywordIcon(":/glsleditor/images/keyword.png") , m_varIcon(":/glsleditor/images/var.png") , m_functionIcon(":/glsleditor/images/func.png") , m_typeIcon(":/glsleditor/images/type.png") , m_constIcon(":/glsleditor/images/const.png") , m_attributeIcon(":/glsleditor/images/attribute.png") , m_uniformIcon(":/glsleditor/images/uniform.png") , m_varyingIcon(":/glsleditor/images/varying.png") , m_otherIcon(":/glsleditor/images/other.png") {} GLSLCompletionAssistProcessor::~GLSLCompletionAssistProcessor() {} IAssistProposal *GLSLCompletionAssistProcessor::perform(const IAssistInterface *interface) { m_interface.reset(static_cast(interface)); if (interface->reason() == IdleEditor && !acceptsIdleEditor()) return 0; int pos = m_interface->position() - 1; QChar ch = m_interface->characterAt(pos); while (ch.isLetterOrNumber() || ch == QLatin1Char('_')) ch = m_interface->characterAt(--pos); CPlusPlus::ExpressionUnderCursor expressionUnderCursor; //GLSLTextEditorWidget *edit = qobject_cast(editor->widget()); QList members; QStringList specialMembers; bool functionCall = (ch == QLatin1Char('(') && pos == m_interface->position() - 1); if (ch == QLatin1Char(',')) { QTextCursor tc(m_interface->document()); tc.setPosition(pos); const int start = expressionUnderCursor.startOfFunctionCall(tc); if (start == -1) return 0; if (m_interface->characterAt(start) == QLatin1Char('(')) { pos = start; ch = QLatin1Char('('); functionCall = true; } } if (ch == QLatin1Char('.') || functionCall) { const bool memberCompletion = ! functionCall; QTextCursor tc(m_interface->document()); tc.setPosition(pos); // get the expression under cursor const QByteArray code = expressionUnderCursor(tc).toLatin1(); //qDebug() << endl << "expression:" << code; // parse the expression GLSL::Engine engine; GLSL::Parser parser(&engine, code, code.size(), languageVariant(m_interface->mimeType())); GLSL::ExpressionAST *expr = parser.parseExpression(); #if 0 // dump it! QTextStream qout(stdout, QIODevice::WriteOnly); GLSL::ASTDump dump(qout); dump(expr); #endif if (Document::Ptr doc = m_interface->glslDocument()) { GLSL::Scope *currentScope = doc->scopeAt(pos); GLSL::Semantic sem; GLSL::Semantic::ExprResult exprTy = sem.expression(expr, currentScope, doc->engine()); if (exprTy.type) { if (memberCompletion) { if (const GLSL::VectorType *vecTy = exprTy.type->asVectorType()) { members = vecTy->members(); // Sort the most relevant swizzle orderings to the top. specialMembers += QLatin1String("xy"); specialMembers += QLatin1String("xyz"); specialMembers += QLatin1String("xyzw"); specialMembers += QLatin1String("rgb"); specialMembers += QLatin1String("rgba"); specialMembers += QLatin1String("st"); specialMembers += QLatin1String("stp"); specialMembers += QLatin1String("stpq"); } else if (const GLSL::Struct *structTy = exprTy.type->asStructType()) { members = structTy->members(); } else { // some other type } } else { // function completion QVector signatures; if (const GLSL::Function *funTy = exprTy.type->asFunctionType()) signatures.append(const_cast(funTy)); // ### get rid of the const_cast else if (const GLSL::OverloadSet *overload = exprTy.type->asOverloadSetType()) signatures = overload->functions(); if (! signatures.isEmpty()) { m_startPosition = pos + 1; return createHintProposal(signatures); } } } else { // undefined } } else { // sorry, there's no document } } else { // it's a global completion if (Document::Ptr doc = m_interface->glslDocument()) { GLSL::Scope *currentScope = doc->scopeAt(pos); bool isGlobal = !currentScope || !currentScope->scope(); // add the members from the scope chain for (; currentScope; currentScope = currentScope->scope()) members += currentScope->members(); // if this is the global scope, then add some standard Qt attribute // and uniform names for autocompleting variable declarations // this isn't a complete list, just the most common if (isGlobal) { static const char * const attributeNames[] = { "qt_Vertex", "qt_Normal", "qt_MultiTexCoord0", "qt_MultiTexCoord1", "qt_MultiTexCoord2", 0 }; static const char * const uniformNames[] = { "qt_ModelViewProjectionMatrix", "qt_ModelViewMatrix", "qt_ProjectionMatrix", "qt_NormalMatrix", "qt_Texture0", "qt_Texture1", "qt_Texture2", "qt_Color", "qt_Opacity", 0 }; for (int index = 0; attributeNames[index]; ++index) addCompletion(QString::fromLatin1(attributeNames[index]), m_attributeIcon); for (int index = 0; uniformNames[index]; ++index) addCompletion(QString::fromLatin1(uniformNames[index]), m_uniformIcon); } } // if (m_keywordVariant != languageVariant(m_interface->mimeType())) { QStringList keywords = GLSL::Lexer::keywords(languageVariant(m_interface->mimeType())); // m_keywordCompletions.clear(); for (int index = 0; index < keywords.size(); ++index) addCompletion(keywords.at(index), m_keywordIcon); // m_keywordVariant = languageVariant(m_interface->mimeType()); // } // m_completions += m_keywordCompletions; } foreach (GLSL::Symbol *s, members) { QIcon icon; GLSL::Variable *var = s->asVariable(); if (var) { int storageType = var->qualifiers() & GLSL::QualifiedTypeAST::StorageMask; if (storageType == GLSL::QualifiedTypeAST::Attribute) icon = m_attributeIcon; else if (storageType == GLSL::QualifiedTypeAST::Uniform) icon = m_uniformIcon; else if (storageType == GLSL::QualifiedTypeAST::Varying) icon = m_varyingIcon; else if (storageType == GLSL::QualifiedTypeAST::Const) icon = m_constIcon; else icon = m_varIcon; } else if (s->asArgument()) { icon = m_varIcon; } else if (s->asFunction() || s->asOverloadSet()) { icon = m_functionIcon; } else if (s->asStruct()) { icon = m_typeIcon; } else { icon = m_otherIcon; } if (specialMembers.contains(s->name())) addCompletion(s->name(), icon, SpecialMemberOrder); else addCompletion(s->name(), icon); } m_startPosition = pos + 1; return createContentProposal(); } IAssistProposal *GLSLCompletionAssistProcessor::createContentProposal() const { IGenericProposalModel *model = new BasicProposalItemListModel(m_completions); IAssistProposal *proposal = new GenericProposal(m_startPosition, model); return proposal; } IAssistProposal *GLSLCompletionAssistProcessor::createHintProposal( const QVector &symbols) { IFunctionHintProposalModel *model = new GLSLFunctionHintProposalModel(symbols); IAssistProposal *proposal = new FunctionHintProposal(m_startPosition, model); return proposal; } bool GLSLCompletionAssistProcessor::acceptsIdleEditor() const { const int cursorPosition = m_interface->position(); const QChar ch = m_interface->characterAt(cursorPosition - 1); const QChar characterUnderCursor = m_interface->characterAt(cursorPosition); if (isIdentifierChar(ch) && (characterUnderCursor.isSpace() || characterUnderCursor.isNull() || isDelimiter(characterUnderCursor))) { int pos = m_interface->position() - 1; for (; pos != -1; --pos) { if (! isIdentifierChar(m_interface->characterAt(pos))) break; } ++pos; const QString word = m_interface->textAt(pos, cursorPosition - pos); if (word.length() > 2 && checkStartOfIdentifier(word)) { for (int i = 0; i < word.length(); ++i) { if (! isIdentifierChar(word.at(i))) return false; } return true; } } return isActivationChar(ch); } void GLSLCompletionAssistProcessor::addCompletion(const QString &text, const QIcon &icon, int order) { BasicProposalItem *item = new BasicProposalItem; item->setText(text); item->setIcon(icon); item->setOrder(order); m_completions.append(item); } // ----------------------------- // GLSLCompletionAssistInterface // ----------------------------- GLSLCompletionAssistInterface::GLSLCompletionAssistInterface(QTextDocument *document, int position, Core::IFile *file, TextEditor::AssistReason reason, const QString &mimeType, const Document::Ptr &glslDoc) : DefaultAssistInterface(document, position, file, reason) , m_mimeType(mimeType) , m_glslDoc(glslDoc) { }