Package org.epic.perleditor.editors

Source Code of org.epic.perleditor.editors.PerlEditor$PreviousWordAction

package org.epic.perleditor.editors;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.*;
import org.eclipse.jface.action.*;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.*;
import org.eclipse.jface.text.source.*;
import org.eclipse.jface.text.source.projection.*;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.*;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ST;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.*;
import org.eclipse.ui.editors.text.*;
import org.eclipse.ui.internal.ViewerActionBuilder;
import org.eclipse.ui.internal.Workbench;
import org.eclipse.ui.keys.IBindingService;
import org.eclipse.ui.texteditor.*;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import org.epic.core.model.*;
import org.epic.core.util.FileUtilities;
import org.epic.perleditor.PerlEditorPlugin;
import org.epic.perleditor.actions.*;
import org.epic.perleditor.preferences.MarkOccurrencesPreferences;
import org.epic.perleditor.preferences.PreferenceConstants;
import org.epic.perleditor.templates.perl.ModuleCompletionHelper;
import org.epic.perleditor.views.PerlOutlinePage;

/**
* Perl specific text editor.
*/
public class PerlEditor extends TextEditor implements IPropertyChangeListener
{
    /**
     * Editor id, as declared in the plug-in manifest.
     */
    public static final String PERL_EDITOR_ID =
        "org.epic.perleditor.editors.PerlEditor";

    private PerlPairMatcher bracketMatcher;
    private PerlBracketInserter bracketInserter;
    private FoldReconciler foldReconciler;
    private TasksReconciler tasksReconciler;
    private PerlOutlinePage outlinePage;
    private PerlSyntaxValidationThread validationThread;
    private PerlSourceViewer sourceViewer;
    private IdleTimer idleTimer;
    private ProjectionSupport projectionSupport;
    private SourceFile source;

    private OccurrencesUpdater occurrencesUpdater;

    /**
     * Flag used to avoid relocating caret in response to an outline selection
     * change triggered by a previous caret move.
     */
    private boolean syncToOutline;

    /**
     * Flag used to avoid updating outline selection in response to a caret
     * move caused by a previous outline selection update.
     */
    private boolean syncFromOutline;

    public PerlEditor()
    {
        setDocumentProvider(PerlEditorPlugin.getDefault().getDocumentProvider());
        PerlEditorPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(this);
        setKeyBindingScopes(new String[] { "org.epic.perleditor.perlEditorScope" });
        setEditorContextMenuId("#PerlEditorContext"); //$NON-NLS-1$
        setRulerContextMenuId("#PerlRulerContext"); //$NON-NLS-1$
    }

    public void createPartControl(Composite parent)
    {
        // Workaround for Eclipse Bug 75440 (to fix it somehow) [LeO]
        if (Workbench.getInstance().isClosing()) return;

        super.createPartControl(parent);

        installProjectionSupport();
        installBracketInserter();
        installCaretMoveListener();
        installModuleCompletionHelper();
        installIdleTimer();
        installSyntaxValidationThread();
        installFoldReconciler();
        installTasksReconciler();
        installAnnotationListener();

        source = new SourceFile(
            PerlEditorPlugin.getDefault().getLog(),
            getViewer().getDocument());

        reconcile();
    }

    /**
     * The PerlEditor implementation of this AbstractTextEditor method performs
     * any extra disposal actions required by the Perl editor.
     */
    public void dispose()
    {
        PerlEditorPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(this);

        uninstallAnnotationListener();

        try
        {
            if (sourceViewer instanceof ITextViewerExtension &&
                bracketInserter != null)
            {
                ((ITextViewerExtension) sourceViewer).removeVerifyKeyListener(
                    bracketInserter);
            }
            if (validationThread != null) validationThread.dispose();
            if (idleTimer != null) idleTimer.dispose();

            String[] actionIds = PerlEditorActionIds.getEditorActions();
            for (int i = 0; i < actionIds.length; i++)
            {
                IAction action = getAction(actionIds[i]);
                if (action instanceof PerlEditorAction)
                    ((PerlEditorAction) action).dispose();
            }

            super.dispose();
        }
        catch (InterruptedException e)
        {
            e.printStackTrace(); // TODO log it
        }
    }

    /**
     * The PerlEditor implementation of this AbstractTextEditor method performs
     * any extra revert behavior required by the Perl editor.
     */
    public void doRevertToSaved()
    {
        super.doRevertToSaved();
        revalidateSyntax();
    }

    /**
     * The PerlEditor implementation of this AbstractTextEditor method performs
     * any extra save behavior required by the Perl editor.
     */
    public void doSave(IProgressMonitor monitor)
    {
        super.doSave(monitor);
        revalidateSyntax();
    }

    /**
     * The PerlEditor implementation of this AbstractTextEditor method performs
     * any extra save as behavior required by the Perl editor.
     */
    public void doSaveAs()
    {
        super.doSaveAs();
        revalidateSyntax();
    }

    /**
     * The PerlEditor implementation of this AbstractTextEditor method performs
     * sets the input of the outline page after AbstractTextEditor has set
     * input.
     */
    public void doSetInput(IEditorInput input)
        throws CoreException
    {
        // Map external files into workspace (epic-links)
        if (input instanceof ILocationProvider)
        {
            ILocationProvider l = (ILocationProvider)
                input.getAdapter(ILocationProvider.class);

            input = FileUtilities.getFileEditorInput(l.getPath(l).makeAbsolute());
            if (input == null) throw new CoreException(new Status(
                IStatus.ERROR,
                PerlEditorPlugin.getPluginId(),
                IStatus.OK,
                "Could not open requested file " + l.getPath(l).makeAbsolute() +
                ". Inspect the Error Log for additional information.",
                null));
        }

        super.doSetInput(input);

        if (getViewer() != null)
        {
            // The editor is being reused (e.g. when the user clicks on matches
            // found through a search). Make sure we synchronize with the new content.

            source = new SourceFile(
                PerlEditorPlugin.getDefault().getLog(),
                getViewer().getDocument());

            reconcile();
        }
    }

    /**
     * Provided that the given document contains a bracket-like
     * character at the given offset, returns the offset of
     * the matching (pair) character (if found). Otherwise, returns -1.
     *
     * @param exact
     *        if false, search for the match within currently displayed text only;
     *        if true, search in the entire document
     */
    public int findMatchingBracket(
        final IDocument document,
        final int offset,
        boolean exact)
    {
        if (exact) bracketMatcher.setViewer(null);
        try
        {
            final int[] ret = new int[1];

            IRegion matchRegion = bracketMatcher.match(document, offset);

            if (matchRegion == null) ret[0] = -1;
            else ret[0] =
                matchRegion.getOffset() == offset - 1
                ? matchRegion.getOffset() + matchRegion.getLength() - 1
                : matchRegion.getOffset();

            return ret[0];
        }
        finally
        {
            if (exact) bracketMatcher.setViewer(sourceViewer);
        }
    }

    /**
     * The PerlEditor implementation of this AbstractTextEditor method performs
     * gets the Perl content outline page if request is for a an outline page.
     */
    public Object getAdapter(Class adapter)
    {
        if (ProjectionAnnotationModel.class.equals(adapter))
        {
            if (this.projectionSupport != null)
            {
                Object result = this.projectionSupport.getAdapter(
                    sourceViewer, adapter);
                if (result != null)
                {
                    return result;
                }
            }
        }

        if (adapter.equals(IContentOutlinePage.class))
        {
            IEditorInput input = getEditorInput();

            if (input instanceof IFileEditorInput)
            {
                if (outlinePage == null)
                {
                    outlinePage = new PerlOutlinePage(source);
                    outlinePage.addSelectionChangedListener(new OutlineSelectionListener());
                }
                return outlinePage;
            }
        }

        return super.getAdapter(adapter);
    }

    /**
     * Returns the Idle Timer associated with the editor
     *
     * @return Idle Timer
     */
    public IdleTimer getIdleTimer()
    {
        return idleTimer;
    }

    /**
     * @return the SourceFile edited in this editor
     */
    public SourceFile getSourceFile()
    {
        return source;
    }

    /**
     * @return test interface to this editor's internals;
     *         do not use outside of white-box test cases!
     */
    public TestInterface getTestInterface()
    {
        return new TestInterface();
    }

    /**
     * @return the source viewer used by this editor
     */
    public ISourceViewer getViewer()
    {
        return super.getSourceViewer();
    }

    /**
     * Provided that the caret's current position is after a bracket-like
     * character, jumps to its matching character (if found). Otherwise,
     * this method has no effect.
     *
     * @see PerlPairMatcher
     */
    public void jumpToMatchingBracket()
    {
        int caretOffset = sourceViewer.getSelectedRange().x;
        int matchOffset =
            findMatchingBracket(sourceViewer.getDocument(), caretOffset, true);

        if (matchOffset == -1) return;

        sourceViewer.revealRange(matchOffset + 1, 1);
        sourceViewer.setSelectedRange(matchOffset + 1, 0);
    }

    public void propertyChange(PropertyChangeEvent event)
    {
        if (event.getProperty().equals("PERL_EXECUTABLE"))
        {
            return;
        }

        // Check if automatic syntax validation has to be enabled or disabled
        if (event.getProperty().equals(
            PreferenceConstants.EDITOR_SYNTAX_VALIDATION))
        {
            if (PerlEditorPlugin.getDefault().getBooleanPreference(
                PreferenceConstants.EDITOR_SYNTAX_VALIDATION))
            {
                if (!idleTimer.isRegistered(validationThread))
                {
                    this.registerIdleListener(validationThread);
                }
            }
            else
            {
                if (idleTimer.isRegistered(validationThread))
                {
                    idleTimer.removeListener(validationThread);
                }
            }
        }
    }

    /**
     * Updates the editor's dependent views and state after a document change.
     * This method is only intended for use by {@link PerlReconcilingStrategy}.
     */
    public void reconcile()
    {
        // the problem is, we might be called after the ISourceViewer
        // has been disposed; this occurs BEFORE dispose() is invoked
        // on the PerlEditor, so there seems to be no good way to
        // synchronise properly
        if (sourceViewer == null) return;
        StyledText widget = sourceViewer.getTextWidget();
        if (widget == null || widget.isDisposed()) return;
        Display display = widget.getDisplay();
        if (display == null) return;
        final IDocument doc = sourceViewer.getDocument();
        if (doc == null) return;

        // We reconcile on the main (Display) thread in order to avoid
        // race conditions due to user's modifications; this also means
        // that the reconciling has to be FAST in order to keep the GUI
        // responsive.
        //
        // An alternate (and better) solution would be to use a better
        // implementation (wrapper?) of IDocument, which supports multithreaded
        // access.
        //
        display.syncExec(new Runnable() {
            public void run()
            {
                source.parse();

                if (outlinePage != null) outlinePage.updateContent(source);
                if (foldReconciler != null) foldReconciler.reconcile();
                if (tasksReconciler != null) tasksReconciler.reconcile();
            } });
    }

    public void refreshTaskView()
    {
        tasksReconciler.reconcile();
    }

    public void registerIdleListener(IdleTimerListener obj)
    {
        idleTimer.addListener(obj);
    }

    /**
     * Immediately revalidates syntax of the edited file.
     * This method has no effect if the syntax validation thread is not installed.
     */
    public void revalidateSyntax()
    {
        if (validationThread != null) validationThread.revalidate();
    }

    protected boolean affectsTextPresentation(PropertyChangeEvent event)
    {
        // TODO examine event.getProperty() to tell whether presentation
        // is affected; see also reconfigureSourceViewer which might be
        // called too often on preference changes
        return true;
    }

    protected void configureSourceViewerDecorationSupport(SourceViewerDecorationSupport support)
    {
        support.setCharacterPairMatcher(bracketMatcher);
        support.setMatchingCharacterPainterPreferenceKeys(
            PreferenceConstants.EDITOR_MATCHING_BRACKETS,
            PreferenceConstants.EDITOR_MATCHING_BRACKETS_COLOR);

        super.configureSourceViewerDecorationSupport(support);
    }

    /**
     * The PerlEditor implementation of this AbstractTextEditor method extend
     * the actions to add those specific to the receiver
     */
    protected void createActions()
    {
        super.createActions();

        wireAction(
            new ContentAssistAction(
                PerlEditorMessages.getResourceBundle(),
                "ContentAssistProposal.",
                this),
            PerlEditorCommandIds.CONTENT_ASSIST,
            PerlEditorActionIds.CONTENT_ASSIST);

        // each marker clearing action gets its own wiring
        wireAction(new ClearMarkerAction.Critic(this), PerlEditorCommandIds.CLEAR_MARKER,
            PerlEditorActionIds.CLEAR_CRITIC_MARKERS);
        wireAction(new ClearMarkerAction.PodChecker(this), PerlEditorCommandIds.CLEAR_MARKER,
            PerlEditorActionIds.CLEAR_POD_MARKERS);
        wireAction(new ClearMarkerAction.AllMarkers(this), PerlEditorCommandIds.CLEAR_MARKER,
            PerlEditorActionIds.CLEAR_ALL_MARKERS);

        wireAction(new PodCheckerAction(this), PerlEditorCommandIds.POD_CHECKER,
            PerlEditorActionIds.POD_CHECKER);
        wireAction(new ToggleCommentAction(this), PerlEditorCommandIds.TOGGLE_COMMENT,
            PerlEditorActionIds.TOGGLE_COMMENT);
        wireAction(new PerlCriticAction(this), PerlEditorCommandIds.CRITIQUE_SOURCE,
            PerlEditorActionIds.PERL_CRITIC);
        wireAction(new FormatSourceAction(this), PerlEditorCommandIds.FORMAT_SOURCE,
            PerlEditorActionIds.FORMAT_SOURCE);
        wireAction(new Jump2BracketAction(this), PerlEditorCommandIds.MATCHING_BRACKET,
            PerlEditorActionIds.MATCHING_BRACKET);
        wireAction(new ExportHtmlSourceAction(this), PerlEditorCommandIds.HTML_EXPORT,
            PerlEditorActionIds.HTML_EXPORT);
        wireAction(new ValidateSourceAction(this), PerlEditorCommandIds.VALIDATE_SYNTAX,
            PerlEditorActionIds.VALIDATE_SYNTAX);
        wireAction(new OpenDeclarationAction(this), PerlEditorCommandIds.OPEN_DECLARATION,
            PerlEditorActionIds.OPEN_DECLARATION);
        wireAction(new PerlDocAction(this), PerlEditorCommandIds.PERL_DOC,
            PerlEditorActionIds.PERL_DOC);
        wireAction(new ExtractSubroutineAction(this), PerlEditorCommandIds.EXTRACT_SUBROUTINE,
            PerlEditorActionIds.EXTRACT_SUBROUTINE);
    }

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

        IAction action;
        StyledText textWidget = getSourceViewer().getTextWidget();

        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 NextWordAction(ST.WORD_NEXT, false, false);
        action.setActionDefinitionId(ITextEditorActionDefinitionIds.WORD_NEXT);
        setAction(ITextEditorActionDefinitionIds.WORD_NEXT, action);
        textWidget.setKeyBinding(SWT.MOD1 | SWT.ARROW_RIGHT, SWT.NULL);

        action = new NextWordAction(ST.SELECT_WORD_NEXT, true, false);
        action.setActionDefinitionId(ITextEditorActionDefinitionIds.SELECT_WORD_NEXT);
        setAction(ITextEditorActionDefinitionIds.SELECT_WORD_NEXT, action);
        textWidget.setKeyBinding(SWT.MOD1 | SWT.MOD2 | SWT.ARROW_RIGHT, SWT.NULL);

        action = new NextWordAction(ST.DELETE_WORD_NEXT, false, true);
        action.setActionDefinitionId(ITextEditorActionDefinitionIds.DELETE_NEXT_WORD);
        setAction(ITextEditorActionDefinitionIds.DELETE_NEXT_WORD, action);
        textWidget.setKeyBinding(SWT.MOD1 | SWT.DEL, SWT.NULL);
       
        action = new PreviousWordAction(ST.WORD_PREVIOUS, false, false);
        action.setActionDefinitionId(ITextEditorActionDefinitionIds.WORD_PREVIOUS);
        setAction(ITextEditorActionDefinitionIds.WORD_PREVIOUS, action);
        textWidget.setKeyBinding(SWT.MOD1 | SWT.ARROW_LEFT, SWT.NULL);

        action = new PreviousWordAction(ST.SELECT_WORD_PREVIOUS, true, false);
        action.setActionDefinitionId(ITextEditorActionDefinitionIds.SELECT_WORD_PREVIOUS);
        setAction(ITextEditorActionDefinitionIds.SELECT_WORD_PREVIOUS, action);
        textWidget.setKeyBinding(SWT.MOD1 | SWT.MOD2 | SWT.ARROW_LEFT, SWT.NULL);
       
        action = new PreviousWordAction(ST.DELETE_WORD_PREVIOUS, false, true);
        action.setActionDefinitionId(ITextEditorActionDefinitionIds.DELETE_PREVIOUS_WORD);
        setAction(ITextEditorActionDefinitionIds.DELETE_PREVIOUS_WORD, action);
        textWidget.setKeyBinding(SWT.MOD1 | SWT.BS, SWT.NULL);
       
        // Only unbind default backspace action from textWidget if there is a key binding
        // for "Delete Previous" registered through Preferences > Keys. Otherwise we'd lose
        // backspace functionality:

        IBindingService service = (IBindingService) getSite().getService(IBindingService.class);
        if (service.getActiveBindingsFor(ITextEditorActionDefinitionIds.DELETE_PREVIOUS).length > 0)
        {
            action = new BackspaceAction(ST.DELETE_PREVIOUS);
            action.setActionDefinitionId(ITextEditorActionDefinitionIds.DELETE_PREVIOUS);
            setAction(ITextEditorActionDefinitionIds.DELETE_PREVIOUS, action);
            textWidget.setKeyBinding(SWT.BS, SWT.NULL);
        }
    }

    /* Create SourceViewer so we can use the PerlSourceViewer class */
    protected final ISourceViewer createSourceViewer(
        Composite parent,
        IVerticalRuler ruler,
        int styles)
    {
        fAnnotationAccess = createAnnotationAccess();
        fOverviewRuler = createOverviewRuler(getSharedColors());
        sourceViewer = new PerlSourceViewer(
            parent,
            ruler,
            fOverviewRuler,
            isOverviewRulerVisible(),
            styles);

        // ensure source viewer decoration support has been created and
        // configured
        installBracketMatcher();
        getSourceViewerDecorationSupport(sourceViewer);
       
        sourceViewer.getTextWidget().setWordWrap(
            getPreferenceStore().getBoolean(
                PreferenceConstants.EDITOR_LINE_WRAP));

        return sourceViewer;
    }

    protected void handlePreferenceStoreChanged(PropertyChangeEvent event)
    {
        if (sourceViewer == null ||
            sourceViewer.getTextWidget() == null ||
            bracketInserter == null) return;

        try
        {
            if (event.getProperty().equals(MarkOccurrencesPreferences.MARK_OCCURRENCES))
            {
                boolean oldValue = event.getOldValue() != null
                    ? Boolean.valueOf(event.getOldValue().toString()).booleanValue()
                    : false;

                boolean newValue = event.getOldValue() != null
                    ? Boolean.valueOf(event.getNewValue().toString()).booleanValue()
                    : false;

                if (newValue != oldValue)
                {
                    if (newValue) installAnnotationListener();
                    else uninstallAnnotationListener();
                }
            }
            else if (event.getProperty().equals(PreferenceConstants.EDITOR_LINE_WRAP))
            {
                boolean oldValue = event.getOldValue() != null
                    ? Boolean.valueOf(event.getOldValue().toString()).booleanValue()
                    : false;

                boolean newValue = event.getOldValue() != null
                    ? Boolean.valueOf(event.getNewValue().toString()).booleanValue()
                    : false;

                if (newValue != oldValue)
                    sourceViewer.getTextWidget().setWordWrap(newValue);
            }
            else if (event.getProperty().equals(PreferenceConstants.EDITOR_FOLD_COLUMN_BG_COLOR))
            {
                sourceViewer.updateFoldColumnBackgroundColor(true);
            }
            else
            {
                reconfigureBracketInserter();
            }
        }
        finally
        {
           super.handlePreferenceStoreChanged(event);
        }
    }

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

        // Make general workbench editor preferences (such as QuickDiff)
        // available through our preference store
        setPreferenceStore(new ChainedPreferenceStore(new IPreferenceStore[] {
            EditorsUI.getPreferenceStore(),
            PerlEditorPlugin.getDefault().getPreferenceStore(),
            this.getPreferenceStore()      
            }));

        setSourceViewerConfiguration(new PerlSourceViewerConfiguration(getPreferenceStore(), this));
    }

    private void caretMoved()
    {
        if (!getPreferenceStore().getBoolean(
            PreferenceConstants.EDITOR_SYNC_OUTLINE_ON_CURSOR_MOVE)) return;
        if (syncFromOutline) { syncFromOutline = false; return; }
        if (outlinePage == null || source == null || source.getDocument() == null) return;

        try
        {
            syncToOutline = true;

            int caretOffset = sourceViewer.getSelectedRange().x;
            int caretLine = source.getDocument().getLineOfOffset(caretOffset);

            outlinePage.updateSelection(caretLine);
        }
        catch (BadLocationException e)
        {
            e.printStackTrace(); // TODO log it
        }
        finally
        {
            syncToOutline = false;
        }
    }

    /**
     * @return the edited workspace resource or null if the editor's input
     *         is not a workspace resource (example: remote files opened
     *         while browsing CVS are not resources)
     */
    private IResource getResource()
    {
        return (IResource)
            ((IAdaptable) getEditorInput()).getAdapter(IResource.class);
    }

    private void installBracketInserter()
    {
        bracketInserter =
            new PerlBracketInserter(PerlEditorPlugin.getDefault().getLog());

        reconfigureBracketInserter();

        if (sourceViewer instanceof ITextViewerExtension)
            ((ITextViewerExtension) sourceViewer).prependVerifyKeyListener(
                bracketInserter);

        bracketInserter.setViewer(sourceViewer);
    }

    private void installBracketMatcher()
    {
        bracketMatcher = new PerlPairMatcher(PerlEditorPlugin.getDefault().getLog());
        bracketMatcher.setViewer(sourceViewer);
    }

    private void installCaretMoveListener()
    {
        new CaretMoveListener().install(getSelectionProvider());
    }

    private void installAnnotationListener()
    {
        if (occurrencesUpdater == null)
        {
            occurrencesUpdater = new OccurrencesUpdater();
        }

        occurrencesUpdater.install(getSourceViewer());
    }

    private void uninstallAnnotationListener()
    {
        if (occurrencesUpdater == null) return;

        occurrencesUpdater.uninstall();
    }

    private void installFoldReconciler()
    {
        foldReconciler = new FoldReconciler(this);
    }

    private void installIdleTimer()
    {
        idleTimer = new IdleTimer(sourceViewer, Display.getCurrent());
        idleTimer.start();
    }

    private void installModuleCompletionHelper()
    {
        IResource resource = getResource();
        if (resource == null) return;
       
        // load the module completion list in a low-priority background thread
      Thread backgroundLoader = new Thread(new Runnable() {
      public void run() {
        try {
              ModuleCompletionHelper completionHelper =
                  ModuleCompletionHelper.getInstance();
          completionHelper.scanForModules(PerlEditor.this);
        }
        catch (CoreException e)
        {
          PerlEditorPlugin.getDefault().getLog().log(e.getStatus());
        }
      } },
            "EPIC:ModuleCompletionHelper");
      backgroundLoader.setPriority(Thread.MIN_PRIORITY);
      backgroundLoader.start();
    }

    private void installProjectionSupport()
    {
        ProjectionViewer viewer = (ProjectionViewer) sourceViewer;

        projectionSupport = new ProjectionSupport(
            viewer, getAnnotationAccess(), getSharedColors());
        projectionSupport.addSummarizableAnnotationType("org.eclipse.ui.workbench.texteditor.error");
        projectionSupport.addSummarizableAnnotationType("org.eclipse.ui.workbench.texteditor.warning");
        projectionSupport.install();

        viewer.doOperation(ProjectionViewer.TOGGLE);
    }

    private void installSyntaxValidationThread()
    {
        IResource resource = getResource();
        if (resource == null) return;
       
        // Always check syntax when editor is opened
        validationThread = new PerlSyntaxValidationThread();
        validationThread.setPriority(Thread.MIN_PRIORITY);
        validationThread.start();
        validationThread.setDocument(
            resource,
            sourceViewer.getDocument());

        // Register the validation thread if automatic checking is enabled
        if (PerlEditorPlugin.getDefault().getBooleanPreference(
            PreferenceConstants.EDITOR_SYNTAX_VALIDATION) &&
            idleTimer != null)
        {
            registerIdleListener(validationThread);
        }
    }

    private void installTasksReconciler()
    {
        IResource resource = getResource();
        if (resource == null) return;
       
        tasksReconciler = new TasksReconciler(this);
    }

    private void reconfigureBracketInserter()
    {
        IPreferenceStore preferenceStore = getPreferenceStore();

        bracketInserter.setCloseBracketsEnabled(
            preferenceStore.getBoolean(PreferenceConstants.AUTO_COMPLETION_BRACKET1));
        bracketInserter.setCloseBracesEnabled(
            preferenceStore.getBoolean(PreferenceConstants.AUTO_COMPLETION_BRACKET2));
        bracketInserter.setCloseParensEnabled(
            preferenceStore.getBoolean(PreferenceConstants.AUTO_COMPLETION_BRACKET3));
        bracketInserter.setCloseAngularBracketsEnabled(
            preferenceStore.getBoolean(PreferenceConstants.AUTO_COMPLETION_BRACKET4));
        bracketInserter.setCloseDoubleQuotesEnabled(
            preferenceStore.getBoolean(PreferenceConstants.AUTO_COMPLETION_QUOTE1));
        bracketInserter.setCloseSingleQuotesEnabled(
            preferenceStore.getBoolean(PreferenceConstants.AUTO_COMPLETION_QUOTE2));
    }

    private void wireAction(IAction action, String commandId, String perlActionId)
    {
        action.setActionDefinitionId(commandId);
        setAction(perlActionId, action);
    }

    /**
     * Contains methods that provide access to internal workings of PerlEditor
     * intended to be available only to white-box test cases. Other clients
     * should not make use of this interface.
     */
    public class TestInterface
    {
        public void clear()
        {
            sourceViewer.getTextWidget().setText("");
        }

        public int getHighlightedBracketOffset()
        {
            int offset = sourceViewer.getTextWidget().getCaretOffset();

            PerlPairMatcher matcher =
                new PerlPairMatcher(PerlEditorPlugin.getDefault().getLog());

            matcher.setViewer(null);
            matcher.match(sourceViewer.getDocument(), offset);

            return
                offset - 1 == matcher.getStartPos()
                ? matcher.getEndPos()
                : matcher.getStartPos();
        }

        public String getText()
        {
            return sourceViewer.getTextWidget().getText();
        }

        public IVerticalRuler getVerticalRuler()
        {
            return ((PerlSourceViewer) sourceViewer)._getVerticalRuler();
        }

        public void setCaretOffset(final int offset)
        {
            Display display = sourceViewer.getTextWidget().getDisplay();
            sourceViewer.setSelectedRange(offset, 0);
            while (display.readAndDispatch());
        }

        public void setExactBracketMatching()
        {
            bracketMatcher.setViewer(null);
        }

        public void setTopIndex(int topIndex)
        {
            sourceViewer.setTopIndex(topIndex);
        }

        public void selectText(String text)
        {
            if (text.length() == 0)
            {
                getSelectionProvider().setSelection(TextSelection.emptySelection());
            }
            else
            {
                int i = sourceViewer.getDocument().get().indexOf(text);
                if (i == -1) throw new RuntimeException(
                    "text \"" + text + "\" not found in editor");
                getSelectionProvider().setSelection(new TextSelection(i, text.length()));
            }
        }
    }

    /**
     * Tracks caret movements in order to update selection in the outline page.
     * Implementation borrowed from org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.
     */
    private class CaretMoveListener 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);
            }
        }

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

        /**
         * 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);
            }
        }
    }

    /**
     * Tracks selection in the outline page in order to highlight subroutine
     * declarations in source code.
     */
    private class OutlineSelectionListener implements ISelectionChangedListener
    {
        public void selectionChanged(SelectionChangedEvent event)
        {
            if (syncToOutline) return;
            if (event == null) return;
            if (!(event.getSelection() instanceof IStructuredSelection)) return;

            IStructuredSelection sel = (IStructuredSelection) event.getSelection();
            if (!(sel.getFirstElement() instanceof ISourceElement)) return;

            ISourceElement elem = (ISourceElement) sel.getFirstElement();
            syncFromOutline = true;
            selectAndReveal(elem.getOffset(), elem.getName().length());
        }
    }
   
    /**
     * Simulate vim-like backspacing over indents (delete whole indent rather than one character).
     * This feature is only enabled when:
     * 1. The preference "insert spaces instead of tabs" is enabled and
     * 2. There is a (user-entered) key binding for "Delete Previous" action in Preferences > Keys.
     *
     * NOTE: in all other cases BackspaceAction is not even installed,
     * and we stay backwards-compatible with standard Eclipse (which leaves all
     * the handling of backspace to SWT's StyledText).
     */
    private class BackspaceAction extends TextNavigationAction
    {
        public BackspaceAction(int code)
        {
            super(getSourceViewer().getTextWidget(), code);
        }
       
        public void run()
        {
            final IPreferenceStore store = getPreferenceStore();
            if (!store.getBoolean(PreferenceConstants.SPACES_INSTEAD_OF_TABS))
            {
                super.run();
                return;
            }
           
            ISourceViewer viewer = getSourceViewer();
            StyledText text = viewer.getTextWidget();
            int caretOffset = text.getCaretOffset();
            if (caretOffset == 0) // start of document, nothing to delete
            {
                super.run();
                return;
            }
           
            String charBeforeCaret = text.getText(caretOffset-1, caretOffset-1);
            if (charBeforeCaret.charAt(0) != ' ') // not after indent
            {
                super.run();
                return;
            }
           
            Point selection = text.getSelection();
            if (selection.x != selection.y) // some text is selected
            {
                super.run();
                return;
            }
           
            IDocument document = viewer.getDocument();
            int position = widgetOffset2ModelOffset(viewer, caretOffset);
            if (position == -1) // ?
            {
                super.run();
                return;
            }
           
            try
            {
                int line = document.getLineOfOffset(position);
                int lineOffset = document.getLineOffset(line);
               
                String prefix = viewer.getDocument().get(lineOffset, position-lineOffset);
                if (prefix.length() > 0)
                {
                    for (int i = 0; i < prefix.length(); i++)
                    {
                        if (prefix.charAt(i) != ' ')
                        {
                            super.run();
                            return;
                        }
                    }
                   
                    int tabWidth = store.getInt(PreferenceConstants.INSERT_TABS_ON_INDENT);
                    if (prefix.length() % tabWidth == 0)
                    {
                        viewer.getDocument().replace(position-tabWidth, tabWidth, ""); //$NON-NLS-1$
                    }
                    else
                    {
                        super.run();
                        return;
                    }
                }
                else
                {
                    super.run();
                    return;
                }
            }
            catch (BadLocationException e) // should never occur
            {
                PerlEditorPlugin.getDefault().getLog().log(
                    new Status(
                        IStatus.ERROR,
                        PerlEditorPlugin.getPluginId(),
                        IStatus.OK,
                        "An unexpected exception occurred in BackspaceAction",
                        e));

                super.run(); // fall back on default behavior
            }
        }
    }

    /**
     * This action implements smart home.
     *
     * 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; beginning of
     *   a comment ('#') counts as whitespace
     * - 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.
     */
    private class SmartLineStartAction extends LineStartAction
    {
        /**
         * @param textWidget the editor's 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(StyledText textWidget, boolean doSelect)
        {
            super(textWidget, doSelect);
        }

        protected int getLineStartPosition(
            final IDocument document,
            final String line,
            final int length,
            final int offset)
        {
            int index = super.getLineStartPosition(document, line, length, offset);

            if (index < length - 1 && line.charAt(index) == '#')
            {
                index++;
                while (index < length && Character.isWhitespace(line.charAt(index)))
                    index++;
            }
            return index;
        }
    }

    /**
     * Base class for actions that navigate to or select text up to the next
     * word boundary.
     */
    protected abstract class WordNavigationAction extends TextNavigationAction
    {
        private boolean select;
        private boolean delete;

        protected WordNavigationAction(int code, boolean select, boolean delete)
        {
            super(getSourceViewer().getTextWidget(), code);
            this.select = select;
            this.delete = delete;
        }

        public final void run()
        {
            final IPreferenceStore store = getPreferenceStore();
            if (!store.getBoolean(PreferenceConstants.EDITOR_SUB_WORD_NAVIGATION))
            {
                super.run();
                return;
            }

            ISourceViewer viewer = getSourceViewer();
            IDocument document = viewer.getDocument();
            int position = widgetOffset2ModelOffset(
                viewer, viewer.getTextWidget().getCaretOffset());

            if (position == -1) return;

            PerlPartitioner partitioner =
                (PerlPartitioner) PartitionTypes.getPerlPartitioner(document);
            int docLength = document.getLength();

            run(viewer, document, position, partitioner, docLength);
        }

        protected abstract void run(
            ISourceViewer viewer,
            IDocument document,
            int position,
            PerlPartitioner partitioner,
            int docLength);

        protected final boolean isWordSeparator(char c)
        {
            return
                !Character.isLetterOrDigit(c) &&
                !Character.isWhitespace(c) &&
                c != '_' && c != '&' && c != '$' && c != '@' && c != '%';
        }
       
        protected final void setCaretPosition(final int modelOffset)
        {  
            final ISourceViewer viewer = getSourceViewer();
            final StyledText text = viewer.getTextWidget();
           
            if (text == null || text.isDisposed()) return;
           
            if (select)
            {
                Point selection = text.getSelection();               
                int oldCaret = text.getCaretOffset();
                int newCaret = modelOffset2WidgetOffset(viewer, modelOffset);
                int anchor, mod;
               
                if (oldCaret == selection.x)
                {
                    anchor = selection.y;
                    mod = selection.x;
                }
                else
                {
                    anchor = selection.x;
                    mod = selection.y;
                }
                if (newCaret < oldCaret) mod -= oldCaret-newCaret;
                else mod += newCaret-oldCaret;

                text.setSelection(anchor, mod);
            }
            else if (delete)
            {
                int oldOffset = widgetOffset2ModelOffset(
                    viewer, text.getCaretOffset());
                int start = oldOffset > modelOffset ? modelOffset : oldOffset;
                int length = Math.abs(modelOffset - oldOffset);
               
                try
                {
                    viewer.getDocument().replace(start, length, ""); //$NON-NLS-1$
                }
                catch (BadLocationException exception)
                {
                    // Should not happen
                }
            }
            else // just move the caret
            {
                text.setCaretOffset(
                    modelOffset2WidgetOffset(viewer, modelOffset));
            }
        }
    }

    /**
     * Navigates or selects/deletes text up to the next word boundary.
     */
    private final class NextWordAction extends WordNavigationAction
    {
        /**
         * @param code
         *        Action code for the default operation.
         *        Must be an action code from {@link org.eclipse.swt.custom.ST}.
         */
        protected NextWordAction(int code, boolean select, boolean delete)
        {
            super(code, select, delete);
        }

        protected void run(
            ISourceViewer viewer,
            IDocument document,
            int position,
            PerlPartitioner partitioner,
            int length)
        {
            try
            {
                int line = document.getLineOfOffset(position);
                if (position < length &&
                    (document.getChar(position) == '\n' ||
                     document.getChar(position) == '\r'))
                {
                    // Make "next word" action at the end of a line go to the begining of
                    // the next line
                    if (line < document.getNumberOfLines())
                        position = document.getLineOffset(line+1);
                    else return;
                }
                else
                {
                    ITypedRegion partition =
                        partitioner.getPartition(position, true);

                    int partitionEnd = partition.getOffset() + partition.getLength();

                    if (position == partitionEnd && position < length)
                    {
                        partition = partitioner.getPartition(position+1, true);
                        partitionEnd = partition.getOffset() + partition.getLength();
                    }

                    // If we start in front of a word separator character,
                    // just skip over it                   
                    int tmp = position;
                    while (position < partitionEnd &&
                           isWordSeparator(document.getChar(position))) position++;

                    if (position == tmp)
                    {
                        // If we get here, we did not start in front of
                        // a word separator. Then we skip until the next
                        // word separator or whitespace, and also over
                        // the whitespace
                       
                        while (position < partitionEnd &&
                               !Character.isWhitespace(document.getChar(position)) &&
                               !isWordSeparator(document.getChar(position))) position++;
                       
                        while (position < length &&
                            Character.isWhitespace(document.getChar(position)) &&
                            document.getChar(position) != '\n' &&
                            document.getChar(position) != '\r') position++;
                    }
                }

                setCaretPosition(position);
                getTextWidget().showSelection();
                fireSelectionChanged();
            }
            catch (BadLocationException e) // should never occur
            {
                PerlEditorPlugin.getDefault().getLog().log(
                    new Status(
                        IStatus.ERROR,
                        PerlEditorPlugin.getPluginId(),
                        IStatus.OK,
                        "An unexpected exception occurred in NextWordAction",
                        e));

                super.run(); // fall back on default behavior
            }
        }
    }

    /**
     * Navigates or selects/deletes text up to the previous word boundary.
     */
    private final class PreviousWordAction extends WordNavigationAction
    {
        /**
         * @param code
         *        Action code for the default operation.
         *        Must be an action code from {@link org.eclipse.swt.custom.ST}.
         */
        protected PreviousWordAction(int code, boolean select, boolean delete)
        {
            super(code, select, delete);
        }

        protected void run(
            ISourceViewer viewer,
            IDocument document,
            int position,
            PerlPartitioner partitioner,
            int length)
        {
            try
            {
                int line = document.getLineOfOffset(position);
                if (document.getLineOffset(line) == position)
                {
                    // Make "previous word" action at the beginning of a line go to the end of
                    // the previous line
                    if (line > 0)
                    {
                        int prevLineOffset = document.getLineOffset(line-1);
                        position = prevLineOffset + document.getLineLength(line-1);
                        while (
                            position > prevLineOffset &&
                            (document.getChar(position-1) == '\n' ||
                            document.getChar(position-1) == '\r')) position--;
                    }
                    else return;
                }
                else
                {
                    ITypedRegion partition =
                        partitioner.getPartition(position, false);

                    int partitionStart = partition.getOffset();

                    if (position == partitionStart && position > 0)
                    {
                        partition = partitioner.getPartition(position-1, false);
                        partitionStart = partition.getOffset();
                    }

                    // If we start after a word separator character,
                    // just skip over it                   
                    int tmp = position;
                    while (position > partitionStart &&
                           isWordSeparator(document.getChar(position-1))) position--;
                   
                    if (position == tmp)
                    {
                        // If we get here, we did not start after
                        // a word separator. Then we skip the whitespace
                        // (if present) and also the previous word
                        // until whitespace or word separator
                       
                        while (position > partitionStart &&
                            Character.isWhitespace(document.getChar(position-1)) &&
                            document.getChar(position-1) != '\n' &&
                            document.getChar(position-1) != '\r') position--;

                        while (position > partitionStart &&
                               !Character.isWhitespace(document.getChar(position-1)) &&
                               !isWordSeparator(document.getChar(position-1))) position--;
                    }
                }

                setCaretPosition(position);
                getTextWidget().showSelection();
                fireSelectionChanged();
            }
            catch (BadLocationException e) // should never occur
            {
                PerlEditorPlugin.getDefault().getLog().log(
                    new Status(
                        IStatus.ERROR,
                        PerlEditorPlugin.getPluginId(),
                        IStatus.OK,
                        "An unexpected exception occurred in PreviousWordAction",
                        e));

                super.run(); // fall back on default behavior
            }
        }
    }
}
TOP

Related Classes of org.epic.perleditor.editors.PerlEditor$PreviousWordAction

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.