Merge pull request #9 from jepler/fuzzy

Switch to textual's own fuzzzy matcher
This commit is contained in:
Jeff Epler 2023-12-16 13:10:33 -06:00 committed by GitHub
commit a6aa81882d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 30 additions and 16 deletions

View file

@ -75,10 +75,10 @@ Your Operating System may report that `ttotp` "pasted from the clipboard".
This is because `ttotp` tries to only clear values that it set,
by checking that the current clipboard value is equal to the value it pasted earlier.
Search for a key by pressing "/" and then entering a modified case insensitive regular expression.
Search for a key by pressing "/" and then entering sub-strings to search for.
Press Ctrl+A to show all keys again.
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.
Textual's built in [fuzzy match](https://textual.textualize.io/api/fuzzy_matcher/) algorithm is used.
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.

View file

@ -15,6 +15,7 @@ import re
from typing import TYPE_CHECKING, Any, Sequence, cast
import rich.text
from textual.fuzzy import Matcher
from textual.app import App, ComposeResult
from textual.widget import Widget
from textual.widgets import Label, Footer, ProgressBar, Button, Input
@ -354,20 +355,33 @@ class TTOTP(App[None]):
self.screen.focus_next()
def on_input_changed(self, event: Input.Changed) -> None:
haystack = event.value.replace(" ", ".* .*")
try:
rx = re.compile(haystack, re.I)
except re.error:
self.search.add_class("error")
return
self.search.remove_class("error")
for otp in self.otp_data:
parent = otp.name_widget.parent
assert parent is not None
if rx.search(otp.name):
parent.remove_class("otp-hidden")
else:
parent.add_class("otp-hidden")
with self.batch_update():
needle = event.value
if not needle:
for otp in self.otp_data:
name_widget = otp.name_widget
parent = name_widget.parent
assert parent is not None
parent.remove_class("otp-hidden")
name_widget.update(
rich.text.Text(otp.name, overflow="ellipsis", no_wrap=True)
)
return
matcher = Matcher(needle)
for otp in self.otp_data:
name_widget = otp.name_widget
parent = name_widget.parent
assert parent is not None
score = matcher.match(otp.name)
if score > 0:
highlighted = matcher.highlight(otp.name)
highlighted.overflow = "ellipsis"
highlighted.no_wrap = True
name_widget.update(highlighted)
parent.remove_class("otp-hidden")
else:
parent.add_class("otp-hidden")
def on_input_submitted(self, event: Input.Changed) -> None:
self.screen.focus_next()