Commit 71549ec6 authored by Erik Verbruggen's avatar Erik Verbruggen

First version of QML navigation.

parent e58ff77b
......@@ -180,6 +180,7 @@ const char * const G_EDIT_OTHER = "QtCreator.Group.Edit.Other";
// advanced edit menu groups
const char * const G_EDIT_FORMAT = "QtCreator.Group.Edit.Format";
const char * const G_EDIT_REFORMAT = "QtCreator.Group.Edit.Reformat";
const char * const G_EDIT_COLLAPSING = "QtCreator.Group.Edit.Collapsing";
const char * const G_EDIT_FONT = "QtCreator.Group.Edit.Font";
const char * const G_EDIT_EDITOR = "QtCreator.Group.Edit.Editor";
......
#include "duicodeformatter.h"
#include "qmljsast_p.h"
using namespace DuiEditor::Internal;
using namespace QmlJS;
using namespace QmlJS::AST;
DuiCodeFormatter::DuiCodeFormatter()
{
}
DuiCodeFormatter::~DuiCodeFormatter()
{
}
bool DuiCodeFormatter::visit(QmlJS::AST::UiProgram *ast)
{
Node::accept(ast->imports, this);
if (ast->imports && ast->members)
newline();
Node::accept(ast->members, this);
return false;
}
QString DuiCodeFormatter::operator()(QmlJS::AST::UiProgram *ast, const QString &originalSource, const QList<QmlJS::AST::SourceLocation> &comments, int start, int end)
{
m_result.clear();
m_result.reserve(originalSource.length() * 2);
m_originalSource = originalSource;
m_start = start;
m_end = end;
Node::acceptChild(ast, this);
return m_result;
}
bool DuiCodeFormatter::visit(UiImport *ast)
{
append("import ");
append(ast->fileNameToken);
if (ast->versionToken.isValid()) {
append(' ');
append(ast->versionToken);
}
if (ast->asToken.isValid()) {
append(" as ");
append(ast->importIdToken);
}
if (ast->semicolonToken.isValid())
append(';');
newline();
return false;
}
bool DuiCodeFormatter::visit(UiObjectDefinition *ast)
{
indent();
Node::accept(ast->qualifiedTypeNameId, this);
append(' ');
Node::accept(ast->initializer, this);
newline();
return false;
}
bool DuiCodeFormatter::visit(QmlJS::AST::UiQualifiedId *ast)
{
for (UiQualifiedId *it = ast; it; it = it->next) {
append(it->name->asString());
if (it->next)
append('.');
}
return false;
}
bool DuiCodeFormatter::visit(QmlJS::AST::UiObjectInitializer *ast)
{
append(ast->lbraceToken.offset, ast->rbraceToken.end() - ast->lbraceToken.offset);
return false;
}
#ifndef DUICODEFORMATTER_H
#define DUICODEFORMATTER_H
#include <QString>
#include "qmljsastfwd_p.h"
#include "qmljsastvisitor_p.h"
#include "qmljsengine_p.h"
namespace DuiEditor {
namespace Internal {
class DuiCodeFormatter: protected QmlJS::AST::Visitor
{
public:
DuiCodeFormatter();
~DuiCodeFormatter();
QString operator()(QmlJS::AST::UiProgram *ast, const QString &originalSource, const QList<QmlJS::AST::SourceLocation> &comments, int start = -1, int end = -1);
protected:
virtual bool visit(QmlJS::AST::UiProgram *ast);
// virtual bool visit(UiImportList *ast);
virtual bool visit(QmlJS::AST::UiImport *ast);
// virtual bool visit(UiPublicMember *ast);
// virtual bool visit(UiSourceElement *ast);
virtual bool visit(QmlJS::AST::UiObjectDefinition *ast);
virtual bool visit(QmlJS::AST::UiObjectInitializer *ast);
// virtual bool visit(UiObjectBinding *ast);
// virtual bool visit(UiScriptBinding *ast);
// virtual bool visit(UiArrayBinding *ast);
// virtual bool visit(UiObjectMemberList *ast);
// virtual bool visit(UiArrayMemberList *ast);
virtual bool visit(QmlJS::AST::UiQualifiedId *ast);
// virtual bool visit(UiSignature *ast);
// virtual bool visit(UiFormalList *ast);
// virtual bool visit(UiFormal *ast);
//
// virtual void endVisit(UiProgram *ast);
// virtual void endVisit(UiImport *ast);
// virtual void endVisit(UiPublicMember *ast);
// virtual void endVisit(UiSourceElement *ast);
// virtual void endVisit(UiObjectDefinition *ast);
// virtual void endVisit(UiObjectInitializer *ast);
// virtual void endVisit(UiObjectBinding *ast);
// virtual void endVisit(UiScriptBinding *ast);
// virtual void endVisit(UiArrayBinding *ast);
// virtual void endVisit(UiObjectMemberList *ast);
// virtual void endVisit(UiArrayMemberList *ast);
// virtual void endVisit(UiQualifiedId *ast);
// virtual void endVisit(UiSignature *ast);
// virtual void endVisit(UiFormalList *ast);
// virtual void endVisit(UiFormal *ast);
private:
void append(char c) { m_result += c; }
void append(char *s) { m_result += s; }
void append(const QString &s) { m_result += s; }
void append(const QmlJS::AST::SourceLocation &loc) { m_result += textAt(loc); }
void append(int pos, int len) { append(textAt(pos, len)); }
QString textAt(const QmlJS::AST::SourceLocation &loc) const
{ return textAt(loc.offset, loc.length); }
QString textAt(int pos, int len) const
{ return m_originalSource.mid(pos, len); }
void indent() { if (m_indentDepth) append(QString(' ', m_indentDepth)); }
void newline() { append('\n'); }
private:
QString m_result;
QString m_originalSource;
int m_start;
int m_end;
unsigned m_indentDepth;
};
} // namespace Internal
} // namespace DuiEditor
#endif // DUICODEFORMATTER_H
......@@ -34,12 +34,14 @@
#include "duidocument.h"
#include "duimodelmanager.h"
#include "rewriter_p.h"
#include "qmljsastvisitor_p.h"
#include "qmljsast_p.h"
#include "qmljsengine_p.h"
#include "rewriter_p.h"
#include "navigationtokenfinder.h"
#include <coreplugin/icore.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <extensionsystem/pluginmanager.h>
......@@ -67,7 +69,6 @@ enum {
using namespace QmlJS;
using namespace QmlJS::AST;
namespace DuiEditor {
namespace Internal {
......@@ -702,6 +703,35 @@ void ScriptEditor::createToolBar(ScriptEditorEditable *editable)
toolBar->insertWidget(actions.first(), m_methodCombo);
}
TextEditor::BaseTextEditor::Link ScriptEditor::findLinkAt(const QTextCursor &cursor, bool resolveTarget)
{
Link link;
if (!m_modelManager)
return link;
const Snapshot snapshot = m_modelManager->snapshot();
DuiDocument::Ptr doc = snapshot.document(file()->fileName());
if (!doc)
return link;
NavigationTokenFinder finder;
if (finder(doc->program(), cursor.position(), resolveTarget)) {
link.fileName = file()->fileName();
link.pos = finder.linkPosition();
link.length = finder.linkLength();
link.line = finder.targetLine();
link.column = finder.targetColumn() - 1;
}
return link;
}
void ScriptEditor::reformat(QTextDocument *, QTextBlock block)
{
// TODO (EV)
}
void ScriptEditor::contextMenuEvent(QContextMenuEvent *e)
{
QMenu *menu = createStandardContextMenu();
......
......@@ -131,6 +131,8 @@ protected:
void contextMenuEvent(QContextMenuEvent *e);
TextEditor::BaseTextEditorEditable *createEditableInterface();
void createToolBar(ScriptEditorEditable *editable);
TextEditor::BaseTextEditor::Link findLinkAt(const QTextCursor &cursor, bool resolveTarget = true);
virtual void reformat(QTextDocument *doc, QTextBlock block);
private:
virtual bool isElectricCharacter(const QChar &ch) const;
......
......@@ -19,7 +19,9 @@ HEADERS += duieditor.h \
duicompletionvisitor.h \
duimodelmanagerinterface.h \
duieditor_global.h \
duimodelmanager.h
duimodelmanager.h \
duicodeformatter.h \
navigationtokenfinder.h
SOURCES += duieditor.cpp \
duieditorfactory.cpp \
duieditorplugin.cpp \
......@@ -30,5 +32,7 @@ SOURCES += duieditor.cpp \
duidocument.cpp \
duicompletionvisitor.cpp \
duimodelmanagerinterface.cpp \
duimodelmanager.cpp
duimodelmanager.cpp \
duicodeformatter.cpp \
navigationtokenfinder.cpp
RESOURCES += duieditor.qrc
......@@ -113,8 +113,10 @@ bool DuiEditorPlugin::initialize(const QStringList & /*arguments*/, QString *err
m_actionHandler = new TextEditor::TextEditorActionHandler(DuiEditor::Constants::C_DUIEDITOR,
TextEditor::TextEditorActionHandler::Format
| TextEditor::TextEditorActionHandler::Reformat
| TextEditor::TextEditorActionHandler::UnCommentSelection
| TextEditor::TextEditorActionHandler::UnCollapseAll);
m_actionHandler->initializeActions();
m_completion = new DuiCodeCompletion();
addAutoReleasedObject(m_completion);
......
#include <QDebug>
#include "qmljsast_p.h"
#include "qmljsengine_p.h"
#include "navigationtokenfinder.h"
using namespace QmlJS;
using namespace QmlJS::AST;
using namespace DuiEditor::Internal;
bool NavigationTokenFinder::operator()(QmlJS::AST::UiProgram *ast, int position, bool resolveTarget)
{
_resolveTarget = resolveTarget;
_scopes.clear();
_pos = position;
_linkPosition = -1;
_targetLine = -1;
Node::accept(ast, this);
if (resolveTarget)
return targetFound();
else
return linkFound();
}
bool NavigationTokenFinder::visit(QmlJS::AST::IdentifierExpression *ast)
{
if (linkFound())
return false;
if (ast->identifierToken.offset <= _pos && _pos <= ast->identifierToken.end()) {
_linkPosition = ast->identifierToken.offset;
_linkLength = ast->identifierToken.length;
if (Node *node = findDeclarationInScopes(ast->name))
rememberStartPosition(node);
}
return false;
}
bool NavigationTokenFinder::visit(QmlJS::AST::UiArrayBinding *ast)
{
if (linkFound())
return false;
Node::accept(ast->members, this);
return false;
}
bool NavigationTokenFinder::visit(QmlJS::AST::UiPublicMember *ast)
{
if (linkFound())
return false;
Node::accept(ast->expression, this);
return false;
}
bool NavigationTokenFinder::visit(QmlJS::AST::Block *ast)
{
_scopes.push(ast);
if (linkFound())
return false;
return true;
}
void NavigationTokenFinder::endVisit(QmlJS::AST::Block *)
{
_scopes.pop();
}
bool NavigationTokenFinder::visit(QmlJS::AST::UiObjectBinding *ast)
{
_scopes.push(ast);
if (linkFound())
return false;
Node::accept(ast->qualifiedTypeNameId, this);
Node::accept(ast->initializer, this);
return false;
}
void NavigationTokenFinder::endVisit(QmlJS::AST::UiObjectBinding *)
{
_scopes.pop();
}
bool NavigationTokenFinder::visit(QmlJS::AST::UiObjectDefinition *ast)
{
_scopes.push(ast);
if (linkFound())
return false;
return true;
}
void NavigationTokenFinder::endVisit(QmlJS::AST::UiObjectDefinition *)
{
_scopes.pop();
}
bool NavigationTokenFinder::visit(QmlJS::AST::UiQualifiedId *ast)
{
if (linkFound())
return false;
if (ast->identifierToken.offset <= _pos) {
for (UiQualifiedId *iter = ast; iter; iter = iter->next) {
if (_pos <= iter->identifierToken.end()) {
_linkPosition = ast->identifierToken.offset;
for (UiQualifiedId *iter2 = ast; iter2; iter2 = iter2->next)
_linkLength = iter2->identifierToken.end() - _linkPosition;
if (Node *node = findDeclarationInScopes(ast))
rememberStartPosition(node);
return false;
}
}
}
return false;
}
bool NavigationTokenFinder::visit(QmlJS::AST::UiScriptBinding *ast)
{
if (linkFound())
return false;
Node::accept(ast->statement, this);
return false;
}
bool NavigationTokenFinder::visit(QmlJS::AST::UiSourceElement * /*ast*/)
{
return false;
}
void NavigationTokenFinder::rememberStartPosition(QmlJS::AST::Node *node)
{
if (UiObjectMember *om = dynamic_cast<UiObjectMember*>(node)) {
_targetLine = om->firstSourceLocation().startLine;
_targetColumn = om->firstSourceLocation().startColumn;
} else if (VariableDeclaration *vd = cast<VariableDeclaration*>(node)) {
_targetLine = vd->identifierToken.startLine;
_targetColumn = vd->identifierToken.startColumn;
} else {
qWarning() << "Found declaration of unknown type as a navigation target";
}
}
static inline bool isId(QmlJS::AST::UiQualifiedId *ast)
{
return !(ast->next) && ast->name->asString() == "id";
}
static inline QString idToString(QmlJS::AST::Statement *stmt)
{
if (ExpressionStatement *e = cast<ExpressionStatement*>(stmt))
if (IdentifierExpression *i = cast<IdentifierExpression*>(e->expression))
return i->name->asString();
return QString::null;
}
static QmlJS::AST::Node *findDeclaration(const QString &nameId, QmlJS::AST::UiObjectMember *m)
{
if (UiPublicMember *p = cast<UiPublicMember*>(m)) {
if (p->name->asString() == nameId)
return p;
} else if (UiObjectBinding *o = cast<UiObjectBinding*>(m)) {
if (!(o->qualifiedId->next) && o->qualifiedId->name->asString() == nameId)
return o;
} else if (UiArrayBinding *a = cast<UiArrayBinding*>(m)) {
if (!(a->qualifiedId->next) && a->qualifiedId->name->asString() == nameId)
return a;
} else if (UiScriptBinding *s = cast<UiScriptBinding*>(m)) {
if (isId(s->qualifiedId) && nameId == idToString(s->statement))
return s;
}
return 0;
}
static QmlJS::AST::Node *findDeclaration(const QString &nameId, QmlJS::AST::UiObjectMemberList *l)
{
for (UiObjectMemberList *iter = l; iter; iter = iter->next)
if (Node *n = findDeclaration(nameId, iter->member))
return n;
return 0;
}
static QmlJS::AST::Node *findDeclaration(const QString &nameId, QmlJS::AST::Statement *s)
{
if (VariableStatement *v = cast<VariableStatement*>(s)) {
for (VariableDeclarationList *l = v->declarations; l; l = l->next) {
if (l->declaration->name->asString() == nameId)
return l->declaration;
}
}
return 0;
}
static QmlJS::AST::Node *findDeclaration(const QString &nameId, QmlJS::AST::StatementList *l)
{
for (StatementList *iter = l; iter; iter = iter->next)
if (Node *n = findDeclaration(nameId, iter->statement))
return n;
return 0;
}
static QmlJS::AST::Node *findDeclarationAsDirectChild(const QString &nameId, QmlJS::AST::Node *node)
{
if (UiObjectBinding *binding = cast<UiObjectBinding*>(node)) {
return findDeclaration(nameId, binding->initializer->members);
} else if (UiObjectDefinition *def = cast<UiObjectDefinition*>(node)) {
return findDeclaration(nameId, def->initializer->members);
} else if (Block *block = cast<Block *>(node)) {
return findDeclaration(nameId, block->statements);
} else {
return 0;
}
}
static QmlJS::AST::Node *findDeclarationInNode(QmlJS::AST::UiQualifiedId *qualifiedId, QmlJS::AST::Node *node)
{
if (!qualifiedId || !node)
return node;
else
return findDeclarationInNode(qualifiedId->next, findDeclarationAsDirectChild(qualifiedId->name->asString(), node));
}
QmlJS::AST::Node *NavigationTokenFinder::findDeclarationInScopes(QmlJS::NameId *nameId)
{
if (!_resolveTarget)
return 0;
const QString nameAsString = nameId->asString();
foreach (QmlJS::AST::Node *scope, _scopes) {
Node *result = findDeclarationAsDirectChild(nameAsString, scope);
if (result)
return result;
}
return 0;
}
QmlJS::AST::Node *NavigationTokenFinder::findDeclarationInScopes(QmlJS::AST::UiQualifiedId *qualifiedId)
{
if (!_resolveTarget)
return 0;
const QString nameAsString = qualifiedId->name->asString();
qDebug() << "findDecl. for id part" << nameAsString;
foreach (QmlJS::AST::Node *scope, _scopes) {
Node *result = findDeclarationAsDirectChild(nameAsString, scope);
if (result)
return findDeclarationInNode(qualifiedId->next, result);
}
return 0;
}
#ifndef NAVIGATIONTOKENFINDER_H
#define NAVIGATIONTOKENFINDER_H
#include <QStack>
#include <QString>
#include "qmljsastvisitor_p.h"
namespace QmlJS {
class NameId;
} // namespace QmlJS
namespace DuiEditor {
namespace Internal {
class NavigationTokenFinder: protected QmlJS::AST::Visitor
{
public:
bool operator()(QmlJS::AST::UiProgram *ast, int position, bool resolveTarget);
bool targetFound() const { return _targetLine != -1; }
bool linkFound() const { return _linkPosition != -1; }
int linkPosition() const { return _linkPosition; }
int linkLength() const { return _linkLength; }
int targetLine() const { return _targetLine; }
int targetColumn() const { return _targetColumn; }
protected:
virtual bool visit(QmlJS::AST::Block *ast);
virtual bool visit(QmlJS::AST::IdentifierExpression *ast);
virtual bool visit(QmlJS::AST::UiArrayBinding *ast);
virtual bool visit(QmlJS::AST::UiPublicMember *ast);
virtual bool visit(QmlJS::AST::UiObjectBinding *ast);
virtual bool visit(QmlJS::AST::UiObjectDefinition *ast);
virtual bool visit(QmlJS::AST::UiQualifiedId *ast);
virtual bool visit(QmlJS::AST::UiScriptBinding *ast);
virtual bool visit(QmlJS::AST::UiSourceElement *ast);
virtual void endVisit(QmlJS::AST::Block *);
virtual void endVisit(QmlJS::AST::UiObjectBinding *);
virtual void endVisit(QmlJS::AST::UiObjectDefinition *);
private:
QmlJS::AST::Node *findDeclarationInScopes(QmlJS::AST::UiQualifiedId *ast);
QmlJS::AST::Node *findDeclarationInScopes(QmlJS::NameId *id);
void rememberStartPosition(QmlJS::AST::Node *node);
private:
bool _resolveTarget;
quint32 _pos;
int _linkPosition;