Skip to content

Commit

Permalink
reworked (hex-)editor search and implemented F14 & F15 hotkey for upw…
Browse files Browse the repository at this point in the history
…ards search
  • Loading branch information
SilenZcience committed Dec 7, 2024
1 parent 665dded commit f8bd3b6
Show file tree
Hide file tree
Showing 4 changed files with 416 additions and 196 deletions.
80 changes: 60 additions & 20 deletions cat_win/src/service/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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.
Expand All @@ -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 = ''
Expand All @@ -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
Expand Down Expand Up @@ -919,25 +925,41 @@ 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
max_y, _ = self.getxymax()
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:
Expand All @@ -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.
Expand All @@ -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 = '[]'
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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",
'',
Expand Down Expand Up @@ -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):
"""
Expand Down
166 changes: 0 additions & 166 deletions cat_win/src/service/helper/editorhelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ def initscr():
curses.initscr = initscr
except ImportError:
pass
import re


UNIFY_HOTKEYS = {
Expand Down Expand Up @@ -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()
Loading

0 comments on commit f8bd3b6

Please sign in to comment.