diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py index a99761940c..a9e6ecc6ae 100644 --- a/src/calibre/gui2/tweak_book/boss.py +++ b/src/calibre/gui2/tweak_book/boss.py @@ -690,21 +690,21 @@ class Boss(QObject): def do_find(): if editor is not None: - if editor.find(pat, marked=marked): + if editor.find(pat, marked=marked, save_match='gui'): return if not files: if not state['wrap']: return no_match() - return editor.find(pat, wrap=True, marked=marked) or no_match() + return editor.find(pat, wrap=True, marked=marked, save_match='gui') or no_match() for fname, syntax in files.iteritems(): if fname in editors: - if not editors[fname].find(pat, complete=True): + if not editors[fname].find(pat, complete=True, save_match='gui'): continue return self.show_editor(fname) raw = current_container().raw_data(fname) if pat.search(raw) is not None: self.edit_file(fname, syntax) - if editors[fname].find(pat, complete=True): + if editors[fname].find(pat, complete=True, save_match='gui'): return return no_match() @@ -720,7 +720,7 @@ class Boss(QObject): def do_replace(): if editor is None: return no_replace() - if not editor.replace(pat, state['replace']): + if not editor.replace(pat, state['replace'], saved_match='gui'): return no_replace(_( 'Currently selected text does not match the search query.')) return True diff --git a/src/calibre/gui2/tweak_book/editor/text.py b/src/calibre/gui2/tweak_book/editor/text.py index d86db05044..be3fb8aae6 100644 --- a/src/calibre/gui2/tweak_book/editor/text.py +++ b/src/calibre/gui2/tweak_book/editor/text.py @@ -115,6 +115,7 @@ class TextEdit(PlainTextEdit): def __init__(self, parent=None): PlainTextEdit.__init__(self, parent) + self.saved_matches = {} self.smarts = NullSmarts(self) self.current_cursor_line = None self.current_search_mark = None @@ -256,7 +257,7 @@ class TextEdit(PlainTextEdit): self.current_search_mark = None self.update_extra_selections() - def find_in_marked(self, pat, wrap=False): + def find_in_marked(self, pat, wrap=False, save_match=None): if self.current_search_mark is None: return False csm = self.current_search_mark.cursor @@ -298,6 +299,8 @@ class TextEdit(PlainTextEdit): self.setTextCursor(c) # Center search result on screen self.centerCursor() + if save_match is not None: + self.saved_matches[save_match] = m return True def all_in_marked(self, pat, template=None): @@ -316,9 +319,9 @@ class TextEdit(PlainTextEdit): self.update_extra_selections() return count - def find(self, pat, wrap=False, marked=False, complete=False): + def find(self, pat, wrap=False, marked=False, complete=False, save_match=None): if marked: - return self.find_in_marked(pat, wrap=wrap) + return self.find_in_marked(pat, wrap=wrap, save_match=save_match) reverse = pat.flags & regex.REVERSE c = self.textCursor() c.clearSelection() @@ -353,12 +356,23 @@ class TextEdit(PlainTextEdit): self.setTextCursor(c) # Center search result on screen self.centerCursor() + if save_match is not None: + self.saved_matches[save_match] = m return True - def replace(self, pat, template): + def replace(self, pat, template, saved_match='gui'): c = self.textCursor() raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n') m = pat.fullmatch(raw) + if m is None: + # This can happen if either the user changed the selected text or + # the search expression uses lookahead/lookbehind operators. See if + # the saved match matches the currently selected text and + # use it, if so. + if saved_match is not None and saved_match in self.saved_matches: + saved = self.saved_matches.pop(saved_match) + if saved.group() == raw: + m = saved if m is None: return False text = m.expand(template)