Fix for Bug 171321 [EditorMgmt] Implement per-tab editor history
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/EditorManager.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/EditorManager.java
index 812e8ba..b4da68c 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/EditorManager.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/EditorManager.java
@@ -607,7 +607,6 @@
 	 */
 	private IEditorReference findReusableEditor(EditorDescriptor desc) {
 
-		IEditorReference editors[] = page.getSortedEditors();
 		IPreferenceStore store = WorkbenchPlugin.getDefault()
 				.getPreferenceStore();
 		if (store
@@ -632,6 +631,7 @@
 			return null;
 		}
 
+		IEditorReference editors[] = page.getSortedEditors();
 		if (editors.length < page.getEditorReuseThreshold()) {
 			return null;
 		}
@@ -931,6 +931,11 @@
 				// findReusableEditor(...) checks pinned and saves editor if
 				// necessary
 				IEditorReference ref = new EditorReference(this, input, desc);
+				IPreferenceStore store = ((Workbench)page.getWorkbenchWindow().getWorkbench()).getPreferenceStore();
+		    	if (store.getBoolean(IPreferenceConstants.EDITOR_EXPERIMENTAL_TAB_BEHAVIOUR)) {
+					NavigationHistory history = (NavigationHistory) page.getNavigationHistory();
+					history.updateCookieForTab(site.getPane(), ((EditorReference)ref).getPane());
+		    	}
 				reusableEditor.getEditorSite().getPage().closeEditor(
 						reusableEditor, false);
 				return ref;
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/NavigationHistory.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/NavigationHistory.java
index f246c3e..97ee77a 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/NavigationHistory.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/NavigationHistory.java
@@ -12,8 +12,13 @@
 package org.eclipse.ui.internal;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
 
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.ui.IEditorInput;
@@ -25,6 +30,7 @@
 import org.eclipse.ui.IPartListener2;
 import org.eclipse.ui.IWorkbenchPage;
 import org.eclipse.ui.IWorkbenchPartReference;
+import org.eclipse.ui.IWorkbenchPartSite;
 
 /**
  * Implementation of the back and forward actions.
@@ -40,6 +46,8 @@
     private int ignoreEntries;
 
     private ArrayList history = new ArrayList(CAPACITY);
+    
+    Map perTabHistoryMap = new HashMap();
 
     private ArrayList editors = new ArrayList(CAPACITY);
 
@@ -73,6 +81,13 @@
             }
 
             public void partClosed(IWorkbenchPartReference partRef) {
+            	if (isPerTabHistoryEnabled() && partRef instanceof EditorReference) {
+            		if (!((EditorReference)partRef).isDisposed()) {
+	            		Object editorTabCookie = ((EditorReference)partRef).getPane();
+	            		disposeHistoryForTab(editorTabCookie);
+	            		updateActions();
+            		}
+            	}
 				updateNavigationHistory(partRef, true);
 				
 				// if there are now no more editors to activate push the active
@@ -154,6 +169,11 @@
     private Display getDisplay() {
         return page.getWorkbenchWindow().getShell().getDisplay();
     }
+    
+    private boolean isPerTabHistoryEnabled() {
+    	IPreferenceStore store = ((Workbench)page.getWorkbenchWindow().getWorkbench()).getPreferenceStore();
+    	return store.getBoolean(IPreferenceConstants.EDITOR_EXPERIMENTAL_TAB_BEHAVIOUR);
+    }
 
     /*
      * Adds an editor to the editor history without getting its location.
@@ -196,8 +216,14 @@
      * Return the backward history entries.  Return in restore order (i.e., the
      * first entry is the entry that would become active if the "Backward" action 
      * was executed).
+     * <p>
+     * (Called by NavigationHistoryAction)
+     * </p>
      */
     NavigationHistoryEntry[] getBackwardEntries() {
+    	if (isPerTabHistoryEnabled()) {
+    		return getEntriesForTab(false);
+    	}
         int length = activeEntry;
         NavigationHistoryEntry[] entries = new NavigationHistoryEntry[length];
         for (int i = 0; i < activeEntry; i++) {
@@ -210,8 +236,14 @@
      * Return the forward history entries.  Return in restore order (i.e., the first
      * entry is the entry that would become active if the "Forward" action was
      * executed).
+     * <p>
+     * (Called by NavigationHistoryAction)
+     * </p>
      */
     NavigationHistoryEntry[] getForwardEntries() {
+    	if (isPerTabHistoryEnabled()) {
+    		return getEntriesForTab(true);
+    	}
         int length = history.size() - activeEntry - 1;
         length = Math.max(0, length);
         NavigationHistoryEntry[] entries = new NavigationHistoryEntry[length];
@@ -247,6 +279,7 @@
      * Disposes this NavigationHistory and all entries.
      */
     public void dispose() {
+    	disposeHistoryForTabs();
         Iterator e = history.iterator();
         while (e.hasNext()) {
             NavigationHistoryEntry entry = (NavigationHistoryEntry) e.next();
@@ -257,8 +290,11 @@
     /**
      * Keeps a reference to the forward action to update its state
      * whenever needed.
+     * <p>
+     * (Called by NavigationHistoryAction)
+     * </p>
      */
-    public void setForwardAction(NavigationHistoryAction action) {
+    void setForwardAction(NavigationHistoryAction action) {
         forwardAction = action;
         updateActions();
     }
@@ -266,8 +302,11 @@
     /**
      * Keeps a reference to the backward action to update its state
      * whenever needed.
+     * <p>
+     * (Called by NavigationHistoryAction)
+     * </p>
      */
-    public void setBackwardAction(NavigationHistoryAction action) {
+    void setBackwardAction(NavigationHistoryAction action) {
         backwardAction = action;
         updateActions();
     }
@@ -316,6 +355,9 @@
 			return;
 		}
 
+        if (isPerTabHistoryEnabled()) {
+        	markLocationForTab(part);
+        }
         INavigationLocation location = null;
         if (part instanceof INavigationLocationProvider) {
 			location = ((INavigationLocationProvider) part)
@@ -359,15 +401,27 @@
 
     /*
      * Returns true if the forward action can be performed otherwise returns false.
+     * <p>
+     * (Called by NavigationHistoryAction)
+     * </p>
      */
     /* package */boolean canForward() {
+    	if (isPerTabHistoryEnabled()) {
+    		return hasEntriesForTab(true);
+    	}
         return (0 <= activeEntry + 1) && (activeEntry + 1 < history.size());
     }
 
     /*
      * Returns true if the backward action can be performed otherwise returns false.
+     * <p>
+     * (Called by NavigationHistoryAction)
+     * </p>
      */
     /* package */boolean canBackward() {
+    	if (isPerTabHistoryEnabled()) {
+    		return hasEntriesForTab(false);
+    	}
         return (0 <= activeEntry - 1) && (activeEntry - 1 < history.size());
     }
 
@@ -418,8 +472,15 @@
     /*
      * Perform the forward action by getting the next location and restoring
      * its context.
+     * <p>
+     * (Called by NavigationHistoryAction)
+     * </p>
      */
-    public void forward() {
+    void forward() {
+    	if (isPerTabHistoryEnabled()) {
+    		forwardForTab();
+    		return;
+    	}
         if (canForward()) {
 			shiftEntry(true);
 		}
@@ -428,8 +489,15 @@
     /*
      * Perform the backward action by getting the previous location and restoring
      * its context.
+     * <p>
+     * (Called by NavigationHistoryAction)
+     * </p>
      */
-    public void backward() {
+    void backward() {
+    	if (isPerTabHistoryEnabled()) {
+    		backwardForTab();
+    		return;
+    	}
         if (canBackward()) {
 			shiftEntry(false);
 		}
@@ -453,8 +521,15 @@
 
     /*
      * Shift the history to the given entry.
+     * <p>
+     * (Called by NavigationHistoryAction)
+     * </p>
      */
-    protected void shiftCurrentEntry(NavigationHistoryEntry entry) {
+    void shiftCurrentEntry(NavigationHistoryEntry entry, boolean forward) {
+    	if (isPerTabHistoryEnabled()) {
+    		gotoEntryForTab(entry, forward);
+    		return;
+    	}
         updateEntry(getEntry(activeEntry));
         activeEntry = history.indexOf(entry);
         gotoEntry(entry);
@@ -614,4 +689,236 @@
         }
         editors.remove(dup);
     }
+    
+    /*********************************************************/
+    /*** new per-tab history code                          ***/
+    /*********************************************************/
+    
+    
+    private static class PerTabHistory {
+    	LinkedList backwardEntries = new LinkedList();
+    	NavigationHistoryEntry currentEntry = null;
+    	LinkedList forwardEntries = new LinkedList();
+    }
+    
+    private void setNewCurrentEntryForTab(PerTabHistory perTabHistory, NavigationHistoryEntry entry) {
+    	if (perTabHistory.currentEntry != null) {
+    		perTabHistory.backwardEntries.addFirst(perTabHistory.currentEntry);
+    	}
+    	perTabHistory.currentEntry = entry;
+    	removeEntriesForTab(perTabHistory.forwardEntries);
+    }
+    
+    private Object getCookieForTab(IEditorPart part) {
+    	if (part != null) {
+	        IWorkbenchPartSite site = part.getSite();
+	        if (site instanceof PartSite) {
+	        	PartSite partSite = (PartSite) site;
+	        	WorkbenchPartReference ref = (WorkbenchPartReference) partSite.getPartReference();
+	        	if (!ref.isDisposed()) {
+	        		return partSite.getPane();
+	        	}
+	        }
+    	}
+    	return null;
+    }
+    
+    private void markLocationForTab(IEditorPart part) {
+    	if (part instanceof ErrorEditorPart) {
+    		updateActions();
+    		return;
+    	}
+		Object tabCookie = getCookieForTab(part);
+		if (tabCookie != null) {
+			INavigationLocation location = null;
+			if (part instanceof INavigationLocationProvider) {
+				location = ((INavigationLocationProvider) part)
+						.createNavigationLocation();
+			}
+			PerTabHistory perTabHistory = (PerTabHistory) perTabHistoryMap
+					.get(tabCookie);
+			if (perTabHistory == null) {
+				perTabHistory = new PerTabHistory();
+				perTabHistoryMap.put(tabCookie, perTabHistory);
+			}
+			NavigationHistoryEntry current = perTabHistory.currentEntry;
+			if (current != null && current.editorInfo.memento != null) {
+				current.editorInfo.restoreEditor();
+				checkDuplicates(current.editorInfo);
+			}
+			NavigationHistoryEntry entry = createEntry(page, part, location);
+			if (current != null && entry.mergeInto(current)) {
+				disposeEntry(entry);
+				removeEntriesForTab(perTabHistory.forwardEntries);
+			} else {
+				setNewCurrentEntryForTab(perTabHistory, entry);
+			}
+		}
+		updateActions();
+	}
+    
+    void updateCookieForTab(Object oldCookie, Object newCookie) {
+    	if (newCookie.equals(oldCookie)) {
+    		return;
+    	}
+    	PerTabHistory perTabHistory = (PerTabHistory) perTabHistoryMap.remove(oldCookie);
+    	if (perTabHistory != null) {
+    		perTabHistoryMap.put(newCookie, perTabHistory);
+    	}
+    }
+    
+    private void gotoEntryForTab(NavigationHistoryEntry target, boolean forward) {
+    	Object editorTabCookie = getCookieForTab(page.getActiveEditor());
+    	if (editorTabCookie!=null) {
+	    	PerTabHistory perTabHistory = (PerTabHistory) perTabHistoryMap.get(editorTabCookie);
+	    	if (perTabHistory != null) {
+	    		LinkedList source = forward ? perTabHistory.forwardEntries : perTabHistory.backwardEntries;
+	    		LinkedList destination = forward ? perTabHistory.backwardEntries : perTabHistory.forwardEntries;
+				if (perTabHistory.currentEntry != null) {
+					perTabHistory.currentEntry.location.update();
+					destination.addFirst(perTabHistory.currentEntry);
+				}
+				NavigationHistoryEntry newCurrent = null;
+	    		while (!source.isEmpty() && newCurrent==null) {
+		    		NavigationHistoryEntry entry = (NavigationHistoryEntry) source
+							.removeFirst();
+		    		if (entry.equals(target)) {
+		    			newCurrent = entry;
+		    		} else {
+		    			destination.addFirst(entry);
+		    		}
+	    		}
+	    		Assert.isTrue(newCurrent != null);
+	    		perTabHistory.currentEntry = newCurrent;
+	            try {
+	                ignoreEntries++;
+	                if (newCurrent.editorInfo.memento != null) {
+	                	newCurrent.editorInfo.restoreEditor();
+	                	checkDuplicates(newCurrent.editorInfo);
+	                }
+	                newCurrent.restoreLocation();
+	                updateActions();
+	            } finally {
+	            	ignoreEntries--;
+	            }
+	    	}
+    	}
+    }
+    
+	private void forwardForTab() {
+    	Object editorTabCookie = getCookieForTab(page.getActiveEditor());
+    	if (editorTabCookie!=null) {
+	    	PerTabHistory perTabHistory = (PerTabHistory) perTabHistoryMap.get(editorTabCookie);
+	    	if (perTabHistory != null && !perTabHistory.forwardEntries.isEmpty()) {
+	    		NavigationHistoryEntry newCurrent = (NavigationHistoryEntry) perTabHistory.forwardEntries
+						.removeFirst();
+	    		if (perTabHistory.currentEntry != null) {
+	    			final INavigationLocation location = perTabHistory.currentEntry.location;
+	    			if (location!=null) {
+	    				location.update();
+	    			}
+	    			perTabHistory.backwardEntries.addFirst(perTabHistory.currentEntry);
+	    		}
+	    		perTabHistory.currentEntry = newCurrent;
+	            try {
+	                ignoreEntries++;
+	                if (newCurrent.editorInfo.memento != null) {
+	                	newCurrent.editorInfo.restoreEditor();
+	                	checkDuplicates(newCurrent.editorInfo);
+	                }
+	                newCurrent.restoreLocation();
+	                updateActions();
+	            } finally {
+	            	ignoreEntries--;
+	            }
+	    	}
+    	}
+    }
+    
+    private void backwardForTab() {
+    	Object editorTabCookie = getCookieForTab(page.getActiveEditor());
+    	if (editorTabCookie!=null) {
+	    	PerTabHistory perTabHistory = (PerTabHistory) perTabHistoryMap.get(editorTabCookie);
+	    	if (perTabHistory != null && !perTabHistory.backwardEntries.isEmpty()) {
+	    		NavigationHistoryEntry newCurrent = (NavigationHistoryEntry) perTabHistory.backwardEntries
+	    				.removeFirst();
+	    		if (perTabHistory.currentEntry != null) {
+	    			perTabHistory.currentEntry.location.update();
+	    			perTabHistory.forwardEntries.addFirst(perTabHistory.currentEntry);
+	    		}
+	    		perTabHistory.currentEntry = newCurrent;
+	            try {
+	                ignoreEntries++;
+	                if (newCurrent.editorInfo.memento != null) {
+	                	newCurrent.editorInfo.restoreEditor();
+	                	checkDuplicates(newCurrent.editorInfo);
+	                }
+	                newCurrent.restoreLocation();
+	                updateActions();
+	            } finally {
+	            	ignoreEntries--;
+	            }
+	    	}
+    	}
+    }
+    
+    private boolean hasEntriesForTab(boolean forward) {
+    	Object editorTabCookie = getCookieForTab(page.getActiveEditor());
+    	if (editorTabCookie!=null) {
+	    	PerTabHistory perTabHistory = (PerTabHistory) perTabHistoryMap.get(editorTabCookie);
+	    	if (perTabHistory != null) {
+		    	LinkedList entries = forward ? perTabHistory.forwardEntries : perTabHistory.backwardEntries;
+		    	return !entries.isEmpty();
+	    	}
+    	}
+    	return false;
+    }
+
+    /**
+     * Returns entries in restore order.
+     * @param editorTabCookie
+     * @param forward
+     * @return
+     */
+    private NavigationHistoryEntry[] getEntriesForTab(boolean forward) {
+		Object editorTabCookie = getCookieForTab(page.getActiveEditor());
+		if (editorTabCookie != null) {
+			PerTabHistory perTabHistory = (PerTabHistory) perTabHistoryMap
+					.get(editorTabCookie);
+			if (perTabHistory != null) {
+				LinkedList entries = forward ? perTabHistory.forwardEntries
+						: perTabHistory.backwardEntries;
+				return (NavigationHistoryEntry[]) entries
+				.toArray(new NavigationHistoryEntry[entries.size()]);
+			}
+		}
+		return new NavigationHistoryEntry[0];
+	}
+    
+    private void disposeHistoryForTabs() {
+    	Object[] keys = perTabHistoryMap.keySet().toArray();
+    	for (int i = 0; i < keys.length; i++) {
+			disposeHistoryForTab(keys[i]);
+		}
+    }
+
+    void disposeHistoryForTab(Object editorTabCookie) {
+    	PerTabHistory perTabHistory = (PerTabHistory) perTabHistoryMap.remove(editorTabCookie);
+    	if (perTabHistory != null) {
+    		if (perTabHistory.currentEntry != null) {
+    			disposeEntry(perTabHistory.currentEntry);
+    			perTabHistory.currentEntry = null;
+    		}
+    		removeEntriesForTab(perTabHistory.backwardEntries);
+    		removeEntriesForTab(perTabHistory.forwardEntries);
+    	}
+    }
+
+	private void removeEntriesForTab(LinkedList entries) {
+		for (Iterator it = entries.iterator(); it.hasNext();) {
+			NavigationHistoryEntry entry = (NavigationHistoryEntry) it.next();
+			disposeEntry(entry);
+			it.remove();
+		}
+	}
 }
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/NavigationHistoryAction.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/NavigationHistoryAction.java
index a48e009..31d33cf 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/NavigationHistoryAction.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/NavigationHistoryAction.java
@@ -90,7 +90,7 @@
                         public void widgetSelected(SelectionEvent e) {
                             history
                                     .shiftCurrentEntry((NavigationHistoryEntry) e.widget
-                                            .getData());
+                                            .getData(), forward);
                         }
                     });
                 }
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPage.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPage.java
index 4d168f8..3475e5c 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPage.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPage.java
@@ -2445,6 +2445,7 @@
         } else {
             editor.setInput(input);
         }
+        navigationHistory.markLocation(editor);
     }
 
     /**
@@ -2532,7 +2533,9 @@
 		IEditorPart editor = null;
 		IPreferenceStore store = WorkbenchPlugin.getDefault().getPreferenceStore();
 		// Reuse an existing open editor, unless we are in "new editor tab management" mode
-		if (!store.getBoolean(IPreferenceConstants.EDITOR_EXPERIMENTAL_TAB_BEHAVIOUR)) {
+		if (store.getBoolean(IPreferenceConstants.EDITOR_EXPERIMENTAL_TAB_BEHAVIOUR)) {
+			editor = getEditorManager().findEditor(editorID, input, IWorkbenchPage.MATCH_ID | IWorkbenchPage.MATCH_INPUT);
+		} else {
 			editor = getEditorManager().findEditor(editorID, input, matchFlags);
 		}
         if (editor != null) {