Skip to content

Drag drop - #2

Merged
ww-mw merged 14 commits into
mainfrom
drag-drop
Aug 12, 2026
Merged

Drag drop#2
ww-mw merged 14 commits into
mainfrom
drag-drop

Conversation

@ww-mw

@ww-mw ww-mw commented Aug 12, 2026

Copy link
Copy Markdown
Member

No description provided.

ww-mw and others added 14 commits August 11, 2026 18:59
Factor the Class->Kind maps out of DataNode/MatlabVariableNode into a
pure kindMap.ts with no model deps, so the drag/drop tooltip can render
Kind labels without a live model. Groundwork for drag-and-drop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dropDecision(source, target, mode) mirrors the cut/copy-paste rules so
the webview can render live drag feedback (cursor + hover tooltip)
without a host round-trip on every dragover:
  - accept/reject mirrors pasteEntry's allow-check (empty array-class,
    i.e. a MATLAB variable, is never rejected);
  - the tooltip's Kind labels mirror the post-paste Kind via kindForClass
    with no classification (design Bus -> arch reads "Convert Bus to
    Data Interface");
  - same-doc same-section move is a no-op (no delete/re-add);
  - any rejected item in a multi-select rejects the whole drop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pure building blocks for host-mediated drag-and-drop (dataTransfer does
not survive the webview iframe boundary, so the host holds the payloads
like the clipboard singleton):
  - dragState: the drag register + a payload-free descriptor broadcast
    to every webview for live dragover prediction;
  - pasteEntries: multi-item drop completion (fold over pasteEntry, each
    paste sees the growing namespace; all-or-nothing allow-check);
  - deleteEntriesByName: source-side of a MOVE (remove dragged entries
    from the source text, high-offset-first, absent names skipped);
  - sectionRules: per-section allow-list + derived flag posted to each
    webview so dropDecision runs client-side.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Complete the host-mediated drag-and-drop:
  - SlddTextEditorProvider: dragStart snapshots the dragged entries into
    the drag register and broadcasts a payload-free descriptor to every
    webview; drop completes as copy/cut + paste (move deletes sources,
    same-doc inline and cross-doc via the source document's own edit);
    dragEnd/dispose clear the register. Section drop-rules ship with each
    setRows.
  - dex-tree-table: a dropPredictor hook drives the live cursor + a
    cursor-following tooltip ("Convert Bus to Data Interface" /
    "Simulink Parameter cannot be in Architectural Data"); rejected and
    no-op drops are refused; the drop carries only target+mode (the
    source comes from the host register, since dataTransfer doesn't
    cross the webview boundary); section headers aren't draggable.
  - table-main: backs the predictor with the pure dropDecision over the
    broadcast descriptor + shipped section rules, and relays the drag
    lifecycle to the host.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Exercise the exact delete+paste sequence SlddTextEditorProvider.applyDrop runs,
using the pure transforms (deleteEntriesByName, pasteEntries) plus dropDecision,
so drag-and-drop behavior is proven without a live VS Code webview:

  - cross-section MOVE within a doc: source gone from arch, arrives in design as
    a genuine (isderived-cleared) entry keeping its name;
  - cross-section COPY: source stays, copy is uniquified to DataInterface1;
  - cross-DOCUMENT move: removed from doc A, present in doc B;
  - same-section move: dropDecision predicts a no-op (no delete/re-add);
  - rejected drop: dropDecision refuses and pasteEntries would throw.

The MOVE vs COPY pair documents a real, name-keyed consequence: the doc-global
SystemComposer catalog classifies entries by NAME and this editor only rewrites
the entries array, never the interfaceDictionary. A MOVE frees the source name
so the paste retains it and the catalog still labels it "Data Interface"; a COPY
uniquifies the name, sheds the classification, and the Kind falls back to "Bus".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The drag showed two overlapping affordances at the cursor: the native drag
ghost (icon + name) and a separate action tooltip. Fold them into a single
floating box — icon + name + action text — and suppress the native ghost with a
transparent drag image, so there is exactly one thing on the cursor.

Also make the verb reflect the LIVE drag mode: "Copy" while Ctrl/Cmd is held,
"Move" otherwise (was always "Copy/Move"). Conversions across design↔arch keep
their "Convert X to Y" wording, which is mode-independent.

The box now trails the cursor for the whole drag (even over the source row,
where it shows just icon + name), matching how the old ghost was always visible.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The drop handler re-read the modifier from the `drop` event to pick copy vs
move, but Chromium on macOS often reports ctrlKey/metaKey as false on `drop`
even while the key is held — so a Cmd-drag that showed "Copy" during hover
completed as a Move (source not left behind).

Track the mode on each dragover (which reports the modifier reliably) and use
that stashed mode at drop time, so the completed action matches the "Move"/
"Copy" the tooltip advertised. Reset it to move at drag start.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A Cmd-drag on macOS showed "Copy" but never dropped: reading ctrlKey/metaKey as
the copy modifier fought the OS convention. Chromium derives the native
dropEffect from the OS modifier, and on macOS Cmd means MOVE (Finder), so
setting dropEffect='copy' made the browser force 'none' and cancel the drop
before it fired — no drop event, nothing happened.

Pick the copy modifier by platform: Option (altKey) on macOS, Ctrl elsewhere.
This matches the effect the browser will actually grant, so the drop fires and
the "Copy"/"Move" tooltip agrees with what happens. Cmd-drag is now a move, as
macOS users expect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The copy-vs-move modifier convention (Option on macOS, Ctrl on Windows/Linux)
was inline in the component, where it can only be exercised on the platform the
tests happen to run on. Move it to a pure dragMode.ts (platform string + held
modifiers -> mode) and cover every platform with unit tests, so the convention
is verified for Windows and Linux too — not just the dev machine.

The component now delegates to dragModeFromModifiers(navigator.platform, ...);
behavior is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cut now only marks the entry on the host clipboard (no text edit); the
source is removed at paste time. A same-document cut becomes one combined
WorkspaceEdit (single undo step, keeps the original name); a cross-document
cut pastes into the target and deletes from the source doc via its own edit;
a same-section cut-paste is a no-op. This mirrors the drag-move flow and data
explorer's lazy ClipboardService.

The clipboard now records sourceDocUri so a deferred cut deletes from the
right document. buildRows stamps Name.clipboardMode on the cut/copied source
row so the table shows the dimmed (cut) / dashed (copied) affordance;
liveWebviews maps each webview to its repaint so a lazy cut (which makes no
text edit) still repaints to show and later clear the mark.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cover the applyPaste composition that lazy cut relies on, mirroring
dropEndToEnd for the cut/paste path: same-document cut+paste keeps the
freed name (one combined edit), same-section cut+paste is the no-op the
handler refuses, cross-document cut+paste pastes into the target and
deletes from the source, and copy+paste leaves the source and uniquifies
(and re-pastes from a retained clipboard).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Guard the payoff of lazy cut in real VS Code: a same-document move is one
full-text edit = one undo step, so a single undo restores the document
exactly (the old eager cut+paste was two steps). Also covers the
cross-document source delete applying as its own revertible edit.

The move is applied through editor.edit rather than a WorkspaceEdit because
a WorkspaceEdit-driven undo does not route to the editor's undo stack in the
headless Electron test host; both are a single edit, so the undo granularity
asserted is identical.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ww-mw
ww-mw merged commit b1f3247 into main Aug 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant