From b5db50f42996c09de12a1bddb98adb960a32bc01 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Mon, 27 Mar 2017 20:37:47 +0300 Subject: [PATCH] Move: Implement Kotlin-specific editor for class name #KT-13192 Fixed --- .../idea/completion/CompletionSession.kt | 2 +- .../kotlin/idea/completion/CompletionUtils.kt | 4 +- .../idea/completion/KeywordCompletion.kt | 12 ++++ .../completion/LookupElementsCollector.kt | 7 +- .../ui/MoveKotlinNestedClassesDialog.java | 32 +++++++-- ...ypeReferenceEditorComboWithBrowseButton.kt | 72 +++++++++++++++++++ 6 files changed, 118 insertions(+), 11 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/ui/KotlinTypeReferenceEditorComboWithBrowseButton.kt diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt index 7c5a5257d3f..8aa60f93ba1 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt @@ -142,7 +142,7 @@ abstract class CompletionSession( // LookupElementsCollector instantiation is deferred because virtual call to createSorter uses data from derived classes protected val collector: LookupElementsCollector by lazy(LazyThreadSafetyMode.NONE) { - LookupElementsCollector(prefixMatcher, parameters, resultSet, createSorter()) + LookupElementsCollector(prefixMatcher, parameters, resultSet, createSorter(), (file as? KtCodeFragment)?.extraCompletionFilter) } protected val searchScope: GlobalSearchScope = getResolveScope(parameters.originalFile as KtFile) diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt index 26a4d16acf5..d09634402b2 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt @@ -418,4 +418,6 @@ fun OffsetMap.tryGetOffset(key: OffsetKey): Int? { catch(e: Exception) { return null } -} \ No newline at end of file +} + +var KtCodeFragment.extraCompletionFilter: ((LookupElement) -> Boolean)? by CopyableUserDataProperty(Key.create("EXTRA_COMPLETION_FILTER")) \ No newline at end of file diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt index 1df8e922143..2bc49984dbb 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt @@ -159,6 +159,7 @@ object KeywordCompletion { CommentFilter(), ParentFilter(ClassFilter(KtLiteralStringTemplateEntry::class.java)), ParentFilter(ClassFilter(KtConstantExpression::class.java)), + FileFilter(ClassFilter(KtTypeCodeFragment::class.java)), LeftNeighbour(TextFilter(".")), LeftNeighbour(TextFilter("?.")) )) @@ -182,6 +183,17 @@ object KeywordCompletion { } } + private class FileFilter(filter : ElementFilter) : PositionElementFilter() { + init { + setFilter(filter) + } + + override fun isAcceptable(element : Any?, context : PsiElement?) : Boolean { + val file = (element as? PsiElement)?.containingFile + return file != null && (filter?.isAcceptable(file, context) ?: true) + } + } + private fun buildFilter(position: PsiElement): (KtKeywordToken) -> Boolean { var parent = position.parent var prevParent = position diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementsCollector.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementsCollector.kt index 05a2665ac22..b2644feb5d8 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementsCollector.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementsCollector.kt @@ -32,7 +32,8 @@ class LookupElementsCollector( private val prefixMatcher: PrefixMatcher, private val completionParameters: CompletionParameters, resultSet: CompletionResultSet, - sorter: CompletionSorter + sorter: CompletionSorter, + private val filter: ((LookupElement) -> Boolean)? ) { var bestMatchingDegree = Int.MIN_VALUE @@ -132,7 +133,9 @@ class LookupElementsCollector( } } - elements.add(result) + if (filter?.invoke(result) ?: true) { + elements.add(result) + } val matchingDegree = RealPrefixMatchingWeigher.getBestMatchingDegree(result, prefixMatcher) bestMatchingDegree = Math.max(bestMatchingDegree, matchingDegree) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesDialog.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesDialog.java index 591a4e80922..1a9a7f74b99 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesDialog.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesDialog.java @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui; +import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.ide.util.ClassFilter; import com.intellij.ide.util.TreeClassChooser; import com.intellij.ide.util.TreeJavaClassChooserDialog; @@ -23,9 +24,9 @@ import com.intellij.openapi.editor.event.DocumentAdapter; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.project.Project; -import com.intellij.psi.JavaCodeFragment; import com.intellij.psi.JavaPsiFacade; import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.refactoring.RefactoringBundle; @@ -34,18 +35,21 @@ import com.intellij.refactoring.classMembers.MemberInfoChange; import com.intellij.refactoring.move.MoveCallback; import com.intellij.refactoring.move.MoveHandler; import com.intellij.refactoring.ui.RefactoringDialog; -import com.intellij.ui.ReferenceEditorComboWithBrowseButton; import kotlin.collections.CollectionsKt; import kotlin.jvm.functions.Function1; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.asJava.LightClassUtilsKt; import org.jetbrains.kotlin.asJava.classes.KtLightClassForSourceDeclaration; +import org.jetbrains.kotlin.idea.completion.CompletionUtilsKt; +import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject; import org.jetbrains.kotlin.idea.projectView.KtClassOrObjectTreeNode; +import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtilKt; import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo; import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionPanel; import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionTable; import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*; +import org.jetbrains.kotlin.idea.refactoring.ui.KotlinTypeReferenceEditorComboWithBrowseButton; import org.jetbrains.kotlin.psi.*; import javax.swing.*; @@ -62,7 +66,7 @@ public class MoveKotlinNestedClassesDialog extends RefactoringDialog { private JPanel mainPanel; private JTextField originalClassField; private JPanel membersInfoPanel; - private ReferenceEditorComboWithBrowseButton targetClassChooser; + private KotlinTypeReferenceEditorComboWithBrowseButton targetClassChooser; private JCheckBox openInEditorCheckBox; private JPanel targetClassChooserPanel; private KotlinMemberSelectionTable memberTable; @@ -96,7 +100,7 @@ public class MoveKotlinNestedClassesDialog extends RefactoringDialog { originalClassField.setText(originalClass.getFqName().asString()); //noinspection ConstantConditions - targetClassChooser = new ReferenceEditorComboWithBrowseButton( + targetClassChooser = new KotlinTypeReferenceEditorComboWithBrowseButton( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -148,10 +152,24 @@ public class MoveKotlinNestedClassesDialog extends RefactoringDialog { } }, initialTargetClass.getFqName().asString(), - myProject, - true, - JavaCodeFragment.VisibilityChecker.PROJECT_SCOPE_VISIBLE, + originalClass, RECENTS_KEY); + KtTypeCodeFragment codeFragment = targetClassChooser.getCodeFragment(); + if (codeFragment != null) { + CompletionUtilsKt.setExtraCompletionFilter( + codeFragment, + new Function1() { + @Override + public Boolean invoke(LookupElement lookupElement) { + Object lookupObject = lookupElement.getObject(); + if (!(lookupObject instanceof DeclarationLookupObject)) return false; + PsiElement psiElement = ((DeclarationLookupObject) lookupObject).getPsiElement(); + if (!(psiElement instanceof KtClassOrObject)) return false; + return KotlinRefactoringUtilKt.canRefactor(psiElement); + } + } + ); + } targetClassChooser.getChildComponent().getDocument().addDocumentListener( new DocumentAdapter() { @Override diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/ui/KotlinTypeReferenceEditorComboWithBrowseButton.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/ui/KotlinTypeReferenceEditorComboWithBrowseButton.kt new file mode 100644 index 00000000000..d0c45a95046 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/ui/KotlinTypeReferenceEditorComboWithBrowseButton.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2000-2009 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 org.jetbrains.kotlin.idea.refactoring.ui + +import com.intellij.openapi.editor.Document +import com.intellij.openapi.ui.ComponentWithBrowseButton +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement +import com.intellij.ui.EditorComboBox +import com.intellij.ui.RecentsManager +import com.intellij.ui.TextAccessor +import com.intellij.util.ArrayUtil +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtTypeCodeFragment +import java.awt.event.ActionListener + +class KotlinTypeReferenceEditorComboWithBrowseButton( + browseActionListener: ActionListener, + text: String?, + contextElement: PsiElement, + recentsKey: String +) : ComponentWithBrowseButton( + EditorComboBox(createDocument(text, contextElement), contextElement.project, KotlinFileType.INSTANCE), + browseActionListener +), TextAccessor { + companion object { + private fun createDocument(text: String?, contextElement: PsiElement): Document? { + val codeFragment = KtPsiFactory(contextElement).createTypeCodeFragment(text ?: "", contextElement) + return PsiDocumentManager.getInstance(contextElement.project).getDocument(codeFragment) + } + } + + private val project = contextElement.project + + init { + RecentsManager.getInstance(contextElement.project).getRecentEntries(recentsKey)?.let { + childComponent.setHistory(ArrayUtil.toStringArray(it)) + } + + if (text != null) { + if (text.isNotEmpty()) { + childComponent.prependItem(text) + } + else { + childComponent.selectedItem = null + } + } + } + + override fun getText() = childComponent.text.trim { it <= ' ' } + + override fun setText(text: String) { + childComponent.text = text + } + + val codeFragment: KtTypeCodeFragment? + get() = PsiDocumentManager.getInstance(project).getPsiFile(childComponent.document) as? KtTypeCodeFragment +} \ No newline at end of file