diff --git a/core/org.eclipse.cdt.ui/icons/etool16/show_invisible_chars.gif b/core/org.eclipse.cdt.ui/icons/etool16/show_invisible_chars.gif new file mode 100644 index 00000000000..7eb6f044d54 Binary files /dev/null and b/core/org.eclipse.cdt.ui/icons/etool16/show_invisible_chars.gif differ diff --git a/core/org.eclipse.cdt.ui/plugin.properties b/core/org.eclipse.cdt.ui/plugin.properties index 235d1bed7f0..53c38ed0957 100644 --- a/core/org.eclipse.cdt.ui/plugin.properties +++ b/core/org.eclipse.cdt.ui/plugin.properties @@ -133,8 +133,10 @@ todoTaskPrefName=Task Tags Editors.DefaultTextEditor = Default Text Editor AsmEditor.name = Assembly Editor -CFolderActionSet.label=C Folder Actions -CFolderActionSet.description=C Folder Action Set +# Show Invisble Characters +ShowInvisibleCharactersAction.label=Show Invisible Characters +ShowInvisibleCharactersAction.tooltip=Show Invisible Characters +ShowInvisibleCharactersAction.description=Show invisible (whitespace) characters CR, LF, TAB and SPACE # Task Action DeleteTaskAction.label=Delete C/C++ Markers @@ -159,18 +161,13 @@ PathNameSorter.tooltip= Sort the view by Resource Path # Action sets CSearchActionSet.label= C/C++ Search CSearchActionSet.description= Action set containing search related C/C++ actions -RefactoringActionSet.label= Refactoring -RefactoringActionSet.description= Action set containing refactoring related actions +CEditorPresentationActionSet.label=C/C++ Editor Presentation +CEditorPresentationActionSet.description=Actions to customize the C/C++ editor presentation # Menus searchMenu.label= Se&arch refactoringMenu.label= Re&factor -# Refactoring -Refactoring.renameAction.label= Re&name -Refactoring.undoAction.label= &Undo -Refactoring.redoAction.label= &Redo - # Open Type OpenTypeAction.label= Open &Type... OpenTypeAction.tooltip= Open Type diff --git a/core/org.eclipse.cdt.ui/plugin.xml b/core/org.eclipse.cdt.ui/plugin.xml index 7fff7589b18..8d89b7328f5 100644 --- a/core/org.eclipse.cdt.ui/plugin.xml +++ b/core/org.eclipse.cdt.ui/plugin.xml @@ -218,6 +218,7 @@ + @@ -801,6 +802,24 @@ tooltip="%NewProjectDropDownAction.tooltip"> + + + + + + + @@ -1052,6 +1071,12 @@ categoryId="org.eclipse.cdt.ui.category.source" id="org.eclipse.cdt.ui.edit.text.c.show.tooltip"> + + = 0 && redrawLength > 0) { + fTextWidget.redrawRange(widgetOffset, redrawLength, true); + } + } catch (BadLocationException e) { + // ignore + } + } + } + + /* + * @see org.eclipse.jface.text.IPainter#deactivate(boolean) + */ + public void deactivate(boolean redraw) { + if (fIsActive) { + fIsActive= false; + fTextWidget.removePaintListener(this); + if (redraw) { + redrawAll(true); + } + } + } + + /* + * @see org.eclipse.jface.text.IPainter#setPositionManager(org.eclipse.jface.text.IPaintPositionManager) + */ + public void setPositionManager(IPaintPositionManager manager) { + // no need for a position manager + } + + /* + * @see org.eclipse.swt.events.PaintListener#paintControl(org.eclipse.swt.events.PaintEvent) + */ + public void paintControl(PaintEvent event) { + if (fTextWidget != null) { + handleDrawRequest(event.gc, event.x, event.y, event.width, event.height); + } + } + + /** + * Draw characters in view range. + * @param gc + * @param x + * @param y + * @param w + * @param h + */ + private void handleDrawRequest(GC gc, int x, int y, int w, int h) { + int lineCount= fTextWidget.getLineCount(); + int startLine= (y + fTextWidget.getTopPixel()) / fTextWidget.getLineHeight(); + int endLine= (y + h - 1 + fTextWidget.getTopPixel()) / fTextWidget.getLineHeight(); + if (startLine <= endLine && startLine < lineCount) { + int startOffset= fTextWidget.getOffsetAtLine(startLine); + int endOffset = + endLine < lineCount - 1 ? fTextWidget.getOffsetAtLine(endLine + 1) : fTextWidget.getCharCount(); + handleDrawRequest(gc, startOffset, endOffset); + } + } + + /** + * Draw characters of content range. + * @param gc + * @param startOffset inclusive start index + * @param endOffset exclusive end index + */ + private void handleDrawRequest(GC gc, int startOffset, int endOffset) { + StyledTextContent content= fTextWidget.getContent(); + int length= endOffset - startOffset; + String text= content.getTextRange(startOffset, length); + assert text.length() == length; + StyleRange styleRange= null; + Color fg= null; + Point selection= fTextWidget.getSelection(); + StringBuffer visibleChar= new StringBuffer(20); + for (int textOffset= 0; textOffset <= length; ++textOffset) { + int delta= 0; + if (textOffset < length) { + delta= 1; + char c= text.charAt(textOffset); + switch (c) { + case ' ' : + visibleChar.append(MIDDLE_DOT); + continue; + case '\t' : + visibleChar.append(RIGHT_POINTING_DOUBLE_ANGLE); + break; + case '\r' : + visibleChar.append(LATIN_SMALL_LETTER_THORN); + if (textOffset >= length - 1 || text.charAt(textOffset + 1) != '\n') { + break; + } + continue; + case '\n' : + visibleChar.append(PILCROW_SIGN); + break; + default : + delta= 0; + break; + } + } + if (visibleChar.length() > 0) { + int widgetOffset= startOffset + textOffset - visibleChar.length() + delta; + if (widgetOffset >= selection.x && widgetOffset < selection.y) { + fg= fTextWidget.getSelectionForeground(); + } else if (styleRange == null || styleRange.start + styleRange.length <= widgetOffset) { + styleRange= fTextWidget.getStyleRangeAtOffset(widgetOffset); + if (styleRange == null || styleRange.foreground == null) { + fg= fTextWidget.getForeground(); + } else { + fg= styleRange.foreground; + } + } + draw(gc, widgetOffset, visibleChar.toString(), fg); + visibleChar.delete(0, visibleChar.length()); + } + } + } + + /** + * Redraw all of the text widgets visible content. + * @param redrawBackground If true, clean background before painting text. + */ + private void redrawAll(boolean redrawBackground) { + int startLine= fTextWidget.getTopPixel() / fTextWidget.getLineHeight(); + int startOffset= fTextWidget.getOffsetAtLine(startLine); + int endLine= 1 + (fTextWidget.getTopPixel() + fTextWidget.getClientArea().height) / fTextWidget.getLineHeight(); + int endOffset; + if (endLine >= fTextWidget.getLineCount()) { + endOffset= fTextWidget.getCharCount(); + } else { + endOffset= fTextWidget.getOffsetAtLine(endLine); + } + if (startOffset < endOffset) { + // add 2 for line separator characters + endOffset= Math.min(endOffset + 2, fTextWidget.getCharCount()); + int redrawOffset= startOffset; + int redrawLength= endOffset - redrawOffset; + fTextWidget.redrawRange(startOffset, redrawLength, redrawBackground); + } + } + + /** + * Draw character c at widget offset. + * @param gc + * @param offset the widget offset + * @param c the character to be drawn + * @param fg the foreground color + */ + private void draw(GC gc, int offset, String c, Color fg) { + Point pos= fTextWidget.getLocationAtOffset(offset); + gc.setForeground(fg); + gc.drawString(c, pos.x, pos.y, true); + } + + /** + * Convert a document offset to the corresponding widget offset. + * @param documentOffset + * @return widget offset + */ + private int getWidgetOffset(int documentOffset) { + if (fTextViewer instanceof ITextViewerExtension5) { + ITextViewerExtension5 extension= (ITextViewerExtension5)fTextViewer; + return extension.modelOffset2WidgetOffset(documentOffset); + } else { + IRegion visible= fTextViewer.getVisibleRegion(); + int widgetOffset= documentOffset - visible.getOffset(); + if (widgetOffset > visible.getLength()) { + return -1; + } + return widgetOffset; + } + } + + /** + * Convert a widget offset to the corresponding document offset. + * @param widgetOffset + * @return document offset + */ + private int getDocumentOffset(int widgetOffset) { + if (fTextViewer instanceof ITextViewerExtension5) { + ITextViewerExtension5 extension= (ITextViewerExtension5)fTextViewer; + return extension.widgetOffset2ModelOffset(widgetOffset); + } else { + IRegion visible= fTextViewer.getVisibleRegion(); + if (widgetOffset > visible.getLength()) { + return -1; + } + return widgetOffset + visible.getOffset(); + } + } + +} diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/actions/ShowInvisibleCharactersAction.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/actions/ShowInvisibleCharactersAction.java new file mode 100644 index 00000000000..b304301a413 --- /dev/null +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/actions/ShowInvisibleCharactersAction.java @@ -0,0 +1,263 @@ +/******************************************************************************* + * Copyright (c) 2006 Wind River Systems, Inc. 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: + * Anton Leherbauer (Wind River Systems) - initial API and implementation + *******************************************************************************/ +package org.eclipse.cdt.internal.ui.actions; + +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.text.IPainter; +import org.eclipse.jface.text.ITextOperationTarget; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.ITextViewerExtension2; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.ui.IEditorActionDelegate; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IPartListener2; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchPartReference; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.IWorkbenchWindowActionDelegate; +import org.eclipse.ui.texteditor.ITextEditor; +import org.eclipse.ui.texteditor.IUpdate; + +import org.eclipse.cdt.internal.ui.InvisibleCharacterPainter; + +/** + * This action toggles the visibility of whitespace characters by + * attaching/detaching an {@link InvisibleCharacterPainter} to the + * active (text-)editor. + * + * @author anton.leherbauer@windriver.com + */ +public class ShowInvisibleCharactersAction implements IEditorActionDelegate, IWorkbenchWindowActionDelegate, IUpdate { + + /** + * The PartListener to act on changes of the active editor. + */ + private class PartListener implements IPartListener2 { + + /* + * @see org.eclipse.ui.IPartListener2#partActivated(org.eclipse.ui.IWorkbenchPartReference) + */ + public void partActivated(IWorkbenchPartReference partRef) { + if (partRef.getPart(false) instanceof IEditorPart) { + updateActiveEditor(); + } + } + + /* + * @see org.eclipse.ui.IPartListener2#partBroughtToTop(org.eclipse.ui.IWorkbenchPartReference) + */ + public void partBroughtToTop(IWorkbenchPartReference partRef) { + } + + /* + * @see org.eclipse.ui.IPartListener2#partClosed(org.eclipse.ui.IWorkbenchPartReference) + */ + public void partClosed(IWorkbenchPartReference partRef) { + if (partRef.getPart(false) instanceof IEditorPart) { + updateActiveEditor(); + } + } + + /* + * @see org.eclipse.ui.IPartListener2#partDeactivated(org.eclipse.ui.IWorkbenchPartReference) + */ + public void partDeactivated(IWorkbenchPartReference partRef) { + } + + /* + * @see org.eclipse.ui.IPartListener2#partOpened(org.eclipse.ui.IWorkbenchPartReference) + */ + public void partOpened(IWorkbenchPartReference partRef) { + if (partRef.getPart(false) instanceof IEditorPart) { + updateActiveEditor(); + } + } + + /* + * @see org.eclipse.ui.IPartListener2#partHidden(org.eclipse.ui.IWorkbenchPartReference) + */ + public void partHidden(IWorkbenchPartReference partRef) { + } + + /* + * @see org.eclipse.ui.IPartListener2#partVisible(org.eclipse.ui.IWorkbenchPartReference) + */ + public void partVisible(IWorkbenchPartReference partRef) { + if (partRef.getPart(false) instanceof IEditorPart) { + updateActiveEditor(); + } + } + + /* + * @see org.eclipse.ui.IPartListener2#partInputChanged(org.eclipse.ui.IWorkbenchPartReference) + */ + public void partInputChanged(IWorkbenchPartReference partRef) { + } + + } + + private IPainter fInvisibleCharPainter; + private IWorkbenchWindow fWindow; + private IPartListener2 fPartListener; + private ITextEditor fTextEditor; + private boolean fIsChecked; + private IAction fAction; + + public ShowInvisibleCharactersAction() { + } + + /** + * Add the painter to the current editor. + */ + private void addPainter() { + ITextEditor editor= getTextEditor(); + ITextViewer viewer= getTextViewer(editor); + if (viewer instanceof ITextViewerExtension2) { + ITextViewerExtension2 viewerExt2= (ITextViewerExtension2)viewer; + if (fInvisibleCharPainter == null) { + fInvisibleCharPainter= new InvisibleCharacterPainter(viewer); + } + viewerExt2.addPainter(fInvisibleCharPainter); + } + } + + /** + * Get the ITextViewer from an ITextEditor by adapting + * it to a ITextOperationTarget. + * @param editor the ITextEditor + * @return the text viewer or null + */ + private ITextViewer getTextViewer(ITextEditor editor) { + Object target= editor.getAdapter(ITextOperationTarget.class); + if (target instanceof ITextViewer) { + return (ITextViewer)target; + } + return null; + } + + /** + * Remove the painter from the current editor. + */ + private void removePainter() { + ITextEditor editor= getTextEditor(); + ITextViewer viewer= getTextViewer(editor); + if (viewer instanceof ITextViewerExtension2) { + ITextViewerExtension2 viewerExt2= (ITextViewerExtension2)viewer; + viewerExt2.removePainter(fInvisibleCharPainter); + } + } + + private void setEditor(ITextEditor editor) { + if (editor != null && editor == getTextEditor()) { + return; + } + if (fInvisibleCharPainter != null) { + removePainter(); + fInvisibleCharPainter.deactivate(false); + fInvisibleCharPainter.dispose(); + fInvisibleCharPainter= null; + } + fTextEditor= editor; + update(); + if (fTextEditor != null && fIsChecked) { + addPainter(); + } + } + + private ITextEditor getTextEditor() { + return fTextEditor; + } + + /* + * @see org.eclipse.ui.IEditorActionDelegate#setActiveEditor(org.eclipse.jface.action.IAction, org.eclipse.ui.IEditorPart) + */ + public void setActiveEditor(IAction action, IEditorPart targetEditor) { + fAction= action; + if (targetEditor instanceof ITextEditor) { + setEditor((ITextEditor)targetEditor); + } else { + setEditor(null); + } + } + + /* + * @see org.eclipse.ui.IWorkbenchWindowActionDelegate#init(org.eclipse.ui.IWorkbenchWindow) + */ + public void init(IWorkbenchWindow window) { + fWindow= window; + fPartListener= new PartListener(); + fWindow.getActivePage().addPartListener(fPartListener); + updateActiveEditor(); + } + + /* + * @see org.eclipse.ui.IWorkbenchWindowActionDelegate#dispose() + */ + public void dispose() { + if (fWindow != null) { + IWorkbenchPage activePage= fWindow.getActivePage(); + if (activePage != null) { + activePage.removePartListener(fPartListener); + } + fPartListener= null; + fWindow= null; + } + fAction= null; + } + + /* + * @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction) + */ + public void run(IAction action) { + fAction= action; + fIsChecked= action.isChecked(); + if (fIsChecked) { + addPainter(); + } else if (fInvisibleCharPainter != null) { + removePainter(); + fInvisibleCharPainter.deactivate(true); + } + } + + /* + * @see org.eclipse.ui.IActionDelegate#selectionChanged(org.eclipse.jface.action.IAction, org.eclipse.jface.viewers.ISelection) + */ + public void selectionChanged(IAction action, ISelection selection) { + fAction= action; + fIsChecked= action.isChecked(); + update(); + } + + /** + * Update the active editor. + */ + protected void updateActiveEditor() { + IWorkbenchPage page= fWindow.getActivePage(); + if (page != null) { + IEditorPart editorPart= page.getActiveEditor(); + if (editorPart instanceof ITextEditor) { + setEditor((ITextEditor)editorPart); + } else { + setEditor(null); + } + } + } + + /* + * @see org.eclipse.ui.texteditor.IUpdate#update() + */ + public void update() { + if (fAction != null) { + fAction.setEnabled(getTextEditor() != null); + } + } + +}