curses-fzf 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- curses_fzf-0.2.0/.github/workflows/test.yml +38 -0
- curses_fzf-0.2.0/.gitignore +210 -0
- curses_fzf-0.2.0/CHANGELOG.md +18 -0
- curses_fzf-0.2.0/LICENSE +21 -0
- curses_fzf-0.2.0/PKG-INFO +340 -0
- curses_fzf-0.2.0/README.md +296 -0
- curses_fzf-0.2.0/examples/curses_preview_with_score_displayed.py +155 -0
- curses_fzf-0.2.0/examples/custom_keybindings_and_external_functions.py +142 -0
- curses_fzf-0.2.0/examples/custom_scoring_and_color_theme.py +132 -0
- curses_fzf-0.2.0/examples/dict_items_with_simple_preview_and_preselect.py +60 -0
- curses_fzf-0.2.0/examples/minimal_example.py +119 -0
- curses_fzf-0.2.0/examples/reusing_fuzzyfinder_and_autoreturn.py +123 -0
- curses_fzf-0.2.0/pyproject.toml +62 -0
- curses_fzf-0.2.0/src/curses_fzf/__about__.py +1 -0
- curses_fzf-0.2.0/src/curses_fzf/__init__.py +17 -0
- curses_fzf-0.2.0/src/curses_fzf/colors.py +80 -0
- curses_fzf-0.2.0/src/curses_fzf/errors.py +23 -0
- curses_fzf-0.2.0/src/curses_fzf/fuzzyfinder.py +590 -0
- curses_fzf-0.2.0/src/curses_fzf/help.py +79 -0
- curses_fzf-0.2.0/src/curses_fzf/scoring.py +176 -0
- curses_fzf-0.2.0/tests/test_colors.py +63 -0
- curses_fzf-0.2.0/tests/test_fuzzyfinder.py +783 -0
- curses_fzf-0.2.0/tests/test_help.py +50 -0
- curses_fzf-0.2.0/tests/test_scoring.py +135 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Python Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
19
|
+
uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
cache: pip
|
|
23
|
+
cache-dependency-path: pyproject.toml
|
|
24
|
+
|
|
25
|
+
- name: Install dependencies
|
|
26
|
+
run: |
|
|
27
|
+
python -m pip install --upgrade pip
|
|
28
|
+
pip install ".[dev]"
|
|
29
|
+
|
|
30
|
+
- name: Run pytest
|
|
31
|
+
run: |
|
|
32
|
+
pytest -v --cov=curses_fzf --cov-report=xml --cov-report=term-missing
|
|
33
|
+
|
|
34
|
+
- name: Upload coverage to Codecov
|
|
35
|
+
uses: codecov/codecov-action@v5
|
|
36
|
+
with:
|
|
37
|
+
files: ./coverage.xml
|
|
38
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
.python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
#poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
#pdm.lock
|
|
116
|
+
#pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
#pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# SageMath parsed files
|
|
135
|
+
*.sage.py
|
|
136
|
+
|
|
137
|
+
# Environments
|
|
138
|
+
.env
|
|
139
|
+
.envrc
|
|
140
|
+
.venv
|
|
141
|
+
env/
|
|
142
|
+
venv/
|
|
143
|
+
ENV/
|
|
144
|
+
env.bak/
|
|
145
|
+
venv.bak/
|
|
146
|
+
|
|
147
|
+
# Spyder project settings
|
|
148
|
+
.spyderproject
|
|
149
|
+
.spyproject
|
|
150
|
+
|
|
151
|
+
# Rope project settings
|
|
152
|
+
.ropeproject
|
|
153
|
+
|
|
154
|
+
# mkdocs documentation
|
|
155
|
+
/site
|
|
156
|
+
|
|
157
|
+
# mypy
|
|
158
|
+
.mypy_cache/
|
|
159
|
+
.dmypy.json
|
|
160
|
+
dmypy.json
|
|
161
|
+
|
|
162
|
+
# Pyre type checker
|
|
163
|
+
.pyre/
|
|
164
|
+
|
|
165
|
+
# pytype static type analyzer
|
|
166
|
+
.pytype/
|
|
167
|
+
|
|
168
|
+
# Cython debug symbols
|
|
169
|
+
cython_debug/
|
|
170
|
+
|
|
171
|
+
# PyCharm
|
|
172
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
173
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
174
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
175
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
176
|
+
#.idea/
|
|
177
|
+
|
|
178
|
+
# Abstra
|
|
179
|
+
# Abstra is an AI-powered process automation framework.
|
|
180
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
181
|
+
# Learn more at https://abstra.io/docs
|
|
182
|
+
.abstra/
|
|
183
|
+
|
|
184
|
+
# Visual Studio Code
|
|
185
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
186
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
188
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
189
|
+
.vscode/
|
|
190
|
+
|
|
191
|
+
# Ruff stuff:
|
|
192
|
+
.ruff_cache/
|
|
193
|
+
|
|
194
|
+
# PyPI configuration file
|
|
195
|
+
.pypirc
|
|
196
|
+
|
|
197
|
+
# Cursor
|
|
198
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
199
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
200
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
201
|
+
.cursorignore
|
|
202
|
+
.cursorindexingignore
|
|
203
|
+
|
|
204
|
+
# Marimo
|
|
205
|
+
marimo/_static/
|
|
206
|
+
marimo/_lsp/
|
|
207
|
+
__marimo__/
|
|
208
|
+
|
|
209
|
+
# additional
|
|
210
|
+
.ignore/
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# CHANGELOG
|
|
2
|
+
|
|
3
|
+
All versions below are listed in reverse chronological order.
|
|
4
|
+
|
|
5
|
+
## [0.2.0](https://github.com/Heiko-san/curses_fzf/releases/tag/0.2.0) (2026-03-02)
|
|
6
|
+
|
|
7
|
+
### Breaking Changes
|
|
8
|
+
|
|
9
|
+
- Complete rework of fuzzyfinder to object oriented version, to actually make FuzzyFinder testable.
|
|
10
|
+
- `fuzzyfinder` function removed, `FuzzyFinder` class added.
|
|
11
|
+
- `FuzzyFinder` mostly takes the same parameters as `fuzzyfinder` before.
|
|
12
|
+
- Use object's `find` method to provide the actual data list and retrieve the result.
|
|
13
|
+
|
|
14
|
+
## [0.1.0](https://github.com/Heiko-san/curses_fzf/releases/tag/0.1.0) (2026-02-26)
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
- First basic functionality
|
curses_fzf-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Heiko Finzel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: curses-fzf
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A pure python implementation of fzf (fuzzyfinder) with some additional features using the curses library.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Heiko-san/curses_fzf
|
|
6
|
+
Project-URL: Documentation, https://github.com/Heiko-san/curses_fzf/blob/main/README.md
|
|
7
|
+
Project-URL: Repository, https://github.com/Heiko-san/curses_fzf.git
|
|
8
|
+
Project-URL: Issues, https://github.com/Heiko-san/curses_fzf/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/Heiko-san/curses_fzf/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: Heiko Finzel <heikofinzel@googlemail.com>
|
|
11
|
+
Maintainer-email: Heiko Finzel <heikofinzel@googlemail.com>
|
|
12
|
+
License-Expression: MIT
|
|
13
|
+
Keywords: cli,curses,filter,finder,fuzzy,fuzzyfinder,fzf,python,search
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Environment :: Console :: Curses
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
19
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
20
|
+
Classifier: Operating System :: MacOS
|
|
21
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
22
|
+
Classifier: Programming Language :: Python :: 3
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
28
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
29
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
30
|
+
Classifier: Topic :: Software Development
|
|
31
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
32
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
33
|
+
Classifier: Topic :: Text Processing :: Filters
|
|
34
|
+
Classifier: Topic :: Utilities
|
|
35
|
+
Classifier: Typing :: Typed
|
|
36
|
+
Requires-Python: >=3.8
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: build>=1; extra == 'dev'
|
|
39
|
+
Requires-Dist: pytest-cov>=4; extra == 'dev'
|
|
40
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
41
|
+
Requires-Dist: pyyaml>=6; extra == 'dev'
|
|
42
|
+
Requires-Dist: twine>=6; extra == 'dev'
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
|
|
45
|
+
# curses_fzf
|
|
46
|
+
|
|
47
|
+
A pure Python implementation of fzf (fuzzyfinder) using the curses library -
|
|
48
|
+
no external `fzf` binary required.
|
|
49
|
+
|
|
50
|
+
Although there are many good fzf libraries available, they all have one thing in common:
|
|
51
|
+
They are wrappers to the shell tool `fzf`.
|
|
52
|
+
|
|
53
|
+
This is not inherently bad, but has one major downside:
|
|
54
|
+
It does not integrate well into Python code.
|
|
55
|
+
|
|
56
|
+
- What if you want to fuzzy-find over a list of dicts or objects?
|
|
57
|
+
- What if you want to pre-select items (e.g. tags already set for a resource,
|
|
58
|
+
that could be unset while selecting new ones)?
|
|
59
|
+
- What if you want to display additional information along with the entry to fuzzy-find on?
|
|
60
|
+
- What if you want to customize the fuzzy-finder algorithm?
|
|
61
|
+
|
|
62
|
+
To all of the above questions this module is the answer.
|
|
63
|
+
|
|
64
|
+
# Features
|
|
65
|
+
|
|
66
|
+
## Multi Select Mode
|
|
67
|
+
|
|
68
|
+
```py
|
|
69
|
+
from curses_fzf import FuzzyFinder, CursesFzfAborted
|
|
70
|
+
|
|
71
|
+
fzf = FuzzyFinder(multi=True)
|
|
72
|
+
try:
|
|
73
|
+
choices = fzf.find(data)
|
|
74
|
+
except CursesFzfAborted:
|
|
75
|
+
print("Fuzzy finder aborted by user.")
|
|
76
|
+
else:
|
|
77
|
+
for item in choices:
|
|
78
|
+
print(item)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
In its simplest form, `FuzzyFinder` only requires the `data` list to present to the user,
|
|
82
|
+
a single item can then be chosen from the list.
|
|
83
|
+
|
|
84
|
+
Setting `multi` to `True` will allow you to select multiple items using the `TAB` key.
|
|
85
|
+
|
|
86
|
+
In any case the returned result is a list of strings.
|
|
87
|
+
It will contain exactly one items in single-selection mode and `0..n` in
|
|
88
|
+
multi-selection mode.
|
|
89
|
+
|
|
90
|
+
If selection is aborted a `CursesFzfAborted` exception is raised, so single
|
|
91
|
+
selection mode can't return an empty list.
|
|
92
|
+
|
|
93
|
+
## Query Pre-Seeding
|
|
94
|
+
|
|
95
|
+
```py
|
|
96
|
+
from curses_fzf import FuzzyFinder
|
|
97
|
+
|
|
98
|
+
fzf = FuzzyFinder(query="the in")
|
|
99
|
+
choices = fzf.find(data)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
By default `FuzzyFinder` will start with an empty `query`.
|
|
103
|
+
The unfiltered list will then be presented in its original order.
|
|
104
|
+
|
|
105
|
+
If the user enters a filter query the list is reduced to the matching items,
|
|
106
|
+
sorted by match score (see `score` function).
|
|
107
|
+
|
|
108
|
+
The `query` can also be pre-seeded with a given string.
|
|
109
|
+
The user is still able to fully modify the query, including completely clearing it.
|
|
110
|
+
The parameter can be given to `FuzzyFinder` constructor or the object's `find` method.
|
|
111
|
+
|
|
112
|
+

|
|
113
|
+
|
|
114
|
+
## Title Prompting The User
|
|
115
|
+
|
|
116
|
+
```py
|
|
117
|
+
from curses_fzf import FuzzyFinder
|
|
118
|
+
|
|
119
|
+
fzf = FuzzyFinder(title="Select an item:")
|
|
120
|
+
choices = fzf.find(data)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Instead of "ITEMS", you can provide a custom header for the `FuzzyFinder` window.
|
|
124
|
+
The parameter can be given to `FuzzyFinder` constructor or the object's `find` method.
|
|
125
|
+
|
|
126
|
+
## Display Function
|
|
127
|
+
|
|
128
|
+
```py
|
|
129
|
+
from curses_fzf import FuzzyFinder
|
|
130
|
+
|
|
131
|
+
def display_name_property(item: Any) -> str:
|
|
132
|
+
return item.name
|
|
133
|
+
|
|
134
|
+
fzf = FuzzyFinder(display=display_name_property)
|
|
135
|
+
choices = fzf.find(data)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Since `curses_fzf` allows you to work with lists of any type of items,
|
|
139
|
+
you may want to define a custom behavior of how it displays your items.
|
|
140
|
+
In the above example we have a list of objects,
|
|
141
|
+
using the `name` property to represent each item in `FuzzyFinder` listing.
|
|
142
|
+
|
|
143
|
+
The `display` function must return a single line of text.
|
|
144
|
+
A `CursesFzfAssertion` exception will be raised, if the function returns multi-line text.
|
|
145
|
+
If you want to present more complex information,
|
|
146
|
+
have a look at the `preview` function.
|
|
147
|
+
|
|
148
|
+
The default behavior is to stringify the item provided:
|
|
149
|
+
|
|
150
|
+
```py
|
|
151
|
+
FuzzyFinder(display=lambda item: str(item))
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Preselect Function
|
|
155
|
+
|
|
156
|
+
```py
|
|
157
|
+
from curses_fzf import FuzzyFinder, ScoringResult
|
|
158
|
+
|
|
159
|
+
def preselect_items(item: Any, scoring_result: ScoringResult) -> bool:
|
|
160
|
+
return item in PREFERRED_ITEMS
|
|
161
|
+
|
|
162
|
+
fzf = FuzzyFinder(multi=True, preselect=preselect_items)
|
|
163
|
+
choices = fzf.find(data)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
If you use `FuzzyFinder` in multi-select mode, you can pre-select some items
|
|
167
|
+
using the `preselect` function.
|
|
168
|
+
This function is expected to return `True` if the item should be selected.
|
|
169
|
+
|
|
170
|
+
The default implementation always returns `False`.
|
|
171
|
+
|
|
172
|
+

|
|
173
|
+
|
|
174
|
+
## Preview Function
|
|
175
|
+
|
|
176
|
+
```py
|
|
177
|
+
import curses
|
|
178
|
+
from curses_fzf import FuzzyFinder, ScoringResult, Color, ColorTheme
|
|
179
|
+
|
|
180
|
+
def my_preview(preview_window: curses.window, color_theme: ColorTheme, item: Any, result: ScoringResult) -> str:
|
|
181
|
+
preview_window.addstr(1, 1, item.description, curses.color_pair(Color.RED))
|
|
182
|
+
return ""
|
|
183
|
+
|
|
184
|
+
fzf = FuzzyFinder(preview=my_preview)
|
|
185
|
+
choices = fzf.find(data)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The `preview` function (default `None`), if set, will show a preview window on the
|
|
189
|
+
right side of the `FuzzyFinder` window.
|
|
190
|
+
You can use this window to present additional information about the item.
|
|
191
|
+
For example you can `yaml.dump` `dict` items.
|
|
192
|
+
|
|
193
|
+
There are two possible ways to use this function:
|
|
194
|
+
|
|
195
|
+
Either you ignore the provided `preview_window` and simply return a string,
|
|
196
|
+
that can also be multi-line.
|
|
197
|
+
The `FuzzyFinder` will take care of the text not leaking out of the window boundaries.
|
|
198
|
+
|
|
199
|
+
Or you return an empty string and use `preview_window` to modify the curses window manually.
|
|
200
|
+
If you do so, you should ensure to handle window boundaries correctly
|
|
201
|
+
to avoid crashes, e.g. on terminal resizing.
|
|
202
|
+
See `ColorTheme` section for information on coloring, the selected `color_theme`
|
|
203
|
+
is also provided to the `preview` function.
|
|
204
|
+
|
|
205
|
+
See `examples` folder for more detailed code snippets.
|
|
206
|
+
|
|
207
|
+
Not only the `item` is provided, but also the `ScoringResult`.
|
|
208
|
+
This allows to display scoring related information.
|
|
209
|
+
|
|
210
|
+
You can use `preview_window_percentage` parameter of `FuzzyFinder` to define the
|
|
211
|
+
width of the preview window.
|
|
212
|
+
The default value is `40` percent of the terminal window.
|
|
213
|
+
Don't worry that the preview window might hide portions of your items,
|
|
214
|
+
you can toggle the preview window any time using `Ctrl + P`.
|
|
215
|
+
|
|
216
|
+

|
|
217
|
+
|
|
218
|
+
## Scoring Function
|
|
219
|
+
|
|
220
|
+
```py
|
|
221
|
+
from curses_fzf import FuzzyFinder, ScoringResult
|
|
222
|
+
|
|
223
|
+
def my_scoring(query: str, candidate: str) -> ScoringResult:
|
|
224
|
+
sr = ScoringResult(query, candidate)
|
|
225
|
+
# ... scoring logic
|
|
226
|
+
sr.score = 100
|
|
227
|
+
# ...
|
|
228
|
+
return sr
|
|
229
|
+
|
|
230
|
+
fzf = FuzzyFinder(score=my_scoring)
|
|
231
|
+
choices = fzf.find(data)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The `curses_fzf` module comes with built-in scoring functions (default `scoring_full_words`).
|
|
235
|
+
Scoring determines if an item is considered to match the `query` the user entered.
|
|
236
|
+
The higher the score the higher the item gets sorted among the matches.
|
|
237
|
+
If score is 0 the item is considered to not be a match, it will not be displayed in the list at all.
|
|
238
|
+
|
|
239
|
+
A scoring function retrieves the user `query` as its first argument and the
|
|
240
|
+
`candidate` to match as the second.
|
|
241
|
+
The `candidate` is the `display` string of the item in question.
|
|
242
|
+
|
|
243
|
+
The function is supposed to return a `ScoringResult`.
|
|
244
|
+
|
|
245
|
+
### ScoringResult
|
|
246
|
+
|
|
247
|
+
The only important thing about the `ScoringResult` is its `score` field.
|
|
248
|
+
Although there are helper functions, you are free to modify this field directly
|
|
249
|
+
as your scoring function requires.
|
|
250
|
+
If the value of this field is `0`, the `candidate` will not be displayed in the list of matches.
|
|
251
|
+
A higher value indicates a better match and will prioritize the item in the sorted list of results.
|
|
252
|
+
|
|
253
|
+
The second field to notice is `matches`, which is a list of tuples containing the
|
|
254
|
+
starting index and length of all matches inside the `candidate` string.
|
|
255
|
+
If set, this information will be used by `FuzzyFinder` to colorize the matched substrings
|
|
256
|
+
in the list of query results.
|
|
257
|
+
|
|
258
|
+
The intended way to set those fields is `sr.add_match(position: int, length: int, score: int)`.
|
|
259
|
+
The first two parameters represent one tuple appended to the `matches` list.
|
|
260
|
+
The `score` parameter is the score associated with the partial match that `position`
|
|
261
|
+
and `length` identifies, it is added to the `score` field of this `ScoringResult`.
|
|
262
|
+
|
|
263
|
+
`ScoringResult` also assists with tokenization of the `query` and `candidate`,
|
|
264
|
+
providing the fields `query`, `query_lower`, `query_words_with_index`, `candidate`,
|
|
265
|
+
`candidate_lower` and `candidate_words_with_index`.
|
|
266
|
+
|
|
267
|
+
## ColorTheme Customization
|
|
268
|
+
|
|
269
|
+
```py
|
|
270
|
+
from curses_fzf import FuzzyFinder, ColorTheme, Color
|
|
271
|
+
|
|
272
|
+
fzf = FuzzyFinder(color_theme=ColorTheme(text=Color.CYAN))
|
|
273
|
+
choices = fzf.find(data)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
`ColorTheme` can be used to customize text colors, e.g. to increase readability.
|
|
277
|
+
Use the indexes defined via `Color` enum.
|
|
278
|
+
If you want to register your own `color_pairs`, the indexes 1 to 29 are safe to use.
|
|
279
|
+
|
|
280
|
+
## Autoreturn
|
|
281
|
+
|
|
282
|
+
```py
|
|
283
|
+
from curses_fzf import FuzzyFinder
|
|
284
|
+
|
|
285
|
+
fzf = FuzzyFinder(multi=True, query="foo", autoreturn=3)
|
|
286
|
+
choices = fzf.find(data)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
If the list provided contains exactly the number of entries defined by `autoreturn`,
|
|
290
|
+
the `FuzzyFinder` will return those entries without user interaction.
|
|
291
|
+
|
|
292
|
+
This is most useful in combination with a pre-seed `query`,
|
|
293
|
+
in which case the number of matches is considered.
|
|
294
|
+
|
|
295
|
+
The default `0` means "don't autoreturn".
|
|
296
|
+
If `multi=True` the number given as `autoreturn`'s value is checked against the filter results.
|
|
297
|
+
If `multi=False` the number given as `autoreturn`'s value is not relevant,
|
|
298
|
+
the match will be returned, if there is only one.
|
|
299
|
+
|
|
300
|
+
## Page Size
|
|
301
|
+
|
|
302
|
+
The `page_size` parameter (default `10`) defines the number of entries that are
|
|
303
|
+
skipped by the keys `PAGE_UP` and `PAGE_DOWN`.
|
|
304
|
+
|
|
305
|
+
## Help
|
|
306
|
+
|
|
307
|
+
Press `F1` to display a help screen with a list of keyboard actions.
|
|
308
|
+
|
|
309
|
+

|
|
310
|
+
|
|
311
|
+
## Exceptions
|
|
312
|
+
|
|
313
|
+
`CursesFzfException` is the base exception type that can catch any `curses_fzf` exceptions.
|
|
314
|
+
|
|
315
|
+
If the user aborts the selection using `ESC` or `Ctrl + C`, a `CursesFzfAborted` is raised.
|
|
316
|
+
In single selection mode the returned list will always contain an item,
|
|
317
|
+
since otherwise this exception would have been raised.
|
|
318
|
+
In multi selection mode the returned list can be empty,
|
|
319
|
+
if the user accepts an empty selection with `Enter`.
|
|
320
|
+
|
|
321
|
+
`CursesFzfAssertion` will be raised if some contracts are broken,
|
|
322
|
+
e.g. if the `display` function returns multiline text.
|
|
323
|
+
|
|
324
|
+
A special case of this assertion is `CursesFzfIndexOutOfBounds`, which is raised e.g.
|
|
325
|
+
if the calls to `query` modification functions use invalid indexes.
|
|
326
|
+
|
|
327
|
+
## Keymap And More
|
|
328
|
+
|
|
329
|
+
```py
|
|
330
|
+
from curses_fzf import FuzzyFinder
|
|
331
|
+
|
|
332
|
+
fzf = FuzzyFinder()
|
|
333
|
+
fzf.keymap[curses.KEY_F2] = lambda: fzf.kb_move_items_cursor_relative(2)
|
|
334
|
+
choices = fzf.find(data)
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
`FuzzyFinder` is designed to allow for deep customization.
|
|
338
|
+
|
|
339
|
+
See `examples` folder for more detailed code snippets, e.g. on how to define
|
|
340
|
+
your own keyboard actions.
|