summaryrefslogtreecommitdiff
path: root/platform/lang-api/src/com/intellij/psi/codeStyle/autodetect
diff options
context:
space:
mode:
Diffstat (limited to 'platform/lang-api/src/com/intellij/psi/codeStyle/autodetect')
-rw-r--r--platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/IndentOptionsDetector.java106
-rw-r--r--platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/IndentUsageInfo.java39
-rw-r--r--platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/IndentUsageStatistics.java30
-rw-r--r--platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/IndentUsageStatisticsImpl.java149
-rw-r--r--platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/LineIndentInfo.java56
-rw-r--r--platform/lang-api/src/com/intellij/psi/codeStyle/autodetect/LineIndentInfoBuilder.java80
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;
+ }
+}