1 // Copyright 2000-2020 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.
2 package com.intellij.codeInsight.lookup.impl;
4 import com.intellij.codeInsight.completion.BaseCompletionService;
5 import com.intellij.codeInsight.completion.CompletionContributor;
6 import com.intellij.codeInsight.lookup.Lookup;
7 import com.intellij.codeInsight.lookup.LookupElement;
8 import com.intellij.codeInsight.lookup.LookupEvent;
9 import com.intellij.codeInsight.lookup.LookupListener;
10 import com.intellij.ide.ui.UISettings;
11 import com.intellij.internal.statistic.collectors.fus.fileTypes.FileTypeUsageCounterCollector;
12 import com.intellij.internal.statistic.eventLog.FeatureUsageData;
13 import com.intellij.internal.statistic.service.fus.collectors.FUCounterUsageLogger;
14 import com.intellij.internal.statistic.utils.PluginInfo;
15 import com.intellij.internal.statistic.utils.PluginInfoDetectorKt;
16 import com.intellij.lang.Language;
17 import com.intellij.openapi.project.DumbService;
18 import com.intellij.openapi.vfs.VirtualFile;
19 import com.intellij.psi.PsiFile;
20 import com.intellij.psi.util.PsiUtilCore;
21 import org.jetbrains.annotations.NotNull;
22 import org.jetbrains.annotations.Nullable;
24 final class LookupUsageTracker {
25 private static final String GROUP_ID = "completion";
26 private static final String EVENT_ID = "finished";
28 private LookupUsageTracker() {
31 static void trackLookup(long createdTimestamp, @NotNull LookupImpl lookup) {
32 lookup.addLookupListener(new MyLookupTracker(createdTimestamp, lookup));
35 private static class MyLookupTracker implements LookupListener {
36 private final LookupImpl myLookup;
37 private final long myCreatedTimestamp;
38 private final long myTimeToShow;
39 private final boolean myIsDumbStart;
40 private final Language myLanguage;
41 private final MyTypingTracker myTypingTracker;
43 private int mySelectionChangedCount = 0;
46 MyLookupTracker(long createdTimestamp, @NotNull LookupImpl lookup) {
48 myCreatedTimestamp = createdTimestamp;
49 myTimeToShow = System.currentTimeMillis() - createdTimestamp;
50 myIsDumbStart = DumbService.isDumb(lookup.getProject());
51 myLanguage = getLanguageAtCaret(lookup);
52 myTypingTracker = new MyTypingTracker();
53 lookup.addPrefixChangeListener(myTypingTracker, lookup);
57 public void currentItemChanged(@NotNull LookupEvent event) {
58 mySelectionChangedCount += 1;
61 private boolean isSelectedByTyping(@NotNull LookupElement item) {
62 if (myLookup.itemPattern(item).equals(item.getLookupString())) {
69 public void itemSelected(@NotNull LookupEvent event) {
70 LookupElement item = event.getItem();
71 char completionChar = event.getCompletionChar();
73 triggerLookupUsed(FinishType.CANCELED_BY_TYPING, null, completionChar);
76 if (isSelectedByTyping(item)) {
77 triggerLookupUsed(FinishType.TYPED, item, completionChar);
80 triggerLookupUsed(FinishType.EXPLICIT, item, completionChar);
86 public void lookupCanceled(@NotNull LookupEvent event) {
87 LookupElement item = myLookup.getCurrentItem();
88 if (item != null && isSelectedByTyping(item)) {
89 triggerLookupUsed(FinishType.TYPED, item, event.getCompletionChar());
92 FinishType detailedCancelType = event.isCanceledExplicitly() ? FinishType.CANCELED_EXPLICITLY : FinishType.CANCELED_BY_TYPING;
93 triggerLookupUsed(detailedCancelType, null, event.getCompletionChar());
97 private void triggerLookupUsed(@NotNull FinishType finishType, @Nullable LookupElement currentItem,
98 char completionChar) {
99 FeatureUsageData data = new FeatureUsageData();
100 addCommonUsageInfo(data, finishType, currentItem, completionChar);
102 LookupUsageDescriptor.EP_NAME.forEachExtensionSafe(usageDescriptor -> {
103 if (PluginInfoDetectorKt.getPluginInfo(usageDescriptor.getClass()).isSafeToReport()) {
104 FeatureUsageData additionalData = new FeatureUsageData();
105 usageDescriptor.fillUsageData(myLookup, additionalData);
106 data.addAll(additionalData);
110 FUCounterUsageLogger.getInstance().logEvent(GROUP_ID, EVENT_ID, data);
113 private void addCommonUsageInfo(@NotNull FeatureUsageData data,
114 @NotNull FinishType finishType,
115 @Nullable LookupElement currentItem,
116 char completionChar) {
118 data.addLanguage(myLanguage);
119 PsiFile file = myLookup.getPsiFile();
121 VirtualFile vFile = file.getVirtualFile();
123 String schema = FileTypeUsageCounterCollector.findSchema(vFile);
124 if (schema != null) {
125 data.addData("schema", schema);
129 data.addData("alphabetically", UISettings.getInstance().getSortLookupElementsLexicographically());
132 data.addData("finish_type", finishType.toString());
133 data.addData("duration", System.currentTimeMillis() - myCreatedTimestamp);
134 data.addData("selected_index", myLookup.getSelectedIndex());
135 data.addData("selection_changed", mySelectionChangedCount);
136 data.addData("typing", myTypingTracker.typing);
137 data.addData("backspaces", myTypingTracker.backspaces);
138 data.addData("completion_char", CompletionChar.of(completionChar).toString());
141 if (currentItem != null) {
142 data.addData("token_length", currentItem.getLookupString().length());
143 data.addData("query_length", myLookup.itemPattern(currentItem).length());
144 CompletionContributor contributor = currentItem.getUserData(BaseCompletionService.LOOKUP_ELEMENT_CONTRIBUTOR);
145 if (contributor != null) {
146 PluginInfo info = PluginInfoDetectorKt.getPluginInfo(contributor.getClass());
147 data.addData("contributor", info.isSafeToReport() ? contributor.getClass().getName() : "third.party");
152 data.addData("time_to_show", myTimeToShow);
155 data.addData("dumb_start", myIsDumbStart);
156 data.addData("dumb_finish", DumbService.isDumb(myLookup.getProject()));
160 private static Language getLanguageAtCaret(@NotNull LookupImpl lookup) {
161 PsiFile psiFile = lookup.getPsiFile();
162 if (psiFile != null) {
163 return PsiUtilCore.getLanguageAtOffset(psiFile, lookup.getEditor().getCaretModel().getOffset());
168 private static class MyTypingTracker implements PrefixChangeListener {
173 public void beforeTruncate() {
178 public void beforeAppend(char c) {
184 private enum FinishType {
185 TYPED, EXPLICIT, CANCELED_EXPLICITLY, CANCELED_BY_TYPING
188 private enum CompletionChar {
189 ENTER, TAB, COMPLETE_STATEMENT, AUTO_INSERT, OTHER;
191 static CompletionChar of(char completionChar) {
192 switch (completionChar) {
193 case Lookup.NORMAL_SELECT_CHAR:
195 case Lookup.REPLACE_SELECT_CHAR:
197 case Lookup.AUTO_INSERT_SELECT_CHAR:
199 case Lookup.COMPLETE_STATEMENT_SELECT_CHAR:
200 return COMPLETE_STATEMENT;