Repository navigation
Add clipboard shortcuts to immediate text fields - #41
Conversation
medvednikov
left a comment
There was a problem hiding this comment.
Reviewed the changed input handlers, editor state updates, existing custom-renderer tests, and V's clipboard implementations. Two actionable findings are inline: cut must preserve the selection when the clipboard write fails, and the newly required character-event modifier argument needs to be supplied by the existing test caller.
Validation: source-level review only; I could not run V compilation or platform clipboard tests in this environment. The one-argument handle_key_down calls in the same test already exist in the base commit and are not counted as regressions introduced by this PR.
| cb.copy(selected) | ||
| editor.replace_selection('') |
There was a problem hiding this comment.
[P2] Preserve selected text when the clipboard write fails
clipboard.Clipboard.copy() returns a success boolean, but this branch deletes the selection regardless of that result. For example, V's Windows backend returns false when it cannot acquire the clipboard lock or SetClipboardData fails; Ctrl+X would then remove the user's text and fire the change event without putting that text on the clipboard. Please only call replace_selection and publish the editor/change updates after a successful copy (for example, return true without changing the editor when !cb.copy(selected)). A failed-copy regression test should assert that both the text and selection are preserved.
There was a problem hiding this comment.
Fixed in e1c0875. Cut now only edits the field after cb.copy() returns true; on failure the text and selection stay untouched and no change event fires.
To make that testable without the system clipboard, the shortcut handler reads through a small TextClipboard interface (copy/paste) and tests install a fake via g_clipboard_override. test_cut_keeps_the_text_when_the_clipboard_rejects_it asserts the text, the selection range and the absence of a change event; I verified it fails against the previous unconditional cut.
| } | ||
|
|
||
| fn handle_char_input(ch u32) { | ||
| fn handle_char_input(ch u32, modifiers u32) { |
There was a problem hiding this comment.
[P2] Update the existing test caller for the new required argument
test_custom_text_editor_replaces_owned_state_after_caret_moves in ui/ui_custom_test.v still calls handle_char_input(x) with one argument. With ui2_custom_rendering enabled, this signature change introduces an additional test-compilation error because modifiers is now required. Please update that call to handle_char_input(x, 0) and exercise the modifier-filtering cases here as well (Ctrl/Super shortcut characters are suppressed, Ctrl+Alt text is retained, and DEL is ignored). The reported -check ui/ verification does not exercise this _test.v caller.
There was a problem hiding this comment.
Done in e1c0875, with one wrinkle: ui_custom_test.v can't compile regardless of the argument count. It sorts before ui_immediate.c.v, so it never sees the renderer globals (g_text_values, g_focused_field, …), it doesn't import gg for the key codes, and its typed map[string]bool{ ... } literals are rejected by 0.5.2 too. It isn't in CI, which is why nobody noticed.
Rather than patch the one call in a file that still can't build, I moved the caret-move test into a new ui/ui_text_clipboard_immediate_test.v that follows the ui_pointer_capture_immediate_test.v pattern (sorts after the renderer, @[has_globals], same platform guard). Besides the original assertion it covers:
handle_char_input: Ctrl+V / Cmd+V / Ctrl+Shift+V letters are dropped, DEL (127) and BS (8) are dropped, Ctrl+Alt@(AltGr) and Shift+aare kept, and change events fire only for real insertions- copy/cut/paste through the fake clipboard, newline flattening in single-line fields vs. text areas, empty-paste being a no-op, and chords without the primary modifier not touching the clipboard
CI runs the new file on the same matrix entries as the scrolling test. I left the rest of ui_custom_test.v alone since that's unrelated to this PR; happy to fix it up separately.
medvednikov
left a comment
There was a problem hiding this comment.
Reviewed e1c0875, ignoring CI as requested. The two earlier findings are addressed: failed clipboard writes now preserve the cut selection, and the moved test supplies the required input-handler arguments and covers modifier filtering.
One remaining correctness issue is inline: copy/cut exports the plaintext from secure password fields.
Validation: source-level review of the changed handlers/tests, editor ownership and selection logic, rendering/focus paths, and V's clipboard backends. V compilation and live platform clipboard tests were not run because the V compiler is unavailable in this environment.
| .c { | ||
| selected := editor_selected_text(editor) | ||
| if selected.len > 0 { | ||
| cb.copy(selected) |
There was a problem hiding this comment.
[P2] Prevent copy and cut from exporting secure-field contents
This path also runs for text_input(password: true, multiline: false) / Element.secure fields. Those fields remain focusable and selectable, and masking happens only in text_field_display_text; editor_selected_text(editor) reads the underlying plaintext. Consequently, focusing a password field containing secret, selecting all, and pressing Ctrl/Cmd+C (or X in the branch below) publishes secret to the system clipboard, where another application or clipboard history can read it. Please retain the focused field's secure flag and consume copy/cut without writing the clipboard or changing the selection/text when it is set; paste should remain available. Add a secure-field regression case to the fake-clipboard tests.
There was a problem hiding this comment.
Good catch — fixed in 9b18122.
The handler had no way to know: g_focused_field is just an id, and el.secure was only read at draw time in text_field_display_text. So the flag is now retained per field in g_text_secure, written next to g_text_kinds in both editable render branches and dropped in forget_text_state. handle_clipboard_shortcut bails out for .c/.x on a secure field before it even reaches system_clipboard(), so nothing is written, the text and selection are untouched, and the chord is still consumed rather than falling through to the key handler. Paste stays available so a password manager can fill the field.
test_secure_fields_never_export_their_contents covers it: copy and cut on a selected secret leave copies == 0, the clipboard contents unchanged, the text and the selection range intact, and no change event; paste then still works; and the same field without the flag copies as before. Verified it fails with the guard removed.
Note this is the custom/gg renderer only — the native macOS path uses NSSecureTextField, which handles this itself.
Cmd/Ctrl+C, X and V now copy, cut and paste in the focused text field of the immediate renderer, with pasted newlines flattened to spaces in single-line fields. Char events are filtered by modifier so the chord's letter is not also inserted as text: macOS delivers 'v' for Cmd+V and X11 delivers it for Ctrl+V. Ctrl+Alt is deliberately allowed through because Windows reports AltGr that way and AltGr characters are genuine text. macOS Backspace (reported as 0x7F) is dropped as well. The clipboard is a single lazily-created instance: on X11 the clipboard window owns the CLIPBOARD selection, so a per-keystroke instance that is destroyed right after copying would hand ownership back to nobody.
clipboard.Clipboard.copy reports failure (the Windows backend returns false when it cannot lock the clipboard or SetClipboardData fails), but Ctrl+X removed the selected text regardless, so a failed cut destroyed the user's text without putting it anywhere. The cut now only edits the field after a successful copy. The shortcut handler reads the clipboard through a small TextClipboard interface, and tests install a fake via g_clipboard_override. The new ui_text_clipboard_immediate_test.v covers copy, cut, paste, the failed copy, newline flattening in single-line fields, the modifier filtering in handle_char_input (Ctrl/Super chord letters and DEL dropped, Ctrl+Alt text kept), and the caret-move test that used to live in ui_custom_test.v with a now-stale one-argument call. That file sorts before ui_immediate.c.v so it cannot see the renderer globals at all; the test moved to a file that can. CI runs the new test on the same matrix entries as the scrolling test. Co-Authored-By: Claude <noreply@anthropic.com>
text_field(password: true) keeps its plaintext in the editor and masks only in text_field_display_text, so Ctrl/Cmd+C or +X on a focused password field published the password to the system clipboard, where another application or clipboard history could read it. Retain each editable field's secure flag in g_text_secure alongside g_text_kinds, and have the shortcut handler consume copy and cut for a secure field without reaching the clipboard or touching the text and selection. Paste stays available so a password manager can still fill the field.
9b18122 to
9151053
Compare
Summary
handle_char_inputnow takes the event modifiers and drops the chord's letter, which macOS (Cmd+V→'v') and X11 (Ctrl+V→'v') both deliver as a char event. Ctrl+Alt passes through because Windows reports AltGr that way and AltGr characters (@,€, …) are real text. macOS Backspace (0x7F) is dropped too.system_clipboard()). On X11 the clipboard window holds CLIPBOARD selection ownership, so a per-keystrokenew()/destroy()would surrender it immediately and also leak a display connection and listener thread per press.Test plan
v fmt -verifyandv -d ui2_custom_rendering -check ui/passv; Backspace does not insertDEL