]> git.lyx.org Git - lyx.git/blob - src/lyxfind.C
ws changes only
[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 namespace lyx {
35 namespace find {
36
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->cursorPar();
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         }
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->cursorPar();
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
154 int replace(BufferView * bv,
155                string const & searchstr, string const & replacestr,
156                bool forward, bool casesens, bool matchwrd, bool replaceall,
157                bool once)
158 {
159         if (!bv->available() || bv->buffer()->isReadonly())
160                 return 0;
161
162         // CutSelection cannot cut a single space, so we have to stop
163         // in order to avoid endless loop :-(
164         if (searchstr.length() == 0
165                 || (searchstr.length() == 1 && searchstr[0] == ' ')) {
166 #ifdef WITH_WARNINGS
167 #warning BLECH. If we have an LFUN for replace, we can sort of fix this bogosity
168 #endif
169                 Alert::error(_("Cannot replace"),
170                         _("You cannot replace a single space or "
171                           "an empty character."));
172                 return 0;
173         }
174
175         // now we can start searching for the first
176         // start at top if replaceall
177         LyXText * text = bv->getLyXText();
178         bool fw = forward;
179         if (replaceall) {
180                 text->clearSelection();
181                 bv->unlockInset(bv->theLockingInset());
182                 text = bv->text;
183                 text->cursorTop();
184                 // override search direction because we search top to bottom
185                 fw = true;
186         }
187
188         // if nothing selected or selection does not equal search string
189         // search and select next occurance and return if no replaceall
190         string str1;
191         string str2;
192         if (casesens) {
193                 str1 = searchstr;
194                 str2 = text->selectionAsString(*bv->buffer(), false);
195         } else {
196                 str1 = lowercase(searchstr);
197                 str2 = lowercase(text->selectionAsString(*bv->buffer(), false));
198         }
199         if (str1 != str2) {
200                 if (!find(bv, searchstr, fw, casesens, matchwrd) ||
201                         !replaceall) {
202                         return 0;
203                 }
204         }
205
206         bool found = false;
207         int replace_count = 0;
208         do {
209                 text = bv->getLyXText();
210                 // We have to do this check only because mathed insets don't
211                 // return their own LyXText but the LyXText of it's parent!
212                 if (!bv->theLockingInset() ||
213                         ((text != bv->text) &&
214                          (text->inset_owner == text->inset_owner->getLockingInset()))) {
215                         text->replaceSelectionWithString(replacestr);
216                         text->setSelectionRange(replacestr.length());
217                         ++replace_count;
218                 }
219                 if (!once)
220                         found = find(bv, searchstr, fw, casesens, matchwrd);
221         } while (!once && replaceall && found);
222
223         // FIXME: should be called via an LFUN
224         bv->buffer()->markDirty();
225         bv->update();
226
227         return replace_count;
228 }
229
230
231 bool find(BufferView * bv,
232              string const & searchstr, bool forward,
233              bool casesens, bool matchwrd)
234 {
235         if (!bv->available() || searchstr.empty())
236                 return false;
237
238         if (bv->theLockingInset()) {
239                 bool found = forward ?
240                         bv->theLockingInset()->searchForward(bv, searchstr, casesens, matchwrd) :
241                         bv->theLockingInset()->searchBackward(bv, searchstr, casesens, matchwrd);
242                 // We found the stuff inside the inset so we don't have to
243                 // do anything as the inset did all the update for us!
244                 if (found)
245                         return true;
246                 // We now are in the main text but if we did a forward
247                 // search we have to put the cursor behind the inset.
248                 if (forward) {
249                         bv->text->cursorRight(true);
250                 }
251         }
252         // If we arrive here we are in the main text again so we
253         // just start searching from the root LyXText at the position
254         // we are!
255         LyXText * text = bv->text;
256
257
258         if (text->selection.set())
259                 text->cursor = forward ?
260                         text->selection.end : text->selection.start;
261
262         text->clearSelection();
263
264         SearchResult result = forward ?
265                 searchForward(bv, text, searchstr, casesens, matchwrd) :
266                 searchBackward(bv, text, searchstr, casesens, matchwrd);
267
268         bool found = true;
269         // If we found the cursor inside an inset we will get back
270         // SR_FOUND_NOUPDATE and we don't have to do anything as the
271         // inset did it already.
272         if (result == SR_FOUND) {
273                 bv->unlockInset(bv->theLockingInset());
274                 text->setSelectionRange(searchstr.length());
275         } else if (result == SR_NOT_FOUND) {
276                 bv->unlockInset(bv->theLockingInset());
277                 found = false;
278         }
279         bv->update();
280
281         return found;
282 }
283
284
285 SearchResult find(BufferView * bv, LyXText * text,
286                      string const & searchstr, bool forward,
287                      bool casesens, bool matchwrd)
288 {
289         if (text->selection.set())
290                 text->cursor = forward ?
291                         text->selection.end : text->selection.start;
292
293         text->clearSelection();
294
295         SearchResult result = forward ?
296                 searchForward(bv, text, searchstr, casesens, matchwrd) :
297                 searchBackward(bv, text, searchstr, casesens, matchwrd);
298
299         return result;
300 }
301
302
303
304
305 SearchResult nextChange(BufferView * bv, LyXText * text, pos_type & length)
306 {
307         ParagraphList::iterator pit = text->cursorPar();
308         ParagraphList::iterator pend = text->ownerParagraphs().end();
309         pos_type pos = text->cursor.pos();
310
311         while (pit != pend) {
312                 pos_type parsize = pit->size();
313
314                 if (pos < parsize) {
315                         if ((!parsize || pos != parsize)
316                                 && pit->lookupChange(pos) != Change::UNCHANGED)
317                                 break;
318
319                         if (pit->isInset(pos) && pit->getInset(pos)->isTextInset()) {
320                                 UpdatableInset * inset = (UpdatableInset *)pit->getInset(pos);
321                                 if (inset->nextChange(bv, length))
322                                         return SR_FOUND_NOUPDATE;
323                         }
324                 }
325
326                 ++pos;
327
328                 if (pos >= parsize) {
329                         ++pit;
330                         pos = 0;
331                 }
332         }
333
334         if (pit == pend)
335                 return SR_NOT_FOUND;
336
337         text->setCursor(pit, pos);
338         Change orig_change = pit->lookupChangeFull(pos);
339         pos_type parsize = pit->size();
340         pos_type end = pos;
341
342         for (; end != parsize; ++end) {
343                 Change change = pit->lookupChangeFull(end);
344                 if (change != orig_change) {
345                         // slight UI optimisation: for replacements, we get
346                         // text like : _old_new. Consider that as one change.
347                         if (!(orig_change.type == Change::DELETED &&
348                                 change.type == Change::INSERTED))
349                                 break;
350                 }
351         }
352         length = end - pos;
353         return SR_FOUND;
354 }
355
356
357 SearchResult findNextChange(BufferView * bv, LyXText * text, pos_type & length)
358 {
359         if (text->selection.set())
360                 text->cursor = text->selection.end;
361
362         text->clearSelection();
363
364         return nextChange(bv, text, length);
365 }
366
367
368 bool findNextChange(BufferView * bv)
369 {
370         if (!bv->available())
371                 return false;
372
373         pos_type length;
374
375         if (bv->theLockingInset()) {
376                 bool found = bv->theLockingInset()->nextChange(bv, length);
377
378                 // We found the stuff inside the inset so we don't have to
379                 // do anything as the inset did all the update for us!
380                 if (found)
381                         return true;
382
383                 // We now are in the main text but if we did a forward
384                 // search we have to put the cursor behind the inset.
385                 bv->text->cursorRight(true);
386         }
387         // If we arrive here we are in the main text again so we
388         // just start searching from the root LyXText at the position
389         // we are!
390         LyXText * text = bv->text;
391
392         if (text->selection.set())
393                 text->cursor = text->selection.end;
394
395         text->clearSelection();
396
397         SearchResult result = nextChange(bv, text, length);
398
399         bool found = true;
400
401         // If we found the cursor inside an inset we will get back
402         // SR_FOUND_NOUPDATE and we don't have to do anything as the
403         // inset did it already.
404         if (result == SR_FOUND) {
405                 bv->unlockInset(bv->theLockingInset());
406                 text->setSelectionRange(length);
407         } else if (result == SR_NOT_FOUND) {
408                 bv->unlockInset(bv->theLockingInset());
409                 found = false;
410         }
411
412         bv->update();
413
414         return found;
415 }
416
417 } // find namespace
418 } // lyx namespace