public static void replaceWhiteSpace(final String whiteSpace,
                                       final ASTNode leafElement,
                                       final IElementType whiteSpaceToken,
                                       final @Nullable TextRange textRange) {
    final CharTable charTable = SharedImplUtil.findCharTableByTree(leafElement);

    if (textRange != null && textRange.getStartOffset() > leafElement.getTextRange().getStartOffset() &&
        textRange.getEndOffset() < leafElement.getTextRange().getEndOffset()) {
      replaceInnerWhiteSpace(whiteSpace, leafElement, textRange);
      return;
    }

    ASTNode treePrev = findPreviousWhiteSpace(leafElement, whiteSpaceToken);
    if (treePrev == null) {
      treePrev = getWsCandidate(leafElement);
    }

    if (treePrev != null &&
        treePrev.getText().trim().isEmpty() &&
        treePrev.getElementType() != whiteSpaceToken &&
        treePrev.getTextLength() > 0 &&
        !whiteSpace.isEmpty()) {
      LeafElement whiteSpaceElement =
        Factory.createSingleLeafElement(treePrev.getElementType(), whiteSpace, charTable, SharedImplUtil.getManagerByTree(leafElement));

      ASTNode treeParent = treePrev.getTreeParent();
      treeParent.replaceChild(treePrev, whiteSpaceElement);
    }
    else {
      LeafElement whiteSpaceElement =
        Factory.createSingleLeafElement(whiteSpaceToken, whiteSpace, charTable, SharedImplUtil.getManagerByTree(leafElement));

      if (treePrev == null) {
        if (!whiteSpace.isEmpty()) {
          addWhiteSpace(leafElement, whiteSpaceElement);
        }
      }
      else {
        if (!(treePrev.getElementType() == whiteSpaceToken)) {
          if (!whiteSpace.isEmpty()) {
            addWhiteSpace(treePrev, whiteSpaceElement);
          }
        }
        else {
          if (treePrev.getElementType() == whiteSpaceToken) {
            final CompositeElement treeParent = (CompositeElement)treePrev.getTreeParent();
            if (!whiteSpace.isEmpty()) {
              //          LOG.assertTrue(textRange == null || treeParent.getTextRange().equals(textRange));
              treeParent.replaceChild(treePrev, whiteSpaceElement);
            }
            else {
              treeParent.removeChild(treePrev);
            }

            // There is a possible case that more than one PSI element is matched by the target text range.
            // That is the case, for example, for Python's multi-line expression. It may looks like below:
            //     import contextlib,\
            //       math, decimal
            // Here single range contains two blocks: '\' & '\n  '. So, we may want to replace that range to another text, hence,
            // we replace last element located there with it ('\n  ') and want to remove any remaining elements ('\').
            ASTNode removeCandidate = findPreviousWhiteSpace(whiteSpaceElement, whiteSpaceToken);
            while (textRange != null && removeCandidate != null && removeCandidate.getStartOffset() >= textRange.getStartOffset()) {
              treePrev = findPreviousWhiteSpace(removeCandidate, whiteSpaceToken);
              removeCandidate.getTreeParent().removeChild(removeCandidate);
              removeCandidate = treePrev;
            }
            //treeParent.subtreeChanged();
          }
        }
      }
    }
  }
public static void replaceWhiteSpace(final String whiteSpace,
                                       final ASTNode leafElement,
                                       final IElementType whiteSpaceToken,
                                       final @Nullable TextRange textRange)