blob: f8448a7c31826bb73f1dc126896d91d713565b86 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2015 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
* Tom Eicher (Avaloq Evolution AG) - block selection mode
* Tom Hofmann (Perspectix AG) - bug 297572
* Sergey Prigogin (Google) - bug 441448
*******************************************************************************/
package org.eclipse.jface.text.source;
import java.util.Iterator;
import java.util.Stack;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.jface.internal.text.NonDeletingPositionUpdater;
import org.eclipse.jface.internal.text.StickyHoverManager;
import org.eclipse.jface.text.AbstractHoverInformationControlManager;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.BlockTextSelection;
import org.eclipse.jface.text.DocumentRewriteSession;
import org.eclipse.jface.text.DocumentRewriteSessionType;
import org.eclipse.jface.text.IBlockTextSelection;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.jface.text.IPositionUpdater;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.IRewriteTarget;
import org.eclipse.jface.text.ISlaveDocumentManager;
import org.eclipse.jface.text.ISlaveDocumentManagerExtension;
import org.eclipse.jface.text.ISynchronizable;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewerExtension2;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.text.contentassist.IContentAssistant;
import org.eclipse.jface.text.contentassist.IContentAssistantExtension2;
import org.eclipse.jface.text.contentassist.IContentAssistantExtension4;
import org.eclipse.jface.text.formatter.FormattingContext;
import org.eclipse.jface.text.formatter.FormattingContextProperties;
import org.eclipse.jface.text.formatter.IContentFormatter;
import org.eclipse.jface.text.formatter.IContentFormatterExtension;
import org.eclipse.jface.text.formatter.IFormattingContext;
import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
import org.eclipse.jface.text.information.IInformationPresenter;
import org.eclipse.jface.text.presentation.IPresentationReconciler;
import org.eclipse.jface.text.projection.ChildDocument;
import org.eclipse.jface.text.quickassist.IQuickAssistAssistant;
import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext;
import org.eclipse.jface.text.reconciler.IReconciler;
/**
* SWT based implementation of
* {@link org.eclipse.jface.text.source.ISourceViewer} and its extension
* interfaces. The same rules apply as for
* {@link org.eclipse.jface.text.TextViewer}. A source viewer uses an
* <code>IVerticalRuler</code> as its annotation presentation area. The
* vertical ruler is a small strip shown left of the viewer's text widget. A
* source viewer uses an <code>IOverviewRuler</code> as its presentation area
* for the annotation overview. The overview ruler is a small strip shown right
* of the viewer's text widget.
* <p>
* Clients are supposed to instantiate a source viewer and subsequently to
* communicate with it exclusively using the <code>ISourceViewer</code> and
* its extension interfaces.</p>
* <p>
* Clients may subclass this class but should expect some breakage by future releases.</p>
*/
public class SourceViewer extends TextViewer implements ISourceViewer, ISourceViewerExtension, ISourceViewerExtension2, ISourceViewerExtension3, ISourceViewerExtension4 {
/**
* Layout of a source viewer. Vertical ruler, text widget, and overview ruler are shown side by side.
*/
protected class RulerLayout extends Layout {
/** The gap between the text viewer and the vertical ruler. */
protected int fGap;
/**
* Cached arrow heights of the vertical scroll bar: An array containing {topArrowHeight, bottomArrowHeight}.
* @since 3.6
*/
private int[] fScrollArrowHeights;
/**
* Creates a new ruler layout with the given gap between text viewer and vertical ruler.
*
* @param gap the gap between text viewer and vertical ruler
*/
public RulerLayout(int gap) {
fGap= gap;
}
@Override
protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
Control[] children= composite.getChildren();
Point s= children[children.length - 1].computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache);
if (fVerticalRuler != null && fIsVerticalRulerVisible)
s.x += fVerticalRuler.getWidth() + fGap;
return s;
}
@Override
protected void layout(Composite composite, boolean flushCache) {
Rectangle clArea= composite.getClientArea();
StyledText textWidget= getTextWidget();
Rectangle trim= textWidget.computeTrim(0, 0, 0, 0);
int topTrim= - trim.y;
int scrollbarHeight= trim.height - topTrim; // horizontal scroll bar is only under the client area
if ((textWidget.getScrollbarsMode() & SWT.SCROLLBAR_OVERLAY) != 0)
scrollbarHeight= 0;
int x= clArea.x;
int width= clArea.width;
int overviewRulerWidth= -1;
if (fOverviewRuler != null && fIsOverviewRulerVisible) {
overviewRulerWidth= fOverviewRuler.getWidth();
width -= overviewRulerWidth + fGap;
}
if (fVerticalRuler != null && fIsVerticalRulerVisible) {
int verticalRulerWidth= fVerticalRuler.getWidth();
final Control verticalRulerControl= fVerticalRuler.getControl();
int oldWidth= verticalRulerControl.getBounds().width;
ScrollBar horizontalBar= textWidget.getHorizontalBar();
boolean hScrollVisible= horizontalBar != null && horizontalBar.isVisible();
int rulerHeight= clArea.height - topTrim;
if (hScrollVisible)
rulerHeight-= scrollbarHeight;
verticalRulerControl.setBounds(clArea.x, clArea.y + topTrim, verticalRulerWidth, rulerHeight);
if (flushCache && getVisualAnnotationModel() != null && oldWidth == verticalRulerWidth)
verticalRulerControl.redraw();
x += verticalRulerWidth + fGap;
width -= verticalRulerWidth + fGap;
}
textWidget.setBounds(x, clArea.y, width, clArea.height);
if (overviewRulerWidth != -1) {
if (scrollbarHeight <= 0)
scrollbarHeight= overviewRulerWidth;
int bottomOffset= clArea.y + clArea.height - scrollbarHeight;
int[] arrowHeights= getVerticalScrollArrowHeights(textWidget, bottomOffset);
int overviewRulerX= clArea.x + clArea.width - overviewRulerWidth - 1;
fOverviewRuler.getControl().setBounds(overviewRulerX, clArea.y + arrowHeights[0], overviewRulerWidth, clArea.height - arrowHeights[0] - arrowHeights[1] - scrollbarHeight);
Control headerControl= fOverviewRuler.getHeaderControl();
boolean noArrows= arrowHeights[0] < 6 && arrowHeights[1] < 6; // need at least 6px to render the header control
if (noArrows || arrowHeights[0] < arrowHeights[1] && arrowHeights[0] < scrollbarHeight && arrowHeights[1] > scrollbarHeight) {
// // not enough space for header at top => move to bottom
int headerHeight= noArrows ? scrollbarHeight : arrowHeights[1];
headerControl.setBounds(overviewRulerX, clArea.y + clArea.height - arrowHeights[1] - scrollbarHeight, overviewRulerWidth, headerHeight);
} else {
headerControl.setBounds(overviewRulerX, clArea.y, overviewRulerWidth, arrowHeights[0]);
}
headerControl.redraw();
}
}
/**
* Computes and caches the arrow heights of the vertical scroll bar.
*
* @param textWidget the StyledText
* @param bottomOffset y-coordinate of the bottom of the overview ruler area
* @return an array containing {topArrowHeight, bottomArrowHeight}
*
* @since 3.6
*/
private int[] getVerticalScrollArrowHeights(StyledText textWidget, int bottomOffset) {
ScrollBar verticalBar= textWidget.getVerticalBar();
if (verticalBar == null || !verticalBar.isVisible())
return new int[] { 0, 0 };
int[] arrowHeights= computeScrollArrowHeights(textWidget, bottomOffset);
if (arrowHeights[0] > 0 || arrowHeights[1] > 0) {
fScrollArrowHeights= arrowHeights;
} else if (fScrollArrowHeights != null) {
return fScrollArrowHeights;
} else {
// No arrow heights available. Enlarge textWidget and tweak scroll bar to get reasonable values.
Point originalSize= textWidget.getSize();
try {
int fakeHeight= 1000;
bottomOffset= bottomOffset - originalSize.y + fakeHeight;
textWidget.setSize(originalSize.x, fakeHeight);
verticalBar.setValues(0, 0, 1 << 30, 1, 10, 10);
arrowHeights= computeScrollArrowHeights(textWidget, bottomOffset);
fScrollArrowHeights= arrowHeights;
} finally {
textWidget.setSize(originalSize); // also resets scroll bar values
}
}
return arrowHeights;
}
/**
* Computes the arrow heights of the vertical scroll bar.
*
* @param textWidget the StyledText
* @param bottomOffset y-coordinate of the bottom of the overview ruler area
* @return an array containing {topArrowHeight, bottomArrowHeight}
*
* @since 3.6
*/
private int[] computeScrollArrowHeights(StyledText textWidget, int bottomOffset) {
ScrollBar verticalBar= textWidget.getVerticalBar();
Rectangle thumbTrackBounds= verticalBar.getThumbTrackBounds();
if (thumbTrackBounds.height == 0) // SWT returns bogus values on Cocoa in this case, see https://bugs.eclipse.org/352990
return new int[] { 0, 0 };
int topArrowHeight= thumbTrackBounds.y;
int bottomArrowHeight= bottomOffset - (thumbTrackBounds.y + thumbTrackBounds.height);
return new int[] { topArrowHeight, bottomArrowHeight };
}
}
/**
* The size of the gap between the vertical ruler and the text widget
* (value <code>2</code>).
* <p>
* Note: As of 3.2, the text editor framework is no longer using 2 as
* gap but 1, see {{@link #GAP_SIZE_1 }.
* </p>
*/
protected final static int GAP_SIZE= 2;
/**
* The size of the gap between the vertical ruler and the text widget
* (value <code>1</code>).
* @since 3.2
*/
protected final static int GAP_SIZE_1= 1;
/**
* Partial name of the position category to manage remembered selections.
* @since 3.0
*/
protected final static String _SELECTION_POSITION_CATEGORY= "__selection_category"; //$NON-NLS-1$
/**
* Key of the model annotation model inside the visual annotation model.
* @since 3.0
*/
protected final static Object MODEL_ANNOTATION_MODEL= new Object();
/** The viewer's content assistant */
protected IContentAssistant fContentAssistant;
/**
* The viewer's facade to its content assistant.
* @since 3.4
*/
private ContentAssistantFacade fContentAssistantFacade;
/**
* Flag indicating whether the viewer's content assistant is installed.
* @since 2.0
*/
protected boolean fContentAssistantInstalled;
/**
* This viewer's quick assist assistant.
* @since 3.2
*/
protected IQuickAssistAssistant fQuickAssistAssistant;
/**
* Flag indicating whether this viewer's quick assist assistant is installed.
* @since 3.2
*/
protected boolean fQuickAssistAssistantInstalled;
/** The viewer's content formatter */
protected IContentFormatter fContentFormatter;
/** The viewer's model reconciler */
protected IReconciler fReconciler;
/** The viewer's presentation reconciler */
protected IPresentationReconciler fPresentationReconciler;
/** The viewer's annotation hover */
protected IAnnotationHover fAnnotationHover;
/**
* Stack of saved selections in the underlying document
* @since 3.0
*/
protected final Stack<Position> fSelections= new Stack<>();
/**
* Position updater for saved selections
* @since 3.0
*/
protected IPositionUpdater fSelectionUpdater= null;
/**
* Position category used by the selection updater
* @since 3.0
*/
protected String fSelectionCategory;
/**
* The viewer's overview ruler annotation hover
* @since 3.0
*/
protected IAnnotationHover fOverviewRulerAnnotationHover;
/**
* The viewer's information presenter
* @since 2.0
*/
protected IInformationPresenter fInformationPresenter;
/** Visual vertical ruler */
private IVerticalRuler fVerticalRuler;
/** Visibility of vertical ruler */
private boolean fIsVerticalRulerVisible;
/** The SWT widget used when supporting a vertical ruler */
private Composite fComposite;
/** The vertical ruler's annotation model */
private IAnnotationModel fVisualAnnotationModel;
/** The viewer's range indicator to be shown in the vertical ruler */
private Annotation fRangeIndicator;
/** The viewer's vertical ruler hovering controller */
private AnnotationBarHoverManager fVerticalRulerHoveringController;
/**
* The viewer's overview ruler hovering controller
* @since 2.1
*/
private AbstractHoverInformationControlManager fOverviewRulerHoveringController;
/**
* The overview ruler.
* @since 2.1
*/
private IOverviewRuler fOverviewRuler;
/**
* The visibility of the overview ruler
* @since 2.1
*/
private boolean fIsOverviewRulerVisible;
/**
* Constructs a new source viewer. The vertical ruler is initially visible.
* The viewer has not yet been initialized with a source viewer configuration.
*
* @param parent the parent of the viewer's control
* @param ruler the vertical ruler used by this source viewer
* @param styles the SWT style bits for the viewer's control,
* <em>if <code>SWT.WRAP</code> is set then a custom document adapter needs to be provided, see {@link #createDocumentAdapter()}
*/
public SourceViewer(Composite parent, IVerticalRuler ruler, int styles) {
this(parent, ruler, null, false, styles);
}
/**
* Constructs a new source viewer. The vertical ruler is initially visible.
* The overview ruler visibility is controlled by the value of <code>showAnnotationsOverview</code>.
* The viewer has not yet been initialized with a source viewer configuration.
*
* @param parent the parent of the viewer's control
* @param verticalRuler the vertical ruler used by this source viewer
* @param overviewRuler the overview ruler
* @param showAnnotationsOverview <code>true</code> if the overview ruler should be visible, <code>false</code> otherwise
* @param styles the SWT style bits for the viewer's control,
* <em>if <code>SWT.WRAP</code> is set then a custom document adapter needs to be provided, see {@link #createDocumentAdapter()}
* @since 2.1
*/
public SourceViewer(Composite parent, IVerticalRuler verticalRuler, IOverviewRuler overviewRuler, boolean showAnnotationsOverview, int styles) {
super();
fVerticalRuler= verticalRuler;
fIsVerticalRulerVisible= (verticalRuler != null);
fOverviewRuler= overviewRuler;
fIsOverviewRulerVisible= (showAnnotationsOverview && overviewRuler != null);
createControl(parent, styles);
}
@Override
protected void createControl(Composite parent, int styles) {
if (fVerticalRuler != null || fOverviewRuler != null) {
styles= (styles & ~SWT.BORDER);
fComposite= new Canvas(parent, SWT.NONE);
fComposite.setLayout(createLayout());
parent= fComposite;
}
super.createControl(parent, styles);
if (fVerticalRuler != null)
fVerticalRuler.createControl(fComposite, this);
if (fOverviewRuler != null)
fOverviewRuler.createControl(fComposite, this);
}
/**
* Creates the layout used for this viewer.
* Subclasses may override this method.
*
* @return the layout used for this viewer
* @since 3.0
*/
protected Layout createLayout() {
return new RulerLayout(GAP_SIZE_1);
}
@Override
public Control getControl() {
if (fComposite != null)
return fComposite;
return super.getControl();
}
@Override
public void setAnnotationHover(IAnnotationHover annotationHover) {
fAnnotationHover= annotationHover;
}
/**
* Sets the overview ruler's annotation hover of this source viewer.
* The annotation hover provides the information to be displayed in a hover
* popup window if requested over the overview rulers area. The annotation
* hover is assumed to be line oriented.
*
* @param annotationHover the hover to be used, <code>null</code> is a valid argument
* @since 3.0
*/
public void setOverviewRulerAnnotationHover(IAnnotationHover annotationHover) {
fOverviewRulerAnnotationHover= annotationHover;
}
@Override
public void configure(SourceViewerConfiguration configuration) {
if (getTextWidget() == null)
return;
setDocumentPartitioning(configuration.getConfiguredDocumentPartitioning(this));
// install content type independent plug-ins
fPresentationReconciler= configuration.getPresentationReconciler(this);
if (fPresentationReconciler != null)
fPresentationReconciler.install(this);
fReconciler= configuration.getReconciler(this);
if (fReconciler != null)
fReconciler.install(this);
fContentAssistant= configuration.getContentAssistant(this);
if (fContentAssistant != null) {
fContentAssistant.install(this);
if (fContentAssistant instanceof IContentAssistantExtension2 && fContentAssistant instanceof IContentAssistantExtension4)
fContentAssistantFacade= new ContentAssistantFacade(fContentAssistant);
fContentAssistantInstalled= true;
}
fQuickAssistAssistant= configuration.getQuickAssistAssistant(this);
if (fQuickAssistAssistant != null) {
fQuickAssistAssistant.install(this);
fQuickAssistAssistantInstalled= true;
}
fContentFormatter= configuration.getContentFormatter(this);
fInformationPresenter= configuration.getInformationPresenter(this);
if (fInformationPresenter != null)
fInformationPresenter.install(this);
setUndoManager(configuration.getUndoManager(this));
getTextWidget().setTabs(configuration.getTabWidth(this));
setAnnotationHover(configuration.getAnnotationHover(this));
setOverviewRulerAnnotationHover(configuration.getOverviewRulerAnnotationHover(this));
setHoverControlCreator(configuration.getInformationControlCreator(this));
setHyperlinkPresenter(configuration.getHyperlinkPresenter(this));
IHyperlinkDetector[] hyperlinkDetectors= configuration.getHyperlinkDetectors(this);
int eventStateMask= configuration.getHyperlinkStateMask(this);
setHyperlinkDetectors(hyperlinkDetectors, eventStateMask);
// install content type specific plug-ins
String[] types= configuration.getConfiguredContentTypes(this);
for (int i= 0; i < types.length; i++) {
String t= types[i];
setAutoEditStrategies(configuration.getAutoEditStrategies(this, t), t);
setTextDoubleClickStrategy(configuration.getDoubleClickStrategy(this, t), t);
int[] stateMasks= configuration.getConfiguredTextHoverStateMasks(this, t);
if (stateMasks != null) {
for (int j= 0; j < stateMasks.length; j++) {
int stateMask= stateMasks[j];
setTextHover(configuration.getTextHover(this, t, stateMask), t, stateMask);
}
} else {
setTextHover(configuration.getTextHover(this, t), t, ITextViewerExtension2.DEFAULT_HOVER_STATE_MASK);
}
String[] prefixes= configuration.getIndentPrefixes(this, t);
if (prefixes != null && prefixes.length > 0)
setIndentPrefixes(prefixes, t);
prefixes= configuration.getDefaultPrefixes(this, t);
if (prefixes != null && prefixes.length > 0)
setDefaultPrefixes(prefixes, t);
}
activatePlugins();
}
/**
* After this method has been executed the caller knows that any installed annotation hover has been installed.
*/
protected void ensureAnnotationHoverManagerInstalled() {
if (fVerticalRuler != null && (fAnnotationHover != null || !isVerticalRulerOnlyShowingAnnotations()) && fVerticalRulerHoveringController == null && fHoverControlCreator != null) {
fVerticalRulerHoveringController= new AnnotationBarHoverManager(fVerticalRuler, this, fAnnotationHover, fHoverControlCreator);
fVerticalRulerHoveringController.install(fVerticalRuler.getControl());
fVerticalRulerHoveringController.getInternalAccessor().setInformationControlReplacer(new StickyHoverManager(this));
}
}
/**
* After this method has been executed the caller knows that any installed overview hover has been installed.
*/
protected void ensureOverviewHoverManagerInstalled() {
if (fOverviewRuler != null && fOverviewRulerAnnotationHover != null && fOverviewRulerHoveringController == null && fHoverControlCreator != null) {
fOverviewRulerHoveringController= new OverviewRulerHoverManager(fOverviewRuler, this, fOverviewRulerAnnotationHover, fHoverControlCreator);
fOverviewRulerHoveringController.install(fOverviewRuler.getControl());
fOverviewRulerHoveringController.getInternalAccessor().setInformationControlReplacer(new StickyHoverManager(this));
}
}
@Override
public void setHoverEnrichMode(EnrichMode mode) {
super.setHoverEnrichMode(mode);
if (fVerticalRulerHoveringController != null)
fVerticalRulerHoveringController.getInternalAccessor().setHoverEnrichMode(mode);
if (fOverviewRulerHoveringController != null)
fOverviewRulerHoveringController.getInternalAccessor().setHoverEnrichMode(mode);
}
@Override
public void activatePlugins() {
ensureAnnotationHoverManagerInstalled();
ensureOverviewHoverManagerInstalled();
super.activatePlugins();
}
@Override
public void setDocument(IDocument document) {
setDocument(document, null, -1, -1);
}
@Override
public void setDocument(IDocument document, int visibleRegionOffset, int visibleRegionLength) {
setDocument(document, null, visibleRegionOffset, visibleRegionLength);
}
@Override
public void setDocument(IDocument document, IAnnotationModel annotationModel) {
setDocument(document, annotationModel, -1, -1);
}
/**
* Creates the visual annotation model on top of the given annotation model.
*
* @param annotationModel the wrapped annotation model
* @return the visual annotation model on top of the given annotation model
* @since 3.0
*/
protected IAnnotationModel createVisualAnnotationModel(IAnnotationModel annotationModel) {
IAnnotationModelExtension model= new AnnotationModel();
model.addAnnotationModel(MODEL_ANNOTATION_MODEL, annotationModel);
return (IAnnotationModel) model;
}
/**
* Disposes the visual annotation model.
*
* @since 3.1
*/
protected void disposeVisualAnnotationModel() {
if (fVisualAnnotationModel != null) {
if (getDocument() != null)
fVisualAnnotationModel.disconnect(getDocument());
if ( fVisualAnnotationModel instanceof IAnnotationModelExtension)
((IAnnotationModelExtension)fVisualAnnotationModel).removeAnnotationModel(MODEL_ANNOTATION_MODEL);
fVisualAnnotationModel= null;
}
}
@Override
public void setDocument(IDocument document, IAnnotationModel annotationModel, int modelRangeOffset, int modelRangeLength) {
disposeVisualAnnotationModel();
if (annotationModel != null && document != null) {
fVisualAnnotationModel= createVisualAnnotationModel(annotationModel);
// Make sure the visual model uses the same lock as the underlying model
if (annotationModel instanceof ISynchronizable && fVisualAnnotationModel instanceof ISynchronizable) {
ISynchronizable sync= (ISynchronizable)fVisualAnnotationModel;
sync.setLockObject(((ISynchronizable)annotationModel).getLockObject());
}
fVisualAnnotationModel.connect(document);
}
if (modelRangeOffset == -1 && modelRangeLength == -1)
super.setDocument(document);
else
super.setDocument(document, modelRangeOffset, modelRangeLength);
if (fVerticalRuler != null)
fVerticalRuler.setModel(fVisualAnnotationModel);
if (fOverviewRuler != null)
fOverviewRuler.setModel(fVisualAnnotationModel);
}
@Override
public IAnnotationModel getAnnotationModel() {
if (fVisualAnnotationModel instanceof IAnnotationModelExtension) {
IAnnotationModelExtension extension= (IAnnotationModelExtension) fVisualAnnotationModel;
return extension.getAnnotationModel(MODEL_ANNOTATION_MODEL);
}
return null;
}
@Override
public IQuickAssistAssistant getQuickAssistAssistant() {
return fQuickAssistAssistant;
}
/**
* {@inheritDoc}
*
* @since 3.4
*/
@Override
public final ContentAssistantFacade getContentAssistantFacade() {
return fContentAssistantFacade;
}
@Override
public IQuickAssistInvocationContext getQuickAssistInvocationContext() {
Point selection= getSelectedRange();
return new TextInvocationContext(this, selection.x, selection.y);
}
@Override
public IAnnotationModel getVisualAnnotationModel() {
return fVisualAnnotationModel;
}
@Override
public void unconfigure() {
clearRememberedSelection();
if (fPresentationReconciler != null) {
fPresentationReconciler.uninstall();
fPresentationReconciler= null;
}
if (fReconciler != null) {
fReconciler.uninstall();
fReconciler= null;
}
if (fContentAssistant != null) {
fContentAssistant.uninstall();
fContentAssistantInstalled= false;
fContentAssistant= null;
if (fContentAssistantFacade != null)
fContentAssistantFacade= null;
}
if (fQuickAssistAssistant != null) {
fQuickAssistAssistant.uninstall();
fQuickAssistAssistantInstalled= false;
fQuickAssistAssistant= null;
}
fContentFormatter= null;
if (fInformationPresenter != null) {
fInformationPresenter.uninstall();
fInformationPresenter= null;
}
fAutoIndentStrategies= null;
fDoubleClickStrategies= null;
fTextHovers= null;
fIndentChars= null;
fDefaultPrefixChars= null;
if (fVerticalRulerHoveringController != null) {
fVerticalRulerHoveringController.dispose();
fVerticalRulerHoveringController= null;
}
if (fOverviewRulerHoveringController != null) {
fOverviewRulerHoveringController.dispose();
fOverviewRulerHoveringController= null;
}
if (fUndoManager != null) {
fUndoManager.disconnect();
fUndoManager= null;
}
setHyperlinkDetectors(null, SWT.NONE);
}
@Override
protected void handleDispose() {
unconfigure();
disposeVisualAnnotationModel();
fVerticalRuler= null;
fOverviewRuler= null;
// http://dev.eclipse.org/bugs/show_bug.cgi?id=15300
fComposite= null;
super.handleDispose();
}
@Override
public boolean canDoOperation(int operation) {
if (getTextWidget() == null || (!redraws() && operation != FORMAT))
return false;
if (operation == CONTENTASSIST_PROPOSALS)
return fContentAssistant != null && fContentAssistantInstalled && isEditable();
if (operation == CONTENTASSIST_CONTEXT_INFORMATION)
return fContentAssistant != null && fContentAssistantInstalled && isEditable();
if (operation == QUICK_ASSIST)
return fQuickAssistAssistant != null && fQuickAssistAssistantInstalled && isEditable();
if (operation == INFORMATION)
return fInformationPresenter != null;
if (operation == FORMAT) {
return fContentFormatter != null && isEditable();
}
return super.canDoOperation(operation);
}
/**
* Creates a new formatting context for a format operation.
* <p>
* After the use of the context, clients are required to call
* its <code>dispose</code> method.
*
* @return The new formatting context
* @since 3.0
*/
protected IFormattingContext createFormattingContext() {
return new FormattingContext();
}
/**
* Creates a new formatting context for a format operation. If the returned context has
* the {@link FormattingContextProperties#CONTEXT_REGION} property set to an {@link IRegion},
* the section of the document defined by that region is formatted, otherwise the whole
* document is formatted.
* <p>
* The default implementation calls {@link #createFormattingContext()} and sets
* the {@link FormattingContextProperties#CONTEXT_REGION} property if the selection is
* not empty. Overriding methods may implement a different logic, or return <code>null</code>
* to indicate that the formatting operation should not proceed.
* <p>
* Returning <code>null</code> may be used, for example, when the user clicks on
* the <b>Cancel</b> button in a dialog that, in case of an empty selection, asks the user
* whether formatting should be applied to the whole document or to the current statement.
* Please notice that returning <code>null</code> from this method cancels the already initiated
* formatting operation unlike {@link #canDoOperation(int)}, which is used for enabling and
* disabling formatting actions.
* <p>
* After the use of the context, clients are required to call its <code>dispose</code> method.
*
* @param selectionOffset the character offset of the selection in the document
* @param selectionLength the length of the selection
* @return The new formatting context, or <code>null</code> to cancel the formatting
* @since 3.10
*/
protected IFormattingContext createFormattingContext(int selectionOffset, int selectionLength) {
IFormattingContext context= createFormattingContext();
if (selectionLength != 0) {
context.setProperty(FormattingContextProperties.CONTEXT_REGION,
new Region(selectionOffset, selectionLength));
}
return context;
}
/**
* Position storing block selection information in order to maintain a column selection.
*
* @since 3.5
*/
private static final class ColumnPosition extends Position {
int fStartColumn, fEndColumn;
ColumnPosition(int offset, int length, int startColumn, int endColumn) {
super(offset, length);
fStartColumn= startColumn;
fEndColumn= endColumn;
}
}
/**
* Remembers and returns the current selection. The saved selection can be restored
* by calling <code>restoreSelection()</code>.
*
* @return the current selection
* @see org.eclipse.jface.text.ITextViewer#getSelectedRange()
* @since 3.0
*/
protected Point rememberSelection() {
final ITextSelection selection= (ITextSelection) getSelection();
final IDocument document= getDocument();
if (fSelections.isEmpty()) {
fSelectionCategory= _SELECTION_POSITION_CATEGORY + hashCode();
fSelectionUpdater= new NonDeletingPositionUpdater(fSelectionCategory);
document.addPositionCategory(fSelectionCategory);
document.addPositionUpdater(fSelectionUpdater);
}
try {
final Position position;
if (selection instanceof IBlockTextSelection)
position= new ColumnPosition(selection.getOffset(), selection.getLength(), ((IBlockTextSelection) selection).getStartColumn(), ((IBlockTextSelection) selection).getEndColumn());
else
position= new Position(selection.getOffset(), selection.getLength());
document.addPosition(fSelectionCategory, position);
fSelections.push(position);
} catch (BadLocationException exception) {
// Should not happen
} catch (BadPositionCategoryException exception) {
// Should not happen
}
return new Point(selection.getOffset(), selection.getLength());
}
/**
* Restores a previously saved selection in the document.
* <p>
* If no selection was previously saved, nothing happens.
*
* @since 3.0
*/
protected void restoreSelection() {
if (!fSelections.isEmpty()) {
final IDocument document= getDocument();
final Position position= fSelections.pop();
try {
document.removePosition(fSelectionCategory, position);
Point currentSelection= getSelectedRange();
if (currentSelection == null || currentSelection.x != position.getOffset() || currentSelection.y != position.getLength()) {
if (position instanceof ColumnPosition && getTextWidget().getBlockSelection()) {
setSelection(new BlockTextSelection(document, document.getLineOfOffset(position.getOffset()), ((ColumnPosition) position).fStartColumn, document.getLineOfOffset(position.getOffset() + position.getLength()), ((ColumnPosition) position).fEndColumn, getTextWidget().getTabs()));
} else {
setSelectedRange(position.getOffset(), position.getLength());
}
}
if (fSelections.isEmpty())
clearRememberedSelection();
} catch (BadPositionCategoryException exception) {
// Should not happen
} catch (BadLocationException x) {
// Should not happen
}
}
}
protected void clearRememberedSelection() {
if (!fSelections.isEmpty())
fSelections.clear();
IDocument document= getDocument();
if (document != null && fSelectionUpdater != null) {
document.removePositionUpdater(fSelectionUpdater);
try {
document.removePositionCategory(fSelectionCategory);
} catch (BadPositionCategoryException e) {
// ignore
}
}
fSelectionUpdater= null;
fSelectionCategory= null;
}
@Override
public void doOperation(int operation) {
if (getTextWidget() == null || (!redraws() && operation != FORMAT))
return;
switch (operation) {
case CONTENTASSIST_PROPOSALS:
fContentAssistant.showPossibleCompletions();
return;
case CONTENTASSIST_CONTEXT_INFORMATION:
fContentAssistant.showContextInformation();
return;
case QUICK_ASSIST:
// FIXME: must find a way to post to the status line
/* String msg= */ fQuickAssistAssistant.showPossibleQuickAssists();
// setStatusLineErrorMessage(msg);
return;
case INFORMATION:
fInformationPresenter.showInformation();
return;
case FORMAT:
{
final Point selection= rememberSelection();
final IRewriteTarget target= getRewriteTarget();
final IDocument document= getDocument();
IFormattingContext context= null;
DocumentRewriteSession rewriteSession= null;
if (document instanceof IDocumentExtension4) {
IDocumentExtension4 extension= (IDocumentExtension4) document;
DocumentRewriteSessionType type= (selection.y == 0 && document.getLength() > 1000) || selection.y > 1000
? DocumentRewriteSessionType.SEQUENTIAL
: DocumentRewriteSessionType.UNRESTRICTED_SMALL;
rewriteSession= extension.startRewriteSession(type);
} else {
setRedraw(false);
target.beginCompoundChange();
}
try {
final String rememberedContents= document.get();
try {
if (fContentFormatter instanceof IContentFormatterExtension) {
final IContentFormatterExtension extension= (IContentFormatterExtension) fContentFormatter;
context= createFormattingContext(selection.x, selection.y);
if (context == null) {
return;
}
Object region = context.getProperty(FormattingContextProperties.CONTEXT_REGION);
context.setProperty(FormattingContextProperties.CONTEXT_DOCUMENT,
Boolean.valueOf(region == null));
extension.format(document, context);
} else {
IRegion r;
if (selection.y == 0) {
IRegion coverage= getModelCoverage();
r= coverage == null ? new Region(0, 0) : coverage;
} else {
r= new Region(selection.x, selection.y);
}
fContentFormatter.format(document, r);
}
updateSlaveDocuments(document);
} catch (RuntimeException x) {
// fire wall for https://bugs.eclipse.org/bugs/show_bug.cgi?id=47472
// if something went wrong we undo the changes we just did
// TODO to be removed after 3.0 M8
document.set(rememberedContents);
throw x;
}
} finally {
if (document instanceof IDocumentExtension4) {
IDocumentExtension4 extension= (IDocumentExtension4) document;
extension.stopRewriteSession(rewriteSession);
} else {
target.endCompoundChange();
setRedraw(true);
}
restoreSelection();
if (context != null)
context.dispose();
}
return;
}
default:
super.doOperation(operation);
}
}
/**
* Updates all slave documents of the given document. This default implementation calls <code>updateSlaveDocument</code>
* for their current visible range. Subclasses may reimplement.
*
* @param masterDocument the master document
* @since 3.0
*/
protected void updateSlaveDocuments(IDocument masterDocument) {
ISlaveDocumentManager manager= getSlaveDocumentManager();
if (manager instanceof ISlaveDocumentManagerExtension) {
ISlaveDocumentManagerExtension extension= (ISlaveDocumentManagerExtension) manager;
IDocument[] slaves= extension.getSlaveDocuments(masterDocument);
if (slaves != null) {
for (int i= 0; i < slaves.length; i++) {
if (slaves[i] instanceof ChildDocument) {
ChildDocument child= (ChildDocument) slaves[i];
Position p= child.getParentDocumentRange();
try {
if (!updateSlaveDocument(child, p.getOffset(), p.getLength()))
child.repairLineInformation();
} catch (BadLocationException e) {
// ignore
}
}
}
}
}
}
@Override
public void enableOperation(int operation, boolean enable) {
switch (operation) {
case CONTENTASSIST_PROPOSALS:
case CONTENTASSIST_CONTEXT_INFORMATION: {
if (fContentAssistant == null)
return;
if (enable) {
if (!fContentAssistantInstalled) {
fContentAssistant.install(this);
fContentAssistantInstalled= true;
}
} else if (fContentAssistantInstalled) {
fContentAssistant.uninstall();
fContentAssistantInstalled= false;
}
break;
}
case QUICK_ASSIST: {
if (fQuickAssistAssistant == null)
return;
if (enable) {
if (!fQuickAssistAssistantInstalled) {
fQuickAssistAssistant.install(this);
fQuickAssistAssistantInstalled= true;
}
} else if (fQuickAssistAssistantInstalled) {
fQuickAssistAssistant.uninstall();
fQuickAssistAssistantInstalled= false;
}
}
}
}
@Override
public void setRangeIndicator(Annotation rangeIndicator) {
fRangeIndicator= rangeIndicator;
}
@Override
public void setRangeIndication(int start, int length, boolean moveCursor) {
if (moveCursor) {
setSelectedRange(start, 0);
revealRange(start, length);
}
if (fRangeIndicator != null && fVisualAnnotationModel instanceof IAnnotationModelExtension) {
IAnnotationModelExtension extension= (IAnnotationModelExtension) fVisualAnnotationModel;
extension.modifyAnnotationPosition(fRangeIndicator, new Position(start, length));
}
}
@Override
public IRegion getRangeIndication() {
if (fRangeIndicator != null && fVisualAnnotationModel != null) {
Position position= fVisualAnnotationModel.getPosition(fRangeIndicator);
if (position != null)
return new Region(position.getOffset(), position.getLength());
}
return null;
}
@Override
public void removeRangeIndication() {
if (fRangeIndicator != null && fVisualAnnotationModel != null)
fVisualAnnotationModel.removeAnnotation(fRangeIndicator);
}
@Override
public void showAnnotations(boolean show) {
boolean old= fIsVerticalRulerVisible;
fIsVerticalRulerVisible= (fVerticalRuler != null && (show || !isVerticalRulerOnlyShowingAnnotations()));
if (old != fIsVerticalRulerVisible && fComposite != null && !fComposite.isDisposed())
fComposite.layout();
if (fIsVerticalRulerVisible && show)
ensureAnnotationHoverManagerInstalled();
else if (fVerticalRulerHoveringController != null) {
fVerticalRulerHoveringController.dispose();
fVerticalRulerHoveringController= null;
}
}
/**
* Tells whether the vertical ruler only acts as annotation ruler.
*
* @return <code>true</code> if the vertical ruler only show annotations
* @since 3.3
*/
private boolean isVerticalRulerOnlyShowingAnnotations() {
if (fVerticalRuler instanceof VerticalRuler)
return true;
if (fVerticalRuler instanceof CompositeRuler) {
Iterator<IVerticalRulerColumn> iter= ((CompositeRuler)fVerticalRuler).getDecoratorIterator();
return iter.hasNext() && iter.next() instanceof AnnotationRulerColumn && !iter.hasNext();
}
return false;
}
/**
* Returns the vertical ruler of this viewer.
*
* @return the vertical ruler of this viewer
* @since 3.0
*/
protected final IVerticalRuler getVerticalRuler() {
return fVerticalRuler;
}
/**
* Adds the give column as last column to this viewer's vertical ruler.
*
* @param column the column to be added
* @since 3.8
*/
public void addVerticalRulerColumn(IVerticalRulerColumn column) {
IVerticalRuler ruler= getVerticalRuler();
if (ruler instanceof CompositeRuler) {
CompositeRuler compositeRuler= (CompositeRuler)ruler;
compositeRuler.addDecorator(99, column);
}
}
/**
* Removes the give column from this viewer's vertical ruler.
*
* @param column the column to be removed
* @since 3.8
*/
public void removeVerticalRulerColumn(IVerticalRulerColumn column) {
IVerticalRuler ruler= getVerticalRuler();
if (ruler instanceof CompositeRuler) {
CompositeRuler compositeRuler= (CompositeRuler)ruler;
compositeRuler.removeDecorator(column);
}
}
@Override
public void showAnnotationsOverview(boolean show) {
boolean old= fIsOverviewRulerVisible;
fIsOverviewRulerVisible= (show && fOverviewRuler != null);
if (old != fIsOverviewRulerVisible) {
if (fComposite != null && !fComposite.isDisposed())
fComposite.layout();
if (fIsOverviewRulerVisible) {
ensureOverviewHoverManagerInstalled();
} else if (fOverviewRulerHoveringController != null) {
fOverviewRulerHoveringController.dispose();
fOverviewRulerHoveringController= null;
}
}
}
@Override
public IAnnotationHover getCurrentAnnotationHover() {
if (fVerticalRulerHoveringController == null)
return null;
return fVerticalRulerHoveringController.getCurrentAnnotationHover();
}
}