Skip to content

[#6] Added file and directory picker widgets with single and multiple selection.#31

Merged
AlexSkrypnyk merged 3 commits into
mainfrom
feature/6-file-picker
Jul 10, 2026
Merged

[#6] Added file and directory picker widgets with single and multiple selection.#31
AlexSkrypnyk merged 3 commits into
mainfrom
feature/6-file-picker

Conversation

@AlexSkrypnyk

Copy link
Copy Markdown
Member

Closes #6

Summary

Adds a filesystem-browsing widget: FilePicker collects a single path and MultiFilePicker collects a list of paths, both built on one FilePickerWidget that navigates directories, type-filters entries, and supports files-only/directories-only/any selection via a new FilePickerMode enum, with an extension allow-list, a hidden-entries toggle, and self-scrolling for large directories.
The widget resolves its keys through the repository's ScopedKeyMap/Action system rather than hardcoding them, so it honours the vim preset and any custom bindings, and its hint line is built from the live bindings instead of a fixed string.
Both field types are wired through the builder, factory, headless input resolution, and JSON schema validation, and documented in the README with generated SVGs for all four display modes.

Changes

Widget

  • New FilePickerWidget (src/Widget/FilePickerWidget.php): directory navigation ( enters the highlighted directory, returns to its parent, bounded at the start directory), type-to-filter, files-only/directories-only/any selection via FilePickerMode, a case-insensitive extension allow-list, a hidden-entries (dot-file) toggle, self-scrolling for large directories via Scroller, and multi-select accumulation across directories in multiple mode.
  • New FilePickerMode enum (src/Config/FilePickerMode.php) with Any, File, and Directory cases; a directory always stays navigable so files beneath it remain reachable even in files-only mode.

Key-map integration

  • DefaultKeyMap gains bindings for both picker scopes: Tab maps to Action::Reveal (toggles hidden entries) on both FilePicker and MultiFilePicker, and Space maps to Action::Toggle on the multiple picker.
  • Scope::TEXT_ENTRY gains FieldType::FilePicker and FieldType::MultiFilePicker, so typed printable characters filter the listing rather than being claimed by a binding.
  • The widget resolves its scope via Scope::field() and looks up actions through the active ScopedKeyMap, and its hint() method renders the hint line from those same live bindings, so both behavior and on-screen hints follow whichever key map (default or vim preset) is active.

Config, builder and factory wiring

  • Field gains pickerMode, pickerStart, pickerExtensions, and pickerShowHidden properties.
  • FieldBuilder gains start(), filesOnly(), directoriesOnly(), extensions(), and showHidden().
  • PanelBuilder gains filePicker() and multiFilePicker().
  • FieldType gains FilePicker and MultiFilePicker cases.
  • WidgetFactory instantiates FilePickerWidget for both field types, passing multiple: TRUE for the list variant.
  • InputResolver::coerce() and SchemaValidator (isType()/typeName()) treat MultiFilePicker as a list type alongside MultiSelect and MultiSearch, so headless JSON/env collection and the generated JSON schema both handle the new field types.

Docs and playground

  • README: feature list and widget count updated to reflect the two new types, plus new FilePicker and MultiFilePicker subsections with usage snippets and four-display-mode SVG tables.
  • Two new playground demos, playground/3-widgets/widget-filepicker.php and widget-multifilepicker.php, browsing a new fixture tree at playground/3-widgets/filepicker-tree/.
  • docs/util/update-assets.php registers the picker demos, with their own row counts and scripted interactions, so their SVGs regenerate alongside the rest of the widget gallery.

Tests

  • New tests/phpunit/Unit/Widget/FilePickerWidgetTest.php (vfsStream-backed) covering navigation, type-to-filter, selection modes, extension filtering, hidden entries, scrolling, and multi-select accumulation.
  • Additions to FormTest, WidgetFactoryTest, InputResolverTest, SchemaValidatorTest, and KeyMapTest covering the two new field types end to end, including their key bindings and text-entry scope classification.

Before / After

BEFORE                                        AFTER
+--------------------------+                  +----------------------------------------+
| Config file                |                | Config file                              |
| > ________________________ |                | config/                                  |
|   (free-text path entry,   |    ---->       | > app.yml                                |
|    no listing, no          |                |   app.local.yml                          |
|    filtering, easy to typo)|                |   services.yml                           |
+--------------------------+                  | move up/down  open ->  up <-  select Enter|
                                               +----------------------------------------+

Widget types (12): text, number, textarea, password, select, multiselect,
                    suggest, search, multisearch, confirm, toggle, pause
                                          |
                                          v
Widget types (14): text, number, textarea, password, select, multiselect,
                    suggest, search, multisearch, filepicker,
                    multifilepicker, confirm, toggle, pause

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 14 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ebf9b8cc-da80-4d55-abe0-525963037eec

📥 Commits

Reviewing files that changed from the base of the PR and between f5b741e and a7e4b09.

⛔ Files ignored due to path filters (8)
  • docs/assets/widget-filepicker-ascii-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widget-filepicker-ascii.svg is excluded by !**/*.svg
  • docs/assets/widget-filepicker-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widget-filepicker.svg is excluded by !**/*.svg
  • docs/assets/widget-multifilepicker-ascii-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widget-multifilepicker-ascii.svg is excluded by !**/*.svg
  • docs/assets/widget-multifilepicker-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widget-multifilepicker.svg is excluded by !**/*.svg
📒 Files selected for processing (27)
  • README.md
  • docs/util/update-assets.php
  • playground/3-widgets/filepicker-tree/README.md
  • playground/3-widgets/filepicker-tree/composer.json
  • playground/3-widgets/filepicker-tree/config/app.yml
  • playground/3-widgets/filepicker-tree/config/services.yml
  • playground/3-widgets/filepicker-tree/src/Kernel.php
  • playground/3-widgets/widget-filepicker.php
  • playground/3-widgets/widget-multifilepicker.php
  • playground/README.md
  • src/Builder/FieldBuilder.php
  • src/Builder/PanelBuilder.php
  • src/Config/Field.php
  • src/Config/FieldType.php
  • src/Config/FilePickerMode.php
  • src/Input/DefaultKeyMap.php
  • src/Input/Scope.php
  • src/Resolver/InputResolver.php
  • src/Schema/SchemaValidator.php
  • src/Widget/FilePickerWidget.php
  • src/Widget/WidgetFactory.php
  • tests/phpunit/Unit/Builder/FormTest.php
  • tests/phpunit/Unit/Input/KeyMapTest.php
  • tests/phpunit/Unit/Resolver/InputResolverTest.php
  • tests/phpunit/Unit/Schema/SchemaValidatorTest.php
  • tests/phpunit/Unit/Widget/FilePickerWidgetTest.php
  • tests/phpunit/Unit/Widget/WidgetFactoryTest.php
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/6-file-picker

Comment @coderabbitai help to get the list of available commands.

@AlexSkrypnyk AlexSkrypnyk added the A1 Board worker 1 label Jul 10, 2026
@github-actions

Copy link
Copy Markdown
Code Coverage Report:
  2026-07-10 12:52:49

 Summary:
  Classes: 91.18% (62/68)
  Methods: 97.87% (460/470)
  Lines:   99.39% (1965/1977)

DrevOps\Tui\Answers\Answer
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Answers\Answers
  Methods: 100.00% ( 9/ 9)   Lines: 100.00% ( 20/ 20)
DrevOps\Tui\Answers\SummaryFormatter
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 20/ 20)
DrevOps\Tui\Builder\FieldBuilder
  Methods: 100.00% (27/27)   Lines: 100.00% ( 87/ 87)
DrevOps\Tui\Builder\Form
  Methods:  93.33% (14/15)   Lines:  98.44% ( 63/ 64)
DrevOps\Tui\Builder\PanelBuilder
  Methods: 100.00% (19/19)   Lines: 100.00% ( 31/ 31)
DrevOps\Tui\Condition\CompositeCondition
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 19/ 19)
DrevOps\Tui\Condition\Condition
  Methods: 100.00% (10/10)   Lines: 100.00% ( 38/ 38)
DrevOps\Tui\Config\Config
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 17/ 17)
DrevOps\Tui\Config\Field
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  2/  2)
DrevOps\Tui\Config\NumberBounds
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 20/ 20)
DrevOps\Tui\Config\Option
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Config\Panel
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Derive\Derive
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 14/ 14)
DrevOps\Tui\Derive\Deriver
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% ( 13/ 13)
DrevOps\Tui\Derive\Transform
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% (  7/  7)
DrevOps\Tui\Discovery\AbstractDiscover
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Discovery\Dotenv
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 19/ 19)
DrevOps\Tui\Discovery\JsonValue
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 16/ 16)
DrevOps\Tui\Discovery\PathExists
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% (  3/  3)
DrevOps\Tui\Discovery\Scan
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 20/ 20)
DrevOps\Tui\Engine\Engine
  Methods: 100.00% (12/12)   Lines: 100.00% ( 92/ 92)
DrevOps\Tui\Handler\HandlerRegistry
  Methods:  85.71% ( 6/ 7)   Lines:  95.45% ( 21/ 22)
DrevOps\Tui\Input\ArrayKeyStream
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 12/ 12)
DrevOps\Tui\Input\Binding
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Input\DefaultKeyMap
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% ( 33/ 33)
DrevOps\Tui\Input\Key
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% (  7/  7)
DrevOps\Tui\Input\KeyMap
  Methods: 100.00% (12/12)   Lines: 100.00% ( 65/ 65)
DrevOps\Tui\Input\KeyMapManager
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  8/  8)
DrevOps\Tui\Input\KeyParser
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 68/ 68)
DrevOps\Tui\Input\Scope
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 11/ 11)
DrevOps\Tui\Input\ScopedKeyMap
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% (  4/  4)
DrevOps\Tui\Input\VimKeyMap
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  8/  8)
DrevOps\Tui\Render\Ansi
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% (  5/  5)
DrevOps\Tui\Render\Box
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 15/ 15)
DrevOps\Tui\Render\ExternalEditor
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 27/ 27)
DrevOps\Tui\Render\Navigator
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 15/ 15)
DrevOps\Tui\Render\PanelController
  Methods: 100.00% (16/16)   Lines: 100.00% ( 95/ 95)
DrevOps\Tui\Render\Scroller
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 12/ 12)
DrevOps\Tui\Render\Terminal
  Methods: 100.00% (12/12)   Lines: 100.00% ( 35/ 35)
DrevOps\Tui\Render\TerminalControl
  Methods: 100.00% ( 9/ 9)   Lines: 100.00% (  9/  9)
DrevOps\Tui\Render\Viewport
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Resolver\InputResolver
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 27/ 27)
DrevOps\Tui\Schema\AgentHelp
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 25/ 25)
DrevOps\Tui\Schema\SchemaGenerator
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 28/ 28)
DrevOps\Tui\Schema\SchemaValidator
  Methods: 100.00% ( 8/ 8)   Lines: 100.00% ( 45/ 45)
DrevOps\Tui\Theme\DefaultTheme
  Methods:  92.06% (58/63)   Lines:  97.22% (210/216)
DrevOps\Tui\Theme\ThemeManager
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  8/  8)
DrevOps\Tui\Tui
  Methods: 100.00% (12/12)   Lines: 100.00% ( 26/ 26)
DrevOps\Tui\Widget\AbstractWidget
  Methods: 100.00% (13/13)   Lines: 100.00% ( 24/ 24)
DrevOps\Tui\Widget\ConfirmWidget
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 22/ 22)
DrevOps\Tui\Widget\FilePickerWidget
  Methods: 100.00% (30/30)   Lines: 100.00% (187/187)
DrevOps\Tui\Widget\MultiSearchWidget
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Widget\MultiSelectWidget
  Methods: 100.00% (10/10)   Lines: 100.00% ( 66/ 66)
DrevOps\Tui\Widget\NumberWidget
  Methods: 100.00% ( 9/ 9)   Lines: 100.00% ( 37/ 37)
DrevOps\Tui\Widget\PasswordDisplay
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  5/  5)
DrevOps\Tui\Widget\PasswordWidget
  Methods: 100.00% ( 8/ 8)   Lines: 100.00% ( 44/ 44)
DrevOps\Tui\Widget\PauseWidget
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 10/ 10)
DrevOps\Tui\Widget\SearchWidget
  Methods:  80.00% ( 4/ 5)   Lines:  94.74% ( 36/ 38)
DrevOps\Tui\Widget\SelectWidget
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 20/ 20)
DrevOps\Tui\Widget\SuggestWidget
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 38/ 38)
DrevOps\Tui\Widget\TextWidget
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 31/ 31)
DrevOps\Tui\Widget\TextareaWidget
  Methods: 100.00% ( 8/ 8)   Lines: 100.00% ( 52/ 52)
DrevOps\Tui\Widget\ToggleWidget
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 31/ 31)
DrevOps\Tui\Widget\WidgetFactory
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 30/ 30)
DrevOps\Tui\Widget\WidgetRunner
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  5/  5)

@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.39%. Comparing base (f5b741e) to head (a7e4b09).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #31      +/-   ##
==========================================
+ Coverage   99.32%   99.39%   +0.07%     
==========================================
  Files          67       68       +1     
  Lines        1769     1977     +208     
==========================================
+ Hits         1757     1965     +208     
  Misses         12       12              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@AlexSkrypnyk AlexSkrypnyk merged commit 1b768e2 into main Jul 10, 2026
9 checks passed
@AlexSkrypnyk AlexSkrypnyk deleted the feature/6-file-picker branch July 10, 2026 12:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A1 Board worker 1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a file and directory picker widget

1 participant