KT-10974 - add import layout configuration table

This commit is contained in:
gcx11
2020-04-20 00:39:38 +02:00
committed by Roman Golyshev
parent 9574ac83e4
commit 8849a66c00
10 changed files with 581 additions and 56 deletions
@@ -13,8 +13,6 @@ import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CustomCodeStyleSettings;
import com.intellij.psi.codeStyle.PackageEntry;
import com.intellij.psi.codeStyle.PackageEntryTable;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -26,7 +24,8 @@ import org.jetbrains.kotlin.idea.util.ReflectionUtil;
import static com.intellij.util.ReflectionUtil.copyFields;
public class KotlinCodeStyleSettings extends CustomCodeStyleSettings {
public final PackageEntryTable PACKAGES_TO_USE_STAR_IMPORTS = new PackageEntryTable();
public final KotlinPackageEntryTable PACKAGES_TO_USE_STAR_IMPORTS = new KotlinPackageEntryTable();
public final KotlinPackageEntryTable PACKAGES_IMPORT_LAYOUT = new KotlinPackageEntryTable();
public boolean SPACE_AROUND_RANGE = false;
public boolean SPACE_BEFORE_TYPE_COLON = false;
public boolean SPACE_AFTER_TYPE_COLON = true;
@@ -78,8 +77,14 @@ public class KotlinCodeStyleSettings extends CustomCodeStyleSettings {
// defaults in IDE but not in tests
if (!ApplicationManager.getApplication().isUnitTestMode()) {
PACKAGES_TO_USE_STAR_IMPORTS.addEntry(new PackageEntry(false, "java.util", false));
PACKAGES_TO_USE_STAR_IMPORTS.addEntry(new PackageEntry(false, "kotlinx.android.synthetic", true));
PACKAGES_TO_USE_STAR_IMPORTS.addEntry(new KotlinPackageEntry("java.util", false));
PACKAGES_TO_USE_STAR_IMPORTS.addEntry(new KotlinPackageEntry("kotlinx.android.synthetic", true));
PACKAGES_IMPORT_LAYOUT.addEntry(KotlinPackageEntry.ALL_OTHER_IMPORTS_ENTRY);
PACKAGES_IMPORT_LAYOUT.addEntry(new KotlinPackageEntry("java", true));
PACKAGES_IMPORT_LAYOUT.addEntry(new KotlinPackageEntry("javax", true));
PACKAGES_IMPORT_LAYOUT.addEntry(new KotlinPackageEntry("kotlin", true));
PACKAGES_IMPORT_LAYOUT.addEntry(KotlinPackageEntry.ALL_OTHER_ALIAS_IMPORTS_ENTRY);
}
}
@@ -104,6 +109,7 @@ public class KotlinCodeStyleSettings extends CustomCodeStyleSettings {
private void copyFrom(@NotNull KotlinCodeStyleSettings from) {
copyFields(getClass().getFields(), from, this);
PACKAGES_TO_USE_STAR_IMPORTS.copyFrom(from.PACKAGES_TO_USE_STAR_IMPORTS);
PACKAGES_IMPORT_LAYOUT.copyFrom(from.PACKAGES_IMPORT_LAYOUT);
}
@Override
@@ -0,0 +1,55 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.core.formatter
import org.jetbrains.kotlin.resolve.ImportPath
data class KotlinPackageEntry(
val packageName: String,
val withSubpackages: Boolean
) {
companion object {
@JvmField
val ALL_OTHER_IMPORTS_ENTRY = KotlinPackageEntry("<all other imports>", withSubpackages = true)
@JvmField
val ALL_OTHER_ALIAS_IMPORTS_ENTRY = KotlinPackageEntry("<all other alias imports>", withSubpackages = true)
}
fun matchesPackageName(otherPackageName: String): Boolean {
if (this == ALL_OTHER_IMPORTS_ENTRY || this == ALL_OTHER_ALIAS_IMPORTS_ENTRY) return true
if (otherPackageName.startsWith(packageName)) {
if (otherPackageName.length == packageName.length) return true
if (withSubpackages) {
if (otherPackageName[packageName.length] == '.') return true
}
}
return false
}
fun isBetterMatchForPackageThan(entry: KotlinPackageEntry?, path: ImportPath): Boolean {
if (!matchesPackageName(path.pathStr)) return false
if (entry == null) return true
if (this == ALL_OTHER_ALIAS_IMPORTS_ENTRY && path.hasAlias()) return true
if (entry == ALL_OTHER_ALIAS_IMPORTS_ENTRY && path.hasAlias()) return false
if (entry == ALL_OTHER_IMPORTS_ENTRY && this != ALL_OTHER_ALIAS_IMPORTS_ENTRY) return true
if (this == ALL_OTHER_IMPORTS_ENTRY && entry != ALL_OTHER_ALIAS_IMPORTS_ENTRY) return false
if (entry.withSubpackages != withSubpackages) return !withSubpackages
return entry.packageName.count { it == '.' } < packageName.count { it == '.' }
}
val isSpecial: Boolean get() {
return (this == ALL_OTHER_IMPORTS_ENTRY || this == ALL_OTHER_ALIAS_IMPORTS_ENTRY)
}
override fun toString(): String {
return packageName
}
}
@@ -0,0 +1,113 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.core.formatter
import com.intellij.openapi.util.InvalidDataException
import com.intellij.openapi.util.JDOMExternalizable
import org.jdom.Element
class KotlinPackageEntryTable: JDOMExternalizable, Cloneable {
private val entries = mutableListOf<KotlinPackageEntry>()
override fun equals(other: Any?): Boolean {
if (other !is KotlinPackageEntryTable) return false
if (other.entries.size != entries.size) return false
return entries.zip(other.entries).any { (entry, otherEntry) -> entry != otherEntry }
}
public override fun clone(): KotlinPackageEntryTable {
val clone = KotlinPackageEntryTable()
clone.copyFrom(this)
return clone
}
override fun hashCode(): Int {
return entries.firstOrNull()?.hashCode() ?: 0
}
fun copyFrom(packageTable: KotlinPackageEntryTable) {
entries.clear()
entries.addAll(packageTable.entries)
}
fun getEntries(): Array<KotlinPackageEntry> {
return entries.toTypedArray()
}
fun insertEntryAt(entry: KotlinPackageEntry, index: Int) {
entries.add(index, entry)
}
fun removeEntryAt(index: Int) {
entries.removeAt(index)
}
fun getEntryAt(index: Int): KotlinPackageEntry {
return entries[index]
}
fun getEntryCount(): Int {
return entries.size
}
fun setEntryAt(entry: KotlinPackageEntry, index: Int) {
entries[index] = entry
}
operator fun contains(packageName: String): Boolean {
for (entry in entries) {
if (packageName.startsWith(entry.packageName)) {
if (packageName.length == entry.packageName.length) return true
if (entry.withSubpackages) {
if (packageName[entry.packageName.length] == '.') return true
}
}
}
return false
}
fun removeEmptyPackages() {
entries.removeAll { it.packageName.isBlank() }
}
fun addEntry(entry: KotlinPackageEntry) {
entries.add(entry)
}
override fun readExternal(element: Element) {
entries.clear()
element.children.forEach {
if (it.name == "package") {
val packageName = it.getAttributeValue("name") ?: throw InvalidDataException()
val alias = it.getAttributeValue("alias")?.toBoolean() ?: false
val withSubpackages = it.getAttributeValue("withSubpackages")?.toBoolean() ?: false
val entry = when {
packageName.isEmpty() && !alias -> KotlinPackageEntry.ALL_OTHER_IMPORTS_ENTRY
packageName.isEmpty() && alias -> KotlinPackageEntry.ALL_OTHER_ALIAS_IMPORTS_ENTRY
else -> KotlinPackageEntry(packageName, withSubpackages)
}
entries.add(entry)
}
}
}
override fun writeExternal(parentNode: Element) {
for (entry in entries) {
val element = Element("package")
parentNode.addContent(element)
val name = if (entry == KotlinPackageEntry.ALL_OTHER_IMPORTS_ENTRY || entry == KotlinPackageEntry.ALL_OTHER_ALIAS_IMPORTS_ENTRY) "" else entry.packageName
val alias = (entry == KotlinPackageEntry.ALL_OTHER_ALIAS_IMPORTS_ENTRY)
element.setAttribute("name", name)
element.setAttribute("alias", alias.toString())
element.setAttribute("withSubpackages", entry.withSubpackages.toString())
}
}
}
@@ -27,36 +27,12 @@ import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.ImportPath
import org.jetbrains.kotlin.resolve.bindingContextUtil.getReferenceTargets
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
object ImportPathComparator : Comparator<ImportPath> {
override fun compare(import1: ImportPath, import2: ImportPath): Int {
// alias imports placed last
if (import1.hasAlias() != import2.hasAlias()) {
return if (import1.hasAlias()) +1 else -1
}
// standard library imports last
val stdlib1 = isJavaOrKotlinStdlibImport(import1)
val stdlib2 = isJavaOrKotlinStdlibImport(import2)
if (stdlib1 != stdlib2) {
return if (stdlib1) +1 else -1
}
return import1.toString().compareTo(import2.toString())
}
private fun isJavaOrKotlinStdlibImport(path: ImportPath): Boolean {
val s = path.pathStr
return s.startsWith("java.") || s.startsWith("javax.") || s.startsWith("kotlin.")
}
}
val DeclarationDescriptor.importableFqName: FqName?
get() {
if (!canBeReferencedViaImport()) return null
@@ -14,6 +14,7 @@ import junit.framework.TestCase
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings
import org.jetbrains.kotlin.idea.core.formatter.KotlinPackageEntry
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
@@ -61,10 +62,10 @@ abstract class AbstractPerformanceImportTest : KotlinLightCodeInsightFixtureTest
InTextDirectivesUtils.getPrefixedBoolean(fileText, "// IMPORT_NESTED_CLASSES:") ?: false
InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PACKAGE_TO_USE_STAR_IMPORTS:").forEach {
codeStyleSettings.PACKAGES_TO_USE_STAR_IMPORTS.addEntry(PackageEntry(false, it.trim(), false))
codeStyleSettings.PACKAGES_TO_USE_STAR_IMPORTS.addEntry(KotlinPackageEntry(it.trim(), false))
}
InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PACKAGES_TO_USE_STAR_IMPORTS:").forEach {
codeStyleSettings.PACKAGES_TO_USE_STAR_IMPORTS.addEntry(PackageEntry(false, it.trim(), true))
codeStyleSettings.PACKAGES_TO_USE_STAR_IMPORTS.addEntry(KotlinPackageEntry(it.trim(), true))
}
var descriptorName = InTextDirectivesUtils.findStringWithPrefixes(file.text, "// IMPORT:")
@@ -0,0 +1,322 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonShortcuts
import com.intellij.openapi.actionSystem.ShortcutSet
import com.intellij.openapi.application.ApplicationBundle
import com.intellij.openapi.wm.IdeFocusManager
import com.intellij.ui.*
import com.intellij.ui.components.JBCheckBox
import com.intellij.ui.layout.selected
import com.intellij.ui.table.JBTable
import com.intellij.util.IconUtil
import com.intellij.util.ui.JBUI
import org.jetbrains.kotlin.idea.core.formatter.KotlinPackageEntry
import org.jetbrains.kotlin.idea.core.formatter.KotlinPackageEntryTable
import org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingColors
import java.awt.BorderLayout
import java.awt.Dimension
import javax.swing.DefaultCellEditor
import javax.swing.JPanel
import javax.swing.JTable
import javax.swing.ListSelectionModel
import javax.swing.table.AbstractTableModel
open class BaseKotlinImportLayoutPanel(title: String): JPanel(BorderLayout()) {
val packageTable = KotlinPackageEntryTable()
val layoutTable = createTableForPackageEntries(packageTable)
init {
border = IdeBorderFactory.createTitledBorder(
title,
false,
JBUI.emptyInsets()
)
}
protected fun addPackage() {
var row = layoutTable.selectedRow + 1
if (row < 0) {
row = packageTable.getEntryCount()
}
val entry = KotlinPackageEntry("", true)
packageTable.insertEntryAt(entry, row)
refreshTableModel(row)
}
protected fun removePackage() {
var row = layoutTable.selectedRow
if (row < 0) return
val entry = packageTable.getEntryAt(row)
if (entry == KotlinPackageEntry.ALL_OTHER_IMPORTS_ENTRY || entry == KotlinPackageEntry.ALL_OTHER_IMPORTS_ENTRY) {
return
}
TableUtil.stopEditing(layoutTable)
packageTable.removeEntryAt(row)
val model = layoutTable.model as AbstractTableModel
model.fireTableRowsDeleted(row, row)
if (row >= packageTable.getEntryCount()) {
row--
}
if (row >= 0) {
layoutTable.setRowSelectionInterval(row, row)
}
}
protected fun movePackageUp() {
val row = layoutTable.selectedRow
if (row < 1) return
TableUtil.stopEditing(layoutTable)
val entry = packageTable.getEntryAt(row)
val previousEntry = packageTable.getEntryAt(row - 1)
packageTable.setEntryAt(entry, row - 1)
packageTable.setEntryAt(previousEntry, row)
val model = layoutTable.model as AbstractTableModel
model.fireTableRowsUpdated(row - 1, row)
layoutTable.setRowSelectionInterval(row - 1, row - 1)
}
protected fun movePackageDown() {
val row = layoutTable.selectedRow
if (row >= packageTable.getEntryCount() - 1) return
TableUtil.stopEditing(layoutTable)
val entry = packageTable.getEntryAt(row)
val nextEntry = packageTable.getEntryAt(row + 1)
packageTable.setEntryAt(entry, row + 1)
packageTable.setEntryAt(nextEntry, row)
val model = layoutTable.model as AbstractTableModel
model.fireTableRowsUpdated(row, row + 1)
layoutTable.setRowSelectionInterval(row + 1, row + 1)
}
private fun refreshTableModel(row: Int) {
val model = layoutTable.model as AbstractTableModel
model.fireTableRowsInserted(row, row)
layoutTable.setRowSelectionInterval(row, row)
TableUtil.editCellAt(layoutTable, row, 0)
val editorComp = layoutTable.editorComponent
if (editorComp != null) {
IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown { IdeFocusManager.getGlobalInstance().requestFocus(editorComp, true) }
}
}
protected fun resizeColumns() {
val packageRenderer: ColoredTableCellRenderer = object : ColoredTableCellRenderer() {
override fun customizeCellRenderer(
table: JTable,
value: Any?,
selected: Boolean,
hasFocus: Boolean,
row: Int,
column: Int
) {
val entry = packageTable.getEntryAt(row)
val attributes = KotlinHighlightingColors.KEYWORD.defaultAttributes
append("import", SimpleTextAttributes.fromTextAttributes(attributes))
append(" ", SimpleTextAttributes.REGULAR_ATTRIBUTES)
when (entry) {
KotlinPackageEntry.ALL_OTHER_IMPORTS_ENTRY -> append(
"all other imports",
SimpleTextAttributes.REGULAR_ATTRIBUTES
)
KotlinPackageEntry.ALL_OTHER_ALIAS_IMPORTS_ENTRY -> append(
"all alias imports",
SimpleTextAttributes.REGULAR_ATTRIBUTES
)
else -> append(
"${entry.packageName}.*",
SimpleTextAttributes.REGULAR_ATTRIBUTES
)
}
}
}
layoutTable.columnModel.apply {
getColumn(0).cellRenderer = packageRenderer
getColumn(1).cellRenderer = BooleanTableCellRenderer()
fixColumnWidthToHeader(1)
}
}
private fun fixColumnWidthToHeader(columnIndex: Int) {
with (layoutTable) {
val column = columnModel.getColumn(columnIndex)
val width = 15 + tableHeader.getFontMetrics(tableHeader.font).stringWidth(getColumnName(columnIndex))
column.minWidth = width
column.maxWidth = width
}
}
}
class KotlinStarImportLayoutPanel: BaseKotlinImportLayoutPanel(ApplicationBundle.message("title.packages.to.use.import.with")) {
init {
val importLayoutPanel = ToolbarDecorator.createDecorator(layoutTable)
.setAddAction { addPackage() }
.setRemoveAction { removePackage() }
.setButtonComparator(
"Add",
"Remove"
).setPreferredSize(Dimension(-1, 100))
.createPanel()
add(importLayoutPanel, BorderLayout.CENTER)
resizeColumns()
}
}
class KotlinImportOrderLayoutPanel: BaseKotlinImportLayoutPanel(ApplicationBundle.message("title.import.layout")) {
private val cbImportAliasesSeparately = JBCheckBox("Import aliases separately")
init {
add(cbImportAliasesSeparately, BorderLayout.NORTH)
val importLayoutPanel = ToolbarDecorator.createDecorator(layoutTable)
.addExtraAction(
object: DumbAwareActionButton(ApplicationBundle.message("button.add.package"), IconUtil.getAddPackageIcon()) {
override fun actionPerformed(event: AnActionEvent) {
addPackage()
}
override fun getShortcut(): ShortcutSet {
return CommonShortcuts.getNewForDialogs()
}
}
)
.setRemoveAction { removePackage() }
.setMoveUpAction { movePackageUp() }
.setMoveDownAction { movePackageDown() }
.setRemoveActionUpdater {
val selectedRow = layoutTable.selectedRow
val entry = if (selectedRow in 0 until packageTable.getEntryCount()) packageTable.getEntryAt(selectedRow) else null
entry != null && entry != KotlinPackageEntry.ALL_OTHER_IMPORTS_ENTRY && entry != KotlinPackageEntry.ALL_OTHER_ALIAS_IMPORTS_ENTRY
}.setButtonComparator(
ApplicationBundle.message("button.add.package"),
"Remove",
"Up",
"Down"
).setPreferredSize(Dimension(-1, 100))
.createPanel()
add(importLayoutPanel, BorderLayout.CENTER)
resizeColumns()
cbImportAliasesSeparately.addItemListener { _ ->
if (areImportAliasesEnabled()) {
if (packageTable.getEntries().none { it == KotlinPackageEntry.ALL_OTHER_ALIAS_IMPORTS_ENTRY }) {
packageTable.addEntry(KotlinPackageEntry.ALL_OTHER_ALIAS_IMPORTS_ENTRY)
val row = packageTable.getEntryCount() - 1
val model = layoutTable.model as AbstractTableModel
model.fireTableRowsInserted(row, row)
layoutTable.setRowSelectionInterval(row, row)
}
} else {
val entryIndex = packageTable.getEntries().indexOfFirst { it == KotlinPackageEntry.ALL_OTHER_ALIAS_IMPORTS_ENTRY }
if (entryIndex != -1) {
val currentIndex = layoutTable.selectedRow
packageTable.removeEntryAt(entryIndex)
val model = layoutTable.model as AbstractTableModel
model.fireTableRowsDeleted(entryIndex, entryIndex)
if (currentIndex < entryIndex) {
layoutTable.setRowSelectionInterval(currentIndex, currentIndex)
} else if (entryIndex > 0) {
layoutTable.setRowSelectionInterval(entryIndex - 1, entryIndex - 1)
}
}
}
}
}
fun recomputeAliasesCheckbox() {
cbImportAliasesSeparately.isSelected = packageTable.getEntries().any { it == KotlinPackageEntry.ALL_OTHER_ALIAS_IMPORTS_ENTRY }
}
private fun areImportAliasesEnabled(): Boolean {
return cbImportAliasesSeparately.isSelected
}
}
fun createTableForPackageEntries(packageTable: KotlinPackageEntryTable): JBTable {
val names = arrayOf(ApplicationBundle.message("listbox.import.package"), ApplicationBundle.message("listbox.import.with.subpackages"))
val dataModel = object : AbstractTableModel() {
override fun getColumnCount(): Int {
return names.size
}
override fun getRowCount(): Int {
return packageTable.getEntryCount()
}
override fun getValueAt(rowIndex: Int, columnIndex: Int): Any? {
val entry = packageTable.getEntryAt(rowIndex)
if (!isCellEditable(rowIndex, columnIndex)) return null
return when (columnIndex) {
0 -> entry.packageName
1 -> entry.withSubpackages
else -> throw IllegalArgumentException(columnIndex.toString())
}
}
override fun isCellEditable(rowIndex: Int, columnIndex: Int): Boolean {
val entry = packageTable.getEntryAt(rowIndex)
return !entry.isSpecial
}
override fun getColumnName(column: Int): String {
return names[column]
}
override fun getColumnClass(columnIndex: Int): Class<*> {
return when (columnIndex) {
0 -> String::class.java
1 -> Boolean::class.java
else -> throw IllegalArgumentException(columnIndex.toString())
}
}
override fun setValueAt(value: Any, rowIndex: Int, columnIndex: Int) {
val entry = packageTable.getEntryAt(rowIndex)
val newEntry = when (columnIndex) {
0 -> KotlinPackageEntry((value as String).trim(), entry.withSubpackages)
1 -> KotlinPackageEntry(entry.packageName, (value as Boolean))
else -> throw IllegalArgumentException(columnIndex.toString())
}
packageTable.setEntryAt(newEntry, rowIndex)
}
}
// Create the table
val result = JBTable(dataModel)
result.selectionModel.selectionMode = ListSelectionModel.SINGLE_SELECTION
val editor = result.getDefaultEditor(String::class.java)
if (editor is DefaultCellEditor) editor.clickCountToStart = 1
return result
}
@@ -11,12 +11,12 @@ import com.intellij.application.options.PackagePanel
import com.intellij.openapi.application.ApplicationBundle
import com.intellij.openapi.editor.colors.EditorColorsScheme
import com.intellij.psi.codeStyle.CodeStyleSettings
import com.intellij.psi.codeStyle.PackageEntryTable
import com.intellij.ui.OptionGroup
import com.intellij.ui.components.JBScrollPane
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings
import org.jetbrains.kotlin.idea.core.formatter.KotlinPackageEntryTable
import java.awt.BorderLayout
import java.awt.GridBagConstraints
import java.awt.GridBagLayout
@@ -53,12 +53,8 @@ class ImportSettingsPanelWrapper(settings: CodeStyleSettings) : CodeStyleAbstrac
class ImportSettingsPanel(private val commonSettings: CodeStyleSettings) : JPanel() {
private val cbImportNestedClasses = JCheckBox(KotlinBundle.message("formatter.checkbox.text.insert.imports.for.nested.classes"))
private val starImportPackageEntryTable = PackageEntryTable()
private val dummyImportLayoutPanel = object : ImportLayoutPanel() {
override fun areStaticImportsEnabled() = false
override fun refresh() {}
}
private val starImportPackageTable = ImportLayoutPanel.createTableForPackageEntries(starImportPackageEntryTable, dummyImportLayoutPanel)
private val starImportLayoutPanel = KotlinStarImportLayoutPanel()
private val importOrderLayoutPanel = KotlinImportOrderLayoutPanel()
private val nameCountToUseStarImportSelector = NameCountToUseStarImportSelector(
KotlinBundle.message("formatter.title.top.level.symbols"), KotlinCodeStyleSettings.defaultSettings().NAME_COUNT_TO_USE_STAR_IMPORT
@@ -88,7 +84,13 @@ class ImportSettingsPanel(private val commonSettings: CodeStyleSettings) : JPane
KotlinBundle.message("formatter.title.other")).apply { add(cbImportNestedClasses) }.createPanel(),
constraints.apply { gridy++ })
add(PackagePanel.createPackagesPanel(starImportPackageTable, starImportPackageEntryTable), constraints.apply {
add(starImportLayoutPanel, constraints.apply {
gridy++
fill = GridBagConstraints.BOTH
weighty = 1.0
})
add(importOrderLayoutPanel, constraints.apply {
gridy++
fill = GridBagConstraints.BOTH
weighty = 1.0
@@ -102,18 +104,27 @@ class ImportSettingsPanel(private val commonSettings: CodeStyleSettings) : JPane
cbImportNestedClasses.isSelected = settings.IMPORT_NESTED_CLASSES
starImportPackageEntryTable.copyFrom(settings.PACKAGES_TO_USE_STAR_IMPORTS)
(starImportPackageTable.model as AbstractTableModel).fireTableDataChanged()
if (starImportPackageTable.rowCount > 0) {
starImportPackageTable.selectionModel.setSelectionInterval(0, 0)
starImportLayoutPanel.packageTable.copyFrom(settings.PACKAGES_TO_USE_STAR_IMPORTS)
(starImportLayoutPanel.layoutTable.model as AbstractTableModel).fireTableDataChanged()
if (starImportLayoutPanel.layoutTable.rowCount > 0) {
starImportLayoutPanel.layoutTable.selectionModel.setSelectionInterval(0, 0)
}
importOrderLayoutPanel.packageTable.copyFrom(settings.PACKAGES_IMPORT_LAYOUT)
(importOrderLayoutPanel.layoutTable.model as AbstractTableModel).fireTableDataChanged()
if (importOrderLayoutPanel.layoutTable.rowCount > 0) {
importOrderLayoutPanel.layoutTable.selectionModel.setSelectionInterval(0, 0)
}
importOrderLayoutPanel.recomputeAliasesCheckbox()
}
fun apply(settings: KotlinCodeStyleSettings) {
settings.NAME_COUNT_TO_USE_STAR_IMPORT = nameCountToUseStarImportSelector.value
settings.NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS = nameCountToUseStarImportForMembersSelector.value
settings.IMPORT_NESTED_CLASSES = cbImportNestedClasses.isSelected
settings.PACKAGES_TO_USE_STAR_IMPORTS.copyFrom(getCopyWithoutEmptyPackages(starImportPackageEntryTable))
settings.PACKAGES_TO_USE_STAR_IMPORTS.copyFrom(getCopyWithoutEmptyPackages(starImportLayoutPanel.packageTable))
settings.PACKAGES_IMPORT_LAYOUT.copyFrom(importOrderLayoutPanel.packageTable)
}
fun isModified(settings: KotlinCodeStyleSettings): Boolean {
@@ -122,7 +133,8 @@ class ImportSettingsPanel(private val commonSettings: CodeStyleSettings) : JPane
isModified = isModified || nameCountToUseStarImportSelector.value != NAME_COUNT_TO_USE_STAR_IMPORT
isModified = isModified || nameCountToUseStarImportForMembersSelector.value != NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS
isModified = isModified || isModified(cbImportNestedClasses, IMPORT_NESTED_CLASSES)
isModified = isModified || isModified(getCopyWithoutEmptyPackages(starImportPackageEntryTable), PACKAGES_TO_USE_STAR_IMPORTS)
isModified = isModified || isModified(getCopyWithoutEmptyPackages(starImportLayoutPanel.packageTable), PACKAGES_TO_USE_STAR_IMPORTS)
isModified = isModified || isModified(importOrderLayoutPanel.packageTable, PACKAGES_IMPORT_LAYOUT)
isModified
}
@@ -133,12 +145,12 @@ class ImportSettingsPanel(private val commonSettings: CodeStyleSettings) : JPane
return checkBox.isSelected != value
}
private fun isModified(list: PackageEntryTable, table: PackageEntryTable): Boolean {
if (list.entryCount != table.entryCount) {
private fun isModified(list: KotlinPackageEntryTable, table: KotlinPackageEntryTable): Boolean {
if (list.getEntryCount() != table.getEntryCount()) {
return true
}
for (i in 0 until list.entryCount) {
for (i in 0 until list.getEntryCount()) {
val entry1 = list.getEntryAt(i)
val entry2 = table.getEntryAt(i)
if (entry1 != entry2) {
@@ -149,9 +161,9 @@ class ImportSettingsPanel(private val commonSettings: CodeStyleSettings) : JPane
return false
}
private fun getCopyWithoutEmptyPackages(table: PackageEntryTable): PackageEntryTable {
private fun getCopyWithoutEmptyPackages(table: KotlinPackageEntryTable): KotlinPackageEntryTable {
try {
val copy = table.clone() as PackageEntryTable
val copy = table.clone()
copy.removeEmptyPackages()
return copy
} catch (ignored: CloneNotSupportedException) {
@@ -12,7 +12,6 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings
import org.jetbrains.kotlin.idea.core.targetDescriptors
import org.jetbrains.kotlin.idea.imports.ImportPathComparator
import org.jetbrains.kotlin.idea.imports.getImportableTargets
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
@@ -45,7 +44,7 @@ class ImportInsertHelperImpl(private val project: Project) : ImportInsertHelper(
get() = KotlinCodeStyleSettings.getInstance(project)
override val importSortComparator: Comparator<ImportPath>
get() = ImportPathComparator
get() = ImportPathComparator(codeStyleSettings.PACKAGES_IMPORT_LAYOUT)
override fun isImportedWithDefault(importPath: ImportPath, contextFile: KtFile): Boolean {
val languageVersionSettings = contextFile.getResolutionFacade().frontendService<LanguageVersionSettings>()
@@ -409,10 +408,11 @@ class ImportInsertHelperImpl(private val project: Project) : ImportInsertHelper(
importList.add(psiFactory.createNewLine())
importList.add(newDirective) as KtImportDirective
} else {
val importPathComparator = ImportInsertHelperImpl(project).importSortComparator
val insertAfter = imports
.lastOrNull {
val directivePath = it.importPath
directivePath != null && ImportPathComparator.compare(directivePath, importPath) <= 0
directivePath != null && importPathComparator.compare(directivePath, importPath) <= 0
}
importList.addAfter(newDirective, insertAfter) as KtImportDirective
}
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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 org.jetbrains.kotlin.idea.core.formatter.KotlinPackageEntry
import org.jetbrains.kotlin.idea.core.formatter.KotlinPackageEntryTable
import org.jetbrains.kotlin.resolve.ImportPath
import java.util.Comparator
class ImportPathComparator(
private val packageTable: KotlinPackageEntryTable
) : Comparator<ImportPath> {
override fun compare(import1: ImportPath, import2: ImportPath): Int {
val index1 = bestMatchIndex(import1)
val index2 = bestMatchIndex(import2)
return when {
index1 == index2 -> import1.toString().compareTo(import2.toString())
index1 < index2 -> -1
else -> +1
}
}
private fun bestMatchIndex(path: ImportPath): Int {
var bestIndex: Int? = null
var bestEntryMatch: KotlinPackageEntry? = null
for ((index, entry) in packageTable.getEntries().withIndex()) {
if (entry.isBetterMatchForPackageThan(bestEntryMatch, path)) {
bestEntryMatch = entry
bestIndex = index
}
}
return bestIndex!!
}
}
@@ -6,10 +6,10 @@
package org.jetbrains.kotlin
import com.intellij.application.options.CodeStyle
import com.intellij.psi.codeStyle.PackageEntry
import com.intellij.testFramework.LightProjectDescriptor
import junit.framework.TestCase
import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings
import org.jetbrains.kotlin.idea.core.formatter.KotlinPackageEntry
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
@@ -56,10 +56,10 @@ abstract class AbstractImportsTest : KotlinLightCodeInsightFixtureTestCase() {
InTextDirectivesUtils.getPrefixedBoolean(fileText, "// IMPORT_NESTED_CLASSES:") ?: false
InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PACKAGE_TO_USE_STAR_IMPORTS:").forEach {
codeStyleSettings.PACKAGES_TO_USE_STAR_IMPORTS.addEntry(PackageEntry(false, it.trim(), false))
codeStyleSettings.PACKAGES_TO_USE_STAR_IMPORTS.addEntry(KotlinPackageEntry(it.trim(), false))
}
InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PACKAGES_TO_USE_STAR_IMPORTS:").forEach {
codeStyleSettings.PACKAGES_TO_USE_STAR_IMPORTS.addEntry(PackageEntry(false, it.trim(), true))
codeStyleSettings.PACKAGES_TO_USE_STAR_IMPORTS.addEntry(KotlinPackageEntry(it.trim(), true))
}
val log = project.executeWriteCommand<String?>("") { doTest(file) }