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