[#11] Added external editor handoff to the textarea widget.#26
Conversation
A textarea field can opt in with '->externalEditor()'. Pressing Ctrl-E while editing suspends the TUI, opens the user's $VISUAL/$EDITOR seeded with the current buffer, and captures the saved result on return; saving commits the value, an aborted edit or absent editor falls back to inline editing.
Renamed test methods to camel caps, matched the data provider to its test, wrapped over-length comments, and extracted the editor and terminal test doubles into reusable typed fixtures.
📝 WalkthroughWalkthroughThe PR adds opt-in external-editor support for textarea fields. It resolves ChangesExternal editor handoff
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 PHPStan (2.2.2)Composer install failed: the lock file is not up to date with the latest changes in composer.json. Run Comment |
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #26 +/- ##
==========================================
+ Coverage 98.92% 98.96% +0.03%
==========================================
Files 57 58 +1
Lines 1395 1445 +50
==========================================
+ Hits 1380 1430 +50
Misses 15 15 ☔ View full report in Codecov by Harness. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/phpunit/Unit/Render/SpawnStubEditor.php`:
- Around line 34-39: Rename the promoted constructor properties and all
references in SpawnStubEditor::__construct and its methods from
writeBack/deleteFile to write_back/delete_file, preserving their existing types,
defaults, and behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: c2cd1432-f121-4a6c-8182-5384c2d3c860
📒 Files selected for processing (15)
README.mdplayground/3-widgets/widget-textarea.phpsrc/Builder/FieldBuilder.phpsrc/Config/Field.phpsrc/Render/ExternalEditor.phpsrc/Render/PanelController.phpsrc/Widget/TextareaWidget.phpsrc/Widget/WidgetFactory.phptests/phpunit/Unit/Builder/FormTest.phptests/phpunit/Unit/Render/ExternalEditorTest.phptests/phpunit/Unit/Render/PanelControllerTest.phptests/phpunit/Unit/Render/RecordingTerminal.phptests/phpunit/Unit/Render/SpawnStubEditor.phptests/phpunit/Unit/Widget/TextareaWidgetTest.phptests/phpunit/Unit/Widget/WidgetFactoryTest.php
| public function __construct( | ||
| protected ?string $writeBack, | ||
| protected int $code, | ||
| protected bool $deleteFile = FALSE, | ||
| ) { | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Promoted constructor properties use camelCase instead of snake_case.
$writeBack and $deleteFile are constructor arguments (promoted to properties). The guideline calls for snake_case on method arguments; the codebase's own convention for multi-word constructor parameters (e.g. PanelController's $external_editor) follows snake_case.
♻️ Proposed rename
public function __construct(
- protected ?string $writeBack,
+ protected ?string $write_back,
protected int $code,
- protected bool $deleteFile = FALSE,
+ protected bool $delete_file = FALSE,
) {
}Update the reference at line 52 ($this->writeBack → $this->write_back) and line 49 ($this->deleteFile → $this->delete_file) accordingly.
As per coding guidelines, "Use snake_case for local variables and method arguments."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public function __construct( | |
| protected ?string $writeBack, | |
| protected int $code, | |
| protected bool $deleteFile = FALSE, | |
| ) { | |
| } | |
| public function __construct( | |
| protected ?string $write_back, | |
| protected int $code, | |
| protected bool $delete_file = FALSE, | |
| ) { | |
| } |
🧰 Tools
🪛 PHPMD (2.15.0)
[error] 37-37: The method __construct has a boolean flag argument $deleteFile, which is a certain sign of a Single Responsibility Principle violation. (undefined)
(BooleanArgumentFlag)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/phpunit/Unit/Render/SpawnStubEditor.php` around lines 34 - 39, Rename
the promoted constructor properties and all references in
SpawnStubEditor::__construct and its methods from writeBack/deleteFile to
write_back/delete_file, preserving their existing types, defaults, and behavior.
Source: Coding guidelines
Closes #11
Summary
Textarea fields can now opt into an external-editor handoff for composing long or structured text.
Calling
->externalEditor()on a textarea field lets the user pressCtrl-Ewhile editing to suspend the TUI and open their$VISUAL/$EDITOR, seeded with the current buffer.Saving and exiting the editor commits the result as the field's value; an aborted edit (a non-zero editor exit) or the absence of a configured editor falls back to the existing inline editing behaviour.
Changes
New service
DrevOps\Tui\Render\ExternalEditor, which resolves the editor command from$VISUAL/$EDITOR($VISUALwins, matching the long-standing Unix convention), reports availability viaisAvailable(), and drives the handoff inedit(): writes the buffer to a temp file, suspends the givenTerminal(restore()), spawns the editor throughproc_open(), restores the terminal (setup()) in afinallyblock so a failed spawn never strands the TUI in raw mode, then reads back and normalizes (drops one trailing newline) the saved file.edit()returnsNULL- signalling an aborted edit - when no editor is configured, the temp file or seed write fails, the editor exits non-zero, or the editor deletes the file instead of saving it.Widget + controller wiring
TextareaWidgettakes a newexternalEditconstructor flag, listens forCtrl-E(\x05) inhandle(), and exposeswantsExternalEdit()/applyExternalEdit()so a driver can perform the handoff between key events; the control byte is swallowed (never inserted into the buffer) when the flag is off.view()appends actrl-e editorfooter hint only when the handoff is enabled.WidgetFactorytakes anexternalEditorAvailableconstructor flag and only offers the handoff on aTextareawidget when both the field opted in ($field->externalEditor) and an editor is actually available.PanelControllernow owns an injectableExternalEditorinstance, passes its availability into theWidgetFactoryit constructs, tracks the activeTerminalfor the duration ofrun(), and after each key is handled on aTextareaWidget, checkswantsExternalEdit()and, if requested, runs the handoff and applies the captured buffer (orNULLon abort).Config/builder opt-in
Fieldgains a readonlybool $externalEditor = FALSEproperty, honoured only by the textarea widget.FieldBuildergainsexternalEditor(bool $enabled = TRUE): self, wired through to the builtField.Docs + playground
README.mddocuments->externalEditor()and theCtrl-Ebehaviour.playground/3-widgets/widget-textarea.phpconstructs anExternalEditor, passes its availability into theTextareaWidget, and performs the handoff inline in its key loop.Tests
ExternalEditorTestcovering command resolution precedence and trimming, availability, and everyedit()branch: no editor, temp file failure, seed write failure, captured value with terminal suspend/restore, no terminal, non-zero exit, deleted file, and trailing-newline normalization.TextareaWidgetTest,WidgetFactoryTest,PanelControllerTest, andFormTestgain coverage for the Ctrl-E request/swallow behaviour, the opt-in x availability matrix, commit/abort through the controller, and footer hint visibility.Before / After
Summary by CodeRabbit
New Features
$VISUALor$EDITORwithCtrl-E.Documentation