From f1445ba635decd9d5c20a0a44b128a7f4a87a8c9 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Wed, 9 Jul 2014 16:11:03 +0400 Subject: [PATCH] Extract Function: Improve container selection UI --- .../codeInsight/unwrap/annotations.xml | 20 +++++ .../com/intellij/ide/util/annotations.xml | 3 + .../openapi/editor/colors/annotations.xml | 5 ++ .../jetbrains/jet/plugin/JetIconProvider.java | 3 + .../refactoring/JetRefactoringUtil.java | 7 +- .../ExtractKotlinFunctionHandler.kt | 83 +++++++++++++----- .../plugin/refactoring/jetRefactoringUtil.kt | 87 ++++++++++++++++++- .../jetbrains/jet/plugin/util/stringUtil.kt | 35 ++++++++ 8 files changed, 214 insertions(+), 29 deletions(-) create mode 100644 annotations/com/intellij/codeInsight/unwrap/annotations.xml create mode 100644 annotations/com/intellij/openapi/editor/colors/annotations.xml create mode 100644 idea/src/org/jetbrains/jet/plugin/util/stringUtil.kt diff --git a/annotations/com/intellij/codeInsight/unwrap/annotations.xml b/annotations/com/intellij/codeInsight/unwrap/annotations.xml new file mode 100644 index 00000000000..d1c4ac95a8d --- /dev/null +++ b/annotations/com/intellij/codeInsight/unwrap/annotations.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/annotations/com/intellij/ide/util/annotations.xml b/annotations/com/intellij/ide/util/annotations.xml index ff27947d8a8..1f6acdd9f8e 100644 --- a/annotations/com/intellij/ide/util/annotations.xml +++ b/annotations/com/intellij/ide/util/annotations.xml @@ -5,4 +5,7 @@ + + + \ No newline at end of file diff --git a/annotations/com/intellij/openapi/editor/colors/annotations.xml b/annotations/com/intellij/openapi/editor/colors/annotations.xml new file mode 100644 index 00000000000..bb3c07a1673 --- /dev/null +++ b/annotations/com/intellij/openapi/editor/colors/annotations.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/JetIconProvider.java b/idea/src/org/jetbrains/jet/plugin/JetIconProvider.java index acffc7a468d..c0ef085397d 100644 --- a/idea/src/org/jetbrains/jet/plugin/JetIconProvider.java +++ b/idea/src/org/jetbrains/jet/plugin/JetIconProvider.java @@ -123,6 +123,9 @@ public class JetIconProvider extends IconProvider { return JetIcons.FUNCTION; } } + + if (psiElement instanceof JetFunctionLiteral) return JetIcons.LAMBDA; + if (psiElement instanceof JetClass) { JetClass jetClass = (JetClass) psiElement; if (jetClass.isTrait()) { diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringUtil.java b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringUtil.java index f089e29246e..0345cdbc5b3 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringUtil.java +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringUtil.java @@ -57,6 +57,7 @@ import org.jetbrains.jet.plugin.JetBundle; import org.jetbrains.jet.plugin.codeInsight.CodeInsightUtils; import org.jetbrains.jet.plugin.codeInsight.DescriptorToDeclarationUtil; import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache; +import org.jetbrains.jet.plugin.util.UtilPackage; import org.jetbrains.jet.renderer.DescriptorRenderer; import javax.swing.*; @@ -509,11 +510,7 @@ public class JetRefactoringUtil { } public static String getExpressionShortText(@NotNull JetElement element) { //todo: write appropriate implementation - String expressionText = element.getText(); - if (expressionText.length() > 20) { - expressionText = expressionText.substring(0, 17) + "..."; - } - return expressionText; + return UtilPackage.collapseSpaces(StringUtil.shortenTextWithEllipsis(element.getText(), 53, 0)); } @Nullable diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractKotlinFunctionHandler.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractKotlinFunctionHandler.kt index 7113ba2184f..c5c42d6fa7b 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractKotlinFunctionHandler.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractKotlinFunctionHandler.kt @@ -32,8 +32,6 @@ 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.psiUtil.getOutermostParentContainedIn import org.jetbrains.jet.plugin.refactoring.checkConflictsInteractively @@ -56,6 +54,17 @@ import org.jetbrains.jet.lang.psi.JetProperty import org.jetbrains.jet.lang.psi.JetParameterList import org.jetbrains.jet.lang.psi.JetClassInitializer import org.jetbrains.jet.lang.psi.JetFunctionLiteral +import com.intellij.ide.util.PsiElementListCellRenderer +import com.intellij.openapi.util.text.StringUtil +import javax.swing.Icon +import org.jetbrains.jet.plugin.refactoring.getPsiElementPopup +import com.intellij.psi.PsiNamedElement +import org.jetbrains.jet.plugin.util.collapseSpaces +import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache +import org.jetbrains.jet.lang.resolve.BindingContext +import org.jetbrains.jet.lang.descriptors.FunctionDescriptor +import org.jetbrains.jet.renderer.DescriptorRenderer +import org.jetbrains.jet.lang.psi.JetPropertyAccessor import org.jetbrains.jet.lang.psi.JetClassOrObject public class ExtractKotlinFunctionHandler(public val allContainersEnabled: Boolean = false) : RefactoringActionHandler { @@ -222,33 +231,61 @@ fun selectElements( return } - IntroduceTargetChooser.showChooser( + getPsiElementPopup( editor, - containers, - object: Pass() { - override fun pass(targetContainer: JetElement?) { - if (targetContainer == null) { - noContainerError() - return + containers.copyToArray(), + object: PsiElementListCellRenderer() { + private fun JetElement.renderName(): String? { + if (this is JetPropertyAccessor) { + return (getParent() as JetProperty).renderName() + if (isGetter()) ".get" else ".set" } + return (this as? PsiNamedElement)?.getName() ?: "" + } - onSelectionComplete(parent, elements, targetContainer) + private fun JetElement.renderDeclaration(): String? { + val name = renderName() + val descriptor = AnalyzerFacadeWithCache.getContextForElement(this)[BindingContext.DECLARATION_TO_DESCRIPTOR, this] + val params = (descriptor as? FunctionDescriptor)?.let { descriptor -> + descriptor.getValueParameters() + .map { DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(it.getType()) } + .joinToString(", ", "(", ")") + } ?: "" + return "$name$params" } + + private fun JetElement.renderText(): String { + return StringUtil.shortenTextWithEllipsis(getText()!!.collapseSpaces(), 53, 0) + } + + private fun JetElement.getRepresentativeElement(): JetElement { + return when (this) { + is JetBlockExpression -> (getParent() as? JetDeclarationWithBody) ?: this + is JetClassBody -> getParent() as JetClassOrObject + else -> this + } + } + + override fun getElementText(element: JetElement): String? { + val representativeElement = element.getRepresentativeElement() + return when (representativeElement) { + is JetFile, is JetDeclarationWithBody, is JetClassOrObject -> representativeElement.renderDeclaration() + else -> representativeElement.renderText() + } + } + + override fun getContainerText(element: JetElement?, name: String?): String? = null + + override fun getIconFlags(): Int = 0 + + override fun getIcon(element: PsiElement?): Icon? = + super.getIcon((element as? JetElement)?.getRepresentativeElement()) }, + "Select target code block", { - 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" - ) + onSelectionComplete(parent, elements, it) + true + } + ).showInBestPositionFor(editor) } fun selectMultipleExpressions() { diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt index cf6139fa1ec..fad09f9baaf 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt @@ -46,7 +46,22 @@ import com.intellij.refactoring.ui.ConflictsDialog import com.intellij.util.containers.MultiMap import com.intellij.openapi.command.CommandProcessor import org.jetbrains.jet.lang.psi.codeFragmentUtil.skipVisibilityCheck -import com.intellij.openapi.ui.DialogWrapper +import com.intellij.ide.util.PsiElementListCellRenderer +import com.intellij.openapi.ui.popup.JBPopup +import com.intellij.openapi.ui.popup.PopupChooserBuilder +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.util.TextRange +import com.intellij.codeInsight.unwrap.RangeSplitter +import com.intellij.codeInsight.unwrap.UnwrapHandler +import com.intellij.openapi.editor.markup.TextAttributes +import com.intellij.openapi.editor.markup.HighlighterTargetArea +import com.intellij.openapi.editor.markup.RangeHighlighter +import java.util.Collections +import com.intellij.openapi.editor.colors.EditorColors +import com.intellij.openapi.editor.colors.EditorColorsManager +import com.intellij.ui.components.JBList +import com.intellij.openapi.ui.popup.JBPopupAdapter +import com.intellij.openapi.ui.popup.LightweightWindowEvent /** * Replace [[JetSimpleNameExpression]] (and its enclosing qualifier) with qualified element given by FqName @@ -165,3 +180,73 @@ public fun Project.executeWriteCommand(name: String, command: () -> Unit) { this, { ApplicationManager.getApplication()!!.runWriteAction(command) }, name, null ) } + +public fun getPsiElementPopup( + editor: Editor, + elements: Array, + renderer: PsiElementListCellRenderer, + title: String? = null, + processor: (T) -> Boolean): JBPopup { + val highlighter = SelectionAwareScopeHighlighter(editor) + + val list = JBList(elements.toList()) + list.setCellRenderer(renderer) + list.addListSelectionListener { e -> + highlighter.dropHighlight() + val index = list.getSelectedIndex() + if (index >= 0) { + highlighter.highlight(list.getModel()!!.getElementAt(index) as PsiElement) + } + } + + return with(PopupChooserBuilder(list)) { + title?.let { setTitle(it) } + renderer.installSpeedSearch(this, true) + setItemChoosenCallback { + for (element in list.getSelectedValues()) { + element.let { + [suppress("UNCHECKED_CAST")] + processor(it as T) + } + } + } + addListener(object: JBPopupAdapter() { + override fun onClosed(event: LightweightWindowEvent?) { + highlighter.dropHighlight(); + } + }) + + createPopup() + } +} + +public class SelectionAwareScopeHighlighter(val editor: Editor) { + private val highlighters = ArrayList() + + private fun addHighlighter(r: TextRange, attr: TextAttributes) { + highlighters.add( + editor.getMarkupModel().addRangeHighlighter( + r.getStartOffset(), + r.getEndOffset(), + UnwrapHandler.HIGHLIGHTER_LEVEL, + attr, + HighlighterTargetArea.EXACT_RANGE + ) + ) + } + + public fun highlight(wholeAffected: PsiElement) { + dropHighlight() + + val attributes = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES)!! + val selectedRange = with(editor.getSelectionModel()) { TextRange(getSelectionStart(), getSelectionEnd()) } + for (r in RangeSplitter.split(wholeAffected.getTextRange()!!, Collections.singletonList(selectedRange))) { + addHighlighter(r, attributes) + } + } + + public fun dropHighlight() { + highlighters.forEach { it.dispose() } + highlighters.clear() + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/util/stringUtil.kt b/idea/src/org/jetbrains/jet/plugin/util/stringUtil.kt new file mode 100644 index 00000000000..ed0121ad110 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/util/stringUtil.kt @@ -0,0 +1,35 @@ +/* + * 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.util + +fun String.collapseSpaces(): String { + val builder = StringBuilder() + var haveSpaces = false + for (c in this) { + if (c.isWhitespace()) { + haveSpaces = true + } + else { + if (haveSpaces) { + builder.append(" ") + haveSpaces = false + } + builder.append(c) + } + } + return builder.toString() +} \ No newline at end of file