diff options
author | Marco Stornelli | 2019-02-16 10:02:42 +0000 |
---|---|---|
committer | Jonah Graham | 2019-02-19 20:49:58 +0000 |
commit | a6d06902b1f2626f3fadbeeb55a52979af22262a (patch) | |
tree | 3b185e5159fb1d2a198e95a6a72628608906b5d9 | |
parent | f60bbf25dda0d99fe7c7cd781afae6f3455a0908 (diff) | |
download | org.eclipse.cdt-a6d06902b1f2626f3fadbeeb55a52979af22262a.tar.gz org.eclipse.cdt-a6d06902b1f2626f3fadbeeb55a52979af22262a.tar.xz org.eclipse.cdt-a6d06902b1f2626f3fadbeeb55a52979af22262a.zip |
Bug 496249: Tags for disabling/enabling CDT code formatter
Change-Id: I4389c61612da6a4ee0011a49d6aeed7b52152436
Signed-off-by: Marco Stornelli <marco.stornelli@gmail.com>
11 files changed, 484 insertions, 7 deletions
diff --git a/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF b/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF index 3d146697542..84147ef8242 100644 --- a/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF +++ b/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.cdt.core; singleton:=true -Bundle-Version: 6.6.100.qualifier +Bundle-Version: 6.7.0.qualifier Bundle-Activator: org.eclipse.cdt.core.CCorePlugin Bundle-Vendor: %providerName Bundle-Localization: plugin diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/formatter/DefaultCodeFormatterConstants.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/formatter/DefaultCodeFormatterConstants.java index 9b0f72a7a95..d4bec0effd5 100644 --- a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/formatter/DefaultCodeFormatterConstants.java +++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/formatter/DefaultCodeFormatterConstants.java @@ -91,6 +91,38 @@ public class DefaultCodeFormatterConstants { */ public static final String FALSE = "false"; //$NON-NLS-1$ + /** + * <pre> + * FORMATTER / Formatter on tag format option + * - option id: "org.eclipse.cdt.core.formatter.comment_formatter_on_tag" + * - default: @formatter:on + * </pre> + * @see CCorePlugin#FORMAT_ON_TAG + * @since 6.7 + */ + public static final String FORMATTER_COMMENT_ON_TAG = CCorePlugin.PLUGIN_ID + ".formatter.comment_formatter_on_tag"; //$NON-NLS-1$ + /** + * <pre> + * FORMATTER / Formatter off tag format option + * - option id: "org.eclipse.cdt.core.formatter.comment_formatter_off_tag" + * - default: @formatter:off + * </pre> + * @see CCorePlugin#FORMAT_OFF_TAG + * @since 6.7 + */ + public static final String FORMATTER_COMMENT_OFF_TAG = CCorePlugin.PLUGIN_ID + + ".formatter.comment_formatter_off_tag"; //$NON-NLS-1$ + /** + * <pre> + * FORMATTER / Formatter on tag format option + * - option id: "org.eclipse.cdt.core.formatter.use_comment_formatter_tag" + * - default: true + * </pre> + * @since 6.7 + */ + public static final String FORMATTER_USE_COMMENT_TAG = CCorePlugin.PLUGIN_ID + + ".formatter.use_comment_formatter_tag"; //$NON-NLS-1$ + // /** // * <pre> // * FORMATTER / Option to align type members of a type declaration on column @@ -2484,6 +2516,21 @@ public class DefaultCodeFormatterConstants { */ public static final int WRAP_ONE_PER_LINE = 3; + /** + * <pre> + * FORMATTER / Default formatter on tag + * </pre> + * @since 6.7 + */ + public static final String FORMATTER_ON_TAG = "@formatter:on"; //$NON-NLS-1$ + /** + * <pre> + * FORMATTER / Default formatter off tag + * </pre> + * @since 6.7 + */ + public static final String FORMATTER_OFF_TAG = "@formatter:off"; //$NON-NLS-1$ + /* * Private constants. */ diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/formatter/DefaultCodeFormatterOptions.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/formatter/DefaultCodeFormatterOptions.java index 6aac8f17204..65d4101d39a 100644 --- a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/formatter/DefaultCodeFormatterOptions.java +++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/formatter/DefaultCodeFormatterOptions.java @@ -264,6 +264,12 @@ public class DefaultCodeFormatterOptions { public boolean use_tabs_only_for_leading_indentations; public int initial_indentation_level; public String line_separator; + /** @since 6.7 */ + public String comment_formatter_on_tag; + /** @since 6.7 */ + public String comment_formatter_off_tag; + /** @since 6.7 */ + public boolean use_fomatter_comment_tag; private DefaultCodeFormatterOptions() { // cannot be instantiated @@ -282,6 +288,11 @@ public class DefaultCodeFormatterOptions { public Map<String, String> getMap() { Map<String, String> options = new HashMap<>(); + options.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_ON_TAG, comment_formatter_on_tag); + options.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_OFF_TAG, comment_formatter_off_tag); + options.put(DefaultCodeFormatterConstants.FORMATTER_USE_COMMENT_TAG, + this.use_fomatter_comment_tag ? DefaultCodeFormatterConstants.TRUE + : DefaultCodeFormatterConstants.FALSE); // options.put(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_ALLOCATION_EXPRESSION, getAlignment(this.alignment_for_arguments_in_allocation_expression)); options.put(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_METHOD_INVOCATION, getAlignment(this.alignment_for_arguments_in_method_invocation)); @@ -2025,10 +2036,25 @@ public class DefaultCodeFormatterOptions { this.tab_char = MIXED; } } + final Object formatterCommentOnTag = settings.get(DefaultCodeFormatterConstants.FORMATTER_COMMENT_ON_TAG); + if (formatterCommentOnTag != null) { + this.comment_formatter_on_tag = (String) formatterCommentOnTag; + } + final Object formatterCommentOffTag = settings.get(DefaultCodeFormatterConstants.FORMATTER_COMMENT_OFF_TAG); + if (formatterCommentOffTag != null) { + this.comment_formatter_off_tag = (String) formatterCommentOffTag; + } + final Object useFormatterCommentTag = settings.get(DefaultCodeFormatterConstants.FORMATTER_USE_COMMENT_TAG); + if (useFormatterCommentTag != null) { + this.use_fomatter_comment_tag = DefaultCodeFormatterConstants.TRUE.equals(useFormatterCommentTag); + } } public void setDefaultSettings() { // this.alignment_for_arguments_in_allocation_expression = Alignment.M_COMPACT_SPLIT; + this.comment_formatter_on_tag = DefaultCodeFormatterConstants.FORMATTER_ON_TAG; + this.comment_formatter_off_tag = DefaultCodeFormatterConstants.FORMATTER_OFF_TAG; + this.use_fomatter_comment_tag = true; this.alignment_for_arguments_in_method_invocation = Alignment.M_COMPACT_SPLIT; this.alignment_for_assignment = Alignment.M_COMPACT_SPLIT; this.alignment_for_base_clause_in_type_declaration = Alignment.M_NEXT_PER_LINE_SPLIT; diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/formatter/CodeFormatterVisitor.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/formatter/CodeFormatterVisitor.java index 22ae42e2b46..4ed22399500 100644 --- a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/formatter/CodeFormatterVisitor.java +++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/formatter/CodeFormatterVisitor.java @@ -35,6 +35,7 @@ import org.eclipse.cdt.core.dom.ast.IASTBinaryExpression; import org.eclipse.cdt.core.dom.ast.IASTBreakStatement; import org.eclipse.cdt.core.dom.ast.IASTCaseStatement; import org.eclipse.cdt.core.dom.ast.IASTCastExpression; +import org.eclipse.cdt.core.dom.ast.IASTComment; import org.eclipse.cdt.core.dom.ast.IASTCompositeTypeSpecifier; import org.eclipse.cdt.core.dom.ast.IASTCompoundStatement; import org.eclipse.cdt.core.dom.ast.IASTConditionalExpression; @@ -438,7 +439,8 @@ public class CodeFormatterVisitor extends ASTVisitor implements ICPPASTVisitor, localScanner.setSource(compilationUnitSource); scribe.initializeScanner(compilationUnitSource); - scribe.setSkipPositions(collectInactiveCodePositions(unit)); + scribe.setSkipInactivePositions(collectInactiveCodePositions(unit)); + scribe.setSkipForbiddenPositions(collectNoFormatCodePositions(unit)); fStatus = new MultiStatus(CCorePlugin.PLUGIN_ID, 0, "Formatting problem(s) in '" + unit.getFilePath() + "'", //$NON-NLS-1$//$NON-NLS-2$ null); @@ -4466,6 +4468,64 @@ public class CodeFormatterVisitor extends ASTVisitor implements ICPPASTVisitor, } /** + * Collect source positions of no-format sections in the given translation unit. + * + * @param translationUnit the {@link IASTTranslationUnit}, may be <code>null</code> + * @return a {@link List} of {@link Position}s + */ + private List<Position> collectNoFormatCodePositions(IASTTranslationUnit translationUnit) { + if (translationUnit == null || !this.preferences.use_fomatter_comment_tag) { + return Collections.emptyList(); + } + String fileName = translationUnit.getFilePath(); + if (fileName == null) { + return Collections.emptyList(); + } + List<Position> positions = new ArrayList<>(); + int inactiveCodeStart = -1; + boolean inInactiveCode = false; + + IASTComment[] commentsStmts = translationUnit.getComments(); + + for (IASTComment commentStmt : commentsStmts) { + IASTComment statement = commentStmt; + if (!statement.isPartOfTranslationUnitFile()) { + // comment is from a different file + continue; + } + IASTNodeLocation nodeLocation = statement.getFileLocation(); + if (nodeLocation == null) { + continue; + } + + String comment = new String(statement.getComment()); + /** + * According to JDT formatter rules, we need to evaluate the latest tag if both + * are defined at the same time in the comment. + */ + int offPos = comment.lastIndexOf(this.preferences.comment_formatter_off_tag); + int onPos = comment.lastIndexOf(this.preferences.comment_formatter_on_tag); + if (offPos != -1 && offPos > onPos) { + if (!inInactiveCode) { + inactiveCodeStart = nodeLocation.getNodeOffset(); + inInactiveCode = true; + } + } else if (onPos != -1 && onPos > offPos) { + if (inInactiveCode) { + int inactiveCodeEnd = nodeLocation.getNodeOffset(); + positions.add(new Position(inactiveCodeStart, inactiveCodeEnd - inactiveCodeStart)); + } + inInactiveCode = false; + } + } + if (inInactiveCode) { + positions.add(new Position(inactiveCodeStart, translationUnit.getFileLocation().getNodeLength())); + inInactiveCode = false; + } + return positions; + } + + /** * Collect source positions of preprocessor-hidden branches * in the given translation unit. * diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/formatter/Scribe.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/formatter/Scribe.java index 2b34203f45b..4bdc4a57ebb 100644 --- a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/formatter/Scribe.java +++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/formatter/Scribe.java @@ -88,7 +88,14 @@ public class Scribe { private int textRegionStart; public int scannerEndPosition; - private List<Position> fSkipPositions = Collections.emptyList(); + /** + * It keeps the list of inactive region. + */ + private List<Position> fSkipInactivePositions = Collections.emptyList(); + /** + * It keeps the list of no-format region. + */ + private List<Position> fSkipForbiddenPositions = Collections.emptyList(); private boolean skipOverInactive; @@ -668,10 +675,19 @@ public class Scribe { } /** + * Set the positions where we don't want to perform any check + * @param list The list of positions + */ + public void setSkipForbiddenPositions(List<Position> list) { + if (list != null) + fSkipForbiddenPositions = list; + } + + /** * @param list */ - public void setSkipPositions(List<Position> list) { - fSkipPositions = list; + public void setSkipInactivePositions(List<Position> list) { + fSkipInactivePositions = list; skipOverInactive = !list.isEmpty(); } @@ -1287,8 +1303,22 @@ public class Scribe { * @param offset * @return */ + private boolean isForbidden(int offset) { + for (Iterator<Position> iter = fSkipForbiddenPositions.iterator(); iter.hasNext();) { + Position pos = iter.next(); + if (pos.includes(offset)) { + return true; + } + } + return false; + } + + /** + * @param offset + * @return + */ private Position getInactivePosAt(int offset) { - for (Iterator<Position> iter = fSkipPositions.iterator(); iter.hasNext();) { + for (Iterator<Position> iter = fSkipInactivePositions.iterator(); iter.hasNext();) { Position pos = iter.next(); if (pos.includes(offset)) { return pos; @@ -2037,7 +2067,7 @@ public class Scribe { } boolean shouldSkip(int offset) { - return offset >= fSkipStartOffset && offset < fSkipEndOffset; + return ((offset >= fSkipStartOffset && offset < fSkipEndOffset) || isForbidden(offset)); } void skipRange(int offset, int endOffset) { diff --git a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/CodeFormatterTest.java b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/CodeFormatterTest.java index 4c2a10cc35a..f959926efae 100644 --- a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/CodeFormatterTest.java +++ b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/CodeFormatterTest.java @@ -3563,4 +3563,44 @@ public class CodeFormatterTest extends BaseUITestCase { public void testAttributedNamedScopedEnumDeclaration_Bug535256_4() throws Exception { assertFormatterResult(); } + + ////@formatter:off + //int + //main(){ + //return + //0 + //;} + ////@formatter:on + + ////@formatter:off + //int + //main(){ + //return + //0 + //;} + ////@formatter:on + public void testOnOffTags() throws Exception { + fOptions.put(DefaultCodeFormatterConstants.FORMATTER_USE_COMMENT_TAG, true); + fOptions.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_ON_TAG, "@formatter:on"); + fOptions.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_OFF_TAG, "@formatter:off"); + assertFormatterResult(); + } + + ////@formatter:off + // void this_line_intentionally_indented() { + // int x; + // } + ////@formatter:on + + ////@formatter:off + // void this_line_intentionally_indented() { + // int x; + // } + ////@formatter:on + public void testOnOffTagsDoesNotChangeFirstLineIndent() throws Exception { + fOptions.put(DefaultCodeFormatterConstants.FORMATTER_USE_COMMENT_TAG, true); + fOptions.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_ON_TAG, "@formatter:on"); + fOptions.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_OFF_TAG, "@formatter:off"); + assertFormatterResult(); + } } diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/formatter/FormatterMessages.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/formatter/FormatterMessages.java index 6deaa290f1b..39eb4f6a6c8 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/formatter/FormatterMessages.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/formatter/FormatterMessages.java @@ -369,6 +369,7 @@ final class FormatterMessages extends NLS { public static String ModifyDialog_tabpage_braces_title; public static String ModifyDialog_tabpage_indentation_title; public static String ModifyDialog_tabpage_whitespace_title; + public static String ModifyDialog_tabpage_formatter_tag_title; // public static String ModifyDialog_tabpage_blank_lines_title; public static String ModifyDialog_tabpage_new_lines_title; public static String ModifyDialog_tabpage_control_statements_title; @@ -402,6 +403,15 @@ final class FormatterMessages extends NLS { public static String CPreview_formatter_exception; + public static String FormatterModifyDialog_offOn_preview_header; + public static String FormatterModifyDialog_offOn_description; + public static String FormatterModifyDialog_offOn_pref_enable; + public static String FormatterModifyDialog_offOn_pref_off_tag; + public static String FormatterModifyDialog_offOn_pref_on_tag; + public static String FormatterModifyDialog_offOn_error_startsWithWhitespace; + public static String FormatterModifyDialog_offOn_error_endsWithWhitespace; + public static String FormatterModifyDialog_offOn_error_empty; + private FormatterMessages() { // Do not instantiate } diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/formatter/FormatterMessages.properties b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/formatter/FormatterMessages.properties index e03b4ad3646..c879b3ae186 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/formatter/FormatterMessages.properties +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/formatter/FormatterMessages.properties @@ -433,6 +433,7 @@ ModifyDialog_tabpage_new_lines_title=New &Lines ModifyDialog_tabpage_control_statements_title=Con&trol Statements ModifyDialog_tabpage_line_wrapping_title=Line Wra&pping ModifyDialog_tabpage_comments_title=Co&mments +ModifyDialog_tabpage_formatter_tag_title=Off/On Tags ModifyDialog_dialog_title=Profile ''{0}'' ModifyDialog_apply_button=Apply @@ -476,3 +477,12 @@ CPreview_formatter_exception=The formatter threw an unhandled exception while fo ProfileConfigurationBlock_load_profile_wrong_profile_message=Import failed. This is not a valid profile: Expected ''{0}'' but encountered ''{1}''. FormatterTabPage_ShowInvisibleCharacters_label=Show &invisible characters + +FormatterModifyDialog_offOn_preview_header=Off/On tags +FormatterModifyDialog_offOn_description=Off/On tags can be used in any comments to turn the formatter off and on in a source file.\n- At the beginning of each file, the formatter is enabled.\n- Each time the formatter sees an off tag, it disables formatting for that comment and the source after it.\n- Each time the formatter sees an on tag, it enables formatting for the source after that comment.\n +FormatterModifyDialog_offOn_pref_enable=Enable Off/On tags +FormatterModifyDialog_offOn_pref_off_tag=Off tag: +FormatterModifyDialog_offOn_pref_on_tag=On tag: +FormatterModifyDialog_offOn_error_startsWithWhitespace=This value must not start with a white space. +FormatterModifyDialog_offOn_error_endsWithWhitespace=This value must not end with a white space. +FormatterModifyDialog_offOn_error_empty=This value must not be empty. diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/formatter/FormatterModifyDialog.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/formatter/FormatterModifyDialog.java index 5838b0bc7b9..e0b242a5260 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/formatter/FormatterModifyDialog.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/formatter/FormatterModifyDialog.java @@ -38,5 +38,6 @@ public class FormatterModifyDialog extends ModifyDialog { new ControlStatementsTabPage(this, values)); addTabPage(FormatterMessages.ModifyDialog_tabpage_line_wrapping_title, new LineWrappingTabPage(this, values)); addTabPage(FormatterMessages.ModifyDialog_tabpage_comments_title, new CommentsTabPage(this, values)); + addTabPage(FormatterMessages.ModifyDialog_tabpage_formatter_tag_title, new FormatterTagTabPage(this, values)); } } diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/formatter/FormatterTagTabPage.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/formatter/FormatterTagTabPage.java new file mode 100644 index 00000000000..4b4f39eb152 --- /dev/null +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/formatter/FormatterTagTabPage.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2019 Marco Stornelli + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + *******************************************************************************/ +package org.eclipse.cdt.internal.ui.preferences.formatter; + +import java.util.Map; +import java.util.Observable; +import java.util.Observer; + +import org.eclipse.cdt.core.formatter.DefaultCodeFormatterConstants; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Group; + +public class FormatterTagTabPage extends FormatterTabPage { + + private final String PREVIEW = createPreviewHeader(FormatterMessages.FormatterModifyDialog_offOn_preview_header) + + "void method1() { doSomething(); }\n\n// @formatter:off\n" //$NON-NLS-1$ + + "void method2() { doSomething(); }\n// @formatter:on\n\n" //$NON-NLS-1$ + + "void method3() { doSomething(); }\n\n" //$NON-NLS-1$ + + "/* @formatter:off */\n\nvoid\nfoo()\n;"; //$NON-NLS-1$ + + private StringPreference fOnTag; + private StringPreference fOffTag; + private CheckboxPreference fUseTag; + private TranslationUnitPreview fPreview; + + public FormatterTagTabPage(IModificationListener modifyListener, Map<String, String> workingValues) { + super(modifyListener, workingValues); + } + + @Override + protected void initializePage() { + fPreview.setPreviewText(PREVIEW); + } + + @Override + protected void doUpdatePreview() { + super.doUpdatePreview(); + fPreview.update(); + } + + @Override + protected void doCreatePreferences(Composite composite, int numColumns) { + final Group generalGroup = createGroup(numColumns, composite, + FormatterMessages.ModifyDialog_tabpage_formatter_tag_title); + createLabel(numColumns, generalGroup, FormatterMessages.FormatterModifyDialog_offOn_description); + fUseTag = createCheckboxPref(generalGroup, numColumns, + FormatterMessages.FormatterModifyDialog_offOn_pref_enable, + DefaultCodeFormatterConstants.FORMATTER_USE_COMMENT_TAG, FALSE_TRUE); + fUseTag.addObserver(new Observer() { + @Override + public void update(Observable arg0, Object arg1) { + fOnTag.setEnabled(fUseTag.getChecked()); + fOffTag.setEnabled(fUseTag.getChecked()); + } + }); + PreferenceValidator validator = new PreferenceValidator() { + @Override + public String validate(String value) { + if (value != null && !value.isEmpty()) { + if (Character.isWhitespace(value.charAt(0))) { + return FormatterMessages.FormatterModifyDialog_offOn_error_startsWithWhitespace; + } else if (Character.isWhitespace(value.charAt(value.length() - 1))) { + return FormatterMessages.FormatterModifyDialog_offOn_error_endsWithWhitespace; + } + return null; + } + return FormatterMessages.FormatterModifyDialog_offOn_error_empty; + } + }; + fOffTag = createStringPref(generalGroup, numColumns, FormatterMessages.FormatterModifyDialog_offOn_pref_off_tag, + DefaultCodeFormatterConstants.FORMATTER_COMMENT_OFF_TAG); + fOffTag.setValidator(validator); + fOnTag = createStringPref(generalGroup, numColumns, FormatterMessages.FormatterModifyDialog_offOn_pref_on_tag, + DefaultCodeFormatterConstants.FORMATTER_COMMENT_ON_TAG); + fOnTag.setValidator(validator); + } + + @Override + protected CPreview doCreateCPreview(Composite parent) { + fPreview = new TranslationUnitPreview(fWorkingValues, parent); + return fPreview; + } + +} diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/formatter/ModifyDialogTabPage.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/formatter/ModifyDialogTabPage.java index 4401c3080c0..65101096722 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/formatter/ModifyDialogTabPage.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/preferences/formatter/ModifyDialogTabPage.java @@ -69,6 +69,18 @@ public abstract class ModifyDialogTabPage implements IModifyDialogTabPage { }; /** + * Preference validator + */ + interface PreferenceValidator { + /** + * Validate callback + * @param value The value to be checked + * @return String error or null otherwise + */ + String validate(String value); + } + + /** * The base class of all Preference classes. A preference class provides a wrapper * around one or more SWT widgets and handles the input of values for some key. * On each change, the new value is written to the map and the listeners are notified. @@ -77,6 +89,7 @@ public abstract class ModifyDialogTabPage implements IModifyDialogTabPage { private final Map<String, String> fPreferences; private boolean fEnabled; private String fKey; + private PreferenceValidator fValidator; /** * Create a new Preference. @@ -143,6 +156,22 @@ public abstract class ModifyDialogTabPage implements IModifyDialogTabPage { * of this object has changed (enabled, key, ...). */ protected abstract void updateWidget(); + + public void setValidator(PreferenceValidator validator) { + fValidator = validator; + } + + /** + * Check if preference is valid according to its validator + * @param value The preference value + * @return Null if valid, the error string otherwise + */ + protected String isValid(String value) { + if (fValidator != null) { + return fValidator.validate(value); + } + return null; + } } /** @@ -319,6 +348,126 @@ public abstract class ModifyDialogTabPage implements IModifyDialogTabPage { } /** + * Wrapper around a textfied + */ + protected final class StringPreference extends Preference { + + private final Label fLabel; + private final Text fText; + + protected String fSelected; + protected String fOldSelected; + + /** + * Create a new NumberPreference. + * @param composite The composite on which the SWT widgets are added. + * @param numColumns The number of columns in the composite's GridLayout. + * @param preferences The map to store the values. + * @param key The key to store the values. + * @param text The label text for this Preference. + */ + public StringPreference(Composite composite, int numColumns, Map<String, String> preferences, String key, + String text) { + super(preferences, key); + + fLabel = createLabel(numColumns - 1, composite, text, GridData.FILL_HORIZONTAL); + fText = new Text(composite, SWT.SINGLE | SWT.BORDER | SWT.LEFT); + fText.setFont(composite.getFont()); + + fText.setLayoutData( + createGridData(1, GridData.HORIZONTAL_ALIGN_END, fPixelConverter.convertWidthInCharsToPixels(20))); + + updateWidget(); + + fText.addFocusListener(new FocusListener() { + @Override + public void focusGained(FocusEvent e) { + StringPreference.this.focusGained(); + } + + @Override + public void focusLost(FocusEvent e) { + StringPreference.this.focusLost(); + } + }); + + fText.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + fieldModified(); + } + }); + } + + private IStatus createErrorStatus(String error) { + return new Status(IStatus.ERROR, CUIPlugin.getPluginId(), 0, Messages.format(error), null); + } + + protected void focusGained() { + fOldSelected = fSelected; + fText.setSelection(0, fText.getCharCount()); + } + + protected void focusLost() { + updateStatus(null); + final String input = fText.getText(); + if (validInput(input) != null) + fSelected = fOldSelected; + else + fSelected = input; + if (fSelected != fOldSelected) { + saveSelected(); + fText.setText(fSelected); + } + } + + protected void fieldModified() { + final String trimInput = fText.getText().trim(); + final String error = validInput(fText.getText()); + + updateStatus(error == null ? null : createErrorStatus(error)); + + if (error == null) { + if (fSelected.equals(trimInput)) { + fSelected = trimInput; + saveSelected(); + } + } + } + + private String validInput(String input) { + return isValid(input); + } + + private void saveSelected() { + getPreferences().put(getKey(), fSelected); + setChanged(); + notifyObservers(); + } + + @Override + protected void updateWidget() { + final boolean hasKey = getKey() != null; + + fLabel.setEnabled(hasKey && getEnabled()); + fText.setEnabled(hasKey && getEnabled()); + + if (hasKey) { + String s = getPreferences().get(getKey()); + fSelected = s; + fText.setText(s); + } else { + fText.setText(""); //$NON-NLS-1$ + } + } + + @Override + public Control getControl() { + return fText; + } + } + + /** * Wrapper around a textfied which requests an integer input of a given range. */ protected final class NumberPreference extends Preference { @@ -901,6 +1050,17 @@ public abstract class ModifyDialogTabPage implements IModifyDialogTabPage { * Convenience method to create a NumberPreference. The widget is registered as * a potential focus holder, and the default updater is added. */ + protected StringPreference createStringPref(Composite composite, int numColumns, String name, String key) { + final StringPreference pref = new StringPreference(composite, numColumns, fWorkingValues, key, name); + fDefaultFocusManager.add(pref); + pref.addObserver(fUpdater); + return pref; + } + + /* + * Convenience method to create a NumberPreference. The widget is registered as + * a potential focus holder, and the default updater is added. + */ protected NumberPreference createNumberPref(Composite composite, int numColumns, String name, String key, int minValue, int maxValue) { final NumberPreference pref = new NumberPreference(composite, numColumns, fWorkingValues, key, minValue, |