Refactoring: Allow multiple ElementKinds when choosing element to refactor

This commit is contained in:
Alexey Sedunov
2016-07-19 18:17:12 +03:00
parent 09d36291de
commit b5828a5fa1
14 changed files with 68 additions and 41 deletions
@@ -34,6 +34,8 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.ui.components.JBList; import com.intellij.ui.components.JBList;
import com.intellij.util.Function; import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.ContainerUtil;
import kotlin.collections.CollectionsKt;
import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.asJava.KtLightMethod; import org.jetbrains.kotlin.asJava.KtLightMethod;
@@ -333,16 +335,16 @@ public class KotlinRefactoringUtil {
public static void selectElement( public static void selectElement(
@NotNull Editor editor, @NotNull Editor editor,
@NotNull KtFile file, @NotNull KtFile file,
@NotNull CodeInsightUtils.ElementKind elementKind, @NotNull Collection<CodeInsightUtils.ElementKind> elementKinds,
@NotNull SelectElementCallback callback @NotNull SelectElementCallback callback
) throws IntroduceRefactoringException { ) throws IntroduceRefactoringException {
selectElement(editor, file, true, elementKind, callback); selectElement(editor, file, true, elementKinds, callback);
} }
public static void selectElement(@NotNull Editor editor, public static void selectElement(@NotNull Editor editor,
@NotNull KtFile file, @NotNull KtFile file,
boolean failOnEmptySuggestion, boolean failOnEmptySuggestion,
@NotNull CodeInsightUtils.ElementKind elementKind, @NotNull Collection<CodeInsightUtils.ElementKind> elementKinds,
@NotNull SelectElementCallback callback @NotNull SelectElementCallback callback
) throws IntroduceRefactoringException { ) throws IntroduceRefactoringException {
if (editor.getSelectionModel().hasSelection()) { if (editor.getSelectionModel().hasSelection()) {
@@ -351,11 +353,20 @@ public class KotlinRefactoringUtil {
String text = file.getText(); String text = file.getText();
while (selectionStart < selectionEnd && Character.isSpaceChar(text.charAt(selectionStart))) ++selectionStart; while (selectionStart < selectionEnd && Character.isSpaceChar(text.charAt(selectionStart))) ++selectionStart;
while (selectionStart < selectionEnd && Character.isSpaceChar(text.charAt(selectionEnd - 1))) --selectionEnd; while (selectionStart < selectionEnd && Character.isSpaceChar(text.charAt(selectionEnd - 1))) --selectionEnd;
callback.run(findElement(file, selectionStart, selectionEnd, failOnEmptySuggestion, elementKind));
for (CodeInsightUtils.ElementKind elementKind : elementKinds) {
PsiElement element = findElement(file, selectionStart, selectionEnd, failOnEmptySuggestion, elementKind);
if (element != null) {
callback.run(element);
return;
}
}
callback.run(null);
} }
else { else {
int offset = editor.getCaretModel().getOffset(); int offset = editor.getCaretModel().getOffset();
smartSelectElement(editor, file, offset, failOnEmptySuggestion, elementKind, callback); smartSelectElement(editor, file, offset, failOnEmptySuggestion, elementKinds, callback);
} }
} }
@@ -442,13 +453,21 @@ public class KotlinRefactoringUtil {
private static void smartSelectElement( private static void smartSelectElement(
@NotNull Editor editor, @NotNull Editor editor,
@NotNull PsiFile file, @NotNull final PsiFile file,
int offset, final int offset,
boolean failOnEmptySuggestion, boolean failOnEmptySuggestion,
@NotNull CodeInsightUtils.ElementKind elementKind, @NotNull Collection<CodeInsightUtils.ElementKind> elementKinds,
@NotNull final SelectElementCallback callback @NotNull final SelectElementCallback callback
) throws IntroduceRefactoringException { ) throws IntroduceRefactoringException {
List<KtElement> elements = getSmartSelectSuggestions(file, offset, elementKind); List<KtElement> elements = CollectionsKt.flatMap(
elementKinds,
new Function1<CodeInsightUtils.ElementKind, Iterable<? extends KtElement>>() {
@Override
public Iterable<? extends KtElement> invoke(CodeInsightUtils.ElementKind kind) {
return getSmartSelectSuggestions(file, offset, kind);
}
}
);
if (elements.size() == 0) { if (elements.size() == 0) {
if (failOnEmptySuggestion) throw new IntroduceRefactoringException( if (failOnEmptySuggestion) throw new IntroduceRefactoringException(
KotlinRefactoringBundle.message("cannot.refactor.not.expression")); KotlinRefactoringBundle.message("cannot.refactor.not.expression"));
@@ -542,7 +561,7 @@ public class KotlinRefactoringUtil {
return element; return element;
} }
public static class IntroduceRefactoringException extends Exception { public static class IntroduceRefactoringException extends RuntimeException {
private final String myMessage; private final String myMessage;
public IntroduceRefactoringException(String message) { public IntroduceRefactoringException(String message) {
@@ -68,7 +68,7 @@ class ExtractKotlinFunctionHandler(
editor, editor,
file, file,
"Select target code block", "Select target code block",
CodeInsightUtils.ElementKind.EXPRESSION, listOf(CodeInsightUtils.ElementKind.EXPRESSION),
{ elements, parent -> parent.getExtractionContainers(elements.size == 1, allContainersEnabled) }, { elements, parent -> parent.getExtractionContainers(elements.size == 1, allContainersEnabled) },
continuation continuation
) )
@@ -191,7 +191,7 @@ fun selectNewParameterContext(
editor = editor, editor = editor,
file = file, file = file,
title = "Introduce parameter to declaration", title = "Introduce parameter to declaration",
elementKind = CodeInsightUtils.ElementKind.EXPRESSION, elementKinds = listOf(CodeInsightUtils.ElementKind.EXPRESSION),
getContainers = { elements, parent -> getContainers = { elements, parent ->
val parents = parent.parents val parents = parent.parents
val stopAt = (parent.parents.zip(parent.parents.drop(1))) val stopAt = (parent.parents.zip(parent.parents.drop(1)))
@@ -72,7 +72,7 @@ class KotlinIntroducePropertyHandler(
editor, editor,
file, file,
"Select target code block", "Select target code block",
CodeInsightUtils.ElementKind.EXPRESSION, listOf(CodeInsightUtils.ElementKind.EXPRESSION),
{ elements, parent -> { elements, parent ->
parent.getExtractionContainers(strict = true, includeAll = true).filter { it is KtClassBody || (it is KtFile && !it.isScript) } parent.getExtractionContainers(strict = true, includeAll = true).filter { it is KtClassBody || (it is KtFile && !it.isScript) }
}, },
@@ -24,7 +24,7 @@ import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.refactoring.RefactoringActionHandler import com.intellij.refactoring.RefactoringActionHandler
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils.ElementKind.TYPE_ELEMENT
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers
import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractIntroduceAction import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractIntroduceAction
@@ -47,7 +47,7 @@ object KotlinIntroduceTypeAliasHandler : RefactoringActionHandler {
editor, editor,
file, file,
"Select target code block", "Select target code block",
CodeInsightUtils.ElementKind.TYPE_ELEMENT, listOf(TYPE_ELEMENT),
{ elements, parent -> parent.getExtractionContainers(strict = true, includeAll = true) }, { elements, parent -> parent.getExtractionContainers(strict = true, includeAll = true) },
continuation continuation
) )
@@ -45,7 +45,7 @@ fun selectElementsWithTargetSibling(
editor: Editor, editor: Editor,
file: KtFile, file: KtFile,
title: String, title: String,
elementKind: CodeInsightUtils.ElementKind, elementKinds: Collection<CodeInsightUtils.ElementKind>,
getContainers: (elements: List<PsiElement>, commonParent: PsiElement) -> List<PsiElement>, getContainers: (elements: List<PsiElement>, commonParent: PsiElement) -> List<PsiElement>,
continuation: (elements: List<PsiElement>, targetSibling: PsiElement) -> Unit continuation: (elements: List<PsiElement>, targetSibling: PsiElement) -> Unit
) { ) {
@@ -68,7 +68,7 @@ fun selectElementsWithTargetSibling(
continuation(elements, outermostParent) continuation(elements, outermostParent)
} }
selectElementsWithTargetParent(operationName, editor, file, title, elementKind, getContainers, ::onSelectionComplete) selectElementsWithTargetParent(operationName, editor, file, title, elementKinds, getContainers, ::onSelectionComplete)
} }
fun selectElementsWithTargetParent( fun selectElementsWithTargetParent(
@@ -76,7 +76,7 @@ fun selectElementsWithTargetParent(
editor: Editor, editor: Editor,
file: KtFile, file: KtFile,
title: String, title: String,
elementKind: CodeInsightUtils.ElementKind, elementKinds: Collection<CodeInsightUtils.ElementKind>,
getContainers: (elements: List<PsiElement>, commonParent: PsiElement) -> List<PsiElement>, getContainers: (elements: List<PsiElement>, commonParent: PsiElement) -> List<PsiElement>,
continuation: (elements: List<PsiElement>, targetParent: PsiElement) -> Unit continuation: (elements: List<PsiElement>, targetParent: PsiElement) -> Unit
) { ) {
@@ -109,27 +109,26 @@ fun selectElementsWithTargetParent(
val startOffset = editor.selectionModel.selectionStart val startOffset = editor.selectionModel.selectionStart
val endOffset = editor.selectionModel.selectionEnd val endOffset = editor.selectionModel.selectionEnd
val elements = CodeInsightUtils.findElements(file, startOffset, endOffset, elementKind) val elements = elementKinds.flatMap { CodeInsightUtils.findElements(file, startOffset, endOffset, it).toList() }
if (elements.isEmpty()) { if (elements.isEmpty()) {
val messageKey = when (elementKind) { return when (elementKinds.singleOrNull()) {
CodeInsightUtils.ElementKind.EXPRESSION -> "cannot.refactor.no.expression" CodeInsightUtils.ElementKind.EXPRESSION -> showErrorHintByKey("cannot.refactor.no.expression")
CodeInsightUtils.ElementKind.TYPE_ELEMENT -> "cannot.refactor.no.type" CodeInsightUtils.ElementKind.TYPE_ELEMENT -> showErrorHintByKey("cannot.refactor.no.type")
else -> showErrorHint(file.project, editor, "Refactoring can't be performed on the selected code element", title)
} }
showErrorHintByKey(messageKey)
return
} }
selectTargetContainer(elements.toList()) selectTargetContainer(elements)
} }
fun selectSingleElement() { fun selectSingleElement() {
KotlinRefactoringUtil.selectElement(editor, file, false, elementKind) { expr -> KotlinRefactoringUtil.selectElement(editor, file, false, elementKinds) { expr ->
if (expr != null) { if (expr != null) {
selectTargetContainer(listOf(expr)) selectTargetContainer(listOf(expr))
} }
else { else {
if (!editor.selectionModel.hasSelection()) { if (!editor.selectionModel.hasSelection()) {
if (elementKind == CodeInsightUtils.ElementKind.EXPRESSION) { if (elementKinds.singleOrNull() == CodeInsightUtils.ElementKind.EXPRESSION) {
val elementAtCaret = file.findElementAt(editor.caretModel.offset) val elementAtCaret = file.findElementAt(editor.caretModel.offset)
elementAtCaret?.getParentOfTypeAndBranch<KtProperty> { nameIdentifier }?.let { elementAtCaret?.getParentOfTypeAndBranch<KtProperty> { nameIdentifier }?.let {
return@selectElement selectTargetContainer(listOf(it)) return@selectElement selectTargetContainer(listOf(it))
@@ -719,7 +719,9 @@ object KotlinIntroduceVariableHandler : RefactoringActionHandler {
if (file !is KtFile) return if (file !is KtFile) return
try { try {
KotlinRefactoringUtil.selectElement(editor, file, CodeInsightUtils.ElementKind.EXPRESSION) { doRefactoring(project, editor, it as KtExpression?, null, null) } KotlinRefactoringUtil.selectElement(editor, file, listOf(CodeInsightUtils.ElementKind.EXPRESSION)) {
doRefactoring(project, editor, it as KtExpression?, null, null)
}
} }
catch (e: KotlinRefactoringUtil.IntroduceRefactoringException) { catch (e: KotlinRefactoringUtil.IntroduceRefactoringException) {
showErrorHint(project, editor, e.message!!) showErrorHint(project, editor, e.message!!)
@@ -1 +1 @@
Cannot perform refactoring without a type Refactoring can't be performed on the selected code element
@@ -25,6 +25,8 @@ import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtil;
import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.test.KotlinTestUtils; import org.jetbrains.kotlin.test.KotlinTestUtils;
import java.util.Collections;
public abstract class AbstractExpressionSelectionTest extends LightCodeInsightTestCase { public abstract class AbstractExpressionSelectionTest extends LightCodeInsightTestCase {
public void doTestExpressionSelection(@NotNull String path) throws Exception { public void doTestExpressionSelection(@NotNull String path) throws Exception {
@@ -32,13 +34,18 @@ public abstract class AbstractExpressionSelectionTest extends LightCodeInsightTe
final String expectedExpression = KotlinTestUtils.getLastCommentInFile((KtFile) getFile()); final String expectedExpression = KotlinTestUtils.getLastCommentInFile((KtFile) getFile());
try { try {
KotlinRefactoringUtil.selectElement(getEditor(), (KtFile) getFile(), CodeInsightUtils.ElementKind.EXPRESSION, new KotlinRefactoringUtil.SelectElementCallback() { KotlinRefactoringUtil.selectElement(
@Override getEditor(),
public void run(@Nullable PsiElement element) { (KtFile) getFile(),
assertNotNull("Selected expression mustn't be null", element); Collections.singletonList(CodeInsightUtils.ElementKind.EXPRESSION),
assertEquals(expectedExpression, element.getText()); new KotlinRefactoringUtil.SelectElementCallback() {
} @Override
}); public void run(@Nullable PsiElement element) {
assertNotNull("Selected expression mustn't be null", element);
assertEquals(expectedExpression, element.getText());
}
}
);
} }
catch (KotlinRefactoringUtil.IntroduceRefactoringException e) { catch (KotlinRefactoringUtil.IntroduceRefactoringException e) {
assertEquals(expectedExpression, ""); assertEquals(expectedExpression, "");
@@ -114,7 +114,7 @@ abstract class AbstractExtractionTest() : KotlinLightCodeInsightFixtureTestCase(
with (handler) { with (handler) {
val target = (file as KtFile).findElementByCommentPrefix("// TARGET:") as? KtNamedDeclaration val target = (file as KtFile).findElementByCommentPrefix("// TARGET:") as? KtNamedDeclaration
if (target != null) { if (target != null) {
KotlinRefactoringUtil.selectElement(fixture.getEditor(), file, true, CodeInsightUtils.ElementKind.EXPRESSION) { element -> KotlinRefactoringUtil.selectElement(fixture.getEditor(), file, true, listOf(CodeInsightUtils.ElementKind.EXPRESSION)) { element ->
invoke(fixture.getProject(), fixture.getEditor(), element as KtExpression, target) invoke(fixture.getProject(), fixture.getEditor(), element as KtExpression, target)
} }
} }
@@ -90,7 +90,7 @@ class KotlinNameSuggesterTest : LightCodeInsightFixtureTestCase() {
if (withRuntime) { if (withRuntime) {
ConfigLibraryUtil.configureKotlinRuntimeAndSdk(myModule, PluginTestCaseBase.mockJdk()) ConfigLibraryUtil.configureKotlinRuntimeAndSdk(myModule, PluginTestCaseBase.mockJdk())
} }
KotlinRefactoringUtil.selectElement(myFixture.editor, file, CodeInsightUtils.ElementKind.EXPRESSION, object : KotlinRefactoringUtil.SelectElementCallback { KotlinRefactoringUtil.selectElement(myFixture.editor, file, listOf(CodeInsightUtils.ElementKind.EXPRESSION), object : KotlinRefactoringUtil.SelectElementCallback {
override fun run(element: PsiElement?) { override fun run(element: PsiElement?) {
val names = KotlinNameSuggester val names = KotlinNameSuggester
.suggestNamesByExpressionAndType(element as KtExpression, .suggestNamesByExpressionAndType(element as KtExpression,