Reformat completion.handlers

This commit is contained in:
Nikolay Krasko
2018-11-15 13:06:47 +03:00
parent 8b2fef3812
commit b1cf98d82a
6 changed files with 99 additions and 81 deletions
@@ -21,14 +21,15 @@ import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.psi.*
object CastReceiverInsertHandler {
fun postHandleInsert(context: InsertionContext, item: LookupElement) {
val expression = PsiTreeUtil.findElementOfClassAtOffset(context.file, context.startOffset, KtSimpleNameExpression::class.java, false)
val expression =
PsiTreeUtil.findElementOfClassAtOffset(context.file, context.startOffset, KtSimpleNameExpression::class.java, false)
val qualifiedExpression = PsiTreeUtil.getParentOfType(expression, KtQualifiedExpression::class.java, true)
if (qualifiedExpression != null) {
val receiver = qualifiedExpression.receiverExpression
@@ -36,7 +37,8 @@ object CastReceiverInsertHandler {
val descriptor = (item.`object` as? DeclarationLookupObject)?.descriptor as CallableDescriptor
val project = context.project
val thisObj = if (descriptor.extensionReceiverParameter != null) descriptor.extensionReceiverParameter else descriptor.dispatchReceiverParameter
val thisObj =
if (descriptor.extensionReceiverParameter != null) descriptor.extensionReceiverParameter else descriptor.dispatchReceiverParameter
val fqName = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(thisObj!!.type.constructor.declarationDescriptor!!)
val parentCast = KtPsiFactory(project).createExpression("(expr as $fqName)") as KtParenthesizedExpression
@@ -32,7 +32,7 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils
abstract class KotlinCallableInsertHandler(val callType: CallType<*>) : BaseDeclarationInsertHandler() {
companion object {
private val shortenReferences = ShortenReferences({ ShortenReferences.Options.DEFAULT.copy(dropBracesInStringTemplates = false)})
private val shortenReferences = ShortenReferences({ ShortenReferences.Options.DEFAULT.copy(dropBracesInStringTemplates = false) })
}
override fun handleInsert(context: InsertionContext, item: LookupElement) {
@@ -41,7 +41,7 @@ abstract class KotlinCallableInsertHandler(val callType: CallType<*>) : BaseDecl
addImport(context, item)
}
private fun addImport(context : InsertionContext, item : LookupElement) {
private fun addImport(context: InsertionContext, item: LookupElement) {
val psiDocumentManager = PsiDocumentManager.getInstance(context.project)
psiDocumentManager.commitAllDocuments()
@@ -53,11 +53,14 @@ abstract class KotlinCallableInsertHandler(val callType: CallType<*>) : BaseDecl
if (DescriptorUtils.isTopLevelDeclaration(descriptor) && !descriptor.isArtificialImportAliasedDescriptor) {
ImportInsertHelper.getInstance(context.project).importDescriptor(file, descriptor)
}
}
else if (callType == CallType.DEFAULT) {
} else if (callType == CallType.DEFAULT) {
if (descriptor.isArtificialImportAliasedDescriptor) return
val fqName = descriptor.importableFqName ?: return
context.document.replaceString(context.startOffset, context.tailOffset, fqName.render() + " ") // insert space after for correct parsing
context.document.replaceString(
context.startOffset,
context.tailOffset,
fqName.render() + " "
) // insert space after for correct parsing
psiDocumentManager.commitAllDocuments()
@@ -25,10 +25,10 @@ import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.completion.isAfterDot
import org.jetbrains.kotlin.idea.completion.isArtificialImportAliasedDescriptor
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject
import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
@@ -53,7 +53,7 @@ object KotlinClassifierInsertHandler : BaseDeclarationInsertHandler() {
val lookupObject = item.`object` as DeclarationLookupObject
if (lookupObject.descriptor?.isArtificialImportAliasedDescriptor == true) return // never need to insert import or use qualified name for import-aliased class
val qualifiedName = qualifiedName(lookupObject)
// first try to resolve short name for faster handling
@@ -62,7 +62,7 @@ object KotlinClassifierInsertHandler : BaseDeclarationInsertHandler() {
if (nameRef != null) {
val bindingContext = nameRef.analyze(BodyResolveMode.PARTIAL)
val target = bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, nameRef]
?: bindingContext[BindingContext.REFERENCE_TARGET, nameRef] as? ClassDescriptor
?: bindingContext[BindingContext.REFERENCE_TARGET, nameRef] as? ClassDescriptor
if (target != null && IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(target) == qualifiedName) return
}
@@ -71,8 +71,7 @@ object KotlinClassifierInsertHandler : BaseDeclarationInsertHandler() {
// we insert space so that any preceding spaces inserted by formatter on reference shortening are deleted
// (but not for annotations where spaces are not allowed after @)
if (isAnnotation) "" else " "
}
else {
} else {
"$;val v:" // if we have no reference in the current context we have a more complicated prefix to get one
}
val tempSuffix = ".xxx" // we add "xxx" after dot because of KT-9606
@@ -99,8 +98,7 @@ object KotlinClassifierInsertHandler : BaseDeclarationInsertHandler() {
private fun qualifiedName(lookupObject: DeclarationLookupObject): String {
return if (lookupObject.descriptor != null) {
IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(lookupObject.descriptor as ClassifierDescriptor)
}
else {
} else {
val qualifiedName = (lookupObject.psiElement as PsiClass).qualifiedName!!
if (FqNameUnsafe.isValid(qualifiedName)) FqNameUnsafe(qualifiedName).render() else qualifiedName
}
@@ -39,12 +39,12 @@ class GenerateLambdaInfo(val lambdaType: KotlinType, val explicitParameters: Boo
sealed class KotlinFunctionInsertHandler(callType: CallType<*>) : KotlinCallableInsertHandler(callType) {
class Normal(
callType: CallType<*>,
val inputTypeArguments: Boolean,
val inputValueArguments: Boolean,
val argumentText: String = "",
val lambdaInfo: GenerateLambdaInfo? = null,
val argumentsOnly: Boolean = false
callType: CallType<*>,
val inputTypeArguments: Boolean,
val inputValueArguments: Boolean,
val argumentText: String = "",
val lambdaInfo: GenerateLambdaInfo? = null,
val argumentsOnly: Boolean = false
) : KotlinFunctionInsertHandler(callType) {
init {
if (lambdaInfo != null) {
@@ -54,12 +54,12 @@ sealed class KotlinFunctionInsertHandler(callType: CallType<*>) : KotlinCallable
//TODO: add 'data' or special annotation when supported
fun copy(
callType: CallType<*> = this.callType,
inputTypeArguments: Boolean = this.inputTypeArguments,
inputValueArguments: Boolean = this.inputValueArguments,
argumentText: String = this.argumentText,
lambdaInfo: GenerateLambdaInfo? = this.lambdaInfo,
argumentsOnly: Boolean = this.argumentsOnly
callType: CallType<*> = this.callType,
inputTypeArguments: Boolean = this.inputTypeArguments,
inputValueArguments: Boolean = this.inputValueArguments,
argumentText: String = this.argumentText,
lambdaInfo: GenerateLambdaInfo? = this.lambdaInfo,
argumentsOnly: Boolean = this.argumentsOnly
) = Normal(callType, inputTypeArguments, inputValueArguments, argumentText, lambdaInfo, argumentsOnly)
override fun handleInsert(context: InsertionContext, item: LookupElement) {
@@ -81,7 +81,7 @@ sealed class KotlinFunctionInsertHandler(callType: CallType<*>) : KotlinCallable
addArguments(context, element)
}
private fun addArguments(context : InsertionContext, offsetElement : PsiElement) {
private fun addArguments(context: InsertionContext, offsetElement: PsiElement) {
val completionChar = context.completionChar
if (completionChar == '(') { //TODO: more correct behavior related to braces type
context.setAddCompletionChar(false)
@@ -98,7 +98,8 @@ sealed class KotlinFunctionInsertHandler(callType: CallType<*>) : KotlinCallable
val openingBracket = if (insertLambda) '{' else '('
val closingBracket = if (insertLambda) '}' else ')'
var insertTypeArguments = inputTypeArguments && (completionChar == '\n' || completionChar == '\r' || completionChar == Lookup.REPLACE_SELECT_CHAR)
var insertTypeArguments =
inputTypeArguments && (completionChar == '\n' || completionChar == '\r' || completionChar == Lookup.REPLACE_SELECT_CHAR)
if (completionChar == Lookup.REPLACE_SELECT_CHAR) {
val offset1 = chars.skipSpaces(offset)
@@ -144,12 +145,10 @@ sealed class KotlinFunctionInsertHandler(callType: CallType<*>) : KotlinCallable
if (isInsertSpacesInOneLineFunctionEnabled(project)) {
document.insertString(offset, " { }")
inBracketsShift = 1
}
else {
} else {
document.insertString(offset, " {}")
}
}
else {
} else {
document.insertString(offset, "()")
}
PsiDocumentManager.getInstance(project).commitDocument(document)
@@ -160,8 +159,15 @@ sealed class KotlinFunctionInsertHandler(callType: CallType<*>) : KotlinCallable
if (insertLambda && lambdaInfo!!.explicitParameters) {
val placeholderRange = TextRange(openingBracketOffset, closeBracketOffset!! + 1)
val explicitParameterTypes = LambdaSignatureTemplates.explicitParameterTypesRequired(context.file as KtFile, placeholderRange, lambdaInfo.lambdaType)
LambdaSignatureTemplates.insertTemplate(context, placeholderRange, lambdaInfo.lambdaType, explicitParameterTypes, signatureOnly = false)
val explicitParameterTypes =
LambdaSignatureTemplates.explicitParameterTypesRequired(context.file as KtFile, placeholderRange, lambdaInfo.lambdaType)
LambdaSignatureTemplates.insertTemplate(
context,
placeholderRange,
lambdaInfo.lambdaType,
explicitParameterTypes,
signatureOnly = false
)
return
}
@@ -176,8 +182,7 @@ sealed class KotlinFunctionInsertHandler(callType: CallType<*>) : KotlinCallable
if (!insertLambda) {
AutoPopupController.getInstance(project)?.autoPopupParameterInfo(editor, offsetElement)
}
}
else {
} else {
editor.caretModel.moveToOffset(closeBracketOffset + 1)
}
}
@@ -189,8 +194,8 @@ sealed class KotlinFunctionInsertHandler(callType: CallType<*>) : KotlinCallable
return inputValueArguments || lambdaInfo != null
}
private fun isInsertSpacesInOneLineFunctionEnabled(project: Project)
= CodeStyleSettingsManager.getSettings(project).getCustomSettings(KotlinCodeStyleSettings::class.java)!!.INSERT_WHITESPACES_IN_SIMPLE_ONE_LINE_METHOD
private fun isInsertSpacesInOneLineFunctionEnabled(project: Project) =
CodeStyleSettingsManager.getSettings(project).getCustomSettings(KotlinCodeStyleSettings::class.java)!!.INSERT_WHITESPACES_IN_SIMPLE_ONE_LINE_METHOD
}
object Infix : KotlinFunctionInsertHandler(CallType.INFIX) {
@@ -205,7 +210,7 @@ sealed class KotlinFunctionInsertHandler(callType: CallType<*>) : KotlinCallable
context.document.insertString(tailOffset, " ")
context.editor.caretModel.moveToOffset(tailOffset + 1)
}
}
}
class OnlyName(callType: CallType<*>) : KotlinFunctionInsertHandler(callType)
@@ -27,10 +27,12 @@ import org.jetbrains.kotlin.idea.completion.KEEP_OLD_ARGUMENT_LIST_ON_TAB_KEY
import org.jetbrains.kotlin.idea.completion.smart.SmartCompletion
import org.jetbrains.kotlin.idea.completion.tryGetOffset
class WithTailInsertHandler(val tailText: String,
val spaceBefore: Boolean,
val spaceAfter: Boolean,
val overwriteText: Boolean = true) : InsertHandler<LookupElement> {
class WithTailInsertHandler(
val tailText: String,
val spaceBefore: Boolean,
val spaceAfter: Boolean,
val overwriteText: Boolean = true
) : InsertHandler<LookupElement> {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
item.handleInsert(context)
postHandleInsert(context, item)
@@ -49,7 +51,7 @@ class WithTailInsertHandler(val tailText: String,
var tailOffset = context.tailOffset
if (completionChar == Lookup.REPLACE_SELECT_CHAR && item.getUserData(KEEP_OLD_ARGUMENT_LIST_ON_TAB_KEY) != null) {
context.offsetMap.tryGetOffset(SmartCompletion.OLD_ARGUMENTS_REPLACEMENT_OFFSET)
?.let { tailOffset = it }
?.let { tailOffset = it }
}
val moveCaret = context.editor.caretModel.offset == tailOffset
@@ -90,21 +90,21 @@ fun CharSequence.indexOfSkippingSpace(c: Char, startIndex: Int): Int? {
return null
}
fun CharSequence.skipSpaces(index: Int): Int
= (index..length - 1).firstOrNull { val c = this[it]; c != ' ' && c != '\t' } ?: this.length
fun CharSequence.skipSpaces(index: Int): Int = (index..length - 1).firstOrNull { val c = this[it]; c != ' ' && c != '\t' } ?: this.length
fun CharSequence.skipSpacesAndLineBreaks(index: Int): Int
= (index..length - 1).firstOrNull { val c = this[it]; c != ' ' && c != '\t' && c != '\n' && c != '\r' } ?: this.length
fun CharSequence.skipSpacesAndLineBreaks(index: Int): Int =
(index..length - 1).firstOrNull { val c = this[it]; c != ' ' && c != '\t' && c != '\n' && c != '\r' } ?: this.length
fun CharSequence.isCharAt(offset: Int, c: Char) = offset < length && this[offset] == c
fun Document.isTextAt(offset: Int, text: String) = offset + text.length <= textLength && getText(TextRange(offset, offset + text.length)) == text
fun Document.isTextAt(offset: Int, text: String) =
offset + text.length <= textLength && getText(TextRange(offset, offset + text.length)) == text
fun createKeywordConstructLookupElement(
project: Project,
keyword: String,
fileTextToReformat: String,
trimSpacesAroundCaret: Boolean = false
project: Project,
keyword: String,
fileTextToReformat: String,
trimSpacesAroundCaret: Boolean = false
): LookupElement {
val file = KtPsiFactory(project).createFile(fileTextToReformat)
CodeStyleManager.getInstance(project).reformat(file)
@@ -133,46 +133,54 @@ fun createKeywordConstructLookupElement(
tailAfterCaret = tailAfterCaret.unindent(indent)
val tailText = (if (tailBeforeCaret.contains('\n')) tailBeforeCaret.replace("\n", "").trimEnd() else tailBeforeCaret) +
"..." +
(if (tailAfterCaret.contains('\n')) tailAfterCaret.replace("\n", "").trimStart() else tailAfterCaret)
"..." +
(if (tailAfterCaret.contains('\n')) tailAfterCaret.replace("\n", "").trimStart() else tailAfterCaret)
return LookupElementBuilder.create(KeywordLookupObject(), keyword)
.bold()
.withTailText(tailText)
.withInsertHandler { insertionContext, _ ->
if (insertionContext.completionChar == Lookup.NORMAL_SELECT_CHAR ||
insertionContext.completionChar == Lookup.REPLACE_SELECT_CHAR ||
insertionContext.completionChar == Lookup.AUTO_INSERT_SELECT_CHAR) {
.bold()
.withTailText(tailText)
.withInsertHandler { insertionContext, _ ->
if (insertionContext.completionChar == Lookup.NORMAL_SELECT_CHAR ||
insertionContext.completionChar == Lookup.REPLACE_SELECT_CHAR ||
insertionContext.completionChar == Lookup.AUTO_INSERT_SELECT_CHAR
) {
val offset = insertionContext.tailOffset
val newIndent = detectIndent(insertionContext.document.charsSequence, offset - keyword.length)
val offset = insertionContext.tailOffset
val newIndent = detectIndent(insertionContext.document.charsSequence, offset - keyword.length)
val beforeCaret = tailBeforeCaret.indentLinesAfterFirst(newIndent)
val afterCaret = tailAfterCaret.indentLinesAfterFirst(newIndent)
val beforeCaret = tailBeforeCaret.indentLinesAfterFirst(newIndent)
val afterCaret = tailAfterCaret.indentLinesAfterFirst(newIndent)
val element = insertionContext.file.findElementAt(offset)
val element = insertionContext.file.findElementAt(offset)
val sibling = when {
element !is PsiWhiteSpace -> element
element.textContains('\n') -> null
else -> element.getNextSiblingIgnoringWhitespace(true)
}
val sibling = when {
element !is PsiWhiteSpace -> element
element.textContains('\n') -> null
else -> element.getNextSiblingIgnoringWhitespace(true)
}
if (sibling != null && beforeCaret.trimStart().startsWith(insertionContext.document.getText(TextRange.from(sibling.startOffset, 1)))) {
insertionContext.editor.moveCaret(sibling.startOffset + 1)
}
else {
insertionContext.document.insertString(offset, beforeCaret + afterCaret)
insertionContext.editor.moveCaret(offset + beforeCaret.length)
}
if (sibling != null && beforeCaret.trimStart().startsWith(
insertionContext.document.getText(
TextRange.from(
sibling.startOffset,
1
)
)
)
) {
insertionContext.editor.moveCaret(sibling.startOffset + 1)
} else {
insertionContext.document.insertString(offset, beforeCaret + afterCaret)
insertionContext.editor.moveCaret(offset + beforeCaret.length)
}
}
}
}
private fun detectIndent(text: CharSequence, offset: Int): String {
return text.substring(0, offset)
.substringAfterLast('\n')
.takeWhile(Char::isWhitespace)
.substringAfterLast('\n')
.takeWhile(Char::isWhitespace)
}
private fun String.indentLinesAfterFirst(indent: String): String {