feat: add regex search with n/N to repeat forward/backward
This commit is contained in:
parent
a00381a2c2
commit
4dc693a9a4
4 changed files with 74 additions and 0 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
import re
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from ..items.Folder import Folder
|
from ..items.Folder import Folder
|
||||||
from ..core.Prompt import Prompt
|
from ..core.Prompt import Prompt
|
||||||
|
|
@ -26,6 +27,9 @@ class Input:
|
||||||
"new_folder": self._new_folder,
|
"new_folder": self._new_folder,
|
||||||
"delete_folder": self._delete_folder,
|
"delete_folder": self._delete_folder,
|
||||||
"new_file": self._new_file,
|
"new_file": self._new_file,
|
||||||
|
"search": self._search,
|
||||||
|
"search_next": self._search_next,
|
||||||
|
"search_previous": self._search_previous,
|
||||||
}
|
}
|
||||||
|
|
||||||
def handle(self, key: str) -> bool:
|
def handle(self, key: str) -> bool:
|
||||||
|
|
@ -156,6 +160,39 @@ class Input:
|
||||||
self.app.open_prompt(Prompt("New file name: ", "text", on_submit))
|
self.app.open_prompt(Prompt("New file name: ", "text", on_submit))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _search(self) -> bool:
|
||||||
|
navigator = self.app.navigator
|
||||||
|
|
||||||
|
def on_submit(pattern: str) -> None:
|
||||||
|
if not pattern:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
regex = re.compile(pattern)
|
||||||
|
except re.error as e:
|
||||||
|
self.app.open_prompt(Prompt.error(f"Invalid regex: {e}"))
|
||||||
|
return
|
||||||
|
if not navigator.search(regex):
|
||||||
|
self.app.open_prompt(Prompt.error(f"No match: {pattern}"))
|
||||||
|
|
||||||
|
self.app.open_prompt(Prompt("Search: ", "text", on_submit))
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _search_next(self) -> bool:
|
||||||
|
navigator = self.app.navigator
|
||||||
|
if navigator.last_pattern is None:
|
||||||
|
return True
|
||||||
|
if not navigator.search_next():
|
||||||
|
self.app.open_prompt(Prompt.error(f"No match: {navigator.last_pattern}"))
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _search_previous(self) -> bool:
|
||||||
|
navigator = self.app.navigator
|
||||||
|
if navigator.last_pattern is None:
|
||||||
|
return True
|
||||||
|
if not navigator.search_previous():
|
||||||
|
self.app.open_prompt(Prompt.error(f"No match: {navigator.last_pattern}"))
|
||||||
|
return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def app(self) -> App:
|
def app(self) -> App:
|
||||||
return self._app
|
return self._app
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,9 @@ DEFAULT_KEYBINDS: dict[str, list[str]] = {
|
||||||
"new_folder": ["d"],
|
"new_folder": ["d"],
|
||||||
"delete_folder": ["D"],
|
"delete_folder": ["D"],
|
||||||
"new_file": ["%"],
|
"new_file": ["%"],
|
||||||
|
"search": ["/"],
|
||||||
|
"search_next": ["n"],
|
||||||
|
"search_previous": ["N"],
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_CURSOR_RESET = "top"
|
DEFAULT_CURSOR_RESET = "top"
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ class Cursor:
|
||||||
def clamp(self, count: int) -> None:
|
def clamp(self, count: int) -> None:
|
||||||
self._pos = min(self._pos, max(count - 1, 0))
|
self._pos = min(self._pos, max(count - 1, 0))
|
||||||
|
|
||||||
|
def set(self, pos: int) -> None:
|
||||||
|
self._pos = pos
|
||||||
|
|
||||||
def up(self) -> None:
|
def up(self) -> None:
|
||||||
self._pos -= 1 if self.pos >= 1 else 0
|
self._pos -= 1 if self.pos >= 1 else 0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from ..items.AItems import AItem
|
from ..items.AItems import AItem
|
||||||
from ..items.Folder import Folder
|
from ..items.Folder import Folder
|
||||||
|
|
@ -19,6 +20,7 @@ class Navigator:
|
||||||
self._current: Folder | None = None
|
self._current: Folder | None = None
|
||||||
self._items: list[AItem] = []
|
self._items: list[AItem] = []
|
||||||
self._error: str | None = None
|
self._error: str | None = None
|
||||||
|
self._last_regex: re.Pattern | None = None
|
||||||
self._load()
|
self._load()
|
||||||
|
|
||||||
def _load(self) -> None:
|
def _load(self) -> None:
|
||||||
|
|
@ -49,6 +51,35 @@ class Navigator:
|
||||||
self._sort_grouping = Sort.next_grouping(self._sort_grouping)
|
self._sort_grouping = Sort.next_grouping(self._sort_grouping)
|
||||||
self._resort()
|
self._resort()
|
||||||
|
|
||||||
|
def search(self, regex: re.Pattern) -> bool:
|
||||||
|
self._last_regex = regex
|
||||||
|
return self._find(regex, 1)
|
||||||
|
|
||||||
|
def search_next(self) -> bool:
|
||||||
|
if self._last_regex is None:
|
||||||
|
return False
|
||||||
|
return self._find(self._last_regex, 1)
|
||||||
|
|
||||||
|
def search_previous(self) -> bool:
|
||||||
|
if self._last_regex is None:
|
||||||
|
return False
|
||||||
|
return self._find(self._last_regex, -1)
|
||||||
|
|
||||||
|
def _find(self, regex: re.Pattern, direction: int) -> bool:
|
||||||
|
count = len(self._items)
|
||||||
|
if count == 0:
|
||||||
|
return False
|
||||||
|
for step in range(1, count + 1):
|
||||||
|
index = (self._cursor.pos + direction * step) % count
|
||||||
|
if regex.search(self._items[index].name):
|
||||||
|
self._cursor.set(index)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def last_pattern(self) -> str | None:
|
||||||
|
return self._last_regex.pattern if self._last_regex else None
|
||||||
|
|
||||||
def _reset_cursor(self) -> None:
|
def _reset_cursor(self) -> None:
|
||||||
if self._config.cursor_reset == "keep":
|
if self._config.cursor_reset == "keep":
|
||||||
self._cursor.clamp(len(self._items))
|
self._cursor.clamp(len(self._items))
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue