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