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