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