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