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 #include "docwordcompletion.h"
00030
00031 #include <ktexteditor/document.h>
00032 #include <ktexteditor/viewcursorinterface.h>
00033 #include <ktexteditor/editinterface.h>
00034 #include <ktexteditor/variableinterface.h>
00035
00036 #include <kapplication.h>
00037 #include <kconfig.h>
00038 #include <kdialog.h>
00039 #include <kgenericfactory.h>
00040 #include <klocale.h>
00041 #include <kaction.h>
00042 #include <knotifyclient.h>
00043 #include <kparts/part.h>
00044 #include <kiconloader.h>
00045
00046 #include <qregexp.h>
00047 #include <qstring.h>
00048 #include <qdict.h>
00049 #include <qspinbox.h>
00050 #include <qlabel.h>
00051 #include <qlayout.h>
00052 #include <qhbox.h>
00053 #include <qwhatsthis.h>
00054 #include <qcheckbox.h>
00055
00056
00057
00058
00059
00060 K_EXPORT_COMPONENT_FACTORY( ktexteditor_docwordcompletion, KGenericFactory<DocWordCompletionPlugin>( "ktexteditor_docwordcompletion" ) )
00061 DocWordCompletionPlugin::DocWordCompletionPlugin( QObject *parent,
00062 const char* name,
00063 const QStringList& )
00064 : KTextEditor::Plugin ( (KTextEditor::Document*) parent, name )
00065 {
00066 readConfig();
00067 }
00068
00069 void DocWordCompletionPlugin::readConfig()
00070 {
00071 KConfig *config = kapp->config();
00072 config->setGroup( "DocWordCompletion Plugin" );
00073 m_treshold = config->readNumEntry( "treshold", 3 );
00074 m_autopopup = config->readBoolEntry( "autopopup", true );
00075 }
00076
00077 void DocWordCompletionPlugin::writeConfig()
00078 {
00079 KConfig *config = kapp->config();
00080 config->setGroup("DocWordCompletion Plugin");
00081 config->writeEntry("autopopup", m_autopopup );
00082 config->writeEntry("treshold", m_treshold );
00083 }
00084
00085 void DocWordCompletionPlugin::addView(KTextEditor::View *view)
00086 {
00087 DocWordCompletionPluginView *nview = new DocWordCompletionPluginView (m_treshold, m_autopopup, view, "Document word completion");
00088 m_views.append (nview);
00089 }
00090
00091 void DocWordCompletionPlugin::removeView(KTextEditor::View *view)
00092 {
00093 for (uint z=0; z < m_views.count(); z++)
00094 if (m_views.at(z)->parentClient() == view)
00095 {
00096 DocWordCompletionPluginView *nview = m_views.at(z);
00097 m_views.remove (nview);
00098 delete nview;
00099 }
00100 }
00101
00102 KTextEditor::ConfigPage* DocWordCompletionPlugin::configPage( uint, QWidget *parent, const char *name )
00103 {
00104 return new DocWordCompletionConfigPage( this, parent, name );
00105 }
00106
00107 QString DocWordCompletionPlugin::configPageName( uint ) const
00108 {
00109 return i18n("Word Completion Plugin");
00110 }
00111
00112 QString DocWordCompletionPlugin::configPageFullName( uint ) const
00113 {
00114 return i18n("Configure the Word Completion Plugin");
00115 }
00116
00117
00118 QPixmap DocWordCompletionPlugin::configPagePixmap( uint, int size ) const
00119 {
00120 return UserIcon( "kte_wordcompletion", size );
00121 }
00122
00123
00124
00125 struct DocWordCompletionPluginViewPrivate
00126 {
00127 uint line, col;
00128 uint cline, ccol;
00129 uint lilen;
00130 QString last;
00131 QString lastIns;
00132 QRegExp re;
00133 KToggleAction *autopopup;
00134 uint treshold;
00135 int directionalPos;
00136 };
00137
00138 DocWordCompletionPluginView::DocWordCompletionPluginView( uint treshold, bool autopopup, KTextEditor::View *view, const char *name )
00139 : QObject( view, name ),
00140 KXMLGUIClient( view ),
00141 m_view( view ),
00142 d( new DocWordCompletionPluginViewPrivate )
00143 {
00144 d->treshold = treshold;
00145 view->insertChildClient( this );
00146 setInstance( KGenericFactory<DocWordCompletionPlugin>::instance() );
00147
00148 (void) new KAction( i18n("Reuse Word Above"), CTRL+Key_8, this,
00149 SLOT(completeBackwards()), actionCollection(), "doccomplete_bw" );
00150 (void) new KAction( i18n("Reuse Word Below"), CTRL+Key_9, this,
00151 SLOT(completeForwards()), actionCollection(), "doccomplete_fw" );
00152 (void) new KAction( i18n("Pop Up Completion List"), 0, this,
00153 SLOT(popupCompletionList()), actionCollection(), "doccomplete_pu" );
00154 (void) new KAction( i18n("Shell Completion"), 0, this,
00155 SLOT(shellComplete()), actionCollection(), "doccomplete_sh" );
00156 d->autopopup = new KToggleAction( i18n("Automatic Completion Popup"), 0, this,
00157 SLOT(toggleAutoPopup()), actionCollection(), "enable_autopopup" );
00158
00159 d->autopopup->setChecked( autopopup );
00160 toggleAutoPopup();
00161
00162 setXMLFile("docwordcompletionui.rc");
00163
00164 KTextEditor::VariableInterface *vi = KTextEditor::variableInterface( view->document() );
00165 if ( vi )
00166 {
00167 QString e = vi->variable("wordcompletion-autopopup");
00168 if ( ! e.isEmpty() )
00169 d->autopopup->setEnabled( e == "true" );
00170
00171 connect( view->document(), SIGNAL(variableChanged(const QString &, const QString &)),
00172 this, SLOT(slotVariableChanged(const QString &, const QString &)) );
00173 }
00174 }
00175
00176 void DocWordCompletionPluginView::settreshold( uint t )
00177 {
00178 d->treshold = t;
00179 }
00180
00181 void DocWordCompletionPluginView::completeBackwards()
00182 {
00183 complete( false );
00184 }
00185
00186 void DocWordCompletionPluginView::completeForwards()
00187 {
00188 complete();
00189 }
00190
00191
00192 void DocWordCompletionPluginView::popupCompletionList( QString w )
00193 {
00194 if ( w.isEmpty() )
00195 w = word();
00196 if ( w.isEmpty() )
00197 return;
00198
00199 KTextEditor::CodeCompletionInterface *cci = codeCompletionInterface( m_view );
00200 cci->showCompletionBox( allMatches( w ), w.length() );
00201 }
00202
00203 void DocWordCompletionPluginView::toggleAutoPopup()
00204 {
00205 if ( d->autopopup->isChecked() ) {
00206 if ( ! connect( m_view->document(), SIGNAL(charactersInteractivelyInserted(int ,int ,const QString&)),
00207 this, SLOT(autoPopupCompletionList()) ))
00208 {
00209 connect( m_view->document(), SIGNAL(textChanged()), this, SLOT(autoPopupCompletionList()) );
00210 }
00211 } else {
00212 disconnect( m_view->document(), SIGNAL(textChanged()), this, SLOT(autoPopupCompletionList()) );
00213 disconnect( m_view->document(), SIGNAL(charactersInteractivelyInserted(int ,int ,const QString&)),
00214 this, SLOT(autoPopupCompletionList()) );
00215
00216 }
00217 }
00218
00219
00220 void DocWordCompletionPluginView::autoPopupCompletionList()
00221 {
00222 if ( ! m_view->hasFocus() ) return;
00223 QString w = word();
00224 if ( w.length() >= d->treshold )
00225 {
00226 popupCompletionList( w );
00227 }
00228 }
00229
00230
00231 void DocWordCompletionPluginView::shellComplete()
00232 {
00233
00234 KTextEditor::EditInterface * ei = KTextEditor::editInterface(m_view->document());
00235
00236 uint cline, ccol;
00237 viewCursorInterface(m_view)->cursorPositionReal(&cline, &ccol);
00238 QString wrd = word();
00239 if (wrd.isEmpty())
00240 return;
00241
00242 QValueList < KTextEditor::CompletionEntry > matches = allMatches(wrd);
00243 if (matches.size() == 0)
00244 return;
00245 QString partial = findLongestUnique(matches);
00246 if (partial.length() == wrd.length())
00247 {
00248 KTextEditor::CodeCompletionInterface * cci = codeCompletionInterface(m_view);
00249 cci->showCompletionBox(matches, wrd.length());
00250 }
00251 else
00252 {
00253 partial.remove(0, wrd.length());
00254 ei->insertText(cline, ccol, partial);
00255 }
00256 }
00257
00258
00259
00260 void DocWordCompletionPluginView::complete( bool fw )
00261 {
00262
00263 KTextEditor::EditInterface *ei = KTextEditor::editInterface( m_view->document() );
00264
00265 uint cline, ccol;
00266 viewCursorInterface( m_view )->cursorPositionReal( &cline, &ccol );
00267 QString wrd = word();
00268 if ( wrd.isEmpty() )
00269 return;
00270
00271 int inc = fw ? 1 : -1;
00272
00273
00274
00275
00276
00277
00278
00279 if ( cline == d-> cline &&
00280 ccol - d->lilen == d->ccol &&
00281 wrd.endsWith( d->lastIns ) )
00282 {
00283
00284
00285
00286 if ( ( fw && d->directionalPos == -1 ) ||
00287 ( !fw && d->directionalPos == 1 ) )
00288 {
00289 if ( d->lilen )
00290 ei->removeText( d->cline, d->ccol, d->cline, d->ccol + d->lilen );
00291
00292 d->lastIns = "";
00293 d->lilen = 0;
00294 d->line = d->cline;
00295 d->col = d->ccol;
00296 d->directionalPos = 0;
00297
00298 return;
00299 }
00300
00301 if ( fw )
00302 d->col += d->lilen;
00303
00304 ccol = d->ccol;
00305 wrd = d->last;
00306
00307 d->directionalPos += inc;
00308 }
00309 else
00310 {
00311 d->cline = cline;
00312 d->ccol = ccol;
00313 d->last = wrd;
00314 d->lastIns = "";
00315 d->line = cline;
00316 d->col = ccol - wrd.length();
00317 d->lilen = 0;
00318 d->directionalPos = inc;
00319 }
00320
00321 d->re.setPattern( "\\b" + wrd + "(\\w+)" );
00322 int pos ( 0 );
00323 QString ln = ei->textLine( d->line );
00324
00325 while ( true )
00326 {
00327 pos = fw ?
00328 d->re.search( ln, d->col ) :
00329 d->re.searchRev( ln, d->col );
00330
00331 if ( pos > -1 )
00332 {
00333 QString m = d->re.cap( 1 );
00334 if ( m != d->lastIns )
00335 {
00336
00337 if ( d->lilen )
00338 ei->removeText( d->cline, d->ccol, d->cline, d->ccol + d->lilen );
00339 ei->insertText( d->cline, d->ccol, m );
00340
00341 d->lastIns = m;
00342 d->lilen = m.length();
00343 d->col = pos;
00344
00345 return;
00346 }
00347
00348
00349 else
00350 {
00351 d->col = pos;
00352
00353 if ( fw )
00354 d->col += d->re.matchedLength();
00355
00356 else
00357 {
00358 if ( pos == 0 )
00359 {
00360 if ( d->line > 0 )
00361 {
00362 d->line += inc;
00363 ln = ei->textLine( d->line );
00364 d->col = ln.length();
00365 }
00366 else
00367 {
00368 KNotifyClient::beep();
00369 return;
00370 }
00371 }
00372
00373 else
00374 d->col--;
00375 }
00376 }
00377 }
00378
00379 else
00380 {
00381 if ( (! fw && d->line == 0 ) || ( fw && d->line >= (uint)ei->numLines() ) )
00382 {
00383 KNotifyClient::beep();
00384 return;
00385 }
00386
00387 d->line += inc;
00388
00389 ln = ei->textLine( d->line );
00390 d->col = fw ? 0 : ln.length();
00391 }
00392 }
00393 }
00394
00395
00396 QString DocWordCompletionPluginView::findLongestUnique(const QValueList < KTextEditor::CompletionEntry > &matches)
00397 {
00398 QString partial = matches.front().text;
00399 QValueList < KTextEditor::CompletionEntry >::const_iterator i = matches.begin();
00400 for (++i; i != matches.end(); ++i)
00401 {
00402 if (!(*i).text.startsWith(partial))
00403 {
00404 while(partial.length() > 0)
00405 {
00406 partial.remove(partial.length() - 1, 1);
00407 if ((*i).text.startsWith(partial))
00408 {
00409 break;
00410 }
00411 }
00412 if (partial.length() == 0)
00413 return QString();
00414 }
00415 }
00416
00417 return partial;
00418 }
00419
00420
00421 QString DocWordCompletionPluginView::word()
00422 {
00423 uint cline, ccol;
00424 viewCursorInterface( m_view )->cursorPositionReal( &cline, &ccol );
00425 if ( ! ccol ) return QString::null;
00426 KTextEditor::EditInterface *ei = KTextEditor::editInterface( m_view->document() );
00427 d->re.setPattern( "\\b(\\w+)$" );
00428 if ( d->re.searchRev(
00429 ei->text( cline, 0, cline, ccol )
00430 ) < 0 )
00431 return QString::null;
00432 return d->re.cap( 1 );
00433 }
00434
00435
00436
00437 QValueList<KTextEditor::CompletionEntry> DocWordCompletionPluginView::allMatches( const QString &word )
00438 {
00439 QValueList<KTextEditor::CompletionEntry> l;
00440 uint i( 0 );
00441 int pos( 0 );
00442 d->re.setPattern( "\\b("+word+"\\w+)" );
00443 QString s, m;
00444 KTextEditor::EditInterface *ei = KTextEditor::editInterface( m_view->document() );
00445 QDict<int> seen;
00446 int sawit(1);
00447 uint cline, ccol;
00448 viewCursorInterface( m_view )->cursorPositionReal( &cline, &ccol );
00449
00450 while( i < ei->numLines() )
00451 {
00452 s = ei->textLine( i );
00453 pos = 0;
00454 while ( pos >= 0 )
00455 {
00456 pos = d->re.search( s, pos );
00457 if ( pos >= 0 )
00458 {
00459
00460 if ( i == cline && pos + word.length() == ccol )
00461 {
00462 pos += word.length();
00463 continue;
00464 }
00465
00466 m = d->re.cap( 1 );
00467 if ( ! seen[ m ] ) {
00468 seen.insert( m, &sawit );
00469 KTextEditor::CompletionEntry e;
00470 e.text = m;
00471 l.append( e );
00472 }
00473 pos += d->re.matchedLength();
00474 }
00475 }
00476 i++;
00477 }
00478 return l;
00479 }
00480
00481 void DocWordCompletionPluginView::slotVariableChanged( const QString &var, const QString &val )
00482 {
00483 if ( var == "wordcompletion-autopopup" )
00484 d->autopopup->setEnabled( val == "true" );
00485 else if ( var == "wordcompletion-treshold" )
00486 d->treshold = val.toInt();
00487 }
00488
00489
00490
00491 DocWordCompletionConfigPage::DocWordCompletionConfigPage( DocWordCompletionPlugin *completion, QWidget *parent, const char *name )
00492 : KTextEditor::ConfigPage( parent, name )
00493 , m_completion( completion )
00494 {
00495 QVBoxLayout *lo = new QVBoxLayout( this );
00496 lo->setSpacing( KDialog::spacingHint() );
00497
00498 cbAutoPopup = new QCheckBox( i18n("Automatically &show completion list"), this );
00499 lo->addWidget( cbAutoPopup );
00500
00501 QHBox *hb = new QHBox( this );
00502 hb->setSpacing( KDialog::spacingHint() );
00503 lo->addWidget( hb );
00504 QLabel *l = new QLabel( i18n(
00505 "Translators: This is the first part of two strings wich will comprise the "
00506 "sentence 'Show completions when a word is at least N characters'. The first "
00507 "part is on the right side of the N, which is represented by a spinbox "
00508 "widget, followed by the second part: 'characters long'. Characters is a "
00509 "ingeger number between and including 1 and 30. Feel free to leave the "
00510 "second part of the sentence blank if it suits your language better. ",
00511 "Show completions &when a word is at least"), hb );
00512 sbAutoPopup = new QSpinBox( 1, 30, 1, hb );
00513 l->setBuddy( sbAutoPopup );
00514 lSbRight = new QLabel( i18n(
00515 "This is the second part of two strings that will comprise teh sentence "
00516 "'Show completions when a word is at least N characters'",
00517 "characters long."), hb );
00518
00519 QWhatsThis::add( cbAutoPopup, i18n(
00520 "Enable the automatic completion list popup as default. The popup can "
00521 "be disabled on a view basis from the 'Tools' menu.") );
00522 QWhatsThis::add( sbAutoPopup, i18n(
00523 "Define the length a word should have before the completion list "
00524 "is displayed.") );
00525
00526 cbAutoPopup->setChecked( m_completion->autoPopupEnabled() );
00527 sbAutoPopup->setValue( m_completion->treshold() );
00528
00529 lo->addStretch();
00530 }
00531
00532 void DocWordCompletionConfigPage::apply()
00533 {
00534 m_completion->setAutoPopupEnabled( cbAutoPopup->isChecked() );
00535 m_completion->setTreshold( sbAutoPopup->value() );
00536 m_completion->writeConfig();
00537 }
00538
00539 void DocWordCompletionConfigPage::reset()
00540 {
00541 cbAutoPopup->setChecked( m_completion->autoPopupEnabled() );
00542 sbAutoPopup->setValue( m_completion->treshold() );
00543 }
00544
00545 void DocWordCompletionConfigPage::defaults()
00546 {
00547 cbAutoPopup->setChecked( true );
00548 sbAutoPopup->setValue( 3 );
00549 }
00550
00551
00552
00553 #include "docwordcompletion.moc"
00554