From c461b4669c7081ddbdfdcf9283205327ac16700f Mon Sep 17 00:00:00 2001 From: Yaroslav Lepenkin Date: Tue, 9 Jun 2015 16:20:10 +0300 Subject: [PATCH] Using formatting blocks to detect proper indent --- .../intellij/formatting/FormatProcessor.java | 2 +- .../com/intellij/formatting/IndentImpl.java | 4 +- .../FormatterBasedLineIndentInfoBuilder.java | 100 +++++++++++++++++ .../autodetect/IndentOptionsDetectorImpl.java | 10 +- .../autodetect/NewLineBlocksIterator.java | 104 ++++++++++++++++++ 5 files changed, 213 insertions(+), 7 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/FormatterBasedLineIndentInfoBuilder.java create mode 100644 platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/NewLineBlocksIterator.java diff --git a/platform/lang-impl/src/com/intellij/formatting/FormatProcessor.java b/platform/lang-impl/src/com/intellij/formatting/FormatProcessor.java index e095d661e685..7142d8f52001 100644 --- a/platform/lang-impl/src/com/intellij/formatting/FormatProcessor.java +++ b/platform/lang-impl/src/com/intellij/formatting/FormatProcessor.java @@ -41,7 +41,7 @@ import org.jetbrains.annotations.Nullable; import java.util.*; -class FormatProcessor { +public class FormatProcessor { private static final Map ALIGNMENT_PROCESSORS = new EnumMap(Alignment.Anchor.class); diff --git a/platform/lang-impl/src/com/intellij/formatting/IndentImpl.java b/platform/lang-impl/src/com/intellij/formatting/IndentImpl.java index 04c806f3407f..7e35e84e4dbf 100644 --- a/platform/lang-impl/src/com/intellij/formatting/IndentImpl.java +++ b/platform/lang-impl/src/com/intellij/formatting/IndentImpl.java @@ -18,7 +18,7 @@ package com.intellij.formatting; import org.jetbrains.annotations.NonNls; -class IndentImpl extends Indent { +public class IndentImpl extends Indent { private final boolean myIsAbsolute; private final boolean myRelativeToDirectParent; @@ -38,7 +38,7 @@ class IndentImpl extends Indent { myEnforceIndentToChildren = enforceIndentToChildren; } - Type getType() { + public Type getType() { return myType; } diff --git a/platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/FormatterBasedLineIndentInfoBuilder.java b/platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/FormatterBasedLineIndentInfoBuilder.java new file mode 100644 index 000000000000..1bba6b022535 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/FormatterBasedLineIndentInfoBuilder.java @@ -0,0 +1,100 @@ +/* + * Copyright 2000-2015 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.codeStyle.autodetect; + +import com.intellij.formatting.*; +import com.intellij.lang.LanguageFormatting; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Condition; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.text.CharArrayUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public class FormatterBasedLineIndentInfoBuilder { + private static final int MAX_NEW_LINE_BLOCKS_TO_PROCESS = 500; + + private final PsiFile myFile; + private final Document myDocument; + private final CharSequence myText; + private final CodeStyleSettings mySettings; + private final FormattingModelBuilder myFormattingModelBuilder; + + public FormatterBasedLineIndentInfoBuilder(@NotNull PsiFile file) { + Project project = file.getProject(); + + myFile = file; + myDocument = PsiDocumentManager.getInstance(project).getDocument(file); + myText = myDocument != null ? myDocument.getCharsSequence() : null; + mySettings = CodeStyleSettingsManager.getSettings(project); + myFormattingModelBuilder = LanguageFormatting.INSTANCE.forContext(myFile); + } + + public List build() { + if (myText == null || myFormattingModelBuilder == null) return null; + + List normallyIndentedBlocks = ContainerUtil.filter(getBlocksStartingNewLine(), new Condition() { + @Override + public boolean value(Block block) { + Indent.Type type = block.getIndent() instanceof IndentImpl ? ((IndentImpl)block.getIndent()).getType() : null; + return type == Indent.Type.NONE || type == Indent.Type.NORMAL; + } + }); + + return ContainerUtil.map(normallyIndentedBlocks, new Function() { + @Override + public LineIndentInfo fun(Block newLineBlock) { + int blockStartOffset = newLineBlock.getTextRange().getStartOffset(); + int lineStartOffset = myDocument.getLineStartOffset(myDocument.getLineNumber(blockStartOffset)); + return createLineIndentInfo(lineStartOffset, blockStartOffset); + } + }); + } + + @NotNull + private List getBlocksStartingNewLine() { + FormattingModel model = myFormattingModelBuilder.createModel(myFile, mySettings); + Block root = model.getRootBlock(); + NewLineBlocksIterator newLineBlocksIterator = new NewLineBlocksIterator(root, myDocument); + + List newLineBlocks = new ArrayList(); + int currentLine = 0; + while (newLineBlocksIterator.hasNext() && currentLine < MAX_NEW_LINE_BLOCKS_TO_PROCESS) { + Block next = newLineBlocksIterator.next(); + newLineBlocks.add(next); + currentLine++; + } + + return newLineBlocks; + } + + @NotNull + private LineIndentInfo createLineIndentInfo(int lineStartOffset, int textStartOffset) { + if (CharArrayUtil.indexOf(myText, "\t", lineStartOffset, textStartOffset) > 0) { + return LineIndentInfo.LINE_WITH_TABS; + } + return LineIndentInfo.newWhiteSpaceIndent(textStartOffset - lineStartOffset); + } + +} diff --git a/platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/IndentOptionsDetectorImpl.java b/platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/IndentOptionsDetectorImpl.java index 5f56ecc9b264..9ce9c929fbfb 100644 --- a/platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/IndentOptionsDetectorImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/IndentOptionsDetectorImpl.java @@ -26,7 +26,7 @@ import org.jetbrains.annotations.NotNull; import java.util.List; -import static com.intellij.psi.codeStyle.CommonCodeStyleSettings.*; +import static com.intellij.psi.codeStyle.CommonCodeStyleSettings.IndentOptions; public class IndentOptionsDetectorImpl implements IndentOptionsDetector { private static Logger LOG = Logger.getInstance("#com.intellij.psi.codeStyle.CommonCodeStyleSettings.IndentOptionsDetector"); @@ -52,9 +52,11 @@ public class IndentOptionsDetectorImpl implements IndentOptionsDetector { IndentOptions indentOptions = (IndentOptions)CodeStyleSettingsManager.getSettings(myProject).getIndentOptions(myFile.getFileType()).clone(); if (myDocument != null) { - List linesInfo = new LineIndentInfoBuilder(myDocument.getCharsSequence(), myLanguage).build(); - IndentUsageStatistics stats = new IndentUsageStatisticsImpl(linesInfo); - adjustIndentOptions(indentOptions, stats); + List linesInfo = new FormatterBasedLineIndentInfoBuilder(myFile).build(); + if (linesInfo != null) { + IndentUsageStatistics stats = new IndentUsageStatisticsImpl(linesInfo); + adjustIndentOptions(indentOptions, stats); + } } return indentOptions; diff --git a/platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/NewLineBlocksIterator.java b/platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/NewLineBlocksIterator.java new file mode 100644 index 000000000000..9bc7a32a5174 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/psi/codeStyle/autodetect/NewLineBlocksIterator.java @@ -0,0 +1,104 @@ +/* + * Copyright 2000-2015 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.codeStyle.autodetect; + +import com.intellij.formatting.Block; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.util.TextRange; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Stack; + + +public class NewLineBlocksIterator implements Iterator { + private final Document myDocument; + private final int myTotalLines; + + private int myCurrentLineStartOffset; + private int myCurrentDocumentLine; + private Stack myStack = new Stack(); + + public NewLineBlocksIterator(Block root, Document document) { + myStack.add(root); + myDocument = document; + myTotalLines = myDocument.getLineCount(); + + myCurrentDocumentLine = 0; + myCurrentLineStartOffset = 0; + } + + @Override + public boolean hasNext() { + if (myCurrentDocumentLine < myTotalLines) { + popUntilTopBlockStartOffsetGreaterOrEqual(myCurrentLineStartOffset); + return !myStack.isEmpty(); + } + return false; + } + + @Override + public Block next() { + popUntilTopBlockStartOffsetGreaterOrEqual(myCurrentLineStartOffset); + + Block current = myStack.peek(); + TextRange currentBlockRange = current.getTextRange(); + + myCurrentDocumentLine = myDocument.getLineNumber(currentBlockRange.getStartOffset()); + myCurrentDocumentLine++; + if (myCurrentDocumentLine < myTotalLines) { + myCurrentLineStartOffset = myDocument.getLineStartOffset(myCurrentDocumentLine); + if (currentBlockRange.getEndOffset() < myCurrentLineStartOffset) { + myStack.pop(); + } + else { + pushAll(current); + } + } + + return current; + } + + private void popUntilTopBlockStartOffsetGreaterOrEqual(final int lineStartOffset) { + if (myStack.isEmpty()) return; + + Block current = myStack.peek(); + TextRange range = current.getTextRange(); + int currentStartOffset = range.getStartOffset(); + int currentEndOffset = range.getEndOffset(); + + if (currentStartOffset < lineStartOffset) { + myStack.pop(); + if (currentEndOffset > lineStartOffset) { + pushAll(current); + } + popUntilTopBlockStartOffsetGreaterOrEqual(lineStartOffset); + } + } + + private void pushAll(Block current) { + List blocks = current.getSubBlocks(); + Collections.reverse(blocks); + for (Block block : blocks) { + myStack.push(block); + } + } + + @Override + public void remove() { + } +}