diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaLineIndentProvider.java b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaLineIndentProvider.java new file mode 100644 index 000000000000..cda6500c11b6 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaLineIndentProvider.java @@ -0,0 +1,54 @@ +/* + * Copyright 2000-2016 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.codeInsight.editorActions; + +import com.intellij.lang.Language; +import com.intellij.lang.java.JavaLanguage; +import com.intellij.psi.JavaTokenType; +import com.intellij.psi.TokenType; +import com.intellij.psi.impl.source.codeStyle.JavaLikeLangLineIndentProvider; +import com.intellij.psi.impl.source.codeStyle.SemanticEditorPosition; +import com.intellij.psi.tree.IElementType; +import com.intellij.util.containers.HashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Rustam Vishnyakov + */ +public class JavaLineIndentProvider extends JavaLikeLangLineIndentProvider { + private final static HashMap SYNTAX_MAP = new HashMap<>(); + static { + SYNTAX_MAP.put(TokenType.WHITE_SPACE, JavaLikeElement.Whitespace); + SYNTAX_MAP.put(JavaTokenType.SEMICOLON, JavaLikeElement.Semicolon); + SYNTAX_MAP.put(JavaTokenType.LBRACE, JavaLikeElement.BlockOpeningBrace); + SYNTAX_MAP.put(JavaTokenType.RBRACE, JavaLikeElement.BlockClosingBrace); + SYNTAX_MAP.put(JavaTokenType.LBRACKET, JavaLikeElement.ArrayOpeningBracket); + SYNTAX_MAP.put(JavaTokenType.RPARENTH, JavaLikeElement.RightParenthesis); + SYNTAX_MAP.put(JavaTokenType.LPARENTH, JavaLikeElement.LeftParenthesis); + } + + @Nullable + @Override + protected SemanticEditorPosition.SyntaxElement mapType(@NotNull IElementType tokenType) { + return SYNTAX_MAP.get(tokenType); + } + + @Override + public boolean isSuitableFor(@Nullable Language language) { + return language != null && language.isKindOf(JavaLanguage.INSTANCE); + } +} diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/JavaLikeLangLineIndentProvider.java b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/JavaLikeLangLineIndentProvider.java index ea0881198961..4eb7f9e07367 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/JavaLikeLangLineIndentProvider.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/JavaLikeLangLineIndentProvider.java @@ -21,10 +21,12 @@ import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; +import com.intellij.psi.impl.source.codeStyle.SemanticEditorPosition.SyntaxElement; import com.intellij.psi.tree.IElementType; import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NotNull; @@ -39,29 +41,30 @@ import static com.intellij.formatting.Indent.Type.*; */ public abstract class JavaLikeLangLineIndentProvider extends FormatterBasedLineIndentProvider { - protected enum JavaLikeElement implements SemanticEditorPosition.SyntaxElement { + protected enum JavaLikeElement implements SyntaxElement { Whitespace, Semicolon, BlockOpeningBrace, BlockClosingBrace, - ArrayOpeningBracket + ArrayOpeningBracket, + RightParenthesis, + LeftParenthesis } - @Nullable @Override public String getLineIndent(@NotNull Project project, @NotNull Editor editor, Language language, int offset) { - Type indent = getIndent(project, editor, language, offset); - if (indent != null){ - return getIndentString(project, editor, offset, indent); + Pair indentData = getIndent(project, editor, language, offset); + if (indentData != null){ + return getIndentString(project, editor, offset, indentData); } return super.getLineIndent(project, editor, language, offset); } @Nullable - protected Type getIndent(@NotNull Project project, @NotNull Editor editor, @Nullable Language language, int offset) { + protected Pair getIndent(@NotNull Project project, @NotNull Editor editor, @Nullable Language language, int offset) { if (offset > 0) { - offset --; + offset--; if (getPosition(editor, offset).matchesRule( position -> position.isAt(JavaLikeElement.Whitespace) && position.isAtMultiline())) { @@ -71,17 +74,17 @@ public abstract class JavaLikeLangLineIndentProvider extends FormatterBasedLineI .beforeOptional(JavaLikeElement.Semicolon) .beforeOptional(JavaLikeElement.Whitespace) .isAt(JavaLikeElement.BlockClosingBrace))) { - return getBlockIndentType(project, language); + return createIndentData(getBlockIndentType(project, language), JavaLikeElement.BlockClosingBrace); } else if (getPosition(editor, offset).matchesRule( - position -> position.before().isAt(JavaLikeElement.ArrayOpeningBracket) + position -> position.before().isAt(JavaLikeElement.ArrayOpeningBracket) )) { - return CONTINUATION; + return createIndentData(CONTINUATION, JavaLikeElement.ArrayOpeningBracket); } else if (getPosition(editor, offset).matchesRule( - position -> position.before().isAt(JavaLikeElement.BlockOpeningBrace) + position -> position.before().isAt(JavaLikeElement.BlockOpeningBrace) )) { - return getIndentTypeInBlock(project, language); + return createIndentData(getIndentTypeInBlock(project, language), JavaLikeElement.BlockOpeningBrace); } } } @@ -98,17 +101,20 @@ public abstract class JavaLikeLangLineIndentProvider extends FormatterBasedLineI } @Nullable - protected abstract SemanticEditorPosition.SyntaxElement mapType(@NotNull IElementType tokenType); + protected abstract SyntaxElement mapType(@NotNull IElementType tokenType); @Nullable - private static String getIndentString(@NotNull Project project, @NotNull Editor editor, int offset, Type indentType) { + private String getIndentString(@NotNull Project project, + @NotNull Editor editor, + int offset, + @NotNull Pair indentData) { Document document = editor.getDocument(); - String lastLineIndent = getLastLineIndent(document.getCharsSequence(), offset); + String baseIndent = getBaseIndent(editor, indentData.second, offset); PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(document); if (file != null) { CommonCodeStyleSettings.IndentOptions options = CodeStyleSettingsManager.getSettings(project).getIndentOptionsByFile(file); - return - lastLineIndent + new IndentInfo(0, indentTypeToSize(indentType, options), 0, false).generateNewWhiteSpace(options); + return + baseIndent + new IndentInfo(0, indentTypeToSize(indentData.first, options), 0, false).generateNewWhiteSpace(options); } return null; } @@ -124,11 +130,12 @@ public abstract class JavaLikeLangLineIndentProvider extends FormatterBasedLineI } @NotNull - private static String getLastLineIndent(@NotNull CharSequence docChars, int offset) { + private String getBaseIndent(@NotNull Editor editor, @NotNull SyntaxElement afterElement, int offset) { + CharSequence docChars = editor.getDocument().getCharsSequence(); if (offset > 0) { - int indentStart = CharArrayUtil.shiftBackward(docChars, offset - 1, " \t\n\r"); - if (indentStart > 0) { - indentStart = CharArrayUtil.shiftBackwardUntil(docChars, indentStart, "\n") + 1; + int indentLineOffset = getOffsetInBaseIndentLine(editor, docChars, afterElement, offset - 1); + if (indentLineOffset > 0) { + int indentStart = CharArrayUtil.shiftBackwardUntil(docChars, indentLineOffset, "\n") + 1; if (indentStart >= 0) { int indentEnd = CharArrayUtil.shiftForward(docChars, indentStart, " \t"); if (indentEnd > indentStart) { @@ -139,6 +146,38 @@ public abstract class JavaLikeLangLineIndentProvider extends FormatterBasedLineI } return ""; } + + + private int getOffsetInBaseIndentLine(@NotNull Editor editor, + @NotNull CharSequence docChars, + @NotNull SyntaxElement afterElement, + int offset) { + if (JavaLikeElement.BlockOpeningBrace.equals(afterElement)) { + return findStatementStart(editor, afterElement, offset); + } + else { + return CharArrayUtil.shiftBackward(docChars, offset, " \t\n\r"); + } + } + + + private int findStatementStart(@NotNull Editor editor, @NotNull SyntaxElement afterElement, int offset) { + SemanticEditorPosition position = getPosition(editor, offset); + position + .beforeOptional(JavaLikeElement.Whitespace) + .beforeOptional(afterElement) + .beforeOptional(JavaLikeElement.Whitespace); + if (position.isAt(JavaLikeElement.RightParenthesis)) { + position.beforeParentheses(JavaLikeElement.LeftParenthesis, JavaLikeElement.RightParenthesis); + } + while (!position.isAtEnd()) { + if (position.isAt(JavaLikeElement.Whitespace) && position.isAtMultiline()) { + return position.after().getStartOffset(); + } + position.before(); + } + return -1; + } @Nullable private static Type getIndentTypeInBlock(@NotNull Project project, @Nullable Language language) { @@ -161,4 +200,9 @@ public abstract class JavaLikeLangLineIndentProvider extends FormatterBasedLineI } return null; } + + @Nullable + private static Pair createIndentData(@Nullable Type type, @NotNull SyntaxElement element) { + return type != null ? Pair.create(type, element) : null; + } } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/SemanticEditorPosition.java b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/SemanticEditorPosition.java index 0482f234f08c..c194a286d538 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/SemanticEditorPosition.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/SemanticEditorPosition.java @@ -56,10 +56,43 @@ public abstract class SemanticEditorPosition { return this; } + public SemanticEditorPosition after() { + if (!myIterator.atEnd()) { + myIterator.advance(); + } + return this; + } + + public SemanticEditorPosition beforeParentheses(@NotNull SyntaxElement leftParenthesis, @NotNull SyntaxElement rightParenthesis) { + int parenLevel = 0; + while (!myIterator.atEnd()) { + SyntaxElement currElement = map(myIterator.getTokenType()); + myIterator.retreat(); + if (rightParenthesis.equals(currElement)) { + parenLevel++; + } + else if (leftParenthesis.equals(currElement)) { + parenLevel--; + if (parenLevel < 1) { + break; + } + } + } + return this; + } + public boolean isAt(@NotNull SyntaxElement syntaxElement) { return !myIterator.atEnd() && syntaxElement.equals(map(myIterator.getTokenType())); } + public boolean isAtEnd() { + return myIterator.atEnd(); + } + + public int getStartOffset() { + return myIterator.getStart(); + } + @SuppressWarnings("unused") public boolean isAtAnyOf(@NotNull SyntaxElement... syntaxElements) { if (!myIterator.atEnd()) { diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index bbd5a7ec7e69..c4bb22800061 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -1320,6 +1320,7 @@ implementationClass="com.intellij.codeInsight.hint.ClassInitializerDeclarationRangeHandler"/> +