extract delayed completion statistics logic out of CompletionLookupArranger

This commit is contained in:
peter
2017-02-01 11:25:38 +01:00
parent 08d64795da
commit e58f41d54c
7 changed files with 156 additions and 149 deletions

View File

@@ -91,7 +91,7 @@ public abstract class CompletionSortingTestCase extends LightFixtureCompletionTe
protected static void imitateItemSelection(final LookupImpl lookup, final int index) {
final LookupElement item = lookup.getItems().get(index);
lookup.setCurrentItem(item);
CompletionLookupArranger.collectStatisticChanges(item, lookup);
CompletionLookupArranger.applyLastCompletionStatisticsUpdate();
StatisticsUpdate.collectStatisticChanges(item);
StatisticsUpdate.applyLastCompletionStatisticsUpdate();
}
}

View File

@@ -70,7 +70,7 @@ class SmartTypeCompletionOrderingTest extends CompletionSortingTestCase {
assertPreferredItems(2, "Component", "String", "FooBean3", "JComponent", "Container")
lookup.currentItem = lookup.items[4] //Container
myFixture.type('\n\b')
CompletionLookupArranger.applyLastCompletionStatisticsUpdate()
StatisticsUpdate.applyLastCompletionStatisticsUpdate()
FileDocumentManager.instance.saveAllDocuments()
invokeCompletion("/JComponentAddNew.java")
assertPreferredItems(2, "Component", "String", "FooBean3", "JComponent", "Container")

View File

@@ -130,7 +130,7 @@ public class CodeCompletionHandlerBase {
markCaretAsProcessed(caret);
if (invokedExplicitly) {
CompletionLookupArranger.applyLastCompletionStatisticsUpdate();
StatisticsUpdate.applyLastCompletionStatisticsUpdate();
}
checkNoWriteAccess();
@@ -564,10 +564,9 @@ public class CodeCompletionHandlerBase {
CompletionAssertions.WatchingInsertionContext context = null;
try {
Lookup lookup = indicator.getLookup();
CompletionLookupArranger.StatisticsUpdate update = CompletionLookupArranger.collectStatisticChanges(item, lookup);
StatisticsUpdate update = StatisticsUpdate.collectStatisticChanges(item);
context = insertItemHonorBlockSelection(indicator, item, completionChar, items, update);
CompletionLookupArranger.trackStatistics(context, update);
update.trackStatistics(context);
}
finally {
afterItemInsertion(indicator, context == null ? null : context.getLaterRunnable());
@@ -579,7 +578,7 @@ public class CodeCompletionHandlerBase {
final LookupElement item,
final char completionChar,
final List<LookupElement> items,
final CompletionLookupArranger.StatisticsUpdate update) {
final StatisticsUpdate update) {
final Editor editor = indicator.getEditor();
final int caretOffset = indicator.getCaret().getOffset();
@@ -657,7 +656,7 @@ public class CodeCompletionHandlerBase {
final LookupElement item,
final char completionChar,
List<LookupElement> items,
final CompletionLookupArranger.StatisticsUpdate update,
final StatisticsUpdate update,
final Editor editor,
final PsiFile psiFile,
final int caretOffset,
@@ -694,7 +693,7 @@ public class CodeCompletionHandlerBase {
}
EditorModificationUtil.scrollToCaret(editor);
});
update.addSparedChars(indicator, item, context, completionChar);
update.addSparedChars(indicator, item, context);
return context;
}

View File

@@ -22,22 +22,16 @@ import com.intellij.codeInsight.lookup.*;
import com.intellij.codeInsight.lookup.impl.EmptyLookupItem;
import com.intellij.codeInsight.lookup.impl.LookupImpl;
import com.intellij.codeInsight.template.impl.LiveTemplateLookupElement;
import com.intellij.featureStatistics.FeatureUsageTracker;
import com.intellij.featureStatistics.FeatureUsageTrackerImpl;
import com.intellij.ide.ui.UISettings;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.editor.event.DocumentAdapter;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.patterns.StandardPatterns;
import com.intellij.psi.statistics.StatisticsInfo;
import com.intellij.util.Alarm;
import com.intellij.util.ProcessingContext;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
@@ -52,8 +46,6 @@ import java.util.*;
public class CompletionLookupArranger extends LookupArranger {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.CompletionLookupArranger");
@Nullable private static StatisticsUpdate ourPendingUpdate;
private static final Alarm ourStatsAlarm = new Alarm(ApplicationManager.getApplication());
private static final Key<String> GLOBAL_PRESENTATION_INVARIANT = Key.create("PRESENTATION_INVARIANT");
private final Key<String> PRESENTATION_INVARIANT = Key.create("PRESENTATION_INVARIANT");
private final Comparator<LookupElement> BY_PRESENTATION_COMPARATOR = (o1, o2) -> {
@@ -67,14 +59,6 @@ public class CompletionLookupArranger extends LookupArranger {
public static final Key<Integer> PREFIX_CHANGES = Key.create("PREFIX_CHANGES");
private static final UISettings ourUISettings = UISettings.getInstance();
private final List<LookupElement> myFrozenItems = new ArrayList<>();
static {
Disposer.register(ApplicationManager.getApplication(), new Disposable() {
@Override
public void dispose() {
cancelLastCompletionStatisticsUpdate();
}
});
}
private final int myLimit = Registry.intValue("ide.completion.variant.limit");
private boolean myOverflow;
@@ -456,86 +440,6 @@ public class CompletionLookupArranger extends LookupArranger {
return element instanceof LiveTemplateLookupElement && ((LiveTemplateLookupElement)element).sudden;
}
public static StatisticsUpdate collectStatisticChanges(LookupElement item, final Lookup lookup) {
applyLastCompletionStatisticsUpdate();
final StatisticsInfo base = StatisticsWeigher.getBaseStatisticsInfo(item, null);
if (base == StatisticsInfo.EMPTY) {
return new StatisticsUpdate(StatisticsInfo.EMPTY);
}
StatisticsUpdate update = new StatisticsUpdate(base);
ourPendingUpdate = update;
Disposer.register(update, new Disposable() {
@Override
public void dispose() {
//noinspection AssignmentToStaticFieldFromInstanceMethod
ourPendingUpdate = null;
}
});
return update;
}
public static void trackStatistics(InsertionContext context, final StatisticsUpdate update) {
if (ourPendingUpdate != update) {
return;
}
if (!context.getOffsetMap().containsOffset(CompletionInitializationContext.START_OFFSET)) {
return;
}
final Document document = context.getDocument();
int startOffset = context.getStartOffset();
int tailOffset = context.getEditor().getCaretModel().getOffset();
if (startOffset < 0 || tailOffset <= startOffset) {
return;
}
final RangeMarker marker = document.createRangeMarker(startOffset, tailOffset);
final DocumentAdapter listener = new DocumentAdapter() {
@Override
public void beforeDocumentChange(DocumentEvent e) {
if (!marker.isValid() || e.getOffset() > marker.getStartOffset() && e.getOffset() < marker.getEndOffset()) {
cancelLastCompletionStatisticsUpdate();
}
}
};
ourStatsAlarm.addRequest(() -> {
if (ourPendingUpdate == update) {
applyLastCompletionStatisticsUpdate();
}
}, 20 * 1000);
document.addDocumentListener(listener);
Disposer.register(update, new Disposable() {
@Override
public void dispose() {
document.removeDocumentListener(listener);
marker.dispose();
ourStatsAlarm.cancelAllRequests();
}
});
}
public static void cancelLastCompletionStatisticsUpdate() {
if (ourPendingUpdate != null) {
Disposer.dispose(ourPendingUpdate);
assert ourPendingUpdate == null;
}
}
public static void applyLastCompletionStatisticsUpdate() {
StatisticsUpdate update = ourPendingUpdate;
if (update != null) {
update.performUpdate();
Disposer.dispose(update);
assert ourPendingUpdate == null;
}
}
private boolean shouldSkip(CompletionPreselectSkipper[] skippers, LookupElement element) {
for (final CompletionPreselectSkipper skipper : skippers) {
if (skipper.skipElement(element, myLocation)) {
@@ -555,43 +459,6 @@ public class CompletionLookupArranger extends LookupArranger {
super.prefixChanged(lookup);
}
static class StatisticsUpdate implements Disposable {
private final StatisticsInfo myInfo;
private int mySpared;
public StatisticsUpdate(StatisticsInfo info) {
myInfo = info;
}
void performUpdate() {
myInfo.incUseCount();
((FeatureUsageTrackerImpl)FeatureUsageTracker.getInstance()).getCompletionStatistics().registerInvocation(mySpared);
}
@Override
public void dispose() {
}
public void addSparedChars(CompletionProgressIndicator indicator, LookupElement item, InsertionContext context, char completionChar) {
String textInserted;
if (context.getOffsetMap().containsOffset(CompletionInitializationContext.START_OFFSET) &&
context.getOffsetMap().containsOffset(InsertionContext.TAIL_OFFSET) &&
context.getTailOffset() >= context.getStartOffset()) {
textInserted = context.getDocument().getImmutableCharSequence().subSequence(context.getStartOffset(), context.getTailOffset()).toString();
} else {
textInserted = item.getLookupString();
}
String withoutSpaces = StringUtil.replace(textInserted, new String[]{" ", "\t", "\n"}, new String[]{"", "", ""});
int spared = withoutSpaces.length() - indicator.getLookup().itemPattern(item).length();
if (!LookupEvent.isSpecialCompletionChar(completionChar) && withoutSpaces.contains(String.valueOf(completionChar))) {
spared--;
}
if (spared > 0) {
mySpared += spared;
}
}
}
private static class EmptyClassifier extends Classifier<LookupElement> {
private EmptyClassifier() {

View File

@@ -487,7 +487,7 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement
else {
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
}
CompletionLookupArranger.cancelLastCompletionStatisticsUpdate();
StatisticsUpdate.cancelLastCompletionStatisticsUpdate();
}
@Override

View File

@@ -0,0 +1,141 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInsight.completion
import com.google.common.annotations.VisibleForTesting
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupEvent
import com.intellij.featureStatistics.FeatureUsageTracker
import com.intellij.featureStatistics.FeatureUsageTrackerImpl
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.event.DocumentAdapter
import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.statistics.StatisticsInfo
import com.intellij.util.Alarm
/**
* @author peter
*/
class StatisticsUpdate
private constructor(private val myInfo: StatisticsInfo) : Disposable {
private var mySpared: Int = 0
override fun dispose() {}
fun addSparedChars(indicator: CompletionProgressIndicator, 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, arrayOf(" ", "\t", "\n"), arrayOf("", "", ""))
var spared = withoutSpaces.length - indicator.lookup.itemPattern(item).length
val completionChar = context.completionChar
if (!LookupEvent.isSpecialCompletionChar(completionChar) && withoutSpaces.contains(completionChar.toString())) {
spared--
}
if (spared > 0) {
mySpared += 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 = context.editor.caretModel.offset
if (startOffset < 0 || tailOffset <= startOffset) {
return
}
val marker = document.createRangeMarker(startOffset, tailOffset)
val listener = object : DocumentAdapter() {
override fun beforeDocumentChange(e: DocumentEvent) {
if (!marker.isValid || e.offset > marker.startOffset && e.offset < marker.endOffset) {
cancelLastCompletionStatisticsUpdate()
}
}
}
ourStatsAlarm.addRequest({
if (ourPendingUpdate === this) {
applyLastCompletionStatisticsUpdate()
}
}, 20 * 1000)
document.addDocumentListener(listener)
Disposer.register(this, Disposable {
document.removeDocumentListener(listener)
marker.dispose()
ourStatsAlarm.cancelAllRequests()
})
}
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 {
it.myInfo.incUseCount()
(FeatureUsageTracker.getInstance() as FeatureUsageTrackerImpl).completionStatistics.registerInvocation(it.mySpared)
}
cancelLastCompletionStatisticsUpdate()
}
}
}

View File

@@ -500,7 +500,7 @@ public class XmlCompletionTest extends LightCodeInsightFixtureTestCase {
selectItem(myFixture.getLookupElements()[4], '\t');
checkResultByFile("CorrectSelectionInsertion_after.xml");
CompletionLookupArranger.applyLastCompletionStatisticsUpdate();
StatisticsUpdate.applyLastCompletionStatisticsUpdate();
configureByFile("CorrectSelectionInsertion2.xml");
myFixture.getEditor().getSelectionModel().removeSelection();