/******************************************************************************* * Copyright (c) 2010, 2011 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 *******************************************************************************/ package org.eclipse.wst.sse.ui.internal.handlers; import java.lang.reflect.InvocationTargetException; 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.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.comment.BlockCommentingStrategy; import org.eclipse.wst.sse.ui.internal.comment.CommentingStrategy; import org.eclipse.wst.sse.ui.internal.comment.CommentingStrategyRegistry; import org.eclipse.wst.sse.ui.internal.comment.LineCommentingStrategy; /** * <p>A comment handler to toggle line comments, this means that if a * comment already exists on a line then toggling it will remove the comment, * if the line in question is not already commented then it will not be commented. * If multiple lines are selected each will be commented separately. The handler * first attempts to find a {@link LineCommentingStrategy} for a line, if it can * not find one then it will try and find a {@link BlockCommentingStrategy} to * wrap just that line in.</p> * * <p>If a great number of lines are being toggled then a progress dialog will be * displayed because this can be a timely process</p> */ public final class ToggleLineCommentHandler extends AbstractCommentHandler { /** if toggling more then this many lines then use a busy indicator */ private static final int TOGGLE_LINES_MAX_NO_BUSY_INDICATOR = 10; /** * @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(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--; } // 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()); } } } 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$ } 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(); } } } /** * <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; /** * @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) { this.fContentType = contentTypeIdentifier; this.fDocument = document; this.fSelectionStartLine = selectionStartLine; this.fSelectionEndLine = selectionEndLine; this.fDisplay = display; } /** * @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 { final CommentingStrategy[] strategies = new CommentingStrategy[fSelectionEndLine - fSelectionStartLine + 1]; final int[] regions = new int[fSelectionEndLine - fSelectionStartLine + 1]; boolean shouldComment = false; int strategyCount = 0; //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 ITypedRegion[] lineTypedRegions = this.fDocument.computePartitioning(lineRegion.getOffset(), lineRegion.getLength()); CommentingStrategy commentType = CommentingStrategyRegistry.getDefault().getLineCommentingStrategy( this.fContentType, lineTypedRegions); //could not find line comment type so find block comment type to use on line if(commentType == null) { commentType = CommentingStrategyRegistry.getDefault().getBlockCommentingStrategy( this.fContentType, lineTypedRegions); } //toggle the comment on the line if(commentType != null) { strategies[strategyCount] = commentType; regions[strategyCount++] = line; if (!shouldComment && !commentType.alreadyCommenting(this.fDocument, lineTypedRegions)) { shouldComment = true; } } } monitor.worked(1); } for (int i = 0; i < strategyCount; i++) { final IRegion lineRegion = fDocument.getLineInformation( regions[i] ); if (shouldComment) { strategies[i].apply(this.fDocument, lineRegion.getOffset(), lineRegion.getLength()); } else { strategies[i].remove(this.fDocument, lineRegion.getOffset(), lineRegion.getLength(), true); } monitor.worked(1); } } catch(BadLocationException e) { Logger.logException("Bad location while toggling comments.", e); //$NON-NLS-1$ } //done work monitor.done(); } /** * <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); } } } }