Skip to content

Commit cd4ffb6

Browse files
committed
Allow reuse of last opened editor via Project Explorer
1 parent 4acc3e3 commit cd4ffb6

File tree

9 files changed

+352
-5
lines changed

9 files changed

+352
-5
lines changed
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
package org.eclipse.ui.actions;
2+
3+
import org.eclipse.core.resources.IFile;
4+
import org.eclipse.core.resources.IResource;
5+
import org.eclipse.jface.util.OpenStrategy;
6+
import org.eclipse.ui.IEditorDescriptor;
7+
import org.eclipse.ui.IEditorPart;
8+
import org.eclipse.ui.IEditorReference;
9+
import org.eclipse.ui.IEditorRegistry;
10+
import org.eclipse.ui.IPageLayout;
11+
import org.eclipse.ui.IReusableEditor;
12+
import org.eclipse.ui.IWorkbenchPage;
13+
import org.eclipse.ui.IWorkbenchPartReference;
14+
import org.eclipse.ui.PartInitException;
15+
import org.eclipse.ui.ide.IDE;
16+
import org.eclipse.ui.internal.IPreferenceConstants;
17+
import org.eclipse.ui.internal.ide.DialogUtil;
18+
import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
19+
import org.eclipse.ui.internal.util.PrefUtil;
20+
import org.eclipse.ui.part.FileEditorInput;
21+
22+
/**
23+
* Open action that optionally reuses a single editor for opens originating from
24+
* the Project Explorer, similar to the Search view behavior. When the feature
25+
* is disabled or outside Project Explorer, behavior falls back to
26+
* {@link OpenFileAction}.
27+
*
28+
* Scope is currently limited to Project Explorer via the creating part id
29+
* passed in the constructor. Reuse stops when the editor becomes dirty, is
30+
* pinned, closed, or when the resolved editor id differs from the currently
31+
* reused editor id.
32+
*
33+
* This class is intentionally package-public in org.eclipse.ui.actions to be a
34+
* drop-in replacement for {@link OpenFileAction} in CNF wiring.
35+
*/
36+
public class OpenFileWithReuseAction extends OpenFileAction {
37+
38+
private final String hostPartId;
39+
private IEditorReference reusedRef;
40+
private boolean disableReuseForRun;
41+
42+
/**
43+
* @param page workbench page
44+
* @param hostPartId id of the part creating this action (used to scope to
45+
* Project Explorer)
46+
*/
47+
public OpenFileWithReuseAction(final IWorkbenchPage page, final String hostPartId) {
48+
this(page, null, hostPartId);
49+
}
50+
51+
/**
52+
* @param page workbench page
53+
* @param descriptor editor descriptor to use (or null for default)
54+
* @param hostPartId id of the part creating this action (used to scope to
55+
* Project Explorer)
56+
*/
57+
public OpenFileWithReuseAction(final IWorkbenchPage page, final IEditorDescriptor descriptor,
58+
final String hostPartId) {
59+
super(page, descriptor);
60+
this.hostPartId = hostPartId;
61+
}
62+
63+
@Override
64+
public void run() {
65+
// Disable reuse for multi-file selection to preserve baseline behavior
66+
int fileCount = 0;
67+
for (final IResource res : getSelectedResources()) {
68+
if (res instanceof IFile) {
69+
fileCount++;
70+
if (fileCount > 1) {
71+
break;
72+
}
73+
}
74+
}
75+
disableReuseForRun = fileCount > 1;
76+
try {
77+
super.run();
78+
} finally {
79+
disableReuseForRun = false;
80+
}
81+
}
82+
83+
@Override
84+
void openFile(final IFile file) {
85+
if (disableReuseForRun || !shouldApplyReuse()) {
86+
// Delegate to baseline behavior
87+
super.openFile(file);
88+
return;
89+
}
90+
91+
final IWorkbenchPage page = getWorkbenchPage();
92+
final boolean activate = OpenStrategy.activateOnOpen();
93+
94+
// If already open, just bring to top/activate
95+
final IEditorPart existing = page.findEditor(new FileEditorInput(file));
96+
if (existing != null) {
97+
if (activate) {
98+
page.activate(existing);
99+
} else {
100+
page.bringToTop(existing);
101+
}
102+
return;
103+
}
104+
105+
// Determine target editor id (explicit),
106+
// falling back to external system editor id
107+
String editorId = IEditorRegistry.SYSTEM_EXTERNAL_EDITOR_ID;
108+
IEditorDescriptor desc = null;
109+
try {
110+
desc = IDE.getEditorDescriptor(file, true, true);
111+
} catch (PartInitException e) {
112+
// ignore here; will fall back to system external editor id
113+
}
114+
if (desc != null) {
115+
editorId = desc.getId();
116+
}
117+
118+
// Do not attempt reuse for external editors
119+
if (IEditorRegistry.SYSTEM_EXTERNAL_EDITOR_ID.equals(editorId)) {
120+
super.openFile(file);
121+
return;
122+
}
123+
124+
// Try reuse if we have a valid reference
125+
if (reusedRef != null) {
126+
final boolean open = reusedRef.getEditor(false) != null;
127+
final boolean valid = open && !reusedRef.isDirty() && !reusedRef.isPinned();
128+
if (valid) {
129+
if (!editorId.equals(reusedRef.getId())) {
130+
// Different editor type needed; close old reusable editor
131+
page.closeEditors(new IEditorReference[] { reusedRef }, false);
132+
reusedRef = null;
133+
} else {
134+
final IEditorPart part = reusedRef.getEditor(true);
135+
if (part instanceof IReusableEditor reusableEditor) {
136+
reusableEditor.setInput(new FileEditorInput(file));
137+
if (activate) {
138+
page.activate(part);
139+
} else {
140+
page.bringToTop(part);
141+
}
142+
return;
143+
}
144+
// Not reusable after all
145+
reusedRef = null;
146+
}
147+
} else {
148+
// Reference no longer valid
149+
reusedRef = null;
150+
}
151+
}
152+
153+
// Open a new editor and remember it if reusable
154+
try {
155+
final IEditorPart opened = IDE.openEditor(page, file, editorId, activate);
156+
if (opened instanceof IReusableEditor) {
157+
final IWorkbenchPartReference ref = page.getReference(opened);
158+
if (ref instanceof IEditorReference editorRef) {
159+
reusedRef = editorRef;
160+
} else {
161+
reusedRef = null;
162+
}
163+
} else {
164+
reusedRef = null;
165+
}
166+
} catch (final PartInitException ex) {
167+
DialogUtil.openError(page.getWorkbenchWindow().getShell(),
168+
IDEWorkbenchMessages.OpenFileAction_openFileShellTitle, ex.getMessage(), ex);
169+
}
170+
}
171+
172+
private boolean shouldApplyReuse() {
173+
// Strictly scope to Project Explorer and opt-in preference
174+
if (hostPartId == null || !hostPartId.startsWith(IPageLayout.ID_PROJECT_EXPLORER)) {
175+
return false;
176+
}
177+
178+
return PrefUtil.getInternalPreferenceStore().getBoolean(IPreferenceConstants.REUSE_LAST_OPENED_EDITOR);
179+
}
180+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.eclipse.jface.viewers.IStructuredSelection;
2727
import org.eclipse.ui.IActionBars;
2828
import org.eclipse.ui.actions.OpenFileAction;
29+
import org.eclipse.ui.actions.OpenFileWithReuseAction;
2930
import org.eclipse.ui.actions.OpenWithMenu;
3031
import org.eclipse.ui.internal.navigator.resources.plugin.WorkbenchNavigatorMessages;
3132
import org.eclipse.ui.navigator.CommonActionProvider;
@@ -51,7 +52,7 @@ public class OpenActionProvider extends CommonActionProvider {
5152
public void init(ICommonActionExtensionSite aConfig) {
5253
if (aConfig.getViewSite() instanceof ICommonViewerWorkbenchSite) {
5354
viewSite = (ICommonViewerWorkbenchSite) aConfig.getViewSite();
54-
openFileAction = new OpenFileAction(viewSite.getPage());
55+
openFileAction = new OpenFileWithReuseAction(viewSite.getPage(), viewSite.getSite().getId());
5556
contribute = true;
5657
}
5758
}

bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/IPreferenceConstants.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,4 +327,10 @@ public interface IPreferenceConstants {
327327
*/
328328
String SHOW_KEYS_TIME_TO_CLOSE = "showCommandKeys_timeToClose"; //$NON-NLS-1$
329329

330+
/**
331+
* Preference to enable editor reuse for opens from (currently only) Project
332+
* Explorer. Applies to both single- and double-click. Default false.
333+
*/
334+
String REUSE_LAST_OPENED_EDITOR = "REUSE_LAST_OPENED_EDITOR"; //$NON-NLS-1$
335+
330336
}

bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/WorkbenchMessages.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,7 @@ public class WorkbenchMessages extends NLS {
545545
public static String WorkbenchPreference_singleClick;
546546
public static String WorkbenchPreference_singleClick_SelectOnHover;
547547
public static String WorkbenchPreference_singleClick_OpenAfterDelay;
548+
public static String WorkbenchPreference_reuseLastOpenedEditor;
548549
public static String WorkbenchPreference_noEffectOnAllViews;
549550
public static String WorkbenchPreference_HeapStatusButton;
550551
public static String WorkbenchPreference_HeapStatusButtonToolTip;

bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/WorkbenchPreferenceInitializer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public void initializeDefaultPreferences() {
5959
node.putBoolean(IPreferenceConstants.STICKY_CYCLE, false);
6060
node.putBoolean(IPreferenceConstants.REUSE_EDITORS_BOOLEAN, true);
6161
node.putInt(IPreferenceConstants.REUSE_EDITORS, 99);
62+
node.putBoolean(IPreferenceConstants.REUSE_LAST_OPENED_EDITOR, false);
6263
node.putBoolean(IPreferenceConstants.OPEN_ON_SINGLE_CLICK, false);
6364
node.putBoolean(IPreferenceConstants.SELECT_ON_HOVER, false);
6465
node.putBoolean(IPreferenceConstants.OPEN_AFTER_DELAY, false);

bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/dialogs/WorkbenchPreferencePage.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ public class WorkbenchPreferencePage extends PreferencePage implements IWorkbenc
8080

8181
private Button showInlineRenameButton;
8282

83+
private Button reuseLastOpenedEditorButton;
84+
8385
protected static int MAX_SAVE_INTERVAL = 9999;
8486
protected static int MAX_VIEW_LIMIT = 1_000_000;
8587

@@ -295,6 +297,14 @@ protected void createOpenModeGroup(Composite composite) {
295297
data.horizontalIndent = LayoutConstants.getIndent();
296298
openAfterDelayButton.setLayoutData(data);
297299

300+
// reuse last opened editor when opening a file (currently only in Project
301+
// Explorer)
302+
reuseLastOpenedEditorButton = new Button(buttonComposite, SWT.CHECK | SWT.LEFT);
303+
reuseLastOpenedEditorButton
304+
.setText(WorkbenchMessages.WorkbenchPreference_reuseLastOpenedEditor);
305+
reuseLastOpenedEditorButton.setSelection(
306+
getPreferenceStore().getBoolean(IPreferenceConstants.REUSE_LAST_OPENED_EDITOR));
307+
298308
createNoteComposite(font, buttonComposite, WorkbenchMessages.Preference_note,
299309
WorkbenchMessages.WorkbenchPreference_noEffectOnAllViews);
300310
}
@@ -419,6 +429,8 @@ protected void performDefaults() {
419429
openAfterDelayButton.setSelection(openAfterDelay);
420430
selectOnHoverButton.setEnabled(openOnSingleClick);
421431
openAfterDelayButton.setEnabled(openOnSingleClick);
432+
reuseLastOpenedEditorButton
433+
.setSelection(store.getDefaultBoolean(IPreferenceConstants.REUSE_LAST_OPENED_EDITOR));
422434
stickyCycleButton.setSelection(store.getDefaultBoolean(IPreferenceConstants.STICKY_CYCLE));
423435
showUserDialogButton.setSelection(store.getDefaultBoolean(IPreferenceConstants.RUN_IN_BACKGROUND));
424436
showHeapStatusButton.setSelection(
@@ -443,6 +455,8 @@ public boolean performOk() {
443455
store.setValue(IPreferenceConstants.OPEN_ON_SINGLE_CLICK, openOnSingleClick);
444456
store.setValue(IPreferenceConstants.SELECT_ON_HOVER, selectOnHover);
445457
store.setValue(IPreferenceConstants.OPEN_AFTER_DELAY, openAfterDelay);
458+
store.setValue(IPreferenceConstants.REUSE_LAST_OPENED_EDITOR,
459+
reuseLastOpenedEditorButton.getSelection());
446460
store.setValue(IPreferenceConstants.RUN_IN_BACKGROUND, showUserDialogButton.getSelection());
447461
store.setValue(IPreferenceConstants.WORKBENCH_SAVE_INTERVAL, saveInterval.getIntValue());
448462
store.setValue(IWorkbenchPreferenceConstants.LARGE_VIEW_LIMIT, largeViewLimit.getIntValue());

bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/messages.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ OpenPerspectiveDialogAction_tooltip=Open Perspective
496496

497497
#---- General Preferences----
498498
PreferencePage_noDescription = (No description available)
499-
PreferencePageParameterValues_pageLabelSeparator = \ >\
499+
PreferencePageParameterValues_pageLabelSeparator = \ >\
500500
ThemingEnabled = E&nable theming
501501
ThemeChangeWarningText = Restart for the theme changes to take full effect
502502
ThemeChangeWarningTitle = Theme Changed
@@ -511,6 +511,7 @@ WorkbenchPreference_doubleClick=D&ouble click
511511
WorkbenchPreference_singleClick=&Single click
512512
WorkbenchPreference_singleClick_SelectOnHover=Select on &hover
513513
WorkbenchPreference_singleClick_OpenAfterDelay=Open when using arrow &keys
514+
WorkbenchPreference_reuseLastOpenedEditor=Reuse last opened editor when opening files via Project Explorer
514515
WorkbenchPreference_noEffectOnAllViews=This preference may not take effect on all views
515516
WorkbenchPreference_inlineRename=&Rename resource inline if available
516517

tests/org.eclipse.ui.tests.navigator/src/org/eclipse/ui/tests/navigator/NavigatorTestSuite.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@
2525
import org.eclipse.ui.tests.navigator.resources.NestedResourcesTests;
2626
import org.eclipse.ui.tests.navigator.resources.PathComparatorTest;
2727
import org.eclipse.ui.tests.navigator.resources.ResourceMgmtActionProviderTests;
28-
29-
import org.junit.platform.suite.api.Suite;
3028
import org.junit.platform.suite.api.SelectClasses;
29+
import org.junit.platform.suite.api.Suite;
3130

3231
@Suite
3332
@SelectClasses({ InitialActivationTest.class, ActionProviderTest.class, ExtensionsTest.class, FilterTest.class,
@@ -37,7 +36,7 @@
3736
FirstClassM1Tests.class, LinkHelperTest.class, ShowInTest.class, ResourceTransferTest.class,
3837
EvaluationCacheTest.class, ResourceMgmtActionProviderTests.class,
3938
NestedResourcesTests.class, PathComparatorTest.class, FoldersAsProjectsContributionTest.class,
40-
GoBackForwardsTest.class
39+
GoBackForwardsTest.class, OpenFileReuseActionTest.class,
4140
// DnDTest.class, // DnDTest.testSetDragOperation() fails
4241
// PerformanceTest.class // Does not pass on all platforms see bug 264449
4342
})

0 commit comments

Comments
 (0)