From a58f249a01f462a3e1202ccdeb6ddbe098ce88a5 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Fri, 24 Jul 2015 19:02:28 +0300 Subject: [PATCH] KT-8576 Parameter name&type completion: don't auto-popup for the second time #KT-8576 --- .../idea/completion/BasicCompletionSession.kt | 31 +++-- .../idea/completion/LookupCancelWatcher.kt | 109 ++++++++++++++++++ idea/src/META-INF/plugin.xml | 5 +- 3 files changed, 133 insertions(+), 12 deletions(-) create mode 100644 idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupCancelWatcher.kt diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt index 295f1161444..39a3a766442 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt @@ -36,6 +36,7 @@ import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.psi.psiUtil.isAncestor +import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.resolve.scopes.DescriptorKindExclude import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter @@ -144,10 +145,17 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration, } public fun shouldDisableAutoPopup(): Boolean { - return when (completionKind) { - CompletionKind.PARAMETER_NAME, CompletionKind.ANNOTATION_TYPES_OR_PARAMETER_NAME -> !shouldCompleteParameterNameAndType() - else -> false + if (completionKind == CompletionKind.PARAMETER_NAME || completionKind == CompletionKind.ANNOTATION_TYPES_OR_PARAMETER_NAME) { + if (LookupCancelWatcher.getInstance(project).wasAutoPopupRecentlyCancelled(parameters.editor, position.startOffset)) { + return true + } + + if (!shouldCompleteParameterNameAndType()) { + return true + } } + + return false } override fun doComplete() { @@ -159,23 +167,24 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration, override fun accepts(prefix: String, context: ProcessingContext?) = prefix.isNotEmpty() && prefix.last().isUpperCase() }) collector.restartCompletionOnPrefixChange(prefixPattern) - } - if (completionKind == CompletionKind.PARAMETER_NAME || completionKind == CompletionKind.ANNOTATION_TYPES_OR_PARAMETER_NAME) { collector.addLookupElementPostProcessor { lookupElement -> lookupElement.putUserData(KotlinCompletionCharFilter.SUPPRESS_ITEM_SELECTION_BY_CHARS_ON_TYPING, Unit) lookupElement.putUserData(KotlinCompletionCharFilter.HIDE_LOOKUP_ON_COLON, Unit) + if (parameters.isAutoPopup) { + lookupElement.putUserData(LookupCancelWatcher.DO_NOT_REPEAT_AUTO_POPUP_AT, position.startOffset) + } lookupElement } + + parameterNameAndTypeCompletion.addFromParametersInFile(position, resolutionFacade, isVisibleFilter) + flushToResultSet() + + parameterNameAndTypeCompletion.addFromImportedClasses(position, bindingContext, isVisibleFilter) + flushToResultSet() } if (completionKind != CompletionKind.NAMED_ARGUMENTS_ONLY) { - parameterNameAndTypeCompletion?.addFromParametersInFile(position, resolutionFacade, isVisibleFilter) - flushToResultSet() - - parameterNameAndTypeCompletion?.addFromImportedClasses(position, bindingContext, isVisibleFilter) - flushToResultSet() - collector.addDescriptorElements(referenceVariants, suppressAutoInsertion = false) val keywordsPrefix = prefix.substringBefore('@') // if there is '@' in the prefix - use shorter prefix to not loose 'this' etc diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupCancelWatcher.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupCancelWatcher.kt new file mode 100644 index 00000000000..1ea0adca2cd --- /dev/null +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupCancelWatcher.kt @@ -0,0 +1,109 @@ +/* + * 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 + +import com.intellij.codeInsight.lookup.Lookup +import com.intellij.codeInsight.lookup.LookupAdapter +import com.intellij.codeInsight.lookup.LookupEvent +import com.intellij.codeInsight.lookup.LookupManager +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.AbstractProjectComponent +import com.intellij.openapi.components.ServiceManager +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.EditorFactory +import com.intellij.openapi.editor.RangeMarker +import com.intellij.openapi.editor.event.* +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Key + +class LookupCancelWatcher(project: Project) : AbstractProjectComponent(project) { + private class Reminiscence(editor: Editor, offset: Int) { + var editor: Editor? = editor + private var marker: RangeMarker? = editor.document.createRangeMarker(offset, offset) + + // forget about auto-popup cancellation when the caret is moved to the start or before it + private var editorListener: CaretListener? = object : CaretAdapter() { + override fun caretPositionChanged(e: CaretEvent) { + if (!marker!!.isValid || editor.logicalPositionToOffset(e.newPosition) <= offset) { + dispose() + } + } + } + + init { + ApplicationManager.getApplication()!!.assertIsDispatchThread() + editor.caretModel.addCaretListener(editorListener!!) + } + + fun matches(editor: Editor, offset: Int): Boolean { + return editor == this.editor && marker?.startOffset == offset + } + + fun dispose() { + ApplicationManager.getApplication()!!.assertIsDispatchThread() + if (marker != null) { + editor!!.caretModel.removeCaretListener(editorListener!!) + marker = null + editor = null + editorListener = null + } + } + } + + private var lastReminiscence: Reminiscence? = null + + companion object { + fun getInstance(project: Project): LookupCancelWatcher = ServiceManager.getService(project, javaClass()) + + val DO_NOT_REPEAT_AUTO_POPUP_AT = Key("LookupCancelWatcher.DO_NOT_REPEAT_AUTO_POPUP_AT") + } + + fun wasAutoPopupRecentlyCancelled(editor: Editor, offset: Int): Boolean { + return lastReminiscence?.matches(editor, offset) ?: false + } + + private val lookupCancelListener = object : LookupAdapter() { + override fun lookupCanceled(event: LookupEvent) { + val lookup = event.lookup + if (event.isCanceledExplicitly && lookup.isCompletion) { + val offset = lookup.currentItem?.getUserData(DO_NOT_REPEAT_AUTO_POPUP_AT) + if (offset != null) { + lastReminiscence?.dispose() + lastReminiscence = Reminiscence(lookup.editor, offset) + } + } + } + } + + override fun initComponent() { + EditorFactory.getInstance().addEditorFactoryListener( + object : EditorFactoryAdapter() { + override fun editorReleased(event: EditorFactoryEvent) { + if (lastReminiscence?.editor == event.editor) { + lastReminiscence!!.dispose() + } + } + }, + myProject) + + LookupManager.getInstance(myProject).addPropertyChangeListener { event -> + if (event.propertyName == LookupManager.PROP_ACTIVE_LOOKUP) { + (event.newValue as Lookup?)?.addLookupListener(lookupCancelListener) + } + } + } +} diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index bac65b38703..ea9fda162d8 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -36,6 +36,9 @@ org.jetbrains.kotlin.idea.js.KotlinJavaScriptLibraryManager + + org.jetbrains.kotlin.idea.completion.LookupCancelWatcher + @@ -310,7 +313,7 @@