]> git.lyx.org Git - lyx.git/blob - src/lyxfind.C
Fix natbib bug spotted by JMarc.
[lyx.git] / src / lyxfind.C
1 #include <config.h>
2
3 #ifdef __GNUG__
4 #pragma implementation
5 #endif
6
7 #include "lyxtext.h"
8 #include "lyxfind.h"
9 #include "paragraph.h"
10 #include "frontends/LyXView.h"
11 #include "frontends/Alert.h"
12 #include "support/textutils.h"
13 #include "support/lstrings.h"
14 #include "BufferView.h"
15 #include "buffer.h"
16 #include "debug.h"
17 #include "gettext.h"
18 #include "insets/insettext.h"
19 #include "changes.h"
20
21 using lyx::pos_type;
22 using std::endl;
23
24 namespace lyxfind {
25
26 /// returns true if the specified string is at the specified  position
27 bool IsStringInText(Paragraph * par, pos_type pos,
28                     string const & str, bool const & = true,
29                     bool const & = false);
30
31 /// if the string is found: return true and set the cursor to the new position
32 SearchResult SearchForward(BufferView *, LyXText * text, string const & str,
33                            bool const & = true, bool const & = false);
34 ///
35 SearchResult SearchBackward(BufferView *, LyXText * text, string const & str,
36                             bool const & = true, bool const & = false);
37
38 int LyXReplace(BufferView * bv,
39                string const & searchstr, string const & replacestr,
40                bool forward, bool casesens, bool matchwrd, bool replaceall,
41                bool once)
42 {
43         if (!bv->available() || bv->buffer()->isReadonly())
44                 return 0;
45
46         // CutSelection cannot cut a single space, so we have to stop
47         // in order to avoid endless loop :-(
48         if (searchstr.length() == 0
49                 || (searchstr.length() == 1 && searchstr[0] == ' '))
50         {
51                 Alert::alert(_("Sorry!"), _("You cannot replace a single space, "
52                                           "nor an empty character."));
53                 return 0;
54         }
55
56         // now we can start searching for the first
57         // start at top if replaceall
58         LyXText * text = bv->getLyXText();
59         bool fw = forward;
60         if (replaceall) {
61                 text->clearSelection();
62                 bv->unlockInset(bv->theLockingInset());
63                 text = bv->text;
64                 text->cursorTop(bv);
65                 // override search direction because we search top to bottom
66                 fw = true;
67         }
68
69         // if nothing selected or selection does not equal search string
70         // search and select next occurance and return if no replaceall
71         string str1;
72         string str2;
73         if (casesens) {
74                 str1 = searchstr;
75                 str2 = text->selectionAsString(bv->buffer(), false);
76         } else {
77                 str1 = lowercase(searchstr);
78                 str2 = lowercase(text->selectionAsString(bv->buffer(), false));
79         }
80         if (str1 != str2) {
81                 if (!LyXFind(bv, searchstr, fw, casesens, matchwrd) ||
82                         !replaceall)
83                 {
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                 {
98                         bv->hideCursor();
99                         bv->update(text, BufferView::SELECT|BufferView::FITCUR);
100                         bv->toggleSelection(false);
101                         text->replaceSelectionWithString(bv, replacestr);
102                         text->setSelectionRange(bv, replacestr.length());
103                         bv->update(text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
104                         ++replace_count;
105                 }
106                 if (!once)
107                         found = LyXFind(bv, searchstr, fw, casesens, matchwrd);
108         } while (!once && replaceall && found);
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|BufferView::FITCUR);
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(bv, 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|BufferView::FITCUR);
161                 text->setSelectionRange(bv, searchstr.length());
162                 bv->toggleSelection(false);
163                 bv->update(text, BufferView::SELECT|BufferView::FITCUR);
164         } else if (result == SR_NOT_FOUND) {
165                 bv->unlockInset(bv->theLockingInset());
166                 bv->update(text, BufferView::SELECT|BufferView::FITCUR);
167                 found = false;
168         }
169
170         return found;
171 }
172
173
174 SearchResult LyXFind(BufferView * bv, LyXText * text,
175                      string const & searchstr, bool forward,
176                      bool casesens, bool matchwrd)
177 {
178         if (text->selection.set())
179                 text->cursor = forward ?
180                         text->selection.end : text->selection.start;
181
182         bv->toggleSelection();
183         text->clearSelection();
184
185         SearchResult result = forward ?
186                 SearchForward(bv, text, searchstr, casesens, matchwrd) :
187                 SearchBackward(bv, text, searchstr, casesens, matchwrd);
188
189         return result;
190 }
191
192
193 // returns true if the specified string is at the specified position
194 bool IsStringInText(Paragraph * par, pos_type pos,
195                     string const & str, bool const & cs,
196                     bool const & mw)
197 {
198         if (!par)
199                 return false;
200
201         string::size_type size = str.length();
202         pos_type i = 0;
203         while (((pos + i) < par->size())
204                && (string::size_type(i) < size)
205                && (cs ? (str[i] == par->getChar(pos + i))
206                    : (uppercase(str[i]) == uppercase(par->getChar(pos + i)))))
207         {
208                 ++i;
209         }
210         if (size == string::size_type(i)) {
211                 // if necessary, check whether string matches word
212                 if (!mw)
213                         return true;
214                 if ((pos <= 0 || !IsLetterCharOrDigit(par->getChar(pos - 1)))
215                         && (pos + pos_type(size) >= par->size()
216                         || !IsLetterCharOrDigit(par->getChar(pos + size)))) {
217                         return true;
218                 }
219         }
220         return false;
221 }
222
223 // forward search:
224 // if the string can be found: return true and set the cursor to
225 // the new position, cs = casesensitive, mw = matchword
226 SearchResult SearchForward(BufferView * bv, LyXText * text, string const & str,
227                            bool const & cs, bool const & mw)
228 {
229         Paragraph * par = text->cursor.par();
230         pos_type pos = text->cursor.pos();
231         Paragraph * prev_par = par;
232         UpdatableInset * inset;
233
234         while (par && !IsStringInText(par, pos, str, cs, mw)) {
235                 if (par->isInset(pos) &&
236                         (inset = (UpdatableInset *)par->getInset(pos)) &&
237                         (inset->isTextInset()))
238                 {
239 #if 0
240                         // lock the inset!
241                         text->setCursor(bv, par, pos);
242                         inset->edit(bv);
243 #endif
244                         if (inset->searchForward(bv, str, cs, mw))
245                                 return SR_FOUND_NOUPDATE;
246                 }
247
248                 ++pos;
249
250                 if (pos >= par->size()) {
251                         prev_par = par;
252                         par = par->next();
253                         pos = 0;
254                 }
255         }
256
257         if (par) {
258                 text->setCursor(bv, par, pos);
259                 return SR_FOUND;
260         } else {
261                 // make sure we end up at the end of the text,
262                 // not the start point of the last search
263                 text->setCursor(bv, prev_par, prev_par->size());
264                 return SR_NOT_FOUND;
265         }
266 }
267
268
269 // backward search:
270 // if the string can be found: return true and set the cursor to
271 // the new position, cs = casesensitive, mw = matchword
272 SearchResult SearchBackward(BufferView * bv, LyXText * text,
273                             string const & str,
274                             bool const & cs, bool const & mw)
275 {
276         Paragraph * par = text->cursor.par();
277         pos_type pos = text->cursor.pos();
278         Paragraph * prev_par = par;
279
280         do {
281                 if (pos > 0)
282                         --pos;
283                 else {
284                         prev_par = par;
285                         // We skip empty paragraphs (Asger)
286                         do {
287                                 par = par->previous();
288                                 if (par)
289                                         pos = par->size() - 1;
290                         } while (par && pos < 0);
291                 }
292                 UpdatableInset * inset;
293                 if (par && par->isInset(pos) &&
294                         (inset = (UpdatableInset *)par->getInset(pos)) &&
295                         (inset->isTextInset()))
296                 {
297 #if 0
298                         // lock the inset!
299                         text->setCursor(bv, par, pos);
300                         inset->edit(bv, false);
301 #endif
302                         if (inset->searchBackward(bv, str, cs, mw))
303                                 return SR_FOUND_NOUPDATE;
304                 }
305         } while (par && !IsStringInText(par, pos, str, cs, mw));
306
307         if (par) {
308                 text->setCursor(bv, par, pos);
309                 return SR_FOUND;
310         } else {
311                 // go to the last part of the unsuccessful search
312                 text->setCursor(bv, prev_par, 0);
313                 return SR_NOT_FOUND;
314         }
315 }
316
317  
318 SearchResult nextChange(BufferView * bv, LyXText * text, pos_type & length)
319 {
320         Paragraph * par = text->cursor.par();
321         pos_type pos = text->cursor.pos();
322         Paragraph * prev_par = par;
323         UpdatableInset * inset;
324
325         while (par) {
326                 if ((!par->size() || pos != par->size())
327                         && par->lookupChange(pos) != Change::UNCHANGED)
328                         break;
329
330                 if (par->isInset(pos) &&
331                         (inset = (UpdatableInset *)par->getInset(pos)) &&
332                         (inset->isTextInset())) {
333                         if (inset->nextChange(bv, length))
334                                 return SR_FOUND_NOUPDATE;
335                 }
336
337                 ++pos;
338
339                 if (pos >= par->size()) {
340                         prev_par = par;
341                         par = par->next();
342                         pos = 0;
343                 }
344         }
345
346         if (par) {
347                 text->setCursor(bv, par, pos);
348                 Change orig_change = par->lookupChangeFull(pos);
349                 pos_type end = pos;
350
351                 for (; end != par->size(); ++end) {
352                         Change change = par->lookupChangeFull(end);
353                         if (change != orig_change) {
354                                 // slight UI optimisation: for replacements, we get
355                                 // text like : _old_new. Consider that as one change.
356                                 if (!(orig_change.type == Change::DELETED &&
357                                         change.type == Change::INSERTED))
358                                         break;
359                         }
360                 }
361                 length = end - pos;
362                 return SR_FOUND;
363         } else {
364                 // make sure we end up at the end of the text,
365                 // not the start point of the last search
366                 text->setCursor(bv, prev_par, prev_par->size());
367                 return SR_NOT_FOUND;
368         }
369 }
370  
371  
372 SearchResult findNextChange(BufferView * bv, LyXText * text, pos_type & length)
373 {
374         if (text->selection.set())
375                 text->cursor = text->selection.end;
376
377         bv->toggleSelection();
378         text->clearSelection();
379
380         return nextChange(bv, text, length);
381 }
382
383
384 bool findNextChange(BufferView * bv)
385 {
386         if (!bv->available())
387                 return false;
388
389         bv->hideCursor();
390         bv->update(bv->getLyXText(), BufferView::SELECT | BufferView::FITCUR);
391
392         pos_type length;
393  
394         if (bv->theLockingInset()) {
395                 bool found = bv->theLockingInset()->nextChange(bv, length);
396  
397                 // We found the stuff inside the inset so we don't have to
398                 // do anything as the inset did all the update for us!
399                 if (found)
400                         return true;
401  
402                 // We now are in the main text but if we did a forward
403                 // search we have to put the cursor behind the inset.
404                 bv->text->cursorRight(bv, true);
405         }
406         // If we arrive here we are in the main text again so we
407         // just start searching from the root LyXText at the position
408         // we are!
409         LyXText * text = bv->text;
410
411         if (text->selection.set())
412                 text->cursor = text->selection.end;
413
414         bv->toggleSelection();
415         text->clearSelection();
416         
417         SearchResult result = nextChange(bv, text, length);
418
419         lyxerr << "Result is " << result << endl;
420  
421         bool found = true;
422  
423         // If we found the cursor inside an inset we will get back
424         // SR_FOUND_NOUPDATE and we don't have to do anything as the
425         // inset did it already.
426         if (result == SR_FOUND) {
427                 bv->unlockInset(bv->theLockingInset());
428                 bv->update(text, BufferView::SELECT|BufferView::FITCUR);
429                 text->setSelectionRange(bv, length);
430                 bv->toggleSelection(false);
431                 bv->update(text, BufferView::SELECT|BufferView::FITCUR);
432         } else if (result == SR_NOT_FOUND) {
433                 bv->unlockInset(bv->theLockingInset());
434                 bv->update(text, BufferView::SELECT|BufferView::FITCUR);
435                 found = false;
436         }
437
438         return found;
439 }
440  
441 } // end lyxfind namespace