diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index b8c4e9dfcde..82936fd7495 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -228,14 +228,9 @@ order="first" implementationClass="org.jetbrains.jet.plugin.completion.JetCompletionContributor"/> - - ())?.firstOrNull() + protected val resolveSession: ResolveSessionForBodies = (position.getContainingFile() as JetFile).getLazyResolveSession() + protected val bindingContext: BindingContext? = jetReference?.let { resolveSession.resolveToElement(it.expression) } + protected val inDescriptor: DeclarationDescriptor? = jetReference?.let { bindingContext!!.get(BindingContext.RESOLUTION_SCOPE, it.expression)?.getContainingDeclaration() } // set prefix matcher here to override default one which relies on CompletionUtil.findReferencePrefix() // which sometimes works incorrectly for Kotlin @@ -71,9 +71,8 @@ abstract class CompletionSessionBase(protected val configuration: CompletionSess class BasicCompletionSession(configuration: CompletionSessionConfiguration, parameters: CompletionParameters, - resultSet: CompletionResultSet, - jetReference: JetSimpleNameReference) -: CompletionSessionBase(configuration, parameters, resultSet, jetReference) { + resultSet: CompletionResultSet) +: CompletionSessionBase(configuration, parameters, resultSet) { private val collector: LookupElementsCollector = LookupElementsCollector(prefixMatcher, resolveSession, { isVisibleDescriptor(it) }) @@ -88,29 +87,34 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration, } private fun collectElements() { - if (isOnlyKeywordCompletion(position)) return - - if (shouldRunOnlyTypeCompletion()) { - if (configuration.completeNonImportedDeclarations) { - TypesCompletion(parameters, resolveSession, prefixMatcher).addAllTypes(collector) + if (jetReference != null && !isOnlyKeywordCompletion()) { + if (shouldRunOnlyTypeCompletion()) { + if (configuration.completeNonImportedDeclarations) { + TypesCompletion(parameters, resolveSession, prefixMatcher).addAllTypes(collector) + } + else { + addReferenceVariants { isPartOfTypeDeclaration(it) } + JavaCompletionContributor.advertiseSecondCompletion(position.getProject(), resultSet) + } } else { - addReferenceVariants { isPartOfTypeDeclaration(it) } - JavaCompletionContributor.advertiseSecondCompletion(parameters.getPosition().getProject(), resultSet) - } + addReferenceVariants { true } - return + addNonImported() + } } - addReferenceVariants { true } + KeywordCompletion().complete(parameters, collector) + } + private fun addNonImported() { val prefix = prefixMatcher.getPrefix() // Try to avoid computing not-imported descriptors for empty prefix if (prefix.isEmpty()) { if (!configuration.completeNonImportedDeclarations) return - if (PsiTreeUtil.getParentOfType(jetReference.expression, javaClass()) == null) return + if (PsiTreeUtil.getParentOfType(jetReference!!.expression, javaClass()) == null) return } if (shouldRunTopLevelCompletion()) { @@ -124,13 +128,13 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration, } } - private fun isOnlyKeywordCompletion(position: PsiElement) + private fun isOnlyKeywordCompletion() = PsiTreeUtil.getParentOfType(position, javaClass()) != null private fun addJetExtensions() { val project = position.getProject() val namesCache = JetShortNamesCache.getKotlinInstance(project) - collector.addDescriptorElements(namesCache.getJetCallableExtensions({ prefixMatcher.prefixMatches(it!!) }, jetReference.expression, resolveSession, GlobalSearchScope.allScope(project))) + collector.addDescriptorElements(namesCache.getJetCallableExtensions({ prefixMatcher.prefixMatches(it!!) }, jetReference!!.expression, resolveSession, GlobalSearchScope.allScope(project))) } private fun isPartOfTypeDeclaration(descriptor: DeclarationDescriptor): Boolean { @@ -157,7 +161,7 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration, // TODO: Fix complete extension not only on contains for (name in functionNames) { if (name.contains(actualPrefix)) { - collector.addDescriptorElements(namesCache.getTopLevelFunctionDescriptorsByName(name, jetReference.expression, resolveSession, scope)) + collector.addDescriptorElements(namesCache.getTopLevelFunctionDescriptorsByName(name, jetReference!!.expression, resolveSession, scope)) } } } @@ -170,7 +174,7 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration, for (name in objectNames) { if (prefixMatcher.prefixMatches(name)) { - collector.addDescriptorElements(namesCache.getTopLevelObjectsByName(name, jetReference.expression, resolveSession, scope)) + collector.addDescriptorElements(namesCache.getTopLevelObjectsByName(name, jetReference!!.expression, resolveSession, scope)) } } } @@ -181,7 +185,7 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration, val typeReference = PsiTreeUtil.getParentOfType(position, javaClass()) if (typeReference != null) { val firstPartReference = PsiTreeUtil.findChildOfType(typeReference, javaClass()) - return firstPartReference == jetReference.expression + return firstPartReference == jetReference!!.expression } return false @@ -205,16 +209,18 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration, } private fun addReferenceVariants(filterCondition: (DeclarationDescriptor) -> Boolean) { - val descriptors = TipsManager.getReferenceVariants(jetReference.expression, bindingContext) + val descriptors = TipsManager.getReferenceVariants(jetReference!!.expression, bindingContext!!) collector.addDescriptorElements(descriptors.filter { filterCondition(it) }) } } -class SmartCompletionSession(configuration: CompletionSessionConfiguration, parameters: CompletionParameters, resultSet: CompletionResultSet, jetReference: JetSimpleNameReference) -: CompletionSessionBase(configuration, parameters, resultSet, jetReference) { +class SmartCompletionSession(configuration: CompletionSessionConfiguration, parameters: CompletionParameters, resultSet: CompletionResultSet) +: CompletionSessionBase(configuration, parameters, resultSet) { public fun complete() { - val descriptors = TipsManager.getReferenceVariants(jetReference.expression, bindingContext) - val completion = SmartCompletion(jetReference.expression, resolveSession, { isVisibleDescriptor(it) }, parameters.getOriginalFile() as JetFile) - completion.buildLookupElements(descriptors)?.forEach { resultSet.addElement(it) } + if (jetReference != null) { + val descriptors = TipsManager.getReferenceVariants(jetReference.expression, bindingContext!!) + val completion = SmartCompletion(jetReference.expression, resolveSession, { isVisibleDescriptor(it) }, parameters.getOriginalFile() as JetFile) + completion.buildLookupElements(descriptors)?.forEach { resultSet.addElement(it) } + } } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/completion/JetCompletionContributor.kt b/idea/src/org/jetbrains/jet/plugin/completion/JetCompletionContributor.kt index 97ee933b3c3..c878f009e48 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/JetCompletionContributor.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/JetCompletionContributor.kt @@ -122,33 +122,27 @@ public class JetCompletionContributor : CompletionContributor() { return } - val jetReference = getJetReference(parameters) - if (jetReference != null) { - try { - result.restartCompletionWhenNothingMatches() + try { + result.restartCompletionWhenNothingMatches() - val configuration = CompletionSessionConfiguration(parameters) - if (parameters.getCompletionType() == CompletionType.BASIC) { - val somethingAdded = BasicCompletionSession(configuration, parameters, result, jetReference).complete() - if (!somethingAdded && parameters.getInvocationCount() < 2) { - // Rerun completion if nothing was found - val newConfiguration = CompletionSessionConfiguration(completeNonImportedDeclarations = true, completeNonAccessibleDeclarations = parameters.getInvocationCount() > 0) - BasicCompletionSession(newConfiguration, parameters, result, jetReference).complete() - } - } - else { - SmartCompletionSession(configuration, parameters, result, jetReference).complete() + val configuration = CompletionSessionConfiguration(parameters) + if (parameters.getCompletionType() == CompletionType.BASIC) { + val somethingAdded = BasicCompletionSession(configuration, parameters, result).complete() + if (!somethingAdded && parameters.getInvocationCount() < 2) { + // Rerun completion if nothing was found + val newConfiguration = CompletionSessionConfiguration(completeNonImportedDeclarations = true, completeNonAccessibleDeclarations = parameters.getInvocationCount() > 0) + BasicCompletionSession(newConfiguration, parameters, result).complete() } } - catch (e: ProcessCanceledException) { - throw rethrowWithCancelIndicator(e) + else { + SmartCompletionSession(configuration, parameters, result).complete() } } + catch (e: ProcessCanceledException) { + throw rethrowWithCancelIndicator(e) + } } - private fun getJetReference(parameters: CompletionParameters): JetSimpleNameReference? - = parameters.getPosition().getParent()?.getReferences()?.filterIsInstance(javaClass())?.firstOrNull() - private fun isAtEndOfLine(offset: Int, document: Document): Boolean { var i = offset val chars = document.getCharsSequence() diff --git a/idea/src/org/jetbrains/jet/plugin/completion/JetKeywordCompletionContributor.kt b/idea/src/org/jetbrains/jet/plugin/completion/JetKeywordCompletionContributor.kt deleted file mode 100644 index 4775a20d6e6..00000000000 --- a/idea/src/org/jetbrains/jet/plugin/completion/JetKeywordCompletionContributor.kt +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright 2010-2013 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.jet.plugin.completion - -import com.intellij.psi.filters.* -import com.intellij.psi.PsiWhiteSpace -import com.intellij.psi.impl.source.tree.LeafPsiElement -import com.intellij.psi.filters.position.LeftNeighbour -import org.jetbrains.jet.lang.psi.* -import org.jetbrains.jet.lexer.JetToken -import org.jetbrains.jet.lexer.JetTokens -import com.intellij.codeInsight.lookup.LookupElement -import com.intellij.psi.filters.position.SuperParentFilter -import com.intellij.psi.util.PsiTreeUtil -import com.intellij.psi.PsiElement -import com.intellij.psi.filters.position.PositionElementFilter -import com.intellij.util.containers.MultiMap -import java.util.HashSet -import com.intellij.openapi.util.text.StringUtil -import com.intellij.codeInsight.completion.* -import org.jetbrains.jet.plugin.completion.handlers.JetFunctionInsertHandler -import org.jetbrains.jet.plugin.completion.handlers.JetKeywordInsertHandler -import com.intellij.util.ProcessingContext -import com.intellij.codeInsight.lookup.LookupElementBuilder -import com.intellij.psi.filters.position.FilterPattern -import com.intellij.patterns.PlatformPatterns -import com.intellij.psi.filters.position.PatternFilter - -class KeywordLookupObject(val keyword: String) - -public class JetKeywordCompletionContributor : CompletionContributor() { - { - val inTopLevel = notIdentifier(InTopFilter()) - val inTypeParameterFirstChildFilter = InTypeParameterFirstChildFilter() - val inClassBody = notIdentifier(InClassBodyFilter()) - val inNonClassBlock = notIdentifier(InNonClassBlockFilter()) - val inAfterClassInClassBody = AfterClassInClassBodyFilter() - val inPropertyBody = notIdentifier(InPropertyBodyFilter()) - - val inNonParameterModifier = notIdentifier(AndFilter( - SuperParentFilter(ClassFilter(javaClass())), - NotFilter(inTypeParameterFirstChildFilter) - )) - - BunchKeywordRegister() - .add(JetTokens.ABSTRACT_KEYWORD, inTopLevel, inNonParameterModifier, inClassBody) - .add(JetTokens.FINAL_KEYWORD, inTopLevel, inNonParameterModifier, inClassBody) - .add(JetTokens.OPEN_KEYWORD, inTopLevel, inNonParameterModifier, inClassBody) - - .add(JetTokens.INTERNAL_KEYWORD, inTopLevel, inNonParameterModifier, inClassBody, inNonClassBlock) - .add(JetTokens.PRIVATE_KEYWORD, inTopLevel, inNonParameterModifier, inClassBody, inNonClassBlock) - .add(JetTokens.PROTECTED_KEYWORD, inTopLevel, inNonParameterModifier, inClassBody, inNonClassBlock) - .add(JetTokens.PUBLIC_KEYWORD, inTopLevel, inNonParameterModifier, inClassBody, inNonClassBlock) - - .add(JetTokens.CLASS_KEYWORD, inTopLevel, inClassBody, inNonClassBlock) - .add(JetTokens.ENUM_KEYWORD, inTopLevel, inClassBody, inNonClassBlock) - .add(JetTokens.FUN_KEYWORD, inTopLevel, inClassBody, inNonClassBlock) - .add(JetTokens.GET_KEYWORD, inTopLevel, inClassBody, inNonClassBlock) - .add(JetTokens.SET_KEYWORD, inTopLevel, inClassBody, inNonClassBlock) - .add(JetTokens.TRAIT_KEYWORD, inTopLevel, inClassBody, inNonClassBlock) - .add(JetTokens.VAL_KEYWORD, inTopLevel, inClassBody, inNonClassBlock) - .add(JetTokens.VAR_KEYWORD, inTopLevel, inClassBody, inNonClassBlock) - .add(JetTokens.TYPE_KEYWORD, inTopLevel, inClassBody, inNonClassBlock) - - .add(JetTokens.IMPORT_KEYWORD, inTopLevel) - .add(JetTokens.PACKAGE_KEYWORD, inTopLevel) - - .add(JetTokens.OVERRIDE_KEYWORD, inClassBody) - - .add(JetTokens.IN_KEYWORD, inNonClassBlock, inTypeParameterFirstChildFilter) - - .add(JetTokens.OUT_KEYWORD, inTypeParameterFirstChildFilter) - - .add(JetTokens.OBJECT_KEYWORD, inNonClassBlock, inAfterClassInClassBody) - - .add(JetTokens.ELSE_KEYWORD, inNonClassBlock, inPropertyBody) - .add(JetTokens.IF_KEYWORD, inNonClassBlock, inPropertyBody) - .add(JetTokens.TRUE_KEYWORD, inNonClassBlock, inPropertyBody) - .add(JetTokens.FALSE_KEYWORD, inNonClassBlock, inPropertyBody) - .add(JetTokens.NULL_KEYWORD, inNonClassBlock, inPropertyBody) - .add(JetTokens.THIS_KEYWORD, inNonClassBlock, inPropertyBody) - .add(JetTokens.WHEN_KEYWORD, inNonClassBlock, inPropertyBody) - - .add(JetTokens.AS_KEYWORD, inNonClassBlock) - .add(JetTokens.BREAK_KEYWORD, inNonClassBlock) - .add(JetTokens.BY_KEYWORD, inNonClassBlock) - .add(JetTokens.CATCH_KEYWORD, inNonClassBlock) - .add(JetTokens.CONTINUE_KEYWORD, inNonClassBlock) - .add(JetTokens.DO_KEYWORD, inNonClassBlock) - .add(JetTokens.FINALLY_KEYWORD, inNonClassBlock) - .add(JetTokens.FOR_KEYWORD, inNonClassBlock) - .add(JetTokens.IS_KEYWORD, inNonClassBlock) - .add(JetTokens.RETURN_KEYWORD, inNonClassBlock) - .add(JetTokens.SUPER_KEYWORD, inNonClassBlock) - .add(JetTokens.CAPITALIZED_THIS_KEYWORD, inNonClassBlock) - .add(JetTokens.THROW_KEYWORD, inNonClassBlock) - .add(JetTokens.TRY_KEYWORD, inNonClassBlock) - .add(JetTokens.VARARG_KEYWORD, inNonClassBlock) - .add(JetTokens.WHERE_KEYWORD, inNonClassBlock) - .add(JetTokens.WHILE_KEYWORD, inNonClassBlock) - .registerAll() - } - - private fun registerScopeKeywordsCompletion(placeFilter : ElementFilter, keywords : Collection) { - extend(CompletionType.BASIC, getPlacePattern(placeFilter), KeywordsCompletionProvider(keywords.map { it.toString() })) - } - - private inner class BunchKeywordRegister() { - private val orFiltersToKeywords : MultiMap, JetToken> = MultiMap.create() - - fun add(keyword: JetToken, vararg filters: ElementFilter): BunchKeywordRegister { - orFiltersToKeywords.putValue(hashSetOf(*filters), keyword) - return this - } - - fun registerAll() { - for ((filters, token) in orFiltersToKeywords.entrySet()) { - val orFilter = OrFilter() - filters.forEach { filter -> orFilter.addFilter(filter) } - - registerScopeKeywordsCompletion(orFilter, token) - } - } - } - - class object { - private val KEYWORDS_INSERT_HANDLER = JetKeywordInsertHandler - private val FUNCTION_KEYWORDS = listOf(JetTokens.GET_KEYWORD.toString(), JetTokens.SET_KEYWORD.toString()) - - private val NOT_IDENTIFIER_FILTER = NotFilter(AndFilter( - LeafElementFilter(JetTokens.IDENTIFIER), - NotFilter(ParentFilter(ClassFilter(javaClass()))) - )) - - private val GENERAL_FILTER : ElementFilter = NotFilter(OrFilter( - CommentFilter(), - ParentFilter(ClassFilter(javaClass())), - ParentFilter(ClassFilter(javaClass())), - LeftNeighbour(TextFilter(".")) - )) - - private fun notIdentifier(filter : ElementFilter) = AndFilter(NOT_IDENTIFIER_FILTER, filter) - - private fun getPlacePattern(placeFilter : ElementFilter) = - PlatformPatterns.psiElement().and(FilterPattern(AndFilter(GENERAL_FILTER, placeFilter))) - - private open class CommentFilter() : ElementFilter { - override fun isAcceptable(element : Any?, context : PsiElement?) - = (element is PsiElement) && JetPsiUtil.isInComment(element as PsiElement) - - override fun isClassAcceptable(hintClass: Class) - = true - } - - private open class ParentFilter(filter : ElementFilter) : PositionElementFilter() { - { - setFilter(filter) - } - - override fun isAcceptable(element : Any?, context : PsiElement?) : Boolean { - val parent = (element as? PsiElement)?.getParent() - return parent != null && (getFilter()?.isAcceptable(parent, context) ?: true) - } - } - - private open class InTopFilter() : PositionElementFilter() { - override fun isAcceptable(element : Any?, context : PsiElement?) : Boolean { - val underFile = PsiTreeUtil.getParentOfType(context, javaClass(), false, - javaClass(), javaClass(), javaClass(), javaClass()) != null - - val notInDeclarationElement = PsiTreeUtil.getParentOfType( - context, - javaClass(), javaClass(), javaClass()) == null - - return underFile && notInDeclarationElement - } - } - - private open class InNonClassBlockFilter() : PositionElementFilter() { - override fun isAcceptable(element : Any?, context : PsiElement?) - = PsiTreeUtil.getParentOfType(context, javaClass(), true, javaClass()) != null - } - - private open class InTypeParameterFirstChildFilter() : PositionElementFilter() { - override fun isAcceptable(element : Any?, context : PsiElement?) : Boolean { - val typeParameterElement = PsiTreeUtil.getParentOfType(context, javaClass(), true) - return typeParameterElement != null && - context != null && - PsiTreeUtil.isAncestor(typeParameterElement.getFirstChild(), context, false) - } - } - - private open class InClassBodyFilter() : PositionElementFilter() { - override fun isAcceptable(element : Any?, context : PsiElement?) : Boolean { - return PsiTreeUtil.getParentOfType( - context, javaClass(), true, - javaClass(), - javaClass(), - javaClass()) != null - } - } - - private open class AfterClassInClassBodyFilter() : InClassBodyFilter() { - override fun isAcceptable(element : Any?, context : PsiElement?) : Boolean { - if (super.isAcceptable(element, context)) { - fun isLeafAndClass(psiElement: PsiElement?) = - psiElement is LeafPsiElement && psiElement.getElementType() == JetTokens.CLASS_KEYWORD - - val ps = context?.getPrevSibling() - return isLeafAndClass(if (ps is PsiWhiteSpace) ps.getPrevSibling() else ps) - } - - return false - } - } - - private open class InPropertyBodyFilter() : PositionElementFilter() { - override fun isAcceptable(element: Any?, context: PsiElement?): Boolean { - if (element !is PsiElement) return false - - val property = PsiTreeUtil.getParentOfType(context, javaClass(), false) - return property != null && isAfterName(property, (element as PsiElement)) - } - - private fun isAfterName(property : JetProperty, element : PsiElement) : Boolean { - var iterableChild = property.getFirstChild() - while (iterableChild != null) { - val child = iterableChild!! - - if (PsiTreeUtil.isAncestor(child, element, false)) { - break - } - - if (child.getNode()?.getElementType() == JetTokens.IDENTIFIER) { - return true - } - - iterableChild = child.getNextSibling() - } - - return false - } - } - - private open class SimplePrefixMatcher(prefix : String) : PrefixMatcher(prefix) { - override fun prefixMatches(name : String) : Boolean = StringUtil.startsWith(name, getPrefix()) - override fun cloneWithPrefix(prefix : String) : PrefixMatcher = SimplePrefixMatcher(prefix) - } - - private inner class KeywordsCompletionProvider(keywords : Collection) : CompletionProvider() { - private val elements : Collection - private val debugName : String - - { - debugName = keywords.sort().joinToString(separator = ", ") - - elements = keywords.map { keyword -> - LookupElementBuilder.create(KeywordLookupObject(keyword), keyword) - .bold() - .withInsertHandler(if (keyword !in FUNCTION_KEYWORDS) - KEYWORDS_INSERT_HANDLER - else - JetFunctionInsertHandler.NO_PARAMETERS_HANDLER) - } - } - - override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) { - result.addKotlinSorting(parameters).withPrefixMatcher(SimplePrefixMatcher(result.getPrefixMatcher().getPrefix())).addAllElements(elements) - } - - override fun toString() : String = debugName - } - } -} diff --git a/idea/src/org/jetbrains/jet/plugin/completion/KeywordCompletion.kt b/idea/src/org/jetbrains/jet/plugin/completion/KeywordCompletion.kt new file mode 100644 index 00000000000..59f152344f1 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/completion/KeywordCompletion.kt @@ -0,0 +1,258 @@ +/* + * Copyright 2010-2013 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.jet.plugin.completion + +import com.intellij.psi.filters.* +import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.impl.source.tree.LeafPsiElement +import com.intellij.psi.filters.position.LeftNeighbour +import org.jetbrains.jet.lang.psi.* +import org.jetbrains.jet.lexer.JetToken +import org.jetbrains.jet.lexer.JetTokens +import com.intellij.psi.filters.position.SuperParentFilter +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.PsiElement +import com.intellij.psi.filters.position.PositionElementFilter +import com.intellij.util.containers.MultiMap +import java.util.HashSet +import com.intellij.codeInsight.completion.* +import org.jetbrains.jet.plugin.completion.handlers.JetFunctionInsertHandler +import org.jetbrains.jet.plugin.completion.handlers.JetKeywordInsertHandler +import com.intellij.codeInsight.lookup.LookupElementBuilder +import java.util.ArrayList + +class KeywordLookupObject(val keyword: String) + +//TODO: it should be object, it's class because of KT-5582 +class KeywordCompletion { + public fun complete(parameters: CompletionParameters, collector: LookupElementsCollector) { + val position = parameters.getPosition() + + if (!GENERAL_FILTER.isAcceptable(position, position)) return + + for ((filter, keywords) in filterToKeyword) { + if (filter.isAcceptable(position, position)) { + for (keyword in keywords) { + val element = LookupElementBuilder.create(KeywordLookupObject(keyword), keyword) + .bold() + .withInsertHandler(if (keyword !in FUNCTION_KEYWORDS) + JetKeywordInsertHandler + else + JetFunctionInsertHandler.NO_PARAMETERS_HANDLER) + collector.addElement(element) + } + } + } + } + + private val FUNCTION_KEYWORDS = listOf(JetTokens.GET_KEYWORD.toString(), JetTokens.SET_KEYWORD.toString()) + + private val NOT_IDENTIFIER_FILTER = NotFilter(AndFilter( + LeafElementFilter(JetTokens.IDENTIFIER), + NotFilter(ParentFilter(ClassFilter(javaClass()))) + )) + + private val GENERAL_FILTER = NotFilter(OrFilter( + CommentFilter(), + ParentFilter(ClassFilter(javaClass())), + ParentFilter(ClassFilter(javaClass())), + LeftNeighbour(TextFilter(".")) + )) + + private fun notIdentifier(filter: ElementFilter) = AndFilter(NOT_IDENTIFIER_FILTER, filter) + + private val filterToKeyword: List>> = run { + val inTopLevel = notIdentifier(InTopFilter) + val inClassBody = notIdentifier(InClassBodyFilter()) + val inNonClassBlock = notIdentifier(InNonClassBlockFilter) + val inPropertyBody = notIdentifier(InPropertyBodyFilter) + + val inNonParameterModifier = notIdentifier(AndFilter( + SuperParentFilter(ClassFilter(javaClass())), + NotFilter(InTypeParameterFirstChildFilter) + )) + + val filtersToKeywords : MultiMap, String> = MultiMap.create() + + fun add(keyword: JetToken, vararg filters: ElementFilter) { + filtersToKeywords.putValue(hashSetOf(*filters), keyword.toString()) + } + + add(JetTokens.ABSTRACT_KEYWORD, inTopLevel, inNonParameterModifier, inClassBody) + add(JetTokens.FINAL_KEYWORD, inTopLevel, inNonParameterModifier, inClassBody) + add(JetTokens.OPEN_KEYWORD, inTopLevel, inNonParameterModifier, inClassBody) + + add(JetTokens.INTERNAL_KEYWORD, inTopLevel, inNonParameterModifier, inClassBody, inNonClassBlock) + add(JetTokens.PRIVATE_KEYWORD, inTopLevel, inNonParameterModifier, inClassBody, inNonClassBlock) + add(JetTokens.PROTECTED_KEYWORD, inTopLevel, inNonParameterModifier, inClassBody, inNonClassBlock) + add(JetTokens.PUBLIC_KEYWORD, inTopLevel, inNonParameterModifier, inClassBody, inNonClassBlock) + + add(JetTokens.CLASS_KEYWORD, inTopLevel, inClassBody, inNonClassBlock) + add(JetTokens.ENUM_KEYWORD, inTopLevel, inClassBody, inNonClassBlock) + add(JetTokens.FUN_KEYWORD, inTopLevel, inClassBody, inNonClassBlock) + add(JetTokens.GET_KEYWORD, inTopLevel, inClassBody, inNonClassBlock) + add(JetTokens.SET_KEYWORD, inTopLevel, inClassBody, inNonClassBlock) + add(JetTokens.TRAIT_KEYWORD, inTopLevel, inClassBody, inNonClassBlock) + add(JetTokens.VAL_KEYWORD, inTopLevel, inClassBody, inNonClassBlock) + add(JetTokens.VAR_KEYWORD, inTopLevel, inClassBody, inNonClassBlock) + add(JetTokens.TYPE_KEYWORD, inTopLevel, inClassBody, inNonClassBlock) + + add(JetTokens.IMPORT_KEYWORD, inTopLevel) + add(JetTokens.PACKAGE_KEYWORD, inTopLevel) + + add(JetTokens.OVERRIDE_KEYWORD, inClassBody) + + add(JetTokens.IN_KEYWORD, inNonClassBlock, InTypeParameterFirstChildFilter) + + add(JetTokens.OUT_KEYWORD, InTypeParameterFirstChildFilter) + + add(JetTokens.OBJECT_KEYWORD, inNonClassBlock, AfterClassInClassBodyFilter) + + add(JetTokens.ELSE_KEYWORD, inNonClassBlock, inPropertyBody) + add(JetTokens.IF_KEYWORD, inNonClassBlock, inPropertyBody) + add(JetTokens.TRUE_KEYWORD, inNonClassBlock, inPropertyBody) + add(JetTokens.FALSE_KEYWORD, inNonClassBlock, inPropertyBody) + add(JetTokens.NULL_KEYWORD, inNonClassBlock, inPropertyBody) + add(JetTokens.THIS_KEYWORD, inNonClassBlock, inPropertyBody) + add(JetTokens.WHEN_KEYWORD, inNonClassBlock, inPropertyBody) + + add(JetTokens.AS_KEYWORD, inNonClassBlock) + add(JetTokens.BREAK_KEYWORD, inNonClassBlock) + add(JetTokens.BY_KEYWORD, inNonClassBlock) + add(JetTokens.CATCH_KEYWORD, inNonClassBlock) + add(JetTokens.CONTINUE_KEYWORD, inNonClassBlock) + add(JetTokens.DO_KEYWORD, inNonClassBlock) + add(JetTokens.FINALLY_KEYWORD, inNonClassBlock) + add(JetTokens.FOR_KEYWORD, inNonClassBlock) + add(JetTokens.IS_KEYWORD, inNonClassBlock) + add(JetTokens.RETURN_KEYWORD, inNonClassBlock) + add(JetTokens.SUPER_KEYWORD, inNonClassBlock) + add(JetTokens.CAPITALIZED_THIS_KEYWORD, inNonClassBlock) + add(JetTokens.THROW_KEYWORD, inNonClassBlock) + add(JetTokens.TRY_KEYWORD, inNonClassBlock) + add(JetTokens.VARARG_KEYWORD, inNonClassBlock) + add(JetTokens.WHERE_KEYWORD, inNonClassBlock) + add(JetTokens.WHILE_KEYWORD, inNonClassBlock) + + val result = ArrayList>>() + for ((filters, tokens) in filtersToKeywords.entrySet()) { + val orFilter = OrFilter() + filters.forEach { filter -> orFilter.addFilter(filter) } + result.add(orFilter to tokens) + } + + result + } + + private class CommentFilter() : ElementFilter { + override fun isAcceptable(element : Any?, context : PsiElement?) + = (element is PsiElement) && JetPsiUtil.isInComment(element as PsiElement) + + override fun isClassAcceptable(hintClass: Class) + = true + } + + private class ParentFilter(filter : ElementFilter) : PositionElementFilter() { + { + setFilter(filter) + } + + override fun isAcceptable(element : Any?, context : PsiElement?) : Boolean { + val parent = (element as? PsiElement)?.getParent() + return parent != null && (getFilter()?.isAcceptable(parent, context) ?: true) + } + } + + private object InTopFilter : PositionElementFilter() { + override fun isAcceptable(element : Any?, context : PsiElement?) : Boolean { + val underFile = PsiTreeUtil.getParentOfType(context, javaClass(), false, + javaClass(), javaClass(), javaClass(), javaClass()) != null + + val notInDeclarationElement = PsiTreeUtil.getParentOfType( + context, + javaClass(), javaClass(), javaClass()) == null + + return underFile && notInDeclarationElement + } + } + + private object InNonClassBlockFilter : PositionElementFilter() { + override fun isAcceptable(element : Any?, context : PsiElement?) + = PsiTreeUtil.getParentOfType(context, javaClass(), true, javaClass()) != null + } + + private object InTypeParameterFirstChildFilter : PositionElementFilter() { + override fun isAcceptable(element : Any?, context : PsiElement?) : Boolean { + val typeParameterElement = PsiTreeUtil.getParentOfType(context, javaClass(), true) + return typeParameterElement != null && + context != null && + PsiTreeUtil.isAncestor(typeParameterElement.getFirstChild(), context, false) + } + } + + private open class InClassBodyFilter() : PositionElementFilter() { + override fun isAcceptable(element : Any?, context : PsiElement?) : Boolean { + return PsiTreeUtil.getParentOfType( + context, javaClass(), true, + javaClass(), + javaClass(), + javaClass()) != null + } + } + + private object AfterClassInClassBodyFilter : InClassBodyFilter() { + override fun isAcceptable(element : Any?, context : PsiElement?) : Boolean { + if (super.isAcceptable(element, context)) { + fun isLeafAndClass(psiElement: PsiElement?) = + psiElement is LeafPsiElement && psiElement.getElementType() == JetTokens.CLASS_KEYWORD + + val ps = context?.getPrevSibling() + return isLeafAndClass(if (ps is PsiWhiteSpace) ps.getPrevSibling() else ps) + } + + return false + } + } + + private object InPropertyBodyFilter : PositionElementFilter() { + override fun isAcceptable(element: Any?, context: PsiElement?): Boolean { + if (element !is PsiElement) return false + + val property = PsiTreeUtil.getParentOfType(context, javaClass(), false) + return property != null && isAfterName(property, (element as PsiElement)) + } + + private fun isAfterName(property : JetProperty, element : PsiElement) : Boolean { + var iterableChild = property.getFirstChild() + while (iterableChild != null) { + val child = iterableChild!! + + if (PsiTreeUtil.isAncestor(child, element, false)) { + break + } + + if (child.getNode()?.getElementType() == JetTokens.IDENTIFIER) { + return true + } + + iterableChild = child.getNextSibling() + } + + return false + } + } +}