org.eclipse.php.internal.ui.actions.PHPToggleLineCommentHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.php.internal.ui.actions.PHPToggleLineCommentHandler.java

Source

/*******************************************************************************
 * 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.actions;

import java.lang.reflect.InvocationTargetException;
import java.util.Collections;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.text.*;
import org.eclipse.php.internal.ui.Logger;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.ui.ISources;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.ui.StructuredTextEditor;
import org.eclipse.wst.sse.ui.internal.SSEUIMessages;
import org.eclipse.wst.sse.ui.internal.StructuredTextViewer;
import org.eclipse.wst.sse.ui.internal.handlers.AbstractCommentHandler;
import org.eclipse.wst.sse.ui.internal.handlers.ToggleLineCommentHandler;

public class PHPToggleLineCommentHandler extends AbstractCommentHandler {
    static final String SINGLE_LINE_COMMENT = "//"; //$NON-NLS-1$
    static final String OPEN_COMMENT = "/*"; //$NON-NLS-1$
    private static final String BLANK = " "; //$NON-NLS-1$
    private static final String SHORT_TAG = "<?"; //$NON-NLS-1$
    private static final String SHORT_TAG_WITH_BLANK = "<? "; //$NON-NLS-1$
    private static final String PHP = "<?php"; //$NON-NLS-1$
    private static final String PHP_WITH_BLANK = "<?php "; //$NON-NLS-1$

    /** if toggling more then this many lines then use a busy indicator */
    private static final int TOGGLE_LINES_MAX_NO_BUSY_INDICATOR = 10;
    private static final ToggleLineCommentHandler toggleLineCommentHandler = new ToggleLineCommentHandler();

    /**
     * @see org.eclipse.wst.sse.ui.internal.handlers.AbstractCommentHandler#processAction(org.eclipse.ui.texteditor.ITextEditor,
     *      org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument,
     *      org.eclipse.jface.text.ITextSelection)
     */
    protected void processAction(final ITextEditor textEditor, final IStructuredDocument document,
            ITextSelection textSelection) {

        IStructuredModel model = null;
        DocumentRewriteSession session = null;
        boolean changed = false;

        try {
            // TODO this will be remove soon,just to see if this method is
            // executed in Zend Studio.
            Logger.log(Logger.ERROR, "PHP Toggle Line Comment is starting."); //$NON-NLS-1$
            // get text selection lines info
            int selectionStartLine = textSelection.getStartLine();
            int selectionEndLine = textSelection.getEndLine();
            boolean isSingleLine = false;
            if (selectionStartLine == selectionEndLine) {
                isSingleLine = true;
            }

            int selectionEndLineOffset = document.getLineOffset(selectionEndLine);
            int selectionEndOffset = textSelection.getOffset() + textSelection.getLength();

            // adjust selection end line
            if ((selectionEndLine > selectionStartLine) && (selectionEndLineOffset == selectionEndOffset)) {
                selectionEndLine--;
                selectionEndLineOffset = document.getLineInformation(selectionEndLine).getOffset();
            }

            int selectionStartLineOffset = document.getLineOffset(selectionStartLine);
            if (selectionEndLineOffset == selectionStartLineOffset) {
                selectionEndLineOffset = document.getLineInformation(selectionEndLine).getOffset()
                        + document.getLineInformation(selectionEndLine).getLength();
            }
            ITypedRegion[] lineTypedRegions = document.computePartitioning(selectionStartLineOffset,
                    selectionEndLineOffset - selectionStartLineOffset);
            if (lineTypedRegions != null && lineTypedRegions.length == 1
                    && lineTypedRegions[0].getType().equals("org.eclipse.php.PHP_DEFAULT")) { //$NON-NLS-1$

                // save the selection position since it will be changing
                Position selectionPosition = null;
                selectionPosition = new Position(textSelection.getOffset(), textSelection.getLength());
                document.addPosition(selectionPosition);

                model = StructuredModelManager.getModelManager().getModelForEdit(document);
                if (model != null) {
                    // makes it so one undo will undo all the edits to the
                    // document
                    model.beginRecording(this, SSEUIMessages.ToggleComment_label,
                            SSEUIMessages.ToggleComment_description);

                    // keeps listeners from doing anything until updates are all
                    // done
                    model.aboutToChangeModel();
                    if (document instanceof IDocumentExtension4) {
                        session = ((IDocumentExtension4) document)
                                .startRewriteSession(DocumentRewriteSessionType.UNRESTRICTED);
                    }
                    changed = true;

                    // get the display for the editor if we can
                    Display display = null;
                    if (textEditor instanceof StructuredTextEditor) {
                        StructuredTextViewer viewer = ((StructuredTextEditor) textEditor).getTextViewer();
                        if (viewer != null) {
                            display = viewer.getControl().getDisplay();
                        }
                    }

                    // create the toggling operation
                    IRunnableWithProgress toggleCommentsRunnable = new ToggleLinesRunnable(
                            model.getContentTypeIdentifier(), document, selectionStartLine, selectionEndLine,
                            display, isSingleLine);

                    // if toggling lots of lines then use progress monitor else
                    // just
                    // run the operation
                    if ((selectionEndLine - selectionStartLine) > TOGGLE_LINES_MAX_NO_BUSY_INDICATOR
                            && display != null) {
                        ProgressMonitorDialog dialog = new ProgressMonitorDialog(display.getActiveShell());
                        dialog.run(false, true, toggleCommentsRunnable);
                    } else {
                        toggleCommentsRunnable.run(new NullProgressMonitor());
                    }
                }
            } else {
                org.eclipse.core.expressions.EvaluationContext evaluationContext = new org.eclipse.core.expressions.EvaluationContext(
                        null, "") { //$NON-NLS-1$
                    @Override
                    public Object getVariable(String name) {
                        if (ISources.ACTIVE_EDITOR_NAME.equals(name)) {
                            return textEditor;
                        }
                        return null;
                    }
                };
                org.eclipse.core.commands.ExecutionEvent executionEvent = new org.eclipse.core.commands.ExecutionEvent(
                        null, Collections.EMPTY_MAP, new Event(), evaluationContext);
                toggleLineCommentHandler.execute(executionEvent);
            }

        } catch (InvocationTargetException e) {
            Logger.logException("Problem running toggle comment progess dialog.", e); //$NON-NLS-1$
        } catch (InterruptedException e) {
            Logger.logException("Problem running toggle comment progess dialog.", e); //$NON-NLS-1$
        } catch (BadLocationException e) {
            Logger.logException("The given selection " + textSelection + " must be invalid", e); //$NON-NLS-1$ //$NON-NLS-2$
        } catch (ExecutionException e) {
        } finally {
            // clean everything up
            if (session != null && document instanceof IDocumentExtension4) {
                ((IDocumentExtension4) document).stopRewriteSession(session);
            }

            if (model != null) {
                model.endRecording(this);
                if (changed) {
                    model.changedModel();
                }
                model.releaseFromEdit();
            }
        }
    }

    public static boolean isCommentLine(IDocument document, int line, boolean isSingleLine) {
        boolean isComment = false;

        try {
            IRegion region = document.getLineInformation(line);
            String string = document.get(region.getOffset(), region.getLength()).trim();
            boolean phpStrat = false;
            // if (isSingleLine) {
            if (string.startsWith(PHP)) {
                string = string.substring(PHP.length()).trim();
                phpStrat = true;
            } else if (string.startsWith(SHORT_TAG)) {
                string = string.substring(SHORT_TAG.length()).trim();
                phpStrat = true;
            }
            // }

            isComment = !phpStrat && StringUtils.isBlank(string)
                    || (string.length() >= OPEN_COMMENT.length() && string.startsWith(OPEN_COMMENT))
                    || (string.length() >= SINGLE_LINE_COMMENT.length() && string.startsWith(SINGLE_LINE_COMMENT));
        } catch (BadLocationException e) {
            Logger.log(Logger.WARNING_DEBUG, e.getMessage(), e);
        }
        return isComment;
    }

    /**
     * <p>
     * The actual line toggling takes place in a runnable so it can be run as
     * part of a progress dialog if there are many lines to toggle and thus the
     * operation will take a noticeable amount of time the user should be aware
     * of, this also allows for the operation to be canceled by the user
     * </p>
     * 
     */
    private static class ToggleLinesRunnable implements IRunnableWithProgress {
        /** the content type for the document being commented */
        private String fContentType;

        /** the document that the lines will be toggled on */
        private IStructuredDocument fDocument;

        /** the first line in the document to toggle */
        private int fSelectionStartLine;

        /** the last line in the document to toggle */
        private int fSelectionEndLine;

        /** the display, so that it can be updated during a long operation */
        private Display fDisplay;

        private boolean isSingleLine;

        /**
         * @param model
         *            {@link IStructuredModel} that the lines will be toggled on
         * @param document
         *            {@link IDocument} that the lines will be toggled on
         * @param selectionStartLine
         *            first line in the document to toggle
         * @param selectionEndLine
         *            last line in the document to toggle
         * @param display
         *            {@link Display}, so that it can be updated during a long
         *            operation
         */
        protected ToggleLinesRunnable(String contentTypeIdentifier, IStructuredDocument document,
                int selectionStartLine, int selectionEndLine, Display display, boolean isSingleLine) {

            this.fContentType = contentTypeIdentifier;
            this.fDocument = document;
            this.fSelectionStartLine = selectionStartLine;
            this.fSelectionEndLine = selectionEndLine;
            this.fDisplay = display;
            this.isSingleLine = isSingleLine;
        }

        /**
         * @see org.eclipse.jface.operation.IRunnableWithProgress#run(org.eclipse.core.runtime.IProgressMonitor)
         */
        public void run(IProgressMonitor monitor) {
            // start work
            monitor.beginTask(SSEUIMessages.ToggleComment_progress,
                    this.fSelectionEndLine - this.fSelectionStartLine);
            try {
                boolean allLinesCommented = true;
                for (int i = fSelectionStartLine; i <= fSelectionEndLine; i++) {
                    try {
                        if (fDocument.getLineLength(i) > 0) {
                            if (!isCommentLine(fDocument, i, isSingleLine)) {
                                allLinesCommented = false;
                                break;
                            }
                        }
                    } catch (BadLocationException e) {
                        Logger.log(Logger.WARNING_DEBUG, e.getMessage(), e);
                    }
                }
                // toggle each line so long as task not canceled
                for (int line = this.fSelectionStartLine; line <= this.fSelectionEndLine
                        && !monitor.isCanceled(); ++line) {

                    // allows the user to be able to click the cancel button
                    readAndDispatch(this.fDisplay);

                    // get the line region
                    IRegion lineRegion = this.fDocument.getLineInformation(line);

                    // don't toggle empty lines
                    String content = this.fDocument.get(lineRegion.getOffset(), lineRegion.getLength());
                    if (content.trim().length() > 0) {
                        // try to get a line comment type

                        // toggle the comment on the line
                        if (allLinesCommented) {
                            remove(this.fDocument, lineRegion.getOffset(), lineRegion.getLength(), true);
                        } else {
                            int offset = 0;
                            String string = content.trim();
                            String commentStr = SINGLE_LINE_COMMENT;
                            if (string.startsWith(PHP_WITH_BLANK)) {
                                // string =
                                // string.substring("<?php".length()).trim();
                                offset = content.indexOf(PHP_WITH_BLANK) + PHP_WITH_BLANK.length();
                                commentStr = SINGLE_LINE_COMMENT;
                            } else if (string.startsWith(PHP)) {
                                // string =
                                // string.substring("<?php".length()).trim();
                                offset = content.indexOf(PHP) + PHP.length();
                                commentStr = BLANK + SINGLE_LINE_COMMENT;
                            } else if (string.startsWith(SHORT_TAG_WITH_BLANK)) {
                                // string =
                                // string.substring("<?".length()).trim();
                                offset = content.indexOf(SHORT_TAG_WITH_BLANK) + SHORT_TAG_WITH_BLANK.length();
                                commentStr = SINGLE_LINE_COMMENT;
                            } else if (string.startsWith(SHORT_TAG)) {
                                // string =
                                // string.substring("<?".length()).trim();
                                offset = content.indexOf(SHORT_TAG) + SHORT_TAG.length();
                                commentStr = BLANK + SINGLE_LINE_COMMENT;
                            } else {
                                commentStr = SINGLE_LINE_COMMENT;
                            }
                            this.fDocument.replace(lineRegion.getOffset() + offset, 0, commentStr + BLANK);
                        }
                    }
                    monitor.worked(1);
                }
            } catch (BadLocationException e) {
                Logger.logException("Bad location while toggling comments.", e); //$NON-NLS-1$
            }
            // done work
            monitor.done();
        }

        /**
         * <p>
         * Assumes that the given offset is at the begining of a line and adds
         * the line comment prefix there
         * </p>
         * 
         */
        public void apply(IStructuredDocument document, int offset, int length) throws BadLocationException {

            document.replace(offset, 0, SINGLE_LINE_COMMENT + BLANK);
        }

        /**
         * <p>
         * Assumes that the given offset is at the beginning of a line that is
         * commented and removes the comment prefix from the beginning of the
         * line, leading whitespace on the line will not prevent this method
         * from finishing correctly
         * </p>
         * 
         */
        public void remove(IStructuredDocument document, int offset, int length, boolean removeEnclosing)
                throws BadLocationException {
            String content = document.get(offset, length);
            int innerOffset = content.indexOf(SINGLE_LINE_COMMENT);
            if (innerOffset > 0) {
                offset += innerOffset;
            }

            uncomment(document, offset, SINGLE_LINE_COMMENT, -1, null);
        }

        /**
         * <p>
         * This method modifies the given document to remove the given comment
         * prefix at the given comment prefix offset and the given comment
         * suffix at the given comment suffix offset. In the case of removing a
         * line comment that does not have a suffix, pass <code>null</code> for
         * the comment suffix and it and its associated offset will be ignored.
         * </p>
         * 
         * <p>
         * <b>NOTE:</b> it is a good idea if a model is at hand when calling
         * this to warn the model of an impending update
         * </p>
         * 
         * @param document
         *            the document to remove the comment from
         * @param commentPrefixOffset
         *            the offset of the comment prefix
         * @param commentSuffixOffset
         *            the offset of the comment suffix (ignored if
         *            <code>commentSuffix</code> is <code>null</code>)
         * @param commentPrefix
         *            the prefix of the comment to remove from its associated
         *            given offset
         * @param commentSuffix
         *            the suffix of the comment to remove from its associated
         *            given offset, or null if there is not suffix to remove for
         *            this comment
         */
        protected static void uncomment(IDocument document, int commentPrefixOffset, String commentPrefix,
                int commentSuffixOffset, String commentSuffix) {

            try {
                // determine if there is a space after the comment prefix that
                // should also be removed
                int commentPrefixLength = commentPrefix.length();
                String postCommentPrefixChar = document.get(commentPrefixOffset + commentPrefix.length(), 1);
                if (postCommentPrefixChar.equals(BLANK)) {
                    commentPrefixLength++;
                }

                // remove the comment prefix
                document.replace(commentPrefixOffset, commentPrefixLength, ""); //$NON-NLS-1$

                if (commentSuffix != null) {
                    commentSuffixOffset -= commentPrefixLength;

                    // determine if there is a space before the comment suffix
                    // that should also be removed
                    int commentSuffixLength = commentSuffix.length();
                    String preCommentSuffixChar = document.get(commentSuffixOffset - 1, 1);
                    if (preCommentSuffixChar.equals(BLANK)) {
                        commentSuffixLength++;
                        commentSuffixOffset--;
                    }

                    // remove the comment suffix
                    document.replace(commentSuffixOffset, commentSuffixLength, ""); //$NON-NLS-1$
                }
            } catch (BadLocationException e) {
                Logger.log(Logger.WARNING_DEBUG, e.getMessage(), e);
            }
        }

        /**
         * <p>
         * When calling {@link Display#readAndDispatch()} the game is off as to
         * whose code you maybe calling into because of event
         * handling/listeners/etc. The only important thing is that the UI has
         * been given a chance to react to user clicks. Thus the logging of most
         * {@link Exception}s and {@link Error}s as caused by
         * {@link Display#readAndDispatch()} because they are not caused by this
         * code and do not effect it.
         * </p>
         * 
         * @param display
         *            the {@link Display} to call <code>readAndDispatch</code>
         *            on with exception/error handling.
         */
        private void readAndDispatch(Display display) {
            try {
                display.readAndDispatch();
            } catch (Exception e) {
                Logger.log(Logger.WARNING, "Exception caused by readAndDispatch, not caused by or fatal to caller", //$NON-NLS-1$
                        e);
            } catch (LinkageError e) {
                Logger.log(Logger.WARNING,
                        "LinkageError caused by readAndDispatch, not caused by or fatal to caller", //$NON-NLS-1$
                        e);
            } catch (VirtualMachineError e) {
                // re-throw these
                throw e;
            } catch (ThreadDeath e) {
                // re-throw these
                throw e;
            } catch (Error e) {
                // catch every error, except for a few that we don't want to
                // handle
                Logger.log(Logger.WARNING, "Error caused by readAndDispatch, not caused by or fatal to caller", //$NON-NLS-1$
                        e);
            }
        }
    }
}