/* * 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.formatting.Block; import com.intellij.formatting.FormattingModel; import com.intellij.formatting.FormattingModelBuilder; import com.intellij.lang.LanguageFormatting; 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.CodeStyleSettings; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import org.jetbrains.annotations.NotNull; import java.util.List; 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"); private static final double RATE_THRESHOLD = 0.8; private static final int MAX_INDENT_TO_DETECT = 8; private final PsiFile myFile; private final Project myProject; private final Document myDocument; public IndentOptionsDetectorImpl(@NotNull PsiFile file) { myFile = file; myProject = file.getProject(); myDocument = PsiDocumentManager.getInstance(myProject).getDocument(myFile); } @Override @NotNull public IndentOptions getIndentOptions() { IndentOptions indentOptions = (IndentOptions)CodeStyleSettingsManager.getSettings(myProject).getIndentOptions(myFile.getFileType()).clone(); List<LineIndentInfo> linesInfo = calcLineIndentInfo(); if (linesInfo != null) { IndentUsageStatistics stats = new IndentUsageStatisticsImpl(linesInfo); adjustIndentOptions(indentOptions, stats); } return indentOptions; } private List<LineIndentInfo> calcLineIndentInfo() { if (myDocument == null) return null; CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(myProject); FormattingModelBuilder modelBuilder = LanguageFormatting.INSTANCE.forContext(myFile); if (modelBuilder == null) return null; FormattingModel model = modelBuilder.createModel(myFile, settings); Block rootBlock = model.getRootBlock(); return new FormatterBasedLineIndentInfoBuilder(myDocument, rootBlock).build(); } private void adjustIndentOptions(@NotNull IndentOptions indentOptions, @NotNull IndentUsageStatistics stats) { if (isTabsUsed(stats)) { adjustForTabUsage(indentOptions); } else if (isSpacesUsed(stats)) { indentOptions.USE_TAB_CHARACTER = false; int newIndentSize = getPositiveIndentSize(stats); if (newIndentSize > 0) { if (indentOptions.INDENT_SIZE != newIndentSize) { indentOptions.INDENT_SIZE = newIndentSize; LOG.debug("Detected indent size: " + newIndentSize + " for file " + myFile); } } } } private static boolean isSpacesUsed(IndentUsageStatistics stats) { int spaces = stats.getTotalLinesWithLeadingSpaces(); int total = stats.getTotalLinesWithLeadingSpaces() + stats.getTotalLinesWithLeadingTabs(); return (double)spaces / total > RATE_THRESHOLD; } private static boolean isTabsUsed(IndentUsageStatistics stats) { return stats.getTotalLinesWithLeadingTabs() > stats.getTotalLinesWithLeadingSpaces(); } private void adjustForTabUsage(@NotNull IndentOptions indentOptions) { if (indentOptions.USE_TAB_CHARACTER) return; int continuationRatio = indentOptions.INDENT_SIZE == 0 ? 1 : indentOptions.CONTINUATION_INDENT_SIZE / indentOptions.INDENT_SIZE; indentOptions.USE_TAB_CHARACTER = true; indentOptions.INDENT_SIZE = indentOptions.TAB_SIZE; indentOptions.CONTINUATION_INDENT_SIZE = indentOptions.TAB_SIZE * continuationRatio; LOG.debug("Using tabs for: " + 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 < 2) return -1; maxUsedIndentInfo = stats.getKMostUsedIndentInfo(1); maxUsedIndentSize = maxUsedIndentInfo.getIndentSize(); } if (maxUsedIndentSize <= MAX_INDENT_TO_DETECT) { double usageRate = (double)maxUsedIndentInfo.getTimesUsed() / stats.getTotalLinesWithLeadingSpaces(); if (usageRate > RATE_THRESHOLD) { return maxUsedIndentSize; } } return -1; } }