diff --git a/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/TabPostFormatProcessor.java b/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/TabPostFormatProcessor.java deleted file mode 100644 index 49f9b8dd178e..000000000000 --- a/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/TabPostFormatProcessor.java +++ /dev/null @@ -1,534 +0,0 @@ -/* - * 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.psi.impl.source.codeStyle; - -import com.intellij.lang.ASTNode; -import com.intellij.lang.Language; -import com.intellij.lang.java.JavaLanguage; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.util.text.StringUtil; -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.FormatterUtil; -import com.intellij.psi.impl.source.tree.TreeUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * This class handles a use-case when reformatted text conflicts with 'use tab' code style setting. E.g. target text uses - * tabs for indentation but our code style is configured to use spaces. - *

- * We already have corresponding support at the block level but it's possible that multiline text is treated as a single block, - * i.e. all its internal indents are not visible to the formatter. That's why current class is introduced. - *

- * Thread-safe. - * - * @author Denis Zhdanov - * @since 8/1/12 2:38 PM - */ -public class TabPostFormatProcessor implements PostFormatProcessor { - - @Override - public PsiElement processElement(@NotNull PsiElement source, @NotNull CodeStyleSettings settings) { - doProcess(source, TextRange.from(source.getTextRange().getStartOffset(), source.getTextLength()), settings); - return source; - } - - @NotNull - @Override - public TextRange processText(@NotNull PsiFile source, @NotNull TextRange rangeToReformat, @NotNull CodeStyleSettings settings) { - return doProcess(source, rangeToReformat, settings); - } - - @NotNull - private static TextRange doProcess(@NotNull PsiElement source, @NotNull TextRange range, @NotNull CodeStyleSettings settings) { - ASTNode node = source.getNode(); - if (node == null) { - return range; - } - - Language language = source.getLanguage(); - if (language != JavaLanguage.INSTANCE) { - // We had the only complaint for tabs not being converted to spaces for now. It was for the java code which has - // a single block for the multi-line comment. This check should be removed if it is decided to generalize - // this logic to other languages as well. - return range; - } - - if (!source.isValid()) return range; - PsiFile file = source.getContainingFile(); - CommonCodeStyleSettings.IndentOptions indentOptions = settings.getIndentOptionsByFile(file, range); - - boolean useTabs = indentOptions.USE_TAB_CHARACTER; - boolean smartTabs = indentOptions.SMART_TABS; - int tabWidth = indentOptions.TAB_SIZE; - return processViaPsi(node, range, new TreeHelperImpl(), useTabs, smartTabs, tabWidth); - } - - @NotNull - public static TextRange processViaPsi(@NotNull ASTNode node, - @NotNull TextRange range, - @NotNull TreeHelper treeHelper, - boolean useTabs, - boolean smartTabs, - int tabWidth) - { - AstHelper helper = new AstHelper(node, treeHelper); - do { - if (useTabs) { - if (smartTabs) { - range = processSmartTabs(helper, range, tabWidth); - } - else { - range = processTabs(helper, range, tabWidth); - } - } - else { - range = processSpaces(helper, range, tabWidth); - } - } - while (helper.nextLine()); - return range; - } - - @NotNull - public static TextRange processViaDocument(@NotNull Document document, - @NotNull TextRange range, - boolean useTabs, - boolean useSmartTabs, - int tabWidth) - { - TextRange result = range; - int startLine = document.getLineNumber(Math.min(document.getTextLength(), range.getStartOffset())); - int endLine = document.getLineNumber(Math.max(0, Math.min(document.getTextLength(), range.getEndOffset()) - 1)); - DocumentHelper helper = new DocumentHelper(document, startLine); - for (int line = startLine; line <= endLine; line++) { - helper.setLine(line); - if (useTabs) { - if (useSmartTabs) { - result = processSmartTabs(helper, result, tabWidth); - } - else { - result = processTabs(helper, result, tabWidth); - } - } - else { - result = processSpaces(helper, result, tabWidth); - } - } - return result; - } - - /** - * Converts tabulations to white spaces at the target line's indent space. - * - * @param helper data facade - * @param range target range allowed for modification - * @param tabWidth tab width in columns to use during conversion (each tab symbol is replaced by white spaces which number is - * equal to tab width) - * @return given text range if no modification to the target line's indent space has been performed: - * adjusted range that points to semantically the same region otherwise - */ - @NotNull - private static TextRange processSpaces(@NotNull Helper helper, @NotNull TextRange range, int tabWidth) { - CharSequence indent = helper.getCurrentLineIndent(); - int start = Math.max(0, range.getStartOffset() - helper.getCurrentLineStartOffset()); - int end = Math.min(indent.length(), range.getEndOffset() - helper.getCurrentLineStartOffset()); - int tabsNumber = 0; - int indentOffset = end; - for (int i = start; i < end; i++) { - char c = indent.charAt(i); - if (c == '\t') { - tabsNumber++; - } - else if (c != ' ') { - indentOffset = i; - break; - } - } - if (tabsNumber > 0) { - helper.replace(start, indentOffset, StringUtil.repeat(" ", indentOffset - start - tabsNumber + tabsNumber * tabWidth)); - return TextRange.create(range.getStartOffset(), range.getEndOffset() - tabsNumber + tabsNumber * tabWidth); - } - else { - return range; - } - } - - /** - * Converts white spaces to tabulations at the target line's indent space. - * - * @param helper data facade - * @param range target range allowed for modification - * @param tabWidth tab width in columns to use during conversion (each tab symbol is replaced by white spaces which number is - * equal to tab width) - * @return given text range if no modification to the target line's indent space has been performed: - * adjusted range that points to semantically the same region otherwise - */ - @NotNull - private static TextRange processTabs(@NotNull Helper helper, @NotNull TextRange range, int tabWidth) { - CharSequence indent = helper.getCurrentLineIndent(); - int start = Math.max(0, range.getStartOffset() - helper.getCurrentLineStartOffset()); - int end = Math.min(indent.length(), range.getEndOffset() - helper.getCurrentLineStartOffset()); - int replacementsNumber = 0; - int consecutiveSpaces = 0; - for (int i = start; i < end; i++) { - char c = indent.charAt(i); - if (c == ' ') { - ++consecutiveSpaces; - } - else { - int tabsNumber = consecutiveSpaces / tabWidth; - if (tabsNumber > 0) { - helper.replace(i - consecutiveSpaces, i - consecutiveSpaces + tabsNumber * tabWidth, StringUtil.repeat("\t", tabsNumber)); - replacementsNumber++; - consecutiveSpaces = 0; - } - if (c != '\t') { - break; - } - } - } - - int tabsNumber = consecutiveSpaces / tabWidth; - if (tabsNumber > 0) { - helper.replace(end - consecutiveSpaces, end - consecutiveSpaces + tabsNumber * tabWidth, StringUtil.repeat("\t", tabsNumber)); - } - - if (replacementsNumber > 0) { - return TextRange.create(range.getStartOffset(), range.getEndOffset() - replacementsNumber * (tabWidth - 1)); - } - else { - return range; - } - } - - /** - * Converts tabulations to white spaces at the target line's indent space. - * - * @param helper data facade - * @param range target range allowed for modification - * @param tabWidth tab width in columns to use during conversion (every group of 'tab width' white spaces from the indent space might - * be replaced by a tab symbol) - * @return given text range if no modification to the target line's indent space has been performed: - * adjusted range that points to semantically the same region otherwise - */ - @SuppressWarnings("AssignmentToForLoopParameter") - @NotNull - private static TextRange processSmartTabs(@NotNull Helper helper, @NotNull TextRange range, int tabWidth) { - // Adjust current line indent. The general idea is to replace white spaces by tab symbols if that maps to the previous line indent. - CharSequence prevLineIndent = helper.getPrevLineIndent(); - if (prevLineIndent == null) { - return processTabs(helper, range, tabWidth); - } - - CharSequence currentLineIndent = helper.getCurrentLineIndent(); - int lineStart = 0; - int start = Math.max(0, range.getStartOffset() - helper.getCurrentLineStartOffset()); - int end = Math.min(currentLineIndent.length(), range.getEndOffset() - helper.getCurrentLineStartOffset()); - int indentOffset = 0; - int tabsReplaced = 0; - for (int i = lineStart; i < end && indentOffset < prevLineIndent.length(); i++, indentOffset++) { - char c = currentLineIndent.charAt(i); - if (prevLineIndent.charAt(indentOffset) == ' ') { - if (c == ' ') { - continue; - } - else { - break; - } - } - - // Assuming that target prevLineIndent symbol is tab then. - if (c == '\t') { - continue; - } - - if (end - i < tabWidth) { - break; - } - - boolean canReplace = true; - for (int j = i + 1, max = Math.min(end, i + tabWidth); j < max; j++) { - if (currentLineIndent.charAt(j) != ' ') { - canReplace = false; - break; - } - } - - if (!canReplace) { - break; - } - - if (i < start) { - // Continue processing if target range doesn't cover the whole white spaces which are intended to replace tab symbol. - i += tabWidth - 1; // -1 because of 'for' loop increment - continue; - } - - helper.replace(i, i + tabWidth, "\t"); - tabsReplaced++; - end -= tabWidth - 1; - } - - return tabsReplaced > 0 ? TextRange.create(range.getStartOffset(), range.getEndOffset() - tabsReplaced * (tabWidth - 1)) : range; - } - - /** - * There are two possible processing use-cases: - *

-   * 
-   * 
- * That's why we hide implementation-specific processing behind the current interface and use it at the generic 'engine'. - *

- * The general idea is to process indent spaces line-by-line from top to bottom. - */ - interface Helper { - - /** - * @return previous line indent space if current line is not the first one; {@code null} otherwise - */ - @Nullable CharSequence getPrevLineIndent(); - - int getCurrentLineStartOffset(); - - /** @return current line's indent space */ - @NotNull CharSequence getCurrentLineIndent(); - - /** - * Asks current helper to modify target line's indent space. - * - * @param start start offset of the indent range to modify (counts from the line start, i.e. doesn't take into - * consideration line start offset at the document) - * @param end end offset of the indent range to modify (counts from the line start, i.e. doesn't take into - * consideration line start offset at the document) - * @param newText replacement text - */ - void replace(int start, int end, @NotNull String newText); - } - - private static class DocumentHelper implements Helper { - - @NotNull private final Document myDocument; - private int myLine; - private int myLineStartOffset; - - DocumentHelper(@NotNull Document document, int line) { - myDocument = document; - setLine(line); - } - - @Nullable - @Override - public CharSequence getPrevLineIndent() { - if (myLine <= 0) { - return null; - } - int prevLineStart = myDocument.getLineStartOffset(myLine - 1); - int prevLineIndentEnd = prevLineStart; - int prevLineEnd = myDocument.getLineEndOffset(myLine - 1); - CharSequence text = myDocument.getCharsSequence(); - for (; prevLineIndentEnd < prevLineEnd; prevLineIndentEnd++) { - char c = text.charAt(prevLineIndentEnd); - if (c != '\t' && c != ' ') { - break; - } - } - return text.subSequence(prevLineStart, prevLineIndentEnd); - } - - @Override - public int getCurrentLineStartOffset() { - return myLineStartOffset; - } - - @NotNull - @Override - public CharSequence getCurrentLineIndent() { - int end = myDocument.getLineEndOffset(myLine); - CharSequence text = myDocument.getCharsSequence(); - for (int i = myLineStartOffset; i < end; i++) { - char c = text.charAt(i); - if (c != ' ' && c != '\t') { - return text.subSequence(myLineStartOffset, i); - } - } - return text.subSequence(myLineStartOffset, end); - } - - @Override - public void replace(int start, int end, @NotNull String newText) { - myDocument.replaceString(myLineStartOffset + start, myLineStartOffset + end, newText); - } - - public void setLine(int line) { - myLine = line; - myLineStartOffset = myDocument.getLineStartOffset(line); - } - } - - private static class AstHelper implements Helper { - - @NotNull private final TreeHelper myHelper; - @Nullable private ASTNode myCurrentIndentHolder; - - private int myLineStartOffset; - - AstHelper(@NotNull ASTNode startNode, @NotNull TreeHelper helper) { - myHelper = helper; - myCurrentIndentHolder = myHelper.firstLeaf(startNode); - if (startNode.getStartOffset() <= 0) { - return; - } - nextLine(); - } - - @SuppressWarnings("LoopStatementThatDoesntLoop") - @Override - public CharSequence getPrevLineIndent() { - if (myCurrentIndentHolder == null) { - return null; - } - - // Check if current white space is multiline. - int end = myLineStartOffset - 1; - CharSequence text = myCurrentIndentHolder.getChars(); - for (int i = end - 1; i >= 0; i--) { - if (text.charAt(i) == '\n') { - return text.subSequence(i + 1, end); - } - } - for (ASTNode prev = prevIndentNode(myCurrentIndentHolder); prev != null; prev = prevIndentNode(prev)) { - CharSequence chars = prev.getChars(); - for (int i = chars.length() - 1; i >= 0; i--) { - if (chars.charAt(i) == '\n') { - return chars.subSequence(i + 1, chars.length()); - } - } - return chars; - } - return null; - } - - @Override - public int getCurrentLineStartOffset() { - ASTNode whiteSpace = myCurrentIndentHolder; - return whiteSpace == null ? 0 : whiteSpace.getStartOffset() + myLineStartOffset; - } - - @SuppressWarnings("UnusedAssignment") - @NotNull - @Override - public CharSequence getCurrentLineIndent() { - if (myCurrentIndentHolder == null || myLineStartOffset < 0) { - return ""; - } - - CharSequence text = myCurrentIndentHolder.getChars(); - for (int i = myLineStartOffset; i < text.length(); i++) { - char c = text.charAt(i); - if (c == '\n' || (c != ' ' && c != '\t')) { - return text.subSequence(myLineStartOffset, i); - } - } - return text.subSequence(myLineStartOffset, text.length()); - } - - @Override - public void replace(int start, int end, @NotNull String newText) { - if (myCurrentIndentHolder != null) { - myHelper.replace(newText, TextRange.create(start, end).shiftRight(getCurrentLineStartOffset()), myCurrentIndentHolder); - } - } - - public boolean nextLine() { - if (myCurrentIndentHolder == null) { - return false; - } - for (ASTNode node = myHelper.nextLeaf(myCurrentIndentHolder); node != null; node = myHelper.nextLeaf(node)) { - if (myCurrentIndentHolder.getTextLength() <= 0) { - continue; - } - CharSequence text = node.getChars(); - for (myLineStartOffset = 0; myLineStartOffset < text.length(); myLineStartOffset++) { - char c = text.charAt(myLineStartOffset); - if (c == '\n' && myLineStartOffset < text.length() - 1) { - myCurrentIndentHolder = node; - myLineStartOffset++; - return true; - } - } - } - - myCurrentIndentHolder = null; - return false; - } - - @Nullable - private ASTNode prevIndentNode(@NotNull ASTNode current) { - for (ASTNode candidate = myHelper.prevLeaf(current); candidate != null; candidate = myHelper.prevLeaf(candidate)) { - if (candidate.getStartOffset() <= 0 || StringUtil.contains(candidate.getChars(), 0, candidate.getTextLength(), '\n')) { - return candidate; - } - } - return null; - } - } - - public interface TreeHelper { - @Nullable - ASTNode prevLeaf(@NotNull ASTNode current); - - @Nullable - ASTNode nextLeaf(@NotNull ASTNode current); - - @Nullable - ASTNode firstLeaf(@NotNull ASTNode startNode); - - void replace(@NotNull String newText, @NotNull TextRange range, @NotNull ASTNode leaf); - } - - private static class TreeHelperImpl implements TreeHelper { - - @Override - public ASTNode prevLeaf(@NotNull ASTNode current) { - return TreeUtil.prevLeaf(current); - } - - @Nullable - @Override - public ASTNode nextLeaf(@NotNull ASTNode current) { - return TreeUtil.nextLeaf(current); - } - - @Nullable - @Override - public ASTNode firstLeaf(@NotNull ASTNode startNode) { - return TreeUtil.findFirstLeaf(startNode); - } - - @Override - public void replace(@NotNull String newText, @NotNull TextRange range, @NotNull ASTNode leaf) { - FormatterUtil.replaceInnerWhiteSpace(newText, leaf, range); - } - } -} diff --git a/java/java-tests/testSrc/com/intellij/java/psi/formatter/java/JavadocFormatterTest.java b/java/java-tests/testSrc/com/intellij/java/psi/formatter/java/JavadocFormatterTest.java index 250b0a5e45ae..36b34e7b6c83 100644 --- a/java/java-tests/testSrc/com/intellij/java/psi/formatter/java/JavadocFormatterTest.java +++ b/java/java-tests/testSrc/com/intellij/java/psi/formatter/java/JavadocFormatterTest.java @@ -986,6 +986,22 @@ public class JavadocFormatterTest extends AbstractJavaFormatterTest { ); } + public void test_JdWithTabs() { + doClassTest( + "\t/**\n" + + "\t \t *\n" + + "\t \t *\n" + + "\t \t */\n" + + "\tvoid check() {\n" + + "\t}", + "/**\n" + + " *\n" + + " *\n" + + " */\n" + + "void check() {\n" + + "}" + ); + } } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/psi/impl/source/codeStyle/TabPostFormatProcessorTest.java b/java/java-tests/testSrc/com/intellij/java/psi/impl/source/codeStyle/TabPostFormatProcessorTest.java deleted file mode 100644 index c53d9e602385..000000000000 --- a/java/java-tests/testSrc/com/intellij/java/psi/impl/source/codeStyle/TabPostFormatProcessorTest.java +++ /dev/null @@ -1,403 +0,0 @@ -/* - * Copyright 2000-2017 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.java.psi.impl.source.codeStyle; - -import com.intellij.lang.ASTNode; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.impl.LineSet; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.TokenType; -import com.intellij.psi.impl.source.codeStyle.TabPostFormatProcessor; -import com.intellij.psi.tree.IElementType; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jmock.Expectations; -import org.jmock.Mockery; -import org.jmock.api.Invocation; -import org.jmock.integration.junit4.JMock; -import org.jmock.integration.junit4.JUnit4Mockery; -import org.jmock.lib.action.CustomAction; -import org.jmock.lib.legacy.ClassImposteriser; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.ArrayList; -import java.util.List; - -import static org.junit.Assert.assertEquals; - -/** - * @author Denis Zhdanov - * @since 08/01/2012 - */ -@RunWith(JMock.class) -public class TabPostFormatProcessorTest { - - private static final String START_RANGE_MARKER = ""; - private static final String END_RANGE_MARKER = ""; - - private Mockery myMockery; - private Document myDocument; - - @Before - public void setUp() { - myMockery = new JUnit4Mockery() {{ - setImposteriser(ClassImposteriser.INSTANCE); - }}; - myDocument = myMockery.mock(Document.class); - } - - @After - public void checkExpectations() { - myMockery.assertIsSatisfied(); - } - - @Test - public void spacesAndWholeLineInsideRange() { - doTestSpaces( - "line 1\n" + - " \t \tline2\n" + - "line 3", - 4, - "line 1\n" + - " line2\n" + - "line 3" - ); - } - @Test - public void spacesAndExactRange() { - doTestSpaces( - "line 1\n" + - " \t \tline2\n" + - "line 3", - 4, - "line 1\n" + - " line2\n" + - "line 3" - ); - } - - @Test - public void spacesAndHeadIntersection() { - doTestSpaces( - "line 1\n" + - " \t \tline2\n" + - "line 3", - 4, - "line 1\n" + - " \tline2\n" + - "line 3" - ); - } - - @Test - public void spacesAndTailIntersection() { - doTestSpaces( - "line 1\n" + - " \t \tline2\n" + - "line 3", - 4, - "line 1\n" + - " \t line2\n" + - "line 3" - ); - } - - @Test - public void spacesAndPartialIndentInsideRange() { - doTestSpaces( - "line 1\n" + - " \t \t \t line2\n" + - "line 3", - 4, - "line 1\n" + - " \t \t line2\n" + - "line 3" - ); - } - - @Test - public void tabsAndWholeLineInsideRange() { - doTestTabs( - "line 1\n" + - " \t line2\n" + - "line 3", - 4, - "line 1\n" + - "\t \t line2\n" + - "line 3" - ); - } - - @Test - public void tabsAndHeadIntersection() { - doTestTabs( - "line 1\n" + - " \t line2\n" + - "line 3", - 4, - "line 1\n" + - "\t\t line2\n" + - "line 3" - ); - } - - @Test - public void tabsAndTailIntersection() { - doTestTabs( - "line 1\n" + - " line2\n" + - "line 3", - 4, - "line 1\n" + - " \t line2\n" + - "line 3" - ); - } - - @Test - public void tabsAndPartialIndentInsideRange() { - doTestTabs( - "line 1\n" + - " line2\n" + - "line 3", - 4, - "line 1\n" + - " \t line2\n" + - "line 3" - ); - } - - @Test - public void smartTabsForTheFirstLine() { - doTestSmartTabs( - " line 1\n" + - "\t line 2", - 4, - "\t\t line 1\n" + - "\t line 2" - ); - } - - @Test - public void smartTabsFromUpperLine() { - doTestSmartTabs( - "\t\t line 1\n" + - " 2", - 4, - "\t\t line 1\n" + - "\t\t 2" - ); - } - - @Test - public void smartTabsExactReplacement() { - doTestSmartTabs( - "\tline 1\n" + - " line 2", - 4, - "\tline 1\n" + - "\tline 2" - ); - } - - @Test - public void smartTabsMismatchedIndent() { - doTestSmartTabs( - " \tline 1\n" + - "\t line 2", - 4, - " \tline 1\n" + - "\t line 2" - ); - } - - @Test - public void smartTabsPartialMatchedIndent() { - doTestSmartTabs( - "\t\tline 1\n" + - " line 2", - 4, - "\t\tline 1\n" + - " \tline 2" - ); - } - - @Test - public void smartTabsPartialMisMatchedIndent() { - doTestSmartTabs( - "\t\tline 1\n" + - " line 2", - 4, - "\t\tline 1\n" + - " line 2" - ); - } - - private void doTestSpaces(@NotNull String initial, final int tabWidth, @NotNull String expected) { - doTest(initial, expected, false, false, tabWidth); - } - - private void doTestTabs(@NotNull String initial, final int tabWidth, @NotNull String expected) { - doTest(initial, expected, true, false, tabWidth); - } - - private void doTestSmartTabs(@NotNull String initial, final int tabWidth, @NotNull String expected) { - doTest(initial, expected, true, true, tabWidth); - } - - private void doTest(@NotNull String initial, @NotNull String expected, boolean useTabs, boolean smartTabs, int tabWidth) { - doDocumentTest(initial, expected, useTabs, smartTabs, tabWidth); - doPsiTest(initial, expected, useTabs, smartTabs, tabWidth); - } - - private void doDocumentTest(@NotNull String initial, @NotNull String expected, boolean useTabs, boolean smartTabs, int tabWidth) { - Pair pair = parse(initial); - final StringBuilder text = new StringBuilder(pair.first); - final TextRange range = pair.second; - - myMockery.checking(new Expectations() {{ - allowing(myDocument).getCharsSequence(); will(returnValue(text.toString())); - allowing(myDocument).getTextLength(); will(returnValue(text.length())); - }}); - - final LineSet lines = LineSet.createLineSet(myDocument.getCharsSequence()); - myMockery.checking(new Expectations() {{ - allowing(myDocument).getLineNumber(with(any(int.class))); will(new CustomAction("getLineNumber()") { - @Override - public Object invoke(Invocation invocation) throws Throwable { - return lines.findLineIndex((Integer)invocation.getParameter(0)); - } - }); - allowing(myDocument).getLineStartOffset(with(any(int.class))); will(new CustomAction("getLineStartOffset()") { - @Override - public Object invoke(Invocation invocation) throws Throwable { - return lines.getLineStart((Integer)invocation.getParameter(0)); - } - }); - allowing(myDocument).getLineEndOffset(with(any(int.class))); will(new CustomAction("getLineEndOffset()") { - @Override - public Object invoke(Invocation invocation) throws Throwable { - return lines.getLineEnd((Integer)invocation.getParameter(0)); - } - }); - allowing(myDocument).replaceString(with(any(int.class)), with(any(int.class)), with(any(String.class))); - will(new CustomAction("replaceString") { - @Nullable - @Override - public Object invoke(Invocation invocation) throws Throwable { - int start = (Integer)invocation.getParameter(0); - int end = (Integer)invocation.getParameter(1); - String newText = (String)invocation.getParameter(2); - text.replace(start, end, newText); - return null; - } - }); - }}); - - TabPostFormatProcessor.processViaDocument(myDocument, range, useTabs, smartTabs, tabWidth); - assertEquals(expected, text.toString()); - } - - private static Pair parse(@NotNull String text) { - int rangeMarkerStart = text.indexOf(START_RANGE_MARKER); - int rangeMarkerEnd = text.indexOf(END_RANGE_MARKER); - final StringBuilder buffer = new StringBuilder(); - final TextRange range; - if (rangeMarkerStart >= 0 && rangeMarkerEnd >= 0) { - range = TextRange.create(rangeMarkerStart, rangeMarkerEnd - START_RANGE_MARKER.length()); - buffer.append(text.substring(0, rangeMarkerStart)) - .append(text.substring(rangeMarkerStart + START_RANGE_MARKER.length(), rangeMarkerEnd)) - .append(text.substring(rangeMarkerEnd + END_RANGE_MARKER.length())); - } - else { - range = TextRange.create(0, text.length()); - buffer.append(text); - } - return Pair.create(buffer.toString(), range); - } - - private void doPsiTest(@NotNull String initial, @NotNull String expected, boolean useTabs, boolean smartTabs, int tabWidth) { - final List children = new ArrayList<>(); - final List childrenText = new ArrayList<>(); - Pair pair = parse(initial); - final String text = pair.first; - int start = 0; - boolean inWhiteSpace = initial.charAt(0) == ' ' || initial.charAt(0) == '\t'; - for (int i = 1; i <= text.length(); i++) { - if (i == text.length() || ((StringUtil.isWhiteSpace(text.charAt(i))) ^ inWhiteSpace)) { - final int childIndex = children.size(); - final int startOffset = start; - childrenText.add(new StringBuilder(text.substring(start, i))); - final ASTNode child = myMockery.mock(ASTNode.class, "child" + childIndex); - children.add(child); - final IElementType type = inWhiteSpace ? TokenType.WHITE_SPACE : TokenType.CODE_FRAGMENT; - myMockery.checking(new Expectations() {{ - allowing(child).getElementType(); will(returnValue(type)); - allowing(child).getChars(); will(returnValue(childrenText.get(childIndex))); - allowing(child).getTextLength(); will(returnValue(childrenText.get(childIndex).length())); - allowing(child).getStartOffset(); will(returnValue(startOffset)); - }}); - inWhiteSpace = !inWhiteSpace; - start = i; - } - } - - final ASTNode root = myMockery.mock(ASTNode.class); - myMockery.checking(new Expectations() {{ - allowing(root).getFirstChildNode(); will(returnValue(children.get(0))); - allowing(root).getTextLength(); will(returnValue(text.length())); - allowing(root).getStartOffset(); will(returnValue(0)); - }}); - - TabPostFormatProcessor.TreeHelper helper = new TabPostFormatProcessor.TreeHelper() { - - @Override - public ASTNode prevLeaf(@NotNull ASTNode current) { - int i = children.indexOf(current); - return i > 0 ? children.get(i - 1) : null; - } - - @Override - public ASTNode nextLeaf(@NotNull ASTNode current) { - int i = children.indexOf(current); - return i < children.size() - 1 ? children.get(i + 1) : null; - } - - @Override - public ASTNode firstLeaf(@NotNull ASTNode startNode) { - return root == startNode ? children.get(0) : null; - } - - @Override - public void replace(@NotNull String newText, @NotNull TextRange range, @NotNull ASTNode leaf) { - int i = children.indexOf(leaf); - childrenText.get(i).replace(range.getStartOffset() - leaf.getStartOffset(), range.getEndOffset() - leaf.getStartOffset(), newText); - } - }; - - TabPostFormatProcessor.processViaPsi(root, pair.second, helper, useTabs, smartTabs, tabWidth); - StringBuilder actual = new StringBuilder(); - for (ASTNode child : children) { - actual.append(child.getChars()); - } - assertEquals(expected, actual.toString()); - } -} diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index dd92cd281200..b5fb9748e057 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -1452,7 +1452,6 @@ -