cppeditor.cpp 66.3 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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
43
#include <cpptools/commentssettings.h>
44
#include <cpptools/cppchecksymbols.h>
Christian Kamm's avatar
Christian Kamm committed
45
#include <cpptools/cppcodeformatter.h>
46
#include <cpptools/cppcompletionassistprovider.h>
47
#include <cpptools/cpphighlightingsupport.h>
Nikolai Kosjar's avatar
Nikolai Kosjar committed
48
#include <cpptools/cppmodelmanagerinterface.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 59

#include <projectexplorer/session.h>

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

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

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

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

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

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

con's avatar
con committed
96 97
namespace {

98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
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;
};

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

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

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

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

147
    static Scope *getScopeAndExpression(CPPEditorWidget *editor, const SemanticInfo &info,
148 149
                                        const QTextCursor &cursor,
                                        QString *code)
150
    {
151
        if (!info.doc)
152 153 154 155 156 157 158 159 160 161
            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();
162

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

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

171 172 173 174 175 176 177 178 179 180 181
        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);
182

183 184 185 186 187 188 189 190
        return 0;
    }

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

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

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

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

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

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

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

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

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

        return 0;
    }
227

228 229
};

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

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

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

Orgad Shaneh's avatar
Orgad Shaneh committed
245
    return false;
246 247 248 249 250
}

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

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

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

Orgad Shaneh's avatar
Orgad Shaneh committed
263
    return false;
264 265 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
}

/// 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();

293
    QString comment = QString(doc->characterAt(pos - 3))
294 295 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
            + 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
322 323 324
          || text.startsWith(QLatin1String("//!")))) {
        return false;
    }
325 326 327 328 329 330

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

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

    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('/')) {
383
                newLine.append(QLatin1String(" * "));
384 385 386 387 388
            } else {
                int start = offset;
                while (offset < blockPos && text.at(offset) == QLatin1Char('*'))
                    ++offset;
                newLine.append(QString(offset - start, QLatin1Char('*')));
389
                newLine.append(QLatin1Char(' '));
390 391 392 393 394 395 396 397 398 399
            }
            cursor.insertText(newLine);
            e->accept();
            return true;
        }
    }

    return false;
}

con's avatar
con committed
400 401
} // end of anonymous namespace

402 403 404
namespace CppEditor {
namespace Internal {

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

414 415
Q_GLOBAL_STATIC(CppTools::SymbolFinder, symbolFinder)

416 417 418 419 420
class CPPEditorWidgetPrivate
{
public:
    CPPEditorWidgetPrivate(CPPEditorWidget *q);

Nikolai Kosjar's avatar
Nikolai Kosjar committed
421 422
    QTimer *newSingleShowTimer(int msecInterval);

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
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)
{
}

Nikolai Kosjar's avatar
Nikolai Kosjar committed
484 485 486 487 488 489 490 491
QTimer *CPPEditorWidgetPrivate::newSingleShowTimer(int msecInterval)
{
    QTimer *timer = new QTimer(q);
    timer->setSingleShot(true);
    timer->setInterval(msecInterval);
    return timer;
}

492
CPPEditorWidget::CPPEditorWidget(QWidget *parent)
493
    : TextEditor::BaseTextEditorWidget(new CPPEditorDocument(), parent)
con's avatar
con committed
494
{
495
    baseTextDocument()->setIndenter(new CppTools::CppQtStyleIndenter);
496 497 498 499 500 501 502 503 504 505 506
    ctor();
}

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

void CPPEditorWidget::ctor()
{
507
    d.reset(new CPPEditorWidgetPrivate(this));
508

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

con's avatar
con committed
511 512
    setParenthesesMatchingEnabled(true);
    setMarksVisible(true);
513
    setCodeFoldingSupported(true);
514
    setAutoCompleter(new CppAutoCompleter);
dt's avatar
dt committed
515

516 517
    if (d->m_modelManager) {
        CppEditorSupport *editorSupport = d->m_modelManager->cppEditorSupport(editor());
518 519 520 521
        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
522 523
        connect(editorSupport, SIGNAL(highlighterStarted(QFuture<TextEditor::HighlightingResult>*,uint)),
                this, SLOT(highlighterStarted(QFuture<TextEditor::HighlightingResult>*,uint)));
con's avatar
con committed
524
    }
525

526 527 528
    connect(this, SIGNAL(refactorMarkerClicked(TextEditor::RefactorMarker)),
            this, SLOT(onRefactorMarkerClicked(TextEditor::RefactorMarker)));

529
    connect(d->m_declDefLinkFinder, SIGNAL(foundLink(QSharedPointer<FunctionDeclDefLink>)),
530
            this, SLOT(onFunctionDeclDefLinkFound(QSharedPointer<FunctionDeclDefLink>)));
531 532 533 534 535

    connect(CppTools::CppToolsSettings::instance(),
            SIGNAL(commentsSettingsChanged(CppTools::CommentsSettings)),
            this,
            SLOT(onCommentsSettingsChanged(CppTools::CommentsSettings)));
536 537 538 539

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

542
CPPEditorWidget::~CPPEditorWidget()
con's avatar
con committed
543
{
544 545
    if (d->m_modelManager)
        d->m_modelManager->deleteCppEditorSupport(editor());
con's avatar
con committed
546 547
}

548 549
CPPEditorDocument *CPPEditorWidget::cppEditorDocument() const
{
550
    return d->m_cppEditorDocument;
551 552
}

553
TextEditor::BaseTextEditor *CPPEditorWidget::createEditor()
con's avatar
con committed
554
{
555
    CPPEditor *editable = new CPPEditor(this);
con's avatar
con committed
556 557 558 559
    createToolBar(editable);
    return editable;
}

560
void CPPEditorWidget::createToolBar(CPPEditor *editor)
con's avatar
con committed
561
{
562 563
    d->m_outlineCombo = new Utils::TreeViewComboBox;
    d->m_outlineCombo->setMinimumContentsLength(22);
564 565

    // Make the combo box prefer to expand
566
    QSizePolicy policy = d->m_outlineCombo->sizePolicy();
567
    policy.setHorizontalPolicy(QSizePolicy::Expanding);
568 569
    d->m_outlineCombo->setSizePolicy(policy);
    d->m_outlineCombo->setMaxVisibleItems(40);
con's avatar
con committed
570

571 572
    d->m_outlineModel = new OverviewModel(this);
    d->m_proxyModel = new OverviewProxyModel(d->m_outlineModel, this);
573
    if (CppEditorPlugin::instance()->sortedOutline())
574
        d->m_proxyModel->sort(0, Qt::AscendingOrder);
575
    else
576 577 578 579 580 581 582 583 584 585
        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)),
586
            CppEditorPlugin::instance(), SLOT(setSortedOutline(bool)));
587 588
    d->m_outlineCombo->addAction(d->m_sortAction);

Nikolai Kosjar's avatar
Nikolai Kosjar committed
589
    d->m_updateOutlineTimer = d->newSingleShowTimer(UPDATE_OUTLINE_INTERVAL);
590 591
    connect(d->m_updateOutlineTimer, SIGNAL(timeout()), this, SLOT(updateOutlineNow()));

Nikolai Kosjar's avatar
Nikolai Kosjar committed
592
    d->m_updateOutlineIndexTimer = d->newSingleShowTimer(UPDATE_OUTLINE_INTERVAL);
593 594
    connect(d->m_updateOutlineIndexTimer, SIGNAL(timeout()), this, SLOT(updateOutlineIndexNow()));

Nikolai Kosjar's avatar
Nikolai Kosjar committed
595
    d->m_updateUsesTimer = d->newSingleShowTimer(UPDATE_USES_INTERVAL);
596 597
    connect(d->m_updateUsesTimer, SIGNAL(timeout()), this, SLOT(updateUsesNow()));

Nikolai Kosjar's avatar
Nikolai Kosjar committed
598
    d->m_updateFunctionDeclDefLinkTimer = d->newSingleShowTimer(UPDATE_FUNCTION_DECL_DEF_LINK_INTERVAL);
599
    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
}

Nikolai Kosjar's avatar
Nikolai Kosjar committed
769
void CPPEditorWidget::renameUsages(const QString &replacement)
770
{
771
    if (!d->m_modelManager)
772 773
        return;

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

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

788
void CPPEditorWidget::markSymbolsNow()
789
{
790 791 792 793 794
    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
795
        TranslationUnit *unit = info.doc->translationUnit();
796
        const QList<int> result = d->m_referencesWatcher->result();
797

Erik Verbruggen's avatar
Erik Verbruggen committed
798
        QList<QTextEdit::ExtraSelection> selections;
799

Erik Verbruggen's avatar
Erik Verbruggen committed
800 801 802
        foreach (int index, result) {
            unsigned line, column;
            unit->getTokenPosition(index, &line, &column);
803

Erik Verbruggen's avatar
Erik Verbruggen committed
804 805
            if (column)
                --column;  // adjust the column position.
806

807
            const int len = unit->tokenAt(index).utf16chars();
808

Erik Verbruggen's avatar
Erik Verbruggen committed
809 810 811
            QTextCursor cursor(document()->findBlockByNumber(line - 1));
            cursor.setPosition(cursor.position() + column);
            cursor.setPosition(cursor.position() + len, QTextCursor::KeepAnchor);
812

Erik Verbruggen's avatar
Erik Verbruggen committed
813 814 815 816 817 818
            QTextEdit::ExtraSelection sel;
            sel.format = baseTextDocument()->fontSettings()
                         .toTextCharFormat(TextEditor::C_OCCURRENCES);
            sel.cursor = cursor;
            selections.append(sel);
        }
819

Erik Verbruggen's avatar
Erik Verbruggen committed
820
        setExtraSelections(CodeSemanticsSelection, selections);
Roberto Raggi's avatar
Roberto Raggi committed
821
    }
822
    d->m_referencesWatcher.reset();
Roberto Raggi's avatar
Roberto Raggi committed
823 824
}

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

839
void CPPEditorWidget::markSymbols(const QTextCursor &tc, const SemanticInfo &info)
840 841 842
{
    abortRename();

843
    if (!info.doc)
844
        return;
845 846
    const QTextCharFormat &occurrencesFormat
            = baseTextDocument()->fontSettings().toTextCharFormat(TextEditor::C_OCCURRENCES);
847 848 849 850 851 852
    if (const Macro *macro = findCanonicalMacro(textCursor(), info.doc)) {
        QList<QTextEdit::ExtraSelection> selections;

        //Macro definition
        if (macro->fileName() == info.doc->fileName()) {
            QTextCursor cursor(document());
853
            cursor.setPosition(macro->utf16CharOffset());
Nikolai Kosjar's avatar
Nikolai Kosjar committed
854
            cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor,
855
                                macro->nameToQString().size());
856 857

            QTextEdit::ExtraSelection sel;
858
            sel.format = occurrencesFormat;
859 860 861 862 863
            sel.cursor = cursor;
            selections.append(sel);
        }

        //Other macro uses
864
        foreach (const Document::MacroUse &use, info.doc->macroUses()) {
865 866
            const Macro &useMacro = use.macro();
            if (useMacro.line() != macro->line()
867
                    || useMacro.utf16CharOffset() != macro->utf16CharOffset()
868 869
                    || useMacro.length() != macro->length()
                    || useMacro.fileName() != macro->fileName())
870 871 872
                continue;

            QTextCursor cursor(document());
873 874
            cursor.setPosition(use.utf16charsBegin());
            cursor.setPosition(use.utf16charsEnd(), QTextCursor::KeepAnchor);
875 876

            QTextEdit::ExtraSelection sel;
877
            sel.format = occurrencesFormat;
878 879 880
            sel.cursor = cursor;
            selections.append(sel);
        }
881

882 883 884 885 886
        setExtraSelections(CodeSemanticsSelection, selections);
    } else {
        CanonicalSymbol cs(this, info);
        QString expression;
        if (Scope *scope = cs.getScopeAndExpression(this, info, tc, &expression)) {
887 888 889 890 891 892 893 894 895
            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));
896 897 898
        } else {
            const QList<QTextEdit::ExtraSelection> selections = extraSelections(CodeSemanticsSelection);

899
            if (!selections.isEmpty())
900 901
                setExtraSelections(CodeSemanticsSelection, QList<QTextEdit::ExtraSelection>());
        }
902 903 904
    }
}

905
void CPPEditorWidget::renameSymbolUnderCursor()
Thorbjørn Lindeijer's avatar
Thorbjørn Lindeijer committed
906
{
907
    if (!d->m_modelManager)
908 909
        return;

910
    CppEditorSupport *edSup = d->m_modelManager->cppEditorSupport(editor());
911
    updateSemanticInfo(edSup->recalculateSemanticInfo());
mae's avatar
mae committed
912
    abortRename();
913

Thorbjørn Lindeijer's avatar
Thorbjørn Lindeijer committed
914 915
    QTextCursor c = textCursor();

916 917
    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
918
        if (c.position() >= s.cursor.anchor()
919
                && c.position() <= s.cursor.position()) {
920 921 922 923 924 925 926
            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
927
                    = baseTextDocument()->fontSettings().toTextCharFormat(TextEditor::C_OCCURRENCES_RENAME);;
928
            setExtraSelections(CodeSemanticsSelection, d->m_renameSelections);
Thorbjørn Lindeijer's avatar
Thorbjørn Lindeijer committed
929 930 931
            break;
        }
    }
932

933
    if (d->m_renameSelections.isEmpty())
934
        renameUsages();
Thorbjørn Lindeijer's avatar
Thorbjørn Lindeijer committed
935 936
}

937
void CPPEditorWidget::onContentsChanged(int position, int charsRemoved, int charsAdded)
938
{
939
    if (d->m_currentRenameSelection == d->NoCurrentRenameSelection || d->m_inRename)
940 941
        return;

942
    if (position + charsAdded == d->m_currentRenameSelectionBegin.position()) {
mae's avatar
mae committed
943
        // we are inserting at the beginning of the rename selection => expand
944 945 946
        d->m_currentRenameSelectionBegin.setPosition(position);
        d->m_renameSelections[d->m_currentRenameSelection].cursor.setPosition(position,
                                                                              QTextCursor::KeepAnchor);
mae's avatar
mae committed
947 948
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
949 950
    // the condition looks odd, but keep in mind that the begin
    // and end cursors do move automatically
Nikolai Kosjar's avatar