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