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.
@@ -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
@@ -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
+ ![image](https://github.com/Heiko-san/curses_fzf/releases/download/0.1.0/simple.png)
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
+ ![image](https://github.com/Heiko-san/curses_fzf/releases/download/0.1.0/multi_with_preview.png)
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
+ ![image](https://github.com/Heiko-san/curses_fzf/releases/download/0.1.0/simple_with_preview.png)
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
+ ![image](https://github.com/Heiko-san/curses_fzf/releases/download/0.2.0/help.png)
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.