diff --git a/core/org.eclipse.cdt.ui/plugin.properties b/core/org.eclipse.cdt.ui/plugin.properties index 53c38ed0957..944bc082d50 100644 --- a/core/org.eclipse.cdt.ui/plugin.properties +++ b/core/org.eclipse.cdt.ui/plugin.properties @@ -24,6 +24,8 @@ perspective.name=C/C++ viewsCategory.name=&C/C++ CView.name=C/C++ Projects +ToggleCommentAction.label= Togg&le Comment + AddBlockCommentAction.label= Add &Block Comment RemoveBlockCommentAction.label= Remove Bloc&k Comment @@ -84,6 +86,9 @@ ActionDefinition.comment.description= Turn the selected lines into // style comm ActionDefinition.uncomment.name= Uncomment ActionDefinition.uncomment.description= Uncomment the selected // style comment lines +ActionDefinition.toggleComment.name= Toggle Comment +ActionDefinition.toggleComment.description= Toggle comment the selected lines + ActionDefinition.opendecl.name= Open Declaration ActionDefinition.opendecl.description= Open an editor on the selected element's declaration(s) diff --git a/core/org.eclipse.cdt.ui/plugin.xml b/core/org.eclipse.cdt.ui/plugin.xml index 91f6dc12707..66e9ee5c9c4 100644 --- a/core/org.eclipse.cdt.ui/plugin.xml +++ b/core/org.eclipse.cdt.ui/plugin.xml @@ -852,17 +852,12 @@ sequence="M1+M2+F" schemeId="org.eclipse.ui.defaultAcceleratorConfiguration" contextId="org.eclipse.cdt.ui.cEditorScope" - commandId="org.eclipse.cdt.ui.edit.text.c.format"/> + commandId="org.eclipse.cdt.ui.edit.text.c.format"/> - + commandId="org.eclipse.cdt.ui.edit.text.c.toggle.comment"/> - - + id="org.eclipse.cdt.ui.edit.text.c.toggle.comment"> comment action - * (value "org.eclipse.cdt.ui.edit.text.c.comment"). + * Action definition ID of the source -> toggle comment action + * (value "org.eclipse.cdt.ui.edit.text.c.toggle.comment"). + * @since 4.0.0 */ - public static final String COMMENT = "org.eclipse.cdt.ui.edit.text.c.comment"; //$NON-NLS-1$ + public static final String TOGGLE_COMMENT= "org.eclipse.cdt.ui.edit.text.c.toggle.comment"; //$NON-NLS-1$ + - /** - * Action definition ID of the source -> uncomment action - * (value "org.eclipse.cdt.ui.edit.text.c.uncomment"). - */ - public static final String UNCOMMENT = "org.eclipse.cdt.ui.edit.text.c.uncomment"; //$NON-NLS-1$ - /** * Action definition ID of the source -> add block comment action * (value "org.eclipse.cdt.ui.edit.text.c.add.block.comment"). diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/ToggleCommentAction.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/ToggleCommentAction.java new file mode 100644 index 00000000000..b9c48ef01d1 --- /dev/null +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/editor/ToggleCommentAction.java @@ -0,0 +1,336 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.cdt.internal.ui.editor; + +import java.util.HashMap; +import java.util.Map; +import java.util.ResourceBundle; + +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextOperationTarget; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.text.ITypedRegion; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TextUtilities; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.jface.text.source.SourceViewerConfiguration; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.swt.custom.BusyIndicator; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.texteditor.ITextEditor; +import org.eclipse.ui.texteditor.ResourceAction; +import org.eclipse.ui.texteditor.TextEditorAction; + +import org.eclipse.cdt.ui.CUIPlugin; + +/** + * An action which toggles comment prefixes on the selected lines. + * + * @since 4.0.0 + */ +public final class ToggleCommentAction extends TextEditorAction { + + /** The text operation target */ + private ITextOperationTarget fOperationTarget; + /** The document partitioning */ + private String fDocumentPartitioning; + /** The comment prefixes */ + private Map fPrefixesMap; + + /** + * Creates and initializes the action for the given text editor. The action + * configures its visual representation from the given resource bundle. + * + * @param bundle the resource bundle + * @param prefix a prefix to be prepended to the various resource keys + * (described in ResourceAction constructor), or + * null if none + * @param editor the text editor + * @see ResourceAction#ResourceAction(ResourceBundle, String, int) + */ + public ToggleCommentAction(ResourceBundle bundle, String prefix, ITextEditor editor) { + super(bundle, prefix, editor); + } + + /** + * Implementation of the IAction prototype. Checks if the selected + * lines are all commented or not and uncomments/comments them respectively. + */ + public void run() { + if (fOperationTarget == null || fDocumentPartitioning == null || fPrefixesMap == null) + return; + + ITextEditor editor= getTextEditor(); + if (editor == null) + return; + + if (!validateEditorInputState()) + return; + + final int operationCode; + if (isSelectionCommented(editor.getSelectionProvider().getSelection())) + operationCode= ITextOperationTarget.STRIP_PREFIX; + else + operationCode= ITextOperationTarget.PREFIX; + + Shell shell= editor.getSite().getShell(); + if (!fOperationTarget.canDoOperation(operationCode)) { + if (shell != null) + MessageDialog.openError(shell, CEditorMessages.getString("ToggleComment_error_title"), CEditorMessages.getString("ToggleComment_error_message")); + return; + } + + Display display= null; + if (shell != null && !shell.isDisposed()) + display= shell.getDisplay(); + + BusyIndicator.showWhile(display, new Runnable() { + public void run() { + fOperationTarget.doOperation(operationCode); + } + }); + } + + /** + * Is the given selection single-line commented? + * + * @param selection Selection to check + * @return true iff all selected lines are commented + */ + private boolean isSelectionCommented(ISelection selection) { + if (!(selection instanceof ITextSelection)) + return false; + + ITextSelection textSelection= (ITextSelection) selection; + if (textSelection.getStartLine() < 0 || textSelection.getEndLine() < 0) + return false; + + IDocument document= getTextEditor().getDocumentProvider().getDocument(getTextEditor().getEditorInput()); + + try { + + IRegion block= getTextBlockFromSelection(textSelection, document); + ITypedRegion[] regions= TextUtilities.computePartitioning(document, fDocumentPartitioning, block.getOffset(), block.getLength(), false); + + int lineCount= 0; + int[] lines= new int[regions.length * 2]; // [startline, endline, startline, endline, ...] + + //For each partition in the text selection, figure out what are the startlines and endlines for + //each partition. Count the number of lines that are selected. + for (int i = 0, j = 0; i < regions.length; i++, j+= 2) { + // start line of region + lines[j]= getFirstCompleteLineOfRegion(regions[i], document); + // end line of region + int length= regions[i].getLength(); + int offset= regions[i].getOffset() + length; + if (length > 0) + offset--; + + //if there is no startline for this region (startline = -1) + //then there is no endline + //otherwise, get the line number of the endline and store it in the array + lines[j + 1]= (lines[j] == -1 ? -1 : document.getLineOfOffset(offset)); + + //count the number of lines that are selected in this region + lineCount += lines[j + 1] - lines[j] + 1; + + assert i < regions.length; + assert j < regions.length * 2; + } + + // Perform the check + for (int i = 0, j = 0; i < regions.length; i++, j += 2) { + String[] prefixes= (String[]) fPrefixesMap.get(regions[i].getType()); + if (prefixes != null && prefixes.length > 0 && lines[j] >= 0 && lines[j + 1] >= 0) + if (!isBlockCommented(lines[j], lines[j + 1], prefixes, document)) + return false; + } + + return true; + + } catch (BadLocationException x) { + // should not happen + CUIPlugin.getDefault().log(x); + } + + return false; + } + + /** + * Creates a region describing the text block (something that starts at + * the beginning of a line) completely containing the current selection. + * + * @param selection The selection to use + * @param document The document + * @return the region describing the text block comprising the given selection + */ + private IRegion getTextBlockFromSelection(ITextSelection selection, IDocument document) { + + try { + IRegion line= document.getLineInformationOfOffset(selection.getOffset()); + int length= selection.getLength() == 0 ? line.getLength() : selection.getLength() + (selection.getOffset() - line.getOffset()); + return new Region(line.getOffset(), length); + + } catch (BadLocationException x) { + // should not happen + CUIPlugin.getDefault().log(x); + } + + return null; + } + + /** + * Returns the index of the first line whose start offset is in the given text range. + * + * @param region the text range in characters where to find the line + * @param document The document + * @return the first line whose start index is in the given range, -1 if there is no such line + */ + private int getFirstCompleteLineOfRegion(IRegion region, IDocument document) { + + try { + + int startLine= document.getLineOfOffset(region.getOffset()); + + int offset= document.getLineOffset(startLine); + if (offset >= region.getOffset()) + return startLine; + + offset= document.getLineOffset(startLine + 1); + return (offset > region.getOffset() + region.getLength() ? -1 : startLine + 1); + + } catch (BadLocationException x) { + // should not happen + CUIPlugin.getDefault().log(x); + } + + return -1; + } + + /** + * Determines whether each line is prefixed by one of the prefixes. + * + * @param startLine Start line in document + * @param endLine End line in document + * @param prefixes Possible comment prefixes + * @param document The document + * @return true iff each line from startLine + * to and including endLine is prepended by one + * of the prefixes, ignoring whitespace at the + * begin of line + */ + private boolean isBlockCommented(int startLine, int endLine, String[] prefixes, IDocument document) { + + try { + + // check for occurrences of prefixes in the given lines + for (int i= startLine; i <= endLine; i++) { + + IRegion line= document.getLineInformation(i); + String text= document.get(line.getOffset(), line.getLength()); + + int[] found= TextUtilities.indexOf(prefixes, text, 0); + + if (found[0] == -1) + // found a line which is not commented + return false; + + String s= document.get(line.getOffset(), found[0]); + s= s.trim(); + if (s.length() != 0) + // found a line which is not commented + return false; + + } + + return true; + + } catch (BadLocationException x) { + // should not happen + CUIPlugin.getDefault().log(x); + } + + return false; + } + + /** + * Implementation of the IUpdate prototype method discovers + * the operation through the current editor's + * ITextOperationTarget adapter, and sets the enabled state + * accordingly. + */ + public void update() { + super.update(); + + if (!canModifyEditor()) { + setEnabled(false); + return; + } + + ITextEditor editor= getTextEditor(); + if (fOperationTarget == null && editor != null) + fOperationTarget= (ITextOperationTarget) editor.getAdapter(ITextOperationTarget.class); + + boolean isEnabled= (fOperationTarget != null && fOperationTarget.canDoOperation(ITextOperationTarget.PREFIX) && fOperationTarget.canDoOperation(ITextOperationTarget.STRIP_PREFIX)); + setEnabled(isEnabled); + } + + /* + * @see TextEditorAction#setEditor(ITextEditor) + */ + public void setEditor(ITextEditor editor) { + super.setEditor(editor); + fOperationTarget= null; + } + + /** + * For the different content types, get its default comment prefix and store the prefixes. + * @param sourceViewer + * @param configuration + */ + public void configure(ISourceViewer sourceViewer, SourceViewerConfiguration configuration) { + fPrefixesMap= null; + + String[] types= configuration.getConfiguredContentTypes(sourceViewer); + Map prefixesMap= new HashMap(types.length); + for (int i= 0; i < types.length; i++) { + String type= types[i]; + String[] prefixes= configuration.getDefaultPrefixes(sourceViewer, type); + if (prefixes != null && prefixes.length > 0) { + int emptyPrefixes= 0; + for (int j= 0; j < prefixes.length; j++) + if (prefixes[j].length() == 0) + emptyPrefixes++; + + if (emptyPrefixes > 0) { + String[] nonemptyPrefixes= new String[prefixes.length - emptyPrefixes]; + for (int j= 0, k= 0; j < prefixes.length; j++) { + String prefix= prefixes[j]; + if (prefix.length() != 0) { + nonemptyPrefixes[k]= prefix; + k++; + } + } + prefixes= nonemptyPrefixes; + } + + prefixesMap.put(type, prefixes); + } + } + fDocumentPartitioning= configuration.getConfiguredDocumentPartitioning(sourceViewer); + fPrefixesMap= prefixesMap; + } +}