forked from qt-creator/qt-creator
479 lines
16 KiB
C++
479 lines
16 KiB
C++
|
|
/**************************************************************************
|
||
|
|
**
|
||
|
|
** 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)
|
||
|
|
{
|
||
|
|
}
|