Add factors based on time between updates of the prefix
authorVitaliy.Bibaev <vitaliy.bibaev@jetbrains.com>
Tue, 5 Dec 2017 13:16:02 +0000 (16:16 +0300)
committerVitaliy.Bibaev <vitaliy.bibaev@jetbrains.com>
Tue, 5 Dec 2017 13:16:02 +0000 (16:16 +0300)
plugins/stats-collector/src/com/intellij/stats/completion/CompletionTrackerInitializer.kt
plugins/stats-collector/src/com/intellij/stats/completion/TimeBetweenTypingTracker.kt [new file with mode: 0644]
plugins/stats-collector/src/com/intellij/stats/personalization/UserFactorDescriptions.kt
plugins/stats-collector/src/com/intellij/stats/personalization/impl/DailyAggregatedDoubleFactor.kt
plugins/stats-collector/src/com/intellij/stats/personalization/impl/TimeBetweenTypingFactors.kt [new file with mode: 0644]
plugins/stats-collector/src/com/intellij/stats/personalization/impl/UserFactorStorageBase.kt
plugins/stats-collector/src/com/intellij/stats/personalization/impl/UserFactorsManagerImpl.kt

index 1d9e5a5ac6d906474a501520fa1b94a1da4526ba..04820e41c92ddab8c2458d3b63bfe75f7c7a9c79 100644 (file)
@@ -67,9 +67,11 @@ class CompletionTrackerInitializer(experimentHelper: WebServiceStatus): Applicat
             val tracker = actionsTracker(lookup, experimentHelper)
             actionListener.listener = tracker
             lookup.addLookupListener(tracker)
-            lookup.addLookupListener(LookupCompletedTracker())
             lookup.setPrefixChangeListener(tracker)
 
+            // setPrefixChangeListener has addPrefixChangeListener semantics
+            lookup.setPrefixChangeListener(TimeBetweenTypingTracker(lookup.project))
+            lookup.addLookupListener(LookupCompletedTracker())
             lookup.addLookupListener(LookupStartedTracker())
         }
     }
diff --git a/plugins/stats-collector/src/com/intellij/stats/completion/TimeBetweenTypingTracker.kt b/plugins/stats-collector/src/com/intellij/stats/completion/TimeBetweenTypingTracker.kt
new file mode 100644 (file)
index 0000000..17939c3
--- /dev/null
@@ -0,0 +1,37 @@
+package com.intellij.stats.completion
+
+import com.intellij.codeInsight.lookup.impl.PrefixChangeListener
+import com.intellij.openapi.project.Project
+import com.intellij.stats.personalization.UserFactorDescriptions
+import com.intellij.stats.personalization.UserFactorStorage
+import java.util.concurrent.TimeUnit
+
+/**
+ * @author Vitaliy.Bibaev
+ */
+class TimeBetweenTypingTracker(private val project: Project) : PrefixChangeListener {
+    private companion object {
+        val MAX_ALLOWED_DELAY = TimeUnit.SECONDS.toMillis(10)
+    }
+
+    private var lastTypingTime: Long = -1L
+
+    override fun beforeAppend(c: Char) = prefixChanged()
+    override fun beforeTruncate() = prefixChanged()
+
+    private fun prefixChanged() {
+        if (lastTypingTime == -1L) {
+            lastTypingTime = System.currentTimeMillis()
+            return
+        }
+
+        val currentTime = System.currentTimeMillis()
+        val delay = currentTime - lastTypingTime
+        if (delay > MAX_ALLOWED_DELAY) return
+        UserFactorStorage.applyOnBoth(project, UserFactorDescriptions.TIME_BETWEEN_TYPING) { updater ->
+            updater.fireTypingPerformed(delay.toInt())
+        }
+
+        lastTypingTime = currentTime
+    }
+}
\ No newline at end of file
index 9d0d184a0ab02c0818b4b2f2ccf70272d384d844..4fd3dce47a98982bb1d5f6dd587b575335ca0dc2 100644 (file)
@@ -12,6 +12,7 @@ object UserFactorDescriptions {
     val COMPLETION_USAGE = Descriptor("completionUsage", ::CompletionUsageUpdater, ::CompletionUsageReader)
     val PREFIX_LENGTH_ON_COMPLETION = Descriptor("prefixLength", ::PrefixLengthUpdater, ::PrefixLengthReader)
     val SELECTED_ITEM_POSITION = Descriptor("itemPosition", ::ItemPositionUpdater, ::ItemPositionReader)
+    val TIME_BETWEEN_TYPING = Descriptor("timeBetweenTyping", ::TimeBetweenTypingUpdater, ::TimeBetweenTypingReader)
 
     class Descriptor<out U : FactorUpdater, out R : FactorReader>(
             override val factorId: String,
index 0753e4e1c76756ea75b0edcb0b12da853780250b..c41cd05f39df240a5cb6f721f8b37969a439b1bb 100644 (file)
@@ -18,6 +18,7 @@ interface MutableDoubleFactor : DailyAggregatedDoubleFactor {
     fun addObservation(key: String, value: Double)
 
     fun setOnDate(date: String, key: String, value: Double)
+    fun updateOnDate(date: String, updater: MutableMap<String, Double>.() -> Unit)
 }
 
 private fun DailyAggregatedDoubleFactor.aggregateBy(reduce: (Double, Double) -> Double): Map<String, Double> {
diff --git a/plugins/stats-collector/src/com/intellij/stats/personalization/impl/TimeBetweenTypingFactors.kt b/plugins/stats-collector/src/com/intellij/stats/personalization/impl/TimeBetweenTypingFactors.kt
new file mode 100644 (file)
index 0000000..7177df1
--- /dev/null
@@ -0,0 +1,54 @@
+package com.intellij.stats.personalization.impl
+
+import com.intellij.stats.personalization.*
+
+/**
+ * @author Vitaliy.Bibaev
+ */
+class TimeBetweenTypingReader(factor: DailyAggregatedDoubleFactor) : UserFactorReaderBase(factor) {
+    fun averageTime(): Double? {
+        var totalCount = 0
+        var average = 0.0
+        for (onDate in factor.availableDates().mapNotNull { factor.onDate(it) }) {
+            val avg = onDate["average"]
+            val count = onDate["count"]?.toInt()
+            if (avg != null && count != null && count > 0) {
+                average = mergeAverage(totalCount, average, count, avg)
+                totalCount += count
+            }
+        }
+
+        return average
+    }
+}
+
+class TimeBetweenTypingUpdater(factor: MutableDoubleFactor) : UserFactorUpdaterBase(factor) {
+    fun fireTypingPerformed(delayMs: Int) {
+        factor.updateOnDate(DateUtil.today()) {
+            val count = get("count")?.toInt()
+            val avg = get("average")
+            if (count != null && avg != null) {
+                val newAverage = mergeAverage(1, delayMs.toDouble(), count, avg)
+                update(this, 1 + count, newAverage)
+            } else {
+                update(this, 1, delayMs.toDouble())
+            }
+        }
+    }
+
+    private fun update(map: MutableMap<String, Double>, count: Int, avg: Double) {
+        map["count"] = count.toDouble()
+        map["average"] = avg
+    }
+}
+
+private fun mergeAverage(n1: Int, avg1: Double, n2: Int, avg2: Double): Double {
+    if (n1 == 0 && n2 == 0) return 0.0
+    val total = (n1 + n2).toDouble()
+    return (n1 / total) * avg1 + (n2 / total) * avg2
+}
+
+class AverageTimeBetweenTyping
+    : UserFactorBase<TimeBetweenTypingReader>("averageTimeBetweenTyping", UserFactorDescriptions.TIME_BETWEEN_TYPING) {
+    override fun compute(reader: TimeBetweenTypingReader): String? = reader.averageTime()?.toString()
+}
\ No newline at end of file
index 5552bc417c1d019461044e7e62547a164045e3ed..d4f7d8f0b46a3b6103225c8ea278c11b4fb2ea26 100644 (file)
@@ -79,6 +79,19 @@ abstract class UserFactorStorageBase
 
         override fun setOnDate(date: String, key: String, value: Double) = aggregates.onDate(date).set(key, value)
 
+        override fun updateOnDate(date: String, updater: MutableMap<String, Double>.() -> Unit) {
+            aggregates.compute(date) { _, data ->
+                if (data == null) {
+                    val dailyData = DailyData()
+                    updater.invoke(dailyData.data)
+                    dailyData
+                } else {
+                    updater.invoke(data.data)
+                    data
+                }
+            }
+        }
+
         private fun MutableMap<String, DailyData>.onDate(date: String): MutableMap<String, Double> =
                 this.computeIfAbsent(date, { DailyData() }).data
 
index 060827a09fecedff7df0531c9cf7855762e0dca0..707c8a2a093a75af5537ae923eb7aa0af5b67ecf 100644 (file)
@@ -35,6 +35,8 @@ class UserFactorsManagerImpl : UserFactorsManager, ProjectComponent {
         register(AverageSelectedItemPosition())
         register(MaxSelectedItemPosition())
         register(MostFrequentSelectedItemPosition())
+
+        register(AverageTimeBetweenTyping())
     }
 
     override fun getAllFactors(): List<UserFactor> = userFactors.values.toList()