Skip to content
Snippets Groups Projects
vcsbaseeditor.cpp 52.3 KiB
Newer Older
hjk's avatar
hjk committed
/****************************************************************************
con's avatar
con committed
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
** Contact: http://www.qt-project.org/legal
con's avatar
con committed
**
hjk's avatar
hjk committed
** This file is part of Qt Creator.
con's avatar
con committed
**
hjk's avatar
hjk committed
** 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
hjk's avatar
hjk committed
** 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
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
****************************************************************************/
hjk's avatar
hjk committed

con's avatar
con committed
#include "vcsbaseeditor.h"
#include "diffhighlighter.h"
#include "baseannotationhighlighter.h"
Orgad Shaneh's avatar
Orgad Shaneh committed
#include "vcsbaseeditorparameterwidget.h"
#include "command.h"
con's avatar
con committed

#include <coreplugin/icore.h>
#include <coreplugin/vcsmanager.h>
#include <extensionsystem/pluginmanager.h>
#include <projectexplorer/editorconfiguration.h>
con's avatar
con committed
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/project.h>
con's avatar
con committed
#include <projectexplorer/session.h>
#include <texteditor/basetextdocument.h>
#include <texteditor/basetextdocumentlayout.h>
#include <texteditor/texteditorsettings.h>
con's avatar
con committed

#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>
con's avatar
con committed

hjk's avatar
hjk committed
    \enum VcsBase::EditorContentType
Leena Miettinen's avatar
Leena Miettinen committed
    This enum describes the contents of a VcsBaseEditor and its interaction.

    \value RegularCommandOutput  No special handling.
Leena Miettinen's avatar
Leena Miettinen committed
    \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
Leena Miettinen's avatar
Leena Miettinen committed
    \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.
hjk's avatar
hjk committed
    \sa VcsBase::VcsBaseEditorWidget
hjk's avatar
hjk committed
namespace VcsBase {
con's avatar
con committed

hjk's avatar
hjk committed
    \class VcsBase::DiffChunk
    \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
hjk's avatar
hjk committed
} // namespace VcsBase
hjk's avatar
hjk committed
Q_DECLARE_METATYPE(VcsBase::Internal::DiffChunkAction)
hjk's avatar
hjk committed
namespace VcsBase {
hjk's avatar
hjk committed
    \class VcsBase::VcsBaseEditor
    \brief The VcsBaseEditor class implements an editor with no support for
    duplicates.

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

hjk's avatar
hjk committed
class VcsBaseEditor : public TextEditor::BaseTextEditor
con's avatar
con committed
{
con's avatar
con committed
public:
hjk's avatar
hjk committed
    VcsBaseEditor(VcsBaseEditorWidget *, const VcsBaseEditorParameters *type);
con's avatar
con committed

hjk's avatar
hjk committed
    Core::Id id() const { return m_id; }
con's avatar
con committed

signals:
    void describeRequested(const QString &source, const QString &change);
    void annotateRevisionRequested(const QString &workingDirectory, const QString &file,
                                   const QString &change, int line);
con's avatar
con committed
private:
hjk's avatar
hjk committed
    Core::Id m_id;
con's avatar
con committed
};

hjk's avatar
hjk committed
VcsBaseEditor::VcsBaseEditor(VcsBaseEditorWidget *widget,
                             const VcsBaseEditorParameters *type)  :
    BaseTextEditor(widget),
    m_id(type->id)
con's avatar
con committed
{
    setContext(Core::Context(type->context, TextEditor::Constants::C_TEXTEDITOR));
con's avatar
con committed
}

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

/*! \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);

Leena Miettinen's avatar
Leena Miettinen committed
    /*! Tries to find some matching contents under \a cursor.
Leena Miettinen's avatar
Leena Miettinen committed
     *  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.
Leena Miettinen's avatar
Leena Miettinen committed
     *  Returns \c 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;

Leena Miettinen's avatar
Leena Miettinen committed
    /*! Fills \a 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 The ChangeTextCursorHandler class 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
{
    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));
        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);
    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
Leena Miettinen's avatar
Leena Miettinen committed
 *  works for hyper-text URLs.
 */
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);
        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 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()));
}

hjk's avatar
hjk committed
class VcsBaseEditorWidgetPrivate
    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();
con's avatar
con committed

hjk's avatar
hjk committed
    const VcsBaseEditorParameters *m_parameters;
con's avatar
con committed
    QString m_source;
    QString m_workingDirectory;
    QRegExp m_logEntryPattern;
    QList<int> m_entrySections; // line number where this section starts
    QString m_annotateRevisionTextFormat;
    QString m_annotatePreviousRevisionTextFormat;
    QString m_copyRevisionTextFormat;
    bool m_fileLogAnnotateEnabled;
    TextEditor::BaseTextEditor *m_editor;
Orgad Shaneh's avatar
Orgad Shaneh committed
    VcsBaseEditorParameterWidget *m_configurationWidget;
    QList<AbstractTextCursorHandler *> m_textCursorHandlers;
    QPointer<Command> m_command;
private:
    QComboBox *m_entriesComboBox;
con's avatar
con committed
};

VcsBaseEditorWidgetPrivate::VcsBaseEditorWidgetPrivate(VcsBaseEditorWidget *editorWidget,
                                                       const VcsBaseEditorParameters *type)  :
    m_parameters(type),
    m_cursorLine(-1),
hjk's avatar
hjk committed
    m_annotateRevisionTextFormat(VcsBaseEditorWidget::tr("Annotate \"%1\"")),
    m_copyRevisionTextFormat(VcsBaseEditorWidget::tr("Copy \"%1\"")),
    m_fileLogAnnotateEnabled(false),
    m_mouseDragging(false),
    m_entriesComboBox(0)
con's avatar
con committed
{
    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
}

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;
}

    \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
    VCS commands with redirection.

hjk's avatar
hjk committed
    \sa VcsBase::VcsBaseEditorWidget, VcsBase::BaseVcsEditorFactory, VcsBase::EditorContentType
hjk's avatar
hjk committed
    \class VcsBase::VcsBaseEditorWidget
    \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.

hjk's avatar
hjk committed
    \sa VcsBase::BaseVcsEditorFactory, VcsBase::VcsBaseEditorParameters, VcsBase::EditorContentType
hjk's avatar
hjk committed
VcsBaseEditorWidget::VcsBaseEditorWidget(const VcsBaseEditorParameters *type, QWidget *parent)
  : BaseTextEditorWidget(parent),
    d(new Internal::VcsBaseEditorWidgetPrivate(this, type))
con's avatar
con committed
{
    viewport()->setMouseTracking(true);
    setMimeType(QLatin1String(d->m_parameters->mimeType));
con's avatar
con committed
}

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();
}

hjk's avatar
hjk committed
void VcsBaseEditorWidget::init()
con's avatar
con committed
{
    d->m_editor = editor();
    case OtherContent:
        break;
con's avatar
con committed
    case LogOutput:
        connect(d->entriesComboBox(), SIGNAL(activated(int)), this, SLOT(slotJumpToEntry(int)));
        connect(this, SIGNAL(textChanged()), this, SLOT(slotPopulateLogBrowser()));
        connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(slotCursorPositionChanged()));
con's avatar
con committed
    case AnnotateOutput:
        // Annotation highlighting depends on contents, which is set later on
        connect(this, SIGNAL(textChanged()), this, SLOT(slotActivateAnnotation()));
        break;
    case DiffOutput:
        // Diff: set up diff file browsing
        connect(d->entriesComboBox(), SIGNAL(activated(int)), this, SLOT(slotJumpToEntry(int)));
        connect(this, SIGNAL(textChanged()), this, SLOT(slotPopulateDiffBrowser()));
        connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(slotCursorPositionChanged()));
con's avatar
con committed
        break;
    }
    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);
con's avatar
con committed
}

hjk's avatar
hjk committed
VcsBaseEditorWidget::~VcsBaseEditorWidget()
con's avatar
con committed
{
con's avatar
con committed
}

hjk's avatar
hjk committed
void VcsBaseEditorWidget::setForceReadOnly(bool b)
hjk's avatar
hjk committed
    VcsBaseEditor *eda = qobject_cast<VcsBaseEditor *>(editor());
    QTC_ASSERT(eda != 0, return);
    eda->document()->setTemporary(b);
hjk's avatar
hjk committed
QString VcsBaseEditorWidget::source() const
con's avatar
con committed
{
con's avatar
con committed
}

hjk's avatar
hjk committed
void VcsBaseEditorWidget::setSource(const  QString &source)
con's avatar
con committed
{
con's avatar
con committed
}

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

hjk's avatar
hjk committed
void VcsBaseEditorWidget::setAnnotateRevisionTextFormat(const QString &f)
hjk's avatar
hjk committed
QString VcsBaseEditorWidget::annotatePreviousRevisionTextFormat() const
{
    return d->m_annotatePreviousRevisionTextFormat;
}

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

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

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

hjk's avatar
hjk committed
bool VcsBaseEditorWidget::isFileLogAnnotateEnabled() const
hjk's avatar
hjk committed
void VcsBaseEditorWidget::setFileLogAnnotateEnabled(bool e)
QString VcsBaseEditorWidget::workingDirectory() const
    return d->m_workingDirectory;
void VcsBaseEditorWidget::setWorkingDirectory(const QString &wd)
    d->m_workingDirectory = wd;
hjk's avatar
hjk committed
QTextCodec *VcsBaseEditorWidget::codec() const
con's avatar
con committed
{
    return const_cast<QTextCodec *>(baseTextDocument()->codec());
con's avatar
con committed
}

hjk's avatar
hjk committed
void VcsBaseEditorWidget::setCodec(QTextCodec *c)
con's avatar
con committed
{
con's avatar
con committed
        baseTextDocument()->setCodec(c);
con's avatar
con committed
        qWarning("%s: Attempt to set 0 codec.", Q_FUNC_INFO);
}

hjk's avatar
hjk committed
EditorContentType VcsBaseEditorWidget::contentType() const
con's avatar
con committed
{
con's avatar
con committed
}

hjk's avatar
hjk committed
bool VcsBaseEditorWidget::isModified() const
con's avatar
con committed
{
    return false;
}

hjk's avatar
hjk committed
TextEditor::BaseTextEditor *VcsBaseEditorWidget::createEditor()
con's avatar
con committed
{
    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)));
    return editor;
hjk's avatar
hjk committed
void VcsBaseEditorWidget::slotPopulateDiffBrowser()
    QComboBox *entriesComboBox = d->entriesComboBox();
    entriesComboBox->clear();
    d->m_entrySections.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.indexIn(text) == 0) {
            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());
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)
    // goto diff/log entry as indicated by index/line number
    if (index < 0 || index >= d->m_entrySections.size())
        return;
    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(), &currentLine, &currentColumn);
    if (lineNumber != currentLine) {
        Core::EditorManager::addCurrentPositionToNavigationHistory();
        gotoLine(lineNumber, 0);
    }
}

// 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;
}

void VcsBaseEditorWidget::slotCursorPositionChanged()
    // Adapt entries combo to new position
    // if the cursor goes across a file line.
    const int newCursorLine = textCursor().blockNumber();
    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);
        QComboBox *entriesComboBox = d->entriesComboBox();
        if (entriesComboBox->currentIndex() != section) {
            const bool blocked = entriesComboBox->blockSignals(true);
            entriesComboBox->setCurrentIndex(section);
            entriesComboBox->blockSignals(blocked);
con's avatar
con committed
}

hjk's avatar
hjk committed
void VcsBaseEditorWidget::contextMenuEvent(QContextMenuEvent *e)
con's avatar
con committed
{
Orgad Shaneh's avatar
Orgad Shaneh committed
    QPointer<QMenu> menu = createStandardContextMenu();
con's avatar
con committed
    // 'click on change-interaction'
    if (supportChangeLinks()) {
        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();
        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);
con's avatar
con committed
    }
Orgad Shaneh's avatar
Orgad Shaneh committed
    connect(this, SIGNAL(destroyed()), menu, SLOT(deleteLater()));
con's avatar
con committed
    menu->exec(e->globalPos());
    delete menu;
}

hjk's avatar
hjk committed
void VcsBaseEditorWidget::mouseMoveEvent(QMouseEvent *e)
con's avatar
con committed
{
    if (e->buttons()) {
        d->m_mouseDragging = true;
        TextEditor::BaseTextEditorWidget::mouseMoveEvent(e);
        return;
    }

    bool overrideCursor = false;
    Qt::CursorShape cursorShape;

    if (supportChangeLinks()) {
con's avatar
con committed
        // Link emulation behaviour for 'click on change-interaction'
        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;