Commit 676739a8 authored by Hugues Delorme's avatar Hugues Delorme

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: default avatarTobias Hunger <tobias.hunger@nokia.com>
Reviewed-by: default avatarHugues Delorme <delorme.hugues@fougsys.fr>
parent f6963123
/**************************************************************************
**
** 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"
/**************************************************************************
**
** 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
......@@ -457,7 +457,7 @@ QStringList SubmitEditorWidget::checkedFiles() const
return rc;
}
QTextEdit *SubmitEditorWidget::descriptionEdit() const
CompletingTextEdit *SubmitEditorWidget::descriptionEdit() const
{
return d->m_ui.description;
}
......
......@@ -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);
......
......@@ -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>
......@@ -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 \
......
......@@ -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);
}
......
......@@ -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);
}
......@@ -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);
......
......@@ -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)
......
......@@ -85,7 +85,7 @@ void CommitEditor::setFields(const QFileInfo &repositoryRoot, const QString &bra
}
}
setFileModel(fileModel);
setFileModel(fileModel, repositoryRoot.absoluteFilePath());
}
QString CommitEditor::committerInfo()
......
......@@ -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)
......@@ -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
......
......@@ -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
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment