OSDN Git Service

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