/* * 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; import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFileFactory; import com.intellij.util.SequentialModalProgressTask; import com.intellij.util.SequentialTask; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import java.lang.reflect.Field; import java.util.*; import static com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider.forLanguage; public class CodeStyleSettingsCodeFragmentFilter { private static final Logger LOG = Logger.getInstance(CodeStyleSettingsCodeFragmentFilter.class); private final Project myProject; private final PsiFile myFile; private final Document myDocument; private final RangeMarker myTextRangeMarker; private final LanguageCodeStyleSettingsProvider myProvider; private CommonCodeStyleSettings myCommonSettings; public CodeStyleSettingsCodeFragmentFilter(@NotNull PsiFile file, @NotNull TextRange range) { myProvider = forLanguage(file.getLanguage()); myProject = file.getProject(); myFile = PsiFileFactory.getInstance(myProject).createFileFromText("copy" + file.getName(), file.getLanguage(), file.getText(), true, false); myDocument = PsiDocumentManager.getInstance(myProject).getDocument(myFile); LOG.assertTrue(myDocument != null); myTextRangeMarker = myDocument.createRangeMarker(range.getStartOffset(), range.getEndOffset()); } @NotNull public CodeStyleSettingsToShow getFieldNamesAffectingCodeFragment(LanguageCodeStyleSettingsProvider.SettingsType... types) { CodeStyleSettingsManager codeStyleSettingsManager = CodeStyleSettingsManager.getInstance(myProject); CodeStyleSettings clonedSettings = codeStyleSettingsManager.getCurrentSettings().clone(); myCommonSettings = clonedSettings.getCommonSettings(myProvider.getLanguage()); try { codeStyleSettingsManager.setTemporarySettings(clonedSettings); String title = CodeInsightBundle.message("configure.code.style.on.fragment.dialog.title"); SequentialModalProgressTask progressTask = new SequentialModalProgressTask(myProject, StringUtil.capitalizeWords(title, true)); progressTask.setCancelText(CodeInsightBundle.message("configure.code.style.on.fragment.dialog.cancel")); CompositeSequentialTask compositeTask = new CompositeSequentialTask(progressTask); compositeTask.setProgressText(CodeInsightBundle.message("configure.code.style.on.fragment.dialog.progress.text")); compositeTask.setProgressText2(CodeInsightBundle.message("configure.code.style.on.fragment.dialog.progress.text.under")); final Map<LanguageCodeStyleSettingsProvider.SettingsType, FilterFieldsTask> typeToTask = ContainerUtil.newHashMap(); for (LanguageCodeStyleSettingsProvider.SettingsType type : types) { Set<String> fields = myProvider.getSupportedFields(type); FilterFieldsTask task = new FilterFieldsTask(fields); compositeTask.addTask(task); typeToTask.put(type, task); } Set<String> otherFields = myProvider.getSupportedFields(); final FilterFieldsTask otherFieldsTask = new FilterFieldsTask(otherFields); if (!otherFields.isEmpty()) { compositeTask.addTask(otherFieldsTask); } progressTask.setTask(compositeTask); progressTask.setMinIterationTime(10); ProgressManager.getInstance().run(progressTask); return new CodeStyleSettingsToShow() { @Override public List<String> getSettings(LanguageCodeStyleSettingsProvider.SettingsType type) { FilterFieldsTask task = typeToTask.get(type); return task.getAffectedFields(); } @Override public List<String> getOtherSetting() { return ContainerUtil.newArrayList(otherFieldsTask.getAffectedFields()); } }; } finally { codeStyleSettingsManager.dropTemporarySettings(); } } private boolean formattingChangedFragment() { final int rangeStart = myTextRangeMarker.getStartOffset(); final int rangeEnd = myTextRangeMarker.getEndOffset(); CharSequence textBefore = myDocument.getCharsSequence(); ApplicationManager.getApplication().runWriteAction(() -> CodeStyleManager.getInstance(myProject).reformatText(myFile, rangeStart, rangeEnd)); if (rangeStart != myTextRangeMarker.getStartOffset() || rangeEnd != myTextRangeMarker.getEndOffset()) { return true; } else { CharSequence fragmentBefore = textBefore.subSequence(rangeStart, rangeEnd); CharSequence fragmentAfter = myDocument.getCharsSequence().subSequence(rangeStart, rangeEnd); return !StringUtil.equals(fragmentBefore, fragmentAfter); } } private class FilterFieldsTask implements SequentialTaskWithFixedIterationsNumber { private final Iterator<String> myIterator; private final int myTotalFieldsNumber; private final Collection<String> myAllFields; private List<String> myAffectingFields = ContainerUtil.newArrayList(); public FilterFieldsTask(@NotNull Collection<String> fields) { myAllFields = fields; myIterator = fields.iterator(); myTotalFieldsNumber = fields.size(); } public List<String> getAffectedFields() { return myAffectingFields; } @Override public int getTotalIterationsNumber() { return myTotalFieldsNumber; } @Override public void stop() { if (!isDone()) myAffectingFields = ContainerUtil.newArrayList(myAllFields); } @Override public boolean isDone() { return !myIterator.hasNext(); } @Override public boolean iteration() { if (!myIterator.hasNext()) return true; String field = myIterator.next(); try { Field classField = CommonCodeStyleSettings.class.getField(field); if (classField.getType() == Integer.TYPE) { int oldValue = classField.getInt(myCommonSettings); int newValue = getNewIntValue(classField, oldValue); if (newValue == oldValue) { return true; } classField.set(myCommonSettings, newValue); } else if (classField.getType() == Boolean.TYPE) { boolean value = classField.getBoolean(myCommonSettings); classField.set(myCommonSettings, !value); } else { return true; } if (formattingChangedFragment()) { myAffectingFields.add(field); } } catch (Exception ignored) { } return true; } private int getNewIntValue(Field classField, int oldValue) throws IllegalAccessException { int newValue = oldValue; String fieldName = classField.getName(); if (fieldName.contains("WRAP")) { newValue = oldValue == CommonCodeStyleSettings.WRAP_ALWAYS ? CommonCodeStyleSettings.DO_NOT_WRAP : CommonCodeStyleSettings.WRAP_ALWAYS; } else if (fieldName.contains("BRACE")) { newValue = oldValue == CommonCodeStyleSettings.FORCE_BRACES_ALWAYS ? CommonCodeStyleSettings.DO_NOT_FORCE : CommonCodeStyleSettings.WRAP_ALWAYS; } return newValue; } @Override public void prepare() { } } public interface CodeStyleSettingsToShow { List<String> getSettings(LanguageCodeStyleSettingsProvider.SettingsType type); List<String> getOtherSetting(); } } interface SequentialTaskWithFixedIterationsNumber extends SequentialTask { int getTotalIterationsNumber(); } class CompositeSequentialTask implements SequentialTask { private List<SequentialTaskWithFixedIterationsNumber> myUnfinishedTasks = ContainerUtil.newArrayList(); private SequentialTask myCurrentTask = null; private int myIterationsFinished; private int myTotalIterations = 0; private final SequentialModalProgressTask myProgressTask; private String myProgressText; private String myProgressText2; public CompositeSequentialTask(@NotNull SequentialModalProgressTask progressTask) { myProgressTask = progressTask; } public void addTask(@NotNull SequentialTaskWithFixedIterationsNumber task) { myUnfinishedTasks.add(task); myTotalIterations += task.getTotalIterationsNumber(); } public void setProgressText(@NotNull String progressText) { myProgressText = progressText; } @Override public boolean isDone() { return myCurrentTask == null && myUnfinishedTasks.size() == 0; } @Override public boolean iteration() { popUntilCurrentTaskUnfinishedOrNull(); if (myCurrentTask != null) { ProgressIndicator indicator = myProgressTask.getIndicator(); if (indicator != null) { if (myProgressText != null) indicator.setText(myProgressText); if (myProgressText2 != null) indicator.setText2(myProgressText2); indicator.setFraction((double)myIterationsFinished++ / myTotalIterations); } myCurrentTask.iteration(); } return true; } private void popUntilCurrentTaskUnfinishedOrNull() { if (myCurrentTask != null) { if (!myCurrentTask.isDone()) { return; } myCurrentTask = null; popUntilCurrentTaskUnfinishedOrNull(); } else { if (myUnfinishedTasks.size() > 0) { myCurrentTask = myUnfinishedTasks.get(0); myUnfinishedTasks.remove(0); popUntilCurrentTaskUnfinishedOrNull(); } } } @Override public void prepare() { } @Override public void stop() { if (myCurrentTask != null) myCurrentTask.stop(); for (SequentialTaskWithFixedIterationsNumber task : myUnfinishedTasks) { task.stop(); } } public void setProgressText2(String progressText2) { myProgressText2 = progressText2; } }