Package org.eclipse.php.internal.ui.editor

Source Code of org.eclipse.php.internal.ui.editor.PHPStructuredEditor

/*******************************************************************************
* Copyright (c) 2009 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
*     Zend Technologies
*******************************************************************************/
package org.eclipse.php.internal.ui.editor;

import java.io.IOException;
import java.text.CharacterIterator;
import java.util.*;

import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.IFileBufferStatusCodes;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.manipulation.MultiTextEditWithProgress;
import org.eclipse.core.filebuffers.manipulation.RemoveTrailingWhitespaceOperation;
import org.eclipse.core.internal.filebuffers.Progress;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.IJobManager;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
import org.eclipse.core.runtime.preferences.IPreferencesService;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.dltk.core.*;
import org.eclipse.dltk.internal.ui.actions.CompositeActionGroup;
import org.eclipse.dltk.internal.ui.actions.FoldingMessages;
import org.eclipse.dltk.internal.ui.editor.*;
import org.eclipse.dltk.internal.ui.text.ScriptWordFinder;
import org.eclipse.dltk.ui.DLTKUIPlugin;
import org.eclipse.dltk.ui.actions.IScriptEditorActionDefinitionIds;
import org.eclipse.emf.common.command.BasicCommandStack;
import org.eclipse.emf.common.command.CommandStack;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.internal.text.html.HTMLTextPresenter;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.*;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.information.IInformationProvider;
import org.eclipse.jface.text.information.IInformationProviderExtension;
import org.eclipse.jface.text.information.IInformationProviderExtension2;
import org.eclipse.jface.text.information.InformationPresenter;
import org.eclipse.jface.text.link.LinkedModeModel;
import org.eclipse.jface.text.reconciler.IReconciler;
import org.eclipse.jface.text.source.*;
import org.eclipse.jface.text.source.ImageUtilities;
import org.eclipse.jface.text.source.projection.ProjectionViewer;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.*;
import org.eclipse.php.internal.core.PHPCoreConstants;
import org.eclipse.php.internal.core.PHPCorePlugin;
import org.eclipse.php.internal.core.PHPToolkitUtil;
import org.eclipse.php.internal.core.ast.locator.PhpElementConciliator;
import org.eclipse.php.internal.core.ast.nodes.*;
import org.eclipse.php.internal.core.corext.dom.NodeFinder;
import org.eclipse.php.internal.core.documentModel.dom.IImplForPhp;
import org.eclipse.php.internal.core.documentModel.parser.PhpSourceParser;
import org.eclipse.php.internal.core.documentModel.parser.regions.IPhpScriptRegion;
import org.eclipse.php.internal.core.documentModel.partitioner.PHPPartitionTypes;
import org.eclipse.php.internal.core.preferences.IPreferencesPropagatorListener;
import org.eclipse.php.internal.core.preferences.PreferencesPropagatorEvent;
import org.eclipse.php.internal.core.preferences.PreferencesSupport;
import org.eclipse.php.internal.core.project.PhpVersionChangedHandler;
import org.eclipse.php.internal.core.search.IOccurrencesFinder;
import org.eclipse.php.internal.core.search.IOccurrencesFinder.OccurrenceLocation;
import org.eclipse.php.internal.core.search.OccurrencesFinderFactory;
import org.eclipse.php.internal.ui.IPHPHelpContextIds;
import org.eclipse.php.internal.ui.Logger;
import org.eclipse.php.internal.ui.PHPUIMessages;
import org.eclipse.php.internal.ui.PHPUiPlugin;
import org.eclipse.php.internal.ui.actions.*;
import org.eclipse.php.internal.ui.actions.GotoMatchingBracketAction;
import org.eclipse.php.internal.ui.autoEdit.TabAutoEditStrategy;
import org.eclipse.php.internal.ui.editor.configuration.PHPStructuredTextViewerConfiguration;
import org.eclipse.php.internal.ui.editor.hover.PHPSourceViewerInformationControl;
import org.eclipse.php.internal.ui.editor.selectionactions.*;
import org.eclipse.php.internal.ui.folding.PHPFoldingStructureProviderProxy;
import org.eclipse.php.internal.ui.preferences.PreferenceConstants;
import org.eclipse.php.internal.ui.text.DocumentCharacterIterator;
import org.eclipse.php.internal.ui.text.PHPWordIterator;
import org.eclipse.php.internal.ui.util.PHPPluginImages;
import org.eclipse.php.internal.ui.viewsupport.ISelectionListenerWithAST;
import org.eclipse.php.internal.ui.viewsupport.SelectionListenerWithASTManager;
import org.eclipse.php.ui.editor.SharedASTProvider;
import org.eclipse.php.ui.editor.hover.IHoverMessageDecorator;
import org.eclipse.php.ui.editor.hover.IPHPTextHover;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.*;
import org.eclipse.swt.dnd.*;
import org.eclipse.swt.graphics.GC;
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.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.text.edits.DeleteEdit;
import org.eclipse.ui.*;
import org.eclipse.ui.actions.ActionContext;
import org.eclipse.ui.actions.ActionGroup;
import org.eclipse.ui.dnd.IDragAndDropService;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.ui.editors.text.IFoldingCommandIds;
import org.eclipse.ui.texteditor.*;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionContainer;
import org.eclipse.wst.sse.ui.StructuredTextEditor;
import org.eclipse.wst.sse.ui.internal.SSEUIPlugin;
import org.eclipse.wst.sse.ui.internal.StorageModelProvider;
import org.eclipse.wst.sse.ui.internal.StructuredTextViewer;
import org.eclipse.wst.sse.ui.internal.actions.ActionDefinitionIds;
import org.eclipse.wst.sse.ui.internal.contentassist.StructuredContentAssistant;
import org.eclipse.wst.sse.ui.internal.contentoutline.ConfigurableContentOutlinePage;
import org.eclipse.wst.sse.ui.internal.projection.AbstractStructuredFoldingStrategy;
import org.eclipse.wst.sse.ui.internal.reconcile.DocumentRegionProcessor;
import org.eclipse.wst.sse.ui.internal.reconcile.ReconcileAnnotationKey;
import org.eclipse.wst.sse.ui.internal.reconcile.TemporaryAnnotation;
import org.eclipse.wst.sse.ui.reconcile.ISourceReconcilingListener;
import org.eclipse.wst.sse.ui.views.contentoutline.ContentOutlineConfiguration;

import com.ibm.icu.text.BreakIterator;

public class PHPStructuredEditor extends StructuredTextEditor implements
    IPhpScriptReconcilingListener {

  private static final String ORG_ECLIPSE_PHP_UI_ACTIONS_OPEN_FUNCTIONS_MANUAL_ACTION = "org.eclipse.php.ui.actions.OpenFunctionsManualAction"; //$NON-NLS-1$

  private IContentOutlinePage fPHPOutlinePage;
  protected PHPPairMatcher fBracketMatcher = new PHPPairMatcher(BRACKETS);
  private CompositeActionGroup fContextMenuGroup;
  private CompositeActionGroup fActionGroups;

  private long fLastActionsUpdate;

  /** Indicates whether the structure editor is displaying an external file */
  protected boolean isExternal;

  /** The editor's save policy */
  protected ISavePolicy fSavePolicy = null;

  /**
   * The internal shell activation listener for updating occurrences.
   *
   * @since 3.4
   */
  private ActivationListener fActivationListener = new ActivationListener();
  private ISelectionListenerWithAST fPostSelectionListenerWithAST;
  private OccurrencesFinderJob fOccurrencesFinderJob;
  /** The occurrences finder job canceler */
  private OccurrencesFinderJobCanceler fOccurrencesFinderJobCanceler;

  /**
   * The cached selected range.
   *
   * @see ITextViewer#getSelectedRange()
   * @since 3.3
   */
  private Point fCachedSelectedRange;

  /**
   * The selection used when forcing occurrence marking through code.
   *
   * @since 3.4
   */
  private ISelection fForcedMarkOccurrencesSelection;
  /**
   * The document modification stamp at the time when the last occurrence
   * marking took place.
   *
   * @since 3.4
   */
  private long fMarkOccurrenceModificationStamp = IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
  /**
   * The region of the word under the caret used to when computing the current
   * occurrence markings.
   *
   * @since 3.4
   */
  private IRegion fMarkOccurrenceTargetRegion;

  /**
   * Holds the current occurrence annotations.
   *
   * @since 3.4
   */
  private Annotation[] fOccurrenceAnnotations = null;
  /**
   * Tells whether all occurrences of the element at the current caret
   * location are automatically marked in this editor.
   *
   * @since 3.4
   */
  private boolean fMarkOccurrenceAnnotations;
  /**
   * Tells whether the occurrence annotations are sticky i.e. whether they
   * stay even if there's no valid Java element at the current caret position.
   * Only valid if {@link #fMarkOccurrenceAnnotations} is <code>true</code>.
   *
   * @since 3.4
   */
  private boolean fStickyOccurrenceAnnotations;
  /**
   * Tells whether to mark type occurrences in this editor. Only valid if
   * {@link #fMarkOccurrenceAnnotations} is <code>true</code>.
   *
   * @since 3.4
   */
  private boolean fMarkTypeOccurrences;
  /**
   * Tells whether to mark method and declaration occurrences in this editor.
   * Only valid if {@link #fMarkOccurrenceAnnotations} is <code>true</code>.
   *
   * @since 3.4
   */
  private boolean fMarkMethodOccurrences;
  /**
   * Tells whether to mark function occurrences in this editor. Only valid if
   * {@link #fMarkOccurrenceAnnotations} is <code>true</code>.
   *
   * @since 3.4
   */
  private boolean fMarkFunctionOccurrences;
  /**
   * Tells whether to mark constant occurrences in this editor. Only valid if
   * {@link #fMarkOccurrenceAnnotations} is <code>true</code>.
   *
   * @since 3.4
   */
  private boolean fMarkConstantOccurrences;
  /**
   * Tells whether to mark field global variable in this editor. Only valid if
   * {@link #fMarkOccurrenceAnnotations} is <code>true</code>.
   *
   * @since 3.4
   */
  private boolean fMarkGlobalVariableOccurrences;
  /**
   * Tells whether to mark local variable occurrences in this editor. Only
   * valid if {@link #fMarkOccurrenceAnnotations} is <code>true</code>.
   *
   * @since 3.4
   */
  private boolean fMarkLocalVariableOccurrences;
  /**
   * Tells whether to mark exception occurrences in this editor. Only valid if
   * {@link #fMarkOccurrenceAnnotations} is <code>true</code>.
   *
   * @since 3.4
   */
  private boolean fMarkExceptions;
  /**
   * Tells whether to mark method exits in this editor. Only valid if
   * {@link #fMarkOccurrenceAnnotations} is <code>true</code>.
   *
   * @since 3.4
   */
  private boolean fMarkMethodExitPoints;

  /**
   * Tells whether to mark targets of <code>break</code> and
   * <code>continue</code> statements in this editor. Only valid if
   * {@link #fMarkOccurrenceAnnotations} is <code>true</code>.
   *
   * @since 3.4
   */
  private boolean fMarkBreakContinueTargets;

  /**
   * Tells whether to mark implementors in this editor. Only valid if
   * {@link #fMarkOccurrenceAnnotations} is <code>true</code>.
   *
   * @since 3.4
   */
  private boolean fMarkImplementors;

  /**
   * Fix for outline synchronization while reconcile
   */
  protected volatile boolean fReconcileSelection = false;

  private boolean saveActionsEnabled = false;
  private boolean saveActionsIgnoreEmptyLines = false;

  /**
   * The override and implements indicator manager for this editor.
   *
   * @since 3.0
   */
  protected OverrideIndicatorManager fOverrideIndicatorManager;

  /**
   * Tells whether text drag and drop has been installed on the control.
   *
   * @since 3.3
   */
  private boolean fIsTextDragAndDropInstalled = false;

  /**
   * Helper token to decide whether drag and drop happens inside the same
   * editor.
   *
   * @since 3.3
   */
  private Object fTextDragAndDropToken;

  /**
   * we use this for updating the code folding listeners and other things
   */
  private PHPFoldingStructureProviderProxy fProjectionModelUpdater;

  /**
   * mark if we have installed the projectionSupport.
   */
  private boolean projectionSupportInstalled = false;;

  /** The selection history of the editor */
  private SelectionHistory fSelectionHistory;

  /**
   * Internal implementation class for a change listener.
   *
   * @since 3.0
   */
  protected abstract class AbstractSelectionChangedListener implements
      ISelectionChangedListener {

    /**
     * Installs this selection changed listener with the given selection
     * provider. If the selection provider is a post selection provider,
     * post selection changed events are the preferred choice, otherwise
     * normal selection changed events are requested.
     *
     * @param selectionProvider
     */
    public void install(ISelectionProvider selectionProvider) {
      if (selectionProvider == null)
        return;

      if (selectionProvider instanceof IPostSelectionProvider) {
        IPostSelectionProvider provider = (IPostSelectionProvider) selectionProvider;
        provider.addPostSelectionChangedListener(this);
      } else {
        selectionProvider.addSelectionChangedListener(this);
      }
    }

    /**
     * Removes this selection changed listener from the given selection
     * provider.
     *
     * @param selectionProvider
     *            the selection provider
     */
    public void uninstall(ISelectionProvider selectionProvider) {
      if (selectionProvider == null)
        return;

      if (selectionProvider instanceof IPostSelectionProvider) {
        IPostSelectionProvider provider = (IPostSelectionProvider) selectionProvider;
        provider.removePostSelectionChangedListener(this);
      } else {
        selectionProvider.removeSelectionChangedListener(this);
      }
    }
  }

  /**
   * Updates the Java outline page selection and this editor's range
   * indicator.
   *
   * @since 3.0
   */
  private class EditorSelectionChangedListener extends
      AbstractSelectionChangedListener {

    /*
     * @see
     * org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged
     * (org.eclipse.jface.viewers.SelectionChangedEvent)
     */
    public void selectionChanged(SelectionChangedEvent event) {
      // XXX: see https://bugs.eclipse.org/bugs/show_bug.cgi?id=56161
      PHPStructuredEditor.this.selectionChanged();
    }
  }

  /**
   * Updates the selection in the editor's widget with the selection of the
   * outline page.
   */
  class OutlineSelectionChangedListener extends
      AbstractSelectionChangedListener implements IDoubleClickListener {

    private ContentOutlineConfiguration configuration;

    public OutlineSelectionChangedListener(
        ContentOutlineConfiguration configuration) {
      this.configuration = configuration;
    }

    public void selectionChanged(SelectionChangedEvent event) {
      if (!configuration.isLinkedWithEditor(null)) {
        return;
      }
      doSelectionChanged(event);
    }

    public void doubleClick(DoubleClickEvent event) {
      doSelectionChanged(event);
    }

  }

  /**
   * The editor selection changed listener.
   */
  private EditorSelectionChangedListener fEditorSelectionChangedListener;
  private IPreferencesPropagatorListener fPhpVersionListener;
  private IPreferenceChangeListener fPreferencesListener;

  private void doSelectionChanged(ISelection selection) {
    ISourceReference reference = null;
    Iterator iter = ((IStructuredSelection) selection).iterator();
    while (iter.hasNext()) {
      Object o = iter.next();
      if (o instanceof ISourceReference) {
        reference = (ISourceReference) o;
        break;
      }
    }
    if (!isActivePart() && PHPUiPlugin.getActivePage() != null)
      PHPUiPlugin.getActivePage().bringToTop(this);
    setSelection(reference, !isActivePart());

  }

  protected void doSelectionChanged(SelectionChangedEvent event) {
    ISelection selection = event.getSelection();
    doSelectionChanged(selection);
  }

  protected void doSelectionChanged(DoubleClickEvent event) {
    ISelection selection = event.getSelection();
    doSelectionChanged(selection);
  }

  /**
   * This action behaves in two different ways: If there is no current text
   * hover, the javadoc is displayed using information presenter. If there is
   * a current text hover, it is converted into a information presenter in
   * order to make it sticky.
   */
  class InformationDispatchAction extends TextEditorAction {

    /** The wrapped text operation action. */
    private final TextOperationAction fTextOperationAction;

    /**
     * Creates a dispatch action.
     *
     * @param resourceBundle
     *            the resource bundle
     * @param prefix
     *            the prefix
     * @param textOperationAction
     *            the text operation action
     */
    public InformationDispatchAction(final ResourceBundle resourceBundle,
        final String prefix,
        final TextOperationAction textOperationAction) {
      super(resourceBundle, prefix, PHPStructuredEditor.this);
      if (textOperationAction == null)
        throw new IllegalArgumentException();
      fTextOperationAction = textOperationAction;
    }

    // modified version from TextViewer
    private int computeOffsetAtLocation(final ITextViewer textViewer,
        final int x, final int y) {

      final StyledText styledText = textViewer.getTextWidget();
      final IDocument document = textViewer.getDocument();

      if (document == null)
        return -1;

      try {
        int widgetOffset = styledText.getOffsetAtLocation(new Point(x,
            y));
        final Point p = styledText.getLocationAtOffset(widgetOffset);
        if (p.x > x)
          widgetOffset--;

        if (textViewer instanceof ITextViewerExtension5) {
          final ITextViewerExtension5 extension = (ITextViewerExtension5) textViewer;
          return extension.widgetOffset2ModelOffset(widgetOffset);
        }
        final IRegion visibleRegion = textViewer.getVisibleRegion();
        return widgetOffset + visibleRegion.getOffset();
      } catch (final IllegalArgumentException e) {
        return -1;
      }

    }

    /**
     * Tries to make an annotation hover focusable (or "sticky").
     *
     * @param sourceViewer
     *            the source viewer to display the hover over
     * @param annotationHover
     *            the hover to make focusable
     * @return <code>true</code> if successful, <code>false</code> otherwise
     * @since 3.2
     */
    private boolean makeAnnotationHoverFocusable(
        final ISourceViewer sourceViewer,
        final IAnnotationHover annotationHover) {
      final IVerticalRulerInfo info = getVerticalRuler();
      final int line = info.getLineOfLastMouseButtonActivity();
      if (line == -1)
        return false;

      try {

        // compute the hover information
        Object hoverInfo;
        if (annotationHover instanceof IAnnotationHoverExtension) {
          final IAnnotationHoverExtension extension = (IAnnotationHoverExtension) annotationHover;
          final ILineRange hoverLineRange = extension
              .getHoverLineRange(sourceViewer, line);
          if (hoverLineRange == null)
            return false;
          final int maxVisibleLines = Integer.MAX_VALUE; // allow any
          // number of
          // lines
          // being
          // displayed,
          // as we
          // support scrolling
          hoverInfo = extension.getHoverInfo(sourceViewer,
              hoverLineRange, maxVisibleLines);
        } else
          hoverInfo = annotationHover
              .getHoverInfo(sourceViewer, line);

        // hover region: the beginning of the concerned line to place
        // the control right over the line
        final IDocument document = sourceViewer.getDocument();
        final int offset = document.getLineOffset(line);
        final String contentType = TextUtilities.getContentType(
            document, PHPPartitionTypes.PHP_DOC, offset, true);

        IInformationControlCreator controlCreator = null;

        /*
         * XXX: This is a hack to avoid API changes at the end of 3.2,
         */
        if ("org.eclipse.jface.text.source.projection.ProjectionAnnotationHover".equals(annotationHover.getClass().getName())) //$NON-NLS-1$
          controlCreator = new IInformationControlCreator() {
            public IInformationControl createInformationControl(
                final Shell shell) {
              final int shellStyle = SWT.RESIZE | SWT.TOOL
                  | getOrientation();
              final int style = SWT.V_SCROLL | SWT.H_SCROLL;
              return new PHPSourceViewerInformationControl(shell,
                  shellStyle, style);
            }
          };
        else if (annotationHover instanceof IInformationProviderExtension2)
          controlCreator = ((IInformationProviderExtension2) annotationHover)
              .getInformationPresenterControlCreator();
        else if (annotationHover instanceof IAnnotationHoverExtension)
          controlCreator = ((IAnnotationHoverExtension) annotationHover)
              .getHoverControlCreator();

        final IInformationProvider informationProvider = new InformationProvider(
            new Region(offset, 0), hoverInfo, controlCreator);

        fInformationPresenter.setOffset(offset);
        fInformationPresenter
            .setAnchor(AbstractInformationControlManager.ANCHOR_RIGHT);
        fInformationPresenter.setMargins(4, 0); // AnnotationBarHoverManager
        // sets (5,0), minus
        // SourceViewer.GAP_SIZE_1
        fInformationPresenter.setInformationProvider(
            informationProvider, contentType);
        fInformationPresenter.showInformation();

        return true;

      } catch (final BadLocationException e) {
        return false;
      }
    }

    /**
     * Tries to make a text hover focusable (or "sticky").
     *
     * @param sourceViewer
     *            the source viewer to display the hover over
     * @param textHover
     *            the hover to make focusable
     * @return <code>true</code> if successful, <code>false</code> otherwise
     * @since 3.2
     */
    private boolean makeTextHoverFocusable(
        final ISourceViewer sourceViewer, final ITextHover textHover) {
      final Point hoverEventLocation = ((ITextViewerExtension2) sourceViewer)
          .getHoverEventLocation();
      final int offset = computeOffsetAtLocation(sourceViewer,
          hoverEventLocation.x, hoverEventLocation.y);
      if (offset == -1)
        return false;

      try {
        final IRegion hoverRegion = textHover.getHoverRegion(
            sourceViewer, offset);
        if (hoverRegion == null)
          return false;

        String hoverInfo = textHover.getHoverInfo(sourceViewer,
            hoverRegion);

        if (textHover instanceof IPHPTextHover) {
          final IHoverMessageDecorator decorator = ((IPHPTextHover) textHover)
              .getMessageDecorator();
          if (decorator != null) {
            final String decoratedMessage = decorator
                .getDecoratedMessage(hoverInfo);
            if (decoratedMessage != null
                && decoratedMessage.length() > 0)
              hoverInfo = decoratedMessage;
          }
        }

        IInformationControlCreator controlCreator = null;
        if (textHover instanceof IInformationProviderExtension2)
          controlCreator = ((IInformationProviderExtension2) textHover)
              .getInformationPresenterControlCreator();

        final IInformationProvider informationProvider = new InformationProvider(
            hoverRegion, hoverInfo, controlCreator);

        fInformationPresenter.setOffset(offset);
        fInformationPresenter
            .setAnchor(AbstractInformationControlManager.ANCHOR_BOTTOM);
        fInformationPresenter.setMargins(6, 6); // default values from
        // AbstractInformationControlManager
        final String contentType = TextUtilities.getContentType(
            sourceViewer.getDocument(), PHPPartitionTypes.PHP_DOC,
            offset, true);
        fInformationPresenter.setInformationProvider(
            informationProvider, contentType);
        fInformationPresenter.showInformation();

        return true;

      } catch (final BadLocationException e) {
        return false;
      }
    }

    /*
     * @see org.eclipse.jface.action.IAction#run()
     */
    @Override
    public void run() {

      final ISourceViewer sourceViewer = getSourceViewer();
      if (sourceViewer == null) {
        fTextOperationAction.run();
        return;
      }

      if (sourceViewer instanceof ITextViewerExtension4) {
        final ITextViewerExtension4 extension4 = (ITextViewerExtension4) sourceViewer;
        if (extension4.moveFocusToWidgetToken())
          return;
      }

      if (sourceViewer instanceof ITextViewerExtension2) {
        // does a text hover exist?
        final ITextHover textHover = ((ITextViewerExtension2) sourceViewer)
            .getCurrentTextHover();
        if (textHover != null
            && makeTextHoverFocusable(sourceViewer, textHover))
          return;
      }

      if (sourceViewer instanceof ISourceViewerExtension3) {
        // does an annotation hover exist?
        final IAnnotationHover annotationHover = ((ISourceViewerExtension3) sourceViewer)
            .getCurrentAnnotationHover();
        if (annotationHover != null
            && makeAnnotationHoverFocusable(sourceViewer,
                annotationHover))
          return;
      }

      // otherwise, just run the action
      fTextOperationAction.run();
    }
  }

  /**
   * Internal activation listener.
   *
   * @since 3.0
   */
  private class ActivationListener implements IWindowListener {

    /*
     * @seeorg.eclipse.ui.IWindowListener#windowActivated(org.eclipse.ui.
     * IWorkbenchWindow)
     *
     * @since 3.1
     */
    public void windowActivated(IWorkbenchWindow window) {
      if (window == getEditorSite().getWorkbenchWindow()
          && fMarkOccurrenceAnnotations && isActivePart()) {
        fForcedMarkOccurrencesSelection = getSelectionProvider()
            .getSelection();
        IModelElement sourceModule = getModelElement();
        if (sourceModule != null
            && sourceModule.getElementType() == IModelElement.SOURCE_MODULE) {
          updateOccurrenceAnnotations(
              (ITextSelection) fForcedMarkOccurrencesSelection,
              sourceModule);
        }
      }
    }

    /*
     * @seeorg.eclipse.ui.IWindowListener#windowDeactivated(org.eclipse.ui.
     * IWorkbenchWindow)
     *
     * @since 3.1
     */
    public void windowDeactivated(IWorkbenchWindow window) {
      if (window == getEditorSite().getWorkbenchWindow()
          && fMarkOccurrenceAnnotations && isActivePart()) {
        removeOccurrenceAnnotations();
      }
    }

    /*
     * @seeorg.eclipse.ui.IWindowListener#windowClosed(org.eclipse.ui.
     * IWorkbenchWindow)
     *
     * @since 3.1
     */
    public void windowClosed(IWorkbenchWindow window) {
    }

    /*
     * @seeorg.eclipse.ui.IWindowListener#windowOpened(org.eclipse.ui.
     * IWorkbenchWindow)
     *
     * @since 3.1
     */
    public void windowOpened(IWorkbenchWindow window) {
    }
  }

  /**
   * Cancels the occurrences finder job upon document changes.
   *
   * @since 3.0
   */
  class OccurrencesFinderJobCanceler implements IDocumentListener,
      ITextInputListener {

    public void install() {
      ISourceViewer sourceViewer = getSourceViewer();
      if (sourceViewer == null)
        return;

      StyledText text = sourceViewer.getTextWidget();
      if (text == null || text.isDisposed())
        return;

      sourceViewer.addTextInputListener(this);

      IDocument document = sourceViewer.getDocument();
      if (document != null)
        document.addDocumentListener(this);
    }

    public void uninstall() {
      ISourceViewer sourceViewer = getSourceViewer();
      if (sourceViewer != null)
        sourceViewer.removeTextInputListener(this);

      IDocumentProvider documentProvider = getDocumentProvider();
      if (documentProvider != null) {
        IDocument document = documentProvider
            .getDocument(getEditorInput());
        if (document != null)
          document.removeDocumentListener(this);
      }
    }

    /*
     * @see
     * org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged
     * (org.eclipse.jface.text.DocumentEvent)
     */
    public void documentAboutToBeChanged(DocumentEvent event) {
      if (fOccurrencesFinderJob != null)
        fOccurrencesFinderJob.doCancel();
    }

    /*
     * @see
     * org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse
     * .jface.text.DocumentEvent)
     */
    public void documentChanged(DocumentEvent event) {
    }

    /*
     * @see
     * org.eclipse.jface.text.ITextInputListener#inputDocumentAboutToBeChanged
     * (org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
     */
    public void inputDocumentAboutToBeChanged(IDocument oldInput,
        IDocument newInput) {
      if (oldInput == null)
        return;

      oldInput.removeDocumentListener(this);
    }

    /*
     * @see
     * org.eclipse.jface.text.ITextInputListener#inputDocumentChanged(org
     * .eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
     */
    public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
      if (newInput == null)
        return;
      newInput.addDocumentListener(this);
    }
  }

  /**
   * Finds and marks occurrence annotations.
   *
   * @since 3.0
   */
  class OccurrencesFinderJob extends Job {

    private final IDocument fDocument;
    private final ISelection fSelection;
    private final ISelectionValidator fPostSelectionValidator;
    private boolean fCanceled = false;
    private final OccurrenceLocation[] fLocations;

    public OccurrencesFinderJob(IDocument document,
        OccurrenceLocation[] locations, ISelection selection) {
      super("mark occrrences job name"); // TODO should externals //$NON-NLS-1$
      fDocument = document;
      fSelection = selection;
      fLocations = locations;

      if (getSelectionProvider() instanceof ISelectionValidator)
        fPostSelectionValidator = (ISelectionValidator) getSelectionProvider();
      else
        fPostSelectionValidator = null;
    }

    // cannot use cancel() because it is declared final
    void doCancel() {
      fCanceled = true;
      cancel();
    }

    private boolean isCanceled(IProgressMonitor progressMonitor) {
      return fCanceled
          || progressMonitor.isCanceled()
          || fPostSelectionValidator != null
          && !(fPostSelectionValidator.isValid(fSelection) || fForcedMarkOccurrencesSelection == fSelection)
          || LinkedModeModel.hasInstalledModel(fDocument);
    }

    /*
     * @see Job#run(org.eclipse.core.runtime.IProgressMonitor)
     */
    public IStatus run(IProgressMonitor progressMonitor) {
      if (isCanceled(progressMonitor))
        return Status.CANCEL_STATUS;

      ITextViewer textViewer = getTextViewer();
      if (textViewer == null)
        return Status.CANCEL_STATUS;

      IDocument document = textViewer.getDocument();
      if (document == null)
        return Status.CANCEL_STATUS;

      IDocumentProvider documentProvider = getDocumentProvider();
      if (documentProvider == null)
        return Status.CANCEL_STATUS;

      IAnnotationModel annotationModel = documentProvider
          .getAnnotationModel(getEditorInput());
      if (annotationModel == null)
        return Status.CANCEL_STATUS;

      // Add occurrence annotations
      int length = fLocations.length;
      Map<Annotation, Position> annotationMap = new HashMap<Annotation, Position>(
          length);
      for (int i = 0; i < length; i++) {

        if (isCanceled(progressMonitor))
          return Status.CANCEL_STATUS;

        OccurrenceLocation location = fLocations[i];
        Position position = new Position(location.getOffset(),
            location.getLength());

        String description = location.getDescription();
        String annotationType = (location.getFlags() == IOccurrencesFinder.F_WRITE_OCCURRENCE) ? "org.eclipse.php.ui.occurrences.write" : "org.eclipse.php.ui.occurrences"; //$NON-NLS-1$ //$NON-NLS-2$

        // create an annotation to mark the occurrence
        ReconcileAnnotationKey reconcileAnnotationKey = new ReconcileAnnotationKey(
            null, PHPPartitionTypes.PHP_DEFAULT,
            ReconcileAnnotationKey.TOTAL);
        TemporaryAnnotation annotation = new TemporaryAnnotation(
            position, annotationType, description,
            reconcileAnnotationKey) {

          // Supply an occurrence image to display in the vertical
          // ruler
          @Override
          public void paint(GC gc, Canvas canvas, Rectangle r) {
            ImageUtilities.drawImage(
                PHPUiPlugin.getImageDescriptorRegistry().get(
                    PHPPluginImages.DESC_OBJS_OCCURRENCES),
                gc, canvas, r, SWT.CENTER, SWT.TOP);
          }

        };
        annotationMap.put(annotation, position);
      }

      if (isCanceled(progressMonitor))
        return Status.CANCEL_STATUS;

      synchronized (getLockObject(annotationModel)) {
        if (annotationModel instanceof IAnnotationModelExtension) {
          ((IAnnotationModelExtension) annotationModel)
              .replaceAnnotations(fOccurrenceAnnotations,
                  annotationMap);
        } else {
          removeOccurrenceAnnotations();
          for (Map.Entry<Annotation, Position> entry : annotationMap
              .entrySet()) {
            annotationModel.addAnnotation(entry.getKey(),
                entry.getValue());
          }
        }
        fOccurrenceAnnotations = annotationMap.keySet().toArray(
            new Annotation[annotationMap.keySet().size()]);
      }

      return Status.OK_STATUS;
    }
  }

  /**
   * Information provider used to present focusable information shells.
   *
   * @since 3.2
   */
  private static final class InformationProvider implements
      IInformationProvider, IInformationProviderExtension,
      IInformationProviderExtension2 {

    private final IInformationControlCreator fControlCreator;
    private final Object fHoverInfo;
    private final IRegion fHoverRegion;

    InformationProvider(final IRegion hoverRegion, final Object hoverInfo,
        final IInformationControlCreator controlCreator) {
      fHoverRegion = hoverRegion;
      fHoverInfo = hoverInfo;
      fControlCreator = controlCreator;
    }

    /*
     * @see
     * org.eclipse.jface.text.information.IInformationProvider#getInformation
     * (org.eclipse.jface.text.ITextViewer, org.eclipse.jface.text.IRegion)
     */
    public String getInformation(final ITextViewer textViewer,
        final IRegion subject) {
      return fHoverInfo.toString();
    }

    /*
     * @see
     * org.eclipse.jface.text.information.IInformationProviderExtension#
     * getInformation2(org.eclipse.jface.text.ITextViewer,
     * org.eclipse.jface.text.IRegion)
     *
     * @since 3.2
     */
    public Object getInformation2(final ITextViewer textViewer,
        final IRegion subject) {
      return fHoverInfo;
    }

    /*
     * @see
     * org.eclipse.jface.text.information.IInformationProviderExtension2
     * #getInformationPresenterControlCreator()
     */
    public IInformationControlCreator getInformationPresenterControlCreator() {
      return fControlCreator;
    }

    /*
     * @see
     * org.eclipse.jface.text.information.IInformationProvider#getSubject
     * (org.eclipse.jface.text.ITextViewer, int)
     */
    public IRegion getSubject(final ITextViewer textViewer,
        final int invocationOffset) {
      return fHoverRegion;
    }
  }

  private void initPHPVersionsListener() {
    if (fPhpVersionListener != null) {
      return;
    }

    fPhpVersionListener = new IPreferencesPropagatorListener() {
      public void preferencesEventOccured(PreferencesPropagatorEvent event) {
        try {
          // get the structured document and go over its regions
          // in case of PhpScriptRegion reparse the region text
          IDocument doc = getDocumentProvider().getDocument(
              getEditorInput());
          if (doc instanceof IStructuredDocument) {
            IStructuredDocumentRegion[] sdRegions = ((IStructuredDocument) doc)
                .getStructuredDocumentRegions();
            for (IStructuredDocumentRegion element : sdRegions) {
              Iterator regionsIt = element.getRegions()
                  .iterator();
              reparseRegion(doc, regionsIt,
                  element.getStartOffset());
            }
            PHPStructuredTextViewer textViewer = (PHPStructuredTextViewer) getTextViewer();
            textViewer.reconcile();
          }
        } catch (BadLocationException e) {
        }
      }

      public IProject getProject() {
        IScriptProject scriptProject = PHPStructuredEditor.this
            .getProject();
        if (scriptProject != null) {
          return scriptProject.getProject();
        }
        return null;
      }
    };

    PhpVersionChangedHandler.getInstance().addPhpVersionChangedListener(
        fPhpVersionListener);

    fPreferencesListener = new IPreferenceChangeListener() {

      public void preferenceChange(PreferenceChangeEvent event) {
        String property = event.getKey();
        if (PHPCoreConstants.CODEASSIST_AUTOACTIVATION.equals(property)
            || PHPCoreConstants.CODEASSIST_AUTOACTIVATION_DELAY
                .equals(property)
            || PHPCoreConstants.CODEASSIST_AUTOINSERT
                .equals(property)) {
          ISourceViewer sourceViewer = getSourceViewer();
          if (sourceViewer != null) {
            PHPStructuredTextViewerConfiguration configuration = (PHPStructuredTextViewerConfiguration) getSourceViewerConfiguration();
            if (configuration != null) {
              StructuredContentAssistant contentAssistant = (StructuredContentAssistant) configuration
                  .getPHPContentAssistant(sourceViewer);

              IPreferencesService preferencesService = Platform
                  .getPreferencesService();
              contentAssistant
                  .enableAutoActivation(preferencesService
                      .getBoolean(
                          PHPCorePlugin.ID,
                          PHPCoreConstants.CODEASSIST_AUTOACTIVATION,
                          false, null));
              contentAssistant
                  .setAutoActivationDelay(preferencesService
                      .getInt(PHPCorePlugin.ID,
                          PHPCoreConstants.CODEASSIST_AUTOACTIVATION_DELAY,
                          0, null));
              contentAssistant
                  .enableAutoInsert(preferencesService
                      .getBoolean(
                          PHPCorePlugin.ID,
                          PHPCoreConstants.CODEASSIST_AUTOINSERT,
                          false, null));
            }
          }
        }
      }
    };

    InstanceScope.INSTANCE.getNode(PHPCorePlugin.ID)
        .addPreferenceChangeListener(fPreferencesListener);
  }

  /**
   * iterate over regions in case of PhpScriptRegion reparse the region. in
   * case of region contaioner iterate over the container regions.
   *
   * @param doc
   *            structured document
   * @param regionsIt
   *            regions iterator
   * @param offset
   *            the container region start offset
   * @throws BadLocationException
   */
  private void reparseRegion(IDocument doc, Iterator regionsIt, int offset)
      throws BadLocationException {
    while (regionsIt.hasNext()) {
      ITextRegion region = (ITextRegion) regionsIt.next();
      if (region instanceof ITextRegionContainer) {
        reparseRegion(doc, ((ITextRegionContainer) region).getRegions()
            .iterator(), offset + region.getStart());
      }
      if (region instanceof IPhpScriptRegion) {
        final IPhpScriptRegion phpRegion = (IPhpScriptRegion) region;
        try {
          phpRegion.completeReparse(doc, offset + region.getStart(),
              region.getLength());
        } catch (Error e) {
          // catch Error from PhpLexer.zzScanError
          // without doing this,the editor will behavior unnormal
          PHPUiPlugin.log(e);
        }

      }
    }
  }

  /** Cursor dependent actions. */
  private final List<String> fCursorActions = new ArrayList<String>(5);

  /** The information presenter. */
  protected InformationPresenter fInformationPresenter;

  private ISourceReconcilingListener fEditorReconcilingListener = new EditorReconcilingListener();

  public PHPStructuredEditor() {
    /**
     * Bug fix: #158170 Set WST's folding support enablement according to
     * PHP editor folding support status. Must be removed, when WTP releases
     * folding support
     */
    boolean foldingEnabled = PHPUiPlugin.getDefault().getPreferenceStore()
        .getBoolean(PreferenceConstants.EDITOR_FOLDING_ENABLED);
    SSEUIPlugin
        .getDefault()
        .getPreferenceStore()
        .setValue(AbstractStructuredFoldingStrategy.FOLDING_ENABLED,
            foldingEnabled);
    setDocumentProvider(DLTKUIPlugin.getDocumentProvider());
  }

  // added by zhaozw,or there will be a exception for files in the phar
  protected void setDocumentProvider(IEditorInput input) {
    setDocumentProvider(DLTKUIPlugin.getDocumentProvider());
  }

  @Override
  public void init(IEditorSite site, IEditorInput input)
      throws PartInitException {
    if (input instanceof IFileEditorInput) {
      // This is the existing workspace file
      final IFileEditorInput fileInput = (IFileEditorInput) input;
      input = new RefactorableFileEditorInput(fileInput.getFile());
    }
    super.init(site, input);
  }

  @Override
  protected void initializeEditor() {
    super.initializeEditor();

    IPreferenceStore store = createCombinedPreferenceStore();
    setPreferenceStore(store);

    fMarkOccurrenceAnnotations = store
        .getBoolean(PreferenceConstants.EDITOR_MARK_OCCURRENCES);
    fStickyOccurrenceAnnotations = store
        .getBoolean(PreferenceConstants.EDITOR_STICKY_OCCURRENCES);
    fMarkTypeOccurrences = store
        .getBoolean(PreferenceConstants.EDITOR_MARK_TYPE_OCCURRENCES);
    fMarkMethodOccurrences = store
        .getBoolean(PreferenceConstants.EDITOR_MARK_METHOD_OCCURRENCES);
    fMarkFunctionOccurrences = store
        .getBoolean(PreferenceConstants.EDITOR_MARK_FUNCTION_OCCURRENCES);
    fMarkConstantOccurrences = store
        .getBoolean(PreferenceConstants.EDITOR_MARK_CONSTANT_OCCURRENCES);
    fMarkGlobalVariableOccurrences = store
        .getBoolean(PreferenceConstants.EDITOR_MARK_GLOBAL_VARIABLE_OCCURRENCES);
    fMarkLocalVariableOccurrences = store
        .getBoolean(PreferenceConstants.EDITOR_MARK_LOCAL_VARIABLE_OCCURRENCES);
    fMarkImplementors = store
        .getBoolean(PreferenceConstants.EDITOR_MARK_IMPLEMENTORS);
    fMarkMethodExitPoints = store
        .getBoolean(PreferenceConstants.EDITOR_MARK_METHOD_EXIT_POINTS);
    fMarkBreakContinueTargets = store
        .getBoolean(PreferenceConstants.EDITOR_MARK_BREAK_CONTINUE_TARGETS);
  }

  /**
   * Create a preference store that combines the source editor preferences
   * with the base editor's preferences.
   *
   * @return IPreferenceStore
   */
  private IPreferenceStore createCombinedPreferenceStore() {
    IPreferenceStore sseEditorPrefs = SSEUIPlugin.getDefault()
        .getPreferenceStore();
    IPreferenceStore baseEditorPrefs = EditorsUI.getPreferenceStore();
    IPreferenceStore phpEditorPrefs = PHPUiPlugin.getDefault()
        .getPreferenceStore();
    return new ChainedPreferenceStore(new IPreferenceStore[] {
        sseEditorPrefs, baseEditorPrefs, phpEditorPrefs });
  }

  @Override
  public void dispose() {
    if (fContextMenuGroup != null) {
      fContextMenuGroup.dispose();
      fContextMenuGroup = null;
    }
    if (fActionGroups != null) {
      fActionGroups.dispose();
      fActionGroups = null;
    }
    if (fInformationPresenter != null) {
      fInformationPresenter.dispose();
      fInformationPresenter = null;
    }
    if (fPhpVersionListener != null) {
      PhpVersionChangedHandler.getInstance()
          .removePhpVersionChangedListener(fPhpVersionListener);
      fPhpVersionListener = null;
    }
    if (fPreferencesListener != null) {
      InstanceScope.INSTANCE.getNode(PHPCorePlugin.ID)
          .removePreferenceChangeListener(fPreferencesListener);
    }

    if (fActivationListener != null) {
      PlatformUI.getWorkbench().removeWindowListener(fActivationListener);
      fActivationListener = null;
    }
    // some things in the configuration need to clean
    // up after themselves
    if (fPHPOutlinePage != null) {
      if (fPHPOutlinePage instanceof ConfigurableContentOutlinePage
          && fPHPOutlinePageListener != null) {
        ((ConfigurableContentOutlinePage) fPHPOutlinePage)
            .removeDoubleClickListener(fPHPOutlinePageListener);
      }
      if (fPHPOutlinePageListener != null) {
        fPHPOutlinePageListener.uninstall(fPHPOutlinePage);
      }
    }
    uninstallOccurrencesFinder();
    uninstallOverrideIndicator();

    // remove the listener we added in createAction method

    if (getSelectionProvider() instanceof IPostSelectionProvider) {
      IPostSelectionProvider psp = (IPostSelectionProvider) getSelectionProvider();
      try {
        IAction action = getAction(IPHPEditorActionDefinitionIds.OPEN_TYPE_HIERARCHY);
        if (action instanceof ISelectionChangedListener) {
          psp.removePostSelectionChangedListener((ISelectionChangedListener) action);
        }
        action = getAction(IPHPEditorActionDefinitionIds.OPEN_CALL_HIERARCHY);
        if (action instanceof ISelectionChangedListener) {
          psp.removePostSelectionChangedListener((ISelectionChangedListener) action);
        }
      } catch (NullPointerException ex) {
        // NPE thrown by getAction in case when class has already been
        // disposed but dispose is called again.
      }
    }

    if (fProjectionModelUpdater != null) {
      fProjectionModelUpdater.uninstall();
      fProjectionModelUpdater = null;
    }
    super.dispose();
  }

  /*
   * @see AbstractTextEditor#editorContextMenuAboutToShow(IMenuManager)
   */
  @Override
  public void editorContextMenuAboutToShow(IMenuManager menu) {
    super.editorContextMenuAboutToShow(menu);

    if (fContextMenuGroup != null) {
      ActionContext context = new ActionContext(getSelectionProvider()
          .getSelection());
      fContextMenuGroup.setContext(context);
      fContextMenuGroup.fillContextMenu(menu);
      fContextMenuGroup.setContext(null);
    }
  }

  @Override
  protected void addContextMenuActions(final IMenuManager menu) {
    super.addContextMenuActions(menu);

    if (getSourceViewer().isEditable()) {
      final String openGroup = "group.open"; //$NON-NLS-1$
      menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT,
          new Separator(openGroup));
      IAction action = getAction(PHPStructuredEditor.ORG_ECLIPSE_PHP_UI_ACTIONS_OPEN_FUNCTIONS_MANUAL_ACTION);
      if (action != null)
        menu.appendToGroup(openGroup, action);
      action = getAction(IPHPEditorActionDefinitionIds.OPEN_DECLARATION);
      if (action != null)
        menu.appendToGroup(openGroup, action);
      action = getAction(IScriptEditorActionDefinitionIds.SHOW_OUTLINE);
      if (action != null)
        menu.appendToGroup(openGroup, action);
      action = getAction(IPHPEditorActionDefinitionIds.OPEN_TYPE_HIERARCHY);
      if (action != null)
        menu.appendToGroup(openGroup, action);
      action = getAction(IScriptEditorActionDefinitionIds.OPEN_HIERARCHY);
      if (action != null)
        menu.appendToGroup(openGroup, action);
      action = getAction(IPHPEditorActionDefinitionIds.OPEN_CALL_HIERARCHY);
      if (action != null)
        menu.appendToGroup(openGroup, action);
    }
  }

  @Override
  protected void createNavigationActions() {
    super.createNavigationActions();
    final StyledText textWidget = getSourceViewer().getTextWidget();

    IAction action = new SmartLineStartAction(textWidget, false);
    action.setActionDefinitionId(ITextEditorActionDefinitionIds.LINE_START);
    setAction(ITextEditorActionDefinitionIds.LINE_START, action);

    action = new SmartLineStartAction(textWidget, true);
    action.setActionDefinitionId(ITextEditorActionDefinitionIds.SELECT_LINE_START);
    setAction(ITextEditorActionDefinitionIds.SELECT_LINE_START, action);

    action = new SmartLineEndAction(textWidget, false);
    action.setActionDefinitionId(ITextEditorActionDefinitionIds.LINE_END);
    setAction(ITextEditorActionDefinitionIds.LINE_END, action);

    action = new SmartLineEndAction(textWidget, true);
    action.setActionDefinitionId(ITextEditorActionDefinitionIds.SELECT_LINE_END);
    setAction(ITextEditorActionDefinitionIds.SELECT_LINE_END, action);

    action = new NavigatePreviousSubWordAction();
    action.setActionDefinitionId(ITextEditorActionDefinitionIds.WORD_PREVIOUS);
    setAction(ITextEditorActionDefinitionIds.WORD_PREVIOUS, action);
    textWidget.setKeyBinding(SWT.CTRL | SWT.ARROW_LEFT, SWT.NULL);

    action = new NavigateNextSubWordAction();
    action.setActionDefinitionId(ITextEditorActionDefinitionIds.WORD_NEXT);
    setAction(ITextEditorActionDefinitionIds.WORD_NEXT, action);
    textWidget.setKeyBinding(SWT.CTRL | SWT.ARROW_RIGHT, SWT.NULL);

    action = new SelectPreviousSubWordAction();
    action.setActionDefinitionId(ITextEditorActionDefinitionIds.SELECT_WORD_PREVIOUS);
    setAction(ITextEditorActionDefinitionIds.SELECT_WORD_PREVIOUS, action);
    textWidget.setKeyBinding(SWT.CTRL | SWT.SHIFT | SWT.ARROW_LEFT,
        SWT.NULL);

    action = new SelectNextSubWordAction();
    action.setActionDefinitionId(ITextEditorActionDefinitionIds.SELECT_WORD_NEXT);
    setAction(ITextEditorActionDefinitionIds.SELECT_WORD_NEXT, action);
    textWidget.setKeyBinding(SWT.CTRL | SWT.SHIFT | SWT.ARROW_RIGHT,
        SWT.NULL);
  }

  /**
   * This action implements smart home. (Taken from JDT implementation)
   * Instead of going to the start of a line it does the following: - if smart
   * home/end is enabled and the caret is after the line's first
   * non-whitespace then the caret is moved directly before it, taking PHPDoc
   * and multi-line comments into account. - if the caret is before the line's
   * first non-whitespace the caret is moved to the beginning of the line - if
   * the caret is at the beginning of the line see first case.
   */
  protected class SmartLineStartAction extends LineStartAction {

    private final boolean fDoSelect;

    /**
     * Creates a new smart line start action
     *
     * @param textWidget
     *            the styled text widget
     * @param doSelect
     *            a boolean flag which tells if the text up to the beginning
     *            of the line should be selected
     */
    public SmartLineStartAction(final StyledText textWidget,
        final boolean doSelect) {
      super(textWidget, doSelect);
      fDoSelect = doSelect;
    }

    /*
     * @seeorg.eclipse.ui.texteditor.AbstractTextEditor.LineStartAction#
     * getLineStartPosition(java.lang.String, int, java.lang.String)
     */
    @Override
    protected int getLineStartPosition(final IDocument document,
        final String line, final int length, final int offset) {

      String type = IDocument.DEFAULT_CONTENT_TYPE;
      try {
        type = TextUtilities.getContentType(document,
            PHPPartitionTypes.PHP_DEFAULT, offset, true);
      } catch (BadLocationException exception) {
        // Should not happen
      }

      int index = super.getLineStartPosition(document, line, length,
          offset);
      if (type.equals(PHPPartitionTypes.PHP_DOC)
          || type.equals(PHPPartitionTypes.PHP_MULTI_LINE_COMMENT)) {
        if (index < length - 1 && line.charAt(index) == '*'
            && line.charAt(index + 1) != '/') {
          do {
            ++index;
          } while (index < length
              && Character.isWhitespace(line.charAt(index)));
        }
      } else {
        if (index < length - 1 && line.charAt(index) == '/'
            && line.charAt(index + 1) == '/') {
          index++;
          do {
            ++index;
          } while (index < length
              && Character.isWhitespace(line.charAt(index)));
        }
      }
      return index;
    }

    private IPreferenceStore getPreferenceStore() {
      return PHPUiPlugin.getDefault().getPreferenceStore();
    }

    /*
     * @see org.eclipse.jface.action.IAction#run()
     */
    @Override
    public void run() {
      boolean isSmartHomeEndEnabled = true;
      IPreferenceStore store = getPreferenceStore();
      if (store != null) {
        isSmartHomeEndEnabled = store
            .getBoolean(PreferenceConstants.USE_SMART_HOME_END);
      }

      ISourceViewer fSourceViewer = getSourceViewer();
      StyledText st = fSourceViewer.getTextWidget();
      if (st == null || st.isDisposed())
        return;

      int caretOffset = st.getCaretOffset();
      int lineNumber = st.getLineAtOffset(caretOffset);
      int lineOffset = st.getOffsetAtLine(lineNumber);

      int lineLength;
      int caretOffsetInDocument;
      final IDocument document = fSourceViewer.getDocument();

      try {
        caretOffsetInDocument = widgetOffset2ModelOffset(fSourceViewer,
            caretOffset);
        lineLength = document.getLineInformationOfOffset(
            caretOffsetInDocument).getLength();
      } catch (BadLocationException ex) {
        return;
      }

      String line = ""; //$NON-NLS-1$
      if (lineLength > 0) {
        int end = lineOffset + lineLength - 1;
        end = Math.min(end, st.getCharCount() - 1);
        line = st.getText(lineOffset, end);
      }

      // Compute the line start offset
      int index = getLineStartPosition(document, line, lineLength,
          caretOffsetInDocument);

      // Remember current selection
      Point oldSelection = st.getSelection();

      // Compute new caret position
      int newCaretOffset = -1;
      if (isSmartHomeEndEnabled) {

        if (caretOffset - lineOffset == index)
          // to beginning of line
          newCaretOffset = lineOffset;
        else
          // to beginning of text
          newCaretOffset = lineOffset + index;

      } else {

        if (caretOffset > lineOffset)
          // to beginning of line
          newCaretOffset = lineOffset;
      }

      if (newCaretOffset == -1)
        newCaretOffset = caretOffset;
      else
        st.setCaretOffset(newCaretOffset);

      if (fDoSelect) {
        if (caretOffset < oldSelection.y)
          st.setSelection(oldSelection.y, newCaretOffset);
        else
          st.setSelection(oldSelection.x, newCaretOffset);
      } else
        st.setSelection(newCaretOffset);

      fireSelectionChanged(oldSelection);
    }
  }

  /**
   * This action implements smart end. (Taken from
   * org.eclipse.ui.texteditor.AbstractTextEditor.LineEndAction) Instead of
   * going to the end of a line it does the following: - if smart home/end is
   * enabled and the caret is before the line's last non-whitespace and then
   * the caret is moved directly after it - if the caret is after last
   * non-whitespace the caret is moved at the end of the line - if the caret
   * is at the end of the line the caret is moved directly after the line's
   * last non-whitespace character
   */
  protected class SmartLineEndAction extends TextNavigationAction {

    /**
     * boolean flag which tells if the text up to the line end should be
     * selected.
     */
    private final boolean fDoSelect;

    /**
     * Create a new line end action.
     *
     * @param textWidget
     *            the styled text widget
     * @param doSelect
     *            a boolean flag which tells if the text up to the line end
     *            should be selected
     */
    public SmartLineEndAction(StyledText textWidget, boolean doSelect) {
      super(textWidget, ST.LINE_END);
      fDoSelect = doSelect;
    }

    private IPreferenceStore getPreferenceStore() {
      return PHPUiPlugin.getDefault().getPreferenceStore();
    }

    /*
     * @see org.eclipse.jface.action.IAction#run()
     */
    @Override
    public void run() {
      boolean isSmartHomeEndEnabled = true;
      IPreferenceStore store = getPreferenceStore();
      if (store != null) {
        isSmartHomeEndEnabled = store
            .getBoolean(PreferenceConstants.USE_SMART_HOME_END);
      }

      ISourceViewer fSourceViewer = getSourceViewer();
      StyledText st = fSourceViewer.getTextWidget();
      if (st == null || st.isDisposed())
        return;
      int caretOffset = st.getCaretOffset();
      int lineNumber = st.getLineAtOffset(caretOffset);
      int lineOffset = st.getOffsetAtLine(lineNumber);

      int lineLength;
      try {
        int caretOffsetInDocument = widgetOffset2ModelOffset(
            fSourceViewer, caretOffset);
        lineLength = fSourceViewer.getDocument()
            .getLineInformationOfOffset(caretOffsetInDocument)
            .getLength();
      } catch (BadLocationException ex) {
        return;
      }
      int lineEndOffset = lineOffset + lineLength;

      int delta = lineEndOffset - st.getCharCount();
      if (delta > 0) {
        lineEndOffset -= delta;
        lineLength -= delta;
      }

      String line = ""; //$NON-NLS-1$
      if (lineLength > 0)
        line = st.getText(lineOffset, lineEndOffset - 1);
      int i = lineLength - 1;
      while (i > -1 && Character.isWhitespace(line.charAt(i))) {
        i--;
      }
      i++;

      // Remember current selection
      Point oldSelection = st.getSelection();

      // Compute new caret position
      int newCaretOffset = -1;

      if (isSmartHomeEndEnabled) {

        if (caretOffset - lineOffset == i)
          // to end of line
          newCaretOffset = lineEndOffset;
        else
          // to end of text
          newCaretOffset = lineOffset + i;

      } else {

        if (caretOffset < lineEndOffset)
          // to end of line
          newCaretOffset = lineEndOffset;

      }

      if (newCaretOffset == -1)
        newCaretOffset = caretOffset;

      int offsetInLine = newCaretOffset - lineOffset;
      int lineFullLength = st.getLine(lineNumber).length();
      while (offsetInLine > lineFullLength) {
        --newCaretOffset;
        --offsetInLine;
      }

      st.setCaretOffset(newCaretOffset);

      if (fDoSelect) {
        if (caretOffset < oldSelection.y)
          st.setSelection(oldSelection.y, newCaretOffset);
        else
          st.setSelection(oldSelection.x, newCaretOffset);
      } else
        st.setSelection(newCaretOffset);

      fireSelectionChanged(oldSelection);
    }
  }

  /**
   * Text navigation action to navigate to the next sub-word.
   */
  protected abstract class NextSubWordAction extends TextNavigationAction {

    protected PHPWordIterator fIterator = new PHPWordIterator();

    /**
     * Creates a new next sub-word action.
     *
     * @param code
     *            Action code for the default operation. Must be an action
     *            code from
     * @see org.eclipse.swt.custom.ST.
     */
    protected NextSubWordAction(int code) {
      super(getSourceViewer().getTextWidget(), code);
    }

    private IPreferenceStore getPreferenceStore() {
      return PHPUiPlugin.getDefault().getPreferenceStore();
    }

    /*
     * @see org.eclipse.jface.action.IAction#run()
     */
    @Override
    public void run() {
      // Check whether the feature is enabled in Preferences
      final IPreferenceStore store = getPreferenceStore();
      if (!store.getBoolean(PreferenceConstants.USE_SUB_WORD_NAVIGATION)) {
        super.run();
        return;
      }

      final ISourceViewer viewer = getSourceViewer();
      final IDocument document = viewer.getDocument();
      fIterator
          .setText((CharacterIterator) new DocumentCharacterIterator(
              document));
      int position = widgetOffset2ModelOffset(viewer, viewer
          .getTextWidget().getCaretOffset());
      if (position == -1) {
        return;
      }

      int next = findNextPosition(position);
      if (next != BreakIterator.DONE) {
        setCaretPosition(next);
        getTextWidget().showSelection();
        fireSelectionChanged();
      }

    }

    /**
     * Finds the next position after the given position.
     *
     * @param position
     *            the current position
     * @return the next position
     */
    protected int findNextPosition(int position) {
      ISourceViewer viewer = getSourceViewer();
      int widget = -1;
      while (position != BreakIterator.DONE && widget == -1) { // TODO:
        // optimize
        position = fIterator.following(position);
        if (position != BreakIterator.DONE) {
          widget = modelOffset2WidgetOffset(viewer, position);
        }
      }
      return position;
    }

    /**
     * Sets the caret position to the sub-word boundary given with
     * <code>position</code>.
     *
     * @param position
     *            Position where the action should move the caret
     */
    protected abstract void setCaretPosition(int position);
  }

  /**
   * Text operation action to select the next sub-word.
   */
  protected class SelectNextSubWordAction extends NextSubWordAction {

    /**
     * Creates a new select next sub-word action.
     */
    public SelectNextSubWordAction() {
      super(ST.SELECT_WORD_NEXT);
    }

    /*
     * @see NextSubWordAction#setCaretPosition(int)
     */
    @Override
    protected void setCaretPosition(final int position) {
      final ISourceViewer viewer = getSourceViewer();

      final StyledText text = viewer.getTextWidget();
      if (text != null && !text.isDisposed()) {

        final Point selection = text.getSelection();
        final int caret = text.getCaretOffset();
        final int offset = modelOffset2WidgetOffset(viewer, position);

        if (caret == selection.x) {
          text.setSelectionRange(selection.y, offset - selection.y);
        } else {
          text.setSelectionRange(selection.x, offset - selection.x);
        }
      }
    }
  }

  /**
   * Text navigation action to navigate to the previous sub-word.
   */
  protected abstract class PreviousSubWordAction extends TextNavigationAction {

    protected PHPWordIterator fIterator = new PHPWordIterator();

    /**
     * Creates a new previous sub-word action.
     *
     * @param code
     *            Action code for the default operation. Must be an action
     *            code from
     * @see org.eclipse.swt.custom.ST.
     */
    protected PreviousSubWordAction(final int code) {
      super(getSourceViewer().getTextWidget(), code);
    }

    private IPreferenceStore getPreferenceStore() {
      return PHPUiPlugin.getDefault().getPreferenceStore();
    }

    /*
     * @see org.eclipse.jface.action.IAction#run()
     */
    @Override
    public void run() {
      // Check whether we are in a java code partition and the preference
      // is enabled
      final IPreferenceStore store = getPreferenceStore();
      if (!store.getBoolean(PreferenceConstants.USE_SUB_WORD_NAVIGATION)) {
        super.run();
        return;
      }

      final ISourceViewer viewer = getSourceViewer();
      final IDocument document = viewer.getDocument();
      fIterator
          .setText((CharacterIterator) new DocumentCharacterIterator(
              document));
      int position = widgetOffset2ModelOffset(viewer, viewer
          .getTextWidget().getCaretOffset());
      if (position == -1) {
        return;
      }

      int previous = findPreviousPosition(position);
      if (previous != BreakIterator.DONE) {
        setCaretPosition(previous);
        getTextWidget().showSelection();
        fireSelectionChanged();
      }

    }

    /**
     * Finds the previous position before the given position.
     *
     * @param position
     *            the current position
     * @return the previous position
     */
    protected int findPreviousPosition(int position) {
      ISourceViewer viewer = getSourceViewer();
      int widget = -1;
      while (position != BreakIterator.DONE && widget == -1) { // TODO:
        // optimize
        position = fIterator.preceding(position);
        if (position != BreakIterator.DONE) {
          widget = modelOffset2WidgetOffset(viewer, position);
        }
      }
      return position;
    }

    /**
     * Sets the caret position to the sub-word boundary given with
     * <code>position</code>.
     *
     * @param position
     *            Position where the action should move the caret
     */
    protected abstract void setCaretPosition(int position);
  }

  /**
   * Text navigation action to navigate to the next sub-word.
   *
   * @since 3.0
   */
  protected class NavigateNextSubWordAction extends NextSubWordAction {

    /**
     * Creates a new navigate next sub-word action.
     */
    public NavigateNextSubWordAction() {
      super(ST.WORD_NEXT);
    }

    /*
     * @see NextSubWordAction#setCaretPosition(int)
     */
    @Override
    protected void setCaretPosition(final int position) {
      getTextWidget().setCaretOffset(
          modelOffset2WidgetOffset(getSourceViewer(), position));
    }
  }

  /**
   * Text operation action to select the previous sub-word.
   *
   * @since 3.0
   */
  protected class SelectPreviousSubWordAction extends PreviousSubWordAction {

    /**
     * Creates a new select previous sub-word action.
     */
    public SelectPreviousSubWordAction() {
      super(ST.SELECT_WORD_PREVIOUS);
    }

    /*
     * @see PreviousSubWordAction#setCaretPosition(int)
     */
    @Override
    protected void setCaretPosition(final int position) {
      final ISourceViewer viewer = getSourceViewer();

      final StyledText text = viewer.getTextWidget();
      if (text != null && !text.isDisposed()) {

        final Point selection = text.getSelection();
        final int caret = text.getCaretOffset();
        final int offset = modelOffset2WidgetOffset(viewer, position);

        if (caret == selection.x) {
          text.setSelectionRange(selection.y, offset - selection.y);
        } else {
          text.setSelectionRange(selection.x, offset - selection.x);
        }
      }
    }
  }

  /**
   * Text navigation action to navigate to the previous sub-word.
   *
   * @since 3.0
   */
  protected class NavigatePreviousSubWordAction extends PreviousSubWordAction {

    /**
     * Creates a new navigate previous sub-word action.
     */
    public NavigatePreviousSubWordAction() {
      super(ST.WORD_PREVIOUS);
    }

    /*
     * @see PreviousSubWordAction#setCaretPosition(int)
     */
    @Override
    protected void setCaretPosition(final int position) {
      getTextWidget().setCaretOffset(
          modelOffset2WidgetOffset(getSourceViewer(), position));
    }
  }

  protected void createActions() {
    super.createActions();

    fSelectionHistory = new SelectionHistory(this);

    Action action = new StructureSelectEnclosingAction(this,
        fSelectionHistory);
    action.setActionDefinitionId(IPHPEditorActionDefinitionIds.SELECT_ENCLOSING);
    setAction(StructureSelectionAction.ENCLOSING, action);

    action = new StructureSelectNextAction(this, fSelectionHistory);
    action.setActionDefinitionId(IPHPEditorActionDefinitionIds.SELECT_NEXT);
    setAction(StructureSelectionAction.NEXT, action);

    action = new StructureSelectPreviousAction(this, fSelectionHistory);
    action.setActionDefinitionId(IPHPEditorActionDefinitionIds.SELECT_PREVIOUS);
    setAction(StructureSelectionAction.PREVIOUS, action);

    StructureSelectHistoryAction historyAction = new StructureSelectHistoryAction(
        this, fSelectionHistory);
    historyAction
        .setActionDefinitionId(IPHPEditorActionDefinitionIds.SELECT_LAST);
    setAction(StructureSelectionAction.HISTORY, historyAction);
    fSelectionHistory.setHistoryAction(historyAction);

    final ResourceBundle resourceBundle = PHPUIMessages.getResourceBundle();

    action = new GotoMatchingBracketAction(this);
    action.setActionDefinitionId(IPHPEditorActionDefinitionIds.GOTO_MATCHING_BRACKET);
    setAction(GotoMatchingBracketAction.GOTO_MATCHING_BRACKET, action);

    action = new OpenFunctionsManualAction(resourceBundle, this);
    action.setActionDefinitionId(IPHPEditorActionDefinitionIds.OPEN_PHP_MANUAL); //$NON-NLS-1$
    setAction(ORG_ECLIPSE_PHP_UI_ACTIONS_OPEN_FUNCTIONS_MANUAL_ACTION,
        action);
    markAsCursorDependentAction(
        ORG_ECLIPSE_PHP_UI_ACTIONS_OPEN_FUNCTIONS_MANUAL_ACTION, true);

    action = new OpenDeclarationAction(resourceBundle, this);
    action.setActionDefinitionId(IPHPEditorActionDefinitionIds.OPEN_DECLARATION); //$NON-NLS-1$
    setAction(IPHPEditorActionDefinitionIds.OPEN_DECLARATION, action);
    markAsCursorDependentAction(
        IPHPEditorActionDefinitionIds.OPEN_DECLARATION, true);

    action = new OpenTypeHierarchyAction(this);
    action.setActionDefinitionId(IPHPEditorActionDefinitionIds.OPEN_TYPE_HIERARCHY); //$NON-NLS-1$
    setAction(IPHPEditorActionDefinitionIds.OPEN_TYPE_HIERARCHY, action);
    markAsCursorDependentAction(
        IPHPEditorActionDefinitionIds.OPEN_TYPE_HIERARCHY, true);
    // add selection changed listener for updating enabled status
    if (getSelectionProvider() instanceof IPostSelectionProvider) {
      IPostSelectionProvider psp = (IPostSelectionProvider) getSelectionProvider();
      psp.addPostSelectionChangedListener((OpenTypeHierarchyAction) action);
    }

    action = new OpenCallHierarchyAction(this);
    action.setActionDefinitionId(IPHPEditorActionDefinitionIds.OPEN_CALL_HIERARCHY); //$NON-NLS-1$
    setAction(IPHPEditorActionDefinitionIds.OPEN_CALL_HIERARCHY, action);
    markAsCursorDependentAction(
        IPHPEditorActionDefinitionIds.OPEN_CALL_HIERARCHY, true);
    // add selection changed listener for updating enabled status
    if (getSelectionProvider() instanceof IPostSelectionProvider) {
      IPostSelectionProvider psp = (IPostSelectionProvider) getSelectionProvider();
      psp.addPostSelectionChangedListener((OpenCallHierarchyAction) action);
    }

    action = new TextOperationAction(
        DLTKEditorMessages.getBundleForConstructedKeys(),
        "OpenHierarchy.", this, PHPStructuredTextViewer.SHOW_HIERARCHY, true); //$NON-NLS-1$
    action.setActionDefinitionId(IScriptEditorActionDefinitionIds.OPEN_HIERARCHY);
    setAction(IScriptEditorActionDefinitionIds.OPEN_HIERARCHY, action);
    markAsCursorDependentAction(
        IScriptEditorActionDefinitionIds.OPEN_HIERARCHY, true);

    ResourceAction resAction = new TextOperationAction(
        DLTKEditorMessages.getBundleForConstructedKeys(),
        "ShowPHPDoc.", this, ISourceViewer.INFORMATION, true); //$NON-NLS-1$
    resAction = new InformationDispatchAction(
        DLTKEditorMessages.getBundleForConstructedKeys(),
        "ShowPHPDoc.", (TextOperationAction) resAction); //$NON-NLS-1$
    resAction
        .setActionDefinitionId(IPHPEditorActionDefinitionIds.SHOW_PHPDOC);
    setAction("ShowPHPDoc", resAction); //$NON-NLS-1$

    resAction = new TextOperationAction(
        DLTKEditorMessages.getBundleForConstructedKeys(),
        "ShowOutline.", this, PHPStructuredTextViewer.SHOW_OUTLINE, true); //$NON-NLS-1$
    resAction
        .setActionDefinitionId(IScriptEditorActionDefinitionIds.SHOW_OUTLINE); //$NON-NLS-1$
    setAction(IScriptEditorActionDefinitionIds.SHOW_OUTLINE, resAction);

    // workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=305786
    // collapse by shortcut does not work
    resAction = new TextOperationAction(
        FoldingMessages.getResourceBundle(),
        "Projection.Expand.", this, ProjectionViewer.EXPAND, true); //$NON-NLS-1$
    resAction.setActionDefinitionId(IFoldingCommandIds.FOLDING_EXPAND);
    setAction("FoldingExpand", resAction); //$NON-NLS-1$
    resAction.setEnabled(true);

    resAction = new TextOperationAction(
        FoldingMessages.getResourceBundle(),
        "Projection.Collapse.", this, ProjectionViewer.COLLAPSE, true); //$NON-NLS-1$
    resAction.setActionDefinitionId(IFoldingCommandIds.FOLDING_COLLAPSE);
    setAction("FoldingCollapse", resAction); //$NON-NLS-1$
    resAction.setEnabled(true);
    // workaround end

    if (isExternal) {
      // Override the way breakpoints are set on external files.
      action = new ToggleExternalBreakpointAction(this,
          getVerticalRuler());
      setAction(ActionDefinitionIds.TOGGLE_BREAKPOINTS, action);
      // StructuredTextEditor Action - manage breakpoints
      action = new ManageExternalBreakpointAction(this,
          getVerticalRuler());
      setAction(ActionDefinitionIds.MANAGE_BREAKPOINTS, action);
      // StructuredTextEditor Action - edit breakpoints
      action = new EditExternalBreakpointAction(this, getVerticalRuler());
      setAction(ActionDefinitionIds.EDIT_BREAKPOINTS, action);

      // Set the ruler double-click behavior.
      setAction(ITextEditorActionConstants.RULER_DOUBLE_CLICK,
          new ToggleExternalBreakpointAction(this,
              getVerticalRuler(), null));
    }

    // ActionGroup rg = new RefactorActionGroup(this,
    // ITextEditorActionConstants.GROUP_EDIT);
    ActionGroup jsg = new PHPSearchActionGroup(this);

    // We have to keep the context menu group separate to have better
    // control over positioning
    fActionGroups = new CompositeActionGroup(new ActionGroup[] { jsg });
    fContextMenuGroup = new CompositeActionGroup(new ActionGroup[] { jsg });
  }

  /**
   * Returns the standard action group of this editor.
   *
   * @return returns this editor's standard action group
   */
  public ActionGroup getActionGroup() {
    return fActionGroups;
  }

  /**
   * Jumps to the matching bracket.
   */
  public void gotoMatchingBracket() {

    ISourceViewer sourceViewer = getSourceViewer();
    IDocument document = sourceViewer.getDocument();
    if (document == null)
      return;

    IRegion selection = getSignedSelection(sourceViewer);

    int selectionLength = Math.abs(selection.getLength());
    if (selectionLength > 1) {
      setStatusLineErrorMessage(PHPUIMessages.GotoMatchingBracket_error_invalidSelection);
      sourceViewer.getTextWidget().getDisplay().beep();
      return;
    }

    // #26314
    int sourceCaretOffset = selection.getOffset() + selection.getLength();
    if (isSurroundedByBrackets(document, sourceCaretOffset))
      sourceCaretOffset -= selection.getLength();

    IRegion region = fBracketMatcher.match(document, sourceCaretOffset);
    if (region == null) {
      setStatusLineErrorMessage(PHPUIMessages.GotoMatchingBracket_error_noMatchingBracket);
      sourceViewer.getTextWidget().getDisplay().beep();
      return;
    }

    int offset = region.getOffset();
    int length = region.getLength();

    if (length < 1)
      return;

    int anchor = fBracketMatcher.getAnchor();
    // http://dev.eclipse.org/bugs/show_bug.cgi?id=34195
    int targetOffset = ICharacterPairMatcher.RIGHT == anchor ? offset + 1
        : offset + length;

    boolean visible = false;
    if (sourceViewer instanceof ITextViewerExtension5) {
      ITextViewerExtension5 extension = (ITextViewerExtension5) sourceViewer;
      visible = extension.modelOffset2WidgetOffset(targetOffset) > -1;
    } else {
      IRegion visibleRegion = sourceViewer.getVisibleRegion();
      // http://dev.eclipse.org/bugs/show_bug.cgi?id=34195
      visible = targetOffset >= visibleRegion.getOffset()
          && targetOffset <= visibleRegion.getOffset()
              + visibleRegion.getLength();
    }

    if (!visible) {
      setStatusLineErrorMessage(PHPUIMessages.GotoMatchingBracket_error_bracketOutsideSelectedElement);
      sourceViewer.getTextWidget().getDisplay().beep();
      return;
    }

    if (selection.getLength() < 0)
      targetOffset -= selection.getLength();

    sourceViewer.setSelectedRange(targetOffset, selection.getLength());
    sourceViewer.revealRange(targetOffset, selection.getLength());
  }

  private static boolean isSurroundedByBrackets(IDocument document, int offset) {
    if (offset == 0 || offset == document.getLength())
      return false;

    try {
      return isBracket(document.getChar(offset - 1))
          && isBracket(document.getChar(offset));

    } catch (BadLocationException e) {
      return false;
    }
  }

  private static boolean isBracket(char character) {
    for (int i = 0; i != BRACKETS.length; ++i)
      if (character == BRACKETS[i])
        return true;
    return false;
  }

  /**
   * Returns the signed current selection. The length will be negative if the
   * resulting selection is right-to-left (RtoL).
   * <p>
   * The selection offset is model based.
   * </p>
   *
   * @param sourceViewer
   *            the source viewer
   * @return a region denoting the current signed selection, for a resulting
   *         RtoL selections length is < 0
   */
  protected IRegion getSignedSelection(ISourceViewer sourceViewer) {
    StyledText text = sourceViewer.getTextWidget();
    Point selection = text.getSelectionRange();

    if (text.getCaretOffset() == selection.x) {
      selection.x = selection.x + selection.y;
      selection.y = -selection.y;
    }

    selection.x = widgetOffset2ModelOffset(sourceViewer, selection.x);

    return new Region(selection.x, selection.y);
  }

  /**
   * Return whether document folding should be enabled according to the
   * preference store settings.
   *
   * @return <code>true</code> if document folding should be enabled
   */
  private boolean isFoldingEnabled() {
    IPreferenceStore store = getPreferenceStore();
    // check both preference store and vm argument
    return (store
        .getBoolean(AbstractStructuredFoldingStrategy.FOLDING_ENABLED));
  }

  /**
   * install the old code folding instead of wtp's
   */
  private void installProjectionSupport() {
    projectionSupportInstalled = true;
    ProjectionViewer projectionViewer = (ProjectionViewer) getSourceViewer();

    fProjectionModelUpdater = new PHPFoldingStructureProviderProxy();
    if (fProjectionModelUpdater != null)
      fProjectionModelUpdater.install(projectionViewer);

    if (isFoldingEnabled()) {
      fProjectionModelUpdater.projectionEnabled();
    }
  }

  @Override
  public void createPartControl(final Composite parent) {
    super.createPartControl(parent);

    // workaround for code folding
    if (isFoldingEnabled()) {
      installProjectionSupport();
    }
    // end
    if (isMarkingOccurrences())
      installOccurrencesFinder(true);

    final IInformationControlCreator informationControlCreator = new IInformationControlCreator() {
      public IInformationControl createInformationControl(Shell shell) {
        boolean cutDown = false;
        int style = cutDown ? SWT.NONE : SWT.V_SCROLL | SWT.H_SCROLL;
        return new DefaultInformationControl(shell, SWT.RESIZE
            | SWT.TOOL, style, new HTMLTextPresenter(cutDown));
      }
    };

    fInformationPresenter = new InformationPresenter(
        informationControlCreator);
    fInformationPresenter.setSizeConstraints(60, 10, true, true);
    fInformationPresenter.install(getSourceViewer());

    addEditorReconcilingListener(getSourceViewerConfiguration(),
        getTextViewer());

    StyledText styledText = getTextViewer().getTextWidget();
    styledText.getContent().addTextChangeListener(new TextChangeListener() {
      public void textChanging(TextChangingEvent event) {
        fReconcileSelection = true;
        PHPUiPlugin.getDefault().getASTProvider().markASTDirty();
      }

      public void textChanged(TextChangedEvent event) {

      }

      public void textSet(TextChangedEvent event) {
      }
    });

    fEditorSelectionChangedListener = new EditorSelectionChangedListener();
    fEditorSelectionChangedListener.install(getSelectionProvider());
    PlatformUI.getWorkbench().addWindowListener(fActivationListener);

    setHelpContextId(IPHPHelpContextIds.EDITOR_PREFERENCES);//$NON-NLS-1$
  }

  private void addEditorReconcilingListener(SourceViewerConfiguration config,
      StructuredTextViewer textViewer) {
    IReconciler reconciler = config.getReconciler(textViewer);
    if (reconciler instanceof DocumentRegionProcessor) {
      ((DocumentRegionProcessor) reconciler)
          .addReconcilingListener(fEditorReconcilingListener);
    }
  }

  private class EditorReconcilingListener implements
      ISourceReconcilingListener {

    public void aboutToBeReconciled() {
      PHPStructuredEditor.this.aboutToBeReconciled();

    }

    public void reconciled(IDocument document, IAnnotationModel model,
        boolean forced, IProgressMonitor progressMonitor) {
    }

  }

  @Override
  protected void setSourceViewerConfiguration(SourceViewerConfiguration config) {
    SourceViewerConfiguration old = config;
    super.setSourceViewerConfiguration(config);
    StructuredTextViewer stv = getTextViewer();
    if (stv != null) {
      removeEditorReconcilingListener(old, stv);
      addEditorReconcilingListener(config, stv);
    }

  }

  private void removeEditorReconcilingListener(
      SourceViewerConfiguration config, StructuredTextViewer textViewer) {

    IReconciler reconciler = config.getReconciler(textViewer);
    if (reconciler instanceof DocumentRegionProcessor) {
      ((DocumentRegionProcessor) reconciler)
          .removeReconcilingListener(fEditorReconcilingListener);
    }

  }

  @Override
  protected void doSetInput(IEditorInput input) throws CoreException {
    IFile resource = null;
    isExternal = false;

    if (input instanceof IFileEditorInput) {
      // This is the existing workspace file
      final IFileEditorInput fileInput = (IFileEditorInput) input;
      resource = fileInput.getFile();
      if (getRefactorableFileEditorInput() != null
          && ((RefactorableFileEditorInput) getRefactorableFileEditorInput())
              .isRefactor()) {
        getRefactorableFileEditorInput().setRefactor(false);
        getDocumentProvider().disconnect(
            getRefactorableFileEditorInput());
        getRefactorableFileEditorInput().setFile(fileInput.getFile());
        input = getRefactorableFileEditorInput();
      } else {
        input = new RefactorableFileEditorInput(fileInput.getFile());
      }

    }

    if (resource != null) {
      if (PHPToolkitUtil.isPhpFile((IFile) resource)) {

        PhpSourceParser.editFile.set(resource);

        super.doSetInput(input);

        initPHPVersionsListener();

      } else {
        super.doSetInput(input);
      }
    } else {
      isExternal = true;
      super.doSetInput(input);
    }

    ImageDescriptor imageDescriptor = input.getImageDescriptor();
    if (imageDescriptor != null) {
      setTitleImage(JFaceResources.getResources().createImageWithDefault(
          imageDescriptor));
    }
    if (isShowingOverrideIndicators()) {
      installOverrideIndicator(true);
    }

    if (fProjectionModelUpdater != null)
      updateProjectionSupport();
  }

  /**
   * Install everything necessary to get document folding working and enable
   * document folding
   */
  private void updateProjectionSupport() {
    // dispose of previous document folding support
    if (fProjectionModelUpdater != null) {
      fProjectionModelUpdater.uninstall();
      fProjectionModelUpdater = null;
    }

    ProjectionViewer projectionViewer = (ProjectionViewer) getSourceViewer();

    fProjectionModelUpdater = new PHPFoldingStructureProviderProxy();
    if (fProjectionModelUpdater != null) {
      fProjectionModelUpdater.install(projectionViewer);
      fProjectionModelUpdater.initialize();
    }

  }

  @Override
  protected boolean canHandleMove(IEditorInput originalElement,
      IEditorInput movedElement) {
    if (getRefactorableFileEditorInput() != null) {
      getRefactorableFileEditorInput().setRefactor(true);
    }
    return super.canHandleMove(originalElement, movedElement);
  }

  private RefactorableFileEditorInput getRefactorableFileEditorInput() {
    if (getEditorInput() instanceof RefactorableFileEditorInput) {
      return (RefactorableFileEditorInput) getEditorInput();
    }
    return null;
  }

  OutlineSelectionChangedListener fPHPOutlinePageListener;

  @SuppressWarnings("unchecked")
  public Object getAdapter(Class required) {

    Object adapter = super.getAdapter(required);

    // add selection listener to outline page
    // so that if outline selects model element, editor selects correct item
    if (adapter instanceof ConfigurableContentOutlinePage
        && IContentOutlinePage.class.equals(required)
        && shouldOutlineViewBeLoaded()) {
      final ConfigurableContentOutlinePage outlinePage = (ConfigurableContentOutlinePage) adapter;
      if (fPHPOutlinePageListener == null) {
        fPHPOutlinePageListener = new OutlineSelectionChangedListener(
            outlinePage.getConfiguration());
        outlinePage.addDoubleClickListener(fPHPOutlinePageListener);
      }
      fPHPOutlinePageListener.install(outlinePage);
      fPHPOutlinePage = outlinePage;
      outlinePage.setInput(getModelElement());
    }
    return adapter;
  }

  private boolean shouldOutlineViewBeLoaded() {
    if (fPHPOutlinePage != null && fPHPOutlinePage.getControl() != null
        && !fPHPOutlinePage.getControl().isDisposed()) {
      return false;
    }
    return true;
  }

  protected void clearStatusLine() {
    setStatusLineErrorMessage(null);
    setStatusLineMessage(null);
  }

  public SourceViewerConfiguration getSourceViwerConfiguration() {
    return super.getSourceViewerConfiguration();
  }

  /**
   * Returns the cached selected range, which allows to query it from a non-UI
   * thread.
   * <p>
   * The result might be outdated if queried from a non-UI thread.</em>
   * </p>
   *
   * @return the caret offset in the master document
   * @see ITextViewer#getSelectedRange()
   * @since 3.3
   */
  public Point getCachedSelectedRange() {
    return fCachedSelectedRange;
  }

  /*
   * (non-Javadoc)
   *
   * @see
   * org.eclipse.wst.sse.ui.StructuredTextEditor#handleCursorPositionChanged()
   */
  @Override
  protected void handleCursorPositionChanged() {
    updateCursorDependentActions();
    fCachedSelectedRange = getTextViewer().getSelectedRange();
    super.handleCursorPositionChanged();
  }

  @Override
  protected void handlePreferenceStoreChanged(final PropertyChangeEvent event) {
    final String property = event.getProperty();
    try {
      if (PreferenceConstants.EDITOR_TEXT_HOVER_MODIFIERS
          .equals(property)
          || PreferenceConstants.EDITOR_TEXT_HOVER_MODIFIER_MASKS
              .equals(property)) {
        updateHoverBehavior();
        return;
      }
      boolean newBooleanValue = false;
      Object newValue = event.getNewValue();
      if (newValue != null) {
        newBooleanValue = Boolean.valueOf(newValue.toString())
            .booleanValue();
      }
      if (PreferenceConstants.EDITOR_MARK_OCCURRENCES.equals(property)) {
        if (newBooleanValue != fMarkOccurrenceAnnotations) {
          fMarkOccurrenceAnnotations = newBooleanValue;
          if (!fMarkOccurrenceAnnotations)
            uninstallOccurrencesFinder();
          else
            installOccurrencesFinder(true);
        }
        return;
      }
      if (PreferenceConstants.EDITOR_MARK_TYPE_OCCURRENCES
          .equals(property)) {
        fMarkTypeOccurrences = newBooleanValue;
        return;
      }
      if (PreferenceConstants.EDITOR_MARK_METHOD_OCCURRENCES
          .equals(property)) {
        fMarkMethodOccurrences = newBooleanValue;
        return;
      }
      if (PreferenceConstants.EDITOR_MARK_FUNCTION_OCCURRENCES
          .equals(property)) {
        fMarkFunctionOccurrences = newBooleanValue;
        return;
      }
      if (PreferenceConstants.EDITOR_MARK_CONSTANT_OCCURRENCES
          .equals(property)) {
        fMarkConstantOccurrences = newBooleanValue;
        return;
      }
      if (PreferenceConstants.EDITOR_MARK_GLOBAL_VARIABLE_OCCURRENCES
          .equals(property)) {
        fMarkGlobalVariableOccurrences = newBooleanValue;
        return;
      }
      if (PreferenceConstants.EDITOR_MARK_LOCAL_VARIABLE_OCCURRENCES
          .equals(property)) {
        fMarkLocalVariableOccurrences = newBooleanValue;
        return;
      }
      if (PreferenceConstants.EDITOR_MARK_METHOD_EXIT_POINTS
          .equals(property)) {
        fMarkMethodExitPoints = newBooleanValue;
        return;
      }
      if (PreferenceConstants.EDITOR_MARK_BREAK_CONTINUE_TARGETS
          .equals(property)) {
        fMarkBreakContinueTargets = newBooleanValue;
        return;
      }
      if (PreferenceConstants.EDITOR_MARK_IMPLEMENTORS.equals(property)) {
        fMarkImplementors = newBooleanValue;
        return;
      }
      if (PreferenceConstants.EDITOR_STICKY_OCCURRENCES.equals(property)) {
        fStickyOccurrenceAnnotations = newBooleanValue;
        return;
      }

      if (affectsOverrideIndicatorAnnotations(event)) {
        if (isShowingOverrideIndicators()) {
          if (fOverrideIndicatorManager == null)
            installOverrideIndicator(true);
        } else {
          if (fOverrideIndicatorManager != null)
            uninstallOverrideIndicator();
        }
        return;
      }

    } finally {
      super.handlePreferenceStoreChanged(event);
    }

    if (AbstractStructuredFoldingStrategy.FOLDING_ENABLED.equals(property)) {
      if (getSourceViewer() instanceof ProjectionViewer) {
        // install projection support if it has not even been
        // installed yet
        if (isFoldingEnabled() && !projectionSupportInstalled) {
          installProjectionSupport();
        }
      }
    }
  }

  /**
   * Determines whether the preference change encoded by the given event
   * changes the override indication.
   *
   * @param event
   *            the event to be investigated
   * @return <code>true</code> if event causes a change
   */
  protected boolean affectsOverrideIndicatorAnnotations(
      PropertyChangeEvent event) {
    String key = event.getProperty();
    AnnotationPreference preference = getAnnotationPreferenceLookup()
        .getAnnotationPreference(
            OverrideIndicatorManager.ANNOTATION_TYPE);
    if (key == null || preference == null)
      return false;

    return key.equals(preference.getHighlightPreferenceKey())
        || key.equals(preference.getVerticalRulerPreferenceKey())
        || key.equals(preference.getOverviewRulerPreferenceKey())
        || key.equals(preference.getTextPreferenceKey());
  }

  /**
   * Tells whether override indicators are shown.
   *
   * @return <code>true</code> if the override indicators are shown
   * @since 3.0
   */
  protected boolean isShowingOverrideIndicators() {
    AnnotationPreference preference = getAnnotationPreferenceLookup()
        .getAnnotationPreference(
            OverrideIndicatorManager.ANNOTATION_TYPE);
    IPreferenceStore store = getPreferenceStore();
    return getBoolean(store, preference.getHighlightPreferenceKey())
        || getBoolean(store, preference.getVerticalRulerPreferenceKey())
        || getBoolean(store, preference.getOverviewRulerPreferenceKey())
        || getBoolean(store, preference.getTextPreferenceKey());
  }

  /**
   * Returns the boolean preference for the given key.
   *
   * @param store
   *            the preference store
   * @param key
   *            the preference key
   * @return <code>true</code> if the key exists in the store and its value is
   *         <code>true</code>
   * @since 3.0
   */
  private boolean getBoolean(IPreferenceStore store, String key) {
    return key != null && store.getBoolean(key);
  }

  @Override
  protected void initializeKeyBindingScopes() {
    setKeyBindingScopes(new String[] { "org.eclipse.php.ui.phpEditorScope" }); //$NON-NLS-1$
  }

  /**
   * Marks or unmarks the given action to be updated on text cursor position
   * changes.
   *
   * @param actionId
   *            the action id
   * @param mark
   *            <code>true</code> if the action is cursor position dependent
   */
  public void markAsCursorDependentAction(final String actionId,
      final boolean mark) {
    assert actionId != null;
    if (mark) {
      if (!fCursorActions.contains(actionId))
        fCursorActions.add(actionId);
    } else
      fCursorActions.remove(actionId);
  }

  public IDocument getDocument() {
    if (getSourceViewer() != null) {
      return getSourceViewer().getDocument();
    }
    return null;
  }

  /**
   * Updates the specified action by calling <code>IUpdate.update</code> if
   * applicable.
   *
   * @param actionId
   *            the action id
   */
  private void updateAction(final String actionId) {
    assert actionId != null;
    final IAction action = getAction(actionId);
    if (action instanceof IUpdate)
      ((IUpdate) action).update();
  }

  /**
   * Updates all cursor position dependent actions.
   */
  protected void updateCursorDependentActions() {
    if (fCursorActions != null) {

      long currentTime = System.currentTimeMillis();
      if (fLastActionsUpdate > currentTime - 1000) { // only allow updates
        // at most once per
        // second
        return;
      }
      fLastActionsUpdate = currentTime;

      final Iterator<String> e = fCursorActions.iterator();
      while (e.hasNext())
        updateAction(e.next());
    }
  }

  /*
   * Update the hovering behavior depending on the preferences.
   */
  private void updateHoverBehavior() {
    final SourceViewerConfiguration configuration = getSourceViewerConfiguration();
    final String[] types = configuration
        .getConfiguredContentTypes(getSourceViewer());

    for (final String t : types) {

      final ISourceViewer sourceViewer = getSourceViewer();
      if (sourceViewer instanceof ITextViewerExtension2) {
        // Remove existing hovers
        ((ITextViewerExtension2) sourceViewer).removeTextHovers(t);

        final int[] stateMasks = configuration
            .getConfiguredTextHoverStateMasks(getSourceViewer(), t);

        if (stateMasks != null)
          for (final int stateMask : stateMasks) {
            final ITextHover textHover = configuration
                .getTextHover(sourceViewer, t, stateMask);
            ((ITextViewerExtension2) sourceViewer).setTextHover(
                textHover, t, stateMask);
          }
        else {
          final ITextHover textHover = configuration.getTextHover(
              sourceViewer, t);
          ((ITextViewerExtension2) sourceViewer).setTextHover(
              textHover, t,
              ITextViewerExtension2.DEFAULT_HOVER_STATE_MASK);
        }
      } else
        sourceViewer.setTextHover(
            configuration.getTextHover(sourceViewer, t), t);
    }
  }

  @Override
  protected StructuredTextViewer createStructedTextViewer(Composite parent,
      IVerticalRuler verticalRuler, int styles) {
    return new PHPStructuredTextViewer(this, parent, verticalRuler,
        getOverviewRuler(), isOverviewRulerVisible(), styles);
  }

  /*
   * @see org.eclipse.ui.texteditor.AbstractTextEditor#performSave(boolean,
   * org.eclipse.core.runtime.IProgressMonitor)
   */
  protected void performSave(boolean overwrite,
      IProgressMonitor progressMonitor) {
    IDocumentProvider p = getDocumentProvider();
    if (p instanceof ISourceModuleDocumentProvider) {
      ISourceModuleDocumentProvider cp = (ISourceModuleDocumentProvider) p;
      cp.setSavePolicy(fSavePolicy);
    }
    try {
      super.performSave(overwrite, progressMonitor);
    } finally {
      if (p instanceof ISourceModuleDocumentProvider) {
        ISourceModuleDocumentProvider cp = (ISourceModuleDocumentProvider) p;
        cp.setSavePolicy(null);
      }
    }
  }

  @Override
  public IDocumentProvider getDocumentProvider() {
    if (getEditorInput() instanceof ExternalStorageEditorInput) {
      IDocumentProvider provider = LocalStorageModelProvider
          .getInstance();
      if (provider != null) {
        return provider;
      }
    }
    if (getEditorInput() instanceof RefactorableFileEditorInput) {
      return super.getDocumentProvider();
    }
    if (getEditorInput() instanceof IStorageEditorInput) {
      IDocumentProvider provider = StorageModelProvider.getInstance();
      if (provider != null) {
        return provider;
      }
    }

    return super.getDocumentProvider();
  }

  /**
   * IScriptReconcilingListener methods - reconcile listeners
   */
  private ListenerList fReconcilingListeners = new ListenerList(
      ListenerList.IDENTITY);

  public void addReconcileListener(
      IPhpScriptReconcilingListener reconcileListener) {
    synchronized (fReconcilingListeners) {
      fReconcilingListeners.add(reconcileListener);
    }
  }

  public void removeReconcileListener(
      IPhpScriptReconcilingListener reconcileListener) {
    synchronized (fReconcilingListeners) {
      fReconcilingListeners.remove(reconcileListener);
    }
  }

  public void aboutToBeReconciled() {

    // Notify AST provider
    PHPUiPlugin.getDefault().getASTProvider()
        .aboutToBeReconciled((ISourceModule) getModelElement());
    // Notify listeners
    Object[] listeners = fReconcilingListeners.getListeners();
    for (int i = 0, length = listeners.length; i < length; ++i)
      ((IPhpScriptReconcilingListener) listeners[i])
          .aboutToBeReconciled();
  }

  /*
   * @see
   * org.eclipse.jdt.internal.ui.text.java.IJavaReconcilingListener#reconciled
   * (CompilationUnit, boolean, IProgressMonitor)
   *
   * @since 3.0
   */
  public void reconciled(Program ast, boolean forced,
      IProgressMonitor progressMonitor) {

    // see: https://bugs.eclipse.org/bugs/show_bug.cgi?id=58245
    PHPUiPlugin phpPlugin = PHPUiPlugin.getDefault();
    if (phpPlugin == null)
      return;

    // Always notify AST provider
    ISourceModule inputModelElement = (ISourceModule) getModelElement();

    phpPlugin.getASTProvider().reconciled(ast, inputModelElement,
        progressMonitor);
    this.fReconcileSelection = false;

    // Notify listeners
    Object[] listeners = fReconcilingListeners.getListeners();
    for (int i = 0, length = listeners.length; i < length; ++i)
      ((IPhpScriptReconcilingListener) listeners[i]).reconciled(ast,
          forced, progressMonitor);
  }

  /**
   * Returns the model element wrapped by this editors input. Most likely to
   * be the relevant source module
   *
   * @return the model element wrapped by this editors input.
   *
   */
  public IModelElement getModelElement() {
    return EditorUtility.getEditorInputModelElement(this, false);
  }

  /**
   * Returns the most narrow model element including the given offset.
   *
   * @param offset
   *            the offset inside of the requested element
   * @return the most narrow model element
   */
  protected IModelElement getElementAt(int offset) {
    return getElementAt(offset, true);
  }

  /**
   * Returns the most narrow element including the given offset. If
   * <code>reconcile</code> is <code>true</code> the editor's input element is
   * reconciled in advance. If it is <code>false</code> this method only
   * returns a result if the editor's input element does not need to be
   * reconciled.
   *
   * @param offset
   *            the offset included by the retrieved element
   * @param reconcile
   *            <code>true</code> if working copy should be reconciled
   * @return the most narrow element which includes the given offset
   */
  protected IModelElement getElementAt(int offset, boolean reconcile) {
    ISourceModule unit = (ISourceModule) getModelElement();
    if (unit != null) {
      try {
        if (reconcile) {
          fReconcileSelection = false;
          ScriptModelUtil.reconcile(unit);
          return unit.getElementAt(offset);
        } else if (unit.isConsistent())
          return unit.getElementAt(offset);
      } catch (ModelException x) {
        if (!x.isDoesNotExist())
          // DLTKUIPlugin.log(x.getStatus());
          System.err.println(x.getStatus());
        // nothing found, be tolerant and go on
      }
    }
    return null;
  }

  /**
   * Returns project that holds the edited file (if any)
   *
   * @return project or <code>null</code> if there's no one
   */
  public IScriptProject getProject() {
    IModelElement modelElement = getModelElement();
    if (modelElement != null) {
      return modelElement.getScriptProject();
    }
    return null;
  }

  /**
   * Support mark occurrences in PHP Editor
   */

  /**
   * Returns the lock object for the given annotation model.
   *
   * @param annotationModel
   *            the annotation model
   * @return the annotation model's lock object
   * @since 3.0
   */
  private Object getLockObject(IAnnotationModel annotationModel) {
    if (annotationModel instanceof ISynchronizable) {
      Object lock = ((ISynchronizable) annotationModel).getLockObject();
      if (lock != null)
        return lock;
    }
    return annotationModel;
  }

  protected void updateOccurrenceAnnotations(ITextSelection selection,
      Program astRoot) {
    updateOccurencesAnnotationsRunJob(selection, astRoot);
  }

  /**
   * Updates the occurrences annotations based on the current selection.
   *
   * TODO : since {@link PHPStructuredEditor#aboutToBeChangedEvent} currently
   * doesn't work, we check if " document.getLength() != astRoot.getEnd() " to
   * identify if the document was not already reconciled
   *
   * @param selection
   *            the text selection
   * @param astRoot
   *            the compilation unit AST
   * @since 3.0
   */
  protected void updateOccurrenceAnnotations(final ITextSelection selection,
      final IModelElement sourceModule) {

    if (fOccurrencesFinderJob != null)
      fOccurrencesFinderJob.cancel();

    if (!fMarkOccurrenceAnnotations)
      return;

    String updatingOccurencesJobName = "Updating occurence annotations"; //$NON-NLS-1$

    IJobManager jobManager = Job.getJobManager();
    if (jobManager.find(updatingOccurencesJobName).length > 0) {
      jobManager.cancel(updatingOccurencesJobName);
    }
    Job job = new Job(updatingOccurencesJobName) {

      @Override
      protected IStatus run(IProgressMonitor monitor) {
        Program astRoot = null;
        try {
          astRoot = SharedASTProvider.getAST(
              (ISourceModule) sourceModule,
              SharedASTProvider.WAIT_ACTIVE_ONLY,
              new NullProgressMonitor());
        } catch (ModelException e) {
          Logger.logException(e);
        } catch (IOException e) {
          Logger.logException(e);
        }
        if (!monitor.isCanceled() && astRoot != null) {
          updateOccurencesAnnotationsRunJob(selection, astRoot);
        }
        return Status.OK_STATUS;
      }

      @Override
      public boolean belongsTo(Object family) {
        return getName().equals(family);
      }
    };
    job.setSystem(true);
    job.setPriority(Job.DECORATE);
    job.schedule();

  }

  private void updateOccurencesAnnotationsRunJob(ITextSelection selection,
      Program astRoot) {
    if (astRoot == null || selection == null)
      return;

    ISourceViewer viewer = getSourceViewer();
    if (viewer == null)
      return;

    IDocument document = viewer.getDocument();
    if (document == null)
      return;

    // TODO: see the method comment, need to be removed once
    // PHPStructuredEditor#aboutToBeChangedEvent is used
    if (document.getLength() != astRoot.getEnd()
        || this.fReconcileSelection) {
      return;
    }

    boolean hasChanged = false;
    if (document instanceof IDocumentExtension4) {
      int offset = selection.getOffset();
      long currentModificationStamp = ((IDocumentExtension4) document)
          .getModificationStamp();
      IRegion markOccurrenceTargetRegion = fMarkOccurrenceTargetRegion;
      hasChanged = currentModificationStamp != fMarkOccurrenceModificationStamp;
      if (markOccurrenceTargetRegion != null && !hasChanged) {
        if (markOccurrenceTargetRegion.getOffset() <= offset
            && offset <= markOccurrenceTargetRegion.getOffset()
                + markOccurrenceTargetRegion.getLength())
          return;
      }
      fMarkOccurrenceTargetRegion = ScriptWordFinder.findWord(document,
          offset);
      fMarkOccurrenceModificationStamp = currentModificationStamp;
    }

    OccurrenceLocation[] locations = null;

    ASTNode selectedNode = NodeFinder.perform(astRoot,
        selection.getOffset(), selection.getLength());

    if (locations == null && fMarkExceptions) {
      // TODO : Implement Me!
      // ExceptionOccurrencesFinder finder= new
      // ExceptionOccurrencesFinder();
      // if (finder.initialize(astRoot, selectedNode) == null) {
      // locations= finder.getOccurrences();
      // }
    }

    if (locations == null && fMarkMethodExitPoints) {
      IOccurrencesFinder finder = OccurrencesFinderFactory
          .createMethodExitsFinder();
      if (finder.initialize(astRoot, selectedNode) == null) {
        locations = finder.getOccurrences();
      }
    }

    if (locations == null && fMarkImplementors) {
      IOccurrencesFinder finder = OccurrencesFinderFactory
          .createIncludeFinder();
      if (finder.initialize(astRoot, selectedNode) == null) {
        locations = finder.getOccurrences();
      }
    }

    if (locations == null && fMarkBreakContinueTargets) {
      IOccurrencesFinder finder = OccurrencesFinderFactory
          .createBreakContinueTargetFinder();
      if (finder.initialize(astRoot, selectedNode) == null) {
        locations = finder.getOccurrences();
      }
    }

    if (locations == null && fMarkImplementors) {
      IOccurrencesFinder finder = OccurrencesFinderFactory
          .createImplementorsOccurrencesFinder();
      if (finder.initialize(astRoot, selectedNode) == null) {
        locations = finder.getOccurrences();
      }
    }

    if (selectedNode != null && selectedNode.getType() == ASTNode.VARIABLE) {
      final Expression name = ((Variable) selectedNode).getName();
      if (name instanceof Identifier) {
        selectedNode = name;
      }
    }

    if (locations == null
        && selectedNode != null
        && (selectedNode instanceof Identifier || (isScalarButNotInString(selectedNode)))) {
      int type = PhpElementConciliator.concile(selectedNode);
      if (markOccurrencesOfType(type)) {
        IOccurrencesFinder finder = OccurrencesFinderFactory
            .getOccurrencesFinder(type);
        if (finder != null) {
          if (finder.initialize(astRoot, selectedNode) == null) {
            locations = finder.getOccurrences();
          }
        }
      }
    }

    if (locations == null) {
      if (!fStickyOccurrenceAnnotations)
        removeOccurrenceAnnotations();
      else if (hasChanged) // check consistency of current annotations
        removeOccurrenceAnnotations();
      return;
    }

    fOccurrencesFinderJob = new OccurrencesFinderJob(document, locations,
        selection);
    // fOccurrencesFinderJob.setPriority(Job.DECORATE);
    // fOccurrencesFinderJob.setSystem(true);
    // fOccurrencesFinderJob.schedule();
    fOccurrencesFinderJob.run(new NullProgressMonitor());
  }

  /**
   * Checks whether or not the node is a scalar and return true only if the
   * scalar is not part of a string
   *
   * @param node
   * @return
   */
  private boolean isScalarButNotInString(ASTNode node) {
    return (node.getType() == ASTNode.SCALAR)
        && (node.getParent().getType() != ASTNode.QUOTE);
  }

  protected void uninstallOverrideIndicator() {
    if (fOverrideIndicatorManager != null) {
      removeReconcileListener(fOverrideIndicatorManager);
      fOverrideIndicatorManager.removeAnnotations();
      fOverrideIndicatorManager = null;
    }
  }

  protected void installOverrideIndicator(boolean provideAST) {
    uninstallOverrideIndicator();
    if (getDocumentProvider() == null) {
      return;
    }
    IAnnotationModel model = getDocumentProvider().getAnnotationModel(
        getEditorInput());
    final IModelElement inputElement = getModelElement();
    if (model == null || inputElement == null
        || inputElement.getElementType() != IModelElement.SOURCE_MODULE)
      return;

    fOverrideIndicatorManager = new OverrideIndicatorManager(model,
        inputElement, null);
    addReconcileListener(fOverrideIndicatorManager);
    if (provideAST) {
      Job job = new Job("Installing override indicator") { //$NON-NLS-1$

        @Override
        protected IStatus run(IProgressMonitor monitor) {
          try {
            Program ast = SharedASTProvider.getAST(
                (ISourceModule) inputElement,
                SharedASTProvider.WAIT_ACTIVE_ONLY,
                new NullProgressMonitor());
            if (fOverrideIndicatorManager != null) {
              fOverrideIndicatorManager.reconciled(ast, true,
                  getProgressMonitor());
            }
          } catch (ModelException e) {
            Logger.logException(e);
          } catch (IOException e) {
            Logger.logException(e);
          }
          return Status.OK_STATUS;
        }
      };
      job.setSystem(true);
      job.setPriority(Job.DECORATE);
      job.schedule();

    }
  }

  protected void installOccurrencesFinder(boolean forceUpdate) {
    fMarkOccurrenceAnnotations = true;

    fPostSelectionListenerWithAST = new ISelectionListenerWithAST() {
      public void selectionChanged(IEditorPart part,
          ITextSelection selection, Program astRoot) {
        updateOccurrenceAnnotations(selection, astRoot);
      }
    };
    SelectionListenerWithASTManager.getDefault().addListener(this,
        fPostSelectionListenerWithAST);
    if (forceUpdate && getSelectionProvider() != null) {
      fForcedMarkOccurrencesSelection = getSelectionProvider()
          .getSelection();
      final IModelElement source = getModelElement();
      if ((source != null)
          && source.getElementType() == IModelElement.SOURCE_MODULE) {
        updateOccurrenceAnnotations(
            (ITextSelection) fForcedMarkOccurrencesSelection,
            (ISourceModule) source);
      }
    }

    if (fOccurrencesFinderJobCanceler == null) {
      fOccurrencesFinderJobCanceler = new OccurrencesFinderJobCanceler();
      fOccurrencesFinderJobCanceler.install();
    }
  }

  protected void uninstallOccurrencesFinder() {
    fMarkOccurrenceAnnotations = false;

    if (fOccurrencesFinderJob != null) {
      fOccurrencesFinderJob.cancel();
      fOccurrencesFinderJob = null;
    }

    if (fOccurrencesFinderJobCanceler != null) {
      fOccurrencesFinderJobCanceler.uninstall();
      fOccurrencesFinderJobCanceler = null;
    }

    if (fPostSelectionListenerWithAST != null) {
      SelectionListenerWithASTManager.getDefault().removeListener(this,
          fPostSelectionListenerWithAST);
      fPostSelectionListenerWithAST = null;
    }

    removeOccurrenceAnnotations();
  }

  public boolean isMarkingOccurrences() {
    IPreferenceStore store = getPreferenceStore();
    return store != null
        && store.getBoolean(PreferenceConstants.EDITOR_MARK_OCCURRENCES);
  }

  /**
   * Returns is the occurrences of the type should be marked.
   *
   * @param type
   *            One of the {@link PhpElementConciliator} constants integer
   *            type.
   * @return True, if the type occurrences should be marked; False, otherwise.
   */
  boolean markOccurrencesOfType(int type) {
    switch (type) {
    case PhpElementConciliator.CONCILIATOR_GLOBAL_VARIABLE:
      return fMarkGlobalVariableOccurrences;
    case PhpElementConciliator.CONCILIATOR_LOCAL_VARIABLE:
      return fMarkLocalVariableOccurrences;
    case PhpElementConciliator.CONCILIATOR_FUNCTION:
      return fMarkFunctionOccurrences;
    case PhpElementConciliator.CONCILIATOR_CLASSNAME:
    case PhpElementConciliator.CONCILIATOR_TRAITNAME:
      return fMarkTypeOccurrences;
    case PhpElementConciliator.CONCILIATOR_CONSTANT:
      return fMarkConstantOccurrences;
    case PhpElementConciliator.CONCILIATOR_CLASS_MEMBER:
      return fMarkMethodOccurrences;
    case PhpElementConciliator.CONCILIATOR_UNKNOWN:
    case PhpElementConciliator.CONCILIATOR_PROGRAM:
    default:
      return false;
    }
  }

  void removeOccurrenceAnnotations() {
    fMarkOccurrenceModificationStamp = IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
    fMarkOccurrenceTargetRegion = null;

    IDocumentProvider documentProvider = getDocumentProvider();
    if (documentProvider == null)
      return;

    IAnnotationModel annotationModel = documentProvider
        .getAnnotationModel(getEditorInput());
    if (annotationModel == null || fOccurrenceAnnotations == null)
      return;

    synchronized (getLockObject(annotationModel)) {
      if (annotationModel instanceof IAnnotationModelExtension) {
        ((IAnnotationModelExtension) annotationModel)
            .replaceAnnotations(fOccurrenceAnnotations, null);
      } else {
        for (int i = 0, length = fOccurrenceAnnotations.length; i < length; i++)
          annotationModel.removeAnnotation(fOccurrenceAnnotations[i]);
      }
      fOccurrenceAnnotations = null;
    }
  }

  protected boolean isActivePart() {
    IWorkbenchPart part = getActivePart();
    return part != null && part.equals(this);
  }

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

  /**
   * React to changed selection.
   *
   * @since 3.0
   */
  protected void selectionChanged() {
    if (getSelectionProvider() == null)
      return;
    Job job = new Job("PHPStructuredEditor selection changed job") { //$NON-NLS-1$

      @Override
      protected IStatus run(IProgressMonitor monitor) {
        ISourceReference element = computeHighlightRangeSourceReference();
        setSelection(element, false);
        return Status.OK_STATUS;
      }
    };
    job.setPriority(Job.DECORATE);
    job.setSystem(true);
    job.schedule();

  }

  /**
   * Computes and returns the source reference that includes the caret and
   * serves as provider for the outline page selection and the editor range
   * indication.
   *
   * @return the computed source reference
   */
  public ISourceReference computeHighlightRangeSourceReference() {
    ISourceViewer sourceViewer = getSourceViewer();
    if (sourceViewer == null)
      return null;
    final StyledText styledText = sourceViewer.getTextWidget();
    if (styledText == null)
      return null;
    final int[] caret = new int[1];
    if (sourceViewer instanceof ITextViewerExtension5) {
      final ITextViewerExtension5 extension = (ITextViewerExtension5) sourceViewer;
      Display.getDefault().syncExec(new Runnable() {
        public void run() {
          if (!styledText.isDisposed()) {
            caret[0] = extension
                .widgetOffset2ModelOffset(styledText
                    .getCaretOffset());
          }
        }
      });

    } else {
      int offset = sourceViewer.getVisibleRegion().getOffset();
      caret[0] = offset + styledText.getCaretOffset();
    }
    IModelElement element = getElementAt(caret[0], false);
    // IModelElement element = getElementAt(caret[0], true);
    if (!(element instanceof ISourceReference))
      return null;
    return (ISourceReference) element;
  }

  protected void setSelection(ISourceReference reference, boolean moveCursor) {
    if (getSelectionProvider() == null)
      return;
    final ISelection[] selection = new ISelection[1];
    Display.getDefault().syncExec(new Runnable() {
      public void run() {
        selection[0] = getSelectionProvider().getSelection();
      }
    });
    if (selection[0] == null) {
      return;
    }
    if (selection[0] instanceof TextSelection) {
      TextSelection textSelection = (TextSelection) selection[0];
      if (textSelection instanceof IStructuredSelection) {
        Object firstElement = ((IStructuredSelection) textSelection)
            .getFirstElement();
        if (firstElement instanceof IImplForPhp) {
          ((IImplForPhp) firstElement)
              .setModelElement(getModelElement());
        }
      }
      // PR 39995: [navigation] Forward history cleared after going back
      // in navigation history:
      // mark only in navigation history if the cursor is being moved
      // (which it isn't if
      // this is called from a PostSelectionEvent that should only update
      // the magnet)
      if (moveCursor
          && (textSelection.getOffset() != 0 || textSelection
              .getLength() != 0))
        markInNavigationHistory();
    }
    if (reference != null) {
      StyledText textWidget = null;
      ISourceViewer sourceViewer = getSourceViewer();
      if (sourceViewer != null)
        textWidget = sourceViewer.getTextWidget();
      if (textWidget == null)
        return;
      try {
        ISourceRange range = null;
        range = reference.getSourceRange();
        if (range == null)
          return;
        int offset = range.getOffset();
        int length = range.getLength();
        if (offset < 0 || length < 0)
          return;
        setHighlightRange(offset, length, moveCursor);
        if (!moveCursor)
          return;
        offset = -1;
        length = -1;
        if (reference instanceof IMember) {
          range = ((IMember) reference).getNameRange();
          if (range != null) {
            offset = range.getOffset();
            length = range.getLength();
          }
        }
        if (offset > -1 && length > 0) {
          try {
            textWidget.setRedraw(false);
            sourceViewer.revealRange(offset, length);
            sourceViewer.setSelectedRange(offset, length);
          } finally {
            textWidget.setRedraw(true);
          }
          markInNavigationHistory();
        }
      } catch (ModelException x) {
      } catch (IllegalArgumentException x) {
      }
    } else if (moveCursor) {
      resetHighlightRange();
      markInNavigationHistory();
    }
  }

  /*
   * Gets the preferences set for this editor in the Save Actions section
   */
  public void updateSaveActionsState(IProject project) {
    PreferencesSupport prefSupport = new PreferencesSupport(PHPUiPlugin.ID,
        PHPUiPlugin.getDefault().getPluginPreferences());
    String doCleanupPref = prefSupport.getPreferencesValue(
        PreferenceConstants.FORMAT_REMOVE_TRAILING_WHITESPACES, null,
        project);
    String ignoreEmptyPref = prefSupport
        .getPreferencesValue(
            PreferenceConstants.FORMAT_REMOVE_TRAILING_WHITESPACES_IGNORE_EMPTY,
            null, project);

    saveActionsEnabled = Boolean.parseBoolean(doCleanupPref);
    saveActionsIgnoreEmptyLines = Boolean.parseBoolean(ignoreEmptyPref);
  }

  /*
   * Added the handling of Save Actions (non-Javadoc)
   *
   * @see
   * org.eclipse.wst.sse.ui.StructuredTextEditor#doSave(org.eclipse.core.runtime
   * .IProgressMonitor)
   */
  @Override
  public void doSave(IProgressMonitor progressMonitor) {
    if (getDocument() instanceof IStructuredDocument) {
      CommandStack commandStack = ((IStructuredDocument) getDocument())
          .getUndoManager().getCommandStack();
      // https://bugs.eclipse.org/bugs/show_bug.cgi?id=322529
      ((IStructuredDocument) getDocument()).getUndoManager()
          .forceEndOfPendingCommand(null,
              getViewer().getSelectedRange().x,
              getViewer().getSelectedRange().y);
      if (commandStack instanceof BasicCommandStack) {
        ((BasicCommandStack) commandStack).saveIsDone();
      }
    }
    IScriptProject project = getProject();
    if (project != null) {
      updateSaveActionsState(project.getProject());
    }

    if (saveActionsEnabled) {
      RemoveTrailingWhitespaceOperation op = new ExtendedRemoveTrailingWhitespaceOperation(
          saveActionsIgnoreEmptyLines);
      try {
        op.run(FileBuffers.getTextFileBufferManager()
            .getTextFileBuffer(getDocument()), progressMonitor);
      } catch (OperationCanceledException e) {
        Logger.logException(e);
      } catch (CoreException e) {
        Logger.logException(e);
      }
    }
    super.doSave(progressMonitor);
  }

  /*
   * Operation used for removing whitepsaces from line ends
   */
  class ExtendedRemoveTrailingWhitespaceOperation extends
      RemoveTrailingWhitespaceOperation {

    // skip empty lines when removing whitespaces
    private boolean fIgnoreEmptyLines;

    public ExtendedRemoveTrailingWhitespaceOperation(
        boolean ignoreEmptyLines) {
      super();
      fIgnoreEmptyLines = ignoreEmptyLines;
    }

    /*
     * Same as in parent, with the addition of the ability to ignore empty
     * lines - depending on the value of fIgnoreEmptyLines
     */
    @Override
    protected MultiTextEditWithProgress computeTextEdit(
        ITextFileBuffer fileBuffer, IProgressMonitor progressMonitor)
        throws CoreException {
      IDocument document = fileBuffer.getDocument();
      int lineCount = document.getNumberOfLines();

      progressMonitor = Progress.getMonitor(progressMonitor);
      progressMonitor
          .beginTask(
              PHPUIMessages.RemoveTrailingWhitespaceOperation_task_generatingChanges,
              lineCount);
      try {

        MultiTextEditWithProgress multiEdit = new MultiTextEditWithProgress(
            PHPUIMessages.RemoveTrailingWhitespaceOperation_task_applyingChanges);

        for (int i = 0; i < lineCount; i++) {
          if (progressMonitor.isCanceled()) {
            throw new OperationCanceledException();
          }
          IRegion region = document.getLineInformation(i);
          if (region.getLength() == 0) {
            continue;
          }
          int lineStart = region.getOffset();
          int lineExclusiveEnd = lineStart + region.getLength();
          int j = lineExclusiveEnd - 1;
          while (j >= lineStart
              && Character.isWhitespace(document.getChar(j))) {
            --j;
          }
          ++j;
          // A flag for skipping empty lines, if required
          if (fIgnoreEmptyLines && j == lineStart) {
            continue;
          }
          if (j < lineExclusiveEnd) {
            multiEdit.addChild(new DeleteEdit(j, lineExclusiveEnd
                - j));
          }
          progressMonitor.worked(1);
        }

        return multiEdit.getChildrenSize() <= 0 ? null : multiEdit;

      } catch (BadLocationException x) {
        throw new CoreException(new Status(IStatus.ERROR,
            PHPUiPlugin.ID,
            IFileBufferStatusCodes.CONTENT_CHANGE_FAILED, "", x)); //$NON-NLS-1$
      } finally {
        progressMonitor.done();
      }
    }
  }

  public ISourceViewer getViewer() {
    return super.getSourceViewer();
  }

  public void update() {
    super.update();
    if (fPHPOutlinePage != null
        && fPHPOutlinePage instanceof ConfigurableContentOutlinePage) {
      ((ConfigurableContentOutlinePage) fPHPOutlinePage)
          .setInput(getModelElement());
    }
  }

  /**
   * Initializes the drag and drop support for the given viewer based on
   * provided editor adapter for drop target listeners.
   *
   * @param viewer
   *            the viewer
   * @since 3.0
   */
  protected void initializeDragAndDrop(ISourceViewer viewer) {
    IDragAndDropService dndService = (IDragAndDropService) getSite()
        .getService(IDragAndDropService.class);
    if (dndService == null)
      return;

    ITextEditorDropTargetListener listener = (ITextEditorDropTargetListener) getAdapter(ITextEditorDropTargetListener.class);

    if (listener == null) {
      Object object = Platform.getAdapterManager().loadAdapter(this,
          "org.eclipse.ui.texteditor.ITextEditorDropTargetListener"); //$NON-NLS-1$
      if (object instanceof ITextEditorDropTargetListener)
        listener = (ITextEditorDropTargetListener) object;
    }

    if (listener != null)
      dndService.addMergedDropTarget(viewer.getTextWidget(),
          DND.DROP_MOVE | DND.DROP_COPY, listener.getTransfers(),
          listener);

    IPreferenceStore store = getPreferenceStore();
    if (store != null
        && store.getBoolean(PREFERENCE_TEXT_DRAG_AND_DROP_ENABLED))
      installTextDragAndDrop(viewer);

  }

  /**
   * Installs text drag and drop on the given source viewer.
   *
   * @param viewer
   *            the viewer
   * @since 3.3
   */
  protected void installTextDragAndDrop(final ISourceViewer viewer) {
    if (viewer == null || fIsTextDragAndDropInstalled)
      return;

    final IDragAndDropService dndService = (IDragAndDropService) getSite()
        .getService(IDragAndDropService.class);
    if (dndService == null)
      return;

    final StyledText st = viewer.getTextWidget();

    // Install drag source
    final ISelectionProvider selectionProvider = viewer
        .getSelectionProvider();
    final DragSource source = new DragSource(st, DND.DROP_COPY
        | DND.DROP_MOVE);
    source.setTransfer(new Transfer[] { TextTransfer.getInstance() });
    source.addDragListener(new DragSourceAdapter() {
      String fSelectedText;
      Point fSelection;

      public void dragStart(DragSourceEvent event) {
        fTextDragAndDropToken = null;
        try {
          fSelection = st.getSelection();
          event.doit = isLocationSelected(new Point(event.x, event.y));

          ISelection selection = selectionProvider.getSelection();
          if (selection instanceof ITextSelection)
            fSelectedText = ((ITextSelection) selection).getText();
          else
            // fallback to widget
            fSelectedText = st.getSelectionText();
        } catch (IllegalArgumentException ex) {
          event.doit = false;
        }
      }

      private boolean isLocationSelected(Point point) {
        // FIXME: https://bugs.eclipse.org/bugs/show_bug.cgi?id=260922
        if (isBlockSelectionModeEnabled())
          return false;

        int offset = st.getOffsetAtLocation(point);
        Point p = st.getLocationAtOffset(offset);
        if (p.x > point.x)
          offset--;
        return offset >= fSelection.x && offset < fSelection.y;
      }

      public void dragSetData(DragSourceEvent event) {
        event.data = fSelectedText;
        fTextDragAndDropToken = this; // Can be any non-null object
      }

      public void dragFinished(DragSourceEvent event) {
        try {
          if (event.detail == DND.DROP_MOVE
              && validateEditorInputState()) {
            Point newSelection = st.getSelection();
            int length = fSelection.y - fSelection.x;
            int delta = 0;
            if (newSelection.x < fSelection.x)
              delta = length;
            st.replaceTextRange(fSelection.x + delta, length, ""); //$NON-NLS-1$

            if (fTextDragAndDropToken == null) {
              // Move in same editor - end compound change
              IRewriteTarget target = (IRewriteTarget) getAdapter(IRewriteTarget.class);
              if (target != null)
                target.endCompoundChange();
            }

          }
        } finally {
          fTextDragAndDropToken = null;
        }
      }
    });

    // Install drag target
    DropTargetListener dropTargetListener = new DropTargetAdapter() {

      private Point fSelection;

      public void dragEnter(DropTargetEvent event) {
        fTextDragAndDropToken = null;
        fSelection = st.getSelection();
        if (event.detail == DND.DROP_DEFAULT) {
          if ((event.operations & DND.DROP_MOVE) != 0) {
            event.detail = DND.DROP_MOVE;
          } else if ((event.operations & DND.DROP_COPY) != 0) {
            event.detail = DND.DROP_COPY;
          } else {
            event.detail = DND.DROP_NONE;
          }
        }
      }

      public void dragOperationChanged(DropTargetEvent event) {
        if (event.detail == DND.DROP_DEFAULT) {
          if ((event.operations & DND.DROP_MOVE) != 0) {
            event.detail = DND.DROP_MOVE;
          } else if ((event.operations & DND.DROP_COPY) != 0) {
            event.detail = DND.DROP_COPY;
          } else {
            event.detail = DND.DROP_NONE;
          }
        }
      }

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

      public void drop(DropTargetEvent event) {
        try {
          if (fTextDragAndDropToken != null
              && event.detail == DND.DROP_MOVE) {
            // Move in same editor
            int caretOffset = st.getCaretOffset();
            if (fSelection.x <= caretOffset
                && caretOffset <= fSelection.y) {
              event.detail = DND.DROP_NONE;
              return;
            }

            // Start compound change
            IRewriteTarget target = (IRewriteTarget) getAdapter(IRewriteTarget.class);
            if (target != null)
              target.beginCompoundChange();
          }

          if (!validateEditorInputState()) {
            event.detail = DND.DROP_NONE;
            return;
          }

          String text = (String) event.data;
          if (isBlockSelectionModeEnabled()) {
            // FIXME fix block selection and DND
            // if (fTextDNDColumnSelection != null &&
            // fTextDragAndDropToken != null && event.detail ==
            // DND.DROP_MOVE) {
            // // DND_MOVE within same editor - remove origin before
            // inserting
            // Rectangle newSelection= st.getColumnSelection();
            //              st.replaceColumnSelection(fTextDNDColumnSelection, ""); //$NON-NLS-1$
            // st.replaceColumnSelection(newSelection, text);
            // st.setColumnSelection(newSelection.x, newSelection.y,
            // newSelection.x + fTextDNDColumnSelection.width -
            // fTextDNDColumnSelection.x, newSelection.y +
            // fTextDNDColumnSelection.height -
            // fTextDNDColumnSelection.y);
            // } else {
            // Point newSelection= st.getSelection();
            // st.insert(text);
            // IDocument document=
            // getDocumentProvider().getDocument(getEditorInput());
            // int startLine= st.getLineAtOffset(newSelection.x);
            // int startColumn= newSelection.x -
            // st.getOffsetAtLine(startLine);
            // int endLine= startLine +
            // document.computeNumberOfLines(text);
            // int endColumn= startColumn +
            // TextUtilities.indexOf(document.getLegalLineDelimiters(),
            // text, 0)[0];
            // st.setColumnSelection(startColumn, startLine,
            // endColumn, endLine);
            // }
          } else {
            Point newSelection = st.getSelection();
            try {
              int modelOffset = widgetOffset2ModelOffset(viewer,
                  newSelection.x);
              viewer.getDocument().replace(modelOffset, 0, text);
            } catch (BadLocationException e) {
              return;
            }
            st.setSelectionRange(newSelection.x, text.length());
          }
        } finally {
          fTextDragAndDropToken = null;
        }
      }
    };
    dndService.addMergedDropTarget(st, DND.DROP_MOVE | DND.DROP_COPY,
        new Transfer[] { TextTransfer.getInstance() },
        dropTargetListener);

    fIsTextDragAndDropInstalled = true;
  }

  /**
   * Uninstalls text drag and drop from the given source viewer.
   *
   * @param viewer
   *            the viewer
   * @since 3.3
   */
  protected void uninstallTextDragAndDrop(ISourceViewer viewer) {
    if (viewer == null || !fIsTextDragAndDropInstalled)
      return;

    final IDragAndDropService dndService = (IDragAndDropService) getSite()
        .getService(IDragAndDropService.class);
    if (dndService == null)
      return;

    StyledText st = viewer.getTextWidget();
    dndService.removeMergedDropTarget(st);

    DragSource dragSource = (DragSource) st.getData(DND.DRAG_SOURCE_KEY);
    if (dragSource != null) {
      dragSource.dispose();
      st.setData(DND.DRAG_SOURCE_KEY, null);
    }

    fIsTextDragAndDropInstalled = false;
  }

  @Override
  public boolean isDirty() {
    // if super.isDirty() return false,it means this
    boolean result = super.isDirty();
    if (!result) {
      return result;
    }
    if (getDocument() instanceof IStructuredDocument) {
      CommandStack commandStack = ((IStructuredDocument) getDocument())
          .getUndoManager().getCommandStack();
      if (commandStack instanceof BasicCommandStack) {
        BasicCommandStack bcs = (BasicCommandStack) commandStack;
        return bcs.isSaveNeeded();
      }
    }
    return result;
  }

  @Override
  public void firePropertyChange(int property) {
    super.firePropertyChange(property);
  }

  protected boolean isTabsToSpacesConversionEnabled() {
    return true;
  }

  protected void installTabsToSpacesConverter() {
    SourceViewerConfiguration config = getSourceViewerConfiguration();
    ISourceViewer sourceViewer = getSourceViewer();
    if (config != null && sourceViewer instanceof ITextViewerExtension7) {
      ((ITextViewerExtension7) sourceViewer)
          .setTabsToSpacesConverter(new TabAutoEditStrategy());
      updateIndentPrefixes();
    }
  }

  /**
   * Installs a tabs to spaces converter.
   *
   * <p>
   * Subclasses may extend or override this method.
   * </p>
   *
   * @since 3.3
   */
  protected void uninstallTabsToSpacesConverter() {
  }

}
TOP

Related Classes of org.eclipse.php.internal.ui.editor.PHPStructuredEditor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.