IDEA-56995 Disable code formatting per region using comments

This commit is contained in:
Rustam Vishnyakov
2013-10-03 13:13:05 +04:00
parent 16bc69c6a6
commit 9b42e988a9
11 changed files with 389 additions and 5 deletions
@@ -0,0 +1,25 @@
import java.util.*;
public class CollectionTest {
public static void main(String[] args) {
int size;
HashSet collection = new HashSet();
// @formatter:off
String str1 = "Yellow",
str2 = "White",
str3 = "Green",
str4 = "Blue";
// @formatter:on
Iterator iterator;
// @formatter:off
collection.add(str1);
collection.add(str2);
collection.add(str3);
// @formatter:on
collection.add(str4);
iterator = collection.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
}
}
@@ -0,0 +1,25 @@
import java.util.*;
public class CollectionTest {
public static void main(String[] args) {
int size;
HashSet collection = new HashSet();
// The following fragement must not be formatteed
String str1 = "Yellow",
str2 = "White",
str3 = "Green",
str4 = "Blue";
// end of fragment
Iterator iterator;
// do not format the following:
collection.add( str1 );
collection.add( str2 );
collection.add( str3 );
// end of fragment
collection.add(str4);
iterator = collection.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
}
}
@@ -0,0 +1,25 @@
import java.util.*;
public class CollectionTest {
public static void main(String[] args) {
int size;
HashSet collection = new HashSet();
// The following fragement must not be formatteed
String str1 = "Yellow",
str2 = "White",
str3 = "Green",
str4 = "Blue";
// end of fragment
Iterator iterator;
// do not format the following:
collection.add( str1 );
collection.add( str2 );
collection.add( str3 );
// end of fragment
collection.add(str4);
iterator = collection.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
}
}
@@ -0,0 +1,25 @@
import java.util.*;
public class CollectionTest {
public static void main(String[] args) {
int size;
HashSet collection = new HashSet();
// @formatter:off
String str1 = "Yellow",
str2 = "White",
str3 = "Green",
str4 = "Blue";
// @formatter:on
Iterator iterator;
// @formatter:off
collection.add(str1);
collection.add(str2);
collection.add(str3);
// @formatter:on
collection.add(str4);
iterator = collection.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
}
}
@@ -3001,4 +3001,18 @@ public void testSCR260() throws Exception {
}
public void testFormatterOnOffTags() throws Exception {
getSettings().getRootSettings().FORMATTER_TAGS_ENABLED = true;
doTest();
}
public void testFormatterOnOffTagsWithRegexp() throws Exception {
CodeStyleSettings settings = getSettings().getRootSettings();
settings.FORMATTER_TAGS_ENABLED = true;
settings.FORMATTER_TAGS_ACCEPT_REGEXP = true;
settings.FORMATTER_OFF_TAG = "not.*format";
settings.FORMATTER_ON_TAG = "end.*fragment";
doTest();
}
}
@@ -36,6 +36,8 @@ import org.jetbrains.annotations.TestOnly;
import javax.swing.*;
import java.util.*;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
public class CodeStyleSettings extends CommonCodeStyleSettings implements Cloneable, JDOMExternalizable {
@@ -372,6 +374,55 @@ public class CodeStyleSettings extends CommonCodeStyleSettings implements Clonea
//----------------------------------------------------------------------------------------
// region Formatter control
public boolean FORMATTER_TAGS_ENABLED = false;
public String FORMATTER_ON_TAG = "@formatter:on";
public String FORMATTER_OFF_TAG = "@formatter:off";
public volatile boolean FORMATTER_TAGS_ACCEPT_REGEXP = false;
private volatile Pattern myFormatterOffPattern = null;
private volatile Pattern myFormatterOnPattern = null;
@Nullable
public Pattern getFormatterOffPattern() {
if (myFormatterOffPattern == null && FORMATTER_TAGS_ENABLED && FORMATTER_TAGS_ACCEPT_REGEXP) {
myFormatterOffPattern = getPatternOrDisableRegexp(FORMATTER_OFF_TAG);
}
return myFormatterOffPattern;
}
public void setFormatterOffPattern(@Nullable Pattern formatterOffPattern) {
myFormatterOffPattern = formatterOffPattern;
}
@Nullable
public Pattern getFormatterOnPattern() {
if (myFormatterOffPattern == null && FORMATTER_TAGS_ENABLED && FORMATTER_TAGS_ACCEPT_REGEXP) {
myFormatterOnPattern = getPatternOrDisableRegexp(FORMATTER_ON_TAG);
}
return myFormatterOnPattern;
}
public void setFormatterOnPattern(@Nullable Pattern formatterOnPattern) {
myFormatterOnPattern = formatterOnPattern;
}
@Nullable
private Pattern getPatternOrDisableRegexp(@NotNull String markerText) {
try {
return Pattern.compile(markerText);
}
catch (PatternSyntaxException pse) {
LOG.error("Loaded regexp pattern is invalid: '" + markerText + "', error message: " + pse.getMessage());
FORMATTER_TAGS_ACCEPT_REGEXP = false;
return null;
}
}
// endregion
//----------------------------------------------------------------------------------------
private CodeStyleSettings myParentSettings;
@@ -7,7 +7,7 @@
<properties/>
<border type="none"/>
<children>
<grid id="6a116" layout-manager="GridLayoutManager" row-count="5" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="6a116" layout-manager="GridLayoutManager" row-count="6" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints border-constraint="West"/>
<properties/>
@@ -57,7 +57,7 @@
</component>
<vspacer id="f290d">
<constraints>
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
<grid row="5" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<grid id="2586a" binding="myDefaultIndentOptionsPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
@@ -71,6 +71,67 @@
<border type="none" title="Default Indent Options"/>
<children/>
</grid>
<grid id="f6e61" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="4" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<clientProperties>
<BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/>
</clientProperties>
<border type="none" title-resource-bundle="messages/ApplicationBundle" title-key="settings.code.style.general.formatter.control"/>
<children>
<component id="1afd0" class="com.intellij.ui.components.JBLabel">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="messages/ApplicationBundle" key="settings.code.style.general.formatter.off.tag"/>
</properties>
</component>
<component id="a4d" class="javax.swing.JTextField" binding="myFormatterOffTagField">
<constraints>
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties/>
</component>
<component id="66f0a" class="javax.swing.JTextField" binding="myFormatterOnTagField">
<constraints>
<grid row="3" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties/>
</component>
<component id="96c9" class="com.intellij.ui.components.JBLabel">
<constraints>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="messages/ApplicationBundle" key="settings.code.style.general.formatter.on.tag"/>
</properties>
</component>
<component id="f7481" class="javax.swing.JCheckBox" binding="myAcceptRegularExpressionsCheckBox">
<constraints>
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="messages/ApplicationBundle" key="settings.code.style.general.formatter.marker.regexp"/>
</properties>
</component>
<component id="3b152" class="javax.swing.JCheckBox" binding="myEnableFormatterTags">
<constraints>
<grid row="0" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="messages/ApplicationBundle" key="settings.code.style.general.enable.formatter.tags"/>
</properties>
</component>
</children>
</grid>
</children>
</grid>
<hspacer id="b7fa6">
@@ -26,21 +26,33 @@ import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypes;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.ui.popup.BalloonBuilder;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.DisplayPriority;
import com.intellij.psi.codeStyle.FileTypeIndentOptionsProvider;
import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider;
import com.intellij.ui.awt.RelativePoint;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
public class GeneralCodeStylePanel extends CodeStyleAbstractPanel {
@SuppressWarnings("UnusedDeclaration")
@@ -57,6 +69,10 @@ public class GeneralCodeStylePanel extends CodeStyleAbstractPanel {
private JPanel myPanel;
private JCheckBox myCbWrapWhenTypingReachesRightMargin;
private JPanel myDefaultIndentOptionsPanel;
private JCheckBox myEnableFormatterTags;
private JTextField myFormatterOnTagField;
private JTextField myFormatterOffTagField;
private JCheckBox myAcceptRegularExpressionsCheckBox;
private final SmartIndentOptionsEditor myIndentOptionsEditor;
@@ -91,6 +107,16 @@ public class GeneralCodeStylePanel extends CodeStyleAbstractPanel {
myIndentOptionsEditor = new SmartIndentOptionsEditor();
myDefaultIndentOptionsPanel.add(myIndentOptionsEditor.createPanel(), BorderLayout.CENTER);
myEnableFormatterTags.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
boolean tagsEnabled = myEnableFormatterTags.isSelected();
myAcceptRegularExpressionsCheckBox.setEnabled(tagsEnabled);
myFormatterOnTagField.setEnabled(tagsEnabled);
myFormatterOffTagField.setEnabled(tagsEnabled);
}
});
}
@Nullable
@@ -130,6 +156,36 @@ public class GeneralCodeStylePanel extends CodeStyleAbstractPanel {
settings.WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN = myCbWrapWhenTypingReachesRightMargin.isSelected();
myIndentOptionsEditor.setEnabled(true);
myIndentOptionsEditor.apply(settings, settings.OTHER_INDENT_OPTIONS);
settings.FORMATTER_TAGS_ENABLED = myEnableFormatterTags.isSelected();
settings.FORMATTER_TAGS_ACCEPT_REGEXP = myAcceptRegularExpressionsCheckBox.isSelected();
settings.FORMATTER_OFF_TAG = getTagText(myFormatterOffTagField, settings.FORMATTER_OFF_TAG);
settings.setFormatterOffPattern(compilePattern(settings, myFormatterOffTagField, settings.FORMATTER_OFF_TAG));
settings.FORMATTER_ON_TAG = getTagText(myFormatterOnTagField, settings.FORMATTER_ON_TAG);
settings.setFormatterOnPattern(compilePattern(settings, myFormatterOnTagField, settings.FORMATTER_ON_TAG));
}
@Nullable
private static Pattern compilePattern(CodeStyleSettings settings, JTextField field, String patternText) {
try {
return Pattern.compile(patternText);
}
catch (PatternSyntaxException pse) {
settings.FORMATTER_TAGS_ACCEPT_REGEXP = false;
showError(field, ApplicationBundle.message("settings.code.style.general.formatter.marker.invalid.regexp"));
return null;
}
}
private static String getTagText(JTextField field, String defualtValue) {
String fieldText = field.getText();
if (StringUtil.isEmpty(field.getText())) {
field.setText(defualtValue);
return defualtValue;
}
return fieldText;
}
@Nullable
@@ -159,6 +215,18 @@ public class GeneralCodeStylePanel extends CodeStyleAbstractPanel {
if (!Comparing.equal(myRightMarginSpinner.getValue(), settings.RIGHT_MARGIN)) return true;
myIndentOptionsEditor.setEnabled(true);
if (myEnableFormatterTags.isSelected()) {
if (
!settings.FORMATTER_TAGS_ENABLED ||
settings.FORMATTER_TAGS_ACCEPT_REGEXP != myAcceptRegularExpressionsCheckBox.isSelected() ||
!StringUtil.equals(myFormatterOffTagField.getText(), settings.FORMATTER_OFF_TAG) ||
!StringUtil.equals(myFormatterOnTagField.getText(), settings.FORMATTER_ON_TAG)) return true;
}
else {
if (settings.FORMATTER_TAGS_ENABLED) return true;
}
return myIndentOptionsEditor.isModified(settings, settings.OTHER_INDENT_OPTIONS);
}
@@ -188,6 +256,16 @@ public class GeneralCodeStylePanel extends CodeStyleAbstractPanel {
myCbWrapWhenTypingReachesRightMargin.setSelected(settings.WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN);
myIndentOptionsEditor.reset(settings, settings.OTHER_INDENT_OPTIONS);
myIndentOptionsEditor.setEnabled(true);
myAcceptRegularExpressionsCheckBox.setSelected(settings.FORMATTER_TAGS_ACCEPT_REGEXP);
myAcceptRegularExpressionsCheckBox.setEnabled(settings.FORMATTER_TAGS_ENABLED);
myEnableFormatterTags.setSelected(settings.FORMATTER_TAGS_ENABLED);
myFormatterOnTagField.setText(settings.FORMATTER_ON_TAG);
myFormatterOnTagField.setEnabled(settings.FORMATTER_TAGS_ENABLED);
myFormatterOffTagField.setText(settings.FORMATTER_OFF_TAG);
myFormatterOffTagField.setEnabled(settings.FORMATTER_TAGS_ENABLED);
}
@Override
@@ -205,4 +283,16 @@ public class GeneralCodeStylePanel extends CodeStyleAbstractPanel {
public Language getDefaultLanguage() {
return null;
}
private static void showError(final JTextField field, final String message) {
BalloonBuilder balloonBuilder = JBPopupFactory.getInstance()
.createHtmlTextBalloonBuilder(message, MessageType.ERROR.getDefaultIcon(), MessageType.ERROR.getPopupBackground(), null);
balloonBuilder.setFadeoutTime(1500);
final Balloon balloon = balloonBuilder.createBalloon();
final Rectangle rect = field.getBounds();
final Point p = new Point(0, rect.height);
final RelativePoint point = new RelativePoint(field, p);
balloon.show(point, Balloon.Position.below);
Disposer.register(ProjectManager.getInstance().getDefaultProject(), balloon);
}
}
@@ -1285,7 +1285,7 @@ class FormatProcessor {
super(FormattingStateId.WRAPPING_BLOCKS);
myModel = model;
myWrapper = InitialInfoBuilder.prepareToBuildBlocksSequentially(
root, model, affectedRanges, myDefaultIndentOption, interestingOffset, myProgressCallback
root, model, affectedRanges, mySettings, myDefaultIndentOption, interestingOffset, myProgressCallback
);
}
@@ -17,9 +17,14 @@
package com.intellij.formatting;
import com.intellij.diagnostic.LogMessageEx;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.formatter.FormattingDocumentModelImpl;
import com.intellij.psi.formatter.ReadOnlyBlockInformationProvider;
@@ -33,6 +38,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Allows to build {@link AbstractBlockWrapper formatting block wrappers} for the target {@link Block formatting blocks}.
@@ -49,9 +55,12 @@ class InitialInfoBuilder {
@NotNull
private final FormattingProgressCallback myProgressCallback;
private final CommonCodeStyleSettings.IndentOptions myOptions;
private final CodeStyleSettings mySettings;
private final Stack<State> myStates = new Stack<State>();
private enum FormatterTag {ON, OFF, NONE}
private WhiteSpace myCurrentWhiteSpace;
private CompositeBlockWrapper myRootBlockWrapper;
private LeafBlockWrapper myPreviousBlock;
@@ -59,11 +68,13 @@ class InitialInfoBuilder {
private LeafBlockWrapper myLastTokenBlock;
private SpacingImpl myCurrentSpaceProperty;
private ReadOnlyBlockInformationProvider myReadOnlyBlockInformationProvider;
private boolean myReadOnlyMode;
private static final boolean INLINE_TABS_ENABLED = "true".equalsIgnoreCase(System.getProperty("inline.tabs.enabled"));
private InitialInfoBuilder(final FormattingDocumentModel model,
@Nullable final FormatTextRanges affectedRanges,
@NotNull CodeStyleSettings settings,
final CommonCodeStyleSettings.IndentOptions options,
final int positionOfInterest,
@NotNull FormattingProgressCallback progressCallback)
@@ -74,16 +85,19 @@ class InitialInfoBuilder {
myCurrentWhiteSpace = new WhiteSpace(0, true);
myOptions = options;
myPositionOfInterest = positionOfInterest;
myReadOnlyMode = false;
mySettings = settings;
}
public static InitialInfoBuilder prepareToBuildBlocksSequentially(Block root,
FormattingDocumentModel model,
@Nullable final FormatTextRanges affectedRanges,
@NotNull CodeStyleSettings settings,
final CommonCodeStyleSettings.IndentOptions options,
int interestingOffset,
@NotNull FormattingProgressCallback progressCallback)
{
InitialInfoBuilder builder = new InitialInfoBuilder(model, affectedRanges, options, interestingOffset, progressCallback);
InitialInfoBuilder builder = new InitialInfoBuilder(model, affectedRanges, settings, options, interestingOffset, progressCallback);
builder.buildFrom(root, 0, null, null, null, true);
return builder;
}
@@ -283,6 +297,17 @@ class InitialInfoBuilder {
info.arrangeParentTextRange();
}
switch (getFormatterTag(rootBlock)) {
case ON:
myReadOnlyMode = false;
break;
case OFF:
myReadOnlyMode = true;
break;
case NONE:
break;
}
TextRange textRange = rootBlock.getTextRange();
if (textRange.getLength() == 0) {
assertInvalidRanges(
@@ -309,6 +334,7 @@ class InitialInfoBuilder {
info.setSpaceProperty(myCurrentSpaceProperty);
myCurrentWhiteSpace = new WhiteSpace(textRange.getEndOffset(), false);
if (myReadOnlyMode) myCurrentWhiteSpace.setReadOnly(true);
myPreviousBlock = info;
if (myPositionOfInterest != -1 && (textRange.contains(myPositionOfInterest) || textRange.getEndOffset() == myPositionOfInterest)) {
@@ -390,6 +416,42 @@ class InitialInfoBuilder {
LogMessageEx.error(LOG, messageBuffer.toString(), buffer.toString());
}
private FormatterTag getFormatterTag(Block block) {
if (mySettings.FORMATTER_TAGS_ENABLED &&
!StringUtil.isEmpty(mySettings.FORMATTER_ON_TAG) &&
!StringUtil.isEmpty(mySettings.FORMATTER_OFF_TAG) &&
block instanceof ASTBlock) {
ASTNode node = ((ASTBlock)block).getNode();
PsiElement element = node.getPsi();
if (element != null && element instanceof PsiComment) {
CharSequence nodeChars = node.getChars();
if (mySettings.FORMATTER_TAGS_ACCEPT_REGEXP) {
Pattern onPattern = mySettings.getFormatterOnPattern();
Pattern offPattern = mySettings.getFormatterOffPattern();
if (onPattern != null && onPattern.matcher(nodeChars).find()) return FormatterTag.ON;
if (offPattern != null && offPattern.matcher(nodeChars).find()) return FormatterTag.OFF;
}
else {
for (int i = 0; i < nodeChars.length(); i++) {
if (isFormatterTagAt(nodeChars, i, mySettings.FORMATTER_ON_TAG)) return FormatterTag.ON;
if (isFormatterTagAt(nodeChars, i, mySettings.FORMATTER_OFF_TAG)) return FormatterTag.OFF;
}
}
}
}
return FormatterTag.NONE;
}
private static boolean isFormatterTagAt(@NotNull CharSequence s, int pos, @NotNull String tagName) {
if (!tagName.isEmpty() && tagName.charAt(0) == s.charAt(pos)) {
int end = pos + tagName.length();
if (end <= s.length()) {
return StringUtil.equalsIgnoreCase(s.subSequence(pos, end), tagName);
}
}
return false;
}
/**
* We want to wrap {@link Block code blocks} sequentially, hence, need to store a processing state and continue from the point
* where we stopped the processing last time.
@@ -607,4 +607,10 @@ arrangement.settings.text.entry.type.xml.tag=tag
arrangement.settings.text.entry.type.xml.attribute=attribute
checkbox.spaces.around.lambda.arrow=Lambda arrow
checkbox.spaces.around.method.ref.dbl.colon.arrow=Method reference double colon
checkbox.spaces.around.method.ref.dbl.colon.arrow=Method reference double colon
settings.code.style.general.formatter.control=Formatter Control
settings.code.style.general.enable.formatter.tags=Enable formatter markers in comments
settings.code.style.general.formatter.off.tag="Formatter off" marker\:
settings.code.style.general.formatter.on.tag="Formatter on" marker\:
settings.code.style.general.formatter.marker.regexp=Accept regular expressions
settings.code.style.general.formatter.marker.invalid.regexp=Invalid regular expression