2 * \file FindAndReplace.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Tommaso Cucinotta
8 * Full author contact details are available in file CREDITS.
13 #include "FindAndReplace.h"
15 #include "GuiApplication.h"
17 #include "GuiWorkArea.h"
18 #include "qt_helpers.h"
20 #include "buffer_funcs.h"
21 #include "BufferParams.h"
22 #include "BufferList.h"
24 #include "FuncRequest.h"
26 #include "output_latex.h"
27 #include "OutputParams.h"
30 #include "frontends/alert.h"
32 #include "support/debug.h"
33 #include "support/filetools.h"
34 #include "support/FileName.h"
35 #include "support/gettext.h"
36 #include "support/lassert.h"
37 #include "support/lstrings.h"
39 #include <QCloseEvent>
46 using namespace lyx::support;
52 FindAndReplaceWidget::FindAndReplaceWidget(GuiView & view)
56 #if QT_VERSION < 0x040400
57 scrollArea->setWidget(scrollAreaWidgetContents);
59 find_work_area_->setGuiView(view_);
60 find_work_area_->init();
61 setFocusProxy(find_work_area_);
62 replace_work_area_->setGuiView(view_);
63 replace_work_area_->init();
64 // We don't want two cursors blinking.
65 replace_work_area_->stopBlinkingCursor();
67 QMenu * menu = new QMenu();
68 QAction * regAny = menu->addAction("&Anything");
69 regAny->setData(".*");
70 QAction * regAnyNonEmpty = menu->addAction("Any non-&empty");
71 regAnyNonEmpty->setData(".+");
72 QAction * regAnyWord = menu->addAction("Any &word");
73 regAnyWord->setData("[a-z]+");
74 QAction * regAnyNumber = menu->addAction("Any &number");
75 regAnyNumber->setData("[0-9]+");
76 QAction * regCustom = menu->addAction("&User-defined");
77 regCustom->setData("");
78 regexpInsertPB->setMenu(menu);
80 connect(menu, SIGNAL(triggered(QAction *)), this, SLOT(insertRegexp(QAction *)));
84 bool FindAndReplaceWidget::eventFilter(QObject *obj, QEvent *event)
86 if (obj == find_work_area_ && event->type() == QEvent::KeyPress) {
87 QKeyEvent *e = static_cast<QKeyEvent *> (event);
88 if (e->key() == Qt::Key_Escape && e->modifiers() == Qt::NoModifier) {
91 } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
92 if (e->modifiers() == Qt::ShiftModifier) {
93 on_findPrevPB_clicked();
95 } else if (e->modifiers() == Qt::NoModifier) {
96 on_findNextPB_clicked();
99 } else if (e->key() == Qt::Key_Tab && e->modifiers() == Qt::NoModifier) {
100 LYXERR(Debug::FIND, "Focusing replace WA");
101 replace_work_area_->setFocus();
105 if (obj == replace_work_area_ && event->type() == QEvent::KeyPress) {
106 QKeyEvent *e = static_cast<QKeyEvent *> (event);
107 if (e->key() == Qt::Key_Escape && e->modifiers() == Qt::NoModifier) {
108 on_closePB_clicked();
110 } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
111 if (e->modifiers() == Qt::ShiftModifier) {
112 on_replacePrevPB_clicked();
114 } else if (e->modifiers() == Qt::NoModifier) {
115 on_replaceNextPB_clicked();
118 } else if (e->key() == Qt::Key_Backtab) {
119 LYXERR(Debug::FIND, "Focusing find WA");
120 find_work_area_->setFocus();
124 // standard event processing
125 return QWidget::eventFilter(obj, event);
128 static docstring buffer_to_latex(Buffer & buffer) {
129 OutputParams runparams(&buffer.params().encoding());
131 runparams.nice = true;
132 runparams.flavor = OutputParams::LATEX;
133 runparams.linelen = 80; //lyxrc.plaintext_linelen;
134 // No side effect of file copying and image conversion
135 runparams.dryrun = true;
136 buffer.texrow().reset();
137 ParagraphList::const_iterator pit = buffer.paragraphs().begin();
138 ParagraphList::const_iterator const end = buffer.paragraphs().end();
139 for (; pit != end; ++pit) {
140 TeXOnePar(buffer, buffer.text(), pit, os, buffer.texrow(), runparams);
141 LYXERR(Debug::FIND, "searchString up to here: " << os.str());
147 static vector<string> const & allManualsFiles() {
148 static vector<string> v;
149 static const char * files[] = {
150 "Intro", "UserGuide", "Tutorial", "Additional", "EmbeddedObjects",
151 "Math", "Customization", "Shortcuts", "LFUNs", "LaTeXConfig"
155 for (size_t i = 0; i < sizeof(files) / sizeof(files[0]); ++i) {
156 fname = i18nLibFileSearch("doc", files[i], "lyx");
157 v.push_back(fname.absFilename());
164 /** Switch p_buf to point to next document buffer.
166 ** Return true if restarted from master-document buffer.
169 ** Not using p_buf->allRelatives() here, because I'm not sure
170 ** whether or not the returned order is independent of p_buf.
172 static bool next_document_buffer(Buffer * & p_buf) {
173 Buffer *p_master = p_buf;
177 p_master = const_cast<Buffer *>(p_master->masterBuffer());
178 LYXERR(Debug::FIND, "p_old=" << p_old << ", p_master=" << p_master);
179 } while (p_master != p_old);
180 LASSERT(p_master != NULL, /**/);
181 vector<Buffer *> v_children;
182 /* Root master added as first buffer in the vector */
183 v_children.push_back(p_master);
184 p_master->getChildren(v_children, true);
185 LYXERR(Debug::FIND, "v_children.size()=" << v_children.size());
186 vector<Buffer *>::const_iterator it = find(v_children.begin(), v_children.end(), p_buf);
187 LASSERT(it != v_children.end(), /**/)
189 if (it == v_children.end()) {
190 p_buf = *v_children.begin();
198 /** Switch p_buf to point to previous document buffer.
200 ** Return true if restarted from last child buffer.
203 ** Not using p_buf->allRelatives() here, because I'm not sure
204 ** whether or not the returned order is independent of p_buf.
206 static bool prev_document_buffer(Buffer * & p_buf) {
207 Buffer *p_master = p_buf;
211 p_master = const_cast<Buffer *>(p_master->masterBuffer());
212 LYXERR(Debug::FIND, "p_old=" << p_old << ", p_master=" << p_master);
213 } while (p_master != p_old);
214 LASSERT(p_master != NULL, /**/);
215 vector<Buffer *> v_children;
216 /* Root master added as first buffer in the vector */
217 v_children.push_back(p_master);
218 p_master->getChildren(v_children, true);
219 LYXERR(Debug::FIND, "v_children.size()=" << v_children.size());
220 vector<Buffer *>::const_iterator it = find(v_children.begin(), v_children.end(), p_buf);
221 LASSERT(it != v_children.end(), /**/)
222 if (it == v_children.begin()) {
223 it = v_children.end();
234 /** Switch buf to point to next or previous buffer in search scope.
236 ** Return true if restarted from scratch.
238 static bool next_prev_buffer(Buffer * & buf, FindAndReplaceOptions const & opt) {
239 bool restarted = false;
241 case FindAndReplaceOptions::S_BUFFER:
244 case FindAndReplaceOptions::S_DOCUMENT:
246 restarted = next_document_buffer(buf);
248 restarted = prev_document_buffer(buf);
250 case FindAndReplaceOptions::S_OPEN_BUFFERS:
252 buf = theBufferList().next(buf);
253 restarted = buf == *theBufferList().begin();
255 buf = theBufferList().previous(buf);
256 restarted = buf == *(theBufferList().end() - 1);
259 case FindAndReplaceOptions::S_ALL_MANUALS:
260 vector<string> const & v = allManualsFiles();
261 vector<string>::const_iterator it = find(v.begin(), v.end(), buf->absFileName());
264 } else if (opt.forward) {
271 if (it == v.begin()) {
277 FileName const & fname = FileName(*it);
278 if (!theBufferList().exists(fname)) {
279 guiApp->currentView()->setBusy(false);
280 guiApp->currentView()->loadDocument(fname, false);
281 guiApp->currentView()->setBusy(true);
283 buf = theBufferList().getBuffer(fname);
290 /** Find the finest question message to post to the user */
291 docstring question_string(FindAndReplaceOptions const & opt)
295 case FindAndReplaceOptions::S_BUFFER:
296 scope = _("file[[scope]]");
298 case FindAndReplaceOptions::S_DOCUMENT:
299 scope = _("master document[[scope]]");
301 case FindAndReplaceOptions::S_OPEN_BUFFERS:
302 scope = _("open files[[scope]]");
304 case FindAndReplaceOptions::S_ALL_MANUALS:
305 scope = _("manuals[[scope]]");
308 docstring message = opt.forward ?
309 bformat(_("End of %1$s reached while searching forward.\n"
310 "Continue searching from begin?"),
312 bformat(_("Beginning of %1$s reached while searching backwards.\n"
313 "Continue searching from end?"),
320 void FindAndReplaceWidget::findAndReplaceScope(FindAndReplaceOptions & opt) {
321 int wrap_answer = -1;
324 FuncRequest cmd(LFUN_WORD_FINDADV, from_utf8(oss.str()));
325 BufferView * bv = view_.documentBufferView();
326 Buffer * buf = &bv->buffer();
328 Buffer * buf_orig = &bv->buffer();
329 Cursor cur_orig(bv->cursor());
331 if (opt.scope == FindAndReplaceOptions::S_ALL_MANUALS) {
332 vector<string> const & v = allManualsFiles();
333 if (std::find(v.begin(), v.end(), buf->absFileName()) == v.end()) {
334 FileName const & fname = FileName(*v.begin());
335 if (!theBufferList().exists(fname)) {
336 guiApp->currentView()->setBusy(false);
337 guiApp->currentView()->loadDocument(fname, false);
338 guiApp->currentView()->setBusy(true);
340 buf = theBufferList().getBuffer(fname);
341 lyx::dispatch(FuncRequest(LFUN_BUFFER_SWITCH,
342 buf->absFileName()));
343 bv = view_.documentBufferView();
344 bv->cursor().clear();
345 bv->cursor().push_back(CursorSlice(buf->inset()));
350 LYXERR(Debug::FIND, "Dispatching LFUN_WORD_FINDADV");
352 if (bv->cursor().result().dispatched()) {
353 // Match found, selected and replaced if needed
357 // No match found in current buffer: select next buffer in scope, if any
358 bool prompt = next_prev_buffer(buf, opt);
360 if (wrap_answer != -1)
362 docstring q = question_string(opt);
363 wrap_answer = frontend::Alert::prompt(
364 _("Wrap search?"), q,
365 0, 1, _("&Yes"), _("&No"));
366 if (wrap_answer == 1)
369 lyx::dispatch(FuncRequest(LFUN_BUFFER_SWITCH,
370 buf->absFileName()));
371 bv = view_.documentBufferView();
373 bv->cursor().clear();
374 bv->cursor().push_back(CursorSlice(buf->inset()));
376 lyx::dispatch(FuncRequest(LFUN_BUFFER_END));
377 bv->cursor().setCursor(doc_iterator_end(buf));
378 bv->cursor().backwardPos();
379 LYXERR(Debug::FIND, "findBackAdv5: cur: " << bv->cursor());
381 bv->clearSelection();
382 } while (wrap_answer != 1);
384 lyx::dispatch(FuncRequest(LFUN_BUFFER_SWITCH,
385 buf_orig->absFileName()));
386 bv = view_.documentBufferView();
387 bv->cursor() = cur_orig;
391 void FindAndReplaceWidget::findAndReplace(
392 bool casesensitive, bool matchword, bool backwards,
393 bool expandmacros, bool ignoreformat, bool replace,
396 Buffer & buffer = find_work_area_->bufferView().buffer();
397 docstring searchString;
399 searchString = buffer_to_latex(buffer);
401 ParIterator it = buffer.par_iterator_begin();
402 ParIterator end = buffer.par_iterator_end();
403 OutputParams runparams(&buffer.params().encoding());
405 runparams.nice = true;
406 runparams.flavor = OutputParams::LATEX;
407 runparams.linelen = 100000; //lyxrc.plaintext_linelen;
408 runparams.dryrun = true;
409 for (; it != end; ++it) {
410 LYXERR(Debug::FIND, "Adding to search string: '" << it->asString(false) << "'");
411 searchString += it->stringify(pos_type(0), it->size(), AS_STR_INSETS, runparams);
414 if (to_utf8(searchString).empty()) {
415 buffer.message(_("Nothing to search"));
418 bool const regexp = to_utf8(searchString).find("\\regexp") != std::string::npos;
419 docstring replaceString;
421 Buffer & repl_buffer = replace_work_area_->bufferView().buffer();
423 repl_buffer.write(oss);
424 replaceString = from_utf8(oss.str()); //buffer_to_latex(replace_buffer);
426 replaceString = from_utf8(LYX_FR_NULL_STRING);
428 FindAndReplaceOptions::SearchScope scope = FindAndReplaceOptions::S_BUFFER;
429 if (CurrentDocument->isChecked())
430 scope = FindAndReplaceOptions::S_BUFFER;
431 else if (MasterDocument->isChecked())
432 scope = FindAndReplaceOptions::S_DOCUMENT;
433 else if (OpenDocuments->isChecked())
434 scope = FindAndReplaceOptions::S_OPEN_BUFFERS;
435 else if (AllManualsRB->isChecked())
436 scope = FindAndReplaceOptions::S_ALL_MANUALS;
438 LASSERT(false, /**/);
439 LYXERR(Debug::FIND, "FindAndReplaceOptions: "
440 << "searchstring=" << searchString
441 << ", casesensitiv=" << casesensitive
442 << ", matchword=" << matchword
443 << ", backwards=" << backwards
444 << ", expandmacros=" << expandmacros
445 << ", ignoreformat=" << ignoreformat
446 << ", regexp=" << regexp
447 << ", replaceString" << replaceString
448 << ", keep_case=" << keep_case
449 << ", scope=" << scope);
450 FindAndReplaceOptions opt(searchString, casesensitive, matchword, ! backwards,
451 expandmacros, ignoreformat, regexp, replaceString, keep_case, scope);
453 findAndReplaceScope(opt);
454 view_.setBusy(false);
458 void FindAndReplaceWidget::findAndReplace(bool backwards, bool replace)
460 if (! view_.currentMainWorkArea()) {
461 view_.message(_("No open document(s) in which to search"));
464 // FIXME: create a Dialog::returnFocus() or something instead of this:
465 view_.setCurrentWorkArea(view_.currentMainWorkArea());
466 findAndReplace(caseCB->isChecked(),
467 wordsCB->isChecked(),
469 expandMacrosCB->isChecked(),
470 ignoreFormatCB->isChecked(),
472 keepCaseCB->isChecked());
476 void FindAndReplaceWidget::insertRegexp(QAction * action)
478 string const regexp = fromqstr(action->data().toString());
479 LYXERR(Debug::FIND, "Regexp: " << regexp);
480 find_work_area_->setFocus();
481 Cursor & cur = find_work_area_->bufferView().cursor();
482 if (!cur.inRegexped())
483 dispatch(FuncRequest(LFUN_REGEXP_MODE));
484 dispatch(FuncRequest(LFUN_SELF_INSERT, regexp));
488 void FindAndReplaceWidget::on_closePB_clicked()
490 dispatch(FuncRequest(LFUN_DIALOG_TOGGLE, "findreplaceadv"));
494 void FindAndReplaceWidget::on_findNextPB_clicked() {
495 findAndReplace(false, false);
496 find_work_area_->setFocus();
500 void FindAndReplaceWidget::on_findPrevPB_clicked() {
501 findAndReplace(true, false);
502 find_work_area_->setFocus();
506 void FindAndReplaceWidget::on_replaceNextPB_clicked()
508 findAndReplace(false, true);
509 replace_work_area_->setFocus();
513 void FindAndReplaceWidget::on_replacePrevPB_clicked()
515 findAndReplace(true, true);
516 replace_work_area_->setFocus();
520 void FindAndReplaceWidget::on_replaceallPB_clicked()
522 replace_work_area_->setFocus();
526 void FindAndReplaceWidget::showEvent(QShowEvent * /* ev */)
528 view_.setCurrentWorkArea(find_work_area_);
529 LYXERR(Debug::FIND, "Selecting entire find buffer");
530 dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
531 dispatch(FuncRequest(LFUN_BUFFER_END_SELECT));
532 find_work_area_->installEventFilter(this);
533 replace_work_area_->installEventFilter(this);
537 void FindAndReplaceWidget::hideEvent(QHideEvent *ev)
539 replace_work_area_->removeEventFilter(this);
540 find_work_area_->removeEventFilter(this);
541 this->QWidget::hideEvent(ev);
545 bool FindAndReplaceWidget::initialiseParams(std::string const & /* params */)
551 FindAndReplace::FindAndReplace(GuiView & parent,
552 Qt::DockWidgetArea area, Qt::WindowFlags flags)
553 : DockView(parent, "Find LyX", qt_("Advanced Find and Replace"), area, flags)
555 widget_ = new FindAndReplaceWidget(parent);
557 setFocusProxy(widget_);
561 FindAndReplace::~FindAndReplace()
568 bool FindAndReplace::initialiseParams(std::string const & params)
570 return widget_->initialiseParams(params);
574 Dialog * createGuiSearchAdv(GuiView & lv)
576 return new FindAndReplace(lv, Qt::RightDockWidgetArea);
580 } // namespace frontend
584 #include "moc_FindAndReplace.cpp"