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 266

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

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

276
    d->m_animator = 0;
con's avatar
con committed
277 278 279 280

    d->m_searchResultFormat.setBackground(QColor(0xffef0b));

    slotUpdateExtraAreaWidth();
con's avatar
con committed
281
    updateHighlights();
con's avatar
con committed
282 283
    setFrameStyle(QFrame::NoFrame);

284 285
    d->m_delayedUpdateTimer.setSingleShot(true);
    connect(&d->m_delayedUpdateTimer, SIGNAL(timeout()), viewport(), SLOT(update()));
286

mae's avatar
mae committed
287
    d->m_moveLineUndoHack = false;
con's avatar
con committed
288 289
}

290
BaseTextEditorWidget::~BaseTextEditorWidget()
con's avatar
con committed
291 292 293 294 295
{
    delete d;
    d = 0;
}

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

mae's avatar
mae committed
308
static int foldBoxWidth(const QFontMetrics &fm)
309 310
{
    const int lineSpacing = fm.lineSpacing();
hjk's avatar
hjk committed
311
    return lineSpacing + lineSpacing % 2 + 1;
312 313
}

con's avatar
con committed
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
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();
}

350
void BaseTextEditorWidgetPrivate::print(QPrinter *printer)
con's avatar
con committed
351 352 353
{
    QTextDocument *doc = q->document();

354
    QString title = m_document->displayName();
con's avatar
con committed
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 381 382 383 384
    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
385
            for (int i = formatList.count() - 1; i >= 0; --i) {
con's avatar
con committed
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 423 424 425 426
                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
427
    if (printer->collateCopies() == true) {
con's avatar
con committed
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 483 484 485 486
        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;
}


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

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


515
void BaseTextEditorWidget::selectEncoding()
con's avatar
con committed
516
{
517
    BaseTextDocument *doc = d->m_document.data();
con's avatar
con committed
518 519 520
    CodecSelector codecSelector(this, doc);

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

538 539 540 541 542
void BaseTextEditorWidget::updateTextCodecLabel()
{
    editor()->setFileEncodingLabelText(QString::fromLatin1(d->m_document->codec()->name()));
}

543
QString BaseTextEditorWidget::msgTextTooLarge(quint64 size)
544 545 546 547 548
{
    return tr("The text is too large to be displayed (%1 MB).").
           arg(size >> 20);
}

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

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

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

610
BaseTextDocument *BaseTextEditorWidget::baseTextDocument() const
con's avatar
con committed
611
{
612
    return d->m_document.data();
con's avatar
con committed
613 614
}

615
void BaseTextEditorWidget::editorContentsChange(int position, int charsRemoved, int charsAdded)
con's avatar
con committed
616
{
617 618 619
    if (d->m_animator)
        d->m_animator->finish();

con's avatar
con committed
620
    d->m_contentsChanged = true;
621
    QTextDocument *doc = document();
622
    BaseTextDocumentLayout *documentLayout = static_cast<BaseTextDocumentLayout*>(doc->documentLayout());
con's avatar
con committed
623 624 625

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

640 641 642
    if (d->m_snippetOverlay->isVisible()) {
        QTextCursor cursor = textCursor();
        cursor.setPosition(position);
643
        d->snippetCheckCursor(cursor);
644 645
    }

646
    if (charsAdded != 0 && document()->characterAt(position + charsAdded - 1).isPrint())
Leandro Melo's avatar
Leandro Melo committed
647
        d->m_assistRelevantContentAdded = true;
con's avatar
con committed
648 649
}

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

658 659
    if (!d->m_selectBlockAnchor.isNull() && !textCursor().hasSelection())
        d->m_selectBlockAnchor = QTextCursor();
660 661 662

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

665
void BaseTextEditorWidget::gotoBlockStart()
666 667
{
    QTextCursor cursor = textCursor();
668
    if (TextBlockUserData::findPreviousOpenParenthesis(&cursor, false)) {
669
        setTextCursor(cursor);
670 671
        _q_matchParentheses();
    }
672 673
}

674
void BaseTextEditorWidget::gotoBlockEnd()
675 676
{
    QTextCursor cursor = textCursor();
677
    if (TextBlockUserData::findNextClosingParenthesis(&cursor, false)) {
678
        setTextCursor(cursor);
679 680
        _q_matchParentheses();
    }
681 682
}

683
void BaseTextEditorWidget::gotoBlockStartWithSelection()
684 685
{
    QTextCursor cursor = textCursor();
686
    if (TextBlockUserData::findPreviousOpenParenthesis(&cursor, true)) {
687
        setTextCursor(cursor);
688 689
        _q_matchParentheses();
    }
690 691
}

692
void BaseTextEditorWidget::gotoBlockEndWithSelection()
693 694
{
    QTextCursor cursor = textCursor();
695
    if (TextBlockUserData::findNextClosingParenthesis(&cursor, true)) {
696
        setTextCursor(cursor);
697 698 699 700
        _q_matchParentheses();
    }
}

mae's avatar
mae committed
701

702
void BaseTextEditorWidget::gotoLineStart()
mae's avatar
mae committed
703 704 705 706
{
    handleHomeKey(false);
}

707
void BaseTextEditorWidget::gotoLineStartWithSelection()
mae's avatar
mae committed
708 709 710 711
{
    handleHomeKey(true);
}

712
void BaseTextEditorWidget::gotoLineEnd()
mae's avatar
mae committed
713 714 715 716
{
    moveCursor(QTextCursor::EndOfLine);
}

717
void BaseTextEditorWidget::gotoLineEndWithSelection()
mae's avatar
mae committed
718 719 720 721
{
    moveCursor(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
}

722
void BaseTextEditorWidget::gotoNextLine()
mae's avatar
mae committed
723
{
724
    moveCursor(QTextCursor::Down);
mae's avatar
mae committed
725 726
}

727
void BaseTextEditorWidget::gotoNextLineWithSelection()
mae's avatar
mae committed
728
{
729
    moveCursor(QTextCursor::Down, QTextCursor::KeepAnchor);
mae's avatar
mae committed
730 731
}

732
void BaseTextEditorWidget::gotoPreviousLine()
mae's avatar
mae committed
733
{
734
    moveCursor(QTextCursor::Up);
mae's avatar
mae committed
735 736
}

737
void BaseTextEditorWidget::gotoPreviousLineWithSelection()
mae's avatar
mae committed
738
{
739
    moveCursor(QTextCursor::Up, QTextCursor::KeepAnchor);
mae's avatar
mae committed
740 741
}

742
void BaseTextEditorWidget::gotoPreviousCharacter()
mae's avatar
mae committed
743 744 745 746
{
    moveCursor(QTextCursor::PreviousCharacter);
}

747
void BaseTextEditorWidget::gotoPreviousCharacterWithSelection()
mae's avatar
mae committed
748 749 750 751
{
    moveCursor(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
}

752
void BaseTextEditorWidget::gotoNextCharacter()
mae's avatar
mae committed
753 754 755 756
{
    moveCursor(QTextCursor::NextCharacter);
}

757
void BaseTextEditorWidget::gotoNextCharacterWithSelection()
mae's avatar
mae committed
758 759 760 761
{
    moveCursor(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
}

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

768
void BaseTextEditorWidget::gotoPreviousWordWithSelection()
mae's avatar
mae committed
769 770
{
    moveCursor(QTextCursor::PreviousWord, QTextCursor::KeepAnchor);
771
    setTextCursor(textCursor());
mae's avatar
mae committed
772 773
}

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

780
void BaseTextEditorWidget::gotoNextWordWithSelection()
mae's avatar
mae committed
781 782
{
    moveCursor(QTextCursor::NextWord, QTextCursor::KeepAnchor);
783
    setTextCursor(textCursor());
mae's avatar
mae committed
784 785
}

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

793
void BaseTextEditorWidget::gotoPreviousWordCamelCaseWithSelection()
mae's avatar
mae committed
794 795 796 797 798 799
{
    QTextCursor c = textCursor();
    camelCaseLeft(c, QTextCursor::KeepAnchor);
    setTextCursor(c);
}

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

807
void BaseTextEditorWidget::gotoNextWordCamelCaseWithSelection()
mae's avatar
mae committed
808 809 810 811 812 813
{
    QTextCursor c = textCursor();
    camelCaseRight(c, QTextCursor::KeepAnchor);
    setTextCursor(c);
}

814 815
static QTextCursor flippedCursor(const QTextCursor &cursor)
{
816 817 818 819
    QTextCursor flipped = cursor;
    flipped.clearSelection();
    flipped.setPosition(cursor.anchor(), QTextCursor::KeepAnchor);
    return flipped;
820 821
}

822
bool BaseTextEditorWidget::selectBlockUp()
823 824 825 826 827 828 829 830
{
    QTextCursor cursor = textCursor();
    if (!cursor.hasSelection())
        d->m_selectBlockAnchor = cursor;
    else
        cursor.setPosition(cursor.selectionStart());

    if (!TextBlockUserData::findPreviousOpenParenthesis(&cursor, false))
831
        return false;
832
    if (!TextBlockUserData::findNextClosingParenthesis(&cursor, true))
833 834
        return false;

835 836
    setTextCursor(flippedCursor(cursor));
    _q_matchParentheses();
837
    return true;
838 839
}

840
bool BaseTextEditorWidget::selectBlockDown()
841 842 843 844 845
{
    QTextCursor tc = textCursor();
    QTextCursor cursor = d->m_selectBlockAnchor;

    if (!tc.hasSelection() || cursor.isNull())
846
        return false;
847 848 849 850 851 852 853 854 855 856 857 858 859
    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);

860 861
    setTextCursor(flippedCursor(cursor));
    _q_matchParentheses();
862
    return true;
863 864
}

865
void BaseTextEditorWidget::copyLineUp()
866 867 868 869
{
    copyLineUpDown(true);
}

870
void BaseTextEditorWidget::copyLineDown()
871 872 873 874
{
    copyLineUpDown(false);
}

875
// @todo: Potential reuse of some code around the following functions...
876
void BaseTextEditorWidget::copyLineUpDown(bool up)
877 878 879 880 881 882 883 884 885 886 887
{
    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);
888 889
        move.movePosition(move.atBlockStart() ? QTextCursor::Left: QTextCursor::EndOfBlock,
                          QTextCursor::KeepAnchor);
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 917 918 919 920
    } 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);

921
    d->m_document->autoIndent(move);
922 923 924 925 926
    move.endEditBlock();

    setTextCursor(move);
}

927
void BaseTextEditorWidget::joinLines()
928
{
929 930 931
    QTextCursor cursor = textCursor();
    QTextCursor start = cursor;
    QTextCursor end = cursor;
932

933 934
    start.setPosition(cursor.selectionStart());
    end.setPosition(cursor.selectionEnd() - 1);
935

936
    int lineCount = qMax(1, end.blockNumber() - start.blockNumber());
937

938 939 940 941 942 943 944
    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();
945

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

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

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

    setTextCursor(cursor);
959 960
}

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

975
void BaseTextEditorWidget::insertLineBelow()
976 977 978 979 980
{
    QTextCursor cursor = textCursor();
    cursor.beginEditBlock();
    cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::MoveAnchor);
    cursor.insertBlock();
981
    d->m_document->autoIndent(cursor);
André Fillipe's avatar