[264246] [content assist] Use Linked Positions for cursor positioning in applied content assist proposals
diff --git a/bundles/org.eclipse.jst.jsp.ui/src/org/eclipse/jst/jsp/ui/internal/JSPUIMessages.java b/bundles/org.eclipse.jst.jsp.ui/src/org/eclipse/jst/jsp/ui/internal/JSPUIMessages.java
index aae9149..3b5034e 100644
--- a/bundles/org.eclipse.jst.jsp.ui/src/org/eclipse/jst/jsp/ui/internal/JSPUIMessages.java
+++ b/bundles/org.eclipse.jst.jsp.ui/src/org/eclipse/jst/jsp/ui/internal/JSPUIMessages.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2011 IBM Corporation and others.
+ * Copyright (c) 2005, 2012 IBM Corporation and others.
  * 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
@@ -118,6 +118,7 @@
 	public static String JSPFContentSettingsPropertyPage_4;
 	public static String ProjectJSPFContentSettingsPropertyPage_0;
 	public static String TagPropertyPage_desc;
+	public static String Template_Taglib_URI;
 	public static String Title_InvalidValue;
 	public static String Message_InvalidValue;
 	public static String SyntaxColoringPage_0;
diff --git a/bundles/org.eclipse.jst.jsp.ui/src/org/eclipse/jst/jsp/ui/internal/JSPUIPluginResources.properties b/bundles/org.eclipse.jst.jsp.ui/src/org/eclipse/jst/jsp/ui/internal/JSPUIPluginResources.properties
index 30a19cf..23c103c 100644
--- a/bundles/org.eclipse.jst.jsp.ui/src/org/eclipse/jst/jsp/ui/internal/JSPUIPluginResources.properties
+++ b/bundles/org.eclipse.jst.jsp.ui/src/org/eclipse/jst/jsp/ui/internal/JSPUIPluginResources.properties
@@ -1,5 +1,5 @@
 ###############################################################################
-# Copyright (c) 2004, 2011 IBM Corporation and others.
+# Copyright (c) 2004, 2012 IBM Corporation and others.
 # 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
@@ -168,6 +168,7 @@
 CustomTagHyperlink_hyperlinkText=Open Declaration
 TLDContentOutlineConfiguration_0=Show Content Values
 TagPropertyPage_desc=Specify the surrounding language used in this tag file:
+Template_Taglib_URI=Tag Library URI
 
 JSPFilesPreferencePage_Search_group=Search
 JSPFilesPreferencePage_Supply_JSP_search_to_Java_search=&Include JSP matches in Java searches
diff --git a/bundles/org.eclipse.jst.jsp.ui/src/org/eclipse/jst/jsp/ui/internal/contentassist/LibraryTagsCompletionProposalComputer.java b/bundles/org.eclipse.jst.jsp.ui/src/org/eclipse/jst/jsp/ui/internal/contentassist/LibraryTagsCompletionProposalComputer.java
index 99abe9b..3b97e3e 100644
--- a/bundles/org.eclipse.jst.jsp.ui/src/org/eclipse/jst/jsp/ui/internal/contentassist/LibraryTagsCompletionProposalComputer.java
+++ b/bundles/org.eclipse.jst.jsp.ui/src/org/eclipse/jst/jsp/ui/internal/contentassist/LibraryTagsCompletionProposalComputer.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2010, 2011 IBM Corporation and others.
+ * Copyright (c) 2010, 2012 IBM Corporation and others.
  * 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
@@ -22,6 +22,8 @@
 import org.eclipse.jst.jsp.core.internal.contentmodel.tld.TLDCMDocumentManager;
 import org.eclipse.jst.jsp.core.internal.contentmodel.tld.provisional.TLDElementDeclaration;
 import org.eclipse.jst.jsp.core.internal.contenttype.DeploymentDescriptorPropertyCache;
+import org.eclipse.jst.jsp.core.internal.modelquery.JSPModelQueryExtension;
+import org.eclipse.jst.jsp.core.internal.modelquery.TaglibModelQueryExtension;
 import org.eclipse.jst.jsp.core.internal.provisional.JSP12Namespace;
 import org.eclipse.jst.jsp.core.internal.provisional.JSP20Namespace;
 import org.eclipse.jst.jsp.core.internal.provisional.contenttype.ContentTypeIdForJSP;
@@ -51,6 +53,7 @@
 import org.eclipse.wst.xml.core.internal.ssemodelquery.ModelQueryAdapter;
 import org.eclipse.wst.xml.ui.internal.contentassist.AbstractXMLModelQueryCompletionProposalComputer;
 import org.eclipse.wst.xml.ui.internal.contentassist.ContentAssistRequest;
+import org.eclipse.wst.xml.ui.internal.contentassist.MarkupCompletionProposal;
 import org.eclipse.wst.xml.ui.internal.contentassist.XMLContentModelGenerator;
 import org.eclipse.wst.xml.ui.internal.contentassist.XMLRelevanceConstants;
 import org.eclipse.wst.xml.ui.internal.editor.CMImageUtil;
@@ -343,7 +346,7 @@
 					// account for the < and >
 					int markupAdjustment = getCursorPositionForProposedText(proposedText);
 					String proposedInfo = getAdditionalInfo(null, ed);
-					CustomCompletionProposal proposal = new CustomCompletionProposal(
+					MarkupCompletionProposal proposal = new MarkupCompletionProposal(
 							proposedText, contentAssistRequest.getReplacementBeginPosition(),
 							contentAssistRequest.getReplacementLength(), markupAdjustment, image,
 							tagname, null, proposedInfo, XMLRelevanceConstants.R_TAG_INSERTION);
diff --git a/bundles/org.eclipse.jst.jsp.ui/src/org/eclipse/jst/jsp/ui/internal/templates/TemplateContextTypeJSP.java b/bundles/org.eclipse.jst.jsp.ui/src/org/eclipse/jst/jsp/ui/internal/templates/TemplateContextTypeJSP.java
index 60e19e7..baf57d2 100644
--- a/bundles/org.eclipse.jst.jsp.ui/src/org/eclipse/jst/jsp/ui/internal/templates/TemplateContextTypeJSP.java
+++ b/bundles/org.eclipse.jst.jsp.ui/src/org/eclipse/jst/jsp/ui/internal/templates/TemplateContextTypeJSP.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2004, 2005 IBM Corporation and others.
+ * Copyright (c) 2004, 2012 IBM Corporation and others.
  * 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
@@ -10,8 +10,31 @@
  *******************************************************************************/
 package org.eclipse.jst.jsp.ui.internal.templates;
 
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.templates.DocumentTemplateContext;
 import org.eclipse.jface.text.templates.GlobalTemplateVariables;
+import org.eclipse.jface.text.templates.SimpleTemplateVariableResolver;
+import org.eclipse.jface.text.templates.TemplateContext;
 import org.eclipse.jface.text.templates.TemplateContextType;
+import org.eclipse.jface.text.templates.TemplateVariable;
+import org.eclipse.jst.jsp.core.internal.util.FacetModuleCoreSupport;
+import org.eclipse.jst.jsp.core.taglib.IJarRecord;
+import org.eclipse.jst.jsp.core.taglib.ITLDRecord;
+import org.eclipse.jst.jsp.core.taglib.ITaglibDescriptor;
+import org.eclipse.jst.jsp.core.taglib.ITaglibRecord;
+import org.eclipse.jst.jsp.core.taglib.TaglibIndex;
+import org.eclipse.jst.jsp.ui.internal.JSPUIMessages;
+import org.eclipse.wst.sse.core.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
 
 /**
  * Base class for JSP template context types. Templates of this context type
@@ -29,5 +52,107 @@
 		addResolver(new GlobalTemplateVariables.WordSelection());
 		addResolver(new GlobalTemplateVariables.Year());
 		addResolver(new EncodingTemplateVariableResolverJSP());
+		addResolver(new URITemplateResolver());
+		
+	}
+
+	/**
+	 * Resolves the ${uri} Template Variable for a taglib directive providing
+	 * URIs from the taglib index
+	 *
+	 */
+	class URITemplateResolver extends SimpleTemplateVariableResolver {
+
+		protected URITemplateResolver() {
+			super("uri", JSPUIMessages.Template_Taglib_URI); //$NON-NLS-1$
+		}
+		
+		public void resolve(TemplateVariable variable, TemplateContext context) {
+			if (context instanceof DocumentTemplateContext) {
+				DocumentTemplateContext docContext = (DocumentTemplateContext) context;
+				final IPath path = getPath(docContext.getDocument());
+				if (path != null) {
+					String[] uris = getURIs(TaglibIndex.getAvailableTaglibRecords(path), path);
+					if (uris != null && uris.length > 0) {
+						variable.setValues(uris);
+					}
+				}
+				
+			}
+		}
+
+		private String[] getURIs(ITaglibRecord[] records, IPath basePath) {
+			if (records != null) {
+				Set uris = new HashSet(records.length);
+				for (int i = 0; i < records.length; i++) {
+					final ITaglibRecord record = records[i];
+					final ITaglibDescriptor descriptor = record.getDescriptor();
+					String uri = null;
+					switch (record.getRecordType()) {
+						case ITaglibRecord.URL:
+							uris.add(descriptor.getURI());
+							break;
+						case ITaglibRecord.JAR: {
+							IPath location = ((IJarRecord) record).getLocation();
+							IFile[] files = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocation(location);
+							IPath localContextRoot = FacetModuleCoreSupport.computeWebContentRootPath(basePath);
+							for (int fileNumber = 0; fileNumber < files.length; fileNumber++) {
+								if (localContextRoot.isPrefixOf(files[fileNumber].getFullPath())) {
+									uri = IPath.SEPARATOR +
+											files[fileNumber].getFullPath().removeFirstSegments(localContextRoot.segmentCount()).toString();
+								}
+								else {
+									uri = FacetModuleCoreSupport.getRuntimePath(files[fileNumber].getFullPath()).toString();
+								}
+								uris.add(uri);
+							}
+							break;
+						}
+						case ITaglibRecord.TLD: {
+							uri = descriptor.getURI();
+							if (uri == null || uri.trim().length() == 0) {
+								IPath path = ((ITLDRecord) record).getPath();
+								IPath localContextRoot = FacetModuleCoreSupport.computeWebContentRootPath(basePath);
+								if (localContextRoot.isPrefixOf(path)) {
+									uri = IPath.SEPARATOR + path.removeFirstSegments(localContextRoot.segmentCount()).toString();
+								}
+								else {
+									uri = FacetModuleCoreSupport.getRuntimePath(path).toString();
+								}
+							}
+							uris.add(uri);
+							break;
+						}
+						
+					}
+				}
+				String[] urisArray = (String[]) uris.toArray(new String[uris.size()]);
+				Arrays.sort(urisArray);
+				return urisArray;
+			}
+			return null;
+		}
+
+		private IPath getPath(IDocument iDoc) {
+			IPath path = null;
+			if (iDoc instanceof IStructuredDocument) {
+				IStructuredDocument document = (IStructuredDocument) iDoc;
+				IStructuredModel model = null;
+				try {
+					model = StructuredModelManager.getModelManager().getModelForRead(document);
+					if (model != null) {
+						String location = model.getBaseLocation();
+						if (location != null) {
+							path = new Path(location);
+						}
+					}
+				}
+				finally {
+					if (model != null)
+						model.releaseFromRead();
+				}
+			}
+			return path;
+		}
 	}
 }
diff --git a/bundles/org.eclipse.jst.jsp.ui/templates/jspdefault-templates.properties b/bundles/org.eclipse.jst.jsp.ui/templates/jspdefault-templates.properties
index 58d9de8..187c4c5 100644
--- a/bundles/org.eclipse.jst.jsp.ui/templates/jspdefault-templates.properties
+++ b/bundles/org.eclipse.jst.jsp.ui/templates/jspdefault-templates.properties
@@ -1,5 +1,5 @@
 ###############################################################################
-# Copyright (c) 2004, 2008 IBM Corporation and others.
+# Copyright (c) 2004, 2012 IBM Corporation and others.
 # 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
@@ -28,7 +28,7 @@
 Templates.jsppagedirective.content=<%@ page contentType="text/html; charset=${encoding}" %>
 Templates.jsptaglibdirective.name=JSP taglib directive
 Templates.jsptaglibdirective.desc=JSP taglib directive
-Templates.jsptaglibdirective.content=<%@ taglib uri="${cursor}" prefix="" %>
+Templates.jsptaglibdirective.content=<%@ taglib uri="${uri}" prefix="${cursor}" %>
 Templates.jsphtml.name=New JSP File (html)
 Templates.jsphtml.desc=JSP with html markup
 Templates.jsphtml.content=<%@ page language="java" contentType="text/html; charset=${encoding}"\n    pageEncoding="${encoding}"%>\n<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n<html>\n<head>\n<meta http-equiv="Content-Type" content="text/html; charset=${encoding}">\n<title>Insert title here</title>\n</head>\n<body>\n${cursor}\n</body>\n</html>
diff --git a/bundles/org.eclipse.wst.xml.ui/src/org/eclipse/wst/xml/ui/internal/contentassist/AbstractXMLModelQueryCompletionProposalComputer.java b/bundles/org.eclipse.wst.xml.ui/src/org/eclipse/wst/xml/ui/internal/contentassist/AbstractXMLModelQueryCompletionProposalComputer.java
index c6c0143..237c524 100644
--- a/bundles/org.eclipse.wst.xml.ui/src/org/eclipse/wst/xml/ui/internal/contentassist/AbstractXMLModelQueryCompletionProposalComputer.java
+++ b/bundles/org.eclipse.wst.xml.ui/src/org/eclipse/wst/xml/ui/internal/contentassist/AbstractXMLModelQueryCompletionProposalComputer.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2010, 2011 IBM Corporation and others.
+ * Copyright (c) 2010, 2012 IBM Corporation and others.
  * 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
@@ -198,7 +198,7 @@
 							if (attrAtLocationHasValue) {
 								// only propose the name
 								proposedText = getRequiredName(node, attrDecl);
-								proposal = new CustomCompletionProposal(
+								proposal = new MarkupCompletionProposal(
 										proposedText, contentAssistRequest.getReplacementBeginPosition(),
 										contentAssistRequest.getReplacementLength(), proposedText.length(),
 										attrImage, proposedText, null, proposedInfo,
@@ -227,7 +227,7 @@
 								}
 								if (proposalNeedsSpace)
 									proposedText += " "; //$NON-NLS-1$
-								proposal = new CustomCompletionProposal(proposedText,
+								proposal = new MarkupCompletionProposal(proposedText,
 										contentAssistRequest.getReplacementBeginPosition(),
 										contentAssistRequest.getReplacementLength(),
 										cursorPosition, attrImage,
@@ -367,7 +367,7 @@
 							if ((matchString.length() == 0) || possibleValue.startsWith(matchString)) {
 								String rString = "\"" + possibleValue + "\""; //$NON-NLS-1$ //$NON-NLS-2$
 								alternateMatch = "\"" + alternateMatch; //$NON-NLS-1$
-								CustomCompletionProposal proposal = new CustomCompletionProposal(
+								CustomCompletionProposal proposal = new MarkupCompletionProposal(
 										rString, rOffset, rLength, possibleValue.length() + 1,
 										XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_ENUM),
 										rString, alternateMatch, null, proposedInfo, XMLRelevanceConstants.R_XML_ATTRIBUTE_VALUE, true);
@@ -377,7 +377,7 @@
 					}
 					if(defaultValue != null && ((matchString.length() == 0) || defaultValue.startsWith(matchString))) {
 						String rString = "\"" + defaultValue + "\""; //$NON-NLS-1$ //$NON-NLS-2$
-						CustomCompletionProposal proposal = new CustomCompletionProposal(
+						CustomCompletionProposal proposal = new MarkupCompletionProposal(
 								rString, rOffset, rLength, defaultValue.length() + 1,
 								XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_DEFAULT),
 								rString, null, proposedInfo, XMLRelevanceConstants.R_XML_ATTRIBUTE_VALUE);
@@ -392,7 +392,7 @@
 					String value = attrDecl.getAttrType().getImpliedValue();
 					if ((value != null) && (value.length() > 0)) {
 						String rValue = "\"" + value + "\"";//$NON-NLS-2$//$NON-NLS-1$
-						CustomCompletionProposal proposal = new CustomCompletionProposal(
+						CustomCompletionProposal proposal = new MarkupCompletionProposal(
 								rValue, contentAssistRequest.getReplacementBeginPosition(),
 								contentAssistRequest.getReplacementLength(), rValue.length() + 1,
 								image, rValue, null, proposedInfo,
@@ -400,7 +400,7 @@
 						contentAssistRequest.addProposal(proposal);
 						if ((currentValue.length() > 0) && !value.equals(currentValue)) {
 							rValue = "\"" + currentValue + "\""; //$NON-NLS-2$//$NON-NLS-1$
-							proposal = new CustomCompletionProposal(rValue, contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest.getReplacementLength(), rValue.length() + 1, image, rValue, null, proposedInfo, XMLRelevanceConstants.R_XML_ATTRIBUTE_VALUE);
+							proposal = new MarkupCompletionProposal(rValue, contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest.getReplacementLength(), rValue.length() + 1, image, rValue, null, proposedInfo, XMLRelevanceConstants.R_XML_ATTRIBUTE_VALUE);
 							contentAssistRequest.addProposal(proposal);
 						}
 					}
@@ -412,7 +412,7 @@
 				CustomCompletionProposal proposal = null;
 				if ((currentValue != null) && (currentValue.length() > 0)) {
 					String rValue = "\"" + currentValue + "\""; //$NON-NLS-2$//$NON-NLS-1$
-					proposal = new CustomCompletionProposal(rValue,
+					proposal = new MarkupCompletionProposal(rValue,
 							contentAssistRequest.getReplacementBeginPosition(),
 							contentAssistRequest.getReplacementLength(), 1, image,
 							rValue, null, proposedInfo,
@@ -447,7 +447,7 @@
 			CompletionProposalInvocationContext context) {
 
 		if (contentAssistRequest.getStartOffset() + contentAssistRequest.getRegion().getTextLength() < contentAssistRequest.getReplacementBeginPosition()) {
-			CustomCompletionProposal proposal = new CustomCompletionProposal(">", //$NON-NLS-1$
+			CustomCompletionProposal proposal = new MarkupCompletionProposal(">", //$NON-NLS-1$
 						contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest.getReplacementLength(), 1, XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_TAG_GENERIC), NLS.bind(XMLUIMessages.Close_with__, (new Object[]{" '>'"})), //$NON-NLS-1$
 						null, null, XMLRelevanceConstants.R_END_TAG_NAME);
 			contentAssistRequest.addProposal(proposal);
@@ -479,14 +479,14 @@
 								image = this.getGenericTagImage();
 							}
 							if (contentAssistRequest.getRegion().getType() == DOMRegionContext.XML_TAG_NAME) {
-								proposal = new CustomCompletionProposal(
+								proposal = new MarkupCompletionProposal(
 										replacementText, contentAssistRequest.getStartOffset(),
 										contentAssistRequest.getRegion().getTextLength(),
 										replacementText.length(), image, displayText, null,
 										proposedInfo, XMLRelevanceConstants.R_END_TAG_NAME);
 							}
 							else {
-								proposal = new CustomCompletionProposal(
+								proposal = new MarkupCompletionProposal(
 										replacementText,
 										contentAssistRequest.getReplacementBeginPosition(),
 										contentAssistRequest.getReplacementLength(),
@@ -631,7 +631,7 @@
 			setErrorMessage(XMLUIMessages.Content_Assist_not_availab_UI_);
 		}
 		if (addProposal == true) {
-			CustomCompletionProposal proposal = new CustomCompletionProposal(replaceText, replaceBegin, replaceLength, cursorOffset, image, displayString, null, proposedInfo, XMLRelevanceConstants.R_END_TAG);
+			CustomCompletionProposal proposal = new MarkupCompletionProposal(replaceText, replaceBegin, replaceLength, cursorOffset, image, displayString, null, proposedInfo, XMLRelevanceConstants.R_END_TAG);
 			contentAssistRequest.addProposal(proposal);
 		}
 	}
@@ -749,7 +749,7 @@
 					// prompt with a self-closing end character if needed
 					// this is one of the few times to ignore the length -- always insert
 					// contentAssistRequest.getReplacementLength()
-					CustomCompletionProposal proposal = new CustomCompletionProposal(
+					CustomCompletionProposal proposal = new MarkupCompletionProposal(
 							getContentGenerator().getStartTagClose(node, elementDecl),
 							contentAssistRequest.getReplacementBeginPosition(), 0,
 							getContentGenerator().getStartTagClose(node, elementDecl).length(), image,
@@ -759,7 +759,7 @@
 				}
 				else {
 					// prompt with a close for the start tag
-					CustomCompletionProposal proposal = new CustomCompletionProposal(">", //$NON-NLS-1$
+					CustomCompletionProposal proposal = new MarkupCompletionProposal(">", //$NON-NLS-1$
 								contentAssistRequest.getReplacementBeginPosition(),
 								// this is one of the few times to ignore the
 								// length -- always insert
@@ -775,7 +775,7 @@
 						IStructuredDocumentRegion sdr = contentAssistRequest.getDocumentRegion();
 						String openingTagText = (sdr != null) ? sdr.getFullText() : ""; //$NON-NLS-1$
 						if ((openingTagText != null) && (openingTagText.indexOf(node.getNodeName()) != -1)) {
-							proposal = new CustomCompletionProposal("></" + node.getNodeName() + ">", //$NON-NLS-2$//$NON-NLS-1$
+							proposal = new MarkupCompletionProposal("></" + node.getNodeName() + ">", //$NON-NLS-2$//$NON-NLS-1$
 										contentAssistRequest.getReplacementBeginPosition(),
 										// this is one of the few times to
 										// ignore the length -- always insert
@@ -786,7 +786,7 @@
 					}
 					// prompt with slash bracket "/>" incase if it's a self ending tag
 					if (endWithSlashBracket) {
-						proposal = new CustomCompletionProposal("/>", //$NON-NLS-1$
+						proposal = new MarkupCompletionProposal("/>", //$NON-NLS-1$
 									contentAssistRequest.getReplacementBeginPosition(),
 									// this is one of the few times to ignore
 									// the length -- always insert
@@ -801,7 +801,7 @@
 			else if ((contentAssistRequest.getDocumentRegion() == node.getLastStructuredDocumentRegion()) && !node.getLastStructuredDocumentRegion().isEnded()) {
 				setErrorMessage(null);
 				// prompt with a closing end character for the end tag
-				CustomCompletionProposal proposal = new CustomCompletionProposal(">", //$NON-NLS-1$
+				CustomCompletionProposal proposal = new MarkupCompletionProposal(">", //$NON-NLS-1$
 							contentAssistRequest.getReplacementBeginPosition(),
 							// this is one of the few times to ignore the length -- always insert
 							// contentAssistRequest.getReplacementLength(),
@@ -865,7 +865,7 @@
 						String proposedInfo = getAdditionalInfo(parentDecl, childType);
 						for (int i = 0; i < childStrings.length; i++) {
 							if(!childStrings[i].equals(defaultValue)) {
-								CustomCompletionProposal textProposal = new CustomCompletionProposal(
+								CustomCompletionProposal textProposal = new MarkupCompletionProposal(
 										childStrings[i],begin, length, childStrings[i].length(),
 										XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_ENUM),
 										childStrings[i], null, proposedInfo, XMLRelevanceConstants.R_TAG_INSERTION);
@@ -873,7 +873,7 @@
 							}
 						}
 						if(defaultValue != null) {
-							CustomCompletionProposal textProposal = new CustomCompletionProposal(
+							CustomCompletionProposal textProposal = new MarkupCompletionProposal(
 									defaultValue, begin, length, defaultValue.length(),
 									XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_DEFAULT),
 									defaultValue, null, proposedInfo, XMLRelevanceConstants.R_TAG_INSERTION);
@@ -940,7 +940,7 @@
 
 							String proposedInfo = getAdditionalInfo(parentDecl, elementDecl);
 							int relevance = isStrictCMNodeSuggestion ? XMLRelevanceConstants.R_STRICTLY_VALID_TAG_INSERTION : XMLRelevanceConstants.R_TAG_INSERTION;
-							CustomCompletionProposal proposal = new CustomCompletionProposal(
+							CustomCompletionProposal proposal = new MarkupCompletionProposal(
 									proposedText, contentAssistRequest.getReplacementBeginPosition(),
 									contentAssistRequest.getReplacementLength(), markupAdjustment,
 									image, tagname, null, proposedInfo, relevance);
@@ -1000,7 +1000,7 @@
 						// account for the &lt; and &gt;
 						int markupAdjustment = getContentGenerator().getMinimalStartTagLength(parent, ed);
 						String proposedInfo = getAdditionalInfo(null, ed);
-						CustomCompletionProposal proposal = new CustomCompletionProposal(
+						CustomCompletionProposal proposal = new MarkupCompletionProposal(
 								proposedText, contentAssistRequest.getReplacementBeginPosition(),
 								contentAssistRequest.getReplacementLength(), markupAdjustment, image,
 								tagname, null, proposedInfo, XMLRelevanceConstants.R_TAG_INSERTION);
@@ -1088,7 +1088,7 @@
 						}
 						int relevance = isStrictCMNodeSuggestion ? XMLRelevanceConstants.R_STRICTLY_VALID_TAG_NAME : XMLRelevanceConstants.R_TAG_NAME;
 						String proposedInfo = getAdditionalInfo(getCMElementDeclaration(parent), elementDecl);
-						CustomCompletionProposal proposal = new CustomCompletionProposal(
+						CustomCompletionProposal proposal = new MarkupCompletionProposal(
 								proposedText, contentAssistRequest.getReplacementBeginPosition(),
 								contentAssistRequest.getReplacementLength(), cursorAdjustment, image,
 								getRequiredName(parent, elementDecl), null, proposedInfo,
@@ -1144,7 +1144,7 @@
 					if (image == null) {
 						image = this.getGenericTagImage();
 					}
-					CustomCompletionProposal proposal = new CustomCompletionProposal(
+					CustomCompletionProposal proposal = new MarkupCompletionProposal(
 							proposedText, contentAssistRequest.getReplacementBeginPosition(),
 							contentAssistRequest.getReplacementLength(), cursorAdjustment, image,
 							getRequiredName(parent, ed), null, proposedInfo, XMLRelevanceConstants.R_TAG_NAME);
diff --git a/bundles/org.eclipse.wst.xml.ui/src/org/eclipse/wst/xml/ui/internal/contentassist/MarkupCompletionProposal.java b/bundles/org.eclipse.wst.xml.ui/src/org/eclipse/wst/xml/ui/internal/contentassist/MarkupCompletionProposal.java
new file mode 100644
index 0000000..6b675af
--- /dev/null
+++ b/bundles/org.eclipse.wst.xml.ui/src/org/eclipse/wst/xml/ui/internal/contentassist/MarkupCompletionProposal.java
@@ -0,0 +1,149 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * 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:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.ui.internal.contentassist;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextViewer;
+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.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.ui.texteditor.link.EditorLinkedModeUI;
+import org.eclipse.wst.sse.ui.internal.contentassist.CustomCompletionProposal;
+import org.eclipse.wst.xml.ui.internal.Logger;
+
+/**
+ * A completion proposal that is capable of establishing linked positions within
+ * inserted markup. 
+ *
+ */
+public class MarkupCompletionProposal extends CustomCompletionProposal {
+
+	private IRegion fSelectedRegion = null;
+
+	public MarkupCompletionProposal(String replacementString, int replacementOffset, int replacementLength, int cursorPosition, Image image, String displayString, IContextInformation contextInformation, String additionalProposalInfo, int relevance) {
+		super(replacementString, replacementOffset, replacementLength, cursorPosition, image, displayString, contextInformation, additionalProposalInfo, relevance);
+	}
+
+	public MarkupCompletionProposal(String replacementString, int replacementOffset, int replacementLength, int cursorPosition, Image image, String displayString, IContextInformation contextInformation, String additionalProposalInfo, int relevance, boolean updateReplacementLengthOnValidate) {
+		super(replacementString, replacementOffset, replacementLength, cursorPosition, image, displayString, null, contextInformation, additionalProposalInfo, relevance, updateReplacementLengthOnValidate);
+	}
+
+	public MarkupCompletionProposal(String replacementString, int replacementOffset, int replacementLength, int cursorPosition, Image image, String displayString, String alternateMatch, IContextInformation contextInformation, String additionalProposalInfo, int relevance, boolean updateReplacementLengthOnValidate) {
+		super(replacementString, replacementOffset, replacementLength, cursorPosition, image, displayString, alternateMatch, contextInformation, additionalProposalInfo, relevance, updateReplacementLengthOnValidate);
+	}
+
+	public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) {
+		super.apply(viewer, trigger, stateMask, offset);
+
+		getLinkedPositions(viewer);
+	}
+
+	/**
+	 * Sets up linked positions and installs them on the viewer.
+	 * 
+	 */
+	protected void getLinkedPositions(ITextViewer viewer) {
+		final String replacement = getReplacementString();
+		final IDocument document = viewer.getDocument();
+		final int length = replacement.length();
+		boolean inAttribute = false, hasGroup = false, inEndTag = false;
+		int offset = 0;
+		char attType = 0;
+		int exitPosition = -1;
+		LinkedModeModel model = new LinkedModeModel();
+
+		try {
+			for (int i = 0; i < length; i++) {
+				final char c = replacement.charAt(i);
+				switch (c) {
+					case '=':
+						break;
+					case '\'':
+					case '\"':
+						if (!inAttribute) {
+							offset = i;
+							attType = c;
+							inAttribute = true;
+						}
+						else {
+							// Found matching quotes establishing an attribute value region
+							if (attType == c && replacement.charAt(i - 1) != '\\') {
+								inAttribute = false; // Record position length
+								addPosition(model, document, getReplacementOffset() + offset + 1, i - offset - 1);
+								hasGroup = true;
+							}
+						}
+						break;
+					case '/':
+							if (!inAttribute) {
+								inEndTag = i > 0 && replacement.charAt(i - 1) == '<';
+							}
+						break;
+					case '>':
+						if (!inAttribute) {
+							if (i == length - 1) {
+								exitPosition = getReplacementOffset() + i + 1;
+								if (!inEndTag) { // Don't add a position within the end-tag
+									addPosition(model, document, getReplacementOffset() + i, 0); // position within start tag
+									hasGroup = true;
+								}
+							}
+							else {
+								addPosition(model, document, getReplacementOffset() + i, 0); // position within start tag
+								addPosition(model, document, getReplacementOffset() + i + 1, 0); // position after start tag
+								hasGroup = true;
+							}
+						}
+						break;
+				}
+			}
+			if (hasGroup) {
+				model.forceInstall();
+				final LinkedModeUI ui= new EditorLinkedModeUI(model, viewer);
+				ui.setExitPosition(viewer, exitPosition < 0 ? getReplacementOffset() + getReplacementLength() + replacement.length() - 1 : exitPosition, 0, Integer.MAX_VALUE);
+				ui.setCyclingMode(LinkedModeUI.CYCLE_WHEN_NO_PARENT);
+				ui.setDoContextInfo(true);
+				ui.enter();
+				fSelectedRegion = ui.getSelectedRegion();
+			}
+		}
+		catch (BadLocationException e) {
+			Logger.logException(e);
+		}
+	}
+
+	/**
+	 * Adds a {@link LinkedPosition} to its own position group. This group is then added to the model
+	 * @param model the linked model for this proposal
+	 * @param document the document the content assist is operating upon
+	 * @param offset the offset to establish the {@link LinkedPosition}
+	 * @param length the length of the {@link LinkedPosition}
+	 * @throws BadLocationException
+	 */
+	private void addPosition(LinkedModeModel model, IDocument document, int offset, int length) throws BadLocationException {
+		final LinkedPositionGroup group = new LinkedPositionGroup();
+		group.addPosition(new LinkedPosition(document, offset, length, LinkedPositionGroup.NO_STOP));
+		model.addGroup(group);
+	}
+
+	public Point getSelection(IDocument document) {
+		// Attempt to return the selection based on the selected region from the LinkedModeUI
+		if (fSelectedRegion == null)
+			return super.getSelection(document);
+		return new Point(fSelectedRegion.getOffset(), fSelectedRegion.getLength());
+	}
+}
\ No newline at end of file