mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-02-04 23:39:07 +07:00
JBAI-11103 Raw version of user-dependent features implementation
Updated some names, got rid of a redundant UserFactor class. Some more cleaning & redundancy removing Updated some names, got rid of a redundant UserFactor class. Some more cleaning & redundancy removing Class naming conflicts resolved Minor logic change in ARFactors Replaced the custom `Day` and `DayImpl` classes with library `LocalDate` for date representation AR factor minar change AR Factors implementation bug fix fixup! JBAI-11103 changed basic properties to getters and added some documentation (cherry picked from commit c3951f4df63cc556f5ebe963426e9ee593150761) IJ-CR-159351 GitOrigin-RevId: 5f4b3a085483981e890e37cf2e9a2f232fed3f4a
This commit is contained in:
committed by
intellij-monorepo-bot
parent
62095078ba
commit
d1bba1490c
2
.idea/runConfigurations/IDEA.xml
generated
2
.idea/runConfigurations/IDEA.xml
generated
@@ -7,7 +7,7 @@
|
||||
<option name="MAIN_CLASS_NAME" value="com.intellij.idea.Main" />
|
||||
<module name="intellij.idea.community.main" />
|
||||
<shortenClasspath name="ARGS_FILE" />
|
||||
<option name="VM_PARAMETERS" value="-Xmx2g -XX:ReservedCodeCacheSize=240m -XX:SoftRefLRUPolicyMSPerMB=50 -XX:MaxJavaStackTraceDepth=10000 -ea -Dsun.io.useCanonCaches=false -Dapple.laf.useScreenMenuBar=true -Dsun.awt.disablegrab=true -Didea.jre.check=true -Didea.is.internal=true -Didea.debug.mode=true -Djdk.attach.allowAttachSelf -Dfus.internal.test.mode=true -Dkotlinx.coroutines.debug=off -Djdk.module.illegalAccess.silent=true -Didea.config.path=../config/idea -Didea.system.path=../system/idea -Didea.initially.ask.config=true --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/jdk.internal.vm=ALL-UNNAMED --add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.desktop/com.apple.eawt.event=ALL-UNNAMED --add-opens=java.desktop/com.apple.eawt=ALL-UNNAMED --add-opens=java.desktop/com.apple.laf=ALL-UNNAMED --add-opens=java.desktop/com.sun.java.swing.plaf.gtk=ALL-UNNAMED --add-opens=java.desktop/java.awt.dnd.peer=ALL-UNNAMED --add-opens=java.desktop/java.awt.event=ALL-UNNAMED --add-opens=java.desktop/java.awt.image=ALL-UNNAMED --add-opens=java.desktop/java.awt.peer=ALL-UNNAMED --add-opens=java.desktop/java.awt=ALL-UNNAMED --add-opens=java.desktop/javax.swing.plaf.basic=ALL-UNNAMED --add-opens=java.desktop/javax.swing.text=ALL-UNNAMED --add-opens=java.desktop/javax.swing.text.html=ALL-UNNAMED --add-opens=java.desktop/javax.swing=ALL-UNNAMED --add-opens=java.desktop/sun.awt.X11=ALL-UNNAMED --add-opens=java.desktop/sun.awt.datatransfer=ALL-UNNAMED --add-opens=java.desktop/sun.awt.image=ALL-UNNAMED --add-opens=java.desktop/sun.awt.windows=ALL-UNNAMED --add-opens=java.desktop/sun.awt=ALL-UNNAMED --add-opens=java.desktop/sun.font=ALL-UNNAMED --add-opens=java.desktop/sun.java2d=ALL-UNNAMED --add-opens=java.desktop/sun.lwawt.macosx=ALL-UNNAMED --add-opens=java.desktop/sun.lwawt=ALL-UNNAMED --add-opens=java.desktop/sun.swing=ALL-UNNAMED --add-opens=jdk.attach/sun.tools.attach=ALL-UNNAMED --add-opens=jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED --add-opens=jdk.jdi/com.sun.tools.jdi=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED -Didea.platform.prefix=Idea -Djava.system.class.loader=com.intellij.util.lang.PathClassLoader" />
|
||||
<option name="VM_PARAMETERS" value="-Xmx2g -XX:ReservedCodeCacheSize=240m -XX:SoftRefLRUPolicyMSPerMB=50 -XX:MaxJavaStackTraceDepth=10000 -ea -Dsun.io.useCanonCaches=false -Dapple.laf.useScreenMenuBar=true -Dsun.awt.disablegrab=true -Didea.jre.check=true -Didea.is.internal=true -Didea.debug.mode=true -Djdk.attach.allowAttachSelf -Dfus.internal.test.mode=true -Dkotlinx.coroutines.debug=off -Djdk.module.illegalAccess.silent=true -Didea.config.path=../config/idea -Didea.system.path=../system/idea -Didea.initially.ask.config=true --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/jdk.internal.vm=ALL-UNNAMED --add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.desktop/com.apple.eawt.event=ALL-UNNAMED --add-opens=java.desktop/com.apple.eawt=ALL-UNNAMED --add-opens=java.desktop/com.apple.laf=ALL-UNNAMED --add-opens=java.desktop/com.sun.java.swing.plaf.gtk=ALL-UNNAMED --add-opens=java.desktop/java.awt.dnd.peer=ALL-UNNAMED --add-opens=java.desktop/java.awt.event=ALL-UNNAMED --add-opens=java.desktop/java.awt.image=ALL-UNNAMED --add-opens=java.desktop/java.awt.peer=ALL-UNNAMED --add-opens=java.desktop/java.awt=ALL-UNNAMED --add-opens=java.desktop/javax.swing.plaf.basic=ALL-UNNAMED --add-opens=java.desktop/javax.swing.text=ALL-UNNAMED --add-opens=java.desktop/javax.swing.text.html=ALL-UNNAMED --add-opens=java.desktop/javax.swing=ALL-UNNAMED --add-opens=java.desktop/sun.awt.X11=ALL-UNNAMED --add-opens=java.desktop/sun.awt.datatransfer=ALL-UNNAMED --add-opens=java.desktop/sun.awt.image=ALL-UNNAMED --add-opens=java.desktop/sun.awt.windows=ALL-UNNAMED --add-opens=java.desktop/sun.awt=ALL-UNNAMED --add-opens=java.desktop/sun.font=ALL-UNNAMED --add-opens=java.desktop/sun.java2d=ALL-UNNAMED --add-opens=java.desktop/sun.lwawt.macosx=ALL-UNNAMED --add-opens=java.desktop/sun.lwawt=ALL-UNNAMED --add-opens=java.desktop/sun.swing=ALL-UNNAMED --add-opens=jdk.attach/sun.tools.attach=ALL-UNNAMED --add-opens=jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED --add-opens=jdk.jdi/com.sun.tools.jdi=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED -Didea.platform.prefix=Idea -Djava.system.class.loader=com.intellij.util.lang.PathClassLoader -Dfus.internal.test.mode=true" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/bin" />
|
||||
<RunnerSettings RunnerId="Debug">
|
||||
<option name="DEBUG_PORT" value="60786" />
|
||||
|
||||
@@ -40,5 +40,6 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.lang" />
|
||||
<orderEntry type="module" module-name="fleet.util.core" />
|
||||
<orderEntry type="library" name="kotlinx-serialization-core" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util.jdom" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -7,6 +7,7 @@ import com.intellij.codeInsight.inline.completion.listeners.typing.InlineComplet
|
||||
import com.intellij.codeInsight.inline.completion.logs.InlineCompletionLogsListener
|
||||
import com.intellij.codeInsight.inline.completion.logs.InlineCompletionUsageTracker
|
||||
import com.intellij.codeInsight.inline.completion.logs.InlineCompletionUsageTracker.ShownEvents.FinishType
|
||||
import com.intellij.codeInsight.inline.completion.logs.UserFactorsListener
|
||||
import com.intellij.codeInsight.inline.completion.session.InlineCompletionContext
|
||||
import com.intellij.codeInsight.inline.completion.session.InlineCompletionInvalidationListener
|
||||
import com.intellij.codeInsight.inline.completion.session.InlineCompletionSession
|
||||
@@ -80,6 +81,8 @@ abstract class InlineCompletionHandler @ApiStatus.Internal constructor(
|
||||
val logsListener = InlineCompletionLogsListener(editor)
|
||||
addEventListener(logsListener)
|
||||
invalidationListeners.addListener(logsListener)
|
||||
val userFactorsListener = UserFactorsListener()
|
||||
addEventListener(userFactorsListener)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,11 @@ package com.intellij.codeInsight.inline.completion.logs
|
||||
import com.intellij.codeInsight.inline.completion.InlineCompletionRequest
|
||||
import com.intellij.codeInsight.inline.completion.features.InlineCompletionFeaturesCollector
|
||||
import com.intellij.codeInsight.inline.completion.features.InlineCompletionFeaturesScopeAnalyzer.ScopeType
|
||||
import com.intellij.codeInsight.inline.completion.logs.statistics.DECAY_DURATIONS
|
||||
import com.intellij.codeInsight.inline.completion.logs.statistics.UserFactorDescriptions
|
||||
import kotlin.time.Duration
|
||||
import com.intellij.codeInsight.inline.completion.logs.statistics.UserFactorStorage
|
||||
import com.intellij.codeInsight.inline.completion.logs.statistics.timeSince
|
||||
import com.intellij.internal.statistic.eventLog.events.EventField
|
||||
import com.intellij.internal.statistic.eventLog.events.EventFields
|
||||
import com.intellij.internal.statistic.eventLog.events.EventPair
|
||||
@@ -24,7 +29,34 @@ internal object InlineCompletionContextLogs {
|
||||
val featureCollectorBased = InlineCompletionFeaturesCollector.get(request.file.language)?.let {
|
||||
captureFeatureCollectorBased(request.file, request.startOffset, it, element)
|
||||
}
|
||||
return simple + typingFeatures + featureCollectorBased.orEmpty()
|
||||
val userFeatures = captureUserStatisticsFactors()
|
||||
return simple + typingFeatures + featureCollectorBased.orEmpty() + userFeatures
|
||||
}
|
||||
|
||||
private fun captureUserStatisticsFactors(): List<EventPair<*>> = buildList {
|
||||
val storage = UserFactorStorage.getInstance()
|
||||
val accRateFactorsReader = storage.getFactorReader(UserFactorDescriptions.ACCEPTANCE_RATE_FACTORS)
|
||||
|
||||
// Add decaying features
|
||||
for (duration in DECAY_DURATIONS) {
|
||||
val (selectionField, showupField, acceptanceField) = Logs.DECAYING_FEATURES[duration] ?: continue
|
||||
add(selectionField with accRateFactorsReader.selectionCountDecayedBy(duration))
|
||||
add(showupField with accRateFactorsReader.showUpCountDecayedBy(duration))
|
||||
add(acceptanceField with accRateFactorsReader.smoothedAcceptanceRate(duration))
|
||||
}
|
||||
add(Logs.PREV_SELECTED with (accRateFactorsReader.prevSelected()?.let { it != 0.0 } ?: false))
|
||||
add(Logs.TIME_SINCE_LAST_SHOWUP with (accRateFactorsReader.lastShowUpTimeToday()?.let(::timeSince) ?: 0))
|
||||
add(Logs.TIME_SINCE_LAST_SELECTION with (accRateFactorsReader.lastSelectionTimeToday()?.let(::timeSince) ?: 0))
|
||||
val finishRatiosReader = storage.getFactorReader(UserFactorDescriptions.COMPLETION_FINISH_TYPE)
|
||||
if(finishRatiosReader.getTotalCount() > 0) {
|
||||
val total = finishRatiosReader.getTotalCount()
|
||||
add(Logs.SELECTED_RATIO with (finishRatiosReader.getCountByKey("selected")) / total)
|
||||
add(Logs.INVALIDATED_RATIO with (finishRatiosReader.getCountByKey("invalidated")) / total)
|
||||
add(Logs.EXPLICIT_CANCEL_RATIO with (finishRatiosReader.getCountByKey("explicitCancel")) / total)
|
||||
}
|
||||
val prefixLengthReader = storage.getFactorReader(UserFactorDescriptions.PREFIX_LENGTH_ON_COMPLETION)
|
||||
add(Logs.MOST_FREQUENT_PREFIX_LENGTH with ((prefixLengthReader.getCountsByPrefixLength().maxByOrNull { it.value }?.key) ?: 0))
|
||||
add(Logs.AVERAGE_PREFIX_LENGTH with ((prefixLengthReader.getAveragePrefixLength()) ?: 0.0))
|
||||
}
|
||||
|
||||
private fun captureSimple(psiFile: PsiFile, editor: Editor, offset: Int, element: PsiElement?): List<EventPair<*>> {
|
||||
@@ -230,6 +262,7 @@ internal object InlineCompletionContextLogs {
|
||||
}
|
||||
|
||||
private object Logs : PhasedLogs(InlineCompletionLogsContainer.Phase.INLINE_API_STARTING) {
|
||||
private fun Duration.toDescription(): String = toString().replace(" ", "")
|
||||
val ELEMENT_PREFIX_LENGTH = register(EventFields.Int("element_prefix_length"))
|
||||
val LINE_NUMBER = register(EventFields.Int("line_number"))
|
||||
val COLUMN_NUMBER = register(EventFields.Int("column_number"))
|
||||
@@ -289,6 +322,28 @@ internal object InlineCompletionContextLogs {
|
||||
register(it)
|
||||
}
|
||||
|
||||
val PREV_SELECTED = register(EventFields.Boolean("prev_selected"))
|
||||
val TIME_SINCE_LAST_SELECTION = register(EventFields.Long("time_since_last_selection", "Duration from previous selected event."))
|
||||
val TIME_SINCE_LAST_SHOWUP = register(EventFields.Long("time_since_last_showup", "Duration from previous showup event."))
|
||||
val SELECTED_RATIO = register(EventFields.Double("selected_ratio"))
|
||||
val INVALIDATED_RATIO = register(EventFields.Double("invalidated_ratio"))
|
||||
val EXPLICIT_CANCEL_RATIO = register(EventFields.Double("explicit_cancel_ratio"))
|
||||
|
||||
val DECAYING_FEATURES: Map<Duration, List<EventField<Double>>> = DECAY_DURATIONS.associateWith { duration ->
|
||||
listOf(
|
||||
register(EventFields.Double("selection_decayed_by_${duration.toDescription()}",
|
||||
"Selection count with exponential decay over ${duration.toDescription()}")),
|
||||
register(EventFields.Double("showup_decayed_by_${duration.toDescription()}",
|
||||
"Show up count with exponential decay over ${duration.toDescription()}")),
|
||||
register(EventFields.Double("acceptance_rate_smoothed_by_${duration.toDescription()}",
|
||||
"Acceptance rate smoothed over ${duration.toDescription()}"))
|
||||
)
|
||||
}
|
||||
|
||||
val AVERAGE_PREFIX_LENGTH = register(EventFields.Double("average_prefix_length"))
|
||||
val MOST_FREQUENT_PREFIX_LENGTH = register(EventFields.Int("most_frequent_prefix_length"))
|
||||
|
||||
|
||||
private fun <T> scopeFeatures(createFeatureDeclaration: (String) -> EventField<T>): List<EventField<T>> {
|
||||
return listOf(
|
||||
register(createFeatureDeclaration("caret")),
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.inline.completion.logs
|
||||
|
||||
import com.intellij.codeInsight.inline.completion.InlineCompletionEventType
|
||||
import com.intellij.codeInsight.inline.completion.InlineCompletionEventAdapter
|
||||
import com.intellij.codeInsight.inline.completion.logs.InlineCompletionUsageTracker.ShownEvents.FinishType
|
||||
import com.intellij.codeInsight.inline.completion.logs.statistics.UserFactorDescriptions
|
||||
import com.intellij.codeInsight.inline.completion.logs.statistics.UserFactorStorage
|
||||
|
||||
private val EXPLICIT_CANCEL_TYPES = setOf(
|
||||
FinishType.MOUSE_PRESSED,
|
||||
FinishType.CARET_CHANGED,
|
||||
FinishType.ESCAPE_PRESSED
|
||||
)
|
||||
|
||||
private val SELECTED_TYPE = FinishType.SELECTED
|
||||
|
||||
private val INVALIDATED_TYPE = FinishType.INVALIDATED
|
||||
|
||||
internal class UserFactorsListener() : InlineCompletionEventAdapter {
|
||||
/**
|
||||
* This field is not thread-safe, please access it only on EDT.
|
||||
*/
|
||||
private var holder = Holder()
|
||||
|
||||
/**
|
||||
* Fields inside [Holder] are not thread-safe, please access them only on EDT.
|
||||
*/
|
||||
private class Holder() {
|
||||
var wasShown: Boolean = false
|
||||
var prefixLength: Int = 0
|
||||
}
|
||||
|
||||
override fun onRequest(event: InlineCompletionEventType.Request) {
|
||||
holder = Holder()
|
||||
val element = if (event.request.startOffset == 0) null else event.request.file.findElementAt(event.request.startOffset - 1)
|
||||
holder.prefixLength = if (element != null) (event.request.startOffset - element.textOffset) else 0
|
||||
}
|
||||
|
||||
override fun onShow(event: InlineCompletionEventType.Show) {
|
||||
holder.wasShown = true
|
||||
}
|
||||
|
||||
override fun onHide(event: InlineCompletionEventType.Hide) {
|
||||
if(holder.wasShown && event.finishType != SELECTED_TYPE) {
|
||||
UserFactorStorage.apply( UserFactorDescriptions.ACCEPTANCE_RATE_FACTORS) {
|
||||
it.fireLookupElementShowUp()
|
||||
}
|
||||
}
|
||||
if(holder.wasShown) {
|
||||
when (event.finishType) {
|
||||
in EXPLICIT_CANCEL_TYPES -> {
|
||||
UserFactorStorage.apply( UserFactorDescriptions.COMPLETION_FINISH_TYPE) {
|
||||
it.fireExplicitCancel()
|
||||
}
|
||||
}
|
||||
SELECTED_TYPE -> {
|
||||
UserFactorStorage.apply( UserFactorDescriptions.ACCEPTANCE_RATE_FACTORS) {
|
||||
it.fireLookupElementSelected()
|
||||
}
|
||||
UserFactorStorage.apply( UserFactorDescriptions.COMPLETION_FINISH_TYPE) {
|
||||
it.fireSelected()
|
||||
}
|
||||
UserFactorStorage.apply(UserFactorDescriptions.PREFIX_LENGTH_ON_COMPLETION) {
|
||||
it.fireCompletionPerformed(holder.prefixLength)
|
||||
}
|
||||
}
|
||||
INVALIDATED_TYPE -> {
|
||||
UserFactorStorage.apply( UserFactorDescriptions.COMPLETION_FINISH_TYPE) {
|
||||
it.fireInvalidated()
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
UserFactorStorage.apply( UserFactorDescriptions.COMPLETION_FINISH_TYPE) {
|
||||
it.fireOther()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.inline.completion.logs.statistics
|
||||
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import kotlin.math.pow
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.days
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
import kotlin.time.DurationUnit
|
||||
|
||||
private const val PREV_SELECTED = "prev_selected"
|
||||
private const val SELECTION = "selection"
|
||||
private const val SHOW_UP = "show_up"
|
||||
|
||||
private const val GLOBAL_ACCEPTANCE_RATE = 0.3
|
||||
private const val GLOBAL_ALPHA = 10
|
||||
|
||||
val DECAY_DURATIONS: List<Duration> = listOf(1.hours, 1.days, 7.days)
|
||||
|
||||
fun lastTimeName(name: String): String = "last_${name}_time"
|
||||
fun decayingCountName(name: String, decayDuration: Duration): String = "${name}_count_decayed_by_$decayDuration"
|
||||
|
||||
class AccRateFactorsReader(factor: DailyAggregatedDoubleFactor) : UserFactorReaderBase(factor) {
|
||||
fun lastSelectionTimeToday(): Double? = getTodayFactor(lastTimeName(SELECTION))
|
||||
fun lastShowUpTimeToday(): Double? = getTodayFactor(lastTimeName(SHOW_UP))
|
||||
fun prevSelected(): Double? = getTodayFactor(PREV_SELECTED)
|
||||
|
||||
private fun getTodayFactor(name: String) = factor.onDate(LocalDate.now())?.get(name)
|
||||
|
||||
fun smoothedAcceptanceRate(decayDuration: Duration): Double {
|
||||
val timestamp = currentTimestamp()
|
||||
return globallySmoothedRatio(selectionCountDecayedBy(decayDuration, timestamp), showUpCountDecayedBy(decayDuration, timestamp))
|
||||
}
|
||||
|
||||
fun selectionCountDecayedBy(decayDuration: Duration, timestamp: Long = currentTimestamp()): Double =
|
||||
factor.aggregateDecayingCount(SELECTION, decayDuration, timestamp)
|
||||
|
||||
fun showUpCountDecayedBy(decayDuration: Duration, timestamp: Long = currentTimestamp()): Double =
|
||||
factor.aggregateDecayingCount(SHOW_UP, decayDuration, timestamp)
|
||||
|
||||
}
|
||||
|
||||
//private fun currentEpochSeconds() = Instant.now().epochSecond.toDouble()
|
||||
private fun currentTimestamp() = Instant.now().toEpochMilli()
|
||||
|
||||
|
||||
class AccRateFactorsUpdater(factor: MutableDoubleFactor) : UserFactorUpdaterBase(factor) {
|
||||
fun fireLookupElementSelected() {
|
||||
val timestamp = currentTimestamp()
|
||||
for (duration in DECAY_DURATIONS) {
|
||||
factor.increment(SELECTION, duration, timestamp)
|
||||
factor.increment(SHOW_UP, duration, timestamp)
|
||||
}
|
||||
factor.updateOnDate(LocalDate.now()) {
|
||||
this[lastTimeName(SELECTION)] = timestamp.toDouble()
|
||||
this[lastTimeName(SHOW_UP)] = timestamp.toDouble()
|
||||
}
|
||||
factor.prevSelected(true)
|
||||
}
|
||||
|
||||
fun fireLookupElementShowUp() {
|
||||
val timestamp = currentTimestamp()
|
||||
for (duration in DECAY_DURATIONS) {
|
||||
factor.increment(SHOW_UP, duration, timestamp)
|
||||
}
|
||||
factor.updateOnDate(LocalDate.now()) {
|
||||
this[lastTimeName(SHOW_UP)] = timestamp.toDouble()
|
||||
}
|
||||
factor.prevSelected(false)
|
||||
}
|
||||
}
|
||||
|
||||
fun DailyAggregatedDoubleFactor.aggregateDecayingCount(name: String, decayDuration: Duration, timestamp: Long): Double {
|
||||
var result = 0.0
|
||||
for (day in availableDays()) {
|
||||
result += get(name, decayDuration, day, timestamp) ?: 0.0
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun DailyAggregatedDoubleFactor.get(name: String, decayDuration: Duration, day: LocalDate, timestamp: Long): Double? {
|
||||
val onDate = onDate(day) ?: return null
|
||||
val lastTimeName = lastTimeName(name)
|
||||
val decayingCountName = decayingCountName(name, decayDuration)
|
||||
val lastTime = onDate[lastTimeName] ?: return null
|
||||
val decayingCount = onDate[decayingCountName]
|
||||
return decayingCount.decay(timestamp - lastTime, decayDuration)
|
||||
}
|
||||
|
||||
fun MutableDoubleFactor.increment(name: String, decayDuration: Duration, timestamp: Long) {
|
||||
updateOnDate(LocalDate.now()) {
|
||||
val lastTimeName = lastTimeName(name)
|
||||
val decayingCountName = decayingCountName(name, decayDuration)
|
||||
this[decayingCountName] = this[lastTimeName]?.let { this[decayingCountName].decay(timestamp - it, decayDuration) + 1 } ?: 1.0
|
||||
//this[lastTimeName] = timestamp.toDouble()
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableDoubleFactor.prevSelected(boolean: Boolean) {
|
||||
updateOnDate(LocalDate.now()) {
|
||||
this[PREV_SELECTED] = if (boolean) 1.0 else 0.0
|
||||
}
|
||||
}
|
||||
|
||||
private fun Double?.decay(duration: Double, decayDuration: Duration) =
|
||||
if (this == null) 0.0
|
||||
else if (duration * this == 0.0) this
|
||||
else 0.5.pow(duration / decayDuration.toDouble(DurationUnit.MILLISECONDS)) * this
|
||||
|
||||
private fun globallySmoothedRatio(quotient: Double?, divisor: Double?) =
|
||||
if (divisor == null) GLOBAL_ACCEPTANCE_RATE
|
||||
else ((quotient ?: 0.0) + GLOBAL_ACCEPTANCE_RATE * GLOBAL_ALPHA) / (divisor + GLOBAL_ALPHA)
|
||||
|
||||
//private fun timeSince(epochSeconds: Double) = (Instant.now().epochSecond - epochSeconds.toLong()).toString()
|
||||
internal fun timeSince(epochSeconds: Double) = Instant.now().toEpochMilli() - epochSeconds.toLong()
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.inline.completion.logs.statistics
|
||||
|
||||
import com.intellij.openapi.components.RoamingType
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.State
|
||||
import com.intellij.openapi.components.Storage
|
||||
|
||||
@Service
|
||||
@State(name = "ApplicationInlineFactors", storages = [(Storage(value = "inline.factors.xml", roamingType = RoamingType.DISABLED))], reportStatistic = false)
|
||||
class ApplicationInlineFactorStorage : UserFactorStorageBase()
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.inline.completion.logs.statistics
|
||||
|
||||
private const val explicitCancelKey = "explicitCancel"
|
||||
private const val selectedKey = "selected"
|
||||
private const val invalidatedKey = "invalidated"
|
||||
private const val otherKey = "other"
|
||||
|
||||
class CompletionFinishTypeReader(private val factor: DailyAggregatedDoubleFactor) : FactorReader {
|
||||
fun getCountByKey(key: String): Double = factor.aggregateSum()[key] ?: 0.0
|
||||
|
||||
fun getTotalCount(): Double =
|
||||
getCountByKey(explicitCancelKey) + getCountByKey(selectedKey) + getCountByKey(invalidatedKey) + getCountByKey(otherKey)
|
||||
}
|
||||
|
||||
class CompletionFinishTypeUpdater(private val factor: MutableDoubleFactor) : FactorUpdater {
|
||||
fun fireExplicitCancel(): Boolean = factor.incrementOnToday(explicitCancelKey)
|
||||
fun fireSelected(): Boolean = factor.incrementOnToday(selectedKey)
|
||||
fun fireInvalidated(): Boolean = factor.incrementOnToday(invalidatedKey)
|
||||
fun fireOther(): Boolean = factor.incrementOnToday(otherKey)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.inline.completion.logs.statistics
|
||||
|
||||
import java.time.LocalDate
|
||||
import kotlin.collections.component1
|
||||
import kotlin.collections.component2
|
||||
import kotlin.collections.iterator
|
||||
|
||||
|
||||
interface DailyAggregatedDoubleFactor {
|
||||
fun availableDays(): List<LocalDate>
|
||||
|
||||
fun onDate(date: LocalDate): Map<String, Double>?
|
||||
}
|
||||
|
||||
interface MutableDoubleFactor : DailyAggregatedDoubleFactor {
|
||||
fun incrementOnToday(key: String): Boolean
|
||||
|
||||
fun updateOnDate(date: LocalDate, updater: MutableMap<String, Double>.() -> Unit): Boolean
|
||||
}
|
||||
|
||||
private fun DailyAggregatedDoubleFactor.aggregateBy(reduce: (Double, Double) -> Double): Map<String, Double> {
|
||||
val result = mutableMapOf<String, Double>()
|
||||
for (onDate in availableDays().mapNotNull(this::onDate)) {
|
||||
for ((key, value) in onDate) {
|
||||
result.compute(key) { _, old -> if (old == null) value else reduce(old, value) }
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun DailyAggregatedDoubleFactor.aggregateSum(): Map<String, Double> = aggregateBy { d1, d2 -> d1 + d2 }
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.inline.completion.logs.statistics
|
||||
|
||||
interface FactorReader
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.inline.completion.logs.statistics
|
||||
|
||||
interface FactorUpdater
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.inline.completion.logs.statistics
|
||||
|
||||
class PrefixLengthReader(factor: DailyAggregatedDoubleFactor) : UserFactorReaderBase(factor) {
|
||||
fun getCountsByPrefixLength(): Map<Int, Double> {
|
||||
return factor.aggregateSum().asIterable().associate { (key, value) -> key.toInt() to value }
|
||||
}
|
||||
|
||||
fun getAveragePrefixLength(): Double? {
|
||||
val lengthToCount = getCountsByPrefixLength()
|
||||
if (lengthToCount.isEmpty()) return null
|
||||
|
||||
val totalChars = lengthToCount.asSequence().sumOf { it.key * it.value }
|
||||
val completionCount = lengthToCount.asSequence().sumOf { it.value }
|
||||
|
||||
if (completionCount == 0.0) return null
|
||||
return totalChars / completionCount
|
||||
}
|
||||
}
|
||||
|
||||
class PrefixLengthUpdater(factor: MutableDoubleFactor) : UserFactorUpdaterBase(factor) {
|
||||
fun fireCompletionPerformed(prefixLength: Int) {
|
||||
factor.incrementOnToday(prefixLength.toString())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.inline.completion.logs.statistics
|
||||
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.State
|
||||
import com.intellij.openapi.components.Storage
|
||||
import com.intellij.openapi.components.StoragePathMacros
|
||||
|
||||
/**
|
||||
* @author Vitaliy.Bibaev
|
||||
*/
|
||||
@Service(Service.Level.PROJECT)
|
||||
@State(name = "ProjectInlineFactors", storages = [Storage(StoragePathMacros.CACHE_FILE)], reportStatistic = false)
|
||||
class ProjectUserFactorStorage : UserFactorStorageBase()
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.inline.completion.logs.statistics
|
||||
|
||||
abstract class UserFactorReaderBase(protected val factor: DailyAggregatedDoubleFactor) : FactorReader
|
||||
|
||||
abstract class UserFactorUpdaterBase(protected val factor: MutableDoubleFactor) : FactorUpdater
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.inline.completion.logs.statistics
|
||||
|
||||
|
||||
|
||||
interface UserFactorDescription<out U : FactorUpdater, out R : FactorReader> {
|
||||
val factorId: String
|
||||
val updaterFactory: (MutableDoubleFactor) -> U
|
||||
val readerFactory: (DailyAggregatedDoubleFactor) -> R
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.inline.completion.logs.statistics
|
||||
|
||||
object UserFactorDescriptions {
|
||||
private val IDS: MutableSet<String> = mutableSetOf()
|
||||
|
||||
val COMPLETION_FINISH_TYPE: UserFactorDescription<CompletionFinishTypeUpdater, CompletionFinishTypeReader> =
|
||||
Descriptor.register("completionFinishedType", ::CompletionFinishTypeUpdater, ::CompletionFinishTypeReader)
|
||||
val PREFIX_LENGTH_ON_COMPLETION: UserFactorDescription<PrefixLengthUpdater, PrefixLengthReader> =
|
||||
Descriptor.register("prefixLength", ::PrefixLengthUpdater, ::PrefixLengthReader)
|
||||
val ACCEPTANCE_RATE_FACTORS: UserFactorDescription<AccRateFactorsUpdater, AccRateFactorsReader> =
|
||||
Descriptor.register("acceptanceRateFactors", ::AccRateFactorsUpdater, ::AccRateFactorsReader)
|
||||
//val TIME_BETWEEN_TYPING: UserFactorDescription<TimeBetweenTypingUpdater, TimeBetweenTypingReader> =
|
||||
// Descriptor.register("timeBetweenTyping", ::TimeBetweenTypingUpdater, ::TimeBetweenTypingReader)
|
||||
|
||||
fun isKnownFactor(id: String): Boolean = id in IDS
|
||||
|
||||
private class Descriptor<out U : FactorUpdater, out R : FactorReader> private constructor(
|
||||
override val factorId: String,
|
||||
override val updaterFactory: (MutableDoubleFactor) -> U,
|
||||
override val readerFactory: (DailyAggregatedDoubleFactor) -> R,
|
||||
) : UserFactorDescription<U, R> {
|
||||
companion object {
|
||||
fun <U : FactorUpdater, R : FactorReader> register(
|
||||
factorId: String,
|
||||
updaterFactory: (MutableDoubleFactor) -> U,
|
||||
readerFactory: (DailyAggregatedDoubleFactor) -> R,
|
||||
): UserFactorDescription<U, R> {
|
||||
assert(!isKnownFactor(factorId)) { "Descriptor with id '$factorId' already exists" }
|
||||
IDS.add(factorId)
|
||||
return Descriptor(factorId, updaterFactory, readerFactory)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.inline.completion.logs.statistics
|
||||
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.Project
|
||||
|
||||
/**
|
||||
* @author Vitaliy.Bibaev
|
||||
*/
|
||||
interface UserFactorStorage {
|
||||
companion object {
|
||||
fun getInstance(): UserFactorStorage = service<ApplicationInlineFactorStorage>()
|
||||
fun getInstance(project: Project): UserFactorStorage = project.service<ProjectUserFactorStorage>()
|
||||
|
||||
fun <U : FactorUpdater> applyOnBoth(project: Project, description: UserFactorDescription<U, *>, updater: (U) -> Unit) {
|
||||
updater(getInstance().getFactorUpdater(description))
|
||||
updater(getInstance(project).getFactorUpdater(description))
|
||||
}
|
||||
|
||||
fun <U : FactorUpdater> apply(description: UserFactorDescription<U, *>, updater: (U) -> Unit) {
|
||||
updater(getInstance().getFactorUpdater(description))
|
||||
}
|
||||
}
|
||||
|
||||
fun <U : FactorUpdater> getFactorUpdater(description: UserFactorDescription<U, *>): U
|
||||
fun <R : FactorReader> getFactorReader(description: UserFactorDescription<*, R>): R
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.inline.completion.logs.statistics
|
||||
|
||||
import com.intellij.openapi.components.PersistentStateComponent
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import java.text.DecimalFormat
|
||||
import org.jdom.Element
|
||||
import java.text.ParseException
|
||||
import java.time.LocalDate
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.*
|
||||
|
||||
abstract class UserFactorStorageBase : UserFactorStorage, PersistentStateComponent<Element> {
|
||||
private companion object {
|
||||
val DOUBLE_VALUE_FORMATTER = DecimalFormat().apply {
|
||||
maximumFractionDigits = 12
|
||||
minimumFractionDigits = 1
|
||||
isGroupingUsed = false
|
||||
}
|
||||
val DATE_FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy")
|
||||
}
|
||||
|
||||
private val state = CollectorState()
|
||||
|
||||
override fun <U : FactorUpdater> getFactorUpdater(description: UserFactorDescription<U, *>): U =
|
||||
description.updaterFactory.invoke(getAggregateFactor(description.factorId))
|
||||
|
||||
override fun <R : FactorReader> getFactorReader(description: UserFactorDescription<*, R>): R =
|
||||
description.readerFactory.invoke(getAggregateFactor(description.factorId))
|
||||
|
||||
override fun getState(): Element {
|
||||
val element = Element("component")
|
||||
state.writeState(element)
|
||||
return element
|
||||
}
|
||||
|
||||
override fun loadState(newState: Element) {
|
||||
state.applyState(newState)
|
||||
}
|
||||
|
||||
private fun getAggregateFactor(factorId: String): MutableDoubleFactor =
|
||||
state.aggregateFactors.computeIfAbsent(factorId) { DailyAggregateFactor() }
|
||||
|
||||
private class CollectorState {
|
||||
val aggregateFactors: MutableMap<String, DailyAggregateFactor> = HashMap()
|
||||
|
||||
fun applyState(element: Element) {
|
||||
aggregateFactors.clear()
|
||||
for (child in element.children) {
|
||||
val factorId = child.getAttributeValue("id")
|
||||
if (child.name == "factor" && factorId != null && UserFactorDescriptions.isKnownFactor(factorId)) {
|
||||
val factor = DailyAggregateFactor.restore(child)
|
||||
if (factor != null) aggregateFactors[factorId] = factor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun writeState(element: Element) {
|
||||
for ((id, factor) in aggregateFactors.asSequence().sortedBy { it.key }) {
|
||||
val factorElement = Element("factor")
|
||||
factorElement.setAttribute("id", id)
|
||||
factor.writeState(factorElement)
|
||||
element.addContent(factorElement)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DailyAggregateFactor private constructor(private val aggregates: SortedMap<LocalDate, DailyData> = sortedMapOf())
|
||||
: MutableDoubleFactor {
|
||||
constructor() : this(sortedMapOf())
|
||||
|
||||
init {
|
||||
ensureLimit()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DAYS_LIMIT = 10
|
||||
|
||||
fun restore(element: Element): DailyAggregateFactor? {
|
||||
val data = sortedMapOf<LocalDate, DailyData>()
|
||||
for (child in element.children) {
|
||||
val date = child.getAttributeValue("date")
|
||||
val day = LocalDate.parse(date, DATE_FORMATTER)
|
||||
if (child.name == "dailyData" && day != null) {
|
||||
val dailyData = DailyData.restore(child)
|
||||
if (dailyData != null) data.put(day, dailyData)
|
||||
}
|
||||
}
|
||||
|
||||
if (data.isEmpty()) return null
|
||||
return DailyAggregateFactor(data)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeState(element: Element) {
|
||||
for ((day, data) in aggregates) {
|
||||
val dailyDataElement = Element("dailyData")
|
||||
dailyDataElement.setAttribute("date", day.format(DATE_FORMATTER))
|
||||
data.writeState(dailyDataElement)
|
||||
element.addContent(dailyDataElement)
|
||||
}
|
||||
}
|
||||
|
||||
override fun availableDays(): List<LocalDate> = aggregates.keys.toList()
|
||||
|
||||
override fun incrementOnToday(key: String): Boolean {
|
||||
return updateOnDate(LocalDate.now()) {
|
||||
compute(key) { _, oldValue -> if (oldValue == null) 1.0 else oldValue + 1.0 }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDate(date: LocalDate): Map<String, Double>? = aggregates[date]?.data
|
||||
|
||||
override fun updateOnDate(date: LocalDate, updater: MutableMap<String, Double>.() -> Unit): Boolean {
|
||||
val old = aggregates[date]
|
||||
if (old != null) {
|
||||
updater.invoke(old.data)
|
||||
return true
|
||||
}
|
||||
|
||||
if (aggregates.size < DAYS_LIMIT || aggregates.firstKey() < date) {
|
||||
val data = DailyData()
|
||||
updater.invoke(data.data)
|
||||
aggregates.put(date, data)
|
||||
ensureLimit()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun ensureLimit() {
|
||||
while (aggregates.size > DAYS_LIMIT) {
|
||||
aggregates.remove(aggregates.firstKey())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DailyData(val data: MutableMap<String, Double> = HashMap()) {
|
||||
companion object {
|
||||
private val LOG = logger<DailyData>()
|
||||
|
||||
fun restore(element: Element): DailyData? {
|
||||
val data = mutableMapOf<String, Double>()
|
||||
for (child in element.children) {
|
||||
if (child.name == "observation") {
|
||||
val dataKey = child.getAttributeValue("name")
|
||||
val dataValue = child.getAttributeValue("value")
|
||||
|
||||
// skip all if any observation is inconsistent
|
||||
val value = try {
|
||||
DOUBLE_VALUE_FORMATTER.parse(dataValue).toDouble()
|
||||
}
|
||||
catch (e: ParseException) {
|
||||
LOG.error(e)
|
||||
return null
|
||||
}
|
||||
data[dataKey] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (data.isEmpty()) return null
|
||||
return DailyData(data)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeState(element: Element) {
|
||||
for ((key, value) in data.asSequence().sortedBy { it.key }) {
|
||||
val observation = Element("observation")
|
||||
observation.setAttribute("name", key)
|
||||
observation.setAttribute("value", DOUBLE_VALUE_FORMATTER.format(value))
|
||||
element.addContent(observation)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user