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 <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 // #include <kdebug.h>
00057 //END
00058 
00059 //BEGIN DocWordCompletionPlugin
00060 K_EXPORT_COMPONENT_FACTORY( ktexteditor_docwordcompletion, KGenericFactory<DocWordCompletionPlugin>( "ktexteditor_docwordcompletion" ) )
00061 DocWordCompletionPlugin::DocWordCompletionPlugin( QObject *parent,
00062                             const char* name,
00063                             const QStringList& /*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, 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 // FIXME provide sucn a icon
00118        QPixmap 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   QString last;         // last word we were trying to match
00131   QString lastIns;      // latest applied completion
00132   QRegExp 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 };
00136 
00137 DocWordCompletionPluginView::DocWordCompletionPluginView( uint treshold, bool autopopup, KTextEditor::View *view, const char *name )
00138   : QObject( view, name ),
00139     KXMLGUIClient( view ),
00140     m_view( view ),
00141     d( new DocWordCompletionPluginViewPrivate )
00142 {
00143   d->treshold = treshold;
00144   view->insertChildClient( this );
00145   setInstance( KGenericFactory<DocWordCompletionPlugin>::instance() );
00146 
00147   (void) new KAction( i18n("Reuse Word Above"), CTRL+Key_8, this,
00148     SLOT(completeBackwards()), actionCollection(), "doccomplete_bw" );
00149   (void) new KAction( i18n("Reuse Word Below"), CTRL+Key_9, this,
00150     SLOT(completeForwards()), actionCollection(), "doccomplete_fw" );
00151   (void) new KAction( i18n("Pop Up Completion List"), 0, this,
00152     SLOT(popupCompletionList()), actionCollection(), "doccomplete_pu" );
00153   (void) new KAction( i18n("Shell Completion"), 0, this,
00154     SLOT(shellComplete()), actionCollection(), "doccomplete_sh" );
00155   d->autopopup = new KToggleAction( i18n("Automatic Completion Popup"), 0, this,
00156     SLOT(toggleAutoPopup()), actionCollection(), "enable_autopopup" );
00157 
00158   d->autopopup->setChecked( autopopup );
00159   toggleAutoPopup();
00160 
00161   setXMLFile("docwordcompletionui.rc");
00162 
00163   KTextEditor::VariableInterface *vi = KTextEditor::variableInterface( view->document() );
00164   if ( vi )
00165   {
00166     QString e = vi->variable("wordcompletion-autopopup");
00167     if ( ! e.isEmpty() )
00168       d->autopopup->setEnabled( e == "true" );
00169 
00170     connect( view->document(), SIGNAL(variableChanged(const QString &, const QString &)),
00171              this, SLOT(slotVariableChanged(const QString &, const QString &)) );
00172   }
00173 }
00174 
00175 void DocWordCompletionPluginView::settreshold( uint t )
00176 {
00177   d->treshold = t;
00178 }
00179 
00180 void DocWordCompletionPluginView::completeBackwards()
00181 {
00182   complete( false );
00183 }
00184 
00185 void DocWordCompletionPluginView::completeForwards()
00186 {
00187   complete();
00188 }
00189 
00190 // Pop up the editors completion list if applicable
00191 void DocWordCompletionPluginView::popupCompletionList( QString w )
00192 {
00193   if ( w.isEmpty() )
00194     w = word();
00195   if ( w.isEmpty() )
00196     return;
00197 
00198   KTextEditor::CodeCompletionInterface *cci = codeCompletionInterface( m_view );
00199   cci->showCompletionBox( allMatches( w ), w.length() );
00200 }
00201 
00202 void DocWordCompletionPluginView::toggleAutoPopup()
00203 {
00204   if ( d->autopopup->isChecked() ) {
00205     if ( ! connect( m_view->document(), SIGNAL(charactersInteractivelyInserted(int ,int ,const QString&)),
00206          this, SLOT(autoPopupCompletionList()) ))
00207     {
00208       connect( m_view->document(), SIGNAL(textChanged()), this, SLOT(autoPopupCompletionList()) );
00209     }
00210   } else {
00211     disconnect( m_view->document(), SIGNAL(textChanged()), this, SLOT(autoPopupCompletionList()) );
00212     disconnect( m_view->document(), SIGNAL(charactersInteractivelyInserted(int ,int ,const QString&)),
00213                 this, SLOT(autoPopupCompletionList()) );
00214 
00215   }
00216 }
00217 
00218 // for autopopup FIXME - don't pop up if reuse word is inserting
00219 void DocWordCompletionPluginView::autoPopupCompletionList()
00220 {
00221   if ( ! m_view->hasFocus() ) return;
00222   QString w = word();
00223   if ( w.length() >= d->treshold )
00224   {
00225       popupCompletionList( w );
00226   }
00227 }
00228 
00229 // Contributed by <brain@hdsnet.hu>
00230 void DocWordCompletionPluginView::shellComplete()
00231 {
00232     // setup
00233   KTextEditor::EditInterface * ei = KTextEditor::editInterface(m_view->document());
00234     // find the word we are typing
00235   uint cline, ccol;
00236   viewCursorInterface(m_view)->cursorPositionReal(&cline, &ccol);
00237   QString wrd = word();
00238   if (wrd.isEmpty())
00239     return;
00240 
00241   QValueList < KTextEditor::CompletionEntry > matches = allMatches(wrd);
00242   if (matches.size() == 0)
00243     return;
00244   QString partial = findLongestUnique(matches);
00245   if (partial.length() == wrd.length())
00246   {
00247     KTextEditor::CodeCompletionInterface * cci = codeCompletionInterface(m_view);
00248     cci->showCompletionBox(matches, wrd.length());
00249   }
00250   else
00251   {
00252     partial.remove(0, wrd.length());
00253     ei->insertText(cline, ccol, partial);
00254   }
00255 }
00256 
00257 // Do one completion, searching in the desired direction,
00258 // if possible
00259 void DocWordCompletionPluginView::complete( bool fw )
00260 {
00261   // setup
00262   KTextEditor::EditInterface *ei = KTextEditor::editInterface( m_view->document() );
00263   // find the word we are typing
00264   uint cline, ccol;
00265   viewCursorInterface( m_view )->cursorPositionReal( &cline, &ccol );
00266   QString wrd = word();
00267   if ( wrd.isEmpty() ) return;
00268 
00269   /* IF the current line is equal to the previous line
00270      AND the position - the length of the last inserted string
00271           is equal to the old position
00272      AND the lastinsertedlength last characters of the word is
00273           equal to the last inserted string
00274           */
00275   if ( cline == d-> cline &&
00276           ccol - d->lilen == d->ccol &&
00277           wrd.endsWith( d->lastIns ) )
00278   {
00279     // this is a repeted activation
00280     ccol = d->ccol;
00281     wrd = d->last;
00282   }
00283   else
00284   {
00285     d->cline = cline;
00286     d->ccol = ccol;
00287     d->last = wrd;
00288     d->lastIns = QString::null;
00289     d->line = d->cline;
00290     d->col = d->ccol - wrd.length();
00291     d->lilen = 0;
00292   }
00293 
00294   d->re.setPattern( "\\b" + wrd + "(\\w+)" );
00295   int inc = fw ? 1 : -1;
00296   int pos ( 0 );
00297   QString ln = ei->textLine( d->line );
00298 
00299   if ( ! fw )
00300     ln = ln.mid( 0, d->col );
00301 
00302   while ( true )
00303   {
00304     pos = fw ?
00305       d->re.search( ln, d->col ) :
00306       d->re.searchRev( ln, d->col );
00307 
00308     if ( pos > -1 ) // we matched a word
00309     {
00310       QString m = d->re.cap( 1 );
00311       if ( m != d->lastIns )
00312       {
00313         d->col = pos; // for next try
00314 
00315         if ( fw )
00316           d->col += m.length();
00317 
00318         // if this is a constructed word at cursor pos, retry.
00319         if ( pos + wrd.length() == ccol )
00320           continue;
00321 
00322         // we got good a match! replace text and return.
00323         if ( d->lilen )
00324           ei->removeText( d->cline, d->ccol, d->cline, d->ccol + d->lilen );
00325         ei->insertText( d->cline, d->ccol, m );
00326 
00327         d->lilen = m.length();
00328         d->lastIns = m;
00329 
00330         return;
00331       }
00332 
00333       // equal to last one, continue
00334       else
00335       {
00336         d->col = pos; // for next try
00337         if ( fw )
00338           d->col += m.length();
00339         else // FIXME figure out if all of that is really nessecary
00340         {
00341           if ( pos == 0 )
00342           {
00343             if ( d->line > 0 )
00344             {
00345               d->line += inc;
00346               ln = ei->textLine( d->line );
00347               d->col = ln.length();
00348             }
00349             else
00350             {
00351               KNotifyClient::beep();
00352               return;
00353             }
00354           }
00355           else
00356             d->col--;
00357         }
00358       }
00359     }
00360 
00361     else  // no match
00362     {
00363       if ( ! fw && d->line == 0)
00364       {
00365         KNotifyClient::beep();
00366         return;
00367       }
00368       else if ( fw && d->line >= ei->numLines() )
00369       {
00370         KNotifyClient::beep();
00371         return;
00372       }
00373 
00374       d->line += inc;
00375       if ( fw )
00376         d->col++;
00377 
00378       ln = ei->textLine( d->line );
00379       d->col = fw ? 0 : ln.length();
00380     }
00381   } // while true
00382 }
00383 
00384 // Contributed by <brain@hdsnet.hu>
00385 QString DocWordCompletionPluginView::findLongestUnique(const QValueList < KTextEditor::CompletionEntry > &matches)
00386 {
00387   QString partial = matches.front().text;
00388   QValueList < KTextEditor::CompletionEntry >::const_iterator i = matches.begin();
00389   for (++i; i != matches.end(); ++i)
00390   {
00391     if (!(*i).text.startsWith(partial))
00392     {
00393       while(partial.length() > 0)
00394       {
00395         partial.remove(partial.length() - 1, 1);
00396         if ((*i).text.startsWith(partial))
00397         {
00398           break;
00399         }
00400       }
00401       if (partial.length() == 0)
00402         return QString();
00403     }
00404   }
00405 
00406   return partial;
00407 }
00408 
00409 // Return the string to complete (the letters behind the cursor)
00410 QString DocWordCompletionPluginView::word()
00411 {
00412   uint cline, ccol;
00413   viewCursorInterface( m_view )->cursorPositionReal( &cline, &ccol );
00414   if ( ! ccol ) return QString::null; // no word
00415   KTextEditor::EditInterface *ei = KTextEditor::editInterface( m_view->document() );
00416   d->re.setPattern( "\\b(\\w+)$" );
00417   if ( d->re.searchRev(
00418         ei->text( cline, 0, cline, ccol )
00419         ) < 0 )
00420     return QString::null; // no word
00421   return d->re.cap( 1 );
00422 }
00423 
00424 // Scan throught the entire document for possible completions,
00425 // ignoring any dublets
00426 QValueList<KTextEditor::CompletionEntry> DocWordCompletionPluginView::allMatches( const QString &word )
00427 {
00428   QValueList<KTextEditor::CompletionEntry> l;
00429   uint i( 0 );
00430   int pos( 0 );
00431   d->re.setPattern( "\\b("+word+"\\w+)" );
00432   QString s, m;
00433   KTextEditor::EditInterface *ei = KTextEditor::editInterface( m_view->document() );
00434   QDict<int> seen; // maybe slow with > 17 matches
00435   int sawit(1);    // to ref for the dict
00436   uint cline, ccol;// needed to avoid constructing a word at cursor position
00437   viewCursorInterface( m_view )->cursorPositionReal( &cline, &ccol );
00438 
00439   while( i < ei->numLines() )
00440   {
00441     s = ei->textLine( i );
00442     pos = 0;
00443     while ( pos >= 0 )
00444     {
00445       pos = d->re.search( s, pos );
00446       if ( pos >= 0 )
00447       {
00448         // do not construct a new word!
00449         if ( i == cline && pos + word.length() == ccol )
00450         {
00451           pos += word.length();
00452           continue;
00453         }
00454 
00455         m = d->re.cap( 1 );
00456         if ( ! seen[ m ] ) {
00457           seen.insert( m, &sawit );
00458           KTextEditor::CompletionEntry e;
00459           e.text = m;
00460           l.append( e );
00461         }
00462         pos += d->re.matchedLength();
00463       }
00464     }
00465     i++;
00466   }
00467   return l;
00468 }
00469 
00470 void DocWordCompletionPluginView::slotVariableChanged( const QString &var, const QString &val )
00471 {
00472   if ( var == "wordcompletion-autopopup" )
00473     d->autopopup->setEnabled( val == "true" );
00474   else if ( var == "wordcompletion-treshold" )
00475     d->treshold = val.toInt();
00476 }
00477 //END
00478 
00479 //BEGIN DocWordCompletionConfigPage
00480 DocWordCompletionConfigPage::DocWordCompletionConfigPage( DocWordCompletionPlugin *completion, QWidget *parent, const char *name )
00481   : KTextEditor::ConfigPage( parent, name )
00482   , m_completion( completion )
00483 {
00484   QVBoxLayout *lo = new QVBoxLayout( this );
00485   lo->setSpacing( KDialog::spacingHint() );
00486 
00487   cbAutoPopup = new QCheckBox( i18n("Automatically &show completion list"), this );
00488   lo->addWidget( cbAutoPopup );
00489 
00490   QHBox *hb = new QHBox( this );
00491   hb->setSpacing( KDialog::spacingHint() );
00492   lo->addWidget( hb );
00493   QLabel *l = new QLabel( i18n(
00494       "Translators: This is the first part of two strings wich will comprise the "
00495       "sentence 'Show completions when a word is at least N characters'. The first "
00496       "part is on the right side of the N, which is represented by a spinbox "
00497       "widget, followed by the second part: 'characters long'. Characters is a "
00498       "ingeger number between and including 1 and 30. Feel free to leave the "
00499       "second part of the sentence blank if it suits your language better. ",
00500       "Show completions &when a word is at least"), hb );
00501   sbAutoPopup = new QSpinBox( 1, 30, 1, hb );
00502   l->setBuddy( sbAutoPopup );
00503   lSbRight = new QLabel( i18n(
00504       "This is the second part of two strings that will comprise teh sentence "
00505       "'Show completions when a word is at least N characters'",
00506       "characters long."), hb );
00507 
00508   QWhatsThis::add( cbAutoPopup, i18n(
00509       "Enable the automatic completion list popup as default. The popup can "
00510       "be disabled on a view basis from the 'Tools' menu.") );
00511   QWhatsThis::add( sbAutoPopup, i18n(
00512       "Define the length a word should have before the completion list "
00513       "is displayed.") );
00514 
00515   cbAutoPopup->setChecked( m_completion->autoPopupEnabled() );
00516   sbAutoPopup->setValue( m_completion->treshold() );
00517 
00518   lo->addStretch();
00519 }
00520 
00521 void DocWordCompletionConfigPage::apply()
00522 {
00523   m_completion->setAutoPopupEnabled( cbAutoPopup->isChecked() );
00524   m_completion->setTreshold( sbAutoPopup->value() );
00525   m_completion->writeConfig();
00526 }
00527 
00528 void DocWordCompletionConfigPage::reset()
00529 {
00530   cbAutoPopup->setChecked( m_completion->autoPopupEnabled() );
00531   sbAutoPopup->setValue( m_completion->treshold() );
00532 }
00533 
00534 void DocWordCompletionConfigPage::defaults()
00535 {
00536   cbAutoPopup->setChecked( true );
00537   sbAutoPopup->setValue( 3 );
00538 }
00539 
00540 //END DocWordCompletionConfigPage
00541 
00542 #include "docwordcompletion.moc"
00543 // kate: space-indent on; indent-width 2; replace-tabs on; mixed-indent off;
KDE Home | KDE Accessibility Home | Description of Access Keys