From 735830ef1a44f34f8be9e5163caab82ac87208de Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 31 Mar 2025 10:32:34 +0530 Subject: [PATCH] E-book viewer: Show an error message when an invalid nearby search expression is used. See #2104850 (Private bug) --- src/calibre/gui2/viewer/search.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/viewer/search.py b/src/calibre/gui2/viewer/search.py index ef6384d1ed..c3e573dd60 100644 --- a/src/calibre/gui2/viewer/search.py +++ b/src/calibre/gui2/viewer/search.py @@ -26,7 +26,7 @@ from qt.core import ( ) from calibre.ebooks.conversion.search_replace import REGEX_FLAGS -from calibre.gui2 import warning_dialog +from calibre.gui2 import error_dialog, warning_dialog from calibre.gui2.gestures import GestureManager from calibre.gui2.progress_indicator import ProgressIndicator from calibre.gui2.viewer import get_boss @@ -103,8 +103,8 @@ def words_and_interval_for_near(expr, default_interval=60): words = [] interval = default_interval - for q in parts: - if q is parts[-1] and q.isdigit(): + for i, q in enumerate(parts): + if q is parts[-1] and q.isdigit() and i > 1: interval = int(q) else: words.append(text_to_regex(q)) @@ -154,12 +154,29 @@ class Search: flags = self.regex_flags flags |= regex.DOTALL match_any_word = r'(?:\b(?:' + '|'.join(words) + r')\b)' - joiner = '.{1,%d}' % interval # noqa: UP031 + joiner = f'.{{1,{interval}}}' full_pat = regex.compile(joiner.join(match_any_word for x in words), flags=flags) word_pats = tuple(regex.compile(rf'\b{x}\b', flags) for x in words) self._nsd = word_pats, full_pat return self._nsd + def validate(self, gui) -> bool: + if self.mode == 'near': + word_pats, full_pat = self.near_search_data + if len(word_pats) < 2: + error_dialog(gui, _('Invalid search expression'), _( + 'In Nearby words mode, you must specify at least two words and an optional trailing number of characters.' + ' The expression: {} does not have at least two words.').format(self.text), show=True) + return False + elif self.mode == 'regex': + try: + self.regex + except Exception as e: + error_dialog(gui, _('Invalid search expression'), _( + 'The search expression {} is not a valid regex.').format(self.text), det_msg=str(e), show=True) + return False + return True + @property def is_empty(self): if not self.text: @@ -802,6 +819,8 @@ class SearchPanel(QWidget): # {{{ if self.current_search is not None and search_query == self.current_search: self.find_next_requested(search_query.backwards) return + if not search_query.validate(self): + return if self.searcher is None: self.searcher = Thread(name='Searcher', target=self.run_searches) self.searcher.daemon = True