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