Skip to content

copilot -- Copying notebook HTML output not working for some keyboard shortcuts. #251433

@Yoyokrazy

Description

@Yoyokrazy

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
Loading
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]
Loading
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]
Loading
stateDiagram-v2
    [*] --> Created
    Created --> Rendering : render()
    Rendering --> AbortingRender : abort signal
    Rendering --> Rendered : success
    Rendered --> Rendering : updateAndRerender()
    AbortingRender --> Created : cleanup
    Rendered --> Disposed : dispose()
    Disposed --> [*]
Loading

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions