diff --git a/plugins/groovy/src/META-INF/plugin.xml b/plugins/groovy/src/META-INF/plugin.xml
index 600e34e6e5bd..78dfd1164efe 100644
--- a/plugins/groovy/src/META-INF/plugin.xml
+++ b/plugins/groovy/src/META-INF/plugin.xml
@@ -198,6 +198,7 @@
+
diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/formatter/GroovyBraceEnforcer.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/formatter/GroovyBraceEnforcer.java
new file mode 100644
index 000000000000..948bde9ff652
--- /dev/null
+++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/formatter/GroovyBraceEnforcer.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2000-2012 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.groovy.formatter;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiManager;
+import com.intellij.psi.SmartPointerManager;
+import com.intellij.psi.SmartPsiElementPointer;
+import com.intellij.psi.codeStyle.CodeStyleManager;
+import com.intellij.psi.codeStyle.CodeStyleSettings;
+import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
+import com.intellij.psi.impl.source.SourceTreeToPsiMap;
+import com.intellij.psi.impl.source.codeStyle.CodeEditUtil;
+import com.intellij.psi.impl.source.codeStyle.PostFormatProcessorHelper;
+import com.intellij.util.IncorrectOperationException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.groovy.GroovyFileType;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyRecursiveElementVisitor;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrForStatement;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrIfStatement;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrWhileStatement;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrCodeBlock;
+
+/**
+ * @author Max Medvedev
+ */
+public class GroovyBraceEnforcer extends GroovyRecursiveElementVisitor {
+ private static final Logger LOG = Logger.getInstance(GroovyBraceEnforcer.class);
+
+ private PostFormatProcessorHelper myPostProcessor;
+
+ public GroovyBraceEnforcer(CodeStyleSettings settings) {
+ myPostProcessor = new PostFormatProcessorHelper(settings.getCommonSettings(GroovyFileType.GROOVY_LANGUAGE));
+ }
+
+ public TextRange processText(final GroovyFile source, final TextRange rangeToReformat) {
+ myPostProcessor.setResultTextRange(rangeToReformat);
+ source.accept(this);
+ return myPostProcessor.getResultTextRange();
+ }
+
+ public PsiElement process(GroovyPsiElement formatted) {
+ LOG.assertTrue(formatted.isValid());
+ formatted.accept(this);
+ return formatted;
+ }
+
+ private void replaceWithBlock(@NotNull GrStatement statement, GrStatement blockCandidate) {
+ if (!statement.isValid()) {
+ LOG.assertTrue(false);
+ }
+
+ if (!checkRangeContainsElement(blockCandidate)) return;
+
+ final PsiManager manager = statement.getManager();
+ LOG.assertTrue(manager != null);
+ GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(manager.getProject());
+
+ String oldText = blockCandidate.getText();
+ // There is a possible case that target block to wrap ends with single-line comment. Example:
+ // if (true) i = 1; // Cool assignment
+ // We can't just surround target block of code with curly braces because the closing one will be treated as comment as well.
+ // Hence, we perform a check if we have such situation at the moment and insert new line before the closing brace.
+ int lastLineFeedIndex = oldText.lastIndexOf("\n");
+ lastLineFeedIndex = Math.max(0, lastLineFeedIndex);
+ int lastLineCommentIndex = oldText.indexOf("//", lastLineFeedIndex);
+ StringBuilder buf = new StringBuilder(oldText.length() + 5);
+ buf.append("{ ").append(oldText);
+ if (lastLineCommentIndex >= 0) {
+ buf.append("\n");
+ }
+ buf.append(" }");
+ final int oldTextLength = statement.getTextLength();
+ try {
+ CodeEditUtil.replaceChild(SourceTreeToPsiMap.psiElementToTree(statement),
+ SourceTreeToPsiMap.psiElementToTree(blockCandidate),
+ SourceTreeToPsiMap.psiElementToTree(factory.createBlockStatementFromText(buf.toString(), null)));
+ CodeStyleManager.getInstance(statement.getProject()).reformat(statement, true);
+ }
+ catch (IncorrectOperationException e) {
+ LOG.error(e);
+ }
+ finally {
+ updateResultRange(oldTextLength, statement.getTextLength());
+ }
+ }
+
+
+ protected void updateResultRange(final int oldTextLength, final int newTextLength) {
+ myPostProcessor.updateResultRange(oldTextLength, newTextLength);
+ }
+
+ protected boolean checkElementContainsRange(final PsiElement element) {
+ return myPostProcessor.isElementPartlyInRange(element);
+ }
+
+ protected boolean checkRangeContainsElement(final PsiElement element) {
+ return myPostProcessor.isElementFullyInRange(element);
+ }
+
+ private void processStatement(GrStatement statement, GrStatement blockCandidate, int options) {
+ if (blockCandidate instanceof GrCodeBlock || blockCandidate == null) return;
+ if (options == CommonCodeStyleSettings.FORCE_BRACES_ALWAYS ||
+ options == CommonCodeStyleSettings.FORCE_BRACES_IF_MULTILINE && PostFormatProcessorHelper.isMultiline(statement)) {
+ replaceWithBlock(statement, blockCandidate);
+ }
+ }
+
+ @Override
+ public void visitIfStatement(GrIfStatement statement) {
+ if (checkElementContainsRange(statement)) {
+ final SmartPsiElementPointer pointer =
+ SmartPointerManager.getInstance(statement.getProject()).createSmartPsiElementPointer(statement);
+ super.visitIfStatement(statement);
+ statement = (GrIfStatement)pointer.getElement();
+ if (statement == null) return;
+
+ processStatement(statement, statement.getThenBranch(), myPostProcessor.getSettings().IF_BRACE_FORCE);
+ final GrStatement elseBranch = statement.getElseBranch();
+ if (!(elseBranch instanceof GrIfStatement) || !myPostProcessor.getSettings().SPECIAL_ELSE_IF_TREATMENT) {
+ processStatement(statement, elseBranch, myPostProcessor.getSettings().IF_BRACE_FORCE);
+ }
+ }
+ }
+
+ @Override
+ public void visitForStatement(GrForStatement statement) {
+ if (checkElementContainsRange(statement)) {
+ super.visitForStatement(statement);
+ processStatement(statement, statement.getBody(), myPostProcessor.getSettings().FOR_BRACE_FORCE);
+ }
+ }
+
+ @Override
+ public void visitWhileStatement(GrWhileStatement statement) {
+ if (checkElementContainsRange(statement)) {
+ super.visitWhileStatement(statement);
+ processStatement(statement, statement.getBody(), myPostProcessor.getSettings().WHILE_BRACE_FORCE);
+ }
+ }
+}
diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/formatter/GroovyBracePostFormatProcessor.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/formatter/GroovyBracePostFormatProcessor.java
new file mode 100644
index 000000000000..93f1622faf61
--- /dev/null
+++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/formatter/GroovyBracePostFormatProcessor.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2000-2012 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.groovy.formatter;
+
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.codeStyle.CodeStyleSettings;
+import com.intellij.psi.impl.source.codeStyle.PostFormatProcessor;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
+
+/**
+ * @author Max Medvedev
+ */
+public class GroovyBracePostFormatProcessor implements PostFormatProcessor {
+ @Override
+ public PsiElement processElement(PsiElement source, CodeStyleSettings settings) {
+ if (source instanceof GroovyPsiElement) {
+ return new GroovyBraceEnforcer(settings).process(((GroovyPsiElement)source));
+ }
+ else {
+ return source;
+ }
+ }
+
+ @Override
+ public TextRange processText(PsiFile source, TextRange rangeToReformat, CodeStyleSettings settings) {
+ if (source instanceof GroovyFile) {
+ return new GroovyBraceEnforcer(settings).processText(((GroovyFile)source), rangeToReformat);
+ }
+ else {
+ return rangeToReformat;
+ }
+ }
+}
diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/GroovyPsiElementFactory.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/GroovyPsiElementFactory.java
index f7e4751f8137..6a26d225c4af 100644
--- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/GroovyPsiElementFactory.java
+++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/GroovyPsiElementFactory.java
@@ -62,6 +62,8 @@ public abstract class GroovyPsiElementFactory implements JVMElementFactory {
public abstract GrThisReferenceExpression createThisExpression(PsiManager manager, PsiClass psiClass);
+ public abstract GrBlockStatement createBlockStatementFromText(String text, @Nullable PsiElement context);
+
public static GroovyPsiElementFactory getInstance(Project project) {
return ServiceManager.getService(project, GroovyPsiElementFactory.class);
}
diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyPsiElementFactoryImpl.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyPsiElementFactoryImpl.java
index 1e8257495d88..59d7e7738fc2 100644
--- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyPsiElementFactoryImpl.java
+++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyPsiElementFactoryImpl.java
@@ -622,6 +622,17 @@ public class GroovyPsiElementFactoryImpl extends GroovyPsiElementFactory {
return (GrThisReferenceExpression)dummy.getStatements()[0];
}
+ @Override
+ public GrBlockStatement createBlockStatementFromText(String text, PsiElement context) {
+ GroovyFileImpl file = createDummyFile("if()" + text);
+ GrStatement[] statements = file.getStatements();
+ LOG.assertTrue(statements.length == 1 && statements[0] instanceof GrIfStatement, text);
+
+ GrStatement branch = ((GrIfStatement)statements[0]).getThenBranch();
+ LOG.assertTrue(branch instanceof GrBlockStatement);
+ return (GrBlockStatement)branch;
+ }
+
public GrImportStatement createImportStatementFromText(String qName, boolean isStatic, boolean isOnDemand, String alias) {
final String text = "import " + (isStatic ? "static " : "") + qName + (isOnDemand ? ".*" : "") +
(alias != null && alias.length() > 0 ? " as " + alias : "");
diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/formatter/FormatterTest.java b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/formatter/FormatterTest.java
index ef7b27a79f5a..bcc84b3b09b0 100644
--- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/formatter/FormatterTest.java
+++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/formatter/FormatterTest.java
@@ -241,6 +241,13 @@ public class FormatterTest extends GroovyFormatterTestCase {
doTest();
}
+ public void testForceBraces() {
+ getGroovySettings().IF_BRACE_FORCE = CommonCodeStyleSettings.FORCE_BRACES_IF_MULTILINE;
+ getGroovySettings().FOR_BRACE_FORCE = CommonCodeStyleSettings.FORCE_BRACES_IF_MULTILINE;
+ getGroovySettings().WHILE_BRACE_FORCE = CommonCodeStyleSettings.FORCE_BRACES_IF_MULTILINE;
+ doTest();
+ }
+
private void doGeeseTest() {
GroovyCodeStyleSettings customSettings = myTempSettings.getCustomSettings(GroovyCodeStyleSettings.class);
boolean oldvalue = customSettings.USE_FLYING_GEESE_BRACES;
diff --git a/plugins/groovy/testdata/groovy/formatter/forceBraces.test b/plugins/groovy/testdata/groovy/formatter/forceBraces.test
new file mode 100644
index 000000000000..fc0079e12e46
--- /dev/null
+++ b/plugins/groovy/testdata/groovy/formatter/forceBraces.test
@@ -0,0 +1,33 @@
+if (a) dosomethingLong(
+)
+
+if (b) doSomethingShort()
+
+for (a; b; c) loooong(
+)
+
+for (a; b; c) short()
+
+while (true) short()
+while (true) loong(
+)
+-----
+if (a) {
+ dosomethingLong(
+ )
+}
+
+if (b) doSomethingShort()
+
+for (a; b; c) {
+ loooong(
+ )
+}
+
+for (a; b; c) short()
+
+while (true) short()
+while (true) {
+ loong(
+ )
+}
\ No newline at end of file