diff options
Diffstat (limited to 'platform/lang-api/src/com/intellij/psi/codeStyle/autodetect')
6 files changed, 460 insertions, 0 deletions
diff --git a/platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/IndentOptionsDetector.java b/platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/IndentOptionsDetector.java new file mode 100644 index 000000000000..527ee9e79a49 --- /dev/null +++ b/platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/IndentOptionsDetector.java @@ -0,0 +1,106 @@ +/* + * Copyright 2000-2014 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.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +import static com.intellij.psi.codeStyle.CommonCodeStyleSettings.*; + +public class IndentOptionsDetector { + private static Logger LOG = Logger.getInstance("#com.intellij.psi.codeStyle.CommonCodeStyleSettings.IndentOptionsDetector"); + + private static final double RATE_THRESHOLD = 0.8; + private static final int MIN_LINES_THRESHOLD = 50; + private static final int MAX_INDENT_TO_DETECT = 8; + + private final PsiFile myFile; + private final Project myProject; + private final Document myDocument; + + public IndentOptionsDetector(@NotNull PsiFile file) { + myFile = file; + myProject = file.getProject(); + myDocument = PsiDocumentManager.getInstance(myProject).getDocument(myFile); + } + + @NotNull + public IndentOptions getIndentOptions() { + IndentOptions indentOptions = (IndentOptions)CodeStyleSettingsManager.getSettings(myProject).getIndentOptions(myFile.getFileType()).clone(); + + if (myDocument != null) { + List<LineIndentInfo> linesInfo = new LineIndentInfoBuilder(myDocument.getCharsSequence()).build(); + IndentUsageStatistics stats = new IndentUsageStatisticsImpl(linesInfo); + adjustIndentOptions(indentOptions, stats); + } + + return indentOptions; + } + + private void adjustIndentOptions(@NotNull IndentOptions indentOptions, @NotNull IndentUsageStatistics stats) { + int linesWithTabs = stats.getTotalLinesWithLeadingTabs(); + int linesWithWhiteSpaceIndent = stats.getTotalLinesWithLeadingSpaces(); + + int totalLines = linesWithTabs + linesWithWhiteSpaceIndent; + double lineWithTabsRate = (double)linesWithTabs / totalLines; + + if (linesWithTabs > MIN_LINES_THRESHOLD && lineWithTabsRate > RATE_THRESHOLD) { + if (!indentOptions.USE_TAB_CHARACTER) { + indentOptions.USE_TAB_CHARACTER = true; + LOG.info("Detected tab usage in" + myFile); + } + } + else if (linesWithWhiteSpaceIndent > MIN_LINES_THRESHOLD && (1 - lineWithTabsRate) > RATE_THRESHOLD) { + int newIndentSize = getPositiveIndentSize(stats); + if (newIndentSize > 0 && indentOptions.INDENT_SIZE != newIndentSize) { + indentOptions.INDENT_SIZE = newIndentSize; + LOG.info("Detected indent size: " + newIndentSize + " for file " + myFile); + } + } + } + + private static int getPositiveIndentSize(@NotNull IndentUsageStatistics stats) { + int totalIndentSizesDetected = stats.getTotalIndentSizesDetected(); + if (totalIndentSizesDetected == 0) return -1; + + IndentUsageInfo maxUsedIndentInfo = stats.getKMostUsedIndentInfo(0); + int maxUsedIndentSize = maxUsedIndentInfo.getIndentSize(); + + if (maxUsedIndentSize == 0) { + if (totalIndentSizesDetected < 1) return -1; + + maxUsedIndentInfo = stats.getKMostUsedIndentInfo(1); + maxUsedIndentSize = maxUsedIndentInfo.getIndentSize(); + } + + if (maxUsedIndentSize <= MAX_INDENT_TO_DETECT) { + int totalUsagesWithoutZeroIndent = stats.getTotalLinesWithLeadingSpaces() - stats.getTimesIndentUsed(0); + double usageRate = (double)maxUsedIndentInfo.getTimesUsed() / totalUsagesWithoutZeroIndent; + if (usageRate > RATE_THRESHOLD) { + return maxUsedIndentSize; + } + } + + return -1; + } +} diff --git a/platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/IndentUsageInfo.java b/platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/IndentUsageInfo.java new file mode 100644 index 000000000000..a600483a07a6 --- /dev/null +++ b/platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/IndentUsageInfo.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2014 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; + +public class IndentUsageInfo { + private final int indentSize; + private final int timesUsed; + + public IndentUsageInfo(int indentSize, int timesUsed) { + this.indentSize = indentSize; + this.timesUsed = timesUsed; + } + + public int getIndentSize() { + return indentSize; + } + + public int getTimesUsed() { + return timesUsed; + } + + @Override + public String toString() { + return "indent: " + indentSize + ", used " + timesUsed; + } +} diff --git a/platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/IndentUsageStatistics.java b/platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/IndentUsageStatistics.java new file mode 100644 index 000000000000..7e3bee7221da --- /dev/null +++ b/platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/IndentUsageStatistics.java @@ -0,0 +1,30 @@ +/* + * Copyright 2000-2014 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; + +public interface IndentUsageStatistics { + + int getTotalLinesWithLeadingTabs(); + + int getTotalLinesWithLeadingSpaces(); + + IndentUsageInfo getKMostUsedIndentInfo(int k); + + int getTotalIndentSizesDetected(); + + int getTimesIndentUsed(int indent); + +} diff --git a/platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/IndentUsageStatisticsImpl.java b/platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/IndentUsageStatisticsImpl.java new file mode 100644 index 000000000000..99de432f101d --- /dev/null +++ b/platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/IndentUsageStatisticsImpl.java @@ -0,0 +1,149 @@ +/* + * Copyright 2000-2014 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.util.containers.ContainerUtil; +import com.intellij.util.containers.Stack; +import gnu.trove.TIntIntHashMap; +import gnu.trove.TIntIntIterator; +import org.jetbrains.annotations.NotNull; + +import java.util.Comparator; +import java.util.List; + +public class IndentUsageStatisticsImpl implements IndentUsageStatistics { + private static final Comparator<IndentUsageInfo> DECREASING_ORDER = new Comparator<IndentUsageInfo>() { + @Override + public int compare(@NotNull IndentUsageInfo o1, @NotNull IndentUsageInfo o2) { + return o1.getTimesUsed() < o2.getTimesUsed() ? 1 : o1.getTimesUsed() == o2.getTimesUsed() ? 0 : -1; + } + }; + + private List<LineIndentInfo> myLineInfos; + + private int myPreviousLineIndent; + private int myPreviousRelativeIndent; + + private int myTotalLinesWithTabs = 0; + private int myTotalLinesWithWhiteSpaces = 0; + + private TIntIntHashMap myIndentToUsagesMap = new TIntIntHashMap(); + private List<IndentUsageInfo> myIndentUsages = ContainerUtil.newArrayList(); + private Stack<IndentData> myParentIndents = ContainerUtil.newStack(new IndentData(0, 0)); + + public IndentUsageStatisticsImpl(@NotNull List<LineIndentInfo> lineInfos) { + myLineInfos = lineInfos; + buildIndentToUsagesMap(); + myIndentUsages = toIndentUsageList(myIndentToUsagesMap); + ContainerUtil.sort(myIndentUsages, DECREASING_ORDER); + } + + @NotNull + private static List<IndentUsageInfo> toIndentUsageList(@NotNull TIntIntHashMap indentToUsages) { + List<IndentUsageInfo> indentUsageInfos = ContainerUtil.newArrayList(); + TIntIntIterator it = indentToUsages.iterator(); + while (it.hasNext()) { + it.advance(); + indentUsageInfos.add(new IndentUsageInfo(it.key(), it.value())); + } + return indentUsageInfos; + } + + public void buildIndentToUsagesMap() { + myPreviousLineIndent = 0; + myPreviousRelativeIndent = 0; + + for (LineIndentInfo lineInfo : myLineInfos) { + if (lineInfo.isLineWithTabs()) { + myTotalLinesWithTabs++; + } + else if (lineInfo.isLineWithWhiteSpaceIndent()) { + handleWhiteSpaceIndent(lineInfo.getIndentSize()); + } + } + } + + @NotNull + private IndentData findParentIndent(int indent) { + while (myParentIndents.size() != 1 && myParentIndents.peek().indent > indent) { + myParentIndents.pop(); + } + return myParentIndents.peek(); + } + + private void handleWhiteSpaceIndent(int currentIndent) { + int relativeIndent = currentIndent - myPreviousLineIndent; + if (relativeIndent < 0) { + IndentData indentData = findParentIndent(currentIndent); + myPreviousLineIndent = indentData.indent; + myPreviousRelativeIndent = indentData.relativeIndent; + relativeIndent = currentIndent - myPreviousLineIndent; + } + + if (relativeIndent == 0) { + relativeIndent = myPreviousRelativeIndent; + } + else { + myParentIndents.push(new IndentData(currentIndent, relativeIndent)); + } + + increaseIndentUsage(relativeIndent); + + myPreviousRelativeIndent = relativeIndent; + myPreviousLineIndent = currentIndent; + myTotalLinesWithWhiteSpaces++; + } + + private void increaseIndentUsage(int relativeIndent) { + int timesUsed = myIndentToUsagesMap.get(relativeIndent); + myIndentToUsagesMap.put(relativeIndent, ++timesUsed); + } + + @Override + public int getTotalLinesWithLeadingTabs() { + return myTotalLinesWithTabs; + } + + @Override + public int getTotalLinesWithLeadingSpaces() { + return myTotalLinesWithWhiteSpaces; + } + + @Override + public IndentUsageInfo getKMostUsedIndentInfo(int k) { + return myIndentUsages.get(k); + } + + @Override + public int getTimesIndentUsed(int indent) { + return myIndentToUsagesMap.get(indent); + } + + @Override + public int getTotalIndentSizesDetected() { + return myIndentToUsagesMap.size(); + } + + private static class IndentData { + public final int indent; + public final int relativeIndent; + + public IndentData(int indent, int relativeIndent) { + this.indent = indent; + this.relativeIndent = relativeIndent; + } + } +} diff --git a/platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/LineIndentInfo.java b/platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/LineIndentInfo.java new file mode 100644 index 000000000000..d85a4cbcaa0e --- /dev/null +++ b/platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/LineIndentInfo.java @@ -0,0 +1,56 @@ +/* + * Copyright 2000-2014 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 org.jetbrains.annotations.NotNull; + +public class LineIndentInfo { + public static final LineIndentInfo EMPTY_LINE = new LineIndentInfo(LineType.EMPTY_LINE, -1); + public static final LineIndentInfo LINE_WITH_COMMENT = new LineIndentInfo(LineType.LINE_WITH_COMMENT, -1); + public static final LineIndentInfo LINE_WITH_TABS = new LineIndentInfo(LineType.LINE_WITH_TABS, -1); + + private final int myIndentSize; + private final LineType myType; + + private LineIndentInfo(@NotNull LineType type, int indentSize) { + myType = type; + myIndentSize = indentSize; + } + + @NotNull + public static LineIndentInfo newWhiteSpaceIndent(int indentSize) { + return new LineIndentInfo(LineType.LINE_WITH_WHITESPACE_INDENT, indentSize); + } + + public int getIndentSize() { + return myIndentSize; + } + + public boolean isLineWithWhiteSpaceIndent() { + return myType == LineType.LINE_WITH_WHITESPACE_INDENT; + } + + public boolean isLineWithTabs() { + return myType == LineType.LINE_WITH_TABS; + } + + private enum LineType { + EMPTY_LINE, + LINE_WITH_COMMENT, + LINE_WITH_TABS, + LINE_WITH_WHITESPACE_INDENT + } +} diff --git a/platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/LineIndentInfoBuilder.java b/platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/LineIndentInfoBuilder.java new file mode 100644 index 000000000000..8efbbc495750 --- /dev/null +++ b/platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/LineIndentInfoBuilder.java @@ -0,0 +1,80 @@ +/* + * Copyright 2000-2014 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.util.containers.ContainerUtil; +import com.intellij.util.text.CharArrayUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class LineIndentInfoBuilder { + private static final int MAX_LINES_TO_PROCESS = 500; + + private final CharSequence myText; + private final int myLength; + + public LineIndentInfoBuilder(@NotNull CharSequence text) { + myText = text; + myLength = text.length(); + } + + @NotNull + public List<LineIndentInfo> build() { + List<LineIndentInfo> lineIndentInfos = ContainerUtil.newArrayList(); + + int lineStartOffset = 0; + int currentLine = 0; + + while (lineStartOffset < myText.length() && currentLine < MAX_LINES_TO_PROCESS) { + int lineEndOffset = getLineEndOffset(lineStartOffset); + int textStartOffset = CharArrayUtil.shiftForward(myText, lineStartOffset, lineEndOffset, " \t"); + + if (textStartOffset != lineEndOffset) { + lineIndentInfos.add(createInfoFromWhiteSpaceRange(lineStartOffset, textStartOffset)); + } else { + lineIndentInfos.add(LineIndentInfo.EMPTY_LINE); + } + + lineStartOffset = lineEndOffset + 1; + currentLine++; + } + + return lineIndentInfos; + } + + @NotNull + private LineIndentInfo createInfoFromWhiteSpaceRange(int lineStartOffset, int textStartOffset) { + if (myText.charAt(textStartOffset) == '*') { + return LineIndentInfo.LINE_WITH_COMMENT; + } + else if (CharArrayUtil.indexOf(myText, "\t", lineStartOffset, textStartOffset) > 0) { + return LineIndentInfo.LINE_WITH_TABS; + } + else { + int indentSize = textStartOffset - lineStartOffset; + return LineIndentInfo.newWhiteSpaceIndent(indentSize); + } + } + + private int getLineEndOffset(int lineStartOffset) { + int lineEndOffset = CharArrayUtil.indexOf(myText, "\n", lineStartOffset, myLength); + if (lineEndOffset < 0) { + lineEndOffset = myText.length(); + } + return lineEndOffset; + } +} |