Extract Function: UI and refactoring handler

This commit is contained in:
Alexey Sedunov
2014-04-10 19:58:24 +04:00
parent 424c5f296f
commit 1fe6df268a
9 changed files with 985 additions and 7 deletions
@@ -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;
}
@@ -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()
}
@@ -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="&amp;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="&amp;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>
@@ -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;
}
}
@@ -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
}
@@ -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
)
}