Merged content_assist_participants to HEAD
diff --git a/org.eclipse.jface.text/component.xml b/org.eclipse.jface.text/component.xml
index 712a1b1..890ea45 100644
--- a/org.eclipse.jface.text/component.xml
+++ b/org.eclipse.jface.text/component.xml
@@ -24,6 +24,7 @@
  </package>
 
  <package name="org.eclipse.jface.text.contentassist">
+   <type name="ContentAssistEvent" instantiate="false" subclass="false"/>
  </package>
 
  <package name="org.eclipse.jface.text.formatter">
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/contentassist/ContentAssistInvocationContext.java b/org.eclipse.jface.text/src/org/eclipse/jface/contentassist/ContentAssistInvocationContext.java
new file mode 100644
index 0000000..7fab65d
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/contentassist/ContentAssistInvocationContext.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2005 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.contentassist;
+
+/**
+ * Describes the context of an invocation of content assist. For a text editor, the context would
+ * typically include the document (or the viewer) and the selection range, while source code editors
+ * may provide specific context information such as an AST.
+ * <p>
+ * An invocation context may also compute additional context information on demand and cache it to
+ * make it available to all {@link org.eclipse.jface.text.contentassist.ICompletionProposalComputer}s
+ * contributing proposals to one content assist invocation.
+ * </p>
+ * <p>
+ * Clients may subclass but must be careful to adhere to the described
+ * {@link #equals(Object) equality} contract.
+ * </p>
+ * <p>
+ * XXX this API is provisional and may change anytime during the course of 3.2
+ * </p>
+ * 
+ * @since 3.2
+ */
+public abstract class ContentAssistInvocationContext {
+	
+	/**
+	 * Invocation contexts are equal if they describe the same context and are of the same type.
+	 * This implementation checks for <code>null</code> values and class equality. Subclasses
+	 * should extend this method by adding checks for their context relevant fields (but not
+	 * necessarily cached values).
+	 * <p>
+	 * Example:
+	 * 
+	 * <pre>
+	 * class MyContext extends ContentAssistInvocationContext {
+	 * 	private final Object fState;
+	 * 	private Object fCachedInfo;
+	 * 
+	 * 	...
+	 * 
+	 * 	public boolean equals(Object obb) {
+	 * 		if (!super.equals(obj))
+	 * 			return false;
+	 * 		MyContext other= (MyContext) obj;
+	 * 		return fState.equals(other.fState);
+	 * 	}
+	 * }
+	 * </pre>
+	 * 
+	 * </p>
+	 * <p>
+	 * Subclasses should also extend {@link Object#hashCode()}.
+	 * </p>
+	 * 
+	 * @param obj {@inheritDoc}
+	 * @return {@inheritDoc}
+	 */
+	public boolean equals(Object obj) {
+		if (obj == null)
+			return false;
+		if (!getClass().equals(obj.getClass()))
+			return false;
+
+		return true;
+	}
+	
+	/*
+	 * @see java.lang.Object#hashCode()
+	 */
+	public int hashCode() {
+		return 23459213; // random
+	}
+
+}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/AdditionalInfoController.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/AdditionalInfoController.java
index fc369e5..648fdcd 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/AdditionalInfoController.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/AdditionalInfoController.java
@@ -227,7 +227,7 @@
 				setCustomInformationControlCreator(null);
 
 			// compute subject area
-			setMargins(4, -2);
+			setMargins(4, 0);
 			Rectangle area= fProposalTable.getBounds();
 			area.x= 0; // subject area is the whole subject control
 			area.y= 0;
@@ -242,12 +242,12 @@
 	 */
 	protected Point computeSizeConstraints(Control subjectControl, IInformationControl informationControl) {
 		Point sizeConstraint= super.computeSizeConstraints(subjectControl, informationControl);
-		Point size= subjectControl.getSize();
+		Point size= subjectControl.getShell().getSize();
 
-		Rectangle otherTrim= subjectControl.getShell().computeTrim(0, 0, 0, 0);
-		size.x += otherTrim.width;
-		size.y += otherTrim.height;
-
+//		Rectangle otherTrim= subjectControl.getShell().computeTrim(0, 0, 0, 0);
+//		size.x += otherTrim.width;
+//		size.y += otherTrim.height;
+//
 		if (informationControl instanceof IInformationControlExtension3) {
 			Rectangle thisTrim= ((IInformationControlExtension3)informationControl).computeTrim();
 			size.x -= thisTrim.width;
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/CompletionProposalPopup.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/CompletionProposalPopup.java
index 54f9862..09056ed 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/CompletionProposalPopup.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/CompletionProposalPopup.java
@@ -23,15 +23,21 @@
 import org.eclipse.swt.events.DisposeListener;
 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.SelectionEvent;
 import org.eclipse.swt.events.SelectionListener;
 import org.eclipse.swt.events.VerifyEvent;
 import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontData;
 import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Listener;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.swt.widgets.Table;
@@ -39,6 +45,7 @@
 
 import org.eclipse.jface.contentassist.IContentAssistSubjectControl;
 
+import org.eclipse.jface.text.Assert;
 import org.eclipse.jface.text.BadLocationException;
 import org.eclipse.jface.text.DocumentEvent;
 import org.eclipse.jface.text.IDocument;
@@ -201,6 +208,29 @@
 	 * @since 3.1.1
 	 */
 	private boolean fIsFilterPending= false;
+	/**
+	 * The info message at the bottom of the popup, or <code>null</code> for no popup (if
+	 * ContentAssistant does not provide one).
+	 * 
+	 * @since 3.2
+	 */
+	private Label fMessageText;
+	/**
+	 * The font used for <code>fMessageText</code> or null; dispose when done.
+	 * 
+	 * @since 3.2
+	 */
+	private Font fMessageTextFont;
+	/**
+	 * The most recent completion offset (used to determine repeteated invocation)
+	 * 
+	 * @since 3.2
+	 */
+	private int fLastCompletionOffset;
+	/**
+	 * Usually <code>true</code>, <code>false</code> if the popup is displaying an empty list after repeated invocation.
+	 */
+	private boolean fHasProposals;
 
 
 	/**
@@ -248,7 +278,8 @@
 		final Control control= fContentAssistSubjectControlAdapter.getControl();
 
 		if (!Helper.okToUse(fProposalShell) && control != null && !control.isDisposed()) {
-
+			fContentAssistant.resetRepetition();
+			
 			// add the listener before computing the proposals so we don't move the caret
 			// when the user types fast.
 			fContentAssistSubjectControlAdapter.addKeyListener(fKeyListener);
@@ -258,36 +289,55 @@
 
 					fInvocationOffset= fContentAssistSubjectControlAdapter.getSelectedRange().x;
 					fFilterOffset= fInvocationOffset;
+					fLastCompletionOffset= fFilterOffset;
 					fComputedProposals= computeProposals(fInvocationOffset);
 
 					int count= (fComputedProposals == null ? 0 : fComputedProposals.length);
 					if (count == 0) {
-
-						if (!autoActivated)
-							control.getDisplay().beep();
-
-						hide();
-
-					} else {
-
-						if (count == 1 && !autoActivated && canAutoInsert(fComputedProposals[0])) {
-
-							insertProposal(fComputedProposals[0], (char) 0, 0, fInvocationOffset);
-							hide();
-
-						} else {
-							createProposalSelector();
-							setProposals(fComputedProposals, false);
-							displayProposals();
+						if (fContentAssistant.recomputeOnRepetition()) {
+							fComputedProposals= computeProposals(fInvocationOffset);
+							count= (fComputedProposals == null ? 0 : fComputedProposals.length);
 						}
+						if (count == 0) {
+							if (!autoActivated)
+								control.getDisplay().beep();
+							
+							hide();
+							return;
+						}
+					}
+					
+					if (count == 1 && !autoActivated && canAutoInsert(fComputedProposals[0])) {
+						insertProposal(fComputedProposals[0], (char) 0, 0, fInvocationOffset);
+						hide();
+					} else {
+						createProposalSelector();
+						setProposals(fComputedProposals, false);
+						displayProposals();
 					}
 				}
 			});
+		} else {
+			if (fLastCompletionOffset == fFilterOffset) {
+				handleRepeatedInvocation();
+			} else {
+				fLastCompletionOffset= fFilterOffset;
+			}
+			
 		}
 
 		return getErrorMessage();
 	}
 
+	private void handleRepeatedInvocation() {
+		ICompletionProposal[] recomputed= null;
+		if (fContentAssistant.recomputeOnRepetition()) {
+			recomputed= computeProposals(fFilterOffset);
+			if (recomputed != null)
+				setProposals(recomputed, false);
+		}
+	}
+
 	/**
 	 * Returns the completion proposal available at the given offset of the
 	 * viewer's document. Delegates the work to the content assistant.
@@ -334,16 +384,32 @@
 
 		fProposalTable.setLocation(0, 0);
 		if (fAdditionalInfoController != null)
-			fAdditionalInfoController.setSizeConstraints(50, 10, true, false);
+			fAdditionalInfoController.setSizeConstraints(50, 10, true, true);
 
 		GridLayout layout= new GridLayout();
 		layout.marginWidth= 0;
 		layout.marginHeight= 0;
+		layout.verticalSpacing= 1;
 		fProposalShell.setLayout(layout);
+		
+		String message= fContentAssistant.getMessage();
+		if (message != null) {
+			fMessageText= new Label(fProposalShell, SWT.RIGHT);
+			GridData textData= new GridData(SWT.FILL, SWT.BOTTOM, true, false);
+			fMessageText.setLayoutData(textData);
+			fMessageText.setText(message + " "); //$NON-NLS-1$
+			Font font= fMessageText.getFont();
+			Display display= fProposalShell.getDisplay();
+			FontData[] fontDatas= font.getFontData();
+			for (int i= 0; i < fontDatas.length; i++)
+				fontDatas[i].setHeight(fontDatas[i].getHeight() * 9 / 10);
+			fMessageTextFont= new Font(display, fontDatas);
+			fMessageText.setFont(fMessageTextFont);
+			fMessageText.setCursor(fProposalShell.getDisplay().getSystemCursor(SWT.CURSOR_HAND));
+		}
 
 		GridData data= new GridData(GridData.FILL_BOTH);
 
-
 		Point size= fContentAssistant.restoreCompletionProposalPopupSize();
 		if (size != null) {
 			fProposalTable.setLayoutData(data);
@@ -353,6 +419,7 @@
 			data.widthHint= 300;
 			fProposalTable.setLayoutData(data);
 			fProposalShell.pack();
+			fSize= fProposalShell.getSize();
 		}
 
 		fProposalShell.addControlListener(new ControlListener() {
@@ -368,14 +435,16 @@
 				fSize= fProposalShell.getSize();
 			}
 		});
-
+		
 		if (!"carbon".equals(SWT.getPlatform())) //$NON-NLS-1$
-			fProposalShell.setBackground(control.getDisplay().getSystemColor(SWT.COLOR_BLACK));
+			fProposalShell.setBackground(control.getDisplay().getSystemColor(SWT.COLOR_GRAY));
 
 		Color c= fContentAssistant.getProposalSelectorBackground();
 		if (c == null)
 			c= control.getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND);
 		fProposalTable.setBackground(c);
+		if (fMessageText != null)
+			fMessageText.setBackground(c);
 
 		c= fContentAssistant.getProposalSelectorForeground();
 		if (c == null)
@@ -401,6 +470,18 @@
 
 		fProposalTable.setHeaderVisible(false);
 		fContentAssistant.addToLayout(this, fProposalShell, ContentAssistant.LayoutManager.LAYOUT_PROPOSAL_SELECTOR, fContentAssistant.getSelectionOffset());
+		
+		if (fMessageText != null) {
+			fMessageText.addMouseListener(new MouseAdapter() {
+				public void mouseUp(MouseEvent e) {
+					fLastCompletionOffset= fFilterOffset;
+					handleRepeatedInvocation();
+				}
+				
+				public void mouseDown(MouseEvent e) {
+				}
+			});
+		}
 	}
 
 	/*
@@ -469,6 +550,9 @@
 	 */
 	private void insertProposal(ICompletionProposal p, char trigger, int stateMask, final int offset) {
 
+		if (!fHasProposals)
+			return;
+		
 		fInserting= true;
 		IRewriteTarget target= null;
 		IEditingSupport helper= new IEditingSupport() {
@@ -580,6 +664,13 @@
 			fProposalShell.dispose();
 			fProposalShell= null;
 		}
+		
+		if (fMessageTextFont != null) {
+			fMessageTextFont.dispose();
+			fMessageTextFont= null;
+		}
+		
+		fLastCompletionOffset= -1;
 	}
 
 	/**
@@ -637,6 +728,15 @@
 			ICompletionProposal oldProposal= getSelectedProposal();
 			if (oldProposal instanceof ICompletionProposalExtension2 && fViewer != null)
 				((ICompletionProposalExtension2) oldProposal).unselected(fViewer);
+			
+			if (proposals == null || proposals.length == 0) {
+				proposals= new ICompletionProposal[] { new CompletionProposal(JFaceTextMessages.getString("CompletionProposalPopup.no_proposals"), fFilterOffset, 0, 0)}; //$NON-NLS-1$
+				fProposalTable.setEnabled(false);
+				fHasProposals= false;
+			} else {
+				fProposalTable.setEnabled(true);
+				fHasProposals= true;
+			}
 
 			fFilteredProposals= proposals;
 			final int newLen= proposals.length;
@@ -1032,7 +1132,12 @@
 	 */
 	public String incrementalComplete() {
 		if (Helper.okToUse(fProposalShell) && fFilteredProposals != null) {
-			completeCommonPrefix();
+			if (fLastCompletionOffset == fFilterOffset) {
+				handleRepeatedInvocation();
+			} else {
+				fLastCompletionOffset= fFilterOffset;
+				completeCommonPrefix();
+			}
 		} else {
 			final Control control= fContentAssistSubjectControlAdapter.getControl();
 
@@ -1047,13 +1152,24 @@
 
 					fInvocationOffset= fContentAssistSubjectControlAdapter.getSelectedRange().x;
 					fFilterOffset= fInvocationOffset;
+					fLastCompletionOffset= fFilterOffset;
 					fFilteredProposals= computeProposals(fInvocationOffset);
 
 					int count= (fFilteredProposals == null ? 0 : fFilteredProposals.length);
 					if (count == 0) {
-						control.getDisplay().beep();
-						hide();
-					} else if (count == 1 && canAutoInsert(fFilteredProposals[0])) {
+						if (fContentAssistant.recomputeOnRepetition()) {
+							fFilteredProposals= computeProposals(fInvocationOffset);
+							count= (fFilteredProposals == null ? 0 : fFilteredProposals.length);
+						}
+						
+						if (count == 0) {
+							control.getDisplay().beep();
+							hide();
+							return;
+						}
+					}
+					
+					if (count == 1 && canAutoInsert(fFilteredProposals[0])) {
 						insertProposal(fFilteredProposals[0], (char) 0, 0, fInvocationOffset);
 						hide();
 					} else {
@@ -1202,6 +1318,7 @@
 
 			fContentAssistSubjectControlAdapter.setSelectedRange(fFilterOffset + postfix.length(), 0);
 			fContentAssistSubjectControlAdapter.revealRange(fFilterOffset + postfix.length(), 0);
+			fLastCompletionOffset= fFilterOffset + postfix.length();
 
 			return false;
 		} catch (BadLocationException e) {
@@ -1284,4 +1401,10 @@
 
 		return insertion;
 	}
+	
+	public void setMessage(String message) {
+		Assert.isNotNull(message);
+		if (isActive() && fMessageText != null)
+			fMessageText.setText(message + " "); //$NON-NLS-1$
+	}
 }
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContentAssistEvent.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContentAssistEvent.java
new file mode 100644
index 0000000..8293542
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContentAssistEvent.java
@@ -0,0 +1,44 @@
+package org.eclipse.jface.text.contentassist;
+
+/**
+ * Describes the state that the content assistant is in when completing proposals.
+ * <p>
+ * Clients may use this class.
+ * </p>
+ * <p>
+ * XXX this API is provisional and may change anytime during the course of 3.2
+ * </p>
+ * 
+ * @since 3.2
+ */
+public final class ContentAssistEvent {
+	ContentAssistEvent(ContentAssistant ca, TextContentAssistInvocationContext ctx,
+			IContentAssistProcessor proc, int rep) {
+		assistant= ca;
+		context= ctx;
+		processor= proc;
+		repetition= rep;
+	}
+
+	/**
+	 * The content assistant computing proposals.
+	 */
+	public final ContentAssistant assistant;
+
+	/**
+	 * The content assist invocation context.
+	 */
+	public final TextContentAssistInvocationContext context;
+
+	/**
+	 * The content assist processor that will be queried for proposals, depending on the current
+	 * partition type.
+	 */
+	public final IContentAssistProcessor processor;
+
+	/**
+	 * The repetition count, i.e. how many times that content assist was invoked in one content
+	 * assist session. The counter is reset once the popup is closed.
+	 */
+	public final int repetition;
+}
\ No newline at end of file
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContentAssistant.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContentAssistant.java
index d52fb04..f0f1267 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContentAssistant.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContentAssistant.java
@@ -10,8 +10,10 @@
  *******************************************************************************/
 package org.eclipse.jface.text.contentassist;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
@@ -42,10 +44,6 @@
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.swt.widgets.Widget;
 
-import org.eclipse.jface.contentassist.IContentAssistSubjectControl;
-import org.eclipse.jface.contentassist.ISubjectControlContentAssistProcessor;
-import org.eclipse.jface.dialogs.IDialogSettings;
-
 import org.eclipse.jface.text.Assert;
 import org.eclipse.jface.text.BadLocationException;
 import org.eclipse.jface.text.IDocument;
@@ -60,13 +58,17 @@
 import org.eclipse.jface.text.IWidgetTokenOwnerExtension;
 import org.eclipse.jface.text.TextUtilities;
 
+import org.eclipse.jface.contentassist.IContentAssistSubjectControl;
+import org.eclipse.jface.contentassist.ISubjectControlContentAssistProcessor;
+import org.eclipse.jface.dialogs.IDialogSettings;
+
 
 /**
  * The standard implementation of the <code>IContentAssistant</code> interface.
  * Usually, clients instantiate this class and configure it before using it.
  */
 public class ContentAssistant implements IContentAssistant, IContentAssistantExtension, IWidgetTokenKeeper, IWidgetTokenKeeperExtension {
-
+	
 	/**
 	 * A generic closer class used to monitor various
 	 * interface events in order to determine whether
@@ -751,6 +753,26 @@
 	 * @since 3.0
 	 */
 	private boolean fIsPrefixCompletionEnabled= false;
+	/**
+	 * The list of completion listeners.
+	 * 
+	 * @since 3.2
+	 */
+	private List fCompletionListeners= new ArrayList();
+	/**
+	 * The repetition count - counts how many times code completion was invoked at the same offset
+	 * in a single code completion session.
+	 * 
+	 * @since 3.2
+	 */
+	private int fRepetition;
+	/**
+	 * The message to display at the bottom of the proposal popup, or <code>null</code> if no
+	 * message should be shown.
+	 * 
+	 * @since 3.2
+	 */
+	private String fMessage= null;
 
 	/**
 	 * Creates a new content assistant. The content assistant is not automatically activated,
@@ -1398,6 +1420,7 @@
 	 */
 	protected void possibleCompletionsClosed() {
 		storeCompletionProposalPopupSize();
+		fRepetition= 0;
 	}
 
 	/*
@@ -1531,6 +1554,7 @@
 
 		IContentAssistProcessor p= getProcessor(viewer, offset);
 		if (p != null) {
+			fireEvent(viewer, offset, p);
 			result= p.computeCompletionProposals(viewer, offset);
 			fLastErrorMessage= p.getErrorMessage();
 		}
@@ -1878,4 +1902,69 @@
 	public boolean hasProposalPopupFocus() {
 		return fProposalPopup.hasFocus();
 	}
+	
+	/**
+	 * Sets the caption message displayed at the bottom of the completion proposal popup.
+	 * 
+	 * @param message the message
+	 * @since 3.2
+	 */
+	public void setMessage(String message) {
+		Assert.isNotNull(message);
+		fMessage= message;
+		if (fProposalPopup != null)
+			fProposalPopup.setMessage(message);
+	}
+	
+	String getMessage() {
+		return fMessage;
+	}
+	
+	/**
+	 * Adds a completion listener that will be informed before proposals are computed.
+	 * <p>
+	 * XXX this API is provisional and may change anytime during the course of 3.2
+	 * </p>
+	 * 
+	 * @param listener the listener
+	 * @since 3.2
+	 */
+	public void addCompletionListener(ICompletionListener listener) {
+		Assert.isNotNull(listener);
+		fCompletionListeners.add(listener);
+	}
+	
+	/**
+	 * Removes the completion listener.
+	 * <p>
+	 * XXX this API is provisional and may change anytime during the course of 3.2
+	 * </p>
+	 * 
+	 * @param listener the listener to remove
+	 * @since 3.2
+	 */
+	public void removeListener(ICompletionListener listener) {
+		fCompletionListeners.remove(listener);
+	}
+
+	private void fireEvent(ITextViewer viewer, int offset, IContentAssistProcessor processor) {
+		ContentAssistEvent event= new ContentAssistEvent(this, new TextContentAssistInvocationContext(viewer, offset), processor, fRepetition);
+		for (Iterator it= new ArrayList(fCompletionListeners).iterator(); it.hasNext();) {
+			ICompletionListener listener= (ICompletionListener) it.next();
+			listener.computingProposals(event);
+		}
+	}
+
+	boolean recomputeOnRepetition() {
+		fRepetition++;
+		return doRepetitionHandling();
+	}
+
+	private boolean doRepetitionHandling() {
+		return fMessage != null;
+	}
+
+	void resetRepetition() {
+		fRepetition= 0;
+	}
 }
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ICompletionListener.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ICompletionListener.java
new file mode 100644
index 0000000..49f8432
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ICompletionListener.java
@@ -0,0 +1,21 @@
+package org.eclipse.jface.text.contentassist;
+
+/**
+ * A completion listener is informed before the content assisant computes completion proposals.
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ * <p>
+ * XXX this API is provisional and may change anytime during the course of 3.2
+ * </p>
+ * 
+ * @since 3.2
+ */
+public interface ICompletionListener {
+	/**
+	 * Informs the receiver that completion proposals will be computed.
+	 * 
+	 * @param event the content assist event
+	 */
+	void computingProposals(ContentAssistEvent event);
+}
\ No newline at end of file
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ICompletionProposalComputer.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ICompletionProposalComputer.java
new file mode 100644
index 0000000..dc67a3b
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ICompletionProposalComputer.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 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.contentassist;
+
+import java.util.List;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+
+/**
+ * Computes completions and context information displayed by a content assistant.
+ * <p>
+ * XXX this API is provisional and may change anytime during the course of 3.2
+ * </p>
+ * 
+ * @since 3.2
+ */
+public interface ICompletionProposalComputer {
+
+	/**
+	 * Returns a list of completion proposals valid at the given invocation context.
+	 * 
+	 * @param context the context of the content assist invocation
+	 * @param monitor a progress monitor to report progress. The monitor is private to this
+	 *        invocation, i.e. there is no need for the receiver to spawn a sub monitor.
+	 * @return an array of completion proposals (element type: {@link ICompletionProposal})
+	 */
+	List computeCompletionProposals(TextContentAssistInvocationContext context, IProgressMonitor monitor);
+
+	/**
+	 * Returns context information objects valid at the given invocation context.
+	 * 
+	 * @param context the context of the content assist invocation
+	 * @param monitor a progress monitor to report progress. The monitor is private to this
+	 *        invocation, i.e. there is no need for the receiver to spawn a sub monitor.
+	 * @return an array of context information objects (element type: {@link IContextInformation})
+	 */
+	List computeContextInformation(TextContentAssistInvocationContext context, IProgressMonitor monitor);
+}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/JFaceTextMessages.properties b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/JFaceTextMessages.properties
index fc0436b..b35cad9 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/JFaceTextMessages.properties
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/JFaceTextMessages.properties
@@ -13,3 +13,4 @@
 InfoPopup.info_delay_timer_name=AdditionalInfo Delay
 
 ContentAssistant.assist_delay_timer_name=AutoAssist Delay
+CompletionProposalPopup.no_proposals=no proposals
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/TextContentAssistInvocationContext.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/TextContentAssistInvocationContext.java
new file mode 100644
index 0000000..3c5669c
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/TextContentAssistInvocationContext.java
@@ -0,0 +1,130 @@
+/*******************************************************************************
+ * Copyright (c) 2005 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.contentassist;
+
+import org.eclipse.jface.text.Assert;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.ITextViewer;
+
+import org.eclipse.jface.contentassist.ContentAssistInvocationContext;
+
+/**
+ * A content assist invocation context for text viewers. The context knows the
+ * viewer, the invocation offset and can lazily compute the identifier
+ * prefix preceding the invocation offset.
+ * <p>
+ * Clients may instantiate and subclass.
+ * </p>
+ * <p>
+ * XXX this API is provisional and may change anytime during the course of 3.2
+ * </p>
+ * 
+ * @since 3.2
+ */
+public class TextContentAssistInvocationContext extends ContentAssistInvocationContext {
+	
+	/* state */
+	private final ITextViewer fViewer;
+	private final int fOffset;
+	
+	/* cached additional info */
+	private CharSequence fPrefix;
+	
+	/**
+	 * Equivalent to
+	 * {@linkplain #TextContentAssistInvocationContext(ITextViewer, int) TextContentAssistInvocationContext(viewer, viewer.getSelectedRange().x)}.
+	 * 
+	 * @param viewer the text viewer that content assist is invoked in
+	 */
+	public TextContentAssistInvocationContext(ITextViewer viewer) {
+		this(viewer, viewer.getSelectedRange().x);
+	}
+
+	/**
+	 * Creates a new context for the given viewer and offset.
+	 * 
+	 * @param viewer the text viewer that content assist is invoked in
+	 * @param offset the offset into the viewer's document where content assist is invoked at
+	 */
+	public TextContentAssistInvocationContext(ITextViewer viewer, int offset) {
+		Assert.isNotNull(viewer);
+		fViewer= viewer;
+		fOffset= offset;
+	}
+	
+	/**
+	 * Returns the invocation offset.
+	 * 
+	 * @return the invocation offset
+	 */
+	public final int getInvocationOffset() {
+		return fOffset;
+	}
+	
+	/**
+	 * Returns the viewer.
+	 * 
+	 * @return the viewer
+	 */
+	public final ITextViewer getViewer() {
+		return fViewer;
+	}
+	
+	/**
+	 * Shortcut for <code>getViewer().getDocument()</code>.
+	 * 
+	 * @return the viewer's document
+	 */
+	public IDocument getDocument() {
+		return getViewer().getDocument();
+	}
+	
+	/**
+	 * Computes the identifier (as specified by {@link Character#isJavaIdentifierPart(char)}) that
+	 * immediately precedes the invocation offset.
+	 * 
+	 * @return the prefix preceding the content assist invocation offset
+	 * @throws BadLocationException if accessing the document fails
+	 */
+	public CharSequence computeIdentifierPrefix() throws BadLocationException {
+		if (fPrefix == null) {
+			IDocument document= getDocument();
+			int end= getInvocationOffset();
+			int start= end;
+			while (--start >= 0) {
+				if (!Character.isJavaIdentifierPart(document.getChar(start)))
+					break;
+			}
+			start++;
+			fPrefix= document.get(start, end - start);
+		}
+		
+		return fPrefix;
+	}
+	
+	/*
+	 * @see java.lang.Object#equals(java.lang.Object)
+	 */
+	public boolean equals(Object obj) {
+		if (!super.equals(obj))
+			return false;
+		TextContentAssistInvocationContext other= (TextContentAssistInvocationContext) obj;
+		return fViewer.equals(other.fViewer) && fOffset == other.fOffset;
+	}
+	
+	/*
+	 * @see java.lang.Object#hashCode()
+	 */
+	public int hashCode() {
+		return super.hashCode() << 5 | fViewer.hashCode() << 3 | fOffset;
+	}
+}
diff --git a/org.eclipse.ui.workbench.texteditor/component.xml b/org.eclipse.ui.workbench.texteditor/component.xml
index dace458..c465592 100644
--- a/org.eclipse.ui.workbench.texteditor/component.xml
+++ b/org.eclipse.ui.workbench.texteditor/component.xml
@@ -20,6 +20,7 @@
    <type name="ITextEditorDropTargetListener" implement="false"/>
    <type name="RevertToSavedAction" subclass="false"/>
    <type name="SaveAction" subclass="false"/>
+   <type name="HippieProposalComputer" subclass="false"/>
  </package>
 
  <package name="org.eclipse.ui.texteditor.link">
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/HippieProposalComputer.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/HippieProposalComputer.java
new file mode 100644
index 0000000..6e2054b
--- /dev/null
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/HippieProposalComputer.java
@@ -0,0 +1,255 @@
+/*******************************************************************************
+ * Copyright (c) 2005 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.ui.texteditor;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.DocumentEvent;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IInformationControlCreator;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.contentassist.ICompletionProposalComputer;
+import org.eclipse.jface.text.contentassist.ICompletionProposalExtension;
+import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2;
+import org.eclipse.jface.text.contentassist.ICompletionProposalExtension3;
+import org.eclipse.jface.text.contentassist.ICompletionProposalExtension4;
+import org.eclipse.jface.text.contentassist.IContextInformation;
+import org.eclipse.jface.text.contentassist.TextContentAssistInvocationContext;
+
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IEditorReference;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.internal.texteditor.HippieCompletionEngine;
+
+/**
+ * A completion proposal computer for hippie word completions.
+ * <p>
+ * Clients may instantiate.
+ * </p>
+ * <p>
+ * XXX this API is provisional and may change anytime during the course of 3.2
+ * </p>
+ * 
+ * @since 3.2
+ */
+public final class HippieProposalComputer implements ICompletionProposalComputer {
+
+	private static final class Proposal implements ICompletionProposal, ICompletionProposalExtension, ICompletionProposalExtension2, ICompletionProposalExtension3, ICompletionProposalExtension4 {
+
+		private final String fString;
+		private final String fPrefix;
+		private final int fOffset;
+
+		public Proposal(String string, String prefix, int offset) {
+			fString= string;
+			fPrefix= prefix;
+			fOffset= offset;
+		}
+
+		public void apply(IDocument document) {
+			apply(null, '\0', 0, fOffset);
+		}
+
+		public Point getSelection(IDocument document) {
+			return new Point(fOffset + fString.length(), 0);
+		}
+
+		public String getAdditionalProposalInfo() {
+			return null;
+		}
+
+		public String getDisplayString() {
+			return fPrefix + fString;
+		}
+
+		public Image getImage() {
+			return null;
+		}
+
+		public IContextInformation getContextInformation() {
+			return null;
+		}
+
+		public void apply(IDocument document, char trigger, int offset) {
+			try {
+				String replacement= fString.substring(offset - fOffset);
+				document.replace(offset, 0, replacement);
+			} catch (BadLocationException x) {
+				// TODO Auto-generated catch block
+				x.printStackTrace();
+			}
+		}
+
+		public boolean isValidFor(IDocument document, int offset) {
+			return validate(document, offset, null);
+		}
+
+		public char[] getTriggerCharacters() {
+			return null;
+		}
+
+		public int getContextInformationPosition() {
+			return 0;
+		}
+
+		public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) {
+			apply(viewer.getDocument(), trigger, offset);
+		}
+
+		public void selected(ITextViewer viewer, boolean smartToggle) {
+		}
+
+		public void unselected(ITextViewer viewer) {
+		}
+
+		public boolean validate(IDocument document, int offset, DocumentEvent event) {
+			try {
+				int prefixStart= fOffset - fPrefix.length();
+				return offset >= fOffset && offset < fOffset + fString.length() && document.get(prefixStart, offset - (prefixStart)).equals((fPrefix + fString).substring(0, offset - prefixStart));
+			} catch (BadLocationException x) {
+				return false;
+			} 
+		}
+
+		public IInformationControlCreator getInformationControlCreator() {
+			return null;
+		}
+
+		public CharSequence getPrefixCompletionText(IDocument document, int completionOffset) {
+			return fPrefix + fString;
+		}
+
+		public int getPrefixCompletionStart(IDocument document, int completionOffset) {
+			return fOffset - fPrefix.length();
+		}
+
+		public boolean isAutoInsertable() {
+			return true;
+		}
+
+	}
+
+	private final HippieCompletionEngine fEngine= new HippieCompletionEngine();
+
+	/**
+	 * Creates a new hippie completion proposal computer.
+	 */
+	public HippieProposalComputer() {
+	}
+
+	/*
+	 * @see org.eclipse.jface.text.contentassist.ICompletionProposalComputer#computeCompletionProposals(org.eclipse.jface.text.contentassist.TextContentAssistInvocationContext, org.eclipse.core.runtime.IProgressMonitor)
+	 */
+	public List computeCompletionProposals(TextContentAssistInvocationContext context, IProgressMonitor monitor) {
+		try {
+			String prefix= context.computeIdentifierPrefix().toString();
+			if (prefix.length() == 0)
+				return Collections.EMPTY_LIST;
+			
+			String[] suggestions= getSuggestions(context.getViewer(), context.getInvocationOffset(), prefix);
+			
+			List result= new ArrayList();
+			for (int i= 0; i < suggestions.length; i++) {
+				String string= suggestions[i];
+				if (string.length() > 0)
+					result.add(createProposal(string, prefix, context.getInvocationOffset()));
+			}
+			
+			return result;
+			
+		} catch (BadLocationException x) {
+			// TODO Auto-generated catch block
+			x.printStackTrace();
+			return Collections.EMPTY_LIST;
+		}
+	}
+
+	private ICompletionProposal createProposal(String string, String prefix, int offset) {
+		return new Proposal(string, prefix, offset);
+	}
+
+	/*
+	 * @see org.eclipse.jface.text.contentassist.ICompletionProposalComputer#computeContextInformation(org.eclipse.jface.text.contentassist.TextContentAssistInvocationContext, org.eclipse.core.runtime.IProgressMonitor)
+	 */
+	public List computeContextInformation(TextContentAssistInvocationContext context, IProgressMonitor monitor) {
+		// no context informations for hippie completions
+		return Collections.EMPTY_LIST;
+	}
+	
+
+	/**
+	 * Return the list of suggestions from the current document. First the
+	 * document is searched backwards from the caret position and then forwards.
+	 * 
+	 * @param offset 
+	 * @param viewer 
+	 * @param prefix the completion prefix
+	 * @return all possible completions that were found in the current document
+	 * @throws BadLocationException if accessing the document fails
+	 */
+	private ArrayList createSuggestionsFromOpenDocument(ITextViewer viewer, int offset, String prefix) throws BadLocationException {
+		IDocument document= viewer.getDocument();
+		ArrayList completions= new ArrayList();
+		completions.addAll(fEngine.getCompletionsBackwards(document, prefix, offset));
+		completions.addAll(fEngine.getCompletionsForward(document, prefix, offset));
+
+		return completions;
+	}
+
+	/**
+	 * Create the array of suggestions. It scans all open text editors and
+	 * prefers suggestions from the currently open editor. It also adds the
+	 * empty suggestion at the end.
+	 * 
+	 * @param viewer 
+	 * @param offset 
+	 * @param prefix the prefix to search for
+	 * @return the list of all possible suggestions in the currently open
+	 *         editors
+	 * @throws BadLocationException if accessing the current document fails
+	 */
+	private String[] getSuggestions(ITextViewer viewer, int offset, String prefix) throws BadLocationException {
+
+		ArrayList suggestions= createSuggestionsFromOpenDocument(viewer, offset, prefix);
+		IDocument currentDocument= viewer.getDocument();
+
+		IWorkbenchWindow window= PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+		IEditorReference editorReferences[]= window.getActivePage().getEditorReferences();
+
+		for (int i= 0; i < editorReferences.length; i++) {
+			IEditorPart editor= editorReferences[i].getEditor(false); // don't create!
+			if (editor instanceof ITextEditor) {
+				ITextEditor textEditor= (ITextEditor) editor;
+				IEditorInput input= textEditor.getEditorInput();
+				IDocument doc= textEditor.getDocumentProvider().getDocument(input);
+				if (!currentDocument.equals(doc))
+					suggestions.addAll(fEngine.getCompletions(doc, prefix));
+			}
+		}
+		// add the empty suggestion
+		suggestions.add(""); //$NON-NLS-1$
+
+		List uniqueSuggestions= fEngine.makeUnique(suggestions);
+
+		return (String[]) uniqueSuggestions.toArray(new String[0]);
+	}
+}