Skip to content

No way to cut, copy or paste a node — ^X/^C/^V act on values and fail silently on containers #23

Description

@MarcelInTO

Summary

^X and ^V do not move nodes. They act on the selected node's value — the text inside
it — so on the elements people spend their time on they appear to do nothing at all:

  • Cut: on a <division>, or any element with children, nothing happens and nothing is said.
    On an element holding text, the text is cleared and the element stays — so "the node did not
    disappear" is correct behaviour for a value cut, and not what anyone expects from ^X on a
    selected node in a tree.
  • Paste: there is no position prompt because nothing is being placed. ^V replaces a
    node's value with the clipboard text, and on an element with children it returns silently.

fux has no node clipboard at all. That is a deliberate decision, recorded at
src/Fux/Program.cs:690:

^X copies the node's value and then clears it, as one undoable edit. It is not a node cut:
fux has no node clipboard, and deleting a subtree on a key that everywhere else means "cut
this text" is how people lose work.

The reasoning against overloading ^X still holds. The gap it leaves — no way to move a
subtree at all except by rebuilding it — does not.

Why nothing appeared to happen

Both commands take the same early exit, and it is the only path in the clipboard family that
says nothing at all:

var n = ui.Tree.SelectedObject;
if (n == null || !EditNodeValue.CanEditValue(n)) return;   // silent — Program.cs:701 and :716

CanEditValue (src/Fux/EditCommands.cs:65) is false for any element with element children,
which is every <division> and every <block> that is not pure text. Compare ^C, which does
report ("nothing to copy"), and the value-present cases, which report "cut" / "pasted" /
"clipboard empty". So the two commands the user reached for are precisely the two that fail
mutely.

That is worth fixing on its own, whatever happens to the larger feature: an operation that
declines should say so.
The messages go to the value pane's border title, which is where the
rest of the clipboard status already goes.

Proposed change: a node clipboard

Cut, copy and paste whole subtrees, with the paste asking where it goes — Below / Above / Child,
the same three positions and the same words the Snippets panel and the Insert dialog now use.

Design points that need deciding before this is built:

  • Which keys. Not Ctrl+Shift+X/C/V: legacy terminal encoding cannot distinguish those from
    ^X/^C/^V, which is why fux refuses to advertise any Ctrl+Shift+<letter> chord at all
    (readme, and the reason Save As is menu-only). Making ^X mean "cut the node" when the
    selection has no editable value is the overloading the original comment warns against — the
    same keystroke destroying a chapter or clearing a word depending on what is selected. Most
    likely answer: menu rows plus their own chords (^K? ^E?), decided deliberately.
  • What goes on the clipboard. The subtree serialized as XML text on the OS clipboard would
    let you paste between two fux instances, and paste XML written anywhere else — attractive, and
    it makes the clipboard inspectable. An internal DOM fragment is simpler and cannot produce a
    parse error on paste. The XML-text route needs a paste-time parse with a real error path.
  • Where paste asks. A prompt on every paste is a keystroke on a repeated operation; the
    Snippets panel solves the same problem with a remembered position and a list that takes
    focus. Worth reusing that shape rather than inventing a second one.
  • Does a node cut confirm? If Confirm before deleting a node that takes others with it #21 lands, a cut is a delete — but a recoverable one, since
    the subtree is on the clipboard. It probably should not prompt, and that should be a decision
    rather than an accident.

Acceptance criteria

  • ^X and ^V on a node with no editable value report why they declined, instead of
    returning silently. (Independent of everything below, and worth landing first.)
  • Cut removes a subtree and places it on the clipboard as one undoable command; ^Z
    restores it in its exact position, as DeleteNode already does.
  • Copy leaves the document untouched.
  • Paste asks for Below / Above / Child — the same vocabulary as the Insert dialog and the
    Snippets panel — and inserts the subtree there as one undoable command, re-indented to
    its new depth (the block-insert path in InsertNewNode already does exactly this).
  • Pasting into a position the DOM forbids (a sibling of the document root, an element into
    an attribute) reports rather than throwing.
  • Cutting and pasting the same subtree round-trips: the document is byte-identical to
    before if it goes back where it came from.
  • Covered by --drill, including the round-trip and the refusals, on a document where the
    subtree is bigger than the pane.

Out of scope

  • Multi-select. One node at a time.
  • Dragging nodes with the mouse.
  • Any change to what ^X/^C/^V do to values, which is well-defined, drilled, and not what
    is broken here.

Context

Reported from use on 2026-08-20; copy was not tried, so its behaviour on a container is
inferred from the shared code path rather than observed. Moving a block from one division to
another is an everyday operation when marking up a book, and today the only way to do it is
Nudge, one level and one position at a time.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions