cppeditor.cpp 66.5 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
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
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8 9 10 11 12 13 14
** 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
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17 18 19 20 21 22 23 24 25
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
26 27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
hjk's avatar
hjk committed
29

con's avatar
con committed
30
#include "cppeditor.h"
31

32
#include "cppautocompleter.h"
con's avatar
con committed
33
#include "cppeditorconstants.h"
34
#include "cppeditorplugin.h"
35
#include "cppfollowsymbolundercursor.h"
con's avatar
con committed
36
#include "cpphighlighter.h"
37
#include "cpppreprocessordialog.h"
Leandro Melo's avatar
Leandro Melo committed
38
#include "cppquickfixassistant.h"
Roberto Raggi's avatar
Roberto Raggi committed
39

40 41
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
42

43
#include <cpptools/cppchecksymbols.h>
Christian Kamm's avatar
Christian Kamm committed
44
#include <cpptools/cppcodeformatter.h>
45
#include <cpptools/cppcompletionassistprovider.h>
46 47
#include <cpptools/cpphighlightingsupport.h>
#include <cpptools/cpplocalsymbols.h>
48
#include <cpptools/cppmodelmanager.h>
49
#include <cpptools/cppqtstyleindenter.h>
50 51 52
#include <cpptools/cppsemanticinfo.h>
#include <cpptools/cpptoolseditorsupport.h>
#include <cpptools/cpptoolsplugin.h>
53
#include <cpptools/cpptoolsreuse.h>
54
#include <cpptools/cpptoolssettings.h>
55
#include <cpptools/doxygengenerator.h>
56
#include <cpptools/symbolfinder.h>
57

58
#include <projectexplorer/nodesvisitor.h>
59 60 61
#include <projectexplorer/projectnodes.h>
#include <projectexplorer/session.h>

con's avatar
con committed
62
#include <texteditor/basetextdocument.h>
63
#include <texteditor/basetextdocumentlayout.h>
64 65 66
#include <texteditor/codeassist/basicproposalitem.h>
#include <texteditor/codeassist/basicproposalitemlistmodel.h>
#include <texteditor/codeassist/genericproposal.h>
con's avatar
con committed
67
#include <texteditor/fontsettings.h>
68
#include <texteditor/refactoroverlay.h>
69 70

#include <utils/qtcassert.h>
Daniel Teske's avatar
Daniel Teske committed
71
#include <utils/treeviewcombobox.h>
72 73

#include <cplusplus/ASTPath.h>
74
#include <cplusplus/BackwardsScanner.h>
75 76
#include <cplusplus/ExpressionUnderCursor.h>
#include <cplusplus/OverviewModel.h>
con's avatar
con committed
77

78
#include <QAction>
79
#include <QFutureWatcher>
80
#include <QMenu>
81 82
#include <QPointer>
#include <QSignalMapper>
83
#include <QSortFilterProxyModel>
84 85
#include <QTextEdit>
#include <QTimer>
86
#include <QToolButton>
con's avatar
con committed
87

Roberto Raggi's avatar
Roberto Raggi committed
88
enum {
89
    UPDATE_OUTLINE_INTERVAL = 500,
90 91
    UPDATE_USES_INTERVAL = 500,
    UPDATE_FUNCTION_DECL_DEF_LINK_INTERVAL = 200
Roberto Raggi's avatar
Roberto Raggi committed
92 93
};

Roberto Raggi's avatar
Roberto Raggi committed
94
using namespace CPlusPlus;
95
using namespace CppTools;
Roberto Raggi's avatar
Roberto Raggi committed
96 97
using namespace CppEditor::Internal;

con's avatar
con committed
98 99
namespace {

100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
class OverviewProxyModel : public QSortFilterProxyModel
{
    Q_OBJECT
public:
    OverviewProxyModel(CPlusPlus::OverviewModel *sourceModel, QObject *parent) :
        QSortFilterProxyModel(parent),
        m_sourceModel(sourceModel)
    {
        setSourceModel(m_sourceModel);
    }

    bool filterAcceptsRow(int sourceRow,const QModelIndex &sourceParent) const
    {
        // ignore generated symbols, e.g. by macro expansion (Q_OBJECT)
        const QModelIndex sourceIndex = m_sourceModel->index(sourceRow, 0, sourceParent);
        CPlusPlus::Symbol *symbol = m_sourceModel->symbolFromIndex(sourceIndex);
        if (symbol && symbol->isGenerated())
            return false;

        return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
    }
private:
    CPlusPlus::OverviewModel *m_sourceModel;
};

125
class CanonicalSymbol
126 127
{
public:
128
    CPPEditorWidget *editor;
129 130 131
    TypeOfExpression typeOfExpression;
    SemanticInfo info;

132
    CanonicalSymbol(CPPEditorWidget *editor, const SemanticInfo &info)
Erik Verbruggen's avatar
Erik Verbruggen committed
133
        : editor(editor), info(info)
134 135
    {
        typeOfExpression.init(info.doc, info.snapshot);
136
        typeOfExpression.setExpandTemplates(true);
137 138 139 140 141 142 143
    }

    const LookupContext &context() const
    {
        return typeOfExpression.context();
    }

144 145 146 147 148
    Scope *getScopeAndExpression(const QTextCursor &cursor, QString *code)
    {
        return getScopeAndExpression(editor, info, cursor, code);
    }

149
    static Scope *getScopeAndExpression(CPPEditorWidget *editor, const SemanticInfo &info,
150 151
                                        const QTextCursor &cursor,
                                        QString *code)
152
    {
153
        if (!info.doc)
154 155 156 157 158 159 160 161 162 163
            return 0;

        QTextCursor tc = cursor;
        int line, col;
        editor->convertPosition(tc.position(), &line, &col);
        ++col; // 1-based line and 1-based column

        QTextDocument *document = editor->document();

        int pos = tc.position();
164

165 166
        if (!isValidIdentifierChar(document->characterAt(pos)))
            if (!(pos > 0 && isValidIdentifierChar(document->characterAt(pos - 1))))
167 168
                return 0;

169
        while (isValidIdentifierChar(document->characterAt(pos)))
170 171 172
            ++pos;
        tc.setPosition(pos);

173 174 175 176 177 178 179 180 181 182 183
        ExpressionUnderCursor expressionUnderCursor;
        *code = expressionUnderCursor(tc);
        return info.doc->scopeAt(line, col);
    }

    Symbol *operator()(const QTextCursor &cursor)
    {
        QString code;

        if (Scope *scope = getScopeAndExpression(cursor, &code))
            return operator()(scope, code);
184

185 186 187 188 189 190 191 192
        return 0;
    }

    Symbol *operator()(Scope *scope, const QString &code)
    {
        return canonicalSymbol(scope, code, typeOfExpression);
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
193 194
    static Symbol *canonicalSymbol(Scope *scope, const QString &code,
                                   TypeOfExpression &typeOfExpression)
195
    {
196 197
        const QList<LookupItem> results =
                typeOfExpression(code.toUtf8(), scope, TypeOfExpression::Preprocess);
198

Roberto Raggi's avatar
Roberto Raggi committed
199 200
        for (int i = results.size() - 1; i != -1; --i) {
            const LookupItem &r = results.at(i);
201
            Symbol *decl = r.declaration();
Roberto Raggi's avatar
Roberto Raggi committed
202

203
            if (!(decl && decl->enclosingScope()))
Roberto Raggi's avatar
Roberto Raggi committed
204 205
                break;

206
            if (Class *classScope = r.declaration()->enclosingScope()->asClass()) {
207 208 209
                const Identifier *declId = decl->identifier();
                const Identifier *classId = classScope->identifier();

210
                if (classId && classId->match(declId))
211 212
                    continue; // skip it, it's a ctor or a dtor.

213
                if (Function *funTy = r.declaration()->type()->asFunctionType()) {
214 215 216 217
                    if (funTy->isVirtual())
                        return r.declaration();
                }
            }
Roberto Raggi's avatar
Roberto Raggi committed
218 219 220
        }

        for (int i = 0; i < results.size(); ++i) {
221 222 223 224 225 226 227 228
            const LookupItem &r = results.at(i);

            if (r.declaration())
                return r.declaration();
        }

        return 0;
    }
229

230 231
};

232 233 234
/// Check if previous line is a CppStyle Doxygen Comment
bool isPreviousLineCppStyleComment(const QTextCursor &cursor)
{
Orgad Shaneh's avatar
Orgad Shaneh committed
235 236 237
    const QTextBlock &currentBlock = cursor.block();
    if (!currentBlock.isValid())
        return false;
238

Orgad Shaneh's avatar
Orgad Shaneh committed
239 240 241
    const QTextBlock &actual = currentBlock.previous();
    if (!actual.isValid())
        return false;
242

Orgad Shaneh's avatar
Orgad Shaneh committed
243 244 245
    const QString text = actual.text().trimmed();
    if (text.startsWith(QLatin1String("///")) || text.startsWith(QLatin1String("//!")))
        return true;
246

Orgad Shaneh's avatar
Orgad Shaneh committed
247
    return false;
248 249 250 251 252
}

/// Check if next line is a CppStyle Doxygen Comment
bool isNextLineCppStyleComment(const QTextCursor &cursor)
{
Orgad Shaneh's avatar
Orgad Shaneh committed
253 254 255
    const QTextBlock &currentBlock = cursor.block();
    if (!currentBlock.isValid())
        return false;
256

Orgad Shaneh's avatar
Orgad Shaneh committed
257 258 259
    const QTextBlock &actual = currentBlock.next();
    if (!actual.isValid())
        return false;
260

Orgad Shaneh's avatar
Orgad Shaneh committed
261 262 263
    const QString text = actual.text().trimmed();
    if (text.startsWith(QLatin1String("///")) || text.startsWith(QLatin1String("//!")))
        return true;
264

Orgad Shaneh's avatar
Orgad Shaneh committed
265
    return false;
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
}

/// Check if line is a CppStyle Doxygen comment and the cursor is after the comment
bool isCursorAfterCppComment(const QTextCursor &cursor, const QTextDocument *doc)
{
    QTextCursor cursorFirstNonBlank(cursor);
    cursorFirstNonBlank.movePosition(QTextCursor::StartOfLine);
    while (doc->characterAt(cursorFirstNonBlank.position()).isSpace()
           && cursorFirstNonBlank.movePosition(QTextCursor::NextCharacter)) {
    }

    const QTextBlock& block = cursorFirstNonBlank.block();
    const QString text = block.text().trimmed();
    if (text.startsWith(QLatin1String("///")) || text.startsWith(QLatin1String("//!")))
        return (cursor.position() >= cursorFirstNonBlank.position() + 3);

    return false;
}

bool isCppStyleContinuation(const QTextCursor& cursor)
{
    return (isPreviousLineCppStyleComment(cursor) || isNextLineCppStyleComment(cursor));
}

DoxygenGenerator::DocumentationStyle doxygenStyle(const QTextCursor &cursor,
                                                  const QTextDocument *doc)
{
    const int pos = cursor.position();

295
    QString comment = QString(doc->characterAt(pos - 3))
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
            + doc->characterAt(pos - 2)
            + doc->characterAt(pos - 1);

    if (comment == QLatin1String("/**"))
        return CppTools::DoxygenGenerator::JavaStyle;
    else if (comment == QLatin1String("/*!"))
        return CppTools::DoxygenGenerator::QtStyle;
    else if (comment == QLatin1String("///"))
        return CppTools::DoxygenGenerator::CppStyleA;
    else
        return CppTools::DoxygenGenerator::CppStyleB;
}

bool handleDoxygenCppStyleContinuation(QTextCursor &cursor,
                                       QKeyEvent *e)
{
    const int blockPos = cursor.positionInBlock();
    const QString &text = cursor.block().text();
    int offset = 0;
    for (; offset < blockPos; ++offset) {
        if (!text.at(offset).isSpace())
            break;
    }

    // If the line does not start with the comment we don't
    // consider it as a continuation. Handles situations like:
    // void d(); ///<enter>
    if (!(text.trimmed().startsWith(QLatin1String("///"))
Orgad Shaneh's avatar
Orgad Shaneh committed
324 325 326
          || text.startsWith(QLatin1String("//!")))) {
        return false;
    }
327 328 329 330 331 332

    QString newLine(QLatin1Char('\n'));
    newLine.append(QString(offset, QLatin1Char(' '))); // indent correctly

    const QString commentMarker = text.mid(offset, 3);
    newLine.append(commentMarker);
333
    newLine.append(QLatin1Char(' '));
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 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384

    cursor.insertText(newLine);
    e->accept();
    return true;
}

bool handleDoxygenContinuation(QTextCursor &cursor,
                               QKeyEvent *e,
                               const QTextDocument *doc,
                               const bool enableDoxygen,
                               const bool leadingAsterisks)
{
    // It might be a continuation if:
    // a) current line starts with /// or //! and cursor is positioned after the comment
    // b) current line is in the middle of a multi-line Qt or Java style comment

    if (enableDoxygen && !cursor.atEnd() && isCursorAfterCppComment(cursor, doc))
        return handleDoxygenCppStyleContinuation(cursor, e);

    if (!leadingAsterisks)
        return false;

    // We continue the comment if the cursor is after a comment's line asterisk and if
    // there's no asterisk immediately after the cursor (that would already be considered
    // a leading asterisk).
    int offset = 0;
    const int blockPos = cursor.positionInBlock();
    const QString &text = cursor.block().text();
    for (; offset < blockPos; ++offset) {
        if (!text.at(offset).isSpace())
            break;
    }

    if (offset < blockPos
            && (text.at(offset) == QLatin1Char('*')
                || (offset < blockPos - 1
                    && text.at(offset) == QLatin1Char('/')
                    && text.at(offset + 1) == QLatin1Char('*')))) {
        int followinPos = blockPos;
        for (; followinPos < text.length(); ++followinPos) {
            if (!text.at(followinPos).isSpace())
                break;
        }
        if (followinPos == text.length()
                || text.at(followinPos) != QLatin1Char('*')) {
            QString newLine(QLatin1Char('\n'));
            QTextCursor c(cursor);
            c.movePosition(QTextCursor::StartOfBlock);
            c.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, offset);
            newLine.append(c.selectedText());
            if (text.at(offset) == QLatin1Char('/')) {
385
                newLine.append(QLatin1String(" * "));
386 387 388 389 390
            } else {
                int start = offset;
                while (offset < blockPos && text.at(offset) == QLatin1Char('*'))
                    ++offset;
                newLine.append(QString(offset - start, QLatin1Char('*')));
391
                newLine.append(QLatin1Char(' '));
392 393 394 395 396 397 398 399 400 401
            }
            cursor.insertText(newLine);
            e->accept();
            return true;
        }
    }

    return false;
}

con's avatar
con committed
402 403
} // end of anonymous namespace

404 405 406
namespace CppEditor {
namespace Internal {

407 408
CPPEditor::CPPEditor(CPPEditorWidget *editor)
    : BaseTextEditor(editor)
con's avatar
con committed
409
{
410 411 412
    m_context.add(CppEditor::Constants::C_CPPEDITOR);
    m_context.add(ProjectExplorer::Constants::LANG_CXX);
    m_context.add(TextEditor::Constants::C_TEXTEDITOR);
413
    setDuplicateSupported(true);
con's avatar
con committed
414 415
}

416 417
Q_GLOBAL_STATIC(CppTools::SymbolFinder, symbolFinder)

418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 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 473 474 475 476 477 478 479 480 481 482 483
class CPPEditorWidgetPrivate
{
public:
    CPPEditorWidgetPrivate(CPPEditorWidget *q);

public:
    CPPEditorWidget *q;

    QPointer<CppTools::CppModelManagerInterface> m_modelManager;

    CPPEditorDocument *m_cppEditorDocument;
    Utils::TreeViewComboBox *m_outlineCombo;
    CPlusPlus::OverviewModel *m_outlineModel;
    QModelIndex m_outlineModelIndex;
    QSortFilterProxyModel *m_proxyModel;
    QAction *m_sortAction;
    QTimer *m_updateOutlineTimer;
    QTimer *m_updateOutlineIndexTimer;
    QTimer *m_updateUsesTimer;
    QTimer *m_updateFunctionDeclDefLinkTimer;
    QHash<int, QTextCharFormat> m_semanticHighlightFormatMap;

    QList<QTextEdit::ExtraSelection> m_renameSelections;
    int m_currentRenameSelection;
    static const int NoCurrentRenameSelection = -1;
    bool m_inRename, m_inRenameChanged, m_firstRenameChange;
    QTextCursor m_currentRenameSelectionBegin;
    QTextCursor m_currentRenameSelectionEnd;

    CppTools::SemanticInfo m_lastSemanticInfo;
    QList<TextEditor::QuickFixOperation::Ptr> m_quickFixes;

    QScopedPointer<QFutureWatcher<TextEditor::HighlightingResult> > m_highlightWatcher;
    unsigned m_highlightRevision; // the editor revision that requested the highlight

    QScopedPointer<QFutureWatcher<QList<int> > > m_referencesWatcher;
    unsigned m_referencesRevision;
    int m_referencesCursorPosition;

    FunctionDeclDefLinkFinder *m_declDefLinkFinder;
    QSharedPointer<FunctionDeclDefLink> m_declDefLink;

    CppTools::CommentsSettings m_commentsSettings;

    QScopedPointer<FollowSymbolUnderCursor> m_followSymbolUnderCursor;
    QToolButton *m_preprocessorButton;
};

CPPEditorWidgetPrivate::CPPEditorWidgetPrivate(CPPEditorWidget *q)
    : q(q)
    , m_modelManager(CppModelManagerInterface::instance())
    , m_cppEditorDocument(qobject_cast<CPPEditorDocument *>(q->baseTextDocument()))
    , m_currentRenameSelection(NoCurrentRenameSelection)
    , m_inRename(false)
    , m_inRenameChanged(false)
    , m_firstRenameChange(false)
    , m_highlightRevision(0)
    , m_referencesRevision(0)
    , m_referencesCursorPosition(0)
    , m_declDefLinkFinder(new FunctionDeclDefLinkFinder(q))
    , m_commentsSettings(CppTools::CppToolsSettings::instance()->commentsSettings())
    , m_followSymbolUnderCursor(new FollowSymbolUnderCursor(q))
    , m_preprocessorButton(0)
{
}

484
CPPEditorWidget::CPPEditorWidget(QWidget *parent)
485
    : TextEditor::BaseTextEditorWidget(new CPPEditorDocument(), parent)
con's avatar
con committed
486
{
487
    baseTextDocument()->setIndenter(new CppTools::CppQtStyleIndenter);
488 489 490 491 492 493 494 495 496 497 498
    ctor();
}

CPPEditorWidget::CPPEditorWidget(CPPEditorWidget *other)
    : TextEditor::BaseTextEditorWidget(other)
{
    ctor();
}

void CPPEditorWidget::ctor()
{
499
    d.reset(new CPPEditorWidgetPrivate(this));
500

501
    qRegisterMetaType<SemanticInfo>("CppTools::SemanticInfo");
Roberto Raggi's avatar
Roberto Raggi committed
502

con's avatar
con committed
503 504
    setParenthesesMatchingEnabled(true);
    setMarksVisible(true);
505
    setCodeFoldingSupported(true);
506
    setAutoCompleter(new CppAutoCompleter);
dt's avatar
dt committed
507

508 509
    if (d->m_modelManager) {
        CppEditorSupport *editorSupport = d->m_modelManager->cppEditorSupport(editor());
510 511 512 513
        connect(editorSupport, SIGNAL(documentUpdated()),
                this, SLOT(onDocumentUpdated()));
        connect(editorSupport, SIGNAL(semanticInfoUpdated(CppTools::SemanticInfo)),
                this, SLOT(updateSemanticInfo(CppTools::SemanticInfo)));
Eike Ziller's avatar
Eike Ziller committed
514 515
        connect(editorSupport, SIGNAL(highlighterStarted(QFuture<TextEditor::HighlightingResult>*,uint)),
                this, SLOT(highlighterStarted(QFuture<TextEditor::HighlightingResult>*,uint)));
con's avatar
con committed
516
    }
517

518 519 520
    connect(this, SIGNAL(refactorMarkerClicked(TextEditor::RefactorMarker)),
            this, SLOT(onRefactorMarkerClicked(TextEditor::RefactorMarker)));

521
    connect(d->m_declDefLinkFinder, SIGNAL(foundLink(QSharedPointer<FunctionDeclDefLink>)),
522
            this, SLOT(onFunctionDeclDefLinkFound(QSharedPointer<FunctionDeclDefLink>)));
523 524 525 526 527

    connect(CppTools::CppToolsSettings::instance(),
            SIGNAL(commentsSettingsChanged(CppTools::CommentsSettings)),
            this,
            SLOT(onCommentsSettingsChanged(CppTools::CommentsSettings)));
528 529 530 531

    connect(baseTextDocument(), SIGNAL(filePathChanged(QString,QString)),
            this, SLOT(onFilePathChanged()));
    onFilePathChanged();
con's avatar
con committed
532 533
}

534
CPPEditorWidget::~CPPEditorWidget()
con's avatar
con committed
535
{
536 537
    if (d->m_modelManager)
        d->m_modelManager->deleteCppEditorSupport(editor());
con's avatar
con committed
538 539
}

540 541
CPPEditorDocument *CPPEditorWidget::cppEditorDocument() const
{
542
    return d->m_cppEditorDocument;
543 544
}

545
TextEditor::BaseTextEditor *CPPEditorWidget::createEditor()
con's avatar
con committed
546
{
547
    CPPEditor *editable = new CPPEditor(this);
con's avatar
con committed
548 549 550 551
    createToolBar(editable);
    return editable;
}

552
void CPPEditorWidget::createToolBar(CPPEditor *editor)
con's avatar
con committed
553
{
554 555
    d->m_outlineCombo = new Utils::TreeViewComboBox;
    d->m_outlineCombo->setMinimumContentsLength(22);
556 557

    // Make the combo box prefer to expand
558
    QSizePolicy policy = d->m_outlineCombo->sizePolicy();
559
    policy.setHorizontalPolicy(QSizePolicy::Expanding);
560 561
    d->m_outlineCombo->setSizePolicy(policy);
    d->m_outlineCombo->setMaxVisibleItems(40);
con's avatar
con committed
562

563 564
    d->m_outlineModel = new OverviewModel(this);
    d->m_proxyModel = new OverviewProxyModel(d->m_outlineModel, this);
565
    if (CppEditorPlugin::instance()->sortedOutline())
566
        d->m_proxyModel->sort(0, Qt::AscendingOrder);
567
    else
568 569 570 571 572 573 574 575 576 577
        d->m_proxyModel->sort(-1, Qt::AscendingOrder); // don't sort yet, but set column for sortedOutline()
    d->m_proxyModel->setDynamicSortFilter(true);
    d->m_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
    d->m_outlineCombo->setModel(d->m_proxyModel);

    d->m_outlineCombo->setContextMenuPolicy(Qt::ActionsContextMenu);
    d->m_sortAction = new QAction(tr("Sort Alphabetically"), d->m_outlineCombo);
    d->m_sortAction->setCheckable(true);
    d->m_sortAction->setChecked(sortedOutline());
    connect(d->m_sortAction, SIGNAL(toggled(bool)),
578
            CppEditorPlugin::instance(), SLOT(setSortedOutline(bool)));
579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599
    d->m_outlineCombo->addAction(d->m_sortAction);

    d->m_updateOutlineTimer = new QTimer(this);
    d->m_updateOutlineTimer->setSingleShot(true);
    d->m_updateOutlineTimer->setInterval(UPDATE_OUTLINE_INTERVAL);
    connect(d->m_updateOutlineTimer, SIGNAL(timeout()), this, SLOT(updateOutlineNow()));

    d->m_updateOutlineIndexTimer = new QTimer(this);
    d->m_updateOutlineIndexTimer->setSingleShot(true);
    d->m_updateOutlineIndexTimer->setInterval(UPDATE_OUTLINE_INTERVAL);
    connect(d->m_updateOutlineIndexTimer, SIGNAL(timeout()), this, SLOT(updateOutlineIndexNow()));

    d->m_updateUsesTimer = new QTimer(this);
    d->m_updateUsesTimer->setSingleShot(true);
    d->m_updateUsesTimer->setInterval(UPDATE_USES_INTERVAL);
    connect(d->m_updateUsesTimer, SIGNAL(timeout()), this, SLOT(updateUsesNow()));

    d->m_updateFunctionDeclDefLinkTimer = new QTimer(this);
    d->m_updateFunctionDeclDefLinkTimer->setSingleShot(true);
    d->m_updateFunctionDeclDefLinkTimer->setInterval(UPDATE_FUNCTION_DECL_DEF_LINK_INTERVAL);
    connect(d->m_updateFunctionDeclDefLinkTimer, SIGNAL(timeout()),
Nikolai Kosjar's avatar
Nikolai Kosjar committed
600
            this, SLOT(updateFunctionDeclDefLinkNow()));
601

602
    connect(d->m_outlineCombo, SIGNAL(activated(int)), this, SLOT(jumpToOutlineElement()));
Kai Koehne's avatar
Kai Koehne committed
603
    connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(updateOutlineIndex()));
604
    connect(d->m_outlineCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateOutlineToolTip()));
con's avatar
con committed
605

606
    // set up slots to document changes
607 608
    connect(document(), SIGNAL(contentsChange(int,int,int)),
            this, SLOT(onContentsChanged(int,int,int)));
con's avatar
con committed
609

610 611 612
    // set up function declaration - definition link
    connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(updateFunctionDeclDefLink()));
    connect(this, SIGNAL(textChanged()), this, SLOT(updateFunctionDeclDefLink()));
Roberto Raggi's avatar
Roberto Raggi committed
613 614 615 616 617

    // set up the semantic highlighter
    connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(updateUses()));
    connect(this, SIGNAL(textChanged()), this, SLOT(updateUses()));

618 619
    d->m_preprocessorButton = new QToolButton(this);
    d->m_preprocessorButton->setText(QLatin1String("#"));
620 621 622
    Core::Command *cmd = Core::ActionManager::command(Constants::OPEN_PREPROCESSOR_DIALOG);
    connect(cmd, SIGNAL(keySequenceChanged()), this, SLOT(updatePreprocessorButtonTooltip()));
    updatePreprocessorButtonTooltip();
623 624 625
    connect(d->m_preprocessorButton, SIGNAL(clicked()), this, SLOT(showPreProcessorWidget()));
    editor->insertExtraToolBarWidget(TextEditor::BaseTextEditor::Left, d->m_preprocessorButton);
    editor->insertExtraToolBarWidget(TextEditor::BaseTextEditor::Left, d->m_outlineCombo);
con's avatar
con committed
626 627
}

628
void CPPEditorWidget::paste()
mae's avatar
mae committed
629
{
630
    if (d->m_currentRenameSelection == d->NoCurrentRenameSelection) {
631
        BaseTextEditorWidget::paste();
mae's avatar
mae committed
632 633 634 635
        return;
    }

    startRename();
636
    BaseTextEditorWidget::paste();
mae's avatar
mae committed
637 638 639
    finishRename();
}

640
void CPPEditorWidget::cut()
mae's avatar
mae committed
641
{
642
    if (d->m_currentRenameSelection == d->NoCurrentRenameSelection) {
643
        BaseTextEditorWidget::cut();
mae's avatar
mae committed
644 645 646 647
        return;
    }

    startRename();
648
    BaseTextEditorWidget::cut();
mae's avatar
mae committed
649 650 651
    finishRename();
}

652 653 654 655
void CPPEditorWidget::selectAll()
{
    // if we are currently renaming a symbol
    // and the cursor is over that symbol, select just that symbol
656
    if (d->m_currentRenameSelection != d->NoCurrentRenameSelection) {
657
        QTextCursor cursor = textCursor();
658 659
        int selectionBegin = d->m_currentRenameSelectionBegin.position();
        int selectionEnd = d->m_currentRenameSelectionEnd.position();
660 661 662 663 664 665 666 667 668 669 670 671 672

        if (cursor.position() >= selectionBegin
                && cursor.position() <= selectionEnd) {
            cursor.setPosition(selectionBegin);
            cursor.setPosition(selectionEnd, QTextCursor::KeepAnchor);
            setTextCursor(cursor);
            return;
        }
    }

    BaseTextEditorWidget::selectAll();
}

673
void CPPEditorWidget::startRename()
mae's avatar
mae committed
674
{
675
    d->m_inRenameChanged = false;
mae's avatar
mae committed
676 677
}

678
void CPPEditorWidget::finishRename()
mae's avatar
mae committed
679
{
680
    if (!d->m_inRenameChanged)
mae's avatar
mae committed
681 682
        return;

683
    d->m_inRename = true;
mae's avatar
mae committed
684 685 686 687

    QTextCursor cursor = textCursor();
    cursor.joinPreviousEditBlock();

688 689 690
    cursor.setPosition(d->m_currentRenameSelectionEnd.position());
    cursor.setPosition(d->m_currentRenameSelectionBegin.position(), QTextCursor::KeepAnchor);
    d->m_renameSelections[d->m_currentRenameSelection].cursor = cursor;
mae's avatar
mae committed
691 692
    QString text = cursor.selectedText();

693 694
    for (int i = 0; i < d->m_renameSelections.size(); ++i) {
        if (i == d->m_currentRenameSelection)
mae's avatar
mae committed
695
            continue;
696
        QTextEdit::ExtraSelection &s = d->m_renameSelections[i];
mae's avatar
mae committed
697 698 699 700 701 702
        int pos = s.cursor.selectionStart();
        s.cursor.removeSelectedText();
        s.cursor.insertText(text);
        s.cursor.setPosition(pos, QTextCursor::KeepAnchor);
    }

703
    setExtraSelections(CodeSemanticsSelection, d->m_renameSelections);
mae's avatar
mae committed
704 705
    cursor.endEditBlock();

706
    d->m_inRename = false;
mae's avatar
mae committed
707 708
}

709
void CPPEditorWidget::abortRename()
710
{
711
    if (d->m_currentRenameSelection <= d->NoCurrentRenameSelection)
mae's avatar
mae committed
712
        return;
713
    d->m_renameSelections[d->m_currentRenameSelection].format
714
            = baseTextDocument()->fontSettings().toTextCharFormat(TextEditor::C_OCCURRENCES);
715 716 717 718
    d->m_currentRenameSelection = d->NoCurrentRenameSelection;
    d->m_currentRenameSelectionBegin = QTextCursor();
    d->m_currentRenameSelectionEnd = QTextCursor();
    setExtraSelections(CodeSemanticsSelection, d->m_renameSelections);
719

720
    semanticRehighlight(/* force = */ true);
721 722
}

723 724 725
/// \brief Called by \c CppEditorSupport when the document corresponding to the
///        file in this editor is updated.
void CPPEditorWidget::onDocumentUpdated()
con's avatar
con committed
726
{
727
    d->m_updateOutlineTimer->start();
con's avatar
con committed
728 729
}

730
const Macro *CPPEditorWidget::findCanonicalMacro(const QTextCursor &cursor, Document::Ptr doc) const
Christian Kamm's avatar
Christian Kamm committed
731
{
732
    if (!doc)
Christian Kamm's avatar
Christian Kamm committed
733 734 735 736 737
        return 0;

    int line, col;
    convertPosition(cursor.position(), &line, &col);

738 739
    if (const Macro *macro = doc->findMacroDefinitionAt(line)) {
        QTextCursor macroCursor = cursor;
740
        const QByteArray name = identifierUnderCursor(&macroCursor).toUtf8();
741
        if (macro->name() == name)
742 743
            return macro;
    } else if (const Document::MacroUse *use = doc->findMacroUseAt(cursor.position())) {
744
        return &use->macro();
745
    }
Christian Kamm's avatar
Christian Kamm committed
746 747 748

    return 0;
}
749

750
void CPPEditorWidget::findUsages()
751
{
752
    if (!d->m_modelManager)
753 754
        return;

755
    SemanticInfo info = d->m_lastSemanticInfo;
756
    info.snapshot = CppModelManagerInterface::instance()->snapshot();
757
    info.snapshot.insert(info.doc);
758

759
    if (const Macro *macro = findCanonicalMacro(textCursor(), info.doc)) {
760
        d->m_modelManager->findMacroUsages(*macro);
761 762 763 764
    } else {
        CanonicalSymbol cs(this, info);
        Symbol *canonicalSymbol = cs(textCursor());
        if (canonicalSymbol)
765
            d->m_modelManager->findUsages(canonicalSymbol, cs.context());
766
    }
767 768
}

769

770
void CPPEditorWidget::renameUsagesNow(const QString &replacement)
771
{
772
    if (!d->m_modelManager)
773 774
        return;

775
    SemanticInfo info = d->m_lastSemanticInfo;
776
    info.snapshot = CppModelManagerInterface::instance()->snapshot();
777 778
    info.snapshot.insert(info.doc);

779
    if (const Macro *macro = findCanonicalMacro(textCursor(), info.doc)) {
780
        d->m_modelManager->renameMacroUsages(*macro, replacement);
781 782 783 784
    } else {
        CanonicalSymbol cs(this, info);
        if (Symbol *canonicalSymbol = cs(textCursor()))
            if (canonicalSymbol->identifier() != 0)
785
                d->m_modelManager->renameUsages(canonicalSymbol, cs.context(), replacement);
786
    }
787 788
}

789
void CPPEditorWidget::renameUsages()
790
{
791 792 793
    renameUsagesNow();
}

794
void CPPEditorWidget::markSymbolsNow()
795
{
796 797 798 799 800
    QTC_ASSERT(d->m_referencesWatcher, return);
    if (!d->m_referencesWatcher->isCanceled()
            && d->m_referencesCursorPosition == position()
            && d->m_referencesRevision == editorRevision()) {
        const SemanticInfo info = d->m_lastSemanticInfo;
Erik Verbruggen's avatar
Erik Verbruggen committed
801
        TranslationUnit *unit = info.doc->translationUnit();
802
        const QList<int> result = d->m_referencesWatcher->result();
803

Erik Verbruggen's avatar
Erik Verbruggen committed
804
        QList<QTextEdit::ExtraSelection> selections;
805

Erik Verbruggen's avatar
Erik Verbruggen committed
806 807 808
        foreach (int index, result) {
            unsigned line, column;
            unit->getTokenPosition(index, &line, &column);
809

Erik Verbruggen's avatar
Erik Verbruggen committed
810 811
            if (column)
                --column;  // adjust the column position.
812

813
            const int len = unit->tokenAt(index).utf16chars();
814

Erik Verbruggen's avatar
Erik Verbruggen committed
815 816 817
            QTextCursor cursor(document()->findBlockByNumber(line - 1));
            cursor.setPosition(cursor.position() + column);
            cursor.setPosition(cursor.position() + len, QTextCursor::KeepAnchor);
818

Erik Verbruggen's avatar
Erik Verbruggen committed
819 820 821 822 823 824
            QTextEdit::ExtraSelection sel;
            sel.format = baseTextDocument()->fontSettings()
                         .toTextCharFormat(TextEditor::C_OCCURRENCES);
            sel.cursor = cursor;
            selections.append(sel);
        }
825

Erik Verbruggen's avatar
Erik Verbruggen committed
826
        setExtraSelections(CodeSemanticsSelection, selections);
Roberto Raggi's avatar
Roberto Raggi committed
827
    }
828
    d->m_referencesWatcher.reset();
Roberto Raggi's avatar
Roberto Raggi committed
829 830
}

Nikolai Kosjar's avatar
Nikolai Kosjar committed
831 832
static QList<int> lazyFindReferences(Scope *scope, QString code, Document::Ptr doc,
                                     Snapshot snapshot)
833 834
{
    TypeOfExpression typeOfExpression;
835 836
    snapshot.insert(doc);
    typeOfExpression.init(doc, snapshot);
837 838
    // make possible to instantiate templates
    typeOfExpression.setExpandTemplates(true);
839
    if (Symbol *canonicalSymbol = CanonicalSymbol::canonicalSymbol(scope, code, typeOfExpression))
Nikolai Kosjar's avatar
Nikolai Kosjar committed
840 841
        return CppModelManagerInterface::instance()->references(canonicalSymbol,
                                                                typeOfExpression.context());
842 843 844
    return QList<int>();
}

845
void CPPEditorWidget::markSymbols(const QTextCursor &tc, const SemanticInfo &info)
846 847 848
{
    abortRename();

849
    if (!info.doc)
850
        return;
851 852
    const QTextCharFormat &occurrencesFormat
            = baseTextDocument()->fontSettings().toTextCharFormat(TextEditor::C_OCCURRENCES);
853 854 855 856 857 858
    if (const Macro *macro = findCanonicalMacro(textCursor(), info.doc)) {
        QList<QTextEdit::ExtraSelection> selections;

        //Macro definition
        if (macro->fileName() == info.doc->fileName()) {
            QTextCursor cursor(document());
859
            cursor.setPosition(macro->utf16CharOffset());
Nikolai Kosjar's avatar
Nikolai Kosjar committed
860
            cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor,
861
                                macro->nameToQString().size());
862 863

            QTextEdit::ExtraSelection sel;
864
            sel.format = occurrencesFormat;
865 866 867 868 869
            sel.cursor = cursor;
            selections.append(sel);
        }

        //Other macro uses
870
        foreach (const Document::MacroUse &use, info.doc->macroUses()) {
871 872
            const Macro &useMacro = use.macro();
            if (useMacro.line() != macro->line()
873
                    || useMacro.utf16CharOffset() != macro->utf16CharOffset()
874 875
                    || useMacro.length() != macro->length()
                    || useMacro.fileName() != macro->fileName())
876 877 878
                continue;

            QTextCursor cursor(document());
879 880
            cursor.setPosition(use.utf16charsBegin());
            cursor.setPosition(use.utf16charsEnd(), QTextCursor::KeepAnchor);
881 882

            QTextEdit::ExtraSelection sel;
883
            sel.format = occurrencesFormat;
884 885 886
            sel.cursor = cursor;
            selections.append(sel);
        }
887

888 889 890 891 892
        setExtraSelections(CodeSemanticsSelection, selections);
    } else {
        CanonicalSymbol cs(this, info);
        QString expression;
        if (Scope *scope = cs.getScopeAndExpression(this, info, tc, &expression)) {
893 894 895 896 897 898 899 900 901
            if (d->m_referencesWatcher)
                d->m_referencesWatcher->cancel();
            d->m_referencesWatcher.reset(new QFutureWatcher<QList<int> >);
            connect(d->m_referencesWatcher.data(), SIGNAL(finished()), SLOT(markSymbolsNow()));

            d->m_referencesRevision = info.revision;
            d->m_referencesCursorPosition = position();
            d->m_referencesWatcher->setFuture(
                QtConcurrent::run(&lazyFindReferences, scope, expression, info.doc, info.snapshot));
902 903 904
        } else {
            const QList<QTextEdit::ExtraSelection> selections = extraSelections(CodeSemanticsSelection);

905
            if (!selections.isEmpty())
906 907
                setExtraSelections(CodeSemanticsSelection, QList<QTextEdit::ExtraSelection>());
        }
908 909 910
    }
}

911
void CPPEditorWidget::renameSymbolUnderCursor()
Thorbjørn Lindeijer's avatar
Thorbjørn Lindeijer committed
912
{
913
    if (!d->m_modelManager)
914 915
        return;

916
    CppEditorSupport *edSup = d->m_modelManager->cppEditorSupport(editor());
917
    updateSemanticInfo(edSup->recalculateSemanticInfo());
mae's avatar
mae committed
918
    abortRename();
919

Thorbjørn Lindeijer's avatar
Thorbjørn Lindeijer committed
920 921
    QTextCursor c = textCursor();

922 923
    for (int i = 0; i < d->m_renameSelections.size(); ++i) {
        QTextEdit::ExtraSelection s = d->m_renameSelections.at(i);
Thorbjørn Lindeijer's avatar
Thorbjørn Lindeijer committed
924
        if (c.position() >= s.cursor.anchor()
925
                && c.position() <= s.cursor.position()) {
926 927 928 929 930 931 932
            d->m_currentRenameSelection = i;
            d->m_firstRenameChange = true;
            d->m_currentRenameSelectionBegin = QTextCursor(c.document()->docHandle(),
                                                           d->m_renameSelections[i].cursor.selectionStart());
            d->m_currentRenameSelectionEnd = QTextCursor(c.document()->docHandle(),
                                                         d->m_renameSelections[i].cursor.selectionEnd());
            d->m_renameSelections[i].format
933
                    = baseTextDocument()->fontSettings().toTextCharFormat(TextEditor::C_OCCURRENCES_RENAME);;
934
            setExtraSelections(CodeSemanticsSelection, d->m_renameSelections);
Thorbjørn Lindeijer's avatar
Thorbjørn Lindeijer committed
935 936 937
            break;
        }
    }
938

939
    if (d->m_renameSelections.isEmpty())
940
        renameUsages();
Thorbjørn Lindeijer's avatar
Thorbjørn Lindeijer committed
941 942
}

943
void CPPEditorWidget::onContentsChanged(int position, int charsRemoved, int charsAdded)
944
{
945
    if (d->m_currentRenameSelection == d->NoCurrentRenameSelection || d->m_inRename)
946 947
        return;

948
    if (position + charsAdded == d->m_currentRenameSelectionBegin.position()) {
mae's avatar
mae committed
949
        // we are inserting at the beginning of the rename selection => expand
950 951 952
        d->m_currentRenameSelectionBegin.setPosition(position);
        d->m_renameSelections[d->m_currentRenameSelection].cursor.setPosition(position,
                                                                              QTextCursor::KeepAnchor);
mae's avatar
mae committed
953 954
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
955 956
    // the condition looks odd, but keep in mind that the begin
    // and end cursors do move automatically
957 958
    d->m_inRenameChanged = (position >= d->m_currentRenameSelectionBegin.position()
                            && position + charsAdded <= d->m_currentRenameSelectionEnd.position());
mae's avatar
mae committed
959

960
    if (!