public static @Nullable Couple<ASTNode> findReparseableRoots(@NotNull PsiFileImpl file,
                                                     @NotNull FileASTNode oldFileNode,
                                                     @NotNull TextRange changedPsiRange,
                                                     @NotNull CharSequence newFileText) {
    CharTable charTable = oldFileNode.getCharTable();
    int lengthShift = newFileText.length() - oldFileNode.getTextLength();

    if (isTooDeep(file)) {
      return null;
    }

    boolean isTemplateFile = oldFileNode.getElementType() instanceof ITemplateDataElementType;

    ASTNode leafAtStart = oldFileNode.findLeafElementAt(Math.max(0, changedPsiRange.getStartOffset() - 1));
    ASTNode leafAtEnd = oldFileNode.findLeafElementAt(Math.min(changedPsiRange.getEndOffset(), oldFileNode.getTextLength() - 1));
    ASTNode node = leafAtStart != null && leafAtEnd != null ? TreeUtil.findCommonParent(leafAtStart, leafAtEnd) : oldFileNode;
    Language baseLanguage = file.getViewProvider().getBaseLanguage();

    Function<ASTNode, Couple<ASTNode>> reparseNodeFunction = astNode -> {
      IElementType elementType = astNode.getElementType();
      if (elementType instanceof IReparseableElementTypeBase || elementType instanceof IReparseableLeafElementType) {
        TextRange textRange = astNode.getTextRange();

        if (textRange.getLength() + lengthShift > 0 &&
            (baseLanguage.isKindOf(elementType.getLanguage()) || elementType instanceof IReparseableLeafElementType ||
             !TreeUtil.containsOuterLanguageElements(astNode))) {
          int start = textRange.getStartOffset();
          int end = start + textRange.getLength() + lengthShift;
          if (end > newFileText.length()) {
            reportInconsistentLength(file, newFileText, astNode, start, end);
            return Couple.of(null, null);
          }

          CharSequence newTextStr = newFileText.subSequence(start, end);

          ASTNode newNode;
          if (elementType instanceof IReparseableElementTypeBase) {
            newNode =
              tryReparseNode((IReparseableElementTypeBase)elementType, astNode, newTextStr, file.getManager(), baseLanguage, charTable);
          }
          else {
            newNode = tryReparseLeaf((IReparseableLeafElementType)elementType, astNode, newTextStr);
          }

          if (newNode != null) {
            if (newNode.getTextLength() != newTextStr.length()) {
              String details = ApplicationManager.getApplication().isInternal()
                               ? "text=" + newTextStr + "; treeText=" + newNode.getText() + ";"
                               : "";
              LOG.error("Inconsistent reparse: " + details + " type=" + elementType);
            }

            return Couple.of(astNode, newNode);
          }
        }
      }
      return null;
    };

    TextRange startLeafRange = leafAtStart == null ? null : leafAtStart.getTextRange();
    TextRange endLeafRange = leafAtEnd == null ? null : leafAtEnd.getTextRange();

    IElementType startLeafType = PsiUtilCore.getElementType(leafAtStart);
    if (startLeafType instanceof IReparseableLeafElementType &&
        startLeafRange.getEndOffset() == changedPsiRange.getEndOffset() &&
        (!isTemplateFile || startLeafType instanceof OuterLanguageElementType)) {
      Couple<ASTNode> reparseResult = reparseNodeFunction.apply(leafAtStart);
      if (reparseResult != null && reparseResult.first != null) {
        return reparseResult;
      }
    }
    IElementType endLeafType = PsiUtilCore.getElementType(leafAtEnd);
    if (endLeafType instanceof IReparseableLeafElementType &&
        endLeafRange.getStartOffset() == changedPsiRange.getStartOffset() &&
        (!isTemplateFile || endLeafType instanceof OuterLanguageElementType)) {
      Couple<ASTNode> reparseResult = reparseNodeFunction.apply(leafAtEnd);
      if (reparseResult != null && reparseResult.first != null) {
        return reparseResult;
      }
    }

    while (node != null && !(node instanceof FileElement)) {
      if (isTemplateFile && !(PsiUtilCore.getElementType(node) instanceof OuterLanguageElementType)) {
        break;
      }
      Couple<ASTNode> couple = reparseNodeFunction.apply(node);
      if (couple != null) {
        if (couple.first == null) {
          break;
        }
        return couple;
      }
      node = node.getTreeParent();
    }
    return null;
  }
public static @Nullable Couple<ASTNode> findReparseableRoots(@NotNull PsiFileImpl file,
                                                     @NotNull FileASTNode oldFileNode,
                                                     @NotNull TextRange changedPsiRange,
                                                     @NotNull CharSequence newFileText)