static BooleanRunnable reparse(@NotNull Language language,
                                 @NotNull DocumentWindowImpl oldDocumentWindow,
                                 @NotNull PsiFile oldInjectedPsi,
                                 @NotNull VirtualFileWindow oldInjectedVirtualFile,
                                 @NotNull VirtualFile hostVirtualFile,
                                 @NotNull PsiFile hostPsiFile,
                                 @NotNull DocumentEx hostDocument,
                                 @NotNull ProgressIndicator indicator,
                                 @NotNull ASTNode oldRoot,
                                 @NotNull ASTNode newRoot,
                                 @NotNull PsiDocumentManagerBase documentManager) {
    Project project = hostPsiFile.getProject();
    String newText = oldDocumentWindow.getText();
    FileASTNode oldNode = oldInjectedPsi.getNode();
    InjectedFileViewProvider oldInjectedPsiViewProvider = (InjectedFileViewProvider)oldInjectedPsi.getViewProvider();
    String oldPsiText = oldNode.getText();
    if (newText.equals(oldPsiText)) return ()->true;
    if (oldDocumentWindow.isOneLine() && newText.contains("\n") != oldPsiText.contains("\n")) {
      // one-lineness changed, e.g. when enter pressed in the middle of a string literal
      return null;
    }
    Place oldPlace = oldDocumentWindow.getShreds();
    // can be different from newText if decode fails in the middle and we'll have to shrink the document
    StringBuilder newDocumentText = new StringBuilder(newText.length());
    // we need escaper, but it only works with committed PSI,
    // so we get the committed (but not yet applied) PSI from the commit-document-in-the-background process
    // and find the corresponding injection host there
    // and create literal escaper from that new (dummy) psi
    List<PlaceInfo> placeInfos = new SmartList<>();
    StringBuilder chars = new StringBuilder();
    for (PsiLanguageInjectionHost.Shred shred : oldPlace) {
      PsiLanguageInjectionHost oldHost = shred.getHost();
      if (oldHost == null) {
        return null;
      }
      SmartPsiElementPointer<PsiLanguageInjectionHost> hostPointer = ((ShredImpl)shred).getSmartPointer();
      Segment hostPsiRange = hostPointer.getPsiRange();
      Segment newInjectionHostRange = SelfElementInfo.calcActualRangeAfterDocumentEvents(hostPsiFile, hostDocument, hostPsiRange, true);
      if (newInjectionHostRange == null) {
        return null;
      }
      PsiLanguageInjectionHost newDummyInjectionHost = findNewInjectionHost(hostPsiFile, oldRoot, newRoot, oldHost, newInjectionHostRange);
      if (newDummyInjectionHost == null) {
        return null;
      }
      newInjectionHostRange = newDummyInjectionHost.getTextRange().shiftRight(oldRoot.getTextRange().getStartOffset());
      Segment hostInjectionRange = shred.getHostRangeMarker(); // in the new document
      if (hostInjectionRange == null) {
        return null;
      }
      TextRange rangeInsideHost = TextRange.create(hostInjectionRange).shiftLeft(newInjectionHostRange.getStartOffset());

      PlaceInfo info = new PlaceInfo(shred.getPrefix(), shred.getSuffix(), newDummyInjectionHost, rangeInsideHost);
      placeInfos.add(info);
      info.newInjectionHostRange = newInjectionHostRange;

      decode(info, chars);

      // pass the old pointers because their offsets will be adjusted automatically (SmartPsiElementPointer does that)
      TextRange rangeInHostElementPSI = info.rangeInHostElement;

      newDocumentText.append(shred.getPrefix());
      newDocumentText.append(newDummyInjectionHost.getText(), rangeInHostElementPSI.getStartOffset(), rangeInHostElementPSI.getEndOffset());
      newDocumentText.append(shred.getSuffix());
    }
    // newDocumentText can be shorter if decode failed
    //assert newText.equals(newDocumentText.toString()) : "-\n"+newText+"\n--\n"+newDocumentText+"\n---\n";

    assert documentManager.isUncommited(hostDocument);
    String fileName = ((VirtualFileWindowImpl)oldInjectedVirtualFile).getName();
    ASTNode[] parsedNodes = parseFile(language, language, oldDocumentWindow,
                                   hostVirtualFile, hostDocument, hostPsiFile, project, newDocumentText, placeInfos, chars,
                                   fileName, documentManager);
    List<PsiFile> oldFiles = ((AbstractFileViewProvider)oldInjectedPsiViewProvider).getCachedPsiFiles();
    synchronized (InjectedLanguageManagerImpl.ourInjectionPsiLock) {
      DiffLog[] diffLogs = new DiffLog[parsedNodes.length];
      for (int i = 0; i < parsedNodes.length; i++) {
        ASTNode parsedNode = parsedNodes[i];
        PsiFile oldFile = oldFiles.get(i);
        diffLogs[i] = BlockSupportImpl.mergeTrees((PsiFileImpl)oldFile, oldFile.getNode(), parsedNode, indicator, oldPsiText);
      }

      return () -> {
        oldInjectedPsiViewProvider.performNonPhysically(() ->
          DebugUtil.performPsiModification("injected tree diff", () -> {
            for (int i = 0; i < diffLogs.length; i++) {
              DiffLog diffLog = diffLogs[i];
              diffLog.doActualPsiChange(oldFiles.get(i));

              // create new shreds after commit is complete because otherwise the range markers will be changed in MarkerCache.updateMarkers
              Place newPlace = new Place();
              for (int j = 0; j < oldPlace.size(); j++) {
                PsiLanguageInjectionHost.Shred shred = oldPlace.get(j);
                PlaceInfo info = placeInfos.get(j);
                TextRange rangeInDecodedPSI = info.rangeInDecodedPSI;
                TextRange rangeInHostElementPSI = info.rangeInHostElement;
                // now find the injection host in the newly committed file
                FileASTNode root = hostPsiFile.getNode();
                PsiLanguageInjectionHost newHost = findNewInjectionHost(hostPsiFile, root, root, info.host, info.newInjectionHostRange);
                ShredImpl newShred = ((ShredImpl)shred).withRange(rangeInDecodedPSI, rangeInHostElementPSI, newHost);
                newPlace.add(newShred);
              }

              cacheEverything(newPlace, oldDocumentWindow, oldInjectedPsiViewProvider, oldFiles.get(i));
              String docText = oldDocumentWindow.getText();
              assert docText.equals(newText) : "=\n" + docText + "\n==\n" + newDocumentText + "\n===\n";
            }
          })
        );
        return true;
      };
    }
  }
static BooleanRunnable reparse(@NotNull Language language,
                                 @NotNull DocumentWindowImpl oldDocumentWindow,
                                 @NotNull PsiFile oldInjectedPsi,
                                 @NotNull VirtualFileWindow oldInjectedVirtualFile,
                                 @NotNull VirtualFile hostVirtualFile,
                                 @NotNull PsiFile hostPsiFile,
                                 @NotNull DocumentEx hostDocument,
                                 @NotNull ProgressIndicator indicator,
                                 @NotNull ASTNode oldRoot,
                                 @NotNull ASTNode newRoot,
                                 @NotNull PsiDocumentManagerBase documentManager)