FUS-450 add project as parameter to FileTypeUsageSchemaDescriptor
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / lookup / impl / LookupUsageTracker.java
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;
3
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;
23
24 final class LookupUsageTracker {
25   private static final String GROUP_ID = "completion";
26   private static final String EVENT_ID = "finished";
27
28   private LookupUsageTracker() {
29   }
30
31   static void trackLookup(long createdTimestamp, @NotNull LookupImpl lookup) {
32     lookup.addLookupListener(new MyLookupTracker(createdTimestamp, lookup));
33   }
34
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;
42
43     private int mySelectionChangedCount = 0;
44
45
46     MyLookupTracker(long createdTimestamp, @NotNull LookupImpl lookup) {
47       myLookup = 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);
54     }
55
56     @Override
57     public void currentItemChanged(@NotNull LookupEvent event) {
58       mySelectionChangedCount += 1;
59     }
60
61     private boolean isSelectedByTyping(@NotNull LookupElement item) {
62       if (myLookup.itemPattern(item).equals(item.getLookupString())) {
63         return true;
64       }
65       return false;
66     }
67
68     @Override
69     public void itemSelected(@NotNull LookupEvent event) {
70       LookupElement item = event.getItem();
71       char completionChar = event.getCompletionChar();
72       if (item == null) {
73         triggerLookupUsed(FinishType.CANCELED_BY_TYPING, null, completionChar);
74       }
75       else {
76         if (isSelectedByTyping(item)) {
77           triggerLookupUsed(FinishType.TYPED, item, completionChar);
78         }
79         else {
80           triggerLookupUsed(FinishType.EXPLICIT, item, completionChar);
81         }
82       }
83     }
84
85     @Override
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());
90       }
91       else {
92         FinishType detailedCancelType = event.isCanceledExplicitly() ? FinishType.CANCELED_EXPLICITLY : FinishType.CANCELED_BY_TYPING;
93         triggerLookupUsed(detailedCancelType, null, event.getCompletionChar());
94       }
95     }
96
97     private void triggerLookupUsed(@NotNull FinishType finishType, @Nullable LookupElement currentItem,
98                                    char completionChar) {
99       FeatureUsageData data = new FeatureUsageData();
100       addCommonUsageInfo(data, finishType, currentItem, completionChar);
101
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);
107         }
108       });
109
110       FUCounterUsageLogger.getInstance().logEvent(GROUP_ID, EVENT_ID, data);
111     }
112
113     private void addCommonUsageInfo(@NotNull FeatureUsageData data,
114                                     @NotNull FinishType finishType,
115                                     @Nullable LookupElement currentItem,
116                                     char completionChar) {
117       // Basic info
118       data.addLanguage(myLanguage);
119       PsiFile file = myLookup.getPsiFile();
120       if (file != null) {
121         VirtualFile vFile = file.getVirtualFile();
122         if (vFile != null) {
123           String schema = FileTypeUsageCounterCollector.findSchema(myLookup.getProject(), vFile);
124           if (schema != null) {
125             data.addData("schema", schema);
126           }
127         }
128       }
129       data.addData("alphabetically", UISettings.getInstance().getSortLookupElementsLexicographically());
130
131       // Quality
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());
139
140       // Details
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");
148         }
149       }
150
151       // Performance
152       data.addData("time_to_show", myTimeToShow);
153
154       // Indexing
155       data.addData("dumb_start", myIsDumbStart);
156       data.addData("dumb_finish", DumbService.isDumb(myLookup.getProject()));
157     }
158
159     @Nullable
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());
164       }
165       return null;
166     }
167
168     private static class MyTypingTracker implements PrefixChangeListener {
169       int backspaces = 0;
170       int typing = 0;
171
172       @Override
173       public void beforeTruncate() {
174         backspaces += 1;
175       }
176
177       @Override
178       public void beforeAppend(char c) {
179         typing += 1;
180       }
181     }
182   }
183
184   private enum FinishType {
185     TYPED, EXPLICIT, CANCELED_EXPLICITLY, CANCELED_BY_TYPING
186   }
187
188   private enum CompletionChar {
189     ENTER, TAB, COMPLETE_STATEMENT, AUTO_INSERT, OTHER;
190
191     static CompletionChar of(char completionChar) {
192       switch (completionChar) {
193         case Lookup.NORMAL_SELECT_CHAR:
194           return ENTER;
195         case Lookup.REPLACE_SELECT_CHAR:
196           return TAB;
197         case Lookup.AUTO_INSERT_SELECT_CHAR:
198           return AUTO_INSERT;
199         case Lookup.COMPLETE_STATEMENT_SELECT_CHAR:
200           return COMPLETE_STATEMENT;
201         default:
202           return OTHER;
203       }
204     }
205   }
206 }