docwordcompletion.cpp
00001 /* 00002 This library is free software; you can redistribute it and/or 00003 modify it under the terms of the GNU Library General Public 00004 License version 2 as published by the Free Software Foundation. 00005 00006 This library is distributed in the hope that it will be useful, 00007 but WITHOUT ANY WARRANTY; without even the implied warranty of 00008 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00009 Library General Public License for more details. 00010 00011 You should have received a copy of the GNU Library General Public License 00012 along with this library; see the file COPYING.LIB. If not, write to 00013 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00014 Boston, MA 02110-1301, USA. 00015 00016 --- 00017 file: docwordcompletion.cpp 00018 00019 KTextEditor plugin to autocompletion with document words. 00020 Copyright Anders Lund <anders.lund@lund.tdcadsl.dk>, 2003 00021 00022 The following completion methods are supported: 00023 * Completion with bigger matching words in 00024 either direction (backward/forward). 00025 * NOT YET Pop up a list of all bigger matching words in document 00026 00027 */ 00028 //BEGIN includes 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 <tqregexp.h> 00047 #include <tqstring.h> 00048 #include <tqdict.h> 00049 #include <tqspinbox.h> 00050 #include <tqlabel.h> 00051 #include <tqlayout.h> 00052 #include <tqhbox.h> 00053 #include <tqwhatsthis.h> 00054 #include <tqcheckbox.h> 00055 00056 // #include <kdebug.h> 00057 //END 00058 00059 //BEGIN DocWordCompletionPlugin 00060 K_EXPORT_COMPONENT_FACTORY( ktexteditor_docwordcompletion, KGenericFactory<DocWordCompletionPlugin>( "ktexteditor_docwordcompletion" ) ) 00061 DocWordCompletionPlugin::DocWordCompletionPlugin( TQObject *parent, 00062 const char* name, 00063 const TQStringList& /*args*/ ) 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, TQWidget *parent, const char *name ) 00103 { 00104 return new DocWordCompletionConfigPage( this, parent, name ); 00105 } 00106 00107 TQString DocWordCompletionPlugin::configPageName( uint ) const 00108 { 00109 return i18n("Word Completion Plugin"); 00110 } 00111 00112 TQString DocWordCompletionPlugin::configPageFullName( uint ) const 00113 { 00114 return i18n("Configure the Word Completion Plugin"); 00115 } 00116 00117 // FIXME provide sucn a icon 00118 TQPixmap DocWordCompletionPlugin::configPagePixmap( uint, int size ) const 00119 { 00120 return UserIcon( "kte_wordcompletion", size ); 00121 } 00122 //END 00123 00124 //BEGIN DocWordCompletionPluginView 00125 struct DocWordCompletionPluginViewPrivate 00126 { 00127 uint line, col; // start position of last match (where to search from) 00128 uint cline, ccol; // cursor position 00129 uint lilen; // length of last insertion 00130 TQString last; // last word we were trying to match 00131 TQString lastIns; // latest applied completion 00132 TQRegExp re; // hrm 00133 KToggleAction *autopopup; // for accessing state 00134 uint treshold; // the required length of a word before popping up the completion list automatically 00135 int directionalPos; // be able to insert "" at the correct time 00136 }; 00137 00138 DocWordCompletionPluginView::DocWordCompletionPluginView( uint treshold, bool autopopup, KTextEditor::View *view, const char *name ) 00139 : TQObject( 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 TQT_SLOT(completeBackwards()), actionCollection(), "doccomplete_bw" ); 00150 (void) new KAction( i18n("Reuse Word Below"), CTRL+Key_9, this, 00151 TQT_SLOT(completeForwards()), actionCollection(), "doccomplete_fw" ); 00152 (void) new KAction( i18n("Pop Up Completion List"), 0, this, 00153 TQT_SLOT(popupCompletionList()), actionCollection(), "doccomplete_pu" ); 00154 (void) new KAction( i18n("Shell Completion"), 0, this, 00155 TQT_SLOT(shellComplete()), actionCollection(), "doccomplete_sh" ); 00156 d->autopopup = new KToggleAction( i18n("Automatic Completion Popup"), 0, this, 00157 TQT_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 TQString e = vi->variable("wordcompletion-autopopup"); 00168 if ( ! e.isEmpty() ) 00169 d->autopopup->setEnabled( e == "true" ); 00170 00171 connect( view->document(), TQT_SIGNAL(variableChanged(const TQString &, const TQString &)), 00172 this, TQT_SLOT(slotVariableChanged(const TQString &, const TQString &)) ); 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 // Pop up the editors completion list if applicable 00192 void DocWordCompletionPluginView::popupCompletionList( TQString 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(), TQT_SIGNAL(charactersInteractivelyInserted(int ,int ,const TQString&)), 00207 this, TQT_SLOT(autoPopupCompletionList()) )) 00208 { 00209 connect( m_view->document(), TQT_SIGNAL(textChanged()), this, TQT_SLOT(autoPopupCompletionList()) ); 00210 } 00211 } else { 00212 disconnect( m_view->document(), TQT_SIGNAL(textChanged()), this, TQT_SLOT(autoPopupCompletionList()) ); 00213 disconnect( m_view->document(), TQT_SIGNAL(charactersInteractivelyInserted(int ,int ,const TQString&)), 00214 this, TQT_SLOT(autoPopupCompletionList()) ); 00215 00216 } 00217 } 00218 00219 // for autopopup FIXME - don't pop up if reuse word is inserting 00220 void DocWordCompletionPluginView::autoPopupCompletionList() 00221 { 00222 if ( ! m_view->hasFocus() ) return; 00223 TQString w = word(); 00224 if ( w.length() >= d->treshold ) 00225 { 00226 popupCompletionList( w ); 00227 } 00228 } 00229 00230 // Contributed by <brain@hdsnet.hu> 00231 void DocWordCompletionPluginView::shellComplete() 00232 { 00233 // setup 00234 KTextEditor::EditInterface * ei = KTextEditor::editInterface(m_view->document()); 00235 // find the word we are typing 00236 uint cline, ccol; 00237 viewCursorInterface(m_view)->cursorPositionReal(&cline, &ccol); 00238 TQString wrd = word(); 00239 if (wrd.isEmpty()) 00240 return; 00241 00242 TQValueList < KTextEditor::CompletionEntry > matches = allMatches(wrd); 00243 if (matches.size() == 0) 00244 return; 00245 TQString 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 // Do one completion, searching in the desired direction, 00259 // if possible 00260 void DocWordCompletionPluginView::complete( bool fw ) 00261 { 00262 // setup 00263 KTextEditor::EditInterface *ei = KTextEditor::editInterface( m_view->document() ); 00264 // find the word we are typing 00265 uint cline, ccol; 00266 viewCursorInterface( m_view )->cursorPositionReal( &cline, &ccol ); 00267 TQString wrd = word(); 00268 if ( wrd.isEmpty() ) 00269 return; 00270 00271 int inc = fw ? 1 : -1; 00272 00273 /* IF the current line is equal to the previous line 00274 AND the position - the length of the last inserted string 00275 is equal to the old position 00276 AND the lastinsertedlength last characters of the word is 00277 equal to the last inserted string 00278 */ 00279 if ( cline == d-> cline && 00280 ccol - d->lilen == d->ccol && 00281 wrd.endsWith( d->lastIns ) ) 00282 { 00283 // this is a repeted activation 00284 00285 // if we are back to where we started, reset. 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 TQString 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 ) // we matched a word 00332 { 00333 TQString m = d->re.cap( 1 ); 00334 if ( m != d->lastIns ) 00335 { 00336 // we got good a match! replace text and return. 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; // for next try 00344 00345 return; 00346 } 00347 00348 // equal to last one, continue 00349 else 00350 { 00351 d->col = pos; // for next try 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 // no match 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 } // while true 00393 } 00394 00395 // Contributed by <brain@hdsnet.hu> 00396 TQString DocWordCompletionPluginView::findLongestUnique(const TQValueList < KTextEditor::CompletionEntry > &matches) 00397 { 00398 TQString partial = matches.front().text; 00399 TQValueList < 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 TQString(); 00414 } 00415 } 00416 00417 return partial; 00418 } 00419 00420 // Return the string to complete (the letters behind the cursor) 00421 TQString DocWordCompletionPluginView::word() 00422 { 00423 uint cline, ccol; 00424 viewCursorInterface( m_view )->cursorPositionReal( &cline, &ccol ); 00425 if ( ! ccol ) return TQString::null; // no word 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 TQString::null; // no word 00432 return d->re.cap( 1 ); 00433 } 00434 00435 // Scan throught the entire document for possible completions, 00436 // ignoring any dublets 00437 TQValueList<KTextEditor::CompletionEntry> DocWordCompletionPluginView::allMatches( const TQString &word ) 00438 { 00439 TQValueList<KTextEditor::CompletionEntry> l; 00440 uint i( 0 ); 00441 int pos( 0 ); 00442 d->re.setPattern( "\\b("+word+"\\w+)" ); 00443 TQString s, m; 00444 KTextEditor::EditInterface *ei = KTextEditor::editInterface( m_view->document() ); 00445 TQDict<int> seen; // maybe slow with > 17 matches 00446 int sawit(1); // to ref for the dict 00447 uint cline, ccol;// needed to avoid constructing a word at cursor position 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 // do not construct a new word! 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 TQString &var, const TQString &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 //END 00489 00490 //BEGIN DocWordCompletionConfigPage 00491 DocWordCompletionConfigPage::DocWordCompletionConfigPage( DocWordCompletionPlugin *completion, TQWidget *parent, const char *name ) 00492 : KTextEditor::ConfigPage( parent, name ) 00493 , m_completion( completion ) 00494 { 00495 TQVBoxLayout *lo = new TQVBoxLayout( this ); 00496 lo->setSpacing( KDialog::spacingHint() ); 00497 00498 cbAutoPopup = new TQCheckBox( i18n("Automatically &show completion list"), this ); 00499 lo->addWidget( cbAutoPopup ); 00500 00501 TQHBox *hb = new TQHBox( this ); 00502 hb->setSpacing( KDialog::spacingHint() ); 00503 lo->addWidget( hb ); 00504 TQLabel *l = new TQLabel( 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 TQSpinBox( 1, 30, 1, hb ); 00513 l->setBuddy( sbAutoPopup ); 00514 lSbRight = new TQLabel( 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 TQWhatsThis::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 TQWhatsThis::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 //END DocWordCompletionConfigPage 00552 00553 #include "docwordcompletion.moc" 00554 // kate: space-indent on; indent-width 2; replace-tabs on; mixed-indent off;