/* * 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.psi.impl.source.codeStyle.lineIndent; import com.intellij.lang.Language; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.project.Project; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.psi.codeStyle.lineIndent.LineIndentProvider; import com.intellij.psi.impl.source.codeStyle.SemanticEditorPosition; import com.intellij.psi.impl.source.codeStyle.SemanticEditorPosition.SyntaxElement; import com.intellij.psi.impl.source.codeStyle.lineIndent.IndentCalculator.BaseLineOffsetCalculator; import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import static com.intellij.formatting.Indent.Type; import static com.intellij.formatting.Indent.Type.*; import static com.intellij.psi.impl.source.codeStyle.lineIndent.JavaLikeLangLineIndentProvider.JavaLikeElement.*; /** * A base class for Java-like language line indent provider. * If a LineIndentProvider is not provided, {@link FormatterBasedLineIndentProvider} is used. * If a registered provider is unable to calculate the indentation, * {@link FormatterBasedIndentAdjuster} will be used. */ public abstract class JavaLikeLangLineIndentProvider implements LineIndentProvider{ public enum JavaLikeElement implements SyntaxElement { Whitespace, Semicolon, BlockOpeningBrace, BlockClosingBrace, ArrayOpeningBracket, ArrayClosingBracket, RightParenthesis, LeftParenthesis, Colon, SwitchCase, SwitchDefault, ElseKeyword, IfKeyword, ForKeyword, TryKeyword, DoKeyword, BlockComment, DocBlockStart, DocBlockEnd, LineComment, Comma, LanguageStartDelimiter } @Nullable @Override public String getLineIndent(@NotNull Project project, @NotNull Editor editor, @Nullable Language language, int offset) { if (offset > 0) { IndentCalculator indentCalculator = getIndent(project, editor, language, offset - 1); if (indentCalculator != null) { return indentCalculator.getIndentString(language, getPosition(editor, offset - 1)); } } else { return ""; } return null; } @Nullable protected IndentCalculator getIndent(@NotNull Project project, @NotNull Editor editor, @Nullable Language language, int offset) { IndentCalculatorFactory myFactory = new IndentCalculatorFactory(project, editor); if (getPosition(editor, offset).matchesRule( position -> position.isAt(Whitespace) && position.isAtMultiline())) { if (getPosition(editor, offset).before().isAt(Comma)) { SemanticEditorPosition position = getPosition(editor,offset); if (position.hasEmptyLineAfter(offset) && !position.after().isAtAnyOf(ArrayClosingBracket, BlockOpeningBrace, BlockClosingBrace, RightParenthesis) && !position.isAtEnd() && position.findLeftParenthesisBackwardsSkippingNested(LeftParenthesis, RightParenthesis, element -> element == BlockClosingBrace || element == BlockOpeningBrace || element == Semicolon) .isAt(LeftParenthesis)) { return myFactory.createIndentCalculator(NONE, IndentCalculator.LINE_AFTER); } } else if (getPosition(editor, offset + 1).matchesRule( position -> position.isAt(BlockClosingBrace) && !position.after().afterOptional(Whitespace).isAt(Comma))) { return myFactory.createIndentCalculator( NONE, position -> { position.findLeftParenthesisBackwardsSkippingNested(BlockOpeningBrace, BlockClosingBrace); if (!position.isAtEnd()) { return getBlockStatementStartOffset(position); } return -1; }); } else if (getPosition(editor, offset).matchesRule( position -> position .before() .beforeOptional(Whitespace) .isAt(BlockClosingBrace))) { return myFactory.createIndentCalculator(getBlockIndentType(project, language), IndentCalculator.LINE_BEFORE); } else if (getPosition(editor, offset).matchesRule(position -> position.before().isAt(Semicolon))) { SemanticEditorPosition beforeSemicolon = getPosition(editor, offset).before().beforeOptional(Semicolon); if (beforeSemicolon.isAt(BlockClosingBrace)) { beforeSemicolon.beforeParentheses(BlockOpeningBrace, BlockClosingBrace); } int statementStart = getStatementStartOffset(beforeSemicolon); SemanticEditorPosition atStatementStart = getPosition(editor, statementStart); if (!isInsideForLikeConstruction(atStatementStart)) { return myFactory.createIndentCalculator(NONE, position -> statementStart); } } else if (getPosition(editor, offset).matchesRule( position -> position.before().isAt(ArrayOpeningBracket) )) { return myFactory.createIndentCalculator(getIndentTypeInBrackets(), IndentCalculator.LINE_BEFORE); } else if (getPosition(editor, offset).matchesRule( position -> position.before().isAt(LeftParenthesis) )) { return myFactory.createIndentCalculator(CONTINUATION, IndentCalculator.LINE_BEFORE); } else if (getPosition(editor, offset).matchesRule( position -> position.before().isAt(BlockOpeningBrace) && !position.before().beforeOptional(Whitespace).isAt(LeftParenthesis) )) { SemanticEditorPosition position = getPosition(editor, offset).before(); return myFactory.createIndentCalculator(getIndentTypeInBlock(project, language, position), this::getBlockStatementStartOffset); } else if (getPosition(editor, offset).matchesRule( position -> position.before().isAt(Colon) && position.isAfterOnSameLine(SwitchCase, SwitchDefault) ) || getPosition(editor, offset).matchesRule( position -> position.before().isAtAnyOf(ElseKeyword, DoKeyword) )) { return myFactory.createIndentCalculator(NORMAL, IndentCalculator.LINE_BEFORE); } else if (getPosition(editor, offset).matchesRule( position -> position.before().isAt(BlockComment) && position.before().isAt(Whitespace) && position.isAtMultiline() )) { return myFactory.createIndentCalculator(NONE, position -> position.findStartOf(BlockComment)); } else if (getPosition(editor, offset).matchesRule( position -> position.before().isAt(DocBlockEnd) )) { return myFactory.createIndentCalculator(NONE, position -> position.findStartOf(DocBlockStart)); } else { SemanticEditorPosition position = getPosition(editor, offset); position = position.before().beforeOptionalMix(LineComment, BlockComment, Whitespace); if (position.isAt(RightParenthesis)) { int offsetAfterParen = position.getStartOffset() + 1; position.beforeParentheses(LeftParenthesis, RightParenthesis); if (!position.isAtEnd()) { position.beforeOptional(Whitespace); if (position.isAt(IfKeyword) || position.isAt(ForKeyword)) { SyntaxElement element = position.getCurrElement(); assert element != null; final int controlKeywordOffset = position.getStartOffset(); Type indentType = getPosition(editor, offsetAfterParen).afterOptional(Whitespace).isAt(BlockOpeningBrace) ? NONE : NORMAL; return myFactory.createIndentCalculator(indentType, baseLineOffset -> controlKeywordOffset); } } } } } //return myFactory.createIndentCalculator(NONE, IndentCalculator.LINE_BEFORE); /* TO CHECK UNCOVERED CASES */ return null; } protected boolean isInsideForLikeConstruction(SemanticEditorPosition position) { return position.isAfterOnSameLine(ForKeyword); } private int getBlockStatementStartOffset(@NotNull SemanticEditorPosition position) { position.before().beforeOptional(BlockOpeningBrace); if (position.isAt(Whitespace)) { if (position.isAtMultiline()) return position.after().getStartOffset(); position.before(); } return getStatementStartOffset(position); } private int getStatementStartOffset(@NotNull SemanticEditorPosition position) { Language currLanguage = position.getLanguage(); while (!position.isAtEnd()) { if (currLanguage == Language.ANY || currLanguage == null) currLanguage = position.getLanguage(); if (position.isAt(Colon)) { SemanticEditorPosition afterColon = getPosition(position.getEditor(), position.getStartOffset()).after().afterOptional(Whitespace); if (getPosition(position.getEditor(), position.getStartOffset()).isAfterOnSameLine(SwitchCase, SwitchDefault)) { return afterColon.getStartOffset(); } } else if (position.isAt(RightParenthesis)) { position.beforeParentheses(LeftParenthesis, RightParenthesis); } else if (position.isAt(BlockClosingBrace)) { position.beforeParentheses(BlockOpeningBrace, BlockClosingBrace); } else if (position.isAt(ArrayClosingBracket)) { position.beforeParentheses(ArrayOpeningBracket, ArrayClosingBracket); } else if (position.isAtAnyOf(Semicolon, BlockOpeningBrace, BlockComment, DocBlockEnd, LeftParenthesis, LanguageStartDelimiter) || (position.getLanguage() != Language.ANY) && !position.isAtLanguage(currLanguage)) { SemanticEditorPosition statementStart = getPosition(position.getEditor(), position.getStartOffset()); statementStart.after().afterOptionalMix(Whitespace, LineComment); if (!statementStart.isAtEnd()) { return statementStart.getStartOffset(); } } position.before(); } return 0; } protected SemanticEditorPosition getPosition(@NotNull Editor editor, int offset) { return new SemanticEditorPosition((EditorEx)editor, offset) { @Override public SyntaxElement map(@NotNull IElementType elementType) { return mapType(elementType); } }; } @Nullable protected abstract SyntaxElement mapType(@NotNull IElementType tokenType); @Nullable protected Type getIndentTypeInBlock(@NotNull Project project, @Nullable Language language, @NotNull SemanticEditorPosition blockStartPosition) { if (language != null) { CommonCodeStyleSettings settings = CodeStyleSettingsManager.getSettings(project).getCommonSettings(language); if (settings.BRACE_STYLE == CommonCodeStyleSettings.NEXT_LINE_SHIFTED) { return settings.METHOD_BRACE_STYLE == CommonCodeStyleSettings.NEXT_LINE_SHIFTED ? NONE : null; } } return NORMAL; } @Nullable private static Type getBlockIndentType(@NotNull Project project, @Nullable Language language) { if (language != null) { CommonCodeStyleSettings settings = CodeStyleSettingsManager.getSettings(project).getCommonSettings(language); if (settings.BRACE_STYLE == CommonCodeStyleSettings.NEXT_LINE || settings.BRACE_STYLE == CommonCodeStyleSettings.END_OF_LINE) { return NONE; } } return null; } public static class IndentCalculatorFactory { private Project myProject; private Editor myEditor; public IndentCalculatorFactory(Project project, Editor editor) { myProject = project; myEditor = editor; } @Nullable public IndentCalculator createIndentCalculator(@Nullable Type indentType, @Nullable BaseLineOffsetCalculator baseLineOffsetCalculator) { return indentType != null ? new IndentCalculator(myProject, myEditor, baseLineOffsetCalculator != null ? baseLineOffsetCalculator : IndentCalculator.LINE_BEFORE, indentType) : null; } } @Override public final boolean isSuitableFor(@Nullable Language language) { return language != null && isSuitableForLanguage(language); } public abstract boolean isSuitableForLanguage(@NotNull Language language); protected Type getIndentTypeInBrackets() { return CONTINUATION; } }