Description
re: #249176
Issue:
Drag and Drop of notebook cell outputs was added for ease of attachment when trying to send an output attachment to copilot chat. When dragging image mime types, you simply need to drag. However, with text, if you try to implement the same thing with just needing to drag, it blocks all ability to highlgiht text without some very fiddly interactions.
The decision was then made to add an alt modifier, which would need to be held to make the textual outputs draggable. This allowed for standard text seleciton without issue for copying text, and then if the user wanted to drag they would have to hold the alt key.
However, some users are reporting that they use alternate keystrokes for copying that involve an alt key and that with HTML plaintext output, they are unable to use that shortcut to copy HTML output text. For example, if the user sets alt+c as an alternate copy keystroke, nothing is copied from HTML outputs. However it would work fine for standard plaintext outputs like a print('hello world'). This is all in python. Additionally, the standard cmd/ctrl+c keystroke works fine for copying HTML output text, it is only alt that causes the issue.
An example code that produces HTML output that cannot be copied with a keystroke involving the alt key is:
from IPython.display import display, HTML
display(HTML("<b>this is HTML</b>"))
The code adding the drag handler to the output element starts at line 2921 of src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts in the OutputElement
class
// Add drag handler
this.element.addEventListener('dragstart', (e: DragEvent) => {
if (!e.dataTransfer) {
return;
}
const outputData: NotebookCellOutputTransferData = {
outputId: this.outputId,
};
e.dataTransfer.setData('notebook-cell-output', JSON.stringify(outputData));
});
// Add alt key handlers
window.addEventListener('keydown', (e) => {
if (e.altKey) {
this.element.draggable = true;
}
});
window.addEventListener('keyup', (e) => {
if (!e.altKey) {
this.element.draggable = this.isImageOutput;
}
});
// Handle window blur to reset draggable state
window.addEventListener('blur', () => {
this.element.draggable = this.isImageOutput;
});
and here is a mermaid diagram:
classDiagram
class OutputElement {
+readonly element: HTMLElement
-_content: ContentInfo
-hasResizeObserver: boolean
-renderTaskAbort: AbortController
-isImageOutput: boolean
-readonly outputId: string
+readonly cellId: string
+constructor(outputId, left, cellId)
+dispose() void
+render(content, preferredRendererId, preloadErrors, signal) Promise~void~
+updateAndRerender(content) void
}
class ContentInfo {
+readonly preferredRendererId: string
+readonly preloadErrors: Error[]
}
class HTMLElement {
+id: string
+classList: DOMTokenList
+style: CSSStyleDeclaration
+draggable: boolean
+innerHTML: string
+offsetHeight: number
+shadowRoot: ShadowRoot
+addEventListener(type, listener) void
}
class AbortController {
+signal: AbortSignal
+abort() void
}
class DragEvent {
+dataTransfer: DataTransfer
+altKey: boolean
+target: HTMLElement
}
class NotebookCellOutputTransferData {
+outputId: string
}
OutputElement --> ContentInfo : stores
OutputElement --> HTMLElement : creates and manages
OutputElement --> AbortController : uses for cancellation
OutputElement --> DragEvent : handles
OutputElement --> NotebookCellOutputTransferData : creates for drag
flowchart TD
A[Constructor] --> B[Create DOM Element]
B --> C[Set Positioning & Styling]
C --> D[Add Event Listeners]
D --> E[Mouse Events]
D --> F[Drag Events]
D --> G[Keyboard Events]
H[render method] --> I{Content Type Check}
I -->|HTML| J[Set innerHTML with trusted HTML]
I -->|Preload Errors| K[Show Render Error]
I -->|Normal Content| L[Process Content]
L --> M[Check Image MIME Types]
M --> N[Set draggable property]
N --> O[Create Output Item]
O --> P[Setup Abort Controller]
P --> Q[Call renderers.render]
Q --> R[Setup Resize Observer]
R --> S[Calculate Dimensions]
S --> T[Update Padding & Height]
T --> U[Process Code Blocks]
V[dispose] --> W[Abort Render Task]
W --> X[Clean up Controller]
graph LR
Y[Mouse Enter] --> Z[Post mouseenter message]
AA[Mouse Leave] --> BB[Post mouseleave message]
CC[Drag Start] --> DD{Has DataTransfer?}
DD -->|Yes| EE[Create Transfer Data]
EE --> FF[Set Data Transfer]
GG[Alt Key Down] --> HH[Set draggable = true]
II[Alt Key Up] --> JJ[Set draggable = isImageOutput]
KK[Window Blur] --> LL[Reset draggable state]
stateDiagram-v2
[*] --> Created
Created --> Rendering : render()
Rendering --> AbortingRender : abort signal
Rendering --> Rendered : success
Rendered --> Rendering : updateAndRerender()
AbortingRender --> Created : cleanup
Rendered --> Disposed : dispose()
Disposed --> [*]