1+ /*---------------------------------------------------------------------------------------------
2+ * Copyright (c) Microsoft Corporation. All rights reserved.
3+ * Licensed under the MIT License. See License.txt in the project root for license information.
4+ *--------------------------------------------------------------------------------------------*/
5+
6+ 'use strict' ;
7+
8+ import { IDraggedResource , IDraggedEditor , extractResources } from 'vs/workbench/browser/editor' ;
9+ import { WORKSPACE_EXTENSION , IWorkspacesService } from 'vs/platform/workspaces/common/workspaces' ;
10+ import { extname } from 'vs/base/common/paths' ;
11+ import { IFileService } from 'vs/platform/files/common/files' ;
12+ import { IWindowsService , IWindowService } from 'vs/platform/windows/common/windows' ;
13+ import URI from 'vs/base/common/uri' ;
14+ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles' ;
15+ import { BACKUP_FILE_RESOLVE_OPTIONS , IBackupFileService } from 'vs/workbench/services/backup/common/backup' ;
16+ import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService' ;
17+ import { TPromise } from 'vs/base/common/winjs.base' ;
18+ import { Schemas } from 'vs/base/common/network' ;
19+ import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService' ;
20+ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService' ;
21+ import { Position } from 'vs/platform/editor/common/editor' ;
22+ import { onUnexpectedError } from 'vs/base/common/errors' ;
23+
24+ /**
25+ * Shared function across some editor components to handle drag & drop of external resources. E.g. of folders and workspace files
26+ * to open them in the window instead of the editor or to handle dirty editors being dropped between instances of Code.
27+ */
28+ export class EditorAreaDropHandler {
29+
30+ constructor (
31+ @IFileService private fileService : IFileService ,
32+ @IWindowsService private windowsService : IWindowsService ,
33+ @IWindowService private windowService : IWindowService ,
34+ @IWorkspacesService private workspacesService : IWorkspacesService ,
35+ @ITextFileService private textFileService : ITextFileService ,
36+ @IBackupFileService private backupFileService : IBackupFileService ,
37+ @IEditorGroupService private groupService : IEditorGroupService ,
38+ @IUntitledEditorService private untitledEditorService : IUntitledEditorService ,
39+ @IWorkbenchEditorService private editorService : IWorkbenchEditorService ,
40+ ) {
41+ }
42+
43+ public handleDrop ( event : DragEvent , afterDrop : ( ) => void , targetPosition : Position , targetIndex ?: number ) : void {
44+ const resources = extractResources ( event ) . filter ( r => r . resource . scheme === Schemas . file || r . resource . scheme === Schemas . untitled ) ;
45+ if ( ! resources . length ) {
46+ return ;
47+ }
48+
49+ return this . doHandleDrop ( resources ) . then ( isWorkspaceOpening => {
50+ if ( isWorkspaceOpening ) {
51+ return void 0 ; // return early if the drop operation resulted in this window changing to a workspace
52+ }
53+
54+ // Add external ones to recently open list unless dropped resource is a workspace
55+ const externalResources = resources . filter ( d => d . isExternal ) . map ( d => d . resource ) ;
56+ if ( externalResources . length ) {
57+ this . windowsService . addRecentlyOpened ( externalResources . map ( resource => resource . fsPath ) ) ;
58+ }
59+
60+ // Open in Editor
61+ return this . windowService . focusWindow ( )
62+ . then ( ( ) => this . editorService . openEditors ( resources . map ( r => {
63+ return {
64+ input : {
65+ resource : r . resource ,
66+ options : {
67+ pinned : true ,
68+ index : targetIndex ,
69+ viewState : ( r as IDraggedEditor ) . viewState
70+ }
71+ } ,
72+ position : targetPosition
73+ } ;
74+ } ) ) ) . then ( ( ) => {
75+
76+ // Finish with provided function
77+ afterDrop ( ) ;
78+ } ) ;
79+ } ) . done ( null , onUnexpectedError ) ;
80+ }
81+
82+ private doHandleDrop ( resources : ( IDraggedResource | IDraggedEditor ) [ ] ) : TPromise < boolean > {
83+
84+ // Check for dirty editor being dropped
85+ if ( resources . length === 1 && ! resources [ 0 ] . isExternal && ( resources [ 0 ] as IDraggedEditor ) . backupResource ) {
86+ return this . handleDirtyEditorDrop ( resources [ 0 ] ) ;
87+ }
88+
89+ // Check for workspace file being dropped
90+ if ( resources . some ( r => r . isExternal ) ) {
91+ return this . handleWorkspaceFileDrop ( resources ) ;
92+ }
93+
94+ return TPromise . as ( false ) ;
95+ }
96+
97+ private handleDirtyEditorDrop ( droppedDirtyEditor : IDraggedEditor ) : TPromise < boolean > {
98+
99+ // Untitled: always ensure that we open a new untitled for each file we drop
100+ if ( droppedDirtyEditor . resource . scheme === Schemas . untitled ) {
101+ droppedDirtyEditor . resource = this . untitledEditorService . createOrGet ( ) . getResource ( ) ;
102+ }
103+
104+ // Return early if the resource is already dirty in target or opened already
105+ if ( this . textFileService . isDirty ( droppedDirtyEditor . resource ) || this . groupService . getStacksModel ( ) . isOpen ( droppedDirtyEditor . resource ) ) {
106+ return TPromise . as ( false ) ;
107+ }
108+
109+ // Resolve the contents of the dropped dirty resource from source
110+ return this . textFileService . resolveTextContent ( droppedDirtyEditor . backupResource , BACKUP_FILE_RESOLVE_OPTIONS ) . then ( content => {
111+
112+ // Set the contents of to the resource to the target
113+ return this . backupFileService . backupResource ( droppedDirtyEditor . resource , this . backupFileService . parseBackupContent ( content . value ) ) ;
114+ } ) . then ( ( ) => false , ( ) => false /* ignore any error */ ) ;
115+ }
116+
117+ private handleWorkspaceFileDrop ( resources : ( IDraggedResource | IDraggedEditor ) [ ] ) : TPromise < boolean > {
118+ const externalResources = resources . filter ( d => d . isExternal ) . map ( d => d . resource ) ;
119+
120+ const externalWorkspaceResources : { workspaces : URI [ ] , folders : URI [ ] } = {
121+ workspaces : [ ] ,
122+ folders : [ ]
123+ } ;
124+
125+ return TPromise . join ( externalResources . map ( resource => {
126+
127+ // Check for Workspace
128+ if ( extname ( resource . fsPath ) === `.${ WORKSPACE_EXTENSION } ` ) {
129+ externalWorkspaceResources . workspaces . push ( resource ) ;
130+
131+ return void 0 ;
132+ }
133+
134+ // Check for Folder
135+ return this . fileService . resolveFile ( resource ) . then ( stat => {
136+ if ( stat . isDirectory ) {
137+ externalWorkspaceResources . folders . push ( stat . resource ) ;
138+ }
139+ } , error => void 0 ) ;
140+ } ) ) . then ( _ => {
141+ const { workspaces, folders } = externalWorkspaceResources ;
142+
143+ // Return early if no external resource is a folder or workspace
144+ if ( workspaces . length === 0 && folders . length === 0 ) {
145+ return false ;
146+ }
147+
148+ // Pass focus to window
149+ this . windowService . focusWindow ( ) ;
150+
151+ let workspacesToOpen : TPromise < string [ ] > ;
152+
153+ // Open in separate windows if we drop workspaces or just one folder
154+ if ( workspaces . length > 0 || folders . length === 1 ) {
155+ workspacesToOpen = TPromise . as ( [ ...workspaces , ...folders ] . map ( resources => resources . fsPath ) ) ;
156+ }
157+
158+ // Multiple folders: Create new workspace with folders and open
159+ else if ( folders . length > 1 ) {
160+ workspacesToOpen = this . workspacesService . createWorkspace ( folders . map ( folder => ( { uri : folder } ) ) ) . then ( workspace => [ workspace . configPath ] ) ;
161+ }
162+
163+ // Open
164+ workspacesToOpen . then ( workspaces => {
165+ this . windowsService . openWindow ( workspaces , { forceReuseWindow : true } ) ;
166+ } ) ;
167+
168+ return true ;
169+ } ) ;
170+ }
171+ }
0 commit comments