diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt index 6e00e8092f5..5423570282f 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt @@ -36,6 +36,9 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.completion.smart.SmartCompletion import org.jetbrains.kotlin.idea.completion.smart.SmartCompletionSession +import org.jetbrains.kotlin.idea.statistics.CompletionFUSCollector.completionStatsData +import org.jetbrains.kotlin.idea.statistics.CompletionTypeStats +import org.jetbrains.kotlin.idea.statistics.FileTypeStats import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* @@ -266,6 +269,20 @@ class KotlinCompletionContributor : CompletionContributor() { result: CompletionResultSet, lookupElementPostProcessor: ((LookupElement) -> LookupElement)? = null ) { + val name = parameters.originalFile.virtualFile?.name ?: "default.kts" + completionStatsData = completionStatsData?.copy( + completionType = when (parameters.completionType) { + CompletionType.BASIC -> CompletionTypeStats.BASIC + CompletionType.CLASS_NAME -> CompletionTypeStats.BASIC //there is no class name anymore actually + CompletionType.SMART -> CompletionTypeStats.SMART + }, + fileType = when { + name.endsWith(".kt") -> FileTypeStats.KT + name.endsWith(".gradle.kts") -> FileTypeStats.GRADLEKTS + else -> FileTypeStats.KTS + }, + invocationCount = parameters.invocationCount + ) val position = parameters.position if (position.getNonStrictParentOfType() != null) { // don't stop here, allow other contributors to run 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 index 509e3824273..6a3f3a44076 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupCancelWatcher.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupCancelWatcher.kt @@ -16,6 +16,8 @@ package org.jetbrains.kotlin.idea.completion +import com.intellij.application.subscribe +import com.intellij.codeInsight.completion.CompletionPhaseListener import com.intellij.codeInsight.lookup.Lookup import com.intellij.codeInsight.lookup.LookupEvent import com.intellij.codeInsight.lookup.LookupListener @@ -31,6 +33,9 @@ import com.intellij.openapi.editor.event.EditorFactoryEvent import com.intellij.openapi.editor.event.EditorFactoryListener import com.intellij.openapi.project.Project import com.intellij.openapi.util.Key +import org.jetbrains.kotlin.idea.statistics.CompletionFUSCollector +import org.jetbrains.kotlin.idea.statistics.CompletionFUSCollector.completionStatsData +import org.jetbrains.kotlin.idea.statistics.FinishReasonStats class LookupCancelWatcher(val project: Project) : ProjectComponent { private class Reminiscence(editor: Editor, offset: Int) { @@ -94,6 +99,21 @@ class LookupCancelWatcher(val project: Project) : ProjectComponent { } override fun initComponent() { + CompletionPhaseListener.TOPIC.subscribe(project, CompletionPhaseListener { isCompletionRunning -> + if (isCompletionRunning) { + if (completionStatsData != null) { + completionStatsData = completionStatsData?.copy(finishReason = FinishReasonStats.INTERRUPTED) + CompletionFUSCollector.log(completionStatsData) + completionStatsData = null + } + completionStatsData = CompletionFUSCollector.CompletionStatsData(System.currentTimeMillis()) + } + + if (!isCompletionRunning) { + completionStatsData = completionStatsData?.copy(finishTime = System.currentTimeMillis()) + } + }) + EditorFactory.getInstance().addEditorFactoryListener( object : EditorFactoryListener { override fun editorReleased(event: EditorFactoryEvent) { @@ -107,7 +127,31 @@ class LookupCancelWatcher(val project: Project) : ProjectComponent { LookupManager.getInstance(project).addPropertyChangeListener { event -> if (event.propertyName == LookupManager.PROP_ACTIVE_LOOKUP) { - (event.newValue as Lookup?)?.addLookupListener(lookupCancelListener) + val lookup = event.newValue as Lookup? + lookup?.addLookupListener(lookupCancelListener) + lookup?.addLookupListener(object : LookupListener { + override fun lookupShown(event: LookupEvent) { + completionStatsData = completionStatsData?.copy(shownTime = System.currentTimeMillis()) + } + + override fun lookupCanceled(event: LookupEvent) { + completionStatsData = completionStatsData?.copy( + finishReason = if (event.isCanceledExplicitly) FinishReasonStats.CANCELLED else FinishReasonStats.HIDDEN + ) + CompletionFUSCollector.log(completionStatsData) + completionStatsData = null + } + + override fun itemSelected(event: LookupEvent) { + val eventLookup = event.lookup + val lookupIndex = eventLookup.items.indexOf(eventLookup.currentItem) + if (lookupIndex >= 0) completionStatsData = completionStatsData?.copy(selectedItem = lookupIndex) + + completionStatsData = completionStatsData?.copy(finishReason = FinishReasonStats.DONE) + CompletionFUSCollector.log(completionStatsData) + completionStatsData = null + } + }) } } } diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/statistics/CompletionFUSCollector.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/statistics/CompletionFUSCollector.kt new file mode 100644 index 00000000000..ebc40ba570f --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/statistics/CompletionFUSCollector.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.statistics + +object CompletionFUSCollector { + private const val FileTypeAttribute = "file_type" + private const val WindowPopulationTimeAttribute = "window_population_time" + private const val CompletionEventAttribute = "completion_event" + private const val ChoiceAtPositionAttribute = "choice_at_position" + private const val CompletionTypeAttribute = "completion_type" + + private const val Chosen = "chosen" + private const val NotChosen = "not_chosen" + + private const val EventName = "Completion" + + @Volatile + var completionStatsData: CompletionStatsData? = null + + data class CompletionStatsData( + val startTime: Long, + val shownTime: Long? = null, + val finishTime: Long? = null, + val completionType: CompletionTypeStats? = null, + val fileType: FileTypeStats? = null, + val finishReason: FinishReasonStats? = null, + val selectedItem: Int? = null, + val invocationCount: Int? = null + ) + + fun log(completionStatsData: CompletionStatsData?) { + /*if (completionStatsData == null) return + val data = mutableMapOf() + .plus(Pair(WindowPopulationTimeAttribute, time)) + .plus(Pair(FileTypeAttribute, fileType.toString())) + .plus(Pair(CompletionTypeAttribute, completionType.toString())) + + if (choicePosition != null) { + data.plus(Pair(ChoiceAtPositionAttribute, choicePosition)) + .plus(Pair(CompletionEventAttribute, Chosen)) + KotlinFUSLogger.log(FUSEventGroups.Editor, EventName, data) + } else { + data.plus(Pair(CompletionEventAttribute, NotChosen)) + KotlinFUSLogger.log(FUSEventGroups.Editor, EventName, data) + }*/ + } +} + +enum class FileTypeStats { + KT, GRADLEKTS, KTS +} + +enum class CompletionTypeStats { + BASIC, SMART +} + +enum class FinishReasonStats { + DONE, CANCELLED, HIDDEN, INTERRUPTED +} \ No newline at end of file