]> git.lyx.org Git - lyx.git/blob - src/lyxfind.C
last Friday's text*.C -> text_func shuffle
[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 namespace lyx::support;
18
19 using lyx::pos_type;
20 using std::endl;
21
22 namespace lyxfind {
23
24 /// returns true if the specified string is at the specified  position
25 bool IsStringInText(Paragraph * par, pos_type pos,
26                     string const & str, bool const & = true,
27                     bool const & = false);
28
29 /// if the string is found: return true and set the cursor to the new position
30 SearchResult SearchForward(BufferView *, LyXText * text, string const & str,
31                            bool const & = true, bool const & = false);
32 ///
33 SearchResult SearchBackward(BufferView *, LyXText * text, string const & str,
34                             bool const & = true, bool const & = false);
35
36 int LyXReplace(BufferView * bv,
37                string const & searchstr, string const & replacestr,
38                bool forward, bool casesens, bool matchwrd, bool replaceall,
39                bool once)
40 {
41         if (!bv->available() || bv->buffer()->isReadonly())
42                 return 0;
43
44         // CutSelection cannot cut a single space, so we have to stop
45         // in order to avoid endless loop :-(
46         if (searchstr.length() == 0
47                 || (searchstr.length() == 1 && searchstr[0] == ' ')) {
48 #ifdef WITH_WARNINGS
49 #warning BLECH. If we have an LFUN for replace, we can sort of fix this bogosity
50 #endif
51                 Alert::error(_("Cannot replace"),
52                         _("You cannot replace a single space or "
53                           "an empty character."));
54                 return 0;
55         }
56
57         // now we can start searching for the first
58         // start at top if replaceall
59         LyXText * text = bv->getLyXText();
60         bool fw = forward;
61         if (replaceall) {
62                 text->clearSelection();
63                 bv->unlockInset(bv->theLockingInset());
64                 text = bv->text;
65                 text->cursorTop();
66                 // override search direction because we search top to bottom
67                 fw = true;
68         }
69
70         // if nothing selected or selection does not equal search string
71         // search and select next occurance and return if no replaceall
72         string str1;
73         string str2;
74         if (casesens) {
75                 str1 = searchstr;
76                 str2 = text->selectionAsString(bv->buffer(), false);
77         } else {
78                 str1 = lowercase(searchstr);
79                 str2 = lowercase(text->selectionAsString(bv->buffer(), false));
80         }
81         if (str1 != str2) {
82                 if (!LyXFind(bv, searchstr, fw, casesens, matchwrd) ||
83                         !replaceall) {
84                         return 0;
85                 }
86         }
87
88         bool found = false;
89         int replace_count = 0;
90         do {
91                 text = bv->getLyXText();
92                 // We have to do this check only because mathed insets don't
93                 // return their own LyXText but the LyXText of it's parent!
94                 if (!bv->theLockingInset() ||
95                         ((text != bv->text) &&
96                          (text->inset_owner == text->inset_owner->getLockingInset()))) {
97                         bv->update(text, BufferView::SELECT);
98                         bv->toggleSelection(false);
99                         text->replaceSelectionWithString(replacestr);
100                         text->setSelectionRange(replacestr.length());
101                         bv->update(text, BufferView::SELECT);
102                         ++replace_count;
103                 }
104                 if (!once)
105                         found = LyXFind(bv, searchstr, fw, casesens, matchwrd);
106         } while (!once && replaceall && found);
107
108         // FIXME: should be called via an LFUN
109         bv->buffer()->markDirty();
110         bv->fitCursor();
111
112         return replace_count;
113 }
114
115
116 bool LyXFind(BufferView * bv,
117              string const & searchstr, bool forward,
118              bool casesens, bool matchwrd)
119 {
120         if (!bv->available() || searchstr.empty())
121                 return false;
122
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->update(bv->getLyXText(), BufferView::SELECT);
378
379         pos_type length;
380
381         if (bv->theLockingInset()) {
382                 bool found = bv->theLockingInset()->nextChange(bv, length);
383
384                 // We found the stuff inside the inset so we don't have to
385                 // do anything as the inset did all the update for us!
386                 if (found)
387                         return true;
388
389                 // We now are in the main text but if we did a forward
390                 // search we have to put the cursor behind the inset.
391                 bv->text->cursorRight(true);
392         }
393         // If we arrive here we are in the main text again so we
394         // just start searching from the root LyXText at the position
395         // we are!
396         LyXText * text = bv->text;
397
398         if (text->selection.set())
399                 text->cursor = text->selection.end;
400
401         bv->toggleSelection();
402         text->clearSelection();
403
404         SearchResult result = nextChange(bv, text, length);
405
406         bool found = true;
407
408         // If we found the cursor inside an inset we will get back
409         // SR_FOUND_NOUPDATE and we don't have to do anything as the
410         // inset did it already.
411         if (result == SR_FOUND) {
412                 bv->unlockInset(bv->theLockingInset());
413                 bv->update(text, BufferView::SELECT);
414                 text->setSelectionRange(length);
415                 bv->toggleSelection(false);
416                 bv->update(text, BufferView::SELECT);
417         } else if (result == SR_NOT_FOUND) {
418                 bv->unlockInset(bv->theLockingInset());
419                 bv->update(text, BufferView::SELECT);
420                 found = false;
421         }
422
423         bv->fitCursor();
424
425         return found;
426 }
427
428 } // end lyxfind namespace