]> git.lyx.org Git - lyx.git/blob - src/lyxfind.C
9f474c5a36456a5b8c0f58c569ce1f4b0618ebf9
[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
41 namespace lyx {
42 namespace find {
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 bool findForward(PosIterator & cur, PosIterator const & end,
84                  MatchString & match)
85 {
86         for (; cur != end && !match(*cur.pit(), cur.pos()); ++cur)
87                 ;
88
89         return cur != end;
90 }
91
92
93 bool findBackwards(PosIterator & cur, PosIterator const & beg,
94                    MatchString & match)
95 {
96         if (beg == cur)
97                 return false;
98         do {
99                 --cur;
100                 if (match(*cur.pit(), cur.pos()))
101                         break;
102         } while (cur != beg);
103
104         return match(*cur.pit(), cur.pos());
105 }
106
107
108 bool findChange(PosIterator & cur, PosIterator const & end)
109 {
110         for (; cur != end; ++cur) {
111                 if ((!cur.pit()->size() || !cur.at_end())
112                     && cur.pit()->lookupChange(cur.pos()) != Change::UNCHANGED)
113                         break;
114         }
115         
116         return cur != end;
117 }
118
119
120 bool searchAllowed(BufferView * bv, string const & str)
121 {
122         if (str.empty()) {
123                 Alert::error(_("Search error"), _("Search string is empty"));
124                 return false;
125         }
126         return bv->available();
127 }
128
129 } // namespace anon
130
131
132
133 bool find(BufferView * bv, string const & searchstr,
134           bool cs, bool mw, bool fw)
135 {
136         if (!searchAllowed(bv, searchstr))
137                 return false;
138
139         PosIterator cur = PosIterator(*bv);
140
141         MatchString match(searchstr, cs, mw);
142         
143         bool found;
144
145         if (fw) {
146                 PosIterator const end = bv->buffer()->pos_iterator_end();
147                 found = findForward(cur, end, match);
148         } else {
149                 PosIterator const beg = bv->buffer()->pos_iterator_begin();
150                 found = findBackwards(cur, beg, match);
151         }
152         
153         if (found)
154                 put_selection_at(bv, cur, searchstr.length(), !fw);
155
156         return found;
157 }
158
159
160 int replaceAll(BufferView * bv,
161                string const & searchstr, string const & replacestr,
162                bool cs, bool mw)
163 {
164         Buffer & buf = *bv->buffer();
165
166         if (!searchAllowed(bv, searchstr) || buf.isReadonly())
167                 return 0;
168         
169         recordUndo(Undo::ATOMIC, bv->text, 0,
170                    buf.paragraphs().size() - 1);
171         
172         PosIterator cur = buf.pos_iterator_begin();
173         PosIterator const end = buf.pos_iterator_end();
174         MatchString match(searchstr, cs, mw);
175         int num = 0;
176
177         int const rsize = replacestr.size();
178         int const ssize = searchstr.size();
179         while (findForward(cur, end, match)) {
180                 pos_type pos = cur.pos();
181                 LyXFont const font
182                         = cur.pit()->getFontSettings(buf.params(), pos);
183                 int striked = ssize - cur.pit()->erase(pos, pos + ssize);
184                 cur.pit()->insert(pos, replacestr, font);
185                 advance(cur, rsize + striked);
186                 ++num;
187         }
188         PosIterator beg = buf.pos_iterator_begin();
189         bv->text->init(bv);
190         put_selection_at(bv, beg, 0, false);
191         if (num)
192                 buf.markDirty();
193         return num;
194 }
195
196
197 int replace(BufferView * bv,
198             string const & searchstr, string const & replacestr,
199             bool cs, bool mw, bool fw)
200 {
201         if (!searchAllowed(bv, searchstr) || bv->buffer()->isReadonly())
202                 return 0;
203         
204         {
205                 LyXText * text = bv->getLyXText();
206                 // if nothing selected or selection does not equal search
207                 // string search and select next occurance and return
208                 string const str1 = searchstr;
209                 string const str2 = text->selectionAsString(*bv->buffer(),
210                                                             false);
211                 if ((cs && str1 != str2)
212                     || lowercase(str1) != lowercase(str2)) {
213                         find(bv, searchstr, cs, mw, fw);
214                         return 0;
215                 }
216         }
217
218 #ifdef LOCK
219         LyXText * text = bv->getLyXText();
220         // We have to do this check only because mathed insets don't
221         // return their own LyXText but the LyXText of it's parent!
222         if (!bv->innerInset() ||
223             ((text != bv->text) &&
224              (text->inset_owner == text->inset_owner->getLockingInset()))) {
225                 text->replaceSelectionWithString(replacestr);
226                 text->setSelectionRange(replacestr.length());
227                 text->cursor = fw ? text->selection.end
228                         : text->selection.start;
229         }
230 #endif
231
232         bv->buffer()->markDirty();
233         find(bv, searchstr, cs, mw, fw);
234         bv->update();
235         
236         return 1;
237 }
238
239
240 bool findNextChange(BufferView * bv)
241 {
242         if (!bv->available())
243                 return false;
244
245         PosIterator cur = PosIterator(*bv);
246         PosIterator const endit = bv->buffer()->pos_iterator_end();
247
248         if (!findChange(cur, endit))
249                 return false;
250         
251         
252         ParagraphList::iterator pit = cur.pit();
253         pos_type pos = cur.pos();
254         
255         Change orig_change = pit->lookupChangeFull(pos);
256         pos_type parsize = pit->size();
257         pos_type end = pos;
258
259         for (; end != parsize; ++end) {
260                 Change change = pit->lookupChangeFull(end);
261                 if (change != orig_change) {
262                         // slight UI optimisation: for replacements, we get
263                         // text like : _old_new. Consider that as one change.
264                         if (!(orig_change.type == Change::DELETED &&
265                                 change.type == Change::INSERTED))
266                                 break;
267                 }
268         }
269         pos_type length = end - pos;
270         bv->text->init(bv);
271         put_selection_at(bv, cur, length, true);
272         return true;
273 }
274
275 } // find namespace
276 } // lyx namespace