qmljseditor.cpp 48.2 KB
Newer Older
1 2 3 4
/**************************************************************************
**
** This file is part of Qt Creator
**
con's avatar
con committed
5
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6
**
hjk's avatar
hjk committed
7
** Contact: Nokia Corporation (info@qt.nokia.com)
8 9 10 11
**
**
** GNU Lesser General Public License Usage
**
hjk's avatar
hjk committed
12 13 14 15 16 17
** 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.
18
**
con's avatar
con committed
19
** In addition, as a special exception, Nokia gives you certain additional
hjk's avatar
hjk committed
20
** rights. These rights are described in the Nokia Qt LGPL Exception
con's avatar
con committed
21 22
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
23 24 25 26 27
** 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.
**
con's avatar
con committed
28
** If you have questions regarding the use of this file, please contact
Tobias Hunger's avatar
Tobias Hunger committed
29
** Nokia at info@qt.nokia.com.
30 31 32
**
**************************************************************************/

33
#include "qmljseditor.h"
34
#include "qmljseditoreditable.h"
35
#include "qmljseditorconstants.h"
36
#include "qmljshighlighter.h"
37
#include "qmljseditorplugin.h"
38 39
#include "qmljsquickfix.h"
#include "qmloutlinemodel.h"
40
#include "qmljsfindreferences.h"
41
#include "qmljssemantichighlighter.h"
42
#include "qmljsindenter.h"
43
#include "qmljsautocompleter.h"
44

45
#include <qmljs/qmljsbind.h>
46
#include <qmljs/qmljsevaluate.h>
47
#include <qmljs/qmljsdocument.h>
48
#include <qmljs/qmljsicontextpane.h>
49
#include <qmljs/qmljslookupcontext.h>
50
#include <qmljs/qmljsmodelmanagerinterface.h>
51 52 53
#include <qmljs/parser/qmljsastvisitor_p.h>
#include <qmljs/parser/qmljsast_p.h>
#include <qmljs/parser/qmljsengine_p.h>
54

55 56
#include <qmljstools/qmljsqtstylecodeformatter.h>

57
#include <coreplugin/actionmanager/actionmanager.h>
58
#include <coreplugin/actionmanager/actioncontainer.h>
59
#include <coreplugin/uniqueidmanager.h>
60
#include <coreplugin/actionmanager/command.h>
61
#include <coreplugin/editormanager/editormanager.h>
62 63
#include <coreplugin/icore.h>
#include <coreplugin/mimedatabase.h>
64
#include <extensionsystem/pluginmanager.h>
65 66
#include <texteditor/basetextdocument.h>
#include <texteditor/fontsettings.h>
67
#include <texteditor/tabsettings.h>
68 69
#include <texteditor/texteditorconstants.h>
#include <texteditor/texteditorsettings.h>
70
#include <texteditor/syntaxhighlighter.h>
71
#include <texteditor/refactoroverlay.h>
72
#include <texteditor/tooltip/tooltip.h>
73
#include <qmldesigner/qmldesignerconstants.h>
74
#include <projectexplorer/projectexplorerconstants.h>
75
#include <utils/changeset.h>
76 77
#include <utils/uncommentselection.h>

78
#include <QtCore/QFileInfo>
79
#include <QtCore/QSignalMapper>
80 81 82 83
#include <QtCore/QTimer>

#include <QtGui/QMenu>
#include <QtGui/QComboBox>
84
#include <QtGui/QHeaderView>
Roberto Raggi's avatar
Roberto Raggi committed
85
#include <QtGui/QInputDialog>
86
#include <QtGui/QMainWindow>
con's avatar
con committed
87
#include <QtGui/QToolBar>
88
#include <QtGui/QTreeView>
89 90

enum {
91
    UPDATE_DOCUMENT_DEFAULT_INTERVAL = 100,
92
    UPDATE_USES_DEFAULT_INTERVAL = 150,
93
    UPDATE_OUTLINE_INTERVAL = 500 // msecs after new semantic info has been arrived / cursor has moved
94 95
};

96 97
using namespace QmlJS;
using namespace QmlJS::AST;
98
using namespace QmlJSEditor;
99
using namespace QmlJSEditor::Internal;
100

101 102
namespace {

Roberto Raggi's avatar
Roberto Raggi committed
103
class FindIdDeclarations: protected Visitor
104 105
{
public:
106
    typedef QHash<QString, QList<AST::SourceLocation> > Result;
107

108
    Result operator()(Document::Ptr doc)
109 110 111
    {
        _ids.clear();
        _maybeIds.clear();
112 113
        if (doc && doc->qmlProgram())
            doc->qmlProgram()->accept(this);
114 115
        return _ids;
    }
116 117

protected:
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
    QString asString(AST::UiQualifiedId *id)
    {
        QString text;
        for (; id; id = id->next) {
            if (id->name)
                text += id->name->asString();
            else
                text += QLatin1Char('?');

            if (id->next)
                text += QLatin1Char('.');
        }

        return text;
    }

    void accept(AST::Node *node)
    { AST::Node::acceptChild(node, this); }

    using Visitor::visit;
    using Visitor::endVisit;

    virtual bool visit(AST::UiScriptBinding *node)
    {
        if (asString(node->qualifiedId) == QLatin1String("id")) {
            if (AST::ExpressionStatement *stmt = AST::cast<AST::ExpressionStatement*>(node->statement)) {
                if (AST::IdentifierExpression *idExpr = AST::cast<AST::IdentifierExpression *>(stmt->expression)) {
                    if (idExpr->name) {
                        const QString id = idExpr->name->asString();
                        QList<AST::SourceLocation> *locs = &_ids[id];
                        locs->append(idExpr->firstSourceLocation());
                        locs->append(_maybeIds.value(id));
                        _maybeIds.remove(id);
                        return false;
                    }
                }
            }
        }

        accept(node->statement);

        return false;
    }

    virtual bool visit(AST::IdentifierExpression *node)
    {
        if (node->name) {
            const QString name = node->name->asString();

            if (_ids.contains(name))
                _ids[name].append(node->identifierToken);
            else
                _maybeIds[name].append(node->identifierToken);
        }
        return false;
    }
174 175

private:
176 177
    Result _ids;
    Result _maybeIds;
178 179
};

180 181
class FindDeclarations: protected Visitor
{
182 183
    QList<Declaration> _declarations;
    int _depth;
184 185

public:
186 187 188 189 190 191 192
    QList<Declaration> operator()(AST::Node *node)
    {
        _depth = -1;
        _declarations.clear();
        accept(node);
        return _declarations;
    }
193 194

protected:
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
    using Visitor::visit;
    using Visitor::endVisit;

    QString asString(AST::UiQualifiedId *id)
    {
        QString text;
        for (; id; id = id->next) {
            if (id->name)
                text += id->name->asString();
            else
                text += QLatin1Char('?');

            if (id->next)
                text += QLatin1Char('.');
        }
210

211 212
        return text;
    }
Roberto Raggi's avatar
Roberto Raggi committed
213

214 215 216 217 218 219 220 221 222 223 224 225
    void accept(AST::Node *node)
    { AST::Node::acceptChild(node, this); }

    void init(Declaration *decl, AST::UiObjectMember *member)
    {
        const SourceLocation first = member->firstSourceLocation();
        const SourceLocation last = member->lastSourceLocation();
        decl->startLine = first.startLine;
        decl->startColumn = first.startColumn;
        decl->endLine = last.startLine;
        decl->endColumn = last.startColumn + last.length;
    }
Roberto Raggi's avatar
Roberto Raggi committed
226

227 228 229 230 231 232 233 234 235 236
    void init(Declaration *decl, AST::ExpressionNode *expressionNode)
    {
        const SourceLocation first = expressionNode->firstSourceLocation();
        const SourceLocation last = expressionNode->lastSourceLocation();
        decl->startLine = first.startLine;
        decl->startColumn = first.startColumn;
        decl->endLine = last.startLine;
        decl->endColumn = last.startColumn + last.length;
    }

237 238 239
    virtual bool visit(AST::UiObjectDefinition *node)
    {
        ++_depth;
Roberto Raggi's avatar
Roberto Raggi committed
240

241 242
        Declaration decl;
        init(&decl, node);
Roberto Raggi's avatar
Roberto Raggi committed
243

244 245 246 247 248
        decl.text.fill(QLatin1Char(' '), _depth);
        if (node->qualifiedTypeNameId)
            decl.text.append(asString(node->qualifiedTypeNameId));
        else
            decl.text.append(QLatin1Char('?'));
Roberto Raggi's avatar
Roberto Raggi committed
249

250
        _declarations.append(decl);
Roberto Raggi's avatar
Roberto Raggi committed
251

252 253
        return true; // search for more bindings
    }
Roberto Raggi's avatar
Roberto Raggi committed
254

255 256 257 258
    virtual void endVisit(AST::UiObjectDefinition *)
    {
        --_depth;
    }
Roberto Raggi's avatar
Roberto Raggi committed
259

260 261 262
    virtual bool visit(AST::UiObjectBinding *node)
    {
        ++_depth;
Roberto Raggi's avatar
Roberto Raggi committed
263

264 265
        Declaration decl;
        init(&decl, node);
Roberto Raggi's avatar
Roberto Raggi committed
266

267
        decl.text.fill(QLatin1Char(' '), _depth);
Roberto Raggi's avatar
Roberto Raggi committed
268

269 270
        decl.text.append(asString(node->qualifiedId));
        decl.text.append(QLatin1String(": "));
Roberto Raggi's avatar
Roberto Raggi committed
271

272 273 274 275
        if (node->qualifiedTypeNameId)
            decl.text.append(asString(node->qualifiedTypeNameId));
        else
            decl.text.append(QLatin1Char('?'));
Roberto Raggi's avatar
Roberto Raggi committed
276

277
        _declarations.append(decl);
Roberto Raggi's avatar
Roberto Raggi committed
278

279 280
        return true; // search for more bindings
    }
Roberto Raggi's avatar
Roberto Raggi committed
281

282 283 284 285
    virtual void endVisit(AST::UiObjectBinding *)
    {
        --_depth;
    }
Roberto Raggi's avatar
Roberto Raggi committed
286

287
    virtual bool visit(AST::UiScriptBinding *)
288 289
    {
        ++_depth;
Roberto Raggi's avatar
Roberto Raggi committed
290

291
#if 0 // ### ignore script bindings for now.
292 293
        Declaration decl;
        init(&decl, node);
Roberto Raggi's avatar
Roberto Raggi committed
294

295 296
        decl.text.fill(QLatin1Char(' '), _depth);
        decl.text.append(asString(node->qualifiedId));
Roberto Raggi's avatar
Roberto Raggi committed
297

298
        _declarations.append(decl);
299
#endif
Roberto Raggi's avatar
Roberto Raggi committed
300

301 302
        return false; // more more bindings in this subtree.
    }
Roberto Raggi's avatar
Roberto Raggi committed
303

304 305 306 307
    virtual void endVisit(AST::UiScriptBinding *)
    {
        --_depth;
    }
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359

    virtual bool visit(AST::FunctionExpression *)
    {
        return false;
    }

    virtual bool visit(AST::FunctionDeclaration *ast)
    {
        if (! ast->name)
            return false;

        Declaration decl;
        init(&decl, ast);

        decl.text.fill(QLatin1Char(' '), _depth);
        decl.text += ast->name->asString();

        decl.text += QLatin1Char('(');
        for (FormalParameterList *it = ast->formals; it; it = it->next) {
            if (it->name)
                decl.text += it->name->asString();

            if (it->next)
                decl.text += QLatin1String(", ");
        }

        decl.text += QLatin1Char(')');

        _declarations.append(decl);

        return false;
    }

    virtual bool visit(AST::VariableDeclaration *ast)
    {
        if (! ast->name)
            return false;

        Declaration decl;
        decl.text.fill(QLatin1Char(' '), _depth);
        decl.text += ast->name->asString();

        const SourceLocation first = ast->identifierToken;
        decl.startLine = first.startLine;
        decl.startColumn = first.startColumn;
        decl.endLine = first.startLine;
        decl.endColumn = first.startColumn + first.length;

        _declarations.append(decl);

        return false;
    }
360 361
};

362 363 364 365 366 367 368 369 370 371
class CreateRanges: protected AST::Visitor
{
    QTextDocument *_textDocument;
    QList<Range> _ranges;

public:
    QList<Range> operator()(QTextDocument *textDocument, Document::Ptr doc)
    {
        _textDocument = textDocument;
        _ranges.clear();
372 373
        if (doc && doc->ast() != 0)
            doc->ast()->accept(this);
374 375 376 377 378 379 380 381
        return _ranges;
    }

protected:
    using AST::Visitor::visit;

    virtual bool visit(AST::UiObjectBinding *ast)
    {
382
        if (ast->initializer && ast->initializer->lbraceToken.length)
383
            _ranges.append(createRange(ast, ast->initializer));
384 385 386 387 388
        return true;
    }

    virtual bool visit(AST::UiObjectDefinition *ast)
    {
389
        if (ast->initializer && ast->initializer->lbraceToken.length)
390
            _ranges.append(createRange(ast, ast->initializer));
391 392 393
        return true;
    }

394 395 396 397 398 399 400 401 402 403 404 405
    virtual bool visit(AST::FunctionExpression *ast)
    {
        _ranges.append(createRange(ast));
        return true;
    }

    virtual bool visit(AST::FunctionDeclaration *ast)
    {
        _ranges.append(createRange(ast));
        return true;
    }

406
    Range createRange(AST::UiObjectMember *member, AST::UiObjectInitializer *ast)
407 408 409
    {
        Range range;

410
        range.ast = member;
411 412

        range.begin = QTextCursor(_textDocument);
413
        range.begin.setPosition(member->firstSourceLocation().begin());
414 415

        range.end = QTextCursor(_textDocument);
416
        range.end.setPosition(ast->rbraceToken.end());
417 418
        return range;
    }
419 420 421 422 423 424 425 426 427 428 429 430

    Range createRange(AST::FunctionExpression *ast)
    {
        Range range;

        range.ast = ast;

        range.begin = QTextCursor(_textDocument);
        range.begin.setPosition(ast->lbraceToken.begin());

        range.end = QTextCursor(_textDocument);
        range.end.setPosition(ast->rbraceToken.end());
431

432 433 434
        return range;
    }

435 436
};

437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472

class CollectASTNodes: protected AST::Visitor
{
public:
    QList<AST::UiQualifiedId *> qualifiedIds;
    QList<AST::IdentifierExpression *> identifiers;
    QList<AST::FieldMemberExpression *> fieldMembers;

    void accept(AST::Node *node)
    {
        if (node)
            node->accept(this);
    }

protected:
    using AST::Visitor::visit;

    virtual bool visit(AST::UiQualifiedId *ast)
    {
        qualifiedIds.append(ast);
        return false;
    }

    virtual bool visit(AST::IdentifierExpression *ast)
    {
        identifiers.append(ast);
        return false;
    }

    virtual bool visit(AST::FieldMemberExpression *ast)
    {
        fieldMembers.append(ast);
        return true;
    }
};

473 474 475
} // end of anonymous namespace


476
AST::Node *SemanticInfo::declaringMember(int cursorPosition) const
477
{
478
    AST::Node *declaringMember = 0;
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493

    for (int i = ranges.size() - 1; i != -1; --i) {
        const Range &range = ranges.at(i);

        if (range.begin.isNull() || range.end.isNull()) {
            continue;
        } else if (cursorPosition >= range.begin.position() && cursorPosition <= range.end.position()) {
            declaringMember = range.ast;
            break;
        }
    }

    return declaringMember;
}

494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
QmlJS::AST::Node *SemanticInfo::declaringMemberNoProperties(int cursorPosition) const
{
   AST::Node *node = declaringMember(cursorPosition);

   if (UiObjectDefinition *objectDefinition = cast<UiObjectDefinition*>(node)) {
       QString name = objectDefinition->qualifiedTypeNameId->name->asString();
       if (!name.isNull() && name.at(0).isLower()) {
           QList<AST::Node *> path = astPath(cursorPosition);
           if (path.size() > 1)
               return path.at(path.size() - 2);
       } else if (name.contains("GradientStop")) {
           QList<AST::Node *> path = astPath(cursorPosition);
           if (path.size() > 2)
               return path.at(path.size() - 3);
       }
   } else if (UiObjectBinding *objectBinding = cast<UiObjectBinding*>(node)) {
       QString name = objectBinding->qualifiedTypeNameId->name->asString();
       if (name.contains("Gradient")) {
           QList<AST::Node *> path = astPath(cursorPosition);
           if (path.size() > 1)
               return path.at(path.size() - 2);
       }
   }

   return node;
}

521 522 523 524 525 526 527 528 529 530 531 532 533 534 535
QList<AST::Node *> SemanticInfo::astPath(int cursorPosition) const
{
    QList<AST::Node *> path;

    foreach (const Range &range, ranges) {
        if (range.begin.isNull() || range.end.isNull()) {
            continue;
        } else if (cursorPosition >= range.begin.position() && cursorPosition <= range.end.position()) {
            path += range.ast;
        }
    }

    return path;
}

536 537
LookupContext::Ptr SemanticInfo::lookupContext(const QList<QmlJS::AST::Node *> &path) const
{
538 539 540 541 542
    Q_ASSERT(! m_context.isNull());

    if (m_context.isNull())
        return LookupContext::create(document, snapshot, path);

543
    return LookupContext::create(document, *m_context, path);
544 545
}

546 547 548 549 550 551
static bool importContainsCursor(UiImport *importAst, unsigned cursorPosition)
{
    return cursorPosition >= importAst->firstSourceLocation().begin()
           && cursorPosition <= importAst->lastSourceLocation().end();
}

552 553 554 555 556 557 558
AST::Node *SemanticInfo::nodeUnderCursor(int pos) const
{
    if (! document)
        return 0;

    const unsigned cursorPosition = pos;

559 560 561
    foreach (const Interpreter::ImportInfo &import, document->bind()->imports()) {
        if (importContainsCursor(import.ast(), cursorPosition))
            return import.ast();
562 563
    }

564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588
    CollectASTNodes nodes;
    nodes.accept(document->ast());

    foreach (AST::UiQualifiedId *q, nodes.qualifiedIds) {
        if (cursorPosition >= q->identifierToken.begin()) {
            for (AST::UiQualifiedId *tail = q; tail; tail = tail->next) {
                if (! tail->next && cursorPosition <= tail->identifierToken.end())
                    return q;
            }
        }
    }

    foreach (AST::IdentifierExpression *id, nodes.identifiers) {
        if (cursorPosition >= id->identifierToken.begin() && cursorPosition <= id->identifierToken.end())
            return id;
    }

    foreach (AST::FieldMemberExpression *mem, nodes.fieldMembers) {
        if (mem->name && cursorPosition >= mem->identifierToken.begin() && cursorPosition <= mem->identifierToken.end())
            return mem;
    }

    return 0;
}

589 590 591 592 593 594 595 596
bool SemanticInfo::isValid() const
{
    if (document && m_context)
        return true;

    return false;
}

597 598 599
int SemanticInfo::revision() const
{
    if (document)
600
        return document->editorRevision();
601 602 603 604

    return 0;
}

605 606
QmlJSTextEditorWidget::QmlJSTextEditorWidget(QWidget *parent) :
    TextEditor::BaseTextEditorWidget(parent),
607
    m_outlineCombo(0),
608
    m_outlineModel(new QmlOutlineModel(this)),
609
    m_modelManager(0),
610
    m_contextPane(0),
611
    m_updateSelectedElements(false),
612
    m_findReferences(new FindReferences(this))
613
{
614
    qRegisterMetaType<QmlJSEditor::SemanticInfo>("QmlJSEditor::SemanticInfo");
Christian Kamm's avatar
Christian Kamm committed
615 616 617 618

    m_semanticHighlighter = new SemanticHighlighter(this);
    m_semanticHighlighter->start();

619 620 621
    setParenthesesMatchingEnabled(true);
    setMarksVisible(true);
    setCodeFoldingSupported(true);
622
    setIndenter(new Indenter);
623
    setAutoCompleter(new AutoCompleter);
624

625 626 627 628
    m_updateDocumentTimer = new QTimer(this);
    m_updateDocumentTimer->setInterval(UPDATE_DOCUMENT_DEFAULT_INTERVAL);
    m_updateDocumentTimer->setSingleShot(true);
    connect(m_updateDocumentTimer, SIGNAL(timeout()), this, SLOT(updateDocumentNow()));
629

630 631 632 633 634
    m_updateUsesTimer = new QTimer(this);
    m_updateUsesTimer->setInterval(UPDATE_USES_DEFAULT_INTERVAL);
    m_updateUsesTimer->setSingleShot(true);
    connect(m_updateUsesTimer, SIGNAL(timeout()), this, SLOT(updateUsesNow()));

635 636 637
    m_semanticRehighlightTimer = new QTimer(this);
    m_semanticRehighlightTimer->setInterval(UPDATE_DOCUMENT_DEFAULT_INTERVAL);
    m_semanticRehighlightTimer->setSingleShot(true);
638
    connect(m_semanticRehighlightTimer, SIGNAL(timeout()), this, SLOT(forceSemanticRehighlight()));
639

640
    connect(this, SIGNAL(textChanged()), this, SLOT(updateDocument()));
641

642
    connect(this, SIGNAL(textChanged()), this, SLOT(updateUses()));
643
    connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(updateUses()));
644

645 646 647 648 649
    m_updateOutlineTimer = new QTimer(this);
    m_updateOutlineTimer->setInterval(UPDATE_OUTLINE_INTERVAL);
    m_updateOutlineTimer->setSingleShot(true);
    connect(m_updateOutlineTimer, SIGNAL(timeout()), this, SLOT(updateOutlineNow()));

650 651 652 653
    m_updateOutlineIndexTimer = new QTimer(this);
    m_updateOutlineIndexTimer->setInterval(UPDATE_OUTLINE_INTERVAL);
    m_updateOutlineIndexTimer->setSingleShot(true);
    connect(m_updateOutlineIndexTimer, SIGNAL(timeout()), this, SLOT(updateOutlineIndexNow()));
654

Lasse Holmstedt's avatar
Lasse Holmstedt committed
655
    m_cursorPositionTimer  = new QTimer(this);
656
    m_cursorPositionTimer->setInterval(UPDATE_OUTLINE_INTERVAL);
Lasse Holmstedt's avatar
Lasse Holmstedt committed
657 658
    m_cursorPositionTimer->setSingleShot(true);
    connect(m_cursorPositionTimer, SIGNAL(timeout()), this, SLOT(updateCursorPositionNow()));
659

660
    baseTextDocument()->setSyntaxHighlighter(new Highlighter(document()));
661
    baseTextDocument()->setCodec(QTextCodec::codecForName("UTF-8")); // qml files are defined to be utf-8
662

Roberto Raggi's avatar
Roberto Raggi committed
663
    m_modelManager = ExtensionSystem::PluginManager::instance()->getObject<ModelManagerInterface>();
664
    m_contextPane = ExtensionSystem::PluginManager::instance()->getObject<QmlJS::IContextPane>();
665 666 667


    if (m_contextPane) {
668
        connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(onCursorPositionChanged()));
669 670
        connect(m_contextPane, SIGNAL(closed()), this, SLOT(showTextMarker()));
    }
Kai Koehne's avatar
Kai Koehne committed
671
    m_oldCursorPosition = -1;
672 673

    if (m_modelManager) {
674
        m_semanticHighlighter->setModelManager(m_modelManager);
675 676
        connect(m_modelManager, SIGNAL(documentUpdated(QmlJS::Document::Ptr)),
                this, SLOT(onDocumentUpdated(QmlJS::Document::Ptr)));
677
        connect(m_modelManager, SIGNAL(libraryInfoUpdated(QString,QmlJS::LibraryInfo)),
678
                this, SLOT(forceSemanticRehighlightIfCurrentEditor()));
679
        connect(this->document(), SIGNAL(modificationChanged(bool)), this, SLOT(modificationChanged(bool)));
680
    }
Christian Kamm's avatar
Christian Kamm committed
681

682 683
    connect(m_semanticHighlighter, SIGNAL(changed(QmlJSEditor::SemanticInfo)),
            this, SLOT(updateSemanticInfo(QmlJSEditor::SemanticInfo)));
684

685 686
    connect(this, SIGNAL(refactorMarkerClicked(TextEditor::RefactorMarker)),
            SLOT(onRefactorMarkerClicked(TextEditor::RefactorMarker)));
687

688
    setRequestMarkEnabled(true);
689 690
}

691
QmlJSTextEditorWidget::~QmlJSTextEditorWidget()
692
{
693
    hideContextPane();
Christian Kamm's avatar
Christian Kamm committed
694 695
    m_semanticHighlighter->abort();
    m_semanticHighlighter->wait();
696 697
}

698
SemanticInfo QmlJSTextEditorWidget::semanticInfo() const
699 700 701 702
{
    return m_semanticInfo;
}

703
int QmlJSTextEditorWidget::editorRevision() const
704 705 706 707
{
    return document()->revision();
}

708
bool QmlJSTextEditorWidget::isOutdated() const
709
{
710
    if (m_semanticInfo.revision() != editorRevision())
711 712 713 714 715
        return true;

    return false;
}

716
QmlOutlineModel *QmlJSTextEditorWidget::outlineModel() const
717 718 719 720
{
    return m_outlineModel;
}

721
QModelIndex QmlJSTextEditorWidget::outlineModelIndex()
722
{
723 724 725 726
    if (!m_outlineModelIndex.isValid()) {
        m_outlineModelIndex = indexForPosition(position());
        emit outlineModelIndexChanged(m_outlineModelIndex);
    }
727 728 729
    return m_outlineModelIndex;
}

730
Core::IEditor *QmlJSEditorEditable::duplicate(QWidget *parent)
731
{
732 733
    QmlJSTextEditorWidget *newEditor = new QmlJSTextEditorWidget(parent);
    newEditor->duplicateFrom(editorWidget());
734
    QmlJSEditorPlugin::instance()->initializeEditor(newEditor);
735
    return newEditor->editor();
736 737
}

738
QString QmlJSEditorEditable::id() const
739
{
740
    return QLatin1String(QmlJSEditor::Constants::C_QMLJSEDITOR_ID);
741 742
}

743
bool QmlJSEditorEditable::open(QString *errorString, const QString &fileName)
744
{
745
    bool b = TextEditor::BaseTextEditor::open(errorString, fileName);
746
    editorWidget()->setMimeType(Core::ICore::instance()->mimeDatabase()->findByFile(QFileInfo(fileName)).type());
747 748 749
    return b;
}

750
void QmlJSTextEditorWidget::updateDocument()
751
{
752
    m_updateDocumentTimer->start(UPDATE_DOCUMENT_DEFAULT_INTERVAL);
753 754
}

755
void QmlJSTextEditorWidget::updateDocumentNow()
756
{
757
    // ### move in the parser thread.
758

759
    m_updateDocumentTimer->stop();
760

761
    const QString fileName = file()->fileName();
762

763
    m_modelManager->updateSourceFiles(QStringList() << fileName, false);
764 765
}

766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801
static void appendExtraSelectionsForMessages(
        QList<QTextEdit::ExtraSelection> *selections,
        const QList<DiagnosticMessage> &messages,
        const QTextDocument *document)
{
    foreach (const DiagnosticMessage &d, messages) {
        const int line = d.loc.startLine;
        const int column = qMax(1U, d.loc.startColumn);

        QTextEdit::ExtraSelection sel;
        QTextCursor c(document->findBlockByNumber(line - 1));
        sel.cursor = c;

        sel.cursor.setPosition(c.position() + column - 1);

        if (d.loc.length == 0) {
            if (sel.cursor.atBlockEnd())
                sel.cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::KeepAnchor);
            else
                sel.cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
        } else {
            sel.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, d.loc.length);
        }

        if (d.isWarning())
            sel.format.setUnderlineColor(Qt::darkYellow);
        else
            sel.format.setUnderlineColor(Qt::red);

        sel.format.setUnderlineStyle(QTextCharFormat::WaveUnderline);
        sel.format.setToolTip(d.message);

        selections->append(sel);
    }
}

802
void QmlJSTextEditorWidget::onDocumentUpdated(QmlJS::Document::Ptr doc)
803
{
804
    if (file()->fileName() != doc->fileName()
805
            || doc->editorRevision() != document()->revision()) {
806 807
        return;
    }
808

809 810 811
    if (doc->ast()) {
        // got a correctly parsed (or recovered) file.

812
        const SemanticHighlighterSource source = currentSource(/*force = */ true);
Christian Kamm's avatar
Christian Kamm committed
813
        m_semanticHighlighter->rehighlight(source);
814
    } else {
815
        // show parsing errors
816 817 818
        QList<QTextEdit::ExtraSelection> selections;
        appendExtraSelectionsForMessages(&selections, doc->diagnosticMessages(), document());
        setExtraSelections(CodeWarningsSelection, selections);
819
    }
820 821
}

822
void QmlJSTextEditorWidget::modificationChanged(bool changed)
823 824 825 826 827
{
    if (!changed && m_modelManager)
        m_modelManager->fileChangedOnDisk(file()->fileName());
}

828
void QmlJSTextEditorWidget::jumpToOutlineElement(int /*index*/)
829
{
830
    QModelIndex index = m_outlineCombo->view()->currentIndex();
831 832 833 834
    AST::SourceLocation location = m_outlineModel->sourceLocation(index);

    if (!location.isValid())
        return;
835

836 837 838 839
    Core::EditorManager *editorManager = Core::EditorManager::instance();
    editorManager->cutForwardNavigationHistory();
    editorManager->addCurrentPositionToNavigationHistory();

840 841 842 843 844
    QTextCursor cursor = textCursor();
    cursor.setPosition(location.offset);
    setTextCursor(cursor);

    setFocus();
845 846
}

847
void QmlJSTextEditorWidget::updateOutlineNow()
848
{
849
    if (!m_semanticInfo.document)
850 851
        return;

852
    if (m_semanticInfo.document->editorRevision() != editorRevision()) {
853 854
        m_updateOutlineTimer->start();
        return;
855
    }
856

857
    m_outlineModel->update(m_semanticInfo);
858 859 860 861

    QTreeView *treeView = static_cast<QTreeView*>(m_outlineCombo->view());
    treeView->expandAll();

862
    updateOutlineIndexNow();
863 864
}

865
void QmlJSTextEditorWidget::updateOutlineIndexNow()
866
{
867
    if (m_updateOutlineTimer->isActive())
Kai Koehne's avatar
Kai Koehne committed
868
        return; // updateOutlineNow will call this function soon anyway
869

870
    if (!m_outlineModel->document())
871
        return;
872

873
    if (m_outlineModel->document()->editorRevision() != editorRevision()) {
874
        m_updateOutlineIndexTimer->start();
875 876
        return;
    }
877

878 879
    m_outlineModelIndex = QModelIndex(); // invalidate
    QModelIndex comboIndex = outlineModelIndex();
880

881
    if (comboIndex.isValid()) {
882
        bool blocked = m_outlineCombo->blockSignals(true);
883

884
        // There is no direct way to select a non-root item
885 886 887
        m_outlineCombo->setRootModelIndex(comboIndex.parent());
        m_outlineCombo->setCurrentIndex(comboIndex.row());
        m_outlineCombo->setRootModelIndex(QModelIndex());
888

889
        m_outlineCombo->blockSignals(blocked);
890
    }
891 892
}

893
static UiQualifiedId *qualifiedTypeNameId(Node *m)
894 895 896 897 898 899 900 901
{
    if (UiObjectDefinition *def = cast<UiObjectDefinition *>(m))
        return def->qualifiedTypeNameId;
    else if (UiObjectBinding *binding = cast<UiObjectBinding *>(m))
        return binding->qualifiedTypeNameId;
    return 0;
}

902
void QmlJSTextEditorWidget::updateCursorPositionNow()
903
{
904 905
    if (m_contextPane && document() && semanticInfo().isValid()
            && document()->revision() == semanticInfo().document->editorRevision())
906
    {
907 908
        Node *oldNode = m_semanticInfo.declaringMemberNoProperties(m_oldCursorPosition);
        Node *newNode = m_semanticInfo.declaringMemberNoProperties(position());
Thomas Hartmann's avatar
Thomas Hartmann committed
909
        if (oldNode != newNode && m_oldCursorPosition != -1)
910 911
            m_contextPane->apply(editor(), semanticInfo().document, LookupContext::Ptr(),newNode, false);
        if (m_contextPane->isAvailable(editor(), semanticInfo().document, newNode) &&
912
            !m_contextPane->widget()->isVisible()) {
913
            QList<TextEditor::RefactorMarker> markers;
914
            if (UiObjectMember *m = newNode->uiObjectMemberCast()) {
915
                const int start = qualifiedTypeNameId(m)->identifierToken.begin();
916 917 918
                for (UiQualifiedId *q = qualifiedTypeNameId(m); q; q = q->next) {
                    if (! q->next) {
                        const int end = q->identifierToken.end();
919
                        if (position() >= start && position() <= end) {
Friedemann Kleint's avatar