Use Python regex for document search tool
This commit is contained in:
@@ -27,6 +27,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
@@ -34,7 +35,7 @@ from functools import partial
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from zipfile import ZipFile, is_zipfile
|
from zipfile import ZipFile, is_zipfile
|
||||||
|
|
||||||
from PyQt5.QtCore import QCoreApplication, QRegularExpression
|
from PyQt5.QtCore import QCoreApplication
|
||||||
|
|
||||||
from novelwriter import CONFIG, SHARED
|
from novelwriter import CONFIG, SHARED
|
||||||
from novelwriter.common import isHandle, minmax, simplified
|
from novelwriter.common import isHandle, minmax, simplified
|
||||||
@@ -297,8 +298,8 @@ class DocDuplicator:
|
|||||||
class DocSearch:
|
class DocSearch:
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._regEx = QRegularExpression()
|
self._regEx = re.compile("")
|
||||||
self.setCaseSensitive(False)
|
self._opts = re.UNICODE | re.IGNORECASE
|
||||||
self._words = False
|
self._words = False
|
||||||
self._escape = True
|
self._escape = True
|
||||||
return
|
return
|
||||||
@@ -309,10 +310,9 @@ class DocSearch:
|
|||||||
|
|
||||||
def setCaseSensitive(self, state: bool) -> None:
|
def setCaseSensitive(self, state: bool) -> None:
|
||||||
"""Set the case sensitive search flag."""
|
"""Set the case sensitive search flag."""
|
||||||
opts = QRegularExpression.PatternOption.UseUnicodePropertiesOption
|
self._opts = re.UNICODE
|
||||||
if not state:
|
if not state:
|
||||||
opts |= QRegularExpression.PatternOption.CaseInsensitiveOption
|
self._opts |= re.IGNORECASE
|
||||||
self._regEx.setPatternOptions(opts)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def setWholeWords(self, state: bool) -> None:
|
def setWholeWords(self, state: bool) -> None:
|
||||||
@@ -329,8 +329,8 @@ class DocSearch:
|
|||||||
self, project: NWProject, search: str
|
self, project: NWProject, search: str
|
||||||
) -> Iterable[tuple[NWItem, list[tuple[int, int, str]], bool]]:
|
) -> Iterable[tuple[NWItem, list[tuple[int, int, str]], bool]]:
|
||||||
"""Iteratively search through documents in a project."""
|
"""Iteratively search through documents in a project."""
|
||||||
self._regEx.setPattern(self._buildPattern(search))
|
self._regEx = re.compile(self._buildPattern(search), self._opts)
|
||||||
logger.debug("Searching with pattern '%s'", self._regEx.pattern())
|
logger.debug("Searching with pattern '%s'", self._regEx.pattern)
|
||||||
storage = project.storage
|
storage = project.storage
|
||||||
for item in project.tree:
|
for item in project.tree:
|
||||||
if item.isFileType():
|
if item.isFileType():
|
||||||
@@ -340,14 +340,12 @@ class DocSearch:
|
|||||||
|
|
||||||
def searchText(self, text: str) -> tuple[list[tuple[int, int, str]], bool]:
|
def searchText(self, text: str) -> tuple[list[tuple[int, int, str]], bool]:
|
||||||
"""Search a piece of text for RegEx matches."""
|
"""Search a piece of text for RegEx matches."""
|
||||||
rxItt = self._regEx.globalMatch(text)
|
|
||||||
count = 0
|
count = 0
|
||||||
capped = False
|
capped = False
|
||||||
results = []
|
results = []
|
||||||
while rxItt.hasNext():
|
for match in re.finditer(self._regEx, text):
|
||||||
rxMatch = rxItt.next()
|
pos = match.start(0)
|
||||||
pos = rxMatch.capturedStart()
|
num = len(match.group(0))
|
||||||
num = rxMatch.capturedLength()
|
|
||||||
lim = text[:pos].rfind("\n") + 1
|
lim = text[:pos].rfind("\n") + 1
|
||||||
cut = text[lim:pos].rfind(" ") + lim + 1
|
cut = text[lim:pos].rfind(" ") + lim + 1
|
||||||
context = text[cut:cut+100].partition("\n")[0]
|
context = text[cut:cut+100].partition("\n")[0]
|
||||||
@@ -366,7 +364,7 @@ class DocSearch:
|
|||||||
def _buildPattern(self, search: str) -> str:
|
def _buildPattern(self, search: str) -> str:
|
||||||
"""Build the search pattern string."""
|
"""Build the search pattern string."""
|
||||||
if self._escape:
|
if self._escape:
|
||||||
search = QRegularExpression.escape(search)
|
search = re.escape(search)
|
||||||
if self._words:
|
if self._words:
|
||||||
search = f"(?:^|\\b){search}(?:$|\\b)"
|
search = f"(?:^|\\b){search}(?:$|\\b)"
|
||||||
return search
|
return search
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
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.QtGui import QColor, QFont, QPainter, QTextCharFormat, QTextCursor, QTextFormat
|
||||||
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QSizePolicy, QStyle
|
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QSizePolicy, QStyle
|
||||||
|
|
||||||
@@ -115,10 +115,6 @@ QtSizeMinimumExpanding = QSizePolicy.Policy.MinimumExpanding
|
|||||||
QtScrollAlwaysOff = Qt.ScrollBarPolicy.ScrollBarAlwaysOff
|
QtScrollAlwaysOff = Qt.ScrollBarPolicy.ScrollBarAlwaysOff
|
||||||
QtScrollAsNeeded = Qt.ScrollBarPolicy.ScrollBarAsNeeded
|
QtScrollAsNeeded = Qt.ScrollBarPolicy.ScrollBarAsNeeded
|
||||||
|
|
||||||
# Other
|
|
||||||
|
|
||||||
QRegExUnicode = QRegularExpression.PatternOption.UseUnicodePropertiesOption
|
|
||||||
|
|
||||||
# Maps
|
# Maps
|
||||||
|
|
||||||
FONT_WEIGHTS: dict[int, int] = {
|
FONT_WEIGHTS: dict[int, int] = {
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ def testCoreTools_DocSearch(monkeypatch, mockGUI, fncPath, mockRnd, ipsumText):
|
|||||||
# Patterns
|
# Patterns
|
||||||
# ========
|
# ========
|
||||||
|
|
||||||
# Escape Using QRegularExpression
|
# Escape
|
||||||
assert search._buildPattern("[A-Za-z0-9_]+") == r"\[A\-Za\-z0\-9_\]\+"
|
assert search._buildPattern("[A-Za-z0-9_]+") == r"\[A\-Za\-z0\-9_\]\+"
|
||||||
|
|
||||||
# Whole Words
|
# Whole Words
|
||||||
|
|||||||
Reference in New Issue
Block a user