]> git.lyx.org Git - features.git/blob - src/lyxfind.C
c17cb344957f4e1b6eb87edbf67b966375dc0c20
[features.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 "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(BufferView * bv, FuncRequest const & ev)
110 {
111         if (!bv || 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         bool const found = ::find(bv, search,
125                                   forward, casesensitive, matchword);
126
127         if (!found)
128                 bv->owner()->message(_("String not found!"));
129 }
130
131
132 void replace(BufferView * bv, FuncRequest const & ev)
133 {
134         if (!bv || ev.action != LFUN_WORD_REPLACE)
135                 return;
136
137         // data is of the form
138         // "<search>
139         //  <replace>
140         //  <casesensitive> <matchword> <all> <forward>"
141         string search;
142         string replace;
143         string howto = split(ev.argument, search, '\n');
144         howto = split(howto, replace, '\n');
145
146         bool casesensitive = parse_bool(howto);
147         bool matchword     = parse_bool(howto);
148         bool all           = parse_bool(howto);
149         bool forward       = parse_bool(howto);
150
151         LyXView * lv = bv->owner();
152
153         int const replace_count = all ?
154                 ::replaceAll(bv, search, replace,
155                              casesensitive, matchword) :
156                 ::replace(bv, search, replace,
157                           casesensitive, matchword, forward);
158  
159         if (replace_count == 0) {
160                 lv->message(_("String not found!"));
161         } else {
162                 if (replace_count == 1) {
163                         lv->message(_("String has been replaced."));
164                 } else {
165                         string str = tostr(replace_count);
166                         str += _(" strings have been replaced.");
167                         lv->message(str);
168                 }
169         }
170 }
171
172
173 bool findNextChange(BufferView * bv)
174 {
175         if (!bv->available())
176                 return false;
177
178         PosIterator cur = PosIterator(*bv);
179         PosIterator const endit = bv->buffer()->pos_iterator_end();
180
181         if (!findChange(cur, endit))
182                 return false;
183         
184         ParagraphList::iterator pit = cur.pit();
185         pos_type pos = cur.pos();
186         
187         Change orig_change = pit->lookupChangeFull(pos);
188         pos_type parsize = pit->size();
189         pos_type end = pos;
190
191         for (; end != parsize; ++end) {
192                 Change change = pit->lookupChangeFull(end);
193                 if (change != orig_change) {
194                         // slight UI optimisation: for replacements, we get
195                         // text like : _old_new. Consider that as one change.
196                         if (!(orig_change.type == Change::DELETED &&
197                                 change.type == Change::INSERTED))
198                                 break;
199                 }
200         }
201         pos_type length = end - pos;
202         bv->putSelectionAt(cur, length, true);
203         return true;
204 }
205
206 } // find namespace
207 } // lyx namespace
208
209
210 namespace {
211
212 class MatchString
213 {
214 public:
215         MatchString(string const & str, bool cs, bool mw)
216                 : str(str), cs(cs), mw(mw)
217         {}
218
219         // returns true if the specified string is at the specified position
220         bool operator()(Paragraph const & par, lyx::pos_type pos) const
221         {
222                 string::size_type const size = str.length();
223                 lyx::pos_type i = 0;
224                 lyx::pos_type const parsize = par.size();
225                 while ((pos + i < parsize)
226                        && (string::size_type(i) < size)
227                        && (cs ? (str[i] == par.getChar(pos + i))
228                            : (uppercase(str[i]) == uppercase(par.getChar(pos + i))))) {
229                         ++i;
230                 }
231
232                 if (size != string::size_type(i))
233                         return false;
234
235                 // if necessary, check whether string matches word
236                 if (mw) {
237                         if (pos > 0     && IsLetterCharOrDigit(par.getChar(pos - 1)))
238                                 return false;
239                         if (pos + lyx::pos_type(size) < parsize
240                                         && IsLetterCharOrDigit(par.getChar(pos + size)));
241                                 return false;
242                 }
243
244                 return true;
245         }
246         
247 private:
248         // search string
249         string str;
250         // case sensitive
251         bool cs;
252         // match whole words only
253         bool mw;
254 };
255
256
257 bool findForward(PosIterator & cur, PosIterator const & end,
258                  MatchString const & match)
259 {
260         for (; cur != end; ++cur) {
261                 if (match(*cur.pit(), cur.pos()))
262                         return true;
263         }
264         return false;
265 }
266
267
268 bool findBackwards(PosIterator & cur, PosIterator const & beg,
269                    MatchString const & match)
270 {
271         while (beg != cur) {
272                 --cur;
273                 if (match(*cur.pit(), cur.pos()))
274                         return true;
275         }
276         return false;
277 }
278
279
280 bool findChange(PosIterator & cur, PosIterator const & end)
281 {
282         for (; cur != end; ++cur) {
283                 if ((!cur.pit()->size() || !cur.at_end())
284                     && cur.pit()->lookupChange(cur.pos()) != Change::UNCHANGED)
285                         return true;
286         }
287         return false;
288 }
289
290
291 bool searchAllowed(BufferView * bv, string const & str)
292 {
293         if (str.empty()) {
294                 Alert::error(_("Search error"), _("Search string is empty"));
295                 return false;
296         }
297         return bv->available();
298 }
299
300
301 bool find(BufferView * bv, string const & searchstr, bool cs, bool mw, bool fw)
302 {
303         if (!searchAllowed(bv, searchstr))
304                 return false;
305
306         PosIterator cur = PosIterator(*bv);
307
308         MatchString const match(searchstr, cs, mw);
309
310         PosIterator const end = bv->buffer()->pos_iterator_end();
311         PosIterator const beg = bv->buffer()->pos_iterator_begin();
312
313         bool found = fw ? findForward(cur, end, match)
314                 : findBackwards(cur, beg, match);
315
316         if (found)
317                 bv->putSelectionAt(cur, searchstr.length(), !fw);
318
319         return found;
320 }
321
322
323 int replaceAll(BufferView * bv,
324                string const & searchstr, string const & replacestr,
325                bool cs, bool mw)
326 {
327         Buffer & buf = *bv->buffer();
328
329         if (!searchAllowed(bv, searchstr) || buf.isReadonly())
330                 return 0;
331         
332         recordUndo(Undo::ATOMIC, bv->text(), 0, buf.paragraphs().size() - 1);
333         
334         PosIterator cur = buf.pos_iterator_begin();
335         PosIterator const end = buf.pos_iterator_end();
336         MatchString const match(searchstr, cs, mw);
337         int num = 0;
338
339         int const rsize = replacestr.size();
340         int const ssize = searchstr.size();
341
342         while (findForward(cur, end, match)) {
343                 lyx::pos_type pos = cur.pos();
344                 LyXFont const font
345                         = cur.pit()->getFontSettings(buf.params(), pos);
346                 int striked = ssize - cur.pit()->erase(pos, pos + ssize);
347                 cur.pit()->insert(pos, replacestr, font);
348                 advance(cur, rsize + striked);
349                 ++num;
350         }
351
352         PosIterator beg = buf.pos_iterator_begin();
353         bv->text()->init(bv);
354         bv->putSelectionAt(beg, 0, false);
355         if (num)
356                 buf.markDirty();
357         return num;
358 }
359
360
361 bool stringSelected(BufferView * bv,
362                     string const & searchstr, 
363                     bool cs, bool mw, bool fw)
364 {
365         LyXText * text = bv->getLyXText();
366         // if nothing selected or selection does not equal search
367         // string search and select next occurance and return
368         string const & str1 = searchstr;
369         string const str2 = text->selectionAsString(*bv->buffer(),
370                                                     false);
371         if ((cs && str1 != str2) || lowercase(str1) != lowercase(str2)) {
372                 find(bv, searchstr, cs, mw, fw);
373                 return false;
374         }
375
376         return true;
377 }
378
379
380 int replace(BufferView * bv,
381             string const & searchstr, string const & replacestr,
382             bool cs, bool mw, bool fw)
383 {
384         if (!searchAllowed(bv, searchstr) || bv->buffer()->isReadonly())
385                 return 0;
386
387         if (!stringSelected(bv, searchstr, cs, mw, fw))
388                 return 0;
389
390         LyXText * text = bv->getLyXText();
391
392         text->replaceSelectionWithString(replacestr);
393         text->setSelectionRange(replacestr.length());
394         bv->cursor().current() = fw ? bv->cursor().selEnd() : bv->cursor().selBegin();
395         bv->buffer()->markDirty();
396         find(bv, searchstr, cs, mw, fw);
397         bv->update();
398         
399         return 1;
400 }
401
402 } //namespace anon