Introduce Refactorings: Implement trimming renderer for expression
chooser. Do not suggest parenthesized expressions #KT-9028 Fixed
This commit is contained in:
@@ -48,7 +48,6 @@ import org.jetbrains.kotlin.idea.caches.resolve.ResolutionUtils;
|
||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde;
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.IntroduceUtilKt;
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers;
|
||||
import org.jetbrains.kotlin.idea.util.string.StringUtilKt;
|
||||
import org.jetbrains.kotlin.psi.*;
|
||||
import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt;
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer;
|
||||
@@ -378,7 +377,10 @@ public class KotlinRefactoringUtil {
|
||||
if (element instanceof KtExpression && !(element instanceof KtStatementExpression)) {
|
||||
boolean addExpression = true;
|
||||
|
||||
if (KtPsiUtil.isLabelIdentifierExpression(element)) {
|
||||
if (element instanceof KtParenthesizedExpression) {
|
||||
addExpression = false;
|
||||
}
|
||||
else if (KtPsiUtil.isLabelIdentifierExpression(element)) {
|
||||
addExpression = false;
|
||||
}
|
||||
else if (element.getParent() instanceof KtQualifiedExpression) {
|
||||
@@ -481,8 +483,12 @@ public class KotlinRefactoringUtil {
|
||||
|
||||
}
|
||||
|
||||
public static String getExpressionShortText(@NotNull KtElement element) { //todo: write appropriate implementation
|
||||
return StringUtilKt.collapseSpaces(StringUtil.shortenTextWithEllipsis(element.getText(), 53, 0));
|
||||
public static String getExpressionShortText(@NotNull KtElement element) {
|
||||
String text = ElementRenderingUtilsKt.renderTrimmed(element);
|
||||
int firstNewLinePos = text.indexOf('\n');
|
||||
String trimmedText = text.substring(0, firstNewLinePos != -1 ? firstNewLinePos : Math.min(100, text.length()));
|
||||
if (trimmedText.length() != text.length()) trimmedText += " ...";
|
||||
return trimmedText;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.kotlin.idea.refactoring
|
||||
|
||||
import com.intellij.psi.PsiComment
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiWhiteSpace
|
||||
import com.intellij.psi.impl.source.tree.LeafPsiElement
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.utils.ifEmpty
|
||||
|
||||
fun KtElement.renderTrimmed(): String {
|
||||
class Renderer : KtTreeVisitorVoid() {
|
||||
val builder = StringBuilder()
|
||||
|
||||
fun render(element: KtElement): String {
|
||||
element.accept(this)
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
private fun <T: PsiElement> Iterable<T>.join(separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "") {
|
||||
builder.append(prefix)
|
||||
var count = 0
|
||||
for (element in this) {
|
||||
if (++count > 1) builder.append(separator)
|
||||
element.accept(this@Renderer)
|
||||
}
|
||||
builder.append(postfix)
|
||||
}
|
||||
|
||||
// Whitespace and comments
|
||||
|
||||
override fun visitWhiteSpace(space: PsiWhiteSpace) {
|
||||
val text = space.text
|
||||
val newLine = text.indexOf('\n')
|
||||
if (newLine != 0) {
|
||||
builder.append(' ')
|
||||
}
|
||||
if (newLine >= 0) {
|
||||
builder.append(text.substring(newLine))
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitComment(comment: PsiComment) {
|
||||
|
||||
}
|
||||
|
||||
// Basic expressions
|
||||
|
||||
override fun visitParenthesizedExpression(expression: KtParenthesizedExpression) {
|
||||
builder.append('(')
|
||||
expression.expression?.accept(this)
|
||||
builder.append(')')
|
||||
}
|
||||
|
||||
override fun visitLabeledExpression(expression: KtLabeledExpression) {
|
||||
expression.baseExpression?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitAnnotatedExpression(expression: KtAnnotatedExpression) {
|
||||
expression.baseExpression?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitPrefixExpression(expression: KtPrefixExpression) {
|
||||
builder.append("${expression.operationReference.getReferencedName()}")
|
||||
expression.baseExpression?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitPostfixExpression(expression: KtPostfixExpression) {
|
||||
expression.baseExpression?.accept(this)
|
||||
builder.append("${expression.operationReference.getReferencedName()}")
|
||||
}
|
||||
|
||||
override fun visitBinaryExpression(expression: KtBinaryExpression) {
|
||||
expression.left?.accept(this)
|
||||
builder.append(" ${expression.operationReference.getReferencedName()} ")
|
||||
expression.right?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitBinaryWithTypeRHSExpression(expression: KtBinaryExpressionWithTypeRHS) {
|
||||
expression.left.accept(this)
|
||||
builder.append(" ${expression.operationReference.getReferencedName()} ")
|
||||
expression.right?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitIsExpression(expression: KtIsExpression) {
|
||||
expression.leftHandSide.accept(this)
|
||||
builder.append(" is ")
|
||||
expression.typeReference?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitArrayAccessExpression(expression: KtArrayAccessExpression) {
|
||||
expression.arrayExpression?.accept(this)
|
||||
expression.indexExpressions.join(builder, prefix = "[", postfix = "]")
|
||||
}
|
||||
|
||||
override fun visitCallExpression(expression: KtCallExpression) {
|
||||
expression.calleeExpression?.accept(this)
|
||||
expression.valueArgumentList?.accept(this)
|
||||
expression.functionLiteralArguments.forEach { builder.append("{...}") }
|
||||
}
|
||||
|
||||
override fun visitValueArgumentList(list: KtValueArgumentList) {
|
||||
builder.append(if (list.arguments.isEmpty()) "()" else "(...)")
|
||||
}
|
||||
|
||||
override fun visitQualifiedExpression(expression: KtQualifiedExpression) {
|
||||
expression.receiverExpression.accept(this)
|
||||
builder.append(expression.operationTokenNode.text)
|
||||
expression.selectorExpression?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitClassLiteralExpression(expression: KtClassLiteralExpression) {
|
||||
expression.typeReference?.accept(this)
|
||||
builder.append("::class")
|
||||
}
|
||||
|
||||
override fun visitCallableReferenceExpression(expression: KtCallableReferenceExpression) {
|
||||
expression.typeReference?.accept(this)
|
||||
builder.append("::")
|
||||
builder.append(expression.callableReference.getReferencedName())
|
||||
}
|
||||
|
||||
override fun visitThisExpression(expression: KtThisExpression) {
|
||||
builder.append("this")
|
||||
expression.getLabelName()?.let { builder.append("@$it") }
|
||||
}
|
||||
|
||||
override fun visitSuperExpression(expression: KtSuperExpression) {
|
||||
builder.append("super")
|
||||
expression.superTypeQualifier?.let {
|
||||
builder.append("<")
|
||||
it.accept(this)
|
||||
builder.append(">")
|
||||
}
|
||||
expression.getLabelName()?.let { builder.append("@$it") }
|
||||
}
|
||||
|
||||
// Control structures
|
||||
|
||||
override fun visitBreakExpression(expression: KtBreakExpression) {
|
||||
builder.append("break")
|
||||
expression.getLabelName()?.let { builder.append("@$it") }
|
||||
}
|
||||
|
||||
override fun visitContinueExpression(expression: KtContinueExpression) {
|
||||
builder.append("continue")
|
||||
expression.getLabelName()?.let { builder.append("@$it") }
|
||||
}
|
||||
|
||||
override fun visitThrowExpression(expression: KtThrowExpression) {
|
||||
builder.append("throw ")
|
||||
expression.thrownExpression?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitReturnExpression(expression: KtReturnExpression) {
|
||||
builder.append("return")
|
||||
expression.getLabelName()?.let { builder.append("@$it") }
|
||||
builder.append(' ')
|
||||
expression.returnedExpression?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitBlockExpression(expression: KtBlockExpression) {
|
||||
if (expression.parent is KtFunctionLiteral) {
|
||||
super.visitBlockExpression(expression)
|
||||
}
|
||||
else {
|
||||
builder.append("{...}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitIfExpression(expression: KtIfExpression) {
|
||||
builder.append("if (")
|
||||
expression.condition?.accept(this)
|
||||
builder.append(")")
|
||||
expression.then?.let {
|
||||
builder.append(' ')
|
||||
it.accept(this)
|
||||
}
|
||||
expression.`else`?.let {
|
||||
builder.append(" else ")
|
||||
it.accept(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitWhenExpression(expression: KtWhenExpression) {
|
||||
builder.append("when")
|
||||
expression.subjectExpression?.let {
|
||||
builder.append('(')
|
||||
it.accept(this)
|
||||
builder.append(')')
|
||||
}
|
||||
builder.append(" {...}")
|
||||
}
|
||||
|
||||
override fun visitForExpression(expression: KtForExpression) {
|
||||
builder.append("for (")
|
||||
(expression.loopParameter ?: expression.multiParameter)?.accept(this)
|
||||
builder.append(" in ")
|
||||
expression.loopRange?.accept(this)
|
||||
builder.append(")")
|
||||
expression.body?.let {
|
||||
builder.append(' ')
|
||||
it.accept(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitWhileExpression(expression: KtWhileExpression) {
|
||||
builder.append("while (")
|
||||
expression.condition?.accept(this)
|
||||
builder.append(")")
|
||||
expression.body?.let {
|
||||
builder.append(' ')
|
||||
it.accept(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitDoWhileExpression(expression: KtDoWhileExpression) {
|
||||
builder.append("do")
|
||||
expression.body?.let {
|
||||
builder.append(' ')
|
||||
it.accept(this)
|
||||
}
|
||||
builder.append(" while (")
|
||||
expression.condition?.accept(this)
|
||||
builder.append(")")
|
||||
}
|
||||
|
||||
override fun visitTryExpression(expression: KtTryExpression) {
|
||||
builder.append("try {...}")
|
||||
}
|
||||
|
||||
// Declarations
|
||||
|
||||
override fun visitNamedFunction(function: KtNamedFunction) {
|
||||
builder.append("fun")
|
||||
function.receiverTypeReference?.let {
|
||||
builder.append('.')
|
||||
it.accept(this)
|
||||
}
|
||||
function.name?.let { builder.append(" $it") }
|
||||
function.valueParameters.mapNotNull { it.typeReference }.joinTo(builder, prefix = "(", postfix = ")")
|
||||
function.equalsToken?.let { builder.append(" = ") }
|
||||
function.bodyExpression?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitPropertyAccessor(accessor: KtPropertyAccessor) {
|
||||
builder.append(if(accessor.isGetter) "get" else "set")
|
||||
builder.append("()")
|
||||
accessor.equalsToken?.let { builder.append(" = ") }
|
||||
accessor.bodyExpression?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitPrimaryConstructor(constructor: KtPrimaryConstructor) {
|
||||
constructor.valueParameters.mapNotNull { it.typeReference }.joinTo(builder, prefix = "(", postfix = ")")
|
||||
}
|
||||
|
||||
override fun visitSecondaryConstructor(constructor: KtSecondaryConstructor) {
|
||||
builder.append("constructor")
|
||||
constructor.valueParameters.mapNotNull { it.typeReference }.joinTo(builder, prefix = "(", postfix = ")")
|
||||
constructor.bodyExpression?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitClassOrObject(classOrObject: KtClassOrObject) {
|
||||
val keyword = when (classOrObject) {
|
||||
is KtClass -> classOrObject.getClassOrInterfaceKeyword()
|
||||
is KtObjectDeclaration -> classOrObject.getObjectKeyword()
|
||||
else -> return
|
||||
}
|
||||
keyword?.accept(this)
|
||||
|
||||
classOrObject.name?.let { builder.append(" $it") }
|
||||
classOrObject.getDelegationSpecifierList()?.accept(this)
|
||||
classOrObject.getBody()?.let { builder.append(" {...}") }
|
||||
}
|
||||
|
||||
override fun visitDelegationSpecifierList(list: KtDelegationSpecifierList) {
|
||||
list.delegationSpecifiers.ifEmpty { return }.join(builder, prefix = " : ")
|
||||
}
|
||||
|
||||
override fun visitDelegationByExpressionSpecifier(specifier: KtDelegatorByExpressionSpecifier) {
|
||||
specifier.typeReference?.accept(this)
|
||||
specifier.delegateExpression?.let {
|
||||
builder.append(" by ")
|
||||
it.accept(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitDelegationToSuperCallSpecifier(specifier: KtDelegatorToSuperCall) {
|
||||
specifier.typeReference?.accept(this)
|
||||
specifier.valueArgumentList?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitDelegationToSuperClassSpecifier(specifier: KtDelegatorToSuperClass) {
|
||||
specifier.typeReference?.accept(this)
|
||||
}
|
||||
|
||||
// Default
|
||||
|
||||
override fun visitElement(element: PsiElement) {
|
||||
if (element is LeafPsiElement) {
|
||||
builder.append(element.text)
|
||||
}
|
||||
else {
|
||||
super.visitElement(element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Renderer().render(this)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fun foo() {
|
||||
(<caret>1 + // abc
|
||||
2 /*def*/) * 3
|
||||
}
|
||||
|
||||
/*
|
||||
1
|
||||
1 + 2
|
||||
(1 + 2) * 3
|
||||
*/
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
interface T {
|
||||
fun foo(a: () -> Any): T = this
|
||||
}
|
||||
|
||||
fun foo(t: T) {
|
||||
t.foo { <caret>1 }
|
||||
.foo {2}
|
||||
.foo({ 3 })
|
||||
}
|
||||
|
||||
/*
|
||||
1
|
||||
{ 1 }
|
||||
t.foo{...}
|
||||
t.foo{...}.foo{...}
|
||||
t.foo{...}.foo{...}.foo(...)
|
||||
*/
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
interface T {
|
||||
fun foo(vararg a: Any): T = this
|
||||
}
|
||||
|
||||
fun foo(t: T) {
|
||||
t.foo(
|
||||
1 + 2,
|
||||
<caret>2, 3
|
||||
).foo()
|
||||
.foo(4)
|
||||
}
|
||||
|
||||
/*
|
||||
2
|
||||
t.foo(...)
|
||||
t.foo(...).foo()
|
||||
t.foo(...).foo().foo(...)
|
||||
*/
|
||||
@@ -0,0 +1,13 @@
|
||||
fun foo() {
|
||||
(<caret>1
|
||||
+ 2 -
|
||||
3)*(-
|
||||
4)
|
||||
}
|
||||
|
||||
/*
|
||||
1
|
||||
1 + 2
|
||||
1 + 2 - 3
|
||||
(1 + 2 - 3) * (-4)
|
||||
*/
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
interface T {
|
||||
fun foo(t: T) = t
|
||||
}
|
||||
|
||||
fun foo() {
|
||||
object : T {
|
||||
val x = 1
|
||||
}.foo(<caret>object : T {
|
||||
val x = 2
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
object : T {...}
|
||||
object : T {...}.foo(...)
|
||||
*/
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
fun foo() {
|
||||
val t = ((<caret>1 + 2) + 3) * 4
|
||||
}
|
||||
|
||||
/*
|
||||
1
|
||||
1 + 2
|
||||
(1 + 2) + 3
|
||||
((1 + 2) + 3) * 4
|
||||
*/
|
||||
+2
-2
@@ -4,6 +4,6 @@ fun main(args: Array<String>) {
|
||||
/*
|
||||
1.0
|
||||
1.0 + 1
|
||||
Math.pow(1.0 + 1, Math.abs(-42.0))
|
||||
print(Math.pow(1.0 + 1, Math.abs(-42.0)))
|
||||
Math.pow(...)
|
||||
print(...)
|
||||
*/
|
||||
|
||||
@@ -38,7 +38,7 @@ public abstract class AbstractSmartSelectionTest extends LightCodeInsightTestCas
|
||||
|
||||
List<String> textualExpressions = new ArrayList<String>();
|
||||
for (KtExpression expression : expressions) {
|
||||
textualExpressions.add(expression.getText());
|
||||
textualExpressions.add(KotlinRefactoringUtil.getExpressionShortText(expression));
|
||||
}
|
||||
assertEquals(expectedResultText, StringUtil.join(textualExpressions, "\n"));
|
||||
}
|
||||
|
||||
@@ -35,6 +35,12 @@ public class SmartSelectionTestGenerated extends AbstractSmartSelectionTest {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/smartSelection"), Pattern.compile("^([^\\.]+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("commentsAndExtraSpaces.kt")
|
||||
public void testCommentsAndExtraSpaces() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/smartSelection/commentsAndExtraSpaces.kt");
|
||||
doTestSmartSelection(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("labelledStatement.kt")
|
||||
public void testLabelledStatement() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/smartSelection/labelledStatement.kt");
|
||||
@@ -47,6 +53,36 @@ public class SmartSelectionTestGenerated extends AbstractSmartSelectionTest {
|
||||
doTestSmartSelection(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("lambdaCalls.kt")
|
||||
public void testLambdaCalls() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/smartSelection/lambdaCalls.kt");
|
||||
doTestSmartSelection(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("multilineCalls.kt")
|
||||
public void testMultilineCalls() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/smartSelection/multilineCalls.kt");
|
||||
doTestSmartSelection(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("multilineOperations.kt")
|
||||
public void testMultilineOperations() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/smartSelection/multilineOperations.kt");
|
||||
doTestSmartSelection(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("objectLiteral.kt")
|
||||
public void testObjectLiteral() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/smartSelection/objectLiteral.kt");
|
||||
doTestSmartSelection(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("parenthesized.kt")
|
||||
public void testParenthesized() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/smartSelection/parenthesized.kt");
|
||||
doTestSmartSelection(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/smartSelection/simple.kt");
|
||||
|
||||
Reference in New Issue
Block a user