]> git.lyx.org Git - lyx.git/blob - src/lyxfind.C
db23d30db9470637aad0e7e479e054803cd10fe6
[lyx.git] / src / lyxfind.C
1 /**
2  * \file lyxfind.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author John Levon
8  * \author Jürgen Vigna
9  * \author Alfredo Braunstein
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "lyxfind.h"
17
18 #include "buffer.h"
19 #include "BufferView.h"
20 #include "debug.h"
21 #include "iterators.h"
22 #include "gettext.h"
23 #include "lyxtext.h"
24 #include "paragraph.h"
25 #include "PosIterator.h"
26 #include "undo.h"
27
28 #include "frontends/Alert.h"
29
30 #include "insets/insettext.h"
31
32 #include "support/textutils.h"
33
34 using lyx::support::lowercase;
35 using lyx::support::uppercase;
36 using bv_funcs::put_selection_at;
37
38 using std::string;
39
40 namespace lyx {
41 namespace find {
42
43
44 namespace {
45
46 class MatchString
47 {
48 public:
49         MatchString(string const & str, bool cs, bool mw)
50                 : str(str), cs(cs), mw(mw) {};
51 // returns true if the specified string is at the specified position
52         bool operator()(Paragraph const & par, pos_type pos) const
53         {                       
54                 string::size_type size = str.length();
55                 pos_type i = 0;
56                 pos_type parsize = par.size();
57                 while ((pos + i < parsize)
58                        && (string::size_type(i) < size)
59                        && (cs ? (str[i] == par.getChar(pos + i))
60                            : (uppercase(str[i]) == uppercase(par.getChar(pos + i))))) {
61                         ++i;
62                 }
63                 if (size == string::size_type(i)) {
64                         // if necessary, check whether string matches word
65                         if (!mw)
66                                 return true;
67                         if ((pos <= 0 || !IsLetterCharOrDigit(par.getChar(pos - 1)))
68                             && (pos + pos_type(size) >= parsize
69                                 || !IsLetterCharOrDigit(par.getChar(pos + size)))) {
70                                 return true;
71                         }
72                 }
73                 return false;
74         }
75         
76 private:
77         string str;
78         bool cs;
79         bool mw;
80 };
81
82
83
84 } //namespace anon
85
86
87
88
89
90 namespace {
91
92
93
94
95 bool findForward(PosIterator & cur, PosIterator const & end,
96                  MatchString & match)
97 {
98         for (; cur != end && !match(*cur.pit(), cur.pos()); ++cur)
99                 ;
100
101         return cur != end;
102 }
103
104
105 bool findBackwards(PosIterator & cur, PosIterator const & beg,
106                    MatchString & match)
107 {
108         if (beg == cur)
109                 return false;
110         do {
111                 --cur;
112                 if (match(*cur.pit(), cur.pos()))
113                         break;
114         } while (cur != beg);
115
116         return match(*cur.pit(), cur.pos());
117 }
118
119
120 bool findChange(PosIterator & cur, PosIterator const & end)
121 {
122         for (; cur != end; ++cur) {
123                 if ((!cur.pit()->size() || !cur.at_end())
124                     && cur.pit()->lookupChange(cur.pos()) != Change::UNCHANGED)
125                         break;
126         }
127         
128         return cur != end;
129 }
130
131
132 bool searchAllowed(BufferView * bv, string const & str)
133 {
134         if (str.empty()) {
135                 Alert::error(_("Search error"), _("Search string is empty"));
136                 return false;
137         }
138         if (!bv->available())
139                 return false;
140         return true;
141             
142 }
143
144 } // namespace anon
145
146
147 bool find(BufferView * bv, string const & searchstr,
148           bool cs, bool mw, bool fw)
149 {
150         if (!searchAllowed(bv, searchstr))
151                 return false;
152
153         PosIterator cur = PosIterator(*bv);
154
155         MatchString match(searchstr, cs, mw);
156         
157         bool found;
158
159         if (fw) {
160                 PosIterator const end = bv->buffer()->pos_iterator_end();
161                 found = findForward(cur, end, match);
162         } else {
163                 PosIterator const beg = bv->buffer()->pos_iterator_begin();
164                 found = findBackwards(cur, beg, match);
165         }
166         
167         if (found)
168                 put_selection_at(bv, cur, searchstr.length(), !fw);
169
170         return found;
171 }
172
173 namespace {
174         
175
176
177  
178 } //namespace anon
179
180
181 int replaceAll(BufferView * bv,
182                string const & searchstr, string const & replacestr,
183                bool cs, bool mw)
184 {
185         Buffer & buf = *bv->buffer();
186
187         if (!searchAllowed(bv, searchstr) || buf.isReadonly())
188                 return 0;
189         
190         recordUndo(Undo::ATOMIC, bv->text, 0,
191                    buf.paragraphs().size() - 1);
192         
193         PosIterator cur = buf.pos_iterator_begin();
194         PosIterator const end = buf.pos_iterator_end();
195         MatchString match(searchstr, cs, mw);
196         int num = 0;
197
198         int const rsize = replacestr.size();
199         int const ssize = searchstr.size();
200         while (findForward(cur, end, match)) {
201                 pos_type pos = cur.pos();
202                 LyXFont const font
203                         = cur.pit()->getFontSettings(buf.params(), pos);
204                 int striked = ssize - cur.pit()->erase(pos, pos + ssize);
205                 cur.pit()->insert(pos, replacestr, font);
206                 advance(cur, rsize + striked);
207                 ++num;
208         }
209         PosIterator beg = buf.pos_iterator_begin();
210         bv->text->init(bv);
211         put_selection_at(bv, beg, 0, false);
212         if (num)
213                 buf.markDirty();
214         return num;
215 }
216
217
218 int replace(BufferView * bv,
219             string const & searchstr, string const & replacestr,
220             bool cs, bool mw, bool fw)
221 {
222         if (!searchAllowed(bv, searchstr) || bv->buffer()->isReadonly())
223                 return 0;
224         
225         {
226                 LyXText * text = bv->getLyXText();
227                 // if nothing selected or selection does not equal search
228                 // string search and select next occurance and return
229                 string const str1 = searchstr;
230                 string const str2 = text->selectionAsString(*bv->buffer(),
231                                                             false);
232                 if ((cs && str1 != str2)
233                     || lowercase(str1) != lowercase(str2)) {
234                         find(bv, searchstr, cs, mw, fw);
235                         return 0;
236                 }
237         }
238
239         LyXText * text = bv->getLyXText();
240         // We have to do this check only because mathed insets don't
241         // return their own LyXText but the LyXText of it's parent!
242         if (!bv->theLockingInset() ||
243             ((text != bv->text) &&
244              (text->inset_owner == text->inset_owner->getLockingInset()))) {
245                 text->replaceSelectionWithString(replacestr);
246                 text->setSelectionRange(replacestr.length());
247                 text->cursor = fw ? text->selection.end
248                         : text->selection.start;
249         }
250
251         // FIXME: should be called via an LFUN
252         bv->buffer()->markDirty();
253
254         find(bv, searchstr, cs, mw, fw);
255         bv->update();
256         
257         return 1;
258 }
259
260
261 bool findNextChange(BufferView * bv)
262 {
263         if (!bv->available())
264                 return false;
265
266         PosIterator cur = PosIterator(*bv);
267         PosIterator const endit = bv->buffer()->pos_iterator_end();
268
269         if (!findChange(cur, endit))
270                 return false;
271         
272         
273         ParagraphList::iterator pit = cur.pit();
274         pos_type pos = cur.pos();
275         
276         Change orig_change = pit->lookupChangeFull(pos);
277         pos_type parsize = pit->size();
278         pos_type end = pos;
279
280         for (; end != parsize; ++end) {
281                 Change change = pit->lookupChangeFull(end);
282                 if (change != orig_change) {
283                         // slight UI optimisation: for replacements, we get
284                         // text like : _old_new. Consider that as one change.
285                         if (!(orig_change.type == Change::DELETED &&
286                                 change.type == Change::INSERTED))
287                                 break;
288                 }
289         }
290         pos_type length = end - pos;
291         bv->text->init(bv);
292         put_selection_at(bv, cur, length, true);
293         return true;
294 }
295
296 } // find namespace
297 } // lyx namespace