@NotNull
  private LookupImpl obtainLookup(Editor editor) {
    CompletionAssertions.checkEditorValid(editor);
    LookupImpl existing = (LookupImpl) LookupManager.getActiveLookup(editor);
    if (existing != null && existing.isCompletion()) {
      existing.markReused();
      if (!autopopup) {
        existing.setFocusDegree(LookupImpl.FocusDegree.FOCUSED);
      }
      return existing;
    }

    LookupImpl lookup =
        (LookupImpl)
            LookupManager.getInstance(editor.getProject())
                .createLookup(
                    editor, LookupElement.EMPTY_ARRAY, "", new LookupArranger.DefaultArranger());
    if (editor.isOneLineMode()) {
      lookup.setCancelOnClickOutside(true);
      lookup.setCancelOnOtherWindowOpen(true);
    }
    lookup.setFocusDegree(
        autopopup ? LookupImpl.FocusDegree.UNFOCUSED : LookupImpl.FocusDegree.FOCUSED);
    return lookup;
  }
  protected void completionFinished(
      final int offset1,
      final int offset2,
      final CompletionProgressIndicator indicator,
      final LookupElement[] items,
      boolean hasModifiers) {
    if (items.length == 0) {
      LookupManager.getInstance(indicator.getProject()).hideActiveLookup();
      indicator.handleEmptyLookup(true);
      checkNotSync(indicator, items);
      return;
    }

    LOG.assertTrue(!indicator.isRunning(), "running");
    LOG.assertTrue(!indicator.isCanceled(), "canceled");

    indicator.getLookup().refreshUi(true, false);
    final AutoCompletionDecision decision = shouldAutoComplete(indicator, items);
    if (decision == AutoCompletionDecision.SHOW_LOOKUP) {
      CompletionServiceImpl.setCompletionPhase(new CompletionPhase.ItemsCalculated(indicator));
      indicator.getLookup().setCalculating(false);
      indicator.showLookup();
    } else if (decision instanceof AutoCompletionDecision.InsertItem) {
      final Runnable restorePrefix = rememberDocumentState(indicator.getEditor());

      final LookupElement item = ((AutoCompletionDecision.InsertItem) decision).getElement();
      CommandProcessor.getInstance()
          .executeCommand(
              indicator.getProject(),
              new Runnable() {
                @Override
                public void run() {
                  indicator.setMergeCommand();
                  indicator.getLookup().finishLookup(Lookup.AUTO_INSERT_SELECT_CHAR, item);
                }
              },
              "Autocompletion",
              null);

      // the insert handler may have started a live template with completion
      if (CompletionService.getCompletionService().getCurrentCompletion() == null
          && !ApplicationManager.getApplication().isUnitTestMode()) {
        CompletionServiceImpl.setCompletionPhase(
            hasModifiers
                ? new CompletionPhase.InsertedSingleItem(indicator, restorePrefix)
                : CompletionPhase.NoCompletion);
      }
      checkNotSync(indicator, items);
    } else if (decision == AutoCompletionDecision.CLOSE_LOOKUP) {
      LookupManager.getInstance(indicator.getProject()).hideActiveLookup();
      checkNotSync(indicator, items);
    }
  }
  public void closeAndFinish(boolean hideLookup) {
    if (!myLookup.isLookupDisposed()) {
      Lookup lookup = LookupManager.getActiveLookup(myEditor);
      LOG.assertTrue(lookup == myLookup, "lookup changed: " + lookup + "; " + this);
    }
    myLookup.removeLookupListener(myLookupListener);
    finishCompletionProcess(true);
    CompletionServiceImpl.assertPhase(CompletionPhase.NoCompletion.getClass());

    if (hideLookup) {
      LookupManager.getInstance(getProject()).hideActiveLookup();
    }
  }
  private void finishCompletionProcess(boolean disposeOffsetMap) {
    cancel();

    ApplicationManager.getApplication().assertIsDispatchThread();
    Disposer.dispose(myQueue);
    LookupManager.getInstance(getProject()).removePropertyChangeListener(myLookupManagerListener);

    CompletionProgressIndicator currentCompletion =
        CompletionServiceImpl.getCompletionService().getCurrentCompletion();
    LOG.assertTrue(currentCompletion == this, currentCompletion + "!=" + this);

    CompletionServiceImpl.assertPhase(
        CompletionPhase.BgCalculation.class,
        CompletionPhase.ItemsCalculated.class,
        CompletionPhase.Synchronous.class,
        CompletionPhase.CommittingDocuments.class);
    if (CompletionServiceImpl.getCompletionPhase() instanceof CompletionPhase.CommittingDocuments) {
      LOG.assertTrue(
          CompletionServiceImpl.getCompletionPhase().indicator != null,
          CompletionServiceImpl.getCompletionPhase());
      ((CompletionPhase.CommittingDocuments) CompletionServiceImpl.getCompletionPhase()).replaced =
          true;
    }
    CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
    if (disposeOffsetMap) {
      disposeOffsetMaps();
    }
  }
  public CompletionProgressIndicator(
      final Editor editor,
      CompletionParameters parameters,
      CodeCompletionHandlerBase handler,
      Semaphore freezeSemaphore,
      final OffsetMap offsetMap,
      boolean hasModifiers) {
    myEditor = editor;
    myParameters = parameters;
    myHandler = handler;
    myFreezeSemaphore = freezeSemaphore;
    myOffsetMap = offsetMap;
    myLookup = (LookupImpl) parameters.getLookup();

    myLookup.setArranger(new CompletionLookupArranger(parameters, this));

    myLookup.addLookupListener(myLookupListener);
    myLookup.setCalculating(true);

    myLookupManagerListener =
        new PropertyChangeListener() {
          @Override
          public void propertyChange(PropertyChangeEvent evt) {
            if (evt.getNewValue() != null) {
              LOG.error(
                  "An attempt to change the lookup during completion, phase = "
                      + CompletionServiceImpl.getCompletionPhase());
            }
          }
        };
    LookupManager.getInstance(getProject()).addPropertyChangeListener(myLookupManagerListener);

    myQueue =
        new MergingUpdateQueue(
            "completion lookup progress", 200, true, myEditor.getContentComponent());
    myQueue.setPassThrough(false);

    ApplicationManager.getApplication().assertIsDispatchThread();
    addMapToDispose(offsetMap);

    if (ApplicationManager.getApplication().isUnitTestMode()) {
      return;
    }

    if (!myLookup.isShown()) {
      scheduleAdvertising();
    }

    if (hasModifiers) {
      trackModifiers();
    }
  }
  private void invokeCompletion(final ExpressionContext context) {
    final Project project = context.getProject();
    final Editor editor = context.getEditor();

    final PsiFile psiFile = editor != null ? PsiUtilBase.getPsiFileInEditor(editor, project) : null;
    Runnable runnable =
        () -> {
          if (project.isDisposed()
              || editor == null
              || editor.isDisposed()
              || psiFile == null
              || !psiFile.isValid()) return;

          // it's invokeLater, so another completion could have started
          if (CompletionServiceImpl.getCompletionService().getCurrentCompletion() != null) return;

          CommandProcessor.getInstance()
              .executeCommand(
                  project,
                  () -> {
                    // if we're in some completion's insert handler, make sure our new completion
                    // isn't treated as the second invocation
                    CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);

                    invokeCompletionHandler(project, editor);
                    Lookup lookup = LookupManager.getInstance(project).getActiveLookup();

                    if (lookup != null) {
                      lookup.addLookupListener(
                          new MyLookupListener(context, myCheckCompletionChar));
                    }
                  },
                  "",
                  null);
        };
    ApplicationManager.getApplication().invokeLater(runnable);
  }