kde-workspace/kate/part/completion/katecompletionmodel.cpp
Ivailo Monev 19b46a1c46 generic: replace QVarLengthArray<T> with std::vector<T> and QVector<T>
std::vector<T> for when the data does not have to be shared (e.g. POD
types) and there is no Q_DECLARE_TYPEINFO() involved, QVector<T> for when
data should be shared

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2023-07-30 18:50:02 +00:00

2277 lines
65 KiB
C++

/* This file is part of the KDE libraries and the Kate part.
*
* Copyright (C) 2005-2006 Hamish Rodda <rodda@kde.org>
* Copyright (C) 2007-2008 David Nolden <david.nolden.kdevelop@art-master.de>
*
* This library 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 library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "katecompletionmodel.h"
#include <QTextEdit>
#include <QMap>
#include <QTimer>
#include <klocale.h>
#include <kiconloader.h>
#include <kapplication.h>
#include "katecompletionwidget.h"
#include "katecompletiontree.h"
#include "katecompletiondelegate.h"
#include "kateargumenthintmodel.h"
#include "kateview.h"
#include "katerenderer.h"
#include "kateconfig.h"
#include "codecompletionmodelcontrollerinterfacev4.h"
#include <vector>
using namespace KTextEditor;
///A helper-class for handling completion-models with hierarchical grouping/optimization
class HierarchicalModelHandler {
public:
HierarchicalModelHandler(CodeCompletionModel* model);
void addValue(CodeCompletionModel::ExtraItemDataRoles role, const QVariant& value);
//Walks the index upwards and collects all defined completion-roles on the way
void collectRoles(const QModelIndex& index);
void takeRole(const QModelIndex& index);
CodeCompletionModel* model() const;
//Assumes that index is a sub-index of the indices where role-values were taken
QVariant getData(CodeCompletionModel::ExtraItemDataRoles role, const QModelIndex& index) const;
bool hasHierarchicalRoles() const;
int inheritanceDepth(const QModelIndex& i) const;
QString customGroup() const {
return m_customGroup;
}
int customGroupingKey() const {
return m_groupSortingKey;
}
private:
typedef QMap<CodeCompletionModel::ExtraItemDataRoles, QVariant> RoleMap;
RoleMap m_roleValues;
QString m_customGroup;
int m_groupSortingKey;
CodeCompletionModel* m_model;
};
CodeCompletionModel* HierarchicalModelHandler::model() const {
return m_model;
}
bool HierarchicalModelHandler::hasHierarchicalRoles() const {
return !m_roleValues.isEmpty();
}
void HierarchicalModelHandler::collectRoles(const QModelIndex& index) {
if( index.parent().isValid() )
collectRoles(index.parent());
if(m_model->rowCount(index) != 0)
takeRole(index);
}
int HierarchicalModelHandler::inheritanceDepth(const QModelIndex& i) const {
return getData(CodeCompletionModel::InheritanceDepth, i).toInt();
}
void HierarchicalModelHandler::takeRole(const QModelIndex& index) {
QVariant v = index.data(CodeCompletionModel::GroupRole);
if( v.isValid() && v.canConvert(QVariant::Int) ) {
QVariant value = index.data(v.toInt());
if(v.toInt() == Qt::DisplayRole) {
m_customGroup = index.data(Qt::DisplayRole).toString();
QVariant sortingKey = index.data(CodeCompletionModel::InheritanceDepth);
if(sortingKey.canConvert(QVariant::Int))
m_groupSortingKey = sortingKey.toInt();
}else{
m_roleValues[(CodeCompletionModel::ExtraItemDataRoles)v.toInt()] = value;
}
}else{
kDebug( 13035 ) << "Did not return valid GroupRole in hierarchical completion-model";
}
}
QVariant HierarchicalModelHandler::getData(CodeCompletionModel::ExtraItemDataRoles role, const QModelIndex& index) const {
RoleMap::const_iterator it = m_roleValues.find(role);
if( it != m_roleValues.end() )
return *it;
else
return index.data(role);
}
HierarchicalModelHandler::HierarchicalModelHandler(CodeCompletionModel* model) : m_groupSortingKey(-1), m_model(model) {
}
void HierarchicalModelHandler::addValue(CodeCompletionModel::ExtraItemDataRoles role, const QVariant& value) {
m_roleValues[role] = value;
}
KateCompletionModel::KateCompletionModel(KateCompletionWidget* parent)
: ExpandingWidgetModel(parent)
, m_hasGroups(false)
, m_matchCaseSensitivity(Qt::CaseInsensitive)
, m_ungrouped(new Group(this))
, m_argumentHints(new Group(this))
, m_bestMatches(new Group(this))
, m_sortingEnabled(false)
, m_sortingAlphabetical(false)
, m_isSortingByInheritance(false)
, m_sortingCaseSensitivity(Qt::CaseInsensitive)
, m_filteringEnabled(false)
, m_filterContextMatchesOnly(false)
, m_filterByAttribute(false)
, m_filterAttributes(KTextEditor::CodeCompletionModel::NoProperty)
, m_maximumInheritanceDepth(0)
, m_groupingEnabled(false)
, m_accessConst(false)
, m_accessStatic(false)
, m_accesSignalSlot(false)
, m_columnMergingEnabled(false)
// , m_haveExactMatch(false)
{
m_ungrouped->attribute = 0;
m_argumentHints->attribute = -1;
m_bestMatches->attribute = BestMatchesProperty;
m_argumentHints->title = i18n("Argument-hints");
m_bestMatches->title = i18n("Best matches");
m_emptyGroups.append(m_ungrouped);
m_emptyGroups.append(m_argumentHints);
m_emptyGroups.append(m_bestMatches);
m_updateBestMatchesTimer = new QTimer(this);
m_updateBestMatchesTimer->setSingleShot(true);
connect(m_updateBestMatchesTimer, SIGNAL(timeout()), this, SLOT(updateBestMatches()));
m_groupHash.insert(0, m_ungrouped);
m_groupHash.insert(-1, m_argumentHints);
m_groupHash.insert(BestMatchesProperty, m_argumentHints);
}
KateCompletionModel::~KateCompletionModel() {
clearCompletionModels();
delete m_argumentHints;
delete m_ungrouped;
delete m_bestMatches;
}
QTreeView* KateCompletionModel::treeView() const {
return view()->completionWidget()->treeView();
}
QVariant KateCompletionModel::data( const QModelIndex & index, int role ) const
{
if (!hasCompletionModel() || !index.isValid())
return QVariant();
if( role == Qt::DecorationRole && index.column() == KTextEditor::CodeCompletionModel::Prefix && isExpandable(index) )
{
cacheIcons();
if( !isExpanded(index ) )
return QVariant( m_collapsedIcon );
else
return QVariant( m_expandedIcon );
}
//groupOfParent returns a group when the index is a member of that group, but not the group head/label.
if (!hasGroups() || groupOfParent(index)) {
switch (role) {
case Qt::TextAlignmentRole:
if (isColumnMergingEnabled() && m_columnMerges.count()) {
int c = 0;
foreach (const QList<int>& list, m_columnMerges) {
foreach (int column, list) {
if (c++ == index.column()) {
if (column == CodeCompletionModel::Scope)
if (list.count() == 1)
return Qt::AlignRight;
goto dontalign;
}
}
}
} else if ((!isColumnMergingEnabled() || m_columnMerges.isEmpty()) && index.column() == CodeCompletionModel::Scope) {
return Qt::AlignRight;
}
dontalign:
break;
}
// Merge text for column merging
if (role == Qt::DisplayRole && m_columnMerges.count() && isColumnMergingEnabled()) {
QString text;
foreach (int column, m_columnMerges[index.column()]) {
QModelIndex sourceIndex = mapToSource(createIndex(index.row(), column, index.internalPointer()));
text.append(sourceIndex.data(role).toString());
}
return text;
}
if(role == CodeCompletionModel::HighlightingMethod)
{
//Return that we are doing custom-highlighting of one of the sub-strings does it. Unfortunately internal highlighting does not work for the other substrings.
foreach (int column, m_columnMerges[index.column()]) {
QModelIndex sourceIndex = mapToSource(createIndex(index.row(), column, index.internalPointer()));
QVariant method = sourceIndex.data(CodeCompletionModel::HighlightingMethod);
if( method.type() == QVariant::Int && method.toInt() == CodeCompletionModel::CustomHighlighting)
return QVariant(CodeCompletionModel::CustomHighlighting);
}
return QVariant();
}
if(role == CodeCompletionModel::CustomHighlight)
{
//Merge custom highlighting if multiple columns were merged
QStringList strings;
//Collect strings
foreach (int column, m_columnMerges[index.column()])
strings << mapToSource(createIndex(index.row(), column, index.internalPointer())).data(Qt::DisplayRole).toString();
QList<QVariantList> highlights;
//Collect custom-highlightings
foreach (int column, m_columnMerges[index.column()])
highlights << mapToSource(createIndex(index.row(), column, index.internalPointer())).data(CodeCompletionModel::CustomHighlight).toList();
return mergeCustomHighlighting( strings, highlights, 0 );
}
QVariant v = mapToSource(index).data(role);
if( v.isValid() )
return v;
else
return ExpandingWidgetModel::data(index, role);
}
//Returns a nonzero group if this index is the head of a group(A Label in the list)
Group* g = groupForIndex(index);
if (g && (!g->isEmpty)) {
switch (role) {
case Qt::DisplayRole:
//We return the group-header for all columns, ExpandingDelegate will paint them properly over the whole space
return QString(' ' + g->title);
break;
case Qt::FontRole:
if (!index.column()) {
QFont f = view()->renderer()->config()->font();
f.setBold(true);
return f;
}
break;
case Qt::ForegroundRole:
return KApplication::kApplication()->palette().toolTipText().color();
case Qt::BackgroundRole:
return KApplication::kApplication()->palette().toolTipBase().color();
}
}
return QVariant();
}
int KateCompletionModel::contextMatchQuality(const QModelIndex& index) const {
if(!index.isValid())
return 0;
Group* g = groupOfParent(index);
if(!g || g->filtered.size() < index.row())
return 0;
return contextMatchQuality(g->filtered[index.row()].sourceRow());
}
int KateCompletionModel::contextMatchQuality(const ModelRow& source) const {
QModelIndex realIndex = source.second;
int bestMatch = -1;
//Iterate through all argument-hints and find the best match-quality
foreach( const Item& item, m_argumentHints->filtered )
{
const ModelRow& row(item.sourceRow());
if( realIndex.model() != row.first )
continue; //We can only match within the same source-model
QModelIndex hintIndex = row.second;
QVariant depth = hintIndex.data(CodeCompletionModel::ArgumentHintDepth);
if( !depth.isValid() || depth.type() != QVariant::Int || depth.toInt() != 1 )
continue; //Only match completion-items to argument-hints of depth 1(the ones the item will be given to as argument)
hintIndex.data(CodeCompletionModel::SetMatchContext);
QVariant matchQuality = realIndex.data(CodeCompletionModel::MatchQuality);
if( matchQuality.isValid() && matchQuality.type() == QVariant::Int ) {
int m = matchQuality.toInt();
if( m > bestMatch )
bestMatch = m;
}
}
if(m_argumentHints->filtered.isEmpty()) {
QVariant matchQuality = realIndex.data(CodeCompletionModel::MatchQuality);
if( matchQuality.isValid() && matchQuality.type() == QVariant::Int ) {
int m = matchQuality.toInt();
if( m > bestMatch )
bestMatch = m;
}
}
return bestMatch;
}
Qt::ItemFlags KateCompletionModel::flags( const QModelIndex & index ) const
{
if (!hasCompletionModel() || !index.isValid())
return 0;
if (!hasGroups() || groupOfParent(index))
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
return Qt::ItemIsEnabled;
}
KateCompletionWidget* KateCompletionModel::widget() const {
return static_cast<KateCompletionWidget*>(QObject::parent());
}
KateView * KateCompletionModel::view( ) const
{
return widget()->view();
}
void KateCompletionModel::setMatchCaseSensitivity( Qt::CaseSensitivity cs )
{
m_matchCaseSensitivity = cs;
}
int KateCompletionModel::columnCount( const QModelIndex& ) const
{
return isColumnMergingEnabled() && !m_columnMerges.isEmpty() ? m_columnMerges.count() : KTextEditor::CodeCompletionModel::ColumnCount;
}
KateCompletionModel::ModelRow KateCompletionModel::modelRowPair(const QModelIndex& index) const
{
return qMakePair(static_cast<CodeCompletionModel*>(const_cast<QAbstractItemModel*>(index.model())), index);
}
bool KateCompletionModel::hasChildren( const QModelIndex & parent ) const
{
if (!hasCompletionModel())
return false;
if (!parent.isValid()) {
if (hasGroups())
return true;
return !m_ungrouped->filtered.isEmpty();
}
if (parent.column() != 0)
return false;
if (!hasGroups())
return false;
if (Group* g = groupForIndex(parent))
return !g->filtered.isEmpty();
return false;
}
QModelIndex KateCompletionModel::index( int row, int column, const QModelIndex & parent ) const
{
if (row < 0 || column < 0 || column >= columnCount(QModelIndex()))
return QModelIndex();
if (parent.isValid() || !hasGroups()) {
if (parent.isValid() && parent.column() != 0)
return QModelIndex();
Group* g = groupForIndex(parent);
if (!g)
return QModelIndex();
if (row >= g->filtered.count()) {
//kWarning() << "Invalid index requested: row " << row << " beyond indivdual range in group " << g;
return QModelIndex();
}
//kDebug( 13035 ) << "Returning index for child " << row << " of group " << g;
return createIndex(row, column, g);
}
if (row >= m_rowTable.count()) {
//kWarning() << "Invalid index requested: row " << row << " beyond group range.";
return QModelIndex();
}
//kDebug( 13035 ) << "Returning index for group " << m_rowTable[row];
return createIndex(row, column, 0);
}
/*QModelIndex KateCompletionModel::sibling( int row, int column, const QModelIndex & index ) const
{
if (row < 0 || column < 0 || column >= columnCount(QModelIndex()))
return QModelIndex();
if (!index.isValid()) {
}
if (Group* g = groupOfParent(index)) {
if (row >= g->filtered.count())
return QModelIndex();
return createIndex(row, column, g);
}
if (hasGroups())
return QModelIndex();
if (row >= m_ungrouped->filtered.count())
return QModelIndex();
return createIndex(row, column, m_ungrouped);
}*/
bool KateCompletionModel::hasIndex( int row, int column, const QModelIndex & parent ) const
{
if (row < 0 || column < 0 || column >= columnCount(QModelIndex()))
return false;
if (parent.isValid() || !hasGroups()) {
if (parent.isValid() && parent.column() != 0)
return false;
Group* g = groupForIndex(parent);
if (row >= g->filtered.count())
return false;
return true;
}
if (row >= m_rowTable.count())
return false;
return true;
}
QModelIndex KateCompletionModel::indexForRow( Group * g, int row ) const
{
if (row < 0 || row >= g->filtered.count())
return QModelIndex();
return createIndex(row, 0, g);
}
QModelIndex KateCompletionModel::indexForGroup( Group * g ) const
{
if (!hasGroups())
return QModelIndex();
int row = m_rowTable.indexOf(g);
if (row == -1)
return QModelIndex();
return createIndex(row, 0, 0);
}
void KateCompletionModel::clearGroups()
{
clearExpanding();
m_ungrouped->clear();
m_argumentHints->clear();
m_bestMatches->clear();
// Don't bother trying to work out where it is
m_rowTable.removeAll(m_ungrouped);
m_emptyGroups.removeAll(m_ungrouped);
m_rowTable.removeAll(m_argumentHints);
m_emptyGroups.removeAll(m_argumentHints);
m_rowTable.removeAll(m_bestMatches);
m_emptyGroups.removeAll(m_bestMatches);
qDeleteAll(m_rowTable);
qDeleteAll(m_emptyGroups);
m_rowTable.clear();
m_emptyGroups.clear();
m_groupHash.clear();
m_customGroupHash.clear();
m_emptyGroups.append(m_ungrouped);
m_groupHash.insert(0, m_ungrouped);
m_emptyGroups.append(m_argumentHints);
m_groupHash.insert(-1, m_argumentHints);
m_emptyGroups.append(m_bestMatches);
m_groupHash.insert(BestMatchesProperty, m_bestMatches);
}
QSet<KateCompletionModel::Group*> KateCompletionModel::createItems(const HierarchicalModelHandler& _handler, const QModelIndex& i, bool notifyModel) {
HierarchicalModelHandler handler(_handler);
QSet<Group*> ret;
if( handler.model()->rowCount(i) == 0 ) {
//Leaf node, create an item
ret.insert( createItem(handler, i, notifyModel) );
} else {
//Non-leaf node, take the role from the node, and recurse to the sub-nodes
handler.takeRole(i);
for(int a = 0; a < handler.model()->rowCount(i); a++)
ret += createItems(handler, i.child(a, 0), notifyModel);
}
return ret;
}
QSet<KateCompletionModel::Group*> KateCompletionModel::deleteItems(const QModelIndex& i) {
QSet<Group*> ret;
if( i.model()->rowCount(i) == 0 ) {
//Leaf node, delete the item
Group* g = groupForIndex(mapFromSource(i));
ret.insert(g);
g->removeItem(ModelRow(const_cast<CodeCompletionModel*>(static_cast<const CodeCompletionModel*>(i.model())), i));
} else {
//Non-leaf node
for(int a = 0; a < i.model()->rowCount(i); a++)
ret += deleteItems(i.child(a, 0));
}
return ret;
}
void KateCompletionModel::createGroups()
{
beginResetModel();
//After clearing the model, it has to be reset, else we will be in an invalid state while inserting
//new groups.
clearGroups();
bool has_groups=false;
foreach (CodeCompletionModel* sourceModel, m_completionModels) {
has_groups|=sourceModel->hasGroups();
for (int i = 0; i < sourceModel->rowCount(); ++i)
createItems(HierarchicalModelHandler(sourceModel), sourceModel->index(i, 0));
}
m_hasGroups=has_groups;
//debugStats();
foreach (Group* g, m_rowTable)
hideOrShowGroup(g);
foreach (Group* g, m_emptyGroups)
hideOrShowGroup(g);
makeGroupItemsUnique();
updateBestMatches();
endResetModel();
}
KateCompletionModel::Group* KateCompletionModel::createItem(const HierarchicalModelHandler& handler, const QModelIndex& sourceIndex, bool notifyModel)
{
//QModelIndex sourceIndex = sourceModel->index(row, CodeCompletionModel::Name, QModelIndex());
int completionFlags = handler.getData(CodeCompletionModel::CompletionRole, sourceIndex).toInt();
//Scope is expensive, should not be used with big models
QString scopeIfNeeded = (groupingMethod() & Scope) ? sourceIndex.sibling(sourceIndex.row(), CodeCompletionModel::Scope).data(Qt::DisplayRole).toString() : QString();
int argumentHintDepth = handler.getData(CodeCompletionModel::ArgumentHintDepth, sourceIndex).toInt();
Group* g;
if( argumentHintDepth ) {
g = m_argumentHints;
} else{
QString customGroup = handler.customGroup();
if(!customGroup.isNull() && m_hasGroups) {
if(m_customGroupHash.contains(customGroup)) {
g = m_customGroupHash[customGroup];
}else{
g = new Group(this);
g->title = customGroup;
g->customSortingKey = handler.customGroupingKey();
m_emptyGroups.append(g);
m_customGroupHash.insert(customGroup, g);
}
}else{
g = fetchGroup(completionFlags, scopeIfNeeded, handler.hasHierarchicalRoles());
}
}
Item item = Item(g != m_argumentHints, this, handler, ModelRow(handler.model(), sourceIndex));
if(g != m_argumentHints)
item.match();
g->addItem(item, notifyModel);
return g;
}
void KateCompletionModel::slotRowsInserted( const QModelIndex & parent, int start, int end )
{
QSet<Group*> affectedGroups;
HierarchicalModelHandler handler(static_cast<CodeCompletionModel*>(sender()));
if(parent.isValid())
handler.collectRoles(parent);
for (int i = start; i <= end; ++i)
affectedGroups += createItems(handler, parent.isValid() ? parent.child(i, 0) : handler.model()->index(i, 0), true);
foreach (Group* g, affectedGroups)
hideOrShowGroup(g, true);
}
void KateCompletionModel::slotRowsRemoved( const QModelIndex & parent, int start, int end )
{
CodeCompletionModel* source = static_cast<CodeCompletionModel*>(sender());
QSet<Group*> affectedGroups;
for (int i = start; i <= end; ++i) {
QModelIndex index = parent.isValid() ? parent.child(i, 0) : source->index(i, 0);
affectedGroups += deleteItems(index);
}
foreach (Group* g, affectedGroups)
hideOrShowGroup(g, true);
}
KateCompletionModel::Group* KateCompletionModel::fetchGroup( int attribute, const QString& scope, bool forceGrouping )
{
Q_UNUSED(forceGrouping);
///@todo use forceGrouping
if (!hasGroups())
return m_ungrouped;
int groupingAttribute = groupingAttributes(attribute);
//kDebug( 13035 ) << attribute << " " << groupingAttribute;
if (m_groupHash.contains(groupingAttribute)) {
if (groupingMethod() & Scope) {
for (QHash<int, Group*>::ConstIterator it = m_groupHash.constFind(groupingAttribute); it != m_groupHash.constEnd() && it.key() == groupingAttribute; ++it)
if (it.value()->scope == scope)
return it.value();
} else {
return m_groupHash.value(groupingAttribute);
}
}
Group* ret = new Group(this);
ret->attribute = attribute;
ret->scope = scope;
QString st, at, it;
if (groupingMethod() & ScopeType) {
if (attribute & KTextEditor::CodeCompletionModel::GlobalScope)
st = "Global";
else if (attribute & KTextEditor::CodeCompletionModel::NamespaceScope)
st = "Namespace";
else if (attribute & KTextEditor::CodeCompletionModel::LocalScope)
st = "Local";
ret->title = st;
}
if (groupingMethod() & Scope) {
if (!ret->title.isEmpty())
ret->title.append(" ");
ret->title.append(scope);
}
if (groupingMethod() & AccessType) {
if (attribute & KTextEditor::CodeCompletionModel::Public)
at = "Public";
else if (attribute & KTextEditor::CodeCompletionModel::Protected)
at = "Protected";
else if (attribute & KTextEditor::CodeCompletionModel::Private)
at = "Private";
if (accessIncludeStatic() && attribute & KTextEditor::CodeCompletionModel::Static)
at.append(" Static");
if (accessIncludeConst() && attribute & KTextEditor::CodeCompletionModel::Const)
at.append(" Const");
if( !at.isEmpty() ) {
if (!ret->title.isEmpty())
ret->title.append(", ");
ret->title.append(at);
}
}
if (groupingMethod() & ItemType) {
if (attribute & CodeCompletionModel::Namespace)
it = i18n("Namespaces");
else if (attribute & CodeCompletionModel::Class)
it = i18n("Classes");
else if (attribute & CodeCompletionModel::Struct)
it = i18n("Structs");
else if (attribute & CodeCompletionModel::Union)
it = i18n("Unions");
else if (attribute & CodeCompletionModel::Function)
it = i18n("Functions");
else if (attribute & CodeCompletionModel::Variable)
it = i18n("Variables");
else if (attribute & CodeCompletionModel::Enum)
it = i18n("Enumerations");
if( !it.isEmpty() ) {
if (!ret->title.isEmpty())
ret->title.append(" ");
ret->title.append(it);
}
}
m_emptyGroups.append(ret);
m_groupHash.insert(groupingAttribute, ret);
return ret;
}
bool KateCompletionModel::hasGroups( ) const
{
//kDebug( 13035 ) << "m_groupHash.size()"<<m_groupHash.size();
//kDebug( 13035 ) << "m_rowTable.count()"<<m_rowTable.count();
// We cannot decide whether there is groups easily. The problem: The code-model can
// be populated with a delay from within a background-thread.
// Proper solution: Ask all attached code-models(Through a new interface) whether they want to use grouping,
// and if at least one wants to, return true, else return false.
return m_groupingEnabled && m_hasGroups;
}
KateCompletionModel::Group* KateCompletionModel::groupForIndex( const QModelIndex & index ) const
{
if (!index.isValid()) {
if (!hasGroups())
return m_ungrouped;
else
return 0L;
}
if (groupOfParent(index))
return 0L;
if (index.row() < 0 || index.row() >= m_rowTable.count())
return m_ungrouped;
return m_rowTable[index.row()];
}
/*QMap< int, QVariant > KateCompletionModel::itemData( const QModelIndex & index ) const
{
if (!hasGroups() || groupOfParent(index)) {
QModelIndex index = mapToSource(index);
if (index.isValid())
return index.model()->itemData(index);
}
return QAbstractItemModel::itemData(index);
}*/
QModelIndex KateCompletionModel::parent( const QModelIndex & index ) const
{
if (!index.isValid())
return QModelIndex();
if (Group* g = groupOfParent(index)) {
if (!hasGroups()) {
Q_ASSERT(g == m_ungrouped);
return QModelIndex();
}
int row = m_rowTable.indexOf(g);
if (row == -1) {
kWarning() << "Couldn't find parent for index" << index;
return QModelIndex();
}
return createIndex(row, 0, 0);
}
return QModelIndex();
}
int KateCompletionModel::rowCount( const QModelIndex & parent ) const
{
if (!parent.isValid()) {
if (hasGroups()) {
//kDebug( 13035 ) << "Returning row count for toplevel " << m_rowTable.count();
return m_rowTable.count();
} else {
//kDebug( 13035 ) << "Returning ungrouped row count for toplevel " << m_ungrouped->filtered.count();
return m_ungrouped->filtered.count();
}
}
Group* g = groupForIndex(parent);
// This is not an error, seems you don't have to check hasChildren()
if (!g)
return 0;
//kDebug( 13035 ) << "Returning row count for group " << g << " as " << g->filtered.count();
return g->filtered.count();
}
void KateCompletionModel::sort( int column, Qt::SortOrder order )
{
Q_UNUSED(column)
Q_UNUSED(order)
}
QModelIndex KateCompletionModel::mapToSource( const QModelIndex & proxyIndex ) const
{
if (!proxyIndex.isValid())
return QModelIndex();
if (Group* g = groupOfParent(proxyIndex)) {
if( proxyIndex.row() >= 0 && proxyIndex.row() < g->filtered.count() ) {
ModelRow source = g->filtered[proxyIndex.row()].sourceRow();
return source.second.sibling(source.second.row(), proxyIndex.column());
}else{
kDebug( 13035 ) << "Invalid proxy-index";
}
}
return QModelIndex();
}
QModelIndex KateCompletionModel::mapFromSource( const QModelIndex & sourceIndex ) const
{
if (!sourceIndex.isValid())
return QModelIndex();
if (!hasGroups())
return index(m_ungrouped->rowOf(modelRowPair(sourceIndex)), sourceIndex.column(), QModelIndex());
foreach (Group* g, m_rowTable) {
int row = g->rowOf(modelRowPair(sourceIndex));
if (row != -1)
return index(row, sourceIndex.column(), indexForGroup(g));
}
// Copied from above
foreach (Group* g, m_emptyGroups) {
int row = g->rowOf(modelRowPair(sourceIndex));
if (row != -1)
return index(row, sourceIndex.column(), indexForGroup(g));
}
return QModelIndex();
}
void KateCompletionModel::setCurrentCompletion( KTextEditor::CodeCompletionModel* model, const QString & completion )
{
if (m_currentMatch[model] == completion)
return;
if (!hasCompletionModel()) {
m_currentMatch[model] = completion;
return;
}
changeTypes changeType = Change;
if (m_currentMatch[model].length() > completion.length() && m_currentMatch[model].startsWith(completion, m_matchCaseSensitivity)) {
// Filter has been broadened
changeType = Broaden;
} else if (m_currentMatch[model].length() < completion.length() && completion.startsWith(m_currentMatch[model], m_matchCaseSensitivity)) {
// Filter has been narrowed
changeType = Narrow;
}
//kDebug( 13035 ) << model << "Old match: " << m_currentMatch[model] << ", new: " << completion << ", type: " << changeType;
m_currentMatch[model] = completion;
const bool resetModel = (changeType != Narrow);
if (resetModel) {
beginResetModel();
}
if (!hasGroups()) {
changeCompletions(m_ungrouped, changeType, !resetModel);
} else {
foreach (Group* g, m_rowTable) {
if(g != m_argumentHints)
changeCompletions(g, changeType, !resetModel);
}
foreach (Group* g, m_emptyGroups) {
if(g != m_argumentHints)
changeCompletions(g, changeType, !resetModel);
}
}
// NOTE: best matches are also updated in resort
resort();
if (resetModel) {
endResetModel();
}
clearExpanding(); //We need to do this, or be aware of expanding-widgets while filtering.
emit layoutChanged();
}
QString KateCompletionModel::commonPrefixInternal(const QString &forcePrefix) const
{
QString commonPrefix; // isNull() = true
QList< Group* > groups = m_rowTable;
groups += m_ungrouped;
foreach (Group* g, groups) {
foreach(const Item& item, g->filtered)
{
uint startPos = m_currentMatch[item.sourceRow().first].length();
const QString candidate = item.name().mid(startPos);
if(!candidate.startsWith(forcePrefix))
continue;
if(commonPrefix.isNull()) {
commonPrefix = candidate;
//Replace QString::null prefix with QString(""), so we won't initialize it again
if(commonPrefix.isNull())
commonPrefix = QString(""); // isEmpty() = true, isNull() = false
}else{
commonPrefix = commonPrefix.left(candidate.length());
for(int a = 0; a < commonPrefix.length(); ++a) {
if(commonPrefix[a] != candidate[a]) {
commonPrefix = commonPrefix.left(a);
break;
}
}
}
}
}
return commonPrefix;
}
QString KateCompletionModel::commonPrefix(QModelIndex selectedIndex) const
{
QString commonPrefix = commonPrefixInternal(QString());
if(commonPrefix.isEmpty() && selectedIndex.isValid()) {
Group* g = m_ungrouped;
if(hasGroups())
g = groupOfParent(selectedIndex);
if(g && selectedIndex.row() < g->filtered.size())
{
//Follow the path of the selected item, finding the next non-empty common prefix
Item item = g->filtered[selectedIndex.row()];
int matchLength = m_currentMatch[item.sourceRow().first].length();
commonPrefix = commonPrefixInternal(item.name().mid(matchLength).left(1));
}
}
return commonPrefix;
}
void KateCompletionModel::changeCompletions( Group * g, changeTypes changeType, bool notifyModel )
{
if(changeType != Narrow) {
g->filtered = g->prefilter;
//In the "Broaden" or "Change" case, just re-filter everything,
//and don't notify the model. The model is notified afterwards through a reset().
}
//This code determines what of the filtered items still fit, and computes the ranges that were removed, giving
//them to beginRemoveRows(..) in batches
QList <KateCompletionModel::Item > newFiltered;
int deleteUntil = -1; //In each state, the range [currentRow+1, deleteUntil] needs to be deleted
for(int currentRow = g->filtered.count()-1; currentRow >= 0; --currentRow) {
if(g->filtered[currentRow].match()) {
//This row does not need to be deleted, which means that currentRow+1 to deleteUntil need to be deleted now
if(deleteUntil != -1 && notifyModel) {
beginRemoveRows(indexForGroup(g), currentRow+1, deleteUntil);
endRemoveRows();
}
deleteUntil = -1;
newFiltered.prepend(g->filtered[currentRow]);
}else{
if(deleteUntil == -1)
deleteUntil = currentRow; //Mark that this row needs to be deleted
}
}
if(deleteUntil != -1 && notifyModel) {
beginRemoveRows(indexForGroup(g), 0, deleteUntil);
endRemoveRows();
}
g->filtered = newFiltered;
hideOrShowGroup(g, notifyModel);
}
int KateCompletionModel::Group::orderNumber() const {
if( this == model->m_ungrouped )
return 700;
if(customSortingKey != -1)
return customSortingKey;
if( attribute & BestMatchesProperty )
return 1;
if (attribute & KTextEditor::CodeCompletionModel::LocalScope)
return 100;
else if (attribute & KTextEditor::CodeCompletionModel::Public)
return 200;
else if (attribute & KTextEditor::CodeCompletionModel::Protected)
return 300;
else if (attribute & KTextEditor::CodeCompletionModel::Private)
return 400;
else if (attribute & KTextEditor::CodeCompletionModel::NamespaceScope)
return 500;
else if (attribute & KTextEditor::CodeCompletionModel::GlobalScope)
return 600;
return 700;
}
bool KateCompletionModel::Group::orderBefore(Group* other) const {
return orderNumber() < other->orderNumber();
}
void KateCompletionModel::hideOrShowGroup(Group* g, bool notifyModel)
{
if( g == m_argumentHints ) {
emit argumentHintsChanged();
m_updateBestMatchesTimer->start(200); //We have new argument-hints, so we have new best matches
return; //Never show argument-hints in the normal completion-list
}
if (!g->isEmpty) {
if (g->filtered.isEmpty()) {
// Move to empty group list
g->isEmpty = true;
int row = m_rowTable.indexOf(g);
if (row != -1) {
if (hasGroups() && notifyModel)
beginRemoveRows(QModelIndex(), row, row);
m_rowTable.removeAt(row);
if (hasGroups() && notifyModel)
endRemoveRows();
m_emptyGroups.append(g);
} else {
kWarning() << "Group " << g << " not found in row table!!";
}
}
} else {
if (!g->filtered.isEmpty()) {
// Move off empty group list
g->isEmpty = false;
int row = 0; //Find row where to insert
for( int a = 0; a < m_rowTable.count(); a++ ) {
if( g->orderBefore(m_rowTable[a]) ) {
row = a;
break;
}
row = a+1;
}
if(notifyModel) {
if (hasGroups())
beginInsertRows(QModelIndex(), row, row);
else
beginInsertRows(QModelIndex(), 0, g->filtered.count());
}
m_rowTable.insert(row, g);
if(notifyModel)
endInsertRows();
m_emptyGroups.removeAll(g);
}
}
}
bool KateCompletionModel::indexIsItem( const QModelIndex & index ) const
{
if (!hasGroups())
return true;
if (groupOfParent(index))
return true;
return false;
}
void KateCompletionModel::slotModelReset()
{
createGroups();
//debugStats();
}
void KateCompletionModel::debugStats()
{
if (!hasGroups())
kDebug( 13035 ) << "Model groupless, " << m_ungrouped->filtered.count() << " items.";
else {
kDebug( 13035 ) << "Model grouped (" << m_rowTable.count() << " groups):";
foreach (Group* g, m_rowTable)
kDebug( 13035 ) << "Group" << g << "count" << g->filtered.count();
}
}
bool KateCompletionModel::hasCompletionModel( ) const
{
return !m_completionModels.isEmpty();
}
void KateCompletionModel::setFilteringEnabled( bool enable )
{
if (m_filteringEnabled != enable)
m_filteringEnabled = enable;
}
void KateCompletionModel::setSortingEnabled( bool enable )
{
if (m_sortingEnabled != enable) {
m_sortingEnabled = enable;
beginResetModel();
resort();
endResetModel();
}
}
void KateCompletionModel::setGroupingEnabled(bool enable)
{
if (m_groupingEnabled != enable)
m_groupingEnabled = enable;
}
void KateCompletionModel::setColumnMergingEnabled(bool enable)
{
if (m_columnMergingEnabled != enable)
m_columnMergingEnabled = enable;
}
bool KateCompletionModel::isColumnMergingEnabled( ) const
{
return m_columnMergingEnabled;
}
bool KateCompletionModel::isGroupingEnabled( ) const
{
return m_groupingEnabled;
}
bool KateCompletionModel::isFilteringEnabled( ) const
{
return m_filteringEnabled;
}
bool KateCompletionModel::isSortingEnabled( ) const
{
return m_sortingEnabled;
}
QString KateCompletionModel::columnName( int column )
{
switch (column) {
case KTextEditor::CodeCompletionModel::Prefix:
return i18n("Prefix");
case KTextEditor::CodeCompletionModel::Icon:
return i18n("Icon");
case KTextEditor::CodeCompletionModel::Scope:
return i18n("Scope");
case KTextEditor::CodeCompletionModel::Name:
return i18n("Name");
case KTextEditor::CodeCompletionModel::Arguments:
return i18n("Arguments");
case KTextEditor::CodeCompletionModel::Postfix:
return i18n("Postfix");
}
return QString();
}
const QList< QList < int > > & KateCompletionModel::columnMerges( ) const
{
return m_columnMerges;
}
void KateCompletionModel::setColumnMerges( const QList< QList < int > > & columnMerges )
{
beginResetModel();
m_columnMerges = columnMerges;
endResetModel();
}
int KateCompletionModel::translateColumn( int sourceColumn ) const
{
if (m_columnMerges.isEmpty())
return sourceColumn;
/* Debugging - dump column merge list
QString columnMerge;
foreach (const QList<int>& list, m_columnMerges) {
columnMerge += '[';
foreach (int column, list) {
columnMerge += QString::number(column) + " ";
}
columnMerge += "] ";
}
kDebug( 13035 ) << columnMerge;*/
int c = 0;
foreach (const QList<int>& list, m_columnMerges) {
foreach (int column, list) {
if (column == sourceColumn)
return c;
}
c++;
}
return -1;
}
int KateCompletionModel::groupingAttributes( int attribute ) const
{
int ret = 0;
if (m_groupingMethod & ScopeType) {
if (countBits(attribute & ScopeTypeMask) > 1)
kWarning() << "Invalid completion model metadata: more than one scope type modifier provided.";
if (attribute & KTextEditor::CodeCompletionModel::GlobalScope)
ret |= KTextEditor::CodeCompletionModel::GlobalScope;
else if (attribute & KTextEditor::CodeCompletionModel::NamespaceScope)
ret |= KTextEditor::CodeCompletionModel::NamespaceScope;
else if (attribute & KTextEditor::CodeCompletionModel::LocalScope)
ret |= KTextEditor::CodeCompletionModel::LocalScope;
}
if (m_groupingMethod & AccessType) {
if (countBits(attribute & AccessTypeMask) > 1)
kWarning() << "Invalid completion model metadata: more than one access type modifier provided.";
if (attribute & KTextEditor::CodeCompletionModel::Public)
ret |= KTextEditor::CodeCompletionModel::Public;
else if (attribute & KTextEditor::CodeCompletionModel::Protected)
ret |= KTextEditor::CodeCompletionModel::Protected;
else if (attribute & KTextEditor::CodeCompletionModel::Private)
ret |= KTextEditor::CodeCompletionModel::Private;
if (accessIncludeStatic() && attribute & KTextEditor::CodeCompletionModel::Static)
ret |= KTextEditor::CodeCompletionModel::Static;
if (accessIncludeConst() && attribute & KTextEditor::CodeCompletionModel::Const)
ret |= KTextEditor::CodeCompletionModel::Const;
}
if (m_groupingMethod & ItemType) {
if (countBits(attribute & ItemTypeMask) > 1)
kWarning() << "Invalid completion model metadata: more than one item type modifier provided.";
if (attribute & KTextEditor::CodeCompletionModel::Namespace)
ret |= KTextEditor::CodeCompletionModel::Namespace;
else if (attribute & KTextEditor::CodeCompletionModel::Class)
ret |= KTextEditor::CodeCompletionModel::Class;
else if (attribute & KTextEditor::CodeCompletionModel::Struct)
ret |= KTextEditor::CodeCompletionModel::Struct;
else if (attribute & KTextEditor::CodeCompletionModel::Union)
ret |= KTextEditor::CodeCompletionModel::Union;
else if (attribute & KTextEditor::CodeCompletionModel::Function)
ret |= KTextEditor::CodeCompletionModel::Function;
else if (attribute & KTextEditor::CodeCompletionModel::Variable)
ret |= KTextEditor::CodeCompletionModel::Variable;
else if (attribute & KTextEditor::CodeCompletionModel::Enum)
ret |= KTextEditor::CodeCompletionModel::Enum;
/*
if (itemIncludeTemplate() && attribute & KTextEditor::CodeCompletionModel::Template)
ret |= KTextEditor::CodeCompletionModel::Template;*/
}
return ret;
}
void KateCompletionModel::setGroupingMethod( GroupingMethods m )
{
m_groupingMethod = m;
createGroups();
}
bool KateCompletionModel::accessIncludeConst( ) const
{
return m_accessConst;
}
void KateCompletionModel::setAccessIncludeConst( bool include )
{
if (m_accessConst != include) {
m_accessConst = include;
if (groupingMethod() & AccessType)
createGroups();
}
}
bool KateCompletionModel::accessIncludeStatic( ) const
{
return m_accessStatic;
}
void KateCompletionModel::setAccessIncludeStatic( bool include )
{
if (m_accessStatic != include) {
m_accessStatic = include;
if (groupingMethod() & AccessType)
createGroups();
}
}
bool KateCompletionModel::accessIncludeSignalSlot( ) const
{
return m_accesSignalSlot;
}
void KateCompletionModel::setAccessIncludeSignalSlot( bool include )
{
if (m_accesSignalSlot != include) {
m_accesSignalSlot = include;
if (groupingMethod() & AccessType)
createGroups();
}
}
int KateCompletionModel::countBits( int value ) const
{
int count = 0;
for (int i = 1; i; i <<= 1)
if (i & value)
count++;
return count;
}
KateCompletionModel::GroupingMethods KateCompletionModel::groupingMethod( ) const
{
return m_groupingMethod;
}
bool KateCompletionModel::isSortingByInheritanceDepth() const {
return m_isSortingByInheritance;
}
void KateCompletionModel::setSortingByInheritanceDepth(bool byInheritance) {
m_isSortingByInheritance = byInheritance;
}
bool KateCompletionModel::isSortingAlphabetical( ) const
{
return m_sortingAlphabetical;
}
Qt::CaseSensitivity KateCompletionModel::sortingCaseSensitivity( ) const
{
return m_sortingCaseSensitivity;
}
KateCompletionModel::Item::Item( bool doInitialMatch, KateCompletionModel* m, const HierarchicalModelHandler& handler, ModelRow sr )
: model(m)
, m_sourceRow(sr)
, matchCompletion(StartsWithMatch)
, matchFilters(true)
, m_haveExactMatch(false)
{
inheritanceDepth = handler.getData(CodeCompletionModel::InheritanceDepth, m_sourceRow.second).toInt();
QModelIndex nameSibling = sr.second.sibling(sr.second.row(), CodeCompletionModel::Name);
m_nameColumn = nameSibling.data(Qt::DisplayRole).toString();
if(doInitialMatch) {
filter();
match();
}
}
bool KateCompletionModel::Item::operator <( const Item & rhs ) const
{
int ret = 0;
//kDebug( 13035 ) << c1 << " c/w " << c2 << " -> " << (model->isSortingReverse() ? ret > 0 : ret < 0) << " (" << ret << ")";
const bool isBad = m_sourceRow.second.data(CodeCompletionModel::UnimportantItemRole).toBool();
const bool otherIsBad = rhs.m_sourceRow.second.data(CodeCompletionModel::UnimportantItemRole).toBool();
if( isBad && !otherIsBad ) {
return false;
}
if( otherIsBad && !isBad ) {
return true;
}
if( matchCompletion < rhs.matchCompletion ) {
// enums are ordered in the order items should be displayed
return true;
}
if( matchCompletion > rhs.matchCompletion ) {
return false;
}
if( model->isSortingByInheritanceDepth() )
ret = inheritanceDepth - rhs.inheritanceDepth;
if (ret == 0 && model->isSortingAlphabetical()) {
// Do not use localeAwareCompare, because it is simply too slow for a list of about 1000 items
ret = QString::compare(m_nameColumn, rhs.m_nameColumn, model->sortingCaseSensitivity());
}
if( ret == 0 ) {
const QString& filter = rhs.model->currentCompletion(rhs.m_sourceRow.first);
if( m_nameColumn.startsWith(filter, Qt::CaseSensitive) ) {
return true;
}
if( rhs.m_nameColumn.startsWith(filter, Qt::CaseSensitive) ) {
return false;
}
// FIXME need to define a better default ordering for multiple model display
ret = m_sourceRow.second.row() - rhs.m_sourceRow.second.row();
}
return ret < 0;
}
void KateCompletionModel::Group::addItem( Item i, bool notifyModel )
{
if (isEmpty)
notifyModel = false;
QModelIndex groupIndex;
if (notifyModel)
groupIndex = model->indexForGroup(this);
if (model->isSortingEnabled()) {
prefilter.insert(qUpperBound(prefilter.begin(), prefilter.end(), i), i);
if(i.isVisible()) {
QList<Item>::iterator it = qUpperBound(filtered.begin(), filtered.end(), i);
uint rowNumber = it - filtered.begin();
if(notifyModel)
model->beginInsertRows(groupIndex, rowNumber, rowNumber);
filtered.insert(it, i);
}
} else {
if(notifyModel)
model->beginInsertRows(groupIndex, prefilter.size(), prefilter.size());
if (i.isVisible())
prefilter.append(i);
}
if(notifyModel)
model->endInsertRows();
}
bool KateCompletionModel::Group::removeItem(const ModelRow& row)
{
for (int pi = 0; pi < prefilter.count(); ++pi)
if (prefilter[pi].sourceRow() == row) {
int index = rowOf(row);
if (index != -1)
model->beginRemoveRows(model->indexForGroup(this), index, index);
filtered.removeAt(index);
prefilter.removeAt(pi);
if (index != -1)
model->endRemoveRows();
return index != -1;
}
Q_ASSERT(false);
return false;
}
KateCompletionModel::Group::Group( KateCompletionModel * m )
: model(m)
, isEmpty(true)
, customSortingKey(-1)
{
Q_ASSERT(model);
}
void KateCompletionModel::setSortingAlphabetical( bool alphabetical )
{
if (m_sortingAlphabetical != alphabetical) {
m_sortingAlphabetical = alphabetical;
beginResetModel();
resort();
endResetModel();
}
}
void KateCompletionModel::Group::resort( )
{
qStableSort(filtered.begin(), filtered.end());
model->hideOrShowGroup(this);
}
void KateCompletionModel::setSortingCaseSensitivity( Qt::CaseSensitivity cs )
{
if (m_sortingCaseSensitivity != cs) {
m_sortingCaseSensitivity = cs;
beginResetModel();
resort();
endResetModel();
}
}
void KateCompletionModel::resort()
{
foreach (Group* g, m_rowTable)
g->resort();
foreach (Group* g, m_emptyGroups)
g->resort();
// call updateBestMatches here, so they are moved to the top again.
updateBestMatches();
}
bool KateCompletionModel::Item::isValid( ) const
{
return model && m_sourceRow.first && m_sourceRow.second.row() >= 0;
}
void KateCompletionModel::Group::clear( )
{
prefilter.clear();
filtered.clear();
isEmpty = true;
}
bool KateCompletionModel::filterContextMatchesOnly( ) const
{
return m_filterContextMatchesOnly;
}
void KateCompletionModel::setFilterContextMatchesOnly( bool filter )
{
if (m_filterContextMatchesOnly != filter) {
m_filterContextMatchesOnly = filter;
refilter();
}
}
bool KateCompletionModel::filterByAttribute( ) const
{
return m_filterByAttribute;
}
void KateCompletionModel::setFilterByAttribute( bool filter )
{
if (m_filterByAttribute == filter) {
m_filterByAttribute = filter;
refilter();
}
}
KTextEditor::CodeCompletionModel::CompletionProperties KateCompletionModel::filterAttributes( ) const
{
return m_filterAttributes;
}
void KateCompletionModel::setFilterAttributes( KTextEditor::CodeCompletionModel::CompletionProperties attributes )
{
if (m_filterAttributes == attributes) {
m_filterAttributes = attributes;
refilter();
}
}
int KateCompletionModel::maximumInheritanceDepth( ) const
{
return m_maximumInheritanceDepth;
}
void KateCompletionModel::setMaximumInheritanceDepth( int maxDepth )
{
if (m_maximumInheritanceDepth != maxDepth) {
m_maximumInheritanceDepth = maxDepth;
refilter();
}
}
void KateCompletionModel::refilter( )
{
beginResetModel();
m_ungrouped->refilter();
foreach (Group* g, m_rowTable)
if(g != m_argumentHints)
g->refilter();
foreach (Group* g, m_emptyGroups)
if(g != m_argumentHints)
g->refilter();
updateBestMatches();
clearExpanding(); //We need to do this, or be aware of expanding-widgets while filtering.
endResetModel();
}
void KateCompletionModel::Group::refilter( )
{
filtered.clear();
foreach (const Item& i, prefilter)
if (!i.isFiltered())
filtered.append(i);
}
bool KateCompletionModel::Item::filter( )
{
matchFilters = false;
if (model->isFilteringEnabled()) {
QModelIndex sourceIndex = m_sourceRow.second.sibling(m_sourceRow.second.row(), CodeCompletionModel::Name);
if (model->filterContextMatchesOnly()) {
QVariant contextMatch = sourceIndex.data(CodeCompletionModel::MatchQuality);
if (contextMatch.canConvert(QVariant::Int) && !contextMatch.toInt())
goto filter;
}
if (model->filterByAttribute()) {
int completionFlags = sourceIndex.data(CodeCompletionModel::CompletionRole).toInt();
if (model->filterAttributes() & completionFlags)
goto filter;
}
if (model->maximumInheritanceDepth() > 0) {
int inheritanceDepth = sourceIndex.data(CodeCompletionModel::InheritanceDepth).toInt();
if (inheritanceDepth > model->maximumInheritanceDepth())
goto filter;
}
}
matchFilters = true;
filter:
return matchFilters;
}
uint KateCompletionModel::filteredItemCount() const
{
uint ret = 0;
foreach(Group* group, m_rowTable)
ret += group->filtered.size();
return ret;
}
bool KateCompletionModel::shouldMatchHideCompletionList() const {
// @todo Make this faster
bool doHide = false;
CodeCompletionModel* hideModel = 0;
foreach(Group* group, m_rowTable)
foreach(const Item& item, group->filtered)
if(item.haveExactMatch()) {
KTextEditor::CodeCompletionModelControllerInterface* iface3 = dynamic_cast<KTextEditor::CodeCompletionModelControllerInterface*>(item.sourceRow().first);
bool hide = false;
if ( !iface3 ) hide = true;
if(iface3 && iface3->matchingItem(item.sourceRow().second) == KTextEditor::CodeCompletionModelControllerInterface::HideListIfAutomaticInvocation)
hide = true;
if(hide)
{
doHide = true;
hideModel = item.sourceRow().first;
}
}
if(doHide)
{
// Check if all other visible items are from the same model
foreach(Group* group, m_rowTable)
foreach(const Item& item, group->filtered)
if(item.sourceRow().first != hideModel)
return false;
}
return doHide;
}
static inline bool matchesAbbreviationHelper(const QString& word, const QString& typed, const std::vector<int>& offsets,
int& depth, int atWord = -1, int i = 0) {
int atLetter = 1;
for ( ; i < typed.size(); i++ ) {
const QChar c = typed.at(i).toLower();
bool haveNextWord = offsets.size() > atWord + 1;
bool canCompare = atWord != -1 && word.size() > offsets.at(atWord) + atLetter;
if ( canCompare && c == word.at(offsets.at(atWord) + atLetter).toLower() ) {
// the typed letter matches a letter after the current word beginning
if ( ! haveNextWord || c != word.at(offsets.at(atWord + 1)).toLower() ) {
// good, simple case, no conflict
atLetter += 1;
continue;
}
// For maliciously crafted data, the code used here theoretically can have very high
// complexity. Thus ensure we don't run into this case, by limiting the amount of branches
// we walk through to 128.
depth++;
if ( depth > 128 ) {
return false;
}
// the letter matches both the next word beginning and the next character in the word
if ( haveNextWord && matchesAbbreviationHelper(word, typed, offsets, depth, atWord + 1, i + 1) ) {
// resolving the conflict by taking the next word's first character worked, fine
return true;
}
// otherwise, continue by taking the next letter in the current word.
atLetter += 1;
continue;
}
else if ( haveNextWord && c == word.at(offsets.at(atWord + 1)).toLower() ) {
// the typed letter matches the next word beginning
atWord++;
atLetter = 1;
continue;
}
// no match
return false;
}
// all characters of the typed word were matched
return true;
}
bool KateCompletionModel::matchesAbbreviation(const QString& word, const QString& typed)
{
// A mismatch is very likely for random even for the first letter,
// thus this optimization makes sense.
if ( word.at(0).toLower() != typed.at(0).toLower() ) {
return false;
}
// First, check if all letters are contained in the word in the right order.
int atLetter = 0;
foreach ( const QChar c, typed ) {
while ( c.toLower() != word.at(atLetter).toLower() ) {
atLetter += 1;
if ( atLetter >= word.size() ) {
return false;
}
}
}
bool haveUnderscore = true;
std::vector<int> offsets;
// We want to make "KComplM" match "KateCompletionModel"; this means we need
// to allow parts of the typed text to be not part of the actual abbreviation,
// which consists only of the uppercased / underscored letters (so "KCM" in this case).
// However it might be ambigous whether a letter is part of such a word or part of
// the following abbreviation, so we need to find all possible word offsets first,
// then compare.
for ( int i = 0; i < word.size(); i++ ) {
const QChar c = word.at(i);
if ( c == QLatin1Char('_') ) {
haveUnderscore = true;
} else if ( haveUnderscore || c.isUpper() ) {
offsets.push_back(i);
haveUnderscore = false;
}
}
int depth = 0;
return matchesAbbreviationHelper(word, typed, offsets, depth);
}
static inline bool containsAtWordBeginning(const QString& word, const QString& typed, Qt::CaseSensitivity caseSensitive) {
for(int i = 1; i < word.size(); i++) {
// The current position is a word beginning if the previous character was an underscore
// or if the current character is uppercase. Subsequent uppercase characters do not count,
// to handle the special case of UPPER_CASE_VARS properly.
const QChar c = word.at(i);
const QChar prev = word.at(i-1);
if(!(prev == QLatin1Char('_') || (c.isUpper() && !prev.isUpper()))) {
continue;
}
if(word.midRef(i).startsWith(typed, caseSensitive)) {
return true;
}
}
return false;
};
KateCompletionModel::Item::MatchType KateCompletionModel::Item::match()
{
// Check to see if the item is matched by the current completion string
QModelIndex sourceIndex = m_sourceRow.second.sibling(m_sourceRow.second.row(), CodeCompletionModel::Name);
QString match = model->currentCompletion(m_sourceRow.first);
m_haveExactMatch = false;
// Hehe, everything matches nothing! (ie. everything matches a blank string)
if (match.isEmpty())
return PerfectMatch;
if (m_nameColumn.isEmpty())
return NoMatch;
matchCompletion = (m_nameColumn.startsWith(match, model->matchCaseSensitivity()) ? StartsWithMatch : NoMatch);
if(matchCompletion == NoMatch) {
// if no match, try for "contains"
// Only match when the occurence is at a "word" beginning, marked by
// an underscore or a capital. So Foo matches BarFoo and Bar_Foo, but not barfoo.
// Starting at 1 saves looking at the beginning of the word, that was already checked above.
if(containsAtWordBeginning(m_nameColumn, match, model->matchCaseSensitivity())) {
matchCompletion = ContainsMatch;
}
}
if(matchCompletion == NoMatch && !m_nameColumn.isEmpty() && !match.isEmpty())
{
// if still no match, try abbreviation matching
if(matchesAbbreviation(m_nameColumn, match)) {
matchCompletion = AbbreviationMatch;
}
}
if(matchCompletion && match.length() == m_nameColumn.length()) {
matchCompletion = PerfectMatch;
m_haveExactMatch = true;
}
return matchCompletion;
}
QString KateCompletionModel::propertyName( KTextEditor::CodeCompletionModel::CompletionProperty property )
{
switch (property) {
case CodeCompletionModel::Public:
return i18n("Public");
case CodeCompletionModel::Protected:
return i18n("Protected");
case CodeCompletionModel::Private:
return i18n("Private");
case CodeCompletionModel::Static:
return i18n("Static");
case CodeCompletionModel::Const:
return i18n("Constant");
case CodeCompletionModel::Namespace:
return i18n("Namespace");
case CodeCompletionModel::Class:
return i18n("Class");
case CodeCompletionModel::Struct:
return i18n("Struct");
case CodeCompletionModel::Union:
return i18n("Union");
case CodeCompletionModel::Function:
return i18n("Function");
case CodeCompletionModel::Variable:
return i18n("Variable");
case CodeCompletionModel::Enum:
return i18n("Enumeration");
case CodeCompletionModel::Template:
return i18n("Template");
case CodeCompletionModel::Virtual:
return i18n("Virtual");
case CodeCompletionModel::Override:
return i18n("Override");
case CodeCompletionModel::Inline:
return i18n("Inline");
case CodeCompletionModel::Friend:
return i18n("Friend");
case CodeCompletionModel::Signal:
return i18n("Signal");
case CodeCompletionModel::Slot:
return i18n("Slot");
case CodeCompletionModel::LocalScope:
return i18n("Local Scope");
case CodeCompletionModel::NamespaceScope:
return i18n("Namespace Scope");
case CodeCompletionModel::GlobalScope:
return i18n("Global Scope");
default:
return i18n("Unknown Property");
}
}
bool KateCompletionModel::Item::isVisible( ) const
{
return matchCompletion && matchFilters;
}
bool KateCompletionModel::Item::isFiltered( ) const
{
return !matchFilters;
}
bool KateCompletionModel::Item::isMatching( ) const
{
return matchFilters;
}
const KateCompletionModel::ModelRow& KateCompletionModel::Item::sourceRow( ) const
{
return m_sourceRow;
}
QString KateCompletionModel::currentCompletion( KTextEditor::CodeCompletionModel* model ) const
{
return m_currentMatch.value(model);
}
Qt::CaseSensitivity KateCompletionModel::matchCaseSensitivity( ) const
{
return m_matchCaseSensitivity;
}
void KateCompletionModel::addCompletionModel(KTextEditor::CodeCompletionModel * model)
{
if (m_completionModels.contains(model))
return;
m_completionModels.append(model);
connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(slotRowsInserted(QModelIndex,int,int)));
connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(slotRowsRemoved(QModelIndex,int,int)));
connect(model, SIGNAL(modelReset()), SLOT(slotModelReset()));
// This performs the reset
createGroups();
}
void KateCompletionModel::setCompletionModel(KTextEditor::CodeCompletionModel* model)
{
clearCompletionModels();
addCompletionModel(model);
}
void KateCompletionModel::setCompletionModels(const QList<KTextEditor::CodeCompletionModel*>& models)
{
//if (m_completionModels == models)
//return;
clearCompletionModels();
m_completionModels = models;
foreach (KTextEditor::CodeCompletionModel* model, models) {
connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(slotRowsInserted(QModelIndex,int,int)));
connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(slotRowsRemoved(QModelIndex,int,int)));
connect(model, SIGNAL(modelReset()), SLOT(slotModelReset()));
}
// This performs the reset
createGroups();
}
QList< KTextEditor::CodeCompletionModel * > KateCompletionModel::completionModels() const
{
return m_completionModels;
}
void KateCompletionModel::removeCompletionModel(CodeCompletionModel * model)
{
if (!model || !m_completionModels.contains(model))
return;
beginResetModel();
m_currentMatch.remove(model);
clearGroups();
model->disconnect(this);
m_completionModels.removeAll(model);
endResetModel();
if (!m_completionModels.isEmpty()) {
// This performs the reset
createGroups();
}
}
void KateCompletionModel::makeGroupItemsUnique(bool onlyFiltered)
{
struct FilterItems {
FilterItems(KateCompletionModel& model, const QVector<KTextEditor::CodeCompletionModel*>& needShadowing) : m_model(model), m_needShadowing(needShadowing) {
}
QHash<QString, CodeCompletionModel*> had;
KateCompletionModel& m_model;
const QVector< KTextEditor::CodeCompletionModel* > m_needShadowing;
void filter(QList<Item>& items)
{
QList<Item> temp;
foreach(const Item& item, items)
{
QHash<QString, CodeCompletionModel*>::const_iterator it = had.constFind(item.name());
if(it != had.constEnd() && *it != item.sourceRow().first && m_needShadowing.contains(item.sourceRow().first))
continue;
had.insert(item.name(), item.sourceRow().first);
temp.push_back(item);
}
items = temp;
}
void filter(Group* group, bool onlyFiltered)
{
if(group->prefilter.size() == group->filtered.size())
{
// Filter only once
filter(group->filtered);
if(!onlyFiltered)
group->prefilter = group->filtered;
}else{
// Must filter twice
filter(group->filtered);
if(!onlyFiltered)
filter(group->prefilter);
}
if(group->filtered.isEmpty())
m_model.hideOrShowGroup(group);
}
};
QVector<KTextEditor::CodeCompletionModel*> needShadowing;
foreach(KTextEditor::CodeCompletionModel* model, m_completionModels)
{
KTextEditor::CodeCompletionModelControllerInterface4* v4 = dynamic_cast<KTextEditor::CodeCompletionModelControllerInterface4*>(model);
if(v4 && v4->shouldHideItemsWithEqualNames())
needShadowing.push_back(model);
}
if(needShadowing.isEmpty())
return;
FilterItems filter(*this, needShadowing);
filter.filter(m_ungrouped, onlyFiltered);
foreach(Group* group, m_rowTable)
filter.filter(group, onlyFiltered);
}
//Updates the best-matches group
void KateCompletionModel::updateBestMatches() {
int maxMatches = 300; //We cannot do too many operations here, because they are all executed whenever a character is added. Would be nice if we could split the operations up somewhat using a timer.
m_updateBestMatchesTimer->stop();
//Maps match-qualities to ModelRows paired together with the BestMatchesCount returned by the items.
typedef QMultiMap<int, QPair<int, ModelRow> > BestMatchMap;
BestMatchMap matches;
if(!hasGroups()) {
//If there is no grouping, just change the order of the items, moving the best matching ones to the front
QMultiMap<int, int> rowsForQuality;
int row = 0;
foreach(const Item& item, m_ungrouped->filtered) {
ModelRow source = item.sourceRow();
QVariant v = source.second.data(CodeCompletionModel::BestMatchesCount);
if( v.type() == QVariant::Int && v.toInt() > 0 ) {
int quality = contextMatchQuality(source);
if(quality > 0)
rowsForQuality.insert(quality, row);
}
++row;
--maxMatches;
if(maxMatches < 0)
break;
}
if(!rowsForQuality.isEmpty()) {
//Rewrite m_ungrouped->filtered in a new order
QSet<int> movedToFront;
QList<Item> newFiltered;
for(QMultiMap<int, int>::const_iterator it = rowsForQuality.constBegin(); it != rowsForQuality.constEnd(); ++it) {
newFiltered.prepend(m_ungrouped->filtered[it.value()]);
movedToFront.insert(it.value());
}
{
uint size = m_ungrouped->filtered.size();
for(uint a = 0; a < size; ++a)
if(!movedToFront.contains(a))
newFiltered.append(m_ungrouped->filtered[a]);
}
m_ungrouped->filtered = newFiltered;
}
return;
}
///@todo Cache the CodeCompletionModel::BestMatchesCount
foreach (Group* g, m_rowTable) {
if( g == m_bestMatches )
continue;
for( int a = 0; a < g->filtered.size(); a++ )
{
ModelRow source = g->filtered[a].sourceRow();
QVariant v = source.second.data(CodeCompletionModel::BestMatchesCount);
if( v.type() == QVariant::Int && v.toInt() > 0 ) {
//Return the best match with any of the argument-hints
int quality = contextMatchQuality(source);
if( quality > 0 )
matches.insert(quality, qMakePair(v.toInt(), g->filtered[a].sourceRow()));
--maxMatches;
}
if( maxMatches < 0 )
break;
}
if( maxMatches < 0 )
break;
}
//Now choose how many of the matches will be taken. This is done with the rule:
//The count of shown best-matches should equal the average count of their BestMatchesCounts
int cnt = 0;
int matchesSum = 0;
BestMatchMap::const_iterator it = matches.constEnd();
while( it != matches.constBegin() )
{
--it;
++cnt;
matchesSum += (*it).first;
if( cnt > matchesSum / cnt )
break;
}
m_bestMatches->filtered.clear();
it = matches.constEnd();
while( it != matches.constBegin() && cnt > 0 )
{
--it;
--cnt;
m_bestMatches->filtered.append( Item( true, this, HierarchicalModelHandler((*it).second.first), (*it).second) );
}
hideOrShowGroup(m_bestMatches);
}
void KateCompletionModel::rowSelected(const QModelIndex& row) {
ExpandingWidgetModel::rowSelected(row);
///@todo delay this
int rc = widget()->argumentHintModel()->rowCount(QModelIndex());
if( rc == 0 ) return;
//For now, simply update the whole column 0
QModelIndex start = widget()->argumentHintModel()->index(0,0);
QModelIndex end = widget()->argumentHintModel()->index(rc-1,0);
widget()->argumentHintModel()->emitDataChanged(start, end);
}
void KateCompletionModel::clearCompletionModels()
{
if (m_completionModels.isEmpty())
return;
beginResetModel();
foreach (CodeCompletionModel * model, m_completionModels)
model->disconnect(this);
m_completionModels.clear();
m_currentMatch.clear();
clearGroups();
endResetModel();
}
#include "moc_katecompletionmodel.cpp"
// kate: space-indent on; indent-width 2; replace-tabs on;