/* * 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.intellij.codeInsight.completion.impl.CompletionServiceImpl; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationAdapter; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.CaretModel; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.SelectionModel; import com.intellij.openapi.editor.event.*; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.ex.FocusChangeListener; import com.intellij.openapi.util.Disposer; import com.intellij.ui.HintListener; import com.intellij.ui.LightweightHint; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.EventObject; /** * @author peter */ public abstract class CompletionPhase implements Disposable { public static final CompletionPhase NoCompletion = new CompletionPhase(null) { @Override public int newCompletionStarted(int time, boolean repeated) { return time; } }; public final CompletionProgressIndicator indicator; protected CompletionPhase(@Nullable CompletionProgressIndicator indicator) { this.indicator = indicator; } @Override public void dispose() { } public abstract int newCompletionStarted(int time, boolean repeated); public boolean fillInCommonPrefix() { return false; } public static class CommittingDocuments extends CompletionPhase { boolean replaced; private final ActionTracker myTracker; public CommittingDocuments(@Nullable CompletionProgressIndicator prevIndicator, Editor editor) { super(prevIndicator); myTracker = new ActionTracker(editor, this); } public void ignoreCurrentDocumentChange() { myTracker.ignoreCurrentDocumentChange(); } public boolean isRestartingCompletion() { return indicator != null; } public boolean checkExpired() { if (CompletionServiceImpl.getCompletionPhase() != this) { return true; } if (myTracker.hasAnythingHappened() || ApplicationManager.getApplication().isWriteAccessAllowed()) { CompletionServiceImpl.setCompletionPhase(NoCompletion); return true; } return false; } @Override public int newCompletionStarted(int time, boolean repeated) { return time; } @Override public void dispose() { if (!replaced && indicator != null) { indicator.closeAndFinish(true); } } @Override public String toString() { return "CommittingDocuments{hasIndicator=" + (indicator != null) + '}'; } } public static class Synchronous extends CompletionPhase { public Synchronous(CompletionProgressIndicator indicator) { super(indicator); } @Override public int newCompletionStarted(int time, boolean repeated) { CompletionServiceImpl.assertPhase(NoCompletion.getClass()); // will fail and log valuable info CompletionServiceImpl.setCompletionPhase(NoCompletion); return time; } } public static class BgCalculation extends CompletionPhase { boolean modifiersChanged = false; public BgCalculation(final CompletionProgressIndicator indicator) { super(indicator); ApplicationManager.getApplication().addApplicationListener(new ApplicationAdapter() { @Override public void beforeWriteActionStart(@NotNull Object action) { if (!indicator.getLookup().isLookupDisposed() && !indicator.isCanceled()) { indicator.scheduleRestart(); } } }, this); if (indicator.isAutopopupCompletion()) { // lookup is not visible, we have to check ourselves if editor retains focus ((EditorEx)indicator.getEditor()).addFocusListener(new FocusChangeListener() { @Override public void focusGained(Editor editor) { } @Override public void focusLost(Editor editor) { indicator.closeAndFinish(true); } }, this); } } @Override public int newCompletionStarted(int time, boolean repeated) { indicator.closeAndFinish(false); return indicator.nextInvocationCount(time, repeated); } } public static class ItemsCalculated extends CompletionPhase { public ItemsCalculated(CompletionProgressIndicator indicator) { super(indicator); } @Override public int newCompletionStarted(int time, boolean repeated) { indicator.closeAndFinish(false); indicator.restorePrefix(() -> indicator.getLookup().restorePrefix()); return indicator.nextInvocationCount(time, repeated); } @Override public boolean fillInCommonPrefix() { if (indicator.isAutopopupCompletion()) { return false; } return indicator.fillInCommonPrefix(true); } } public static abstract class ZombiePhase extends CompletionPhase { protected ZombiePhase(@Nullable final LightweightHint hint, final CompletionProgressIndicator indicator) { super(indicator); @NotNull Editor editor = indicator.getEditor(); final HintListener hintListener = new HintListener() { @Override public void hintHidden(final EventObject event) { CompletionServiceImpl.setCompletionPhase(NoCompletion); } }; final DocumentListener documentListener = new DocumentListener() { @Override public void beforeDocumentChange(DocumentEvent e) { CompletionServiceImpl.setCompletionPhase(NoCompletion); } }; final SelectionListener selectionListener = new SelectionListener() { @Override public void selectionChanged(SelectionEvent e) { CompletionServiceImpl.setCompletionPhase(NoCompletion); } }; final CaretListener caretListener = new CaretListener() { @Override public void caretPositionChanged(CaretEvent e) { CompletionServiceImpl.setCompletionPhase(NoCompletion); } }; final Document document = editor.getDocument(); final SelectionModel selectionModel = editor.getSelectionModel(); final CaretModel caretModel = editor.getCaretModel(); if (hint != null) { hint.addHintListener(hintListener); } document.addDocumentListener(documentListener); selectionModel.addSelectionListener(selectionListener); caretModel.addCaretListener(caretListener); Disposer.register(this, new Disposable() { @Override public void dispose() { if (hint != null) { hint.removeHintListener(hintListener); } document.removeDocumentListener(documentListener); selectionModel.removeSelectionListener(selectionListener); caretModel.removeCaretListener(caretListener); } }); } } public static class InsertedSingleItem extends ZombiePhase { public final Runnable restorePrefix; public InsertedSingleItem(CompletionProgressIndicator indicator, Runnable restorePrefix) { super(null, indicator); this.restorePrefix = restorePrefix; } @Override public int newCompletionStarted(int time, boolean repeated) { CompletionServiceImpl.setCompletionPhase(NoCompletion); indicator.restorePrefix(restorePrefix); return indicator.nextInvocationCount(time, repeated); } } public static class NoSuggestionsHint extends ZombiePhase { public NoSuggestionsHint(@Nullable LightweightHint hint, CompletionProgressIndicator indicator) { super(hint, indicator); } @Override public int newCompletionStarted(int time, boolean repeated) { CompletionServiceImpl.setCompletionPhase(NoCompletion); return indicator.nextInvocationCount(time, repeated); } } }