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