A pure python implementation of fzf (fuzzyfinder) with some additional features using the curses library.
Project description
curses_fzf
A pure Python implementation of fzf (fuzzyfinder) using the curses library -
no external fzf binary required.
Although there are many good fzf libraries available, they all have one thing in common:
They are wrappers to the shell tool fzf.
This is not inherently bad, but has one major downside: It does not integrate well into Python code.
- What if you want to fuzzy-find over a list of dicts or objects?
- What if you want to pre-select items (e.g. tags already set for a resource, that could be unset while selecting new ones)?
- What if you want to display additional information along with the entry to fuzzy-find on?
- What if you want to customize the fuzzy-finder algorithm?
To all of the above questions this module is the answer.
Features
Multi Select Mode
from curses_fzf import FuzzyFinder, CursesFzfAborted
fzf = FuzzyFinder(multi=True)
try:
choices = fzf.find(data)
except CursesFzfAborted:
print("Fuzzy finder aborted by user.")
else:
for item in choices:
print(item)
In its simplest form, FuzzyFinder only requires the data list to present to the user,
a single item can then be chosen from the list.
Setting multi to True will allow you to select multiple items using the TAB key.
In any case the returned result is a list of strings.
It will contain exactly one items in single-selection mode and 0..n in
multi-selection mode.
If selection is aborted a CursesFzfAborted exception is raised, so single
selection mode can't return an empty list.
Query Pre-Seeding
from curses_fzf import FuzzyFinder
fzf = FuzzyFinder(query="the in")
choices = fzf.find(data)
By default FuzzyFinder will start with an empty query.
The unfiltered list will then be presented in its original order.
If the user enters a filter query the list is reduced to the matching items,
sorted by match score (see score function).
The query can also be pre-seeded with a given string.
The user is still able to fully modify the query, including completely clearing it.
The parameter can be given to FuzzyFinder constructor or the object's find method.
Title Prompting The User
from curses_fzf import FuzzyFinder
fzf = FuzzyFinder(title="Select an item:")
choices = fzf.find(data)
Instead of "ITEMS", you can provide a custom header for the FuzzyFinder window.
The parameter can be given to FuzzyFinder constructor or the object's find method.
Display Function
from curses_fzf import FuzzyFinder
def display_name_property(item: Any) -> str:
return item.name
fzf = FuzzyFinder(display=display_name_property)
choices = fzf.find(data)
Since curses_fzf allows you to work with lists of any type of items,
you may want to define a custom behavior of how it displays your items.
In the above example we have a list of objects,
using the name property to represent each item in FuzzyFinder listing.
The display function must return a single line of text.
A CursesFzfAssertion exception will be raised, if the function returns multi-line text.
If you want to present more complex information,
have a look at the preview function.
The default behavior is to stringify the item provided:
FuzzyFinder(display=lambda item: str(item))
Preselect Function
from curses_fzf import FuzzyFinder, ScoringResult
def preselect_items(item: Any, scoring_result: ScoringResult) -> bool:
return item in PREFERRED_ITEMS
fzf = FuzzyFinder(multi=True, preselect=preselect_items)
choices = fzf.find(data)
If you use FuzzyFinder in multi-select mode, you can pre-select some items
using the preselect function.
This function is expected to return True if the item should be selected.
The default implementation always returns False.
Preview Function
import curses
from curses_fzf import FuzzyFinder, ScoringResult, Color, ColorTheme
def my_preview(preview_window: curses.window, color_theme: ColorTheme, item: Any, result: ScoringResult) -> str:
preview_window.addstr(1, 1, item.description, curses.color_pair(Color.RED))
return ""
fzf = FuzzyFinder(preview=my_preview)
choices = fzf.find(data)
The preview function (default None), if set, will show a preview window on the
right side of the FuzzyFinder window.
You can use this window to present additional information about the item.
For example you can yaml.dump dict items.
There are two possible ways to use this function:
Either you ignore the provided preview_window and simply return a string,
that can also be multi-line.
The FuzzyFinder will take care of the text not leaking out of the window boundaries.
Or you return an empty string and use preview_window to modify the curses window manually.
If you do so, you should ensure to handle window boundaries correctly
to avoid crashes, e.g. on terminal resizing.
See ColorTheme section for information on coloring, the selected color_theme
is also provided to the preview function.
See examples folder for more detailed code snippets.
Not only the item is provided, but also the ScoringResult.
This allows to display scoring related information.
You can use preview_window_percentage parameter of FuzzyFinder to define the
width of the preview window.
The default value is 40 percent of the terminal window.
Don't worry that the preview window might hide portions of your items,
you can toggle the preview window any time using Ctrl + P.
Scoring Function
from curses_fzf import FuzzyFinder, ScoringResult
def my_scoring(query: str, candidate: str) -> ScoringResult:
sr = ScoringResult(query, candidate)
# ... scoring logic
sr.score = 100
# ...
return sr
fzf = FuzzyFinder(score=my_scoring)
choices = fzf.find(data)
The curses_fzf module comes with built-in scoring functions (default scoring_full_words).
Scoring determines if an item is considered to match the query the user entered.
The higher the score the higher the item gets sorted among the matches.
If score is 0 the item is considered to not be a match, it will not be displayed in the list at all.
A scoring function retrieves the user query as its first argument and the
candidate to match as the second.
The candidate is the display string of the item in question.
The function is supposed to return a ScoringResult.
ScoringResult
The only important thing about the ScoringResult is its score field.
Although there are helper functions, you are free to modify this field directly
as your scoring function requires.
If the value of this field is 0, the candidate will not be displayed in the list of matches.
A higher value indicates a better match and will prioritize the item in the sorted list of results.
The second field to notice is matches, which is a list of tuples containing the
starting index and length of all matches inside the candidate string.
If set, this information will be used by FuzzyFinder to colorize the matched substrings
in the list of query results.
The intended way to set those fields is sr.add_match(position: int, length: int, score: int).
The first two parameters represent one tuple appended to the matches list.
The score parameter is the score associated with the partial match that position
and length identifies, it is added to the score field of this ScoringResult.
ScoringResult also assists with tokenization of the query and candidate,
providing the fields query, query_lower, query_words_with_index, candidate,
candidate_lower and candidate_words_with_index.
ColorTheme Customization
from curses_fzf import FuzzyFinder, ColorTheme, Color
fzf = FuzzyFinder(color_theme=ColorTheme(text=Color.CYAN))
choices = fzf.find(data)
ColorTheme can be used to customize text colors, e.g. to increase readability.
Use the indexes defined via Color enum.
If you want to register your own color_pairs, the indexes 1 to 29 are safe to use.
Autoreturn
from curses_fzf import FuzzyFinder
fzf = FuzzyFinder(multi=True, query="foo", autoreturn=3)
choices = fzf.find(data)
If the list provided contains exactly the number of entries defined by autoreturn,
the FuzzyFinder will return those entries without user interaction.
This is most useful in combination with a pre-seed query,
in which case the number of matches is considered.
The default 0 means "don't autoreturn".
If multi=True the number given as autoreturn's value is checked against the filter results.
If multi=False the number given as autoreturn's value is not relevant,
the match will be returned, if there is only one.
Page Size
The page_size parameter (default 10) defines the number of entries that are
skipped by the keys PAGE_UP and PAGE_DOWN.
Help
Press F1 to display a help screen with a list of keyboard actions.
Exceptions
CursesFzfException is the base exception type that can catch any curses_fzf exceptions.
If the user aborts the selection using ESC or Ctrl + C, a CursesFzfAborted is raised.
In single selection mode the returned list will always contain an item,
since otherwise this exception would have been raised.
In multi selection mode the returned list can be empty,
if the user accepts an empty selection with Enter.
CursesFzfAssertion will be raised if some contracts are broken,
e.g. if the display function returns multiline text.
A special case of this assertion is CursesFzfIndexOutOfBounds, which is raised e.g.
if the calls to query modification functions use invalid indexes.
Keymap And More
from curses_fzf import FuzzyFinder
fzf = FuzzyFinder()
fzf.keymap[curses.KEY_F2] = lambda: fzf.kb_move_items_cursor_relative(2)
choices = fzf.find(data)
FuzzyFinder is designed to allow for deep customization.
See examples folder for more detailed code snippets, e.g. on how to define
your own keyboard actions.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file curses_fzf-0.2.0.tar.gz.
File metadata
- Download URL: curses_fzf-0.2.0.tar.gz
- Upload date:
- Size: 29.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.8.20
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
519d0f4af5e62a2f4f0c900cf2b12c078745863fdfe45fdf453ec0c7ace28750
|
|
| MD5 |
77f1e6750ca0f2e00935fcb92f2ec2f3
|
|
| BLAKE2b-256 |
a5b2048d9e7321b7f10bafcb223d82ff147feec3ee5b3d93e45f8bc52667d81a
|
File details
Details for the file curses_fzf-0.2.0-py3-none-any.whl.
File metadata
- Download URL: curses_fzf-0.2.0-py3-none-any.whl
- Upload date:
- Size: 16.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.8.20
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2188499119a0328d1455c8961cd3d47d0b48132c8c4ab5e33c9724b786aa134e
|
|
| MD5 |
1049322ff43aa1ce67568a4bd9363706
|
|
| BLAKE2b-256 |
8775d682dd88d9dfa9e1e23d2279891be4b9a9e120506c2f9a239ff2c6246b68
|