diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index dda4627ecca7b..c7c481518c8d4 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -15,7 +15,7 @@ import { URI } from 'vs/base/common/uri'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { EditorOpenContext } from 'vs/platform/editor/common/editor'; -import { IExternalOpener, IExternalUriResolver, IOpener, IOpenerService, IResolvedExternalUri, IValidator, matchesScheme, matchesSomeScheme, OpenOptions, ResolveExternalUriOptions } from 'vs/platform/opener/common/opener'; +import { IExternalOpener, IExternalUriResolver, IOpener, IOpenerService, IResolvedExternalUri, IValidator, matchesScheme, matchesSomeScheme, OpenOptions, ResolveExternalUriOptions, selectionFragment } from 'vs/platform/opener/common/opener'; class CommandOpener implements IOpener { @@ -62,16 +62,8 @@ class EditorOpener implements IOpener { if (typeof target === 'string') { target = URI.parse(target); } - let selection: { startLineNumber: number; startColumn: number; } | undefined = undefined; - const match = /^L?(\d+)(?:,(\d+))?/.exec(target.fragment); - if (match) { - // support file:///some/file.js#73,84 - // support file:///some/file.js#L73 - selection = { - startLineNumber: parseInt(match[1]), - startColumn: match[2] ? parseInt(match[2]) : 1 - }; - // remove fragment + const selection: { startLineNumber: number; startColumn: number; } | undefined = selectionFragment(target); + if (selection) { target = target.with({ fragment: '' }); } diff --git a/src/vs/platform/opener/common/opener.ts b/src/vs/platform/opener/common/opener.ts index ea79903e8a959..f71caa5927953 100644 --- a/src/vs/platform/opener/common/opener.ts +++ b/src/vs/platform/opener/common/opener.ts @@ -136,3 +136,17 @@ export function matchesScheme(target: URI | string, scheme: string): boolean { export function matchesSomeScheme(target: URI | string, ...schemes: string[]): boolean { return schemes.some(scheme => matchesScheme(target, scheme)); } + +export function selectionFragment(target: URI): { startLineNumber: number; startColumn: number; } | undefined { + let selection: { startLineNumber: number; startColumn: number; } | undefined = undefined; + const match = /^L?(\d+)(?:,(\d+))?/.exec(target.fragment); + if (match) { + // support file:///some/file.js#73,84 + // support file:///some/file.js#L73 + selection = { + startLineNumber: parseInt(match[1]), + startColumn: match[2] ? parseInt(match[2]) : 1 + }; + } + return selection; +} diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index bb7117b98cd95..0953de6627c2f 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -33,6 +33,8 @@ import { parse, stringify } from 'vs/base/common/marshalling'; import { ILabelService } from 'vs/platform/label/common/label'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { ITreeDataTransfer } from 'vs/workbench/common/views'; +import { selectionFragment } from 'vs/platform/opener/common/opener'; //#region Editor / Resources DND @@ -46,6 +48,11 @@ export class DraggedEditorGroupIdentifier { constructor(readonly identifier: GroupIdentifier) { } } +export class DraggedTreeItemsIdentifier { + + constructor(readonly identifier: string) { } +} + export const CodeDataTransfers = { EDITORS: 'CodeEditors', FILES: 'CodeFiles' @@ -74,14 +81,7 @@ export function extractEditorsDropData(e: DragEvent): Array 0) { // mitigate https://github.com/microsoft/vscode/issues/124946 - editors.push({ resource: URI.parse(resourceRaw) }); - } - } - } + editors.push(...createDraggedEditorInputFromRawResourcesData(rawResourcesData)); } catch (error) { // Invalid transfer } @@ -127,6 +127,45 @@ export function extractEditorsDropData(e: DragEvent): Array 0) { // mitigate https://github.com/microsoft/vscode/issues/124946 + const resource = URI.parse(resourceRaw); + editors.push({ + resource, + options: { + selection: selectionFragment(resource) + } + }); + } + } + } + + return editors; +} + +export async function extractTreeDropData(dataTransfer: ITreeDataTransfer): Promise> { + const editors: IDraggedResourceEditorInput[] = []; + const resourcesKey = DataTransfers.RESOURCES.toLowerCase(); + + // Data Transfer: Resources + if (dataTransfer.has(resourcesKey)) { + try { + const rawResourcesData = await dataTransfer.get(resourcesKey)?.asString(); + editors.push(...createDraggedEditorInputFromRawResourcesData(rawResourcesData)); + } catch (error) { + // Invalid transfer + } + } + return editors; } diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 71c66435f04d1..d3eb933b0a1dd 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/editordroptarget'; -import { LocalSelectionTransfer, DraggedEditorIdentifier, ResourcesDropHandler, DraggedEditorGroupIdentifier, DragAndDropObserver, containsDragType, CodeDataTransfers, extractFilesDropData } from 'vs/workbench/browser/dnd'; +import { LocalSelectionTransfer, DraggedEditorIdentifier, ResourcesDropHandler, DraggedEditorGroupIdentifier, DragAndDropObserver, containsDragType, CodeDataTransfers, extractFilesDropData, DraggedTreeItemsIdentifier, extractTreeDropData } from 'vs/workbench/browser/dnd'; import { addDisposableListener, EventType, EventHelper, isAncestor } from 'vs/base/browser/dom'; import { IEditorGroupsAccessor, IEditorGroupView, fillActiveEditorViewState } from 'vs/workbench/browser/parts/editor/editor'; import { EDITOR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme'; import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; -import { IEditorIdentifier, EditorInputCapabilities } from 'vs/workbench/common/editor'; +import { IEditorIdentifier, EditorInputCapabilities, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { isMacintosh, isWeb } from 'vs/base/common/platform'; import { GroupDirection, IEditorGroupsService, MergeGroupMode } from 'vs/workbench/services/editor/common/editorGroupsService'; import { toDisposable } from 'vs/base/common/lifecycle'; @@ -21,6 +21,8 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; +import { ITreeViewsDragAndDropService } from 'vs/workbench/services/views/common/treeViewsDragAndDropService'; +import { ITreeDataTransfer } from 'vs/workbench/common/views'; interface IDropOperation { splitDirection?: GroupDirection; @@ -40,6 +42,7 @@ class DropOverlay extends Themable { private readonly editorTransfer = LocalSelectionTransfer.getInstance(); private readonly groupTransfer = LocalSelectionTransfer.getInstance(); + private readonly treeItemsTransfer = LocalSelectionTransfer.getInstance(); constructor( private accessor: IEditorGroupsAccessor, @@ -47,7 +50,8 @@ class DropOverlay extends Themable { @IThemeService themeService: IThemeService, @IInstantiationService private instantiationService: IInstantiationService, @IEditorService private readonly editorService: IEditorService, - @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, + @ITreeViewsDragAndDropService private readonly treeViewsDragAndDropService: ITreeViewsDragAndDropService ) { super(themeService); @@ -202,7 +206,7 @@ class DropOverlay extends Themable { return undefined; } - private handleDrop(event: DragEvent, splitDirection?: GroupDirection): void { + private async handleDrop(event: DragEvent, splitDirection?: GroupDirection): Promise { // Determine target group const ensureTargetGroup = () => { @@ -292,6 +296,34 @@ class DropOverlay extends Themable { } } + // Check for tree items + else if (this.treeItemsTransfer.hasData(DraggedTreeItemsIdentifier.prototype)) { + const data = this.treeItemsTransfer.getData(DraggedTreeItemsIdentifier.prototype); + if (Array.isArray(data)) { + const editors: IUntypedEditorInput[] = []; + for (const id of data) { + const dataTransferItem = await this.treeViewsDragAndDropService.removeDragOperationTransfer(id.identifier); + if (dataTransferItem) { + const extractedDropData = await extractTreeDropData(dataTransferItem); + editors.push(...extractedDropData.map(editor => { + return { + ...editor, + resource: editor.resource, + options: { + ...editor.options, + pinned: true + } + }; + })); + } + } + + this.editorService.openEditors(editors, ensureTargetGroup(), { validateTrust: true }); + } + + this.treeItemsTransfer.clearData(DraggedTreeItemsIdentifier.prototype); + } + // Web: check for file transfer else if (isWeb && containsDragType(event, DataTransfers.FILES)) { let targetGroup: IEditorGroupView | undefined = undefined; diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 8d4c90f4a5b0c..909e35cf99734 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -57,7 +57,7 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance import { Command } from 'vs/editor/common/languages'; import { isCancellationError } from 'vs/base/common/errors'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; -import { CodeDataTransfers, fillEditorsDragData } from 'vs/workbench/browser/dnd'; +import { CodeDataTransfers, DraggedTreeItemsIdentifier, fillEditorsDragData, LocalSelectionTransfer } from 'vs/workbench/browser/dnd'; import { Schemas } from 'vs/base/common/network'; import { ITreeViewsDragAndDropService } from 'vs/workbench/services/views/common/treeViewsDragAndDropService'; import { generateUuid } from 'vs/base/common/uuid'; @@ -1241,6 +1241,8 @@ const TREE_DRAG_UUID_MIME = 'tree-dnd'; export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { private readonly treeMimeType: string; + private readonly treeItemsTransfer = LocalSelectionTransfer.getInstance(); + constructor( private readonly treeId: string, @ILabelService private readonly labelService: ILabelService, @@ -1262,6 +1264,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { const uuid = generateUuid(); this.treeViewsDragAndDropService.addDragOperationTransfer(uuid, this.dndController.handleDrag(itemHandles, uuid)); originalEvent.dataTransfer.setData(TREE_DRAG_UUID_MIME, uuid); + this.treeItemsTransfer.setData([new DraggedTreeItemsIdentifier(uuid)], DraggedTreeItemsIdentifier.prototype); } private addResourceInfoToTransfer(originalEvent: DragEvent, resources: URI[]) { diff --git a/src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts b/src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts index f135fa73a8d57..6d9151ded01d5 100644 --- a/src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts +++ b/src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts @@ -102,6 +102,10 @@ declare module 'vscode' { * When the items are dropped on **another tree item** in **the same tree**, your `TreeDataTransferItem` objects * will be preserved. See the documentation for `TreeDataTransferItem` for how best to take advantage of this. * + * To add a data transfer item that can be dragged into the editor, use the application specific mime type "resourceurls". + * The data for "resourceurls" should be an array of `toString()`ed Uris. To specify a cursor position in the file, + * set the Uri's fragment to `L3,5`, where 3 is the line number and 5 is the column number. + * * @param source The source items for the drag and drop operation. * @param treeDataTransfer The data transfer associated with this drag. */