qmljseditor.cpp 51.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
#include "qmloutlinemodel.h"
39
#include "qmljsfindreferences.h"
40
#include "qmljssemanticinfoupdater.h"
41
#include "qmljsautocompleter.h"
Leandro Melo's avatar
Leandro Melo committed
42 43
#include "qmljscompletionassist.h"
#include "qmljsquickfixassist.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/qmljsmodelmanagerinterface.h>
Christian Kamm's avatar
Christian Kamm committed
50
#include <qmljs/qmljsscopebuilder.h>
51 52 53
#include <qmljs/parser/qmljsastvisitor_p.h>
#include <qmljs/parser/qmljsast_p.h>
#include <qmljs/parser/qmljsengine_p.h>
54

55
#include <qmljstools/qmljsindenter.h>
56 57
#include <qmljstools/qmljsqtstylecodeformatter.h>

58
#include <coreplugin/actionmanager/actionmanager.h>
59
#include <coreplugin/actionmanager/actioncontainer.h>
60
#include <coreplugin/uniqueidmanager.h>
61
#include <coreplugin/actionmanager/command.h>
62
#include <coreplugin/editormanager/editormanager.h>
63 64
#include <coreplugin/icore.h>
#include <coreplugin/mimedatabase.h>
65
#include <extensionsystem/pluginmanager.h>
66 67
#include <texteditor/basetextdocument.h>
#include <texteditor/fontsettings.h>
68
#include <texteditor/tabsettings.h>
69 70
#include <texteditor/texteditorconstants.h>
#include <texteditor/texteditorsettings.h>
71
#include <texteditor/syntaxhighlighter.h>
72
#include <texteditor/refactoroverlay.h>
73
#include <texteditor/tooltip/tooltip.h>
Leandro Melo's avatar
Leandro Melo committed
74 75
#include <texteditor/codeassist/genericproposal.h>
#include <texteditor/codeassist/basicproposalitemlistmodel.h>
76
#include <qmldesigner/qmldesignerconstants.h>
77
#include <projectexplorer/projectexplorerconstants.h>
78
#include <utils/changeset.h>
79
#include <utils/uncommentselection.h>
80
#include <utils/qtcassert.h>
81

82
#include <QtCore/QFileInfo>
83
#include <QtCore/QSignalMapper>
84
#include <QtCore/QTimer>
Leandro Melo's avatar
Leandro Melo committed
85
#include <QtCore/QScopedPointer>
86 87 88

#include <QtGui/QMenu>
#include <QtGui/QComboBox>
89
#include <QtGui/QHeaderView>
Roberto Raggi's avatar
Roberto Raggi committed
90
#include <QtGui/QInputDialog>
91
#include <QtGui/QMainWindow>
con's avatar
con committed
92
#include <QtGui/QToolBar>
93
#include <QtGui/QTreeView>
94 95

enum {
96
    UPDATE_DOCUMENT_DEFAULT_INTERVAL = 100,
97
    UPDATE_USES_DEFAULT_INTERVAL = 150,
98
    UPDATE_OUTLINE_INTERVAL = 500 // msecs after new semantic info has been arrived / cursor has moved
99 100
};

101 102
using namespace QmlJS;
using namespace QmlJS::AST;
103
using namespace QmlJSEditor;
104
using namespace QmlJSEditor::Internal;
105

106 107
namespace {

Roberto Raggi's avatar
Roberto Raggi committed
108
class FindIdDeclarations: protected Visitor
109 110
{
public:
111
    typedef QHash<QString, QList<AST::SourceLocation> > Result;
112

113
    Result operator()(Document::Ptr doc)
114 115 116
    {
        _ids.clear();
        _maybeIds.clear();
117 118
        if (doc && doc->qmlProgram())
            doc->qmlProgram()->accept(this);
119 120
        return _ids;
    }
121 122

protected:
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 174 175 176 177 178
    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;
    }
179 180

private:
181 182
    Result _ids;
    Result _maybeIds;
183 184
};

185 186
class FindDeclarations: protected Visitor
{
187 188
    QList<Declaration> _declarations;
    int _depth;
189 190

public:
191 192 193 194 195 196 197
    QList<Declaration> operator()(AST::Node *node)
    {
        _depth = -1;
        _declarations.clear();
        accept(node);
        return _declarations;
    }
198 199

protected:
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
    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('.');
        }
215

216 217
        return text;
    }
Roberto Raggi's avatar
Roberto Raggi committed
218

219 220 221 222 223 224 225 226 227 228 229 230
    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
231

232 233 234 235 236 237 238 239 240 241
    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;
    }

242 243 244
    virtual bool visit(AST::UiObjectDefinition *node)
    {
        ++_depth;
Roberto Raggi's avatar
Roberto Raggi committed
245

246 247
        Declaration decl;
        init(&decl, node);
Roberto Raggi's avatar
Roberto Raggi committed
248

249 250 251 252 253
        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
254

255
        _declarations.append(decl);
Roberto Raggi's avatar
Roberto Raggi committed
256

257 258
        return true; // search for more bindings
    }
Roberto Raggi's avatar
Roberto Raggi committed
259

260 261 262 263
    virtual void endVisit(AST::UiObjectDefinition *)
    {
        --_depth;
    }
Roberto Raggi's avatar
Roberto Raggi committed
264

265 266 267
    virtual bool visit(AST::UiObjectBinding *node)
    {
        ++_depth;
Roberto Raggi's avatar
Roberto Raggi committed
268

269 270
        Declaration decl;
        init(&decl, node);
Roberto Raggi's avatar
Roberto Raggi committed
271

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

274 275
        decl.text.append(asString(node->qualifiedId));
        decl.text.append(QLatin1String(": "));
Roberto Raggi's avatar
Roberto Raggi committed
276

277 278 279 280
        if (node->qualifiedTypeNameId)
            decl.text.append(asString(node->qualifiedTypeNameId));
        else
            decl.text.append(QLatin1Char('?'));
Roberto Raggi's avatar
Roberto Raggi committed
281

282
        _declarations.append(decl);
Roberto Raggi's avatar
Roberto Raggi committed
283

284 285
        return true; // search for more bindings
    }
Roberto Raggi's avatar
Roberto Raggi committed
286

287 288 289 290
    virtual void endVisit(AST::UiObjectBinding *)
    {
        --_depth;
    }
Roberto Raggi's avatar
Roberto Raggi committed
291

292
    virtual bool visit(AST::UiScriptBinding *)
293 294
    {
        ++_depth;
Roberto Raggi's avatar
Roberto Raggi committed
295

296
#if 0 // ### ignore script bindings for now.
297 298
        Declaration decl;
        init(&decl, node);
Roberto Raggi's avatar
Roberto Raggi committed
299

300 301
        decl.text.fill(QLatin1Char(' '), _depth);
        decl.text.append(asString(node->qualifiedId));
Roberto Raggi's avatar
Roberto Raggi committed
302

303
        _declarations.append(decl);
304
#endif
Roberto Raggi's avatar
Roberto Raggi committed
305

306 307
        return false; // more more bindings in this subtree.
    }
Roberto Raggi's avatar
Roberto Raggi committed
308

309 310 311 312
    virtual void endVisit(AST::UiScriptBinding *)
    {
        --_depth;
    }
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 360 361 362 363 364

    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;
    }
365 366
};

367 368 369 370 371 372 373 374 375 376
class CreateRanges: protected AST::Visitor
{
    QTextDocument *_textDocument;
    QList<Range> _ranges;

public:
    QList<Range> operator()(QTextDocument *textDocument, Document::Ptr doc)
    {
        _textDocument = textDocument;
        _ranges.clear();
377 378
        if (doc && doc->ast() != 0)
            doc->ast()->accept(this);
379 380 381 382 383 384 385 386
        return _ranges;
    }

protected:
    using AST::Visitor::visit;

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

    virtual bool visit(AST::UiObjectDefinition *ast)
    {
394
        if (ast->initializer && ast->initializer->lbraceToken.length)
395
            _ranges.append(createRange(ast, ast->initializer));
396 397 398
        return true;
    }

399 400 401 402 403 404 405 406 407 408 409 410
    virtual bool visit(AST::FunctionExpression *ast)
    {
        _ranges.append(createRange(ast));
        return true;
    }

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

411 412 413 414 415 416 417 418
    virtual bool visit(AST::UiScriptBinding *ast)
    {
        if (AST::Block *block = AST::cast<AST::Block *>(ast->statement)) {
            _ranges.append(createRange(ast, block));
        }
        return true;
    }

419
    Range createRange(AST::UiObjectMember *member, AST::UiObjectInitializer *ast)
420
    {
421 422
        return createRange(member, member->firstSourceLocation(), ast->rbraceToken);
    }
423

424 425 426 427
    Range createRange(AST::FunctionExpression *ast)
    {
        return createRange(ast, ast->lbraceToken, ast->rbraceToken);
    }
428

429 430 431
    Range createRange(AST::UiScriptBinding *ast, AST::Block *block)
    {
        return createRange(ast, block->lbraceToken, block->rbraceToken);
432
    }
433

434
    Range createRange(AST::Node *ast, AST::SourceLocation start, AST::SourceLocation end)
435 436 437 438 439 440
    {
        Range range;

        range.ast = ast;

        range.begin = QTextCursor(_textDocument);
441
        range.begin.setPosition(start.begin());
442 443

        range.end = QTextCursor(_textDocument);
444
        range.end.setPosition(end.end());
445

446 447 448
        return range;
    }

449 450
};

451 452 453 454
// ### does not necessarily give the full AST path!
// intentionally does not contain lists like
// UiImportList, SourceElements, UiObjectMemberList
class AstPath: protected AST::Visitor
455
{
456 457 458
    QList<AST::Node *> _path;
    unsigned _offset;

459
public:
460 461 462 463 464 465 466 467 468 469
    QList<AST::Node *> operator()(AST::Node *node, unsigned offset)
    {
        _offset = offset;
        _path.clear();
        accept(node);
        return _path;
    }

protected:
    using AST::Visitor::visit;
470 471 472 473 474 475 476

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

477 478 479 480
    bool containsOffset(AST::SourceLocation start, AST::SourceLocation end)
    {
        return _offset >= start.begin() && _offset <= end.end();
    }
481

482 483 484
    bool handle(AST::Node *ast,
                AST::SourceLocation start, AST::SourceLocation end,
                bool addToPath = true)
485
    {
486 487 488 489 490
        if (containsOffset(start, end)) {
            if (addToPath)
                _path.append(ast);
            return true;
        }
491 492 493
        return false;
    }

494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512
    template <class T>
    bool handleLocationAst(T *ast, bool addToPath = true)
    {
        return handle(ast, ast->firstSourceLocation(), ast->lastSourceLocation(), addToPath);
    }

    virtual bool preVisit(AST::Node *node)
    {
        if (Statement *stmt = node->statementCast()) {
            return handleLocationAst(stmt);
        } else if (ExpressionNode *exp = node->expressionCast()) {
            return handleLocationAst(exp);
        } else if (UiObjectMember *ui = node->uiObjectMemberCast()) {
            return handleLocationAst(ui);
        }
        return true;
    }

    virtual bool visit(AST::UiQualifiedId *ast)
513
    {
514 515 516 517 518 519
        AST::SourceLocation first = ast->identifierToken;
        AST::SourceLocation last;
        for (AST::UiQualifiedId *it = ast; it; it = it->next)
            last = it->identifierToken;
        if (containsOffset(first, last))
            _path.append(ast);
520 521 522
        return false;
    }

523
    virtual bool visit(AST::UiProgram *ast)
524
    {
525
        _path.append(ast);
526 527
        return true;
    }
528 529 530 531 532 533 534 535 536 537 538 539

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

    virtual bool visit(AST::UiImport *ast)
    {
        return handleLocationAst(ast);
    }

540 541
};

542 543 544
} // end of anonymous namespace


545
AST::Node *SemanticInfo::rangeAt(int cursorPosition) const
546
{
547
    AST::Node *declaringMember = 0;
548 549 550 551 552 553 554 555 556 557 558 559 560 561 562

    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;
}

563
// ### the name and behavior of this function is dubious
564 565
QmlJS::AST::Node *SemanticInfo::declaringMemberNoProperties(int cursorPosition) const
{
566
   AST::Node *node = rangeAt(cursorPosition);
567 568 569 570

   if (UiObjectDefinition *objectDefinition = cast<UiObjectDefinition*>(node)) {
       QString name = objectDefinition->qualifiedTypeNameId->name->asString();
       if (!name.isNull() && name.at(0).isLower()) {
571
           QList<AST::Node *> path = rangePath(cursorPosition);
572 573 574
           if (path.size() > 1)
               return path.at(path.size() - 2);
       } else if (name.contains("GradientStop")) {
575
           QList<AST::Node *> path = rangePath(cursorPosition);
576 577 578 579 580 581
           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")) {
582
           QList<AST::Node *> path = rangePath(cursorPosition);
583 584 585 586 587 588 589 590
           if (path.size() > 1)
               return path.at(path.size() - 2);
       }
   }

   return node;
}

591
QList<AST::Node *> SemanticInfo::rangePath(int cursorPosition) const
592 593 594 595 596 597 598 599 600 601 602 603 604 605
{
    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;
}

606
ScopeChain SemanticInfo::scopeChain(const QList<QmlJS::AST::Node *> &path) const
607
{
Christian Kamm's avatar
Christian Kamm committed
608
    Q_ASSERT(m_rootScopeChain);
609

Christian Kamm's avatar
Christian Kamm committed
610 611
    if (path.isEmpty())
        return *m_rootScopeChain;
612

613
    ScopeChain scope = *m_rootScopeChain;
Christian Kamm's avatar
Christian Kamm committed
614 615 616
    ScopeBuilder builder(&scope);
    builder.push(path);
    return scope;
617 618
}

619
QList<AST::Node *> SemanticInfo::astPath(int pos) const
620
{
621 622 623 624 625 626
    QList<AST::Node *> result;
    if (! document)
        return result;

    AstPath astPath;
    return astPath(document->ast(), pos);
627 628
}

629
AST::Node *SemanticInfo::astNodeAt(int pos) const
630
{
631 632
    const QList<AST::Node *> path = astPath(pos);
    if (path.isEmpty())
633
        return 0;
634
    return path.last();
635 636
}

637 638
bool SemanticInfo::isValid() const
{
Christian Kamm's avatar
Christian Kamm committed
639
    if (document && context && m_rootScopeChain)
640 641 642 643 644
        return true;

    return false;
}

645 646 647
int SemanticInfo::revision() const
{
    if (document)
648
        return document->editorRevision();
649 650 651 652

    return 0;
}

653 654
QmlJSTextEditorWidget::QmlJSTextEditorWidget(QWidget *parent) :
    TextEditor::BaseTextEditorWidget(parent),
655
    m_outlineCombo(0),
656
    m_outlineModel(new QmlOutlineModel(this)),
657
    m_modelManager(0),
658
    m_contextPane(0),
659
    m_updateSelectedElements(false),
660
    m_findReferences(new FindReferences(this))
661
{
662
    qRegisterMetaType<QmlJSEditor::SemanticInfo>("QmlJSEditor::SemanticInfo");
Christian Kamm's avatar
Christian Kamm committed
663

664 665
    m_semanticInfoUpdater = new SemanticInfoUpdater(this);
    m_semanticInfoUpdater->start();
Christian Kamm's avatar
Christian Kamm committed
666

667 668 669
    setParenthesesMatchingEnabled(true);
    setMarksVisible(true);
    setCodeFoldingSupported(true);
670
    setIndenter(new Indenter);
671
    setAutoCompleter(new AutoCompleter);
672

673 674 675 676
    m_updateDocumentTimer = new QTimer(this);
    m_updateDocumentTimer->setInterval(UPDATE_DOCUMENT_DEFAULT_INTERVAL);
    m_updateDocumentTimer->setSingleShot(true);
    connect(m_updateDocumentTimer, SIGNAL(timeout()), this, SLOT(updateDocumentNow()));
677

678 679 680 681 682
    m_updateUsesTimer = new QTimer(this);
    m_updateUsesTimer->setInterval(UPDATE_USES_DEFAULT_INTERVAL);
    m_updateUsesTimer->setSingleShot(true);
    connect(m_updateUsesTimer, SIGNAL(timeout()), this, SLOT(updateUsesNow()));

683 684 685 686
    m_localReparseTimer = new QTimer(this);
    m_localReparseTimer->setInterval(UPDATE_DOCUMENT_DEFAULT_INTERVAL);
    m_localReparseTimer->setSingleShot(true);
    connect(m_localReparseTimer, SIGNAL(timeout()), this, SLOT(forceReparseIfCurrentEditor()));
687

688
    connect(this, SIGNAL(textChanged()), this, SLOT(updateDocument()));
689

690
    connect(this, SIGNAL(textChanged()), this, SLOT(updateUses()));
691
    connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(updateUses()));
692

693 694 695 696 697
    m_updateOutlineTimer = new QTimer(this);
    m_updateOutlineTimer->setInterval(UPDATE_OUTLINE_INTERVAL);
    m_updateOutlineTimer->setSingleShot(true);
    connect(m_updateOutlineTimer, SIGNAL(timeout()), this, SLOT(updateOutlineNow()));

698 699 700 701
    m_updateOutlineIndexTimer = new QTimer(this);
    m_updateOutlineIndexTimer->setInterval(UPDATE_OUTLINE_INTERVAL);
    m_updateOutlineIndexTimer->setSingleShot(true);
    connect(m_updateOutlineIndexTimer, SIGNAL(timeout()), this, SLOT(updateOutlineIndexNow()));
702

Lasse Holmstedt's avatar
Lasse Holmstedt committed
703
    m_cursorPositionTimer  = new QTimer(this);
704
    m_cursorPositionTimer->setInterval(UPDATE_OUTLINE_INTERVAL);
Lasse Holmstedt's avatar
Lasse Holmstedt committed
705 706
    m_cursorPositionTimer->setSingleShot(true);
    connect(m_cursorPositionTimer, SIGNAL(timeout()), this, SLOT(updateCursorPositionNow()));
707

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

Roberto Raggi's avatar
Roberto Raggi committed
711
    m_modelManager = ExtensionSystem::PluginManager::instance()->getObject<ModelManagerInterface>();
712
    m_contextPane = ExtensionSystem::PluginManager::instance()->getObject<QmlJS::IContextPane>();
713 714 715


    if (m_contextPane) {
716
        connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(onCursorPositionChanged()));
717 718
        connect(m_contextPane, SIGNAL(closed()), this, SLOT(showTextMarker()));
    }
Kai Koehne's avatar
Kai Koehne committed
719
    m_oldCursorPosition = -1;
720 721

    if (m_modelManager) {
722
        m_semanticInfoUpdater->setModelManager(m_modelManager);
723 724
        connect(m_modelManager, SIGNAL(documentUpdated(QmlJS::Document::Ptr)),
                this, SLOT(onDocumentUpdated(QmlJS::Document::Ptr)));
725
        connect(m_modelManager, SIGNAL(libraryInfoUpdated(QString,QmlJS::LibraryInfo)),
726
                this, SLOT(forceReparseIfCurrentEditor()));
727
        connect(this->document(), SIGNAL(modificationChanged(bool)), this, SLOT(modificationChanged(bool)));
728
    }
Christian Kamm's avatar
Christian Kamm committed
729

730
    connect(m_semanticInfoUpdater, SIGNAL(updated(QmlJSEditor::SemanticInfo)),
731
            this, SLOT(updateSemanticInfo(QmlJSEditor::SemanticInfo)));
732

733 734
    connect(this, SIGNAL(refactorMarkerClicked(TextEditor::RefactorMarker)),
            SLOT(onRefactorMarkerClicked(TextEditor::RefactorMarker)));
735

736
    setRequestMarkEnabled(true);
737 738
}

739
QmlJSTextEditorWidget::~QmlJSTextEditorWidget()
740
{
741
    hideContextPane();
742 743
    m_semanticInfoUpdater->abort();
    m_semanticInfoUpdater->wait();
744 745
}

746
SemanticInfo QmlJSTextEditorWidget::semanticInfo() const
747 748 749 750
{
    return m_semanticInfo;
}

751
int QmlJSTextEditorWidget::editorRevision() const
752 753 754 755
{
    return document()->revision();
}

756
bool QmlJSTextEditorWidget::isOutdated() const
757
{
758
    if (m_semanticInfo.revision() != editorRevision())
759 760 761 762 763
        return true;

    return false;
}

764
QmlOutlineModel *QmlJSTextEditorWidget::outlineModel() const
765 766 767 768
{
    return m_outlineModel;
}

769
QModelIndex QmlJSTextEditorWidget::outlineModelIndex()
770
{
771 772 773 774
    if (!m_outlineModelIndex.isValid()) {
        m_outlineModelIndex = indexForPosition(position());
        emit outlineModelIndexChanged(m_outlineModelIndex);
    }
775 776 777
    return m_outlineModelIndex;
}

778
Core::IEditor *QmlJSEditorEditable::duplicate(QWidget *parent)
779
{
780 781
    QmlJSTextEditorWidget *newEditor = new QmlJSTextEditorWidget(parent);
    newEditor->duplicateFrom(editorWidget());
782
    QmlJSEditorPlugin::instance()->initializeEditor(newEditor);
783
    return newEditor->editor();
784 785
}

786
QString QmlJSEditorEditable::id() const
787
{
788
    return QLatin1String(QmlJSEditor::Constants::C_QMLJSEDITOR_ID);
789 790
}

791
bool QmlJSEditorEditable::open(QString *errorString, const QString &fileName, const QString &realFileName)
792
{
793
    bool b = TextEditor::BaseTextEditor::open(errorString, fileName, realFileName);
794
    editorWidget()->setMimeType(Core::ICore::instance()->mimeDatabase()->findByFile(QFileInfo(fileName)).type());
795 796 797
    return b;
}

798
void QmlJSTextEditorWidget::updateDocument()
799
{
800
    m_updateDocumentTimer->start(UPDATE_DOCUMENT_DEFAULT_INTERVAL);
801 802
}

803
void QmlJSTextEditorWidget::updateDocumentNow()
804
{
805
    // ### move in the parser thread.
806

807
    m_updateDocumentTimer->stop();
808

809
    const QString fileName = file()->fileName();
810

811
    m_modelManager->updateSourceFiles(QStringList() << fileName, false);
812 813
}

814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849
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);
    }
}

850
void QmlJSTextEditorWidget::onDocumentUpdated(QmlJS::Document::Ptr doc)
851
{
852
    if (file()->fileName() != doc->fileName()
853
            || doc->editorRevision() != document()->revision()) {
854 855
        // maybe a dependency changed: schedule a potential rehighlight
        // will not rehighlight if the current editor changes away from this file
856
        m_localReparseTimer->start();
857 858
        return;
    }
859

860 861 862
    if (doc->ast()) {
        // got a correctly parsed (or recovered) file.

863 864
        const SemanticInfoUpdaterSource source = currentSource(/*force = */ true);
        m_semanticInfoUpdater->update(source);
865
    } else {
866
        // show parsing errors
867 868 869
        QList<QTextEdit::ExtraSelection> selections;
        appendExtraSelectionsForMessages(&selections, doc->diagnosticMessages(), document());
        setExtraSelections(CodeWarningsSelection, selections);
870
    }
871 872
}

873
void QmlJSTextEditorWidget::modificationChanged(bool changed)
874 875 876 877 878
{
    if (!changed && m_modelManager)
        m_modelManager->fileChangedOnDisk(file()->fileName());
}

879
void QmlJSTextEditorWidget::jumpToOutlineElement(int /*index*/)
880
{
881
    QModelIndex index = m_outlineCombo->view()->currentIndex();
882 883 884 885
    AST::SourceLocation location = m_outlineModel->sourceLocation(index);

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

887 888 889 890
    Core::EditorManager *editorManager = Core::EditorManager::instance();
    editorManager->cutForwardNavigationHistory();
    editorManager->addCurrentPositionToNavigationHistory();

891 892 893 894 895
    QTextCursor cursor = textCursor();
    cursor.setPosition(location.offset);
    setTextCursor(cursor);

    setFocus();
896 897
}

898
void QmlJSTextEditorWidget::updateOutlineNow()
899
{
900
    if (!m_semanticInfo.document)
901 902
        return;

903
    if (m_semanticInfo.document->editorRevision() != editorRevision()) {
904 905
        m_updateOutlineTimer->start();
        return;
906
    }
907

908
    m_outlineModel->update(m_semanticInfo);
909 910 911 912

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

913
    updateOutlineIndexNow();
914 915
}

916
void QmlJSTextEditorWidget::updateOutlineIndexNow()
917
{
918
    if (m_updateOutlineTimer->isActive())
Kai Koehne's avatar
Kai Koehne committed
919
        return; // updateOutlineNow will call this function soon anyway
920

921
    if (!m_outlineModel->document())