KT-7731 Completion: replacement of call with no parameters with property by Tab

#KT-7731 Fixed
This commit is contained in:
Valentin Kipyatkov
2015-05-14 19:26:34 +03:00
parent 8ea0441348
commit 858adfb2db
8 changed files with 121 additions and 37 deletions
@@ -95,8 +95,8 @@ public class KotlinCompletionContributor : CompletionContributor() {
// search expression to be replaced - go up while we are the first child of parent expression // search expression to be replaced - go up while we are the first child of parent expression
var expression = parent as JetExpression var expression = parent as JetExpression
parent = expression.getParent() parent = expression.getParent()
while (parent is JetExpression && parent!!.getFirstChild() == expression) { while (parent is JetExpression && parent.getFirstChild() == expression) {
expression = parent as JetExpression expression = parent
parent = expression.getParent() parent = expression.getParent()
} }
@@ -180,7 +180,7 @@ public class KotlinCompletionContributor : CompletionContributor() {
val text = builder.toString() val text = builder.toString()
val file = JetPsiFactory(tokenBefore.getProject()).createFile(text) val file = JetPsiFactory(tokenBefore.getProject()).createFile(text)
val declaration = file.getDeclarations().singleOrNull() ?: return null val declaration = file.getDeclarations().singleOrNull() ?: return null
if (declaration.getTextLength() != text.length) return null if (declaration.getTextLength() != text.length()) return null
val containsErrorElement = !PsiTreeUtil.processElements(file, PsiElementProcessor<PsiElement>{ it !is PsiErrorElement }) val containsErrorElement = !PsiTreeUtil.processElements(file, PsiElementProcessor<PsiElement>{ it !is PsiErrorElement })
return if (containsErrorElement) null else tail + "$" return if (containsErrorElement) null else tail + "$"
} }
@@ -384,7 +384,7 @@ public class KotlinCompletionContributor : CompletionContributor() {
JetTokens.LPAR -> balance++ JetTokens.LPAR -> balance++
JetTokens.RPAR -> balance-- JetTokens.RPAR -> balance--
} }
current = current!!.prevLeaf() current = current.prevLeaf()
} }
return balance return balance
} }
@@ -20,7 +20,6 @@ import com.intellij.codeInsight.AutoPopupController
import com.intellij.codeInsight.completion.InsertionContext import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.lookup.Lookup import com.intellij.codeInsight.lookup.Lookup
import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.openapi.editor.Document
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiDocumentManager
@@ -52,11 +51,6 @@ public abstract class KotlinCallableInsertHandler : BaseDeclarationInsertHandler
private fun addImport(context : InsertionContext, item : LookupElement) { private fun addImport(context : InsertionContext, item : LookupElement) {
PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments() PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments()
val startOffset = context.getStartOffset()
val element = context.getFile().findElementAt(startOffset)
if (element == null) return
val file = context.getFile() val file = context.getFile()
val o = item.getObject() val o = item.getObject()
if (file is JetFile && o is DeclarationDescriptorLookupObject) { if (file is JetFile && o is DeclarationDescriptorLookupObject) {
@@ -77,7 +71,30 @@ public abstract class KotlinCallableInsertHandler : BaseDeclarationInsertHandler
} }
} }
public object KotlinPropertyInsertHandler : KotlinCallableInsertHandler() public object KotlinPropertyInsertHandler : KotlinCallableInsertHandler() {
public override fun handleInsert(context: InsertionContext, item: LookupElement) {
super.handleInsert(context, item)
if (context.getCompletionChar() == Lookup.REPLACE_SELECT_CHAR) {
deleteEmptyParenthesis(context)
}
}
private fun deleteEmptyParenthesis(context: InsertionContext) {
val psiDocumentManager = PsiDocumentManager.getInstance(context.getProject())
psiDocumentManager.commitAllDocuments()
psiDocumentManager.doPostponedOperationsAndUnblockDocument(context.getDocument())
val offset = context.getTailOffset()
val document = context.getDocument()
val chars = document.getCharsSequence()
val lParenOffset = chars.indexOfSkippingSpace('(', offset) ?: return
val rParenOffset = chars.indexOfSkippingSpace(')', lParenOffset + 1) ?: return
document.deleteString(offset, rParenOffset + 1)
}
}
public enum class CaretPosition { public enum class CaretPosition {
IN_BRACKETS IN_BRACKETS
@@ -143,8 +160,8 @@ public class KotlinFunctionInsertHandler(val caretPosition : CaretPosition, val
val closingBracket = if (braces) '}' else ')' val closingBracket = if (braces) '}' else ')'
if (completionChar == Lookup.REPLACE_SELECT_CHAR) { if (completionChar == Lookup.REPLACE_SELECT_CHAR) {
val offset1 = skipSpaces(chars, offset) val offset1 = chars.skipSpaces(offset)
if (offset1 < document.getTextLength()) { if (offset1 < chars.length()) {
if (chars[offset1] == '<') { if (chars[offset1] == '<') {
PsiDocumentManager.getInstance(context.getProject()).commitDocument(document) PsiDocumentManager.getInstance(context.getProject()).commitDocument(document)
val token = context.getFile().findElementAt(offset1)!! val token = context.getFile().findElementAt(offset1)!!
@@ -158,9 +175,9 @@ public class KotlinFunctionInsertHandler(val caretPosition : CaretPosition, val
} }
} }
var openingBracketOffset = indexOfSkippingSpace(document, openingBracket, offset) var openingBracketOffset = chars.indexOfSkippingSpace(openingBracket, offset)
var inBracketsShift = 0 var inBracketsShift = 0
if (openingBracketOffset == -1) { if (openingBracketOffset == null) {
if (braces) { if (braces) {
if (completionChar == ' ' || completionChar == '{') { if (completionChar == ' ' || completionChar == '{') {
context.setAddCompletionChar(false) context.setAddCompletionChar(false)
@@ -180,13 +197,12 @@ public class KotlinFunctionInsertHandler(val caretPosition : CaretPosition, val
PsiDocumentManager.getInstance(context.getProject()).commitDocument(document) PsiDocumentManager.getInstance(context.getProject()).commitDocument(document)
} }
openingBracketOffset = indexOfSkippingSpace(document, openingBracket, offset) openingBracketOffset = chars.indexOfSkippingSpace(openingBracket, offset)!!
assert(openingBracketOffset != -1, "If there wasn't open bracket it should already have been inserted")
val closeBracketOffset = chars.indexOfSkippingSpace(closingBracket, openingBracketOffset + 1)
val closeBracketOffset = indexOfSkippingSpace(document, closingBracket, openingBracketOffset + 1)
val editor = context.getEditor() val editor = context.getEditor()
if (shouldPlaceCaretInBrackets(completionChar) || closeBracketOffset == null) {
if (shouldPlaceCaretInBrackets(completionChar) || closeBracketOffset == -1) {
editor.getCaretModel().moveToOffset(openingBracketOffset + 1 + inBracketsShift) editor.getCaretModel().moveToOffset(openingBracketOffset + 1 + inBracketsShift)
AutoPopupController.getInstance(context.getProject())?.autoPopupParameterInfo(editor, offsetElement) AutoPopupController.getInstance(context.getProject())?.autoPopupParameterInfo(editor, offsetElement)
} }
@@ -197,7 +213,7 @@ public class KotlinFunctionInsertHandler(val caretPosition : CaretPosition, val
PsiDocumentManager.getInstance(context.getProject()).commitDocument(document) PsiDocumentManager.getInstance(context.getProject()).commitDocument(document)
if (lambdaInfo != null && lambdaInfo.explicitParameters) { if (lambdaInfo != null && lambdaInfo.explicitParameters) {
insertLambdaTemplate(context, TextRange(openingBracketOffset, closeBracketOffset + 1), lambdaInfo.lambdaType) insertLambdaTemplate(context, TextRange(openingBracketOffset, closeBracketOffset!! + 1), lambdaInfo.lambdaType)
} }
} }
@@ -211,22 +227,8 @@ public class KotlinFunctionInsertHandler(val caretPosition : CaretPosition, val
public val NO_PARAMETERS_HANDLER: KotlinFunctionInsertHandler = KotlinFunctionInsertHandler(CaretPosition.AFTER_BRACKETS, null) public val NO_PARAMETERS_HANDLER: KotlinFunctionInsertHandler = KotlinFunctionInsertHandler(CaretPosition.AFTER_BRACKETS, null)
public val WITH_PARAMETERS_HANDLER: KotlinFunctionInsertHandler = KotlinFunctionInsertHandler(CaretPosition.IN_BRACKETS, null) public val WITH_PARAMETERS_HANDLER: KotlinFunctionInsertHandler = KotlinFunctionInsertHandler(CaretPosition.IN_BRACKETS, null)
private fun indexOfSkippingSpace(document: Document, ch : Char, startIndex : Int) : Int { private fun isInsertSpacesInOneLineFunctionEnabled(project: Project)
val text = document.getCharsSequence() = CodeStyleSettingsManager.getSettings(project).getCustomSettings(javaClass<JetCodeStyleSettings>())!!.INSERT_WHITESPACES_IN_SIMPLE_ONE_LINE_METHOD
for (i in startIndex..text.length() - 1) {
val currentChar = text[i]
if (ch == currentChar) return i
if (currentChar != ' ' && currentChar != '\t') return -1
}
return -1
}
private fun skipSpaces(chars: CharSequence, index : Int) : Int
= (index..chars.length() - 1).firstOrNull { val c = chars[it]; c != ' ' && c != '\t' } ?: chars.length()
private fun isInsertSpacesInOneLineFunctionEnabled(project : Project)
= CodeStyleSettingsManager.getSettings(project)
.getCustomSettings(javaClass<JetCodeStyleSettings>())!!.INSERT_WHITESPACES_IN_SIMPLE_ONE_LINE_METHOD
} }
} }
@@ -0,0 +1,30 @@
/*
* 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.completion.handlers
fun CharSequence.indexOfSkippingSpace(c: Char, startIndex: Int): Int? {
for (i in startIndex..this.length() - 1) {
val currentChar = this[i]
if (c == currentChar) return i
if (currentChar != ' ' && currentChar != '\t') return null
}
return null
}
fun CharSequence.skipSpaces(index: Int): Int
= (index..length() - 1).firstOrNull { val c = this[it]; c != ' ' && c != '\t' } ?: this.length()
@@ -0,0 +1,10 @@
class C {
val bar: Int
}
fun foo(c: C) {
val v = c.<caret>getBar()
}
// ELEMENT: bar
// CHAR: '\t'
@@ -0,0 +1,10 @@
class C {
val bar: Int
}
fun foo(c: C) {
val v = c.bar<caret>
}
// ELEMENT: bar
// CHAR: '\t'
@@ -0,0 +1,10 @@
class C {
val bar: Int
}
fun foo(c: C) {
val v = c.<caret>getBar(0)
}
// ELEMENT: bar
// CHAR: '\t'
@@ -0,0 +1,10 @@
class C {
val bar: Int
}
fun foo(c: C) {
val v = c.bar<caret>(0)
}
// ELEMENT: bar
// CHAR: '\t'
@@ -101,6 +101,18 @@ public class BasicCompletionHandlerTestGenerated extends AbstractBasicCompletion
doTest(fileName); doTest(fileName);
} }
@TestMetadata("ReplaceFunctionCallByProperty.kt")
public void testReplaceFunctionCallByProperty() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/ReplaceFunctionCallByProperty.kt");
doTest(fileName);
}
@TestMetadata("ReplaceFunctionCallByPropertyArgs.kt")
public void testReplaceFunctionCallByPropertyArgs() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/ReplaceFunctionCallByPropertyArgs.kt");
doTest(fileName);
}
@TestMetadata("SecondTypeArg.kt") @TestMetadata("SecondTypeArg.kt")
public void testSecondTypeArg() throws Exception { public void testSecondTypeArg() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/SecondTypeArg.kt"); String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/SecondTypeArg.kt");