texteditor.cpp 268 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"
Jarek Kobus's avatar
Jarek Kobus committed
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 "texteditoroverlay.h"
50
#include "refactoroverlay.h"
51
52
#include "texteditorsettings.h"
#include "typingsettings.h"
53
54
#include "extraencodingsettings.h"
#include "storagesettings.h"
55

56
#include <texteditor/codeassist/assistinterface.h>
57
58
#include <texteditor/codeassist/codeassistant.h>
#include <texteditor/codeassist/completionassistprovider.h>
59
60
61
62
63
#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
64

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

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

115
116
//#define DO_FOO

Leandro Melo's avatar
Leandro Melo committed
117
118
119
120
121
122
123
124
125
126
127
128
/*!
    \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
*/

129
130
/*!
    \class TextEditor::BaseTextEditor
131
132
133
134
135
136
    \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.
137

138
139
    It is the default editor for text files used by \QC, if no other editor
    implementation matches the MIME type.
140
141
*/

hjk's avatar
hjk committed
142

143
using namespace Core;
144
using namespace Utils;
con's avatar
con committed
145
146

namespace TextEditor {
147
namespace Internal {
con's avatar
con committed
148

149
150
enum { NExtraSelectionKinds = 12 };

151
152
153
154
155
156
157
158
159
160
161
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();
}
162

163
class TextEditorAnimator : public QObject
164
165
166
167
{
    Q_OBJECT

public:
168
    TextEditorAnimator(QObject *parent);
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187

    inline void setPosition(int position) { m_position = position; }
    inline int position() const { return m_position; }

    void setData(const QFont &f, const QPalette &pal, const QString &text);

    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:
    void updateRequest(int position, QPointF lastPos, QRectF rect);

188
private:
189
190
    void step(qreal v);

191
    QTimeLine m_timeline;
192
193
194
195
196
197
198
199
200
    qreal m_value;
    int m_position;
    QPointF m_lastDrawPos;
    QFont m_font;
    QPalette m_palette;
    QString m_text;
    QSizeF m_size;
};

201
202
203
class TextEditExtraArea : public QWidget
{
public:
204
    TextEditExtraArea(TextEditorWidget *edit)
205
206
207
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
        : 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:
239
    TextEditorWidget *textEdit;
240
241
};

hjk's avatar
hjk committed
242
243
244
class BaseTextEditorPrivate
{
public:
245
    BaseTextEditorPrivate() {}
246

hjk's avatar
hjk committed
247
    TextEditorFactoryPrivate *m_origin;
hjk's avatar
hjk committed
248
249
};

250
class TextEditorWidgetPrivate : public QObject
251
252
{
public:
253
    TextEditorWidgetPrivate(TextEditorWidget *parent);
254
    ~TextEditorWidgetPrivate();
255
256
257
258
259
260

    void setupDocumentSignals();
    void updateLineSelectionColor();

    void print(QPrinter *printer);

261
262
263
264
    void maybeSelectLine();
    void updateCannotDecodeInfo();
    void collectToCircularClipboard();

265
    void ctor(const QSharedPointer<TextDocument> &doc);
266
267
268
269
270
271
    void handleHomeKey(bool anchor);
    void handleBackspaceKey();
    void moveLineUpDown(bool up);
    void copyLineUpDown(bool up);
    void saveCurrentCursorPositionForNavigation();
    void updateHighlights();
272
    void updateCurrentLineInScrollbar();
273
274
275
276
277
278
279
280
281
282
283
284
285
    void updateCurrentLineHighlight();

    void drawFoldingMarker(QPainter *painter, const QPalette &pal,
                           const QRect &rect,
                           bool expanded,
                           bool active,
                           bool hovered) const;

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

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

286
287
    void requestUpdateLink(QMouseEvent *e, bool immediate = false);
    void updateLink();
288
    void showLink(const TextEditorWidget::Link &);
289
290
291
292
293
294
295
296
297
298
    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);

299
300
    void transformSelection(TransformationMethod method);
    void transformBlockSelection(TransformationMethod method);
301

302
303
304
305
306
307
308
309
310
    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);
311
    void highlightSearchResultsSlot(const QString &txt, FindFlags findFlags);
312
313
314
315
316
317
318
319
320
321
322
323
324
    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();

325
326
    void setFindScope(const QTextCursor &start, const QTextCursor &end, int, int);

327
328
    void updateCursorPosition();

329
330
331
332
333
334
335
    // parentheses matcher
    void _q_matchParentheses();
    void _q_highlightBlocks();
    void slotSelectionChanged();
    void _q_animateUpdate(int position, QPointF lastPos, QRectF rect);
    void updateCodeFoldingVisible();

336
337
    void reconfigure();

338
public:
339
    TextEditorWidget *q;
340
341
    QToolBar *m_toolBar;
    QWidget *m_stretchWidget;
342
343
    LineColumnLabel *m_cursorPositionLabel;
    LineColumnLabel *m_fileEncodingLabel;
344
345
346
    QAction *m_cursorPositionLabelAction;
    QAction *m_fileEncodingLabelAction;

347
348
349
    bool m_contentsChanged;
    bool m_lastCursorChangeWasInteresting;

350
    QSharedPointer<TextDocument> m_document;
351
352
353
354
355
356
357
358
359
360
361
362
    QByteArray m_tempState;
    QByteArray m_tempNavigationState;

    bool m_parenthesesMatchingEnabled;

    // parentheses matcher
    bool m_formatRange;
    QTimer m_parenthesesMatchingTimer;
    // end parentheses matcher

    QWidget *m_extraArea;

363
    Id m_tabSettingsId;
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
    ICodeStylePreferences *m_codeStylePreferences;
    DisplaySettings m_displaySettings;
    MarginSettings m_marginSettings;
    bool m_fontSettingsNeedsApply;
    BehaviorSettings m_behaviorSettings;

    int extraAreaSelectionAnchorBlockNumber;
    int extraAreaToggleMarkBlockNumber;
    int extraAreaHighlightFoldedBlockNumber;

    TextEditorOverlay *m_overlay;
    TextEditorOverlay *m_snippetOverlay;
    TextEditorOverlay *m_searchResultOverlay;
    bool snippetCheckCursor(const QTextCursor &cursor);
    void snippetTabOrBacktab(bool forward);

    RefactorOverlay *m_refactorOverlay;
381
    QString m_contextHelpId;
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403

    QBasicTimer foldedBlockTimer;
    int visibleFoldedBlockNumber;
    int suggestedVisibleFoldedBlockNumber;
    void clearVisibleFoldedBlock();
    bool m_mouseOnFoldedMarker;
    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 autoParenthesisOverwriteBackup : 1;
    uint surroundWithEnabledOverwriteBackup : 1;
    uint m_maybeFakeTooltipEvent : 1;
    int m_visibleWrapColumn;

404
    TextEditorWidget::Link m_currentLink;
405
    bool m_linkPressed;
406
407
    QTextCursor m_pendingLinkUpdate;
    QTextCursor m_lastLinkUpdate;
408
409

    QRegExp m_searchExpr;
410
    FindFlags m_findFlags;
411
412
413
    void highlightSearchResults(const QTextBlock &block, TextEditorOverlay *overlay);
    QTimer m_delayedUpdateTimer;

414
415
    void setExtraSelections(Core::Id kind, const QList<QTextEdit::ExtraSelection> &selections);
    QHash<Core::Id, QList<QTextEdit::ExtraSelection>> m_extraSelections;
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439

    // block selection mode
    bool m_inBlockSelectionMode;
    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);
    void disableBlockSelection(bool keepSelection = true);
    void resetCursorFlashTimer();
    QBasicTimer m_cursorFlashTimer;
    bool m_cursorVisible;
    bool m_moveLineUndoHack;

    QTextCursor m_findScopeStart;
    QTextCursor m_findScopeEnd;
    int m_findScopeVerticalBlockSelectionFirstColumn;
    int m_findScopeVerticalBlockSelectionLastColumn;

    QTextCursor m_selectBlockAnchor;

440
    TextBlockSelection m_blockSelection;
441
442
443
444

    void moveCursorVisible(bool ensureVisible = true);

    int visualIndent(const QTextBlock &block) const;
445
    TextEditorPrivateHighlightBlocks m_highlightBlocksInfo;
446
447
    QTimer m_highlightBlocksTimer;

448
    CodeAssistant m_codeAssistant;
449
    bool m_assistRelevantContentAdded;
450
    QList<BaseHoverHandler *> m_hoverHandlers; // Not owned
451

452
    QPointer<TextEditorAnimator> m_animator;
453
454
455
456
457
458
    int m_cursorBlockNumber;
    int m_blockCount;

    QPoint m_markDragStart;
    bool m_markDragging;

459
    QScopedPointer<ClipboardAssistProvider> m_clipboardAssistProvider;
460
461

    bool m_isMissingSyntaxDefinition;
462
463

    QScopedPointer<AutoCompleter> m_autoCompleter;
464
    CommentDefinition m_commentDefinition;
465
466
467
468
469
470

    QFutureWatcher<FileSearchResultList> *m_searchWatcher;
    QVector<SearchResult> m_searchResults;
    QTimer m_scrollBarUpdateTimer;
    HighlightScrollBar *m_highlightScrollBar;
    bool m_scrollBarUpdateScheduled;
471
472
};

473
TextEditorWidgetPrivate::TextEditorWidgetPrivate(TextEditorWidget *parent)
474
  : q(parent),
475
476
477
478
479
480
    m_toolBar(0),
    m_stretchWidget(0),
    m_cursorPositionLabel(0),
    m_fileEncodingLabel(0),
    m_cursorPositionLabelAction(0),
    m_fileEncodingLabelAction(0),
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
    m_contentsChanged(false),
    m_lastCursorChangeWasInteresting(false),
    m_parenthesesMatchingEnabled(false),
    m_formatRange(false),
    m_parenthesesMatchingTimer(0),
    m_extraArea(0),
    m_codeStylePreferences(0),
    m_fontSettingsNeedsApply(true), // apply when making visible the first time, for the split case
    extraAreaSelectionAnchorBlockNumber(-1),
    extraAreaToggleMarkBlockNumber(-1),
    extraAreaHighlightFoldedBlockNumber(-1),
    m_overlay(0),
    m_snippetOverlay(0),
    m_searchResultOverlay(0),
    m_refactorOverlay(0),
    visibleFoldedBlockNumber(-1),
    suggestedVisibleFoldedBlockNumber(-1),
    m_mouseOnFoldedMarker(false),
    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),
    m_visibleWrapColumn(0),
    m_linkPressed(false),
    m_delayedUpdateTimer(0),
    m_inBlockSelectionMode(false),
    m_moveLineUndoHack(false),
    m_findScopeVerticalBlockSelectionFirstColumn(-1),
    m_findScopeVerticalBlockSelectionLastColumn(-1),
    m_highlightBlocksTimer(0),
    m_assistRelevantContentAdded(false),
    m_cursorBlockNumber(-1),
    m_blockCount(0),
    m_markDragging(false),
520
    m_clipboardAssistProvider(new ClipboardAssistProvider),
521
    m_isMissingSyntaxDefinition(false),
522
523
524
525
526
    m_autoCompleter(new AutoCompleter),
    m_searchWatcher(0),
    m_scrollBarUpdateTimer(0),
    m_highlightScrollBar(0),
    m_scrollBarUpdateScheduled(false)
527
528
529
{
    Aggregation::Aggregate *aggregate = new Aggregation::Aggregate;
    BaseTextFind *baseTextFind = new BaseTextFind(q);
hjk's avatar
hjk committed
530
    connect(baseTextFind, &BaseTextFind::highlightAllRequested,
531
            this, &TextEditorWidgetPrivate::highlightSearchResultsSlot);
532
    connect(baseTextFind, &BaseTextFind::findScopeChanged,
533
            this, &TextEditorWidgetPrivate::setFindScope);
534
535
    aggregate->add(baseTextFind);
    aggregate->add(q);
536

537
538
539
    m_extraArea = new TextEditExtraArea(q);
    m_extraArea->setMouseTracking(true);

540
541
542
543
544
545
    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);

546
    m_cursorPositionLabel = new LineColumnLabel;
547
548
549
    const int spacing = q->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing) / 2;
    m_cursorPositionLabel->setContentsMargins(spacing, 0, spacing, 0);

550
    m_fileEncodingLabel = new LineColumnLabel;
551
552
553
554
    m_fileEncodingLabel->setContentsMargins(spacing, 0, spacing, 0);

    m_cursorPositionLabelAction = m_toolBar->addWidget(m_cursorPositionLabel);
    m_fileEncodingLabelAction = m_toolBar->addWidget(m_fileEncodingLabel);
555
    m_extraSelections.reserve(NExtraSelectionKinds);
556
}
557

558
559
560
561
562
563
TextEditorWidgetPrivate::~TextEditorWidgetPrivate()
{
    q->disconnect(this);
    delete m_toolBar;
}

564
} // namespace Internal
hjk's avatar
hjk committed
565
566

using namespace Internal;
con's avatar
con committed
567

568
569
570
571
/*!
 * 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.
 */
572
static void updateEditorInfoBar(TextEditorWidget *widget)
573
574
575
576
577
578
579
580
581
582
{
    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);
583
        info.setCustomButtonInfo(BaseTextEditor::tr("Show Highlighter Options..."), [widget]() {
584
            ICore::showOptionsDialog(Constants::TEXT_EDITOR_HIGHLIGHTER_SETTINGS, widget);
585
586
        });

587
588
589
590
        infoBar->addInfo(info);
    }
}

591
QString TextEditorWidget::plainTextFromSelection(const QTextCursor &cursor) const
592
{
593
594
595
596
597
    // Copy the selected text as plain text
    QString text = cursor.selectedText();
    return convertToPlainText(text);
}

598
QString TextEditorWidget::convertToPlainText(const QString &txt)
599
600
601
602
{
    QString ret = txt;
    QChar *uc = ret.data();
    QChar *e = uc + ret.size();
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618

    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:
            ;
        }
    }
619
    return ret;
620
621
}

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

624
625
626
627
628
629
630
631
632
633
634
635
Id TextEditorWidget::SnippetPlaceholderSelection("TextEdit.SnippetPlaceHolderSelection");
Id TextEditorWidget::CurrentLineSelection("TextEdit.CurrentLineSelection");
Id TextEditorWidget::ParenthesesMatchingSelection("TextEdit.ParenthesesMatchingSelection");
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");

636
TextEditorWidget::TextEditorWidget(QWidget *parent)
637
638
    : QPlainTextEdit(parent)
{
639
640
641
    // "Needed", as the creation below triggers ChildEvents that are
    // passed to this object's event() which uses 'd'.
    d = 0;
642
    d = new TextEditorWidgetPrivate(this);
643
644
}

645
void TextEditorWidget::setTextDocument(const QSharedPointer<TextDocument> &doc)
646
{
647
    d->ctor(doc);
648
649
}

650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
void TextEditorWidgetPrivate::setupScrollBar()
{
    if (m_displaySettings.m_scrollBarHighlights) {
        if (m_highlightScrollBar)
            return;
        m_highlightScrollBar = new HighlightScrollBar(Qt::Vertical, q);
        m_highlightScrollBar->setColor(Constants::SCROLL_BAR_SEARCH_RESULT,
                                       Theme::TextEditor_SearchResult_ScrollBarColor);
        m_highlightScrollBar->setColor(Constants::SCROLL_BAR_CURRENT_LINE,
                                       Theme::TextEditor_CurrentLine_ScrollBarColor);
        m_highlightScrollBar->setPriority(
                    Constants::SCROLL_BAR_SEARCH_RESULT, HighlightScrollBar::HighPriority);
        m_highlightScrollBar->setPriority(
                    Constants::SCROLL_BAR_CURRENT_LINE, HighlightScrollBar::HighestPriority);
        q->setVerticalScrollBar(m_highlightScrollBar);
        highlightSearchResultsInScrollBar();
        scheduleUpdateHighlightScrollBar();
    } else if (m_highlightScrollBar) {
        q->setVerticalScrollBar(new QScrollBar(Qt::Vertical, q));
        m_highlightScrollBar = 0;
    }
}

673
void TextEditorWidgetPrivate::ctor(const QSharedPointer<TextDocument> &doc)
con's avatar
con committed
674
{
675
    q->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
con's avatar
con committed
676

677
678
679
680
    m_overlay = new TextEditorOverlay(q);
    m_snippetOverlay = new TextEditorOverlay(q);
    m_searchResultOverlay = new TextEditorOverlay(q);
    m_refactorOverlay = new RefactorOverlay(q);
681

682
683
    m_document = doc;
    setupDocumentSignals();
con's avatar
con committed
684
685
686

    // from RESEARCH

687
688
    q->setLayoutDirection(Qt::LeftToRight);
    q->viewport()->setMouseTracking(true);
con's avatar
con committed
689

690
691
692
693
694
    extraAreaSelectionAnchorBlockNumber = -1;
    extraAreaToggleMarkBlockNumber = -1;
    extraAreaHighlightFoldedBlockNumber = -1;
    visibleFoldedBlockNumber = -1;
    suggestedVisibleFoldedBlockNumber = -1;
con's avatar
con committed
695

696
    QObject::connect(&m_codeAssistant, &CodeAssistant::finished,
697
                     q, &TextEditorWidget::assistFinished);
698

699
    QObject::connect(q, &QPlainTextEdit::blockCountChanged,
700
                     this, &TextEditorWidgetPrivate::slotUpdateExtraAreaWidth);
701

702
    QObject::connect(q, &QPlainTextEdit::modificationChanged, m_extraArea,
703
704
705
                     static_cast<void (QWidget::*)()>(&QWidget::update));

    QObject::connect(q, &QPlainTextEdit::cursorPositionChanged,
706
                     q, &TextEditorWidget::slotCursorPositionChanged);
707

708
    QObject::connect(q, &QPlainTextEdit::cursorPositionChanged,
709
                     this, &TextEditorWidgetPrivate::updateCursorPosition);
710

711
    QObject::connect(q, &QPlainTextEdit::updateRequest,
712
                     this, &TextEditorWidgetPrivate::slotUpdateRequest);
713
714

    QObject::connect(q, &QPlainTextEdit::selectionChanged,
715
                     this, &TextEditorWidgetPrivate::slotSelectionChanged);
con's avatar
con committed
716
717
718
719
720

//     (void) new QShortcut(tr("CTRL+L"), this, SLOT(centerCursor()), 0, Qt::WidgetShortcut);
//     (void) new QShortcut(tr("F9"), this, SLOT(slotToggleMark()), 0, Qt::WidgetShortcut);
//     (void) new QShortcut(tr("F11"), this, SLOT(slotToggleBlockVisible()));

721
#ifdef DO_FOO
722
    (void) new QShortcut(TextEditorWidget::tr("CTRL+D"), this, SLOT(doFoo()));
723
724
#endif

con's avatar
con committed
725
    // parentheses matcher
726
727
    m_formatRange = true;
    m_parenthesesMatchingTimer.setSingleShot(true);
728
    QObject::connect(&m_parenthesesMatchingTimer, &QTimer::timeout,
729
                     this, &TextEditorWidgetPrivate::_q_matchParentheses);
con's avatar
con committed
730

731
    m_highlightBlocksTimer.setSingleShot(true);
732
    QObject::connect(&m_highlightBlocksTimer, &QTimer::timeout,
733
                     this, &TextEditorWidgetPrivate::_q_highlightBlocks);
734

735
736
737
738
    m_scrollBarUpdateTimer.setSingleShot(true);
    QObject::connect(&m_scrollBarUpdateTimer, &QTimer::timeout,
                     this, &TextEditorWidgetPrivate::highlightSearchResultsInScrollBar);

739
    m_animator = 0;
con's avatar
con committed
740

741
    slotUpdateExtraAreaWidth();
con's avatar
con committed
742
    updateHighlights();
743
    q->setFrameStyle(QFrame::NoFrame);
con's avatar
con committed
744

745
    m_delayedUpdateTimer.setSingleShot(true);
746
747
    QObject::connect(&m_delayedUpdateTimer, &QTimer::timeout, q->viewport(),
                     static_cast<void (QWidget::*)()>(&QWidget::update));
748

749
    m_moveLineUndoHack = false;
750
751
752

    updateCannotDecodeInfo();

753
754
755
756
    QObject::connect(m_document.data(), &TextDocument::aboutToOpen,
                     q, &TextEditorWidget::aboutToOpen);
    QObject::connect(m_document.data(), &TextDocument::openFinishedSuccessfully,
                     q, &TextEditorWidget::openFinishedSuccessfully);
757
758
759
760
761
    connect(m_fileEncodingLabel, &LineColumnLabel::clicked,
            q, &TextEditorWidget::selectEncoding);
    connect(m_document->document(), &QTextDocument::modificationChanged,
            q, &TextEditorWidget::updateTextCodecLabel);
    q->updateTextCodecLabel();
con's avatar
con committed
762
763
}

764
TextEditorWidget::~TextEditorWidget()
con's avatar
con committed
765
766
767
768
769
{
    delete d;
    d = 0;
}

770
void TextEditorWidget::print(QPrinter *printer)
con's avatar
con committed
771
{
hjk's avatar
hjk committed
772
    const bool oldFullPage = printer->fullPage();
con's avatar
con committed
773
774
775
    printer->setFullPage(true);
    QPrintDialog *dlg = new QPrintDialog(printer, this);
    dlg->setWindowTitle(tr("Print Document"));
776
    if (dlg->exec() == QDialog::Accepted)
con's avatar
con committed
777
778
779
780
781
        d->print(printer);
    printer->setFullPage(oldFullPage);
    delete dlg;
}

mae's avatar
mae committed
782
static int foldBoxWidth(const QFontMetrics &fm)
783
784
{
    const int lineSpacing = fm.lineSpacing();
hjk's avatar
hjk committed
785
    return lineSpacing + lineSpacing % 2 + 1;
786
787
}

con's avatar
con committed
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
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());
    QRectF view(0, (index - 1) * body.height(), body.width(), body.height());

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

    painter->setFont(QFont(doc->defaultFont()));
    QRectF box = titleBox.translated(0, view.top());
    int dpix = painter->device()->logicalDpiX();
    int dpiy = painter->device()->logicalDpiY();
    int mx = 5 * dpix / 72.0;
    int my = 2 * dpiy / 72.0;
    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();
}

824
void TextEditorWidgetPrivate::print(QPrinter *printer)
con's avatar
con committed
825
826
827
{
    QTextDocument *doc = q->document();

828
    QString title = m_document->displayName();
829
    if (!title.isEmpty())
con's avatar
con committed
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
        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()) {


        QList<QTextLayout::FormatRange> formatList = srcBlock.layout()->additionalFormats();
        if (backgroundIsDark) {
            // adjust syntax highlighting colors for better contrast
hjk's avatar
hjk committed
859
            for (int i = formatList.count() - 1; i >= 0; --i) {
con's avatar
con committed
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
                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);
            }
        }

        dstBlock.layout()->setAdditionalFormats(formatList);
    }

    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
901
    if (printer->collateCopies() == true) {
con's avatar
con committed
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
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
955
956
957
958
959
960
        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;
}


961
int TextEditorWidgetPrivate::visualIndent(const QTextBlock &block) const
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
{
    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
978

979
void TextEditorWidget::selectEncoding()
con's avatar
con committed
980
{
981
    TextDocument *doc = d->m_document.data();
con's avatar
con committed
982
983
984
    CodecSelector codecSelector(this, doc);

    switch (codecSelector.exec()) {
985
986
987
988
989
990
991
    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
992
993
    case CodecSelector::Save:
        doc->setCodec(codecSelector.selectedCodec());
994
        EditorManager::saveDocument(textDocument());
995
        updateTextCodecLabel();
con's avatar
con committed
996
997
998
999
1000
1001
        break;
    case CodecSelector::Cancel:
        break;
    }
}

1002
void TextEditorWidget::updateTextCodecLabel()
1003
{
1004
1005
    QString text = QString::fromLatin1(d->m_document->codec()->name());
    d->m_fileEncodingLabel->setText(text, text);
1006
1007
}

1008
QString TextEditorWidget::msgTextTooLarge(quint64 size)
1009
1010
1011
1012
1013
{
    return tr("The text is too large to be displayed (%1 MB).").
           arg(size >> 20);
}

1014
void TextEditorWidget::insertPlainText(const QString &text)
David Schulz's avatar
David Schulz committed
1015
1016
1017
1018
1019
1020
1021
{
    if (d->m_inBlockSelectionMode)
        d->insertIntoBlockSelection(text);
    else
        QPlainTextEdit::insertPlainText(text);
}

1022
QString TextEditorWidget::selectedText() const
David Schulz's avatar
David Schulz committed
1023
1024
1025
1026
1027
1028
1029
{
    if (d->m_inBlockSelectionMode)
        return d->copyBlockSelection();
    else
        return textCursor().selectedText();
}

1030
void TextEditorWidgetPrivate::updateCannotDecodeInfo()
1031
{
1032
    q->setReadOnly(m_document->hasDecodingError());
1033
1034
    InfoBar *infoBar = m_document->infoBar();
    Id selectEncodingId(Constants::SELECT_ENCODING);
1035
    if (m_document->hasDecodingError()) {
1036
1037
        if (!infoBar->canInfoBeAdded(selectEncodingId))
            return;
1038
        InfoBarEntry info(selectEncodingId,
1039
            TextEditorWidget::tr("<b>Error:</b> Could not decode \"%1\" with \"%2\"-encoding. Editing not possible.")
1040
            .arg(m_document->displayName()).arg(QString::fromLatin1(m_document->codec()->name())));
1041
        info.setCustomButtonInfo(TextEditorWidget::tr("Select Encoding"), [this]() { q->selectEncoding(); });
1042
        infoBar->addInfo(info);
1043
    } else {
1044
        infoBar->removeInfo(selectEncodingId);
1045
1046
1047
    }
}

1048
1049
1050
/*
  Collapses the first comment in a file, if there is only whitespace above
  */
1051
void TextEditorWidgetPrivate::foldLicenseHeader()
1052
1053
{
    QTextDocument *doc = q->document();
1054
    TextDocumentLayout *documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout());
1055
1056
    QTC_ASSERT(documentLayout, return);
    QTextBlock block = doc->firstBlock();
1057
    while (block.isValid() && block.isVisible()) {
1058
        QString text = block.text();
1059
        if (TextDocumentLayout::canFold(block) && block.next().isVisible()) {
mae's avatar
mae committed
1060
            if (text.trimmed().startsWith(QLatin1String("/*"))) {
1061
                TextDocumentLayout::doFoldOrUnfold(block, false);
mae's avatar
mae committed
1062
1063
1064
1065
1066
1067
                moveCursorVisible();
                documentLayout->requestUpdate();
                documentLayout->emitDocumentSizeChanged();
                break;
            }
        }
1068
        if (TabSettings::firstNonSpace(text) < text.size())
1069
1070
1071
1072
1073
            break;
        block = block.next();
    }
}

1074
TextDocument *TextEditorWidget::textDocument() const
con's avatar
con committed
1075
{
1076
    return d->m_document.data();
con's avatar
con committed
1077
1078
}

1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
void TextEditorWidget::aboutToOpen(const QString &fileName, const QString &realFileName)
{
    Q_UNUSED(fileName)
    Q_UNUSED(realFileName)
}

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

1092
TextDocumentPtr TextEditorWidget::textDocumentPtr() const
1093
1094
1095
1096
{
    return d->m_document;
}

1097
1098
1099
1100
1101
1102
TextEditorWidget *TextEditorWidget::currentTextEditorWidget()
{
    BaseTextEditor *editor = qobject_cast<BaseTextEditor *>(EditorManager::currentEditor());
    return editor ? editor->editorWidget() : 0;
}

1103
void TextEditorWidgetPrivate::editorContentsChange(int position, int charsRemoved, int charsAdded)
con's avatar
con committed
1104
{
1105
1106
    if (m_animator)
        m_animator->finish();
1107

1108
1109
    m_contentsChanged = true;
    QTextDocument *doc = q->document();
1110
    TextDocumentLayout *documentLayout = static_cast<TextDocumentLayout*>(doc->documentLayout());
1111
    const QTextBlock posBlock = doc->findBlock(position);
con's avatar
con committed
1112
1113
1114

    // Keep the line numbers and the block information for the text marks updated
    if (charsRemoved != 0) {
1115
        documentLayout->updateMarksLineNumber();
1116
        documentLayout->updateMarksBlock(posBlock);
con's avatar
con committed
1117
    } else {
1118
        const QTextBlock nextBlock = doc->findBlock(position + charsAdded);
con's avatar
con committed
1119
        if (posBlock != nextBlock) {
1120
1121
1122
            documentLayout->updateMarksLineNumber();
            documentLayout->updateMarksBlock(posBlock);
            documentLayout->updateMarksBlock(nextBlock);
con's avatar
con committed
1123
        } else {
1124
            documentLayout->updateMarksBlock(posBlock);
con's avatar
con committed
1125
1126
        }
    }
1127

1128
1129
    if (m_snippetOverlay->isVisible()) {
        QTextCursor cursor = q->textCursor();
1130
        cursor.setPosition(position);
1131
        snippetCheckCursor(cursor);
1132
1133
    }

1134
1135
    if (charsAdded != 0 && q->document()->characterAt(position + charsAdded - 1).isPrint())
        m_assistRelevantContentAdded = true;
1136
1137

    int newBlockCount = doc->blockCount();
1138
    if (!q->hasFocus() && newBlockCount != m_blockCount) {
1139
        // lines were inserted or removed from outside, keep viewport on same part of text
1140
1141
        if (q->firstVisibleBlock().blockNumber() > posBlock.blockNumber())
            q->verticalScrollBar()->setValue(q->verticalScrollBar()->value() + newBlockCount - m_blockCount);
1142
    }
1143
    m_blockCount = newBlockCount;
1144
    m_scrollBarUpdateTimer.start(500);
con's avatar
con committed
1145
1146
}

1147
void TextEditorWidgetPrivate::slotSelectionChanged()
con's avatar
con committed
1148
{
1149
1150
    if (!q->textCursor().hasSelection() && !m_selectBlockAnchor.isNull())
        m_selectBlockAnchor = QTextCursor();
1151
    // Clear any link which might be showing when the selection changes
1152
    clearLink();
con's avatar
con committed
1153
1154
}

1155
void TextEditorWidget::gotoBlockStart()
1156
1157
{
    QTextCursor cursor = textCursor();
1158
    if (TextBlockUserData::findPreviousOpenParenthesis(&cursor, false)) {