diff options
Diffstat (limited to 'src/plugins/glsleditor/glslcompletionassist.cpp')
-rw-r--r-- | src/plugins/glsleditor/glslcompletionassist.cpp | 478 |
1 files changed, 478 insertions, 0 deletions
diff --git a/src/plugins/glsleditor/glslcompletionassist.cpp b/src/plugins/glsleditor/glslcompletionassist.cpp new file mode 100644 index 0000000000..125dbad66f --- /dev/null +++ b/src/plugins/glsleditor/glslcompletionassist.cpp @@ -0,0 +1,478 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.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 info@qt.nokia.com. +** +**************************************************************************/ + +#include "glslcompletionassist.h" +#include "glsleditorconstants.h" +#include "glsleditorplugin.h" +#include "reuse.h" + +#include <glsl/glslengine.h> +#include <glsl/glslengine.h> +#include <glsl/glsllexer.h> +#include <glsl/glslparser.h> +#include <glsl/glslsemantic.h> +#include <glsl/glslsymbols.h> +#include <glsl/glslastdump.h> + +#include <coreplugin/ifile.h> +#include <texteditor/completionsettings.h> +#include <texteditor/codeassist/basicproposalitem.h> +#include <texteditor/codeassist/basicproposalitemlistmodel.h> +#include <texteditor/codeassist/genericproposal.h> +#include <texteditor/codeassist/functionhintproposal.h> +#include <cplusplus/ExpressionUnderCursor.h> +#include <utils/faketooltip.h> + +#include <QtGui/QIcon> +#include <QtGui/QPainter> +#include <QtGui/QLabel> +#include <QtGui/QToolButton> +#include <QtGui/QHBoxLayout> +#include <QtGui/QApplication> +#include <QtGui/QDesktopWidget> +#include <QtCore/QDebug> + +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 QString &editorId) const +{ + return editorId == QLatin1String(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<GLSL::Function *> 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<GLSL::Function *> 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<GLSL::Token> 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<const GLSLCompletionAssistInterface *>(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<GLSLTextEditorWidget *>(editor->widget()); + + QList<GLSL::Symbol *> 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<GLSL::Function *> signatures; + if (const GLSL::Function *funTy = exprTy.type->asFunctionType()) + signatures.append(const_cast<GLSL::Function *>(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<GLSL::Function *> &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) +{ +} |