/**
* \file lyxfind.cpp
* This file is part of LyX, the document processor.
- * Licence details can be found in the file COPYING.
+ * License details can be found in the file COPYING.
*
* \author Lars Gullik Bjønnes
* \author John Levon
#include "ParIterator.h"
#include "TexRow.h"
#include "Text.h"
+#include "FuncRequest.h"
+#include "LyXFunc.h"
#include "mathed/InsetMath.h"
#include "mathed/InsetMathGrid.h"
#include "support/docstream.h"
#include "support/gettext.h"
#include "support/lstrings.h"
+#include "support/lassert.h"
#include <boost/regex.hpp>
+#include <boost/next_prior.hpp>
using namespace std;
using namespace lyx::support;
}
-bool findChange(DocIterator & cur)
+bool findChange(DocIterator & cur, bool next)
{
- for (; cur; cur.forwardPos())
- if (cur.inTexted() && !cur.paragraph().isUnchanged(cur.pos()))
+ if (!next)
+ cur.backwardPos();
+ for (; cur; next ? cur.forwardPos() : cur.backwardPos())
+ if (cur.inTexted() && !cur.paragraph().isUnchanged(cur.pos())) {
+ if (!next)
+ // if we search backwards, take a step forward
+ // to correctly set the anchor
+ cur.forwardPos();
return true;
+ }
+
return false;
}
int const ssize = searchstr.size();
Cursor cur(*bv);
- cur.setCursor(doc_iterator_begin(buf.inset()));
+ cur.setCursor(doc_iterator_begin(&buf));
while (findForward(cur, match, false)) {
// Backup current cursor position and font.
pos_type const pos = cur.pos();
}
buf.updateLabels();
- bv->putSelectionAt(doc_iterator_begin(buf.inset()), 0, false);
+ bv->putSelectionAt(doc_iterator_begin(&buf), 0, false);
if (num)
buf.markDirty();
return num;
bool findNextChange(BufferView * bv)
{
+ return findChange(bv, true);
+}
+
+
+bool findPreviousChange(BufferView * bv)
+{
+ return findChange(bv, false);
+}
+
+
+bool findChange(BufferView * bv, bool next)
+{
+ if (bv->cursor().selection()) {
+ // set the cursor at the beginning or at the end of the selection
+ // before searching. Otherwise, the current change will be found.
+ if (next != bv->cursor().top() > bv->cursor().anchor())
+ bv->cursor().setCursorToAnchor();
+ }
+
DocIterator cur = bv->cursor();
+
+ // Are we within a change ? Then first search forward (backward),
+ // clear the selection and search the other way around (see the end
+ // of this function). This will avoid changes to be selected half.
+ bool search_both_sides = false;
+ if (cur.pos() > 1) {
+ Change change_next_pos
+ = cur.paragraph().lookupChange(cur.pos());
+ Change change_prev_pos
+ = cur.paragraph().lookupChange(cur.pos() - 1);
+ if (change_next_pos.isSimilarTo(change_prev_pos))
+ search_both_sides = true;
+ }
- if (!findChange(cur))
+ if (!findChange(cur, next))
return false;
bv->cursor().setCursor(cur);
bv->cursor().resetAnchor();
+ if (!next)
+ // take a step into the change
+ cur.backwardPos();
+
Change orig_change = cur.paragraph().lookupChange(cur.pos());
CursorSlice & tip = cur.top();
- for (; !tip.at_end(); tip.forwardPos()) {
- Change change = tip.paragraph().lookupChange(tip.pos());
- if (change != orig_change)
- break;
+ if (next) {
+ for (; !tip.at_end(); tip.forwardPos()) {
+ Change change = tip.paragraph().lookupChange(tip.pos());
+ if (change != orig_change)
+ break;
+ }
+ } else {
+ for (; !tip.at_begin();) {
+ tip.backwardPos();
+ Change change = tip.paragraph().lookupChange(tip.pos());
+ if (change != orig_change) {
+ // take a step forward to correctly set the selection
+ tip.forwardPos();
+ break;
+ }
+ }
}
// Now put cursor to end of selection:
bv->cursor().setCursor(cur);
bv->cursor().setSelection();
+ if (search_both_sides) {
+ bv->cursor().setSelection(false);
+ findChange(bv, !next);
+ }
+
return true;
}
**/
size_t find_matching_brace(string const & s, size_t pos)
{
- BOOST_ASSERT(s[pos] == '{');
+ LASSERT(s[pos] == '{', /* */);
int open_braces = 1;
for (++pos; pos < s.size(); ++pos) {
if (s[pos] == '\\')
**/
class MatchStringAdv {
public:
- MatchStringAdv(lyx::Buffer const & buf, FindAdvOptions const & opt);
+ MatchStringAdv(lyx::Buffer const & buf, FindAndReplaceOptions const & opt);
/** Tests if text starting at the supplied position matches with the one provided to the MatchStringAdv
** constructor as opt.search, under the opt.* options settings.
**
+ ** @param at_begin
+ ** If set, then match is searched only against beginning of text starting at cur.
+ ** If unset, then match is searched anywhere in text starting at cur.
+ **
** @return
** The length of the matching text, or zero if no match was found.
**/
- int operator()(DocIterator const & cur, int len = -1) const;
+ int operator()(DocIterator const & cur, int len = -1, bool at_begin = true) const;
public:
/// buffer
lyx::Buffer const & buf;
/// options
- FindAdvOptions const & opt;
+ FindAndReplaceOptions const & opt;
private:
/** Normalize a stringified or latexified LyX paragraph.
string par_as_string;
// regular expression to use for searching
boost::regex regexp;
+ // same as regexp, but prefixed with a ".*"
+ boost::regex regexp2;
// unmatched open braces in the search string/regexp
int open_braces;
// number of (.*?) subexpressions added at end of search regexp for closing
};
-MatchStringAdv::MatchStringAdv(lyx::Buffer const & buf, FindAdvOptions const & opt)
+MatchStringAdv::MatchStringAdv(lyx::Buffer const & buf, FindAndReplaceOptions const & opt)
: buf(buf), opt(opt)
{
par_as_string = normalize(opt.search);
break;
} while (true);
LYXERR(Debug::DEBUG, "Open braces: " << open_braces);
- BOOST_ASSERT(braces_match(par_as_string.begin(), par_as_string.end(), open_braces));
+ LASSERT(braces_match(par_as_string.begin(), par_as_string.end(), open_braces), /* */);
LYXERR(Debug::DEBUG, "Built MatchStringAdv object: par_as_string = '" << par_as_string << "'");
} else {
par_as_string = escape_for_regex(par_as_string);
LYXERR(Debug::DEBUG, "par_as_string now is '" << par_as_string << "'");
LYXERR(Debug::DEBUG, "Open braces: " << open_braces);
LYXERR(Debug::DEBUG, "Close .*? : " << close_wildcards);
- BOOST_ASSERT(braces_match(par_as_string.begin(), par_as_string.end(), open_braces));
+ LASSERT(braces_match(par_as_string.begin(), par_as_string.end(), open_braces), /* */);
// Entered regexp must match at begin of searched string buffer
par_as_string = string("\\`") + par_as_string;
LYXERR(Debug::DEBUG, "Replaced text (to be used as regex): " << par_as_string);
regexp = boost::regex(par_as_string);
+ regexp2 = boost::regex(string(".*") + par_as_string);
}
}
-int MatchStringAdv::operator()(DocIterator const & cur, int len) const
+
+int MatchStringAdv::operator()(DocIterator const & cur, int len, bool at_begin) const
{
- docstring docstr = stringifyFromForSearch(opt, buf, cur, len);
+ docstring docstr = stringifyFromForSearch(opt, cur, len);
LYXERR(Debug::DEBUG, "Matching against '" << lyx::to_utf8(docstr) << "'");
string str = normalize(docstr);
LYXERR(Debug::DEBUG, "After normalization: '" << str << "'");
if (! opt.regexp) {
- if (str.substr(0, par_as_string.size()) == par_as_string)
- return par_as_string.size();
+ if (at_begin) {
+ if (str.substr(0, par_as_string.size()) == par_as_string)
+ return par_as_string.size();
+ } else {
+ size_t pos = str.find(par_as_string);
+ if (pos != string::npos)
+ return par_as_string.size();
+ }
} else {
// Try all possible regexp matches, until one that verifies the braces match test is found
- boost::sregex_iterator re_it(str.begin(), str.end(), regexp);
+ boost::regex const *p_regexp = at_begin ? ®exp : ®exp2;
+ boost::sregex_iterator re_it(str.begin(), str.end(), *p_regexp);
boost::sregex_iterator re_it_end;
for (; re_it != re_it_end; ++re_it) {
boost::match_results<string::const_iterator> const & m = *re_it;
Paragraph const & par = cur.paragraph();
// TODO what about searching beyond/across paragraph breaks ?
// TODO Try adding a AS_STR_INSERTS as last arg
- return par.asString(cur.pos(), ( len == -1 || cur.pos() + len > int(par.size()) ) ? int(par.size()) : cur.pos() + len, AS_STR_INSETS);
+ pos_type end = ( len == -1 || cur.pos() + len > int(par.size()) ) ? int(par.size()) : cur.pos() + len;
+ OutputParams runparams(&cur.buffer()->params().encoding());
+ odocstringstream os;
+ runparams.nice = true;
+ runparams.flavor = OutputParams::LATEX;
+ runparams.linelen = 100000; //lyxrc.plaintext_linelen;
+ // No side effect of file copying and image conversion
+ runparams.dryrun = true;
+ LYXERR(Debug::DEBUG, "Stringifying with cur: " << cur << ", from pos: " << cur.pos() << ", end: " << end);
+ return par.stringify(cur.pos(), end, AS_STR_INSETS, runparams);
} else if (cur.inMathed()) {
odocstringstream os;
CursorSlice cs = cur.top();
return docstring();
}
-/** Computes the LaTeX export of buf starting from cur and ending len positions after cur,
- ** if len is positive, or at the paragraph or innermost inset end if len is -1.
- **/
-docstring latexifyFromCursor(Buffer const & buf, DocIterator const & cur, int len)
+/** Computes the LaTeX export of buf starting from cur and ending len positions
+ * after cur, if len is positive, or at the paragraph or innermost inset end
+ * if len is -1.
+ */
+
+docstring latexifyFromCursor(DocIterator const & cur, int len)
{
LYXERR(Debug::DEBUG, "Latexifying with len=" << len << " from cursor at pos: " << cur);
LYXERR(Debug::DEBUG, " with cur.lastpost=" << cur.lastpos() << ", cur.lastrow="
<< cur.lastrow() << ", cur.lastcol=" << cur.lastcol());
- BOOST_ASSERT(buf.isLatex());
+ Buffer const & buf = *cur.buffer();
+ LASSERT(buf.isLatex(), /* */);
TexRow texrow;
odocstringstream ods;
for (MathData::const_iterator it = md.begin() + cs.pos(); it != it_end; ++it)
ods << *it;
-// MathData md = cur.cell();
-// MathData::const_iterator it_end = ( ( len == -1 || cur.pos() + len > int(md.size()) ) ? md.end() : md.begin() + cur.pos() + len );
-// for (MathData::const_iterator it = md.begin() + cur.pos(); it != it_end; ++it) {
-// MathAtom const & ma = *it;
-// ma.nucleus()->latex(buf, ods, runparams);
-// }
+ // MathData md = cur.cell();
+ // MathData::const_iterator it_end = ( ( len == -1 || cur.pos() + len > int(md.size()) ) ? md.end() : md.begin() + cur.pos() + len );
+ // for (MathData::const_iterator it = md.begin() + cur.pos(); it != it_end; ++it) {
+ // MathAtom const & ma = *it;
+ // ma.nucleus()->latex(buf, ods, runparams);
+ // }
- // Retrieve the math environment type, and add '$' or '$]' or others (\end{equation}) accordingly
+ // Retrieve the math environment type, and add '$' or '$]'
+ // or others (\end{equation}) accordingly
for (int s = cur.depth() - 1; s >= 0; --s) {
CursorSlice const & cs = cur[s];
- if (cs.asInsetMath() && cs.asInsetMath() && cs.asInsetMath()->asHullInset()) {
- WriteStream ws(ods);
- cs.asInsetMath()->asHullInset()->footer_write(ws);
- break;
+ InsetMath * inset = cs.asInsetMath();
+ if (inset && inset->asHullInset()) {
+ WriteStream ws(ods);
+ inset->asHullInset()->footer_write(ws);
+ break;
}
}
LYXERR(Debug::DEBUG, "Latexified math: '" << lyx::to_utf8(ods.str()) << "'");
**/
int findAdvFinalize(DocIterator & cur, MatchStringAdv const & match)
{
- // Search the foremost position that matches (avoids find of entire math inset when match at start of it)
+ // Search the foremost position that matches (avoids find of entire math
+ // inset when match at start of it)
size_t d;
- DocIterator old_cur;
+ DocIterator old_cur(cur.buffer());
do {
- LYXERR(Debug::DEBUG, "Forwarding one step (searching for innermost match)");
+ LYXERR(Debug::DEBUG, "Forwarding one step (searching for innermost match)");
d = cur.depth();
old_cur = cur;
cur.forwardPos();
} while (cur && cur.depth() > d && match(cur) > 0);
cur = old_cur;
- BOOST_ASSERT(match(cur) > 0);
+ LASSERT(match(cur) > 0, /* */);
LYXERR(Debug::DEBUG, "Ok");
// Compute the match length
++len;
LYXERR(Debug::DEBUG, "verifying unmatch with len = " << len);
}
- int old_len = match(cur, len); // Length of matched text (different from len param)
+ // Length of matched text (different from len param)
+ int old_len = match(cur, len);
int new_len;
// Greedy behaviour while matching regexps
while ((new_len = match(cur, len + 1)) > old_len) {
return len;
}
+
/// Finds forward
int findForwardAdv(DocIterator & cur, MatchStringAdv const & match)
{
- if (! cur)
+ if (!cur)
return 0;
- for (; cur; cur.forwardPos()) {
-// odocstringstream ods;
-// ods << _("Searching ... ") << (cur.bottom().lastpit() - cur.bottom().pit()) * 100 / total;
-// cur.message(ods.str());
- if (match(cur))
- return findAdvFinalize(cur, match);
- }
+ int wrap_answer;
+ do {
+ while (cur && !match(cur, -1, false)) {
+ if (cur.pit() < cur.lastpit())
+ cur.forwardPar();
+ else {
+ cur.forwardPos();
+ }
+ }
+ for (; cur; cur.forwardPos()) {
+ if (match(cur))
+ return findAdvFinalize(cur, match);
+ }
+ wrap_answer = frontend::Alert::prompt(
+ _("Wrap search ?"),
+ _("End of document reached while searching forward\n"
+ "\n"
+ "Continue searching from beginning ?"),
+ 0, 1, _("&Yes"), _("&No"));
+ cur.clear();
+ cur.push_back(CursorSlice(match.buf.inset()));
+ } while (wrap_answer == 0);
return 0;
}
+
/// Finds backwards
int findBackwardsAdv(DocIterator & cur, MatchStringAdv const & match) {
-// if (cur.pos() > 0 || cur.depth() > 0)
-// cur.backwardPos();
+ // if (cur.pos() > 0 || cur.depth() > 0)
+ // cur.backwardPos();
DocIterator cur_orig(cur);
if (match(cur_orig))
findAdvFinalize(cur_orig, match);
-// int total = cur.bottom().pit() + 1;
- for (; cur; cur.backwardPos()) {
-// odocstringstream ods;
-// ods << _("Searching ... ") << (total - cur.bottom().pit()) * 100 / total;
-// cur.message(ods.str());
- if (match(cur)) {
- // Find the most backward consecutive match within same paragraph while searching backwards.
- int pit = cur.pit();
- int old_len;
- DocIterator old_cur;
- int len = findAdvFinalize(cur, match);
- do {
- old_cur = cur;
- old_len = len;
+ // int total = cur.bottom().pit() + 1;
+ int wrap_answer;
+ do {
+ // TODO No ! così non va.
+ bool pit_changed = false;
+ while (cur && !match(cur, -1, false)) {
+ if (cur.pit() > 0)
+ --cur.pit();
+ else {
cur.backwardPos();
- LYXERR(Debug::DEBUG, "old_cur: " << old_cur << ", old_len=" << len << ", cur: " << cur);
- } while (cur && cur.pit() == pit && match(cur)
- && (len = findAdvFinalize(cur, match)) > old_len);
- cur = old_cur;
- len = old_len;
- LYXERR(Debug::DEBUG, "cur_orig : " << cur_orig);
- LYXERR(Debug::DEBUG, "cur : " << cur);
- if (cur != cur_orig)
- return len;
+ if (cur)
+ cur.pos() = 0;
+ }
+ pit_changed = true;
}
- }
+ if (cur && pit_changed)
+ cur.pos() = cur.lastpos();
+ for (; cur; cur.backwardPos()) {
+ if (match(cur)) {
+ // Find the most backward consecutive match within same paragraph while searching backwards.
+ int pit = cur.pit();
+ int old_len;
+ DocIterator old_cur;
+ int len = findAdvFinalize(cur, match);
+ do {
+ old_cur = cur;
+ old_len = len;
+ cur.backwardPos();
+ LYXERR(Debug::DEBUG, "old_cur: " << old_cur << ", old_len=" << len << ", cur: " << cur);
+ } while (cur && cur.pit() == pit && match(cur)
+ && (len = findAdvFinalize(cur, match)) > old_len);
+ cur = old_cur;
+ len = old_len;
+ LYXERR(Debug::DEBUG, "cur_orig : " << cur_orig);
+ LYXERR(Debug::DEBUG, "cur : " << cur);
+ if (cur != cur_orig)
+ return len;
+ }
+ }
+ wrap_answer = frontend::Alert::prompt(
+ _("Wrap search ?"),
+ _("Beginning of document reached while searching backwards\n"
+ "\n"
+ "Continue searching from end ?"),
+ 0, 1, _("&Yes"), _("&No"));
+ cur = doc_iterator_end(&match.buf);
+ cur.backwardPos();
+ } while (wrap_answer == 0);
return 0;
}
} // anonym namespace
-docstring stringifyFromForSearch(FindAdvOptions const & opt, Buffer const & buf,
+docstring stringifyFromForSearch(FindAndReplaceOptions const & opt,
DocIterator const & cur, int len)
{
- if (! opt.ignoreformat)
- return latexifyFromCursor(buf, cur, len);
+ if (!opt.ignoreformat)
+ return latexifyFromCursor(cur, len);
else
return stringifyFromCursor(cur, len);
}
-lyx::FindAdvOptions::FindAdvOptions(docstring const & search, bool casesensitive,
+lyx::FindAndReplaceOptions::FindAndReplaceOptions(docstring const & search, bool casesensitive,
bool matchword, bool forward, bool expandmacros, bool ignoreformat,
- bool regexp)
+ bool regexp, docstring const & replace)
: search(search), casesensitive(casesensitive), matchword(matchword),
forward(forward), expandmacros(expandmacros), ignoreformat(ignoreformat),
- regexp(regexp)
+ regexp(regexp), replace(replace)
{
}
/// Perform a FindAdv operation.
-bool findAdv(BufferView * bv, FindAdvOptions const & opt)
+bool findAdv(BufferView * bv, FindAndReplaceOptions const & opt)
{
DocIterator cur = bv->cursor();
int match_len = 0;
if (opt.search.empty()) {
- bv->message(_("Search text is empty !"));
+ bv->message(_("Search text is empty!"));
return false;
}
// if (! bv->buffer()) {
match_len = findBackwardsAdv(cur, matchAdv);
} catch (...) {
// This may only be raised by boost::regex()
- bv->message(_("Invalid regular expression !"));
+ bv->message(_("Invalid regular expression!"));
return false;
}
if (match_len == 0) {
- bv->message(_("Match not found !"));
+ bv->message(_("Match not found!"));
return false;
}
LYXERR(Debug::DEBUG, "Putting selection at " << cur << " with len: " << match_len);
bv->putSelectionAt(cur, match_len, ! opt.forward);
- bv->message(_("Match found !"));
- //bv->update();
+ bv->message(_("Match found!"));
+ if (opt.replace != docstring(from_utf8(LYX_FR_NULL_STRING))) {
+ dispatch(FuncRequest(LFUN_SELF_INSERT, opt.replace));
+ }
return true;
}
if (!bv || ev.action != LFUN_WORD_FINDADV)
return;
- FindAdvOptions opt;
+ FindAndReplaceOptions opt;
istringstream iss(to_utf8(ev.argument()));
iss >> opt;
findAdv(bv, opt);
}
-ostringstream & operator<<(ostringstream & os, lyx::FindAdvOptions const & opt)
+ostringstream & operator<<(ostringstream & os, lyx::FindAndReplaceOptions const & opt)
{
os << to_utf8(opt.search) << "\nEOSS\n"
<< opt.casesensitive << ' '
<< opt.forward << ' '
<< opt.expandmacros << ' '
<< opt.ignoreformat << ' '
- << opt.regexp;
+ << opt.regexp << ' '
+ << to_utf8(opt.replace) << "\nEOSS\n";
LYXERR(Debug::DEBUG, "built: " << os.str());
return os;
}
-istringstream & operator>>(istringstream & is, lyx::FindAdvOptions & opt)
+istringstream & operator>>(istringstream & is, lyx::FindAndReplaceOptions & opt)
{
LYXERR(Debug::DEBUG, "parsing");
string s;
LYXERR(Debug::DEBUG, "searching for: '" << s << "'");
opt.search = from_utf8(s);
is >> opt.casesensitive >> opt.matchword >> opt.forward >> opt.expandmacros >> opt.ignoreformat >> opt.regexp;
+ is.get(); // Waste space before replace string
+ s = "";
+ getline(is, line);
+ while (line != "EOSS") {
+ if (! s.empty())
+ s = s + "\n";
+ s = s + line;
+ if (is.eof()) // Tolerate malformed request
+ break;
+ getline(is, line);
+ }
LYXERR(Debug::DEBUG, "parsed: " << opt.casesensitive << ' ' << opt.matchword << ' ' << opt.forward << ' '
<< opt.expandmacros << ' ' << opt.ignoreformat << ' ' << opt.regexp);
+ LYXERR(Debug::DEBUG, "replacing with: '" << s << "'");
+ opt.replace = from_utf8(s);
return is;
}