org.eclipse.jface.text.link.LinkedModeUI Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.eclipse.jface.text Show documentation
Show all versions of org.eclipse.jface.text Show documentation
This is org.eclipse.jface.text jar used by Scout SDK
The newest version!
/*******************************************************************************
* Copyright (c) 2000, 2012 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
* Danail Nachev - http://bugs.eclipse.org/348608
*******************************************************************************/
package org.eclipse.jface.text.link;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.custom.VerifyKeyListener;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.events.ShellListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.internal.text.link.contentassist.ContentAssistant2;
import org.eclipse.jface.internal.text.link.contentassist.IProposalListener;
import org.eclipse.jface.viewers.IPostSelectionProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPartitioningException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.DefaultPositionUpdater;
import org.eclipse.jface.text.DocumentCommand;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IAutoEditStrategy;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension3;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IEditingSupport;
import org.eclipse.jface.text.IEditingSupportRegistry;
import org.eclipse.jface.text.IPositionUpdater;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.IRewriteTarget;
import org.eclipse.jface.text.ITextInputListener;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.ITextViewerExtension;
import org.eclipse.jface.text.ITextViewerExtension2;
import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension6;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModelExtension;
import org.eclipse.jface.text.source.ISourceViewer;
/**
* The UI for linked mode. Detects events that influence behavior of the linked mode
* UI and acts upon them.
*
* LinkedModeUI
relies on all added
* LinkedModeUITarget
s to provide implementations of
* ITextViewer
that implement ITextViewerExtension
,
* and the documents being edited to implement IDocumentExtension3
.
*
*
* Clients may instantiate and extend this class.
*
*
* @since 3.0
*/
public class LinkedModeUI {
/* cycle constants */
/**
* Constant indicating that this UI should never cycle from the last
* position to the first and vice versa.
*/
public static final Object CYCLE_NEVER= new Object();
/**
* Constant indicating that this UI should always cycle from the last
* position to the first and vice versa.
*/
public static final Object CYCLE_ALWAYS= new Object();
/**
* Constant indicating that this UI should cycle from the last position to
* the first and vice versa if its model is not nested.
*/
public static final Object CYCLE_WHEN_NO_PARENT= new Object();
/**
* Listener that gets notified when the linked mode UI switches its focus position.
*
* Clients may implement this interface.
*
*/
public interface ILinkedModeUIFocusListener {
/**
* Called when the UI for the linked mode leaves a linked position.
*
* @param position the position being left
* @param target the target where position
resides in
*/
void linkingFocusLost(LinkedPosition position, LinkedModeUITarget target);
/**
* Called when the UI for the linked mode gives focus to a linked position.
*
* @param position the position being entered
* @param target the target where position
resides in
*/
void linkingFocusGained(LinkedPosition position, LinkedModeUITarget target);
}
/**
* Null object implementation of focus listener.
*/
private static final class EmtpyFocusListener implements ILinkedModeUIFocusListener {
@Override
public void linkingFocusGained(LinkedPosition position, LinkedModeUITarget target) {
// ignore
}
@Override
public void linkingFocusLost(LinkedPosition position, LinkedModeUITarget target) {
// ignore
}
}
/**
* A link target consists of a viewer and gets notified if the linked mode UI on
* it is being shown.
*
* Clients may extend this class.
*
* @since 3.0
*/
public static abstract class LinkedModeUITarget implements ILinkedModeUIFocusListener {
/**
* Returns the viewer represented by this target, never null
.
*
* @return the viewer associated with this target.
*/
public abstract ITextViewer getViewer();
/**
* The viewer's text widget is initialized when the UI first connects
* to the viewer and never changed thereafter. This is to keep the
* reference of the widget that we have registered our listeners with,
* as the viewer, when it gets disposed, does not remember it, resulting
* in a situation where we cannot uninstall the listeners and a memory leak.
*/
StyledText fWidget;
/** The cached shell - same reason as fWidget. */
Shell fShell;
/** The registered listener, or null
. */
KeyListener fKeyListener;
/** The cached custom annotation model. */
LinkedPositionAnnotations fAnnotationModel;
}
private static final class EmptyTarget extends LinkedModeUITarget {
private ITextViewer fTextViewer;
/**
* @param viewer the viewer
*/
public EmptyTarget(ITextViewer viewer) {
Assert.isNotNull(viewer);
fTextViewer= viewer;
}
@Override
public ITextViewer getViewer() {
return fTextViewer;
}
@Override
public void linkingFocusLost(LinkedPosition position, LinkedModeUITarget target) {
}
@Override
public void linkingFocusGained(LinkedPosition position, LinkedModeUITarget target) {
}
}
/**
* Listens for state changes in the model.
*/
private final class ExitListener implements ILinkedModeListener {
@Override
public void left(LinkedModeModel model, int flags) {
leave(ILinkedModeListener.EXIT_ALL | flags);
}
@Override
public void suspend(LinkedModeModel model) {
disconnect();
redraw();
}
@Override
public void resume(LinkedModeModel model, int flags) {
if ((flags & ILinkedModeListener.EXIT_ALL) != 0) {
leave(flags);
} else {
connect();
if ((flags & ILinkedModeListener.SELECT) != 0)
select();
ensureAnnotationModelInstalled();
redraw();
}
}
}
/**
* Exit flags returned if a custom exit policy wants to exit linked mode.
*
* Clients may instantiate this class.
*
*/
public static class ExitFlags {
/** The flags to return in the leave
method. */
public int flags;
/** The doit flag of the checked VerifyKeyEvent
. */
public boolean doit;
/**
* Creates a new instance.
*
* @param flags the exit flags
* @param doit the doit flag for the verify event
*/
public ExitFlags(int flags, boolean doit) {
this.flags= flags;
this.doit= doit;
}
}
/**
* An exit policy can be registered by a caller to get custom exit
* behavior.
*
* Clients may implement this interface.
*
*/
public interface IExitPolicy {
/**
* Checks whether the linked mode should be left after receiving the
* given VerifyEvent
and selection. Note that the event
* carries widget coordinates as opposed to offset
and
* length
which are document coordinates.
*
* @param model the linked mode model
* @param event the verify event
* @param offset the offset of the current selection
* @param length the length of the current selection
* @return valid exit flags or null
if no special action
* should be taken
*/
ExitFlags doExit(LinkedModeModel model, VerifyEvent event, int offset, int length);
}
/**
* A NullObject implementation of IExitPolicy
.
*/
private static class NullExitPolicy implements IExitPolicy {
@Override
public ExitFlags doExit(LinkedModeModel model, VerifyEvent event, int offset, int length) {
return null;
}
}
/**
* Listens for shell events and acts upon them.
*/
private class Closer implements ShellListener, ITextInputListener {
@Override
public void shellActivated(ShellEvent e) {
}
@Override
public void shellClosed(ShellEvent e) {
leave(ILinkedModeListener.EXIT_ALL);
}
@Override
public void shellDeactivated(ShellEvent e) {
// TODO re-enable after debugging
// if (true) return;
// from LinkedPositionUI:
// don't deactivate on focus lost, since the proposal popups may take focus
// plus: it doesn't hurt if you can check with another window without losing linked mode
// since there is no intrusive popup sticking out.
// need to check first what happens on reentering based on an open action
// Seems to be no problem
// Better:
// Check with content assistant and only leave if its not the proposal shell that took the
// focus away.
StyledText text;
final ITextViewer viewer;
Display display;
if (fCurrentTarget == null || (text= fCurrentTarget.fWidget) == null
|| text.isDisposed() || (display= text.getDisplay()) == null
|| display.isDisposed()
|| (viewer= fCurrentTarget.getViewer()) == null)
{
leave(ILinkedModeListener.EXIT_ALL);
}
else
{
// Post in UI thread since the assistant popup will only get the focus after we lose it.
display.asyncExec(new Runnable() {
@Override
public void run() {
if (fIsActive && viewer instanceof IEditingSupportRegistry) {
IEditingSupport[] helpers= ((IEditingSupportRegistry) viewer).getRegisteredSupports();
for (int i= 0; i < helpers.length; i++) {
if (helpers[i].ownsFocusShell())
return;
}
}
// else
leave(ILinkedModeListener.EXIT_ALL);
}
});
}
}
@Override
public void shellDeiconified(ShellEvent e) {
}
@Override
public void shellIconified(ShellEvent e) {
leave(ILinkedModeListener.EXIT_ALL);
}
@Override
public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {
leave(ILinkedModeListener.EXIT_ALL);
}
@Override
public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
}
}
/**
* @since 3.1
*/
private class DocumentListener implements IDocumentListener {
@Override
public void documentAboutToBeChanged(DocumentEvent event) {
// default behavior: any document change outside a linked position
// causes us to exit
int end= event.getOffset() + event.getLength();
for (int offset= event.getOffset(); offset <= end; offset++) {
if (!fModel.anyPositionContains(offset)) {
ITextViewer viewer= fCurrentTarget.getViewer();
if (fFramePosition != null && viewer instanceof IEditingSupportRegistry) {
IEditingSupport[] helpers= ((IEditingSupportRegistry) viewer).getRegisteredSupports();
for (int i= 0; i < helpers.length; i++) {
if (helpers[i].isOriginator(null, new Region(fFramePosition.getOffset(), fFramePosition.getLength())))
return;
}
}
leave(ILinkedModeListener.EXTERNAL_MODIFICATION);
return;
}
}
// Make sure that any document change is done inside a compound change
beginCompoundChangeIfNeeded();
}
@Override
public void documentChanged(DocumentEvent event) {
}
}
/**
* Listens for key events, checks the exit policy for custom exit
* strategies but defaults to handling Tab, Enter, and Escape.
*/
private class KeyListener implements VerifyKeyListener {
private boolean fIsEnabled= true;
@Override
public void verifyKey(VerifyEvent event) {
if (!event.doit || !fIsEnabled)
return;
Point selection= fCurrentTarget.getViewer().getSelectedRange();
int offset= selection.x;
int length= selection.y;
// if the custom exit policy returns anything, use that
ExitFlags exitFlags= fExitPolicy.doExit(fModel, event, offset, length);
if (exitFlags != null) {
leave(exitFlags.flags);
event.doit= exitFlags.doit;
return;
}
// standard behavior:
// (Shift+)Tab: jumps from position to position, depending on cycle mode
// Enter: accepts all entries and leaves all (possibly stacked) environments, the last sets the caret
// Esc: accepts all entries and leaves all (possibly stacked) environments, the caret stays
// ? what do we do to leave one level of a cycling model that is stacked?
// -> This is only the case if the level was set up with forced cycling (CYCLE_ALWAYS), in which case
// the caller is sure that one does not need by-level exiting.
switch (event.character) {
// [SHIFT-]TAB = hop between edit boxes
case 0x09:
if (!(fExitPosition != null && fExitPosition.includes(offset)) && !fModel.anyPositionContains(offset)) {
// outside any edit box -> leave (all? TODO should only leave the affected, level and forward to the next upper)
leave(ILinkedModeListener.EXIT_ALL);
break;
}
if (event.stateMask == SWT.SHIFT)
previous();
else
next();
event.doit= false;
break;
// ENTER
case 0x0A:
// Ctrl+Enter on WinXP
case 0x0D:
// if ((fExitPosition != null && fExitPosition.includes(offset)) || !fModel.anyPositionContains(offset)) {
if (!fModel.anyPositionContains(offset)) {
// if ((fExitPosition == null || !fExitPosition.includes(offset)) && !fModel.anyPositionContains(offset)) {
// outside any edit box or on exit position -> leave (all? TODO should only leave the affected, level and forward to the next upper)
leave(ILinkedModeListener.EXIT_ALL);
break;
}
// normal case: exit entire stack and put caret to final position
leave(ILinkedModeListener.EXIT_ALL | ILinkedModeListener.UPDATE_CARET);
event.doit= false;
break;
// ESC
case 0x1B:
// exit entire stack and leave caret
leave(ILinkedModeListener.EXIT_ALL);
event.doit= false;
break;
default:
if (event.character != 0) {
if (!controlUndoBehavior(offset, length)) {
leave(ILinkedModeListener.EXIT_ALL);
break;
}
}
}
}
private boolean controlUndoBehavior(int offset, int length) {
LinkedPosition position= fModel.findPosition(new LinkedPosition(fCurrentTarget.getViewer().getDocument(), offset, length, LinkedPositionGroup.NO_STOP));
if (position != null) {
// if the last position is not the same and there is an open change: close it.
if (!position.equals(fPreviousPosition))
endCompoundChangeIfNeeded();
beginCompoundChangeIfNeeded();
}
fPreviousPosition= position;
return fPreviousPosition != null;
}
/**
* @param enabled the new enabled state
*/
public void setEnabled(boolean enabled) {
fIsEnabled= enabled;
}
}
/**
* Installed as post selection listener on the watched viewer. Updates the
* linked position after cursor movement, even to positions not in the
* iteration list.
*/
private class MySelectionListener implements ISelectionChangedListener {
@Override
public void selectionChanged(SelectionChangedEvent event) {
ISelection selection= event.getSelection();
if (selection instanceof ITextSelection) {
ITextSelection textsel= (ITextSelection) selection;
if (event.getSelectionProvider() instanceof ITextViewer) {
IDocument doc= ((ITextViewer) event.getSelectionProvider()).getDocument();
if (doc != null) {
int offset= textsel.getOffset();
int length= textsel.getLength();
if (offset >= 0 && length >= 0) {
LinkedPosition find= new LinkedPosition(doc, offset, length, LinkedPositionGroup.NO_STOP);
LinkedPosition pos= fModel.findPosition(find);
if (pos == null && fExitPosition != null && fExitPosition.includes(find))
pos= fExitPosition;
if (pos != null)
switchPosition(pos, false, false);
}
}
}
}
}
}
private class ProposalListener implements IProposalListener {
@Override
public void proposalChosen(ICompletionProposal proposal) {
next();
}
}
/** The current viewer. */
private LinkedModeUITarget fCurrentTarget;
/**
* The manager of the linked positions we provide a UI for.
* @since 3.1
*/
private LinkedModeModel fModel;
/** The set of viewers we manage. */
private LinkedModeUITarget[] fTargets;
/** The iterator over the tab stop positions. */
private TabStopIterator fIterator;
/* Our team of event listeners */
/** The shell listener. */
private Closer fCloser= new Closer();
/** The linked mode listener. */
private ILinkedModeListener fLinkedListener= new ExitListener();
/** The selection listener. */
private MySelectionListener fSelectionListener= new MySelectionListener();
/** The content assist listener. */
private ProposalListener fProposalListener= new ProposalListener();
/**
* The document listener.
* @since 3.1
*/
private IDocumentListener fDocumentListener= new DocumentListener();
/** The last caret position, used by fCaretListener. */
private final Position fCaretPosition= new Position(0, 0);
/** The exit policy to control custom exit behavior */
private IExitPolicy fExitPolicy= new NullExitPolicy();
/** The current frame position shown in the UI, or null
. */
private LinkedPosition fFramePosition;
/** The last visited position, used for undo / redo. */
private LinkedPosition fPreviousPosition;
/** The content assistant used to show proposals. */
private ContentAssistant2 fAssistant;
/** The exit position. */
private LinkedPosition fExitPosition;
/** State indicator to prevent multiple invocation of leave. */
private boolean fIsActive= false;
/** The position updater for the exit position. */
private IPositionUpdater fPositionUpdater= new DefaultPositionUpdater(getCategory());
/** Whether to show context info. */
private boolean fDoContextInfo= false;
/** Whether we have begun a compound change, but not yet closed. */
private boolean fHasOpenCompoundChange= false;
/** The position listener. */
private ILinkedModeUIFocusListener fPositionListener= new EmtpyFocusListener();
private IAutoEditStrategy fAutoEditVetoer= new IAutoEditStrategy() {
@Override
public void customizeDocumentCommand(IDocument document, DocumentCommand command) {
// invalidate the change to ensure that the change is performed on the document only.
if (fModel.anyPositionContains(command.offset)) {
command.doit= false;
command.caretOffset= command.offset + command.length;
}
}
};
/** Whether this UI is in simple highlighting mode or not. */
private boolean fSimple;
/**
* Creates a new UI on the given model and the set of viewers. The model
* must provide a tab stop sequence with a non-empty list of tab stops.
*
* @param model the linked mode model
* @param targets the non-empty list of targets upon which the linked mode
* UI should act
*/
public LinkedModeUI(LinkedModeModel model, LinkedModeUITarget[] targets) {
constructor(model, targets);
}
/**
* Convenience constructor for just one viewer.
*
* @param model the linked mode model
* @param viewer the viewer upon which the linked mode UI should act
*/
public LinkedModeUI(LinkedModeModel model, ITextViewer viewer) {
constructor(model, new LinkedModeUITarget[]{new EmptyTarget(viewer)});
}
/**
* Convenience constructor for multiple viewers.
*
* @param model the linked mode model
* @param viewers the non-empty list of viewers upon which the linked mode
* UI should act
*/
public LinkedModeUI(LinkedModeModel model, ITextViewer[] viewers) {
LinkedModeUITarget[] array= new LinkedModeUITarget[viewers.length];
for (int i= 0; i < array.length; i++) {
array[i]= new EmptyTarget(viewers[i]);
}
constructor(model, array);
}
/**
* Convenience constructor for one target.
*
* @param model the linked mode model
* @param target the target upon which the linked mode UI should act
*/
public LinkedModeUI(LinkedModeModel model, LinkedModeUITarget target) {
constructor(model, new LinkedModeUITarget[]{target});
}
/**
* This does the actual constructor work.
*
* @param model the linked mode model
* @param targets the non-empty array of targets upon which the linked mode UI
* should act
*/
private void constructor(LinkedModeModel model, LinkedModeUITarget[] targets) {
Assert.isNotNull(model);
Assert.isNotNull(targets);
Assert.isTrue(targets.length > 0);
Assert.isTrue(model.getTabStopSequence().size() > 0);
fModel= model;
fTargets= targets;
fCurrentTarget= targets[0];
fIterator= new TabStopIterator(fModel.getTabStopSequence());
fIterator.setCycling(!fModel.isNested());
fModel.addLinkingListener(fLinkedListener);
fAssistant= new ContentAssistant2();
fAssistant.addProposalListener(fProposalListener);
// TODO find a way to set up content assistant.
// fAssistant.setDocumentPartitioning(IJavaPartitions.JAVA_PARTITIONING);
fCaretPosition.delete();
}
/**
* Starts this UI on the first position.
*/
public void enter() {
fIsActive= true;
connect();
next();
}
/**
* Sets an IExitPolicy
to customize the exit behavior of
* this linked mode UI.
*
* @param policy the exit policy to use.
*/
public void setExitPolicy(IExitPolicy policy) {
fExitPolicy= policy;
}
/**
* Sets the exit position to move the caret to when linked mode mode is
* exited.
*
* @param target the target where the exit position is located
* @param offset the offset of the exit position
* @param length the length of the exit position (in case there should be a
* selection)
* @param sequence set to the tab stop position of the exit position, or
* LinkedPositionGroup.NO_STOP
if there should be no
* tab stop.
* @throws BadLocationException if the position is not valid in the viewer's
* document
*/
public void setExitPosition(LinkedModeUITarget target, int offset, int length, int sequence) throws BadLocationException {
// remove any existing exit position
if (fExitPosition != null) {
fExitPosition.getDocument().removePosition(fExitPosition);
fIterator.removePosition(fExitPosition);
fExitPosition= null;
}
IDocument doc= target.getViewer().getDocument();
if (doc == null)
return;
fExitPosition= new LinkedPosition(doc, offset, length, sequence);
doc.addPosition(fExitPosition); // gets removed in leave()
if (sequence != LinkedPositionGroup.NO_STOP)
fIterator.addPosition(fExitPosition);
}
/**
* Sets the exit position to move the caret to when linked mode is exited.
*
* @param viewer the viewer where the exit position is located
* @param offset the offset of the exit position
* @param length the length of the exit position (in case there should be a
* selection)
* @param sequence set to the tab stop position of the exit position, or
* LinkedPositionGroup.NO_STOP
if there should be no tab stop.
* @throws BadLocationException if the position is not valid in the
* viewer's document
*/
public void setExitPosition(ITextViewer viewer, int offset, int length, int sequence) throws BadLocationException {
setExitPosition(new EmptyTarget(viewer), offset, length, sequence);
}
/**
* Sets the cycling mode to either of CYCLING_ALWAYS
,
* CYCLING_NEVER
, or CYCLING_WHEN_NO_PARENT
,
* which is the default.
*
* @param mode the new cycling mode.
*/
public void setCyclingMode(Object mode) {
if (mode != CYCLE_ALWAYS && mode != CYCLE_NEVER && mode != CYCLE_WHEN_NO_PARENT)
throw new IllegalArgumentException();
if (mode == CYCLE_ALWAYS || mode == CYCLE_WHEN_NO_PARENT && !fModel.isNested())
fIterator.setCycling(true);
else
fIterator.setCycling(false);
}
void next() {
if (fIterator.hasNext(fFramePosition)) {
switchPosition(fIterator.next(fFramePosition), true, true);
return;
}
leave(ILinkedModeListener.UPDATE_CARET);
}
void previous() {
if (fIterator.hasPrevious(fFramePosition)) {
switchPosition(fIterator.previous(fFramePosition), true, true);
} else
// dont't update caret, but rather select the current frame
leave(ILinkedModeListener.SELECT);
}
private void triggerContextInfo() {
fAssistant.showContextInformation();
}
/** Trigger content assist on choice positions */
private void triggerContentAssist() {
if (fFramePosition instanceof ProposalPosition) {
ProposalPosition pp= (ProposalPosition) fFramePosition;
ICompletionProposal[] choices= pp.getChoices();
if (choices != null && choices.length > 0) {
fAssistant.setCompletions(choices);
fAssistant.showPossibleCompletions();
return;
}
}
fAssistant.setCompletions(new ICompletionProposal[0]);
fAssistant.hidePossibleCompletions();
}
private void switchPosition(LinkedPosition pos, boolean select, boolean showProposals) {
Assert.isNotNull(pos);
if (pos.equals(fFramePosition))
return;
if (fFramePosition != null && fCurrentTarget != null)
fPositionListener.linkingFocusLost(fFramePosition, fCurrentTarget);
// undo
endCompoundChangeIfNeeded();
redraw(); // redraw current position being left - usually not needed
IDocument oldDoc= fFramePosition == null ? null : fFramePosition.getDocument();
IDocument newDoc= pos.getDocument();
switchViewer(oldDoc, newDoc, pos);
fFramePosition= pos;
if (select)
select();
if (fFramePosition == fExitPosition && !fIterator.isCycling())
leave(ILinkedModeListener.NONE);
else {
redraw(); // redraw new position
ensureAnnotationModelInstalled();
}
if (showProposals)
triggerContentAssist();
if (fFramePosition != fExitPosition && fDoContextInfo)
triggerContextInfo();
if (fFramePosition != null && fCurrentTarget != null)
fPositionListener.linkingFocusGained(fFramePosition, fCurrentTarget);
}
private void ensureAnnotationModelInstalled() {
LinkedPositionAnnotations lpa= fCurrentTarget.fAnnotationModel;
if (lpa != null) {
ITextViewer viewer= fCurrentTarget.getViewer();
if (viewer instanceof ISourceViewer) {
ISourceViewer sv= (ISourceViewer) viewer;
IAnnotationModel model= sv.getAnnotationModel();
if (model instanceof IAnnotationModelExtension) {
IAnnotationModelExtension ext= (IAnnotationModelExtension) model;
IAnnotationModel ourModel= ext.getAnnotationModel(getUniqueKey());
if (ourModel == null) {
ext.addAnnotationModel(getUniqueKey(), lpa);
}
}
}
}
}
private void uninstallAnnotationModel(LinkedModeUITarget target) {
ITextViewer viewer= target.getViewer();
if (viewer instanceof ISourceViewer) {
ISourceViewer sv= (ISourceViewer) viewer;
IAnnotationModel model= sv.getAnnotationModel();
if (model instanceof IAnnotationModelExtension) {
IAnnotationModelExtension ext= (IAnnotationModelExtension) model;
ext.removeAnnotationModel(getUniqueKey());
}
}
}
private void switchViewer(IDocument oldDoc, IDocument newDoc, LinkedPosition pos) {
if (oldDoc != newDoc) {
// redraw current document with new position before switching viewer
if (fCurrentTarget.fAnnotationModel != null)
fCurrentTarget.fAnnotationModel.switchToPosition(fModel, pos);
LinkedModeUITarget target= null;
for (int i= 0; i < fTargets.length; i++) {
if (fTargets[i].getViewer().getDocument() == newDoc) {
target= fTargets[i];
break;
}
}
if (target != fCurrentTarget) {
disconnect();
fCurrentTarget= target;
target.linkingFocusLost(fFramePosition, target);
connect();
ensureAnnotationModelInstalled();
if (fCurrentTarget != null)
fCurrentTarget.linkingFocusGained(pos, fCurrentTarget);
}
}
}
private void select() {
ITextViewer viewer= fCurrentTarget.getViewer();
if (viewer instanceof ITextViewerExtension5) {
ITextViewerExtension5 extension5= (ITextViewerExtension5) viewer;
extension5.exposeModelRange(new Region(fFramePosition.offset, fFramePosition.length));
} else if (!viewer.overlapsWithVisibleRegion(fFramePosition.offset, fFramePosition.length)) {
viewer.resetVisibleRegion();
}
viewer.revealRange(fFramePosition.offset, fFramePosition.length);
viewer.setSelectedRange(fFramePosition.offset, fFramePosition.length);
}
private void redraw() {
if (fCurrentTarget.fAnnotationModel != null)
fCurrentTarget.fAnnotationModel.switchToPosition(fModel, fFramePosition);
}
private void connect() {
Assert.isNotNull(fCurrentTarget);
ITextViewer viewer= fCurrentTarget.getViewer();
Assert.isNotNull(viewer);
fCurrentTarget.fWidget= viewer.getTextWidget();
if (fCurrentTarget.fWidget == null)
leave(ILinkedModeListener.EXIT_ALL);
if (fCurrentTarget.fKeyListener == null) {
fCurrentTarget.fKeyListener= new KeyListener();
((ITextViewerExtension) viewer).prependVerifyKeyListener(fCurrentTarget.fKeyListener);
} else
fCurrentTarget.fKeyListener.setEnabled(true);
registerAutoEditVetoer(viewer);
((IPostSelectionProvider) viewer).addPostSelectionChangedListener(fSelectionListener);
createAnnotationModel();
showSelection();
fCurrentTarget.fShell= fCurrentTarget.fWidget.getShell();
if (fCurrentTarget.fShell == null)
leave(ILinkedModeListener.EXIT_ALL);
fCurrentTarget.fShell.addShellListener(fCloser);
fAssistant.install(viewer);
viewer.addTextInputListener(fCloser);
viewer.getDocument().addDocumentListener(fDocumentListener);
}
/**
* Reveals the selection on the current target's widget, if it is valid.
*/
private void showSelection() {
final StyledText widget= fCurrentTarget.fWidget;
if (widget == null || widget.isDisposed())
return;
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=132263
widget.getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
if (!widget.isDisposed())
try {
widget.showSelection();
} catch (IllegalArgumentException e) {
/*
* See https://bugs.eclipse.org/bugs/show_bug.cgi?id=66914
* if the StyledText is in setRedraw(false) mode, its
* selection may not be up2date and calling showSelection
* will throw an IAE.
* We don't have means to find out whether the selection is valid
* or whether the widget is redrawing or not therefore we try
* and ignore an IAE.
*/
}
}
});
}
/**
* Registers our auto edit vetoer with the viewer.
*
* @param viewer the viewer we want to veto ui-triggered changes within
* linked positions
*/
private void registerAutoEditVetoer(ITextViewer viewer) {
try {
String[] contentTypes= getContentTypes(viewer.getDocument());
if (viewer instanceof ITextViewerExtension2) {
ITextViewerExtension2 vExtension= ((ITextViewerExtension2) viewer);
for (int i= 0; i < contentTypes.length; i++) {
vExtension.prependAutoEditStrategy(fAutoEditVetoer, contentTypes[i]);
}
} else {
Assert.isTrue(false);
}
} catch (BadPartitioningException e) {
leave(ILinkedModeListener.EXIT_ALL);
}
}
private void unregisterAutoEditVetoer(ITextViewer viewer) {
try {
String[] contentTypes= getContentTypes(viewer.getDocument());
if (viewer instanceof ITextViewerExtension2) {
ITextViewerExtension2 vExtension= ((ITextViewerExtension2) viewer);
for (int i= 0; i < contentTypes.length; i++) {
vExtension.removeAutoEditStrategy(fAutoEditVetoer, contentTypes[i]);
}
} else {
Assert.isTrue(false);
}
} catch (BadPartitioningException e) {
leave(ILinkedModeListener.EXIT_ALL);
}
}
/**
* Returns all possible content types of document
.
*
* @param document the document
* @return all possible content types of document
* @throws BadPartitioningException if partitioning is invalid for this document
* @since 3.1
*/
private String[] getContentTypes(IDocument document) throws BadPartitioningException {
if (document instanceof IDocumentExtension3) {
IDocumentExtension3 ext= (IDocumentExtension3) document;
String[] partitionings= ext.getPartitionings();
Set contentTypes= new HashSet<>(20);
for (int i= 0; i < partitionings.length; i++) {
contentTypes.addAll(Arrays.asList(ext.getLegalContentTypes(partitionings[i])));
}
contentTypes.add(IDocument.DEFAULT_CONTENT_TYPE);
return contentTypes.toArray(new String[contentTypes.size()]);
}
return document.getLegalContentTypes();
}
private void createAnnotationModel() {
if (fCurrentTarget.fAnnotationModel == null) {
LinkedPositionAnnotations lpa= new LinkedPositionAnnotations();
if (fSimple) {
lpa.markExitTarget(true);
lpa.markFocus(false);
lpa.markSlaves(false);
lpa.markTargets(false);
}
lpa.setTargets(fIterator.getPositions());
lpa.setExitTarget(fExitPosition);
lpa.connect(fCurrentTarget.getViewer().getDocument());
fCurrentTarget.fAnnotationModel= lpa;
}
}
private String getUniqueKey() {
return "linked.annotationmodelkey."+toString(); //$NON-NLS-1$
}
private void disconnect() {
Assert.isNotNull(fCurrentTarget);
ITextViewer viewer= fCurrentTarget.getViewer();
Assert.isNotNull(viewer);
viewer.getDocument().removeDocumentListener(fDocumentListener);
fAssistant.uninstall();
fAssistant.removeProposalListener(fProposalListener);
fCurrentTarget.fWidget= null;
Shell shell= fCurrentTarget.fShell;
fCurrentTarget.fShell= null;
if (shell != null && !shell.isDisposed())
shell.removeShellListener(fCloser);
// this one is asymmetric: we don't install the model in
// connect, but leave it to its callers to ensure they
// have the model installed if they need it
uninstallAnnotationModel(fCurrentTarget);
unregisterAutoEditVetoer(viewer);
// don't remove the verify key listener to let it keep its position
// in the listener queue
if (fCurrentTarget.fKeyListener != null)
fCurrentTarget.fKeyListener.setEnabled(false);
((IPostSelectionProvider) viewer).removePostSelectionChangedListener(fSelectionListener);
redraw();
}
void leave(final int flags) {
if (!fIsActive)
return;
fIsActive= false;
endCompoundChangeIfNeeded();
Display display= null;
if (fCurrentTarget.fWidget != null && !fCurrentTarget.fWidget.isDisposed())
display= fCurrentTarget.fWidget.getDisplay();
if (fCurrentTarget.fAnnotationModel != null)
fCurrentTarget.fAnnotationModel.removeAllAnnotations();
disconnect();
for (int i= 0; i < fTargets.length; i++) {
LinkedModeUITarget target= fTargets[i];
ITextViewer viewer= target.getViewer();
if (target.fKeyListener != null) {
((ITextViewerExtension) viewer).removeVerifyKeyListener(target.fKeyListener);
target.fKeyListener= null;
}
viewer.removeTextInputListener(fCloser);
}
for (int i= 0; i < fTargets.length; i++) {
if (fTargets[i].fAnnotationModel != null) {
fTargets[i].fAnnotationModel.removeAllAnnotations();
fTargets[i].fAnnotationModel.disconnect(fTargets[i].getViewer().getDocument());
fTargets[i].fAnnotationModel= null;
}
uninstallAnnotationModel(fTargets[i]);
}
if ((flags & ILinkedModeListener.UPDATE_CARET) != 0 && fExitPosition != null && fFramePosition != fExitPosition && !fExitPosition.isDeleted())
switchPosition(fExitPosition, true, false);
final List docs= new ArrayList<>();
for (int i= 0; i < fTargets.length; i++) {
IDocument doc= fTargets[i].getViewer().getDocument();
if (doc != null)
docs.add(doc);
}
fModel.stopForwarding(flags);
Runnable runnable= new Runnable() {
@Override
public void run() {
if (fExitPosition != null)
fExitPosition.getDocument().removePosition(fExitPosition);
for (Iterator iter = docs.iterator(); iter.hasNext(); ) {
IDocument doc= iter.next();
doc.removePositionUpdater(fPositionUpdater);
boolean uninstallCat= false;
String[] cats= doc.getPositionCategories();
for (int j= 0; j < cats.length; j++) {
if (getCategory().equals(cats[j])) {
uninstallCat= true;
break;
}
}
if (uninstallCat)
try {
doc.removePositionCategory(getCategory());
} catch (BadPositionCategoryException e) {
// ignore
}
}
fModel.exit(flags);
}
};
// remove positions (both exit positions AND linked positions in the
// model) asynchronously to make sure that the annotation painter
// gets correct document offsets.
if (display != null)
display.asyncExec(runnable);
else
runnable.run();
}
private void endCompoundChangeIfNeeded() {
if (fHasOpenCompoundChange) {
ITextViewerExtension extension= (ITextViewerExtension) fCurrentTarget.getViewer();
IRewriteTarget target= extension.getRewriteTarget();
target.endCompoundChange();
fHasOpenCompoundChange= false;
}
}
private void beginCompoundChangeIfNeeded() {
if (!fHasOpenCompoundChange) {
ITextViewerExtension extension= (ITextViewerExtension) fCurrentTarget.getViewer();
IRewriteTarget target= extension.getRewriteTarget();
target.beginCompoundChange();
fHasOpenCompoundChange= true;
}
}
/**
* Returns the currently selected region or null
.
*
* @return the currently selected region or null
*/
public IRegion getSelectedRegion() {
if (fFramePosition != null)
return new Region(fFramePosition.getOffset(), fFramePosition.getLength());
if (fExitPosition != null)
return new Region(fExitPosition.getOffset(), fExitPosition.getLength());
return null;
}
private String getCategory() {
return toString();
}
/**
* Sets the context info property. If set to true
, context
* info will be invoked on the current target's viewer whenever a position
* is switched.
*
* @param doContextInfo true
if context information should be
* displayed
*/
public void setDoContextInfo(boolean doContextInfo) {
fDoContextInfo= doContextInfo;
}
/**
* Sets the focus callback which will get informed when the focus of the
* linked mode UI changes.
*
* If there is a listener installed already, it will be replaced.
*
*
* @param listener the new listener, never null
.
*/
protected void setPositionListener(ILinkedModeUIFocusListener listener) {
Assert.isNotNull(listener);
fPositionListener= listener;
}
/**
* Sets the "simple" mode of the receiver. A linked mode UI in simple mode
* merely draws the exit position, but not the target, focus, and slave
* positions. Default is false
. This method must be called
* before it is entered.
*
* @param simple true
if the UI should be in simple mode.
*/
public void setSimpleMode(boolean simple) {
fSimple= simple;
}
/**
* Enables the support for colored labels in the proposal popup.
* Completion proposals can implement {@link ICompletionProposalExtension6}
* to provide colored proposal labels.
*
* @param isEnabled if true
the support for colored labels is enabled in the proposal popup
* @since 3.4
*/
public void enableColoredLabels(boolean isEnabled) {
fAssistant.enableColoredLabels(isEnabled);
}
}