]> git.lyx.org Git - lyx.git/blob - src/lyxfind.C
Fix dEPM crash:
[lyx.git] / src / lyxfind.C
1 /**
2  * \file lyxfind.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author John Levon
8  * \author Jürgen Vigna
9  * \author Alfredo Braunstein
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "lyxfind.h"
17
18 #include "buffer.h"
19 #include "cursor.h"
20 #include "CutAndPaste.h"
21 #include "BufferView.h"
22 #include "debug.h"
23 #include "funcrequest.h"
24 #include "gettext.h"
25 #include "lyxtext.h"
26 #include "paragraph.h"
27 #include "pariterator.h"
28 #include "undo.h"
29
30 #include "frontends/Alert.h"
31
32 #include "support/convert.h"
33 #include "support/docstream.h"
34
35 namespace lyx {
36
37 using support::lowercase;
38 using support::uppercase;
39 using support::split;
40
41 using std::advance;
42
43
44 namespace {
45
46 bool parse_bool(docstring & howto)
47 {
48         if (howto.empty())
49                 return false;
50         docstring var;
51         howto = split(howto, var, ' ');
52         return (var == "1");
53 }
54
55
56 class MatchString : public std::binary_function<Paragraph, pos_type, bool>
57 {
58 public:
59         MatchString(docstring const & str, bool cs, bool mw)
60                 : str(str), cs(cs), mw(mw)
61         {}
62
63         // returns true if the specified string is at the specified position
64         bool operator()(Paragraph const & par, pos_type pos) const
65         {
66                 docstring::size_type const size = str.length();
67                 pos_type i = 0;
68                 pos_type const parsize = par.size();
69                 for (i = 0; pos + i < parsize; ++i) {
70                         if (docstring::size_type(i) >= size)
71                                 break;
72                         if (cs && str[i] != par.getChar(pos + i))
73                                 break;
74                         if (!cs && uppercase(str[i]) != uppercase(par.getChar(pos + i)))
75                                 break;
76                 }
77
78                 if (size != docstring::size_type(i))
79                         return false;
80
81                 // if necessary, check whether string matches word
82                 if (mw) {
83                         if (pos > 0 && par.isLetter(pos - 1))
84                                 return false;
85                         if (pos + pos_type(size) < parsize
86                             && par.isLetter(pos + size))
87                                 return false;
88                 }
89
90                 return true;
91         }
92
93 private:
94         // search string
95         docstring str;
96         // case sensitive
97         bool cs;
98         // match whole words only
99         bool mw;
100 };
101
102
103 bool findForward(DocIterator & cur, MatchString const & match)
104 {
105         for (; cur; cur.forwardChar())
106                 if (cur.inTexted() && match(cur.paragraph(), cur.pos()))
107                         return true;
108         return false;
109 }
110
111
112 bool findBackwards(DocIterator & cur, MatchString const & match)
113 {
114         while (cur) {
115                 cur.backwardChar();
116                 if (cur.inTexted() && match(cur.paragraph(), cur.pos()))
117                         return true;
118         }
119         return false;
120 }
121
122
123 bool findChange(DocIterator & cur)
124 {
125         for (; cur; cur.forwardPos())
126                 if (cur.inTexted() && !cur.paragraph().isUnchanged(cur.pos()))
127                         return true;
128         return false;
129 }
130
131
132 bool searchAllowed(BufferView * bv, docstring const & str)
133 {
134         if (str.empty()) {
135                 frontend::Alert::error(_("Search error"),
136                                             _("Search string is empty"));
137                 return false;
138         }
139         return bv->buffer();
140 }
141
142
143 bool find(BufferView * bv, docstring const & searchstr, bool cs, bool mw, bool fw)
144 {
145         if (!searchAllowed(bv, searchstr))
146                 return false;
147
148         DocIterator cur = bv->cursor();
149
150         MatchString const match(searchstr, cs, mw);
151
152         bool found = fw ? findForward(cur, match) : findBackwards(cur, match);
153
154         if (found)
155                 bv->putSelectionAt(cur, searchstr.length(), !fw);
156
157         return found;
158 }
159
160
161 int replaceAll(BufferView * bv,
162                docstring const & searchstr, docstring const & replacestr,
163                bool cs, bool mw)
164 {
165         Buffer & buf = *bv->buffer();
166
167         if (!searchAllowed(bv, searchstr) || buf.isReadonly())
168                 return 0;
169
170         recordUndoFullDocument(bv);
171
172         MatchString const match(searchstr, cs, mw);
173         int num = 0;
174
175         int const rsize = replacestr.size();
176         int const ssize = searchstr.size();
177
178         DocIterator cur = doc_iterator_begin(buf.inset());
179         while (findForward(cur, match)) {
180                 pos_type pos = cur.pos();
181                 LyXFont const font
182                         = cur.paragraph().getFontSettings(buf.params(), pos);
183                 int striked = ssize - cur.paragraph().eraseChars(pos, pos + ssize,
184                                                             buf.params().trackChanges);
185                 cur.paragraph().insert(pos, replacestr, font,
186                                        Change(buf.params().trackChanges ?
187                                               Change::INSERTED : Change::UNCHANGED));
188                 for (int i = 0; i < rsize + striked; ++i)
189                         cur.forwardChar();
190                 ++num;
191         }
192
193         bv->buffer()->text().init(bv);
194         bv->putSelectionAt(doc_iterator_begin(buf.inset()), 0, false);
195         if (num)
196                 buf.markDirty();
197         return num;
198 }
199
200
201 bool stringSelected(BufferView * bv, docstring const & searchstr,
202                     bool cs, bool mw, bool fw)
203 {
204         // if nothing selected or selection does not equal search
205         // string search and select next occurance and return
206         docstring const & str1 = searchstr;
207         docstring const str2 = bv->cursor().selectionAsString(false);
208         if ((cs && str1 != str2) || lowercase(str1) != lowercase(str2)) {
209                 find(bv, searchstr, cs, mw, fw);
210                 return false;
211         }
212
213         return true;
214 }
215
216
217 int replace(BufferView * bv, docstring const & searchstr,
218             docstring const & replacestr, bool cs, bool mw, bool fw)
219 {
220         if (!searchAllowed(bv, searchstr) || bv->buffer()->isReadonly())
221                 return 0;
222
223         if (!stringSelected(bv, searchstr, cs, mw, fw))
224                 return 0;
225
226         LCursor & cur = bv->cursor();
227         cap::replaceSelectionWithString(cur, replacestr, fw);
228         bv->buffer()->markDirty();
229         find(bv, searchstr, cs, mw, fw);
230         bv->update();
231
232         return 1;
233 }
234
235 } // namespace anon
236
237
238 docstring const find2string(docstring const & search,
239                          bool casesensitive, bool matchword, bool forward)
240 {
241         odocstringstream ss;
242         ss << search << '\n'
243            << int(casesensitive) << ' '
244            << int(matchword) << ' '
245            << int(forward);
246         return ss.str();
247 }
248
249
250 docstring const replace2string(docstring const & search, docstring const & replace,
251                             bool casesensitive, bool matchword,
252                             bool all, bool forward)
253 {
254         odocstringstream ss;
255         ss << search << '\n'
256            << replace << '\n'
257            << int(casesensitive) << ' '
258            << int(matchword) << ' '
259            << int(all) << ' '
260            << int(forward);
261         return ss.str();
262 }
263
264
265 void find(BufferView * bv, FuncRequest const & ev)
266 {
267         if (!bv || ev.action != LFUN_WORD_FIND)
268                 return;
269
270         //lyxerr << "find called, cmd: " << ev << std::endl;
271
272         // data is of the form
273         // "<search>
274         //  <casesensitive> <matchword> <forward>"
275         docstring search;
276         docstring howto = split(ev.argument(), search, '\n');
277
278         bool casesensitive = parse_bool(howto);
279         bool matchword     = parse_bool(howto);
280         bool forward       = parse_bool(howto);
281
282         bool const found = find(bv, search,
283                                   casesensitive, matchword, forward);
284
285         if (!found)
286                 // emit message signal.
287                 bv->message(_("String not found!"));
288 }
289
290
291 void replace(BufferView * bv, FuncRequest const & ev)
292 {
293         if (!bv || ev.action != LFUN_WORD_REPLACE)
294                 return;
295
296         // data is of the form
297         // "<search>
298         //  <replace>
299         //  <casesensitive> <matchword> <all> <forward>"
300         docstring search;
301         docstring rplc;
302         docstring howto = split(ev.argument(), search, '\n');
303         howto = split(howto, rplc, '\n');
304
305         bool casesensitive = parse_bool(howto);
306         bool matchword     = parse_bool(howto);
307         bool all           = parse_bool(howto);
308         bool forward       = parse_bool(howto);
309
310         Buffer * buf = bv->buffer();
311
312         int const replace_count = all
313                 ? replaceAll(bv, search, rplc, casesensitive, matchword)
314                 : replace(bv, search, rplc, casesensitive, matchword, forward);
315
316         if (replace_count == 0) {
317                 // emit message signal.
318                 buf->message(_("String not found!"));
319         } else {
320                 if (replace_count == 1) {
321                         // emit message signal.
322                         buf->message(_("String has been replaced."));
323                 } else {
324                         docstring str = convert<docstring>(replace_count);
325                         str += _(" strings have been replaced.");
326                         // emit message signal.
327                         buf->message(str);
328                 }
329         }
330 }
331
332
333 bool findNextChange(BufferView * bv)
334 {
335         if (!bv->buffer())
336                 return false;
337
338         DocIterator cur = bv->cursor();
339
340         if (!findChange(cur))
341                 return false;
342
343         bv->cursor().setCursor(cur);
344         bv->cursor().resetAnchor();
345
346         Change orig_change = cur.paragraph().lookupChange(cur.pos());
347
348         DocIterator et = doc_iterator_end(cur.inset());
349         for (; cur != et; cur.forwardPosNoDescend()) {
350                 Change change = cur.paragraph().lookupChange(cur.pos());
351                 if (change != orig_change) {
352                         break;
353                 }
354         }
355         // Now put cursor to end of selection:
356         bv->cursor().setCursor(cur);
357         bv->cursor().setSelection();
358         // if we used a lfun like in find/replace, dispatch would do
359         // that for us
360         bv->update();
361
362         return true;
363 }
364
365 } // lyx namespace