6 #include "frontends/LyXView.h"
7 #include "frontends/Alert.h"
8 #include "support/textutils.h"
9 #include "support/lstrings.h"
10 #include "BufferView.h"
14 #include "insets/insettext.h"
17 using namespace lyx::support;
24 /// returns true if the specified string is at the specified position
25 bool IsStringInText(Paragraph * par, pos_type pos,
26 string const & str, bool const & = true,
27 bool const & = false);
29 /// if the string is found: return true and set the cursor to the new position
30 SearchResult SearchForward(BufferView *, LyXText * text, string const & str,
31 bool const & = true, bool const & = false);
33 SearchResult SearchBackward(BufferView *, LyXText * text, string const & str,
34 bool const & = true, bool const & = false);
36 int LyXReplace(BufferView * bv,
37 string const & searchstr, string const & replacestr,
38 bool forward, bool casesens, bool matchwrd, bool replaceall,
41 if (!bv->available() || bv->buffer()->isReadonly())
44 // CutSelection cannot cut a single space, so we have to stop
45 // in order to avoid endless loop :-(
46 if (searchstr.length() == 0
47 || (searchstr.length() == 1 && searchstr[0] == ' ')) {
49 #warning BLECH. If we have an LFUN for replace, we can sort of fix this bogosity
51 Alert::error(_("Cannot replace"),
52 _("You cannot replace a single space or "
53 "an empty character."));
57 // now we can start searching for the first
58 // start at top if replaceall
59 LyXText * text = bv->getLyXText();
62 text->clearSelection();
63 bv->unlockInset(bv->theLockingInset());
66 // override search direction because we search top to bottom
70 // if nothing selected or selection does not equal search string
71 // search and select next occurance and return if no replaceall
76 str2 = text->selectionAsString(bv->buffer(), false);
78 str1 = lowercase(searchstr);
79 str2 = lowercase(text->selectionAsString(bv->buffer(), false));
82 if (!LyXFind(bv, searchstr, fw, casesens, matchwrd) ||
89 int replace_count = 0;
91 text = bv->getLyXText();
92 // We have to do this check only because mathed insets don't
93 // return their own LyXText but the LyXText of it's parent!
94 if (!bv->theLockingInset() ||
95 ((text != bv->text) &&
96 (text->inset_owner == text->inset_owner->getLockingInset()))) {
97 bv->update(text, BufferView::SELECT);
98 bv->toggleSelection(false);
99 text->replaceSelectionWithString(replacestr);
100 text->setSelectionRange(replacestr.length());
101 bv->update(text, BufferView::SELECT);
105 found = LyXFind(bv, searchstr, fw, casesens, matchwrd);
106 } while (!once && replaceall && found);
108 // FIXME: should be called via an LFUN
109 bv->buffer()->markDirty();
112 return replace_count;
116 bool LyXFind(BufferView * bv,
117 string const & searchstr, bool forward,
118 bool casesens, bool matchwrd)
120 if (!bv->available() || searchstr.empty())
123 bv->update(bv->getLyXText(), BufferView::SELECT);
125 if (bv->theLockingInset()) {
126 bool found = forward ?
127 bv->theLockingInset()->searchForward(bv, searchstr, casesens, matchwrd) :
128 bv->theLockingInset()->searchBackward(bv, searchstr, casesens, matchwrd);
129 // We found the stuff inside the inset so we don't have to
130 // do anything as the inset did all the update for us!
133 // We now are in the main text but if we did a forward
134 // search we have to put the cursor behind the inset.
136 bv->text->cursorRight(true);
139 // If we arrive here we are in the main text again so we
140 // just start searching from the root LyXText at the position
142 LyXText * text = bv->text;
145 if (text->selection.set())
146 text->cursor = forward ?
147 text->selection.end : text->selection.start;
149 bv->toggleSelection();
150 text->clearSelection();
152 SearchResult result = forward ?
153 SearchForward(bv, text, searchstr, casesens, matchwrd) :
154 SearchBackward(bv, text, searchstr, casesens, matchwrd);
157 // If we found the cursor inside an inset we will get back
158 // SR_FOUND_NOUPDATE and we don't have to do anything as the
159 // inset did it already.
160 if (result == SR_FOUND) {
161 bv->unlockInset(bv->theLockingInset());
162 bv->update(text, BufferView::SELECT);
163 text->setSelectionRange(searchstr.length());
164 bv->toggleSelection(false);
165 bv->update(text, BufferView::SELECT);
166 } else if (result == SR_NOT_FOUND) {
167 bv->unlockInset(bv->theLockingInset());
168 bv->update(text, BufferView::SELECT);
178 SearchResult LyXFind(BufferView * bv, LyXText * text,
179 string const & searchstr, bool forward,
180 bool casesens, bool matchwrd)
182 if (text->selection.set())
183 text->cursor = forward ?
184 text->selection.end : text->selection.start;
186 bv->toggleSelection();
187 text->clearSelection();
189 SearchResult result = forward ?
190 SearchForward(bv, text, searchstr, casesens, matchwrd) :
191 SearchBackward(bv, text, searchstr, casesens, matchwrd);
197 // returns true if the specified string is at the specified position
198 bool IsStringInText(Paragraph const & par, pos_type pos,
199 string const & str, bool const & cs,
202 string::size_type size = str.length();
204 pos_type parsize = par.size();
205 while (((pos + i) < parsize)
206 && (string::size_type(i) < size)
207 && (cs ? (str[i] == par.getChar(pos + i))
208 : (uppercase(str[i]) == uppercase(par.getChar(pos + i))))) {
212 if (size == string::size_type(i)) {
213 // if necessary, check whether string matches word
216 if ((pos <= 0 || !IsLetterCharOrDigit(par.getChar(pos - 1)))
217 && (pos + pos_type(size) >= parsize
218 || !IsLetterCharOrDigit(par.getChar(pos + size)))) {
226 // if the string can be found: return true and set the cursor to
227 // the new position, cs = casesensitive, mw = matchword
228 SearchResult SearchForward(BufferView * bv, LyXText * text, string const & str,
229 bool const & cs, bool const & mw)
231 ParagraphList::iterator pit = text->cursor.par();
232 ParagraphList::iterator pend = text->ownerParagraphs().end();
233 pos_type pos = text->cursor.pos();
234 UpdatableInset * inset;
236 while (pit != pend && !IsStringInText(*pit, pos, str, cs, mw)) {
237 if (pos < pit->size()
239 && (inset = (UpdatableInset *)pit->getInset(pos))
240 && inset->isTextInset()
241 && inset->searchForward(bv, str, cs, mw))
242 return SR_FOUND_NOUPDATE;
244 if (++pos >= pit->size()) {
251 text->setCursor(pit, pos);
259 // if the string can be found: return true and set the cursor to
260 // the new position, cs = casesensitive, mw = matchword
261 SearchResult SearchBackward(BufferView * bv, LyXText * text,
263 bool const & cs, bool const & mw)
265 ParagraphList::iterator pit = text->cursor.par();
266 ParagraphList::iterator pbegin = text->ownerParagraphs().begin();
267 pos_type pos = text->cursor.pos();
269 // skip past a match at the current cursor pos
272 } else if (pit != pbegin) {
280 if (pos < pit->size()) {
281 if (pit->isInset(pos) && pit->getInset(pos)->isTextInset()) {
282 UpdatableInset * inset = (UpdatableInset *)pit->getInset(pos);
283 if (inset->searchBackward(bv, str, cs, mw))
284 return SR_FOUND_NOUPDATE;
287 if (IsStringInText(*pit, pos, str, cs, mw)) {
288 text->setCursor(pit, pos);
293 if (pos == 0 && pit == pbegin)
298 } else if (pit != pbegin) {
308 SearchResult nextChange(BufferView * bv, LyXText * text, pos_type & length)
310 ParagraphList::iterator pit = text->cursor.par();
311 ParagraphList::iterator pend = text->ownerParagraphs().end();
312 pos_type pos = text->cursor.pos();
314 while (pit != pend) {
315 pos_type parsize = pit->size();
318 if ((!parsize || pos != parsize)
319 && pit->lookupChange(pos) != Change::UNCHANGED)
322 if (pit->isInset(pos) && pit->getInset(pos)->isTextInset()) {
323 UpdatableInset * inset = (UpdatableInset *)pit->getInset(pos);
324 if (inset->nextChange(bv, length))
325 return SR_FOUND_NOUPDATE;
331 if (pos >= parsize) {
340 text->setCursor(pit, pos);
341 Change orig_change = pit->lookupChangeFull(pos);
342 pos_type parsize = pit->size();
345 for (; end != parsize; ++end) {
346 Change change = pit->lookupChangeFull(end);
347 if (change != orig_change) {
348 // slight UI optimisation: for replacements, we get
349 // text like : _old_new. Consider that as one change.
350 if (!(orig_change.type == Change::DELETED &&
351 change.type == Change::INSERTED))
360 SearchResult findNextChange(BufferView * bv, LyXText * text, pos_type & length)
362 if (text->selection.set())
363 text->cursor = text->selection.end;
365 bv->toggleSelection();
366 text->clearSelection();
368 return nextChange(bv, text, length);
372 bool findNextChange(BufferView * bv)
374 if (!bv->available())
377 bv->update(bv->getLyXText(), BufferView::SELECT);
381 if (bv->theLockingInset()) {
382 bool found = bv->theLockingInset()->nextChange(bv, length);
384 // We found the stuff inside the inset so we don't have to
385 // do anything as the inset did all the update for us!
389 // We now are in the main text but if we did a forward
390 // search we have to put the cursor behind the inset.
391 bv->text->cursorRight(true);
393 // If we arrive here we are in the main text again so we
394 // just start searching from the root LyXText at the position
396 LyXText * text = bv->text;
398 if (text->selection.set())
399 text->cursor = text->selection.end;
401 bv->toggleSelection();
402 text->clearSelection();
404 SearchResult result = nextChange(bv, text, length);
408 // If we found the cursor inside an inset we will get back
409 // SR_FOUND_NOUPDATE and we don't have to do anything as the
410 // inset did it already.
411 if (result == SR_FOUND) {
412 bv->unlockInset(bv->theLockingInset());
413 bv->update(text, BufferView::SELECT);
414 text->setSelectionRange(length);
415 bv->toggleSelection(false);
416 bv->update(text, BufferView::SELECT);
417 } else if (result == SR_NOT_FOUND) {
418 bv->unlockInset(bv->theLockingInset());
419 bv->update(text, BufferView::SELECT);
428 } // end lyxfind namespace