From 795e1fa9c01581b72db6824bddc33e91e0c0ae0f Mon Sep 17 00:00:00 2001 From: Jonathan Pietkiewicz Date: Tue, 25 Jan 2022 14:05:39 -0600 Subject: [PATCH 1/4] Fix crash when regex is not valid (Fixes olivierkes#989) Wrap regex preparation in try-catch Log when there is a problem preparing the regex --- manuskript/ui/search.py | 73 +++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/manuskript/ui/search.py b/manuskript/ui/search.py index f177c12d..a9826fea 100644 --- a/manuskript/ui/search.py +++ b/manuskript/ui/search.py @@ -13,6 +13,8 @@ from manuskript.enums import Model from manuskript.models.flatDataModelWrapper import flatDataModelWrapper from manuskript.ui.searchMenu import searchMenu from manuskript.ui.highlighters.searchResultHighlighters.searchResultHighlighter import searchResultHighlighter +import logging +LOGGER = logging.getLogger(__name__) class search(QWidget, Ui_search): @@ -69,21 +71,30 @@ class search(QWidget, Ui_search): self.openItem(self.result.currentItem()) def prepareRegex(self, searchText): - import re + rtn = None + try: + import re - flags = re.UNICODE + flags = re.UNICODE - if self.searchMenu.caseSensitive() is False: - flags |= re.IGNORECASE + if self.searchMenu.caseSensitive() is False: + flags |= re.IGNORECASE - if self.searchMenu.regex() is False: - searchText = re.escape(searchText) + if self.searchMenu.regex() is False: + searchText = re.escape(searchText) - if self.searchMenu.matchWords() is True: - # Source: https://stackoverflow.com/a/15863102 - searchText = r'\b' + searchText + r'\b' + if self.searchMenu.matchWords() is True: + # Source: https://stackoverflow.com/a/15863102 + searchText = r'\b' + searchText + r'\b' - return re.compile(searchText, flags) + rtn = re.compile(searchText, flags) + except re.error as e: + LOGGER.info("Problem preparing regular expression: " + e.msg) + rtn = None + except Exception as e: + LOGGER.info("Problem preparing regular expression") + rtn = None + return rtn def search(self): self.result.clear() @@ -91,30 +102,33 @@ class search(QWidget, Ui_search): searchText = self.searchTextInput.text() if len(searchText) > 0: + results = list() searchRegex = self.prepareRegex(searchText) - results = [] + if searchRegex is not None: + # Set override cursor + qApp.setOverrideCursor(Qt.WaitCursor) - # Set override cursor - qApp.setOverrideCursor(Qt.WaitCursor) + for model, modelName in [ + (mainWindow().mdlOutline, Model.Outline), + (mainWindow().mdlCharacter, Model.Character), + (flatDataModelWrapper(mainWindow().mdlFlatData), Model.FlatData), + (mainWindow().mdlWorld, Model.World), + (mainWindow().mdlPlots, Model.Plot) + ]: + filteredColumns = self.searchMenu.columns(modelName) - for model, modelName in [ - (mainWindow().mdlOutline, Model.Outline), - (mainWindow().mdlCharacter, Model.Character), - (flatDataModelWrapper(mainWindow().mdlFlatData), Model.FlatData), - (mainWindow().mdlWorld, Model.World), - (mainWindow().mdlPlots, Model.Plot) - ]: - filteredColumns = self.searchMenu.columns(modelName) + # Searching + if len(filteredColumns): + results += model.searchOccurrences(searchRegex, filteredColumns) - # Searching - if len(filteredColumns): - results += model.searchOccurrences(searchRegex, filteredColumns) + # Showing results + self.generateResultsLists(results) - # Showing results - self.generateResultsLists(results) - - # Remove override cursor - qApp.restoreOverrideCursor() + # Remove override cursor + qApp.restoreOverrideCursor() + else: + # No results to generate if there is a problem with the regex + self.generateResultsLists(list()) def generateResultsLists(self, results): self.noResultsLabel.setVisible(len(results) == 0) @@ -131,6 +145,7 @@ class search(QWidget, Ui_search): def leaveEvent(self, event): self.delegate.mouseLeave() + class listResultDelegate(QStyledItemDelegate): def __init__(self, parent=None): QStyledItemDelegate.__init__(self, parent) From e0a3d01091006bcb9a463307f5f0975333108624 Mon Sep 17 00:00:00 2001 From: Jonathan Pietkiewicz Date: Tue, 25 Jan 2022 21:16:32 -0600 Subject: [PATCH 2/4] move import outside try-catch --- manuskript/ui/search.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/manuskript/ui/search.py b/manuskript/ui/search.py index a9826fea..a1d448fb 100644 --- a/manuskript/ui/search.py +++ b/manuskript/ui/search.py @@ -71,10 +71,9 @@ class search(QWidget, Ui_search): self.openItem(self.result.currentItem()) def prepareRegex(self, searchText): + import re rtn = None try: - import re - flags = re.UNICODE if self.searchMenu.caseSensitive() is False: From 23ded19d5890900b5e6e7e698d6f36fa468cf733 Mon Sep 17 00:00:00 2001 From: Jonathan Pietkiewicz Date: Tue, 25 Jan 2022 21:57:54 -0600 Subject: [PATCH 3/4] Change text color to red when the regex is not correct updated everytime the text changes in the lineedit --- manuskript/ui/search.py | 44 ++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/manuskript/ui/search.py b/manuskript/ui/search.py index a1d448fb..c4e2edca 100644 --- a/manuskript/ui/search.py +++ b/manuskript/ui/search.py @@ -1,5 +1,7 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- +import re + from PyQt5.QtCore import Qt, QRect, QEvent, QCoreApplication from PyQt5.QtGui import QPalette, QFontMetrics, QKeySequence from PyQt5.QtWidgets import QWidget, qApp, QListWidgetItem, QStyledItemDelegate, QStyle, QLabel, QToolTip, QShortcut @@ -25,6 +27,7 @@ class search(QWidget, Ui_search): self.setupUi(self) self.searchTextInput.returnPressed.connect(self.search) + self.searchTextInput.textChanged.connect(self.updateSearchFeedback) self.searchMenu = searchMenu() self.btnOptions.setMenu(self.searchMenu) @@ -70,23 +73,20 @@ class search(QWidget, Ui_search): if 0 < self.result.currentRow() < self.result.count(): self.openItem(self.result.currentItem()) + def updateSearchFeedback(self, search_string): + palette = QPalette() + try: + self.compileRegex(search_string) + except Exception as e: + # From https://stackoverflow.com/questions/27432456/python-qlineedit-text-color + palette.setColor(QPalette.Text, Qt.red) + + self.searchTextInput.setPalette(palette) + def prepareRegex(self, searchText): - import re rtn = None try: - flags = re.UNICODE - - if self.searchMenu.caseSensitive() is False: - flags |= re.IGNORECASE - - if self.searchMenu.regex() is False: - searchText = re.escape(searchText) - - if self.searchMenu.matchWords() is True: - # Source: https://stackoverflow.com/a/15863102 - searchText = r'\b' + searchText + r'\b' - - rtn = re.compile(searchText, flags) + rtn = self.compileRegex(searchText) except re.error as e: LOGGER.info("Problem preparing regular expression: " + e.msg) rtn = None @@ -95,6 +95,22 @@ class search(QWidget, Ui_search): rtn = None return rtn + def compileRegex(self, searchText): + # Intentionally throws exceptions for use elsewhere + flags = re.UNICODE + + if self.searchMenu.caseSensitive() is False: + flags |= re.IGNORECASE + + if self.searchMenu.regex() is False: + searchText = re.escape(searchText) + + if self.searchMenu.matchWords() is True: + # Source: https://stackoverflow.com/a/15863102 + searchText = r'\b' + searchText + r'\b' + + return re.compile(searchText, flags) + def search(self): self.result.clear() self.result.setCurrentRow(0) From 50e305aa2f703e3a45fb0c70a9c36596d01c9611 Mon Sep 17 00:00:00 2001 From: Jonathan Pietkiewicz Date: Tue, 25 Jan 2022 22:08:01 -0600 Subject: [PATCH 4/4] Update search feedback when menu is triggered --- manuskript/ui/search.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/manuskript/ui/search.py b/manuskript/ui/search.py index c4e2edca..b9b9977d 100644 --- a/manuskript/ui/search.py +++ b/manuskript/ui/search.py @@ -31,6 +31,7 @@ class search(QWidget, Ui_search): self.searchMenu = searchMenu() self.btnOptions.setMenu(self.searchMenu) + self.searchMenu.triggered.connect(self.onSearchMenuChange) self.delegate = listResultDelegate(self) self.result.setItemDelegate(self.delegate) @@ -73,6 +74,10 @@ class search(QWidget, Ui_search): if 0 < self.result.currentRow() < self.result.count(): self.openItem(self.result.currentItem()) + def onSearchMenuChange(self): + search_string = self.searchTextInput.text() + self.updateSearchFeedback(search_string) + def updateSearchFeedback(self, search_string): palette = QPalette() try: