public static List<Block> mergeBlocks(@NotNull List<? extends TemplateLanguageBlock> tlBlocks, @NotNull List<DataLanguageBlockWrapper> foreignBlocks) {
    ArrayList<Block> result = new ArrayList<>(tlBlocks.size() + foreignBlocks.size());
    int vInd = 0;
    int fInd = 0;
    while (vInd < tlBlocks.size() && fInd < foreignBlocks.size()) {
      final TemplateLanguageBlock v = tlBlocks.get(vInd);
      final DataLanguageBlockWrapper f = foreignBlocks.get(fInd);
      final TextRange vRange = v.getTextRange();
      final TextRange fRange = f.getTextRange();
      if (vRange.getStartOffset() >= fRange.getEndOffset()) {
        // add leading foreign blocks
        result.add(f);
        fInd++;
      }
      else if (vRange.getEndOffset() <= fRange.getStartOffset()) {
        // add leading TL blocks
        result.add(v);
        vInd++;
      }
      else if (vRange.getStartOffset() < fRange.getStartOffset() ||
               vRange.getStartOffset() == fRange.getStartOffset() && vRange.getEndOffset() >= fRange.getEndOffset()) {
        // add including TL blocks and split intersecting foreign blocks
        result.add(v);
        while (fInd < foreignBlocks.size() && vRange.contains(foreignBlocks.get(fInd).getTextRange())) {
          v.addForeignChild(foreignBlocks.get(fInd++));
        }
        if (fInd < foreignBlocks.size()) {
          final DataLanguageBlockWrapper notContainedF = foreignBlocks.get(fInd);
          if (vRange.intersectsStrict(notContainedF.getTextRange())) {
            Pair<List<DataLanguageBlockWrapper>, List<DataLanguageBlockWrapper>> splitBlocks = splitBlocksByRightBound(notContainedF.getOriginal(), vRange);
            v.addForeignChildren(splitBlocks.getFirst());
            foreignBlocks.remove(fInd);
            if (splitBlocks.getSecond().size() > 0) {
              foreignBlocks.addAll(fInd, splitBlocks.getSecond());
            }
          }
        }
        vInd++;
      }
      else {
        // add including foreign blocks or split them if needed
        int lastContainedTlInd = vInd;
        while (lastContainedTlInd < tlBlocks.size() && fRange.intersectsStrict(tlBlocks.get(lastContainedTlInd).getTextRange())) {
          lastContainedTlInd++;
        }
        if (fRange.contains(tlBlocks.get(lastContainedTlInd - 1).getTextRange())) {
          result.add(f);
          fInd++;
          while (vInd < lastContainedTlInd) {
            f.addTlChild(tlBlocks.get(vInd++));
          }
        }
        else {
          Block original = f.getOriginal();
          if (!original.getSubBlocks().isEmpty()) {
            foreignBlocks.remove(fInd);
            foreignBlocks.addAll(fInd, buildChildWrappers(original));
          } else {
            result.add(new ErrorLeafBlock(f.getTextRange().getStartOffset(), getEndOffset(tlBlocks, foreignBlocks)));
            return result;
          }
        }
      }
    }
    while (vInd < tlBlocks.size()) {
      result.add(tlBlocks.get(vInd++));
    }
    while (fInd < foreignBlocks.size()) {
      result.add(foreignBlocks.get(fInd++));
    }
    return result;
  }
public static List<Block> mergeBlocks(@NotNull List<? extends TemplateLanguageBlock> tlBlocks, @NotNull List<DataLanguageBlockWrapper> foreignBlocks)