Java tutorial
/******************************************************************************* * This file is part of the Twig eclipse plugin. * * (c) Robert Gruendler <r.gruendler@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. ******************************************************************************/ package com.dubture.twig.ui.actions; import java.lang.reflect.InvocationTargetException; import java.util.Collections; 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.BadLocationException; import org.eclipse.jface.text.DocumentRewriteSession; import org.eclipse.jface.text.DocumentRewriteSessionType; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentExtension4; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.ITypedRegion; import org.eclipse.jface.text.Position; 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.Logger; 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; @SuppressWarnings("restriction") public class TwigToggleLineCommentHandler extends AbstractCommentHandler { static final String OPEN_COMMENT = "{#"; //$NON-NLS-1$ static final String CLOSE_COMMENT = "#}"; //$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 = 20; private static final ToggleLineCommentHandler toggleLineCommentHandler = new ToggleLineCommentHandler(); @Override protected void processAction(final ITextEditor textEditor, final IStructuredDocument document, ITextSelection textSelection) { IStructuredModel model = null; DocumentRewriteSession session = null; boolean changed = false; try { // get text selection lines info int selectionStartLine = textSelection.getStartLine(); int selectionEndLine = textSelection.getEndLine(); 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); ITypedRegion[] lineTypedRegions = document.computePartitioning(selectionStartLineOffset, selectionEndLineOffset - selectionStartLineOffset); if (lineTypedRegions != null && lineTypedRegions.length >= 1 && (lineTypedRegions[0].getType().equals("org.eclipse.wst.html.HTML_DEFAULT") || lineTypedRegions[0].getType().equals("com.dubture.twig.TWIG_DEFAULT"))) { // 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); // 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, "") { @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(); } } } private static class ToggleLinesRunnable implements IRunnableWithProgress { private IStructuredDocument fDocument; private int fSelectionStartLine; private int fSelectionEndLine; private Display fDisplay; protected ToggleLinesRunnable(String contentTypeIdentifier, IStructuredDocument document, int selectionStartLine, int selectionEndLine, Display display) { this.fDocument = document; this.fSelectionStartLine = selectionStartLine; this.fSelectionEndLine = selectionEndLine; this.fDisplay = display; } @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { 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)) { 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 { apply(this.fDocument, lineRegion.getOffset(), lineRegion.getLength()); } } 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, OPEN_COMMENT + " "); document.replace(offset + length + 3, 0, CLOSE_COMMENT); } /** * <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(" ")) { 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(" ")) { 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> * 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 origOffset = offset; int innerOffset = content.indexOf(OPEN_COMMENT); if (innerOffset > 0) { offset += innerOffset; } // remove the opening tag uncomment(document, offset, OPEN_COMMENT, -1, null); innerOffset = content.indexOf(CLOSE_COMMENT); if (innerOffset > 0) { origOffset += innerOffset; } // take into account the removed opening comment tag origOffset -= 3; // remove the closing tag uncomment(document, origOffset, CLOSE_COMMENT, -1, null); } /** * <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", e); } catch (LinkageError e) { Logger.log(Logger.WARNING, "LinkageError caused by readAndDispatch, not caused by or fatal to caller", 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", e); } } } public static boolean isCommentLine(IDocument document, int line) { boolean isComment = false; try { IRegion region = document.getLineInformation(line); String string = document.get(region.getOffset(), region.getLength()).trim(); // empty line if (string.trim().length() == 0) return true; isComment = (string.length() >= OPEN_COMMENT.length() && string.startsWith(OPEN_COMMENT)) || (string.length() >= OPEN_COMMENT.length() && string.startsWith(OPEN_COMMENT)); } catch (BadLocationException e) { Logger.log(Logger.WARNING_DEBUG, e.getMessage(), e); } return isComment; } }