diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index d765620746f..868b77ade0e 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -11,7 +11,7 @@ import { TimeoutTimer } from 'vs/base/common/async'; import { CharCode } from 'vs/base/common/charCode'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { coalesce } from 'vs/base/common/arrays'; @@ -1157,3 +1157,18 @@ export function windowOpenNoOpener(url: string): void { } } } + +export function animate(fn: () => void): IDisposable { + const step = () => { + fn(); + stepDisposable = scheduleAtNextAnimationFrame(step); + }; + + let stepDisposable = scheduleAtNextAnimationFrame(step); + return toDisposable(() => stepDisposable.dispose()); +} + +export function timeout(fn: () => void, millis: number): IDisposable { + const timer = setTimeout(fn, millis); + return toDisposable(() => clearTimeout(timer)); +} \ No newline at end of file diff --git a/src/vs/base/browser/ui/list/list.css b/src/vs/base/browser/ui/list/list.css index a651191df53..55038eb39b6 100644 --- a/src/vs/base/browser/ui/list/list.css +++ b/src/vs/base/browser/ui/list/list.css @@ -52,4 +52,13 @@ /* Focus */ .monaco-list.element-focused, .monaco-list.selection-single, .monaco-list.selection-multiple { outline: 0 !important; +} + +/* Dnd */ +.monaco-list-drag-image { + display: inline-block; + padding: 1px 7px; + border-radius: 10px; + font-size: 12px; + position: absolute; } \ No newline at end of file diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index 1db53632a68..1d502b622c7 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -7,6 +7,7 @@ import { GestureEvent } from 'vs/base/browser/touch'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { DragMouseEvent } from 'vs/base/browser/mouseEvent'; export interface IListVirtualDelegate { getHeight(element: T): number; @@ -62,6 +63,45 @@ export interface IKeyboardNavigationLabelProvider { mightProducePrintableCharacter?(event: IKeyboardEvent): boolean; } +export const enum DragOverEffect { + Copy, + Move +} + +// export const enum DragOverBubble { +// Down, +// Up +// } + +export interface IDragOverReaction { + accept: boolean; + effect?: DragOverEffect; + // bubble?: DragOverBubble; + // autoExpand?: boolean; +} + +export const DragOverReactions = { + reject(): IDragOverReaction { return { accept: false }; }, + accept(): IDragOverReaction { return { accept: true }; }, + // acceptBubbleUp(): IDragOverReaction { return { accept: true, bubble: DragOverBubble.Up }; }, + // acceptBubbleDown(autoExpand = false): IDragOverReaction { return { accept: true, bubble: DragOverBubble.Down, autoExpand }; }, + // acceptCopyBubbleUp(): IDragOverReaction { return { accept: true, bubble: DragOverBubble.Up, effect: DragOverEffect.Copy }; }, + // acceptCopyBubbleDown(autoExpand = false): IDragOverReaction { return { accept: true, bubble: DragOverBubble.Down, effect: DragOverEffect.Copy, autoExpand }; } +}; + +export interface IDragAndDropData { + update(event: DragMouseEvent): void; + getData(): any; +} + +export interface IDragAndDrop { + getDragURI(element: T): string | null; + getDragLabel?(elements: T[]): string; + onDragStart(data: IDragAndDropData, originalEvent: DragMouseEvent): void; + onDragOver(data: IDragAndDropData, targetElement: T, originalEvent: DragMouseEvent): boolean | IDragOverReaction; + drop(data: IDragAndDropData, targetElement: T, originalEvent: DragMouseEvent): void; +} + /** * Use this renderer when you want to re-render elements on account of * an event firing. diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index e62db1ff2eb..6062e33ebdc 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { getOrDefault } from 'vs/base/common/objects'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { Gesture, EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; import * as DOM from 'vs/base/browser/dom'; import { Event, Emitter } from 'vs/base/common/event'; @@ -12,7 +12,7 @@ import { domEvent } from 'vs/base/browser/event'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollEvent, ScrollbarVisibility, INewScrollDimensions } from 'vs/base/common/scrollable'; import { RangeMap, shift } from './rangeMap'; -import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListTouchEvent, IListGestureEvent } from './list'; +import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListTouchEvent, IListGestureEvent, IDragAndDrop } from './list'; import { RowCache, IRow } from './rowCache'; import { isWindows } from 'vs/base/common/platform'; import * as browser from 'vs/base/browser/browser'; @@ -41,9 +41,12 @@ interface IItem { size: number; hasDynamicHeight: boolean; renderWidth: number | undefined; + uri: string | undefined; + dragStartDisposable: IDisposable; } -export interface IListViewOptions { +export interface IListViewOptions { + readonly dnd?: IDragAndDrop; readonly useShadows?: boolean; readonly verticalScrollMode?: ScrollbarVisibility; readonly setRowLineHeight?: boolean; @@ -55,7 +58,13 @@ const DefaultOptions = { useShadows: true, verticalScrollMode: ScrollbarVisibility.Auto, setRowLineHeight: true, - supportDynamicHeights: false + supportDynamicHeights: false, + dnd: { + getDragURI() { return null; }, + onDragStart(): void { }, + onDragOver() { return false; }, + drop() { } + } }; export class ListView implements ISpliceable, IDisposable { @@ -76,22 +85,26 @@ export class ListView implements ISpliceable, IDisposable { private _scrollHeight: number; private scrollableElementUpdateDisposable: IDisposable | null = null; private splicing = false; - private dragAndDropScrollInterval: number; - private dragAndDropScrollTimeout: number; - private dragAndDropMouseY: number; + private dragOverAnimationDisposable: IDisposable | undefined; + private dragOverAnimationStopDisposable: IDisposable = Disposable.None; + private dragOverMouseY: number; private setRowLineHeight: boolean; private supportDynamicHeights: boolean; + private readonly dnd: IDragAndDrop; private disposables: IDisposable[]; private _onDidChangeContentHeight = new Emitter(); readonly onDidChangeContentHeight: Event = Event.latch(this._onDidChangeContentHeight.event); get contentHeight(): number { return this.rangeMap.size; } + private _onDragStart = new Emitter<{ element: T, uri: string, event: DragMouseEvent }>(); + readonly onDragStart = this._onDragStart.event; + constructor( container: HTMLElement, private virtualDelegate: IListVirtualDelegate, renderers: IListRenderer[], - options: IListViewOptions = DefaultOptions + options: IListViewOptions = DefaultOptions ) { this.items = []; this.itemId = 0; @@ -136,9 +149,12 @@ export class ListView implements ISpliceable, IDisposable { const onDragOver = Event.map(domEvent(this.rowsContainer, 'dragover'), e => new DragMouseEvent(e)); onDragOver(this.onDragOver, this, this.disposables); + const onDragEnd = Event.map(domEvent(document, 'dragend'), e => new DragMouseEvent(e)); + onDragEnd(this.onDragEnd, this, this.disposables); this.setRowLineHeight = getOrDefault(options, o => o.setRowLineHeight, DefaultOptions.setRowLineHeight); this.supportDynamicHeights = getOrDefault(options, o => o.supportDynamicHeights, DefaultOptions.supportDynamicHeights); + this.dnd = getOrDefault, IDragAndDrop>(options, o => o.dnd, DefaultOptions.dnd); this.layout(); } @@ -178,7 +194,9 @@ export class ListView implements ISpliceable, IDisposable { size: this.virtualDelegate.getHeight(element), hasDynamicHeight: !!this.virtualDelegate.hasDynamicHeight && this.virtualDelegate.hasDynamicHeight(element), renderWidth: undefined, - row: null + row: null, + uri: undefined, + dragStartDisposable: Disposable.None })); let deleted: IItem[]; @@ -354,6 +372,15 @@ export class ListView implements ISpliceable, IDisposable { const renderer = this.renderers.get(item.templateId); renderer.renderElement(item.element, index, item.row.templateData); + + const uri = this.dnd.getDragURI(item.element); + item.dragStartDisposable.dispose(); + + if (uri) { + item.row.domNode!.draggable = true; + const onDragStart = Event.map(domEvent(item.row.domNode!, 'dragstart'), e => new DragMouseEvent(e)); + item.dragStartDisposable = onDragStart(event => this._onDragStart.fire({ element: item.element, uri, event }), this); + } } private updateItemInDOM(item: IItem, index: number): void { @@ -372,6 +399,8 @@ export class ListView implements ISpliceable, IDisposable { private removeItemFromDOM(index: number): void { const item = this.items[index]; + item.dragStartDisposable.dispose(); + const renderer = this.renderers.get(item.templateId); if (renderer.disposeElement) { @@ -464,55 +493,44 @@ export class ListView implements ISpliceable, IDisposable { this.scrollTop -= event.translationY; } + // DND + private onDragOver(event: DragMouseEvent): void { - this.setupDragAndDropScrollInterval(); - this.dragAndDropMouseY = event.posy; + if (!this.dragOverAnimationDisposable) { + const viewTop = DOM.getTopLeftOffset(this.domNode).top; + this.dragOverAnimationDisposable = DOM.animate(this.animateDragAndDropScrollTop.bind(this, viewTop)); + } + + this.dragOverAnimationStopDisposable.dispose(); + this.dragOverAnimationStopDisposable = DOM.timeout(() => { + this.dragOverAnimationDisposable.dispose(); + this.dragOverAnimationDisposable = undefined; + }, 1000); + + this.dragOverMouseY = event.posy; } - private setupDragAndDropScrollInterval(): void { - const viewTop = DOM.getTopLeftOffset(this.domNode).top; + private animateDragAndDropScrollTop(viewTop: number): void { + if (this.dragOverMouseY === undefined) { + return; + } - if (!this.dragAndDropScrollInterval) { - this.dragAndDropScrollInterval = window.setInterval(() => { - if (this.dragAndDropMouseY === undefined) { - return; - } + const diff = this.dragOverMouseY - viewTop; + const upperLimit = this.renderHeight - 35; - let diff = this.dragAndDropMouseY - viewTop; - let scrollDiff = 0; - let upperLimit = this.renderHeight - 35; - - if (diff < 35) { - scrollDiff = Math.max(-14, 0.2 * (diff - 35)); - } else if (diff > upperLimit) { - scrollDiff = Math.min(14, 0.2 * (diff - upperLimit)); - } - - this.scrollTop += scrollDiff; - }, 10); - - this.cancelDragAndDropScrollTimeout(); - - this.dragAndDropScrollTimeout = window.setTimeout(() => { - this.cancelDragAndDropScrollInterval(); - this.dragAndDropScrollTimeout = -1; - }, 1000); + if (diff < 35) { + this.scrollTop += Math.max(-14, Math.floor(0.3 * (diff - 35))); + } else if (diff > upperLimit) { + this.scrollTop += Math.min(14, Math.floor(0.3 * (diff - upperLimit))); } } - private cancelDragAndDropScrollInterval(): void { - if (this.dragAndDropScrollInterval) { - window.clearInterval(this.dragAndDropScrollInterval); - this.dragAndDropScrollInterval = -1; - } + private onDragEnd(): void { + this.dragOverAnimationStopDisposable.dispose(); - this.cancelDragAndDropScrollTimeout(); - } - - private cancelDragAndDropScrollTimeout(): void { - if (this.dragAndDropScrollTimeout) { - window.clearTimeout(this.dragAndDropScrollTimeout); - this.dragAndDropScrollTimeout = -1; + if (this.dragOverAnimationDisposable) { + this.dragOverAnimationDisposable.dispose(); + this.dragOverAnimationDisposable = undefined; } } diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 29b226215c7..e83d5aa2164 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -16,15 +16,17 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Event, Emitter, EventBufferer } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; -import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider } from './list'; +import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider, IDragAndDrop, IDragAndDropData } from './list'; import { ListView, IListViewOptions } from './listView'; import { Color } from 'vs/base/common/color'; -import { mixin } from 'vs/base/common/objects'; +import { mixin, getOrDefault } from 'vs/base/common/objects'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { ISpliceable } from 'vs/base/common/sequence'; import { CombinedSpliceable } from 'vs/base/browser/ui/list/splice'; import { clamp } from 'vs/base/common/numbers'; import { matchesPrefix } from 'vs/base/common/filters'; +import { DragMouseEvent } from 'vs/base/browser/mouseEvent'; +import { DataTransfers } from 'vs/base/browser/dnd'; interface ITraitChangeEvent { indexes: number[]; @@ -658,11 +660,17 @@ export class DefaultStyleController implements IStyleController { } if (styles.listFocusAndSelectionBackground) { - content.push(`.monaco-list${suffix}:focus .monaco-list-row.selected.focused { background-color: ${styles.listFocusAndSelectionBackground}; }`); + content.push(` + .monaco-list-drag-image, + .monaco-list${suffix}:focus .monaco-list-row.selected.focused { background-color: ${styles.listFocusAndSelectionBackground}; } + `); } if (styles.listFocusAndSelectionForeground) { - content.push(`.monaco-list${suffix}:focus .monaco-list-row.selected.focused { color: ${styles.listFocusAndSelectionForeground}; }`); + content.push(` + .monaco-list-drag-image, + .monaco-list${suffix}:focus .monaco-list-row.selected.focused { color: ${styles.listFocusAndSelectionForeground}; } + `); } if (styles.listInactiveFocusBackground) { @@ -692,7 +700,10 @@ export class DefaultStyleController implements IStyleController { } if (styles.listFocusOutline) { - content.push(`.monaco-list${suffix}:focus .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }`); + content.push(` + .monaco-list-drag-image, + .monaco-list${suffix}:focus .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; } + `); } if (styles.listInactiveFocusOutline) { @@ -703,6 +714,14 @@ export class DefaultStyleController implements IStyleController { content.push(`.monaco-list${suffix} .monaco-list-row:hover { outline: 1px dashed ${styles.listHoverOutline}; outline-offset: -1px; }`); } + // TODO + // if (styles.listDropBackground) { + // content.push(` + // .monaco-tree${suffix} .monaco-tree-wrapper.drop-target, + // .monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row.drop-target { background-color: ${styles.listDropBackground} !important; color: inherit !important; } + // `); + // } + const newStyles = content.join('\n'); if (newStyles !== this.styleElement.innerHTML) { this.styleElement.innerHTML = newStyles; @@ -710,8 +729,9 @@ export class DefaultStyleController implements IStyleController { } } -export interface IListOptions extends IListViewOptions, IListStyles { +export interface IListOptions extends IListViewOptions, IListStyles { identityProvider?: IIdentityProvider; + dnd?: IDragAndDrop; keyboardNavigationLabelProvider?: IKeyboardNavigationLabelProvider; ariaLabel?: string; mouseSupport?: boolean; @@ -757,7 +777,13 @@ const defaultStyles: IListStyles = { const DefaultOptions: IListOptions = { keyboardSupport: true, mouseSupport: true, - multipleSelectionSupport: true + multipleSelectionSupport: true, + dnd: { + getDragURI() { return null; }, + onDragStart(): void { }, + onDragOver() { return false; }, + drop() { } + } }; // TODO@Joao: move these utils into a SortedArray class @@ -912,8 +938,73 @@ class AccessibiltyRenderer implements IListRenderer { } } +export class ElementsDragAndDropData implements IDragAndDropData { + + private elements: T[]; + + constructor(elements: T[]) { + this.elements = elements; + } + + public update(event: DragMouseEvent): void { + // no-op + } + + public getData(): any { + return this.elements; + } +} + +export class ExternalElementsDragAndDropData implements IDragAndDropData { + + private elements: T[]; + + constructor(elements: T[]) { + this.elements = elements; + } + + public update(event: DragMouseEvent): void { + // no-op + } + + public getData(): any { + return this.elements; + } +} + +export class DesktopDragAndDropData implements IDragAndDropData { + + private types: any[]; + private files: any[]; + + constructor() { + this.types = []; + this.files = []; + } + + public update(event: DragMouseEvent): void { + if (event.dataTransfer.types) { + this.types = [...event.dataTransfer.types]; + } + + if (event.dataTransfer.files) { + this.files = [...event.dataTransfer.files]; + this.files = this.files.filter(f => f.size || f.type); + } + } + + public getData(): any { + return { + types: this.types, + files: this.files + }; + } +} + export class List implements ISpliceable, IDisposable { + private static currentExternalDragAndDropData: IDragAndDropData | undefined; + private static InstanceCount = 0; private idPrefix = `list_id_${++List.InstanceCount}`; @@ -922,10 +1013,14 @@ export class List implements ISpliceable, IDisposable { private eventBufferer = new EventBufferer(); private view: ListView; private spliceable: ISpliceable; - protected disposables: IDisposable[]; private styleElement: HTMLStyleElement; private styleController: IStyleController; + private readonly dnd: IDragAndDrop; + private currentDragAndDropData: IDragAndDropData | undefined; + + protected disposables: IDisposable[]; + @memoize get onFocusChange(): Event> { return Event.map(this.eventBufferer.wrapEvent(this.focus.onChange), e => this.toListEvent(e)); } @@ -1009,6 +1104,7 @@ export class List implements ISpliceable, IDisposable { ) { this.focus = new FocusTrait(i => this.getElementDomId(i)); this.selection = new Trait('selected'); + this.dnd = getOrDefault, IDragAndDrop>(options, o => o.dnd, DefaultOptions.dnd); mixin(options, defaultStyles, false); @@ -1056,6 +1152,8 @@ export class List implements ISpliceable, IDisposable { this.disposables.push(new MouseController(this, this.view, options)); } + this.view.onDragStart(this.onDragStart, this, this.disposables); + this.onFocusChange(this._onFocusChange, this, this.disposables); this.onSelectionChange(this._onSelectionChange, this, this.disposables); @@ -1342,6 +1440,37 @@ export class List implements ISpliceable, IDisposable { DOM.toggleClass(this.view.domNode, 'selection-multiple', selection.length > 1); } + // DND + + private onDragStart({ element, uri, event }: { element: T, uri: string, event: DragMouseEvent }): void { + const selection = this.getSelectedElements(); + const elements = selection.indexOf(element) > -1 ? selection : [element]; + + event.dataTransfer.effectAllowed = 'copyMove'; + event.dataTransfer.setData(DataTransfers.RESOURCES, JSON.stringify([uri])); + + if (event.dataTransfer.setDragImage) { + let label: string; + + if (this.dnd.getDragLabel) { + label = this.dnd.getDragLabel(elements); + } else { + label = String(elements.length); + } + + const dragImage = DOM.$('.monaco-list-drag-image'); + dragImage.textContent = label; + document.body.appendChild(dragImage); + event.dataTransfer.setDragImage(dragImage, -10, -10); + setTimeout(() => document.body.removeChild(dragImage), 0); + } + + this.currentDragAndDropData = new ElementsDragAndDropData(elements); + List.currentExternalDragAndDropData = new ExternalElementsDragAndDropData(elements); + + this.dnd.onDragStart(this.currentDragAndDropData, event); + } + dispose(): void { this._onDidDispose.fire(); this.disposables = dispose(this.disposables); diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 09cc5afa457..0ece88b2d95 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -22,6 +22,23 @@ function asListOptions(options?: IAbstractTreeOptions { + return options.dnd!.getDragLabel!(nodes.map(node => node.element)); + }), + onDragStart(data, originalEvent) { + return options.dnd!.onDragStart(data, originalEvent); + }, + onDragOver(data, targetNode, originalEvent) { + return options.dnd!.onDragOver(data, targetNode.element, originalEvent); + }, + drop(data, targetNode, originalEvent) { + return options.dnd!.drop(data, targetNode.element, originalEvent); + } + }, multipleSelectionController: options.multipleSelectionController && { isSelectionSingleChangeEvent(e) { return options.multipleSelectionController!.isSelectionSingleChangeEvent({ ...e, element: e.element } as any); diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index b0970cb21bb..48bb09a8909 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -130,6 +130,23 @@ function asObjectTreeOptions(options?: IAsyncDataTreeOpt return options.identityProvider!.getId(el.element as T); } }, + dnd: options.dnd && { + getDragURI(node) { + return options.dnd!.getDragURI(node.element as T); + }, + getDragLabel: options.dnd!.getDragLabel && ((nodes) => { + return options.dnd!.getDragLabel!(nodes.map(node => node.element as T)); + }), + onDragStart(data, originalEvent) { + return options.dnd!.onDragStart(data, originalEvent); + }, + onDragOver(data, targetNode, originalEvent) { + return options.dnd!.onDragOver(data, targetNode.element as T, originalEvent); + }, + drop(data, targetNode, originalEvent) { + return options.dnd!.drop(data, targetNode.element as T, originalEvent); + } + }, multipleSelectionController: options.multipleSelectionController && { isSelectionSingleChangeEvent(e) { return options.multipleSelectionController!.isSelectionSingleChangeEvent({ ...e, element: e.element } as any); diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 19d30856bec..4d619e03f5d 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -880,7 +880,14 @@ export class RepositoryPanel extends ViewletPanel { this.list = this.instantiationService.createInstance(WorkbenchList, this.listContainer, delegate, renderers, { identityProvider: scmResourceIdentityProvider, - keyboardNavigationLabelProvider: scmKeyboardNavigationLabelProvider + keyboardNavigationLabelProvider: scmKeyboardNavigationLabelProvider, + dnd: { + getDragURI(element) { return 'file:///foo'; }, + // getDragLabel(elements) { return 'dragging'; }, + onDragStart(data, originalEvent) { }, + onDragOver(data, targetElement, originalEvent) { return true; }, + drop(data, targetElement, originalEvent) { } + } }) as WorkbenchList; Event.chain(this.list.onDidOpen)