OSDN Git Service

69b7b24a18ad6afd73a9f89162c9af48da3098b3
[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 (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights.  These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "vcsbaseeditor.h"
35 #include "diffhighlighter.h"
36 #include "baseannotationhighlighter.h"
37 #include "vcsbasetextdocument.h"
38 #include "vcsbaseconstants.h"
39
40 #include <coreplugin/editormanager/editormanager.h>
41 #include <coreplugin/ifile.h>
42 #include <coreplugin/iversioncontrol.h>
43 #include <coreplugin/coreconstants.h>
44 #include <coreplugin/modemanager.h>
45 #include <extensionsystem/pluginmanager.h>
46 #include <projectexplorer/editorconfiguration.h>
47 #include <projectexplorer/projectexplorer.h>
48 #include <projectexplorer/project.h>
49 #include <projectexplorer/session.h>
50 #include <texteditor/fontsettings.h>
51 #include <texteditor/texteditorconstants.h>
52 #include <utils/qtcassert.h>
53
54 #include <QtCore/QDebug>
55 #include <QtCore/QFileInfo>
56 #include <QtCore/QProcess>
57 #include <QtCore/QRegExp>
58 #include <QtCore/QSet>
59 #include <QtCore/QTextCodec>
60 #include <QtCore/QTextStream>
61 #include <QtGui/QTextBlock>
62 #include <QtGui/QAction>
63 #include <QtGui/QKeyEvent>
64 #include <QtGui/QLayout>
65 #include <QtGui/QMenu>
66 #include <QtGui/QTextCursor>
67 #include <QtGui/QTextEdit>
68 #include <QtGui/QComboBox>
69 #include <QtGui/QToolBar>
70 #include <QtGui/QClipboard>
71 #include <QtGui/QApplication>
72
73 namespace VCSBase {
74
75 // VCSBaseEditor: An editor with no support for duplicates.
76 // Creates a browse combo in the toolbar for diff output.
77 // It also mirrors the signals of the VCSBaseEditor since the editor
78 // manager passes the editor around.
79 class VCSBaseEditor : public TextEditor::BaseTextEditor
80 {
81     Q_OBJECT
82 public:
83     VCSBaseEditor(VCSBaseEditorWidget *,
84                   const VCSBaseEditorParameters *type);
85     Core::Context context() const;
86
87     bool duplicateSupported() const { return false; }
88     Core::IEditor *duplicate(QWidget * /*parent*/) { return 0; }
89     QString id() const { return m_id; }
90
91     bool isTemporary() const { return m_temporary; }
92     void setTemporary(bool t) { m_temporary = t; }
93
94 signals:
95     void describeRequested(const QString &source, const QString &change);
96     void annotateRevisionRequested(const QString &source, const QString &change, int line);
97
98 private:
99     QString m_id;
100     Core::Context m_context;
101     bool m_temporary;
102 };
103
104 VCSBaseEditor::VCSBaseEditor(VCSBaseEditorWidget *widget,
105                              const VCSBaseEditorParameters *type)  :
106     BaseTextEditor(widget),
107     m_id(type->id),
108     m_context(type->context, TextEditor::Constants::C_TEXTEDITOR),
109     m_temporary(false)
110 {
111 }
112
113 Core::Context VCSBaseEditor::context() const
114 {
115     return m_context;
116 }
117
118 // Diff editor: creates a browse combo in the toolbar for diff output.
119 class VCSBaseDiffEditor : public VCSBaseEditor
120 {
121 public:
122     VCSBaseDiffEditor(VCSBaseEditorWidget *, const VCSBaseEditorParameters *type);
123
124     QComboBox *diffFileBrowseComboBox() const { return m_diffFileBrowseComboBox; }
125
126 private:
127     QComboBox *m_diffFileBrowseComboBox;
128 };
129
130 VCSBaseDiffEditor::VCSBaseDiffEditor(VCSBaseEditorWidget *w, const VCSBaseEditorParameters *type) :
131     VCSBaseEditor(w, type),
132     m_diffFileBrowseComboBox(new QComboBox)
133 {
134     m_diffFileBrowseComboBox->setMinimumContentsLength(20);
135     // Make the combo box prefer to expand
136     QSizePolicy policy = m_diffFileBrowseComboBox->sizePolicy();
137     policy.setHorizontalPolicy(QSizePolicy::Expanding);
138     m_diffFileBrowseComboBox->setSizePolicy(policy);
139
140     insertExtraToolBarWidget(Left, m_diffFileBrowseComboBox);
141 }
142
143 // ----------- VCSBaseEditorPrivate
144
145 struct VCSBaseEditorWidgetPrivate
146 {
147     VCSBaseEditorWidgetPrivate(const VCSBaseEditorParameters *type);
148
149     const VCSBaseEditorParameters *m_parameters;
150
151     QString m_currentChange;
152     QString m_source;
153     QString m_diffBaseDirectory;
154
155     QRegExp m_diffFilePattern;
156     QList<int> m_diffSections; // line number where this section starts
157     int m_cursorLine;
158     QString m_annotateRevisionTextFormat;
159     QString m_annotatePreviousRevisionTextFormat;
160     QString m_copyRevisionTextFormat;
161     bool m_fileLogAnnotateEnabled;
162     TextEditor::BaseTextEditor *m_editor;
163     QWidget *m_configurationWidget;
164 };
165
166 VCSBaseEditorWidgetPrivate::VCSBaseEditorWidgetPrivate(const VCSBaseEditorParameters *type)  :
167     m_parameters(type),
168     m_cursorLine(-1),
169     m_annotateRevisionTextFormat(VCSBaseEditorWidget::tr("Annotate \"%1\"")),
170     m_copyRevisionTextFormat(VCSBaseEditorWidget::tr("Copy \"%1\"")),
171     m_fileLogAnnotateEnabled(false),
172     m_editor(0),
173     m_configurationWidget(0)
174 {
175 }
176
177 // ------------ VCSBaseEditor
178 VCSBaseEditorWidget::VCSBaseEditorWidget(const VCSBaseEditorParameters *type, QWidget *parent)
179   : BaseTextEditorWidget(parent),
180     d(new VCSBaseEditorWidgetPrivate(type))
181 {
182     if (VCSBase::Constants::Internal::debug)
183         qDebug() << "VCSBaseEditor::VCSBaseEditor" << type->type << type->id;
184
185     viewport()->setMouseTracking(true);
186     setBaseTextDocument(new Internal::VCSBaseTextDocument);
187     setMimeType(QLatin1String(d->m_parameters->mimeType));
188 }
189
190 void VCSBaseEditorWidget::init()
191 {
192     switch (d->m_parameters->type) {
193     case RegularCommandOutput:
194     case LogOutput:
195     case AnnotateOutput:
196         // Annotation highlighting depends on contents, which is set later on
197         connect(this, SIGNAL(textChanged()), this, SLOT(slotActivateAnnotation()));
198         break;
199     case DiffOutput: {
200         DiffHighlighter *dh = createDiffHighlighter();
201         setCodeFoldingSupported(true);
202         baseTextDocument()->setSyntaxHighlighter(dh);
203         d->m_diffFilePattern = dh->filePattern();
204         connect(this, SIGNAL(textChanged()), this, SLOT(slotPopulateDiffBrowser()));
205         connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(slotDiffCursorPositionChanged()));
206     }
207         break;
208     }
209 }
210
211 VCSBaseEditorWidget::~VCSBaseEditorWidget()
212 {
213     delete d;
214 }
215
216 void VCSBaseEditorWidget::setForceReadOnly(bool b)
217 {
218     Internal::VCSBaseTextDocument *vbd = qobject_cast<Internal::VCSBaseTextDocument*>(baseTextDocument());
219     VCSBaseEditor *eda = qobject_cast<VCSBaseEditor *>(editor());
220     QTC_ASSERT(vbd != 0 && eda != 0, return);
221     setReadOnly(b);
222     vbd->setForceReadOnly(b);
223     eda->setTemporary(b);
224 }
225
226 bool VCSBaseEditorWidget::isForceReadOnly() const
227 {
228     const Internal::VCSBaseTextDocument *vbd = qobject_cast<const Internal::VCSBaseTextDocument*>(baseTextDocument());
229     QTC_ASSERT(vbd, return false);
230     return vbd->isForceReadOnly();
231 }
232
233 QString VCSBaseEditorWidget::source() const
234 {
235     return d->m_source;
236 }
237
238 void VCSBaseEditorWidget::setSource(const  QString &source)
239 {
240     d->m_source = source;
241 }
242
243 QString VCSBaseEditorWidget::annotateRevisionTextFormat() const
244 {
245     return d->m_annotateRevisionTextFormat;
246 }
247
248 void VCSBaseEditorWidget::setAnnotateRevisionTextFormat(const QString &f)
249 {
250     d->m_annotateRevisionTextFormat = f;
251 }
252
253 QString VCSBaseEditorWidget::annotatePreviousRevisionTextFormat() const
254 {
255     return d->m_annotatePreviousRevisionTextFormat;
256 }
257
258 void VCSBaseEditorWidget::setAnnotatePreviousRevisionTextFormat(const QString &f)
259 {
260     d->m_annotatePreviousRevisionTextFormat = f;
261 }
262
263 QString VCSBaseEditorWidget::copyRevisionTextFormat() const
264 {
265     return d->m_copyRevisionTextFormat;
266 }
267
268 void VCSBaseEditorWidget::setCopyRevisionTextFormat(const QString &f)
269 {
270     d->m_copyRevisionTextFormat = f;
271 }
272
273 bool VCSBaseEditorWidget::isFileLogAnnotateEnabled() const
274 {
275     return d->m_fileLogAnnotateEnabled;
276 }
277
278 void VCSBaseEditorWidget::setFileLogAnnotateEnabled(bool e)
279 {
280     d->m_fileLogAnnotateEnabled = e;
281 }
282
283 QString VCSBaseEditorWidget::diffBaseDirectory() const
284 {
285     return d->m_diffBaseDirectory;
286 }
287
288 void VCSBaseEditorWidget::setDiffBaseDirectory(const QString &bd)
289 {
290     d->m_diffBaseDirectory = bd;
291 }
292
293 QTextCodec *VCSBaseEditorWidget::codec() const
294 {
295     return baseTextDocument()->codec();
296 }
297
298 void VCSBaseEditorWidget::setCodec(QTextCodec *c)
299 {
300     if (c) {
301         baseTextDocument()->setCodec(c);
302     } else {
303         qWarning("%s: Attempt to set 0 codec.", Q_FUNC_INFO);
304     }
305 }
306
307 EditorContentType VCSBaseEditorWidget::contentType() const
308 {
309     return d->m_parameters->type;
310 }
311
312 bool VCSBaseEditorWidget::isModified() const
313 {
314     return false;
315 }
316
317 TextEditor::BaseTextEditor *VCSBaseEditorWidget::createEditor()
318 {
319     TextEditor::BaseTextEditor *editor = 0;
320     if (d->m_parameters->type == DiffOutput) {
321         // Diff: set up diff file browsing
322         VCSBaseDiffEditor *de = new VCSBaseDiffEditor(this, d->m_parameters);
323         QComboBox *diffBrowseComboBox = de->diffFileBrowseComboBox();
324         connect(diffBrowseComboBox, SIGNAL(activated(int)), this, SLOT(slotDiffBrowse(int)));
325         editor = de;
326     } else {
327         editor = new VCSBaseEditor(this, d->m_parameters);
328     }
329     d->m_editor = editor;
330
331     // Pass on signals.
332     connect(this, SIGNAL(describeRequested(QString,QString)),
333             editor, SIGNAL(describeRequested(QString,QString)));
334     connect(this, SIGNAL(annotateRevisionRequested(QString,QString,int)),
335             editor, SIGNAL(annotateRevisionRequested(QString,QString,int)));
336     return editor;
337 }
338
339 void VCSBaseEditorWidget::slotPopulateDiffBrowser()
340 {
341     VCSBaseDiffEditor *de = static_cast<VCSBaseDiffEditor*>(editor());
342     QComboBox *diffBrowseComboBox = de->diffFileBrowseComboBox();
343     diffBrowseComboBox->clear();
344     d->m_diffSections.clear();
345     // Create a list of section line numbers (diffed files)
346     // and populate combo with filenames.
347     const QTextBlock cend = document()->end();
348     int lineNumber = 0;
349     QString lastFileName;
350     for (QTextBlock it = document()->begin(); it != cend; it = it.next(), lineNumber++) {
351         const QString text = it.text();
352         // Check for a new diff section (not repeating the last filename)
353         if (d->m_diffFilePattern.exactMatch(text)) {
354             const QString file = fileNameFromDiffSpecification(it);
355             if (!file.isEmpty() && lastFileName != file) {
356                 lastFileName = file;
357                 // ignore any headers
358                 d->m_diffSections.push_back(d->m_diffSections.empty() ? 0 : lineNumber);
359                 diffBrowseComboBox->addItem(QFileInfo(file).fileName());
360             }
361         }
362     }
363 }
364
365 void VCSBaseEditorWidget::slotDiffBrowse(int index)
366 {
367     // goto diffed file as indicated by index/line number
368     if (index < 0 || index >= d->m_diffSections.size())
369         return;
370     const int lineNumber = d->m_diffSections.at(index) + 1; // TextEdit uses 1..n convention
371     // check if we need to do something, especially to avoid messing up navigation history
372     int currentLine, currentColumn;
373     convertPosition(position(), &currentLine, &currentColumn);
374     if (lineNumber != currentLine) {
375         Core::EditorManager *editorManager = Core::EditorManager::instance();
376         editorManager->addCurrentPositionToNavigationHistory();
377         gotoLine(lineNumber, 0);
378     }
379 }
380
381 // Locate a line number in the list of diff sections.
382 static int sectionOfLine(int line, const QList<int> &sections)
383 {
384     const int sectionCount = sections.size();
385     if (!sectionCount)
386         return -1;
387     // The section at s indicates where the section begins.
388     for (int s = 0; s < sectionCount; s++) {
389         if (line < sections.at(s))
390             return s - 1;
391     }
392     return sectionCount - 1;
393 }
394
395 void VCSBaseEditorWidget::slotDiffCursorPositionChanged()
396 {
397     // Adapt diff file browse combo to new position
398     // if the cursor goes across a file line.
399     QTC_ASSERT(d->m_parameters->type == DiffOutput, return)
400     const int newCursorLine = textCursor().blockNumber();
401     if (newCursorLine == d->m_cursorLine)
402         return;
403     // Which section does it belong to?
404     d->m_cursorLine = newCursorLine;
405     const int section = sectionOfLine(d->m_cursorLine, d->m_diffSections);
406     if (section != -1) {
407         VCSBaseDiffEditor *de = static_cast<VCSBaseDiffEditor*>(editor());
408         QComboBox *diffBrowseComboBox = de->diffFileBrowseComboBox();
409         if (diffBrowseComboBox->currentIndex() != section) {
410             const bool blocked = diffBrowseComboBox->blockSignals(true);
411             diffBrowseComboBox->setCurrentIndex(section);
412             diffBrowseComboBox->blockSignals(blocked);
413         }
414     }
415 }
416
417 QAction *VCSBaseEditorWidget::createDescribeAction(const QString &change)
418 {
419     QAction *a = new QAction(tr("Describe change %1").arg(change), 0);
420     connect(a, SIGNAL(triggered()), this, SLOT(describe()));
421     return a;
422 }
423
424 QAction *VCSBaseEditorWidget::createAnnotateAction(const QString &change, bool previous)
425 {
426     // Use 'previous' format if desired and available, else default to standard.
427     const QString &format =  previous && !d->m_annotatePreviousRevisionTextFormat.isEmpty() ?
428                 d->m_annotatePreviousRevisionTextFormat : d->m_annotateRevisionTextFormat;
429     QAction *a = new QAction(format.arg(change), 0);
430     a->setData(change);
431     connect(a, SIGNAL(triggered()), this, SLOT(slotAnnotateRevision()));
432     return a;
433 }
434
435 QAction *VCSBaseEditorWidget::createCopyRevisionAction(const QString &change)
436 {
437     QAction *a = new QAction(d->m_copyRevisionTextFormat.arg(change), 0);
438     a->setData(change);
439     connect(a, SIGNAL(triggered()), this, SLOT(slotCopyRevision()));
440     return a;
441 }
442
443 void VCSBaseEditorWidget::contextMenuEvent(QContextMenuEvent *e)
444 {
445     QMenu *menu = createStandardContextMenu();
446     // 'click on change-interaction'
447     if (d->m_parameters->type == LogOutput || d->m_parameters->type == AnnotateOutput) {
448         d->m_currentChange = changeUnderCursor(cursorForPosition(e->pos()));
449         if (!d->m_currentChange.isEmpty()) {
450             switch (d->m_parameters->type) {
451             case LogOutput: // Describe current / Annotate file of current
452                 menu->addSeparator();
453                 menu->addAction(createCopyRevisionAction(d->m_currentChange));
454                 menu->addAction(createDescribeAction(d->m_currentChange));
455                 if (d->m_fileLogAnnotateEnabled)
456                     menu->addAction(createAnnotateAction(d->m_currentChange, false));
457                 break;
458             case AnnotateOutput: { // Describe current / annotate previous
459                     menu->addSeparator();
460                     menu->addAction(createCopyRevisionAction(d->m_currentChange));
461                     menu->addAction(createDescribeAction(d->m_currentChange));
462                     const QStringList previousVersions = annotationPreviousVersions(d->m_currentChange);
463                     if (!previousVersions.isEmpty()) {
464                         menu->addSeparator();
465                         foreach(const QString &pv, previousVersions)
466                             menu->addAction(createAnnotateAction(pv, true));
467                     } // has previous versions
468                 }
469                 break;
470             default:
471                 break;
472             }         // switch type
473         }             // has current change
474     }
475     menu->exec(e->globalPos());
476     delete menu;
477 }
478
479 void VCSBaseEditorWidget::mouseMoveEvent(QMouseEvent *e)
480 {
481     bool overrideCursor = false;
482     Qt::CursorShape cursorShape;
483
484     if (d->m_parameters->type == LogOutput || d->m_parameters->type == AnnotateOutput) {
485         // Link emulation behaviour for 'click on change-interaction'
486         QTextCursor cursor = cursorForPosition(e->pos());
487         QString change = changeUnderCursor(cursor);
488         if (!change.isEmpty()) {
489             QTextEdit::ExtraSelection sel;
490             sel.cursor = cursor;
491             sel.cursor.select(QTextCursor::WordUnderCursor);
492             sel.format.setFontUnderline(true);
493             sel.format.setProperty(QTextFormat::UserProperty, change);
494             setExtraSelections(OtherSelection, QList<QTextEdit::ExtraSelection>() << sel);
495             overrideCursor = true;
496             cursorShape = Qt::PointingHandCursor;
497         }
498     } else {
499         setExtraSelections(OtherSelection, QList<QTextEdit::ExtraSelection>());
500         overrideCursor = true;
501         cursorShape = Qt::IBeamCursor;
502     }
503     TextEditor::BaseTextEditorWidget::mouseMoveEvent(e);
504
505     if (overrideCursor)
506         viewport()->setCursor(cursorShape);
507 }
508
509 void VCSBaseEditorWidget::mouseReleaseEvent(QMouseEvent *e)
510 {
511     if (d->m_parameters->type == LogOutput || d->m_parameters->type == AnnotateOutput) {
512         if (e->button() == Qt::LeftButton &&!(e->modifiers() & Qt::ShiftModifier)) {
513             QTextCursor cursor = cursorForPosition(e->pos());
514             d->m_currentChange = changeUnderCursor(cursor);
515             if (!d->m_currentChange.isEmpty()) {
516                 describe();
517                 e->accept();
518                 return;
519             }
520         }
521     }
522     TextEditor::BaseTextEditorWidget::mouseReleaseEvent(e);
523 }
524
525 void VCSBaseEditorWidget::mouseDoubleClickEvent(QMouseEvent *e)
526 {
527     if (d->m_parameters->type == DiffOutput) {
528         if (e->button() == Qt::LeftButton &&!(e->modifiers() & Qt::ShiftModifier)) {
529             QTextCursor cursor = cursorForPosition(e->pos());
530             jumpToChangeFromDiff(cursor);
531         }
532     }
533     TextEditor::BaseTextEditorWidget::mouseDoubleClickEvent(e);
534 }
535
536 void VCSBaseEditorWidget::keyPressEvent(QKeyEvent *e)
537 {
538     // Do not intercept return in editable patches.
539     if (d->m_parameters->type == DiffOutput && isReadOnly()
540         && (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return)) {
541         jumpToChangeFromDiff(textCursor());
542         return;
543     }
544     BaseTextEditorWidget::keyPressEvent(e);
545 }
546
547 void VCSBaseEditorWidget::describe()
548 {
549     if (VCSBase::Constants::Internal::debug)
550         qDebug() << "VCSBaseEditor::describe" << d->m_currentChange;
551     if (!d->m_currentChange.isEmpty())
552         emit describeRequested(d->m_source, d->m_currentChange);
553 }
554
555 void VCSBaseEditorWidget::slotActivateAnnotation()
556 {
557     // The annotation highlighting depends on contents (change number
558     // set with assigned colors)
559     if (d->m_parameters->type != AnnotateOutput)
560         return;
561
562     const QSet<QString> changes = annotationChanges();
563     if (changes.isEmpty())
564         return;
565     if (VCSBase::Constants::Internal::debug)
566         qDebug() << "VCSBaseEditor::slotActivateAnnotation(): #" << changes.size();
567
568     disconnect(this, SIGNAL(textChanged()), this, SLOT(slotActivateAnnotation()));
569
570     if (BaseAnnotationHighlighter *ah = qobject_cast<BaseAnnotationHighlighter *>(baseTextDocument()->syntaxHighlighter())) {
571         ah->setChangeNumbers(changes);
572         ah->rehighlight();
573     } else {
574         baseTextDocument()->setSyntaxHighlighter(createAnnotationHighlighter(changes));
575     }
576 }
577
578 // Check for a change chunk "@@ -91,7 +95,7 @@" and return
579 // the modified line number (95).
580 // Note that git appends stuff after "  @@" (function names, etc.).
581 static inline bool checkChunkLine(const QString &line, int *modifiedLineNumber)
582 {
583     if (!line.startsWith(QLatin1String("@@ ")))
584         return false;
585     const int endPos = line.indexOf(QLatin1String(" @@"), 3);
586     if (endPos == -1)
587         return false;
588     // the first chunk range applies to the original file, the second one to
589     // the modified file, the one we're interested int
590     const int plusPos = line.indexOf(QLatin1Char('+'), 3);
591     if (plusPos == -1 || plusPos > endPos)
592         return false;
593     const int lineNumberPos = plusPos + 1;
594     const int commaPos = line.indexOf(QLatin1Char(','), lineNumberPos);
595     if (commaPos == -1 || commaPos > endPos)
596         return false;
597     const QString lineNumberStr = line.mid(lineNumberPos, commaPos - lineNumberPos);
598     bool ok;
599     *modifiedLineNumber = lineNumberStr.toInt(&ok);
600     return ok;
601 }
602
603 void VCSBaseEditorWidget::jumpToChangeFromDiff(QTextCursor cursor)
604 {
605     int chunkStart = 0;
606     int lineCount = -1;
607     const QChar deletionIndicator = QLatin1Char('-');
608     // find nearest change hunk
609     QTextBlock block = cursor.block();
610     for ( ; block.isValid() ; block = block.previous()) {
611         const QString line = block.text();
612         if (checkChunkLine(line, &chunkStart)) {
613             break;
614         } else {
615             if (!line.startsWith(deletionIndicator))
616                 ++lineCount;
617         }
618     }
619
620     if (VCSBase::Constants::Internal::debug)
621         qDebug() << "VCSBaseEditor::jumpToChangeFromDiff()1" << chunkStart << lineCount;
622
623     if (chunkStart == -1 || lineCount < 0 || !block.isValid())
624         return;
625
626     // find the filename in previous line, map depot name back
627     block = block.previous();
628     if (!block.isValid())
629         return;
630     const QString fileName = fileNameFromDiffSpecification(block);
631
632     const bool exists = fileName.isEmpty() ? false : QFile::exists(fileName);
633
634     if (VCSBase::Constants::Internal::debug)
635         qDebug() << "VCSBaseEditor::jumpToChangeFromDiff()2" << fileName << "ex=" << exists << "line" << chunkStart <<  lineCount;
636
637     if (!exists)
638         return;
639
640     Core::EditorManager *em = Core::EditorManager::instance();
641     Core::IEditor *ed = em->openEditor(fileName, QString(), Core::EditorManager::ModeSwitch);
642     if (TextEditor::ITextEditor *editor = qobject_cast<TextEditor::ITextEditor *>(ed))
643         editor->gotoLine(chunkStart + lineCount);
644 }
645
646 void VCSBaseEditorWidget::setPlainTextData(const QByteArray &data)
647 {
648     if (data.size() > Core::EditorManager::maxTextFileSize()) {
649         setPlainText(msgTextTooLarge(data.size()));
650     } else {
651         setPlainText(codec()->toUnicode(data));
652     }
653 }
654
655 void VCSBaseEditorWidget::setFontSettings(const TextEditor::FontSettings &fs)
656 {
657     TextEditor::BaseTextEditorWidget::setFontSettings(fs);
658     if (d->m_parameters->type == DiffOutput) {
659         if (DiffHighlighter *highlighter = qobject_cast<DiffHighlighter*>(baseTextDocument()->syntaxHighlighter())) {
660             static QVector<QString> categories;
661             if (categories.isEmpty()) {
662                 categories << QLatin1String(TextEditor::Constants::C_TEXT)
663                            << QLatin1String(TextEditor::Constants::C_ADDED_LINE)
664                            << QLatin1String(TextEditor::Constants::C_REMOVED_LINE)
665                            << QLatin1String(TextEditor::Constants::C_DIFF_FILE)
666                            << QLatin1String(TextEditor::Constants::C_DIFF_LOCATION);
667             }
668             highlighter->setFormats(fs.toTextCharFormats(categories));
669             highlighter->rehighlight();
670         }
671     }
672 }
673
674 const VCSBaseEditorParameters *VCSBaseEditorWidget::findType(const VCSBaseEditorParameters *array,
675                                                        int arraySize,
676                                                        EditorContentType et)
677 {
678     for (int i = 0; i < arraySize; i++)
679         if (array[i].type == et)
680             return array + i;
681     return 0;
682 }
683
684 // Find the codec used for a file querying the editor.
685 static QTextCodec *findFileCodec(const QString &source)
686 {
687     typedef QList<Core::IEditor *> EditorList;
688
689     const EditorList editors = Core::EditorManager::instance()->editorsForFileName(source);
690     if (!editors.empty()) {
691         const EditorList::const_iterator ecend =  editors.constEnd();
692         for (EditorList::const_iterator it = editors.constBegin(); it != ecend; ++it)
693             if (const TextEditor::BaseTextEditor *be = qobject_cast<const TextEditor::BaseTextEditor *>(*it)) {
694                 QTextCodec *codec = be->editorWidget()->textCodec();
695                 if (VCSBase::Constants::Internal::debug)
696                     qDebug() << Q_FUNC_INFO << source << codec->name();
697                 return codec;
698             }
699     }
700     if (VCSBase::Constants::Internal::debug)
701         qDebug() << Q_FUNC_INFO << source << "not found";
702     return 0;
703 }
704
705 // Find the codec by checking the projects (root dir of project file)
706 static QTextCodec *findProjectCodec(const QString &dir)
707 {
708     typedef  QList<ProjectExplorer::Project*> ProjectList;
709     // Try to find a project under which file tree the file is.
710     const ProjectExplorer::SessionManager *sm = ProjectExplorer::ProjectExplorerPlugin::instance()->session();
711     const ProjectList projects = sm->projects();
712     if (!projects.empty()) {
713         const ProjectList::const_iterator pcend = projects.constEnd();
714         for (ProjectList::const_iterator it = projects.constBegin(); it != pcend; ++it)
715             if (const Core::IFile *file = (*it)->file())
716                 if (file->fileName().startsWith(dir)) {
717                     QTextCodec *codec = (*it)->editorConfiguration()->textCodec();
718                     if (VCSBase::Constants::Internal::debug)
719                         qDebug() << Q_FUNC_INFO << dir << (*it)->displayName() << codec->name();
720                     return codec;
721                 }
722     }
723     if (VCSBase::Constants::Internal::debug)
724         qDebug() << Q_FUNC_INFO << dir << "not found";
725     return 0;
726 }
727
728 QTextCodec *VCSBaseEditorWidget::getCodec(const QString &source)
729 {
730     if (!source.isEmpty()) {
731         // Check file
732         const QFileInfo sourceFi(source);
733         if (sourceFi.isFile())
734             if (QTextCodec *fc = findFileCodec(source))
735                 return fc;
736         // Find by project via directory
737         if (QTextCodec *pc = findProjectCodec(sourceFi.isFile() ? sourceFi.absolutePath() : source))
738             return pc;
739     }
740     QTextCodec *sys = QTextCodec::codecForLocale();
741     if (VCSBase::Constants::Internal::debug)
742         qDebug() << Q_FUNC_INFO << source << "defaulting to " << sys->name();
743     return sys;
744 }
745
746 QTextCodec *VCSBaseEditorWidget::getCodec(const QString &workingDirectory, const QStringList &files)
747 {
748     if (files.empty())
749         return getCodec(workingDirectory);
750     return getCodec(workingDirectory + QLatin1Char('/') + files.front());
751 }
752
753 VCSBaseEditorWidget *VCSBaseEditorWidget::getVcsBaseEditor(const Core::IEditor *editor)
754 {
755     if (const TextEditor::BaseTextEditor *be = qobject_cast<const TextEditor::BaseTextEditor *>(editor))
756         return qobject_cast<VCSBaseEditorWidget *>(be->editorWidget());
757     return 0;
758 }
759
760 // Return line number of current editor if it matches.
761 int VCSBaseEditorWidget::lineNumberOfCurrentEditor(const QString &currentFile)
762 {
763     Core::IEditor *ed = Core::EditorManager::instance()->currentEditor();
764     if (!ed)
765         return -1;
766     if (!currentFile.isEmpty()) {
767         const Core::IFile *ifile  = ed->file();
768         if (!ifile || ifile->fileName() != currentFile)
769             return -1;
770     }
771     const TextEditor::BaseTextEditor *eda = qobject_cast<const TextEditor::BaseTextEditor *>(ed);
772     if (!eda)
773         return -1;
774     return eda->currentLine();
775 }
776
777 bool VCSBaseEditorWidget::gotoLineOfEditor(Core::IEditor *e, int lineNumber)
778 {
779     if (lineNumber >= 0 && e) {
780         if (TextEditor::BaseTextEditor *be = qobject_cast<TextEditor::BaseTextEditor*>(e)) {
781             be->gotoLine(lineNumber);
782             return true;
783         }
784     }
785     return false;
786 }
787
788 // Return source file or directory string depending on parameters
789 // ('git diff XX' -> 'XX' , 'git diff XX file' -> 'XX/file').
790 QString VCSBaseEditorWidget::getSource(const QString &workingDirectory,
791                                  const QString &fileName)
792 {
793     if (fileName.isEmpty())
794         return workingDirectory;
795
796     QString rc = workingDirectory;
797     const QChar slash = QLatin1Char('/');
798     if (!rc.isEmpty() && !(rc.endsWith(slash) || rc.endsWith(QLatin1Char('\\'))))
799         rc += slash;
800     rc += fileName;
801     return rc;
802 }
803
804 QString VCSBaseEditorWidget::getSource(const QString &workingDirectory,
805                                  const QStringList &fileNames)
806 {
807     return fileNames.size() == 1 ?
808             getSource(workingDirectory, fileNames.front()) :
809             workingDirectory;
810 }
811
812 QString VCSBaseEditorWidget::getTitleId(const QString &workingDirectory,
813                                   const QStringList &fileNames,
814                                   const QString &revision)
815 {
816     QString rc;
817     switch (fileNames.size()) {
818     case 0:
819         rc = workingDirectory;
820         break;
821     case 1:
822         rc = fileNames.front();
823         break;
824     default:
825         rc = fileNames.join(QLatin1String(", "));
826         break;
827     }
828     if (!revision.isEmpty()) {
829         rc += QLatin1Char(':');
830         rc += revision;
831     }
832     return rc;
833 }
834
835 bool VCSBaseEditorWidget::setConfigurationWidget(QWidget *w)
836 {
837     if (!d->m_editor || d->m_configurationWidget)
838         return false;
839
840     d->m_configurationWidget = w;
841     d->m_editor->insertExtraToolBarWidget(TextEditor::BaseTextEditor::Right, w);
842
843     return true;
844 }
845
846 QWidget *VCSBaseEditorWidget::configurationWidget() const
847 {
848     return d->m_configurationWidget;
849 }
850
851 // Find the complete file from a diff relative specification.
852 QString VCSBaseEditorWidget::findDiffFile(const QString &f, Core::IVersionControl *control /* = 0 */) const
853 {
854     // Try the file itself, expand to absolute.
855     const QFileInfo in(f);
856     if (in.isAbsolute())
857         return in.isFile() ? f : QString();
858     if (in.isFile())
859         return in.absoluteFilePath();
860     // 1) Try base dir
861     const QChar slash = QLatin1Char('/');
862     if (!d->m_diffBaseDirectory.isEmpty()) {
863         const QFileInfo baseFileInfo(d->m_diffBaseDirectory + slash + f);
864         if (baseFileInfo.isFile())
865             return baseFileInfo.absoluteFilePath();
866     }
867     // 2) Try in source (which can be file or directory)
868     if (source().isEmpty())
869         return QString();
870     const QFileInfo sourceInfo(source());
871     const QString sourceDir = sourceInfo.isDir() ? sourceInfo.absoluteFilePath() : sourceInfo.absolutePath();
872     const QFileInfo sourceFileInfo(sourceDir + slash + f);
873     if (sourceFileInfo.isFile())
874         return sourceFileInfo.absoluteFilePath();
875     // Try to locate via repository.
876     if (!control)
877         return QString();
878     QString topLevel;
879     if (!control->managesDirectory(sourceDir, &topLevel))
880         return QString();
881     const QFileInfo topLevelFileInfo(topLevel + slash + f);
882     if (topLevelFileInfo.isFile())
883         return topLevelFileInfo.absoluteFilePath();
884     return QString();
885 }
886
887 void VCSBaseEditorWidget::slotAnnotateRevision()
888 {
889     if (const QAction *a = qobject_cast<const QAction *>(sender()))
890         emit annotateRevisionRequested(source(), a->data().toString(),
891                                        editor()->currentLine());
892 }
893
894 void VCSBaseEditorWidget::slotCopyRevision()
895 {
896     QApplication::clipboard()->setText(d->m_currentChange);
897 }
898
899 QStringList VCSBaseEditorWidget::annotationPreviousVersions(const QString &) const
900 {
901     return QStringList();
902 }
903
904 } // namespace VCSBase
905
906 #include "vcsbaseeditor.moc"