Skip to content

Commit

Permalink
highlight surrounding searches in editor
Browse files Browse the repository at this point in the history
  • Loading branch information
SilenZcience committed Nov 27, 2024
1 parent 74e77f1 commit 53f532d
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 19 deletions.
57 changes: 44 additions & 13 deletions cat_win/src/service/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def __init__(self, file: Path, display_name: str) -> None:
self.special_chars: dict = {}
self.search = '' # str | re.Pattern
self.replace = ''
self.search_items = []
self.search_items: dict = {}

self.status_bar_size = 1
self.error_bar = ''
Expand Down Expand Up @@ -880,6 +880,8 @@ def _action_find(self) -> bool:
sub_s += wchar
elif key == b'_key_enter':
self.search = sub_s if sub_s else self.search
if not self.search:
break
if Editor.unicode_escaped_search and sub_s:
try:
self.search = sub_s.encode().decode('unicode_escape').encode('latin-1').decode()
Expand All @@ -902,14 +904,25 @@ def _action_find(self) -> bool:
except ValueError as exc:
tmp_error = str(exc)
continue
self.cpos.set_pos(next(search))
self.search_items.append((*self.cpos.get_pos(), search.s_len))
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)
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
self.cpos.set_pos(cpos)
break
except StopIteration:
if self.selecting:
self.cpos.set_pos(cpos_tmp)
self.spos.set_pos(spos_tmp)
tmp_error = 'no matches were found!'
tmp_error = 'no matches were found'
tmp_error+= ' within the selection!' if self.selecting else '!'
return True

def _action_replace(self) -> bool:
Expand Down Expand Up @@ -983,22 +996,25 @@ def _action_replace(self) -> bool:
self.cpos.set_pos(sel_pos_a)
self.spos.set_pos(sel_pos_b)
try:
search = _SearchIter(self, 0)
search = _SearchIter(self, 0, True)
except ValueError as exc:
tmp_error = str(exc)
continue
for found_row, found_col in search:
self.cpos.set_pos((found_row,found_col))
self.search_items.append((self.cpos.row, self.cpos.col, search.r_len))
for search_pos in search:
self.cpos.set_pos(search_pos)
self.search_items[search_pos] = search.r_len
self._replace_search(self.search, self.replace, search)
if not replace_all:
break
else:
self.cpos.col -= search.r_len
if self.selecting:
self.cpos.set_pos(cpos_tmp)
self.spos.set_pos(spos_tmp)
if search.yielded_result:
break
tmp_error = 'no matches were found!'
tmp_error = 'no matches were found'
tmp_error+= ' within the selection!' if self.selecting else '!'
return True

def _action_reload(self) -> bool:
Expand Down Expand Up @@ -1290,13 +1306,28 @@ def _render_scr(self) -> None:
self.curse_window.addch(row, col, '�', self._get_color(3))
self.curse_window.clrtoeol()
self.curse_window.move(row+1, 0)
for row, col, length in self.search_items:

for (row, col), length in self.search_items.items():
if row < self.wpos.row or row >= self.wpos.row+max_y:
continue
if col+length < self.wpos.col or col >= self.wpos.col+max_x:
continue
self.curse_window.chgat(row-self.wpos.row, col-self.wpos.col,
length, self._get_color(6))
if col < self.wpos.col:
length -= self.wpos.col - col
col = self.wpos.col
self.curse_window.chgat(
row-self.wpos.row,
col-self.wpos.col,
length,
self._get_color(6)
)
if self.cpos.get_pos() in self.search_items:
self.curse_window.chgat(
self.cpos.row-self.wpos.row,
self.cpos.col-self.wpos.col,
self.search_items[self.cpos.get_pos()],
self._get_color(4)
)
self.search_items.clear()

# display status/error_bar
Expand Down Expand Up @@ -1468,7 +1499,7 @@ def _init_screen(self) -> None:
curses.init_pair(2, curses.COLOR_RED , curses.COLOR_WHITE )
# special char (not printable or ws) & prompts
curses.init_pair(3, curses.COLOR_WHITE, curses.COLOR_RED )
# tab-char
# tab-char & current match
curses.init_pair(4, curses.COLOR_BLACK, curses.COLOR_GREEN )
# selection
curses.init_pair(5, curses.COLOR_BLACK, curses.COLOR_YELLOW)
Expand Down
86 changes: 80 additions & 6 deletions cat_win/src/service/helper/editorhelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,10 +469,11 @@ class _SearchIter:
inspired by:
# -------- https://github.com/asottile/babi -------- #
"""
def __init__(self, editor, offset: int) -> None:
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
Expand Down Expand Up @@ -513,12 +514,12 @@ def _stop_if_past_original(self, row: int, f_col: int) -> tuple:
(row, f_col) >= self.editor.spos.get_pos()
):
raise StopIteration()
if self.wrapped and row == self._start_y:
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, l) if r == row and c >= self._start_x else (r, c, l)
for r, c, l in self.editor.search_items
]
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)
Expand Down Expand Up @@ -552,3 +553,76 @@ def __next__(self) -> tuple:
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)
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()

0 comments on commit 53f532d

Please sign in to comment.