private void doReplaceString(int startOffset, int endOffset, CharSequence s) {
    assert intersectWithEditable(new TextRange(startOffset, startOffset)) != null;
    assert intersectWithEditable(new TextRange(endOffset, endOffset)) != null;

    List<Pair<TextRange, CharSequence>> hostRangesToModify;
    synchronized (myLock) {
      hostRangesToModify = new ArrayList<Pair<TextRange, CharSequence>>(myShreds.size());

      int offset = startOffset;
      int curRangeStart = 0;
      for (int i = 0; i < myShreds.size(); i++) {
        PsiLanguageInjectionHost.Shred shred = myShreds.get(i);
        curRangeStart += shred.getPrefix().length();
        if (offset < curRangeStart) offset = curRangeStart;
        Segment hostRange = shred.getHostRangeMarker();
        if (hostRange == null) continue;
        int hostRangeLength = hostRange.getEndOffset() - hostRange.getStartOffset();
        TextRange range = TextRange.from(curRangeStart, hostRangeLength);
        if (range.contains(offset)
            || range.getEndOffset() == offset /* in case of inserting at the end*/) {
          TextRange rangeToModify =
              new TextRange(offset, Math.min(range.getEndOffset(), endOffset));
          TextRange hostRangeToModify =
              rangeToModify.shiftRight(hostRange.getStartOffset() - curRangeStart);
          CharSequence toReplace =
              i == myShreds.size() - 1
                      || range.getEndOffset() + shred.getSuffix().length() >= endOffset
                  ? s
                  : s.subSequence(0, Math.min(hostRangeToModify.getLength(), s.length()));
          s = toReplace == s ? "" : s.subSequence(toReplace.length(), s.length());
          hostRangesToModify.add(Pair.create(hostRangeToModify, toReplace));
          offset = rangeToModify.getEndOffset();
        }
        curRangeStart += hostRangeLength;
        curRangeStart += shred.getSuffix().length();
        if (curRangeStart > endOffset) break;
      }
    }

    int delta = 0;
    for (Pair<TextRange, CharSequence> pair : hostRangesToModify) {
      TextRange hostRange = pair.getFirst();
      CharSequence replace = pair.getSecond();

      myDelegate.replaceString(
          hostRange.getStartOffset() + delta, hostRange.getEndOffset() + delta, replace);
      delta -= hostRange.getLength() - replace.length();
    }
  }
 @Override
 @Deprecated
 @Nullable
 public TextRange intersectWithEditable(@NotNull TextRange rangeToEdit) {
   int startOffset = -1;
   int endOffset = -1;
   synchronized (myLock) {
     int offset = 0;
     for (PsiLanguageInjectionHost.Shred shred : myShreds) {
       Segment hostRange = shred.getHostRangeMarker();
       if (hostRange == null) continue;
       offset += shred.getPrefix().length();
       int length = hostRange.getEndOffset() - hostRange.getStartOffset();
       TextRange intersection =
           new ProperTextRange(offset, offset + length).intersection(rangeToEdit);
       if (intersection != null) {
         if (startOffset == -1) {
           startOffset = intersection.getStartOffset();
         }
         endOffset = intersection.getEndOffset();
       }
       offset += length;
       offset += shred.getSuffix().length();
     }
   }
   if (startOffset == -1) return null;
   return new ProperTextRange(startOffset, endOffset);
 }
  @Override
  public int getLineStartOffset(int line) {
    LOG.assertTrue(line >= 0, line);
    if (line == 0) return 0;
    String hostText = myDelegate.getText();

    int[] pos = new int[2]; // pos[0] = curLine; pos[1] == offset;
    synchronized (myLock) {
      for (PsiLanguageInjectionHost.Shred shred : myShreds) {
        Segment hostRange = shred.getHostRangeMarker();
        if (hostRange == null) continue;

        int found = countNewLinesIn(shred.getPrefix(), pos, line);
        if (found != -1) return found;

        CharSequence text =
            hostText.subSequence(hostRange.getStartOffset(), hostRange.getEndOffset());
        found = countNewLinesIn(text, pos, line);
        if (found != -1) return found;

        found = countNewLinesIn(shred.getSuffix(), pos, line);
        if (found != -1) return found;
      }
    }

    return pos[1];
  }
 private void initGuardedBlocks(Place shreds) {
   int origOffset = -1;
   int curOffset = 0;
   for (PsiLanguageInjectionHost.Shred shred : shreds) {
     Segment hostRangeMarker = shred.getHostRangeMarker();
     int start = shred.getRange().getStartOffset() + shred.getPrefix().length();
     int end = shred.getRange().getEndOffset() - shred.getSuffix().length();
     if (curOffset < start) {
       RangeMarker guard = myNewDocument.createGuardedBlock(curOffset, start);
       if (curOffset == 0 && shred == shreds.get(0)) guard.setGreedyToLeft(true);
       String padding =
           origOffset < 0
               ? ""
               : myOrigDocument.getText().substring(origOffset, hostRangeMarker.getStartOffset());
       guard.putUserData(REPLACEMENT_KEY, fixQuotes(padding));
     }
     curOffset = end;
     origOffset = hostRangeMarker.getEndOffset();
   }
   if (curOffset < myNewDocument.getTextLength()) {
     RangeMarker guard =
         myNewDocument.createGuardedBlock(curOffset, myNewDocument.getTextLength());
     guard.setGreedyToRight(true);
     guard.putUserData(REPLACEMENT_KEY, "");
   }
 }
  public void initMarkers(Place shreds) {
    SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(myProject);
    int curOffset = -1;
    for (PsiLanguageInjectionHost.Shred shred : shreds) {
      final RangeMarker rangeMarker =
          myNewDocument.createRangeMarker(
              shred.getRange().getStartOffset() + shred.getPrefix().length(),
              shred.getRange().getEndOffset() - shred.getSuffix().length());
      final TextRange rangeInsideHost = shred.getRangeInsideHost();
      PsiLanguageInjectionHost host = shred.getHost();
      RangeMarker origMarker =
          myOrigDocument.createRangeMarker(
              rangeInsideHost.shiftRight(host.getTextRange().getStartOffset()));
      SmartPsiElementPointer<PsiLanguageInjectionHost> elementPointer =
          smartPointerManager.createSmartPsiElementPointer(host);
      Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer> markers =
          Trinity.<RangeMarker, RangeMarker, SmartPsiElementPointer>create(
              origMarker, rangeMarker, elementPointer);
      myMarkers.add(markers);

      origMarker.setGreedyToRight(true);
      rangeMarker.setGreedyToRight(true);
      if (origMarker.getStartOffset() > curOffset) {
        origMarker.setGreedyToLeft(true);
        rangeMarker.setGreedyToLeft(true);
      }
      curOffset = origMarker.getEndOffset();
    }
    initGuardedBlocks(shreds);
  }
  @Override
  public boolean areRangesEqual(@NotNull DocumentWindow otherd) {
    DocumentWindowImpl window = (DocumentWindowImpl) otherd;
    Place shreds = getShreds();
    Place otherShreds = window.getShreds();
    if (shreds.size() != otherShreds.size()) return false;
    for (int i = 0; i < shreds.size(); i++) {
      PsiLanguageInjectionHost.Shred shred = shreds.get(i);
      PsiLanguageInjectionHost.Shred otherShred = otherShreds.get(i);
      if (!shred.getPrefix().equals(otherShred.getPrefix())) return false;
      if (!shred.getSuffix().equals(otherShred.getSuffix())) return false;

      Segment hostRange = shred.getHostRangeMarker();
      Segment other = otherShred.getHostRangeMarker();
      if (hostRange == null
          || other == null
          || hostRange.getStartOffset() != other.getStartOffset()) return false;
      if (hostRange.getEndOffset() != other.getEndOffset()) return false;
    }
    return true;
  }
 private String getRangeText(@NotNull String hostText, int hostNum) {
   synchronized (myLock) {
     PsiLanguageInjectionHost.Shred shred = myShreds.get(hostNum);
     Segment hostRangeMarker = shred.getHostRangeMarker();
     return shred.getPrefix()
         + (hostRangeMarker == null
             ? ""
             : hostText.substring(
                 hostRangeMarker.getStartOffset(), hostRangeMarker.getEndOffset()))
         + shred.getSuffix();
   }
 }
 @Override
 public int getTextLength() {
   int length = 0;
   synchronized (myLock) {
     for (PsiLanguageInjectionHost.Shred shred : myShreds) {
       Segment hostRange = shred.getHostRangeMarker();
       if (hostRange == null) continue;
       length += shred.getPrefix().length();
       length += hostRange.getEndOffset() - hostRange.getStartOffset();
       length += shred.getSuffix().length();
     }
   }
   return length;
 }
 @NotNull
 private String calcText() {
   StringBuilder text = new StringBuilder();
   CharSequence hostText = myDelegate.getCharsSequence();
   synchronized (myLock) {
     for (PsiLanguageInjectionHost.Shred shred : myShreds) {
       Segment hostRange = shred.getHostRangeMarker();
       if (hostRange != null) {
         text.append(shred.getPrefix());
         text.append(hostText, hostRange.getStartOffset(), hostRange.getEndOffset());
         text.append(shred.getSuffix());
       }
     }
   }
   return text.toString();
 }
  @NotNull
  @Override
  public List<TextRange> getNonEditableFragments(@NotNull DocumentWindow window) {
    List<TextRange> result = ContainerUtil.newArrayList();
    int offset = 0;
    for (PsiLanguageInjectionHost.Shred shred : ((DocumentWindowImpl) window).getShreds()) {
      Segment hostRange = shred.getHostRangeMarker();
      if (hostRange == null) continue;

      offset = appendRange(result, offset, shred.getPrefix().length());
      offset += hostRange.getEndOffset() - hostRange.getStartOffset();
      offset = appendRange(result, offset, shred.getSuffix().length());
    }

    return result;
  }
 /**
  * intersection may spread over several injected fragments
  *
  * @param rangeToEdit range in encoded(raw) PSI
  * @return list of ranges in encoded (raw) PSI
  */
 @Override
 @SuppressWarnings({"ConstantConditions", "unchecked"})
 @NotNull
 public List<TextRange> intersectWithAllEditableFragments(
     @NotNull PsiFile injectedPsi, @NotNull TextRange rangeToEdit) {
   Place shreds = InjectedLanguageUtil.getShreds(injectedPsi);
   if (shreds == null) return Collections.emptyList();
   Object result = null; // optimization: TextRange or ArrayList
   int count = 0;
   int offset = 0;
   for (PsiLanguageInjectionHost.Shred shred : shreds) {
     TextRange encodedRange =
         TextRange.from(
             offset + shred.getPrefix().length(), shred.getRangeInsideHost().getLength());
     TextRange intersection = encodedRange.intersection(rangeToEdit);
     if (intersection != null) {
       count++;
       if (count == 1) {
         result = intersection;
       } else if (count == 2) {
         TextRange range = (TextRange) result;
         if (range.isEmpty()) {
           result = intersection;
           count = 1;
         } else if (intersection.isEmpty()) {
           count = 1;
         } else {
           List<TextRange> list = new ArrayList<TextRange>();
           list.add(range);
           list.add(intersection);
           result = list;
         }
       } else if (intersection.isEmpty()) {
         count--;
       } else {
         ((List<TextRange>) result).add(intersection);
       }
     }
     offset +=
         shred.getPrefix().length()
             + shred.getRangeInsideHost().getLength()
             + shred.getSuffix().length();
   }
   return count == 0
       ? Collections.<TextRange>emptyList()
       : count == 1 ? Collections.singletonList((TextRange) result) : (List<TextRange>) result;
 }
  @Override
  public int getLineNumber(int offset) {
    int lineNumber = 0;
    String hostText = myDelegate.getText();
    synchronized (myLock) {
      for (PsiLanguageInjectionHost.Shred shred : myShreds) {
        String prefix = shred.getPrefix();
        String suffix = shred.getSuffix();
        lineNumber +=
            StringUtil.getLineBreakCount(prefix.substring(0, Math.min(offset, prefix.length())));
        if (offset < prefix.length()) {
          return lineNumber;
        }
        offset -= prefix.length();

        Segment currentRange = shred.getHostRangeMarker();
        if (currentRange == null) continue;
        int rangeLength = currentRange.getEndOffset() - currentRange.getStartOffset();
        CharSequence rangeText =
            hostText.subSequence(currentRange.getStartOffset(), currentRange.getEndOffset());

        lineNumber +=
            StringUtil.getLineBreakCount(rangeText.subSequence(0, Math.min(offset, rangeLength)));
        if (offset < rangeLength) {
          return lineNumber;
        }
        offset -= rangeLength;

        lineNumber +=
            StringUtil.getLineBreakCount(suffix.substring(0, Math.min(offset, suffix.length())));
        if (offset < suffix.length()) {
          return lineNumber;
        }

        offset -= suffix.length();
      }
    }
    lineNumber = getLineCount() - 1;
    return lineNumber < 0 ? 0 : lineNumber;
  }
  @Override
  public void deleteString(final int startOffset, final int endOffset) {
    assert intersectWithEditable(new TextRange(startOffset, startOffset)) != null;
    assert intersectWithEditable(new TextRange(endOffset, endOffset)) != null;

    List<TextRange> hostRangesToDelete;
    synchronized (myLock) {
      hostRangesToDelete = new ArrayList<TextRange>(myShreds.size());

      int offset = startOffset;
      int curRangeStart = 0;
      for (PsiLanguageInjectionHost.Shred shred : myShreds) {
        curRangeStart += shred.getPrefix().length();
        if (offset < curRangeStart) offset = curRangeStart;
        if (offset >= endOffset) break;
        Segment hostRange = shred.getHostRangeMarker();
        if (hostRange == null) continue;
        int hostRangeLength = hostRange.getEndOffset() - hostRange.getStartOffset();
        TextRange range = TextRange.from(curRangeStart, hostRangeLength);
        if (range.contains(offset)) {
          TextRange rangeToDelete =
              new TextRange(offset, Math.min(range.getEndOffset(), endOffset));
          hostRangesToDelete.add(
              rangeToDelete.shiftRight(hostRange.getStartOffset() - curRangeStart));
          offset = rangeToDelete.getEndOffset();
        }
        curRangeStart += hostRangeLength;
        curRangeStart += shred.getSuffix().length();
      }
    }

    int delta = 0;
    for (TextRange hostRangeToDelete : hostRangesToDelete) {
      myDelegate.deleteString(
          hostRangeToDelete.getStartOffset() + delta, hostRangeToDelete.getEndOffset() + delta);
      delta -= hostRangeToDelete.getLength();
    }
  }