1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
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
16 ** GNU Lesser General Public License Usage
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.
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.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "vcsbasesubmiteditor.h"
36 #include "commonvcssettings.h"
37 #include "vcsbaseoutputwindow.h"
38 #include "vcsplugin.h"
39 #include "nicknamedialog.h"
40 #include "submiteditorfile.h"
42 #include <aggregation/aggregate.h>
43 #include <coreplugin/ifile.h>
44 #include <coreplugin/icore.h>
45 #include <coreplugin/editormanager/editormanager.h>
46 #include <coreplugin/uniqueidmanager.h>
47 #include <coreplugin/actionmanager/actionmanager.h>
48 #include <utils/submiteditorwidget.h>
49 #include <utils/checkablemessagebox.h>
50 #include <utils/synchronousprocess.h>
51 #include <utils/submitfieldwidget.h>
52 #include <find/basetextfind.h>
53 #include <texteditor/fontsettings.h>
54 #include <texteditor/texteditorsettings.h>
56 #include <projectexplorer/projectexplorer.h>
57 #include <projectexplorer/session.h>
58 #include <projectexplorer/project.h>
60 #include <QtCore/QDebug>
61 #include <QtCore/QDir>
62 #include <QtCore/QTemporaryFile>
63 #include <QtCore/QProcess>
64 #include <QtCore/QFile>
65 #include <QtCore/QFileInfo>
66 #include <QtCore/QPointer>
67 #include <QtCore/QTextStream>
68 #include <QtGui/QStyle>
69 #include <QtGui/QToolBar>
70 #include <QtGui/QAction>
71 #include <QtGui/QApplication>
72 #include <QtGui/QMessageBox>
73 #include <QtGui/QMainWindow>
74 #include <QtGui/QCompleter>
75 #include <QtGui/QLineEdit>
76 #include <QtGui/QTextEdit>
79 enum { wantToolBar = 0 };
83 static inline QString submitMessageCheckScript()
85 return Internal::VCSPlugin::instance()->settings().submitMessageCheckScript;
88 struct VCSBaseSubmitEditorPrivate
90 VCSBaseSubmitEditorPrivate(const VCSBaseSubmitEditorParameters *parameters,
91 Utils::SubmitEditorWidget *editorWidget,
94 Utils::SubmitEditorWidget *m_widget;
95 QToolBar *m_toolWidget;
96 const VCSBaseSubmitEditorParameters *m_parameters;
97 QString m_displayName;
98 QString m_checkScriptWorkingDirectory;
99 VCSBase::Internal::SubmitEditorFile *m_file;
101 QPointer<QAction> m_diffAction;
102 QPointer<QAction> m_submitAction;
104 Internal::NickNameDialog *m_nickNameDialog;
105 Core::Context m_contexts;
108 VCSBaseSubmitEditorPrivate::VCSBaseSubmitEditorPrivate(const VCSBaseSubmitEditorParameters *parameters,
109 Utils::SubmitEditorWidget *editorWidget,
111 m_widget(editorWidget),
113 m_parameters(parameters),
114 m_file(new VCSBase::Internal::SubmitEditorFile(QLatin1String(parameters->mimeType), q)),
116 m_contexts(parameters->context)
120 VCSBaseSubmitEditor::VCSBaseSubmitEditor(const VCSBaseSubmitEditorParameters *parameters,
121 Utils::SubmitEditorWidget *editorWidget) :
122 m_d(new VCSBaseSubmitEditorPrivate(parameters, editorWidget, this))
124 // Message font according to settings
125 const TextEditor::FontSettings fs = TextEditor::TextEditorSettings::instance()->fontSettings();
126 QFont font = editorWidget->descriptionEdit()->font();
127 font.setFamily(fs.family());
128 font.setPointSize(fs.fontSize());
129 editorWidget->descriptionEdit()->setFont(font);
131 m_d->m_file->setModified(false);
132 // We are always clean to prevent the editor manager from asking to save.
133 connect(m_d->m_file, SIGNAL(saveMe(QString)), this, SLOT(save(QString)));
135 connect(m_d->m_widget, SIGNAL(diffSelected(QStringList)), this, SLOT(slotDiffSelectedVCSFiles(QStringList)));
136 connect(m_d->m_widget->descriptionEdit(), SIGNAL(textChanged()), this, SLOT(slotDescriptionChanged()));
138 const Internal::CommonVcsSettings settings = Internal::VCSPlugin::instance()->settings();
139 // Add additional context menu settings
140 if (!settings.submitMessageCheckScript.isEmpty() || !settings.nickNameMailMap.isEmpty()) {
141 QAction *sep = new QAction(this);
142 sep->setSeparator(true);
143 m_d->m_widget->addDescriptionEditContextMenuAction(sep);
145 if (!settings.submitMessageCheckScript.isEmpty()) {
146 QAction *checkAction = new QAction(tr("Check Message"), this);
147 connect(checkAction, SIGNAL(triggered()), this, SLOT(slotCheckSubmitMessage()));
148 m_d->m_widget->addDescriptionEditContextMenuAction(checkAction);
151 if (!settings.nickNameMailMap.isEmpty()) {
152 QAction *insertAction = new QAction(tr("Insert Name..."), this);
153 connect(insertAction, SIGNAL(triggered()), this, SLOT(slotInsertNickName()));
154 m_d->m_widget->addDescriptionEditContextMenuAction(insertAction);
157 // Do we have user fields?
158 if (!settings.nickNameFieldListFile.isEmpty())
159 createUserFields(settings.nickNameFieldListFile);
162 slotUpdateEditorSettings(settings);
163 connect(Internal::VCSPlugin::instance(),
164 SIGNAL(settingsChanged(VCSBase::Internal::CommonVcsSettings)),
165 this, SLOT(slotUpdateEditorSettings(VCSBase::Internal::CommonVcsSettings)));
167 Aggregation::Aggregate *aggregate = new Aggregation::Aggregate;
168 aggregate->add(new Find::BaseTextFind(m_d->m_widget->descriptionEdit()));
169 aggregate->add(this);
172 VCSBaseSubmitEditor::~VCSBaseSubmitEditor()
174 delete m_d->m_toolWidget;
175 delete m_d->m_widget;
179 void VCSBaseSubmitEditor::slotUpdateEditorSettings(const Internal::CommonVcsSettings &s)
181 setLineWrapWidth(s.lineWrapWidth);
182 setLineWrap(s.lineWrap);
185 // Return a trimmed list of non-empty field texts
186 static inline QStringList fieldTexts(const QString &fileContents)
189 const QStringList rawFields = fileContents.trimmed().split(QLatin1Char('\n'));
190 foreach(const QString &field, rawFields) {
191 const QString trimmedField = field.trimmed();
192 if (!trimmedField.isEmpty())
193 rc.push_back(trimmedField);
198 void VCSBaseSubmitEditor::createUserFields(const QString &fieldConfigFile)
200 QFile fieldFile(fieldConfigFile);
201 if (!fieldFile.open(QIODevice::ReadOnly|QIODevice::Text)) {
202 qWarning("%s: Unable to open %s: %s", Q_FUNC_INFO, qPrintable(fieldConfigFile), qPrintable(fieldFile.errorString()));
206 const QStringList fields = fieldTexts(QString::fromUtf8(fieldFile.readAll()));
209 // Create a completer on user names
210 const QStandardItemModel *nickNameModel = Internal::VCSPlugin::instance()->nickNameModel();
211 QCompleter *completer = new QCompleter(Internal::NickNameDialog::nickNameList(nickNameModel), this);
213 Utils::SubmitFieldWidget *fieldWidget = new Utils::SubmitFieldWidget;
214 connect(fieldWidget, SIGNAL(browseButtonClicked(int,QString)),
215 this, SLOT(slotSetFieldNickName(int)));
216 fieldWidget->setCompleter(completer);
217 fieldWidget->setAllowDuplicateFields(true);
218 fieldWidget->setHasBrowseButton(true);
219 fieldWidget->setFields(fields);
220 m_d->m_widget->addSubmitFieldWidget(fieldWidget);
223 void VCSBaseSubmitEditor::registerActions(QAction *editorUndoAction, QAction *editorRedoAction,
224 QAction *submitAction, QAction *diffAction)\
226 m_d->m_widget->registerActions(editorUndoAction, editorRedoAction, submitAction, diffAction);
227 m_d->m_diffAction = diffAction;
228 m_d->m_submitAction = submitAction;
231 void VCSBaseSubmitEditor::unregisterActions(QAction *editorUndoAction, QAction *editorRedoAction,
232 QAction *submitAction, QAction *diffAction)
234 m_d->m_widget->unregisterActions(editorUndoAction, editorRedoAction, submitAction, diffAction);
235 m_d->m_diffAction = m_d->m_submitAction = 0;
238 int VCSBaseSubmitEditor::fileNameColumn() const
240 return m_d->m_widget->fileNameColumn();
243 void VCSBaseSubmitEditor::setFileNameColumn(int c)
245 m_d->m_widget->setFileNameColumn(c);
248 QAbstractItemView::SelectionMode VCSBaseSubmitEditor::fileListSelectionMode() const
250 return m_d->m_widget->fileListSelectionMode();
253 void VCSBaseSubmitEditor::setFileListSelectionMode(QAbstractItemView::SelectionMode sm)
255 m_d->m_widget->setFileListSelectionMode(sm);
258 bool VCSBaseSubmitEditor::isEmptyFileListEnabled() const
260 return m_d->m_widget->isEmptyFileListEnabled();
263 void VCSBaseSubmitEditor::setEmptyFileListEnabled(bool e)
265 m_d->m_widget->setEmptyFileListEnabled(e);
268 bool VCSBaseSubmitEditor::lineWrap() const
270 return m_d->m_widget->lineWrap();
273 void VCSBaseSubmitEditor::setLineWrap(bool w)
275 m_d->m_widget->setLineWrap(w);
278 int VCSBaseSubmitEditor::lineWrapWidth() const
280 return m_d->m_widget->lineWrapWidth();
283 void VCSBaseSubmitEditor::setLineWrapWidth(int w)
285 m_d->m_widget->setLineWrapWidth(w);
288 void VCSBaseSubmitEditor::slotDescriptionChanged()
292 bool VCSBaseSubmitEditor::createNew(const QString &contents)
294 setFileContents(contents);
298 bool VCSBaseSubmitEditor::open(const QString &fileName)
300 if (fileName.isEmpty())
303 const QFileInfo fi(fileName);
304 if (!fi.isFile() || !fi.isReadable())
307 QFile file(fileName);
308 if (!file.open(QIODevice::ReadOnly|QIODevice::Text)) {
309 qWarning("Unable to open %s: %s", qPrintable(fileName), qPrintable(file.errorString()));
313 const QString text = QString::fromLocal8Bit(file.readAll());
314 if (!createNew(text))
317 m_d->m_file->setFileName(fi.absoluteFilePath());
321 Core::IFile *VCSBaseSubmitEditor::file()
326 QString VCSBaseSubmitEditor::displayName() const
328 if (m_d->m_displayName.isEmpty())
329 m_d->m_displayName = QCoreApplication::translate("VCS", m_d->m_parameters->displayName);
330 return m_d->m_displayName;
333 void VCSBaseSubmitEditor::setDisplayName(const QString &title)
335 m_d->m_displayName = title;
339 QString VCSBaseSubmitEditor::checkScriptWorkingDirectory() const
341 return m_d->m_checkScriptWorkingDirectory;
344 void VCSBaseSubmitEditor::setCheckScriptWorkingDirectory(const QString &s)
346 m_d->m_checkScriptWorkingDirectory = s;
349 bool VCSBaseSubmitEditor::duplicateSupported() const
354 Core::IEditor *VCSBaseSubmitEditor::duplicate(QWidget * /*parent*/)
359 QString VCSBaseSubmitEditor::id() const
361 return m_d->m_parameters->id;
364 static QToolBar *createToolBar(const QWidget *someWidget, QAction *submitAction, QAction *diffAction)
367 QToolBar *toolBar = new QToolBar;
368 toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
369 const int size = someWidget->style()->pixelMetric(QStyle::PM_SmallIconSize);
370 toolBar->setIconSize(QSize(size, size));
371 toolBar->addSeparator();
374 toolBar->addAction(submitAction);
376 toolBar->addAction(diffAction);
380 QWidget *VCSBaseSubmitEditor::toolBar()
385 if (m_d->m_toolWidget)
386 return m_d->m_toolWidget;
388 if (!m_d->m_diffAction && !m_d->m_submitAction)
392 m_d->m_toolWidget = createToolBar(m_d->m_widget, m_d->m_submitAction, m_d->m_diffAction);
393 return m_d->m_toolWidget;
396 Core::Context VCSBaseSubmitEditor::context() const
398 return m_d->m_contexts;
401 QWidget *VCSBaseSubmitEditor::widget()
403 return m_d->m_widget;
406 QByteArray VCSBaseSubmitEditor::saveState() const
411 bool VCSBaseSubmitEditor::restoreState(const QByteArray &/*state*/)
416 QStringList VCSBaseSubmitEditor::checkedFiles() const
418 return m_d->m_widget->checkedFiles();
421 void VCSBaseSubmitEditor::setFileModel(QAbstractItemModel *m)
423 m_d->m_widget->setFileModel(m);
426 QAbstractItemModel *VCSBaseSubmitEditor::fileModel() const
428 return m_d->m_widget->fileModel();
431 void VCSBaseSubmitEditor::slotDiffSelectedVCSFiles(const QStringList &rawList)
433 emit diffSelectedFiles(rawList);
436 bool VCSBaseSubmitEditor::save(const QString &fileName)
438 const QString fName = fileName.isEmpty() ? m_d->m_file->fileName() : fileName;
440 if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
441 qWarning("Unable to open %s: %s", qPrintable(fName), qPrintable(file.errorString()));
444 file.write(fileContents().toLocal8Bit());
448 const QFileInfo fi(fName);
449 m_d->m_file->setFileName(fi.absoluteFilePath());
450 m_d->m_file->setModified(false);
454 QString VCSBaseSubmitEditor::fileContents() const
456 return m_d->m_widget->descriptionText();
459 bool VCSBaseSubmitEditor::setFileContents(const QString &contents)
461 m_d->m_widget->setDescriptionText(contents);
465 enum { checkDialogMinimumWidth = 500 };
467 VCSBaseSubmitEditor::PromptSubmitResult
468 VCSBaseSubmitEditor::promptSubmit(const QString &title,
469 const QString &question,
470 const QString &checkFailureQuestion,
473 bool canCommitOnFailure) const
475 Utils::SubmitEditorWidget *submitWidget =
476 static_cast<Utils::SubmitEditorWidget *>(const_cast<VCSBaseSubmitEditor *>(this)->widget());
480 QString errorMessage;
481 QMessageBox::StandardButton answer = QMessageBox::Yes;
483 const bool prompt = forcePrompt || *promptSetting;
485 QWidget *parent = Core::ICore::instance()->mainWindow();
486 // Pop up a message depending on whether the check succeeded and the
487 // user wants to be prompted
488 bool canCommit = checkSubmitMessage(&errorMessage) && submitWidget->canSubmit();
490 // Check ok, do prompt?
492 // Provide check box to turn off prompt ONLY if it was not forced
493 if (*promptSetting && !forcePrompt) {
494 const QDialogButtonBox::StandardButton danswer =
495 Utils::CheckableMessageBox::question(parent, title, question,
496 tr("Prompt to submit"), promptSetting,
497 QDialogButtonBox::Yes|QDialogButtonBox::No|
498 QDialogButtonBox::Cancel,
499 QDialogButtonBox::Yes);
500 answer = Utils::CheckableMessageBox::dialogButtonBoxToMessageBoxButton(danswer);
502 answer = QMessageBox::question(parent, title, question,
503 QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel,
509 QMessageBox msgBox(QMessageBox::Question, title, checkFailureQuestion,
510 QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel, parent);
511 msgBox.setDefaultButton(QMessageBox::Cancel);
512 msgBox.setInformativeText(errorMessage);
513 msgBox.setMinimumWidth(checkDialogMinimumWidth);
514 answer = static_cast<QMessageBox::StandardButton>(msgBox.exec());
516 if (!canCommit && !canCommitOnFailure) {
518 case QMessageBox::No:
519 return SubmitDiscarded;
520 case QMessageBox::Yes:
521 return SubmitCanceled;
527 case QMessageBox::No:
528 return SubmitDiscarded;
529 case QMessageBox::Yes:
530 return SubmitConfirmed;
536 return SubmitCanceled;
539 QString VCSBaseSubmitEditor::promptForNickName()
541 if (!m_d->m_nickNameDialog)
542 m_d->m_nickNameDialog = new Internal::NickNameDialog(Internal::VCSPlugin::instance()->nickNameModel(), m_d->m_widget);
543 if (m_d->m_nickNameDialog->exec() == QDialog::Accepted)
544 return m_d->m_nickNameDialog->nickName();
548 void VCSBaseSubmitEditor::slotInsertNickName()
550 const QString nick = promptForNickName();
552 m_d->m_widget->descriptionEdit()->textCursor().insertText(nick);
555 void VCSBaseSubmitEditor::slotSetFieldNickName(int i)
557 if (Utils::SubmitFieldWidget *sfw =m_d->m_widget->submitFieldWidgets().front()) {
558 const QString nick = promptForNickName();
560 sfw->setFieldValue(i, nick);
564 void VCSBaseSubmitEditor::slotCheckSubmitMessage()
566 QString errorMessage;
567 if (!checkSubmitMessage(&errorMessage)) {
568 QMessageBox msgBox(QMessageBox::Warning, tr("Submit Message Check Failed"),
569 errorMessage, QMessageBox::Ok, m_d->m_widget);
570 msgBox.setMinimumWidth(checkDialogMinimumWidth);
575 bool VCSBaseSubmitEditor::checkSubmitMessage(QString *errorMessage) const
577 const QString checkScript = submitMessageCheckScript();
578 if (checkScript.isEmpty())
580 QApplication::setOverrideCursor(Qt::WaitCursor);
581 const bool rc = runSubmitMessageCheckScript(checkScript, errorMessage);
582 QApplication::restoreOverrideCursor();
586 static inline QString msgCheckScript(const QString &workingDir, const QString &cmd)
588 const QString nativeCmd = QDir::toNativeSeparators(cmd);
589 return workingDir.isEmpty() ?
590 VCSBaseSubmitEditor::tr("Executing %1").arg(nativeCmd) :
591 VCSBaseSubmitEditor::tr("Executing [%1] %2").
592 arg(QDir::toNativeSeparators(workingDir), nativeCmd);
595 bool VCSBaseSubmitEditor::runSubmitMessageCheckScript(const QString &checkScript, QString *errorMessage) const
598 QString tempFilePattern = QDir::tempPath();
599 if (!tempFilePattern.endsWith(QDir::separator()))
600 tempFilePattern += QDir::separator();
601 tempFilePattern += QLatin1String("msgXXXXXX.txt");
602 QTemporaryFile messageFile(tempFilePattern);
603 messageFile.setAutoRemove(true);
604 if (!messageFile.open()) {
605 *errorMessage = tr("Unable to open '%1': %2").
606 arg(QDir::toNativeSeparators(messageFile.fileName()),
607 messageFile.errorString());
610 const QString messageFileName = messageFile.fileName();
611 messageFile.write(fileContents().toUtf8());
614 VCSBaseOutputWindow *outputWindow = VCSBaseOutputWindow::instance();
615 outputWindow->appendCommand(msgCheckScript(m_d->m_checkScriptWorkingDirectory, checkScript));
616 QProcess checkProcess;
617 if (!m_d->m_checkScriptWorkingDirectory.isEmpty())
618 checkProcess.setWorkingDirectory(m_d->m_checkScriptWorkingDirectory);
619 checkProcess.start(checkScript, QStringList(messageFileName));
620 checkProcess.closeWriteChannel();
621 if (!checkProcess.waitForStarted()) {
622 *errorMessage = tr("The check script '%1' could not be started: %2").arg(checkScript, checkProcess.errorString());
625 QByteArray stdOutData;
626 QByteArray stdErrData;
627 if (!Utils::SynchronousProcess::readDataFromProcess(checkProcess, 30000, &stdOutData, &stdErrData, false)) {
628 Utils::SynchronousProcess::stopProcess(checkProcess);
629 *errorMessage = tr("The check script '%1' timed out.").
630 arg(QDir::toNativeSeparators(checkScript));
633 if (checkProcess.exitStatus() != QProcess::NormalExit) {
634 *errorMessage = tr("The check script '%1' crashed").
635 arg(QDir::toNativeSeparators(checkScript));
638 if (!stdOutData.isEmpty())
639 outputWindow->appendSilently(QString::fromLocal8Bit(stdOutData));
640 const QString stdErr = QString::fromLocal8Bit(stdErrData);
641 if (!stdErr.isEmpty())
642 outputWindow->appendSilently(stdErr);
643 const int exitCode = checkProcess.exitCode();
645 const QString exMessage = tr("The check script returned exit code %1.").
647 outputWindow->appendError(exMessage);
648 *errorMessage = stdErr;
649 if (errorMessage->isEmpty())
650 *errorMessage = exMessage;
656 QIcon VCSBaseSubmitEditor::diffIcon()
658 return QIcon(QLatin1String(":/vcsbase/images/diff.png"));
661 QIcon VCSBaseSubmitEditor::submitIcon()
663 return QIcon(QLatin1String(":/vcsbase/images/submit.png"));
666 // Compile a list if files in the current projects. TODO: Recurse down qrc files?
667 QStringList VCSBaseSubmitEditor::currentProjectFiles(bool nativeSeparators, QString *name)
672 if (ProjectExplorer::ProjectExplorerPlugin *pe = ProjectExplorer::ProjectExplorerPlugin::instance()) {
673 if (const ProjectExplorer::Project *currentProject = pe->currentProject()) {
674 QStringList files = currentProject->files(ProjectExplorer::Project::ExcludeGeneratedFiles);
676 *name = currentProject->displayName();
677 if (nativeSeparators && !files.empty()) {
678 const QStringList::iterator end = files.end();
679 for (QStringList::iterator it = files.begin(); it != end; ++it)
680 *it = QDir::toNativeSeparators(*it);
685 return QStringList();
688 // Reduce a list of untracked files reported by a VCS down to the files
689 // that are actually part of the current project(s).
690 void VCSBaseSubmitEditor::filterUntrackedFilesOfProject(const QString &repositoryDirectory, QStringList *untrackedFiles)
692 if (untrackedFiles->empty())
694 const QStringList nativeProjectFiles = VCSBase::VCSBaseSubmitEditor::currentProjectFiles(true);
695 if (nativeProjectFiles.empty())
697 const QDir repoDir(repositoryDirectory);
698 for (QStringList::iterator it = untrackedFiles->begin(); it != untrackedFiles->end(); ) {
699 const QString path = QDir::toNativeSeparators(repoDir.absoluteFilePath(*it));
700 if (nativeProjectFiles.contains(path)) {
703 it = untrackedFiles->erase(it);
708 // Helper to raise an already open submit editor to prevent opening twice.
709 bool VCSBaseSubmitEditor::raiseSubmitEditor()
711 Core::EditorManager *em = Core::EditorManager::instance();
713 if (Core::IEditor *ce = em->currentEditor())
714 if (qobject_cast<VCSBaseSubmitEditor*>(ce))
716 // Try to activate a hidden one
717 foreach (Core::IEditor *e, em->openedEditors()) {
718 if (qobject_cast<VCSBaseSubmitEditor*>(e)) {
719 em->activateEditor(e, Core::EditorManager::IgnoreNavigationHistory | Core::EditorManager::ModeSwitch);
726 } // namespace VCSBase