]> git.lyx.org Git - lyx.git/blob - src/lyxfind.C
Self-consistent header files.
[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  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14
15 #include "lyxfind.h"
16
17 #include "buffer.h"
18 #include "BufferView.h"
19 #include "gettext.h"
20 #include "lyxtext.h"
21 #include "paragraph.h"
22
23 #include "frontends/Alert.h"
24
25 #include "insets/insettext.h"
26
27 #include "support/textutils.h"
28
29 using lyx::support::lowercase;
30 using lyx::support::uppercase;
31
32 using std::string;
33
34
35 namespace lyx {
36 namespace find {
37
38 namespace {
39
40 // returns true if the specified string is at the specified position
41 bool isStringInText(Paragraph const & par, pos_type pos,
42                     string const & str, bool const & cs,
43                     bool const & mw)
44 {
45         string::size_type size = str.length();
46         pos_type i = 0;
47         pos_type parsize = par.size();
48         while (((pos + i) < parsize)
49                && (string::size_type(i) < size)
50                && (cs ? (str[i] == par.getChar(pos + i))
51                    : (uppercase(str[i]) == uppercase(par.getChar(pos + i))))) {
52                 ++i;
53         }
54
55         if (size == string::size_type(i)) {
56                 // if necessary, check whether string matches word
57                 if (!mw)
58                         return true;
59                 if ((pos <= 0 || !IsLetterCharOrDigit(par.getChar(pos - 1)))
60                         && (pos + pos_type(size) >= parsize
61                         || !IsLetterCharOrDigit(par.getChar(pos + size)))) {
62                         return true;
63                 }
64         }
65         return false;
66 }
67
68 // forward search:
69 // if the string can be found: return true and set the cursor to
70 // the new position, cs = casesensitive, mw = matchword
71 SearchResult searchForward(BufferView * bv, LyXText * text, string const & str,
72                            bool const & cs, bool const & mw)
73 {
74         ParagraphList::iterator pit = text->cursor.par();
75         ParagraphList::iterator pend = text->ownerParagraphs().end();
76         pos_type pos = text->cursor.pos();
77         UpdatableInset * inset;
78
79         while (pit != pend && !isStringInText(*pit, pos, str, cs, mw)) {
80                 if (pos < pit->size()
81                     && pit->isInset(pos)
82                     && (inset = (UpdatableInset *)pit->getInset(pos))
83                     && inset->isTextInset()
84                     && inset->searchForward(bv, str, cs, mw))
85                         return SR_FOUND_NOUPDATE;
86
87                 if (++pos >= pit->size()) {
88                         ++pit;
89                         pos = 0;
90                 }
91         }
92
93         if (pit != pend) {
94                 text->setCursor(pit, pos);
95                 return SR_FOUND;
96         } else
97                 return SR_NOT_FOUND;
98 }
99
100
101 // backward search:
102 // if the string can be found: return true and set the cursor to
103 // the new position, cs = casesensitive, mw = matchword
104 SearchResult searchBackward(BufferView * bv, LyXText * text,
105                             string const & str,
106                             bool const & cs, bool const & mw)
107 {
108         ParagraphList::iterator pit = text->cursor.par();
109         ParagraphList::iterator pbegin = text->ownerParagraphs().begin();
110         pos_type pos = text->cursor.pos();
111
112         // skip past a match at the current cursor pos
113         if (pos > 0) {
114                 --pos;
115         } else if (pit != pbegin) {
116                 --pit;
117                 pos = pit->size();
118         } else {
119                 return SR_NOT_FOUND;
120         }
121
122         while (true) {
123                 if (pos < pit->size()) {
124                         if (pit->isInset(pos) && pit->getInset(pos)->isTextInset()) {
125                                 UpdatableInset * inset = (UpdatableInset *)pit->getInset(pos);
126                                 if (inset->searchBackward(bv, str, cs, mw))
127                                         return SR_FOUND_NOUPDATE;
128                         }
129
130                         if (isStringInText(*pit, pos, str, cs, mw)) {
131                                 text->setCursor(pit, pos);
132                                 return SR_FOUND;
133                         }
134                 }
135
136                 if (pos == 0 && pit == pbegin)
137                         break;
138
139                 if (pos > 0) {
140                         --pos;
141                 } else if (pit != pbegin) {
142                         --pit;
143                         pos = pit->size();
144                 }
145         }
146
147         return SR_NOT_FOUND;
148 }
149
150 } // anon namespace
151
152
153 int replace(BufferView * bv,
154                string const & searchstr, string const & replacestr,
155                bool forward, bool casesens, bool matchwrd, bool replaceall,
156                bool once)
157 {
158         if (!bv->available() || bv->buffer()->isReadonly())
159                 return 0;
160
161         // CutSelection cannot cut a single space, so we have to stop
162         // in order to avoid endless loop :-(
163         if (searchstr.length() == 0
164                 || (searchstr.length() == 1 && searchstr[0] == ' ')) {
165 #ifdef WITH_WARNINGS
166 #warning BLECH. If we have an LFUN for replace, we can sort of fix this bogosity
167 #endif
168                 Alert::error(_("Cannot replace"),
169                         _("You cannot replace a single space or "
170                           "an empty character."));
171                 return 0;
172         }
173
174         // now we can start searching for the first
175         // start at top if replaceall
176         LyXText * text = bv->getLyXText();
177         bool fw = forward;
178         if (replaceall) {
179                 text->clearSelection();
180                 bv->unlockInset(bv->theLockingInset());
181                 text = bv->text;
182                 text->cursorTop();
183                 // override search direction because we search top to bottom
184                 fw = true;
185         }
186
187         // if nothing selected or selection does not equal search string
188         // search and select next occurance and return if no replaceall
189         string str1;
190         string str2;
191         if (casesens) {
192                 str1 = searchstr;
193                 str2 = text->selectionAsString(*bv->buffer(), false);
194         } else {
195                 str1 = lowercase(searchstr);
196                 str2 = lowercase(text->selectionAsString(*bv->buffer(), false));
197         }
198         if (str1 != str2) {
199                 if (!find(bv, searchstr, fw, casesens, matchwrd) ||
200                         !replaceall) {
201                         return 0;
202                 }
203         }
204
205         bool found = false;
206         int replace_count = 0;
207         do {
208                 text = bv->getLyXText();
209                 // We have to do this check only because mathed insets don't
210                 // return their own LyXText but the LyXText of it's parent!
211                 if (!bv->theLockingInset() ||
212                         ((text != bv->text) &&
213                          (text->inset_owner == text->inset_owner->getLockingInset()))) {
214                         text->replaceSelectionWithString(replacestr);
215                         text->setSelectionRange(replacestr.length());
216                         ++replace_count;
217                 }
218                 if (!once)
219                         found = find(bv, searchstr, fw, casesens, matchwrd);
220         } while (!once && replaceall && found);
221
222         // FIXME: should be called via an LFUN
223         bv->buffer()->markDirty();
224         bv->update();
225
226         return replace_count;
227 }
228
229
230 bool find(BufferView * bv,
231              string const & searchstr, bool forward,
232              bool casesens, bool matchwrd)
233 {
234         if (!bv->available() || searchstr.empty())
235                 return false;
236
237         if (bv->theLockingInset()) {
238                 bool found = forward ?
239                         bv->theLockingInset()->searchForward(bv, searchstr, casesens, matchwrd) :
240                         bv->theLockingInset()->searchBackward(bv, searchstr, casesens, matchwrd);
241                 // We found the stuff inside the inset so we don't have to
242                 // do anything as the inset did all the update for us!
243                 if (found)
244                         return true;
245                 // We now are in the main text but if we did a forward
246                 // search we have to put the cursor behind the inset.
247                 if (forward) {
248                         bv->text->cursorRight(true);
249                 }
250         }
251         // If we arrive here we are in the main text again so we
252         // just start searching from the root LyXText at the position
253         // we are!
254         LyXText * text = bv->text;
255
256
257         if (text->selection.set())
258                 text->cursor = forward ?
259                         text->selection.end : text->selection.start;
260
261         text->clearSelection();
262
263         SearchResult result = forward ?
264                 searchForward(bv, text, searchstr, casesens, matchwrd) :
265                 searchBackward(bv, text, searchstr, casesens, matchwrd);
266
267         bool found = true;
268         // If we found the cursor inside an inset we will get back
269         // SR_FOUND_NOUPDATE and we don't have to do anything as the
270         // inset did it already.
271         if (result == SR_FOUND) {
272                 bv->unlockInset(bv->theLockingInset());
273                 text->setSelectionRange(searchstr.length());
274         } else if (result == SR_NOT_FOUND) {
275                 bv->unlockInset(bv->theLockingInset());
276                 found = false;
277         }
278         bv->update();
279
280         return found;
281 }
282
283
284 SearchResult find(BufferView * bv, LyXText * text,
285                      string const & searchstr, bool forward,
286                      bool casesens, bool matchwrd)
287 {
288         if (text->selection.set())
289                 text->cursor = forward ?
290                         text->selection.end : text->selection.start;
291
292         text->clearSelection();
293
294         SearchResult result = forward ?
295                 searchForward(bv, text, searchstr, casesens, matchwrd) :
296                 searchBackward(bv, text, searchstr, casesens, matchwrd);
297
298         return result;
299 }
300
301
302
303
304 SearchResult nextChange(BufferView * bv, LyXText * text, pos_type & length)
305 {
306         ParagraphList::iterator pit = text->cursor.par();
307         ParagraphList::iterator pend = text->ownerParagraphs().end();
308         pos_type pos = text->cursor.pos();
309
310         while (pit != pend) {
311                 pos_type parsize = pit->size();
312
313                 if (pos < parsize) {
314                         if ((!parsize || pos != parsize)
315                                 && pit->lookupChange(pos) != Change::UNCHANGED)
316                                 break;
317
318                         if (pit->isInset(pos) && pit->getInset(pos)->isTextInset()) {
319                                 UpdatableInset * inset = (UpdatableInset *)pit->getInset(pos);
320                                 if (inset->nextChange(bv, length))
321                                         return SR_FOUND_NOUPDATE;
322                         }
323                 }
324
325                 ++pos;
326
327                 if (pos >= parsize) {
328                         ++pit;
329                         pos = 0;
330                 }
331         }
332
333         if (pit == pend)
334                 return SR_NOT_FOUND;
335
336         text->setCursor(pit, pos);
337         Change orig_change = pit->lookupChangeFull(pos);
338         pos_type parsize = pit->size();
339         pos_type end = pos;
340
341         for (; end != parsize; ++end) {
342                 Change change = pit->lookupChangeFull(end);
343                 if (change != orig_change) {
344                         // slight UI optimisation: for replacements, we get
345                         // text like : _old_new. Consider that as one change.
346                         if (!(orig_change.type == Change::DELETED &&
347                                 change.type == Change::INSERTED))
348                                 break;
349                 }
350         }
351         length = end - pos;
352         return SR_FOUND;
353 }
354
355
356 SearchResult findNextChange(BufferView * bv, LyXText * text, pos_type & length)
357 {
358         if (text->selection.set())
359                 text->cursor = text->selection.end;
360
361         text->clearSelection();
362
363         return nextChange(bv, text, length);
364 }
365
366
367 bool findNextChange(BufferView * bv)
368 {
369         if (!bv->available())
370                 return false;
371
372         pos_type length;
373
374         if (bv->theLockingInset()) {
375                 bool found = bv->theLockingInset()->nextChange(bv, length);
376
377                 // We found the stuff inside the inset so we don't have to
378                 // do anything as the inset did all the update for us!
379                 if (found)
380                         return true;
381
382                 // We now are in the main text but if we did a forward
383                 // search we have to put the cursor behind the inset.
384                 bv->text->cursorRight(true);
385         }
386         // If we arrive here we are in the main text again so we
387         // just start searching from the root LyXText at the position
388         // we are!
389         LyXText * text = bv->text;
390
391         if (text->selection.set())
392                 text->cursor = text->selection.end;
393
394         text->clearSelection();
395
396         SearchResult result = nextChange(bv, text, length);
397
398         bool found = true;
399
400         // If we found the cursor inside an inset we will get back
401         // SR_FOUND_NOUPDATE and we don't have to do anything as the
402         // inset did it already.
403         if (result == SR_FOUND) {
404                 bv->unlockInset(bv->theLockingInset());
405                 text->setSelectionRange(length);
406         } else if (result == SR_NOT_FOUND) {
407                 bv->unlockInset(bv->theLockingInset());
408                 found = false;
409         }
410
411         bv->update();
412
413         return found;
414 }
415
416 } // find namespace
417 } // lyx namespace