Refactoring

This commit is contained in:
Valentin Kipyatkov
2016-04-18 22:05:09 +03:00
parent 2a3b87f783
commit c7a96401da
3 changed files with 141 additions and 172 deletions
@@ -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<LookupElement> {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
WithTailInsertHandler(":", spaceBefore = false, spaceAfter = false).postHandleInsert(context, item)
}
}
private object SpaceAfterInsertHandler : InsertHandler<LookupElement> {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
WithTailInsertHandler.SPACE.postHandleInsert(context, item)
}
}
private val GENERAL_FILTER = NotFilter(OrFilter(
CommentFilter(),
@@ -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<LookupElement> {
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<LookupElement> {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
WithTailInsertHandler(":", spaceBefore = false, spaceAfter = false).postHandleInsert(context, item)
}
}
@@ -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')
}
}
}