From: khartlage <khartlage>
Date: Mon, 9 Dec 2002 18:23:11 +0000 (+0000)
Subject: fixed bug in template expansion
X-Git-Url: http://git.phpeclipse.com

fixed bug in template expansion
---

diff --git a/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/link/LinkedPositionListener.java b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/link/LinkedPositionListener.java
new file mode 100644
index 0000000..94028c9
--- /dev/null
+++ b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/link/LinkedPositionListener.java
@@ -0,0 +1,31 @@
+/*
+ * (c) Copyright IBM Corp. 2000, 2001.
+ * All Rights Reserved.
+ */
+package net.sourceforge.phpdt.internal.ui.text.link;
+
+import org.eclipse.jface.text.IDocumentExtension;
+import org.eclipse.jface.text.Position;
+
+/**
+ * A listener for highlight change notification and exititing linked mode.
+ */
+public interface LinkedPositionListener {
+	
+	/**
+	 * Notifies that the linked mode has been left. On success, all changes
+	 * are kept, otherwise all changes made to the linked positions are restored
+	 * to the state before entering linked mode.
+	 */
+	void exit(boolean success);
+	
+	/**
+	 * Notifies the changed linked position. The listener is asked
+	 * to reposition the caret at the given offset.
+	 * 
+	 * @param position    the linked position which initiated the change.
+	 * @param caretOffset the caret offset relative to the position.
+	 */
+	void setCurrentPosition(Position position, int caretOffset);
+
+}
diff --git a/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/link/LinkedPositionManager.java b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/link/LinkedPositionManager.java
new file mode 100644
index 0000000..4ce53ee
--- /dev/null
+++ b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/link/LinkedPositionManager.java
@@ -0,0 +1,448 @@
+/*
+ * (c) Copyright IBM Corp. 2000, 2001.
+ * All Rights Reserved.
+ */
+package net.sourceforge.phpdt.internal.ui.text.link;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sourceforge.phpeclipse.PHPeclipsePlugin;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.BadPositionCategoryException;
+import org.eclipse.jface.text.DocumentEvent;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IDocumentExtension;
+import org.eclipse.jface.text.IDocumentListener;
+import org.eclipse.jface.text.IPositionUpdater;
+import org.eclipse.jface.text.Position;
+import org.eclipse.jface.text.TypedPosition;
+import org.eclipse.jface.util.Assert;
+
+//import org.eclipse.jdt.internal.ui.JavaPlugin;
+
+/**
+ * This class manages linked positions in a document. Positions are linked
+ * by type names. If positions have the same type name, they are considered
+ * as <em>linked</em>.
+ * 
+ * The manager remains active on a document until any of the following actions
+ * occurs:
+ * 
+ * <ul>
+ *   <li>A document change is performed which would invalidate any of the
+ *       above constraints.</li>
+ * 
+ *   <li>The method <code>uninstall()</code> is called.</li>
+ * 
+ *   <li>Another instance of <code>LinkedPositionManager</code> tries to
+ *       gain control of the same document.
+ * </ul>
+ */
+public class LinkedPositionManager implements IDocumentListener, IPositionUpdater {
+	
+	private static class PositionComparator implements Comparator {
+		/*
+		 * @see Comparator#compare(Object, Object)
+		 */
+		public int compare(Object object0, Object object1) {
+			Position position0= (Position) object0;
+			Position position1= (Position) object1;
+			
+			return position0.getOffset() - position1.getOffset();
+		}
+	}
+	
+	private class Replace implements IDocumentExtension.IReplace {
+		
+		private Position fReplacePosition;
+		private int fReplaceDeltaOffset;
+		private int fReplaceLength;
+		private String fReplaceText;
+		
+		public Replace(Position position, int deltaOffset, int length, String text) {
+			fReplacePosition= position;
+			fReplaceDeltaOffset= deltaOffset;
+			fReplaceLength= length;
+			fReplaceText= text;
+		}
+				
+		public void perform(IDocument document, IDocumentListener owner) {
+			document.removeDocumentListener(owner);
+			try {
+				document.replace(fReplacePosition.getOffset() + fReplaceDeltaOffset, fReplaceLength, fReplaceText);
+			} catch (BadLocationException e) {
+				PHPeclipsePlugin.log(e);
+				// TBD
+			}
+			document.addDocumentListener(owner);
+		}
+	}
+
+	private static final String LINKED_POSITION= "LinkedPositionManager.linked.position"; //$NON-NLS-1$
+	private static final Comparator fgPositionComparator= new PositionComparator();
+	private static final Map fgActiveManagers= new HashMap();
+		
+	private IDocument fDocument;
+	
+	private LinkedPositionListener fListener;
+
+	/**
+	 * Creates a <code>LinkedPositionManager</code> for a <code>IDocument</code>.
+	 * 
+	 * @param document the document to use with linked positions.
+	 */
+	public LinkedPositionManager(IDocument document) {
+		Assert.isNotNull(document);
+		
+		fDocument= document;		
+		install();
+	}
+
+	/**
+	 * Sets a listener to notify changes of current linked position.
+	 */
+	public void setLinkedPositionListener(LinkedPositionListener listener) {
+		fListener= listener;	
+	}
+	
+	/**
+	 * Adds a linked position to the manager.
+	 * There are the following constraints for linked positions:
+	 * 
+	 * <ul>
+	 *   <li>Any two positions have spacing of at least one character.
+	 *       This implies that two positions must not overlap.</li>
+	 *
+	 *   <li>The string at any position must not contain line delimiters.</li>
+	 * </ul>
+	 * 
+	 * @param offset the offset of the position.
+	 * @param length the length of the position.
+	 */
+	public void addPosition(int offset, int length) throws BadLocationException {
+		Position[] positions= getPositions(fDocument);
+
+		if (positions != null) {
+			for (int i = 0; i < positions.length; i++)
+				if (collides(positions[i], offset, length))
+					throw new BadLocationException(LinkedPositionMessages.getString(("LinkedPositionManager.error.position.collision"))); //$NON-NLS-1$
+		}
+		
+		String type= fDocument.get(offset, length);		
+
+		if (containsLineDelimiters(type))
+			throw new BadLocationException(LinkedPositionMessages.getString(("LinkedPositionManager.error.contains.line.delimiters"))); //$NON-NLS-1$
+
+		try {
+			fDocument.addPosition(LINKED_POSITION, new TypedPosition(offset, length, type));
+		} catch (BadPositionCategoryException e) {
+			PHPeclipsePlugin.log(e);
+			Assert.isTrue(false);
+		}
+	}
+
+	/**
+	 * Tests if a manager is already active for a document.
+	 */
+	public static boolean hasActiveManager(IDocument document) {
+		return fgActiveManagers.get(document) != null;
+	}
+
+	private void install() {
+		LinkedPositionManager manager= (LinkedPositionManager) fgActiveManagers.get(fDocument);
+		if (manager != null)
+			manager.leave(true);		
+
+		fgActiveManagers.put(fDocument, this);
+		
+		fDocument.addPositionCategory(LINKED_POSITION);
+		fDocument.addPositionUpdater(this);		
+		fDocument.addDocumentListener(this);
+	}	
+	
+	/**
+	 * Leaves the linked mode. If unsuccessful, the linked positions
+	 * are restored to the values at the time they were added.
+	 */
+	public void uninstall(boolean success) {			
+		fDocument.removeDocumentListener(this);
+
+		try {
+			Position[] positions= getPositions(fDocument);	
+			if ((!success) && (positions != null)) {
+				// restore
+				for (int i= 0; i != positions.length; i++) {
+					TypedPosition position= (TypedPosition) positions[i];				
+					fDocument.replace(position.getOffset(), position.getLength(), position.getType());
+				}
+			}		
+			
+			fDocument.removePositionCategory(LINKED_POSITION);
+
+		} catch (BadLocationException e) {
+			PHPeclipsePlugin.log(e);
+			Assert.isTrue(false);
+
+		} catch (BadPositionCategoryException e) {
+			PHPeclipsePlugin.log(e);
+			Assert.isTrue(false);
+
+		} finally {
+			fDocument.removePositionUpdater(this);		
+			fgActiveManagers.remove(fDocument);		
+		}
+	}
+
+	/**
+	 * Returns the first linked position.
+	 * 
+	 * @return returns <code>null</code> if no linked position exist.
+	 */
+	public Position getFirstPosition() {
+		return getNextPosition(-1);
+	}
+
+	/**
+	 * Returns the next linked position with an offset greater than <code>offset</code>.
+	 * If another position with the same type and offset lower than <code>offset</code>
+	 * exists, the position is skipped.
+	 * 
+	 * @return returns <code>null</code> if no linked position exist.
+	 */
+	public Position getNextPosition(int offset) {
+		Position[] positions= getPositions(fDocument);
+		return findNextPosition(positions, offset);
+	}
+
+	private static Position findNextPosition(Position[] positions, int offset) {
+		// skip already visited types
+		for (int i= 0; i != positions.length; i++) {			
+			if (positions[i].getOffset() > offset) {
+				String type= ((TypedPosition) positions[i]).getType();
+				int j;
+				for (j = 0; j != i; j++)
+					if (((TypedPosition) positions[j]).getType().equals(type))
+						break;
+
+				if (j == i)
+					return positions[i];				
+			}
+		}
+
+		return null;
+	}
+	
+	/**
+	 * Returns the position with the greatest offset smaller than <code>offset</code>.
+	 *
+	 * @return returns <code>null</code> if no linked position exist.
+	 */
+	public Position getPreviousPosition(int offset) {
+		Position[] positions= getPositions(fDocument);		
+		if (positions == null)
+			return null;
+
+		Position lastPosition= null;
+		Position position= getFirstPosition();
+		
+		while ((position != null) && (position.getOffset() < offset)) {
+			lastPosition= position;
+			position= findNextPosition(positions, position.getOffset());
+		}		
+		
+		return lastPosition;
+	}
+
+	private static Position[] getPositions(IDocument document) {
+		try {
+			Position[] positions= document.getPositions(LINKED_POSITION);
+			Arrays.sort(positions, fgPositionComparator);
+			return positions;
+
+		} catch (BadPositionCategoryException e) {
+			PHPeclipsePlugin.log(e);
+			Assert.isTrue(false);
+		}
+		
+		return null;
+	}	
+
+	public static boolean includes(Position position, int offset, int length) {
+		return
+			(offset >= position.getOffset()) &&
+			(offset + length <= position.getOffset() + position.getLength());
+	}
+
+	public static boolean excludes(Position position, int offset, int length) {
+		return
+			(offset + length <= position.getOffset()) ||
+			(position.getOffset() + position.getLength() <= offset);
+	}
+
+	/*
+	 * Collides if spacing if positions intersect each other or are adjacent.
+	 */
+	private static boolean collides(Position position, int offset, int length) {
+		return
+			(offset <= position.getOffset() + position.getLength()) &&
+			(position.getOffset() <= offset + length);	
+	}
+	
+	private void leave(boolean success) {
+		uninstall(success);
+
+		if (fListener != null)
+			fListener.exit(success);		
+	}
+
+	/*
+	 * @see IDocumentListener#documentAboutToBeChanged(DocumentEvent)
+	 */
+	public void documentAboutToBeChanged(DocumentEvent event) {
+		IDocument document= event.getDocument();
+
+		Position[] positions= getPositions(document);
+		Position position= findCurrentEditablePosition(positions, event.getOffset());
+
+		// modification outside editable position
+		if (position == null) {
+			position= findCurrentPosition(positions, event.getOffset());
+
+			// modification outside any position			
+			if (position == null) {
+				// check for destruction of constraints (spacing of at least 1)
+				if ((event.getText().length() == 0) &&
+					(findCurrentPosition(positions, event.getOffset()) != null) &&
+					(findCurrentPosition(positions, event.getOffset() + event.getLength()) != null))
+				{
+					leave(true);
+				}
+				
+			// modification intersects non-editable position
+			} else {
+				leave(true);
+			}
+
+		// modification intersects editable position
+		} else {
+			// modificaction inside editable position
+			if (includes(position, event.getOffset(), event.getLength())) {
+				if (containsLineDelimiters(event.getText()))
+					leave(true);
+
+			// modificaction exceeds editable position
+			} else {
+				leave(true);
+			}
+		}
+	}
+
+	/*
+	 * @see IDocumentListener#documentChanged(DocumentEvent)
+	 */
+	public void documentChanged(DocumentEvent event) {		
+		IDocument document= event.getDocument();
+
+		Position[] positions= getPositions(document);
+		TypedPosition currentPosition= (TypedPosition) findCurrentEditablePosition(positions, event.getOffset());
+
+		// ignore document changes (assume it won't invalidate constraints)
+		if (currentPosition == null)
+			return;
+		
+		int deltaOffset= event.getOffset() - currentPosition.getOffset();		
+
+		if (fListener != null)
+			fListener.setCurrentPosition(currentPosition, deltaOffset + event.getText().length());
+		
+		for (int i= 0; i != positions.length; i++) {
+			TypedPosition p= (TypedPosition) positions[i];			
+			
+			if (p.getType().equals(currentPosition.getType()) && !p.equals(currentPosition)) {
+				Replace replace= new Replace(p, deltaOffset, event.getLength(), event.getText());
+				((IDocumentExtension) document).registerPostNotificationReplace(this, replace);
+			}
+		}
+	}
+	
+	/*
+	 * @see IPositionUpdater#update(DocumentEvent)
+	 */
+	public void update(DocumentEvent event) {
+		int deltaLength= event.getText().length() - event.getLength();
+
+		Position[] positions= getPositions(event.getDocument());
+		TypedPosition currentPosition= (TypedPosition) findCurrentPosition(positions, event.getOffset());
+
+		// document change outside positions
+		if (currentPosition == null) {
+			
+			for (int i= 0; i != positions.length; i++) {
+				TypedPosition position= (TypedPosition) positions[i];
+				int offset= position.getOffset();
+				
+				if (offset >= event.getOffset())
+					position.setOffset(offset + deltaLength);
+			}
+			
+		// document change within a position
+		} else {
+			int length= currentPosition.getLength();
+	
+			for (int i= 0; i != positions.length; i++) {
+				TypedPosition position= (TypedPosition) positions[i];
+				int offset= position.getOffset();
+				
+				if (position.equals(currentPosition)) {
+					position.setLength(length + deltaLength);					
+				} else if (offset > currentPosition.getOffset()) {
+					position.setOffset(offset + deltaLength);
+				}
+			}		
+		}
+	}
+
+	private static Position findCurrentPosition(Position[] positions, int offset) {
+		for (int i= 0; i != positions.length; i++)
+			if (includes(positions[i], offset, 0))
+				return positions[i];
+		
+		return null;			
+	}
+
+	private static Position findCurrentEditablePosition(Position[] positions, int offset) {
+		Position position= positions[0];
+
+		while ((position != null) && !includes(position, offset, 0))
+			position= findNextPosition(positions, position.getOffset());
+
+		return position;
+	}
+
+	private boolean containsLineDelimiters(String string) {
+		String[] delimiters= fDocument.getLegalLineDelimiters();
+
+		for (int i= 0; i != delimiters.length; i++)
+			if (string.indexOf(delimiters[i]) != -1)
+				return true;
+
+		return false;
+	}
+	
+	/**
+	 * Test if ok to modify through UI.
+	 */
+	public boolean anyPositionIncludes(int offset, int length) {
+		Position[] positions= getPositions(fDocument);
+
+		Position position= findCurrentEditablePosition(positions, offset);
+		if (position == null)
+			return false;
+		
+		return includes(position, offset, length);
+	}
+	
+}
diff --git a/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/link/LinkedPositionMessages.java b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/link/LinkedPositionMessages.java
new file mode 100644
index 0000000..4a6b8fb
--- /dev/null
+++ b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/link/LinkedPositionMessages.java
@@ -0,0 +1,43 @@
+/*
+ * (c) Copyright IBM Corp. 2000, 2001.
+ * All Rights Reserved.
+ */
+package net.sourceforge.phpdt.internal.ui.text.link;
+
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+public class LinkedPositionMessages {
+
+	private static final String RESOURCE_BUNDLE= LinkedPositionMessages.class.getName();
+	private static ResourceBundle fgResourceBundle= ResourceBundle.getBundle(RESOURCE_BUNDLE);
+
+	private LinkedPositionMessages() {
+	}
+
+	public static String getString(String key) {
+		try {
+			return fgResourceBundle.getString(key);
+		} catch (MissingResourceException e) {
+			return '!' + key + '!';
+		}
+	}
+	
+	/**
+	 * Gets a string from the resource bundle and formats it with the argument
+	 * 
+	 * @param key	the string used to get the bundle value, must not be null
+	 */
+	public static String getFormattedString(String key, Object arg) {
+		return MessageFormat.format(getString(key), new Object[] { arg });
+	}
+
+
+	/**
+	 * Gets a string from the resource bundle and formats it with arguments
+	 */	
+	public static String getFormattedString(String key, Object[] args) {
+		return MessageFormat.format(getString(key), args);
+	}
+}
diff --git a/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/link/LinkedPositionMessages.properties b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/link/LinkedPositionMessages.properties
new file mode 100644
index 0000000..267939d
--- /dev/null
+++ b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/link/LinkedPositionMessages.properties
@@ -0,0 +1,9 @@
+#########################################
+# (c) Copyright IBM Corp. 2000, 2001.
+# All Rights Reserved.
+#########################################
+
+LinkedPositionUI.error.title=Error in LinkedPositionError
+
+LinkedPositionManager.error.contains.line.delimiters=String contains line delimiters.
+LinkedPositionManager.error.position.collision=Linked position collides with another linked position.
diff --git a/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/link/LinkedPositionUI.java b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/link/LinkedPositionUI.java
new file mode 100644
index 0000000..a9389ea
--- /dev/null
+++ b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/link/LinkedPositionUI.java
@@ -0,0 +1,526 @@
+/*
+ * (c) Copyright IBM Corp. 2000, 2001.
+ * All Rights Reserved.
+ */
+package net.sourceforge.phpdt.internal.ui.text.link;
+
+import java.lang.reflect.InvocationTargetException;
+
+import net.sourceforge.phpeclipse.PHPeclipsePlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.PreferenceConverter;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.BadPositionCategoryException;
+import org.eclipse.jface.text.DefaultPositionUpdater;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IPositionUpdater;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextInputListener;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.ITextViewerExtension;
+import org.eclipse.jface.text.Position;
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.util.Assert;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.custom.VerifyKeyListener;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.events.VerifyEvent;
+import org.eclipse.swt.events.VerifyListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * A user interface for <code>LinkedPositionManager</code>, using <code>ITextViewer</code>.
+ */
+public class LinkedPositionUI implements LinkedPositionListener,
+	ITextInputListener, ModifyListener, VerifyListener, VerifyKeyListener, PaintListener, IPropertyChangeListener {
+
+	/**
+	 * A listener for notification when the user cancelled the edit operation.
+	 */
+	public interface ExitListener {
+		void exit(boolean accept);
+	}
+	
+  /** Preference key for linked position color */
+ // public final static String LINKED_POSITION_COLOR= "_linkedPositionColor"; //$NON-NLS-1$
+	// leave flags
+	private static final int UNINSTALL= 1;			// uninstall linked position manager
+	private static final int COMMIT= 2;				// commit changes
+	private static final int DOCUMENT_CHANGED= 4;	// document has changed
+	private static final int UPDATE_CARET= 8;		// update caret
+
+	private static final String CARET_POSITION= "LinkedPositionUI.caret.position"; //$NON-NLS-1$
+	private static final IPositionUpdater fgUpdater= new DefaultPositionUpdater(CARET_POSITION);
+	private static final IPreferenceStore fgStore= PHPeclipsePlugin.getDefault().getPreferenceStore();
+	
+	private final ITextViewer fViewer;
+	private final LinkedPositionManager fManager;	
+	private Color fFrameColor;
+
+	private int fFinalCaretOffset= -1; // no final caret offset
+
+	private Position fFramePosition;
+	private int fCaretOffset;
+	
+	private ExitListener fExitListener;
+	
+	/**
+	 * Creates a user interface for <code>LinkedPositionManager</code>.
+	 * 
+	 * @param viewer  the text viewer.
+	 * @param manager the <code>LinkedPositionManager</code> managing a <code>IDocument</code> of the <code>ITextViewer</code>.
+	 */
+	public LinkedPositionUI(ITextViewer viewer, LinkedPositionManager manager) {
+		Assert.isNotNull(viewer);
+		Assert.isNotNull(manager);
+		
+		fViewer= viewer;
+		fManager= manager;
+		
+		fManager.setLinkedPositionListener(this);
+
+		initializeHighlightColor(viewer);
+	}
+
+	/*
+	 * @see IPropertyChangeListener#propertyChange(PropertyChangeEvent)
+	 */
+	public void propertyChange(PropertyChangeEvent event) {
+	//	if (event.getProperty().equals(CompilationUnitEditor.LINKED_POSITION_COLOR)) {
+    if (event.getProperty().equals(PHPeclipsePlugin.LINKED_POSITION_COLOR)) {
+			initializeHighlightColor(fViewer);
+			redrawRegion();
+		}
+	}
+
+	private void initializeHighlightColor(ITextViewer viewer) {
+
+		if (fFrameColor != null)
+			fFrameColor.dispose();
+
+		StyledText text= viewer.getTextWidget();
+		if (text != null) {
+			Display display= text.getDisplay();
+	//		fFrameColor= createColor(fgStore, CompilationUnitEditor.LINKED_POSITION_COLOR, display);
+		  fFrameColor= createColor(fgStore, PHPeclipsePlugin.LINKED_POSITION_COLOR, display);
+    }
+	}
+
+	/**
+	 * Creates a color from the information stored in the given preference store.
+	 * Returns <code>null</code> if there is no such information available.
+	 */
+	private Color createColor(IPreferenceStore store, String key, Display display) {
+	
+		RGB rgb= null;		
+		
+		if (store.contains(key)) {
+			
+			if (store.isDefault(key))
+				rgb= PreferenceConverter.getDefaultColor(store, key);
+			else
+				rgb= PreferenceConverter.getColor(store, key);
+		
+			if (rgb != null)
+				return new Color(display, rgb);
+		}
+		
+		return null;
+	}
+	
+	/**
+	 * Sets the final position of the caret when the linked mode is exited
+	 * successfully by leaving the last linked position using TAB.
+	 */
+	public void setFinalCaretOffset(int offset) {
+		fFinalCaretOffset= offset;	
+	}
+
+	/**
+	 * Sets a <code>CancelListener</code> which is notified if the linked mode
+	 * is exited unsuccessfully by hitting ESC.
+	 */
+	public void setCancelListener(ExitListener listener) {
+		fExitListener= listener;
+	}
+
+	/*
+	 * @see LinkedPositionManager.LinkedPositionListener#setCurrentPositions(Position, int)
+	 */
+	public void setCurrentPosition(Position position, int caretOffset) {
+		if (!fFramePosition.equals(position)) {
+			redrawRegion();
+			fFramePosition= position;
+		}
+
+		fCaretOffset= caretOffset;
+	}
+
+	/**
+	 * Enters the linked mode. The linked mode can be left by calling
+	 * <code>exit</code>.
+	 * 
+	 * @see #exit(boolean)
+	 */
+	public void enter() {
+		// track final caret
+		IDocument document= fViewer.getDocument();
+		document.addPositionCategory(CARET_POSITION);
+		document.addPositionUpdater(fgUpdater);
+		try {
+			if (fFinalCaretOffset != -1)
+				document.addPosition(CARET_POSITION, new Position(fFinalCaretOffset));
+		} catch (BadLocationException e) {
+			handleException(fViewer.getTextWidget().getShell(), e);
+
+		} catch (BadPositionCategoryException e) {
+			PHPeclipsePlugin.log(e);
+			Assert.isTrue(false);
+		}
+
+		fViewer.addTextInputListener(this);
+				
+		ITextViewerExtension extension= (ITextViewerExtension) fViewer;
+		extension.prependVerifyKeyListener(this);
+
+		StyledText text= fViewer.getTextWidget();			
+		text.addVerifyListener(this);
+		text.addModifyListener(this);
+		text.addPaintListener(this);
+		text.showSelection();
+
+		fFramePosition= fManager.getFirstPosition();
+		if (fFramePosition == null)
+			leave(UNINSTALL | COMMIT | UPDATE_CARET);
+
+		fgStore.addPropertyChangeListener(this);
+	}
+
+	/*
+	 * @see LinkedPositionManager.LinkedPositionListener#exit(boolean)
+	 */
+	public void exit(boolean success) {
+		// no UNINSTALL since manager has already uninstalled itself
+		leave((success ? COMMIT : 0) | UPDATE_CARET);
+	}
+
+	/**
+	 * Returns the cursor selection, after having entered the linked mode.
+	 * <code>enter()</code> must be called prior to a call to this method.
+	 */
+	public IRegion getSelectedRegion() {
+		if (fFramePosition == null)
+			return new Region(fFinalCaretOffset, 0);
+		else
+			return new Region(fFramePosition.getOffset(), fFramePosition.getLength());
+	}
+	
+	private void leave(int flags) {
+		if ((flags & UNINSTALL) != 0)
+			fManager.uninstall((flags & COMMIT) != 0);
+
+		fgStore.removePropertyChangeListener(this);
+		
+		if (fFrameColor != null) {
+			fFrameColor.dispose();
+			fFrameColor= null;
+		}			
+		
+		StyledText text= fViewer.getTextWidget();	
+		text.removePaintListener(this);
+		text.removeModifyListener(this);
+		text.removeVerifyListener(this);
+
+		ITextViewerExtension extension= (ITextViewerExtension) fViewer;
+		extension.removeVerifyKeyListener(this);
+
+		fViewer.removeTextInputListener(this);
+		
+		try {
+			IRegion region= fViewer.getVisibleRegion();
+			IDocument document= fViewer.getDocument();
+
+			if (((flags & COMMIT) != 0) &&
+				((flags & DOCUMENT_CHANGED) == 0) &&
+				((flags & UPDATE_CARET) != 0))
+			{
+				Position[] positions= document.getPositions(CARET_POSITION);
+
+				if ((positions != null) && (positions.length != 0)) {
+					int offset= positions[0].getOffset() - region.getOffset();		
+					if ((offset >= 0) && (offset <= region.getLength()))
+						text.setSelection(offset, offset);
+				}
+			}
+
+			document.removePositionUpdater(fgUpdater);
+			document.removePositionCategory(CARET_POSITION);
+			
+			if (fExitListener != null)
+				fExitListener.exit(
+					((flags & COMMIT) != 0) ||
+					((flags & DOCUMENT_CHANGED) != 0));
+
+		} catch (BadPositionCategoryException e) {
+			PHPeclipsePlugin.log(e);
+			Assert.isTrue(false);
+		}
+
+		if ((flags & DOCUMENT_CHANGED) == 0)
+			text.redraw();
+	}
+
+	private void next() {
+		redrawRegion();
+		
+		fFramePosition= fManager.getNextPosition(fFramePosition.getOffset());
+		if (fFramePosition == null) {
+			leave(UNINSTALL | COMMIT | UPDATE_CARET);
+		} else {
+			selectRegion();
+			redrawRegion();
+		}
+	}
+	
+	private void previous() {
+		redrawRegion();
+		
+		Position position= fManager.getPreviousPosition(fFramePosition.getOffset());
+		if (position == null) {
+			fViewer.getTextWidget().getDisplay().beep();
+		} else {
+			fFramePosition= position;
+			selectRegion();
+			redrawRegion();
+		}				
+	}
+
+	/*
+	 * @see VerifyKeyListener#verifyKey(VerifyEvent)
+	 */
+	public void verifyKey(VerifyEvent event) {
+		switch (event.character) {
+		// [SHIFT-]TAB = hop between edit boxes
+		case 0x09:
+			{
+				Point selection= fViewer.getTextWidget().getSelection();
+				IRegion region= fViewer.getVisibleRegion();
+				int offset= selection.x + region.getOffset();
+				int length= selection.y - selection.x;
+				
+				// if tab was treated as a document change, would it exceed variable range?
+				if (!LinkedPositionManager.includes(fFramePosition, offset, length)) {
+					leave(UNINSTALL | COMMIT | UPDATE_CARET);
+					return;
+				}
+			}
+		
+			if (event.stateMask == SWT.SHIFT)
+				previous();
+			else 
+				next();			
+			
+			event.doit= false;
+			break;
+
+		// ENTER
+		case 0x0D:
+			leave(UNINSTALL | COMMIT | UPDATE_CARET);
+			event.doit= false;
+			break;
+
+		// ESC
+		case 0x1B:
+			leave(UNINSTALL | COMMIT);
+			event.doit= false;
+			break;
+		}
+	}
+
+	/*
+	 * @see VerifyListener#verifyText(VerifyEvent)
+	 */
+	public void verifyText(VerifyEvent event) {
+		if (!event.doit)
+			return;
+
+		IRegion region= fViewer.getVisibleRegion();
+
+		int offset= event.start + region.getOffset();
+		int length= event.end - event.start;
+
+		// allow changes only within linked positions when coming through UI
+		if (!fManager.anyPositionIncludes(offset, length))
+			leave(UNINSTALL | COMMIT);
+	}
+
+	/*
+	 * @see PaintListener#paintControl(PaintEvent)
+	 */
+	public void paintControl(PaintEvent event) {	
+		if (fFramePosition == null)
+			return;
+
+		IRegion region= fViewer.getVisibleRegion();
+		
+		// #6824
+		if (!includes(region, fFramePosition)) {
+		 	leave(UNINSTALL | COMMIT | DOCUMENT_CHANGED);
+		 	return;		    
+		}
+		
+		int offset= fFramePosition.getOffset() -  region.getOffset();
+		int length= fFramePosition.getLength();
+			
+		StyledText text= fViewer.getTextWidget();
+		
+		// support for bidi
+		Point minLocation= getMinimumLocation(text, offset, length);
+		Point maxLocation= getMaximumLocation(text, offset, length);
+
+		int x1= minLocation.x;
+		int x2= minLocation.x + maxLocation.x - minLocation.x - 1;
+		int y= minLocation.y + text.getLineHeight() - 1;
+		
+		GC gc= event.gc;
+		gc.setForeground(fFrameColor);
+		gc.drawLine(x1, y, x2, y);
+	}
+
+	private static Point getMinimumLocation(StyledText text, int offset, int length) {
+		Point minLocation= new Point(Integer.MAX_VALUE, Integer.MAX_VALUE);
+
+		for (int i= 0; i <= length; i++) {
+			Point location= text.getLocationAtOffset(offset + i);
+			
+			if (location.x < minLocation.x)
+				minLocation.x= location.x;			
+			if (location.y < minLocation.y)
+				minLocation.y= location.y;			
+		}	
+		
+		return minLocation;
+	}
+
+	private static Point getMaximumLocation(StyledText text, int offset, int length) {
+		Point maxLocation= new Point(Integer.MIN_VALUE, Integer.MIN_VALUE);
+
+		for (int i= 0; i <= length; i++) {
+			Point location= text.getLocationAtOffset(offset + i);
+			
+			if (location.x > maxLocation.x)
+				maxLocation.x= location.x;			
+			if (location.y > maxLocation.y)
+				maxLocation.y= location.y;			
+		}	
+		
+		return maxLocation;
+	}
+
+	private void redrawRegion() {
+		IRegion region= fViewer.getVisibleRegion();
+		
+		if (!includes(region, fFramePosition)) {
+		 	leave(UNINSTALL | COMMIT | DOCUMENT_CHANGED);
+		 	return;		    
+		}
+
+		int offset= fFramePosition.getOffset() -  region.getOffset();
+		int length= fFramePosition.getLength();
+
+		StyledText text= fViewer.getTextWidget();
+		if (text != null && !text.isDisposed())
+			text.redrawRange(offset, length, true);
+	}
+
+	private void selectRegion() {
+		IRegion region= fViewer.getVisibleRegion();
+
+		if (!includes(region, fFramePosition)) {
+		 	leave(UNINSTALL | COMMIT | DOCUMENT_CHANGED);
+		 	return;   
+		}
+
+		int start= fFramePosition.getOffset() - region.getOffset();
+		int end= fFramePosition.getLength() + start;	
+
+		StyledText text= fViewer.getTextWidget();
+		if (text != null && !text.isDisposed())
+			text.setSelection(start, end);
+	}
+
+	private void updateCaret() {
+		IRegion region= fViewer.getVisibleRegion();		
+
+		if (!includes(region, fFramePosition)) {
+		 	leave(UNINSTALL | COMMIT | DOCUMENT_CHANGED);
+		 	return;   
+		}
+
+		int offset= fFramePosition.getOffset() + fCaretOffset - region.getOffset();
+		
+		if ((offset >= 0) && (offset <= region.getLength())) {
+			StyledText text= fViewer.getTextWidget();
+			if (text != null && !text.isDisposed())
+				text.setCaretOffset(offset);
+		}
+	}
+
+	/*
+	 * @see ModifyListener#modifyText(ModifyEvent)
+	 */	 
+	public void modifyText(ModifyEvent e) {
+		// reposition caret after StyledText
+		redrawRegion();
+		updateCaret();
+	}
+
+	private static void handleException(Shell shell, Exception e) {
+		String title= LinkedPositionMessages.getString("LinkedPositionUI.error.title"); //$NON-NLS-1$
+		if (e instanceof CoreException)
+			PHPeclipsePlugin.log(e);
+   //   ExceptionHandler.handle((CoreException)e, shell, title, null);
+		else if (e instanceof InvocationTargetException)
+			PHPeclipsePlugin.log(e);
+    //  ExceptionHandler.handle((InvocationTargetException)e, shell, title, null);
+		else {
+			MessageDialog.openError(shell, title, e.getMessage());
+			PHPeclipsePlugin.log(e);
+		}
+	}
+
+	/*
+	 * @see ITextInputListener#inputDocumentAboutToBeChanged(IDocument, IDocument)
+	 */
+	public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {
+		// 5326: leave linked mode on document change
+		int flags= UNINSTALL | COMMIT | (oldInput.equals(newInput) ? 0 : DOCUMENT_CHANGED);
+		leave(flags);
+	}
+
+	/*
+	 * @see ITextInputListener#inputDocumentChanged(IDocument, IDocument)
+	 */
+	public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
+	}
+
+	private static boolean includes(IRegion region, Position position) {
+		return
+			position.getOffset() >= region.getOffset() &&
+			position.getOffset() + position.getLength() <= region.getOffset() + region.getLength();
+	}
+
+}
\ No newline at end of file
diff --git a/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/template/TemplateProposal.java b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/template/TemplateProposal.java
index 5178caa..ca3ac38 100644
--- a/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/template/TemplateProposal.java
+++ b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/text/template/TemplateProposal.java
@@ -9,10 +9,13 @@ import net.sourceforge.phpdt.internal.corext.template.TemplateBuffer;
 import net.sourceforge.phpdt.internal.corext.template.TemplateContext;
 import net.sourceforge.phpdt.internal.corext.template.TemplateMessages;
 import net.sourceforge.phpdt.internal.corext.template.TemplatePosition;
-import net.sourceforge.phpeclipse.PHPeclipsePlugin;
-import org.eclipse.core.runtime.CoreException;
 import net.sourceforge.phpdt.internal.corext.template.java.CompilationUnitContext;
 import net.sourceforge.phpdt.internal.corext.template.java.JavaTemplateMessages;
+import net.sourceforge.phpdt.internal.ui.text.java.IJavaCompletionProposal;
+import net.sourceforge.phpeclipse.PHPeclipsePlugin;
+import org.eclipse.core.runtime.CoreException;
+import net.sourceforge.phpdt.internal.ui.text.link.LinkedPositionManager;
+import net.sourceforge.phpdt.internal.ui.text.link.LinkedPositionUI;
 import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.jface.text.BadLocationException;
 import org.eclipse.jface.text.IDocument;
@@ -22,8 +25,6 @@ import org.eclipse.jface.text.contentassist.IContextInformation;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.widgets.Shell;
-//import org.eclipse.jdt.internal.ui.JavaPlugin;
-import net.sourceforge.phpdt.internal.ui.text.java.IJavaCompletionProposal;
 //import org.eclipse.jdt.internal.ui.text.link.LinkedPositionManager;
 //import org.eclipse.jdt.internal.ui.text.link.LinkedPositionUI;
 //import org.eclipse.jdt.internal.ui.util.ExceptionHandler;
@@ -41,7 +42,7 @@ public class TemplateProposal implements IJavaCompletionProposal {
 
 	private TemplateBuffer fTemplateBuffer;
 	private String fOldText;
-//	private IRegion fSelectedRegion; // initialized by apply()
+	private IRegion fSelectedRegion; // initialized by apply()
 		
 	/**
 	 * Creates a template proposal with a template and its context.
@@ -78,7 +79,7 @@ public class TemplateProposal implements IJavaCompletionProposal {
 			document.replace(start, end - start, templateString);	
 
 			// translate positions
-	//		LinkedPositionManager manager= new LinkedPositionManager(document);
+			LinkedPositionManager manager= new LinkedPositionManager(document);
 			TemplatePosition[] variables= fTemplateBuffer.getVariables();
 			for (int i= 0; i != variables.length; i++) {
 				TemplatePosition variable= variables[i];
@@ -89,15 +90,15 @@ public class TemplateProposal implements IJavaCompletionProposal {
 				int[] offsets= variable.getOffsets();
 				int length= variable.getLength();
 				
-//				for (int j= 0; j != offsets.length; j++)
-//					manager.addPosition(offsets[j] + start, length);
+				for (int j= 0; j != offsets.length; j++)
+					manager.addPosition(offsets[j] + start, length);
 			}
 			
-//			LinkedPositionUI editor= new LinkedPositionUI(fViewer, manager);
-//			editor.setFinalCaretOffset(getCaretOffset(fTemplateBuffer) + start);
-//			editor.enter();
+			LinkedPositionUI editor= new LinkedPositionUI(fViewer, manager);
+			editor.setFinalCaretOffset(getCaretOffset(fTemplateBuffer) + start);
+			editor.enter();
 
-//			fSelectedRegion= editor.getSelectedRegion();
+			fSelectedRegion= editor.getSelectedRegion();
 			
 		} catch (BadLocationException e) {
 			PHPeclipsePlugin.log(e);	
@@ -124,8 +125,8 @@ public class TemplateProposal implements IJavaCompletionProposal {
 	 * @see ICompletionProposal#getSelection(IDocument)
 	 */
 	public Point getSelection(IDocument document) {
-//		return new Point(fSelectedRegion.getOffset(), fSelectedRegion.getLength());
-	  return null;
+		return new Point(fSelectedRegion.getOffset(), fSelectedRegion.getLength());
+//	  return null;
   }
 
 	/*
diff --git a/net.sourceforge.phpeclipse/src/net/sourceforge/phpeclipse/IPreferenceConstants.java b/net.sourceforge.phpeclipse/src/net/sourceforge/phpeclipse/IPreferenceConstants.java
index ac605f0..88f3dd7 100644
--- a/net.sourceforge.phpeclipse/src/net/sourceforge/phpeclipse/IPreferenceConstants.java
+++ b/net.sourceforge.phpeclipse/src/net/sourceforge/phpeclipse/IPreferenceConstants.java
@@ -59,5 +59,7 @@ public interface IPreferenceConstants {
   public static final String PHP_STRING = "_php_string";
   public static final String PHP_DEFAULT = "_php_default";
   
+  public static final String LINKED_POSITION_COLOR= "_linkedPositionColor";
+  
   
 }
\ No newline at end of file
diff --git a/net.sourceforge.phpeclipse/src/net/sourceforge/phpeclipse/PHPeclipsePlugin.java b/net.sourceforge.phpeclipse/src/net/sourceforge/phpeclipse/PHPeclipsePlugin.java
index 7c46c83..dc81130 100644
--- a/net.sourceforge.phpeclipse/src/net/sourceforge/phpeclipse/PHPeclipsePlugin.java
+++ b/net.sourceforge.phpeclipse/src/net/sourceforge/phpeclipse/PHPeclipsePlugin.java
@@ -270,8 +270,9 @@ public class PHPeclipsePlugin extends AbstractUIPlugin implements IPreferenceCon
     PreferenceConverter.setDefault(store, PHP_KEYWORD, PHPColorProvider.KEYWORD);
     PreferenceConverter.setDefault(store, PHP_VARIABLE, PHPColorProvider.VARIABLE);
     PreferenceConverter.setDefault(store, PHP_FUNCTIONNAME, PHPColorProvider.FUNCTION_NAME);
-    PreferenceConverter.setDefault(store, PHP_STRING, PHPColorProvider.STRING);
+    PreferenceConverter.setDefault(store, PHP_STRING, PHPColorProvider.STRING); 
     PreferenceConverter.setDefault(store, PHP_DEFAULT, PHPColorProvider.DEFAULT);
+    PreferenceConverter.setDefault(store, LINKED_POSITION_COLOR, PHPColorProvider.LINKED_POSITION_COLOR);
 
   }
   
diff --git a/net.sourceforge.phpeclipse/src/net/sourceforge/phpeclipse/phpeditor/util/PHPColorProvider.java b/net.sourceforge.phpeclipse/src/net/sourceforge/phpeclipse/phpeditor/util/PHPColorProvider.java
index 48d7527..607d90d 100644
--- a/net.sourceforge.phpeclipse/src/net/sourceforge/phpeclipse/phpeditor/util/PHPColorProvider.java
+++ b/net.sourceforge.phpeclipse/src/net/sourceforge/phpeclipse/phpeditor/util/PHPColorProvider.java
@@ -30,6 +30,8 @@ public class PHPColorProvider {
 	public static final RGB FUNCTION_NAME= new RGB(127, 127, 159);
 	public static final RGB STRING= new RGB(42, 0, 255);
 	public static final RGB DEFAULT= new RGB(0, 0, 0);
+  
+  public static final RGB  LINKED_POSITION_COLOR= new RGB(0, 0, 0);
 
 	protected Map fColorTable= new HashMap(10);