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();
con's avatar
con committed
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
    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
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()
{