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 "\0"..."\9" with the corresponding capture,
00381     // "\n" and "\t" with newline and tab,
00382     // "\\" with "\",
00383     // and remove the "\" for any other sequence.
00384     QRegExp br("\\\\(.)");
00385     int pos = br.search( replaceWith );
00386     int ncaps = m_re.numCaptures();
00387     while ( pos >= 0 ) {
00388       QString substitute;
00389       QChar argument = br.cap(1).at(0);
00390       if ( argument.isDigit() ) {
00391         // the second character is a digit, this is a backreference
00392         int ccap = argument.digitValue();
00393         if (ccap <= ncaps ) {
00394           substitute = m_re.cap( ccap );
00395         } else {
00396           kdDebug()<<"KateSearch::replaceOne(): you don't have "<<ccap<<" backreferences in regexp '"<<m_re.pattern()<<"'"<<endl;
00397           break;
00398         }
00399       } else if ( argument == 'n' ) {
00400         substitute = '\n';
00401       } else if ( argument == 't' ) {
00402         substitute = '\t';
00403       } else {
00404         // handle a validly escaped backslash, or an invalid escape.
00405         substitute = argument;
00406       }
00407       replaceWith.replace( pos, br.matchedLength(), substitute );
00408       pos = br.search( replaceWith, pos + substitute.length() );
00409     }
00410   }
00411 
00412   doc()->editStart();
00413   doc()->removeText( s.cursor.line(), s.cursor.col(),
00414       s.cursor.line(), s.cursor.col() + s.matchedLength );
00415   doc()->insertText( s.cursor.line(), s.cursor.col(), replaceWith );
00416   doc()->editEnd(),
00417 
00418   replaces++;
00419 
00420   // if we inserted newlines, we better adjust.
00421   uint newlines = replaceWith.contains('\n');
00422   if ( newlines )
00423   {
00424     if ( ! s.flags.backward )
00425     {
00426       s.cursor.setLine( s.cursor.line() + newlines );
00427       s.cursor.setCol( replaceWith.length() - replaceWith.findRev('\n') );
00428     }
00429     // selection?
00430     if ( s.flags.selected )
00431       s.selEnd.setLine( s.selEnd.line() + newlines );
00432   }
00433 
00434 
00435   // adjust selection endcursor if needed
00436   if( s.flags.selected && s.cursor.line() == s.selEnd.line() )
00437   {
00438     s.selEnd.setCol(s.selEnd.col() + replaceWith.length() - s.matchedLength );
00439   }
00440 
00441   // adjust wrap cursor if needed
00442   if( s.cursor.line() == s.wrappedEnd.line() && s.cursor.col() <= s.wrappedEnd.col())
00443   {
00444     s.wrappedEnd.setCol(s.wrappedEnd.col() + replaceWith.length() - s.matchedLength );
00445   }
00446 
00447   if( !s.flags.backward ) {
00448     s.cursor.setCol(s.cursor.col() + replaceWith.length());
00449   } else if( s.cursor.col() > 0 ) {
00450     s.cursor.setCol(s.cursor.col() - 1);
00451   } else {
00452     s.cursor.setLine(s.cursor.line() - 1);
00453     if( s.cursor.line() >= 0 ) {
00454       s.cursor.setCol(doc()->lineLength( s.cursor.line() ));
00455     }
00456   }
00457 }
00458 
00459 void KateSearch::skipOne()
00460 {
00461   if( !s.flags.backward ) {
00462     s.cursor.setCol(s.cursor.col() + s.matchedLength);
00463   } else if( s.cursor.col() > 0 ) {
00464     s.cursor.setCol(s.cursor.col() - 1);
00465   } else {
00466     s.cursor.setLine(s.cursor.line() - 1);
00467     if( s.cursor.line() >= 0 ) {
00468       s.cursor.setCol(doc()->lineLength(s.cursor.line()));
00469     }
00470   }
00471 }
00472 
00473 void KateSearch::replaceSlot() {
00474   switch( (Dialog_results)replacePrompt->result() ) {
00475   case srCancel: replacePrompt->hide();                break;
00476   case srAll:    replacePrompt->hide(); replaceAll();  break;
00477   case srYes:    replaceOne(); promptReplace();        break;
00478   case srLast:   replacePrompt->hide(), replaceOne();  break;
00479   case srNo:     skipOne();    promptReplace();        break;
00480   }
00481 }
00482 
00483 bool KateSearch::askContinue()
00484 {
00485   QString made =
00486      i18n( "%n replacement made.",
00487            "%n replacements made.",
00488            replaces );
00489 
00490   QString reached = !s.flags.backward ?
00491      i18n( "End of document reached." ) :
00492      i18n( "Beginning of document reached." );
00493 
00494   if (KateViewConfig::global()->searchFlags() & KFindDialog::SelectedText)
00495   {
00496     reached = !s.flags.backward ?
00497      i18n( "End of selection reached." ) :
00498      i18n( "Beginning of selection reached." );
00499   }
00500 
00501   QString question = !s.flags.backward ?
00502      i18n( "Continue from the beginning?" ) :
00503      i18n( "Continue from the end?" );
00504 
00505   QString text = s.flags.replace ?
00506      made + "\n" + reached + "\n" + question :
00507      reached + "\n" + question;
00508 
00509   return KMessageBox::Yes == KMessageBox::questionYesNo(
00510      view(), text, s.flags.replace ? i18n("Replace") : i18n("Find"),
00511      KStdGuiItem::cont(), i18n("&Stop") );
00512 }
00513 
00514 QString KateSearch::getSearchText()
00515 {
00516   // SelectionOnly: use selection
00517   // WordOnly: use word under cursor
00518   // SelectionWord: use selection if available, else use word under cursor
00519   // WordSelection: use word if available, else use selection
00520   QString str;
00521 
00522   int getFrom = view()->config()->textToSearchMode();
00523   switch (getFrom)
00524   {
00525   case KateViewConfig::SelectionOnly: // (Windows)
00526     //kdDebug() << "getSearchText(): SelectionOnly" << endl;
00527     if( m_view->hasSelection() )
00528       str = m_view->selection();
00529     break;
00530 
00531   case KateViewConfig::SelectionWord: // (classic Kate behavior)
00532     //kdDebug() << "getSearchText(): SelectionWord" << endl;
00533     if( m_view->hasSelection() )
00534       str = m_view->selection();
00535     else
00536       str = view()->currentWord();
00537     break;
00538 
00539   case KateViewConfig::WordOnly: // (weird?)
00540     //kdDebug() << "getSearchText(): WordOnly" << endl;
00541     str = view()->currentWord();
00542     break;
00543 
00544   case KateViewConfig::WordSelection: // (persistent selection lover)
00545     //kdDebug() << "getSearchText(): WordSelection" << endl;
00546     str = view()->currentWord();
00547     if (str.isEmpty() && m_view->hasSelection() )
00548       str = m_view->selection();
00549     break;
00550 
00551   default: // (nowhere)
00552     //kdDebug() << "getSearchText(): Nowhere" << endl;
00553     break;
00554   }
00555 
00556   str.replace( QRegExp("^\\n"), "" );
00557   str.replace( QRegExp("\\n.*"), "" );
00558 
00559   return str;
00560 }
00561 
00562 KateTextCursor KateSearch::getCursor( SearchFlags flags )
00563 {
00564   if (flags.backward && !flags.selected && view()->hasSelection())
00565   {
00566     // We're heading backwards (and not within a selection),
00567     // the selection might start before the cursor.
00568     return kMin( KateTextCursor(view()->selStartLine(), view()->selStartCol()),
00569                  KateTextCursor(view()->cursorLine(), view()->cursorColumnReal()));
00570   }
00571   return KateTextCursor(view()->cursorLine(), view()->cursorColumnReal());
00572 }
00573 
00574 bool KateSearch::doSearch( const QString& text )
00575 {
00576 /*
00577   rodda: Still Working on this... :)
00578 
00579   bool result = false;
00580 
00581   if (m_searchResults.count()) {
00582     m_resultIndex++;
00583     if (m_resultIndex < (int)m_searchResults.count()) {
00584       s = m_searchResults[m_resultIndex];
00585       result = true;
00586     }
00587 
00588   } else {
00589     int temp = 0;
00590     do {*/
00591 
00592 #if 0
00593   static int oldLine = -1;
00594   static int oldCol = -1;
00595 #endif
00596 
00597   uint line = s.cursor.line();
00598   uint col = s.cursor.col();// + (result ? s.matchedLength : 0);
00599   bool backward = s.flags.backward;
00600   bool caseSensitive = s.flags.caseSensitive;
00601   bool regExp = s.flags.regExp;
00602   bool wholeWords = s.flags.wholeWords;
00603   uint foundLine, foundCol, matchLen;
00604   bool found = false;
00605   //kdDebug() << "Searching at " << line << ", " << col << endl;
00606 //   kdDebug()<<"KateSearch::doSearch: "<<line<<", "<<col<<", "<<backward<<endl;
00607 
00608   if (backward)
00609   {
00610     KateDocCursor docCursor(line, col, doc());
00611 
00612     // If we're at the top of the document, we're not gonna find anything, so bail.
00613     if (docCursor.line() == 0 && docCursor.col() == 0)
00614       return false;
00615 
00616     // Move one step backward before searching, if this is a "find again", we don't
00617     // want to find the same match.
00618     docCursor.moveBackward(1);
00619     line = docCursor.line();
00620     col = docCursor.col();
00621   }
00622 
00623   do {
00624       if( regExp ) {
00625         m_re = QRegExp( text, caseSensitive );
00626         found = doc()->searchText( line, col, m_re,
00627                                   &foundLine, &foundCol,
00628                                   &matchLen, backward );
00629       }
00630       else if ( wholeWords )
00631       {
00632         bool maybefound = false;
00633         do
00634         {
00635           maybefound = doc()->searchText( line, col, text,
00636                                   &foundLine, &foundCol,
00637                                   &matchLen, caseSensitive, backward );
00638           if ( maybefound )
00639           {
00640             found = (
00641                       ( foundCol == 0 ||
00642                         ! doc()->highlight()->isInWord( doc()->textLine( foundLine ).at( foundCol - 1 ) ) ) &&
00643                       ( foundCol + matchLen == doc()->lineLength( foundLine ) ||
00644                         ! doc()->highlight()->isInWord( doc()->textLine( foundLine ).at( foundCol + matchLen ) ) )
00645                     );
00646             if ( found )
00647             {
00648               break;
00649             }
00650             else if ( backward && foundCol == 0 ) // we are done on this line and want to avoid endless loops like in #137312
00651             {
00652               if ( line == 0 ) // we are completely done...
00653                 break;
00654               else
00655                 line--;
00656             }
00657             else
00658             {
00659               line = foundLine;
00660               col = foundCol + 1;
00661             }
00662           }
00663         } while ( maybefound );
00664       }
00665       else {
00666         found = doc()->searchText( line, col, text,
00667                                   &foundLine, &foundCol,
00668                                   &matchLen, caseSensitive, backward );
00669       }
00670 
00671     if ( found && s.flags.selected )
00672     {
00673       KateTextCursor start (s.selBegin);
00674       KateTextCursor end (s.selEnd);
00675 
00676       // recalc for block sel, to have start with lowest col, end with highest
00677       if (m_view->blockSelectionMode())
00678       {
00679         start.setCol (kMin(s.selBegin.col(), s.selEnd.col()));
00680         end.setCol (kMax(s.selBegin.col(), s.selEnd.col()));
00681       }
00682 
00683       if ( !s.flags.backward && KateTextCursor( foundLine, foundCol ) >= end
00684         ||  s.flags.backward && KateTextCursor( foundLine, foundCol ) < start )
00685       {
00686         found = false;
00687       }
00688       else if (m_view->blockSelectionMode())
00689       {
00690         if ((int)foundCol >= start.col() && (int)foundCol < end.col())
00691           break;
00692       }
00693     }
00694 
00695     line = foundLine;
00696     col = foundCol+1;
00697   }
00698   while (s.flags.selected && m_view->blockSelectionMode() && found);
00699   // in the case we want to search in selection + blockselection we need to loop
00700 
00701   if( !found ) return false;
00702 
00703   // save the search result
00704   s.cursor.setPos(foundLine, foundCol);
00705   s.matchedLength = matchLen;
00706 
00707   // we allready wrapped around one time
00708   if (s.wrapped)
00709   {
00710     if (s.flags.backward)
00711     {
00712       if ( (s.cursor.line() < s.wrappedEnd.line())
00713            || ( (s.cursor.line() == s.wrappedEnd.line()) && ((s.cursor.col()+matchLen) <= uint(s.wrappedEnd.col())) ) )
00714         return false;
00715     }
00716     else
00717     {
00718       if ( (s.cursor.line() > s.wrappedEnd.line())
00719            || ( (s.cursor.line() == s.wrappedEnd.line()) && (s.cursor.col() > s.wrappedEnd.col()) ) )
00720         return false;
00721     }
00722   }
00723 
00724 //   kdDebug() << "Found at " << s.cursor.line() << ", " << s.cursor.col() << endl;
00725 
00726 
00727   //m_searchResults.append(s);
00728 
00729   if (arbitraryHLExample)  {
00730     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);
00731     hl->setBold();
00732     hl->setTextColor(Qt::white);
00733     hl->setBGColor(Qt::black);
00734     // destroy the highlight upon change
00735     connect(hl, SIGNAL(contentsChanged()), hl, SIGNAL(eliminated()));
00736     m_arbitraryHLList->append(hl);
00737   }
00738 
00739   return true;
00740 
00741     /* rodda: more of my search highlighting work
00742 
00743     } while (++temp < 100);
00744 
00745     if (result) {
00746       s = m_searchResults.first();
00747       m_resultIndex = 0;
00748     }
00749   }
00750 
00751   return result;*/
00752 }
00753 
00754 void KateSearch::exposeFound( KateTextCursor &cursor, int slen )
00755 {
00756   view()->setCursorPositionInternal ( cursor.line(), cursor.col() + slen, 1 );
00757   view()->setSelection( cursor.line(), cursor.col(), cursor.line(), cursor.col() + slen );
00758   view()->syncSelectionCache();
00759 }
00760 //END KateSearch
00761 
00762 //BEGIN KateReplacePrompt
00763 // this dialog is not modal
00764 KateReplacePrompt::KateReplacePrompt ( QWidget *parent )
00765   : KDialogBase ( parent, 0L, false, i18n( "Replace Confirmation" ),
00766                   User3 | User2 | User1 | Close | Ok , Ok, true,
00767                   i18n("Replace &All"), i18n("Re&place && Close"), i18n("&Replace") )
00768 {
00769   setButtonOK( i18n("&Find Next") );
00770   QWidget *page = new QWidget(this);
00771   setMainWidget(page);
00772 
00773   QBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() );
00774   QLabel *label = new QLabel(i18n("Found an occurrence of your search term. What do you want to do?"),page);
00775   topLayout->addWidget(label );
00776 }
00777 
00778 void KateReplacePrompt::slotOk ()
00779 { // Search Next
00780   done(KateSearch::srNo);
00781   actionButton(Ok)->setFocus();
00782 }
00783 
00784 void KateReplacePrompt::slotClose ()
00785 { // Close
00786   done(KateSearch::srCancel);
00787   actionButton(Close)->setFocus();
00788 }
00789 
00790 void KateReplacePrompt::slotUser1 ()
00791 { // Replace All
00792   done(KateSearch::srAll);
00793   actionButton(User1)->setFocus();
00794 }
00795 
00796 void KateReplacePrompt::slotUser2 ()
00797 { // Replace & Close
00798   done(KateSearch::srLast);
00799   actionButton(User2)->setFocus();
00800 }
00801 
00802 void KateReplacePrompt::slotUser3 ()
00803 { // Replace
00804   done(KateSearch::srYes);
00805   actionButton(User3)->setFocus();
00806 }
00807 
00808 void KateReplacePrompt::done (int result)
00809 {
00810   setResult(result);
00811 
00812   emit clicked();
00813 }
00814 //END KateReplacePrompt
00815 
00816 //BEGIN SearchCommand
00817 bool SearchCommand::exec(class Kate::View *view, const QString &cmd, QString &msg)
00818 {
00819   QString flags, pattern, replacement;
00820   if ( cmd.startsWith( "find" ) )
00821   {
00822 
00823     static QRegExp re_find("find(?::([bcersw]*))?\\s+(.+)");
00824     if ( re_find.search( cmd ) < 0 )
00825     {
00826       msg = i18n("Usage: find[:[bcersw]] PATTERN");
00827       return false;
00828     }
00829     flags = re_find.cap( 1 );
00830     pattern = re_find.cap( 2 );
00831   }
00832 
00833   else if ( cmd.startsWith( "ifind" ) )
00834   {
00835     static QRegExp re_ifind("ifind(?::([bcrs]*))?\\s+(.*)");
00836     if ( re_ifind.search( cmd ) < 0 )
00837     {
00838       msg = i18n("Usage: ifind[:[bcrs]] PATTERN");
00839       return false;
00840     }
00841     ifindClear();
00842     return true;
00843   }
00844 
00845   else if ( cmd.startsWith( "replace" ) )
00846   {
00847     // Try if the pattern and replacement is quoted, using a quote character ["']
00848     static QRegExp re_rep("replace(?::([bceprsw]*))?\\s+([\"'])((?:[^\\\\\\\\2]|\\\\.)*)\\2\\s+\\2((?:[^\\\\\\\\2]|\\\\.)*)\\2\\s*$");
00849     // Or one quoted argument
00850     QRegExp re_rep1("replace(?::([bceprsw]*))?\\s+([\"'])((?:[^\\\\\\\\2]|\\\\.)*)\\2\\s*$");
00851     // Else, it's just one or two (space separated) words
00852     QRegExp re_rep2("replace(?::([bceprsw]*))?\\s+(\\S+)(.*)");
00853 #define unbackslash(s) p=0;\
00854 while ( (p = pattern.find( '\\' + delim, p )) > -1 )\
00855 {\
00856   if ( !p || pattern[p-1] != '\\' )\
00857     pattern.remove( p, 1 );\
00858   p++;\
00859 }
00860 
00861     if ( re_rep.search( cmd ) >= 0 )
00862     {
00863       flags = re_rep.cap(1);
00864       pattern = re_rep.cap( 3 );
00865       replacement = re_rep.cap( 4 );
00866 
00867       int p(0);
00868       // unbackslash backslashed delimiter strings
00869       // in pattern ..
00870       QString delim = re_rep.cap( 2 );
00871       unbackslash(pattern);
00872       // .. and in replacement
00873       unbackslash(replacement);
00874     }
00875     else if ( re_rep1.search( cmd ) >= 0 )
00876     {
00877       flags = re_rep1.cap(1);
00878       pattern = re_rep1.cap( 3 );
00879 
00880       int p(0);
00881       QString delim = re_rep1.cap( 2 );
00882       unbackslash(pattern);
00883     }
00884     else if ( re_rep2.search( cmd ) >= 0 )
00885     {
00886       flags = re_rep2.cap( 1 );
00887       pattern = re_rep2.cap( 2 );
00888       replacement = re_rep2.cap( 3 ).stripWhiteSpace();
00889     }
00890     else
00891     {
00892       msg = i18n("Usage: replace[:[bceprsw]] PATTERN [REPLACEMENT]");
00893       return false;
00894     }
00895     kdDebug()<<"replace '"<<pattern<<"' with '"<<replacement<<"'"<<endl;
00896 #undef unbackslash
00897   }
00898 
00899   long f = 0;
00900   if ( flags.contains( 'b' ) ) f |= KFindDialog::FindBackwards;
00901   if ( flags.contains( 'c' ) ) f |= KFindDialog::FromCursor;
00902   if ( flags.contains( 'e' ) ) f |= KFindDialog::SelectedText;
00903   if ( flags.contains( 'r' ) ) f |= KFindDialog::RegularExpression;
00904   if ( flags.contains( 'p' ) ) f |= KReplaceDialog::PromptOnReplace;
00905   if ( flags.contains( 's' ) ) f |= KFindDialog::CaseSensitive;
00906   if ( flags.contains( 'w' ) ) f |= KFindDialog::WholeWordsOnly;
00907 
00908   if ( cmd.startsWith( "find" ) )
00909   {
00910     ((KateView*)view)->find( pattern, f );
00911     return true;
00912   }
00913   else if ( cmd.startsWith( "replace" ) )
00914   {
00915     f |= KReplaceDialog::BackReference; // mandatory here?
00916     ((KateView*)view)->replace( pattern, replacement, f );
00917     return true;
00918   }
00919 
00920   return false;
00921 }
00922 
00923 bool SearchCommand::help(class Kate::View *, const QString &cmd, QString &msg)
00924 {
00925   if ( cmd == "find" )
00926     msg = i18n("<p>Usage: <code>find[:bcersw] PATTERN</code></p>");
00927 
00928   else if ( cmd == "ifind" )
00929     msg = i18n("<p>Usage: <code>ifind:[:bcrs] PATTERN</code>"
00930         "<br>ifind does incremental or 'as-you-type' search</p>");
00931 
00932   else
00933     msg = i18n("<p>Usage: <code>replace[:bceprsw] PATTERN REPLACEMENT</code></p>");
00934 
00935   msg += i18n(
00936       "<h4><caption>Options</h4><p>"
00937       "<b>b</b> - Search backward"
00938       "<br><b>c</b> - Search from cursor"
00939       "<br><b>r</b> - Pattern is a regular expression"
00940       "<br><b>s</b> - Case sensitive search"
00941              );
00942 
00943   if ( cmd == "find" )
00944     msg += i18n(
00945         "<br><b>e</b> - Search in selected text only"
00946         "<br><b>w</b> - Search whole words only"
00947                );
00948 
00949   if ( cmd == "replace" )
00950     msg += i18n(
00951         "<br><b>p</b> - Prompt for replace</p>"
00952         "<p>If REPLACEMENT is not present, an empty string is used.</p>"
00953         "<p>If you want to have whitespace in your PATTERN, you need to "
00954         "quote both PATTERN and REPLACEMENT with either single or double "
00955         "quotes. To have the quote characters in the strings, prepend them "
00956         "with a backslash.");
00957 
00958   msg += "</p>";
00959   return true;
00960 }
00961 
00962 QStringList SearchCommand::cmds()
00963 {
00964   QStringList l;
00965   l << "find" << "replace" << "ifind";
00966   return l;
00967 }
00968 
00969 bool SearchCommand::wantsToProcessText( const QString &cmdname )
00970 {
00971   return  cmdname == "ifind";
00972 }
00973 
00974 void SearchCommand::processText( Kate::View *view, const QString &cmd )
00975 {
00976   static QRegExp re_ifind("ifind(?::([bcrs]*))?\\s(.*)");
00977   if ( re_ifind.search( cmd ) > -1 )
00978   {
00979     QString flags = re_ifind.cap( 1 );
00980     QString pattern = re_ifind.cap( 2 );
00981 
00982 
00983     // if there is no setup, or the text length is 0, set up the properties
00984     if ( ! m_ifindFlags || pattern.isEmpty() )
00985       ifindInit( flags );
00986     // if there is no fromCursor, add it if this is not the first character
00987     else if ( ! ( m_ifindFlags & KFindDialog::FromCursor ) && ! pattern.isEmpty() )
00988       m_ifindFlags |= KFindDialog::FromCursor;
00989 
00990     // search..
00991     if ( ! pattern.isEmpty() )
00992     {
00993       KateView *v = (KateView*)view;
00994 
00995       // If it *looks like* we are continuing, place the cursor
00996       // at the beginning of the selection, so that the search continues.
00997       // ### check more carefully, like is  the cursor currently at the end
00998       // of the selection.
00999       if ( pattern.startsWith( v->selection() ) &&
01000            v->selection().length() + 1 == pattern.length() )
01001         v->setCursorPositionInternal( v->selStartLine(), v->selStartCol() );
01002 
01003       v->find( pattern, m_ifindFlags, false );
01004     }
01005   }
01006 }
01007 
01008 void SearchCommand::ifindInit( const QString &flags )
01009 {
01010   long f = 0;
01011   if ( flags.contains( 'b' ) ) f |= KFindDialog::FindBackwards;
01012   if ( flags.contains( 'c' ) ) f |= KFindDialog::FromCursor;
01013   if ( flags.contains( 'r' ) ) f |= KFindDialog::RegularExpression;
01014   if ( flags.contains( 's' ) ) f |= KFindDialog::CaseSensitive;
01015   m_ifindFlags = f;
01016 }
01017 
01018 void SearchCommand::ifindClear()
01019 {
01020   m_ifindFlags = 0;
01021 }
01022 //END SearchCommand
01023 
01024 // kate: space-indent on; indent-width 2; replace-tabs on;
KDE Home | KDE Accessibility Home | Description of Access Keys