OSDN Git Service

b4b636bae99cd73b1098141e466caf8efe34afdd
[qt-creator-jp/qt-creator-jp.git] / src / plugins / vcsbase / vcsbaseeditor.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
8 **
9 **
10 ** GNU Lesser General Public License Usage
11 **
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.
18 **
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.
22 **
23 ** Other Usage
24 **
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.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at info@qt.nokia.com.
30 **
31 **************************************************************************/
32
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"
40
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>
58
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>
79
80 /*!
81     \enum VCSBase::EditorContentType
82
83     \brief Contents of a VCSBaseEditor and its interaction.
84
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:
90            \code
91            <change description>: file line
92            \endcode
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'.
96
97     \sa VCSBase::VCSBaseEditorWidget
98 */
99
100 namespace VCSBase {
101
102 /*!
103     \class VCSBase::DiffChunk
104
105     \brief A diff chunk consisting of file name and chunk data.
106 */
107
108 bool DiffChunk::isValid() const
109 {
110     return !fileName.isEmpty() && !chunk.isEmpty();
111 }
112
113 QByteArray DiffChunk::asPatch() const
114 {
115     const QByteArray fileNameBA = QFile::encodeName(fileName);
116     QByteArray rc = "--- ";
117     rc += fileNameBA;
118     rc += "\n+++ ";
119     rc += fileNameBA;
120     rc += '\n';
121     rc += chunk;
122     return rc;
123 }
124
125 namespace Internal {
126
127 // Data to be passed to apply/revert diff chunk actions.
128 class DiffChunkAction
129 {
130 public:
131     DiffChunkAction(const DiffChunk &dc = DiffChunk(), bool revertIn = false) :
132         chunk(dc), revert(revertIn) {}
133
134     DiffChunk chunk;
135     bool revert;
136 };
137
138 } // namespace Internal
139 } // VCSBase
140
141 Q_DECLARE_METATYPE(VCSBase::Internal::DiffChunkAction)
142
143 namespace VCSBase {
144
145 /*!
146     \class VCSBase::VCSBaseEditor
147
148     \brief An editor with no support for duplicates.
149
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.
153 */
154
155 class VCSBaseEditor : public TextEditor::BaseTextEditor
156 {
157     Q_OBJECT
158 public:
159     VCSBaseEditor(VCSBaseEditorWidget *, const VCSBaseEditorParameters *type);
160
161     bool duplicateSupported() const { return false; }
162     Core::IEditor *duplicate(QWidget * /*parent*/) { return 0; }
163     QString id() const { return m_id; }
164
165     bool isTemporary() const { return m_temporary; }
166     void setTemporary(bool t) { m_temporary = t; }
167
168 signals:
169     void describeRequested(const QString &source, const QString &change);
170     void annotateRevisionRequested(const QString &source, const QString &change, int line);
171
172 private:
173     QString m_id;
174     bool m_temporary;
175 };
176
177 VCSBaseEditor::VCSBaseEditor(VCSBaseEditorWidget *widget,
178                              const VCSBaseEditorParameters *type)  :
179     BaseTextEditor(widget),
180     m_id(type->id),
181     m_temporary(false)
182 {
183     setContext(Core::Context(type->context, TextEditor::Constants::C_TEXTEDITOR));
184 }
185
186 // Diff editor: creates a browse combo in the toolbar for diff output.
187 class VCSBaseDiffEditor : public VCSBaseEditor
188 {
189 public:
190     VCSBaseDiffEditor(VCSBaseEditorWidget *, const VCSBaseEditorParameters *type);
191
192     QComboBox *diffFileBrowseComboBox() const { return m_diffFileBrowseComboBox; }
193
194 private:
195     QComboBox *m_diffFileBrowseComboBox;
196 };
197
198 VCSBaseDiffEditor::VCSBaseDiffEditor(VCSBaseEditorWidget *w, const VCSBaseEditorParameters *type) :
199     VCSBaseEditor(w, type),
200     m_diffFileBrowseComboBox(new QComboBox)
201 {
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);
207
208     insertExtraToolBarWidget(Left, m_diffFileBrowseComboBox);
209 }
210
211 // ----------- VCSBaseEditorPrivate
212
213 struct VCSBaseEditorWidgetPrivate
214 {
215     VCSBaseEditorWidgetPrivate(const VCSBaseEditorParameters *type);
216
217     const VCSBaseEditorParameters *m_parameters;
218
219     QString m_currentChange;
220     QString m_source;
221     QString m_diffBaseDirectory;
222
223     QRegExp m_diffFilePattern;
224     QList<int> m_diffSections; // line number where this section starts
225     int m_cursorLine;
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;
234 };
235
236 VCSBaseEditorWidgetPrivate::VCSBaseEditorWidgetPrivate(const VCSBaseEditorParameters *type)  :
237     m_parameters(type),
238     m_cursorLine(-1),
239     m_annotateRevisionTextFormat(VCSBaseEditorWidget::tr("Annotate \"%1\"")),
240     m_copyRevisionTextFormat(VCSBaseEditorWidget::tr("Copy \"%1\"")),
241     m_fileLogAnnotateEnabled(false),
242     m_editor(0),
243     m_configurationWidget(0),
244     m_revertChunkEnabled(false),
245     m_mouseDragging(false)
246 {
247 }
248
249 /*!
250     \struct VCSBase::VCSBaseEditorParameters
251
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.
255
256     \sa VCSBase::VCSBaseEditorWidget, VCSBase::BaseVCSEditorFactory, VCSBase::EditorContentType
257 */
258
259 /*!
260     \class VCSBase::VCSBaseEditorWidget
261
262     \brief Base class for editors showing version control system output
263     of the type enumerated by EditorContentType.
264
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.
268
269     \sa VCSBase::BaseVCSEditorFactory, VCSBase::VCSBaseEditorParameters, VCSBase::EditorContentType
270 */
271
272 VCSBaseEditorWidget::VCSBaseEditorWidget(const VCSBaseEditorParameters *type, QWidget *parent)
273   : BaseTextEditorWidget(parent),
274     d(new VCSBaseEditorWidgetPrivate(type))
275 {
276     if (VCSBase::Constants::Internal::debug)
277         qDebug() << "VCSBaseEditor::VCSBaseEditor" << type->type << type->id;
278
279     viewport()->setMouseTracking(true);
280     setBaseTextDocument(new Internal::VCSBaseTextDocument);
281     setMimeType(QLatin1String(d->m_parameters->mimeType));
282 }
283
284 void VCSBaseEditorWidget::init()
285 {
286     switch (d->m_parameters->type) {
287     case RegularCommandOutput:
288     case LogOutput:
289     case AnnotateOutput:
290         // Annotation highlighting depends on contents, which is set later on
291         connect(this, SIGNAL(textChanged()), this, SLOT(slotActivateAnnotation()));
292         break;
293     case DiffOutput: {
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()));
300     }
301         break;
302     }
303     TextEditor::TextEditorSettings::instance()->initializeEditor(this);
304 }
305
306 VCSBaseEditorWidget::~VCSBaseEditorWidget()
307 {
308     delete d;
309 }
310
311 void VCSBaseEditorWidget::setForceReadOnly(bool b)
312 {
313     Internal::VCSBaseTextDocument *vbd = qobject_cast<Internal::VCSBaseTextDocument*>(baseTextDocument());
314     VCSBaseEditor *eda = qobject_cast<VCSBaseEditor *>(editor());
315     QTC_ASSERT(vbd != 0 && eda != 0, return);
316     setReadOnly(b);
317     vbd->setForceReadOnly(b);
318     eda->setTemporary(b);
319 }
320
321 bool VCSBaseEditorWidget::isForceReadOnly() const
322 {
323     const Internal::VCSBaseTextDocument *vbd = qobject_cast<const Internal::VCSBaseTextDocument*>(baseTextDocument());
324     QTC_ASSERT(vbd, return false);
325     return vbd->isForceReadOnly();
326 }
327
328 QString VCSBaseEditorWidget::source() const
329 {
330     return d->m_source;
331 }
332
333 void VCSBaseEditorWidget::setSource(const  QString &source)
334 {
335     d->m_source = source;
336 }
337
338 QString VCSBaseEditorWidget::annotateRevisionTextFormat() const
339 {
340     return d->m_annotateRevisionTextFormat;
341 }
342
343 void VCSBaseEditorWidget::setAnnotateRevisionTextFormat(const QString &f)
344 {
345     d->m_annotateRevisionTextFormat = f;
346 }
347
348 QString VCSBaseEditorWidget::annotatePreviousRevisionTextFormat() const
349 {
350     return d->m_annotatePreviousRevisionTextFormat;
351 }
352
353 void VCSBaseEditorWidget::setAnnotatePreviousRevisionTextFormat(const QString &f)
354 {
355     d->m_annotatePreviousRevisionTextFormat = f;
356 }
357
358 QString VCSBaseEditorWidget::copyRevisionTextFormat() const
359 {
360     return d->m_copyRevisionTextFormat;
361 }
362
363 void VCSBaseEditorWidget::setCopyRevisionTextFormat(const QString &f)
364 {
365     d->m_copyRevisionTextFormat = f;
366 }
367
368 bool VCSBaseEditorWidget::isFileLogAnnotateEnabled() const
369 {
370     return d->m_fileLogAnnotateEnabled;
371 }
372
373 void VCSBaseEditorWidget::setFileLogAnnotateEnabled(bool e)
374 {
375     d->m_fileLogAnnotateEnabled = e;
376 }
377
378 QString VCSBaseEditorWidget::diffBaseDirectory() const
379 {
380     return d->m_diffBaseDirectory;
381 }
382
383 void VCSBaseEditorWidget::setDiffBaseDirectory(const QString &bd)
384 {
385     d->m_diffBaseDirectory = bd;
386 }
387
388 QTextCodec *VCSBaseEditorWidget::codec() const
389 {
390     return const_cast<QTextCodec *>(baseTextDocument()->codec());
391 }
392
393 void VCSBaseEditorWidget::setCodec(QTextCodec *c)
394 {
395     if (c) {
396         baseTextDocument()->setCodec(c);
397     } else {
398         qWarning("%s: Attempt to set 0 codec.", Q_FUNC_INFO);
399     }
400 }
401
402 EditorContentType VCSBaseEditorWidget::contentType() const
403 {
404     return d->m_parameters->type;
405 }
406
407 bool VCSBaseEditorWidget::isModified() const
408 {
409     return false;
410 }
411
412 TextEditor::BaseTextEditor *VCSBaseEditorWidget::createEditor()
413 {
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)));
420         editor = de;
421     } else {
422         editor = new VCSBaseEditor(this, d->m_parameters);
423     }
424     d->m_editor = editor;
425
426     // Pass on signals.
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)));
431     return editor;
432 }
433
434 void VCSBaseEditorWidget::slotPopulateDiffBrowser()
435 {
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();
443     int lineNumber = 0;
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) {
451                 lastFileName = file;
452                 // ignore any headers
453                 d->m_diffSections.push_back(d->m_diffSections.empty() ? 0 : lineNumber);
454                 diffBrowseComboBox->addItem(QFileInfo(file).fileName());
455             }
456         }
457     }
458 }
459
460 void VCSBaseEditorWidget::slotDiffBrowse(int index)
461 {
462     // goto diffed file as indicated by index/line number
463     if (index < 0 || index >= d->m_diffSections.size())
464         return;
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(), &currentLine, &currentColumn);
469     if (lineNumber != currentLine) {
470         Core::EditorManager *editorManager = Core::EditorManager::instance();
471         editorManager->addCurrentPositionToNavigationHistory();
472         gotoLine(lineNumber, 0);
473     }
474 }
475
476 // Locate a line number in the list of diff sections.
477 static int sectionOfLine(int line, const QList<int> &sections)
478 {
479     const int sectionCount = sections.size();
480     if (!sectionCount)
481         return -1;
482     // The section at s indicates where the section begins.
483     for (int s = 0; s < sectionCount; s++) {
484         if (line < sections.at(s))
485             return s - 1;
486     }
487     return sectionCount - 1;
488 }
489
490 void VCSBaseEditorWidget::slotDiffCursorPositionChanged()
491 {
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)
497         return;
498     // Which section does it belong to?
499     d->m_cursorLine = newCursorLine;
500     const int section = sectionOfLine(d->m_cursorLine, d->m_diffSections);
501     if (section != -1) {
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);
508         }
509     }
510 }
511
512 QAction *VCSBaseEditorWidget::createDescribeAction(const QString &change)
513 {
514     QAction *a = new QAction(tr("Describe change %1").arg(change), 0);
515     connect(a, SIGNAL(triggered()), this, SLOT(describe()));
516     return a;
517 }
518
519 QAction *VCSBaseEditorWidget::createAnnotateAction(const QString &change, bool previous)
520 {
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);
525     a->setData(change);
526     connect(a, SIGNAL(triggered()), this, SLOT(slotAnnotateRevision()));
527     return a;
528 }
529
530 QAction *VCSBaseEditorWidget::createCopyRevisionAction(const QString &change)
531 {
532     QAction *a = new QAction(d->m_copyRevisionTextFormat.arg(change), 0);
533     a->setData(change);
534     connect(a, SIGNAL(triggered()), this, SLOT(slotCopyRevision()));
535     return a;
536 }
537
538 void VCSBaseEditorWidget::contextMenuEvent(QContextMenuEvent *e)
539 {
540     QMenu *menu = createStandardContextMenu();
541     // 'click on change-interaction'
542     switch (d->m_parameters->type) {
543     case LogOutput:
544     case AnnotateOutput:
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));
554                 break;
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
565                 }
566                 break;
567             default:
568                 break;
569             }         // switch type
570         }             // has current change
571         break;
572     case DiffOutput: {
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()));
594     }
595         break;
596     default:
597         break;
598     }
599     menu->exec(e->globalPos());
600     delete menu;
601 }
602
603 void VCSBaseEditorWidget::mouseMoveEvent(QMouseEvent *e)
604 {
605     if (e->buttons()) {
606         d->m_mouseDragging = true;
607         TextEditor::BaseTextEditorWidget::mouseMoveEvent(e);
608         return;
609     }
610
611     bool overrideCursor = false;
612     Qt::CursorShape cursorShape;
613
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;
620             sel.cursor = cursor;
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;
627         }
628     } else {
629         setExtraSelections(OtherSelection, QList<QTextEdit::ExtraSelection>());
630         overrideCursor = true;
631         cursorShape = Qt::IBeamCursor;
632     }
633     TextEditor::BaseTextEditorWidget::mouseMoveEvent(e);
634
635     if (overrideCursor)
636         viewport()->setCursor(cursorShape);
637 }
638
639 void VCSBaseEditorWidget::mouseReleaseEvent(QMouseEvent *e)
640 {
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()) {
648                 describe();
649                 e->accept();
650                 return;
651             }
652         }
653     }
654     TextEditor::BaseTextEditorWidget::mouseReleaseEvent(e);
655 }
656
657 void VCSBaseEditorWidget::mouseDoubleClickEvent(QMouseEvent *e)
658 {
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);
663         }
664     }
665     TextEditor::BaseTextEditorWidget::mouseDoubleClickEvent(e);
666 }
667
668 void VCSBaseEditorWidget::keyPressEvent(QKeyEvent *e)
669 {
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());
674         return;
675     }
676     BaseTextEditorWidget::keyPressEvent(e);
677 }
678
679 void VCSBaseEditorWidget::describe()
680 {
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);
685 }
686
687 void VCSBaseEditorWidget::slotActivateAnnotation()
688 {
689     // The annotation highlighting depends on contents (change number
690     // set with assigned colors)
691     if (d->m_parameters->type != AnnotateOutput)
692         return;
693
694     const QSet<QString> changes = annotationChanges();
695     if (changes.isEmpty())
696         return;
697     if (VCSBase::Constants::Internal::debug)
698         qDebug() << "VCSBaseEditor::slotActivateAnnotation(): #" << changes.size();
699
700     disconnect(this, SIGNAL(textChanged()), this, SLOT(slotActivateAnnotation()));
701
702     if (BaseAnnotationHighlighter *ah = qobject_cast<BaseAnnotationHighlighter *>(baseTextDocument()->syntaxHighlighter())) {
703         ah->setChangeNumbers(changes);
704         ah->rehighlight();
705     } else {
706         baseTextDocument()->setSyntaxHighlighter(createAnnotationHighlighter(changes));
707     }
708 }
709
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)
714 {
715     if (!line.startsWith(QLatin1String("@@ ")))
716         return false;
717     const int endPos = line.indexOf(QLatin1String(" @@"), 3);
718     if (endPos == -1)
719         return false;
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)
724         return false;
725     const int lineNumberPos = plusPos + 1;
726     const int commaPos = line.indexOf(QLatin1Char(','), lineNumberPos);
727     if (commaPos == -1 || commaPos > endPos)
728         return false;
729     const QString lineNumberStr = line.mid(lineNumberPos, commaPos - lineNumberPos);
730     bool ok;
731     *modifiedLineNumber = lineNumberStr.toInt(&ok);
732     return ok;
733 }
734
735 void VCSBaseEditorWidget::jumpToChangeFromDiff(QTextCursor cursor)
736 {
737     int chunkStart = 0;
738     int lineCount = -1;
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. */
744         return;
745     for ( ; block.isValid() ; block = block.previous()) {
746         const QString line = block.text();
747         if (checkChunkLine(line, &chunkStart)) {
748             break;
749         } else {
750             if (!line.startsWith(deletionIndicator))
751                 ++lineCount;
752         }
753     }
754
755     if (VCSBase::Constants::Internal::debug)
756         qDebug() << "VCSBaseEditor::jumpToChangeFromDiff()1" << chunkStart << lineCount;
757
758     if (chunkStart == -1 || lineCount < 0 || !block.isValid())
759         return;
760
761     // find the filename in previous line, map depot name back
762     block = block.previous();
763     if (!block.isValid())
764         return;
765     const QString fileName = fileNameFromDiffSpecification(block);
766
767     const bool exists = fileName.isEmpty() ? false : QFile::exists(fileName);
768
769     if (VCSBase::Constants::Internal::debug)
770         qDebug() << "VCSBaseEditor::jumpToChangeFromDiff()2" << fileName << "ex=" << exists << "line" << chunkStart <<  lineCount;
771
772     if (!exists)
773         return;
774
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);
779 }
780
781 // cut out chunk and determine file name.
782 DiffChunk VCSBaseEditorWidget::diffChunk(QTextCursor cursor) const
783 {
784     QTC_ASSERT(d->m_parameters->type == DiffOutput, return DiffChunk(); )
785     DiffChunk rc;
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. */
791         return rc;
792
793     int chunkStart = 0;
794     for ( ; block.isValid() ; block = block.previous()) {
795         if (checkChunkLine(block.text(), &chunkStart)) {
796             break;
797         }
798     }
799     if (!chunkStart || !block.isValid())
800         return rc;
801     rc.fileName = fileNameFromDiffSpecification(block);
802     if (rc.fileName.isEmpty())
803         return rc;
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)) {
811             break;
812         } else {
813             unicode += line;
814             unicode += QLatin1Char('\n');
815         }
816     }
817     const QTextCodec *cd = textCodec();
818     rc.chunk = cd ? cd->fromUnicode(unicode) : unicode.toLocal8Bit();
819     return rc;
820 }
821
822 void VCSBaseEditorWidget::setPlainTextData(const QByteArray &data)
823 {
824     if (data.size() > Core::EditorManager::maxTextFileSize()) {
825         setPlainText(msgTextTooLarge(data.size()));
826     } else {
827         setPlainText(codec()->toUnicode(data));
828     }
829 }
830
831 void VCSBaseEditorWidget::setFontSettings(const TextEditor::FontSettings &fs)
832 {
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);
843             }
844             highlighter->setFormats(fs.toTextCharFormats(categories));
845             highlighter->rehighlight();
846         }
847     }
848 }
849
850 const VCSBaseEditorParameters *VCSBaseEditorWidget::findType(const VCSBaseEditorParameters *array,
851                                                        int arraySize,
852                                                        EditorContentType et)
853 {
854     for (int i = 0; i < arraySize; i++)
855         if (array[i].type == et)
856             return array + i;
857     return 0;
858 }
859
860 // Find the codec used for a file querying the editor.
861 static QTextCodec *findFileCodec(const QString &source)
862 {
863     typedef QList<Core::IEditor *> EditorList;
864
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();
873                 return codec;
874             }
875     }
876     if (VCSBase::Constants::Internal::debug)
877         qDebug() << Q_FUNC_INFO << source << "not found";
878     return 0;
879 }
880
881 // Find the codec by checking the projects (root dir of project file)
882 static QTextCodec *findProjectCodec(const QString &dir)
883 {
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();
896                     return codec;
897                 }
898     }
899     if (VCSBase::Constants::Internal::debug)
900         qDebug() << Q_FUNC_INFO << dir << "not found";
901     return 0;
902 }
903
904 QTextCodec *VCSBaseEditorWidget::getCodec(const QString &source)
905 {
906     if (!source.isEmpty()) {
907         // Check file
908         const QFileInfo sourceFi(source);
909         if (sourceFi.isFile())
910             if (QTextCodec *fc = findFileCodec(source))
911                 return fc;
912         // Find by project via directory
913         if (QTextCodec *pc = findProjectCodec(sourceFi.isFile() ? sourceFi.absolutePath() : source))
914             return pc;
915     }
916     QTextCodec *sys = QTextCodec::codecForLocale();
917     if (VCSBase::Constants::Internal::debug)
918         qDebug() << Q_FUNC_INFO << source << "defaulting to " << sys->name();
919     return sys;
920 }
921
922 QTextCodec *VCSBaseEditorWidget::getCodec(const QString &workingDirectory, const QStringList &files)
923 {
924     if (files.empty())
925         return getCodec(workingDirectory);
926     return getCodec(workingDirectory + QLatin1Char('/') + files.front());
927 }
928
929 VCSBaseEditorWidget *VCSBaseEditorWidget::getVcsBaseEditor(const Core::IEditor *editor)
930 {
931     if (const TextEditor::BaseTextEditor *be = qobject_cast<const TextEditor::BaseTextEditor *>(editor))
932         return qobject_cast<VCSBaseEditorWidget *>(be->editorWidget());
933     return 0;
934 }
935
936 // Return line number of current editor if it matches.
937 int VCSBaseEditorWidget::lineNumberOfCurrentEditor(const QString &currentFile)
938 {
939     Core::IEditor *ed = Core::EditorManager::instance()->currentEditor();
940     if (!ed)
941         return -1;
942     if (!currentFile.isEmpty()) {
943         const Core::IFile *ifile  = ed->file();
944         if (!ifile || ifile->fileName() != currentFile)
945             return -1;
946     }
947     const TextEditor::BaseTextEditor *eda = qobject_cast<const TextEditor::BaseTextEditor *>(ed);
948     if (!eda)
949         return -1;
950     return eda->currentLine();
951 }
952
953 bool VCSBaseEditorWidget::gotoLineOfEditor(Core::IEditor *e, int lineNumber)
954 {
955     if (lineNumber >= 0 && e) {
956         if (TextEditor::BaseTextEditor *be = qobject_cast<TextEditor::BaseTextEditor*>(e)) {
957             be->gotoLine(lineNumber);
958             return true;
959         }
960     }
961     return false;
962 }
963
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)
968 {
969     if (fileName.isEmpty())
970         return workingDirectory;
971
972     QString rc = workingDirectory;
973     const QChar slash = QLatin1Char('/');
974     if (!rc.isEmpty() && !(rc.endsWith(slash) || rc.endsWith(QLatin1Char('\\'))))
975         rc += slash;
976     rc += fileName;
977     return rc;
978 }
979
980 QString VCSBaseEditorWidget::getSource(const QString &workingDirectory,
981                                  const QStringList &fileNames)
982 {
983     return fileNames.size() == 1 ?
984             getSource(workingDirectory, fileNames.front()) :
985             workingDirectory;
986 }
987
988 QString VCSBaseEditorWidget::getTitleId(const QString &workingDirectory,
989                                   const QStringList &fileNames,
990                                   const QString &revision)
991 {
992     QString rc;
993     switch (fileNames.size()) {
994     case 0:
995         rc = workingDirectory;
996         break;
997     case 1:
998         rc = fileNames.front();
999         break;
1000     default:
1001         rc = fileNames.join(QLatin1String(", "));
1002         break;
1003     }
1004     if (!revision.isEmpty()) {
1005         rc += QLatin1Char(':');
1006         rc += revision;
1007     }
1008     return rc;
1009 }
1010
1011 bool VCSBaseEditorWidget::setConfigurationWidget(QWidget *w)
1012 {
1013     if (!d->m_editor || d->m_configurationWidget)
1014         return false;
1015
1016     d->m_configurationWidget = w;
1017     d->m_editor->insertExtraToolBarWidget(TextEditor::BaseTextEditor::Right, w);
1018
1019     return true;
1020 }
1021
1022 QWidget *VCSBaseEditorWidget::configurationWidget() const
1023 {
1024     return d->m_configurationWidget;
1025 }
1026
1027 // Find the complete file from a diff relative specification.
1028 QString VCSBaseEditorWidget::findDiffFile(const QString &f,
1029                                           Core::IVersionControl *control /* = 0 */) const
1030 {
1031     // Check if file is absolute
1032     const QFileInfo in(f);
1033     if (in.isAbsolute())
1034         return in.isFile() ? f : QString();
1035
1036     // 1) Try base dir
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();
1042     }
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();
1051
1052         QString topLevel;
1053         if (control && control->managesDirectory(sourceDir, &topLevel)) {
1054             const QFileInfo topLevelFileInfo(topLevel + slash + f);
1055             if (topLevelFileInfo.isFile())
1056                 return topLevelFileInfo.absoluteFilePath();
1057         }
1058     }
1059
1060     // 3) Try working directory
1061     if (in.isFile())
1062         return in.absoluteFilePath();
1063
1064     return QString();
1065 }
1066
1067 void VCSBaseEditorWidget::slotAnnotateRevision()
1068 {
1069     if (const QAction *a = qobject_cast<const QAction *>(sender()))
1070         emit annotateRevisionRequested(source(), a->data().toString(),
1071                                        editor()->currentLine());
1072 }
1073
1074 void VCSBaseEditorWidget::slotCopyRevision()
1075 {
1076     QApplication::clipboard()->setText(d->m_currentChange);
1077 }
1078
1079 QStringList VCSBaseEditorWidget::annotationPreviousVersions(const QString &) const
1080 {
1081     return QStringList();
1082 }
1083
1084 void VCSBaseEditorWidget::slotPaste()
1085 {
1086     // Retrieve service by soft dependency.
1087     QObject *pasteService =
1088             ExtensionSystem::PluginManager::instance()
1089                 ->getObjectByClassName("CodePaster::CodePasterService");
1090     if (pasteService) {
1091         QMetaObject::invokeMethod(pasteService, "postCurrentEditor");
1092     } else {
1093         QMessageBox::information(this, tr("Unable to Paste"),
1094                                  tr("Code pasting services are not available."));
1095     }
1096 }
1097
1098 bool VCSBaseEditorWidget::isRevertDiffChunkEnabled() const
1099 {
1100     return d->m_revertChunkEnabled;
1101 }
1102
1103 void VCSBaseEditorWidget::setRevertDiffChunkEnabled(bool e)
1104 {
1105     d->m_revertChunkEnabled = e;
1106 }
1107
1108 bool VCSBaseEditorWidget::canApplyDiffChunk(const DiffChunk &dc) const
1109 {
1110     if (!dc.isValid())
1111         return false;
1112     const QFileInfo fi(dc.fileName);
1113     // Default implementation using patch.exe relies on absolute paths.
1114     return fi.isFile() && fi.isAbsolute() && fi.isWritable();
1115 }
1116
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
1120 {
1121     return VCSBasePlugin::runPatch(dc.asPatch(), QString(), 0, revert);
1122 }
1123
1124 void VCSBaseEditorWidget::slotApplyDiffChunk()
1125 {
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))
1133         return;
1134
1135     if (applyDiffChunk(chunkAction.chunk, chunkAction.revert)) {
1136         if (chunkAction.revert) {
1137             emit diffChunkReverted(chunkAction.chunk);
1138         } else {
1139             emit diffChunkApplied(chunkAction.chunk);
1140         }
1141     }
1142 }
1143
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)
1149 {
1150     const QChar colon = QLatin1Char(':');
1151     QString rc = QString::number(t);
1152     rc += colon;
1153     if (!revision.isEmpty()) {
1154         rc += revision;
1155         rc += colon;
1156     }
1157     rc += workingDirectory;
1158     if (!files.isEmpty()) {
1159         rc += colon;
1160         rc += files.join(QString(colon));
1161     }
1162     return rc;
1163 }
1164
1165 static const char tagPropertyC[] = "_q_VCSBaseEditorTag";
1166
1167 void VCSBaseEditorWidget::tagEditor(Core::IEditor *e, const QString &tag)
1168 {
1169     e->setProperty(tagPropertyC, QVariant(tag));
1170 }
1171
1172 Core::IEditor* VCSBaseEditorWidget::locateEditorByTag(const QString &tag)
1173 {
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) {
1178             rc = ed;
1179             break;
1180         }
1181     }
1182     if (VCSBase::Constants::Internal::debug)
1183         qDebug() << "locateEditorByTag " << tag << rc;
1184     return rc;
1185 }
1186
1187 } // namespace VCSBase
1188
1189 #include "vcsbaseeditor.moc"