X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Flyxfind.C;h=cfee2c9f04435a7be031677ef35ef4764c07eef2;hb=005545f28100fd30afa22313d6e3b1b67aa9a857;hp=03de70a4695de83e917cf170289c7cedf6558da2;hpb=9f3dd22dd39c419522914846b21dd2b45720ad0a;p=lyx.git diff --git a/src/lyxfind.C b/src/lyxfind.C index 03de70a469..cfee2c9f04 100644 --- a/src/lyxfind.C +++ b/src/lyxfind.C @@ -1,327 +1,369 @@ -#include +/** + * \file lyxfind.C + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author Lars Gullik Bjønnes + * \author John Levon + * \author Jürgen Vigna + * \author Alfredo Braunstein + * + * Full author contact details are available in file CREDITS. + */ -#ifdef __GNUG__ -#pragma implementation -#endif +#include -#include "lyxtext.h" #include "lyxfind.h" -#include "LyXView.h" -#include "lyx_gui_misc.h" -#include "support/textutils.h" -#include "support/lstrings.h" -#include "BufferView.h" + #include "buffer.h" +#include "cursor.h" +#include "CutAndPaste.h" +#include "BufferView.h" +#include "debug.h" +#include "funcrequest.h" #include "gettext.h" +#include "lyxtext.h" +#include "paragraph.h" +#include "pariterator.h" +#include "undo.h" -/// -// locally used enum -/// -enum SearchResult { - // - SR_NOT_FOUND = 0, - // - SR_FOUND, - // - SR_FOUND_NOUPDATE -}; +#include "frontends/Alert.h" +#include "frontends/LyXView.h" + +#include "support/textutils.h" +#include "support/tostr.h" + +#include "support/std_sstream.h" +using lyx::support::lowercase; +using lyx::support::uppercase; +using lyx::support::split; -/// returns true if the specified string is at the specified position -bool IsStringInText(Paragraph * par, Paragraph::size_type pos, - string const & str, bool const & = true, - bool const & = false); +using lyx::par_type; +using lyx::pos_type; -/// if the string is found: return true and set the cursor to the new position -SearchResult SearchForward(BufferView *, LyXText * text, string const & str, - bool const & = true, bool const & = false); -/// -SearchResult SearchBackward(BufferView *, LyXText * text, string const & str, - bool const & = true, bool const & = false); +using std::advance; +using std::ostringstream; +using std::string; -int LyXReplace(BufferView * bv, - string const & searchstr, string const & replacestr, - bool forward, bool casesens, bool matchwrd, bool replaceall, - bool once) +namespace { + +bool parse_bool(string & howto) { - if (!bv->available() || bv->buffer()->isReadonly()) - return 0; - - // CutSelection cannot cut a single space, so we have to stop - // in order to avoid endless loop :-( - if (searchstr.length() == 0 - || (searchstr.length() == 1 && searchstr[0] == ' ')) + if (howto.empty()) + return false; + string var; + howto = split(howto, var, ' '); + return (var == "1"); +} + + +class MatchString : public std::binary_function +{ +public: + MatchString(string const & str, bool cs, bool mw) + : str(str), cs(cs), mw(mw) + {} + + // returns true if the specified string is at the specified position + bool operator()(Paragraph const & par, lyx::pos_type pos) const { - WriteAlert(_("Sorry!"), _("You cannot replace a single space, " - "nor an empty character.")); - return 0; - } - - LyXText * text = bv->getLyXText(); - - // now we can start searching for the first - // start at top if replaceall - bool fw = forward; - if (replaceall) { - text->clearSelection(); - if (text->inset_owner) { - bv->unlockInset(bv->theLockingInset()); - text = bv->text; + string::size_type const size = str.length(); + lyx::pos_type i = 0; + lyx::pos_type const parsize = par.size(); + for (i = 0; pos + i < parsize; ++i) { + if (string::size_type(i) >= size) + break; + if (cs && str[i] != par.getChar(pos + i)) + break; + if (!cs && uppercase(str[i]) != uppercase(par.getChar(pos + i))) + break; } - text->cursorTop(bv); - // override search direction because we search top to bottom - fw = true; - } - - // if nothing selected or selection does not equal search string - // search and select next occurance and return if no replaceall - string str1; - string str2; - if (casesens) { - str1 = searchstr; - str2 = text->selectionAsString(bv->buffer()); - } else { - str1 = lowercase(searchstr); - str2 = lowercase(text->selectionAsString(bv->buffer())); - } - if (str1 != str2) { - if (!LyXFind(bv, searchstr, fw, false, casesens, matchwrd) || - !replaceall) - { - return 0; + + if (size != string::size_type(i)) + return false; + + // if necessary, check whether string matches word + if (mw) { + if (pos > 0 && IsLetterCharOrDigit(par.getChar(pos - 1))) + return false; + if (pos + lyx::pos_type(size) < parsize + && IsLetterCharOrDigit(par.getChar(pos + size))); + return false; } + + return true; } - bool found = false; - int replace_count = 0; - do { - bv->hideCursor(); - bv->update(bv->getLyXText(), BufferView::SELECT|BufferView::FITCUR); - bv->toggleSelection(false); - bv->getLyXText()->replaceSelectionWithString(bv, replacestr); - bv->getLyXText()->setSelectionOverString(bv, replacestr); - bv->update(bv->getLyXText(), BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE); - ++replace_count; - if (!once) - found = LyXFind(bv, searchstr, fw, false, casesens, matchwrd); - } while (!once && replaceall && found); - - if (bv->focus()) - bv->showCursor(); - - return replace_count; +private: + // search string + string str; + // case sensitive + bool cs; + // match whole words only + bool mw; +}; + + +bool findForward(DocIterator & cur, MatchString const & match) +{ + for (; cur; cur.forwardChar()) + if (cur.inTexted() && match(cur.paragraph(), cur.pos())) + return true; + return false; +} + + +bool findBackwards(DocIterator & cur, MatchString const & match) +{ + for (; cur; cur.backwardChar()) + if (cur.inTexted() && match(cur.paragraph(), cur.pos())) + return true; + return false; +} + + +bool findChange(DocIterator & cur) +{ + for (; cur; cur.forwardChar()) + if (cur.inTexted() && !cur.paragraph().empty() && + cur.paragraph().lookupChange(cur.pos()) + != Change::UNCHANGED) + return true; + return false; } -bool LyXFind(BufferView * bv, - string const & searchstr, bool forward, - bool frominset, bool casesens, bool matchwrd) + +bool searchAllowed(BufferView * bv, string const & str) { - if (!bv->available() || searchstr.empty()) + if (str.empty()) { + Alert::error(_("Search error"), _("Search string is empty")); return false; - - LyXText * text = bv->getLyXText(); - - bv->hideCursor(); - bv->update(text, BufferView::SELECT|BufferView::FITCUR); - - if (text->selection.set()) - text->cursor = forward ? - text->selection.end : text->selection.start; - - SearchResult result = SR_NOT_FOUND; - - if (!frominset && bv->theLockingInset()) { - bool found = forward ? - bv->theLockingInset()->searchForward(bv, searchstr, casesens, matchwrd) : - bv->theLockingInset()->searchBackward(bv, searchstr, casesens, matchwrd); - if (found) - result = SR_FOUND_NOUPDATE; - else { - text = bv->getLyXText(); - Paragraph * par = text->cursor.par(); - Paragraph::size_type pos = text->cursor.pos(); - if (forward) { - if (pos < par->size() - 1) - ++pos; - else { - pos = 0; - par = par->next(); - } - if (par) - text->setCursor(bv, par, pos); - } - if (par) { - result = forward ? - SearchForward(bv, text, searchstr, casesens, matchwrd) : - SearchBackward(bv, text, searchstr, casesens, matchwrd); - } - } - } else { - result = forward ? - SearchForward(bv, text, searchstr, casesens, matchwrd) : - SearchBackward(bv, text, searchstr, casesens, matchwrd); } + return bv->available(); +} + + +bool find(BufferView * bv, string const & searchstr, bool cs, bool mw, bool fw) +{ + if (!searchAllowed(bv, searchstr)) + return false; + + DocIterator cur = bv->cursor(); + + MatchString const match(searchstr, cs, mw); + + bool found = fw ? findForward(cur, match) : findBackwards(cur, match); + + if (found) + bv->putSelectionAt(cur, searchstr.length(), !fw); - bool found = true; - if (result == SR_FOUND) { - // the actual text pointer could have changed! - bv->update(bv->getLyXText(), BufferView::SELECT|BufferView::FITCUR); - bv->toggleSelection(); - bv->getLyXText()->clearSelection(); - bv->getLyXText()->setSelectionOverString(bv, searchstr); - bv->toggleSelection(false); - bv->update(bv->getLyXText(), BufferView::SELECT|BufferView::FITCUR); - } else if (result == SR_NOT_FOUND) - found = false; - - if (bv->focus()) - bv->showCursor(); - return found; } -// returns true if the specified string is at the specified position -bool IsStringInText(Paragraph * par, Paragraph::size_type pos, - string const & str, bool const & cs, - bool const & mw) +int replaceAll(BufferView * bv, + string const & searchstr, string const & replacestr, + bool cs, bool mw) { - if (!par) - return false; - - string::size_type size = str.length(); - Paragraph::size_type i = 0; - while (((pos + i) < par->size()) - && (string::size_type(i) < size) - && (cs ? (str[i] == par->getChar(pos + i)) - : (toupper(str[i]) == toupper(par->getChar(pos + i))))) - { - ++i; - } - if (size == string::size_type(i)) { - // if necessary, check whether string matches word - if (!mw || - (mw && ((pos <= 0 || !IsLetterCharOrDigit(par->getChar(pos - 1))) - && (pos + Paragraph::size_type(size) >= par->size() - || !IsLetterCharOrDigit(par->getChar(pos + size)))) - )) - { - return true; - } + Buffer & buf = *bv->buffer(); + + if (!searchAllowed(bv, searchstr) || buf.isReadonly()) + return 0; + + recordUndoFullDocument(bv->cursor()); + + MatchString const match(searchstr, cs, mw); + int num = 0; + + int const rsize = replacestr.size(); + int const ssize = searchstr.size(); + + DocIterator cur = doc_iterator_begin(buf.inset()); + while (findForward(cur, match)) { + lyx::pos_type pos = cur.pos(); + LyXFont const font + = cur.paragraph().getFontSettings(buf.params(), pos); + int striked = ssize - cur.paragraph().erase(pos, pos + ssize); + cur.paragraph().insert(pos, replacestr, font); + for (int i = 0; i < rsize + striked; ++i) + cur.forwardChar(); + ++num; } - return false; + + bv->text()->init(bv); + bv->putSelectionAt(doc_iterator_begin(buf.inset()), 0, false); + if (num) + buf.markDirty(); + return num; } -// forward search: -// if the string can be found: return true and set the cursor to -// the new position, cs = casesensitive, mw = matchword -SearchResult SearchForward(BufferView * bv, LyXText * text, string const & str, - bool const & cs, bool const & mw) + +bool stringSelected(BufferView * bv, string const & searchstr, + bool cs, bool mw, bool fw) { - Paragraph * par = text->cursor.par(); - Paragraph::size_type pos = text->cursor.pos(); - UpdatableInset * inset; - - while (par && !IsStringInText(par, pos, str, cs, mw)) { - if (par->isInset(pos) && - (inset = (UpdatableInset *)par->getInset(pos)) && - (inset->isTextInset())) - { - // lock the inset! - text->setCursor(bv, par, pos); - inset->edit(bv); - if (inset->searchForward(bv, str, cs, mw)) - return SR_FOUND_NOUPDATE; - text = bv->getLyXText(); - } - if (pos < par->size() - 1) - ++pos; - else { - pos = 0; - par = par->next(); - } + // if nothing selected or selection does not equal search + // string search and select next occurance and return + string const & str1 = searchstr; + string const str2 = bv->cursor().selectionAsString(false); + if ((cs && str1 != str2) || lowercase(str1) != lowercase(str2)) { + find(bv, searchstr, cs, mw, fw); + return false; } - if (par) { - text->setCursor(bv, par, pos); - return SR_FOUND; -#if 0 - } else if (text->inset_owner) { - // test if we're inside an inset if yes unlock the inset - // and recall us with the outside LyXText! - bv->unlockInset((UpdatableInset *)text->inset_owner); - if (!bv->theLockingInset()) { - text = bv->getLyXText(); - par = text->cursor.par(); - pos = text->cursor.pos(); - if (pos < par->size() - 1) - ++pos; - else { - pos = 0; - par = par->next(); - } - if (!par) - return SR_NOT_FOUND; - text->setCursor(bv, par, pos); - return SearchForward(bv, text, str, cs, mw); - } else { - return SR_NOT_FOUND; - } -#endif - } else - return SR_NOT_FOUND; + + return true; +} + + +int replace(BufferView * bv, string const & searchstr, + string const & replacestr, bool cs, bool mw, bool fw) +{ + if (!searchAllowed(bv, searchstr) || bv->buffer()->isReadonly()) + return 0; + + if (!stringSelected(bv, searchstr, cs, mw, fw)) + return 0; + + LCursor & cur = bv->cursor(); + lyx::cap::replaceSelectionWithString(cur, replacestr); + lyx::cap::setSelectionRange(cur, replacestr.length()); + cur.top() = fw ? cur.selEnd() : cur.selBegin(); + bv->buffer()->markDirty(); + find(bv, searchstr, cs, mw, fw); + bv->update(); + + return 1; +} + +} // namespace anon + + +namespace lyx { +namespace find { + +string const find2string(string const & search, + bool casesensitive, bool matchword, bool forward) +{ + ostringstream ss; + ss << search << '\n' + << int(casesensitive) << ' ' + << int(matchword) << ' ' + << int(forward); + return ss.str(); } -// backward search: -// if the string can be found: return true and set the cursor to -// the new position, cs = casesensitive, mw = matchword -SearchResult SearchBackward(BufferView * bv, LyXText * text, - string const & str, - bool const & cs, bool const & mw) +string const replace2string(string const & search, string const & replace, + bool casesensitive, bool matchword, + bool all, bool forward) { - Paragraph * par = text->cursor.par(); - Paragraph::size_type pos = text->cursor.pos(); - - do { - if (pos > 0) - --pos; - else { - // We skip empty paragraphs (Asger) - do { - par = par->previous(); - if (par) - pos = par->size() - 1; - } while (par && pos < 0); + ostringstream ss; + ss << search << '\n' + << replace << '\n' + << int(casesensitive) << ' ' + << int(matchword) << ' ' + << int(all) << ' ' + << int(forward); + return ss.str(); +} + + +void find(BufferView * bv, FuncRequest const & ev) +{ + if (!bv || ev.action != LFUN_WORD_FIND) + return; + + lyxerr << "find called, cmd: " << ev << std::endl; + + // data is of the form + // " + // " + string search; + string howto = split(ev.argument, search, '\n'); + + bool casesensitive = parse_bool(howto); + bool matchword = parse_bool(howto); + bool forward = parse_bool(howto); + + bool const found = ::find(bv, search, + casesensitive, matchword, forward); + + if (!found) + bv->owner()->message(_("String not found!")); +} + + +void replace(BufferView * bv, FuncRequest const & ev) +{ + if (!bv || ev.action != LFUN_WORD_REPLACE) + return; + + // data is of the form + // " + // + // " + string search; + string replace; + string howto = split(ev.argument, search, '\n'); + howto = split(howto, replace, '\n'); + + bool casesensitive = parse_bool(howto); + bool matchword = parse_bool(howto); + bool all = parse_bool(howto); + bool forward = parse_bool(howto); + + LyXView * lv = bv->owner(); + + int const replace_count = all + ? ::replaceAll(bv, search, replace, casesensitive, matchword) + : ::replace(bv, search, replace, casesensitive, matchword, forward); + + if (replace_count == 0) { + lv->message(_("String not found!")); + } else { + if (replace_count == 1) { + lv->message(_("String has been replaced.")); + } else { + string str = tostr(replace_count); + str += _(" strings have been replaced."); + lv->message(str); } - UpdatableInset * inset; - if (par && par->isInset(pos) && - (inset = (UpdatableInset *)par->getInset(pos)) && - (inset->isTextInset())) - { - // lock the inset! - text->setCursor(bv, par, pos); - inset->edit(bv, false); - if (inset->searchBackward(bv, str, cs, mw)) - return SR_FOUND_NOUPDATE; - text = bv->getLyXText(); - } - } while (par && !IsStringInText(par, pos, str, cs, mw)); - - if (par) { - text->setCursor(bv, par, pos); - return SR_FOUND; } -#if 0 - else if (text->inset_owner) { - // test if we're inside an inset if yes unlock the inset - // and recall us with the outside LyXText! - bv->unlockInset((UpdatableInset *)text->inset_owner); - if (!bv->theLockingInset()) { - return SearchBackward(bv, bv->getLyXText(), str, cs, mw); +} + + +bool findNextChange(BufferView * bv) +{ + if (!bv->available()) + return false; + + DocIterator cur = DocIterator(bv->cursor()); + + if (!findChange(cur)) + return false; + + Paragraph const & par = cur.paragraph(); + pos_type pos = cur.pos(); + + Change orig_change = par.lookupChangeFull(pos); + pos_type parsize = par.size(); + pos_type end = pos; + + for (; end != parsize; ++end) { + Change change = par.lookupChangeFull(end); + if (change != orig_change) { + // slight UI optimisation: for replacements, we get + // text like : _old_new. Consider that as one change. + if (!(orig_change.type == Change::DELETED && + change.type == Change::INSERTED)) + break; } } -#endif - return SR_NOT_FOUND; + pos_type length = end - pos; + bv->putSelectionAt(cur, length, true); + return true; } +} // find namespace +} // lyx namespace