]> git.lyx.org Git - lyx.git/blob - src/lyxfind.C
96c3da77a069677bcdbc7d7a81e6f8851a2dcc23
[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_slice.h"
20 #include "BufferView.h"
21 #include "debug.h"
22 #include "iterators.h"
23 #include "funcrequest.h"
24 #include "gettext.h"
25 #include "lyxtext.h"
26 #include "paragraph.h"
27 #include "PosIterator.h"
28 #include "undo.h"
29
30 #include "frontends/Alert.h"
31 #include "frontends/LyXView.h"
32
33 #include "support/textutils.h"
34 #include "support/tostr.h"
35
36 #include "support/std_sstream.h"
37
38 using lyx::support::lowercase;
39 using lyx::support::uppercase;
40 using lyx::support::split;
41
42 using std::ostringstream;
43 using std::string;
44
45
46 namespace {
47
48 bool parse_bool(string & howto)
49 {
50         if (howto.empty())
51                 return false;
52         string var;
53         howto = split(howto, var, ' ');
54         return (var == "1");
55 }
56
57
58 bool find(BufferView * bv,
59           string const & searchstr, bool cs, bool mw, bool fw);
60
61
62 int replace(BufferView * bv,
63             string const & searchstr, string const & replacestr,
64             bool cs, bool mw, bool fw);
65
66
67 int replaceAll(BufferView * bv,
68                string const & searchstr, string const & replacestr,
69                bool cs, bool mw);
70
71
72 bool findChange(PosIterator & cur, PosIterator const & end);
73
74 } // namespace anon
75
76
77 namespace lyx {
78 namespace find {
79
80 string const find2string(string const & search,
81                          bool casesensitive, bool matchword, bool forward)
82 {
83         ostringstream ss;
84         ss << search << '\n'
85            << int(casesensitive) << ' '
86            << int(matchword) << ' '
87            << int(forward);
88
89         return ss.str();
90 }
91  
92
93 string const replace2string(string const & search, string const & replace,
94                             bool casesensitive, bool matchword,
95                             bool all, bool forward)
96 {
97         ostringstream ss;
98         ss << search << '\n'
99            << replace << '\n'
100            << int(casesensitive) << ' '
101            << int(matchword) << ' '
102            << int(all) << ' '
103            << int(forward);
104
105         return ss.str();
106 }
107
108
109 void find(FuncRequest const & ev)
110 {
111         if (!ev.view() || ev.action != LFUN_WORD_FIND)
112                 return;
113
114         // data is of the form
115         // "<search>
116         //  <casesensitive> <matchword> <forward>"
117         string search;
118         string howto = split(ev.argument, search, '\n');
119
120         bool casesensitive = parse_bool(howto);
121         bool matchword     = parse_bool(howto);
122         bool forward       = parse_bool(howto);
123
124         BufferView * bv = ev.view();
125         bool const found = ::find(bv, search,
126                                   forward, casesensitive, matchword);
127
128         if (!found)
129                 bv->owner()->message(_("String not found!"));
130 }
131
132
133 void replace(FuncRequest const & ev)
134 {
135         if (!ev.view() || ev.action != LFUN_WORD_REPLACE)
136                 return;
137
138         // data is of the form
139         // "<search>
140         //  <replace>
141         //  <casesensitive> <matchword> <all> <forward>"
142         string search;
143         string replace;
144         string howto = split(ev.argument, search, '\n');
145         howto = split(howto, replace, '\n');
146
147         bool casesensitive = parse_bool(howto);
148         bool matchword     = parse_bool(howto);
149         bool all           = parse_bool(howto);
150         bool forward       = parse_bool(howto);
151
152         BufferView * bv = ev.view();
153         LyXView * lv = bv->owner();
154
155         int const replace_count = all ?
156                 ::replaceAll(bv, search, replace,
157                              casesensitive, matchword) :
158                 ::replace(bv, search, replace,
159                           casesensitive, matchword, forward);
160  
161         if (replace_count == 0) {
162                 lv->message(_("String not found!"));
163         } else {
164                 if (replace_count == 1) {
165                         lv->message(_("String has been replaced."));
166                 } else {
167                         string str = tostr(replace_count);
168                         str += _(" strings have been replaced.");
169                         lv->message(str);
170                 }
171         }
172 }
173
174
175 bool findNextChange(BufferView * bv)
176 {
177         if (!bv->available())
178                 return false;
179
180         PosIterator cur = PosIterator(*bv);
181         PosIterator const endit = bv->buffer()->pos_iterator_end();
182
183         if (!findChange(cur, endit))
184                 return false;
185         
186         ParagraphList::iterator pit = cur.pit();
187         pos_type pos = cur.pos();
188         
189         Change orig_change = pit->lookupChangeFull(pos);
190         pos_type parsize = pit->size();
191         pos_type end = pos;
192
193         for (; end != parsize; ++end) {
194                 Change change = pit->lookupChangeFull(end);
195                 if (change != orig_change) {
196                         // slight UI optimisation: for replacements, we get
197                         // text like : _old_new. Consider that as one change.
198                         if (!(orig_change.type == Change::DELETED &&
199                                 change.type == Change::INSERTED))
200                                 break;
201                 }
202         }
203         pos_type length = end - pos;
204         bv->putSelectionAt(cur, length, true);
205         return true;
206 }
207
208 } // find namespace
209 } // lyx namespace
210
211
212 namespace {
213
214 class MatchString
215 {
216 public:
217         MatchString(string const & str, bool cs, bool mw)
218                 : str(str), cs(cs), mw(mw)
219         {}
220
221         // returns true if the specified string is at the specified position
222         bool operator()(Paragraph const & par, lyx::pos_type pos) const
223         {
224                 string::size_type const size = str.length();
225                 lyx::pos_type i = 0;
226                 lyx::pos_type const parsize = par.size();
227                 while ((pos + i < parsize)
228                        && (string::size_type(i) < size)
229                        && (cs ? (str[i] == par.getChar(pos + i))
230                            : (uppercase(str[i]) == uppercase(par.getChar(pos + i))))) {
231                         ++i;
232                 }
233
234                 if (size != string::size_type(i))
235                         return false;
236
237                 // if necessary, check whether string matches word
238                 if (mw) {
239                         if (pos > 0     && IsLetterCharOrDigit(par.getChar(pos - 1)))
240                                 return false;
241                         if (pos + lyx::pos_type(size) < parsize
242                                         && IsLetterCharOrDigit(par.getChar(pos + size)));
243                                 return false;
244                 }
245
246                 return true;
247         }
248         
249 private:
250         // search string
251         string str;
252         // case sensitive
253         bool cs;
254         // match whole words only
255         bool mw;
256 };
257
258
259 bool findForward(PosIterator & cur, PosIterator const & end,
260                  MatchString const & match)
261 {
262         for (; cur != end; ++cur) {
263                 if (match(*cur.pit(), cur.pos()))
264                         return true;
265         }
266         return false;
267 }
268
269
270 bool findBackwards(PosIterator & cur, PosIterator const & beg,
271                    MatchString const & match)
272 {
273         while (beg != cur) {
274                 --cur;
275                 if (match(*cur.pit(), cur.pos()))
276                         return true;
277         }
278         return false;
279 }
280
281
282 bool findChange(PosIterator & cur, PosIterator const & end)
283 {
284         for (; cur != end; ++cur) {
285                 if ((!cur.pit()->size() || !cur.at_end())
286                     && cur.pit()->lookupChange(cur.pos()) != Change::UNCHANGED)
287                         return true;
288         }
289         return false;
290 }
291
292
293 bool searchAllowed(BufferView * bv, string const & str)
294 {
295         if (str.empty()) {
296                 Alert::error(_("Search error"), _("Search string is empty"));
297                 return false;
298         }
299         return bv->available();
300 }
301
302
303 bool find(BufferView * bv, string const & searchstr, bool cs, bool mw, bool fw)
304 {
305         if (!searchAllowed(bv, searchstr))
306                 return false;
307
308         PosIterator cur = PosIterator(*bv);
309
310         MatchString const match(searchstr, cs, mw);
311
312         PosIterator const end = bv->buffer()->pos_iterator_end();
313         PosIterator const beg = bv->buffer()->pos_iterator_begin();
314
315         bool found = fw ? findForward(cur, end, match)
316                 : findBackwards(cur, beg, match);
317
318         if (found)
319                 bv->putSelectionAt(cur, searchstr.length(), !fw);
320
321         return found;
322 }
323
324
325 int replaceAll(BufferView * bv,
326                string const & searchstr, string const & replacestr,
327                bool cs, bool mw)
328 {
329         Buffer & buf = *bv->buffer();
330
331         if (!searchAllowed(bv, searchstr) || buf.isReadonly())
332                 return 0;
333         
334         recordUndo(Undo::ATOMIC, bv->text(), 0, buf.paragraphs().size() - 1);
335         
336         PosIterator cur = buf.pos_iterator_begin();
337         PosIterator const end = buf.pos_iterator_end();
338         MatchString const match(searchstr, cs, mw);
339         int num = 0;
340
341         int const rsize = replacestr.size();
342         int const ssize = searchstr.size();
343
344         while (findForward(cur, end, match)) {
345                 lyx::pos_type pos = cur.pos();
346                 LyXFont const font
347                         = cur.pit()->getFontSettings(buf.params(), pos);
348                 int striked = ssize - cur.pit()->erase(pos, pos + ssize);
349                 cur.pit()->insert(pos, replacestr, font);
350                 advance(cur, rsize + striked);
351                 ++num;
352         }
353
354         PosIterator beg = buf.pos_iterator_begin();
355         bv->text()->init(bv);
356         bv->putSelectionAt(beg, 0, false);
357         if (num)
358                 buf.markDirty();
359         return num;
360 }
361
362
363 bool stringSelected(BufferView * bv,
364                     string const & searchstr, 
365                     bool cs, bool mw, bool fw)
366 {
367         LyXText * text = bv->getLyXText();
368         // if nothing selected or selection does not equal search
369         // string search and select next occurance and return
370         string const & str1 = searchstr;
371         string const str2 = text->selectionAsString(*bv->buffer(),
372                                                     false);
373         if ((cs && str1 != str2) || lowercase(str1) != lowercase(str2)) {
374                 find(bv, searchstr, cs, mw, fw);
375                 return false;
376         }
377
378         return true;
379 }
380
381
382 int replace(BufferView * bv,
383             string const & searchstr, string const & replacestr,
384             bool cs, bool mw, bool fw)
385 {
386         if (!searchAllowed(bv, searchstr) || bv->buffer()->isReadonly())
387                 return 0;
388
389         if (!stringSelected(bv, searchstr, cs, mw, fw))
390                 return 0;
391
392         LyXText * text = bv->getLyXText();
393
394         text->replaceSelectionWithString(replacestr);
395         text->setSelectionRange(replacestr.length());
396         bv->cursor() = fw ? bv->selEnd() : bv->selStart();
397         bv->buffer()->markDirty();
398         find(bv, searchstr, cs, mw, fw);
399         bv->update();
400         
401         return 1;
402 }
403
404 } //namespace anon