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())
}
}
--- /dev/null
+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
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,
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> {
--- /dev/null
+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
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
register(AverageSelectedItemPosition())
register(MaxSelectedItemPosition())
register(MostFrequentSelectedItemPosition())
+
+ register(AverageTimeBetweenTyping())
}
override fun getAllFactors(): List<UserFactor> = userFactors.values.toList()