diff --git a/annotations/com/intellij/refactoring/annotations.xml b/annotations/com/intellij/refactoring/annotations.xml
index df013310544..f9e6d56b074 100644
--- a/annotations/com/intellij/refactoring/annotations.xml
+++ b/annotations/com/intellij/refactoring/annotations.xml
@@ -16,6 +16,14 @@
name='com.intellij.refactoring.MultiFileTestCase.PerformAction void performAction(com.intellij.openapi.vfs.VirtualFile, com.intellij.openapi.vfs.VirtualFile) 0'>
+ -
+
+
+ -
+
+
-
diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringSupportProvider.java b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringSupportProvider.java
index 96adb04ede1..e800a1f7578 100644
--- a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringSupportProvider.java
+++ b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringSupportProvider.java
@@ -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) {
diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringUtil.java b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringUtil.java
index d09a5f6aa8d..b8ba93efa32 100644
--- a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringUtil.java
+++ b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringUtil.java
@@ -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 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;
}
diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractKotlinFunctionHandler.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractKotlinFunctionHandler.kt
new file mode 100644
index 00000000000..044e93fcd73
--- /dev/null
+++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractKotlinFunctionHandler.kt
@@ -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,
+ 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, 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, 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, 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) {
+ 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() {
+ 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()
+}
diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinExtractFunctionDialog.form b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinExtractFunctionDialog.form
new file mode 100644
index 00000000000..85baf2a39b5
--- /dev/null
+++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinExtractFunctionDialog.form
@@ -0,0 +1,122 @@
+
+
diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinExtractFunctionDialog.java b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinExtractFunctionDialog.java
new file mode 100644
index 00000000000..a8219aa044a
--- /dev/null
+++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinExtractFunctionDialog.java
@@ -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 parameterInfos = parameterTablePanel.getParameterInfos();
+
+ Map 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 replacementMap = ContainerUtil.newHashMap();
+ for (Map.Entry 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;
+ }
+}
diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinFunctionSignatureComponent.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinFunctionSignatureComponent.kt
new file mode 100644
index 00000000000..6bdbf3f2ff2
--- /dev/null
+++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinFunctionSignatureComponent.kt
@@ -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
+}
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinParameterTablePanel.java b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinParameterTablePanel.java
new file mode 100644
index 00000000000..119c3cb47f2
--- /dev/null
+++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinParameterTablePanel.java
@@ -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 parameterInfos;
+
+ private JBTable myTable;
+ private MyTableModel myTableModel;
+
+ public KotlinParameterTablePanel() {
+ super(new BorderLayout());
+ }
+
+ public void init(List parameters) {
+ parameterInfos = KotlinPackage.map(
+ parameters,
+ new Function1() {
+ @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 getParameterInfos() {
+ return KotlinPackage.filter(
+ parameterInfos,
+ new Function1() {
+ @Override
+ public Boolean invoke(ParameterInfo info) {
+ return info.isEnabled();
+ }
+ }
+ );
+ }
+}
diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt
index 489022fe5c6..5ce2235a8e3 100644
--- a/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt
+++ b/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt
@@ -131,3 +131,33 @@ public inline fun JetFile.createTempCopy(textTransform: (String) -> String): Jet
return tmpFile
}
+public fun PsiElement.getAllExtractionContainers(strict: Boolean): List {
+ val containers = ArrayList()
+
+ 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): 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
+ )
+}