From 676739a87af932a472df757250442f2930a5d7e0 Mon Sep 17 00:00:00 2001
From: Hugues Delorme <delorme.hugues@fougsys.fr>
Date: Mon, 5 Dec 2011 15:37:19 +0100
Subject: [PATCH] VCS: auto-completion in the description text edit

In the description field of the commit editor, class
VCSBaseSubmitEditor provides completion of file names and C++
entities (based on QtCreator's internal C++ code model).

Change-Id: Ie5323714dbf6f6e635953dfbb35596201d86fc37
Reviewed-by: Tobias Hunger <tobias.hunger@nokia.com>
Reviewed-by: Hugues Delorme <delorme.hugues@fougsys.fr>
---
 src/libs/utils/completingtextedit.cpp        | 178 +++++++++++++++++++
 src/libs/utils/completingtextedit.h          |  68 +++++++
 src/libs/utils/submiteditorwidget.cpp        |   2 +-
 src/libs/utils/submiteditorwidget.h          |   4 +-
 src/libs/utils/submiteditorwidget.ui         |   9 +-
 src/libs/utils/utils-lib.pri                 |   6 +-
 src/plugins/bazaar/bazaarplugin.cpp          |   3 +-
 src/plugins/bazaar/commiteditor.cpp          |   8 +-
 src/plugins/bazaar/commiteditor.h            |   2 +-
 src/plugins/git/gitsubmiteditor.cpp          |   2 +-
 src/plugins/mercurial/commiteditor.cpp       |   2 +-
 src/plugins/vcsbase/vcsbase_dependencies.pri |   1 +
 src/plugins/vcsbase/vcsbasesubmiteditor.cpp  | 134 +++++++++++++-
 src/plugins/vcsbase/vcsbasesubmiteditor.h    |   2 +-
 14 files changed, 405 insertions(+), 16 deletions(-)
 create mode 100644 src/libs/utils/completingtextedit.cpp
 create mode 100644 src/libs/utils/completingtextedit.h

diff --git a/src/libs/utils/completingtextedit.cpp b/src/libs/utils/completingtextedit.cpp
new file mode 100644
index 00000000000..b870995668f
--- /dev/null
+++ b/src/libs/utils/completingtextedit.cpp
@@ -0,0 +1,178 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "completingtextedit.h"
+
+#include <QtGui/QAbstractItemView>
+#include <QtGui/QCompleter>
+#include <QtGui/QKeyEvent>
+#include <QtGui/QScrollBar>
+
+static bool isEndOfWordChar(const QChar &c)
+{
+    return !c.isLetterOrNumber() && c.category() != QChar::Punctuation_Connector;
+}
+
+/*! \class Utils::CompletingTextEdit
+
+  \brief A QTextEdit with auto-completion support
+
+  Excerpted from Qt examples/tools/customcompleter
+*/
+
+namespace Utils {
+
+class CompletingTextEditPrivate
+{
+public:
+    CompletingTextEditPrivate(CompletingTextEdit *textEdit);
+
+    void insertCompletion(const QString &completion);
+    QString textUnderCursor() const;
+
+    QCompleter *m_completer;
+
+private:
+    CompletingTextEdit *m_backPointer;
+};
+
+CompletingTextEditPrivate::CompletingTextEditPrivate(CompletingTextEdit *textEdit)
+    : m_completer(0),
+      m_backPointer(textEdit)
+{
+}
+
+void CompletingTextEditPrivate::insertCompletion(const QString &completion)
+{
+    if (m_completer->widget() != m_backPointer)
+        return;
+    QTextCursor tc = m_backPointer->textCursor();
+    int extra = completion.length() - m_completer->completionPrefix().length();
+    tc.movePosition(QTextCursor::Left);
+    tc.movePosition(QTextCursor::EndOfWord);
+    tc.insertText(completion.right(extra));
+    m_backPointer->setTextCursor(tc);
+}
+
+QString CompletingTextEditPrivate::textUnderCursor() const
+{
+    QTextCursor tc = m_backPointer->textCursor();
+    tc.select(QTextCursor::WordUnderCursor);
+    return tc.selectedText();
+}
+
+CompletingTextEdit::CompletingTextEdit(QWidget *parent)
+    : QTextEdit(parent),
+      d(new CompletingTextEditPrivate(this))
+{
+}
+
+CompletingTextEdit::~CompletingTextEdit()
+{
+    delete d;
+}
+
+void CompletingTextEdit::setCompleter(QCompleter *c)
+{
+    if (completer())
+        disconnect(completer(), 0, this, 0);
+
+    d->m_completer = c;
+
+    if (!c)
+        return;
+
+    completer()->setWidget(this);
+    completer()->setCompletionMode(QCompleter::PopupCompletion);
+    connect(completer(), SIGNAL(activated(QString)), this, SLOT(insertCompletion(QString)));
+}
+
+QCompleter *CompletingTextEdit::completer() const
+{
+    return d->m_completer;
+}
+
+void CompletingTextEdit::keyPressEvent(QKeyEvent *e)
+{
+    if (completer() && completer()->popup()->isVisible()) {
+        // The following keys are forwarded by the completer to the widget
+        switch (e->key()) {
+        case Qt::Key_Enter:
+        case Qt::Key_Return:
+        case Qt::Key_Escape:
+        case Qt::Key_Tab:
+        case Qt::Key_Backtab:
+            e->ignore();
+            return; // let the completer do default behavior
+        default:
+            break;
+        }
+    }
+
+    const bool isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_E); // CTRL+E
+    if (completer() == 0 || !isShortcut) // do not process the shortcut when we have a completer
+        QTextEdit::keyPressEvent(e);
+
+    const bool ctrlOrShift = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier);
+    if (completer() == 0 || (ctrlOrShift && e->text().isEmpty()))
+        return;
+
+    const bool hasModifier = (e->modifiers() != Qt::NoModifier) && !ctrlOrShift;
+    const QString newCompletionPrefix = d->textUnderCursor();
+    const QChar lastChar = e->text().isEmpty() ? QChar() : e->text().right(1).at(0);
+
+    if (!isShortcut && (hasModifier || e->text().isEmpty() || newCompletionPrefix.length() < 3
+                        || isEndOfWordChar(lastChar))) {
+        completer()->popup()->hide();
+        return;
+    }
+
+    if (newCompletionPrefix != completer()->completionPrefix()) {
+        completer()->setCompletionPrefix(newCompletionPrefix);
+        completer()->popup()->setCurrentIndex(completer()->completionModel()->index(0, 0));
+    }
+    QRect cr = cursorRect();
+    cr.setWidth(completer()->popup()->sizeHintForColumn(0)
+                + completer()->popup()->verticalScrollBar()->sizeHint().width());
+    completer()->complete(cr); // popup it up!
+}
+
+void CompletingTextEdit::focusInEvent(QFocusEvent *e)
+{
+    if (completer() != 0)
+        completer()->setWidget(this);
+    QTextEdit::focusInEvent(e);
+}
+
+} // namespace Utils
+
+#include "moc_completingtextedit.cpp"
diff --git a/src/libs/utils/completingtextedit.h b/src/libs/utils/completingtextedit.h
new file mode 100644
index 00000000000..9c69150f357
--- /dev/null
+++ b/src/libs/utils/completingtextedit.h
@@ -0,0 +1,68 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#ifndef COMPLETINGTEXTEDIT_H
+#define COMPLETINGTEXTEDIT_H
+
+#include "utils_global.h"
+
+#include <QtGui/QTextEdit>
+
+QT_BEGIN_NAMESPACE
+class QCompleter;
+QT_END_NAMESPACE
+
+namespace Utils {
+
+class QTCREATOR_UTILS_EXPORT CompletingTextEdit : public QTextEdit
+{
+    Q_OBJECT
+
+public:
+    CompletingTextEdit(QWidget *parent = 0);
+    ~CompletingTextEdit();
+
+    void setCompleter(QCompleter *c);
+    QCompleter *completer() const;
+
+protected:
+    void keyPressEvent(QKeyEvent *e);
+    void focusInEvent(QFocusEvent *e);
+
+private:
+    class CompletingTextEditPrivate *d;
+    Q_PRIVATE_SLOT(d, void insertCompletion(const QString &))
+};
+
+} // namespace Utils
+
+#endif // COMPLETINGTEXTEDIT_H
diff --git a/src/libs/utils/submiteditorwidget.cpp b/src/libs/utils/submiteditorwidget.cpp
index 5b59a90fbd9..321f502c0cc 100644
--- a/src/libs/utils/submiteditorwidget.cpp
+++ b/src/libs/utils/submiteditorwidget.cpp
@@ -457,7 +457,7 @@ QStringList SubmitEditorWidget::checkedFiles() const
     return rc;
 }
 
-QTextEdit *SubmitEditorWidget::descriptionEdit() const
+CompletingTextEdit *SubmitEditorWidget::descriptionEdit() const
 {
     return d->m_ui.description;
 }
diff --git a/src/libs/utils/submiteditorwidget.h b/src/libs/utils/submiteditorwidget.h
index 4fec4faadd3..3a0b286c82a 100644
--- a/src/libs/utils/submiteditorwidget.h
+++ b/src/libs/utils/submiteditorwidget.h
@@ -34,12 +34,12 @@
 #define SUBMITEDITORWIDGET_H
 
 #include "utils_global.h"
+#include "completingtextedit.h"
 
 #include <QtGui/QWidget>
 #include <QtGui/QAbstractItemView>
 
 QT_BEGIN_NAMESPACE
-class QTextEdit;
 class QListWidgetItem;
 class QAction;
 class QAbstractItemModel;
@@ -102,7 +102,7 @@ public:
     // Selected files for diff
     QStringList selectedFiles() const;
 
-    QTextEdit *descriptionEdit() const;
+    CompletingTextEdit *descriptionEdit() const;
 
     void addDescriptionEditContextMenuAction(QAction *a);
     void insertDescriptionEditContextMenuAction(int pos, QAction *a);
diff --git a/src/libs/utils/submiteditorwidget.ui b/src/libs/utils/submiteditorwidget.ui
index 9f12c073532..2c5459fed0c 100644
--- a/src/libs/utils/submiteditorwidget.ui
+++ b/src/libs/utils/submiteditorwidget.ui
@@ -24,7 +24,7 @@
      </property>
      <layout class="QVBoxLayout" name="verticalLayout">
       <item>
-       <widget class="QTextEdit" name="description">
+       <widget class="Utils::CompletingTextEdit" name="description">
         <property name="acceptRichText">
          <bool>false</bool>
         </property>
@@ -77,6 +77,13 @@
    </item>
   </layout>
  </widget>
+ <customwidgets>
+  <customwidget>
+   <class>Utils::CompletingTextEdit</class>
+   <extends>QTextEdit</extends>
+   <header>utils/completingtextedit.h</header>
+  </customwidget>
+ </customwidgets>
  <resources/>
  <connections/>
 </ui>
diff --git a/src/libs/utils/utils-lib.pri b/src/libs/utils/utils-lib.pri
index 3277e46240a..395882b8393 100644
--- a/src/libs/utils/utils-lib.pri
+++ b/src/libs/utils/utils-lib.pri
@@ -91,7 +91,8 @@ SOURCES += $$PWD/environment.cpp \
     $$PWD/outputformatter.cpp \
     $$PWD/flowlayout.cpp \
     $$PWD/networkaccessmanager.cpp \
-    $$PWD/persistentsettings.cpp
+    $$PWD/persistentsettings.cpp \
+    $$PWD/completingtextedit.cpp
 
 win32 {
     SOURCES += \
@@ -197,7 +198,8 @@ HEADERS += \
     $$PWD/outputformat.h \
     $$PWD/flowlayout.h \
     $$PWD/networkaccessmanager.h \
-    $$PWD/persistentsettings.h
+    $$PWD/persistentsettings.h \
+    $$PWD/completingtextedit.h
 
 FORMS += $$PWD/filewizardpage.ui \
     $$PWD/projectintropage.ui \
diff --git a/src/plugins/bazaar/bazaarplugin.cpp b/src/plugins/bazaar/bazaarplugin.cpp
index bdc33c94af8..f1f0f8f7787 100644
--- a/src/plugins/bazaar/bazaarplugin.cpp
+++ b/src/plugins/bazaar/bazaarplugin.cpp
@@ -596,7 +596,8 @@ void BazaarPlugin::showCommitWidget(const QList<VCSBase::VCSBaseClient::StatusIt
     commitEditor->setDisplayName(msg);
 
     const BranchInfo branch = m_client->synchronousBranchQuery(m_submitRepository);
-    commitEditor->setFields(branch, m_bazaarSettings.stringValue(BazaarSettings::userNameKey),
+    commitEditor->setFields(m_submitRepository, branch,
+                            m_bazaarSettings.stringValue(BazaarSettings::userNameKey),
                             m_bazaarSettings.stringValue(BazaarSettings::userEmailKey), status);
 }
 
diff --git a/src/plugins/bazaar/commiteditor.cpp b/src/plugins/bazaar/commiteditor.cpp
index 20ca57df0a1..dda7dbed86e 100644
--- a/src/plugins/bazaar/commiteditor.cpp
+++ b/src/plugins/bazaar/commiteditor.cpp
@@ -60,9 +60,9 @@ BazaarCommitWidget *CommitEditor::commitWidget()
     return static_cast<BazaarCommitWidget *>(widget());
 }
 
-void CommitEditor::setFields(const BranchInfo &branch,
-                             const QString &userName, const QString &email,
-                             const QList<VCSBase::VCSBaseClient::StatusItem> &repoStatus)
+void CommitEditor::setFields(const QString &repositoryRoot,
+                             const BranchInfo &branch, const QString &userName,
+                             const QString &email, const QList<VCSBase::VCSBaseClient::StatusItem> &repoStatus)
 {
     BazaarCommitWidget *bazaarWidget = commitWidget();
     if (!bazaarWidget)
@@ -74,5 +74,5 @@ void CommitEditor::setFields(const BranchInfo &branch,
     foreach (const VCSBase::VCSBaseClient::StatusItem &item, repoStatus)
         if (item.flags != QLatin1String("Unknown"))
             m_fileModel->addFile(item.file, item.flags, true);
-    setFileModel(m_fileModel);
+    setFileModel(m_fileModel, repositoryRoot);
 }
diff --git a/src/plugins/bazaar/commiteditor.h b/src/plugins/bazaar/commiteditor.h
index 489bc4d663e..2e1048a0dce 100644
--- a/src/plugins/bazaar/commiteditor.h
+++ b/src/plugins/bazaar/commiteditor.h
@@ -54,7 +54,7 @@ public:
     explicit CommitEditor(const VCSBase::VCSBaseSubmitEditorParameters *parameters,
                           QWidget *parent);
 
-    void setFields(const BranchInfo &branch,
+    void setFields(const QString &repositoryRoot, const BranchInfo &branch,
                    const QString &userName, const QString &email,
                    const QList<VCSBase::VCSBaseClient::StatusItem> &repoStatus);
 
diff --git a/src/plugins/git/gitsubmiteditor.cpp b/src/plugins/git/gitsubmiteditor.cpp
index 8c5c64c5c0e..3924c591798 100644
--- a/src/plugins/git/gitsubmiteditor.cpp
+++ b/src/plugins/git/gitsubmiteditor.cpp
@@ -78,7 +78,7 @@ void GitSubmitEditor::setCommitData(const CommitData &d)
                              QVariant(static_cast<int>(state)));
         }
     }
-    setFileModel(m_model);
+    setFileModel(m_model, d.panelInfo.repository);
 }
 
 void GitSubmitEditor::slotDiffSelected(const QStringList &files)
diff --git a/src/plugins/mercurial/commiteditor.cpp b/src/plugins/mercurial/commiteditor.cpp
index fc422a2f9a2..5b1e975aa30 100644
--- a/src/plugins/mercurial/commiteditor.cpp
+++ b/src/plugins/mercurial/commiteditor.cpp
@@ -85,7 +85,7 @@ void CommitEditor::setFields(const QFileInfo &repositoryRoot, const QString &bra
         }
     }
 
-    setFileModel(fileModel);
+    setFileModel(fileModel, repositoryRoot.absoluteFilePath());
 }
 
 QString CommitEditor::committerInfo()
diff --git a/src/plugins/vcsbase/vcsbase_dependencies.pri b/src/plugins/vcsbase/vcsbase_dependencies.pri
index 3a68ac96009..672a4f65cf4 100644
--- a/src/plugins/vcsbase/vcsbase_dependencies.pri
+++ b/src/plugins/vcsbase/vcsbase_dependencies.pri
@@ -2,3 +2,4 @@ include(../../plugins/coreplugin/coreplugin.pri)
 include(../../plugins/texteditor/texteditor.pri)
 include(../../plugins/projectexplorer/projectexplorer.pri)
 include(../../plugins/find/find.pri)
+include(../../libs/cplusplus/cplusplus.pri)
diff --git a/src/plugins/vcsbase/vcsbasesubmiteditor.cpp b/src/plugins/vcsbase/vcsbasesubmiteditor.cpp
index ffa75517fb3..81653407573 100644
--- a/src/plugins/vcsbase/vcsbasesubmiteditor.cpp
+++ b/src/plugins/vcsbase/vcsbasesubmiteditor.cpp
@@ -39,11 +39,20 @@
 #include "submiteditorfile.h"
 
 #include <aggregation/aggregate.h>
+#include <cplusplus/Control.h>
+#include <cplusplus/CoreTypes.h>
+#include <cplusplus/FullySpecifiedType.h>
+#include <cplusplus/Literals.h>
+#include <cplusplus/ModelManagerInterface.h>
+#include <cplusplus/Symbol.h>
+#include <cplusplus/Symbols.h>
+#include <cplusplus/TranslationUnit.h>
 #include <coreplugin/ifile.h>
 #include <coreplugin/icore.h>
 #include <coreplugin/editormanager/editormanager.h>
 #include <coreplugin/id.h>
 #include <coreplugin/actionmanager/actionmanager.h>
+#include <utils/completingtextedit.h>
 #include <utils/submiteditorwidget.h>
 #include <utils/checkablemessagebox.h>
 #include <utils/synchronousprocess.h>
@@ -64,6 +73,7 @@
 #include <QtCore/QFile>
 #include <QtCore/QFileInfo>
 #include <QtCore/QPointer>
+#include <QtGui/QStringListModel>
 #include <QtCore/QTextStream>
 #include <QtGui/QStyle>
 #include <QtGui/QToolBar>
@@ -78,6 +88,59 @@
 enum { debug = 0 };
 enum { wantToolBar = 0 };
 
+// Return true if word is meaningful and can be added to a completion model
+static bool acceptsWordForCompletion(const char *word)
+{
+    return !(word == 0 || word[0] == 0 || word[1] == 0 || word[2] == 0);
+}
+
+// Return the class name which function belongs to
+static const char *belongingClassName(const CPlusPlus::Function *function)
+{
+    if (function == 0)
+        return 0;
+
+    const CPlusPlus::Name *funcName = function->name();
+    if (funcName != 0 && funcName->asQualifiedNameId() != 0) {
+        const CPlusPlus::Name *funcBaseName = funcName->asQualifiedNameId()->base();
+        if (funcBaseName != 0 && funcBaseName->identifier() != 0)
+            return funcBaseName->identifier()->chars();
+    }
+
+    return 0;
+}
+
+// Recursively return the core element type
+static const CPlusPlus::Type *pointerAndReferenceSimplified(const CPlusPlus::Type *type)
+{
+    const CPlusPlus::PointerType *ptrType = type != 0 ? type->asPointerType() : 0;
+    if (ptrType != 0) {
+        return pointerAndReferenceSimplified(ptrType->elementType().type());
+    }
+    else {
+        const CPlusPlus::ReferenceType *refType = type != 0 ? type->asReferenceType() : 0;
+        if (refType != 0)
+            return pointerAndReferenceSimplified(refType->elementType().type());
+        else
+            return type;
+    }
+}
+
+// Return the core and non-primitive type name (not void, int, float, pointer, ...)
+static const char *nonPrimitiveTypeName(const CPlusPlus::Type *type)
+{
+    if (type == 0)
+        return 0;
+
+    const CPlusPlus::Type *coreType = pointerAndReferenceSimplified(type);
+    const CPlusPlus::Name *coreTypeName =
+            coreType != 0 && coreType->isNamedType() ? coreType->asNamedType()->name() : 0;
+    if (coreTypeName != 0 && coreTypeName->identifier() != 0)
+        return coreTypeName->identifier()->chars();
+
+    return 0;
+}
+
 /*!
     \struct VCSBase::VCSBaseSubmitEditorParameters
 
@@ -147,6 +210,10 @@ VCSBaseSubmitEditorPrivate::VCSBaseSubmitEditorPrivate(const VCSBaseSubmitEditor
     m_file(new VCSBase::Internal::SubmitEditorFile(QLatin1String(parameters->mimeType), q)),
     m_nickNameDialog(0)
 {
+    QCompleter *completer = new QCompleter(q);
+    completer->setCaseSensitivity(Qt::CaseInsensitive);
+    completer->setModelSorting(QCompleter::CaseSensitivelySortedModel);
+    m_widget->descriptionEdit()->setCompleter(completer);
 }
 
 VCSBaseSubmitEditor::VCSBaseSubmitEditor(const VCSBaseSubmitEditorParameters *parameters,
@@ -437,9 +504,74 @@ QStringList VCSBaseSubmitEditor::checkedFiles() const
     return d->m_widget->checkedFiles();
 }
 
-void VCSBaseSubmitEditor::setFileModel(QAbstractItemModel *m)
+void VCSBaseSubmitEditor::setFileModel(QAbstractItemModel *m, const QString &repositoryDirectory)
 {
     d->m_widget->setFileModel(m);
+
+    QSet<QString> uniqueSymbols;
+    const CPlusPlus::Snapshot cppSnapShot = CPlusPlus::CppModelManagerInterface::instance()->snapshot();
+
+    // Iterate over the files and get interesting symbols
+    for (int row = 0; row < m->rowCount(); ++row) {
+        const QString fileName = m->data(m->index(row, d->m_widget->fileNameColumn())).toString();
+        const QFileInfo fileInfo(repositoryDirectory, fileName);
+
+        // Add file name
+        uniqueSymbols.insert(fileInfo.fileName());
+
+        const QString filePath = fileInfo.absoluteFilePath();
+        // Add symbols from the C++ code model
+        const CPlusPlus::Document::Ptr doc = cppSnapShot.document(filePath);
+        if (!doc.isNull() && doc->control() != 0) {
+            const CPlusPlus::Control *ctrl = doc->control();
+            CPlusPlus::Symbol **symPtr = ctrl->firstSymbol(); // Read-only
+            while (symPtr != ctrl->lastSymbol()) {
+                const CPlusPlus::Symbol *sym = *symPtr;
+
+                // Regular identifiers
+                if (sym->identifier() != 0 && acceptsWordForCompletion(sym->identifier()->chars()))
+                    uniqueSymbols.insert(sym->identifier()->chars());
+
+                // Handle specific cases
+                if (sym->isFunction()) {
+                    const CPlusPlus::Function *symFunc = sym->asFunction();
+
+                    // Get "Foo" in "void Foo::function() {}"
+                    if (!symFunc->isDeclaration()) {
+                        const char *className = belongingClassName(symFunc);
+                        if (acceptsWordForCompletion(className))
+                            uniqueSymbols.insert(className);
+                    }
+
+                    // Insert symbol for the return type
+                    const char *retTypeName = nonPrimitiveTypeName(symFunc->returnType().type());
+                    if (acceptsWordForCompletion(retTypeName))
+                        uniqueSymbols.insert(retTypeName);
+                }
+                else if (sym->isArgument()) {
+                    const char *typeName = nonPrimitiveTypeName(sym->asArgument()->type().type());
+                    if (acceptsWordForCompletion(typeName))
+                        uniqueSymbols.insert(typeName);
+                }
+
+                ++symPtr;
+            }
+
+            // Insert macros
+            foreach (const CPlusPlus::Macro &macro, doc->definedMacros()) {
+                if (acceptsWordForCompletion(macro.name().constData()))
+                    uniqueSymbols.insert(macro.name());
+            }
+        }
+    }
+
+    // Populate completer with symbols
+    if (!uniqueSymbols.isEmpty()) {
+        QCompleter *completer = d->m_widget->descriptionEdit()->completer();
+        QStringList symbolsList = uniqueSymbols.toList();
+        symbolsList.sort();
+        completer->setModel(new QStringListModel(symbolsList, completer));
+    }
 }
 
 QAbstractItemModel *VCSBaseSubmitEditor::fileModel() const
diff --git a/src/plugins/vcsbase/vcsbasesubmiteditor.h b/src/plugins/vcsbase/vcsbasesubmiteditor.h
index 6cc477bba36..552e304d8b5 100644
--- a/src/plugins/vcsbase/vcsbasesubmiteditor.h
+++ b/src/plugins/vcsbase/vcsbasesubmiteditor.h
@@ -134,7 +134,7 @@ public:
 
     QStringList checkedFiles() const;
 
-    void setFileModel(QAbstractItemModel *m);
+    void setFileModel(QAbstractItemModel *m, const QString &repositoryDirectory = QString());
     QAbstractItemModel *fileModel() const;
 
     // Utilities returning some predefined icons for actions
-- 
GitLab