1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-07-02 22:55:26 +02:00

Fix for 216437: [Formatter] Shift left/right incorrect in Mixed tab policy/indentation mode

This commit is contained in:
Anton Leherbauer 2008-02-08 12:30:10 +00:00
parent 4c7161361e
commit 00091c99cb
7 changed files with 408 additions and 49 deletions

View file

@ -0,0 +1,157 @@
/*******************************************************************************
* Copyright (c) 2008 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.ui.tests.text;
import java.util.ListResourceBundle;
import junit.extensions.TestSetup;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.ui.texteditor.ShiftAction;
import org.eclipse.cdt.core.formatter.DefaultCodeFormatterConstants;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.testplugin.CProjectHelper;
import org.eclipse.cdt.ui.tests.BaseUITestCase;
import org.eclipse.cdt.ui.tests.text.MarkOccurrenceTest.MarkOccurrenceTestSetup;
import org.eclipse.cdt.internal.ui.editor.CEditor;
/**
* Test the Shift left/right actions.
*
* @since 5.0
*/
public class ShiftActionTest extends BaseUITestCase {
private static final String PROJECT= "ShiftTests";
private static final String FILE = "shiftTest.c";
private static final class EmptyBundle extends ListResourceBundle {
protected Object[][] getContents() {
return new Object[0][];
}
}
protected static class ShiftTestSetup extends TestSetup {
private ICProject fCProject;
public ShiftTestSetup(Test test) {
super(test);
}
protected void setUp() throws Exception {
super.setUp();
fCProject= CProjectHelper.createCProject(PROJECT, null);
fCProject.setOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR, DefaultCodeFormatterConstants.MIXED);
fCProject.setOption(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE, String.valueOf(8));
fCProject.setOption(DefaultCodeFormatterConstants.FORMATTER_INDENTATION_SIZE, String.valueOf(4));
IFile file= EditorTestHelper.createFile(fCProject.getProject(), FILE, "", new NullProgressMonitor());
}
protected void tearDown () throws Exception {
EditorTestHelper.closeAllEditors();
if (fCProject != null) {
CProjectHelper.delete(fCProject);
}
super.tearDown();
}
}
private static final Class THIS= ShiftActionTest.class;
public static Test suite() {
return new ShiftTestSetup(new TestSuite(THIS));
}
private CEditor fEditor;
private SourceViewer fSourceViewer;
private IDocument fDocument;
private MarkOccurrenceTestSetup fProjectSetup;
/*
* @see junit.framework.TestCase#setUp()
*/
protected void setUp() throws Exception {
if (!ResourcesPlugin.getWorkspace().getRoot().exists(new Path(PROJECT))) {
fProjectSetup= new MarkOccurrenceTestSetup(this);
fProjectSetup.setUp();
}
fEditor= (CEditor) EditorTestHelper.openInEditor(ResourceTestHelper.findFile(PROJECT + '/' + FILE), true);
fSourceViewer= EditorTestHelper.getSourceViewer(fEditor);
fDocument= fSourceViewer.getDocument();
super.setUp();
}
/*
* @see junit.framework.TestCase#tearDown()
*/
protected void tearDown() throws Exception {
if (fProjectSetup != null) {
fProjectSetup.tearDown();
}
super.tearDown();
}
private void shiftLeft() throws Exception {
new ShiftAction(new EmptyBundle(), "prefix", fEditor, SourceViewer.SHIFT_LEFT).run();
}
private void shiftRight() throws Exception {
new ShiftAction(new EmptyBundle(), "prefix", fEditor, SourceViewer.SHIFT_RIGHT).run();
}
private void selectAll() {
fSourceViewer.setSelectedRange(0, fDocument.getLength());
}
//void f() {
// for(;;) {
//}
// void f() {
// for(;;) {
// }
public void testShiftRight() throws Exception {
StringBuffer[] contents= getContentsForTest(2);
String before= contents[0].toString();
String after= contents[1].toString();
fDocument.set(before);
selectAll();
shiftRight();
assertEquals(after, fDocument.get());
}
// void f() {
// for(;;) {
// }
//void f() {
// for(;;) {
//}
public void testShiftLeft() throws Exception {
StringBuffer[] contents= getContentsForTest(2);
String before= contents[0].toString();
String after= contents[1].toString();
fDocument.set(before);
selectAll();
shiftLeft();
assertEquals(after, fDocument.get());
}
}

View file

@ -33,6 +33,7 @@ public class TextTestSuite extends TestSuite {
addTest(BracketInserterTest.suite());
addTest(IndentActionTest.suite());
addTest(FormatActionTest.suite());
addTest(ShiftActionTest.suite());
addTest(CodeFormatterTest.suite());
addTest(CIndenterTest.suite());

View file

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2000, 2007 IBM Corporation and others.
* Copyright (c) 2000, 2008 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
@ -269,11 +269,12 @@ public class IndentAction extends TextEditorAction {
String currentIndent= document.get(offset, length);
// if we are right before the text start / line end, and already after the insertion point
// then just insert a tab.
// then just shift to the right
if (fIsTabAction && caret == end && whiteSpaceLength(currentIndent) >= whiteSpaceLength(indent)) {
String tab= getTabEquivalent();
document.replace(caret, 0, tab);
fCaretOffset= caret + tab.length();
int indentWidth= whiteSpaceLength(currentIndent) + getIndentSize();
String replacement= IndentUtil.changePrefix(currentIndent.trim(), indentWidth, getTabSize(), useSpaces());
document.replace(offset, length, replacement);
fCaretOffset= offset + replacement.length();
return true;
}
@ -329,36 +330,17 @@ public class IndentAction extends TextEditorAction {
private int whiteSpaceLength(String indent) {
if (indent == null)
return 0;
else {
int size= 0;
int l= indent.length();
int tabSize= getTabSize();
for (int i= 0; i < l; i++)
size += indent.charAt(i) == '\t' ? tabSize : 1;
return size;
}
return IndentUtil.computeVisualLength(indent, getTabSize());
}
/**
* Returns a tab equivalent, either as a tab character or as spaces, depending on the editor and
* Returns whether spaces should be used exclusively for indentation, depending on the editor and
* formatter preferences.
*
* @return a string representing one tab in the editor, never <code>null</code>
* @return <code>true</code> if only spaces should be used
*/
private String getTabEquivalent() {
String tab;
if (CCorePlugin.SPACE.equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR))) {
int size= getTabSize();
StringBuffer buf= new StringBuffer();
for (int i= 0; i< size; i++)
buf.append(' ');
tab= buf.toString();
} else {
tab= "\t"; //$NON-NLS-1$
}
return tab;
private boolean useSpaces() {
return CCorePlugin.SPACE.equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR));
}
/**
@ -371,6 +353,16 @@ public class IndentAction extends TextEditorAction {
return getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE, 4);
}
/**
* Returns the indent size used by the editor, which is deduced from the
* formatter preferences.
*
* @return the indent size as defined in the current formatter preferences
*/
private int getIndentSize() {
return getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_INDENTATION_SIZE, 4);
}
/**
* Returns <code>true</code> if empty lines should be indented, <code>false</code> otherwise.
*

View file

@ -175,6 +175,7 @@ import org.eclipse.cdt.ui.text.ICPartitions;
import org.eclipse.cdt.ui.text.folding.ICFoldingStructureProvider;
import org.eclipse.cdt.internal.core.model.ASTCache.ASTRunnable;
import org.eclipse.cdt.internal.corext.util.CodeFormatterUtil;
import org.eclipse.cdt.internal.ui.CPluginImages;
import org.eclipse.cdt.internal.ui.ICHelpContextIds;
@ -1093,8 +1094,6 @@ public class CEditor extends TextEditor implements ISelectionChangedListener, IC
public static final String INACTIVE_CODE_ENABLE = "inactiveCodeEnable"; //$NON-NLS-1$
/** Preference key for inactive code painter color */
public static final String INACTIVE_CODE_COLOR = "inactiveCodeColor"; //$NON-NLS-1$
/** Preference key for inserting spaces rather than tabs */
public final static String SPACES_FOR_TABS = DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR;
/** Preference key for automatically closing strings */
private final static String CLOSE_STRINGS = PreferenceConstants.EDITOR_CLOSE_STRINGS;
/** Preference key for automatically closing brackets and parenthesis */
@ -1211,6 +1210,13 @@ public class CEditor extends TextEditor implements ISelectionChangedListener, IC
if (fCEditorErrorTickUpdater != null) {
fCEditorErrorTickUpdater.updateEditorImage(getInputCElement());
}
ICElement element= getInputCElement();
if (element instanceof ITranslationUnit) {
fBracketMatcher.configure(((ITranslationUnit)element).getLanguage());
} else {
fBracketMatcher.configure(null);
}
}
/**
@ -1342,14 +1348,6 @@ public class CEditor extends TextEditor implements ISelectionChangedListener, IC
return;
}
if (SPACES_FOR_TABS.equals(property)) {
if (isTabsToSpacesConversionEnabled())
installTabsToSpacesConverter();
else
uninstallTabsToSpacesConverter();
return;
}
if (PreferenceConstants.EDITOR_TEXT_HOVER_MODIFIERS.equals(property))
updateHoverBehavior();
@ -1392,7 +1390,7 @@ public class CEditor extends TextEditor implements ISelectionChangedListener, IC
uninstallTabsToSpacesConverter();
installTabsToSpacesConverter();
} else {
updateIndentPrefixes();
updateIndentationMode();
}
return;
}
@ -1723,10 +1721,23 @@ public class CEditor extends TextEditor implements ISelectionChangedListener, IC
} else
tabToSpacesConverter.setLineTracker(new DefaultLineTracker());
((ITextViewerExtension7)sourceViewer).setTabsToSpacesConverter(tabToSpacesConverter);
updateIndentPrefixes();
updateIndentationMode();
}
}
private void updateIndentationMode() {
ISourceViewer sourceViewer= getSourceViewer();
if (sourceViewer instanceof CSourceViewer) {
CSourceViewer cSourceVieer= (CSourceViewer) sourceViewer;
ICElement element= getInputCElement();
ICProject project= element == null ? null : element.getCProject();
final int indentWidth= CodeFormatterUtil.getIndentWidth(project);
final boolean useSpaces= isTabsToSpacesConversionEnabled();
cSourceVieer.configureIndentation(indentWidth, useSpaces);
}
super.updateIndentPrefixes();
}
/*
* @see org.eclipse.ui.texteditor.AbstractDecoratedTextEditor#isTabsToSpacesConversionEnabled()
* @since 4.0
@ -1736,9 +1747,9 @@ public class CEditor extends TextEditor implements ISelectionChangedListener, IC
ICProject project= element == null ? null : element.getCProject();
String option;
if (project == null)
option= CCorePlugin.getOption(SPACES_FOR_TABS);
option= CCorePlugin.getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR);
else
option= project.getOption(SPACES_FOR_TABS, true);
option= project.getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR, true);
return CCorePlugin.SPACE.equals(option);
}

View file

@ -18,7 +18,11 @@ import java.util.ArrayList;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
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.ITextPresentationListener;
import org.eclipse.jface.text.Region;
@ -35,6 +39,7 @@ import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
@ -51,11 +56,11 @@ import org.eclipse.cdt.internal.ui.text.CSourceViewerConfiguration;
public class CSourceViewer extends ProjectionViewer implements IPropertyChangeListener {
/** Show outline operation id. */
public static final int SHOW_OUTLINE = 101;
public static final int SHOW_OUTLINE= 101;
/** Show type hierarchy operation id. */
public static final int SHOW_HIERARCHY = 102;
public static final int SHOW_HIERARCHY= 102;
/** Show macro explorer operation id. */
public static final int SHOW_MACRO_EXPLORER = 103;
public static final int SHOW_MACRO_EXPLORER= 103;
/** Presents outline. */
private IInformationPresenter fOutlinePresenter;
@ -111,6 +116,15 @@ public class CSourceViewer extends ProjectionViewer implements IPropertyChangeLi
* Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=195808
*/
private boolean fWasProjectionMode;
/**
* The configured indent width.
*/
private int fIndentWidth= 4;
/**
* Flag indicating whether to use spaces exclusively for indentation.
*/
private boolean fUseSpaces;
/**
* Creates new source viewer.
@ -152,6 +166,7 @@ public class CSourceViewer extends ProjectionViewer implements IPropertyChangeLi
}
super.configure(configuration);
if (configuration instanceof CSourceViewerConfiguration) {
CSourceViewerConfiguration cConfiguration= (CSourceViewerConfiguration)configuration;
fOutlinePresenter= cConfiguration.getOutlinePresenter(this);
@ -164,6 +179,12 @@ public class CSourceViewer extends ProjectionViewer implements IPropertyChangeLi
if (fMacroExplorationPresenter != null) {
fMacroExplorationPresenter.install(this);
}
String[] defaultIndentPrefixes= (String[])fIndentChars.get(IDocument.DEFAULT_CONTENT_TYPE);
if (defaultIndentPrefixes != null && defaultIndentPrefixes.length > 0) {
final int indentWidth= cConfiguration.getIndentWidth(this);
final boolean useSpaces= cConfiguration.useSpacesOnly(this);
configureIndentation(indentWidth, useSpaces);
}
}
if (fPreferenceStore != null) {
fPreferenceStore.addPropertyChangeListener(this);
@ -290,7 +311,7 @@ public class CSourceViewer extends ProjectionViewer implements IPropertyChangeLi
* @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
*/
public void propertyChange(PropertyChangeEvent event) {
String property = event.getProperty();
String property= event.getProperty();
if (AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND.equals(property)
|| AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT.equals(property)
|| AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND.equals(property)
@ -468,4 +489,114 @@ public class CSourceViewer extends ProjectionViewer implements IPropertyChangeLi
enableProjection();
}
}
/**
* Configure the indentation mode for this viewer.
*
* @param indentWidth the indentation width
* @param useSpaces if <code>true</code>, only spaces are used for indentation
*/
public void configureIndentation(int indentWidth, boolean useSpaces) {
fIndentWidth= indentWidth;
fUseSpaces= useSpaces;
}
/*
* @see org.eclipse.jface.text.TextViewer#shift(boolean, boolean, boolean)
*/
protected void shift(boolean useDefaultPrefixes, boolean right, boolean ignoreWhitespace) {
if (!useDefaultPrefixes) {
// simple shift case
adjustIndent(right, fIndentWidth, fUseSpaces);
return;
}
super.shift(useDefaultPrefixes, right, ignoreWhitespace);
}
/**
* Increase/decrease indentation of current selection.
*
* @param increase if <code>true</code>, indent is increased by one unit
* @param shiftWidth width in spaces of one indent unit
* @param useSpaces if <code>true</code>, only spaces are used for indentation
*/
protected void adjustIndent(boolean increase, int shiftWidth, boolean useSpaces) {
if (fUndoManager != null) {
fUndoManager.beginCompoundChange();
}
IDocument d= getDocument();
DocumentRewriteSession rewriteSession= null;
try {
if (d instanceof IDocumentExtension4) {
IDocumentExtension4 extension= (IDocumentExtension4) d;
rewriteSession= extension.startRewriteSession(DocumentRewriteSessionType.SEQUENTIAL);
}
Point selection= getSelectedRange();
// perform the adjustment
int tabWidth= getTextWidget().getTabs();
int startLine= d.getLineOfOffset(selection.x);
int endLine= selection.y == 0 ? startLine : d.getLineOfOffset(selection.x + selection.y - 1);
for (int line= startLine; line <= endLine; ++line) {
IRegion lineRegion= d.getLineInformation(line);
String indent= IndentUtil.getCurrentIndent(d, line, false);
int indentWidth= IndentUtil.computeVisualLength(indent, tabWidth);
int newIndentWidth= Math.max(0, indentWidth + (increase ? shiftWidth : -shiftWidth));
String newIndent= IndentUtil.changePrefix(indent.trim(), newIndentWidth, tabWidth, useSpaces);
int commonLen= getCommonPrefixLength(indent, newIndent);
if (commonLen < Math.max(indent.length(), newIndent.length())) {
if (commonLen > 0) {
indent= indent.substring(commonLen);
newIndent= newIndent.substring(commonLen);
}
final int offset= lineRegion.getOffset() + commonLen;
if (!increase && newIndent.length() > indent.length() && indent.length() > 0) {
d.replace(offset, indent.length(), ""); //$NON-NLS-1$
d.replace(offset, 0, newIndent);
} else {
d.replace(offset, indent.length(), newIndent);
}
}
}
} catch (BadLocationException x) {
// ignored
} finally {
if (rewriteSession != null) {
((IDocumentExtension4)d).stopRewriteSession(rewriteSession);
}
if (fUndoManager != null) {
fUndoManager.endCompoundChange();
}
}
}
/**
* Compute the length of the common prefix of two strings.
*
* @param s1
* @param s2
* @return the length of the common prefix
*/
private static int getCommonPrefixLength(String s1, String s2) {
final int l1= s1.length();
final int l2= s2.length();
int i= 0;
while (i < l1 && i < l2 && s1.charAt(i) == s2.charAt(i)) {
++i;
}
return i;
}
/*
* work around for memory leak in TextViewer$WidgetCommand
*/
protected void updateTextListeners(WidgetCommand cmd) {
super.updateTextListeners(cmd);
cmd.preservedText= null;
cmd.event= null;
cmd.text= null;
}
}

View file

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2005, 2007 IBM Corporation and others.
* Copyright (c) 2005, 2008 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
@ -8,6 +8,7 @@
* Contributors:
* IBM Corporation - initial API and implementation
* Sergey Prigogin, Google
* Anton Leherbauer (Wind River Systems)
*******************************************************************************/
package org.eclipse.cdt.internal.ui.editor;
@ -618,4 +619,46 @@ public final class IndentUtil {
return computed.toString();
}
/**
* Extends the string with whitespace to match displayed width.
* @param prefix add to this string
* @param displayedWidth the desired display width
* @param tabWidth the configured tab width
* @param useSpaces whether to use spaces only
*/
public static String changePrefix(String prefix, int displayedWidth, int tabWidth, boolean useSpaces) {
int column = computeVisualLength(prefix, tabWidth);
if (column > displayedWidth) {
return prefix;
}
final StringBuffer buffer = new StringBuffer(prefix);
appendIndent(buffer, displayedWidth, tabWidth, useSpaces, column);
return buffer.toString();
}
/**
* Appends whitespace to given buffer such that its visual length equals the given width.
* @param buffer the buffer to add whitespace to
* @param width the desired visual indent width
* @param tabWidth the configured tab width
* @param useSpaces whether tabs should be substituted by spaces
* @param startColumn the column where to start measurement
* @return StringBuffer
*/
private static StringBuffer appendIndent(StringBuffer buffer, int width, int tabWidth, boolean useSpaces, int startColumn) {
assert tabWidth > 0;
int tabStop = startColumn - startColumn % tabWidth;
int tabs = useSpaces ? 0 : (width-tabStop) / tabWidth;
for (int i = 0; i < tabs; ++i) {
buffer.append('\t');
tabStop += tabWidth;
startColumn = tabStop;
}
int spaces = width - startColumn;
for (int i = 0; i < spaces; ++i) {
buffer.append(' ');
}
return buffer;
}
}

View file

@ -530,6 +530,31 @@ public class CSourceViewerConfiguration extends TextSourceViewerConfiguration {
return CodeFormatterUtil.getTabWidth(getProject());
}
/**
* Returns the configured indent width for this viewer.
* @param sourceViewer
* @return the indent width
*/
public int getIndentWidth(ISourceViewer sourceViewer) {
return CodeFormatterUtil.getIndentWidth(getProject());
}
/**
* Returns whether spaces should be used exclusively for indentation.
*
* @param sourceViewer
* @return <code>true</code> if spaces should be used for indentation
*/
public boolean useSpacesOnly(ISourceViewer sourceViewer) {
ICProject project= getProject();
String option;
if (project == null)
option= CCorePlugin.getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR);
else
option= project.getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR, true);
return CCorePlugin.SPACE.equals(option);
}
/**
* @see SourceViewerConfiguration#getAnnotationHover(ISourceViewer)
*/
@ -874,5 +899,4 @@ public class CSourceViewerConfiguration extends TextSourceViewerConfiguration {
return conrolCreator;
}
}