From f8bd3b6bf959c0c5caa087eb197b7306302d9812 Mon Sep 17 00:00:00 2001 From: Silas Kraume Date: Sat, 7 Dec 2024 03:45:12 +0100 Subject: [PATCH] reworked (hex-)editor search and implemented F14 & F15 hotkey for upwards search --- cat_win/src/service/editor.py | 80 +++-- cat_win/src/service/helper/editorhelper.py | 166 --------- .../src/service/helper/editorsearchhelper.py | 325 ++++++++++++++++++ cat_win/src/service/hexeditor.py | 41 ++- 4 files changed, 416 insertions(+), 196 deletions(-) create mode 100644 cat_win/src/service/helper/editorsearchhelper.py diff --git a/cat_win/src/service/editor.py b/cat_win/src/service/editor.py index 65d7b20..1a6d8b0 100644 --- a/cat_win/src/service/editor.py +++ b/cat_win/src/service/editor.py @@ -16,7 +16,8 @@ from cat_win.src.const.escapecodes import ESC_CODE from cat_win.src.const.regex import compile_re -from cat_win.src.service.helper.editorhelper import History, Position, _SearchIter, frepr, \ +from cat_win.src.service.helper.editorsearchhelper import _SearchIterBase, search_iter_factory +from cat_win.src.service.helper.editorhelper import History, Position, frepr, \ UNIFY_HOTKEYS, KEY_HOTKEYS, ACTION_HOTKEYS, SCROLL_HOTKEYS, MOVE_HOTKEYS, \ SELECT_HOTKEYS, HISTORY_HOTKEYS, INDENT_HOTKEYS, FUNCTION_HOTKEYS, HEX_BYTE_KEYS from cat_win.src.service.helper.environment import on_windows_os @@ -427,7 +428,9 @@ def _scroll_key_end(self) -> None: self.wpos.col = max(max_line+1-max_x, 0) def _move_key_home(self) -> None: - self.cpos.col = 0 + n_col = len(self.window_content[self.cpos.row]) + n_col-= len(self.window_content[self.cpos.row].lstrip()) + self.cpos.col = n_col if n_col < self.cpos.col else 0 def _move_key_ctl_home(self) -> None: self.cpos.row = 0 @@ -494,7 +497,7 @@ def _key_replace_search_(self, r_this: str, r_with: str) -> str: self.cpos.col -= len(r_with) return self._key_replace_search(r_this=r_with, r_with=r_this) - def _replace_search(self, r_this, r_with: str, search_: _SearchIter) -> None: + def _replace_search(self, r_this, r_with: str, search_: _SearchIterBase) -> None: pre_cpos = self.cpos.get_pos() pre_spos = self.spos.get_pos() if not isinstance(r_this, str): @@ -847,7 +850,7 @@ def _action_jump(self) -> bool: break return True - def _action_find(self, find_next: bool = False) -> bool: + def _action_find(self, find_next: int = 0) -> bool: """ handles the find in editor action. @@ -859,7 +862,7 @@ def _action_find(self, find_next: bool = False) -> bool: search_regex = not isinstance(self.search, str) wchar, sub_s, tmp_error = '', '', '' - key = b'_key_enter' + key, running = b'_key_enter', False while str(wchar) != ESC_CODE: if not find_next: pre_s = '' @@ -873,6 +876,9 @@ def _action_find(self, find_next: bool = False) -> bool: tmp_error ) wchar, key = next(self.get_char) + elif running: + break + running = True if key in ACTION_HOTKEYS: if key in [b'_action_quit', b'_action_interrupt']: break @@ -919,11 +925,18 @@ def _action_find(self, find_next: bool = False) -> bool: cpos_tmp, spos_tmp = self.cpos.get_pos(), self.spos.get_pos() try: sel_pos_a, sel_pos_b = self.selected_area - if self.selecting and self.cpos.get_pos() == sel_pos_b: + if self.selecting and self.cpos.get_pos() == sel_pos_b and find_next >= 0: self.cpos.set_pos(sel_pos_a) self.spos.set_pos(sel_pos_b) + elif self.selecting and self.cpos.get_pos() == sel_pos_a and find_next < 0: + self.cpos.set_pos(sel_pos_b) + self.spos.set_pos(sel_pos_a) try: - search = _SearchIter(self, 1-self.selecting) + search = search_iter_factory( + self, + 1-(self.selecting and find_next >= 0), + downwards=(find_next >= 0) + ) except ValueError as exc: tmp_error = str(exc) continue @@ -931,13 +944,22 @@ def _action_find(self, find_next: bool = False) -> bool: cpos = next(search) self.search_items[cpos] = search.s_len if not self.selecting: - self.cpos.set_pos((max(cpos[0]-max_y, 0), 0)) - search = _SearchIter(self, 1) + if find_next >= 0: + self.cpos.set_pos((max(cpos[0]-max_y, 0), 0)) + else: + self.cpos.set_pos((min(cpos[0]+max_y, len(self.window_content)-1), + len(self.window_content[cpos[0]]))) + search = search_iter_factory( + self, + 1, + downwards=(find_next >= 0) + ) for search_pos in search: if search_pos[0] < cpos[0]-max_y or search_pos[0] > cpos[0]+max_y: break self.cpos.set_pos(search_pos) - self.search_items[search_pos] = search.s_len + if search.s_len: + self.search_items[search_pos] = search.s_len self.cpos.set_pos(cpos) break except StopIteration: @@ -948,7 +970,7 @@ def _action_find(self, find_next: bool = False) -> bool: tmp_error+= ' within the selection!' if self.selecting else '!' return True - def _action_replace(self, replace_next: bool = False) -> bool: + def _action_replace(self, replace_next: int = 0) -> bool: """ handles the replace in editor action. @@ -960,7 +982,7 @@ def _action_replace(self, replace_next: bool = False) -> bool: replace_all = False wchar, sub_s, tmp_error = '', '', '' - key = b'_key_enter' + key, running = b'_key_enter', False while str(wchar) != ESC_CODE: if not replace_next: pre_s = '[]' @@ -975,6 +997,9 @@ def _action_replace(self, replace_next: bool = False) -> bool: tmp_error ) wchar, key = next(self.get_char) + elif running: + break + running = True if key in ACTION_HOTKEYS: if key in [b'_action_quit', b'_action_interrupt']: break @@ -1022,18 +1047,29 @@ def _action_replace(self, replace_next: bool = False) -> bool: pass cpos_tmp, spos_tmp = self.cpos.get_pos(), self.spos.get_pos() sel_pos_a, sel_pos_b = self.selected_area - if self.selecting and self.cpos.get_pos() == sel_pos_b: + if self.selecting and self.cpos.get_pos() == sel_pos_b and replace_next >= 0: self.cpos.set_pos(sel_pos_a) self.spos.set_pos(sel_pos_b) + elif self.selecting and self.cpos.get_pos() == sel_pos_a and replace_next < 0: + self.cpos.set_pos(sel_pos_b) + self.spos.set_pos(sel_pos_a) try: - search = _SearchIter(self, 0, True) + search = search_iter_factory( + self, + 0, + True, + downwards=(replace_next >= 0) + ) except ValueError as exc: tmp_error = str(exc) continue for search_pos in search: self.cpos.set_pos(search_pos) - self.search_items[search_pos] = search.r_len + if search.r_len: + self.search_items[search_pos] = search.r_len self._replace_search(self.search, self.replace, search) + if replace_next < 0: + self.cpos.set_pos(search_pos) if not replace_all: break else: @@ -1233,7 +1269,7 @@ def _function_help(self) -> None: f"{'^T':<{coff}}transform", f"{'^N':<{coff}}insert byte sequence", f"{'^F':<{coff}}find strings or patterns", - f"{'F3':<{coff}}find next", + f"{'(Shift-)F3':<{coff}}find next/(previous)", f"{'^P':<{coff}}replace string or pattern", f"{'F2':<{coff}}replace next", '', @@ -1267,18 +1303,22 @@ def _function_help(self) -> None: def _function_search(self) -> None: if not self.search: return - self._action_find(True) + self._action_find(1) def _function_search_r(self) -> None: - self.error_bar = 'not implemented yet!' + if not self.search: + return + self._action_find(-1) def _function_replace(self) -> None: if not self.search: return - self._action_replace(True) + self._action_replace(1) def _function_replace_r(self) -> None: - self.error_bar = 'not implemented yet!' + if not self.search: + return + self._action_replace(-1) def _get_new_char(self): """ diff --git a/cat_win/src/service/helper/editorhelper.py b/cat_win/src/service/helper/editorhelper.py index 564cd24..a0ab802 100644 --- a/cat_win/src/service/helper/editorhelper.py +++ b/cat_win/src/service/helper/editorhelper.py @@ -19,7 +19,6 @@ def initscr(): curses.initscr = initscr except ImportError: pass -import re UNIFY_HOTKEYS = { @@ -483,168 +482,3 @@ def redo(self, editor: object) -> None: break # print('Redo ', list(map(str, self._stack_undo))) # print(' ', list(map(str, self._stack_redo))) - - -class _SearchIter: - """ - inspired by: - # -------- https://github.com/asottile/babi -------- # - """ - def __init__(self, editor, offset: int, replacing: bool = False) -> None: - self.editor = editor - self.offset = offset - self.empty_match_offset = 0 - self.replacing = replacing - self.wrapped = False - self._start_y = editor.cpos.row - self._start_x = editor.cpos.col + offset - self.yielded_result = False - self.search = self.editor.search - self.s_len = len(self.search) if isinstance(self.search, str) else 0 - self.replace = self.editor.replace - self.r_len = len(self.replace) - - def __iter__(self): - return self - - def _get_next_pos(self, line: str): - if isinstance(self.search, str): - return line.find(self.search) - if len(line) == 0: - return -1 - match_ = self.search.search(line) - if match_ is None: - return -1 - self.s_len = match_.end() - match_.start() - self.empty_match_offset = int(self.s_len == 0 == self.offset) - try: - self.replace = self.search.sub(self.editor.replace, line[match_.start():match_.end()]) - self.r_len = len(self.replace) - except re.error: - self.replace = self.editor.replace - self.r_len = len(self.replace) - return match_.start() - - def _stop_if_past_original(self, row: int, f_col: int) -> tuple: - if self.wrapped and ( - row > self._start_y or - row == self._start_y and f_col >= self._start_x - ): - raise StopIteration() - if self.editor.selecting and ( - (row, f_col) >= self.editor.spos.get_pos() - ): - raise StopIteration() - if self.replacing and self.wrapped and row == self._start_y: - # offset highlights and start positions - self.editor.search_items = { - ((r, c+self.r_len-self.s_len) if r == row and c >= self._start_x else (r, c)): l - for (r, c), l in self.editor.search_items.items() - } - self._start_x += self.r_len-self.s_len - self.yielded_result = True - return (row, f_col) - - def __next__(self) -> tuple: - row = self.editor.cpos.row - col = self.editor.cpos.col + self.offset + self.empty_match_offset - - found_pos = self._get_next_pos(self.editor.window_content[row][col:]) - if found_pos >= 0: - return self._stop_if_past_original(row, found_pos+col) - if self.wrapped: - for line_y in range(row + 1, self._start_y + 1): - found_pos = self._get_next_pos(self.editor.window_content[line_y]) - if found_pos >= 0: - return self._stop_if_past_original(line_y, found_pos) - else: - content_len = -1 - while content_len != len(self.editor.window_content): - content_len = len(self.editor.window_content) - for line_y in range(row + 1, len(self.editor.window_content)): - found_pos = self._get_next_pos(self.editor.window_content[line_y]) - if found_pos >= 0: - return self._stop_if_past_original(line_y, found_pos) - self.editor._build_file_upto(content_len+30) - if self.editor.selecting: - raise StopIteration() - self.wrapped = True - for line_y in range(0, self._start_y + 1): - found_pos = self._get_next_pos(self.editor.window_content[line_y]) - if found_pos >= 0: - return self._stop_if_past_original(line_y, found_pos) - raise StopIteration() - -class _SearchIterHex: - """ - inspired by: - # -------- https://github.com/asottile/babi -------- # - """ - def __init__(self, editor, offset: int) -> None: - self.editor = editor - self.offset = offset - self.wrapped = False - self._start_y = editor.cpos.row - self._start_x = editor.cpos.col + offset - self.yielded_result = False - self.search = self.editor.search - self.s_len = len(self.search) - - def __iter__(self): - return self - - def _get_next_pos(self, row: int, col: int = 0): - search_in = ''.join(self.editor._get_current_state_row(row)[col:]) - search_wrap = '' - while len(self.search)-1 > len(search_wrap) and row < len(self.editor.hex_array)-1: - row += 1 - search_wrap += ''.join(self.editor._get_current_state_row(row)) - search_in += search_wrap[:len(self.search)-1] - - for i in range(0, len(search_in), 2): - if search_in[i:].startswith(self.search): - return i//2 - return -1 - - def _stop_if_past_original(self, row: int, f_col: int) -> tuple: - if self.wrapped and ( - row > self._start_y or - row == self._start_y and f_col >= self._start_x - ): - raise StopIteration() - if self.editor.selecting and ( - (row, f_col) >= self.editor.spos.get_pos() - ): - raise StopIteration() - self.yielded_result = True - return (row, f_col) - - def __next__(self) -> tuple: - row = self.editor.cpos.row - col = self.editor.cpos.col + self.offset - - found_pos = self._get_next_pos(row, col) - if found_pos >= 0: - return self._stop_if_past_original(row, found_pos+col) - if self.wrapped: - for line_y in range(row + 1, self._start_y + 1): - found_pos = self._get_next_pos(line_y) - if found_pos >= 0: - return self._stop_if_past_original(line_y, found_pos) - else: - content_len = -1 - while content_len != len(self.editor.hex_array): - content_len = len(self.editor.hex_array) - for line_y in range(row + 1, len(self.editor.hex_array)): - found_pos = self._get_next_pos(line_y) - if found_pos >= 0: - return self._stop_if_past_original(line_y, found_pos) - self.editor._build_file_upto(content_len+30) - if self.editor.selecting: - raise StopIteration() - self.wrapped = True - for line_y in range(0, self._start_y + 1): - found_pos = self._get_next_pos(line_y) - if found_pos >= 0: - return self._stop_if_past_original(line_y, found_pos) - raise StopIteration() diff --git a/cat_win/src/service/helper/editorsearchhelper.py b/cat_win/src/service/helper/editorsearchhelper.py new file mode 100644 index 0000000..82d2eae --- /dev/null +++ b/cat_win/src/service/helper/editorsearchhelper.py @@ -0,0 +1,325 @@ +""" +editorsearchhelper +""" +import re + + +class _SearchIterBase: + def __init__(self, editor, offset: int, replacing: bool = False) -> None: + self.editor = editor + self.offset = offset + self.empty_match_offset = 0 + self.replacing = replacing + self.wrapped = False + self._start_y = editor.cpos.row + self._start_x = editor.cpos.col + self.yielded_result = False + self.search = self.editor.search + self.s_len = len(self.search) if isinstance(self.search, str) else 0 + self.replace = self.editor.replace + self.r_len = len(self.replace) + + def __iter__(self): + return self + + def _stop_if_past_original(self, row: int, f_col: int) -> tuple: + raise NotImplementedError + + def __next__(self) -> tuple: + raise NotImplementedError + +class _SearchIterUp(_SearchIterBase): + def _get_next_pos(self, line: str, col: int = None): + if col is not None and col < 0: + return -1 + if isinstance(self.search, str): + if col is not None: + col += self.s_len + found_pos = line.rfind(self.search, 0, col) + return found_pos + if col is None: + col = len(line) + match_ = None + match_start = None + for i in range(col, -1, -1): + m_ = self.search.search(line[i:]) + if m_ is not None and m_.start() == 0: + match_ = m_ + match_start = i + break + if match_ is None: + return -1 + self.s_len = match_.end()# - match_.start() + self.empty_match_offset = int(self.s_len == 0 == self.offset) + try: + self.replace = self.search.sub(self.editor.replace, + line[match_start:match_start+self.s_len]) + self.r_len = len(self.replace) + except re.error: + self.replace = self.editor.replace + self.r_len = len(self.replace) + return match_start + + def _stop_if_past_original(self, row: int, f_col: int) -> tuple: + if self.wrapped and ( + row < self._start_y or + row == self._start_y and f_col < self._start_x + ): + raise StopIteration() + if self.editor.selecting and ( + (row, f_col) < self.editor.selected_area[0] + ): + raise StopIteration() + if self.replacing and row == self._start_y: + # offset highlights and start positions + self.editor.search_items = { + ((r, c+self.r_len-self.s_len) if r == row and c >= f_col else (r, c)): l + for (r, c), l in self.editor.search_items.items() + } + if not self.wrapped: + self._start_x += self.r_len-self.s_len + self.yielded_result = True + return (row, f_col) + + def __next__(self) -> tuple: + row = self.editor.cpos.row + col = self.editor.cpos.col - self.offset - self.empty_match_offset + + found_pos = self._get_next_pos(self.editor.window_content[row], col) + if found_pos >= 0: + return self._stop_if_past_original(row, found_pos) + if self.wrapped: + for line_y in range(row - 1, self._start_y - 1, -1): + found_pos = self._get_next_pos(self.editor.window_content[line_y]) + if found_pos >= 0: + return self._stop_if_past_original(line_y, found_pos) + else: + for line_y in range(row-1, -1, -1): + found_pos = self._get_next_pos(self.editor.window_content[line_y]) + if found_pos >= 0: + return self._stop_if_past_original(line_y, found_pos) + self.editor._build_file() + if self.editor.selecting: + raise StopIteration() + self.wrapped = True + for line_y in range(len(self.editor.window_content)-1, self._start_y-1, -1): + found_pos = self._get_next_pos(self.editor.window_content[line_y]) + if found_pos >= 0: + return self._stop_if_past_original(line_y, found_pos) + raise StopIteration() + +class _SearchIterDown(_SearchIterBase): + def _get_next_pos(self, line: str, col: int = None): + if col is not None and col > len(line): + return -1 + line = line[col:] + if isinstance(self.search, str): + return line.find(self.search) + match_ = self.search.search(line) + if match_ is None: + return -1 + self.s_len = match_.end() - match_.start() + self.empty_match_offset = int(self.s_len == 0 == self.offset) + try: + self.replace = self.search.sub(self.editor.replace, line[match_.start():match_.end()]) + self.r_len = len(self.replace) + except re.error: + self.replace = self.editor.replace + self.r_len = len(self.replace) + return match_.start() + + def _stop_if_past_original(self, row: int, f_col: int) -> tuple: + if self.wrapped and ( + row > self._start_y or + row == self._start_y and f_col > self._start_x - (not self.offset) + ): + raise StopIteration() + if self.editor.selecting and ( + (row, f_col) >= self.editor.selected_area[1] + ): + raise StopIteration() + if self.replacing and row == self._start_y: + # offset highlights and start positions + self.editor.search_items = { + ((r, c+self.r_len-self.s_len) if r == row and c >= f_col else (r, c)): l + for (r, c), l in self.editor.search_items.items() + } + if self.wrapped: + self._start_x += self.r_len-self.s_len + self.yielded_result = True + return (row, f_col) + + def __next__(self) -> tuple: + row = self.editor.cpos.row + col = self.editor.cpos.col + self.offset + self.empty_match_offset + + found_pos = self._get_next_pos(self.editor.window_content[row], col) + if found_pos >= 0: + return self._stop_if_past_original(row, found_pos+col) + if self.wrapped: + for line_y in range(row + 1, self._start_y + 1): + found_pos = self._get_next_pos(self.editor.window_content[line_y]) + if found_pos >= 0: + return self._stop_if_past_original(line_y, found_pos) + else: + content_len = -1 + while content_len != len(self.editor.window_content): + content_len = len(self.editor.window_content) + for line_y in range(row + 1, len(self.editor.window_content)): + found_pos = self._get_next_pos(self.editor.window_content[line_y]) + if found_pos >= 0: + return self._stop_if_past_original(line_y, found_pos) + self.editor._build_file_upto(content_len+30) + if self.editor.selecting: + raise StopIteration() + self.wrapped = True + for line_y in range(0, self._start_y + 1): + found_pos = self._get_next_pos(self.editor.window_content[line_y]) + if found_pos >= 0: + return self._stop_if_past_original(line_y, found_pos) + raise StopIteration() + + +def search_iter_factory(*args, downwards: bool = True) -> _SearchIterBase: + if downwards: + return _SearchIterDown(*args) + return _SearchIterUp(*args) + + +class _SearchIterHexBase: + def __init__(self, editor, offset: int) -> None: + self.editor = editor + self.offset = offset + self.wrapped = False + self._start_y = editor.cpos.row + self._start_x = editor.cpos.col + offset + self.yielded_result = False + self.search = self.editor.search + self.s_len = len(self.search) + + def __iter__(self): + return self + + def _stop_if_past_original(self, row: int, f_col: int) -> tuple: + raise NotImplementedError + + def __next__(self) -> tuple: + raise NotImplementedError + + +class _SearchIterHexUp(_SearchIterHexBase): + def _get_next_pos(self, row: int, col: int = None): + search_in = ''.join(self.editor._get_current_state_row(row)) + if col is None: + col = len(search_in)//2 + search_wrap = '' + while self.s_len-1 > len(search_wrap) and row < len(self.editor.hex_array)-1: + row += 1 + search_wrap += ''.join(self.editor._get_current_state_row(row)) + search_in += search_wrap[:self.s_len-1] + for i in range(col*2, -1, -2): + if search_in[i:].startswith(self.search): + return i//2 + return -1 + + def _stop_if_past_original(self, row: int, f_col: int) -> tuple: + if self.wrapped and ( + row < self._start_y or + row == self._start_y and f_col < self._start_x + ): + raise StopIteration() + if self.editor.selecting and ( + (row, f_col) < self.editor.selected_area[0] + ): + raise StopIteration() + self.yielded_result = True + return (row, f_col) + + def __next__(self) -> tuple: + row = self.editor.cpos.row + col = self.editor.cpos.col - self.offset + + found_pos = self._get_next_pos(row, col) + if found_pos >= 0: + return self._stop_if_past_original(row, found_pos) + if self.wrapped: + for line_y in range(row - 1, self._start_y - 1, -1): + found_pos = self._get_next_pos(line_y) + if found_pos >= 0: + return self._stop_if_past_original(line_y, found_pos) + else: + for line_y in range(row-1, -1, -1): + found_pos = self._get_next_pos(line_y) + if found_pos >= 0: + return self._stop_if_past_original(line_y, found_pos) + self.editor._build_file() + if self.editor.selecting: + raise StopIteration() + self.wrapped = True + for line_y in range(len(self.editor.hex_array)-1, self._start_y-1, -1): + found_pos = self._get_next_pos(line_y) + if found_pos >= 0: + return self._stop_if_past_original(line_y, found_pos) + raise StopIteration() + +class _SearchIterHexDown(_SearchIterHexBase): + def _get_next_pos(self, row: int, col: int = 0): + search_in = ''.join(self.editor._get_current_state_row(row)[col:]) + search_wrap = '' + while self.s_len-1 > len(search_wrap) and row < len(self.editor.hex_array)-1: + row += 1 + search_wrap += ''.join(self.editor._get_current_state_row(row)) + search_in += search_wrap[:self.s_len-1] + + for i in range(0, len(search_in)-len(self.search)+1, 2): + if search_in[i:].startswith(self.search): + return i//2 + return -1 + + def _stop_if_past_original(self, row: int, f_col: int) -> tuple: + if self.wrapped and ( + row > self._start_y or + row == self._start_y and f_col >= self._start_x + ): + raise StopIteration() + if self.editor.selecting and ( + (row, f_col) > self.editor.selected_area[1] + ): + raise StopIteration() + self.yielded_result = True + return (row, f_col) + + def __next__(self) -> tuple: + row = self.editor.cpos.row + col = self.editor.cpos.col + self.offset + + found_pos = self._get_next_pos(row, col) + if found_pos >= 0: + return self._stop_if_past_original(row, found_pos+col) + if self.wrapped: + for line_y in range(row + 1, self._start_y + 1): + found_pos = self._get_next_pos(line_y) + if found_pos >= 0: + return self._stop_if_past_original(line_y, found_pos) + else: + content_len = -1 + while content_len != len(self.editor.hex_array): + content_len = len(self.editor.hex_array) + for line_y in range(row + 1, len(self.editor.hex_array)): + found_pos = self._get_next_pos(line_y) + if found_pos >= 0: + return self._stop_if_past_original(line_y, found_pos) + self.editor._build_file_upto(content_len+30) + if self.editor.selecting: + raise StopIteration() + self.wrapped = True + for line_y in range(0, self._start_y + 1): + found_pos = self._get_next_pos(line_y) + if found_pos >= 0: + return self._stop_if_past_original(line_y, found_pos) + raise StopIteration() + +def search_iter_hex_factory(*args, downwards: bool = True) -> _SearchIterHexBase: + if downwards: + return _SearchIterHexDown(*args) + return _SearchIterHexUp(*args) diff --git a/cat_win/src/service/hexeditor.py b/cat_win/src/service/hexeditor.py index d7d3d4d..86e8419 100644 --- a/cat_win/src/service/hexeditor.py +++ b/cat_win/src/service/hexeditor.py @@ -13,7 +13,8 @@ import sys from cat_win.src.const.escapecodes import ESC_CODE -from cat_win.src.service.helper.editorhelper import Position, _SearchIterHex, frepr, \ +from cat_win.src.service.helper.editorsearchhelper import search_iter_hex_factory +from cat_win.src.service.helper.editorhelper import Position, frepr, \ UNIFY_HOTKEYS, KEY_HOTKEYS, ACTION_HOTKEYS, MOVE_HOTKEYS, SELECT_HOTKEYS, \ FUNCTION_HOTKEYS, HEX_BYTE_KEYS from cat_win.src.service.helper.environment import on_windows_os @@ -515,7 +516,7 @@ def _action_jump(self) -> bool: break return True - def _action_find(self, find_next: bool = False) -> bool: + def _action_find(self, find_next: int = 0) -> bool: """ handles the find in editor action. @@ -526,7 +527,7 @@ def _action_find(self, find_next: bool = False) -> bool: search_byte_mode, bm_ind = True, '0x' sub_s_encoded = self.search wchar, sub_s, tmp_error= '', '', '' - key = b'_key_enter' + key, running = b'_key_enter', False while str(wchar) != ESC_CODE: if not find_next: pre_s = '' @@ -542,6 +543,9 @@ def _action_find(self, find_next: bool = False) -> bool: tmp_error ) wchar, key = self._get_next_char() + elif running: + break + running = True if key in ACTION_HOTKEYS: if key in [b'_action_quit', b'_action_interrupt']: break @@ -597,11 +601,18 @@ def _action_find(self, find_next: bool = False) -> bool: cpos_tmp, spos_tmp = self.cpos.get_pos(), self.spos.get_pos() try: sel_pos_a, sel_pos_b = self.selected_area - if self.selecting and self.cpos.get_pos() == sel_pos_b: + if self.selecting and self.cpos.get_pos() == sel_pos_b and find_next >= 0: self.cpos.set_pos(sel_pos_a) self.spos.set_pos(sel_pos_b) + elif self.selecting and self.cpos.get_pos() == sel_pos_a and find_next < 0: + self.cpos.set_pos(sel_pos_b) + self.spos.set_pos(sel_pos_a) try: - search = _SearchIterHex(self, 1-self.selecting) + search = search_iter_hex_factory( + self, + 1-self.selecting, + downwards=(find_next >= 0) + ) except ValueError as exc: tmp_error = str(exc) continue @@ -609,8 +620,16 @@ def _action_find(self, find_next: bool = False) -> bool: cpos = next(search) self.search_items[cpos] = search.s_len if not self.selecting: - self.cpos.set_pos((max(cpos[0]-max_y, 0), 0)) - search = _SearchIterHex(self, 1) + if find_next >= 0: + self.cpos.set_pos((max(cpos[0]-max_y, 0), 0)) + else: + self.cpos.set_pos((min(cpos[0]+max_y, len(self.hex_array)-1), + len(self.hex_array[cpos[0]])-1)) + search = search_iter_hex_factory( + self, + 1, + downwards=(find_next >= 0) + ) for search_pos in search: if search_pos[0] < cpos[0]-max_y or search_pos[0] > cpos[0]+max_y: break @@ -790,7 +809,7 @@ def _function_help(self) -> None: f"{'^E':<{coff}}jump to offset", f"{'^N':<{coff}}insert text sequence", f"{'^F':<{coff}}find bytes or strings", - f"{'F3':<{coff}}find next", + f"{'(Shift+)F3':<{coff}}find next/(previous)", '', f"{'Space,>,<':<{coff}}insert new byte", '', @@ -822,10 +841,12 @@ def _function_help(self) -> None: def _function_search(self) -> None: if not self.search: return - self._action_find(True) + self._action_find(1) def _function_search_r(self) -> None: - self.error_bar = 'not implemented yet!' + if not self.search: + return + self._action_find(-1) def _get_next_char(self) -> tuple: """