diff --git a/debug/tk_debug_window.py b/debug/tk_debug_window.py index df55c082..f8e4c614 100644 --- a/debug/tk_debug_window.py +++ b/debug/tk_debug_window.py @@ -1,5 +1,6 @@ import re import socket +import string import threading import tkinter as tk from datetime import datetime @@ -101,10 +102,12 @@ def __init__(self, *args, placeholder="", **kwargs): self.placeholder = placeholder self.user_has_interacted = False self.insert(0, self.placeholder) + self.word_pattern = re.compile(r'[\s\W]|$') self.config(fg='grey') - self.bind('', self.on_focus_in) self.bind('', self.on_focus_out) + self.bind('', self.on_focus_in) self.bind('', self.handle_ctrl_backspace) + self.bind('', self.handle_ctrl_delete) self.bind('', self.on_key_press) # Bind key press event def on_focus_in(self, event): @@ -117,6 +120,8 @@ def on_focus_out(self, event): self.insert(0, self.placeholder) self.config(fg='grey') self.user_has_interacted = False # Reset flag if entry is empty + else: + self.user_has_interacted = True def on_key_press(self, event): self.user_has_interacted = True # User has interacted when any key is pressed @@ -128,17 +133,38 @@ def handle_ctrl_backspace(self, event: tk.Event): # Get the current content of the entry and the cursor position content = self.get() cursor_pos = self.index(tk.INSERT) + + # If the last character before the cursor is a space or punctuation, delete it + if cursor_pos > 0 and (content[cursor_pos - 1] == ' ' or content[cursor_pos - 1] in string.punctuation): + self.delete(cursor_pos - 1, tk.INSERT) + return "break" # Prevent default behavior + # Find the start of the word to the left of the cursor pre_cursor = content[:cursor_pos] + match = self.word_pattern.search(pre_cursor[::-1]) # [\s\W]|$ matches spaces, punctuation, or end of string + word_start = cursor_pos - match.start() if match else 0 - # If the last character before the cursor is a space, delete it - if len(pre_cursor) > 0 and pre_cursor[-1] == ' ': - self.delete(cursor_pos - 1, tk.INSERT) + # Delete the word + self.delete(word_start, cursor_pos) + return "break" # Prevent default behavior + + def handle_ctrl_delete(self, event: tk.Event): + # Get the current content of the entry and the cursor position + content = self.get() + cursor_pos = self.index(tk.INSERT) + + # If the first character after the cursor is a space or punctuation, delete it + if len(content) > cursor_pos and (content[cursor_pos] == ' ' or content[cursor_pos] in string.punctuation): + self.delete(cursor_pos, cursor_pos + 1) return "break" # Prevent default behavior - word_start = pre_cursor.rfind(' ') + 1 if ' ' in pre_cursor else 0 + # Find the end of the word to the right of the cursor + post_cursor = content[cursor_pos:] + match = self.word_pattern.search(post_cursor) # [\s\W]|$ matches spaces, punctuation, or end of string + word_end = match.start() if match else len(post_cursor) + # Delete the word - self.delete(f"{word_start}", tk.INSERT) + self.delete(cursor_pos, cursor_pos + word_end) return "break" # Prevent default behavior @@ -197,6 +223,7 @@ def create_widgets(self): for mode in self.search_modes: mode.pack(side=tk.LEFT, padx=(5, 0)) self.search_entry.bind('', lambda event: self.console.next_occurrence()) + self.search_entry.bind('', lambda event: self.console.previous_occurrence()) def on_entry_changed(self, *args): if self.after_id: @@ -247,13 +274,9 @@ def set_filter( ): if global_search_str is not None and self.option_frame.global_search_frame.search_entry.user_has_interacted: self.global_search_str = global_search_str - elif global_search_str is None or not self.option_frame.global_search_frame.search_entry.user_has_interacted: - self.global_search_str = "" if logger_name is not None and self.option_frame.specific_search_frame.logger_entry.user_has_interacted: self.logger_name = logger_name - elif logger_name is None or not self.option_frame.specific_search_frame.logger_entry.user_has_interacted: - self.logger_name = "" if global_search_mode is not None: self.global_search_mode = global_search_mode @@ -296,6 +319,7 @@ def apply_filters(self): self.update_text_widget() def filter_log(self, log): + # print(self.global_search_str, self.global_search_mode, self.logger_name, self.log_level, self.and_above) if self.and_above: flag = log_levels[log.log_level] >= log_levels[self.log_level] else: @@ -353,10 +377,10 @@ def search_text(self): self.text_widget.see(first_occurrence[0]) self.next_occurrence() - def next_occurrence(self): + def prepare_occurrence_navigation(self): current_tags = self.text_widget.tag_ranges('found') if not current_tags: - return + return None, None # Ensure the 'current_found' tag exists with a blue background. self.text_widget.tag_config('current_found', background='#ADD8E6') @@ -370,6 +394,15 @@ def next_occurrence(self): # Convert the current cursor index to a comparable value. cursor_line, cursor_char = map(int, cursor_index.split('.')) + return current_tags, (cursor_line, cursor_char) + + def next_occurrence(self): + current_tags, cursor_position = self.prepare_occurrence_navigation() + if not current_tags or not cursor_position: + return + + cursor_line, cursor_char = cursor_position + for i in range(0, len(current_tags), 2): tag_start = current_tags[i] tag_end = current_tags[i + 1] @@ -391,6 +424,34 @@ def next_occurrence(self): self.text_widget.see(str(current_tags[0])) self.text_widget.tag_add('current_found', current_tags[0], current_tags[1]) + def previous_occurrence(self): + current_tags, cursor_position = self.prepare_occurrence_navigation() + if not current_tags or not cursor_position: + return + + cursor_line, cursor_char = cursor_position + + for i in range(len(current_tags) - 2, -1, -2): + tag_start = current_tags[i] + tag_end = current_tags[i + 1] + + # Convert tag start index to comparable values. + tag_start_line, tag_start_char = map(int, str(tag_start).split('.')) + + # Check if the tag start is less than the cursor position. + if tag_start_line < cursor_line or (tag_start_line == cursor_line and tag_start_char < cursor_char): + self.text_widget.mark_set(tk.INSERT, tag_start) + self.text_widget.see(tag_start) + + # Apply the 'current_found' tag to the current occurrence. + self.text_widget.tag_add('current_found', tag_start, tag_end) + break + else: + # Wrap to the last tag if no previous tag is found. + self.text_widget.mark_set(tk.INSERT, str(current_tags[-2])) + self.text_widget.see(str(current_tags[-2])) + self.text_widget.tag_add('current_found', current_tags[-2], current_tags[-1]) + class SpecificSearchFrame(tk.Frame): def __init__(self, parent, root): @@ -460,7 +521,8 @@ def on_entry_changed(self, *args): self.after_id = self.root.after(250, self.apply_logger_entry_var) def apply_logger_entry_var(self): - self.console.set_filter(logger_name=self.logger_entry_var.get()) + if self.logger_entry.user_has_interacted: + self.console.set_filter(logger_name=self.logger_entry_var.get()) self.after_id = None @@ -517,6 +579,18 @@ def __init__(self): self.bind('', self.focus_search) self.bind('', self.focus_search) + self.bind('', lambda event: self.menu_bar.export_filtered_logs()) + self.bind('', lambda event: self.menu_bar.export_filtered_logs()) + + self.bind('', lambda event: self.menu_bar.export_all_logs()) + self.bind('', lambda event: self.menu_bar.export_all_logs()) + + self.bind('', lambda event: self.console.clear_logs()) + self.bind('', lambda event: self.console.clear_logs()) + + self.bind('', self.focus_logger) + self.bind('', self.focus_logger) + def create_widgets(self): self.console.pack(side=tk.TOP, expand=True, fill='both') self.options_frame.pack(side=tk.BOTTOM, fill='x', expand=False) @@ -528,6 +602,9 @@ def get_console(self): def focus_search(self, event): self.options_frame.global_search_frame.search_entry.focus() + def focus_logger(self, event): + self.options_frame.specific_search_frame.logger_entry.focus() + def client_handler(client_socket, console: Console): buffer = [] @@ -569,4 +646,4 @@ def listen_for_clients(console: Console): if __name__ == "__main__": root = MainWindow() threading.Thread(target=listen_for_clients, daemon=True, args=(root.get_console(),)).start() - root.mainloop() \ No newline at end of file + root.mainloop()