]> git.lyx.org Git - lyx.git/blob - src/lyxfind.C
34499ae36b5d13cf686bbbbfe73929682c108598
[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,
163                    buf.paragraphs().size() - 1);
164         
165         PosIterator cur = buf.pos_iterator_begin();
166         PosIterator const end = buf.pos_iterator_end();
167         MatchString const match(searchstr, cs, mw);
168         int num = 0;
169
170         int const rsize = replacestr.size();
171         int const ssize = searchstr.size();
172
173         while (findForward(cur, end, match)) {
174                 pos_type pos = cur.pos();
175                 LyXFont const font
176                         = cur.pit()->getFontSettings(buf.params(), pos);
177                 int striked = ssize - cur.pit()->erase(pos, pos + ssize);
178                 cur.pit()->insert(pos, replacestr, font);
179                 advance(cur, rsize + striked);
180                 ++num;
181         }
182
183         PosIterator beg = buf.pos_iterator_begin();
184         bv->text->init(bv);
185         put_selection_at(bv, beg, 0, false);
186         if (num)
187                 buf.markDirty();
188         return num;
189 }
190
191
192 namespace {
193
194 bool stringSelected(BufferView * bv,
195                     string const & searchstr, 
196                     bool cs, bool mw, bool fw)
197 {
198         LyXText * text = bv->getLyXText();
199         // if nothing selected or selection does not equal search
200         // string search and select next occurance and return
201         string const & str1 = searchstr;
202         string const str2 = text->selectionAsString(*bv->buffer(),
203                                                     false);
204         if ((cs && str1 != str2)
205             || lowercase(str1) != lowercase(str2)) {
206                 find(bv, searchstr, cs, mw, fw);
207                 return false;
208         }
209
210         return true;
211 }
212
213 } //namespace anon
214
215
216 int replace(BufferView * bv,
217             string const & searchstr, string const & replacestr,
218             bool cs, bool mw, bool fw)
219 {
220         if (!searchAllowed(bv, searchstr) || bv->buffer()->isReadonly())
221                 return 0;
222
223         if (!stringSelected(bv, searchstr, cs, mw, fw))
224                 return 0;
225
226         LyXText * text = bv->getLyXText();
227
228         text->replaceSelectionWithString(replacestr);
229         text->setSelectionRange(replacestr.length());
230         text->cursor = fw ? text->selection.end : text->selection.start;
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         ParagraphList::iterator pit = cur.pit();
252         pos_type pos = cur.pos();
253         
254         Change orig_change = pit->lookupChangeFull(pos);
255         pos_type parsize = pit->size();
256         pos_type end = pos;
257
258         for (; end != parsize; ++end) {
259                 Change change = pit->lookupChangeFull(end);
260                 if (change != orig_change) {
261                         // slight UI optimisation: for replacements, we get
262                         // text like : _old_new. Consider that as one change.
263                         if (!(orig_change.type == Change::DELETED &&
264                                 change.type == Change::INSERTED))
265                                 break;
266                 }
267         }
268         pos_type length = end - pos;
269         bv->text->init(bv);
270         put_selection_at(bv, cur, length, true);
271         return true;
272 }
273
274 } // find namespace
275 } // lyx namespace