From 657c523636b4fa46b8895c98abafc162cf6909cf Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Thu, 28 Jun 2012 15:33:34 +0400 Subject: [PATCH] IDEA-87639 Add a possibility to specify a dependent LF spacing if a checked range does NOT have line feeds. 1. API changes - DependentSpacingRule allows to define properties to use on dependent change trigger; 2. Corresponding changes have been made to the formatter core; --- .../formatting/DependentSpacingRule.java | 97 +++++++++++++++++++ .../src/com/intellij/formatting/Spacing.java | 42 +++++++- .../intellij/formatting/SpacingFactory.java | 39 +++++--- .../formatting/DependantSpacingImpl.java | 49 ++++++++-- .../intellij/formatting/FormatProcessor.java | 2 +- .../intellij/formatting/FormatterImpl.java | 20 +++- .../common/InjectedLanguageBlockWrapper.java | 4 +- 7 files changed, 216 insertions(+), 37 deletions(-) create mode 100644 platform/lang-api/src/com/intellij/formatting/DependentSpacingRule.java diff --git a/platform/lang-api/src/com/intellij/formatting/DependentSpacingRule.java b/platform/lang-api/src/com/intellij/formatting/DependentSpacingRule.java new file mode 100644 index 000000000000..2ee5a71a2842 --- /dev/null +++ b/platform/lang-api/src/com/intellij/formatting/DependentSpacingRule.java @@ -0,0 +1,97 @@ +/* + * 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 com.intellij.formatting; + +import com.intellij.openapi.util.TextRange; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; + +/** + * Holds settings that should be used if + * {@link Spacing#createDependentLFSpacing(int, int, TextRange, boolean, int, DependentSpacingRule) dependent spacing} + * target region changes its 'contains line feeds' status. + * + * @author Denis Zhdanov + * @since 6/28/12 1:08 PM + */ +public class DependentSpacingRule { + + enum Anchor { + MIN_LINE_FEEDS, MAX_LINE_FEEDS + } + + enum Trigger { + HAS_LINE_FEEDS, DOES_NOT_HAVE_LINE_FEEDS + } + + public static final DependentSpacingRule DEFAULT = new DependentSpacingRule(Trigger.HAS_LINE_FEEDS).registerData(Anchor.MIN_LINE_FEEDS, 1); + + private final Map myData = new HashMap(); + + @NotNull private final Trigger myTrigger; + + public DependentSpacingRule(@NotNull Trigger trigger) { + myTrigger = trigger; + } + + @NotNull + public Trigger getTrigger() { + return myTrigger; + } + + /** + * Allows to register given data for the given anchor within the current rule. + * + * @param anchor target anchor + * @param data data to register for the given anchor + * @param data's type + * @see #getData(Anchor) + */ + public DependentSpacingRule registerData(@NotNull Anchor anchor, @NotNull T data) { + myData.put(anchor, data); + return this; + } + + /** + * @param anchor target data anchor + * @return true if there is a data registered for the given anchor within the current rule; + * false otherwise + */ + public boolean hasData(@NotNull Anchor anchor) { + return myData.containsKey(anchor); + } + + /** + * Allows to retrieve data associated with the given anchor. + * + * @param anchor target anchor + * @param data's type + * @return data associated for the given anchor + * @throws IllegalArgumentException if no data is registered for the given anchor + * (use {@link #hasData(Anchor)} for the preliminary examination) + */ + @SuppressWarnings("unchecked") + public T getData(@NotNull Anchor anchor) throws IllegalArgumentException { + if (!myData.containsKey(anchor)) { + throw new IllegalArgumentException(String.format( + "No data is registered for the dependent spacing rule %s. Registered: %s", anchor, myData + )); + } + return (T)myData.get(anchor); + } +} diff --git a/platform/lang-api/src/com/intellij/formatting/Spacing.java b/platform/lang-api/src/com/intellij/formatting/Spacing.java index 4077efbdc970..923370a9025b 100644 --- a/platform/lang-api/src/com/intellij/formatting/Spacing.java +++ b/platform/lang-api/src/com/intellij/formatting/Spacing.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * 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. @@ -16,6 +16,7 @@ package com.intellij.formatting; import com.intellij.openapi.util.TextRange; +import org.jetbrains.annotations.NotNull; /** * The spacing setting for a formatting model block. Indicates the number of spaces and/or @@ -100,19 +101,50 @@ public abstract class Spacing { * to which the spacing setting instance is related, or Integer.MAX_VALUE * if the number of spaces is not limited. Spaces are deleted if there are * more than this amount of spaces in the document. - * @param dependance The text range checked for the presense of line breaks. + * @param dependency The text range checked for the presence of line breaks. * @param keepLineBreaks Whether the existing line breaks between the blocks should be preserved. * @param keepBlankLines Whether the existing blank lines between the blocks should be preserved. * @return the spacing setting instance. */ public static Spacing createDependentLFSpacing(int minSpaces, int maxSpaces, - TextRange dependance, + TextRange dependency, boolean keepLineBreaks, - int keepBlankLines) { - return myFactory.createDependentLFSpacing(minSpaces, maxSpaces, dependance, keepLineBreaks, keepBlankLines); + int keepBlankLines) + { + return createDependentLFSpacing(minSpaces, maxSpaces, dependency, keepLineBreaks, keepBlankLines, DependentSpacingRule.DEFAULT); } + /** + * Creates a spacing setting instance which uses settings from the given dependent spacing rule if the specified text range changes + * its 'has line feed' status during formatting (new line feed is added and the range hasn't contained them before + * or it contained line feed(s) and it was removed during formatting). + *

+ * Used for formatting rules like the "next line if wrapped" brace placement. + * + * @param minSpaces The minimum number of spaces that should be present between the blocks + * to which the spacing setting instance is related. Spaces are inserted + * if there are less than this amount of spaces in the document. + * @param maxSpaces The maximum number of spaces that should be present between the blocks + * to which the spacing setting instance is related, or Integer.MAX_VALUE + * if the number of spaces is not limited. Spaces are deleted if there are + * more than this amount of spaces in the document. + * @param dependencyRange The text range checked for the presence of line breaks. + * @param keepLineBreaks Whether the existing line breaks between the blocks should be preserved. + * @param keepBlankLines Whether the existing blank lines between the blocks should be preserved. + * @param rule settings to use if dependent region changes its 'contains line feed' status during formatting + * @return the spacing setting instance for the given parameters + */ + public static Spacing createDependentLFSpacing(int minSpaces, + int maxSpaces, + @NotNull TextRange dependencyRange, + boolean keepLineBreaks, + int keepBlankLines, + @NotNull DependentSpacingRule rule) + { + return myFactory.createDependentLFSpacing(minSpaces, maxSpaces, dependencyRange, keepLineBreaks, keepBlankLines, rule); + } + /** * Creates a spacing setting instance which preserves the presence of spaces between the blocks but, * if spaces are present, may insert or delete the spaces. Used, for example, for HTML formatting diff --git a/platform/lang-api/src/com/intellij/formatting/SpacingFactory.java b/platform/lang-api/src/com/intellij/formatting/SpacingFactory.java index 40e6f1f29f27..2c105693a68d 100644 --- a/platform/lang-api/src/com/intellij/formatting/SpacingFactory.java +++ b/platform/lang-api/src/com/intellij/formatting/SpacingFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * 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. @@ -16,34 +16,43 @@ package com.intellij.formatting; import com.intellij.openapi.util.TextRange; +import org.jetbrains.annotations.NotNull; /** * Internal interface for creating spacing instances. */ interface SpacingFactory { - public Spacing createSpacing(int minSpaces, + + @NotNull + Spacing createSpacing(int minSpaces, int maxSpaces, int minLineFeeds, boolean keepLineBreaks, int keepBlankLines); - public Spacing getReadOnlySpacing(); + @NotNull + Spacing getReadOnlySpacing(); + + @NotNull + Spacing createDependentLFSpacing(int minSpaces, + int maxSpaces, + @NotNull TextRange dependencyRange, + boolean keepLineBreaks, + int keepBlankLines, + @NotNull DependentSpacingRule rule); - public Spacing createDependentLFSpacing(int minSpaces, - int maxSpaces, - TextRange dependance, - boolean keepLineBreaks, - int keepBlankLines); + @NotNull + Spacing createSafeSpacing(boolean keepLineBreaks, + int keepBlankLines); - public Spacing createSafeSpacing(boolean keepLineBreaks, - int keepBlankLines); - - public Spacing createKeepingFirstColumnSpacing(final int minSpaces, - final int maxSpaces, - final boolean keepLineBreaks, - final int keepBlankLines); + @NotNull + Spacing createKeepingFirstColumnSpacing(final int minSpaces, + final int maxSpaces, + final boolean keepLineBreaks, + final int keepBlankLines); + @NotNull Spacing createSpacing(final int minSpaces, final int maxSpaces, final int minLineFeeds, final boolean keepLineBreaks, final int keepBlankLines, final int prefLineFeeds); } diff --git a/platform/lang-impl/src/com/intellij/formatting/DependantSpacingImpl.java b/platform/lang-impl/src/com/intellij/formatting/DependantSpacingImpl.java index 9290989ca6bf..b1d9304ae599 100644 --- a/platform/lang-impl/src/com/intellij/formatting/DependantSpacingImpl.java +++ b/platform/lang-impl/src/com/intellij/formatting/DependantSpacingImpl.java @@ -17,6 +17,7 @@ package com.intellij.formatting; import com.intellij.openapi.util.TextRange; +import org.jetbrains.annotations.NotNull; /** * Extends {@link SpacingImpl} in order to add notion of dependency range. @@ -24,29 +25,50 @@ import com.intellij.openapi.util.TextRange; * 'Dependency' here affect {@link #getMinLineFeeds() minLineFieeds} property value. See property contract for more details. */ public class DependantSpacingImpl extends SpacingImpl { - private final TextRange myDependency; - private static final int DEPENDENCE_CONTAINS_LF_MASK = 0x10; + private static final int DEPENDENCE_CONTAINS_LF_MASK = 0x10; private static final int DEPENDENT_REGION_LF_CHANGED_MASK = 0x20; + @NotNull private final TextRange myDependency; + @NotNull private final DependentSpacingRule myRule; + + public DependantSpacingImpl(final int minSpaces, final int maxSpaces, - TextRange dependency, + @NotNull TextRange dependency, final boolean keepLineBreaks, - final int keepBlankLines) { + final int keepBlankLines, + @NotNull DependentSpacingRule rule) + { super(minSpaces, maxSpaces, 0, false, false, keepLineBreaks, keepBlankLines, false, 0); myDependency = dependency; + myRule = rule; } /** * @return 1 if dependency has line feeds; 0 otherwise */ public int getMinLineFeeds() { - if ((myFlags & DEPENDENCE_CONTAINS_LF_MASK) != 0) { - return 1; + if (!isTriggered()) { + return super.getMinLineFeeds(); } - else { - return 0; + + if (myRule.hasData(DependentSpacingRule.Anchor.MIN_LINE_FEEDS)) { + return myRule.getData(DependentSpacingRule.Anchor.MIN_LINE_FEEDS); } + + if (myRule.hasData(DependentSpacingRule.Anchor.MAX_LINE_FEEDS)) { + return myRule.getData(DependentSpacingRule.Anchor.MAX_LINE_FEEDS); + } + return super.getMinLineFeeds(); + } + + @Override + public int getKeepBlankLines() { + if (!isTriggered() || !myRule.hasData(DependentSpacingRule.Anchor.MAX_LINE_FEEDS)) { + return super.getKeepBlankLines(); + } + + return 0; } public void refresh(FormatProcessor formatter) { @@ -58,14 +80,16 @@ public class DependantSpacingImpl extends SpacingImpl { else myFlags &= ~DEPENDENCE_CONTAINS_LF_MASK; } + @NotNull public TextRange getDependency() { return myDependency; } /** - * Allows to answer whether the target dependent regions has been changed during formatting. + * Allows to answer whether 'contains line feed' status has been changed for the target dependent region during formatting. * - * @return true if target dependent region has been changed during formatting; false otherwise + * @return true if target 'contains line feed' status has been changed for the target dependent region during formatting; + * false otherwise */ public final boolean isDependentRegionChanged() { return (myFlags & DEPENDENT_REGION_LF_CHANGED_MASK) != 0; @@ -85,4 +109,9 @@ public class DependantSpacingImpl extends SpacingImpl { return ""; } + + private boolean isTriggered() { + return myRule.getTrigger() == DependentSpacingRule.Trigger.HAS_LINE_FEEDS + ^ (myFlags & DEPENDENCE_CONTAINS_LF_MASK) == 0; + } } diff --git a/platform/lang-impl/src/com/intellij/formatting/FormatProcessor.java b/platform/lang-impl/src/com/intellij/formatting/FormatProcessor.java index 91d8a8988a87..246b4cfd8915 100644 --- a/platform/lang-impl/src/com/intellij/formatting/FormatProcessor.java +++ b/platform/lang-impl/src/com/intellij/formatting/FormatProcessor.java @@ -108,7 +108,7 @@ class FormatProcessor { *

    *       int[] data = {1, 2, 3};
    * 
- * We want to keep that in one line with possible but place curly braces on separate lines if the width is not enough: + * We want to keep that in one line if possible but place curly braces on separate lines if the width is not enough: *
    *      int[] data = {    | < right margin
    *          1, 2, 3       |
diff --git a/platform/lang-impl/src/com/intellij/formatting/FormatterImpl.java b/platform/lang-impl/src/com/intellij/formatting/FormatterImpl.java
index 0b47d69e55ef..afefeb6b6774 100644
--- a/platform/lang-impl/src/com/intellij/formatting/FormatterImpl.java
+++ b/platform/lang-impl/src/com/intellij/formatting/FormatterImpl.java
@@ -133,6 +133,7 @@ public class FormatterImpl extends FormatterEx
     return result;
   }
 
+  @NotNull
   public Spacing createSpacing(int minOffset,
                                int maxOffset,
                                int minLineFeeds,
@@ -141,13 +142,21 @@ public class FormatterImpl extends FormatterEx
     return getSpacingImpl(minOffset, maxOffset, minLineFeeds, false, false, keepLineBreaks, keepBlankLines,false, 0);
   }
 
+  @NotNull
   public Spacing getReadOnlySpacing() {
     return myReadOnlySpacing;
   }
-
-  public Spacing createDependentLFSpacing(int minOffset, int maxOffset, TextRange dependence, boolean keepLineBreaks,
-                                          int keepBlankLines) {
-    return new DependantSpacingImpl(minOffset, maxOffset, dependence, keepLineBreaks, keepBlankLines);
+  
+  @NotNull
+  @Override
+  public Spacing createDependentLFSpacing(int minSpaces,
+                                          int maxSpaces,
+                                          @NotNull TextRange dependencyRange,
+                                          boolean keepLineBreaks,
+                                          int keepBlankLines,
+                                          @NotNull DependentSpacingRule rule)
+  {
+    return new DependantSpacingImpl(minSpaces, maxSpaces, dependencyRange, keepLineBreaks, keepBlankLines, rule);
   }
 
   @NotNull
@@ -669,10 +678,12 @@ public class FormatterImpl extends FormatterEx
     return myAbsoluteLabelIndent;
   }
 
+  @NotNull
   public Spacing createSafeSpacing(final boolean shouldKeepLineBreaks, final int keepBlankLines) {
     return getSpacingImpl(0, 0, 0, false, true, shouldKeepLineBreaks, keepBlankLines, false, 0);
   }
 
+  @NotNull
   public Spacing createKeepingFirstColumnSpacing(final int minSpace,
                                                  final int maxSpace,
                                                  final boolean keepLineBreaks,
@@ -680,6 +691,7 @@ public class FormatterImpl extends FormatterEx
     return getSpacingImpl(minSpace, maxSpace, -1, false, false, keepLineBreaks, keepBlankLines, true, 0);
   }
 
+  @NotNull
   public Spacing createSpacing(final int minSpaces, final int maxSpaces, final int minLineFeeds, final boolean keepLineBreaks, final int keepBlankLines,
                                final int prefLineFeeds) {
     return getSpacingImpl(minSpaces, maxSpaces, -1, false, false, keepLineBreaks, keepBlankLines, false, prefLineFeeds);
diff --git a/platform/lang-impl/src/com/intellij/psi/formatter/common/InjectedLanguageBlockWrapper.java b/platform/lang-impl/src/com/intellij/psi/formatter/common/InjectedLanguageBlockWrapper.java
index 717973a5c62c..38b968f912f6 100644
--- a/platform/lang-impl/src/com/intellij/psi/formatter/common/InjectedLanguageBlockWrapper.java
+++ b/platform/lang-impl/src/com/intellij/psi/formatter/common/InjectedLanguageBlockWrapper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -132,7 +132,7 @@ public final class InjectedLanguageBlockWrapper implements Block {
       DependantSpacingImpl hostSpacing = (DependantSpacingImpl)spacing;
       return new DependantSpacingImpl(
         hostSpacing.getMinSpaces(), hostSpacing.getMaxSpaces(), hostSpacing.getDependency().shiftRight(shift),
-        hostSpacing.shouldKeepLineFeeds(), hostSpacing.getKeepBlankLines()
+        hostSpacing.shouldKeepLineFeeds(), hostSpacing.getKeepBlankLines(), DependentSpacingRule.DEFAULT
       );
     } 
     return spacing;