]> git.lyx.org Git - lyx.git/blob - src/lyxfind.C
de0857924fa21af12d56f6278f5f977aa056c233
[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 "frontends/LyXView.h"
10 #include "frontends/Alert.h"
11 #include "support/textutils.h"
12 #include "support/lstrings.h"
13 #include "BufferView.h"
14 #include "buffer.h"
15 #include "debug.h"
16 #include "gettext.h"
17 #include "insets/insettext.h"
18
19 using lyx::pos_type;
20
21 namespace lyxfind {
22
23 /// returns true if the specified string is at the specified  position
24 bool IsStringInText(Paragraph * par, pos_type pos,
25                     string const & str, bool const & = true,
26                     bool const & = false);
27
28 /// if the string is found: return true and set the cursor to the new position
29 SearchResult SearchForward(BufferView *, LyXText * text, string const & str,
30                            bool const & = true, bool const & = false);
31 ///
32 SearchResult SearchBackward(BufferView *, LyXText * text, string const & str,
33                             bool const & = true, bool const & = false);
34
35 int LyXReplace(BufferView * bv,
36                string const & searchstr, string const & replacestr,
37                bool forward, bool casesens, bool matchwrd, bool replaceall,
38                bool once)
39 {
40         if (!bv->available() || bv->buffer()->isReadonly())
41                 return 0;
42
43         // CutSelection cannot cut a single space, so we have to stop
44         // in order to avoid endless loop :-(
45         if (searchstr.length() == 0
46                 || (searchstr.length() == 1 && searchstr[0] == ' '))
47         {
48                 Alert::alert(_("Sorry!"), _("You cannot replace a single space, "
49                                           "nor an empty character."));
50                 return 0;
51         }
52
53         // now we can start searching for the first
54         // start at top if replaceall
55         LyXText * text = bv->getLyXText();
56         bool fw = forward;
57         if (replaceall) {
58                 text->clearSelection();
59                 bv->unlockInset(bv->theLockingInset());
60                 text = bv->text;
61                 text->cursorTop(bv);
62                 // override search direction because we search top to bottom
63                 fw = true;
64         }
65
66         // if nothing selected or selection does not equal search string
67         // search and select next occurance and return if no replaceall
68         string str1;
69         string str2;
70         if (casesens) {
71                 str1 = searchstr;
72                 str2 = text->selectionAsString(bv->buffer(), false);
73         } else {
74                 str1 = lowercase(searchstr);
75                 str2 = lowercase(text->selectionAsString(bv->buffer(), false));
76         }
77         if (str1 != str2) {
78                 if (!LyXFind(bv, searchstr, fw, casesens, matchwrd) ||
79                         !replaceall)
80                 {
81                         return 0;
82                 }
83         }
84
85         bool found = false;
86         int replace_count = 0;
87         do {
88                 text = bv->getLyXText();
89                 // We have to do this check only because mathed insets don't
90                 // return their own LyXText but the LyXText of it's parent!
91                 if (!bv->theLockingInset() ||
92                         ((text != bv->text) &&
93                          (text->inset_owner == text->inset_owner->getLockingInset())))
94                 {
95                         bv->hideCursor();
96                         bv->update(text, BufferView::SELECT|BufferView::FITCUR);
97                         bv->toggleSelection(false);
98                         text->replaceSelectionWithString(bv, replacestr);
99                         text->setSelectionOverString(bv, replacestr);
100                         bv->update(text, BufferView::SELECT|BufferView::FITCUR|BufferView::CHANGE);
101                         ++replace_count;
102                 }
103                 if (!once)
104                         found = LyXFind(bv, searchstr, fw, casesens, matchwrd);
105         } while (!once && replaceall && found);
106
107         return replace_count;
108 }
109
110
111 bool LyXFind(BufferView * bv,
112              string const & searchstr, bool forward,
113              bool casesens, bool matchwrd)
114 {
115         if (!bv->available() || searchstr.empty())
116                 return false;
117
118         bv->hideCursor();
119         bv->update(bv->getLyXText(), BufferView::SELECT|BufferView::FITCUR);
120
121         if (bv->theLockingInset()) {
122                 bool found = forward ?
123                         bv->theLockingInset()->searchForward(bv, searchstr, casesens, matchwrd) :
124                         bv->theLockingInset()->searchBackward(bv, searchstr, casesens, matchwrd);
125                 // We found the stuff inside the inset so we don't have to
126                 // do anything as the inset did all the update for us!
127                 if (found)
128                         return true;
129                 // We now are in the main text but if we did a forward
130                 // search we have to put the cursor behind the inset.
131                 if (forward) {
132                         bv->text->cursorRight(bv, true);
133                 }
134         }
135         // If we arrive here we are in the main text again so we
136         // just start searching from the root LyXText at the position
137         // we are!
138         LyXText * text = bv->text;
139
140         if (text->selection.set())
141                 text->cursor = forward ?
142                         text->selection.end : text->selection.start;
143
144         bv->toggleSelection();
145         text->clearSelection();
146
147         SearchResult result = forward ?
148                 SearchForward(bv, text, searchstr, casesens, matchwrd) :
149                 SearchBackward(bv, text, searchstr, casesens, matchwrd);
150
151         bool found = true;
152         // If we found the cursor inside an inset we will get back
153         // SR_FOUND_NOUPDATE and we don't have to do anything as the
154         // inset did it already.
155         if (result == SR_FOUND) {
156                 bv->unlockInset(bv->theLockingInset());
157                 bv->update(text, BufferView::SELECT|BufferView::FITCUR);
158                 text->setSelectionOverString(bv, searchstr);
159                 bv->toggleSelection(false);
160                 bv->update(text, BufferView::SELECT|BufferView::FITCUR);
161         } else if (result == SR_NOT_FOUND) {
162                 bv->unlockInset(bv->theLockingInset());
163                 bv->update(text, BufferView::SELECT|BufferView::FITCUR);
164                 found = false;
165         }
166
167         return found;
168 }
169
170
171 SearchResult LyXFind(BufferView * bv, LyXText * text,
172                      string const & searchstr, bool forward,
173                      bool casesens, bool matchwrd)
174 {
175         if (text->selection.set())
176                 text->cursor = forward ?
177                         text->selection.end : text->selection.start;
178
179         bv->toggleSelection();
180         text->clearSelection();
181
182         SearchResult result = forward ?
183                 SearchForward(bv, text, searchstr, casesens, matchwrd) :
184                 SearchBackward(bv, text, searchstr, casesens, matchwrd);
185
186         return result;
187 }
188
189
190 // returns true if the specified string is at the specified position
191 bool IsStringInText(Paragraph * par, pos_type pos,
192                     string const & str, bool const & cs,
193                     bool const & mw)
194 {
195         if (!par)
196                 return false;
197
198         string::size_type size = str.length();
199         pos_type i = 0;
200         while (((pos + i) < par->size())
201                && (string::size_type(i) < size)
202                && (cs ? (str[i] == par->getChar(pos + i))
203                    : (uppercase(str[i]) == uppercase(par->getChar(pos + i)))))
204         {
205                 ++i;
206         }
207         if (size == string::size_type(i)) {
208                 // if necessary, check whether string matches word
209                 if (!mw)
210                         return true;
211                 if ((pos <= 0 || !IsLetterCharOrDigit(par->getChar(pos - 1)))
212                         && (pos + pos_type(size) >= par->size()
213                         || !IsLetterCharOrDigit(par->getChar(pos + size)))) {
214                         return true;
215                 }
216         }
217         return false;
218 }
219
220 // forward search:
221 // if the string can be found: return true and set the cursor to
222 // the new position, cs = casesensitive, mw = matchword
223 SearchResult SearchForward(BufferView * bv, LyXText * text, string const & str,
224                            bool const & cs, bool const & mw)
225 {
226         Paragraph * par = text->cursor.par();
227         pos_type pos = text->cursor.pos();
228         Paragraph * prev_par = par;
229         UpdatableInset * inset;
230
231         while (par && !IsStringInText(par, pos, str, cs, mw)) {
232                 if (par->isInset(pos) &&
233                         (inset = (UpdatableInset *)par->getInset(pos)) &&
234                         (inset->isTextInset()))
235                 {
236 #if 0
237                         // lock the inset!
238                         text->setCursor(bv, par, pos);
239                         inset->edit(bv);
240 #endif
241                         if (inset->searchForward(bv, str, cs, mw))
242                                 return SR_FOUND_NOUPDATE;
243                 }
244
245                 ++pos;
246
247                 if (pos >= par->size()) {
248                         prev_par = par;
249                         par = par->next();
250                         pos = 0;
251                 }
252         }
253
254         if (par) {
255                 text->setCursor(bv, par, pos);
256                 return SR_FOUND;
257         } else {
258                 // make sure we end up at the end of the text,
259                 // not the start point of the last search
260                 text->setCursor(bv, prev_par, prev_par->size());
261                 return SR_NOT_FOUND;
262         }
263 }
264
265
266 // backward search:
267 // if the string can be found: return true and set the cursor to
268 // the new position, cs = casesensitive, mw = matchword
269 SearchResult SearchBackward(BufferView * bv, LyXText * text,
270                             string const & str,
271                             bool const & cs, bool const & mw)
272 {
273         Paragraph * par = text->cursor.par();
274         pos_type pos = text->cursor.pos();
275         Paragraph * prev_par = par;
276
277         do {
278                 if (pos > 0)
279                         --pos;
280                 else {
281                         prev_par = par;
282                         // We skip empty paragraphs (Asger)
283                         do {
284                                 par = par->previous();
285                                 if (par)
286                                         pos = par->size() - 1;
287                         } while (par && pos < 0);
288                 }
289                 UpdatableInset * inset;
290                 if (par && par->isInset(pos) &&
291                         (inset = (UpdatableInset *)par->getInset(pos)) &&
292                         (inset->isTextInset()))
293                 {
294 #if 0
295                         // lock the inset!
296                         text->setCursor(bv, par, pos);
297                         inset->edit(bv, false);
298 #endif
299                         if (inset->searchBackward(bv, str, cs, mw))
300                                 return SR_FOUND_NOUPDATE;
301                 }
302         } while (par && !IsStringInText(par, pos, str, cs, mw));
303
304         if (par) {
305                 text->setCursor(bv, par, pos);
306                 return SR_FOUND;
307         } else {
308                 // go to the last part of the unsuccessful search
309                 text->setCursor(bv, prev_par, 0);
310                 return SR_NOT_FOUND;
311         }
312 }
313
314 } // end lyxfind namespace