vcsbaseeditor.cpp 49.2 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2
3
4
**
** This file is part of Qt Creator
**
hjk's avatar
hjk committed
5
** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
con's avatar
con committed
6
**
7
** Contact: Nokia Corporation (qt-info@nokia.com)
con's avatar
con committed
8
**
9
**
10
** GNU Lesser General Public License Usage
11
**
hjk's avatar
hjk committed
12
13
14
15
16
17
** 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.
18
**
con's avatar
con committed
19
** In addition, as a special exception, Nokia gives you certain additional
hjk's avatar
hjk committed
20
** rights. These rights are described in the Nokia Qt LGPL Exception
con's avatar
con committed
21
22
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
23
24
25
26
27
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
con's avatar
con committed
28
** If you have questions regarding the use of this file, please contact
29
** Nokia at qt-info@nokia.com.
con's avatar
con committed
30
**
31
**************************************************************************/
hjk's avatar
hjk committed
32

con's avatar
con committed
33
34
35
36
#include "vcsbaseeditor.h"
#include "diffhighlighter.h"
#include "baseannotationhighlighter.h"
#include "vcsbaseconstants.h"
37
38
#include "vcsbaseoutputwindow.h"
#include "vcsbaseplugin.h"
con's avatar
con committed
39

40
#include <coreplugin/editormanager/editormanager.h>
Tobias Hunger's avatar
Tobias Hunger committed
41
#include <coreplugin/icore.h>
42
#include <coreplugin/idocument.h>
43
#include <coreplugin/iversioncontrol.h>
44
45
#include <coreplugin/coreconstants.h>
#include <coreplugin/modemanager.h>
Tobias Hunger's avatar
Tobias Hunger committed
46
#include <coreplugin/vcsmanager.h>
47
48
#include <extensionsystem/pluginmanager.h>
#include <projectexplorer/editorconfiguration.h>
con's avatar
con committed
49
#include <projectexplorer/projectexplorer.h>
50
#include <projectexplorer/project.h>
con's avatar
con committed
51
#include <projectexplorer/session.h>
52
#include <texteditor/basetextdocument.h>
53
#include <texteditor/basetextdocumentlayout.h>
54
55
#include <texteditor/fontsettings.h>
#include <texteditor/texteditorconstants.h>
56
#include <texteditor/texteditorsettings.h>
57
#include <utils/qtcassert.h>
58
59
#include <extensionsystem/invoker.h>
#include <extensionsystem/pluginmanager.h>
con's avatar
con committed
60

61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <QDebug>
#include <QFileInfo>
#include <QFile>
#include <QProcess>
#include <QRegExp>
#include <QSet>
#include <QTextCodec>
#include <QTextStream>
#include <QUrl>
#include <QTextBlock>
#include <QDesktopServices>
#include <QAction>
#include <QKeyEvent>
#include <QLayout>
#include <QMenu>
#include <QTextCursor>
#include <QTextEdit>
#include <QComboBox>
#include <QToolBar>
#include <QClipboard>
#include <QApplication>
#include <QMessageBox>
con's avatar
con committed
83

84
/*!
hjk's avatar
hjk committed
85
    \enum VcsBase::EditorContentType
86

hjk's avatar
hjk committed
87
    \brief Contents of a VcsBaseEditor and its interaction.
88
89
90
91
92
93
94
95
96
97
98
99
100

    \value RegularCommandOutput  No special handling.
    \value LogOutput  Log of a file under revision control. Provide  'click on change'
           description and 'Annotate' if is the log of a single file.
    \value AnnotateOutput  Color contents per change number and provide 'click on change' description.
           Context menu offers "Annotate previous version". Expected format:
           \code
           <change description>: file line
           \endcode
    \value DiffOutput  Diff output. Might includes describe output, which consists of a
           header and diffs. Interaction is 'double click in  hunk' which
           opens the file. Context menu offers 'Revert chunk'.

hjk's avatar
hjk committed
101
    \sa VcsBase::VcsBaseEditorWidget
102
103
*/

hjk's avatar
hjk committed
104
namespace VcsBase {
con's avatar
con committed
105

106
/*!
hjk's avatar
hjk committed
107
    \class VcsBase::DiffChunk
108
109
110
111

    \brief A diff chunk consisting of file name and chunk data.
*/

112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
bool DiffChunk::isValid() const
{
    return !fileName.isEmpty() && !chunk.isEmpty();
}

QByteArray DiffChunk::asPatch() const
{
    const QByteArray fileNameBA = QFile::encodeName(fileName);
    QByteArray rc = "--- ";
    rc += fileNameBA;
    rc += "\n+++ ";
    rc += fileNameBA;
    rc += '\n';
    rc += chunk;
    return rc;
}

129
130
131
132
133
134
135
136
137
138
139
140
141
142
namespace Internal {

// Data to be passed to apply/revert diff chunk actions.
class DiffChunkAction
{
public:
    DiffChunkAction(const DiffChunk &dc = DiffChunk(), bool revertIn = false) :
        chunk(dc), revert(revertIn) {}

    DiffChunk chunk;
    bool revert;
};

} // namespace Internal
hjk's avatar
hjk committed
143
} // namespace VcsBase
144

hjk's avatar
hjk committed
145
Q_DECLARE_METATYPE(VcsBase::Internal::DiffChunkAction)
146

hjk's avatar
hjk committed
147
namespace VcsBase {
148

149
/*!
hjk's avatar
hjk committed
150
    \class VcsBase::VcsBaseEditor
151
152
153
154

    \brief An editor with no support for duplicates.

    Creates a browse combo in the toolbar for diff output.
hjk's avatar
hjk committed
155
    It also mirrors the signals of the VcsBaseEditor since the editor
156
157
158
    manager passes the editor around.
*/

hjk's avatar
hjk committed
159
class VcsBaseEditor : public TextEditor::BaseTextEditor
con's avatar
con committed
160
{
161
    Q_OBJECT
con's avatar
con committed
162
public:
hjk's avatar
hjk committed
163
    VcsBaseEditor(VcsBaseEditorWidget *, const VcsBaseEditorParameters *type);
con's avatar
con committed
164
165
166

    bool duplicateSupported() const { return false; }
    Core::IEditor *duplicate(QWidget * /*parent*/) { return 0; }
hjk's avatar
hjk committed
167
    Core::Id id() const { return m_id; }
con's avatar
con committed
168

169
170
    bool isTemporary() const { return m_temporary; }
    void setTemporary(bool t) { m_temporary = t; }
dt's avatar
dt committed
171

172
173
signals:
    void describeRequested(const QString &source, const QString &change);
174
    void annotateRevisionRequested(const QString &source, const QString &change, int line);
175

con's avatar
con committed
176
private:
hjk's avatar
hjk committed
177
    Core::Id m_id;
178
    bool m_temporary;
con's avatar
con committed
179
180
};

hjk's avatar
hjk committed
181
182
VcsBaseEditor::VcsBaseEditor(VcsBaseEditorWidget *widget,
                             const VcsBaseEditorParameters *type)  :
183
    BaseTextEditor(widget),
184
185
    m_id(type->id),
    m_temporary(false)
con's avatar
con committed
186
{
187
    setContext(Core::Context(type->context, TextEditor::Constants::C_TEXTEDITOR));
con's avatar
con committed
188
189
}

190
// Diff editor: creates a browse combo in the toolbar for diff output.
hjk's avatar
hjk committed
191
class VcsBaseDiffEditor : public VcsBaseEditor
192
193
{
public:
hjk's avatar
hjk committed
194
    VcsBaseDiffEditor(VcsBaseEditorWidget *, const VcsBaseEditorParameters *type);
195

196
    QComboBox *diffFileBrowseComboBox() const { return m_diffFileBrowseComboBox; }
197
198

private:
dt's avatar
dt committed
199
    QComboBox *m_diffFileBrowseComboBox;
200
201
};

hjk's avatar
hjk committed
202
203
VcsBaseDiffEditor::VcsBaseDiffEditor(VcsBaseEditorWidget *w, const VcsBaseEditorParameters *type) :
    VcsBaseEditor(w, type),
204
    m_diffFileBrowseComboBox(new QComboBox)
205
206
207
208
209
210
211
{
    m_diffFileBrowseComboBox->setMinimumContentsLength(20);
    // Make the combo box prefer to expand
    QSizePolicy policy = m_diffFileBrowseComboBox->sizePolicy();
    policy.setHorizontalPolicy(QSizePolicy::Expanding);
    m_diffFileBrowseComboBox->setSizePolicy(policy);

212
    insertExtraToolBarWidget(Left, m_diffFileBrowseComboBox);
dt's avatar
dt committed
213
214
}

hjk's avatar
hjk committed
215
// ----------- VcsBaseEditorPrivate
con's avatar
con committed
216

217
218
namespace Internal {

219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
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
344
345
346
347
348
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
379
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
421
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
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
/*! \class AbstractTextCursorHandler
 *  \brief Provides an interface to handle the contents under a text cursor inside an editor
 */
class AbstractTextCursorHandler : public QObject
{
public:
    AbstractTextCursorHandler(VcsBaseEditorWidget *editorWidget = 0);

    /*! \brief Try to find some matching contents under \p cursor
     *
     *  It's the first function to be called because it changes the internal state of the handler.
     *  Other functions (highlightCurrentContents(), handleCurrentContents(), ...) use the result
     *  of the matching
     *
     *  \return true If contents could be found
     */
    virtual bool findContentsUnderCursor(const QTextCursor &cursor);

    //! Highlight (eg underline) the contents matched with findContentsUnderCursor()
    virtual void highlightCurrentContents() = 0;

    //! React to user-interaction with the contents matched with findContentsUnderCursor()
    virtual void handleCurrentContents() = 0;

    //! Contents matched with the last call to findContentsUnderCursor()
    virtual QString currentContents() const = 0;

    /*! \brief Fill \p menu with contextual actions applying to the contents matched
     *         with findContentsUnderCursor()
     */
    virtual void fillContextMenu(QMenu *menu, EditorContentType type) const = 0;

    //! Editor passed on construction of this handler
    VcsBaseEditorWidget *editorWidget() const;

    //! Text cursor used to match contents with findContentsUnderCursor()
    QTextCursor currentCursor() const;

private:
    VcsBaseEditorWidget *m_editorWidget;
    QTextCursor m_currentCursor;
};

AbstractTextCursorHandler::AbstractTextCursorHandler(VcsBaseEditorWidget *editorWidget)
    : QObject(editorWidget),
      m_editorWidget(editorWidget)
{
}

bool AbstractTextCursorHandler::findContentsUnderCursor(const QTextCursor &cursor)
{
    m_currentCursor = cursor;
    return false;
}

VcsBaseEditorWidget *AbstractTextCursorHandler::editorWidget() const
{
    return m_editorWidget;
}

QTextCursor AbstractTextCursorHandler::currentCursor() const
{
    return m_currentCursor;
}

/*! \class ChangeTextCursorHandler
 *  \brief Provides a handler for VCS change identifiers
 */
class ChangeTextCursorHandler : public AbstractTextCursorHandler
{
    Q_OBJECT

public:
    ChangeTextCursorHandler(VcsBaseEditorWidget *editorWidget = 0);

    bool findContentsUnderCursor(const QTextCursor &cursor);
    void highlightCurrentContents();
    void handleCurrentContents();
    QString currentContents() const;
    void fillContextMenu(QMenu *menu, EditorContentType type) const;

private slots:
    void slotDescribe();
    void slotCopyRevision();

private:
    QAction *createDescribeAction(const QString &change) const;
    QAction *createAnnotateAction(const QString &change, bool previous) const;
    QAction *createCopyRevisionAction(const QString &change) const;

    QString m_currentChange;
};

ChangeTextCursorHandler::ChangeTextCursorHandler(VcsBaseEditorWidget *editorWidget)
    : AbstractTextCursorHandler(editorWidget)
{
}

bool ChangeTextCursorHandler::findContentsUnderCursor(const QTextCursor &cursor)
{
    AbstractTextCursorHandler::findContentsUnderCursor(cursor);
    m_currentChange = editorWidget()->changeUnderCursor(cursor);
    return !m_currentChange.isEmpty();
}

void ChangeTextCursorHandler::highlightCurrentContents()
{
    QTextEdit::ExtraSelection sel;
    sel.cursor = currentCursor();
    sel.cursor.select(QTextCursor::WordUnderCursor);
    sel.format.setFontUnderline(true);
    sel.format.setProperty(QTextFormat::UserProperty, m_currentChange);
    editorWidget()->setExtraSelections(VcsBaseEditorWidget::OtherSelection,
                                       QList<QTextEdit::ExtraSelection>() << sel);
}

void ChangeTextCursorHandler::handleCurrentContents()
{
    slotDescribe();
}

void ChangeTextCursorHandler::fillContextMenu(QMenu *menu, EditorContentType type) const
{
    switch (type) {
    case LogOutput: { // Describe current / Annotate file of current
        menu->addSeparator();
        menu->addAction(createCopyRevisionAction(m_currentChange));
        menu->addAction(createDescribeAction(m_currentChange));
        if (editorWidget()->isFileLogAnnotateEnabled())
            menu->addAction(createAnnotateAction(m_currentChange, false));
        break;
    }
    case AnnotateOutput: { // Describe current / annotate previous
        menu->addSeparator();
        menu->addAction(createCopyRevisionAction(m_currentChange));
        menu->addAction(createDescribeAction(m_currentChange));
        const QStringList previousVersions = editorWidget()->annotationPreviousVersions(m_currentChange);
        if (!previousVersions.isEmpty()) {
            menu->addSeparator();
            foreach (const QString &pv, previousVersions)
                menu->addAction(createAnnotateAction(pv, true));
        }
        break;
    }
    default:
        break;
    }
}

QString ChangeTextCursorHandler::currentContents() const
{
    return m_currentChange;
}

void ChangeTextCursorHandler::slotDescribe()
{
    emit editorWidget()->describeRequested(editorWidget()->source(), m_currentChange);
}

void ChangeTextCursorHandler::slotCopyRevision()
{
    QApplication::clipboard()->setText(m_currentChange);
}

QAction *ChangeTextCursorHandler::createDescribeAction(const QString &change) const
{
    QAction *a = new QAction(VcsBaseEditorWidget::tr("Describe change %1").arg(change), 0);
    connect(a, SIGNAL(triggered()), this, SLOT(slotDescribe()));
    return a;
}

QAction *ChangeTextCursorHandler::createAnnotateAction(const QString &change, bool previous) const
{
    // Use 'previous' format if desired and available, else default to standard.
    const QString &format =
            previous && !editorWidget()->annotatePreviousRevisionTextFormat().isEmpty() ?
                editorWidget()->annotatePreviousRevisionTextFormat() :
                editorWidget()->annotateRevisionTextFormat();
    QAction *a = new QAction(format.arg(change), 0);
    a->setData(change);
    connect(a, SIGNAL(triggered()), editorWidget(), SLOT(slotAnnotateRevision()));
    return a;
}

QAction *ChangeTextCursorHandler::createCopyRevisionAction(const QString &change) const
{
    QAction *a = new QAction(editorWidget()->copyRevisionTextFormat().arg(change), 0);
    a->setData(change);
    connect(a, SIGNAL(triggered()), this, SLOT(slotCopyRevision()));
    return a;
}

/*! \class UrlTextCursorHandler
 *  \brief Provides a handler for URL like http://www.nokia.com
 *
 *  The URL pattern can be redefined in sub-classes with setUrlPattern(), by default the pattern
 *  works for hyper-text URL
 */
class UrlTextCursorHandler : public AbstractTextCursorHandler
{
    Q_OBJECT

public:
    UrlTextCursorHandler(VcsBaseEditorWidget *editorWidget = 0);

    bool findContentsUnderCursor(const QTextCursor &cursor);
    void highlightCurrentContents();
    void handleCurrentContents();
    void fillContextMenu(QMenu *menu, EditorContentType type) const;
    QString currentContents() const;

protected slots:
    virtual void slotCopyUrl();
    virtual void slotOpenUrl();

protected:
    void setUrlPattern(const QString &pattern);
    QAction *createOpenUrlAction(const QString &text) const;
    QAction *createCopyUrlAction(const QString &text) const;

private:
    class UrlData
    {
    public:
        int startColumn;
        QString url;
    };

    UrlData m_urlData;
    QString m_urlPattern;
};

UrlTextCursorHandler::UrlTextCursorHandler(VcsBaseEditorWidget *editorWidget)
    : AbstractTextCursorHandler(editorWidget)
{
    setUrlPattern(QLatin1String("https?\\://[^\\s]+"));
}

bool UrlTextCursorHandler::findContentsUnderCursor(const QTextCursor &cursor)
{
    AbstractTextCursorHandler::findContentsUnderCursor(cursor);

    m_urlData.url.clear();
    m_urlData.startColumn = -1;

    QTextCursor cursorForUrl = cursor;
    cursorForUrl.select(QTextCursor::LineUnderCursor);
    if (cursorForUrl.hasSelection()) {
        const QString line = cursorForUrl.selectedText();
        const int cursorCol = cursor.columnNumber();
        const QRegExp urlRx(m_urlPattern);
        int urlMatchIndex = -1;
        do {
            urlMatchIndex = urlRx.indexIn(line, urlMatchIndex + 1);
            if (urlMatchIndex != -1) {
                const QString url = urlRx.cap(0);
                if (urlMatchIndex <= cursorCol && cursorCol <= urlMatchIndex + url.length()) {
                    m_urlData.startColumn = urlMatchIndex;
                    m_urlData.url = url;
                }
            }
        } while (urlMatchIndex != -1 && m_urlData.startColumn == -1);
    }

    return m_urlData.startColumn != -1;
}

void UrlTextCursorHandler::highlightCurrentContents()
{
    QTextEdit::ExtraSelection sel;
    sel.cursor = currentCursor();
    sel.cursor.setPosition(currentCursor().position()
                           - (currentCursor().columnNumber() - m_urlData.startColumn));
    sel.cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, m_urlData.url.length());
    sel.format.setFontUnderline(true);
    sel.format.setForeground(Qt::blue);
    sel.format.setUnderlineColor(Qt::blue);
    sel.format.setProperty(QTextFormat::UserProperty, m_urlData.url);
    editorWidget()->setExtraSelections(VcsBaseEditorWidget::OtherSelection,
                                       QList<QTextEdit::ExtraSelection>() << sel);
}

void UrlTextCursorHandler::handleCurrentContents()
{
    slotOpenUrl();
}

void UrlTextCursorHandler::fillContextMenu(QMenu *menu, EditorContentType type) const
{
    Q_UNUSED(type);
    menu->addSeparator();
    menu->addAction(createOpenUrlAction(tr("Open URL in browser ...")));
    menu->addAction(createCopyUrlAction(tr("Copy URL location")));
}

QString UrlTextCursorHandler::currentContents() const
{
    return  m_urlData.url;
}

void UrlTextCursorHandler::setUrlPattern(const QString &pattern)
{
    m_urlPattern = pattern;
}

void UrlTextCursorHandler::slotCopyUrl()
{
    QApplication::clipboard()->setText(m_urlData.url);
}

void UrlTextCursorHandler::slotOpenUrl()
{
    QDesktopServices::openUrl(QUrl(m_urlData.url));
}

QAction *UrlTextCursorHandler::createOpenUrlAction(const QString &text) const
{
    QAction *a = new QAction(text, 0);
    a->setData(m_urlData.url);
    connect(a, SIGNAL(triggered()), this, SLOT(slotOpenUrl()));
    return a;
}

QAction *UrlTextCursorHandler::createCopyUrlAction(const QString &text) const
{
    QAction *a = new QAction(text, 0);
    a->setData(m_urlData.url);
    connect(a, SIGNAL(triggered()), this, SLOT(slotCopyUrl()));
    return a;
}

/*! \class EmailTextCursorHandler
 *  \brief Provides a handler for email addresses
 */
class EmailTextCursorHandler : public UrlTextCursorHandler
{
    Q_OBJECT

public:
    EmailTextCursorHandler(VcsBaseEditorWidget *editorWidget = 0);
    void fillContextMenu(QMenu *menu, EditorContentType type) const;

protected slots:
    void slotOpenUrl();
};

EmailTextCursorHandler::EmailTextCursorHandler(VcsBaseEditorWidget *editorWidget)
    : UrlTextCursorHandler(editorWidget)
{
    setUrlPattern(QLatin1String("[a-zA-Z0-9_\\.]+@[a-zA-Z0-9_\\.]+"));
}

void EmailTextCursorHandler::fillContextMenu(QMenu *menu, EditorContentType type) const
{
    Q_UNUSED(type);
    menu->addSeparator();
    menu->addAction(createOpenUrlAction(tr("Send email to ...")));
    menu->addAction(createCopyUrlAction(tr("Copy email address")));
}

void EmailTextCursorHandler::slotOpenUrl()
{
    QDesktopServices::openUrl(QUrl(QLatin1String("mailto:") + currentContents()));
}

hjk's avatar
hjk committed
584
class VcsBaseEditorWidgetPrivate
585
{
586
public:
587
588
589
    VcsBaseEditorWidgetPrivate(VcsBaseEditorWidget* editorWidget, const VcsBaseEditorParameters *type);

    AbstractTextCursorHandler *findTextCursorHandler(const QTextCursor &cursor);
con's avatar
con committed
590

hjk's avatar
hjk committed
591
    const VcsBaseEditorParameters *m_parameters;
592

con's avatar
con committed
593
    QString m_source;
594
    QString m_diffBaseDirectory;
595
596
597
598

    QRegExp m_diffFilePattern;
    QList<int> m_diffSections; // line number where this section starts
    int m_cursorLine;
599
    QString m_annotateRevisionTextFormat;
600
    QString m_annotatePreviousRevisionTextFormat;
601
    QString m_copyRevisionTextFormat;
602
    bool m_fileLogAnnotateEnabled;
603
    TextEditor::BaseTextEditor *m_editor;
604
    QWidget *m_configurationWidget;
605
    bool m_revertChunkEnabled;
606
    bool m_mouseDragging;
607
    QList<AbstractTextCursorHandler *> m_textCursorHandlers;
608
609

    QColor m_backgroundColor;
con's avatar
con committed
610
611
};

612
613
VcsBaseEditorWidgetPrivate::VcsBaseEditorWidgetPrivate(VcsBaseEditorWidget *editorWidget,
                                                       const VcsBaseEditorParameters *type)  :
614
615
    m_parameters(type),
    m_cursorLine(-1),
hjk's avatar
hjk committed
616
617
    m_annotateRevisionTextFormat(VcsBaseEditorWidget::tr("Annotate \"%1\"")),
    m_copyRevisionTextFormat(VcsBaseEditorWidget::tr("Copy \"%1\"")),
618
    m_fileLogAnnotateEnabled(false),
619
    m_editor(0),
620
    m_configurationWidget(0),
621
622
    m_revertChunkEnabled(false),
    m_mouseDragging(false)
con's avatar
con committed
623
{
624
625
626
627
628
629
630
631
632
633
634
635
    m_textCursorHandlers.append(new ChangeTextCursorHandler(editorWidget));
    m_textCursorHandlers.append(new UrlTextCursorHandler(editorWidget));
    m_textCursorHandlers.append(new EmailTextCursorHandler(editorWidget));
}

AbstractTextCursorHandler *VcsBaseEditorWidgetPrivate::findTextCursorHandler(const QTextCursor &cursor)
{
    foreach (AbstractTextCursorHandler *handler, m_textCursorHandlers) {
        if (handler->findContentsUnderCursor(cursor))
            return handler;
    }
    return 0;
con's avatar
con committed
636
637
}

638
639
} // namespace Internal

640
/*!
hjk's avatar
hjk committed
641
    \struct VcsBase::VcsBaseEditorParameters
642
643
644
645
646

    \brief Helper struct used to parametrize an editor with mime type, context
    and id. The extension is currently only a suggestion when running
    VCS commands with redirection.

hjk's avatar
hjk committed
647
    \sa VcsBase::VcsBaseEditorWidget, VcsBase::BaseVcsEditorFactory, VcsBase::EditorContentType
648
649
650
*/

/*!
hjk's avatar
hjk committed
651
    \class VcsBase::VcsBaseEditorWidget
652
653
654
655
656
657
658
659

    \brief Base class for editors showing version control system output
    of the type enumerated by EditorContentType.

    The source property should contain the file or directory the log
    refers to and will be emitted with describeRequested().
    This is for VCS that need a current directory.

hjk's avatar
hjk committed
660
    \sa VcsBase::BaseVcsEditorFactory, VcsBase::VcsBaseEditorParameters, VcsBase::EditorContentType
661
662
*/

hjk's avatar
hjk committed
663
VcsBaseEditorWidget::VcsBaseEditorWidget(const VcsBaseEditorParameters *type, QWidget *parent)
664
  : BaseTextEditorWidget(parent),
665
    d(new Internal::VcsBaseEditorWidgetPrivate(this, type))
con's avatar
con committed
666
667
{
    viewport()->setMouseTracking(true);
668
    setMimeType(QLatin1String(d->m_parameters->mimeType));
con's avatar
con committed
669
670
}

hjk's avatar
hjk committed
671
void VcsBaseEditorWidget::init()
con's avatar
con committed
672
{
673
    switch (d->m_parameters->type) {
con's avatar
con committed
674
675
676
677
678
679
    case RegularCommandOutput:
    case LogOutput:
    case AnnotateOutput:
        // Annotation highlighting depends on contents, which is set later on
        connect(this, SIGNAL(textChanged()), this, SLOT(slotActivateAnnotation()));
        break;
680
681
    case DiffOutput: {
        DiffHighlighter *dh = createDiffHighlighter();
682
        setCodeFoldingSupported(true);
683
684
685
686
687
        baseTextDocument()->setSyntaxHighlighter(dh);
        d->m_diffFilePattern = dh->filePattern();
        connect(this, SIGNAL(textChanged()), this, SLOT(slotPopulateDiffBrowser()));
        connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(slotDiffCursorPositionChanged()));
    }
con's avatar
con committed
688
689
        break;
    }
690
    TextEditor::TextEditorSettings::instance()->initializeEditor(this);
691
692
    // override revisions display (green or red bar on the left, marking changes):
    setRevisionsVisible(false);
con's avatar
con committed
693
694
}

hjk's avatar
hjk committed
695
VcsBaseEditorWidget::~VcsBaseEditorWidget()
con's avatar
con committed
696
{
697
    delete d;
con's avatar
con committed
698
699
}

hjk's avatar
hjk committed
700
void VcsBaseEditorWidget::setForceReadOnly(bool b)
701
{
hjk's avatar
hjk committed
702
    VcsBaseEditor *eda = qobject_cast<VcsBaseEditor *>(editor());
703
    QTC_ASSERT(eda != 0, return);
704
705
706
707
    setReadOnly(b);
    eda->setTemporary(b);
}

hjk's avatar
hjk committed
708
QString VcsBaseEditorWidget::source() const
con's avatar
con committed
709
{
710
    return d->m_source;
con's avatar
con committed
711
712
}

hjk's avatar
hjk committed
713
void VcsBaseEditorWidget::setSource(const  QString &source)
con's avatar
con committed
714
{
715
    d->m_source = source;
con's avatar
con committed
716
717
}

hjk's avatar
hjk committed
718
QString VcsBaseEditorWidget::annotateRevisionTextFormat() const
719
720
721
722
{
    return d->m_annotateRevisionTextFormat;
}

hjk's avatar
hjk committed
723
void VcsBaseEditorWidget::setAnnotateRevisionTextFormat(const QString &f)
724
725
726
727
{
    d->m_annotateRevisionTextFormat = f;
}

hjk's avatar
hjk committed
728
QString VcsBaseEditorWidget::annotatePreviousRevisionTextFormat() const
729
730
731
732
{
    return d->m_annotatePreviousRevisionTextFormat;
}

hjk's avatar
hjk committed
733
void VcsBaseEditorWidget::setAnnotatePreviousRevisionTextFormat(const QString &f)
734
735
736
737
{
    d->m_annotatePreviousRevisionTextFormat = f;
}

hjk's avatar
hjk committed
738
QString VcsBaseEditorWidget::copyRevisionTextFormat() const
739
740
741
742
{
    return d->m_copyRevisionTextFormat;
}

hjk's avatar
hjk committed
743
void VcsBaseEditorWidget::setCopyRevisionTextFormat(const QString &f)
744
745
746
747
{
    d->m_copyRevisionTextFormat = f;
}

hjk's avatar
hjk committed
748
bool VcsBaseEditorWidget::isFileLogAnnotateEnabled() const
749
750
751
752
{
    return d->m_fileLogAnnotateEnabled;
}

hjk's avatar
hjk committed
753
void VcsBaseEditorWidget::setFileLogAnnotateEnabled(bool e)
754
755
756
757
{
    d->m_fileLogAnnotateEnabled = e;
}

hjk's avatar
hjk committed
758
QString VcsBaseEditorWidget::diffBaseDirectory() const
759
760
761
762
{
    return d->m_diffBaseDirectory;
}

hjk's avatar
hjk committed
763
void VcsBaseEditorWidget::setDiffBaseDirectory(const QString &bd)
764
765
766
767
{
    d->m_diffBaseDirectory = bd;
}

hjk's avatar
hjk committed
768
QTextCodec *VcsBaseEditorWidget::codec() const
con's avatar
con committed
769
{
770
    return const_cast<QTextCodec *>(baseTextDocument()->codec());
con's avatar
con committed
771
772
}

hjk's avatar
hjk committed
773
void VcsBaseEditorWidget::setCodec(QTextCodec *c)
con's avatar
con committed
774
775
776
777
778
779
780
781
{
    if (c) {
        baseTextDocument()->setCodec(c);
    } else {
        qWarning("%s: Attempt to set 0 codec.", Q_FUNC_INFO);
    }
}

hjk's avatar
hjk committed
782
EditorContentType VcsBaseEditorWidget::contentType() const
con's avatar
con committed
783
{
784
    return d->m_parameters->type;
con's avatar
con committed
785
786
}

hjk's avatar
hjk committed
787
bool VcsBaseEditorWidget::isModified() const
con's avatar
con committed
788
789
790
791
{
    return false;
}

hjk's avatar
hjk committed
792
TextEditor::BaseTextEditor *VcsBaseEditorWidget::createEditor()
con's avatar
con committed
793
{
794
    TextEditor::BaseTextEditor *editor = 0;
795
796
    if (d->m_parameters->type == DiffOutput) {
        // Diff: set up diff file browsing
hjk's avatar
hjk committed
797
        VcsBaseDiffEditor *de = new VcsBaseDiffEditor(this, d->m_parameters);
798
        QComboBox *diffBrowseComboBox = de->diffFileBrowseComboBox();
799
        connect(diffBrowseComboBox, SIGNAL(activated(int)), this, SLOT(slotDiffBrowse(int)));
800
        editor = de;
801
    } else {
hjk's avatar
hjk committed
802
        editor = new VcsBaseEditor(this, d->m_parameters);
803
    }
804
    d->m_editor = editor;
805

806
807
    // Pass on signals.
    connect(this, SIGNAL(describeRequested(QString,QString)),
808
            editor, SIGNAL(describeRequested(QString,QString)));
809
    connect(this, SIGNAL(annotateRevisionRequested(QString,QString,int)),
810
811
            editor, SIGNAL(annotateRevisionRequested(QString,QString,int)));
    return editor;
812
813
}

hjk's avatar
hjk committed
814
void VcsBaseEditorWidget::slotPopulateDiffBrowser()
815
{
hjk's avatar
hjk committed
816
    VcsBaseDiffEditor *de = static_cast<VcsBaseDiffEditor*>(editor());
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
    QComboBox *diffBrowseComboBox = de->diffFileBrowseComboBox();
    diffBrowseComboBox->clear();
    d->m_diffSections.clear();
    // Create a list of section line numbers (diffed files)
    // and populate combo with filenames.
    const QTextBlock cend = document()->end();
    int lineNumber = 0;
    QString lastFileName;
    for (QTextBlock it = document()->begin(); it != cend; it = it.next(), lineNumber++) {
        const QString text = it.text();
        // Check for a new diff section (not repeating the last filename)
        if (d->m_diffFilePattern.exactMatch(text)) {
            const QString file = fileNameFromDiffSpecification(it);
            if (!file.isEmpty() && lastFileName != file) {
                lastFileName = file;
                // ignore any headers
                d->m_diffSections.push_back(d->m_diffSections.empty() ? 0 : lineNumber);
                diffBrowseComboBox->addItem(QFileInfo(file).fileName());
            }
        }
    }
}

hjk's avatar
hjk committed
840
void VcsBaseEditorWidget::slotDiffBrowse(int index)
841
842
843
{
    // goto diffed file as indicated by index/line number
    if (index < 0 || index >= d->m_diffSections.size())
844
        return;
845
846
847
848
849
850
851
852
853
    const int lineNumber = d->m_diffSections.at(index) + 1; // TextEdit uses 1..n convention
    // check if we need to do something, especially to avoid messing up navigation history
    int currentLine, currentColumn;
    convertPosition(position(), &currentLine, &currentColumn);
    if (lineNumber != currentLine) {
        Core::EditorManager *editorManager = Core::EditorManager::instance();
        editorManager->addCurrentPositionToNavigationHistory();
        gotoLine(lineNumber, 0);
    }
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
}

// Locate a line number in the list of diff sections.
static int sectionOfLine(int line, const QList<int> &sections)
{
    const int sectionCount = sections.size();
    if (!sectionCount)
        return -1;
    // The section at s indicates where the section begins.
    for (int s = 0; s < sectionCount; s++) {
        if (line < sections.at(s))
            return s - 1;
    }
    return sectionCount - 1;
}

hjk's avatar
hjk committed
870
void VcsBaseEditorWidget::slotDiffCursorPositionChanged()
871
872
873
874
{
    // Adapt diff file browse combo to new position
    // if the cursor goes across a file line.
    QTC_ASSERT(d->m_parameters->type == DiffOutput, return)
875
    const int newCursorLine = textCursor().blockNumber();
876
877
878
879
880
881
    if (newCursorLine == d->m_cursorLine)
        return;
    // Which section does it belong to?
    d->m_cursorLine = newCursorLine;
    const int section = sectionOfLine(d->m_cursorLine, d->m_diffSections);
    if (section != -1) {
hjk's avatar
hjk committed
882
        VcsBaseDiffEditor *de = static_cast<VcsBaseDiffEditor*>(editor());
883
884
885
886
887
888
889
        QComboBox *diffBrowseComboBox = de->diffFileBrowseComboBox();
        if (diffBrowseComboBox->currentIndex() != section) {
            const bool blocked = diffBrowseComboBox->blockSignals(true);
            diffBrowseComboBox->setCurrentIndex(section);
            diffBrowseComboBox->blockSignals(blocked);
        }
    }
con's avatar
con committed
890
891
}

hjk's avatar
hjk committed
892
void VcsBaseEditorWidget::contextMenuEvent(QContextMenuEvent *e)
con's avatar
con committed
893
894
895
{
    QMenu *menu = createStandardContextMenu();
    // 'click on change-interaction'
896
897
    switch (d->m_parameters->type) {
    case LogOutput:
898
899
900
901
902
    case AnnotateOutput: {
        const QTextCursor cursor = cursorForPosition(e->pos());
        Internal::AbstractTextCursorHandler *handler = d->findTextCursorHandler(cursor);
        if (handler != 0)
            handler->fillContextMenu(menu, d->m_parameters->type);
903
        break;
904
    }
905
906
    case DiffOutput: {
        menu->addSeparator();
907
908
909
        connect(menu->addAction(tr("Send to CodePaster...")), SIGNAL(triggered()),
                this, SLOT(slotPaste()));
        menu->addSeparator();
910
        // Apply/revert diff chunk.
911
        const DiffChunk chunk = diffChunk(cursorForPosition(e->pos()));
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
        const bool canApply = canApplyDiffChunk(chunk);
        // Apply a chunk from a diff loaded into the editor. This typically will
        // not have the 'source' property set and thus will only work if the working
        // directory matches that of the patch (see findDiffFile()). In addition,
        // the user has "Open With" and choose the right diff editor so that
        // fileNameFromDiffSpecification() works.
        QAction *applyAction = menu->addAction(tr("Apply Chunk..."));
        applyAction->setEnabled(canApply);
        applyAction->setData(qVariantFromValue(Internal::DiffChunkAction(chunk, false)));
        connect(applyAction, SIGNAL(triggered()), this, SLOT(slotApplyDiffChunk()));
        // Revert a chunk from a VCS diff, which might be linked to reloading the diff.
        QAction *revertAction = menu->addAction(tr("Revert Chunk..."));
        revertAction->setEnabled(isRevertDiffChunkEnabled() && canApply);
        revertAction->setData(qVariantFromValue(Internal::DiffChunkAction(chunk, true)));
        connect(revertAction, SIGNAL(triggered()), this, SLOT(slotApplyDiffChunk()));
927
928
929
930
    }
        break;
    default:
        break;
con's avatar
con committed
931
932
933
934
935
    }
    menu->exec(e->globalPos());
    delete menu;
}

hjk's avatar
hjk committed
936
void VcsBaseEditorWidget::mouseMoveEvent(QMouseEvent *e)
con's avatar
con committed
937
{
938
939
940
941
942
943
    if (e->buttons()) {
        d->m_mouseDragging = true;
        TextEditor::BaseTextEditorWidget::mouseMoveEvent(e);
        return;
    }

944
945
946
    bool overrideCursor = false;
    Qt::CursorShape cursorShape;

947
    if (d->m_parameters->type == LogOutput || d->m_parameters->type == AnnotateOutput) {
con's avatar
con committed
948
        // Link emulation behaviour for 'click on change-interaction'
949
950
951
952
        const QTextCursor cursor = cursorForPosition(e->pos());
        Internal::AbstractTextCursorHandler *handler = d->findTextCursorHandler(cursor);
        if (handler != 0) {
            handler->highlightCurrentContents();
953
954
            overrideCursor = true;
            cursorShape = Qt::PointingHandCursor;
con's avatar
con committed
955
        }
956
957
    } else {
        setExtraSelections(OtherSelection, QList<QTextEdit::ExtraSelection>());
958
959
        overrideCursor = true;
        cursorShape = Qt::IBeamCursor;
con's avatar
con committed
960
    }
961
    TextEditor::BaseTextEditorWidget::mouseMoveEvent(e);
962
963
964

    if (overrideCursor)
        viewport()->setCursor(cursorShape);
con's avatar
con committed
965
966
}

hjk's avatar
hjk committed
967
void VcsBaseEditorWidget::mouseReleaseEvent(QMouseEvent *e)
con's avatar
con committed
968
{
969
970
971
    const bool wasDragging = d->m_mouseDragging;
    d->m_mouseDragging = false;
    if (!wasDragging && (d->m_parameters->type == LogOutput || d->m_parameters->type == AnnotateOutput)) {
con's avatar
con committed
972
        if (e->button() == Qt::LeftButton &&!(e->modifiers() & Qt::ShiftModifier)) {
973
974
975
976
            const QTextCursor cursor = cursorForPosition(e->pos());
            Internal::AbstractTextCursorHandler *handler = d->findTextCursorHandler(cursor);
            if (handler != 0) {
                handler->handleCurrentContents();
con's avatar
con committed
977
978
979
980
981
                e->accept();
                return;
            }
        }
    }
982
    TextEditor::BaseTextEditorWidget::mouseReleaseEvent(e);
con's avatar
con committed
983
984
}

hjk's avatar
hjk committed
985
void VcsBaseEditorWidget::mouseDoubleClickEvent(QMouseEvent *e)
con's avatar
con committed
986
{
987
    if (d->m_parameters->type == DiffOutput) {
con's avatar
con committed
988
989
990
991
992
        if (e->button() == Qt::LeftButton &&!(e->modifiers() & Qt::ShiftModifier)) {
            QTextCursor cursor = cursorForPosition(e->pos());
            jumpToChangeFromDiff(cursor);
        }
    }
993
    TextEditor::BaseTextEditorWidget::mouseDoubleClickEvent(e);
con's avatar
con committed
994
995
}

hjk's avatar
hjk committed
996
void VcsBaseEditorWidget::keyPressEvent(QKeyEvent *e)
con's avatar
con committed
997
{
998
999
1000
    // Do not intercept return in editable patches.
    if (d->m_parameters->type == DiffOutput && isReadOnly()
        && (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return)) {
con's avatar
con committed
1001
1002
1003
        jumpToChangeFromDiff(textCursor());
        return;
    }
1004
    BaseTextEditorWidget::keyPressEvent(e);
con's avatar
con committed
1005
1006
}

hjk's avatar
hjk committed
1007
void VcsBaseEditorWidget::slotActivateAnnotation()
con's avatar
con committed
1008
1009
1010
{
    // The annotation highlighting depends on contents (change number
    // set with assigned colors)
1011
    if (d->m_parameters->type != AnnotateOutput)
con's avatar
con committed
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
        return;

    const QSet<QString> changes = annotationChanges();
    if (changes.isEmpty())
        return;

    disconnect(this, SIGNAL(textChanged()), this, SLOT(slotActivateAnnotation()));

    if (BaseAnnotationHighlighter *ah = qobject_cast<BaseAnnotationHighlighter *>(baseTextDocument()->syntaxHighlighter())) {
        ah->setChangeNumbers(changes);
        ah->rehighlight();
    } else {
1024
        baseTextDocument()->setSyntaxHighlighter(createAnnotationHighlighter(changes, d->m_backgroundColor));
con's avatar
con committed
1025
1026
1027
    }
}

1028
1029
1030
1031
1032
1033
// Check for a chunk of
//       - changes          :  "@@ -91,7 +95,7 @@"
//       - merged conflicts : "@@@ -91,7 +95,7 @@@"
// and return the modified line number (here 95).
// Note that git appends stuff after "  @@"/" @@@" (function names, etc.).
static inline bool checkChunkLine(const QString &line, int *modifiedLineNumber, int numberOfAts)
con's avatar
con committed
1034
{
1035
1036
    const QString ats(numberOfAts, QLatin1Char('@'));
    if (!line.startsWith(ats + QLatin1Char(' ')))
con's avatar
con committed
1037
        return false;
1038
1039
    const int len = ats.size() + 1;
    const int endPos = line.indexOf(QLatin1Char(' ') + ats, len);
con's avatar
con committed
1040
1041
1042
1043
    if (endPos == -1)
        return false;
    // the first chunk range applies to the original file, the second one to
    // the modified file, the one we're interested int
1044
    const int plusPos = line.indexOf(QLatin1Char('+'), len);
con's avatar
con committed
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
    if (plusPos == -1 || plusPos > endPos)
        return false;
    const int lineNumberPos = plusPos + 1;
    const int commaPos = line.indexOf(QLatin1Char(','), lineNumberPos);
    if (commaPos == -1 || commaPos > endPos)
        return false;
    const QString lineNumberStr = line.mid(lineNumberPos, commaPos - lineNumberPos);
    bool ok;
    *modifiedLineNumber = lineNumberStr.toInt(&ok);
    return ok;
}

1057
1058
1059
1060
1061
1062
1063
static inline bool checkChunkLine(const QString &line, int *modifiedLineNumber)
{
    if (checkChunkLine(line, modifiedLineNumber, 2))
        return true;
    return checkChunkLine(line, modifiedLineNumber, 3);
}

hjk's avatar
hjk committed
1064
void VcsBaseEditorWidget::jumpToChangeFromDiff(QTextCursor cursor)
con's avatar
con committed
1065
1066
1067
1068
1069
1070
{
    int chunkStart = 0;
    int lineCount = -1;
    const QChar deletionIndicator = QLatin1Char('-');
    // find nearest change hunk
    QTextBlock block = cursor.block();
1071
1072
1073
    if (TextEditor::BaseTextDocumentLayout::foldingIndent(block) <= 1)
        /* We are in a diff header, do not jump anywhere. DiffHighlighter sets the foldingIndent for us. */
        return;
con's avatar
con committed
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
    for ( ; block.isValid() ; block = block.previous()) {
        const QString line = block.text();
        if (checkChunkLine(line, &chunkStart)) {
            break;
        } else {
            if (!line.startsWith(deletionIndicator))
                ++lineCount;
        }
    }

    if (chunkStart == -1 || lineCount < 0 || !block.isValid())
        return;

    // find the filename in previous line, map depot name back
    block = block.previous();
    if (!block.isValid())
        return;
1091
    const QString fileName = findDiffFile(fileNameFromDiffSpecification(block));
con's avatar
con committed
1092
1093
1094
1095
1096
1097

    const bool exists = fileName.isEmpty() ? false : QFile::exists(fileName);

    if (!exists)
        return;

1098
    Core::EditorManager *em = Core::EditorManager::instance();
hjk's avatar
hjk committed
1099
    Core::IEditor *ed = em->openEditor(fileName, Core::Id(), Core::EditorManager::ModeSwitch);
1100
    if (TextEditor::ITextEditor *editor = qobject_cast<TextEditor::ITextEditor *>(ed))
con's avatar
con committed
1101
1102
1103
        editor->gotoLine(chunkStart + lineCount);
}

1104
// cut out chunk and determine file name.
hjk's avatar
hjk committed
1105
DiffChunk VcsBaseEditorWidget::diffChunk(QTextCursor cursor) const
1106
1107
1108
1109
1110
{
    QTC_ASSERT(d->m_parameters->type == DiffOutput, return DiffChunk(); )
    DiffChunk rc;
    // Search back for start of chunk.
    QTextBlock block = cursor.block();
1111
    if (block.isValid() && TextEditor::BaseTextDocumentLayout::foldingIndent(block) <= 1)
1112
1113
1114
        /* We are in a diff header, not in a chunk! DiffHighlighter sets the foldingIndent for us. */
        return rc;

1115
1116
1117
1118
1119
1120
1121
1122
    int chunkStart = 0;
    for ( ; block.isValid() ; block = block.previous()) {
        if (checkChunkLine(block.text(), &chunkStart)) {
            break;
        }
    }
    if (!chunkStart || !block.isValid())
        return rc;
1123
    rc.fileName = findDiffFile(fileNameFromDiffSpecification(block));
1124
1125
1126
1127
    if (rc.fileName.isEmpty())
        return rc;
    // Concatenate chunk and convert
    QString unicode = block.text();
1128
1129
    if (!unicode.endsWith(QLatin1Char('\n'))) // Missing in case of hg.
        unicode.append(QLatin1Char('\n'));
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
    for (block = block.next() ; block.isValid() ; block = block.next()) {
        const QString line = block.text();
        if (checkChunkLine(line, &chunkStart)) {
            break;
        } else {
            unicode += line;
            unicode += QLatin1Char('\n');
        }
    }
    const QTextCodec *cd = textCodec();
    rc.chunk = cd ? cd->fromUnicode(unicode) : unicode.toLocal8Bit();
    return rc;
}

hjk's avatar
hjk committed
1144
void VcsBaseEditorWidget::setPlainTextData(const QByteArray &data)
con's avatar
con committed
1145
{
1146
1147
1148
1149
1150
    if (data.size() > Core::EditorManager::maxTextFileSize()) {
        setPlainText(msgTextTooLarge(data.size()));
    } else {
        setPlainText(codec()->toUnicode(data));
    }
con's avatar
con committed
1151
1152
}

hjk's avatar
hjk committed
1153
void VcsBaseEditorWidget::setFontSettings(const TextEditor::FontSettings &fs)
con's avatar
con committed
1154
{
1155
    TextEditor::BaseTextEditorWidget::setFontSettings(fs);
1156
1157
1158
    d->m_backgroundColor = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_TEXT))
            .brushProperty(QTextFormat::BackgroundBrush).color();

1159
    if (d->m_parameters->type == DiffOutput) {
con's avatar
con committed
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
        if (DiffHighlighter *highlighter = qobject_cast<DiffHighlighter*>(baseTextDocument()->syntaxHighlighter())) {
            static QVector<QString> categories;
            if (categories.isEmpty()) {
                categories << QLatin1String(TextEditor::Constants::C_TEXT)
                           << QLatin1String(TextEditor::Constants::C_ADDED_LINE)
                           << QLatin1String(TextEditor::Constants::C_REMOVED_LINE)
                           << QLatin1String(TextEditor::Constants::C_DIFF_FILE)
                           << QLatin1String(TextEditor::Constants::C_DIFF_LOCATION);
            }
            highlighter->setFormats(fs.toTextCharFormats(categories));
            highlighter->rehighlight();
        }
1172
1173
1174
1175
1176
    } else if (d->m_parameters->type == AnnotateOutput) {
        if (BaseAnnotationHighlighter *highlighter = qobject_cast<BaseAnnotationHighlighter *>(baseTextDocument()->syntaxHighlighter())) {
            highlighter->setBackgroundColor(d->m_backgroundColor);
            highlighter->rehighlight();
        }
con's avatar
con committed
1177
1178
1179
    }
}

hjk's avatar
hjk committed
1180
const VcsBaseEditorParameters *VcsBaseEditorWidget::findType(const VcsBaseEditorParameters *array,
con's avatar
con committed
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
                                                       int arraySize,
                                                       EditorContentType et)
{
    for (int i = 0; i < arraySize; i++)
        if (array[i].type == et)
            return array + i;
    return 0;
}

// Find the codec used for a file querying the editor.
1191
static QTextCodec *findFileCodec(const QString &source)
con's avatar
con committed
1192
1193
1194
{
    typedef QList<Core::IEditor *> EditorList;

1195
    const EditorList editors = Core::EditorManager::instance()->editorsForFileName(source);
con's avatar
con committed
1196
1197
1198
    if (!editors.empty()) {
        const EditorList::const_iterator ecend =  editors.constEnd();
        for (EditorList::const_iterator it = editors.constBegin(); it != ecend; ++it)
1199
1200
            if (const TextEditor::BaseTextEditor *be = qobject_cast<const TextEditor::BaseTextEditor *>(*it)) {
                QTextCodec *codec = be->editorWidget()->textCodec();
con's avatar
con committed
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
                return codec;
            }
    }
    return 0;
}

// Find the codec by checking the projects (root dir of project file)
static QTextCodec *findProjectCodec(const QString &dir)
{
    typedef  QList<ProjectExplorer::Project*> ProjectList;
    // Try to find a project under which file tree the file is.
    const ProjectExplorer::SessionManager *sm = ProjectExplorer::ProjectExplorerPlugin::instance()->session();
    const ProjectList projects = sm->projects();
    if (!projects.empty()) {
        const ProjectList::const_iterator pcend = projects.constEnd();
        for (ProjectList::const_iterator it = projects.constBegin(); it != pcend; ++it)
1217
1218
            if (const Core::IDocument *document = (*it)->document())
                if (document->fileName().startsWith(dir)) {
1219
                    QTextCodec *codec = (*it)->editorConfiguration()->textCodec();
con's avatar
con committed
1220
1221
1222
1223
1224
1225
                    return codec;
                }
    }
    return 0;
}

hjk's avatar
hjk committed
1226
QTextCodec *VcsBaseEditorWidget::getCodec(const QString &source)
con's avatar
con committed
1227
1228
1229
1230
1231
{
    if (!source.isEmpty()) {
        // Check file
        const QFileInfo sourceFi(source);
        if (sourceFi.isFile())
1232
            if (QTextCodec *fc = findFileCodec(source))
con's avatar
con committed
1233
1234
1235
1236
1237
1238
1239
1240
1241
                return fc;
        // Find by project via directory
        if (QTextCodec *pc = findProjectCodec(sourceFi.isFile() ? sourceFi.absolutePath() : source))
            return pc;
    }
    QTextCodec *sys = QTextCodec::codecForLocale();
    return sys;
}

hjk's avatar
hjk committed
1242
QTextCodec *VcsBaseEditorWidget::getCodec(const QString &workingDirectory, const QStringList &files)
1243
1244
1245
1246
1247
1248
{
    if (files.empty())
        return getCodec(workingDirectory);
    return getCodec(workingDirectory + QLatin1Char('/') + files.front());
}

hjk's avatar
hjk committed
1249
VcsBaseEditorWidget *VcsBaseEditorWidget::getVcsBaseEditor(const Core::IEditor *editor)
con's avatar
con committed
1250
{
1251
    if (const TextEditor::BaseTextEditor *be = qobject_cast<const TextEditor::BaseTextEditor *>(editor))
hjk's avatar
hjk committed
1252
        return qobject_cast<VcsBaseEditorWidget *>(be->editorWidget());
con's avatar
con committed
1253
1254
1255
    return 0;
}

1256
// Return line number of current editor if it matches.
hjk's avatar
hjk committed
1257
int VcsBaseEditorWidget::lineNumberOfCurrentEditor(const QString &currentFile)
1258
1259
1260
1261
1262
{
    Core::IEditor *ed = Core::EditorManager::instance()->currentEditor();
    if (!ed)
        return -1;
    if (!currentFile.isEmpty()) {
1263
1264
        const Core::IDocument *idocument  = ed->document();
        if (!idocument || idocument->fileName() != currentFile)
1265
1266
            return -1;
    }
1267
    const TextEditor::BaseTextEditor *eda = qobject_cast<const TextEditor::BaseTextEditor *>(ed);
1268
1269
1270
1271
1272
    if (!eda)
        return -1;
    return eda->currentLine();
}