-
Notifications
You must be signed in to change notification settings - Fork 5.2k
/
StatisticsUpdate.kt
149 lines (129 loc) · 5.67 KB
/
StatisticsUpdate.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.completion
import com.intellij.codeInsight.lookup.Lookup
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupEvent
import com.intellij.featureStatistics.FeatureStatisticsUpdateListener
import com.intellij.featureStatistics.FeatureUsageTracker
import com.intellij.featureStatistics.FeatureUsageTrackerImpl
import com.intellij.openapi.Disposable
import com.intellij.openapi.WeakReferenceDisposableWrapper
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.WriteIntentReadAction
import com.intellij.openapi.application.writeIntentReadAction
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.editor.event.DocumentListener
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.statistics.StatisticsInfo
import com.intellij.psi.statistics.StatisticsManager
import com.intellij.util.Alarm
import com.intellij.util.application
import org.jetbrains.annotations.VisibleForTesting
class StatisticsUpdate
private constructor(private val myInfo: StatisticsInfo) : Disposable {
private var mySpared: Int = 0
override fun dispose() {}
fun addSparedChars(lookup: Lookup, item: LookupElement, context: InsertionContext) {
val textInserted: String
if (context.offsetMap.containsOffset(CompletionInitializationContext.START_OFFSET) &&
context.offsetMap.containsOffset(InsertionContext.TAIL_OFFSET) &&
context.tailOffset >= context.startOffset) {
textInserted = context.document.immutableCharSequence.subSequence(context.startOffset, context.tailOffset).toString()
}
else {
textInserted = item.lookupString
}
val withoutSpaces = StringUtil.replace(textInserted, listOf(" ", "\t", "\n"), listOf("", "", ""))
var spared = withoutSpaces.length - lookup.itemPattern(item).length
val completionChar = context.completionChar
if (!LookupEvent.isSpecialCompletionChar(completionChar) && withoutSpaces.contains(completionChar.toString())) {
spared--
}
if (spared > 0) {
mySpared += spared
application.messageBus.syncPublisher(FeatureStatisticsUpdateListener.TOPIC).completionStatUpdated(spared)
}
}
fun trackStatistics(context: InsertionContext) {
if (ourPendingUpdate !== this) {
return
}
if (!context.offsetMap.containsOffset(CompletionInitializationContext.START_OFFSET)) {
return
}
val document = context.document
val startOffset = context.startOffset
val tailOffset =
if (context.editor.selectionModel.hasSelection()) context.editor.selectionModel.selectionStart
else context.editor.caretModel.offset
if (startOffset < 0 || tailOffset <= startOffset) {
return
}
val marker = document.createRangeMarker(startOffset, tailOffset)
val listener = DocumentChangeListener(document, marker)
document.addDocumentListener(listener)
// Avoid hard-ref from Disposer to the document through the listener.
// The document could be some text field with the project scope life-time,
// don't make it leak past closing of the project.
Disposer.register(this, WeakReferenceDisposableWrapper(listener))
ourStatsAlarm.addRequest({
if (ourPendingUpdate === this) {
//readaction is not enough
WriteIntentReadAction.run {
applyLastCompletionStatisticsUpdate()
}
}
}, 20 * 1000)
Disposer.register(this, Disposable {
ourStatsAlarm.cancelAllRequests()
})
}
private class DocumentChangeListener(val document: Document,
val marker: RangeMarker) : DocumentListener, Disposable {
override fun beforeDocumentChange(e: DocumentEvent) {
if (!marker.isValid || e.offset > marker.startOffset && e.offset < marker.endOffset) {
cancelLastCompletionStatisticsUpdate()
}
}
override fun dispose() {
document.removeDocumentListener(this)
marker.dispose()
}
}
companion object {
private val ourStatsAlarm = Alarm(ApplicationManager.getApplication())
private var ourPendingUpdate: StatisticsUpdate? = null
init {
Disposer.register(ApplicationManager.getApplication(), Disposable { cancelLastCompletionStatisticsUpdate() })
}
@VisibleForTesting
@JvmStatic
fun collectStatisticChanges(item: LookupElement): StatisticsUpdate {
applyLastCompletionStatisticsUpdate()
val base = StatisticsWeigher.getBaseStatisticsInfo(item, null)
if (base === StatisticsInfo.EMPTY) {
return StatisticsUpdate(StatisticsInfo.EMPTY)
}
val update = StatisticsUpdate(base)
ourPendingUpdate = update
Disposer.register(update, Disposable { ourPendingUpdate = null })
return update
}
@JvmStatic
fun cancelLastCompletionStatisticsUpdate() {
ourPendingUpdate?.let { Disposer.dispose(it) }
assert(ourPendingUpdate == null)
}
@JvmStatic
fun applyLastCompletionStatisticsUpdate() {
ourPendingUpdate?.let {
StatisticsManager.getInstance().incUseCount(it.myInfo)
(FeatureUsageTracker.getInstance() as FeatureUsageTrackerImpl).completionStatistics.registerInvocation(it.mySpared)
}
cancelLastCompletionStatisticsUpdate()
}
}
}