mirror of
https://bitbucket.org/smil3y/kde-extraapps.git
synced 2025-02-26 11:52:54 +00:00
456 lines
14 KiB
C++
456 lines
14 KiB
C++
/*
|
|
* KDevelop Generic Code Completion Support
|
|
*
|
|
* Copyright 2006-2008 Hamish Rodda <rodda@kde.org>
|
|
* Copyright 2007-2008 David Nolden <david.nolden.kdevelop@art-master.de>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Library General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public
|
|
* License along with this program; if not, write to the
|
|
* Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "codecompletionmodel.h"
|
|
|
|
#include <QIcon>
|
|
#include <QMetaType>
|
|
#include <QTextFormat>
|
|
#include <QBrush>
|
|
#include <QDir>
|
|
#include <kdebug.h>
|
|
#include <ktexteditor/view.h>
|
|
#include <ktexteditor/document.h>
|
|
#include <kiconloader.h>
|
|
|
|
|
|
#include "../duchain/declaration.h"
|
|
#include "../duchain/classfunctiondeclaration.h"
|
|
#include "../duchain/ducontext.h"
|
|
#include "../duchain/duchain.h"
|
|
#include "../duchain/namespacealiasdeclaration.h"
|
|
#include "../duchain/parsingenvironment.h"
|
|
#include "../duchain/duchainlock.h"
|
|
#include "../duchain/duchainbase.h"
|
|
#include "../duchain/topducontext.h"
|
|
#include "../duchain/duchainutils.h"
|
|
#include "../interfaces/quickopendataprovider.h"
|
|
#include "../interfaces/icore.h"
|
|
#include "../interfaces/ilanguagecontroller.h"
|
|
#include "../interfaces/icompletionsettings.h"
|
|
|
|
#include "codecompletionworker.h"
|
|
#include "codecompletioncontext.h"
|
|
#include <duchain/specializationstore.h>
|
|
|
|
using namespace KTextEditor;
|
|
|
|
//Multi-threaded completion creates some multi-threading related crashes, and sometimes shows the completions in the wrong position if the cursor was moved
|
|
// #define SINGLE_THREADED_COMPLETION
|
|
|
|
namespace KDevelop {
|
|
|
|
class CompletionWorkerThread : public QThread
|
|
{
|
|
public:
|
|
|
|
CompletionWorkerThread(CodeCompletionModel* model)
|
|
: QThread(model), m_model(model), m_worker(m_model->createCompletionWorker())
|
|
{
|
|
Q_ASSERT(m_worker->parent() == 0); // Must be null, else we cannot change the thread affinity!
|
|
m_worker->moveToThread(this);
|
|
Q_ASSERT(m_worker->thread() == this);
|
|
}
|
|
|
|
~CompletionWorkerThread() {
|
|
delete m_worker;
|
|
}
|
|
|
|
virtual void run () {
|
|
//We connect directly, so we can do the pre-grouping within the background thread
|
|
connect(m_worker, SIGNAL(foundDeclarationsReal(QList<KSharedPtr<CompletionTreeElement> >,KSharedPtr<CodeCompletionContext>)), m_model, SLOT(foundDeclarations(QList<KSharedPtr<CompletionTreeElement> >,KSharedPtr<CodeCompletionContext>)), Qt::QueuedConnection);
|
|
|
|
connect(m_model, SIGNAL(completionsNeeded(KDevelop::DUContextPointer,KTextEditor::Cursor,KTextEditor::View*)), m_worker, SLOT(computeCompletions(KDevelop::DUContextPointer,KTextEditor::Cursor,KTextEditor::View*)), Qt::QueuedConnection);
|
|
connect(m_model, SIGNAL(doSpecialProcessingInBackground(uint)), m_worker, SLOT(doSpecialProcessing(uint)));
|
|
exec();
|
|
}
|
|
|
|
CodeCompletionModel* m_model;
|
|
CodeCompletionWorker* m_worker;
|
|
};
|
|
|
|
bool CodeCompletionModel::forceWaitForModel()
|
|
{
|
|
return m_forceWaitForModel;
|
|
}
|
|
|
|
void CodeCompletionModel::setForceWaitForModel(bool wait)
|
|
{
|
|
m_forceWaitForModel = wait;
|
|
}
|
|
|
|
|
|
CodeCompletionModel::CodeCompletionModel( QObject * parent )
|
|
: CodeCompletionModel2(parent)
|
|
, m_forceWaitForModel(false)
|
|
, m_fullCompletion(true)
|
|
, m_mutex(new QMutex)
|
|
, m_thread(0)
|
|
{
|
|
qRegisterMetaType<QList<CompletionTreeElement> >("QList<KSharedPtr<CompletionTreeElement> >");
|
|
qRegisterMetaType<KSharedPtr<CodeCompletionContext> >("KSharedPtr<CodeCompletionContext>");
|
|
qRegisterMetaType<KTextEditor::Cursor>("KTextEditor::Cursor");
|
|
}
|
|
|
|
void CodeCompletionModel::initialize() {
|
|
if(!m_thread) {
|
|
m_thread = new CompletionWorkerThread(this);
|
|
#ifdef SINGLE_THREADED_COMPLETION
|
|
m_thread->m_worker = createCompletionWorker();
|
|
#endif
|
|
m_thread->start();
|
|
}
|
|
}
|
|
|
|
CodeCompletionModel::~CodeCompletionModel()
|
|
{
|
|
if(m_thread->m_worker)
|
|
m_thread->m_worker->abortCurrentCompletion();
|
|
m_thread->quit();
|
|
m_thread->wait();
|
|
|
|
delete m_thread;
|
|
delete m_mutex;
|
|
}
|
|
|
|
void CodeCompletionModel::addNavigationWidget(const CompletionTreeElement* element, QWidget* widget) const
|
|
{
|
|
Q_ASSERT(dynamic_cast<QuickOpenEmbeddedWidgetInterface*>(widget));
|
|
m_navigationWidgets[element] = widget;
|
|
}
|
|
|
|
|
|
bool CodeCompletionModel::fullCompletion() const
|
|
{
|
|
return m_fullCompletion;
|
|
}
|
|
|
|
KDevelop::CodeCompletionWorker* CodeCompletionModel::worker() const {
|
|
return m_thread->m_worker;
|
|
}
|
|
|
|
void CodeCompletionModel::clear() {
|
|
m_completionItems.clear();
|
|
m_navigationWidgets.clear();
|
|
m_completionContext.clear();
|
|
reset();
|
|
}
|
|
|
|
void CodeCompletionModel::completionInvokedInternal(KTextEditor::View* view, const KTextEditor::Range& range, InvocationType invocationType, const KUrl& url)
|
|
{
|
|
Q_ASSERT(m_thread == worker()->thread());
|
|
Q_UNUSED(invocationType)
|
|
|
|
DUChainReadLocker lock(DUChain::lock(), 400);
|
|
if( !lock.locked() ) {
|
|
kDebug() << "could not lock du-chain in time";
|
|
return;
|
|
}
|
|
|
|
TopDUContext* top = DUChainUtils::standardContextForUrl( url );
|
|
if(!top) {
|
|
return;
|
|
}
|
|
setCurrentTopContext(TopDUContextPointer(top));
|
|
|
|
RangeInRevision rangeInRevision = top->transformToLocalRevision(SimpleRange(range));
|
|
|
|
if (top) {
|
|
kDebug() << "completion invoked for context" << (DUContext*)top;
|
|
|
|
if( top->parsingEnvironmentFile() && top->parsingEnvironmentFile()->modificationRevision() != ModificationRevision::revisionForFile(IndexedString(url.pathOrUrl())) ) {
|
|
kDebug() << "Found context is not current. Its revision is " /*<< top->parsingEnvironmentFile()->modificationRevision() << " while the document-revision is " << ModificationRevision::revisionForFile(IndexedString(url.pathOrUrl()))*/;
|
|
}
|
|
|
|
DUContextPointer thisContext;
|
|
{
|
|
kDebug() << "apply specialization:" << range.start();
|
|
thisContext = SpecializationStore::self().applySpecialization(top->findContextAt(rangeInRevision.start), top);
|
|
|
|
if ( thisContext ) {
|
|
kDebug() << "after specialization:" << thisContext->localScopeIdentifier().toString() << thisContext->rangeInCurrentRevision().textRange();
|
|
}
|
|
|
|
if(!thisContext)
|
|
thisContext = top;
|
|
|
|
kDebug() << "context is set to" << thisContext.data();
|
|
if( !thisContext ) {
|
|
kDebug() << "================== NO CONTEXT FOUND =======================";
|
|
m_completionItems.clear();
|
|
m_navigationWidgets.clear();
|
|
reset();
|
|
return;
|
|
}
|
|
}
|
|
|
|
lock.unlock();
|
|
|
|
if(m_forceWaitForModel)
|
|
emit waitForReset();
|
|
|
|
emit completionsNeeded(thisContext, range.start(), view);
|
|
} else {
|
|
kDebug() << "Completion invoked for unknown context. Document:" << url << ", Known documents:" << DUChain::self()->documents();
|
|
}
|
|
}
|
|
|
|
|
|
void CodeCompletionModel::completionInvoked(KTextEditor::View* view, const KTextEditor::Range& range, InvocationType invocationType)
|
|
{
|
|
//If this triggers, initialize() has not been called after creation.
|
|
Q_ASSERT(m_thread);
|
|
|
|
KDevelop::ICompletionSettings::CompletionLevel level = KDevelop::ICore::self()->languageController()->completionSettings()->completionLevel();
|
|
if(level == KDevelop::ICompletionSettings::AlwaysFull || (invocationType != AutomaticInvocation && level == KDevelop::ICompletionSettings::MinimalWhenAutomatic))
|
|
m_fullCompletion = true;
|
|
else
|
|
m_fullCompletion = false;
|
|
|
|
//Only use grouping in full completion mode
|
|
setHasGroups(m_fullCompletion);
|
|
|
|
Q_UNUSED(invocationType)
|
|
|
|
if (!worker()) {
|
|
kWarning() << "Completion invoked on a completion model which has no code completion worker assigned!";
|
|
}
|
|
|
|
m_navigationWidgets.clear();
|
|
m_completionItems.clear();
|
|
|
|
reset();
|
|
|
|
worker()->abortCurrentCompletion();
|
|
worker()->setFullCompletion(m_fullCompletion);
|
|
|
|
KUrl url = view->document()->url();
|
|
|
|
completionInvokedInternal(view, range, invocationType, url);
|
|
}
|
|
|
|
void CodeCompletionModel::foundDeclarations(QList<KSharedPtr<CompletionTreeElement> > items, KSharedPtr<CodeCompletionContext> completionContext)
|
|
{
|
|
m_completionContext = completionContext;
|
|
|
|
if(m_completionItems.isEmpty() && items.isEmpty()) {
|
|
if(m_forceWaitForModel)
|
|
reset(); //If we need to reset the model, reset it
|
|
return; //We don't need to reset, which is bad for target model
|
|
}
|
|
|
|
m_completionItems = items;
|
|
|
|
|
|
if(m_completionContext) {
|
|
kDebug() << "got completion-context with " << m_completionContext->ungroupedElements().size() << "ungrouped elements";
|
|
}
|
|
|
|
reset();
|
|
|
|
/* if (completionContext == m_completionContext.data()) {
|
|
if( !m_completionItems.isEmpty() ) {
|
|
beginInsertRows(QModelIndex(), m_completionItems.count(), m_completionItems.count() + items.count() - 1);
|
|
m_completionItems += items;
|
|
endInsertRows();
|
|
} else {*/
|
|
/* }
|
|
}*/
|
|
}
|
|
|
|
CodeCompletionModelControllerInterface::MatchReaction CodeCompletionModel::matchingItem(const QModelIndex& /*matched*/)
|
|
{
|
|
return None;
|
|
}
|
|
|
|
void CodeCompletionModel::setCompletionContext(KSharedPtr<CodeCompletionContext> completionContext)
|
|
{
|
|
QMutexLocker lock(m_mutex);
|
|
m_completionContext = completionContext;
|
|
|
|
if(m_completionContext) {
|
|
kDebug() << "got completion-context with " << m_completionContext->ungroupedElements().size() << "ungrouped elements";
|
|
}
|
|
}
|
|
|
|
KSharedPtr<CodeCompletionContext> CodeCompletionModel::completionContext() const
|
|
{
|
|
QMutexLocker lock(m_mutex);
|
|
return m_completionContext;
|
|
}
|
|
|
|
void CodeCompletionModel::executeCompletionItem2(Document* document, const Range& word, const QModelIndex& index) const
|
|
{
|
|
//We must not lock the duchain at this place, because the items might rely on that
|
|
CompletionTreeElement* element = (CompletionTreeElement*)index.internalPointer();
|
|
if( !element || !element->asItem() )
|
|
return;
|
|
|
|
element->asItem()->execute(document, word);
|
|
}
|
|
|
|
KSharedPtr< KDevelop::CompletionTreeElement > CodeCompletionModel::itemForIndex(QModelIndex index) const {
|
|
CompletionTreeElement* element = (CompletionTreeElement*)index.internalPointer();
|
|
return KSharedPtr< KDevelop::CompletionTreeElement >(element);
|
|
}
|
|
|
|
QVariant CodeCompletionModel::data(const QModelIndex& index, int role) const
|
|
{
|
|
if ( role == Qt::TextAlignmentRole && index.column() == 0 ) {
|
|
return Qt::AlignRight;
|
|
}
|
|
CompletionTreeElement* element = (CompletionTreeElement*)index.internalPointer();
|
|
if( !element )
|
|
return QVariant();
|
|
|
|
CompletionTreeElement& treeElement(*element);
|
|
|
|
if( role == CodeCompletionModel::GroupRole ) {
|
|
if( treeElement.asNode() ) {
|
|
return QVariant(treeElement.asNode()->role);
|
|
}else {
|
|
kDebug() << "Requested group-role from leaf tree element";
|
|
return QVariant();
|
|
}
|
|
}else{
|
|
if( treeElement.asNode() ) {
|
|
if( role == CodeCompletionModel::InheritanceDepth ) {
|
|
CompletionCustomGroupNode* customGroupNode = dynamic_cast<CompletionCustomGroupNode*>(element);
|
|
if(customGroupNode)
|
|
return QVariant(customGroupNode->inheritanceDepth);
|
|
}
|
|
if( role == treeElement.asNode()->role ) {
|
|
return treeElement.asNode()->roleValue;
|
|
} else {
|
|
return QVariant();
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!treeElement.asItem()) {
|
|
kWarning() << "Error in completion model";
|
|
return QVariant();
|
|
}
|
|
|
|
//Navigation widget interaction is done here, the other stuff is done within the tree-elements
|
|
switch (role) {
|
|
case CodeCompletionModel::InheritanceDepth:
|
|
return treeElement.asItem()->inheritanceDepth();
|
|
case CodeCompletionModel::ArgumentHintDepth:
|
|
return treeElement.asItem()->argumentHintDepth();
|
|
|
|
case CodeCompletionModel::ItemSelected: {
|
|
DeclarationPointer decl = treeElement.asItem()->declaration();
|
|
if(decl) {
|
|
DUChain::self()->emitDeclarationSelected(decl);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//In minimal completion mode, hide all columns except the "name" one
|
|
if(!m_fullCompletion && role == Qt::DisplayRole && index.column() != Name && (treeElement.asItem()->argumentHintDepth() == 0 || index.column() == Prefix))
|
|
return QVariant();
|
|
|
|
QVariant ret = treeElement.asItem()->data(index, role, this);
|
|
|
|
//In reduced completion mode, don't show information text with the selected items
|
|
if(role == ItemSelected && (!m_fullCompletion || !ICore::self()->languageController()->completionSettings()->showMultiLineSelectionInformation()))
|
|
return QVariant();
|
|
|
|
return ret;
|
|
}
|
|
|
|
KDevelop::TopDUContextPointer CodeCompletionModel::currentTopContext() const
|
|
{
|
|
return m_currentTopContext;
|
|
}
|
|
|
|
void CodeCompletionModel::setCurrentTopContext(KDevelop::TopDUContextPointer topContext)
|
|
{
|
|
m_currentTopContext = topContext;
|
|
}
|
|
|
|
QModelIndex CodeCompletionModel::index(int row, int column, const QModelIndex& parent) const
|
|
{
|
|
if( parent.isValid() ) {
|
|
CompletionTreeElement* element = (CompletionTreeElement*)parent.internalPointer();
|
|
|
|
CompletionTreeNode* node = element->asNode();
|
|
|
|
if( !node ) {
|
|
kDebug() << "Requested sub-index of leaf node";
|
|
return QModelIndex();
|
|
}
|
|
|
|
if (row < 0 || row >= node->children.count() || column < 0 || column >= ColumnCount)
|
|
return QModelIndex();
|
|
|
|
return createIndex(row, column, node->children[row].data());
|
|
} else {
|
|
if (row < 0 || row >= m_completionItems.count() || column < 0 || column >= ColumnCount)
|
|
return QModelIndex();
|
|
|
|
return createIndex(row, column, const_cast<CompletionTreeElement*>(m_completionItems[row].data()));
|
|
}
|
|
}
|
|
|
|
QModelIndex CodeCompletionModel::parent ( const QModelIndex & index ) const
|
|
{
|
|
if(rowCount() == 0)
|
|
return QModelIndex();
|
|
|
|
if( index.isValid() ) {
|
|
CompletionTreeElement* element = (CompletionTreeElement*)index.internalPointer();
|
|
|
|
if( element->parent() )
|
|
return createIndex( element->rowInParent(), element->columnInParent(), element->parent() );
|
|
}
|
|
|
|
return QModelIndex();
|
|
}
|
|
|
|
int CodeCompletionModel::rowCount ( const QModelIndex & parent ) const
|
|
{
|
|
if( parent.isValid() ) {
|
|
CompletionTreeElement* element = (CompletionTreeElement*)parent.internalPointer();
|
|
|
|
CompletionTreeNode* node = element->asNode();
|
|
|
|
if( !node )
|
|
return 0;
|
|
|
|
return node->children.count();
|
|
}else{
|
|
return m_completionItems.count();
|
|
}
|
|
}
|
|
|
|
QString CodeCompletionModel::filterString(KTextEditor::View* view, const KTextEditor::Range& range, const KTextEditor::Cursor& position)
|
|
{
|
|
m_filterString = KTextEditor::CodeCompletionModelControllerInterface::filterString(view, range, position);
|
|
return m_filterString;
|
|
}
|
|
|
|
}
|
|
|
|
#include "moc_codecompletionmodel.cpp"
|