summaryrefslogtreecommitdiff
path: root/src/plugins/glsleditor/glslcompletionassist.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/glsleditor/glslcompletionassist.cpp')
-rw-r--r--src/plugins/glsleditor/glslcompletionassist.cpp478
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)
+{
+}