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