diff --git a/src/vs/base/parts/tree/browser/treeDnd.ts b/src/vs/base/parts/tree/browser/treeDnd.ts index 7b2fa8c4b1ad27008349466f70240f40e0bfde0d..a13cf53a55c24e2cfa09d6934e649c249c71a771 100644 --- a/src/vs/base/parts/tree/browser/treeDnd.ts +++ b/src/vs/base/parts/tree/browser/treeDnd.ts @@ -6,11 +6,6 @@ import _ = require('vs/base/parts/tree/browser/tree'); import Mouse = require('vs/base/browser/mouseEvent'); -import { DefaultDragAndDrop } from 'vs/base/parts/tree/browser/treeDefaults'; -import URI from 'vs/base/common/uri'; -import { basename } from 'vs/base/common/paths'; -import { getPathLabel } from 'vs/base/common/labels'; -import { DataTransfers } from 'vs/base/browser/dnd'; export class ElementsDragAndDropData implements _.IDragAndDropData { @@ -76,48 +71,4 @@ export class DesktopDragAndDropData implements _.IDragAndDropData { files: this.files }; } -} - -export class SimpleFileResourceDragAndDrop extends DefaultDragAndDrop { - - constructor(private toResource: (obj: any) => URI) { - super(); - } - - public getDragURI(tree: _.ITree, obj: any): string { - const resource = this.toResource(obj); - if (resource) { - return resource.toString(); - } - - return void 0; - } - - public getDragLabel(tree: _.ITree, elements: any[]): string { - if (elements.length > 1) { - return String(elements.length); - } - - const resource = this.toResource(elements[0]); - if (resource) { - return basename(resource.fsPath); - } - - return void 0; - } - - public onDragStart(tree: _.ITree, data: _.IDragAndDropData, originalEvent: Mouse.DragMouseEvent): void { - const sources: object[] = data.getData(); - - let source: object = null; - if (sources.length > 0) { - source = sources[0]; - } - - // Apply some datatransfer types to allow for dragging the element outside of the application - const resource = this.toResource(source); - if (resource) { - originalEvent.dataTransfer.setData(DataTransfers.TEXT, getPathLabel(resource)); - } - } } \ No newline at end of file diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts new file mode 100644 index 0000000000000000000000000000000000000000..e4d2f368a0004e3d3d16c3c0d5e4cbaa0ef5f555 --- /dev/null +++ b/src/vs/workbench/browser/dnd.ts @@ -0,0 +1,380 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { WORKSPACE_EXTENSION, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { extname, basename } from 'vs/base/common/paths'; +import { IFileService, IFileStat } from 'vs/platform/files/common/files'; +import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; +import URI from 'vs/base/common/uri'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { Schemas } from 'vs/base/common/network'; +import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { Position } from 'vs/platform/editor/common/editor'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { DefaultEndOfLine } from 'vs/editor/common/model'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IEditorViewState } from 'vs/editor/common/editorCommon'; +import { DataTransfers } from 'vs/base/browser/dnd'; +import { DefaultDragAndDrop } from 'vs/base/parts/tree/browser/treeDefaults'; +import { DragMouseEvent } from 'vs/base/browser/mouseEvent'; +import { getPathLabel } from 'vs/base/common/labels'; +import { MIME_BINARY } from 'vs/base/common/mime'; +import { ITree, IDragAndDropData } from 'vs/base/parts/tree/browser/tree'; +import { isWindows } from 'vs/base/common/platform'; +import { coalesce } from 'vs/base/common/arrays'; +import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { getCodeEditor } from 'vs/editor/browser/services/codeEditorService'; + +export interface IDraggedResource { + resource: URI; + isExternal: boolean; +} + +export interface IDraggedEditor extends IDraggedResource { + backupResource?: URI; + viewState?: IEditorViewState; +} + +export interface ISerializedDraggedEditor { + resource: string; + backupResource: string; + viewState: IEditorViewState; +} + +export const CodeDataTransfers = { + EDITORS: 'CodeEditors' +}; + +export function extractResources(e: DragEvent, externalOnly?: boolean): (IDraggedResource | IDraggedEditor)[] { + const resources: (IDraggedResource | IDraggedEditor)[] = []; + if (e.dataTransfer.types.length > 0) { + + // Check for window-to-window DND + if (!externalOnly) { + + // Data Transfer: Code Editors + const rawEditorsData = e.dataTransfer.getData(CodeDataTransfers.EDITORS); + if (rawEditorsData) { + try { + const draggedEditors = JSON.parse(rawEditorsData) as ISerializedDraggedEditor[]; + draggedEditors.forEach(draggedEditor => { + resources.push({ resource: URI.parse(draggedEditor.resource), backupResource: URI.parse(draggedEditor.backupResource), viewState: draggedEditor.viewState, isExternal: false }); + }); + } catch (error) { + // Invalid URI + } + } + + // Data Transfer: URL/URLS + else { + try { + const rawURLsData = e.dataTransfer.getData(DataTransfers.URLS); + if (rawURLsData) { + const uriStrArray: string[] = JSON.parse(rawURLsData); + resources.push(...uriStrArray.map(uriStr => ({ resource: URI.parse(uriStr), isExternal: false }))); + } else { + const rawURLData = e.dataTransfer.getData(DataTransfers.URL); + if (rawURLData) { + resources.push({ resource: URI.parse(rawURLData), isExternal: false }); + } + } + } catch (error) { + // Invalid URI + } + } + } + + // Check for native file transfer + if (e.dataTransfer && e.dataTransfer.files) { + for (let i = 0; i < e.dataTransfer.files.length; i++) { + const file = e.dataTransfer.files[i] as { path: string }; + if (file && file.path) { + try { + resources.push({ resource: URI.file(file.path), isExternal: true }); + } catch (error) { + // Invalid URI + } + } + } + } + } + + return resources; +} + +/** + * Shared function across some editor components to handle drag & drop of external resources. E.g. of folders and workspace files + * to open them in the window instead of the editor or to handle dirty editors being dropped between instances of Code. + */ +export class EditorAreaDropHandler { + + constructor( + @IFileService private fileService: IFileService, + @IWindowsService private windowsService: IWindowsService, + @IWindowService private windowService: IWindowService, + @IWorkspacesService private workspacesService: IWorkspacesService, + @ITextFileService private textFileService: ITextFileService, + @IBackupFileService private backupFileService: IBackupFileService, + @IEditorGroupService private groupService: IEditorGroupService, + @IUntitledEditorService private untitledEditorService: IUntitledEditorService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @IConfigurationService private configurationService: IConfigurationService + ) { + } + + public handleDrop(event: DragEvent, afterDrop: () => void, targetPosition: Position, targetIndex?: number): void { + const resources = extractResources(event).filter(r => r.resource.scheme === Schemas.file || r.resource.scheme === Schemas.untitled); + if (!resources.length) { + return; + } + + return this.doHandleDrop(resources).then(isWorkspaceOpening => { + if (isWorkspaceOpening) { + return void 0; // return early if the drop operation resulted in this window changing to a workspace + } + + // Add external ones to recently open list unless dropped resource is a workspace + const externalResources = resources.filter(d => d.isExternal).map(d => d.resource); + if (externalResources.length) { + this.windowsService.addRecentlyOpened(externalResources.map(resource => resource.fsPath)); + } + + // Open in Editor + return this.windowService.focusWindow() + .then(() => this.editorService.openEditors(resources.map(r => { + return { + input: { + resource: r.resource, + options: { + pinned: true, + index: targetIndex, + viewState: (r as IDraggedEditor).viewState + } + }, + position: targetPosition + }; + }))).then(() => { + + // Finish with provided function + afterDrop(); + }); + }).done(null, onUnexpectedError); + } + + private doHandleDrop(resources: (IDraggedResource | IDraggedEditor)[]): TPromise { + + // Check for dirty editors being dropped + const resourcesWithBackups: IDraggedEditor[] = resources.filter(resource => !resource.isExternal && !!(resource as IDraggedEditor).backupResource); + if (resourcesWithBackups.length > 0) { + return TPromise.join(resourcesWithBackups.map(resourceWithBackup => this.handleDirtyEditorDrop(resourceWithBackup))).then(() => false); + } + + // Check for workspace file being dropped + if (resources.some(r => r.isExternal)) { + return this.handleWorkspaceFileDrop(resources); + } + + return TPromise.as(false); + } + + private handleDirtyEditorDrop(droppedDirtyEditor: IDraggedEditor): TPromise { + + // Untitled: always ensure that we open a new untitled for each file we drop + if (droppedDirtyEditor.resource.scheme === Schemas.untitled) { + droppedDirtyEditor.resource = this.untitledEditorService.createOrGet().getResource(); + } + + // Return early if the resource is already dirty in target or opened already + if (this.textFileService.isDirty(droppedDirtyEditor.resource) || this.groupService.getStacksModel().isOpen(droppedDirtyEditor.resource)) { + return TPromise.as(false); + } + + // Resolve the contents of the dropped dirty resource from source + return this.backupFileService.resolveBackupContent(droppedDirtyEditor.backupResource).then(content => { + + // Set the contents of to the resource to the target + return this.backupFileService.backupResource(droppedDirtyEditor.resource, content.create(this.getDefaultEOL()).createSnapshot(true)); + }).then(() => false, () => false /* ignore any error */); + } + + private getDefaultEOL(): DefaultEndOfLine { + const eol = this.configurationService.getValue('files.eol'); + if (eol === '\r\n') { + return DefaultEndOfLine.CRLF; + } + + return DefaultEndOfLine.LF; + } + + private handleWorkspaceFileDrop(resources: (IDraggedResource | IDraggedEditor)[]): TPromise { + const externalResources = resources.filter(d => d.isExternal).map(d => d.resource); + + const externalWorkspaceResources: { workspaces: URI[], folders: URI[] } = { + workspaces: [], + folders: [] + }; + + return TPromise.join(externalResources.map(resource => { + + // Check for Workspace + if (extname(resource.fsPath) === `.${WORKSPACE_EXTENSION}`) { + externalWorkspaceResources.workspaces.push(resource); + + return void 0; + } + + // Check for Folder + return this.fileService.resolveFile(resource).then(stat => { + if (stat.isDirectory) { + externalWorkspaceResources.folders.push(stat.resource); + } + }, error => void 0); + })).then(_ => { + const { workspaces, folders } = externalWorkspaceResources; + + // Return early if no external resource is a folder or workspace + if (workspaces.length === 0 && folders.length === 0) { + return false; + } + + // Pass focus to window + this.windowService.focusWindow(); + + let workspacesToOpen: TPromise; + + // Open in separate windows if we drop workspaces or just one folder + if (workspaces.length > 0 || folders.length === 1) { + workspacesToOpen = TPromise.as([...workspaces, ...folders].map(resources => resources.fsPath)); + } + + // Multiple folders: Create new workspace with folders and open + else if (folders.length > 1) { + workspacesToOpen = this.workspacesService.createWorkspace(folders.map(folder => ({ uri: folder }))).then(workspace => [workspace.configPath]); + } + + // Open + workspacesToOpen.then(workspaces => { + this.windowsService.openWindow(workspaces, { forceReuseWindow: true }); + }); + + return true; + }); + } +} + +export class SimpleFileResourceDragAndDrop extends DefaultDragAndDrop { + + constructor( + private toResource: (obj: any) => URI, + @IInstantiationService protected instantiationService: IInstantiationService + ) { + super(); + } + + public getDragURI(tree: ITree, obj: any): string { + const resource = this.toResource(obj); + if (resource) { + return resource.toString(); + } + + return void 0; + } + + public getDragLabel(tree: ITree, elements: any[]): string { + if (elements.length > 1) { + return String(elements.length); + } + + const resource = this.toResource(elements[0]); + if (resource) { + return basename(resource.fsPath); + } + + return void 0; + } + + public onDragStart(tree: ITree, data: IDragAndDropData, originalEvent: DragMouseEvent): void { + + // Apply some datatransfer types to allow for dragging the element outside of the application + const resources: URI[] = data.getData().map(source => this.toResource(source)); + if (resources) { + this.instantiationService.invokeFunction(fillResourceDataTransfers, coalesce(resources), originalEvent); + } + } +} + +export function fillResourceDataTransfers(accessor: ServicesAccessor, resources: (URI | IFileStat)[], event: DragMouseEvent | DragEvent): void { + if (resources.length === 0) { + return; + } + + const sources = resources.map(obj => { + if (URI.isUri(obj)) { + return { resource: obj, isDirectory: false /* assume resource is not a directory */ }; + } + + return obj; + }); + + const firstSource = sources[0]; + + // Text: allows to paste into text-capable areas + const lineDelimiter = isWindows ? '\r\n' : '\n'; + event.dataTransfer.setData(DataTransfers.TEXT, sources.map(source => source.resource.scheme === 'file' ? getPathLabel(source.resource) : source.resource.toString()).join(lineDelimiter)); + + // Download URL: enables support to drag a tab as file to desktop (only single file supported, not directories) + if (sources.length === 1 && firstSource.resource.scheme === 'file' && !firstSource.isDirectory) { + event.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [MIME_BINARY, basename(firstSource.resource.fsPath), firstSource.resource.toString()].join(':')); + } + + // URI: allows to drop a single resource to a target in VS Code (not directory) + if (sources.length === 1 && !firstSource.isDirectory) { + event.dataTransfer.setData(DataTransfers.URL, firstSource.resource.toString()); + } + + // URLS: allows to drop multiple resources to a target in VS Code (not directories) + else { + event.dataTransfer.setData(DataTransfers.URLS, JSON.stringify(sources.filter(s => !s.isDirectory).map(s => s.resource.toString()))); + } + + // Editors: enables cross window DND of tabs into the editor area + const textFileService = accessor.get(ITextFileService); + const backupFileService = accessor.get(IBackupFileService); + const editorService = accessor.get(IWorkbenchEditorService); + + const draggedEditors: ISerializedDraggedEditor[] = []; + sources.forEach(source => { + + // Try to find editor view state from the visible editors that match given resource + let viewState: IEditorViewState; + const editors = editorService.getVisibleEditors(); + for (let i = 0; i < editors.length; i++) { + const editor = editors[i]; + const codeEditor = getCodeEditor(editor); + if (codeEditor) { + const model = codeEditor.getModel(); + if (model && model.uri && model.uri.toString() === source.resource.toString()) { + viewState = codeEditor.saveViewState(); + break; + } + } + } + + // Add as dragged editor + draggedEditors.push({ + resource: source.resource.toString(), + backupResource: textFileService.isDirty(source.resource) ? backupFileService.toBackupResource(source.resource).toString() : void 0, + viewState + }); + }); + + event.dataTransfer.setData(CodeDataTransfers.EDITORS, JSON.stringify(draggedEditors)); +} \ No newline at end of file diff --git a/src/vs/workbench/browser/editor.ts b/src/vs/workbench/browser/editor.ts index 35486960348cbf55322f011a05d4c2578a921f90..525a263c59f603645b0c54d0e8aaa94acbb752ad 100644 --- a/src/vs/workbench/browser/editor.ts +++ b/src/vs/workbench/browser/editor.ts @@ -11,9 +11,6 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { IConstructorSignature0, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { isArray } from 'vs/base/common/types'; -import URI from 'vs/base/common/uri'; -import { DataTransfers } from 'vs/base/browser/dnd'; -import { IEditorViewState } from 'vs/editor/common/editorCommon'; export interface IEditorDescriptor { instantiate(instantiationService: IInstantiationService): BaseEditor; @@ -199,79 +196,4 @@ export const Extensions = { Editors: 'workbench.contributions.editors' }; -Registry.add(Extensions.Editors, new EditorRegistry()); - -export interface IDraggedResource { - resource: URI; - isExternal: boolean; -} - -export interface IDraggedEditor extends IDraggedResource { - backupResource?: URI; - viewState?: IEditorViewState; -} - -export interface ISerializedDraggedEditor { - resource: string; - backupResource: string; - viewState: IEditorViewState; -} - -export const CodeDataTransfers = { - EDITOR: 'CodeEditor' -}; - -export function extractResources(e: DragEvent, externalOnly?: boolean): (IDraggedResource | IDraggedEditor)[] { - const resources: (IDraggedResource | IDraggedEditor)[] = []; - if (e.dataTransfer.types.length > 0) { - - // Check for window-to-window DND - if (!externalOnly) { - - // Data Transfer: Code Editor - const rawEditorData = e.dataTransfer.getData(CodeDataTransfers.EDITOR); - if (rawEditorData) { - try { - const draggedEditor = JSON.parse(rawEditorData) as ISerializedDraggedEditor; - resources.push({ resource: URI.parse(draggedEditor.resource), backupResource: URI.parse(draggedEditor.backupResource), viewState: draggedEditor.viewState, isExternal: false }); - } catch (error) { - // Invalid URI - } - } - - // Data Transfer: URL - else { - try { - const rawURLsData = e.dataTransfer.getData(DataTransfers.URLS); - if (rawURLsData) { - const uriStrArray: string[] = JSON.parse(rawURLsData); - resources.push(...uriStrArray.map(uriStr => ({ resource: URI.parse(uriStr), isExternal: false }))); - } else { - const rawURLData = e.dataTransfer.getData(DataTransfers.URL); - if (rawURLData) { - resources.push({ resource: URI.parse(rawURLData), isExternal: false }); - } - } - } catch (error) { - // Invalid URI - } - } - } - - // Check for native file transfer - if (e.dataTransfer && e.dataTransfer.files) { - for (let i = 0; i < e.dataTransfer.files.length; i++) { - const file = e.dataTransfer.files[i] as { path: string }; - if (file && file.path) { - try { - resources.push({ resource: URI.file(file.path), isExternal: true }); - } catch (error) { - // Invalid URI - } - } - } - } - } - - return resources; -} +Registry.add(Extensions.Editors, new EditorRegistry()); \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/editor/editorAreaDropHandler.ts b/src/vs/workbench/browser/parts/editor/editorAreaDropHandler.ts deleted file mode 100644 index 179201416641d3d812b2883c1f0aa331244f82cd..0000000000000000000000000000000000000000 --- a/src/vs/workbench/browser/parts/editor/editorAreaDropHandler.ts +++ /dev/null @@ -1,183 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import { IDraggedResource, IDraggedEditor, extractResources } from 'vs/workbench/browser/editor'; -import { WORKSPACE_EXTENSION, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; -import { extname } from 'vs/base/common/paths'; -import { IFileService } from 'vs/platform/files/common/files'; -import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; -import URI from 'vs/base/common/uri'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; -import { TPromise } from 'vs/base/common/winjs.base'; -import { Schemas } from 'vs/base/common/network'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { Position } from 'vs/platform/editor/common/editor'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { DefaultEndOfLine } from 'vs/editor/common/model'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; - -/** - * Shared function across some editor components to handle drag & drop of external resources. E.g. of folders and workspace files - * to open them in the window instead of the editor or to handle dirty editors being dropped between instances of Code. - */ -export class EditorAreaDropHandler { - - constructor( - @IFileService private fileService: IFileService, - @IWindowsService private windowsService: IWindowsService, - @IWindowService private windowService: IWindowService, - @IWorkspacesService private workspacesService: IWorkspacesService, - @ITextFileService private textFileService: ITextFileService, - @IBackupFileService private backupFileService: IBackupFileService, - @IEditorGroupService private groupService: IEditorGroupService, - @IUntitledEditorService private untitledEditorService: IUntitledEditorService, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IConfigurationService private configurationService: IConfigurationService - ) { - } - - public handleDrop(event: DragEvent, afterDrop: () => void, targetPosition: Position, targetIndex?: number): void { - const resources = extractResources(event).filter(r => r.resource.scheme === Schemas.file || r.resource.scheme === Schemas.untitled); - if (!resources.length) { - return; - } - - return this.doHandleDrop(resources).then(isWorkspaceOpening => { - if (isWorkspaceOpening) { - return void 0; // return early if the drop operation resulted in this window changing to a workspace - } - - // Add external ones to recently open list unless dropped resource is a workspace - const externalResources = resources.filter(d => d.isExternal).map(d => d.resource); - if (externalResources.length) { - this.windowsService.addRecentlyOpened(externalResources.map(resource => resource.fsPath)); - } - - // Open in Editor - return this.windowService.focusWindow() - .then(() => this.editorService.openEditors(resources.map(r => { - return { - input: { - resource: r.resource, - options: { - pinned: true, - index: targetIndex, - viewState: (r as IDraggedEditor).viewState - } - }, - position: targetPosition - }; - }))).then(() => { - - // Finish with provided function - afterDrop(); - }); - }).done(null, onUnexpectedError); - } - - private doHandleDrop(resources: (IDraggedResource | IDraggedEditor)[]): TPromise { - - // Check for dirty editor being dropped - if (resources.length === 1 && !resources[0].isExternal && (resources[0] as IDraggedEditor).backupResource) { - return this.handleDirtyEditorDrop(resources[0]); - } - - // Check for workspace file being dropped - if (resources.some(r => r.isExternal)) { - return this.handleWorkspaceFileDrop(resources); - } - - return TPromise.as(false); - } - - private handleDirtyEditorDrop(droppedDirtyEditor: IDraggedEditor): TPromise { - - // Untitled: always ensure that we open a new untitled for each file we drop - if (droppedDirtyEditor.resource.scheme === Schemas.untitled) { - droppedDirtyEditor.resource = this.untitledEditorService.createOrGet().getResource(); - } - - // Return early if the resource is already dirty in target or opened already - if (this.textFileService.isDirty(droppedDirtyEditor.resource) || this.groupService.getStacksModel().isOpen(droppedDirtyEditor.resource)) { - return TPromise.as(false); - } - - // Resolve the contents of the dropped dirty resource from source - return this.backupFileService.resolveBackupContent(droppedDirtyEditor.backupResource).then(content => { - - // Set the contents of to the resource to the target - return this.backupFileService.backupResource(droppedDirtyEditor.resource, content.create(this.getDefaultEOL()).createSnapshot(true)); - }).then(() => false, () => false /* ignore any error */); - } - - private getDefaultEOL(): DefaultEndOfLine { - const eol = this.configurationService.getValue('files.eol'); - if (eol === '\r\n') { - return DefaultEndOfLine.CRLF; - } - - return DefaultEndOfLine.LF; - } - - private handleWorkspaceFileDrop(resources: (IDraggedResource | IDraggedEditor)[]): TPromise { - const externalResources = resources.filter(d => d.isExternal).map(d => d.resource); - - const externalWorkspaceResources: { workspaces: URI[], folders: URI[] } = { - workspaces: [], - folders: [] - }; - - return TPromise.join(externalResources.map(resource => { - - // Check for Workspace - if (extname(resource.fsPath) === `.${WORKSPACE_EXTENSION}`) { - externalWorkspaceResources.workspaces.push(resource); - - return void 0; - } - - // Check for Folder - return this.fileService.resolveFile(resource).then(stat => { - if (stat.isDirectory) { - externalWorkspaceResources.folders.push(stat.resource); - } - }, error => void 0); - })).then(_ => { - const { workspaces, folders } = externalWorkspaceResources; - - // Return early if no external resource is a folder or workspace - if (workspaces.length === 0 && folders.length === 0) { - return false; - } - - // Pass focus to window - this.windowService.focusWindow(); - - let workspacesToOpen: TPromise; - - // Open in separate windows if we drop workspaces or just one folder - if (workspaces.length > 0 || folders.length === 1) { - workspacesToOpen = TPromise.as([...workspaces, ...folders].map(resources => resources.fsPath)); - } - - // Multiple folders: Create new workspace with folders and open - else if (folders.length > 1) { - workspacesToOpen = this.workspacesService.createWorkspace(folders.map(folder => ({ uri: folder }))).then(workspace => [workspace.configPath]); - } - - // Open - workspacesToOpen.then(workspaces => { - this.windowsService.openWindow(workspaces, { forceReuseWindow: true }); - }); - - return true; - }); - } -} \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts b/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts index 06f06fb13561c4e110097b1c93a8a92f8dfd41c1..c984e61fda3283d0f2f84d04ad1927969c6fb743 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts @@ -35,7 +35,7 @@ import { editorBackground, contrastBorder, activeContrastBorder } from 'vs/platf import { Themable, EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, EDITOR_GROUP_BACKGROUND, EDITOR_GROUP_HEADER_TABS_BORDER } from 'vs/workbench/common/theme'; import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; -import { EditorAreaDropHandler } from 'vs/workbench/browser/parts/editor/editorAreaDropHandler'; +import { EditorAreaDropHandler } from 'vs/workbench/browser/dnd'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export enum Rochade { diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 1544dc0af9271fc21701a0ee436d701d62c31b9c..629e6049ceaa5069b3ffa8e0c6a40b769ca2942b 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -11,8 +11,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import errors = require('vs/base/common/errors'); import DOM = require('vs/base/browser/dom'); import { isMacintosh } from 'vs/base/common/platform'; -import { MIME_BINARY } from 'vs/base/common/mime'; -import { shorten, getPathLabel } from 'vs/base/common/labels'; +import { shorten } from 'vs/base/common/labels'; import { ActionRunner, IAction } from 'vs/base/common/actions'; import { Position, IEditorInput, Verbosity, IUntitledResourceInput } from 'vs/platform/editor/common/editor'; import { IEditorGroup, toResource } from 'vs/workbench/common/editor'; @@ -35,7 +34,6 @@ import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; -import { CodeDataTransfers, ISerializedDraggedEditor } from 'vs/workbench/browser/editor'; import { getOrSet } from 'vs/base/common/map'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; @@ -43,11 +41,7 @@ import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, import { activeContrastBorder, contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { Dimension } from 'vs/base/browser/builder'; import { scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { DataTransfers } from 'vs/base/browser/dnd'; -import { EditorAreaDropHandler } from 'vs/workbench/browser/parts/editor/editorAreaDropHandler'; -import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; +import { EditorAreaDropHandler, fillResourceDataTransfers } from 'vs/workbench/browser/dnd'; import { Color } from 'vs/base/common/color'; interface IEditorInputLabel { @@ -81,9 +75,7 @@ export class TabsTitleControl extends TitleControl { @IMessageService messageService: IMessageService, @IMenuService menuService: IMenuService, @IQuickOpenService quickOpenService: IQuickOpenService, - @IThemeService themeService: IThemeService, - @ITextFileService private textFileService: ITextFileService, - @IBackupFileService private backupFileService: IBackupFileService + @IThemeService themeService: IThemeService ) { super(contextMenuService, instantiationService, editorService, editorGroupService, contextKeyService, keybindingService, telemetryService, messageService, menuService, quickOpenService, themeService); @@ -754,26 +746,10 @@ export class TabsTitleControl extends TitleControl { this.onEditorDragStart({ editor, group }); e.dataTransfer.effectAllowed = 'copyMove'; - // Insert transfer accordingly + // Apply some datatransfer types to allow for dragging the element outside of the application const resource = toResource(editor, { supportSideBySide: true }); if (resource) { - const resourceStr = resource.toString(); - - e.dataTransfer.setData(DataTransfers.TEXT, getPathLabel(resource)); // enables dropping tab resource path into text controls - - if (resource.scheme === 'file') { - e.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [MIME_BINARY, editor.getName(), resourceStr].join(':')); // enables support to drag a tab as file to desktop - } - - // Prepare IDraggedEditor transfer - const activeEditor = this.editorService.getActiveEditor(); - const draggedEditor: ISerializedDraggedEditor = { - resource: resourceStr, - backupResource: this.textFileService.isDirty(resource) ? this.backupFileService.toBackupResource(resource).toString() : void 0, - viewState: activeEditor instanceof BaseTextEditor ? activeEditor.getControl().saveViewState() : void 0 - }; - - e.dataTransfer.setData(CodeDataTransfers.EDITOR, JSON.stringify(draggedEditor)); // enables cross window DND of tabs into the editor area + this.instantiationService.invokeFunction(fillResourceDataTransfers, [resource], e); } // Fixes https://github.com/Microsoft/vscode/issues/18733 diff --git a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts index 94f649e84edbdc18a4cceb942f8b987c3ce9f164..6687d3f02a5dbed36c8573e92305ff0b40d863c1 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts @@ -10,7 +10,6 @@ import lifecycle = require('vs/base/common/lifecycle'); import objects = require('vs/base/common/objects'); import DOM = require('vs/base/browser/dom'); import URI from 'vs/base/common/uri'; -import { MIME_BINARY } from 'vs/base/common/mime'; import { once } from 'vs/base/common/functional'; import paths = require('vs/base/common/paths'); import resources = require('vs/base/common/resources'); @@ -28,7 +27,7 @@ import { FileOperationError, FileOperationResult, IFileService, FileKind } from import { ResourceMap } from 'vs/base/common/map'; import { DuplicateFileAction, ImportFileAction, IEditableData, IFileViewletState, FileCopiedContext } from 'vs/workbench/parts/files/electron-browser/fileActions'; import { IDataSource, ITree, IAccessibilityProvider, IRenderer, ContextMenuEvent, ISorter, IFilter, IDragAndDropData, IDragOverReaction, DRAG_OVER_ACCEPT_BUBBLE_DOWN, DRAG_OVER_ACCEPT_BUBBLE_DOWN_COPY, DRAG_OVER_ACCEPT_BUBBLE_UP, DRAG_OVER_ACCEPT_BUBBLE_UP_COPY, DRAG_OVER_REJECT } from 'vs/base/parts/tree/browser/tree'; -import { DesktopDragAndDropData, ExternalElementsDragAndDropData, SimpleFileResourceDragAndDrop } from 'vs/base/parts/tree/browser/treeDnd'; +import { DesktopDragAndDropData, ExternalElementsDragAndDropData } from 'vs/base/parts/tree/browser/treeDnd'; import { ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults'; import { FileStat, NewStatPlaceholder, Model } from 'vs/workbench/parts/files/common/explorerModel'; import { DragMouseEvent, IMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -51,10 +50,8 @@ import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; -import { getPathLabel } from 'vs/base/common/labels'; -import { extractResources } from 'vs/workbench/browser/editor'; +import { extractResources, SimpleFileResourceDragAndDrop, fillResourceDataTransfers } from 'vs/workbench/browser/dnd'; import { relative } from 'path'; -import { DataTransfers } from 'vs/base/browser/dnd'; import { distinctParents } from 'vs/base/common/resources'; import { WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -687,13 +684,13 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { @IWorkspaceContextService private contextService: IWorkspaceContextService, @IFileService private fileService: IFileService, @IConfigurationService private configurationService: IConfigurationService, - @IInstantiationService private instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @ITextFileService private textFileService: ITextFileService, @IBackupFileService private backupFileService: IBackupFileService, @IWindowService private windowService: IWindowService, @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService ) { - super(stat => this.statToResource(stat)); + super(stat => this.statToResource(stat), instantiationService); this.toDispose = []; @@ -730,15 +727,7 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { }); // Apply some datatransfer types to allow for dragging the element outside of the application - originalEvent.dataTransfer.setData(DataTransfers.TEXT, sources.map(fs => fs.resource.scheme === 'file' ? getPathLabel(fs.resource) : fs.resource.toString()).join('\n')); - if (sources.length === 1) { - if (!sources[0].isDirectory) { - originalEvent.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [MIME_BINARY, sources[0].name, sources[0].resource.toString()].join(':')); - } - - } else { - originalEvent.dataTransfer.setData(DataTransfers.URLS, JSON.stringify(sources.filter(s => !s.isDirectory).map(s => s.resource.toString()))); - } + this.instantiationService.invokeFunction(fillResourceDataTransfers, sources, originalEvent); } } diff --git a/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts b/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts index f9d95b0e10caf49ba546b71086510168b5212be7..059994a4835ecf9b6be33e16f8ae78b2380ae520 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts @@ -40,9 +40,7 @@ import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; import { OpenEditorsGroupContext, DirtyEditorContext } from 'vs/workbench/parts/files/electron-browser/fileCommands'; import { ResourceContextKey } from 'vs/workbench/common/resources'; -import { DataTransfers } from 'vs/base/browser/dnd'; -import { getPathLabel, getBaseLabel } from 'vs/base/common/labels'; -import { MIME_BINARY } from 'vs/base/common/mime'; +import { fillResourceDataTransfers } from 'vs/workbench/browser/dnd'; const $ = dom.$; @@ -553,18 +551,7 @@ class OpenEditorRenderer implements IRenderer d.getResource()).map(resource => resource.scheme === 'file' ? getPathLabel(resource) : resource.toString()).join('\n')); - - if (dragged.length === 1) { - const resource = dragged[0].getResource(); - e.dataTransfer.setData(DataTransfers.URL, resource.toString()); // enables dropping editor into editor area - if (resource.scheme === 'file') { - e.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [MIME_BINARY, getBaseLabel(resource), resource.toString()].join(':')); // enables support to drag an editor as file to desktop - } - } else { - e.dataTransfer.setData(DataTransfers.URLS, JSON.stringify(dragged.map(s => s.getResource().toString()))); - } + this.instantiationService.invokeFunction(fillResourceDataTransfers, dragged.map(d => d.getResource()), e); } })); editorTemplate.toDispose.push(dom.addDisposableListener(container, dom.EventType.DRAG_OVER, () => { diff --git a/src/vs/workbench/parts/markers/browser/markersPanel.ts b/src/vs/workbench/parts/markers/browser/markersPanel.ts index 4291b3db443a6e1bdcebf097972702ecc2085ab3..84b9cb51aae8ae9786b5d63a251b379700c30b03 100644 --- a/src/vs/workbench/parts/markers/browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/browser/markersPanel.ts @@ -28,10 +28,10 @@ import Messages from 'vs/workbench/parts/markers/common/messages'; import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { debounceEvent } from 'vs/base/common/event'; -import { SimpleFileResourceDragAndDrop } from 'vs/base/parts/tree/browser/treeDnd'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { TreeResourceNavigator, WorkbenchTree } from 'vs/platform/list/browser/listService'; import { IMarkersWorkbenchService } from 'vs/workbench/parts/markers/common/markers'; +import { SimpleFileResourceDragAndDrop } from 'vs/workbench/browser/dnd'; export class MarkersPanel extends Panel { @@ -193,7 +193,7 @@ export class MarkersPanel extends Panel { this.treeContainer = dom.append(parent, dom.$('.tree-container')); dom.addClass(this.treeContainer, 'show-file-icons'); const renderer = this.instantiationService.createInstance(Viewer.Renderer); - const dnd = new SimpleFileResourceDragAndDrop(obj => obj instanceof Resource ? obj.uri : void 0); + const dnd = this.instantiationService.createInstance(SimpleFileResourceDragAndDrop, obj => obj instanceof Resource ? obj.uri : void 0); const controller = this.instantiationService.createInstance(Controller); this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, { dataSource: new Viewer.DataSource(), diff --git a/src/vs/workbench/parts/search/browser/searchViewlet.ts b/src/vs/workbench/parts/search/browser/searchViewlet.ts index b394dbe4291f88144a809bbc56c617bb04c136a7..06f2c55d78405a2951f4372d4425d61a175777f5 100644 --- a/src/vs/workbench/parts/search/browser/searchViewlet.ts +++ b/src/vs/workbench/parts/search/browser/searchViewlet.ts @@ -55,10 +55,10 @@ import { IThemeService, ITheme, ICssStyleCollector, registerThemingParticipant } import { editorFindMatchHighlight, diffInserted, diffRemoved, diffInsertedOutline, diffRemovedOutline, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { getOutOfWorkspaceEditorResources } from 'vs/workbench/parts/search/common/search'; import { PreferencesEditor } from 'vs/workbench/parts/preferences/browser/preferencesEditor'; -import { SimpleFileResourceDragAndDrop } from 'vs/base/parts/tree/browser/treeDnd'; import { isDiffEditor, isCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { TreeResourceNavigator, WorkbenchTree } from 'vs/platform/list/browser/listService'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { SimpleFileResourceDragAndDrop } from 'vs/workbench/browser/dnd'; export class SearchViewlet extends Viewlet { @@ -494,7 +494,7 @@ export class SearchViewlet extends Viewlet { let renderer = this.instantiationService.createInstance(SearchRenderer, this.getActionRunner(), this); this.toUnbind.push(renderer); - let dnd = new SimpleFileResourceDragAndDrop(obj => obj instanceof FileMatch ? obj.resource() : void 0); + let dnd = this.instantiationService.createInstance(SimpleFileResourceDragAndDrop, obj => obj instanceof FileMatch ? obj.resource() : void 0); this.tree = this.instantiationService.createInstance(WorkbenchTree, div.getHTMLElement(), { dataSource: dataSource,