texteditor.cpp 287 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3 4
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
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
** 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
12 13 14
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
15
**
16 17 18 19 20 21 22
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
con's avatar
con committed
23
**
hjk's avatar
hjk committed
24
****************************************************************************/
hjk's avatar
hjk committed
25

26 27
#include "texteditor.h"
#include "texteditor_p.h"
28 29 30 31
#include "displaysettings.h"
#include "marginsettings.h"
#include "fontsettings.h"
#include "texteditoractionhandler.h"
hjk's avatar
hjk committed
32

33
#include "autocompleter.h"
34
#include "basehoverhandler.h"
35
#include "behaviorsettings.h"
36 37
#include "circularclipboard.h"
#include "circularclipboardassist.h"
con's avatar
con committed
38
#include "codecselector.h"
39
#include "completionsettings.h"
40 41
#include "convenience.h"
#include "highlighterutils.h"
42
#include "icodestylepreferences.h"
43
#include "indenter.h"
44 45 46 47 48
#include "snippets/snippet.h"
#include "syntaxhighlighter.h"
#include "tabsettings.h"
#include "textdocument.h"
#include "textdocumentlayout.h"
49
#include "texteditorconstants.h"
50
#include "texteditoroverlay.h"
51
#include "refactoroverlay.h"
52 53
#include "texteditorsettings.h"
#include "typingsettings.h"
54 55
#include "extraencodingsettings.h"
#include "storagesettings.h"
56

57
#include <texteditor/codeassist/assistinterface.h>
58 59
#include <texteditor/codeassist/codeassistant.h>
#include <texteditor/codeassist/completionassistprovider.h>
60
#include <texteditor/codeassist/keywordscompletionassist.h>
61 62 63 64 65
#include <texteditor/generichighlighter/context.h>
#include <texteditor/generichighlighter/highlightdefinition.h>
#include <texteditor/generichighlighter/highlighter.h>
#include <texteditor/generichighlighter/highlightersettings.h>
#include <texteditor/generichighlighter/manager.h>
con's avatar
con committed
66

67
#include <coreplugin/icore.h>
68
#include <aggregation/aggregate.h>
69
#include <coreplugin/actionmanager/actionmanager.h>
70
#include <coreplugin/actionmanager/actioncontainer.h>
71
#include <coreplugin/actionmanager/command.h>
con's avatar
con committed
72
#include <coreplugin/coreconstants.h>
73
#include <coreplugin/editormanager/editormanager.h>
74
#include <coreplugin/infobar.h>
75
#include <coreplugin/manhattanstyle.h>
76
#include <coreplugin/find/basetextfind.h>
77
#include <coreplugin/find/highlightscrollbar.h>
78
#include <utils/algorithm.h>
79
#include <utils/asconst.h>
con's avatar
con committed
80
#include <utils/linecolumnlabel.h>
81
#include <utils/fileutils.h>
82
#include <utils/dropsupport.h>
83
#include <utils/fadingindicator.h>
84
#include <utils/filesearch.h>
85
#include <utils/hostosinfo.h>
Eike Ziller's avatar
Eike Ziller committed
86
#include <utils/mimetypes/mimedatabase.h>
hjk's avatar
hjk committed
87
#include <utils/qtcassert.h>
88
#include <utils/stylehelper.h>
89
#include <utils/tooltip/tooltip.h>
90
#include <utils/uncommentselection.h>
91
#include <utils/theme/theme.h>
con's avatar
con committed
92

93 94
#include <QAbstractTextDocumentLayout>
#include <QApplication>
95 96 97
#include <QClipboard>
#include <QCoreApplication>
#include <QDebug>
98
#include <QGridLayout>
99
#include <QKeyEvent>
100 101 102
#include <QMenu>
#include <QMessageBox>
#include <QMimeData>
103 104
#include <QPainter>
#include <QPrintDialog>
105
#include <QPrinter>
106
#include <QPropertyAnimation>
107
#include <QDrag>
108
#include <QSequentialAnimationGroup>
109 110 111
#include <QScrollBar>
#include <QShortcut>
#include <QStyle>
112 113
#include <QTextBlock>
#include <QTextCodec>
114 115 116
#include <QTextCursor>
#include <QTextDocumentFragment>
#include <QTextLayout>
117 118 119
#include <QTime>
#include <QTimeLine>
#include <QTimer>
120
#include <QToolBar>
con's avatar
con committed
121

122 123 124 125 126 127 128 129 130 131 132 133
/*!
    \namespace TextEditor
    \brief The TextEditor namespace contains the base text editor and several classes which
    provide supporting functionality like snippets, highlighting, \l{CodeAssist}{code assist},
    indentation and style, and others.
*/

/*!
    \namespace TextEditor::Internal
    \internal
*/

134 135
/*!
    \class TextEditor::BaseTextEditor
136 137 138 139 140 141
    \brief The BaseTextEditor class is base implementation for QPlainTextEdit-based
    text editors. It can use the Kate text highlighting definitions, and some basic
    auto indentation.

    The corresponding document base class is BaseTextDocument, the corresponding
    widget base class is BaseTextEditorWidget.
142

143 144
    It is the default editor for text files used by \QC, if no other editor
    implementation matches the MIME type.
145 146
*/

147

148
using namespace Core;
149
using namespace Utils;
con's avatar
con committed
150 151

namespace TextEditor {
152
namespace Internal {
con's avatar
con committed
153

154 155
enum { NExtraSelectionKinds = 12 };

156 157 158 159 160 161 162 163 164 165 166
typedef QString (TransformationMethod)(const QString &);

static QString QString_toUpper(const QString &str)
{
    return str.toUpper();
}

static QString QString_toLower(const QString &str)
{
    return str.toLower();
}
167

168
class TextEditorAnimator : public QObject
169 170 171 172
{
    Q_OBJECT

public:
173
    TextEditorAnimator(QObject *parent);
174

175 176
    void init(const QTextCursor &cursor, const QFont &f, const QPalette &pal);
    inline QTextCursor cursor() const { return m_cursor; }
177 178 179 180 181 182 183 184 185 186 187 188

    void draw(QPainter *p, const QPointF &pos);
    QRectF rect() const;

    inline qreal value() const { return m_value; }
    inline QPointF lastDrawPos() const { return m_lastDrawPos; }

    void finish();

    bool isRunning() const;

signals:
189
    void updateRequest(const QTextCursor &cursor, QPointF lastPos, QRectF rect);
190

191
private:
192 193
    void step(qreal v);

194
    QTimeLine m_timeline;
195
    qreal m_value;
196
    QTextCursor m_cursor;
197 198 199 200 201 202 203
    QPointF m_lastDrawPos;
    QFont m_font;
    QPalette m_palette;
    QString m_text;
    QSizeF m_size;
};

204 205 206
class TextEditExtraArea : public QWidget
{
public:
207
    TextEditExtraArea(TextEditorWidget *edit)
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
        : QWidget(edit)
    {
        textEdit = edit;
        setAutoFillBackground(true);
    }

protected:
    QSize sizeHint() const {
        return QSize(textEdit->extraAreaWidth(), 0);
    }
    void paintEvent(QPaintEvent *event) {
        textEdit->extraAreaPaintEvent(event);
    }
    void mousePressEvent(QMouseEvent *event) {
        textEdit->extraAreaMouseEvent(event);
    }
    void mouseMoveEvent(QMouseEvent *event) {
        textEdit->extraAreaMouseEvent(event);
    }
    void mouseReleaseEvent(QMouseEvent *event) {
        textEdit->extraAreaMouseEvent(event);
    }
    void leaveEvent(QEvent *event) {
        textEdit->extraAreaLeaveEvent(event);
    }
    void contextMenuEvent(QContextMenuEvent *event) {
        textEdit->extraAreaContextMenuEvent(event);
    }

    void wheelEvent(QWheelEvent *event) {
        QCoreApplication::sendEvent(textEdit->viewport(), event);
    }

private:
242
    TextEditorWidget *textEdit;
243 244
};

hjk's avatar
hjk committed
245 246 247
class BaseTextEditorPrivate
{
public:
248
    BaseTextEditorPrivate() {}
249

hjk's avatar
hjk committed
250
    TextEditorFactoryPrivate *m_origin;
hjk's avatar
hjk committed
251 252
};

253 254 255 256 257 258 259 260 261 262 263 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 293 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 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
class HoverHandlerRunner
{
public:
    HoverHandlerRunner(TextEditorWidget *widget, QList<BaseHoverHandler *> &handlers)
        : m_widget(widget)
        , m_handlers(handlers)
    {
    }

    void startChecking(const QTextCursor &textCursor, const QPoint &point)
    {
        if (m_handlers.empty())
            return;

        // Does the last handler still applies?
        const int documentRevision = textCursor.document()->revision();
        const int position = Convenience::wordStartCursor(textCursor).position();
        if (m_lastHandlerInfo.applies(documentRevision, position)) {
            m_lastHandlerInfo.handler->showToolTip(m_widget, point, /*decorate=*/ false);
            return;
        }

        // Cancel currently running checks
        for (BaseHoverHandler *handler : m_handlers) {
            if (handler->isAsyncHandler())
                handler->cancelAsyncCheck();
        }

        // Update invocation data
        m_documentRevision = documentRevision;
        m_position = position;
        m_point = point;

        // Re-initialize process data
        m_currentHandlerIndex = 0;
        m_bestHandler = nullptr;
        m_highestHandlerPriority = -1;

        // Start checking
        checkNext();
    }

    void checkNext()
    {
        QTC_ASSERT(m_currentHandlerIndex < m_handlers.size(), return);
        BaseHoverHandler *currentHandler = m_handlers[m_currentHandlerIndex];

        currentHandler->checkPriority(m_widget, m_position, [this](int priority) {
            onHandlerFinished(m_documentRevision, m_position, priority);
        });
    }

    void onHandlerFinished(int documentRevision, int position, int priority)
    {
        QTC_ASSERT(m_currentHandlerIndex < m_handlers.size(), return);
        QTC_ASSERT(documentRevision == m_documentRevision, return);
        QTC_ASSERT(position == m_position, return);

        BaseHoverHandler *currentHandler = m_handlers[m_currentHandlerIndex];
        if (priority > m_highestHandlerPriority) {
            m_highestHandlerPriority = priority;
            m_bestHandler = currentHandler;
        }

        // There are more, check next
        ++m_currentHandlerIndex;
        if (m_currentHandlerIndex < m_handlers.size()) {
            checkNext();
            return;
        }

        // All were queried, run the best
        if (m_bestHandler) {
            m_lastHandlerInfo = LastHandlerInfo(m_bestHandler, m_documentRevision, m_position);
            m_bestHandler->showToolTip(m_widget, m_point);
        }
    }

private:
    TextEditorWidget *m_widget = nullptr;
    const QList<BaseHoverHandler *> &m_handlers;

    struct LastHandlerInfo {
        LastHandlerInfo() = default;
        LastHandlerInfo(BaseHoverHandler *handler, int documentRevision, int cursorPosition)
            : handler(handler)
            , documentRevision(documentRevision)
            , cursorPosition(cursorPosition)
        {}

        bool applies(int documentRevision, int cursorPosition) const
        {
            return handler
                && documentRevision == this->documentRevision
                && cursorPosition == this->cursorPosition;
        }

        BaseHoverHandler *handler = nullptr;
        int documentRevision = -1;
        int cursorPosition = -1;
    } m_lastHandlerInfo;

    // invocation data
    QPoint m_point;
    int m_position = -1;
    int m_documentRevision = -1;

    // processing data
    int m_currentHandlerIndex = -1;
    int m_highestHandlerPriority = -1;
    BaseHoverHandler *m_bestHandler = nullptr;
};

366
class TextEditorWidgetPrivate : public QObject
367 368
{
public:
369
    TextEditorWidgetPrivate(TextEditorWidget *parent);
370
    ~TextEditorWidgetPrivate();
371 372 373 374 375 376

    void setupDocumentSignals();
    void updateLineSelectionColor();

    void print(QPrinter *printer);

377
    void maybeSelectLine();
378 379
    void duplicateSelection(bool comment);
    void duplicateBlockSelection(bool comment);
380 381 382
    void updateCannotDecodeInfo();
    void collectToCircularClipboard();

383
    void ctor(const QSharedPointer<TextDocument> &doc);
384 385 386 387 388 389
    void handleHomeKey(bool anchor);
    void handleBackspaceKey();
    void moveLineUpDown(bool up);
    void copyLineUpDown(bool up);
    void saveCurrentCursorPositionForNavigation();
    void updateHighlights();
390
    void updateCurrentLineInScrollbar();
391 392 393 394 395 396 397
    void updateCurrentLineHighlight();

    void drawFoldingMarker(QPainter *painter, const QPalette &pal,
                           const QRect &rect,
                           bool expanded,
                           bool active,
                           bool hovered) const;
398
    void drawLineAnnotation(QPainter &painter, const QTextBlock &block, qreal start);
399 400 401 402 403 404

    void toggleBlockVisible(const QTextBlock &block);
    QRect foldBox();

    QTextBlock foldedBlockAt(const QPoint &pos, QRect *box = 0) const;

405 406
    void requestUpdateLink(QMouseEvent *e, bool immediate = false);
    void updateLink();
407
    void showLink(const TextEditorWidget::Link &);
408 409 410 411 412 413 414 415 416
    void clearLink();

    void universalHelper(); // test function for development

    bool cursorMoveKeyEvent(QKeyEvent *e);
    bool camelCaseRight(QTextCursor &cursor, QTextCursor::MoveMode mode);
    bool camelCaseLeft(QTextCursor &cursor, QTextCursor::MoveMode mode);

    void processTooltipRequest(const QTextCursor &c);
417
    bool processAnnotaionTooltipRequest(const QTextBlock &block, const QPoint &pos) const;
418

419 420
    void transformSelection(TransformationMethod method);
    void transformBlockSelection(TransformationMethod method);
421

422 423 424 425 426 427 428 429 430
    void slotUpdateExtraAreaWidth();
    void slotUpdateRequest(const QRect &r, int dy);
    void slotUpdateBlockNotify(const QTextBlock &);
    void updateTabStops();
    void applyFontSettingsDelayed();

    void editorContentsChange(int position, int charsRemoved, int charsAdded);
    void documentAboutToBeReloaded();
    void documentReloadFinished(bool success);
431
    void highlightSearchResultsSlot(const QString &txt, FindFlags findFlags);
432 433 434 435 436 437 438 439 440 441 442 443 444
    void searchResultsReady(int beginIndex, int endIndex);
    void searchFinished();
    void setupScrollBar();
    void highlightSearchResultsInScrollBar();
    void scheduleUpdateHighlightScrollBar();
    void updateHighlightScrollBarNow();
    struct SearchResult {
        int start;
        int length;
    };
    void addSearchResultsToScrollBar(QVector<SearchResult> results);
    void adjustScrollBarRanges();

445 446
    void setFindScope(const QTextCursor &start, const QTextCursor &end, int, int);

447 448
    void updateCursorPosition();

449 450 451
    // parentheses matcher
    void _q_matchParentheses();
    void _q_highlightBlocks();
452
    void autocompleterHighlight(const QTextCursor &cursor = QTextCursor());
453 454
    void updateAnimator(QPointer<TextEditorAnimator> animator, QPainter &painter);
    void cancelCurrentAnimations();
455
    void slotSelectionChanged();
456
    void _q_animateUpdate(const QTextCursor &cursor, QPointF lastPos, QRectF rect);
457 458
    void updateCodeFoldingVisible();

459 460
    void reconfigure();

461
public:
462
    TextEditorWidget *q;
463 464 465 466 467 468
    QToolBar *m_toolBar = nullptr;
    QWidget *m_stretchWidget = nullptr;
    LineColumnLabel *m_cursorPositionLabel = nullptr;
    LineColumnLabel *m_fileEncodingLabel = nullptr;
    QAction *m_cursorPositionLabelAction = nullptr;
    QAction *m_fileEncodingLabelAction = nullptr;
469

470 471
    bool m_contentsChanged = false;
    bool m_lastCursorChangeWasInteresting = false;
472

473
    QSharedPointer<TextDocument> m_document;
474 475 476
    QByteArray m_tempState;
    QByteArray m_tempNavigationState;

477
    bool m_parenthesesMatchingEnabled = false;
478 479

    // parentheses matcher
480
    bool m_formatRange = false;
481 482 483
    QTimer m_parenthesesMatchingTimer;
    // end parentheses matcher

484
    QWidget *m_extraArea = nullptr;
485

486
    Id m_tabSettingsId;
487
    ICodeStylePreferences *m_codeStylePreferences = nullptr;
488
    DisplaySettings m_displaySettings;
489
    bool m_annotationsrRight = true;
490
    MarginSettings m_marginSettings;
491 492
    // apply when making visible the first time, for the split case
    bool m_fontSettingsNeedsApply = true;
493 494
    BehaviorSettings m_behaviorSettings;

495 496 497 498
    int extraAreaSelectionAnchorBlockNumber = -1;
    int extraAreaToggleMarkBlockNumber = -1;
    int extraAreaHighlightFoldedBlockNumber = -1;
    int extraAreaPreviousMarkTooltipRequestedLine = -1;
499

500 501 502
    TextEditorOverlay *m_overlay = nullptr;
    TextEditorOverlay *m_snippetOverlay = nullptr;
    TextEditorOverlay *m_searchResultOverlay = nullptr;
503 504 505
    bool snippetCheckCursor(const QTextCursor &cursor);
    void snippetTabOrBacktab(bool forward);

506 507 508 509 510 511
    struct AnnotationRect
    {
        QRectF rect;
        const TextMark *mark;
    };
    QMap<int, QList<AnnotationRect>> m_annotationRects;
512
    QRectF getLastLineLineRect(const QTextBlock &block);
513

514
    RefactorOverlay *m_refactorOverlay = nullptr;
515
    QString m_contextHelpId;
516 517

    QBasicTimer foldedBlockTimer;
518 519
    int visibleFoldedBlockNumber = -1;
    int suggestedVisibleFoldedBlockNumber = -1;
520
    void clearVisibleFoldedBlock();
521
    bool m_mouseOnFoldedMarker = false;
522 523 524 525 526 527 528 529 530 531 532 533
    void foldLicenseHeader();

    QBasicTimer autoScrollTimer;
    uint m_marksVisible : 1;
    uint m_codeFoldingVisible : 1;
    uint m_codeFoldingSupported : 1;
    uint m_revisionsVisible : 1;
    uint m_lineNumbersVisible : 1;
    uint m_highlightCurrentLine : 1;
    uint m_requestMarkEnabled : 1;
    uint m_lineSeparatorsAllowed : 1;
    uint m_maybeFakeTooltipEvent : 1;
534
    int m_visibleWrapColumn = 0;
535

536
    TextEditorWidget::Link m_currentLink;
537
    bool m_linkPressed = false;
538 539
    QTextCursor m_pendingLinkUpdate;
    QTextCursor m_lastLinkUpdate;
540 541

    QRegExp m_searchExpr;
542
    FindFlags m_findFlags;
543 544 545
    void highlightSearchResults(const QTextBlock &block, TextEditorOverlay *overlay);
    QTimer m_delayedUpdateTimer;

546 547
    void setExtraSelections(Core::Id kind, const QList<QTextEdit::ExtraSelection> &selections);
    QHash<Core::Id, QList<QTextEdit::ExtraSelection>> m_extraSelections;
548 549

    // block selection mode
550
    bool m_inBlockSelectionMode = false;
551 552 553 554 555 556 557 558
    QString copyBlockSelection();
    void insertIntoBlockSelection(const QString &text = QString());
    void setCursorToColumn(QTextCursor &cursor, int column,
                          QTextCursor::MoveMode moveMode = QTextCursor::MoveAnchor);
    void removeBlockSelection();
    void enableBlockSelection(const QTextCursor &cursor);
    void enableBlockSelection(int positionBlock, int positionColumn,
                              int anchorBlock, int anchorColumn);
559 560 561 562 563 564 565

    enum BlockSelectionUpdateKind {
        NoCursorUpdate,
        CursorUpdateKeepSelection,
        CursorUpdateClearSelection,
    };
    void disableBlockSelection(BlockSelectionUpdateKind kind);
566 567 568
    void resetCursorFlashTimer();
    QBasicTimer m_cursorFlashTimer;
    bool m_cursorVisible;
569
    bool m_moveLineUndoHack = false;
570 571 572

    QTextCursor m_findScopeStart;
    QTextCursor m_findScopeEnd;
573 574
    int m_findScopeVerticalBlockSelectionFirstColumn = -1;
    int m_findScopeVerticalBlockSelectionLastColumn = -1;
575 576 577

    QTextCursor m_selectBlockAnchor;

578
    TextBlockSelection m_blockSelection;
579 580 581 582

    void moveCursorVisible(bool ensureVisible = true);

    int visualIndent(const QTextBlock &block) const;
583
    TextEditorPrivateHighlightBlocks m_highlightBlocksInfo;
584 585
    QTimer m_highlightBlocksTimer;

586
    CodeAssistant m_codeAssistant;
587
    bool m_assistRelevantContentAdded = false;
588

589
    QList<BaseHoverHandler *> m_hoverHandlers; // Not owned
590
    HoverHandlerRunner m_hoverHandlerRunner;
591

592 593
    QPointer<QSequentialAnimationGroup> m_navigationAnimation;

594
    QPointer<TextEditorAnimator> m_bracketsAnimator;
595 596

    // Animation and highlighting of auto completed text
597 598
    QPointer<TextEditorAnimator> m_autocompleteAnimator;
    bool m_animateAutoComplete = true;
599
    bool m_highlightAutoComplete = true;
600
    bool m_skipAutoCompletedText = true;
601
    bool m_removeAutoCompletedText = true;
602
    bool m_keepAutoCompletionHighlight = false;
603 604
    QList<QTextCursor> m_autoCompleteHighlightPos;
    void updateAutoCompleteHighlight();
605

606 607
    int m_cursorBlockNumber = -1;
    int m_blockCount = 0;
608 609

    QPoint m_markDragStart;
610
    bool m_markDragging = false;
611

612
    QScopedPointer<ClipboardAssistProvider> m_clipboardAssistProvider;
613

614
    bool m_isMissingSyntaxDefinition = false;
615 616

    QScopedPointer<AutoCompleter> m_autoCompleter;
617
    CommentDefinition m_commentDefinition;
618

619
    QFutureWatcher<FileSearchResultList> *m_searchWatcher = nullptr;
620 621
    QVector<SearchResult> m_searchResults;
    QTimer m_scrollBarUpdateTimer;
622 623
    HighlightScrollBar *m_highlightScrollBar = nullptr;
    bool m_scrollBarUpdateScheduled = false;
624 625
};

626
TextEditorWidgetPrivate::TextEditorWidgetPrivate(TextEditorWidget *parent)
627 628 629 630 631 632 633 634 635 636
  : q(parent),
    m_marksVisible(false),
    m_codeFoldingVisible(false),
    m_codeFoldingSupported(false),
    m_revisionsVisible(false),
    m_lineNumbersVisible(true),
    m_highlightCurrentLine(true),
    m_requestMarkEnabled(true),
    m_lineSeparatorsAllowed(false),
    m_maybeFakeTooltipEvent(false),
637
    m_hoverHandlerRunner(parent, m_hoverHandlers),
638
    m_clipboardAssistProvider(new ClipboardAssistProvider),
639
    m_autoCompleter(new AutoCompleter)
640 641 642
{
    Aggregation::Aggregate *aggregate = new Aggregation::Aggregate;
    BaseTextFind *baseTextFind = new BaseTextFind(q);
hjk's avatar
hjk committed
643
    connect(baseTextFind, &BaseTextFind::highlightAllRequested,
644
            this, &TextEditorWidgetPrivate::highlightSearchResultsSlot);
645
    connect(baseTextFind, &BaseTextFind::findScopeChanged,
646
            this, &TextEditorWidgetPrivate::setFindScope);
647 648
    aggregate->add(baseTextFind);
    aggregate->add(q);
649

650 651 652
    m_extraArea = new TextEditExtraArea(q);
    m_extraArea->setMouseTracking(true);

653 654 655 656 657 658
    m_stretchWidget = new QWidget;
    m_stretchWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
    m_toolBar = new QToolBar;
    m_toolBar->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
    m_toolBar->addWidget(m_stretchWidget);

659
    m_cursorPositionLabel = new LineColumnLabel;
660 661 662
    const int spacing = q->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing) / 2;
    m_cursorPositionLabel->setContentsMargins(spacing, 0, spacing, 0);

663
    m_fileEncodingLabel = new LineColumnLabel;
664 665 666 667
    m_fileEncodingLabel->setContentsMargins(spacing, 0, spacing, 0);

    m_cursorPositionLabelAction = m_toolBar->addWidget(m_cursorPositionLabel);
    m_fileEncodingLabelAction = m_toolBar->addWidget(m_fileEncodingLabel);
668
    m_extraSelections.reserve(NExtraSelectionKinds);
669
}
670

671 672 673 674 675 676
TextEditorWidgetPrivate::~TextEditorWidgetPrivate()
{
    q->disconnect(this);
    delete m_toolBar;
}

677
} // namespace Internal
hjk's avatar
hjk committed
678 679

using namespace Internal;
con's avatar
con committed
680

681 682 683 684
/*!
 * Test if syntax highlighter is available (or unneeded) for \a widget.
 * If not found, show a warning with a link to the relevant settings page.
 */
685
static void updateEditorInfoBar(TextEditorWidget *widget)
686 687 688 689 690 691 692 693 694 695
{
    Id infoSyntaxDefinition(Constants::INFO_SYNTAX_DEFINITION);
    InfoBar *infoBar = widget->textDocument()->infoBar();
    if (!widget->isMissingSyntaxDefinition()) {
        infoBar->removeInfo(infoSyntaxDefinition);
    } else if (infoBar->canInfoBeAdded(infoSyntaxDefinition)) {
        InfoBarEntry info(infoSyntaxDefinition,
                          BaseTextEditor::tr("A highlight definition was not found for this file. "
                                             "Would you like to try to find one?"),
                          InfoBarEntry::GlobalSuppressionEnabled);
696
        info.setCustomButtonInfo(BaseTextEditor::tr("Show Highlighter Options..."), [widget]() {
697
            ICore::showOptionsDialog(Constants::TEXT_EDITOR_HIGHLIGHTER_SETTINGS, widget);
698 699
        });

700 701 702 703
        infoBar->addInfo(info);
    }
}

704
QString TextEditorWidget::plainTextFromSelection(const QTextCursor &cursor) const
705
{
706 707 708 709 710
    // Copy the selected text as plain text
    QString text = cursor.selectedText();
    return convertToPlainText(text);
}

711
QString TextEditorWidget::convertToPlainText(const QString &txt)
712 713 714 715
{
    QString ret = txt;
    QChar *uc = ret.data();
    QChar *e = uc + ret.size();
716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731

    for (; uc != e; ++uc) {
        switch (uc->unicode()) {
        case 0xfdd0: // QTextBeginningOfFrame
        case 0xfdd1: // QTextEndOfFrame
        case QChar::ParagraphSeparator:
        case QChar::LineSeparator:
            *uc = QLatin1Char('\n');
            break;
        case QChar::Nbsp:
            *uc = QLatin1Char(' ');
            break;
        default:
            ;
        }
    }
732
    return ret;
733 734
}

Orgad Shaneh's avatar
Orgad Shaneh committed
735
static const char kTextBlockMimeType[] = "application/vnd.qtcreator.blocktext";
736

737 738 739
Id TextEditorWidget::SnippetPlaceholderSelection("TextEdit.SnippetPlaceHolderSelection");
Id TextEditorWidget::CurrentLineSelection("TextEdit.CurrentLineSelection");
Id TextEditorWidget::ParenthesesMatchingSelection("TextEdit.ParenthesesMatchingSelection");
740
Id TextEditorWidget::AutoCompleteSelection("TextEdit.AutoCompleteSelection");
741 742 743 744 745 746 747 748 749
Id TextEditorWidget::CodeWarningsSelection("TextEdit.CodeWarningsSelection");
Id TextEditorWidget::CodeSemanticsSelection("TextEdit.CodeSemanticsSelection");
Id TextEditorWidget::UndefinedSymbolSelection("TextEdit.UndefinedSymbolSelection");
Id TextEditorWidget::UnusedSymbolSelection("TextEdit.UnusedSymbolSelection");
Id TextEditorWidget::OtherSelection("TextEdit.OtherSelection");
Id TextEditorWidget::ObjCSelection("TextEdit.ObjCSelection");
Id TextEditorWidget::DebuggerExceptionSelection("TextEdit.DebuggerExceptionSelection");
Id TextEditorWidget::FakeVimSelection("TextEdit.FakeVimSelection");

750
TextEditorWidget::TextEditorWidget(QWidget *parent)
751 752
    : QPlainTextEdit(parent)
{
753 754 755
    // "Needed", as the creation below triggers ChildEvents that are
    // passed to this object's event() which uses 'd'.
    d = 0;
756
    d = new TextEditorWidgetPrivate(this);
757 758
}

759
void TextEditorWidget::setTextDocument(const QSharedPointer<TextDocument> &doc)
760
{
761
    d->ctor(doc);
762 763
}

764 765 766 767 768 769 770 771 772 773 774 775 776 777 778
void TextEditorWidgetPrivate::setupScrollBar()
{
    if (m_displaySettings.m_scrollBarHighlights) {
        if (m_highlightScrollBar)
            return;
        m_highlightScrollBar = new HighlightScrollBar(Qt::Vertical, q);
        q->setVerticalScrollBar(m_highlightScrollBar);
        highlightSearchResultsInScrollBar();
        scheduleUpdateHighlightScrollBar();
    } else if (m_highlightScrollBar) {
        q->setVerticalScrollBar(new QScrollBar(Qt::Vertical, q));
        m_highlightScrollBar = 0;
    }
}

779
void TextEditorWidgetPrivate::ctor(const QSharedPointer<TextDocument> &doc)
con's avatar
con committed
780
{
781
    q->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
con's avatar
con committed
782

783 784 785 786
    m_overlay = new TextEditorOverlay(q);
    m_snippetOverlay = new TextEditorOverlay(q);
    m_searchResultOverlay = new TextEditorOverlay(q);
    m_refactorOverlay = new RefactorOverlay(q);
787

788 789
    m_document = doc;
    setupDocumentSignals();
con's avatar
con committed
790 791 792

    // from RESEARCH

793 794
    q->setLayoutDirection(Qt::LeftToRight);
    q->viewport()->setMouseTracking(true);
con's avatar
con committed
795

796 797 798 799 800
    extraAreaSelectionAnchorBlockNumber = -1;
    extraAreaToggleMarkBlockNumber = -1;
    extraAreaHighlightFoldedBlockNumber = -1;
    visibleFoldedBlockNumber = -1;
    suggestedVisibleFoldedBlockNumber = -1;
con's avatar
con committed
801

802
    QObject::connect(&m_codeAssistant, &CodeAssistant::finished,
803
                     q, &TextEditorWidget::assistFinished);
804

805
    QObject::connect(q, &QPlainTextEdit::blockCountChanged,
806
                     this, &TextEditorWidgetPrivate::slotUpdateExtraAreaWidth);
807

808
    QObject::connect(q, &QPlainTextEdit::modificationChanged, m_extraArea,
809 810 811
                     static_cast<void (QWidget::*)()>(&QWidget::update));

    QObject::connect(q, &QPlainTextEdit::cursorPositionChanged,
812
                     q, &TextEditorWidget::slotCursorPositionChanged);
813

814
    QObject::connect(q, &QPlainTextEdit::cursorPositionChanged,
815
                     this, &TextEditorWidgetPrivate::updateCursorPosition);
816

817
    QObject::connect(q, &QPlainTextEdit::updateRequest,
818
                     this, &TextEditorWidgetPrivate::slotUpdateRequest);
819 820

    QObject::connect(q, &QPlainTextEdit::selectionChanged,
821
                     this, &TextEditorWidgetPrivate::slotSelectionChanged);
con's avatar
con committed
822 823

    // parentheses matcher
824 825
    m_formatRange = true;
    m_parenthesesMatchingTimer.setSingleShot(true);
826
    QObject::connect(&m_parenthesesMatchingTimer, &QTimer::timeout,
827
                     this, &TextEditorWidgetPrivate::_q_matchParentheses);
con's avatar
con committed
828

829
    m_highlightBlocksTimer.setSingleShot(true);
830
    QObject::connect(&m_highlightBlocksTimer, &QTimer::timeout,
831
                     this, &TextEditorWidgetPrivate::_q_highlightBlocks);
832

833 834 835 836
    m_scrollBarUpdateTimer.setSingleShot(true);
    QObject::connect(&m_scrollBarUpdateTimer, &QTimer::timeout,
                     this, &TextEditorWidgetPrivate::highlightSearchResultsInScrollBar);

837 838
    m_bracketsAnimator = 0;
    m_autocompleteAnimator = 0;
con's avatar
con committed
839

840
    slotUpdateExtraAreaWidth();
con's avatar
con committed
841
    updateHighlights();
842
    q->setFrameStyle(QFrame::NoFrame);
con's avatar
con committed
843

844
    m_delayedUpdateTimer.setSingleShot(true);
845 846
    QObject::connect(&m_delayedUpdateTimer, &QTimer::timeout, q->viewport(),
                     static_cast<void (QWidget::*)()>(&QWidget::update));
847

848
    m_moveLineUndoHack = false;
849 850 851

    updateCannotDecodeInfo();

852 853 854 855
    QObject::connect(m_document.data(), &TextDocument::aboutToOpen,
                     q, &TextEditorWidget::aboutToOpen);
    QObject::connect(m_document.data(), &TextDocument::openFinishedSuccessfully,
                     q, &TextEditorWidget::openFinishedSuccessfully);
856 857 858 859 860
    connect(m_fileEncodingLabel, &LineColumnLabel::clicked,
            q, &TextEditorWidget::selectEncoding);
    connect(m_document->document(), &QTextDocument::modificationChanged,
            q, &TextEditorWidget::updateTextCodecLabel);
    q->updateTextCodecLabel();
con's avatar
con committed
861 862
}

863
TextEditorWidget::~TextEditorWidget()
con's avatar
con committed
864 865 866 867 868
{
    delete d;
    d = 0;
}

869
void TextEditorWidget::print(QPrinter *printer)
con's avatar
con committed
870
{
hjk's avatar
hjk committed
871
    const bool oldFullPage = printer->fullPage();
con's avatar
con committed
872 873 874
    printer->setFullPage(true);
    QPrintDialog *dlg = new QPrintDialog(printer, this);
    dlg->setWindowTitle(tr("Print Document"));
875
    if (dlg->exec() == QDialog::Accepted)
con's avatar
con committed
876 877 878 879 880
        d->print(printer);
    printer->setFullPage(oldFullPage);
    delete dlg;
}

mae's avatar
mae committed
881
static int foldBoxWidth(const QFontMetrics &fm)
882 883
{
    const int lineSpacing = fm.lineSpacing();
hjk's avatar
hjk committed
884
    return lineSpacing + lineSpacing % 2 + 1;
885 886
}

con's avatar
con committed
887 888 889 890 891 892 893
static void printPage(int index, QPainter *painter, const QTextDocument *doc,
                      const QRectF &body, const QRectF &titleBox,
                      const QString &title)
{
    painter->save();

    painter->translate(body.left(), body.top() - (index - 1) * body.height());
894
    const QRectF view(0, (index - 1) * body.height(), body.width(), body.height());
con's avatar
con committed
895 896 897 898 899

    QAbstractTextDocumentLayout *layout = doc->documentLayout();
    QAbstractTextDocumentLayout::PaintContext ctx;

    painter->setFont(QFont(doc->defaultFont()));
900 901 902 903 904
    const QRectF box = titleBox.translated(0, view.top());
    const int dpix = painter->device()->logicalDpiX();
    const int dpiy = painter->device()->logicalDpiY();
    const int mx = 5 * dpix / 72.0;
    const int my = 2 * dpiy / 72.0;
con's avatar
con committed
905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922
    painter->fillRect(box.adjusted(-mx, -my, mx, my), QColor(210, 210, 210));
    if (!title.isEmpty())
        painter->drawText(box, Qt::AlignCenter, title);
    const QString pageString = QString::number(index);
    painter->drawText(box, Qt::AlignRight, pageString);

    painter->setClipRect(view);
    ctx.clip = view;
    // don't use the system palette text as default text color, on HP/UX
    // for example that's white, and white text on white paper doesn't
    // look that nice
    ctx.palette.setColor(QPalette::Text, Qt::black);

    layout->draw(painter, ctx);

    painter->restore();
}

923
void TextEditorWidgetPrivate::print(QPrinter *printer)
con's avatar
con committed
924 925 926
{
    QTextDocument *doc = q->document();

927
    QString title = m_document->displayName();
928
    if (!title.isEmpty())
con's avatar
con committed
929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954
        printer->setDocName(title);


    QPainter p(printer);

    // Check that there is a valid device to print to.
    if (!p.isActive())
        return;

    doc = doc->clone(doc);

    QTextOption opt = doc->defaultTextOption();
    opt.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
    doc->setDefaultTextOption(opt);

    (void)doc->documentLayout(); // make sure that there is a layout


    QColor background = q->palette().color(QPalette::Base);
    bool backgroundIsDark = background.value() < 128;

    for (QTextBlock srcBlock = q->document()->firstBlock(), dstBlock = doc->firstBlock();
         srcBlock.isValid() && dstBlock.isValid();
         srcBlock = srcBlock.next(), dstBlock = dstBlock.next()) {


955
        QVector<QTextLayout::FormatRange> formatList = srcBlock.layout()->formats();
con's avatar
con committed
956 957
        if (backgroundIsDark) {
            // adjust syntax highlighting colors for better contrast
hjk's avatar
hjk committed
958
            for (int i = formatList.count() - 1; i >= 0; --i) {
con's avatar
con committed
959 960 961 962 963 964 965 966 967 968 969 970 971 972
                QTextCharFormat &format = formatList[i].format;
                if (format.background().color() == background) {
                    QBrush brush = format.foreground();
                    QColor color = brush.color();
                    int h,s,v,a;
                    color.getHsv(&h, &s, &v, &a);
                    color.setHsv(h, s, qMin(128, v), a);
                    brush.setColor(color);
                    format.setForeground(brush);
                }
                format.setBackground(Qt::white);
            }
        }

973
        dstBlock.layout()->setFormats(formatList);
con's avatar
con committed
974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999
    }

    QAbstractTextDocumentLayout *layout = doc->documentLayout();
    layout->setPaintDevice(p.device());

    int dpiy = p.device()->logicalDpiY();
    int margin = (int) ((2/2.54)*dpiy); // 2 cm margins

    QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
    fmt.setMargin(margin);
    doc->rootFrame()->setFrameFormat(fmt);

    QRectF pageRect(printer->pageRect());
    QRectF body = QRectF(0, 0, pageRect.width(), pageRect.height());
    QFontMetrics fontMetrics(doc->defaultFont(), p.device());

    QRectF titleBox(margin,
                    body.top() + margin
                    - fontMetrics.height()
                    - 6 * dpiy / 72.0,
                    body.width() - 2*margin,
                    fontMetrics.height());
    doc->setPageSize(body.size());

    int docCopies;
    int pageCopies;
hjk's avatar
hjk committed
1000
    if (printer->collateCopies() == true) {
con's avatar
con committed
1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059
        docCopies = 1;
        pageCopies = printer->numCopies();
    } else {
        docCopies = printer->numCopies();
        pageCopies = 1;
    }

    int fromPage = printer->fromPage();
    int toPage = printer->toPage();
    bool ascending = true;

    if (fromPage == 0 && toPage == 0) {
        fromPage = 1;
        toPage = doc->pageCount();
    }
    // paranoia check
    fromPage = qMax(1, fromPage);
    toPage = qMin(doc->pageCount(), toPage);

    if (printer->pageOrder() == QPrinter::LastPageFirst) {
        int tmp = fromPage;
        fromPage = toPage;
        toPage = tmp;
        ascending = false;
    }

    for (int i = 0; i < docCopies; ++i) {

        int page = fromPage;
        while (true) {
            for (int j = 0; j < pageCopies; ++j) {
                if (printer->printerState() == QPrinter::Aborted
                    || printer->printerState() == QPrinter::Error)
                    goto UserCanceled;
                printPage(page, &p, doc, body, titleBox, title);
                if (j < pageCopies - 1)
                    printer->newPage();
            }

            if (page == toPage)
                break;

            if (ascending)
                ++page;
            else
                --page;

            printer->newPage();
        }

        if ( i < docCopies - 1)
            printer->newPage();
    }

UserCanceled:
    delete doc;
}


1060
int TextEditorWidgetPrivate::visualIndent(const QTextBlock &block) const
1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076
{
    if (!block.isValid())
        return 0;
    const QTextDocument *document = block.document();
    int i = 0;
    while (i < block.length()) {
        if (!document->characterAt(block.position() + i).isSpace()) {
            QTextCursor cursor(block);
            cursor.setPosition(block.position() + i);
            return q->cursorRect(cursor).x();
        }
        ++i;
    }

    return 0;
}
con's avatar
con committed
1077

1078 1079 1080
void TextEditorWidgetPrivate::updateAutoCompleteHighlight()
{
    const QTextCharFormat &matchFormat
1081
            = q->textDocument()->fontSettings().toTextCharFormat(C_AUTOCOMPLETE);
1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092

    QList<QTextEdit::ExtraSelection> extraSelections;
    for (QTextCursor cursor : Utils::asConst(m_autoCompleteHighlightPos)) {
        QTextEdit::ExtraSelection sel;
        sel.cursor = cursor;
        sel.format.setBackground(matchFormat.background());
        extraSelections.append(sel);
    }
    q->setExtraSelections(TextEditorWidget::AutoCompleteSelection, extraSelections);
}

1093
void TextEditorWidget::selectEncoding()
con's avatar
con committed
1094
{
1095
    TextDocument *doc = d->m_document.data();
con's avatar
con committed
1096 1097 1098
    CodecSelector codecSelector(this, doc);

    switch (codecSelector.exec()) {
1099 1100 1101 1102 1103 1104 1105
    case CodecSelector::Reload: {
        QString errorString;
        if (!doc->reload(&errorString, codecSelector.selectedCodec())) {
            QMessageBox::critical(this, tr("File Error"), errorString);
            break;
        }
        break; }
con's avatar
con committed
1106 1107
    case CodecSelector::Save:
        doc->setCodec(codecSelector.selectedCodec());
1108
        EditorManager::saveDocument(textDocument());
1109
        updateTextCodecLabel();
con's avatar
con committed
1110 1111 1112 1113 1114 1115
        break;
    case CodecSelector::Cancel:
        break;
    }
}

1116
void TextEditorWidget::updateTextCodecLabel()
1117
{
1118 1119
    QString text = QString::fromLatin1(d->m_document->codec()->name());
    d->m_fileEncodingLabel->setText(text, text);
1120 1121
}

1122
QString TextEditorWidget::msgTextTooLarge(quint64 size)
1123 1124 1125 1126 1127
{
    return tr("The text is too large to be displayed (%1 MB).").
           arg(size >> 20);
}

1128
void TextEditorWidget::insertPlainText(const QString &text)
1129 1130 1131 1132 1133 1134 1135
{
    if (d->m_inBlockSelectionMode)
        d->insertIntoBlockSelection(text);
    else
        QPlainTextEdit::insertPlainText(text);
}

1136
QString TextEditorWidget::selectedText() const
1137 1138 1139 1140 1141 1142 1143
{
    if (d->m_inBlockSelectionMode)
        return d->copyBlockSelection();
    else
        return textCursor().selectedText();
}

1144
void TextEditorWidgetPrivate::updateCannotDecodeInfo()
1145
{
1146
    q->setReadOnly(m_document->hasDecodingError());
1147 1148
    InfoBar *infoBar = m_document->infoBar();
    Id selectEncodingId(Constants::SELECT_ENCODING);
1149
    if (m_document->hasDecodingError()) {
1150 1151
        if (!infoBar->canInfoBeAdded(selectEncodingId))
            return;
1152
        InfoBarEntry info(selectEncodingId,
1153
            TextEditorWidget::tr("<b>Error:</b> Could not decode \"%1\" with \"%2\"-encoding. Editing not possible.")
1154
            .arg(m_document->displayName()).arg(QString::fromLatin1(m_document->codec()->name())));
1155
        info.setCustomButtonInfo(TextEditorWidget::tr("Select Encoding"), [this]() { q->selectEncoding(); });
1156
        infoBar->addInfo(info);
1157
    } else {
1158
        infoBar->removeInfo(selectEncodingId);
1159 1160 1161
    }
}

1162 1163 1164
/*
  Collapses the first comment in a file, if there is only whitespace above
  */
1165
void TextEditorWidgetPrivate::foldLicenseHeader()
1166 1167
{
    QTextDocument *doc = q->document();
1168
    TextDocumentLayout *documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout());
1169 1170
    QTC_ASSERT(documentLayout, return);
    QTextBlock block = doc->firstBlock();
1171
    while (block.isValid() && block.isVisible()) {
1172
        QString text = block.text();
1173
        if (TextDocumentLayout::canFold(block) && block.next().isVisible()) {
mae's avatar
mae committed
1174
            if (text.trimmed().startsWith(QLatin1String("/*"))) {
1175
                TextDocumentLayout::doFoldOrUnfold(block, false);
mae's avatar
mae committed
1176 1177 1178 1179 1180 1181
                moveCursorVisible();
                documentLayout->requestUpdate();
                documentLayout->emitDocumentSizeChanged();
                break;
            }
        }
1182
        if (TabSettings::firstNonSpace(text) < text.size())
1183 1184 1185 1186 1187
            break;
        block = block.next();
    }
}

1188
TextDocument *TextEditorWidget::textDocument() const
con's avatar
con committed
1189
{
1190
    return d->m_document.data();
con's avatar
con committed
1191 1192
}

1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205
void TextEditorWidget::aboutToOpen(const QString &fileName, const QString &realFileName)
{
    Q_UNUSED(fileName)
    Q_UNUSED(realFileName)
}

void TextEditorWidget::openFinishedSuccessfully()
{
    moveCursor(QTextCursor::Start);
    d->updateCannotDecodeInfo();
    updateTextCodecLabel();
}

1206
TextDocumentPtr TextEditorWidget::textDocumentPtr() const
1207 1208 1209 1210
{
    return d->m_document;
}

1211 1212 1213 1214 1215 1216
TextEditorWidget *TextEditorWidget::currentTextEditorWidget()
{
    BaseTextEditor *editor = qobject_cast<BaseTextEditor *>(EditorManager::currentEditor());
    return editor ? editor->editorWidget() : 0;
}

1217
void TextEditorWidgetPrivate::editorContentsChange(int position, int charsRemoved, int charsAdded)
con's avatar
con committed
1218
{
1219 1220
    if (m_bracketsAnimator)
        m_bracketsAnimator->finish();
1221

1222 1223
    m_contentsChanged = true;
    QTextDocument *doc = q->document();
1224
    TextDocumentLayout *documentLayout = static_cast<TextDocumentLayout*>(doc->documentLayout());
1225
    const QTextBlock posBlock = doc->findBlock(position);
con's avatar
con committed
1226 1227 1228

    // Keep the line numbers and the block information for the text marks updated
    if (charsRemoved != 0) {
1229
        documentLayout->updateMarksLineNumber();
1230
        documentLayout->updateMarksBlock(posBlock);
con's avatar
con committed
1231
    } else {
1232
        const QTextBlock nextBlock = doc->findBlock(position + charsAdded);
con's avatar
con committed
1233
        if (posBlock != nextBlock) {
1234 1235 1236
            documentLayout->updateMarksLineNumber();
            documentLayout->updateMarksBlock(posBlock);
            documentLayout->updateMarksBlock(nextBlock);
con's avatar
con committed
1237
        } else {
1238
            documentLayout->updateMarksBlock(posBlock);
con's avatar
con committed
1239 1240
        }
    }
1241

1242 1243
    if (m_snippetOverlay->isVisible()) {
        QTextCursor cursor = q->textCursor();
1244
        cursor.setPosition(position);
1245
        snippetCheckCursor(cursor);
1246 1247
    }

1248 1249
    if (charsAdded != 0 && q->document()->characterAt(position + charsAdded - 1).isPrint())
        m_assistRelevantContentAdded = true;
1250 1251

    int newBlockCount = doc->blockCount();
1252
    if (!q->hasFocus() && newBlockCount != m_blockCount) {
1253
        // lines were inserted or removed from outside, keep viewport on same part of text
1254 1255
        if (q->firstVisibleBlock().blockNumber() > posBlock.blockNumber())
            q->verticalScrollBar()->setValue(q->verticalScrollBar()->value() + newBlockCount - m_blockCount);
1256 1257 1258 1259 1260

        if (m_inBlockSelectionMode) {
            disableBlockSelection(CursorUpdateClearSelection);
            q->viewport()->update();
        }
1261
    }
1262
    m_blockCount = newBlockCount;
1263
    m_scrollBarUpdateTimer.start(500);
con's avatar
con committed
1264 1265
}

1266
void TextEditorWidgetPrivate::slotSelectionChanged()
con's avatar
con committed
1267
{
1268 1269
    if (!q->textCursor().hasSelection() && !m_selectBlockAnchor.isNull())
        m_selectBlockAnchor = QTextCursor();
1270
    // Clear any link which might be showing when the selection changes
1271
    clearLink();
con's avatar
con committed
1272 1273
}

1274
void TextEditorWidget::gotoBlockStart()
1275 1276
{
    QTextCursor cursor = textCursor();
1277
    if (TextBlockUserData::findPreviousOpenParenthesis(&cursor, false)) {
1278
        setTextCursor(cursor);
1279
        d->_q_matchParentheses();
1280
    }
1281 1282
}

1283
void TextEditorWidget::gotoBlockEnd()
1284 1285
{
    QTextCursor cursor = textCursor();
1286
    if (TextBlockUserData::findNextClosingParenthesis(&cursor, false)) {
1287
        setTextCursor(cursor);
1288
        d->_q_matchParentheses();
1289
    }
1290 1291
}

1292
void TextEditorWidget::gotoBlockStartWithSelection()
1293 1294
{
    QTextCursor cursor = textCursor();
1295
    if (TextBlockUserData::findPreviousOpenParenthesis(&cursor, true)) {
1296
        setTextCursor(cursor);
1297
        d->_q_matchParentheses();
1298
    }
1299 1300
}

1301
void TextEditorWidget::gotoBlockEndWithSelection()
1302 1303
{
    QTextCursor cursor = textCursor();
1304
    if (TextBlockUserData::findNextClosingParenthesis(&cursor, true)) {
1305
        setTextCursor(cursor);
1306
        d->_q_matchParentheses();
1307 1308 1309
    }
}

1310
void