00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
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
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
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
00419
00420
00421
00422 QString str;
00423
00424 int getFrom = view()->config()->textToSearchMode();
00425 switch (getFrom)
00426 {
00427 case KateViewConfig::SelectionOnly:
00428
00429 if( doc()->hasSelection() )
00430 str = doc()->selection();
00431 break;
00432
00433 case KateViewConfig::SelectionWord:
00434
00435 if( doc()->hasSelection() )
00436 str = doc()->selection();
00437 else
00438 str = view()->currentWord();
00439 break;
00440
00441 case KateViewConfig::WordOnly:
00442
00443 str = view()->currentWord();
00444 break;
00445
00446 case KateViewConfig::WordSelection:
00447
00448 str = view()->currentWord();
00449 if (str.isEmpty() && doc()->hasSelection() )
00450 str = doc()->selection();
00451 break;
00452
00453 default:
00454
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
00473
00474
00475
00476
00477
00478
00479
00480
00481
00482
00483
00484
00485
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();
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
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;
00523
00524
00525
00526 s.cursor.setPos(foundLine, foundCol);
00527
00528
00529 s.matchedLength = matchLen;
00530
00531
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
00539 connect(hl, SIGNAL(contentsChanged()), hl, SIGNAL(eliminated()));
00540 m_arbitraryHLList->append(hl);
00541 }
00542
00543 return true;
00544
00545
00546
00547
00548
00549
00550
00551
00552
00553
00554
00555
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
00565
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 ) {
00581 done(KateSearch::srYes);
00582 }
00583
00584 void ReplacePrompt::slotClose( void ) {
00585 done(KateSearch::srCancel);
00586 }
00587
00588 void ReplacePrompt::slotUser1( void ) {
00589 done(KateSearch::srAll);
00590 }
00591
00592 void ReplacePrompt::slotUser2( void ) {
00593 done(KateSearch::srLast);
00594 }
00595
00596 void ReplacePrompt::slotUser3( void ) {
00597 done(KateSearch::srNo);
00598 }
00599
00600 void ReplacePrompt::done(int r) {
00601 setResult(r);
00602 emit clicked();
00603 }
00604
00605
00606