com.google.dart.tools.ui.internal.text.editor.DartSourceViewer.java Source code

Java tutorial

Introduction

Here is the source code for com.google.dart.tools.ui.internal.text.editor.DartSourceViewer.java

Source

/*
 * Copyright (c) 2011, the Dart project authors.
 * 
 * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.dart.tools.ui.internal.text.editor;

import com.google.dart.tools.core.DartCore;
import com.google.dart.tools.ui.DartUI;
import com.google.dart.tools.ui.PreferenceConstants;
import com.google.dart.tools.ui.internal.text.comment.CommentFormattingContext;
import com.google.dart.tools.ui.internal.text.functions.SmartBackspaceManager;
import com.google.dart.tools.ui.text.DartPartitions;
import com.google.dart.tools.ui.text.DartSourceViewerConfiguration;
import com.google.dart.tools.ui.text.IDartColorConstants;

import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextPresentationListener;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.formatter.FormattingContextProperties;
import org.eclipse.jface.text.formatter.IFormattingContext;
import org.eclipse.jface.text.information.IInformationPresenter;
import org.eclipse.jface.text.reconciler.IReconciler;
import org.eclipse.jface.text.source.IOverviewRuler;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.SourceViewerConfiguration;
import org.eclipse.jface.text.source.projection.ProjectionViewer;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BidiSegmentEvent;
import org.eclipse.swt.custom.BidiSegmentListener;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
import org.eclipse.ui.texteditor.AbstractTextEditor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DartSourceViewer extends ProjectionViewer implements IPropertyChangeListener {

    /**
     * Text operation code for requesting the outline for the current input.
     */
    public static final int SHOW_OUTLINE = 51;

    /**
     * Text operation code for requesting the outline for the element at the current position.
     */
    public static final int OPEN_STRUCTURE = 52;

    /**
     * Text operation code for requesting the hierarchy for the current input.
     */
    public static final int SHOW_HIERARCHY = 53;

    /**
     * Returns a segmentation of the line of the given document appropriate for BIDI rendering. The
     * default implementation returns only the string literals of a java code line as segments.
     * 
     * @param document the document
     * @param lineOffset the offset of the line
     * @return the line's BIDI segmentation
     * @throws BadLocationException in case lineOffset is not valid in document
     */
    protected static int[] getBidiLineSegments(IDocument document, int lineOffset) throws BadLocationException {

        if (document == null) {
            return null;
        }

        IRegion line = document.getLineInformationOfOffset(lineOffset);
        ITypedRegion[] linePartitioning = TextUtilities.computePartitioning(document,
                DartPartitions.DART_PARTITIONING, lineOffset, line.getLength(), false);

        List<ITypedRegion> segmentation = new ArrayList<ITypedRegion>();
        for (int i = 0; i < linePartitioning.length; i++) {
            if (DartPartitions.DART_STRING.equals(linePartitioning[i].getType())) {
                segmentation.add(linePartitioning[i]);
            }
        }

        if (segmentation.size() == 0) {
            return null;
        }

        int size = segmentation.size();
        int[] segments = new int[size * 2 + 1];

        int j = 0;
        for (int i = 0; i < size; i++) {
            ITypedRegion segment = segmentation.get(i);

            if (i == 0) {
                segments[j++] = 0;
            }

            int offset = segment.getOffset() - lineOffset;
            if (offset > segments[j - 1]) {
                segments[j++] = offset;
            }

            if (offset + segment.getLength() >= line.getLength()) {
                break;
            }

            segments[j++] = offset + segment.getLength();
        }

        if (j < segments.length) {
            int[] result = new int[j];
            System.arraycopy(segments, 0, result, 0, j);
            segments = result;
        }

        return segments;
    }

    private IInformationPresenter fOutlinePresenter;
    private IInformationPresenter fStructurePresenter;

    private IInformationPresenter fHierarchyPresenter;
    /**
     * This viewer's foreground color.
     */
    private Color fForegroundColor;
    /**
     * The viewer's background color.
     */
    private Color fBackgroundColor;
    /**
     * This viewer's selection foreground color.
     */
    private Color fSelectionForegroundColor;
    /**
     * The viewer's selection background color.
     */
    private Color fSelectionBackgroundColor;
    /**
     * The preference store.
     */
    private IPreferenceStore fPreferenceStore;
    /**
     * Is this source viewer configured?
     */
    private boolean fIsConfigured;

    /**
     * The backspace manager of this viewer.
     */
    private SmartBackspaceManager fBackspaceManager;

    /**
     * Whether to delay setting the visual document until the projection has been computed.
     * <p>
     * Added for performance optimization.
     * </p>
     * 
     * @see #prepareDelayedProjection()
     */
    private boolean fIsSetVisibleDocumentDelayed = false;

    public DartSourceViewer(Composite parent, IVerticalRuler verticalRuler, IOverviewRuler overviewRuler,
            boolean showAnnotationsOverview, int styles, IPreferenceStore store) {
        super(parent, verticalRuler, overviewRuler, showAnnotationsOverview, styles);
        setPreferenceStore(store);
    }

    /**
     * Sets the viewer's background color to the given control's background color. The background
     * color is <em>only</em> set if it's visibly distinct from the default Dart source text color.
     * 
     * @param control the control with the default background color
     */
    public void adaptBackgroundColor(Control control) {
        Color defaultColor = control.getBackground();
        float[] defaultBgHSB = defaultColor.getRGB().getHSB();

        Color javaDefaultColor = DartUI.getColorManager().getColor(IDartColorConstants.JAVA_DEFAULT);
        RGB javaDefaultRGB = javaDefaultColor != null ? javaDefaultColor.getRGB() : new RGB(255, 255, 255);
        float[] javaDefaultHSB = javaDefaultRGB.getHSB();

        if (Math.abs(defaultBgHSB[2] - javaDefaultHSB[2]) >= 0.5f) {
            getTextWidget().setBackground(defaultColor);
            if (fBackgroundColor != null) {
                fBackgroundColor.dispose();
                fBackgroundColor = null;
            }
        }
    }

    /*
     * @see ITextOperationTarget#canDoOperation(int)
     */
    @Override
    public boolean canDoOperation(int operation) {
        if (operation == SHOW_OUTLINE) {
            return fOutlinePresenter != null;
        }
        if (operation == OPEN_STRUCTURE) {
            return fStructurePresenter != null;
        }
        if (operation == SHOW_HIERARCHY) {
            return fHierarchyPresenter != null;
        }

        return super.canDoOperation(operation);
    }

    /*
     * @see ISourceViewer#configure(SourceViewerConfiguration)
     */
    @Override
    public void configure(SourceViewerConfiguration configuration) {
        /*
         * Prevent access to colors disposed in unconfigure(), see:
         * https://bugs.eclipse.org/bugs/show_bug.cgi?id=53641
         * https://bugs.eclipse.org/bugs/show_bug.cgi?id=86177
         */
        StyledText textWidget = getTextWidget();
        if (textWidget != null && !textWidget.isDisposed()) {
            Color foregroundColor = textWidget.getForeground();
            if (foregroundColor != null && foregroundColor.isDisposed()) {
                textWidget.setForeground(null);
            }
            Color backgroundColor = textWidget.getBackground();
            if (backgroundColor != null && backgroundColor.isDisposed()) {
                textWidget.setBackground(null);
            }
        }

        super.configure(configuration);
        if (configuration instanceof DartSourceViewerConfiguration) {
            DartSourceViewerConfiguration javaSVCconfiguration = (DartSourceViewerConfiguration) configuration;
            fOutlinePresenter = javaSVCconfiguration.getOutlinePresenter(this, false);
            if (fOutlinePresenter != null) {
                fOutlinePresenter.install(this);
            }

            fStructurePresenter = javaSVCconfiguration.getOutlinePresenter(this, true);
            if (fStructurePresenter != null) {
                fStructurePresenter.install(this);
            }

            fHierarchyPresenter = javaSVCconfiguration.getHierarchyPresenter(this, true);
            if (fHierarchyPresenter != null) {
                fHierarchyPresenter.install(this);
            }

        }

        if (fPreferenceStore != null) {
            fPreferenceStore.addPropertyChangeListener(this);
            initializeViewerColors();
        }

        fIsConfigured = true;
    }

    /*
     * @see org.eclipse.jface.text.source.SourceViewer#createFormattingContext()
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public IFormattingContext createFormattingContext() {
        // it's ok to use instance preferences here as subclasses replace
        // with project dependent versions (see
        // CompilationUnitEditor.AdaptedSourceViewer)
        IFormattingContext context = new CommentFormattingContext();
        Map map = new HashMap(DartCore.getOptions());
        context.setProperty(FormattingContextProperties.CONTEXT_PREFERENCES, map);

        return context;
    }

    /*
     * @see ITextOperationTarget#doOperation(int)
     */
    @Override
    public void doOperation(int operation) {
        if (getTextWidget() == null) {
            return;
        }

        switch (operation) {
        case SHOW_OUTLINE:
            if (fOutlinePresenter != null) {
                fOutlinePresenter.showInformation();
            }
            return;
        case OPEN_STRUCTURE:
            if (fStructurePresenter != null) {
                fStructurePresenter.showInformation();
            }
            return;
        case SHOW_HIERARCHY:
            if (fHierarchyPresenter != null) {
                fHierarchyPresenter.showInformation();
            }
            return;
        }

        super.doOperation(operation);
    }

    /**
     * Returns the backspace manager for this viewer.
     * 
     * @return the backspace manager for this viewer, or <code>null</code> if there is none
     */
    public SmartBackspaceManager getBackspaceManager() {
        return fBackspaceManager;
    }

    /**
     * Prepends the text presentation listener at the beginning of the viewer's list of text
     * presentation listeners. If the listener is already registered with the viewer this call moves
     * the listener to the beginning of the list.
     * 
     * @param listener the text presentation listener
     */
    @SuppressWarnings("unchecked")
    public void prependTextPresentationListener(ITextPresentationListener listener) {

        Assert.isNotNull(listener);

        if (fTextPresentationListeners == null) {
            fTextPresentationListeners = new ArrayList<ITextPresentationListener>();
        }

        fTextPresentationListeners.remove(listener);
        fTextPresentationListeners.add(0, listener);
    }

    /*
     * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse
     * .jface.util.PropertyChangeEvent)
     */
    @Override
    public void propertyChange(PropertyChangeEvent event) {
        String property = event.getProperty();
        if (AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND.equals(property)
                || AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT.equals(property)
                || AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND.equals(property)
                || AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT.equals(property)
                || AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_FOREGROUND_COLOR.equals(property)
                || AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_FOREGROUND_DEFAULT_COLOR
                        .equals(property)
                || AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_BACKGROUND_COLOR.equals(property)
                || AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_BACKGROUND_DEFAULT_COLOR
                        .equals(property)) {
            initializeViewerColors();
        }
    }

    /*
     * @see org.eclipse.jface.text.source.SourceViewer#rememberSelection()
     */
    @Override
    public Point rememberSelection() {
        return super.rememberSelection();
    }

    /*
     * @see org.eclipse.jface.text.ITextViewer#resetVisibleRegion()
     */
    @Override
    public void resetVisibleRegion() {
        super.resetVisibleRegion();
        // re-enable folding if ProjectionViewer failed to due so
        if (fPreferenceStore != null && fPreferenceStore.getBoolean(PreferenceConstants.EDITOR_FOLDING_ENABLED)
                && !isProjectionMode()) {
            enableProjection();
        }
    }

    /*
     * @see org.eclipse.jface.text.source.SourceViewer#restoreSelection()
     */
    @Override
    public void restoreSelection() {
        super.restoreSelection();
    }

    /**
     * Sets the preference store on this viewer.
     * 
     * @param store the preference store
     */
    public void setPreferenceStore(IPreferenceStore store) {
        if (fIsConfigured && fPreferenceStore != null) {
            fPreferenceStore.removePropertyChangeListener(this);
        }

        fPreferenceStore = store;

        if (fIsConfigured && fPreferenceStore != null) {
            fPreferenceStore.addPropertyChangeListener(this);
            initializeViewerColors();
        }
    }

    /*
     * @see org.eclipse.jface.text.source.ISourceViewerExtension2#unconfigure()
     */
    @Override
    public void unconfigure() {
        if (fOutlinePresenter != null) {
            fOutlinePresenter.uninstall();
            fOutlinePresenter = null;
        }
        if (fStructurePresenter != null) {
            fStructurePresenter.uninstall();
            fStructurePresenter = null;
        }
        if (fHierarchyPresenter != null) {
            fHierarchyPresenter.uninstall();
            fHierarchyPresenter = null;
        }
        if (fForegroundColor != null) {
            fForegroundColor.dispose();
            fForegroundColor = null;
        }
        if (fBackgroundColor != null) {
            fBackgroundColor.dispose();
            fBackgroundColor = null;
        }

        if (fPreferenceStore != null) {
            fPreferenceStore.removePropertyChangeListener(this);
        }

        super.unconfigure();

        fIsConfigured = false;
    }

    /*
     * @see org.eclipse.jface.text.source.SourceViewer#createControl(org.eclipse.swt
     * .widgets.Composite, int)
     */
    @Override
    protected void createControl(Composite parent, int styles) {

        // Use LEFT_TO_RIGHT unless otherwise specified.
        if ((styles & SWT.RIGHT_TO_LEFT) == 0 && (styles & SWT.LEFT_TO_RIGHT) == 0) {
            styles |= SWT.LEFT_TO_RIGHT;
        }

        super.createControl(parent, styles);

        fBackspaceManager = new SmartBackspaceManager();
        fBackspaceManager.install(this);

        StyledText text = getTextWidget();
        text.addBidiSegmentListener(new BidiSegmentListener() {
            @Override
            public void lineGetSegments(BidiSegmentEvent event) {
                if (redraws()) {
                    event.segments = getBidiLineSegments(event.lineOffset, event.lineText);
                }
            }
        });
    }

    /**
     * Returns a segmentation of the given line appropriate for BIDI rendering. The default
     * implementation returns only the string literals of a java code line as segments.
     * 
     * @param widgetLineOffset the offset of the line
     * @param line the content of the line
     * @return the line's BIDI segmentation
     */
    protected int[] getBidiLineSegments(int widgetLineOffset, String line) {
        if (line != null && line.length() > 0) {
            int lineOffset = widgetOffset2ModelOffset(widgetLineOffset);
            try {
                return getBidiLineSegments(getDocument(), lineOffset);
            } catch (BadLocationException x) {
                return null; // don't segment line in this case
            }
        }
        return null;
    }

    /*
     * @see org.eclipse.jface.text.source.SourceViewer#handleDispose()
     */
    @Override
    protected void handleDispose() {
        if (fBackspaceManager != null) {
            fBackspaceManager.uninstall();
            fBackspaceManager = null;
        }

        super.handleDispose();
    }

    protected void initializeViewerColors() {
        if (fPreferenceStore != null) {

            StyledText styledText = getTextWidget();

            // ----------- foreground color --------------------
            Color color = fPreferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT)
                    ? null
                    : createColor(fPreferenceStore, AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND,
                            styledText.getDisplay());
            styledText.setForeground(color);

            if (fForegroundColor != null) {
                fForegroundColor.dispose();
            }

            fForegroundColor = color;

            // ---------- background color ----------------------
            color = fPreferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT)
                    ? null
                    : createColor(fPreferenceStore, AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND,
                            styledText.getDisplay());
            styledText.setBackground(color);

            if (fBackgroundColor != null) {
                fBackgroundColor.dispose();
            }

            fBackgroundColor = color;

            // ----------- selection foreground color --------------------
            color = fPreferenceStore.getBoolean(
                    AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_FOREGROUND_DEFAULT_COLOR)
                            ? null
                            : createColor(fPreferenceStore,
                                    AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_FOREGROUND_COLOR,
                                    styledText.getDisplay());
            styledText.setSelectionForeground(color);

            if (fSelectionForegroundColor != null) {
                fSelectionForegroundColor.dispose();
            }

            fSelectionForegroundColor = color;

            // ---------- selection background color ----------------------
            color = fPreferenceStore.getBoolean(
                    AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_BACKGROUND_DEFAULT_COLOR)
                            ? null
                            : createColor(fPreferenceStore,
                                    AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_BACKGROUND_COLOR,
                                    styledText.getDisplay());
            styledText.setSelectionBackground(color);

            if (fSelectionBackgroundColor != null) {
                fSelectionBackgroundColor.dispose();
            }

            fSelectionBackgroundColor = color;
        }
    }

    /**
     * {@inheritDoc}
     * <p>
     * Performance optimization: since we know at this place that none of the clients expects the
     * given range to be untouched we reuse the given range as return value.
     * </p>
     */
    @Override
    protected StyleRange modelStyleRange2WidgetStyleRange(StyleRange range) {
        IRegion region = modelRange2WidgetRange(new Region(range.start, range.length));
        if (region != null) {
            // don't clone the style range, but simply reuse it.
            range.start = region.getOffset();
            range.length = region.getLength();
            return range;
        }
        return null;
    }

    /**
     * {@inheritDoc}
     * <p>
     * This is a performance optimization to reduce the computation of the text presentation triggered
     * by {@link #setVisibleDocument(IDocument)}
     * </p>
     * 
     * @see #prepareDelayedProjection()
     */
    @Override
    protected void setVisibleDocument(IDocument document) {
        if (fIsSetVisibleDocumentDelayed) {
            fIsSetVisibleDocumentDelayed = false;
            IDocument previous = getVisibleDocument();
            enableProjection(); // will set the visible document if anything is folded
            IDocument current = getVisibleDocument();
            // if the visible document was not replaced, continue as usual
            if (current != null && current != previous) {
                return;
            }
        }

        super.setVisibleDocument(document);
    }

    /**
     * Returns the reconciler.
     * 
     * @return the reconciler or <code>null</code> if not set
     */
    IReconciler getReconciler() {
        return fReconciler;
    }

    /**
     * Delays setting the visual document until after the projection has been computed. This method
     * must only be called before the document is set on the viewer.
     * <p>
     * This is a performance optimization to reduce the computation of the text presentation triggered
     * by <code>setVisibleDocument(IDocument)</code>.
     * </p>
     * 
     * @see #setVisibleDocument(IDocument)
     */
    void prepareDelayedProjection() {
        Assert.isTrue(!fIsSetVisibleDocumentDelayed);
        fIsSetVisibleDocumentDelayed = true;
    }

    /**
     * Sets the given reconciler.
     * 
     * @param reconciler the reconciler
     */
    void setReconciler(IReconciler reconciler) {
        fReconciler = reconciler;
    }

    /**
     * Creates a color from the information stored in the given preference store. Returns
     * <code>null</code> if there is no such information available.
     * 
     * @param store the store to read from
     * @param key the key used for the lookup in the preference store
     * @param display the display used create the color
     * @return the created color according to the specification in the preference store
     */
    private Color createColor(IPreferenceStore store, String key, Display display) {

        RGB rgb = null;

        if (store.contains(key)) {

            if (store.isDefault(key)) {
                rgb = PreferenceConverter.getDefaultColor(store, key);
            } else {
                rgb = PreferenceConverter.getColor(store, key);
            }

            if (rgb != null) {
                return new Color(display, rgb);
            }
        }

        return null;
    }
}