From 9b08ebbbd5c6d33c5bf5aa089cc462afcb231eb6 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sun, 22 Sep 2024 17:47:49 +0200
Subject: [PATCH] Use Python regex for document search tool
---
novelwriter/core/coretools.py | 26 ++++++++++++--------------
novelwriter/types.py | 6 +-----
tests/test_core/test_core_coretools.py | 2 +-
3 files changed, 14 insertions(+), 20 deletions(-)
diff --git a/novelwriter/core/coretools.py b/novelwriter/core/coretools.py
index 08b96957..5b4dc6d7 100644
--- a/novelwriter/core/coretools.py
+++ b/novelwriter/core/coretools.py
@@ -27,6 +27,7 @@ along with this program. If not, see .
from __future__ import annotations
import logging
+import re
import shutil
from collections.abc import Iterable
@@ -34,7 +35,7 @@ from functools import partial
from pathlib import Path
from zipfile import ZipFile, is_zipfile
-from PyQt5.QtCore import QCoreApplication, QRegularExpression
+from PyQt5.QtCore import QCoreApplication
from novelwriter import CONFIG, SHARED
from novelwriter.common import isHandle, minmax, simplified
@@ -297,8 +298,8 @@ class DocDuplicator:
class DocSearch:
def __init__(self) -> None:
- self._regEx = QRegularExpression()
- self.setCaseSensitive(False)
+ self._regEx = re.compile("")
+ self._opts = re.UNICODE | re.IGNORECASE
self._words = False
self._escape = True
return
@@ -309,10 +310,9 @@ class DocSearch:
def setCaseSensitive(self, state: bool) -> None:
"""Set the case sensitive search flag."""
- opts = QRegularExpression.PatternOption.UseUnicodePropertiesOption
+ self._opts = re.UNICODE
if not state:
- opts |= QRegularExpression.PatternOption.CaseInsensitiveOption
- self._regEx.setPatternOptions(opts)
+ self._opts |= re.IGNORECASE
return
def setWholeWords(self, state: bool) -> None:
@@ -329,8 +329,8 @@ class DocSearch:
self, project: NWProject, search: str
) -> Iterable[tuple[NWItem, list[tuple[int, int, str]], bool]]:
"""Iteratively search through documents in a project."""
- self._regEx.setPattern(self._buildPattern(search))
- logger.debug("Searching with pattern '%s'", self._regEx.pattern())
+ self._regEx = re.compile(self._buildPattern(search), self._opts)
+ logger.debug("Searching with pattern '%s'", self._regEx.pattern)
storage = project.storage
for item in project.tree:
if item.isFileType():
@@ -340,14 +340,12 @@ class DocSearch:
def searchText(self, text: str) -> tuple[list[tuple[int, int, str]], bool]:
"""Search a piece of text for RegEx matches."""
- rxItt = self._regEx.globalMatch(text)
count = 0
capped = False
results = []
- while rxItt.hasNext():
- rxMatch = rxItt.next()
- pos = rxMatch.capturedStart()
- num = rxMatch.capturedLength()
+ for match in re.finditer(self._regEx, text):
+ pos = match.start(0)
+ num = len(match.group(0))
lim = text[:pos].rfind("\n") + 1
cut = text[lim:pos].rfind(" ") + lim + 1
context = text[cut:cut+100].partition("\n")[0]
@@ -366,7 +364,7 @@ class DocSearch:
def _buildPattern(self, search: str) -> str:
"""Build the search pattern string."""
if self._escape:
- search = QRegularExpression.escape(search)
+ search = re.escape(search)
if self._words:
search = f"(?:^|\\b){search}(?:$|\\b)"
return search
diff --git a/novelwriter/types.py b/novelwriter/types.py
index 40f22acf..ab684dee 100644
--- a/novelwriter/types.py
+++ b/novelwriter/types.py
@@ -23,7 +23,7 @@ along with this program. If not, see .
"""
from __future__ import annotations
-from PyQt5.QtCore import QRegularExpression, Qt
+from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor, QFont, QPainter, QTextCharFormat, QTextCursor, QTextFormat
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QSizePolicy, QStyle
@@ -115,10 +115,6 @@ QtSizeMinimumExpanding = QSizePolicy.Policy.MinimumExpanding
QtScrollAlwaysOff = Qt.ScrollBarPolicy.ScrollBarAlwaysOff
QtScrollAsNeeded = Qt.ScrollBarPolicy.ScrollBarAsNeeded
-# Other
-
-QRegExUnicode = QRegularExpression.PatternOption.UseUnicodePropertiesOption
-
# Maps
FONT_WEIGHTS: dict[int, int] = {
diff --git a/tests/test_core/test_core_coretools.py b/tests/test_core/test_core_coretools.py
index faf11cfc..3827db7a 100644
--- a/tests/test_core/test_core_coretools.py
+++ b/tests/test_core/test_core_coretools.py
@@ -421,7 +421,7 @@ def testCoreTools_DocSearch(monkeypatch, mockGUI, fncPath, mockRnd, ipsumText):
# Patterns
# ========
- # Escape Using QRegularExpression
+ # Escape
assert search._buildPattern("[A-Za-z0-9_]+") == r"\[A\-Za\-z0\-9_\]\+"
# Whole Words