Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--org.eclipse.ui.editors/icons/full/dtool16/next_edit_pos.pngbin0 -> 529 bytes
-rw-r--r--org.eclipse.ui.editors/icons/full/dtool16/next_edit_pos@2x.pngbin0 -> 1081 bytes
-rw-r--r--org.eclipse.ui.editors/icons/full/etool16/next_edit_pos.pngbin0 -> 576 bytes
-rw-r--r--org.eclipse.ui.editors/icons/full/etool16/next_edit_pos@2x.pngbin0 -> 1190 bytes
-rw-r--r--org.eclipse.ui.editors/plugin.properties2
-rw-r--r--org.eclipse.ui.editors/plugin.xml13
-rw-r--r--org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/TextEditorPluginTest.java387
-rw-r--r--org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/WorkbenchTextEditorTestSuite.java3
-rw-r--r--org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF2
-rw-r--r--org.eclipse.ui.workbench.texteditor/plugin.properties2
-rw-r--r--org.eclipse.ui.workbench.texteditor/plugin.xml11
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/EditPosition.java49
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/EditorMessages.java2
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/EditorMessages.properties2
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/HistoryTracker.java412
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/TextEditorPlugin.java110
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java72
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/GotoLastEditPositionAction.java91
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/GotoNextEditPositionAction.java162
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/IAbstractTextEditorHelpContextIds.java13
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/ITextEditorActionDefinitionIds.java8
21 files changed, 1252 insertions, 89 deletions
diff --git a/org.eclipse.ui.editors/icons/full/dtool16/next_edit_pos.png b/org.eclipse.ui.editors/icons/full/dtool16/next_edit_pos.png
new file mode 100644
index 00000000000..995f1badd96
--- /dev/null
+++ b/org.eclipse.ui.editors/icons/full/dtool16/next_edit_pos.png
Binary files differ
diff --git a/org.eclipse.ui.editors/icons/full/dtool16/next_edit_pos@2x.png b/org.eclipse.ui.editors/icons/full/dtool16/next_edit_pos@2x.png
new file mode 100644
index 00000000000..ee5c0432839
--- /dev/null
+++ b/org.eclipse.ui.editors/icons/full/dtool16/next_edit_pos@2x.png
Binary files differ
diff --git a/org.eclipse.ui.editors/icons/full/etool16/next_edit_pos.png b/org.eclipse.ui.editors/icons/full/etool16/next_edit_pos.png
new file mode 100644
index 00000000000..5fa266b0760
--- /dev/null
+++ b/org.eclipse.ui.editors/icons/full/etool16/next_edit_pos.png
Binary files differ
diff --git a/org.eclipse.ui.editors/icons/full/etool16/next_edit_pos@2x.png b/org.eclipse.ui.editors/icons/full/etool16/next_edit_pos@2x.png
new file mode 100644
index 00000000000..47c1f8a8159
--- /dev/null
+++ b/org.eclipse.ui.editors/icons/full/etool16/next_edit_pos@2x.png
Binary files differ
diff --git a/org.eclipse.ui.editors/plugin.properties b/org.eclipse.ui.editors/plugin.properties
index 42b9b6ea423..f21d49ba5e3 100644
--- a/org.eclipse.ui.editors/plugin.properties
+++ b/org.eclipse.ui.editors/plugin.properties
@@ -84,6 +84,8 @@ revisionInfo.label= Revision Information
goToLastEditPosition.label= Last Edit Lo&cation
goToLastEditPosition.tooltip= Last Edit Location
+goToNextEditPosition.label= Next Edit Lo&cation
+goToNextEditPosition.tooltip= Next Edit Location
textEditorNavigationActionSet.label= Editor Navigation
diff --git a/org.eclipse.ui.editors/plugin.xml b/org.eclipse.ui.editors/plugin.xml
index cf169c3895f..050a3d0f3d3 100644
--- a/org.eclipse.ui.editors/plugin.xml
+++ b/org.eclipse.ui.editors/plugin.xml
@@ -481,6 +481,19 @@
tooltip="%goToLastEditPosition.tooltip"
initialEnabled="false">
</action>
+ <action
+ toolbarPath="org.eclipse.ui.workbench.navigate/history.group"
+ id="org.eclipse.ui.edit.text.gotoNextEditPosition"
+ class="org.eclipse.ui.texteditor.GotoNextEditPositionAction"
+ definitionId="org.eclipse.ui.edit.text.gotoNextEditPosition"
+ disabledIcon="$nl$/icons/full/dtool16/next_edit_pos.png"
+ icon="$nl$/icons/full/etool16/next_edit_pos.png"
+ helpContextId="org.eclipse.ui.goto_next_edit_position_action_context"
+ label="%goToNextEditPosition.label"
+ menubarPath="navigate/"
+ tooltip="%goToNextEditPosition.tooltip"
+ initialEnabled="false">
+ </action>
</actionSet>
<actionSet
label="%conversionActionSet.label"
diff --git a/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/TextEditorPluginTest.java b/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/TextEditorPluginTest.java
new file mode 100644
index 00000000000..57d2faea923
--- /dev/null
+++ b/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/TextEditorPluginTest.java
@@ -0,0 +1,387 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2016 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.workbench.texteditor.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Random;
+
+import org.junit.FixMethodOrder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runners.MethodSorters;
+
+import org.eclipse.ui.internal.texteditor.HistoryTracker;
+
+/**
+ * Tests the FindReplaceDialog.
+ *
+ * @since 3.1
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class TextEditorPluginTest {
+
+ @Rule
+ public TestName testName = new TestName();
+
+ Random rand = new Random(55); //pseudo-random for repeatability
+
+ @Test
+ public void testEditPositionHistory() {
+ HistoryTracker<Integer> history= new HistoryTracker<>(3,
+ Integer.class,
+ (a, b) -> Math.abs(a - b) < 5, true);
+
+ assertEquals(0, history.getSize());
+ history.addOrReplace(10);
+ assertEquals(1, history.getSize());
+ assertTrue(10 == history.getCurrentBrowsePoint());
+
+ history.addOrReplace(20);
+ assertEquals(2, history.getSize());
+ assertTrue(20 == history.getCurrentBrowsePoint());
+
+ history.addOrReplace(30);
+ assertEquals(3, history.getSize());
+ assertTrue(30 == history.getCurrentBrowsePoint());
+
+ checkContent(history, new Integer[] { 10, 20, 30 });
+
+
+ int replaced = history.addOrReplace(40);
+ assertEquals(3, history.getSize());
+ assertEquals(10, replaced);
+ assertEquals(Integer.valueOf(40), history.getCurrentBrowsePoint());
+
+ HistoryTracker.Navigator<Integer> nav = history.navigator();
+ assertEquals(Integer.valueOf(40), nav.currentItem());
+ assertEquals(Integer.valueOf(30), nav.priorItem());
+ assertEquals(Integer.valueOf(20), nav.priorItem());
+ assertEquals(Integer.valueOf(40), nav.priorItem());
+ checkContent(history, new Integer[] { 20, 30, 40 });
+
+ assertFalse(history.contains(10));
+
+ replaced = history.addOrReplace(22);
+ assertTrue(history.contains(22));
+ assertFalse(history.contains(20));
+ assertEquals(20, replaced);
+ checkContent(history, new Integer[] { 30, 40, 22 });
+ //assertTrue(22 == history.getCurrentBrowsePoint());
+
+ replaced = history.addOrReplace(31);
+ assertTrue(history.contains(31));
+ assertFalse(history.contains(30));
+ assertEquals(30, replaced);
+ assertTrue(31 == history.getCurrentBrowsePoint());
+ checkContent(history, new Integer[] { 40, 22, 31 });
+
+ replaced = history.addOrReplace(60);
+ assertTrue(history.contains(60));
+ assertTrue(60 == history.getCurrentBrowsePoint());
+ assertEquals(3, history.getSize());
+ checkContent(history, new Integer[] { 22, 31, 60 });
+
+ assertTrue(31 == history.browseBackward());
+ assertTrue(31 == history.getCurrentBrowsePoint());
+ assertEquals(3, history.getSize());
+
+ //consuming size times should bring you full circle back to origin
+ testBacktrackCycle(history);
+
+ //try editing after backtracking less than full cycle
+ history.browseBackward();
+ Integer last = history.getCurrentBrowsePoint();
+ assertEquals(Integer.valueOf(22), last);
+ history.addOrReplace(11);
+ checkContent(history, new Integer[] { 31, 60, 11 });
+
+ testBacktrackCycle(history);
+ history.browseBackward();
+ assertEquals(Integer.valueOf(60), history.getCurrentBrowsePoint());
+ }
+
+ <T> void checkContent(HistoryTracker<T> history, T[] data) {
+ HistoryTracker.Navigator<T> nav = history.navigator();
+ assertEquals(data[data.length - 1], nav.currentItem());
+
+ for(int i=data.length - 2; i>0; i--) {
+ assertEquals(data[i], nav.priorItem());
+ }
+ assertEquals(history.getSize(), data.length);
+ }
+
+ @Test
+ public void testEditPositionHistory2() {
+ HistoryTracker<Integer> history= new HistoryTracker<>(3,
+ Integer.class,
+ (a, b) -> Math.abs(a - b) < 5, true);
+
+ assertEquals(0, history.getSize());
+ history.addOrReplace(10);
+ assertEquals(1, history.getSize());
+ assertTrue(10 == history.getCurrentBrowsePoint());
+
+ history.addOrReplace(20);
+ assertEquals(2, history.getSize());
+ assertTrue(20 == history.getCurrentBrowsePoint());
+
+ history.addOrReplace(30);
+ assertEquals(3, history.getSize());
+ assertTrue(30 == history.getCurrentBrowsePoint());
+
+ int replaced = history.addOrReplace(22);
+ assertEquals(3, history.getSize());
+ assertEquals(20, replaced);
+ assertEquals(Integer.valueOf(22), history.getCurrentBrowsePoint());
+
+ assertEquals(Integer.valueOf(30), history.browseBackward());
+ assertEquals(Integer.valueOf(10), history.browseBackward());
+ assertEquals(Integer.valueOf(22), history.browseBackward());
+
+ testBacktrackCycle(history);
+ }
+
+ @Test
+ public void testHistoryEviction() {
+ HistoryTracker<Integer> history= new HistoryTracker<>(3,
+ Integer.class,
+ (a, b) -> Math.abs(a - b) < 5,
+ true);
+
+ assertEquals(0, history.getSize());
+ history.addOrReplace(10);
+ assertEquals(1, history.getSize());
+ assertTrue(10 == history.getCurrentBrowsePoint());
+
+ history.addOrReplace(10);
+ assertEquals(1, history.getSize());
+ assertTrue(10 == history.getCurrentBrowsePoint());
+
+ history.addOrReplace(11);
+ assertEquals(1, history.getSize());
+ assertTrue(11 == history.getCurrentBrowsePoint());
+ }
+
+ @Test
+ public void testHistoryEviction2() {
+ HistoryTracker<Integer> history= new HistoryTracker<>(3,
+ Integer.class,
+ (a, b) -> Math.abs(a - b) < 5,
+ true);
+
+ history.addOrReplace(10);
+ history.addOrReplace(20);
+ history.addOrReplace(30);
+ assertEquals(Integer.valueOf(20), history.browseBackward());
+ assertEquals(Integer.valueOf(10), history.addOrReplace(40));
+ }
+
+ @Test
+ public void testHistoryEviction3() {
+ HistoryTracker<Integer> history= new HistoryTracker<>(3,
+ Integer.class,
+ (a, b) -> Math.abs(a - b) < 5,
+ true);
+
+ history.addOrReplace(10);
+ history.addOrReplace(11);
+ history.addOrReplace(20);
+ history.addOrReplace(21);
+ history.addOrReplace(12);
+ checkContent(history, new Integer[] { 21, 12 });
+ }
+
+ @Test
+ public void testLinearEditPositionHistory() {
+ HistoryTracker<Integer> history= new HistoryTracker<>(3,
+ Integer.class,
+ (a, b) -> Math.abs(a - b) < 5,
+ false);
+
+ assertEquals(0, history.getSize());
+ history.addOrReplace(10);
+ assertEquals(1, history.getSize());
+ assertTrue(10 == history.getCurrentBrowsePoint());
+
+ history.addOrReplace(20);
+ assertEquals(2, history.getSize());
+ assertTrue(20 == history.getCurrentBrowsePoint());
+
+ history.addOrReplace(30);
+ assertEquals(3, history.getSize());
+ assertTrue(30 == history.getCurrentBrowsePoint());
+
+ int replaced = history.addOrReplace(22);
+ assertEquals(3, history.getSize());
+ assertEquals(20, replaced);
+ assertEquals(Integer.valueOf(22), history.getCurrentBrowsePoint());
+
+ assertEquals(Integer.valueOf(30), history.browseBackward());
+ assertEquals(Integer.valueOf(10), history.browseBackward());
+ assertEquals(Integer.valueOf(10), history.browseBackward());
+
+ assertEquals(Integer.valueOf(30), history.browseForward());
+ assertEquals(Integer.valueOf(22), history.browseForward());
+ assertEquals(Integer.valueOf(22), history.browseForward());
+
+ }
+
+ @Test
+ public void testLinearEditPositionHistory2() {
+ HistoryTracker<Integer> history= new HistoryTracker<>(3,
+ Integer.class,
+ (a, b) -> Math.abs(a - b) < 5,
+ false);
+
+ history.addOrReplace(10);
+ history.addOrReplace(20);
+ history.addOrReplace(30);
+
+ int replaced = history.addOrReplace(22);
+ assertEquals(3, history.getSize());
+ assertEquals(20, replaced);
+ assertEquals(Integer.valueOf(22), history.getCurrentBrowsePoint());
+
+ //end reached, go no further
+ assertEquals(Integer.valueOf(22), history.getNext());
+
+ assertEquals(Integer.valueOf(30), history.browseBackward());
+ assertEquals(Integer.valueOf(10), history.browseBackward());
+
+ //beginning reached, go no further
+ assertEquals(Integer.valueOf(10), history.browseBackward());
+ }
+
+ @Test
+ public void testMRUOrderAlwaysPreserved() {
+ HistoryTracker<Integer> history= new HistoryTracker<>(3,
+ Integer.class,
+ (a, b) -> Math.abs(a - b) < 5,
+ false);
+
+ history.addOrReplace(10);
+ history.addOrReplace(20);
+ history.addOrReplace(30);
+
+ assertEquals(Integer.valueOf(20), history.browseBackward());
+ history.addOrReplace(11);
+ assertEquals(Integer.valueOf(11), history.getCurrentBrowsePoint());
+
+ assertEquals(Integer.valueOf(30), history.browseBackward());
+ assertEquals(Integer.valueOf(20), history.browseBackward());
+ }
+
+ @Test
+ public void testMRUOrderAlwaysPreserved2() {
+ HistoryTracker<Integer> history= new HistoryTracker<>(3,
+ Integer.class,
+ (a, b) -> Math.abs(a - b) < 5,
+ false);
+
+ history.addOrReplace(10);
+ history.addOrReplace(20);
+ history.addOrReplace(30);
+
+ assertEquals(Integer.valueOf(20), history.browseBackward());
+ history.addOrReplace(40);
+ assertEquals(Integer.valueOf(40), history.getCurrentBrowsePoint());
+
+ assertEquals(Integer.valueOf(30), history.browseBackward());
+ assertEquals(Integer.valueOf(20), history.browseBackward());
+ assertEquals(Integer.valueOf(20), history.browseBackward());
+
+ }
+
+ private <T> void testBacktrackCycle(HistoryTracker<T> history) {
+ T last = history.getCurrentBrowsePoint();
+ for(int i=0; i<history.getSize() -1; i++) {
+ history.browseBackward();
+ assertNotEquals(last, history.getCurrentBrowsePoint());
+ }
+ history.browseBackward();
+ assertEquals(last, history.getCurrentBrowsePoint());
+ }
+
+ @Test
+ public void testEditPositionHistoryChaos() {
+ final int HISTORY_SIZE= 10;
+ HistoryTracker<Integer> history= new HistoryTracker<>(HISTORY_SIZE,
+ Integer.class,
+ (a, b) -> Math.abs(a - b) < 5, true);
+
+ for(int i=0;i<100;i++) {
+ if(rand.nextBoolean()) {
+ addRandom(history);
+ } else {
+ goBack(history);
+ }
+ assertTrue(history.isHealthy());
+ assertTrue(history.getSize() <= HISTORY_SIZE);
+ }
+ }
+
+ @Test
+ public void testLinearEditPositionHistoryChaos() {
+ final int HISTORY_SIZE= 10;
+ HistoryTracker<Integer> history= new HistoryTracker<>(HISTORY_SIZE,
+ Integer.class,
+ (a,b) -> Math.abs(a - b) < 5,
+ false
+ );
+
+ int backsInARow = 0;
+ for(int i=0;i<100;i++) {
+ if(rand.nextBoolean()) {
+ backsInARow = 0;
+ addRandom(history);
+ } else {
+ backsInARow ++;
+ goBackLinear(history, backsInARow < history.getSize());
+ }
+ assertTrue(history.isHealthy());
+ assertTrue(history.getSize() <= HISTORY_SIZE);
+ }
+ }
+
+
+ private void addRandom(HistoryTracker<Integer> history) {
+ Integer latest = rand.nextInt(50);
+ history.addOrReplace(latest);
+ assertEquals(latest, history.getCurrentBrowsePoint());
+ }
+
+ private void goBack(HistoryTracker<Integer> history) {
+ int size = history.getSize();
+ Integer latest = history.getCurrentBrowsePoint();
+ Integer prior = history.browseBackward();
+ if (size > 1) {
+ assertNotEquals(latest, prior);
+ }
+ }
+
+ private void goBackLinear(HistoryTracker<Integer> history, boolean shouldMove) {
+ Integer latest= history.getCurrentBrowsePoint();
+ Integer prior = history.browseBackward();
+ if(shouldMove)
+ assertNotEquals(latest, prior);
+ else
+ assertEquals(latest, prior);
+ }
+
+}
diff --git a/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/WorkbenchTextEditorTestSuite.java b/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/WorkbenchTextEditorTestSuite.java
index db70c7a3560..c6f03ab0862 100644
--- a/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/WorkbenchTextEditorTestSuite.java
+++ b/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/WorkbenchTextEditorTestSuite.java
@@ -42,7 +42,8 @@ import org.eclipse.ui.workbench.texteditor.tests.rulers.RulerTestSuite;
AbstractTextZoomHandlerTest.class,
DocumentLineDifferTest.class,
MinimapPageTest.class,
- MinimapWidgetTest.class
+ MinimapWidgetTest.class,
+ TextEditorPluginTest.class
})
public class WorkbenchTextEditorTestSuite {
// see @SuiteClasses
diff --git a/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF b/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF
index 67ed9a92f96..44dced6b1cc 100644
--- a/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF
+++ b/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.ui.workbench.texteditor; singleton:=true
-Bundle-Version: 3.14.300.qualifier
+Bundle-Version: 3.15.0.qualifier
Bundle-Activator: org.eclipse.ui.internal.texteditor.TextEditorPlugin
Bundle-ActivationPolicy: lazy
Bundle-Vendor: %providerName
diff --git a/org.eclipse.ui.workbench.texteditor/plugin.properties b/org.eclipse.ui.workbench.texteditor/plugin.properties
index a47dc433f86..fea5ab02b1a 100644
--- a/org.eclipse.ui.workbench.texteditor/plugin.properties
+++ b/org.eclipse.ui.workbench.texteditor/plugin.properties
@@ -67,6 +67,8 @@ lowerCase.label= To Lower Case
goToLastEditPosition.label= Last Edit Location
goToLastEditPosition.description= Last edit location
+goToNextEditPosition.label= Next Edit Location
+goToNextEditPosition.description= Next edit location
command.clearMark.description = Clear the mark
command.clearMark.name = Clear Mark
diff --git a/org.eclipse.ui.workbench.texteditor/plugin.xml b/org.eclipse.ui.workbench.texteditor/plugin.xml
index 26cf0ac9ffd..093dffa0316 100644
--- a/org.eclipse.ui.workbench.texteditor/plugin.xml
+++ b/org.eclipse.ui.workbench.texteditor/plugin.xml
@@ -359,6 +359,12 @@
id="org.eclipse.ui.edit.text.gotoLastEditPosition">
</command>
<command
+ name="%goToNextEditPosition.label"
+ description="%goToNextEditPosition.description"
+ categoryId="org.eclipse.ui.category.navigate"
+ id="org.eclipse.ui.edit.text.gotoNextEditPosition">
+ </command>
+ <command
name="%smartEnter.label"
description="%smartEnter.description"
categoryId="org.eclipse.ui.category.textEditor"
@@ -593,6 +599,11 @@
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
sequence="CTRL+Q"/> <!-- Command+Q is quit on carbon, so don't overwrite it -->
<key
+ commandId="org.eclipse.ui.edit.text.gotoNextEditPosition"
+ contextId="org.eclipse.ui.contexts.window"
+ schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
+ sequence="M3+CTRL+Q"/>
+ <key
commandId="org.eclipse.ui.edit.text.smartEnter"
contextId="org.eclipse.ui.textEditorScope"
sequence="M2+CR"
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/EditPosition.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/EditPosition.java
index 87805f2d0fe..c59ffc83611 100644
--- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/EditPosition.java
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/EditPosition.java
@@ -32,6 +32,12 @@ public final class EditPosition {
private final String fEditorId;
/** The position */
private final Position fPosition;
+ /**
+ * how many characters may come in between two edit positions for them to
+ * still be lumped into same bucket in position history (designed to prevent
+ * filling history with meaningless noise of very similar positions)
+ */
+ public static final int PROXIMITY_THRESHOLD = 30;
/**
* Creates a new edit position.
@@ -73,4 +79,47 @@ public final class EditPosition {
public Position getPosition() {
return fPosition;
}
+
+ /**
+ * @param a Position to compare the other arg against
+ * @param b Another position to compare the first arg against
+ * @param threshold The maximum allowed distance between Position args for them
+ * to be considered co-located
+ * @return true if both Position args are colocated as defined by the threshold
+ * param
+ * @since 3.15
+ */
+ public static boolean areCoLocated(Position a, Position b, int threshold) {
+ if (a == null || b == null) {
+ return false;
+ }
+ int center1 = a.offset + (a.length / 2);
+ int center2 = b.offset + (b.length / 2);
+ int centerDistance = Math.abs(center1 - center2);
+ int minWithoutOverlap = a.length / 2 + b.length / 2;
+ return centerDistance < (minWithoutOverlap + threshold);
+ }
+
+ /**
+ * @since 3.15
+ */
+ public static boolean areCoLocated(Position a, Position b) {
+ return EditPosition.areCoLocated(a, b, EditPosition.PROXIMITY_THRESHOLD);
+ }
+
+ /**
+ * @since 3.15
+ */
+ public static boolean areCoLocated(EditPosition a, EditPosition b, int threshold) {
+ return a != null && b != null && a.getEditorInput().getName()
+ .equals(b.getEditorInput().getName())
+ && EditPosition.areCoLocated(a.getPosition(), b.getPosition(), threshold);
+ }
+
+ /**
+ * @since 3.15
+ */
+ public static boolean areCoLocated(EditPosition a, EditPosition b) {
+ return EditPosition.areCoLocated(a, b, EditPosition.PROXIMITY_THRESHOLD);
+ }
}
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/EditorMessages.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/EditorMessages.java
index f3743d6b667..311a154b4ad 100644
--- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/EditorMessages.java
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/EditorMessages.java
@@ -28,6 +28,8 @@ final class EditorMessages extends NLS {
public static String Editor_error_gotoLastEditPosition_title;
public static String Editor_error_gotoLastEditPosition_message;
+ public static String Editor_error_gotoNextEditPosition_title;
+ public static String Editor_error_gotoNextEditPosition_message;
static {
NLS.initializeMessages(BUNDLE_NAME, EditorMessages.class);
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/EditorMessages.properties b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/EditorMessages.properties
index edf80884612..9603efa20a5 100644
--- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/EditorMessages.properties
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/EditorMessages.properties
@@ -17,3 +17,5 @@
Editor_error_gotoLastEditPosition_title= Problems going to last edit position
Editor_error_gotoLastEditPosition_message= Unable to go to the last edit position.
+Editor_error_gotoNextEditPosition_title= Problems going to next edit position
+Editor_error_gotoNextEditPosition_message= Unable to go to the next edit position.
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/HistoryTracker.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/HistoryTracker.java
new file mode 100644
index 00000000000..a96686c6371
--- /dev/null
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/HistoryTracker.java
@@ -0,0 +1,412 @@
+/*******************************************************************************
+* Copyright (c) 2020 Ari Kast and others.
+*
+* This program and the accompanying materials
+* are made available under the terms of the Eclipse Public License 2.0
+* which accompanies this distribution, and is available at
+* https://www.eclipse.org/legal/epl-2.0/
+*
+* SPDX-License-Identifier: EPL-2.0
+*
+* Contributors:
+* Ari Kast - initial API and implementation
+*******************************************************************************/
+
+package org.eclipse.ui.internal.texteditor;
+
+import java.lang.reflect.Array;
+
+/**
+ * @author Ari Kast
+ *
+ * Tracks history in order of insertion. It can operate either as a ring
+ * or as a line: in ring mode, if history size is N, then calling
+ * goBackward N times brings you full circle back to your current
+ * location in linear mode, if history size is N, then calling
+ * goBackward N times brings you to the beginning, after which
+ * additional calls to goBackward will have no effect until either
+ * goForward is called or a new entry is added Both linear and ring mode
+ * overwrite history as needed when buffer is full
+ * @param <T> the type of the object instances being tracked in history
+ *
+ * @since 3.15
+ */
+public class HistoryTracker<T> {
+ // the actual historical data
+ T[] fHistory;
+
+ // function to determine whether history elements can be merged
+ CandidateEvaluator<T> fEvaluator;
+
+ // pointer to current location in history queue
+ Navigator<T> fBrowsePoint;
+
+ // pointer to most recent insertion to history queue. New insertions always
+ // go to index fMostRecent + 1
+ Navigator<T> fInsertionPoint;
+
+ // the size of the dataset contained in history queue
+ // grows til it reaches history.length, and generally wont
+ // shrink
+ int fSize;
+
+ // controls whether navigation wraps around in circular fashion
+ // or if it is purely linear
+ boolean fUseCircularNavigation;
+
+ /**
+ * @param historySize the buffer size. additional insertions will
+ * overwrite oldest insertions
+ * @param clazz the class type of the objects being tracked
+ * @param evaluator The expression which compares incoming elements
+ * against existing elements. If this expression
+ * yields true, then the element(s) for which it
+ * was true is/are replaced
+ * @param useCircularNavigation when true the history operates in ring mode,
+ * otherwise it is linear
+ */
+ @SuppressWarnings("unchecked")
+ public HistoryTracker(int historySize, Class<T> clazz, CandidateEvaluator<T> evaluator,
+ boolean useCircularNavigation) {
+ historySize = Math.max(historySize, 1); // size < 1 makes no sense, so
+ // enforce at least size 1
+ fHistory = (T[]) Array.newInstance(clazz, historySize);
+ this.fEvaluator = evaluator;
+ this.fUseCircularNavigation = useCircularNavigation;
+ fBrowsePoint = new Navigator<>(this);
+ fInsertionPoint = new Navigator<>(this);
+ }
+
+ public T browseBackward() {
+ if (canGoBackward()) {
+ fBrowsePoint.decr();
+ }
+ return getCurrentBrowsePoint();
+ }
+
+ public T browseForward() {
+ if (canGoForward()) {
+ fBrowsePoint.incr();
+ }
+ return getCurrentBrowsePoint();
+ }
+
+ private boolean canGoBackward() {
+ return fSize > 0 && (fUseCircularNavigation || fBrowsePoint.getPriorIndex() != fInsertionPoint.getIndex());
+ }
+
+ private boolean canGoForward() {
+ return fSize > 0 && (fUseCircularNavigation || fBrowsePoint.getIndex() != fInsertionPoint.getIndex());
+ }
+
+ public T getCurrentBrowsePoint() {
+ return fBrowsePoint.currentItem();
+ }
+
+ public T getNext() {
+ if (canGoForward()) {
+ return getAt(fBrowsePoint.getNextIndex());
+ } else {
+ return fBrowsePoint.currentItem();
+ }
+ }
+
+ T getAt(int index) {
+ if (fSize == 0) {
+ return null;
+ }
+
+ int i = moddedIndex(index);
+ return fHistory[i];
+ }
+
+ /**
+ * This method always adds the parameter element at the current history
+ * location. If history is full (capacity == size) then an existing element is
+ * overwritten in the process. If there exists an element such that
+ * evaluator.canReplace() yields true, then that element is prioritized for
+ * being overwritten
+ *
+ * @param newItem The object instance being added to history
+ * @return The element which was evicted to make room for the incoming element.
+ * Returns null if nothing was evicted
+ */
+ public T addOrReplace(T newItem) {
+
+ T answer = null;
+ // if a replacement candidate exists, delete it since this
+ // incoming will be replacing it
+
+ /**
+ * This loop could potentially degrade to N^2 performance in case of multiple
+ * deletions, but in practice it 1) shouldnt matter for our small history sizes,
+ * and 2) will seldom delete more than 1 item for O(N) performance.
+ *
+ * If performance is ever a concern, several improvements could be made: 1)
+ * perform all deletions in a single pass, then do a single additional pass for
+ * compaction for a total of 2 passes 2) uncomment the "break" statement as
+ * explained below 3) use a different data structure altogether, eg maybe an
+ * ordered tree of some kind
+ */
+ for (int i = fInsertionPoint.getIndex(); i > fInsertionPoint.getIndex() - fSize; i--) {
+ T candidate = getAt(i);
+ if (candidate != null && fEvaluator.canReplace(newItem, candidate)) {
+ answer = deleteAt(i);
+ /**
+ * if performance is ever a concern, the below break could be uncommented so
+ * that this method only de-dupes first match instead of all matches. generally
+ * first match would be sufficient, except there can be drift over time eg if
+ * history contains [10,20,30] and vicinity threshold defined as distance 2,
+ * then you insert the following series: 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
+ * even if each insert overwrote the prior, you still could end up with dataset
+ * [20, 20, 30]. By not breaking here we prevent that state from occuring, but
+ * at some computation cost. Therefore if cost (performance) ever a problem, we
+ * could just accept that occasional suboptimal history state for better
+ * performance
+ */
+ // break;
+ }
+ }
+
+ T replaced = addLast(newItem);
+ if (answer == null) {
+ answer = replaced;
+ }
+
+ return answer;
+ }
+
+ private T addLast(T newItem) {
+ if (newItem == null) {
+ return deleteLast();
+ }
+
+ if (fSize >= fHistory.length) {
+ // no space, so just overwrite next slot
+ fInsertionPoint.incr();
+ fBrowsePoint.jumpTo(fInsertionPoint);
+ return replaceAt(newItem, fInsertionPoint);
+ } else {
+ // there's at least one empty slot so data size can grow
+ expand();
+ fInsertionPoint.incr();
+ fBrowsePoint.jumpTo(fInsertionPoint);
+ return shiftInsert(newItem);
+ }
+
+ }
+
+ // inserts here and shifts existing values until either empty space is found
+ // or end is reached, in which case last evaluated slot is discarded
+ private T shiftInsert(T newItem) {
+
+ T answer = replaceAt(newItem, fInsertionPoint);
+ T tmp = answer;
+
+ // shift from here to the end until empty slot found
+ for (int i = fInsertionPoint.getIndex() + 1; i < fSize; i++) {
+ tmp = replaceAt(tmp, i);
+ }
+
+ return answer;
+ }
+
+ // deletes and shifts to fill in the hole created by deletion
+ private T deleteAt(int index) {
+ if (fSize == 0) {
+ return null;
+ }
+ int modIndex = moddedIndex(index);
+ T answer = replaceAt(null, modIndex);
+
+ // shift to fill in any gaps to keep data contiguous so that mod
+ // calculations work
+ T priorVal = null;
+ for (int i = fSize - 1; i >= modIndex; i--) {
+ priorVal = replaceAt(priorVal, i);
+ }
+
+ if (answer != null) {
+ fSize--;
+ }
+
+ // adjust insertion point if it was affected by the shift
+ if (fInsertionPoint.getIndex() >= modIndex && fSize > 0) {
+ fInsertionPoint.decr();
+ }
+
+ // adjust browse point if it was affected by the shift
+ if (fBrowsePoint.getIndex() >= modIndex && fSize > 0) {
+ fBrowsePoint.decr();
+ }
+
+ return answer;
+ }
+
+ public T deleteLast() {
+ T answer = deleteAt(fInsertionPoint.getIndex());
+ return answer;
+ }
+
+ private T replaceAt(T newItem, int index) {
+ if (fSize == 0) {
+ return null;
+ }
+ int i = moddedIndex(index);
+
+ T replaced = getAt(i);
+ fHistory[i] = newItem;
+ return replaced;
+ }
+
+ private T replaceAt(T newItem, Navigator<T> navigator) {
+ return replaceAt(newItem, navigator.getIndex());
+ }
+
+ public T replaceLast(T newItem) {
+ if (newItem == null) {
+ return deleteLast();
+ }
+ fBrowsePoint.jumpTo(fInsertionPoint);
+ return replaceAt(newItem, fInsertionPoint);
+ }
+
+ void expand() {
+ fSize = Math.max(fSize, (fSize + 1) % (fHistory.length + 1));
+ }
+
+ private int moddedIndex(int index) {
+ return Math.floorMod(index, fSize);
+ }
+
+ public boolean isEmpty() {
+ return fInsertionPoint.currentItem() == null;
+ }
+
+ public int getSize() {
+ return fSize;
+ }
+
+ public Navigator<T> navigator() {
+ Navigator<T> answer = new Navigator<>(this);
+ answer.jumpTo(fInsertionPoint);
+ return answer;
+ }
+
+ // for internal use, not public
+ Navigator<T> navigator(int index) {
+ Navigator<T> answer = new Navigator<>(this);
+ answer.jumpTo(index);
+ return answer;
+ }
+
+ /**
+ * This method is intended for testing/troubleshooting, not for general use
+ * Beware it has O(N) performance
+ *
+ * @param item The item to check whether this history contains
+ * @return true if history contains item
+ */
+ public boolean contains(T item) {
+ if (item == null) {
+ return false;
+ }
+
+ for (int i = 0; i < fSize; i++) {
+ if (item.equals(getAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * This method is intended for testing/troubleshooting
+ *
+ * @return true if the state of this object is healthy/as expected
+ */
+ public boolean isHealthy() {
+ // make sure nulls are consolidated not scattered
+ boolean priorWasNull = false;
+ int flipCount = 0;
+ for (int i = 0; i < fHistory.length; i++) {
+ boolean isNull = (fHistory[i] == null);
+ if (priorWasNull != isNull) {
+ flipCount++;
+ priorWasNull = isNull;
+ }
+ }
+ return flipCount < 2;
+ }
+
+ /**
+ *
+ * used during history compaction to consolidate like candidates in the history
+ */
+ public static interface CandidateEvaluator<T> {
+ public boolean canReplace(T a, T b);
+ }
+
+ /**
+ * for easy traversing thru history
+ */
+ public static class Navigator<T> {
+ HistoryTracker<T> historyTracker;
+ int fIndex;
+
+ public Navigator(HistoryTracker<T> tracker) {
+ this.historyTracker = tracker;
+ fIndex = Math.floorMod(-1, tracker.fHistory.length);
+ }
+
+ void incr() {
+ if (historyTracker.fSize == 0) {
+ return;
+ }
+
+ fIndex = getNextIndex();
+ }
+
+ void decr() {
+ if (historyTracker.fSize == 0) {
+ return;
+ }
+
+ fIndex = getPriorIndex();
+ }
+
+ int getIndex() {
+ return fIndex;
+ }
+
+ int getNextIndex() {
+ return historyTracker.moddedIndex(fIndex + 1);
+ }
+
+ int getPriorIndex() {
+ return historyTracker.moddedIndex(fIndex - 1);
+ }
+
+ public T currentItem() {
+ return historyTracker.getAt(fIndex);
+ }
+
+ public T nextItem() {
+ incr();
+ return historyTracker.getAt(fIndex);
+ }
+
+ public T priorItem() {
+ decr();
+ return historyTracker.getAt(fIndex);
+ }
+
+ void jumpTo(Navigator<T> b) {
+ this.fIndex = b.fIndex;
+ }
+
+ public void jumpTo(int index) {
+ fIndex = historyTracker.moddedIndex(index);
+ }
+ }
+}
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/TextEditorPlugin.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/TextEditorPlugin.java
index 1c2663c82a0..b999befd10d 100644
--- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/TextEditorPlugin.java
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/TextEditorPlugin.java
@@ -47,13 +47,31 @@ import org.eclipse.ui.plugin.AbstractUIPlugin;
*/
public final class TextEditorPlugin extends AbstractUIPlugin implements IRegistryChangeListener {
+ /** how many edit locations will be remembered in history */
+ public static final int EDIT_LOCATION_HISTORY_SIZE = 15;
+
/** The plug-in instance */
private static TextEditorPlugin fgPlugin;
- /** The last edit position */
- private EditPosition fLastEditPosition;
+ /**
+ * tracks whether cursor has moved since fEditPositionHistory was used to
+ * return to prior location. If cursor has moved, then goto last edit
+ * location simply returns to last edit location. But if cursor has not
+ * moved, that means the command was invoked twice in a row without
+ * intervening other actions; in that case we start traversing backward thru
+ * history to prior edit locations
+ */
+ boolean movedSinceLastEditRecall = true;
+
+ // an ordered history of prior edit positions
+ private HistoryTracker<EditPosition> fEditPositionHistory = new HistoryTracker<>(
+ EDIT_LOCATION_HISTORY_SIZE, EditPosition.class,
+ (a, b) -> b.getPosition().isDeleted || b.getPosition().isDeleted
+ || EditPosition.areCoLocated(a, b),
+ false);
+
/** The action which goes to the last edit position */
- private Set<IAction> fLastEditPositionDependentActions;
+ private Set<IAction> fEditPositionDependentActions;
/**
* The quick diff extension registry.
@@ -105,29 +123,53 @@ public final class TextEditorPlugin extends AbstractUIPlugin implements IRegistr
*/
public static final String REFERENCE_PROVIDER_EXTENSION_POINT= "quickDiffReferenceProvider"; //$NON-NLS-1$
+ public boolean isMovedSinceLastEditRecall() {
+ return movedSinceLastEditRecall;
+ }
+
+ public void setMovedSinceLastEditRecall(boolean movedSinceLastEditRecall) {
+ this.movedSinceLastEditRecall = movedSinceLastEditRecall;
+ }
+
+ public HistoryTracker<EditPosition> getEditPositionHistory() {
+ return fEditPositionHistory;
+ }
+
+ public void setEditPositionHistory(HistoryTracker<EditPosition> editPositionHistory) {
+ fEditPositionHistory = editPositionHistory;
+ }
+
/**
* Returns the last edit position.
*
- * @return the last edit position or <code>null</code> if there is no last edit position
+ * @return the last edit position or <code>null</code> if there is no last
+ * edit position
* @see EditPosition
*/
public EditPosition getLastEditPosition() {
- return fLastEditPosition;
+ return fEditPositionHistory.getCurrentBrowsePoint();
}
- /**
- * Sets the last edit position.
- *
- * @param lastEditPosition the last edit position
- * @see EditPosition
- */
- public void setLastEditPosition(EditPosition lastEditPosition) {
- fLastEditPosition= lastEditPosition;
- if (fLastEditPosition != null && fLastEditPositionDependentActions != null) {
- Iterator<IAction> iter= fLastEditPositionDependentActions.iterator();
+ public EditPosition getNextEditPosition() {
+ return fEditPositionHistory.getNext();
+ }
+
+ public EditPosition backtrackEditPosition() {
+ return fEditPositionHistory.browseBackward();
+ }
+
+ public EditPosition advanceEditPosition() {
+ return fEditPositionHistory.browseForward();
+ }
+
+ public void enableLastEditPositionDependentActions() {
+ EditPosition last = fEditPositionHistory.getCurrentBrowsePoint();
+
+ if (last != null && getDependentActions() != null) {
+ Iterator<IAction> iter = getDependentActions().iterator();
while (iter.hasNext())
iter.next().setEnabled(true);
- fLastEditPositionDependentActions= null;
+ setDependentActions(null);
}
}
@@ -137,11 +179,11 @@ public final class TextEditorPlugin extends AbstractUIPlugin implements IRegistr
* @param action the goto last edit position action
*/
public void addLastEditPositionDependentAction(IAction action) {
- if (fLastEditPosition != null)
+ if (!fEditPositionHistory.isEmpty()) {
return;
- if (fLastEditPositionDependentActions == null)
- fLastEditPositionDependentActions= new HashSet<>();
- fLastEditPositionDependentActions.add(action);
+ }
+
+ addDependentAction(action);
}
/**
@@ -150,12 +192,33 @@ public final class TextEditorPlugin extends AbstractUIPlugin implements IRegistr
* @param action the action that depends on the last edit position
*/
public void removeLastEditPositionDependentAction(IAction action) {
- if (fLastEditPosition != null)
+ if (!fEditPositionHistory.isEmpty()) {
return;
- if (fLastEditPositionDependentActions != null)
- fLastEditPositionDependentActions.remove(action);
+ }
+
+ removeDependentAction(action);
}
+ private Set<IAction> getDependentActions() {
+ return fEditPositionDependentActions;
+ }
+
+ private void setDependentActions(Set<IAction> actions) {
+ fEditPositionDependentActions = actions;
+ }
+
+ public void addDependentAction(IAction action) {
+ if (getDependentActions() == null) {
+ setDependentActions(new HashSet<>());
+ }
+ getDependentActions().add(action);
+ }
+
+ public void removeDependentAction(IAction action) {
+ if (getDependentActions() != null) {
+ getDependentActions().remove(action);
+ }
+ }
@Override
public void start(BundleContext context) throws Exception {
@@ -216,4 +279,5 @@ public final class TextEditorPlugin extends AbstractUIPlugin implements IRegistr
public CodeMiningProviderRegistry getCodeMiningProviderRegistry() {
return fCodeMiningProviderRegistry;
}
+
}
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java
index 93f30cf1fac..9835d97a60c 100644
--- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java
@@ -215,6 +215,7 @@ import org.eclipse.ui.dialogs.PropertyDialogAction;
import org.eclipse.ui.dnd.IDragAndDropService;
import org.eclipse.ui.internal.texteditor.EditPosition;
import org.eclipse.ui.internal.texteditor.FocusedInformationPresenter;
+import org.eclipse.ui.internal.texteditor.HistoryTracker;
import org.eclipse.ui.internal.texteditor.NLSUtility;
import org.eclipse.ui.internal.texteditor.TextEditorPlugin;
import org.eclipse.ui.internal.texteditor.rulers.StringSetSerializer;
@@ -605,7 +606,6 @@ public abstract class AbstractTextEditor extends EditorPart implements ITextEdit
*
* @since 3.0
*/
- private Position fLocalLastEditPosition;
/** The posted updater code. */
private Runnable fRunnable = () -> {
@@ -616,35 +616,36 @@ public abstract class AbstractTextEditor extends EditorPart implements ITextEdit
// remember the last edit position
if (isDirty() && fUpdateLastEditPosition) {
+ HistoryTracker<EditPosition> positionHistory = TextEditorPlugin.getDefault()
+ .getEditPositionHistory();
fUpdateLastEditPosition = false;
ISelection sel = getSelectionProvider().getSelection();
IEditorInput input = getEditorInput();
IDocument document = getDocumentProvider().getDocument(input);
-
- if (fLocalLastEditPosition != null) {
- if (document != null) {
- document.removePosition(fLocalLastEditPosition);
- }
- fLocalLastEditPosition = null;
- }
+ IEditorSite editorSite = getEditorSite();
+ if (editorSite instanceof MultiPageEditorSite)
+ editorSite = ((MultiPageEditorSite) editorSite).getMultiPageEditor().getEditorSite();
if (sel instanceof ITextSelection && !sel.isEmpty()) {
ITextSelection s = (ITextSelection) sel;
- fLocalLastEditPosition = new Position(s.getOffset(), s.getLength());
+ Position newPosition = new Position(s.getOffset(), s.getLength());
+ EditPosition newEditPosition = new EditPosition(input, editorSite.getId(), newPosition);
+ EditPosition replaced = positionHistory.addOrReplace(newEditPosition);
if (document != null) {
+ if (replaced != null) {
+ document.removePosition(replaced.getPosition());
+ }
try {
- document.addPosition(fLocalLastEditPosition);
+ if (positionHistory.getSize() > 0) {
+ document.addPosition(positionHistory.getCurrentBrowsePoint().getPosition());
+ }
} catch (BadLocationException ex) {
- fLocalLastEditPosition = null;
+ positionHistory.deleteLast();
}
}
}
- IEditorSite editorSite = getEditorSite();
- if (editorSite instanceof MultiPageEditorSite)
- editorSite = ((MultiPageEditorSite) editorSite).getMultiPageEditor().getEditorSite();
- TextEditorPlugin.getDefault()
- .setLastEditPosition(new EditPosition(input, editorSite.getId(), fLocalLastEditPosition));
+ TextEditorPlugin.getDefault().enableLastEditPositionDependentActions();
}
}
};
@@ -681,14 +682,25 @@ public abstract class AbstractTextEditor extends EditorPart implements ITextEdit
@Override
public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {
- if (oldInput != null && fLocalLastEditPosition != null) {
- oldInput.removePosition(fLocalLastEditPosition);
- fLocalLastEditPosition= null;
+ HistoryTracker<EditPosition> positionHistory = TextEditorPlugin.getDefault().getEditPositionHistory();
+ if (oldInput != null && !positionHistory.isEmpty()) {
+ for (int i = 0; i < positionHistory.getSize(); i++)
+ oldInput.removePosition(positionHistory.browseBackward().getPosition());
}
}
@Override
public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
+ discardHistoryFor(oldInput);
+ }
+
+ private void discardHistoryFor(IDocument input) {
+ HistoryTracker<EditPosition> positionHistory = TextEditorPlugin.getDefault().getEditPositionHistory();
+ if (input != null && !positionHistory.isEmpty()) {
+ for (int i = 0; i < positionHistory.getSize(); i++)
+ input.removePosition(positionHistory.browseBackward().getPosition());
+ }
+
}
}
@@ -3073,7 +3085,7 @@ public abstract class AbstractTextEditor extends EditorPart implements ITextEdit
private Runnable fRunnable = () -> {
// check whether editor has not been disposed yet
if (fSourceViewer != null && fSourceViewer.getDocument() != null) {
- handleCursorPositionChanged();
+ handleCursorPositionChangedWrapper();
updateSelectionDependentActions();
}
};
@@ -3110,7 +3122,7 @@ public abstract class AbstractTextEditor extends EditorPart implements ITextEdit
@Override
public void keyPressed(KeyEvent e) {
- handleCursorPositionChanged();
+ handleCursorPositionChangedWrapper();
}
@Override
@@ -3127,7 +3139,7 @@ public abstract class AbstractTextEditor extends EditorPart implements ITextEdit
@Override
public void mouseUp(MouseEvent e) {
- handleCursorPositionChanged();
+ handleCursorPositionChangedWrapper();
}
};
}
@@ -6599,8 +6611,20 @@ public abstract class AbstractTextEditor extends EditorPart implements ITextEdit
}
/**
- * Handles a potential change of the cursor position.
- * Subclasses may extend.
+ * Prepares to handles a potential change of the cursor position. Wraps the
+ * actual handler which cannot itself be modified for fear of disrupting
+ * existing subclasses which may have overridden it without calling
+ * super.handleCursorPositionChanged()
+ *
+ * @since 3.15
+ */
+ private void handleCursorPositionChangedWrapper() {
+ TextEditorPlugin.getDefault().setMovedSinceLastEditRecall(true);
+ handleCursorPositionChanged();
+ }
+
+ /**
+ * Handles a potential change of the cursor position. Subclasses may extend.
*
* @since 2.0
*/
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/GotoLastEditPositionAction.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/GotoLastEditPositionAction.java
index 2df3872a8ae..effdbea938b 100644
--- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/GotoLastEditPositionAction.java
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/GotoLastEditPositionAction.java
@@ -69,50 +69,64 @@ public class GotoLastEditPositionAction extends Action implements IWorkbenchWind
@Override
public void run() {
- EditPosition editPosition= TextEditorPlugin.getDefault().getLastEditPosition();
- if (editPosition == null)
- return;
-
- final Position pos= editPosition.getPosition();
- if (pos == null || pos.isDeleted)
- return;
+ if (!TextEditorPlugin.getDefault().isMovedSinceLastEditRecall()) {
+ TextEditorPlugin.getDefault().backtrackEditPosition();
+ }
+ EditPosition editPosition = TextEditorPlugin.getDefault().getLastEditPosition();
+ try {
- IWorkbenchWindow window= getWindow();
- if (window == null)
- return;
+ if (editPosition == null) {
+ return;
+ }
- IWorkbenchPage page= window.getActivePage();
+ final Position pos = editPosition.getPosition();
+ if (pos == null || pos.isDeleted) {
+ return;
+ }
- IEditorPart editor;
- try {
- editor= page.openEditor(editPosition.getEditorInput(), editPosition.getEditorId());
- } catch (PartInitException ex) {
- IStatus status= new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID, IStatus.OK, "Go to Last Edit Location failed", ex); //$NON-NLS-1$
- TextEditorPlugin.getDefault().getLog().log(status);
- return;
- }
+ IWorkbenchWindow window = getWindow();
+ if (window == null) {
+ return;
+ }
- // Optimization - could also use else branch
- if (editor instanceof ITextEditor) {
- ITextEditor textEditor= (ITextEditor)editor;
- textEditor.selectAndReveal(pos.offset, pos.length);
- return;
- }
+ IWorkbenchPage page = window.getActivePage();
- /*
- * Workaround: send out a text selection
- * XXX: Needs to be improved, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=32214
- */
- if (editor != null) {
- IEditorSite site= editor.getEditorSite();
- if (site == null)
+ IEditorPart editor;
+ try {
+ editor = page.openEditor(editPosition.getEditorInput(), editPosition.getEditorId());
+ } catch (PartInitException ex) {
+ IStatus status = new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID, IStatus.OK,
+ "Go to Last Edit Location failed", ex); //$NON-NLS-1$
+ TextEditorPlugin.getDefault().getLog().log(status);
return;
+ }
- ISelectionProvider provider= editor.getEditorSite().getSelectionProvider();
- if (provider == null)
+ // Optimization - could also use else branch
+ if (editor instanceof ITextEditor) {
+ ITextEditor textEditor = (ITextEditor) editor;
+ textEditor.selectAndReveal(pos.offset, pos.length);
return;
-
- provider.setSelection(new TextSelection(pos.offset, pos.length));
+ }
+
+ /*
+ * Workaround: send out a text selection XXX: Needs to be improved,
+ * see https://bugs.eclipse.org/bugs/show_bug.cgi?id=32214
+ */
+ if (editor != null) {
+ IEditorSite site = editor.getEditorSite();
+ if (site == null) {
+ return;
+ }
+
+ ISelectionProvider provider = editor.getEditorSite().getSelectionProvider();
+ if (provider == null) {
+ return;
+ }
+
+ provider.setSelection(new TextSelection(pos.offset, pos.length));
+ }
+ } finally {
+ TextEditorPlugin.getDefault().setMovedSinceLastEditRecall(false);
}
}
@@ -127,7 +141,7 @@ public class GotoLastEditPositionAction extends Action implements IWorkbenchWind
// adding the same action twice has no effect.
TextEditorPlugin.getDefault().addLastEditPositionDependentAction(action);
// this is always the same action for this instance
- fAction= action;
+ fAction = action;
}
}
@@ -137,8 +151,9 @@ public class GotoLastEditPositionAction extends Action implements IWorkbenchWind
* @return the workbench window
*/
private IWorkbenchWindow getWindow() {
- if (fWindow == null)
+ if (fWindow == null) {
fWindow= PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+ }
return fWindow;
}
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/GotoNextEditPositionAction.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/GotoNextEditPositionAction.java
new file mode 100644
index 00000000000..587a0644a3b
--- /dev/null
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/GotoNextEditPositionAction.java
@@ -0,0 +1,162 @@
+/*******************************************************************************
+ * Copyright (c) 2020 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Ari Kast - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.texteditor;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionProvider;
+
+import org.eclipse.jface.text.Position;
+import org.eclipse.jface.text.TextSelection;
+
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IEditorSite;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.internal.texteditor.EditPosition;
+import org.eclipse.ui.internal.texteditor.TextEditorPlugin;
+
+
+/**
+ * Goes to next edit position, ie travels forward in the edit position history
+ * Acts as a complement to GotoLastEditPositionAction which travels backward in
+ * the history.
+ *
+ * @since 3.15
+ */
+public class GotoNextEditPositionAction extends Action implements IWorkbenchWindowActionDelegate {
+
+ /** The workbench window */
+ private IWorkbenchWindow fWindow;
+ /** The action */
+ private IAction fAction;
+
+ /**
+ * Creates a goto next edit action.
+ */
+ public GotoNextEditPositionAction() {
+ PlatformUI.getWorkbench().getHelpSystem().setHelp(this,
+ IAbstractTextEditorHelpContextIds.GOTO_NEXT_EDIT_POSITION_ACTION);
+ setId(ITextEditorActionDefinitionIds.GOTO_NEXT_EDIT_POSITION);
+ setActionDefinitionId(ITextEditorActionDefinitionIds.GOTO_NEXT_EDIT_POSITION);
+ setEnabled(false);
+ }
+
+ @Override
+ public void init(IWorkbenchWindow window) {
+ fWindow= window;
+ }
+
+ @Override
+ public void run(IAction action) {
+ run();
+ }
+
+ @Override
+ public void run() {
+ if (!TextEditorPlugin.getDefault().isMovedSinceLastEditRecall()) {
+ TextEditorPlugin.getDefault().advanceEditPosition();
+ }
+ EditPosition editPosition = TextEditorPlugin.getDefault().getNextEditPosition();
+ try {
+
+ if (editPosition == null)
+ return;
+
+ final Position pos = editPosition.getPosition();
+ if (pos == null || pos.isDeleted)
+ return;
+
+ IWorkbenchWindow window = getWindow();
+ if (window == null)
+ return;
+
+ IWorkbenchPage page = window.getActivePage();
+
+ IEditorPart editor;
+ try {
+ editor = page.openEditor(editPosition.getEditorInput(), editPosition.getEditorId());
+ } catch (PartInitException ex) {
+ IStatus status = new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID, IStatus.OK,
+ "Go to Last Edit Location failed", ex); //$NON-NLS-1$
+ TextEditorPlugin.getDefault().getLog().log(status);
+ return;
+ }
+
+ // Optimization - could also use else branch
+ if (editor instanceof ITextEditor) {
+ ITextEditor textEditor = (ITextEditor) editor;
+ textEditor.selectAndReveal(pos.offset, pos.length);
+ return;
+ }
+
+ /*
+ * Workaround: send out a text selection XXX: Needs to be improved,
+ * see https://bugs.eclipse.org/bugs/show_bug.cgi?id=32214
+ */
+ if (editor != null) {
+ IEditorSite site = editor.getEditorSite();
+ if (site == null)
+ return;
+
+ ISelectionProvider provider = editor.getEditorSite().getSelectionProvider();
+ if (provider == null)
+ return;
+
+ provider.setSelection(new TextSelection(pos.offset, pos.length));
+ }
+ } finally {
+ TextEditorPlugin.getDefault().setMovedSinceLastEditRecall(false);
+ }
+ }
+
+ @Override
+ public void selectionChanged(IAction action, ISelection selection) {
+ boolean enabled= TextEditorPlugin.getDefault().getLastEditPosition() != null;
+ setEnabled(enabled);
+ action.setEnabled(enabled);
+
+ // This is no longer needed once the action is enabled.
+ if (!enabled) {
+ // adding the same action twice has no effect.
+ TextEditorPlugin.getDefault().addLastEditPositionDependentAction(action);
+ // this is always the same action for this instance
+ fAction = action;
+ }
+ }
+
+ /**
+ * Returns the workbench window.
+ *
+ * @return the workbench window
+ */
+ private IWorkbenchWindow getWindow() {
+ if (fWindow == null)
+ fWindow= PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+ return fWindow;
+ }
+
+ @Override
+ public void dispose() {
+ fWindow= null;
+ TextEditorPlugin.getDefault().removeLastEditPositionDependentAction(fAction);
+ }
+}
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/IAbstractTextEditorHelpContextIds.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/IAbstractTextEditorHelpContextIds.java
index 22e039b4273..d05fd2b8845 100644
--- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/IAbstractTextEditorHelpContextIds.java
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/IAbstractTextEditorHelpContextIds.java
@@ -299,8 +299,17 @@ public interface IAbstractTextEditorHelpContextIds {
String GOTO_LAST_EDIT_POSITION_ACTION= PREFIX + "goto_last_edit_position" + ACTION_POSTFIX; //$NON-NLS-1$
/**
- * Help context id for the action.
- * Value: <code>"org.eclipse.ui.move_lines_action_context"</code>
+ * Help context id for the action. Value:
+ * <code>"org.eclipse.ui.goto_next_edit_position_action_context"</code>
+ *
+ * @since 3.15
+ */
+ String GOTO_NEXT_EDIT_POSITION_ACTION = PREFIX + "goto_next_edit_position" + ACTION_POSTFIX; //$NON-NLS-1$
+
+ /**
+ * Help context id for the action. Value:
+ * <code>"org.eclipse.ui.move_lines_action_context"</code>
+ *
* @since 3.0
*/
String MOVE_LINES_ACTION= PREFIX + "move_lines" + ACTION_POSTFIX; //$NON-NLS-1$
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/ITextEditorActionDefinitionIds.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/ITextEditorActionDefinitionIds.java
index ca9a78d948f..2168fa25728 100644
--- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/ITextEditorActionDefinitionIds.java
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/ITextEditorActionDefinitionIds.java
@@ -442,6 +442,14 @@ public interface ITextEditorActionDefinitionIds extends IWorkbenchActionDefiniti
String GOTO_LAST_EDIT_POSITION= "org.eclipse.ui.edit.text.gotoLastEditPosition"; //$NON-NLS-1$
/**
+ * Action definition id of go to next edit position action. Value:
+ * <code>"org.eclipse.ui.edit.text.gotoNextEditPosition"</code>
+ *
+ * @since 3.15
+ */
+ String GOTO_NEXT_EDIT_POSITION= "org.eclipse.ui.edit.text.gotoNextEditPosition"; //$NON-NLS-1$
+
+ /**
* Action definition id of go to next annotation action.
* Value: <code>"org.eclipse.ui.edit.text.gotoNextAnnotation"</code>
* @since 3.0

Back to the top