Committed Benno's patch to fix bug 215473: [navigation] Show more then one hyperlink per modifier key
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/JFaceTextUtil.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/JFaceTextUtil.java
index 8ecd6d3..cf09010 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/JFaceTextUtil.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/JFaceTextUtil.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2006, 2007 IBM Corporation and others.
+ * Copyright (c) 2006, 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
@@ -11,6 +11,10 @@
 package org.eclipse.jface.text;
 
 import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Control;
 
 import org.eclipse.jface.text.source.ILineRange;
 import org.eclipse.jface.text.source.LineRange;
@@ -263,7 +267,7 @@
 	 * Returns <code>true</code> if the widget displays the entire contents, i.e. it cannot
 	 * be vertically scrolled.
 	 * 
-	 * @param widget the widget 
+	 * @param widget the widget
 	 * @return <code>true</code> if the widget displays the entire contents, i.e. it cannot
 	 *         be vertically scrolled, <code>false</code> otherwise
 	 */
@@ -276,4 +280,76 @@
 		return lastPossiblePixel <= lastVisiblePixel;
 	}
 
+	/**
+	 * Determines the graphical area covered by the given text region in
+	 * the given viewer.
+	 *
+	 * @param region the region whose graphical extend must be computed
+	 * @param textViewer the text viewer containing the region
+	 * @return the graphical extend of the given region in the given viewer
+	 * 
+	 * @since 3.4
+	 */
+	public static Rectangle computeArea(IRegion region, ITextViewer textViewer) {
+		int start= 0;
+		int end= 0;
+		IRegion widgetRegion= modelRange2WidgetRange(region, textViewer);
+		if (widgetRegion != null) {
+			start= widgetRegion.getOffset();
+			end= start + widgetRegion.getLength();
+		}
+		
+		StyledText styledText= textViewer.getTextWidget();
+		Rectangle bounds;
+		if (end > 0 && start < end)
+			bounds= styledText.getTextBounds(start, end - 1);
+		else {
+			Point loc= styledText.getLocationAtOffset(start);
+			bounds= new Rectangle(loc.x, loc.y, getAverageCharWidth(textViewer.getTextWidget()), styledText.getLineHeight(start));
+		}
+		
+		return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);
+	}
+
+	/**
+	 * Translates a given region of the text viewer's document into
+	 * the corresponding region of the viewer's widget.
+	 *
+	 * @param region the document region
+	 * @param textViewer the viewer containing the region
+	 * @return the corresponding widget region
+	 * 
+	 * @since 3.4
+	 */
+	private static IRegion modelRange2WidgetRange(IRegion region, ITextViewer textViewer) {
+		if (textViewer instanceof ITextViewerExtension5) {
+			ITextViewerExtension5 extension= (ITextViewerExtension5) textViewer;
+			return extension.modelRange2WidgetRange(region);
+		}
+		
+		IRegion visibleRegion= textViewer.getVisibleRegion();
+		int start= region.getOffset() - visibleRegion.getOffset();
+		int end= start + region.getLength();
+		if (end > visibleRegion.getLength())
+			end= visibleRegion.getLength();
+		
+		return new Region(start, end - start);
+	}
+
+	/**
+	 * Returns the average character width of the given control's font.
+	 * 
+	 * @param control the control to calculate the average char width for
+	 * @return the average character width of the controls font
+	 * 
+	 * @since 3.4
+	 */
+	public static int getAverageCharWidth(Control control) {
+		GC gc= new GC(control);
+		gc.setFont(control.getFont());
+		int increment= gc.getFontMetrics().getAverageCharWidth();
+		gc.dispose();
+		return increment;
+	}
+
 }
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewer.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewer.java
index 6c2a979..d1ce70a 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewer.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewer.java
@@ -20,8 +20,6 @@
 import java.util.Set;
 import java.util.regex.PatternSyntaxException;
 
-import org.eclipse.core.runtime.Assert;
-
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.LineBackgroundEvent;
 import org.eclipse.swt.custom.LineBackgroundListener;
@@ -61,13 +59,9 @@
 import org.eclipse.swt.widgets.Listener;
 import org.eclipse.swt.widgets.ScrollBar;
 
+import org.eclipse.core.runtime.Assert;
+
 import org.eclipse.jface.internal.text.NonDeletingPositionUpdater;
-import org.eclipse.jface.text.hyperlink.HyperlinkManager;
-import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
-import org.eclipse.jface.text.hyperlink.IHyperlinkDetectorExtension;
-import org.eclipse.jface.text.hyperlink.IHyperlinkPresenter;
-import org.eclipse.jface.text.projection.ChildDocument;
-import org.eclipse.jface.text.projection.ChildDocumentManager;
 import org.eclipse.jface.viewers.IPostSelectionProvider;
 import org.eclipse.jface.viewers.ISelection;
 import org.eclipse.jface.viewers.ISelectionChangedListener;
@@ -75,6 +69,14 @@
 import org.eclipse.jface.viewers.SelectionChangedEvent;
 import org.eclipse.jface.viewers.Viewer;
 
+import org.eclipse.jface.text.hyperlink.HyperlinkManager;
+import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
+import org.eclipse.jface.text.hyperlink.IHyperlinkDetectorExtension;
+import org.eclipse.jface.text.hyperlink.IHyperlinkPresenter;
+import org.eclipse.jface.text.hyperlink.HyperlinkManager.DETECTION_STRATEGY;
+import org.eclipse.jface.text.projection.ChildDocument;
+import org.eclipse.jface.text.projection.ChildDocumentManager;
+
 
 /**
  * SWT based implementation of {@link ITextViewer} and its extension interfaces.
@@ -3230,11 +3232,7 @@
 	 * @return the average character width of this viewer's widget
 	 */
 	final protected int getAverageCharWidth() {
-		GC gc= new GC(fTextWidget);
-		gc.setFont(fTextWidget.getFont());
-		int increment= gc.getFontMetrics().getAverageCharWidth();
-		gc.dispose();
-		return increment;
+		return JFaceTextUtil.getAverageCharWidth(getTextWidget());
 	}
 
 	/*
@@ -5366,7 +5364,8 @@
 	 */
 	private void ensureHyperlinkManagerInstalled() {
 		if (fHyperlinkDetectors != null && fHyperlinkDetectors.length > 0 && fHyperlinkPresenter != null && fHyperlinkManager == null) {
-			fHyperlinkManager= new HyperlinkManager(HyperlinkManager.FIRST);
+			DETECTION_STRATEGY strategy= fHyperlinkPresenter.canShowMultipleHyperlinks() ? HyperlinkManager.ALL : HyperlinkManager.FIRST;
+			fHyperlinkManager= new HyperlinkManager(strategy);
 			fHyperlinkManager.install(this, fHyperlinkPresenter, fHyperlinkDetectors, fHyperlinkStateMask);
 		}
 	}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewerHoverManager.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewerHoverManager.java
index a7b5455..264f4af 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewerHoverManager.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewerHoverManager.java
@@ -15,7 +15,7 @@
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;
-
+import org.eclipse.jface.text.information.IInformationProviderExtension2;
 import org.eclipse.swt.custom.StyledText;
 import org.eclipse.swt.events.MouseEvent;
 import org.eclipse.swt.events.MouseMoveListener;
@@ -23,8 +23,6 @@
 import org.eclipse.swt.graphics.Rectangle;
 import org.eclipse.swt.widgets.Display;
 
-import org.eclipse.jface.text.information.IInformationProviderExtension2;
-
 
 /**
  * This manager controls the layout, content, and visibility of an information
@@ -145,7 +143,7 @@
 			return;
 		}
 
-		final Rectangle area= computeArea(region);
+		final Rectangle area= JFaceTextUtil.computeArea(region, fTextViewer);
 		if (area == null || area.isEmpty()) {
 			setInformation(null, null);
 			return;
@@ -277,57 +275,6 @@
 		}
 	}
 
-	/**
-	 * Determines graphical area covered by the given text region.
-	 *
-	 * @param region the region whose graphical extend must be computed
-	 * @return the graphical extend of the given region
-	 */
-	private Rectangle computeArea(IRegion region) {
-
-		int start= 0;
-		int end= 0;
-		IRegion widgetRegion= modelRange2WidgetRange(region);
-		if (widgetRegion != null) {
-			start= widgetRegion.getOffset();
-			end= start + widgetRegion.getLength();
-		}
-
-		StyledText styledText= fTextViewer.getTextWidget();
-		Rectangle bounds;
-		if (end > 0 && start < end)
-			bounds= styledText.getTextBounds(start, end - 1);
-		else {
-			Point loc= styledText.getLocationAtOffset(start);
-			bounds= new Rectangle(loc.x, loc.y, fTextViewer.getAverageCharWidth(), styledText.getLineHeight(start));
-		}
-		
-		return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);
-	}
-
-	/**
-	 * Translates a given region of the text viewer's document into
-	 * the corresponding region of the viewer's widget.
-	 *
-	 * @param region the document region
-	 * @return the corresponding widget region
-	 * @since 2.1
-	 */
-	private IRegion modelRange2WidgetRange(IRegion region) {
-		if (fTextViewer instanceof ITextViewerExtension5) {
-			ITextViewerExtension5 extension= (ITextViewerExtension5) fTextViewer;
-			return extension.modelRange2WidgetRange(region);
-		}
-
-		IRegion visibleRegion= fTextViewer.getVisibleRegion();
-		int start= region.getOffset() - visibleRegion.getOffset();
-		int end= start + region.getLength();
-		if (end > visibleRegion.getLength())
-			end= visibleRegion.getLength();
-
-		return new Region(start, end - start);
-	}
-
 	/*
 	 * @see org.eclipse.jface.text.AbstractInformationControlManager#showInformationControl(org.eclipse.swt.graphics.Rectangle)
 	 */
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/MultipleHyperlinkPresenter.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/MultipleHyperlinkPresenter.java
new file mode 100644
index 0000000..16081c1
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/hyperlink/MultipleHyperlinkPresenter.java
@@ -0,0 +1,721 @@
+/*******************************************************************************
+ * 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.jface.text.hyperlink;
+
+import com.ibm.icu.text.MessageFormat;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseMoveListener;
+import org.eclipse.swt.events.ShellAdapter;
+import org.eclipse.swt.events.ShellEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableItem;
+
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.util.Geometry;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.IOpenListener;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.OpenEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.Viewer;
+
+import org.eclipse.jface.text.AbstractInformationControl;
+import org.eclipse.jface.text.AbstractInformationControlManager;
+import org.eclipse.jface.text.IInformationControl;
+import org.eclipse.jface.text.IInformationControlCreator;
+import org.eclipse.jface.text.IInformationControlExtension2;
+import org.eclipse.jface.text.IInformationControlExtension3;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextHover;
+import org.eclipse.jface.text.ITextHoverExtension;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.IWidgetTokenKeeper;
+import org.eclipse.jface.text.IWidgetTokenKeeperExtension;
+import org.eclipse.jface.text.IWidgetTokenOwner;
+import org.eclipse.jface.text.IWidgetTokenOwnerExtension;
+import org.eclipse.jface.text.JFaceTextUtil;
+import org.eclipse.jface.text.Region;
+
+
+/**
+ * A hyperlink presenter capable of showing multiple hyperlinks in a hover.
+ * 
+ * @since 3.4
+ */
+public class MultipleHyperlinkPresenter extends DefaultHyperlinkPresenter {
+	
+	/**
+	 * An information control capable of showing a list of hyperlinks. The hyperlinks can be opened.
+	 */
+	private static class LinkListInformationControl extends AbstractInformationControl implements IInformationControlExtension2 {
+		
+		private static final class LinkConentenProvider implements IStructuredContentProvider {
+			
+			/*
+			 * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
+			 */
+			public Object[] getElements(Object inputElement) {
+				return (Object[]) inputElement;
+			}
+			
+			/*
+			 * @see org.eclipse.jface.viewers.IContentProvider#dispose()
+			 */
+			public void dispose() {
+			}
+			
+			/*
+			 * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object)
+			 */
+			public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+			}
+		}
+		
+		private static final class LinkLabelProvider extends ColumnLabelProvider {
+			
+			private final IHyperlink fDefaultLink;
+			
+			public LinkLabelProvider(IHyperlink defaultLink) {
+				fDefaultLink= defaultLink;
+			}
+			
+			/*
+			 * @see org.eclipse.jface.viewers.ColumnLabelProvider#getText(java.lang.Object)
+			 */
+			public String getText(Object element) {
+				IHyperlink link= (IHyperlink) element;
+				
+				String text= link.getHyperlinkText();
+				if (text == null)
+					text= HyperlinkMessages.getString("LinkListInformationControl.unknownLink"); //$NON-NLS-1$
+					
+				if (link == fDefaultLink) {
+					text= MessageFormat.format(HyperlinkMessages.getString("LinkListInformationControl.defaultLinkPattern"), new Object[] { text }); //$NON-NLS-1$
+				}
+				
+				return text;
+			}
+			
+
+//			/*
+//			 * @see org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider#getStyledText(java.lang.Object)
+//			 */
+//			public StyledStringBuilder getStyledText(Object element) {
+//				String text= getText(element);
+//				StyledStringBuilder result= new StyledStringBuilder(text);
+//				if (element == fDefaultLink) {
+//					result.setStyle(text.length() - 10, 10, new Styler() {
+//						public void applyStyles(TextStyle textStyle) {
+//							textStyle.foreground= Display.getDefault().getSystemColor(SWT.COLOR_DARK_GRAY);
+//						}
+//					});
+//				}
+//
+//				return result;
+//			}
+		}
+		
+		private final MultipleHyperlinkHoverManager fManager;
+		
+		private IHyperlink[] fInput;
+		private Composite fParent;
+		private Table fTable;
+		
+		/**
+		 * Creates a link list information control with the given shell as parent.
+		 *
+		 * @param parentShell the parent shell
+		 * @param manager
+		 */
+		public LinkListInformationControl(Shell parentShell, MultipleHyperlinkHoverManager manager) {
+			super(parentShell, true);
+			fManager= manager;
+			
+			create();
+		}
+		
+		/*
+		 * @see org.eclipse.jface.text.IInformationControl#setInformation(java.lang.String)
+		 */
+		public void setInformation(String information) {
+			//replaced by IInformationControlExtension2#setInput(java.lang.Object)
+		}
+		
+		/*
+		 * @see org.eclipse.jface.text.IInformationControlExtension2#setInput(java.lang.Object)
+		 */
+		public void setInput(Object input) {
+			fInput= (IHyperlink[]) input;
+			deferredCreateContent(fParent);
+		}
+		
+		/*
+		 * @see org.eclipse.jface.text.AbstractInformationControl#createContent(org.eclipse.swt.widgets.Composite)
+		 */
+		protected void createContent(Composite parent) {
+			fParent= new Composite(parent, SWT.NONE);
+			
+			GridLayout layout= new GridLayout(1, false);
+			layout.marginWidth= 0;
+			fParent.setLayout(layout);
+			fParent.setBackground(fParent.getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
+		}
+		
+		/*
+		 * @see org.eclipse.jface.text.AbstractInformationControl#computeSizeHint()
+		 */
+		public Point computeSizeHint() {
+			Point preferedSize= getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
+			
+			Point constraints= getSizeConstraints();
+			if (constraints == null)
+				return preferedSize;
+		
+			if (fTable.getVerticalBar() == null || fTable.getHorizontalBar() == null)
+				return Geometry.min(constraints, preferedSize);
+			
+			int scrollBarWidth= fTable.getVerticalBar().getSize().x;
+			int scrollBarHeight= fTable.getHorizontalBar().getSize().y;
+			
+			int width;
+			if (preferedSize.y - scrollBarHeight < constraints.y) {
+				width= preferedSize.x - scrollBarWidth;
+			} else {
+				width= Math.min(preferedSize.x, constraints.x);
+			}
+			
+			int height;
+			if (preferedSize.x - scrollBarWidth < constraints.x) {
+				height= preferedSize.y - scrollBarHeight;
+			} else {
+				height= Math.min(preferedSize.y, constraints.y);
+			}
+			
+			return new Point(width, height);
+		}
+		
+		private void deferredCreateContent(Composite parent) {
+			fTable= new Table(parent, SWT.SINGLE);
+			GridData data= new GridData(SWT.FILL, SWT.FILL, true, true);
+			fTable.setLayoutData(data);
+			fTable.setLinesVisible(false);
+			fTable.setHeaderVisible(false);
+			fTable.setBackground(parent.getBackground());
+			
+			final TableViewer viewer= new TableViewer(fTable);
+			viewer.setContentProvider(new LinkConentenProvider());
+			viewer.setLabelProvider(new LinkLabelProvider(fInput[0]));
+			viewer.setInput(fInput);
+			
+			registerQuickViewTableListeners(viewer);
+			
+			getShell().addShellListener(new ShellAdapter() {
+				
+				/*
+				 * @see org.eclipse.swt.events.ShellAdapter#shellActivated(org.eclipse.swt.events.ShellEvent)
+				 */
+				public void shellActivated(ShellEvent e) {
+					if (viewer.getTable().getSelectionCount() == 0) {
+						viewer.getTable().setSelection(0);
+					}
+					
+					viewer.getTable().setFocus();
+				}
+			});
+		}
+		
+		private void registerQuickViewTableListeners(final TableViewer viewer) {
+			final Table table= viewer.getTable();
+			
+			table.addMouseMoveListener(new MouseMoveListener() {
+				TableItem fLastItem= null;
+				
+				public void mouseMove(MouseEvent e) {
+					if (table.equals(e.getSource())) {
+						Object o= table.getItem(new Point(e.x, e.y));
+						if (o instanceof TableItem) {
+							TableItem item= (TableItem) o;
+							if (!o.equals(fLastItem)) {
+								fLastItem= (TableItem) o;
+								table.setSelection(new TableItem[] { fLastItem });
+							} else if (e.y < table.getItemHeight() / 4) {
+								// Scroll up
+								int index= table.indexOf(item);
+								if (index > 0) {
+									fLastItem= table.getItem(index - 1);
+									table.setSelection(new TableItem[] { fLastItem });
+								}
+							} else if (e.y > table.getBounds().height - table.getItemHeight() / 4) {
+								// Scroll down
+								int index= table.indexOf(item);
+								if (index < table.getItemCount() - 1) {
+									fLastItem= table.getItem(index + 1);
+									table.setSelection(new TableItem[] { fLastItem });
+								}
+							}
+						}
+					}
+				}
+			});
+			
+			table.addMouseListener(new MouseAdapter() {
+				public void mouseUp(MouseEvent e) {
+					
+					if (table.getSelectionCount() < 1)
+						return;
+					
+					if (e.button != 1)
+						return;
+					
+					if (table.equals(e.getSource())) {
+						Object o= table.getItem(new Point(e.x, e.y));
+						TableItem selection= table.getSelection()[0];
+						if (selection.equals(o)) {
+							openLink((IHyperlink) selection.getData());
+						}
+					}
+				}
+			});
+			
+			viewer.addOpenListener(new IOpenListener() {
+				public void open(OpenEvent event) {
+					StructuredSelection selection= (StructuredSelection) event.getSelection();
+					openLink((IHyperlink) selection.getFirstElement());
+				}
+			});
+		}
+		
+		/*
+		 * @see org.eclipse.jface.text.IInformationControlExtension#hasContents()
+		 */
+		public boolean hasContents() {
+			return true;
+		}
+
+		/**
+		 * Opens the given link.
+		 * 
+		 * @param link the link to open
+		 */
+		private void openLink(IHyperlink link) {
+			fManager.hideInformationControl();
+			link.open();
+		}
+	}
+	
+	private class MultipleHyperlinkHover implements ITextHover, ITextHoverExtension {
+		
+		/**
+		 * @see org.eclipse.jface.text.ITextHover#getHoverInfo(org.eclipse.jface.text.ITextViewer, org.eclipse.jface.text.IRegion)
+		 * @deprecated
+		 */
+		public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) {
+			return null;
+		}
+		
+		/*
+		 * @see org.eclipse.jface.text.ITextHover#getHoverRegion(org.eclipse.jface.text.ITextViewer, int)
+		 */
+		public IRegion getHoverRegion(ITextViewer textViewer, int offset) {
+			return fSubjectRegion;
+		}
+		
+		/*
+		 * @see org.eclipse.jface.text.ITextHoverExtension2#getHoverInfo2(org.eclipse.jface.text.ITextViewer, org.eclipse.jface.text.IRegion)
+		 */
+		public Object getHoverInfo2(ITextViewer textViewer, IRegion hoverRegion) {
+			return fHyperlinks;
+		}
+		
+		/*
+		 * @see org.eclipse.jface.text.ITextHoverExtension#getHoverControlCreator()
+		 */
+		public IInformationControlCreator getHoverControlCreator() {
+			return new IInformationControlCreator() {
+				public IInformationControl createInformationControl(Shell parent) {
+					return new LinkListInformationControl(parent, fManager);
+				}
+			};
+		}
+	}
+	
+	private static class MultipleHyperlinkHoverManager extends AbstractInformationControlManager implements IWidgetTokenKeeper, IWidgetTokenKeeperExtension {
+		
+		private class Closer implements IInformationControlCloser, Listener, KeyListener {
+			
+			private Control fSubjectControl;
+			private Display fDisplay;
+			private IInformationControl fControl;
+			private Rectangle fSubjectArea;
+			
+			/*
+			 * @see org.eclipse.jface.text.AbstractInformationControlManager.IInformationControlCloser#setInformationControl(org.eclipse.jface.text.IInformationControl)
+			 */
+			public void setInformationControl(IInformationControl control) {
+				fControl= control;
+			}
+			
+			/*
+			 * @see org.eclipse.jface.text.AbstractInformationControlManager.IInformationControlCloser#setSubjectControl(org.eclipse.swt.widgets.Control)
+			 */
+			public void setSubjectControl(Control subject) {
+				fSubjectControl= subject;
+			}
+			
+			/*
+			 * @see org.eclipse.jface.text.AbstractInformationControlManager.IInformationControlCloser#start(org.eclipse.swt.graphics.Rectangle)
+			 */
+			public void start(Rectangle subjectArea) {
+				fSubjectArea= subjectArea;
+				
+				fDisplay= fSubjectControl.getDisplay();
+				if (!fDisplay.isDisposed()) {
+					fDisplay.addFilter(SWT.FocusOut, this);
+					fDisplay.addFilter(SWT.MouseMove, this);
+					fTextViewer.getTextWidget().addKeyListener(this);
+				}
+			}
+			
+			/*
+			 * @see org.eclipse.jface.text.AbstractInformationControlManager.IInformationControlCloser#stop()
+			 */
+			public void stop() {
+				if (fDisplay != null && !fDisplay.isDisposed()) {
+					fDisplay.removeFilter(SWT.FocusOut, this);
+					fDisplay.removeFilter(SWT.MouseMove, this);
+					fTextViewer.getTextWidget().removeKeyListener(this);
+				}
+				
+				fSubjectArea= null;
+			}
+			
+			/*
+			 * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event)
+			 */
+			public void handleEvent(Event event) {
+				switch (event.type) {
+					case SWT.FocusOut:
+						if (!fControl.isFocusControl())
+							disposeInformationControl();
+						break;
+					case SWT.MouseMove:
+						handleMouseMove(event);
+						break;
+				}
+			}
+			
+			/**
+			 * Handle mouse movement events.
+			 * 
+			 * @param event the event
+			 */
+			private void handleMouseMove(Event event) {
+				if (!(event.widget instanceof Control))
+					return;
+				
+				if (fControl.isFocusControl())
+					return;
+				
+				Control eventControl= (Control) event.widget;
+				
+				//transform coordinates to subject control:
+				Point mouseLoc= event.display.map(eventControl, fSubjectControl, event.x, event.y);
+				
+				if (fSubjectArea.contains(mouseLoc))
+					return;
+				
+				if (inKeepUpZone(mouseLoc.x, mouseLoc.y, ((IInformationControlExtension3) fControl).getBounds()))
+					return;
+				
+				hideInformationControl();
+			}
+			
+			/**
+			 * Tests whether a given mouse location is within the keep-up zone.
+			 * The hover should not be hidden as long as the mouse stays inside this zone.
+			 * 
+			 * @param x the x coordinate, relative to the <em>subject control</em>
+			 * @param y the y coordinate, relative to the <em>subject control</em>
+			 * @param controlBounds the bounds of the current control
+			 * 
+			 * @return <code>true</code> iff the mouse event occurred in the keep-up zone
+			 */
+			private boolean inKeepUpZone(int x, int y, Rectangle controlBounds) {
+				Rectangle iControlBounds= fSubjectControl.getDisplay().map(null, fSubjectControl, controlBounds);
+				Rectangle totalBounds= Geometry.copy(iControlBounds);
+				
+				// FIXME: should maybe use convex hull, not bounding box
+				totalBounds.add(fSubjectArea);
+				return totalBounds.contains(x, y);
+			}
+			
+			/*
+			 * @see org.eclipse.swt.events.KeyListener#keyPressed(org.eclipse.swt.events.KeyEvent)
+			 */
+			public void keyPressed(KeyEvent e) {
+			}
+			
+			/*
+			 * @see org.eclipse.swt.events.KeyListener#keyReleased(org.eclipse.swt.events.KeyEvent)
+			 */
+			public void keyReleased(KeyEvent e) {
+				if (e.keyCode == SWT.ARROW_DOWN) {
+					fControl.setFocus();
+					return;
+				}
+				
+				hideInformationControl();
+			}
+		}
+		
+		/**
+		 * Priority of the hover managed by this manager.
+		 * Default value: One higher then for the hovers
+		 * managed by TextViewerHoverManager.
+		 */
+		private static final int WIDGET_TOKEN_PRIORITY= 1;
+		
+		private final MultipleHyperlinkHover fHover;
+		private final ITextViewer fTextViewer;
+		private Closer fCloser;
+		
+		/**
+		 * Create a new MultipleHyperlinkHoverManager. The MHHM can show and hide
+		 * the given MultipleHyperlinkHover inside the given ITextViewer.
+		 * 
+		 * @param hover the hover to manage
+		 * @param viewer the viewer to show the hover in
+		 */
+		public MultipleHyperlinkHoverManager(MultipleHyperlinkHover hover, ITextViewer viewer) {
+			super(hover.getHoverControlCreator());
+			
+			fHover= hover;
+			fTextViewer= viewer;
+			
+			fCloser= new Closer();
+			setCloser(fCloser);
+		}
+		
+		/*
+		 * @see org.eclipse.jface.text.AbstractInformationControlManager#computeInformation()
+		 */
+		protected void computeInformation() {
+			IRegion region= fHover.getHoverRegion(fTextViewer, -1);
+			if (region == null) {
+				setInformation(null, null);
+				return;
+			}
+			
+			Rectangle area= JFaceTextUtil.computeArea(region, fTextViewer);
+			if (area == null || area.isEmpty()) {
+				setInformation(null, null);
+				return;
+			}
+			
+			Object information= fHover.getHoverInfo2(fTextViewer, region);
+			setCustomInformationControlCreator(fHover.getHoverControlCreator());
+			setInformation(information, area);
+		}
+		
+		/*
+		 * @see org.eclipse.jface.text.AbstractInformationControlManager#showInformationControl(org.eclipse.swt.graphics.Rectangle)
+		 */
+		protected void showInformationControl(Rectangle subjectArea) {
+			if (fTextViewer instanceof IWidgetTokenOwnerExtension) {
+				if (((IWidgetTokenOwnerExtension) fTextViewer).requestWidgetToken(this, WIDGET_TOKEN_PRIORITY))
+					super.showInformationControl(subjectArea);
+			} else if (fTextViewer instanceof IWidgetTokenOwner) {
+				if (((IWidgetTokenOwner) fTextViewer).requestWidgetToken(this))
+					super.showInformationControl(subjectArea);
+			} else {
+				super.showInformationControl(subjectArea);
+			}
+		}
+		
+		/*
+		 * @see org.eclipse.jface.text.AbstractInformationControlManager#hideInformationControl()
+		 */
+		protected void hideInformationControl() {
+			super.hideInformationControl();
+			
+			if (fTextViewer instanceof IWidgetTokenOwner) {
+				((IWidgetTokenOwner) fTextViewer).releaseWidgetToken(this);
+			}
+		}
+		
+		/*
+		 * @see org.eclipse.jface.text.AbstractInformationControlManager#disposeInformationControl()
+		 */
+		public void disposeInformationControl() {
+			super.disposeInformationControl();
+			
+			if (fTextViewer instanceof IWidgetTokenOwner) {
+				((IWidgetTokenOwner) fTextViewer).releaseWidgetToken(this);
+			}
+		}
+		
+		/*
+		 * @see org.eclipse.jface.text.IWidgetTokenKeeper#requestWidgetToken(org.eclipse.jface.text.IWidgetTokenOwner)
+		 */
+		public boolean requestWidgetToken(IWidgetTokenOwner owner) {
+			hideInformationControl();
+			return true;
+		}
+		
+		/*
+		 * @see org.eclipse.jface.text.IWidgetTokenKeeperExtension#requestWidgetToken(org.eclipse.jface.text.IWidgetTokenOwner, int)
+		 */
+		public boolean requestWidgetToken(IWidgetTokenOwner owner, int priority) {
+			if (priority < WIDGET_TOKEN_PRIORITY)
+				return false;
+			
+			hideInformationControl();
+			return true;
+		}
+		
+		/*
+		 * @see org.eclipse.jface.text.IWidgetTokenKeeperExtension#setFocus(org.eclipse.jface.text.IWidgetTokenOwner)
+		 */
+		public boolean setFocus(IWidgetTokenOwner owner) {
+			return false;
+		}
+	}
+	
+	private ITextViewer fTextViewer;
+	
+	private IHyperlink[] fHyperlinks;
+	private Region fSubjectRegion;
+	private MultipleHyperlinkHoverManager fManager;
+	
+	/**
+	 * Creates a new multiple hyperlink presenter which uses
+	 * {@link #HYPERLINK_COLOR} to read the color from the given preference store.
+	 *
+	 * @param store the preference store
+	 */
+	public MultipleHyperlinkPresenter(IPreferenceStore store) {
+		super(store);
+	}
+	
+	/**
+	 * Creates a new multiple hyperlink presenter.
+	 *
+	 * @param color the hyperlink color, to be disposed by the caller
+	 */
+	public MultipleHyperlinkPresenter(RGB color) {
+		super(color);
+	}
+	
+	/*
+	 * @see org.eclipse.jface.text.hyperlink.DefaultHyperlinkPresenter#install(org.eclipse.jface.text.ITextViewer)
+	 */
+	public void install(ITextViewer viewer) {
+		super.install(viewer);
+		fTextViewer= viewer;
+		
+		fManager= new MultipleHyperlinkHoverManager(new MultipleHyperlinkHover(), fTextViewer);
+		fManager.install(viewer.getTextWidget());
+		fManager.setSizeConstraints(100, 12, false, true);
+	}
+	
+	/*
+	 * @see org.eclipse.jface.text.hyperlink.DefaultHyperlinkPresenter#uninstall()
+	 */
+	public void uninstall() {
+		super.uninstall();
+		
+		if (fTextViewer != null) {
+			fManager.dispose();
+			
+			fTextViewer= null;
+		}
+	}
+	
+	/*
+	 * @see org.eclipse.jface.text.hyperlink.DefaultHyperlinkPresenter#canShowMultipleHyperlinks()
+	 */
+	public boolean canShowMultipleHyperlinks() {
+		return true;
+	}
+	
+	/*
+	 * @see org.eclipse.jface.text.hyperlink.DefaultHyperlinkPresenter#hideHyperlinks()
+	 */
+	public void hideHyperlinks() {
+		super.hideHyperlinks();
+		
+		fHyperlinks= null;
+	}
+	
+	/*
+	 * @see org.eclipse.jface.text.hyperlink.DefaultHyperlinkPresenter#showHyperlinks(org.eclipse.jface.text.hyperlink.IHyperlink[])
+	 */
+	public void showHyperlinks(IHyperlink[] hyperlinks) {
+		super.showHyperlinks(new IHyperlink[] { hyperlinks[0] });
+		
+		if (equals(fHyperlinks, hyperlinks))
+			return;
+		
+		fManager.disposeInformationControl();
+		fSubjectRegion= null;
+		fHyperlinks= hyperlinks;
+		
+		if (hyperlinks.length == 1)
+			return;
+		
+		int start= hyperlinks[0].getHyperlinkRegion().getOffset();
+		int end= start + hyperlinks[0].getHyperlinkRegion().getLength();
+		
+		for (int i= 1; i < hyperlinks.length; i++) {
+			int hstart= hyperlinks[i].getHyperlinkRegion().getOffset();
+			int hend= hstart + hyperlinks[i].getHyperlinkRegion().getLength();
+			
+			start= Math.min(start, hstart);
+			end= Math.max(end, hend);
+		}
+		
+		fSubjectRegion= new Region(start, end - start);
+		
+		fManager.showInformation();
+	}
+	
+	private boolean equals(IHyperlink[] oldLinks, IHyperlink[] newLinks) {
+		if (oldLinks == null)
+			return false;
+		
+		if (oldLinks.length != newLinks.length)
+			return false;
+		
+		for (int i= 0; i < newLinks.length; i++) {
+			if (!oldLinks[i].getHyperlinkRegion().equals(newLinks[i].getHyperlinkRegion()))
+				return false;
+		}
+		
+		return true;
+	}
+}
diff --git a/org.eclipse.ui.editors/src/org/eclipse/ui/editors/text/TextSourceViewerConfiguration.java b/org.eclipse.ui.editors/src/org/eclipse/ui/editors/text/TextSourceViewerConfiguration.java
index eb0b9dc..e49523c 100644
--- a/org.eclipse.ui.editors/src/org/eclipse/ui/editors/text/TextSourceViewerConfiguration.java
+++ b/org.eclipse.ui.editors/src/org/eclipse/ui/editors/text/TextSourceViewerConfiguration.java
@@ -17,6 +17,7 @@
 import java.util.Map.Entry;
 
 import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.RGB;
 import org.eclipse.swt.widgets.Shell;
 
 import org.eclipse.core.runtime.Assert;
@@ -34,9 +35,9 @@
 import org.eclipse.jface.text.ITextViewerExtension2;
 import org.eclipse.jface.text.IUndoManager;
 import org.eclipse.jface.text.TextViewerUndoManager;
-import org.eclipse.jface.text.hyperlink.DefaultHyperlinkPresenter;
 import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
 import org.eclipse.jface.text.hyperlink.IHyperlinkPresenter;
+import org.eclipse.jface.text.hyperlink.MultipleHyperlinkPresenter;
 import org.eclipse.jface.text.quickassist.IQuickAssistAssistant;
 import org.eclipse.jface.text.quickassist.QuickAssistAssistant;
 import org.eclipse.jface.text.reconciler.IReconciler;
@@ -299,9 +300,9 @@
 	 */
 	public IHyperlinkPresenter getHyperlinkPresenter(ISourceViewer sourceViewer) {
 		if (fPreferenceStore == null)
-			return super.getHyperlinkPresenter(sourceViewer);
+			return new MultipleHyperlinkPresenter(new RGB(0, 0, 255));
 
-		return new DefaultHyperlinkPresenter(fPreferenceStore);
+		return new MultipleHyperlinkPresenter(fPreferenceStore);
 	}
 
 	/**