void insertOuterElementsAndRemoveRanges(@NotNull TreeElement templateFileElement,
                                          @NotNull CharSequence sourceCode,
                                          @NotNull CharTable charTable,
                                          @NotNull Language language) {
    TreePatcher templateTreePatcher = TemplateDataElementType.TREE_PATCHER.forLanguage(language);

    TreeElement currentLeafOrLazyParseable = findFirstSuitableElement(templateFileElement);

    // we use manual offset counter because node.getStartOffset() is expensive here
    // offset in original text
    int currentLeafOffset = 0;

    for (TextRange rangeToProcess : myOuterAndRemoveRanges) {
      int rangeStartOffset = rangeToProcess.getStartOffset();

      // search for leaf following or intersecting range
      while (currentLeafOrLazyParseable != null &&
             currentLeafOffset + currentLeafOrLazyParseable.getTextLength() <= rangeStartOffset) {
        currentLeafOffset += currentLeafOrLazyParseable.getTextLength();
        currentLeafOrLazyParseable = findNextSuitableElement(currentLeafOrLazyParseable);
      }

      if (rangeToProcess instanceof RangeToRemove) {
        if (currentLeafOrLazyParseable == null) {
          Logger.getInstance(RangeCollectorImpl.class).error(
            "RangeToRemove's range is out of original text bound",
            new Attachment("myOuterAndRemoveRanges", StringUtil.join(myOuterAndRemoveRanges, Object::toString, ", ")),
            new Attachment("rangeToProcess", rangeToProcess.toString()),
            new Attachment("sourceCode", sourceCode.toString()));
          continue;
        }

        if (currentLeafOffset > rangeToProcess.getStartOffset() ||
            currentLeafOffset + currentLeafOrLazyParseable.getTextLength() < rangeToProcess.getStartOffset()) {
          Logger.getInstance(RangeCollectorImpl.class).error("startLeaf doesn't contain rangeToRemove start offset");
          continue;
        }

        // don't modify currentLeafOffset as it stays the same as in original file because we are removing artificial insertion
        // currentLeafOrLazyParseable changes because leaf is changed and old one is invalidated
        currentLeafOrLazyParseable = removeElementsForRange(currentLeafOrLazyParseable, currentLeafOffset, rangeToProcess, charTable);
      }
      else {
        if (currentLeafOrLazyParseable instanceof LeafElement && currentLeafOffset < rangeStartOffset) {
          int splitOffset = rangeStartOffset - currentLeafOffset;
          currentLeafOrLazyParseable = templateTreePatcher.split((LeafElement)currentLeafOrLazyParseable, splitOffset, charTable);
          currentLeafOffset = rangeStartOffset;
        }
        if (currentLeafOrLazyParseable == null) {
          insertLastOuterElementForRange((CompositeElement)templateFileElement, rangeToProcess, sourceCode, charTable);
        }
        else {
          currentLeafOrLazyParseable =
            insertOuterElementFromRange(currentLeafOrLazyParseable, currentLeafOffset, rangeToProcess, sourceCode, templateTreePatcher,
                                        charTable);
          addRangeToLazyParseableCollector(currentLeafOrLazyParseable, rangeToProcess.shiftLeft(currentLeafOffset));
        }
      }
    }

    if (ApplicationManager.getApplication().isUnitTestMode()) {
      String after = templateFileElement.getText();
      assert after.contentEquals(sourceCode) :
        "Text presentation for the new tree must be the same: \nbefore: " + sourceCode + "\nafter: " + after;
    }
  }
void insertOuterElementsAndRemoveRanges(@NotNull TreeElement templateFileElement,
                                          @NotNull CharSequence sourceCode,
                                          @NotNull CharTable charTable,
                                          @NotNull Language language)