Newer
Older
/****************************************************************************
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** 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.
** GNU Lesser General Public License Usage
** 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
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#include "vcsbaseeditor.h"
#include "diffhighlighter.h"
#include "baseannotationhighlighter.h"
#include "vcsbaseplugin.h"
#include <coreplugin/icore.h>
#include <coreplugin/vcsmanager.h>
#include <extensionsystem/pluginmanager.h>
#include <projectexplorer/editorconfiguration.h>
#include <projectexplorer/project.h>
#include <texteditor/basetextdocument.h>
#include <texteditor/basetextdocumentlayout.h>
#include <texteditor/texteditorsettings.h>

Friedemann Kleint
committed
#include <utils/qtcassert.h>
#include <QDebug>
#include <QFileInfo>
#include <QFile>
#include <QRegExp>
#include <QSet>
#include <QTextCodec>
#include <QUrl>
#include <QTextBlock>
#include <QDesktopServices>
#include <QAction>
#include <QKeyEvent>
#include <QMenu>
#include <QTextCursor>
#include <QTextEdit>
#include <QComboBox>
#include <QClipboard>
#include <QApplication>
#include <QMessageBox>
This enum describes the contents of a VcsBaseEditor and its interaction.
\value RegularCommandOutput No special handling.
\value LogOutput Log of a file under revision control. Provide a
description of the change that users can click to view detailed
information about the change and \e Annotate for the log of a
single file.
\value AnnotateOutput Color contents per change number and provide a
clickable change description.
Context menu offers annotate previous version functionality.
Expected format:
\code
<change description>: file line
\endcode
\value DiffOutput Diff output. Might include describe output, which consists of a
header and diffs. Double-clicking the chunk opens the file. The context
menu offers the functionality to revert the chunk.
\brief The DiffChunk class provides a diff chunk consisting of file name
and chunk data.
bool DiffChunk::isValid() const
{
return !fileName.isEmpty() && !chunk.isEmpty();
}
QByteArray DiffChunk::asPatch(const QString &workingDirectory) const
{
QString relativeFile = workingDirectory.isEmpty() ?
fileName : QDir(workingDirectory).relativeFilePath(fileName);
const QByteArray fileNameBA = QFile::encodeName(relativeFile);
QByteArray rc = "--- ";
rc += fileNameBA;
rc += "\n+++ ";
rc += fileNameBA;
rc += '\n';
rc += chunk;
return rc;
}
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
\brief The VcsBaseEditor class implements an editor with no support for
duplicates.
Creates a browse combo in the toolbar for diff output.
manager passes the editor around.
*/
Q_OBJECT
VcsBaseEditor(VcsBaseEditorWidget *, const VcsBaseEditorParameters *type);
signals:
void describeRequested(const QString &source, const QString &change);
void annotateRevisionRequested(const QString &workingDirectory, const QString &file,
const QString &change, int line);
VcsBaseEditor::VcsBaseEditor(VcsBaseEditorWidget *widget,
const VcsBaseEditorParameters *type) :
setContext(Core::Context(type->context, TextEditor::Constants::C_TEXTEDITOR));
namespace Internal {
/*! \class AbstractTextCursorHandler
* \brief The AbstractTextCursorHandler class provides an interface to handle
* the contents under a text cursor inside an editor.
*/
class AbstractTextCursorHandler : public QObject
{
public:
AbstractTextCursorHandler(VcsBaseEditorWidget *editorWidget = 0);
/*! Tries to find some matching contents under \a cursor.
* It is the first function to be called because it changes the internal
* state of the handler. Other functions (such as
* highlightCurrentContents() and handleCurrentContents()) use the result
* of the matching.
*/
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;
/*! Fills \a menu with contextual actions applying to the contents matched
* with findContentsUnderCursor().
211
212
213
214
215
216
217
218
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
*/
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 The ChangeTextCursorHandler class provides a handler for VCS change
* identifiers.
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
*/
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
{
VcsBaseEditorWidget *widget = editorWidget();
switch (type) {
case AnnotateOutput: { // Describe current / annotate previous
bool currentValid = widget->isValidRevision(m_currentChange);
menu->addSeparator();
menu->addAction(createCopyRevisionAction(m_currentChange));
if (currentValid)
menu->addAction(createDescribeAction(m_currentChange));
menu->addSeparator();
if (currentValid)
menu->addAction(createAnnotateAction(widget->decorateVersion(m_currentChange), false));
const QStringList previousVersions = widget->annotationPreviousVersions(m_currentChange);
if (!previousVersions.isEmpty()) {
foreach (const QString &pv, previousVersions)
menu->addAction(createAnnotateAction(widget->decorateVersion(pv), true));
default: // Describe current / Annotate file of current
menu->addSeparator();
menu->addAction(createCopyRevisionAction(m_currentChange));
menu->addAction(createDescribeAction(m_currentChange));
if (widget->isFileLogAnnotateEnabled())
menu->addAction(createAnnotateAction(m_currentChange, false));
widget->addChangeActions(menu, m_currentChange);
}
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);
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
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 The UrlTextCursorHandler class provides a handler for URLs, such as
* http://qt-project.org/.
*
* The URL pattern can be redefined in sub-classes with setUrlPattern(), by default the pattern
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
*/
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();
QRegExp urlRx(m_urlPattern);
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
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")));
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
}
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 The EmailTextCursorHandler class 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()));
}
VcsBaseEditorWidgetPrivate(VcsBaseEditorWidget* editorWidget, const VcsBaseEditorParameters *type);
AbstractTextCursorHandler *findTextCursorHandler(const QTextCursor &cursor);
// creates a browse combo in the toolbar for quick access to entries.
// Can be used for diff and log. Combo created on first call.
QComboBox *entriesComboBox();

Friedemann Kleint
committed
QString m_workingDirectory;

Friedemann Kleint
committed
QRegExp m_diffFilePattern;
QList<int> m_entrySections; // line number where this section starts

Friedemann Kleint
committed
int m_cursorLine;
QString m_annotateRevisionTextFormat;
QString m_annotatePreviousRevisionTextFormat;
QString m_copyRevisionTextFormat;
bool m_fileLogAnnotateEnabled;
TextEditor::BaseTextEditor *m_editor;
VcsBaseEditorParameterWidget *m_configurationWidget;
bool m_mouseDragging;
QList<AbstractTextCursorHandler *> m_textCursorHandlers;
QPointer<Command> m_command;
private:
QComboBox *m_entriesComboBox;
VcsBaseEditorWidgetPrivate::VcsBaseEditorWidgetPrivate(VcsBaseEditorWidget *editorWidget,
const VcsBaseEditorParameters *type) :
m_parameters(type),
m_cursorLine(-1),
m_annotateRevisionTextFormat(VcsBaseEditorWidget::tr("Annotate \"%1\"")),
m_copyRevisionTextFormat(VcsBaseEditorWidget::tr("Copy \"%1\"")),
m_configurationWidget(0),
m_mouseDragging(false),
m_entriesComboBox(0)
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;
QComboBox *VcsBaseEditorWidgetPrivate::entriesComboBox()
{
if (m_entriesComboBox)
return m_entriesComboBox;
m_entriesComboBox = new QComboBox;
m_entriesComboBox->setMinimumContentsLength(20);
// Make the combo box prefer to expand
QSizePolicy policy = m_entriesComboBox->sizePolicy();
policy.setHorizontalPolicy(QSizePolicy::Expanding);
m_entriesComboBox->setSizePolicy(policy);
m_editor->insertExtraToolBarWidget(TextEditor::BaseTextEditor::Left, m_entriesComboBox);
return m_entriesComboBox;
}
} // namespace Internal
\class VcsBase::VcsBaseEditorParameters
\brief The VcsBaseEditorParameters class is a helper class used to
parametrize an editor with MIME type, context
and id.
The extension is currently only a suggestion when running
\sa VcsBase::VcsBaseEditorWidget, VcsBase::BaseVcsEditorFactory, VcsBase::EditorContentType
\brief The VcsBaseEditorWidget class is the 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.
\sa VcsBase::BaseVcsEditorFactory, VcsBase::VcsBaseEditorParameters, VcsBase::EditorContentType
VcsBaseEditorWidget::VcsBaseEditorWidget(const VcsBaseEditorParameters *type, QWidget *parent)
d(new Internal::VcsBaseEditorWidgetPrivate(this, type))
setMimeType(QLatin1String(d->m_parameters->mimeType));
void VcsBaseEditorWidget::setDiffFilePattern(const QRegExp &pattern)
{
QTC_ASSERT(pattern.isValid() && pattern.captureCount() >= 1, return);
d->m_diffFilePattern = pattern;
}
void VcsBaseEditorWidget::setLogEntryPattern(const QRegExp &pattern)
{
QTC_ASSERT(pattern.isValid() && pattern.captureCount() >= 1, return);
d->m_logEntryPattern = pattern;
}
bool VcsBaseEditorWidget::supportChangeLinks() const
{
switch (d->m_parameters->type) {
case LogOutput:
case AnnotateOutput:
return true;
default:
return false;
}
}
QString VcsBaseEditorWidget::fileNameForLine(int line) const
{
Q_UNUSED(line);
return source();
}
switch (d->m_parameters->type) {
case OtherContent:
break;
connect(d->entriesComboBox(), SIGNAL(activated(int)), this, SLOT(slotJumpToEntry(int)));
connect(this, SIGNAL(textChanged()), this, SLOT(slotPopulateLogBrowser()));
connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(slotCursorPositionChanged()));
case AnnotateOutput:
// Annotation highlighting depends on contents, which is set later on
connect(this, SIGNAL(textChanged()), this, SLOT(slotActivateAnnotation()));
break;
// Diff: set up diff file browsing
connect(d->entriesComboBox(), SIGNAL(activated(int)), this, SLOT(slotJumpToEntry(int)));

Friedemann Kleint
committed
connect(this, SIGNAL(textChanged()), this, SLOT(slotPopulateDiffBrowser()));
connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(slotCursorPositionChanged()));
if (hasDiff()) {
DiffHighlighter *dh = new DiffHighlighter(d->m_diffFilePattern);
setCodeFoldingSupported(true);
baseTextDocument()->setSyntaxHighlighter(dh);
}
TextEditor::TextEditorSettings::initializeEditor(this);
// override revisions display (green or red bar on the left, marking changes):
setRevisionsVisible(false);
delete d;
setReadOnly(b);
return d->m_source;
d->m_source = source;
{
return d->m_annotateRevisionTextFormat;
}
void VcsBaseEditorWidget::setAnnotateRevisionTextFormat(const QString &f)
{
d->m_annotateRevisionTextFormat = f;
}
QString VcsBaseEditorWidget::annotatePreviousRevisionTextFormat() const
{
return d->m_annotatePreviousRevisionTextFormat;
}
void VcsBaseEditorWidget::setAnnotatePreviousRevisionTextFormat(const QString &f)
{
d->m_annotatePreviousRevisionTextFormat = f;
}
{
return d->m_copyRevisionTextFormat;
}
void VcsBaseEditorWidget::setCopyRevisionTextFormat(const QString &f)
{
d->m_copyRevisionTextFormat = f;
}
{
return d->m_fileLogAnnotateEnabled;
}
{
d->m_fileLogAnnotateEnabled = e;
}
QString VcsBaseEditorWidget::workingDirectory() const
return d->m_workingDirectory;
void VcsBaseEditorWidget::setWorkingDirectory(const QString &wd)
d->m_workingDirectory = wd;
return const_cast<QTextCodec *>(baseTextDocument()->codec());
return d->m_parameters->type;
TextEditor::BaseTextEditor *editor = new VcsBaseEditor(this, d->m_parameters);
// Pass on signals.
connect(this, SIGNAL(describeRequested(QString,QString)),
editor, SIGNAL(describeRequested(QString,QString)));
connect(this, SIGNAL(annotateRevisionRequested(QString,QString,QString,int)),
editor, SIGNAL(annotateRevisionRequested(QString,QString,QString,int)));

Friedemann Kleint
committed
}

Friedemann Kleint
committed
{
QComboBox *entriesComboBox = d->entriesComboBox();
entriesComboBox->clear();

Friedemann Kleint
committed
// 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.indexIn(text) == 0) {

Friedemann Kleint
committed
const QString file = fileNameFromDiffSpecification(it);
if (!file.isEmpty() && lastFileName != file) {
lastFileName = file;
// ignore any headers
d->m_entrySections.push_back(d->m_entrySections.empty() ? 0 : lineNumber);
entriesComboBox->addItem(QFileInfo(file).fileName());

Friedemann Kleint
committed
}
}
}
}
void VcsBaseEditorWidget::slotPopulateLogBrowser()
{
QComboBox *entriesComboBox = d->entriesComboBox();
entriesComboBox->clear();
d->m_entrySections.clear();
// Create a list of section line numbers (log entries)
// and populate combo with subjects (if any).
const QTextBlock cend = document()->end();
int lineNumber = 0;
for (QTextBlock it = document()->begin(); it != cend; it = it.next(), lineNumber++) {
const QString text = it.text();
// Check for a new log section (not repeating the last filename)
if (d->m_logEntryPattern.indexIn(text) != -1) {
d->m_entrySections.push_back(d->m_entrySections.empty() ? 0 : lineNumber);
QString entry = d->m_logEntryPattern.cap(1);
QString subject = revisionSubject(it);
if (!subject.isEmpty()) {
if (subject.length() > 100) {
subject.truncate(97);
subject.append(QLatin1String("..."));
}
entry.append(QLatin1String(" - ")).append(subject);
}
entriesComboBox->addItem(entry);
void VcsBaseEditorWidget::slotJumpToEntry(int index)

Friedemann Kleint
committed
{
// goto diff/log entry as indicated by index/line number
if (index < 0 || index >= d->m_entrySections.size())
const int lineNumber = d->m_entrySections.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(), ¤tLine, ¤tColumn);
if (lineNumber != currentLine) {
Core::EditorManager::addCurrentPositionToNavigationHistory();
gotoLine(lineNumber, 0);
}

Friedemann Kleint
committed
}
// Locate a line number in the list of diff sections.
static int sectionOfLine(int line, const QList<int> §ions)
{
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;
}
void VcsBaseEditorWidget::slotCursorPositionChanged()

Friedemann Kleint
committed
{
// Adapt entries combo to new position

Friedemann Kleint
committed
// if the cursor goes across a file line.
const int newCursorLine = textCursor().blockNumber();

Friedemann Kleint
committed
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_entrySections);

Friedemann Kleint
committed
if (section != -1) {
QComboBox *entriesComboBox = d->entriesComboBox();
if (entriesComboBox->currentIndex() != section) {
const bool blocked = entriesComboBox->blockSignals(true);
entriesComboBox->setCurrentIndex(section);
entriesComboBox->blockSignals(blocked);

Friedemann Kleint
committed
}
}
const QTextCursor cursor = cursorForPosition(e->pos());
if (Internal::AbstractTextCursorHandler *handler = d->findTextCursorHandler(cursor))
handler->fillContextMenu(menu, d->m_parameters->type);
}
switch (d->m_parameters->type) {
case LogOutput: // log might have diff
case DiffOutput: {
menu->addSeparator();
connect(menu->addAction(tr("Send to CodePaster...")), SIGNAL(triggered()),
this, SLOT(slotPaste()));
menu->addSeparator();
// Apply/revert diff chunk.
const DiffChunk chunk = diffChunk(cursorForPosition(e->pos()));
if (!canApplyDiffChunk(chunk))
break;
// 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->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->setData(qVariantFromValue(Internal::DiffChunkAction(chunk, true)));
connect(revertAction, SIGNAL(triggered()), this, SLOT(slotApplyDiffChunk()));
// Custom diff actions
addDiffActions(menu, chunk);
break;
default:
break;
connect(this, SIGNAL(destroyed()), menu, SLOT(deleteLater()));
if (e->buttons()) {
d->m_mouseDragging = true;
TextEditor::BaseTextEditorWidget::mouseMoveEvent(e);
return;
}
bool overrideCursor = false;
Qt::CursorShape cursorShape;
const QTextCursor cursor = cursorForPosition(e->pos());
Internal::AbstractTextCursorHandler *handler = d->findTextCursorHandler(cursor);
if (handler != 0) {
handler->highlightCurrentContents();
overrideCursor = true;
cursorShape = Qt::PointingHandCursor;
} else {
setExtraSelections(OtherSelection, QList<QTextEdit::ExtraSelection>());
overrideCursor = true;