qmljseditor.cpp 31.9 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
7
**
hjk's avatar
hjk committed
8 9 10 11 12
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
Eike Ziller's avatar
Eike Ziller committed
13 14
** conditions see http://www.qt.io/licensing.  For further information
** use the contact form at http://www.qt.io/contact-us.
15 16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18 19 20 21 22 23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24 25 26
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
27 28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
30

31
#include "qmljseditor.h"
32

33
#include "qmljsautocompleter.h"
34
#include "qmljscompletionassist.h"
35
#include "qmljseditorconstants.h"
36
#include "qmljseditordocument.h"
37
#include "qmljseditorplugin.h"
38
#include "qmljsfindreferences.h"
39
#include "qmljshoverhandler.h"
Leandro Melo's avatar
Leandro Melo committed
40
#include "qmljsquickfixassist.h"
41
#include "qmloutlinemodel.h"
42

43
#include <qmljs/qmljsbind.h>
44
#include <qmljs/qmljsevaluate.h>
45
#include <qmljs/qmljsicontextpane.h>
46
#include <qmljs/qmljsmodelmanagerinterface.h>
47
#include <qmljs/qmljsutils.h>
48

49
#include <qmljstools/qmljstoolsconstants.h>
50
#include <projectexplorer/projectexplorerconstants.h>
51

52
#include <coreplugin/actionmanager/actioncontainer.h>
53
#include <coreplugin/actionmanager/actionmanager.h>
54
#include <coreplugin/actionmanager/command.h>
55 56
#include <coreplugin/coreconstants.h>
#include <coreplugin/designmode.h>
57
#include <coreplugin/editormanager/editormanager.h>
58
#include <coreplugin/icore.h>
59
#include <coreplugin/id.h>
60
#include <coreplugin/infobar.h>
61 62
#include <coreplugin/modemanager.h>

63
#include <extensionsystem/pluginmanager.h>
64

65
#include <texteditor/textdocument.h>
66
#include <texteditor/fontsettings.h>
67
#include <texteditor/tabsettings.h>
68
#include <texteditor/texteditorconstants.h>
69
#include <texteditor/syntaxhighlighter.h>
70
#include <texteditor/refactoroverlay.h>
Leandro Melo's avatar
Leandro Melo committed
71
#include <texteditor/codeassist/genericproposal.h>
72
#include <texteditor/codeassist/genericproposalmodel.h>
73 74
#include <texteditor/texteditoractionhandler.h>

75
#include <utils/changeset.h>
76
#include <utils/uncommentselection.h>
77
#include <utils/qtcassert.h>
78
#include <utils/annotateditemdelegate.h>
79

80 81
#include <QComboBox>
#include <QCoreApplication>
82
#include <QFileInfo>
83 84
#include <QHeaderView>
#include <QMenu>
85
#include <QPointer>
86
#include <QScopedPointer>
87
#include <QSignalMapper>
88
#include <QTextCodec>
89
#include <QTimer>
90
#include <QTreeView>
91 92

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

hjk's avatar
hjk committed
97
using namespace Core;
98 99
using namespace QmlJS;
using namespace QmlJS::AST;
100
using namespace QmlJSTools;
101
using namespace TextEditor;
102

103
namespace QmlJSEditor {
104
namespace Internal {
105

106 107 108 109
//
// QmlJSEditorWidget
//

110
QmlJSEditorWidget::QmlJSEditorWidget()
111
{
112 113
    m_outlineCombo = 0;
    m_contextPane = 0;
114
    m_firstSementicInfo = true;
115 116
    m_findReferences = new FindReferences(this);

117
    setLanguageSettingsId(QmlJSTools::Constants::QML_JS_SETTINGS_ID);
118 119 120 121 122
}

void QmlJSEditorWidget::finalizeInitialization()
{
    m_qmlJsEditorDocument = static_cast<QmlJSEditorDocument *>(textDocument());
123

hjk's avatar
hjk committed
124 125 126 127 128
    m_updateUsesTimer.setInterval(UPDATE_USES_DEFAULT_INTERVAL);
    m_updateUsesTimer.setSingleShot(true);
    connect(&m_updateUsesTimer, &QTimer::timeout, this, &QmlJSEditorWidget::updateUses);
    connect(this, &QPlainTextEdit::cursorPositionChanged,
            &m_updateUsesTimer, static_cast<void (QTimer::*)()>(&QTimer::start));
129

hjk's avatar
hjk committed
130 131 132 133
    m_updateOutlineIndexTimer.setInterval(UPDATE_OUTLINE_INTERVAL);
    m_updateOutlineIndexTimer.setSingleShot(true);
    connect(&m_updateOutlineIndexTimer, &QTimer::timeout,
            this, &QmlJSEditorWidget::updateOutlineIndexNow);
134

135
    textDocument()->setCodec(QTextCodec::codecForName("UTF-8")); // qml files are defined to be utf-8
136

137
    m_modelManager = QmlJS::ModelManagerInterface::instance();
138
    m_contextPane = ExtensionSystem::PluginManager::getObject<QmlJS::IContextPane>();
139

140 141
    m_modelManager->activateScan();

hjk's avatar
hjk committed
142 143 144
    m_contextPaneTimer.setInterval(UPDATE_OUTLINE_INTERVAL);
    m_contextPaneTimer.setSingleShot(true);
    connect(&m_contextPaneTimer, &QTimer::timeout, this, &QmlJSEditorWidget::updateContextPane);
145
    if (m_contextPane) {
hjk's avatar
hjk committed
146 147 148
        connect(this, &QmlJSEditorWidget::cursorPositionChanged,
                &m_contextPaneTimer, static_cast<void (QTimer::*)()>(&QTimer::start));
        connect(m_contextPane, &IContextPane::closed, this, &QmlJSEditorWidget::showTextMarker);
149
    }
Kai Koehne's avatar
Kai Koehne committed
150
    m_oldCursorPosition = -1;
151

hjk's avatar
hjk committed
152 153
    connect(this->document(), &QTextDocument::modificationChanged,
            this, &QmlJSEditorWidget::modificationChanged);
Christian Kamm's avatar
Christian Kamm committed
154

155 156
    connect(m_qmlJsEditorDocument, SIGNAL(updateCodeWarnings(QmlJS::Document::Ptr)),
            this, SLOT(updateCodeWarnings(QmlJS::Document::Ptr)));
hjk's avatar
hjk committed
157

158 159
    connect(m_qmlJsEditorDocument, SIGNAL(semanticInfoUpdated(QmlJSTools::SemanticInfo)),
            this, SLOT(semanticInfoUpdated(QmlJSTools::SemanticInfo)));
160

161
    setRequestMarkEnabled(true);
162
    createToolBar();
163 164
}

165
QModelIndex QmlJSEditorWidget::outlineModelIndex()
166
{
167 168 169 170
    if (!m_outlineModelIndex.isValid()) {
        m_outlineModelIndex = indexForPosition(position());
        emit outlineModelIndexChanged(m_outlineModelIndex);
    }
171 172 173
    return m_outlineModelIndex;
}

174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
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);
    }
}

210
void QmlJSEditorWidget::updateCodeWarnings(QmlJS::Document::Ptr doc)
211
{
212
    if (doc->ast()) {
213
        setExtraSelections(CodeWarningsSelection, QList<QTextEdit::ExtraSelection>());
214
    } else if (doc->language().isFullySupportedLanguage()) {
215
        // show parsing errors
216 217 218
        QList<QTextEdit::ExtraSelection> selections;
        appendExtraSelectionsForMessages(&selections, doc->diagnosticMessages(), document());
        setExtraSelections(CodeWarningsSelection, selections);
219 220
    } else {
        setExtraSelections(CodeWarningsSelection, QList<QTextEdit::ExtraSelection>());
221
    }
222 223
}

224
void QmlJSEditorWidget::modificationChanged(bool changed)
225 226
{
    if (!changed && m_modelManager)
227
        m_modelManager->fileChangedOnDisk(textDocument()->filePath());
228 229
}

230
void QmlJSEditorWidget::jumpToOutlineElement(int /*index*/)
231
{
232
    QModelIndex index = m_outlineCombo->view()->currentIndex();
233
    AST::SourceLocation location = m_qmlJsEditorDocument->outlineModel()->sourceLocation(index);
234 235 236

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

hjk's avatar
hjk committed
238 239
    EditorManager::cutForwardNavigationHistory();
    EditorManager::addCurrentPositionToNavigationHistory();
240

241 242 243 244 245
    QTextCursor cursor = textCursor();
    cursor.setPosition(location.offset);
    setTextCursor(cursor);

    setFocus();
246 247
}

248
void QmlJSEditorWidget::updateOutlineIndexNow()
249
{
250
    if (!m_qmlJsEditorDocument->outlineModel()->document())
251
        return;
252

253
    if (m_qmlJsEditorDocument->outlineModel()->document()->editorRevision() != document()->revision()) {
hjk's avatar
hjk committed
254
        m_updateOutlineIndexTimer.start();
255 256
        return;
    }
257

258 259
    m_outlineModelIndex = QModelIndex(); // invalidate
    QModelIndex comboIndex = outlineModelIndex();
260

261
    if (comboIndex.isValid()) {
262
        bool blocked = m_outlineCombo->blockSignals(true);
263

264
        // There is no direct way to select a non-root item
265 266 267
        m_outlineCombo->setRootModelIndex(comboIndex.parent());
        m_outlineCombo->setCurrentIndex(comboIndex.row());
        m_outlineCombo->setRootModelIndex(QModelIndex());
268

269
        m_outlineCombo->blockSignals(blocked);
270
    }
271
}
272
} // namespace Internal
273 274
} // namespace QmlJSEditor

275 276 277
class QtQuickToolbarMarker {};
Q_DECLARE_METATYPE(QtQuickToolbarMarker)

278
namespace QmlJSEditor {
279
namespace Internal {
280

281
template <class T>
282
static QList<RefactorMarker> removeMarkersOfType(const QList<RefactorMarker> &markers)
283
{
284 285
    QList<RefactorMarker> result;
    foreach (const RefactorMarker &marker, markers) {
286 287 288 289 290 291
        if (!marker.data.canConvert<T>())
            result += marker;
    }
    return result;
}

292
void QmlJSEditorWidget::updateContextPane()
293
{
294 295 296
    const SemanticInfo info = m_qmlJsEditorDocument->semanticInfo();
    if (m_contextPane && document() && info.isValid()
            && document()->revision() == info.document->editorRevision())
297
    {
298 299
        Node *oldNode = info.declaringMemberNoProperties(m_oldCursorPosition);
        Node *newNode = info.declaringMemberNoProperties(position());
Thomas Hartmann's avatar
Thomas Hartmann committed
300
        if (oldNode != newNode && m_oldCursorPosition != -1)
301
            m_contextPane->apply(this, info.document, 0, newNode, false);
302

303
        if (m_contextPane->isAvailable(this, info.document, newNode) &&
304
            !m_contextPane->widget()->isVisible()) {
305
            QList<RefactorMarker> markers = removeMarkersOfType<QtQuickToolbarMarker>(refactorMarkers());
306
            if (UiObjectMember *m = newNode->uiObjectMemberCast()) {
307
                const int start = qualifiedTypeNameId(m)->identifierToken.begin();
308 309 310
                for (UiQualifiedId *q = qualifiedTypeNameId(m); q; q = q->next) {
                    if (! q->next) {
                        const int end = q->identifierToken.end();
311
                        if (position() >= start && position() <= end) {
312
                            RefactorMarker marker;
313 314 315 316
                            QTextCursor tc(document());
                            tc.setPosition(end);
                            marker.cursor = tc;
                            marker.tooltip = tr("Show Qt Quick ToolBar");
317
                            marker.data = QVariant::fromValue(QtQuickToolbarMarker());
318 319
                            markers.append(marker);
                        }
320 321 322 323
                    }
                }
            }
            setRefactorMarkers(markers);
Thomas Hartmann's avatar
Thomas Hartmann committed
324
        } else if (oldNode != newNode) {
325
            setRefactorMarkers(removeMarkersOfType<QtQuickToolbarMarker>(refactorMarkers()));
326 327
        }
        m_oldCursorPosition = position();
328 329

        setSelectedElements();
330 331 332
    }
}

333
void QmlJSEditorWidget::showTextMarker()
334 335
{
    m_oldCursorPosition = -1;
Eike Ziller's avatar
Eike Ziller committed
336
    updateContextPane();
337 338
}

339
void QmlJSEditorWidget::updateUses()
340
{
341
    if (m_qmlJsEditorDocument->isSemanticInfoOutdated()) // will be updated when info is updated
342
        return;
343

344
    QList<QTextEdit::ExtraSelection> selections;
345 346
    foreach (const AST::SourceLocation &loc,
             m_qmlJsEditorDocument->semanticInfo().idLocations.value(wordUnderCursor())) {
347 348
        if (! loc.isValid())
            continue;
Roberto Raggi's avatar
Roberto Raggi committed
349

350
        QTextEdit::ExtraSelection sel;
351
        sel.format = textDocument()->fontSettings().toTextCharFormat(TextEditor::C_OCCURRENCES);
352 353 354 355 356
        sel.cursor = textCursor();
        sel.cursor.setPosition(loc.begin());
        sel.cursor.setPosition(loc.end(), QTextCursor::KeepAnchor);
        selections.append(sel);
    }
357

358
    setExtraSelections(CodeSemanticsSelection, selections);
359 360 361 362
}

class SelectedElement: protected Visitor
{
363 364 365
    unsigned m_cursorPositionStart;
    unsigned m_cursorPositionEnd;
    QList<UiObjectMember *> m_selectedMembers;
366 367 368

public:
    SelectedElement()
369
        : m_cursorPositionStart(0), m_cursorPositionEnd(0) {}
370

Christian Kamm's avatar
Christian Kamm committed
371
    QList<UiObjectMember *> operator()(const Document::Ptr &doc, unsigned startPosition, unsigned endPosition)
372
    {
373 374 375
        m_cursorPositionStart = startPosition;
        m_cursorPositionEnd = endPosition;
        m_selectedMembers.clear();
Christian Kamm's avatar
Christian Kamm committed
376
        Node::accept(doc->qmlProgram(), this);
377
        return m_selectedMembers;
378 379 380
    }

protected:
381

382
    bool isSelectable(UiObjectMember *member) const
383
    {
384
        UiQualifiedId *id = qualifiedTypeNameId(member);
385
        if (id) {
386
            const QStringRef &name = id->name;
387
            if (!name.isEmpty() && name.at(0).isUpper())
388 389 390 391 392 393
                return true;
        }

        return false;
    }

394
    inline bool isIdBinding(UiObjectMember *member) const
395 396 397 398
    {
        if (UiScriptBinding *script = cast<UiScriptBinding *>(member)) {
            if (! script->qualifiedId)
                return false;
399
            else if (script->qualifiedId->name.isEmpty())
400 401 402 403
                return false;
            else if (script->qualifiedId->next)
                return false;

404
            const QStringRef &propertyName = script->qualifiedId->name;
405 406 407 408 409 410 411 412

            if (propertyName == QLatin1String("id"))
                return true;
        }

        return false;
    }

413
    inline bool containsCursor(unsigned begin, unsigned end)
414
    {
415
        return m_cursorPositionStart >= begin && m_cursorPositionEnd <= end;
416 417
    }

418
    inline bool intersectsCursor(unsigned begin, unsigned end)
419
    {
420
        return (m_cursorPositionEnd >= begin && m_cursorPositionStart <= end);
421 422
    }

423
    inline bool isRangeSelected() const
424
    {
425
        return (m_cursorPositionStart != m_cursorPositionEnd);
426 427
    }

428
    void postVisit(Node *ast)
429
    {
430
        if (!isRangeSelected() && !m_selectedMembers.isEmpty())
431
            return; // nothing to do, we already have the results.
432 433 434 435 436

        if (UiObjectMember *member = ast->uiObjectMemberCast()) {
            unsigned begin = member->firstSourceLocation().begin();
            unsigned end = member->lastSourceLocation().end();

437 438
            if ((isRangeSelected() && intersectsCursor(begin, end))
            || (!isRangeSelected() && containsCursor(begin, end)))
439
            {
440
                if (initializerOfObject(member) && isSelectable(member)) {
441 442 443
                    m_selectedMembers << member;
                    // move start towards end; this facilitates multiselection so that root is usually ignored.
                    m_cursorPositionStart = qMin(end, m_cursorPositionEnd);
444 445 446 447 448 449
                }
            }
        }
    }
};

450
void QmlJSEditorWidget::setSelectedElements()
451
{
452
    if (!receivers(SIGNAL(selectedElementsChanged(QList<QmlJS::AST::UiObjectMember*>,QString))))
453 454
        return;

455
    QTextCursor tc = textCursor();
456
    QString wordAtCursor;
457
    QList<UiObjectMember *> offsets;
458 459 460 461 462 463 464 465 466 467 468 469 470 471

    unsigned startPos;
    unsigned endPos;

    if (tc.hasSelection()) {
        startPos = tc.selectionStart();
        endPos = tc.selectionEnd();
    } else {
        tc.movePosition(QTextCursor::StartOfWord);
        tc.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);

        startPos = textCursor().position();
        endPos = textCursor().position();
    }
472

473
    if (m_qmlJsEditorDocument->semanticInfo().isValid()) {
474
        SelectedElement selectedMembers;
475 476
        QList<UiObjectMember *> members
                = selectedMembers(m_qmlJsEditorDocument->semanticInfo().document, startPos, endPos);
477
        if (!members.isEmpty()) {
478
            foreach (UiObjectMember *m, members) {
479
                offsets << m;
480
            }
481 482
        }
    }
483 484 485
    wordAtCursor = tc.selectedText();

    emit selectedElementsChanged(offsets, wordAtCursor);
486 487
}

488
void QmlJSEditorWidget::applyFontSettings()
489
{
490
    TextEditorWidget::applyFontSettings();
491
    if (!m_qmlJsEditorDocument->isSemanticInfoOutdated())
492
        updateUses();
493 494
}

495

496
QString QmlJSEditorWidget::wordUnderCursor() const
Roberto Raggi's avatar
Roberto Raggi committed
497
{
498
    QTextCursor tc = textCursor();
499
    const QChar ch = document()->characterAt(tc.position() - 1);
500 501 502
    // make sure that we're not at the start of the next word.
    if (ch.isLetterOrNumber() || ch == QLatin1Char('_'))
        tc.movePosition(QTextCursor::Left);
503 504
    tc.movePosition(QTextCursor::StartOfWord);
    tc.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
505
    const QString word = tc.selectedText();
506
    return word;
Roberto Raggi's avatar
Roberto Raggi committed
507 508
}

509
bool QmlJSEditorWidget::isClosingBrace(const QList<Token> &tokens) const
510
{
511

Erik Verbruggen's avatar
Erik Verbruggen committed
512
    if (tokens.size() == 1) {
513
        const Token firstToken = tokens.first();
514

515
        return firstToken.is(Token::RightBrace) || firstToken.is(Token::RightBracket);
Erik Verbruggen's avatar
Erik Verbruggen committed
516
    }
517

Erik Verbruggen's avatar
Erik Verbruggen committed
518 519
    return false;
}
520

521
void QmlJSEditorWidget::createToolBar()
522
{
523 524
    m_outlineCombo = new QComboBox;
    m_outlineCombo->setMinimumContentsLength(22);
525
    m_outlineCombo->setModel(m_qmlJsEditorDocument->outlineModel());
526 527

    QTreeView *treeView = new QTreeView;
528 529 530 531 532 533

    Utils::AnnotatedItemDelegate *itemDelegate = new Utils::AnnotatedItemDelegate(this);
    itemDelegate->setDelimiter(QLatin1String(" "));
    itemDelegate->setAnnotationRole(QmlOutlineModel::AnnotationRole);
    treeView->setItemDelegateForColumn(0, itemDelegate);

534 535
    treeView->header()->hide();
    treeView->setItemsExpandable(false);
536
    treeView->setRootIsDecorated(false);
537
    m_outlineCombo->setView(treeView);
538 539
    treeView->expandAll();

540
    //m_outlineCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
541

542
    // Make the combo box prefer to expand
543
    QSizePolicy policy = m_outlineCombo->sizePolicy();
544
    policy.setHorizontalPolicy(QSizePolicy::Expanding);
545
    m_outlineCombo->setSizePolicy(policy);
546

Kai Koehne's avatar
Kai Koehne committed
547
    connect(m_outlineCombo, SIGNAL(activated(int)), this, SLOT(jumpToOutlineElement(int)));
548 549 550 551 552
    connect(m_qmlJsEditorDocument->outlineModel(), SIGNAL(updated()),
            m_outlineCombo->view()/*QTreeView*/, SLOT(expandAll()));
    connect(m_qmlJsEditorDocument->outlineModel(), SIGNAL(updated()),
            this, SLOT(updateOutlineIndexNow()));

hjk's avatar
hjk committed
553 554
    connect(this, &QmlJSEditorWidget::cursorPositionChanged,
            &m_updateOutlineIndexTimer, static_cast<void (QTimer::*)()>(&QTimer::start));
555

556
    insertExtraToolBarWidget(TextEditorWidget::Left, m_outlineCombo);
557 558
}

559
TextEditorWidget::Link QmlJSEditorWidget::findLinkAt(const QTextCursor &cursor,
560 561
                                                             bool /*resolveTarget*/,
                                                             bool /*inNextSplit*/)
562
{
563
    const SemanticInfo semanticInfo = m_qmlJsEditorDocument->semanticInfo();
564 565 566
    if (! semanticInfo.isValid())
        return Link();

567
    const unsigned cursorPosition = cursor.position();
568

569 570
    AST::Node *node = semanticInfo.astNodeAt(cursorPosition);
    QTC_ASSERT(node, return Link());
571

572 573
    if (AST::UiImport *importAst = cast<AST::UiImport *>(node)) {
        // if it's a file import, link to the file
574
        foreach (const ImportInfo &import, semanticInfo.document->bind()->imports()) {
Fawzi Mohamed's avatar
Fawzi Mohamed committed
575
            if (import.ast() == importAst && import.type() == ImportType::File) {
576
                TextEditorWidget::Link link(import.path());
David Schulz's avatar
David Schulz committed
577 578
                link.linkTextStart = importAst->firstSourceLocation().begin();
                link.linkTextEnd = importAst->lastSourceLocation().end();
579 580 581 582 583 584
                return link;
            }
        }
        return Link();
    }

585 586
    // string literals that could refer to a file link to them
    if (StringLiteral *literal = cast<StringLiteral *>(node)) {
587
        const QString &text = literal->value.toString();
588
        TextEditorWidget::Link link;
David Schulz's avatar
David Schulz committed
589 590
        link.linkTextStart = literal->literalToken.begin();
        link.linkTextEnd = literal->literalToken.end();
591
        if (semanticInfo.snapshot.document(text)) {
David Schulz's avatar
David Schulz committed
592
            link.targetFileName = text;
593 594
            return link;
        }
595
        const QString relative = QString::fromLatin1("%1/%2").arg(
596 597 598
                    semanticInfo.document->path(),
                    text);
        if (semanticInfo.snapshot.document(relative)) {
David Schulz's avatar
David Schulz committed
599
            link.targetFileName = relative;
600 601 602 603
            return link;
        }
    }

604
    const ScopeChain scopeChain = semanticInfo.scopeChain(semanticInfo.rangePath(cursorPosition));
Christian Kamm's avatar
Christian Kamm committed
605
    Evaluate evaluator(&scopeChain);
606
    const Value *value = evaluator.reference(node);
607 608 609 610 611 612 613

    QString fileName;
    int line = 0, column = 0;

    if (! (value && value->getSourceLocation(&fileName, &line, &column)))
        return Link();

614
    TextEditorWidget::Link link;
David Schulz's avatar
David Schulz committed
615 616 617
    link.targetFileName = fileName;
    link.targetLine = line;
    link.targetColumn = column - 1; // adjust the column
618

619 620 621
    if (AST::UiQualifiedId *q = AST::cast<AST::UiQualifiedId *>(node)) {
        for (AST::UiQualifiedId *tail = q; tail; tail = tail->next) {
            if (! tail->next && cursorPosition <= tail->identifierToken.end()) {
David Schulz's avatar
David Schulz committed
622 623
                link.linkTextStart = tail->identifierToken.begin();
                link.linkTextEnd = tail->identifierToken.end();
624 625 626 627
                return link;
            }
        }

628
    } else if (AST::IdentifierExpression *id = AST::cast<AST::IdentifierExpression *>(node)) {
David Schulz's avatar
David Schulz committed
629 630
        link.linkTextStart = id->firstSourceLocation().begin();
        link.linkTextEnd = id->lastSourceLocation().end();
631
        return link;
632

633
    } else if (AST::FieldMemberExpression *mem = AST::cast<AST::FieldMemberExpression *>(node)) {
David Schulz's avatar
David Schulz committed
634 635
        link.linkTextStart = mem->lastSourceLocation().begin();
        link.linkTextEnd = mem->lastSourceLocation().end();
636
        return link;
637 638
    }

639
    return Link();
640 641
}

642
void QmlJSEditorWidget::findUsages()
643
{
644
    m_findReferences->findUsages(textDocument()->filePath(), textCursor().position());
645 646
}

647
void QmlJSEditorWidget::renameUsages()
648
{
649
    m_findReferences->renameUsages(textDocument()->filePath(), textCursor().position());
650 651
}

652
void QmlJSEditorWidget::showContextPane()
653
{
654 655 656 657
    const SemanticInfo info = m_qmlJsEditorDocument->semanticInfo();
    if (m_contextPane && info.isValid()) {
        Node *newNode = info.declaringMemberNoProperties(position());
        ScopeChain scopeChain = info.scopeChain(info.rangePath(position()));
658
        m_contextPane->apply(this, info.document,
Christian Kamm's avatar
Christian Kamm committed
659 660
                             &scopeChain,
                             newNode, false, true);
661
        m_oldCursorPosition = position();
662
        setRefactorMarkers(removeMarkersOfType<QtQuickToolbarMarker>(refactorMarkers()));
663 664 665
    }
}

666
void QmlJSEditorWidget::performQuickFix(int index)
667
{
668
    m_quickFixes.at(index)->perform();
669 670
}

671
void QmlJSEditorWidget::contextMenuEvent(QContextMenuEvent *e)
672
{
673
    QPointer<QMenu> menu(new QMenu(this));
674

675
    QMenu *refactoringMenu = new QMenu(tr("Refactoring"), menu);
676

677 678
    QSignalMapper mapper;
    connect(&mapper, SIGNAL(mapped(int)), this, SLOT(performQuickFix(int)));
679
    if (!m_qmlJsEditorDocument->isSemanticInfoOutdated()) {
680
        AssistInterface *interface = createAssistInterface(QuickFix, ExplicitlyInvoked);
Leandro Melo's avatar
Leandro Melo committed
681
        if (interface) {
682
            QScopedPointer<IAssistProcessor> processor(
Leandro Melo's avatar
Leandro Melo committed
683
                        QmlJSEditorPlugin::instance()->quickFixAssistProvider()->createProcessor());
684
            QScopedPointer<IAssistProposal> proposal(processor->perform(interface));
Leandro Melo's avatar
Leandro Melo committed
685
            if (!proposal.isNull()) {
686
                GenericProposalModel *model = static_cast<GenericProposalModel *>(proposal->model());
Leandro Melo's avatar
Leandro Melo committed
687
                for (int index = 0; index < model->size(); ++index) {
688
                    AssistProposalItem *item = static_cast<AssistProposalItem *>(model->proposalItem(index));
689
                    QuickFixOperation::Ptr op = item->data().value<QuickFixOperation::Ptr>();
Leandro Melo's avatar
Leandro Melo committed
690 691 692 693 694 695
                    m_quickFixes.append(op);
                    QAction *action = refactoringMenu->addAction(op->description());
                    mapper.setMapping(action, index);
                    connect(action, SIGNAL(triggered()), &mapper, SLOT(map()));
                }
                delete model;
696 697 698 699 700 701
            }
        }
    }

    refactoringMenu->setEnabled(!refactoringMenu->isEmpty());

hjk's avatar
hjk committed
702
    if (ActionContainer *mcontext = ActionManager::actionContainer(Constants::M_CONTEXT)) {
703 704 705
        QMenu *contextMenu = mcontext->menu();
        foreach (QAction *action, contextMenu->actions()) {
            menu->addAction(action);
706
            if (action->objectName() == QLatin1String(Constants::M_REFACTORING_MENU_INSERTION_POINT))
707
                menu->addMenu(refactoringMenu);
708
            if (action->objectName() == QLatin1String(Constants::SHOW_QT_QUICK_HELPER)) {
709
                bool enabled = m_contextPane->isAvailable(
710
                            this, m_qmlJsEditorDocument->semanticInfo().document,
711
                            m_qmlJsEditorDocument->semanticInfo().declaringMemberNoProperties(position()));
712 713
                action->setEnabled(enabled);
            }
714 715 716
        }
    }

717 718
    appendStandardContextMenuActions(menu);

719
    menu->exec(e->globalPos());
720 721
    if (!menu)
        return;
722
    m_quickFixes.clear();
723
    delete menu;
724 725
}

726
bool QmlJSEditorWidget::event(QEvent *e)
727 728 729 730
{
    switch (e->type()) {
    case QEvent::ShortcutOverride:
        if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape && m_contextPane) {
731
            if (hideContextPane()) {
732 733 734
                e->accept();
                return true;
            }
735 736 737 738 739 740
        }
        break;
    default:
        break;
    }

741
    return TextEditorWidget::event(e);
742 743 744
}


745
void QmlJSEditorWidget::wheelEvent(QWheelEvent *event)
746
{
747 748 749 750
    bool visible = false;
    if (m_contextPane && m_contextPane->widget()->isVisible())
        visible = true;

751
    TextEditorWidget::wheelEvent(event);
752

753
    if (visible)
754
        m_contextPane->apply(this, m_qmlJsEditorDocument->semanticInfo().document, 0,
755 756
                             m_qmlJsEditorDocument->semanticInfo().declaringMemberNoProperties(m_oldCursorPosition),
                             false, true);
757 758
}

759
void QmlJSEditorWidget::resizeEvent(QResizeEvent *event)
760
{
761
    TextEditorWidget::resizeEvent(event);
762
    hideContextPane();
763 764
}

765
 void QmlJSEditorWidget::scrollContentsBy(int dx, int dy)
766
 {
767
     TextEditorWidget::scrollContentsBy(dx, dy);
768 769 770
     hideContextPane();
 }

771
QmlJSEditorDocument *QmlJSEditorWidget::qmlJsEditorDocument() const
772 773 774 775
{
    return m_qmlJsEditorDocument;
}

776
void QmlJSEditorWidget::semanticInfoUpdated(const SemanticInfo &semanticInfo)
Christian Kamm's avatar
Christian Kamm committed
777
{
778 779
    if (isVisible()) {
         // trigger semantic highlighting and model update if necessary
780
        textDocument()->triggerPendingUpdates();
781
    }
782

783
    if (m_contextPane) {
784
        Node *newNode = semanticInfo.declaringMemberNoProperties(position());
785
        if (newNode) {
786
            m_contextPane->apply(this, semanticInfo.document, 0, newNode, true);
hjk's avatar
hjk committed
787
            m_contextPaneTimer.start(); //update text marker
788
        }
Christian Kamm's avatar
Christian Kamm committed
789 790
    }

791 792 793 794 795 796 797 798 799 800
    if (m_firstSementicInfo) {
        m_firstSementicInfo = false;
        if (semanticInfo.document->language() == Dialect::QmlQtQuick2Ui) {
            Core::InfoBarEntry info(Core::Id(Constants::QML_UI_FILE_WARNING),
                                    tr("This file should only be edited in <b>Design</b> mode."));
            info.setCustomButtonInfo(tr("Switch Mode"), []() { ModeManager::activateMode(Core::Constants::MODE_DESIGN); });
            textDocument()->infoBar()->addInfo(info);
        }
    }

801
    updateUses();
Christian Kamm's avatar
Christian Kamm committed
802 803
}

804
void QmlJSEditorWidget::onRefactorMarkerClicked(const RefactorMarker &marker)
805
{
806 807
    if (marker.data.canConvert<QtQuickToolbarMarker>())
        showContextPane();
808 809
}

810
QModelIndex QmlJSEditorWidget::indexForPosition(unsigned cursorPosition, const QModelIndex &rootIndex) const
811 812 813
{
    QModelIndex lastIndex = rootIndex;

814 815
    QmlOutlineModel *model = m_qmlJsEditorDocument->outlineModel();
    const int rowCount = model->rowCount(rootIndex);