| /******************************************************************************* |
| * Copyright (c) 2010 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 { |
| //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) { |
| if(commentType.alreadyCommenting(this.fDocument, lineTypedRegions)) { |
| commentType.remove(this.fDocument, lineRegion.getOffset(), lineRegion.getLength(), true); |
| } else { |
| commentType.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>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); |
| } |
| } |
| } |
| } |