Skip to content

Commit 69da2a4

Browse files
committed
Improve searching
* previously, would crash if an invalid RE was entered * make a literal space search for anything-whitespace-anything as a cheap fuzzy search facility
1 parent 5015d9b commit 69da2a4

File tree

2 files changed

+30
-3
lines changed

2 files changed

+30
-3
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,16 @@ Your Operating System may report that `ttotp` "pasted from the clipboard".
7575
This is because `ttotp` tries to only clear values that it set,
7676
by checking that the current clipboard value is equal to the value it pasted earlier.
7777

78-
Search for a key by pressing "/" and then entering a case insensitive regular expression.
78+
Search for a key by pressing "/" and then entering a modified case insensitive regular expression.
7979
Press Ctrl+A to show all keys again.
8080

81+
In this type of regular expression, a space ` ` stands for "zero or more characters, followed by whitespace, followed by zero or more characters"; the sequence backslash-space stands for a literal space.
82+
83+
This makes it easy to search for e.g., "Jay Doe / example.com" by entering "ja d ex", while not requiring any sophisticated fuzzy search technology.
84+
85+
Due to the simple way this is implemented, a space character inside a character class does not function as expected.
86+
Since complicated regular expressions are likely seldom used, this is not likely to be a huge limitation.
87+
8188
Exit the app with Ctrl+C.
8289

8390
# In-memory storage of TOTPs

src/ttotp/__main__.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,12 @@ class SearchInput(Input, can_focus=False):
176176
]
177177

178178
def on_focus(self) -> None:
179-
self.placeholder = "Enter regular expression"
179+
self.placeholder = "Enter search expression"
180180

181181
def on_blur(self) -> None:
182182
self.placeholder = "Type / to search"
183183
self.can_focus = False
184+
self.remove_class("error")
184185

185186

186187
class TOTPButton(Button, can_focus=False):
@@ -243,6 +244,18 @@ def widgets(self) -> Sequence[Widget]:
243244
)
244245

245246

247+
def search_preprocess(s):
248+
def replace_escape_sequence(m):
249+
s = m.group(0)
250+
if s == "\\ ":
251+
return " "
252+
if s == " ":
253+
return r".*\s+.*"
254+
return s
255+
256+
return re.sub(r"\\.| |[^\\ ]+", replace_escape_sequence, s)
257+
258+
246259
class TTOTP(App[None]):
247260
CSS = """
248261
VerticalScroll { min-height: 1; }
@@ -256,6 +269,7 @@ class TTOTP(App[None]):
256269
Button { border: none; height: 1; width: 3; min-width: 4 }
257270
Horizontal { height: 1; }
258271
Input { border: none; height: 1; width: 1fr; }
272+
Input.error { background: $error; }
259273
"""
260274

261275
BINDINGS = [
@@ -339,7 +353,13 @@ def action_clear_search(self) -> None:
339353
self.screen.focus_next()
340354

341355
def on_input_changed(self, event: Input.Changed) -> None:
342-
rx = re.compile(event.value or ".", re.I)
356+
haystack = event.value.replace(" ", ".* .*")
357+
try:
358+
rx = re.compile(haystack, re.I)
359+
except re.error:
360+
self.search.add_class("error")
361+
return
362+
self.search.remove_class("error")
343363
for otp in self.otp_data:
344364
parent = otp.name_widget.parent
345365
assert parent is not None

0 commit comments

Comments
 (0)