basetexteditor.cpp 226 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>
con's avatar
con committed
60 61
#include <find/basetextfind.h>
#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
using namespace Utils;
con's avatar
con committed
108 109

namespace TextEditor {
110
namespace Internal {
con's avatar
con committed
111

hjk's avatar
hjk committed
112 113
class TextEditExtraArea : public QWidget
{
con's avatar
con committed
114
public:
hjk's avatar
hjk committed
115 116 117
    TextEditExtraArea(BaseTextEditorWidget *edit)
        : QWidget(edit)
    {
con's avatar
con committed
118 119 120 121
        textEdit = edit;
        setAutoFillBackground(true);
    }

hjk's avatar
hjk committed
122
protected:
con's avatar
con committed
123 124 125
    QSize sizeHint() const {
        return QSize(textEdit->extraAreaWidth(), 0);
    }
hjk's avatar
hjk committed
126
    void paintEvent(QPaintEvent *event) {
con's avatar
con committed
127 128
        textEdit->extraAreaPaintEvent(event);
    }
hjk's avatar
hjk committed
129
    void mousePressEvent(QMouseEvent *event) {
con's avatar
con committed
130 131
        textEdit->extraAreaMouseEvent(event);
    }
hjk's avatar
hjk committed
132
    void mouseMoveEvent(QMouseEvent *event) {
con's avatar
con committed
133 134
        textEdit->extraAreaMouseEvent(event);
    }
hjk's avatar
hjk committed
135
    void mouseReleaseEvent(QMouseEvent *event) {
con's avatar
con committed
136 137
        textEdit->extraAreaMouseEvent(event);
    }
hjk's avatar
hjk committed
138
    void leaveEvent(QEvent *event) {
con's avatar
con committed
139 140
        textEdit->extraAreaLeaveEvent(event);
    }
141 142 143
    void contextMenuEvent(QContextMenuEvent *event) {
        textEdit->extraAreaContextMenuEvent(event);
    }
con's avatar
con committed
144 145 146 147

    void wheelEvent(QWheelEvent *event) {
        QCoreApplication::sendEvent(textEdit->viewport(), event);
    }
hjk's avatar
hjk committed
148 149 150

private:
    BaseTextEditorWidget *textEdit;
con's avatar
con committed
151 152
};

153
} // namespace Internal
hjk's avatar
hjk committed
154 155

using namespace Internal;
con's avatar
con committed
156

157
QString BaseTextEditorWidget::plainTextFromSelection(const QTextCursor &cursor) const
158
{
159 160 161 162 163 164 165 166 167 168
    // 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();
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184

    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:
            ;
        }
    }
185
    return ret;
186 187
}

Orgad Shaneh's avatar
Orgad Shaneh committed
188 189
static const char kTextBlockMimeType[] = "application/vnd.qtcreator.blocktext";
static const char kVerticalTextBlockMimeType[] = "application/vnd.qtcreator.vblocktext";
190

191
BaseTextEditorWidget::BaseTextEditorWidget(QWidget *parent)
con's avatar
con committed
192
    : QPlainTextEdit(parent)
193 194 195 196 197 198 199 200 201 202 203
{
    ctor(QSharedPointer<BaseTextDocument>(new BaseTextDocument));
}

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

void BaseTextEditorWidget::ctor(const QSharedPointer<BaseTextDocument> &doc)
con's avatar
con committed
204
{
205
    d = new BaseTextEditorWidgetPrivate;
con's avatar
con committed
206 207 208 209 210
    d->q = this;
    d->m_extraArea = new TextEditExtraArea(this);
    d->m_extraArea->setMouseTracking(true);
    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);

211
    d->m_overlay = new TextEditorOverlay(this);
212
    d->m_snippetOverlay = new TextEditorOverlay(this);
213
    d->m_searchResultOverlay = new TextEditorOverlay(this);
214
    d->m_refactorOverlay = new RefactorOverlay(this);
215

216
    d->m_document = doc;
con's avatar
con committed
217 218 219 220 221 222 223 224 225 226
    d->setupDocumentSignals(d->m_document);

    d->m_lastScrollPos = -1;

    // from RESEARCH

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

mae's avatar
mae committed
230
    d->visibleFoldedBlockNumber = d->suggestedVisibleFoldedBlockNumber = -1;
con's avatar
con committed
231

232 233
    connect(d->m_codeAssistant.data(), SIGNAL(finished()), this, SIGNAL(assistFinished()));

con's avatar
con committed
234 235 236
    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
237
    connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(slotUpdateRequest(QRect,int)));
con's avatar
con committed
238 239 240 241 242 243
    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()));

244 245 246 247
#ifdef DO_FOO
    (void) new QShortcut(tr("CTRL+D"), this, SLOT(doFoo()));
#endif

con's avatar
con committed
248 249 250 251

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

258 259
    d->m_highlightBlocksTimer.setSingleShot(true);
    connect(&d->m_highlightBlocksTimer, SIGNAL(timeout()), this, SLOT(_q_highlightBlocks()));
260

261
    d->m_animator = 0;
con's avatar
con committed
262 263 264 265

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

    slotUpdateExtraAreaWidth();
con's avatar
con committed
266
    updateHighlights();
con's avatar
con committed
267 268
    setFrameStyle(QFrame::NoFrame);

269 270
    d->m_delayedUpdateTimer.setSingleShot(true);
    connect(&d->m_delayedUpdateTimer, SIGNAL(timeout()), viewport(), SLOT(update()));
271

mae's avatar
mae committed
272
    d->m_moveLineUndoHack = false;
con's avatar
con committed
273 274
}

275
BaseTextEditorWidget::~BaseTextEditorWidget()
con's avatar
con committed
276 277 278 279 280
{
    delete d;
    d = 0;
}

281
QString BaseTextEditorWidget::mimeType() const
con's avatar
con committed
282 283 284 285
{
    return d->m_document->mimeType();
}

286
void BaseTextEditorWidget::setMimeType(const QString &mt)
con's avatar
con committed
287 288 289 290
{
    d->m_document->setMimeType(mt);
}

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

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

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

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

349
    QString title = m_document->displayName();
con's avatar
con committed
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
    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
380
            for (int i = formatList.count() - 1; i >= 0; --i) {
con's avatar
con committed
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 421
                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
422
    if (printer->collateCopies() == true) {
con's avatar
con committed
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 481
        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;
}


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

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


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

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

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

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

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

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

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

607
const Utils::ChangeSet &BaseTextEditorWidget::changeSet() const
608 609 610 611
{
    return d->m_changeSet;
}

612
void BaseTextEditorWidget::setChangeSet(const Utils::ChangeSet &changeSet)
613 614
{
    d->m_changeSet = changeSet;
Roberto Raggi's avatar
Roberto Raggi committed
615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641

    foreach (const ChangeSet::EditOp &op, changeSet.operationList()) {
        // ### TODO: process the edit operation

        switch (op.type) {
        case ChangeSet::EditOp::Replace:
            break;

        case ChangeSet::EditOp::Move:
            break;

        case ChangeSet::EditOp::Insert:
            break;

        case ChangeSet::EditOp::Remove:
            break;

        case ChangeSet::EditOp::Flip:
            break;

        case ChangeSet::EditOp::Copy:
            break;

        default:
            break;
        } // switch
    }
642 643
}

644
BaseTextDocument *BaseTextEditorWidget::baseTextDocument() const
con's avatar
con committed
645
{
646
    return d->m_document.data();
con's avatar
con committed
647 648
}

649
void BaseTextEditorWidget::editorContentsChange(int position, int charsRemoved, int charsAdded)
con's avatar
con committed
650
{
651 652 653
    if (d->m_animator)
        d->m_animator->finish();

con's avatar
con committed
654
    d->m_contentsChanged = true;
655
    QTextDocument *doc = document();
656
    BaseTextDocumentLayout *documentLayout = static_cast<BaseTextDocumentLayout*>(doc->documentLayout());
con's avatar
con committed
657 658 659

    // Keep the line numbers and the block information for the text marks updated
    if (charsRemoved != 0) {
660 661
        documentLayout->updateMarksLineNumber();
        documentLayout->updateMarksBlock(document()->findBlock(position));
con's avatar
con committed
662
    } else {
663 664
        const QTextBlock posBlock = doc->findBlock(position);
        const QTextBlock nextBlock = doc->findBlock(position + charsAdded);
con's avatar
con committed
665
        if (posBlock != nextBlock) {
666 667 668
            documentLayout->updateMarksLineNumber();
            documentLayout->updateMarksBlock(posBlock);
            documentLayout->updateMarksBlock(nextBlock);
con's avatar
con committed
669
        } else {
670
            documentLayout->updateMarksBlock(posBlock);
con's avatar
con committed
671 672
        }
    }
673

674 675 676
    if (d->m_snippetOverlay->isVisible()) {
        QTextCursor cursor = textCursor();
        cursor.setPosition(position);
677
        d->snippetCheckCursor(cursor);
678 679
    }

680
    if (charsAdded != 0 && document()->characterAt(position + charsAdded - 1).isPrint())
Leandro Melo's avatar
Leandro Melo committed
681
        d->m_assistRelevantContentAdded = true;
con's avatar
con committed
682 683
}

684
void BaseTextEditorWidget::slotSelectionChanged()
con's avatar
con committed
685
{
mae's avatar
mae committed
686 687 688
    if (d->m_inBlockSelectionMode && !textCursor().hasSelection()) {
        d->m_inBlockSelectionMode = false;
        d->m_blockSelection.clear();
con's avatar
con committed
689
        viewport()->update();
mae's avatar
mae committed
690 691
    }

692 693
    if (!d->m_selectBlockAnchor.isNull() && !textCursor().hasSelection())
        d->m_selectBlockAnchor = QTextCursor();
694 695 696

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

699
void BaseTextEditorWidget::gotoBlockStart()
700 701
{
    QTextCursor cursor = textCursor();
702
    if (TextBlockUserData::findPreviousOpenParenthesis(&cursor, false)) {
703
        setTextCursor(cursor);
704 705
        _q_matchParentheses();
    }
706 707
}

708
void BaseTextEditorWidget::gotoBlockEnd()
709 710
{
    QTextCursor cursor = textCursor();
711
    if (TextBlockUserData::findNextClosingParenthesis(&cursor, false)) {
712
        setTextCursor(cursor);
713 714
        _q_matchParentheses();
    }
715 716
}

717
void BaseTextEditorWidget::gotoBlockStartWithSelection()
718 719
{
    QTextCursor cursor = textCursor();
720
    if (TextBlockUserData::findPreviousOpenParenthesis(&cursor, true)) {
721
        setTextCursor(cursor);
722 723
        _q_matchParentheses();
    }
724 725
}

726
void BaseTextEditorWidget::gotoBlockEndWithSelection()
727 728
{
    QTextCursor cursor = textCursor();
729
    if (TextBlockUserData::findNextClosingParenthesis(&cursor, true)) {
730
        setTextCursor(cursor);
731 732 733 734
        _q_matchParentheses();
    }
}

mae's avatar
mae committed
735

736
void BaseTextEditorWidget::gotoLineStart()
mae's avatar
mae committed
737 738 739 740
{
    handleHomeKey(false);
}

741
void BaseTextEditorWidget::gotoLineStartWithSelection()
mae's avatar
mae committed
742 743 744 745
{
    handleHomeKey(true);
}

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

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

756
void BaseTextEditorWidget::gotoNextLine()
mae's avatar
mae committed
757
{
758
    moveCursor(QTextCursor::Down);
mae's avatar
mae committed
759 760
}

761
void BaseTextEditorWidget::gotoNextLineWithSelection()
mae's avatar
mae committed
762
{
763
    moveCursor(QTextCursor::Down, QTextCursor::KeepAnchor);
mae's avatar
mae committed
764 765
}

766
void BaseTextEditorWidget::gotoPreviousLine()
mae's avatar
mae committed
767
{
768
    moveCursor(QTextCursor::Up);
mae's avatar
mae committed
769 770
}

771
void BaseTextEditorWidget::gotoPreviousLineWithSelection()
mae's avatar
mae committed
772
{
773
    moveCursor(QTextCursor::Up, QTextCursor::KeepAnchor);
mae's avatar
mae committed
774 775
}

776
void BaseTextEditorWidget::gotoPreviousCharacter()
mae's avatar
mae committed
777 778 779 780
{
    moveCursor(QTextCursor::PreviousCharacter);
}

781
void BaseTextEditorWidget::gotoPreviousCharacterWithSelection()
mae's avatar
mae committed
782 783 784 785
{
    moveCursor(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
}

786
void BaseTextEditorWidget::gotoNextCharacter()
mae's avatar
mae committed
787 788 789 790
{
    moveCursor(QTextCursor::NextCharacter);
}

791
void BaseTextEditorWidget::gotoNextCharacterWithSelection()
mae's avatar
mae committed
792 793 794 795
{
    moveCursor(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
}

796
void BaseTextEditorWidget::gotoPreviousWord()
mae's avatar
mae committed
797 798
{
    moveCursor(QTextCursor::PreviousWord);
799
    setTextCursor(textCursor());
mae's avatar
mae committed
800 801
}

802
void BaseTextEditorWidget::gotoPreviousWordWithSelection()
mae's avatar
mae committed
803 804
{
    moveCursor(QTextCursor::PreviousWord, QTextCursor::KeepAnchor);
805
    setTextCursor(textCursor());
mae's avatar
mae committed
806 807
}

808
void BaseTextEditorWidget::gotoNextWord()
mae's avatar
mae committed
809 810
{
    moveCursor(QTextCursor::NextWord);
811
    setTextCursor(textCursor());
mae's avatar
mae committed
812 813
}

814
void BaseTextEditorWidget::gotoNextWordWithSelection()
mae's avatar
mae committed
815 816
{
    moveCursor(QTextCursor::NextWord, QTextCursor::KeepAnchor);
817
    setTextCursor(textCursor());
mae's avatar
mae committed
818 819
}

820
void BaseTextEditorWidget::gotoPreviousWordCamelCase()
mae's avatar
mae committed
821 822 823 824 825 826
{
    QTextCursor c = textCursor();
    camelCaseLeft(c, QTextCursor::MoveAnchor);
    setTextCursor(c);
}

827
void BaseTextEditorWidget::gotoPreviousWordCamelCaseWithSelection()
mae's avatar
mae committed
828 829 830 831 832 833
{
    QTextCursor c = textCursor();
    camelCaseLeft(c, QTextCursor::KeepAnchor);
    setTextCursor(c);
}

834
void BaseTextEditorWidget::gotoNextWordCamelCase()
mae's avatar
mae committed
835 836 837 838 839 840
{
    QTextCursor c = textCursor();
    camelCaseRight(c, QTextCursor::MoveAnchor);
    setTextCursor(c);
}

841
void BaseTextEditorWidget::gotoNextWordCamelCaseWithSelection()
mae's avatar
mae committed
842 843 844 845 846 847
{
    QTextCursor c = textCursor();
    camelCaseRight(c, QTextCursor::KeepAnchor);
    setTextCursor(c);
}

848 849
static QTextCursor flippedCursor(const QTextCursor &cursor)
{
850 851 852 853
    QTextCursor flipped = cursor;
    flipped.clearSelection();
    flipped.setPosition(cursor.anchor(), QTextCursor::KeepAnchor);
    return flipped;
854 855
}

856
bool BaseTextEditorWidget::selectBlockUp()
857 858 859 860 861 862 863 864
{
    QTextCursor cursor = textCursor();
    if (!cursor.hasSelection())
        d->m_selectBlockAnchor = cursor;
    else
        cursor.setPosition(cursor.selectionStart());

    if (!TextBlockUserData::findPreviousOpenParenthesis(&cursor, false))
865
        return false;
866
    if (!TextBlockUserData::findNextClosingParenthesis(&cursor, true))
867 868
        return false;

869 870
    setTextCursor(flippedCursor(cursor));
    _q_matchParentheses();
871
    return true;
872 873
}

874
bool BaseTextEditorWidget::selectBlockDown()
875 876 877 878 879
{
    QTextCursor tc = textCursor();
    QTextCursor cursor = d->m_selectBlockAnchor;

    if (!tc.hasSelection() || cursor.isNull())
880
        return false;
881 882 883 884 885 886 887 888 889 890 891 892 893
    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);

894 895
    setTextCursor(flippedCursor(cursor));
    _q_matchParentheses();
896
    return true;
897 898
}

899
void BaseTextEditorWidget::copyLineUp()
900 901 902 903
{
    copyLineUpDown(true);
}

904
void BaseTextEditorWidget::copyLineDown()
905 906 907 908
{
    copyLineUpDown(false);
}

909
// @todo: Potential reuse of some code around the following functions...
910
void BaseTextEditorWidget::copyLineUpDown(bool up)
911 912 913 914 915 916 917 918 919 920 921
{
    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);
922 923
        move.movePosition(move.atBlockStart() ? QTextCursor::Left: QTextCursor::EndOfBlock,
                          QTextCursor::KeepAnchor);
924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960
    } 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);

    indent(document(), move, QChar::Null);
    move.endEditBlock();

    setTextCursor(move);
}

961
void BaseTextEditorWidget::joinLines()
962
{
963 964 965
    QTextCursor cursor = textCursor();
    QTextCursor start = cursor;
    QTextCursor end = cursor;
966

967 968
    start.setPosition(cursor.selectionStart());
    end.setPosition(cursor.selectionEnd() - 1);
969

970
    int lineCount = qMax(1, end.blockNumber() - start.blockNumber());
971

972 973 974 975 976 977 978
    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();
979

980
        // Collapse leading whitespaces to one or insert whitespace
981
        cutLine.replace(QRegExp(QLatin1String("^\\s*")), QLatin1String(" "));
982 983 984 985 986 987 988 989 990 991 992
        cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
        cursor.removeSelectedText();

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

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

    setTextCursor(cursor);
993 994
}

995
void BaseTextEditorWidget::insertLineAbove()
996 997 998
{
    QTextCursor cursor = textCursor();
    cursor.beginEditBlock();
999 1000 1001
    // 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);
1002
    cursor.insertBlock();
1003
    cursor.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor);
1004 1005 1006 1007 1008
    indent(document(), cursor, QChar::Null);
    cursor.endEditBlock();
    setTextCursor(cursor);
}

1009
void BaseTextEditorWidget::insertLineBelow()
1010 1011 1012 1013 1014 1015 1016 1017 1018 1019
{
    QTextCursor cursor = textCursor();
    cursor.beginEditBlock();
    cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::MoveAnchor);
    cursor.insertBlock();
    indent(document(), cursor, QChar::Null);
    cursor.endEditBlock();
    setTextCursor(cursor);
}

1020
void BaseTextEditorWidget::moveLineUp()
1021 1022 1023 1024
{
    moveLineUpDown(true);
}

1025
void BaseTextEditorWidget::moveLineDown()
1026 1027 1028 1029
{
    moveLineUpDown(false);
}

1030 1031 1032 1033 1034 1035 1036 1037 1038 1039
void BaseTextEditorWidget::uppercaseSelection()
{
    transformSelection(&QString::toUpper);
}

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

1040 1041 1042 1043 1044 1045 1046 1047 1048 1049
void BaseTextEditorWidget::indent()
{
    indentOrUnindent(true);
}

void BaseTextEditorWidget::unindent()
{
    indentOrUnindent(false);
}

1050 1051
void BaseTextEditorWidget::openLinkUnderCursor()
{