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