1 /******************************************************************************* 2 * Copyright (c) 2000, 2004 IBM Corporation and others. 3 * All rights reserved. This program and the accompanying materials 4 * are made available under the terms of the Common Public License v1.0 5 * which accompanies this distribution, and is available at 6 * http://www.eclipse.org/legal/cpl-v10.html 7 * 8 * Contributors: 9 * IBM Corporation - initial API and implementation 10 *******************************************************************************/ 11 package net.sourceforge.phpeclipse.phpeditor; 12 13 import java.util.HashMap; 14 import java.util.Map; 15 import java.util.ResourceBundle; 16 17 import net.sourceforge.phpeclipse.PHPeclipsePlugin; 18 19 import org.eclipse.jface.dialogs.MessageDialog; 20 import org.eclipse.jface.text.BadLocationException; 21 import org.eclipse.jface.text.IDocument; 22 import org.eclipse.jface.text.IRegion; 23 import org.eclipse.jface.text.ITextOperationTarget; 24 import org.eclipse.jface.text.ITextSelection; 25 import org.eclipse.jface.text.ITypedRegion; 26 import org.eclipse.jface.text.Region; 27 import org.eclipse.jface.text.TextUtilities; 28 import org.eclipse.jface.text.source.ISourceViewer; 29 import org.eclipse.jface.text.source.SourceViewerConfiguration; 30 import org.eclipse.jface.viewers.ISelection; 31 import org.eclipse.swt.custom.BusyIndicator; 32 import org.eclipse.swt.widgets.Display; 33 import org.eclipse.swt.widgets.Shell; 34 import org.eclipse.ui.texteditor.ITextEditor; 35 import org.eclipse.ui.texteditor.ResourceAction; 36 import org.eclipse.ui.texteditor.TextEditorAction; 37 38 /** 39 * An action which toggles comment prefixes on the selected lines. 40 * 41 * @since 3.0 42 */ 43 public final class ToggleCommentAction extends TextEditorAction { 44 45 /** The text operation target */ 46 private ITextOperationTarget fOperationTarget; 47 48 /** The document partitioning */ 49 private String fDocumentPartitioning; 50 51 /** The comment prefixes */ 52 private Map fPrefixesMap; 53 54 /** 55 * Creates and initializes the action for the given text editor. The action 56 * configures its visual representation from the given resource bundle. 57 * 58 * @param bundle 59 * the resource bundle 60 * @param prefix 61 * a prefix to be prepended to the various resource keys 62 * (described in <code>ResourceAction</code> constructor), or 63 * <code>null</code> if none 64 * @param editor 65 * the text editor 66 * @see ResourceAction#ResourceAction(ResourceBundle, String, int) 67 */ ToggleCommentAction(ResourceBundle bundle, String prefix, ITextEditor editor)68 public ToggleCommentAction(ResourceBundle bundle, String prefix, 69 ITextEditor editor) { 70 super(bundle, prefix, editor); 71 } 72 73 /** 74 * Implementation of the <code>IAction</code> prototype. Checks if the 75 * selected lines are all commented or not and uncomments/comments them 76 * respectively. 77 */ run()78 public void run() { 79 if (fOperationTarget == null || fDocumentPartitioning == null 80 || fPrefixesMap == null) 81 return; 82 83 ITextEditor editor = getTextEditor(); 84 if (editor == null) 85 return; 86 87 if (!validateEditorInputState()) 88 return; 89 90 final int operationCode; 91 if (isSelectionCommented(editor.getSelectionProvider().getSelection())) 92 operationCode = ITextOperationTarget.STRIP_PREFIX; 93 else 94 operationCode = ITextOperationTarget.PREFIX; 95 96 Shell shell = editor.getSite().getShell(); 97 if (!fOperationTarget.canDoOperation(operationCode)) { 98 if (shell != null) 99 MessageDialog 100 .openError( 101 shell, 102 PHPEditorMessages 103 .getString("ToggleComment.error.title"), PHPEditorMessages.getString("ToggleComment.error.message")); //$NON-NLS-1$ //$NON-NLS-2$ 104 return; 105 } 106 107 Display display = null; 108 if (shell != null && !shell.isDisposed()) 109 display = shell.getDisplay(); 110 111 BusyIndicator.showWhile(display, new Runnable() { 112 public void run() { 113 fOperationTarget.doOperation(operationCode); 114 } 115 }); 116 } 117 118 /** 119 * Is the given selection single-line commented? 120 * 121 * @param selection 122 * Selection to check 123 * @return <code>true</code> iff all selected lines are commented 124 */ isSelectionCommented(ISelection selection)125 private boolean isSelectionCommented(ISelection selection) { 126 if (!(selection instanceof ITextSelection)) 127 return false; 128 129 ITextSelection textSelection = (ITextSelection) selection; 130 if (textSelection.getStartLine() < 0 || textSelection.getEndLine() < 0) 131 return false; 132 133 IDocument document = getTextEditor().getDocumentProvider().getDocument( 134 getTextEditor().getEditorInput()); 135 136 try { 137 138 IRegion block = getTextBlockFromSelection(textSelection, document); 139 ITypedRegion[] regions = TextUtilities.computePartitioning( 140 document, fDocumentPartitioning, block.getOffset(), block 141 .getLength(), false); 142 143 int lineCount = 0; 144 int[] lines = new int[regions.length * 2]; // [startline, endline, 145 // startline, endline, 146 // ...] 147 for (int i = 0, j = 0; i < regions.length; i++, j += 2) { 148 // start line of region 149 lines[j] = getFirstCompleteLineOfRegion(regions[i], document); 150 // end line of region 151 int length = regions[i].getLength(); 152 int offset = regions[i].getOffset() + length; 153 if (length > 0) 154 offset--; 155 lines[j + 1] = (lines[j] == -1 ? -1 : document 156 .getLineOfOffset(offset)); 157 lineCount += lines[j + 1] - lines[j] + 1; 158 } 159 160 // Perform the check 161 for (int i = 0, j = 0; i < regions.length; i++, j += 2) { 162 String[] prefixes = (String[]) fPrefixesMap.get(regions[i] 163 .getType()); 164 if (prefixes != null && prefixes.length > 0 && lines[j] >= 0 165 && lines[j + 1] >= 0) 166 if (!isBlockCommented(lines[j], lines[j + 1], prefixes, 167 document)) 168 return false; 169 } 170 171 return true; 172 173 } catch (BadLocationException x) { 174 // should not happen 175 PHPeclipsePlugin.log(x); 176 } 177 178 return false; 179 } 180 181 /** 182 * Creates a region describing the text block (something that starts at the 183 * beginning of a line) completely containing the current selection. 184 * 185 * @param selection 186 * The selection to use 187 * @param document 188 * The document 189 * @return the region describing the text block comprising the given 190 * selection 191 */ getTextBlockFromSelection(ITextSelection selection, IDocument document)192 private IRegion getTextBlockFromSelection(ITextSelection selection, 193 IDocument document) { 194 195 try { 196 IRegion line = document.getLineInformationOfOffset(selection 197 .getOffset()); 198 int length = selection.getLength() == 0 ? line.getLength() 199 : selection.getLength() 200 + (selection.getOffset() - line.getOffset()); 201 return new Region(line.getOffset(), length); 202 203 } catch (BadLocationException x) { 204 // should not happen 205 PHPeclipsePlugin.log(x); 206 } 207 208 return null; 209 } 210 211 /** 212 * Returns the index of the first line whose start offset is in the given 213 * text range. 214 * 215 * @param region 216 * the text range in characters where to find the line 217 * @param document 218 * The document 219 * @return the first line whose start index is in the given range, -1 if 220 * there is no such line 221 */ getFirstCompleteLineOfRegion(IRegion region, IDocument document)222 private int getFirstCompleteLineOfRegion(IRegion region, IDocument document) { 223 224 try { 225 226 int startLine = document.getLineOfOffset(region.getOffset()); 227 228 int offset = document.getLineOffset(startLine); 229 if (offset >= region.getOffset()) 230 return startLine; 231 232 offset = document.getLineOffset(startLine + 1); 233 return (offset > region.getOffset() + region.getLength() ? -1 234 : startLine + 1); 235 236 } catch (BadLocationException x) { 237 // should not happen 238 PHPeclipsePlugin.log(x); 239 } 240 241 return -1; 242 } 243 244 /** 245 * Determines whether each line is prefixed by one of the prefixes. 246 * 247 * @param startLine 248 * Start line in document 249 * @param endLine 250 * End line in document 251 * @param prefixes 252 * Possible comment prefixes 253 * @param document 254 * The document 255 * @return <code>true</code> iff each line from <code>startLine</code> 256 * to and including <code>endLine</code> is prepended by one of 257 * the <code>prefixes</code>, ignoring whitespace at the begin of 258 * line 259 */ isBlockCommented(int startLine, int endLine, String[] prefixes, IDocument document)260 private boolean isBlockCommented(int startLine, int endLine, 261 String[] prefixes, IDocument document) { 262 263 try { 264 265 // check for occurrences of prefixes in the given lines 266 for (int i = startLine; i <= endLine; i++) { 267 268 IRegion line = document.getLineInformation(i); 269 String text = document.get(line.getOffset(), line.getLength()); 270 271 int[] found = TextUtilities.indexOf(prefixes, text, 0); 272 273 if (found[0] == -1) 274 // found a line which is not commented 275 return false; 276 277 String s = document.get(line.getOffset(), found[0]); 278 s = s.trim(); 279 if (s.length() != 0) 280 // found a line which is not commented 281 return false; 282 283 } 284 285 return true; 286 287 } catch (BadLocationException x) { 288 // should not happen 289 PHPeclipsePlugin.log(x); 290 } 291 292 return false; 293 } 294 295 /** 296 * Implementation of the <code>IUpdate</code> prototype method discovers 297 * the operation through the current editor's 298 * <code>ITextOperationTarget</code> adapter, and sets the enabled state 299 * accordingly. 300 */ update()301 public void update() { 302 super.update(); 303 304 if (!canModifyEditor()) { 305 setEnabled(false); 306 return; 307 } 308 309 ITextEditor editor = getTextEditor(); 310 if (fOperationTarget == null && editor != null) 311 fOperationTarget = (ITextOperationTarget) editor 312 .getAdapter(ITextOperationTarget.class); 313 314 boolean isEnabled = (fOperationTarget != null 315 && fOperationTarget.canDoOperation(ITextOperationTarget.PREFIX) && fOperationTarget 316 .canDoOperation(ITextOperationTarget.STRIP_PREFIX)); 317 setEnabled(isEnabled); 318 } 319 320 /* 321 * @see TextEditorAction#setEditor(ITextEditor) 322 */ setEditor(ITextEditor editor)323 public void setEditor(ITextEditor editor) { 324 super.setEditor(editor); 325 fOperationTarget = null; 326 } 327 configure(ISourceViewer sourceViewer, SourceViewerConfiguration configuration)328 public void configure(ISourceViewer sourceViewer, 329 SourceViewerConfiguration configuration) { 330 fPrefixesMap = null; 331 332 String[] types = configuration.getConfiguredContentTypes(sourceViewer); 333 Map prefixesMap = new HashMap(types.length); 334 for (int i = 0; i < types.length; i++) { 335 String type = types[i]; 336 String[] prefixes = configuration.getDefaultPrefixes(sourceViewer, 337 type); 338 if (prefixes != null && prefixes.length > 0) { 339 int emptyPrefixes = 0; 340 for (int j = 0; j < prefixes.length; j++) 341 if (prefixes[j].length() == 0) 342 emptyPrefixes++; 343 344 if (emptyPrefixes > 0) { 345 String[] nonemptyPrefixes = new String[prefixes.length 346 - emptyPrefixes]; 347 for (int j = 0, k = 0; j < prefixes.length; j++) { 348 String prefix = prefixes[j]; 349 if (prefix.length() != 0) { 350 nonemptyPrefixes[k] = prefix; 351 k++; 352 } 353 } 354 prefixes = nonemptyPrefixes; 355 } 356 357 prefixesMap.put(type, prefixes); 358 } 359 } 360 fDocumentPartitioning = configuration 361 .getConfiguredDocumentPartitioning(sourceViewer); 362 fPrefixesMap = prefixesMap; 363 } 364 } 365