From 6201aa4fd90ab4ef948ca5ce55578ae07561cfe3 Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Thu, 30 Mar 2017 19:45:48 +0300 Subject: [PATCH] Re-parse after lambda was converted to block (KT-17156) #KT-17156 Fixed --- .../src/org/jetbrains/kotlin/KtNodeTypes.java | 66 ++++++++++++++++++- .../kotlin/idea/editor/LazyElementTypeTest.kt | 57 ++++++++++++++++ 2 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 idea/tests/org/jetbrains/kotlin/idea/editor/LazyElementTypeTest.kt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/KtNodeTypes.java b/compiler/frontend/src/org/jetbrains/kotlin/KtNodeTypes.java index 78e8f0a9633..a7d7bfb22b1 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/KtNodeTypes.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/KtNodeTypes.java @@ -22,9 +22,12 @@ import com.intellij.lang.PsiBuilder; import com.intellij.lang.PsiBuilderFactory; import com.intellij.lexer.Lexer; import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.IErrorCounterReparseableElementType; import com.intellij.psi.tree.IFileElementType; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.idea.KotlinLanguage; import org.jetbrains.kotlin.lexer.KotlinLexer; import org.jetbrains.kotlin.lexer.KtTokens; @@ -130,7 +133,8 @@ public interface KtNodeTypes { @Override public ASTNode parseContents(ASTNode chameleon) { Project project = chameleon.getPsi().getProject(); - PsiBuilder builder = PsiBuilderFactory.getInstance().createBuilder(project, chameleon, null, KotlinLanguage.INSTANCE, chameleon.getChars()); + PsiBuilder builder = PsiBuilderFactory.getInstance().createBuilder( + project, chameleon, null, KotlinLanguage.INSTANCE, chameleon.getChars()); return KotlinParser.parseLambdaExpression(builder).getFirstChildNode(); } @@ -139,6 +143,66 @@ public interface KtNodeTypes { return new KtLambdaExpression(text); } + @Override + public boolean isParsable(@Nullable ASTNode parent, CharSequence buffer, Language fileLanguage, Project project) { + return super.isParsable(parent, buffer, fileLanguage, project) && + !wasArrowMovedOrDeleted(parent, buffer); + } + + private boolean wasArrowMovedOrDeleted(@Nullable ASTNode parent, CharSequence buffer) { + if (parent == null) return false; + + PsiElement parentPsi = parent.getPsi(); + KtLambdaExpression[] lambdaExpressions = PsiTreeUtil.getChildrenOfType(parentPsi, KtLambdaExpression.class); + if (lambdaExpressions == null || lambdaExpressions.length != 1) return false; + + // Now works only when actual node can be spotted ambiguously. Need change in API. + KtLambdaExpression lambdaExpression = lambdaExpressions[0]; + KtFunctionLiteral literal = lambdaExpression.getFunctionLiteral(); + PsiElement arrow = literal.getArrow(); + + // No arrow in original node + if (arrow == null) return false; + + int arrowOffset = arrow.getStartOffsetInParent() + literal.getStartOffsetInParent(); + + Lexer oldLexer = new KotlinLexer(); + oldLexer.start(lambdaExpression.getText()); + + Lexer newLexer = new KotlinLexer(); + newLexer.start(buffer); + + while (true) { + IElementType oldType = oldLexer.getTokenType(); + if (oldType == null) break; // Didn't find an arrow token. Consider it as no arrow was present. + + IElementType newType = newLexer.getTokenType(); + if (newType == null) return true; // New text was finished before reaching arrow in old text + + if (newType != oldType) { + if (newType == KtTokens.WHITE_SPACE) { + newLexer.advance(); + continue; + } + else if (oldType == KtTokens.WHITE_SPACE) { + oldLexer.advance(); + continue; + } + + return true; // Arrow was moved or deleted + } + + if (oldType == KtTokens.ARROW && oldLexer.getCurrentPosition().getOffset() == arrowOffset) { + break; + } + + oldLexer.advance(); + newLexer.advance(); + } + + return false; + } + @Override public int getErrorsCount(CharSequence seq, Language fileLanguage, Project project) { Lexer lexer = new KotlinLexer(); diff --git a/idea/tests/org/jetbrains/kotlin/idea/editor/LazyElementTypeTest.kt b/idea/tests/org/jetbrains/kotlin/idea/editor/LazyElementTypeTest.kt new file mode 100644 index 00000000000..99d4d27755b --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/editor/LazyElementTypeTest.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2010-2017 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.editor + +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.testFramework.EditorTestUtil +import com.intellij.testFramework.LightPlatformTestCase +import com.intellij.testFramework.LightProjectDescriptor +import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCaseBase +import org.jetbrains.kotlin.psi.KtLambdaExpression +import org.junit.Assert + +class LazyElementTypeTest : KotlinLightCodeInsightFixtureTestCaseBase() { + fun testSplitArrow() = reparse("val t = { a: Int -> }", ' ') + fun testDeleteArrow() = reparse("val t = { a: Int -> }", EditorTestUtil.BACKSPACE_FAKE_CHAR) + + fun testReformatNearArrow() = noReparse("val t = { a: Int-> }", ' ') + fun testChangeAfterArrow() = noReparse("val t = { a: Int -> }", 'a') + fun testDeleteIrrelevantArrow() = noReparse("val t = { a: Int -> (1..3).filter { b -> b > 2 } }", EditorTestUtil.BACKSPACE_FAKE_CHAR) + fun testReformatNearLambdaStart() = noReparse("val t = {a: Int -> }", ' ') + fun testNoArrow() = noReparse("val t = { }", 'a') + + fun reparse(text: String, char: Char): Unit = doTest(text, char, true) + fun noReparse(text: String, char: Char): Unit = doTest(text, char, false) + + fun doTest(text: String, char: Char, reparse: Boolean) { + val file = myFixture.configureByText("a.kt", text.trimMargin()) + + val lambdaExpressionBefore = PsiTreeUtil.findChildOfType(file, KtLambdaExpression::class.java) + + EditorTestUtil.performTypingAction(myFixture.editor, char) + PsiDocumentManager.getInstance(LightPlatformTestCase.getProject()).commitDocument(myFixture.getDocument(file)) + + val lambdaExpressionAfter = PsiTreeUtil.findChildOfType(file, KtLambdaExpression::class.java) + + val actualReparse = lambdaExpressionAfter != lambdaExpressionBefore + + Assert.assertEquals("Lazy element behaviour was unexpected", reparse, actualReparse) + } + + override fun getProjectDescriptor() = LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR +} \ No newline at end of file