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 "docwordcompletion.h"
00031
00032 #include <ktexteditor/document.h>
00033 #include <ktexteditor/variableinterface.h>
00034 #include <ktexteditor/smartinterface.h>
00035 #include <ktexteditor/smartrange.h>
00036 #include <ktexteditor/rangefeedback.h>
00037
00038 #include <kconfig.h>
00039 #include <kdialog.h>
00040 #include <kgenericfactory.h>
00041 #include <klocale.h>
00042 #include <kaction.h>
00043 #include <kactioncollection.h>
00044 #include <knotification.h>
00045 #include <kparts/part.h>
00046 #include <kiconloader.h>
00047 #include <kpagedialog.h>
00048 #include <kpagewidgetmodel.h>
00049 #include <ktoggleaction.h>
00050 #include <kconfiggroup.h>
00051 #include <kcolorscheme.h>
00052 #include <kaboutdata.h>
00053
00054 #include <QtCore/QRegExp>
00055 #include <QtCore/QString>
00056 #include <QtCore/QSet>
00057 #include <QtGui/QSpinBox>
00058 #include <QtGui/QLabel>
00059 #include <QtGui/QLayout>
00060
00061 #include <kvbox.h>
00062 #include <QtGui/QCheckBox>
00063
00064 #include <kdebug.h>
00065
00066
00067
00068 DocWordCompletionModel::DocWordCompletionModel( QObject *parent, DocWordCompletionPlugin *plugin )
00069 : CodeCompletionModel( parent ),m_plugin(plugin)
00070 {
00071 setHasGroups(false);
00072 }
00073
00074 DocWordCompletionModel::~DocWordCompletionModel()
00075 {
00076 }
00077
00078 void DocWordCompletionModel::saveMatches( KTextEditor::View* view,
00079 const KTextEditor::Range& range)
00080 {
00081 m_matches = allMatches( view, range, 2 );
00082 m_matches.sort();
00083 }
00084
00085 QVariant DocWordCompletionModel::data(const QModelIndex& index, int role) const
00086 {
00087 if ( index.column() != KTextEditor::CodeCompletionModel::Name ) return QVariant();
00088
00089 switch ( role )
00090 {
00091 case Qt::DisplayRole:
00092
00093 return m_matches.at( index.row() );
00094 case CompletionRole:
00095 return (int)FirstProperty|LastProperty|Public;
00096 case ScopeIndex:
00097 return 0;
00098 case MatchQuality:
00099 return 10;
00100 case HighlightingMethod:
00101 return QVariant::Invalid;
00102 case InheritanceDepth:
00103 return 0;
00104 }
00105
00106 return QVariant();
00107 }
00108
00109 QModelIndex DocWordCompletionModel::index(int row, int column, const QModelIndex& parent) const
00110 {
00111 if (row < 0 || row >= m_matches.count() || column < 0 || column >= ColumnCount || parent.isValid())
00112 return QModelIndex();
00113
00114 return createIndex(row, column, 0);
00115 }
00116
00117 int DocWordCompletionModel::rowCount ( const QModelIndex & parent ) const
00118 {
00119 if( parent.isValid() )
00120 return 0;
00121 else
00122 return m_matches.count();
00123 }
00124
00125 void DocWordCompletionModel::completionInvoked(KTextEditor::View* view, const KTextEditor::Range& range, InvocationType it)
00126 {
00127 if (it==AutomaticInvocation) {
00128 DocWordCompletionPluginView *v=m_plugin->m_views[view];
00129 if (v->autoPopupEnabled()) {
00130 if ((range.columnWidth())>=v->threshold())
00131 saveMatches( view, range );
00132 else
00133 m_matches.clear();
00134 } else m_matches.clear();
00135 } else saveMatches( view, range );
00136 }
00137
00138
00139
00140
00141 const QStringList DocWordCompletionModel::allMatches( KTextEditor::View *view, const KTextEditor::Range &range, int minAdditionalLength ) const
00142 {
00143 QStringList l;
00144
00145
00146 if ( range.numberOfLines() || ! range.columnWidth() )
00147 return l;
00148
00149 int i( 0 );
00150 int pos( 0 );
00151 KTextEditor::Document *doc = view->document();
00152 QRegExp re( "\\b(" + doc->text( range ) + "\\w{" + QString::number(minAdditionalLength) + ",})" );
00153 QString s, m;
00154 QSet<QString> seen;
00155
00156 while( i < doc->lines() )
00157 {
00158 s = doc->line( i );
00159 pos = 0;
00160 while ( pos >= 0 )
00161 {
00162 pos = re.indexIn( s, pos );
00163 if ( pos >= 0 )
00164 {
00165
00166 if ( ! ( i == range.start().line() && pos == range.start().column() ) )
00167 {
00168 m = re.cap( 1 );
00169 if ( ! seen.contains( m ) ) {
00170 seen.insert( m );
00171 l << m;
00172 }
00173 }
00174 pos += re.matchedLength();
00175 }
00176 }
00177 i++;
00178 }
00179 return l;
00180 }
00181
00182
00183
00184
00185 DocWordCompletionPlugin *DocWordCompletionPlugin::plugin = 0;
00186 K_PLUGIN_FACTORY_DECLARATION(DocWordCompletionFactory)
00187 DocWordCompletionPlugin::DocWordCompletionPlugin( QObject *parent,
00188 const QVariantList& )
00189 : KTextEditor::Plugin ( parent )
00190 {
00191 plugin = this;
00192 m_dWCompletionModel = new DocWordCompletionModel( this,this );
00193 readConfig();
00194 }
00195
00196 void DocWordCompletionPlugin::addView(KTextEditor::View *view)
00197 {
00198 DocWordCompletionPluginView *nview = new DocWordCompletionPluginView (m_treshold, m_autopopup, view, m_dWCompletionModel );
00199 m_views.insert(view,nview);
00200 }
00201
00202 void DocWordCompletionPlugin::removeView(KTextEditor::View *view)
00203 {
00204 DocWordCompletionPluginView *nview=m_views[view];
00205 m_views.remove(view);
00206 delete nview;
00207
00208
00209
00210
00211
00212
00213
00214 }
00215
00216 void DocWordCompletionPlugin::readConfig()
00217 {
00218 KConfigGroup cg(KGlobal::config(), "DocWordCompletion Plugin" );
00219 m_treshold = cg.readEntry( "treshold", 3 );
00220 m_autopopup = cg.readEntry( "autopopup", true );
00221 }
00222
00223 void DocWordCompletionPlugin::writeConfig()
00224 {
00225 KConfigGroup cg(KGlobal::config(), "DocWordCompletion Plugin" );
00226 cg.writeEntry("autopopup", m_autopopup );
00227 cg.writeEntry("treshold", m_treshold );
00228 }
00229
00230 uint DocWordCompletionPlugin::treshold() const
00231 {
00232 return m_treshold;
00233 }
00234
00235 void DocWordCompletionPlugin::setTreshold(uint t)
00236 {
00237 m_treshold = t;
00238
00239
00240
00241 foreach (DocWordCompletionPluginView *view, m_views)
00242 {
00243 view->setTreshold(t);
00244 }
00245 }
00246
00247 bool DocWordCompletionPlugin::autoPopupEnabled() const
00248 {
00249 return m_autopopup;
00250 }
00251
00252 void DocWordCompletionPlugin::setAutoPopupEnabled(bool enable)
00253 {
00254 m_autopopup = enable;
00255
00256
00257
00258 foreach (DocWordCompletionPluginView *view, m_views)
00259 {
00260 view->setAutoPopupEnabled(enable);
00261 view->toggleAutoPopup();
00262 }
00263 }
00264
00265
00266
00267
00268 struct DocWordCompletionPluginViewPrivate
00269 {
00270 KTextEditor::SmartRange* liRange;
00271 KTextEditor::Range dcRange;
00272 KTextEditor::Cursor dcCursor;
00273 QRegExp re;
00274 KToggleAction *autopopup;
00275 uint treshold;
00276 int directionalPos;
00277 bool isCompleting;
00278 };
00279
00280 DocWordCompletionPluginView::DocWordCompletionPluginView( uint treshold,
00281 bool autopopup,
00282 KTextEditor::View *view,
00283 DocWordCompletionModel *completionModel )
00284 : QObject( view ),
00285 KXMLGUIClient( view ),
00286 m_view( view ),
00287 m_dWCompletionModel( completionModel ),
00288 d( new DocWordCompletionPluginViewPrivate )
00289 {
00290 setComponentData( DocWordCompletionFactory::componentData() );
00291
00292
00293 d->isCompleting = false;
00294 d->treshold = treshold;
00295 d->dcRange = KTextEditor::Range::invalid();
00296 KTextEditor::SmartInterface *si =
00297 qobject_cast<KTextEditor::SmartInterface*>( m_view->document() );
00298
00299 if( ! si )
00300 return;
00301
00302 d->liRange = si->newSmartRange();
00303
00304 KColorScheme colors(QPalette::Active);
00305 KTextEditor::Attribute::Ptr a = KTextEditor::Attribute::Ptr( new KTextEditor::Attribute() );
00306 a->setBackground( colors.background(KColorScheme::ActiveBackground) );
00307 a->setForeground( colors.foreground(KColorScheme::ActiveText) );
00308 d->liRange->setAttribute( a );
00309
00310 KTextEditor::CodeCompletionInterface *cci = qobject_cast<KTextEditor::CodeCompletionInterface *>(view);
00311
00312 KAction *action;
00313
00314 if (cci)
00315 {
00316 cci->registerCompletionModel( m_dWCompletionModel );
00317
00318 d->autopopup = new KToggleAction( i18n("Automatic Completion Popup"), this );
00319 actionCollection()->addAction( "enable_autopopup", d->autopopup );
00320 connect( d->autopopup, SIGNAL( triggered() ), this, SLOT(toggleAutoPopup()) );
00321
00322 d->autopopup->setChecked( autopopup );
00323 toggleAutoPopup();
00324
00325 action = new KAction( i18n("Shell Completion"), this );
00326 actionCollection()->addAction( "doccomplete_sh", action );
00327 connect( action, SIGNAL( triggered() ), this, SLOT(shellComplete()) );
00328 }
00329
00330
00331 action = new KAction( i18n("Reuse Word Above"), this );
00332 actionCollection()->addAction( "doccomplete_bw", action );
00333 action->setShortcut( Qt::CTRL+Qt::Key_8 );
00334 connect( action, SIGNAL( triggered() ), this, SLOT(completeBackwards()) );
00335
00336 action = new KAction( i18n("Reuse Word Below"), this );
00337 actionCollection()->addAction( "doccomplete_fw", action );
00338 action->setShortcut( Qt::CTRL+Qt::Key_9 );
00339 connect( action, SIGNAL( triggered() ), this, SLOT(completeForwards()) );
00340
00341 setXMLFile("docwordcompletionui.rc");
00342
00343 KTextEditor::VariableInterface *vi = qobject_cast<KTextEditor::VariableInterface *>( view->document() );
00344 if ( vi )
00345 {
00346 QString e = vi->variable("wordcompletion-autopopup");
00347 if ( ! e.isEmpty() )
00348 d->autopopup->setEnabled( e == "true" );
00349
00350 connect( view->document(), SIGNAL(variableChanged(KTextEditor::Document*,const QString &, const QString &)),
00351 this, SLOT(slotVariableChanged(KTextEditor::Document *,const QString &, const QString &)) );
00352 }
00353 }
00354
00355 DocWordCompletionPluginView::~DocWordCompletionPluginView()
00356 {
00357 KTextEditor::CodeCompletionInterface *cci = qobject_cast<KTextEditor::CodeCompletionInterface *>(m_view);
00358
00359 if (cci) cci->unregisterCompletionModel(m_dWCompletionModel);
00360
00361 delete d;
00362 d=0;
00363 }
00364
00365 void DocWordCompletionPluginView::setTreshold( uint t )
00366 {
00367 d->treshold = t;
00368 }
00369
00370 uint DocWordCompletionPluginView::threshold() {
00371 return d->treshold;
00372 }
00373
00374 bool DocWordCompletionPluginView::autoPopupEnabled() {
00375 return d->autopopup->isChecked();
00376 }
00377
00378 void DocWordCompletionPluginView::setAutoPopupEnabled( bool enable )
00379 {
00380 d->autopopup->setChecked(enable);
00381 }
00382
00383 void DocWordCompletionPluginView::completeBackwards()
00384 {
00385 complete( false );
00386 }
00387
00388 void DocWordCompletionPluginView::completeForwards()
00389 {
00390 complete();
00391 }
00392
00393
00394 void DocWordCompletionPluginView::popupCompletionList()
00395 {
00396 kDebug( 13040 ) << "entered ...";
00397 KTextEditor::Range r = range();
00398
00399 if ( r.isEmpty() )
00400 return;
00401
00402 KTextEditor::CodeCompletionInterface *cci = qobject_cast<KTextEditor::CodeCompletionInterface *>( m_view );
00403 if(!cci || cci->isCompletionActive())
00404 return;
00405
00406 m_dWCompletionModel->saveMatches( m_view, r );
00407
00408 kDebug( 13040 ) << "after save matches ...";
00409
00410 if ( ! m_dWCompletionModel->rowCount(QModelIndex()) ) return;
00411
00412 cci->startCompletion( r, m_dWCompletionModel );
00413 }
00414
00415 void DocWordCompletionPluginView::toggleAutoPopup()
00416 {
00417 if ( d->autopopup->isChecked() ) {
00418 if ( ! connect( m_view, SIGNAL(textInserted ( KTextEditor::View *, const KTextEditor::Cursor &, const QString & )),
00419 this, SLOT(autoPopupCompletionList()) ))
00420 {
00421 connect( m_view->document(), SIGNAL(textChanged(KTextEditor::View *)), this, SLOT(autoPopupCompletionList()) );
00422 }
00423 } else {
00424 disconnect( m_view->document(), SIGNAL(textChanged(KTextEditor::View *)), this, SLOT(autoPopupCompletionList()) );
00425 disconnect( m_view, SIGNAL(textInserted( KTextEditor::View *, const KTextEditor::Cursor &, const QString &)),
00426 this, SLOT(autoPopupCompletionList()) );
00427
00428 }
00429 }
00430
00431
00432 void DocWordCompletionPluginView::autoPopupCompletionList()
00433 {
00434 if ( ! m_view->hasFocus() ) return;
00435
00436 KTextEditor::Range r = range();
00437 if ( r.columnWidth() >= (int)d->treshold )
00438 {
00439 popupCompletionList();
00440 }
00441 }
00442
00443
00444 void DocWordCompletionPluginView::shellComplete()
00445 {
00446 KTextEditor::Range r = range();
00447 if (r.isEmpty())
00448 return;
00449
00450 QStringList matches = m_dWCompletionModel->allMatches( m_view, r );
00451
00452 if (matches.size() == 0)
00453 return;
00454
00455 QString partial = findLongestUnique( matches, r.columnWidth() );
00456
00457 if ( ! partial.length() )
00458 popupCompletionList();
00459
00460 else
00461 {
00462 m_view->document()->insertText( r.end(), partial.mid( r.columnWidth() ) );
00463 KTextEditor::SmartInterface *si = qobject_cast<KTextEditor::SmartInterface*>( m_view->document() );
00464 if ( si ) {
00465 si->addHighlightToView( m_view, d->liRange, true );
00466 d->liRange->setRange( KTextEditor::Range( r.end(), partial.length() - r.columnWidth() ) );
00467 connect( m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*, const KTextEditor::Cursor&)), this, SLOT(slotCursorMoved()) );
00468 }
00469 }
00470 }
00471
00472
00473
00474 void DocWordCompletionPluginView::complete( bool fw )
00475 {
00476 KTextEditor::Range r = range();
00477 if ( r.isEmpty() )
00478 return;
00479
00480 int inc = fw ? 1 : -1;
00481 KTextEditor::Document *doc = m_view->document();
00482
00483 if ( d->dcRange.isValid() )
00484 {
00485
00486
00487
00488
00489 if ( ( fw && d->directionalPos == -1 ) ||
00490 ( !fw && d->directionalPos == 1 ) )
00491 {
00492 if ( d->liRange->columnWidth() )
00493 doc->removeText( *d->liRange );
00494
00495 d->liRange->setRange( KTextEditor::Range( d->liRange->start(), 0 ) );
00496 d->dcCursor = r.end();
00497 d->directionalPos = 0;
00498
00499 return;
00500 }
00501
00502 if ( fw )
00503 d->dcCursor.setColumn( d->dcCursor.column() + d->liRange->columnWidth() );
00504
00505 d->directionalPos += inc;
00506 }
00507 else
00508 {
00509
00510 d->dcRange = r;
00511 d->liRange->setRange( KTextEditor::Range( r.end(), 0 ) );
00512 d->dcCursor = r.start();
00513 d->directionalPos = inc;
00514
00515 KTextEditor::SmartInterface *si =
00516 qobject_cast<KTextEditor::SmartInterface*>( m_view->document() );
00517 if ( si )
00518 si->addHighlightToView( m_view, d->liRange, true );
00519
00520 connect( m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*, const KTextEditor::Cursor&)), this, SLOT(slotCursorMoved()) );
00521
00522 }
00523
00524 d->re.setPattern( "\\b" + doc->text( d->dcRange ) + "(\\w+)" );
00525 int pos ( 0 );
00526 QString ln = doc->line( d->dcCursor.line() );
00527
00528 while ( true )
00529 {
00530
00531 pos = fw ?
00532 d->re.indexIn( ln, d->dcCursor.column() ) :
00533 d->re.lastIndexIn( ln, d->dcCursor.column() );
00534
00535 if ( pos > -1 )
00536 {
00537
00538 QString m = d->re.cap( 1 );
00539 if ( m != doc->text( *d->liRange ) && (d->dcCursor.line() != d->dcRange.start().line() || pos != d->dcRange.start().column() ) )
00540 {
00541
00542 d->isCompleting = true;
00543 doc->replaceText( *d->liRange, m );
00544 d->liRange->setRange( KTextEditor::Range( d->dcRange.end(), m.length() ) );
00545
00546 d->dcCursor.setColumn( pos );
00547
00548 d->isCompleting = false;
00549 return;
00550 }
00551
00552
00553 else
00554 {
00555
00556 d->dcCursor.setColumn( pos );
00557
00558 if ( fw )
00559 d->dcCursor.setColumn( pos + m.length() );
00560
00561 else
00562 {
00563 if ( pos == 0 )
00564 {
00565 if ( d->dcCursor.line() > 0 )
00566 {
00567 int l = d->dcCursor.line() + inc;
00568 ln = doc->line( l );
00569 d->dcCursor.setPosition( l, ln.length() );
00570 }
00571 else
00572 {
00573 KNotification::beep();
00574 return;
00575 }
00576 }
00577
00578 else
00579 d->dcCursor.setColumn( d->dcCursor.column()-1 );
00580 }
00581 }
00582 }
00583
00584 else
00585 {
00586
00587 if ( (! fw && d->dcCursor.line() == 0 ) || ( fw && d->dcCursor.line() >= doc->lines() ) )
00588 {
00589 KNotification::beep();
00590 return;
00591 }
00592
00593 int l = d->dcCursor.line() + inc;
00594 ln = doc->line( l );
00595 d->dcCursor.setPosition( l, fw ? 0 : ln.length() );
00596 }
00597 }
00598 }
00599
00600 void DocWordCompletionPluginView::slotCursorMoved()
00601 {
00602 if ( d->isCompleting) return;
00603
00604 d->dcRange = KTextEditor::Range::invalid();
00605
00606 disconnect( m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*, const KTextEditor::Cursor&)), this, SLOT(slotCursorMoved()) );
00607
00608 KTextEditor::SmartInterface *si =
00609 qobject_cast<KTextEditor::SmartInterface*>( m_view->document() );
00610 if ( si )
00611 si->removeHighlightFromView( m_view, d->liRange );
00612 }
00613
00614
00615 QString DocWordCompletionPluginView::findLongestUnique( const QStringList &matches, int lead ) const
00616 {
00617 QString partial = matches.first();
00618
00619 QStringListIterator it( matches );
00620 QString current;
00621 while ( it.hasNext() )
00622 {
00623 current = it.next();
00624 if ( !current.startsWith( partial ) )
00625 {
00626 while( partial.length() > lead )
00627 {
00628 partial.remove( partial.length() - 1, 1 );
00629 if ( current.startsWith( partial ) )
00630 break;
00631 }
00632
00633 if ( partial.length() == lead )
00634 return QString();
00635 }
00636 }
00637
00638 return partial;
00639 }
00640
00641
00642 const QString DocWordCompletionPluginView::word() const
00643 {
00644 return m_view->document()->text( range() );
00645 }
00646
00647
00648 const KTextEditor::Range DocWordCompletionPluginView::range() const
00649 {
00650 KTextEditor::Cursor end = m_view->cursorPosition();
00651
00652 if ( ! end.column() ) return KTextEditor::Range();
00653 int line = end.line();
00654 int col = end.column();
00655
00656 KTextEditor::Document *doc = m_view->document();
00657 while ( col > 0 )
00658 {
00659 QChar c = ( doc->character( KTextEditor::Cursor( line, col-1 ) ) );
00660 if ( c.isLetterOrNumber() || c.isMark() || c == '_' )
00661 {
00662 col--;
00663 continue;
00664 }
00665
00666 break;
00667 }
00668
00669 return KTextEditor::Range( KTextEditor::Cursor( line, col ), end );
00670 }
00671
00672 void DocWordCompletionPluginView::slotVariableChanged( KTextEditor::Document*,const QString &var, const QString &val )
00673 {
00674 if ( var == "wordcompletion-autopopup" )
00675 d->autopopup->setEnabled( val == "true" );
00676 else if ( var == "wordcompletion-treshold" )
00677 d->treshold = val.toInt();
00678 }
00679
00680
00681 #include "docwordcompletion_config.h"
00682 K_PLUGIN_FACTORY_DEFINITION(DocWordCompletionFactory,
00683 registerPlugin<DocWordCompletionConfig>("ktexteditor_docwordcompletion_config");
00684 registerPlugin<DocWordCompletionPlugin>("ktexteditor_docwordcompletion");
00685 )
00686 K_EXPORT_PLUGIN(DocWordCompletionFactory(KAboutData("ktexteditor_docwordcompletion", "ktexteditor_plugins", ki18n("WordCompletion"), "0.1", ki18n("Complete words"), KAboutData::License_LGPL_V2)))
00687
00688 #include "docwordcompletion.moc"
00689