From abac5967b34d50d52cf0d7082a7ca3c06e287163 Mon Sep 17 00:00:00 2001 From: Mike Hendricks Date: Fri, 22 Dec 2023 18:19:50 -0600 Subject: [PATCH] WIP: Find Files --- preditor/gui/find_files.py | 170 ++++++++++++++++++++++++++++++++ preditor/gui/loggerwindow.py | 10 ++ preditor/gui/ui/find_files.ui | 134 +++++++++++++++++++++++++ preditor/gui/ui/loggerwindow.ui | 40 +++++++- 4 files changed, 352 insertions(+), 2 deletions(-) create mode 100644 preditor/gui/find_files.py create mode 100644 preditor/gui/ui/find_files.ui diff --git a/preditor/gui/find_files.py b/preditor/gui/find_files.py new file mode 100644 index 00000000..7571944d --- /dev/null +++ b/preditor/gui/find_files.py @@ -0,0 +1,170 @@ +from __future__ import absolute_import, print_function + +from collections import deque + +from Qt.QtCore import Qt +from Qt.QtWidgets import QApplication, QShortcut, QWidget + +from ..gui import loadUi + + +class FindFiles(QWidget): + def __init__(self, parent=None, managers=None, console=None): + super(FindFiles, self).__init__(parent=parent) + if managers is None: + managers = [] + self.managers = managers + self.console = console + + loadUi(__file__, self) + + self.uiCloseSCT = QShortcut( + Qt.Key_Escape, self, context=Qt.WidgetWithChildrenShortcut + ) + self.uiCloseSCT.activated.connect(self.hide) + + def activate(self): + """Called to make this widget ready for the user to interact with.""" + self.show() + self.uiFindTXT.setFocus() + + def insert_found_text(self, found_text, href, tool_tip): + cursor = self.console.textCursor() + # Insert hyperlink + fmt = cursor.charFormat() + fmt.setAnchor(True) + fmt.setAnchorHref(href) + fmt.setFontUnderline(True) + fmt.setToolTip(tool_tip) + cursor.insertText(found_text, fmt) + # Show the updated text output + QApplication.instance().processEvents() + + def insert_text(self, text): + cursor = self.console.textCursor() + fmt = cursor.charFormat() + fmt.setAnchor(False) + fmt.setAnchorHref('') + fmt.setFontUnderline(False) + fmt.setToolTip('') + cursor.insertText(text, fmt) + # Show the updated text output + QApplication.instance().processEvents() + + def indicate_results( + self, + line, + line_num, + find_text="undefined", + path="undefined", + workbox_id="undefined", + fmt="", + ): + href = ', {}, {}'.format(workbox_id, line_num) + tool_tip = "Open {} at line number {}".format(path, line_num) + find_len = len(find_text) + + start = 0 + line = fmt.format( + num=line_num, split=" " if line.find(find_text) == -1 else ":", line=line + ) + end = line.find(find_text) + count = 0 + while end != -1: + # insert prefix text + self.insert_text(line[start:end]) + # insert indicated text preserving case + self.insert_found_text(line[end : end + find_len], href, tool_tip) + start = end + find_len + end = line.find(find_text, start) + count += 1 + if count > 10: + print('break', start, end) + break + if end < find_len: + self.insert_text(line[start:]) + + def print_lines(self, start, *lines, info=None): + if info is None: + info = {} + info.setdefault("fmt", " {num: 4d}{split} {line}") + + # If start is negative, set it to zero + start = max(0, start) + for i, line in enumerate(lines): + # line = fmt.format(num=start + i, line=line) + # print(line) + self.indicate_results(line + "\n", start + i, **info) + + def search_file_simple(self, editor, path, workbox_id): + # print('search_file_simple', path) + context = self.uiContextSPN.value() + find_text = self.uiFindTXT.text() + # Ensure the editor text is loaded + editor.__show__() + raw_lines = editor.__text__().splitlines() + # Add enough padding to cover the number of lines in the file + padding = len(str(len(raw_lines))) + fmt = " {{num: >{}}}{{split}} {{line}}".format(padding) + + # https://stackoverflow.com/a/52009859 + pre_history = deque(maxlen=context) + post_history = None + post_line = None + first = True + for i, line in enumerate(raw_lines): + # Lines are 1 based indexes + i += 1 + info = dict(find_text=find_text, fmt=fmt, path=path, workbox_id=workbox_id) + if find_text in line: + ii = i - context + if first: + # Print the filename on the first find + print("# File: {}".format(path)) + first = False + else: + print( + " {dot: >{padding}} ".format( + dot='.' * len(str(ii)), padding=padding + ) + ) + self.print_lines(ii, *pre_history, line, info=info) + # print(" xxx:", *pre_history, line, sep='') + # Clear history so if two errors seen in close proximity, we don't + # echo some lines twice + pre_history.clear() + # Start recording the post context + post_history = deque(maxlen=context) + post_line = i + 1 + else: + # When deque reaches 25 lines, will automatically evict oldest + pre_history.append(line) + # Add the line to the post history after we find a result. + if post_history is not None: + post_history.append(line) + # Once we exceed context without finding another result, + # print the post text and stop tracking post_history + if len(post_history) >= context: + self.print_lines(post_line, *post_history, info=info) + post_history = None + post_line = None + + if post_history: + print('# --------') + self.print_lines(post_line, *post_history, info=info) + + def find(self): + find_text = self.uiFindTXT.text() + print("Find:", find_text) + + for manager in self.managers: + for ( + editor, + group_name, + tab_name, + group_index, + tab_index, + ) in manager.all_widgets(): + path = "/".join((group_name, tab_name)) + workbox_id = '{},{}'.format(group_index, tab_index) + self.search_file_simple(editor, path, workbox_id) diff --git a/preditor/gui/loggerwindow.py b/preditor/gui/loggerwindow.py index 3c29d127..e08bbf65 100644 --- a/preditor/gui/loggerwindow.py +++ b/preditor/gui/loggerwindow.py @@ -105,6 +105,11 @@ def __init__(self, parent, name=None, run_workbox=False, standalone=False): ) self.uiConsoleTOOLBAR.insertSeparator(self.uiRunSelectedACT) + # Configure Find in Workboxes + self.uiFindInWorkboxesWGT.hide() + self.uiFindInWorkboxesWGT.managers.append(self.uiWorkboxTAB) + self.uiFindInWorkboxesWGT.console = self.console() + # Initial configuration of the logToFile feature self._logToFilePath = None self._stds = None @@ -1023,6 +1028,11 @@ def showEvent(self, event): def show_workbox_options(self): self.uiWorkboxSTACK.setCurrentIndex(WorkboxPages.Options) + @Slot() + def show_find_in_workboxes(self): + """Ensure the find workboxes widget is visible and has focus.""" + self.uiFindInWorkboxesWGT.activate() + @Slot() def show_focus_name(self): model = GroupTabListItemModel(manager=self.uiWorkboxTAB) diff --git a/preditor/gui/ui/find_files.ui b/preditor/gui/ui/find_files.ui new file mode 100644 index 00000000..a7a05489 --- /dev/null +++ b/preditor/gui/ui/find_files.ui @@ -0,0 +1,134 @@ + + + uiFindFilesWGT + + + + 0 + 0 + 636 + 41 + + + + Form + + + + + + Find: + + + + + + + + + Regex + + + Regex + + + + + + + Case Sensitive + + + Case Sensitive + + + + + + + Context Lines + + + QAbstractSpinBox::PlusMinus + + + 2 + + + + + + + + + + + + Find + + + + + + + x + + + + + + + + + uiFindBTN + released() + uiFindFilesWGT + find() + + + 601 + 31 + + + 421 + 29 + + + + + uiFindTXT + returnPressed() + uiFindFilesWGT + find() + + + 488 + 23 + + + 501 + 65 + + + + + uiCloseBTN + released() + uiFindFilesWGT + hide() + + + 620 + 19 + + + 676 + 24 + + + + + + find() + + diff --git a/preditor/gui/ui/loggerwindow.ui b/preditor/gui/ui/loggerwindow.ui index 4fc142c3..37000c68 100644 --- a/preditor/gui/ui/loggerwindow.ui +++ b/preditor/gui/ui/loggerwindow.ui @@ -81,6 +81,9 @@ + + + @@ -322,6 +325,8 @@ + + @@ -951,6 +956,14 @@ at the indicated line in the specified text editor. Ctrl+Alt+Shift+R + + + Find in Workboxes + + + Ctrl+Shift+F + + @@ -969,6 +982,12 @@ at the indicated line in the specified text editor. QWidget
preditor.gui.editor_chooser.h
+ + FindFiles + QWidget +
preditor.gui.find_files.h
+ 1 +
@@ -1011,8 +1030,8 @@ at the indicated line in the specified text editor. update_workbox_stack() - 754 - 377 + 763 + 371 747 @@ -1020,11 +1039,28 @@ at the indicated line in the specified text editor. + + uiFindInWorkboxesACT + triggered() + PrEditorWindow + show_find_in_workboxes() + + + -1 + -1 + + + 397 + 202 + + + apply_options() reset_options() show_workbox_options() update_workbox_stack() + show_find_in_workboxes()