diff --git a/platform/lang-api/src/com/intellij/formatting/FormattingDocumentModel.java b/platform/lang-api/src/com/intellij/formatting/FormattingDocumentModel.java
index 24216afce816..56e65cb74f8a 100644
--- a/platform/lang-api/src/com/intellij/formatting/FormattingDocumentModel.java
+++ b/platform/lang-api/src/com/intellij/formatting/FormattingDocumentModel.java
@@ -54,4 +54,23 @@ public interface FormattingDocumentModel {
int getTextLength();
Document getDocument();
+
+ /**
+ * Allows to answer if all document symbols from [startOffset; endOffset) region are treated as white spaces by formatter.
+ *
+ * @param startOffset target start document offset (inclusive)
+ * @param endOffset target end document offset (exclusive)
+ * @return true if all document symbols from [startOffset; endOffset) region are treated
+ * as white spaces by formatter; false otherwise
+ */
+ boolean containsWhiteSpaceSymbolsOnly(int startOffset, int endOffset);
+
+ /**
+ * Allows to answer if given symbol is treated by the current model as white space symbol during formatting.
+ *
+ * @param symbol symbols to check
+ * @return true if given symbol is treated by the current model as white space symbol during formatting;
+ * false otherwise
+ */
+ boolean isWhiteSpaceSymbol(char symbol);
}
diff --git a/platform/lang-api/src/com/intellij/formatting/LanguageWhiteSpaceFormattingStrategy.java b/platform/lang-api/src/com/intellij/formatting/LanguageWhiteSpaceFormattingStrategy.java
new file mode 100644
index 000000000000..bae9e8b8ba7c
--- /dev/null
+++ b/platform/lang-api/src/com/intellij/formatting/LanguageWhiteSpaceFormattingStrategy.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2000-2010 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.lang.LanguageExtension;
+
+import java.util.Collection;
+
+/**
+ * Exposes pre-configured {@link WhiteSpaceFormattingStrategy} objects to use in a per-language manner.
+ *
+ * @author Denis Zhdanov
+ * @since Sep 20, 2010 7:41:55 PM
+ */
+public class LanguageWhiteSpaceFormattingStrategy extends LanguageExtension> {
+
+ public static final String EP_NAME = "lang.whiteSpaceFormattingStrategy";
+ public static final LanguageWhiteSpaceFormattingStrategy INSTANCE = new LanguageWhiteSpaceFormattingStrategy();
+
+ private LanguageWhiteSpaceFormattingStrategy() {
+ super(EP_NAME);
+ }
+}
diff --git a/platform/lang-api/src/com/intellij/formatting/WhiteSpaceFormattingStrategy.java b/platform/lang-api/src/com/intellij/formatting/WhiteSpaceFormattingStrategy.java
new file mode 100644
index 000000000000..3a9d1b494fc8
--- /dev/null
+++ b/platform/lang-api/src/com/intellij/formatting/WhiteSpaceFormattingStrategy.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2000-2010 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 org.jetbrains.annotations.NotNull;
+
+/**
+ * Defines common contract for strategy that determines if particular symbol or sequence of symbols may be treated as
+ * white space during formatting.
+ *
+ * 'Treated as white space' here means that formatter is free to remove any number of such symbols or replace them
+ * by 'pure' white spaces.
+ *
+ * @author Denis Zhdanov
+ * @since Sep 20, 2010 5:05:08 PM
+ */
+public interface WhiteSpaceFormattingStrategy {
+
+ /**
+ * Checks if given sub-sequence of the given text contains symbols that may be treated as white spaces.
+ *
+ * @param text text to check
+ * @param start start offset to use with the given text (inclusive)
+ * @param end end offset to use with the given text (exclusive)
+ * @return offset of the first symbol that belongs to [startOffset; endOffset) range
+ * and is not treated as white space by the current strategy or value that is greater
+ * or equal to the given 'end' parameter if all target sub-sequence symbols
+ * can be treated as white spaces
+ */
+ int check(@NotNull CharSequence text, int start, int end);
+}
diff --git a/platform/lang-impl/src/com/intellij/formatting/WhiteSpace.java b/platform/lang-impl/src/com/intellij/formatting/WhiteSpace.java
index 274fbb049a28..7054fd0516d4 100644
--- a/platform/lang-impl/src/com/intellij/formatting/WhiteSpace.java
+++ b/platform/lang-impl/src/com/intellij/formatting/WhiteSpace.java
@@ -23,7 +23,6 @@ import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.formatter.FormattingDocumentModelImpl;
-import org.jetbrains.annotations.NonNls;
import java.util.ArrayList;
@@ -66,8 +65,6 @@ class WhiteSpace {
private static final byte CONTAINS_SPACES_INITIALLY = 0x40;
private static final int LF_COUNT_SHIFT = 7;
private static final int MAX_LF_COUNT = 1 << 24;
- @NonNls private static final String CDATA_START = "";
/**
* Creates new WhiteSpace object with the given start offset and a flag that shows if current white space is
@@ -158,19 +155,13 @@ class WhiteSpace {
*/
private boolean coveredByBlock(final FormattingDocumentModel model) {
if (myInitial == null) return true;
- String s = myInitial.toString().trim();
- if (s.length() == 0) return true;
+ if (model.containsWhiteSpaceSymbolsOnly(myStart, myEnd)) return true;
+
if (!(model instanceof FormattingDocumentModelImpl)) return false;
PsiFile psiFile = ((FormattingDocumentModelImpl)model).getFile();
if (psiFile == null) return false;
PsiElement start = psiFile.findElementAt(myStart);
PsiElement end = psiFile.findElementAt(myEnd-1);
-
- // CDATA usage at common white space stuff smells because it makes no sense in context, for example, white space for pure java block.
- if (s.startsWith(CDATA_START)) s = s.substring(CDATA_START.length());
- if (s.endsWith(CDATA_END)) s = s.substring(0, s.length() - CDATA_END.length());
- s = s.trim();
- if (s.length() == 0) return true;
return start == end && start instanceof PsiWhiteSpace; // there maybe non-white text inside CDATA-encoded injected elements
}
@@ -183,12 +174,10 @@ class WhiteSpace {
mySpaces = 0;
myIndentSpaces = 0;
break;
- case ' ':
- mySpaces++;
- break;
case '\t':
myIndentSpaces += tabSize;
break;
+ default: mySpaces++;
}
}
}
diff --git a/platform/lang-impl/src/com/intellij/psi/formatter/CdataWhiteSpaceDefinitionStrategy.java b/platform/lang-impl/src/com/intellij/psi/formatter/CdataWhiteSpaceDefinitionStrategy.java
new file mode 100644
index 000000000000..37d7da5d3f21
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/psi/formatter/CdataWhiteSpaceDefinitionStrategy.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2000-2010 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.psi.formatter;
+
+import com.intellij.formatting.WhiteSpaceFormattingStrategy;
+import com.intellij.util.text.CharArrayUtil;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * {@link WhiteSpaceFormattingStrategy} implementation that considers to be white spaces all symbols from
+ * standard XML CDATA section ('{@code }').
+ *
+ * Thread-safe.
+ *
+ * @author Denis Zhdanov
+ * @since Sep 20, 2010 5:39:50 PM
+ */
+public class CdataWhiteSpaceDefinitionStrategy implements WhiteSpaceFormattingStrategy {
+
+ @NonNls public static final String CDATA_START = "";
+
+ @Override
+ public int check(@NotNull CharSequence text, int start, int end) {
+ if (CharArrayUtil.indexOf(text, CDATA_START, start, end) != start) {
+ return start;
+ }
+
+ int i = CharArrayUtil.indexOf(text, CDATA_END, start, end);
+ if (i < 0) {
+ return start;
+ }
+
+ int result = i + CDATA_END.length();
+ return result > end ? start : result;
+ }
+}
diff --git a/platform/lang-impl/src/com/intellij/psi/formatter/FormattingDocumentModelImpl.java b/platform/lang-impl/src/com/intellij/psi/formatter/FormattingDocumentModelImpl.java
index 2afe6c2464ee..ac68f25a86c1 100644
--- a/platform/lang-impl/src/com/intellij/psi/formatter/FormattingDocumentModelImpl.java
+++ b/platform/lang-impl/src/com/intellij/psi/formatter/FormattingDocumentModelImpl.java
@@ -17,6 +17,9 @@
package com.intellij.psi.formatter;
import com.intellij.formatting.FormattingDocumentModel;
+import com.intellij.formatting.LanguageWhiteSpaceFormattingStrategy;
+import com.intellij.formatting.WhiteSpaceFormattingStrategy;
+import com.intellij.lang.Language;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.impl.DocumentImpl;
@@ -28,15 +31,31 @@ import com.intellij.psi.impl.PsiDocumentManagerImpl;
import com.intellij.psi.impl.PsiToDocumentSynchronizer;
import org.jetbrains.annotations.NotNull;
+import java.nio.CharBuffer;
+import java.util.*;
+
public class FormattingDocumentModelImpl implements FormattingDocumentModel{
+ private static final List SHARED_STRATEGIES = Arrays.asList(
+ new StaticWhiteSpaceDefinitionStrategy(' ', '\t'), new CdataWhiteSpaceDefinitionStrategy()
+ );
+
+ private final Set myWhiteSpaceStrategies = new HashSet(SHARED_STRATEGIES);
private final Document myDocument;
private final PsiFile myFile;
+
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.formatter.FormattingDocumentModelImpl");
public FormattingDocumentModelImpl(final Document document, PsiFile file) {
myDocument = document;
myFile = file;
+ if (file != null) {
+ Language language = file.getLanguage();
+ Collection strategies = LanguageWhiteSpaceFormattingStrategy.INSTANCE.forLanguage(language);
+ if (strategies != null) {
+ myWhiteSpaceStrategies.addAll(strategies);
+ }
+ }
}
public static FormattingDocumentModelImpl createOn(PsiFile file) {
@@ -68,23 +87,28 @@ public class FormattingDocumentModelImpl implements FormattingDocumentModel{
return document;
}
+ @Override
public int getLineNumber(int offset) {
LOG.assertTrue (offset <= myDocument.getTextLength());
return myDocument.getLineNumber(offset);
}
+ @Override
public int getLineStartOffset(int line) {
return myDocument.getLineStartOffset(line);
}
+ @Override
public CharSequence getText(final TextRange textRange) {
return myDocument.getCharsSequence().subSequence(textRange.getStartOffset(), textRange.getEndOffset());
}
+ @Override
public int getTextLength() {
return myDocument.getTextLength();
}
+ @Override
public Document getDocument() {
return myDocument;
}
@@ -93,6 +117,33 @@ public class FormattingDocumentModelImpl implements FormattingDocumentModel{
return myFile;
}
+ @Override
+ public boolean containsWhiteSpaceSymbolsOnly(int startOffset, int endOffset) {
+ return containsWhiteSpaceSymbolsOnly(myDocument.getCharsSequence(), startOffset, endOffset);
+ }
+
+ @Override
+ public boolean isWhiteSpaceSymbol(char symbol) {
+ return containsWhiteSpaceSymbolsOnly(CharBuffer.wrap(new char[] {symbol}), 0, 1);
+ }
+
+ private boolean containsWhiteSpaceSymbolsOnly(CharSequence text, int startOffset, int endOffset) {
+ int offset = startOffset;
+ while (offset < endOffset) {
+ int oldOffset = offset;
+ for (WhiteSpaceFormattingStrategy strategy : myWhiteSpaceStrategies) {
+ offset = strategy.check(text, offset, endOffset);
+ if (offset > oldOffset) {
+ break;
+ }
+ }
+ if (offset == oldOffset) {
+ return false;
+ }
+ }
+ return offset >= endOffset;
+ }
+
public static boolean canUseDocumentModel(@NotNull Document document,@NotNull PsiFile file) {
PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(file.getProject());
return !psiDocumentManager.isUncommited(document) &&
diff --git a/platform/lang-impl/src/com/intellij/psi/formatter/StaticWhiteSpaceDefinitionStrategy.java b/platform/lang-impl/src/com/intellij/psi/formatter/StaticWhiteSpaceDefinitionStrategy.java
new file mode 100644
index 000000000000..0bc43fa5ea05
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/psi/formatter/StaticWhiteSpaceDefinitionStrategy.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2000-2010 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.psi.formatter;
+
+import com.intellij.formatting.WhiteSpaceFormattingStrategy;
+import gnu.trove.TIntHashSet;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * {@link WhiteSpaceFormattingStrategy} implementation that is pre-configured with the set of symbols that may
+ * be treated as white spaces.
+ *
+ * Thread-safe.
+ *
+ * @author Denis Zhdanov
+ * @since Sep 20, 2010 5:11:49 PM
+ */
+public class StaticWhiteSpaceDefinitionStrategy implements WhiteSpaceFormattingStrategy {
+
+ private final TIntHashSet myWhiteSpaceSymbols = new TIntHashSet();
+
+ /**
+ * Creates new StaticWhiteSpaceDefinitionStrategy object with the symbols that should be treated as white spaces.
+ *
+ * @param whiteSpaceSymbols symbols that should be treated as white spaces by the current strategy
+ */
+ public StaticWhiteSpaceDefinitionStrategy(char ... whiteSpaceSymbols) {
+ for (char symbol : whiteSpaceSymbols) {
+ myWhiteSpaceSymbols.add(symbol);
+ }
+ }
+
+ @Override
+ public int check(@NotNull CharSequence text, int start, int end) {
+ for (int i = start; i < end; i++) {
+ char c = text.charAt(i);
+ if (!myWhiteSpaceSymbols.contains(c)) {
+ return i;
+ }
+ }
+ return end;
+ }
+}
diff --git a/platform/lang-impl/testSrc/com/intellij/psi/formatter/CdataWhiteSpaceDefinitionStrategyTest.java b/platform/lang-impl/testSrc/com/intellij/psi/formatter/CdataWhiteSpaceDefinitionStrategyTest.java
new file mode 100644
index 000000000000..2a6acbe45aac
--- /dev/null
+++ b/platform/lang-impl/testSrc/com/intellij/psi/formatter/CdataWhiteSpaceDefinitionStrategyTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2000-2010 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.psi.formatter;
+
+import static org.junit.Assert.*;
+import com.intellij.psi.formatter.CdataWhiteSpaceDefinitionStrategy;
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.After;
+import org.junit.runner.RunWith;
+
+/**
+ * @author Denis Zhdanov
+ * @since 09/20/2010
+ */
+public class CdataWhiteSpaceDefinitionStrategyTest {
+
+ private CdataWhiteSpaceDefinitionStrategy myStrategy;
+
+ @Before
+ public void setUp() {
+ myStrategy = new CdataWhiteSpaceDefinitionStrategy();
+ }
+
+ @Test
+ public void withoutClosingSection() {
+ String text = ""; // Doesn't contain double ']'
+ assertSame(0, myStrategy.check(text, 0, text.length()));
+
+ text = "";
+ assertSame(0, myStrategy.check(text, 0, text.length() - 1)); // Last symbol is out of scope
+ }
+
+ @Test
+ public void cdataNotAtFirstOffset() {
+ String text = " ";
+ assertSame(0, myStrategy.check(text, 0, text.length()));
+ assertSame(1, myStrategy.check(text, 1, text.length()));
+ }
+
+ @Test
+ public void partialMatch() {
+ String text = " ";
+ int expectedOffset = text.indexOf(">") + 1;
+ assertSame(expectedOffset, myStrategy.check(text, 0, text.length()));
+ assertSame(expectedOffset + 1, myStrategy.check(" " + text, 1, text.length()));
+ }
+}
diff --git a/platform/lang-impl/testSrc/com/intellij/psi/formatter/StaticWhiteSpaceDefinitionStrategyTest.java b/platform/lang-impl/testSrc/com/intellij/psi/formatter/StaticWhiteSpaceDefinitionStrategyTest.java
new file mode 100644
index 000000000000..c31c34468232
--- /dev/null
+++ b/platform/lang-impl/testSrc/com/intellij/psi/formatter/StaticWhiteSpaceDefinitionStrategyTest.java
@@ -0,0 +1,44 @@
+package com.intellij.psi.formatter;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertSame;
+
+/**
+ * @author Denis Zhdanov
+ * @since 09/20/2010
+ */
+public class StaticWhiteSpaceDefinitionStrategyTest {
+
+ private StaticWhiteSpaceDefinitionStrategy myStrategy;
+
+ @Before
+ public void setUp() {
+ myStrategy = new StaticWhiteSpaceDefinitionStrategy('a', 'b', 'c');
+ }
+
+ @Test
+ public void failOnTheFirstSymbol() {
+ assertSame(0, myStrategy.check("def", 0, 2));
+ assertSame(1, myStrategy.check("defghi", 1, 2));
+ }
+
+ @Test
+ public void failInTheMiddle() {
+ assertSame(1, myStrategy.check("adef", 0, 3));
+ assertSame(2, myStrategy.check("daefghi", 1, 3));
+ }
+
+ @Test
+ public void failOnTheLastSymbol() {
+ assertSame(2, myStrategy.check("abe", 0, 3));
+ assertSame(3, myStrategy.check("dabefghi", 1, 4));
+ }
+
+ @Test
+ public void successfulMatch() {
+ assertSame(3, myStrategy.check("abc", 0, 3));
+ assertSame(4, myStrategy.check("dabcefg", 1, 4));
+ }
+}
diff --git a/platform/platform-resources/src/META-INF/LangExtensionPoints.xml b/platform/platform-resources/src/META-INF/LangExtensionPoints.xml
index 6b08fe2ab7ee..5d58a0045261 100644
--- a/platform/platform-resources/src/META-INF/LangExtensionPoints.xml
+++ b/platform/platform-resources/src/META-INF/LangExtensionPoints.xml
@@ -280,6 +280,8 @@
beanClass="com.intellij.lang.LanguageExtensionPoint"/>
+