00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030 #include "katewordcompletion.h"
00031 #include "kateview.h"
00032 #include "katedocument.h"
00033 #include "kateglobal.h"
00034 #include "katesmartrange.h"
00035
00036 #include <ktexteditor/variableinterface.h>
00037 #include <ktexteditor/smartinterface.h>
00038 #include <ktexteditor/smartrange.h>
00039 #include <ktexteditor/rangefeedback.h>
00040
00041 #include <kconfig.h>
00042 #include <kdialog.h>
00043 #include <kgenericfactory.h>
00044 #include <klocale.h>
00045 #include <kaction.h>
00046 #include <kactioncollection.h>
00047 #include <knotification.h>
00048 #include <kparts/part.h>
00049 #include <kiconloader.h>
00050 #include <kpagedialog.h>
00051 #include <kpagewidgetmodel.h>
00052 #include <ktoggleaction.h>
00053 #include <kconfiggroup.h>
00054 #include <kcolorscheme.h>
00055 #include <kaboutdata.h>
00056
00057 #include <QtCore/QRegExp>
00058 #include <QtCore/QString>
00059 #include <QtCore/QSet>
00060 #include <QtGui/QSpinBox>
00061 #include <QtGui/QLabel>
00062 #include <QtGui/QLayout>
00063
00064 #include <kvbox.h>
00065 #include <QtGui/QCheckBox>
00066
00067 #include <kdebug.h>
00068
00069
00070
00071 KateWordCompletionModel::KateWordCompletionModel( QObject *parent )
00072 : CodeCompletionModel( parent )
00073 {
00074 setHasGroups(false);
00075 }
00076
00077 KateWordCompletionModel::~KateWordCompletionModel()
00078 {
00079 }
00080
00081 void KateWordCompletionModel::saveMatches( KTextEditor::View* view,
00082 const KTextEditor::Range& range)
00083 {
00084 m_matches = allMatches( view, range, 2 );
00085 m_matches.sort();
00086 }
00087
00088 QVariant KateWordCompletionModel::data(const QModelIndex& index, int role) const
00089 {
00090 if( role == InheritanceDepth )
00091 return 10000;
00092
00093 if( !index.parent().isValid() ) {
00094
00095 switch ( role )
00096 {
00097 case Qt::DisplayRole:
00098 return i18n("Auto Word Completion");
00099 case GroupRole:
00100 return Qt::DisplayRole;
00101 }
00102 }
00103
00104 if( index.column() == KTextEditor::CodeCompletionModel::Name && role == Qt::DisplayRole )
00105 return m_matches.at( index.row() );
00106
00107 if( index.column() == KTextEditor::CodeCompletionModel::Icon && role == Qt::DecorationRole ) {
00108 static QIcon icon(KIcon("insert-text").pixmap(QSize(16, 16)));
00109 return icon;
00110 }
00111
00112 return QVariant();
00113 }
00114
00115 QModelIndex KateWordCompletionModel::parent(const QModelIndex& index) const
00116 {
00117 if(index.internalId())
00118 return createIndex(0, 0, 0);
00119 else
00120 return QModelIndex();
00121 }
00122
00123 QModelIndex KateWordCompletionModel::index(int row, int column, const QModelIndex& parent) const
00124 {
00125 if( !parent.isValid()) {
00126 if(row == 0)
00127 return createIndex(row, column, 0);
00128 else
00129 return QModelIndex();
00130
00131 }else if(parent.parent().isValid())
00132 return QModelIndex();
00133
00134
00135 if (row < 0 || row >= m_matches.count() || column < 0 || column >= ColumnCount )
00136 return QModelIndex();
00137
00138 return createIndex(row, column, 1);
00139 }
00140
00141 int KateWordCompletionModel::rowCount ( const QModelIndex & parent ) const
00142 {
00143 if( !parent.isValid() && !m_matches.isEmpty() )
00144 return 1;
00145 else if(parent.parent().isValid())
00146 return 0;
00147 else
00148 return m_matches.count();
00149 }
00150
00151 void KateWordCompletionModel::completionInvoked(KTextEditor::View* view, const KTextEditor::Range& range, InvocationType it)
00152 {
00156 if (it==AutomaticInvocation) {
00157 KateView *v = qobject_cast<KateView*> (view);
00158
00159 if (range.columnWidth() >= v->config()->wordCompletionMinimalWordLength())
00160 saveMatches( view, range );
00161 else
00162 m_matches.clear();
00163
00164
00165 return;
00166 }
00167
00168
00169 saveMatches( view, range );
00170 }
00171
00172
00173
00174
00175 const QStringList KateWordCompletionModel::allMatches( KTextEditor::View *view, const KTextEditor::Range &range, int minAdditionalLength ) const
00176 {
00177 QStringList l;
00178
00179
00180 if ( range.numberOfLines() || ! range.columnWidth() )
00181 return l;
00182
00183 int i( 0 );
00184 int pos( 0 );
00185 KTextEditor::Document *doc = view->document();
00186 QRegExp re( "\\b(" + doc->text( range ) + "\\w{" + QString::number(minAdditionalLength) + ",})" );
00187 QString s, m;
00188 QSet<QString> seen;
00189
00190 while( i < doc->lines() )
00191 {
00192 s = doc->line( i );
00193 pos = 0;
00194 while ( pos >= 0 )
00195 {
00196 pos = re.indexIn( s, pos );
00197 if ( pos >= 0 )
00198 {
00199
00200 if ( ! ( i == range.start().line() && pos == range.start().column() ) )
00201 {
00202 m = re.cap( 1 );
00203 if ( ! seen.contains( m ) ) {
00204 seen.insert( m );
00205 l << m;
00206 }
00207 }
00208 pos += re.matchedLength();
00209 }
00210 }
00211 i++;
00212 }
00213 return l;
00214 }
00215
00216
00217
00218
00219
00220 struct KateWordCompletionViewPrivate
00221 {
00222 KTextEditor::SmartRange* liRange;
00223 KTextEditor::Range dcRange;
00224 KTextEditor::Cursor dcCursor;
00225 QRegExp re;
00226 int directionalPos;
00227 bool isCompleting;
00228 };
00229
00230 KateWordCompletionView::KateWordCompletionView( KTextEditor::View *view, KActionCollection* ac )
00231 : QObject( view ),
00232 m_view( view ),
00233 m_dWCompletionModel( KateGlobal::self()->wordCompletionModel() ),
00234 d( new KateWordCompletionViewPrivate )
00235 {
00236 d->isCompleting = false;
00237 d->dcRange = KTextEditor::Range::invalid();
00238 KTextEditor::SmartInterface *si =
00239 qobject_cast<KTextEditor::SmartInterface*>( m_view->document() );
00240
00241 if( ! si )
00242 return;
00243
00244 d->liRange = si->newSmartRange();
00245
00246 static_cast<KateSmartRange*>(d->liRange)->setInternal();
00247
00248 KColorScheme colors(QPalette::Active);
00249 KTextEditor::Attribute::Ptr a = KTextEditor::Attribute::Ptr( new KTextEditor::Attribute() );
00250 a->setBackground( colors.background(KColorScheme::ActiveBackground) );
00251 a->setForeground( colors.foreground(KColorScheme::ActiveText) );
00252 d->liRange->setAttribute( a );
00253
00254 KTextEditor::CodeCompletionInterface *cci = qobject_cast<KTextEditor::CodeCompletionInterface *>(view);
00255
00256 KAction *action;
00257
00258 if (cci)
00259 {
00260 cci->registerCompletionModel( m_dWCompletionModel );
00261
00262 action = new KAction( i18n("Shell Completion"), this );
00263 ac->addAction( "doccomplete_sh", action );
00264 connect( action, SIGNAL( triggered() ), this, SLOT(shellComplete()) );
00265 }
00266
00267
00268 action = new KAction( i18n("Reuse Word Above"), this );
00269 ac->addAction( "doccomplete_bw", action );
00270 action->setShortcut( Qt::CTRL+Qt::Key_8 );
00271 connect( action, SIGNAL( triggered() ), this, SLOT(completeBackwards()) );
00272
00273 action = new KAction( i18n("Reuse Word Below"), this );
00274 ac->addAction( "doccomplete_fw", action );
00275 action->setShortcut( Qt::CTRL+Qt::Key_9 );
00276 connect( action, SIGNAL( triggered() ), this, SLOT(completeForwards()) );
00277 }
00278
00279 KateWordCompletionView::~KateWordCompletionView()
00280 {
00281 KTextEditor::CodeCompletionInterface *cci = qobject_cast<KTextEditor::CodeCompletionInterface *>(m_view);
00282
00283 if (cci) cci->unregisterCompletionModel(m_dWCompletionModel);
00284
00285 delete d;
00286 }
00287
00288 void KateWordCompletionView::completeBackwards()
00289 {
00290 complete( false );
00291 }
00292
00293 void KateWordCompletionView::completeForwards()
00294 {
00295 complete();
00296 }
00297
00298
00299 void KateWordCompletionView::popupCompletionList()
00300 {
00301 kDebug( 13040 ) << "entered ...";
00302 KTextEditor::Range r = range();
00303
00304 if ( r.isEmpty() )
00305 return;
00306
00307 KTextEditor::CodeCompletionInterface *cci = qobject_cast<KTextEditor::CodeCompletionInterface *>( m_view );
00308 if(!cci || cci->isCompletionActive())
00309 return;
00310
00311 m_dWCompletionModel->saveMatches( m_view, r );
00312
00313 kDebug( 13040 ) << "after save matches ...";
00314
00315 if ( ! m_dWCompletionModel->rowCount(QModelIndex()) ) return;
00316
00317 cci->startCompletion( r, m_dWCompletionModel );
00318 }
00319
00320
00321 void KateWordCompletionView::shellComplete()
00322 {
00323 KTextEditor::Range r = range();
00324 if (r.isEmpty())
00325 return;
00326
00327 QStringList matches = m_dWCompletionModel->allMatches( m_view, r );
00328
00329 if (matches.size() == 0)
00330 return;
00331
00332 QString partial = findLongestUnique( matches, r.columnWidth() );
00333
00334 if ( ! partial.length() )
00335 popupCompletionList();
00336
00337 else
00338 {
00339 m_view->document()->insertText( r.end(), partial.mid( r.columnWidth() ) );
00340 KTextEditor::SmartInterface *si = qobject_cast<KTextEditor::SmartInterface*>( m_view->document() );
00341 if ( si ) {
00342 si->addHighlightToView( m_view, d->liRange, true );
00343 d->liRange->setRange( KTextEditor::Range( r.end(), partial.length() - r.columnWidth() ) );
00344 connect( m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*, const KTextEditor::Cursor&)), this, SLOT(slotCursorMoved()) );
00345 }
00346 }
00347 }
00348
00349
00350
00351 void KateWordCompletionView::complete( bool fw )
00352 {
00353 KTextEditor::Range r = range();
00354 if ( r.isEmpty() )
00355 return;
00356
00357 int inc = fw ? 1 : -1;
00358 KTextEditor::Document *doc = m_view->document();
00359
00360 if ( d->dcRange.isValid() )
00361 {
00362
00363
00364
00365
00366 if ( ( fw && d->directionalPos == -1 ) ||
00367 ( !fw && d->directionalPos == 1 ) )
00368 {
00369 if ( d->liRange->columnWidth() )
00370 doc->removeText( *d->liRange );
00371
00372 d->liRange->setRange( KTextEditor::Range( d->liRange->start(), 0 ) );
00373 d->dcCursor = r.end();
00374 d->directionalPos = 0;
00375
00376 return;
00377 }
00378
00379 if ( fw )
00380 d->dcCursor.setColumn( d->dcCursor.column() + d->liRange->columnWidth() );
00381
00382 d->directionalPos += inc;
00383 }
00384 else
00385 {
00386
00387 d->dcRange = r;
00388 d->liRange->setRange( KTextEditor::Range( r.end(), 0 ) );
00389 d->dcCursor = r.start();
00390 d->directionalPos = inc;
00391
00392 KTextEditor::SmartInterface *si =
00393 qobject_cast<KTextEditor::SmartInterface*>( m_view->document() );
00394 if ( si )
00395 si->addHighlightToView( m_view, d->liRange, true );
00396
00397 connect( m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*, const KTextEditor::Cursor&)), this, SLOT(slotCursorMoved()) );
00398
00399 }
00400
00401 d->re.setPattern( "\\b" + doc->text( d->dcRange ) + "(\\w+)" );
00402 int pos ( 0 );
00403 QString ln = doc->line( d->dcCursor.line() );
00404
00405 while ( true )
00406 {
00407
00408 pos = fw ?
00409 d->re.indexIn( ln, d->dcCursor.column() ) :
00410 d->re.lastIndexIn( ln, d->dcCursor.column() );
00411
00412 if ( pos > -1 )
00413 {
00414
00415 QString m = d->re.cap( 1 );
00416 if ( m != doc->text( *d->liRange ) && (d->dcCursor.line() != d->dcRange.start().line() || pos != d->dcRange.start().column() ) )
00417 {
00418
00419 d->isCompleting = true;
00420 doc->replaceText( *d->liRange, m );
00421 d->liRange->setRange( KTextEditor::Range( d->dcRange.end(), m.length() ) );
00422
00423 d->dcCursor.setColumn( pos );
00424
00425 d->isCompleting = false;
00426 return;
00427 }
00428
00429
00430 else
00431 {
00432
00433 d->dcCursor.setColumn( pos );
00434
00435 if ( fw )
00436 d->dcCursor.setColumn( pos + m.length() );
00437
00438 else
00439 {
00440 if ( pos == 0 )
00441 {
00442 if ( d->dcCursor.line() > 0 )
00443 {
00444 int l = d->dcCursor.line() + inc;
00445 ln = doc->line( l );
00446 d->dcCursor.setPosition( l, ln.length() );
00447 }
00448 else
00449 {
00450 KNotification::beep();
00451 return;
00452 }
00453 }
00454
00455 else
00456 d->dcCursor.setColumn( d->dcCursor.column()-1 );
00457 }
00458 }
00459 }
00460
00461 else
00462 {
00463
00464 if ( (! fw && d->dcCursor.line() == 0 ) || ( fw && d->dcCursor.line() >= doc->lines() ) )
00465 {
00466 KNotification::beep();
00467 return;
00468 }
00469
00470 int l = d->dcCursor.line() + inc;
00471 ln = doc->line( l );
00472 d->dcCursor.setPosition( l, fw ? 0 : ln.length() );
00473 }
00474 }
00475 }
00476
00477 void KateWordCompletionView::slotCursorMoved()
00478 {
00479 if ( d->isCompleting) return;
00480
00481 d->dcRange = KTextEditor::Range::invalid();
00482
00483 disconnect( m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*, const KTextEditor::Cursor&)), this, SLOT(slotCursorMoved()) );
00484
00485 KTextEditor::SmartInterface *si =
00486 qobject_cast<KTextEditor::SmartInterface*>( m_view->document() );
00487 if ( si )
00488 si->removeHighlightFromView( m_view, d->liRange );
00489 }
00490
00491
00492 QString KateWordCompletionView::findLongestUnique( const QStringList &matches, int lead ) const
00493 {
00494 QString partial = matches.first();
00495
00496 QStringListIterator it( matches );
00497 QString current;
00498 while ( it.hasNext() )
00499 {
00500 current = it.next();
00501 if ( !current.startsWith( partial ) )
00502 {
00503 while( partial.length() > lead )
00504 {
00505 partial.remove( partial.length() - 1, 1 );
00506 if ( current.startsWith( partial ) )
00507 break;
00508 }
00509
00510 if ( partial.length() == lead )
00511 return QString();
00512 }
00513 }
00514
00515 return partial;
00516 }
00517
00518
00519 const QString KateWordCompletionView::word() const
00520 {
00521 return m_view->document()->text( range() );
00522 }
00523
00524
00525 const KTextEditor::Range KateWordCompletionView::range() const
00526 {
00527 KTextEditor::Cursor end = m_view->cursorPosition();
00528
00529 if ( ! end.column() ) return KTextEditor::Range();
00530 int line = end.line();
00531 int col = end.column();
00532
00533 KTextEditor::Document *doc = m_view->document();
00534 while ( col > 0 )
00535 {
00536 QChar c = ( doc->character( KTextEditor::Cursor( line, col-1 ) ) );
00537 if ( c.isLetterOrNumber() || c.isMark() || c == '_' )
00538 {
00539 col--;
00540 continue;
00541 }
00542
00543 break;
00544 }
00545
00546 return KTextEditor::Range( KTextEditor::Cursor( line, col ), end );
00547 }
00548
00549
00550 #include "katewordcompletion.moc"
00551