diff --git a/platform/lang-impl/api-dump-unreviewed.txt b/platform/lang-impl/api-dump-unreviewed.txt index ca9f1c9bf4ca..37ffe0038bf0 100644 --- a/platform/lang-impl/api-dump-unreviewed.txt +++ b/platform/lang-impl/api-dump-unreviewed.txt @@ -1928,11 +1928,11 @@ f:com.intellij.codeInsight.completion.CompletionPhase$BgCalculation f:com.intellij.codeInsight.completion.CompletionPhase$CommittingDocuments - com.intellij.codeInsight.completion.CompletionPhase - dispose():V -- ignoreCurrentDocumentChange():V +- f:ignoreCurrentDocumentChange():V - newCompletionStarted(I,Z):I f:com.intellij.codeInsight.completion.CompletionPhase$EmptyAutoPopup - com.intellij.codeInsight.completion.CompletionPhase$ZombiePhase -- allowsSkippingNewAutoPopup(com.intellij.openapi.editor.Editor,C):Z +- f:allowsSkippingNewAutoPopup(com.intellij.openapi.editor.Editor,C):Z - newCompletionStarted(I,Z):I f:com.intellij.codeInsight.completion.CompletionPhase$InsertedSingleItem - com.intellij.codeInsight.completion.CompletionPhase$ZombiePhase diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionPhase.kt b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionPhase.kt index 6b81730fea3c..02fe522554f4 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionPhase.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionPhase.kt @@ -1,452 +1,400 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.codeInsight.completion; +package com.intellij.codeInsight.completion -import com.intellij.codeInsight.completion.impl.CompletionServiceImpl; -import com.intellij.codeWithMe.ClientId; -import com.intellij.lang.Language; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.ApplicationListener; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.application.ReadAction; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.event.*; -import com.intellij.openapi.editor.ex.EditorEx; -import com.intellij.openapi.editor.ex.FocusChangeListener; -import com.intellij.openapi.project.IndexNotReadyException; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Condition; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.Pair; -import com.intellij.patterns.ElementPattern; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.impl.source.tree.injected.InjectedLanguageEditorUtil; -import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; -import com.intellij.psi.util.PsiUtilCore; -import com.intellij.ui.HintListener; -import com.intellij.ui.LightweightHint; -import com.intellij.util.ThreeState; -import com.intellij.util.concurrency.AppExecutorUtil; -import com.intellij.util.concurrency.annotations.RequiresBackgroundThread; -import com.intellij.util.ui.EDT; -import com.intellij.util.ui.accessibility.ScreenReader; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.event.FocusEvent; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ExecutorService; +import com.intellij.codeInsight.completion.impl.CompletionServiceImpl +import com.intellij.codeInsight.completion.impl.CompletionServiceImpl.Companion.assertPhase +import com.intellij.codeWithMe.ClientId +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationListener +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.ModalityState +import com.intellij.openapi.application.ReadAction +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.event.* +import com.intellij.openapi.editor.ex.EditorEx +import com.intellij.openapi.editor.ex.FocusChangeListener +import com.intellij.openapi.project.IndexNotReadyException +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Condition +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.Key +import com.intellij.openapi.util.Pair +import com.intellij.patterns.ElementPattern +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiFile +import com.intellij.psi.impl.source.tree.injected.InjectedLanguageEditorUtil +import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil +import com.intellij.psi.util.PsiUtilCore +import com.intellij.ui.HintListener +import com.intellij.ui.LightweightHint +import com.intellij.util.ThreeState +import com.intellij.util.concurrency.AppExecutorUtil +import com.intellij.util.concurrency.annotations.RequiresBackgroundThread +import com.intellij.util.ui.EDT +import com.intellij.util.ui.accessibility.ScreenReader +import org.jetbrains.annotations.ApiStatus +import java.awt.event.FocusEvent +import java.util.concurrent.Callable +import java.util.function.Consumer +import javax.swing.SwingUtilities +import kotlin.math.max /** * Code completion lifecycle within an IntelliJ-based IDE. * - *

- * * Phases: - *

  • {@link #NoCompletion} - no completion is running - *
  • {@link CommittingDocuments} - preparing the document for completion including committing all the project documents - *
  • {@link Synchronous} - completion is computing candidates synchronously. - *
  • {@link BgCalculation} - inferring candidates on background - *
  • {@link ItemsCalculated} - completion items have been calculated - *
  • {@link EmptyAutoPopup} - completion was triggered by typing, but no completion items were found, and the lookup is not shown - *
  • {@link InsertedSingleItem} - a single item was found, and it was inserted into the document - *
  • {@link NoSuggestionsHint} - candidate inference has finished, but no candidates were found and a warning "no suggestions found" is shown. + * * [NoCompletion] - no completion is running + * * [CommittingDocuments] - preparing the document for completion including committing all the project documents + * * [Synchronous] - completion is computing candidates synchronously. + * * [BgCalculation] - inferring candidates on background + * * [ItemsCalculated] - completion items have been calculated + * * [EmptyAutoPopup] - completion was triggered by typing, but no completion items were found, and the lookup is not shown + * * [InsertedSingleItem] - a single item was found, and it was inserted into the document + * * [NoSuggestionsHint] - candidate inference has finished, but no candidates were found and a warning "no suggestions found" is shown. */ -public abstract class CompletionPhase implements Disposable { - @ApiStatus.Internal - public static final Key AUTO_POPUP_TYPED_EVENT = Key.create("AutoPopupTypedEvent"); +abstract class CompletionPhase @ApiStatus.Internal protected constructor( + @JvmField + val indicator: CompletionProgressIndicator? +) : Disposable { - @ApiStatus.Internal - public static final Key CUSTOM_CODE_COMPLETION_ACTION_ID = Key.create("CodeCompletionActionID"); + abstract fun newCompletionStarted(time: Int, repeated: Boolean): Int - private static final Logger LOG = Logger.getInstance(CompletionPhase.class); + override fun dispose() {} - public static final CompletionPhase NoCompletion = new CompletionPhase(null) { - @Override - public int newCompletionStarted(int time, boolean repeated) { - return time; + class CommittingDocuments internal constructor( + prevIndicator: CompletionProgressIndicator?, + editor: Editor, + private val event: TypedEvent? + ) : CompletionPhase(prevIndicator) { + @JvmField + internal var replaced: Boolean = false + + private val myTracker: ActionTracker = ActionTracker(editor, this) + private var myRequestCount = 1 + + fun ignoreCurrentDocumentChange() { + myTracker.ignoreCurrentDocumentChange() } - @Override - public String toString() { - return "NoCompletion"; - } - }; + @get:ApiStatus.Internal + val isExpired: Boolean + get() = myTracker.hasAnythingHappened() || myRequestCount <= 0 - public final CompletionProgressIndicator indicator; - - @ApiStatus.Internal - protected CompletionPhase(@Nullable CompletionProgressIndicator indicator) { - this.indicator = indicator; - } - - @Override - public void dispose() { - } - - public abstract int newCompletionStarted(int time, boolean repeated); - - public static final class CommittingDocuments extends CompletionPhase { - private static final ExecutorService ourExecutor = AppExecutorUtil.createBoundedApplicationPoolExecutor("Completion Preparation", 1); - - boolean replaced; - private final @NotNull ActionTracker myTracker; - private final @Nullable TypedEvent myEvent; - private int myRequestCount = 1; - - CommittingDocuments(@Nullable CompletionProgressIndicator prevIndicator, - @NotNull Editor editor, - @Nullable TypedEvent event) { - super(prevIndicator); - myTracker = new ActionTracker(editor, this); - myEvent = event; + internal fun incrementRequestCount() { + myRequestCount++ + LOG.trace("Increment request count :: new myRequestCount=$myRequestCount") } - public void ignoreCurrentDocumentChange() { - myTracker.ignoreCurrentDocumentChange(); + private fun decrementRequestCount() { + myRequestCount-- + LOG.trace("Decrement request count :: new myRequestCount=$myRequestCount") } - @ApiStatus.Internal - public boolean isExpired() { - return myTracker.hasAnythingHappened() || myRequestCount <= 0; + private fun requestCompleted() { + LOG.trace("Request completed") + myRequestCount = 0 } - private @Nullable TypedEvent getEvent() { - return myEvent; + override fun newCompletionStarted(time: Int, repeated: Boolean): Int { + return time } - void incrementRequestCount() { - myRequestCount++; - LOG.trace("Increment request count :: new myRequestCount=" + myRequestCount); - } - - private void decrementRequestCount() { - myRequestCount--; - LOG.trace("Decrement request count :: new myRequestCount=" + myRequestCount); - } - - private void requestCompleted() { - LOG.trace("Request completed"); - myRequestCount = 0; - } - - @Override - public int newCompletionStarted(int time, boolean repeated) { - return time; - } - - @Override - public void dispose() { - LOG.trace("Dispose completion phase: " + this); - myRequestCount = 0; + override fun dispose() { + LOG.trace("Dispose completion phase: $this") + myRequestCount = 0 if (!replaced && indicator != null) { - indicator.closeAndFinish(true); + indicator.closeAndFinish(true) } } - @Override - public String toString() { - return "CommittingDocuments{hasIndicator=" + (indicator != null) + '}'; + override fun toString(): String { + return "CommittingDocuments{hasIndicator=${indicator != null}}" } @ApiStatus.Internal - public static void scheduleAsyncCompletion(@NotNull Editor _editor, - @NotNull CompletionType completionType, - @Nullable Condition condition, - @NotNull Project project, - @Nullable CompletionProgressIndicator prevIndicator) { - LOG.trace("Schedule async completion"); - Editor topLevelEditor = InjectedLanguageEditorUtil.getTopLevelEditor(_editor); - int offset = topLevelEditor.getCaretModel().getOffset(); + companion object { + private val ourExecutor = AppExecutorUtil.createBoundedApplicationPoolExecutor("Completion Preparation", 1) - CommittingDocuments phase = getCompletionPhase(prevIndicator, topLevelEditor, _editor.getUserData(AUTO_POPUP_TYPED_EVENT)); + @ApiStatus.Internal + @JvmStatic + fun scheduleAsyncCompletion( + _editor: Editor, + completionType: CompletionType, + condition: Condition?, + project: Project, + prevIndicator: CompletionProgressIndicator? + ) { + LOG.trace("Schedule async completion") + val topLevelEditor = InjectedLanguageEditorUtil.getTopLevelEditor(_editor) + val offset = topLevelEditor.getCaretModel().offset - boolean autopopup = prevIndicator == null || prevIndicator.isAutopopupCompletion(); + val phase = getCompletionPhase(prevIndicator, topLevelEditor, _editor.getUserData(AUTO_POPUP_TYPED_EVENT)) - ReadAction - .nonBlocking(() -> { - if (phase.isExpired()) { - LOG.trace("Phase is expired"); - return null; - } + val autopopup = prevIndicator == null || prevIndicator.isAutopopupCompletion - LOG.trace("Start non-blocking read action :: phase=" + phase.replaced); - // retrieve the injected file from scratch since our typing might have destroyed the old one completely - PsiFile topLevelFile = PsiDocumentManager.getInstance(project).getPsiFile(topLevelEditor.getDocument()); - Editor completionEditor = InjectedLanguageUtil.getEditorForInjectedLanguageNoCommit(topLevelEditor, topLevelFile, offset); - PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(completionEditor.getDocument()); - if (file == null || - autopopup && shouldSkipAutoPopup(completionEditor, file) || - condition != null && !condition.value(file)) { - LOG.trace("File is null or should skip auto popup or condition is not met :: file=" + file + ", condition=" + condition); - return null; - } - - loadContributorsOutsideEdt(completionEditor, file); - - return completionEditor; - }) - .withDocumentsCommitted(project) - .expireWith(phase) - .finishOnUiThread(ModalityState.current(), completionEditor -> { - LOG.trace("Finish on UI thread :: completionEditor=" + completionEditor); - if (completionEditor != null && !phase.isExpired()) { - LOG.trace("Starting completion phase :: completionEditor=" + completionEditor); - phase.requestCompleted(); - int time = prevIndicator == null ? 0 : prevIndicator.getInvocationCount(); - - String customId = completionEditor.getUserData(CUSTOM_CODE_COMPLETION_ACTION_ID); - if (customId == null) { - customId = "CodeCompletion"; + ReadAction + .nonBlocking(Callable { + if (phase.isExpired) { + LOG.trace("Phase is expired") + return@Callable null } - CodeCompletionHandlerBase handler = CodeCompletionHandlerBase.createHandler(completionType, false, autopopup, false, customId); - handler.invokeCompletion(project, completionEditor, time, false); + LOG.trace("Start non-blocking read action :: phase=" + phase.replaced) + // retrieve the injected file from scratch since our typing might have destroyed the old one completely + val topLevelFile = PsiDocumentManager.getInstance(project).getPsiFile(topLevelEditor.getDocument()) + val completionEditor = InjectedLanguageUtil.getEditorForInjectedLanguageNoCommit(topLevelEditor, topLevelFile, offset) + val file = PsiDocumentManager.getInstance(project).getPsiFile(completionEditor.getDocument()) + if (file == null || autopopup && shouldSkipAutoPopup(completionEditor, file) || condition != null && !condition.value(file)) { + LOG.trace("File is null or should skip auto popup or condition is not met :: file=$file, condition=$condition") + return@Callable null + } + + loadContributorsOutsideEdt(completionEditor, file) + completionEditor + }) + .withDocumentsCommitted(project) + .expireWith(phase) + .finishOnUiThread(ModalityState.current(), Consumer { completionEditor: Editor? -> + LOG.trace("Finish on UI thread :: completionEditor=$completionEditor") + if (completionEditor != null && !phase.isExpired) { + LOG.trace("Starting completion phase :: completionEditor=$completionEditor") + phase.requestCompleted() + val time = prevIndicator?.invocationCount ?: 0 + + val customId = completionEditor.getUserData(CUSTOM_CODE_COMPLETION_ACTION_ID) ?: "CodeCompletion" + val handler = CodeCompletionHandlerBase.createHandler(completionType, false, autopopup, false, customId) + handler.invokeCompletion(project, completionEditor, time, false) + } + else if (phase == CompletionServiceImpl.completionPhase) { + LOG.trace("Setting NoCompletion phase :: completionEditor=" + completionEditor + ", isExpired=" + phase.isExpired) + phase.decrementRequestCount() + if (phase.isExpired) { + CompletionServiceImpl.setCompletionPhase(NoCompletion) + } + } + }) + .submit(ourExecutor) + } + + private fun getCompletionPhase( + prevIndicator: CompletionProgressIndicator?, + topLevelEditor: Editor, + event: TypedEvent? + ): CommittingDocuments { + if (event != null) { + val currentPhase = CompletionServiceImpl.completionPhase + if (currentPhase is CommittingDocuments && !currentPhase.isExpired && event == currentPhase.event) { + currentPhase.incrementRequestCount() + return currentPhase } - else if (phase == CompletionServiceImpl.getCompletionPhase()) { - LOG.trace("Setting NoCompletion phase :: completionEditor=" + completionEditor + ", isExpired=" + phase.isExpired()); - phase.decrementRequestCount(); - if (phase.isExpired()) { - CompletionServiceImpl.setCompletionPhase(NoCompletion); + } + val phase = CommittingDocuments(prevIndicator, topLevelEditor, event) + CompletionServiceImpl.setCompletionPhase(phase) + phase.ignoreCurrentDocumentChange() + return phase + } + + @ApiStatus.Internal + @RequiresBackgroundThread + @JvmStatic + fun loadContributorsOutsideEdt(editor: Editor, file: PsiFile) { + CompletionContributor.forLanguage(PsiUtilCore.getLanguageAtOffset(file, editor.getCaretModel().offset)) + } + + @ApiStatus.Internal + @JvmStatic + fun shouldSkipAutoPopup(editor: Editor, psiFile: PsiFile): Boolean { + val offset = editor.getCaretModel().offset + val psiOffset = max(0, offset - 1) + + val elementAt = psiFile.findElementAt(psiOffset) ?: return true + + val language = PsiUtilCore.findLanguageFromElement(elementAt) + + for (confidence in CompletionConfidenceEP.forLanguage(language)) { + try { + val result = confidence.shouldSkipAutopopup(editor, elementAt, psiFile, offset) + if (result != ThreeState.UNSURE) { + LOG.debug("$confidence has returned shouldSkipAutopopup=$result") + return result == ThreeState.YES } } - }) - .submit(ourExecutor); - } - - private static @NotNull CommittingDocuments getCompletionPhase(@Nullable CompletionProgressIndicator prevIndicator, - @NotNull Editor topLevelEditor, - @Nullable TypedEvent event) { - if (event != null) { - CompletionPhase currentPhase = CompletionServiceImpl.getCompletionPhase(); - if (currentPhase instanceof CommittingDocuments committingPhase && - !committingPhase.isExpired() && - event.equals(committingPhase.getEvent())) { - committingPhase.incrementRequestCount(); - return committingPhase; - } - } - CommittingDocuments phase = new CommittingDocuments(prevIndicator, topLevelEditor, event); - CompletionServiceImpl.setCompletionPhase(phase); - phase.ignoreCurrentDocumentChange(); - return phase; - } - - @ApiStatus.Internal - @RequiresBackgroundThread - public static void loadContributorsOutsideEdt(@NotNull Editor editor, @NotNull PsiFile file) { - CompletionContributor.forLanguage(PsiUtilCore.getLanguageAtOffset(file, editor.getCaretModel().getOffset())); - } - - @ApiStatus.Internal - public static boolean shouldSkipAutoPopup(@NotNull Editor editor, @NotNull PsiFile psiFile) { - int offset = editor.getCaretModel().getOffset(); - int psiOffset = Math.max(0, offset - 1); - - PsiElement elementAt = psiFile.findElementAt(psiOffset); - if (elementAt == null) return true; - - Language language = PsiUtilCore.findLanguageFromElement(elementAt); - - for (CompletionConfidence confidence : CompletionConfidenceEP.forLanguage(language)) { - try { - ThreeState result = confidence.shouldSkipAutopopup(editor, elementAt, psiFile, offset); - if (result != ThreeState.UNSURE) { - LOG.debug(confidence + " has returned shouldSkipAutopopup=" + result); - return result == ThreeState.YES; + catch (e: IndexNotReadyException) { + LOG.debug(e) + return true } } - catch (IndexNotReadyException e) { - LOG.debug(e); - return true; - } + return false } - return false; } } - public static final class Synchronous extends CompletionPhase { - Synchronous(@NotNull 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; + class Synchronous internal constructor(indicator: CompletionProgressIndicator) : CompletionPhase(indicator) { + override fun newCompletionStarted(time: Int, repeated: Boolean): Int { + assertPhase(NoCompletion.javaClass) // will fail and log valuable info + CompletionServiceImpl.setCompletionPhase(NoCompletion) + return time } } - public static final class BgCalculation extends CompletionPhase { - boolean modifiersChanged = false; - private final @NotNull ClientId ownerId = ClientId.getCurrent(); + class BgCalculation internal constructor(indicator: CompletionProgressIndicator) : CompletionPhase(indicator) { + @JvmField + internal var modifiersChanged: Boolean = false + private val ownerId = ClientId.current - BgCalculation(@NotNull CompletionProgressIndicator indicator) { - super(indicator); - restartOnWriteAction(); - cancelOnEditorLoseFocus(indicator); + init { + restartOnWriteAction() + cancelOnEditorLoseFocus(indicator) } - private void restartOnWriteAction() { - ApplicationManager.getApplication().addApplicationListener(new ApplicationListener() { - @Override - public void beforeWriteActionStart(@NotNull Object action) { - if (!indicator.getLookup().isLookupDisposed() && !indicator.isCanceled() && ownerId.equals(ClientId.getCurrent())) { - indicator.cancel(); + private fun restartOnWriteAction() { + ApplicationManager.getApplication().addApplicationListener(object : ApplicationListener { + override fun beforeWriteActionStart(action: Any) { + if (!indicator!!.lookup.isLookupDisposed && !indicator.isCanceled && ownerId == ClientId.current) { + indicator.cancel() if (EDT.isCurrentThreadEdt()) { - indicator.scheduleRestart(); - } else { + indicator.scheduleRestart() + } + else { // this branch is possible because completion can be canceled on background write action ApplicationManager.getApplication().invokeLater( - indicator::scheduleRestart, - // since we break the synchronous execution here, it is possible that some other EDT event finishes completion before us + /* runnable = */ Runnable { indicator.scheduleRestart() }, // since we break the synchronous execution here, it is possible that some other EDT event finishes completion before us + // in this case, the current indicator becomes obsolete, and we don't need to reschedule the session anymore - (__) -> CompletionServiceImpl.getCurrentCompletionProgressIndicator() != indicator); + /* expired = */ Condition { CompletionServiceImpl.currentCompletionProgressIndicator != indicator }) } } } - }, this); + }, this) } - private void cancelOnEditorLoseFocus(@NotNull CompletionProgressIndicator indicator) { - if (indicator.isAutopopupCompletion()) { + private fun cancelOnEditorLoseFocus(indicator: CompletionProgressIndicator) { + if (indicator.isAutopopupCompletion) { // lookup is not visible, we have to check ourselves if the editor retains focus - ((EditorEx)indicator.getEditor()).addFocusListener(new FocusChangeListener() { - @Override - public void focusLost(@NotNull Editor editor, @NotNull FocusEvent event) { + (indicator.editor as EditorEx).addFocusListener(object : FocusChangeListener { + override fun focusLost(editor: Editor, event: FocusEvent) { // When ScreenReader is active, the lookup gets focus on show, and we should not close it. if (ScreenReader.isActive() && - event.getOppositeComponent() != null && - // Check the opposite is in the lookup ancestor - SwingUtilities.getWindowAncestor(event.getOppositeComponent()) == - SwingUtilities.getWindowAncestor(indicator.getLookup().getComponent())) { - return; + event.getOppositeComponent() != null && // Check the opposite is in the lookup ancestor + SwingUtilities.getWindowAncestor(event.getOppositeComponent()) === SwingUtilities.getWindowAncestor(indicator.lookup.component) + ) { + return } - indicator.closeAndFinish(true); + indicator.closeAndFinish(true) } - }, this); + }, this) } } - @Override - public int newCompletionStarted(int time, boolean repeated) { - indicator.closeAndFinish(false); - return indicator.nextInvocationCount(time, repeated); + override fun newCompletionStarted(time: Int, repeated: Boolean): Int { + indicator!!.closeAndFinish(false) + return indicator.nextInvocationCount(time, repeated) } } - public static final class ItemsCalculated extends CompletionPhase { - - ItemsCalculated(@NotNull CompletionProgressIndicator indicator) { - super(indicator); - } - - @Override - public int newCompletionStarted(int time, boolean repeated) { - Objects.requireNonNull(indicator, "`ItemsCalculated#indicator` is not-null as its constructor accepts not-null `indicator`").closeAndFinish(false); - return indicator.nextInvocationCount(time, repeated); + class ItemsCalculated internal constructor(indicator: CompletionProgressIndicator) : CompletionPhase(indicator) { + override fun newCompletionStarted(time: Int, repeated: Boolean): Int { + requireNotNull(indicator) { "`ItemsCalculated#indicator` is not-null as its constructor accepts not-null `indicator`" }.closeAndFinish(false) + return indicator.nextInvocationCount(time, repeated) } } - public abstract static class ZombiePhase extends CompletionPhase { - - ZombiePhase(@Nullable CompletionProgressIndicator indicator) { - super(indicator); - } - - void expireOnAnyEditorChange(@NotNull Editor editor) { - editor.getDocument().addDocumentListener(new DocumentListener() { - @Override - public void beforeDocumentChange(@NotNull DocumentEvent e) { - CompletionServiceImpl.setCompletionPhase(NoCompletion); + abstract class ZombiePhase internal constructor(indicator: CompletionProgressIndicator?) : CompletionPhase(indicator) { + internal fun expireOnAnyEditorChange(editor: Editor) { + editor.getDocument().addDocumentListener(object : DocumentListener { + override fun beforeDocumentChange(e: DocumentEvent) { + CompletionServiceImpl.setCompletionPhase(NoCompletion) } - }, this); - editor.getSelectionModel().addSelectionListener(new SelectionListener() { - @Override - public void selectionChanged(@NotNull SelectionEvent e) { - CompletionServiceImpl.setCompletionPhase(NoCompletion); + }, this) + editor.getSelectionModel().addSelectionListener(object : SelectionListener { + override fun selectionChanged(e: SelectionEvent) { + CompletionServiceImpl.setCompletionPhase(NoCompletion) } - }, this); - editor.getCaretModel().addCaretListener(new CaretListener() { - @Override - public void caretPositionChanged(@NotNull CaretEvent e) { - CompletionServiceImpl.setCompletionPhase(NoCompletion); + }, this) + editor.getCaretModel().addCaretListener(object : CaretListener { + override fun caretPositionChanged(e: CaretEvent) { + CompletionServiceImpl.setCompletionPhase(NoCompletion) } - }, this); + }, this) } } - public static final class InsertedSingleItem extends ZombiePhase { - public final @NotNull Runnable restorePrefix; - - InsertedSingleItem(@NotNull CompletionProgressIndicator indicator, @NotNull Runnable restorePrefix) { - super(indicator); - this.restorePrefix = restorePrefix; - expireOnAnyEditorChange(indicator.getEditor()); + class InsertedSingleItem internal constructor( + indicator: CompletionProgressIndicator, + @JvmField + val restorePrefix: Runnable + ) : ZombiePhase(indicator) { + init { + expireOnAnyEditorChange(indicator.editor) } - @Override - public int newCompletionStarted(int time, boolean repeated) { - CompletionServiceImpl.setCompletionPhase(NoCompletion); + override fun newCompletionStarted(time: Int, repeated: Boolean): Int { + CompletionServiceImpl.setCompletionPhase(NoCompletion) if (repeated) { - indicator.restorePrefix(restorePrefix); + indicator!!.restorePrefix(restorePrefix) } - return indicator.nextInvocationCount(time, repeated); + return indicator!!.nextInvocationCount(time, repeated) } } - public static final class NoSuggestionsHint extends ZombiePhase { - NoSuggestionsHint(@Nullable LightweightHint hint, @NotNull CompletionProgressIndicator indicator) { - super(indicator); - expireOnAnyEditorChange(indicator.getEditor()); + class NoSuggestionsHint internal constructor(hint: LightweightHint?, indicator: CompletionProgressIndicator) : ZombiePhase(indicator) { + init { + expireOnAnyEditorChange(indicator.editor) if (hint != null) { - HintListener hintListener = event -> CompletionServiceImpl.setCompletionPhase(NoCompletion); - hint.addHintListener(hintListener); - Disposer.register(this, () -> hint.removeHintListener(hintListener)); + val hintListener = HintListener { CompletionServiceImpl.setCompletionPhase(NoCompletion) } + hint.addHintListener(hintListener) + Disposer.register(this, Disposable { hint.removeHintListener(hintListener) }) } } - @Override - public int newCompletionStarted(int time, boolean repeated) { - CompletionServiceImpl.setCompletionPhase(NoCompletion); - return indicator.nextInvocationCount(time, repeated); + override fun newCompletionStarted(time: Int, repeated: Boolean): Int { + CompletionServiceImpl.setCompletionPhase(NoCompletion) + return indicator!!.nextInvocationCount(time, repeated) } } - public static final class EmptyAutoPopup extends ZombiePhase { - private final ActionTracker myTracker; - private final Editor myEditor; - private final Set>> myRestartingPrefixConditions; + class EmptyAutoPopup internal constructor( + editor: Editor, + private val restartingPrefixConditions: Set>> + ) : ZombiePhase(null) { + private val myTracker: ActionTracker = ActionTracker(editor, this) + private val myEditor: Editor = editor - EmptyAutoPopup(@NotNull Editor editor, - @NotNull Set>> restartingPrefixConditions) { - super(null); - myTracker = new ActionTracker(editor, this); - myEditor = editor; - myRestartingPrefixConditions = restartingPrefixConditions; - } - - public boolean allowsSkippingNewAutoPopup(@NotNull Editor editor, char toType) { - if (myEditor == editor && - !myTracker.hasAnythingHappened() && - !CompletionProgressIndicator.shouldRestartCompletion(editor, myRestartingPrefixConditions, String.valueOf(toType))) { - myTracker.ignoreCurrentDocumentChange(); - return true; + fun allowsSkippingNewAutoPopup(editor: Editor, toType: Char): Boolean { + if (myEditor === editor && !myTracker.hasAnythingHappened() && !CompletionProgressIndicator.shouldRestartCompletion(editor, restartingPrefixConditions, toType.toString())) { + myTracker.ignoreCurrentDocumentChange() + return true } - return false; + return false } - @Override - public int newCompletionStarted(int time, boolean repeated) { - CompletionServiceImpl.setCompletionPhase(NoCompletion); - return time; + override fun newCompletionStarted(time: Int, repeated: Boolean): Int { + CompletionServiceImpl.setCompletionPhase(NoCompletion) + return time + } + } + + @ApiStatus.Internal + companion object { + @ApiStatus.Internal + @JvmField + internal val AUTO_POPUP_TYPED_EVENT: Key = Key.create("AutoPopupTypedEvent") + + @ApiStatus.Internal + @JvmField + val CUSTOM_CODE_COMPLETION_ACTION_ID: Key = Key.create("CodeCompletionActionID") + + @JvmField + val NoCompletion: CompletionPhase = object : CompletionPhase(null) { + override fun newCompletionStarted(time: Int, repeated: Boolean): Int { + return time + } + + override fun toString(): String { + return "NoCompletion" + } } } } + +private val LOG = Logger.getInstance(CompletionPhase::class.java) diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CompletionServiceImpl.kt b/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CompletionServiceImpl.kt index db367c999868..2ef1ec992ad9 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CompletionServiceImpl.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CompletionServiceImpl.kt @@ -3,6 +3,7 @@ package com.intellij.codeInsight.completion.impl import com.intellij.codeInsight.completion.* import com.intellij.codeInsight.completion.CompletionPhase.* +import com.intellij.codeInsight.completion.CompletionPhase.Companion.NoCompletion import com.intellij.codeInsight.completion.StatisticsWeigher.LookupStatisticsWeigher import com.intellij.codeInsight.lookup.Classifier import com.intellij.codeInsight.lookup.ClassifierFactory diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/TerminalCommandCompletion.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/TerminalCommandCompletion.kt index 2dfb2bebbaa6..86beb4a0065e 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/TerminalCommandCompletion.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/TerminalCommandCompletion.kt @@ -52,7 +52,7 @@ internal class TerminalCommandCompletion( var time = time val phase = CompletionServiceImpl.completionPhase - val repeated = phase.indicator != null && phase.indicator.isRepeatedInvocation(completionType, editor) + val repeated = phase.indicator?.isRepeatedInvocation(completionType, editor) == true val newTime = phase.newCompletionStarted(time, repeated) if (invokedExplicitly) {