Skip to content

Commit 73d8c53

Browse files
committed
Implement Cut action for resources in Project Explorer
Added a new CutAction and integrated it into the Common Navigator's EditActionGroup. Modified PasteAction to support moves when a cut operation is pending. Key changes: - Created CutAction.java to handle clipboard 'Cut' operations. - Updated PasteAction.java to use MoveFilesAndFoldersOperation when CutAction.isCut is true. - Wired CutAction into EditActionGroup's context menu and action bars. - Added localized strings for the Cut action. - Expanded CopyPasteActionTest.java with comprehensive tests for Cut enablement, clipboard contents, and move behavior. The tests use a dedicated TEST_VIEWER to ensure navigator actions are tested without interference from JDT's action provider overrides.
1 parent 4f17b3a commit 73d8c53

File tree

7 files changed

+332
-24
lines changed

7 files changed

+332
-24
lines changed

bundles/org.eclipse.ui.navigator.resources/src/org/eclipse/ui/internal/navigator/resources/actions/CopyAction.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ public void run() {
130130
}
131131
setClipboard(resources, fileNames, buf.toString());
132132

133+
CutAction.isCut = false;
134+
133135
// update the enablement of the paste action
134136
// workaround since the clipboard does not suppot callbacks
135137
if (pasteAction != null && pasteAction.getStructuredSelection() != null) {
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 vogella GmbH and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* vogella GmbH - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.ui.internal.navigator.resources.actions;
15+
16+
import java.util.List;
17+
18+
import org.eclipse.core.resources.IContainer;
19+
import org.eclipse.core.resources.IResource;
20+
import org.eclipse.core.runtime.Assert;
21+
import org.eclipse.core.runtime.IPath;
22+
import org.eclipse.jface.dialogs.MessageDialog;
23+
import org.eclipse.jface.viewers.IStructuredSelection;
24+
import org.eclipse.swt.SWTError;
25+
import org.eclipse.swt.dnd.Clipboard;
26+
import org.eclipse.swt.dnd.DND;
27+
import org.eclipse.swt.dnd.FileTransfer;
28+
import org.eclipse.swt.dnd.TextTransfer;
29+
import org.eclipse.swt.dnd.Transfer;
30+
import org.eclipse.swt.widgets.Shell;
31+
import org.eclipse.ui.ISharedImages;
32+
import org.eclipse.ui.IWorkbenchCommandConstants;
33+
import org.eclipse.ui.PlatformUI;
34+
import org.eclipse.ui.actions.SelectionListenerAction;
35+
import org.eclipse.ui.internal.navigator.resources.plugin.WorkbenchNavigatorMessages;
36+
import org.eclipse.ui.part.ResourceTransfer;
37+
38+
/**
39+
* Standard action for cutting the currently selected resources to the clipboard.
40+
*
41+
* @since 3.10
42+
*/
43+
/*package*/class CutAction extends SelectionListenerAction {
44+
45+
/**
46+
* The id of this action.
47+
*/
48+
public static final String ID = PlatformUI.PLUGIN_ID + ".CutAction"; //$NON-NLS-1$
49+
50+
/**
51+
* Tracks whether the last clipboard operation was a cut.
52+
*/
53+
public static boolean isCut = false;
54+
55+
/**
56+
* The shell in which to show any dialogs.
57+
*/
58+
private final Shell shell;
59+
60+
/**
61+
* System clipboard
62+
*/
63+
private final Clipboard clipboard;
64+
65+
/**
66+
* Associated paste action. May be <code>null</code>
67+
*/
68+
private PasteAction pasteAction;
69+
70+
/**
71+
* Creates a new action.
72+
*
73+
* @param shell the shell for any dialogs
74+
* @param clipboard a platform clipboard
75+
*/
76+
public CutAction(Shell shell, Clipboard clipboard) {
77+
super(WorkbenchNavigatorMessages.CutAction_Cut);
78+
Assert.isNotNull(shell);
79+
Assert.isNotNull(clipboard);
80+
this.shell = shell;
81+
this.clipboard = clipboard;
82+
setToolTipText(WorkbenchNavigatorMessages.CutAction_Cut_selected_resource_s_);
83+
setId(CutAction.ID);
84+
setActionDefinitionId(IWorkbenchCommandConstants.EDIT_CUT);
85+
ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
86+
setImageDescriptor(sharedImages.getImageDescriptor(ISharedImages.IMG_TOOL_CUT));
87+
setDisabledImageDescriptor(sharedImages.getImageDescriptor(ISharedImages.IMG_TOOL_CUT_DISABLED));
88+
PlatformUI.getWorkbench().getHelpSystem().setHelp(this, "CutHelpId"); //$NON-NLS-1$
89+
}
90+
91+
/**
92+
* Creates a new action.
93+
*
94+
* @param shell the shell for any dialogs
95+
* @param clipboard a platform clipboard
96+
* @param pasteAction a paste action
97+
*/
98+
public CutAction(Shell shell, Clipboard clipboard, PasteAction pasteAction) {
99+
this(shell, clipboard);
100+
this.pasteAction = pasteAction;
101+
}
102+
103+
@Override
104+
public void run() {
105+
List<? extends IResource> selectedResources = getSelectedResources();
106+
IResource[] resources = selectedResources.toArray(new IResource[selectedResources.size()]);
107+
108+
// Get the file names and a string representation
109+
final int length = resources.length;
110+
int actualLength = 0;
111+
String[] fileNames = new String[length];
112+
StringBuilder buf = new StringBuilder();
113+
for (int i = 0; i < length; i++) {
114+
IPath location = resources[i].getLocation();
115+
if (location != null) {
116+
fileNames[actualLength++] = location.toOSString();
117+
}
118+
if (i > 0) {
119+
buf.append("\n"); //$NON-NLS-1$
120+
}
121+
buf.append(resources[i].getName());
122+
}
123+
if (actualLength < length) {
124+
String[] tempFileNames = fileNames;
125+
fileNames = new String[actualLength];
126+
System.arraycopy(tempFileNames, 0, fileNames, 0, actualLength);
127+
}
128+
setClipboard(resources, fileNames, buf.toString());
129+
130+
isCut = true;
131+
132+
if (pasteAction != null && pasteAction.getStructuredSelection() != null) {
133+
pasteAction.selectionChanged(pasteAction.getStructuredSelection());
134+
}
135+
}
136+
137+
private void setClipboard(IResource[] resources, String[] fileNames, String names) {
138+
try {
139+
if (fileNames.length > 0) {
140+
clipboard.setContents(new Object[] { resources, fileNames, names },
141+
new Transfer[] { ResourceTransfer.getInstance(), FileTransfer.getInstance(),
142+
TextTransfer.getInstance() });
143+
} else {
144+
clipboard.setContents(new Object[] { resources, names },
145+
new Transfer[] { ResourceTransfer.getInstance(), TextTransfer.getInstance() });
146+
}
147+
} catch (SWTError e) {
148+
if (e.code != DND.ERROR_CANNOT_SET_CLIPBOARD) {
149+
throw e;
150+
}
151+
if (MessageDialog.openQuestion(shell, "Problem with cut title", "Problem with cut.")) { //$NON-NLS-1$ //$NON-NLS-2$
152+
setClipboard(resources, fileNames, names);
153+
}
154+
}
155+
}
156+
157+
@Override
158+
protected boolean updateSelection(IStructuredSelection selection) {
159+
if (!super.updateSelection(selection)) {
160+
return false;
161+
}
162+
163+
if (getSelectedNonResources().size() > 0) {
164+
return false;
165+
}
166+
167+
List<? extends IResource> selectedResources = getSelectedResources();
168+
if (selectedResources.isEmpty()) {
169+
return false;
170+
}
171+
172+
boolean projSelected = selectionIsOfType(IResource.PROJECT);
173+
boolean fileFoldersSelected = selectionIsOfType(IResource.FILE | IResource.FOLDER);
174+
if (!projSelected && !fileFoldersSelected) {
175+
return false;
176+
}
177+
178+
// selection must be homogeneous
179+
if (projSelected && fileFoldersSelected) {
180+
return false;
181+
}
182+
183+
// must have a common parent
184+
IContainer firstParent = selectedResources.get(0).getParent();
185+
if (firstParent == null) {
186+
return false;
187+
}
188+
189+
for (IResource currentResource : selectedResources) {
190+
if (!currentResource.getParent().equals(firstParent)) {
191+
return false;
192+
}
193+
// resource location must exist
194+
if (currentResource.getLocationURI() == null) {
195+
return false;
196+
}
197+
}
198+
return true;
199+
}
200+
201+
}

bundles/org.eclipse.ui.navigator.resources/src/org/eclipse/ui/internal/navigator/resources/actions/EditActionGroup.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public class EditActionGroup extends ActionGroup {
3838

3939
private Clipboard clipboard;
4040

41+
private CutAction cutAction;
42+
4143
private CopyAction copyAction;
4244

4345
private DeleteResourceAction deleteAction;
@@ -69,6 +71,8 @@ public void fillContextMenu(IMenuManager menu) {
6971
boolean anyResourceSelected = !selection.isEmpty()
7072
&& ResourceSelectionUtil.allResourcesAreOfType(selection, IResource.PROJECT | IResource.FOLDER | IResource.FILE);
7173

74+
cutAction.selectionChanged(selection);
75+
menu.appendToGroup(ICommonMenuConstants.GROUP_EDIT, cutAction);
7276
copyAction.selectionChanged(selection);
7377
menu.appendToGroup(ICommonMenuConstants.GROUP_EDIT, copyAction);
7478
pasteAction.selectionChanged(selection);
@@ -89,6 +93,7 @@ public void fillActionBars(IActionBars actionBars) {
8993
textActionHandler = new TextActionHandler(actionBars); // hook
9094
// handlers
9195
}
96+
textActionHandler.setCutAction(cutAction);
9297
textActionHandler.setCopyAction(copyAction);
9398
textActionHandler.setPasteAction(pasteAction);
9499
textActionHandler.setDeleteAction(deleteAction);
@@ -124,6 +129,8 @@ protected void makeActions() {
124129
pasteAction.setImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_PASTE));
125130
pasteAction.setActionDefinitionId(IWorkbenchCommandConstants.EDIT_PASTE);
126131

132+
cutAction = new CutAction(shell, clipboard, pasteAction);
133+
127134
copyAction = new CopyAction(shell, clipboard, pasteAction);
128135
copyAction.setDisabledImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_COPY_DISABLED));
129136
copyAction.setImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_COPY));
@@ -140,6 +147,7 @@ protected void makeActions() {
140147
public void updateActionBars() {
141148
IStructuredSelection selection = (IStructuredSelection) getContext().getSelection();
142149

150+
cutAction.selectionChanged(selection);
143151
copyAction.selectionChanged(selection);
144152
pasteAction.selectionChanged(selection);
145153
deleteAction.selectionChanged(selection);

bundles/org.eclipse.ui.navigator.resources/src/org/eclipse/ui/internal/navigator/resources/actions/PasteAction.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.eclipse.ui.PlatformUI;
3232
import org.eclipse.ui.actions.CopyFilesAndFoldersOperation;
3333
import org.eclipse.ui.actions.CopyProjectOperation;
34+
import org.eclipse.ui.actions.MoveFilesAndFoldersOperation;
3435
import org.eclipse.ui.actions.SelectionListenerAction;
3536
import org.eclipse.ui.internal.navigator.resources.plugin.WorkbenchNavigatorMessages;
3637
import org.eclipse.ui.part.ResourceTransfer;
@@ -155,9 +156,15 @@ public void run() {
155156
} else {
156157
// enablement should ensure that we always have access to a container
157158
IContainer container = getContainer(resourceData);
158-
CopyFilesAndFoldersOperation operation = new CopyFilesAndFoldersOperation(shell);
159-
operation.copyResources(resourceData, container);
159+
if (CutAction.isCut) {
160+
MoveFilesAndFoldersOperation operation = new MoveFilesAndFoldersOperation(shell);
161+
operation.copyResources(resourceData, container);
162+
} else {
163+
CopyFilesAndFoldersOperation operation = new CopyFilesAndFoldersOperation(shell);
164+
operation.copyResources(resourceData, container);
165+
}
160166
}
167+
CutAction.isCut = false;
161168
return;
162169
}
163170

@@ -171,6 +178,7 @@ public void run() {
171178
CopyFilesAndFoldersOperation operation = new CopyFilesAndFoldersOperation(shell);
172179
operation.copyFiles(fileData, container);
173180
}
181+
CutAction.isCut = false;
174182
}
175183

176184
/**

bundles/org.eclipse.ui.navigator.resources/src/org/eclipse/ui/internal/navigator/resources/plugin/WorkbenchNavigatorMessages.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ public class WorkbenchNavigatorMessages extends NLS {
5555
public static String WorkingSetRootModeActionGroup_Working_Set_;
5656
public static String WorkingSetActionProvider_multipleWorkingSets;
5757

58+
public static String CutAction_Cut;
59+
public static String CutAction_Cut_selected_resource_s_;
60+
5861
public static String CopyAction_Cop_;
5962
public static String CopyAction_Copy_selected_resource_s_;
6063

bundles/org.eclipse.ui.navigator.resources/src/org/eclipse/ui/internal/navigator/resources/plugin/messages.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ DropAdapter_canNotDropProjectIntoProject=Cannot drop a project into another proj
3333
DropAdapter_problemsMoving=Problems occurred while moving resources.
3434
DropAdapter_dropOperationErrorOther=An error occurred during the drop operation.
3535
NewActionProvider_NewMenu_label=&New
36+
CutAction_Cut=Cut
37+
CutAction_Cut_selected_resource_s_=Cut the selected resource(s)
3638
CopyAction_Cop_=Copy
3739
CopyAction_Copy_selected_resource_s_=Copy selected resource(s)
3840
PasteAction_Past_=Paste

0 commit comments

Comments
 (0)