/* * Copyright 2013-2017 consulo.io * * 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.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.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.Disposer; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.statistics.StatisticsInfo; import com.intellij.util.Alarm; import org.jetbrains.annotations.Nullable; /** * @author VISTALL * @since 02-May-17 * <p> * from kotlin platform\lang-impl\src\com\intellij\codeInsight\completion\StatisticsUpdate.kt */ public class StatisticsUpdate implements Disposable { static { Disposer.register(ApplicationManager.getApplication(), StatisticsUpdate::cancelLastCompletionStatisticsUpdate); } @Nullable private static StatisticsUpdate ourPendingUpdate; private static final Alarm ourStatsAlarm = new Alarm(ApplicationManager.getApplication()); public static StatisticsUpdate collectStatisticChanges(LookupElement item) { 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, () -> ourPendingUpdate = null); return update; } public void trackStatistics(InsertionContext context) { if (ourPendingUpdate != this) { 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 == this) { applyLastCompletionStatisticsUpdate(); } }, 20 * 1000); document.addDocumentListener(listener); Disposer.register(this, () -> { 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 final StatisticsInfo myInfo; private int mySpared; public StatisticsUpdate(StatisticsInfo info) { myInfo = info; } void performUpdate() { myInfo.incUseCount(); ((FeatureUsageTrackerImpl)FeatureUsageTracker.getInstance()).getCompletionStatistics().registerInvocation(mySpared); } public void addSparedChars(CompletionProgressIndicator indicator, LookupElement item, InsertionContext context) { 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(); char completionChar = context.getCompletionChar(); if (!LookupEvent.isSpecialCompletionChar(completionChar) && withoutSpaces.contains(String.valueOf(completionChar))) { spared--; } if (spared > 0) { mySpared += spared; } } @Override public void dispose() { } }