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