• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

Kate

katewordcompletion.cpp

Go to the documentation of this file.
00001 /*  This file is part of the KDE libraries and the Kate part.
00002  *
00003  *  Copyright (C) 2003 Anders Lund <anders.lund@lund.tdcadsl.dk>
00004  *  Copyright (C) 2010 Christoph Cullmann <cullmann@kde.org>
00005  *
00006  *  This library is free software; you can redistribute it and/or
00007  *  modify it under the terms of the GNU Library General Public
00008  *  License as published by the Free Software Foundation; either
00009  *  version 2 of the License, or (at your option) any later version.
00010  *
00011  *  This library is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014  *  Library General Public License for more details.
00015  *
00016  *  You should have received a copy of the GNU Library General Public License
00017  *  along with this library; see the file COPYING.LIB.  If not, write to
00018  *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00019  *  Boston, MA 02110-1301, USA.
00020  */
00021 
00022 //BEGIN includes
00023 #include "katewordcompletion.h"
00024 #include "kateview.h"
00025 #include "kateconfig.h"
00026 #include "katedocument.h"
00027 #include "kateglobal.h"
00028 
00029 #include <ktexteditor/variableinterface.h>
00030 #include <ktexteditor/movingrange.h>
00031 
00032 #include <kconfig.h>
00033 #include <kdialog.h>
00034 #include <kpluginfactory.h>
00035 #include <klocale.h>
00036 #include <kaction.h>
00037 #include <kactioncollection.h>
00038 #include <knotification.h>
00039 #include <kparts/part.h>
00040 #include <kiconloader.h>
00041 #include <kpagedialog.h>
00042 #include <kpagewidgetmodel.h>
00043 #include <ktoggleaction.h>
00044 #include <kconfiggroup.h>
00045 #include <kcolorscheme.h>
00046 #include <kaboutdata.h>
00047 
00048 #include <QtCore/QRegExp>
00049 #include <QtCore/QString>
00050 #include <QtCore/QSet>
00051 #include <QtGui/QSpinBox>
00052 #include <QtGui/QLabel>
00053 #include <QtGui/QLayout>
00054 
00055 #include <kvbox.h>
00056 #include <QtGui/QCheckBox>
00057 
00058 #include <kdebug.h>
00059 //END
00060 
00061 //BEGIN KateWordCompletionModel
00062 KateWordCompletionModel::KateWordCompletionModel( QObject *parent )
00063   : CodeCompletionModel( parent ), m_automatic(false)
00064 {
00065   setHasGroups(false);
00066 }
00067 
00068 KateWordCompletionModel::~KateWordCompletionModel()
00069 {
00070 }
00071 
00072 void KateWordCompletionModel::saveMatches( KTextEditor::View* view,
00073                         const KTextEditor::Range& range)
00074 {
00075   m_matches = allMatches( view, range );
00076   m_matches.sort();
00077 }
00078 
00079 QVariant KateWordCompletionModel::data(const QModelIndex& index, int role) const
00080 {
00081   if( role == InheritanceDepth )
00082     return 10000; //Very high value, so the word-completion group and items are shown behind any other groups/items if there is multiple
00083 
00084   if( !index.parent().isValid() ) {
00085     //It is the group header
00086     switch ( role )
00087     {
00088       case Qt::DisplayRole:
00089         return i18n("Auto Word Completion");
00090       case GroupRole:
00091         return Qt::DisplayRole;
00092     }
00093   }
00094 
00095   if( index.column() == KTextEditor::CodeCompletionModel::Name && role == Qt::DisplayRole )
00096     return m_matches.at( index.row() );
00097 
00098   if( index.column() == KTextEditor::CodeCompletionModel::Icon && role == Qt::DecorationRole ) {
00099     static QIcon icon(KIcon("insert-text").pixmap(QSize(16, 16)));
00100     return icon;
00101   }
00102 
00103   return QVariant();
00104 }
00105 
00106 QModelIndex KateWordCompletionModel::parent(const QModelIndex& index) const
00107 {
00108   if(index.internalId())
00109     return createIndex(0, 0, 0);
00110   else
00111     return QModelIndex();
00112 }
00113 
00114 QModelIndex KateWordCompletionModel::index(int row, int column, const QModelIndex& parent) const
00115 {
00116   if( !parent.isValid()) {
00117     if(row == 0)
00118       return createIndex(row, column, 0);
00119     else
00120       return QModelIndex();
00121 
00122   }else if(parent.parent().isValid())
00123     return QModelIndex();
00124 
00125 
00126   if (row < 0 || row >= m_matches.count() || column < 0 || column >= ColumnCount )
00127     return QModelIndex();
00128 
00129   return createIndex(row, column, 1);
00130 }
00131 
00132 int KateWordCompletionModel::rowCount ( const QModelIndex & parent ) const
00133 {
00134   if( !parent.isValid() && !m_matches.isEmpty() )
00135     return 1; //One root node to define the custom group
00136   else if(parent.parent().isValid())
00137     return 0; //Completion-items have no children
00138   else
00139     return m_matches.count();
00140 }
00141 
00142 
00143 bool KateWordCompletionModel::shouldStartCompletion(KTextEditor::View* view, const QString &insertedText, bool userInsertion, const KTextEditor::Cursor &position)
00144 {
00145     if (!userInsertion) return false;
00146     if(insertedText.isEmpty())
00147         return false;
00148 
00149 
00150     KateView *v = qobject_cast<KateView*> (view);
00151 
00152     QString text = view->document()->line(position.line()).left(position.column());
00153     uint check=v->config()->wordCompletionMinimalWordLength();
00154     if (check<=0) return true;
00155     int start=text.length();
00156     int end=text.length()-check;
00157     if (end<0) return false;
00158     for (int i=start-1;i>=end;i--) {
00159       QChar c=text.at(i);
00160       if (! (c.isLetter() || (c.isNumber()) || c=='_') ) return false;
00161     }
00162 
00163     return true;
00164 }
00165 
00166 bool KateWordCompletionModel::shouldAbortCompletion(KTextEditor::View* view, const KTextEditor::Range &range, const QString &currentCompletion) {
00167 
00168     if (m_automatic) {
00169       KateView *v = qobject_cast<KateView*> (view);
00170       if (currentCompletion.length()<v->config()->wordCompletionMinimalWordLength()) return true;
00171     }
00172 
00173     return CodeCompletionModelControllerInterface4::shouldAbortCompletion(view,range,currentCompletion);
00174 }
00175 
00176 
00177 
00178 void KateWordCompletionModel::completionInvoked(KTextEditor::View* view, const KTextEditor::Range& range, InvocationType it)
00179 {
00183   m_automatic=false;
00184   if (it==AutomaticInvocation) {
00185       m_automatic=true;
00186       KateView *v = qobject_cast<KateView*> (view);
00187 
00188       if (range.columnWidth() >= v->config()->wordCompletionMinimalWordLength())
00189         saveMatches( view, range );
00190       else
00191         m_matches.clear();
00192 
00193       // done here...
00194       return;
00195   }
00196 
00197   // normal case ;)
00198   saveMatches( view, range );
00199 }
00200 
00201 
00202 // Scan throughout the entire document for possible completions,
00203 // ignoring any dublets
00204 const QStringList KateWordCompletionModel::allMatches( KTextEditor::View *view, const KTextEditor::Range &range ) const
00205 {
00206   QStringList l;
00207 
00208   int i( 0 );
00209   int pos( 0 );
00210   KTextEditor::Document *doc = view->document();
00211   QRegExp re( "\\b(" + doc->text( range ) + "\\w{1,})" );
00212   QString s, m;
00213   QSet<QString> seen;
00214 
00215   while( i < doc->lines() )
00216   {
00217     s = doc->line( i );
00218     pos = 0;
00219     while ( pos >= 0 )
00220     {
00221       pos = re.indexIn( s, pos );
00222       if ( pos >= 0 )
00223       {
00224         // typing in the middle of a word
00225         if ( ! ( i == range.start().line() && pos == range.start().column() ) )
00226         {
00227           m = re.cap( 1 );
00228           if ( ! seen.contains( m ) ) {
00229             seen.insert( m );
00230             l << m;
00231           }
00232         }
00233         pos += re.matchedLength();
00234       }
00235     }
00236     i++;
00237   }
00238   return l;
00239 }
00240 
00241 KTextEditor::CodeCompletionModelControllerInterface3::MatchReaction KateWordCompletionModel::matchingItem(const QModelIndex& /*matched*/)
00242 {
00243   return HideListIfAutomaticInvocation;
00244 }
00245 
00246 bool KateWordCompletionModel::shouldHideItemsWithEqualNames() const
00247 {
00248   // We don't want word-completion items if the same items
00249   // are available through more sophisticated completion models
00250   return true;
00251 }
00252 
00253 //END KateWordCompletionModel
00254 
00255 
00256 //BEGIN KateWordCompletionView
00257 struct KateWordCompletionViewPrivate
00258 {
00259   KTextEditor::MovingRange* liRange;       // range containing last inserted text
00260   KTextEditor::Range dcRange;  // current range to be completed by directional completion
00261   KTextEditor::Cursor dcCursor;     // directional completion search cursor
00262   QRegExp re;           // hrm
00263   int directionalPos;   // be able to insert "" at the correct time
00264   bool isCompleting; // true when the directional completion is doing a completion
00265 };
00266 
00267 KateWordCompletionView::KateWordCompletionView( KTextEditor::View *view, KActionCollection* ac )
00268   : QObject( view ),
00269     m_view( view ),
00270     m_dWCompletionModel( KateGlobal::self()->wordCompletionModel() ),
00271     d( new KateWordCompletionViewPrivate )
00272 {
00273   d->isCompleting = false;
00274   d->dcRange = KTextEditor::Range::invalid();
00275 
00276   d->liRange = static_cast<KateDocument*>(m_view->document())->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand);
00277 
00278   KColorScheme colors(QPalette::Active);
00279   KTextEditor::Attribute::Ptr a = KTextEditor::Attribute::Ptr( new KTextEditor::Attribute() );
00280   a->setBackground( colors.background(KColorScheme::ActiveBackground) );
00281   a->setForeground( colors.foreground(KColorScheme::ActiveText) ); // ### this does 0
00282   d->liRange->setAttribute( a );
00283 
00284   KTextEditor::CodeCompletionInterface *cci = qobject_cast<KTextEditor::CodeCompletionInterface *>(view);
00285 
00286   KAction *action;
00287 
00288   if (cci)
00289   {
00290     cci->registerCompletionModel( m_dWCompletionModel );
00291 
00292     action = new KAction( i18n("Shell Completion"), this );
00293     ac->addAction( "doccomplete_sh", action );
00294     connect( action, SIGNAL( triggered() ), this, SLOT(shellComplete()) );
00295   }
00296 
00297 
00298   action = new KAction( i18n("Reuse Word Above"), this );
00299   ac->addAction( "doccomplete_bw", action );
00300   action->setShortcut( Qt::CTRL+Qt::Key_8 );
00301   connect( action, SIGNAL( triggered() ), this, SLOT(completeBackwards()) );
00302 
00303   action = new KAction( i18n("Reuse Word Below"), this );
00304   ac->addAction( "doccomplete_fw", action );
00305   action->setShortcut( Qt::CTRL+Qt::Key_9 );
00306   connect( action, SIGNAL( triggered() ), this, SLOT(completeForwards()) );
00307 }
00308 
00309 KateWordCompletionView::~KateWordCompletionView()
00310 {
00311   KTextEditor::CodeCompletionInterface *cci = qobject_cast<KTextEditor::CodeCompletionInterface *>(m_view);
00312 
00313   if (cci) cci->unregisterCompletionModel(m_dWCompletionModel);
00314 
00315   delete d;
00316 }
00317 
00318 void KateWordCompletionView::completeBackwards()
00319 {
00320   complete( false );
00321 }
00322 
00323 void KateWordCompletionView::completeForwards()
00324 {
00325   complete();
00326 }
00327 
00328 // Pop up the editors completion list if applicable
00329 void KateWordCompletionView::popupCompletionList()
00330 {
00331   kDebug( 13040 ) << "entered ...";
00332   KTextEditor::Range r = range();
00333 
00334   KTextEditor::CodeCompletionInterface *cci = qobject_cast<KTextEditor::CodeCompletionInterface *>( m_view );
00335   if(!cci || cci->isCompletionActive())
00336     return;
00337 
00338   m_dWCompletionModel->saveMatches( m_view, r );
00339 
00340   kDebug( 13040 ) << "after save matches ...";
00341 
00342   if ( ! m_dWCompletionModel->rowCount(QModelIndex()) ) return;
00343 
00344   cci->startCompletion( r, m_dWCompletionModel );
00345 }
00346 
00347 // Contributed by <brain@hdsnet.hu>
00348 void KateWordCompletionView::shellComplete()
00349 {
00350   KTextEditor::Range r = range();
00351 
00352   QStringList matches = m_dWCompletionModel->allMatches( m_view, r );
00353 
00354   if (matches.size() == 0)
00355     return;
00356 
00357   QString partial = findLongestUnique( matches, r.columnWidth() );
00358 
00359   if ( ! partial.length() )
00360     popupCompletionList();
00361 
00362   else
00363   {
00364     m_view->document()->insertText( r.end(), partial.mid( r.columnWidth() ) );
00365     d->liRange->setView(m_view);
00366     d->liRange->setRange( KTextEditor::Range( r.end(), partial.length() - r.columnWidth() ) );
00367     connect( m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*, const KTextEditor::Cursor&)), this, SLOT(slotCursorMoved()) );
00368   }
00369 }
00370 
00371 // Do one completion, searching in the desired direction,
00372 // if possible
00373 void KateWordCompletionView::complete( bool fw )
00374 {
00375   KTextEditor::Range r = range();
00376 
00377   int inc = fw ? 1 : -1;
00378   KTextEditor::Document *doc = m_view->document();
00379 
00380   if ( d->dcRange.isValid() )
00381   {
00382     //kDebug( 13040 )<<"CONTINUE "<<d->dcRange;
00383     // this is a repeted activation
00384 
00385     // if we are back to where we started, reset.
00386     if ( ( fw && d->directionalPos == -1 ) ||
00387          ( !fw && d->directionalPos == 1 ) )
00388     {
00389       const int spansColumns = d->liRange->end().column() - d->liRange->start().column();
00390       if ( spansColumns > 0 )
00391         doc->removeText( *d->liRange );
00392 
00393       d->liRange->setRange( KTextEditor::Range::invalid()  );
00394       d->dcCursor = r.end();
00395       d->directionalPos = 0;
00396 
00397       return;
00398     }
00399 
00400     if ( fw ) {
00401       const int spansColumns = d->liRange->end().column() - d->liRange->start().column();
00402       d->dcCursor.setColumn( d->dcCursor.column() + spansColumns );
00403     }
00404 
00405     d->directionalPos += inc;
00406   }
00407   else // new completion, reset all
00408   {
00409     //kDebug( 13040 )<<"RESET FOR NEW";
00410     d->dcRange = r;
00411     d->liRange->setRange( KTextEditor::Range::invalid() );
00412     d->dcCursor = r.start();
00413     d->directionalPos = inc;
00414 
00415     d->liRange->setView( m_view );
00416 
00417     connect( m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*, const KTextEditor::Cursor&)), this, SLOT(slotCursorMoved()) );
00418 
00419   }
00420 
00421   d->re.setPattern( "\\b" + doc->text( d->dcRange ) + "(\\w+)" );
00422   int pos ( 0 );
00423   QString ln = doc->line( d->dcCursor.line() );
00424 
00425   while ( true )
00426   {
00427     //kDebug( 13040 )<<"SEARCHING FOR "<<d->re.pattern()<<" "<<ln<<" at "<<d->dcCursor;
00428     pos = fw ?
00429       d->re.indexIn( ln, d->dcCursor.column() ) :
00430       d->re.lastIndexIn( ln, d->dcCursor.column() );
00431 
00432     if ( pos > -1 ) // we matched a word
00433     {
00434       //kDebug( 13040 )<<"USABLE MATCH";
00435       QString m = d->re.cap( 1 );
00436       if ( m != doc->text( *d->liRange ) && (d->dcCursor.line() != d->dcRange.start().line() || pos != d->dcRange.start().column() ) )
00437       {
00438         // we got good a match! replace text and return.
00439         d->isCompleting = true;
00440         KTextEditor::Range replaceRange(d->liRange->toRange());
00441         if (!replaceRange.isValid()) {
00442           replaceRange.setRange(r.end(), r.end());
00443         }
00444         doc->replaceText( replaceRange, m );
00445         d->liRange->setRange( KTextEditor::Range( d->dcRange.end(), m.length() ) );
00446 
00447         d->dcCursor.setColumn( pos ); // for next try
00448 
00449         d->isCompleting = false;
00450         return;
00451       }
00452 
00453       // equal to last one, continue
00454       else
00455       {
00456         //kDebug( 13040 )<<"SKIPPING, EQUAL MATCH";
00457         d->dcCursor.setColumn( pos ); // for next try
00458 
00459         if ( fw )
00460           d->dcCursor.setColumn( pos + m.length() );
00461 
00462         else
00463         {
00464           if ( pos == 0 )
00465           {
00466             if ( d->dcCursor.line() > 0 )
00467             {
00468               int l = d->dcCursor.line() + inc;
00469               ln = doc->line( l );
00470               d->dcCursor.setPosition( l, ln.length() );
00471             }
00472             else
00473             {
00474               KNotification::beep();
00475               return;
00476             }
00477           }
00478 
00479           else
00480             d->dcCursor.setColumn( d->dcCursor.column()-1 );
00481         }
00482       }
00483     }
00484 
00485     else  // no match
00486     {
00487       //kDebug( 13040 )<<"NO MATCH";
00488       if ( (! fw && d->dcCursor.line() == 0 ) || ( fw && d->dcCursor.line() >= doc->lines() ) )
00489       {
00490         KNotification::beep();
00491         return;
00492       }
00493 
00494       int l = d->dcCursor.line() + inc;
00495       ln = doc->line( l );
00496       d->dcCursor.setPosition( l, fw ? 0 : ln.length() );
00497     }
00498   } // while true
00499 }
00500 
00501 void KateWordCompletionView::slotCursorMoved()
00502 {
00503   if ( d->isCompleting) return;
00504 
00505   d->dcRange = KTextEditor::Range::invalid();
00506 
00507   disconnect( m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*, const KTextEditor::Cursor&)), this, SLOT(slotCursorMoved()) );
00508 
00509   d->liRange->setView(0);
00510   d->liRange->setRange(KTextEditor::Range::invalid());
00511 }
00512 
00513 // Contributed by <brain@hdsnet.hu> FIXME
00514 QString KateWordCompletionView::findLongestUnique( const QStringList &matches, int lead ) const
00515 {
00516   QString partial = matches.first();
00517 
00518   QStringListIterator it( matches );
00519   QString current;
00520   while ( it.hasNext() )
00521   {
00522     current = it.next();
00523     if ( !current.startsWith( partial ) )
00524     {
00525       while( partial.length() > lead )
00526       {
00527         partial.remove( partial.length() - 1, 1 );
00528         if ( current.startsWith( partial ) )
00529           break;
00530       }
00531 
00532       if ( partial.length() == lead )
00533         return QString();
00534     }
00535   }
00536 
00537   return partial;
00538 }
00539 
00540 // Return the string to complete (the letters behind the cursor)
00541 const QString KateWordCompletionView::word() const
00542 {
00543   return m_view->document()->text( range() );
00544 }
00545 
00546 // Return the range containing the word behind the cursor
00547 const KTextEditor::Range KateWordCompletionView::range() const
00548 {
00549   KTextEditor::Cursor end = m_view->cursorPosition();
00550 
00551   int line = end.line();
00552   int col = end.column();
00553 
00554   KTextEditor::Document *doc = m_view->document();
00555   while ( col > 0 )
00556   {
00557     QChar c = ( doc->character( KTextEditor::Cursor( line, col-1 ) ) );
00558     if ( c.isLetterOrNumber() || c.isMark() || c == '_' )
00559     {
00560       col--;
00561       continue;
00562     }
00563 
00564     break;
00565   }
00566 
00567   return KTextEditor::Range( KTextEditor::Cursor( line, col ), end );
00568 }
00569 //END
00570 
00571 #include "katewordcompletion.moc"
00572 // kate: space-indent on; indent-width 2; replace-tabs on; mixed-indent off;

Kate

Skip menu "Kate"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal