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

30
31
#include "basetexteditor.h"
#include "basetexteditor_p.h"
hjk's avatar
hjk committed
32

con's avatar
con committed
33
#include "basetextdocument.h"
34
#include "basetextdocumentlayout.h"
35
#include "behaviorsettings.h"
con's avatar
con committed
36
#include "codecselector.h"
37
#include "completionsettings.h"
38
#include "snippets/snippet.h"
39
#include "tabsettings.h"
Jarek Kobus's avatar
Jarek Kobus committed
40
41
#include "typingsettings.h"
#include "icodestylepreferences.h"
42
#include "syntaxhighlighter.h"
43
#include "indenter.h"
44
#include "autocompleter.h"
Leandro Melo's avatar
Leandro Melo committed
45
#include "convenience.h"
46
#include "texteditorsettings.h"
47
#include "texteditoroverlay.h"
48
#include "circularclipboard.h"
49
#include "circularclipboardassist.h"
50
51
#include <texteditor/codeassist/codeassistant.h>
#include <texteditor/codeassist/defaultassistinterface.h>
con's avatar
con committed
52

53
#include <aggregation/aggregate.h>
54
#include <coreplugin/actionmanager/actionmanager.h>
55
#include <coreplugin/actionmanager/actioncontainer.h>
con's avatar
con committed
56
#include <coreplugin/coreconstants.h>
57
#include <coreplugin/editormanager/editormanager.h>
58
#include <coreplugin/infobar.h>
59
#include <coreplugin/manhattanstyle.h>
60
#include <coreplugin/find/basetextfind.h>
con's avatar
con committed
61
#include <utils/linecolumnlabel.h>
62
#include <utils/hostosinfo.h>
hjk's avatar
hjk committed
63
#include <utils/qtcassert.h>
64
#include <utils/stylehelper.h>
65
66
#include <utils/tooltip/tooltip.h>
#include <utils/tooltip/tipcontents.h>
67
#include <utils/uncommentselection.h>
con's avatar
con committed
68

69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <QCoreApplication>
#include <QTextCodec>
#include <QDebug>
#include <QTimer>
#include <QTimeLine>
#include <QMimeData>
#include <QAbstractTextDocumentLayout>
#include <QApplication>
#include <QKeyEvent>
#include <QPainter>
#include <QPrinter>
#include <QPrintDialog>
#include <QScrollBar>
#include <QShortcut>
#include <QStyle>
#include <QTextCursor>
#include <QTextDocumentFragment>
#include <QTextBlock>
#include <QTextLayout>
#include <QToolBar>
#include <QMenu>
#include <QMessageBox>
#include <QClipboard>
con's avatar
con committed
92

93
94
//#define DO_FOO

Leandro Melo's avatar
Leandro Melo committed
95
96
97
98
99
100
101
102
103
104
105
106
/*!
    \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
*/

107
108
109
110
111
112
113
114
/*!
    \class TextEditor::BaseTextEditor
    \brief The BaseTextEditor class is a base class for QPlainTextEdit-based text editors.

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

115
using namespace Core;
116
using namespace Utils;
con's avatar
con committed
117
118

namespace TextEditor {
119
namespace Internal {
con's avatar
con committed
120

hjk's avatar
hjk committed
121
122
class TextEditExtraArea : public QWidget
{
con's avatar
con committed
123
public:
hjk's avatar
hjk committed
124
125
126
    TextEditExtraArea(BaseTextEditorWidget *edit)
        : QWidget(edit)
    {
con's avatar
con committed
127
128
129
130
        textEdit = edit;
        setAutoFillBackground(true);
    }

hjk's avatar
hjk committed
131
protected:
con's avatar
con committed
132
133
134
    QSize sizeHint() const {
        return QSize(textEdit->extraAreaWidth(), 0);
    }
hjk's avatar
hjk committed
135
    void paintEvent(QPaintEvent *event) {
con's avatar
con committed
136
137
        textEdit->extraAreaPaintEvent(event);
    }
hjk's avatar
hjk committed
138
    void mousePressEvent(QMouseEvent *event) {
con's avatar
con committed
139
140
        textEdit->extraAreaMouseEvent(event);
    }
hjk's avatar
hjk committed
141
    void mouseMoveEvent(QMouseEvent *event) {
con's avatar
con committed
142
143
        textEdit->extraAreaMouseEvent(event);
    }
hjk's avatar
hjk committed
144
    void mouseReleaseEvent(QMouseEvent *event) {
con's avatar
con committed
145
146
        textEdit->extraAreaMouseEvent(event);
    }
hjk's avatar
hjk committed
147
    void leaveEvent(QEvent *event) {
con's avatar
con committed
148
149
        textEdit->extraAreaLeaveEvent(event);
    }
150
151
152
    void contextMenuEvent(QContextMenuEvent *event) {
        textEdit->extraAreaContextMenuEvent(event);
    }
con's avatar
con committed
153
154
155
156

    void wheelEvent(QWheelEvent *event) {
        QCoreApplication::sendEvent(textEdit->viewport(), event);
    }
hjk's avatar
hjk committed
157
158
159

private:
    BaseTextEditorWidget *textEdit;
con's avatar
con committed
160
161
};

162
} // namespace Internal
hjk's avatar
hjk committed
163
164

using namespace Internal;
con's avatar
con committed
165

166
QString BaseTextEditorWidget::plainTextFromSelection(const QTextCursor &cursor) const
167
{
168
169
170
171
172
173
174
175
176
177
    // Copy the selected text as plain text
    QString text = cursor.selectedText();
    return convertToPlainText(text);
}

QString BaseTextEditorWidget::convertToPlainText(const QString &txt)
{
    QString ret = txt;
    QChar *uc = ret.data();
    QChar *e = uc + ret.size();
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193

    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:
            ;
        }
    }
194
    return ret;
195
196
}

Orgad Shaneh's avatar
Orgad Shaneh committed
197
198
static const char kTextBlockMimeType[] = "application/vnd.qtcreator.blocktext";
static const char kVerticalTextBlockMimeType[] = "application/vnd.qtcreator.vblocktext";
199

200
BaseTextEditorWidget::BaseTextEditorWidget(QWidget *parent)
con's avatar
con committed
201
    : QPlainTextEdit(parent)
202
203
204
205
206
207
208
209
210
211
{
    ctor(QSharedPointer<BaseTextDocument>(new BaseTextDocument));
}

BaseTextEditorWidget::BaseTextEditorWidget(BaseTextDocument *doc, QWidget *parent)
    : QPlainTextEdit(parent)
{
    ctor(QSharedPointer<BaseTextDocument>(doc));
}

212
213
214
215
216
217
BaseTextEditorWidget::BaseTextEditorWidget(BaseTextEditorWidget *other)
{
    ctor(other->d->m_document);
    d->m_revisionsVisible = other->d->m_revisionsVisible;
}

218
void BaseTextEditorWidget::ctor(const QSharedPointer<BaseTextDocument> &doc)
con's avatar
con committed
219
{
220
    d = new BaseTextEditorWidgetPrivate;
con's avatar
con committed
221
222
223
224
225
    d->q = this;
    d->m_extraArea = new TextEditExtraArea(this);
    d->m_extraArea->setMouseTracking(true);
    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);

226
    d->m_overlay = new TextEditorOverlay(this);
227
    d->m_snippetOverlay = new TextEditorOverlay(this);
228
    d->m_searchResultOverlay = new TextEditorOverlay(this);
229
    d->m_refactorOverlay = new RefactorOverlay(this);
230

231
    d->m_document = doc;
232
    d->setupDocumentSignals();
con's avatar
con committed
233
234
235
236
237
238
239

    // from RESEARCH

    setLayoutDirection(Qt::LeftToRight);
    viewport()->setMouseTracking(true);
    d->extraAreaSelectionAnchorBlockNumber
        = d->extraAreaToggleMarkBlockNumber
mae's avatar
mae committed
240
        = d->extraAreaHighlightFoldedBlockNumber
con's avatar
con committed
241
242
        = -1;

mae's avatar
mae committed
243
    d->visibleFoldedBlockNumber = d->suggestedVisibleFoldedBlockNumber = -1;
con's avatar
con committed
244

245
246
    connect(d->m_codeAssistant.data(), SIGNAL(finished()), this, SIGNAL(assistFinished()));

con's avatar
con committed
247
    connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(slotUpdateExtraAreaWidth()));
248
    connect(this, SIGNAL(modificationChanged(bool)), d->m_extraArea, SLOT(update()));
con's avatar
con committed
249
    connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(slotCursorPositionChanged()));
Robert Loehning's avatar
Robert Loehning committed
250
    connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(slotUpdateRequest(QRect,int)));
con's avatar
con committed
251
252
253
254
255
256
    connect(this, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged()));

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

257
258
259
260
#ifdef DO_FOO
    (void) new QShortcut(tr("CTRL+D"), this, SLOT(doFoo()));
#endif

con's avatar
con committed
261
262
263

    // parentheses matcher
    d->m_formatRange = true;
264
265
    d->m_mismatchFormat.setBackground(palette().color(QPalette::Base).value() < 128
                                      ? Qt::darkMagenta : Qt::magenta);
266
267
    d->m_parenthesesMatchingTimer.setSingleShot(true);
    connect(&d->m_parenthesesMatchingTimer, SIGNAL(timeout()), this, SLOT(_q_matchParentheses()));
con's avatar
con committed
268

269
270
    d->m_highlightBlocksTimer.setSingleShot(true);
    connect(&d->m_highlightBlocksTimer, SIGNAL(timeout()), this, SLOT(_q_highlightBlocks()));
271

272
    d->m_animator = 0;
con's avatar
con committed
273
274

    slotUpdateExtraAreaWidth();
con's avatar
con committed
275
    updateHighlights();
con's avatar
con committed
276
277
    setFrameStyle(QFrame::NoFrame);

278
279
    d->m_delayedUpdateTimer.setSingleShot(true);
    connect(&d->m_delayedUpdateTimer, SIGNAL(timeout()), viewport(), SLOT(update()));
280

mae's avatar
mae committed
281
    d->m_moveLineUndoHack = false;
con's avatar
con committed
282
283
}

284
BaseTextEditorWidget::~BaseTextEditorWidget()
con's avatar
con committed
285
286
287
288
289
{
    delete d;
    d = 0;
}

290
void BaseTextEditorWidget::print(QPrinter *printer)
con's avatar
con committed
291
{
hjk's avatar
hjk committed
292
    const bool oldFullPage = printer->fullPage();
con's avatar
con committed
293
294
295
    printer->setFullPage(true);
    QPrintDialog *dlg = new QPrintDialog(printer, this);
    dlg->setWindowTitle(tr("Print Document"));
296
    if (dlg->exec() == QDialog::Accepted)
con's avatar
con committed
297
298
299
300
301
        d->print(printer);
    printer->setFullPage(oldFullPage);
    delete dlg;
}

mae's avatar
mae committed
302
static int foldBoxWidth(const QFontMetrics &fm)
303
304
{
    const int lineSpacing = fm.lineSpacing();
hjk's avatar
hjk committed
305
    return lineSpacing + lineSpacing % 2 + 1;
306
307
}

con's avatar
con committed
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
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();
}

344
void BaseTextEditorWidgetPrivate::print(QPrinter *printer)
con's avatar
con committed
345
346
347
{
    QTextDocument *doc = q->document();

348
    QString title = m_document->displayName();
349
    if (!title.isEmpty())
con's avatar
con committed
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
        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
379
            for (int i = formatList.count() - 1; i >= 0; --i) {
con's avatar
con committed
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
                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
421
    if (printer->collateCopies() == true) {
con's avatar
con committed
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
        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;
}


481
int BaseTextEditorWidgetPrivate::visualIndent(const QTextBlock &block) const
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
{
    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
498

499
BaseTextEditor *BaseTextEditorWidget::editor() const
con's avatar
con committed
500
{
501
    if (!d->m_editor) {
Leandro Melo's avatar
Leandro Melo committed
502
503
        d->m_editor = const_cast<BaseTextEditorWidget *>(this)->createEditor();
        d->m_codeAssistant->configure(d->m_editor);
con's avatar
con committed
504
    }
505
    return d->m_editor;
con's avatar
con committed
506
507
508
}


509
void BaseTextEditorWidget::selectEncoding()
con's avatar
con committed
510
{
511
    BaseTextDocument *doc = d->m_document.data();
con's avatar
con committed
512
513
514
    CodecSelector codecSelector(this, doc);

    switch (codecSelector.exec()) {
515
516
517
518
519
520
521
    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
522
523
    case CodecSelector::Save:
        doc->setCodec(codecSelector.selectedCodec());
524
        Core::EditorManager::saveEditor(editor());
525
        updateTextCodecLabel();
con's avatar
con committed
526
527
528
529
530
531
        break;
    case CodecSelector::Cancel:
        break;
    }
}

532
533
534
535
536
void BaseTextEditorWidget::updateTextCodecLabel()
{
    editor()->setFileEncodingLabelText(QString::fromLatin1(d->m_document->codec()->name()));
}

537
QString BaseTextEditorWidget::msgTextTooLarge(quint64 size)
538
539
540
541
542
{
    return tr("The text is too large to be displayed (%1 MB).").
           arg(size >> 20);
}

543
544
545
void BaseTextEditorWidget::updateCannotDecodeInfo()
{
    setReadOnly(d->m_document->hasDecodingError());
546
547
    Core::InfoBar *infoBar = d->m_document->infoBar();
    Core::Id selectEncodingId(Constants::SELECT_ENCODING);
548
    if (d->m_document->hasDecodingError()) {
549
550
551
        if (!infoBar->canInfoBeAdded(selectEncodingId))
            return;
        Core::InfoBarEntry info(selectEncodingId,
552
            tr("<b>Error:</b> Could not decode \"%1\" with \"%2\"-encoding. Editing not possible.")
553
            .arg(d->m_document->displayName()).arg(QString::fromLatin1(d->m_document->codec()->name())));
554
        info.setCustomButtonInfo(tr("Select Encoding"), this, SLOT(selectEncoding()));
555
        infoBar->addInfo(info);
556
    } else {
557
        infoBar->removeInfo(selectEncodingId);
558
559
560
    }
}

561
bool BaseTextEditorWidget::open(QString *errorString, const QString &fileName, const QString &realFileName)
con's avatar
con committed
562
{
563
    if (d->m_document->open(errorString, fileName, realFileName)) {
con's avatar
con committed
564
        moveCursor(QTextCursor::Start);
565
        updateCannotDecodeInfo();
566
567
568
569
570
571
572
        if (editor()->m_fileEncodingLabel) {
            connect(editor()->m_fileEncodingLabel, SIGNAL(clicked()), this,
                    SLOT(selectEncoding()), Qt::UniqueConnection);
            connect(d->m_document->document(), SIGNAL(modificationChanged(bool)), this,
                    SLOT(updateTextCodecLabel()), Qt::UniqueConnection);
            updateTextCodecLabel();
        }
con's avatar
con committed
573
574
575
576
577
        return true;
    }
    return false;
}

578
579
580
/*
  Collapses the first comment in a file, if there is only whitespace above
  */
581
void BaseTextEditorWidgetPrivate::foldLicenseHeader()
582
583
{
    QTextDocument *doc = q->document();
584
    BaseTextDocumentLayout *documentLayout = qobject_cast<BaseTextDocumentLayout*>(doc->documentLayout());
585
586
    QTC_ASSERT(documentLayout, return);
    QTextBlock block = doc->firstBlock();
587
    while (block.isValid() && block.isVisible()) {
588
        QString text = block.text();
mae's avatar
mae committed
589
590
591
592
593
594
595
596
597
        if (BaseTextDocumentLayout::canFold(block) && block.next().isVisible()) {
            if (text.trimmed().startsWith(QLatin1String("/*"))) {
                BaseTextDocumentLayout::doFoldOrUnfold(block, false);
                moveCursorVisible();
                documentLayout->requestUpdate();
                documentLayout->emitDocumentSizeChanged();
                break;
            }
        }
598
        if (TabSettings::firstNonSpace(text) < text.size())
599
600
601
602
603
            break;
        block = block.next();
    }
}

604
BaseTextDocument *BaseTextEditorWidget::baseTextDocument() const
con's avatar
con committed
605
{
606
    return d->m_document.data();
con's avatar
con committed
607
608
}

609
void BaseTextEditorWidget::editorContentsChange(int position, int charsRemoved, int charsAdded)
con's avatar
con committed
610
{
611
612
613
    if (d->m_animator)
        d->m_animator->finish();

con's avatar
con committed
614
    d->m_contentsChanged = true;
615
    QTextDocument *doc = document();
616
    BaseTextDocumentLayout *documentLayout = static_cast<BaseTextDocumentLayout*>(doc->documentLayout());
con's avatar
con committed
617
618
619

    // Keep the line numbers and the block information for the text marks updated
    if (charsRemoved != 0) {
620
621
        documentLayout->updateMarksLineNumber();
        documentLayout->updateMarksBlock(document()->findBlock(position));
con's avatar
con committed
622
    } else {
623
624
        const QTextBlock posBlock = doc->findBlock(position);
        const QTextBlock nextBlock = doc->findBlock(position + charsAdded);
con's avatar
con committed
625
        if (posBlock != nextBlock) {
626
627
628
            documentLayout->updateMarksLineNumber();
            documentLayout->updateMarksBlock(posBlock);
            documentLayout->updateMarksBlock(nextBlock);
con's avatar
con committed
629
        } else {
630
            documentLayout->updateMarksBlock(posBlock);
con's avatar
con committed
631
632
        }
    }
633

634
635
636
    if (d->m_snippetOverlay->isVisible()) {
        QTextCursor cursor = textCursor();
        cursor.setPosition(position);
637
        d->snippetCheckCursor(cursor);
638
639
    }

640
    if (charsAdded != 0 && document()->characterAt(position + charsAdded - 1).isPrint())
Leandro Melo's avatar
Leandro Melo committed
641
        d->m_assistRelevantContentAdded = true;
con's avatar
con committed
642
643
}

644
void BaseTextEditorWidget::slotSelectionChanged()
con's avatar
con committed
645
{
mae's avatar
mae committed
646
647
648
    if (d->m_inBlockSelectionMode && !textCursor().hasSelection()) {
        d->m_inBlockSelectionMode = false;
        d->m_blockSelection.clear();
con's avatar
con committed
649
        viewport()->update();
mae's avatar
mae committed
650
651
    }

652
653
    if (!d->m_selectBlockAnchor.isNull() && !textCursor().hasSelection())
        d->m_selectBlockAnchor = QTextCursor();
654
655
656

    // Clear any link which might be showing when the selection changes
    clearLink();
con's avatar
con committed
657
658
}

659
void BaseTextEditorWidget::gotoBlockStart()
660
661
{
    QTextCursor cursor = textCursor();
662
    if (TextBlockUserData::findPreviousOpenParenthesis(&cursor, false)) {
663
        setTextCursor(cursor);
664
665
        _q_matchParentheses();
    }
666
667
}

668
void BaseTextEditorWidget::gotoBlockEnd()
669
670
{
    QTextCursor cursor = textCursor();
671
    if (TextBlockUserData::findNextClosingParenthesis(&cursor, false)) {
672
        setTextCursor(cursor);
673
674
        _q_matchParentheses();
    }
675
676
}

677
void BaseTextEditorWidget::gotoBlockStartWithSelection()
678
679
{
    QTextCursor cursor = textCursor();
680
    if (TextBlockUserData::findPreviousOpenParenthesis(&cursor, true)) {
681
        setTextCursor(cursor);
682
683
        _q_matchParentheses();
    }
684
685
}

686
void BaseTextEditorWidget::gotoBlockEndWithSelection()
687
688
{
    QTextCursor cursor = textCursor();
689
    if (TextBlockUserData::findNextClosingParenthesis(&cursor, true)) {
690
        setTextCursor(cursor);
691
692
693
694
        _q_matchParentheses();
    }
}

mae's avatar
mae committed
695

696
void BaseTextEditorWidget::gotoLineStart()
mae's avatar
mae committed
697
698
699
700
{
    handleHomeKey(false);
}

701
void BaseTextEditorWidget::gotoLineStartWithSelection()
mae's avatar
mae committed
702
703
704
705
{
    handleHomeKey(true);
}

706
void BaseTextEditorWidget::gotoLineEnd()
mae's avatar
mae committed
707
708
709
710
{
    moveCursor(QTextCursor::EndOfLine);
}

711
void BaseTextEditorWidget::gotoLineEndWithSelection()
mae's avatar
mae committed
712
713
714
715
{
    moveCursor(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
}

716
void BaseTextEditorWidget::gotoNextLine()
mae's avatar
mae committed
717
{
718
    moveCursor(QTextCursor::Down);
mae's avatar
mae committed
719
720
}

721
void BaseTextEditorWidget::gotoNextLineWithSelection()
mae's avatar
mae committed
722
{
723
    moveCursor(QTextCursor::Down, QTextCursor::KeepAnchor);
mae's avatar
mae committed
724
725
}

726
void BaseTextEditorWidget::gotoPreviousLine()
mae's avatar
mae committed
727
{
728
    moveCursor(QTextCursor::Up);
mae's avatar
mae committed
729
730
}

731
void BaseTextEditorWidget::gotoPreviousLineWithSelection()
mae's avatar
mae committed
732
{
733
    moveCursor(QTextCursor::Up, QTextCursor::KeepAnchor);
mae's avatar
mae committed
734
735
}

736
void BaseTextEditorWidget::gotoPreviousCharacter()
mae's avatar
mae committed
737
738
739
740
{
    moveCursor(QTextCursor::PreviousCharacter);
}

741
void BaseTextEditorWidget::gotoPreviousCharacterWithSelection()
mae's avatar
mae committed
742
743
744
745
{
    moveCursor(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
}

746
void BaseTextEditorWidget::gotoNextCharacter()
mae's avatar
mae committed
747
748
749
750
{
    moveCursor(QTextCursor::NextCharacter);
}

751
void BaseTextEditorWidget::gotoNextCharacterWithSelection()
mae's avatar
mae committed
752
753
754
755
{
    moveCursor(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
}

756
void BaseTextEditorWidget::gotoPreviousWord()
mae's avatar
mae committed
757
758
{
    moveCursor(QTextCursor::PreviousWord);
759
    setTextCursor(textCursor());
mae's avatar
mae committed
760
761
}

762
void BaseTextEditorWidget::gotoPreviousWordWithSelection()
mae's avatar
mae committed
763
764
{
    moveCursor(QTextCursor::PreviousWord, QTextCursor::KeepAnchor);
765
    setTextCursor(textCursor());
mae's avatar
mae committed
766
767
}

768
void BaseTextEditorWidget::gotoNextWord()
mae's avatar
mae committed
769
770
{
    moveCursor(QTextCursor::NextWord);
771
    setTextCursor(textCursor());
mae's avatar
mae committed
772
773
}

774
void BaseTextEditorWidget::gotoNextWordWithSelection()
mae's avatar
mae committed
775
776
{
    moveCursor(QTextCursor::NextWord, QTextCursor::KeepAnchor);
777
    setTextCursor(textCursor());
mae's avatar
mae committed
778
779
}

780
void BaseTextEditorWidget::gotoPreviousWordCamelCase()
mae's avatar
mae committed
781
782
783
784
785
786
{
    QTextCursor c = textCursor();
    camelCaseLeft(c, QTextCursor::MoveAnchor);
    setTextCursor(c);
}

787
void BaseTextEditorWidget::gotoPreviousWordCamelCaseWithSelection()
mae's avatar
mae committed
788
789
790
791
792
793
{
    QTextCursor c = textCursor();
    camelCaseLeft(c, QTextCursor::KeepAnchor);
    setTextCursor(c);
}

794
void BaseTextEditorWidget::gotoNextWordCamelCase()
mae's avatar
mae committed
795
796
797
798
799
800
{
    QTextCursor c = textCursor();
    camelCaseRight(c, QTextCursor::MoveAnchor);
    setTextCursor(c);
}

801
void BaseTextEditorWidget::gotoNextWordCamelCaseWithSelection()
mae's avatar
mae committed
802
803
804
805
806
807
{
    QTextCursor c = textCursor();
    camelCaseRight(c, QTextCursor::KeepAnchor);
    setTextCursor(c);
}

808
809
static QTextCursor flippedCursor(const QTextCursor &cursor)
{
810
811
812
813
    QTextCursor flipped = cursor;
    flipped.clearSelection();
    flipped.setPosition(cursor.anchor(), QTextCursor::KeepAnchor);
    return flipped;
814
815
}

816
bool BaseTextEditorWidget::selectBlockUp()
817
818
819
820
821
822
823
824
{
    QTextCursor cursor = textCursor();
    if (!cursor.hasSelection())
        d->m_selectBlockAnchor = cursor;
    else
        cursor.setPosition(cursor.selectionStart());

    if (!TextBlockUserData::findPreviousOpenParenthesis(&cursor, false))
825
        return false;
826
    if (!TextBlockUserData::findNextClosingParenthesis(&cursor, true))
827
828
        return false;

829
830
    setTextCursor(flippedCursor(cursor));
    _q_matchParentheses();
831
    return true;
832
833
}

834
bool BaseTextEditorWidget::selectBlockDown()
835
836
837
838
839
{
    QTextCursor tc = textCursor();
    QTextCursor cursor = d->m_selectBlockAnchor;

    if (!tc.hasSelection() || cursor.isNull())
840
        return false;
841
842
843
844
845
846
847
848
849
850
851
852
853
    tc.setPosition(tc.selectionStart());

    forever {
        QTextCursor ahead = cursor;
        if (!TextBlockUserData::findPreviousOpenParenthesis(&ahead, false))
            break;
        if (ahead.position() <= tc.position())
            break;
        cursor = ahead;
    }
    if ( cursor != d->m_selectBlockAnchor)
        TextBlockUserData::findNextClosingParenthesis(&cursor, true);

854
855
    setTextCursor(flippedCursor(cursor));
    _q_matchParentheses();
856
    return true;
857
858
}

859
void BaseTextEditorWidget::copyLineUp()
860
861
862
863
{
    copyLineUpDown(true);
}

864
void BaseTextEditorWidget::copyLineDown()
865
866
867
868
{
    copyLineUpDown(false);
}

869
// @todo: Potential reuse of some code around the following functions...
870
void BaseTextEditorWidget::copyLineUpDown(bool up)
871
872
873
874
875
876
877
878
879
880
881
{
    QTextCursor cursor = textCursor();
    QTextCursor move = cursor;
    move.beginEditBlock();

    bool hasSelection = cursor.hasSelection();

    if (hasSelection) {
        move.setPosition(cursor.selectionStart());
        move.movePosition(QTextCursor::StartOfBlock);
        move.setPosition(cursor.selectionEnd(), QTextCursor::KeepAnchor);
882
883
        move.movePosition(move.atBlockStart() ? QTextCursor::Left: QTextCursor::EndOfBlock,
                          QTextCursor::KeepAnchor);
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
    } else {
        move.movePosition(QTextCursor::StartOfBlock);
        move.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
    }

    QString text = move.selectedText();

    if (up) {
        move.setPosition(cursor.selectionStart());
        move.movePosition(QTextCursor::StartOfBlock);
        move.insertBlock();
        move.movePosition(QTextCursor::Left);
    } else {
        move.movePosition(QTextCursor::EndOfBlock);
        if (move.atBlockStart()) {
            move.movePosition(QTextCursor::NextBlock);
            move.insertBlock();
            move.movePosition(QTextCursor::Left);
        } else {
            move.insertBlock();
        }
    }

    int start = move.position();
    move.clearSelection();
    move.insertText(text);
    int end = move.position();

    move.setPosition(start);
    move.setPosition(end, QTextCursor::KeepAnchor);

915
    d->m_document->autoIndent(move);
916
917
918
919
920
    move.endEditBlock();

    setTextCursor(move);
}

921
void BaseTextEditorWidget::joinLines()
922
{
923
924
925
    QTextCursor cursor = textCursor();
    QTextCursor start = cursor;
    QTextCursor end = cursor;
926

927
928
    start.setPosition(cursor.selectionStart());
    end.setPosition(cursor.selectionEnd() - 1);
929

930
    int lineCount = qMax(1, end.blockNumber() - start.blockNumber());
931

932
933
934
935
936
937
938
    cursor.beginEditBlock();
    cursor.setPosition(cursor.selectionStart());
    while (lineCount--) {
        cursor.movePosition(QTextCursor::NextBlock);
        cursor.movePosition(QTextCursor::StartOfBlock);
        cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
        QString cutLine = cursor.selectedText();
939

940
        // Collapse leading whitespaces to one or insert whitespace
941
        cutLine.replace(QRegExp(QLatin1String("^\\s*")), QLatin1String(" "));
942
943
944
945
946
947
948
949
950
951
952
        cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
        cursor.removeSelectedText();

        cursor.movePosition(QTextCursor::PreviousBlock);
        cursor.movePosition(QTextCursor::EndOfBlock);

        cursor.insertText(cutLine);
    }
    cursor.endEditBlock();

    setTextCursor(cursor);
953
954
}

955
void BaseTextEditorWidget::insertLineAbove()
956
957
958
{
    QTextCursor cursor = textCursor();
    cursor.beginEditBlock();
959
960
961
    // If the cursor is at the beginning of the document,
    // it should still insert a line above the current line.
    cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
962
    cursor.insertBlock();
963
    cursor.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor);
964
    d->m_document->autoIndent(cursor);
965
966
967
968
    cursor.endEditBlock();
    setTextCursor(cursor);
}

969
void BaseTextEditorWidget::insertLineBelow()
970
971
972
973
974
{
    QTextCursor cursor = textCursor();
    cursor.beginEditBlock();
    cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::MoveAnchor);
    cursor.insertBlock();
975
    d->m_document->autoIndent(cursor);
976
977
978
979
    cursor.endEditBlock();
    setTextCursor(cursor);
}

980
void BaseTextEditorWidget::moveLineUp()
981
982
983
984
{
    moveLineUpDown(true);
}

985
void BaseTextEditorWidget::moveLineDown()
986
987
988
989
{
    moveLineUpDown(false);
}

990
991
992
993
994
995
996
997
998
999
void BaseTextEditorWidget::uppercaseSelection()
{
    transformSelection(&QString::toUpper);
}

void BaseTextEditorWidget::lowercaseSelection()
{
    transformSelection(&QString::toLower);
}

1000
1001
void BaseTextEditorWidget::indent()
{
1002
    setTextCursor(baseTextDocument()->indent(textCursor()));
1003
1004
1005
1006
}

void BaseTextEditorWidget::unindent()
{
1007
    setTextCursor(baseTextDocument()->unindent(textCursor()));
1008
1009
}

1010
1011
void BaseTextEditorWidget::openLinkUnderCursor()
{
1012
1013
1014
    const bool openInNextSplit = alwaysOpenLinksInNextSplit();
    Link symbolLink = findLinkAt(textCursor(), true, openInNextSplit);
    openLink(symbolLink, openInNextSplit);
1015
}
1016

1017
1018
void BaseTextEditorWidget::openLinkUnderCursorInNextSplit()
{
1019
1020
1021
    const bool openInNextSplit = !alwaysOpenLinksInNextSplit();
    Link symbolLink = findLinkAt(textCursor(), true, openInNextSplit);
    openLink(symbolLink, openInNextSplit);
1022
1023
}

1024
1025
1026
1027
1028
void BaseTextEditorWidget::abortAssist()
{
    d->m_codeAssistant->destroyContext();
}

1029
void BaseTextEditorWidget::moveLineUpDown(bool up)
1030
1031
1032
{
    QTextCursor cursor = textCursor();
    QTextCursor move = cursor;
1033

mae's avatar
mae committed
1034
    move.setVisualNavigation(false); // this opens folded items instead of destroying them
1035

1036
1037
1038
1039
    if (d->m_moveLineUndoHack)
        move.joinPreviousEditBlock();
    else
        move.beginEditBlock();
1040
1041
1042

    bool hasSelection = cursor.hasSelection();

Andrey M. Tokarev's avatar
Andrey M. Tokarev committed
1043
    if (hasSelection) {
1044
1045
1046
        move.setPosition(cursor.selectionStart());
        move.movePosition(QTextCursor::StartOfBlock);
        move.setPosition(cursor.selectionEnd(), QTextCursor::KeepAnchor);
1047
1048
        move.movePosition(move.atBlockStart() ? QTextCursor::Left: QTextCursor::EndOfBlock,
                          QTextCursor::KeepAnchor);
1049
1050
1051
1052
1053
    } else {
        move.movePosition(QTextCursor::StartOfBlock);
        move.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
    }
    QString text = move.selectedText();
1054
1055
1056
1057
1058
1059
1060

    RefactorMarkers affectedMarkers;
    RefactorMarkers nonAffectedMarkers;
    QList<int> markerOffsets;

    foreach (const RefactorMarker &marker, d->m_refactorOverlay->markers()) {
        //test if marker is part of the selection to be moved
1061
1062
        if ((move.selectionStart() <= marker.cursor.position())
                && (move.selectionEnd() >= marker.cursor.position())) {
1063
1064
1065
1066
1067
1068
1069
1070
1071
            affectedMarkers.append(marker);
            //remember the offset of markers in text
            int offset = marker.cursor.position() - move.selectionStart();
            markerOffsets.append(offset);
        } else {
            nonAffectedMarkers.append(marker);
        }
    }

1072
1073
    move.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
    move.removeSelectedText();
1074

1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
    if (up) {
        move.movePosition(QTextCursor::PreviousBlock);
        move.insertBlock();
        move.movePosition(QTextCursor::Left);
    } else {
        move.movePosition(QTextCursor::EndOfBlock);
        if (move.atBlockStart()) { // empty block
            move.movePosition(QTextCursor::NextBlock);
            move.insertBlock();
            move.movePosition(QTextCursor::Left);
        } else {
            move.insertBlock();
        }
    }
1089

1090
1091
1092
1093
    int start = move.position();
    move.clearSelection();
    move.insertText(text);
    int end = move.position();
1094

1095
    if (hasSelection) {
1096
1097
1098
        move.setPosition(end);
        move.setPosition(start, QTextCursor::KeepAnchor);
    } else {
1099
1100
1101
        move.setPosition(start);
    }

1102
1103
1104
1105
1106
1107
1108
    //update positions of affectedMarkers
    for (int i=0;i < affectedMarkers.count(); i++) {
        int newPosition = start + markerOffsets.at(i);
        affectedMarkers[i].cursor.setPosition(newPosition);
    }
    d->m_refactorOverlay->setMarkers(nonAffectedMarkers + affectedMarkers);

1109
    bool shouldReindent