]> git.lyx.org Git - lyx.git/blob - src/lyxfind.C
somewhat clearer logic
[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 "support/textutils.h"
31
32 using lyx::support::lowercase;
33 using lyx::support::uppercase;
34 using bv_funcs::put_selection_at;
35
36 using std::string;
37
38
39 namespace lyx {
40 namespace find {
41
42 namespace {
43
44 class MatchString
45 {
46 public:
47         MatchString(string const & str, bool cs, bool mw)
48                 : str(str), cs(cs), mw(mw)
49         {}
50
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 const 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
64                 if (size != string::size_type(i))
65                         return false;
66
67                 // if necessary, check whether string matches word
68                 if (mw) {
69                         if (pos > 0     && IsLetterCharOrDigit(par.getChar(pos - 1)))
70                                 return false;
71                         if (pos + pos_type(size) < parsize
72                                         && IsLetterCharOrDigit(par.getChar(pos + size)));
73                                 return false;
74                 }
75
76                 return true;
77         }
78         
79 private:
80         // search string
81         string str;
82         // case sensitive
83         bool cs;
84         // match whole words only
85         bool mw;
86 };
87
88
89 bool findForward(PosIterator & cur, PosIterator const & end,
90                  MatchString const & match)
91 {
92         for (; cur != end; ++cur) {
93                 if (match(*cur.pit(), cur.pos()))
94                         return true;
95         }
96         return false;
97 }
98
99
100 bool findBackwards(PosIterator & cur, PosIterator const & beg,
101                    MatchString const & match)
102 {
103         while (beg != cur) {
104                 --cur;
105                 if (match(*cur.pit(), cur.pos()))
106                         return true;
107         }
108         return false;
109 }
110
111
112 bool findChange(PosIterator & cur, PosIterator const & end)
113 {
114         for (; cur != end; ++cur) {
115                 if ((!cur.pit()->size() || !cur.at_end())
116                     && cur.pit()->lookupChange(cur.pos()) != Change::UNCHANGED)
117                         return true;
118         }
119         return false;
120 }
121
122
123 bool searchAllowed(BufferView * bv, string const & str)
124 {
125         if (str.empty()) {
126                 Alert::error(_("Search error"), _("Search string is empty"));
127                 return false;
128         }
129         return bv->available();
130 }
131
132 } // namespace anon
133
134
135
136 bool find(BufferView * bv, string const & searchstr, bool cs, bool mw, bool fw)
137 {
138         if (!searchAllowed(bv, searchstr))
139                 return false;
140
141         PosIterator cur = PosIterator(*bv);
142
143         MatchString const match(searchstr, cs, mw);
144
145         PosIterator const end = bv->buffer()->pos_iterator_end();
146         PosIterator const beg = bv->buffer()->pos_iterator_begin();
147
148         bool found = fw ? findForward(cur, end, match)
149                 : findBackwards(cur, beg, match);
150
151         if (found)
152                 put_selection_at(bv, cur, searchstr.length(), !fw);
153
154         return found;
155 }
156
157
158 int replaceAll(BufferView * bv,
159                string const & searchstr, string const & replacestr,
160                bool cs, bool mw)
161 {
162         Buffer & buf = *bv->buffer();
163
164         if (!searchAllowed(bv, searchstr) || buf.isReadonly())
165                 return 0;
166         
167         recordUndo(Undo::ATOMIC, bv->text(), 0, buf.paragraphs().size() - 1);
168         
169         PosIterator cur = buf.pos_iterator_begin();
170         PosIterator const end = buf.pos_iterator_end();
171         MatchString const match(searchstr, cs, mw);
172         int num = 0;
173
174         int const rsize = replacestr.size();
175         int const ssize = searchstr.size();
176
177         while (findForward(cur, end, match)) {
178                 pos_type pos = cur.pos();
179                 LyXFont const font
180                         = cur.pit()->getFontSettings(buf.params(), pos);
181                 int striked = ssize - cur.pit()->erase(pos, pos + ssize);
182                 cur.pit()->insert(pos, replacestr, font);
183                 advance(cur, rsize + striked);
184                 ++num;
185         }
186
187         PosIterator beg = buf.pos_iterator_begin();
188         bv->text()->init(bv);
189         put_selection_at(bv, beg, 0, false);
190         if (num)
191                 buf.markDirty();
192         return num;
193 }
194
195
196 namespace {
197
198 bool stringSelected(BufferView * bv,
199                     string const & searchstr, 
200                     bool cs, bool mw, bool fw)
201 {
202         LyXText * text = bv->getLyXText();
203         // if nothing selected or selection does not equal search
204         // string search and select next occurance and return
205         string const & str1 = searchstr;
206         string const str2 = text->selectionAsString(*bv->buffer(),
207                                                     false);
208         if ((cs && str1 != str2) || lowercase(str1) != lowercase(str2)) {
209                 find(bv, searchstr, cs, mw, fw);
210                 return false;
211         }
212
213         return true;
214 }
215
216 } //namespace anon
217
218
219 int replace(BufferView * bv,
220             string const & searchstr, string const & replacestr,
221             bool cs, bool mw, bool fw)
222 {
223         if (!searchAllowed(bv, searchstr) || bv->buffer()->isReadonly())
224                 return 0;
225
226         if (!stringSelected(bv, searchstr, cs, mw, fw))
227                 return 0;
228
229         LyXText * text = bv->getLyXText();
230
231         text->replaceSelectionWithString(replacestr);
232         text->setSelectionRange(replacestr.length());
233         text->cursor = fw ? text->selEnd() : text->selStart();
234
235         bv->buffer()->markDirty();
236         find(bv, searchstr, cs, mw, fw);
237         bv->update();
238         
239         return 1;
240 }
241
242
243 bool findNextChange(BufferView * bv)
244 {
245         if (!bv->available())
246                 return false;
247
248         PosIterator cur = PosIterator(*bv);
249         PosIterator const endit = bv->buffer()->pos_iterator_end();
250
251         if (!findChange(cur, endit))
252                 return false;
253         
254         ParagraphList::iterator pit = cur.pit();
255         pos_type pos = cur.pos();
256         
257         Change orig_change = pit->lookupChangeFull(pos);
258         pos_type parsize = pit->size();
259         pos_type end = pos;
260
261         for (; end != parsize; ++end) {
262                 Change change = pit->lookupChangeFull(end);
263                 if (change != orig_change) {
264                         // slight UI optimisation: for replacements, we get
265                         // text like : _old_new. Consider that as one change.
266                         if (!(orig_change.type == Change::DELETED &&
267                                 change.type == Change::INSERTED))
268                                 break;
269                 }
270         }
271         pos_type length = end - pos;
272         put_selection_at(bv, cur, length, true);
273         return true;
274 }
275
276 } // find namespace
277 } // lyx namespace