basetexteditor.cpp 221 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 240 241

    d->m_lastScrollPos = -1;

    // from RESEARCH

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

mae's avatar
mae committed
245
    d->visibleFoldedBlockNumber = d->suggestedVisibleFoldedBlockNumber = -1;
con's avatar
con committed
246

247 248
    connect(d->m_codeAssistant.data(), SIGNAL(finished()), this, SIGNAL(assistFinished()));

con's avatar
con committed
249 250 251
    connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(slotUpdateExtraAreaWidth()));
    connect(this, SIGNAL(modificationChanged(bool)), this, SLOT(slotModificationChanged(bool)));
    connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(slotCursorPositionChanged()));
Robert Loehning's avatar
Robert Loehning committed
252
    connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(slotUpdateRequest(QRect,int)));
con's avatar
con committed
253 254 255 256 257 258
    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()));

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

con's avatar
con committed
263 264 265

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

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

274
    d->m_animator = 0;
con's avatar
con committed
275 276

    slotUpdateExtraAreaWidth();
con's avatar
con committed
277
    updateHighlights();
con's avatar
con committed
278 279
    setFrameStyle(QFrame::NoFrame);

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

mae's avatar
mae committed
283
    d->m_moveLineUndoHack = false;
con's avatar
con committed
284 285
}

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

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

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

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

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

350
    QString title = m_document->displayName();
con's avatar
con committed
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
    if (title.isEmpty())
        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
381
            for (int i = formatList.count() - 1; i >= 0; --i) {
con's avatar
con committed
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 421 422
                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
423
    if (printer->collateCopies() == true) {
con's avatar
con committed
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
        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;
}


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

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


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

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

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

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

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

563
bool BaseTextEditorWidget::open(QString *errorString, const QString &fileName, const QString &realFileName)
con's avatar
con committed
564
{
565
    if (d->m_document->open(errorString, fileName, realFileName)) {
con's avatar
con committed
566
        moveCursor(QTextCursor::Start);
567
        updateCannotDecodeInfo();
568 569 570 571 572 573 574
        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
575 576 577 578 579
        return true;
    }
    return false;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

mae's avatar
mae committed
697

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

831 832
    setTextCursor(flippedCursor(cursor));
    _q_matchParentheses();
833
    return true;
834 835
}

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

    if (!tc.hasSelection() || cursor.isNull())
842
        return false;
843 844 845 846 847 848 849 850 851 852 853 854 855
    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);

856 857
    setTextCursor(flippedCursor(cursor));
    _q_matchParentheses();
858
    return true;
859 860
}

861
void BaseTextEditorWidget::copyLineUp()
862 863 864 865
{
    copyLineUpDown(true);
}

866
void BaseTextEditorWidget::copyLineDown()
867 868 869 870
{
    copyLineUpDown(false);
}

871
// @todo: Potential reuse of some code around the following functions...
872
void BaseTextEditorWidget::copyLineUpDown(bool up)
873 874 875 876 877 878 879 880 881 882 883
{
    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);
884 885
        move.movePosition(move.atBlockStart() ? QTextCursor::Left: QTextCursor::EndOfBlock,
                          QTextCursor::KeepAnchor);
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 915 916
    } 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);

917
    d->m_document->autoIndent(move);
918 919 920 921 922
    move.endEditBlock();

    setTextCursor(move);
}

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

929 930
    start.setPosition(cursor.selectionStart());
    end.setPosition(cursor.selectionEnd() - 1);
931

932
    int lineCount = qMax(1, end.blockNumber() - start.blockNumber());
933

934 935 936 937 938 939 940
    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();
941

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

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

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

    setTextCursor(cursor);
955 956
}

957
void BaseTextEditorWidget::insertLineAbove()
958 959 960
{
    QTextCursor cursor = textCursor();
    cursor.beginEditBlock();
961 962 963
    // 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);
964
    cursor.insertBlock();
965
    cursor.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor);
966
    d->m_document->autoIndent(cursor);
967 968 969 970
    cursor.endEditBlock();
    setTextCursor(cursor);
}

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

982
void BaseTextEditorWidget::moveLineUp()
983 984 985 986
{
    moveLineUpDown(true);
}

987
void BaseTextEditorWidget::moveLineDown()
988 989 990 991
{
    moveLineUpDown(false);
}

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

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

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

void BaseTextEditorWidget::unindent()
{
1009
    setTextCursor(baseTextDocument()->unindent(textCursor()));
1010 1011
}

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

1019 1020
void BaseTextEditorWidget::openLinkUnderCursorInNextSplit()
{
1021 1022 1023
    const b