ahel is live on Product Hunt today. Upvote

Gum Copy/Paste Reference

SkillDev tools

Gum copy/paste. Triggers: CopyPasteLogic, ICopyPasteLogic, OnCopy/OnPaste/OnCut, PasteInstanceSaves, CopiedData, clipboard behavior, multi-paste selection tracking.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Gum Copy/Paste Reference skill

What this skill tells your AI

The instructions your AI receives, as published by vchelaru/gum in .claude/skills/gum-tool-copy-paste/SKILL.md and read by ahel’s review.

In-Memory, Not System Clipboard

Gum's copy/paste does not use the system clipboard. Copied data lives in CopyPasteLogic.CopiedData (an in-memory instance). MonoGameGum/Clipboard/ClipboardImplementation.cs is a separate text-only clipboard used elsewhere in the app — not part of this system.

CopyType Dispatch

CopyType drives the entire operation:

  • InstanceOrElement — copies selected instances within an element, or a whole element if nothing is selected
  • State — copies the selected state within a state category

Copy, cut, and paste each accept a CopyType parameter and bail out early if it doesn't match what's stored.

TopOrRecursive

Paste offers two modes: Recursive (instances + all descendants) vs Top (only top-level instances). The UI exposes both as separate "Paste" and "Paste Top Level Instance(s)" menu items.

Cut Is Immediate

Cut calls StoreCopiedObject() (same as copy) then immediately deletes the source instances via IDeleteLogic.RemoveInstance(). The deletion happens at cut time, not deferred to paste. This means cutting and then not pasting still destroys the original.

Multi-Paste Selection Tracking

_hasChangedSelectionSinceCopy is the key flag for multi-paste behavior. It starts false after a copy and flips true when SelectionChangedMessage fires (unless the selection change was triggered by the paste itself — isSelectionCausedByPaste guards against that false-positive).

During paste, this flag drives parent assignment:

  • false — re-use the parent from lastPasteOriginalToParentAssociation (repeat-paste to same location)
  • true — attach to current selection (user moved to a new target)

Paste Creates New Instances, Does Not Clone

PasteInstanceSaves() creates fresh InstanceSave objects and assigns properties from the copied states. It does not simply clone the stored instances. Name uniqueness is enforced via StringFunctions.MakeStringUnique(), and an oldNewNameDictionary is built to remap parent references throughout the copied hierarchy.

Base-Element Capture is Filtered, Not Wholesale

StoreCopiedInstances captures the source's own selected state into CopiedStates AND the default state of every base element in the source's BaseType chain into a separate CopiedBaseElementDefaultStates list. The base captures exist so the new instance keeps inherited effective values (e.g. Text="Click Me" from a base) even after renaming/relocation, where the regular inheritance walk would no longer reach them.

At paste time, base captures are filtered by CopiedNamesOwnedByReachableStates — the set of qualified item names owned by any reachable categorized state on the source instance. "Owned" covers three shapes:

  1. Local VariableReferences rows on the source state targeting this instance — their LHSes are scalar names owned.
  2. The matched categorized state of the instance's BaseType (e.g. TextCategoryState = "Title" on a Label instance → Label.Title, with the BaseType chain walked via GetStateSaveRecursively):
    • LHSes from its VariableReferences rows are owned scalars.
    • Direct scalar variables on the state are owned (this is what catches the broader case where the state sets values directly without a ref — e.g. Label.Title.Variables["FontSize"] = 28).
    • If the matched state has any VariableReferences row, the qualified list marker "<instance>.VariableReferences" is also added so paste drops the base-snapshotted reference row that would otherwise shadow the categorized walk.

Anything in a base capture whose name matches the owned set is dropped rather than snapshotted. The new instance picks up the active categorized state's contribution via the normal GetValueRecursive / GetVariableListRecursive walks instead of carrying the source's base-level orphans.

The walk is implemented in ElementSaveExtensions.GetItemNamesOwnedByReachableCategorizedStates(StateSave, InstanceSave). The shape mirrors HeadlessErrorChecker's GUM0002 check, though GUM0002 currently only flags the reference-owned subset; broadening the error check to direct-scalar conflicts is a separate piece of work.

Variables and lists on the source's own selected state are never filtered — directly-authored values pass through untouched even when they conflict with a reachable reference. GUM0002 handles surfacing that conflict at the source; copy/paste preserves author intent.

Undo Integration

Paste and cut each acquire an undo lock (_undoManager.RequestLock()) for their whole operation. The entire paste — all instances, state variable copies, parent assignments — records as a single undo action; a cut records the removal of every cut instance as one action. IDeleteLogic.RemoveInstance() does not take a lock of its own, so any caller that removes through it must hold one (#4691).

State Paste Validation

Pasting a state runs ValidateStatePaste() first. It checks that all variables in the copied state exist in the target category and are supported by the target element. Paste is silently blocked (or shows a dialog) if incompatible.

Plugin Hook

PluginManager.InstanceAdd() is called for each newly pasted instance, allowing plugins to react to paste-created instances the same way they react to manually added ones.

Animation Copy/Paste

AnimationCopyPasteManager.cs is a separate, independent copy/paste system for animations. It has its own CopiedData class, stores a single AnimationViewModel, and does not interact with CopyPasteLogic.

Entry Points

All copy/paste is triggered through:

  • HotkeyManager — routes Ctrl+C / Ctrl+X / Ctrl+VICopyPasteLogic.OnCopy / OnCut / OnPaste
  • ElementTreeViewManager.RightClick — right-click context menu items

Do not call CopyPasteLogic methods directly from outside these entry points.

Key Files

FilePurpose
Tools/Gum.Presentation/Logic/CopyPasteLogic.csAll copy/paste orchestration; CopiedData nested class defined here
Tools/Gum.Presentation/Logic/ICopyPasteLogic.csInterface
Tools/Gum.Presentation/Commands/EditCommands.csThin wrappers that delegate to ICopyPasteLogic
Tools/Gum.Presentation/Managers/HotkeyManager.csKeyboard entry point
Gum/Plugins/InternalPlugins/TreeView/ElementTreeViewManager.RightClick.csContext menu entry point
Gum/StateAnimationPlugin/Managers/AnimationCopyPasteManager.csSeparate animation copy/paste
Tool/Tests/GumToolUnitTests/Logic/CopyPasteLogicTests.csUnit tests

Signals

GitHub stars
620
Forks
80
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
gum-tool-copy-paste
Source
github.com/vchelaru/gum