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"/> +