extracted common base class LinkedModeScriptCompletionProposal
diff --git a/core/plugins/org.eclipse.dltk.ui/src/org/eclipse/dltk/ui/text/completion/AbstractScriptCompletionProposal.java b/core/plugins/org.eclipse.dltk.ui/src/org/eclipse/dltk/ui/text/completion/AbstractScriptCompletionProposal.java
index 784a342..3767eb8 100644
--- a/core/plugins/org.eclipse.dltk.ui/src/org/eclipse/dltk/ui/text/completion/AbstractScriptCompletionProposal.java
+++ b/core/plugins/org.eclipse.dltk.ui/src/org/eclipse/dltk/ui/text/completion/AbstractScriptCompletionProposal.java
@@ -287,6 +287,7 @@
 
 			replace(document, getReplacementOffset(), getReplacementLength(),
 					replacement);
+			postReplace(document);
 
 			referenceOffset = referenceTracker.postReplace(document);
 			setReplacementOffset(referenceOffset
@@ -301,6 +302,9 @@
 		}
 	}
 
+	protected void postReplace(IDocument document) throws BadLocationException {
+	}
+
 	/**
 	 * @since 3.0
 	 */
@@ -747,7 +751,6 @@
 		return DLTKCore.ENABLED.equals(value);
 	}
 
-
 	protected IPreferenceStore getPreferenceStore() {
 		return DLTKUIPlugin.getDefault().getPreferenceStore();
 	}
diff --git a/core/plugins/org.eclipse.dltk.ui/src/org/eclipse/dltk/ui/text/completion/LinkedModeScriptCompletionProposal.java b/core/plugins/org.eclipse.dltk.ui/src/org/eclipse/dltk/ui/text/completion/LinkedModeScriptCompletionProposal.java
new file mode 100644
index 0000000..ea9a855
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.ui/src/org/eclipse/dltk/ui/text/completion/LinkedModeScriptCompletionProposal.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright (c) 2012 NumberFour AG
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     NumberFour AG - initial API and Implementation (Alex Panchenko)
+ *******************************************************************************/
+package org.eclipse.dltk.ui.text.completion;
+
+import org.eclipse.dltk.core.CompletionProposal;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.link.LinkedModeModel;
+import org.eclipse.jface.text.link.LinkedModeUI;
+import org.eclipse.jface.text.link.LinkedPosition;
+import org.eclipse.jface.text.link.LinkedPositionGroup;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.ui.texteditor.link.EditorLinkedModeUI;
+
+/**
+ * @since 4.0
+ */
+public abstract class LinkedModeScriptCompletionProposal extends
+		LazyScriptCompletionProposal {
+
+	public LinkedModeScriptCompletionProposal(CompletionProposal proposal,
+			ScriptContentAssistInvocationContext context) {
+		super(proposal, context);
+	}
+
+	private IRegion fSelectedRegion; // initialized by apply()
+
+	@Override
+	public void apply(IDocument document, char trigger, int offset) {
+		if (trigger == ' ' || trigger == getOpenTrigger())
+			trigger = '\0';
+		super.apply(document, trigger, offset);
+
+		int exit = getReplacementOffset() + getReplacementString().length();
+
+		if (replacementBuffer != null && replacementBuffer.hasArguments()
+				&& getTextViewer() != null) {
+			final int replacementOffset = getReplacementOffset();
+			final int cursor = getCursorPosition();
+			try {
+				LinkedModeModel model = new LinkedModeModel();
+				for (ReplacementBuffer.Argument region : replacementBuffer.arguments) {
+					LinkedPositionGroup group = new LinkedPositionGroup();
+					int argOffset = replacementOffset + region.offset;
+					if (region.relativeToCursor)
+						argOffset += cursor;
+					group.addPosition(new LinkedPosition(document, argOffset,
+							region.length, LinkedPositionGroup.NO_STOP));
+					model.addGroup(group);
+				}
+
+				model.forceInstall();
+
+				LinkedModeUI ui = new EditorLinkedModeUI(model, getTextViewer());
+				ui.setExitPosition(getTextViewer(), exit, 0, Integer.MAX_VALUE);
+				ui.setExitPolicy(new ExitPolicy(getExitTigger(), document));
+				final char[] exitTriggers = getExitTiggers();
+				if (exitTriggers != null) {
+					for (int i = 0; i < exitTriggers.length; ++i) {
+						ui.setExitPolicy(new ExitPolicy(exitTriggers[i],
+								document));
+					}
+				}
+
+				ui.setCyclingMode(LinkedModeUI.CYCLE_WHEN_NO_PARENT);
+				ui.enter();
+
+				fSelectedRegion = ui.getSelectedRegion();
+
+			} catch (BadLocationException e) {
+			}
+		} else {
+			fSelectedRegion = new Region(exit, 0);
+		}
+	}
+
+	protected abstract char getOpenTrigger();
+
+	protected abstract char getExitTigger();
+
+	protected char[] getExitTiggers() {
+		return null;
+	}
+
+	/**
+	 * @see org.eclipse.dltk.ui.text.completion.AbstractScriptCompletionProposal#getSelection(org.eclipse.jface.text.IDocument)
+	 */
+	@Override
+	public Point getSelection(IDocument document) {
+		if (fSelectedRegion == null)
+			return new Point(getReplacementOffset(), 0);
+
+		return new Point(fSelectedRegion.getOffset(),
+				fSelectedRegion.getLength());
+	}
+
+	private ReplacementBuffer replacementBuffer;
+
+	/**
+	 * Override {@link #computeReplacement(IReplacementBuffer)}
+	 */
+	@Override
+	protected final String computeReplacementString() {
+		replacementBuffer = new ReplacementBuffer();
+		computeReplacement(replacementBuffer);
+		return replacementBuffer.toString();
+	}
+
+	protected abstract void computeReplacement(ReplacementBuffer buffer);
+
+}
diff --git a/core/plugins/org.eclipse.dltk.ui/src/org/eclipse/dltk/ui/text/completion/ReplacementBuffer.java b/core/plugins/org.eclipse.dltk.ui/src/org/eclipse/dltk/ui/text/completion/ReplacementBuffer.java
index 6f8e1b0..a76beff 100644
--- a/core/plugins/org.eclipse.dltk.ui/src/org/eclipse/dltk/ui/text/completion/ReplacementBuffer.java
+++ b/core/plugins/org.eclipse.dltk.ui/src/org/eclipse/dltk/ui/text/completion/ReplacementBuffer.java
@@ -14,17 +14,33 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import org.eclipse.jface.text.IRegion;
-import org.eclipse.jface.text.Region;
-
 /**
  * @since 4.0
  */
 public class ReplacementBuffer {
-	private final List<IRegion> arguments = new ArrayList<IRegion>();
+	static class Argument {
+		final int offset;
+		final int length;
+		final boolean relativeToCursor;
 
+		public Argument(int offset, int length, boolean relativeToCursor) {
+			this.offset = offset;
+			this.length = length;
+			this.relativeToCursor = relativeToCursor;
+		}
+	}
+
+	final List<Argument> arguments = new ArrayList<Argument>();
+
+	@Deprecated
 	public void addArgument(int offset, int length) {
-		arguments.add(new Region(offset, length));
+		arguments.add(new Argument(offset, length, true));
+	}
+
+	public void addArgument(String value) {
+		final int offset = length();
+		append(value);
+		arguments.add(new Argument(offset, value.length(), false));
 	}
 
 	private final StringBuilder buffer = new StringBuilder();
@@ -46,8 +62,4 @@
 		return !arguments.isEmpty();
 	}
 
-	public List<IRegion> getArguments() {
-		return arguments;
-	}
-
 }
diff --git a/core/plugins/org.eclipse.dltk.ui/src/org/eclipse/dltk/ui/text/completion/ScriptMethodCompletionProposal.java b/core/plugins/org.eclipse.dltk.ui/src/org/eclipse/dltk/ui/text/completion/ScriptMethodCompletionProposal.java
index 024b73f..0c2be59 100644
--- a/core/plugins/org.eclipse.dltk.ui/src/org/eclipse/dltk/ui/text/completion/ScriptMethodCompletionProposal.java
+++ b/core/plugins/org.eclipse.dltk.ui/src/org/eclipse/dltk/ui/text/completion/ScriptMethodCompletionProposal.java
@@ -14,20 +14,11 @@
 import org.eclipse.dltk.ui.DLTKUIPlugin;
 import org.eclipse.dltk.ui.PreferenceConstants;
 import org.eclipse.jface.preference.IPreferenceStore;
-import org.eclipse.jface.text.BadLocationException;
 import org.eclipse.jface.text.IDocument;
-import org.eclipse.jface.text.IRegion;
-import org.eclipse.jface.text.Region;
 import org.eclipse.jface.text.contentassist.IContextInformation;
-import org.eclipse.jface.text.link.LinkedModeModel;
-import org.eclipse.jface.text.link.LinkedModeUI;
-import org.eclipse.jface.text.link.LinkedPosition;
-import org.eclipse.jface.text.link.LinkedPositionGroup;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.ui.texteditor.link.EditorLinkedModeUI;
 
 public class ScriptMethodCompletionProposal extends
-		LazyScriptCompletionProposal implements
+		LinkedModeScriptCompletionProposal implements
 		IScriptCompletionProposalExtension2 {
 	/**
 	 * Triggers for method proposals without parameters. Do not modify.
@@ -44,8 +35,6 @@
 	/** Triggers for method name proposals (static imports). Do not modify. */
 	protected final static char[] METHOD_NAME_TRIGGERS = new char[] { ';' };
 
-	private IRegion fSelectedRegion; // initialized by apply()
-
 	private boolean fHasParameters;
 	private boolean fHasParametersComputed = false;
 	private int fContextInformationPosition;
@@ -62,58 +51,6 @@
 		return fProposal.getName();
 	}
 
-	private ReplacementBuffer replacementBuffer;
-
-	@Override
-	public void apply(IDocument document, char trigger, int offset) {
-		if (trigger == ' ' || trigger == '(')
-			trigger = '\0';
-		super.apply(document, trigger, offset);
-
-		int exit = getReplacementOffset() + getReplacementString().length();
-
-		if (replacementBuffer != null && replacementBuffer.hasArguments()
-				&& getTextViewer() != null) {
-			int baseOffset = getReplacementOffset() + getCursorPosition();
-			try {
-				LinkedModeModel model = new LinkedModeModel();
-				for (IRegion region : replacementBuffer.getArguments()) {
-					LinkedPositionGroup group = new LinkedPositionGroup();
-					group.addPosition(new LinkedPosition(document, baseOffset
-							+ region.getOffset(), region.getLength(),
-							LinkedPositionGroup.NO_STOP));
-					model.addGroup(group);
-				}
-
-				model.forceInstall();
-
-				LinkedModeUI ui = new EditorLinkedModeUI(model, getTextViewer());
-				ui.setExitPosition(getTextViewer(), exit, 0, Integer.MAX_VALUE);
-				ui.setExitPolicy(new ExitPolicy(')', document));
-				ui.setCyclingMode(LinkedModeUI.CYCLE_WHEN_NO_PARENT);
-				ui.enter();
-
-				fSelectedRegion = ui.getSelectedRegion();
-
-			} catch (BadLocationException e) {
-			}
-		} else {
-			fSelectedRegion = new Region(exit, 0);
-		}
-	}
-
-	/**
-	 * @see org.eclipse.dltk.ui.text.completion.AbstractScriptCompletionProposal#getSelection(org.eclipse.jface.text.IDocument)
-	 */
-	@Override
-	public Point getSelection(IDocument document) {
-		if (fSelectedRegion == null)
-			return new Point(getReplacementOffset(), 0);
-
-		return new Point(fSelectedRegion.getOffset(),
-				fSelectedRegion.getLength());
-	}
-
 	@Override
 	public CharSequence getPrefixCompletionText(IDocument document,
 			int completionOffset) {
@@ -204,14 +141,14 @@
 				&& (noOverwrite || completion.charAt(completion.length() - 1) == ')');
 	}
 
-	/**
-	 * Override {@link #computeReplacement(IReplacementBuffer)}
-	 */
 	@Override
-	protected final String computeReplacementString() {
-		replacementBuffer = new ReplacementBuffer();
-		computeReplacement(replacementBuffer);
-		return replacementBuffer.toString();
+	protected char getOpenTrigger() {
+		return '(';
+	}
+
+	@Override
+	protected char getExitTigger() {
+		return ')';
 	}
 
 	/**
@@ -239,15 +176,11 @@
 			// buffer.append(SPACE);
 
 			String[] parameterNames = fProposal.findParameterNames(null);
-			int argumentOffset = 0;
 			for (int i = 0; i < parameterNames.length; ++i) {
 				if (i != 0) {
 					buffer.append(COMMA);
-					argumentOffset += 1;
 				}
-				buffer.append(parameterNames[i]);
-				buffer.addArgument(argumentOffset, parameterNames[i].length());
-				argumentOffset += parameterNames[i].length();
+				buffer.addArgument(parameterNames[i]);
 			}
 
 			// don't add the trailing space, but let the user type it in himself