kate Library API Documentation

katesearch.cpp

00001 /* This file is part of the KDE libraries
00002    Copyright (C) 2003 Clarence Dang <dang@kde.org>
00003    Copyright (C) 2002 John Firebaugh <jfirebaugh@kde.org>
00004    Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org>
00005    Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org>
00006    Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de>
00007 
00008    This library is free software; you can redistribute it and/or
00009    modify it under the terms of the GNU Library General Public
00010    License version 2 as published by the Free Software Foundation.
00011 
00012    This library is distributed in the hope that it will be useful,
00013    but WITHOUT ANY WARRANTY; without even the implied warranty of
00014    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00015    Library General Public License for more details.
00016 
00017    You should have received a copy of the GNU Library General Public License
00018    along with this library; see the file COPYING.LIB.  If not, write to
00019    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00020    Boston, MA 02111-1307, USA.
00021 */
00022 
00023 #include "katesearch.h"
00024 #include "katesearch.moc"
00025 
00026 #include "kateview.h"
00027 #include "katedocument.h"
00028 #include "katesupercursor.h"
00029 #include "katearbitraryhighlight.h"
00030 #include "kateconfig.h"
00031 
00032 #include <klocale.h>
00033 #include <kstdaction.h>
00034 #include <kmessagebox.h>
00035 #include <kstringhandler.h>
00036 #include <kdebug.h>
00037 #include <kfinddialog.h>
00038 #include <kreplacedialog.h>
00039 
00040 #include <qlayout.h>
00041 #include <qlabel.h>
00042 
00043 QStringList KateSearch::s_searchList  = QStringList();
00044 QStringList KateSearch::s_replaceList = QStringList();
00045 static const bool arbitraryHLExample = false;
00046 
00047 KateSearch::KateSearch( KateView* view )
00048   : QObject( view, "kate search" )
00049   , m_view( view )
00050   , m_doc( view->doc() )
00051   , replacePrompt( new ReplacePrompt( view ) )
00052 {
00053   m_arbitraryHLList = new KateSuperRangeList();
00054   if (arbitraryHLExample) m_doc->arbitraryHL()->addHighlightToView(m_arbitraryHLList, m_view);
00055 
00056   connect(replacePrompt,SIGNAL(clicked()),this,SLOT(replaceSlot()));
00057 }
00058 
00059 KateSearch::~KateSearch()
00060 {
00061   delete m_arbitraryHLList;
00062 }
00063 
00064 void KateSearch::createActions( KActionCollection* ac )
00065 {
00066   KStdAction::find( this, SLOT(find()), ac )->setWhatsThis(
00067     i18n("Look up the first occurrence of a piece of text or regular expression."));
00068   KStdAction::findNext( this, SLOT(slotFindNext()), ac )->setWhatsThis(
00069     i18n("Look up the next occurrence of the search phrase."));
00070   KStdAction::findPrev( this, SLOT(slotFindPrev()), ac, "edit_find_prev" )->setWhatsThis(
00071     i18n("Look up the previous occurrence of the search phrase."));
00072   KStdAction::replace( this, SLOT(replace()), ac )->setWhatsThis(
00073     i18n("Look up a piece of text or regular expression and replace the result with some given text."));
00074 }
00075 
00076 void KateSearch::addToList( QStringList& list, const QString& s )
00077 {
00078   if( list.count() > 0 ) {
00079     QStringList::Iterator it = list.find( s );
00080     if( *it != 0L )
00081       list.remove( it );
00082     if( list.count() >= 16 )
00083       list.remove( list.fromLast() );
00084   }
00085   list.prepend( s );
00086 }
00087 
00088 void KateSearch::find()
00089 {
00090   KFindDialog *findDialog = new KFindDialog (  m_view, "", KateViewConfig::global()->searchFlags(),
00091                                                s_searchList, m_doc->hasSelection() );
00092 
00093   findDialog->setPattern (getSearchText());
00094 
00095   if( findDialog->exec() == QDialog::Accepted ) {
00096     s_searchList =  findDialog->findHistory () ;
00097     KateViewConfig::global()->setSearchFlags(findDialog->options ());
00098 
00099     SearchFlags searchFlags;
00100 
00101     searchFlags.caseSensitive = KateViewConfig::global()->searchFlags() & KFindDialog::CaseSensitive;
00102     searchFlags.wholeWords = KateViewConfig::global()->searchFlags() & KFindDialog::WholeWordsOnly;
00103     searchFlags.fromBeginning = !(KateViewConfig::global()->searchFlags() & KFindDialog::FromCursor)
00104                                && !(KateViewConfig::global()->searchFlags() & KFindDialog::SelectedText);
00105     searchFlags.backward = KateViewConfig::global()->searchFlags() & KFindDialog::FindBackwards;
00106     searchFlags.selected = KateViewConfig::global()->searchFlags() & KFindDialog::SelectedText;
00107     searchFlags.prompt = false;
00108     searchFlags.replace = false;
00109     searchFlags.finished = false;
00110     searchFlags.regExp = KateViewConfig::global()->searchFlags() & KFindDialog::RegularExpression;
00111     if ( searchFlags.selected )
00112     {
00113       s.selBegin = KateTextCursor( doc()->selStartLine(), doc()->selStartCol() );
00114       s.selEnd   = KateTextCursor( doc()->selEndLine(),   doc()->selEndCol()   );
00115       s.cursor   = s.flags.backward ? s.selEnd : s.selBegin;
00116     } else {
00117       s.cursor = getCursor();
00118     }
00119 
00120     search( searchFlags );
00121   }
00122 
00123   delete findDialog;
00124   m_view->repaintText ();
00125 }
00126 
00127 void KateSearch::replace()
00128 {
00129   if (!doc()->isReadWrite()) return;
00130 
00131   KReplaceDialog *replaceDialog = new KReplaceDialog (  m_view, "", KateViewConfig::global()->searchFlags(),
00132                                                s_searchList, s_replaceList, m_doc->hasSelection() );
00133 
00134   replaceDialog->setPattern (getSearchText());
00135 
00136   if( replaceDialog->exec() == QDialog::Accepted ) {
00137     m_replacement = replaceDialog->replacement();
00138     s_searchList = replaceDialog->findHistory () ;
00139     s_replaceList = replaceDialog->replacementHistory () ;
00140     KateViewConfig::global()->setSearchFlags(replaceDialog->options ());
00141 
00142     SearchFlags searchFlags;
00143     searchFlags.caseSensitive = KateViewConfig::global()->searchFlags() & KFindDialog::CaseSensitive;
00144     searchFlags.wholeWords = KateViewConfig::global()->searchFlags() & KFindDialog::WholeWordsOnly;
00145     searchFlags.fromBeginning = !(KateViewConfig::global()->searchFlags() & KFindDialog::FromCursor)
00146                                && !(KateViewConfig::global()->searchFlags() & KFindDialog::SelectedText);
00147     searchFlags.backward = KateViewConfig::global()->searchFlags() & KFindDialog::FindBackwards;
00148     searchFlags.selected = KateViewConfig::global()->searchFlags() & KFindDialog::SelectedText;
00149     searchFlags.prompt = KateViewConfig::global()->searchFlags() & KReplaceDialog::PromptOnReplace;
00150     searchFlags.replace = true;
00151     searchFlags.finished = false;
00152     searchFlags.regExp = KateViewConfig::global()->searchFlags() & KFindDialog::RegularExpression;
00153     if ( searchFlags.selected )
00154     {
00155       s.selBegin = KateTextCursor( doc()->selStartLine(), doc()->selStartCol() );
00156       s.selEnd   = KateTextCursor( doc()->selEndLine(),   doc()->selEndCol()   );
00157       s.cursor   = s.flags.backward ? s.selEnd : s.selBegin;
00158     } else {
00159       s.cursor = getCursor();
00160     }
00161 
00162     search( searchFlags );
00163   }
00164 
00165   delete replaceDialog;
00166   m_view->update ();
00167 }
00168 
00169 void KateSearch::findAgain( bool back )
00170 {
00171   SearchFlags searchFlags;
00172   searchFlags.caseSensitive = KateViewConfig::global()->searchFlags() & KFindDialog::CaseSensitive;
00173   searchFlags.wholeWords = KateViewConfig::global()->searchFlags() & KFindDialog::WholeWordsOnly;
00174   searchFlags.fromBeginning = !(KateViewConfig::global()->searchFlags() & KFindDialog::FromCursor)
00175                                && !(KateViewConfig::global()->searchFlags() & KFindDialog::SelectedText);
00176   searchFlags.backward = KateViewConfig::global()->searchFlags() & KFindDialog::FindBackwards;
00177   searchFlags.selected = KateViewConfig::global()->searchFlags() & KFindDialog::SelectedText;
00178   searchFlags.prompt = KateViewConfig::global()->searchFlags() & KReplaceDialog::PromptOnReplace;
00179   searchFlags.replace = false;
00180   searchFlags.finished = false;
00181   searchFlags.regExp = KateViewConfig::global()->searchFlags() & KFindDialog::RegularExpression;
00182 
00183   searchFlags.backward = searchFlags.backward != back;
00184   searchFlags.fromBeginning = false;
00185   searchFlags.prompt = true;
00186   s.cursor = getCursor();
00187 
00188   search( searchFlags );
00189 }
00190 
00191 void KateSearch::search( SearchFlags flags )
00192 {
00193   s.flags = flags;
00194 
00195   if( s.flags.fromBeginning ) {
00196     if( !s.flags.backward ) {
00197       s.cursor.setPos(0, 0);
00198     } else {
00199       s.cursor.setLine(doc()->numLines() - 1);
00200       s.cursor.setCol(doc()->lineLength( s.cursor.line() ));
00201     }
00202   }
00203 
00204   if((!s.flags.backward &&
00205        s.cursor.col() == 0 &&
00206        s.cursor.line() == 0 ) ||
00207      ( s.flags.backward &&
00208        s.cursor.col() == doc()->lineLength( s.cursor.line() ) &&
00209        s.cursor.line() == (((int)doc()->numLines()) - 1) ) ) {
00210     s.flags.finished = true;
00211   }
00212 
00213   if( s.flags.replace ) {
00214     replaces = 0;
00215     if( s.flags.prompt )
00216       promptReplace();
00217     else
00218       replaceAll();
00219   } else {
00220     findAgain();
00221   }
00222 }
00223 
00224 void KateSearch::wrapSearch()
00225 {
00226   if( s.flags.selected )
00227   {
00228       s.cursor = s.flags.backward ? s.selEnd : s.selBegin;
00229   }
00230   else
00231   {
00232     if( !s.flags.backward ) {
00233       s.cursor.setPos(0, 0);
00234     } else {
00235       s.cursor.setLine(doc()->numLines() - 1);
00236       s.cursor.setCol(doc()->lineLength( s.cursor.line() ));
00237     }
00238   }
00239   replaces = 0;
00240   s.flags.finished = true;
00241 }
00242 
00243 void KateSearch::findAgain()
00244 {
00245   QString searchFor = s_searchList.first();
00246 
00247   if( searchFor.isEmpty() ) {
00248     find();
00249     return;
00250   }
00251 
00252   if ( doSearch( searchFor ) ) {
00253     exposeFound( s.cursor, s.matchedLength );
00254   } else if( !s.flags.finished ) {
00255     if( askContinue() ) {
00256       wrapSearch();
00257       findAgain();
00258     } else {
00259       if (arbitraryHLExample) m_arbitraryHLList->clear();
00260     }
00261   } else {
00262     if (arbitraryHLExample) m_arbitraryHLList->clear();
00263     KMessageBox::sorry( view(),
00264         i18n("Search string '%1' not found!")
00265              .arg( KStringHandler::csqueeze( searchFor ) ),
00266         i18n("Find"));
00267   }
00268 }
00269 
00270 void KateSearch::replaceAll()
00271 {
00272   QString searchFor = s_searchList.first();
00273 
00274   doc()->editStart ();
00275 
00276   while( doSearch( searchFor ) )
00277     replaceOne();
00278 
00279   doc()->editEnd ();
00280 
00281   if( !s.flags.finished ) {
00282     if( askContinue() ) {
00283       wrapSearch();
00284       replaceAll();
00285     }
00286   } else {
00287     KMessageBox::information( view(),
00288         i18n("%n replacement made.","%n replacements made.",replaces),
00289         i18n("Replace") );
00290   }
00291 }
00292 
00293 void KateSearch::promptReplace()
00294 {
00295   QString searchFor = s_searchList.first();
00296   if ( doSearch( searchFor ) ) {
00297     exposeFound( s.cursor, s.matchedLength );
00298     replacePrompt->show();
00299   } else if( !s.flags.finished && askContinue() ) {
00300     wrapSearch();
00301     promptReplace();
00302   } else {
00303     if (arbitraryHLExample) m_arbitraryHLList->clear();
00304     replacePrompt->hide();
00305     KMessageBox::information( view(),
00306         i18n("%n replacement made.","%n replacements made.",replaces),
00307         i18n("Replace") );
00308   }
00309 }
00310 
00311 void KateSearch::replaceOne()
00312 {
00313   QString replaceWith = m_replacement;
00314   if ( s.flags.regExp ) {
00315     // replace each "(?!\)\d+" with the corresponding capture
00316     QRegExp br("\\\\(\\d+)");
00317     int pos = br.search( replaceWith );
00318     int ncaps = m_re.numCaptures();
00319     while ( pos >= 0 ) {
00320       QString sc;
00321       if ( !pos ||  replaceWith.at( pos-1) != '\\' ) {
00322         int ccap = br.cap(1).toInt();
00323         if (ccap <= ncaps ) {
00324           sc = m_re.cap( ccap );
00325           replaceWith.replace( pos, br.matchedLength(), sc );
00326         }
00327         else {
00328           // TODO add a sanity check at some point prior to this
00329           kdDebug()<<"KateSearch::replaceOne(): you don't have "<<ccap<<" backreferences in regexp '"<<m_re.pattern()<<"'"<<endl;
00330         }
00331       }
00332       pos = br.search( replaceWith, pos+QMAX(br.matchedLength(), (int)sc.length()) );
00333     }
00334   }
00335 
00336   doc()->editStart();
00337   doc()->removeText( s.cursor.line(), s.cursor.col(),
00338       s.cursor.line(), s.cursor.col() + s.matchedLength );
00339   doc()->insertText( s.cursor.line(), s.cursor.col(), replaceWith );
00340   doc()->editEnd(),
00341 
00342   replaces++;
00343 
00344   if( s.flags.selected && s.cursor.line() == s.selEnd.line() )
00345   {
00346     s.selEnd.setCol(s.selEnd.col() + replaceWith.length() - s.matchedLength );
00347   }
00348 
00349   if( !s.flags.backward ) {
00350     s.cursor.setCol(s.cursor.col() + replaceWith.length());
00351   } else if( s.cursor.col() > 0 ) {
00352     s.cursor.setCol(s.cursor.col() - 1);
00353   } else {
00354     s.cursor.setLine(s.cursor.line() - 1);
00355     if( s.cursor.line() >= 0 ) {
00356       s.cursor.setCol(doc()->lineLength( s.cursor.line() ));
00357     }
00358   }
00359 }
00360 
00361 void KateSearch::skipOne()
00362 {
00363   if( !s.flags.backward ) {
00364     s.cursor.setCol(s.cursor.col() + s.matchedLength);
00365   } else if( s.cursor.col() > 0 ) {
00366     s.cursor.setCol(s.cursor.col() - 1);
00367   } else {
00368     s.cursor.setLine(s.cursor.line() - 1);
00369     if( s.cursor.line() >= 0 ) {
00370       s.cursor.setCol(doc()->lineLength(s.cursor.line()));
00371     }
00372   }
00373 }
00374 
00375 void KateSearch::replaceSlot() {
00376   switch( (Dialog_results)replacePrompt->result() ) {
00377   case srCancel: replacePrompt->hide();                break;
00378   case srAll:    replacePrompt->hide(); replaceAll();  break;
00379   case srYes:    replaceOne(); promptReplace();        break;
00380   case srLast:   replacePrompt->hide(), replaceOne();  break;
00381   case srNo:     skipOne();    promptReplace();        break;
00382   }
00383 }
00384 
00385 bool KateSearch::askContinue()
00386 {
00387   QString made =
00388      i18n( "%n replacement made.",
00389            "%n replacements made.",
00390            replaces );
00391 
00392   QString reached = !s.flags.backward ?
00393      i18n( "End of document reached." ) :
00394      i18n( "Beginning of document reached." );
00395 
00396   if (KateViewConfig::global()->searchFlags() & KFindDialog::SelectedText)
00397   {
00398     reached = !s.flags.backward ?
00399      i18n( "End of selection reached." ) :
00400      i18n( "Beginning of selection reached." );
00401   }
00402 
00403   QString question = !s.flags.backward ?
00404      i18n( "Continue from the beginning?" ) :
00405      i18n( "Continue from the end?" );
00406 
00407   QString text = s.flags.replace ?
00408      made + "\n" + reached + "\n" + question :
00409      reached + "\n" + question;
00410 
00411   return KMessageBox::Yes == KMessageBox::questionYesNo(
00412      view(), text, s.flags.replace ? i18n("Replace") : i18n("Find"),
00413      i18n("&Continue"), i18n("&Stop") );
00414 }
00415 
00416 QString KateSearch::getSearchText()
00417 {
00418   // SelectionOnly: use selection
00419   // WordOnly: use word under cursor
00420   // SelectionWord: use selection if available, else use word under cursor
00421   // WordSelection: use word if available, else use selection
00422   QString str;
00423 
00424   int getFrom = view()->config()->textToSearchMode();
00425   switch (getFrom)
00426   {
00427   case KateViewConfig::SelectionOnly: // (Windows)
00428     //kdDebug() << "getSearchText(): SelectionOnly" << endl;
00429     if( doc()->hasSelection() )
00430       str = doc()->selection();
00431     break;
00432 
00433   case KateViewConfig::SelectionWord: // (classic Kate behavior)
00434     //kdDebug() << "getSearchText(): SelectionWord" << endl;
00435     if( doc()->hasSelection() )
00436       str = doc()->selection();
00437     else
00438       str = view()->currentWord();
00439     break;
00440 
00441   case KateViewConfig::WordOnly: // (weird?)
00442     //kdDebug() << "getSearchText(): WordOnly" << endl;
00443     str = view()->currentWord();
00444     break;
00445 
00446   case KateViewConfig::WordSelection: // (persistent selection lover)
00447     //kdDebug() << "getSearchText(): WordSelection" << endl;
00448     str = view()->currentWord();
00449     if (str.isEmpty() && doc()->hasSelection() )
00450       str = doc()->selection();
00451     break;
00452 
00453   default: // (nowhere)
00454     //kdDebug() << "getSearchText(): Nowhere" << endl;
00455     break;
00456   }
00457 
00458   str.replace( QRegExp("^\\n"), "" );
00459   str.replace( QRegExp("\\n.*"), "" );
00460 
00461   return str;
00462 }
00463 
00464 KateTextCursor KateSearch::getCursor()
00465 {
00466   return KateTextCursor(view()->cursorLine(), view()->cursorColumnReal());
00467 }
00468 
00469 bool KateSearch::doSearch( const QString& text )
00470 {
00471 /*
00472   rodda: Still Working on this... :)
00473 
00474   bool result = false;
00475 
00476   if (m_searchResults.count()) {
00477     m_resultIndex++;
00478     if (m_resultIndex < (int)m_searchResults.count()) {
00479       s = m_searchResults[m_resultIndex];
00480       result = true;
00481     }
00482 
00483   } else {
00484     int temp = 0;
00485     do {*/
00486 
00487 #if 0
00488   static int oldLine = -1;
00489   static int oldCol = -1;
00490 #endif
00491 
00492   uint line = s.cursor.line();
00493   uint col = s.cursor.col();// + (result ? s.matchedLength : 0);
00494   bool backward = s.flags.backward;
00495   bool caseSensitive = s.flags.caseSensitive;
00496   bool regExp = s.flags.regExp;
00497   bool wholeWords = s.flags.wholeWords;
00498   uint foundLine, foundCol, matchLen;
00499   bool found = false;
00500   //kdDebug() << "Searching at " << line << ", " << col << endl;
00501   if( regExp ) {
00502     m_re = QRegExp( text, caseSensitive );
00503     found = doc()->searchText( line, col, m_re,
00504                               &foundLine, &foundCol,
00505                               &matchLen, backward );
00506   } else if ( wholeWords ) {
00507     QRegExp re( "\\b" + text + "\\b", caseSensitive );
00508     found = doc()->searchText( line, col, re,
00509                               &foundLine, &foundCol,
00510                               &matchLen, backward );
00511   } else {
00512     found = doc()->searchText( line, col, text,
00513                               &foundLine, &foundCol,
00514                               &matchLen, caseSensitive, backward );
00515   }
00516   if ( found && s.flags.selected )
00517   {
00518     if ( !s.flags.backward && KateTextCursor( foundLine, foundCol ) >= s.selEnd
00519       ||  s.flags.backward && KateTextCursor( foundLine, foundCol ) < s.selBegin )
00520       found = false;
00521   }
00522   if( !found ) return false; //break;
00523 
00524   //result = true;
00525 
00526   s.cursor.setPos(foundLine, foundCol);
00527 
00528   //kdDebug() << "Found at " << s.cursor.line() << ", " << s.cursor.col() << endl;
00529   s.matchedLength = matchLen;
00530 
00531   //m_searchResults.append(s);
00532 
00533   if (arbitraryHLExample)  {
00534     ArbitraryHighlightRange* hl = new ArbitraryHighlightRange(new KateSuperCursor(m_doc, true, s.cursor), new KateSuperCursor(m_doc, true, s.cursor.line(), s.cursor.col() + s.matchedLength), this);
00535     hl->setBold();
00536     hl->setTextColor(Qt::white);
00537     hl->setBGColor(Qt::black);
00538     // destroy the highlight upon change
00539     connect(hl, SIGNAL(contentsChanged()), hl, SIGNAL(eliminated()));
00540     m_arbitraryHLList->append(hl);
00541   }
00542 
00543   return true;
00544 
00545     /* rodda: more of my search highlighting work
00546 
00547     } while (++temp < 100);
00548 
00549     if (result) {
00550       s = m_searchResults.first();
00551       m_resultIndex = 0;
00552     }
00553   }
00554 
00555   return result;*/
00556 }
00557 
00558 void KateSearch::exposeFound( KateTextCursor &cursor, int slen )
00559 {
00560   view()->setCursorPositionInternal ( cursor.line(), cursor.col() + slen, 1 );
00561   doc()->setSelection( cursor.line(), cursor.col(), cursor.line(), cursor.col() + slen );
00562 }
00563 
00564 //BEGIN ReplacePrompt
00565 // this dialog is not modal
00566 ReplacePrompt::ReplacePrompt( QWidget *parent )
00567   : KDialogBase(parent, 0L, false, i18n( "Replace Text" ),
00568   User3 | User2 | User1 | Close | Ok , Ok, true,
00569   i18n("&All"), i18n("&Last"), i18n("&No") ) {
00570 
00571   setButtonOK( KStdGuiItem::yes() );
00572   QWidget *page = new QWidget(this);
00573   setMainWidget(page);
00574 
00575   QBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() );
00576   QLabel *label = new QLabel(i18n("Replace this occurrence?"),page);
00577   topLayout->addWidget(label );
00578 }
00579 
00580 void ReplacePrompt::slotOk( void ) { // Yes
00581   done(KateSearch::srYes);
00582 }
00583 
00584 void ReplacePrompt::slotClose( void ) { // Close
00585   done(KateSearch::srCancel);
00586 }
00587 
00588 void ReplacePrompt::slotUser1( void ) { // All
00589   done(KateSearch::srAll);
00590 }
00591 
00592 void ReplacePrompt::slotUser2( void ) { // Last
00593   done(KateSearch::srLast);
00594 }
00595 
00596 void ReplacePrompt::slotUser3( void ) { // No
00597   done(KateSearch::srNo);
00598 }
00599 
00600 void ReplacePrompt::done(int r) {
00601   setResult(r);
00602   emit clicked();
00603 }
00604 //END ReplacePrompt
00605 
00606 // kate: space-indent on; indent-width 2; replace-tabs on;
KDE Logo
This file is part of the documentation for kate Library Version 3.2.0.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Wed Feb 4 12:37:42 2004 by doxygen 1.2.18 written by Dimitri van Heesch, © 1997-2003