static void performAtomically(@NotNull PsiFile file, @NotNull Runnable runnable) {
    assert !isInsideAtomicChange(file);
    file.putUserData(PSI_DOCUMENT_ATOMIC_ACTION, Boolean.TRUE);

    try {
      runnable.run();
    } finally {
      file.putUserData(PSI_DOCUMENT_ATOMIC_ACTION, null);
    }
  }
 public static void installCompletion(
     Document document, Project project, @Nullable Consumer<Task> consumer, boolean autoPopup) {
   PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document);
   if (psiFile != null) {
     //noinspection unchecked
     psiFile.putUserData(
         KEY, consumer == null ? (Consumer<Task>) Consumer.EMPTY_CONSUMER : consumer);
     psiFile.putUserData(AUTO_POPUP_KEY, autoPopup);
   }
 }
  private static PsiFile createFileCopy(PsiFile file, long caret, long selEnd) {
    final VirtualFile virtualFile = file.getVirtualFile();
    boolean mayCacheCopy =
        file.isPhysical()
            &&
            // we don't want to cache code fragment copies even if they appear to be physical
            virtualFile != null
            && virtualFile.isInLocalFileSystem();
    long combinedOffsets = caret + (selEnd << 32);
    if (mayCacheCopy) {
      final Trinity<PsiFile, Document, Long> cached =
          SoftReference.dereference(file.getUserData(FILE_COPY_KEY));
      if (cached != null
          && cached.first.getClass().equals(file.getClass())
          && isCopyUpToDate(cached.second, cached.first)) {
        final PsiFile copy = cached.first;
        if (copy.getViewProvider().getModificationStamp()
                > file.getViewProvider().getModificationStamp()
            && cached.third.longValue() != combinedOffsets) {
          // the copy PSI might have some caches that are not cleared on its modification because
          // there are no events in the copy
          //   so, clear all the caches
          // hopefully it's a rare situation that the user invokes completion in different parts of
          // the file
          //   without modifying anything physical in between
          ((PsiModificationTrackerImpl) file.getManager().getModificationTracker()).incCounter();
        }
        final Document document = cached.second;
        assert document != null;
        file.putUserData(
            FILE_COPY_KEY,
            new SoftReference<Trinity<PsiFile, Document, Long>>(
                Trinity.create(copy, document, combinedOffsets)));

        Document originalDocument = file.getViewProvider().getDocument();
        assert originalDocument != null;
        assert originalDocument.getTextLength() == file.getTextLength() : originalDocument;
        document.setText(originalDocument.getImmutableCharSequence());
        return copy;
      }
    }

    final PsiFile copy = (PsiFile) file.copy();
    if (mayCacheCopy) {
      final Document document = copy.getViewProvider().getDocument();
      assert document != null;
      file.putUserData(
          FILE_COPY_KEY,
          new SoftReference<Trinity<PsiFile, Document, Long>>(
              Trinity.create(copy, document, combinedOffsets)));
    }
    return copy;
  }
  private static void assertAfterCommit(
      Document document,
      final PsiFile file,
      String oldPsiText,
      FileElement myTreeElementBeingReparsedSoItWontBeCollected) {
    if (myTreeElementBeingReparsedSoItWontBeCollected.getTextLength() != document.getTextLength()) {
      final String documentText = document.getText();
      if (ApplicationManagerEx.getApplicationEx().isInternal()) {
        String fileText = file.getText();
        LOG.error(
            "commitDocument left PSI inconsistent; file len="
                + myTreeElementBeingReparsedSoItWontBeCollected.getTextLength()
                + "; doc len="
                + document.getTextLength()
                + "; doc.getText() == file.getText(): "
                + Comparing.equal(fileText, documentText)
                + ";\n file psi text="
                + fileText
                + ";\n doc text="
                + documentText
                + ";\n old psi file text="
                + oldPsiText);
      } else {
        LOG.error("commitDocument left PSI inconsistent: " + file);
      }

      file.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, Boolean.TRUE);
      try {
        BlockSupport blockSupport = BlockSupport.getInstance(file.getProject());
        final DiffLog diffLog =
            blockSupport.reparseRange(
                file, 0, documentText.length(), 0, documentText, new ProgressIndicatorBase());
        CodeStyleManager.getInstance(file.getProject())
            .performActionWithFormatterDisabled(
                new Runnable() {
                  @Override
                  public void run() {
                    synchronized (PsiLock.LOCK) {
                      doActualPsiChange(file, diffLog);
                    }
                  }
                });

        if (myTreeElementBeingReparsedSoItWontBeCollected.getTextLength()
            != document.getTextLength()) {
          LOG.error("PSI is broken beyond repair in: " + file);
        }
      } finally {
        file.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, null);
      }
    }
  }
  @Override
  @Nullable
  public ObjectStubTree readOrBuild(
      Project project, final VirtualFile vFile, @Nullable PsiFile psiFile) {
    final ObjectStubTree fromIndices = readFromVFile(project, vFile);
    if (fromIndices != null) {
      return fromIndices;
    }

    if (!canHaveStub(vFile)) {
      return null;
    }

    try {
      final FileContent fc = new FileContentImpl(vFile, vFile.contentsToByteArray());
      fc.putUserData(IndexingDataKeys.PROJECT, project);
      if (psiFile != null) {
        fc.putUserData(IndexingDataKeys.PSI_FILE, psiFile);
        if (!vFile.getFileType().isBinary()) {
          fc.putUserData(
              IndexingDataKeys.FILE_TEXT_CONTENT_KEY, psiFile.getViewProvider().getContents());
        }
        psiFile.putUserData(PsiFileImpl.BUILDING_STUB, true);
      }
      Stub element;
      try {
        element = StubTreeBuilder.buildStubTree(fc);
      } finally {
        if (psiFile != null) {
          psiFile.putUserData(PsiFileImpl.BUILDING_STUB, null);
        }
      }
      if (element instanceof PsiFileStub) {
        StubTree tree = new StubTree((PsiFileStub) element);
        tree.setDebugInfo("created from file content, timestamp=" + vFile.getTimeStamp());
        return tree;
      }
    } catch (IOException e) {
      LOG.info(
          e); // content can be not cached yet, and the file can be deleted on disk already, without
      // refresh
    }

    return null;
  }
  /**
   * @return psiFile associated with the content. If the file was not set on FileContentCreation, it
   *     will be created on the spot
   */
  @NotNull
  @Override
  public PsiFile getPsiFile() {
    PsiFile psi = getUserData(IndexingDataKeys.PSI_FILE);

    if (psi == null) {
      psi = getUserData(CACHED_PSI);
    }

    if (psi == null) {
      psi = createFileFromText(getContentAsText());
      psi.putUserData(IndexingDataKeys.VIRTUAL_FILE, getFile());
      putUserData(CACHED_PSI, psi);
    }
    return psi;
  }
  @Override
  public PsiElement getMirror() {
    TreeElement mirrorTreeElement = myMirrorFileElement;
    if (mirrorTreeElement == null) {
      synchronized (myMirrorLock) {
        mirrorTreeElement = myMirrorFileElement;
        if (mirrorTreeElement == null) {
          VirtualFile file = getVirtualFile();
          PsiClass[] classes = getClasses();
          String fileName =
              (classes.length > 0 ? classes[0].getName() : file.getNameWithoutExtension())
                  + JavaFileType.DOT_DEFAULT_EXTENSION;

          final Document document = FileDocumentManager.getInstance().getDocument(file);
          assert document != null : file.getUrl();

          CharSequence mirrorText = document.getImmutableCharSequence();
          boolean internalDecompiler = StringUtil.startsWith(mirrorText, BANNER);
          PsiFileFactory factory = PsiFileFactory.getInstance(getManager().getProject());
          PsiFile mirror =
              factory.createFileFromText(fileName, JavaLanguage.INSTANCE, mirrorText, false, false);
          mirror.putUserData(PsiUtil.FILE_LANGUAGE_LEVEL_KEY, getLanguageLevel());

          mirrorTreeElement = SourceTreeToPsiMap.psiToTreeNotNull(mirror);
          try {
            final TreeElement finalMirrorTreeElement = mirrorTreeElement;
            ProgressManager.getInstance()
                .executeNonCancelableSection(
                    new Runnable() {
                      @Override
                      public void run() {
                        setMirror(finalMirrorTreeElement);
                        putUserData(CLS_DOCUMENT_LINK_KEY, document);
                      }
                    });
          } catch (InvalidMirrorException e) {
            //noinspection ThrowableResultOfMethodCallIgnored
            LOG.error(file.getUrl(), internalDecompiler ? e : wrapException(e, file));
          }

          ((PsiFileImpl) mirror).setOriginalFile(this);
          myMirrorFileElement = mirrorTreeElement;
        }
      }
    }
    return mirrorTreeElement.getPsi();
  }
  @Nullable
  @Override
  public Document getDocument(@NotNull PsiFile file) {
    if (file instanceof PsiBinaryFile) return null;

    Document document = getCachedDocument(file);
    if (document != null) {
      if (!file.getViewProvider().isPhysical() && document.getUserData(HARD_REF_TO_PSI) == null) {
        PsiUtilCore.ensureValid(file);
        cachePsi(document, file);
      }
      return document;
    }

    FileViewProvider viewProvider = file.getViewProvider();
    if (!viewProvider.isEventSystemEnabled()) return null;

    document = FileDocumentManager.getInstance().getDocument(viewProvider.getVirtualFile());
    if (document != null) {
      if (document.getTextLength() != file.getTextLength()) {
        String message =
            "Document/PSI mismatch: "
                + file
                + " ("
                + file.getClass()
                + "); physical="
                + viewProvider.isPhysical();
        if (document.getTextLength() + file.getTextLength() < 8096) {
          message +=
              "\n=== document ===\n" + document.getText() + "\n=== PSI ===\n" + file.getText();
        }
        throw new AssertionError(message);
      }

      if (!viewProvider.isPhysical()) {
        PsiUtilCore.ensureValid(file);
        cachePsi(document, file);
        file.putUserData(HARD_REF_TO_DOCUMENT, document);
      }
    }

    return document;
  }
  @Override
  public void customize(@NotNull EditorEx editor) {
    boolean apply = isEnabled();

    if (!READY) {
      return;
    }

    Project project = editor.getProject();
    if (project == null) {
      return;
    }

    PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
    if (file == null) {
      return;
    }

    Function<InspectionProfileImpl, InspectionProfileWrapper> strategy =
        file.getUserData(InspectionProfileWrapper.CUSTOMIZATION_KEY);
    if (strategy == null) {
      file.putUserData(
          InspectionProfileWrapper.CUSTOMIZATION_KEY, strategy = new MyInspectionProfileStrategy());
    }

    if (!(strategy instanceof MyInspectionProfileStrategy)) {
      return;
    }

    ((MyInspectionProfileStrategy) strategy).setUseSpellCheck(apply);

    if (apply) {
      editor.putUserData(IntentionManager.SHOW_INTENTION_OPTIONS_KEY, false);
    }

    // Update representation.
    DaemonCodeAnalyzer analyzer = DaemonCodeAnalyzer.getInstance(project);
    if (analyzer != null) {
      analyzer.restart(file);
    }
  }
  private static Collection<String> suggestKeywords(PsiElement position) {
    TextRange posRange = position.getTextRange();
    BnfFile posFile = (BnfFile) position.getContainingFile();
    BnfRule statement = PsiTreeUtil.getTopmostParentOfType(position, BnfRule.class);
    final TextRange range;
    if (statement != null) {
      range = new TextRange(statement.getTextRange().getStartOffset(), posRange.getStartOffset());
    } else {
      int offset = posRange.getStartOffset();
      for (PsiElement cur = GrammarUtil.getDummyAwarePrevSibling(position);
          cur != null;
          cur = GrammarUtil.getDummyAwarePrevSibling(cur)) {
        if (cur instanceof BnfAttrs) offset = cur.getTextRange().getEndOffset();
        else if (cur instanceof BnfRule) offset = cur.getTextRange().getStartOffset();
        else continue;
        break;
      }
      range = new TextRange(offset, posRange.getStartOffset());
    }
    final String text =
        range.isEmpty()
            ? CompletionInitializationContext.DUMMY_IDENTIFIER
            : range.substring(posFile.getText());

    PsiFile file =
        PsiFileFactory.getInstance(posFile.getProject())
            .createFileFromText("a.bnf", BnfLanguage.INSTANCE, text, true, false);
    int completionOffset = posRange.getStartOffset() - range.getStartOffset();
    GeneratedParserUtilBase.CompletionState state =
        new GeneratedParserUtilBase.CompletionState(completionOffset) {
          @Override
          public String convertItem(Object o) {
            // we do not have other keywords
            return o instanceof String ? (String) o : null;
          }
        };
    file.putUserData(GeneratedParserUtilBase.COMPLETION_STATE_KEY, state);
    TreeUtil.ensureParsed(file.getNode());
    return state.items;
  }
  private XmlNSDescriptor getDefaultNSDescriptorInner(
      final String namespace, final boolean strict) {
    final XmlFile containingFile = XmlUtil.getContainingFile(this);
    if (containingFile == null) return null;
    final XmlProlog prolog = getProlog();
    final XmlDoctype doctype = prolog != null ? prolog.getDoctype() : null;
    boolean dtdUriFromDocTypeIsNamespace = false;

    if (XmlUtil.HTML_URI.equals(namespace)) {
      XmlNSDescriptor nsDescriptor =
          doctype != null ? getNsDescriptorFormDocType(doctype, containingFile, true) : null;
      if (doctype != null) {
        LOG.debug(
            "Descriptor from doctype "
                + doctype
                + " is "
                + (nsDescriptor != null ? nsDescriptor.getClass().getCanonicalName() : "NULL"));
      }

      if (nsDescriptor == null) {
        String htmlns =
            ExternalResourceManagerEx.getInstanceEx().getDefaultHtmlDoctype(getProject());
        if (htmlns.isEmpty()) {
          htmlns = Html5SchemaProvider.getHtml5SchemaLocation();
        }
        nsDescriptor = getDefaultNSDescriptor(htmlns, false);
      }
      return new HtmlNSDescriptorImpl(nsDescriptor);
    } else if (XmlUtil.XHTML_URI.equals(namespace)) {
      String xhtmlNamespace = XmlUtil.getDefaultXhtmlNamespace(getProject());
      if (xhtmlNamespace == null || xhtmlNamespace.isEmpty()) {
        xhtmlNamespace = Html5SchemaProvider.getXhtml5SchemaLocation();
      }
      return getDefaultNSDescriptor(xhtmlNamespace, false);
    } else if (namespace != null && namespace != XmlUtil.EMPTY_URI) {
      if (doctype == null || !namespace.equals(XmlUtil.getDtdUri(doctype))) {
        boolean documentIsSchemaThatDefinesNs =
            namespace.equals(XmlUtil.getTargetSchemaNsFromTag(getRootTag()));

        final XmlFile xmlFile =
            documentIsSchemaThatDefinesNs
                ? containingFile
                : XmlUtil.findNamespace(containingFile, namespace);
        if (xmlFile != null) {
          final XmlDocument document = xmlFile.getDocument();
          if (document != null) {
            return (XmlNSDescriptor) document.getMetaData();
          }
        }
      } else {
        dtdUriFromDocTypeIsNamespace = true;
      }
    }

    if (strict && !dtdUriFromDocTypeIsNamespace) return null;

    if (doctype != null) {
      XmlNSDescriptor descr = getNsDescriptorFormDocType(doctype, containingFile, false);

      if (descr != null) {
        return XmlExtension.getExtension(containingFile)
            .getDescriptorFromDoctype(containingFile, descr);
      }
    }

    if (strict) return null;
    if (namespace == XmlUtil.EMPTY_URI) {
      final XmlFile xmlFile = XmlUtil.findNamespace(containingFile, namespace);
      if (xmlFile != null) {
        return (XmlNSDescriptor) xmlFile.getDocument().getMetaData();
      }
    }
    try {
      final PsiFile fileFromText =
          PsiFileFactory.getInstance(getProject())
              .createFileFromText(
                  containingFile.getName() + ".dtd",
                  DTDLanguage.INSTANCE,
                  XmlUtil.generateDocumentDTD(this, false),
                  false,
                  false);
      if (fileFromText instanceof XmlFile) {
        fileFromText.putUserData(AUTO_GENERATED, Boolean.TRUE);
        return (XmlNSDescriptor) ((XmlFile) fileFromText).getDocument().getMetaData();
      }
    } catch (ProcessCanceledException ex) {
      throw ex;
    } catch (RuntimeException ignored) {
    } // e.g. dtd isn't mapped to xml type

    return null;
  }
  @Nullable("returns runnable to execute under write action in AWT to finish the commit")
  private Processor<Document> doCommit(
      @NotNull final Document document,
      @NotNull final PsiFile file,
      @NotNull ProgressIndicator indicator,
      final boolean synchronously,
      @NotNull PsiDocumentManager documentManager) {
    ((PsiDocumentManagerImpl) documentManager).clearTreeHardRef(document);
    final TextBlock textBlock = TextBlock.get(file);
    if (textBlock.isEmpty()) return null;
    final long startPsiModificationTimeStamp = file.getModificationStamp();
    final long startDocModificationTimeStamp = document.getModificationStamp();
    final FileElement myTreeElementBeingReparsedSoItWontBeCollected =
        ((PsiFileImpl) file).calcTreeElement();
    if (textBlock.isEmpty())
      return null; // if tree was just loaded above textBlock will be cleared by contentsLoaded
    final CharSequence chars = document.getCharsSequence();
    final Boolean data = document.getUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY);
    if (data != null) {
      document.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, null);
      file.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, data);
    }
    final String oldPsiText =
        ApplicationManagerEx.getApplicationEx().isInternal()
                && !ApplicationManagerEx.getApplicationEx().isUnitTestMode()
            ? myTreeElementBeingReparsedSoItWontBeCollected.getText()
            : null;
    int startOffset;
    int endOffset;
    int lengthShift;
    if (file.getViewProvider().supportsIncrementalReparse(file.getLanguage())) {
      startOffset = textBlock.getStartOffset();
      int psiEndOffset = textBlock.getPsiEndOffset();
      endOffset = psiEndOffset;
      lengthShift = textBlock.getTextEndOffset() - psiEndOffset;
    } else {
      startOffset = 0;
      endOffset = document.getTextLength();
      lengthShift =
          document.getTextLength() - myTreeElementBeingReparsedSoItWontBeCollected.getTextLength();
    }
    assertBeforeCommit(
        document,
        file,
        textBlock,
        chars,
        oldPsiText,
        myTreeElementBeingReparsedSoItWontBeCollected);
    BlockSupport blockSupport = BlockSupport.getInstance(file.getProject());
    final DiffLog diffLog =
        blockSupport.reparseRange(file, startOffset, endOffset, lengthShift, chars, indicator);

    return new Processor<Document>() {
      @Override
      public boolean process(Document document) {
        ApplicationManager.getApplication().assertWriteAccessAllowed();
        log(
            "Finishing",
            document,
            synchronously,
            document.getModificationStamp(),
            startDocModificationTimeStamp);
        // if (file.getModificationStamp() != startPsiModificationTimeStamp) return; // optimistic
        // locking failed
        if (document.getModificationStamp() != startDocModificationTimeStamp) {
          return false; // optimistic locking failed
        }

        try {
          textBlock.performAtomically(
              new Runnable() {
                @Override
                public void run() {
                  CodeStyleManager.getInstance(file.getProject())
                      .performActionWithFormatterDisabled(
                          new Runnable() {
                            @Override
                            public void run() {
                              synchronized (PsiLock.LOCK) {
                                doActualPsiChange(file, diffLog);
                              }
                            }
                          });
                }
              });

          assertAfterCommit(
              document, file, oldPsiText, myTreeElementBeingReparsedSoItWontBeCollected);
        } finally {
          textBlock.clear();
          SmartPointerManagerImpl.synchronizePointers(file);
        }

        return true;
      }
    };
  }
 public static void clearCachedInjectedFragmentsForFile(@NotNull PsiFile file) {
   file.putUserData(INJECTED_DOCS_KEY, null);
 }
  QuickEditHandler(
      Project project,
      @NotNull PsiFile injectedFile,
      final PsiFile origFile,
      Editor editor,
      QuickEditAction action) {
    myProject = project;
    myEditor = editor;
    myAction = action;
    myOrigDocument = editor.getDocument();
    Place shreds = InjectedLanguageUtil.getShreds(injectedFile);
    FileType fileType = injectedFile.getFileType();
    Language language = injectedFile.getLanguage();
    PsiLanguageInjectionHost.Shred firstShred = ContainerUtil.getFirstItem(shreds);

    PsiFileFactory factory = PsiFileFactory.getInstance(project);
    String text = InjectedLanguageManager.getInstance(project).getUnescapedText(injectedFile);
    String newFileName =
        StringUtil.notNullize(language.getDisplayName(), "Injected")
            + " Fragment "
            + "("
            + origFile.getName()
            + ":"
            + firstShred.getHost().getTextRange().getStartOffset()
            + ")"
            + "."
            + fileType.getDefaultExtension();

    // preserve \r\n as it is done in MultiHostRegistrarImpl
    myNewFile = factory.createFileFromText(newFileName, language, text, true, false);
    myNewVirtualFile = ObjectUtils.assertNotNull((LightVirtualFile) myNewFile.getVirtualFile());
    myNewVirtualFile.setOriginalFile(origFile.getVirtualFile());

    assert myNewFile != null : "PSI file is null";
    assert myNewFile.getTextLength() == myNewVirtualFile.getContent().length()
        : "PSI / Virtual file text mismatch";

    myNewVirtualFile.setOriginalFile(origFile.getVirtualFile());
    // suppress possible errors as in injected mode
    myNewFile.putUserData(
        InjectedLanguageUtil.FRANKENSTEIN_INJECTION,
        injectedFile.getUserData(InjectedLanguageUtil.FRANKENSTEIN_INJECTION));
    myNewFile.putUserData(FileContextUtil.INJECTED_IN_ELEMENT, shreds.getHostPointer());
    myNewDocument = PsiDocumentManager.getInstance(project).getDocument(myNewFile);
    assert myNewDocument != null;
    EditorActionManager.getInstance()
        .setReadonlyFragmentModificationHandler(myNewDocument, new MyQuietHandler());
    myOrigCreationStamp =
        myOrigDocument.getModificationStamp(); // store creation stamp for UNDO tracking
    myOrigDocument.addDocumentListener(this, this);
    myNewDocument.addDocumentListener(this, this);
    EditorFactory editorFactory = ObjectUtils.assertNotNull(EditorFactory.getInstance());
    // not FileEditorManager listener because of RegExp checker and alike
    editorFactory.addEditorFactoryListener(
        new EditorFactoryAdapter() {
          int useCount;

          @Override
          public void editorCreated(@NotNull EditorFactoryEvent event) {
            if (event.getEditor().getDocument() != myNewDocument) return;
            useCount++;
          }

          @Override
          public void editorReleased(@NotNull EditorFactoryEvent event) {
            if (event.getEditor().getDocument() != myNewDocument) return;
            if (--useCount > 0) return;
            if (Boolean.TRUE.equals(
                myNewVirtualFile.getUserData(FileEditorManagerImpl.CLOSING_TO_REOPEN))) return;

            Disposer.dispose(QuickEditHandler.this);
          }
        },
        this);

    if ("JAVA".equals(firstShred.getHost().getLanguage().getID())) {
      PsiLanguageInjectionHost.Shred lastShred = ContainerUtil.getLastItem(shreds);
      myAltFullRange =
          myOrigDocument.createRangeMarker(
              firstShred.getHostRangeMarker().getStartOffset(),
              lastShred.getHostRangeMarker().getEndOffset());
      myAltFullRange.setGreedyToLeft(true);
      myAltFullRange.setGreedyToRight(true);

      initGuardedBlocks(shreds);
      myInjectedFile = null;
    } else {
      initMarkers(shreds);
      myAltFullRange = null;
      myInjectedFile = injectedFile;
    }
  }
 public static void installProvider(
     @NotNull PsiFile psiFile, @NotNull TextCompletionProvider provider, boolean autoPopup) {
   psiFile.putUserData(COMPLETING_TEXT_FIELD_KEY, provider);
   psiFile.putUserData(AUTO_POPUP_KEY, autoPopup);
 }
  public final void invokeCompletion(
      @NotNull final Project project,
      @NotNull final Editor editor,
      int time,
      boolean hasModifiers,
      boolean restarted) {
    final PsiFile psiFile = PsiUtilBase.getPsiFileInEditor(editor, project);
    assert psiFile != null
        : "no PSI file: " + FileDocumentManager.getInstance().getFile(editor.getDocument());

    checkNoWriteAccess();

    CompletionAssertions.checkEditorValid(editor);

    if (editor.isViewer()) {
      editor.getDocument().fireReadOnlyModificationAttempt();
      return;
    }

    if (invokedExplicitly) {
      CompletionLookupArranger.applyLastCompletionStatisticsUpdate();
    }

    if (!CodeInsightUtilBase.prepareEditorForWrite(editor)
        || !FileDocumentManager.getInstance().requestWriting(editor.getDocument(), project)) {
      return;
    }

    psiFile.putUserData(PsiFileEx.BATCH_REFERENCE_PROCESSING, Boolean.TRUE);

    CompletionPhase phase = CompletionServiceImpl.getCompletionPhase();
    boolean repeated =
        phase.indicator != null && phase.indicator.isRepeatedInvocation(myCompletionType, editor);
    /*
    if (repeated && isAutocompleteCommonPrefixOnInvocation() && phase.fillInCommonPrefix()) {
      return;
    }
    */

    int newTime = phase.newCompletionStarted(time, repeated);
    if (invokedExplicitly) {
      time = newTime;
    }
    if (CompletionServiceImpl.isPhase(CompletionPhase.InsertedSingleItem.class)) {
      CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
    }
    CompletionServiceImpl.assertPhase(
        CompletionPhase.NoCompletion.getClass(), CompletionPhase.CommittingDocuments.class);

    if (time > 1) {
      if (myCompletionType == CompletionType.BASIC) {
        FeatureUsageTracker.getInstance()
            .triggerFeatureUsed(CodeCompletionFeatures.SECOND_BASIC_COMPLETION);
      }
    }

    final CompletionInitializationContext[] initializationContext = {null};

    Runnable initCmd =
        new Runnable() {
          @Override
          public void run() {
            Runnable runnable =
                new Runnable() {
                  @Override
                  public void run() {
                    EditorUtil.fillVirtualSpaceUntilCaret(editor);
                    PsiDocumentManager.getInstance(project).commitAllDocuments();

                    CompletionAssertions.assertCommitSuccessful(editor, psiFile);
                    CompletionAssertions.checkEditorValid(editor);

                    initializationContext[0] = runContributorsBeforeCompletion(editor, psiFile);
                  }
                };
            ApplicationManager.getApplication().runWriteAction(runnable);
          }
        };
    if (autopopup) {
      CommandProcessor.getInstance().runUndoTransparentAction(initCmd);
      if (!restarted && shouldSkipAutoPopup(editor, psiFile)) {
        CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
        return;
      }
    } else {
      CommandProcessor.getInstance().executeCommand(project, initCmd, null, null);
    }

    insertDummyIdentifier(initializationContext[0], hasModifiers, time);
  }