katesearch.cpp

00001 /* This file is part of the KDE libraries
00002    Copyright (C) 2004-2005 Anders Lund <anders@alweb.dk>
00003    Copyright (C) 2003 Clarence Dang <dang@kde.org>
00004    Copyright (C) 2002 John Firebaugh <jfirebaugh@kde.org>
00005    Copyright (C) 2001-2004 Christoph Cullmann <cullmann@kde.org>
00006    Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org>
00007    Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de>
00008 
00009    This library is free software; you can redistribute it and/or
00010    modify it under the terms of the GNU Library General Public
00011    License version 2 as published by the Free Software Foundation.
00012 
00013    This library is distributed in the hope that it will be useful,
00014    but WITHOUT ANY WARRANTY; without even the implied warranty of
00015    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016    Library General Public License for more details.
00017 
00018    You should have received a copy of the GNU Library General Public License
00019    along with this library; see the file COPYING.LIB.  If not, write to
00020    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00021    Boston, MA 02110-1301, USA.
00022 */
00023 
00024 #include "katesearch.h"
00025 #include "katesearch.moc"
00026 
00027 #include "kateview.h"
00028 #include "katedocument.h"
00029 #include "katesupercursor.h"
00030 #include "katearbitraryhighlight.h"
00031 #include "kateconfig.h"
00032 #include "katehighlight.h"
00033 
00034 #include <klocale.h>
00035 #include <kstdaction.h>
00036 #include <kmessagebox.h>
00037 #include <kstringhandler.h>
00038 #include <kdebug.h>
00039 #include <kfinddialog.h>
00040 #include <kreplacedialog.h>
00041 #include <kpushbutton.h>
00042 
00043 #include <qlayout.h>
00044 #include <qlabel.h>
00045 
00046 //BEGIN KateSearch
00047 QStringList KateSearch::s_searchList  = QStringList();
00048 QStringList KateSearch::s_replaceList = QStringList();
00049 QString KateSearch::s_pattern = QString();
00050 static const bool arbitraryHLExample = false;
00051 
00052 KateSearch::KateSearch( KateView* view )
00053   : QObject( view, "kate search" )
00054   , m_view( view )
00055   , m_doc( view->doc() )
00056   , replacePrompt( new KateReplacePrompt( view ) )
00057 {
00058   m_arbitraryHLList = new KateSuperRangeList();
00059   if (arbitraryHLExample) m_doc->arbitraryHL()->addHighlightToView(m_arbitraryHLList, m_view);
00060 
00061   connect(replacePrompt,SIGNAL(clicked()),this,SLOT(replaceSlot()));
00062 }
00063 
00064 KateSearch::~KateSearch()
00065 {
00066   delete m_arbitraryHLList;
00067 }
00068 
00069 void KateSearch::createActions( KActionCollection* ac )
00070 {
00071   KStdAction::find( this, SLOT(find()), ac )->setWhatsThis(
00072     i18n("Look up the first occurrence of a piece of text or regular expression."));
00073   KStdAction::findNext( this, SLOT(slotFindNext()), ac )->setWhatsThis(
00074     i18n("Look up the next occurrence of the search phrase."));
00075   KStdAction::findPrev( this, SLOT(slotFindPrev()), ac, "edit_find_prev" )->setWhatsThis(
00076     i18n("Look up the previous occurrence of the search phrase."));
00077   KStdAction::replace( this, SLOT(replace()), ac )->setWhatsThis(
00078     i18n("Look up a piece of text or regular expression and replace the result with some given text."));
00079 }
00080 
00081 void KateSearch::addToList( QStringList& list, const QString& s )
00082 {
00083   if( list.count() > 0 ) {
00084     QStringList::Iterator it = list.find( s );
00085     if( *it != 0L )
00086       list.remove( it );
00087     if( list.count() >= 16 )
00088       list.remove( list.fromLast() );
00089   }
00090   list.prepend( s );
00091 }
00092 
00093 void KateSearch::find()
00094 {
00095   // if multiline selection around, search in it
00096   long searchf = KateViewConfig::global()->searchFlags();
00097   if (m_view->hasSelection() && m_view->selStartLine() != m_view->selEndLine())
00098     searchf |= KFindDialog::SelectedText;
00099 
00100   KFindDialog *findDialog = new KFindDialog (  m_view, "", searchf,
00101                                                s_searchList, m_view->hasSelection() );
00102 
00103   findDialog->setPattern (getSearchText());
00104 
00105 
00106   if( findDialog->exec() == QDialog::Accepted ) {
00107     s_searchList =  findDialog->findHistory () ;
00108     // Do *not* remove the QString() wrapping, it fixes a nasty crash
00109     find( QString(s_searchList.first()), findDialog->options(), true, true );
00110   }
00111 
00112   delete findDialog;
00113   m_view->repaintText ();
00114 }
00115 
00116 void KateSearch::find( const QString &pattern, long flags, bool add, bool shownotfound )
00117 {
00118   KateViewConfig::global()->setSearchFlags( flags );
00119   if( add )
00120     addToList( s_searchList, pattern );
00121 
00122    s_pattern = pattern;
00123 
00124   SearchFlags searchFlags;
00125 
00126   searchFlags.caseSensitive = KateViewConfig::global()->searchFlags() & KFindDialog::CaseSensitive;
00127   searchFlags.wholeWords = KateViewConfig::global()->searchFlags() & KFindDialog::WholeWordsOnly;
00128   searchFlags.fromBeginning = !(KateViewConfig::global()->searchFlags() & KFindDialog::FromCursor)
00129       && !(KateViewConfig::global()->searchFlags() & KFindDialog::SelectedText);
00130   searchFlags.backward = KateViewConfig::global()->searchFlags() & KFindDialog::FindBackwards;
00131   searchFlags.selected = KateViewConfig::global()->searchFlags() & KFindDialog::SelectedText;
00132   searchFlags.prompt = false;
00133   searchFlags.replace = false;
00134   searchFlags.finished = false;
00135   searchFlags.regExp = KateViewConfig::global()->searchFlags() & KFindDialog::RegularExpression;
00136   searchFlags.useBackRefs = KateViewConfig::global()->searchFlags() & KReplaceDialog::BackReference;
00137 
00138   if ( searchFlags.selected )
00139   {
00140     s.selBegin = KateTextCursor( m_view->selStartLine(), m_view->selStartCol() );
00141     s.selEnd   = KateTextCursor( m_view->selEndLine(),   m_view->selEndCol()   );
00142     s.cursor   = s.flags.backward ? s.selEnd : s.selBegin;
00143   } else {
00144     s.cursor = getCursor( searchFlags );
00145   }
00146 
00147   s.wrappedEnd = s.cursor;
00148   s.wrapped = false;
00149   s.showNotFound = shownotfound;
00150 
00151   search( searchFlags );
00152 }
00153 
00154 void KateSearch::replace()
00155 {
00156   if (!doc()->isReadWrite()) return;
00157 
00158   // if multiline selection around, search in it
00159   long searchf = KateViewConfig::global()->searchFlags();
00160   if (m_view->hasSelection() && m_view->selStartLine() != m_view->selEndLine())
00161     searchf |= KFindDialog::SelectedText;
00162 
00163   KReplaceDialog *replaceDialog = new KReplaceDialog (  m_view, "", searchf,
00164                                                s_searchList, s_replaceList, m_view->hasSelection() );
00165 
00166   replaceDialog->setPattern (getSearchText());
00167 
00168   if( replaceDialog->exec() == QDialog::Accepted ) {
00169     long opts = replaceDialog->options();
00170     m_replacement = replaceDialog->replacement();
00171     s_searchList = replaceDialog->findHistory () ;
00172     s_replaceList = replaceDialog->replacementHistory () ;
00173 
00174     // Do *not* remove the QString() wrapping, it fixes a nasty crash
00175     replace( QString(s_searchList.first()), m_replacement, opts );
00176   }
00177 
00178   delete replaceDialog;
00179   m_view->update ();
00180 }
00181 
00182 void KateSearch::replace( const QString& pattern, const QString &replacement, long flags )
00183 {
00184   if (!doc()->isReadWrite()) return;
00185 
00186   addToList( s_searchList, pattern );
00187    s_pattern = pattern;
00188   addToList( s_replaceList, replacement );
00189   m_replacement = replacement;
00190   KateViewConfig::global()->setSearchFlags( flags );
00191 
00192   SearchFlags searchFlags;
00193   searchFlags.caseSensitive = KateViewConfig::global()->searchFlags() & KFindDialog::CaseSensitive;
00194   searchFlags.wholeWords = KateViewConfig::global()->searchFlags() & KFindDialog::WholeWordsOnly;
00195   searchFlags.fromBeginning = !(KateViewConfig::global()->searchFlags() & KFindDialog::FromCursor)
00196       && !(KateViewConfig::global()->searchFlags() & KFindDialog::SelectedText);
00197   searchFlags.backward = KateViewConfig::global()->searchFlags() & KFindDialog::FindBackwards;
00198   searchFlags.selected = KateViewConfig::global()->searchFlags() & KFindDialog::SelectedText;
00199   searchFlags.prompt = KateViewConfig::global()->searchFlags() & KReplaceDialog::PromptOnReplace;
00200   searchFlags.replace = true;
00201   searchFlags.finished = false;
00202   searchFlags.regExp = KateViewConfig::global()->searchFlags() & KFindDialog::RegularExpression;
00203   searchFlags.useBackRefs = KateViewConfig::global()->searchFlags() & KReplaceDialog::BackReference;
00204   if ( searchFlags.selected )
00205   {
00206     s.selBegin = KateTextCursor( m_view->selStartLine(), m_view->selStartCol() );
00207     s.selEnd   = KateTextCursor( m_view->selEndLine(), m_view->selEndCol()   );
00208     s.cursor   = s.flags.backward ? s.selEnd : s.selBegin;
00209   } else {
00210     s.cursor = getCursor( searchFlags );
00211   }
00212 
00213   s.wrappedEnd = s.cursor;
00214   s.wrapped = false;
00215 
00216   search( searchFlags );
00217 }
00218 
00219 void KateSearch::findAgain( bool reverseDirection )
00220 {
00221   SearchFlags searchFlags;
00222   searchFlags.caseSensitive = KateViewConfig::global()->searchFlags() & KFindDialog::CaseSensitive;
00223   searchFlags.wholeWords = KateViewConfig::global()->searchFlags() & KFindDialog::WholeWordsOnly;
00224   searchFlags.fromBeginning = !(KateViewConfig::global()->searchFlags() & KFindDialog::FromCursor)
00225                                && !(KateViewConfig::global()->searchFlags() & KFindDialog::SelectedText);
00226   searchFlags.backward = KateViewConfig::global()->searchFlags() & KFindDialog::FindBackwards;
00227   searchFlags.selected = KateViewConfig::global()->searchFlags() & KFindDialog::SelectedText;
00228   searchFlags.prompt = KateViewConfig::global()->searchFlags() & KReplaceDialog::PromptOnReplace;
00229   searchFlags.replace = false;
00230   searchFlags.finished = false;
00231   searchFlags.regExp = KateViewConfig::global()->searchFlags() & KFindDialog::RegularExpression;
00232   searchFlags.useBackRefs = KateViewConfig::global()->searchFlags() & KReplaceDialog::BackReference;
00233 
00234   if (reverseDirection)
00235     searchFlags.backward = !searchFlags.backward;
00236 
00237   searchFlags.fromBeginning = false;
00238   searchFlags.prompt = true; // ### why is the above assignment there?
00239 
00240   s.cursor = getCursor( searchFlags );
00241   search( searchFlags );
00242 }
00243 
00244 void KateSearch::search( SearchFlags flags )
00245 {
00246   s.flags = flags;
00247 
00248   if( s.flags.fromBeginning ) {
00249     if( !s.flags.backward ) {
00250       s.cursor.setPos(0, 0);
00251     } else {
00252       s.cursor.setLine(doc()->numLines() - 1);
00253       s.cursor.setCol(doc()->lineLength( s.cursor.line() ));
00254     }
00255   }
00256 
00257   if((!s.flags.backward &&
00258        s.cursor.col() == 0 &&
00259        s.cursor.line() == 0 ) ||
00260      ( s.flags.backward &&
00261        s.cursor.col() == doc()->lineLength( s.cursor.line() ) &&
00262        s.cursor.line() == (((int)doc()->numLines()) - 1) ) ) {
00263     s.flags.finished = true;
00264   }
00265 
00266   if( s.flags.replace ) {
00267     replaces = 0;
00268     if( s.flags.prompt )
00269       promptReplace();
00270     else
00271       replaceAll();
00272   } else {
00273     findAgain();
00274   }
00275 }
00276 
00277 void KateSearch::wrapSearch()
00278 {
00279   if( s.flags.selected )
00280   {
00281     KateTextCursor start (s.selBegin);
00282     KateTextCursor end (s.selEnd);
00283 
00284     // recalc for block sel, to have start with lowest col, end with highest
00285     if (m_view->blockSelectionMode())
00286     {
00287       start.setCol (kMin(s.selBegin.col(), s.selEnd.col()));
00288       end.setCol (kMax(s.selBegin.col(), s.selEnd.col()));
00289     }
00290 
00291     s.cursor = s.flags.backward ? end : start;
00292   }
00293   else
00294   {
00295     if( !s.flags.backward ) {
00296       s.cursor.setPos(0, 0);
00297     } else {
00298       s.cursor.setLine(doc()->numLines() - 1);
00299       s.cursor.setCol(doc()->lineLength( s.cursor.line() ) );
00300     }
00301   }
00302 
00303   // oh, we wrapped around one time allready now !
00304   // only check that on replace
00305   s.wrapped = s.flags.replace;
00306 
00307   replaces = 0;
00308   s.flags.finished = true;
00309 }
00310 
00311 void KateSearch::findAgain()
00312 {
00313   if(  s_pattern.isEmpty() ) {
00314     find();
00315     return;
00316   }
00317 
00318   if ( doSearch(  s_pattern ) ) {
00319     exposeFound( s.cursor, s.matchedLength );
00320   } else if( !s.flags.finished ) {
00321     if( askContinue() ) {
00322       wrapSearch();
00323       findAgain();
00324     } else {
00325       if (arbitraryHLExample) m_arbitraryHLList->clear();
00326     }
00327   } else {
00328     if (arbitraryHLExample) m_arbitraryHLList->clear();
00329     if ( s.showNotFound )
00330       KMessageBox::sorry( view(),
00331         i18n("Search string '%1' not found!")
00332              .arg( KStringHandler::csqueeze(  s_pattern ) ),
00333         i18n("Find"));
00334   }
00335 }
00336 
00337 void KateSearch::replaceAll()
00338 {
00339   doc()->editStart ();
00340 
00341   while( doSearch(  s_pattern ) )
00342     replaceOne();
00343 
00344   doc()->editEnd ();
00345 
00346   if( !s.flags.finished ) {
00347     if( askContinue() ) {
00348       wrapSearch();
00349       replaceAll();
00350     }
00351   } else {
00352     KMessageBox::information( view(),
00353         i18n("%n replacement made.","%n replacements made.",replaces),
00354         i18n("Replace") );
00355   }
00356 }
00357 
00358 void KateSearch::promptReplace()
00359 {
00360   if ( doSearch(  s_pattern ) ) {
00361     exposeFound( s.cursor, s.matchedLength );
00362     replacePrompt->show();
00363     replacePrompt->setFocus ();
00364   } else if( !s.flags.finished && askContinue() ) {
00365     wrapSearch();
00366     promptReplace();
00367   } else {
00368     if (arbitraryHLExample) m_arbitraryHLList->clear();
00369     replacePrompt->hide();
00370     KMessageBox::information( view(),
00371         i18n("%n replacement made.","%n replacements made.",replaces),
00372         i18n("Replace") );
00373   }
00374 }
00375 
00376 void KateSearch::replaceOne()
00377 {
00378   QString replaceWith = m_replacement;
00379   if ( s.flags.regExp && s.flags.useBackRefs ) {
00380     // replace each "(?!\)\d+" with the corresponding capture
00381     QRegExp br("\\\\(\\d+)");
00382     int pos = br.search( replaceWith );
00383     int ncaps = m_re.numCaptures();
00384     while ( pos >= 0 ) {
00385       QString sc;
00386       if ( !pos ||  replaceWith.at( pos-1) != '\\' ) {
00387         int ccap = br.cap(1).toInt();
00388         if (ccap <= ncaps ) {
00389           sc = m_re.cap( ccap );
00390           replaceWith.replace( pos, br.matchedLength(), sc );
00391         }
00392         else {
00393           kdDebug()<<"KateSearch::replaceOne(): you don't have "<<ccap<<" backreferences in regexp '"<<m_re.pattern()<<"'"<<endl;
00394         }
00395       }
00396       pos = br.search( replaceWith, pos+kMax(br.matchedLength(), (int)sc.length()) );
00397     }
00398   }
00399 
00400   doc()->editStart();
00401   doc()->removeText( s.cursor.line(), s.cursor.col(),
00402       s.cursor.line(), s.cursor.col() + s.matchedLength );
00403   doc()->insertText( s.cursor.line(), s.cursor.col(), replaceWith );
00404   doc()->editEnd(),
00405 
00406   replaces++;
00407 
00408   // if we inserted newlines, we better adjust.
00409   uint newlines = replaceWith.contains('\n');
00410   if ( newlines )
00411   {
00412     if ( ! s.flags.backward )
00413     {
00414       s.cursor.setLine( s.cursor.line() + newlines );
00415       s.cursor.setCol( replaceWith.length() - replaceWith.findRev('\n') );
00416     }
00417     // selection?
00418     if ( s.flags.selected )
00419       s.selEnd.setLine( s.selEnd.line() + newlines );
00420   }
00421 
00422 
00423   // adjust selection endcursor if needed
00424   if( s.flags.selected && s.cursor.line() == s.selEnd.line() )
00425   {
00426     s.selEnd.setCol(s.selEnd.col() + replaceWith.length() - s.matchedLength );
00427   }
00428 
00429   // adjust wrap cursor if needed
00430   if( s.cursor.line() == s.wrappedEnd.line() && s.cursor.col() <= s.wrappedEnd.col())
00431   {
00432     s.wrappedEnd.setCol(s.wrappedEnd.col() + replaceWith.length() - s.matchedLength );
00433   }
00434 
00435   if( !s.flags.backward ) {
00436     s.cursor.setCol(s.cursor.col() + replaceWith.length());
00437   } else if( s.cursor.col() > 0 ) {
00438     s.cursor.setCol(s.cursor.col() - 1);
00439   } else {
00440     s.cursor.setLine(s.cursor.line() - 1);
00441     if( s.cursor.line() >= 0 ) {
00442       s.cursor.setCol(doc()->lineLength( s.cursor.line() ));
00443     }
00444   }
00445 }
00446 
00447 void KateSearch::skipOne()
00448 {
00449   if( !s.flags.backward ) {
00450     s.cursor.setCol(s.cursor.col() + s.matchedLength);
00451   } else if( s.cursor.col() > 0 ) {
00452     s.cursor.setCol(s.cursor.col() - 1);
00453   } else {
00454     s.cursor.setLine(s.cursor.line() - 1);
00455     if( s.cursor.line() >= 0 ) {
00456       s.cursor.setCol(doc()->lineLength(s.cursor.line()));
00457     }
00458   }
00459 }
00460 
00461 void KateSearch::replaceSlot() {
00462   switch( (Dialog_results)replacePrompt->result() ) {
00463   case srCancel: replacePrompt->hide();                break;
00464   case srAll:    replacePrompt->hide(); replaceAll();  break;
00465   case srYes:    replaceOne(); promptReplace();        break;
00466   case srLast:   replacePrompt->hide(), replaceOne();  break;
00467   case srNo:     skipOne();    promptReplace();        break;
00468   }
00469 }
00470 
00471 bool KateSearch::askContinue()
00472 {
00473   QString made =
00474      i18n( "%n replacement made.",
00475            "%n replacements made.",
00476            replaces );
00477 
00478   QString reached = !s.flags.backward ?
00479      i18n( "End of document reached." ) :
00480      i18n( "Beginning of document reached." );
00481 
00482   if (KateViewConfig::global()->searchFlags() & KFindDialog::SelectedText)
00483   {
00484     reached = !s.flags.backward ?
00485      i18n( "End of selection reached." ) :
00486      i18n( "Beginning of selection reached." );
00487   }
00488 
00489   QString question = !s.flags.backward ?
00490      i18n( "Continue from the beginning?" ) :
00491      i18n( "Continue from the end?" );
00492 
00493   QString text = s.flags.replace ?
00494      made + "\n" + reached + "\n" + question :
00495      reached + "\n" + question;
00496 
00497   return KMessageBox::Yes == KMessageBox::questionYesNo(
00498      view(), text, s.flags.replace ? i18n("Replace") : i18n("Find"),
00499      KStdGuiItem::cont(), i18n("&Stop") );
00500 }
00501 
00502 QString KateSearch::getSearchText()
00503 {
00504   // SelectionOnly: use selection
00505   // WordOnly: use word under cursor
00506   // SelectionWord: use selection if available, else use word under cursor
00507   // WordSelection: use word if available, else use selection
00508   QString str;
00509 
00510   int getFrom = view()->config()->textToSearchMode();
00511   switch (getFrom)
00512   {
00513   case KateViewConfig::SelectionOnly: // (Windows)
00514     //kdDebug() << "getSearchText(): SelectionOnly" << endl;
00515     if( m_view->hasSelection() )
00516       str = m_view->selection();
00517     break;
00518 
00519   case KateViewConfig::SelectionWord: // (classic Kate behavior)
00520     //kdDebug() << "getSearchText(): SelectionWord" << endl;
00521     if( m_view->hasSelection() )
00522       str = m_view->selection();
00523     else
00524       str = view()->currentWord();
00525     break;
00526 
00527   case KateViewConfig::WordOnly: // (weird?)
00528     //kdDebug() << "getSearchText(): WordOnly" << endl;
00529     str = view()->currentWord();
00530     break;
00531 
00532   case KateViewConfig::WordSelection: // (persistent selection lover)
00533     //kdDebug() << "getSearchText(): WordSelection" << endl;
00534     str = view()->currentWord();
00535     if (str.isEmpty() && m_view->hasSelection() )
00536       str = m_view->selection();
00537     break;
00538 
00539   default: // (nowhere)
00540     //kdDebug() << "getSearchText(): Nowhere" << endl;
00541     break;
00542   }
00543 
00544   str.replace( QRegExp("^\\n"), "" );
00545   str.replace( QRegExp("\\n.*"), "" );
00546 
00547   return str;
00548 }
00549 
00550 KateTextCursor KateSearch::getCursor( SearchFlags flags )
00551 {
00552   if (flags.backward && !flags.selected && view()->hasSelection())
00553   {
00554     // We're heading backwards (and not within a selection),
00555     // the selection might start before the cursor.
00556     return kMin( KateTextCursor(view()->selStartLine(), view()->selStartCol()),
00557                  KateTextCursor(view()->cursorLine(), view()->cursorColumnReal()));
00558   }
00559   return KateTextCursor(view()->cursorLine(), view()->cursorColumnReal());
00560 }
00561 
00562 bool KateSearch::doSearch( const QString& text )
00563 {
00564 /*
00565   rodda: Still Working on this... :)
00566 
00567   bool result = false;
00568 
00569   if (m_searchResults.count()) {
00570     m_resultIndex++;
00571     if (m_resultIndex < (int)m_searchResults.count()) {
00572       s = m_searchResults[m_resultIndex];
00573       result = true;
00574     }
00575 
00576   } else {
00577     int temp = 0;
00578     do {*/
00579 
00580 #if 0
00581   static int oldLine = -1;
00582   static int oldCol = -1;
00583 #endif
00584 
00585   uint line = s.cursor.line();
00586   uint col = s.cursor.col();// + (result ? s.matchedLength : 0);
00587   bool backward = s.flags.backward;
00588   bool caseSensitive = s.flags.caseSensitive;
00589   bool regExp = s.flags.regExp;
00590   bool wholeWords = s.flags.wholeWords;
00591   uint foundLine, foundCol, matchLen;
00592   bool found = false;
00593   //kdDebug() << "Searching at " << line << ", " << col << endl;
00594 //   kdDebug()<<"KateSearch::doSearch: "<<line<<", "<<col<<", "<<backward<<endl;
00595 
00596   if (backward)
00597   {
00598     KateDocCursor docCursor(line, col, doc());
00599 
00600     // If we're at the top of the document, we're not gonna find anything, so bail.
00601     if (docCursor.line() == 0 && docCursor.col() == 0)
00602       return false;
00603 
00604     // Move one step backward before searching, if this is a "find again", we don't
00605     // want to find the same match.
00606     docCursor.moveBackward(1);
00607     line = docCursor.line();
00608     col = docCursor.col();
00609   }
00610 
00611   do {
00612       if( regExp ) {
00613         m_re = QRegExp( text, caseSensitive );
00614         found = doc()->searchText( line, col, m_re,
00615                                   &foundLine, &foundCol,
00616                                   &matchLen, backward );
00617       }
00618       else if ( wholeWords )
00619       {
00620         bool maybefound = false;
00621         do
00622         {
00623           maybefound = doc()->searchText( line, col, text,
00624                                   &foundLine, &foundCol,
00625                                   &matchLen, caseSensitive, backward );
00626           if ( maybefound )
00627           {
00628             found = (
00629                       ( foundCol == 0 ||
00630                         ! doc()->highlight()->isInWord( doc()->textLine( foundLine ).at( foundCol - 1 ) ) ) &&
00631                       ( foundCol + matchLen == doc()->lineLength( foundLine ) ||
00632                         ! doc()->highlight()->isInWord( doc()->textLine( foundLine ).at( foundCol + matchLen ) ) )
00633                     );
00634             if ( found )
00635             {
00636               break;
00637             }
00638             else
00639             {
00640               line = foundLine;
00641               col = foundCol + 1;
00642             }
00643           }
00644         } while ( maybefound );
00645       }
00646       else {
00647         found = doc()->searchText( line, col, text,
00648                                   &foundLine, &foundCol,
00649                                   &matchLen, caseSensitive, backward );
00650       }
00651 
00652     if ( found && s.flags.selected )
00653     {
00654       KateTextCursor start (s.selBegin);
00655       KateTextCursor end (s.selEnd);
00656 
00657       // recalc for block sel, to have start with lowest col, end with highest
00658       if (m_view->blockSelectionMode())
00659       {
00660         start.setCol (kMin(s.selBegin.col(), s.selEnd.col()));
00661         end.setCol (kMax(s.selBegin.col(), s.selEnd.col()));
00662       }
00663 
00664       if ( !s.flags.backward && KateTextCursor( foundLine, foundCol ) >= end
00665         ||  s.flags.backward && KateTextCursor( foundLine, foundCol ) < start )
00666       {
00667         found = false;
00668       }
00669       else if (m_view->blockSelectionMode())
00670       {
00671         if ((int)foundCol >= start.col() && (int)foundCol < end.col())
00672           break;
00673       }
00674     }
00675 
00676     line = foundLine;
00677     col = foundCol+1;
00678   }
00679   while (s.flags.selected && m_view->blockSelectionMode() && found);
00680   // in the case we want to search in selection + blockselection we need to loop
00681 
00682   if( !found ) return false;
00683 
00684   // save the search result
00685   s.cursor.setPos(foundLine, foundCol);
00686   s.matchedLength = matchLen;
00687 
00688   // we allready wrapped around one time
00689   if (s.wrapped)
00690   {
00691     if (s.flags.backward)
00692     {
00693       if ( (s.cursor.line() < s.wrappedEnd.line())
00694            || ( (s.cursor.line() == s.wrappedEnd.line()) && ((s.cursor.col()+matchLen) <= uint(s.wrappedEnd.col())) ) )
00695         return false;
00696     }
00697     else
00698     {
00699       if ( (s.cursor.line() > s.wrappedEnd.line())
00700            || ( (s.cursor.line() == s.wrappedEnd.line()) && (s.cursor.col() > s.wrappedEnd.col()) ) )
00701         return false;
00702     }
00703   }
00704 
00705 //   kdDebug() << "Found at " << s.cursor.line() << ", " << s.cursor.col() << endl;
00706 
00707 
00708   //m_searchResults.append(s);
00709 
00710   if (arbitraryHLExample)  {
00711     KateArbitraryHighlightRange* hl = new KateArbitraryHighlightRange(new KateSuperCursor(m_doc, true, s.cursor), new KateSuperCursor(m_doc, true, s.cursor.line(), s.cursor.col() + s.matchedLength), this);
00712     hl->setBold();
00713     hl->setTextColor(Qt::white);
00714     hl->setBGColor(Qt::black);
00715     // destroy the highlight upon change
00716     connect(hl, SIGNAL(contentsChanged()), hl, SIGNAL(eliminated()));
00717     m_arbitraryHLList->append(hl);
00718   }
00719 
00720   return true;
00721 
00722     /* rodda: more of my search highlighting work
00723 
00724     } while (++temp < 100);
00725 
00726     if (result) {
00727       s = m_searchResults.first();
00728       m_resultIndex = 0;
00729     }
00730   }
00731 
00732   return result;*/
00733 }
00734 
00735 void KateSearch::exposeFound( KateTextCursor &cursor, int slen )
00736 {
00737   view()->setCursorPositionInternal ( cursor.line(), cursor.col() + slen, 1 );
00738   view()->setSelection( cursor.line(), cursor.col(), cursor.line(), cursor.col() + slen );
00739   view()->syncSelectionCache();
00740 }
00741 //END KateSearch
00742 
00743 //BEGIN KateReplacePrompt
00744 // this dialog is not modal
00745 KateReplacePrompt::KateReplacePrompt ( QWidget *parent )
00746   : KDialogBase ( parent, 0L, false, i18n( "Replace Confirmation" ),
00747                   User3 | User2 | User1 | Close | Ok , Ok, true,
00748                   i18n("Replace &All"), i18n("Re&place && Close"), i18n("&Replace") )
00749 {
00750   setButtonOK( i18n("&Find Next") );
00751   QWidget *page = new QWidget(this);
00752   setMainWidget(page);
00753 
00754   QBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() );
00755   QLabel *label = new QLabel(i18n("Found an occurrence of your search term. What do you want to do?"),page);
00756   topLayout->addWidget(label );
00757 }
00758 
00759 void KateReplacePrompt::slotOk ()
00760 { // Search Next
00761   done(KateSearch::srNo);
00762   actionButton(Ok)->setFocus();
00763 }
00764 
00765 void KateReplacePrompt::slotClose ()
00766 { // Close
00767   done(KateSearch::srCancel);
00768   actionButton(Close)->setFocus();
00769 }
00770 
00771 void KateReplacePrompt::slotUser1 ()
00772 { // Replace All
00773   done(KateSearch::srAll);
00774   actionButton(User1)->setFocus();
00775 }
00776 
00777 void KateReplacePrompt::slotUser2 ()
00778 { // Replace & Close
00779   done(KateSearch::srLast);
00780   actionButton(User2)->setFocus();
00781 }
00782 
00783 void KateReplacePrompt::slotUser3 ()
00784 { // Replace
00785   done(KateSearch::srYes);
00786   actionButton(User3)->setFocus();
00787 }
00788 
00789 void KateReplacePrompt::done (int result)
00790 {
00791   setResult(result);
00792 
00793   emit clicked();
00794 }
00795 //END KateReplacePrompt
00796 
00797 //BEGIN SearchCommand
00798 bool SearchCommand::exec(class Kate::View *view, const QString &cmd, QString &msg)
00799 {
00800   QString flags, pattern, replacement;
00801   if ( cmd.startsWith( "find" ) )
00802   {
00803 
00804     static QRegExp re_find("find(?::([bcersw]*))?\\s+(.+)");
00805     if ( re_find.search( cmd ) < 0 )
00806     {
00807       msg = i18n("Usage: find[:[bcersw]] PATTERN");
00808       return false;
00809     }
00810     flags = re_find.cap( 1 );
00811     pattern = re_find.cap( 2 );
00812   }
00813 
00814   else if ( cmd.startsWith( "ifind" ) )
00815   {
00816     static QRegExp re_ifind("ifind(?::([bcrs]*))?\\s+(.*)");
00817     if ( re_ifind.search( cmd ) < 0 )
00818     {
00819       msg = i18n("Usage: ifind[:[bcrs]] PATTERN");
00820       return false;
00821     }
00822     ifindClear();
00823     return true;
00824   }
00825 
00826   else if ( cmd.startsWith( "replace" ) )
00827   {
00828     // Try if the pattern and replacement is quoted, using a quote character ["']
00829     static QRegExp re_rep("replace(?::([bceprsw]*))?\\s+([\"'])((?:[^\\\\\\\\2]|\\\\.)*)\\2\\s+\\2((?:[^\\\\\\\\2]|\\\\.)*)\\2\\s*$");
00830     // Or one quoted argument
00831     QRegExp re_rep1("replace(?::([bceprsw]*))?\\s+([\"'])((?:[^\\\\\\\\2]|\\\\.)*)\\2\\s*$");
00832     // Else, it's just one or two (space separated) words
00833     QRegExp re_rep2("replace(?::([bceprsw]*))?\\s+(\\S+)(.*)");
00834 #define unbackslash(s) p=0;\
00835 while ( (p = pattern.find( '\\' + delim, p )) > -1 )\
00836 {\
00837   if ( !p || pattern[p-1] != '\\' )\
00838     pattern.remove( p, 1 );\
00839   p++;\
00840 }
00841 
00842     if ( re_rep.search( cmd ) >= 0 )
00843     {
00844       flags = re_rep.cap(1);
00845       pattern = re_rep.cap( 3 );
00846       replacement = re_rep.cap( 4 );
00847 
00848       int p(0);
00849       // unbackslash backslashed delimiter strings
00850       // in pattern ..
00851       QString delim = re_rep.cap( 2 );
00852       unbackslash(pattern);
00853       // .. and in replacement
00854       unbackslash(replacement);
00855     }
00856     else if ( re_rep1.search( cmd ) >= 0 )
00857     {
00858       flags = re_rep1.cap(1);
00859       pattern = re_rep1.cap( 3 );
00860 
00861       int p(0);
00862       QString delim = re_rep1.cap( 2 );
00863       unbackslash(pattern);
00864     }
00865     else if ( re_rep2.search( cmd ) >= 0 )
00866     {
00867       flags = re_rep2.cap( 1 );
00868       pattern = re_rep2.cap( 2 );
00869       replacement = re_rep2.cap( 3 ).stripWhiteSpace();
00870     }
00871     else
00872     {
00873       msg = i18n("Usage: replace[:[bceprsw]] PATTERN [REPLACEMENT]");
00874       return false;
00875     }
00876     kdDebug()<<"replace '"<<pattern<<"' with '"<<replacement<<"'"<<endl;
00877 #undef unbackslash
00878   }
00879 
00880   long f = 0;
00881   if ( flags.contains( 'b' ) ) f |= KFindDialog::FindBackwards;
00882   if ( flags.contains( 'c' ) ) f |= KFindDialog::FromCursor;
00883   if ( flags.contains( 'e' ) ) f |= KFindDialog::SelectedText;
00884   if ( flags.contains( 'r' ) ) f |= KFindDialog::RegularExpression;
00885   if ( flags.contains( 'p' ) ) f |= KReplaceDialog::PromptOnReplace;
00886   if ( flags.contains( 's' ) ) f |= KFindDialog::CaseSensitive;
00887   if ( flags.contains( 'w' ) ) f |= KFindDialog::WholeWordsOnly;
00888 
00889   if ( cmd.startsWith( "find" ) )
00890   {
00891     ((KateView*)view)->find( pattern, f );
00892     return true;
00893   }
00894   else if ( cmd.startsWith( "replace" ) )
00895   {
00896     f |= KReplaceDialog::BackReference; // mandatory here?
00897     ((KateView*)view)->replace( pattern, replacement, f );
00898     return true;
00899   }
00900 
00901   return false;
00902 }
00903 
00904 bool SearchCommand::help(class Kate::View *, const QString &cmd, QString &msg)
00905 {
00906   if ( cmd == "find" )
00907     msg = i18n("<p>Usage: <code>find[:bcersw] PATTERN</code></p>");
00908 
00909   else if ( cmd == "ifind" )
00910     msg = i18n("<p>Usage: <code>ifind:[:bcrs] PATTERN</code>"
00911         "<br>ifind does incremental or 'as-you-type' search</p>");
00912 
00913   else
00914     msg = i18n("<p>Usage: <code>replace[:bceprsw] PATTERN REPLACEMENT</code></p>");
00915 
00916   msg += i18n(
00917       "<h4><caption>Options</h4><p>"
00918       "<b>b</b> - Search backward"
00919       "<br><b>c</b> - Search from cursor"
00920       "<br><b>r</b> - Pattern is a regular expression"
00921       "<br><b>s</b> - Case sensitive search"
00922              );
00923 
00924   if ( cmd == "find" )
00925     msg += i18n(
00926         "<br><b>e</b> - Search in selected text only"
00927         "<br><b>w</b> - Search whole words only"
00928                );
00929 
00930   if ( cmd == "replace" )
00931     msg += i18n(
00932         "<br><b>p</b> - Prompt for replace</p>"
00933         "<p>If REPLACEMENT is not present, an empty string is used.</p>"
00934         "<p>If you want to have whitespace in your PATTERN, you need to "
00935         "quote both PATTERN and REPLACEMENT with either single or double "
00936         "quotes. To have the quote characters in the strings, prepend them "
00937         "with a backslash.");
00938 
00939   msg += "</p>";
00940   return true;
00941 }
00942 
00943 QStringList SearchCommand::cmds()
00944 {
00945   QStringList l;
00946   l << "find" << "replace" << "ifind";
00947   return l;
00948 }
00949 
00950 bool SearchCommand::wantsToProcessText( const QString &cmdname )
00951 {
00952   return  cmdname == "ifind";
00953 }
00954 
00955 void SearchCommand::processText( Kate::View *view, const QString &cmd )
00956 {
00957   static QRegExp re_ifind("ifind(?::([bcrs]*))?\\s(.*)");
00958   if ( re_ifind.search( cmd ) > -1 )
00959   {
00960     QString flags = re_ifind.cap( 1 );
00961     QString pattern = re_ifind.cap( 2 );
00962 
00963 
00964     // if there is no setup, or the text length is 0, set up the properties
00965     if ( ! m_ifindFlags || pattern.isEmpty() )
00966       ifindInit( flags );
00967     // if there is no fromCursor, add it if this is not the first character
00968     else if ( ! ( m_ifindFlags & KFindDialog::FromCursor ) && ! pattern.isEmpty() )
00969       m_ifindFlags |= KFindDialog::FromCursor;
00970 
00971     // search..
00972     if ( ! pattern.isEmpty() )
00973     {
00974       KateView *v = (KateView*)view;
00975 
00976       // If it *looks like* we are continuing, place the cursor
00977       // at the beginning of the selection, so that the search continues.
00978       // ### check more carefully, like is  the cursor currently at the end
00979       // of the selection.
00980       if ( pattern.startsWith( v->selection() ) &&
00981            v->selection().length() + 1 == pattern.length() )
00982         v->setCursorPositionInternal( v->selStartLine(), v->selStartCol() );
00983 
00984       v->find( pattern, m_ifindFlags, false );
00985     }
00986   }
00987 }
00988 
00989 void SearchCommand::ifindInit( const QString &flags )
00990 {
00991   long f = 0;
00992   if ( flags.contains( 'b' ) ) f |= KFindDialog::FindBackwards;
00993   if ( flags.contains( 'c' ) ) f |= KFindDialog::FromCursor;
00994   if ( flags.contains( 'r' ) ) f |= KFindDialog::RegularExpression;
00995   if ( flags.contains( 's' ) ) f |= KFindDialog::CaseSensitive;
00996   m_ifindFlags = f;
00997 }
00998 
00999 void SearchCommand::ifindClear()
01000 {
01001   m_ifindFlags = 0;
01002 }
01003 //END SearchCommand
01004 
01005 // kate: space-indent on; indent-width 2; replace-tabs on;
KDE Home | KDE Accessibility Home | Description of Access Keys