Extract Function: UI and refactoring handler
This commit is contained in:
@@ -16,6 +16,14 @@
|
||||
name='com.intellij.refactoring.MultiFileTestCase.PerformAction void performAction(com.intellij.openapi.vfs.VirtualFile, com.intellij.openapi.vfs.VirtualFile) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item
|
||||
name='com.intellij.refactoring.RefactoringActionHandler void invoke(com.intellij.openapi.project.Project, com.intellij.openapi.editor.Editor, com.intellij.psi.PsiFile, com.intellij.openapi.actionSystem.DataContext) 1'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item
|
||||
name='com.intellij.refactoring.RefactoringActionHandler void invoke(com.intellij.openapi.project.Project, com.intellij.openapi.editor.Editor, com.intellij.psi.PsiFile, com.intellij.openapi.actionSystem.DataContext) 2'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='com.intellij.refactoring.RefactoringHelper void performOperation(com.intellij.openapi.project.Project, T) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.jet.lang.psi.*;
|
||||
import org.jetbrains.jet.plugin.refactoring.changeSignature.JetChangeSignatureHandler;
|
||||
import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractKotlinFunctionHandler;
|
||||
import org.jetbrains.jet.plugin.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler;
|
||||
import org.jetbrains.jet.plugin.refactoring.safeDelete.SafeDeletePackage;
|
||||
|
||||
@@ -38,6 +39,11 @@ public class JetRefactoringSupportProvider extends RefactoringSupportProvider {
|
||||
return new KotlinIntroduceVariableHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RefactoringActionHandler getExtractMethodHandler() {
|
||||
return new ExtractKotlinFunctionHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInplaceRenameAvailable(@NotNull PsiElement element, PsiElement context) {
|
||||
if (element instanceof JetProperty) {
|
||||
|
||||
@@ -366,8 +366,16 @@ public class JetRefactoringUtil {
|
||||
void run(@Nullable JetExpression expression);
|
||||
}
|
||||
|
||||
public static void selectExpression(
|
||||
@NotNull Editor editor,
|
||||
@NotNull PsiFile file,
|
||||
@NotNull SelectExpressionCallback callback) throws IntroduceRefactoringException {
|
||||
selectExpression(editor, file, true, callback);
|
||||
}
|
||||
|
||||
public static void selectExpression(@NotNull Editor editor,
|
||||
@NotNull PsiFile file,
|
||||
boolean failOnEmptySuggestion,
|
||||
@NotNull SelectExpressionCallback callback) throws IntroduceRefactoringException {
|
||||
if (editor.getSelectionModel().hasSelection()) {
|
||||
int selectionStart = editor.getSelectionModel().getSelectionStart();
|
||||
@@ -375,11 +383,11 @@ public class JetRefactoringUtil {
|
||||
String text = file.getText();
|
||||
while (selectionStart < selectionEnd && Character.isSpaceChar(text.charAt(selectionStart))) ++selectionStart;
|
||||
while (selectionStart < selectionEnd && Character.isSpaceChar(text.charAt(selectionEnd - 1))) --selectionEnd;
|
||||
callback.run(findExpression(file, selectionStart, selectionEnd));
|
||||
callback.run(findExpression(file, selectionStart, selectionEnd, failOnEmptySuggestion));
|
||||
}
|
||||
else {
|
||||
int offset = editor.getCaretModel().getOffset();
|
||||
smartSelectExpression(editor, file, offset, callback);
|
||||
smartSelectExpression(editor, file, offset, failOnEmptySuggestion, callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,11 +450,15 @@ public class JetRefactoringUtil {
|
||||
|
||||
private static void smartSelectExpression(
|
||||
@NotNull Editor editor, @NotNull PsiFile file, int offset,
|
||||
boolean failOnEmptySuggestion,
|
||||
@NotNull final SelectExpressionCallback callback) throws IntroduceRefactoringException {
|
||||
List<JetExpression> expressions = getSmartSelectSuggestions(file, offset);
|
||||
if (expressions.size() == 0) throw new IntroduceRefactoringException(JetRefactoringBundle.message("cannot.refactor.not.expression"));
|
||||
if (expressions.size() == 0) {
|
||||
if (failOnEmptySuggestion) throw new IntroduceRefactoringException(JetRefactoringBundle.message("cannot.refactor.not.expression"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (expressions.size() == 1) {
|
||||
if (expressions.size() == 1 || ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
callback.run(expressions.get(0));
|
||||
return;
|
||||
}
|
||||
@@ -509,12 +521,18 @@ public class JetRefactoringUtil {
|
||||
return expressionText;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static JetExpression findExpression(@NotNull PsiFile file, int startOffset, int endOffset) throws IntroduceRefactoringException {
|
||||
@Nullable
|
||||
private static JetExpression findExpression(
|
||||
@NotNull PsiFile file, int startOffset, int endOffset, boolean failOnNoExpression
|
||||
) throws IntroduceRefactoringException {
|
||||
JetExpression element = CodeInsightUtils.findExpression(file, startOffset, endOffset);
|
||||
if (element == null) {
|
||||
//todo: if it's infix expression => add (), then commit document then return new created expression
|
||||
throw new IntroduceRefactoringException(JetRefactoringBundle.message("cannot.refactor.not.expression"));
|
||||
|
||||
if (failOnNoExpression) {
|
||||
throw new IntroduceRefactoringException(JetRefactoringBundle.message("cannot.refactor.not.expression"));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* 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.extractFunction
|
||||
|
||||
import com.intellij.refactoring.RefactoringActionHandler
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.editor.ScrollType
|
||||
import org.jetbrains.jet.plugin.codeInsight.CodeInsightUtils
|
||||
import org.jetbrains.jet.plugin.refactoring.JetRefactoringUtil
|
||||
import com.intellij.refactoring.HelpID
|
||||
import org.jetbrains.jet.plugin.refactoring.JetRefactoringBundle
|
||||
import org.jetbrains.jet.plugin.refactoring.extractFunction.ui.KotlinExtractFunctionDialog
|
||||
import org.jetbrains.jet.lang.psi.JetFile
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import org.jetbrains.jet.lang.psi.JetElement
|
||||
import org.jetbrains.jet.plugin.refactoring.getAllExtractionContainers
|
||||
import com.intellij.refactoring.IntroduceTargetChooser
|
||||
import com.intellij.openapi.util.Pass
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import org.jetbrains.jet.lang.psi.JetPsiFactory
|
||||
import org.jetbrains.jet.lang.psi.psiUtil.getOutermostParentContainedIn
|
||||
import org.jetbrains.jet.plugin.refactoring.checkConflictsInteractively
|
||||
import org.jetbrains.jet.plugin.refactoring.executeWriteCommand
|
||||
import org.jetbrains.jet.plugin.util.MaybeError
|
||||
import org.jetbrains.jet.plugin.util.MaybeValue
|
||||
import org.jetbrains.jet.lang.psi.JetBlockExpression
|
||||
import org.jetbrains.jet.lang.psi.JetClassOrObject
|
||||
import org.jetbrains.jet.lang.psi.JetClassBody
|
||||
import org.jetbrains.jet.lang.psi.JetNamedFunction
|
||||
import org.jetbrains.jet.plugin.codeInsight.ShortenReferences
|
||||
import kotlin.test.fail
|
||||
import org.jetbrains.jet.lang.psi.JetDeclarationWithBody
|
||||
|
||||
public class ExtractKotlinFunctionHandler : RefactoringActionHandler {
|
||||
fun doInvoke(
|
||||
editor: Editor,
|
||||
file: JetFile,
|
||||
elements: List<PsiElement>,
|
||||
nextSibling: PsiElement
|
||||
) {
|
||||
val project = file.getProject()
|
||||
|
||||
val analysisResult = ExtractionData(file, elements, nextSibling).performAnalysis()
|
||||
if (analysisResult is MaybeError) {
|
||||
showErrorHint(project, editor, analysisResult.error)
|
||||
return
|
||||
}
|
||||
|
||||
val validationResult = (analysisResult as MaybeValue).value.validate()
|
||||
if (!project.checkConflictsInteractively(validationResult.conflicts)) return
|
||||
|
||||
val descriptor =
|
||||
if (ApplicationManager.getApplication()!!.isUnitTestMode()) {
|
||||
validationResult.descriptor
|
||||
}
|
||||
else {
|
||||
val dialog = KotlinExtractFunctionDialog(project, validationResult.descriptor)
|
||||
if (!dialog.showAndGet()) return
|
||||
|
||||
dialog.getCurrentDescriptor()
|
||||
}
|
||||
|
||||
project.executeWriteCommand(EXTRACT_FUNCTION) { descriptor.generateFunction() }
|
||||
}
|
||||
|
||||
override fun invoke(project: Project, editor: Editor, file: PsiFile, dataContext: DataContext?) {
|
||||
if (file !is JetFile) return
|
||||
|
||||
selectElements(editor, file) { (elements, targetNextSibling) ->
|
||||
doInvoke(editor, file, elements, targetNextSibling)
|
||||
}
|
||||
}
|
||||
|
||||
override fun invoke(project: Project, elements: Array<out PsiElement>, dataContext: DataContext?) {
|
||||
fail("Extract Function can only be invoked from editor")
|
||||
}
|
||||
}
|
||||
|
||||
private val EXTRACT_FUNCTION: String = JetRefactoringBundle.message("extract.function")!!
|
||||
|
||||
private fun showErrorHint(project: Project, editor: Editor, message: String) {
|
||||
CodeInsightUtils.showErrorHint(project, editor, message, EXTRACT_FUNCTION, HelpID.EXTRACT_METHOD)
|
||||
}
|
||||
|
||||
private fun showErrorHintByKey(project: Project, editor: Editor, key: String) {
|
||||
showErrorHint(project, editor, JetRefactoringBundle.message(key)!!)
|
||||
}
|
||||
|
||||
fun selectElements(
|
||||
editor: Editor,
|
||||
file: PsiFile,
|
||||
continuation: (elements: List<PsiElement>, targetNextSibling: PsiElement) -> Unit
|
||||
) {
|
||||
fun noExpressionError() {
|
||||
showErrorHintByKey(file.getProject(), editor, "cannot.refactor.no.expression")
|
||||
}
|
||||
|
||||
fun noContainerError() {
|
||||
showErrorHintByKey(file.getProject(), editor, "cannot.refactor.no.container")
|
||||
}
|
||||
|
||||
fun onSelectionComplete(parent: PsiElement, elements: List<PsiElement>, targetContainer: JetElement) {
|
||||
if (parent == targetContainer) {
|
||||
continuation(elements, elements.first!!)
|
||||
return
|
||||
}
|
||||
|
||||
val outermostParent = parent.getOutermostParentContainedIn(targetContainer)
|
||||
if (outermostParent == null) {
|
||||
noContainerError()
|
||||
return
|
||||
}
|
||||
|
||||
continuation(elements, outermostParent)
|
||||
}
|
||||
|
||||
fun selectTargetContainer(elements: List<PsiElement>) {
|
||||
val parent = PsiTreeUtil.findCommonParent(elements)
|
||||
?: throw AssertionError("Should have at least one parent: ${elements.makeString("\n")}")
|
||||
|
||||
val containers = parent.getAllExtractionContainers(elements.size == 1)
|
||||
if (containers.empty) {
|
||||
noContainerError()
|
||||
return
|
||||
}
|
||||
|
||||
if (ApplicationManager.getApplication()!!.isUnitTestMode()) {
|
||||
onSelectionComplete(parent, elements, containers[0])
|
||||
return
|
||||
}
|
||||
|
||||
IntroduceTargetChooser.showChooser(
|
||||
editor,
|
||||
containers,
|
||||
object: Pass<JetElement>() {
|
||||
override fun pass(targetContainer: JetElement?) {
|
||||
if (targetContainer == null) {
|
||||
noContainerError()
|
||||
return
|
||||
}
|
||||
|
||||
onSelectionComplete(parent, elements, targetContainer)
|
||||
}
|
||||
},
|
||||
{
|
||||
when (it) {
|
||||
is JetFile -> "File: ${it.getName()}"
|
||||
is JetBlockExpression -> {
|
||||
(it.getParent() as? JetDeclarationWithBody)?.getText() ?: "...${it.getStatements().first?.getText()}"
|
||||
}
|
||||
is JetClassBody -> {
|
||||
(it.getParent() as? JetClassOrObject)?.getText()
|
||||
}
|
||||
else -> it?.getText()
|
||||
}
|
||||
},
|
||||
"Select target code block"
|
||||
)
|
||||
}
|
||||
|
||||
fun selectMultipleExpressions() {
|
||||
val startOffset = editor.getSelectionModel().getSelectionStart()
|
||||
val endOffset = editor.getSelectionModel().getSelectionEnd()
|
||||
|
||||
val elements = CodeInsightUtils.findStatements(file, startOffset, endOffset)
|
||||
if (elements.isEmpty()) {
|
||||
noExpressionError()
|
||||
return
|
||||
}
|
||||
|
||||
selectTargetContainer(elements.toList())
|
||||
}
|
||||
|
||||
fun selectSingleExpression() {
|
||||
JetRefactoringUtil.selectExpression(editor, file, false) { expr ->
|
||||
if (expr != null) {
|
||||
selectTargetContainer(listOf(expr))
|
||||
}
|
||||
else {
|
||||
if (!editor.getSelectionModel().hasSelection()) {
|
||||
editor.getSelectionModel().selectLineAtCaret()
|
||||
}
|
||||
selectMultipleExpressions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE)
|
||||
selectSingleExpression()
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.jet.plugin.refactoring.extractFunction.ui.KotlinExtractFunctionDialog">
|
||||
<grid id="27dc6" binding="contentPane" layout-manager="GridLayoutManager" row-count="4" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="522" height="278"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<grid id="34b30" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<grid id="fa2d1" binding="inputParametersPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none" title="Parameters"/>
|
||||
<children/>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
<grid id="b45d1" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="-1" height="200"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<grid id="4266d" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none" title="Signature Preview"/>
|
||||
<children>
|
||||
<component id="7722b" class="org.jetbrains.jet.plugin.refactoring.extractFunction.ui.KotlinFunctionSignatureComponent" binding="signaturePreviewField" custom-create="true">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
|
||||
<minimum-size width="500" height="100"/>
|
||||
<preferred-size width="500" height="100"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value=""/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
<grid id="5fe96" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<enabled value="true"/>
|
||||
<visible value="true"/>
|
||||
</properties>
|
||||
<border type="empty"/>
|
||||
<children>
|
||||
<component id="d1d09" class="javax.swing.JLabel" binding="functionNameLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="&Name:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="52aa7" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<labelFor value="71ee7"/>
|
||||
<text value="&Visibility:"/>
|
||||
<visible value="true"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="bee3c" class="com.intellij.ui.EditorTextField" binding="functionNameField">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="71ee7" class="javax.swing.JComboBox" binding="visibilityBox">
|
||||
<constraints>
|
||||
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<model>
|
||||
<item value="private"/>
|
||||
<item value="protected"/>
|
||||
<item value="internal"/>
|
||||
<item value="public"/>
|
||||
</model>
|
||||
<visible value="true"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
<vspacer id="cd10f">
|
||||
<constraints>
|
||||
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* 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.extractFunction.ui;
|
||||
|
||||
import com.intellij.openapi.editor.event.DocumentAdapter;
|
||||
import com.intellij.openapi.editor.event.DocumentEvent;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.ui.EditorTextField;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.jet.lang.psi.JetClassOrObject;
|
||||
import org.jetbrains.jet.lang.psi.JetFile;
|
||||
import org.jetbrains.jet.plugin.refactoring.JetNameSuggester;
|
||||
import org.jetbrains.jet.plugin.refactoring.JetRefactoringBundle;
|
||||
import org.jetbrains.jet.plugin.refactoring.RefactoringPackage;
|
||||
import org.jetbrains.jet.plugin.refactoring.extractFunction.*;
|
||||
import org.jetbrains.jet.renderer.DescriptorRenderer;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class KotlinExtractFunctionDialog extends DialogWrapper {
|
||||
private JPanel contentPane;
|
||||
private JPanel inputParametersPanel;
|
||||
private JComboBox visibilityBox;
|
||||
private KotlinFunctionSignatureComponent signaturePreviewField;
|
||||
private EditorTextField functionNameField;
|
||||
private JLabel functionNameLabel;
|
||||
private KotlinParameterTablePanel parameterTablePanel;
|
||||
|
||||
private final Project project;
|
||||
|
||||
private final ExtractionDescriptor originalDescriptor;
|
||||
private ExtractionDescriptor currentDescriptor;
|
||||
|
||||
public KotlinExtractFunctionDialog(Project project, ExtractionDescriptor originalDescriptor) {
|
||||
super(project, true);
|
||||
|
||||
this.project = project;
|
||||
this.originalDescriptor = originalDescriptor;
|
||||
this.currentDescriptor = originalDescriptor;
|
||||
|
||||
setModal(true);
|
||||
setTitle(JetRefactoringBundle.message("extract.function"));
|
||||
init();
|
||||
update(false);
|
||||
}
|
||||
|
||||
private void createUIComponents() {
|
||||
this.signaturePreviewField = new KotlinFunctionSignatureComponent("", project);
|
||||
}
|
||||
|
||||
private boolean isVisibilitySectionAvailable() {
|
||||
PsiElement target = originalDescriptor.getExtractionData().getNextSibling().getParent();
|
||||
return target instanceof JetClassOrObject || target instanceof JetFile;
|
||||
}
|
||||
|
||||
private String getFunctionName() {
|
||||
return functionNameField.getText();
|
||||
}
|
||||
|
||||
private String getVisibility() {
|
||||
if (!isVisibilitySectionAvailable()) return "";
|
||||
|
||||
String value = (String) visibilityBox.getSelectedItem();
|
||||
return "internal".equals(value) ? "" : value;
|
||||
}
|
||||
|
||||
private boolean checkNames() {
|
||||
if (!JetNameSuggester.isIdentifier(getFunctionName())) return false;
|
||||
for (KotlinParameterTablePanel.ParameterInfo parameterInfo : parameterTablePanel.getParameterInfos()) {
|
||||
if (!JetNameSuggester.isIdentifier(parameterInfo.getName())) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void update(boolean recreateDescriptor) {
|
||||
if (recreateDescriptor) {
|
||||
this.currentDescriptor = createDescriptor();
|
||||
}
|
||||
|
||||
setOKActionEnabled(checkNames());
|
||||
signaturePreviewField.setText(
|
||||
ExtractFunctionPackage.getFunctionText(currentDescriptor, false, DescriptorRenderer.SOURCE_CODE_SHORT_NAMES_IN_TYPES)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
|
||||
functionNameLabel.setLabelFor(functionNameField);
|
||||
|
||||
functionNameField.setText(originalDescriptor.getName());
|
||||
functionNameField.addDocumentListener(
|
||||
new DocumentAdapter() {
|
||||
@Override
|
||||
public void documentChanged(DocumentEvent event) {
|
||||
update(true);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
boolean enableVisibility = isVisibilitySectionAvailable();
|
||||
visibilityBox.setEnabled(enableVisibility);
|
||||
if (enableVisibility) {
|
||||
String visibility = originalDescriptor.getVisibility();
|
||||
visibilityBox.setSelectedItem(visibility.isEmpty() ? "internal" : visibility);
|
||||
}
|
||||
|
||||
parameterTablePanel = new KotlinParameterTablePanel() {
|
||||
@Override
|
||||
protected void updateSignature() {
|
||||
KotlinExtractFunctionDialog.this.update(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnterAction() {
|
||||
doOKAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelAction() {
|
||||
doCancelAction();
|
||||
}
|
||||
};
|
||||
parameterTablePanel.init(originalDescriptor.getParameters());
|
||||
inputParametersPanel.add(parameterTablePanel);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doOKAction() {
|
||||
ExtractionDescriptorWithConflicts validationResult = ExtractFunctionPackage.validate(currentDescriptor);
|
||||
if (RefactoringPackage.checkConflictsInteractively(project, validationResult.getConflicts())) {
|
||||
super.doOKAction();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getPreferredFocusedComponent() {
|
||||
return functionNameField;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
return contentPane;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JComponent createContentPane() {
|
||||
return contentPane;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private ExtractionDescriptor createDescriptor() {
|
||||
List<KotlinParameterTablePanel.ParameterInfo> parameterInfos = parameterTablePanel.getParameterInfos();
|
||||
|
||||
Map<Parameter, Parameter> oldToNewParameters = ContainerUtil.newLinkedHashMap();
|
||||
for (KotlinParameterTablePanel.ParameterInfo parameterInfo : parameterInfos) {
|
||||
oldToNewParameters.put(parameterInfo.getOriginalParameter(), parameterInfo.toParameter());
|
||||
}
|
||||
|
||||
ControlFlow controlFlow = originalDescriptor.getControlFlow();
|
||||
if (controlFlow instanceof ParameterUpdate) {
|
||||
controlFlow = new ParameterUpdate(oldToNewParameters.get(((ParameterUpdate) controlFlow).getParameter()));
|
||||
}
|
||||
|
||||
Map<Integer, Replacement> replacementMap = ContainerUtil.newHashMap();
|
||||
for (Map.Entry<Integer, Replacement> e : originalDescriptor.getReplacementMap().entrySet()) {
|
||||
Integer offset = e.getKey();
|
||||
Replacement replacement = e.getValue();
|
||||
|
||||
if (replacement instanceof ParameterReplacement) {
|
||||
ParameterReplacement parameterReplacement = (ParameterReplacement) replacement;
|
||||
Parameter parameter = parameterReplacement.getParameter();
|
||||
|
||||
Parameter newParameter = oldToNewParameters.get(parameter);
|
||||
if (newParameter != null) {
|
||||
replacementMap.put(offset, parameterReplacement.copy(newParameter));
|
||||
}
|
||||
}
|
||||
else {
|
||||
replacementMap.put(offset, replacement);
|
||||
}
|
||||
}
|
||||
|
||||
return new ExtractionDescriptor(
|
||||
originalDescriptor.getExtractionData(),
|
||||
getFunctionName(),
|
||||
getVisibility(),
|
||||
ContainerUtil.newArrayList(oldToNewParameters.values()),
|
||||
originalDescriptor.getReceiverParameter(),
|
||||
replacementMap,
|
||||
controlFlow
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ExtractionDescriptor getCurrentDescriptor() {
|
||||
return currentDescriptor;
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.extractFunction.ui
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.jet.plugin.JetFileType
|
||||
import com.intellij.refactoring.ui.MethodSignatureComponent
|
||||
|
||||
public class KotlinFunctionSignatureComponent(
|
||||
signature: String, project: Project
|
||||
) : MethodSignatureComponent(signature, project, JetFileType.INSTANCE) {
|
||||
private val myFileName = "dummy." + JetFileType.INSTANCE.getDefaultExtension()
|
||||
|
||||
override fun getFileName(): String = myFileName
|
||||
}
|
||||
+338
@@ -0,0 +1,338 @@
|
||||
/*
|
||||
* 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.extractFunction.ui;
|
||||
|
||||
import com.intellij.ui.BooleanTableCellRenderer;
|
||||
import com.intellij.ui.TableUtil;
|
||||
import com.intellij.ui.ToolbarDecorator;
|
||||
import com.intellij.ui.table.JBTable;
|
||||
import com.intellij.util.ui.EditableModel;
|
||||
import kotlin.Function1;
|
||||
import kotlin.KotlinPackage;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.jet.plugin.refactoring.JetNameSuggester;
|
||||
import org.jetbrains.jet.plugin.refactoring.extractFunction.Parameter;
|
||||
import org.jetbrains.jet.renderer.DescriptorRenderer;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import javax.swing.table.TableCellEditor;
|
||||
import javax.swing.table.TableColumn;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.List;
|
||||
|
||||
public class KotlinParameterTablePanel extends JPanel {
|
||||
public static class ParameterInfo {
|
||||
private final Parameter originalParameter;
|
||||
private String name;
|
||||
private boolean enabled = true;
|
||||
|
||||
public ParameterInfo(Parameter originalParameter) {
|
||||
this.originalParameter = originalParameter;
|
||||
this.name = originalParameter.getName();
|
||||
}
|
||||
|
||||
public Parameter getOriginalParameter() {
|
||||
return originalParameter;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getTypeAsString() {
|
||||
return DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(getOriginalParameter().getParameterType());
|
||||
}
|
||||
|
||||
public Parameter toParameter() {
|
||||
return new Parameter(
|
||||
originalParameter.getArgumentText(),
|
||||
name,
|
||||
originalParameter.getMirrorVarName(),
|
||||
originalParameter.getParameterType(),
|
||||
originalParameter.getReceiverCandidate()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ParameterInfo> parameterInfos;
|
||||
|
||||
private JBTable myTable;
|
||||
private MyTableModel myTableModel;
|
||||
|
||||
public KotlinParameterTablePanel() {
|
||||
super(new BorderLayout());
|
||||
}
|
||||
|
||||
public void init(List<Parameter> parameters) {
|
||||
parameterInfos = KotlinPackage.map(
|
||||
parameters,
|
||||
new Function1<Parameter, ParameterInfo>() {
|
||||
@Override
|
||||
public ParameterInfo invoke(Parameter parameter) {
|
||||
return new ParameterInfo(parameter);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
myTableModel = new MyTableModel();
|
||||
myTable = new JBTable(myTableModel);
|
||||
DefaultCellEditor defaultEditor = (DefaultCellEditor) myTable.getDefaultEditor(Object.class);
|
||||
defaultEditor.setClickCountToStart(1);
|
||||
|
||||
myTable.setTableHeader(null);
|
||||
myTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
|
||||
TableColumn checkBoxColumn = myTable.getColumnModel().getColumn(MyTableModel.CHECKMARK_COLUMN);
|
||||
TableUtil.setupCheckboxColumn(checkBoxColumn);
|
||||
checkBoxColumn.setCellRenderer(
|
||||
new BooleanTableCellRenderer() {
|
||||
@NotNull
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(
|
||||
JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column
|
||||
) {
|
||||
Component rendererComponent = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
rendererComponent.setEnabled(KotlinParameterTablePanel.this.isEnabled());
|
||||
return rendererComponent;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
myTable.setPreferredScrollableViewportSize(new Dimension(250, myTable.getRowHeight() * 5));
|
||||
myTable.setShowGrid(false);
|
||||
myTable.setIntercellSpacing(new Dimension(0, 0));
|
||||
|
||||
@NonNls InputMap inputMap = myTable.getInputMap();
|
||||
@NonNls ActionMap actionMap = myTable.getActionMap();
|
||||
|
||||
// SPACE: toggle enable/disable
|
||||
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "enable_disable");
|
||||
actionMap.put("enable_disable", new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull ActionEvent e) {
|
||||
if (myTable.isEditing()) return;
|
||||
int[] rows = myTable.getSelectedRows();
|
||||
if (rows.length > 0) {
|
||||
boolean valueToBeSet = false;
|
||||
for (int row : rows) {
|
||||
if (!parameterInfos.get(row).isEnabled()) {
|
||||
valueToBeSet = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int row : rows) {
|
||||
parameterInfos.get(row).setEnabled(valueToBeSet);
|
||||
}
|
||||
myTableModel.fireTableRowsUpdated(rows[0], rows[rows.length - 1]);
|
||||
TableUtil.selectRows(myTable, rows);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// F2: edit parameter name
|
||||
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0), "edit_parameter_name");
|
||||
actionMap.put("edit_parameter_name", new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull ActionEvent e) {
|
||||
if (!myTable.isEditing()) {
|
||||
int row = myTable.getSelectedRow();
|
||||
if (row >= 0 && row < myTableModel.getRowCount()) {
|
||||
TableUtil.editCellAt(myTable, row, MyTableModel.PARAMETER_NAME_COLUMN);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// make ENTER work when the table has focus
|
||||
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "invoke_impl");
|
||||
actionMap.put("invoke_impl", new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull ActionEvent e) {
|
||||
TableCellEditor editor = myTable.getCellEditor();
|
||||
if (editor != null) {
|
||||
editor.stopCellEditing();
|
||||
}
|
||||
else {
|
||||
onEnterAction();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// make ESCAPE work when the table has focus
|
||||
actionMap.put("doCancel", new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull ActionEvent e) {
|
||||
TableCellEditor editor = myTable.getCellEditor();
|
||||
if (editor != null) {
|
||||
editor.stopCellEditing();
|
||||
}
|
||||
else {
|
||||
onCancelAction();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
JPanel listPanel = ToolbarDecorator.createDecorator(myTable).disableAddAction().disableRemoveAction().createPanel();
|
||||
add(listPanel, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
protected void updateSignature() {
|
||||
|
||||
}
|
||||
|
||||
protected void onEnterAction() {
|
||||
|
||||
}
|
||||
|
||||
protected void onCancelAction() {
|
||||
|
||||
}
|
||||
|
||||
private class MyTableModel extends AbstractTableModel implements EditableModel {
|
||||
public static final int CHECKMARK_COLUMN = 0;
|
||||
public static final int PARAMETER_NAME_COLUMN = 1;
|
||||
public static final int PARAMETER_TYPE_COLUMN = 2;
|
||||
|
||||
@Override
|
||||
public void addRow() {
|
||||
throw new IllegalAccessError("Not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRow(int index) {
|
||||
throw new IllegalAccessError("Not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exchangeRows(int oldIndex, int newIndex) {
|
||||
if (oldIndex < 0 || newIndex < 0) return;
|
||||
if (oldIndex >= parameterInfos.size() || newIndex >= parameterInfos.size()) return;
|
||||
|
||||
ParameterInfo old = parameterInfos.get(oldIndex);
|
||||
parameterInfos.set(oldIndex, parameterInfos.get(newIndex));
|
||||
parameterInfos.set(newIndex, old);
|
||||
|
||||
fireTableRowsUpdated(Math.min(oldIndex, newIndex), Math.max(oldIndex, newIndex));
|
||||
updateSignature();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canExchangeRows(int oldIndex, int newIndex) {
|
||||
if (oldIndex < 0 || newIndex < 0) return false;
|
||||
if (oldIndex >= parameterInfos.size() || newIndex >= parameterInfos.size()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return parameterInfos.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||
switch (columnIndex) {
|
||||
case CHECKMARK_COLUMN: {
|
||||
return parameterInfos.get(rowIndex).isEnabled();
|
||||
}
|
||||
case PARAMETER_NAME_COLUMN: {
|
||||
return parameterInfos.get(rowIndex).getName();
|
||||
}
|
||||
case PARAMETER_TYPE_COLUMN: {
|
||||
return parameterInfos.get(rowIndex).getTypeAsString();
|
||||
}
|
||||
}
|
||||
assert false;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
|
||||
switch (columnIndex) {
|
||||
case CHECKMARK_COLUMN: {
|
||||
parameterInfos.get(rowIndex).setEnabled((Boolean) aValue);
|
||||
fireTableRowsUpdated(rowIndex, rowIndex);
|
||||
myTable.getSelectionModel().setSelectionInterval(rowIndex, rowIndex);
|
||||
updateSignature();
|
||||
break;
|
||||
}
|
||||
case PARAMETER_NAME_COLUMN: {
|
||||
ParameterInfo info = parameterInfos.get(rowIndex);
|
||||
String name = (String) aValue;
|
||||
if (JetNameSuggester.isIdentifier(name)) {
|
||||
info.setName(name);
|
||||
}
|
||||
updateSignature();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
||||
switch (columnIndex) {
|
||||
case CHECKMARK_COLUMN:
|
||||
return isEnabled();
|
||||
case PARAMETER_NAME_COLUMN:
|
||||
return isEnabled() && parameterInfos.get(rowIndex).isEnabled();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Class getColumnClass(int columnIndex) {
|
||||
if (columnIndex == CHECKMARK_COLUMN) {
|
||||
return Boolean.class;
|
||||
}
|
||||
return super.getColumnClass(columnIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public List<ParameterInfo> getParameterInfos() {
|
||||
return KotlinPackage.filter(
|
||||
parameterInfos,
|
||||
new Function1<ParameterInfo, Boolean>() {
|
||||
@Override
|
||||
public Boolean invoke(ParameterInfo info) {
|
||||
return info.isEnabled();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -131,3 +131,33 @@ public inline fun JetFile.createTempCopy(textTransform: (String) -> String): Jet
|
||||
return tmpFile
|
||||
}
|
||||
|
||||
public fun PsiElement.getAllExtractionContainers(strict: Boolean): List<JetElement> {
|
||||
val containers = ArrayList<JetElement>()
|
||||
|
||||
var element: PsiElement? = if (strict) getParent() else this
|
||||
while (element != null) {
|
||||
when (element) {
|
||||
is JetBlockExpression, is JetClassBody, is JetFile -> containers.add(element as JetElement)
|
||||
}
|
||||
|
||||
element = element!!.getParent()
|
||||
}
|
||||
|
||||
return containers
|
||||
}
|
||||
|
||||
public fun Project.checkConflictsInteractively(conflicts: MultiMap<PsiElement, String>): Boolean {
|
||||
if (conflicts.isEmpty()) return true
|
||||
|
||||
if (ApplicationManager.getApplication()!!.isUnitTestMode()) throw ConflictsInTestsException(conflicts.values())
|
||||
|
||||
val dialog = ConflictsDialog(this, conflicts)
|
||||
dialog.show()
|
||||
return dialog.isOK()
|
||||
}
|
||||
|
||||
public fun Project.executeWriteCommand(name: String, command: () -> Unit) {
|
||||
CommandProcessor.getInstance()!!.executeCommand(
|
||||
this, { ApplicationManager.getApplication()!!.runWriteAction(command) }, name, null
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user