Initial cut of generalized hyperlink feature
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextViewer.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextViewer.java
index 255a6f5..b229716 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextViewer.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextViewer.java
@@ -89,6 +89,8 @@
  * <li>{@link org.eclipse.jface.text.ITextViewerExtension5}since version 3.0
  * extending the visible region concept with explicit handling and conversation
  * of widget and model coordinates.</li>
+ * <li>{@link org.eclipse.jface.text.ITextViewerExtension6}since version 3.1
+ * extending the text viewer with the ability to detect hyperlinks.</li>
  * </ul>
  * 
  * Clients may implement this interface and its extension interfaces or use the
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextViewerExtension6.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextViewerExtension6.java
new file mode 100644
index 0000000..bed2905
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextViewerExtension6.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials 
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jface.text;
+
+import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
+
+/**
+ * Extension interface for {@link org.eclipse.jface.text.ITextViewer}. 
+ * Introduces the concept of text hyperlinks.
+ * <p>
+ * NOTE: This API is work in progress and may change before the final API freeze.
+ * </p>
+ *
+ * @see org.eclipse.jface.text.hyperlink.IHyperlink
+ * @see org.eclipse.jface.text.hyperlink.IHyperlinkDetector
+ * @since 3.1
+ */
+public interface ITextViewerExtension6 {
+	
+	/**
+	 * Sets this viewer's hyperlinkDetectors for the given content type.
+	 *
+	 * @param hyperlinkDetectors the new list of hyperlink detectors, must not be empty
+	 * @throws IllegalArgumentException if hyperlinkDetectors is <code>null</code> or empty 
+	 */
+	void setHyperlinkDetectors(IHyperlinkDetector[] hyperlinkDetectors) throws IllegalArgumentException;
+
+	/**
+	 * Sets whether hyperlinking is enabled or not.
+	 * 
+	 * @param state <code>true</code> if enabled 
+	 */
+	void setHyperlinksEnabled(boolean state);
+	
+	/**
+	 * Sets the hyperlink state mask.
+	 * 
+	 * @param hyperlinkStateMask the hyperlink state mask
+	 */
+	public void setHyperlinkStateMask(int hyperlinkStateMask);
+}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/DefaultHyperlinkController.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/DefaultHyperlinkController.java
new file mode 100644
index 0000000..b268acb
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/DefaultHyperlinkController.java
@@ -0,0 +1,490 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials 
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jface.text.hyperlink;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.StyleRange;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Cursor;
+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.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.PreferenceConverter;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+
+import org.eclipse.jface.text.Assert;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.DocumentEvent;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IDocumentListener;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextInputListener;
+import org.eclipse.jface.text.ITextPresentationListener;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.ITextViewerExtension2;
+import org.eclipse.jface.text.ITextViewerExtension4;
+import org.eclipse.jface.text.ITextViewerExtension5;
+import org.eclipse.jface.text.Position;
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.TextPresentation;
+
+
+/**
+ * The default hyperlink controller underlines the
+ * link and colors the line and the text with
+ * the given color.
+ * <p>
+ * NOTE: This API is work in progress and may change before the final API freeze.
+ * </p>
+ * 
+ * @since 3.1
+ */
+public class DefaultHyperlinkController implements IHyperlinkController, ITextPresentationListener, PaintListener, ITextInputListener, IDocumentListener, IPropertyChangeListener {
+
+	/**
+	 * A named preference that holds the color used for hyperlinks.
+	 * <p>
+	 * Value is of type <code>String</code>. A RGB color value encoded as a string
+	 * using class <code>PreferenceConverter</code>
+	 * </p>
+	 *
+	 * @see org.eclipse.jface.resource.StringConverter
+	 * @see org.eclipse.jface.preference.PreferenceConverter
+	 * @since 3.1
+	 */
+	public final static String HYPERLINK_COLOR= "hyperlinkColor"; //$NON-NLS-1$
+
+	
+	/** The text viewer. */
+	private ITextViewer fTextViewer;
+	/** The hand cursor. */
+	private Cursor fCursor;
+	/** The link color. */
+	private Color fColor;
+	/** Tells whether to dispose the color on uninstall. */
+	private boolean fDisposeColor;
+	/** The currently active region. */
+	private IRegion fActiveRegion;
+	/** The currently active style range as position. */
+	private Position fRememberedPosition;
+	/** The optional preference store */
+	private IPreferenceStore fPreferenceStore;
+
+
+	/**
+	 * Creates a new default hyperlink controller which uses
+	 * {@link #HYPERLINK_COLOR} to read the color from the given preference store.
+	 * 
+	 * @param store the preference store
+	 */
+	public DefaultHyperlinkController(IPreferenceStore store) {
+		fPreferenceStore= store;
+		fDisposeColor= true;
+	}
+	
+	/**
+	 * Creates a new default hyperlink controller.
+	 * 
+	 * @param color the hyperlink color, to be disposed by the caller
+	 */
+	public DefaultHyperlinkController(Color color) {
+		fDisposeColor= false;
+		fColor= color;
+	}
+	
+	/*
+	 * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlinkControl#canShowMultipleHyperlinks()
+	 * @since 3.1
+	 */
+	public boolean canShowMultipleHyperlinks() {
+		return false;
+	}
+	
+	/*
+	 * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlinkControl#activate(org.eclipse.jdt.internal.ui.javaeditor.IHyperlink[])
+	 * @since 3.1
+	 */
+	public void activate(IHyperlink[] hyperlinks) {
+		Assert.isLegal(hyperlinks != null && hyperlinks.length == 1);
+		highlightRegion(hyperlinks[0].getRegion());
+		activateCursor();
+	}
+	
+	/*
+	 * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlinkControl#deactivate()
+	 * @since 3.1
+	 */
+	public void deactivate() {
+		repairRepresentation();
+		fRememberedPosition= null;
+	}
+
+	/*
+	 * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlinkControl#install(org.eclipse.jface.text.ITextViewer)
+	 * @since 3.1
+	 */
+	public void install(ITextViewer textViewer) {
+		Assert.isNotNull(textViewer);
+		fTextViewer= textViewer;
+		fTextViewer.addTextInputListener(this);
+		if (fTextViewer instanceof ITextViewerExtension4)
+			((ITextViewerExtension4)fTextViewer).addTextPresentationListener(this);
+		
+		StyledText text= fTextViewer.getTextWidget();
+		if (text != null && !text.isDisposed()) {
+			text.addPaintListener(this);
+			if (fPreferenceStore != null)
+				fColor= createColor(fPreferenceStore, HYPERLINK_COLOR, text.getDisplay());
+		}
+		
+		if (fPreferenceStore != null)
+			fPreferenceStore.addPropertyChangeListener(this);
+	}
+
+	/*
+	 * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlinkControl#uninstall()
+	 * @since 3.1
+	 */
+	public void uninstall() {
+		fTextViewer.removeTextInputListener(this);
+		
+		if (fColor != null) {
+			if (fDisposeColor)
+				fColor.dispose();
+			fColor= null;
+		}
+		
+		if (fCursor != null) {
+			fCursor.dispose();
+			fCursor= null;
+		}
+		
+		StyledText text= fTextViewer.getTextWidget();
+		if (text != null && !text.isDisposed())
+			text.removePaintListener(this);
+
+		if (fTextViewer instanceof ITextViewerExtension4)
+			((ITextViewerExtension4)fTextViewer).removeTextPresentationListener(this);
+		fTextViewer= null;
+		
+		if (fPreferenceStore != null)
+			fPreferenceStore.removePropertyChangeListener(this);
+	}
+	
+	public void setColor(Color color) {
+		Assert.isNotNull(fTextViewer);
+		fColor= color;
+	}
+	
+	/*
+	 * @see org.eclipse.jface.text.ITextPresentationListener#applyTextPresentation(org.eclipse.jface.text.TextPresentation)
+	 * @since 3.1
+	 */
+	public void applyTextPresentation(TextPresentation textPresentation) {
+		if (fActiveRegion == null)
+			return;
+		IRegion region= textPresentation.getExtent();
+		if (fActiveRegion.getOffset() + fActiveRegion.getLength() >= region.getOffset() && region.getOffset() + region.getLength() > fActiveRegion.getOffset())
+			textPresentation.mergeStyleRange(new StyleRange(fActiveRegion.getOffset(), fActiveRegion.getLength(), fColor, null));
+	}
+	
+	private void highlightRegion(IRegion region) {
+
+		if (region.equals(fActiveRegion))
+			return;
+
+		repairRepresentation();
+		
+		StyledText text= fTextViewer.getTextWidget();
+		if (text == null || text.isDisposed())
+			return;
+
+		
+		// Underline
+		int offset= 0;
+		int length= 0;
+		if (fTextViewer instanceof ITextViewerExtension5) {
+			ITextViewerExtension5 extension= (ITextViewerExtension5)fTextViewer;
+			IRegion widgetRange= extension.modelRange2WidgetRange(region);
+			if (widgetRange == null)
+				return;
+				
+			offset= widgetRange.getOffset();
+			length= widgetRange.getLength();
+			
+		} else {
+			offset= region.getOffset() - fTextViewer.getVisibleRegion().getOffset();
+			length= region.getLength();
+		}
+		text.redrawRange(offset, length, false);
+		
+		// Invalidate region ==> apply text presentation
+		fActiveRegion= region;
+		if (fTextViewer instanceof ITextViewerExtension2)
+			((ITextViewerExtension2)fTextViewer).invalidateTextPresentation(region.getOffset(), region.getLength());
+		else
+			fTextViewer.invalidateTextPresentation();
+	}
+	
+	private void activateCursor() {
+		StyledText text= fTextViewer.getTextWidget();
+		if (text == null || text.isDisposed())
+			return;
+		Display display= text.getDisplay();
+		if (fCursor == null)
+			fCursor= new Cursor(display, SWT.CURSOR_HAND);
+		text.setCursor(fCursor);
+	}
+	
+	private void resetCursor() {
+		StyledText text= fTextViewer.getTextWidget();
+		if (text != null && !text.isDisposed())
+			text.setCursor(null);
+					
+		if (fCursor != null) {
+			fCursor.dispose();
+			fCursor= null;
+		}
+	}
+
+	private void repairRepresentation() {			
+
+		if (fActiveRegion == null)
+			return;
+		
+		int offset= fActiveRegion.getOffset();
+		int length= fActiveRegion.getLength();
+		fActiveRegion= null;
+			
+		resetCursor();
+		
+		// Invalidate ==> remove applied text presentation
+		if (fTextViewer instanceof ITextViewerExtension2)
+			((ITextViewerExtension2) fTextViewer).invalidateTextPresentation(offset, length);
+		else
+			fTextViewer.invalidateTextPresentation();
+		
+		// Remove underline
+		if (fTextViewer instanceof ITextViewerExtension5) {
+			ITextViewerExtension5 extension= (ITextViewerExtension5) fTextViewer;
+			offset= extension.modelOffset2WidgetOffset(offset);
+		} else {
+			offset -= fTextViewer.getVisibleRegion().getOffset();
+		}
+		try {
+			StyledText text= fTextViewer.getTextWidget();
+
+			text.redrawRange(offset, length, false);
+		} catch (IllegalArgumentException x) {
+			//	ignore - do not log
+		}
+	}
+
+	/*
+	 * @see PaintListener#paintControl(PaintEvent)
+	 */
+	public void paintControl(PaintEvent event) {	
+		if (fActiveRegion == null)
+			return;
+
+		StyledText text= fTextViewer.getTextWidget();
+		if (text == null || text.isDisposed())
+			return;
+			
+		int offset= 0;
+		int length= 0;
+
+		if (fTextViewer instanceof ITextViewerExtension5) {
+			
+			ITextViewerExtension5 extension= (ITextViewerExtension5)fTextViewer;
+			IRegion widgetRange= extension.modelRange2WidgetRange(fActiveRegion);
+			if (widgetRange == null)
+				return;
+				
+			offset= widgetRange.getOffset();
+			length= widgetRange.getLength();
+			
+		} else {
+			
+			IRegion region= fTextViewer.getVisibleRegion();			
+			if (!includes(region, fActiveRegion))
+				return;		    
+			
+			offset= fActiveRegion.getOffset() - region.getOffset();
+			length= fActiveRegion.getLength();
+		}
+		
+		// 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;
+		if (fColor != null && !fColor.isDisposed())
+			gc.setForeground(fColor);
+		else if (fColor == null) {
+			StyleRange style= text.getStyleRangeAtOffset(offset);
+			if (style != null)
+				gc.setForeground(style.foreground);
+		}
+		gc.drawLine(x1, y, x2, y);
+	}
+	
+	private 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 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 boolean includes(IRegion region, IRegion position) {
+		return
+			position.getOffset() >= region.getOffset() &&
+			position.getOffset() + position.getLength() <= region.getOffset() + region.getLength();
+	}
+	
+	/*
+	 * @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
+	 */
+	public void documentAboutToBeChanged(DocumentEvent event) {
+		if (fActiveRegion != null) {
+			fRememberedPosition= new Position(fActiveRegion.getOffset(), fActiveRegion.getLength());
+			try {
+				event.getDocument().addPosition(fRememberedPosition);
+			} catch (BadLocationException x) {
+				fRememberedPosition= null;
+			}
+		}
+	}
+
+	/*
+	 * @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent)
+	 */
+	public void documentChanged(DocumentEvent event) {
+		if (fRememberedPosition != null) {
+			if (!fRememberedPosition.isDeleted()) {
+				
+				event.getDocument().removePosition(fRememberedPosition);
+				fActiveRegion= new Region(fRememberedPosition.getOffset(), fRememberedPosition.getLength());
+				fRememberedPosition= null;
+				
+				   StyledText widget= fTextViewer.getTextWidget();
+				if (widget != null && !widget.isDisposed()) {
+					widget.getDisplay().asyncExec(new Runnable() {
+						public void run() {
+							deactivate();
+						}
+					});
+				}
+				
+			} else {
+				fActiveRegion= null;
+				fRememberedPosition= null;
+				deactivate();
+			}
+		}
+	}
+
+	/*
+	 * @see org.eclipse.jface.text.ITextInputListener#inputDocumentAboutToBeChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
+	 */
+	public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {
+		if (oldInput == null)
+			return;
+		deactivate();
+		oldInput.removeDocumentListener(this);
+	}
+
+	/*
+	 * @see org.eclipse.jface.text.ITextInputListener#inputDocumentChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
+	 */
+	public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
+		if (newInput == null)
+			return;
+		newInput.addDocumentListener(this);
+	}
+	
+	/**
+	 * Creates a color from the information stored in the given preference store.
+	 * 
+	 * @param store the preference store
+	 * @param key the key
+	 * @param display the display
+	 * @return the color or <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;
+	}
+
+	/*
+	 * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
+	 * @since 3.1
+	 */
+	public void propertyChange(PropertyChangeEvent event) {
+		if (!HYPERLINK_COLOR.equals(event.getProperty()))
+			return;
+		
+		if (fDisposeColor && fColor != null && !fColor.isDisposed())
+			fColor.dispose();
+		fColor= null;
+		
+		StyledText textWidget= fTextViewer.getTextWidget();
+		if (textWidget != null && !textWidget.isDisposed())
+			fColor= createColor(fPreferenceStore, HYPERLINK_COLOR, textWidget.getDisplay());
+	}
+}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/DefaultHyperlinkManager.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/DefaultHyperlinkManager.java
new file mode 100644
index 0000000..a8c8878
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/DefaultHyperlinkManager.java
@@ -0,0 +1,456 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials 
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.jface.text.hyperlink;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.MouseMoveListener;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+
+import org.eclipse.jface.text.Assert;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.ITextViewerExtension5;
+import org.eclipse.jface.text.Region;
+
+/**
+ * Default implementation of a hyperlink manager.
+ * <p>
+ * NOTE: This API is work in progress and may change before the final API freeze.
+ * </p>
+ * 
+ * @since 3.1
+ */
+public class DefaultHyperlinkManager implements IHyperlinkManager, KeyListener, MouseListener, MouseMoveListener, FocusListener {
+
+	/**
+	 * Detection strategy.
+	 */
+	private static final class DETECTION_STRATEGY {
+		
+		String fName;
+		
+		private DETECTION_STRATEGY(String name) {
+			fName= name;
+		}
+		
+		/*
+		 * @see java.lang.Object#toString()
+		 */
+		public String toString() {
+			return fName;
+		}
+	}
+	
+
+	/**
+	 * A named preference that controls if hyperlinks are turned on or off.
+	 * <p>
+	 * Value is of type <code>Boolean</code>.
+	 * </p>
+	 * 
+	 * @since 3.1
+	 */
+	public static final String HYPERLINKS_ENABLED= "hyperlinksEnabled"; //$NON-NLS-1$
+
+	/**
+	 * A named preference that controls the key modifier for hyperlinks.
+	 * <p>
+	 * Value is of type <code>String</code>.
+	 * </p>
+	 * 
+	 * @since 3.1
+	 */
+	public static final String HYPERLINK_KEY_MODIFIER= "hyperlinkKeyModifier"; //$NON-NLS-1$
+
+	/**
+	 * A named preference that controls the key modifier mask for hyperlinks.
+	 * The value is only used if the value of <code>EHYPERLINK_KEY_MODIFIER</code>
+	 * cannot be resolved to valid SWT modifier bits.
+	 * <p>
+	 * Value is of type <code>String</code>.
+	 * </p>
+	 * 
+	 * @see #HYPERLINK_KEY_MODIFIER
+	 * @since 3.1
+	 */
+	public static final String HYPERLINK_KEY_MODIFIER_MASK= "hyperlinkKeyModifierMask"; //$NON-NLS-1$
+
+	/**
+	 * The first detected hyperlink is passed to the
+	 * hyperlink controller and no further detector
+	 * is consulted.
+	 */
+	public static final DETECTION_STRATEGY FIRST= new DETECTION_STRATEGY("first"); //$NON-NLS-1$
+	
+	/**
+	 * All detected hyperlinks from all detectors are collected
+	 * and passed to the hyperlink controller.
+	 * <p>
+	 * This strategy is only allowed if {@link IHyperlinkController#canShowMultipleHyperlinks()}
+	 * returns <code>true</code>.
+	 * </p>
+	 */
+	public static final DETECTION_STRATEGY ALL= new DETECTION_STRATEGY("all"); //$NON-NLS-1$
+	
+	/**
+	 * All detected hyperlinks from all detectors are collected
+	 * and all those with the longest region are passed to the
+	 * hyperlink controller.
+	 * <p>
+	 * This strategy is only allowed if {@link IHyperlinkController#canShowMultipleHyperlinks()}
+	 * returns <code>true</code>.
+	 * </p>
+	 */
+	public static final DETECTION_STRATEGY LONGEST_REGION_ALL= new DETECTION_STRATEGY("all with same longest region"); //$NON-NLS-1$
+	
+	/**
+	 * All detected hyperlinks from all detectors are collected
+	 * and form all those with the longest region only the first
+	 * one is passed to the hyperlink controller.
+	 */
+	public static final DETECTION_STRATEGY LONGEST_REGION_FIRST= new DETECTION_STRATEGY("first with longest region"); //$NON-NLS-1$
+
+	
+	/** The text viewer on which this hyperlink manager works. */
+	private ITextViewer fTextViewer;
+	/** The session is active. */
+	private boolean fActive;
+	/** The key modifier mask. */
+	private int fKeyModifierMask;
+	/** The active hyperlinks. */
+	private IHyperlink[] fActiveHyperlinks;
+	/** The hyperlink detectors. */
+	private IHyperlinkDetector[] fHyperlinkDetectors;
+	/** The hyperlink controller. */
+	private IHyperlinkController fHyperlinkController;
+	/** The detection strategy. */
+	private final DETECTION_STRATEGY fDetectionStrategy;
+
+	
+	/**
+	 * Creates a new hyperlink manager.
+	 * 
+	 * @param detectionStrategy the detection strategy one of {{@link #ALL}, {@link #FIRST}, {@link #LONGEST_REGION_ALL}, {@link #LONGEST_REGION_FIRST}}
+	 */
+	public DefaultHyperlinkManager(DETECTION_STRATEGY detectionStrategy) {
+		Assert.isNotNull(detectionStrategy);
+		fDetectionStrategy= detectionStrategy;
+	}
+
+	/*
+	 * @see org.eclipse.jface.text.hyperlink.IHyperlinkManager#install(org.eclipse.jface.text.ITextViewer, org.eclipse.jface.text.hyperlink.IHyperlinkController, org.eclipse.jface.text.hyperlink.IHyperlinkDetector[], int)
+	 * @since 3.1
+	 */
+	public void install(ITextViewer textViewer, IHyperlinkController hyperlinkController, IHyperlinkDetector[] hyperlinkDetectors, int keyModifierMask) {
+		Assert.isNotNull(textViewer);
+		Assert.isNotNull(hyperlinkController);
+		fTextViewer= textViewer;
+		fHyperlinkController= hyperlinkController;
+		Assert.isLegal(!fHyperlinkController.canShowMultipleHyperlinks() && (fDetectionStrategy == FIRST || fDetectionStrategy == LONGEST_REGION_FIRST)); 
+		setHyperlinkDetectors(hyperlinkDetectors);
+		setKeyModifierMask(keyModifierMask);
+		
+		StyledText text= fTextViewer.getTextWidget();			
+		if (text == null || text.isDisposed())
+			return;
+		
+		text.addKeyListener(this);
+		text.addMouseListener(this);
+		text.addMouseMoveListener(this);
+		text.addFocusListener(this);
+		
+		fHyperlinkController.install(fTextViewer);
+	}
+	
+	/*
+	 * @see org.eclipse.jface.text.hyperlink.IHyperlinkManager#setHyperlinkDetectors(org.eclipse.jface.text.hyperlink.IHyperlinkDetector[])
+	 * @since 3.1
+	 */
+	public void setHyperlinkDetectors(IHyperlinkDetector[] hyperlinkDetectors) {
+		Assert.isTrue(hyperlinkDetectors != null && hyperlinkDetectors.length > 0);
+		if (fHyperlinkDetectors == null)
+			fHyperlinkDetectors= hyperlinkDetectors;
+		else {
+			synchronized (fHyperlinkDetectors) {
+				fHyperlinkDetectors= hyperlinkDetectors;
+			}
+		}
+	}
+	
+	/*
+	 * @see org.eclipse.jface.text.hyperlink.IHyperlinkManager#setKeyModifierMask(int)
+	 * @since 3.1
+	 */
+	public void setKeyModifierMask(int keyModifierMask) {
+		fKeyModifierMask= keyModifierMask;
+	}
+
+	/*
+	 * @see org.eclipse.jface.text.hyperlink.IHyperlinkManager#uninstall()
+	 * @since 3.1
+	 */
+	public void uninstall() {
+		deactivate();
+
+		StyledText text= fTextViewer.getTextWidget();
+		if (text != null && !text.isDisposed()) {
+			text.removeKeyListener(this);
+			text.removeMouseListener(this);
+			text.removeMouseMoveListener(this);
+			text.removeFocusListener(this);
+		}
+		fHyperlinkController.uninstall();
+		
+		fHyperlinkController= null;
+		fTextViewer= null;
+		fHyperlinkDetectors= null;
+	}
+
+	protected void deactivate() {
+		if (!fActive)
+			return;
+
+		fHyperlinkController.deactivate();
+		fActive= false;
+	}
+
+	protected IHyperlink[] findHyperlinks() {
+		int offset= getCurrentTextOffset();				
+		if (offset == -1)
+			return null;
+		
+		IRegion region= new Region(offset, 0);
+		List allHyperlinks= new ArrayList(fHyperlinkDetectors.length * 2);
+		synchronized (fHyperlinkDetectors) {
+			for (int i= 0, length= fHyperlinkDetectors.length; i < length; i++) {
+				IHyperlink[] hyperlinks= fHyperlinkDetectors[i].detectHyperlinks(fTextViewer, region);
+				if (hyperlinks == null)
+					continue;
+				
+				Assert.isLegal(hyperlinks.length > 0);
+				
+				if (fDetectionStrategy == FIRST) {
+					if (hyperlinks.length == 1)
+						return hyperlinks;
+					return new IHyperlink[] {hyperlinks[0]};
+				}
+				allHyperlinks.addAll(Arrays.asList(hyperlinks));
+			}
+		}
+		
+		if (allHyperlinks.isEmpty())
+			return null;
+		
+		if (fDetectionStrategy != ALL) {
+			int maxLength= computeLongestHyperlinkLength(allHyperlinks);
+			Iterator iter= new ArrayList(allHyperlinks).iterator();
+			while (iter.hasNext()) {
+				IHyperlink hyperlink= (IHyperlink)iter.next();
+				if (hyperlink.getRegion().getLength() < maxLength)
+					allHyperlinks.remove(hyperlink);
+			}
+		}
+		
+		if (fDetectionStrategy == LONGEST_REGION_FIRST)
+			return new IHyperlink[] {(IHyperlink)allHyperlinks.get(0)};
+		
+		return (IHyperlink[])allHyperlinks.toArray(new IHyperlink[allHyperlinks.size()]);
+		
+	}
+	
+	protected int computeLongestHyperlinkLength(List hyperlinks) {
+		Assert.isLegal(hyperlinks != null && !hyperlinks.isEmpty());
+		Iterator iter= hyperlinks.iterator();
+		int length= Integer.MIN_VALUE;
+		while (iter.hasNext()) {
+			IRegion region= ((IHyperlink)iter.next()).getRegion();
+			if (region.getLength() < length)
+				continue;
+			length= region.getLength();
+		}
+		return length;
+	}
+
+	protected int getCurrentTextOffset() {
+
+		try {					
+			StyledText text= fTextViewer.getTextWidget();			
+			if (text == null || text.isDisposed())
+				return -1;
+
+			Display display= text.getDisplay();				
+			Point absolutePosition= display.getCursorLocation();
+			Point relativePosition= text.toControl(absolutePosition);
+			
+			int widgetOffset= text.getOffsetAtLocation(relativePosition);
+			if (fTextViewer instanceof ITextViewerExtension5) {
+				ITextViewerExtension5 extension= (ITextViewerExtension5)fTextViewer;
+				return extension.widgetOffset2ModelOffset(widgetOffset);
+			}
+			
+			return widgetOffset + fTextViewer.getVisibleRegion().getOffset();
+
+		} catch (IllegalArgumentException e) {
+			return -1;
+		}			
+	}
+
+	/*
+	 * @see org.eclipse.swt.events.KeyListener#keyPressed(org.eclipse.swt.events.KeyEvent)
+	 */
+	public void keyPressed(KeyEvent event) {
+
+		if (fActive) {
+			deactivate();
+			return;	
+		}
+
+		if (event.keyCode != fKeyModifierMask) {
+			deactivate();
+			return;
+		}
+		
+		fActive= true;
+
+//			removed for #25871			
+//
+//			ITextViewer viewer= getSourceViewer();
+//			if (viewer == null)
+//				return;
+//			
+//			IRegion region= getCurrentTextRegion(viewer);
+//			if (region == null)
+//				return;
+//			
+//			highlightRegion(viewer, region);
+//			activateCursor(viewer);												
+	}
+
+	/*
+	 * @see org.eclipse.swt.events.KeyListener#keyReleased(org.eclipse.swt.events.KeyEvent)
+	 */
+	public void keyReleased(KeyEvent event) {
+		
+		if (!fActive)
+			return;
+
+		deactivate();				
+	}
+
+	/*
+	 * @see org.eclipse.swt.events.MouseListener#mouseDoubleClick(org.eclipse.swt.events.MouseEvent)
+	 */
+	public void mouseDoubleClick(MouseEvent e) {
+		
+	}
+	
+	/*
+	 * @see org.eclipse.swt.events.MouseListener#mouseDown(org.eclipse.swt.events.MouseEvent)
+	 */
+	public void mouseDown(MouseEvent event) {
+		
+		if (!fActive)
+			return;
+			
+		if (event.stateMask != fKeyModifierMask) {
+			deactivate();
+			return;	
+		}
+		
+		if (event.button != 1) {
+			deactivate();
+			return;	
+		}			
+	}
+
+	/*
+	 * @see org.eclipse.swt.events.MouseListener#mouseUp(org.eclipse.swt.events.MouseEvent)
+	 */
+	public void mouseUp(MouseEvent e) {
+
+		if (!fActive) {
+			fActiveHyperlinks= null;
+			return;
+		}
+			
+		if (e.button != 1)
+			fActiveHyperlinks= null;
+		
+		deactivate();
+
+		if (fActiveHyperlinks != null)
+			fActiveHyperlinks[0].open();
+	}
+	
+	/*
+	 * @see org.eclipse.swt.events.MouseMoveListener#mouseMove(org.eclipse.swt.events.MouseEvent)
+	 */
+	public void mouseMove(MouseEvent event) {
+		
+		if (event.widget instanceof Control && !((Control) event.widget).isFocusControl()) {
+			deactivate();
+			return;
+		}
+		
+		if (!fActive) {
+			if (event.stateMask != fKeyModifierMask)
+				return;
+			// modifier was already pressed
+			fActive= true;
+		}
+
+		StyledText text= fTextViewer.getTextWidget();
+		if (text == null || text.isDisposed()) {
+			deactivate();
+			return;
+		}
+			
+		if ((event.stateMask & SWT.BUTTON1) != 0 && text.getSelectionCount() != 0) {
+			deactivate();
+			return;
+		}
+	
+		fActiveHyperlinks= findHyperlinks();
+		if (fActiveHyperlinks == null || fActiveHyperlinks.length == 0) {
+			fHyperlinkController.deactivate();
+			return;
+		}
+		
+		fHyperlinkController.activate(fActiveHyperlinks);
+	}
+
+	/*
+	 * @see org.eclipse.swt.events.FocusListener#focusGained(org.eclipse.swt.events.FocusEvent)
+	 */
+	public void focusGained(FocusEvent e) {}
+
+	/*
+	 * @see org.eclipse.swt.events.FocusListener#focusLost(org.eclipse.swt.events.FocusEvent)
+	 */
+	public void focusLost(FocusEvent event) {
+		deactivate();
+	}
+}
\ No newline at end of file
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlink.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlink.java
new file mode 100644
index 0000000..e23322c
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlink.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials 
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jface.text.hyperlink;
+
+import org.eclipse.jface.text.IRegion;
+
+
+/**
+ * Represents a hyperlink.
+ * <p>
+ * NOTE: This API is work in progress and may change before the final API freeze.
+ * </p>
+ * 
+ * @since 3.1
+ */
+public interface IHyperlink {
+	IRegion getRegion();
+	
+	/**
+	 * Optional label for this type of hyperlink.
+	 * <p>
+	 * This type label can be used by {@link IHyperlinkController}s
+	 * which show several hyperlinks at once.
+	 * </p>
+	 * 
+	 * @return the type label or <code>null</code> if none.
+	 */
+	String getTypeLabel();
+	
+	/**
+	 * Optional text for this hyperlink.
+	 * <p>
+	 * This can be used in situations where there are
+	 * several targets for the same hyperlink location.
+	 * </p>
+	 * 
+	 * @return the text or <code>null</code> if none.
+	 */
+	String getText();
+	
+	/**
+	 * Opens the given hyperlink.
+	 */
+	void open();
+}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlinkController.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlinkController.java
new file mode 100644
index 0000000..a773dd1
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlinkController.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials 
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jface.text.hyperlink;
+
+import org.eclipse.jface.text.ITextViewer;
+
+
+/**
+ * A hyperlink controller displays the given hyperlinks on
+ * the installed text viewer and allows to select one
+ * on of the hyperlinks.
+ * <p>
+ * NOTE: This API is work in progress and may change before the final API freeze.
+ * </p>
+ * 
+ * @since 3.1
+ */
+public interface IHyperlinkController {
+
+	/**
+	 * Tells whether this controller is able to handle
+	 * more than one hyperlink.
+	 * 
+	 * @return <code>true</code> if this controller can handle more than one hyperlink
+	 */
+	boolean canShowMultipleHyperlinks();
+	
+	/**
+	 * Activates the this hyperlink controller on the installed
+	 * text viewer for the given hyperlinks.
+	 * 
+	 * @param hyperlinks the hyperlinks to show
+	 * @throws IllegalArgumentException if
+	 * 			<ul>
+	 * 				<li><code>hyperlinks</code> is empty</li>
+	 * 				<li>{@link #canShowMultipleHyperlinks()} returns <code>false</code> and <code>hyperlinks</code> contains more than one element</li>
+	 * 			</ul>  
+	 */
+	void activate(IHyperlink[] hyperlinks) throws IllegalArgumentException;
+
+	/**
+	 * Deactivates this hyperlink controller.
+	 */
+	void deactivate();
+
+	/**
+	 * Installs this hyperlink controller on the given text viewer.
+	 * 
+	 * @param textViewer the text viewer
+	 */
+	void install(ITextViewer textViewer);
+
+	/**
+	 * Uninstalls this hyperlink controller.
+	 */
+	void uninstall();
+}
\ No newline at end of file
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlinkDetector.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlinkDetector.java
new file mode 100644
index 0000000..eb70de3
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlinkDetector.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials 
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jface.text.hyperlink;
+
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextViewer;
+
+
+/**
+ * A hyperlink detector tries to find a hyperlink at
+ * a given location in a given text viewer.
+ * <p>
+ * Hyperlink detectors can be contributed via the
+ * <code>hyperlinkDetectors</code> extension point.
+ * </p>
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ * <p>
+ * NOTE: This API is work in progress and may change before the final API freeze.
+ * </p>
+ * 
+ * @since 3.1
+ */
+public interface IHyperlinkDetector {
+	
+	/**
+	 * Tries to detect hyperlinks for the given region in
+	 * the given text viewer and returns them.
+	 * <p>
+	 * In most of the cases only one hyperlink should be returned.
+	 * </p>
+	 * @param textViewer the text viewer on which the hover popup should be shown
+	 * @param region the text range in the text viewer which is used to detect the hyperlinks
+	 * @return the hyperlinks or <code>null</code> if no hyperlink was detected
+	 */
+	IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region);
+
+}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlinkManager.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlinkManager.java
new file mode 100644
index 0000000..5d34924
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/IHyperlinkManager.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials 
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.jface.text.hyperlink;
+
+import org.eclipse.jface.text.ITextViewer;
+
+/**
+ * A hyperlink manager for a text viewer detects
+ * {@link org.eclipse.jface.text.hyperlink.IHyperlink hyperlinks} with the given
+ * {@link org.eclipse.jface.text.hyperlink.IHyperlinkDetector hyperlink detectors}
+ * and displays the links using a {@link org.eclipse.jface.text.hyperlink.IHyperlinkController hyperlink controller}.
+ * <p>
+ * NOTE: This API is work in progress and may change before the final API freeze.
+ * </p>
+ * 
+ * @since 3.1
+ */
+public interface IHyperlinkManager {
+
+	/**
+	 * Installs this hyperlink manager on the given text viewer.
+	 * 
+	 * @param textViewer the text viewer
+	 * @param hyperlinkController the hyperlink controller
+	 * @param hyperlinkDetectors the hyperlink detectors
+	 * @param keyModifierMask the modifier mask which in combination with the left mouse button triggers the hyperlink mode
+	 */
+	void install(ITextViewer textViewer, IHyperlinkController hyperlinkController, IHyperlinkDetector[] hyperlinkDetectors, int keyModifierMask);
+
+	/**
+	 * Uninstalls this hyperlink manager from the text viewer. 
+	 */
+	void uninstall();
+	
+	/**
+	 * Sets the hyperlink detectors for this hyperlink manager.
+	 * <p>
+	 * It is allowed to called this method after this
+	 * hyperlink manger has been installed.
+	 * </p>
+	 * 
+	 * @param hyperlinkDetectors
+	 */
+	void setHyperlinkDetectors(IHyperlinkDetector[] hyperlinkDetectors);
+
+	/**
+	 * Sets the SWT key modifier mask which in combination
+	 * with the left mouse button triggers the hyperlink mode.
+	 * <p>
+	 * It is allowed to call this method after this
+	 * hyperlink manger has been installed.
+	 * </p>
+	 *  
+	 * @param modifierMask the modifier mask
+	 */
+	void setKeyModifierMask(int modifierMask);
+}
\ No newline at end of file
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/URLHyperlink.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/URLHyperlink.java
new file mode 100644
index 0000000..d91a135
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/URLHyperlink.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials 
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jface.text.hyperlink;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.program.Program;
+
+import org.eclipse.jface.text.Assert;
+import org.eclipse.jface.text.IRegion;
+
+
+/**
+ * URL hyperlink.
+ * <p>
+ * NOTE: This API is work in progress and may change before the final API freeze.
+ * </p>
+ * 
+ * @since 3.1
+ */
+public class URLHyperlink implements IHyperlink {
+
+	private String fURLString;
+	private IRegion fRegion;
+
+	/**
+	 * Creates a new URL hyperlink.
+	 * 
+	 * @param region
+	 * @param urlString
+	 */
+	public URLHyperlink(IRegion region, String urlString) {
+		Assert.isNotNull(urlString);
+		Assert.isNotNull(region);
+		
+		fRegion= region;
+		fURLString= urlString;
+	}
+
+	/*
+	 * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlink#getRegion()
+	 * @since 3.1
+	 */
+	public IRegion getRegion() {
+		return fRegion;
+	}
+
+	/*
+	 * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlink#open()
+	 * @since 3.1
+	 */
+	public void open() {
+		if (fURLString != null) {
+			String platform= SWT.getPlatform();
+			if ("motif".equals(platform) || "gtk".equals(platform)) { //$NON-NLS-1$ //$NON-NLS-2$
+				Program program= Program.findProgram("html"); //$NON-NLS-1$
+				if (program == null)
+					program= Program.findProgram("htm"); //$NON-NLS-1$
+				if (program != null)
+					program.execute(fURLString);
+			} else
+				Program.launch(fURLString);
+			fURLString= null;
+			return;
+		}
+	}
+
+	/*
+	 * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlink#getTypeLabel()
+	 * @since 3.1
+	 */
+	public String getTypeLabel() {
+		return null;
+	}
+
+	/*
+	 * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlink#getText()
+	 * @since 3.1
+	 */
+	public String getText() {
+		return null;
+	}
+}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/URLHyperlinkDetector.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/URLHyperlinkDetector.java
new file mode 100644
index 0000000..add1b35
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/URLHyperlinkDetector.java
@@ -0,0 +1,114 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials 
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.jface.text.hyperlink;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.StringTokenizer;
+
+import org.eclipse.jface.text.Assert;
+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.Region;
+
+
+
+/**
+ * URL hyperlink detector.
+ * <p>
+ * NOTE: This API is work in progress and may change before the final API freeze.
+ * </p>
+ * 
+ * @since 3.1
+ */
+public class URLHyperlinkDetector implements IHyperlinkDetector {
+
+	private ITextViewer fTextViewer;
+
+	
+	/**
+	 * Creates a new URL hyperlink detector.
+	 *  
+	 * @param textViewer the text viewer in which to detect the hyperlink
+	 */
+	public URLHyperlinkDetector(ITextViewer textViewer) {
+		Assert.isNotNull(textViewer);
+		fTextViewer= textViewer;
+	}
+
+	/*
+	 * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlinkDetector#detectHyperlink(org.eclipse.jface.text.IRegion)
+	 * @since 3.1
+	 */
+	public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region) {
+		if (region == null || fTextViewer == null)
+			return null;
+
+		IDocument document= fTextViewer.getDocument();
+
+		int offset= region.getOffset();
+		
+		String urlString= null;
+		if (document == null)
+			return null;
+		
+		IRegion lineInfo;
+		String line;
+		try {
+			lineInfo= document.getLineInformationOfOffset(offset);
+			line= document.get(lineInfo.getOffset(), lineInfo.getLength());
+		} catch (BadLocationException ex) {
+			return null;
+		}
+		
+		int offsetInLine= offset - lineInfo.getOffset();
+		
+		int urlSeparatorOffset= line.indexOf("://"); //$NON-NLS-1$
+		if (urlSeparatorOffset < 0)
+			return null;
+		
+		// URL protocol (left to "://")
+		int urlOffsetInLine= urlSeparatorOffset;
+		char ch;
+		do {
+			urlOffsetInLine--;
+			ch= ' ';
+			if (urlOffsetInLine > -1)
+				ch= line.charAt(urlOffsetInLine);
+		} while (!Character.isWhitespace(ch));
+		urlOffsetInLine++;
+		
+		// Right to "://"
+		StringTokenizer tokenizer= new StringTokenizer(line.substring(urlSeparatorOffset + 3));
+		if (!tokenizer.hasMoreTokens())
+			return null;
+		
+		int urlLength= tokenizer.nextToken().length() + 3 + urlSeparatorOffset - urlOffsetInLine;
+		if (offsetInLine < urlOffsetInLine || offsetInLine > urlOffsetInLine + urlLength)
+			return null;
+		
+		// Set and validate URL string
+		try {
+			urlString= line.substring(urlOffsetInLine, urlOffsetInLine + urlLength);
+			new URL(urlString);
+		} catch (MalformedURLException ex) {
+			urlString= null;
+			return null;
+		}
+		
+		IRegion urlRegion= new Region(lineInfo.getOffset() + urlOffsetInLine, urlLength);
+		return new IHyperlink[] {new URLHyperlink(urlRegion, urlString)};
+	}
+
+}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/SourceViewer.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/SourceViewer.java
index 03cfe6f..b584a8b 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/SourceViewer.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/SourceViewer.java
@@ -43,6 +43,7 @@
 import org.eclipse.jface.text.formatter.IContentFormatter;
 import org.eclipse.jface.text.formatter.IContentFormatterExtension;
 import org.eclipse.jface.text.formatter.IFormattingContext;
+import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
 import org.eclipse.jface.text.information.IInformationPresenter;
 import org.eclipse.jface.text.presentation.IPresentationReconciler;
 import org.eclipse.jface.text.projection.ChildDocument;
@@ -348,6 +349,14 @@
 		
 		setHoverControlCreator(configuration.getInformationControlCreator(this));
 		
+		if (configuration.getHyperlinksEnabled(this)) {
+			setHyperlinkController(configuration.getHyperlinkController(this));
+			setHyperlinkStateMask(configuration.getHyperlinkStateMask(this));
+			IHyperlinkDetector[] hyperlinkDetectors= configuration.getHyperlinkDetectors(this);
+			if (hyperlinkDetectors != null)
+				setHyperlinkDetectors(hyperlinkDetectors);
+		}
+		
 		// install content type specific plug-ins
 		String[] types= configuration.getConfiguredContentTypes(this);
 		for (int i= 0; i < types.length; i++) {
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/SourceViewerConfiguration.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/SourceViewerConfiguration.java
index b81849c..5af2704 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/SourceViewerConfiguration.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/SourceViewerConfiguration.java
@@ -11,8 +11,11 @@
 package org.eclipse.jface.text.source;
 
 
+import org.eclipse.swt.SWT;
 import org.eclipse.swt.widgets.Shell;
 
+import org.eclipse.jface.preference.IPreferenceStore;
+
 import org.eclipse.jface.text.DefaultAutoIndentStrategy;
 import org.eclipse.jface.text.DefaultInformationControl;
 import org.eclipse.jface.text.DefaultTextDoubleClickStrategy;
@@ -28,6 +31,10 @@
 import org.eclipse.jface.text.IUndoManager;
 import org.eclipse.jface.text.contentassist.IContentAssistant;
 import org.eclipse.jface.text.formatter.IContentFormatter;
+import org.eclipse.jface.text.hyperlink.DefaultHyperlinkController;
+import org.eclipse.jface.text.hyperlink.IHyperlinkController;
+import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
+import org.eclipse.jface.text.hyperlink.URLHyperlinkDetector;
 import org.eclipse.jface.text.information.IInformationPresenter;
 import org.eclipse.jface.text.presentation.IPresentationReconciler;
 import org.eclipse.jface.text.presentation.PresentationReconciler;
@@ -324,4 +331,58 @@
 	public String getConfiguredDocumentPartitioning(ISourceViewer sourceViewer) {
 		return IDocumentExtension3.DEFAULT_PARTITIONING;
 	}
+	
+	/**
+	 * Returns the whether hyperlinks are enabled in the
+	 * given source viewer.
+	 * This implementation always returns the <code>true</code>.
+	 *
+	 * @param sourceViewer the source viewer to be configured by this configuration
+	 * @return <code>true</code> if hyperlinks are enabled 
+	 * @since 3.1
+	 */
+	public boolean getHyperlinksEnabled(ISourceViewer sourceViewer) {
+		return true;
+	}
+
+	/**
+	 * Returns the hyperlink detectors which be used to detect hyperlinks
+	 * in the given source viewer. This
+	 * implementation always returns an array with an URL hyperlink detector.
+	 *
+	 * @param sourceViewer the source viewer to be configured by this configuration
+	 * @return an array with hyperlink detectors or <code>null</code> if no hyperlink support should be installed
+	 * @since 3.1
+	 */
+	public IHyperlinkDetector[] getHyperlinkDetectors(ISourceViewer sourceViewer) {
+		if (sourceViewer == null)
+			return null;
+		
+		return new IHyperlinkDetector[] {new URLHyperlinkDetector(sourceViewer)};
+	}
+
+	/**
+	 * Returns the hyperlink controller for the given source viewer.
+	 * This implementation always returns the {@link DefaultHyperlinkController}.
+	 * 
+	 * @param sourceViewer the source viewer to be configured by this configuration
+	 * @return the hyperlink controller or <code>null</code> if no hyperlink support should be installed
+	 * @since 3.1
+	 */
+	public IHyperlinkController getHyperlinkController(ISourceViewer sourceViewer) {
+		return new DefaultHyperlinkController((IPreferenceStore)null);
+	}
+	
+	/**
+	 * Returns the SWT event state mask which in combination
+	 * with the left mouse button activates hyperlinking.
+	 * This implementation always returns the {@link SWT#MOD1}.
+	 *
+	 * @param sourceViewer the source viewer to be configured by this configuration
+	 * @return the state mask for 
+	 * @since 3.1
+	 */
+	public int getHyperlinkStateMask(ISourceViewer sourceViewer) {
+		return SWT.CTRL;
+	}
 }