Skip to content

Feat/picker multi select#52

Open
sigfriedCub1990 wants to merge 34 commits into
olzhasar:masterfrom
sigfriedCub1990:feat/picker-multi-select
Open

Feat/picker multi select#52
sigfriedCub1990 wants to merge 34 commits into
olzhasar:masterfrom
sigfriedCub1990:feat/picker-multi-select

Conversation

@sigfriedCub1990

Copy link
Copy Markdown
Contributor

No description provided.

sigfriedCub1990 and others added 30 commits March 15, 2025 22:24
* Add ASCII BEL symbol to notify tmux on test failure

* Refactor BEL notification

* Add --notify-on-failure option

* Adjust tests to pass with new option

* Move BEL notification logic to the Terminal layer. Update README

---------

Co-authored-by: olzhasar <o.arystanov@gmail.com>
* Migrate to uv. Remove tox

* Use "latest" release in sphinx conf. Update README
Add a fuzzy matching module with:
- fuzzy_match(): case-insensitive subsequence matching with scoring
  (consecutive bonus, word-boundary bonus, shorter-tail bonus)
- find_test_files(): discovers test files (test_*.py, *_test.py) via rglob
- fuzzy_filter(): filters and ranks candidates by fuzzy match score
Add an fzf-style interactive TUI (press 't') that lets users:
- Type a query to fuzzy-filter test files with live-updating results
- Navigate results with arrow keys (up/down)
- Select a file with Enter to run pytest on it
- Cancel with Escape to return to the watcher

Components:
- picker.py: PickerState, key event parsing (including arrow escape
  sequences), pure-function state machine, ANSI rendering with
  highlighted cursor row, injectable I/O for testability
- FuzzyFilterCommand in commands.py: discovers test files, launches
  the picker, preserves CLI flags, triggers pytest on selection
Two bugs in _read_key_event caused arrow keys to be misinterpreted
as Escape (cancelling the picker):

1. Python's BufferedReader eagerly consumes bytes from the fd into its
   internal buffer. When select() checks the OS-level fd for the
   continuation bytes of an escape sequence (e.g. '[' and 'B' after
   ESC), it finds nothing — the bytes are already in Python's buffer.
   read_char() returns None, and the sequence is treated as bare ESC.

   Fix: _read_continuation() retries up to 4 times on None returns
   before giving up, giving the buffered bytes a chance to surface.

2. Application-mode arrow keys (ESC O A/B) were not recognized —
   only normal-mode (ESC [ A/B) was handled. Some terminals send
   application-mode sequences depending on configuration.

   Fix: accept both 'O' and '[' as the second byte of arrow sequences.

Added 12 new tests:
- 7 _read_key_event tests for None gaps and application-mode sequences
- 5 end-to-end run_picker tests with gapped readers simulating real
  terminal behavior
The previous retry-based fix could not work. The real problem:

1. Arrow key press sends 3 bytes to the OS fd: \x1b [ B
2. select(fd) reports data available
3. sys.stdin.read(1) triggers Python's BufferedReader to call
   os.read(fd, 8192) internally, pulling ALL 3 bytes into Python's
   buffer, but only returning the first (\x1b)
4. _read_continuation retries read_char(), but each retry calls
   select(fd) which sees an EMPTY fd — the bytes are permanently
   trapped in Python's BufferedReader, invisible to select()
5. All retries fail → arrow key misinterpreted as Escape → picker
   cancels immediately

Fix: replace sys.stdin.read(1) with os.read(fd, 1) via make_raw_reader().
This bypasses Python's BufferedReader entirely, so select() and reads
operate on the same OS-level buffer. Each os.read(fd, 1) consumes
exactly 1 byte, leaving the rest on the fd for the next select() call.

Added 6 new tests (TestMakeRawReader):
- Single byte read, None on empty, byte-by-byte escape sequence reading
- Arrow key parsing through raw reader
- Multiple arrow keys in a single burst
- Full run_picker end-to-end using raw reader on a real pipe fd
sigfriedCub1990 and others added 4 commits February 22, 2026 17:27
In raw terminal mode, \n only moves the cursor down without returning
to column 0. Replace all terminal output \n with \r\n in the picker
to fix the garbled initial render.

Also fix:
- finally block bare \n causing misaligned post-picker output
- _printed_line_count to use splitlines() for robustness
- tests to use splitlines() instead of split("\n")
Tab toggles a mark on the current item and moves down; Shift-Tab
toggles and moves up (ESC [ Z, previously misread as Escape). Enter
accepts all marked files — or the highlighted one when none are
marked — and passes them all to the test runner, following fzf
semantics. Marks are stored by value so they survive re-filtering.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants