diff --git a/annotations/com/intellij/refactoring/annotations.xml b/annotations/com/intellij/refactoring/annotations.xml index 489d1ad655d..46eab669011 100644 --- a/annotations/com/intellij/refactoring/annotations.xml +++ b/annotations/com/intellij/refactoring/annotations.xml @@ -3,4 +3,7 @@ name='com.intellij.refactoring.MultiFileTestCase.PerformAction void performAction(com.intellij.openapi.vfs.VirtualFile, com.intellij.openapi.vfs.VirtualFile) 0'> + + + \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetCallExpression.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetCallExpression.java index 4ec8e3e0efd..9b6245e3ef2 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetCallExpression.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetCallExpression.java @@ -19,6 +19,7 @@ package org.jetbrains.jet.lang.psi; import com.intellij.lang.ASTNode; import com.intellij.psi.PsiElement; import com.intellij.util.SmartList; +import jet.runtime.typeinfo.KotlinSignature; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.JetNodeTypes; @@ -85,6 +86,7 @@ public class JetCallExpression extends JetReferenceExpression implements JetCall return result; } + @KotlinSignature("fun getValueArguments(): MutableList") @Override @NotNull public List getValueArguments() { diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/psiUtil/jetPsiUtil.kt b/compiler/frontend/src/org/jetbrains/jet/lang/psi/psiUtil/jetPsiUtil.kt index 4e70da3dda2..3e5fd21f5d1 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/psiUtil/jetPsiUtil.kt +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/psiUtil/jetPsiUtil.kt @@ -39,8 +39,6 @@ import com.intellij.psi.search.SearchScope import com.intellij.psi.search.PsiSearchScopeUtil import org.jetbrains.jet.lang.psi.JetClassBody import org.jetbrains.jet.lang.psi.JetParameterList -import org.jetbrains.jet.lang.psi.JetNamedDeclaration -import com.intellij.psi.PsiNamedElement import org.jetbrains.jet.lang.psi.JetObjectDeclaration import org.jetbrains.jet.lang.psi.JetNamedFunction import org.jetbrains.jet.lang.psi.JetProperty @@ -49,6 +47,14 @@ import org.jetbrains.jet.lang.psi.JetPropertyAccessor import org.jetbrains.jet.lang.psi.JetParameter import com.intellij.psi.PsiParameterList import com.intellij.psi.PsiParameter +import org.jetbrains.jet.lang.psi.JetQualifiedExpression +import org.jetbrains.jet.lang.psi.JetUserType +import org.jetbrains.jet.lang.resolve.name.FqName +import org.jetbrains.jet.lang.psi.JetCallExpression +import com.intellij.psi.PsiPackage +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiMember +import org.jetbrains.jet.lang.psi.JetNamedDeclaration fun PsiElement.getParentByTypesAndPredicate( strict : Boolean = false, vararg parentClasses : Class, predicate: (T) -> Boolean @@ -209,4 +215,65 @@ fun PsiElement.parameterIndex(): Int { this is PsiParameter && parent is PsiParameterList -> parent.getParameterIndex(this) else -> -1 } +} + +/** + * Returns enclosing qualifying element for given [[JetSimpleNameExpression]] + * ([[JetQualifiedExpression]] or [[JetUserType]] or original expression) + */ +fun JetSimpleNameExpression.getQualifiedElement(): JetElement { + val baseExpression = (getParent() as? JetCallExpression) ?: this + val parent = baseExpression.getParent() + return when (parent) { + is JetQualifiedExpression -> if (parent.getSelectorExpression().isAncestor(baseExpression)) parent else baseExpression + is JetUserType -> if (parent.getReferenceExpression().isAncestor(baseExpression)) parent else baseExpression + else -> baseExpression + } +} + +/** + * Returns rightmost selector of the qualified element (null if there is no such selector) + */ +fun JetElement.getQualifiedElementSelector(): JetElement? { + return when (this) { + is JetSimpleNameExpression -> this + is JetQualifiedExpression -> { + val selector = getSelectorExpression() + if (selector is JetCallExpression) selector.getCalleeExpression() else selector + } + is JetUserType -> getReferenceExpression() + else -> this + } +} + +/** + * Returns outermost qualified element ([[JetQualifiedExpression]] or [[JetUserType]]) in the non-interleaving chain + * of qualified elements which enclose given expression + * If there is no such elements original expression is returned + */ +fun JetSimpleNameExpression.getOutermostNonInterleavingQualifiedElement(): JetElement { + var element = ((getParent() as? JetCallExpression) ?: this).getParent() + if (element !is JetQualifiedExpression && element !is JetUserType) return this + + while (true) { + val parent = element!!.getParent() + if (parent !is JetQualifiedExpression && parent !is JetUserType) return element as JetElement + element = parent + } +} + +/** + * Returns FqName for given declaration (either Java or Kotlin) + */ +fun PsiElement.getFqName(): FqName? { + return when (this) { + is PsiPackage -> FqName(getQualifiedName()) + is PsiClass -> getQualifiedName()?.let { FqName(it) } + is PsiMember -> getName()?.let { name -> + val prefix = getContainingClass()?.getQualifiedName() + FqName(if (prefix != null) "$prefix.$name" else name) + } + is JetNamedDeclaration -> JetPsiUtil.getFQName(this) + else -> null + } } \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 6fd05c835e5..641ede8c6c2 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -166,6 +166,7 @@ implementationClass="org.jetbrains.jet.plugin.codeInsight.surroundWith.statement.KotlinStatementSurroundDescriptor"/> + diff --git a/idea/src/org/jetbrains/jet/plugin/codeInsight/KotlinShortenReferencesRefactoringHelper.kt b/idea/src/org/jetbrains/jet/plugin/codeInsight/KotlinShortenReferencesRefactoringHelper.kt new file mode 100644 index 00000000000..1c43d65d402 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/codeInsight/KotlinShortenReferencesRefactoringHelper.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2010-2014 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.jet.plugin.codeInsight + +import com.intellij.refactoring.RefactoringHelper +import com.intellij.usageView.UsageInfo +import com.intellij.openapi.project.Project +import java.util.HashSet +import com.intellij.openapi.util.Key +import com.intellij.openapi.application.ApplicationManager +import com.intellij.psi.SmartPsiElementPointer +import org.jetbrains.jet.lang.psi.JetSimpleNameExpression +import org.jetbrains.jet.lang.resolve.name.FqName +import org.jetbrains.jet.plugin.refactoring.changeQualifiedName +import org.jetbrains.jet.lang.psi.psiUtil.getQualifiedElementSelector +import org.jetbrains.jet.lang.psi.psiUtil.getOutermostNonInterleavingQualifiedElement + +public class KotlinShortenReferencesRefactoringHelper: RefactoringHelper> { + override fun prepareOperation(usages: Array?): Set? { + if (usages != null && usages.isNotEmpty()) { + ApplicationManager.getApplication()!!.runWriteAction { usages[0].getProject().getElementsToShorten(true)!!.clear() } + } + return null + } + + override fun performOperation(project: Project, operationData: Set?) { + ApplicationManager.getApplication()!!.runWriteAction { + project.getElementsToShorten(false)?.let { bindRequests -> + project.putUserData(ELEMENTS_TO_SHORTEN_KEY, null) + ShortenReferences.process( + bindRequests + .map() { req -> req.process()?.getOutermostNonInterleavingQualifiedElement() } + .filterNotNull() + ) + } + } + } +} + +private val ELEMENTS_TO_SHORTEN_KEY = Key.create>("ELEMENTS_TO_SHORTEN_KEY") + +class ReferenceBindRequest(val refExpression: SmartPsiElementPointer, val fqName: FqName) { + fun process(): JetSimpleNameExpression? { + fun bindToFqName(expression: JetSimpleNameExpression, fqName: FqName): JetSimpleNameExpression { + val qualifier = expression.changeQualifiedName(fqName) + val newExpression = qualifier.getQualifiedElementSelector() as JetSimpleNameExpression? + assert(newExpression != null) { "No selector in qualified element" } + + return newExpression!! + } + + val originalExpression = refExpression.getElement() + if (originalExpression == null) return null + + return bindToFqName(originalExpression, fqName) + } + +} + +private fun Project.getElementsToShorten(createIfNeeded: Boolean): MutableSet? { + var elementsToShorten = getUserData(ELEMENTS_TO_SHORTEN_KEY) + if (createIfNeeded && elementsToShorten == null) { + elementsToShorten = HashSet() + putUserData(ELEMENTS_TO_SHORTEN_KEY, elementsToShorten) + } + + return elementsToShorten +} + +public fun Project.addReferenceBindRequest(request: ReferenceBindRequest) { + assert (ApplicationManager.getApplication()!!.isWriteAccessAllowed(), "Write access needed") + getElementsToShorten(true)!!.add(request) +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt new file mode 100644 index 00000000000..01e7bba7624 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2010-2014 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.jet.plugin.refactoring + +import org.jetbrains.jet.lang.psi.JetSimpleNameExpression +import org.jetbrains.jet.lang.resolve.name.FqName +import org.jetbrains.jet.lang.psi.JetElement +import org.jetbrains.jet.lang.psi.JetCallExpression +import org.jetbrains.jet.lang.psi.JetPsiFactory +import org.jetbrains.jet.lang.psi.psiUtil.getQualifiedElement +import org.jetbrains.jet.lang.psi.JetUserType +import org.jetbrains.jet.lang.resolve.name.isOneSegmentFQN + +/** + * Replace [[JetSimpleNameExpression]] (and its enclosing qualifier) with qualified element given by FqName + * Result is either the same as original element, or [[JetQualifiedExpression]], or [[JetUserType]] + * Note that FqName may not be empty + */ +fun JetSimpleNameExpression.changeQualifiedName(fqName: FqName): JetElement { + assert (!fqName.isRoot(), "Can't set empty FqName for element $this") + + val project = getProject() + + val shortName = fqName.shortName().asString() + val fqNameBase = (getParent() as? JetCallExpression)?.let { parent -> + val callCopy = parent.copy() as JetCallExpression + callCopy.getCalleeExpression()!!.replace(JetPsiFactory.createSimpleName(project, shortName)).getParent()!!.getText() + } ?: shortName + + val text = if (!fqName.isOneSegmentFQN()) "${fqName.parent().asString()}.$fqNameBase" else fqNameBase + + val elementToReplace = getQualifiedElement() + return when (elementToReplace) { + is JetUserType -> elementToReplace.replace(JetPsiFactory.createType(project, text).getTypeElement()!!) + else -> elementToReplace.replace(JetPsiFactory.createExpression(project, text)) + } as JetElement +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/references/JetSimpleNameReference.java b/idea/src/org/jetbrains/jet/plugin/references/JetSimpleNameReference.java index 98112b99c96..866198ae0e9 100644 --- a/idea/src/org/jetbrains/jet/plugin/references/JetSimpleNameReference.java +++ b/idea/src/org/jetbrains/jet/plugin/references/JetSimpleNameReference.java @@ -19,15 +19,19 @@ package org.jetbrains.jet.plugin.references; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; +import com.intellij.psi.SmartPointerManager; import com.intellij.psi.tree.IElementType; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.lang.psi.JetPsiFactory; import org.jetbrains.jet.lang.psi.JetSimpleNameExpression; +import org.jetbrains.jet.lang.psi.psiUtil.PsiUtilPackage; +import org.jetbrains.jet.lang.resolve.name.FqName; import org.jetbrains.jet.lexer.JetTokens; +import org.jetbrains.jet.plugin.codeInsight.CodeInsightPackage; +import org.jetbrains.jet.plugin.codeInsight.ReferenceBindRequest; public class JetSimpleNameReference extends JetSimpleReference { - public JetSimpleNameReference(@NotNull JetSimpleNameExpression jetSimpleNameExpression) { super(jetSimpleNameExpression); } @@ -55,6 +59,35 @@ public class JetSimpleNameReference extends JetSimpleReference