diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt index 80b8c08aa88..3ef396e2349 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KeywordCompletion.kt @@ -16,6 +16,8 @@ package org.jetbrains.kotlin.idea.completion +import com.intellij.codeInsight.completion.InsertHandler +import com.intellij.codeInsight.completion.InsertionContext import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.openapi.project.Project @@ -30,9 +32,7 @@ import com.intellij.psi.tree.IElementType import com.intellij.psi.tree.TokenSet import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget.* -import org.jetbrains.kotlin.idea.completion.handlers.KotlinFunctionInsertHandler -import org.jetbrains.kotlin.idea.completion.handlers.KotlinKeywordInsertHandler -import org.jetbrains.kotlin.idea.completion.handlers.UseSiteAnnotationTargetInsertHandler +import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler import org.jetbrains.kotlin.idea.completion.handlers.createKeywordConstructLookupElement import org.jetbrains.kotlin.lexer.KtKeywordToken import org.jetbrains.kotlin.lexer.KtModifierKeywordToken @@ -72,6 +72,20 @@ object KeywordCompletion { CONSTRUCTOR_KEYWORD to "class C { constructor(caret)" ) + private val NO_SPACE_AFTER = listOf(THIS_KEYWORD, + SUPER_KEYWORD, + NULL_KEYWORD, + TRUE_KEYWORD, + FALSE_KEYWORD, + BREAK_KEYWORD, + CONTINUE_KEYWORD, + ELSE_KEYWORD, + WHEN_KEYWORD, + FILE_KEYWORD, + DYNAMIC_KEYWORD, + GET_KEYWORD, + SET_KEYWORD).map { it.value} + "companion object" + fun complete(position: PsiElement, prefix: String, isJvmModule: Boolean, consumer: (LookupElement) -> Unit) { if (!GENERAL_FILTER.isAcceptable(position, position)) return @@ -109,12 +123,14 @@ object KeywordCompletion { val isUseSiteAnnotationTarget = position.prevLeaf()?.node?.elementType == KtTokens.AT - val insertHandler = if (isUseSiteAnnotationTarget) - UseSiteAnnotationTargetInsertHandler - else if (keywordToken !in FUNCTION_KEYWORDS) - KotlinKeywordInsertHandler - else - KotlinFunctionInsertHandler.Normal(inputTypeArguments = false, inputValueArguments = false) + val insertHandler = when { + isUseSiteAnnotationTarget -> UseSiteAnnotationTargetInsertHandler + + keyword in NO_SPACE_AFTER -> null + + else -> SpaceAfterInsertHandler + } + element = element.withInsertHandler(insertHandler) if (isUseSiteAnnotationTarget) { @@ -126,7 +142,17 @@ object KeywordCompletion { } } - private val FUNCTION_KEYWORDS = listOf(CONSTRUCTOR_KEYWORD) + private object UseSiteAnnotationTargetInsertHandler : InsertHandler { + override fun handleInsert(context: InsertionContext, item: LookupElement) { + WithTailInsertHandler(":", spaceBefore = false, spaceAfter = false).postHandleInsert(context, item) + } + } + + private object SpaceAfterInsertHandler : InsertHandler { + override fun handleInsert(context: InsertionContext, item: LookupElement) { + WithTailInsertHandler.SPACE.postHandleInsert(context, item) + } + } private val GENERAL_FILTER = NotFilter(OrFilter( CommentFilter(), diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/KotlinKeywordInsertHandler.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/KotlinKeywordInsertHandler.kt deleted file mode 100644 index 5378baf682a..00000000000 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/KotlinKeywordInsertHandler.kt +++ /dev/null @@ -1,162 +0,0 @@ -/* - * 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 - -import com.intellij.codeInsight.completion.InsertHandler -import com.intellij.codeInsight.completion.InsertionContext -import com.intellij.codeInsight.lookup.Lookup -import com.intellij.codeInsight.lookup.LookupElement -import com.intellij.codeInsight.lookup.LookupElementBuilder -import com.intellij.openapi.project.Project -import com.intellij.psi.codeStyle.CodeStyleManager -import org.jetbrains.kotlin.idea.completion.KeywordLookupObject -import org.jetbrains.kotlin.idea.core.moveCaret -import org.jetbrains.kotlin.lexer.KtTokens.* -import org.jetbrains.kotlin.psi.KtPsiFactory - -object KotlinKeywordInsertHandler : InsertHandler { - private val NO_SPACE_AFTER = listOf(THIS_KEYWORD, - SUPER_KEYWORD, - FOR_KEYWORD, - NULL_KEYWORD, - TRUE_KEYWORD, - FALSE_KEYWORD, - BREAK_KEYWORD, - CONTINUE_KEYWORD, - IF_KEYWORD, - ELSE_KEYWORD, - WHILE_KEYWORD, - DO_KEYWORD, - TRY_KEYWORD, - WHEN_KEYWORD, - FILE_KEYWORD, - CATCH_KEYWORD, - FINALLY_KEYWORD, - DYNAMIC_KEYWORD, - GET_KEYWORD, - SET_KEYWORD).map { it.value } + "companion object" - - override fun handleInsert(context: InsertionContext, item: LookupElement) { - val keyword = item.lookupString - if (keyword !in NO_SPACE_AFTER) { - WithTailInsertHandler.SPACE.postHandleInsert(context, item) - } - } -} - -fun createKeywordConstructLookupElement( - project: Project, - keyword: String, - fileTextToReformat: String, - trimSpacesAroundCaret: Boolean = false, - showConstructInLookup: Boolean = true -): LookupElement { - val file = KtPsiFactory(project).createFile(fileTextToReformat) - CodeStyleManager.getInstance(project).reformat(file) - val newFileText = file.text - - val keywordOffset = newFileText.indexOf(keyword) - assert(keywordOffset >= 0) - val keywordEndOffset = keywordOffset + keyword.length - - val caretPlaceHolder = "caret" - - val caretOffset = newFileText.indexOf(caretPlaceHolder) - assert(caretOffset >= 0) - assert(caretOffset >= keywordEndOffset) - - var tailBeforeCaret = newFileText.substring(keywordEndOffset, caretOffset) - var tailAfterCaret = newFileText.substring(caretOffset + caretPlaceHolder.length) - - if (trimSpacesAroundCaret) { - tailBeforeCaret = tailBeforeCaret.trimEnd() - tailAfterCaret = tailAfterCaret.trimStart() - } - - val indent = detectIndent(newFileText, keywordOffset) - if (indent != null) { - tailBeforeCaret = tailBeforeCaret.unindent(indent) - tailAfterCaret = tailAfterCaret.unindent(indent) - } - - var lookupElementBuilder = LookupElementBuilder.create(KeywordLookupObject(), keyword) - .bold() - .withInsertHandler { insertionContext, lookupElement -> - if (insertionContext.completionChar == Lookup.NORMAL_SELECT_CHAR || insertionContext.completionChar == Lookup.REPLACE_SELECT_CHAR) { - val offset = insertionContext.tailOffset - val newIndent = detectIndent(insertionContext.document.charsSequence, offset - keyword.length) - var beforeCaret = tailBeforeCaret - var afterCaret = tailAfterCaret - if (newIndent != null) { - beforeCaret = beforeCaret.indentLinesAfterFirst(newIndent) - afterCaret = afterCaret.indentLinesAfterFirst(newIndent) - } - insertionContext.document.insertString(offset, beforeCaret + afterCaret) - insertionContext.editor.moveCaret(offset + beforeCaret.length) - } - } - if (showConstructInLookup) { - lookupElementBuilder = lookupElementBuilder.withTailText(tailBeforeCaret + tailAfterCaret) - } - return lookupElementBuilder -} - -private fun detectIndent(text: CharSequence, offset: Int): String? { - val reversedIndent = buildString { - var index = offset - 1 - Loop@ - while (index >= 0) { - val c = text[index] - when (c) { - ' ', '\t' -> append(c) - '\r', '\n' -> break@Loop - else -> return null - } - index-- - } - } - return reversedIndent.reversed() -} - -private fun String.indentLinesAfterFirst(indent: String): String { - val text = this - return buildString { - val lines = text.lines() - for ((index, line) in lines.withIndex()) { - if (index > 0) append(indent) - append(line) - if (index != lines.lastIndex) append('\n') - } - } -} - -private fun String.unindent(indent: String): String { - val text = this - return buildString { - val lines = text.lines() - for ((index, line) in lines.withIndex()) { - append(line.removePrefix(indent)) - if (index != lines.lastIndex) append('\n') - } - } -} - -object UseSiteAnnotationTargetInsertHandler : InsertHandler { - override fun handleInsert(context: InsertionContext, item: LookupElement) { - WithTailInsertHandler(":", spaceBefore = false, spaceAfter = false).postHandleInsert(context, item) - } -} diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/handlerUtils.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/handlerUtils.kt index 2c9e28f0174..60fd9cce225 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/handlerUtils.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/handlerUtils.kt @@ -18,10 +18,18 @@ package org.jetbrains.kotlin.idea.completion.handlers import com.intellij.codeInsight.completion.CompletionInitializationContext import com.intellij.codeInsight.completion.InsertionContext +import com.intellij.codeInsight.lookup.Lookup +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.openapi.editor.Document +import com.intellij.openapi.project.Project import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.codeStyle.CodeStyleManager +import org.jetbrains.kotlin.idea.completion.KeywordLookupObject +import org.jetbrains.kotlin.idea.core.moveCaret import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtPsiFactory fun surroundWithBracesIfInStringTemplate(context: InsertionContext) { val startOffset = context.startOffset @@ -61,3 +69,100 @@ fun CharSequence.skipSpacesAndLineBreaks(index: Int): Int 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 createKeywordConstructLookupElement( + project: Project, + keyword: String, + fileTextToReformat: String, + trimSpacesAroundCaret: Boolean = false, + showConstructInLookup: Boolean = true +): LookupElement { + val file = KtPsiFactory(project).createFile(fileTextToReformat) + CodeStyleManager.getInstance(project).reformat(file) + val newFileText = file.text + + val keywordOffset = newFileText.indexOf(keyword) + assert(keywordOffset >= 0) + val keywordEndOffset = keywordOffset + keyword.length + + val caretPlaceHolder = "caret" + + val caretOffset = newFileText.indexOf(caretPlaceHolder) + assert(caretOffset >= 0) + assert(caretOffset >= keywordEndOffset) + + var tailBeforeCaret = newFileText.substring(keywordEndOffset, caretOffset) + var tailAfterCaret = newFileText.substring(caretOffset + caretPlaceHolder.length) + + if (trimSpacesAroundCaret) { + tailBeforeCaret = tailBeforeCaret.trimEnd() + tailAfterCaret = tailAfterCaret.trimStart() + } + + val indent = detectIndent(newFileText, keywordOffset) + if (indent != null) { + tailBeforeCaret = tailBeforeCaret.unindent(indent) + tailAfterCaret = tailAfterCaret.unindent(indent) + } + + var lookupElementBuilder = LookupElementBuilder.create(KeywordLookupObject(), keyword) + .bold() + .withInsertHandler { insertionContext, lookupElement -> + if (insertionContext.completionChar == Lookup.NORMAL_SELECT_CHAR || insertionContext.completionChar == Lookup.REPLACE_SELECT_CHAR) { + val offset = insertionContext.tailOffset + val newIndent = detectIndent(insertionContext.document.charsSequence, offset - keyword.length) + var beforeCaret = tailBeforeCaret + var afterCaret = tailAfterCaret + if (newIndent != null) { + beforeCaret = beforeCaret.indentLinesAfterFirst(newIndent) + afterCaret = afterCaret.indentLinesAfterFirst(newIndent) + } + insertionContext.document.insertString(offset, beforeCaret + afterCaret) + insertionContext.editor.moveCaret(offset + beforeCaret.length) + } + } + if (showConstructInLookup) { + lookupElementBuilder = lookupElementBuilder.withTailText(tailBeforeCaret + tailAfterCaret) + } + return lookupElementBuilder +} + +private fun detectIndent(text: CharSequence, offset: Int): String? { + val reversedIndent = buildString { + var index = offset - 1 + Loop@ + while (index >= 0) { + val c = text[index] + when (c) { + ' ', '\t' -> append(c) + '\r', '\n' -> break@Loop + else -> return null + } + index-- + } + } + return reversedIndent.reversed() +} + +private fun String.indentLinesAfterFirst(indent: String): String { + val text = this + return buildString { + val lines = text.lines() + for ((index, line) in lines.withIndex()) { + if (index > 0) append(indent) + append(line) + if (index != lines.lastIndex) append('\n') + } + } +} + +private fun String.unindent(indent: String): String { + val text = this + return buildString { + val lines = text.lines() + for ((index, line) in lines.withIndex()) { + append(line.removePrefix(indent)) + if (index != lines.lastIndex) append('\n') + } + } +}