11// Copyright (c) Microsoft Corporation. All rights reserved.
22// Licensed under the MIT license.
33
4- import { DataTransfer , DataTransferItem , TreeDragAndDropController } from "vscode" ;
4+ import * as path from "path" ;
5+ import { commands , DataTransfer , DataTransferItem , TreeDragAndDropController , Uri , window , workspace , WorkspaceEdit } from "vscode" ;
6+ import { Commands } from "../commands" ;
57import { Explorer } from "../constants" ;
68import { BaseSymbolNode } from "./baseSymbolNode" ;
9+ import { ContainerNode , ContainerType } from "./containerNode" ;
710import { DataNode } from "./dataNode" ;
811import { ExplorerNode } from "./explorerNode" ;
912import { FileNode } from "./fileNode" ;
13+ import { FolderNode } from "./folderNode" ;
14+ import { explorerNodeCache } from "./nodeCache/explorerNodeCache" ;
15+ import { PackageNode } from "./packageNode" ;
16+ import { PackageRootNode } from "./packageRootNode" ;
1017import { PrimaryTypeNode } from "./PrimaryTypeNode" ;
18+ import { ProjectNode } from "./projectNode" ;
19+ import { WorkspaceNode } from "./workspaceNode" ;
1120
1221export class DragAndDropController implements TreeDragAndDropController < ExplorerNode > {
1322
@@ -16,17 +25,31 @@ export class DragAndDropController implements TreeDragAndDropController<Explorer
1625 ] ;
1726 dragMimeTypes : string [ ] = [
1827 Explorer . Mime . TextUriList ,
19- ] ; ;
28+ ] ;
2029
2130 public handleDrag ( source : ExplorerNode [ ] , treeDataTransfer : DataTransfer ) : void {
2231 // select many is not supported yet
23- let dragItem = source [ 0 ] ;
32+ const dragItem = source [ 0 ] ;
2433 this . addDragToEditorDataTransfer ( dragItem , treeDataTransfer ) ;
34+ this . addInternalDragDataTransfer ( dragItem , treeDataTransfer ) ;
35+ }
36+
37+ public async handleDrop ( target : ExplorerNode | undefined , dataTransfer : DataTransfer ) : Promise < void > {
38+ const data = dataTransfer . get ( Explorer . Mime . JavaProjectExplorer ) ;
39+ if ( data ) {
40+ await this . dropFromJavaProjectExplorer ( target , data . value ) ;
41+ return ;
42+ }
2543 }
2644
45+ /**
46+ * Add data transfer that is used when node is dropped to the editor.
47+ * @param node node being dragged.
48+ * @param treeDataTransfer A map containing a mapping of the mime type of the corresponding transferred data.
49+ */
2750 private addDragToEditorDataTransfer ( node : ExplorerNode , treeDataTransfer : DataTransfer ) {
28- if ( ( node instanceof PrimaryTypeNode || node instanceof FileNode ) && ( node as DataNode ) . uri ) {
29- treeDataTransfer . set ( Explorer . Mime . TextUriList , new DataTransferItem ( ( node as DataNode ) . uri ) ) ;
51+ if ( ( node instanceof PrimaryTypeNode || node instanceof FileNode ) && node . uri ) {
52+ treeDataTransfer . set ( Explorer . Mime . TextUriList , new DataTransferItem ( node . uri ) ) ;
3053 } else if ( ( node instanceof BaseSymbolNode ) ) {
3154 const parent = ( node . getParent ( ) as PrimaryTypeNode ) ;
3255 if ( parent . uri ) {
@@ -37,4 +60,154 @@ export class DragAndDropController implements TreeDragAndDropController<Explorer
3760 }
3861 }
3962 }
63+
64+ /**
65+ * Add data transfer that is used when node is dropped into other Java Project Explorer node.
66+ * @param node node being dragged.
67+ * @param treeDataTransfer A map containing a mapping of the mime type of the corresponding transferred data.
68+ */
69+ private addInternalDragDataTransfer ( node : ExplorerNode , treeDataTransfer : DataTransfer ) : void {
70+ // draggable node must have uri
71+ if ( ! ( node instanceof DataNode ) || ! node . uri ) {
72+ return ;
73+ }
74+
75+ // whether the node can be dropped will be check in handleDrop(...)
76+ treeDataTransfer . set ( Explorer . Mime . JavaProjectExplorer , new DataTransferItem ( node . uri ) ) ;
77+ }
78+
79+ /**
80+ * Handle the DnD event which comes from Java Project explorer itself.
81+ * @param target the drop node.
82+ * @param uri uri in the data transfer.
83+ */
84+ public async dropFromJavaProjectExplorer ( target : ExplorerNode | undefined , uri : string ) : Promise < void > {
85+ const source : DataNode | undefined = explorerNodeCache . getDataNode ( Uri . parse ( uri ) ) ;
86+ if ( ! this . isDraggableNode ( source ) ) {
87+ return ;
88+ }
89+
90+ if ( ! this . isDroppableNode ( target ) ) {
91+ return ;
92+ }
93+
94+ // check if the target node is source node itself or its parent.
95+ if ( target ?. isItselfOrAncestorOf ( source , 1 /*levelToCheck*/ ) ) {
96+ return ;
97+ }
98+
99+ if ( target instanceof ContainerNode ) {
100+ if ( target . getContainerType ( ) !== ContainerType . ReferencedLibrary ) {
101+ return ;
102+ }
103+
104+ if ( ! ( target . getParent ( ) as ProjectNode ) . isUnmanagedFolder ( ) ) {
105+ return ;
106+ }
107+
108+ // TODO: referenced library
109+ } else if ( target instanceof PackageRootNode || target instanceof PackageNode
110+ || target instanceof FolderNode ) {
111+ await this . move ( source ! , target ) ;
112+ }
113+ }
114+
115+ /**
116+ * Check whether the dragged node is draggable.
117+ * @param node the dragged node.
118+ */
119+ private isDraggableNode ( node : DataNode | undefined ) : boolean {
120+ if ( ! node ?. uri ) {
121+ return false ;
122+ }
123+ if ( node instanceof WorkspaceNode || node instanceof ProjectNode
124+ || node instanceof PackageRootNode || node instanceof ContainerNode
125+ || node instanceof BaseSymbolNode ) {
126+ return false ;
127+ }
128+
129+ return this . isUnderSourceRoot ( node ) ;
130+ }
131+
132+ /**
133+ * Check whether the node is under source root.
134+ *
135+ * Note: There is one exception: The primary type directly under an unmanaged folder project,
136+ * in that case, `true` is returned.
137+ * @param node DataNode
138+ */
139+ private isUnderSourceRoot ( node : DataNode ) : boolean {
140+ let parent = node . getParent ( ) ;
141+ while ( parent ) {
142+ if ( parent instanceof ContainerNode ) {
143+ return false ;
144+ }
145+
146+ if ( parent instanceof PackageRootNode ) {
147+ return parent . isSourceRoot ( ) ;
148+ }
149+ parent = parent . getParent ( ) ;
150+ }
151+ return true ;
152+ }
153+
154+ /**
155+ * Check whether the node is able to be dropped.
156+ */
157+ private isDroppableNode ( node : ExplorerNode | undefined ) : boolean {
158+ // drop to root is not supported yet
159+ if ( ! node ) {
160+ return false ;
161+ }
162+
163+ if ( node instanceof DataNode && ! node . uri ) {
164+ return false ;
165+ }
166+
167+ if ( node instanceof WorkspaceNode || node instanceof ProjectNode
168+ || node instanceof BaseSymbolNode ) {
169+ return false ;
170+ }
171+
172+ let parent : ExplorerNode | undefined = node ;
173+ while ( parent ) {
174+ if ( parent instanceof ProjectNode ) {
175+ return false ;
176+ } else if ( parent instanceof PackageRootNode ) {
177+ return parent . isSourceRoot ( ) ;
178+ } else if ( parent instanceof ContainerNode ) {
179+ if ( parent . getContainerType ( ) === ContainerType . ReferencedLibrary ) {
180+ return ( parent . getParent ( ) as ProjectNode ) . isUnmanagedFolder ( ) ;
181+ }
182+ return false ;
183+ }
184+ parent = parent . getParent ( ) ;
185+ }
186+ return false ;
187+ }
188+
189+ /**
190+ * Trigger a workspace edit that move the source node into the target node.
191+ */
192+ private async move ( source : DataNode , target : DataNode ) : Promise < void > {
193+ const sourceUri = Uri . parse ( source . uri ! ) ;
194+ const targetUri = Uri . parse ( target . uri ! ) ;
195+ if ( sourceUri === targetUri ) {
196+ return ;
197+ }
198+
199+ const newPath = path . join ( targetUri . fsPath , path . basename ( sourceUri . fsPath ) ) ;
200+ const choice = await window . showInformationMessage (
201+ `Are you sure you want to move '${ path . basename ( sourceUri . fsPath ) } ' into '${ path . basename ( targetUri . fsPath ) } '?` ,
202+ { modal : true } ,
203+ "Move" ,
204+ ) ;
205+
206+ if ( choice === "Move" ) {
207+ const edit = new WorkspaceEdit ( ) ;
208+ edit . renameFile ( sourceUri , Uri . file ( newPath ) ) ;
209+ await workspace . applyEdit ( edit ) ;
210+ commands . executeCommand ( Commands . VIEW_PACKAGE_REFRESH , /* debounce = */ true ) ;
211+ }
212+ }
40213}
0 commit comments