KT-10974 - add import layout configuration table
This commit is contained in:
+11
-5
@@ -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
|
||||
}
|
||||
}
|
||||
+113
@@ -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
|
||||
|
||||
+3
-2
@@ -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) }
|
||||
|
||||
Reference in New Issue
Block a user