blob: 4d8ab9836f13300cc52052b033d71be2235a2810 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008 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.html.ui.internal.hyperlink;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
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.ITextViewerExtension5;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.text.hyperlink.AbstractHyperlinkDetector;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.wst.html.core.internal.provisional.HTML40Namespace;
import org.eclipse.wst.html.core.internal.validate.ModuleCoreSupport;
import org.eclipse.wst.html.ui.internal.HTMLUIMessages;
import org.eclipse.wst.html.ui.internal.HTMLUIPlugin;
import org.eclipse.wst.html.ui.internal.Logger;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
import org.eclipse.wst.sse.core.utils.StringUtils;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.ibm.icu.util.StringTokenizer;
public class AnchorHyperlinkDetector extends AbstractHyperlinkDetector {
static class ExternalElementHyperlink implements IHyperlink {
private String fAnchorName = null;
private Element fBaseElement = null;
private Display fDisplay = null;
private IRegion fHyperlinkRegion = null;
/**
* @param hyperlinkRegion
* @param anchorName
*/
public ExternalElementHyperlink(Display display, IRegion hyperlinkRegion, String anchorName, Element baseElement) {
super();
fDisplay = display;
fHyperlinkRegion = hyperlinkRegion;
fAnchorName = anchorName;
fBaseElement = baseElement;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.hyperlink.IHyperlink#open()
*/
IStatus _open() {
if (fBaseElement instanceof IDOMNode) {
StringTokenizer tokenizer = new StringTokenizer(fAnchorName, "#"); //$NON-NLS-1$
String filename = null;
String anchorName = null;
if (tokenizer.hasMoreTokens()) {
try {
filename = tokenizer.nextToken();
anchorName = tokenizer.nextToken();
}
catch (NoSuchElementException e) {
// poorly formed value
}
}
if (filename != null && anchorName != null) {
// System.out.println(filename + ":" + anchorName + "-" +
// fBaseElement);
IPath basePath = new Path(((IDOMNode) fBaseElement).getModel().getBaseLocation());
if (basePath.segmentCount() > 1) {
IPath resolved = ModuleCoreSupport.resolve(basePath, filename);
IFile targetFile = ResourcesPlugin.getWorkspace().getRoot().getFile(resolved);
if (targetFile.isAccessible()) {
IStructuredModel model = null;
int start = -1;
int end = -1;
try {
model = StructuredModelManager.getModelManager().getModelForRead(targetFile);
if (model instanceof IDOMModel) {
NodeList anchors = ((IDOMModel) model).getDocument().getElementsByTagNameNS("*", HTML40Namespace.ElementName.A); //$NON-NLS-1$
for (int i = 0; i < anchors.getLength() && start < 0; i++) {
Node item = anchors.item(i);
Node nameNode = item.getAttributes().getNamedItem(HTML40Namespace.ATTR_NAME_NAME);
if (nameNode == null)
nameNode = item.getAttributes().getNamedItem(HTML40Namespace.ATTR_NAME_ID);
if (nameNode != null) {
String name = nameNode.getNodeValue();
if (anchorName.equals(name) && nameNode instanceof IndexedRegion) {
start = ((IndexedRegion) nameNode).getStartOffset();
end = ((IndexedRegion) nameNode).getEndOffset();
}
}
}
anchors = ((IDOMModel) model).getDocument().getElementsByTagName(HTML40Namespace.ElementName.A);
for (int i = 0; i < anchors.getLength() && start < 0; i++) {
Node item = anchors.item(i);
Node nameNode = item.getAttributes().getNamedItem(HTML40Namespace.ATTR_NAME_NAME);
if (nameNode == null)
nameNode = item.getAttributes().getNamedItem(HTML40Namespace.ATTR_NAME_ID);
if (nameNode != null) {
String name = nameNode.getNodeValue();
if (anchorName.equals(name) && nameNode instanceof IndexedRegion) {
start = ((IndexedRegion) nameNode).getStartOffset();
end = ((IndexedRegion) nameNode).getEndOffset();
}
}
}
anchors = ((IDOMModel) model).getDocument().getElementsByTagName("*"); //$NON-NLS-1$
for (int i = 0; i < anchors.getLength() && start < 0; i++) {
Node item = anchors.item(i);
Node nameNode = item.getAttributes().getNamedItem(HTML40Namespace.ATTR_NAME_NAME);
if (nameNode == null)
nameNode = item.getAttributes().getNamedItem(HTML40Namespace.ATTR_NAME_ID);
if (nameNode != null) {
String name = nameNode.getNodeValue();
if (anchorName.equals(name) && nameNode instanceof IndexedRegion) {
start = ((IndexedRegion) nameNode).getStartOffset();
end = ((IndexedRegion) nameNode).getEndOffset();
}
}
}
}
return open(basePath.toString(), targetFile, start, end);
}
catch (Exception e) {
Logger.logException(e);
return new Status(IStatus.ERROR, HTMLUIPlugin.ID, e.getMessage());
}
finally {
if (model != null)
model.releaseFromRead();
}
}
}
}
}
return Status.OK_STATUS;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jface.text.hyperlink.IHyperlink#getHyperlinkRegion()
*/
public IRegion getHyperlinkRegion() {
return fHyperlinkRegion;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.hyperlink.IHyperlink#getHyperlinkText()
*/
public String getHyperlinkText() {
return NLS.bind(HTMLUIMessages.Open, fAnchorName);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.hyperlink.IHyperlink#getTypeLabel()
*/
public String getTypeLabel() {
return null;
}
public void open() {
scheduleOpen();
}
/**
* @param targetFile
* @param start
* @param end
*/
private IStatus open(String base, IFile targetFile, int start, int end) throws CoreException, PartInitException {
IMarker temporaryMarker = null;
try {
IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
IEditorPart editor = IDE.openEditor(page, targetFile);
if (0 <= start && start <= end) {
temporaryMarker = targetFile.createMarker(IMarker.BOOKMARK);
temporaryMarker.setAttribute(IMarker.MESSAGE, base);
temporaryMarker.setAttribute(IMarker.CHAR_START, start);
temporaryMarker.setAttribute(IMarker.CHAR_END, end);
IDE.gotoMarker(editor, temporaryMarker);
}
return Status.OK_STATUS;
}
finally {
if (temporaryMarker != null)
try {
temporaryMarker.delete();
}
catch (CoreException e) {
Logger.logException(e);
}
}
}
void scheduleOpen() {
Job opener = new UIJob(fDisplay, fAnchorName) {
public IStatus runInUIThread(IProgressMonitor monitor) {
return _open();
}
};
opener.setSystem(true);
opener.setUser(false);
opener.schedule();
}
}
/**
* Links to the given target node within the text viewer. The target node
* is expected to implement IndexedNode and appear in that text viewer
* (i.e. same editor).
*
*/
static class InternalElementHyperlink implements IHyperlink {
private IRegion fHyperlinkRegion;
private Node fTarget = null;
private ITextViewer fViewer = null;
/**
*
*/
public InternalElementHyperlink(ITextViewer textViewer, IRegion hyperlinkRegion, Node targetNode) {
fHyperlinkRegion = hyperlinkRegion;
fTarget = targetNode;
fViewer = textViewer;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jface.text.hyperlink.IHyperlink#getHyperlinkRegion()
*/
public IRegion getHyperlinkRegion() {
return fHyperlinkRegion;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.hyperlink.IHyperlink#getHyperlinkText()
*/
public String getHyperlinkText() {
if (fTarget instanceof IndexedRegion) {
try {
int line = fViewer.getDocument().getLineOfOffset(((IndexedRegion) fTarget).getStartOffset()) + 1;
return NLS.bind(HTMLUIMessages.Hyperlink_line, new String[]{fTarget.getNodeName(), fTarget.getNodeValue(), String.valueOf(line)});
}
catch (BadLocationException e) {
Logger.logException(e);
}
}
return NLS.bind(HTMLUIMessages.Open, fTarget.getNodeName());
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.hyperlink.IHyperlink#getTypeLabel()
*/
public String getTypeLabel() {
return null;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.hyperlink.IHyperlink#open()
*/
public void open() {
if (fTarget instanceof IndexedRegion) {
int offset = ((IndexedRegion) fTarget).getStartOffset();
if (fViewer instanceof ITextViewerExtension5) {
offset = ((ITextViewerExtension5) fViewer).modelOffset2WidgetOffset(offset);
}
fViewer.getSelectionProvider().setSelection(new TextSelection(offset, 0));
fViewer.revealRange(offset, 0);
}
}
}
public AnchorHyperlinkDetector() {
super();
}
private void addHyperLinkForHref(ITextViewer textViewer, IRegion linkRegion, Element element, String hrefValue, List links, Node anchor) {
Node nameNode = anchor.getAttributes().getNamedItem(HTML40Namespace.ATTR_NAME_HREF);
if (nameNode != null) {
String name = nameNode.getNodeValue();
if (hrefValue.equals(name) && nameNode instanceof IndexedRegion) {
links.add(new InternalElementHyperlink(textViewer, linkRegion, nameNode));
}
}
}
private void addHyperLinkForName(ITextViewer textViewer, IRegion linkRegion, Element element, String anchorName, List links, Node anchor) {
Node nameNode = anchor.getAttributes().getNamedItem(HTML40Namespace.ATTR_NAME_NAME);
if (nameNode != null) {
String name = nameNode.getNodeValue();
if (anchorName.equals(name) && nameNode instanceof IndexedRegion) {
links.add(new InternalElementHyperlink(textViewer, linkRegion, nameNode));
}
}
nameNode = anchor.getAttributes().getNamedItem(HTML40Namespace.ATTR_NAME_ID);
if (nameNode != null) {
String name = nameNode.getNodeValue();
if (anchorName.equals(name) && nameNode instanceof IndexedRegion) {
links.add(new InternalElementHyperlink(textViewer, linkRegion, nameNode));
}
}
}
/**
* @param documentRegion
* @param valueRegion
* @return
*/
private IRegion createHyperlinkRegion(IStructuredDocumentRegion documentRegion, ITextRegion valueRegion) {
return new Region(documentRegion.getStartOffset(valueRegion), valueRegion.getTextLength());
}
// link to anchors with the given name (value includes the '#')
IHyperlink[] createHyperlinksToAnchorNamed(ITextViewer textViewer, IRegion hyperlinkRegion, Element element, String anchorName, boolean canShowMultipleHyperlinks) {
List links = new ArrayList(1);
// >1 guards the substring-ing
if (anchorName.length() > 1 && anchorName.startsWith("#")) { //$NON-NLS-1$
// an anchor in this document
NodeList anchors = null;//element.getOwnerDocument().getElementsByTagNameNS("*", HTML40Namespace.ElementName.A); //$NON-NLS-1$
String internalAnchorName = anchorName.substring(1);
// for (int i = 0; i < anchors.getLength(); i++) {
// addHyperLinkForName(textViewer, hyperlinkRegion, element, internalAnchorName, links, anchors.item(i));
// }
// anchors = element.getOwnerDocument().getElementsByTagName(HTML40Namespace.ElementName.A);
// for (int i = 0; i < anchors.getLength(); i++) {
// addHyperLinkForName(textViewer, hyperlinkRegion, element, internalAnchorName, links, anchors.item(i));
// }
anchors = element.getOwnerDocument().getElementsByTagName("*"); //$NON-NLS-1$
for (int i = 0; i < anchors.getLength(); i++) {
addHyperLinkForName(textViewer, hyperlinkRegion, element, internalAnchorName, links, anchors.item(i));
}
}
else {
// another file, possibly very slow to compute ahead of time
links.add(new ExternalElementHyperlink(textViewer.getTextWidget().getDisplay(), hyperlinkRegion, anchorName, element));
}
if (!links.isEmpty()) {
return (IHyperlink[]) links.toArray(new IHyperlink[links.size()]);
}
return null;
}
// link to anchors that link to this target
IHyperlink[] createReferrerHyperlinks(ITextViewer textViewer, IRegion hyperlinkRegion, Element element, String nameValue, boolean canShowMultipleHyperlinks) {
List links = new ArrayList(1);
if (nameValue.length() > 0) {
String target = "#" + nameValue; //$NON-NLS-1$
NodeList anchors = null;//element.getOwnerDocument().getElementsByTagNameNS("*", HTML40Namespace.ElementName.A); //$NON-NLS-1$
// for (int i = 0; i < anchors.getLength(); i++) {
// addHyperLinkForHref(textViewer, hyperlinkRegion, element, target, links, anchors.item(i));
// }
// anchors = element.getOwnerDocument().getElementsByTagName(HTML40Namespace.ElementName.A);
// for (int i = 0; i < anchors.getLength(); i++) {
// addHyperLinkForHref(textViewer, hyperlinkRegion, element, target, links, anchors.item(i));
// }
anchors = element.getOwnerDocument().getElementsByTagName("*"); //$NON-NLS-1$
for (int i = 0; i < anchors.getLength(); i++) {
addHyperLinkForHref(textViewer, hyperlinkRegion, element, target, links, anchors.item(i));
}
}
if (!links.isEmpty()) {
return (IHyperlink[]) links.toArray(new IHyperlink[links.size()]);
}
return null;
}
public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region, boolean canShowMultipleHyperlinks) {
if (textViewer != null && region != null) {
IDocument document = textViewer.getDocument();
if (document != null) {
Node currentNode = getCurrentNode(document, region.getOffset());
if (currentNode != null && currentNode.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) currentNode;
IStructuredDocumentRegion documentRegion = ((IStructuredDocument) document).getRegionAtCharacterOffset(region.getOffset());
ITextRegion textRegion = documentRegion.getRegionAtCharacterOffset(region.getOffset());
ITextRegion nameRegion = null;
ITextRegion valueRegion = null;
String name = null;
String value = null;
if (DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE.equals(textRegion.getType())) {
ITextRegionList regions = documentRegion.getRegions();
/*
* Could use 2, but there needs to be the tag open
* and name regions
*/
int index = regions.indexOf(textRegion);
if (index >= 4) {
nameRegion = regions.get(index - 2);
valueRegion = textRegion;
name = documentRegion.getText(nameRegion);
value = StringUtils.strip(documentRegion.getText(valueRegion));
}
}
else if (DOMRegionContext.XML_TAG_ATTRIBUTE_NAME.equals(textRegion.getType())) {
ITextRegionList regions = documentRegion.getRegions();
int index = regions.indexOf(textRegion);
// minus 3 to leave equal and value regions
if (index <= regions.size() - 3) {
nameRegion = textRegion;
valueRegion = regions.get(index + 2);
name = documentRegion.getText(nameRegion);
value = StringUtils.strip(documentRegion.getText(valueRegion));
}
}
if (name != null && value != null) {
if (HTML40Namespace.ATTR_NAME_HREF.equalsIgnoreCase(name) && value.indexOf("#") >= 0) { //$NON-NLS-1$
return createHyperlinksToAnchorNamed(textViewer, createHyperlinkRegion(documentRegion, valueRegion), element, value, canShowMultipleHyperlinks);
}
if (HTML40Namespace.ATTR_NAME_NAME.equalsIgnoreCase(name)||HTML40Namespace.ATTR_NAME_ID.equalsIgnoreCase(name)) {
return createReferrerHyperlinks(textViewer, createHyperlinkRegion(documentRegion, valueRegion), element, value, canShowMultipleHyperlinks);
}
}
}
}
}
return null;
}
/**
* Returns the node the cursor is currently on in the document. null if no
* node is selected
*
* @param offset
* @return Node either element, doctype, text, or null
*/
private Node getCurrentNode(IDocument document, int offset) {
// get the current node at the offset (returns either: element,
// doctype, text)
IndexedRegion inode = null;
IStructuredModel sModel = null;
try {
sModel = StructuredModelManager.getModelManager().getExistingModelForRead(document);
if (sModel != null) {
inode = sModel.getIndexedRegion(offset);
if (inode == null) {
inode = sModel.getIndexedRegion(offset - 1);
}
}
}
finally {
if (sModel != null)
sModel.releaseFromRead();
}
if (inode instanceof Node) {
return (Node) inode;
}
return null;
}
}