IContentAssistant
interface.
* This implementation is used by the linked mode UI. This is internal and subject
* to change without notice.
*/
public class ContentAssistant2 implements IContentAssistant, IContentAssistantExtension, IWidgetTokenKeeper, IWidgetTokenKeeperExtension {
/**
* A generic closer class used to monitor various
* interface events in order to determine whether
* content-assist should be terminated and all
* associated windows closed.
*/
class Closer implements ControlListener, MouseListener, FocusListener, DisposeListener, IViewportListener {
/** The shell on which we add listeners. */
private Shell fShell;
private long fViewportListenerStartTime;
/**
* Installs this closer on it's viewer's text widget.
*/
protected void install() {
Control w= fViewer.getTextWidget();
if (Helper2.okToUse(w)) {
Shell shell= w.getShell();
fShell= shell;
shell.addControlListener(this);
w.addMouseListener(this);
w.addFocusListener(this);
/*
* 1GGYYWK: ITPJUI:ALL - Dismissing editor with code assist up causes lots of Internal Errors
*/
w.addDisposeListener(this);
}
fViewer.addViewportListener(this);
fViewportListenerStartTime= System.currentTimeMillis() + 500;
}
/**
* Uninstalls this closer from the viewer's text widget.
*/
protected void uninstall() {
Shell shell= fShell;
fShell= null;
if (Helper2.okToUse(shell))
shell.removeControlListener(this);
Control w= fViewer.getTextWidget();
if (Helper2.okToUse(w)) {
w.removeMouseListener(this);
w.removeFocusListener(this);
/*
* 1GGYYWK: ITPJUI:ALL - Dismissing editor with code assist up causes lots of Internal Errors
*/
w.removeDisposeListener(this);
}
fViewer.removeViewportListener(this);
}
@Override
public void controlResized(ControlEvent e) {
hide();
}
@Override
public void controlMoved(ControlEvent e) {
hide();
}
@Override
public void mouseDown(MouseEvent e) {
hide();
}
@Override
public void mouseUp(MouseEvent e) {
}
@Override
public void mouseDoubleClick(MouseEvent e) {
hide();
}
@Override
public void focusGained(FocusEvent e) {
}
@Override
public void focusLost(FocusEvent e) {
if (fViewer != null) {
Control control= fViewer.getTextWidget();
if (control != null) {
Display d= control.getDisplay();
if (d != null) {
d.asyncExec(() -> {
if (!hasFocus())
hide();
});
}
}
}
}
/*
* @seeDisposeListener#widgetDisposed(DisposeEvent)
*/
@Override
public void widgetDisposed(DisposeEvent e) {
/*
* 1GGYYWK: ITPJUI:ALL - Dismissing editor with code assist up causes lots of Internal Errors
*/
hide();
}
@Override
public void viewportChanged(int topIndex) {
if (System.currentTimeMillis() > fViewportListenerStartTime)
hide();
}
}
/**
* An implementation of IContentAssistListener
, this class is
* used to monitor key events in support of automatic activation
* of the content assistant. If enabled, the implementation utilizes a
* thread to watch for input characters matching the activation
* characters specified by the content assist processor, and if
* detected, will wait the indicated delay interval before
* activating the content assistant.
*/
class AutoAssistListener implements VerifyKeyListener, Runnable {
private Thread fThread;
private boolean fIsReset= false;
private Object fMutex= new Object();
private int fShowStyle;
private final static int SHOW_PROPOSALS= 1;
private final static int SHOW_CONTEXT_INFO= 2;
protected AutoAssistListener() {
}
protected void start(int showStyle) {
fShowStyle= showStyle;
fThread= new Thread(this, ContentAssistMessages.getString("ContentAssistant.assist_delay_timer_name")); //$NON-NLS-1$
fThread.start();
}
@Override
public void run() {
try {
while (true) {
synchronized (fMutex) {
if (fAutoActivationDelay != 0)
fMutex.wait(fAutoActivationDelay);
if (fIsReset) {
fIsReset= false;
continue;
}
}
showAssist(fShowStyle);
break;
}
} catch (InterruptedException e) {
}
fThread= null;
}
protected void reset(int showStyle) {
synchronized (fMutex) {
fShowStyle= showStyle;
fIsReset= true;
fMutex.notifyAll();
}
}
protected void stop() {
Thread threadToStop= fThread;
if (threadToStop != null)
threadToStop.interrupt();
}
private boolean contains(char[] characters, char character) {
if (characters != null) {
for (char c : characters) {
if (character == c) {
return true;
}
}
}
return false;
}
@Override
public void verifyKey(VerifyEvent e) {
// Only act on typed characters and ignore modifier-only events
if (e.character == 0 && (e.keyCode & SWT.KEYCODE_BIT) == 0)
return;
if (e.character != 0 && (e.stateMask == SWT.ALT))
return;
int showStyle;
int pos= fViewer.getSelectedRange().x;
char[] activation= getCompletionProposalAutoActivationCharacters(fViewer, pos);
if (contains(activation, e.character) && !fProposalPopup.isActive())
showStyle= SHOW_PROPOSALS;
else {
activation= getContextInformationAutoActivationCharacters(fViewer, pos);
if (contains(activation, e.character) && !fContextInfoPopup.isActive())
showStyle= SHOW_CONTEXT_INFO;
else {
if (fThread != null && fThread.isAlive())
stop();
return;
}
}
if (fThread != null && fThread.isAlive())
reset(showStyle);
else
start(showStyle);
}
protected void showAssist(final int showStyle) {
Control control= fViewer.getTextWidget();
Display d= control.getDisplay();
if (d != null) {
try {
d.syncExec(() -> {
if (showStyle == SHOW_PROPOSALS)
fProposalPopup.showProposals(true);
else if (showStyle == SHOW_CONTEXT_INFO)
fContextInfoPopup.showContextProposals(true);
});
} catch (SWTError e) {
}
}
}
}
/**
* The layout manager layouts the various
* windows associated with the content assistant based on the
* settings of the content assistant.
*/
class LayoutManager implements Listener {
// Presentation types.
/** proposal selector */
public final static int LAYOUT_PROPOSAL_SELECTOR= 0;
/** context selector */
public final static int LAYOUT_CONTEXT_SELECTOR= 1;
/** context info */
public final static int LAYOUT_CONTEXT_INFO_POPUP= 2;
int fContextType= LAYOUT_CONTEXT_SELECTOR;
Shell[] fShells= new Shell[3];
Object[] fPopups= new Object[3];
protected void add(Object popup, Shell shell, int type, int offset) {
Assert.isNotNull(popup);
Assert.isTrue(shell != null && !shell.isDisposed());
checkType(type);
if (fShells[type] != shell) {
if (fShells[type] != null)
fShells[type].removeListener(SWT.Dispose, this);
shell.addListener(SWT.Dispose, this);
fShells[type]= shell;
}
fPopups[type]= popup;
if (type == LAYOUT_CONTEXT_SELECTOR || type == LAYOUT_CONTEXT_INFO_POPUP)
fContextType= type;
layout(type, offset);
adjustListeners(type);
}
protected void checkType(int type) {
Assert.isTrue(type == LAYOUT_PROPOSAL_SELECTOR ||
type == LAYOUT_CONTEXT_SELECTOR || type == LAYOUT_CONTEXT_INFO_POPUP);
}
@Override
public void handleEvent(Event event) {
Widget source= event.widget;
source.removeListener(SWT.Dispose, this);
int type= getShellType(source);
checkType(type);
fShells[type]= null;
switch (type) {
case LAYOUT_PROPOSAL_SELECTOR:
if (fContextType == LAYOUT_CONTEXT_SELECTOR &&
Helper2.okToUse(fShells[LAYOUT_CONTEXT_SELECTOR])) {
// Restore event notification to the tip popup.
addContentAssistListener((IContentAssistListener2) fPopups[LAYOUT_CONTEXT_SELECTOR], CONTEXT_SELECTOR);
}
break;
case LAYOUT_CONTEXT_SELECTOR:
if (Helper2.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR])) {
if (fProposalPopupOrientation == PROPOSAL_STACKED)
layout(LAYOUT_PROPOSAL_SELECTOR, getSelectionOffset());
// Restore event notification to the proposal popup.
addContentAssistListener((IContentAssistListener2) fPopups[LAYOUT_PROPOSAL_SELECTOR], PROPOSAL_SELECTOR);
}
fContextType= LAYOUT_CONTEXT_INFO_POPUP;
break;
case LAYOUT_CONTEXT_INFO_POPUP:
if (Helper2.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR])) {
if (fContextInfoPopupOrientation == CONTEXT_INFO_BELOW)
layout(LAYOUT_PROPOSAL_SELECTOR, getSelectionOffset());
}
fContextType= LAYOUT_CONTEXT_SELECTOR;
break;
}
}
protected int getShellType(Widget shell) {
for (int i=0; i10
.
*
* @since 3.0
*/
public static final int WIDGET_PRIORITY= 10;
private static final int DEFAULT_AUTO_ACTIVATION_DELAY= 500;
private IInformationControlCreator fInformationControlCreator;
private int fAutoActivationDelay= DEFAULT_AUTO_ACTIVATION_DELAY;
private boolean fIsAutoActivated= false;
private boolean fIsAutoInserting= false;
private int fProposalPopupOrientation= PROPOSAL_OVERLAY;
private int fContextInfoPopupOrientation= CONTEXT_INFO_ABOVE;
private MapIInformationControlCreator
to be used to display context information.
*
* @return an IInformationControlCreator
to be used to display context information
*/
private IInformationControlCreator getInformationControlCreator() {
return parent -> new DefaultInformationControl(parent, false);
}
/**
* Sets the document partitioning this content assistant is using.
*
* @param partitioning the document partitioning for this content assistant
*/
public void setDocumentPartitioning(String partitioning) {
Assert.isNotNull(partitioning);
fPartitioning= partitioning;
}
@Override
public String getDocumentPartitioning() {
return fPartitioning;
}
/**
* Registers a given content assist processor for a particular content type.
* If there is already a processor registered for this type, the new processor
* is registered instead of the old one.
*
* @param processor the content assist processor to register, or null
to remove an existing one
* @param contentType the content type under which to register
*/
public void setContentAssistProcessor(IContentAssistProcessor processor, String contentType) {
Assert.isNotNull(contentType);
if (fProcessors == null)
fProcessors= new HashMap<>();
if (processor == null)
fProcessors.remove(contentType);
else
fProcessors.put(contentType, processor);
}
/*
* @see IContentAssistant#getContentAssistProcessor
*/
@Override
public IContentAssistProcessor getContentAssistProcessor(String contentType) {
if (fProcessors == null)
return null;
return fProcessors.get(contentType);
}
/**
* Enables the content assistant's auto activation mode.
*
* @param enabled indicates whether auto activation is enabled or not
*/
public void enableAutoActivation(boolean enabled) {
fIsAutoActivated= enabled;
manageAutoActivation(fIsAutoActivated);
}
/**
* Enables the content assistant's auto insertion mode. If enabled,
* the content assistant inserts a proposal automatically if it is
* the only proposal. In the case of ambiguities, the user must
* make the choice.
*
* @param enabled indicates whether auto insertion is enabled or not
* @since 2.0
*/
public void enableAutoInsert(boolean enabled) {
fIsAutoInserting= enabled;
}
/**
* Returns whether this content assistant is in the auto insertion
* mode or not.
*
* @return true
if in auto insertion mode
* @since 2.0
*/
boolean isAutoInserting() {
return fIsAutoInserting;
}
/**
* Installs and uninstall the listeners needed for auto activation.
*
* @param start true
if listeners must be installed, false
if they
* must be removed
* @since 2.0
*/
private void manageAutoActivation(boolean start) {
if (start) {
if (fViewer != null && fAutoAssistListener == null) {
fAutoAssistListener= new AutoAssistListener();
if (fViewer instanceof ITextViewerExtension) {
ITextViewerExtension extension= (ITextViewerExtension) fViewer;
extension.appendVerifyKeyListener(fAutoAssistListener);
} else {
StyledText textWidget= fViewer.getTextWidget();
if (Helper2.okToUse(textWidget))
textWidget.addVerifyKeyListener(fAutoAssistListener);
}
}
} else if (fAutoAssistListener != null) {
if (fViewer instanceof ITextViewerExtension) {
ITextViewerExtension extension= (ITextViewerExtension) fViewer;
extension.removeVerifyKeyListener(fAutoAssistListener);
} else {
StyledText textWidget= fViewer.getTextWidget();
if (Helper2.okToUse(textWidget))
textWidget.removeVerifyKeyListener(fAutoAssistListener);
}
fAutoAssistListener= null;
}
}
/**
* Sets the delay after which the content assistant is automatically invoked
* if the cursor is behind an auto activation character.
*
* @param delay the auto activation delay
*/
public void setAutoActivationDelay(int delay) {
fAutoActivationDelay= delay;
}
/**
* Sets the proposal pop-ups' orientation.
* The following values may be used:
* * proposal popup windows should overlay each other *
* any currently shown proposal popup should be closed *
* proposal popup windows should be vertical stacked, with no overlap, * beneath the line containing the current cursor location *
* context information popup should always appear above the line containing * the current cursor location *
* context information popup should always appear below the line containing * the current cursor location *
LayoutManager
.
*
* @param popup a content assist popup
* @param shell the shell of the content-assist popup
* @param type the type of popup
* @param visibleOffset the offset at which to layout the popup relative to the offset of the viewer's visible region
* @since 2.0
*/
void addToLayout(Object popup, Shell shell, int type, int visibleOffset) {
fLayoutManager.add(popup, shell, type, visibleOffset);
}
/**
* Layouts the registered popup of the given type relative to the
* given offset. The offset is relative to the offset of the viewer's visible region.
* Valid types are defined by LayoutManager
.
*
* @param type the type of popup to layout
* @param visibleOffset the offset at which to layout relative to the offset of the viewer's visible region
* @since 2.0
*/
void layout(int type, int visibleOffset) {
fLayoutManager.layout(type, visibleOffset);
}
/**
* Notifies the controller that a popup has lost focus.
*
* @param e the focus event
*/
void popupFocusLost(FocusEvent e) {
fCloser.focusLost(e);
}
/**
* Returns the offset of the selection relative to the offset of the visible region.
*
* @return the offset of the selection relative to the offset of the visible region
* @since 2.0
*/
int getSelectionOffset() {
StyledText text= fViewer.getTextWidget();
return text.getSelectionRange().x;
}
/**
* Returns whether the widget token could be acquired.
* The following are valid listener types:
* true
if the widget token could be acquired
* @since 2.0
*/
private boolean acquireWidgetToken(int type) {
switch (type) {
case CONTEXT_SELECTOR:
case PROPOSAL_SELECTOR:
if (fViewer instanceof IWidgetTokenOwner) {
IWidgetTokenOwner owner= (IWidgetTokenOwner) fViewer;
return owner.requestWidgetToken(this);
} else if (fViewer instanceof IWidgetTokenOwnerExtension) {
IWidgetTokenOwnerExtension extension= (IWidgetTokenOwnerExtension) fViewer;
return extension.requestWidgetToken(this, WIDGET_PRIORITY);
}
}
return true;
}
/**
* Registers a content assist listener.
* The following are valid listener types:
* true
if the listener could be added
*/
boolean addContentAssistListener(IContentAssistListener2 listener, int type) {
if (acquireWidgetToken(type)) {
fListeners[type]= listener;
if (getNumberOfListeners() == 1) {
fCloser= new Closer();
fCloser.install();
fViewer.setEventConsumer(fInternalListener);
installKeyListener();
}
return true;
}
return false;
}
/**
* Installs a key listener on the text viewer's widget.
*/
private void installKeyListener() {
if (!fKeyListenerHooked) {
StyledText text= fViewer.getTextWidget();
if (Helper2.okToUse(text)) {
if (fViewer instanceof ITextViewerExtension) {
ITextViewerExtension e= (ITextViewerExtension) fViewer;
e.prependVerifyKeyListener(fInternalListener);
} else {
text.addVerifyKeyListener(fInternalListener);
}
fKeyListenerHooked= true;
}
}
}
/**
* Releases the previously acquired widget token if the token
* is no longer necessary.
* The following are valid listener types:
* null
if no error has occurred
*/
String getErrorMessage() {
return fLastErrorMessage;
}
/**
* Returns the content assist processor for the content
* type of the specified document position.
*
* @param viewer the text viewer
* @param offset a offset within the document
* @return a content-assist processor or null
if none exists
*/
private IContentAssistProcessor getProcessor(ITextViewer viewer, int offset) {
try {
String type= TextUtilities.getContentType(viewer.getDocument(), getDocumentPartitioning(), offset, true);
return getContentAssistProcessor(type);
} catch (BadLocationException x) {
}
return null;
}
/**
* Returns an array of completion proposals computed based on
* the specified document position. The position is used to
* determine the appropriate content assist processor to invoke.
*
* @param viewer the viewer for which to compute the proposals
* @param position a document position
* @return an array of completion proposals
*
* @see IContentAssistProcessor#computeCompletionProposals
*/
ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int position) {
if (fProposals != null) {
return fProposals;
} else if (fProposalStrings != null) {
ICompletionProposal[] result= new ICompletionProposal[fProposalStrings.length];
for (int i= 0; i < fProposalStrings.length; i++) {
result[i]= new CompletionProposal(fProposalStrings[i], position, fProposalStrings[i].length(), fProposalStrings[i].length());
}
return result;
} else return null;
}
/**
* Returns an array of context information objects computed based
* on the specified document position. The position is used to determine
* the appropriate content assist processor to invoke.
*
* @param viewer the viewer for which to compute the context information
* @param position a document position
* @return an array of context information objects
*
* @see IContentAssistProcessor#computeContextInformation
*/
IContextInformation[] computeContextInformation(ITextViewer viewer, int position) {
fLastErrorMessage= null;
IContextInformation[] result= null;
IContentAssistProcessor p= getProcessor(viewer, position);
if (p != null) {
result= p.computeContextInformation(viewer, position);
fLastErrorMessage= p.getErrorMessage();
}
return result;
}
/**
* Returns the context information validator that should be used to
* determine when the currently displayed context information should
* be dismissed. The position is used to determine the appropriate
* content assist processor to invoke.
*
* @param textViewer the text viewer
* @param offset a document offset
* @return an validator
*
* @see IContentAssistProcessor#getContextInformationValidator
*/
IContextInformationValidator getContextInformationValidator(ITextViewer textViewer, int offset) {
IContentAssistProcessor p= getProcessor(textViewer, offset);
return p != null ? p.getContextInformationValidator() : null;
}
/**
* Returns the context information presenter that should be used to
* display context information. The position is used to determine the appropriate
* content assist processor to invoke.
*
* @param textViewer the text viewer
* @param offset a document offset
* @return a presenter
* @since 2.0
*/
IContextInformationPresenter getContextInformationPresenter(ITextViewer textViewer, int offset) {
IContextInformationValidator validator= getContextInformationValidator(textViewer, offset);
if (validator instanceof IContextInformationPresenter)
return (IContextInformationPresenter) validator;
return null;
}
/**
* Returns the characters which when typed by the user should automatically
* initiate proposing completions. The position is used to determine the
* appropriate content assist processor to invoke.
*
* @param textViewer the text viewer
* @param offset a document offset
* @return the auto activation characters
*
* @see IContentAssistProcessor#getCompletionProposalAutoActivationCharacters
*/
private char[] getCompletionProposalAutoActivationCharacters(ITextViewer textViewer, int offset) {
IContentAssistProcessor p= getProcessor(textViewer, offset);
return p != null ? p.getCompletionProposalAutoActivationCharacters() : null;
}
/**
* Returns the characters which when typed by the user should automatically
* initiate the presentation of context information. The position is used
* to determine the appropriate content assist processor to invoke.
*
* @param textViewer the text viewer
* @param offset a document offset
* @return the auto activation characters
*
* @see IContentAssistProcessor#getContextInformationAutoActivationCharacters
*/
private char[] getContextInformationAutoActivationCharacters(ITextViewer textViewer, int offset) {
IContentAssistProcessor p= getProcessor(textViewer, offset);
return p != null ? p.getContextInformationAutoActivationCharacters() : null;
}
@Override
public boolean requestWidgetToken(IWidgetTokenOwner owner) {
hidePossibleCompletions();
return true;
}
/**
* @param completionPosition the completion position
*/
public void setCompletionPosition(int completionPosition) {
fCompletionPosition= completionPosition;
}
/**
* @return the completion position
*/
public int getCompletionPosition() {
return fCompletionPosition;
}
/**
* @param proposals the proposals
*/
public void setCompletions(String[] proposals) {
fProposalStrings= proposals;
}
/**
* @param proposals the proposals
*/
public void setCompletions(ICompletionProposal[] proposals) {
fProposals= proposals;
}
@Override
public boolean requestWidgetToken(IWidgetTokenOwner owner, int priority) {
if (priority > WIDGET_PRIORITY) {
hidePossibleCompletions();
return true;
}
return false;
}
@Override
public boolean setFocus(IWidgetTokenOwner owner) {
if (fProposalPopup != null) {
fProposalPopup.setFocus();
return fProposalPopup.hasFocus();
}
return false;
}
/**
* Returns whether any popups controlled by the receiver have the input focus.
*
* @return true
if any of the managed popups have the focus, false
otherwise
*/
public boolean hasFocus() {
return (fProposalPopup != null && fProposalPopup.hasFocus())
|| (fContextInfoPopup != null && fContextInfoPopup.hasFocus());
}
@Override
public String completePrefix() {
return null;
}
/**
* @param proposal the proposal
*/
public void fireProposalChosen(ICompletionProposal proposal) {
Listtrue
if the support for colored labels is enabled, false
otherwise
* @since 3.4
*/
boolean isColoredLabelsSupportEnabled() {
return fIsColoredLabelsSupportEnabled;
}
/**
* Enables the support for colored labels in the proposal popup.
* Completion proposals can implement {@link ICompletionProposalExtension6} * to provide colored proposal labels.
* * @param isEnabled iftrue
the support for colored labels is enabled in the proposal popup
* @since 3.4
*/
public void enableColoredLabels(boolean isEnabled) {
fIsColoredLabelsSupportEnabled= isEnabled;
}
}