1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
10 ** GNU Lesser General Public License Usage
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at info@qt.nokia.com.
31 **************************************************************************/
33 #include "vcsbaseeditor.h"
34 #include "diffhighlighter.h"
35 #include "baseannotationhighlighter.h"
36 #include "vcsbasetextdocument.h"
37 #include "vcsbaseconstants.h"
38 #include "vcsbaseoutputwindow.h"
39 #include "vcsbaseplugin.h"
41 #include <coreplugin/editormanager/editormanager.h>
42 #include <coreplugin/ifile.h>
43 #include <coreplugin/iversioncontrol.h>
44 #include <coreplugin/coreconstants.h>
45 #include <coreplugin/modemanager.h>
46 #include <extensionsystem/pluginmanager.h>
47 #include <projectexplorer/editorconfiguration.h>
48 #include <projectexplorer/projectexplorer.h>
49 #include <projectexplorer/project.h>
50 #include <projectexplorer/session.h>
51 #include <texteditor/basetextdocumentlayout.h>
52 #include <texteditor/fontsettings.h>
53 #include <texteditor/texteditorconstants.h>
54 #include <texteditor/texteditorsettings.h>
55 #include <utils/qtcassert.h>
56 #include <extensionsystem/invoker.h>
57 #include <extensionsystem/pluginmanager.h>
59 #include <QtCore/QDebug>
60 #include <QtCore/QFileInfo>
61 #include <QtCore/QFile>
62 #include <QtCore/QProcess>
63 #include <QtCore/QRegExp>
64 #include <QtCore/QSet>
65 #include <QtCore/QTextCodec>
66 #include <QtCore/QTextStream>
67 #include <QtGui/QTextBlock>
68 #include <QtGui/QAction>
69 #include <QtGui/QKeyEvent>
70 #include <QtGui/QLayout>
71 #include <QtGui/QMenu>
72 #include <QtGui/QTextCursor>
73 #include <QtGui/QTextEdit>
74 #include <QtGui/QComboBox>
75 #include <QtGui/QToolBar>
76 #include <QtGui/QClipboard>
77 #include <QtGui/QApplication>
78 #include <QtGui/QMessageBox>
81 \enum VCSBase::EditorContentType
83 \brief Contents of a VCSBaseEditor and its interaction.
85 \value RegularCommandOutput No special handling.
86 \value LogOutput Log of a file under revision control. Provide 'click on change'
87 description and 'Annotate' if is the log of a single file.
88 \value AnnotateOutput Color contents per change number and provide 'click on change' description.
89 Context menu offers "Annotate previous version". Expected format:
91 <change description>: file line
93 \value DiffOutput Diff output. Might includes describe output, which consists of a
94 header and diffs. Interaction is 'double click in hunk' which
95 opens the file. Context menu offers 'Revert chunk'.
97 \sa VCSBase::VCSBaseEditorWidget
103 \class VCSBase::DiffChunk
105 \brief A diff chunk consisting of file name and chunk data.
108 bool DiffChunk::isValid() const
110 return !fileName.isEmpty() && !chunk.isEmpty();
113 QByteArray DiffChunk::asPatch() const
115 const QByteArray fileNameBA = QFile::encodeName(fileName);
116 QByteArray rc = "--- ";
127 // Data to be passed to apply/revert diff chunk actions.
128 class DiffChunkAction
131 DiffChunkAction(const DiffChunk &dc = DiffChunk(), bool revertIn = false) :
132 chunk(dc), revert(revertIn) {}
138 } // namespace Internal
141 Q_DECLARE_METATYPE(VCSBase::Internal::DiffChunkAction)
146 \class VCSBase::VCSBaseEditor
148 \brief An editor with no support for duplicates.
150 Creates a browse combo in the toolbar for diff output.
151 It also mirrors the signals of the VCSBaseEditor since the editor
152 manager passes the editor around.
155 class VCSBaseEditor : public TextEditor::BaseTextEditor
159 VCSBaseEditor(VCSBaseEditorWidget *, const VCSBaseEditorParameters *type);
161 bool duplicateSupported() const { return false; }
162 Core::IEditor *duplicate(QWidget * /*parent*/) { return 0; }
163 QString id() const { return m_id; }
165 bool isTemporary() const { return m_temporary; }
166 void setTemporary(bool t) { m_temporary = t; }
169 void describeRequested(const QString &source, const QString &change);
170 void annotateRevisionRequested(const QString &source, const QString &change, int line);
177 VCSBaseEditor::VCSBaseEditor(VCSBaseEditorWidget *widget,
178 const VCSBaseEditorParameters *type) :
179 BaseTextEditor(widget),
183 setContext(Core::Context(type->context, TextEditor::Constants::C_TEXTEDITOR));
186 // Diff editor: creates a browse combo in the toolbar for diff output.
187 class VCSBaseDiffEditor : public VCSBaseEditor
190 VCSBaseDiffEditor(VCSBaseEditorWidget *, const VCSBaseEditorParameters *type);
192 QComboBox *diffFileBrowseComboBox() const { return m_diffFileBrowseComboBox; }
195 QComboBox *m_diffFileBrowseComboBox;
198 VCSBaseDiffEditor::VCSBaseDiffEditor(VCSBaseEditorWidget *w, const VCSBaseEditorParameters *type) :
199 VCSBaseEditor(w, type),
200 m_diffFileBrowseComboBox(new QComboBox)
202 m_diffFileBrowseComboBox->setMinimumContentsLength(20);
203 // Make the combo box prefer to expand
204 QSizePolicy policy = m_diffFileBrowseComboBox->sizePolicy();
205 policy.setHorizontalPolicy(QSizePolicy::Expanding);
206 m_diffFileBrowseComboBox->setSizePolicy(policy);
208 insertExtraToolBarWidget(Left, m_diffFileBrowseComboBox);
211 // ----------- VCSBaseEditorPrivate
213 struct VCSBaseEditorWidgetPrivate
215 VCSBaseEditorWidgetPrivate(const VCSBaseEditorParameters *type);
217 const VCSBaseEditorParameters *m_parameters;
219 QString m_currentChange;
221 QString m_diffBaseDirectory;
223 QRegExp m_diffFilePattern;
224 QList<int> m_diffSections; // line number where this section starts
226 QString m_annotateRevisionTextFormat;
227 QString m_annotatePreviousRevisionTextFormat;
228 QString m_copyRevisionTextFormat;
229 bool m_fileLogAnnotateEnabled;
230 TextEditor::BaseTextEditor *m_editor;
231 QWidget *m_configurationWidget;
232 bool m_revertChunkEnabled;
233 bool m_mouseDragging;
236 VCSBaseEditorWidgetPrivate::VCSBaseEditorWidgetPrivate(const VCSBaseEditorParameters *type) :
239 m_annotateRevisionTextFormat(VCSBaseEditorWidget::tr("Annotate \"%1\"")),
240 m_copyRevisionTextFormat(VCSBaseEditorWidget::tr("Copy \"%1\"")),
241 m_fileLogAnnotateEnabled(false),
243 m_configurationWidget(0),
244 m_revertChunkEnabled(false),
245 m_mouseDragging(false)
250 \struct VCSBase::VCSBaseEditorParameters
252 \brief Helper struct used to parametrize an editor with mime type, context
253 and id. The extension is currently only a suggestion when running
254 VCS commands with redirection.
256 \sa VCSBase::VCSBaseEditorWidget, VCSBase::BaseVCSEditorFactory, VCSBase::EditorContentType
260 \class VCSBase::VCSBaseEditorWidget
262 \brief Base class for editors showing version control system output
263 of the type enumerated by EditorContentType.
265 The source property should contain the file or directory the log
266 refers to and will be emitted with describeRequested().
267 This is for VCS that need a current directory.
269 \sa VCSBase::BaseVCSEditorFactory, VCSBase::VCSBaseEditorParameters, VCSBase::EditorContentType
272 VCSBaseEditorWidget::VCSBaseEditorWidget(const VCSBaseEditorParameters *type, QWidget *parent)
273 : BaseTextEditorWidget(parent),
274 d(new VCSBaseEditorWidgetPrivate(type))
276 if (VCSBase::Constants::Internal::debug)
277 qDebug() << "VCSBaseEditor::VCSBaseEditor" << type->type << type->id;
279 viewport()->setMouseTracking(true);
280 setBaseTextDocument(new Internal::VCSBaseTextDocument);
281 setMimeType(QLatin1String(d->m_parameters->mimeType));
284 void VCSBaseEditorWidget::init()
286 switch (d->m_parameters->type) {
287 case RegularCommandOutput:
290 // Annotation highlighting depends on contents, which is set later on
291 connect(this, SIGNAL(textChanged()), this, SLOT(slotActivateAnnotation()));
294 DiffHighlighter *dh = createDiffHighlighter();
295 setCodeFoldingSupported(true);
296 baseTextDocument()->setSyntaxHighlighter(dh);
297 d->m_diffFilePattern = dh->filePattern();
298 connect(this, SIGNAL(textChanged()), this, SLOT(slotPopulateDiffBrowser()));
299 connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(slotDiffCursorPositionChanged()));
303 TextEditor::TextEditorSettings::instance()->initializeEditor(this);
306 VCSBaseEditorWidget::~VCSBaseEditorWidget()
311 void VCSBaseEditorWidget::setForceReadOnly(bool b)
313 Internal::VCSBaseTextDocument *vbd = qobject_cast<Internal::VCSBaseTextDocument*>(baseTextDocument());
314 VCSBaseEditor *eda = qobject_cast<VCSBaseEditor *>(editor());
315 QTC_ASSERT(vbd != 0 && eda != 0, return);
317 vbd->setForceReadOnly(b);
318 eda->setTemporary(b);
321 bool VCSBaseEditorWidget::isForceReadOnly() const
323 const Internal::VCSBaseTextDocument *vbd = qobject_cast<const Internal::VCSBaseTextDocument*>(baseTextDocument());
324 QTC_ASSERT(vbd, return false);
325 return vbd->isForceReadOnly();
328 QString VCSBaseEditorWidget::source() const
333 void VCSBaseEditorWidget::setSource(const QString &source)
335 d->m_source = source;
338 QString VCSBaseEditorWidget::annotateRevisionTextFormat() const
340 return d->m_annotateRevisionTextFormat;
343 void VCSBaseEditorWidget::setAnnotateRevisionTextFormat(const QString &f)
345 d->m_annotateRevisionTextFormat = f;
348 QString VCSBaseEditorWidget::annotatePreviousRevisionTextFormat() const
350 return d->m_annotatePreviousRevisionTextFormat;
353 void VCSBaseEditorWidget::setAnnotatePreviousRevisionTextFormat(const QString &f)
355 d->m_annotatePreviousRevisionTextFormat = f;
358 QString VCSBaseEditorWidget::copyRevisionTextFormat() const
360 return d->m_copyRevisionTextFormat;
363 void VCSBaseEditorWidget::setCopyRevisionTextFormat(const QString &f)
365 d->m_copyRevisionTextFormat = f;
368 bool VCSBaseEditorWidget::isFileLogAnnotateEnabled() const
370 return d->m_fileLogAnnotateEnabled;
373 void VCSBaseEditorWidget::setFileLogAnnotateEnabled(bool e)
375 d->m_fileLogAnnotateEnabled = e;
378 QString VCSBaseEditorWidget::diffBaseDirectory() const
380 return d->m_diffBaseDirectory;
383 void VCSBaseEditorWidget::setDiffBaseDirectory(const QString &bd)
385 d->m_diffBaseDirectory = bd;
388 QTextCodec *VCSBaseEditorWidget::codec() const
390 return const_cast<QTextCodec *>(baseTextDocument()->codec());
393 void VCSBaseEditorWidget::setCodec(QTextCodec *c)
396 baseTextDocument()->setCodec(c);
398 qWarning("%s: Attempt to set 0 codec.", Q_FUNC_INFO);
402 EditorContentType VCSBaseEditorWidget::contentType() const
404 return d->m_parameters->type;
407 bool VCSBaseEditorWidget::isModified() const
412 TextEditor::BaseTextEditor *VCSBaseEditorWidget::createEditor()
414 TextEditor::BaseTextEditor *editor = 0;
415 if (d->m_parameters->type == DiffOutput) {
416 // Diff: set up diff file browsing
417 VCSBaseDiffEditor *de = new VCSBaseDiffEditor(this, d->m_parameters);
418 QComboBox *diffBrowseComboBox = de->diffFileBrowseComboBox();
419 connect(diffBrowseComboBox, SIGNAL(activated(int)), this, SLOT(slotDiffBrowse(int)));
422 editor = new VCSBaseEditor(this, d->m_parameters);
424 d->m_editor = editor;
427 connect(this, SIGNAL(describeRequested(QString,QString)),
428 editor, SIGNAL(describeRequested(QString,QString)));
429 connect(this, SIGNAL(annotateRevisionRequested(QString,QString,int)),
430 editor, SIGNAL(annotateRevisionRequested(QString,QString,int)));
434 void VCSBaseEditorWidget::slotPopulateDiffBrowser()
436 VCSBaseDiffEditor *de = static_cast<VCSBaseDiffEditor*>(editor());
437 QComboBox *diffBrowseComboBox = de->diffFileBrowseComboBox();
438 diffBrowseComboBox->clear();
439 d->m_diffSections.clear();
440 // Create a list of section line numbers (diffed files)
441 // and populate combo with filenames.
442 const QTextBlock cend = document()->end();
444 QString lastFileName;
445 for (QTextBlock it = document()->begin(); it != cend; it = it.next(), lineNumber++) {
446 const QString text = it.text();
447 // Check for a new diff section (not repeating the last filename)
448 if (d->m_diffFilePattern.exactMatch(text)) {
449 const QString file = fileNameFromDiffSpecification(it);
450 if (!file.isEmpty() && lastFileName != file) {
452 // ignore any headers
453 d->m_diffSections.push_back(d->m_diffSections.empty() ? 0 : lineNumber);
454 diffBrowseComboBox->addItem(QFileInfo(file).fileName());
460 void VCSBaseEditorWidget::slotDiffBrowse(int index)
462 // goto diffed file as indicated by index/line number
463 if (index < 0 || index >= d->m_diffSections.size())
465 const int lineNumber = d->m_diffSections.at(index) + 1; // TextEdit uses 1..n convention
466 // check if we need to do something, especially to avoid messing up navigation history
467 int currentLine, currentColumn;
468 convertPosition(position(), ¤tLine, ¤tColumn);
469 if (lineNumber != currentLine) {
470 Core::EditorManager *editorManager = Core::EditorManager::instance();
471 editorManager->addCurrentPositionToNavigationHistory();
472 gotoLine(lineNumber, 0);
476 // Locate a line number in the list of diff sections.
477 static int sectionOfLine(int line, const QList<int> §ions)
479 const int sectionCount = sections.size();
482 // The section at s indicates where the section begins.
483 for (int s = 0; s < sectionCount; s++) {
484 if (line < sections.at(s))
487 return sectionCount - 1;
490 void VCSBaseEditorWidget::slotDiffCursorPositionChanged()
492 // Adapt diff file browse combo to new position
493 // if the cursor goes across a file line.
494 QTC_ASSERT(d->m_parameters->type == DiffOutput, return)
495 const int newCursorLine = textCursor().blockNumber();
496 if (newCursorLine == d->m_cursorLine)
498 // Which section does it belong to?
499 d->m_cursorLine = newCursorLine;
500 const int section = sectionOfLine(d->m_cursorLine, d->m_diffSections);
502 VCSBaseDiffEditor *de = static_cast<VCSBaseDiffEditor*>(editor());
503 QComboBox *diffBrowseComboBox = de->diffFileBrowseComboBox();
504 if (diffBrowseComboBox->currentIndex() != section) {
505 const bool blocked = diffBrowseComboBox->blockSignals(true);
506 diffBrowseComboBox->setCurrentIndex(section);
507 diffBrowseComboBox->blockSignals(blocked);
512 QAction *VCSBaseEditorWidget::createDescribeAction(const QString &change)
514 QAction *a = new QAction(tr("Describe change %1").arg(change), 0);
515 connect(a, SIGNAL(triggered()), this, SLOT(describe()));
519 QAction *VCSBaseEditorWidget::createAnnotateAction(const QString &change, bool previous)
521 // Use 'previous' format if desired and available, else default to standard.
522 const QString &format = previous && !d->m_annotatePreviousRevisionTextFormat.isEmpty() ?
523 d->m_annotatePreviousRevisionTextFormat : d->m_annotateRevisionTextFormat;
524 QAction *a = new QAction(format.arg(change), 0);
526 connect(a, SIGNAL(triggered()), this, SLOT(slotAnnotateRevision()));
530 QAction *VCSBaseEditorWidget::createCopyRevisionAction(const QString &change)
532 QAction *a = new QAction(d->m_copyRevisionTextFormat.arg(change), 0);
534 connect(a, SIGNAL(triggered()), this, SLOT(slotCopyRevision()));
538 void VCSBaseEditorWidget::contextMenuEvent(QContextMenuEvent *e)
540 QMenu *menu = createStandardContextMenu();
541 // 'click on change-interaction'
542 switch (d->m_parameters->type) {
545 d->m_currentChange = changeUnderCursor(cursorForPosition(e->pos()));
546 if (!d->m_currentChange.isEmpty()) {
547 switch (d->m_parameters->type) {
548 case LogOutput: // Describe current / Annotate file of current
549 menu->addSeparator();
550 menu->addAction(createCopyRevisionAction(d->m_currentChange));
551 menu->addAction(createDescribeAction(d->m_currentChange));
552 if (d->m_fileLogAnnotateEnabled)
553 menu->addAction(createAnnotateAction(d->m_currentChange, false));
555 case AnnotateOutput: { // Describe current / annotate previous
556 menu->addSeparator();
557 menu->addAction(createCopyRevisionAction(d->m_currentChange));
558 menu->addAction(createDescribeAction(d->m_currentChange));
559 const QStringList previousVersions = annotationPreviousVersions(d->m_currentChange);
560 if (!previousVersions.isEmpty()) {
561 menu->addSeparator();
562 foreach(const QString &pv, previousVersions)
563 menu->addAction(createAnnotateAction(pv, true));
564 } // has previous versions
570 } // has current change
573 menu->addSeparator();
574 connect(menu->addAction(tr("Send to CodePaster...")), SIGNAL(triggered()),
575 this, SLOT(slotPaste()));
576 menu->addSeparator();
577 // Apply/revert diff chunk.
578 const DiffChunk chunk = diffChunk(cursorForPosition(e->pos()));
579 const bool canApply = canApplyDiffChunk(chunk);
580 // Apply a chunk from a diff loaded into the editor. This typically will
581 // not have the 'source' property set and thus will only work if the working
582 // directory matches that of the patch (see findDiffFile()). In addition,
583 // the user has "Open With" and choose the right diff editor so that
584 // fileNameFromDiffSpecification() works.
585 QAction *applyAction = menu->addAction(tr("Apply Chunk..."));
586 applyAction->setEnabled(canApply);
587 applyAction->setData(qVariantFromValue(Internal::DiffChunkAction(chunk, false)));
588 connect(applyAction, SIGNAL(triggered()), this, SLOT(slotApplyDiffChunk()));
589 // Revert a chunk from a VCS diff, which might be linked to reloading the diff.
590 QAction *revertAction = menu->addAction(tr("Revert Chunk..."));
591 revertAction->setEnabled(isRevertDiffChunkEnabled() && canApply);
592 revertAction->setData(qVariantFromValue(Internal::DiffChunkAction(chunk, true)));
593 connect(revertAction, SIGNAL(triggered()), this, SLOT(slotApplyDiffChunk()));
599 menu->exec(e->globalPos());
603 void VCSBaseEditorWidget::mouseMoveEvent(QMouseEvent *e)
606 d->m_mouseDragging = true;
607 TextEditor::BaseTextEditorWidget::mouseMoveEvent(e);
611 bool overrideCursor = false;
612 Qt::CursorShape cursorShape;
614 if (d->m_parameters->type == LogOutput || d->m_parameters->type == AnnotateOutput) {
615 // Link emulation behaviour for 'click on change-interaction'
616 QTextCursor cursor = cursorForPosition(e->pos());
617 QString change = changeUnderCursor(cursor);
618 if (!change.isEmpty()) {
619 QTextEdit::ExtraSelection sel;
621 sel.cursor.select(QTextCursor::WordUnderCursor);
622 sel.format.setFontUnderline(true);
623 sel.format.setProperty(QTextFormat::UserProperty, change);
624 setExtraSelections(OtherSelection, QList<QTextEdit::ExtraSelection>() << sel);
625 overrideCursor = true;
626 cursorShape = Qt::PointingHandCursor;
629 setExtraSelections(OtherSelection, QList<QTextEdit::ExtraSelection>());
630 overrideCursor = true;
631 cursorShape = Qt::IBeamCursor;
633 TextEditor::BaseTextEditorWidget::mouseMoveEvent(e);
636 viewport()->setCursor(cursorShape);
639 void VCSBaseEditorWidget::mouseReleaseEvent(QMouseEvent *e)
641 const bool wasDragging = d->m_mouseDragging;
642 d->m_mouseDragging = false;
643 if (!wasDragging && (d->m_parameters->type == LogOutput || d->m_parameters->type == AnnotateOutput)) {
644 if (e->button() == Qt::LeftButton &&!(e->modifiers() & Qt::ShiftModifier)) {
645 QTextCursor cursor = cursorForPosition(e->pos());
646 d->m_currentChange = changeUnderCursor(cursor);
647 if (!d->m_currentChange.isEmpty()) {
654 TextEditor::BaseTextEditorWidget::mouseReleaseEvent(e);
657 void VCSBaseEditorWidget::mouseDoubleClickEvent(QMouseEvent *e)
659 if (d->m_parameters->type == DiffOutput) {
660 if (e->button() == Qt::LeftButton &&!(e->modifiers() & Qt::ShiftModifier)) {
661 QTextCursor cursor = cursorForPosition(e->pos());
662 jumpToChangeFromDiff(cursor);
665 TextEditor::BaseTextEditorWidget::mouseDoubleClickEvent(e);
668 void VCSBaseEditorWidget::keyPressEvent(QKeyEvent *e)
670 // Do not intercept return in editable patches.
671 if (d->m_parameters->type == DiffOutput && isReadOnly()
672 && (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return)) {
673 jumpToChangeFromDiff(textCursor());
676 BaseTextEditorWidget::keyPressEvent(e);
679 void VCSBaseEditorWidget::describe()
681 if (VCSBase::Constants::Internal::debug)
682 qDebug() << "VCSBaseEditor::describe" << d->m_currentChange;
683 if (!d->m_currentChange.isEmpty())
684 emit describeRequested(d->m_source, d->m_currentChange);
687 void VCSBaseEditorWidget::slotActivateAnnotation()
689 // The annotation highlighting depends on contents (change number
690 // set with assigned colors)
691 if (d->m_parameters->type != AnnotateOutput)
694 const QSet<QString> changes = annotationChanges();
695 if (changes.isEmpty())
697 if (VCSBase::Constants::Internal::debug)
698 qDebug() << "VCSBaseEditor::slotActivateAnnotation(): #" << changes.size();
700 disconnect(this, SIGNAL(textChanged()), this, SLOT(slotActivateAnnotation()));
702 if (BaseAnnotationHighlighter *ah = qobject_cast<BaseAnnotationHighlighter *>(baseTextDocument()->syntaxHighlighter())) {
703 ah->setChangeNumbers(changes);
706 baseTextDocument()->setSyntaxHighlighter(createAnnotationHighlighter(changes));
710 // Check for a change chunk "@@ -91,7 +95,7 @@" and return
711 // the modified line number (95).
712 // Note that git appends stuff after " @@" (function names, etc.).
713 static inline bool checkChunkLine(const QString &line, int *modifiedLineNumber)
715 if (!line.startsWith(QLatin1String("@@ ")))
717 const int endPos = line.indexOf(QLatin1String(" @@"), 3);
720 // the first chunk range applies to the original file, the second one to
721 // the modified file, the one we're interested int
722 const int plusPos = line.indexOf(QLatin1Char('+'), 3);
723 if (plusPos == -1 || plusPos > endPos)
725 const int lineNumberPos = plusPos + 1;
726 const int commaPos = line.indexOf(QLatin1Char(','), lineNumberPos);
727 if (commaPos == -1 || commaPos > endPos)
729 const QString lineNumberStr = line.mid(lineNumberPos, commaPos - lineNumberPos);
731 *modifiedLineNumber = lineNumberStr.toInt(&ok);
735 void VCSBaseEditorWidget::jumpToChangeFromDiff(QTextCursor cursor)
739 const QChar deletionIndicator = QLatin1Char('-');
740 // find nearest change hunk
741 QTextBlock block = cursor.block();
742 if (TextEditor::BaseTextDocumentLayout::foldingIndent(block) <= 1)
743 /* We are in a diff header, do not jump anywhere. DiffHighlighter sets the foldingIndent for us. */
745 for ( ; block.isValid() ; block = block.previous()) {
746 const QString line = block.text();
747 if (checkChunkLine(line, &chunkStart)) {
750 if (!line.startsWith(deletionIndicator))
755 if (VCSBase::Constants::Internal::debug)
756 qDebug() << "VCSBaseEditor::jumpToChangeFromDiff()1" << chunkStart << lineCount;
758 if (chunkStart == -1 || lineCount < 0 || !block.isValid())
761 // find the filename in previous line, map depot name back
762 block = block.previous();
763 if (!block.isValid())
765 const QString fileName = fileNameFromDiffSpecification(block);
767 const bool exists = fileName.isEmpty() ? false : QFile::exists(fileName);
769 if (VCSBase::Constants::Internal::debug)
770 qDebug() << "VCSBaseEditor::jumpToChangeFromDiff()2" << fileName << "ex=" << exists << "line" << chunkStart << lineCount;
775 Core::EditorManager *em = Core::EditorManager::instance();
776 Core::IEditor *ed = em->openEditor(fileName, QString(), Core::EditorManager::ModeSwitch);
777 if (TextEditor::ITextEditor *editor = qobject_cast<TextEditor::ITextEditor *>(ed))
778 editor->gotoLine(chunkStart + lineCount);
781 // cut out chunk and determine file name.
782 DiffChunk VCSBaseEditorWidget::diffChunk(QTextCursor cursor) const
784 QTC_ASSERT(d->m_parameters->type == DiffOutput, return DiffChunk(); )
786 // Search back for start of chunk.
787 QTextBlock block = cursor.block();
788 QTextBlock next = block.next();
789 if (next.isValid() && TextEditor::BaseTextDocumentLayout::foldingIndent(next) <= 1)
790 /* We are in a diff header, not in a chunk! DiffHighlighter sets the foldingIndent for us. */
794 for ( ; block.isValid() ; block = block.previous()) {
795 if (checkChunkLine(block.text(), &chunkStart)) {
799 if (!chunkStart || !block.isValid())
801 rc.fileName = fileNameFromDiffSpecification(block);
802 if (rc.fileName.isEmpty())
804 // Concatenate chunk and convert
805 QString unicode = block.text();
806 if (!unicode.endsWith(QLatin1Char('\n'))) // Missing in case of hg.
807 unicode.append(QLatin1Char('\n'));
808 for (block = block.next() ; block.isValid() ; block = block.next()) {
809 const QString line = block.text();
810 if (checkChunkLine(line, &chunkStart)) {
814 unicode += QLatin1Char('\n');
817 const QTextCodec *cd = textCodec();
818 rc.chunk = cd ? cd->fromUnicode(unicode) : unicode.toLocal8Bit();
822 void VCSBaseEditorWidget::setPlainTextData(const QByteArray &data)
824 if (data.size() > Core::EditorManager::maxTextFileSize()) {
825 setPlainText(msgTextTooLarge(data.size()));
827 setPlainText(codec()->toUnicode(data));
831 void VCSBaseEditorWidget::setFontSettings(const TextEditor::FontSettings &fs)
833 TextEditor::BaseTextEditorWidget::setFontSettings(fs);
834 if (d->m_parameters->type == DiffOutput) {
835 if (DiffHighlighter *highlighter = qobject_cast<DiffHighlighter*>(baseTextDocument()->syntaxHighlighter())) {
836 static QVector<QString> categories;
837 if (categories.isEmpty()) {
838 categories << QLatin1String(TextEditor::Constants::C_TEXT)
839 << QLatin1String(TextEditor::Constants::C_ADDED_LINE)
840 << QLatin1String(TextEditor::Constants::C_REMOVED_LINE)
841 << QLatin1String(TextEditor::Constants::C_DIFF_FILE)
842 << QLatin1String(TextEditor::Constants::C_DIFF_LOCATION);
844 highlighter->setFormats(fs.toTextCharFormats(categories));
845 highlighter->rehighlight();
850 const VCSBaseEditorParameters *VCSBaseEditorWidget::findType(const VCSBaseEditorParameters *array,
852 EditorContentType et)
854 for (int i = 0; i < arraySize; i++)
855 if (array[i].type == et)
860 // Find the codec used for a file querying the editor.
861 static QTextCodec *findFileCodec(const QString &source)
863 typedef QList<Core::IEditor *> EditorList;
865 const EditorList editors = Core::EditorManager::instance()->editorsForFileName(source);
866 if (!editors.empty()) {
867 const EditorList::const_iterator ecend = editors.constEnd();
868 for (EditorList::const_iterator it = editors.constBegin(); it != ecend; ++it)
869 if (const TextEditor::BaseTextEditor *be = qobject_cast<const TextEditor::BaseTextEditor *>(*it)) {
870 QTextCodec *codec = be->editorWidget()->textCodec();
871 if (VCSBase::Constants::Internal::debug)
872 qDebug() << Q_FUNC_INFO << source << codec->name();
876 if (VCSBase::Constants::Internal::debug)
877 qDebug() << Q_FUNC_INFO << source << "not found";
881 // Find the codec by checking the projects (root dir of project file)
882 static QTextCodec *findProjectCodec(const QString &dir)
884 typedef QList<ProjectExplorer::Project*> ProjectList;
885 // Try to find a project under which file tree the file is.
886 const ProjectExplorer::SessionManager *sm = ProjectExplorer::ProjectExplorerPlugin::instance()->session();
887 const ProjectList projects = sm->projects();
888 if (!projects.empty()) {
889 const ProjectList::const_iterator pcend = projects.constEnd();
890 for (ProjectList::const_iterator it = projects.constBegin(); it != pcend; ++it)
891 if (const Core::IFile *file = (*it)->file())
892 if (file->fileName().startsWith(dir)) {
893 QTextCodec *codec = (*it)->editorConfiguration()->textCodec();
894 if (VCSBase::Constants::Internal::debug)
895 qDebug() << Q_FUNC_INFO << dir << (*it)->displayName() << codec->name();
899 if (VCSBase::Constants::Internal::debug)
900 qDebug() << Q_FUNC_INFO << dir << "not found";
904 QTextCodec *VCSBaseEditorWidget::getCodec(const QString &source)
906 if (!source.isEmpty()) {
908 const QFileInfo sourceFi(source);
909 if (sourceFi.isFile())
910 if (QTextCodec *fc = findFileCodec(source))
912 // Find by project via directory
913 if (QTextCodec *pc = findProjectCodec(sourceFi.isFile() ? sourceFi.absolutePath() : source))
916 QTextCodec *sys = QTextCodec::codecForLocale();
917 if (VCSBase::Constants::Internal::debug)
918 qDebug() << Q_FUNC_INFO << source << "defaulting to " << sys->name();
922 QTextCodec *VCSBaseEditorWidget::getCodec(const QString &workingDirectory, const QStringList &files)
925 return getCodec(workingDirectory);
926 return getCodec(workingDirectory + QLatin1Char('/') + files.front());
929 VCSBaseEditorWidget *VCSBaseEditorWidget::getVcsBaseEditor(const Core::IEditor *editor)
931 if (const TextEditor::BaseTextEditor *be = qobject_cast<const TextEditor::BaseTextEditor *>(editor))
932 return qobject_cast<VCSBaseEditorWidget *>(be->editorWidget());
936 // Return line number of current editor if it matches.
937 int VCSBaseEditorWidget::lineNumberOfCurrentEditor(const QString ¤tFile)
939 Core::IEditor *ed = Core::EditorManager::instance()->currentEditor();
942 if (!currentFile.isEmpty()) {
943 const Core::IFile *ifile = ed->file();
944 if (!ifile || ifile->fileName() != currentFile)
947 const TextEditor::BaseTextEditor *eda = qobject_cast<const TextEditor::BaseTextEditor *>(ed);
950 return eda->currentLine();
953 bool VCSBaseEditorWidget::gotoLineOfEditor(Core::IEditor *e, int lineNumber)
955 if (lineNumber >= 0 && e) {
956 if (TextEditor::BaseTextEditor *be = qobject_cast<TextEditor::BaseTextEditor*>(e)) {
957 be->gotoLine(lineNumber);
964 // Return source file or directory string depending on parameters
965 // ('git diff XX' -> 'XX' , 'git diff XX file' -> 'XX/file').
966 QString VCSBaseEditorWidget::getSource(const QString &workingDirectory,
967 const QString &fileName)
969 if (fileName.isEmpty())
970 return workingDirectory;
972 QString rc = workingDirectory;
973 const QChar slash = QLatin1Char('/');
974 if (!rc.isEmpty() && !(rc.endsWith(slash) || rc.endsWith(QLatin1Char('\\'))))
980 QString VCSBaseEditorWidget::getSource(const QString &workingDirectory,
981 const QStringList &fileNames)
983 return fileNames.size() == 1 ?
984 getSource(workingDirectory, fileNames.front()) :
988 QString VCSBaseEditorWidget::getTitleId(const QString &workingDirectory,
989 const QStringList &fileNames,
990 const QString &revision)
993 switch (fileNames.size()) {
995 rc = workingDirectory;
998 rc = fileNames.front();
1001 rc = fileNames.join(QLatin1String(", "));
1004 if (!revision.isEmpty()) {
1005 rc += QLatin1Char(':');
1011 bool VCSBaseEditorWidget::setConfigurationWidget(QWidget *w)
1013 if (!d->m_editor || d->m_configurationWidget)
1016 d->m_configurationWidget = w;
1017 d->m_editor->insertExtraToolBarWidget(TextEditor::BaseTextEditor::Right, w);
1022 QWidget *VCSBaseEditorWidget::configurationWidget() const
1024 return d->m_configurationWidget;
1027 // Find the complete file from a diff relative specification.
1028 QString VCSBaseEditorWidget::findDiffFile(const QString &f,
1029 Core::IVersionControl *control /* = 0 */) const
1031 // Check if file is absolute
1032 const QFileInfo in(f);
1033 if (in.isAbsolute())
1034 return in.isFile() ? f : QString();
1037 const QChar slash = QLatin1Char('/');
1038 if (!d->m_diffBaseDirectory.isEmpty()) {
1039 const QFileInfo baseFileInfo(d->m_diffBaseDirectory + slash + f);
1040 if (baseFileInfo.isFile())
1041 return baseFileInfo.absoluteFilePath();
1043 // 2) Try in source (which can be file or directory)
1044 if (!source().isEmpty()) {
1045 const QFileInfo sourceInfo(source());
1046 const QString sourceDir = sourceInfo.isDir() ? sourceInfo.absoluteFilePath()
1047 : sourceInfo.absolutePath();
1048 const QFileInfo sourceFileInfo(sourceDir + slash + f);
1049 if (sourceFileInfo.isFile())
1050 return sourceFileInfo.absoluteFilePath();
1053 if (control && control->managesDirectory(sourceDir, &topLevel)) {
1054 const QFileInfo topLevelFileInfo(topLevel + slash + f);
1055 if (topLevelFileInfo.isFile())
1056 return topLevelFileInfo.absoluteFilePath();
1060 // 3) Try working directory
1062 return in.absoluteFilePath();
1067 void VCSBaseEditorWidget::slotAnnotateRevision()
1069 if (const QAction *a = qobject_cast<const QAction *>(sender()))
1070 emit annotateRevisionRequested(source(), a->data().toString(),
1071 editor()->currentLine());
1074 void VCSBaseEditorWidget::slotCopyRevision()
1076 QApplication::clipboard()->setText(d->m_currentChange);
1079 QStringList VCSBaseEditorWidget::annotationPreviousVersions(const QString &) const
1081 return QStringList();
1084 void VCSBaseEditorWidget::slotPaste()
1086 // Retrieve service by soft dependency.
1087 QObject *pasteService =
1088 ExtensionSystem::PluginManager::instance()
1089 ->getObjectByClassName("CodePaster::CodePasterService");
1091 QMetaObject::invokeMethod(pasteService, "postCurrentEditor");
1093 QMessageBox::information(this, tr("Unable to Paste"),
1094 tr("Code pasting services are not available."));
1098 bool VCSBaseEditorWidget::isRevertDiffChunkEnabled() const
1100 return d->m_revertChunkEnabled;
1103 void VCSBaseEditorWidget::setRevertDiffChunkEnabled(bool e)
1105 d->m_revertChunkEnabled = e;
1108 bool VCSBaseEditorWidget::canApplyDiffChunk(const DiffChunk &dc) const
1112 const QFileInfo fi(dc.fileName);
1113 // Default implementation using patch.exe relies on absolute paths.
1114 return fi.isFile() && fi.isAbsolute() && fi.isWritable();
1117 // Default implementation of revert: Apply a chunk by piping it into patch,
1118 // (passing '-R' for revert), assuming we got absolute paths from the VCS plugins.
1119 bool VCSBaseEditorWidget::applyDiffChunk(const DiffChunk &dc, bool revert) const
1121 return VCSBasePlugin::runPatch(dc.asPatch(), QString(), 0, revert);
1124 void VCSBaseEditorWidget::slotApplyDiffChunk()
1126 const QAction *a = qobject_cast<QAction *>(sender());
1127 QTC_ASSERT(a, return ; )
1128 const Internal::DiffChunkAction chunkAction = qvariant_cast<Internal::DiffChunkAction>(a->data());
1129 const QString title = chunkAction.revert ? tr("Revert Chunk") : tr("Apply Chunk");
1130 const QString question = chunkAction.revert ?
1131 tr("Would you like to revert the chunk?") : tr("Would you like to apply the chunk?");
1132 if (QMessageBox::No == QMessageBox::question(this, title, question, QMessageBox::Yes|QMessageBox::No))
1135 if (applyDiffChunk(chunkAction.chunk, chunkAction.revert)) {
1136 if (chunkAction.revert) {
1137 emit diffChunkReverted(chunkAction.chunk);
1139 emit diffChunkApplied(chunkAction.chunk);
1144 // Tagging of editors for re-use.
1145 QString VCSBaseEditorWidget::editorTag(EditorContentType t,
1146 const QString &workingDirectory,
1147 const QStringList &files,
1148 const QString &revision)
1150 const QChar colon = QLatin1Char(':');
1151 QString rc = QString::number(t);
1153 if (!revision.isEmpty()) {
1157 rc += workingDirectory;
1158 if (!files.isEmpty()) {
1160 rc += files.join(QString(colon));
1165 static const char tagPropertyC[] = "_q_VCSBaseEditorTag";
1167 void VCSBaseEditorWidget::tagEditor(Core::IEditor *e, const QString &tag)
1169 e->setProperty(tagPropertyC, QVariant(tag));
1172 Core::IEditor* VCSBaseEditorWidget::locateEditorByTag(const QString &tag)
1174 Core::IEditor *rc = 0;
1175 foreach (Core::IEditor *ed, Core::EditorManager::instance()->openedEditors()) {
1176 const QVariant tagPropertyValue = ed->property(tagPropertyC);
1177 if (tagPropertyValue.type() == QVariant::String && tagPropertyValue.toString() == tag) {
1182 if (VCSBase::Constants::Internal::debug)
1183 qDebug() << "locateEditorByTag " << tag << rc;
1187 } // namespace VCSBase
1189 #include "vcsbaseeditor.moc"