From 1e567438a258f9e6845cc80fae3fbc299d39d001 Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Fri, 2 Feb 2018 17:58:56 +0300 Subject: [PATCH] Store selected set of defaults in code style settings (KT-22252) #KT-22252 In Progress --- idea/formatter/build.gradle.kts | 2 +- .../formatter/KotlinCodeStyleSettings.java | 64 ++++++++++++- .../idea/formatter/KotlinCommonBlock.kt | 4 +- .../KotlinCommonCodeStyleSettings.java | 50 +++++++++- .../formatter/KotlinStyleGuideCodeStyle.kt | 15 ++- .../kotlin/idea/util/ReflectionUtil.java | 56 +++++++++++ .../KotlinCodeStyleSettingsProvider.java | 1 + .../idea/formatter/KotlinSaveStylePanel.kt | 93 +++++++++++++++++++ 8 files changed, 272 insertions(+), 13 deletions(-) rename idea/{ => formatter}/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonCodeStyleSettings.java (83%) create mode 100644 idea/formatter/src/org/jetbrains/kotlin/idea/util/ReflectionUtil.java create mode 100644 idea/src/org/jetbrains/kotlin/idea/formatter/KotlinSaveStylePanel.kt diff --git a/idea/formatter/build.gradle.kts b/idea/formatter/build.gradle.kts index 33c0281f21b..108ad69d447 100644 --- a/idea/formatter/build.gradle.kts +++ b/idea/formatter/build.gradle.kts @@ -4,7 +4,7 @@ apply { plugin("kotlin") } dependencies { compile(project(":compiler:util")) compile(project(":compiler:frontend")) - compileOnly(intellijDep()) { includeJars("idea", "openapi", "util") } + compileOnly(intellijDep()) { includeJars("idea", "openapi", "util", "jdom") } } sourceSets { diff --git a/idea/formatter/src/org/jetbrains/kotlin/idea/core/formatter/KotlinCodeStyleSettings.java b/idea/formatter/src/org/jetbrains/kotlin/idea/core/formatter/KotlinCodeStyleSettings.java index 224a54dd6bc..90b095433bf 100644 --- a/idea/formatter/src/org/jetbrains/kotlin/idea/core/formatter/KotlinCodeStyleSettings.java +++ b/idea/formatter/src/org/jetbrains/kotlin/idea/core/formatter/KotlinCodeStyleSettings.java @@ -18,9 +18,15 @@ package org.jetbrains.kotlin.idea.core.formatter; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.InvalidDataException; +import com.intellij.openapi.util.WriteExternalException; import com.intellij.psi.codeStyle.*; -import com.intellij.util.ReflectionUtil; +import org.jdom.Element; import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.idea.formatter.KotlinStyleGuideCodeStyle; +import org.jetbrains.kotlin.idea.util.ReflectionUtil; + +import static com.intellij.util.ReflectionUtil.copyFields; public class KotlinCodeStyleSettings extends CustomCodeStyleSettings { public static final KotlinCodeStyleSettings DEFAULT = new KotlinCodeStyleSettings(new CodeStyleSettings()); @@ -52,9 +58,20 @@ public class KotlinCodeStyleSettings extends CustomCodeStyleSettings { public int WRAP_ELVIS_EXPRESSIONS = 1; public boolean IF_RPAREN_ON_NEW_LINE = false; + @ReflectionUtil.SkipInEquals + public String CODE_STYLE_DEFAULTS = null; + + private final boolean isTempForDeserialize; + public KotlinCodeStyleSettings(CodeStyleSettings container) { + this(container, false); + } + + private KotlinCodeStyleSettings(CodeStyleSettings container, boolean isTempForDeserialize) { super("JetCodeStyleSettings", container); + this.isTempForDeserialize = isTempForDeserialize; + // defaults in IDE but not in tests if (!ApplicationManager.getApplication().isUnitTestMode()) { PACKAGES_TO_USE_STAR_IMPORTS.addEntry(new PackageEntry(false, "java.util", false)); @@ -74,8 +91,49 @@ public class KotlinCodeStyleSettings extends CustomCodeStyleSettings { } private void copyFrom(@NotNull KotlinCodeStyleSettings from) { - ReflectionUtil.copyFields(getClass().getFields(), from, this); - + copyFields(getClass().getFields(), from, this); PACKAGES_TO_USE_STAR_IMPORTS.copyFrom(from.PACKAGES_TO_USE_STAR_IMPORTS); } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof KotlinCodeStyleSettings)) return false; + if (!ReflectionUtil.comparePublicNonFinalFieldsWithSkip(this, obj)) return false; + return true; + } + + @Override + public void writeExternal(Element parentElement, @NotNull CustomCodeStyleSettings parentSettings) throws WriteExternalException { + if (KotlinStyleGuideCodeStyle.CODE_STYLE_ID.equals(CODE_STYLE_DEFAULTS)) { + KotlinCodeStyleSettings defaultKotlinCodeStyle = (KotlinCodeStyleSettings) parentSettings.clone(); + KotlinStyleGuideCodeStyle.Companion.applyToKotlinCustomSettings(defaultKotlinCodeStyle, false); + parentSettings = defaultKotlinCodeStyle; + } + + super.writeExternal(parentElement, parentSettings); + } + + @Override + public void readExternal(Element parentElement) throws InvalidDataException { + if (isTempForDeserialize) { + super.readExternal(parentElement); + return; + } + + KotlinCodeStyleSettings tempSettings = readExternalToTemp(parentElement); + if (KotlinStyleGuideCodeStyle.CODE_STYLE_ID.equals(tempSettings.CODE_STYLE_DEFAULTS)) { + KotlinStyleGuideCodeStyle.Companion.applyToKotlinCustomSettings(this, true); + } + + // Actual read + super.readExternal(parentElement); + } + + private static KotlinCodeStyleSettings readExternalToTemp(Element parentElement) { + // Read to temp + KotlinCodeStyleSettings tempSettings = new KotlinCodeStyleSettings(null, true); + tempSettings.readExternal(parentElement); + + return tempSettings; + } } diff --git a/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonBlock.kt b/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonBlock.kt index ba37935d702..19c90ef3cf4 100644 --- a/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonBlock.kt +++ b/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonBlock.kt @@ -44,8 +44,8 @@ private val CODE_BLOCKS = TokenSet.create(KtNodeTypes.BLOCK, KtNodeTypes.CLASS_B private val ALIGN_FOR_BINARY_OPERATIONS = TokenSet.create(MUL, DIV, PERC, PLUS, MINUS, ELVIS, LT, GT, LTEQ, GTEQ, ANDAND, OROR) private val ANNOTATIONS = TokenSet.create(KtNodeTypes.ANNOTATION_ENTRY, KtNodeTypes.ANNOTATION) -val CodeStyleSettings.kotlinCommonSettings: CommonCodeStyleSettings - get() = getCommonSettings(KotlinLanguage.INSTANCE) +val CodeStyleSettings.kotlinCommonSettings: KotlinCommonCodeStyleSettings + get() = getCommonSettings(KotlinLanguage.INSTANCE) as KotlinCommonCodeStyleSettings val CodeStyleSettings.kotlinCustomSettings: KotlinCodeStyleSettings get() = getCustomSettings(KotlinCodeStyleSettings::class.java)!! diff --git a/idea/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonCodeStyleSettings.java b/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonCodeStyleSettings.java similarity index 83% rename from idea/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonCodeStyleSettings.java rename to idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonCodeStyleSettings.java index 893af606396..0cbefe89213 100644 --- a/idea/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonCodeStyleSettings.java +++ b/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonCodeStyleSettings.java @@ -15,13 +15,13 @@ import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider; import com.intellij.psi.codeStyle.arrangement.ArrangementSettings; import com.intellij.psi.codeStyle.arrangement.ArrangementUtil; -import com.intellij.util.ReflectionUtil; import com.intellij.util.xmlb.XmlSerializer; import kotlin.collections.ArraysKt; import org.jdom.Element; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.idea.KotlinLanguage; +import org.jetbrains.kotlin.idea.util.ReflectionUtil; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -31,22 +31,62 @@ import java.util.Set; @SuppressWarnings("UnnecessaryFinalOnLocalVariableOrParameter") public class KotlinCommonCodeStyleSettings extends CommonCodeStyleSettings { + @ReflectionUtil.SkipInEquals + public String CODE_STYLE_DEFAULTS = null; + + private final boolean isTempForDeserialize; + public KotlinCommonCodeStyleSettings() { - super(KotlinLanguage.INSTANCE); + this(false); + } + + private KotlinCommonCodeStyleSettings(boolean isTempForDeserialize) { + super(KotlinLanguage.INSTANCE); + this.isTempForDeserialize = isTempForDeserialize; + } + + private static KotlinCommonCodeStyleSettings createForTempDeserialize() { + return new KotlinCommonCodeStyleSettings(true); } - // @Override public void readExternal(Element element) throws InvalidDataException { - super.readExternal(element); + if (isTempForDeserialize) { + super.readExternal(element); + return; + } + + KotlinCommonCodeStyleSettings tempDeserialize = createForTempDeserialize(); + tempDeserialize.readExternal(element); + + if (KotlinStyleGuideCodeStyle.CODE_STYLE_ID.equals(tempDeserialize.CODE_STYLE_DEFAULTS)) { + KotlinStyleGuideCodeStyle.Companion.applyToCommonSettings(this, true); + } + + readExternalBase(element); } @Override public void writeExternal(Element element) throws WriteExternalException { CommonCodeStyleSettings defaultSettings = getDefaultSettings(); + + if (defaultSettings != null && KotlinStyleGuideCodeStyle.CODE_STYLE_ID.equals(CODE_STYLE_DEFAULTS)) { + KotlinStyleGuideCodeStyle.Companion.applyToCommonSettings(defaultSettings, false); + } + + writeExternalBase(element, defaultSettings); + } + + // + private void readExternalBase(Element element) throws InvalidDataException { + super.readExternal(element); + } + + private void writeExternalBase(Element element, CommonCodeStyleSettings defaultSettings) throws WriteExternalException { Set supportedFields = getSupportedFields(); if (supportedFields != null) { supportedFields.add("FORCE_REARRANGE_MODE"); + supportedFields.add("CODE_STYLE_DEFAULTS"); } //noinspection deprecation DefaultJDOMExternalizer.writeExternal(this, element, new SupportedFieldsDiffFilter(this, supportedFields, defaultSettings)); @@ -124,7 +164,7 @@ public class KotlinCommonCodeStyleSettings extends CommonCodeStyleSettings { return false; } - if (!ReflectionUtil.comparePublicNonFinalFields(this, obj)) { + if (!ReflectionUtil.comparePublicNonFinalFieldsWithSkip(this, obj)) { return false; } diff --git a/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinStyleGuideCodeStyle.kt b/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinStyleGuideCodeStyle.kt index de8ba640df9..694c5601e2c 100644 --- a/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinStyleGuideCodeStyle.kt +++ b/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinStyleGuideCodeStyle.kt @@ -28,13 +28,20 @@ class KotlinStyleGuideCodeStyle : PredefinedCodeStyle("Kotlin style guide", Kotl } companion object { + const val CODE_STYLE_ID = "KOTLIN_OFFICIAL" + const val CODE_STYLE_TITLE = "Kotlin Coding Conventions" + fun apply(settings: CodeStyleSettings) { applyToKotlinCustomSettings(settings.kotlinCustomSettings) applyToCommonSettings(settings.kotlinCommonSettings) } - fun applyToKotlinCustomSettings(kotlinCustomSettings: KotlinCodeStyleSettings) { + fun applyToKotlinCustomSettings(kotlinCustomSettings: KotlinCodeStyleSettings, modifyCodeStyle: Boolean = true) { kotlinCustomSettings.apply { + if (modifyCodeStyle) { + CODE_STYLE_DEFAULTS = CODE_STYLE_ID + } + CONTINUATION_INDENT_IN_PARAMETER_LISTS = false CONTINUATION_INDENT_IN_ARGUMENT_LISTS = false CONTINUATION_INDENT_FOR_EXPRESSION_BODIES = false @@ -46,7 +53,7 @@ class KotlinStyleGuideCodeStyle : PredefinedCodeStyle("Kotlin style guide", Kotl } } - fun applyToCommonSettings(commonSettings: CommonCodeStyleSettings) { + fun applyToCommonSettings(commonSettings: CommonCodeStyleSettings, modifyCodeStyle: Boolean = true) { commonSettings.apply { WHILE_ON_NEW_LINE = false ELSE_ON_NEW_LINE = false @@ -67,6 +74,10 @@ class KotlinStyleGuideCodeStyle : PredefinedCodeStyle("Kotlin style guide", Kotl ALIGN_MULTILINE_BINARY_OPERATION = false } + + if (modifyCodeStyle && commonSettings is KotlinCommonCodeStyleSettings) { + commonSettings.CODE_STYLE_DEFAULTS = CODE_STYLE_ID + } } } } diff --git a/idea/formatter/src/org/jetbrains/kotlin/idea/util/ReflectionUtil.java b/idea/formatter/src/org/jetbrains/kotlin/idea/util/ReflectionUtil.java new file mode 100644 index 00000000000..509ef3d369e --- /dev/null +++ b/idea/formatter/src/org/jetbrains/kotlin/idea/util/ReflectionUtil.java @@ -0,0 +1,56 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.util; + +import com.intellij.openapi.util.Comparing; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.Predicate; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Set; + +public class ReflectionUtil { + @Retention(RetentionPolicy.RUNTIME) + public @interface SkipInEquals {} + + public static boolean comparePublicNonFinalFieldsWithSkip(@NotNull Object first, @NotNull Object second) { + return comparePublicNonFinalFields(first, second, field -> field.getAnnotation(SkipInEquals.class) == null); + } + + private static boolean comparePublicNonFinalFields(@NotNull Object first, @NotNull Object second, @Nullable Predicate acceptPredicate) { + Set firstFields = ContainerUtil.newHashSet(first.getClass().getFields()); + + for (Field field : second.getClass().getFields()) { + if (firstFields.contains(field)) { + if (isPublic(field) && !isFinal(field) && (acceptPredicate == null || acceptPredicate.apply(field))) { + try { + if (!Comparing.equal(field.get(first), field.get(second))) { + return false; + } + } + catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + } + } + + return true; + } + + private static boolean isPublic(Field field) { + return (field.getModifiers() & Modifier.PUBLIC) != 0; + } + + private static boolean isFinal(Field field) { + return (field.getModifiers() & Modifier.FINAL) != 0; + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/formatter/KotlinCodeStyleSettingsProvider.java b/idea/src/org/jetbrains/kotlin/idea/formatter/KotlinCodeStyleSettingsProvider.java index 8b9c4ca7e26..a4f26ce1a61 100644 --- a/idea/src/org/jetbrains/kotlin/idea/formatter/KotlinCodeStyleSettingsProvider.java +++ b/idea/src/org/jetbrains/kotlin/idea/formatter/KotlinCodeStyleSettingsProvider.java @@ -60,6 +60,7 @@ public class KotlinCodeStyleSettingsProvider extends CodeStyleSettingsProvider { addWrappingAndBracesTab(settings); addBlankLinesTab(settings); addTab(new ImportSettingsPanelWrapper(settings)); + addTab(new KotlinSaveStylePanel(settings)); } }; } diff --git a/idea/src/org/jetbrains/kotlin/idea/formatter/KotlinSaveStylePanel.kt b/idea/src/org/jetbrains/kotlin/idea/formatter/KotlinSaveStylePanel.kt new file mode 100644 index 00000000000..33e5a91f475 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/formatter/KotlinSaveStylePanel.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.formatter + +import com.intellij.application.options.CodeStyleAbstractPanel +import com.intellij.openapi.editor.colors.EditorColorsScheme +import com.intellij.openapi.ui.ComboBox +import com.intellij.psi.codeStyle.CodeStyleSettings +import com.intellij.ui.ListCellRendererWrapper +import com.intellij.ui.components.JBScrollPane +import com.intellij.ui.components.panels.HorizontalLayout +import com.intellij.ui.components.panels.VerticalLayout +import com.intellij.util.ui.JBUI +import com.intellij.util.ui.UIUtil +import java.awt.BorderLayout +import javax.swing.BorderFactory +import javax.swing.JLabel +import javax.swing.JList +import javax.swing.JPanel + +class KotlinSaveStylePanel(settings: CodeStyleSettings) : CodeStyleAbstractPanel(settings) { + override fun getRightMargin() = throw UnsupportedOperationException() + override fun createHighlighter(scheme: EditorColorsScheme) = throw UnsupportedOperationException() + override fun getFileType() = throw UnsupportedOperationException() + override fun getPreviewText(): String? = null + + override fun getTabTitle(): String = "Load/Save" + + private data class SaveItem(val label: String, val id: String?) + + private val saveDefaultsComboBox = ComboBox() + private val saveDefaultsItems = listOf( + SaveItem("", null), + SaveItem("Current set of defaults", "KOTLIN_OLD_DEFAULTS"), + SaveItem(KotlinStyleGuideCodeStyle.CODE_STYLE_TITLE, KotlinStyleGuideCodeStyle.CODE_STYLE_ID) + ) + + var selectedId: String? + get() { + val (_, id) = saveDefaultsComboBox.selectedItem as SaveItem + return id + } + set(value) { + saveDefaultsComboBox.selectedItem = saveDefaultsItems.firstOrNull { (_, id) -> id == value } ?: saveDefaultsItems.first() + } + + private val jPanel = JPanel(BorderLayout()).apply { + add( + JBScrollPane( + JPanel(VerticalLayout(JBUI.scale(5))).apply { + border = BorderFactory.createEmptyBorder(UIUtil.DEFAULT_VGAP, 10, UIUtil.DEFAULT_VGAP, 10) + add(JPanel(HorizontalLayout(JBUI.scale(5))).apply { + saveDefaultsItems.forEach { + saveDefaultsComboBox.addItem(it) + } + + saveDefaultsComboBox.setRenderer(object : ListCellRendererWrapper() { + override fun customize(list: JList<*>?, value: SaveItem, index: Int, selected: Boolean, hasFocus: Boolean) { + setText(value.label) + } + }) + + add(JLabel("Use defaults from:")) + add(saveDefaultsComboBox) + }) + } + ) + ) + } + + override fun apply(settings: CodeStyleSettings) { + settings.kotlinCustomSettings.CODE_STYLE_DEFAULTS = selectedId + settings.kotlinCommonSettings.CODE_STYLE_DEFAULTS = selectedId + } + + override fun isModified(settings: CodeStyleSettings): Boolean { + return selectedId != settings.kotlinCustomSettings.CODE_STYLE_DEFAULTS || + selectedId != settings.kotlinCommonSettings.CODE_STYLE_DEFAULTS + } + + override fun getPanel() = jPanel + + override fun resetImpl(settings: CodeStyleSettings) { + selectedId = settings.kotlinCustomSettings.CODE_STYLE_DEFAULTS ?: settings.kotlinCommonSettings.CODE_STYLE_DEFAULTS + } + + override fun onSomethingChanged() { + // There's no way settings are going to be changed from other tabs without calling resetImpl + } +}