+/**
+ * \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.
+ */
+
#include <config.h>
-#include "lyxtext.h"
#include "lyxfind.h"
-#include "paragraph.h"
-#include "frontends/LyXView.h"
-#include "frontends/Alert.h"
-#include "support/textutils.h"
-#include "support/lstrings.h"
-#include "BufferView.h"
+
#include "buffer.h"
+#include "BufferView.h"
#include "debug.h"
+#include "iterators.h"
#include "gettext.h"
-#include "insets/insettext.h"
-#include "changes.h"
+#include "lyxtext.h"
+#include "paragraph.h"
+#include "PosIterator.h"
+#include "undo.h"
-using namespace lyx::support;
+#include "frontends/Alert.h"
-using lyx::pos_type;
-using std::endl;
+#include "support/textutils.h"
-namespace lyxfind {
+using lyx::support::lowercase;
+using lyx::support::uppercase;
+using bv_funcs::put_selection_at;
-/// returns true if the specified string is at the specified position
-bool IsStringInText(Paragraph * par, pos_type pos,
- string const & str, bool const & = true,
- bool const & = false);
+using std::string;
-/// 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);
-int LyXReplace(BufferView * bv,
- string const & searchstr, string const & replacestr,
- bool forward, bool casesens, bool matchwrd, bool replaceall,
- bool once)
-{
- if (!bv->available() || bv->buffer()->isReadonly())
- return 0;
+namespace lyx {
+namespace find {
- // 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] == ' ')) {
-#ifdef WITH_WARNINGS
-#warning BLECH. If we have an LFUN for replace, we can sort of fix this bogosity
-#endif
- Alert::error(_("Cannot replace"),
- _("You cannot replace a single space or "
- "an empty character."));
- return 0;
- }
+namespace {
- // now we can start searching for the first
- // start at top if replaceall
- LyXText * text = bv->getLyXText();
- bool fw = forward;
- if (replaceall) {
- text->clearSelection();
- bv->unlockInset(bv->theLockingInset());
- text = bv->text;
- text->cursorTop();
- // 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(), false);
- } else {
- str1 = lowercase(searchstr);
- str2 = lowercase(text->selectionAsString(bv->buffer(), false));
- }
- if (str1 != str2) {
- if (!LyXFind(bv, searchstr, fw, casesens, matchwrd) ||
- !replaceall) {
- return 0;
+class MatchString
+{
+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, pos_type pos) const
+ {
+ string::size_type size = str.length();
+ pos_type i = 0;
+ pos_type const parsize = par.size();
+ while ((pos + i < parsize)
+ && (string::size_type(i) < size)
+ && (cs ? (str[i] == par.getChar(pos + i))
+ : (uppercase(str[i]) == uppercase(par.getChar(pos + i))))) {
+ ++i;
}
- }
- bool found = false;
- int replace_count = 0;
- do {
- text = bv->getLyXText();
- // We have to do this check only because mathed insets don't
- // return their own LyXText but the LyXText of it's parent!
- if (!bv->theLockingInset() ||
- ((text != bv->text) &&
- (text->inset_owner == text->inset_owner->getLockingInset()))) {
- bv->update(text, BufferView::SELECT);
- bv->toggleSelection(false);
- text->replaceSelectionWithString(replacestr);
- text->setSelectionRange(replacestr.length());
- bv->update(text, BufferView::SELECT);
- ++replace_count;
- }
- if (!once)
- found = LyXFind(bv, searchstr, fw, casesens, matchwrd);
- } while (!once && replaceall && found);
+ if (size != string::size_type(i))
+ return false;
- // FIXME: should be called via an LFUN
- bv->buffer()->markDirty();
- bv->fitCursor();
+ // if necessary, check whether string matches word
+ if (mw) {
+ if (pos > 0 && IsLetterCharOrDigit(par.getChar(pos - 1)))
+ return false;
+ if (pos + pos_type(size) < parsize
+ && IsLetterCharOrDigit(par.getChar(pos + size)));
+ return false;
+ }
- return replace_count;
+ return true;
+ }
+
+private:
+ // search string
+ string str;
+ // case sensitive
+ bool cs;
+ // match whole words only
+ bool mw;
+};
+
+
+bool findForward(PosIterator & cur, PosIterator const & end,
+ MatchString const & match)
+{
+ for (; cur != end; ++cur) {
+ if (match(*cur.pit(), cur.pos()))
+ return true;
+ }
+ return false;
}
-bool LyXFind(BufferView * bv,
- string const & searchstr, bool forward,
- bool casesens, bool matchwrd)
+bool findBackwards(PosIterator & cur, PosIterator const & beg,
+ MatchString const & match)
{
- if (!bv->available() || searchstr.empty())
- return false;
+ while (beg != cur) {
+ --cur;
+ if (match(*cur.pit(), cur.pos()))
+ return true;
+ }
+ return false;
+}
- bv->update(bv->getLyXText(), BufferView::SELECT);
- if (bv->theLockingInset()) {
- bool found = forward ?
- bv->theLockingInset()->searchForward(bv, searchstr, casesens, matchwrd) :
- bv->theLockingInset()->searchBackward(bv, searchstr, casesens, matchwrd);
- // We found the stuff inside the inset so we don't have to
- // do anything as the inset did all the update for us!
- if (found)
+bool findChange(PosIterator & cur, PosIterator const & end)
+{
+ for (; cur != end; ++cur) {
+ if ((!cur.pit()->size() || !cur.at_end())
+ && cur.pit()->lookupChange(cur.pos()) != Change::UNCHANGED)
return true;
- // We now are in the main text but if we did a forward
- // search we have to put the cursor behind the inset.
- if (forward) {
- bv->text->cursorRight(true);
- }
- }
- // If we arrive here we are in the main text again so we
- // just start searching from the root LyXText at the position
- // we are!
- LyXText * text = bv->text;
-
-
- if (text->selection.set())
- text->cursor = forward ?
- text->selection.end : text->selection.start;
-
- bv->toggleSelection();
- text->clearSelection();
-
- SearchResult result = forward ?
- SearchForward(bv, text, searchstr, casesens, matchwrd) :
- SearchBackward(bv, text, searchstr, casesens, matchwrd);
-
- bool found = true;
- // If we found the cursor inside an inset we will get back
- // SR_FOUND_NOUPDATE and we don't have to do anything as the
- // inset did it already.
- if (result == SR_FOUND) {
- bv->unlockInset(bv->theLockingInset());
- bv->update(text, BufferView::SELECT);
- text->setSelectionRange(searchstr.length());
- bv->toggleSelection(false);
- bv->update(text, BufferView::SELECT);
- } else if (result == SR_NOT_FOUND) {
- bv->unlockInset(bv->theLockingInset());
- bv->update(text, BufferView::SELECT);
- found = false;
}
+ return false;
+}
- bv->fitCursor();
- return found;
+bool searchAllowed(BufferView * bv, string const & str)
+{
+ if (str.empty()) {
+ Alert::error(_("Search error"), _("Search string is empty"));
+ return false;
+ }
+ return bv->available();
}
+} // namespace anon
-SearchResult LyXFind(BufferView * bv, LyXText * text,
- string const & searchstr, bool forward,
- bool casesens, bool matchwrd)
-{
- if (text->selection.set())
- text->cursor = forward ?
- text->selection.end : text->selection.start;
- bv->toggleSelection();
- text->clearSelection();
- SearchResult result = forward ?
- SearchForward(bv, text, searchstr, casesens, matchwrd) :
- SearchBackward(bv, text, searchstr, casesens, matchwrd);
+bool find(BufferView * bv, string const & searchstr, bool cs, bool mw, bool fw)
+{
+ if (!searchAllowed(bv, searchstr))
+ return false;
- return result;
-}
+ PosIterator cur = PosIterator(*bv);
+ MatchString const match(searchstr, cs, mw);
-// returns true if the specified string is at the specified position
-bool IsStringInText(Paragraph const & par, pos_type pos,
- string const & str, bool const & cs,
- bool const & mw)
-{
- string::size_type size = str.length();
- pos_type i = 0;
- pos_type parsize = par.size();
- while (((pos + i) < parsize)
- && (string::size_type(i) < size)
- && (cs ? (str[i] == par.getChar(pos + i))
- : (uppercase(str[i]) == uppercase(par.getChar(pos + i))))) {
- ++i;
- }
+ PosIterator const end = bv->buffer()->pos_iterator_end();
+ PosIterator const beg = bv->buffer()->pos_iterator_begin();
- if (size == string::size_type(i)) {
- // if necessary, check whether string matches word
- if (!mw)
- return true;
- if ((pos <= 0 || !IsLetterCharOrDigit(par.getChar(pos - 1)))
- && (pos + pos_type(size) >= parsize
- || !IsLetterCharOrDigit(par.getChar(pos + size)))) {
- return true;
- }
- }
- return false;
-}
+ bool found = fw ? findForward(cur, end, match)
+ : findBackwards(cur, beg, match);
-// 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)
-{
- ParagraphList::iterator pit = text->cursor.par();
- ParagraphList::iterator pend = text->ownerParagraphs().end();
- pos_type pos = text->cursor.pos();
- UpdatableInset * inset;
-
- while (pit != pend && !IsStringInText(*pit, pos, str, cs, mw)) {
- if (pos < pit->size()
- && pit->isInset(pos)
- && (inset = (UpdatableInset *)pit->getInset(pos))
- && inset->isTextInset()
- && inset->searchForward(bv, str, cs, mw))
- return SR_FOUND_NOUPDATE;
-
- if (++pos >= pit->size()) {
- ++pit;
- pos = 0;
- }
- }
+ if (found)
+ put_selection_at(bv, cur, searchstr.length(), !fw);
- if (pit != pend) {
- text->setCursor(pit, pos);
- return SR_FOUND;
- } else
- return SR_NOT_FOUND;
+ return found;
}
-// 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)
+int replaceAll(BufferView * bv,
+ string const & searchstr, string const & replacestr,
+ bool cs, bool mw)
{
- ParagraphList::iterator pit = text->cursor.par();
- ParagraphList::iterator pbegin = text->ownerParagraphs().begin();
- pos_type pos = text->cursor.pos();
-
- // skip past a match at the current cursor pos
- if (pos > 0) {
- --pos;
- } else if (pit != pbegin) {
- --pit;
- pos = pit->size();
- } else {
- return SR_NOT_FOUND;
+ Buffer & buf = *bv->buffer();
+
+ if (!searchAllowed(bv, searchstr) || buf.isReadonly())
+ return 0;
+
+ recordUndo(Undo::ATOMIC, bv->text(), 0, buf.paragraphs().size() - 1);
+
+ PosIterator cur = buf.pos_iterator_begin();
+ PosIterator const end = buf.pos_iterator_end();
+ MatchString const match(searchstr, cs, mw);
+ int num = 0;
+
+ int const rsize = replacestr.size();
+ int const ssize = searchstr.size();
+
+ while (findForward(cur, end, match)) {
+ pos_type pos = cur.pos();
+ LyXFont const font
+ = cur.pit()->getFontSettings(buf.params(), pos);
+ int striked = ssize - cur.pit()->erase(pos, pos + ssize);
+ cur.pit()->insert(pos, replacestr, font);
+ advance(cur, rsize + striked);
+ ++num;
}
- while (true) {
- if (pos < pit->size()) {
- if (pit->isInset(pos) && pit->getInset(pos)->isTextInset()) {
- UpdatableInset * inset = (UpdatableInset *)pit->getInset(pos);
- if (inset->searchBackward(bv, str, cs, mw))
- return SR_FOUND_NOUPDATE;
- }
-
- if (IsStringInText(*pit, pos, str, cs, mw)) {
- text->setCursor(pit, pos);
- return SR_FOUND;
- }
- }
+ PosIterator beg = buf.pos_iterator_begin();
+ bv->text()->init(bv);
+ put_selection_at(bv, beg, 0, false);
+ if (num)
+ buf.markDirty();
+ return num;
+}
- if (pos == 0 && pit == pbegin)
- break;
- if (pos > 0) {
- --pos;
- } else if (pit != pbegin) {
- --pit;
- pos = pit->size();
- }
+namespace {
+
+bool stringSelected(BufferView * bv,
+ string const & searchstr,
+ bool cs, bool mw, bool fw)
+{
+ LyXText * text = bv->getLyXText();
+ // if nothing selected or selection does not equal search
+ // string search and select next occurance and return
+ string const & str1 = searchstr;
+ string const str2 = text->selectionAsString(*bv->buffer(),
+ false);
+ if ((cs && str1 != str2) || lowercase(str1) != lowercase(str2)) {
+ find(bv, searchstr, cs, mw, fw);
+ return false;
}
- return SR_NOT_FOUND;
+ return true;
}
+} //namespace anon
-SearchResult nextChange(BufferView * bv, LyXText * text, pos_type & length)
+
+int replace(BufferView * bv,
+ string const & searchstr, string const & replacestr,
+ bool cs, bool mw, bool fw)
{
- ParagraphList::iterator pit = text->cursor.par();
- ParagraphList::iterator pend = text->ownerParagraphs().end();
- pos_type pos = text->cursor.pos();
+ if (!searchAllowed(bv, searchstr) || bv->buffer()->isReadonly())
+ return 0;
- while (pit != pend) {
- pos_type parsize = pit->size();
+ if (!stringSelected(bv, searchstr, cs, mw, fw))
+ return 0;
- if (pos < parsize) {
- if ((!parsize || pos != parsize)
- && pit->lookupChange(pos) != Change::UNCHANGED)
- break;
+ LyXText * text = bv->getLyXText();
- if (pit->isInset(pos) && pit->getInset(pos)->isTextInset()) {
- UpdatableInset * inset = (UpdatableInset *)pit->getInset(pos);
- if (inset->nextChange(bv, length))
- return SR_FOUND_NOUPDATE;
- }
- }
+ text->replaceSelectionWithString(replacestr);
+ text->setSelectionRange(replacestr.length());
+ text->cursor = fw ? text->selEnd() : text->selStart();
- ++pos;
+ bv->buffer()->markDirty();
+ find(bv, searchstr, cs, mw, fw);
+ bv->update();
+
+ return 1;
+}
- if (pos >= parsize) {
- ++pit;
- pos = 0;
- }
- }
- if (pit == pend)
- return SR_NOT_FOUND;
+bool findNextChange(BufferView * bv)
+{
+ if (!bv->available())
+ return false;
+
+ PosIterator cur = PosIterator(*bv);
+ PosIterator const endit = bv->buffer()->pos_iterator_end();
- text->setCursor(pit, pos);
+ if (!findChange(cur, endit))
+ return false;
+
+ ParagraphList::iterator pit = cur.pit();
+ pos_type pos = cur.pos();
+
Change orig_change = pit->lookupChangeFull(pos);
pos_type parsize = pit->size();
pos_type end = pos;
break;
}
}
- length = end - pos;
- return SR_FOUND;
-}
-
-
-SearchResult findNextChange(BufferView * bv, LyXText * text, pos_type & length)
-{
- if (text->selection.set())
- text->cursor = text->selection.end;
-
- bv->toggleSelection();
- text->clearSelection();
-
- return nextChange(bv, text, length);
-}
-
-
-bool findNextChange(BufferView * bv)
-{
- if (!bv->available())
- return false;
-
- bv->update(bv->getLyXText(), BufferView::SELECT);
-
- pos_type length;
-
- if (bv->theLockingInset()) {
- bool found = bv->theLockingInset()->nextChange(bv, length);
-
- // We found the stuff inside the inset so we don't have to
- // do anything as the inset did all the update for us!
- if (found)
- return true;
-
- // We now are in the main text but if we did a forward
- // search we have to put the cursor behind the inset.
- bv->text->cursorRight(true);
- }
- // If we arrive here we are in the main text again so we
- // just start searching from the root LyXText at the position
- // we are!
- LyXText * text = bv->text;
-
- if (text->selection.set())
- text->cursor = text->selection.end;
-
- bv->toggleSelection();
- text->clearSelection();
-
- SearchResult result = nextChange(bv, text, length);
-
- bool found = true;
-
- // If we found the cursor inside an inset we will get back
- // SR_FOUND_NOUPDATE and we don't have to do anything as the
- // inset did it already.
- if (result == SR_FOUND) {
- bv->unlockInset(bv->theLockingInset());
- bv->update(text, BufferView::SELECT);
- text->setSelectionRange(length);
- bv->toggleSelection(false);
- bv->update(text, BufferView::SELECT);
- } else if (result == SR_NOT_FOUND) {
- bv->unlockInset(bv->theLockingInset());
- bv->update(text, BufferView::SELECT);
- found = false;
- }
-
- bv->fitCursor();
-
- return found;
+ pos_type length = end - pos;
+ put_selection_at(bv, cur, length, true);
+ return true;
}
-} // end lyxfind namespace
+} // find namespace
+} // lyx namespace