com.aptana.editor.common.AbstractThemeableEditor.java Source code

Java tutorial

Introduction

Here is the source code for com.aptana.editor.common.AbstractThemeableEditor.java

Source

/**
 * Aptana Studio
 * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions).
 * Please see the license.html included with this distribution for details.
 * Any modifications to this file must keep this entire header intact.
 */
package com.aptana.editor.common;

import java.io.File;
import java.text.MessageFormat;
import java.util.List;
import java.util.StringTokenizer;

import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.manipulation.RemoveTrailingWhitespaceOperation;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.jface.text.source.CommonLineNumberChangeRulerColumn;
import org.eclipse.jface.text.source.IChangeRulerColumn;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.IVerticalRulerColumn;
import org.eclipse.jface.text.source.LineNumberRulerColumn;
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.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.IPostSelectionProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.dnd.IDragAndDropService;
import org.eclipse.ui.editors.text.TextFileDocumentProvider;
import org.eclipse.ui.ide.FileStoreEditorInput;
import org.eclipse.ui.internal.editors.text.EditorsPlugin;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
import org.eclipse.ui.texteditor.ChainedPreferenceStore;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.ITextEditorActionConstants;
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
import org.eclipse.ui.texteditor.TextOperationAction;
import org.eclipse.ui.views.contentoutline.ContentOutline;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import org.eclipse.ui.views.properties.IPropertySheetPage;
import org.osgi.service.prefs.BackingStoreException;

import com.aptana.core.logging.IdeLog;
import com.aptana.core.util.CollectionsUtil;
import com.aptana.core.util.EclipseUtil;
import com.aptana.core.util.StringUtil;
import com.aptana.editor.common.actions.FilterThroughCommandAction;
import com.aptana.editor.common.actions.FoldingActionsGroup;
import com.aptana.editor.common.dnd.SnippetTransfer;
import com.aptana.editor.common.extensions.FindBarEditorExtension;
import com.aptana.editor.common.extensions.IThemeableEditor;
import com.aptana.editor.common.extensions.ThemeableEditorExtension;
import com.aptana.editor.common.internal.AbstractFoldingEditor;
import com.aptana.editor.common.internal.peer.CharacterPairMatcher;
import com.aptana.editor.common.internal.peer.PeerCharacterCloser;
import com.aptana.editor.common.internal.scripting.CommandElementsProvider;
import com.aptana.editor.common.outline.CommonOutlinePage;
import com.aptana.editor.common.preferences.IPreferenceConstants;
import com.aptana.editor.common.properties.CommonEditorPropertySheetPage;
import com.aptana.editor.common.scripting.commands.CommandExecutionUtils;
import com.aptana.editor.common.text.reconciler.IFoldingComputer;
import com.aptana.editor.common.text.reconciler.RubyRegexpFolder;
import com.aptana.editor.common.viewer.CommonProjectionViewer;
import com.aptana.parsing.ParserPoolFactory;
import com.aptana.parsing.ast.IParseNode;
import com.aptana.parsing.ast.IParseRootNode;
import com.aptana.parsing.lexer.IRange;
import com.aptana.scripting.ScriptingActivator;
import com.aptana.scripting.model.BundleElement;
import com.aptana.scripting.model.CommandResult;
import com.aptana.scripting.model.InvocationType;
import com.aptana.scripting.model.SnippetElement;
import com.aptana.scripting.ui.ICommandElementsProvider;
import com.aptana.scripting.ui.ScriptingUIPlugin;
import com.aptana.theme.ThemePlugin;
import com.aptana.ui.util.UIUtils;

/**
 * Provides a way to override the editor fg, bg caret, highlight and selection from what is set in global text editor
 * color prefs.
 * 
 * @author cwilliams
 * @author schitale
 */
@SuppressWarnings("restriction")
public abstract class AbstractThemeableEditor extends AbstractFoldingEditor implements IThemeableEditor {

    private class SelectionChangedListener implements ISelectionChangedListener {

        public void install(ISelectionProvider selectionProvider) {
            if (selectionProvider == null) {
                return;
            }
            if (selectionProvider instanceof IPostSelectionProvider) {
                ((IPostSelectionProvider) selectionProvider).addPostSelectionChangedListener(this);
            } else {
                selectionProvider.addSelectionChangedListener(this);
            }
        }

        public void uninstall(ISelectionProvider selectionProvider) {
            if (selectionProvider == null) {
                return;
            }
            if (selectionProvider instanceof IPostSelectionProvider) {
                ((IPostSelectionProvider) selectionProvider).removePostSelectionChangedListener(this);
            } else {
                selectionProvider.removeSelectionChangedListener(this);
            }
        }

        public void selectionChanged(SelectionChangedEvent event) {
            AbstractThemeableEditor.this.selectionChanged();
        }
    }

    private class PropertyChangeListener implements IPropertyChangeListener {

        public void propertyChange(PropertyChangeEvent event) {
            handlePreferenceStoreChanged(event);
        }
    }

    private class SnippetDropTargetListener extends DropTargetAdapter {
        public void drop(DropTargetEvent event) {
            if (event.data instanceof SnippetElement) {
                SnippetElement snippet = (SnippetElement) event.data;
                CommandResult commandResult = CommandExecutionUtils.executeCommand(snippet, InvocationType.MENU,
                        AbstractThemeableEditor.this);
                if (commandResult == null) {
                    BundleElement bundle = snippet.getOwningBundle();
                    String bundleName = (bundle == null) ? "Unknown bundle" : bundle.getDisplayName(); //$NON-NLS-1$
                    IdeLog.logError(CommonEditorPlugin.getDefault(),
                            StringUtil.format("Error executing command {0} in bundle {1}. Command returned null.", //$NON-NLS-1$
                                    new String[] { snippet.getDisplayName(), bundleName }),
                            IDebugScopes.DRAG_DROP);
                } else {
                    CommandExecutionUtils.processCommandResult(snippet, commandResult,
                            AbstractThemeableEditor.this);
                    AbstractThemeableEditor.this.setFocus();
                }
            }
        }

        public void dragOver(DropTargetEvent event) {
            if (event.data instanceof SnippetElement) {
                event.feedback |= DND.FEEDBACK_SCROLL;
            }
        }

        public void dragEnter(DropTargetEvent event) {
            if (event.data instanceof SnippetElement) {
                event.detail = DND.DROP_COPY;
            }
        }
    }

    private static final char[] DEFAULT_PAIR_MATCHING_CHARS = new char[] { '(', ')', '{', '}', '[', ']', '`', '`',
            '\'', '\'', '"', '"' };

    private ICommandElementsProvider fCommandElementsProvider;

    private CommonOutlinePage fOutlinePage;

    private boolean fCursorChangeListened;
    private SelectionChangedListener fSelectionChangedListener;

    /**
     * Manages what's needed to make the find bar work.
     */
    private FindBarEditorExtension fThemeableEditorFindBarExtension;

    /**
     * Manages what's needed to make the colors obey the current theme.
     */
    private ThemeableEditorExtension fThemeableEditorColorsExtension;

    private IPropertyChangeListener fThemeListener;

    private PeerCharacterCloser fPeerCharacterCloser;

    private FoldingActionsGroup foldingActionsGroup;

    private ControlListener fWordWrapControlListener;

    private CommonOccurrencesUpdater occurrencesUpdater;

    private Job linkWithEditorJob;
    /**
     * Flag used to auto-expand outlines to 2nd level on first open.
     */
    protected boolean outlineAutoExpanded;

    /**
     * Used to cache the last ast for a document.
     */
    private long lastModificationStamp = IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;

    /**
     * Used to cache the last ast for a document.
     */
    private IParseRootNode lastAstForModificationStamp;

    /**
     * Lock used to cache the last ast for a document.
     */
    private Object modificationStampLock = new Object();

    /**
     * AbstractThemeableEditor
     */
    protected AbstractThemeableEditor() {
        super();
        fThemeableEditorFindBarExtension = new FindBarEditorExtension(this);
        fThemeableEditorColorsExtension = new ThemeableEditorExtension(this);
    }

    /*
     * (non-Javadoc)
     * @see com.aptana.editor.common.extensions.IThemeableEditor#getISourceViewer()
     */
    public final ISourceViewer getISourceViewer() {
        return super.getSourceViewer();
    }

    public final SourceViewerConfiguration getISourceViewerConfiguration() {
        return super.getSourceViewerConfiguration();

    }

    /*
     * (non-Javadoc)
     * @see com.aptana.editor.common.extensions.IThemeableEditor#getIVerticalRuler()
     */
    public final IVerticalRuler getIVerticalRuler() {
        return super.getVerticalRuler();
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.ui.texteditor.AbstractDecoratedTextEditor#createPartControl(org.eclipse.swt.widgets.Composite)
     */
    @Override
    public void createPartControl(Composite parent) {
        this.fThemeableEditorColorsExtension.setParent(parent);
        Composite findBarComposite = this.fThemeableEditorFindBarExtension.createFindBarComposite(parent);
        Assert.isNotNull(findBarComposite); // the find bar must be the new parent.
        super.createPartControl(findBarComposite);
        this.fThemeableEditorFindBarExtension.createFindBar(getSourceViewer());
        this.fThemeableEditorColorsExtension.overrideThemeColors();

        // TODO Let ERB editor override via subclass that does special handling of % pairing, where it only happens if
        // preceding char is '<'...
        fPeerCharacterCloser = new PeerCharacterCloser(getSourceViewer());
        fPeerCharacterCloser.install();
        fPeerCharacterCloser.setAutoInsertEnabled(
                getPreferenceStore().getBoolean(IPreferenceConstants.EDITOR_PEER_CHARACTER_CLOSE));
        fPeerCharacterCloser
                .setAutoWrapEnabled(getPreferenceStore().getBoolean(IPreferenceConstants.EDITOR_WRAP_SELECTION));

        fCursorChangeListened = true;

        fSelectionChangedListener = new SelectionChangedListener();
        fSelectionChangedListener.install(getSelectionProvider());
        fThemeListener = new PropertyChangeListener();
        ThemePlugin.getDefault().getPreferenceStore().addPropertyChangeListener(fThemeListener);
        this.fThemeableEditorFindBarExtension.activateContexts(
                new String[] { ScriptingActivator.EDITOR_CONTEXT_ID, ScriptingUIPlugin.SCRIPTING_CONTEXT_ID });

        if (isWordWrapEnabled()) {
            setWordWrapEnabled(true);
        }

        installOccurrencesUpdater();
    }

    protected void installOccurrencesUpdater() {
        // Initialize the occurrences annotations marker
        occurrencesUpdater = new CommonOccurrencesUpdater(this);
        occurrencesUpdater.initialize(getPreferenceStore());
    }

    @Override
    protected void initializeDragAndDrop(ISourceViewer viewer) {
        super.initializeDragAndDrop(viewer);

        // Adds snippet drag/drop support
        IDragAndDropService dndService = (IDragAndDropService) getSite().getService(IDragAndDropService.class);
        if (dndService == null) {
            return;
        }
        StyledText st = viewer.getTextWidget();
        DropTarget dropTarget = (DropTarget) st.getData(DND.DROP_TARGET_KEY);
        if (dropTarget != null) {
            Transfer[] transfers = dropTarget.getTransfer();
            List<Transfer> allTransfers = CollectionsUtil.newList(transfers);
            allTransfers.add(SnippetTransfer.getInstance());
            dropTarget.setTransfer(allTransfers.toArray(new Transfer[allTransfers.size()]));
            dropTarget.addDropListener(new SnippetDropTargetListener());
        } else {
            dndService.addMergedDropTarget(st, DND.DROP_COPY, new Transfer[] { SnippetTransfer.getInstance() },
                    new SnippetDropTargetListener());
        }
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.ui.texteditor.AbstractDecoratedTextEditor#getAdapter(java.lang.Class)
     */
    @SuppressWarnings("rawtypes")
    @Override
    public Object getAdapter(Class adapter) {
        if (SourceViewerConfiguration.class == adapter) {
            return getSourceViewerConfiguration();
        } else if (IContentOutlinePage.class == adapter) {
            // returns our custom adapter for the content outline page
            return getOutlinePage();
        } else if (ISourceViewer.class == adapter || ITextViewer.class == adapter) {
            return getSourceViewer();
        } else if (IPreferenceStore.class == adapter) {
            return getPluginPreferenceStore();
        } else if (ICommandElementsProvider.class == adapter) {
            return getCommandElementsProvider();
        }

        if (this.fThemeableEditorFindBarExtension != null) {
            Object adaptable = this.fThemeableEditorFindBarExtension.getFindBarDecoratorAdapter(adapter);
            if (adaptable != null) {
                return adaptable;
            }
        }

        if (adapter == IPropertySheetPage.class) {
            return new CommonEditorPropertySheetPage(getSourceViewer());
        }

        return super.getAdapter(adapter);
    }

    public CommonOutlinePage getOutlinePage() {
        if (fOutlinePage == null) {
            fOutlinePage = createOutlinePage();
        }
        return fOutlinePage;
    }

    public ITreeContentProvider getOutlineContentProvider() {
        return null;
    }

    public ILabelProvider getOutlineLabelProvider() {
        return null;
    }

    protected CommonOutlinePage createOutlinePage() {
        ITreeContentProvider outlineContentProvider = getOutlineContentProvider();
        ILabelProvider outlineLabelProvider = getOutlineLabelProvider();
        if (outlineContentProvider == null || outlineLabelProvider == null) {
            return null;
        }
        CommonOutlinePage outline = new CommonOutlinePage(this, getOutlinePreferenceStore());
        outline.setContentProvider(outlineContentProvider);
        outline.setLabelProvider(outlineLabelProvider);
        return outline;
    }

    protected abstract IPreferenceStore getPluginPreferenceStore();

    @Override
    protected void initializeLineNumberRulerColumn(LineNumberRulerColumn rulerColumn) {
        super.initializeLineNumberRulerColumn(rulerColumn);
        if (rulerColumn instanceof CommonLineNumberChangeRulerColumn) {
            ((CommonLineNumberChangeRulerColumn) rulerColumn).showLineNumbers(isLineNumberVisible());
        }
        this.fThemeableEditorColorsExtension.initializeLineNumberRulerColumn(rulerColumn);
    }

    private boolean isLineNumberVisible() {
        IPreferenceStore store = getPreferenceStore();
        return (store != null)
                ? store.getBoolean(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER)
                : false;
    }

    @Override
    protected ISourceViewer createSourceViewer(Composite parent, final IVerticalRuler ruler, int styles) {
        fAnnotationAccess = getAnnotationAccess();
        fOverviewRuler = createOverviewRuler(getSharedColors());

        // Need to make it a projection viewer now that we have folding...
        CommonProjectionViewer viewer = new CommonProjectionViewer(parent, ruler, getOverviewRuler(),
                isOverviewRulerVisible(), styles) {
            @SuppressWarnings("rawtypes")
            @Override
            public Object getAdapter(Class adapter) {
                if (AbstractThemeableEditor.class == adapter || ITextEditor.class == adapter) {
                    return AbstractThemeableEditor.this;
                }
                return super.getAdapter(adapter);
            }

        };

        // ensure decoration support has been created and configured.
        getSourceViewerDecorationSupport(viewer);

        fThemeableEditorColorsExtension.createBackgroundPainter(viewer);

        return viewer;
    }

    @Override
    protected void configureSourceViewerDecorationSupport(SourceViewerDecorationSupport support) {
        super.configureSourceViewerDecorationSupport(support);

        support.setCharacterPairMatcher(new CharacterPairMatcher(getPairMatchingCharacters()));
        support.setMatchingCharacterPainterPreferenceKeys(IPreferenceConstants.ENABLE_CHARACTER_PAIR_COLORING,
                IPreferenceConstants.CHARACTER_PAIR_COLOR);
    }

    /**
     * Return an array of character pairs used in our pair matching highlighter. Even number chars are the start, odd
     * are the end.
     * 
     * @return
     */
    public char[] getPairMatchingCharacters() {
        return DEFAULT_PAIR_MATCHING_CHARS;
    }

    @Override
    public void dispose() {
        try {
            SourceViewerConfiguration svc = getSourceViewerConfiguration();
            if (svc instanceof CommonSourceViewerConfiguration) {
                ((CommonSourceViewerConfiguration) svc).dispose();
            }
            if (fWordWrapControlListener != null) {
                ISourceViewer sourceViewer = getSourceViewer();
                if (sourceViewer != null) {
                    StyledText textWidget = sourceViewer.getTextWidget();
                    if (textWidget != null && !textWidget.isDisposed()) {
                        textWidget.removeControlListener(fWordWrapControlListener);
                    }
                }
                fWordWrapControlListener = null;
            }

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

            if (fSelectionChangedListener != null) {
                fSelectionChangedListener.uninstall(getSelectionProvider());
                fSelectionChangedListener = null;
            }

            if (fThemeListener != null) {
                ThemePlugin.getDefault().getPreferenceStore().removePropertyChangeListener(fThemeListener);
                fThemeListener = null;
            }

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

            if (fThemeableEditorFindBarExtension != null) {
                fThemeableEditorFindBarExtension.dispose();
                fThemeableEditorFindBarExtension = null;
            }
            if (foldingActionsGroup != null) {
                foldingActionsGroup.dispose();
                foldingActionsGroup = null;
            }

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

            fCommandElementsProvider = null;
            fPeerCharacterCloser = null;

            IDragAndDropService dndService = (IDragAndDropService) getSite().getService(IDragAndDropService.class);
            if (dndService != null) {
                ProjectionViewer viewer = (ProjectionViewer) getSourceViewer();
                StyledText st = viewer.getTextWidget();
                dndService.removeMergedDropTarget(st);
            }
        } finally {
            super.dispose();
        }
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.ui.texteditor.AbstractTextEditor#init(org.eclipse.ui.IEditorSite, org.eclipse.ui.IEditorInput)
     */
    @Override
    public void init(IEditorSite site, IEditorInput input) throws PartInitException {
        super.init(site, input);
        setEditorContextMenuId(getSite().getId());
    }

    @Override
    protected void initializeEditor() {
        setPreferenceStore(new ChainedPreferenceStore(
                new IPreferenceStore[] { CommonEditorPlugin.getDefault().getPreferenceStore(),
                        EditorsPlugin.getDefault().getPreferenceStore() }));
    }

    @Override
    public void doSave(IProgressMonitor progressMonitor) {
        if (getPreferenceStore().getBoolean(IPreferenceConstants.EDITOR_REMOVE_TRAILING_WHITESPACE)) {
            // Remove any trailing spaces
            RemoveTrailingWhitespaceOperation removeSpacesOperation = new RemoveTrailingWhitespaceOperation();
            try {
                removeSpacesOperation.run(FileBuffers.getTextFileBufferManager().getTextFileBuffer(getDocument()),
                        progressMonitor);
            } catch (Exception e) {
                IdeLog.logError(CommonEditorPlugin.getDefault(), "Error while removing the trailing whitespaces.", //$NON-NLS-1$
                        e);
            }
        }
        if (getEditorInput() instanceof UntitledFileStorageEditorInput) {
            // forces to show save as dialog on untitled file
            performSaveAs(progressMonitor);
        } else {
            super.doSave(progressMonitor);
        }
    }

    @Override
    protected void performSaveAs(IProgressMonitor progressMonitor) {
        progressMonitor = (progressMonitor == null) ? new NullProgressMonitor() : progressMonitor;
        IEditorInput input = getEditorInput();

        if (input instanceof UntitledFileStorageEditorInput) {
            Shell shell = getSite().getShell();

            // checks if user wants to save on the file system or in a workspace project
            boolean saveToProject = false;
            boolean byPassDialog = Platform.getPreferencesService().getBoolean(CommonEditorPlugin.PLUGIN_ID,
                    IPreferenceConstants.REMEMBER_UNTITLED_FILE_SAVE_TYPE, false, null);
            if (byPassDialog) {
                // grabs from preferences
                saveToProject = Platform.getPreferencesService().getBoolean(CommonEditorPlugin.PLUGIN_ID,
                        IPreferenceConstants.SAVE_UNTITLED_FILE_TO_PROJECT, false, null);
            } else {
                // asks the user
                MessageDialogWithToggle dialog = new MessageDialogWithToggle(shell,
                        Messages.AbstractThemeableEditor_SaveToggleDialog_Title, null,
                        Messages.AbstractThemeableEditor_SaveToggleDialog_Message, MessageDialog.NONE,
                        new String[] { Messages.AbstractThemeableEditor_SaveToggleDialog_LocalFilesystem,
                                Messages.AbstractThemeableEditor_SaveToggleDialog_Project },
                        0, null, false);
                int code = dialog.open();
                if (code == SWT.DEFAULT) {
                    return;
                }
                saveToProject = (code != IDialogConstants.INTERNAL_ID);
                if (dialog.getToggleState()) {
                    // the decision is remembered, so saves it
                    IEclipsePreferences prefs = (EclipseUtil.instanceScope()).getNode(CommonEditorPlugin.PLUGIN_ID);
                    prefs.putBoolean(IPreferenceConstants.REMEMBER_UNTITLED_FILE_SAVE_TYPE, true);
                    prefs.putBoolean(IPreferenceConstants.SAVE_UNTITLED_FILE_TO_PROJECT, saveToProject);
                    try {
                        prefs.flush();
                    } catch (BackingStoreException e) {
                        IdeLog.logError(CommonEditorPlugin.getDefault(), e);
                    }
                }
            }

            if (!saveToProject) {
                // saves to local filesystem
                FileDialog fileDialog = new FileDialog(shell, SWT.SAVE);
                String path = fileDialog.open();
                if (path == null) {
                    progressMonitor.setCanceled(true);
                    return;
                }

                // Check whether file exists and if so, confirm overwrite
                File localFile = new File(path);
                if (localFile.exists()) {
                    if (!MessageDialog.openConfirm(shell, Messages.AbstractThemeableEditor_ConfirmOverwrite_Title,
                            MessageFormat.format(Messages.AbstractThemeableEditor_ConfirmOverwrite_Message,
                                    path))) {
                        progressMonitor.setCanceled(true);
                    }
                }

                IFileStore fileStore;
                try {
                    fileStore = EFS.getStore(localFile.toURI());
                } catch (CoreException e) {
                    IdeLog.logError(CommonEditorPlugin.getDefault(), e);
                    MessageDialog.openError(shell, Messages.AbstractThemeableEditor_Error_Title,
                            MessageFormat.format(Messages.AbstractThemeableEditor_Error_Message, path));
                    return;
                }

                IDocumentProvider provider = getDocumentProvider();
                if (provider == null) {
                    return;
                }

                IEditorInput newInput;
                IFile file = getWorkspaceFile(fileStore);
                if (file != null) {
                    newInput = new FileEditorInput(file);
                } else {
                    newInput = new FileStoreEditorInput(fileStore);
                }

                boolean success = false;
                try {
                    provider.aboutToChange(newInput);
                    provider.saveDocument(progressMonitor, newInput, provider.getDocument(input), true);
                    success = true;
                } catch (CoreException e) {
                    IStatus status = e.getStatus();
                    if (status == null || status.getSeverity() != IStatus.CANCEL) {
                        MessageDialog.openError(shell, Messages.AbstractThemeableEditor_Error_Title,
                                MessageFormat.format(Messages.AbstractThemeableEditor_Error_Message, path));
                    }
                } finally {
                    provider.changed(newInput);
                    if (success) {
                        setInput(newInput);
                    }
                }

                progressMonitor.setCanceled(!success);
            } else {
                super.performSaveAs(progressMonitor);
            }
        } else {
            super.performSaveAs(progressMonitor);
        }
    }

    private IFile getWorkspaceFile(IFileStore fileStore) {
        IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
        IFile[] files = workspaceRoot.findFilesForLocationURI(fileStore.toURI());
        if (files != null && files.length > 0) {
            return files[0];
        }
        return null;
    }

    public String getContentType() {
        try {
            IContentType contentType = ((TextFileDocumentProvider) getDocumentProvider())
                    .getContentType(getEditorInput());
            if (contentType != null) {
                return contentType.getId();
            }
        } catch (Exception e) {
            IdeLog.logError(CommonEditorPlugin.getDefault(), e);
        }
        return null;
    }

    @Override
    protected void initializeViewerColors(ISourceViewer viewer) {
        if (viewer == null || viewer.getTextWidget() == null)
            return;
        super.initializeViewerColors(viewer);
    }

    @Override
    protected void handlePreferenceStoreChanged(PropertyChangeEvent event) {
        super.handlePreferenceStoreChanged(event);
        if (this.fThemeableEditorColorsExtension == null) {
            return;
        }
        this.fThemeableEditorColorsExtension.handlePreferenceStoreChanged(event);

        // Add case when the global editor settings have changed
        String property = event.getProperty();

        if (property.equals(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER)) {
            ((CommonLineNumberChangeRulerColumn) fLineNumberRulerColumn).showLineNumbers(isLineNumberVisible());
        } else if (property.equals(IPreferenceConstants.EDITOR_PEER_CHARACTER_CLOSE)) {
            fPeerCharacterCloser
                    .setAutoInsertEnabled(Boolean.parseBoolean(StringUtil.getStringValue(event.getNewValue())));
        } else if (property.equals(IPreferenceConstants.EDITOR_WRAP_SELECTION)) {
            fPeerCharacterCloser
                    .setAutoWrapEnabled(Boolean.parseBoolean(StringUtil.getStringValue(event.getNewValue())));
        } else if (property.equals(IPreferenceConstants.EDITOR_ENABLE_FOLDING)) {
            SourceViewerConfiguration config = getSourceViewerConfiguration();
            if (config instanceof CommonSourceViewerConfiguration) {
                ((CommonSourceViewerConfiguration) config).forceReconcile();
            }
        } else if (IPreferenceConstants.USE_GLOBAL_DEFAULTS.equals(property)) {
            // Update the tab settings when we modify the use global defaults preference
            IPreferenceStore store = getPreferenceStore();
            if (store != null) {
                getSourceViewer().getTextWidget()
                        .setTabs(store.getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH));
            }
            if (isTabsToSpacesConversionEnabled()) {
                installTabsToSpacesConverter();
            } else {
                uninstallTabsToSpacesConverter();
            }
            return;
        }
    }

    public Object computeHighlightedOutlineNode(int caretOffset) {
        return getOutlineElementAt(caretOffset);
    }

    public int getCaretOffset() {
        ISourceViewer sourceViewer = getSourceViewer();
        if (sourceViewer == null) {
            return -1;
        }
        StyledText styledText = sourceViewer.getTextWidget();
        if (styledText == null) {
            return -1;
        }

        if (sourceViewer instanceof ITextViewerExtension5) {
            ITextViewerExtension5 extension = (ITextViewerExtension5) sourceViewer;
            return extension.widgetOffset2ModelOffset(styledText.getCaretOffset());
        }
        int offset = sourceViewer.getVisibleRegion().getOffset();
        return offset + styledText.getCaretOffset();
    }

    public void select(IRange element, boolean checkIfOutlineActive) {
        try {
            if (element != null && (!checkIfOutlineActive || isOutlinePageActive())) {
                // disables listening to cursor change so we don't get into the loop of setting selections between
                // editor and outline
                fCursorChangeListened = false;
                setSelectedElement(element);
            }
        } catch (Exception e) {
            IdeLog.logError(CommonEditorPlugin.getDefault(), e);
        }
    }

    protected void setSelectedElement(IRange element) {
        if (element == null) {
            return;
        }
        try {
            int offset = element.getStartingOffset();
            int length = element.getLength();
            setHighlightRange(offset, length, false);
            selectAndReveal(offset, length);
        } catch (Exception e) {
            IdeLog.logError(CommonEditorPlugin.getDefault(), e);
        }
    }

    protected void selectionChanged() {
        try {
            if (fCursorChangeListened) {
                if (hasOutlinePageCreated() && isLinkedWithEditor()) {
                    final int caretOffset = getCaretOffset();
                    // runs the computation of which node in the outline tp select in a non-UI job
                    if (linkWithEditorJob != null) {
                        linkWithEditorJob.cancel();
                    }

                    linkWithEditorJob = new Job("Computing Outline node to select...") //$NON-NLS-1$
                    {

                        @Override
                        protected IStatus run(IProgressMonitor monitor) {
                            final Object outlineNode = computeHighlightedOutlineNode(caretOffset);
                            UIUtils.getDisplay().asyncExec(new Runnable() {

                                public void run() {
                                    getOutlinePage().select(outlineNode);
                                }
                            });
                            return Status.OK_STATUS;
                        }
                    };
                    EclipseUtil.setSystemForJob(linkWithEditorJob);
                    linkWithEditorJob.schedule();
                }
            } else {
                // re-enables listening to cursor change
                fCursorChangeListened = true;
            }
        } catch (Exception e) {
            IdeLog.logError(CommonEditorPlugin.getDefault(), e);
        }
    }

    @Override
    protected void createActions() {
        super.createActions();
        setAction(FilterThroughCommandAction.COMMAND_ID, FilterThroughCommandAction.create(this));
        this.fThemeableEditorFindBarExtension.createFindBarActions();

        // Code formatter setup
        Action action = new TextOperationAction(Messages.getBundleForConstructedKeys(), "Format.", this, //$NON-NLS-1$
                ISourceViewer.FORMAT);
        action.setActionDefinitionId(ICommonConstants.FORMATTER_ACTION_DEFINITION_ID);
        setAction(ICommonConstants.FORMATTER_ACTION_ID, action);
        markAsStateDependentAction(ICommonConstants.FORMATTER_ACTION_ID, true);
        markAsSelectionDependentAction(ICommonConstants.FORMATTER_ACTION_ID, true);

        // Folding setup
        foldingActionsGroup = new FoldingActionsGroup(this);
    }

    /*
     * (non-Javadoc)
     * @seeorg.eclipse.ui.texteditor.AbstractDecoratedTextEditor#rulerContextMenuAboutToShow(org.eclipse.jface.action.
     * IMenuManager)
     */
    @Override
    protected void rulerContextMenuAboutToShow(IMenuManager menu) {
        super.rulerContextMenuAboutToShow(menu);
        IMenuManager foldingMenu = new MenuManager(Messages.Folding_GroupName, "folding"); //$NON-NLS-1$
        menu.appendToGroup(ITextEditorActionConstants.GROUP_RULERS, foldingMenu);
        getFoldingActionsGroup().fillMenu(foldingMenu);
    }

    synchronized ICommandElementsProvider getCommandElementsProvider() {
        if (fCommandElementsProvider == null) {
            fCommandElementsProvider = new CommandElementsProvider(this, getSourceViewer());
        }
        return fCommandElementsProvider;
    }

    /**
     * Returns a description of the cursor position.
     * 
     * @return a description of the cursor position
     */
    protected String getCursorPosition() {
        String raw = null;
        try {
            raw = super.getCursorPosition();
            StringTokenizer tokenizer = new StringTokenizer(raw, " :"); //$NON-NLS-1$
            String line = tokenizer.nextToken();
            String column = tokenizer.nextToken();
            return MessageFormat.format(Messages.AbstractThemeableEditor_CursorPositionLabel, line, column);
        } catch (Exception e) {
            IdeLog.logError(CommonEditorPlugin.getDefault(), e);
        }
        return raw;
    }

    /**
     * Retrieves the logical parse element closest to the caret position for the outline. Subclass should override.
     * 
     * @param caret
     *            the caret position
     * @return the closest logical parse element
     */
    protected Object getOutlineElementAt(int caret) {
        try {
            if (fOutlinePage == null) {
                return null;
            }
            IParseNode astNode = getASTNodeAt(caret, fOutlinePage.getCurrentAst());
            if (astNode == null) {
                return null;
            }
            return fOutlinePage.getOutlineItem(astNode);
        } catch (Exception e) {
            IdeLog.logError(CommonEditorPlugin.getDefault(), e);
        }
        return null;
    }

    /**
     * @return the preference store for outline page
     */
    protected IPreferenceStore getOutlinePreferenceStore() {
        return CommonEditorPlugin.getDefault().getPreferenceStore();
    }

    protected IDocument getDocument() {
        IDocumentProvider documentProvider = getDocumentProvider();
        if (documentProvider == null) {
            return null;
        }
        return documentProvider.getDocument(getEditorInput());
    }

    @Override
    protected void doSetInput(IEditorInput input) throws CoreException {
        synchronized (modificationStampLock) {
            // Reset our cache when a new input is set.
            lastModificationStamp = IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
            lastAstForModificationStamp = null;

        }
        super.doSetInput(input);
    }

    /**
     * @return the parse node for the current contents of this editor to be used during a reconcile. This means that the
     *         AST generated will be used for updating the outline and updating the folding structure (subclasses may
     *         override -- i.e.: in the JS editor, if folding is enabled, the ast will need to be generated with
     *         comments).
     */
    public IParseRootNode getReconcileAST() {
        return getAST();
    }

    /**
     * Note: this was deprecated and is restored as this has a faster cache based on the document time (so, this is the
     * preferred way of getting the ast based on the full document for the editor).
     * 
     * @return the parse node for this editor.
     * @note this call may lock until the parser finishes generating the ast.
     * @note override doGetAST if something needs to be customized and the document-based cache maintained (i.e.: php
     *       may need to override this method as the parse depends on the grammar version which may change).
     */
    public IParseRootNode getAST() {
        try {
            IDocument document = getDocument();
            if (document == null) {
                return null;
            }
            long modificationStamp = IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
            if (document instanceof IDocumentExtension4) {
                synchronized (modificationStampLock) {
                    IDocumentExtension4 iDocumentExtension = (IDocumentExtension4) document;
                    modificationStamp = iDocumentExtension.getModificationStamp();
                    if (modificationStamp != IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP
                            && modificationStamp == lastModificationStamp) {
                        return lastAstForModificationStamp;
                    }
                }
            }
            // Don't synchronize the actual parse!
            IParseRootNode ast = doGetAST(document);

            synchronized (modificationStampLock) {
                lastAstForModificationStamp = ast;
                lastModificationStamp = modificationStamp;
                return lastAstForModificationStamp;
            }
        } catch (Exception e) {
            IdeLog.logTrace(CommonEditorPlugin.getDefault(), e.getMessage(), e, IDebugScopes.AST);
        }
        return null;
    }

    /**
     * Override this method to calculate the ast (while maintaining the document time based cache).
     */
    protected IParseRootNode doGetAST(IDocument document) throws Exception {
        return ParserPoolFactory.parse(getContentType(), document.get()).getRootNode();
    }

    /**
     * @deprecated This doesn't belong on the editor, this should be in some ASTUtil method or something...
     * @param offset
     * @param iParseRootNode
     * @return
     */
    protected IParseNode getASTNodeAt(int offset, IParseRootNode root) {
        try {
            if (root == null) {
                return null;
            }
            return root.getNodeAtOffset(offset);
        } catch (Exception e) {
            IdeLog.logError(CommonEditorPlugin.getDefault(), e);
        }
        return null;
    }

    /**
     * Returns the folding actions group for the editor.
     * 
     * @return The {@link FoldingActionsGroup} for this editor.
     */
    protected FoldingActionsGroup getFoldingActionsGroup() {
        return foldingActionsGroup;
    }

    private boolean isLinkedWithEditor() {
        return getOutlinePreferenceStore().getBoolean(IPreferenceConstants.LINK_OUTLINE_WITH_EDITOR);
    }

    private boolean isOutlinePageActive() {
        IWorkbenchPart part = getActivePart();
        return part instanceof ContentOutline && ((ContentOutline) part).getCurrentPage() == fOutlinePage;
    }

    private IWorkbenchPart getActivePart() {
        IWorkbenchWindow window = getSite().getWorkbenchWindow();
        return window.getPartService().getActivePart();
    }

    /**
     * Made public so we can set TM_SOFT_TABS for scripting
     */
    @Override
    public boolean isTabsToSpacesConversionEnabled() {
        // Make public so we can grab the value
        return super.isTabsToSpacesConversionEnabled();
    }

    /**
     * Added so we can set TM_TAB_SIZE for scripting.
     * 
     * @return
     */
    public int getTabSize() {
        SourceViewerConfiguration config = getSourceViewerConfiguration();
        if (config != null) {
            return config.getTabWidth(getSourceViewer());
        }
        return 4;
    }

    @Override
    public boolean isSaveAsAllowed() {
        return true;
    }

    public boolean hasOutlinePageCreated() {
        return fOutlinePage != null;
    }

    /**
     * Returns true if the editor's preferences are set to fold.
     * 
     * @return True, if folding is on; False, in case it's off.
     */
    public boolean isFoldingEnabled() {
        IPreferenceStore store = getPreferenceStore();
        return store != null && store.getBoolean(IPreferenceConstants.EDITOR_ENABLE_FOLDING);
    }

    /**
     * Returns true if the editor's preferences are set to mark element occurrences.
     * 
     * @return True, if mark occurrences is on; False, in case it's off.
     */
    public boolean isMarkingOccurrences() {
        IPreferenceStore store = getPreferenceStore();
        return store != null && store.getBoolean(IPreferenceConstants.EDITOR_MARK_OCCURRENCES);
    }

    /**
     * Create the implementation of the folding computer. Default is to use regexp defined in bundle/ruble for this
     * language. Can be overridden on a per-editor basis.
     * 
     * @param document
     * @return
     */
    public IFoldingComputer createFoldingComputer(IDocument document) {
        return new RubyRegexpFolder(this, document);
    }

    public boolean getWordWrapEnabled() {
        return getSourceViewer().getTextWidget().getWordWrap();
    }

    public void setWordWrapEnabled(boolean enabled) {
        StyledText textWidget = getSourceViewer().getTextWidget();
        if (textWidget.getWordWrap() != enabled) {
            textWidget.setWordWrap(enabled);
            fLineNumberRulerColumn.redraw();
        }
    }

    @Override
    protected IVerticalRulerColumn createLineNumberRulerColumn() {
        fLineNumberRulerColumn = new CommonLineNumberChangeRulerColumn(getSharedColors());
        ((IChangeRulerColumn) fLineNumberRulerColumn).setHover(createChangeHover());
        initializeLineNumberRulerColumn(fLineNumberRulerColumn);
        return fLineNumberRulerColumn;
    }

    private boolean isWordWrapEnabled() {
        return Platform.getPreferencesService().getBoolean(CommonEditorPlugin.PLUGIN_ID,
                IPreferenceConstants.ENABLE_WORD_WRAP, false, null);
    }

    public void refreshOutline(final IParseRootNode ast) {
        if (!hasOutlinePageCreated()) {
            return;
        }
        // TODO Does this need to be run in asyncExec here?

        Display.getDefault().asyncExec(new Runnable() {

            public void run() {
                CommonOutlinePage page = getOutlinePage();
                page.refresh(ast);

                if (!outlineAutoExpanded) {
                    page.expandToLevel(2);
                    outlineAutoExpanded = true;
                }
            }
        });
    }

}